AGENTS.md
Rama main · réplica de lectura del repositorio original.
# Agent Guidelines for nix-podman-stacks
Declarative Podman Quadlet management using Home Manager and Nix.
## Build/Lint/Test Commands
### Format Code
```bash
nix fmt .
```
### Validate Flake
```bash
nix flake check --keep-going
```
### Build Configurations
```bash
# Full CI config (tests all stacks)
nix build .#homeConfigurations.ci.activationPackage
# Template config
nix build ./template#homeConfigurations.myhost.activationPackage
# Documentation
nix build .#packages.x86_64-linux.book
nix build .#packages.x86_64-linux.search
```
### Integration Tests
Each stack that ships a `modules/<stack>/vm-test.nix` gets a NixOS VM integration
test that activates the stack like a real deployment and verifies that all its
systemd user services reach a stable `active (running)` state.
```bash
# Requires network access for the VM, hence sandbox is disabled
nix build .#integrationTests.x86_64-linux.<stack>-integration --no-link --option sandbox false
```
The central test scaffolding lives in `tests/` (`base-config.nix`, `base-home.nix`,
`mk-integration-test.nix`, `check.sh`, `driver.py`, `dummy-values.nix`) and is merged
with the per-stack `vm-test.nix` home-manager fragment.
The scaffolding is also exported as the `lib` flake output (`lib.mkIntegrationTest`,
`lib.stackTestModules`) so consumers can test their own configuration. The
`integrationTests` output is generated with
`self.lib.mkIntegrationTest`, so repo and consumer tests share one code path. When
changing anything under `tests/` or `lib/`, update the `integrationTests` call site in
`flake.nix` and the options table in `docs/book/testing.md` with it. CI builds the stacks
matching changed files and falls back to `it-tools` when the shared scaffolding changed,
but it only covers `x86_64-linux`/KVM, so verify locally with:
`nix build .#integrationTests.x86_64-linux.it-tools-integration --option sandbox false`.
`npsTest.*` options are declared by `tests/base-config.nix`.
## Module Structure
Every module (`modules/<name>/default.nix`) follows this pattern:
```nix
{
config,
lib,
pkgs,
...
}: let
name = "modulename";
cfg = config.nps.stacks.${name};
storage = "${config.nps.storageBaseDir}/${name}";
# category, description, displayName for Homepage/Glance
in {
imports = import ../mkAliases.nix config lib name [name];
options.nps.stacks.${name} = {
enable = lib.mkEnableOption name;
# ... options
};
config = lib.mkIf cfg.enable {
# Configuration
};
}
```
### Test Files
When adding a new module, also add a `modules/<name>/vm-test.nix` (a home-manager
fragment enabling the stack with dummy secrets) so the stack gets a NixOS VM
integration test. Available dummy secrets are exposed via `_module.args`
(`dummySecretFile`, `dummyClientSecretHash`) from `tests/base-home.nix`, which sources them from
`tests/dummy-values.nix`. See `modules/it-tools/vm-test.nix` for a minimal example.
A memory hungry stack raises `npsTests.memorySize` in its own `vm-test.nix` (e.g. `immich`).
That option is declared by `tests/base-home.nix` and used by `tests/base-config.nix`, so
exported stack test modules work standalone.
## Container Configuration Patterns
### Traefik & Network
- `traefik.name = name` - **only on routable** (web-facing) containers
- `stack = name` - **all containers** in a stack share a Podman network
- `dependsOnContainer` / `wantsContainer` for container dependencies
### Dashboard Configs
Use the `dashboard` option, it configures Homepage and Glance from a single value:
```nix
dashboard = {
inherit category description; # let variables, applied to both dashboards
name = displayName;
icon = "di:jellyfin"; # Glance syntax, translated for Homepage
parent = name; # optional, child service, Glance only
};
homepage.settings.widget.type = "jellyfin"; # Homepage only
glance.icon = "si:jellyfin"; # Glance only
```
- `dashboard` - **all containers**
- `homepage` / `glance` - only for what is specific to one dashboard
- containers with a `parent` are child services, they are not shown on Homepage
### Volume Abstraction
Use `volumeMap` attrset (not `volumes` list) for easier single-entry override:
```nix
volumeMap = {
data = "${storage}/data:/data";
config = "${storage}/config:/config";
};
```
## OIDC Patterns
**With claim-based admin mapping** (services support admin role via groups):
```nix
oidc = {
enable = lib.mkOption { ... };
clientSecretFile = (import ../authelia/options.nix lib).clientSecretFile;
clientSecretHash = (import ../authelia/options.nix lib).derivableClientSecretHash ...;
adminGroup = lib.mkOption { default = "${name}_admin"; ... };
userGroup = lib.mkOption { default = "${name}_user"; ... };
};
```
**UserGroup only** (service doesn't support admin role mapping):
```nix
oidc = {
enable = lib.mkOption { ... };
clientSecretFile = ...;
clientSecretHash = ...;
userGroup = lib.mkOption { default = "${name}_user"; ... };
# No adminGroup - service only checks user membership
};
```
## Database Patterns
**Simple** (single DB type, e.g., SQLite or external):
```nix
db.passwordFile = lib.mkOption { type = lib.types.path; ... };
```
**Full** (sqlite/postgres choice):
```nix
db = {
type = lib.mkOption { type = lib.types.enum ["sqlite" "postgres"]; default = "sqlite"; ... };
username = lib.mkOption { default = name; ... }; # postgres only
passwordFile = lib.mkOption { type = lib.types.path; ... };
};
```
## Naming Conventions
- **Options**: camelCase (`enablePrometheusExport`)
- **Image tags**: stable versions only, never `latest`
- **Image registry**: prefer `ghcr.io` over `docker.io` when both are available
## Option Patterns
### Standard Option
Always include descriptions:
```nix
lib.mkOption {
type = lib.types.str;
description = "What this option does.";
}
```
### extraEnv Option
For services with many environment variables:
```nix
extraEnv = lib.mkOption {
type = (import ../types.nix lib).extraEnv;
default = {};
description = "Extra environment variables (supports fromFile, fromTemplate, fromCommand).";
};
```
### Environment Variable Values
When setting environment variables in modules use native Nix types:
```nix
# Good - native types
environment = {
ENABLE_FEATURE = true; # boolean, not "true"
MAX_CONNECTIONS = 5; # integer, not "5"
DEBUG_MODE = false; # boolean, not "false"
PG_PORT = 5432; # integer port number
};
# Avoid - unnecessary strings
environment = {
ENABLE_FEATURE = "true";
MAX_CONNECTIONS = "5";
PG_PORT = "5432";
};
```
### Config Files
For YAML/JSON configurable services:
```nix
yaml = pkgs.formats.yaml {};
settings = lib.mkOption {
type = yaml.type;
apply = yaml.generate "config.yml";
description = "Service configuration";
};
```
### Shared Utilities
```nix
(import ../types.nix lib).extraEnv
(import ../authelia/options.nix lib).clientSecretFile
(import ../socket-proxy/mkSocketProxyOptionModule.nix {stack = name;})
```
### Option Exposure Guidelines
Keep modules minimal. For details, see:
- **Secrets**: Always expose required secrets (see [Secrets Handling](#secrets-handling))
- **Database**: Expose `db.type` only when supporting multiple DB types (see [Database Patterns](#database-patterns))
- **Environment**: Expose `extraEnv` when service has a lot of configurable env vars (see [Option Patterns](#option-patterns))
- **OIDC**: Never expose Authelia URLs or internal auth settings — auto-set when OIDC enabled
## Documentation & Testing
- Update `modules/<name>/README.md` when adding options with short description, reference to Github and website (if present) and a short usage example
- Add new module to root level README.md
- Test: add to `module_list.nix` and `ci_config.nix`, then run:
```bash
nix flake check --keep-going
nix build .#homeConfigurations.ci.activationPackage
```
## Secrets Handling
- Use `config.sops.secrets."path/to/secret".path` for sops-nix examples
- Use `dummySecretFile` in `ci_config.nix` for CI testing
- Never commit real secrets
### Secret File Options
Always use `lib.types.path` for secret options to support reading secrets from files at runtime, e.g. from sops-nix decrypted files:
```nix
secretKeyFile = lib.mkOption {
type = lib.types.path;
description = "Secret key. Generate with `openssl rand -hex 16`";
};
clientSecretFile = (import ../authelia/options.nix lib).clientSecretFile;
db.passwordFile = lib.mkOption { type = lib.types.path; description = "Database password file"; };
```
## Reference Modules
Use these modules as examples for implementing specific patterns:
| Module | Demonstrates |
| --------------------------------- | -------------------------------------------- |
| `modules/example/default.nix` | Complete module structure (the template) |
| `modules/streaming/default.nix` | Multi-container stack with dependencies |
| `modules/gatus/default.nix` | Full DB pattern (postgres/sqlite) + extraEnv |
| `modules/mealie/default.nix` | OIDC with admin/user groups |
| `modules/vaultwarden/default.nix` | OIDC userGroup only (no admin mapping) |
| `modules/paperless/default.nix` | Full database pattern + secretKeyFile |
| `modules/blocky/default.nix` | Config files (YAML settings generation) |
| `modules/aiostreams/default.nix` | extraEnv usage patterns |
## Renovate Configuration
When a project has multiple related container images (e.g., app + server), add them to the "Multi-Container Projects" group in `renovate.json` so they are updated together in a single PR.