# irc.now Project Memory ## Project Overview IRC bouncer-as-a-service platform. Rust workspace monorepo at ~/development/irc-now. ## Architecture - **Workspace crates**: common, web-api, web-landing, bot-landing, net-landing, bot, soju-operator, ergo-operator, txt, pics, platform-operator - **Non-crate services**: chat/ (gamja + nginx, not Rust) - **Rust**: Axum 0.8, kube.rs 3.0, sqlx, openid 0.23, askama 0.15 - **MSRV**: 1.88 (askama 0.15, kube 3.0, home 0.5.12 require it) - **Containerfiles**: rust:1.88 -> ubi9-minimal multi-stage builds ## Cluster State (OCP 4.20) See [cluster.md](cluster.md) for details. ### Namespaces - `irc-josie-cloud`: web-landing, web-api, bot-landing, accounts-db, soju, soju-operator, ergo-operator, platform-operator, irc-now-net, txt, pics, minio, paste-db, pics-db, bot-db, chat - `irc-keycloak`: keycloak-db (CNPG only, Keycloak deployment moved to `keycloak` namespace) - `keycloak`: keycloak-operator, irc-now Keycloak CR, josie.cloud Keycloak CR ### Services Live - `irc.now` -- landing page (web-landing) - `irc.bot` -- bot service + landing page (bot, bot-landing) - `network.irc.now` -- network landing page (net-landing), brand is `net.irc.now` - `my.irc.now` -- account portal (web-api) - `txt.irc.now` -- pastebin (txt) - `irc.pics` -- image host (pics) - `chat.irc.now` -- web IRC client (gamja + nginx, WebSocket proxy to soju) - `auth.irc.now` -- Keycloak 26.1 (realm: irc-now, operator-managed CR in `keycloak` ns) - `stats.irc.now` -- Grafana dashboards (Keycloak SSO, admin/viewer roles) ### TLS Pattern cert-manager Certificate CR -> Secret -> Route `tls.externalCertificate.name` ClusterIssuer: `letsencrypt-prod` (dns-01 via Cloudflare) ## Build Pattern OCP binary builds using `--from-archive` with minimal tar (~65K): ```bash tar czf /tmp/build.tar.gz --exclude='target' --exclude='.git' \ --exclude='irc-now-landing-page' --exclude='status' --exclude='design' \ --exclude='./docs' --exclude='notes' Cargo.toml Cargo.lock crates/ oc start-build --from-archive=/tmp/build.tar.gz -n irc-josie-cloud --follow ``` Use `--exclude='./docs'` (not `--exclude='docs'`) to avoid excluding `crates/*/static/docs/`. ## Keycloak Theme Custom login theme at `deploy/keycloak/theme/irc-now/login/`. Deployed via ConfigMap + init container. To update: edit theme files, recreate ConfigMap, delete the Keycloak pod. ## Key Secrets - `accounts-db-app` (irc-josie-cloud): CNPG-managed DB creds - `oidc-account-portal` (irc-josie-cloud): OIDC client secret - `oidc-paste`, `oidc-pics`, `oidc-soju`, `oidc-ergo`, `oidc-bot`, `oidc-stats` (irc-josie-cloud): per-service OIDC clients - `stripe-keys` (irc-josie-cloud): secret-key, price-id, webhook-secret (placeholder values) - `minio-credentials` (irc-josie-cloud): root-user, root-password - `paste-db-app`, `pics-db-app`, `bot-db-app` (irc-josie-cloud): CNPG-managed DB creds - `grafana-sa-token` (irc-josie-cloud): SA token for Prometheus access - Keycloak secrets in `keycloak` + `irc-keycloak` namespaces ## CNPG Notes - StorageClass: `customer-workload-storage` (hostPath PVs) - SELinux context: `container_file_t` required on hostPath dirs - UID ranges: irc-josie-cloud=1001260000, irc-keycloak=1001300000 ## Phase Status - **Phase 1** (Foundation): done. Plan: docs/plans/2026-03-03-irc-now-phase1-plan.md - **Phase 1.5** (Fill the Gaps): done. Remaining: replace placeholder stripe-keys, Upptime, remove znc DNS - **Phase 2** (Observability): done. Grafana, Prometheus, alertmanager, ServiceMonitors - **Phase 3** (Supporting Services): done. txt, pics, chat, content expiry, Keycloak theme - **Phase 4** (Network Hosting): ergo-operator done, portal integration done, wildcard DNS pending - **Phase 5** (Analytics): done. Events table, business metrics, Grafana dashboards - **Phase 6** (Bots): deployed. mlua Lua 5.4 sandbox, OIDC auth, PostgreSQL storage ## Key Patterns / Gotchas - `stripe_util.rs` (not `stripe.rs`) to avoid shadowing the `stripe` crate - Axum route ordering: static routes (`/my`) must precede parameterized (`/{id}`) - SameSite::Lax required on session cookies for cross-site OIDC redirects - `metrics-exporter-prometheus` needs `default-features = false` to avoid aws-lc-rs/ring conflict - OnceLock pattern (not Extension) for metrics handler - `make_interval` needs `$1::int` cast (sqlx binds i64 as bigint) - CNPG user is `accounts` (not `app`) -- grants needed for new tables ## Content Expiry + Keycloak Sync - `update_keycloak_user_attributes()` in profile.rs syncs plan + content_expires to Keycloak - Called from: profile save, auth callback, billing webhook - Keycloak user attributes -> protocol mappers -> ID token claims - txt/pics: `extract_custom_claims()` base64-decodes JWT payload ## Chat Service (chat.irc.now) gamja web IRC client served by nginx. Top-level `chat/` directory (not a Rust crate). - Containerfile clones gamja from `git.sr.ht/~emersion/gamja` (codeberg mirror unreliable) - OAuth2 SSO: gamja -> Keycloak (public client `chat`) -> SASL OAUTHBEARER -> soju - Keycloak `chat` client: PKCE disabled, `exclude.session.state.from.auth.response` set - Build: `oc start-build chat --from-dir=chat/ -n irc-josie-cloud --follow` ## Whois Hostname Cloaking - CRD has `hostname_cloak: Option`, operator emits `hostname-cloak` in soju.conf - Custom soju image at `crates/soju-operator/soju/` (Codeberg master, nosqlite build) - Cloaking applies to downstream connections only (not upstream network whois) ## Ergo Operator (net.irc.now) - CRD: ErgoNetwork (`irc.now/v1alpha1`), manages Ergo IRC server instances - Image: `ghcr.io/ergochat/ergo:stable` - OAuth2 token introspection via Keycloak (`oidc-ergo` secret) - External connect: `net.irc.now:443` TLS, internal: `irc-now-net:6667` plaintext - Portal: network management tab at /networks (list, create, detail, delete, status polling) - web-api ErgoNetwork CRD types in k8s.rs, `default_network_spec()` fills platform defaults - Hostnames: `{name}.irc.now` via wildcard DNS, shared `oidc-ergo` Keycloak client - Networks filtered by `irc.now/owner` label (same pattern as bouncers) ## Soju Version Notes - Primary repo: Codeberg (codeberg.org/emersion/soju) -- GitHub/SourceHut are stale - Custom image builds from Codeberg master, `nosqlite` build tag, imagePullPolicy Always ## Business Metrics + Analytics - `events` table: signup, login, bouncer_create/delete, network_create/delete, plan changes - `business_metrics.rs`: Stripe MRR (300s), event gauges (60s), soju stats (300s) - txt/pics expose `GET /api/usage/{sub}` for cross-service per-user stats - Grafana admin dashboard: MRR, conversion, churn, revenue over time ## Platform Operator (Deployed) Manages WebService, ChatService, MinioInstance CRDs. Server-side apply, 300s reconcile. - CRD group: `irc.now`, version: `v1alpha1` - Deploy manifests: `crates/platform-operator/deploy/` - Instance CRs: `deploy/instances/` ## Obsidian Docs Service notes at ~/Documents/Obsidian Vault/98 josiedot/services/: - irc-now-landing.md, irc-now-portal.md, irc-now-keycloak.md, irc-bot-landing.md - irc-now-bot.md, irc-now-chat.md, irc-now-txt.md, irc-now-pics.md, irc-now-stats.md - irc-now-net-landing.md, irc-now-soju-operator.md, irc-now-ergo-operator.md, irc-now-platform-operator.md