Files
automations/deployments/pocket-id/deploy.sh
T
57_WolveandClaude Opus 5 947c89931d fix(deployments): stop interpolating .env values into a sed script
The set_env/env_set helper escaped its value with
`esc=${val//\/\\}; esc=${esc//|/\|}; esc=${esc//&/\&}` and then
interpolated it into `sed -i -e "s|^KEY=.*|KEY=${esc}|"`. The escaping does
not do what it looks like. Tested on bash 5.2:

  set_env K 'a&b'   ->  K=aK=seedb        (sed expanded & to the whole match)
  set_env K 'a|b'   ->  sed: unknown option to `s'   (rc!=0, aborts under set -e)

So any value containing & is silently corrupted and any value containing the
s||| delimiter kills the run. That is reachable: headscale writes
OIDC_CLIENT_SECRET through this, pocket-id writes REDIRECT_URL, copyparty
writes DATA_DIR. A generated secret or a URL query string hits both cases.

The copies in headscale and pocket-id were additionally mangled when they
were introduced -- `${val//\/\}` (pattern `\/`, a literal SLASH) and a raw
newline inside `printf '%s=%s\n'`. The mangled form is a no-op rather than a
corrupter, so the practical failure mode was the same as the original.

Replace all of them with an awk rewrite that passes the key and value through
the ENVIRONMENT, so the value is never parsed as part of a script and needs no
escaping at all. ENVIRON and index() are POSIX, so busybox awk handles them.
Output goes to a temp file and is copied back with `cat >`, which preserves the
original mode and owner -- a .env holding secrets stays 0600. If awk fails,
set -e aborts before .env is touched, which `sed -i` could not promise.

Verified against plain, a&b, a|b, a\b, p@ss&w|rd\x, R&D, a URL with a query
string, s/foo/bar/, a trailing space and the empty string; plus the
append-when-key-absent path, the file-does-not-exist path, non-target lines
left intact, no line-count drift, and mode preservation.

copyparty/update.sh and the rebuilt copyparty payload are included because
update.sh is embedded; regenerated with build.sh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 14:21:07 -05:00

379 lines
18 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
# The value goes through the ENVIRONMENT, never interpolated into a sed
# script. Interpolating it corrupts any value containing & (sed expands it to
# the whole match) and aborts the run on one containing the s||| delimiter --
# which is reachable for an OIDC secret, a password or a URL query string.
local key="$1" val="$2" tmp
if [[ ! -f "$ENV_FILE" ]]; then
printf '%s=%s
' "$key" "$val" >> "$ENV_FILE"
return 0
fi
tmp="$(mktemp)"
_SE_KEY="$key" _SE_VAL="$val" awk '
BEGIN { k = ENVIRON["_SE_KEY"]; v = ENVIRON["_SE_VAL"]; seen = 0 }
!seen && index($0, k "=") == 1 { print k "=" v; seen = 1; next }
{ print }
END { if (!seen) print k "=" v }
' "$ENV_FILE" > "$tmp"
cat "$tmp" > "$ENV_FILE" # rewrite in place: keeps the original mode/owner
rm -f "$tmp"
}
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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