ops: paivana and caddy config refresh
This commit is contained in:
parent
d8dbc6242b
commit
56f814a6de
74 changed files with 4517 additions and 79 deletions
179
scripts/caddy/apply-notes-basicauth.sh
Executable file
179
scripts/caddy/apply-notes-basicauth.sh
Executable file
|
|
@ -0,0 +1,179 @@
|
|||
#!/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" <<EOF
|
||||
USER=${USER_NAME}
|
||||
PASS=${PASS}
|
||||
HASH=${HASH}
|
||||
EOF
|
||||
chmod 600 "$PASS_FILE"
|
||||
echo "OK: wrote new password to $PASS_FILE — mirror into koopa-admin-secrets-enc (git-crypt)"
|
||||
fi
|
||||
|
||||
# Write bcrypt hash with single "$". Doubling to "$$" left literal "$$" in
|
||||
# `caddy adapt` JSON here and made every password 401.
|
||||
HASH_CADDY="$HASH"
|
||||
|
||||
ts=$(date +%Y%m%d-%H%M%S)
|
||||
cp -a "$CADDY" "${CADDY}.bak-silverbullet-auth-${ts}"
|
||||
echo "backup ${CADDY}.bak-silverbullet-auth-${ts}"
|
||||
|
||||
python3 - "$CADDY" "$USER_NAME" "$HASH_CADDY" "$SITE" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
user = sys.argv[2]
|
||||
hash_caddy = sys.argv[3]
|
||||
site = sys.argv[4]
|
||||
text = path.read_text()
|
||||
orig = text
|
||||
|
||||
|
||||
def site_span(src: str, site_name: str) -> 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"
|
||||
Loading…
Add table
Add a link
Reference in a new issue