310 lines
8.2 KiB
Markdown
310 lines
8.2 KiB
Markdown
|
|
# 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
|