dorfteich/deploy/self-hosting-verify.sh
Claude Fable 5 4b55fb92ac
All checks were successful
CD / Build and push images (push) Successful in 1m7s
CD / Deploy to Test (push) Successful in 10s
CD / Smoke tests against Test (push) Successful in 1m8s
CD / Promote to Int (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 3m13s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m18s
CI / Import/export fidelity gate (push) Successful in 46s
Write the self-hosting guide and add the optional caddy TLS profile (#88)
docs/self-hosting/README.md is the complete operator contract: install
from the two reference files, first-run wizard walkthrough, update
procedure with the one-release downgrade window, backup/restore with the
sidecar, readyz-based troubleshooting (incl. the classic proxy/WebSocket
and APP_BASE_URL/CSRF mistakes), and a build-from-source note; linked
from the repository README; English-only by documented decision. The
reference compose gains a `caddy` profile (new Caddyfile) that publishes
80/443 and terminates TLS via Let's Encrypt for $DOMAIN — localhost uses
Caddy's internal CA for smoke tests. deploy/self-hosting-verify.sh
scripts the clean-machine test: a fresh directory with only the
published files boots to the wizard answering over TLS, then removes
itself; verified green on the stage host.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-11 21:21:10 +02:00

77 lines
3.2 KiB
Bash
Executable File

#!/usr/bin/env sh
# Scripted clean-machine install test for the self-hosting guide (issue #88):
# proves that a fresh directory containing ONLY the published reference files
# (docker-compose.yml, Caddyfile, .env.example) boots to a working first-run
# wizard behind the caddy profile's TLS. Uses its own compose project name,
# high ports, and DOMAIN=localhost (internal-CA certificate), then removes
# everything — safe to run on a host with live stacks.
#
# Environment:
# IMAGE_PREFIX (default gitea.101010.cloud/stwaidele/dorfteich)
# TAG (default test; a release = its semver tag)
# HTTPS_PORT (default 8443) HTTP_PORT (default 8480)
set -eu
IMAGE_PREFIX="${IMAGE_PREFIX:-gitea.101010.cloud/stwaidele/dorfteich}"
TAG="${TAG:-test}"
HTTPS_PORT="${HTTPS_PORT:-8443}"
HTTP_PORT="${HTTP_PORT:-8480}"
HERE=$(cd "$(dirname "$0")" && pwd)
PROJECT="dorfteich-selfhost-verify-$(date +%s)"
DIR=$(mktemp -d)
log() { echo "self-hosting-verify: $*"; }
fail() { echo "self-hosting-verify: FAILED — $*" >&2; exit 1; }
cleanup() {
log "tearing down $PROJECT"
(cd "$DIR" && docker compose --profile caddy down -v --remove-orphans >/dev/null 2>&1) || true
rm -rf "$DIR"
}
trap cleanup EXIT
# --- the guide's install steps, scripted -------------------------------------
cp "$HERE/compose/docker-compose.yml" "$HERE/compose/Caddyfile" "$DIR/"
cp "$HERE/compose/.env.example" "$DIR/.env"
chmod 600 "$DIR/.env"
edit() { sed -i.bak "s|^#*$1=.*|$1=$2|" "$DIR/.env" && rm "$DIR/.env.bak"; }
edit POSTGRES_PASSWORD "verify-$(date +%s%N | tail -c 13)"
edit COLLAB_TOKEN_SECRET "verify-secret-$(date +%s%N)"
edit IMAGE_PREFIX "$IMAGE_PREFIX"
edit TAG "$TAG"
edit COMPOSE_PROJECT_NAME "$PROJECT"
edit WEB_PORT 0 && edit API_PORT 0 && edit COLLAB_PORT 0 # ephemeral host ports
edit APP_BASE_URL "https://localhost:$HTTPS_PORT"
edit DOMAIN localhost
edit CADDY_HTTP_PORT "$HTTP_PORT"
edit CADDY_HTTPS_PORT "$HTTPS_PORT"
# Mail stays unconfigured — exactly the guide's "skip SMTP for now" path.
edit SMTP_HOST "" && edit SMTP_PORT "" && edit SMTP_SECURE ""
edit SMTP_USER "" && edit SMTP_PASS "" && edit SMTP_FROM ""
log "starting $PROJECT from $DIR (images $IMAGE_PREFIX-*:$TAG)"
(cd "$DIR" && docker compose --profile caddy pull --quiet && docker compose --profile caddy up -d)
# --- the guide's promise: the wizard answers over TLS ------------------------
SETUP=""
for _ in $(seq 1 60); do
SETUP=$(curl -sk "https://localhost:$HTTPS_PORT/api/v1/setup" || true)
case "$SETUP" in *'"status":"required"'*) break ;; esac
sleep 2
done
case "$SETUP" in
*'"status":"required"'*) log "wizard reachable over TLS: $SETUP" ;;
*) (cd "$DIR" && docker compose logs api caddy | tail -40); fail "wizard never answered: $SETUP" ;;
esac
echo | openssl s_client -connect "localhost:$HTTPS_PORT" -servername localhost 2>/dev/null \
| grep -q 'Caddy Local Authority' || fail "TLS certificate is not Caddy-issued"
log "TLS certificate issued by Caddy's internal CA (a real DOMAIN gets Let's Encrypt)"
curl -sk "https://localhost:$HTTPS_PORT/" | grep -qi '<div id="root">' \
|| fail "web app shell not served through the ingress"
log "SPA shell served through the ingress"
log "OK — a clean install following only the guide reaches the working wizard"