Implement ZFS dataset management and snapshot configuration

- Introduced a new `datasets.nix` file to define ZFS datasets and their properties, allowing for centralized management of dataset configurations.
- Updated `default.nix` to import and enable the `zfs_manager` service, integrating it into the system configuration.
- Refactored `zfs.sh` to remove dataset creation commands, delegating dataset management to the `zfs_manager` service.
- Removed the legacy `snapshot_config.toml` file, as snapshot configurations are now handled within `datasets.nix`.
- Modified `vars.nix` to derive mountpoint paths from `datasets.nix`, ensuring consistency across the configuration.
- Created a new `zfs.nix` file to define the `zfs_manager` service and its dependencies.
- Added comprehensive tests for the `zfs_manager` functionality, covering dataset creation, property management, and error handling.
This commit is contained in:
2026-08-19 22:17:13 -04:00
parent 507b23f6ee
commit 0398e5e878
10 changed files with 1372 additions and 173 deletions
+26 -5
View File
@@ -23,6 +23,7 @@ def main(config_file: Path) -> None:
"""Main."""
configure_logger(level="DEBUG")
logger.info("Starting snapshot_manager")
failures: list[str] = []
try:
time_stamp = get_time_stamp()
@@ -34,16 +35,23 @@ def main(config_file: Path) -> None:
msg = f"{dataset.name} failed to create snapshot {time_stamp}"
logger.error(msg)
signal_alert(msg)
failures.append(msg)
continue
count_lookup = get_count_lookup(config_file, dataset.name)
logger.info(f"using {count_lookup} for {dataset.name}")
get_snapshots_to_delete(dataset, count_lookup)
failures.extend(get_snapshots_to_delete(dataset, count_lookup))
except Exception:
logger.exception("snapshot_manager failed")
signal_alert("snapshot_manager failed")
sys.exit(1)
else:
logger.info("snapshot_manager completed")
if failures:
logger.error(f"snapshot_manager completed with {len(failures)} errors")
for failure in failures:
logger.error(f" {failure}")
sys.exit(1)
logger.info("snapshot_manager completed")
def get_count_lookup(config_file: Path, dataset_name: str) -> dict[str, int]:
@@ -92,19 +100,29 @@ def load_config_data(config_file: Path) -> dict[str, dict[str, int]]:
def get_snapshots_to_delete(
dataset: Dataset,
count_lookup: dict[str, int],
) -> None:
) -> list[str]:
"""Get snapshots to delete.
Args:
dataset (Dataset): the dataset
count_lookup (dict[str, int]): the count lookup
Returns:
list[str]: Snapshot deletion failures encountered while pruning.
"""
for retention_class in ("15_min", "hourly", "daily", "monthly"):
count = count_lookup.get(retention_class)
if not isinstance(count, int) or isinstance(count, bool) or count < 0:
error = f"{retention_class} retention must be a non-negative integer, got {count!r}"
raise ValueError(error)
failures: list[str] = []
snapshots = dataset.get_snapshots()
logger.info(f"calculating snapshots for {dataset.name} to be deleted")
if not snapshots:
logger.info(f"{dataset.name} has no snapshots")
return
return failures
filters = (
("15_min", re_compile(r"auto_\d{10}(?:15|30|45)")),
@@ -129,6 +147,9 @@ def get_snapshots_to_delete(
error_message = f"{dataset.name}@{snapshot} failed to delete: {error}"
signal_alert(error_message)
logger.error(error_message)
failures.append(error_message)
return failures
def get_time_stamp() -> str: