#!/usr/bin/env bash # Apply HTTP basic_auth to silverbullet.hacktivism.ch (dossiers pattern). # # On koopa (clone admin-log from Forgejo when needed; no home git dump): # sudo bash /path/to/koopa-admin-log/scripts/caddy/apply-notes-basicauth.sh # # Host runtime file (root-only, not under ~): # /etc/caddy/secrets/silverbullet-basicauth.txt # SoT (git-crypt): koopa-admin-secrets-enc → koopa/home-hernani/caddy/ set -euo pipefail if [[ "$(id -u)" -ne 0 ]]; then echo "ERROR: run as root: sudo bash $0" >&2 exit 1 fi CADDY=/etc/caddy/Caddyfile USER_NAME="${NOTES_AUTH_USER:-hernani}" SECRETS_DIR=/etc/caddy/secrets PASS_FILE="${SECRETS_DIR}/silverbullet-basicauth.txt" # legacy home dumps (migrate once, then delete) OLD_PASS_FILE=/home/hernani/koopa-secrets/silverbullet-basicauth.txt OLD_NOTES_FILE=/home/hernani/koopa-secrets/notes-basicauth.txt OLD_FREENOTES_FILE=/home/hernani/koopa-secrets/freenotes-basicauth.txt SITE=silverbullet.hacktivism.ch if [[ ! -f "$CADDY" ]]; then echo "ERROR: missing $CADDY" >&2 exit 1 fi command -v caddy >/dev/null || { echo "ERROR: caddy not in PATH" >&2; exit 1; } mkdir -p "$SECRETS_DIR" chmod 700 "$SECRETS_DIR" # Prefer explicit seed (SoT from secrets-enc), then legacy home dumps once. SEED_FILE="${NOTES_AUTH_SEED:-}" if [[ ! -f "$PASS_FILE" && -n "$SEED_FILE" && -f "$SEED_FILE" ]]; then cp -a "$SEED_FILE" "$PASS_FILE" chmod 600 "$PASS_FILE" echo "OK: seeded $SEED_FILE → $PASS_FILE" fi if [[ ! -f "$PASS_FILE" && -f "$OLD_PASS_FILE" ]]; then cp -a "$OLD_PASS_FILE" "$PASS_FILE" chmod 600 "$PASS_FILE" echo "OK: migrated $OLD_PASS_FILE → $PASS_FILE" fi if [[ ! -f "$PASS_FILE" && -f "$OLD_NOTES_FILE" ]]; then cp -a "$OLD_NOTES_FILE" "$PASS_FILE" chmod 600 "$PASS_FILE" echo "OK: migrated $OLD_NOTES_FILE → $PASS_FILE" fi if [[ ! -f "$PASS_FILE" && -f "$OLD_FREENOTES_FILE" ]]; then cp -a "$OLD_FREENOTES_FILE" "$PASS_FILE" chmod 600 "$PASS_FILE" echo "OK: migrated $OLD_FREENOTES_FILE → $PASS_FILE" fi if [[ -f "$PASS_FILE" ]]; then USER_NAME="$(awk -F= '/^USER=/{print substr($0,6); exit}' "$PASS_FILE")" PASS="$(awk -F= '/^PASS=/{print substr($0,6); exit}' "$PASS_FILE")" HASH="$(awk -F= '/^HASH=/{print substr($0,6); exit}' "$PASS_FILE")" if [[ -z "$USER_NAME" || -z "$HASH" ]]; then echo "ERROR: $PASS_FILE incomplete — move it aside and re-run" >&2 exit 1 fi echo "OK: reusing credentials in $PASS_FILE" else PASS="$(openssl rand -base64 18 | tr -d '/+=' | head -c 20)" HASH="$(caddy hash-password --plaintext "$PASS")" umask 077 cat >"$PASS_FILE" < tuple[int, int]: marker = f"{site_name} {{" i = src.find(marker) if i < 0: raise SystemExit( f"ERROR: site block not found: {site_name} — merge silverbullet.hacktivism.ch.caddy first" ) depth = 0 for k in range(i + len(marker) - 1, len(src)): ch = src[k] if ch == "{": depth += 1 elif ch == "}": depth -= 1 if depth == 0: return i, k + 1 raise SystemExit(f"ERROR: unclosed block: {site_name}") def has_basic_auth_directive(block: str) -> bool: # Ignore comments; only real directives count. stripped = "\n".join( ln for ln in block.splitlines() if not ln.lstrip().startswith("#") ) return bool(re.search(r"(?m)^\s*basic_?auth\b", stripped)) i, j = site_span(text, site) block = text[i:j] auth_lines = ( "\tbasic_auth {\n" f"\t\t{user} {hash_caddy}\n" "\t}\n" ) # Replace existing basic_auth block (also fixes legacy "$$" hashes that never matched). block_new, n = re.subn( r"(?m)^[ \t]*basic_auth\s*\{.*?\n[ \t]*\}\n?", auth_lines, block, count=1, flags=re.S, ) if n: text = text[:i] + block_new + text[j:] print(f"OK: replaced basic_auth in {site}") elif has_basic_auth_directive(block): print(f"OK: {site} already has basic_auth") else: m = re.search(r'(?m)^[ \t]*header Alt-Svc "clear"[ \t]*\n', block) if m: insert_at = m.end() new_block = block[:insert_at] + auth_lines + block[insert_at:] else: nl = block.find("\n") new_block = block[: nl + 1] + auth_lines + block[nl + 1 :] text = text[:i] + new_block + text[j:] print(f"OK: injected basic_auth in {site}") if text != orig: path.write_text(text) print(f"wrote {path}") else: print("no Caddyfile change") PY echo "== validate ==" caddy validate --config "$CADDY" echo "== reload ==" if systemctl is-active --quiet caddy; then systemctl reload caddy else echo "WARN: caddy unit not active — start it yourself" >&2 fi echo echo "Done. Browser: https://${SITE}/" echo "User/pass: $PASS_FILE"