diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 13d8a7c..4bfbfdb 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -114,7 +114,7 @@ Developer pushes to hantim-server -> deploy-.yml triggers (based on changed paths under docker//) -> SSH to deploy@hantim as deploy- -> deploy.sh: git pull, docker compose pull, docker compose up -d - -> (nginx only): test config with nginx -t before applying + -> (nginx only): auto-issues certs for new domains, then tests config with nginx -t ``` ### Setup change (push to hantim-server) @@ -131,7 +131,10 @@ Developer pushes change to scripts/configure.sh - **App name = domain** (e.g., `hcsuzuki.net`) - **Container name** = domain with dots replaced by underscores (e.g., `hcsuzuki_net`) -- **Cert name** = domain (stored at `/etc/letsencrypt/live//`) +- **Cert name** = config filename without `.conf` (stored at `/etc/letsencrypt/live//`) +- **One domain per conf file** -- `deploy.sh` derives the cert name from the conf + filename and collects all `server_name` values for that cert. Putting unrelated + domains in the same conf file will bundle them into one cert incorrectly. - **Image name** = `git.timothykim.net/hantim/:latest` - All HTTP traffic redirects to `https://www.` - Bare domain HTTPS redirects to `https://www.` diff --git a/CLAUDE.md b/CLAUDE.md index c2b6c57..d92ff0b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,8 @@ Actions workflows. - **App name = domain** (e.g., `hcsuzuki.net`) - **Container name** = domain with dots replaced by underscores (e.g., `hcsuzuki_net`) - **Image** = `git.timothykim.net/hantim/:latest` -- **Cert name** = domain (at `/etc/letsencrypt/live//`) +- **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 diff --git a/USECASES.md b/USECASES.md index 56cbf3d..cc3a350 100644 --- a/USECASES.md +++ b/USECASES.md @@ -119,9 +119,16 @@ git push **What happens:** 1. `deploy-nginx.yml` workflow triggers (path match) 2. `deploy.sh` pulls latest code -3. Tests nginx config with `nginx -t` -- if it fails, deploy aborts and the +3. If any conf files reference certs that don't exist yet, nginx is briefly + stopped and certs are issued via standalone certbot (auto-restarts on failure) +4. Tests nginx config with `nginx -t` -- if it fails, deploy aborts and the running nginx is untouched -4. Runs `docker compose up -d` and reloads nginx +5. Runs `docker compose up -d` and reloads nginx + +**Note:** Each conf file must contain server blocks for only one domain. The +cert name is derived from the filename (e.g. `hcsuzuki.net.conf` -> cert +`hcsuzuki.net`). Redirect-only domains like `hcsuzukiviolin.com` get their +own conf file. ## 7. Re-run configure on existing server diff --git a/scripts/deploy.sh b/scripts/deploy.sh index d18de4e..016d097 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -54,7 +54,34 @@ if ! docker compose pull; then fi if [ "$APP" = "nginx" ]; then - # Test config before applying — a bad config (e.g. missing cert) would take down all sites + # Issue certs for any new domains (brief nginx downtime while certbot binds port 80) + missing_certs=false + for conf in /opt/hantim/docker/nginx/conf.d/*.conf; do + cert_name=$(basename "$conf" .conf) + if [ ! -d "/etc/letsencrypt/live/$cert_name" ]; then + missing_certs=true + break + fi + done + if [ "$missing_certs" = true ]; then + docker stop nginx 2>/dev/null || true + trap 'docker start nginx 2>/dev/null || true' EXIT + for conf in /opt/hantim/docker/nginx/conf.d/*.conf; do + cert_name=$(basename "$conf" .conf) + if [ ! -d "/etc/letsencrypt/live/$cert_name" ]; then + domains=$(grep -oP 'server_name\s+\K[^;]+' "$conf" | tr ' ' '\n' | sort -u) + domain_args="" + for d in $domains; do + domain_args="$domain_args -d $d" + done + echo "Issuing cert for $cert_name..." + certbot certonly --standalone --non-interactive --agree-tos \ + --register-unsafely-without-email --cert-name "$cert_name" $domain_args + fi + done + trap - EXIT + fi + # Test config before applying — a bad config would take down all sites docker compose run --rm -T nginx nginx -t fi