# Scripts and settings

> Every script, setting, port and endpoint, with defaults.

## Scripts

| Script | Runs on | Does |
|---|---|---|
| [provision.sh](https://github.com/sudharshanx/runfarcaster/blob/main/provision.sh) | your machine | Creates the firewall, volume and server on Hetzner with `hcloud`. Checks the price first. Prints the IPv4 address |
| [node.sh](https://github.com/sudharshanx/runfarcaster/blob/main/node.sh) | VM (`ssh … bash -s < node.sh`) | `all` (default): checks, Docker, disk helpers on small disks, official bootstrap, wait for sync, health check. Also `wait`, `health`, `helpers` |
| [deploy-client.sh](https://github.com/sudharshanx/runfarcaster/blob/main/deploy-client.sh) | your machine | Builds the app on your computer, copies it and Caddy to `/opt/snapchain-client` |
| [teardown.sh](https://github.com/sudharshanx/runfarcaster/blob/main/teardown.sh) | your machine | Deletes the server, volume and firewall; lists what is left. Safe to run twice |
| [watchdog.sh](https://github.com/sudharshanx/runfarcaster/blob/main/watchdog.sh) | your machine | Runs `teardown.sh` before the bill reaches `MAX_USD`, after `MAX_WALL_SECS`, or after `HANG_SECS` with no progress |
| [disk-guard.sh](https://github.com/sudharshanx/runfarcaster/blob/main/disk-guard.sh) | VM (systemd timer; `node.sh` installs it) | `install`, `check`, `status`, `uninstall`. Stops the node at 90% disk |
| [snapshot-space-saver.sh](https://github.com/sudharshanx/runfarcaster/blob/main/snapshot-space-saver.sh) | VM (systemd; `node.sh` installs it) | `install`, `run`, `once`, `uninstall`. Deletes snapshot files the node has finished with; stops when the restore is done |

`provision.sh`, `node.sh`, `deploy-client.sh`, `teardown.sh` and the two VM helpers take `DRY_RUN=1`: they print what they would do and change nothing. `watchdog.sh` does not. Tokens come from the environment and are never printed.

## Environment variables

| Variable | Script | Default | Notes |
|---|---|---|---|
| `HCLOUD_TOKEN` | provision, teardown, watchdog | (required) | Hetzner API token, read/write |
| `SSH_KEY` | provision | (required) | Name or ID of an SSH key in your Hetzner project |
| `SERVER_TYPE` | provision | `cx43` | Tested with `cax31`. Empty = auto-pick from `SERVER_TYPE_CANDIDATES`. If it isn't available to rent in `LOCATION`, the error lists what is, cheapest first |
| `SERVER_TYPE_CANDIDATES` | provision | `cx43 cax31 cpx42 ccx23` | Cheapest first |
| `VOLUME_GB` | provision | `2000` (`REQUIRED_DISK_GB`) | `1000` fits a new account's quota |
| `LOCATION` | provision | `fsn1` | `fsn1`, `nbg1`, `hel1`, `ash`, `hil` or `sin`. Server and volume share it |
| `MAX_USD` | provision, watchdog | `8` | Price limit for the check before creating anything; the watchdog deletes everything at it. In your Hetzner account's currency |
| `EXPECTED_HOURS` | provision | `24` | Hours the price check multiplies by. Our cax31 sync took 14 h 06 m |
| `WATCHDOG` | provision | `1` | `0` = no watchdog; the server runs until you delete it. The Full run uses `0` |
| `ESTIMATE_ONLY` | provision | `0` | `1` = price check only, creates nothing |
| `SSH_SOURCE_CIDRS` | provision | `0.0.0.0/0,::/0` | Set to `"$MY_IP/32"` (from `curl -4 -s https://ifconfig.me`) |
| `NODE_API_CIDRS` | provision | empty | Addresses allowed to reach 3381 and 3383. Empty keeps both closed |
| `PORT_PROFILE` | provision | `docs` | `readme` also opens the README's TCP+UDP superset |
| `NAME` | provision, teardown, watchdog | `snapchain-read-node` | Volume `$NAME-data`, firewall `$NAME-fw`. The Full run exports it, so every step and `teardown.sh` use the same name |
| `REUSE` | provision | `0` | If the server `$NAME`, the volume `$NAME-data` or an applied firewall `$NAME-fw` already exists, `provision.sh` stops before it creates or changes anything. `1` continues with them (your own earlier run) |
| `VOLUME_QUOTA_GB` | provision | `1024` | Account-wide volume limit (new Hetzner accounts: 1024 GB across all projects). Before creating anything, `provision.sh` adds the volumes in this project to `VOLUME_GB` and stops if the sum is over it. Volumes in other projects are not visible, so Hetzner can still refuse. Set it if Hetzner raised your limit |
| `WATCHDOG_MAX_WALL_SECS` | provision | `86400` | Time limit `provision.sh` passes to the watchdog (as `MAX_WALL_SECS`; `MAX_WALL_SECS` on `provision.sh` works too). `172800` = 48 hours |
| `CONFIRM` | teardown | (prompt) | Must equal `NAME` |
| `OTHER_SERVER_OK` | teardown | `0` | `teardown.sh` stops and deletes nothing if the server named `$NAME` has a different ID from the one `provision.sh` recorded in `.provision-state/server-$NAME.id`. `1` deletes it anyway |
| `SNAPCHAIN_AGREE_NO_REWARDS` | node.sh | (prompt) | `yes` records the no-rewards agreement |
| `OPERATOR_FID` | node.sh | unset | Enables the weekly auto-upgrade cron; unset = `SKIP_CRONTAB=true` |
| `MIN_FREE_GB` | node.sh | `1500` | `900` for a 1000 GB volume |
| `SYNC_TIMEOUT_SECS` | node.sh | `86400` | 24 hours; our cax31 sync took 14 h 06 m |
| `DISK_HELPERS` | node.sh | `auto` | `auto` installs disk-guard and the space saver when the disk is under `DISK_HELPERS_BELOW_GB`; `1` always; `0` never |
| `DISK_HELPERS_BELOW_GB` | node.sh | `1500` | |
| `SPACE_SAVER_WAIT_SECS` | node.sh | `7200` | How long the space saver waits for the snapshot folder to appear |
| `SNAPSHOT_REPAIR` | node.sh | `auto` | If a shard's snapshot download fails with no upstream retry, restore only that shard. `0` only prints the command |
| `REPAIR_ATTEMPTS` / `REPAIR_PARALLEL` | node.sh | `3` / `4` | Fresh snapshot lists per repair; parallel chunk downloads |
| `SYNC_MAX_BLOCK_DELAY` / `SYNC_STABLE_CHECKS` / `SYNC_POLL_SECS` | node.sh | `10` / `3` / `60` | Sync criterion |
| `NODE_UPGRADE` | node.sh | `0` | `1` re-runs the official bootstrap (upgrade) |
| `SSH_OPTS` | deploy-client | empty | Extra ssh/scp options, e.g. `-i key` |
| `LIMIT_PCT` | disk-guard | `90` | |
| `HANG_SECS` / `MAX_WALL_SECS` | watchdog | `7200` / `86400` | No-progress limit; total time limit. When `provision.sh` starts the watchdog, set the time limit with `WATCHDOG_MAX_WALL_SECS` |
| `SSH_KEY_FILE` | watchdog | empty | Private key for the progress check. Empty turns the check off |
| `PROGRESS_MIN_BYTES` | watchdog | `1048576` | Database growth that counts as progress; any block height increase also counts |
| `UNREACHABLE_IS_HANG` | watchdog | `0` | `1` counts lost SSH as no progress |

## Ports

| Port | Purpose | Open in the firewall |
|---|---|---|
| 3381/tcp | Snapchain HTTP API | No, unless you set `NODE_API_CIDRS`. Caddy reaches it on 127.0.0.1 |
| 3382/udp | Gossip (libp2p QUIC) | Yes |
| 3383/tcp | Snapchain gRPC API | No, unless you set `NODE_API_CIDRS` |
| 80/tcp, 443/tcp | Caddy: Let's Encrypt and the HTTPS client | Yes |
| 22/tcp | SSH | Yes, limit with `SSH_SOURCE_CIDRS` |
| 3000/tcp | Grafana (default `admin:admin`) | No. Keep it closed |
| 8125/udp, 8126/tcp | statsd | No |

Docker publishes 3381, 3383, 3000 and the statsd ports on every interface of the server, so the Hetzner firewall is what keeps them private. The app reaches the node through Caddy on 443. To read the API from your computer, use `https://<name>/v1/...` or `ssh -N -L 3381:127.0.0.1:3381 root@$IP`.

## HTTP endpoints the client uses

`/v1/info`, `/v1/castsByFid`, `/v1/castById`, `/v1/linksByFid`, `/v1/linkById`, `/v1/reactionById`, `/v1/userDataByFid`, `/v1/signersByFid`, `/v1/signer`, `/v1/onChainIdRegistryEventByAddress`, `POST /v1/submitMessage`. Reference: [HTTP API](https://snapchain.farcaster.xyz/reference/httpapi/httpapi).

## Repo layout

| Path | What |
|---|---|
| `client/` | The web app: Vite and TypeScript, `@farcaster/core` 0.20.0 |
| `deploy/` | `Caddyfile`, Caddy compose file, Hetzner firewall rules |
| `test/`, `e2e/` | Tests for the scripts; browser tests against a mock node and the live node |
| `site/` | This site (Astro Starlight) |
| `MEASUREMENTS.md`, `run-log.md` | Numbers and log from the mainnet run |
| `DOCS_ISSUES.md`, `SOURCES.md` | Problems found in the official docs; the source for each fact |
| `SECURITY.md`, `VOUCHER.md` | How keys are handled; the checklist for vouching |

## Sources

- Snapchain v0.14.3, commit `fc87f26`: [github.com/farcasterxyz/snapchain](https://github.com/farcasterxyz/snapchain)
- Gasless app keys: [writing-messages](https://snapchain.farcaster.xyz/guides/writing-messages); live since engine version V16 on 2026-05-07 ([`src/version/version.rs`](https://github.com/farcasterxyz/snapchain/blob/fc87f2686472598143062a00e1c2b2633b2d87ab/src/version/version.rs)); the guide was updated in [docs PR #989](https://github.com/farcasterxyz/snapchain/pull/989)
- Read nodes pass messages on: [node-types](https://snapchain.farcaster.xyz/node-types#message-submission-and-the-mempool) (documented in [docs PR #1034](https://github.com/farcasterxyz/snapchain/pull/1034)); code: `submit_message_internal` in `src/network/server.rs`, `ReadNodeMempool` in `src/mempool/mempool.rs`
- [Getting Started](https://snapchain.farcaster.xyz/getting-started), [managing-a-node](https://snapchain.farcaster.xyz/guides/managing-a-node)
- Full list: [SOURCES.md](https://github.com/sudharshanx/runfarcaster/blob/main/SOURCES.md)
