Compare commits

...

25 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
ac76b8c842 feat: systemd-networkd during initrd 2026-04-16 23:36:10 +02:00
df7c4cec83 feat: bump claude-code to v2.1.112 2026-04-16 23:10:00 +02:00
5b52e41496 feat: self-package claude-code 2026-04-16 22:59:01 +02:00
a60b40eeac feat: propagate XDG_DATA_DIRS to dbus/systemd 2026-04-15 09:45:21 +02:00
b341f7f4fc feat: setup mime apps 2026-04-15 00:25:07 +02:00
571fb2ff99 chore: blame ignore 2204b12fad 2026-04-13 23:04:46 +02:00
2204b12fad chore: run lint & format 2026-04-13 23:04:16 +02:00
31 changed files with 920 additions and 471 deletions

View File

@@ -1,6 +1,8 @@
# nix fmt & statix # nix fmt & statix
f011c8d71ba09bd94ab04b8d771858b90a03fbf9 f011c8d71ba09bd94ab04b8d771858b90a03fbf9
3aff25b4486a143cd6282f8845c16216598e1c7e 3aff25b4486a143cd6282f8845c16216598e1c7e
2204b12fadf27886058e6945806ce93a547f5278
77236af5896524218605badcd3cdfc2267b213da
# host rename # host rename
cfe4c43887a41e52be4e6472474c0fc3788f86e8 cfe4c43887a41e52be4e6472474c0fc3788f86e8

View File

@@ -1,6 +1,11 @@
{ {
nixos = nixos =
{ config, lib, inputs, ... }: {
config,
lib,
inputs,
...
}:
let let
cfg = config.features.bootloader; cfg = config.features.bootloader;
in in
@@ -17,24 +22,52 @@
]; ];
default = "systemd-boot"; default = "systemd-boot";
}; };
plymouth.enable = lib.mkEnableOption "plymouth boot splash";
}; };
config = lib.mkIf cfg.enable (lib.mkMerge [ config = lib.mkIf cfg.enable (
{ lib.mkMerge [
boot.loader.efi.canTouchEfiVariables = true; {
} 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") { (lib.mkIf (cfg.mode == "systemd-boot") {
boot.loader.systemd-boot.enable = true; boot.loader.systemd-boot.enable = true;
}) })
(lib.mkIf (cfg.mode == "lanzaboote") { (lib.mkIf (cfg.mode == "lanzaboote") {
boot.loader.systemd-boot.enable = lib.mkForce false; boot.loader.systemd-boot.enable = lib.mkForce false;
boot.lanzaboote = { boot.lanzaboote = {
enable = true; enable = true;
pkiBundle = "/var/lib/sbctl"; 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

@@ -6,14 +6,21 @@
}; };
home = home =
{ pkgs, lib, osConfig, ... }: {
pkgs,
lib,
inputs,
osConfig,
...
}:
let let
cfg = osConfig.features.claude; cfg = osConfig.features.claude;
packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system};
in in
{ {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home.packages = [ home.packages = [
pkgs.claude-code packages.claude-code
pkgs.mcp-nixos pkgs.mcp-nixos
]; ];
}; };

View File

@@ -1,6 +1,12 @@
{ {
nixos = nixos =
{ config, lib, pkgs, inputs, ... }: {
config,
lib,
pkgs,
inputs,
...
}:
let let
cfg = config.features.desktop; cfg = config.features.desktop;
in in
@@ -49,104 +55,219 @@
}; };
}; };
config = lib.mkIf cfg.enable (lib.mkMerge [ config = lib.mkIf cfg.enable (
# base desktop lib.mkMerge [
{ # base desktop
security.polkit.enable = true; {
services.dbus.enable = true; security.polkit.enable = true;
services.playerctld.enable = true; services.dbus.enable = true;
services.playerctld.enable = true;
xdg.portal = { xdg.portal = {
enable = true; enable = true;
xdgOpenUsePortal = true; extraPortals = with pkgs; [
extraPortals = with pkgs; [ xdg-desktop-portal-wlr
xdg-desktop-portal-wlr xdg-desktop-portal-gtk
xdg-desktop-portal-gtk ];
};
# 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
]; ];
};
fonts.packages = with pkgs; [ stylix = {
font-awesome enable = true;
nerd-fonts.jetbrains-mono inherit (cfg.theme) polarity;
]; image = cfg.theme.wallpaper;
base16Scheme = "${pkgs.base16-schemes}/share/themes/${cfg.theme.scheme}.yaml";
};
}
stylix = { # audio
enable = true; (lib.mkIf cfg.audio.enable {
polarity = cfg.theme.polarity; services.pipewire = {
image = cfg.theme.wallpaper; enable = true;
base16Scheme = "${pkgs.base16-schemes}/share/themes/${cfg.theme.scheme}.yaml"; pulse.enable = true;
}; };
} environment.systemPackages = with pkgs; [
pavucontrol
easyeffects
];
})
# audio # bluetooth
(lib.mkIf cfg.audio.enable { (lib.mkIf cfg.bluetooth.enable {
services.pipewire = { hardware.bluetooth.enable = true;
enable = true; services.blueman.enable = true;
pulse.enable = true; })
};
environment.systemPackages = with pkgs; [
pavucontrol
easyeffects
];
})
# bluetooth # apps
(lib.mkIf cfg.bluetooth.enable { (lib.mkIf cfg.apps.enable {
hardware.bluetooth.enable = true; programs.thunderbird.enable = true;
services.blueman.enable = true;
})
# apps environment.systemPackages = with pkgs; [
(lib.mkIf cfg.apps.enable { ghostty
programs.thunderbird.enable = true; google-chrome
zathura
calibre
bolt-launcher
libnotify
bibata-cursors
discord
rocketchat-desktop
telegram-desktop
slack
jellyfin-media-player
cider-2
mpv
ffmpeg
wf-recorder
wl-mirror
protonmail-bridge
ledger-live-desktop
imv
yazi
nemo
file-roller
libreoffice-still
];
environment.systemPackages = with pkgs; [ # kindle udev rules for calibre
ghostty features.udev.kindle.enable = lib.mkDefault true;
google-chrome })
zathura
calibre
bolt-launcher
libnotify
bibata-cursors
vesktop
rocketchat-desktop
telegram-desktop
slack
jellyfin-media-player
cider-2
mpv
ffmpeg
wf-recorder
wl-mirror
protonmail-bridge
ledger-live-desktop
];
xdg.mime.defaultApplications = { # internal CA
"application/pdf" = "org.pwmt.zathura.desktop"; (lib.mkIf cfg.internalCA.enable {
}; security.pki.certificateFiles = [
inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system}.ca-matheo-si
# kindle udev rules for calibre ];
features.udev.kindle.enable = lib.mkDefault true; })
}) ]
);
# internal CA
(lib.mkIf cfg.internalCA.enable {
security.pki.certificateFiles = [
inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system}.ca-matheo-si
];
})
]);
}; };
home = home =
{ lib, inputs, osConfig, ... }: {
lib,
inputs,
osConfig,
...
}:
let let
cfg = osConfig.features.desktop; cfg = osConfig.features.desktop;
in in
{ {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable (
home.file.".assets".source = inputs.assets; lib.mkMerge [
}; {
home.file.".assets".source = inputs.assets;
}
(lib.mkIf cfg.apps.enable {
# TODO:(@janezicmatej) consider moving nvim desktop entry to neovim feature
xdg.desktopEntries.nvim = {
name = "Neovim";
exec = "ghostty -e nvim %F";
terminal = false;
mimeType = [
"text/plain"
"application/json"
"text/markdown"
];
};
xdg.mimeApps = {
enable = true;
defaultApplications = {
# text
"text/plain" = "nvim.desktop";
"application/json" = "nvim.desktop";
"text/markdown" = "nvim.desktop";
# web
"text/html" = "google-chrome.desktop";
"application/xhtml+xml" = "google-chrome.desktop";
"x-scheme-handler/http" = "google-chrome.desktop";
"x-scheme-handler/https" = "google-chrome.desktop";
"x-scheme-handler/ftp" = "google-chrome.desktop";
"x-scheme-handler/about" = "google-chrome.desktop";
"x-scheme-handler/unknown" = "google-chrome.desktop";
# mail and calendar
"x-scheme-handler/mailto" = "thunderbird.desktop";
"message/rfc822" = "thunderbird.desktop";
"text/calendar" = "thunderbird.desktop";
# documents
"application/pdf" = "org.pwmt.zathura.desktop";
"application/postscript" = "org.pwmt.zathura.desktop";
"image/vnd.djvu" = "org.pwmt.zathura.desktop";
"application/epub+zip" = "org.pwmt.zathura.desktop";
# office
"application/msword" = "libreoffice-writer.desktop";
"application/vnd.ms-excel" = "libreoffice-calc.desktop";
"application/vnd.ms-powerpoint" = "libreoffice-impress.desktop";
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" =
"libreoffice-writer.desktop";
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" = "libreoffice-calc.desktop";
"application/vnd.openxmlformats-officedocument.presentationml.presentation" =
"libreoffice-impress.desktop";
"application/vnd.oasis.opendocument.text" = "libreoffice-writer.desktop";
"application/vnd.oasis.opendocument.spreadsheet" = "libreoffice-calc.desktop";
"application/vnd.oasis.opendocument.presentation" = "libreoffice-impress.desktop";
"text/csv" = "libreoffice-calc.desktop";
# images
"image/png" = "imv-dir.desktop";
"image/jpeg" = "imv-dir.desktop";
"image/gif" = "imv-dir.desktop";
"image/webp" = "imv-dir.desktop";
"image/tiff" = "imv-dir.desktop";
"image/bmp" = "imv-dir.desktop";
"image/svg+xml" = "google-chrome.desktop";
# video
"video/mp4" = "mpv.desktop";
"video/x-matroska" = "mpv.desktop";
"video/webm" = "mpv.desktop";
"video/quicktime" = "mpv.desktop";
"video/x-msvideo" = "mpv.desktop";
# audio
"audio/mpeg" = "mpv.desktop";
"audio/flac" = "mpv.desktop";
"audio/ogg" = "mpv.desktop";
"audio/wav" = "mpv.desktop";
"audio/aac" = "mpv.desktop";
# archives
"application/zip" = "org.gnome.FileRoller.desktop";
"application/x-tar" = "org.gnome.FileRoller.desktop";
"application/gzip" = "org.gnome.FileRoller.desktop";
"application/x-rar-compressed" = "org.gnome.FileRoller.desktop";
"application/x-7z-compressed" = "org.gnome.FileRoller.desktop";
"application/x-bzip2" = "org.gnome.FileRoller.desktop";
"application/x-xz" = "org.gnome.FileRoller.desktop";
# file manager
"inode/directory" = "nemo.desktop";
# app deep links
"x-scheme-handler/tg" = "org.telegram.desktop.desktop";
"x-scheme-handler/discord" = "discord.desktop";
"x-scheme-handler/slack" = "slack.desktop";
};
};
})
]
);
}; };
} }

View File

@@ -6,7 +6,13 @@
}; };
home = home =
{ pkgs, lib, inputs, osConfig, ... }: {
pkgs,
lib,
inputs,
osConfig,
...
}:
let let
cfg = osConfig.features.dev; cfg = osConfig.features.dev;
packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system}; packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system};

View File

@@ -1,6 +1,11 @@
{ {
nixos = nixos =
{ config, lib, inputs, ... }: {
config,
lib,
inputs,
...
}:
let let
cfg = config.features.direnv; cfg = config.features.direnv;
in in

View File

@@ -1,6 +1,11 @@
{ {
nixos = nixos =
{ config, lib, user, ... }: {
config,
lib,
user,
...
}:
let let
cfg = config.features.docker; cfg = config.features.docker;
in in

View File

@@ -1,6 +1,11 @@
{ {
nixos = nixos =
{ config, lib, userKeys, ... }: {
config,
lib,
userKeys,
...
}:
let let
cfg = config.features.filedrop; cfg = config.features.filedrop;
in in

View File

@@ -1,6 +1,11 @@
{ {
nixos = nixos =
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.features.gaming; cfg = config.features.gaming;
in in

View File

@@ -6,7 +6,13 @@
}; };
home = home =
{ pkgs, lib, inputs, osConfig, ... }: {
pkgs,
lib,
inputs,
osConfig,
...
}:
let let
cfg = osConfig.features.git; cfg = osConfig.features.git;
packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system}; packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system};

View File

@@ -1,6 +1,11 @@
{ {
nixos = nixos =
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.features.gnupg; cfg = config.features.gnupg;
in in
@@ -14,23 +19,25 @@
}; };
}; };
config = lib.mkIf cfg.enable (lib.mkMerge [ config = lib.mkIf cfg.enable (
{ lib.mkMerge [
programs.gnupg.agent = { {
enable = true; programs.gnupg.agent = {
enableSSHSupport = true; enable = true;
enableExtraSocket = true; enableSSHSupport = true;
}; enableExtraSocket = true;
} };
}
(lib.mkIf cfg.yubikey.enable { (lib.mkIf cfg.yubikey.enable {
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
yubikey-personalization yubikey-personalization
yubikey-manager yubikey-manager
]; ];
services.pcscd.enable = true; services.pcscd.enable = true;
}) })
]); ]
);
}; };
} }

View File

@@ -57,9 +57,13 @@
"ip=${mkIpString cfg.ip}" "ip=${mkIpString cfg.ip}"
]; ];
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 = { boot.initrd.network = {
enable = true; enable = true;
udhcpc.enable = !cfg.ip.enable;
ssh = { ssh = {
enable = true; enable = true;
port = 22; port = 22;
@@ -69,10 +73,18 @@
]; ];
inherit (cfg) authorizedKeys; inherit (cfg) authorizedKeys;
}; };
postCommands = ''
echo 'cryptsetup-askpass' >> /root/.profile
'';
}; };
# systemd-networkd retries DHCP indefinitely, unlike udhcpc
boot.initrd.systemd.network.networks = lib.mkIf (!cfg.ip.enable) {
"10-initrd" = {
matchConfig.Driver = cfg.networkModule;
networkConfig.DHCP = "yes";
};
};
# forward LUKS password prompt to the SSH session
boot.initrd.systemd.users.root.shell = "/bin/systemd-tty-ask-password-agent";
}; };
}; };
} }

View File

@@ -26,54 +26,57 @@
cfg = osConfig.features.neovim; cfg = osConfig.features.neovim;
in in
{ {
config = lib.mkIf cfg.enable (lib.mkMerge [ config = lib.mkIf cfg.enable (
(lib.optionalAttrs (options ? stylix) { lib.mkMerge [
# disable stylix neovim target when stylix is present (lib.optionalAttrs (options ? stylix) {
stylix.targets.neovim.enable = false; # disable stylix neovim target when stylix is present
}) stylix.targets.neovim.enable = false;
{ })
xdg.configFile."nvim" = lib.mkIf (cfg.dotfiles != null) { {
source = cfg.dotfiles; xdg.configFile."nvim" = lib.mkIf (cfg.dotfiles != null) {
}; source = cfg.dotfiles;
};
programs.neovim = { programs.neovim = {
enable = true; enable = true;
vimAlias = true; sideloadInitLua = true;
defaultEditor = true; vimAlias = true;
package = inputs.neovim-nightly-overlay.packages.${pkgs.stdenv.hostPlatform.system}.default; defaultEditor = true;
package = inputs.neovim-nightly-overlay.packages.${pkgs.stdenv.hostPlatform.system}.default;
extraPackages = with pkgs; [ extraPackages = with pkgs; [
gcc gcc
luajit luajit
nodejs_22 nodejs_22
tree-sitter tree-sitter
gnumake gnumake
osc osc
fd fd
ripgrep ripgrep
bat bat
delta delta
pyright pyright
typescript-language-server typescript-language-server
lua-language-server lua-language-server
gopls gopls
nil nil
nixd nixd
nixpkgs-fmt nixpkgs-fmt
stylua stylua
]; ];
extraWrapperArgs = [ extraWrapperArgs = [
"--suffix" "--suffix"
"LD_LIBRARY_PATH" "LD_LIBRARY_PATH"
":" ":"
"${lib.makeLibraryPath [ pkgs.stdenv.cc.cc.lib ]}" "${lib.makeLibraryPath [ pkgs.stdenv.cc.cc.lib ]}"
]; ];
}; };
} }
]); ]
);
}; };
} }

View File

@@ -40,30 +40,30 @@
"flakes" "flakes"
]; ];
download-buffer-size = 2 * 1024 * 1024 * 1024; download-buffer-size = 2 * 1024 * 1024 * 1024;
download-attempts = 3;
fallback = true;
warn-dirty = false; warn-dirty = false;
substituters = substituters = [
[ "https://cache.nixos.org"
"https://cache.nixos.org" "https://nix-community.cachix.org?priority=45"
"https://nix-community.cachix.org?priority=45" ]
] ++ lib.optional cfg.towerCache.enable "http://tower:5000?priority=50";
++ lib.optional cfg.towerCache.enable "http://tower:5000?priority=50"; trusted-public-keys = [
trusted-public-keys = "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
[ "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ]
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" ++ lib.optional cfg.towerCache.enable "matej.nix-1:TdbemLVYblvAxqJcwb3mVKmmr3cfzXbMcZHE5ILnZDE=";
]
++ lib.optional cfg.towerCache.enable "matej.nix-1:TdbemLVYblvAxqJcwb3mVKmmr3cfzXbMcZHE5ILnZDE=";
}; };
gc = { gc = {
automatic = true; automatic = true;
dates = cfg.gc.dates; inherit (cfg.gc) dates;
options = "--delete-older-than ${cfg.gc.olderThan}"; options = "--delete-older-than ${cfg.gc.olderThan}";
}; };
optimise = { optimise = {
automatic = true; automatic = true;
dates = cfg.optimise.dates; inherit (cfg.optimise) dates;
}; };
}; };
}; };

View File

@@ -1,6 +1,11 @@
{ {
nixos = nixos =
{ config, lib, user, ... }: {
config,
lib,
user,
...
}:
let let
cfg = config.features.onepassword; cfg = config.features.onepassword;
in in

View File

@@ -1,6 +1,11 @@
{ {
nixos = nixos =
{ config, lib, user, ... }: {
config,
lib,
user,
...
}:
let let
cfg = config.features.remote-base; cfg = config.features.remote-base;
in in

View File

@@ -6,7 +6,12 @@
}; };
home = home =
{ pkgs, lib, osConfig, ... }: {
pkgs,
lib,
osConfig,
...
}:
let let
cfg = osConfig.features.shell; cfg = osConfig.features.shell;
in in

View File

@@ -1,6 +1,11 @@
{ {
nixos = nixos =
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.features.sway; cfg = config.features.sway;
desktopCfg = config.features.desktop; desktopCfg = config.features.desktop;
@@ -15,77 +20,81 @@
}; };
}; };
config = lib.mkIf cfg.enable (lib.mkMerge [ config = lib.mkIf cfg.enable (
{ lib.mkMerge [
# soft dependency {
features.desktop.enable = lib.mkDefault true; # soft dependency
features.desktop.enable = lib.mkDefault true;
# hard dependency # hard dependency
assertions = [ assertions = [
{ {
assertion = desktopCfg.enable; assertion = desktopCfg.enable;
message = "features.sway requires features.desktop"; message = "features.sway requires features.desktop";
} }
];
programs.sway = {
enable = true;
package = pkgs.swayfx;
wrapperFeatures.gtk = true;
extraSessionCommands = ''
# fix for java awt apps not rendering
export _JAVA_AWT_WM_NONREPARENTING=1
'';
};
environment.systemPackages = with pkgs; [
waybar
mako
wob
playerctl
brightnessctl
foot
grim
pulseaudio
swayidle
swaylock-effects
jq
slurp
wl-clipboard
pamixer
wlsunset
satty
wayland-pipewire-idle-inhibit
fuzzel
cliphist
zenity
];
}
# greeter
(lib.mkIf cfg.greeter.enable {
programs.regreet = {
enable = true;
cageArgs = [
"-s"
"-m"
"last"
]; ];
font = {
name = lib.mkForce "JetBrainsMono Nerd Font"; programs.sway = {
size = lib.mkForce 14; enable = true;
package = pkgs.swayfx;
wrapperFeatures.gtk = true;
extraSessionCommands = ''
# fix for java awt apps not rendering
export _JAVA_AWT_WM_NONREPARENTING=1
# propagate XDG_DATA_DIRS to dbus/systemd for d-bus activated apps
dbus-update-activation-environment --systemd XDG_DATA_DIRS
'';
}; };
settings = {
background = { environment.systemPackages = with pkgs; [
path = lib.mkForce (toString desktopCfg.theme.wallpaper); waybar
fit = lib.mkForce "Cover"; mako
wob
playerctl
brightnessctl
foot
grim
pulseaudio
swayidle
swaylock-effects
jq
slurp
wl-clipboard
pamixer
wlsunset
satty
wayland-pipewire-idle-inhibit
fuzzel
cliphist
zenity
];
}
# greeter
(lib.mkIf cfg.greeter.enable {
programs.regreet = {
enable = true;
cageArgs = [
"-s"
"-m"
"last"
];
font = {
name = lib.mkForce "JetBrainsMono Nerd Font";
size = lib.mkForce 14;
}; };
GTK = { settings = {
application_prefer_dark_theme = lib.mkForce true; background = {
path = lib.mkForce (toString desktopCfg.theme.wallpaper);
fit = lib.mkForce "Cover";
};
GTK = {
application_prefer_dark_theme = lib.mkForce true;
};
}; };
}; };
}; })
}) ]
]); );
}; };
} }

View File

@@ -1,6 +1,11 @@
{ {
nixos = nixos =
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.features.udev; cfg = config.features.udev;
in in
@@ -24,29 +29,31 @@
}; };
}; };
config = lib.mkIf cfg.enable (lib.mkMerge [ config = lib.mkIf cfg.enable (
(lib.mkIf cfg.kindle.enable { lib.mkMerge [
# NOTE:(@janezicmatej) uses services.udev.packages instead of extraRules (lib.mkIf cfg.kindle.enable {
# because extraRules writes to 99-local.rules which is too late for uaccess # NOTE:(@janezicmatej) uses services.udev.packages instead of extraRules
services.udev.packages = [ # because extraRules writes to 99-local.rules which is too late for uaccess
pkgs.libmtp services.udev.packages = [
(pkgs.writeTextFile { pkgs.libmtp
name = "kindle-udev-rules"; (pkgs.writeTextFile {
text = '' name = "kindle-udev-rules";
ACTION!="remove", SUBSYSTEM=="usb", ATTRS{idVendor}=="1949", TAG+="uaccess" text = ''
''; ACTION!="remove", SUBSYSTEM=="usb", ATTRS{idVendor}=="1949", TAG+="uaccess"
destination = "/etc/udev/rules.d/70-kindle.rules"; '';
}) destination = "/etc/udev/rules.d/70-kindle.rules";
]; })
}) ];
})
(lib.mkIf cfg.ledger.enable { (lib.mkIf cfg.ledger.enable {
hardware.ledger.enable = true; hardware.ledger.enable = true;
}) })
(lib.mkIf cfg.keyboard-zsa.enable { (lib.mkIf cfg.keyboard-zsa.enable {
hardware.keyboard.zsa.enable = true; hardware.keyboard.zsa.enable = true;
}) })
]); ]
);
}; };
} }

View File

@@ -9,23 +9,21 @@ in
sshAuthorizedKeys = sshKeys; sshAuthorizedKeys = sshKeys;
}; };
nixos = nixos = _: {
{ ... }: users.users.matej = {
{ uid = 1000;
users.users.matej = { isNormalUser = true;
uid = 1000; home = "/home/matej";
isNormalUser = true; extraGroups = [ "wheel" ];
home = "/home/matej"; openssh.authorizedKeys.keys = sshKeys;
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = sshKeys;
};
users.groups.matej = {
gid = 1000;
members = [ "matej" ];
};
}; };
users.groups.matej = {
gid = 1000;
members = [ "matej" ];
};
};
home = _: { home = _: {
home.stateVersion = "26.05"; home.stateVersion = "26.05";
}; };

View File

@@ -40,76 +40,73 @@
}; };
}; };
config = lib.mkIf cfg.enable (lib.mkMerge [ config = lib.mkIf cfg.enable (
{ lib.mkMerge [
services.qemuGuest.enable = true; {
services.spice-vdagentd.enable = lib.mkIf (!cfg.headless) true; services.spice-vdagentd.enable = lib.mkIf (!cfg.headless) true;
boot.kernelParams = lib.mkIf cfg.headless [ "console=ttyS0,115200" ]; boot.kernelParams = lib.mkIf cfg.headless [ "console=ttyS0,115200" ];
boot.initrd.availableKernelModules = [ # 9p autoloads on first mount
"9p" boot.initrd.availableKernelModules = [
"9pnet_virtio" "9p"
]; "9pnet_virtio"
boot.kernelModules = [
"9p"
"9pnet_virtio"
];
networking = {
useDHCP = true;
firewall.allowedTCPPorts = [ 22 ];
};
security.sudo.wheelNeedsPassword = false;
environment.systemPackages = with pkgs; [
curl
wget
htop
sshfs
];
}
(lib.mkIf cfg.automount.enable {
systemd.services.vm-9p-automount = {
description = "Auto-discover and mount 9p shares";
after = [
"local-fs.target"
"nss-user-lookup.target"
"systemd-modules-load.service"
]; ];
wants = [ "systemd-modules-load.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "vm-9p-automount" ''
BASE="${cfg.automount.basePath}"
PREFIX="${cfg.automount.prefix}"
mkdir -p "$BASE"
chown ${autoUser}:${autoGroup} "$BASE"
for tagfile in $(find /sys/devices -name mount_tag 2>/dev/null); do networking = {
[ -f "$tagfile" ] || continue useDHCP = true;
tag=$(tr -d '\0' < "$tagfile") firewall.allowedTCPPorts = [ 22 ];
case "$tag" in
"$PREFIX"*) ;;
*) continue ;;
esac
name="''${tag#"$PREFIX"}"
target="$BASE/$name"
mkdir -p "$target"
${pkgs.util-linux}/bin/mount -t 9p "$tag" "$target" \
-o trans=virtio,version=9p2000.L || continue
done
'';
}; };
};
}) security.sudo.wheelNeedsPassword = false;
]);
environment.systemPackages = with pkgs; [
curl
wget
htop
];
}
(lib.mkIf cfg.automount.enable {
systemd.services.vm-9p-automount = {
description = "Auto-discover and mount 9p shares";
after = [
"local-fs.target"
"nss-user-lookup.target"
"systemd-modules-load.service"
];
wants = [ "systemd-modules-load.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "vm-9p-automount" ''
BASE="${cfg.automount.basePath}"
PREFIX="${cfg.automount.prefix}"
mkdir -p "$BASE"
chown ${autoUser}:${autoGroup} "$BASE"
for tagfile in $(find /sys/devices -name mount_tag 2>/dev/null); do
[ -f "$tagfile" ] || continue
tag=$(tr -d '\0' < "$tagfile")
case "$tag" in
"$PREFIX"*) ;;
*) continue ;;
esac
name="''${tag#"$PREFIX"}"
target="$BASE/$name"
mkdir -p "$target"
${pkgs.util-linux}/bin/mount -t 9p "$tag" "$target" \
-o trans=virtio,version=9p2000.L || continue
done
'';
};
};
})
]
);
}; };
} }

View File

@@ -1,6 +1,12 @@
{ {
nixos = nixos =
{ config, lib, pkgs, user, ... }: {
config,
lib,
pkgs,
user,
...
}:
let let
cfg = config.features.zsh; cfg = config.features.zsh;
in in
@@ -15,22 +21,29 @@
}; };
}; };
config = lib.mkIf cfg.enable (lib.mkMerge [ config = lib.mkIf cfg.enable (
{ lib.mkMerge [
programs.zsh.enable = true; {
environment.etc."zshenv".text = '' programs.zsh.enable = true;
export ZDOTDIR=$HOME/.config/zsh environment.etc."zshenv".text = ''
''; export ZDOTDIR=$HOME/.config/zsh
} '';
}
(lib.mkIf cfg.loginShell.enable { (lib.mkIf cfg.loginShell.enable {
users.users.${user}.shell = pkgs.zsh; users.users.${user}.shell = pkgs.zsh;
}) })
]); ]
);
}; };
home = home =
{ pkgs, lib, osConfig, ... }: {
pkgs,
lib,
osConfig,
...
}:
let let
cfg = osConfig.features.zsh; cfg = osConfig.features.zsh;
in in

60
flake.lock generated
View File

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

View File

@@ -1,19 +1,5 @@
{ inputs, ... }: _:
{ {
flake.overlays.default = flake.overlays.default = _: _: { };
_: prev:
let
pkgs-stable = import inputs.nixpkgs-stable {
inherit (prev.stdenv.hostPlatform) system;
inherit (prev) config;
};
pkgs-master = import inputs.nixpkgs-master {
inherit (prev.stdenv.hostPlatform) system;
inherit (prev) config;
};
in
{
inherit (pkgs-master) claude-code;
};
} }

View File

@@ -13,19 +13,38 @@
documentation.enable = false; documentation.enable = false;
environment.defaultPackages = [ ]; 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 = image.modules.qemu =
{ config, modulesPath, ... }: { config, modulesPath, ... }:
{ {
system.build.image = lib.mkForce ( system.build.image = lib.mkForce (
import (modulesPath + "/../lib/make-disk-image.nix") { let
inherit lib config pkgs; rawImage = import (modulesPath + "/../lib/make-disk-image.nix") {
inherit (config.virtualisation) diskSize; inherit lib config pkgs;
inherit (config.virtualisation) diskSize;
inherit (config.image) baseName;
format = "qcow2";
copyChannel = false;
partitionTableType = "legacy";
};
inherit (config.image) baseName; inherit (config.image) baseName;
format = "qcow2-compressed"; in
copyChannel = false; pkgs.runCommand baseName { nativeBuildInputs = [ pkgs.qemu-utils ]; } ''
partitionTableType = "legacy"; 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,14 +89,16 @@
features.neovim.dotfiles = inputs.nvim; features.neovim.dotfiles = inputs.nvim;
# ensure .config exists with correct ownership before automount # 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 # TODO:(@janezicmatej) replace ssh with virtio-console (hvc0) when qemu 11.0 lands
# https://www.mail-archive.com/qemu-devel@nongnu.org/msg1162844.html # https://www.mail-archive.com/qemu-devel@nongnu.org/msg1162844.html
# accept any ssh key (ephemeral localhost-only vm) # accept any ssh key (ephemeral localhost-only vm)
services.openssh.settings.AuthorizedKeysCommand = let services.openssh.settings.AuthorizedKeysCommand =
acceptKey = pkgs.writeShellScript "ephvm-accept-key" ''echo "$1 $2"''; let
in "${acceptKey} %t %k"; acceptKey = pkgs.writeShellScript "ephvm-accept-key" ''echo "$1 $2"'';
in
"${acceptKey} %t %k";
services.openssh.settings.AuthorizedKeysCommandUser = "nobody"; services.openssh.settings.AuthorizedKeysCommandUser = "nobody";
# writable claude config via 9p # writable claude config via 9p

View File

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

View File

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

View File

@@ -79,30 +79,29 @@ let
in in
nixpkgs.lib.nixosSystem { nixpkgs.lib.nixosSystem {
inherit system; inherit system;
modules = modules = [
[ inputs.sops-nix.nixosModules.sops
inputs.sops-nix.nixosModules.sops inputs.stylix.nixosModules.stylix
inputs.stylix.nixosModules.stylix
{ nixpkgs.overlays = overlays; } { nixpkgs.overlays = overlays; }
{ nixpkgs.config.allowUnfree = true; } { nixpkgs.config.allowUnfree = true; }
{ networking.hostName = name; } { networking.hostName = name; }
featureEnableModule featureEnableModule
hostConfig hostConfig
] ]
++ lib.optional (builtins.pathExists hostHWConfig) hostHWConfig ++ lib.optional (builtins.pathExists hostHWConfig) hostHWConfig
++ nixosMods ++ nixosMods
++ lib.optionals hasUser [ ++ lib.optionals hasUser [
inputs.home-manager.nixosModules.home-manager inputs.home-manager.nixosModules.home-manager
{ {
home-manager.useGlobalPkgs = true; home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true; home-manager.useUserPackages = true;
home-manager.backupFileExtension = "backup"; home-manager.backupFileExtension = "backup";
home-manager.users.${user}.imports = homeMods; home-manager.users.${user}.imports = homeMods;
home-manager.extraSpecialArgs = { inherit inputs; }; home-manager.extraSpecialArgs = { inherit inputs; };
} }
]; ];
specialArgs = { specialArgs = {
inherit inputs userKeys user; inherit inputs userKeys user;
}; };

View File

@@ -0,0 +1,91 @@
{ pkgs, ... }:
let
inherit (pkgs) stdenv lib;
version = "2.1.116";
# 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=";
};
};
source =
sources.${stdenv.hostPlatform.system}
or (throw "claude-code: unsupported system ${stdenv.hostPlatform.system}");
in
stdenv.mkDerivation {
pname = "claude-code";
inherit version;
src = pkgs.fetchzip {
url = "https://registry.npmjs.org/@anthropic-ai/claude-code-${source.slug}/-/claude-code-${source.slug}-${version}.tgz";
inherit (source) hash;
};
nativeBuildInputs = [
pkgs.makeWrapper
]
++ lib.optionals stdenv.hostPlatform.isLinux [ pkgs.patchelf ];
dontBuild = true;
dontConfigure = true;
dontStrip = true;
installPhase = ''
runHook preInstall
install -Dm755 claude $out/bin/claude
runHook 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 : ${
lib.makeBinPath (
[
pkgs.procps
]
++ lib.optionals stdenv.hostPlatform.isLinux [
pkgs.bubblewrap
pkgs.socat
]
)
}
'';
meta = {
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 = lib.licenses.unfree;
mainProgram = "claude";
platforms = lib.attrNames sources;
};
}

53
packages/claude-code/update.sh Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p curl jq nix
# shellcheck shell=bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PKG_FILE="$SCRIPT_DIR/package.nix"
# keep in sync with the `sources` attrset in package.nix
PLATFORMS=(linux-x64 linux-arm64 darwin-x64 darwin-arm64)
prefetch() {
local url="$1"
nix --extra-experimental-features 'nix-command flakes' \
store prefetch-file --unpack --json "$url" 2>/dev/null | jq -r '.hash'
}
main() {
echo "fetching latest version from npm..."
local latest current
latest=$(curl -sf "https://registry.npmjs.org/@anthropic-ai/claude-code/latest" | jq -r '.version')
current=$(grep 'version = ' "$PKG_FILE" | head -1 | sed 's/.*"\(.*\)".*/\1/')
if [[ "$current" == "$latest" ]]; then
echo "claude-code already at $latest"
return 0
fi
echo "updating claude-code: $current -> $latest"
sed -i "s|version = \"$current\"|version = \"$latest\"|" "$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"
}
main "$@"

View File

@@ -27,14 +27,32 @@ info() {
# globals for cleanup trap # globals for cleanup trap
CLEANUP_OVERLAY="" CLEANUP_OVERLAY=""
CLEANUP_TMPDIR=""
QEMU_PID="" QEMU_PID=""
VM_READY=false
cleanup() { cleanup() {
[ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null && wait "$QEMU_PID" 2>/dev/null [ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null && wait "$QEMU_PID" 2>/dev/null
[ -n "$CLEANUP_OVERLAY" ] && rm -rf "$CLEANUP_OVERLAY" [ -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 return 0
} }
trap cleanup EXIT 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() { usage() {
cat <<EOF cat <<EOF
Usage: ephvm-run.sh [options] Usage: ephvm-run.sh [options]
@@ -55,6 +73,8 @@ EOF
main() { main() {
setup_colors 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 ssh_port="" memory=4G cpus=2 claude=true disk_size="" serial=false
local -a mounts=() local -a mounts=()
@@ -110,15 +130,13 @@ main() {
CLEANUP_OVERLAY=$(mktemp -d) CLEANUP_OVERLAY=$(mktemp -d)
local overlay="$CLEANUP_OVERLAY/overlay.qcow2" local overlay="$CLEANUP_OVERLAY/overlay.qcow2"
qemu-img create -f qcow2 -b "$(realpath "$image")" -F qcow2 "$overlay" "$disk_size" 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 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 fi
command -v qemu-system-x86_64 &>/dev/null || die "qemu-system-x86_64 not found" command -v qemu-system-x86_64 &>/dev/null || die "qemu-system-x86_64 not found"
[ -r /dev/kvm ] || die "/dev/kvm not readable; kvm is required"
local accel="tcg"
[ -r /dev/kvm ] && accel="kvm"
# auto-allocate ssh port unless serial mode # auto-allocate ssh port unless serial mode
if [ "$serial" = false ] && [ -z "$ssh_port" ]; then if [ "$serial" = false ] && [ -z "$ssh_port" ]; then
@@ -128,28 +146,33 @@ main() {
done done
fi fi
local nic_arg="user" local nic_arg="user,model=virtio-net-pci"
if [ -n "$ssh_port" ]; then 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 fi
local -a qemu_args=( local -a qemu_args=(
qemu-system-x86_64 qemu-system-x86_64
-accel "$accel" -accel kvm
-cpu host
-m "$memory" -m "$memory"
-smp "$cpus" -smp "$cpus"
-drive "$drive_arg" -drive "$drive_arg"
-device "virtio-blk-pci,drive=hd0"
-device virtio-rng-pci
-nic "$nic_arg" -nic "$nic_arg"
-nographic -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 local fs_id=0 mount_path name tag
for mount_path in "${mounts[@]}"; do for mount_path in "${mounts[@]}"; do
[ -e "$mount_path" ] || die "--mount path does not exist: $mount_path"
mount_path=$(realpath "$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") name=$(basename "$mount_path")
tag="m_${name:0:29}" tag="m_${name:0:29}"
qemu_args+=( qemu_args+=(
@@ -163,6 +186,9 @@ main() {
mkdir -p "$CLAUDE_CONFIG_DIR" mkdir -p "$CLAUDE_CONFIG_DIR"
local claude_dir local claude_dir
claude_dir=$(realpath "$CLAUDE_CONFIG_DIR") claude_dir=$(realpath "$CLAUDE_CONFIG_DIR")
case "$claude_dir" in
*,*) die "claude config dir may not contain commas: $claude_dir" ;;
esac
qemu_args+=( qemu_args+=(
-virtfs "local,path=$claude_dir,mount_tag=claude,security_model=none,id=fs${fs_id}" -virtfs "local,path=$claude_dir,mount_tag=claude,security_model=none,id=fs${fs_id}"
@@ -171,27 +197,38 @@ main() {
fi fi
info "---" info "---"
info "Accel: $accel" [ -n "$ssh_port" ] && info "SSH: ssh -p $ssh_port matej@localhost"
info "---" info "---"
if [ "$serial" = true ]; then if [ "$serial" = true ]; then
exec "${qemu_args[@]}" exec "${qemu_args[@]}"
fi fi
CLEANUP_TMPDIR=$(mktemp -d)
local qemu_log="$CLEANUP_TMPDIR/qemu.log"
# start qemu in background and auto-ssh # start qemu in background and auto-ssh
"${qemu_args[@]}" &>/dev/null & "${qemu_args[@]}" &>"$qemu_log" &
QEMU_PID=$! 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)..." info "waiting for vm (port $ssh_port)..."
local attempts=0 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=$((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" kill -0 "$QEMU_PID" 2>/dev/null || die "qemu exited unexpectedly"
sleep 1 sleep 0.5
done done
VM_READY=true
ssh -p "$ssh_port" -t \ ssh -p "$ssh_port" -t \
-i "$ssh_key" \
-o StrictHostKeyChecking=no \ -o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \ -o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \ -o LogLevel=ERROR \