Files
homelab/COUCHDB-ERLANGCOOKIE-FIX.md
Hermes Agent service account d974c75d7c feat(jmri): headless JMRI server with Leviton layout power monitor and X11 GUI mode
- Stable udev device symlinks (/dev/jmri/nce, /dev/jmri/loconet, /dev/jmri/lcc)
- jmri-monitor: polls Leviton Decora Smart switch to start/stop JMRI automatically
  - Quiet hours 1-10 AM (no polling)
  - 30s off-delay before shutdown
- LCRR config cloned from Gitea (ssh://gitea.mk-labs.cloud:2221/rblundon/LCRR.git)
- ~/.jmri symlinked to LCRR repo for GitOps config management
- jmri-gui: X11 remote GUI access (PanelPro/DecoderPro) via ssh -X as jmri user
  - Stops daemon, launches GUI, restarts daemon on exit if layout still on
- jmri user gets login shell + SSH key for GUI sessions
- Full JRE installed (openjdk-21-jre) for AWT/X11 support
2026-07-29 00:43:23 -05:00

5.2 KiB

CouchDB erlangCookie Fix - Implementation Guide

Summary

Problem: CouchDB deployment fails because erlangCookie is missing from the ExternalSecret configuration.

Decision: Externalize erlangCookie to 1Password (pragmatic approach)

Rationale:

  • ExternalSecret architecture requires ownership of the entire secret
  • Mixing externalized and chart-generated fields in the same secret is not supported
  • Single-node deployment makes erlangCookie rotation unnecessary
  • This is an acceptable deviation from the pure Harbor pattern given the architectural constraints

Implementation Steps

1. Generate erlangCookie Value

openssl rand -hex 20

Example output: f4e3c2b1a9d8e7f6c5b4a3d2e1f0a9b8c7d6e5f4

2. Add to 1Password

  • Vault: mk-labs
  • Item: couchdb
  • Field Name: erlang-cookie
  • Field Type: password (concealed)
  • Value: <paste generated value from step 1>

3. Update ExternalSecret Configuration

File: cluster/applications/couchdb/externalsecret.yaml

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: couchdb-credentials
  namespace: couchdb
  labels:
    app.kubernetes.io/name: couchdb
    app.kubernetes.io/part-of: mk-labs
spec:
  refreshInterval: 1h
  secretStoreRef:
    kind: ClusterSecretStore
    name: onepassword-connect
  target:
    name: couchdb-admin
    creationPolicy: Owner
    template:
      engineVersion: v2
      data:
        adminUsername: "admin"
        adminPassword: "{{ .adminPassword }}"
        cookieAuthSecret: "{{ .cookieAuthSecret }}"
        erlangCookie: "{{ .erlangCookie }}"  # ← ADD THIS LINE
  data:
    - secretKey: adminPassword
      remoteRef:
        key: couchdb
        property: admin-password
    - secretKey: cookieAuthSecret
      remoteRef:
        key: couchdb
        property: cookie-auth-secret
    - secretKey: erlangCookie  # ← ADD THIS BLOCK
      remoteRef:
        key: couchdb
        property: erlang-cookie

4. Update values.yaml Documentation (Optional)

File: cluster/applications/couchdb/values.yaml

Update the comment block at line 9-10:

# Admin credentials managed via ExternalSecret
# See externalsecret.yaml for 1Password integration
#
# NOTE: erlangCookie is externalized to 1Password for architectural
# simplicity (ExternalSecret ownership model). In a pure Harbor pattern,
# this would be chart-generated, but single-node deployment makes this
# acceptable. The erlangCookie is treated as an immutable infrastructure
# secret (generate once, never rotate).
createAdminSecret: false
extraSecretName: "couchdb-admin"

5. Commit and Push

cd ~/git/homelab
git add cluster/applications/couchdb/externalsecret.yaml
git add cluster/applications/couchdb/values.yaml  # if modified
git commit -m "fix(couchdb): add erlangCookie to ExternalSecret from 1Password"
git push origin main

6. Verify Deployment

# Watch ExternalSecret sync
kubectl get externalsecret -n couchdb couchdb-credentials -w
# Wait for: SecretSynced

# Verify secret created with all four keys
kubectl get secret -n couchdb couchdb-admin -o yaml
# Should contain: adminUsername, adminPassword, cookieAuthSecret, erlangCookie

# Watch ArgoCD sync
kubectl get application -n argocd couchdb -w
# Wait for: Healthy/Synced

# Watch pod startup
kubectl get pods -n couchdb -w
# Wait for: Running

# Test CouchDB access
kubectl port-forward -n couchdb svc/couchdb-svc-couchdb 5984:5984 &
curl http://localhost:5984/
# Expected: {"couchdb":"Welcome","version":"3.5.1"}

Why Not Follow Harbor Pattern Exactly?

Harbor Pattern: Only user-facing credentials externalized, internal secrets chart-generated.

CouchDB Constraint: ExternalSecret uses creationPolicy: Owner, which takes full ownership of the target secret. This prevents the Helm chart from adding auto-generated fields to the same secret.

Options Considered:

  1. Externalize erlangCookie (SELECTED) - Works with current architecture
  2. Chart auto-generation - Conflicts with ExternalSecret ownership
  3. Dual-secret approach - Requires Helm chart customization
  4. Disable ExternalSecret - Loses 1Password integration for admin password

Decision: Pragmatic approach wins. erlangCookie is treated as an infrastructure secret (generate once, never rotate), which is acceptable for a single-node deployment.

Secret Classification

Secret Type 1Password? Rationale
adminUsername User-facing No* Static value, hardcoded in template
adminPassword User-facing YES User login credential
cookieAuthSecret Gray area YES Session security, periodic rotation
erlangCookie Internal YES** Architectural constraint

* Hardcoded in ExternalSecret template (not fetched from 1Password)
** Pragmatic deviation from Harbor pattern due to ExternalSecret architecture

References

  • Full analysis: /home/hermes/couchdb-erlangcookie-analysis.txt
  • Harbor pattern: /home/hermes/harbor-simplification-complete.txt
  • CouchDB Helm chart: apache/couchdb v4.6.3