ops: castopod/bonfire notes; amount-ladder bench
This commit is contained in:
parent
acff09d130
commit
84388ae6a9
9 changed files with 749 additions and 3 deletions
30
scripts/castopod/README.md
Normal file
30
scripts/castopod/README.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Castopod ops scripts (koopa)
|
||||
|
||||
Live stack: `/home/hernani/koopa-castopod/` (podman-compose).
|
||||
These scripts live in admin-log and should be **copied** to the host when changed.
|
||||
|
||||
## Hang-avoidance rules (learned 2026-07-09)
|
||||
|
||||
| Failure mode | What hung | Fix in scripts |
|
||||
|--------------|-----------|----------------|
|
||||
| `php spark shield:user activate` | interactive `[y,n]` without TTY → **TypeError / hang** | SQL: `UPDATE cp_users SET active=1` via `cp_activate_users` |
|
||||
| bare `curl` / image pulls | no deadline → agent waits forever | `curl --connect-timeout` + `--max-time`; `timeout(1)` around `podman-compose pull` |
|
||||
| health loops | infinite `sleep` | capped `CP_HEALTH_TRIES` + fail |
|
||||
| Wikimedia/random downloads | slow/blocked CDN | local/known files only; always `--max-time` |
|
||||
| empty optional file fields | Castopod validates empty transcript/chapters | send real `.srt` / `.json` **or** omit fields carefully |
|
||||
|
||||
## Usage (on koopa as `hernani`)
|
||||
|
||||
```bash
|
||||
# copy once
|
||||
mkdir -p ~/koopa-castopod/bin
|
||||
cp /path/to/koopa-admin-log/scripts/castopod/{lib.sh,status.sh,up.sh} ~/koopa-castopod/bin/
|
||||
chmod +x ~/koopa-castopod/bin/*.sh
|
||||
|
||||
~/koopa-castopod/bin/status.sh
|
||||
~/koopa-castopod/bin/up.sh
|
||||
```
|
||||
|
||||
Env overrides: `CP_MAX_TIME`, `CP_PULL_TIMEOUT`, `CP_HEALTH_TRIES`, `CP_BASEURL`.
|
||||
|
||||
Passwords stay in `~/koopa-castopod/.env` and `users.env` (mode 600) — never in admin-log.
|
||||
116
scripts/castopod/lib.sh
Normal file
116
scripts/castopod/lib.sh
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# shellcheck shell=bash
|
||||
# Shared helpers for Castopod ops on koopa — fail fast, never hang forever.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- defaults (override via env) ---
|
||||
: "${CP_BASEURL:=https://castopod.hacktivism.ch}"
|
||||
: "${CP_COMPOSE_DIR:=/home/hernani/koopa-castopod}"
|
||||
: "${CP_CONNECT_TIMEOUT:=10}" # seconds — TCP connect
|
||||
: "${CP_MAX_TIME:=120}" # seconds — whole HTTP transfer
|
||||
: "${CP_PULL_TIMEOUT:=600}" # seconds — image pull
|
||||
: "${CP_HEALTH_TRIES:=30}" # health poll attempts
|
||||
: "${CP_HEALTH_SLEEP:=5}" # seconds between polls
|
||||
|
||||
# curl that cannot hang forever
|
||||
cp_curl() {
|
||||
curl -sS \
|
||||
--connect-timeout "${CP_CONNECT_TIMEOUT}" \
|
||||
--max-time "${CP_MAX_TIME}" \
|
||||
--retry 2 \
|
||||
--retry-delay 2 \
|
||||
--retry-connrefused \
|
||||
"$@"
|
||||
}
|
||||
|
||||
# HTTP status only (no hang)
|
||||
cp_http_code() {
|
||||
local url=$1
|
||||
cp_curl -o /dev/null -w '%{http_code}' "$url" || echo "000"
|
||||
}
|
||||
|
||||
# Run command with hard wall-clock limit (GNU coreutils timeout)
|
||||
cp_timeout() {
|
||||
local secs=$1
|
||||
shift
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout --signal=TERM --kill-after=15s "${secs}" "$@"
|
||||
else
|
||||
# fallback: no timeout binary — still run, but warn
|
||||
echo "WARN: timeout(1) missing; running without wall limit: $*" >&2
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Non-interactive answer stream for spark / CLI that prompts [y,n] or passwords.
|
||||
# Usage: cp_yes | podman exec -i … php spark …
|
||||
# Prefer SQL for activate when possible (spark prompts hang without TTY).
|
||||
cp_yes() {
|
||||
# enough y's for a few prompts; never block waiting for input
|
||||
yes y 2>/dev/null | head -n 20
|
||||
}
|
||||
|
||||
# Poll until URL returns expected code or give up
|
||||
cp_wait_http() {
|
||||
local url=$1
|
||||
local want=${2:-200}
|
||||
local i code
|
||||
for ((i = 1; i <= CP_HEALTH_TRIES; i++)); do
|
||||
code=$(cp_http_code "$url")
|
||||
echo "health try $i/${CP_HEALTH_TRIES}: $url → $code"
|
||||
if [[ "$code" == "$want" || "$code" =~ ^[23] ]]; then
|
||||
return 0
|
||||
fi
|
||||
sleep "${CP_HEALTH_SLEEP}"
|
||||
done
|
||||
echo "ERROR: $url still not healthy after ${CP_HEALTH_TRIES} tries (last=$code)" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
# MariaDB password from compose .env (no hang if missing)
|
||||
cp_mysql_password() {
|
||||
local envf="${CP_COMPOSE_DIR}/.env"
|
||||
[[ -f "$envf" ]] || { echo "ERROR: missing $envf" >&2; return 1; }
|
||||
# shellcheck disable=SC1090
|
||||
grep -E '^MYSQL_PASSWORD=' "$envf" | head -1 | cut -d= -f2-
|
||||
}
|
||||
|
||||
cp_mysql() {
|
||||
local pw
|
||||
pw=$(cp_mysql_password)
|
||||
podman exec koopa-castopod-mariadb \
|
||||
mariadb -ucastopod -p"$pw" castopod "$@"
|
||||
}
|
||||
|
||||
# Activate shield users without spark interactive prompt
|
||||
cp_activate_users() {
|
||||
cp_mysql -e "UPDATE cp_users SET active=1 WHERE username IN ('admin','ngi');"
|
||||
}
|
||||
|
||||
# Session login for admin UI automation (uses users.env)
|
||||
cp_admin_login() {
|
||||
local cookie=${1:-/tmp/cp-cj}
|
||||
local envf="${CP_COMPOSE_DIR}/users.env"
|
||||
[[ -f "$envf" ]] || { echo "ERROR: missing $envf" >&2; return 1; }
|
||||
# shellcheck disable=SC1090
|
||||
source "$envf"
|
||||
[[ -n "${ADMIN_PW:-}" ]] || { echo "ERROR: ADMIN_PW empty" >&2; return 1; }
|
||||
|
||||
rm -f "$cookie"
|
||||
cp_curl -c "$cookie" -b "$cookie" "${CP_BASEURL}/cp-auth/login" -o /tmp/cp-login.html
|
||||
local csrf
|
||||
csrf=$(sed -n 's/.*name="csrf_test_name" value="\([^"]*\)".*/\1/p' /tmp/cp-login.html | head -1)
|
||||
[[ -n "$csrf" ]] || { echo "ERROR: no CSRF on login page" >&2; return 1; }
|
||||
|
||||
local code
|
||||
code=$(cp_curl -c "$cookie" -b "$cookie" -o /dev/null -w '%{http_code}' \
|
||||
-X POST "${CP_BASEURL}/cp-auth/login" \
|
||||
--data-urlencode "csrf_test_name=${csrf}" \
|
||||
--data-urlencode "email=admin@castopod.hacktivism.ch" \
|
||||
--data-urlencode "password=${ADMIN_PW}")
|
||||
# 303 see other is success
|
||||
if [[ "$code" != "303" && "$code" != "302" && "$code" != "200" ]]; then
|
||||
echo "ERROR: login HTTP $code" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
21
scripts/castopod/status.sh
Normal file
21
scripts/castopod/status.sh
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env bash
|
||||
# Quick Castopod health — bounded network, no hangs.
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
source "${ROOT}/lib.sh"
|
||||
|
||||
echo "== podman =="
|
||||
podman ps --filter name=koopa-castopod --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' || true
|
||||
|
||||
echo "== local backend :9020 =="
|
||||
code=$(cp_http_code "http://127.0.0.1:9020/health" || true)
|
||||
echo "local health → ${code:-000} (307 redirect to public is OK)"
|
||||
|
||||
echo "== public =="
|
||||
# Note: do not put bare @url in curl -w; @ means "read file" in some curl contexts.
|
||||
for path in "/" "/@foss" "/@foss/feed.xml" "/@foss/episodes/four-freedoms"; do
|
||||
url="${CP_BASEURL}${path}"
|
||||
code=$(cp_http_code "$url")
|
||||
printf ' %s → %s\n' "$url" "$code"
|
||||
done
|
||||
32
scripts/castopod/up.sh
Normal file
32
scripts/castopod/up.sh
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env bash
|
||||
# Start Castopod stack with hard timeouts on pull/up (no infinite hang).
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
source "${ROOT}/lib.sh"
|
||||
|
||||
cd "${CP_COMPOSE_DIR}"
|
||||
|
||||
echo "== pull (max ${CP_PULL_TIMEOUT}s) =="
|
||||
cp_timeout "${CP_PULL_TIMEOUT}" podman-compose pull
|
||||
|
||||
echo "== up =="
|
||||
cp_timeout 180 podman-compose up -d
|
||||
|
||||
echo "== wait for app on :9020 =="
|
||||
# Castopod often 307 from /health to https — accept 2xx/3xx
|
||||
ok=0
|
||||
for ((i = 1; i <= CP_HEALTH_TRIES; i++)); do
|
||||
code=$(cp_http_code "http://127.0.0.1:9020/" || true)
|
||||
echo "try $i: local / → $code"
|
||||
if [[ "$code" =~ ^[23] ]]; then
|
||||
ok=1
|
||||
break
|
||||
fi
|
||||
sleep "${CP_HEALTH_SLEEP}"
|
||||
done
|
||||
[[ "$ok" -eq 1 ]] || { echo "ERROR: app not answering on 9020"; podman-compose ps; exit 1; }
|
||||
|
||||
echo "== public via Caddy =="
|
||||
cp_wait_http "${CP_BASEURL}/" || true
|
||||
echo "done."
|
||||
Loading…
Add table
Add a link
Reference in a new issue