No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Sean OMeara bd79a32d2c
fix(outline): stop the CA bundle being read as a server certificate
Outline will not boot:

    Environment configuration is invalid, please check the following:
    - SSL_CERT cannot be used without SSL_KEY.

Nothing here sets SSL_CERT. The cluster injects
SSL_CERT_FILE=/etc/ssl/cluster-trust/ca-bundle.crt into every pod, which
to OpenSSL means "the trust store is here" -- which certificates to
BELIEVE. Outline reads its environment through a Docker-secrets proxy
(server/utils/environment.ts): reading NAME when it is `undefined` returns
the contents of the file named by NAME_FILE. So Outline loaded the cluster
CA bundle and concluded the operator had supplied a TLS SERVER certificate
to terminate with. There is no SSL_KEY to go with it, and
@CannotUseWithout("SSL_KEY") ends the process before it serves.

Two conventions, one name. The proxy falls back only when the value is
strictly `undefined`, so declaring SSL_CERT at all -- even empty -- stops
the fallback, and Outline's own toOptionalString("") turns it back into
`undefined` for validation. Node still trusts the bundle through
NODE_EXTRA_CA_CERTS, the variable Node actually reads; SSL_CERT_FILE was
never doing that job here.

Any other application in this cluster that implements the `_FILE`
convention meets the same collision, so the reason is written down in both
the manifest and the README rather than left as an empty value someone
will tidy away.

Claude-Session: https://claude.ai/code/session_01JGe4pyhQ36AhgDfPJry4d3
2026-09-14 15:41:57 +02:00
apps feat(outline): the wiki workload 2026-09-14 14:26:39 +02:00
manifests/outline fix(outline): stop the CA bundle being read as a server certificate 2026-09-14 15:41:57 +02:00
README.md fix(outline): stop the CA bundle being read as a server certificate 2026-09-14 15:41:57 +02:00

outline

Outline, the team wiki at https://outline.sean.farm.

This repository holds the workload ONLY. Everything Outline depends on is owned elsewhere, on purpose: a prune here can never reach the data.

piece owner
namespace, ArgoCD AppProject, root Application sean/seanfarm kubernetes/flux/infrastructure/outline-argocd/
Hydra OIDC client, S3 credential delivery same
Valkey (the BullMQ queues, on a ZFS volume) sean/seanfarm kubernetes/flux/infrastructure/valkey-outline/
Postgres database + rotating owner role sean/seanfarm kubernetes/flux/infrastructure/tenantdatabase/outline.yaml
RustFS key sean/seanfarm kubernetes/flux/infrastructure/rustfs-identities/outline.yaml
RustFS bucket + policy sean/binjovi-plans object-store.json
TLS certificate, gateway listener, portal card sean/seanfarm kubernetes/flux/infrastructure/portal/

Layout

apps/outline.yaml            the child ArgoCD Application
manifests/outline/           the workload it syncs

The app-of-apps root in sean/seanfarm syncs apps/; that Application syncs manifests/outline.

Things that are not obvious

The DNS record comes from the HTTPRoute. external-dns runs with --source=gateway-httproute, so outline.sean.farm resolves only once manifests/outline is on main. The gateway listener and its certificate exist already and are not enough on their own.

SECRET_KEY must be exactly 64 hexadecimal characters (@IsHexadecimal() and @Length(64, 64) in server/env.ts). External Secrets cannot emit hex, so the generator is asked for 64 characters of which 64 are digits -- digits are a subset of hex. Allowing letters would produce g-z and Outline would refuse to boot. It is frozen (refreshInterval: "0") because it encrypts stored data: changing it locks every user out.

DATABASE_URL is assembled from a rotating credential, and the password is urlquery-encoded. OpenBao rotates it hourly under a policy this repository does not control; one @ or / in a generated value would re-point the connection at another host.

The queues need maxmemory-policy noeviction. Outline drives Valkey through bull, where an evicted key is a silently dropped job, not an error. That setting lives with the Valkey in sean/seanfarm.

SSL_CERT is declared empty on purpose. The cluster injects SSL_CERT_FILE=/etc/ssl/cluster-trust/ca-bundle.crt into every pod, meaning "the trust store is here". Outline reads its environment through a Docker-secrets proxy where NAME_FILE supplies NAME, so it read that bundle as a TLS server certificate, found no SSL_KEY, and exited before serving. The proxy falls back only on a strictly undefined value, so the empty declaration stops it. Node still trusts the bundle through NODE_EXTRA_CA_CERTS. Any other app here that implements the _FILE convention will hit the same collision.

This repository has a ** tag protection although it publishes no tags. Forgejo.Authority.check/2 reads trunk, then main, then the tag protections, and requires all three of every provider-guarded project: the check proves that nobody but binjovi-bot can move a ref here, which is a different claim from "this project ships tags". Onboarding left the tag rule out on the second reading, and binjovictl authority outline answered blocked · invalid_tag_protection_response while every release died 503 (2026-09-14). The declaration is sean/binjovi's ForgejoServiceBindings; the scoped legacy-CR absence read is sean/seanfarm's service-legacy-access.yaml.

Changes

Through the release train, like every other project:

binjovictl status outline
binjovictl builds outline
binjovictl release outline --build BUILD_ID --wait

Work pull requests target trunk. main is what ArgoCD syncs.