wip
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,3 +5,6 @@ result-*
|
|||||||
# Ignore automatically generated direnv output
|
# Ignore automatically generated direnv output
|
||||||
.direnv
|
.direnv
|
||||||
|
|
||||||
|
# Ignore generated seed ISOs
|
||||||
|
*.iso
|
||||||
|
|
||||||
|
|||||||
69
.gitlab-ci.yml
Normal file
69
.gitlab-ci.yml
Normal file
@@ -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"
|
||||||
39
dist/make-seed.sh
vendored
Executable file
39
dist/make-seed.sh
vendored
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: make-seed.sh <pubkey-file> [output.iso]"
|
||||||
|
echo "Creates a seed ISO with the given SSH public key."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "${1:-}" ] || usage
|
||||||
|
|
||||||
|
PUBKEY_FILE="$1"
|
||||||
|
OUTPUT="${2:-seed.iso}"
|
||||||
|
|
||||||
|
if [ ! -f "$PUBKEY_FILE" ]; then
|
||||||
|
echo "error: public key file not found: $PUBKEY_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMPDIR=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMPDIR"' EXIT
|
||||||
|
|
||||||
|
cp "$PUBKEY_FILE" "$TMPDIR/authorized_keys"
|
||||||
|
|
||||||
|
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"
|
||||||
181
dist/run.sh
vendored
Executable file
181
dist/run.sh
vendored
Executable file
@@ -0,0 +1,181 @@
|
|||||||
|
#!/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_KEY=""
|
||||||
|
SEED_ISO=""
|
||||||
|
IMAGE=""
|
||||||
|
GUEST_ARCH=""
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: run.sh <image.qcow2> [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--arch <arch> Guest architecture (auto-detected from image name)
|
||||||
|
--ssh-key <key.pub> SSH public key (auto-generates seed ISO)
|
||||||
|
--seed-iso <iso> Pre-built seed ISO (alternative to --ssh-key)
|
||||||
|
--projects <path> Mount host directory as ~/projects in VM
|
||||||
|
--claude-dir <path> Mount host .claude directory for auth
|
||||||
|
--memory <size> VM memory (default: 8G)
|
||||||
|
--cpus <n> VM CPUs (default: 4)
|
||||||
|
--ssh-port <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_KEY="$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 [ -n "$SSH_KEY" ] && [ -z "$SEED_ISO" ]; then
|
||||||
|
SEED_ISO=$(mktemp /tmp/seed-XXXXXX.iso)
|
||||||
|
CLEANUP_SEED="$SEED_ISO"
|
||||||
|
bash "$SCRIPT_DIR/make-seed.sh" "$SSH_KEY" "$SEED_ISO"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
[ -n "$CLEANUP_SEED" ] && rm -f "$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
|
||||||
|
if [ -n "$PROJECTS" ]; then
|
||||||
|
QEMU_ARGS+=(
|
||||||
|
-virtfs "local,path=$PROJECTS,mount_tag=projects,security_model=mapped-xattr,id=fs0"
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$CLAUDE_DIR" ]; then
|
||||||
|
QEMU_ARGS+=(
|
||||||
|
-virtfs "local,path=$CLAUDE_DIR,mount_tag=hostclaude,security_model=mapped-xattr,id=fs1"
|
||||||
|
)
|
||||||
|
# also mount parent home for .claude.json
|
||||||
|
QEMU_ARGS+=(
|
||||||
|
-virtfs "local,path=$(dirname "$CLAUDE_DIR"),mount_tag=hosthome,security_model=mapped-xattr,id=fs2,readonly=on"
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "---"
|
||||||
|
echo "Guest: $GUEST_ARCH | Accel: $ACCEL"
|
||||||
|
echo "SSH: ssh -A -p $SSH_PORT gordaina@localhost"
|
||||||
|
echo "---"
|
||||||
|
|
||||||
|
exec "${QEMU_ARGS[@]}"
|
||||||
11
flake.nix
11
flake.nix
@@ -93,6 +93,17 @@
|
|||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
users = [ ];
|
users = [ ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# nixos-rebuild build-image --image-variant qemu --flake .#sandbox
|
||||||
|
# nixos-rebuild build-image --image-variant qemu-efi --flake .#sandbox-aarch64
|
||||||
|
sandbox = mkHost "sandbox" {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
users = [ "gordaina" ];
|
||||||
|
};
|
||||||
|
sandbox-aarch64 = mkHost "sandbox" {
|
||||||
|
system = "aarch64-linux";
|
||||||
|
users = [ "gordaina" ];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
nixosModules = import ./modules/nixos {
|
nixosModules = import ./modules/nixos {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ in
|
|||||||
inputs.self.nixosModules.nvidia
|
inputs.self.nixosModules.nvidia
|
||||||
inputs.self.nixosModules.initrd-ssh
|
inputs.self.nixosModules.initrd-ssh
|
||||||
inputs.self.nixosModules.localisation
|
inputs.self.nixosModules.localisation
|
||||||
|
inputs.self.nixosModules.aarch64-vm
|
||||||
];
|
];
|
||||||
|
|
||||||
yubikey.enable = true;
|
yubikey.enable = true;
|
||||||
@@ -65,6 +66,8 @@ in
|
|||||||
base16Scheme = "${pkgs.base16-schemes}/share/themes/gruvbox-material-dark-medium.yaml";
|
base16Scheme = "${pkgs.base16-schemes}/share/themes/gruvbox-material-dark-medium.yaml";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
aarch64-vm.enable = true;
|
||||||
|
|
||||||
boot.loader.systemd-boot.enable = true;
|
boot.loader.systemd-boot.enable = true;
|
||||||
boot.loader.efi.canTouchEfiVariables = true;
|
boot.loader.efi.canTouchEfiVariables = true;
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
inputs.self.nixosModules.workstation
|
inputs.self.nixosModules.workstation
|
||||||
inputs.self.nixosModules.initrd-ssh
|
inputs.self.nixosModules.initrd-ssh
|
||||||
inputs.self.nixosModules.localisation
|
inputs.self.nixosModules.localisation
|
||||||
|
inputs.self.nixosModules.aarch64-vm
|
||||||
];
|
];
|
||||||
|
|
||||||
yubikey.enable = true;
|
yubikey.enable = true;
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
command = "sway";
|
command = "sway";
|
||||||
};
|
};
|
||||||
sway.enable = true;
|
sway.enable = true;
|
||||||
|
aarch64-vm.enable = true;
|
||||||
|
|
||||||
initrd-ssh = {
|
initrd-ssh = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
162
hosts/sandbox/configuration.nix
Normal file
162
hosts/sandbox/configuration.nix
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
inputs,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./hardware-configuration.nix
|
||||||
|
inputs.self.nixosModules.vm-guest
|
||||||
|
inputs.self.nixosModules.seed-ssh
|
||||||
|
inputs.self.nixosModules.zsh
|
||||||
|
inputs.self.nixosModules.localisation
|
||||||
|
];
|
||||||
|
|
||||||
|
vm-guest = {
|
||||||
|
enable = true;
|
||||||
|
headless = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
seed-ssh = {
|
||||||
|
enable = true;
|
||||||
|
user = "gordaina";
|
||||||
|
};
|
||||||
|
|
||||||
|
zsh.enable = true;
|
||||||
|
|
||||||
|
localisation = {
|
||||||
|
enable = true;
|
||||||
|
timeZone = "UTC";
|
||||||
|
defaultLocale = "en_US.UTF-8";
|
||||||
|
};
|
||||||
|
|
||||||
|
users = {
|
||||||
|
groups.gordaina = {
|
||||||
|
gid = 1000;
|
||||||
|
};
|
||||||
|
users.gordaina = {
|
||||||
|
group = "gordaina";
|
||||||
|
uid = 1000;
|
||||||
|
isNormalUser = true;
|
||||||
|
home = "/home/gordaina";
|
||||||
|
createHome = true;
|
||||||
|
shell = pkgs.zsh;
|
||||||
|
extraGroups = [
|
||||||
|
"wheel"
|
||||||
|
"users"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# 9p mounts — silently fail if shares not provided at runtime
|
||||||
|
fileSystems."/home/gordaina/projects" = {
|
||||||
|
device = "projects";
|
||||||
|
fsType = "9p";
|
||||||
|
options = [
|
||||||
|
"trans=virtio"
|
||||||
|
"version=9p2000.L"
|
||||||
|
"msize=65536"
|
||||||
|
"nofail"
|
||||||
|
"x-systemd.automount"
|
||||||
|
"x-systemd.device-timeout=2s"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = 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/gordaina/.claude
|
||||||
|
if mountpoint -q /mnt/host-claude; then
|
||||||
|
cp -a /mnt/host-claude/. /home/gordaina/.claude/
|
||||||
|
fi
|
||||||
|
if mountpoint -q /mnt/host-home; then
|
||||||
|
cp /mnt/host-home/.claude.json /home/gordaina/.claude.json || true
|
||||||
|
fi
|
||||||
|
chown -R gordaina:gordaina /home/gordaina/.claude /home/gordaina/.claude.json 2>/dev/null || true
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
claude-code
|
||||||
|
git
|
||||||
|
];
|
||||||
|
|
||||||
|
# image builder VM needs more than the default 1G to copy closure
|
||||||
|
image.modules.qemu =
|
||||||
|
{ 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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
image.modules.qemu-efi =
|
||||||
|
{ 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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
system.stateVersion = "25.11";
|
||||||
|
}
|
||||||
21
hosts/sandbox/hardware-configuration.nix
Normal file
21
hosts/sandbox/hardware-configuration.nix
Normal file
@@ -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";
|
||||||
|
}
|
||||||
12
justfile
12
justfile
@@ -36,3 +36,15 @@ iso:
|
|||||||
# garbage collect old generations
|
# garbage collect old generations
|
||||||
clean:
|
clean:
|
||||||
sudo nix-collect-garbage $(nix eval --raw -f ./nix.nix nix.gc.options)
|
sudo nix-collect-garbage $(nix eval --raw -f ./nix.nix nix.gc.options)
|
||||||
|
|
||||||
|
# build sandbox VM image
|
||||||
|
sandbox-build:
|
||||||
|
nixos-rebuild build-image --image-variant qemu --flake .#sandbox
|
||||||
|
|
||||||
|
# run sandbox VM
|
||||||
|
sandbox-run *ARGS:
|
||||||
|
bash dist/run.sh $(find -L result -name '*.qcow2' | head -1) {{ARGS}}
|
||||||
|
|
||||||
|
# ssh into running sandbox
|
||||||
|
sandbox-ssh:
|
||||||
|
ssh -A -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null gordaina@localhost
|
||||||
|
|||||||
14
modules/nixos/aarch64-vm.nix
Normal file
14
modules/nixos/aarch64-vm.nix
Normal file
@@ -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" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
61
modules/nixos/seed-ssh.nix
Normal file
61
modules/nixos/seed-ssh.nix
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
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" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
ExecStart =
|
||||||
|
let
|
||||||
|
cfg = config.seed-ssh;
|
||||||
|
inherit (cfg) user;
|
||||||
|
inherit (config.users.users.${user}) home;
|
||||||
|
in
|
||||||
|
pkgs.writeShellScript "seed-ssh" ''
|
||||||
|
DEVICE="/dev/disk/by-label/${cfg.label}"
|
||||||
|
if [ ! -e "$DEVICE" ]; then
|
||||||
|
echo "seed ISO not found, skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
MOUNT=$(mktemp -d)
|
||||||
|
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}:${user} "${home}/.ssh"
|
||||||
|
|
||||||
|
umount "$MOUNT"
|
||||||
|
rmdir "$MOUNT"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
62
modules/nixos/vm-guest.nix
Normal file
62
modules/nixos/vm-guest.nix
Normal file
@@ -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
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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 ]}"
|
|
||||||
];
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
21
users/gordaina/home-manager.nix
Normal file
21
users/gordaina/home-manager.nix
Normal file
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user