# 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. |