No description
Find a file
Daniel Noulet f40485842d
Some checks failed
Build, test, and publish / Build and test (push) Has been cancelled
Build, test, and publish / Build and push Docker image (push) Has been cancelled
CI / test (push) Has been cancelled
fixing style
2026-10-07 15:10:10 +02:00
.github/workflows deploy configuration 2026-10-03 04:41:31 +02:00
cmd/server fixing style 2026-10-07 15:10:10 +02:00
deploy fixing style 2026-10-07 15:10:10 +02:00
internal fixing style 2026-10-07 15:10:10 +02:00
migrations some extra make target 2026-10-01 16:11:41 +02:00
ui fixing style 2026-10-07 15:10:10 +02:00
.dockerignore build and ready for demo 2026-09-19 17:33:44 +02:00
.env.example deploy configuration 2026-10-03 04:41:31 +02:00
.gitignore change layout of login 2026-09-20 15:41:01 +02:00
cert-manager.yaml fixing style 2026-10-07 15:10:10 +02:00
Dockerfile deploy configuration 2026-10-03 04:41:31 +02:00
go.mod deploy configuration 2026-10-03 04:41:31 +02:00
go.sum deploy configuration 2026-10-03 04:41:31 +02:00
Makefile fixing style 2026-10-07 15:10:10 +02:00
README.md deploy configuration 2026-10-03 04:41:31 +02:00
server git actions 2026-09-19 17:48:04 +02:00

Go Tell

Go Tell is a small content CMS: one Go binary that serves the API and embeds the React frontend, backed by a JSON file. It is served from a single host, https://tell.dev, and it uses Go Loose to decide who is allowed to edit.

Backend

The backend targets Go 1.27.1 and uses a layered design (internal/web, internal/service, internal/security, internal/golooseauth). Content is seeded in memory and can be mirrored to a JSON file configured through CMS_CONTENT_FILE.

go run ./cmd/server

Configuration is read from environment variables (see .env.example). The API exposes GET /health, GET /api/auth/session, GET /api/protected, GET /api/content, GET /api/content/categories, GET /api/content/badges, and GET|PUT /api/content/:slug.

Variable Default Purpose
CMS_HTTP_ADDR :8080 Listen address
CMS_PROFILE demo demo enables the in-memory seed and the sudo terminal, anything else requires an authenticated editor
CMS_CONTENT_FILE empty Path of the JSON file that stores edits; empty keeps everything in memory
CMS_POSTGRES_URL empty Reserved for the PostgreSQL persistence adapter
CMS_ALLOWED_ORIGINS http://localhost:5173 Access-Control-Allow-Origin value
CMS_SSO_HEADER X-SSO-Token Identity header set by the proxy; empty disables proxy identity
CMS_SSO_TOKEN empty Shared secret the proxy value must match
CMS_SSO_COOKIE empty Cookie carrying the same value when the proxy sets no header
GO_LOOSE_TENANT tell Go Loose tenant that owns the login
GO_LOOSE_AUTH_DOMAIN auth.dev Identity domain shared by all Go Loose tenants
GO_LOOSE_APP_DOMAIN tell.dev Public host of the CMS, which also determines the redirect URI
GO_LOOSE_CLIENT_ID empty Client id of the Go Loose application
GO_LOOSE_CLIENT_SECRET empty Client secret of the Go Loose application
GO_LOOSE_CA_FILE empty Extra CA bundle for an internally signed identity domain

Authentication

Go Tell accepts an editor from three sources, in this order: the demo sudo token, the identity-aware proxy header, and the Go Loose browser session. Reading content is always anonymous; PUT /api/content/:slug answers 401 without one of them.

Go Loose

Set GO_LOOSE_CLIENT_ID and GO_LOOSE_CLIENT_SECRET and the CMS grows a browser login:

  1. GET /api/auth/login redirects to https://<GO_LOOSE_TENANT>.<GO_LOOSE_AUTH_DOMAIN>/connect/authorize.
  2. Go Loose authenticates the user and redirects to https://<GO_LOOSE_APP_DOMAIN>/api/auth/callback, which exchanges the authorization code and stores the session in an HTTP-only cookie.
  3. GET /api/auth/session then reports authenticated: true, the editor name, and the login and logout endpoints the frontend follows.
  4. GET|POST /api/auth/logout revokes the session at the identity provider and clears the cookie.

That redirect URI has to be registered on the Go Loose application, next to the client id and secret. Without credentials the login routes answer 404 with the variables to set, and the CMS falls back to the proxy identity.

The local platform application lives in the Go Loose tenant tell, so the login origin is https://tell.auth.dev and the interview accounts are interview@tell.auth.dev and reviewer@tell.auth.dev. The one-time client secret is recorded in the platform's gitignored secrets/credentials.md and in the gitignored .env of this repository.

Proxy identity

Without Go Loose, an identity-aware proxy (oauth2-proxy, Authelia, Cloudflare Access, ...) can authenticate the owner and forward a header on every accepted request: the header present means the request can edit, the header absent means read-only. Because the backend trusts that header, it must only be reachable through the proxy, and the proxy must strip any client-supplied copy of it.

Content

Content supports one reusable category per document plus any number of badges. The anonymous GET /api/content endpoint accepts category=engineering and repeated badge=go&badge=docker filters; selected badges are combined with AND semantics. GET /api/content/categories returns the category dropdown values. The frontend applies these filters immediately when the category changes or when a badge is entered.

make backend   # run the Go server
make frontend  # run the React app
make run       # build the frontend, then run the server
make check     # gofmt, vet, and tests
make docker    # build the all-in-one image

Kubernetes deployment

The Helm chart at deploy/helm/go-tell deploys the single image to the k3s cluster behind ingress-nginx and cert-manager, both of which have to be present first:

make cluster-prepare          # install ingress-nginx and cert-manager
make helm-lint                # lint and render both profiles
make helm-deploy-production   # namespace go-tell-production
make helm-deploy-development  # namespace go-tell-development

Go Loose credentials are never written into a values file. The production target reads GO_LOOSE_CLIENT_ID, GO_LOOSE_CLIENT_SECRET, and GO_LOOSE_CA_SECRET from the environment, after sourcing the gitignored .env:

make helm-deploy-production

They end up in the release secret and in the --set-string flags the target builds. Override any of them on the command line when deploying from elsewhere:

make helm-deploy-production \
  --set-string goLoose.clientID="$GO_LOOSE_CLIENT_ID" \
  --set-string goLoose.clientSecret="$GO_LOOSE_CLIENT_SECRET" \
  --set-string goLoose.ca.existingSecret=local-ca

goLoose.ca.existingSecret names the Secret holding the platform CA bundle, so the pod trusts tell.auth.dev. It has to exist in the namespace before the release starts:

kubectl -n go-tell-production create secret generic local-ca \
  --from-file=ca.crt=~/sources/go/infrastructure/certs/local-ca.crt

go-tell-production is the profile that matters: one host, TLS from cert-manager, and a persistent volume for the content file. go-tell-development runs the demo profile on NodePort 30010 with ephemeral content and no Go Loose login, which is what make frontend talks to.

The earlier dedicated-host deployment (cms.urpi.be through host Nginx on a Rancher cluster) is retired: deploy/nginx and deploy/rancher are gone, and every Makefile target talks to ~/.kube/config.

Continuous delivery

build-test-push.yml publishes batty1039.startdedicated.net:5000/go-tell on every push to main. deploy-rancher.yml deploys a successful build to production and can also be dispatched by hand for either profile. Create the GitHub environments development and production with the secrets RANCHER_KUBE_CONFIG_BASE64, REGISTRY_USERNAME, REGISTRY_PASSWORD, GO_LOOSE_CLIENT_ID, GO_LOOSE_CLIENT_SECRET, and optionally CMS_SSO_TOKEN and LOCAL_CA_CERT (the internal CA bundle mounted through goLoose.ca.existingSecret). The variables RANCHER_NAMESPACE, CMS_ALLOWED_ORIGINS, CMS_SSO_HEADER, and CMS_SSO_COOKIE override the chart defaults.