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>
89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
"""File generation using Jinja2 templates."""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from jinja2 import Environment, BaseLoader, TemplateNotFound
|
|
|
|
from models import HostConfig
|
|
|
|
|
|
class PackageTemplateLoader(BaseLoader):
|
|
"""Custom Jinja2 loader that works with both dev and installed packages."""
|
|
|
|
def __init__(self):
|
|
# Try to find templates in multiple locations
|
|
self.template_dirs = []
|
|
|
|
# Location 1: Development (scripts/create-host/templates)
|
|
dev_dir = Path(__file__).parent / "templates"
|
|
if dev_dir.exists():
|
|
self.template_dirs.append(dev_dir)
|
|
|
|
# Location 2: Installed via Nix (../share/create-host/templates from bin dir)
|
|
# When installed via Nix, __file__ is in lib/python3.X/site-packages/
|
|
# and templates are in ../../../share/create-host/templates
|
|
for site_path in sys.path:
|
|
site_dir = Path(site_path)
|
|
# Try to find the Nix store path
|
|
if "site-packages" in str(site_dir):
|
|
# Go up to the package root (e.g., /nix/store/xxx-create-host-0.1.0)
|
|
pkg_root = site_dir.parent.parent.parent
|
|
share_templates = pkg_root / "share" / "create-host" / "templates"
|
|
if share_templates.exists():
|
|
self.template_dirs.append(share_templates)
|
|
|
|
# Location 3: Fallback - sys.path templates
|
|
for site_path in sys.path:
|
|
site_templates = Path(site_path) / "templates"
|
|
if site_templates.exists():
|
|
self.template_dirs.append(site_templates)
|
|
|
|
def get_source(self, environment, template):
|
|
for template_dir in self.template_dirs:
|
|
template_path = template_dir / template
|
|
if template_path.exists():
|
|
mtime = template_path.stat().st_mtime
|
|
source = template_path.read_text()
|
|
return source, str(template_path), lambda: mtime == template_path.stat().st_mtime
|
|
|
|
raise TemplateNotFound(template)
|
|
|
|
|
|
def generate_host_files(config: HostConfig, repo_root: Path) -> None:
|
|
"""
|
|
Generate host configuration files from templates.
|
|
|
|
Args:
|
|
config: Host configuration
|
|
repo_root: Path to repository root
|
|
"""
|
|
# Setup Jinja2 environment with custom loader
|
|
env = Environment(
|
|
loader=PackageTemplateLoader(),
|
|
trim_blocks=True,
|
|
lstrip_blocks=True,
|
|
)
|
|
|
|
# Create host directory
|
|
host_dir = repo_root / "hosts" / config.hostname
|
|
host_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Generate default.nix
|
|
default_template = env.get_template("default.nix.j2")
|
|
default_content = default_template.render(hostname=config.hostname)
|
|
(host_dir / "default.nix").write_text(default_content)
|
|
|
|
# Generate configuration.nix
|
|
config_template = env.get_template("configuration.nix.j2")
|
|
config_content = config_template.render(
|
|
hostname=config.hostname,
|
|
domain=config.domain,
|
|
nameservers=config.nameservers,
|
|
is_static_ip=config.is_static_ip,
|
|
ip=config.ip,
|
|
gateway=config.gateway,
|
|
state_version=config.state_version,
|
|
)
|
|
(host_dir / "configuration.nix").write_text(config_content)
|