feat: improve ephvm ux
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
packages.ahab
|
||||
pkgs.just
|
||||
pkgs.presenterm
|
||||
pkgs.qemu
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -29,6 +29,39 @@
|
||||
);
|
||||
};
|
||||
|
||||
# auto-login on serial console
|
||||
services.getty.autologinUser = "matej";
|
||||
|
||||
# enable zsh in home-manager so starship init gets wired up
|
||||
home-manager.users.matej.programs.zsh = {
|
||||
enable = true;
|
||||
dotDir = "/home/matej/.config/zsh";
|
||||
shellAliases.dsp = "claude --dangerously-skip-permissions";
|
||||
};
|
||||
|
||||
home-manager.users.matej.programs.starship = {
|
||||
enable = true;
|
||||
settings = {
|
||||
add_newline = false;
|
||||
format = "$username$hostname$directory$character";
|
||||
hostname = {
|
||||
ssh_only = false;
|
||||
style = "bold blue";
|
||||
format = "[@$hostname]($style)";
|
||||
};
|
||||
username = {
|
||||
show_always = true;
|
||||
style_user = "bold blue";
|
||||
format = "[$user]($style)";
|
||||
};
|
||||
directory.format = " [$path]($style) ";
|
||||
character = {
|
||||
success_symbol = "[>](bold green)";
|
||||
error_symbol = "[>](bold red)";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
features.vm-guest.headless = true;
|
||||
features.vm-guest.automount = {
|
||||
enable = true;
|
||||
@@ -39,6 +72,14 @@
|
||||
# ensure .config exists with correct ownership before automount
|
||||
systemd.tmpfiles.rules = [ "d /home/matej/.config 0755 matej users -" ];
|
||||
|
||||
# TODO:(@janezicmatej) replace ssh with virtio-console (hvc0) when qemu 11.0 lands
|
||||
# https://www.mail-archive.com/qemu-devel@nongnu.org/msg1162844.html
|
||||
# accept any ssh key (ephemeral localhost-only vm)
|
||||
services.openssh.settings.AuthorizedKeysCommand = let
|
||||
acceptKey = pkgs.writeShellScript "ephvm-accept-key" ''echo "$1 $2"'';
|
||||
in "${acceptKey} %t %k";
|
||||
services.openssh.settings.AuthorizedKeysCommandUser = "nobody";
|
||||
|
||||
# writable claude config via 9p
|
||||
fileSystems."/home/matej/.config/claude" = {
|
||||
device = "claude";
|
||||
|
||||
@@ -27,7 +27,9 @@ info() {
|
||||
|
||||
# globals for cleanup trap
|
||||
CLEANUP_OVERLAY=""
|
||||
QEMU_PID=""
|
||||
cleanup() {
|
||||
[ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null && wait "$QEMU_PID" 2>/dev/null
|
||||
[ -n "$CLEANUP_OVERLAY" ] && rm -rf "$CLEANUP_OVERLAY"
|
||||
return 0
|
||||
}
|
||||
@@ -39,11 +41,12 @@ Usage: ephvm-run.sh [options]
|
||||
|
||||
Options:
|
||||
--mount <path> Mount host directory into VM (repeatable)
|
||||
--claude Mount claude config dir (requires CLAUDE_CONFIG_DIR)
|
||||
--no-claude Skip mounting claude config dir
|
||||
--disk-size <size> Resize guest disk (e.g. 50G)
|
||||
--memory <size> VM memory (default: 8G)
|
||||
--cpus <n> VM CPUs (default: 4)
|
||||
--ssh-port <port> SSH port forward (default: 2222)
|
||||
--memory <size> VM memory (default: 4G)
|
||||
--cpus <n> VM CPUs (default: 2)
|
||||
--ssh-port <port> Use specific SSH port (default: auto)
|
||||
--serial Attach to serial console instead of SSH
|
||||
-h, --help Show usage
|
||||
EOF
|
||||
exit "${1:-0}"
|
||||
@@ -52,7 +55,7 @@ EOF
|
||||
main() {
|
||||
setup_colors
|
||||
|
||||
local ssh_port=2222 memory=8G cpus=4 claude=false disk_size=""
|
||||
local ssh_port="" memory=4G cpus=2 claude=true disk_size="" serial=false
|
||||
local -a mounts=()
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
@@ -61,8 +64,8 @@ main() {
|
||||
mounts+=("$2")
|
||||
shift 2
|
||||
;;
|
||||
--claude)
|
||||
claude=true
|
||||
--no-claude)
|
||||
claude=false
|
||||
shift
|
||||
;;
|
||||
--disk-size)
|
||||
@@ -81,6 +84,10 @@ main() {
|
||||
ssh_port="$2"
|
||||
shift 2
|
||||
;;
|
||||
--serial)
|
||||
serial=true
|
||||
shift
|
||||
;;
|
||||
-h | --help) usage ;;
|
||||
*)
|
||||
echo "${red}error:${reset} unknown option: $1" >&2
|
||||
@@ -91,7 +98,9 @@ main() {
|
||||
|
||||
info "building ephvm image..."
|
||||
local image_dir image
|
||||
image_dir=$(nix build --no-link --print-out-paths .#nixosConfigurations.ephvm.config.system.build.images.qemu)
|
||||
[ -n "${EPHVM_FLAKE:-}" ] || die "EPHVM_FLAKE must be set to the flake directory"
|
||||
local flake="$EPHVM_FLAKE"
|
||||
image_dir=$(nix build --no-link --print-out-paths "${flake}#nixosConfigurations.ephvm.config.system.build.images.qemu")
|
||||
image=$(find "$image_dir" -name '*.qcow2' -print -quit)
|
||||
[ -n "$image" ] || die "no qcow2 image found in $image_dir"
|
||||
|
||||
@@ -106,16 +115,31 @@ main() {
|
||||
drive_arg="file=$image,format=qcow2,snapshot=on"
|
||||
fi
|
||||
|
||||
command -v qemu-system-x86_64 &>/dev/null || die "qemu-system-x86_64 not found"
|
||||
|
||||
local accel="tcg"
|
||||
[ -r /dev/kvm ] && accel="kvm"
|
||||
|
||||
# auto-allocate ssh port unless serial mode
|
||||
if [ "$serial" = false ] && [ -z "$ssh_port" ]; then
|
||||
ssh_port=10022
|
||||
while ss -tln | grep -q ":${ssh_port}\b"; do
|
||||
ssh_port=$((ssh_port + 1))
|
||||
done
|
||||
fi
|
||||
|
||||
local nic_arg="user"
|
||||
if [ -n "$ssh_port" ]; then
|
||||
nic_arg="user,hostfwd=tcp::${ssh_port}-:22"
|
||||
fi
|
||||
|
||||
local -a qemu_args=(
|
||||
qemu-system-x86_64
|
||||
-accel "$accel"
|
||||
-m "$memory"
|
||||
-smp "$cpus"
|
||||
-drive "$drive_arg"
|
||||
-nic "user,hostfwd=tcp::${ssh_port}-:22"
|
||||
-nic "$nic_arg"
|
||||
-nographic
|
||||
)
|
||||
|
||||
@@ -135,7 +159,7 @@ main() {
|
||||
done
|
||||
|
||||
if [ "$claude" = true ]; then
|
||||
[ -n "${CLAUDE_CONFIG_DIR:-}" ] || die "--claude requires CLAUDE_CONFIG_DIR to be set"
|
||||
[ -n "${CLAUDE_CONFIG_DIR:-}" ] || die "CLAUDE_CONFIG_DIR must be set (use --no-claude to skip)"
|
||||
mkdir -p "$CLAUDE_CONFIG_DIR"
|
||||
local claude_dir
|
||||
claude_dir=$(realpath "$CLAUDE_CONFIG_DIR")
|
||||
@@ -147,10 +171,31 @@ main() {
|
||||
fi
|
||||
|
||||
info "---"
|
||||
info "Accel: $accel | SSH: ssh -p $ssh_port matej@localhost"
|
||||
info "Accel: $accel"
|
||||
info "---"
|
||||
|
||||
exec "${qemu_args[@]}"
|
||||
if [ "$serial" = true ]; then
|
||||
exec "${qemu_args[@]}"
|
||||
fi
|
||||
|
||||
# start qemu in background and auto-ssh
|
||||
"${qemu_args[@]}" &>/dev/null &
|
||||
QEMU_PID=$!
|
||||
|
||||
info "waiting for vm (port $ssh_port)..."
|
||||
local attempts=0
|
||||
while ! (echo > /dev/tcp/localhost/"$ssh_port") 2>/dev/null; do
|
||||
attempts=$((attempts + 1))
|
||||
[ $attempts -gt 60 ] && die "vm did not become ready in 60s"
|
||||
kill -0 "$QEMU_PID" 2>/dev/null || die "qemu exited unexpectedly"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
ssh -p "$ssh_port" -t \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR \
|
||||
matej@localhost
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user