Files
hantim-server/CLAUDE.md
T

3.8 KiB

CLAUDE.md

Quick context for Claude Code. See README.md for full details, ARCHITECTURE.md for technical deep-dives, USECASES.md for expected behaviors.

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/app.sh -- App provisioning by subcommand (dns, repo, files, cert, garage, build, verify, all)
  • tools/service.sh -- Manage Docker services by subcommand (dns, files, nginx, cert, all)
  • tools/remove-app.sh -- Remove a static site (local files + Gitea repo)
  • docker/nginx/conf.d/ -- Per-app nginx server blocks
  • docker/<domain>/compose.yml -- Per-app compose files
  • docker/garage/ -- Garage S3 object storage (Dockerfile, garage.toml, .env.keys)
  • .gitea/workflows/ -- Per-app deploy workflows + provision.yml

Conventions

  • App name = domain (e.g., example.com)
  • Container name = dots → underscores (e.g., example_com)
  • One domain per conf file -- deploy.sh derives cert name from filename
  • All HTTP → HTTPS; bare domain → www.<domain>
  • All shell scripts use set -euo pipefail and must be idempotent
  • The shared Docker network is created by nginx compose and used by all apps
  • Secrets are in Bitwarden Secrets Manager (see README.md for full list)
  • Service secrets use .env.keys files (see README.md)

Deploy flow

  • App repo push → build.yml → build/push image → SSH deploy-<domain> → deploy.sh
  • This repo push → deploy-<app>.yml (path match) → SSH deploy-<app> → deploy.sh
  • Setup change → provision.yml → SSH provision → configure.sh

Build and test

No build step. Run e2e tests with:

uvx pytest tests/test_app_e2e.py -v -x

Prerequisites

  • uv installed (curl -LsSf https://astral.sh/uv/install.sh | sh)
  • CLI tools: bws, jq, dig, ssh, curl, git
  • bws token: set BWS_ACCESS_TOKEN env var or save to ~/.config/hantim/bws-token
  • Vultr API key must allow requests from the machine's IP (check https://my.vultr.com/settings/#settingsapi)

Test structure

  • tests/conftest.py -- Fixtures: preflight checks, bws secrets, API helper, cleanup
  • tests/test_app_e2e.py -- E2E tests for tools/app.sh against real services
  • tests/test_security_headers.py -- Verify security headers on live sites
  • tests/test_default_server.py -- Verify unknown Host headers are dropped
  • tests/test_tls.py -- Verify TLS hardening

E2E tests (test_app_e2e.py)

Tests use kgfamily.com as a throwaway test domain. Two phases:

  1. TestAppSubcommands -- Runs each app.sh subcommand individually (dns, repo, files, cert, garage, build, monitor), verifies side effects via API, then cleans up. Does not test verify (needs full deploy flow).
  2. TestAppAll -- Runs app.sh all kgfamily.com, verifies everything including site liveness and git commit, then cleans up (including reverting the commit+push).

Cleanup is done directly via APIs (Vultr, Gitea, Garage, UptimeRobot) + local file deletion. Does NOT use remove-app.sh to avoid coupling. Runs before and after each phase to handle leftovers from crashed runs.

Known issue

  • Vultr API keys can be IP-restricted. If test_dns fails with HTTP 401, add the machine's IP to the Vultr API access control list.

Other tests

The security/TLS/default-server tests run against live sites and are triggered automatically after nginx deploy. They can also be run locally:

uvx pytest tests/ -v --ignore=tests/test_app_e2e.py

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