diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ea36006..b83b778 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -92,7 +92,7 @@ parses it to determine the action: - `deploy-` — deploy a specific app - `deploy-nginx` — deploy nginx (fails if certs are missing) -- `cert-` — issue a cert only (via `certbot --webroot`, zero downtime) +- `cert-` — issue a cert only (via `certbot --webroot`, zero downtime; subdomains like `garage.hantim.net` skip the `www.` variant) - `provision` — run `configure.sh` ## SSL certificate handling diff --git a/CLAUDE.md b/CLAUDE.md index 65944c6..3e72c11 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,7 @@ for technical deep-dives, USECASES.md for expected behaviors. - `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, 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//compose.yml` -- Per-app compose files diff --git a/README.md b/README.md index 7d8a1fd..e7a08f2 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ scripts/ deploy.sh # Deploy + provision (called via SSH) tools/ app.sh # Add a new static site (run from dev machine) - service.sh # Manage Docker services by subcommand (dns, files, all) + service.sh # Manage Docker services by subcommand (dns, files, nginx, cert, all) remove-app.sh # Remove a static site (run from dev machine) docker/ nginx/ # Reverse proxy (nginx:alpine) + SSL config diff --git a/USECASES.md b/USECASES.md index 972fddf..876fe7e 100644 --- a/USECASES.md +++ b/USECASES.md @@ -89,7 +89,20 @@ for stateless static sites. - Existing certs skipped - Services restarted -## 8. Upload media files +## 8. Add a new Docker service + +**Trigger:** `./tools/service.sh all ` from dev machine. + +**Prerequisites:** `bws`/`jq`/`dig` installed. + +**Expected outcome:** +- DNS A record created for `.hantim.net` +- Compose file and deploy workflow scaffolded +- Nginx conf created proxying `.hantim.net` to port `` +- SSL cert issued for `.hantim.net` (no www variant) +- Service ready for customization (edit compose.yml image, add `.env.keys` if needed) + +## 9. Upload media files **Trigger:** `aws s3 cp` or `aws s3 sync` to `s3://example.com/`. diff --git a/scripts/deploy.sh b/scripts/deploy.sh index ea8c2d8..d423906 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -6,12 +6,21 @@ CMD="${SSH_ORIGINAL_COMMAND:-${1:-}}" if [[ "$CMD" =~ ^cert-[a-zA-Z0-9._-]+$ ]]; then APP="${CMD#cert-}" + # Subdomains (e.g., garage.hantim.net) don't have www variants + if [[ "$APP" == *.*.* ]]; then + CERT_DOMAINS="-d $APP" + SERVER_NAMES="$APP" + else + CERT_DOMAINS="-d $APP -d www.$APP" + SERVER_NAMES="$APP www.$APP" + fi + # Write temporary HTTP-only config so nginx can serve ACME challenges cat > "/opt/hantim/docker/nginx/conf.d/$APP.conf" </dev/null || rm -f "/opt/hantim/docker/nginx/conf.d/$APP.conf" trap - EXIT docker exec nginx nginx -t && docker exec nginx nginx -s reload diff --git a/tools/service.sh b/tools/service.sh index d176cfe..2335f07 100755 --- a/tools/service.sh +++ b/tools/service.sh @@ -5,16 +5,20 @@ usage() { echo "Usage: ./tools/service.sh " echo "" echo "Commands:" - echo " dns Create A record for .hantim.net" - echo " files Create compose.yml and deploy workflow" - echo " all Run dns + files" + echo " dns Create A record for .hantim.net" + echo " files Create compose.yml and deploy workflow" + echo " nginx Create nginx conf for .hantim.net" + echo " cert Issue SSL certificate for .hantim.net" + echo " all Run dns + files + nginx + cert" echo "" echo "Examples:" echo " ./tools/service.sh files garage" - echo " ./tools/service.sh dns garage # garage.hantim.net -> server IP" - echo " ./tools/service.sh all garage" + echo " ./tools/service.sh dns garage # garage.hantim.net -> server IP" + echo " ./tools/service.sh nginx garage 3903 # create nginx conf" + echo " ./tools/service.sh cert garage # issue SSL cert" + echo " ./tools/service.sh all garage 3903" echo "" - echo "Requires: bws, jq, dig (for dns)" + echo "Requires: bws, jq, dig (for dns/cert)" exit 1 } @@ -80,6 +84,25 @@ require_dig() { fi } +resolve_server_ip() { + require_dig + SERVER_IP=$(dig +short hantim.net | head -1) + if [ -z "$SERVER_IP" ]; then + echo "Error: Could not resolve hantim.net to get server IP." + exit 1 + fi +} + +setup_deploy_key() { + require_bws + echo "==> Fetching deploy key..." + DEPLOY_KEY=$(bws_get "hantim-deploy-ssh-private-key") + DEPLOY_KEY_FILE=$(mktemp) + echo "$DEPLOY_KEY" > "$DEPLOY_KEY_FILE" + chmod 600 "$DEPLOY_KEY_FILE" + trap "rm -f $DEPLOY_KEY_FILE" EXIT +} + validate_name() { local name="$1" if [ -z "$name" ]; then @@ -228,11 +251,73 @@ OUTER echo " configure.sh will generate the .env file on the server automatically." } +cmd_nginx() { + local name="$1" + local port="$2" + local fqdn="$name.hantim.net" + + if [ -e "$REPO_ROOT/docker/nginx/conf.d/$fqdn.conf" ]; then + echo " docker/nginx/conf.d/$fqdn.conf already exists, skipping." + return + fi + + echo "==> Creating docker/nginx/conf.d/$fqdn.conf..." + cat > "$REPO_ROOT/docker/nginx/conf.d/$fqdn.conf" < Issuing SSL certificate for $fqdn via deploy user..." + ssh -o StrictHostKeyChecking=accept-new -i "$DEPLOY_KEY_FILE" deploy@"$SERVER_IP" "cert-$fqdn" + echo " Certificate issued." +} + cmd_all() { local name="$1" + local port="$2" cmd_dns "$name" cmd_files "$name" + cmd_nginx "$name" "$port" + cmd_cert "$name" } # --- Dispatch --- @@ -246,9 +331,27 @@ case "$COMMAND" in validate_name "${2:-}" cmd_files "${2:-}" ;; + nginx) + validate_name "${2:-}" + if [ -z "${3:-}" ]; then + echo "Error: port is required for nginx command." + echo " Usage: ./tools/service.sh nginx " + exit 1 + fi + cmd_nginx "${2:-}" "${3:-}" + ;; + cert) + validate_name "${2:-}" + cmd_cert "${2:-}" + ;; all) validate_name "${2:-}" - cmd_all "${2:-}" + if [ -z "${3:-}" ]; then + echo "Error: port is required for all command." + echo " Usage: ./tools/service.sh all " + exit 1 + fi + cmd_all "${2:-}" "${3:-}" ;; *) echo "Unknown command: $COMMAND"