ops: paivana and caddy config refresh
This commit is contained in:
parent
d8dbc6242b
commit
56f814a6de
74 changed files with 4517 additions and 79 deletions
|
|
@ -15,12 +15,52 @@ sudo systemctl reload caddy
|
|||
Caddy **ignores** Apache `.htaccess`. Use:
|
||||
|
||||
```bash
|
||||
sudo bash /home/hernani/koopa-admin-log/scripts/caddy/apply-dossiers-ngi-basicauth.sh
|
||||
# clone koopa-admin-log from Forgejo when needed (no permanent ~/ clone)
|
||||
sudo bash /path/to/koopa-admin-log/scripts/caddy/apply-dossiers-ngi-basicauth.sh
|
||||
```
|
||||
|
||||
Password file (not git): `/home/hernani/koopa-secrets/dossiers-ngi-basicauth.txt`
|
||||
Host runtime: `/etc/caddy/secrets/dossiers-ngi-basicauth.txt` (root-only).
|
||||
SoT: `koopa-admin-secrets-enc` (git-crypt) → `koopa/home-hernani/caddy/`.
|
||||
Hosts: `dossiers.ngi` / `dossiers.2.ngi` / `dossiers.3.ngi`.hacktivism.ch
|
||||
|
||||
## New HTTPS vhost
|
||||
## silverbullet.hacktivism.ch — merge + basic_auth
|
||||
|
||||
Add a site block to the Caddyfile (live + mirror), then validate and reload. Include the host in the shared HTTP site list (ACME + redirect) at the bottom of the file.
|
||||
After podman cutover (`scripts/notes/cutover-to-silverbullet.sh --apply`) and DNS:
|
||||
|
||||
```bash
|
||||
# laptop: SoT → /tmp seed (no secret echo)
|
||||
bash /path/to/koopa-admin-log/scripts/notes/prepare-basicauth-seed.sh
|
||||
scp /tmp/silverbullet-basicauth-seed.txt hernani@koopa:/tmp/
|
||||
|
||||
# koopa root:
|
||||
sudo bash /path/to/koopa-admin-log/scripts/caddy/merge-notes-site.sh
|
||||
sudo NOTES_AUTH_SEED=/tmp/silverbullet-basicauth-seed.txt \
|
||||
bash /path/to/koopa-admin-log/scripts/caddy/apply-notes-basicauth.sh
|
||||
```
|
||||
|
||||
`merge-notes-site.sh` renames leftover `freenotes`/`notes` site blocks → `silverbullet.hacktivism.ch`.
|
||||
Host runtime: `/etc/caddy/secrets/silverbullet-basicauth.txt` (root-only).
|
||||
SoT: `koopa-admin-secrets-enc` (git-crypt) → `koopa/home-hernani/caddy/silverbullet-basicauth.txt`.
|
||||
|
||||
## New HTTPS vhost — `caddy-apply.sh`
|
||||
|
||||
Generic merge (site snippet + ACME `http://` list + ports comment):
|
||||
|
||||
```bash
|
||||
# laptop mirror (no reload):
|
||||
bash scripts/caddy/caddy-apply.sh \
|
||||
--config configs/caddy/Caddyfile \
|
||||
--snippet configs/caddy/galene.hacktivism.ch.caddy \
|
||||
--site galene.hacktivism.ch --port 9029 --label galene \
|
||||
--no-reload
|
||||
|
||||
# koopa live (root):
|
||||
sudo bash …/scripts/caddy/caddy-apply.sh \
|
||||
--snippet …/configs/caddy/SITE.caddy \
|
||||
--site SITE.hacktivism.ch --port NNNN --label NAME
|
||||
```
|
||||
|
||||
Allocated collab vhosts (2026-09-08): `galene` **9029**, `cryptpad` **9030**, `meet` **9031**, `bbb` **9032**.
|
||||
Snippets under `configs/caddy/*.hacktivism.ch.caddy`. Image bases: CachyOS except **bbb=Ubuntu**.
|
||||
|
||||
Still valid: hand-edit live + mirror, then validate/reload.
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
#
|
||||
# Caddy file_server does NOT honor Apache .htaccess — this edits the Caddyfile.
|
||||
#
|
||||
# On koopa (password for sudo):
|
||||
# sudo -n true 2>/dev/null || sudo -v
|
||||
# sudo bash /home/hernani/koopa-admin-log/scripts/caddy/apply-dossiers-ngi-basicauth.sh
|
||||
# On koopa (clone admin-log from Forgejo when needed; no home git dump):
|
||||
# sudo bash /path/to/koopa-admin-log/scripts/caddy/apply-dossiers-ngi-basicauth.sh
|
||||
#
|
||||
# Idempotent. Password file is NOT in git:
|
||||
# /home/hernani/koopa-secrets/dossiers-ngi-basicauth.txt
|
||||
# Host runtime file (root-only, not under ~):
|
||||
# /etc/caddy/secrets/dossiers-ngi-basicauth.txt
|
||||
# SoT (git-crypt): koopa-admin-secrets-enc → koopa/home-hernani/caddy/
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
|
|
@ -19,8 +19,9 @@ fi
|
|||
|
||||
CADDY=/etc/caddy/Caddyfile
|
||||
USER_NAME="${DOSSIERS_AUTH_USER:-hernani}"
|
||||
SECRETS_DIR=/home/hernani/koopa-secrets
|
||||
SECRETS_DIR=/etc/caddy/secrets
|
||||
PASS_FILE="${SECRETS_DIR}/dossiers-ngi-basicauth.txt"
|
||||
OLD_PASS_FILE=/home/hernani/koopa-secrets/dossiers-ngi-basicauth.txt
|
||||
SITES=(
|
||||
dossiers.ngi.hacktivism.ch
|
||||
dossiers.2.ngi.hacktivism.ch
|
||||
|
|
@ -34,9 +35,14 @@ fi
|
|||
command -v caddy >/dev/null || { echo "ERROR: caddy not in PATH" >&2; exit 1; }
|
||||
|
||||
mkdir -p "$SECRETS_DIR"
|
||||
chown hernani:hernani "$SECRETS_DIR"
|
||||
chmod 700 "$SECRETS_DIR"
|
||||
|
||||
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" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
# file format: USER=... PASS=... HASH=...
|
||||
|
|
|
|||
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"
|
||||
217
scripts/caddy/caddy-apply.sh
Executable file
217
scripts/caddy/caddy-apply.sh
Executable file
|
|
@ -0,0 +1,217 @@
|
|||
#!/usr/bin/env bash
|
||||
# Generic: merge a site snippet into a Caddyfile + ACME http:// list + ports comment.
|
||||
#
|
||||
# Laptop (mirror only, no reload):
|
||||
# bash scripts/caddy/caddy-apply.sh \
|
||||
# --config configs/caddy/Caddyfile \
|
||||
# --snippet configs/caddy/galene.hacktivism.ch.caddy \
|
||||
# --site galene.hacktivism.ch --port 9029 --label galene \
|
||||
# --no-reload
|
||||
#
|
||||
# Koopa live (root, Freigabe):
|
||||
# sudo bash …/caddy-apply.sh \
|
||||
# --snippet /home/hernani/koopa-admin-log/configs/caddy/galene.hacktivism.ch.caddy \
|
||||
# --site galene.hacktivism.ch --port 9029 --label galene
|
||||
set -euo pipefail
|
||||
|
||||
CADDY=/etc/caddy/Caddyfile
|
||||
SNIPPET=""
|
||||
SITE=""
|
||||
PORT=""
|
||||
LABEL=""
|
||||
INSERT_AFTER="decidim.hacktivism.ch"
|
||||
ACME_ANCHOR="http://decidim.hacktivism.ch"
|
||||
NO_RELOAD=0
|
||||
DRY=0
|
||||
OLD_SITES=()
|
||||
|
||||
usage() {
|
||||
sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
|
||||
echo "Options: --config PATH --snippet PATH --site HOST --port N --label NAME"
|
||||
echo " --insert-after HOST --acme-anchor http://HOST --old-site HOST"
|
||||
echo " --no-reload --dry-run"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--config) CADDY=$2; shift 2 ;;
|
||||
--snippet) SNIPPET=$2; shift 2 ;;
|
||||
--site) SITE=$2; shift 2 ;;
|
||||
--port) PORT=$2; shift 2 ;;
|
||||
--label) LABEL=$2; shift 2 ;;
|
||||
--insert-after) INSERT_AFTER=$2; shift 2 ;;
|
||||
--acme-anchor) ACME_ANCHOR=$2; shift 2 ;;
|
||||
--old-site) OLD_SITES+=("$2"); shift 2 ;;
|
||||
--no-reload) NO_RELOAD=1; shift ;;
|
||||
--dry-run) DRY=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "ERROR: unknown arg: $1" >&2; usage >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$SNIPPET" && -n "$SITE" ]] || { echo "ERROR: --snippet and --site required" >&2; exit 2; }
|
||||
[[ -f "$CADDY" ]] || { echo "ERROR: missing $CADDY" >&2; exit 1; }
|
||||
[[ -f "$SNIPPET" ]] || { echo "ERROR: missing $SNIPPET" >&2; exit 1; }
|
||||
LABEL="${LABEL:-$SITE}"
|
||||
|
||||
if [[ "$CADDY" == /etc/caddy/Caddyfile && "$(id -u)" -ne 0 && "$DRY" -eq 0 ]]; then
|
||||
echo "ERROR: live Caddyfile needs root (or --dry-run / --config mirror)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ts=$(date +%Y%m%d-%H%M%S)
|
||||
if [[ "$DRY" -eq 0 ]]; then
|
||||
cp -a "$CADDY" "${CADDY}.bak-caddy-apply-${ts}"
|
||||
echo "backup ${CADDY}.bak-caddy-apply-${ts}"
|
||||
fi
|
||||
|
||||
export CADDY SNIPPET SITE PORT LABEL INSERT_AFTER ACME_ANCHOR DRY
|
||||
export OLD_SITES_CSV
|
||||
OLD_SITES_CSV=$(IFS=,; echo "${OLD_SITES[*]-}")
|
||||
|
||||
python3 - <<'PY'
|
||||
import os, re, sys
|
||||
from pathlib import Path
|
||||
|
||||
caddy = Path(os.environ["CADDY"])
|
||||
snip = Path(os.environ["SNIPPET"]).read_text().rstrip() + "\n\n"
|
||||
site = os.environ["SITE"]
|
||||
port = os.environ.get("PORT") or ""
|
||||
label = os.environ.get("LABEL") or site
|
||||
insert_after = os.environ["INSERT_AFTER"]
|
||||
acme_anchor = os.environ["ACME_ANCHOR"]
|
||||
dry = os.environ.get("DRY") == "1"
|
||||
old_sites = [s for s in os.environ.get("OLD_SITES_CSV", "").split(",") if s]
|
||||
|
||||
text = caddy.read_text()
|
||||
orig = text
|
||||
|
||||
def drop_site_block(text: str, old_site: str) -> str:
|
||||
marker_old = f"{old_site} {{"
|
||||
i = text.find(marker_old)
|
||||
if i < 0:
|
||||
return text
|
||||
j = text.find("\n}", i)
|
||||
if j < 0:
|
||||
return text
|
||||
end = j + 2
|
||||
while end < len(text) and text[end] == "\n":
|
||||
end += 1
|
||||
text = text[:i] + text[end:]
|
||||
text = text.replace(f", http://{old_site}", "").replace(f"http://{old_site}, ", "")
|
||||
text = text.replace(f"http://{old_site}", "")
|
||||
print(f"OK: removed duplicate old block {old_site}")
|
||||
return text
|
||||
|
||||
for old_site in old_sites:
|
||||
if old_site in text and site not in text:
|
||||
text = text.replace(old_site, site)
|
||||
print(f"OK: renamed {old_site} → {site}")
|
||||
elif old_site in text and site in text:
|
||||
text = drop_site_block(text, old_site)
|
||||
|
||||
marker = f"{site} {{"
|
||||
if marker in text:
|
||||
print(f"OK: site block already present: {site}")
|
||||
else:
|
||||
insert_at = None
|
||||
after = text.find(f"{insert_after} {{")
|
||||
if after >= 0:
|
||||
j = text.find("\n}", after)
|
||||
if j >= 0:
|
||||
insert_at = j + 2
|
||||
while insert_at < len(text) and text[insert_at] == "\n":
|
||||
insert_at += 1
|
||||
if insert_at is None:
|
||||
acme = text.find("http://taler.hacktivism.ch")
|
||||
insert_at = acme if acme >= 0 else len(text)
|
||||
text = text[:insert_at] + snip + text[insert_at:]
|
||||
print(f"OK: inserted site block {site}")
|
||||
|
||||
http_tok = f"http://{site}"
|
||||
if http_tok in text:
|
||||
print(f"OK: ACME http list already has {http_tok}")
|
||||
else:
|
||||
# Prefer exact "anchor {" form; else append before " {" of the long http list
|
||||
old = f"{acme_anchor} {{"
|
||||
new = f"{acme_anchor}, {http_tok} {{"
|
||||
if old in text:
|
||||
text = text.replace(old, new, 1)
|
||||
print(f"OK: added {http_tok} to ACME http list (anchor)")
|
||||
else:
|
||||
# Find the shared ACME line (starts with http://taler…)
|
||||
m = re.search(r"(http://taler\.hacktivism\.ch[^\n]*?)(\s*\{)", text)
|
||||
if not m:
|
||||
raise SystemExit("ERROR: ACME http list not found")
|
||||
line = m.group(1)
|
||||
if http_tok in line:
|
||||
print(f"OK: ACME http list already has {http_tok}")
|
||||
else:
|
||||
text = text[: m.start(1)] + line.rstrip() + f", {http_tok}" + text[m.end(1) :]
|
||||
print(f"OK: added {http_tok} to ACME http list (append)")
|
||||
|
||||
# Ports comment header (first matching line starting with "# 9020 castopod")
|
||||
if port:
|
||||
token = f"{port} {label}"
|
||||
def upd_hdr(line: str) -> str:
|
||||
if token in line or f"| {port} " in line or f"| {port}|" in line:
|
||||
return line
|
||||
# insert before " | 9200 forgejo-ssh" if present, else before end
|
||||
if "9200 forgejo-ssh" in line:
|
||||
return line.replace(" | 9200 forgejo-ssh", f" | {token} | 9200 forgejo-ssh", 1)
|
||||
if line.rstrip().endswith("forgejo-ssh"):
|
||||
return line.rstrip() + f" | {token}\n"
|
||||
return line.rstrip() + f" | {token}\n"
|
||||
|
||||
lines = text.splitlines(keepends=True)
|
||||
changed = False
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith("# 9020 castopod"):
|
||||
new_line = upd_hdr(line)
|
||||
if new_line != line:
|
||||
lines[i] = new_line if new_line.endswith("\n") else new_line + "\n"
|
||||
print(f"OK: updated ports comment (+{token})")
|
||||
changed = True
|
||||
else:
|
||||
print(f"OK: ports comment already has {token}")
|
||||
break
|
||||
if changed:
|
||||
text = "".join(lines)
|
||||
|
||||
if text != orig:
|
||||
if dry:
|
||||
print(f"DRY: would write {caddy} ({len(text) - len(orig):+d} bytes)")
|
||||
else:
|
||||
caddy.write_text(text)
|
||||
print(f"wrote {caddy}")
|
||||
else:
|
||||
print("no Caddyfile change")
|
||||
PY
|
||||
|
||||
if [[ "$DRY" -eq 1 ]]; then
|
||||
echo "dry-run done (no validate/reload)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if command -v caddy >/dev/null 2>&1; then
|
||||
echo "== validate =="
|
||||
caddy validate --config "$CADDY"
|
||||
else
|
||||
echo "WARN: caddy binary missing — skip validate" >&2
|
||||
fi
|
||||
|
||||
if [[ "$NO_RELOAD" -eq 1 ]]; then
|
||||
echo "skip reload (--no-reload)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$CADDY" == /etc/caddy/Caddyfile ]] && command -v systemctl >/dev/null 2>&1; then
|
||||
echo "== reload =="
|
||||
if systemctl is-active --quiet caddy; then
|
||||
systemctl reload caddy
|
||||
else
|
||||
echo "WARN: caddy unit not active — start it yourself" >&2
|
||||
fi
|
||||
else
|
||||
echo "skip reload (not live path or no systemctl)"
|
||||
fi
|
||||
133
scripts/caddy/merge-notes-site.sh
Executable file
133
scripts/caddy/merge-notes-site.sh
Executable file
|
|
@ -0,0 +1,133 @@
|
|||
#!/usr/bin/env bash
|
||||
# Merge silverbullet.hacktivism.ch into live /etc/caddy/Caddyfile + ACME http list.
|
||||
# Run on koopa as root AFTER podman is up on :9028:
|
||||
# sudo bash /home/hernani/koopa-admin-log/scripts/caddy/merge-notes-site.sh
|
||||
# Then:
|
||||
# sudo bash /home/hernani/koopa-admin-log/scripts/caddy/apply-notes-basicauth.sh
|
||||
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
|
||||
SNIPPET=/home/hernani/koopa-admin-log/configs/caddy/silverbullet.hacktivism.ch.caddy
|
||||
SITE=silverbullet.hacktivism.ch
|
||||
# Prior hostnames to rename into SITE (order: newest leftovers first)
|
||||
OLD_SITES="freenotes.hacktivism.ch notes.hacktivism.ch"
|
||||
|
||||
[[ -f "$CADDY" ]] || { echo "ERROR: missing $CADDY" >&2; exit 1; }
|
||||
[[ -f "$SNIPPET" ]] || { echo "ERROR: missing $SNIPPET" >&2; exit 1; }
|
||||
command -v caddy >/dev/null || { echo "ERROR: caddy not in PATH" >&2; exit 1; }
|
||||
|
||||
ts=$(date +%Y%m%d-%H%M%S)
|
||||
cp -a "$CADDY" "${CADDY}.bak-silverbullet-merge-${ts}"
|
||||
echo "backup ${CADDY}.bak-silverbullet-merge-${ts}"
|
||||
|
||||
python3 - "$CADDY" "$SNIPPET" "$SITE" $OLD_SITES <<'PY'
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
caddy = Path(sys.argv[1])
|
||||
snippet_path = Path(sys.argv[2])
|
||||
site = sys.argv[3]
|
||||
old_sites = sys.argv[4:]
|
||||
text = caddy.read_text()
|
||||
orig = text
|
||||
snip = snippet_path.read_text().rstrip() + "\n\n"
|
||||
|
||||
def drop_site_block(text: str, old_site: str) -> str:
|
||||
marker_old = f"{old_site} {{"
|
||||
i = text.find(marker_old)
|
||||
if i < 0:
|
||||
return text
|
||||
j = text.find("\n}", i)
|
||||
if j < 0:
|
||||
return text
|
||||
end = j + 2
|
||||
while end < len(text) and text[end] == "\n":
|
||||
end += 1
|
||||
text = text[:i] + text[end:]
|
||||
text = text.replace(f", http://{old_site}", "").replace(f"http://{old_site}, ", "")
|
||||
text = text.replace(f"http://{old_site}", "")
|
||||
print(f"OK: removed duplicate old block {old_site}")
|
||||
return text
|
||||
|
||||
# Rename leftover freenotes/notes.hacktivism.ch → silverbullet.hacktivism.ch
|
||||
for old_site in old_sites:
|
||||
if old_site in text and site not in text:
|
||||
text = text.replace(old_site, site)
|
||||
text = text.replace("9028 freenotes", "9028 silverbullet")
|
||||
text = text.replace("9028 notes", "9028 silverbullet")
|
||||
print(f"OK: renamed {old_site} → {site}")
|
||||
elif old_site in text and site in text:
|
||||
text = drop_site_block(text, old_site)
|
||||
|
||||
marker = f"{site} {{"
|
||||
if marker in text:
|
||||
print(f"OK: site block already present: {site}")
|
||||
else:
|
||||
# Insert after decidim block if present, else before ACME http list
|
||||
insert_at = None
|
||||
dec = text.find("decidim.hacktivism.ch {")
|
||||
if dec >= 0:
|
||||
j = text.find("\n}", dec)
|
||||
if j >= 0:
|
||||
insert_at = j + 2 # after closing brace newline
|
||||
while insert_at < len(text) and text[insert_at] == "\n":
|
||||
insert_at += 1
|
||||
if insert_at is None:
|
||||
acme = text.find("http://taler.hacktivism.ch")
|
||||
insert_at = acme if acme >= 0 else len(text)
|
||||
text = text[:insert_at] + snip + text[insert_at:]
|
||||
print(f"OK: inserted site block {site}")
|
||||
|
||||
http_tok = f"http://{site}"
|
||||
if http_tok in text:
|
||||
print(f"OK: ACME http list already has {http_tok}")
|
||||
else:
|
||||
old = "http://decidim.hacktivism.ch {"
|
||||
new = f"http://decidim.hacktivism.ch, {http_tok} {{"
|
||||
if old not in text:
|
||||
# maybe already on a long list without trailing brace-only form
|
||||
raise SystemExit("ERROR: ACME http list marker not found (decidim)")
|
||||
text = text.replace(old, new, 1)
|
||||
print(f"OK: added {http_tok} to ACME http list")
|
||||
|
||||
# Comment header line near top ports comment
|
||||
for old_hdr, new_hdr in (
|
||||
(
|
||||
"# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9026 lemmy | 9027 decidim | 9200 forgejo-ssh",
|
||||
"# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9026 lemmy | 9027 decidim | 9028 silverbullet | 9200 forgejo-ssh",
|
||||
),
|
||||
(
|
||||
"# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9026 lemmy | 9027 decidim | 9028 notes | 9200 forgejo-ssh",
|
||||
"# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9026 lemmy | 9027 decidim | 9028 silverbullet | 9200 forgejo-ssh",
|
||||
),
|
||||
):
|
||||
if old_hdr in text:
|
||||
text = text.replace(old_hdr, new_hdr, 1)
|
||||
print("OK: updated ports comment header")
|
||||
break
|
||||
|
||||
if text != orig:
|
||||
caddy.write_text(text)
|
||||
print(f"wrote {caddy}")
|
||||
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 "Next: sudo bash /home/hernani/koopa-admin-log/scripts/caddy/apply-notes-basicauth.sh"
|
||||
echo "DNS/VeciGate for ${SITE} still separate."
|
||||
Loading…
Add table
Add a link
Reference in a new issue