From d1f624ad1fb36540495a14949e9f997d37964c16 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 3 Dec 2024 12:01:24 +0100 Subject: [PATCH] api/disk-templates: adopt directory structure from clan modules --- docs/mkdocs.yml | 5 +- docs/site/manual/disk-templates.md | 94 +++++++++++++++++++ pkgs/clan-cli/clan_cli/api/disk.py | 57 +++++++---- templates/disk/single-disk/README.md | 23 +++++ .../default.nix} | 0 5 files changed, 159 insertions(+), 20 deletions(-) create mode 100644 docs/site/manual/disk-templates.md create mode 100644 templates/disk/single-disk/README.md rename templates/disk/{single-disk.nix => single-disk/default.nix} (100%) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 461aee185..25d5d0bd0 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -58,8 +58,9 @@ nav: - Secrets: manual/secrets.md - Secure Boot: manual/secure-boot.md - Flake-parts: manual/flake-parts.md - - Authoring ClanModules: - - clanmodules/index.md + - Authoring: + - Modules: clanmodules/index.md + - Disk Templates: manual/disk-templates.md - Contribute: manual/contribute.md # - Concepts: # - Overview: concepts/index.md diff --git a/docs/site/manual/disk-templates.md b/docs/site/manual/disk-templates.md new file mode 100644 index 000000000..1ce8e8597 --- /dev/null +++ b/docs/site/manual/disk-templates.md @@ -0,0 +1,94 @@ +# Disk Templates + + +!!! Danger "!!! Under construction !!!" + Currently under construction use with caution + + +## Structure + +A disk template consists of exactly two files + +- `default.nix` +- `README.md` + +```sh +└── single-disk + ├── default.nix + └── README.md +``` + +## `default.nix` + +Placeholders are filled with their machine specific options when a template is used for a machine. + +The user can choose any valid options from the hardware report. + +The file itself is then copied to `machines/{machineName}/disko.nix` and will be automatically loaded by the machine. + +`single-disk/default.nix` +``` +{ + disko.devices = { + disk = { + main = { + device = "{{mainDisk}}"; + ... + }; + }; + }; +} +``` + +## Placeholders + +Each template must declare the options of its placeholders depending on the hardware-report. + +`api/disk.py` +```py +templates: dict[str, dict[str, Callable[[dict[str, Any]], Placeholder]]] = { + "single-disk": { + # Placeholders + "mainDisk": lambda hw_report: Placeholder( + label="Main disk", options=hw_main_disk_options(hw_report), required=True + ), + } +} +``` + +Introducing new local or global placeholders requires contributing to clan-core `api/disks.py`. + +### Predefined placeholders + +Some placeholders provide predefined functionality + +- `uuid`: In most cases we recommend adding a unique id to all disks. This prevents the system to false boot from i.e. hot-plugged devices. + ``` + disko.devices = { + disk = { + main = { + name = "main-{{uuid}}"; + ... + } + } + } + ``` + + +## Readme + +The readme frontmatter must be of the same format as modules frontmatter. + +```markdown +--- +description = "Simple disk schema for single disk setups" +--- + +# Single disk + +Use this schema for simple setups where .... + +``` + + +The format and fields of this file is not clear yet. We might change that once fully implemented. \ No newline at end of file diff --git a/pkgs/clan-cli/clan_cli/api/disk.py b/pkgs/clan-cli/clan_cli/api/disk.py index b8408c25b..07427f2ff 100644 --- a/pkgs/clan-cli/clan_cli/api/disk.py +++ b/pkgs/clan-cli/clan_cli/api/disk.py @@ -7,6 +7,7 @@ from typing import Any from uuid import uuid4 from clan_cli.api import API +from clan_cli.api.modules import Frontmatter, extract_frontmatter from clan_cli.dirs import TemplateType, clan_templates from clan_cli.errors import ClanError from clan_cli.git import commit_file @@ -29,14 +30,13 @@ def get_best_unix_device_name(unix_device_names: list[str]) -> str: return unix_device_names[0] -def hw_main_disk_options(hw_report: dict) -> list[str]: +def hw_main_disk_options(hw_report: dict) -> list[str] | None: + options: list[str] = [] if not disk_in_facter_report(hw_report): - msg = "hw_report doesnt include 'disk' information" - raise ClanError(msg, description=f"{hw_report.keys()}") + return None disks = hw_report["hardware"]["disk"] - options: list[str] = [] for disk in disks: unix_device_names = disk["unix_device_names"] device_name = get_best_unix_device_name(unix_device_names) @@ -56,6 +56,8 @@ class Placeholder: @dataclass class DiskSchema: name: str + readme: str + frontmatter: Frontmatter placeholders: dict[str, Placeholder] @@ -71,27 +73,31 @@ templates: dict[str, dict[str, Callable[[dict[str, Any]], Placeholder]]] = { @API.register -def get_disk_schemas(base_path: Path, machine_name: str) -> dict[str, DiskSchema]: +def get_disk_schemas( + base_path: Path, machine_name: str | None = None +) -> dict[str, DiskSchema]: """ Get the available disk schemas """ disk_templates = clan_templates(TemplateType.DISK) disk_schemas = {} - - hw_report_path = HardwareConfig.NIXOS_FACTER.config_path(base_path, machine_name) - if not hw_report_path.exists(): - msg = "Hardware configuration missing" - raise ClanError(msg) - hw_report = {} - with hw_report_path.open("r") as hw_report_file: - hw_report = json.load(hw_report_file) + + if machine_name is not None: + hw_report_path = HardwareConfig.NIXOS_FACTER.config_path( + base_path, machine_name + ) + if not hw_report_path.exists(): + msg = "Hardware configuration missing" + raise ClanError(msg) + with hw_report_path.open("r") as hw_report_file: + hw_report = json.load(hw_report_file) for disk_template in disk_templates.iterdir(): - if disk_template.is_file(): + if disk_template.is_dir(): schema_name = disk_template.stem if schema_name not in templates: - msg = f"Disk schema {schema_name} not found in templates" + msg = f"Disk schema {schema_name} not found in templates {templates.keys()}" raise ClanError( msg, description="This is an internal architecture problem. Because disk schemas dont define their own interface", @@ -103,8 +109,16 @@ def get_disk_schemas(base_path: Path, machine_name: str) -> dict[str, DiskSchema if placeholder_getters: placeholders = {k: v(hw_report) for k, v in placeholder_getters.items()} + raw_readme = (disk_template / "README.md").read_text() + frontmatter, readme = extract_frontmatter( + raw_readme, f"{disk_template}/README.md" + ) + disk_schemas[schema_name] = DiskSchema( - name=schema_name, placeholders=placeholders + name=schema_name, + placeholders=placeholders, + readme=readme, + frontmatter=frontmatter, ) return disk_schemas @@ -120,7 +134,7 @@ def set_machine_disk_schema( placeholders: dict[str, str], force: bool = False, ) -> None: - """ + """ " Set the disk placeholders of the template """ # Assert the hw-config must exist before setting the disk @@ -135,7 +149,7 @@ def set_machine_disk_schema( msg = "Hardware configuration must use type FACTER for applying disk schema automatically" raise ClanError(msg) - disk_schema_path = clan_templates(TemplateType.DISK) / f"{schema_name}.nix" + disk_schema_path = clan_templates(TemplateType.DISK) / f"{schema_name}/default.nix" if not disk_schema_path.exists(): msg = f"Disk schema not found at {disk_schema_path}" @@ -169,6 +183,12 @@ def set_machine_disk_schema( ) raise ClanError(msg, description=f"Valid options: {ph.options}") + header = f"""# --- +# schema = "{schema_name}"; +# --- +# This file was automatically generated! +# CHANGING this configuration requires wiping and reinstalling the machine +""" with disk_schema_path.open("r") as disk_template: config_str = disk_template.read() for placeholder_name, placeholder_value in placeholders.items(): @@ -186,6 +206,7 @@ def set_machine_disk_schema( raise ClanError(msg, description="Use 'force' to overwrite") with disko_file_path.open("w") as disk_config: + disk_config.write(header) disk_config.write(config_str) commit_file( diff --git a/templates/disk/single-disk/README.md b/templates/disk/single-disk/README.md new file mode 100644 index 000000000..098efb5a2 --- /dev/null +++ b/templates/disk/single-disk/README.md @@ -0,0 +1,23 @@ +--- +description = "Simple single disk schema" +--- +# Description + +This schema defines a GPT-based disk layout. + +### **Disk Overview** + +- **Name**: `main-{{uuid}}` +- **Device**: `{{mainDisk}}` + +### **Partitions** + +1. **EFI System Partition (ESP)** + - Size: `500M`. + - Filesystem: `vfat`. + - Mount Point: `/boot` (secure `umask=0077`). + +2. **Root Partition** + - Size: Remaining disk space (`100%`). + - Filesystem: `ext4`. + - Mount Point: `/`. diff --git a/templates/disk/single-disk.nix b/templates/disk/single-disk/default.nix similarity index 100% rename from templates/disk/single-disk.nix rename to templates/disk/single-disk/default.nix