# Install and operations

This guide covers installing Coldtill on a Linux server, the first-run setup, and day-to-day operations:
backups, upgrades, monitoring and key rotation.

## Requirements

- A Linux server with 2 GB of RAM or more. Any distribution with Docker works; these commands are for
  Ubuntu, Debian and Linux Mint.
- A DNS name pointing at the server (for example `pay.example.com`), with ports 80 and 443 open. Caddy
  obtains the TLS certificate automatically.
- For mainnet: an SMTP account for payout approval emails, and RPC endpoints you trust (see
  [Nodes](#nodes-and-networks)).

Install Docker and the Compose plugin:

```bash
sudo apt update
sudo apt install docker.io docker-compose-v2 git
sudo usermod -aG docker $USER   # log out and back in afterwards
```

## Install

```bash
git clone git@github.com:rollounden/coldtill.git
cd coldtill/deploy
./install.sh --mode testnet --site-url https://pay.example.com
```

`install.sh` accepts `--mode testnet|mainnet`, `--site-url URL` and `--no-build` (use a prebuilt image set in
`COLDTILL_IMAGE`). It:

1. builds the `coldtill:local` image;
2. runs `coldtill init`, which asks for a hot-wallet passphrase (at least 12 characters) and writes:
   - `deploy/.env`: mode, site URL, the server secret, the database password and the age recipient;
   - `deploy/secrets/coldtill-identity.age`: the hot-wallet identity, encrypted under your passphrase;
   - `deploy/secrets/hot_passphrase`: the passphrase, so the sweeper and payouts can restart unattended;
3. starts PostgreSQL, the four Coldtill processes and Caddy with Docker Compose. Database migrations run
   automatically when the processes start.

For a local trial without DNS, use `--site-url http://localhost:8080`.

### Running without a stored passphrase

If you'd rather not keep the passphrase on disk, initialise with `coldtill init --store-passphrase=false` (or
delete `deploy/secrets/hot_passphrase`) and supply it each time you start the stack:

```bash
cd deploy
read -rs COLDTILL_HOT_PASSPHRASE && export COLDTILL_HOT_PASSPHRASE
docker compose up -d
unset COLDTILL_HOT_PASSPHRASE
```

Only the `sweeper` and `payouts` containers receive it. If either container restarts, it can't unlock the
hot wallets until you run this again.

## First-run setup

Open the site URL and create the **root** account (the first account becomes root). Then, in order:

1. **Account:** enable two-factor authentication. Anything that moves or redirects money needs a fresh
   two-factor code, so most of the steps below require it.
2. **Nodes & networks:** enable the networks you want, set confirmations, and add RPC nodes. Public defaults
   are rate limited and fine only for testing.
3. **Projects:** rename the default project or add more. Each project is a shop with its own settings.
4. **Deposit wallets** (per project):
   - **Bitcoin:** paste a BIP84 account xpub (`zpub` on mainnet, `vpub` on signet). The browser derives the
     first address; confirm it matches your wallet's first receive address.
   - **EVM:** connect a browser wallet (your "master" account) and enter your cold wallet. Deploy the deposit
     factory on each EVM network from the same master account; the factory address is the same on every chain.
   - **Solana:** connect a wallet, enter your treasury (a hardware wallet or a Squads vault address), save,
     then create the config by signing in your wallet. The browser checks the transaction before signing.
5. **Hot wallets** (global admin): generate or import one EVM and one Solana key. Fund each with a little gas,
   then assign them to the project.
6. **Project settings:** branding, sweep thresholds, payout limits and (optional) auto-convert. See
   [Branding](https://coldtill.com/docs/branding.md), [Payouts](https://coldtill.com/docs/payouts.md) and [Auto-convert](https://coldtill.com/docs/auto-convert.md).
7. **API keys and webhooks:** create an API key (project admin, with two-factor) and a webhook endpoint for
   your shop. See [Merchant API](https://coldtill.com/docs/api.md) and [Webhooks](https://coldtill.com/docs/webhooks.md).

## Users and roles

- **Root:** the first account. It can do everything, and is the only account that can disable admins.
- **Admin:** global administrator. Manages nodes, networks, hot wallets and users.
- **Member:** access only to the projects they are added to, with a project role:
  - `project_admin`: settings, deposit wallets, creating API keys, the address book, payouts and approvals;
  - `developer`: payments, webhooks, and revoking API keys, but not settings, payouts or creating keys;
  - `read_only`: view only.

## Nodes and networks

Coldtill trusts RPC nodes to report deposits. A malicious node can't redirect funds, but it could report a
deposit that never happened. For production:

- run your own node, or use a paid provider (Alchemy, Infura, QuickNode, Helius, Blockstream) with an API key;
- add more than one node per network, so failover works and decisions that ask every node (Solana payout
  re-signing, Bitcoin dropped deposits) have something to compare;
- keep the default confirmation counts or raise them. Lowering them needs a two-factor code.

## Backups

What to back up, and what losing it means:

| Item | Where | If lost |
|---|---|---|
| Database | `pgdata` Docker volume | Payment history, settings, encrypted hot keys. Customer funds are safe on-chain, but you lose the records |
| `deploy/.env` | Server | The server secret: sessions, two-factor secrets and webhook secrets become unreadable |
| `deploy/secrets/` | Server | The hot-wallet identity. You'd need to re-import or replace hot wallets |
| Cold wallet seed | Offline, with you | **Everything swept so far.** Coldtill can't help with this |

Nightly database dump:

```bash
cd ~/coldtill/deploy
docker compose exec -T postgres pg_dump -U coldtill coldtill | gzip > ~/backups/coldtill-$(date +%F).sql.gz
```

Add it to cron with `crontab -e`:

```text
15 3 * * * cd $HOME/coldtill/deploy && docker compose exec -T postgres pg_dump -U coldtill coldtill | gzip > $HOME/backups/coldtill-$(date +\%F).sql.gz
```

Copy `deploy/.env` and `deploy/secrets/` to encrypted offline storage once, and again whenever they change.

### Restore

```bash
cd ~/coldtill/deploy
docker compose up -d postgres
gunzip -c ~/backups/coldtill-2026-10-01.sql.gz | docker compose exec -T postgres psql -U coldtill coldtill
docker compose up -d
```

Restore `.env` and `secrets/` from the same point in time as the dump.

## Upgrades

```bash
cd ~/coldtill
git pull
cd deploy
docker compose build
docker compose up -d
```

Migrations run on start-up. Take a database dump first. Read the release notes before upgrading across major
versions.

Never run `make contracts` on a live install: rebuilding the EVM contracts changes the factory bytecode, and
with it every deposit address.

## Monitoring

- **Health:** `https://pay.example.com/healthz` returns `ok`. The `api` container has a Docker health check.
- **Logs** are JSON, one line per event:

  ```bash
  docker compose logs -f --since 1h api watcher sweeper payouts
  docker compose logs sweeper | grep '"level":"WARN"'
  ```

- **Things to alert on:**
  - `"level":"ERROR"` from any process;
  - `conversion delivery mismatch`, `filled a nonce gap`, `sweep not possible`, `hot wallet … needs at least`;
  - payouts in `needs-review` (they also send a `payout.needs-review` webhook);
  - a network's "last block processed" in **Nodes & networks** falling behind.
- **Hot wallet balances:** shown under **Hot wallets**. Top them up before they run dry, or sweeps stop.

## Rotating secrets

- **Hot wallet:** add a new hot wallet, assign it to the project, move any remaining balance out of the old
  one with your own wallet, then delete the old one.
- **API key:** create a new key, deploy it, then revoke the old one.
- **Webhook secret:** create a new endpoint, switch your receiver, then delete the old endpoint.
- **Hot-wallet passphrase:** re-run `coldtill init --force` in a scratch directory to create a new identity,
  then re-import the hot keys under the new identity. Plan this as a maintenance window.
- **`COLDTILL_SECRET`:** changing it signs everyone out and makes stored two-factor and webhook secrets
  unreadable. Users must re-enrol two-factor and webhooks must be recreated. Only rotate it after a suspected
  compromise.

## Moving to mainnet

Run a separate install with `--mode mainnet`. Mainnet requires an `https://` site URL and SMTP settings
(`COLDTILL_SMTP_HOST`, `COLDTILL_SMTP_PORT`, `COLDTILL_SMTP_USERNAME`, `COLDTILL_SMTP_PASSWORD`,
`COLDTILL_SMTP_FROM`). Before taking real payments, work through the mainnet checklist in
Audit preparation and the advice in
How to get Coldtill audited.

---

Source: https://coldtill.com/docs/install/ · All Coldtill docs: https://coldtill.com/llms.txt
