The Caddyfile is reassembled every run, but the WebFinger decision reads BASE_DOMAIN/REDIRECT_URL from .env, and .env was written only on the first run. So enabling WebFinger on a re-run was a silent no-op: the operator answers the apex prompt, and the script prints the green "[+] No BASE_DOMAIN -- pocket-id only" -- contradicting what they just typed -- then pulls, restarts and prints DEPLOYED with exit 0. The failure is invisible at the far end too. Caddy has no site block for the apex, so it never gets a cert for it and discovery fails with a TLS or connection error rather than a 404. Nothing warns; the one contradicting line is a green [+] among docker pull output. Moving the apex had the squid shape: compose reads the shell environment first, so the new value served for that run only, and the documented later `docker compose up -d` fell back to stale .env and reverted it. Record which of the two keys arrived in the environment BEFORE the ":=" defaults, then write those through with a set_env upsert. Only keys actually passed are touched: automations.sh drops a blank optional answer, so "blank" cannot be told apart from "not supplied" and must not be read as "disable". Since that means the prompt cannot retire WebFinger, the enabled path now says so and points at the .env edit that can. Verified: enabling on a re-run lands in .env and appends the block; changing the apex lands and is logged; passing nothing touches nothing; BASE_DOMAIN without REDIRECT_URL still dies rather than half-enabling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
368 lines
17 KiB
Bash
368 lines
17 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# deploy.sh -- deploy the pocket-id stack (caddy + anubis + pocket-id) on
|
|
# Alpine Linux. Single-node, dedicated host: runs everything as root.
|
|
#
|
|
# What this does:
|
|
# 1. Installs docker + docker-cli-compose if missing.
|
|
# 2. Lays down docker-compose.yml, Caddyfile, .env.example in $STACK_DIR.
|
|
# 3. Generates .env on first run with random ENCRYPTION_KEY and
|
|
# ANUBIS_PID_KEY. Existing .env is never overwritten.
|
|
# 4. Prompts for POCKETID_DOMAIN and ACME_EMAIL if not preset.
|
|
# 5. Enables docker on boot, runs `docker compose pull && up -d`.
|
|
# 6. Waits for healthchecks to go green.
|
|
#
|
|
# Self-contained: docker-compose.yml, Caddyfile, Caddyfile.webfinger and
|
|
# .env.example are embedded at the bottom of this file as a base64-encoded
|
|
# tar.gz. The script extracts them at runtime, so this single file is all you
|
|
# need on the target box. The WebFinger block is appended only when BASE_DOMAIN
|
|
# is set in .env.
|
|
#
|
|
# To rebuild after editing the loose files: run ./build.sh in this dir.
|
|
#
|
|
# Idempotent: re-running pulls new images and recreates changed services
|
|
# without touching .env or named volumes.
|
|
#
|
|
# Usage:
|
|
# bash deploy.sh # interactive prompts
|
|
# POCKETID_DOMAIN=id.example.com ACME_EMAIL=me@example.com \
|
|
# bash deploy.sh
|
|
# STACK_DIR=/opt/pocket-id bash deploy.sh
|
|
# SKIP_DOCKER_INSTALL=1 bash deploy.sh # docker already installed
|
|
# FORCE=1 bash deploy.sh # skip confirmations
|
|
|
|
set -euo pipefail
|
|
|
|
: "${STACK_DIR:=/srv/pocket-id}"
|
|
: "${SKIP_DOCKER_INSTALL:=0}"
|
|
: "${FORCE:=0}"
|
|
: "${SKIP_PROMPTS:=0}" # non-interactive: require values via env, no prompts
|
|
[[ "$SKIP_PROMPTS" == "1" ]] && FORCE=1
|
|
: "${POCKETID_DOMAIN:=}"
|
|
: "${ACME_EMAIL:=}"
|
|
# Whether the WebFinger keys actually arrived in this script's ENVIRONMENT has to
|
|
# be recorded BEFORE the ":=" defaults below, which make an unset variable look
|
|
# like an empty one. The .env is what decides whether the WebFinger block is
|
|
# served, so a value passed to a re-run has to be written through to it.
|
|
WF_KEYS_FROM_ENV=()
|
|
for _k in BASE_DOMAIN REDIRECT_URL; do
|
|
if [[ -n "${!_k+x}" ]]; then WF_KEYS_FROM_ENV+=("$_k"); fi
|
|
done
|
|
|
|
: "${BASE_DOMAIN:=}" # optional: enables the WebFinger block (set with REDIRECT_URL)
|
|
: "${REDIRECT_URL:=}" # optional: where the base domain 301s non-webfinger traffic
|
|
|
|
log() { printf '\033[1;32m[+]\033[0m %s\n' "$*"; }
|
|
warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*" >&2; }
|
|
die() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
|
|
[[ $EUID -eq 0 ]] || die "Run as root."
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# OS detection + Docker install (Alpine / Debian / Alma). This deploy.sh is
|
|
# self-contained (scp'd standalone), so the OS logic is inlined here instead
|
|
# of sourced from scripts/oslib.sh.
|
|
# ---------------------------------------------------------------------------
|
|
osfam() {
|
|
local id="" like=""
|
|
if [[ -r /etc/os-release ]]; then
|
|
id="$(. /etc/os-release 2>/dev/null && echo "${ID:-}")"
|
|
like="$(. /etc/os-release 2>/dev/null && echo "${ID_LIKE:-}")"
|
|
fi
|
|
case " $id $like " in
|
|
*" alpine "*) echo alpine ;;
|
|
*" debian "*|*" ubuntu "*) echo debian ;;
|
|
*" rhel "*|*" fedora "*|*" centos "*) echo rhel ;;
|
|
*) echo "${id:-unknown}" ;;
|
|
esac
|
|
}
|
|
|
|
install_docker() {
|
|
if command -v docker >/dev/null 2>&1; then
|
|
log "Docker already installed: $(docker --version)"
|
|
else
|
|
log "Installing Docker (OS: $(osfam))..."
|
|
case "$(osfam)" in
|
|
alpine) apk add -q docker docker-cli-compose openrc ;;
|
|
debian|rhel) command -v curl >/dev/null 2>&1 || \
|
|
{ command -v apt-get >/dev/null 2>&1 && apt-get install -y -qq curl; } || \
|
|
{ command -v dnf >/dev/null 2>&1 && dnf install -y -q curl; }
|
|
curl -fsSL https://get.docker.com | sh ;;
|
|
*) die "Unsupported OS for auto Docker install. Set SKIP_DOCKER_INSTALL=1 and install Docker yourself." ;;
|
|
esac
|
|
fi
|
|
# Enable + start under whichever init is present.
|
|
if command -v rc-update >/dev/null 2>&1; then
|
|
rc-update add docker default >/dev/null 2>&1 || true
|
|
rc-service docker status >/dev/null 2>&1 || rc-service docker start
|
|
elif command -v systemctl >/dev/null 2>&1; then
|
|
systemctl enable --now docker >/dev/null 2>&1 || systemctl start docker || true
|
|
fi
|
|
}
|
|
|
|
open_web_ports() {
|
|
# Register 80/443 for this stack. Prefer the host firewall (harden-firewall.sh)
|
|
# when present: drop in a rule file and re-apply. Else fall back to ufw/
|
|
# firewalld if active (no-op when neither is).
|
|
#
|
|
# NOTE: Caddy publishes 80/443 via Docker, which reaches the host through the
|
|
# nat/FORWARD chains and BYPASSES the INPUT firewall -- so this is harmless
|
|
# belt-and-braces for any host-bound bind and self-documents the stack ports.
|
|
if [[ -d /etc/firewall/ports.d && -x /usr/local/sbin/firewall-apply ]]; then
|
|
log "Registering 80,443/tcp with host firewall..."
|
|
printf '80/tcp\n443/tcp\n' > /etc/firewall/ports.d/pocket-id.rule
|
|
/usr/local/sbin/firewall-apply
|
|
elif command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
|
|
log "ufw active -- allowing 80,443/tcp..."
|
|
ufw allow 80/tcp >/dev/null; ufw allow 443/tcp >/dev/null
|
|
elif command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then
|
|
log "firewalld active -- allowing http,https..."
|
|
firewall-cmd -q --add-service=http --permanent
|
|
firewall-cmd -q --add-service=https --permanent
|
|
firewall-cmd -q --reload
|
|
fi
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Extract embedded archive
|
|
# ----------------------------------------------------------------------------
|
|
SCRIPT_DIR=$(mktemp -d -t pocket-id-deploy.XXXXXX)
|
|
trap 'rm -rf "$SCRIPT_DIR"' EXIT
|
|
|
|
extract_archive() {
|
|
grep -a -A 9999999 '^__ARCHIVE_BELOW__$' "$0" \
|
|
| tail -n +2 \
|
|
| base64 -d \
|
|
| tar -xz -C "$SCRIPT_DIR"
|
|
}
|
|
|
|
if grep -q -a '^__ARCHIVE_BELOW__$' "$0"; then
|
|
log "Extracting embedded deployment files..."
|
|
extract_archive
|
|
else
|
|
die "No embedded archive found. Run build.sh to embed deployment files."
|
|
fi
|
|
|
|
for f in docker-compose.yml Caddyfile Caddyfile.webfinger .env.example; do
|
|
[[ -f "$SCRIPT_DIR/$f" ]] || die "Embedded archive missing $f"
|
|
done
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Prompt for required vars if not set
|
|
# ----------------------------------------------------------------------------
|
|
prompt() {
|
|
local varname="$1" message="$2"
|
|
local -n ref="$varname"
|
|
if [[ -z "${ref:-}" ]]; then
|
|
[[ "$SKIP_PROMPTS" == "1" ]] && die "$varname required (set it in the environment; running with SKIP_PROMPTS=1)."
|
|
read -r -p "$message: " ref
|
|
[[ -n "$ref" ]] || die "$varname required."
|
|
fi
|
|
}
|
|
prompt POCKETID_DOMAIN "Public hostname (e.g. id.example.com)"
|
|
prompt ACME_EMAIL "Let's Encrypt email"
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Docker
|
|
# ----------------------------------------------------------------------------
|
|
if [[ "$SKIP_DOCKER_INSTALL" != "1" ]]; then
|
|
install_docker
|
|
fi
|
|
open_web_ports
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Stack directory + files
|
|
# ----------------------------------------------------------------------------
|
|
log "Setting up $STACK_DIR..."
|
|
install -d -m 0750 "$STACK_DIR"
|
|
install -m 0640 "$SCRIPT_DIR/docker-compose.yml" "$STACK_DIR/docker-compose.yml"
|
|
# (Caddyfile is assembled below, after .env, so the optional WebFinger block
|
|
# can be appended when BASE_DOMAIN is set.)
|
|
|
|
ENV_FILE="$STACK_DIR/.env"
|
|
set_env() { # <KEY> <value>: update KEY in .env, or append if absent
|
|
local key="$1" val="$2" esc
|
|
esc=${val//\/\\}; esc=${esc//|/\|}; esc=${esc//&/\&}
|
|
if grep -qE "^${key}=" "$ENV_FILE"; then
|
|
sed -i -e "s|^${key}=.*|${key}=${esc}|" "$ENV_FILE"
|
|
else
|
|
printf '%s=%s
|
|
' "$key" "$val" >> "$ENV_FILE"
|
|
fi
|
|
}
|
|
|
|
if [[ ! -f "$ENV_FILE" ]]; then
|
|
log "Seeding $ENV_FILE with generated secrets..."
|
|
install -m 0600 "$SCRIPT_DIR/.env.example" "$ENV_FILE"
|
|
sed -i \
|
|
-e "s|^POCKETID_DOMAIN=.*|POCKETID_DOMAIN=${POCKETID_DOMAIN}|" \
|
|
-e "s|^ACME_EMAIL=.*|ACME_EMAIL=${ACME_EMAIL}|" \
|
|
-e "s|^ENCRYPTION_KEY=.*|ENCRYPTION_KEY=$(openssl rand -base64 32)|" \
|
|
-e "s|^ANUBIS_PID_KEY=.*|ANUBIS_PID_KEY=$(openssl rand -hex 32)|" \
|
|
-e "s|^BASE_DOMAIN=.*|BASE_DOMAIN=${BASE_DOMAIN}|" \
|
|
-e "s|^REDIRECT_URL=.*|REDIRECT_URL=${REDIRECT_URL}|" \
|
|
"$ENV_FILE"
|
|
else
|
|
log ".env exists; keeping it (secrets and values not passed are left alone)."
|
|
# The WebFinger decision below is read from .env, so a BASE_DOMAIN passed to a
|
|
# re-run has to land there -- otherwise enabling WebFinger is a silent no-op
|
|
# that still reports DEPLOYED. Only keys actually passed this run are touched:
|
|
# automations.sh drops a blank optional answer, so "blank" is indistinguishable
|
|
# from "not supplied" and must not be treated as "disable".
|
|
for _k in ${WF_KEYS_FROM_ENV[@]+"${WF_KEYS_FROM_ENV[@]}"}; do
|
|
_cur=$(sed -n "s/^${_k}=//p" "$ENV_FILE" | tail -n1)
|
|
if [[ "$_cur" == "${!_k}" ]]; then continue; fi
|
|
set_env "$_k" "${!_k}"
|
|
log " ${_k}: ${_cur:-<unset>} -> ${!_k:-<empty>}"
|
|
done
|
|
fi
|
|
|
|
# Validate required values are present.
|
|
missing=()
|
|
for var in POCKETID_DOMAIN ACME_EMAIL ENCRYPTION_KEY ANUBIS_PID_KEY; do
|
|
grep -E "^${var}=.+$" "$ENV_FILE" >/dev/null || missing+=("$var")
|
|
done
|
|
(( ${#missing[@]} == 0 )) || die "Missing values in $ENV_FILE: ${missing[*]}"
|
|
|
|
# Assemble the deployed Caddyfile each run: base (pocket-id) + the optional
|
|
# WebFinger block when BASE_DOMAIN is set in .env. Regenerated every run so
|
|
# edits to .env propagate.
|
|
install -m 0640 "$SCRIPT_DIR/Caddyfile" "$STACK_DIR/Caddyfile"
|
|
bd="$(sed -n 's/^BASE_DOMAIN=//p' "$ENV_FILE")"
|
|
ru="$(sed -n 's/^REDIRECT_URL=//p' "$ENV_FILE")"
|
|
if [[ -n "$bd" ]]; then
|
|
[[ -n "$ru" ]] || die "BASE_DOMAIN is set but REDIRECT_URL is empty in $ENV_FILE; set both or neither."
|
|
log "WebFinger enabled -- serving /.well-known/webfinger at ${bd}."
|
|
log " (to retire it, clear BASE_DOMAIN + REDIRECT_URL in $ENV_FILE and re-run --"
|
|
log " leaving the prompt blank cannot disable it, as a blank answer is dropped)"
|
|
cat "$SCRIPT_DIR/Caddyfile.webfinger" >> "$STACK_DIR/Caddyfile"
|
|
else
|
|
log "No BASE_DOMAIN -- pocket-id only (use the webfinger deployment for OIDC discovery)."
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Bring up the stack
|
|
# ----------------------------------------------------------------------------
|
|
if [[ "$FORCE" != "1" ]]; then
|
|
cat <<EOF
|
|
|
|
About to pull images and start the stack from $STACK_DIR.
|
|
|
|
Caddy will request a Let's Encrypt cert for ${POCKETID_DOMAIN}. DNS for
|
|
that name must already point at this host, and ports 80/443 must be
|
|
reachable from the internet, or the cert request will fail.
|
|
|
|
Continue? [y/N]
|
|
EOF
|
|
read -r ans
|
|
[[ "${ans,,}" == "y" || "${ans,,}" == "yes" ]] || { warn "Aborted."; exit 0; }
|
|
fi
|
|
|
|
cd "$STACK_DIR"
|
|
log "Pulling images..."
|
|
docker compose pull
|
|
|
|
log "Starting stack..."
|
|
docker compose up -d --remove-orphans
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Wait for health
|
|
# ----------------------------------------------------------------------------
|
|
log "Waiting for services to become healthy (up to 120s)..."
|
|
deadline=$(( $(date +%s) + 120 ))
|
|
while (( $(date +%s) < deadline )); do
|
|
status=$(docker compose ps --format '{{.Service}} {{.Health}}' 2>/dev/null || true)
|
|
unhealthy=$(echo "$status" | awk '$2 != "healthy" && $2 != "" {print $1}')
|
|
if [[ -z "$unhealthy" && -n "$status" ]]; then
|
|
log "All services healthy."
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
|
|
echo
|
|
log "Stack status:"
|
|
docker compose ps
|
|
echo
|
|
cat <<EOF
|
|
================================================================
|
|
DEPLOYED
|
|
|
|
URL: https://${POCKETID_DOMAIN}
|
|
Stack dir: $STACK_DIR
|
|
|
|
Manage (run from $STACK_DIR):
|
|
docker compose logs -f
|
|
docker compose restart
|
|
docker compose pull && docker compose up -d # update
|
|
docker compose down # stop, keep volumes
|
|
docker compose down -v # stop, WIPE data
|
|
|
|
Or just re-run this script -- it's idempotent.
|
|
================================================================
|
|
EOF
|
|
|
|
# IMPORTANT: do not put any code below this exit. Everything after the
|
|
# __ARCHIVE_BELOW__ marker is the embedded tar.gz payload (base64).
|
|
exit 0
|
|
__ARCHIVE_BELOW__
|
|
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
|