# #############################################
# ╭───────────────────────────────────────╮  #
# │                                        │  #
# │   ___  ____  ___  _    ____            │  #
# │  | _ \|  _ \/ _ \| |  | ___|          │  #
# │  |  _/| |_) | | | | |  |___ \         │  #
# │  | |  |  __/| |_| | |___ ___) |       │  #
# │  |_|  |_|    \___/|_____|____/  .      │  #
# │                                        │  #
# │          svc · db · git · api          │  #
# ╰───────────────────────────────────────╯  #
# #############################################
[svc.prole.org](https://svc.prole.org)  ·  [db.prole.org](https://db.prole.org)  ·  [git.prole.org](https://git.prole.org)  ·  [api.prole.org](https://api.prole.org)
# Prole. **Prole** is the reference homelab deployment of the **[Knoe](https://git.knoe.dev)** platform — running on a three-node k3s ARM64 cluster with Samba Active Directory, Kerberos SSO, and Google Workspace login across all services. This repo is the `prole.org` fork of [knoe-db](https://git.knoe.dev/knoe-dev/knoe-db), tracking upstream and carrying the cluster-specific manifests, identity configuration, and operational tooling for `prole.org`. --- ## Knoe platform **Knoe makes it practical to run a Supabase-style platform across air-gapped, edge, and cloud environments.** It packages the core building blocks needed for a modern internal developer platform around PostgreSQL, object storage, secrets, auth, observability, and Kubernetes-native operations — with a bias toward simple deployment, shard-based scale-out, and deterministic ingestion. --- ## Platform badges
![Kubernetes](https://img.shields.io/badge/Kubernetes-326CE5?style=for-the-badge&logo=kubernetes&logoColor=white) ![K3s](https://img.shields.io/badge/K3s-FFC61C?style=for-the-badge&logo=k3s&logoColor=black) ![K3d](https://img.shields.io/badge/K3d-FFC61C?style=for-the-badge&logo=k3s&logoColor=black) ![GCP](https://img.shields.io/badge/Google%20Cloud-4285F4?style=for-the-badge&logo=googlecloud&logoColor=white) ![Helm](https://img.shields.io/badge/Helm-0F1689?style=for-the-badge&logo=helm&logoColor=white) ![Argo CD](https://img.shields.io/badge/Argo%20CD-EF7B4D?style=for-the-badge&logo=argo&logoColor=white) ![Docker](https://img.shields.io/badge/Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white) ![containerd](https://img.shields.io/badge/containerd-575757?style=for-the-badge&logo=linuxcontainers&logoColor=white) ![PostgreSQL](https://img.shields.io/badge/PostgreSQL-4169E1?style=for-the-badge&logo=postgresql&logoColor=white) ![CloudNativePG](https://img.shields.io/badge/CloudNativePG-0A6C74?style=for-the-badge&logo=postgresql&logoColor=white) ![psql](https://img.shields.io/badge/psql-336791?style=for-the-badge&logo=postgresql&logoColor=white) ![pgvector](https://img.shields.io/badge/pgvector-4B5563?style=for-the-badge&logo=postgresql&logoColor=white) ![MariaDB](https://img.shields.io/badge/MariaDB-003545?style=for-the-badge&logo=mariadb&logoColor=white) ![SQLite](https://img.shields.io/badge/SQLite-003B57?style=for-the-badge&logo=sqlite&logoColor=white) ![Python](https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white) ![uv](https://img.shields.io/badge/uv-5C48EE?style=for-the-badge&logo=python&logoColor=white) ![FastAPI](https://img.shields.io/badge/FastAPI-009688?style=for-the-badge&logo=fastapi&logoColor=white) ![Pydantic](https://img.shields.io/badge/Pydantic-E92063?style=for-the-badge&logo=pydantic&logoColor=white) ![Next.js](https://img.shields.io/badge/Next.js-000000?style=for-the-badge&logo=next.js&logoColor=white) ![React](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB) ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white) ![Supabase](https://img.shields.io/badge/Supabase-3ECF8E?style=for-the-badge&logo=supabase&logoColor=white) ![GitLab](https://img.shields.io/badge/GitLab-FC6D26?style=for-the-badge&logo=gitlab&logoColor=white) ![Gitea](https://img.shields.io/badge/Gitea-609926?style=for-the-badge&logo=gitea&logoColor=white) ![OpenBao](https://img.shields.io/badge/OpenBao-FFEC6E?style=for-the-badge&logo=vault&logoColor=black) ![Garage](https://img.shields.io/badge/Garage-555555?style=for-the-badge&logo=minio&logoColor=white) ![Vector](https://img.shields.io/badge/Vector-7A4BFF?style=for-the-badge&logo=vector&logoColor=white) ![Prometheus](https://img.shields.io/badge/Prometheus-E6522C?style=for-the-badge&logo=prometheus&logoColor=white) ![Grafana](https://img.shields.io/badge/Grafana-F46800?style=for-the-badge&logo=grafana&logoColor=white) ![cert-manager](https://img.shields.io/badge/cert--manager-326CE5?style=for-the-badge&logo=letsencrypt&logoColor=white) ![Traefik](https://img.shields.io/badge/Traefik-24A1C1?style=for-the-badge&logo=traefikproxy&logoColor=white) ![Kong](https://img.shields.io/badge/Kong-003459?style=for-the-badge&logo=kong&logoColor=white) ![OIDC](https://img.shields.io/badge/OIDC-4B5563?style=for-the-badge&logo=openid&logoColor=white) ![Google Workspace](https://img.shields.io/badge/Google%20Workspace-4285F4?style=for-the-badge&logo=google&logoColor=white) ![Samba AD](https://img.shields.io/badge/Samba%20AD-0C4DA2?style=for-the-badge&logo=windows&logoColor=white) ![1Password](https://img.shields.io/badge/1Password-3B66BC?style=for-the-badge&logo=1password&logoColor=white) ![OpenTofu](https://img.shields.io/badge/OpenTofu-FFDA18?style=for-the-badge&logo=opentofu&logoColor=black) ![Linux](https://img.shields.io/badge/Linux-333333?style=for-the-badge&logo=linux&logoColor=white) ![Raspberry%20Pi](https://img.shields.io/badge/Raspberry%20Pi-C51A4A?style=for-the-badge&logo=raspberrypi&logoColor=white)
--- ## What Knoe is Knoe is an infrastructure stack for running a Supabase-like data and application platform across constrained, sovereign, and cloud environments, including: - air-gapped networks - edge and small-cluster deployments (K3s, K3d) - Google Cloud Platform (GCP) deployments - sovereign or private data environments - disconnected labs and field systems - internal platforms where cloud dependencies are undesirable The design goal is not to mimic Supabase branding or every managed feature exactly. The design goal is to provide the **useful substrate** people actually want: - PostgreSQL as the center of gravity - object storage - auth integration - secret management - ingress and service exposure - observability - reproducible Kubernetes deployment - application and worker services around the database - ingestion patterns that are safe, resumable, and idempotent --- ## Core idea **Knoe can run Supabase-style workloads in an air gap, on the edge, or in the cloud.** That means: - PostgreSQL remains the system of record, managed as `knoe-db` within the `knoe-system` namespace - services are deployed on Kubernetes (K3d locally, K3s on-prem, GKE on GCP) - auth can come from local identity systems or cloud OIDC where available - object storage stays inside the environment - secrets stay inside the environment - ingestion does not depend on live internet services - the stack can be deployed in shards for locality, resilience, and operational simplicity --- ## Main components ### Application layer - **Next.js app** for the user-facing platform - **knoe-agent worker** for async and background execution ### Data layer - **knoe-db** — CloudNativePG-managed PostgreSQL cluster in the `knoe-system` namespace - **PostgreSQL** as the primary relational store - **append-only fact tables** for durable ingestion and replay-friendly modeling - **PostgreSQL COPY** for efficient bulk ingest ### Deployed add-ons - **Supabase** — self-hosted Supabase stack for auth, realtime, storage, and API gateway - **ArgoCD** — GitOps continuous delivery for the `knoe-system` cluster - **Gitea** — lightweight self-hosted Git service - **GitLab** — full-featured self-hosted DevOps platform ### Storage and secrets - **Garage** for S3-compatible object storage - **OpenBao** for secret storage and secret distribution ### Identity - **Dev auth:** Samba AD + IdP - **Cloud auth:** Google Workspace OIDC ### Observability - **Vector** for log and event shipping - **Prometheus / Grafana** for metrics and dashboards ### Platform operations - **K3d** — local development clusters - **K3s** — on-prem lightweight Kubernetes - **GCP / GKE** — cloud-hosted cluster tier - **Helm** - **ArgoCD** - **Ansible** - **OpenTofu** --- ## Ingestion model Knoe is designed around deterministic, replayable ingestion. Key patterns: - **SQLite delta cartridges** as portable input units - **append-only fact tables** for auditability and recovery - **PostgreSQL COPY** for high-throughput loading - **queue-driven workers** using `SKIP LOCKED` - **idempotent ingestion** so retries are safe - **shard-based deployment** so data movement stays deliberate and bounded This makes the platform well suited to environments where data may arrive in batches, be transferred physically, or need careful replay and provenance. --- ## Architecture at a glance ```mermaid flowchart LR subgraph Clients U["Users / Operators"] end subgraph knoe-system ["knoe-system (Kubernetes)"] A["Next.js App"] W["knoe-agent worker"] SB["Supabase\n(auth · realtime · storage · API)"] ARGO["ArgoCD\n(GitOps)"] GITEA["Gitea\n(Git)"] GITLAB["GitLab\n(DevOps)"] PG["knoe-db\n(CloudNativePG / PostgreSQL)"] OBJ["Garage\n(Object Store)"] SEC["OpenBao\n(Secrets)"] OBS["Vector / Prometheus / Grafana\n(Observability)"] ING["Traefik / Kong\n(Ingress)"] end subgraph Infra ["Infrastructure"] K3D["K3d\n(local dev)"] K3S["K3s\n(on-prem)"] GCP["GCP / GKE\n(cloud)"] end subgraph Identity AUTH["Samba AD / Google Workspace OIDC"] end U --> ING ING --> A ING --> SB ING --> ARGO ING --> GITEA ING --> GITLAB A --> PG A --> OBJ A --> SEC A --> AUTH W --> PG W --> OBJ W --> SEC SB --> PG SB --> OBJ SB --> AUTH ARGO --> A ARGO --> W ARGO --> SB ARGO --> PG ARGO --> OBJ ARGO --> SEC GITEA --> ARGO GITLAB --> ARGO K3D & K3S & GCP --> |hosts| knoe-system OBS -.-> PG OBS -.-> A OBS -.-> W ``` --- ## Deployment model Knoe favors simple, understandable deployment over excessive platform ceremony. Typical characteristics: - Kubernetes-native deployment across K3d (local), K3s (on-prem), and GCP (cloud) - lightweight K3s/K3d-friendly footprint - HA where it matters - shard-oriented layout instead of one giant control surface - GitOps-driven operations via ArgoCD, backed by Gitea or GitLab - support for small clusters, including Pi-based or edge environments --- ## Use cases Knoe is a good fit for teams that need: - a PostgreSQL-centered application stack inside a disconnected or sovereign environment - a self-hosted substrate for Supabase-style internal platforms - ingestion from offline or intermittently connected sources - reproducible deployment on small Kubernetes clusters (K3s/K3d) or GCP - strong control over secrets, storage, and identity boundaries - a full GitOps workflow with ArgoCD, Gitea, and GitLab --- ## Project principles - **Air-gap first** - **Postgres first** - **Simple over ornate** - **Deterministic ingestion** - **Kubernetes-native** - **Shard-friendly** - **Operationally boring where possible** --- ## Repository scope This repository contains the infrastructure and platform materials used to stand up Knoe components, including Kubernetes deployment, database operations (`knoe-db` in `knoe-system`), storage, auth, observability, and supporting services. As the project evolves, this README should stay focused on: - what Knoe is - why it exists - the major building blocks - the deployment model - the operational philosophy Detailed setup docs, cluster procedures, and host-specific notes should live in dedicated documents under `docs/`, `ops/`, `k8s/`, or role-specific subdirectories rather than expanding this file indefinitely. --- ## prole.org deployment This repository is the **prole.org fork** of the upstream knoe-db platform. It runs a production k3s cluster on a three-node ARM64 LAN (myrddin / gandalf / merlin) with Samba AD providing Kerberos identity for single sign-on across all platform services. ### Live services | Service | URL | Auth | |---|---|---| | Supabase Studio | `https://db.prole.org` | SPNEGO → OIDC → Studio | | Gitea | `https://git.prole.org` | SPNEGO (auto) or Gitea login | | PostgreSQL | `pg.prole.org:5432` | mTLS + CNPG cert | | SSH (Gitea) | `git.prole.org:3022` | SSH key | ### Cluster nodes | Host | Role | IP | |---|---|---| | `myrddin.prole.org` | k3s server, Samba AD DC, container registry | 10.0.0.3 | | `gandalf.prole.org` | k3s agent | 10.0.0.5 | | `merlin.prole.org` | k3s agent | 10.0.0.6 | Kubectl context: `prole-service-cluster` (kubeconfig at `knoe-k3s.kubeconfig`). ### Key namespaces | Namespace | Contents | |---|---| | `knoe-system` | knoe-auth, Kong API gateway, KDC, Redis, Traefik | | `gitea` | Gitea, gitea-spnego-proxy | | `knoe-db` | CloudNativePG cluster | | `supabase` | Supabase Studio, Kong (supabase), oauth2-proxy | --- ## Identity & SSO All platform services use a unified Kerberos SSO stack. The flow from browser to service is: ``` Browser (Chrome, Safari, curl --negotiate) │ kinit-obtained TGT from PROLE.ORG KDC (myrddin.prole.org) ▼ Traefik (TLS termination, *.prole.org wildcard cert via ACME) │ ▼ Kong API gateway (knoe-system/knoe-svc-kong) │ routes by Host header ├── db.prole.org ──► oauth2-proxy ──► knoe-auth (OIDC) ──► Supabase Studio └── git.prole.org ──► gitea-spnego-proxy (port 4000) ──► Gitea (port 3000) ``` ### Kerberos realm - **Realm:** `PROLE.ORG` - **KDC / AD DC:** `myrddin.prole.org` (Samba 4, `10.0.0.3`) - **Cross-realm trust:** `PROLE.ORG ↔ KNOE.LOCAL` (in-cluster MIT KDC for knoe-auth internal use) - **Encryption:** AES256 + AES128 only (`msDS-SupportedEncryptionTypes=24`); RC4 disabled on all service accounts ### knoe-auth (OIDC provider — `api.prole.org/auth`) knoe-auth is a Spring Boot 3 / JDK 21 OIDC authorization server that validates Kerberos SPNEGO tokens and issues OIDC tokens for downstream services. **Key implementation details:** | Detail | Value | |---|---| | Deployment | `knoe-system/knoe-auth` | | SPNEGO endpoint | `https://api.prole.org/auth/spnego` | | Keytab secret | `knoe-system/knoe-auth-keytab` (`HTTP/api.prole.org@PROLE.ORG`, AES-only) | | JDK Subject API | `Subject.callAs()` — **not** `Subject.doAs()` (removed in JDK 21) | | RC4 | Hard-removed in JDK 21 JGSS — keytab and AD account must be AES-only | | krb5.conf | Mounted via ConfigMap; `permitted_enctypes = aes256 aes128` (cannot re-enable RC4 here) | Keytab provisioning (`etc/init_knoe_users.sh`): ```bash # On myrddin — set AES-only, reset password, export and rekey keytab sudo ldbmodify -H /var/lib/samba/private/sam.ldb <AuthServerAllowlist *.prole.org AuthNegotiateDelegateAllowlist *.prole.org ``` Deploy to a Mac workstation: ```bash make workstation # or: ansible-playbook infrastructure/playbooks/workstation_kerberos.yml --ask-become-pass ``` ### git.prole.org SPNEGO (Gitea) ``` Browser/curl → Kong → gitea-spnego-proxy (:4000) → Gitea (:3000) │ ├─ No Authorization header → 401 + WWW-Authenticate: Negotiate ├─ Authorization: Negotiate → SPNEGO validate → X-WEBAUTH-USER → Gitea auto-login └─ Authorization: Basic/token → pass-through → Gitea auth ``` **Component:** `gitea/spnego-proxy/` — Go binary using `gokrb5/v8`, built for `linux/arm64`. | Detail | Value | |---|---| | Image | `myrddin.prole.org:5000/gitea-spnego-proxy:latest` | | Keytab secret | `gitea/gitea-krb5-keytab` (`HTTP/git.prole.org@PROLE.ORG`, AES-only, KVNO 4) | | AD account | `CN=gitea-http,CN=Users,DC=prole,DC=org` (`msDS-SupportedEncryptionTypes=24`) | | Rebuild | Build on myrddin (native arm64); `docker build` then `docker push localhost:5000/...` | Keytab rotation: ```bash ansible-playbook infrastructure/playbooks/gitea_spnego_keytab.yml -e force_keytab_reset=true ``` Manual rebuild (if source changed): ```bash # Transfer source and build on myrddin (all nodes are arm64) tar -czf /tmp/src.tar.gz gitea/spnego-proxy/ && scp /tmp/src.tar.gz myrddin:/tmp/ ssh myrddin "mkdir -p /tmp/spnego-build && tar -xzf /tmp/src.tar.gz -C /tmp/spnego-build && \ sudo docker build -t localhost:5000/gitea-spnego-proxy:latest /tmp/spnego-build/gitea/spnego-proxy/ && \ sudo docker push localhost:5000/gitea-spnego-proxy:latest" kubectl --context=prole-service-cluster -n gitea rollout restart deployment/gitea-spnego-proxy ``` Smoke test: ```bash curl -s -o /dev/null -w "%{http_code}\n" https://git.prole.org/ # → 401 (challenge) curl -s -o /dev/null -w "%{http_code}\n" --negotiate -u : https://git.prole.org/ # → 200 (authed) ``` --- ## Status **As of 2026-05-28** the following work has shipped on the prole.org fork: **Identity / SSO (May 2026)** - `feat(gitea)` — SPNEGO Kerberos SSO for `git.prole.org`; proxy issues `WWW-Authenticate: Negotiate` challenge; `gitea_spnego_keytab.yml` Ansible playbook for full provisioning lifecycle (`5077e13`) - `fix(ansible)` — workstation install script + `make workstation` target for Chrome SPNEGO policy on personal Macs (`b245593`) - `fix(spnego)` — `Subject.callAs()`, AES-only keytab, and `krb5.conf` sync for JDK 21 knoe-auth (`c1d2a91`) - `feat(oidc)` — knoe-auth routing through Kong; Flyway schema baseline for clean OIDC DB (`9523045`) - `feat(prole)` — knoe-auth bootstrap on k3s; tenant onboarding; cluster stabilisation (`cf33342`) **Infrastructure (earlier)** - `fix(cfg)` — `_validate_cfg_values` prevents MagicMock reprs from leaking into `conf/*.cfg` - `feat(env)` — `gke_` kubecontext prefix auto-detected as `prod`; `status.py` context helpers - `refactor(mock_val)` — `prole_*` shell lib and cfg tooling renamed to `knoe_*` namespace - `feat(mock_val)` — init scripts rewritten; 10+ new service init scripts added - `feat(scripts)` — upstream knoe-db sync script + procedure doc - `feat(pg-knoe-auth)` — upstream PostgreSQL JWT auth extension imported; compiled in `knoe-db` image Working tree is clean. Browser SPNEGO SSO is live on `db.prole.org` and `git.prole.org`. --- ## Upstream fork — knoe-db This repository is an independent fork of the upstream knoe-db project: ``` git@git.knoe.dev:knoe-dev/knoe-db.git ``` Upstream changes are pulled into a dated review branch (`upstream/knoe-db/YYYYMMDD`) using the sync script, inspected, and selectively merged into `main`. ```bash # Preview what the script will do ./scripts/sync_upstream_knoe_db.sh --dry-run # Run the sync (requires a clean working tree) ./scripts/sync_upstream_knoe_db.sh ``` See [`docs/upstream-knoe-db-sync.md`](docs/upstream-knoe-db-sync.md) for the full review-and-merge procedure. ### Fork-specific files Files added or substantially modified in the prole.org fork (not present or not relevant upstream): | Path | Purpose | |---|---| | `infrastructure/` | Ansible roles, playbooks, and inventory for the prole.org cluster | | `infrastructure/playbooks/gitea_spnego_keytab.yml` | Gitea SPNEGO keytab provisioning | | `infrastructure/playbooks/workstation_kerberos.yml` | Chrome SPNEGO policy + krb5.conf for macOS workstations | | `infrastructure/playbooks/kerberos_trust_setup.yml` | PROLE.ORG ↔ KNOE.LOCAL cross-realm trust | | `infrastructure/bin/install_workstation.sh` | Wrapper for workstation Ansible (adds `--ask-become-pass`) | | `gitea/spnego-proxy/` | Go SPNEGO reverse proxy for `git.prole.org` | | `deploy/opentofu/k3s/manifests/knoe/gitea-spnego-proxy.yaml` | k8s deployment for the SPNEGO proxy | | `conf/k3s.cfg` | prole.org k3s cluster configuration | --- ## License Add the project license here.