From 0bf9dfad5090e4e5a0fbb51fe84e2af4633eeee2 Mon Sep 17 00:00:00 2001 From: r1n04h Date: Wed, 20 May 2026 14:06:17 +0100 Subject: [PATCH] ci: production deploy job --- .bun-version | 1 + .github/workflows/ci.yml | 12 +-- .github/workflows/deploy.yml | 64 ++++++++++++++++ README.md | 6 ++ deploy.exclude | 7 ++ docs/deploy.md | 142 +++++++++++++++++++++++++++++++++++ package.json | 1 + 7 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 .bun-version create mode 100644 .github/workflows/deploy.yml create mode 100644 deploy.exclude create mode 100644 docs/deploy.md diff --git a/.bun-version b/.bun-version new file mode 100644 index 0000000..085c0f2 --- /dev/null +++ b/.bun-version @@ -0,0 +1 @@ +1.3.14 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b932f0..2d0ffe4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,9 +16,9 @@ jobs: uses: actions/checkout@v4 - name: Setup Bun - uses: oven-sh/setup-bun@v1 + uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version-file: .bun-version - name: Install dependencies run: bun install @@ -35,9 +35,9 @@ jobs: uses: actions/checkout@v4 - name: Setup Bun - uses: oven-sh/setup-bun@v1 + uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version-file: .bun-version - name: Install dependencies run: bun install @@ -54,9 +54,9 @@ jobs: uses: actions/checkout@v4 - name: Setup Bun - uses: oven-sh/setup-bun@v1 + uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version-file: .bun-version - name: Install dependencies run: bun install diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..fb4d279 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,64 @@ +name: Deploy + +on: + workflow_run: + workflows: [CI] + types: [completed] + workflow_dispatch: + +concurrency: + group: deploy-production + cancel-in-progress: false + +env: + DEPLOY_PATH: ${{ vars.DEPLOY_PATH || '/var/www/sparkhub' }} + +jobs: + deploy: + name: Deploy to VPS + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + if: > + github.event_name == 'workflow_dispatch' || + ( + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'master' + ) + steps: + - name: Checkout code at deployed revision + uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha || github.sha }} + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version-file: .bun-version + + - name: Build + run: | + bun install --frozen-lockfile + bun run build + + - name: Prune to production dependencies + run: | + rm -rf node_modules + bun install --production --frozen-lockfile + + - name: Deploy to VPS + env: + SSHPASS: ${{ secrets.SSH_PASSWORD }} + SSH_PORT: ${{ secrets.SSH_PORT || '22' }} + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq sshpass rsync + SSH_OPTS="-p ${SSH_PORT} -o StrictHostKeyChecking=accept-new" + TARGET="${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}" + + export RSYNC_RSH="ssh ${SSH_OPTS}" + sshpass -e rsync -avz --delete-delay --delay-updates \ + --exclude-from=deploy.exclude \ + ./ "${TARGET}:${DEPLOY_PATH}/" diff --git a/README.md b/README.md index 8bca468..77a1285 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,9 @@ bun run dev:full ## API Documentation Swagger documentation is available at `http://localhost:3000/swagger` when the server is running. + +## Deployment + +Pushes to `master` run CI, then build and rsync to the VPS; the server reloads via Bun `--watch`. + +See [docs/deploy.md](docs/deploy.md) for one-time server setup and GitHub secrets. diff --git a/deploy.exclude b/deploy.exclude new file mode 100644 index 0000000..1a8c8a6 --- /dev/null +++ b/deploy.exclude @@ -0,0 +1,7 @@ +.git/ +.github/ +test/ +docs/ +*.sqlite +src/config.ts +src/config-server.ts diff --git a/docs/deploy.md b/docs/deploy.md new file mode 100644 index 0000000..69dd9af --- /dev/null +++ b/docs/deploy.md @@ -0,0 +1,142 @@ +# Production deployment + +SparkHub deploys automatically when CI passes on a push to `master`. + +```text +push master → CI → GitHub Actions (install + build) → rsync to VPS → --watch reloads +``` + +The app is **built on GitHub Actions** (`bun install` + `bun run build`), then **rsynced in bulk** to the VPS (including `node_modules`). Server config files on the VPS are never overwritten (see `deploy.exclude`). The server runs **`bun run start:watch`** and reloads when rsync updates files. + +## One-time VPS setup + +Git is not required on the server for deploys. + +### 1. Deploy user and directory + +```bash +sudo mkdir -p /var/www/sparkhub +sudo useradd --system --home-dir /var/www/sparkhub --shell /bin/bash deploy +sudo chown -R deploy:deploy /var/www/sparkhub +``` + +### 2. Install Bun (as the deploy user) + +```bash +sudo -iu deploy bash -lc 'curl -fsSL https://bun.sh/install | bash' +``` + +Bun will install to `/var/www/sparkhub/.bun/bin/bun`. Verify: + +```bash +sudo -iu deploy bash -lc 'which bun && bun --version' +``` + +### 3. Production configuration + +Create config on the server before the first deploy (these paths are excluded from rsync): + +```bash +cd /var/www/sparkhub +mkdir -p src +# copy or create src/config.ts and src/config-server.ts +``` + +- `src/config.ts` — public domain +- `src/config-server.ts` — SQLite path, wallet seed, TLS cert paths + +Ensure TLS certificate files exist at the paths referenced in `config-server.ts`. + +Binding to port 443 usually requires: + +```bash +sudo setcap 'cap_net_bind_service=+ep' /var/www/sparkhub/.bun/bin/bun +``` + +### 4. Keep the server running (pick one) + +**Option A — PM2** + +Run all of this as the `deploy` user (e.g. `sudo -iu deploy`): + +```bash +cd /var/www/sparkhub +pm2 start /var/www/sparkhub/.bun/bin/bun --name sparkhub -- run start:watch +pm2 save +pm2 startup # run the command it prints (as root) +``` + +**Option B — systemd** + +```bash +sudo tee /etc/systemd/system/sparkhub.service <<'EOF' +[Unit] +Description=SparkHub +After=network.target + +[Service] +Type=simple +User=deploy +WorkingDirectory=/var/www/sparkhub +Environment=HOME=/var/www/sparkhub +ExecStart=/var/www/sparkhub/.bun/bin/bun run start:watch +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +EOF + +sudo systemctl daemon-reload +sudo systemctl enable --now sparkhub +``` + +Adjust the `bun` path if needed (`sudo -iu deploy which bun`). + +### 5. First deploy + +Merge this repo’s CD setup to `master`, add GitHub secrets (below), and let the **Deploy** workflow run. It will rsync the built app to the server. + +If the app directory is empty, create `src/config.ts` and `src/config-server.ts` first, then run **Actions → Deploy → Run workflow**. + +### 6. SSH credentials for GitHub Actions + +| Secret | Description | +|--------|-------------| +| `SSH_HOST` | VPS hostname or IP | +| `SSH_USER` | `deploy` | +| `SSH_PASSWORD` | Deploy user password | +| `SSH_PORT` | Optional; default `22` | + +Optional repository **variables**: + +| Variable | Default | +|----------|---------| +| `DEPLOY_PATH` | `/var/www/sparkhub` | + +### 7. Branch protection (recommended) + +On `master`, require the **CI** workflow to pass before merge. + +## What gets synced + +`deploy.exclude` keeps server-only files safe: + +- `src/config.ts`, `src/config-server.ts` +- `.git/`, tests, docs, local SQLite files + +## Day-to-day operations + +- **Deploy**: push to `master` (after CI passes) or **Actions → Deploy → Run workflow**. +- **Logs**: `pm2 logs sparkhub` or `journalctl -u sparkhub -f` +- **Rollback**: in **Actions → Deploy → Run workflow**, pick the branch or tag at the revision you want to ship. The workflow checks out the dispatched ref. For a fast revert, `git revert` and push to `master`. + +## Troubleshooting + +| Symptom | Check | +|---------|--------| +| Deploy workflow skipped | CI must pass on a **push** to `master`. | +| Config overwritten | Paths must stay listed in `deploy.exclude`. | +| Server did not reload | Process must use `bun run start:watch`. | +| `bun: command not found` | Bun not on deploy user `PATH`. | +| Permission denied on 443 | `setcap` on `bun` or reverse proxy on 443. | diff --git a/package.json b/package.json index 9553fde..5dab2a1 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "build": "bun build src/client/app.tsx --outdir dist --target browser --production", "build:watch": "bun build src/client/app.tsx --outdir dist --target browser --watch", "start": "bun run build && PORT=443 bun run src/index.ts", + "start:watch": "NODE_ENV=production PORT=443 bun run --watch src/index.ts", "dev:full": "concurrently \"bun run dev\" \"bun run build:watch\"", "tslint": "tsc --noEmit", "check": "biome check src/",