# CLAUDE.md ## Project overview Server provisioning and app management for the hantim webserver. This repo lives at `/opt/hantim` on the production server (Rocky Linux 9 on Vultr) and manages Docker Compose configs, deploy scripts, nginx configs, and Gitea Actions workflows. ## Key files - `scripts/bootstrap.sh` -- First-time server setup (manual, uses Bitwarden) - `scripts/configure.sh` -- Idempotent server config (firewall, certbot, start services; CI-safe) - `scripts/deploy.sh` -- SSH-triggered deploy + cert issuance + provision (deploy-*, cert-*, provision commands) - `tools/new-app.sh` -- Single-command new static site (DNS + Gitea repo + cert + deploy + verify) - `tools/new-service.sh` -- Scaffold a new Docker Compose service (compose + workflow) - `tools/remove-app.sh` -- Remove a static site (local files + Gitea repo) - `docker/nginx/` -- Reverse proxy (nginx:alpine + certbot on host) - `docker//compose.yml` -- Per-app compose files - `.gitea/workflows/deploy-.yml` -- Per-app deploy workflows - `.gitea/workflows/provision.yml` -- Triggers configure.sh on setup changes ## Conventions - **App name = domain** (e.g., `example.com`) - **Container name** = domain with dots replaced by underscores (e.g., `example_com`) - **Image** = `git.timothykim.net/hantim/:latest` - **Cert name** = config filename without `.conf` (at `/etc/letsencrypt/live//`) - **One domain per conf file** -- deploy.sh derives cert name from filename - All HTTP redirects to HTTPS; bare domain redirects to `www.` - The `shared` Docker network is created by nginx compose and used by all apps ## Secrets Bitwarden Secrets Manager (project: `hantim`, fetched via `bws` CLI): - `hantim-ci-registry-push` -- Gitea registry token (used by bootstrap.sh) - `hantim-deploy-ssh-private-key` -- deploy user SSH private key (used by new-app.sh) - `hantim-deploy-ssh-public-key` -- deploy user SSH public key (used by bootstrap.sh) - `hantim-new-app-script` -- Gitea API token for creating repos (used by new-app.sh) - `hantim-vultr-api-key` -- Vultr API key for DNS management (used by new-app.sh) Machine accounts: - `hantim-server` -- access token stored at `/etc/bws-token` on hantim - `hantim-ci` -- access token stored as Gitea secret (reserved for future use) Gitea org-level (`hantim`) secrets/variables: - `DEPLOY_HOST` -- Variable (server IP) - `DEPLOY_SSH_KEY` -- Secret (deploy SSH private key) - `CI_REGISTRY_TOKEN` -- Secret (registry token) ## Working with this repo ### Adding a new static site Run `./tools/new-app.sh ` from dev machine. Handles everything: DNS, Gitea repo, SSL cert, commit/push, build trigger, and verification. See USECASES.md for full details. ### Adding a new Docker service Run `./tools/new-service.sh ` to scaffold the compose file and deploy workflow, then edit the compose file and commit. ### Editing scripts All shell scripts use `set -euo pipefail`. Keep all steps idempotent. `configure.sh` must be safe to re-run from CI. ### Deploy flow App repo push -> `build.yml` builds image, pushes to registry, SSHes to server -> `deploy.sh` pulls repo, pulls image, restarts container. Infrastructure push (this repo) -> `deploy-.yml` triggers on path match -> same `deploy.sh` flow. nginx deploys test config with `nginx -t` first. Setup change push -> `provision.yml` triggers -> `deploy.sh provision` -> `configure.sh` (firewall, certbot, start services). ## Build and test No build step. No tests. Verify changes by: 1. Reading scripts and checking idempotency 2. Walking through each use case in USECASES.md 3. Cross-checking documentation (README.md, ARCHITECTURE.md, USECASES.md) against scripts ## Commit style - Short, lowercase commit messages (e.g., "add example.com", "fix deploy command") - Do NOT include "Co-Authored-By" lines - Commit only when explicitly asked