diff --git a/.gitea/workflows/SETUP.md b/.gitea/workflows/SETUP.md new file mode 100644 index 0000000..103d92d --- /dev/null +++ b/.gitea/workflows/SETUP.md @@ -0,0 +1,131 @@ +# Gitea Actions — Setup Guide + +## Prerequisites + +You need a **Gitea runner** registered to your repo or organisation. +If you don't have one yet: + +```bash +# On your droplet (or any always-on machine) +# Download the runner binary from your Gitea instance: +# https://git.etcprs.app/-/admin/runners (site admin) +# or https://git.etcprs.app//runners (org level) + +# Install and register +./gitea-runner register \ + --instance https://git.etcprs.app \ + --token LSD3GDaXRaU9TUxtrlm8M3hOF72KFipIYchUpqda \ + --name "droplet-runner" \ + --labels "ubuntu-latest" +``` + +--- + +## 1. Generate a deploy key + +Run this **on your local machine** (not the server): + +```bash +ssh-keygen -t ed25519 -C "gitea-deploy" -f ~/.ssh/deploy_key -N "" +``` + +This creates two files: +- `~/.ssh/deploy_key` — private key (goes into Gitea secret) +- `~/.ssh/deploy_key.pub` — public key (goes onto the server) + +--- + +## 2. Add the public key to the server + +```bash +# Copy the public key to the droplet +ssh-copy-id -i ~/.ssh/deploy_key.pub root@your-droplet-ip + +# Or manually: +cat ~/.ssh/deploy_key.pub | ssh root@your-droplet-ip \ + "cat >> ~/.ssh/authorized_keys" +``` + +Test it works: + +```bash +ssh -i ~/.ssh/deploy_key root@your-droplet-ip "echo connected" +``` + +--- + +## 3. Add secrets to Gitea + +Go to your repo → **Settings → Secrets → Actions** and add: + +| Secret name | Value | +|------------------|------------------------------------------------| +| `DEPLOY_HOST` | Your droplet IP or hostname | +| `DEPLOY_USER` | `root` | +| `DEPLOY_SSH_KEY` | Contents of `~/.ssh/deploy_key` (private key) | +| `DEPLOY_PORT` | `22` | + +To get the private key contents: + +```bash +cat ~/.ssh/deploy_key +``` + +Copy the entire output including the `-----BEGIN...-----` and `-----END...-----` lines. + +--- + +## 4. Enable Actions on your repo + +In Gitea: **Settings → Repository → Enable Repository Actions** ✓ + +--- + +## 5. How it works + +### On any branch push or PR → `ci.yml` runs: +1. Install dependencies (`npm ci`) +2. JS syntax check (`node --check` on all `.js` files) +3. Svelte component check (`svelte-check`) +4. Full build (`npm run build`) + +If any step fails, the push is marked as failed. No deploy occurs. + +### On push to `main` → `deploy.yml` runs: +1. All CI steps above (build must pass first) +2. SSH into the droplet +3. `git pull` +4. `npm install` +5. `npm run build` +6. `chown` to fix file ownership +7. `pm2 reload etc-prs` (zero-downtime reload) + +--- + +## 6. Monitoring + +View workflow runs at: +`https://git.etcprs.app//actions` + +--- + +## Troubleshooting + +**"Host key verification failed"** +Add your droplet to known hosts on the runner, or add `StrictHostKeyChecking no` +to the SSH action config (already handled by `appleboy/ssh-action`). + +**"pm2: command not found"** +PM2 is installed globally but the SSH session may not have it in PATH. Fix: + +```bash +# On the server, find where pm2 is +which pm2 # e.g. /usr/local/bin/pm2 + +# If needed, symlink it to /usr/bin +ln -s /usr/local/bin/pm2 /usr/bin/pm2 +``` + +**Build fails with missing env vars** +The CI workflow passes dummy env vars — this is intentional. The real vars +are in the PM2 ecosystem config on the server and are never needed at build time. diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..aae6778 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,50 @@ +# .gitea/workflows/ci.yml +# Runs on every push and pull request. +# Checks syntax, imports, and that the build succeeds. + +name: CI + +on: + push: + branches-ignore: + - main # main is handled by deploy.yml + pull_request: + +jobs: + build: + name: Check & Build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + # ── Syntax check all server-side JS files ──────────────────────────── + - name: JS syntax check + run: | + find src -name "*.js" | xargs -I{} node --check {} + echo "✓ JS syntax OK" + + # ── Svelte component check ──────────────────────────────────────────── + - name: Svelte check + run: npx svelte-check --tsconfig ./jsconfig.json 2>&1 | tail -5 + continue-on-error: false + + # ── Full Vite build (catches broken imports + SSR errors) ───────────── + - name: Build + run: npm run build + env: + # Provide dummy env vars so the build doesn't fail on missing config + DATABASE_URL: ./dummy.db + RATE_LIMIT_PUBLISH: '5' + RATE_LIMIT_READ: '100' + PUBLIC_BASE_URL: 'https://example.com' diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..5b0e6dc --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,89 @@ +# .gitea/workflows/deploy.yml +# Triggered on push to main. +# Runs the full CI suite first, then deploys to the production droplet. +# +# Required Gitea secrets (Settings → Secrets): +# DEPLOY_HOST — droplet IP or hostname (e.g. 192.168.1.1) +# DEPLOY_USER — SSH user (e.g. root) +# DEPLOY_SSH_KEY — private key contents (the output of: cat ~/.ssh/deploy_key) +# DEPLOY_PORT — SSH port (usually 22) + +name: Deploy + +on: + push: + branches: + - main + +jobs: + # ── Stage 1: CI ───────────────────────────────────────────────────────────── + build: + name: Check & Build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: JS syntax check + run: | + find src -name "*.js" | xargs -I{} node --check {} + echo "✓ JS syntax OK" + + - name: Svelte check + run: npx svelte-check --tsconfig ./jsconfig.json 2>&1 | tail -5 + + - name: Build + run: npm run build + env: + DATABASE_URL: ./dummy.db + RATE_LIMIT_PUBLISH: '5' + RATE_LIMIT_READ: '100' + PUBLIC_BASE_URL: 'https://example.com' + + # ── Stage 2: Deploy ────────────────────────────────────────────────────────── + deploy: + name: Deploy to Production + runs-on: ubuntu-latest + needs: build # only runs if build job passes + + steps: + - name: Deploy via SSH + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.DEPLOY_HOST }} + username: ${{ secrets.DEPLOY_USER }} + key: ${{ secrets.DEPLOY_SSH_KEY }} + port: ${{ secrets.DEPLOY_PORT }} + script: | + set -e + + APP_DIR=/opt/etc-prs/app + APP_USER=prs + + echo "▸ Pulling latest code…" + cd "$APP_DIR" + git pull + + echo "▸ Installing dependencies…" + npm install --quiet + + echo "▸ Building…" + npm run build + + echo "▸ Fixing ownership…" + chown -R "${APP_USER}:${APP_USER}" "$APP_DIR" + + echo "▸ Reloading PM2…" + pm2 reload etc-prs + + echo "✓ Deploy complete"