Stand up multi-tenant PostgreSQL on chicken (issue #4) #6

Open
hermes-bot wants to merge 20 commits from postgres-chicken into main
Member

Implements issue #4: moves PostgreSQL to bare metal (chicken), multi-tenant, with credential injection into the cluster and restic backups.

What's added

  • modules/postgres.nix — new pickler.postgres module:
    • services.postgresql enabled with enableTCPIP, scram-sha-256 for TCP from allowedSourceNetworks (defaults: 192.168.97.0/24 local + 10.0.0.0/24 k8s), unix peer auth locally.
    • Tenants declared as pickler.postgres.tenants.<name> with per-tenant role/database and kubernetesNamespaces targets.
    • Credential injection — per-tenant SOPS password keys (postgres-<tenant>-password); pickler-postgres-setup.service ensures role/db + applies the password via postgres peer auth; pickler-postgres-k8s-sync.service pushes <tenant>-postgres Secrets (password, uri keys) into each namespace with kubectl create --dry-run | apply (idempotent), per your kubectl-at-startup suggestion — plus a pickler-postgres-reconcile systemd timer (default 12h) so key rotation is automatic: change SOPS, the timer re-applies on the DB and re-pushes into the cluster.
    • Backups — services.restic.backups.postgres: backupPrepareCommand runs pg_dumpall --globals-only + pg_dump -Fc per tenant into /var/backups/postgres, restic uploads and prunes (--keep-daily 7 --keep-weekly 5 --keep-monthly 12); nixpkgs' restic module has native command/backupPrepareCommand support, so no custom units.
    • No password material lands in the Nix store: scripts read root-only SOPS runtime files; the ALTER ROLE password travels on stdin as a dollar-quoted literal with a random tag.
  • flake.nix — module added to the chicken system.
  • hosts/chicken.nix — pickler.postgres.enable = true + restic repository pointing at the rest: server on pi4 (repo path chicken-postgres; passphrase restic-passphrase, already used by the restic server / home backups).

Follow-ups (deliberately not guessed)

  1. No tenants declared yet — add password to SOPS + a tenants.<name> entry per database you move over; the assertion in the module will nag until the repository/tenants pair is consistent.
  2. K8s injection is off until a kubeconfig exists — set pickler.postgres.kubernetes.kubeconfigSecret to a SOPS key holding a kubeconfig scoped to Secret-management in the tenant namespaces (ServiceAccount + Role/RoleBinding bound to it is enough; kubernetes.addonManager.bootstrapAddons doesn't do secret injection).
  3. The pi4 repository URL currently embeds the backup: htpasswd user; if you'd rather keep credentials out of nixos git, I can switch to environmentFile + SOPS.
  4. sslmode=disable in the generated URIs — fine on the LAN/k8s overlay, flag it if you want certs.

⚠️ Not evaluated: no nix flake check run in this environment (no nix available). Needs one nix build .#nixosConfigurations.chicken.config.system.build.toplevel (or your auto-upgrade pipeline) on a machine with nix before deploying.

Implements issue #4: moves PostgreSQL to bare metal (chicken), multi-tenant, with credential injection into the cluster and restic backups. ## What's added - **`modules/postgres.nix`** — new `pickler.postgres` module: - `services.postgresql` enabled with `enableTCPIP`, scram-sha-256 for TCP from `allowedSourceNetworks` (defaults: `192.168.97.0/24` local + `10.0.0.0/24` k8s), unix peer auth locally. - **Tenants** declared as `pickler.postgres.tenants.<name>` with per-tenant role/database and `kubernetesNamespaces` targets. - **Credential injection** — per-tenant SOPS password keys (`postgres-<tenant>-password`); `pickler-postgres-setup.service` ensures role/db + applies the password via postgres peer auth; `pickler-postgres-k8s-sync.service` pushes `<tenant>-postgres` Secrets (`password`, `uri` keys) into each namespace with `kubectl create --dry-run | apply` (idempotent), per your kubectl-at-startup suggestion — plus a `pickler-postgres-reconcile` systemd timer (default 12h) so **key rotation is automatic**: change SOPS, the timer re-applies on the DB and re-pushes into the cluster. - **Backups** — `services.restic.backups.postgres`: `backupPrepareCommand` runs `pg_dumpall --globals-only` + `pg_dump -Fc` per tenant into `/var/backups/postgres`, restic uploads and prunes (`--keep-daily 7 --keep-weekly 5 --keep-monthly 12`); nixpkgs' restic module has native `command`/`backupPrepareCommand` support, so no custom units. - No password material lands in the Nix store: scripts read root-only SOPS runtime files; the ALTER ROLE password travels on stdin as a dollar-quoted literal with a random tag. - **`flake.nix`** — module added to the `chicken` system. - **`hosts/chicken.nix`** — `pickler.postgres.enable = true` + restic repository pointing at the rest: server on pi4 (repo path `chicken-postgres`; passphrase `restic-passphrase`, already used by the restic server / home backups). ## Follow-ups (deliberately not guessed) 1. **No tenants declared yet** — add password to SOPS + a `tenants.<name>` entry per database you move over; the assertion in the module will nag until the repository/tenants pair is consistent. 2. **K8s injection is off until a kubeconfig exists** — set `pickler.postgres.kubernetes.kubeconfigSecret` to a SOPS key holding a kubeconfig scoped to Secret-management in the tenant namespaces (ServiceAccount + Role/RoleBinding bound to it is enough; `kubernetes.addonManager.bootstrapAddons` doesn't do secret injection). 3. The pi4 repository URL currently embeds the `backup:` htpasswd user; if you'd rather keep credentials out of `nixos` git, I can switch to `environmentFile` + SOPS. 4. `sslmode=disable` in the generated URIs — fine on the LAN/k8s overlay, flag it if you want certs. ⚠️ Not evaluated: no `nix flake check` run in this environment (no nix available). Needs one `nix build .#nixosConfigurations.chicken.config.system.build.toplevel` (or your auto-upgrade pipeline) on a machine with nix before deploying.
Owner

@hermes-bot How do I make a secret for kubeconfig in this case? I've never made a service that needs to talk to the kube api before.

@hermes-bot How do I make a secret for kubeconfig in this case? I've never made a service that needs to talk to the kube api before.
Owner

I've also pushed a secret for Authentik, as that will be the first thing we re-target to this database

I've also pushed a secret for Authentik, as that will be the first thing we re-target to this database
@ -26,0 +29,4 @@
# restic server on pi4 (modules/postgres.nix).
pickler.postgres = {
enable = true;
backup.repository = "rest:http://backup:UnicycleResignedClash@pi4:8000/chicken-postgres";
Owner

Please find a way to use the restic-passphrase secret in this url. It should be 'rest:http://backup:@pi4:8000/chicken-postgres'

If the url needs to become a constant in our nix implementation to do so, that's fine. This is unlikely to change any time soon across my whole fleet.

Please find a way to use the restic-passphrase secret in this url. It should be 'rest:http://backup:<secret>@pi4:8000/chicken-postgres' If the url needs to become a constant in our nix implementation to do so, that's fine. This is unlikely to change any time soon across my whole fleet.
@ -26,0 +32,4 @@
backup.repository = "rest:http://backup:UnicycleResignedClash@pi4:8000/chicken-postgres";
# Add a SOPS key `postgres-<tenant>-password` (plaintext password) per
# tenant, then declare it:
# tenants.grafana.kubernetesNamespaces = [ "grafana" ];
Owner

Start with 'authentik'

Start with 'authentik'
@ -0,0 +244,4 @@
sops.secrets.restic-passphrase = { };
sops.secrets =
(lib.listToAttrs (map (name: lib.nameValuePair (pwSecret' name) { })
Owner

pwSecret' does not exist. I think this is a minor hallucination that you've repeated elsewhere.

pwSecret' does not exist. I think this is a minor hallucination that you've repeated elsewhere.
@ -0,0 +251,4 @@
];
sops.templates = lib.listToAttrs (lib.mapAttrsToList (name: t:
lib.nameValuePair (connTpl' name) {
Owner

Undefined variable connTpl'

Check https://noogle.dev/ for nix function reference documentation

Undefined variable connTpl' Check https://noogle.dev/ for nix function reference documentation
- chicken: build the restic URL from the restic-passphrase SOPS secret
  via a placeholder rendered through an sops template at boot; the URL
  (which embeds the passphrase) reaches restic via repositoryFile and
  never lands in the Nix store.
- chicken: declare the authentik tenant (kubernetesNamespaces).
- postgres module: pwSecret/connTpl interpolated as literal strings
  (''${...}) instead of evaluating; call them correctly (they were
  referenced as pwSecret'/connTpl' in several places).
- postgres module: tenant passwords resolved from the nested SOPS key
  postgresql-tenants/<tenant> (sops-nix addresses nested keys with /).
- postgres module: KUBECONFIG env referenced a literal secret path.
- postgres module: compose sops.secrets/tenants as sets (listToAttrs
  ++ optionals was a type error).
Author
Member

Implemented all four review findings in c0bc771:

  • restic URL from secret (hosts/chicken.nix): the passphrase is no longer hardcoded. The URL is now rest:http://backup:<placeholder>@pi4:8000/chicken-postgres built from config.sops.placeholder.restic-passphrase, rendered at boot via an sops template and handed to restic through repositoryFile, so the final URL never lands in the Nix store. pi4 host + chicken-postgres repo unchanged, as agreed.
  • authentik tenant (hosts/chicken.nix): tenants.authentik.kubernetesNamespaces = [ "authentik" ].
  • pwSecret' / connTpl' (modules/postgres.nix): root cause was the helpers being defined with ''${...} escaping — they returned literal ${cfg.passwordSecretPrefix}... strings instead of evaluating, and several call sites then referenced the non-existent primed names. Helpers now interpolate properly, all call sites use them, and the primed names are gone.

Related fixes found while implementing the above:

  • Tenant secrets resolve from your nested SOPS layout (postgresql-tenants: → authentik:) via key = "postgresql-tenants/<tenant>" — sops-nix addresses nested keys with / (verified against the pinned sops-nix sops-install-secrets, recurseSecretKey), so the key you pushed works as-is.
  • environment.KUBECONFIG had the same literal-''${...} bug — fixed.
  • sops.secrets / sops.templates composition used listToAttrs ... ++ optionals (type error — listToAttrs returns a set and ++ wants lists) — fixed with proper set composition.

Both changed files verified with the real tree-sitter Nix grammar (0 parse errors; a control file with an injected error confirms the checker detects them). A full nix eval of the chicken system is still pending — no Nix toolchain in my sandbox — so a nixos-rebuild dry-build (or nix eval .#chicken.config.systemd.services) on chicken is worth doing before merge.

Also: still open from the discussion above — the Kubernetes credential sync only activates once pickler.postgres.kubernetes.kubeconfigSecret points at a SOPS key holding a kubeconfig. Happy to walk through creating a least-privilege ServiceAccount for that when you're ready.

Implemented all four review findings in c0bc771: - **restic URL from secret** (`hosts/chicken.nix`): the passphrase is no longer hardcoded. The URL is now `rest:http://backup:<placeholder>@pi4:8000/chicken-postgres` built from `config.sops.placeholder.restic-passphrase`, rendered at boot via an sops template and handed to restic through `repositoryFile`, so the final URL never lands in the Nix store. pi4 host + `chicken-postgres` repo unchanged, as agreed. - **authentik tenant** (`hosts/chicken.nix`): `tenants.authentik.kubernetesNamespaces = [ "authentik" ]`. - **`pwSecret'` / `connTpl'` (`modules/postgres.nix`)**: root cause was the helpers being defined with `''${...}` escaping — they returned *literal* `${cfg.passwordSecretPrefix}...` strings instead of evaluating, and several call sites then referenced the non-existent primed names. Helpers now interpolate properly, all call sites use them, and the primed names are gone. Related fixes found while implementing the above: - Tenant secrets resolve from your nested SOPS layout (`postgresql-tenants:` → `authentik:`) via `key = "postgresql-tenants/<tenant>"` — sops-nix addresses nested keys with `/` (verified against the pinned sops-nix `sops-install-secrets`, `recurseSecretKey`), so the key you pushed works as-is. - `environment.KUBECONFIG` had the same literal-`''${...}` bug — fixed. - `sops.secrets` / `sops.templates` composition used `listToAttrs ... ++ optionals` (type error — `listToAttrs` returns a set and `++` wants lists) — fixed with proper set composition. Both changed files verified with the real tree-sitter Nix grammar (0 parse errors; a control file with an injected error confirms the checker detects them). A full `nix eval` of the chicken system is still pending — no Nix toolchain in my sandbox — so a `nixos-rebuild dry-build` (or `nix eval .#chicken.config.systemd.services`) on chicken is worth doing before merge. Also: still open from the discussion above — the Kubernetes credential sync only activates once `pickler.postgres.kubernetes.kubeconfigSecret` points at a SOPS key holding a kubeconfig. Happy to walk through creating a least-privilege ServiceAccount for that when you're ready.
Secret material only changes with a deployed config (the SOPS file
lives in the repo), so deploy-time activation is the real trigger:
restart the setup/k8s-sync pair from an activation script, guarded on
postgresql being active so boot-time activation defers to the
wantedBy'd units, --no-block so the switch never hangs on it. Drops
the 12h polling timer and the reconcileInterval option.
Replace the string-fragment header soup (''..'' + "\\n" + '') with a
single indented-string bash preamble defining apply_password and
ensure_tenant once, plus a per-tenant one-line invocation generated by
concatMapStrings. Fixes:

- od -An -N12 was invalid coreutils syntax; od exited non-zero and
  pipefail killed the whole setup run. Tag is now
  head -c16 /dev/urandom | od -An -tx1 | tr -d '[:space:]' (pipefail
  safe, verified against real coreutils).
- Trailing whitespace/newlines in the secret file are no longer baked
  into the role password: cat $2 strips them via command
  substitution.
- Bash vars are bare $name so they can never collide with ''''''''''''''''''''''${...} Nix
  interpolation inside the indented string.
sops-nix defines config.sops.placeholder behind
'mkIf (config.sops.templates != {})', so evaluating any placeholder
forces the templates attribute set. The chicken host config defines
pickler.postgres.backup.repository by interpolating
config.sops.placeholder.restic-passphrase; the template guard
'cfg.backup.enable && cfg.backup.repository != ""' sat on the
templates spine, forcing the repository string while the templates set
was being computed — placeholder -> templates -> repository ->
placeholder, forever.

The guard is now placeholder-free (cfg.backup.enable only, a plain
bool). The element content stays lazily evaluated: listToAttrs forces
only the name part of each pair, and the URL reaches the store only at
activation via the sops template. Unset repository is still caught by
the existing backup assertion for the tenants case.
systemd.services.*.script and services.restic.backups.*.backupPrepareCommand
are typed as strings, not paths; pass the writeShellScript derivations
via interpolation (\${setupScript}\) so eval accepts them.
lib.concatMapStrings with a single argument is a partial application
('concatMapStrings sep f') and evaluates to a function — hence 'cannot
coerce a function to a string'. mapAttrsToList already yields a list of
strings, so concatStrings is the right combinator.
No such type in nixpkgs lib.types; the intended constraint (>= 1) is
ints.positive (> 0 for ints).
textClosureList resolves every deps name strictly via predefined.${dep};
a name no module registers aborts eval with 'attribute missing'.
'networking' is a legacy dep name that no longer exists in this nixpkgs
(checked config/networking.nix at the pinned revision; 'users' and
'groups' still exist but order nothing we care about). The snippet only
restarts units guarded on postgresql.service being active, so it needs
no ordering against other activation snippets at all.
pickler-postgres-setup ran before sops-install-secrets had installed
/run/secrets/postgres-authentik on chicken: 'cat: No such file or
directory'. sops-nix does not wire consumers to the install unit; each
unit must declare it itself. Add after/wants on sops-install-secrets
(wants, not requires: sops's own serviceConfig failure handling stays
its own) to the setup and k8s-sync units.
lib.getExe pkgs.systemd resolves to bin/systemd (the package has no
meta.mainProgram, so getExe falls back to the package name). The
activation guard and the reconcile ExecStart would have exec'd the
wrong binary; use lib.getExe' pkgs.systemd "systemctl". Verified with a
full chicken system eval (clean, warning gone, activation script now
calls bin/systemctl).
Author
Member

Post-assessment (full write-up in issue #4): nix eval .#nixosConfigurations.chicken.config.system.build.toplevel.drvPath now passes clean on this branch — full chicken system, exit 0, module assertions included.

One bug fixed today (d31c05c): systemctlExe = lib.getExe pkgs.systemd resolved to bin/systemd (the init binary — no meta.mainProgram, so getExe falls back to the package name). The activation guard and the reconcile ExecStart would have exec'd the wrong program, so credential reconcile would silently never run. Now lib.getExe' pkgs.systemd "systemctl"; re-eval clean and the activation script verified to exec bin/systemctl.

All 4 review findings were already fixed (see the Oct 16 comment). This review still formally sits at REQUEST_CHANGES — ready for re-review. Remaining checklist (kubeconfig ServiceAccount recipe, first switch on chicken, authentik re-target) is in my issue #4 comment.

Post-assessment (full write-up in issue #4): `nix eval .#nixosConfigurations.chicken.config.system.build.toplevel.drvPath` now passes **clean** on this branch — full chicken system, exit 0, module assertions included. One bug fixed today (d31c05c): `systemctlExe = lib.getExe pkgs.systemd` resolved to `bin/systemd` (the init binary — no `meta.mainProgram`, so getExe falls back to the package name). The activation guard and the reconcile `ExecStart` would have exec'd the wrong program, so credential reconcile would silently never run. Now `lib.getExe' pkgs.systemd "systemctl"`; re-eval clean and the activation script verified to exec `bin/systemctl`. All 4 review findings were already fixed (see the Oct 16 comment). This review still formally sits at REQUEST_CHANGES — ready for re-review. Remaining checklist (kubeconfig ServiceAccount recipe, first switch on chicken, authentik re-target) is in my issue #4 comment.
Owner

Instead of trying to inject the secrets into k8s, why not directly support sops secrets in k8s using something like this?

https://github.com/isindir/sops-secrets-operator

Instead of trying to *inject* the secrets into k8s, why not directly support sops secrets in k8s using something like this? https://github.com/isindir/sops-secrets-operator
Author
Member

Adopted this direction and implemented the NixOS side in d55f2b8.

What changed on this branch

  • Removed the host→cluster injection path entirely: pickler.postgres.kubernetes.kubeconfigSecret, the pickler-postgres-k8s-sync unit, the kubectl create secret | apply script, and the per-tenant kubernetesNamespaces / secretName options. hosts/chicken.nix now just declares tenants.authentik = { }.
  • Safe to delete outright rather than deprecate: kubeconfigSecret was never set on chicken, so the injection service was dormant (anyK8s = false) — nothing existing depended on it. The kubeconfig/ServiceAccount recipe from my earlier checklist is now obsolete, one less credential to create.
  • chicken remains authoritative for the DB half: role/database/password ensured from postgresql-tenants.authentik in modules/secrets.yaml (the key you pushed), restic backups untouched.
  • Also merged main (the flake.nix conflict was just jellyfin vs postgres modules in the chicken block — kept both).
  • Verified: nix eval .#nixosConfigurations.chicken.config.system.build.toplevel.drvPath passes clean; generated config no longer contains the sync unit, and the activation path references only pickler-postgres-setup.service.

Cluster side (helm repo — the remaining half)

sops-secrets-operator (isindir) fits without Flux — a SopsSecret CR reconciles into native Secrets however it's applied, so helmfile deploys it like anything else:

  1. age-keygen a dedicated cluster keypair. Don't reuse the sops-nix host keys — the operator's private key lives as a k8s Secret in etcd, so keep it able to decrypt only the files addressed to it.
  2. Install helm repo add sops https://isindir.github.io/sops-secrets-operator/ (chart 0.28.1 / operator 0.21.2), key wired via secretsAsFiles + SOPS_AGE_KEY_FILE env.
  3. In the helm repo, a manifest like:
apiVersion: isindir.github.com/v1alpha3
kind: SopsSecret
metadata:
  name: authentik-postgres
  namespace: authentik
spec:
  secretTemplates:
    - name: postgresql-conn
      stringData:
        uri: postgresql://authentik:REDACTED@chicken.lan:5432/authentik?sslmode=disable
        password: REDACTED

encrypted in place with sops --age <cluster-pubkey> — and per the operator's docs, use unencrypted_regex: '^(apiVersion|kind|metadata|status|name)$' so the k8s-injected plain fields keep decrypting. Apply with helmfile; the operator materialises postgresql-conn in the authentik namespace, and authentik's chart values point at that Secret plus chicken.lan:5432.

Two caveats worth knowing before we commit to it:

  • The operator protects secret material from git, not from the cluster — final Secrets are plaintext in etcd and RBAC applies as usual (their own Known Issues say this). So it's not "more secure secrets in k8s", it's "no plaintext push channel and chicken needs zero cluster credentials".
  • The tenant password then lives in two encrypted files (modules/secrets.yaml here, the SopsSecret there). Same value — re-encrypt on rotation, never regenerate one side. Worth a comment in both files stating that invariant.

Ready for re-review: the four findings from your review are fixed (d31c05c comment above), this change is the fifth.

Adopted this direction and implemented the NixOS side in d55f2b8. **What changed on this branch** - Removed the host→cluster injection path entirely: `pickler.postgres.kubernetes.kubeconfigSecret`, the `pickler-postgres-k8s-sync` unit, the `kubectl create secret | apply` script, and the per-tenant `kubernetesNamespaces` / `secretName` options. `hosts/chicken.nix` now just declares `tenants.authentik = { }`. - Safe to delete outright rather than deprecate: `kubeconfigSecret` was never set on chicken, so the injection service was dormant (`anyK8s` = false) — nothing existing depended on it. The kubeconfig/ServiceAccount recipe from my earlier checklist is now obsolete, one less credential to create. - chicken remains authoritative for the DB half: role/database/password ensured from `postgresql-tenants.authentik` in `modules/secrets.yaml` (the key you pushed), restic backups untouched. - Also merged `main` (the `flake.nix` conflict was just jellyfin vs postgres modules in the chicken block — kept both). - Verified: `nix eval .#nixosConfigurations.chicken.config.system.build.toplevel.drvPath` passes clean; generated config no longer contains the sync unit, and the activation path references only `pickler-postgres-setup.service`. **Cluster side (helm repo — the remaining half)** sops-secrets-operator (isindir) fits without Flux — a `SopsSecret` CR reconciles into native Secrets however it's applied, so helmfile deploys it like anything else: 1. `age-keygen` a **dedicated cluster keypair**. Don't reuse the sops-nix host keys — the operator's private key lives as a k8s Secret in etcd, so keep it able to decrypt only the files addressed to it. 2. Install `helm repo add sops https://isindir.github.io/sops-secrets-operator/` (chart 0.28.1 / operator 0.21.2), key wired via `secretsAsFiles` + `SOPS_AGE_KEY_FILE` env. 3. In the helm repo, a manifest like: ```yaml apiVersion: isindir.github.com/v1alpha3 kind: SopsSecret metadata: name: authentik-postgres namespace: authentik spec: secretTemplates: - name: postgresql-conn stringData: uri: postgresql://authentik:REDACTED@chicken.lan:5432/authentik?sslmode=disable password: REDACTED ``` encrypted in place with `sops --age <cluster-pubkey>` — and per the operator's docs, use `unencrypted_regex: '^(apiVersion|kind|metadata|status|name)$'` so the k8s-injected plain fields keep decrypting. Apply with helmfile; the operator materialises `postgresql-conn` in the `authentik` namespace, and authentik's chart values point at that Secret plus `chicken.lan:5432`. Two caveats worth knowing before we commit to it: - The operator protects secret material **from git**, not from the cluster — final Secrets are plaintext in etcd and RBAC applies as usual (their own Known Issues say this). So it's not "more secure secrets in k8s", it's "no plaintext push channel and chicken needs zero cluster credentials". - The tenant password then lives in **two encrypted files** (`modules/secrets.yaml` here, the SopsSecret there). Same value — re-encrypt on rotation, never regenerate one side. Worth a comment in both files stating that invariant. Ready for re-review: the four findings from your review are fixed (d31c05c comment above), this change is the fifth.
Author
Member

Checked your Merge branch 'main' into postgres-chicken (af97bc4) — merge is sound, and re-validated against the bumped lockfile:

  • Merge itself: git diff d55f2b8..af97bc4 carries only main-side changes (flake.lock, modules/llama-swap.nix, neovim plugin files). modules/postgres.nix, hosts/chicken.nix and modules/secrets.yaml are byte-identical to my head — no conflict resolution touched our paths this time (main didn't touch flake.nix).
  • Re-validation was needed: the flake update moves the nixpkgs pin, which voids my Nov 2 eval gate, so I re-ran it against af97bc4 — nixosConfigurations.chicken.config.system.build.toplevel.drvPath evaluates clean against the new lock (nixos-system-chicken-26.05.20261105.c56a87d.drv, exit 0). Spot assertions on the merged config: pickler-postgres-setup present, pickler-postgres-k8s-sync absent, tenants = [ "authentik" ].

PR stays mergeable=true. Nothing further from me — all five review findings are addressed (Oct 16, Oct 21, Nov 2 comments); the formal REQUEST_CHANGES just needs your re-review when you get a chance.

Checked your `Merge branch 'main' into postgres-chicken` (af97bc4) — merge is sound, and re-validated against the bumped lockfile: - **Merge itself**: `git diff d55f2b8..af97bc4` carries only main-side changes (`flake.lock`, `modules/llama-swap.nix`, neovim plugin files). `modules/postgres.nix`, `hosts/chicken.nix` and `modules/secrets.yaml` are byte-identical to my head — no conflict resolution touched our paths this time (main didn't touch `flake.nix`). - **Re-validation was needed**: the flake update moves the nixpkgs pin, which voids my Nov 2 eval gate, so I re-ran it against af97bc4 — `nixosConfigurations.chicken.config.system.build.toplevel.drvPath` evaluates clean against the new lock (`nixos-system-chicken-26.05.20261105.c56a87d.drv`, exit 0). Spot assertions on the merged config: `pickler-postgres-setup` present, `pickler-postgres-k8s-sync` absent, `tenants = [ "authentik" ]`. PR stays mergeable=true. Nothing further from me — all five review findings are addressed (Oct 16, Oct 21, Nov 2 comments); the formal REQUEST_CHANGES just needs your re-review when you get a chance.
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin postgres-chicken:postgres-chicken
git switch postgres-chicken

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff postgres-chicken
git switch postgres-chicken
git rebase main
git switch main
git merge --ff-only postgres-chicken
git switch postgres-chicken
git rebase main
git switch main
git merge --no-ff postgres-chicken
git switch main
git merge --squash postgres-chicken
git switch main
git merge --ff-only postgres-chicken
git switch main
git merge postgres-chicken
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
richardpickler.com/nixos!6
No description provided.