diff --git a/docs/ci-deploy-setup.md b/docs/ci-deploy-setup.md new file mode 100644 index 0000000..363fbf2 --- /dev/null +++ b/docs/ci-deploy-setup.md @@ -0,0 +1,129 @@ +# CI/CD Deploy Setup + +Auto-deploys to your LXC server on every push to `main` via `.gitea/workflows/deploy.yml`. + +## 1. Server preparation + +On the LXC server, allow the deploy user to restart the service without a password: + +```bash +# As root on the LXC +echo "budget ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart buildfor-life-budget, /usr/bin/systemctl status buildfor-life-budget" > /etc/sudoers.d/budget-deploy +chmod 440 /etc/sudoers.d/budget-deploy +``` + +Make sure the repo is cloned and the app works manually first (see [`docs/deployment.md`](./deployment.md)). + +## 2. Generate SSH keys + +You need **two** SSH key pairs: + +### a) Deploy key (CI runner → LXC server) + +This lets the CI runner SSH into your server: + +```bash +ssh-keygen -t ed25519 -C "ci-to-server" -f ci_deploy_key -N "" +``` + +Copy the **public** key to the server: + +```bash +ssh-copy-id -i ci_deploy_key.pub budget@your-lxc-ip +``` + +### b) Repo deploy key (LXC server → private Gitea repo) + +This lets the server `git pull` from the private repo: + +```bash +ssh-keygen -t ed25519 -C "server-to-repo" -f repo_deploy_key -N "" +``` + +Add the **public** key in Gitea: repo → **Settings** → **Deploy Keys** → **Add Deploy Key**, paste `repo_deploy_key.pub`. + +## 3. Add secrets in Gitea + +Go to your repo on git.b4l.co.th → **Settings** → **Actions** → **Secrets**, and add: + +| Secret | Value | +|--------|-------| +| `DEPLOY_HOST` | LXC server IP (e.g. `192.168.10.5`) | +| `DEPLOY_USER` | SSH user (e.g. `budget`) | +| `DEPLOY_KEY` | Contents of `ci_deploy_key` (private key — CI runner → server) | +| `REPO_DEPLOY_KEY` | Contents of `repo_deploy_key` (private key — server → Gitea repo) | +| `DEPLOY_PORT` | SSH port (optional, defaults to 22) | +| `DEPLOY_PATH` | App directory (optional, defaults to `/opt/buildfor-life-budget`) | + +### First clone on the server + +The workflow will clone the repo automatically on the first run if `DEPLOY_PATH` doesn't exist. If you prefer to clone manually: + +```bash +# On the server as the budget user, set up the deploy key first +mkdir -p ~/.ssh +cp repo_deploy_key ~/.ssh/repo_deploy_key +chmod 600 ~/.ssh/repo_deploy_key +cat >> ~/.ssh/config < + +# Start +./act_runner-linux-amd64 daemon +``` + +## 5. Test + +Push any change to `main` and check the Actions tab in Gitea for the deploy log. + +## What the workflow does + +1. SSHs into the LXC server +2. Installs the repo deploy key for private repo access +3. `git pull` the latest code (or `git clone` on first deploy) +4. `npm ci` to install exact lockfile deps +5. `npm run build` to compile SvelteKit (adapter-node) +6. `npm run db:push` to apply any schema changes to PostgreSQL +7. `sudo systemctl restart buildfor-life-budget` to restart the service +8. Verifies the service started successfully via `systemctl is-active` + +## Troubleshooting + +| Symptom | Likely cause | +|---|---| +| `Permission denied (publickey)` from CI | `DEPLOY_KEY` mismatches what's in `~/.ssh/authorized_keys` for the deploy user. Re-paste exactly (include `-----BEGIN/END-----` lines). | +| `sudo: a password is required` | Sudoers file not installed or has a typo. Check `/etc/sudoers.d/budget-deploy` with `sudo visudo -cf /etc/sudoers.d/budget-deploy`. | +| `git@git.b4l.co.th: Permission denied` on the LXC | `REPO_DEPLOY_KEY` not registered as a Deploy Key on the repo, or the LXC's `~/.ssh/repo_deploy_key` has wrong permissions (must be `600`). | +| `npm ci` fails with "lock file version mismatch" | Node version on the LXC doesn't match what produced `package-lock.json`. Use the same Node major version as local dev (check `.nvmrc` if present, else `node --version` on both sides). | +| `npm run db:push` hangs on interactive prompt | Destructive schema change (column/table drop). Either revert the change or run it manually with `npx drizzle-kit push --force` after confirming data loss is acceptable. | +| Service restarts but then exits | Missing or invalid `.env` — check `journalctl -u buildfor-life-budget -n 50`. `ORIGIN`, `DATABASE_URL`, and `UPLOADS_DIR` are mandatory. |