Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -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 }}"
37 changes: 37 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down