> ## Documentation Index
> Fetch the complete documentation index at: https://docs.signalium.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Deployment

> How signalium.site + api.signalium.site are deployed and continuously updated.

Production runs on a single VPS behind nginx, two pm2 processes:

| Domain               | Process         | Local port |
| -------------------- | --------------- | ---------- |
| `signalium.site`     | `signalium-web` | `8040`     |
| `api.signalium.site` | `signalium-api` | `8041`     |

## CI/CD

Push to `main` triggers [`.github/workflows/cd.yml`](https://github.com/rstfulzz/signalium/blob/main/.github/workflows/cd.yml):

1. **verify** job (GitHub-hosted Ubuntu): `pnpm install --frozen-lockfile`, then `pnpm typecheck` and `pnpm lint` scoped to runtime packages (skips `@signalium/contracts` which needs Foundry and `@signalium/subgraph` which needs `graph-cli` codegen).
2. **deploy** job: SSHes to the VPS using a deploy-only ed25519 key, runs `git fetch && reset --hard origin/main`, `pnpm install --frozen-lockfile --prefer-offline`, `pnpm build --filter=@signalium/web --filter=@signalium/api`, then `pm2 reload ecosystem.config.cjs --update-env`.

End-to-end: \~1.5 min from push to live.

## Required GitHub Actions secrets

| Secret        | Value                                                                                           |
| ------------- | ----------------------------------------------------------------------------------------------- |
| `VPS_HOST`    | hostname or IP of the VPS                                                                       |
| `VPS_USER`    | ssh user                                                                                        |
| `VPS_SSH_KEY` | full contents of an ed25519 private key whose pub key is in `~/.ssh/authorized_keys` on the VPS |
| `VPS_PATH`    | absolute repo path on the VPS, e.g. `/home/cuyvps/signalium`                                    |

## First-time bootstrap

Walks through cloning, env setup, pm2 init, nginx vhosts, and certbot. Lives in [`deploy/README.md`](https://github.com/rstfulzz/signalium/blob/main/deploy/README.md). Highlights:

```bash theme={null}
# Tools
curl -fsSL https://get.pnpm.io/install.sh | sh -
curl -fsSL https://bun.sh/install | bash

# Code
git clone https://github.com/rstfulzz/signalium.git ~/signalium
cd ~/signalium
pnpm install --frozen-lockfile
pnpm build --filter=@signalium/web --filter=@signalium/api

# Drop in the production .env (rsync from local — never commit)
scp ./.env vpsuser@vps:~/signalium/.env

# Start
pm2 start ecosystem.config.cjs
pm2 save

# nginx vhosts
sudo ln -sf ~/signalium/deploy/nginx/signalium.site.conf      /etc/nginx/sites-enabled/
sudo ln -sf ~/signalium/deploy/nginx/api.signalium.site.conf  /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# SSL
sudo certbot --nginx -d signalium.site -d www.signalium.site
sudo certbot --nginx -d api.signalium.site
```

## pm2 ecosystem

[`ecosystem.config.cjs`](https://github.com/rstfulzz/signalium/blob/main/ecosystem.config.cjs) declares both processes. Key gotcha: `interpreter: 'none'` is required so pm2 doesn't try to fork the pnpm shell wrapper as a Node script.

## SQLite cache

The API's SQLite db lives at `apps/api/data/signalium.db` and holds the X post metadata cache, AI proposals (keyed by `judgeStorageRoot`), and the 0G/USD price cache. **The CD workflow never touches this directory** — `apps/api/data/` is gitignored, so production state is preserved across deploys.

If the production DB needs a backfill (e.g., proposals created locally need to flow to prod), checkpoint the local WAL with `sqlite3 apps/api/data/signalium.db "PRAGMA wal_checkpoint(TRUNCATE);"` and `scp` the file after stopping the API process.
