From 197565971553c845e8b5491f4b8ebfb9bb25c25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Jane=C5=BEi=C4=8D?= Date: Sun, 15 Mar 2026 10:05:06 +0100 Subject: [PATCH] feat: ephvm improvements --- hosts/ephvm/configuration.nix | 23 +++--------- justfile | 8 ++--- modules/nixos/vm-9p-automount.nix | 2 +- scripts/ephvm-run.sh | 58 +++++++++++++++---------------- 4 files changed, 37 insertions(+), 54 deletions(-) diff --git a/hosts/ephvm/configuration.nix b/hosts/ephvm/configuration.nix index 406d7be..fc4d6d5 100644 --- a/hosts/ephvm/configuration.nix +++ b/hosts/ephvm/configuration.nix @@ -58,8 +58,11 @@ neovim.dotfiles = inputs.nvim; }; + # ensure .config exists with correct ownership before automount + systemd.tmpfiles.rules = [ "d /home/matej/.config 0755 matej users -" ]; + # writable claude config via 9p - fileSystems."/home/matej/.claude" = { + fileSystems."/home/matej/.config/claude" = { device = "claude"; fsType = "9p"; options = [ @@ -70,23 +73,7 @@ ]; }; - # .claude.json passed via qemu fw_cfg - boot.kernelModules = [ "qemu_fw_cfg" ]; - systemd.services.claude-json = { - after = [ "systemd-modules-load.service" ]; - wants = [ "systemd-modules-load.service" ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = pkgs.writeShellScript "claude-json" '' - src="/sys/firmware/qemu_fw_cfg/by_name/opt/claude.json/raw" - [ -f "$src" ] || exit 0 - cp "$src" /home/matej/.claude.json - chown matej:users /home/matej/.claude.json - ''; - }; - }; + environment.sessionVariables.CLAUDE_CONFIG_DIR = "/home/matej/.config/claude"; system.stateVersion = "25.11"; } diff --git a/justfile b/justfile index 5069614..dc533a1 100644 --- a/justfile +++ b/justfile @@ -33,13 +33,9 @@ build: iso: nixos-rebuild build-image --image-variant iso-installer --flake .#iso -# build ephemeral VM image -ephvm-build: - nixos-rebuild build-image --image-variant qemu --flake .#ephvm - # run ephemeral VM -ephvm-run *ARGS: - bash scripts/ephvm-run.sh $(find -L result -name '*.qcow2' | head -1) {{ARGS}} +ephvm *ARGS: + bash scripts/ephvm-run.sh {{ARGS}} # ssh into running ephemeral VM ephvm-ssh port="2222": diff --git a/modules/nixos/vm-9p-automount.nix b/modules/nixos/vm-9p-automount.nix index 5e53364..2fbe427 100644 --- a/modules/nixos/vm-9p-automount.nix +++ b/modules/nixos/vm-9p-automount.nix @@ -20,7 +20,7 @@ in prefix = lib.mkOption { type = lib.types.str; - default = "mount_"; + default = "m_"; description = "9p mount tag prefix to match"; }; diff --git a/scripts/ephvm-run.sh b/scripts/ephvm-run.sh index 5c254ce..0877309 100755 --- a/scripts/ephvm-run.sh +++ b/scripts/ephvm-run.sh @@ -5,44 +5,40 @@ SSH_PORT=2222 MEMORY=8G CPUS=4 MOUNTS=() -CLAUDE_DIR="" -CLAUDE_JSON="" -IMAGE="" +CLAUDE=false usage() { cat < [options] +Usage: ephvm-run.sh [options] Options: --mount Mount host directory into VM (repeatable) - --claude Mount claude config dir writable into VM - --claude-json Copy claude.json into mounted claude dir + --claude Mount claude config dir (requires CLAUDE_CONFIG_DIR) --memory VM memory (default: 8G) - --cpus VM CPUs (default: 4) - --ssh-port SSH port forward (default: 2222) + --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 --mount) MOUNTS+=("$2"); shift 2 ;; - --claude) CLAUDE_DIR="$2"; shift 2 ;; - --claude-json) CLAUDE_JSON="$2"; shift 2 ;; + --claude) CLAUDE=true; shift ;; --memory) MEMORY="$2"; shift 2 ;; - --cpus) CPUS="$2"; shift 2 ;; - --ssh-port) SSH_PORT="$2"; shift 2 ;; - *) echo "unknown option: $1"; usage ;; + --cpus) CPUS="$2"; shift 2 ;; + --ssh-port) SSH_PORT="$2"; shift 2 ;; + -h|--help) usage ;; + *) echo "unknown option: $1"; usage ;; esac done -if [ ! -f "$IMAGE" ]; then - echo "error: image not found: $IMAGE" +echo "building ephvm image..." +IMAGE_DIR=$(nix build --no-link --print-out-paths .#nixosConfigurations.ephvm.config.system.build.images.qemu) +IMAGE=$(find "$IMAGE_DIR" -name '*.qcow2' -print -quit) + +if [ -z "$IMAGE" ]; then + echo "error: no qcow2 image found in $IMAGE_DIR" exit 1 fi @@ -67,21 +63,25 @@ FS_ID=0 for mount_path in "${MOUNTS[@]}"; do mount_path=$(realpath "$mount_path") name=$(basename "$mount_path") + tag="m_${name:0:29}" QEMU_ARGS+=( - -virtfs "local,path=$mount_path,mount_tag=mount_$name,security_model=none,id=fs${FS_ID}" + -virtfs "local,path=$mount_path,mount_tag=$tag,security_model=none,id=fs${FS_ID}" ) FS_ID=$((FS_ID + 1)) done -if [ -n "$CLAUDE_DIR" ]; then - CLAUDE_DIR=$(realpath "$CLAUDE_DIR") - QEMU_ARGS+=( - -virtfs "local,path=$CLAUDE_DIR,mount_tag=claude,security_model=none,id=fs${FS_ID}" - ) -fi +if [ "$CLAUDE" = true ]; then + if [ -z "${CLAUDE_CONFIG_DIR:-}" ]; then + echo "error: --claude requires CLAUDE_CONFIG_DIR to be set" + exit 1 + fi + mkdir -p "$CLAUDE_CONFIG_DIR" + claude_dir=$(realpath "$CLAUDE_CONFIG_DIR") -if [ -n "$CLAUDE_JSON" ]; then - QEMU_ARGS+=(-fw_cfg "name=opt/claude.json,file=$CLAUDE_JSON") + QEMU_ARGS+=( + -virtfs "local,path=$claude_dir,mount_tag=claude,security_model=none,id=fs${FS_ID}" + ) + FS_ID=$((FS_ID + 1)) fi exec "${QEMU_ARGS[@]}"