Skip to content

feat: move apps to targets, support env_refs as a list - #110

Merged
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs
Aug 20, 2026
Merged

feat: move apps to targets, support env_refs as a list#110
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs

Conversation

@ineedjet

Copy link
Copy Markdown
Collaborator

Summary

Implements #108 and #104 together, leaving #103 (ref-resolution dedup) as a separate follow-up.

  • apps moves out of vault manifests (which now hold only env/secrets) onto target manifests, where it belongs conceptually — it's a property of the deployment, not of any one secrets source.
  • env_ref becomes env_refs (list, required non-empty), mirroring app_refs.

Why together

apps living in vaults was #104's main blocker: an APPS=... line was rendered per-vault, so merging multiple env sources meant reconciling their APPS lists too (naive concatenation silently drops earlier sources' apps — dotenv parsing is last-value-wins on duplicate keys). Moving apps onto the target first removes that obstacle entirely: merging env_refs is now just concatenation of N decrypted sources plus one synthesized APPS= line built from the target's own apps field, with fail-loud collision detection across all of them — no APPS-specific union logic needed.

Cross-repo age-key distribution (the other obstacle #104 raised) is untouched and stays out of scope — not needed while every vault lives in this repo, encrypted for the same keys/hawkeye.pub recipient.

No external consumers exist yet, so this is a breaking schema change: no legacy support for vault-level apps or singular env_ref.

Schema changes

vaults/hawkeye.ymlapps dropped:

asset: hawkeye.sops.envkeys:
- hawkeyeenv:
APPS_DOMAIN: RUBYKATZEN_COM_DOMAIN...

targets/hawkeye.ymlapps added, env_refenv_refs:

flightdeck_ref: rubykatzen/flightdeck@latestenv_refs:
- rubykatzen/flightdeck@latest:hawkeye.sops.envapp_refs:
- rubykatzen/flightdeck@latestapps:
- traefik
- rybbithosts:
- rubykatzen-com@100.75.50.2credentials: ...

load-yaml-matrix needed zero changes — confirms it's genuinely schema-agnostic, fields just flow through.

ansible/deploy.yml

Replaces the single env pull/decrypt chain with a loop shaped like the existing app-bundle merge loop: for each ref in flightdeck_env_refs, download + sops decrypt, then append every line to the merged output via a small add_line() function that rejects any key already seen — fails loud with Env key conflicts with an existing source: $key. The "seen keys" set is seeded with a synthesized APPS={{ flightdeck_apps | join(',') }} line before the loop, so a vault accidentally emitting its own APPS collides too, uniformly, with no special-casing.

This is the fourth near-identical "download + resolve @latest" block in this file (after the machinery pull, the app-refs loop, and now this) — #103 tracks extracting a shared script, deliberately deferred rather than folded in here.

Verification

  • encrypt-env unit tests updated (dropped apps/APPS fixtures and assertions, removed two now-obsolete tests) and passing
  • load-yaml-matrix unit tests — unchanged, still passing (confirms schema-agnosticism)
  • ansible-playbook --syntax-check ansible/deploy.yml
  • Real matrix trace-through against the actual vaults/hawkeye.yml / targets/hawkeye.ymlenv_refs/apps show up correctly in the generated JSON
  • Standalone bash dry-run of the add_line() collision-detection logic: clean multi-source merge, ordinary key collision, and a source colliding with the synthesized APPS line — all three behave as intended
  • pre-commit run --all-files
  • Real deploy to hawkeye exercising the new merge path (tracked in Normalize the hawkeye/rybbit deploy: configure prerequisites and get the first real deployment running #106)

Closes#108, closes#104.

Implements #108 and #104 together.
apps moves out of vault manifests (which now hold only env/secrets)
onto target manifests, where it belongs conceptually — it's a property
of the deployment, not of any one secrets source. This removes #104's
main blocker: with apps no longer rendered per-vault as an APPS= line,
merging multiple env sources is just concatenation with fail-loud
key-collision detection, no APPS-specific union logic needed.
env_ref becomes env_refs (list, required non-empty), mirroring
app_refs. ansible/deploy.yml decrypts every entry, merges them plus a
synthesized APPS= line (built from the target's own apps field, seeded
into the collision check first so a vault accidentally defining APPS
collides too), and fails loud on any duplicate key across sources -
matching the existing app-bundle-conflict philosophy.
No external consumers exist yet, so this is a breaking schema change:
no legacy support for vault-level apps or singular env_ref.
#103 (dedup the now four near-identical ref-resolution blocks) stays
deferred as its own follow-up, per plan.
@ineedjet
ineedjet requested a lite review from CopilotAugust 20, 2026 19:05

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Flightdeck’s deployment schema and pipeline to make app selection a property of deployment targets (not vaults) and to support merging multiple encrypted env sources per target, aligning env composition with existing multi-bundle app composition.

Changes:

  • Move apps out of vaults/*.yml into targets/*.yml and render APPS from the target’s desired app list.
  • Replace singular env_ref with required non-empty env_refs (list) and implement fail-loud env key collision detection when merging decrypted env assets.
  • Update deploy workflows and the encrypt-env action/docs/tests to match the new manifest and target schemas.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
vaults/hawkeye.ymlRemoves apps from the vault manifest so vaults contain only env/secrets.
targets/hawkeye.ymlAdds apps and switches env_refenv_refs for target-driven app selection and env composition.
ansible/deploy.ymlImplements looped download/decrypt/merge for flightdeck_env_refs with collision detection and synthesized APPS from flightdeck_apps.
README.mdDocuments new env_refs/apps target schema and updated Ansible/deploy usage examples.
AGENTS.mdUpdates repo agent guidance to reflect env merging and target-owned app selection.
.github/workflows/release.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy-shared.ymlAdds env-refs + apps inputs, validates them as non-empty JSON arrays, and forwards to Ansible as extra-vars.
.github/actions/encrypt-env/scripts/render-env.pyDrops apps from manifest schema and stops generating APPS in rendered env output.
.github/actions/encrypt-env/tests/test_render_env.pyUpdates tests to reflect removal of apps/APPS from the encrypt-env action output and schema validation.
.github/actions/encrypt-env/README.mdUpdates action documentation to remove apps and point app selection to targets.
Suppressed comments (2)

README.md:129

  • This multiple-env-source example also omits required playbook variables (flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, flightdeck_sops_age_key_file), so it won't run successfully as written.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env","<owner>/<other-secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'

README.md:118

  • This additional app-bundle example has the same issue as the earlier one: it doesn't pass required playbook variables like flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, and flightdeck_sops_age_key_file, so it will fail the playbook's initial assert.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest","<owner>/<extra-repo>@latest"],"flightdeck_apps":["traefik","rybbit"]}'

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadREADME.md Outdated
Comment on lines +90 to +93
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e flightdeck_env_ref=<owner>/<secrets-repo>@latest:<server>.sops.env \
-e '{"flightdeck_app_refs":["rubykatzen/flightdeck@latest"]}'
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'
Comment threadREADME.md Outdated
```

The `flightdeck_env_ref` format is `owner/repo@tag:asset`. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads the asset, decrypts it with the server-local SOPS age key (`flightdeck_sops_age_key_file`), links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
Each `flightdeck_env_refs` entry is in `owner/repo@tag:asset` format. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads and decrypts every entry with the server-local SOPS age key (`flightdeck_sops_age_key_file`), merges them into one `.env` alongside a synthesized `APPS` line built from `flightdeck_apps`, links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
…interface
deploy-shared.yml already exists specifically to hide flightdeck_*
Ansible variable names and -e JSON from callers, but the README's
"Ansible Deploy" section showed exactly that - three ansible-playbook
-e '{...}' examples, all of which actually fail at the playbook's
initial assert (missing flightdeck_app_ref/path/keep_releases/
sops_age_key_file), per Copilot's review on #110.
Replace it with a short pointer to deploy-shared.yml as the actual
interface and to the Vaults And Targets section for how multi-source
merging is configured, instead of patching three broken CLI examples
that shouldn't be a documented entry point in the first place.
Sign up for freeto 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.

Move desired apps from vault configuration to target configuration Support multiple env sources (env_refs) merged per target, like app_refs

2 participants

@ineedjet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat: move apps to targets, support env_refs as a list by ineedjet · Pull Request #110 · rubykatzen/flightdeck · GitHub
Skip to content

feat: move apps to targets, support env_refs as a list - #110

Merged
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs
Aug 20, 2026
Merged

feat: move apps to targets, support env_refs as a list#110
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs

Conversation

@ineedjet

Copy link
Copy Markdown
Collaborator

Summary

Implements #108 and #104 together, leaving #103 (ref-resolution dedup) as a separate follow-up.

  • apps moves out of vault manifests (which now hold only env/secrets) onto target manifests, where it belongs conceptually — it's a property of the deployment, not of any one secrets source.
  • env_ref becomes env_refs (list, required non-empty), mirroring app_refs.

Why together

apps living in vaults was #104's main blocker: an APPS=... line was rendered per-vault, so merging multiple env sources meant reconciling their APPS lists too (naive concatenation silently drops earlier sources' apps — dotenv parsing is last-value-wins on duplicate keys). Moving apps onto the target first removes that obstacle entirely: merging env_refs is now just concatenation of N decrypted sources plus one synthesized APPS= line built from the target's own apps field, with fail-loud collision detection across all of them — no APPS-specific union logic needed.

Cross-repo age-key distribution (the other obstacle #104 raised) is untouched and stays out of scope — not needed while every vault lives in this repo, encrypted for the same keys/hawkeye.pub recipient.

No external consumers exist yet, so this is a breaking schema change: no legacy support for vault-level apps or singular env_ref.

Schema changes

vaults/hawkeye.ymlapps dropped:

asset: hawkeye.sops.envkeys:
- hawkeyeenv:
APPS_DOMAIN: RUBYKATZEN_COM_DOMAIN...

targets/hawkeye.ymlapps added, env_refenv_refs:

flightdeck_ref: rubykatzen/flightdeck@latestenv_refs:
- rubykatzen/flightdeck@latest:hawkeye.sops.envapp_refs:
- rubykatzen/flightdeck@latestapps:
- traefik
- rybbithosts:
- rubykatzen-com@100.75.50.2credentials: ...

load-yaml-matrix needed zero changes — confirms it's genuinely schema-agnostic, fields just flow through.

ansible/deploy.yml

Replaces the single env pull/decrypt chain with a loop shaped like the existing app-bundle merge loop: for each ref in flightdeck_env_refs, download + sops decrypt, then append every line to the merged output via a small add_line() function that rejects any key already seen — fails loud with Env key conflicts with an existing source: $key. The "seen keys" set is seeded with a synthesized APPS={{ flightdeck_apps | join(',') }} line before the loop, so a vault accidentally emitting its own APPS collides too, uniformly, with no special-casing.

This is the fourth near-identical "download + resolve @latest" block in this file (after the machinery pull, the app-refs loop, and now this) — #103 tracks extracting a shared script, deliberately deferred rather than folded in here.

Verification

  • encrypt-env unit tests updated (dropped apps/APPS fixtures and assertions, removed two now-obsolete tests) and passing
  • load-yaml-matrix unit tests — unchanged, still passing (confirms schema-agnosticism)
  • ansible-playbook --syntax-check ansible/deploy.yml
  • Real matrix trace-through against the actual vaults/hawkeye.yml / targets/hawkeye.ymlenv_refs/apps show up correctly in the generated JSON
  • Standalone bash dry-run of the add_line() collision-detection logic: clean multi-source merge, ordinary key collision, and a source colliding with the synthesized APPS line — all three behave as intended
  • pre-commit run --all-files
  • Real deploy to hawkeye exercising the new merge path (tracked in Normalize the hawkeye/rybbit deploy: configure prerequisites and get the first real deployment running #106)

Closes#108, closes#104.

Implements #108 and #104 together.
apps moves out of vault manifests (which now hold only env/secrets)
onto target manifests, where it belongs conceptually — it's a property
of the deployment, not of any one secrets source. This removes #104's
main blocker: with apps no longer rendered per-vault as an APPS= line,
merging multiple env sources is just concatenation with fail-loud
key-collision detection, no APPS-specific union logic needed.
env_ref becomes env_refs (list, required non-empty), mirroring
app_refs. ansible/deploy.yml decrypts every entry, merges them plus a
synthesized APPS= line (built from the target's own apps field, seeded
into the collision check first so a vault accidentally defining APPS
collides too), and fails loud on any duplicate key across sources -
matching the existing app-bundle-conflict philosophy.
No external consumers exist yet, so this is a breaking schema change:
no legacy support for vault-level apps or singular env_ref.
#103 (dedup the now four near-identical ref-resolution blocks) stays
deferred as its own follow-up, per plan.
@ineedjet
ineedjet requested a lite review from CopilotAugust 20, 2026 19:05

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Flightdeck’s deployment schema and pipeline to make app selection a property of deployment targets (not vaults) and to support merging multiple encrypted env sources per target, aligning env composition with existing multi-bundle app composition.

Changes:

  • Move apps out of vaults/*.yml into targets/*.yml and render APPS from the target’s desired app list.
  • Replace singular env_ref with required non-empty env_refs (list) and implement fail-loud env key collision detection when merging decrypted env assets.
  • Update deploy workflows and the encrypt-env action/docs/tests to match the new manifest and target schemas.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
vaults/hawkeye.ymlRemoves apps from the vault manifest so vaults contain only env/secrets.
targets/hawkeye.ymlAdds apps and switches env_refenv_refs for target-driven app selection and env composition.
ansible/deploy.ymlImplements looped download/decrypt/merge for flightdeck_env_refs with collision detection and synthesized APPS from flightdeck_apps.
README.mdDocuments new env_refs/apps target schema and updated Ansible/deploy usage examples.
AGENTS.mdUpdates repo agent guidance to reflect env merging and target-owned app selection.
.github/workflows/release.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy-shared.ymlAdds env-refs + apps inputs, validates them as non-empty JSON arrays, and forwards to Ansible as extra-vars.
.github/actions/encrypt-env/scripts/render-env.pyDrops apps from manifest schema and stops generating APPS in rendered env output.
.github/actions/encrypt-env/tests/test_render_env.pyUpdates tests to reflect removal of apps/APPS from the encrypt-env action output and schema validation.
.github/actions/encrypt-env/README.mdUpdates action documentation to remove apps and point app selection to targets.
Suppressed comments (2)

README.md:129

  • This multiple-env-source example also omits required playbook variables (flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, flightdeck_sops_age_key_file), so it won't run successfully as written.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env","<owner>/<other-secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'

README.md:118

  • This additional app-bundle example has the same issue as the earlier one: it doesn't pass required playbook variables like flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, and flightdeck_sops_age_key_file, so it will fail the playbook's initial assert.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest","<owner>/<extra-repo>@latest"],"flightdeck_apps":["traefik","rybbit"]}'

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadREADME.md Outdated
Comment on lines +90 to +93
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e flightdeck_env_ref=<owner>/<secrets-repo>@latest:<server>.sops.env \
-e '{"flightdeck_app_refs":["rubykatzen/flightdeck@latest"]}'
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'
Comment threadREADME.md Outdated
```

The `flightdeck_env_ref` format is `owner/repo@tag:asset`. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads the asset, decrypts it with the server-local SOPS age key (`flightdeck_sops_age_key_file`), links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
Each `flightdeck_env_refs` entry is in `owner/repo@tag:asset` format. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads and decrypts every entry with the server-local SOPS age key (`flightdeck_sops_age_key_file`), merges them into one `.env` alongside a synthesized `APPS` line built from `flightdeck_apps`, links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
…interface
deploy-shared.yml already exists specifically to hide flightdeck_*
Ansible variable names and -e JSON from callers, but the README's
"Ansible Deploy" section showed exactly that - three ansible-playbook
-e '{...}' examples, all of which actually fail at the playbook's
initial assert (missing flightdeck_app_ref/path/keep_releases/
sops_age_key_file), per Copilot's review on #110.
Replace it with a short pointer to deploy-shared.yml as the actual
interface and to the Vaults And Targets section for how multi-source
merging is configured, instead of patching three broken CLI examples
that shouldn't be a documented entry point in the first place.
Sign up for freeto 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.

Move desired apps from vault configuration to target configuration Support multiple env sources (env_refs) merged per target, like app_refs

2 participants

@ineedjet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: move apps to targets, support env_refs as a list by ineedjet · Pull Request #110 · rubykatzen/flightdeck · GitHub
Skip to content

feat: move apps to targets, support env_refs as a list - #110

Merged
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs
Aug 20, 2026
Merged

feat: move apps to targets, support env_refs as a list#110
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs

Conversation

@ineedjet

Copy link
Copy Markdown
Collaborator

Summary

Implements #108 and #104 together, leaving #103 (ref-resolution dedup) as a separate follow-up.

  • apps moves out of vault manifests (which now hold only env/secrets) onto target manifests, where it belongs conceptually — it's a property of the deployment, not of any one secrets source.
  • env_ref becomes env_refs (list, required non-empty), mirroring app_refs.

Why together

apps living in vaults was #104's main blocker: an APPS=... line was rendered per-vault, so merging multiple env sources meant reconciling their APPS lists too (naive concatenation silently drops earlier sources' apps — dotenv parsing is last-value-wins on duplicate keys). Moving apps onto the target first removes that obstacle entirely: merging env_refs is now just concatenation of N decrypted sources plus one synthesized APPS= line built from the target's own apps field, with fail-loud collision detection across all of them — no APPS-specific union logic needed.

Cross-repo age-key distribution (the other obstacle #104 raised) is untouched and stays out of scope — not needed while every vault lives in this repo, encrypted for the same keys/hawkeye.pub recipient.

No external consumers exist yet, so this is a breaking schema change: no legacy support for vault-level apps or singular env_ref.

Schema changes

vaults/hawkeye.ymlapps dropped:

asset: hawkeye.sops.envkeys:
- hawkeyeenv:
APPS_DOMAIN: RUBYKATZEN_COM_DOMAIN...

targets/hawkeye.ymlapps added, env_refenv_refs:

flightdeck_ref: rubykatzen/flightdeck@latestenv_refs:
- rubykatzen/flightdeck@latest:hawkeye.sops.envapp_refs:
- rubykatzen/flightdeck@latestapps:
- traefik
- rybbithosts:
- rubykatzen-com@100.75.50.2credentials: ...

load-yaml-matrix needed zero changes — confirms it's genuinely schema-agnostic, fields just flow through.

ansible/deploy.yml

Replaces the single env pull/decrypt chain with a loop shaped like the existing app-bundle merge loop: for each ref in flightdeck_env_refs, download + sops decrypt, then append every line to the merged output via a small add_line() function that rejects any key already seen — fails loud with Env key conflicts with an existing source: $key. The "seen keys" set is seeded with a synthesized APPS={{ flightdeck_apps | join(',') }} line before the loop, so a vault accidentally emitting its own APPS collides too, uniformly, with no special-casing.

This is the fourth near-identical "download + resolve @latest" block in this file (after the machinery pull, the app-refs loop, and now this) — #103 tracks extracting a shared script, deliberately deferred rather than folded in here.

Verification

  • encrypt-env unit tests updated (dropped apps/APPS fixtures and assertions, removed two now-obsolete tests) and passing
  • load-yaml-matrix unit tests — unchanged, still passing (confirms schema-agnosticism)
  • ansible-playbook --syntax-check ansible/deploy.yml
  • Real matrix trace-through against the actual vaults/hawkeye.yml / targets/hawkeye.ymlenv_refs/apps show up correctly in the generated JSON
  • Standalone bash dry-run of the add_line() collision-detection logic: clean multi-source merge, ordinary key collision, and a source colliding with the synthesized APPS line — all three behave as intended
  • pre-commit run --all-files
  • Real deploy to hawkeye exercising the new merge path (tracked in Normalize the hawkeye/rybbit deploy: configure prerequisites and get the first real deployment running #106)

Closes#108, closes#104.

Implements #108 and #104 together.
apps moves out of vault manifests (which now hold only env/secrets)
onto target manifests, where it belongs conceptually — it's a property
of the deployment, not of any one secrets source. This removes #104's
main blocker: with apps no longer rendered per-vault as an APPS= line,
merging multiple env sources is just concatenation with fail-loud
key-collision detection, no APPS-specific union logic needed.
env_ref becomes env_refs (list, required non-empty), mirroring
app_refs. ansible/deploy.yml decrypts every entry, merges them plus a
synthesized APPS= line (built from the target's own apps field, seeded
into the collision check first so a vault accidentally defining APPS
collides too), and fails loud on any duplicate key across sources -
matching the existing app-bundle-conflict philosophy.
No external consumers exist yet, so this is a breaking schema change:
no legacy support for vault-level apps or singular env_ref.
#103 (dedup the now four near-identical ref-resolution blocks) stays
deferred as its own follow-up, per plan.
@ineedjet
ineedjet requested a lite review from CopilotAugust 20, 2026 19:05

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Flightdeck’s deployment schema and pipeline to make app selection a property of deployment targets (not vaults) and to support merging multiple encrypted env sources per target, aligning env composition with existing multi-bundle app composition.

Changes:

  • Move apps out of vaults/*.yml into targets/*.yml and render APPS from the target’s desired app list.
  • Replace singular env_ref with required non-empty env_refs (list) and implement fail-loud env key collision detection when merging decrypted env assets.
  • Update deploy workflows and the encrypt-env action/docs/tests to match the new manifest and target schemas.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
vaults/hawkeye.ymlRemoves apps from the vault manifest so vaults contain only env/secrets.
targets/hawkeye.ymlAdds apps and switches env_refenv_refs for target-driven app selection and env composition.
ansible/deploy.ymlImplements looped download/decrypt/merge for flightdeck_env_refs with collision detection and synthesized APPS from flightdeck_apps.
README.mdDocuments new env_refs/apps target schema and updated Ansible/deploy usage examples.
AGENTS.mdUpdates repo agent guidance to reflect env merging and target-owned app selection.
.github/workflows/release.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy-shared.ymlAdds env-refs + apps inputs, validates them as non-empty JSON arrays, and forwards to Ansible as extra-vars.
.github/actions/encrypt-env/scripts/render-env.pyDrops apps from manifest schema and stops generating APPS in rendered env output.
.github/actions/encrypt-env/tests/test_render_env.pyUpdates tests to reflect removal of apps/APPS from the encrypt-env action output and schema validation.
.github/actions/encrypt-env/README.mdUpdates action documentation to remove apps and point app selection to targets.
Suppressed comments (2)

README.md:129

  • This multiple-env-source example also omits required playbook variables (flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, flightdeck_sops_age_key_file), so it won't run successfully as written.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env","<owner>/<other-secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'

README.md:118

  • This additional app-bundle example has the same issue as the earlier one: it doesn't pass required playbook variables like flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, and flightdeck_sops_age_key_file, so it will fail the playbook's initial assert.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest","<owner>/<extra-repo>@latest"],"flightdeck_apps":["traefik","rybbit"]}'

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadREADME.md Outdated
Comment on lines +90 to +93
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e flightdeck_env_ref=<owner>/<secrets-repo>@latest:<server>.sops.env \
-e '{"flightdeck_app_refs":["rubykatzen/flightdeck@latest"]}'
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'
Comment threadREADME.md Outdated
```

The `flightdeck_env_ref` format is `owner/repo@tag:asset`. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads the asset, decrypts it with the server-local SOPS age key (`flightdeck_sops_age_key_file`), links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
Each `flightdeck_env_refs` entry is in `owner/repo@tag:asset` format. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads and decrypts every entry with the server-local SOPS age key (`flightdeck_sops_age_key_file`), merges them into one `.env` alongside a synthesized `APPS` line built from `flightdeck_apps`, links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
…interface
deploy-shared.yml already exists specifically to hide flightdeck_*
Ansible variable names and -e JSON from callers, but the README's
"Ansible Deploy" section showed exactly that - three ansible-playbook
-e '{...}' examples, all of which actually fail at the playbook's
initial assert (missing flightdeck_app_ref/path/keep_releases/
sops_age_key_file), per Copilot's review on #110.
Replace it with a short pointer to deploy-shared.yml as the actual
interface and to the Vaults And Targets section for how multi-source
merging is configured, instead of patching three broken CLI examples
that shouldn't be a documented entry point in the first place.
Sign up for freeto 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.

Move desired apps from vault configuration to target configuration Support multiple env sources (env_refs) merged per target, like app_refs

2 participants

@ineedjet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: move apps to targets, support env_refs as a list by ineedjet · Pull Request #110 · rubykatzen/flightdeck · GitHub
Skip to content

feat: move apps to targets, support env_refs as a list - #110

Merged
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs
Aug 20, 2026
Merged

feat: move apps to targets, support env_refs as a list#110
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs

Conversation

@ineedjet

Copy link
Copy Markdown
Collaborator

Summary

Implements #108 and #104 together, leaving #103 (ref-resolution dedup) as a separate follow-up.

  • apps moves out of vault manifests (which now hold only env/secrets) onto target manifests, where it belongs conceptually — it's a property of the deployment, not of any one secrets source.
  • env_ref becomes env_refs (list, required non-empty), mirroring app_refs.

Why together

apps living in vaults was #104's main blocker: an APPS=... line was rendered per-vault, so merging multiple env sources meant reconciling their APPS lists too (naive concatenation silently drops earlier sources' apps — dotenv parsing is last-value-wins on duplicate keys). Moving apps onto the target first removes that obstacle entirely: merging env_refs is now just concatenation of N decrypted sources plus one synthesized APPS= line built from the target's own apps field, with fail-loud collision detection across all of them — no APPS-specific union logic needed.

Cross-repo age-key distribution (the other obstacle #104 raised) is untouched and stays out of scope — not needed while every vault lives in this repo, encrypted for the same keys/hawkeye.pub recipient.

No external consumers exist yet, so this is a breaking schema change: no legacy support for vault-level apps or singular env_ref.

Schema changes

vaults/hawkeye.ymlapps dropped:

asset: hawkeye.sops.envkeys:
- hawkeyeenv:
APPS_DOMAIN: RUBYKATZEN_COM_DOMAIN...

targets/hawkeye.ymlapps added, env_refenv_refs:

flightdeck_ref: rubykatzen/flightdeck@latestenv_refs:
- rubykatzen/flightdeck@latest:hawkeye.sops.envapp_refs:
- rubykatzen/flightdeck@latestapps:
- traefik
- rybbithosts:
- rubykatzen-com@100.75.50.2credentials: ...

load-yaml-matrix needed zero changes — confirms it's genuinely schema-agnostic, fields just flow through.

ansible/deploy.yml

Replaces the single env pull/decrypt chain with a loop shaped like the existing app-bundle merge loop: for each ref in flightdeck_env_refs, download + sops decrypt, then append every line to the merged output via a small add_line() function that rejects any key already seen — fails loud with Env key conflicts with an existing source: $key. The "seen keys" set is seeded with a synthesized APPS={{ flightdeck_apps | join(',') }} line before the loop, so a vault accidentally emitting its own APPS collides too, uniformly, with no special-casing.

This is the fourth near-identical "download + resolve @latest" block in this file (after the machinery pull, the app-refs loop, and now this) — #103 tracks extracting a shared script, deliberately deferred rather than folded in here.

Verification

  • encrypt-env unit tests updated (dropped apps/APPS fixtures and assertions, removed two now-obsolete tests) and passing
  • load-yaml-matrix unit tests — unchanged, still passing (confirms schema-agnosticism)
  • ansible-playbook --syntax-check ansible/deploy.yml
  • Real matrix trace-through against the actual vaults/hawkeye.yml / targets/hawkeye.ymlenv_refs/apps show up correctly in the generated JSON
  • Standalone bash dry-run of the add_line() collision-detection logic: clean multi-source merge, ordinary key collision, and a source colliding with the synthesized APPS line — all three behave as intended
  • pre-commit run --all-files
  • Real deploy to hawkeye exercising the new merge path (tracked in Normalize the hawkeye/rybbit deploy: configure prerequisites and get the first real deployment running #106)

Closes#108, closes#104.

Implements #108 and #104 together.
apps moves out of vault manifests (which now hold only env/secrets)
onto target manifests, where it belongs conceptually — it's a property
of the deployment, not of any one secrets source. This removes #104's
main blocker: with apps no longer rendered per-vault as an APPS= line,
merging multiple env sources is just concatenation with fail-loud
key-collision detection, no APPS-specific union logic needed.
env_ref becomes env_refs (list, required non-empty), mirroring
app_refs. ansible/deploy.yml decrypts every entry, merges them plus a
synthesized APPS= line (built from the target's own apps field, seeded
into the collision check first so a vault accidentally defining APPS
collides too), and fails loud on any duplicate key across sources -
matching the existing app-bundle-conflict philosophy.
No external consumers exist yet, so this is a breaking schema change:
no legacy support for vault-level apps or singular env_ref.
#103 (dedup the now four near-identical ref-resolution blocks) stays
deferred as its own follow-up, per plan.
@ineedjet
ineedjet requested a lite review from CopilotAugust 20, 2026 19:05

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Flightdeck’s deployment schema and pipeline to make app selection a property of deployment targets (not vaults) and to support merging multiple encrypted env sources per target, aligning env composition with existing multi-bundle app composition.

Changes:

  • Move apps out of vaults/*.yml into targets/*.yml and render APPS from the target’s desired app list.
  • Replace singular env_ref with required non-empty env_refs (list) and implement fail-loud env key collision detection when merging decrypted env assets.
  • Update deploy workflows and the encrypt-env action/docs/tests to match the new manifest and target schemas.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
vaults/hawkeye.ymlRemoves apps from the vault manifest so vaults contain only env/secrets.
targets/hawkeye.ymlAdds apps and switches env_refenv_refs for target-driven app selection and env composition.
ansible/deploy.ymlImplements looped download/decrypt/merge for flightdeck_env_refs with collision detection and synthesized APPS from flightdeck_apps.
README.mdDocuments new env_refs/apps target schema and updated Ansible/deploy usage examples.
AGENTS.mdUpdates repo agent guidance to reflect env merging and target-owned app selection.
.github/workflows/release.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy-shared.ymlAdds env-refs + apps inputs, validates them as non-empty JSON arrays, and forwards to Ansible as extra-vars.
.github/actions/encrypt-env/scripts/render-env.pyDrops apps from manifest schema and stops generating APPS in rendered env output.
.github/actions/encrypt-env/tests/test_render_env.pyUpdates tests to reflect removal of apps/APPS from the encrypt-env action output and schema validation.
.github/actions/encrypt-env/README.mdUpdates action documentation to remove apps and point app selection to targets.
Suppressed comments (2)

README.md:129

  • This multiple-env-source example also omits required playbook variables (flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, flightdeck_sops_age_key_file), so it won't run successfully as written.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env","<owner>/<other-secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'

README.md:118

  • This additional app-bundle example has the same issue as the earlier one: it doesn't pass required playbook variables like flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, and flightdeck_sops_age_key_file, so it will fail the playbook's initial assert.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest","<owner>/<extra-repo>@latest"],"flightdeck_apps":["traefik","rybbit"]}'

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadREADME.md Outdated
Comment on lines +90 to +93
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e flightdeck_env_ref=<owner>/<secrets-repo>@latest:<server>.sops.env \
-e '{"flightdeck_app_refs":["rubykatzen/flightdeck@latest"]}'
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'
Comment threadREADME.md Outdated
```

The `flightdeck_env_ref` format is `owner/repo@tag:asset`. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads the asset, decrypts it with the server-local SOPS age key (`flightdeck_sops_age_key_file`), links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
Each `flightdeck_env_refs` entry is in `owner/repo@tag:asset` format. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads and decrypts every entry with the server-local SOPS age key (`flightdeck_sops_age_key_file`), merges them into one `.env` alongside a synthesized `APPS` line built from `flightdeck_apps`, links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
…interface
deploy-shared.yml already exists specifically to hide flightdeck_*
Ansible variable names and -e JSON from callers, but the README's
"Ansible Deploy" section showed exactly that - three ansible-playbook
-e '{...}' examples, all of which actually fail at the playbook's
initial assert (missing flightdeck_app_ref/path/keep_releases/
sops_age_key_file), per Copilot's review on #110.
Replace it with a short pointer to deploy-shared.yml as the actual
interface and to the Vaults And Targets section for how multi-source
merging is configured, instead of patching three broken CLI examples
that shouldn't be a documented entry point in the first place.
Sign up for freeto 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.

Move desired apps from vault configuration to target configuration Support multiple env sources (env_refs) merged per target, like app_refs

2 participants

@ineedjet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat: move apps to targets, support env_refs as a list by ineedjet · Pull Request #110 · rubykatzen/flightdeck · GitHub
Skip to content

feat: move apps to targets, support env_refs as a list - #110

Merged
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs
Aug 20, 2026
Merged

feat: move apps to targets, support env_refs as a list#110
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs

Conversation

@ineedjet

Copy link
Copy Markdown
Collaborator

Summary

Implements #108 and #104 together, leaving #103 (ref-resolution dedup) as a separate follow-up.

  • apps moves out of vault manifests (which now hold only env/secrets) onto target manifests, where it belongs conceptually — it's a property of the deployment, not of any one secrets source.
  • env_ref becomes env_refs (list, required non-empty), mirroring app_refs.

Why together

apps living in vaults was #104's main blocker: an APPS=... line was rendered per-vault, so merging multiple env sources meant reconciling their APPS lists too (naive concatenation silently drops earlier sources' apps — dotenv parsing is last-value-wins on duplicate keys). Moving apps onto the target first removes that obstacle entirely: merging env_refs is now just concatenation of N decrypted sources plus one synthesized APPS= line built from the target's own apps field, with fail-loud collision detection across all of them — no APPS-specific union logic needed.

Cross-repo age-key distribution (the other obstacle #104 raised) is untouched and stays out of scope — not needed while every vault lives in this repo, encrypted for the same keys/hawkeye.pub recipient.

No external consumers exist yet, so this is a breaking schema change: no legacy support for vault-level apps or singular env_ref.

Schema changes

vaults/hawkeye.ymlapps dropped:

asset: hawkeye.sops.envkeys:
- hawkeyeenv:
APPS_DOMAIN: RUBYKATZEN_COM_DOMAIN...

targets/hawkeye.ymlapps added, env_refenv_refs:

flightdeck_ref: rubykatzen/flightdeck@latestenv_refs:
- rubykatzen/flightdeck@latest:hawkeye.sops.envapp_refs:
- rubykatzen/flightdeck@latestapps:
- traefik
- rybbithosts:
- rubykatzen-com@100.75.50.2credentials: ...

load-yaml-matrix needed zero changes — confirms it's genuinely schema-agnostic, fields just flow through.

ansible/deploy.yml

Replaces the single env pull/decrypt chain with a loop shaped like the existing app-bundle merge loop: for each ref in flightdeck_env_refs, download + sops decrypt, then append every line to the merged output via a small add_line() function that rejects any key already seen — fails loud with Env key conflicts with an existing source: $key. The "seen keys" set is seeded with a synthesized APPS={{ flightdeck_apps | join(',') }} line before the loop, so a vault accidentally emitting its own APPS collides too, uniformly, with no special-casing.

This is the fourth near-identical "download + resolve @latest" block in this file (after the machinery pull, the app-refs loop, and now this) — #103 tracks extracting a shared script, deliberately deferred rather than folded in here.

Verification

  • encrypt-env unit tests updated (dropped apps/APPS fixtures and assertions, removed two now-obsolete tests) and passing
  • load-yaml-matrix unit tests — unchanged, still passing (confirms schema-agnosticism)
  • ansible-playbook --syntax-check ansible/deploy.yml
  • Real matrix trace-through against the actual vaults/hawkeye.yml / targets/hawkeye.ymlenv_refs/apps show up correctly in the generated JSON
  • Standalone bash dry-run of the add_line() collision-detection logic: clean multi-source merge, ordinary key collision, and a source colliding with the synthesized APPS line — all three behave as intended
  • pre-commit run --all-files
  • Real deploy to hawkeye exercising the new merge path (tracked in Normalize the hawkeye/rybbit deploy: configure prerequisites and get the first real deployment running #106)

Closes#108, closes#104.

Implements #108 and #104 together.
apps moves out of vault manifests (which now hold only env/secrets)
onto target manifests, where it belongs conceptually — it's a property
of the deployment, not of any one secrets source. This removes #104's
main blocker: with apps no longer rendered per-vault as an APPS= line,
merging multiple env sources is just concatenation with fail-loud
key-collision detection, no APPS-specific union logic needed.
env_ref becomes env_refs (list, required non-empty), mirroring
app_refs. ansible/deploy.yml decrypts every entry, merges them plus a
synthesized APPS= line (built from the target's own apps field, seeded
into the collision check first so a vault accidentally defining APPS
collides too), and fails loud on any duplicate key across sources -
matching the existing app-bundle-conflict philosophy.
No external consumers exist yet, so this is a breaking schema change:
no legacy support for vault-level apps or singular env_ref.
#103 (dedup the now four near-identical ref-resolution blocks) stays
deferred as its own follow-up, per plan.
@ineedjet
ineedjet requested a lite review from CopilotAugust 20, 2026 19:05

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Flightdeck’s deployment schema and pipeline to make app selection a property of deployment targets (not vaults) and to support merging multiple encrypted env sources per target, aligning env composition with existing multi-bundle app composition.

Changes:

  • Move apps out of vaults/*.yml into targets/*.yml and render APPS from the target’s desired app list.
  • Replace singular env_ref with required non-empty env_refs (list) and implement fail-loud env key collision detection when merging decrypted env assets.
  • Update deploy workflows and the encrypt-env action/docs/tests to match the new manifest and target schemas.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
vaults/hawkeye.ymlRemoves apps from the vault manifest so vaults contain only env/secrets.
targets/hawkeye.ymlAdds apps and switches env_refenv_refs for target-driven app selection and env composition.
ansible/deploy.ymlImplements looped download/decrypt/merge for flightdeck_env_refs with collision detection and synthesized APPS from flightdeck_apps.
README.mdDocuments new env_refs/apps target schema and updated Ansible/deploy usage examples.
AGENTS.mdUpdates repo agent guidance to reflect env merging and target-owned app selection.
.github/workflows/release.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy-shared.ymlAdds env-refs + apps inputs, validates them as non-empty JSON arrays, and forwards to Ansible as extra-vars.
.github/actions/encrypt-env/scripts/render-env.pyDrops apps from manifest schema and stops generating APPS in rendered env output.
.github/actions/encrypt-env/tests/test_render_env.pyUpdates tests to reflect removal of apps/APPS from the encrypt-env action output and schema validation.
.github/actions/encrypt-env/README.mdUpdates action documentation to remove apps and point app selection to targets.
Suppressed comments (2)

README.md:129

  • This multiple-env-source example also omits required playbook variables (flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, flightdeck_sops_age_key_file), so it won't run successfully as written.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env","<owner>/<other-secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'

README.md:118

  • This additional app-bundle example has the same issue as the earlier one: it doesn't pass required playbook variables like flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, and flightdeck_sops_age_key_file, so it will fail the playbook's initial assert.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest","<owner>/<extra-repo>@latest"],"flightdeck_apps":["traefik","rybbit"]}'

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadREADME.md Outdated
Comment on lines +90 to +93
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e flightdeck_env_ref=<owner>/<secrets-repo>@latest:<server>.sops.env \
-e '{"flightdeck_app_refs":["rubykatzen/flightdeck@latest"]}'
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'
Comment threadREADME.md Outdated
```

The `flightdeck_env_ref` format is `owner/repo@tag:asset`. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads the asset, decrypts it with the server-local SOPS age key (`flightdeck_sops_age_key_file`), links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
Each `flightdeck_env_refs` entry is in `owner/repo@tag:asset` format. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads and decrypts every entry with the server-local SOPS age key (`flightdeck_sops_age_key_file`), merges them into one `.env` alongside a synthesized `APPS` line built from `flightdeck_apps`, links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
…interface
deploy-shared.yml already exists specifically to hide flightdeck_*
Ansible variable names and -e JSON from callers, but the README's
"Ansible Deploy" section showed exactly that - three ansible-playbook
-e '{...}' examples, all of which actually fail at the playbook's
initial assert (missing flightdeck_app_ref/path/keep_releases/
sops_age_key_file), per Copilot's review on #110.
Replace it with a short pointer to deploy-shared.yml as the actual
interface and to the Vaults And Targets section for how multi-source
merging is configured, instead of patching three broken CLI examples
that shouldn't be a documented entry point in the first place.
Sign up for freeto 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.

Move desired apps from vault configuration to target configuration Support multiple env sources (env_refs) merged per target, like app_refs

2 participants

@ineedjet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: move apps to targets, support env_refs as a list by ineedjet · Pull Request #110 · rubykatzen/flightdeck · GitHub
Skip to content

feat: move apps to targets, support env_refs as a list - #110

Merged
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs
Aug 20, 2026
Merged

feat: move apps to targets, support env_refs as a list#110
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs

Conversation

@ineedjet

Copy link
Copy Markdown
Collaborator

Summary

Implements #108 and #104 together, leaving #103 (ref-resolution dedup) as a separate follow-up.

  • apps moves out of vault manifests (which now hold only env/secrets) onto target manifests, where it belongs conceptually — it's a property of the deployment, not of any one secrets source.
  • env_ref becomes env_refs (list, required non-empty), mirroring app_refs.

Why together

apps living in vaults was #104's main blocker: an APPS=... line was rendered per-vault, so merging multiple env sources meant reconciling their APPS lists too (naive concatenation silently drops earlier sources' apps — dotenv parsing is last-value-wins on duplicate keys). Moving apps onto the target first removes that obstacle entirely: merging env_refs is now just concatenation of N decrypted sources plus one synthesized APPS= line built from the target's own apps field, with fail-loud collision detection across all of them — no APPS-specific union logic needed.

Cross-repo age-key distribution (the other obstacle #104 raised) is untouched and stays out of scope — not needed while every vault lives in this repo, encrypted for the same keys/hawkeye.pub recipient.

No external consumers exist yet, so this is a breaking schema change: no legacy support for vault-level apps or singular env_ref.

Schema changes

vaults/hawkeye.ymlapps dropped:

asset: hawkeye.sops.envkeys:
- hawkeyeenv:
APPS_DOMAIN: RUBYKATZEN_COM_DOMAIN...

targets/hawkeye.ymlapps added, env_refenv_refs:

flightdeck_ref: rubykatzen/flightdeck@latestenv_refs:
- rubykatzen/flightdeck@latest:hawkeye.sops.envapp_refs:
- rubykatzen/flightdeck@latestapps:
- traefik
- rybbithosts:
- rubykatzen-com@100.75.50.2credentials: ...

load-yaml-matrix needed zero changes — confirms it's genuinely schema-agnostic, fields just flow through.

ansible/deploy.yml

Replaces the single env pull/decrypt chain with a loop shaped like the existing app-bundle merge loop: for each ref in flightdeck_env_refs, download + sops decrypt, then append every line to the merged output via a small add_line() function that rejects any key already seen — fails loud with Env key conflicts with an existing source: $key. The "seen keys" set is seeded with a synthesized APPS={{ flightdeck_apps | join(',') }} line before the loop, so a vault accidentally emitting its own APPS collides too, uniformly, with no special-casing.

This is the fourth near-identical "download + resolve @latest" block in this file (after the machinery pull, the app-refs loop, and now this) — #103 tracks extracting a shared script, deliberately deferred rather than folded in here.

Verification

  • encrypt-env unit tests updated (dropped apps/APPS fixtures and assertions, removed two now-obsolete tests) and passing
  • load-yaml-matrix unit tests — unchanged, still passing (confirms schema-agnosticism)
  • ansible-playbook --syntax-check ansible/deploy.yml
  • Real matrix trace-through against the actual vaults/hawkeye.yml / targets/hawkeye.ymlenv_refs/apps show up correctly in the generated JSON
  • Standalone bash dry-run of the add_line() collision-detection logic: clean multi-source merge, ordinary key collision, and a source colliding with the synthesized APPS line — all three behave as intended
  • pre-commit run --all-files
  • Real deploy to hawkeye exercising the new merge path (tracked in Normalize the hawkeye/rybbit deploy: configure prerequisites and get the first real deployment running #106)

Closes#108, closes#104.

Implements #108 and #104 together.
apps moves out of vault manifests (which now hold only env/secrets)
onto target manifests, where it belongs conceptually — it's a property
of the deployment, not of any one secrets source. This removes #104's
main blocker: with apps no longer rendered per-vault as an APPS= line,
merging multiple env sources is just concatenation with fail-loud
key-collision detection, no APPS-specific union logic needed.
env_ref becomes env_refs (list, required non-empty), mirroring
app_refs. ansible/deploy.yml decrypts every entry, merges them plus a
synthesized APPS= line (built from the target's own apps field, seeded
into the collision check first so a vault accidentally defining APPS
collides too), and fails loud on any duplicate key across sources -
matching the existing app-bundle-conflict philosophy.
No external consumers exist yet, so this is a breaking schema change:
no legacy support for vault-level apps or singular env_ref.
#103 (dedup the now four near-identical ref-resolution blocks) stays
deferred as its own follow-up, per plan.
@ineedjet
ineedjet requested a lite review from CopilotAugust 20, 2026 19:05

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Flightdeck’s deployment schema and pipeline to make app selection a property of deployment targets (not vaults) and to support merging multiple encrypted env sources per target, aligning env composition with existing multi-bundle app composition.

Changes:

  • Move apps out of vaults/*.yml into targets/*.yml and render APPS from the target’s desired app list.
  • Replace singular env_ref with required non-empty env_refs (list) and implement fail-loud env key collision detection when merging decrypted env assets.
  • Update deploy workflows and the encrypt-env action/docs/tests to match the new manifest and target schemas.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
vaults/hawkeye.ymlRemoves apps from the vault manifest so vaults contain only env/secrets.
targets/hawkeye.ymlAdds apps and switches env_refenv_refs for target-driven app selection and env composition.
ansible/deploy.ymlImplements looped download/decrypt/merge for flightdeck_env_refs with collision detection and synthesized APPS from flightdeck_apps.
README.mdDocuments new env_refs/apps target schema and updated Ansible/deploy usage examples.
AGENTS.mdUpdates repo agent guidance to reflect env merging and target-owned app selection.
.github/workflows/release.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy-shared.ymlAdds env-refs + apps inputs, validates them as non-empty JSON arrays, and forwards to Ansible as extra-vars.
.github/actions/encrypt-env/scripts/render-env.pyDrops apps from manifest schema and stops generating APPS in rendered env output.
.github/actions/encrypt-env/tests/test_render_env.pyUpdates tests to reflect removal of apps/APPS from the encrypt-env action output and schema validation.
.github/actions/encrypt-env/README.mdUpdates action documentation to remove apps and point app selection to targets.
Suppressed comments (2)

README.md:129

  • This multiple-env-source example also omits required playbook variables (flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, flightdeck_sops_age_key_file), so it won't run successfully as written.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env","<owner>/<other-secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'

README.md:118

  • This additional app-bundle example has the same issue as the earlier one: it doesn't pass required playbook variables like flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, and flightdeck_sops_age_key_file, so it will fail the playbook's initial assert.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest","<owner>/<extra-repo>@latest"],"flightdeck_apps":["traefik","rybbit"]}'

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadREADME.md Outdated
Comment on lines +90 to +93
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e flightdeck_env_ref=<owner>/<secrets-repo>@latest:<server>.sops.env \
-e '{"flightdeck_app_refs":["rubykatzen/flightdeck@latest"]}'
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'
Comment threadREADME.md Outdated
```

The `flightdeck_env_ref` format is `owner/repo@tag:asset`. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads the asset, decrypts it with the server-local SOPS age key (`flightdeck_sops_age_key_file`), links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
Each `flightdeck_env_refs` entry is in `owner/repo@tag:asset` format. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads and decrypts every entry with the server-local SOPS age key (`flightdeck_sops_age_key_file`), merges them into one `.env` alongside a synthesized `APPS` line built from `flightdeck_apps`, links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
…interface
deploy-shared.yml already exists specifically to hide flightdeck_*
Ansible variable names and -e JSON from callers, but the README's
"Ansible Deploy" section showed exactly that - three ansible-playbook
-e '{...}' examples, all of which actually fail at the playbook's
initial assert (missing flightdeck_app_ref/path/keep_releases/
sops_age_key_file), per Copilot's review on #110.
Replace it with a short pointer to deploy-shared.yml as the actual
interface and to the Vaults And Targets section for how multi-source
merging is configured, instead of patching three broken CLI examples
that shouldn't be a documented entry point in the first place.
Sign up for freeto 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.

Move desired apps from vault configuration to target configuration Support multiple env sources (env_refs) merged per target, like app_refs

2 participants

@ineedjet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: move apps to targets, support env_refs as a list by ineedjet · Pull Request #110 · rubykatzen/flightdeck · GitHub
Skip to content

feat: move apps to targets, support env_refs as a list - #110

Merged
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs
Aug 20, 2026
Merged

feat: move apps to targets, support env_refs as a list#110
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs

Conversation

@ineedjet

Copy link
Copy Markdown
Collaborator

Summary

Implements #108 and #104 together, leaving #103 (ref-resolution dedup) as a separate follow-up.

  • apps moves out of vault manifests (which now hold only env/secrets) onto target manifests, where it belongs conceptually — it's a property of the deployment, not of any one secrets source.
  • env_ref becomes env_refs (list, required non-empty), mirroring app_refs.

Why together

apps living in vaults was #104's main blocker: an APPS=... line was rendered per-vault, so merging multiple env sources meant reconciling their APPS lists too (naive concatenation silently drops earlier sources' apps — dotenv parsing is last-value-wins on duplicate keys). Moving apps onto the target first removes that obstacle entirely: merging env_refs is now just concatenation of N decrypted sources plus one synthesized APPS= line built from the target's own apps field, with fail-loud collision detection across all of them — no APPS-specific union logic needed.

Cross-repo age-key distribution (the other obstacle #104 raised) is untouched and stays out of scope — not needed while every vault lives in this repo, encrypted for the same keys/hawkeye.pub recipient.

No external consumers exist yet, so this is a breaking schema change: no legacy support for vault-level apps or singular env_ref.

Schema changes

vaults/hawkeye.ymlapps dropped:

asset: hawkeye.sops.envkeys:
- hawkeyeenv:
APPS_DOMAIN: RUBYKATZEN_COM_DOMAIN...

targets/hawkeye.ymlapps added, env_refenv_refs:

flightdeck_ref: rubykatzen/flightdeck@latestenv_refs:
- rubykatzen/flightdeck@latest:hawkeye.sops.envapp_refs:
- rubykatzen/flightdeck@latestapps:
- traefik
- rybbithosts:
- rubykatzen-com@100.75.50.2credentials: ...

load-yaml-matrix needed zero changes — confirms it's genuinely schema-agnostic, fields just flow through.

ansible/deploy.yml

Replaces the single env pull/decrypt chain with a loop shaped like the existing app-bundle merge loop: for each ref in flightdeck_env_refs, download + sops decrypt, then append every line to the merged output via a small add_line() function that rejects any key already seen — fails loud with Env key conflicts with an existing source: $key. The "seen keys" set is seeded with a synthesized APPS={{ flightdeck_apps | join(',') }} line before the loop, so a vault accidentally emitting its own APPS collides too, uniformly, with no special-casing.

This is the fourth near-identical "download + resolve @latest" block in this file (after the machinery pull, the app-refs loop, and now this) — #103 tracks extracting a shared script, deliberately deferred rather than folded in here.

Verification

  • encrypt-env unit tests updated (dropped apps/APPS fixtures and assertions, removed two now-obsolete tests) and passing
  • load-yaml-matrix unit tests — unchanged, still passing (confirms schema-agnosticism)
  • ansible-playbook --syntax-check ansible/deploy.yml
  • Real matrix trace-through against the actual vaults/hawkeye.yml / targets/hawkeye.ymlenv_refs/apps show up correctly in the generated JSON
  • Standalone bash dry-run of the add_line() collision-detection logic: clean multi-source merge, ordinary key collision, and a source colliding with the synthesized APPS line — all three behave as intended
  • pre-commit run --all-files
  • Real deploy to hawkeye exercising the new merge path (tracked in Normalize the hawkeye/rybbit deploy: configure prerequisites and get the first real deployment running #106)

Closes#108, closes#104.

Implements #108 and #104 together.
apps moves out of vault manifests (which now hold only env/secrets)
onto target manifests, where it belongs conceptually — it's a property
of the deployment, not of any one secrets source. This removes #104's
main blocker: with apps no longer rendered per-vault as an APPS= line,
merging multiple env sources is just concatenation with fail-loud
key-collision detection, no APPS-specific union logic needed.
env_ref becomes env_refs (list, required non-empty), mirroring
app_refs. ansible/deploy.yml decrypts every entry, merges them plus a
synthesized APPS= line (built from the target's own apps field, seeded
into the collision check first so a vault accidentally defining APPS
collides too), and fails loud on any duplicate key across sources -
matching the existing app-bundle-conflict philosophy.
No external consumers exist yet, so this is a breaking schema change:
no legacy support for vault-level apps or singular env_ref.
#103 (dedup the now four near-identical ref-resolution blocks) stays
deferred as its own follow-up, per plan.
@ineedjet
ineedjet requested a lite review from CopilotAugust 20, 2026 19:05

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Flightdeck’s deployment schema and pipeline to make app selection a property of deployment targets (not vaults) and to support merging multiple encrypted env sources per target, aligning env composition with existing multi-bundle app composition.

Changes:

  • Move apps out of vaults/*.yml into targets/*.yml and render APPS from the target’s desired app list.
  • Replace singular env_ref with required non-empty env_refs (list) and implement fail-loud env key collision detection when merging decrypted env assets.
  • Update deploy workflows and the encrypt-env action/docs/tests to match the new manifest and target schemas.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
vaults/hawkeye.ymlRemoves apps from the vault manifest so vaults contain only env/secrets.
targets/hawkeye.ymlAdds apps and switches env_refenv_refs for target-driven app selection and env composition.
ansible/deploy.ymlImplements looped download/decrypt/merge for flightdeck_env_refs with collision detection and synthesized APPS from flightdeck_apps.
README.mdDocuments new env_refs/apps target schema and updated Ansible/deploy usage examples.
AGENTS.mdUpdates repo agent guidance to reflect env merging and target-owned app selection.
.github/workflows/release.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy-shared.ymlAdds env-refs + apps inputs, validates them as non-empty JSON arrays, and forwards to Ansible as extra-vars.
.github/actions/encrypt-env/scripts/render-env.pyDrops apps from manifest schema and stops generating APPS in rendered env output.
.github/actions/encrypt-env/tests/test_render_env.pyUpdates tests to reflect removal of apps/APPS from the encrypt-env action output and schema validation.
.github/actions/encrypt-env/README.mdUpdates action documentation to remove apps and point app selection to targets.
Suppressed comments (2)

README.md:129

  • This multiple-env-source example also omits required playbook variables (flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, flightdeck_sops_age_key_file), so it won't run successfully as written.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env","<owner>/<other-secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'

README.md:118

  • This additional app-bundle example has the same issue as the earlier one: it doesn't pass required playbook variables like flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, and flightdeck_sops_age_key_file, so it will fail the playbook's initial assert.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest","<owner>/<extra-repo>@latest"],"flightdeck_apps":["traefik","rybbit"]}'

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadREADME.md Outdated
Comment on lines +90 to +93
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e flightdeck_env_ref=<owner>/<secrets-repo>@latest:<server>.sops.env \
-e '{"flightdeck_app_refs":["rubykatzen/flightdeck@latest"]}'
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'
Comment threadREADME.md Outdated
```

The `flightdeck_env_ref` format is `owner/repo@tag:asset`. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads the asset, decrypts it with the server-local SOPS age key (`flightdeck_sops_age_key_file`), links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
Each `flightdeck_env_refs` entry is in `owner/repo@tag:asset` format. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads and decrypts every entry with the server-local SOPS age key (`flightdeck_sops_age_key_file`), merges them into one `.env` alongside a synthesized `APPS` line built from `flightdeck_apps`, links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
…interface
deploy-shared.yml already exists specifically to hide flightdeck_*
Ansible variable names and -e JSON from callers, but the README's
"Ansible Deploy" section showed exactly that - three ansible-playbook
-e '{...}' examples, all of which actually fail at the playbook's
initial assert (missing flightdeck_app_ref/path/keep_releases/
sops_age_key_file), per Copilot's review on #110.
Replace it with a short pointer to deploy-shared.yml as the actual
interface and to the Vaults And Targets section for how multi-source
merging is configured, instead of patching three broken CLI examples
that shouldn't be a documented entry point in the first place.
Sign up for freeto 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.

Move desired apps from vault configuration to target configuration Support multiple env sources (env_refs) merged per target, like app_refs

2 participants

@ineedjet
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat: move apps to targets, support env_refs as a list by ineedjet · Pull Request #110 · rubykatzen/flightdeck · GitHub
Skip to content

feat: move apps to targets, support env_refs as a list - #110

Merged
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs
Aug 20, 2026
Merged

feat: move apps to targets, support env_refs as a list#110
ineedjet merged 2 commits into
mainfrom
feat/apps-in-targets-env-refs

Conversation

@ineedjet

Copy link
Copy Markdown
Collaborator

Summary

Implements #108 and #104 together, leaving #103 (ref-resolution dedup) as a separate follow-up.

  • apps moves out of vault manifests (which now hold only env/secrets) onto target manifests, where it belongs conceptually — it's a property of the deployment, not of any one secrets source.
  • env_ref becomes env_refs (list, required non-empty), mirroring app_refs.

Why together

apps living in vaults was #104's main blocker: an APPS=... line was rendered per-vault, so merging multiple env sources meant reconciling their APPS lists too (naive concatenation silently drops earlier sources' apps — dotenv parsing is last-value-wins on duplicate keys). Moving apps onto the target first removes that obstacle entirely: merging env_refs is now just concatenation of N decrypted sources plus one synthesized APPS= line built from the target's own apps field, with fail-loud collision detection across all of them — no APPS-specific union logic needed.

Cross-repo age-key distribution (the other obstacle #104 raised) is untouched and stays out of scope — not needed while every vault lives in this repo, encrypted for the same keys/hawkeye.pub recipient.

No external consumers exist yet, so this is a breaking schema change: no legacy support for vault-level apps or singular env_ref.

Schema changes

vaults/hawkeye.ymlapps dropped:

asset: hawkeye.sops.envkeys:
- hawkeyeenv:
APPS_DOMAIN: RUBYKATZEN_COM_DOMAIN...

targets/hawkeye.ymlapps added, env_refenv_refs:

flightdeck_ref: rubykatzen/flightdeck@latestenv_refs:
- rubykatzen/flightdeck@latest:hawkeye.sops.envapp_refs:
- rubykatzen/flightdeck@latestapps:
- traefik
- rybbithosts:
- rubykatzen-com@100.75.50.2credentials: ...

load-yaml-matrix needed zero changes — confirms it's genuinely schema-agnostic, fields just flow through.

ansible/deploy.yml

Replaces the single env pull/decrypt chain with a loop shaped like the existing app-bundle merge loop: for each ref in flightdeck_env_refs, download + sops decrypt, then append every line to the merged output via a small add_line() function that rejects any key already seen — fails loud with Env key conflicts with an existing source: $key. The "seen keys" set is seeded with a synthesized APPS={{ flightdeck_apps | join(',') }} line before the loop, so a vault accidentally emitting its own APPS collides too, uniformly, with no special-casing.

This is the fourth near-identical "download + resolve @latest" block in this file (after the machinery pull, the app-refs loop, and now this) — #103 tracks extracting a shared script, deliberately deferred rather than folded in here.

Verification

  • encrypt-env unit tests updated (dropped apps/APPS fixtures and assertions, removed two now-obsolete tests) and passing
  • load-yaml-matrix unit tests — unchanged, still passing (confirms schema-agnosticism)
  • ansible-playbook --syntax-check ansible/deploy.yml
  • Real matrix trace-through against the actual vaults/hawkeye.yml / targets/hawkeye.ymlenv_refs/apps show up correctly in the generated JSON
  • Standalone bash dry-run of the add_line() collision-detection logic: clean multi-source merge, ordinary key collision, and a source colliding with the synthesized APPS line — all three behave as intended
  • pre-commit run --all-files
  • Real deploy to hawkeye exercising the new merge path (tracked in Normalize the hawkeye/rybbit deploy: configure prerequisites and get the first real deployment running #106)

Closes#108, closes#104.

Implements #108 and #104 together.
apps moves out of vault manifests (which now hold only env/secrets)
onto target manifests, where it belongs conceptually — it's a property
of the deployment, not of any one secrets source. This removes #104's
main blocker: with apps no longer rendered per-vault as an APPS= line,
merging multiple env sources is just concatenation with fail-loud
key-collision detection, no APPS-specific union logic needed.
env_ref becomes env_refs (list, required non-empty), mirroring
app_refs. ansible/deploy.yml decrypts every entry, merges them plus a
synthesized APPS= line (built from the target's own apps field, seeded
into the collision check first so a vault accidentally defining APPS
collides too), and fails loud on any duplicate key across sources -
matching the existing app-bundle-conflict philosophy.
No external consumers exist yet, so this is a breaking schema change:
no legacy support for vault-level apps or singular env_ref.
#103 (dedup the now four near-identical ref-resolution blocks) stays
deferred as its own follow-up, per plan.
@ineedjet
ineedjet requested a lite review from CopilotAugust 20, 2026 19:05

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Flightdeck’s deployment schema and pipeline to make app selection a property of deployment targets (not vaults) and to support merging multiple encrypted env sources per target, aligning env composition with existing multi-bundle app composition.

Changes:

  • Move apps out of vaults/*.yml into targets/*.yml and render APPS from the target’s desired app list.
  • Replace singular env_ref with required non-empty env_refs (list) and implement fail-loud env key collision detection when merging decrypted env assets.
  • Update deploy workflows and the encrypt-env action/docs/tests to match the new manifest and target schemas.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
vaults/hawkeye.ymlRemoves apps from the vault manifest so vaults contain only env/secrets.
targets/hawkeye.ymlAdds apps and switches env_refenv_refs for target-driven app selection and env composition.
ansible/deploy.ymlImplements looped download/decrypt/merge for flightdeck_env_refs with collision detection and synthesized APPS from flightdeck_apps.
README.mdDocuments new env_refs/apps target schema and updated Ansible/deploy usage examples.
AGENTS.mdUpdates repo agent guidance to reflect env merging and target-owned app selection.
.github/workflows/release.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy.ymlPasses env-refs and apps through to deploy-shared.yml from the targets matrix.
.github/workflows/deploy-shared.ymlAdds env-refs + apps inputs, validates them as non-empty JSON arrays, and forwards to Ansible as extra-vars.
.github/actions/encrypt-env/scripts/render-env.pyDrops apps from manifest schema and stops generating APPS in rendered env output.
.github/actions/encrypt-env/tests/test_render_env.pyUpdates tests to reflect removal of apps/APPS from the encrypt-env action output and schema validation.
.github/actions/encrypt-env/README.mdUpdates action documentation to remove apps and point app selection to targets.
Suppressed comments (2)

README.md:129

  • This multiple-env-source example also omits required playbook variables (flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, flightdeck_sops_age_key_file), so it won't run successfully as written.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env","<owner>/<other-secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'

README.md:118

  • This additional app-bundle example has the same issue as the earlier one: it doesn't pass required playbook variables like flightdeck_app_ref, flightdeck_path, flightdeck_keep_releases, and flightdeck_sops_age_key_file, so it will fail the playbook's initial assert.
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest","<owner>/<extra-repo>@latest"],"flightdeck_apps":["traefik","rybbit"]}'

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadREADME.md Outdated
Comment on lines +90 to +93
ansible-playbook ansible/deploy.yml \
-i mainframe, \
-u root \
-e flightdeck_env_ref=<owner>/<secrets-repo>@latest:<server>.sops.env \
-e '{"flightdeck_app_refs":["rubykatzen/flightdeck@latest"]}'
-e '{"flightdeck_env_refs":["<owner>/<secrets-repo>@latest:<server>.sops.env"],"flightdeck_app_refs":["rubykatzen/flightdeck@latest"],"flightdeck_apps":["traefik","rybbit"]}'
Comment threadREADME.md Outdated
```

The `flightdeck_env_ref` format is `owner/repo@tag:asset`. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads the asset, decrypts it with the server-local SOPS age key (`flightdeck_sops_age_key_file`), links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
Each `flightdeck_env_refs` entry is in `owner/repo@tag:asset` format. Use an immutable semver tag for a pinned deploy, or `@latest` to resolve GitHub's latest release at deploy time. The playbook downloads and decrypts every entry with the server-local SOPS age key (`flightdeck_sops_age_key_file`), merges them into one `.env` alongside a synthesized `APPS` line built from `flightdeck_apps`, links shared `.env` and `apps-data` into a timestamped release, switches `current`, and runs `./deploy.sh`.
…interface
deploy-shared.yml already exists specifically to hide flightdeck_*
Ansible variable names and -e JSON from callers, but the README's
"Ansible Deploy" section showed exactly that - three ansible-playbook
-e '{...}' examples, all of which actually fail at the playbook's
initial assert (missing flightdeck_app_ref/path/keep_releases/
sops_age_key_file), per Copilot's review on #110.
Replace it with a short pointer to deploy-shared.yml as the actual
interface and to the Vaults And Targets section for how multi-source
merging is configured, instead of patching three broken CLI examples
that shouldn't be a documented entry point in the first place.
Sign up for freeto 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.

Move desired apps from vault configuration to target configuration Support multiple env sources (env_refs) merged per target, like app_refs

2 participants

@ineedjet