Feature Specification: Component valkey

Created: 2026-10-07

Status: Implemented

Input: Reconstructed from the code at 093e68b6f (core/components/shared/valkey/default.nix, core/components/shared/valkey/valkey.nix, core/components/shared/valkey/publish-coordinates.nix, core/components/shared/valkey/restore-stream.py, core/components/shared/valkey/valkey-test.nix, core/components/hosting-provider/redis-commander.nix, core/setups/hosts-common.nix, core/resources/redis/), pull requests #1381, #1560, #1571, #1595, #1597, #1656, #1741, #1747, #1749, #1760, #1809, #1822, #1835, #1852 and #1996.

User Scenarios & Testing (mandatory)

User Story 1 - Give each application its own cache instance (Priority: P1)

Applications of a group need a redis-compatible cache. The valkey component declares provides = [ "redis" ], so the node answers the redis requests of the group (see redis). The module valkey.nix runs one services.redis.servers.<ns> instance with pkgs.valkey for each entry of fediversity.valkey.namespaces. The port of each instance comes from portOf namespace in core/resources/redis/port.nix.

Why this priority: the applications do not start without their cache.

Independent Test: test-valkey-service. It runs the node with the OpenBao valkey database engine, and checks the ACL, the password rotation and the backup to garage. contracts-redis-valkey, nix-unit-setups-valkey-config, nix-unit-setups-valkey-gate and nix-unit-resources-redis-port cover the decisions.

Acceptance Scenarios:

  1. Given a group whose nodes request redis, When the setups evaluate, Then fediversity.valkey.namespaces lists one namespace per request and host is the valkey machine.
  2. Given an empty namespaces list, When the node evaluates, Then it runs no instance.
  3. Given a TLS certificate, When an instance starts, Then the plain port is 0, tls-port is the derived port and tls-auth-clients is no.

User Story 2 - Keep the cache passwords secret and rotated (Priority: P1)

Each instance has two users. The default user has a bootstrap password. The generateFiles instance valkey-<ns> makes it with openssl rand -hex 32 (owner redis-<ns>, mode 0400). The password stays the same across deploys. The app user gets a password that OpenBao rotates every 24h (see generateFiles).

Why this priority: an instance without a password is open to every node on the network.

Independent Test: test-valkey-service.

Acceptance Scenarios:

  1. Given a missing bootstrap password, When the instance starts, Then it fails, because the unit Requires generate-vars and asserts that the password is not empty.
  2. Given a rotated app password at database/static-creds/<ns> (<ns>-<deployment namespace> in a tenant deployment), When valkey-<ns>-apply-password runs, Then it applies the password with ACL SETUSER ... resetpass over stdin, reads it back and runs ACL SAVE.
  3. Given a new render of the vars, When services.vars-openbao.reloadUnits fires, Then valkey-<ns>-apply-password runs again.

User Story 3 - Back up and restore a namespace (Priority: P2)

An admin needs a copy of the cache data. A timer streams valkey-cli --rdb - to restic through the streamingBackup contract, into the garage of the group (see garage). The restore program restore-stream.py boots a scratch valkey on a fixed offset port. It sends RESTORE ... REPLACE frames through valkey-cli --pipe to the live instance, without a restart.

Why this priority: the cache can be rebuilt, but some applications keep queues in it.

Independent Test: test-valkey-service.

Acceptance Scenarios:

  1. Given backup.enable = true, When the timer fires (OnCalendar = "daily", Persistent = true), Then restic writes a snapshot to the bucket buckets.${namespace}.
  2. Given a snapshot, When an admin runs the restore, Then the live instance gets the keys and keeps running.

User Story 4 - Inspect the cache in a web UI (Priority: P3)

An admin inspects the keys of the instances. The component requests "redis-ui".oidc with forward-auth on redis.<apex> (the label is in admin-ui-subdomain.nix, which served-domains.nix also reads), for the group fediversity-admins with one_factor. The nixos-module request of the component imports redis-commander.nix, which sets its units only when fediversity.forwardAuth.domains has an entry for 127.0.0.1:8081 and the node serves at least one namespace. The redis-commander-config oneshot renders /run/redis-commander/node-config.env with one connection per namespace. The connection uses TLS with the PKI CA when the instances serve TLS, and plain text otherwise. redis-commander listens on 127.0.0.1:8081.

Why this priority: the UI is for diagnosis only.

Independent Test: nix-unit-setups-valkey-config checks that the redis-commander-config oneshot reads the PKI CA, is ordered after the runtime fetch of the CA, and owns its retry. It also checks that the vhost of the UI asks authelia for each request and sends the request to the address and port that redis-commander binds, that the firewall does not open that port, and that a node without the forwardAuth entry of the UI runs no redis-commander. No check starts redis-commander itself.

Acceptance Scenarios:

  1. Given deployAdminUIs is false, When the setups evaluate, Then the apply of the oidc resource does not deploy the UI.
  2. Given an empty namespaces list, When the node evaluates, Then redis-commander is not enabled.

Edge Cases

  • A null fediversity.valkey.tls.certFile disables TLS. The instances then serve plaintext on the management network.
  • The setup gives the valkey node a certificate only when the policy resources."cache".redis.tls.enable is true. Its default is true when the PKI plan of the deployment holds the valkey host. Each consumer reads the same value from the tls field of its redis contract result, so the server and its clients agree on TLS or plain text.
  • A first boot adds an app line without a password to the ACL file. A later start keeps an existing app line and rewrites only the default line.
  • valkey-<ns>-apply-password retries without a start limit. It uses only soft edges, so a failure does not stop the instance.
  • valkey-publish-coordinates runs only when namespaces is not empty, host is set and the vars go to OpenBao. It writes only host=<host> to <kvPrefixOf>/public/valkey-<ns>/instance, because a consumer derives the port.
  • The CLI tools get the default password in REDISCLI_AUTH, not in argv.

Requirements (mandatory)

Functional Requirements

  • FR-001: The component MUST declare provides = [ "redis" ] and use pkgs.valkey.
  • FR-002: The implementation MUST request "spiffe".spiffe.enable = true and import valkey.nix through "code".nixos-module.
  • FR-003: The implementation MUST request "valkey-cache".redis with namespace = "valkey" and consumerName = "valkey".
  • FR-004: valkey.nix MUST run one instance per namespace on the port that portOf derives, and none for an empty list.
  • FR-005: An instance MUST NOT start without a non-empty bootstrap password.
  • FR-006: valkey-<ns>-apply-password MUST apply the rotated app password from OpenBao and verify it before ACL SAVE.
  • FR-007: The backup MUST stream to restic through the streamingBackup contract. It MUST get the S3 key pair as the systemd credentials restic-s3.
  • FR-008: The restore MUST NOT restart the live instance.
  • FR-009: redis-commander MUST listen only on 127.0.0.1:8081 and rely on forward-auth for authentication.

Key Entities

  • fediversity.valkey: the options namespaces, host, tls.{certFile,keyFile,issueService,caCertFile,serverName} and backup.{enable,garageApiUrl,buckets,timerConfig}.
  • Instance <ns>: services.redis.servers.<ns> with RDB saves at 900 1, 300 10 and 60 10000, and appendOnly.
  • valkey-tls: the group that reads the TLS key. The setup issues the certificate from openbao-pki with keyMode = "0640" and restarts redis-<ns>.service on renewal (see ssl).
  • valkey-publish-coordinates: the unit that publishes the host of each instance to OpenBao KV.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: test-valkey-service and contracts-redis-valkey pass.
  • SC-002: nix-unit-setups-valkey-config, nix-unit-setups-valkey-gate, nix-unit-setups-valkey-coordinates, nix-unit-setups-valkey-tls-agreement and nix-unit-resources-redis-port pass.
  • SC-003: applications-catalog passes with the valkey icon from sources.valkey-assets.

Assumptions

  • The hosting-provider and operator groups both declare a valkey machine.
  • The setup sets backup.garageApiUrl from the external garage resource and buckets from cacheBackupBucketFor.
  • The OpenBao valkey database engine (hosting-provider/openbao/valkey-engine.nix) owns the rotation period.
  • The colors of the icon must not change, because of the trademark policy of valkey.

Known Deviations

  • redis-commander has no native OIDC. It relies on forward-auth only.