api/disk-templates: adopt directory structure from clan modules
This commit is contained in:
@@ -58,8 +58,9 @@ nav:
|
|||||||
- Secrets: manual/secrets.md
|
- Secrets: manual/secrets.md
|
||||||
- Secure Boot: manual/secure-boot.md
|
- Secure Boot: manual/secure-boot.md
|
||||||
- Flake-parts: manual/flake-parts.md
|
- Flake-parts: manual/flake-parts.md
|
||||||
- Authoring ClanModules:
|
- Authoring:
|
||||||
- clanmodules/index.md
|
- Modules: clanmodules/index.md
|
||||||
|
- Disk Templates: manual/disk-templates.md
|
||||||
- Contribute: manual/contribute.md
|
- Contribute: manual/contribute.md
|
||||||
# - Concepts:
|
# - Concepts:
|
||||||
# - Overview: concepts/index.md
|
# - Overview: concepts/index.md
|
||||||
|
|||||||
94
docs/site/manual/disk-templates.md
Normal file
94
docs/site/manual/disk-templates.md
Normal file
@@ -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.
|
||||||
@@ -7,6 +7,7 @@ from typing import Any
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from clan_cli.api import API
|
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.dirs import TemplateType, clan_templates
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
from clan_cli.git import commit_file
|
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]
|
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):
|
if not disk_in_facter_report(hw_report):
|
||||||
msg = "hw_report doesnt include 'disk' information"
|
return None
|
||||||
raise ClanError(msg, description=f"{hw_report.keys()}")
|
|
||||||
|
|
||||||
disks = hw_report["hardware"]["disk"]
|
disks = hw_report["hardware"]["disk"]
|
||||||
|
|
||||||
options: list[str] = []
|
|
||||||
for disk in disks:
|
for disk in disks:
|
||||||
unix_device_names = disk["unix_device_names"]
|
unix_device_names = disk["unix_device_names"]
|
||||||
device_name = get_best_unix_device_name(unix_device_names)
|
device_name = get_best_unix_device_name(unix_device_names)
|
||||||
@@ -56,6 +56,8 @@ class Placeholder:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class DiskSchema:
|
class DiskSchema:
|
||||||
name: str
|
name: str
|
||||||
|
readme: str
|
||||||
|
frontmatter: Frontmatter
|
||||||
placeholders: dict[str, Placeholder]
|
placeholders: dict[str, Placeholder]
|
||||||
|
|
||||||
|
|
||||||
@@ -71,27 +73,31 @@ templates: dict[str, dict[str, Callable[[dict[str, Any]], Placeholder]]] = {
|
|||||||
|
|
||||||
|
|
||||||
@API.register
|
@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
|
Get the available disk schemas
|
||||||
"""
|
"""
|
||||||
disk_templates = clan_templates(TemplateType.DISK)
|
disk_templates = clan_templates(TemplateType.DISK)
|
||||||
disk_schemas = {}
|
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 = {}
|
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():
|
for disk_template in disk_templates.iterdir():
|
||||||
if disk_template.is_file():
|
if disk_template.is_dir():
|
||||||
schema_name = disk_template.stem
|
schema_name = disk_template.stem
|
||||||
if schema_name not in templates:
|
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(
|
raise ClanError(
|
||||||
msg,
|
msg,
|
||||||
description="This is an internal architecture problem. Because disk schemas dont define their own interface",
|
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:
|
if placeholder_getters:
|
||||||
placeholders = {k: v(hw_report) for k, v in placeholder_getters.items()}
|
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(
|
disk_schemas[schema_name] = DiskSchema(
|
||||||
name=schema_name, placeholders=placeholders
|
name=schema_name,
|
||||||
|
placeholders=placeholders,
|
||||||
|
readme=readme,
|
||||||
|
frontmatter=frontmatter,
|
||||||
)
|
)
|
||||||
|
|
||||||
return disk_schemas
|
return disk_schemas
|
||||||
@@ -120,7 +134,7 @@ def set_machine_disk_schema(
|
|||||||
placeholders: dict[str, str],
|
placeholders: dict[str, str],
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
""" "
|
||||||
Set the disk placeholders of the template
|
Set the disk placeholders of the template
|
||||||
"""
|
"""
|
||||||
# Assert the hw-config must exist before setting the disk
|
# 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"
|
msg = "Hardware configuration must use type FACTER for applying disk schema automatically"
|
||||||
raise ClanError(msg)
|
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():
|
if not disk_schema_path.exists():
|
||||||
msg = f"Disk schema not found at {disk_schema_path}"
|
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}")
|
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:
|
with disk_schema_path.open("r") as disk_template:
|
||||||
config_str = disk_template.read()
|
config_str = disk_template.read()
|
||||||
for placeholder_name, placeholder_value in placeholders.items():
|
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")
|
raise ClanError(msg, description="Use 'force' to overwrite")
|
||||||
|
|
||||||
with disko_file_path.open("w") as disk_config:
|
with disko_file_path.open("w") as disk_config:
|
||||||
|
disk_config.write(header)
|
||||||
disk_config.write(config_str)
|
disk_config.write(config_str)
|
||||||
|
|
||||||
commit_file(
|
commit_file(
|
||||||
|
|||||||
23
templates/disk/single-disk/README.md
Normal file
23
templates/disk/single-disk/README.md
Normal file
@@ -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: `/`.
|
||||||
Reference in New Issue
Block a user