From a511c65d8451f9e25274e9cf72fa6b49ce8b4de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Jane=C5=BEi=C4=8D?= Date: Mon, 2 Mar 2026 16:35:14 +0100 Subject: [PATCH] wip --- .gitignore | 3 + .gitlab-ci.yml | 69 +++++++++ dist/make-seed.sh | 42 ++++++ dist/nix-cheatsheet.md | 42 ++++++ dist/run.sh | 183 +++++++++++++++++++++++ flake.nix | 9 ++ hosts/matej-nixos/configuration.nix | 3 + hosts/matej-tower/configuration.nix | 2 + hosts/sandbox/configuration.nix | 177 ++++++++++++++++++++++ hosts/sandbox/hardware-configuration.nix | 21 +++ justfile | 21 +++ modules/nixos/aarch64-vm.nix | 14 ++ modules/nixos/seed-ssh.nix | 69 +++++++++ modules/nixos/vm-guest.nix | 62 ++++++++ users/gorazd/home-manager.nix | 52 ------- users/matej/home-manager.nix | 3 + users/sandbox/home-manager.nix | 21 +++ 17 files changed, 741 insertions(+), 52 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100755 dist/make-seed.sh create mode 100644 dist/nix-cheatsheet.md create mode 100755 dist/run.sh create mode 100644 hosts/sandbox/configuration.nix create mode 100644 hosts/sandbox/hardware-configuration.nix create mode 100644 modules/nixos/aarch64-vm.nix create mode 100644 modules/nixos/seed-ssh.nix create mode 100644 modules/nixos/vm-guest.nix delete mode 100644 users/gorazd/home-manager.nix create mode 100644 users/sandbox/home-manager.nix diff --git a/.gitignore b/.gitignore index e8c8f8d..157e6d5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ result-* # Ignore automatically generated direnv output .direnv +# Ignore generated seed ISOs +*.iso + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..49a79d5 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,69 @@ +stages: + - build + - upload + - release + +build-x86_64: + stage: build + image: ubuntu:24.04 + script: + - apt-get update && apt-get install -y curl xz-utils sudo + - curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install linux --no-confirm + - . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + - nix build .#nixosConfigurations.sandbox.config.system.build.image --out-link result-x86_64 + - cp $(find -L result-x86_64 -name '*.qcow2') sandbox-x86_64.qcow2 + artifacts: + paths: + - sandbox-x86_64.qcow2 + expire_in: 1 week + +build-aarch64: + stage: build + image: ubuntu:24.04 + tags: + - aarch64 + allow_failure: true + script: + - apt-get update && apt-get install -y curl xz-utils sudo + - curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install linux --no-confirm + - . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + - nix build .#nixosConfigurations.sandbox-aarch64.config.system.build.image --out-link result-aarch64 + - cp $(find -L result-aarch64 -name '*.qcow2') sandbox-aarch64.qcow2 + artifacts: + paths: + - sandbox-aarch64.qcow2 + expire_in: 1 week + +upload: + stage: upload + image: curlimages/curl:latest + rules: + - if: $CI_COMMIT_TAG + script: + - | + curl --header "JOB-TOKEN: $CI_JOB_TOKEN" \ + --upload-file sandbox-x86_64.qcow2 \ + "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/sandbox/${CI_COMMIT_TAG}/sandbox-x86_64.qcow2" + - | + if [ -f sandbox-aarch64.qcow2 ]; then + curl --header "JOB-TOKEN: $CI_JOB_TOKEN" \ + --upload-file sandbox-aarch64.qcow2 \ + "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/sandbox/${CI_COMMIT_TAG}/sandbox-aarch64.qcow2" + fi + +release: + stage: release + image: registry.gitlab.com/gitlab-org/release-cli:latest + rules: + - if: $CI_COMMIT_TAG + script: + - echo "Creating release $CI_COMMIT_TAG" + release: + tag_name: $CI_COMMIT_TAG + description: "Sandbox VM $CI_COMMIT_TAG" + assets: + links: + - name: sandbox-x86_64.qcow2 + url: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/sandbox/${CI_COMMIT_TAG}/sandbox-x86_64.qcow2" + - name: sandbox-aarch64.qcow2 + url: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/sandbox/${CI_COMMIT_TAG}/sandbox-aarch64.qcow2" diff --git a/dist/make-seed.sh b/dist/make-seed.sh new file mode 100755 index 0000000..9becd1b --- /dev/null +++ b/dist/make-seed.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + echo "Usage: make-seed.sh ..." + echo "Creates a seed ISO with the given SSH public keys." + exit 1 +} + +[ "${1:-}" ] || usage +[ "${2:-}" ] || usage + +OUTPUT="$1" +shift + +TMPDIR=$(mktemp -d) +trap 'rm -rf "$TMPDIR"' EXIT + +: > "$TMPDIR/authorized_keys" +for PUBKEY_FILE in "$@"; do + if [ ! -f "$PUBKEY_FILE" ]; then + echo "error: public key file not found: $PUBKEY_FILE" + exit 1 + fi + cat "$PUBKEY_FILE" >> "$TMPDIR/authorized_keys" +done + +if command -v mkisofs >/dev/null 2>&1; then + ISO_CMD="mkisofs" +elif command -v genisoimage >/dev/null 2>&1; then + ISO_CMD="genisoimage" +else + echo "error: mkisofs or genisoimage required" + echo " linux: sudo apt install genisoimage" + echo " macos: brew install cdrtools" + echo " nix: nix shell nixpkgs#cdrtools" + exit 1 +fi + +"$ISO_CMD" -quiet -V SEEDCONFIG -J -R -o "$OUTPUT" "$TMPDIR" + +echo "seed ISO created: $OUTPUT" diff --git a/dist/nix-cheatsheet.md b/dist/nix-cheatsheet.md new file mode 100644 index 0000000..dba1f00 --- /dev/null +++ b/dist/nix-cheatsheet.md @@ -0,0 +1,42 @@ +# Nix package manager cheatsheet + +## Search for packages + +```sh +nix search nixpkgs python +``` + +## Add packages to current shell + +```sh +# single package +nix shell nixpkgs#python3 + +# multiple packages +nix shell nixpkgs#python3 nixpkgs#nodejs +``` + +## Run a command directly + +```sh +nix run nixpkgs#python3 -- --version +``` + +## Install packages persistently (available in new SSH sessions) + +```sh +# nix shell packages are lost when the session ends +# use profile install to persist across sessions +# packages use the nixpkgs# prefix +nix profile install nixpkgs#nodejs nixpkgs#pnpm nixpkgs#yarn +``` + +## Language stacks + +| Stack | Packages | +|-------|----------| +| Node.js | `nodejs`, `pnpm`, `yarn` | +| Python | `python3`, `python3Packages.pip`, `python3Packages.virtualenv`, `uv` | +| Go | `go` | +| Rust | `cargo`, `rustc`, `rustfmt`, `clippy` | +| Foundry | `foundry` | diff --git a/dist/run.sh b/dist/run.sh new file mode 100755 index 0000000..848019d --- /dev/null +++ b/dist/run.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# defaults +SSH_PORT=2222 +MEMORY=8G +CPUS=4 +PROJECTS="" +CLAUDE_DIR="" +SSH_KEYS=() +SEED_ISO="" +IMAGE="" +GUEST_ARCH="" + +usage() { + cat < [options] + +Options: + --arch Guest architecture (auto-detected from image name) + --ssh-key SSH public key (repeatable, auto-generates seed ISO) + --seed-iso Pre-built seed ISO (alternative to --ssh-key) + --projects Mount host directory as ~/projects in VM + --claude-dir Mount host .claude directory for auth + --memory VM memory (default: 8G) + --cpus VM CPUs (default: 4) + --ssh-port SSH port forward (default: 2222) +EOF + exit 1 +} + +[ "${1:-}" ] || usage + +IMAGE="$1" +shift + +while [ $# -gt 0 ]; do + case "$1" in + --arch) GUEST_ARCH="$2"; shift 2 ;; + --ssh-key) SSH_KEYS+=("$2"); shift 2 ;; + --seed-iso) SEED_ISO="$2"; shift 2 ;; + --projects) PROJECTS="$2"; shift 2 ;; + --claude-dir) CLAUDE_DIR="$2"; shift 2 ;; + --memory) MEMORY="$2"; shift 2 ;; + --cpus) CPUS="$2"; shift 2 ;; + --ssh-port) SSH_PORT="$2"; shift 2 ;; + *) echo "unknown option: $1"; usage ;; + esac +done + +if [ ! -f "$IMAGE" ]; then + echo "error: image not found: $IMAGE" + exit 1 +fi + +# detect guest architecture from image filename if not specified +if [ -z "$GUEST_ARCH" ]; then + case "$IMAGE" in + *aarch64*|*arm64*) GUEST_ARCH="aarch64" ;; + *x86_64*|*amd64*) GUEST_ARCH="x86_64" ;; + *) + # fallback to host architecture + case "$(uname -m)" in + x86_64|amd64) GUEST_ARCH="x86_64" ;; + aarch64|arm64) GUEST_ARCH="aarch64" ;; + *) echo "error: cannot detect guest arch, use --arch"; exit 1 ;; + esac + ;; + esac +fi + +# normalize +case "$GUEST_ARCH" in + x86_64|amd64) GUEST_ARCH="x86_64" ;; + aarch64|arm64) GUEST_ARCH="aarch64" ;; + *) echo "error: unsupported architecture: $GUEST_ARCH"; exit 1 ;; +esac + +# platform detection +HOST_ARCH=$(uname -m) +OS=$(uname -s) +ACCEL="tcg" + +case "$OS" in + Linux) + [ -r /dev/kvm ] && ACCEL="kvm" + ;; + Darwin) + # hvf only works when guest matches host + case "$HOST_ARCH" in + aarch64|arm64) [ "$GUEST_ARCH" = "aarch64" ] && ACCEL="hvf" ;; + x86_64|amd64) [ "$GUEST_ARCH" = "x86_64" ] && ACCEL="hvf" ;; + esac + ;; +esac + +case "$GUEST_ARCH" in + x86_64) QEMU_BIN="qemu-system-x86_64" ;; + aarch64) QEMU_BIN="qemu-system-aarch64" ;; +esac + +# auto-generate seed ISO from SSH key +CLEANUP_SEED="" +if [ "${#SSH_KEYS[@]}" -gt 0 ] && [ -z "$SEED_ISO" ]; then + SEED_ISO="$(mktemp -d)/seed.iso" + CLEANUP_SEED="$SEED_ISO" + bash "$SCRIPT_DIR/make-seed.sh" "$SEED_ISO" "${SSH_KEYS[@]}" +fi + +cleanup() { + [ -n "$CLEANUP_SEED" ] && rm -rf "$(dirname "$CLEANUP_SEED")" +} +trap cleanup EXIT + +# build qemu command +QEMU_ARGS=( + "$QEMU_BIN" + -accel "$ACCEL" + -m "$MEMORY" + -smp "$CPUS" + -drive "file=$IMAGE,format=qcow2,snapshot=on" + -nic "user,hostfwd=tcp::${SSH_PORT}-:22" + -nographic +) + +# aarch64 guest needs machine type and uefi firmware +if [ "$GUEST_ARCH" = "aarch64" ]; then + if [ "$ACCEL" = "hvf" ]; then + QEMU_ARGS+=(-machine virt -cpu host) + else + QEMU_ARGS+=(-machine virt -cpu max) + fi + + EFI_CODE="" + for p in \ + /opt/homebrew/share/qemu/edk2-aarch64-code.fd \ + /usr/local/share/qemu/edk2-aarch64-code.fd \ + /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \ + /usr/share/AAVMF/AAVMF_CODE.fd; do + [ -f "$p" ] && EFI_CODE="$p" && break + done + + if [ -z "$EFI_CODE" ]; then + echo "error: aarch64 EFI firmware not found" + echo " macos: brew install qemu" + echo " linux: apt install qemu-efi-aarch64" + exit 1 + fi + QEMU_ARGS+=(-bios "$EFI_CODE") +fi + +# seed ISO +if [ -n "$SEED_ISO" ]; then + QEMU_ARGS+=(-drive "file=$SEED_ISO,format=raw,media=cdrom,readonly=on") +fi + +# 9p mounts — none lets the guest control ownership via dfltuid/dfltgid +SECURITY_MODEL="none" + +if [ -n "$PROJECTS" ]; then + QEMU_ARGS+=( + -virtfs "local,path=$PROJECTS,mount_tag=projects,security_model=$SECURITY_MODEL,id=fs0" + ) +fi + +if [ -n "$CLAUDE_DIR" ]; then + QEMU_ARGS+=( + -virtfs "local,path=$CLAUDE_DIR,mount_tag=hostclaude,security_model=$SECURITY_MODEL,id=fs1" + ) + # also mount parent home for .claude.json + QEMU_ARGS+=( + -virtfs "local,path=$(dirname "$CLAUDE_DIR"),mount_tag=hosthome,security_model=$SECURITY_MODEL,id=fs2,readonly=on" + ) +fi + +echo "---" +echo "Guest: $GUEST_ARCH | Accel: $ACCEL" +echo "SSH: ssh -p $SSH_PORT sandbox@localhost" +echo "---" + +exec "${QEMU_ARGS[@]}" diff --git a/flake.nix b/flake.nix index 19c52b3..7b37e33 100644 --- a/flake.nix +++ b/flake.nix @@ -93,6 +93,15 @@ system = "x86_64-linux"; users = [ ]; }; + + sandbox-x86_64 = mkHost "sandbox" { + system = "x86_64-linux"; + users = [ "sandbox" ]; + }; + sandbox-aarch64 = mkHost "sandbox" { + system = "aarch64-linux"; + users = [ "sandbox" ]; + }; }; nixosModules = import ./modules/nixos { diff --git a/hosts/matej-nixos/configuration.nix b/hosts/matej-nixos/configuration.nix index 5fe2c82..44c60cd 100644 --- a/hosts/matej-nixos/configuration.nix +++ b/hosts/matej-nixos/configuration.nix @@ -26,6 +26,7 @@ in inputs.self.nixosModules.nvidia inputs.self.nixosModules.initrd-ssh inputs.self.nixosModules.localisation + inputs.self.nixosModules.aarch64-vm ]; yubikey.enable = true; @@ -65,6 +66,8 @@ in base16Scheme = "${pkgs.base16-schemes}/share/themes/gruvbox-material-dark-medium.yaml"; }; + aarch64-vm.enable = true; + boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; diff --git a/hosts/matej-tower/configuration.nix b/hosts/matej-tower/configuration.nix index 10c4b66..281ad39 100644 --- a/hosts/matej-tower/configuration.nix +++ b/hosts/matej-tower/configuration.nix @@ -24,6 +24,7 @@ inputs.self.nixosModules.workstation inputs.self.nixosModules.initrd-ssh inputs.self.nixosModules.localisation + inputs.self.nixosModules.aarch64-vm ]; yubikey.enable = true; @@ -38,6 +39,7 @@ command = "sway"; }; sway.enable = true; + aarch64-vm.enable = true; initrd-ssh = { enable = true; diff --git a/hosts/sandbox/configuration.nix b/hosts/sandbox/configuration.nix new file mode 100644 index 0000000..d34ad55 --- /dev/null +++ b/hosts/sandbox/configuration.nix @@ -0,0 +1,177 @@ +{ + pkgs, + lib, + inputs, + config, + ... +}: +{ + imports = [ + ./hardware-configuration.nix + inputs.self.nixosModules.vm-guest + inputs.self.nixosModules.seed-ssh + inputs.self.nixosModules.localisation + ]; + + networking.hostName = "sandbox"; + + vm-guest = { + enable = true; + headless = true; + }; + + seed-ssh = { + enable = true; + user = "sandbox"; + }; + + localisation = { + enable = true; + timeZone = "UTC"; + defaultLocale = "en_US.UTF-8"; + }; + + users.users.sandbox = { + isNormalUser = true; + extraGroups = [ + "wheel" + "docker" + ]; + }; + + # 9p mounts — silently fail if shares not provided at runtime + fileSystems."/mnt/9p-projects" = { + device = "projects"; + fsType = "9p"; + options = [ + "trans=virtio" + "version=9p2000.L" + "msize=65536" + "nofail" + "x-systemd.automount" + "x-systemd.device-timeout=2s" + ]; + }; + + # remap ownership so sandbox user can read/write regardless of host UID + fileSystems."/home/sandbox/projects" = { + device = "/mnt/9p-projects"; + fsType = "fuse.bindfs"; + options = [ + "force-user=sandbox" + "force-group=users" + "perms=0755" + "create-for-user=sandbox" + "create-for-group=users" + "nofail" + "x-systemd.automount" + ]; + }; + + fileSystems."/mnt/host-claude" = { + device = "hostclaude"; + fsType = "9p"; + options = [ + "trans=virtio" + "version=9p2000.L" + "msize=65536" + "nofail" + "x-systemd.device-timeout=2s" + ]; + }; + + fileSystems."/mnt/host-home" = { + device = "hosthome"; + fsType = "9p"; + options = [ + "trans=virtio" + "version=9p2000.L" + "msize=65536" + "nofail" + "x-systemd.device-timeout=2s" + "ro" + ]; + }; + + # pre-auth claude-code from host config + systemd.services.claude-auth = { + description = "Copy claude-code credentials from host mount"; + after = [ + "mnt-host\\x2dclaude.mount" + "mnt-host\\x2dhome.mount" + ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = + let + mountpoint = "${pkgs.util-linux}/bin/mountpoint"; + in + pkgs.writeShellScript "claude-auth" '' + # wait for mounts to appear + for i in $(seq 1 10); do + ${mountpoint} -q /mnt/host-claude && break + ${mountpoint} -q /mnt/host-home && break + sleep 1 + done + + if ! ${mountpoint} -q /mnt/host-claude && ! ${mountpoint} -q /mnt/host-home; then + echo "no host mounts found, skipping" + exit 0 + fi + + mkdir -p /home/sandbox/.claude + if ${mountpoint} -q /mnt/host-claude; then + cp -a /mnt/host-claude/. /home/sandbox/.claude/ + fi + if ${mountpoint} -q /mnt/host-home; then + cp /mnt/host-home/.claude.json /home/sandbox/.claude.json || true + fi + chown -R sandbox:users /home/sandbox/.claude /home/sandbox/.claude.json 2>/dev/null || true + ''; + }; + }; + + virtualisation.docker = { + enable = true; + logDriver = "json-file"; + }; + + environment.systemPackages = with pkgs; [ + bindfs + claude-code + + # tools + tmux + fd + ripgrep + jq + fzf + just + ]; + + # image builder VM needs more than the default 1G to copy closure + image.modules = + let + imageMemOverride = + { config, modulesPath, ... }: + { + system.build.image = lib.mkForce ( + import (modulesPath + "/../lib/make-disk-image.nix") { + inherit lib config pkgs; + inherit (config.virtualisation) diskSize; + inherit (config.image) baseName format; + partitionTableType = if config.image.efiSupport then "efi" else "legacy"; + memSize = 16384; + } + ); + }; + in + { + qemu = imageMemOverride; + qemu-efi = imageMemOverride; + }; + + system.stateVersion = "25.11"; +} diff --git a/hosts/sandbox/hardware-configuration.nix b/hosts/sandbox/hardware-configuration.nix new file mode 100644 index 0000000..16eba00 --- /dev/null +++ b/hosts/sandbox/hardware-configuration.nix @@ -0,0 +1,21 @@ +{ + lib, + pkgs, + modulesPath, + ... +}: +{ + imports = [ + (modulesPath + "/profiles/qemu-guest.nix") + ]; + + fileSystems."/" = { + device = "/dev/disk/by-label/nixos"; + autoResize = true; + fsType = "ext4"; + }; + + # image.modules (disk-image.nix) overrides boot loader per variant + # x86_64: qemu (grub), aarch64: qemu-efi (systemd-boot) + boot.loader.grub.device = lib.mkDefault "/dev/vda"; +} diff --git a/justfile b/justfile index c79400b..5b9ce83 100644 --- a/justfile +++ b/justfile @@ -36,3 +36,24 @@ iso: # garbage collect old generations clean: sudo nix-collect-garbage $(nix eval --raw -f ./nix.nix nix.gc.options) + +# build sandbox VM image +sandbox-build arch: + #!/usr/bin/env bash + set -euo pipefail + if [ "{{arch}}" = "x86_64" ]; then + nixos-rebuild build-image --image-variant qemu --flake .#sandbox-x86_64 + elif [ "{{arch}}" = "aarch64" ]; then + nixos-rebuild build-image --image-variant qemu-efi --flake .#sandbox-aarch64 + else + echo "error: arch must be x86_64 or aarch64"; exit 1 + fi + ln -sfn "$(readlink result)" "result-sandbox-{{arch}}" + +# run sandbox VM +sandbox-run arch *ARGS: + bash dist/run.sh $(find -L result-sandbox-{{arch}} -name '*.qcow2' | head -1) {{ARGS}} + +# ssh into running sandbox +sandbox-ssh: + ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null sandbox@localhost diff --git a/modules/nixos/aarch64-vm.nix b/modules/nixos/aarch64-vm.nix new file mode 100644 index 0000000..ec043a7 --- /dev/null +++ b/modules/nixos/aarch64-vm.nix @@ -0,0 +1,14 @@ +{ + lib, + config, + ... +}: +{ + options = { + aarch64-vm.enable = lib.mkEnableOption "aarch64 virtualisation support"; + }; + + config = lib.mkIf config.aarch64-vm.enable { + boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; + }; +} diff --git a/modules/nixos/seed-ssh.nix b/modules/nixos/seed-ssh.nix new file mode 100644 index 0000000..8c0c23a --- /dev/null +++ b/modules/nixos/seed-ssh.nix @@ -0,0 +1,69 @@ +{ + pkgs, + lib, + config, + ... +}: +{ + options = { + seed-ssh = { + enable = lib.mkEnableOption "SSH key injection from seed ISO"; + + user = lib.mkOption { + type = lib.types.str; + description = "user to install authorized_keys for"; + }; + + label = lib.mkOption { + type = lib.types.str; + default = "SEEDCONFIG"; + description = "volume label of the seed ISO"; + }; + }; + }; + + config = lib.mkIf config.seed-ssh.enable { + systemd.services.seed-ssh = { + description = "Install SSH authorized_keys from seed ISO"; + after = [ + "local-fs.target" + "nss-user-lookup.target" + ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = + let + cfg = config.seed-ssh; + inherit (cfg) user; + inherit (config.users.users.${user}) home group; + in + pkgs.writeShellScript "seed-ssh" '' + # try by-label first, then scan block devices for the volume label + DEVICE="/dev/disk/by-label/${cfg.label}" + if [ ! -e "$DEVICE" ]; then + DEVICE=$(${pkgs.util-linux}/bin/blkid -t LABEL="${cfg.label}" -o device | head -1) + fi + + if [ -z "$DEVICE" ] || [ ! -e "$DEVICE" ]; then + echo "seed ISO not found, skipping" + exit 0 + fi + + MOUNT=$(mktemp -d) + ${pkgs.util-linux}/bin/mount -o ro "$DEVICE" "$MOUNT" + + mkdir -p "${home}/.ssh" + cp "$MOUNT/authorized_keys" "${home}/.ssh/authorized_keys" + chmod 700 "${home}/.ssh" + chmod 600 "${home}/.ssh/authorized_keys" + chown -R ${user}:${group} "${home}/.ssh" + + ${pkgs.util-linux}/bin/umount "$MOUNT" + rmdir "$MOUNT" + ''; + }; + }; + }; +} diff --git a/modules/nixos/vm-guest.nix b/modules/nixos/vm-guest.nix new file mode 100644 index 0000000..63c08ec --- /dev/null +++ b/modules/nixos/vm-guest.nix @@ -0,0 +1,62 @@ +{ + pkgs, + lib, + config, + ... +}: +{ + + options = { + vm-guest = { + enable = lib.mkEnableOption "VM guest configuration"; + headless = lib.mkOption { + type = lib.types.bool; + default = false; + description = "run without display, serial console only"; + }; + }; + }; + + config = lib.mkIf config.vm-guest.enable { + services.qemuGuest.enable = true; + services.spice-vdagentd.enable = lib.mkIf (!config.vm-guest.headless) true; + + boot.kernelParams = lib.mkIf config.vm-guest.headless [ "console=ttyS0,115200" ]; + + # 9p for host file mounting + boot.initrd.availableKernelModules = [ + "9p" + "9pnet_virtio" + ]; + boot.kernelModules = [ + "9p" + "9pnet_virtio" + ]; + + # ssh with agent forwarding for git and hot-mount + services.openssh = { + enable = true; + ports = [ 22 ]; + settings = { + PasswordAuthentication = false; + PermitRootLogin = "no"; + AllowAgentForwarding = true; + StreamLocalBindUnlink = "yes"; + }; + }; + + networking = { + useDHCP = true; + firewall.allowedTCPPorts = [ 22 ]; + }; + + security.sudo.wheelNeedsPassword = false; + + environment.systemPackages = with pkgs; [ + curl + wget + htop + sshfs + ]; + }; +} diff --git a/users/gorazd/home-manager.nix b/users/gorazd/home-manager.nix deleted file mode 100644 index 850ece1..0000000 --- a/users/gorazd/home-manager.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ - config, - lib, - pkgs, - inputs, - ... -}: - -let - packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system}; -in - -{ - home.stateVersion = "24.11"; - - home.packages = [ - pkgs.git - ]; - - programs.neovim = { - enable = true; - vimAlias = true; - defaultEditor = true; - - package = inputs.neovim-nightly-overlay.packages.${pkgs.stdenv.hostPlatform.system}.default; - - extraPackages = with pkgs; [ - # runtime deps - fzf - ripgrep - gnumake - gcc - luajit - - lua-language-server - nil - nixd - - nixpkgs-fmt - stylua - - ]; - - extraWrapperArgs = [ - "--suffix" - "LD_LIBRARY_PATH" - ":" - "${lib.makeLibraryPath [ pkgs.stdenv.cc.cc.lib ]}" - ]; - - }; -} diff --git a/users/matej/home-manager.nix b/users/matej/home-manager.nix index d5a4045..e90f0e1 100644 --- a/users/matej/home-manager.nix +++ b/users/matej/home-manager.nix @@ -17,6 +17,9 @@ in services.dunst.enable = true; home.packages = [ + pkgs.qemu + pkgs.cdrtools + pkgs.bibata-cursors pkgs.starship diff --git a/users/sandbox/home-manager.nix b/users/sandbox/home-manager.nix new file mode 100644 index 0000000..cb03e73 --- /dev/null +++ b/users/sandbox/home-manager.nix @@ -0,0 +1,21 @@ +{ + pkgs, + ... +}: +{ + home.stateVersion = "25.11"; + + home.packages = with pkgs; [ + git + tmux + ripgrep + fd + jq + ]; + + programs.neovim = { + enable = true; + vimAlias = true; + defaultEditor = true; + }; +}