Compare commits

...

18 Commits

Author SHA1 Message Date
6772afb845 merge: harden ephvm 2026-04-24 14:14:29 +02:00
e9755d41c6 feat: tighten ephvm perms, zstd compress qcow2 2026-04-24 14:13:01 +02:00
68411d9459 feat: prune vm-guest module 2026-04-24 14:12:57 +02:00
7fd5b790ff feat: ephvm-run.sh virtio devices, require kvm 2026-04-24 14:12:52 +02:00
37bca1fdd1 feat: ephvm-run.sh resilience 2026-04-24 14:12:48 +02:00
75ca09949c feat: harden ephvm-run.sh 2026-04-24 14:12:42 +02:00
2fcdee5d81 feat: set XDPW_PERSIST_MODE="permanent" 2026-04-23 23:14:45 +02:00
c01f797e79 chore: bump lockfile 2026-04-22 00:10:04 +02:00
59a2bfa126 chore: update claude-code to v2.1.116 2026-04-22 00:08:31 +02:00
e486bb28b0 feat: enable hM.neovim.sidloadInitLua 2026-04-22 00:06:16 +02:00
d33fd60ce4 feat: switch from vesktop to discord 2026-04-21 23:39:57 +02:00
37428d922b feat: add plymouth option to bootloader 2026-04-21 22:43:13 +02:00
b1cfe1e31b feat: initrd infinite default device timeout 2026-04-21 22:42:33 +02:00
df2bc27f54 chore: blame ignore 77236af589 2026-04-21 22:11:33 +02:00
77236af589 chore: run format 2026-04-21 22:09:39 +02:00
f71d156ea8 feat: enable cache fallback 2026-04-21 10:08:08 +02:00
0c517e0957 chore: bump lockfile 2026-04-20 07:33:32 +02:00
37620c76fe chore: bump claude-code to v2.1.114 2026-04-20 07:32:02 +02:00
15 changed files with 259 additions and 469 deletions

View File

@@ -2,6 +2,7 @@
f011c8d71ba09bd94ab04b8d771858b90a03fbf9
3aff25b4486a143cd6282f8845c16216598e1c7e
2204b12fadf27886058e6945806ce93a547f5278
77236af5896524218605badcd3cdfc2267b213da
# host rename
cfe4c43887a41e52be4e6472474c0fc3788f86e8

View File

@@ -22,12 +22,16 @@
];
default = "systemd-boot";
};
plymouth.enable = lib.mkEnableOption "plymouth boot splash";
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
boot.loader.efi.canTouchEfiVariables = true;
# request the largest framebuffer uefi offers; plymouth inherits it
boot.loader.systemd-boot.consoleMode = "max";
}
(lib.mkIf (cfg.mode == "systemd-boot") {
@@ -41,6 +45,28 @@
pkiBundle = "/var/lib/sbctl";
};
})
(lib.mkIf cfg.plymouth.enable {
# plymouth needs systemd-initrd to render the luks prompt cleanly
boot.initrd.systemd.enable = true;
# host is responsible for early-KMS so plymouth lands on the gpu driver,
# not simpledrm (e.g. hardware.amdgpu.initrd.enable on amd hosts)
boot.plymouth.enable = true;
stylix.targets.plymouth.logoAnimated = false;
boot.kernelParams = [
"quiet"
"splash"
"loglevel=3"
"rd.systemd.show_status=false"
"rd.udev.log_level=3"
"udev.log_priority=3"
"plymouth.force-scale=1"
];
boot.consoleLogLevel = 0;
boot.initrd.verbose = false;
})
]
);
};

View File

@@ -71,6 +71,12 @@
];
};
# honor persist_mode so electron apps don't re-prompt for screencast every login
systemd.user.services.xdg-desktop-portal-wlr.environment.XDPW_PERSIST_MODE = "permanent";
# enable ozone/wayland for electron apps so idle detection works
environment.sessionVariables.NIXOS_OZONE_WL = "1";
fonts.packages = with pkgs; [
font-awesome
nerd-fonts.jetbrains-mono
@@ -114,7 +120,7 @@
bolt-launcher
libnotify
bibata-cursors
vesktop
discord
rocketchat-desktop
telegram-desktop
slack
@@ -256,7 +262,7 @@
# app deep links
"x-scheme-handler/tg" = "org.telegram.desktop.desktop";
"x-scheme-handler/discord" = "vesktop.desktop";
"x-scheme-handler/discord" = "discord.desktop";
"x-scheme-handler/slack" = "slack.desktop";
};
};

View File

@@ -59,6 +59,9 @@
boot.initrd.systemd.enable = true;
# remote unlock may take a while; don't let device units give up
boot.initrd.systemd.settings.Manager.DefaultDeviceTimeoutSec = "infinity";
boot.initrd.network = {
enable = true;
ssh = {

View File

@@ -39,6 +39,7 @@
programs.neovim = {
enable = true;
sideloadInitLua = true;
vimAlias = true;
defaultEditor = true;
package = inputs.neovim-nightly-overlay.packages.${pkgs.stdenv.hostPlatform.system}.default;

View File

@@ -40,6 +40,8 @@
"flakes"
];
download-buffer-size = 2 * 1024 * 1024 * 1024;
download-attempts = 3;
fallback = true;
warn-dirty = false;
substituters = [
"https://cache.nixos.org"

View File

@@ -43,19 +43,15 @@
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
services.qemuGuest.enable = true;
services.spice-vdagentd.enable = lib.mkIf (!cfg.headless) true;
boot.kernelParams = lib.mkIf cfg.headless [ "console=ttyS0,115200" ];
# 9p autoloads on first mount
boot.initrd.availableKernelModules = [
"9p"
"9pnet_virtio"
];
boot.kernelModules = [
"9p"
"9pnet_virtio"
];
networking = {
useDHCP = true;
@@ -68,7 +64,6 @@
curl
wget
htop
sshfs
];
}

60
flake.lock generated
View File

@@ -106,11 +106,11 @@
]
},
"locked": {
"lastModified": 1773889306,
"narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=",
"lastModified": 1776613567,
"narHash": "sha256-gC9Cp5ibBmGD5awCA9z7xy6MW6iJufhazTYJOiGlCUI=",
"owner": "nix-community",
"repo": "disko",
"rev": "5ad85c82cc52264f4beddc934ba57f3789f28347",
"rev": "32f4236bfc141ae930b5ba2fb604f561fed5219d",
"type": "github"
},
"original": {
@@ -273,11 +273,11 @@
]
},
"locked": {
"lastModified": 1776030105,
"narHash": "sha256-b4cNpWPDSH+/CTTiw8++yGh1UYG2kQNrbIehV2iGoeo=",
"lastModified": 1776777932,
"narHash": "sha256-0R3Yow/NzSeVGUke5tL7CCkqmss4Vmi6BbV6idHzq/8=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "49088dc2e7a876e338e510c5f5f60f659819c650",
"rev": "5d5640599a0050b994330328b9fd45709c909720",
"type": "github"
},
"original": {
@@ -317,11 +317,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1775952282,
"narHash": "sha256-iJcGy0pW0wX7q6HAQuKx8sskTyu8an0l0gI3TBgzk3E=",
"lastModified": 1776729909,
"narHash": "sha256-wGu/N42PJqrj8ju9GoXdppg4rwaKzZqdAjsgxJbCvfY=",
"owner": "nix-community",
"repo": "neovim-nightly-overlay",
"rev": "f719e136a8e0cd91e70515e590385356abce1341",
"rev": "ff21a18bde28b4c8ca0bc1f9a5b7186a1b89a3d1",
"type": "github"
},
"original": {
@@ -333,11 +333,11 @@
"neovim-src": {
"flake": false,
"locked": {
"lastModified": 1775949028,
"narHash": "sha256-JXrr9lxKfTIm/VW4jvaB1RU9r+7pAoaXeDsy24TGPiw=",
"lastModified": 1776727374,
"narHash": "sha256-iP5SviNXW5W+ay4ZmwjDFsfQjfM+fYlUxRlLPHjpwWI=",
"owner": "neovim",
"repo": "neovim",
"rev": "4a289bfce3e71bf00d1eced168a6a7bbb270b95b",
"rev": "901b3f0c394a53961781ebeee682e64ad690a242",
"type": "github"
},
"original": {
@@ -364,11 +364,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1775888245,
"narHash": "sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90=",
"lastModified": 1776329215,
"narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "13043924aaa7375ce482ebe2494338e058282925",
"rev": "b86751bc4085f48661017fa226dee99fab6c651b",
"type": "github"
},
"original": {
@@ -395,11 +395,11 @@
},
"nixpkgs-master": {
"locked": {
"lastModified": 1776031281,
"narHash": "sha256-MCXhNHfTvsvbdkn9WV3Rv5Z0tUig1CtINZV+jaWh04k=",
"lastModified": 1776807375,
"narHash": "sha256-LDnHG0T54OEHyRydmGUlAND8ham0KrRNWjgoS+6GUd4=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "4ee46f65286df51761a238bb0f024f8d696ac683",
"rev": "553ecb1686a2edb75dee44c9f72e1674e6adc26a",
"type": "github"
},
"original": {
@@ -411,11 +411,11 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1775811116,
"narHash": "sha256-t+HZK42pB6N+i5RGbuy7Xluez/VvWbembBdvzsc23Ss=",
"lastModified": 1776560675,
"narHash": "sha256-p68udKWWh7+V4ZPpcMDq0gTHWNZJnr4JPI+kHPPE40o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "54170c54449ea4d6725efd30d719c5e505f1c10e",
"rev": "e07580dae39738e46609eaab8b154de2488133ce",
"type": "github"
},
"original": {
@@ -427,11 +427,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1775710090,
"narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=",
"lastModified": 1776548001,
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4c1018dae018162ec878d42fec712642d214fdfa",
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
"type": "github"
},
"original": {
@@ -550,11 +550,11 @@
]
},
"locked": {
"lastModified": 1775971308,
"narHash": "sha256-VKp9bhVSm0bT6JWctFy06ocqxGGnWHi1NfoE90IgIcY=",
"lastModified": 1776771786,
"narHash": "sha256-DRFGPfFV6hbrfO9a1PH1FkCi7qR5FgjSqsQGGvk1rdI=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "31ac5fe5d015f76b54058c69fcaebb66a55871a4",
"rev": "bef289e2248991f7afeb95965c82fbcd8ff72598",
"type": "github"
},
"original": {
@@ -583,11 +583,11 @@
"tinted-zed": "tinted-zed"
},
"locked": {
"lastModified": 1775936757,
"narHash": "sha256-KJO/7qoxJ+hlsb3WlFSl6IGrExBIf1GvKdrhOlnGdKY=",
"lastModified": 1776170745,
"narHash": "sha256-Tl1aZVP5EIlT+k0+iAKH018GLHJpLz3hhJ0LNQOWxCc=",
"owner": "danth",
"repo": "stylix",
"rev": "d3e447786b74d62c75f665e17cb3e681c66e90c7",
"rev": "e3861617645a43c9bbefde1aa6ac54dd0a44bfa9",
"type": "github"
},
"original": {

View File

@@ -13,19 +13,38 @@
documentation.enable = false;
environment.defaultPackages = [ ];
# compressed qcow2, no channel copy
# qcow2, no channel copy; post-processed with parallel zstd on qcow2 v3
# (~half the size of zlib v2, faster decompress)
image.modules.qemu =
{ config, modulesPath, ... }:
{
system.build.image = lib.mkForce (
import (modulesPath + "/../lib/make-disk-image.nix") {
let
rawImage = import (modulesPath + "/../lib/make-disk-image.nix") {
inherit lib config pkgs;
inherit (config.virtualisation) diskSize;
inherit (config.image) baseName;
format = "qcow2-compressed";
format = "qcow2";
copyChannel = false;
partitionTableType = "legacy";
}
};
inherit (config.image) baseName;
in
pkgs.runCommand baseName { nativeBuildInputs = [ pkgs.qemu-utils ]; } ''
mkdir -p $out
# qemu-img caps -m at 16
cores="''${NIX_BUILD_CORES:-4}"
[ "$cores" -gt 0 ] || cores=4
[ "$cores" -gt 16 ] && cores=16
qemu-img convert \
-f qcow2 \
-O qcow2 \
-c \
-o compression_type=zstd \
-m "$cores" \
${rawImage}/${baseName}.qcow2 \
$out/${baseName}.qcow2
''
);
};
@@ -70,7 +89,7 @@
features.neovim.dotfiles = inputs.nvim;
# ensure .config exists with correct ownership before automount
systemd.tmpfiles.rules = [ "d /home/matej/.config 0755 matej users -" ];
systemd.tmpfiles.rules = [ "d /home/matej/.config 0700 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

View File

@@ -10,6 +10,7 @@
inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series
];
features.bootloader.plymouth.enable = true;
features.desktop.bluetooth.enable = true;
features.gnupg.yubikey.enable = true;
features.udev = {

View File

@@ -6,7 +6,10 @@
{
features.nix-settings.towerCache.enable = false;
features.bootloader.mode = "lanzaboote";
features.bootloader = {
mode = "lanzaboote";
plymouth.enable = true;
};
features.desktop.bluetooth.enable = true;
features.gnupg.yubikey.enable = true;
features.udev = {
@@ -23,6 +26,8 @@
nix.settings.secret-key-files = [ config.sops.secrets.nix-signing-key.path ];
boot.kernelParams = [ "btusb.reset=1" ];
# early kms so plymouth lands on amdgpu, not simpledrm
hardware.amdgpu.initrd.enable = true;
services.udisks2.enable = true;

View File

@@ -1,334 +0,0 @@
{
"name": "@anthropic-ai/claude-code",
"version": "2.1.112",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@anthropic-ai/claude-code",
"version": "2.1.112",
"license": "SEE LICENSE IN README.md",
"bin": {
"claude": "cli.js"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "^0.34.2",
"@img/sharp-darwin-x64": "^0.34.2",
"@img/sharp-linux-arm": "^0.34.2",
"@img/sharp-linux-arm64": "^0.34.2",
"@img/sharp-linux-x64": "^0.34.2",
"@img/sharp-linuxmusl-arm64": "^0.34.2",
"@img/sharp-linuxmusl-x64": "^0.34.2",
"@img/sharp-win32-arm64": "^0.34.2",
"@img/sharp-win32-x64": "^0.34.2"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.4"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
}
}
}

View File

@@ -1,41 +1,78 @@
{ pkgs, ... }:
pkgs.buildNpmPackage (finalAttrs: {
pname = "claude-code";
version = "2.1.112";
let
inherit (pkgs) stdenv lib;
version = "2.1.116";
src = pkgs.fetchzip {
url = "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-${finalAttrs.version}.tgz";
hash = "sha256-SJJqU7XHbu9IRGPMJNUg6oaMZiQUKqJhI2wm7BnR1gs=";
# upstream ships platform-native binaries as separate npm packages under
# @anthropic-ai/claude-code-<platform>; the wrapper package is just a
# postinstall shim that copies the matching one into place
sources = {
"x86_64-linux" = {
slug = "linux-x64";
hash = "sha256-QEjJ4CRk35TubDNW02Dzcu+EMRLLndJUXJeP3BFT3b8=";
};
"aarch64-linux" = {
slug = "linux-arm64";
hash = "sha256-/Hqp8GQx8Hub8K4w0Fnx/AksksY61vRC44XxrJVwF5w=";
};
"x86_64-darwin" = {
slug = "darwin-x64";
hash = "sha256-O3J/ew2fWbUQePs6tHEhK0Q9E3Mx/BDSL7b7NL3FRc8=";
};
"aarch64-darwin" = {
slug = "darwin-arm64";
hash = "sha256-O41sf7b05SJfXVjszMeTp838mja+PgZ+aEKykLsHeNo=";
};
};
npmDepsHash = "sha256-bdkej9Z41GLew9wi1zdNX+Asauki3nT1+SHmBmaUIBU=";
source =
sources.${stdenv.hostPlatform.system}
or (throw "claude-code: unsupported system ${stdenv.hostPlatform.system}");
in
stdenv.mkDerivation {
pname = "claude-code";
inherit version;
strictDeps = true;
src = pkgs.fetchzip {
url = "https://registry.npmjs.org/@anthropic-ai/claude-code-${source.slug}/-/claude-code-${source.slug}-${version}.tgz";
inherit (source) hash;
};
postPatch = ''
cp ${./package-lock.json} package-lock.json
nativeBuildInputs = [
pkgs.makeWrapper
]
++ lib.optionals stdenv.hostPlatform.isLinux [ pkgs.patchelf ];
substituteInPlace cli.js \
--replace-fail '#!/bin/sh' '#!/usr/bin/env sh'
dontBuild = true;
dontConfigure = true;
dontStrip = true;
installPhase = ''
runHook preInstall
install -Dm755 claude $out/bin/claude
runHook postInstall
'';
dontNpmBuild = true;
env.AUTHORIZED = "1";
postInstall = ''
# NOTE:(@janezicmatej) upstream is a bun single-file-executable; the
# embedded script payload sits at the tail of the ELF, so autoPatchelfHook's
# section-layout changes corrupt it — only the interpreter can be rewritten
postFixup =
lib.optionalString stdenv.hostPlatform.isLinux ''
patchelf --set-interpreter ${stdenv.cc.bintools.dynamicLinker} $out/bin/claude
''
+ ''
wrapProgram $out/bin/claude \
--set DISABLE_AUTOUPDATER 1 \
--set-default FORCE_AUTOUPDATE_PLUGINS 1 \
--set DISABLE_INSTALLATION_CHECKS 1 \
--unset DEV \
--prefix PATH : ${
pkgs.lib.makeBinPath (
lib.makeBinPath (
[
pkgs.procps
]
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [
++ lib.optionals stdenv.hostPlatform.isLinux [
pkgs.bubblewrap
pkgs.socat
]
@@ -47,7 +84,8 @@ pkgs.buildNpmPackage (finalAttrs: {
description = "Agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster";
homepage = "https://github.com/anthropics/claude-code";
downloadPage = "https://www.npmjs.com/package/@anthropic-ai/claude-code";
license = pkgs.lib.licenses.unfree;
license = lib.licenses.unfree;
mainProgram = "claude";
platforms = lib.attrNames sources;
};
})
}

View File

@@ -1,17 +1,18 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p curl jq nodejs prefetch-npm-deps nix-prefetch
#!nix-shell -i bash -p curl jq nix
# shellcheck shell=bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
PKG_FILE="$SCRIPT_DIR/package.nix"
LOCK_FILE="$SCRIPT_DIR/package-lock.json"
cd "$ROOT_DIR"
# keep in sync with the `sources` attrset in package.nix
PLATFORMS=(linux-x64 linux-arm64 darwin-x64 darwin-arm64)
extract_hash() {
sed 's/\x1b\[[0-9;]*m//g' | grep 'got:' | tail -1 | grep -oP 'sha256-[A-Za-z0-9+/]+='
prefetch() {
local url="$1"
nix --extra-experimental-features 'nix-command flakes' \
store prefetch-file --unpack --json "$url" 2>/dev/null | jq -r '.hash'
}
main() {
@@ -27,35 +28,24 @@ main() {
echo "updating claude-code: $current -> $latest"
local url="https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-${latest}.tgz"
echo " prefetching source..."
local base32 src_hash
base32=$(nix-prefetch-url --unpack "$url" 2>/dev/null)
src_hash=$(nix hash convert --to sri "sha256:$base32")
echo " source: $src_hash"
echo " generating package-lock.json..."
local tmpdir
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' RETURN
curl -sf "$url" -o "$tmpdir/pkg.tgz"
tar xzf "$tmpdir/pkg.tgz" -C "$tmpdir" --strip-components=1
(cd "$tmpdir" && npm install --package-lock-only --ignore-scripts --no-audit --no-fund 2>/dev/null)
cp "$tmpdir/package-lock.json" "$LOCK_FILE"
echo " computing npm deps hash..."
local npm_hash
npm_hash=$(prefetch-npm-deps "$LOCK_FILE" 2>/dev/null)
echo " npmDepsHash: $npm_hash"
local old_src old_npm
old_src=$(grep 'hash = "sha256-' "$PKG_FILE" | head -1 | grep -oP 'sha256-[A-Za-z0-9+/]+=')
old_npm=$(grep 'npmDepsHash = "sha256-' "$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_npm|$npm_hash|" "$PKG_FILE"
local slug url new_hash old_hash
for slug in "${PLATFORMS[@]}"; do
url="https://registry.npmjs.org/@anthropic-ai/claude-code-${slug}/-/claude-code-${slug}-${latest}.tgz"
echo " prefetching $slug..."
new_hash=$(prefetch "$url")
old_hash=$(awk -v slug="$slug" '
$0 ~ "slug = \"" slug "\";" { found=1; next }
found && /hash = "sha256-/ {
match($0, /sha256-[A-Za-z0-9+\/]+=*/)
print substr($0, RSTART, RLENGTH)
exit
}
' "$PKG_FILE")
sed -i "s|$old_hash|$new_hash|" "$PKG_FILE"
echo " $new_hash"
done
echo "claude-code updated to $latest"
}

View File

@@ -27,14 +27,32 @@ info() {
# globals for cleanup trap
CLEANUP_OVERLAY=""
CLEANUP_TMPDIR=""
QEMU_PID=""
VM_READY=false
cleanup() {
[ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null && wait "$QEMU_PID" 2>/dev/null
[ -n "$CLEANUP_OVERLAY" ] && rm -rf "$CLEANUP_OVERLAY"
# preserve tmpdir on abnormal exit so the qemu log survives for inspection
if [ -n "$CLEANUP_TMPDIR" ]; then
if [ "$VM_READY" = true ]; then
rm -rf "$CLEANUP_TMPDIR"
else
echo "qemu log preserved: $CLEANUP_TMPDIR/qemu.log" >&2
fi
fi
return 0
}
trap cleanup EXIT
# returns 0 once the guest's sshd is speaking (first bytes are "SSH-")
awaiting_ssh_banner() {
local port="$1"
local banner
banner=$(timeout 2 bash -c "exec 3<>/dev/tcp/localhost/$port; head -c 4 <&3" 2>/dev/null) || return 1
[ "$banner" = "SSH-" ]
}
usage() {
cat <<EOF
Usage: ephvm-run.sh [options]
@@ -55,6 +73,8 @@ EOF
main() {
setup_colors
[ "$EUID" -eq 0 ] && die "ephvm-run.sh must not run as root"
local ssh_port="" memory=4G cpus=2 claude=true disk_size="" serial=false
local -a mounts=()
@@ -110,15 +130,13 @@ main() {
CLEANUP_OVERLAY=$(mktemp -d)
local overlay="$CLEANUP_OVERLAY/overlay.qcow2"
qemu-img create -f qcow2 -b "$(realpath "$image")" -F qcow2 "$overlay" "$disk_size"
drive_arg="file=$overlay,format=qcow2"
drive_arg="if=none,id=hd0,file=$overlay,format=qcow2,cache=writeback,aio=threads,discard=unmap,detect-zeroes=unmap"
else
drive_arg="file=$image,format=qcow2,snapshot=on"
drive_arg="if=none,id=hd0,file=$image,format=qcow2,snapshot=on,cache=writeback,aio=threads,discard=unmap,detect-zeroes=unmap"
fi
command -v qemu-system-x86_64 &>/dev/null || die "qemu-system-x86_64 not found"
local accel="tcg"
[ -r /dev/kvm ] && accel="kvm"
[ -r /dev/kvm ] || die "/dev/kvm not readable; kvm is required"
# auto-allocate ssh port unless serial mode
if [ "$serial" = false ] && [ -z "$ssh_port" ]; then
@@ -128,28 +146,33 @@ main() {
done
fi
local nic_arg="user"
local nic_arg="user,model=virtio-net-pci"
if [ -n "$ssh_port" ]; then
nic_arg="user,hostfwd=tcp::${ssh_port}-:22"
nic_arg="user,model=virtio-net-pci,hostfwd=tcp:127.0.0.1:${ssh_port}-:22"
fi
local -a qemu_args=(
qemu-system-x86_64
-accel "$accel"
-accel kvm
-cpu host
-m "$memory"
-smp "$cpus"
-drive "$drive_arg"
-device "virtio-blk-pci,drive=hd0"
-device virtio-rng-pci
-nic "$nic_arg"
-nographic
-sandbox "on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny"
)
if [ "$accel" != "tcg" ]; then
qemu_args+=(-cpu host)
fi
local fs_id=0 mount_path name tag
for mount_path in "${mounts[@]}"; do
[ -e "$mount_path" ] || die "--mount path does not exist: $mount_path"
mount_path=$(realpath "$mount_path")
# qemu parses -virtfs as csv, a comma in the path would inject options
case "$mount_path" in
*,*) die "--mount path may not contain commas: $mount_path" ;;
esac
name=$(basename "$mount_path")
tag="m_${name:0:29}"
qemu_args+=(
@@ -163,6 +186,9 @@ main() {
mkdir -p "$CLAUDE_CONFIG_DIR"
local claude_dir
claude_dir=$(realpath "$CLAUDE_CONFIG_DIR")
case "$claude_dir" in
*,*) die "claude config dir may not contain commas: $claude_dir" ;;
esac
qemu_args+=(
-virtfs "local,path=$claude_dir,mount_tag=claude,security_model=none,id=fs${fs_id}"
@@ -171,27 +197,38 @@ main() {
fi
info "---"
info "Accel: $accel"
[ -n "$ssh_port" ] && info "SSH: ssh -p $ssh_port matej@localhost"
info "---"
if [ "$serial" = true ]; then
exec "${qemu_args[@]}"
fi
CLEANUP_TMPDIR=$(mktemp -d)
local qemu_log="$CLEANUP_TMPDIR/qemu.log"
# start qemu in background and auto-ssh
"${qemu_args[@]}" &>/dev/null &
"${qemu_args[@]}" &>"$qemu_log" &
QEMU_PID=$!
# throwaway ssh key (vm accepts any key via AuthorizedKeysCommand)
local ssh_key="$CLEANUP_TMPDIR/id_ed25519"
ssh-keygen -t ed25519 -f "$ssh_key" -N "" -q
info "waiting for vm (port $ssh_port)..."
local attempts=0
while ! (echo > /dev/tcp/localhost/"$ssh_port") 2>/dev/null; do
# poll for the real SSH banner, not TCP accept: qemu's user-mode nic
# accepts host-side the moment qemu starts, well before guest sshd is up
while ! awaiting_ssh_banner "$ssh_port"; do
attempts=$((attempts + 1))
[ $attempts -gt 60 ] && die "vm did not become ready in 60s"
[ $attempts -gt 120 ] && die "vm did not become ready in 60s"
kill -0 "$QEMU_PID" 2>/dev/null || die "qemu exited unexpectedly"
sleep 1
sleep 0.5
done
VM_READY=true
ssh -p "$ssh_port" -t \
-i "$ssh_key" \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \