# 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`): ```nickel 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`): ```nickel 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 ```bash 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**: 1. **Provider Defaults**: Cloud-specific settings (Hetzner, Upcloud, OCI) 2. **Mode Defaults**: Deployment mode settings (solo, multiuser, cicd) 3. **User Overrides**: Workspace-specific and server-specific settings Deep merge preserves nested fields (unlike shallow `&` operator): ```nickel # 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.TaskServDef` for validation ### lib/helpers.ncl - Composition Utilities Core functions for configuration management: - `compose_config`: Deep recursive merge of three records - `validate_required`: Ensure required fields exist - `filter_active_servers`: Remove disabled servers - `get_provider`: Extract provider name from config - `to_flake_path`: Generate NixOS flake directory path - `collect_taskservs`: Extract unique taskserv names - `minimal_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 daemon - `kubernetes`: Container orchestration (roles: master, worker) - `etcd`: Distributed key-value store - `coredns`: DNS resolution - `cilium`: Network policy engine - `rook_ceph`: Ceph storage operator - `registry`: Container image registry - `nfs_client`: NFS client mounts - `postgres`: PostgreSQL database - `redis`: Cache/queue system - `resolv`: DNS configuration - `proxy`: 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 export` on 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 1. Create `lib/myprovider_defaults.ncl`: ```nickel { provider = "myprovider", region = "my-region-1", plan = "standard-plan", cloud_api_enabled = true, } ``` 2. Import in workspace servers: ```nickel let workspace_defaults = import "../lib/myprovider_defaults.ncl" in ``` ### Add Taskserv 1. Define in `taskserv_defaults.ncl`: ```nickel my_service = { name = "my_service", version = "1.0.0", enable = false, } ``` 2. Use in server definition: ```nickel taskservs = [ {name = "my_service", enable = true} | lib.TaskServDef, ] ``` ### Override Server Settings Customize any field in server definition: ```nickel (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 generator - `provisioning/schemas/lib.ncl` - Base library (TaskServDef, Storage, etc.) - `docs/architecture/adr/ADR-011-nickel-migration.md` - Nickel adoption details