8.2 KiB
Nickel Workspace Configuration Templates
This directory contains reusable templates and factories for generating NixOS flakes from Nickel workspace configurations.
Structure
nickel/
├── main.ncl # Workspace factory: compose defaults → mode → overrides
├── servers.ncl # Server definitions template
├── lib/
│ ├── helpers.ncl # Composition helpers (deep merge, validation, filtering)
│ ├── provider_defaults.ncl # Generic provider defaults
│ ├── hetzner_defaults.ncl # Hetzner Cloud pre-configured defaults
│ └── taskserv_defaults.ncl # Taskserv configuration patterns
└── taskservs/
└── (provider-specific overrides)
Quick Start
Create Workspace Config
In your workspace (workspaces/librecloud_hetzner/config/config.ncl):
let provisioning = import "schemas/main.ncl" in
{
workspace_name = "librecloud_hetzner",
deployment_mode = "solo",
provider = "hetzner",
region = "eu-central",
custom_setting = "value",
}
Define Servers
In your workspace (workspaces/librecloud_hetzner/infra/wuji/servers.ncl):
let provisioning = import "schemas/main.ncl" in
let lib = provisioning.lib in
let workspace_hetzner_defaults = import "../lib/hetzner_defaults.ncl" in
{
servers = [
(workspace_hetzner_defaults & {
hostname = "cp-0",
title = "Control Plane 0",
plan = "CX21",
nixos_anywhere_enabled = true,
taskservs = [
{name = "os", profile = "control-plane"} | lib.TaskServDef,
{name = "kubernetes", role = "master"} | lib.TaskServDef,
{name = "etcd"} | lib.TaskServDef,
],
}),
],
}
Generate NixOS Flakes
nu provisioning/scripts/generate-flakes.nu workspaces/librecloud_hetzner
This generates directories for each server:
workspaces/librecloud_hetzner/nixos/cp-0/
├── flake.nix # Flake inputs + outputs
├── configuration.nix # NixOS base config
└── hardware-configuration.nix # Hardware setup (customize)
Composition Pattern
The factory uses three-layer composition:
- Provider Defaults: Cloud-specific settings (Hetzner, Upcloud, OCI)
- Mode Defaults: Deployment mode settings (solo, multiuser, cicd)
- User Overrides: Workspace-specific and server-specific settings
Deep merge preserves nested fields (unlike shallow & operator):
# Shallow merge (❌ loses settings):
{server = {port = 9000}} & {server = {workers = 4}}
# Result: {server = {workers = 4}} port lost!
# Deep merge (✅ preserves nested):
helpers.compose_config
{server = {port = 9000}}
{server = {workers = 4}}
{}
# Result: {server = {port = 9000, workers = 4}}
Core Templates
main.ncl - Workspace Factory
Generates complete workspace config by composing defaults:
- Loads mode-specific defaults (solo/multiuser/cicd)
- Loads provider-specific defaults (Hetzner/Upcloud/OCI)
- Merges with user overrides
- Returns fully composed configuration
servers.ncl - Server Definitions
Template showing the pattern for server definitions:
- Import provider defaults (e.g.,
hetzner_defaults.ncl) - Define servers as merge of defaults + overrides
- Include taskservs array with enabled services
- Use
lib.TaskServDeffor validation
lib/helpers.ncl - Composition Utilities
Core functions for configuration management:
compose_config: Deep recursive merge of three recordsvalidate_required: Ensure required fields existfilter_active_servers: Remove disabled serversget_provider: Extract provider name from configto_flake_path: Generate NixOS flake directory pathcollect_taskservs: Extract unique taskserv namesminimal_server: Create minimal test server record
lib/provider_defaults.ncl - Generic Defaults
Base provider template with fields for all providers:
- Network configuration (IPv4, IPv6)
- Storage defaults (root filesystem, additional volumes)
- Liveness probing (health checks)
- Instance plan/size
- Labels and metadata
- Taskservs list
- Server lifecycle (enabled/disabled)
- Cloud API settings
lib/hetzner_defaults.ncl - Hetzner Cloud
Pre-configured defaults for Hetzner deployments:
- Region: eu-central (customizable)
- Plans: CX11, CX21, CX31, CX41, CX51, CX61, CX81
- Network: IPv6 enabled, private networking
- Storage: Ext4 root filesystem with data partition
- Labels: provisioning=true, environment=production
- Liveness: SSH port 22 probe
lib/taskserv_defaults.ncl - Taskserv Patterns
Configuration templates for common taskservs:
os: Operating system (profiles: worker, control-plane)containerd: Container runtime daemonkubernetes: Container orchestration (roles: master, worker)etcd: Distributed key-value storecoredns: DNS resolutioncilium: Network policy enginerook_ceph: Ceph storage operatorregistry: Container image registrynfs_client: NFS client mountspostgres: PostgreSQL databaseredis: Cache/queue systemresolv: DNS configurationproxy: HTTP gateway (nginx/envoy)webhook: Event hook service
Each taskserv includes version, enablement flag, and profile-specific settings.
Integration with generate-flakes.nu
The script orchestrates three execution phases:
Phase 1: Export
- Executes
nickel exporton servers.ncl - Converts Nickel config to JSON
- Parses JSON into servers array
Phase 2: Generate
- For each server with
nixos_anywhere_enabled = true - Build flake.nix with taskserv inputs and outputs
- Generate configuration.nix with base NixOS settings
- Create hardware-configuration.nix template
Phase 3: Write
- Create workspace directories if needed
- Save three files per server
- Report generation status
Customization Examples
Add New Provider
- Create
lib/myprovider_defaults.ncl:
{
provider = "myprovider",
region = "my-region-1",
plan = "standard-plan",
cloud_api_enabled = true,
}
- Import in workspace servers:
let workspace_defaults = import "../lib/myprovider_defaults.ncl" in
Add Taskserv
- Define in
taskserv_defaults.ncl:
my_service = {
name = "my_service",
version = "1.0.0",
enable = false,
}
- Use in server definition:
taskservs = [
{name = "my_service", enable = true} | lib.TaskServDef,
]
Override Server Settings
Customize any field in server definition:
(workspace_defaults & {
hostname = "custom-host",
plan = "CX51",
taskservs = [
{name = "os", profile = "control-plane"} | lib.TaskServDef,
{name = "kubernetes", role = "master"} | lib.TaskServDef,
],
labels = "custom=true,role=master",
})
Validation
Nickel provides automatic validation via contract types. Invalid configurations are rejected at export time:
- Empty strings rejected by String contract
- Negative numbers rejected by range constraints
- Missing required fields fail during validation
- Type mismatches caught before JSON generation
Dependencies
- Nickel (0.7+): Configuration language and export
- Nushell (0.110.0+): Script orchestration
- nickel export: JSON/YAML output generation
Workflow
User servers.ncl (Nickel)
↓
nickel export → JSON
↓
generate-flakes.nu (Phase 1: Export)
↓
Parse servers array
↓
For each server (Phase 2: Generate)
↓
Generate flake.nix
Generate configuration.nix
Generate hardware-configuration.nix
↓
Phase 3: Write to filesystem
↓
workspaces/{ws}/nixos/{hostname}/
↓
Ready for: nix flake check
Ready for: nixos-anywhere deploy
Files
main.ncl- Workspace composition factory (262 lines)servers.ncl- Server definition template (180 lines)lib/helpers.ncl- Composition utilities (130 lines)lib/provider_defaults.ncl- Generic provider (75 lines)lib/hetzner_defaults.ncl- Hetzner specific (90 lines)lib/taskserv_defaults.ncl- Taskserv patterns (210 lines)
Related
provisioning/scripts/generate-flakes.nu- NixOS flake generatorprovisioning/schemas/lib.ncl- Base library (TaskServDef, Storage, etc.)docs/architecture/adr/ADR-011-nickel-migration.md- Nickel adoption details