todo.txt-4-trust + safety: create photodna-credentials secret, set PHOTODNA_ENDPOINT + PHOTODNA_API_KEY on pics deployment todo.txt-5-alertmanager: add cert-manager + CNPG metrics (needs ServiceMonitors in other namespaces) todo.txt:6:shared bouncer: end-to-end test free user flow (chat.irc.now -> soju-shared) todo.txt:7:shared bouncer: end-to-end test pro upgrade + dedicated bouncer + gamja sidecar todo.txt-8-alertmanager: create ops bot, bot-internal-token secret, set ALERT_BOT_ID + ALERT_CHANNEL on bot deployment /home/josie/development/irc-now/todo.txt-9- -- /home/josie/development/irc-now/notes/company.md-70-| log search | $2/mo | full-text search across scrollback history | /home/josie/development/irc-now/notes/company.md-71-| extra storage | $3/mo | 5GB pack for pastes and images beyond pro limits | notes/company.md:72:| dedicated IP | $3/mo | avoid per-IP rate limits on shared bouncers | /home/josie/development/irc-now/notes/company.md-73-| team billing | $2/mo | consolidated invoice for a group, single admin | /home/josie/development/irc-now/notes/company.md-74- -- /home/josie/development/irc-now/notes/company.md-135-- "empowering IRC communities worldwide" /home/josie/development/irc-now/notes/company.md-136-- "comprehensive bouncer management platform" notes/company.md:137:- "Get started with our amazing free tier" /home/josie/development/irc-now/notes/company.md-138- /home/josie/development/irc-now/notes/company.md-139-## design identity -- /home/josie/development/irc-now/chat/nginx.conf-11- /home/josie/development/irc-now/chat/nginx.conf-12- location /socket { chat/nginx.conf:13: proxy_pass http://soju-shared.irc-josie-cloud.svc.cluster.local:8080/; /home/josie/development/irc-now/chat/nginx.conf-14- proxy_http_version 1.1; /home/josie/development/irc-now/chat/nginx.conf-15- proxy_set_header Host $host; -- docs/plans/2026-03-03-irc-now-roadmap.md-87-### Phase 7: Shared Bouncer /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-roadmap.md-88- docs/plans/2026-03-03-irc-now-roadmap.md:89:Free-tier shared bouncer, pro-tier dedicated bouncers with gamja sidecar, plan /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-roadmap.md-90-gating, validation webhook, bidirectional migration. /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-roadmap.md-91- /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-roadmap.md-92-| Item | Description | Status | /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-roadmap.md-93-|------|-------------|--------| docs/plans/2026-03-03-irc-now-roadmap.md:94:| soju-shared | SojuBouncer CR for free-tier shared bouncer, multi-user mode | deployed | docs/plans/2026-03-03-irc-now-roadmap.md:95:| chat.irc.now routing | nginx proxies to soju-shared instead of individual bouncer | deployed | /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-roadmap.md-96-| Keycloak chat token | access.token.lifespan set to 8h on chat client (was 5min default) | done | docs/plans/2026-03-03-irc-now-roadmap.md:97:| Plan gating (web-api) | Free users blocked from creating bouncers + networks, dashboard shows shared bouncer UI | code done | /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-roadmap.md-98-| Gamja sidecar | soju-operator adds gamja container + edge Route to dedicated bouncers | code done | /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-roadmap.md-99-| bouncer-webhook | ValidatingWebhookConfiguration enforces plan + max bouncer count | code done | -- /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-roadmap.md-105-| Downgrade UI | Network picker for reverse migration on pro->free | pending | /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-roadmap.md-106- docs/plans/2026-03-03-irc-now-roadmap.md:107:**Design doc:** `docs/plans/2026-03-07-shared-bouncer-design.md` docs/plans/2026-03-03-irc-now-roadmap.md:108:**Implementation plan:** `docs/plans/2026-03-07-shared-bouncer-plan.md` /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-roadmap.md-109- /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-roadmap.md-110---- -- docs/plans/2026-03-03-irc-now-platform-design.md-110-**Billing flow:** Stripe webhooks update Keycloak user attributes (plan tier) /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-platform-design.md-111-and trigger operator reconciliation for resource changes (e.g., upgrading from docs/plans/2026-03-03-irc-now-platform-design.md:112:shared to dedicated bouncer). /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-platform-design.md-113- /home/josie/development/irc-now/docs/plans/2026-03-03-irc-now-platform-design.md-114-### chat.irc.now -- Web Client -- deploy/instances/soju-shared.yaml-2-kind: SojuBouncer deploy/instances/soju-shared.yaml-3-metadata: deploy/instances/soju-shared.yaml:4: name: soju-shared deploy/instances/soju-shared.yaml-5- namespace: irc-josie-cloud deploy/instances/soju-shared.yaml-6-spec: deploy/instances/soju-shared.yaml-7- hostname: irc.now deploy/instances/soju-shared.yaml:8: title: irc.now shared bouncer deploy/instances/soju-shared.yaml-9- listeners: deploy/instances/soju-shared.yaml-10- - address: ":6667" -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-5-## Overview /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-6- docs/plans/2026-03-07-shared-bouncer-design.md:7:Free-tier users share a single soju bouncer instance (`soju-shared`). Pro-tier users /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-8-get dedicated bouncer instances with a gamja web chat sidecar. A validation webhook /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-9-enforces plan-based quotas. Migration between shared and dedicated is user-initiated -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-20- WebSocket WebSocket /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-21- | | docs/plans/2026-03-07-shared-bouncer-design.md:22: soju-shared:8080 apple:8080 /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-23- (multi-user bouncer) (dedicated bouncer) /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-24- | | docs/plans/2026-03-07-shared-bouncer-design.md:25: soju_shared DB soju_apple DB /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-26- (all free user data) (single user data) /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-27-``` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-28- docs/plans/2026-03-07-shared-bouncer-design.md:29:### soju-shared /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-30- docs/plans/2026-03-07-shared-bouncer-design.md:31:A SojuBouncer CR deployed via `deploy/instances/soju-shared.yaml`. Serves all free-tier /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-32-users with soju's built-in multi-user isolation (`enable-user-on-auth true`). Each user /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-33-authenticates via SASL OAUTHBEARER and gets their own networks, channels, and message -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-44-### chat.irc.now /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-45- docs/plans/2026-03-07-shared-bouncer-design.md:46:Unchanged. gamja + nginx proxying WebSocket to `soju-shared:8080`. Free-tier web chat /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-47-entry point. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-48- -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-62-### Portal level (web-api) /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-63- docs/plans/2026-03-07-shared-bouncer-design.md:64:- `POST /bouncers`: rejected if `plan == "free"`. Free users use soju-shared /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-65- automatically. docs/plans/2026-03-07-shared-bouncer-design.md-66-- Dashboard UI: free users see "Shared Bouncer" section with a link to chat.irc.now -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-87-Free-tier network limit (1 upstream network) enforced at both the portal level and /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-88-via soju configuration where possible. The portal talks to soju's admin socket to docs/plans/2026-03-07-shared-bouncer-design.md:89:manage networks for shared bouncer users. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-90- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-91-## Gamja Sidecar (Dedicated Bouncers) /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-92- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-93-When the soju-operator reconciles a dedicated bouncer (any SojuBouncer CR that is docs/plans/2026-03-07-shared-bouncer-design.md:94:not `soju-shared`), it adds: /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-95- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-96-### Sidecar container -- docs/plans/2026-03-07-shared-bouncer-design.md-120-3. If unchecked: dedicated bouncer created fresh. docs/plans/2026-03-07-shared-bouncer-design.md-121-4. If checked: dedicated bouncer created, then a Kubernetes Job migrates user data docs/plans/2026-03-07-shared-bouncer-design.md:122: from `soju_shared` DB to the new tenant DB. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-123- docs/plans/2026-03-07-shared-bouncer-design.md-124-Migration Job copies: -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-131-Message logs are NOT migrated (scrollback resets). /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-132- docs/plans/2026-03-07-shared-bouncer-design.md:133:The Job removes the user from `soju_shared` DB after successful copy. If the Job /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-134-fails, the dedicated bouncer CR is deleted and the user stays on shared. Portal shows /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-135-an error. -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-140- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-141-1. Portal shows "Migrate to Shared Bouncer" with a network picker if the user has docs/plans/2026-03-07-shared-bouncer-design.md:142: more than 1 upstream network (free tier allows 1). /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-143-2. Reverse migration Job copies selected network + channels + receipts back to docs/plans/2026-03-07-shared-bouncer-design.md:144: `soju_shared` DB, within free-tier quota limits. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-145-3. Excess networks and their data are dropped. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-146-4. Message logs not migrated (48h scrollback resets). -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-149-## Components to Build /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-150- docs/plans/2026-03-07-shared-bouncer-design.md:151:1. **Deploy soju-shared** -- apply `deploy/instances/soju-shared.yaml`, verify /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-152- chat.irc.now connects to it docs/plans/2026-03-07-shared-bouncer-design.md-153-2. **Soju-operator: gamja sidecar** -- add gamja container + edge Route generation /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-154- for dedicated bouncers docs/plans/2026-03-07-shared-bouncer-design.md:155:3. **Web-api: plan gating** -- free users see shared bouncer UI, pro users see docs/plans/2026-03-07-shared-bouncer-design.md:156: bouncer management. Block bouncer creation for free tier. docs/plans/2026-03-07-shared-bouncer-design.md-157-4. **Web-api: migration UI** -- "Migrate from shared" checkbox on create, /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-158- "Migrate to shared" on delete/downgrade with network picker -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-161-6. **Validation webhook (bouncer-webhook)** -- new crate, ValidatingWebhookConfiguration. docs/plans/2026-03-07-shared-bouncer-design.md-162- Enforces: no bouncer creation for free, max bouncer count for pro, network limits. docs/plans/2026-03-07-shared-bouncer-design.md:163:7. **Network limit enforcement** -- portal + soju-level checks for free-tier /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-design.md-164- 1-network limit -- docs/plans/2026-03-06-alertmanager-design.md-19-Severity: critical. /home/josie/development/irc-now/docs/plans/2026-03-06-alertmanager-design.md-20- docs/plans/2026-03-06-alertmanager-design.md:21:Services: web-api, txt, pics, bot, soju (shared bouncer), ergo (irc-now-net), /home/josie/development/irc-now/docs/plans/2026-03-06-alertmanager-design.md-22-chat, keycloak. /home/josie/development/irc-now/docs/plans/2026-03-06-alertmanager-design.md-23- -- docs/plans/2026-03-07-shared-bouncer-plan.md-3-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-4- docs/plans/2026-03-07-shared-bouncer-plan.md:5:**Goal:** Implement free-tier shared bouncer, pro-tier dedicated bouncers with gamja sidecar, plan gating, validation webhook, and bidirectional migration. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-6- docs/plans/2026-03-07-shared-bouncer-plan.md:7:**Architecture:** soju-shared serves all free users via multi-user mode. Pro users get dedicated SojuBouncer CRs with a gamja sidecar container and edge Route. A ValidatingWebhookConfiguration enforces plan-based quotas. Migration Jobs copy user data between tenant DBs. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-8- docs/plans/2026-03-07-shared-bouncer-plan.md-9-**Tech Stack:** Rust (Axum 0.8, kube.rs 3.0, sqlx, tokio-postgres), Kubernetes (OCP 4.20, cert-manager), gamja (nginx), Keycloak 26.1 /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-10- docs/plans/2026-03-07-shared-bouncer-plan.md:11:**Design doc:** `docs/plans/2026-03-07-shared-bouncer-design.md` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-12- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-13---- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-14- docs/plans/2026-03-07-shared-bouncer-plan.md:15:## Task 1: Deploy soju-shared /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-16- docs/plans/2026-03-07-shared-bouncer-plan.md:17:Deploy the shared bouncer instance and verify chat.irc.now connects. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-18- docs/plans/2026-03-07-shared-bouncer-plan.md-19-**Files:** docs/plans/2026-03-07-shared-bouncer-plan.md:20:- Modify: `deploy/instances/soju-shared.yaml` docs/plans/2026-03-07-shared-bouncer-plan.md-21-- Modify: `chat/nginx.conf` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-22- docs/plans/2026-03-07-shared-bouncer-plan.md:23:**Step 1: Apply the soju-shared CR** /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-24- docs/plans/2026-03-07-shared-bouncer-plan.md:25:The manifest already exists at `deploy/instances/soju-shared.yaml`. It needs the `auth` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-26-field format to match what the operator expects (`oauth2_url` not `oauth2Url`). Verify /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-27-the YAML matches the operator's `AuthSpec` struct. The operator will create the -- docs/plans/2026-03-07-shared-bouncer-plan.md-30-Run: /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-31-```bash docs/plans/2026-03-07-shared-bouncer-plan.md:32:oc apply -f deploy/instances/soju-shared.yaml -n irc-josie-cloud /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-33-``` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-34- docs/plans/2026-03-07-shared-bouncer-plan.md:35:Expected: SojuBouncer `soju-shared` created. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-36- docs/plans/2026-03-07-shared-bouncer-plan.md-37-**Step 2: Verify resources created** -- docs/plans/2026-03-07-shared-bouncer-plan.md-39-Run: /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-40-```bash docs/plans/2026-03-07-shared-bouncer-plan.md:41:oc get deployment soju-shared -n irc-josie-cloud docs/plans/2026-03-07-shared-bouncer-plan.md:42:oc get svc soju-shared -n irc-josie-cloud docs/plans/2026-03-07-shared-bouncer-plan.md:43:oc get cm soju-shared-config -n irc-josie-cloud docs/plans/2026-03-07-shared-bouncer-plan.md:44:oc get secret soju-shared-db -n irc-josie-cloud /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-45-``` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-46- -- docs/plans/2026-03-07-shared-bouncer-plan.md-49-**Step 3: Update chat nginx.conf** /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-50- docs/plans/2026-03-07-shared-bouncer-plan.md:51:The repo copy of `chat/nginx.conf` currently points to `soju-shared` which is correct. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-52-The running container was manually patched to `josie`. Rebuild chat to pick up the /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-53-repo config. -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-58-``` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-59- docs/plans/2026-03-07-shared-bouncer-plan.md:60:Expected: Build succeeds, chat pod restarts with nginx proxying to `soju-shared:8080`. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-61- docs/plans/2026-03-07-shared-bouncer-plan.md-62-**Step 4: Verify chat.irc.now connects** /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-63- docs/plans/2026-03-07-shared-bouncer-plan.md:64:Open `https://chat.irc.now`, authenticate via Keycloak. Verify connection to soju-shared /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-65-and that messages work. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-66- -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-70- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-71-```bash docs/plans/2026-03-07-shared-bouncer-plan.md:72:git add deploy/instances/soju-shared.yaml chat/nginx.conf docs/plans/2026-03-07-shared-bouncer-plan.md:73:git commit -m "deploy: apply soju-shared bouncer for free tier" /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-74-``` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-75- -- docs/plans/2026-03-07-shared-bouncer-plan.md-122-For free users (`plan != "pro"`), show: /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-123-```html docs/plans/2026-03-07-shared-bouncer-plan.md:124:
you're on the shared bouncer. open web chat
/home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-126-upgrade to pro /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-127-``` -- docs/plans/2026-03-07-shared-bouncer-plan.md-131-**Step 6: Update bouncers page** /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-132- docs/plans/2026-03-07-shared-bouncer-plan.md:133:In `crates/web-api/templates/bouncers.html`, add a "migrate from shared bouncer" docs/plans/2026-03-07-shared-bouncer-plan.md-134-checkbox to the create form (lines 13-24): /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-135- -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-138- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-142- -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-172- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-173-Test that `build_deployment()` adds a second container when the bouncer name is NOT docs/plans/2026-03-07-shared-bouncer-plan.md:174:`soju-shared`. Test that `soju-shared` gets only the soju container. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-175- docs/plans/2026-03-07-shared-bouncer-plan.md-176-**Step 2: Run test to verify it fails** -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-229-In `build_configmap()` at `crates/soju-operator/src/resources/configmap.rs`, add /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-230-gamja nginx config and config.json as additional data keys when the bouncer name docs/plans/2026-03-07-shared-bouncer-plan.md:231:is not `soju-shared`: /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-232- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-233-```rust docs/plans/2026-03-07-shared-bouncer-plan.md:234:if bouncer.name_any() != "soju-shared" { /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-235- data.insert("gamja-nginx".to_string(), gamja_nginx_config()); /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-236- data.insert("gamja-config".to_string(), gamja_config_json(bouncer)); -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-242-In `build_deployment()` at `crates/soju-operator/src/resources/deployment.rs`, after /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-243-the soju container (line ~163), add a gamja sidecar container when the bouncer name docs/plans/2026-03-07-shared-bouncer-plan.md:244:is not `soju-shared`: /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-245- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-246-```rust docs/plans/2026-03-07-shared-bouncer-plan.md:247:if bouncer.name_any() != "soju-shared" { /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-248- containers.push(Container { docs/plans/2026-03-07-shared-bouncer-plan.md-249- name: "gamja".to_string(), -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-302- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-303-```rust docs/plans/2026-03-07-shared-bouncer-plan.md:304:if bouncer.name_any() != "soju-shared" { /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-305- let edge_route = build_edge_route(&bouncer); /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-306- apply_resource(&api_route, &edge_route).await?; -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-327-oc get pods -l app.kubernetes.io/managed-by=soju-operator -n irc-josie-cloud /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-328-# Each dedicated bouncer pod should show 2/2 containers docs/plans/2026-03-07-shared-bouncer-plan.md:329:# soju-shared should show 1/1 /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-330-``` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-331- -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-380- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-381-1. Extracts `irc.now/owner` label from the incoming CR docs/plans/2026-03-07-shared-bouncer-plan.md:382:2. If owner is empty (admin-applied CR like soju-shared), ALLOW docs/plans/2026-03-07-shared-bouncer-plan.md-383-3. Queries the accounts DB: `SELECT plan FROM users WHERE keycloak_sub = $1` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-384-4. If plan != 'pro', DENY with message "upgrade to pro to create a dedicated bouncer" -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-511- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-512-/// Copy a user's data from dedicated DB back to shared DB, docs/plans/2026-03-07-shared-bouncer-plan.md:513:/// keeping only the specified network (free tier = 1 network). /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-514-pub async fn reverse_migrate_user( docs/plans/2026-03-07-shared-bouncer-plan.md-515- source: &PgPool, -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-527- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-528-The exact table names and schemas come from soju's Postgres schema. Query the docs/plans/2026-03-07-shared-bouncer-plan.md:529:soju-shared DB to discover the schema: /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-530- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-531-```bash docs/plans/2026-03-07-shared-bouncer-plan.md:532:oc exec soju-db-1 -n irc-josie-cloud -- psql -U accounts -d soju_shared -c '\dt' /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-533-``` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-534- -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-557- docs/plans/2026-03-07-shared-bouncer-plan.md-558-After the bouncer CR is created and becomes Ready, if `migrate == Some("true")`: docs/plans/2026-03-07-shared-bouncer-plan.md:559:1. Connect to the soju-shared tenant DB /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-560-2. Connect to the new bouncer's tenant DB /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-561-3. Call `migrate_user(shared_pool, new_pool, &user.email)` -- docs/plans/2026-03-07-shared-bouncer-plan.md-581-## Task 6: Network limit enforcement /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-582- docs/plans/2026-03-07-shared-bouncer-plan.md:583:Enforce 1 upstream network limit for free-tier users on the shared bouncer. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-584- docs/plans/2026-03-07-shared-bouncer-plan.md-585-**Files:** -- docs/plans/2026-03-07-shared-bouncer-plan.md-589-**Step 1: Write test for network limit** /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-590- docs/plans/2026-03-07-shared-bouncer-plan.md:591:Test that a free user on the shared bouncer cannot add a second upstream network. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-592- docs/plans/2026-03-07-shared-bouncer-plan.md-593-**Step 2: Run test to verify it fails** -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-601-current count. If already at 1, reject with a message to upgrade. /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-602- docs/plans/2026-03-07-shared-bouncer-plan.md:603:The portal manages networks for shared bouncer users by talking to soju's admin docs/plans/2026-03-07-shared-bouncer-plan.md:604:socket or directly querying the shared bouncer's tenant DB: /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-605- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-606-```sql -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-610-``` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-611- docs/plans/2026-03-07-shared-bouncer-plan.md:612:If count >= 1, return 403 with "free tier is limited to 1 upstream network". /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-613- docs/plans/2026-03-07-shared-bouncer-plan.md-614-**Step 4: Run test to verify it passes** -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-626-```bash /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-627-git add crates/web-api/ docs/plans/2026-03-07-shared-bouncer-plan.md:628:git commit -m "feat(web-api): enforce 1-network limit for free tier" /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-629-``` /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-630- -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-654- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-655-1. Log in as a free user at my.irc.now docs/plans/2026-03-07-shared-bouncer-plan.md:656:2. Dashboard shows "shared bouncer" section with chat.irc.now link docs/plans/2026-03-07-shared-bouncer-plan.md:657:3. Navigate to /bouncers -- no create button, shows shared bouncer info docs/plans/2026-03-07-shared-bouncer-plan.md:658:4. Open chat.irc.now -- connects to soju-shared, can join channels /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-659-5. Try to apply a SojuBouncer CR with the free user's owner label -- webhook denies /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-660- -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-672-1. Cancel subscription (or manually set plan='free') /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-673-2. Delete dedicated bouncer, select network to keep docs/plans/2026-03-07-shared-bouncer-plan.md:674:3. Verify reverse migration to shared bouncer /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-675-4. Verify dedicated bouncer CR and resources cleaned up /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-676-5. Verify user can connect to chat.irc.now again -- /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-680-```bash /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-681-git add -A docs/plans/2026-03-07-shared-bouncer-plan.md:682:git commit -m "fix: integration test fixes for shared bouncer" /home/josie/development/irc-now/docs/plans/2026-03-07-shared-bouncer-plan.md-683-``` -- /home/josie/development/irc-now/crates/web-api/templates/bouncer_detail.html-58-move to the shared bouncer, keeping one network.
crates/web-api/templates/bouncer_detail.html-61- /home/josie/development/irc-now/crates/web-api/templates/bouncer_detail.html-62- downgrade to shared -- crates/web-api/templates/bouncer_downgrade.html-32- /home/josie/development/irc-now/crates/web-api/templates/bouncer_downgrade.html-36- -- /home/josie/development/irc-now/crates/web-api/templates/bouncers.html-20- /home/josie/development/irc-now/crates/web-api/templates/bouncers.html-24- -- /home/josie/development/irc-now/crates/web-api/templates/dashboard.html-75- {% endif %} /home/josie/development/irc-now/crates/web-api/templates/dashboard.html-76- {% else %} crates/web-api/templates/dashboard.html:77:your free plan includes access to the shared bouncer via web chat.
/home/josie/development/irc-now/crates/web-api/templates/dashboard.html-79-free accounts get 1 network with 48 hours of scrollback on a shared bouncer. pro accounts ($4/mo) get unlimited networks, unlimited scrollback, a dedicated bouncer, and a custom hostname.
/home/josie/development/irc-now/crates/web-landing/static/docs/index.html-51- /home/josie/development/irc-now/crates/web-landing/static/docs/index.html-52-free gets you 1 network, 48 hours of scrollback, and a shared bouncer. pro ($4/mo) gives you unlimited networks, unlimited scrollback, a dedicated bouncer, and a custom hostname for /whois.
free-tier content (images, pastes) expires automatically. pro content may also expire based on your account settings. we are not responsible for expired content.
/home/josie/development/irc-now/crates/web-landing/static/terms/index.html-47- /home/josie/development/irc-now/crates/web-landing/static/terms/index.html-48-