Skip to content

Support multiple env sources (env_refs) merged per target, like app_refs #104

Description

@ineedjet

Problem

targets/*.yml now supports app_refs (a list) to assemble a target's app
catalog from multiple independent bundles/repos. env_ref is still a single
ref — there's no equivalent way to compose a target's env vars from more
than one encrypted vault source, even though the same "compose config from
multiple independent repos" motivation applies to secrets/env as much as it
does to apps.

Why this isn't just "make it a list"

Unlike apps (file-based, namespaced by directory name, trivially
conflict-checked), merging multiple decrypted .sops.env sources has real
obstacles:

  1. APPS= is a generated aggregate, not a plain field. Each vault
    manifest renders its own APPS=... line from its own apps: list.
    Naively concatenating multiple decrypted .env files means the last
    APPS= line wins (dotenv parsing is last-value-wins on duplicate keys) —
    apps merged into apps/ via app_refs from earlier sources would
    silently never start, because they'd have dropped out of the effective
    APPS= value. Merging env sources correctly requires explicitly
    unioningAPPS= across all contributing vaults, not just concatenating
    files.
  2. Cross-repo encryption-recipient coordination.env_ref points at a
    SOPS-encrypted asset, encrypted for a specific set of age recipients
    (keys: in the vault manifest). For a third-party repo to contribute env
    vars to the same target, its author needs the target server's public age
    key (keys/hawkeye.pub today lives only in this repo) ahead of time and
    must explicitly encrypt for it. There's no existing mechanism for
    sharing/publishing that public key across repos. app_refs has no
    equivalent problem since app bundles aren't per-recipient encrypted.
  3. Ordinary key collisions (two sources both define, say,
    APPS_TIMEZONE) also need an explicit policy — silently overwrite, or
    fail loud like the existing app-name-conflict check in
    ansible/deploy.yml.

Proposed direction (not designed yet)

  • env_refs (list, mirroring app_refs) in targets/*.yml.
  • ansible/deploy.yml (or a new step) decrypts each source, unions the
    apps: lists before rendering APPS= (or unions the already-rendered
    APPS= values), and fails loud on any other key collision across sources
    — matching the existing app-conflict philosophy instead of introducing
    silent overwrite behavior.
  • Cross-repo key sharing needs its own decision — publish keys/*.pub as a
    discoverable asset/convention, or require the target repo owner to hand
    out the pubkey out-of-band.

Related

Surfaced during final review of #102, same session as #103 (ansible
ref-resolution dedup).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , '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" + '
      Support multiple env sources (env_refs) merged per target, like app_refs · Issue #104 · rubykatzen/flightdeck · GitHub
      Skip to content

      Support multiple env sources (env_refs) merged per target, like app_refs #104

      Description

      @ineedjet

      Problem

      targets/*.yml now supports app_refs (a list) to assemble a target's app
      catalog from multiple independent bundles/repos. env_ref is still a single
      ref — there's no equivalent way to compose a target's env vars from more
      than one encrypted vault source, even though the same "compose config from
      multiple independent repos" motivation applies to secrets/env as much as it
      does to apps.

      Why this isn't just "make it a list"

      Unlike apps (file-based, namespaced by directory name, trivially
      conflict-checked), merging multiple decrypted .sops.env sources has real
      obstacles:

      1. APPS= is a generated aggregate, not a plain field. Each vault
        manifest renders its own APPS=... line from its own apps: list.
        Naively concatenating multiple decrypted .env files means the last
        APPS= line wins (dotenv parsing is last-value-wins on duplicate keys) —
        apps merged into apps/ via app_refs from earlier sources would
        silently never start, because they'd have dropped out of the effective
        APPS= value. Merging env sources correctly requires explicitly
        unioningAPPS= across all contributing vaults, not just concatenating
        files.
      2. Cross-repo encryption-recipient coordination.env_ref points at a
        SOPS-encrypted asset, encrypted for a specific set of age recipients
        (keys: in the vault manifest). For a third-party repo to contribute env
        vars to the same target, its author needs the target server's public age
        key (keys/hawkeye.pub today lives only in this repo) ahead of time and
        must explicitly encrypt for it. There's no existing mechanism for
        sharing/publishing that public key across repos. app_refs has no
        equivalent problem since app bundles aren't per-recipient encrypted.
      3. Ordinary key collisions (two sources both define, say,
        APPS_TIMEZONE) also need an explicit policy — silently overwrite, or
        fail loud like the existing app-name-conflict check in
        ansible/deploy.yml.

      Proposed direction (not designed yet)

      • env_refs (list, mirroring app_refs) in targets/*.yml.
      • ansible/deploy.yml (or a new step) decrypts each source, unions the
        apps: lists before rendering APPS= (or unions the already-rendered
        APPS= values), and fails loud on any other key collision across sources
        — matching the existing app-conflict philosophy instead of introducing
        silent overwrite behavior.
      • Cross-repo key sharing needs its own decision — publish keys/*.pub as a
        discoverable asset/convention, or require the target repo owner to hand
        out the pubkey out-of-band.

      Related

      Surfaced during final review of #102, same session as #103 (ansible
      ref-resolution dedup).

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , '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('^' + ".*" + ' Support multiple env sources (env_refs) merged per target, like app_refs · Issue #104 · rubykatzen/flightdeck · GitHub
          Skip to content

          Support multiple env sources (env_refs) merged per target, like app_refs #104

          Description

          @ineedjet

          Problem

          targets/*.yml now supports app_refs (a list) to assemble a target's app
          catalog from multiple independent bundles/repos. env_ref is still a single
          ref — there's no equivalent way to compose a target's env vars from more
          than one encrypted vault source, even though the same "compose config from
          multiple independent repos" motivation applies to secrets/env as much as it
          does to apps.

          Why this isn't just "make it a list"

          Unlike apps (file-based, namespaced by directory name, trivially
          conflict-checked), merging multiple decrypted .sops.env sources has real
          obstacles:

          1. APPS= is a generated aggregate, not a plain field. Each vault
            manifest renders its own APPS=... line from its own apps: list.
            Naively concatenating multiple decrypted .env files means the last
            APPS= line wins (dotenv parsing is last-value-wins on duplicate keys) —
            apps merged into apps/ via app_refs from earlier sources would
            silently never start, because they'd have dropped out of the effective
            APPS= value. Merging env sources correctly requires explicitly
            unioningAPPS= across all contributing vaults, not just concatenating
            files.
          2. Cross-repo encryption-recipient coordination.env_ref points at a
            SOPS-encrypted asset, encrypted for a specific set of age recipients
            (keys: in the vault manifest). For a third-party repo to contribute env
            vars to the same target, its author needs the target server's public age
            key (keys/hawkeye.pub today lives only in this repo) ahead of time and
            must explicitly encrypt for it. There's no existing mechanism for
            sharing/publishing that public key across repos. app_refs has no
            equivalent problem since app bundles aren't per-recipient encrypted.
          3. Ordinary key collisions (two sources both define, say,
            APPS_TIMEZONE) also need an explicit policy — silently overwrite, or
            fail loud like the existing app-name-conflict check in
            ansible/deploy.yml.

          Proposed direction (not designed yet)

          • env_refs (list, mirroring app_refs) in targets/*.yml.
          • ansible/deploy.yml (or a new step) decrypts each source, unions the
            apps: lists before rendering APPS= (or unions the already-rendered
            APPS= values), and fails loud on any other key collision across sources
            — matching the existing app-conflict philosophy instead of introducing
            silent overwrite behavior.
          • Cross-repo key sharing needs its own decision — publish keys/*.pub as a
            discoverable asset/convention, or require the target repo owner to hand
            out the pubkey out-of-band.

          Related

          Surfaced during final review of #102, same session as #103 (ansible
          ref-resolution dedup).

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , '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('^' + ".*" + ' Support multiple env sources (env_refs) merged per target, like app_refs · Issue #104 · rubykatzen/flightdeck · GitHub
              Skip to content

              Support multiple env sources (env_refs) merged per target, like app_refs #104

              Description

              @ineedjet

              Problem

              targets/*.yml now supports app_refs (a list) to assemble a target's app
              catalog from multiple independent bundles/repos. env_ref is still a single
              ref — there's no equivalent way to compose a target's env vars from more
              than one encrypted vault source, even though the same "compose config from
              multiple independent repos" motivation applies to secrets/env as much as it
              does to apps.

              Why this isn't just "make it a list"

              Unlike apps (file-based, namespaced by directory name, trivially
              conflict-checked), merging multiple decrypted .sops.env sources has real
              obstacles:

              1. APPS= is a generated aggregate, not a plain field. Each vault
                manifest renders its own APPS=... line from its own apps: list.
                Naively concatenating multiple decrypted .env files means the last
                APPS= line wins (dotenv parsing is last-value-wins on duplicate keys) —
                apps merged into apps/ via app_refs from earlier sources would
                silently never start, because they'd have dropped out of the effective
                APPS= value. Merging env sources correctly requires explicitly
                unioningAPPS= across all contributing vaults, not just concatenating
                files.
              2. Cross-repo encryption-recipient coordination.env_ref points at a
                SOPS-encrypted asset, encrypted for a specific set of age recipients
                (keys: in the vault manifest). For a third-party repo to contribute env
                vars to the same target, its author needs the target server's public age
                key (keys/hawkeye.pub today lives only in this repo) ahead of time and
                must explicitly encrypt for it. There's no existing mechanism for
                sharing/publishing that public key across repos. app_refs has no
                equivalent problem since app bundles aren't per-recipient encrypted.
              3. Ordinary key collisions (two sources both define, say,
                APPS_TIMEZONE) also need an explicit policy — silently overwrite, or
                fail loud like the existing app-name-conflict check in
                ansible/deploy.yml.

              Proposed direction (not designed yet)

              • env_refs (list, mirroring app_refs) in targets/*.yml.
              • ansible/deploy.yml (or a new step) decrypts each source, unions the
                apps: lists before rendering APPS= (or unions the already-rendered
                APPS= values), and fails loud on any other key collision across sources
                — matching the existing app-conflict philosophy instead of introducing
                silent overwrite behavior.
              • Cross-repo key sharing needs its own decision — publish keys/*.pub as a
                discoverable asset/convention, or require the target repo owner to hand
                out the pubkey out-of-band.

              Related

              Surfaced during final review of #102, same session as #103 (ansible
              ref-resolution dedup).

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , '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" + ' Support multiple env sources (env_refs) merged per target, like app_refs · Issue #104 · rubykatzen/flightdeck · GitHub
                  Skip to content

                  Support multiple env sources (env_refs) merged per target, like app_refs #104

                  Description

                  @ineedjet

                  Problem

                  targets/*.yml now supports app_refs (a list) to assemble a target's app
                  catalog from multiple independent bundles/repos. env_ref is still a single
                  ref — there's no equivalent way to compose a target's env vars from more
                  than one encrypted vault source, even though the same "compose config from
                  multiple independent repos" motivation applies to secrets/env as much as it
                  does to apps.

                  Why this isn't just "make it a list"

                  Unlike apps (file-based, namespaced by directory name, trivially
                  conflict-checked), merging multiple decrypted .sops.env sources has real
                  obstacles:

                  1. APPS= is a generated aggregate, not a plain field. Each vault
                    manifest renders its own APPS=... line from its own apps: list.
                    Naively concatenating multiple decrypted .env files means the last
                    APPS= line wins (dotenv parsing is last-value-wins on duplicate keys) —
                    apps merged into apps/ via app_refs from earlier sources would
                    silently never start, because they'd have dropped out of the effective
                    APPS= value. Merging env sources correctly requires explicitly
                    unioningAPPS= across all contributing vaults, not just concatenating
                    files.
                  2. Cross-repo encryption-recipient coordination.env_ref points at a
                    SOPS-encrypted asset, encrypted for a specific set of age recipients
                    (keys: in the vault manifest). For a third-party repo to contribute env
                    vars to the same target, its author needs the target server's public age
                    key (keys/hawkeye.pub today lives only in this repo) ahead of time and
                    must explicitly encrypt for it. There's no existing mechanism for
                    sharing/publishing that public key across repos. app_refs has no
                    equivalent problem since app bundles aren't per-recipient encrypted.
                  3. Ordinary key collisions (two sources both define, say,
                    APPS_TIMEZONE) also need an explicit policy — silently overwrite, or
                    fail loud like the existing app-name-conflict check in
                    ansible/deploy.yml.

                  Proposed direction (not designed yet)

                  • env_refs (list, mirroring app_refs) in targets/*.yml.
                  • ansible/deploy.yml (or a new step) decrypts each source, unions the
                    apps: lists before rendering APPS= (or unions the already-rendered
                    APPS= values), and fails loud on any other key collision across sources
                    — matching the existing app-conflict philosophy instead of introducing
                    silent overwrite behavior.
                  • Cross-repo key sharing needs its own decision — publish keys/*.pub as a
                    discoverable asset/convention, or require the target repo owner to hand
                    out the pubkey out-of-band.

                  Related

                  Surfaced during final review of #102, same session as #103 (ansible
                  ref-resolution dedup).

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    No labels
                    No labels

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , '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('^' + ".*" + ' Support multiple env sources (env_refs) merged per target, like app_refs · Issue #104 · rubykatzen/flightdeck · GitHub
                      Skip to content

                      Support multiple env sources (env_refs) merged per target, like app_refs #104

                      Description

                      @ineedjet

                      Problem

                      targets/*.yml now supports app_refs (a list) to assemble a target's app
                      catalog from multiple independent bundles/repos. env_ref is still a single
                      ref — there's no equivalent way to compose a target's env vars from more
                      than one encrypted vault source, even though the same "compose config from
                      multiple independent repos" motivation applies to secrets/env as much as it
                      does to apps.

                      Why this isn't just "make it a list"

                      Unlike apps (file-based, namespaced by directory name, trivially
                      conflict-checked), merging multiple decrypted .sops.env sources has real
                      obstacles:

                      1. APPS= is a generated aggregate, not a plain field. Each vault
                        manifest renders its own APPS=... line from its own apps: list.
                        Naively concatenating multiple decrypted .env files means the last
                        APPS= line wins (dotenv parsing is last-value-wins on duplicate keys) —
                        apps merged into apps/ via app_refs from earlier sources would
                        silently never start, because they'd have dropped out of the effective
                        APPS= value. Merging env sources correctly requires explicitly
                        unioningAPPS= across all contributing vaults, not just concatenating
                        files.
                      2. Cross-repo encryption-recipient coordination.env_ref points at a
                        SOPS-encrypted asset, encrypted for a specific set of age recipients
                        (keys: in the vault manifest). For a third-party repo to contribute env
                        vars to the same target, its author needs the target server's public age
                        key (keys/hawkeye.pub today lives only in this repo) ahead of time and
                        must explicitly encrypt for it. There's no existing mechanism for
                        sharing/publishing that public key across repos. app_refs has no
                        equivalent problem since app bundles aren't per-recipient encrypted.
                      3. Ordinary key collisions (two sources both define, say,
                        APPS_TIMEZONE) also need an explicit policy — silently overwrite, or
                        fail loud like the existing app-name-conflict check in
                        ansible/deploy.yml.

                      Proposed direction (not designed yet)

                      • env_refs (list, mirroring app_refs) in targets/*.yml.
                      • ansible/deploy.yml (or a new step) decrypts each source, unions the
                        apps: lists before rendering APPS= (or unions the already-rendered
                        APPS= values), and fails loud on any other key collision across sources
                        — matching the existing app-conflict philosophy instead of introducing
                        silent overwrite behavior.
                      • Cross-repo key sharing needs its own decision — publish keys/*.pub as a
                        discoverable asset/convention, or require the target repo owner to hand
                        out the pubkey out-of-band.

                      Related

                      Surfaced during final review of #102, same session as #103 (ansible
                      ref-resolution dedup).

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        No labels
                        No labels

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , '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('^' + ".*" + ' Support multiple env sources (env_refs) merged per target, like app_refs · Issue #104 · rubykatzen/flightdeck · GitHub
                          Skip to content

                          Support multiple env sources (env_refs) merged per target, like app_refs #104

                          Description

                          @ineedjet

                          Problem

                          targets/*.yml now supports app_refs (a list) to assemble a target's app
                          catalog from multiple independent bundles/repos. env_ref is still a single
                          ref — there's no equivalent way to compose a target's env vars from more
                          than one encrypted vault source, even though the same "compose config from
                          multiple independent repos" motivation applies to secrets/env as much as it
                          does to apps.

                          Why this isn't just "make it a list"

                          Unlike apps (file-based, namespaced by directory name, trivially
                          conflict-checked), merging multiple decrypted .sops.env sources has real
                          obstacles:

                          1. APPS= is a generated aggregate, not a plain field. Each vault
                            manifest renders its own APPS=... line from its own apps: list.
                            Naively concatenating multiple decrypted .env files means the last
                            APPS= line wins (dotenv parsing is last-value-wins on duplicate keys) —
                            apps merged into apps/ via app_refs from earlier sources would
                            silently never start, because they'd have dropped out of the effective
                            APPS= value. Merging env sources correctly requires explicitly
                            unioningAPPS= across all contributing vaults, not just concatenating
                            files.
                          2. Cross-repo encryption-recipient coordination.env_ref points at a
                            SOPS-encrypted asset, encrypted for a specific set of age recipients
                            (keys: in the vault manifest). For a third-party repo to contribute env
                            vars to the same target, its author needs the target server's public age
                            key (keys/hawkeye.pub today lives only in this repo) ahead of time and
                            must explicitly encrypt for it. There's no existing mechanism for
                            sharing/publishing that public key across repos. app_refs has no
                            equivalent problem since app bundles aren't per-recipient encrypted.
                          3. Ordinary key collisions (two sources both define, say,
                            APPS_TIMEZONE) also need an explicit policy — silently overwrite, or
                            fail loud like the existing app-name-conflict check in
                            ansible/deploy.yml.

                          Proposed direction (not designed yet)

                          • env_refs (list, mirroring app_refs) in targets/*.yml.
                          • ansible/deploy.yml (or a new step) decrypts each source, unions the
                            apps: lists before rendering APPS= (or unions the already-rendered
                            APPS= values), and fails loud on any other key collision across sources
                            — matching the existing app-conflict philosophy instead of introducing
                            silent overwrite behavior.
                          • Cross-repo key sharing needs its own decision — publish keys/*.pub as a
                            discoverable asset/convention, or require the target repo owner to hand
                            out the pubkey out-of-band.

                          Related

                          Surfaced during final review of #102, same session as #103 (ansible
                          ref-resolution dedup).

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            No labels
                            No labels

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , '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); } })(); })(); Support multiple env sources (env_refs) merged per target, like app_refs · Issue #104 · rubykatzen/flightdeck · GitHub
                              Skip to content

                              Support multiple env sources (env_refs) merged per target, like app_refs #104

                              Description

                              @ineedjet

                              Problem

                              targets/*.yml now supports app_refs (a list) to assemble a target's app
                              catalog from multiple independent bundles/repos. env_ref is still a single
                              ref — there's no equivalent way to compose a target's env vars from more
                              than one encrypted vault source, even though the same "compose config from
                              multiple independent repos" motivation applies to secrets/env as much as it
                              does to apps.

                              Why this isn't just "make it a list"

                              Unlike apps (file-based, namespaced by directory name, trivially
                              conflict-checked), merging multiple decrypted .sops.env sources has real
                              obstacles:

                              1. APPS= is a generated aggregate, not a plain field. Each vault
                                manifest renders its own APPS=... line from its own apps: list.
                                Naively concatenating multiple decrypted .env files means the last
                                APPS= line wins (dotenv parsing is last-value-wins on duplicate keys) —
                                apps merged into apps/ via app_refs from earlier sources would
                                silently never start, because they'd have dropped out of the effective
                                APPS= value. Merging env sources correctly requires explicitly
                                unioningAPPS= across all contributing vaults, not just concatenating
                                files.
                              2. Cross-repo encryption-recipient coordination.env_ref points at a
                                SOPS-encrypted asset, encrypted for a specific set of age recipients
                                (keys: in the vault manifest). For a third-party repo to contribute env
                                vars to the same target, its author needs the target server's public age
                                key (keys/hawkeye.pub today lives only in this repo) ahead of time and
                                must explicitly encrypt for it. There's no existing mechanism for
                                sharing/publishing that public key across repos. app_refs has no
                                equivalent problem since app bundles aren't per-recipient encrypted.
                              3. Ordinary key collisions (two sources both define, say,
                                APPS_TIMEZONE) also need an explicit policy — silently overwrite, or
                                fail loud like the existing app-name-conflict check in
                                ansible/deploy.yml.

                              Proposed direction (not designed yet)

                              • env_refs (list, mirroring app_refs) in targets/*.yml.
                              • ansible/deploy.yml (or a new step) decrypts each source, unions the
                                apps: lists before rendering APPS= (or unions the already-rendered
                                APPS= values), and fails loud on any other key collision across sources
                                — matching the existing app-conflict philosophy instead of introducing
                                silent overwrite behavior.
                              • Cross-repo key sharing needs its own decision — publish keys/*.pub as a
                                discoverable asset/convention, or require the target repo owner to hand
                                out the pubkey out-of-band.

                              Related

                              Surfaced during final review of #102, same session as #103 (ansible
                              ref-resolution dedup).

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                No labels
                                No labels

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions