deviant/scripts/deploy-deviant-navigate-st.sh
Local Dev 2e4a85cea8 brand: website fonts in SVG + more social formats; deploy script fixes
Fonts: every text-carrying brand SVG (wordmark, OG, twitter-card, twitter-header,
FB cover, LinkedIn banner, IG square) now @imports Fraunces + DM Sans + JetBrains
Mono from Google Fonts inside a <style> block — so browsers render the website's
own faces. Fallback stack (Georgia / system sans) still applies in renderers that
ignore remote CSS (rsvg-convert). README documents the trade-off.

New social sizes:
- twitter-card-1200x675.svg — Twitter's current summary_large_image spec (was 1200×600, wrong)
- twitter-header-1500x500.svg — profile banner with lower-left profile-photo safe zone
- facebook-cover-820x312.svg — Facebook page cover
- linkedin-banner-1584x396.svg — LinkedIn company banner with profile-photo safe zone
- instagram-square-1080x1080.svg — IG feed / Threads / Discord
(retired the wrong 1200×600 Twitter card)

Deploy script fixes (scripts/deploy-deviant-navigate-st.sh):
- Portable DNS check — getent → dig/nslookup fallback so it runs on Git Bash / macOS
- Phase 1 awk: match the testchipnet :80 block SPECIFICALLY (was matching :80 and :443,
  causing two duplicate deviant.navigate.st :80 blocks and no :443)
- Phase 3 skip check: verify a :443 block actually exists (was counting occurrences of
  the server_name line, which the Phase 1 bug already inflated)
Live prod was patched manually before this commit; the script is now safe for greenfield.
2026-08-29 22:50:13 +02:00

158 lines
7 KiB
Bash

#!/usr/bin/env bash
# Add deviant.navigate.st as an ICANN mirror of the deviant.x wallet on the
# shared VPS (130.12.242.84). Idempotent: safe to re-run.
#
# Runs in three phases:
# Phase 1: patch nginx.conf with :80 ACME block + reload
# Phase 2: issue LetsEncrypt cert via webroot (--webroot -w /opt/checkers/nginx/html/deviant)
# Phase 3: patch nginx.conf with :443 vhost + reload + verify
#
# Prereqs:
# - DNS: deviant.navigate.st -> 130.12.242.84 (verified before running)
# - SSH key: ~/.ssh/checkers_deploy
#
# Pattern cloned from checkers.x.navigate.st in the same nginx.conf.
# Single-file bind mount: edits happen in place (cat > file), NOT sed -i / mv.
set -euo pipefail
SSH_KEY="${SSH_KEY:-$HOME/.ssh/checkers_deploy}"
HOST="${HOST:-root@130.12.242.84}"
NAME="deviant.navigate.st"
NGINX_CONF="/opt/checkers/nginx/nginx.conf"
WEBROOT="/opt/checkers/nginx/html/deviant"
CONTAINER="checkers-nginx-1"
log() { printf '\n\033[1;36m▸ %s\033[0m\n' "$*"; }
sshx() { ssh -i "$SSH_KEY" -o ConnectTimeout=10 "$HOST" "$@"; }
# ---------------------------------------------------------------------------
log "Phase 0 — DNS check"
# Portable across Linux (getent), macOS/Windows-Git-Bash (nslookup/dig fallback).
resolved=""
if command -v getent >/dev/null 2>&1; then
resolved=$(getent hosts "$NAME" 2>/dev/null | awk '{print $1}' | head -1)
elif command -v dig >/dev/null 2>&1; then
resolved=$(dig +short "$NAME" @8.8.8.8 | head -1)
elif command -v nslookup >/dev/null 2>&1; then
resolved=$(nslookup "$NAME" 8.8.8.8 2>/dev/null | awk '/^Address: / && !/#53$/ {print $2; exit}')
fi
[[ -n "$resolved" ]] || { echo "ERROR: $NAME does not resolve (tried getent/dig/nslookup)"; exit 1; }
echo "$NAME resolves to $resolved"
# ---------------------------------------------------------------------------
log "Phase 1 — install :80 ACME block + reload"
sshx "cp $NGINX_CONF ${NGINX_CONF}.bak.\$(date +%Y%m%d-%H%M%S)"
# If the :80 block for our name is already present, skip.
# Match specifically: server_name deviant.navigate.st; INSIDE a :80 block.
if sshx "awk 'BEGIN{IGNORECASE=0} /listen 80;/ {p80=1} p80 && /server_name deviant.navigate.st;/ {found=1; exit} /^ }\$/ {p80=0} END{exit found?0:1}' $NGINX_CONF"; then
echo ":80 block for $NAME already present, skipping Phase 1 insert"
else
# Insert the :80 block right after the FIRST closing brace that follows the testchipnet :80
# server_name line (i.e. the testchipnet :80 block's closing brace, NOT the :443 one).
sshx "awk '
{ print }
/^ server_name testchipnet\\.duckdns\\.org;\$/ && !seen_test {
in_test80=1
}
in_test80 && /^ \\}\$/ && !seen_test {
seen_test=1; in_test80=0
print \"\"
print \" # ── Deviant landing (deviant.navigate.st) — ICANN mirror of deviant.x ──\"
print \" server {\"
print \" listen 80;\"
print \" server_name deviant.navigate.st;\"
print \" location /.well-known/acme-challenge/ {\"
print \" root /usr/share/nginx/html/deviant;\"
print \" }\"
print \" location / { return 301 https://\\\$host\\\$request_uri; }\"
print \" }\"
}
' $NGINX_CONF > /tmp/nginx.conf.p1 && cat /tmp/nginx.conf.p1 > $NGINX_CONF && rm /tmp/nginx.conf.p1"
fi
log "nginx -t (phase 1)"
sshx "docker exec $CONTAINER nginx -t"
log "nginx -s reload (phase 1)"
sshx "docker exec $CONTAINER nginx -s reload"
# ---------------------------------------------------------------------------
log "Phase 2 — LetsEncrypt cert via webroot"
if sshx "[ -d /etc/letsencrypt/live/$NAME ]"; then
echo "cert already present, skipping issuance"
else
sshx "certbot certonly --non-interactive --agree-tos --register-unsafely-without-email --webroot -w $WEBROOT -d $NAME"
fi
# ---------------------------------------------------------------------------
log "Phase 3 — install :443 vhost + reload"
# If the :443 block for our name is already present, skip.
# Match specifically: server_name deviant.navigate.st; INSIDE a :443 block.
if sshx "awk '/listen 443 ssl;/ {p443=1} p443 && /server_name deviant.navigate.st;/ {found=1; exit} /^ }\$/ {p443=0} END{exit found?0:1}' $NGINX_CONF"; then
echo ":443 block already present, skipping Phase 3 insert"
else
# Insert the :443 block right after the existing testchipnet :443 block's closing brace.
# The testchipnet :443 block's LAST line has "try_files \$uri \$uri/ /index.html; }" — its
# closing brace comes on the next indented line. We match the try_files line and the
# following " }" to know we're at that specific block's end.
sshx "awk '
{ buf[++n] = \$0 }
END {
done=0
for (i=1; i<=n; i++) {
print buf[i]
if (!done && buf[i] ~ /try_files .* \\/index\\.html; \\}\$/) {
# find the next line that is exactly \" }\" - that closes the testchipnet 443 block
j = i+1
while (j<=n && buf[j] !~ /^ \\}\$/) { print buf[j]; j++ }
if (j<=n) { print buf[j]; i = j }
print \"\"
print \" # -- Deviant landing (deviant.navigate.st) -- HTTPS mirror of deviant.x --\"
print \" server {\"
print \" listen 443 ssl;\"
print \" server_name deviant.navigate.st;\"
print \" ssl_certificate /etc/letsencrypt/live/deviant.navigate.st/fullchain.pem;\"
print \" ssl_certificate_key /etc/letsencrypt/live/deviant.navigate.st/privkey.pem;\"
print \" ssl_protocols TLSv1.2 TLSv1.3;\"
print \" ssl_ciphers HIGH:!aNULL:!MD5;\"
print \" ssl_session_cache shared:SSL:10m;\"
print \" root /usr/share/nginx/html/deviant;\"
print \" index index.html;\"
print \" location = /sw.js { add_header Cache-Control \\\"no-cache\\\"; }\"
print \" location = /index.html { add_header Cache-Control \\\"no-cache\\\"; }\"
print \" location = /manifest.webmanifest { default_type application/manifest+json; add_header Cache-Control \\\"no-cache\\\"; }\"
print \" location /assets/ { add_header Cache-Control \\\"public, max-age=31536000, immutable\\\"; }\"
print \" location / { add_header Cache-Control \\\"no-cache\\\"; try_files \\\$uri \\\$uri/ /index.html; }\"
print \" }\"
done=1
}
}
}
' $NGINX_CONF > /tmp/nginx.conf.p3 && cat /tmp/nginx.conf.p3 > $NGINX_CONF && rm /tmp/nginx.conf.p3"
fi
log "nginx -t (phase 3)"
sshx "docker exec $CONTAINER nginx -t"
log "nginx -s reload (phase 3)"
sshx "docker exec $CONTAINER nginx -s reload"
# ---------------------------------------------------------------------------
log "Verify"
sleep 2
echo "== HTTPS handshake =="
curl -sI "https://$NAME/" | head -6
echo
echo "== cert subject =="
echo | openssl s_client -connect "$NAME:443" -servername "$NAME" 2>/dev/null \
| openssl x509 -noout -subject -dates
echo
echo "== body =="
curl -s "https://$NAME/" | grep -oE '<title>[^<]+</title>' | head -1
log "Done. https://$NAME/ is live."