Design — why this repo works the way it does
The problem this solves
The OpenWoo config is an OpenRegister configuration export — an
OpenAPI-enveloped JSON document (openapi / info / components /
x-openregister) whose components hold the config buckets: registers,
schemas, mappings, sources, rules, synchronizations, endpoints,
jobs, workflows, objects. It is ~7,500 lines pretty-printed, devs
hand-edit and re-export it, and mistakes in it silently break app
functionality.
When an export is taken from an instance that has imported into PostgreSQL, runtime state (sync cursors, content hashes, last-synced timestamps, created/updated metadata) leaks back into the export and pollutes the config. That noise causes broken diffs and unpredictable imports. This repo version-controls the config and gates it in CI before it can ever reach a tenant.
Zero third-party dependencies is deliberate: full auditability, no
supply-chain surface, reproducible anywhere python3 exists.
Two tracks: source validation and target configuration
| Track | Question it answers | Tooling | Needs a tenant? |
|---|---|---|---|
| Source — config validation | Is the config artefact correct and portable? | scripts/oac.py (lint / sanitize) + tests/ |
no — runs on the file |
| Target — configuration, validation & repair | Is a running tenant in the desired state, and bring it there | scripts/provision.py |
yes — points at a tenant URL |
The source track is the CI gate: pollution, dangling refs and bad
authorization keys are caught on the JSON before it can reach a tenant.
The target track drives a real tenant over the API and asserts each
step — some steps validate (verify-import, sync-check), others
configure or repair (settings, oc-settings, import,
authorization, catalog, delete-menu, credentials).
Provisioning is operator-driven (public URL by default)
The target track is operator-driven: after Nextcloud-base deploys a
tenant, an operator runs provision.py (or the GUI) to converge the WOO
config and set the source connection. By default it drives the tenant's
public URL (a trusted domain), so it needs no standing in-cluster
wiring. We deliberately avoided standing Argo apps for this — the operator
runs it on demand.
In-cluster mode (opt-in), for when the public record is unreliable.
The public host is published by external-dns with a very short TTL and can
briefly flap while a tenant's ingress is (re)created; because nothing caches
a short-TTL record, a single run does one DNS lookup per step and any lookup
landing in an "absent" moment aborts the whole run with [Errno -5] No
address associated with hostname. In-cluster mode sidesteps this: it
connects to the tenant's cluster-local Service
(http://nextcloud.<org>-<env>.svc.cluster.local:8080, resolved over
cluster.local which never flaps) while presenting the public host via
--host-header — so Nextcloud still sees a trusted domain (the earlier
"internal Host isn't trusted" objection is exactly what --host-header
resolves). It also avoids a hairpin from the in-cluster GUI to its own
cluster's public ingress IP. Enable it with provision.py --host-header
or the GUI's "Via in-cluster service" checkbox (default on for
*.commonground.nu); it falls back to the public base for any other host.
Hosted control-plane (webgui/)
The operator flow is also available as a small hosted web GUI — a
Flask app (webgui/server.py) that runs provision.py all from a form
and streams the log back. One hosted instance can converge any tenant —
over the tenant's public URL, or (default for *.commonground.nu) via the
cluster-local Service with --host-header, see the in-cluster note above.
- Auth: no login of its own — it sits behind oauth2-proxy →
Keycloak (realm
commonground), which brokers Google. The app fails closed (REQUIRE_AUTH, default on): every route except/healthzreturns403without the proxy's identity header. Seewebgui/auth/README.md. - Creds model A: the operator types the tenant password + source key per run; nothing is stored. Secrets go to the subprocess via env, never argv, never logs.
- Deploy:
webgui/deploy/(kustomize) — app on127.0.0.1+ oauth2-proxy sidecar as the sole listener, NetworkPolicy, nginx Ingress + TLS, atplatform.commonground.nu. Build the image withmake image. Seewebgui/deploy/README.md. Local dev:REQUIRE_AUTH=false python3 webgui/server.py.
The host is named generically (platform.) because the control-plane is
intended to grow beyond provisioning (e.g. driving deployments) over time.
How Nextcloud-base consumes this
Nextcloud-base (the GitOps platform) does not own this config. It
consumes a tagged, validated version of config/woo.configuration.json —
the same way it pins app versions. Config errors are caught in this
repo's CI, before they can reach a tenant.