#!/bin/sh # Membership reconciliation sweep — run by the Cloudron scheduler addon. # # Calls the portal's own /admin/sweep endpoint (localhost), which reconciles # the portal against the live Open Collective member list: provisions missing # members, deactivates lapsed members, and syncs Loomio. # # The ADMIN_TOKEN env var is shared with the main app (scheduler runs in the # same launch environment), so we use it to authenticate the call. Admin auth is # a header, never a URL token (security fix 2026-10-27). The WEBHOOK_TOKEN # fallback was removed when the cutover completed (ADMIN_TOKEN_LEGACY=0): the # legacy token now gets 403 on /admin/*, so falling back to it would turn # "ADMIN_TOKEN unset" into a confusing mid-run 403 instead of a clear abort. # # Uses Python (guaranteed present in the base image) rather than curl. set -e if [ -z "$ADMIN_TOKEN" ]; then echo "sweep: ADMIN_TOKEN is not set; aborting" >&2 exit 1 fi python3 - "$ADMIN_TOKEN" <<'PY' import sys, urllib.request, urllib.error token = sys.argv[1] url = "http://127.0.0.1:8000/admin/sweep" req = urllib.request.Request(url, method="POST", data=b"", headers={ "Content-Type": "application/json", "X-Admin-Token": token, }) try: with urllib.request.urlopen(req, timeout=60) as resp: print("sweep:", resp.read().decode()) except urllib.error.HTTPError as e: print(f"sweep: HTTP {e.code}: {e.read().decode()}", file=sys.stderr) sys.exit(1) except Exception as e: print(f"sweep: request failed: {e}", file=sys.stderr) sys.exit(1) PY