Repository navigation
fix(bundles): no placeholder value from .env.example reaches an install (stacked on #504) - #505
Merged
kh0pper merged 10 commits intoOct 16, 2026
Merged
Conversation
kh0pper
force-pushed
the
fix/bundle-generated-secrets
branch
from
October 16, 2026 21:29
52a55ec to
f8308d5
Compare
kh0pper
force-pushed
the
fix/bundle-env-placeholders
branch
from
October 16, 2026 21:29
dfcff0e to
14289ad
Compare
kh0pper
force-pushed
the
fix/bundle-generated-secrets
branch
from
October 16, 2026 21:56
f8308d5 to
d467777
Compare
- servers/gateway/bundle-env-placeholders.js recognises example values (/path/to/, youruser, your-, *.example.com, change-me, replace-me, <...>, xxx, placeholder) and, by SHA-256, example secrets once published here. - The installer leaves example values out when it builds .env from .env.example: such a line becomes a comment naming only the key. - env_vars[].default_in_crow_home: a blank or example path becomes a folder inside this instance's CROW_HOME, created by the gateway (never by Docker as root), and the key no longer blocks install. jellyfin, navidrome, audiobookshelf and kavita start with an empty library there. - 'Finish setup' lists a required key whose value is an example, so existing installs are flagged without rewriting their .env. - tax never encrypts with an example key (decrypt still reads data saved with one); its .env.example no longer carries a key. - .env.example files no longer carry example domains, paths or secrets as live values (kept as commented examples). plex follows once the install-guard PR, which also bumps it, lands. Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
…y in Crow's folder Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
…ld returns readable; domains must be real
- tax refuses only the published example key (by hash), never a passphrase
for its shape. CROW_TAX_ENCRYPTION_KEY_PREVIOUS opens returns saved with
an old passphrase; saving re-encrypts them with the current one.
- gotosocial, writefreely and matrix-dendrite fail to start without a real
domain (${X:?}) instead of booting with a blank one.
- kavita and audiobookshelf keep their named-volume fallback (only the
example paths are left out); jellyfin/navidrome's CROW_HOME paths are never
copied to the shared gateway .env (propagate: false).
Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
… key; reinstall keeps the library path - env_vars[].previous_key: changing tax's encryption key in Configure keeps the old one in CROW_TAX_ENCRYPTION_KEY_PREVIOUS (when that slot is empty), so returns saved with it stay readable. The contract checks the name. - Uninstall keeps default_in_crow_home paths in the retained copy and a reinstall reuses them, so kept Jellyfin/Navidrome data never gets an empty library mounted over it. - GTS_ACCOUNT_DOMAIN is optional (compose falls back to GTS_HOST); the domain fields say they are fixed at first start. - The scrub also handles KEY = value lines; navidrome docs (en/es). Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
… from its own MCP entry - Configure reads the outgoing key where the tax server really runs with it (its mcp-addons entry, then the gateway env, then the bundle .env) and keeps it in CROW_TAX_ENCRYPTION_KEY_PREVIOUS. A second change while that slot still holds a different older key is refused (409), so no key in use is ever overwritten. - At start the tax server re-encrypts every return that only the previous key opens, so the slot can then be cleared. - tax's server.envKeys list both keys, so a typed key reaches the server. - A kept library path follows the latest install; log wording. Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
…S; re-encryption never overwrites a newer save The Configure-side copy of the outgoing key (and its 409) is removed: it could only be undone by hand-editing mcp-addons.json. A key change now means: set the new key and put the old one in CROW_TAX_ENCRYPTION_KEY_PREVIOUS (the field and the refusal message say so). Returns move to the new key when the tax server starts or a return is saved; the start-time move is a compare-and-swap, so a save that lands meanwhile wins. Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
…slot is empty Configure copies the outgoing CROW_TAX_ENCRYPTION_KEY (read where the tax server runs with it) into CROW_TAX_ENCRYPTION_KEY_PREVIOUS when the request leaves that slot alone and it is empty or holds the new key. A slot holding another key is never overwritten; the response warns, naming keys only. A typed previous key always wins. Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
…onfigure shows server warnings The tax server moves every return to the current key when it starts, so the key that matters after a change is always the one just replaced. Keeping an older one instead stranded everything on the current key at the next change. The Configure dialog now shows the response's warnings in the warning colour, long enough to read. Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
…all-guard PR is underneath); tax error text Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
kh0pper
force-pushed
the
fix/bundle-env-placeholders
branch
from
October 16, 2026 21:57
14289ad to
c293348
Compare
kh0pper
added a commit
that referenced
this pull request
Oct 16, 2026
…e values never reach an install (#504, includes #505) * fix(bundles): generate the admin passwords Crow can choose, and save them to the keychain A one-click (collection) install left these blank, so the install failed or the app had no usable login: - miniflux MINIFLUX_ADMIN_PASSWORD and maker-lab-advanced MLA_ADMIN_PASSWORD are generated, saved to the keychain with their usernames, and seed the app's admin on first start. MLA_ADMIN_USER defaults to admin (the compose no longer hard-fails on it). - lemmy's first-admin password was a literal in the public compose file. It is a generated LEMMY_ADMIN_PASSWORD now, and every secret reaches the entrypoint through the environment, so the script text in PID 1's command line holds names, not values. The read-only ./config mount the entrypoint wrote into is gone. - actual-budget: a generated ACTUAL_SERVER_PASSWORD for the server Crow installs, set right after install by a postInstall hook (Actual's first-run bootstrap is first-caller-wins) and as a fallback on first connect. It is only ever sent to a loopback URL. ACTUAL_PASSWORD stays typed, for an Actual server you run elsewhere. - The installer puts generated values a manifest lists in server.envKeys into this instance's MCP entry (mcp-addons.json, mode 600), never into the gateway .env that co-hosted instances share. campaigns and tax encryption keys are not generated yet: that waits for the recovery-key decision (tests/bundle-form-friction.test.js EXEMPT). Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * test(bundles): staged install fixtures carry no ports, so the installer never calls sudo on the test host Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * fix(bundles): generated-secrets review fixes — Lemmy left out; Actual explains a kept password - Lemmy is left out of this PR: its entrypoint execs a binary the pinned image doesn't have, so it can't start today; generating an admin password it never creates would put a wrong entry in the keychain. Follow-up. - Actual: when the server already has a password (kept data from an earlier install), the hook checks whether the generated one opens it and, if not, says to type the old one into ACTUAL_PASSWORD; the MCP login error says the same. The hook tolerates a missing .env and stays within its timeout. - A stored hash (store_as) never reaches an MCP entry. - maker-lab-advanced's panel says log in (the admin exists from first boot). - Actual docs (en/es) and skill describe the generated password. Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * chore(skills): record the updated actual-budget skill text in the ledger Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * fix(lemmy): no literal credential in the compose file The first-admin password was a literal in the public compose file. It is a generated LEMMY_ADMIN_PASSWORD now, saved to the keychain (username admin_pending), and every Lemmy secret reaches the entrypoint through the environment ($${...}), so the script text in PID 1's command line holds names, not values. The bundle's entrypoint still runs a path the pinned image doesn't have (a known issue in its notes; the fix needs a live test). Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * fix(actual-budget): only the generated password sets up a new server; login falls back to it A typed ACTUAL_PASSWORD can be stale (kept in the gateway .env from an older install). It is never used to bootstrap a new server any more, and on a loopback server a failed typed login retries with the generated password, in the MCP server and the panel. Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * fix(bundles): no placeholder value from .env.example reaches an install (stacked on #504) (#505) * fix(bundles): no placeholder value from .env.example reaches an install - servers/gateway/bundle-env-placeholders.js recognises example values (/path/to/, youruser, your-, *.example.com, change-me, replace-me, <...>, xxx, placeholder) and, by SHA-256, example secrets once published here. - The installer leaves example values out when it builds .env from .env.example: such a line becomes a comment naming only the key. - env_vars[].default_in_crow_home: a blank or example path becomes a folder inside this instance's CROW_HOME, created by the gateway (never by Docker as root), and the key no longer blocks install. jellyfin, navidrome, audiobookshelf and kavita start with an empty library there. - 'Finish setup' lists a required key whose value is an example, so existing installs are flagged without rewriting their .env. - tax never encrypts with an example key (decrypt still reads data saved with one); its .env.example no longer carries a key. - .env.example files no longer carry example domains, paths or secrets as live values (kept as commented examples). plex follows once the install-guard PR, which also bumps it, lands. Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * docs(bundles): jellyfin and audiobookshelf start with an empty library in Crow's folder Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * fix(bundles): placeholder fixes after review — tax key change keeps old returns readable; domains must be real - tax refuses only the published example key (by hash), never a passphrase for its shape. CROW_TAX_ENCRYPTION_KEY_PREVIOUS opens returns saved with an old passphrase; saving re-encrypts them with the current one. - gotosocial, writefreely and matrix-dendrite fail to start without a real domain (${X:?}) instead of booting with a blank one. - kavita and audiobookshelf keep their named-volume fallback (only the example paths are left out); jellyfin/navidrome's CROW_HOME paths are never copied to the shared gateway .env (propagate: false). Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * fix(bundles): placeholder review fixes — tax key change keeps the old key; reinstall keeps the library path - env_vars[].previous_key: changing tax's encryption key in Configure keeps the old one in CROW_TAX_ENCRYPTION_KEY_PREVIOUS (when that slot is empty), so returns saved with it stay readable. The contract checks the name. - Uninstall keeps default_in_crow_home paths in the retained copy and a reinstall reuses them, so kept Jellyfin/Navidrome data never gets an empty library mounted over it. - GTS_ACCOUNT_DOMAIN is optional (compose falls back to GTS_HOST); the domain fields say they are fixed at first start. - The scrub also handles KEY = value lines; navidrome docs (en/es). Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * fix(tax): key changes never strand data; the tax server reads its key from its own MCP entry - Configure reads the outgoing key where the tax server really runs with it (its mcp-addons entry, then the gateway env, then the bundle .env) and keeps it in CROW_TAX_ENCRYPTION_KEY_PREVIOUS. A second change while that slot still holds a different older key is refused (409), so no key in use is ever overwritten. - At start the tax server re-encrypts every return that only the previous key opens, so the slot can then be cleared. - tax's server.envKeys list both keys, so a typed key reaches the server. - A kept library path follows the latest install; log wording. Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * test(tax): the start-time re-encryption to the current key, as a tested function Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * fix(tax): simpler key change — the user keeps the old key in _PREVIOUS; re-encryption never overwrites a newer save The Configure-side copy of the outgoing key (and its 409) is removed: it could only be undone by hand-editing mcp-addons.json. A key change now means: set the new key and put the old one in CROW_TAX_ENCRYPTION_KEY_PREVIOUS (the field and the refusal message say so). Returns move to the new key when the tax server starts or a return is saved; the start-time move is a compare-and-swap, so a save that lands meanwhile wins. Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * fix(tax): a key change keeps the outgoing key in _PREVIOUS when that slot is empty Configure copies the outgoing CROW_TAX_ENCRYPTION_KEY (read where the tax server runs with it) into CROW_TAX_ENCRYPTION_KEY_PREVIOUS when the request leaves that slot alone and it is empty or holds the new key. A slot holding another key is never overwritten; the response warns, naming keys only. A typed previous key always wins. Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * fix(tax): the previous-key slot always takes the key just replaced; Configure shows server warnings The tax server moves every return to the current key when it starts, so the key that matters after a change is always the one just replaced. Keeping an older one instead stranded everything on the current key at the next change. The Configure dialog now shows the response's warnings in the warning colour, long enough to read. Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp * fix(plex): no example media path from .env.example (now that the install-guard PR is underneath); tax error text Claude-Session: https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp
This was referenced Oct 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR is stacked on #504 (
fix/bundle-generated-secrets). Merge #504 first, then retarget this one tomain.Queue item 30, fix C (one-click audit). The installer copied
.env.exampleexample values as if they were real config, and "Finish setup" counted them as filled. The result was fake media paths that Docker created as root,*.example.comdomains, and a tax encryption key published in this public repo.What changes
servers/gateway/bundle-env-placeholders.jsrecognises them by shape:/path/to/,youruser,your-,*.example.com(including@x:example.com),change-me,replace-me,<…>,xxx,placeholder. It also recognises example secrets once published here, by SHA-256 only..envfrom.env.example, it turns example-value lines into comments that name only the key. An existing.envis never rewritten.env_vars[].default_in_crow_homefield. A blank or example path becomes a folder inside this instance's CROW_HOME, created by the gateway (never by Docker as root). It is never copied to the shared gateway.env, and it no longer blocks install.media/jellyfinand navidromemedia/music..envis not rewritten.CROW_TAX_ENCRYPTION_KEY_PREVIOUSopens returns saved with an earlier key. Configure fills that slot with the outgoing key when it is empty, never overwrites a different kept key, and warns instead.server.envKeysnow include both keys, so a typed key reaches the server. Before, it never did.${X:?}), and the fields say the domain is fixed at first start.GTS_ACCOUNT_DOMAINis optional..env.examplefiles no longer carry example domains, paths or secrets as live values; they are kept as commented examples.Not in this PR
ROMM_AUTH_SECRET_KEY, which needs an operator rotation.:-changemecompose fallbacks (peertube, frigate, minio).Existing installs are never rewritten. Placeholders show up in "Finish setup".
Tests
tests/bundle-env-placeholders.test.js. It covers the detector, the scrub, install rungs, default folders and reinstall reuse, a collection-style jellyfin install,needsConfigKeys, a lint over all.env.examplefiles, and tax encrypt/decrypt, fallback, re-encryption and race handling, plus the Configure key change.bundles-install-hardening.Version bumps: jellyfin 1.0.2, navidrome 1.0.2, audiobookshelf 1.0.1, kavita 1.0.1, calibre-server 1.0.1, calibre-web 1.0.1, gotosocial 1.0.2, matrix-dendrite 1.0.4, writefreely 1.0.3, tax 2.0.3, nextcloud 1.1.1. The registry is rebuilt.
Deploy: gateway code. Restart
crow-gateway, plus r4's gateway on the same tree. Deploy together with #504.https://claude.ai/code/session_01N9fXzvNy8rCEArj1y5p6fp