Compare commits

..

1 Commits

Author SHA1 Message Date
174d546a39 wip 2026-03-01 11:55:30 +01:00
23 changed files with 197 additions and 663 deletions

3
.gitignore vendored
View File

@@ -5,6 +5,3 @@ result-*
# Ignore automatically generated direnv output # Ignore automatically generated direnv output
.direnv .direnv
# Ignore generated seed ISOs
*.iso

View File

@@ -1,69 +0,0 @@
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
View File

@@ -1,39 +0,0 @@
#!/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"

149
dist/run.sh vendored
View File

@@ -1,149 +0,0 @@
#!/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=""
usage() {
cat <<EOF
Usage: run.sh <image.qcow2> [options]
Options:
--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
--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
# platform and accelerator detection
ACCEL="tcg"
ARCH=$(uname -m)
OS=$(uname -s)
case "$OS" in
Linux)
[ -r /dev/kvm ] && ACCEL="kvm"
;;
Darwin)
ACCEL="hvf"
;;
esac
case "$ARCH" in
x86_64|amd64) QEMU_BIN="qemu-system-x86_64" ;;
aarch64|arm64) QEMU_BIN="qemu-system-aarch64" ;;
*) echo "error: unsupported architecture: $ARCH"; exit 1 ;;
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-specific flags
if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
QEMU_ARGS+=(-machine virt -cpu host)
# uefi firmware
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 "SSH: ssh -A -p $SSH_PORT gordaina@localhost"
echo "Password: sandbox"
echo "---"
exec "${QEMU_ARGS[@]}"

54
flake.lock generated
View File

@@ -89,11 +89,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1772252606, "lastModified": 1772160751,
"narHash": "sha256-SiIhFq4XbD3LmODQ2mTtakRBnjBn/KoSgAOId1cL1Ks=", "narHash": "sha256-PXv9nrm8HHLGIU2B1XRjOvzlPW9sFdzbtWiJQ1wE8dM=",
"owner": "ryoppippi", "owner": "ryoppippi",
"repo": "claude-code-overlay", "repo": "claude-code-overlay",
"rev": "b1ebf027412136bbbe4202741c3d48721644bc4b", "rev": "662bdbf5ea61fc3cde167bbe3686343a149ea6c6",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -157,11 +157,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1772408722, "lastModified": 1769996383,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", "rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -273,11 +273,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1772380125, "lastModified": 1771744638,
"narHash": "sha256-8C+y46xA9bxcchj9GeDPJaRUDApaA3sy2fhJr1bTbUw=", "narHash": "sha256-EDLi+YAsEEAmMeZe1v6GccuGRbCkpSZp/+A6g+pivR8=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "a07a44a839eb036e950bf397d9b782916f8dcab3", "rev": "cb6c151f5c9db4df0b69d06894dc8484de1f16a0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -318,11 +318,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1772409903, "lastModified": 1771891493,
"narHash": "sha256-yue9XaZ7WHOFJmm3DMEmrF536pHwGxTxh/xr0f1MzNU=", "narHash": "sha256-L0OCnG8rsWJYZ3mzHSz0iENtlBXQjjcGgvMgsBqN14U=",
"owner": "nix-community", "owner": "nix-community",
"repo": "neovim-nightly-overlay", "repo": "neovim-nightly-overlay",
"rev": "28962d176db883e4fda4b808e220051f376969da", "rev": "7db85d094c68697fc36801bccdf015b4c2bdb274",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -334,11 +334,11 @@
"neovim-src": { "neovim-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1772353308, "lastModified": 1771885993,
"narHash": "sha256-k/3msPgpWW9CRFIp3nz6hJzV+GArXw4m35c0t6fKJK4=", "narHash": "sha256-2c4H+5f0qhsp13Vx8pbsGiSRTHBJIfQaRAAUSHGEpgo=",
"owner": "neovim", "owner": "neovim",
"repo": "neovim", "repo": "neovim",
"rev": "563f9ef7994a35686419b4524cd772c97960dac1", "rev": "d9d8c660fd5559d928c8870a21970a375674e310",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -365,11 +365,11 @@
}, },
"nixpkgs-master": { "nixpkgs-master": {
"locked": { "locked": {
"lastModified": 1772461137, "lastModified": 1771932323,
"narHash": "sha256-5MFNMLcDmaXQbdGJVITwFTqJq3IVok4TSR/Oa/DbJys=", "narHash": "sha256-3PadsTzuMJT/x0KmiD/Me1GG6rW8kaHoWVduSs0ue7o=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "388c66870001909259d1879acd2e3e1108c1854d", "rev": "89bb5c5da7a857869cc88ef9b856bffdff8af264",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -381,11 +381,11 @@
}, },
"nixpkgs-unstable": { "nixpkgs-unstable": {
"locked": { "locked": {
"lastModified": 1772419343, "lastModified": 1771482645,
"narHash": "sha256-QU3Cd5DJH7dHyMnGEFfPcZDaCAsJQ6tUD+JuUsYqnKU=", "narHash": "sha256-MpAKyXfJRDTgRU33Hja+G+3h9ywLAJJNRq4Pjbb4dQs=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "93178f6a00c22fcdee1c6f5f9ab92f2072072ea9", "rev": "724cf38d99ba81fbb4a347081db93e2e3a9bc2ae",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -397,11 +397,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1772173633, "lastModified": 1771423170,
"narHash": "sha256-MOH58F4AIbCkh6qlQcwMycyk5SWvsqnS/TCfnqDlpj4=", "narHash": "sha256-K7Dg9TQ0mOcAtWTO/FX/FaprtWQ8BmEXTpLIaNRhEwU=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c0f3d81a7ddbc2b1332be0d8481a672b4f6004d6", "rev": "bcc4a9d9533c033d806a46b37dc444f9b0da49dd",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -413,11 +413,11 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1772047000, "lastModified": 1771714954,
"narHash": "sha256-7DaQVv4R97cii/Qdfy4tmDZMB2xxtyIvNGSwXBBhSmo=", "narHash": "sha256-nhZJPnBavtu40/L2aqpljrfUNb2rxmWTmSjK2c9UKds=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "1267bb4920d0fc06ea916734c11b0bf004bbe17e", "rev": "afbbf774e2087c3d734266c22f96fca2e78d3620",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -97,11 +97,7 @@
# nixos-rebuild build-image --image-variant qemu --flake .#sandbox # nixos-rebuild build-image --image-variant qemu --flake .#sandbox
sandbox = mkHost "sandbox" { sandbox = mkHost "sandbox" {
system = "x86_64-linux"; system = "x86_64-linux";
users = [ "gordaina" ]; users = [ "gorazd" ];
};
sandbox-aarch64 = mkHost "sandbox" {
system = "aarch64-linux";
users = [ "gordaina" ];
}; };
}; };

View File

@@ -65,7 +65,6 @@ in
base16Scheme = "${pkgs.base16-schemes}/share/themes/gruvbox-material-dark-medium.yaml"; base16Scheme = "${pkgs.base16-schemes}/share/themes/gruvbox-material-dark-medium.yaml";
}; };
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
boot.loader.systemd-boot.enable = true; boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true; boot.loader.efi.canTouchEfiVariables = true;

View File

@@ -2,48 +2,59 @@
pkgs, pkgs,
lib, lib,
inputs, inputs,
config,
... ...
}: }:
{ {
imports = [ imports = [
./hardware-configuration.nix ./hardware-configuration.nix
inputs.self.nixosModules.vm-guest inputs.self.nixosModules.vm-guest
inputs.self.nixosModules.seed-ssh inputs.self.nixosModules.desktop
inputs.self.nixosModules.zsh inputs.self.nixosModules.zsh
inputs.self.nixosModules.localisation
]; ];
vm-guest = { vm-guest.enable = true;
enable = true; desktop.enable = true;
headless = true;
};
seed-ssh = {
enable = true;
user = "gordaina";
};
zsh.enable = true; zsh.enable = true;
localisation = { programs.labwc.enable = true;
enable = true;
timeZone = "UTC"; # labwc stacking compositor with auto-login
defaultLocale = "en_US.UTF-8"; services.greetd =
}; let
labwc-session = pkgs.writeShellScript "labwc-session" ''
export XDG_SESSION_TYPE=wayland
export XDG_CURRENT_DESKTOP=labwc:wlroots
# software renderer for qemu virtio-vga
export WLR_RENDERER=pixman
export WLR_DRM_NO_ATOMIC=1
exec ${pkgs.labwc}/bin/labwc
'';
in
{
enable = true;
settings = {
default_session = {
command = labwc-session;
user = "gorazd";
};
initial_session = {
command = labwc-session;
user = "gorazd";
};
};
};
users = { users = {
groups.gordaina = { groups.gorazd = {
gid = 1000; gid = 1000;
}; };
users.gordaina = { users.gorazd = {
group = "gordaina"; group = "gorazd";
uid = 1000; uid = 1000;
isNormalUser = true; isNormalUser = true;
home = "/home/gordaina"; home = "/home/gorazd";
createHome = true; createHome = true;
password = "sandbox"; password = "sandbox";
shell = pkgs.zsh;
extraGroups = [ extraGroups = [
"wheel" "wheel"
"users" "users"
@@ -51,8 +62,8 @@
}; };
}; };
# 9p mounts — silently fail if shares not provided at runtime # 9p mounts for host files
fileSystems."/home/gordaina/projects" = { fileSystems."/home/gorazd/projects" = {
device = "projects"; device = "projects";
fsType = "9p"; fsType = "9p";
options = [ options = [
@@ -60,8 +71,6 @@
"version=9p2000.L" "version=9p2000.L"
"msize=65536" "msize=65536"
"nofail" "nofail"
"x-systemd.automount"
"x-systemd.device-timeout=2s"
]; ];
}; };
@@ -73,8 +82,6 @@
"version=9p2000.L" "version=9p2000.L"
"msize=65536" "msize=65536"
"nofail" "nofail"
"x-systemd.automount"
"x-systemd.device-timeout=2s"
]; ];
}; };
@@ -86,8 +93,6 @@
"version=9p2000.L" "version=9p2000.L"
"msize=65536" "msize=65536"
"nofail" "nofail"
"x-systemd.automount"
"x-systemd.device-timeout=2s"
"ro" "ro"
]; ];
}; };
@@ -95,33 +100,32 @@
# pre-auth claude-code from host config # pre-auth claude-code from host config
systemd.services.claude-auth = { systemd.services.claude-auth = {
description = "Copy claude-code credentials from host mount"; description = "Copy claude-code credentials from host mount";
after = [ "local-fs.target" ]; after = [
"mnt-host\\x2dclaude.mount"
"mnt-host\\x2dhome.mount"
];
requires = [
"mnt-host\\x2dclaude.mount"
"mnt-host\\x2dhome.mount"
];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "claude-auth" '' ExecStart = pkgs.writeShellScript "claude-auth" ''
# skip if host mounts are not available mkdir -p /home/gorazd/.claude
if ! mountpoint -q /mnt/host-claude && ! mountpoint -q /mnt/host-home; then cp -a /mnt/host-claude/. /home/gorazd/.claude/
echo "no host mounts found, skipping" cp /mnt/host-home/.claude.json /home/gorazd/.claude.json || true
exit 0 chown -R gorazd:gorazd /home/gorazd/.claude /home/gorazd/.claude.json
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; [ environment.systemPackages = with pkgs; [
claude-code claude-code
git labwc
sfwbar
foot
firefox
]; ];
system.stateVersion = "25.11"; system.stateVersion = "25.11";

View File

@@ -1,6 +1,5 @@
{ {
lib, lib,
pkgs,
modulesPath, modulesPath,
... ...
}: }:
@@ -15,9 +14,5 @@
fsType = "ext4"; fsType = "ext4";
}; };
# x86_64: bios/grub, aarch64: uefi/systemd-boot boot.loader.grub.device = lib.mkDefault "/dev/vda";
boot.loader.grub.device = lib.mkIf pkgs.stdenv.hostPlatform.isx86_64 (lib.mkDefault "/dev/vda");
boot.loader.grub.enable = lib.mkIf pkgs.stdenv.hostPlatform.isAarch64 false;
boot.loader.systemd-boot.enable = lib.mkIf pkgs.stdenv.hostPlatform.isAarch64 true;
boot.loader.efi.canTouchEfiVariables = lib.mkIf pkgs.stdenv.hostPlatform.isAarch64 true;
} }

View File

@@ -17,18 +17,6 @@ update:
# update flake inputs, rebuild and switch # update flake inputs, rebuild and switch
bump: update switch bump: update switch
# update a package to latest version
update-package pkg:
bash packages/{{pkg}}/update.sh
# update all packages with update scripts
update-package-all:
@for script in packages/*/update.sh; do bash "$script"; done
# build all packages and hosts
build:
nix flake check
# build installation iso # build installation iso
iso: iso:
nixos-rebuild build-image --image-variant iso-installer --flake .#live-iso nixos-rebuild build-image --image-variant iso-installer --flake .#live-iso
@@ -41,10 +29,32 @@ clean:
sandbox-build: sandbox-build:
nixos-rebuild build-image --image-variant qemu --flake .#sandbox nixos-rebuild build-image --image-variant qemu --flake .#sandbox
# run sandbox VM # run sandbox with GUI (ephemeral, changes discarded)
sandbox-run *ARGS: sandbox-run:
bash dist/run.sh $(find -L result -name '*.qcow2' | head -1) {{ARGS}} nix shell nixpkgs#qemu -c qemu-system-x86_64 -enable-kvm -m 8G -smp 4 \
-drive file=$(find -L result -name '*.qcow2' | head -1),format=qcow2,snapshot=on \
-vga virtio -display gtk,zoom-to-fit=false \
-device virtio-serial-pci \
-chardev qemu-vdagent,id=ch1,name=vdagent,clipboard=on \
-device virtserialport,chardev=ch1,id=ch1,name=com.redhat.spice.0 \
-virtfs local,path=$HOME/git,mount_tag=projects,security_model=mapped-xattr,id=fs0 \
-virtfs local,path=$HOME/.claude,mount_tag=hostclaude,security_model=mapped-xattr,id=fs1 \
-virtfs local,path=$HOME,mount_tag=hosthome,security_model=mapped-xattr,id=fs2,readonly=on \
-nic user,hostfwd=tcp::2222-:22
# run sandbox headless (ephemeral, changes discarded)
sandbox-run-headless:
nix shell nixpkgs#qemu -c qemu-system-x86_64 -enable-kvm -m 8G -smp 4 \
-drive file=$(find -L result -name '*.qcow2' | head -1),format=qcow2,snapshot=on \
-virtfs local,path=$HOME/git,mount_tag=projects,security_model=mapped-xattr,id=fs0 \
-virtfs local,path=$HOME/.claude,mount_tag=hostclaude,security_model=mapped-xattr,id=fs1 \
-virtfs local,path=$HOME,mount_tag=hosthome,security_model=mapped-xattr,id=fs2,readonly=on \
-nic user,hostfwd=tcp::2222-:22 -nographic
# ssh into running sandbox # ssh into running sandbox
sandbox-ssh: sandbox-ssh:
ssh -A -p 2222 gordaina@localhost ssh -A -p 2222 gorazd@localhost
# hot-mount a host directory into the running sandbox
sandbox-mount path:
ssh -p 2222 gorazd@localhost "mkdir -p ~/mnt/$(basename {{path}}) && sshfs matej@10.0.2.2:{{path}} ~/mnt/$(basename {{path}})"

View File

@@ -1,21 +1,15 @@
lib: lib:
# auto-discover nix modules from a directory # import all .nix files in dir as attribute set
# - flat .nix files (excluding default.nix) are imported directly
# - subdirectories containing package.nix are imported via package.nix
dir: dir:
let let
readDir = builtins.readDir dir; readDir = builtins.readDir dir;
files = lib.attrNames (
lib.filterAttrs (
name: type: type == "regular" && lib.hasSuffix ".nix" name && name != "default.nix"
) readDir
);
files = lib.filterAttrs ( packages = builtins.map (name: lib.removeSuffix ".nix" name) files;
name: type: type == "regular" && lib.hasSuffix ".nix" name && name != "default.nix"
) readDir;
dirs = lib.filterAttrs (
name: type: type == "directory" && builtins.pathExists (dir + "/${name}/package.nix")
) readDir;
in in
lib.mapAttrs' ( lib.genAttrs packages (name: import (dir + "/${name}.nix"))
name: _: lib.nameValuePair (lib.removeSuffix ".nix" name) (import (dir + "/${name}"))
) files
// lib.mapAttrs (name: _: import (dir + "/${name}/package.nix")) dirs

View File

@@ -1,61 +0,0 @@
{
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"
'';
};
};
};
}

View File

@@ -9,19 +9,12 @@
options = { options = {
vm-guest = { vm-guest = {
enable = lib.mkEnableOption "VM guest configuration"; 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 { config = lib.mkIf config.vm-guest.enable {
services.qemuGuest.enable = true; services.qemuGuest.enable = true;
services.spice-vdagentd.enable = lib.mkIf (!config.vm-guest.headless) true; services.spice-vdagentd.enable = true;
boot.kernelParams = lib.mkIf config.vm-guest.headless [ "console=ttyS0,115200" ];
# 9p for host file mounting # 9p for host file mounting
boot.initrd.availableKernelModules = [ boot.initrd.availableKernelModules = [

View File

@@ -1,7 +1,7 @@
{ pkgs, ... }: { pkgs, ... }:
let let
version = "v0.4.2"; version = "v0.4.1";
in in
pkgs.rustPlatform.buildRustPackage { pkgs.rustPlatform.buildRustPackage {
pname = "ahab"; pname = "ahab";
@@ -12,10 +12,10 @@ pkgs.rustPlatform.buildRustPackage {
owner = "janezicmatej"; owner = "janezicmatej";
repo = "ahab"; repo = "ahab";
rev = version; rev = version;
sha256 = "sha256-hJg6vRaqTu9a3fua2J/e6akdJQffAk6TBAzJRBD5qHQ="; sha256 = "sha256-Y8UqZOskSlt8GrYem97yKXNbGkd6Ab7WRynKEA9w16E=";
}; };
cargoHash = "sha256-T/2+kxa5X2fmMQs023JN9ZDihExfYvPnunJ8b2Irwoo="; cargoHash = "sha256-T5r+Og3+mHMsqCFGi+QzHdN2MgvPxzA/R+xu38I+lcg=";
buildType = "debug"; buildType = "debug";

View File

@@ -1,62 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p curl jq nix-prefetch
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
PKG_FILE="$SCRIPT_DIR/package.nix"
cd "$ROOT_DIR"
extract_hash() {
sed 's/\x1b\[[0-9;]*m//g' | grep 'got:' | tail -1 | grep -oP 'sha256-[A-Za-z0-9+/]+='
}
echo "fetching latest version..."
LATEST=$(curl -sf "https://git.janezic.dev/api/v1/repos/janezicmatej/ahab/tags?limit=1" | jq -r '.[0].name')
CURRENT=$(grep 'version = ' "$PKG_FILE" | head -1 | sed 's/.*"\(.*\)".*/\1/')
if [[ "$CURRENT" == "$LATEST" ]]; then
echo "ahab already at $LATEST"
exit 0
fi
echo "updating ahab: $CURRENT -> $LATEST"
echo " prefetching source..."
BASE32=$(nix-prefetch-url --unpack "https://git.janezic.dev/janezicmatej/ahab/archive/${LATEST}.tar.gz" 2>/dev/null)
SRC_HASH=$(nix hash convert --to sri "sha256:$BASE32")
echo " source: $SRC_HASH"
echo " computing cargo hash..."
BUILD_OUTPUT=$(nix build --no-link --impure --expr "
let
pkgs = (builtins.getFlake \"path:$ROOT_DIR\").inputs.nixpkgs.legacyPackages.\${builtins.currentSystem};
in pkgs.rustPlatform.fetchCargoVendor {
src = pkgs.fetchFromGitea {
domain = \"git.janezic.dev\";
owner = \"janezicmatej\";
repo = \"ahab\";
rev = \"$LATEST\";
hash = \"$SRC_HASH\";
};
hash = \"\";
}
" 2>&1) || true
CARGO_HASH=$(echo "$BUILD_OUTPUT" | extract_hash) || true
if [[ -z "$CARGO_HASH" ]]; then
echo " error: failed to compute cargo hash"
echo "$BUILD_OUTPUT"
exit 1
fi
echo " cargo: $CARGO_HASH"
OLD_SRC=$(grep 'sha256 = ' "$PKG_FILE" | grep -oP 'sha256-[A-Za-z0-9+/]+=')
OLD_CARGO=$(grep 'cargoHash = ' "$PKG_FILE" | grep -oP 'sha256-[A-Za-z0-9+/]+=')
sed -i "s|version = \"$CURRENT\"|version = \"$LATEST\"|" "$PKG_FILE"
sed -i "s|$OLD_SRC|$SRC_HASH|" "$PKG_FILE"
sed -i "s|$OLD_CARGO|$CARGO_HASH|" "$PKG_FILE"
echo "ahab updated to $LATEST"

View File

@@ -2,7 +2,7 @@
let let
pkgs = pkgs-master; pkgs = pkgs-master;
version = "v1.27.1"; version = "e24855c";
in in
pkgs.buildGoModule.override pkgs.buildGoModule.override
{ {
@@ -16,10 +16,10 @@ pkgs.buildGoModule.override
owner = "tkw1536"; owner = "tkw1536";
repo = "ggman"; repo = "ggman";
rev = version; rev = version;
sha256 = "sha256-z7zqV69rPYwtkm4ieF+FIssBsFbREvaYQzSF648DHK0="; sha256 = "sha256-H78xtF7l5joX3/qDFaRIT4LyZpHmm6DMR4JIKzNO7c0=";
}; };
vendorHash = "sha256-5c5EgYjZXfexWMrUDS4fo46GCJBmFuWkw0cVqqGT7Ik="; vendorHash = "sha256-w8NrOt0xtn+/gugJ4amzdJP70Y5KHe5DlhsEprycm3U=";
subPackages = [ "cmd/ggman" ]; subPackages = [ "cmd/ggman" ];
ldflags = [ ldflags = [

View File

@@ -1,63 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p curl jq nix-prefetch
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
PKG_FILE="$SCRIPT_DIR/package.nix"
cd "$ROOT_DIR"
extract_hash() {
sed 's/\x1b\[[0-9;]*m//g' | grep 'got:' | tail -1 | grep -oP 'sha256-[A-Za-z0-9+/]+='
}
echo "fetching latest tag..."
LATEST=$(curl -sf "https://api.github.com/repos/tkw1536/ggman/tags?per_page=1" | jq -r '.[0].name')
CURRENT=$(grep 'version = ' "$PKG_FILE" | head -1 | sed 's/.*"\(.*\)".*/\1/')
if [[ "$CURRENT" == "$LATEST" ]]; then
echo "ggman already at $LATEST"
exit 0
fi
echo "updating ggman: $CURRENT -> $LATEST"
echo " prefetching source..."
BASE32=$(nix-prefetch-url --unpack "https://github.com/tkw1536/ggman/archive/${LATEST}.tar.gz" 2>/dev/null)
SRC_HASH=$(nix hash convert --to sri "sha256:$BASE32")
echo " source: $SRC_HASH"
echo " computing vendor hash..."
BUILD_OUTPUT=$(nix build --no-link --impure --expr "
let
pkgs = (builtins.getFlake \"path:$ROOT_DIR\").inputs.nixpkgs-master.legacyPackages.\${builtins.currentSystem};
in (pkgs.buildGoModule.override { go = pkgs.go_1_26; } {
pname = \"ggman\";
version = \"$LATEST\";
src = pkgs.fetchFromGitHub {
owner = \"tkw1536\";
repo = \"ggman\";
rev = \"$LATEST\";
hash = \"$SRC_HASH\";
};
vendorHash = \"\";
}).goModules
" 2>&1) || true
VENDOR_HASH=$(echo "$BUILD_OUTPUT" | extract_hash) || true
if [[ -z "$VENDOR_HASH" ]]; then
echo " error: failed to compute vendor hash"
echo "$BUILD_OUTPUT"
exit 1
fi
echo " vendor: $VENDOR_HASH"
OLD_SRC=$(grep 'sha256 = ' "$PKG_FILE" | grep -oP 'sha256-[A-Za-z0-9+/]+=')
OLD_VENDOR=$(grep 'vendorHash = ' "$PKG_FILE" | grep -oP 'sha256-[A-Za-z0-9+/]+=')
sed -i "s|version = \"$CURRENT\"|version = \"$LATEST\"|" "$PKG_FILE"
sed -i "s|$OLD_SRC|$SRC_HASH|" "$PKG_FILE"
sed -i "s|$OLD_VENDOR|$VENDOR_HASH|" "$PKG_FILE"
echo "ggman updated to $LATEST"

View File

@@ -1,7 +1,7 @@
{ pkgs, ... }: { pkgs, ... }:
let let
version = "v0.3.1"; version = "v0.2.1";
in in
pkgs.rustPlatform.buildRustPackage { pkgs.rustPlatform.buildRustPackage {
pname = "todo-mcp"; pname = "todo-mcp";
@@ -12,10 +12,10 @@ pkgs.rustPlatform.buildRustPackage {
owner = "janezicmatej"; owner = "janezicmatej";
repo = "todo-mcp"; repo = "todo-mcp";
rev = version; rev = version;
sha256 = "sha256-FLsPatHeWcDMLaGZS91aaXtZEful5frN2pqZkQN9vNs="; sha256 = "sha256-BBL7PAgTdGR/+vEJmot8c8mgw5vq5Y/szud0YEiR1UY=";
}; };
cargoHash = "sha256-gdR4p5LIEMGBV3ikuuRZ5R8CYIjE1K2OnMJm7yo18Nw="; cargoHash = "sha256-uAyD7Tj9qctDXQ5NkR6T/aItxRmd5WqIXr7NeOlCl8M=";
nativeBuildInputs = [ pkgs.installShellFiles ]; nativeBuildInputs = [ pkgs.installShellFiles ];

View File

@@ -1,62 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p curl jq nix-prefetch
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
PKG_FILE="$SCRIPT_DIR/package.nix"
cd "$ROOT_DIR"
extract_hash() {
sed 's/\x1b\[[0-9;]*m//g' | grep 'got:' | tail -1 | grep -oP 'sha256-[A-Za-z0-9+/]+='
}
echo "fetching latest version..."
LATEST=$(curl -sf "https://git.janezic.dev/api/v1/repos/janezicmatej/todo-mcp/tags?limit=1" | jq -r '.[0].name')
CURRENT=$(grep 'version = ' "$PKG_FILE" | head -1 | sed 's/.*"\(.*\)".*/\1/')
if [[ "$CURRENT" == "$LATEST" ]]; then
echo "todo-mcp already at $LATEST"
exit 0
fi
echo "updating todo-mcp: $CURRENT -> $LATEST"
echo " prefetching source..."
BASE32=$(nix-prefetch-url --unpack "https://git.janezic.dev/janezicmatej/todo-mcp/archive/${LATEST}.tar.gz" 2>/dev/null)
SRC_HASH=$(nix hash convert --to sri "sha256:$BASE32")
echo " source: $SRC_HASH"
echo " computing cargo hash..."
BUILD_OUTPUT=$(nix build --no-link --impure --expr "
let
pkgs = (builtins.getFlake \"path:$ROOT_DIR\").inputs.nixpkgs.legacyPackages.\${builtins.currentSystem};
in pkgs.rustPlatform.fetchCargoVendor {
src = pkgs.fetchFromGitea {
domain = \"git.janezic.dev\";
owner = \"janezicmatej\";
repo = \"todo-mcp\";
rev = \"$LATEST\";
hash = \"$SRC_HASH\";
};
hash = \"\";
}
" 2>&1) || true
CARGO_HASH=$(echo "$BUILD_OUTPUT" | extract_hash) || true
if [[ -z "$CARGO_HASH" ]]; then
echo " error: failed to compute cargo hash"
echo "$BUILD_OUTPUT"
exit 1
fi
echo " cargo: $CARGO_HASH"
OLD_SRC=$(grep 'sha256 = ' "$PKG_FILE" | grep -oP 'sha256-[A-Za-z0-9+/]+=')
OLD_CARGO=$(grep 'cargoHash = ' "$PKG_FILE" | grep -oP 'sha256-[A-Za-z0-9+/]+=')
sed -i "s|version = \"$CURRENT\"|version = \"$LATEST\"|" "$PKG_FILE"
sed -i "s|$OLD_SRC|$SRC_HASH|" "$PKG_FILE"
sed -i "s|$OLD_CARGO|$CARGO_HASH|" "$PKG_FILE"
echo "todo-mcp updated to $LATEST"

View File

@@ -0,0 +1,72 @@
{
config,
lib,
pkgs,
inputs,
...
}:
let
packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system};
in
{
home.stateVersion = "24.11";
home.packages = [
pkgs.git
];
# labwc desktop menu (right-click)
xdg.configFile."labwc/menu.xml".text = ''
<?xml version="1.0" encoding="UTF-8"?>
<openbox_menu>
<menu id="root-menu" label="">
<item label="Terminal"><action name="Execute"><command>foot</command></action></item>
<item label="Firefox"><action name="Execute"><command>firefox</command></action></item>
<item label="Files"><action name="Execute"><command>foot -e ranger</command></action></item>
<separator />
<item label="Reconfigure"><action name="Reconfigure" /></item>
<item label="Exit"><action name="Exit" /></item>
</menu>
</openbox_menu>
'';
# labwc autostart panel
xdg.configFile."labwc/autostart".text = ''
sfwbar &
'';
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 ]}"
];
};
}

View File

@@ -1,21 +0,0 @@
{
pkgs,
...
}:
{
home.stateVersion = "25.11";
home.packages = with pkgs; [
git
tmux
ripgrep
fd
jq
];
programs.neovim = {
enable = true;
vimAlias = true;
defaultEditor = true;
};
}