scripts: add create-host tool for automated host configuration generation
Some checks failed
Run nix flake check / flake-check (push) Failing after 1m50s
Run nix flake check / flake-check (pull_request) Failing after 1m49s

Implements Phase 2 of the automated deployment pipeline.

This commit adds a Python CLI tool that automates the creation of NixOS host
configurations, eliminating manual boilerplate and reducing errors.

Features:
- Python CLI using typer framework with rich terminal UI
- Comprehensive validation (hostname format/uniqueness, IP subnet/uniqueness)
- Jinja2 templates for NixOS configurations
- Automatic updates to flake.nix and terraform/vms.tf
- Support for both static IP and DHCP configurations
- Dry-run mode for safe previews
- Packaged as Nix derivation and added to devShell

Usage:
  create-host --hostname myhost --ip 10.69.13.50/24

The tool generates:
- hosts/<hostname>/default.nix
- hosts/<hostname>/configuration.nix
- Updates flake.nix with new nixosConfigurations entry
- Updates terraform/vms.tf with new VM definition

All generated configurations include full system imports (monitoring, SOPS,
autoupgrade, etc.) and are validated with nix flake check and tofu validate.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 02:27:57 +01:00
parent b20ad9c275
commit 408554b477
15 changed files with 1040 additions and 17 deletions

View File

@@ -0,0 +1,100 @@
"""Text manipulation for flake.nix and Terraform files."""
import re
from pathlib import Path
from models import HostConfig
def update_flake_nix(config: HostConfig, repo_root: Path) -> None:
"""
Add new host entry to flake.nix nixosConfigurations.
Args:
config: Host configuration
repo_root: Path to repository root
"""
flake_path = repo_root / "flake.nix"
content = flake_path.read_text()
# Find the closing of nixosConfigurations block
# Pattern: " };\n packages ="
pattern = r"( \};)\n( packages =)"
# Create new entry
new_entry = f""" {config.hostname} = nixpkgs.lib.nixosSystem {{
inherit system;
specialArgs = {{
inherit inputs self sops-nix;
}};
modules = [
(
{{ config, pkgs, ... }}:
{{
nixpkgs.overlays = commonOverlays;
}}
)
./hosts/{config.hostname}
sops-nix.nixosModules.sops
];
}};
"""
# Insert new entry before closing brace
replacement = rf"\g<1>\n{new_entry}\g<2>"
new_content, count = re.subn(pattern, replacement, content)
if count == 0:
raise ValueError(
"Could not find insertion point in flake.nix. "
"Looking for pattern: ' };\\n devShells ='"
)
flake_path.write_text(new_content)
def update_terraform_vms(config: HostConfig, repo_root: Path) -> None:
"""
Add new VM entry to terraform/vms.tf locals.vms map.
Args:
config: Host configuration
repo_root: Path to repository root
"""
terraform_path = repo_root / "terraform" / "vms.tf"
content = terraform_path.read_text()
# Find the closing of locals.vms block
# Pattern: " }\n\n # Compute VM configurations"
pattern = r"( \})\n\n( # Compute VM configurations)"
# Create new entry based on whether we have static IP or DHCP
if config.is_static_ip:
new_entry = f''' "{config.hostname}" = {{
ip = "{config.ip}"
cpu_cores = {config.cpu}
memory = {config.memory}
disk_size = "{config.disk}"
}}
'''
else:
new_entry = f''' "{config.hostname}" = {{
cpu_cores = {config.cpu}
memory = {config.memory}
disk_size = "{config.disk}"
}}
'''
# Insert new entry before closing brace
replacement = rf"{new_entry}\g<1>\n\n\g<2>"
new_content, count = re.subn(pattern, replacement, content)
if count == 0:
raise ValueError(
"Could not find insertion point in terraform/vms.tf. "
"Looking for pattern: ' }\\n\\n # Compute VM configurations'"
)
terraform_path.write_text(new_content)