clan flash: Remove root requirement for flash, add a flash-template

This commit is contained in:
Qubasa
2024-09-24 13:42:21 +02:00
parent cef290b135
commit 1b0b111f03
10 changed files with 191 additions and 73 deletions

View File

@@ -1,27 +1,21 @@
import logging
import os
import shutil
from collections.abc import Generator
from contextlib import contextmanager
from pathlib import Path
from clan_cli.cmd import run
from clan_cli.cmd import Log, run
from clan_cli.errors import ClanError
log = logging.getLogger(__name__)
@contextmanager
def pause_automounting(
devices: list[Path], no_udev: bool
) -> Generator[None, None, None]:
def pause_automounting(devices: list[Path]) -> Generator[None, None, None]:
"""
Pause automounting on the device for the duration of this context
manager
"""
if no_udev:
yield None
return
if shutil.which("udevadm") is None:
msg = "udev is required to disable automounting"
@@ -29,37 +23,18 @@ def pause_automounting(
yield None
return
if os.geteuid() != 0:
msg = "root privileges are required to disable automounting"
inhibit_path = Path(__file__).parent / "inhibit.sh"
if not inhibit_path.exists():
msg = f"{inhibit_path} not found"
raise ClanError(msg)
try:
# See /usr/lib/udisks2/udisks2-inhibit
rules_dir = Path("/run/udev/rules.d")
rules_dir.mkdir(exist_ok=True)
rule_files: list[Path] = []
for device in devices:
devpath: str = str(device)
rule_file: Path = (
rules_dir / f"90-udisks-inhibit-{devpath.replace('/', '_')}.rules"
)
with rule_file.open("w") as fd:
print(
'SUBSYSTEM=="block", ENV{DEVNAME}=="'
+ devpath
+ '*", ENV{UDISKS_IGNORE}="1"',
file=fd,
)
fd.flush()
os.fsync(fd.fileno())
rule_files.append(rule_file)
run(["udevadm", "control", "--reload"])
run(["udevadm", "trigger", "--settle", "--subsystem-match=block"])
yield None
except Exception as ex:
log.fatal(ex)
finally:
for rule_file in rule_files:
rule_file.unlink(missing_ok=True)
run(["udevadm", "control", "--reload"], check=False)
run(["udevadm", "trigger", "--settle", "--subsystem-match=block"], check=False)
str_devs = [str(dev) for dev in devices]
cmd = ["sudo", str(inhibit_path), "enable", *str_devs]
result = run(cmd, log=Log.BOTH, check=False)
if result.returncode != 0:
log.error("Failed to inhibit automounting")
yield None
cmd = ["sudo", str(inhibit_path), "disable", *str_devs]
result = run(cmd, log=Log.BOTH, check=False)
if result.returncode != 0:
log.error("Failed to re-enable automounting")

View File

@@ -11,9 +11,11 @@ from typing import Any
from clan_cli.api import API
from clan_cli.cmd import Log, run
from clan_cli.errors import ClanError
from clan_cli.facts.generate import generate_facts
from clan_cli.facts.secret_modules import SecretStoreBase
from clan_cli.machines.machines import Machine
from clan_cli.nix import nix_shell
from clan_cli.vars.generate import generate_vars_for_machine
from .automount import pause_automounting
from .list import list_possible_keymaps, list_possible_languages
@@ -24,7 +26,6 @@ log = logging.getLogger(__name__)
@dataclass
class WifiConfig:
ssid: str
password: str
@dataclass
@@ -51,19 +52,21 @@ def flash_machine(
dry_run: bool,
write_efi_boot_entries: bool,
debug: bool,
no_udev: bool = False,
extra_args: list[str] | None = None,
) -> None:
devices = [Path(disk.device) for disk in disks]
with pause_automounting(devices, no_udev):
with pause_automounting(devices):
if extra_args is None:
extra_args = []
system_config_nix: dict[str, Any] = {}
generate_vars_for_machine(machine, generator_name=None, regenerate=False)
generate_facts([machine], service=None, regenerate=False)
if system_config.wifi_settings:
wifi_settings = {}
wifi_settings: dict[str, dict[str, str]] = {}
for wifi in system_config.wifi_settings:
wifi_settings[wifi.ssid] = {"password": wifi.password}
wifi_settings[wifi.ssid] = {}
system_config_nix["clan"] = {"iwd": {"networks": wifi_settings}}
if system_config.language:

View File

@@ -26,7 +26,6 @@ class FlashOptions:
mode: str
write_efi_boot_entries: bool
nix_options: list[str]
no_udev: bool
system_config: SystemConfig
@@ -73,15 +72,11 @@ def flash_command(args: argparse.Namespace) -> None:
wifi_settings=None,
),
write_efi_boot_entries=args.write_efi_boot_entries,
no_udev=args.no_udev,
nix_options=args.option,
)
if args.wifi:
opts.system_config.wifi_settings = [
WifiConfig(ssid=ssid, password=password)
for ssid, password in args.wifi.items()
]
opts.system_config.wifi_settings = [WifiConfig(ssid=ssid) for ssid in args.wifi]
machine = Machine(opts.machine, flake=opts.flake)
if opts.confirm and not opts.dry_run:
@@ -102,7 +97,6 @@ def flash_command(args: argparse.Namespace) -> None:
dry_run=opts.dry_run,
debug=opts.debug,
write_efi_boot_entries=opts.write_efi_boot_entries,
no_udev=opts.no_udev,
extra_args=opts.nix_options,
)
@@ -135,11 +129,9 @@ def register_flash_apply_parser(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"--wifi",
type=str,
nargs=2,
metavar=("ssid", "password"),
action=AppendDiskAction,
help="wifi network to connect to",
default={},
action="append",
help="wifi ssid to connect to",
default=[],
)
parser.add_argument(
"--mode",
@@ -160,12 +152,6 @@ def register_flash_apply_parser(parser: argparse.ArgumentParser) -> None:
type=str,
help="system language",
)
parser.add_argument(
"--no-udev",
help="Disable udev rules to block automounting",
default=False,
action="store_true",
)
parser.add_argument(
"--keymap",
type=str,

View File

@@ -0,0 +1,78 @@
#!/usr/bin/env bash
# A function to log error messages
log_fatal() {
echo "FATAL: $*" >&2
}
# A function to log warning messages
log_warning() {
echo "WARNING: $*" >&2
}
# Function to enable inhibition (pause automounting)
enable_inhibition() {
local devices=("$@")
if ! command -v udevadm &> /dev/null; then
log_fatal "udev is required to disable automounting"
exit 1
fi
if [ "$EUID" -ne 0 ]; then
log_fatal "Root privileges are required to disable automounting"
exit 1
fi
local rules_dir="/run/udev/rules.d"
mkdir -p "$rules_dir"
for device in "${devices[@]}"; do
local devpath="$device"
local rule_file="$rules_dir/90-udisks-inhibit-${devpath//\//_}.rules"
echo 'SUBSYSTEM=="block", ENV{DEVNAME}=="'"$devpath"'*", ENV{UDISKS_IGNORE}="1"' > "$rule_file"
sync
done
udevadm control --reload
udevadm trigger --settle --subsystem-match=block
}
# Function to disable inhibition (cleanup)
disable_inhibition() {
local devices=("$@")
local rules_dir="/run/udev/rules.d"
for device in "${devices[@]}"; do
local devpath="$device"
local rule_file="$rules_dir/90-udisks-inhibit-${devpath//\//_}.rules"
rm -f "$rule_file" || log_warning "Could not remove file: $rule_file"
done
udevadm control --reload || log_warning "Could not reload udev rules"
udevadm trigger --settle --subsystem-match=block || log_warning "Could not trigger udev settle"
}
if [ "$#" -lt 2 ]; then
echo "Usage: $0 [enable|disable] /dev/sdX ..."
exit 1
fi
action=$1
shift
devices=("$@")
case "$action" in
enable)
enable_inhibition "${devices[@]}"
;;
disable)
disable_inhibition "${devices[@]}"
;;
*)
echo "Invalid action: $action"
echo "Usage: $0 [enable|disable] /dev/sdX ..."
exit 1
;;
esac