Skip to content

feat: Add ZeroSSL support as alternative ACME CA #2

Description

@ameistad

Add support for ZeroSSL, configurable per-domain via haloy.yaml. Let's Encrypt remains the default. EAB credentials for ZeroSSL use the existing ValueSource pattern, so they can be provided inline, via env vars, or via built-in secret management

Proposed Changes:

Add three new fields to config.Domain:

  • acme_provider (string): "letsencrypt" (default) or "zerossl"
  • eab_kid (ValueSource): External Account Binding Key ID for ZeroSSL
  • eab_hmac (ValueSource): External Account Binding HMAC key for ZeroSSL
domains:
- domain: example.comacme_provider: zerossleab_kid:
from:
secret: "onepassword:zerossl_creds.eab_kid"eab_hmac:
from:
secret: "onepassword:zerossl_creds.eab_hmac"# Using environment variablesdomains:
- domain: example.comacme_provider: zerossleab_kid:
from:
env: ZEROSSL_EAB_KIDeab_hmac:
from:
env: ZEROSSL_EAB_HMAC# Inline domains:
- domain: example.comacme_provider: zerossleab_kid:
value: "abc123"eab_hmac:
value: "hmac-key-here"

Implementation Plan

Config layer (internal/config/)

  • deploy_config.go: Add ACMEProvider, EABKeyID, EABHMACKey fields to Domain struct. Update Domain.Validate() to enforce that ZeroSSL requires both EAB fields and that Let's Encrypt rejects them.
  • labels.go: Add label format strings (dev.haloy.domain.%d.acme-provider, dev.haloy.domain.%d.eab-kid, dev.haloy.domain.%d.eab-hmac). Update ToLabels() and ParseContainerLabels() to serialize/deserialize the new fields.

Secret resolution (internal/configloader/)

  • resolve_secrets.go: Add domain EAB ValueSource fields to gatherValueSources() and gatherTargetValueSources() so they go through the same resolution pipeline as env vars and API tokens.

Certificate management (internal/haloyd/)

  • certificates.go:
    • Add ZeroSSL ACME directory constant (https://acme.zerossl.com/v2/DV90)
    • Extend CertificatesDomain with ACMEProvider, EABKeyID, EABHMACKey fields
    • Refactor ACMEClientManager to support per-provider ACME clients (keyed by provider name)
    • Support EAB registration for ZeroSSL via acme.Client.Register with ExternalAccountBinding
    • Separate account storage per provider (accounts/letsencrypt/account.json, accounts/zerossl/account.json)
    • Update ObtainCertificate to accept and use provider info
    • Update hasConfigurationChanged to detect provider changes (trigger re-issuance when provider switches)
  • deployments.go: Update GetCertificateDomains() to populate new CertificatesDomain fields from parsed container labels.
  • updater.go: Extended domain info flows through naturally since GetCertificateDomains populates it.

Config validation

  • acme_provider: "zerossl" requires both eab_kid and eab_hmac
  • acme_provider: "" or "letsencrypt" rejects EAB fields
  • Unknown provider values are rejected

Files to modify

FileChange
internal/config/deploy_config.goAdd fields to Domain, update validation
internal/config/labels.goAdd label constants, update ToLabels/ParseContainerLabels
internal/configloader/resolve_secrets.goGather EAB ValueSources from domains
internal/haloyd/certificates.goZeroSSL directory, EAB registration, per-provider clients
internal/haloyd/deployments.goPopulate provider/EAB in GetCertificateDomains
internal/haloyd/updater.goMinor comment update

Testing

  • Unit tests for Domain validation with new fields (valid/invalid combos)
  • Unit tests for label round-trip (ToLabels -> ParseContainerLabels) with EAB fields
  • Unit tests for gatherValueSources including domain EAB fields
  • Unit tests for provider-specific ACME client creation
  • Verify existing Let's Encrypt flow is unaffected (default, no EAB)

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" + '
    feat: Add ZeroSSL support as alternative ACME CA · Issue #2 · haloydev/haloy · GitHub
    Skip to content

    feat: Add ZeroSSL support as alternative ACME CA #2

    Description

    @ameistad

    Add support for ZeroSSL, configurable per-domain via haloy.yaml. Let's Encrypt remains the default. EAB credentials for ZeroSSL use the existing ValueSource pattern, so they can be provided inline, via env vars, or via built-in secret management

    Proposed Changes:

    Add three new fields to config.Domain:

    • acme_provider (string): "letsencrypt" (default) or "zerossl"
    • eab_kid (ValueSource): External Account Binding Key ID for ZeroSSL
    • eab_hmac (ValueSource): External Account Binding HMAC key for ZeroSSL
    domains:
    - domain: example.comacme_provider: zerossleab_kid:
    from:
    secret: "onepassword:zerossl_creds.eab_kid"eab_hmac:
    from:
    secret: "onepassword:zerossl_creds.eab_hmac"# Using environment variablesdomains:
    - domain: example.comacme_provider: zerossleab_kid:
    from:
    env: ZEROSSL_EAB_KIDeab_hmac:
    from:
    env: ZEROSSL_EAB_HMAC# Inline domains:
    - domain: example.comacme_provider: zerossleab_kid:
    value: "abc123"eab_hmac:
    value: "hmac-key-here"

    Implementation Plan

    Config layer (internal/config/)

    • deploy_config.go: Add ACMEProvider, EABKeyID, EABHMACKey fields to Domain struct. Update Domain.Validate() to enforce that ZeroSSL requires both EAB fields and that Let's Encrypt rejects them.
    • labels.go: Add label format strings (dev.haloy.domain.%d.acme-provider, dev.haloy.domain.%d.eab-kid, dev.haloy.domain.%d.eab-hmac). Update ToLabels() and ParseContainerLabels() to serialize/deserialize the new fields.

    Secret resolution (internal/configloader/)

    • resolve_secrets.go: Add domain EAB ValueSource fields to gatherValueSources() and gatherTargetValueSources() so they go through the same resolution pipeline as env vars and API tokens.

    Certificate management (internal/haloyd/)

    • certificates.go:
      • Add ZeroSSL ACME directory constant (https://acme.zerossl.com/v2/DV90)
      • Extend CertificatesDomain with ACMEProvider, EABKeyID, EABHMACKey fields
      • Refactor ACMEClientManager to support per-provider ACME clients (keyed by provider name)
      • Support EAB registration for ZeroSSL via acme.Client.Register with ExternalAccountBinding
      • Separate account storage per provider (accounts/letsencrypt/account.json, accounts/zerossl/account.json)
      • Update ObtainCertificate to accept and use provider info
      • Update hasConfigurationChanged to detect provider changes (trigger re-issuance when provider switches)
    • deployments.go: Update GetCertificateDomains() to populate new CertificatesDomain fields from parsed container labels.
    • updater.go: Extended domain info flows through naturally since GetCertificateDomains populates it.

    Config validation

    • acme_provider: "zerossl" requires both eab_kid and eab_hmac
    • acme_provider: "" or "letsencrypt" rejects EAB fields
    • Unknown provider values are rejected

    Files to modify

    FileChange
    internal/config/deploy_config.goAdd fields to Domain, update validation
    internal/config/labels.goAdd label constants, update ToLabels/ParseContainerLabels
    internal/configloader/resolve_secrets.goGather EAB ValueSources from domains
    internal/haloyd/certificates.goZeroSSL directory, EAB registration, per-provider clients
    internal/haloyd/deployments.goPopulate provider/EAB in GetCertificateDomains
    internal/haloyd/updater.goMinor comment update

    Testing

    • Unit tests for Domain validation with new fields (valid/invalid combos)
    • Unit tests for label round-trip (ToLabels -> ParseContainerLabels) with EAB fields
    • Unit tests for gatherValueSources including domain EAB fields
    • Unit tests for provider-specific ACME client creation
    • Verify existing Let's Encrypt flow is unaffected (default, no EAB)

    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('^' + ".*" + ' feat: Add ZeroSSL support as alternative ACME CA · Issue #2 · haloydev/haloy · GitHub
      Skip to content

      feat: Add ZeroSSL support as alternative ACME CA #2

      Description

      @ameistad

      Add support for ZeroSSL, configurable per-domain via haloy.yaml. Let's Encrypt remains the default. EAB credentials for ZeroSSL use the existing ValueSource pattern, so they can be provided inline, via env vars, or via built-in secret management

      Proposed Changes:

      Add three new fields to config.Domain:

      • acme_provider (string): "letsencrypt" (default) or "zerossl"
      • eab_kid (ValueSource): External Account Binding Key ID for ZeroSSL
      • eab_hmac (ValueSource): External Account Binding HMAC key for ZeroSSL
      domains:
      - domain: example.comacme_provider: zerossleab_kid:
      from:
      secret: "onepassword:zerossl_creds.eab_kid"eab_hmac:
      from:
      secret: "onepassword:zerossl_creds.eab_hmac"# Using environment variablesdomains:
      - domain: example.comacme_provider: zerossleab_kid:
      from:
      env: ZEROSSL_EAB_KIDeab_hmac:
      from:
      env: ZEROSSL_EAB_HMAC# Inline domains:
      - domain: example.comacme_provider: zerossleab_kid:
      value: "abc123"eab_hmac:
      value: "hmac-key-here"

      Implementation Plan

      Config layer (internal/config/)

      • deploy_config.go: Add ACMEProvider, EABKeyID, EABHMACKey fields to Domain struct. Update Domain.Validate() to enforce that ZeroSSL requires both EAB fields and that Let's Encrypt rejects them.
      • labels.go: Add label format strings (dev.haloy.domain.%d.acme-provider, dev.haloy.domain.%d.eab-kid, dev.haloy.domain.%d.eab-hmac). Update ToLabels() and ParseContainerLabels() to serialize/deserialize the new fields.

      Secret resolution (internal/configloader/)

      • resolve_secrets.go: Add domain EAB ValueSource fields to gatherValueSources() and gatherTargetValueSources() so they go through the same resolution pipeline as env vars and API tokens.

      Certificate management (internal/haloyd/)

      • certificates.go:
        • Add ZeroSSL ACME directory constant (https://acme.zerossl.com/v2/DV90)
        • Extend CertificatesDomain with ACMEProvider, EABKeyID, EABHMACKey fields
        • Refactor ACMEClientManager to support per-provider ACME clients (keyed by provider name)
        • Support EAB registration for ZeroSSL via acme.Client.Register with ExternalAccountBinding
        • Separate account storage per provider (accounts/letsencrypt/account.json, accounts/zerossl/account.json)
        • Update ObtainCertificate to accept and use provider info
        • Update hasConfigurationChanged to detect provider changes (trigger re-issuance when provider switches)
      • deployments.go: Update GetCertificateDomains() to populate new CertificatesDomain fields from parsed container labels.
      • updater.go: Extended domain info flows through naturally since GetCertificateDomains populates it.

      Config validation

      • acme_provider: "zerossl" requires both eab_kid and eab_hmac
      • acme_provider: "" or "letsencrypt" rejects EAB fields
      • Unknown provider values are rejected

      Files to modify

      FileChange
      internal/config/deploy_config.goAdd fields to Domain, update validation
      internal/config/labels.goAdd label constants, update ToLabels/ParseContainerLabels
      internal/configloader/resolve_secrets.goGather EAB ValueSources from domains
      internal/haloyd/certificates.goZeroSSL directory, EAB registration, per-provider clients
      internal/haloyd/deployments.goPopulate provider/EAB in GetCertificateDomains
      internal/haloyd/updater.goMinor comment update

      Testing

      • Unit tests for Domain validation with new fields (valid/invalid combos)
      • Unit tests for label round-trip (ToLabels -> ParseContainerLabels) with EAB fields
      • Unit tests for gatherValueSources including domain EAB fields
      • Unit tests for provider-specific ACME client creation
      • Verify existing Let's Encrypt flow is unaffected (default, no EAB)

      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('^' + ".*" + ' feat: Add ZeroSSL support as alternative ACME CA · Issue #2 · haloydev/haloy · GitHub
        Skip to content

        feat: Add ZeroSSL support as alternative ACME CA #2

        Description

        @ameistad

        Add support for ZeroSSL, configurable per-domain via haloy.yaml. Let's Encrypt remains the default. EAB credentials for ZeroSSL use the existing ValueSource pattern, so they can be provided inline, via env vars, or via built-in secret management

        Proposed Changes:

        Add three new fields to config.Domain:

        • acme_provider (string): "letsencrypt" (default) or "zerossl"
        • eab_kid (ValueSource): External Account Binding Key ID for ZeroSSL
        • eab_hmac (ValueSource): External Account Binding HMAC key for ZeroSSL
        domains:
        - domain: example.comacme_provider: zerossleab_kid:
        from:
        secret: "onepassword:zerossl_creds.eab_kid"eab_hmac:
        from:
        secret: "onepassword:zerossl_creds.eab_hmac"# Using environment variablesdomains:
        - domain: example.comacme_provider: zerossleab_kid:
        from:
        env: ZEROSSL_EAB_KIDeab_hmac:
        from:
        env: ZEROSSL_EAB_HMAC# Inline domains:
        - domain: example.comacme_provider: zerossleab_kid:
        value: "abc123"eab_hmac:
        value: "hmac-key-here"

        Implementation Plan

        Config layer (internal/config/)

        • deploy_config.go: Add ACMEProvider, EABKeyID, EABHMACKey fields to Domain struct. Update Domain.Validate() to enforce that ZeroSSL requires both EAB fields and that Let's Encrypt rejects them.
        • labels.go: Add label format strings (dev.haloy.domain.%d.acme-provider, dev.haloy.domain.%d.eab-kid, dev.haloy.domain.%d.eab-hmac). Update ToLabels() and ParseContainerLabels() to serialize/deserialize the new fields.

        Secret resolution (internal/configloader/)

        • resolve_secrets.go: Add domain EAB ValueSource fields to gatherValueSources() and gatherTargetValueSources() so they go through the same resolution pipeline as env vars and API tokens.

        Certificate management (internal/haloyd/)

        • certificates.go:
          • Add ZeroSSL ACME directory constant (https://acme.zerossl.com/v2/DV90)
          • Extend CertificatesDomain with ACMEProvider, EABKeyID, EABHMACKey fields
          • Refactor ACMEClientManager to support per-provider ACME clients (keyed by provider name)
          • Support EAB registration for ZeroSSL via acme.Client.Register with ExternalAccountBinding
          • Separate account storage per provider (accounts/letsencrypt/account.json, accounts/zerossl/account.json)
          • Update ObtainCertificate to accept and use provider info
          • Update hasConfigurationChanged to detect provider changes (trigger re-issuance when provider switches)
        • deployments.go: Update GetCertificateDomains() to populate new CertificatesDomain fields from parsed container labels.
        • updater.go: Extended domain info flows through naturally since GetCertificateDomains populates it.

        Config validation

        • acme_provider: "zerossl" requires both eab_kid and eab_hmac
        • acme_provider: "" or "letsencrypt" rejects EAB fields
        • Unknown provider values are rejected

        Files to modify

        FileChange
        internal/config/deploy_config.goAdd fields to Domain, update validation
        internal/config/labels.goAdd label constants, update ToLabels/ParseContainerLabels
        internal/configloader/resolve_secrets.goGather EAB ValueSources from domains
        internal/haloyd/certificates.goZeroSSL directory, EAB registration, per-provider clients
        internal/haloyd/deployments.goPopulate provider/EAB in GetCertificateDomains
        internal/haloyd/updater.goMinor comment update

        Testing

        • Unit tests for Domain validation with new fields (valid/invalid combos)
        • Unit tests for label round-trip (ToLabels -> ParseContainerLabels) with EAB fields
        • Unit tests for gatherValueSources including domain EAB fields
        • Unit tests for provider-specific ACME client creation
        • Verify existing Let's Encrypt flow is unaffected (default, no EAB)

        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" + ' feat: Add ZeroSSL support as alternative ACME CA · Issue #2 · haloydev/haloy · GitHub
          Skip to content

          feat: Add ZeroSSL support as alternative ACME CA #2

          Description

          @ameistad

          Add support for ZeroSSL, configurable per-domain via haloy.yaml. Let's Encrypt remains the default. EAB credentials for ZeroSSL use the existing ValueSource pattern, so they can be provided inline, via env vars, or via built-in secret management

          Proposed Changes:

          Add three new fields to config.Domain:

          • acme_provider (string): "letsencrypt" (default) or "zerossl"
          • eab_kid (ValueSource): External Account Binding Key ID for ZeroSSL
          • eab_hmac (ValueSource): External Account Binding HMAC key for ZeroSSL
          domains:
          - domain: example.comacme_provider: zerossleab_kid:
          from:
          secret: "onepassword:zerossl_creds.eab_kid"eab_hmac:
          from:
          secret: "onepassword:zerossl_creds.eab_hmac"# Using environment variablesdomains:
          - domain: example.comacme_provider: zerossleab_kid:
          from:
          env: ZEROSSL_EAB_KIDeab_hmac:
          from:
          env: ZEROSSL_EAB_HMAC# Inline domains:
          - domain: example.comacme_provider: zerossleab_kid:
          value: "abc123"eab_hmac:
          value: "hmac-key-here"

          Implementation Plan

          Config layer (internal/config/)

          • deploy_config.go: Add ACMEProvider, EABKeyID, EABHMACKey fields to Domain struct. Update Domain.Validate() to enforce that ZeroSSL requires both EAB fields and that Let's Encrypt rejects them.
          • labels.go: Add label format strings (dev.haloy.domain.%d.acme-provider, dev.haloy.domain.%d.eab-kid, dev.haloy.domain.%d.eab-hmac). Update ToLabels() and ParseContainerLabels() to serialize/deserialize the new fields.

          Secret resolution (internal/configloader/)

          • resolve_secrets.go: Add domain EAB ValueSource fields to gatherValueSources() and gatherTargetValueSources() so they go through the same resolution pipeline as env vars and API tokens.

          Certificate management (internal/haloyd/)

          • certificates.go:
            • Add ZeroSSL ACME directory constant (https://acme.zerossl.com/v2/DV90)
            • Extend CertificatesDomain with ACMEProvider, EABKeyID, EABHMACKey fields
            • Refactor ACMEClientManager to support per-provider ACME clients (keyed by provider name)
            • Support EAB registration for ZeroSSL via acme.Client.Register with ExternalAccountBinding
            • Separate account storage per provider (accounts/letsencrypt/account.json, accounts/zerossl/account.json)
            • Update ObtainCertificate to accept and use provider info
            • Update hasConfigurationChanged to detect provider changes (trigger re-issuance when provider switches)
          • deployments.go: Update GetCertificateDomains() to populate new CertificatesDomain fields from parsed container labels.
          • updater.go: Extended domain info flows through naturally since GetCertificateDomains populates it.

          Config validation

          • acme_provider: "zerossl" requires both eab_kid and eab_hmac
          • acme_provider: "" or "letsencrypt" rejects EAB fields
          • Unknown provider values are rejected

          Files to modify

          FileChange
          internal/config/deploy_config.goAdd fields to Domain, update validation
          internal/config/labels.goAdd label constants, update ToLabels/ParseContainerLabels
          internal/configloader/resolve_secrets.goGather EAB ValueSources from domains
          internal/haloyd/certificates.goZeroSSL directory, EAB registration, per-provider clients
          internal/haloyd/deployments.goPopulate provider/EAB in GetCertificateDomains
          internal/haloyd/updater.goMinor comment update

          Testing

          • Unit tests for Domain validation with new fields (valid/invalid combos)
          • Unit tests for label round-trip (ToLabels -> ParseContainerLabels) with EAB fields
          • Unit tests for gatherValueSources including domain EAB fields
          • Unit tests for provider-specific ACME client creation
          • Verify existing Let's Encrypt flow is unaffected (default, no EAB)

          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('^' + ".*" + ' feat: Add ZeroSSL support as alternative ACME CA · Issue #2 · haloydev/haloy · GitHub
            Skip to content

            feat: Add ZeroSSL support as alternative ACME CA #2

            Description

            @ameistad

            Add support for ZeroSSL, configurable per-domain via haloy.yaml. Let's Encrypt remains the default. EAB credentials for ZeroSSL use the existing ValueSource pattern, so they can be provided inline, via env vars, or via built-in secret management

            Proposed Changes:

            Add three new fields to config.Domain:

            • acme_provider (string): "letsencrypt" (default) or "zerossl"
            • eab_kid (ValueSource): External Account Binding Key ID for ZeroSSL
            • eab_hmac (ValueSource): External Account Binding HMAC key for ZeroSSL
            domains:
            - domain: example.comacme_provider: zerossleab_kid:
            from:
            secret: "onepassword:zerossl_creds.eab_kid"eab_hmac:
            from:
            secret: "onepassword:zerossl_creds.eab_hmac"# Using environment variablesdomains:
            - domain: example.comacme_provider: zerossleab_kid:
            from:
            env: ZEROSSL_EAB_KIDeab_hmac:
            from:
            env: ZEROSSL_EAB_HMAC# Inline domains:
            - domain: example.comacme_provider: zerossleab_kid:
            value: "abc123"eab_hmac:
            value: "hmac-key-here"

            Implementation Plan

            Config layer (internal/config/)

            • deploy_config.go: Add ACMEProvider, EABKeyID, EABHMACKey fields to Domain struct. Update Domain.Validate() to enforce that ZeroSSL requires both EAB fields and that Let's Encrypt rejects them.
            • labels.go: Add label format strings (dev.haloy.domain.%d.acme-provider, dev.haloy.domain.%d.eab-kid, dev.haloy.domain.%d.eab-hmac). Update ToLabels() and ParseContainerLabels() to serialize/deserialize the new fields.

            Secret resolution (internal/configloader/)

            • resolve_secrets.go: Add domain EAB ValueSource fields to gatherValueSources() and gatherTargetValueSources() so they go through the same resolution pipeline as env vars and API tokens.

            Certificate management (internal/haloyd/)

            • certificates.go:
              • Add ZeroSSL ACME directory constant (https://acme.zerossl.com/v2/DV90)
              • Extend CertificatesDomain with ACMEProvider, EABKeyID, EABHMACKey fields
              • Refactor ACMEClientManager to support per-provider ACME clients (keyed by provider name)
              • Support EAB registration for ZeroSSL via acme.Client.Register with ExternalAccountBinding
              • Separate account storage per provider (accounts/letsencrypt/account.json, accounts/zerossl/account.json)
              • Update ObtainCertificate to accept and use provider info
              • Update hasConfigurationChanged to detect provider changes (trigger re-issuance when provider switches)
            • deployments.go: Update GetCertificateDomains() to populate new CertificatesDomain fields from parsed container labels.
            • updater.go: Extended domain info flows through naturally since GetCertificateDomains populates it.

            Config validation

            • acme_provider: "zerossl" requires both eab_kid and eab_hmac
            • acme_provider: "" or "letsencrypt" rejects EAB fields
            • Unknown provider values are rejected

            Files to modify

            FileChange
            internal/config/deploy_config.goAdd fields to Domain, update validation
            internal/config/labels.goAdd label constants, update ToLabels/ParseContainerLabels
            internal/configloader/resolve_secrets.goGather EAB ValueSources from domains
            internal/haloyd/certificates.goZeroSSL directory, EAB registration, per-provider clients
            internal/haloyd/deployments.goPopulate provider/EAB in GetCertificateDomains
            internal/haloyd/updater.goMinor comment update

            Testing

            • Unit tests for Domain validation with new fields (valid/invalid combos)
            • Unit tests for label round-trip (ToLabels -> ParseContainerLabels) with EAB fields
            • Unit tests for gatherValueSources including domain EAB fields
            • Unit tests for provider-specific ACME client creation
            • Verify existing Let's Encrypt flow is unaffected (default, no EAB)

            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); } })(); })(); feat: Add ZeroSSL support as alternative ACME CA · Issue #2 · haloydev/haloy · GitHub
              Skip to content

              feat: Add ZeroSSL support as alternative ACME CA #2

              Description

              @ameistad

              Add support for ZeroSSL, configurable per-domain via haloy.yaml. Let's Encrypt remains the default. EAB credentials for ZeroSSL use the existing ValueSource pattern, so they can be provided inline, via env vars, or via built-in secret management

              Proposed Changes:

              Add three new fields to config.Domain:

              • acme_provider (string): "letsencrypt" (default) or "zerossl"
              • eab_kid (ValueSource): External Account Binding Key ID for ZeroSSL
              • eab_hmac (ValueSource): External Account Binding HMAC key for ZeroSSL
              domains:
              - domain: example.comacme_provider: zerossleab_kid:
              from:
              secret: "onepassword:zerossl_creds.eab_kid"eab_hmac:
              from:
              secret: "onepassword:zerossl_creds.eab_hmac"# Using environment variablesdomains:
              - domain: example.comacme_provider: zerossleab_kid:
              from:
              env: ZEROSSL_EAB_KIDeab_hmac:
              from:
              env: ZEROSSL_EAB_HMAC# Inline domains:
              - domain: example.comacme_provider: zerossleab_kid:
              value: "abc123"eab_hmac:
              value: "hmac-key-here"

              Implementation Plan

              Config layer (internal/config/)

              • deploy_config.go: Add ACMEProvider, EABKeyID, EABHMACKey fields to Domain struct. Update Domain.Validate() to enforce that ZeroSSL requires both EAB fields and that Let's Encrypt rejects them.
              • labels.go: Add label format strings (dev.haloy.domain.%d.acme-provider, dev.haloy.domain.%d.eab-kid, dev.haloy.domain.%d.eab-hmac). Update ToLabels() and ParseContainerLabels() to serialize/deserialize the new fields.

              Secret resolution (internal/configloader/)

              • resolve_secrets.go: Add domain EAB ValueSource fields to gatherValueSources() and gatherTargetValueSources() so they go through the same resolution pipeline as env vars and API tokens.

              Certificate management (internal/haloyd/)

              • certificates.go:
                • Add ZeroSSL ACME directory constant (https://acme.zerossl.com/v2/DV90)
                • Extend CertificatesDomain with ACMEProvider, EABKeyID, EABHMACKey fields
                • Refactor ACMEClientManager to support per-provider ACME clients (keyed by provider name)
                • Support EAB registration for ZeroSSL via acme.Client.Register with ExternalAccountBinding
                • Separate account storage per provider (accounts/letsencrypt/account.json, accounts/zerossl/account.json)
                • Update ObtainCertificate to accept and use provider info
                • Update hasConfigurationChanged to detect provider changes (trigger re-issuance when provider switches)
              • deployments.go: Update GetCertificateDomains() to populate new CertificatesDomain fields from parsed container labels.
              • updater.go: Extended domain info flows through naturally since GetCertificateDomains populates it.

              Config validation

              • acme_provider: "zerossl" requires both eab_kid and eab_hmac
              • acme_provider: "" or "letsencrypt" rejects EAB fields
              • Unknown provider values are rejected

              Files to modify

              FileChange
              internal/config/deploy_config.goAdd fields to Domain, update validation
              internal/config/labels.goAdd label constants, update ToLabels/ParseContainerLabels
              internal/configloader/resolve_secrets.goGather EAB ValueSources from domains
              internal/haloyd/certificates.goZeroSSL directory, EAB registration, per-provider clients
              internal/haloyd/deployments.goPopulate provider/EAB in GetCertificateDomains
              internal/haloyd/updater.goMinor comment update

              Testing

              • Unit tests for Domain validation with new fields (valid/invalid combos)
              • Unit tests for label round-trip (ToLabels -> ParseContainerLabels) with EAB fields
              • Unit tests for gatherValueSources including domain EAB fields
              • Unit tests for provider-specific ACME client creation
              • Verify existing Let's Encrypt flow is unaffected (default, no EAB)

              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