#!/usr/bin/env bash # Apply hacktivism branding to Castopod (official settings + optional CSS overlay). # # Official docs (instance settings — site name/description/icon + 6 accent themes): # https://docs.castopod.org/main/en/user-guide/instance/settings/ # Docker/compose background: # https://docs.castopod.org/main/en/getting-started/docker/ # # Logo: same magician mark as git.hacktivism.ch / bonfire.hacktivism.ch # (configs/castopod/assets/img/logo.png — ≥512×512 required by Castopod). # Official accent closest to gold: "amber" (among pine/crimson/lake/amber/jacaranda/onyx). # Optional warm-dark CSS: NOT official — see configs/castopod/assets/css/theme-hacktivism-overlay.css set -euo pipefail ROOT="${KOOPA_CASTOPOD_ROOT:-$HOME/koopa-castopod}" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." 2>/dev/null && pwd || true)" # shellcheck source=lib.sh source "${SCRIPT_DIR}/lib.sh" : "${CP_BASEURL:=https://castopod.hacktivism.ch}" : "${CP_ADMIN_GATEWAY:=cp-admin}" : "${CP_SITE_NAME:=hacktivism castopod}" : "${CP_SITE_DESCRIPTION:=Castopod on hacktivism.ch — FOSS Airwaves and free-culture podcasting.}" : "${CP_THEME:=amber}" : "${CP_APPLY_OVERLAY:=1}" BRANDING_DST="${ROOT}/branding" ICON_SRC="" CSS_SRC="" if [[ -d "${REPO_ROOT}/configs/castopod/assets/img" ]]; then ICON_SRC="${REPO_ROOT}/configs/castopod/assets/img/logo.png" CSS_SRC="${REPO_ROOT}/configs/castopod/assets/css/theme-hacktivism-overlay.css" elif [[ -f "${ROOT}/branding/logo.png" ]]; then ICON_SRC="${ROOT}/branding/logo.png" CSS_SRC="${ROOT}/branding/theme-hacktivism-overlay.css" else echo "missing logo.png (run from koopa-admin-log checkout or copy assets to ${ROOT}/branding/)" >&2 exit 1 fi install -d -m 755 "${BRANDING_DST}" if [[ "$(realpath "${ICON_SRC}" 2>/dev/null || echo "${ICON_SRC}")" != "$(realpath "${BRANDING_DST}/logo.png" 2>/dev/null || echo "${BRANDING_DST}/logo.png")" ]]; then install -m 644 "${ICON_SRC}" "${BRANDING_DST}/logo.png" fi if [[ -n "${CSS_SRC}" && -f "${CSS_SRC}" ]]; then if [[ "$(realpath "${CSS_SRC}" 2>/dev/null || echo "${CSS_SRC}")" != "$(realpath "${BRANDING_DST}/theme-hacktivism-overlay.css" 2>/dev/null || true)" ]]; then install -m 644 "${CSS_SRC}" "${BRANDING_DST}/theme-hacktivism-overlay.css" fi fi # Mount overlay into public assets if compose branding volume is present if [[ -d "${ROOT}/branding" ]]; then # Ensure host path for compose bind exists before recreate true fi COOKIE=$(mktemp /tmp/cp-brand-cj.XXXXXX) trap 'rm -f "$COOKIE"' EXIT echo "== admin login ==" cp_admin_login "$COOKIE" csrf_from() { local url=$1 local html html=$(cp_curl -c "$COOKIE" -b "$COOKIE" "$url") # multiple possible CSRF field names printf '%s' "$html" | sed -n 's/.*name="csrf_test_name" value="\([^"]*\)".*/\1/p' | head -1 } echo "== official: general settings (site name/description/icon) ==" # UI/docs: https://docs.castopod.org/main/en/user-guide/instance/settings/ # POST route: /{admin}/settings/instance (SettingsController::attemptInstanceEdit) GEN_GET="${CP_BASEURL}/${CP_ADMIN_GATEWAY}/settings" GEN_POST="${CP_BASEURL}/${CP_ADMIN_GATEWAY}/settings/instance" csrf=$(csrf_from "$GEN_GET") [[ -n "$csrf" ]] || { echo "ERROR: no CSRF on settings general" >&2; exit 1; } code=$(cp_curl -c "$COOKIE" -b "$COOKIE" -o /tmp/cp-gen.out -w '%{http_code}' \ -X POST "${GEN_POST}" \ -F "csrf_test_name=${csrf}" \ -F "site_name=${CP_SITE_NAME}" \ -F "site_description=${CP_SITE_DESCRIPTION}" \ -F "site_icon=@${BRANDING_DST}/logo.png;type=image/png") echo "general POST → HTTP $code" # accept redirect if [[ "$code" != "303" && "$code" != "302" && "$code" != "200" ]]; then echo "WARN: unexpected status; body:" >&2 head -c 400 /tmp/cp-gen.out >&2 || true fi echo "== official: theme accent (${CP_THEME}) ==" THEME_URL="${CP_BASEURL}/${CP_ADMIN_GATEWAY}/settings/theme" csrf=$(csrf_from "$THEME_URL") [[ -n "$csrf" ]] || { echo "ERROR: no CSRF on settings theme" >&2; exit 1; } code=$(cp_curl -c "$COOKIE" -b "$COOKIE" -o /tmp/cp-theme.out -w '%{http_code}' \ -X POST "${THEME_URL}" \ --data-urlencode "csrf_test_name=${csrf}" \ --data-urlencode "theme=${CP_THEME}") echo "theme POST → HTTP $code" if [[ "${CP_APPLY_OVERLAY}" == "1" && -f "${BRANDING_DST}/theme-hacktivism-overlay.css" ]]; then echo "== optional: warm-dark CSS overlay (not official) ==" # Serve from container public path via bind (compose) or copy into media volume podman cp "${BRANDING_DST}/theme-hacktivism-overlay.css" \ koopa-castopod:/app/public/assets/hacktivism-overlay.css 2>/dev/null || true # Idempotent inject before in public layouts (re-run after image upgrade) # Themes are root-owned in the image — patch as root (re-run after image upgrade). # shellcheck disable=SC2016 podman exec -u 0 koopa-castopod sh -c ' LINK="" for f in /app/themes/cp_app/home.php \ /app/themes/cp_app/podcast/_layout.php \ /app/themes/cp_app/episode/_layout.php \ /app/themes/cp_app/pages/_layout.php \ /app/themes/cp_app/embed.php; do [ -f "$f" ] || continue if grep -q "hacktivism-overlay" "$f"; then echo "already: $f" continue fi awk -v link="$LINK" "{ if (\$0 ~ /<\\/head>/ && !done) { print link; done=1 } print }" "$f" > "$f.tmp" \ && mv "$f.tmp" "$f" \ && echo "patched: $f" done ' || echo "WARN: layout patch skipped (container missing?)" fi # Clear page cache so theme/name show up podman exec koopa-castopod sh -c 'rm -rf /app/writable/cache/* 2>/dev/null || true' || true echo "Done. Hard-reload https://castopod.hacktivism.ch/" echo "Official UI also: ${CP_BASEURL}/${CP_ADMIN_GATEWAY}/settings (and /settings/theme)"