e55d8a3f1e0aa875238a26a2aca8a58df473e976
info@inference.coop is on the Members list but has no members row, so the reconcile classified the co-op's own inbox as a stale subscriber and would have unsubscribed it. The data model already distinguishes this: OPERATOR_EMAILS (default info@inference.coop,bot@inference.coop) are infrastructure accounts — the SSO owner and the bot — which the panel surfaces under "admins" and excludes from member counts. They live outside the members table by design. The Loomio reconciliation applies the same rule to the same accounts via LOOMIO_ALWAYS_KEEP ("must always remain in the group"). So operators become the third class here: neither members nor stale. Their subscription is left exactly as it is (never unsubscribed, and the reconcile never subscribes, so an operator who unsubscribes stays unsubscribed). The response reports how many were left, and the log line carries counts only.
Member Portal — membership middleware for the Inference Cooperative
Reconciles three systems into one membership lifecycle:
| System | Role | Source of truth for |
|---|---|---|
| Open Collective | Billing | Who is a paying member |
| Cloudron | Identity/SSO | Who can log in |
| LiteLLM | Inference | Who can use models, and how much |
Architecture
Open Collective (billing)
│ webhook
▼
┌─────────────────────────────┐
│ Member Portal (this app) │
│ - OC webhook handler │
│ - Cloudron user management │
│ - LiteLLM key management │
│ - key-injection proxy │
└─────────────────────────────┘
▲
│ X-User-Email header (LibreChat forwards identity)
│
LibreChat ──→ Member Portal ──→ LiteLLM ──→ backend
The three responsibilities
- Webhook handler (
POST /webhook/opencollective) — listens for Open Collective membership events and activates/deactivates members. - Key injector (
/v1/*) — reads the member's email from theX-User-Emailheader, looks up their LiteLLM key, and forwards the request to the gateway with that key. - Admin (
/health,/) — health and status.
Configuration (environment variables)
| Variable | Purpose |
|---|---|
CLOUDRON_API |
Cloudron API origin (auto-set by Cloudron) |
CLOUDRON_TOKEN |
Cloudron API token (Read+Write) |
MEMBERS_GROUP_ID |
Cloudron group ID for members (grants chat access) |
LITELLM_BASE |
LiteLLM gateway URL |
LITELLM_MASTER_KEY |
LiteLLM master key |
OPENCOLLECTIVE_WEBHOOK_SECRET |
Optional webhook signature secret |
Tier budgets
Governance decision (set in Loomio). Defaults:
free— $2/month of tokensmember— $15/monthsupporter— $30/month
LibreChat wiring
Point LibreChat's custom endpoint at this portal (not directly at LiteLLM), and add the identity header:
endpoints:
custom:
- name: "Inference Cooperative"
apiKey: "${LITELLM_KEY}"
baseURL: "https://portal.inference.coop/v1"
headers:
X-User-Email: "{{LIBRECHAT_USER_EMAIL}}"
models:
default: ["deepseek-v4-flash", "gpt-oss-120b"]
fetch: true
Status
Scaffold — the three handlers are stubbed with the correct integration points. Remaining work before production:
- Verify Open Collective webhook event names + payload shape
→
order.processed(every payment),new member(first only),firstPaymentflag - Verify Cloudron user-creation API payload (role/group assignment)
→
POST /api/v1/userswith{username, email, displayName, role, active}→ group assignment viaPUT /api/v1/users/:userId/groupswith{groupIds: [...]} - Add a persistent store (SQLite) for email→key mapping
→
key/listreturns token strings (not objects), so we store email→token locally - Verify LiteLLM key/generate + key/list payload shapes against live gateway
→
key/generatereturns{key: "sk-..."};key/listreturns{keys: ["<token>", ...]} - Add HMAC signature verification for the OC webhook (if OC supports it)
- Wire the OIDC addon for admin access
Scheduled tasks (scheduler addon)
Two jobs run inside the container via CloudronManifest.json → addons.scheduler:
- membership_sweep (nightly 3am) —
sweep.shcalls the portal's own/admin/sweep(auth:X-Admin-Token=WEBHOOK_TOKEN) to reconcile members against the live Open Collective list: provisions missed signups, deactivates lapsed members, syncs Loomio + Matrix. Idempotent; a missed webhook self-heals within 24 hours. - rag_refresh (nightly 3:30am) —
rag_refresh.pypulls this repo's sibling docs from the Gitea raw API (public, no credentials), uploads changed files to Open WebUI (OWUI_API_KEY), attaches them to the shared knowledge collection (DOCS_KNOWLEDGE_ID), reindexes, and re-grants the members group. State persists in/app/data/rag_refresh_state.json.
Verify after any manifest change: the deploy log must show
setupAddons: ... scheduler ... listing both tasks, and each script must
succeed when run manually inside the container.
Description
Membership middleware: syncs Open Collective members with Cloudron users and LiteLLM keys
5 MiB
0 Stars
2 Watchers
0 Forks
Languages
Python
99%
Shell
0.7%
Dockerfile
0.3%