clan-cli: add simple flash command

This commit is contained in:
lassulus 2024-02-07 05:24:43 +01:00
parent cd45bb3174
commit b780754621
5 changed files with 159 additions and 2 deletions

View File

@ -69,6 +69,12 @@
json metadata about the vm
'';
};
iso = lib.mkOption {
type = lib.types.path;
description = ''
A generated iso of the machine for the flash command
'';
};
};
};
description = ''
@ -84,6 +90,5 @@
inherit (config.clanCore) secretsUploadDirectory;
};
system.clan.deployment.file = pkgs.writeText "deployment.json" (builtins.toJSON config.system.clan.deployment.data);
};
}

View File

@ -5,6 +5,7 @@
clanCore.imports = [
inputs.sops-nix.nixosModules.sops
./clanCore
./iso
({ pkgs, lib, ... }: {
clanCore.clanPkgs = lib.mkDefault self.packages.${pkgs.hostPlatform.system};
})

View File

@ -0,0 +1,84 @@
{ config, extendModules, lib, pkgs, ... }:
let
# Generates a fileSystems entry for bind mounting a given state folder path
# It binds directories from /var/clanstate/{some-path} to /{some-path}.
# As a result, all state paths will be persisted across reboots, because
# the state folder is mounted from the host system.
mkBindMount = path: {
name = path;
value = {
device = "/var/clanstate/${path}";
options = [ "bind" ];
};
};
# Flatten the list of state folders into a single list
stateFolders = lib.flatten (
lib.mapAttrsToList
(_item: attrs: attrs.folders)
config.clanCore.state
);
# A module setting up bind mounts for all state folders
stateMounts = {
virtualisation.fileSystems =
lib.listToAttrs
(map mkBindMount stateFolders);
};
isoModule = { config, ... }: {
imports = [
stateMounts
];
options.clan.iso.disko = lib.mkOption {
type = lib.types.submodule {
freeformType = (pkgs.formats.json { }).type;
};
default = {
disk = {
iso = {
type = "disk";
imageSize = "10G"; # TODO add auto image size in disko
content = {
type = "gpt";
partitions = {
boot = {
size = "1M";
type = "EF02"; # for grub MBR
};
ESP = {
size = "100M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
};
};
};
config.disko.devices = lib.mkOverride 51 config.clan.iso.disko;
};
isoConfig = extendModules {
modules = [ isoModule ];
};
in
{
config = {
# for clan vm create
system.clan.iso = isoConfig.config.system.build.diskoImages;
};
}

View File

@ -6,7 +6,7 @@ from pathlib import Path
from types import ModuleType
from typing import Any
from . import backups, config, flakes, history, machines, secrets, vms
from . import backups, config, flakes, flash, history, machines, secrets, vms
from .custom_logger import setup_logging
from .dirs import get_clan_flake_toplevel
from .errors import ClanCmdError, ClanError
@ -102,6 +102,11 @@ def create_parser(prog: str | None = None) -> argparse.ArgumentParser:
parser_history = subparsers.add_parser("history", help="manage history")
history.register_parser(parser_history)
parser_flash = subparsers.add_parser(
"flash", help="flash machines to usb sticks or into isos"
)
flash.register_parser(parser_flash)
if argcomplete:
argcomplete.autocomplete(parser)

View File

@ -0,0 +1,62 @@
import argparse
import importlib
import logging
from dataclasses import dataclass
from pathlib import Path
from tempfile import TemporaryDirectory
from .machines.machines import Machine
from .secrets.generate import generate_secrets
log = logging.getLogger(__name__)
def flash_machine(machine: Machine, device: str | None = None) -> None:
secrets_module = importlib.import_module(machine.secrets_module)
secret_store = secrets_module.SecretStore(machine=machine)
generate_secrets(machine)
with TemporaryDirectory() as tmpdir_:
tmpdir = Path(tmpdir_)
upload_dir_ = machine.secrets_upload_directory
if upload_dir_.startswith("/"):
upload_dir_ = upload_dir_[1:]
upload_dir = tmpdir / upload_dir_
upload_dir.mkdir(parents=True)
secret_store.upload(upload_dir)
fs_image = machine.build_nix("config.system.clan.iso")
print(fs_image)
@dataclass
class FlashOptions:
flake: Path
machine: str
device: str | None
def flash_command(args: argparse.Namespace) -> None:
opts = FlashOptions(
flake=args.flake,
machine=args.machine,
device=args.device,
)
machine = Machine(opts.machine, flake=opts.flake)
flash_machine(machine, device=opts.device)
def register_parser(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"machine",
type=str,
help="machine to install",
)
parser.add_argument(
"--device",
type=str,
help="device to flash the system to",
)
parser.set_defaults(func=flash_command)