Compare commits

...

8 Commits

23 changed files with 827 additions and 96 deletions

3
.gitignore vendored
View File

@@ -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
View 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
View 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"

149
dist/run.sh vendored Executable file
View File

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

View File

@@ -93,6 +93,16 @@
system = "x86_64-linux"; system = "x86_64-linux";
users = [ ]; users = [ ];
}; };
# nixos-rebuild build-image --image-variant qemu --flake .#sandbox
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 {

View File

@@ -65,6 +65,7 @@ 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

@@ -0,0 +1,128 @@
{
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;
password = "sandbox";
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.automount"
"x-systemd.device-timeout=2s"
];
};
fileSystems."/mnt/host-home" = {
device = "hosthome";
fsType = "9p";
options = [
"trans=virtio"
"version=9p2000.L"
"msize=65536"
"nofail"
"x-systemd.automount"
"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 = [ "local-fs.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "claude-auth" ''
# skip if host mounts are not available
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
];
system.stateVersion = "25.11";
}

View File

@@ -0,0 +1,23 @@
{
lib,
pkgs,
modulesPath,
...
}:
{
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
];
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
autoResize = true;
fsType = "ext4";
};
# x86_64: bios/grub, aarch64: uefi/systemd-boot
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,6 +17,18 @@ 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
@@ -24,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 gordaina@localhost

View File

@@ -1,15 +1,21 @@
lib: lib:
# import all .nix files in dir as attribute set # auto-discover nix modules from a directory
# - 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
);
packages = builtins.map (name: lib.removeSuffix ".nix" name) files; files = lib.filterAttrs (
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.genAttrs packages (name: import (dir + "/${name}.nix")) lib.mapAttrs' (
name: _: lib.nameValuePair (lib.removeSuffix ".nix" name) (import (dir + "/${name}"))
) files
// lib.mapAttrs (name: _: import (dir + "/${name}/package.nix")) dirs

View 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"
'';
};
};
};
}

View 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 = true;
PermitRootLogin = "no";
AllowAgentForwarding = true;
StreamLocalBindUnlink = "yes";
};
};
networking = {
useDHCP = true;
firewall.allowedTCPPorts = [ 22 ];
};
security.sudo.wheelNeedsPassword = false;
environment.systemPackages = with pkgs; [
curl
wget
htop
sshfs
];
};
}

View File

@@ -1,7 +1,7 @@
{ pkgs, ... }: { pkgs, ... }:
let let
version = "v0.4.1"; version = "v0.4.2";
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-Y8UqZOskSlt8GrYem97yKXNbGkd6Ab7WRynKEA9w16E="; sha256 = "sha256-hJg6vRaqTu9a3fua2J/e6akdJQffAk6TBAzJRBD5qHQ=";
}; };
cargoHash = "sha256-T5r+Og3+mHMsqCFGi+QzHdN2MgvPxzA/R+xu38I+lcg="; cargoHash = "sha256-T/2+kxa5X2fmMQs023JN9ZDihExfYvPnunJ8b2Irwoo=";
buildType = "debug"; buildType = "debug";

62
packages/ahab/update.sh Executable file
View File

@@ -0,0 +1,62 @@
#!/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 = "e24855c"; version = "v1.27.1";
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-H78xtF7l5joX3/qDFaRIT4LyZpHmm6DMR4JIKzNO7c0="; sha256 = "sha256-z7zqV69rPYwtkm4ieF+FIssBsFbREvaYQzSF648DHK0=";
}; };
vendorHash = "sha256-w8NrOt0xtn+/gugJ4amzdJP70Y5KHe5DlhsEprycm3U="; vendorHash = "sha256-5c5EgYjZXfexWMrUDS4fo46GCJBmFuWkw0cVqqGT7Ik=";
subPackages = [ "cmd/ggman" ]; subPackages = [ "cmd/ggman" ];
ldflags = [ ldflags = [

63
packages/ggman/update.sh Executable file
View File

@@ -0,0 +1,63 @@
#!/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.2.1"; version = "v0.3.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-BBL7PAgTdGR/+vEJmot8c8mgw5vq5Y/szud0YEiR1UY="; sha256 = "sha256-FLsPatHeWcDMLaGZS91aaXtZEful5frN2pqZkQN9vNs=";
}; };
cargoHash = "sha256-uAyD7Tj9qctDXQ5NkR6T/aItxRmd5WqIXr7NeOlCl8M="; cargoHash = "sha256-gdR4p5LIEMGBV3ikuuRZ5R8CYIjE1K2OnMJm7yo18Nw=";
nativeBuildInputs = [ pkgs.installShellFiles ]; nativeBuildInputs = [ pkgs.installShellFiles ];

62
packages/todo-mcp/update.sh Executable file
View File

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

@@ -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 ]}"
];
};
}

View 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;
};
}