Skip to content

refactor: drop redundant env-naming prefixes (target, app, and shared APPS_) - #130

Merged
ineedjet merged 12 commits into
mainfrom
normalize-vault-env-naming
Aug 23, 2026
Merged

refactor: drop redundant env-naming prefixes (target, app, and shared APPS_)#130
ineedjet merged 12 commits into
mainfrom
normalize-vault-env-naming

Conversation

@ineedjet

@ineedjet ineedjet commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Four rounds of naming/structure cleanup, all converging on the same principle: nothing here should exist "just in case."

hawkeye vault GitHub Secret/Variable names:

  • Dropped the RUBYKATZEN_COM_ target prefix - no second target shares this repo's Secrets/Variables namespace, and none is planned.
  • Dropped the TRAEFIK_ app prefix from ADMIN_MAIL/CLOUDFLARE_DNS_API_TOKEN/etc. - redundant given vaults are already one-per-app.
  • Kept a TRAEFIK_ prefix specifically on the two port GitHub Variables (TRAEFIK_HTTP_PORT/TRAEFIK_HTTPS_PORT) - a bare HTTP_PORT doesn't self-document which app it belongs to, since traefik is the one app binding host ports directly.
  • Fixed two names that had drifted from their app-side counterpart: CLOUDFLARE_TOKEN -> CLOUDFLARE_DNS_API_TOKEN, CERT_RESOLVER -> CERTIFICATE_RESOLVER.

Catalog-wide APPS_ prefix:

  • APPS_{VAR} was the convention for "shared across apps" - dropped it everywhere: common.yml, every app's docker-compose.yml, the shared database templates, and the two rendered *.tpl config files.
  • One deliberate exception: PUID/PGID instead of bare UID/GID in a doc example - those two are actual shell-reserved variable names.

Unused per-app override fallback:

  • ${APPNAME_VAR:-${VAR}} let an app override a shared value while falling back to it - a real capability, but nothing in the catalog or in any vault ever used it (verified: zero matches). Simplified the four apps that carried it (gatus, codecov, semaphore, twofauth) to reference the shared variable directly.

Vault-sourced vs. internal environment split:

  • An app's x-environment mixed values that must come from its vault with fixed/internal wiring, with no way to tell which was which at a glance. Split into x-vault-env/x-internal-env anchors, merged at point of use via <<: [*vault-env, *internal-env]. Trialed on rybbit first, confirmed, then rolled out to every app with a real mix (beszel-agent, codecov, gatus, glitchtip, semaphore, twofauth); traefik/homepage use a single x-vault-env since they have no internal-only vars. Documented as the required convention in AGENTS.md.

Updated AGENTS.md/README.md's naming-convention writeup, the encrypt-env action's example, and every test fixture that illustrated the old conventions throughout.

Not included here

The actual GitHub repo Secrets/Variables have already been updated to match live (done separately, outside git) - confirmed synced.

Test plan

  • Full deploy/ unit test suite (48 tests) passes
  • pre-commit run --all-files passes (yamllint, pymarkdown, ruff, actionlint)
  • docker compose config validated for every changed app against an isolated empty .env - correct variable counts, correct merge, no unexpected keys in any case

…ble names

Two prefixes were doing no real work here: the target-name prefix
(RUBYKATZEN_COM_) only matters once a second target shares this repo's
GitHub Secrets/Variables namespace, which isn't planned; the app-name
prefix (TRAEFIK_) is redundant given vaults are already one-per-app.
Drop both, and fix two names that had drifted from their app-side
counterpart (CLOUDFLARE_TOKEN -> CLOUDFLARE_DNS_API_TOKEN matching
APPS_CLOUDFLARE_DNS_API_TOKEN, CERT_RESOLVER -> CERTIFICATE_RESOLVER
matching APPS_CERTIFICATE_RESOLVER). GitHub Secret/Variable name is
now identical to the app-side var name it feeds, minus the APPS_
prefix where present.

The actual GitHub Secrets/Variables still need updating to match -
tracked separately since that's a live-state change outside git.
APPS_{VAR} was flightdeck's convention for "server-wide variable
shared across apps" (vs. {APPNAME}_{VAR} for a per-app override), but
the prefix wasn't distinguishing anything a bare name doesn't already
convey by not having an app prefix - it's dead weight the same way
the vault-level RUBYKATZEN_COM_/TRAEFIK_ prefixes were. Drop it across
the whole catalog: common.yml, every app's docker-compose.yml, the
shared database templates (postgres/mysql/mongodb/etc.), and the two
rendered *.tpl config files (traefik, codecov) - all now reference
plain names (DOMAIN, DATABASE_PASSWORD, KEY_HEX_32, ...). Per-app
override vars follow along automatically (CODECOV_APPS_DOMAIN ->
CODECOV_DOMAIN, TWOFAUTH_APPS_DOMAIN -> TWOFAUTH_DOMAIN), since they
were always just {APPNAME}_ glued onto the shared name.

One deliberate exception: AGENTS.md's UID/GID example now uses
PUID/PGID (the linuxserver.io convention), not bare UID/GID - unlike
every other bare name here, those two are actual shell-reserved
variable names, so dropping their prefix would trade a redundant
prefix for a real collision risk instead of removing dead weight.

Updated AGENTS.md/README.md's naming-convention writeup, the
encrypt-env action's example, and every test fixture that illustrated
the old convention. Verified: full deploy/ test suite (48 tests),
pre-commit, and `docker compose config` for traefik and rybbit.
@ineedjet ineedjet changed the title chore: drop redundant prefixes from hawkeye vault GitHub Secret/Variable names refactor: drop redundant env-naming prefixes (target, app, and shared APPS_) Aug 23, 2026
Unlike the other shared vars (ADMIN_MAIL, CLOUDFLARE_DNS_API_TOKEN,
DOMAIN, CERTIFICATE_RESOLVER, DATABASE_PASSWORD, KEY_HEX_32- each an
inherently target-wide, singular concept), HTTP_PORT/HTTPS_PORT are
specific to traefik being the one app that binds host ports directly.
A bare HTTP_PORT/HTTPS_PORT GitHub Variable name doesn't self-document
which app it belongs to the way the others do. App-side var names in
traefik's docker-compose.yml are unaffected - still plain HTTP_PORT/
HTTPS_PORT; only the GitHub Variable name (the vault mapping's right
side) gets TRAEFIK_ back.
${APPNAME_VAR:-${VAR}} let an app override a shared value while
falling back to it - a genuine capability, not just naming cruft, but
one nothing in the catalog or in flightdeck's own deploy state ever
actually used (grep across every vault: zero). Simplify all four
apps that carried it (gatus, codecov, semaphore, twofauth) to
reference the shared variable directly. The app-specific-only pattern
({APPNAME}_{VAR} with no fallback, e.g. BESZEL_AGENT_PUBLIC_KEY) is
real and stays - only the fallback/override half is gone.

Downstream consumers of the apps catalog lose the override option;
if one ever needs it, add an app-specific variable name instead of
reaching for a fallback nobody exercises today.
x-environment mixed vault-sourced inputs (DOMAIN, KEY_HEX_32,
DATABASE_PASSWORD, the optional DISABLE_*/MAPBOX_TOKEN) with fixed or
APP_NAME-derived internal wiring (postgres/clickhouse hostnames,
ports, per-app db/user names) in one flat list - no way to tell which
env vars this app actually needs from its vault without reading every
line. Split into x-vault-env/x-internal-env, merged at the point of
use via multi-anchor <<: [*a, *b] (verified docker compose supports
this). Verified the resolved config is byte-identical to before this
split, aside from the split x- blocks themselves.

Trying this on rybbit first before deciding whether to make it a
catalog-wide convention.
x-vault-env/x-internal-env names are self-explanatory.
Applies the same split trialed on rybbit to every other app that
mixes vault-sourced values with fixed/internal wiring: beszel-agent,
codecov, gatus, glitchtip, semaphore, twofauth. traefik and homepage
have only vault-sourced vars, so their single anchor is renamed to
x-vault-env with no internal-env counterpart, per the "only split
when there's actually a mix" rule.

Documents the convention in AGENTS.md: when to split vs. use a single
appropriately-named anchor, the classification rule (a var is
vault-env if its value references any vault-sourced variable, even
mixed with ${APP_NAME} or literal text), and updates the Field
Ordering Rules' x-fields order and "environment via anchor" principle
to match.

Verified every changed app's resolved services: config against an
isolated empty .env (no real .env files exist in any app directory
anymore - see prior cleanup) - correct var counts and no unexpected
keys in every case.
The compose file already overrides the container's healthcheck
explicitly (test: ["CMD", "/agent", "health"]) - Docker never
consults the image's built-in HEALTHCHECK when one is set at the
compose level, so this env var (presumably meant to feed that
built-in check) was never actually read. Only app with this var.
BESZEL_AGENT_DISK_1_DEVICE/_2_DEVICE are vault-configurable inputs
(same {APPNAME}_{VAR} pattern as any other app-specific vault var),
but they lived directly in devices:, invisible to the x-vault-env
convention meant to make an app's vault interface visible at a
glance. A YAML anchor isn't limited to holding a map - split it into
x-vault-devices: &vault-devices (a list) and reference it directly
with devices: *vault-devices.

Verified duplicate /dev/null:/dev/null device entries (the default
when neither var is set) don't break container startup - tested with
both `docker run --device` and `docker compose up` directly; compose
config even dedupes identical entries on its own.

Documented the general pattern in AGENTS.md: x-vault-{field} for any
non-environment compose field with vault-sourced values, not just
x-vault-env.
beszel-agent had x-internal-env sandwiched between x-vault-env and
x-vault-devices. Vault-sourced anchors should stay grouped together
regardless of which compose field they target, so the vault interface
reads as one contiguous block at the top of the file. Documented in
AGENTS.md's Field Ordering Rules.
Every compose/*.tpl file is already scoped to exactly one app, so a
BESZEL_AGENT_/CODECOV_/GLITCHTIP_-style prefix inside it disambiguates
nothing - it only ever mattered at the vault-to-GitHub-Secret mapping
layer, where a flat namespace across all of a target's apps genuinely
can collide (kept e.g. TRAEFIK_HTTP_PORT there for exactly that
reason). Renamed every app-name-prefixed variable reference to its
bare form: beszel-agent's PUBLIC_KEY/DISK_1_DEVICE/DISK_2_DEVICE,
codecov.yml.tpl's LICENSE/ADMIN_GITHUB_USERNAME/GITHUB_CLIENT_ID/
GITHUB_CLIENT_SECRET/GITHUB_WEBHOOK_SECRET/GITHUB_APP_ID/S3_BUCKET,
glitchtip's EMAIL_URL/DEFAULT_FROM_EMAIL.

Rewrote AGENTS.md's "App-specific vs. shared variables" and README's
matching paragraph: what used to be framed as a compose-file-level
{APPNAME}_{VAR} vs {VAR} naming rule is now correctly framed as a
vault-manifest-level judgment call (whether a GitHub Secret/Variable
name needs its app spelled out to be legible in a flat per-target
list) - the compose file itself never needs to know or care.

Verified: full deploy/ test suite (48 tests), pre-commit, and
`docker compose config` for all three changed apps against an
isolated empty .env.
Grouping keys by byte-length rather than role had a real consequence:
twofauth and semaphore both drew from KEY_HEX_16, meaning two apps
with completely unrelated encryption needs would silently share the
same secret value if deployed together.

Split by actual risk profile instead:
- Session-signing keys (rybbit's BETTER_AUTH_SECRET, glitchtip's
  Django SECRET_KEY, codecov's cookie_secret) are safe to share -
  compromising one just forces re-authentication everywhere. One
  shared name and vault mapping: SESSION_KEY.
- Encryption keys (twofauth's Laravel APP_KEY, semaphore's stored-
  credential encryption) must never be shared - compromising or
  rotating one can make already-stored ciphertext unrecoverable in
  a way that isn't reversible by re-login. Same semantic app-side
  name (ENCRYPTION_KEY) so the role is still obvious from the compose
  file, but each app's vault maps it to its own distinct, app-
  prefixed GitHub Secret (TWOFAUTH_ENCRYPTION_KEY,
  SEMAPHORE_ENCRYPTION_KEY) once either is actually deployed.

Updates vaults/hawkeye-rybbit.yml (KEY_HEX_32 -> SESSION_KEY) since
rybbit is the only one of these actually live. Documents the
by-purpose-not-format naming principle in AGENTS.md. The live GH
secret still needs the matching rename (KEY_HEX_32 -> SESSION_KEY) -
that's outside git, done separately.
@ineedjet
ineedjet merged commit 0c5b112 into main Aug 23, 2026
4 checks passed
@ineedjet
ineedjet deleted the normalize-vault-env-naming branch August 23, 2026 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant