From f37269622d476e415204c704a8ef6ba41c22d527 Mon Sep 17 00:00:00 2001 From: Marc Date: Tue, 18 Aug 2026 18:01:23 -0400 Subject: [PATCH] feat: deploy to CodeBoxx Web Claude droplet via GitHub Actions Adds a workflow that builds the Vite site and rsyncs dist/ to the DigitalOcean droplet over SSH on push to main, using a dedicated non-root deploy user. Droplet-side (nginx, deploy user, /var/www/codeboxx) already provisioned. --- .github/workflows/deploy.yml | 41 ++++++++++++++++++++++++++++++++++++ readme.md | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..248390f --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,41 @@ +name: CodeBoxx Web Claude - Deploy to DigitalOcean + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + env: + VITE_SANITY_PROJECT_ID: ${{ secrets.VITE_SANITY_PROJECT_ID }} + VITE_SANITY_DATASET: ${{ secrets.VITE_SANITY_DATASET }} + VITE_SANITY_API_VERSION: ${{ secrets.VITE_SANITY_API_VERSION }} + VITE_SANITY_TOKEN: ${{ secrets.VITE_SANITY_TOKEN }} + + - name: Add deploy SSH key + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.DROPLET_SSH_KEY }} + + - name: Deploy dist/ to Droplet + run: | + mkdir -p ~/.ssh + ssh-keyscan -H "${{ secrets.DROPLET_HOST }}" >> ~/.ssh/known_hosts + rsync -avz --delete dist/ "${{ secrets.DROPLET_USER }}@${{ secrets.DROPLET_HOST }}:${{ secrets.DROPLET_TARGET_PATH }}" diff --git a/readme.md b/readme.md index f904ab1..f7a830b 100644 --- a/readme.md +++ b/readme.md @@ -124,6 +124,43 @@ Brand components with no Bootstrap equivalent (`Logo`, `Avatar`) are tiny hand-w components in `src/components/`, not a vendored bundle. Everything else — buttons, badges, forms, the Codi/Enroll drawers — is react-bootstrap, restyled via the SCSS above. +## Deployment + +`.github/workflows/deploy.yml` builds the site and rsyncs `dist/` to the +"CodeBoxx Web Claude" DigitalOcean Droplet over SSH on every push to `main` (or +manually via "Run workflow"). It does not provision anything — the Droplet, nginx, +the `deploy` user, and the target directory are already set up (see below). + +Required repo secrets (Settings → Secrets and variables → Actions): + +| Secret | Value | +| ------------------------- | --------------------------------------------------------------------- | +| `VITE_SANITY_PROJECT_ID` | Same as `.env`'s `VITE_SANITY_PROJECT_ID` | +| `VITE_SANITY_DATASET` | Same as `.env`'s `VITE_SANITY_DATASET` | +| `VITE_SANITY_API_VERSION` | Same as `.env`'s `VITE_SANITY_API_VERSION` | +| `VITE_SANITY_TOKEN` | Same as `.env`'s `VITE_SANITY_TOKEN` (blank is fine if unset there) | +| `DROPLET_HOST` | `159.223.145.47` | +| `DROPLET_USER` | `deploy` — a dedicated, non-root, key-only user with no sudo | +| `DROPLET_SSH_KEY` | Private half of the `deploy` user's dedicated deploy key (no passphrase) | +| `DROPLET_TARGET_PATH` | `/var/www/codeboxx` | + +On the Droplet (already done for "CodeBoxx Web Claude"): + +- nginx installed and enabled, serving `/var/www/codeboxx` with + `try_files $uri /index.html;` in its `location /` block — required because this + is a client-side-routed SPA (`react-router` `BrowserRouter`); without the + fallback, deep links like `/blog/some-post` 404 on a hard refresh. +- A `deploy` system user owns `/var/www/codeboxx`, has no sudo access, and accepts + SSH only via the dedicated deploy key (password auth disabled). Its + `authorized_keys` holds only that key's public half. +- No domain/TLS yet — nginx answers on port 80 for any `Host` (catch-all + `server_name _;`). Point a domain's A record at the Droplet and run `certbot + --nginx` later to add HTTPS; update `server_name` accordingly at that point. + +The deploy step runs `rsync --delete`, so `DROPLET_TARGET_PATH` should stay +dedicated to this site — anything else living in that directory gets removed to +match `dist/`. + ## Structure ```