Files
buildfor_life_budget/docs/ci-deploy-setup.md
T
grabowski a1fffebbf6
Validate / validate (push) Successful in 31s
Deploy to LXC / deploy (push) Successful in 1m55s
Add CI/CD deploy setup doc
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:52:46 +07:00

4.9 KiB

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:

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

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:

ssh-keygen -t ed25519 -C "ci-to-server" -f ci_deploy_key -N ""

Copy the public key to the server:

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:

ssh-keygen -t ed25519 -C "server-to-repo" -f repo_deploy_key -N ""

Add the public key in Gitea: repo → SettingsDeploy KeysAdd Deploy Key, paste repo_deploy_key.pub.

3. Add secrets in Gitea

Go to your repo on git.b4l.co.th → SettingsActionsSecrets, 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:

# 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 <<EOF
Host git.b4l.co.th
  HostName git.b4l.co.th
  IdentityFile ~/.ssh/repo_deploy_key
  StrictHostKeyChecking accept-new
EOF

sudo mkdir -p /opt/buildfor-life-budget
sudo chown budget:budget /opt/buildfor-life-budget
git clone git@git.b4l.co.th:B4L/buildfor_life_budget.git /opt/buildfor-life-budget

Remember to create /opt/buildfor-life-budget/.env (see docs/deployment.md) before the first deploy — the service won't start without it.

4. Enable Actions in Gitea

Make sure Gitea Actions is enabled on your instance:

# In app.ini (Gitea config)
[actions]
ENABLED = true

You also need a runner registered. If you don't have one, install the Gitea runner on the Gitea host or another machine:

# Download the runner
wget https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-amd64
chmod +x act_runner-linux-amd64

# Register with your Gitea instance
./act_runner-linux-amd64 register --instance https://git.b4l.co.th --token <your-runner-token>

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