feat(flags): feature-flag control on the project page #5
Reference in New Issue
Block a user
Delete Branch "feature-flags"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a Feature flags card to the project page, next to the
.enveditor. Featuresthat ship behind
?flag=1are now declared once in the project'spublic/feature-flags.json, and this card flips each one's for everyone default,mints the token link that unlocks the admin panel on the deployed site, and pushes the
JSON to that site with no rebuild.
Half of the feature lives outside this repo: the widget that resolves the flags and
renders the panel is
~/projects/feature-flags(served at
flags.dev.gabvdl.xyz/script.js), and the Traefik route that lets the panelreach this API is in the homelab repo.
Key changes
backend/projectflags.py(new) — reads/writes a project'spublic/feature-flags.json(atomic replace, unknown per-flag fields preserved, a malformed file reported rather
than silently emptied), and mints/verifies the HMAC-SHA256 admin token. The signing
key lives at
/data/flags-secret, created on first use; deleting it revokes everytoken ever issued.
backend/main.py—GET/PUT /api/project-flags,POST /api/project-flags/admin-link,POST /api/project-flags/publish(all behind Authelia), plusPOST /api/flags/publishfor the panel on the live site: token-authed, CORS-scoped tohttps://*.gabvdl.xyz.sidecar/sidecar.py—POST /publish-flags: stages the one JSON file and runszipgo deploy --no-deleteonto the hosts the project'spackage.jsondeclares.frontend/src/business/projects/components/FlagsEditor.tsx(new) — the card: a row perflag with an everyone switch, an Admin link button, and Publish now.
backend/schemas.py+ regeneratedfrontend/openapi.json/src/generated/.CLAUDE.md— a Feature flags section covering the endpoints and the threeload-bearing constraints below.
Key decisions
card refuses an unknown key rather than creating one. A flag with no code behind it is
dead weight, and the alternative invites flags that exist only in a UI.
lab.gabvdl.xyz, so a credentialed call would actually carry the session cookie — butonly from the LAN/tailnet, where the lab hosts resolve. A minted token works from
anywhere, which is what "show a colleague a feature from my phone" needs.
the panel from visitors and guards nothing secret — the flag list is public JSON the
page already fetched. The write that affects other people is verified server-side.
text/plainPOST, on purpose. It keeps the panel's cross-origin write a CORSsimple request. With
application/jsonthe browser sends a preflightOPTIONS,which carries no cookie, which forward-auth answers with a login redirect — the request
would never reach the token check. Same reason
/api/flagsneeds its own Traefikrouter.
the project itself declares), not a generic exec endpoint. The container has neither
zipgo nor the deploy key, and "run this command on the host" is not something the
bearer token should buy.
sidecar that is down must not read as "the toggle failed", so the response reports the
publish error separately.
Changelog
?flag=1feature on foreveryone, and publish it live without a rebuild.
Test notes
python3 -m py_compileon the changed backend/sidecar modules;npx tsc --noEmitclean.
npm test— 73 passed, 1 failed; the failure (BottomBar.test.tsx) reproduces onmainwith these changes stashed, so it is pre-existing and unrelated.npm run gen:apire-run; generated treecommitted.
dist/+ the widget, served locally):visiting
?ff-admin=…stored the token and showed the panel; toggling On in thepanel made the gated nav link appear live; a plain visitor at
?archives=1gotpanel=absent,archives=true, and an address bar with the param stripped.Screenshots below.
Screenshots
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.