Add --ssh-pubkey FILE argument

This commit is contained in:
Luis Hebendanz 2024-04-23 18:55:00 +02:00
parent 0bf9a566eb
commit 4f6d25160f
15 changed files with 192 additions and 31 deletions

View File

@ -41,7 +41,7 @@
}; };
testScript = '' testScript = ''
start_all() start_all()
machine.succeed("clan --flake ${../..} flash --debug --yes --disk main /dev/vdb test_install_machine") machine.succeed("clan --debug --flake ${../..} flash --yes --disk main /dev/vdb test_install_machine")
''; '';
} { inherit pkgs self; }; } { inherit pkgs self; };
}; };

View File

@ -20,6 +20,7 @@
boot = { boot = {
size = "1M"; size = "1M";
type = "EF02"; # for grub MBR type = "EF02"; # for grub MBR
priority = 1;
}; };
ESP = { ESP = {
size = "512M"; size = "512M";

View File

@ -15,7 +15,7 @@
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nix-community", "owner": "Qubasa",
"repo": "disko", "repo": "disko",
"type": "github" "type": "github"
} }
@ -55,6 +55,22 @@
"type": "github" "type": "github"
} }
}, },
"nixos-2311": {
"locked": {
"lastModified": 1713397591,
"narHash": "sha256-1P6Plf9a9KwgERtuijPpET/s4AwIZUYqIu1nuVJqPPU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "392320f29b07e74131d4e4a7b435e8e9b9b85adf",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixos-generators": { "nixos-generators": {
"inputs": { "inputs": {
"nixlib": "nixlib", "nixlib": "nixlib",
@ -76,6 +92,27 @@
"type": "github" "type": "github"
} }
}, },
"nixos-images": {
"inputs": {
"nixos-2311": "nixos-2311",
"nixos-unstable": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1713523007,
"narHash": "sha256-kEnwogkcBn0omgIsGo3zbfAP9nJTDUhp+Q9QWXxsUd0=",
"owner": "nix-community",
"repo": "nixos-images",
"rev": "f064936faf1d12452212030c38e8c325f5b4dfe5",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixos-images",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1714290118, "lastModified": 1714290118,
@ -97,6 +134,7 @@
"disko": "disko", "disko": "disko",
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"nixos-generators": "nixos-generators", "nixos-generators": "nixos-generators",
"nixos-images": "nixos-images",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"sops-nix": "sops-nix", "sops-nix": "sops-nix",
"treefmt-nix": "treefmt-nix" "treefmt-nix": "treefmt-nix"

View File

@ -8,14 +8,15 @@
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small";
disko.url = "github:Qubasa/disko";
disko.url = "github:nix-community/disko";
disko.inputs.nixpkgs.follows = "nixpkgs"; disko.inputs.nixpkgs.follows = "nixpkgs";
sops-nix.url = "github:Mic92/sops-nix"; sops-nix.url = "github:Mic92/sops-nix";
sops-nix.inputs.nixpkgs.follows = "nixpkgs"; sops-nix.inputs.nixpkgs.follows = "nixpkgs";
sops-nix.inputs.nixpkgs-stable.follows = ""; sops-nix.inputs.nixpkgs-stable.follows = "";
nixos-generators.url = "github:nix-community/nixos-generators"; nixos-generators.url = "github:nix-community/nixos-generators";
nixos-generators.inputs.nixpkgs.follows = "nixpkgs"; nixos-generators.inputs.nixpkgs.follows = "nixpkgs";
nixos-images.url = "github:nix-community/nixos-images";
nixos-images.inputs.nixos-unstable.follows = "nixpkgs";
flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts.url = "github:hercules-ci/flake-parts";
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
treefmt-nix.url = "github:numtide/treefmt-nix"; treefmt-nix.url = "github:numtide/treefmt-nix";

View File

@ -16,7 +16,7 @@
(modulesPath + "/profiles/installation-device.nix") (modulesPath + "/profiles/installation-device.nix")
(modulesPath + "/profiles/all-hardware.nix") (modulesPath + "/profiles/all-hardware.nix")
(modulesPath + "/profiles/base.nix") (modulesPath + "/profiles/base.nix")
(modulesPath + "/installer/cd-dvd/iso-image.nix") #(modulesPath + "/installer/cd-dvd/iso-image.nix")
]; ];
services.openssh.settings.PermitRootLogin = "yes"; services.openssh.settings.PermitRootLogin = "yes";
system.activationScripts.root-password = '' system.activationScripts.root-password = ''
@ -65,5 +65,5 @@
cat /var/shared/qrcode.utf8 cat /var/shared/qrcode.utf8
fi fi
''; '';
isoImage.squashfsCompression = "zstd"; #isoImage.squashfsCompression = "zstd";
} }

View File

@ -43,6 +43,7 @@ let
boot = { boot = {
size = "1M"; size = "1M";
type = "EF02"; # for grub MBR type = "EF02"; # for grub MBR
priority = 1; # Needs to be first partition
}; };
ESP = { ESP = {
size = "100M"; size = "100M";

View File

@ -58,6 +58,7 @@ def create_parser(prog: str | None = None) -> argparse.ArgumentParser:
"--debug", "--debug",
help="Enable debug logging", help="Enable debug logging",
action="store_true", action="store_true",
default=False,
) )
parser.add_argument( parser.add_argument(

View File

@ -1,8 +1,8 @@
import argparse import argparse
import importlib import importlib
import json
import logging import logging
import os import os
import shlex
import shutil import shutil
import textwrap import textwrap
from collections.abc import Sequence from collections.abc import Sequence
@ -20,8 +20,48 @@ from .nix import nix_shell
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def list_available_ssh_keys(ssh_dir: Path = Path("~/.ssh").expanduser()) -> list[Path]:
"""
Function to list all available SSH public keys in the default .ssh directory.
Returns a list of paths to available public key files.
"""
public_key_patterns = ["*.pub"]
available_keys: list[Path] = []
# Check for public key files
for pattern in public_key_patterns:
for key_path in ssh_dir.glob(pattern):
if key_path.is_file():
available_keys.append(key_path)
return available_keys
def read_public_key_contents(public_keys: list[Path]) -> list[str]:
"""
Function to read and return the contents of available SSH public keys.
Returns a list containing the contents of each public key.
"""
public_key_contents = []
for key_path in public_keys:
try:
with open(key_path.expanduser()) as key_file:
public_key_contents.append(key_file.read().strip())
except FileNotFoundError:
log.error(f"Public key file not found: {key_path}")
return public_key_contents
def flash_machine( def flash_machine(
machine: Machine, mode: str, disks: dict[str, str], dry_run: bool, debug: bool machine: Machine,
*,
mode: str,
disks: dict[str, str],
system_config: dict[str, Any],
dry_run: bool,
debug: bool,
) -> None: ) -> None:
secret_facts_module = importlib.import_module(machine.secret_facts_module) secret_facts_module = importlib.import_module(machine.secret_facts_module)
secret_facts_store: SecretStoreBase = secret_facts_module.SecretStore( secret_facts_store: SecretStoreBase = secret_facts_module.SecretStore(
@ -58,12 +98,17 @@ def flash_machine(
disko_install.extend(["--extra-files", str(local_dir), upload_dir]) disko_install.extend(["--extra-files", str(local_dir), upload_dir])
disko_install.extend(["--flake", str(machine.flake) + "#" + machine.name]) disko_install.extend(["--flake", str(machine.flake) + "#" + machine.name])
disko_install.extend(["--mode", str(mode)]) disko_install.extend(["--mode", str(mode)])
disko_install.extend(
[
"--system-config",
json.dumps(system_config),
]
)
cmd = nix_shell( cmd = nix_shell(
["nixpkgs#disko"], ["nixpkgs#disko"],
disko_install, disko_install,
) )
print("$", " ".join(map(shlex.quote, cmd)))
run(cmd, log=Log.BOTH, error_msg=f"Failed to flash {machine}") run(cmd, log=Log.BOTH, error_msg=f"Failed to flash {machine}")
@ -72,6 +117,7 @@ class FlashOptions:
flake: Path flake: Path
machine: str machine: str
disks: dict[str, str] disks: dict[str, str]
ssh_keys_path: list[Path]
dry_run: bool dry_run: bool
confirm: bool confirm: bool
debug: bool debug: bool
@ -99,11 +145,13 @@ def flash_command(args: argparse.Namespace) -> None:
flake=args.flake, flake=args.flake,
machine=args.machine, machine=args.machine,
disks=args.disk, disks=args.disk,
ssh_keys_path=args.ssh_pubkey,
dry_run=args.dry_run, dry_run=args.dry_run,
confirm=not args.yes, confirm=not args.yes,
debug=args.debug, debug=args.debug,
mode=args.mode, mode=args.mode,
) )
machine = Machine(opts.machine, flake=opts.flake) machine = Machine(opts.machine, flake=opts.flake)
if opts.confirm and not opts.dry_run: if opts.confirm and not opts.dry_run:
disk_str = ", ".join(f"{name}={device}" for name, device in opts.disks.items()) disk_str = ", ".join(f"{name}={device}" for name, device in opts.disks.items())
@ -114,8 +162,38 @@ def flash_command(args: argparse.Namespace) -> None:
ask = input(msg) ask = input(msg)
if ask != "y": if ask != "y":
return return
root_keys = read_public_key_contents(opts.ssh_keys_path)
if opts.confirm and not root_keys:
msg = "Should we add your SSH public keys to the root user? [y/N] "
ask = input(msg)
if ask == "y":
pubkeys = list_available_ssh_keys()
root_keys.extend(read_public_key_contents(pubkeys))
elif not opts.confirm and not root_keys:
pubkeys = list_available_ssh_keys()
root_keys.extend(read_public_key_contents(pubkeys))
# If ssh-pubkeys set, we don't need to ask for confirmation
elif opts.confirm and root_keys:
pass
elif not opts.confirm and root_keys:
pass
else:
raise ClanError("Invalid state")
user_keys = {
"users": {
"users": {"root": {"openssh": {"authorizedKeys": {"keys": root_keys}}}}
}
}
flash_machine( flash_machine(
machine, opts.mode, disks=opts.disks, dry_run=opts.dry_run, debug=opts.debug machine,
mode=opts.mode,
disks=opts.disks,
system_config=user_keys,
dry_run=opts.dry_run,
debug=opts.debug,
) )
@ -147,7 +225,13 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
choices=["format", "mount"], choices=["format", "mount"],
default="format", default="format",
) )
parser.add_argument(
"--ssh-pubkey",
type=Path,
action="append",
default=[],
help="ssh pubkey file to add to the root user. Can be used multiple times",
)
parser.add_argument( parser.add_argument(
"--yes", "--yes",
action="store_true", action="store_true",
@ -160,10 +244,4 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
default=False, default=False,
action="store_true", action="store_true",
) )
parser.add_argument(
"--debug",
help="Print debug information",
default=False,
action="store_true",
)
parser.set_defaults(func=flash_command) parser.set_defaults(func=flash_command)

View File

@ -106,13 +106,7 @@ def nix_shell(packages: list[str], cmd: list[str]) -> list[str]:
if os.environ.get("IN_NIX_SANDBOX"): if os.environ.get("IN_NIX_SANDBOX"):
return cmd return cmd
return [ return [
*nix_command( *nix_command(["shell", "--offline", "--inputs-from", f"{nixpkgs_flake()!s}"]),
[
"shell",
"--inputs-from",
f"{nixpkgs_flake()!s}",
]
),
*packages, *packages,
"-c", "-c",
*cmd, *cmd,

View File

@ -23,11 +23,11 @@
zbar, zbar,
tor, tor,
git, git,
nixpkgs,
qemu, qemu,
gnupg, gnupg,
e2fsprogs, e2fsprogs,
mypy, mypy,
nixpkgs,
clan-core-path, clan-core-path,
}: }:
let let
@ -95,17 +95,16 @@ let
description = "dependencies for the clan-cli"; description = "dependencies for the clan-cli";
inputs = { inputs = {
nixpkgs.url = "nixpkgs"; nixpkgs.url = "path://${nixpkgs}";
}; };
outputs = _inputs: { }; outputs = _inputs: { };
} }
EOF EOF
ln -s ${nixpkgs} $out/path ln -s ${nixpkgs} $out/path
nix flake lock $out \ nix flake update $out \
--store ./. \ --store ./. \
--extra-experimental-features 'nix-command flakes' \ --extra-experimental-features 'nix-command flakes'
--override-input nixpkgs ${nixpkgs}
''; '';
in in
python3.pkgs.buildPythonApplication { python3.pkgs.buildPythonApplication {

View File

@ -38,6 +38,7 @@
''; '';
in in
{ {
devShells.clan-cli = pkgs.callPackage ./shell.nix { inherit (self'.packages) clan-cli; }; devShells.clan-cli = pkgs.callPackage ./shell.nix { inherit (self'.packages) clan-cli; };
packages = { packages = {
clan-cli = pkgs.python3.pkgs.callPackage ./default.nix { clan-cli = pkgs.python3.pkgs.callPackage ./default.nix {

View File

@ -12,6 +12,7 @@ let
rope rope
setuptools setuptools
wheel wheel
ipdb
pip pip
]); ]);
in in
@ -21,6 +22,8 @@ mkShell {
ruff ruff
] ++ devshellTestDeps; ] ++ devshellTestDeps;
PYTHONBREAKPOINT = "ipdb.set_trace";
shellHook = '' shellHook = ''
export GIT_ROOT="$(git rev-parse --show-toplevel)" export GIT_ROOT="$(git rev-parse --show-toplevel)"
export PKG_ROOT="$GIT_ROOT/pkgs/clan-cli" export PKG_ROOT="$GIT_ROOT/pkgs/clan-cli"

View File

@ -40,6 +40,8 @@ mkShell {
desktop-file-utils # verify desktop files desktop-file-utils # verify desktop files
]); ]);
PYTHONBREAKPOINT = "ipdb.set_trace";
shellHook = '' shellHook = ''
export GIT_ROOT=$(git rev-parse --show-toplevel) export GIT_ROOT=$(git rev-parse --show-toplevel)
export PKG_ROOT=$GIT_ROOT/pkgs/clan-vm-manager export PKG_ROOT=$GIT_ROOT/pkgs/clan-vm-manager

View File

@ -1,4 +1,5 @@
{ ... }: { ... }:
{ {
imports = [ imports = [
./clan-cli/flake-module.nix ./clan-cli/flake-module.nix

View File

@ -1,12 +1,22 @@
{ self, lib, ... }: { self, lib, ... }:
let let
installerModule = installerModule =
{ config, ... }: {
config,
pkgs,
modulesPath,
...
}:
{ {
imports = [ imports = [
self.nixosModules.installer self.nixosModules.installer
self.inputs.nixos-generators.nixosModules.all-formats self.inputs.nixos-generators.nixosModules.all-formats
self.inputs.disko.nixosModules.disko
(modulesPath + "/installer/cd-dvd/iso-image.nix")
]; ];
isoImage.squashfsCompression = "zstd";
# Provide convenience for connecting to wifi # Provide convenience for connecting to wifi
networking.wireless.enable = false; networking.wireless.enable = false;
@ -34,9 +44,35 @@ let
{ disko.memSize = 4096; } # FIXME: otherwise the image builder goes OOM { disko.memSize = 4096; } # FIXME: otherwise the image builder goes OOM
]; ];
}; };
flashInstallerModule =
{ config, pkgs, ... }:
{
imports = [
self.nixosModules.installer
self.clanModules.diskLayouts
];
# Provide convenience for connecting to wifi
networking.wireless.enable = false;
# Use iwd instead of wpa_supplicant. It has a user friendly CLI
networking.wireless.iwd = {
settings = {
Network = {
EnableIPv6 = true;
RoutePriorityOffset = 300;
};
Settings = {
AutoConnect = true;
};
};
enable = true;
};
system.stateVersion = config.system.nixos.version;
nixpkgs.pkgs = self.inputs.nixpkgs.legacyPackages.x86_64-linux;
};
in in
{ {
clan = { clan = {
clanName = "clan-core"; clanName = "clan-core";
directory = self; directory = self;
@ -44,6 +80,11 @@ in
imports = [ installerModule ]; imports = [ installerModule ];
fileSystems."/".device = lib.mkDefault "/dev/null"; fileSystems."/".device = lib.mkDefault "/dev/null";
}; };
machines.flash-installer = {
imports = [ flashInstallerModule ];
clan.diskLayouts.singleDiskExt4.device = "/dev/sda";
boot.loader.grub.enable = lib.mkForce true;
};
}; };
flake.packages.x86_64-linux.install-iso = installer.config.formats.iso; flake.packages.x86_64-linux.install-iso = installer.config.formats.iso;
flake.apps.x86_64-linux.install-vm.program = installer.config.formats.vm.outPath; flake.apps.x86_64-linux.install-vm.program = installer.config.formats.vm.outPath;