Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1fffebbf6 | |||
| 1fed8ee920 |
@@ -0,0 +1,66 @@
|
|||||||
|
name: Deploy to LXC
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Deploy via SSH
|
||||||
|
uses: appleboy/ssh-action@v1
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOY_HOST }}
|
||||||
|
username: ${{ secrets.DEPLOY_USER }}
|
||||||
|
key: ${{ secrets.DEPLOY_KEY }}
|
||||||
|
port: ${{ secrets.DEPLOY_PORT || 22 }}
|
||||||
|
script: |
|
||||||
|
set -e
|
||||||
|
|
||||||
|
APP_DIR="${{ secrets.DEPLOY_PATH || '/opt/buildfor-life-budget' }}"
|
||||||
|
|
||||||
|
# Set up deploy key for private repo access
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "${{ secrets.REPO_DEPLOY_KEY }}" > ~/.ssh/repo_deploy_key
|
||||||
|
chmod 600 ~/.ssh/repo_deploy_key
|
||||||
|
|
||||||
|
# Configure SSH to use deploy key for git.b4l.co.th
|
||||||
|
if ! grep -q "git.b4l.co.th" ~/.ssh/config 2>/dev/null; then
|
||||||
|
cat >> ~/.ssh/config <<EOF
|
||||||
|
Host git.b4l.co.th
|
||||||
|
HostName git.b4l.co.th
|
||||||
|
IdentityFile ~/.ssh/repo_deploy_key
|
||||||
|
StrictHostKeyChecking accept-new
|
||||||
|
EOF
|
||||||
|
chmod 600 ~/.ssh/config
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clone if first deploy, otherwise pull
|
||||||
|
if [ ! -d "$APP_DIR" ]; then
|
||||||
|
echo "==> First deploy, cloning..."
|
||||||
|
git clone git@git.b4l.co.th:B4L/buildfor_life_budget.git "$APP_DIR"
|
||||||
|
cd "$APP_DIR"
|
||||||
|
else
|
||||||
|
cd "$APP_DIR"
|
||||||
|
echo "==> Resetting local changes..."
|
||||||
|
git checkout -- .
|
||||||
|
echo "==> Pulling latest code..."
|
||||||
|
git pull origin main
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Installing dependencies..."
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
echo "==> Building..."
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
echo "==> Running migrations..."
|
||||||
|
npm run db:push
|
||||||
|
|
||||||
|
echo "==> Restarting service..."
|
||||||
|
sudo systemctl restart buildfor-life-budget
|
||||||
|
|
||||||
|
echo "==> Waiting for startup..."
|
||||||
|
sleep 2
|
||||||
|
systemctl is-active --quiet buildfor-life-budget && echo "Deploy successful!" || (echo "Service failed to start!" && exit 1)
|
||||||
@@ -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 <<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`](./deployment.md#1-node-app)) 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:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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. |
|
||||||
Reference in New Issue
Block a user