provisioning-code/templates/workspace/nickel/README.md

310 lines
8.2 KiB
Markdown
Raw Normal View History

# 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