4.2 KiB
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/<domain>/compose.yml-- Per-app compose files.gitea/workflows/deploy-<app>.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/<domain>:latest - Cert name = config filename without
.conf(at/etc/letsencrypt/live/<name>/) - One domain per conf file -- deploy.sh derives cert name from filename
- All HTTP redirects to HTTPS; bare domain redirects to
www.<domain> - The
sharedDocker 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)hantim-garage-rpc-secret-- Garage cluster RPC secret (used by deploy.sh)hantim-garage-argento-node-id-- Argento's Garage node ID + address (used by deploy.sh)hantim-garage-admin-token-- Garage admin API token (used by deploy.sh, new-app.sh)hantim-garage-media-key-id-- S3 access key ID for media uploads (used by new-app.sh)hantim-garage-media-secret-key-- S3 secret key for media uploads (used by aws CLI)
Machine accounts:
hantim-server-- access token stored at/etc/bws-tokenon hantimhantim-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 <domain> 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 <name> 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-<app>.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:
- Reading scripts and checking idempotency
- Walking through each use case in USECASES.md
- 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