Compare commits

...

46 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
df2ee459a1 feat: improve cache-builder service trigger 2026-04-13 11:53:41 +02:00
f7d86e7718 feat: improve ephvm ux 2026-04-13 11:48:18 +02:00
2e5eb92e32 chore: bump lockfile 2026-04-13 00:22:51 +02:00
f30b03cc04 fix: enable dhcp in initrd if no static ip 2026-04-13 00:20:36 +02:00
c7fb218511 merge: feature improvements 2026-04-12 23:33:33 +02:00
3caace87d3 refactor: update hosts and feature lists for new architecture 2026-04-12 23:33:14 +02:00
8793f97a04 feat: add udev, onepassword, bootloader, power features 2026-04-12 23:33:07 +02:00
b8509196d5 feat: merge greeter into sway, yubikey into gnupg, automount into vm-guest 2026-04-12 23:33:01 +02:00
e1d136bd2f feat: merge desktop + desktop-minimal + calibre with sub-options 2026-04-12 23:32:55 +02:00
898751576d feat: add zsh + git features, redesign shell 2026-04-12 23:32:47 +02:00
6770bc76a2 refactor: mkHost load-all infrastructure and convert simple features 2026-04-12 23:32:28 +02:00
3ff4583017 merge: fortress host 2026-04-10 22:51:26 +02:00
f85980190e feat: prepare initial fortress host 2026-04-10 16:40:55 +02:00
8e5557921d feat: prepare minimal features for fortress 2026-04-10 16:05:34 +02:00
216328927d feat: improve dev-components with --extra 2026-04-09 17:05:33 +02:00
86e8fe7397 feat: filedrop via sftp 2026-04-09 13:44:28 +02:00
0fa91d4f40 feat: migrate home-manager to 26.05 2026-04-07 13:54:50 +02:00
07334db7ee chore: bump lockfile 2026-04-07 13:54:44 +02:00
a08f824d0e feat: rename steam to gaming and add prismlauncher 2026-04-07 13:28:51 +02:00
dd4fc4eff3 feat: switch to unstable channel 2026-04-05 14:43:26 +02:00
89e36c5096 chore: bump lockfile 2026-04-02 12:56:34 +02:00
55 changed files with 2012 additions and 808 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

73
features/bootloader.nix Normal file
View File

@@ -0,0 +1,73 @@
{
nixos =
{
config,
lib,
inputs,
...
}:
let
cfg = config.features.bootloader;
in
{
imports = [ inputs.lanzaboote.nixosModules.lanzaboote ];
options.features.bootloader = {
enable = lib.mkEnableOption "bootloader";
mode = lib.mkOption {
type = lib.types.enum [
"systemd-boot"
"lanzaboote"
];
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") {
boot.loader.systemd-boot.enable = true;
})
(lib.mkIf (cfg.mode == "lanzaboote") {
boot.loader.systemd-boot.enable = lib.mkForce false;
boot.lanzaboote = {
enable = true;
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

@@ -1,22 +0,0 @@
{
nixos =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.calibre ];
# udev rules for kindle and mtp device access
# NOTE:(@janezicmatej) uses services.udev.packages instead of extraRules
# because extraRules writes to 99-local.rules which is too late for uaccess
# see https://github.com/NixOS/nixpkgs/issues/308681
services.udev.packages = [
pkgs.libmtp
(pkgs.writeTextFile {
name = "kindle-udev-rules";
text = ''
ACTION!="remove", SUBSYSTEM=="usb", ATTRS{idVendor}=="1949", TAG+="uaccess"
'';
destination = "/etc/udev/rules.d/70-kindle.rules";
})
];
};
}

View File

@@ -1,10 +1,28 @@
{ {
home = nixos =
{ pkgs, ... }: { lib, ... }:
{ {
options.features.claude.enable = lib.mkEnableOption "claude";
};
home =
{
pkgs,
lib,
inputs,
osConfig,
...
}:
let
cfg = osConfig.features.claude;
packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system};
in
{
config = lib.mkIf cfg.enable {
home.packages = [ home.packages = [
pkgs.claude-code packages.claude-code
pkgs.mcp-nixos pkgs.mcp-nixos
]; ];
}; };
};
} }

View File

@@ -1,59 +1,126 @@
{ {
nixos = nixos =
{ pkgs, inputs, ... }:
{ {
imports = [ inputs.stylix.nixosModules.stylix ]; config,
lib,
pkgs,
inputs,
...
}:
let
cfg = config.features.desktop;
in
{
options.features.desktop = {
enable = lib.mkEnableOption "desktop environment";
# audio audio.enable = lib.mkOption {
services.pipewire = { type = lib.types.bool;
enable = true; default = true;
pulse.enable = true;
}; };
# bluetooth bluetooth.enable = lib.mkOption {
hardware.bluetooth.enable = true; type = lib.types.bool;
services.blueman.enable = true; default = false;
};
apps.enable = lib.mkOption {
type = lib.types.bool;
default = true;
};
theme = {
wallpaper = lib.mkOption {
type = lib.types.path;
default = "${inputs.assets}/wallpaper.png";
};
scheme = lib.mkOption {
type = lib.types.str;
default = "gruvbox-material-dark-medium";
};
polarity = lib.mkOption {
type = lib.types.enum [
"dark"
"light"
];
default = "dark";
};
};
internalCA.enable = lib.mkOption {
type = lib.types.bool;
default = true;
};
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
# base desktop
{
security.polkit.enable = true; security.polkit.enable = true;
services.dbus.enable = true; services.dbus.enable = true;
services.playerctld.enable = true; services.playerctld.enable = true;
xdg.portal = { xdg.portal = {
enable = true; enable = true;
xdgOpenUsePortal = true; extraPortals = with pkgs; [
extraPortals = [ xdg-desktop-portal-wlr
pkgs.xdg-desktop-portal-wlr xdg-desktop-portal-gtk
pkgs.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; [ fonts.packages = with pkgs; [
font-awesome font-awesome
nerd-fonts.jetbrains-mono nerd-fonts.jetbrains-mono
]; ];
# theming
stylix = { stylix = {
enable = true; enable = true;
polarity = "dark"; inherit (cfg.theme) polarity;
image = "${inputs.assets}/wallpaper.png"; image = cfg.theme.wallpaper;
base16Scheme = "${pkgs.base16-schemes}/share/themes/gruvbox-material-dark-medium.yaml"; base16Scheme = "${pkgs.base16-schemes}/share/themes/${cfg.theme.scheme}.yaml";
}; };
}
# audio
(lib.mkIf cfg.audio.enable {
services.pipewire = {
enable = true;
pulse.enable = true;
};
environment.systemPackages = with pkgs; [
pavucontrol
easyeffects
];
})
# bluetooth
(lib.mkIf cfg.bluetooth.enable {
hardware.bluetooth.enable = true;
services.blueman.enable = true;
})
# apps
(lib.mkIf cfg.apps.enable {
programs.thunderbird.enable = true; programs.thunderbird.enable = true;
programs._1password.enable = true;
programs._1password-gui.enable = true;
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
easyeffects
ghostty ghostty
google-chrome google-chrome
zathura zathura
pavucontrol calibre
bolt-launcher bolt-launcher
libnotify libnotify
bibata-cursors bibata-cursors
vesktop discord
rocketchat-desktop rocketchat-desktop
telegram-desktop telegram-desktop
slack slack
@@ -65,21 +132,142 @@
wl-mirror wl-mirror
protonmail-bridge protonmail-bridge
ledger-live-desktop ledger-live-desktop
imv
yazi
nemo
file-roller
libreoffice-still
]; ];
# kindle udev rules for calibre
features.udev.kindle.enable = lib.mkDefault true;
})
# internal CA # internal CA
(lib.mkIf cfg.internalCA.enable {
security.pki.certificateFiles = [ security.pki.certificateFiles = [
inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system}.ca-matheo-si inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system}.ca-matheo-si
]; ];
})
xdg.mime.defaultApplications = { ]
"application/pdf" = "org.pwmt.zathura.desktop"; );
};
}; };
home = home =
{ inputs, ... }: {
lib,
inputs,
osConfig,
...
}:
let
cfg = osConfig.features.desktop;
in
{
config = lib.mkIf cfg.enable (
lib.mkMerge [
{ {
home.file.".assets".source = inputs.assets; 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

@@ -1,25 +1,36 @@
{ {
nixos =
{ lib, ... }:
{
options.features.dev.enable = lib.mkEnableOption "development tools";
};
home = home =
{ pkgs, inputs, ... }: {
pkgs,
lib,
inputs,
osConfig,
...
}:
let let
cfg = osConfig.features.dev;
packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system}; packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system};
in in
{ {
config = lib.mkIf cfg.enable {
home.packages = [ home.packages = [
pkgs.git
packages.git-linearize
packages.ggman
pkgs.python3 pkgs.python3
pkgs.mdbook pkgs.osc
pkgs.marksman
pkgs.mdformat
pkgs.google-cloud-sdk pkgs.google-cloud-sdk
pkgs.google-cloud-sql-proxy pkgs.google-cloud-sql-proxy
packages.ahab packages.ahab
pkgs.just pkgs.just
pkgs.presenterm pkgs.presenterm
pkgs.osc pkgs.qemu
]; ];
};
}; };
} }

View File

@@ -1,7 +1,18 @@
{ {
nixos = nixos =
{ inputs, ... }:
{ {
config,
lib,
inputs,
...
}:
let
cfg = config.features.direnv;
in
{
options.features.direnv.enable = lib.mkEnableOption "direnv";
config = lib.mkIf cfg.enable {
nix.registry.dev = { nix.registry.dev = {
from = { from = {
type = "indirect"; type = "indirect";
@@ -13,8 +24,15 @@
}; };
}; };
}; };
};
home = _: { home =
{ lib, osConfig, ... }:
let
cfg = osConfig.features.direnv;
in
{
config = lib.mkIf cfg.enable {
programs.direnv = { programs.direnv = {
enable = true; enable = true;
nix-direnv.enable = true; nix-direnv.enable = true;
@@ -23,4 +41,5 @@
xdg.configFile."direnv/lib/use_dev.sh".source = ./use_dev.sh; xdg.configFile."direnv/lib/use_dev.sh".source = ./use_dev.sh;
}; };
};
} }

View File

@@ -1,16 +1,28 @@
# shellcheck shell=bash # shellcheck shell=bash
# composable nix devshell from matej.nix # composable nix devshell from matej.nix
# usage in .envrc: use dev uv_14 pg_18 # usage in .envrc: use dev uv_14 pg_18 --extra cairo pkg-config
# generates a flake and delegates to use_flake at the calling scope # generates a flake and delegates to use_flake at the calling scope
use_dev() { use_dev() {
local nix_list="" local nix_list="" extra_list="" in_extra=0
for c in "$@"; do for c in "$@"; do
if [[ "$c" == "--extra" ]]; then
in_extra=1
continue
fi
if [[ $in_extra -eq 1 ]]; then
if [[ ! "$c" =~ ^[a-zA-Z_][a-zA-Z0-9_-]*$ ]]; then
log_error "use_dev: invalid package name: $c"
return 1
fi
extra_list="$extra_list pkgs.$c"
else
if [[ ! "$c" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then if [[ ! "$c" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
log_error "use_dev: invalid component name: $c" log_error "use_dev: invalid component name: $c"
return 1 return 1
fi fi
nix_list="$nix_list \"$c\"" nix_list="$nix_list \"$c\""
fi
done done
local system local system
@@ -64,7 +76,7 @@ use_dev() {
pkgs = nixpkgs.legacyPackages.\${system}; pkgs = nixpkgs.legacyPackages.\${system};
devLib = import "\${dev}/flake/dev-components.nix" { inherit pkgs; lib = nixpkgs.lib; }; devLib = import "\${dev}/flake/dev-components.nix" { inherit pkgs; lib = nixpkgs.lib; };
in { in {
devShells.\${system}.default = devLib.mkComponentShell [$nix_list ]; devShells.\${system}.default = devLib.mkComponentShell [$nix_list ] [${extra_list} ];
}; };
} }
DEVFLAKE DEVFLAKE

View File

@@ -1,7 +1,18 @@
{ {
nixos = nixos =
{ user, ... }:
{ {
config,
lib,
user,
...
}:
let
cfg = config.features.docker;
in
{
options.features.docker.enable = lib.mkEnableOption "docker";
config = lib.mkIf cfg.enable {
virtualisation.docker = { virtualisation.docker = {
enable = true; enable = true;
logDriver = "json-file"; logDriver = "json-file";
@@ -9,4 +20,5 @@
users.users.${user}.extraGroups = [ "docker" ]; users.users.${user}.extraGroups = [ "docker" ];
}; };
};
} }

61
features/filedrop.nix Normal file
View File

@@ -0,0 +1,61 @@
{
nixos =
{
config,
lib,
userKeys,
...
}:
let
cfg = config.features.filedrop;
in
{
options.features.filedrop = {
enable = lib.mkEnableOption "filedrop sftp service";
sopsFile = lib.mkOption {
type = lib.types.path;
};
};
config = lib.mkIf cfg.enable {
sops.secrets.filedrop-authorized-keys = {
inherit (cfg) sopsFile;
mode = "0444";
};
users.groups.filedrop = {
members = [ "matej" ];
};
users.users.filedrop = {
isSystemUser = true;
group = "filedrop";
home = "/home/filedrop";
shell = "/run/current-system/sw/bin/nologin";
openssh.authorizedKeys.keys = userKeys.sshAuthorizedKeys;
};
# chroot dir must be root-owned; incoming is writable by filedrop
systemd.tmpfiles.rules = [
"d /home/filedrop 0755 root root -"
"d /home/filedrop/incoming 2775 filedrop filedrop -"
"a+ /home/filedrop/incoming - - - - group:filedrop:rwx"
"a+ /home/filedrop/incoming - - - - default:group:filedrop:rwx"
"a+ /home/filedrop/incoming - - - - default:mask::rwx"
"L /home/matej/filedrop - - - - /home/filedrop/incoming"
];
# relaxed umask so default acl takes full effect
services.openssh.extraConfig = ''
Match User filedrop
ForceCommand internal-sftp -u 0002
ChrootDirectory /home/filedrop
AuthorizedKeysFile /etc/ssh/authorized_keys.d/filedrop %h/.ssh/authorized_keys ${config.sops.secrets.filedrop-authorized-keys.path}
AllowTcpForwarding no
X11Forwarding no
'';
};
};
}

26
features/gaming.nix Normal file
View File

@@ -0,0 +1,26 @@
{
nixos =
{
config,
lib,
pkgs,
...
}:
let
cfg = config.features.gaming;
in
{
options.features.gaming.enable = lib.mkEnableOption "gaming";
config = lib.mkIf cfg.enable {
programs.steam = {
enable = true;
remotePlay.openFirewall = true;
dedicatedServer.openFirewall = true;
localNetworkGameTransfers.openFirewall = true;
};
environment.systemPackages = [ pkgs.prismlauncher ];
};
};
}

29
features/git.nix Normal file
View File

@@ -0,0 +1,29 @@
{
nixos =
{ lib, ... }:
{
options.features.git.enable = lib.mkEnableOption "git";
};
home =
{
pkgs,
lib,
inputs,
osConfig,
...
}:
let
cfg = osConfig.features.git;
packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system};
in
{
config = lib.mkIf cfg.enable {
home.packages = [
pkgs.git
packages.git-linearize
packages.ggman
];
};
};
}

View File

@@ -1,9 +1,43 @@
{ {
nixos = _: { nixos =
{
config,
lib,
pkgs,
...
}:
let
cfg = config.features.gnupg;
in
{
options.features.gnupg = {
enable = lib.mkEnableOption "gnupg";
yubikey.enable = lib.mkOption {
type = lib.types.bool;
default = false;
};
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
programs.gnupg.agent = { programs.gnupg.agent = {
enable = true; enable = true;
enableSSHSupport = true; enableSSHSupport = true;
enableExtraSocket = true; enableExtraSocket = true;
}; };
}
(lib.mkIf cfg.yubikey.enable {
environment.systemPackages = with pkgs; [
yubikey-personalization
yubikey-manager
];
services.pcscd.enable = true;
})
]
);
}; };
} }

View File

@@ -1,28 +0,0 @@
{
nixos =
{ lib, inputs, ... }:
{
programs.regreet = {
enable = true;
# single output to avoid stretching across monitors
cageArgs = [
"-s"
"-m"
"last"
];
font = {
name = lib.mkForce "JetBrainsMono Nerd Font";
size = lib.mkForce 14;
};
settings = {
background = {
path = lib.mkForce "${inputs.assets}/wallpaper.png";
fit = lib.mkForce "Cover";
};
GTK = {
application_prefer_dark_theme = lib.mkForce true;
};
};
};
};
}

View File

@@ -1,12 +1,14 @@
{ {
nixos = nixos =
{ {
pkgs,
config, config,
lib,
pkgs,
inputs, inputs,
... ...
}: }:
let let
cfg = config.features.harmonia;
hosts = [ hosts = [
"fw16" "fw16"
"tower" "tower"
@@ -17,7 +19,10 @@
flakeRef = inputs.self.outPath; flakeRef = inputs.self.outPath;
in in
{ {
services.harmonia = { options.features.harmonia.enable = lib.mkEnableOption "harmonia";
config = lib.mkIf cfg.enable {
services.harmonia.cache = {
enable = true; enable = true;
signKeyPaths = [ config.sops.secrets.nix-signing-key.path ]; signKeyPaths = [ config.sops.secrets.nix-signing-key.path ];
}; };
@@ -38,14 +43,10 @@
path = [ config.nix.package ]; path = [ config.nix.package ];
}; };
systemd.timers.cache-builder = { # restart cache-builder after every nixos switch (non-blocking)
description = "Periodically build all host closures"; system.activationScripts.cache-builder = lib.stringAfter [ "specialfs" ] ''
wantedBy = [ "timers.target" ]; ${config.systemd.package}/bin/systemctl restart --no-block cache-builder.service || true
timerConfig = { '';
OnUnitActiveSec = "15min";
OnBootSec = "5min";
Persistent = true;
};
}; };
}; };
} }

View File

@@ -2,6 +2,7 @@
nixos = nixos =
{ lib, config, ... }: { lib, config, ... }:
let let
cfg = config.features.initrd-ssh;
keyDir = "/etc/secrets/initrd"; keyDir = "/etc/secrets/initrd";
mkIpString = mkIpString =
@@ -15,8 +16,9 @@
"${address}::${gateway}:${netmask}::${interface}:none"; "${address}::${gateway}:${netmask}::${interface}:none";
in in
{ {
options = { options.features.initrd-ssh = {
initrd-ssh = { enable = lib.mkEnableOption "initrd ssh";
ip = { ip = {
enable = lib.mkEnableOption "static IP for initrd (otherwise DHCP)"; enable = lib.mkEnableOption "static IP for initrd (otherwise DHCP)";
@@ -47,14 +49,19 @@
type = lib.types.str; type = lib.types.str;
}; };
}; };
};
config = { config = lib.mkIf cfg.enable {
boot.initrd.kernelModules = [ config.initrd-ssh.networkModule ]; boot.initrd.availableKernelModules = [ cfg.networkModule ];
boot.kernelParams = lib.mkIf config.initrd-ssh.ip.enable [ boot.initrd.kernelModules = [ cfg.networkModule ];
"ip=${mkIpString config.initrd-ssh.ip}" boot.kernelParams = lib.mkIf cfg.ip.enable [
"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;
ssh = { ssh = {
@@ -64,12 +71,20 @@
"${keyDir}/ssh_host_rsa_key" "${keyDir}/ssh_host_rsa_key"
"${keyDir}/ssh_host_ed25519_key" "${keyDir}/ssh_host_ed25519_key"
]; ];
inherit (config.initrd-ssh) 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

@@ -1,25 +1,30 @@
{ {
nixos = nixos =
{ lib, config, ... }: { lib, config, ... }:
let
cfg = config.features.localisation;
in
{ {
options = { options.features.localisation = {
localisation = { enable = lib.mkEnableOption "localisation";
timeZone = lib.mkOption { timeZone = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "Europe/Ljubljana";
}; };
defaultLocale = lib.mkOption { defaultLocale = lib.mkOption {
type = lib.types.str; type = lib.types.str;
}; default = "en_US.UTF-8";
}; };
}; };
config = { config = lib.mkIf cfg.enable {
time.timeZone = config.localisation.timeZone; time.timeZone = cfg.timeZone;
i18n.defaultLocale = config.localisation.defaultLocale; i18n.defaultLocale = cfg.defaultLocale;
# NOTE:(@janezicmatej) some apps (e.g. java) need TZ env var explicitly # NOTE:(@janezicmatej) some apps (e.g. java) need TZ env var explicitly
environment.variables.TZ = config.localisation.timeZone; environment.variables.TZ = cfg.timeZone;
}; };
}; };
} }

View File

@@ -1,4 +1,17 @@
{ {
nixos =
{ lib, ... }:
{
options.features.neovim = {
enable = lib.mkEnableOption "neovim";
dotfiles = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
};
};
};
home = home =
{ {
config, config,
@@ -6,28 +19,27 @@
lib, lib,
pkgs, pkgs,
inputs, inputs,
osConfig,
... ...
}: }:
let
cfg = osConfig.features.neovim;
in
{ {
options = { config = lib.mkIf cfg.enable (
neovim.dotfiles = lib.mkOption { lib.mkMerge [
type = lib.types.nullOr lib.types.path;
default = null;
};
};
config = lib.mkMerge [
(lib.optionalAttrs (options ? stylix) { (lib.optionalAttrs (options ? stylix) {
# disable stylix neovim target when stylix is present (loaded by desktop feature) # disable stylix neovim target when stylix is present
stylix.targets.neovim.enable = false; stylix.targets.neovim.enable = false;
}) })
{ {
xdg.configFile."nvim" = lib.mkIf (config.neovim.dotfiles != null) { xdg.configFile."nvim" = lib.mkIf (cfg.dotfiles != null) {
source = config.neovim.dotfiles; source = cfg.dotfiles;
}; };
programs.neovim = { programs.neovim = {
enable = true; enable = true;
sideloadInitLua = true;
vimAlias = true; vimAlias = true;
defaultEditor = true; defaultEditor = true;
package = inputs.neovim-nightly-overlay.packages.${pkgs.stdenv.hostPlatform.system}.default; package = inputs.neovim-nightly-overlay.packages.${pkgs.stdenv.hostPlatform.system}.default;
@@ -64,6 +76,7 @@
]; ];
}; };
} }
]; ]
);
}; };
} }

View File

@@ -1,9 +1,18 @@
{ {
nixos = _: { nixos =
{ config, lib, ... }:
let
cfg = config.features.networkmanager;
in
{
options.features.networkmanager.enable = lib.mkEnableOption "networkmanager";
config = lib.mkIf cfg.enable {
networking.networkmanager.enable = true; networking.networkmanager.enable = true;
networking.nameservers = [ networking.nameservers = [
"1.1.1.1" "1.1.1.1"
"8.8.8.8" "8.8.8.8"
]; ];
}; };
};
} }

View File

@@ -1,5 +1,14 @@
{ {
nixos = _: { nixos =
{ config, lib, ... }:
let
cfg = config.features.nix-ld;
in
{
options.features.nix-ld.enable = lib.mkEnableOption "nix-ld";
config = lib.mkIf cfg.enable {
programs.nix-ld.enable = true; programs.nix-ld.enable = true;
}; };
};
} }

71
features/nix-settings.nix Normal file
View File

@@ -0,0 +1,71 @@
{
nixos =
{ config, lib, ... }:
let
cfg = config.features.nix-settings;
in
{
options.features.nix-settings = {
enable = lib.mkEnableOption "nix settings";
towerCache.enable = lib.mkOption {
type = lib.types.bool;
default = true;
};
gc = {
dates = lib.mkOption {
type = lib.types.str;
default = "monthly";
};
olderThan = lib.mkOption {
type = lib.types.str;
default = "30d";
};
};
optimise.dates = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "monthly" ];
};
};
config = lib.mkIf cfg.enable {
nix = {
settings = {
experimental-features = [
"nix-command"
"flakes"
];
download-buffer-size = 2 * 1024 * 1024 * 1024;
download-attempts = 3;
fallback = true;
warn-dirty = false;
substituters = [
"https://cache.nixos.org"
"https://nix-community.cachix.org?priority=45"
]
++ lib.optional cfg.towerCache.enable "http://tower:5000?priority=50";
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
]
++ lib.optional cfg.towerCache.enable "matej.nix-1:TdbemLVYblvAxqJcwb3mVKmmr3cfzXbMcZHE5ILnZDE=";
};
gc = {
automatic = true;
inherit (cfg.gc) dates;
options = "--delete-older-than ${cfg.gc.olderThan}";
};
optimise = {
automatic = true;
inherit (cfg.optimise) dates;
};
};
};
};
}

23
features/onepassword.nix Normal file
View File

@@ -0,0 +1,23 @@
{
nixos =
{
config,
lib,
user,
...
}:
let
cfg = config.features.onepassword;
in
{
options.features.onepassword.enable = lib.mkEnableOption "1password";
config = lib.mkIf cfg.enable {
programs._1password.enable = true;
programs._1password-gui = {
enable = true;
polkitPolicyOwners = [ user ];
};
};
};
}

View File

@@ -1,18 +1,23 @@
{ {
nixos = nixos =
{ lib, config, ... }: { lib, config, ... }:
let
cfg = config.features.openssh;
in
{ {
options = { options.features.openssh = {
openssh.port = lib.mkOption { enable = lib.mkEnableOption "openssh";
port = lib.mkOption {
type = lib.types.port; type = lib.types.port;
default = 22; default = 22;
}; };
}; };
config = { config = lib.mkIf cfg.enable {
services.openssh = { services.openssh = {
enable = true; enable = true;
ports = [ config.openssh.port ]; ports = [ cfg.port ];
settings = { settings = {
PasswordAuthentication = false; PasswordAuthentication = false;
AllowUsers = null; AllowUsers = null;

57
features/power.nix Normal file
View File

@@ -0,0 +1,57 @@
{
nixos =
{ config, lib, ... }:
let
cfg = config.features.power;
in
{
options.features.power = {
enable = lib.mkEnableOption "laptop power management";
resumeDevice = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
lidSwitch = lib.mkOption {
type = lib.types.str;
default = "suspend-then-hibernate";
};
powerKey = lib.mkOption {
type = lib.types.str;
default = "suspend-then-hibernate";
};
idleAction = lib.mkOption {
type = lib.types.str;
default = "suspend-then-hibernate";
};
idleActionSec = lib.mkOption {
type = lib.types.str;
default = "15min";
};
hibernateDelaySec = lib.mkOption {
type = lib.types.str;
default = "30min";
};
};
config = lib.mkIf cfg.enable {
boot.resumeDevice = lib.mkIf (cfg.resumeDevice != null) cfg.resumeDevice;
services.logind.settings.Login = {
HandleLidSwitch = cfg.lidSwitch;
HandlePowerKey = cfg.powerKey;
IdleAction = cfg.idleAction;
IdleActionSec = cfg.idleActionSec;
};
systemd.sleep.settings.Sleep = {
HibernateDelaySec = cfg.hibernateDelaySec;
};
};
};
}

View File

@@ -1,5 +1,13 @@
{ {
nixos = _: { nixos =
{ config, lib, ... }:
let
cfg = config.features.printing;
in
{
options.features.printing.enable = lib.mkEnableOption "printing";
config = lib.mkIf cfg.enable {
services.printing.enable = true; services.printing.enable = true;
services.avahi = { services.avahi = {
enable = true; enable = true;
@@ -7,4 +15,5 @@
openFirewall = true; openFirewall = true;
}; };
}; };
};
} }

View File

@@ -1,7 +1,18 @@
{ {
nixos = nixos =
{ config, user, ... }:
{ {
config,
lib,
user,
...
}:
let
cfg = config.features.remote-base;
in
{
options.features.remote-base.enable = lib.mkEnableOption "remote-base";
config = lib.mkIf cfg.enable {
sops.secrets.user-password = { sops.secrets.user-password = {
sopsFile = ../secrets/common.yaml; sopsFile = ../secrets/common.yaml;
neededForUsers = true; neededForUsers = true;
@@ -10,4 +21,5 @@
users.mutableUsers = false; users.mutableUsers = false;
users.users.${user}.hashedPasswordFile = config.sops.secrets.user-password.path; users.users.${user}.hashedPasswordFile = config.sops.secrets.user-password.path;
}; };
};
} }

View File

@@ -1,16 +1,23 @@
{ {
nixos = _: { nixos =
programs.zsh.enable = true; { lib, ... }:
environment.etc."zshenv".text = '' {
export ZDOTDIR=$HOME/.config/zsh options.features.shell.enable = lib.mkEnableOption "shell extras";
'';
}; };
home = home =
{ pkgs, ... }:
{ {
pkgs,
lib,
osConfig,
...
}:
let
cfg = osConfig.features.shell;
in
{
config = lib.mkIf cfg.enable {
home.packages = with pkgs; [ home.packages = with pkgs; [
starship
fzf fzf
htop htop
jc jc
@@ -22,4 +29,5 @@
tmux tmux
]; ];
}; };
};
} }

View File

@@ -1,10 +0,0 @@
{
nixos = _: {
programs.steam = {
enable = true;
remotePlay.openFirewall = true;
dedicatedServer.openFirewall = true;
localNetworkGameTransfers.openFirewall = true;
};
};
}

View File

@@ -1,7 +1,39 @@
{ {
nixos = nixos =
{ pkgs, ... }:
{ {
config,
lib,
pkgs,
...
}:
let
cfg = config.features.sway;
desktopCfg = config.features.desktop;
in
{
options.features.sway = {
enable = lib.mkEnableOption "sway window manager";
greeter.enable = lib.mkOption {
type = lib.types.bool;
default = true;
};
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
# soft dependency
features.desktop.enable = lib.mkDefault true;
# hard dependency
assertions = [
{
assertion = desktopCfg.enable;
message = "features.sway requires features.desktop";
}
];
programs.sway = { programs.sway = {
enable = true; enable = true;
package = pkgs.swayfx; package = pkgs.swayfx;
@@ -9,6 +41,8 @@
extraSessionCommands = '' extraSessionCommands = ''
# fix for java awt apps not rendering # fix for java awt apps not rendering
export _JAVA_AWT_WM_NONREPARENTING=1 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
''; '';
}; };
@@ -34,5 +68,33 @@
cliphist cliphist
zenity 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;
};
settings = {
background = {
path = lib.mkForce (toString desktopCfg.theme.wallpaper);
fit = lib.mkForce "Cover";
};
GTK = {
application_prefer_dark_theme = lib.mkForce true;
};
};
};
})
]
);
}; };
} }

View File

@@ -1,8 +1,17 @@
{ {
nixos = _: { nixos =
{ config, lib, ... }:
let
cfg = config.features.tailscale;
in
{
options.features.tailscale.enable = lib.mkEnableOption "tailscale";
config = lib.mkIf cfg.enable {
services.tailscale = { services.tailscale = {
enable = true; enable = true;
useRoutingFeatures = "both"; useRoutingFeatures = "both";
}; };
}; };
};
} }

59
features/udev.nix Normal file
View File

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

View File

@@ -9,16 +9,11 @@ in
sshAuthorizedKeys = sshKeys; sshAuthorizedKeys = sshKeys;
}; };
nixos = nixos = _: {
{ pkgs, ... }:
{
programs.zsh.enable = true;
users.users.matej = { users.users.matej = {
uid = 1000; uid = 1000;
isNormalUser = true; isNormalUser = true;
home = "/home/matej"; home = "/home/matej";
shell = pkgs.zsh;
extraGroups = [ "wheel" ]; extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = sshKeys; openssh.authorizedKeys.keys = sshKeys;
}; };
@@ -30,6 +25,6 @@ in
}; };
home = _: { home = _: {
home.stateVersion = "24.11"; home.stateVersion = "26.05";
}; };
} }

View File

@@ -1,72 +0,0 @@
{
nixos =
{
pkgs,
lib,
config,
...
}:
let
inherit (config.vm-9p-automount) user;
inherit (config.users.users.${user}) home group;
in
{
options = {
vm-9p-automount = {
user = lib.mkOption {
type = lib.types.str;
};
prefix = lib.mkOption {
type = lib.types.str;
default = "m_";
};
basePath = lib.mkOption {
type = lib.types.str;
default = "${home}/mnt";
};
};
};
config = {
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="${config.vm-9p-automount.basePath}"
PREFIX="${config.vm-9p-automount.prefix}"
mkdir -p "$BASE"
chown ${user}:${group} "$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

@@ -6,28 +6,52 @@
config, config,
... ...
}: }:
let
cfg = config.features.vm-guest;
autoUser = cfg.automount.user;
autoHome = config.users.users.${autoUser}.home;
autoGroup = config.users.users.${autoUser}.group;
in
{ {
options = { options.features.vm-guest = {
vm-guest.headless = lib.mkOption { enable = lib.mkEnableOption "qemu vm guest";
headless = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = false; default = false;
}; };
automount = {
enable = lib.mkEnableOption "9p share automount";
user = lib.mkOption {
type = lib.types.str;
}; };
config = { prefix = lib.mkOption {
services.qemuGuest.enable = true; type = lib.types.str;
services.spice-vdagentd.enable = lib.mkIf (!config.vm-guest.headless) true; default = "m_";
};
boot.kernelParams = lib.mkIf config.vm-guest.headless [ "console=ttyS0,115200" ]; basePath = lib.mkOption {
type = lib.types.str;
default = "${autoHome}/mnt";
};
};
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
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 = [ boot.initrd.availableKernelModules = [
"9p" "9p"
"9pnet_virtio" "9pnet_virtio"
]; ];
boot.kernelModules = [
"9p"
"9pnet_virtio"
];
networking = { networking = {
useDHCP = true; useDHCP = true;
@@ -40,8 +64,49 @@
curl curl
wget wget
htop 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
[ -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,12 +0,0 @@
{
nixos =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
yubikey-personalization
yubikey-manager
];
services.pcscd.enable = true;
};
}

55
features/zsh.nix Normal file
View File

@@ -0,0 +1,55 @@
{
nixos =
{
config,
lib,
pkgs,
user,
...
}:
let
cfg = config.features.zsh;
in
{
options.features.zsh = {
enable = lib.mkEnableOption "zsh";
loginShell.enable = lib.mkOption {
type = lib.types.bool;
default = true;
};
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
programs.zsh.enable = true;
environment.etc."zshenv".text = ''
export ZDOTDIR=$HOME/.config/zsh
'';
}
(lib.mkIf cfg.loginShell.enable {
users.users.${user}.shell = pkgs.zsh;
})
]
);
};
home =
{
pkgs,
lib,
osConfig,
...
}:
let
cfg = osConfig.features.zsh;
in
{
config = lib.mkIf cfg.enable {
home.packages = [ pkgs.starship ];
};
};
}

160
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": {
@@ -122,11 +122,11 @@
"firefox-gnome-theme": { "firefox-gnome-theme": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1764873433, "lastModified": 1775176642,
"narHash": "sha256-1XPewtGMi+9wN9Ispoluxunw/RwozuTRVuuQOmxzt+A=", "narHash": "sha256-2veEED0Fg7Fsh81tvVDNYR6SzjqQxa7hbi18Jv4LWpM=",
"owner": "rafaelmardojai", "owner": "rafaelmardojai",
"repo": "firefox-gnome-theme", "repo": "firefox-gnome-theme",
"rev": "f7ffd917ac0d253dbd6a3bf3da06888f57c69f92", "rev": "179704030c5286c729b5b0522037d1d51341022c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -156,11 +156,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1772408722, "lastModified": 1775087534,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -177,11 +177,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1772408722, "lastModified": 1775087534,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -198,11 +198,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1767609335, "lastModified": 1775087534,
"narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=", "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "250481aafeb741edfe23d29195671c19b36b6dca", "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -252,20 +252,18 @@
"gnome-shell": { "gnome-shell": {
"flake": false, "flake": false,
"locked": { "locked": {
"host": "gitlab.gnome.org",
"lastModified": 1767737596, "lastModified": 1767737596,
"narHash": "sha256-eFujfIUQDgWnSJBablOuG+32hCai192yRdrNHTv0a+s=", "narHash": "sha256-eFujfIUQDgWnSJBablOuG+32hCai192yRdrNHTv0a+s=",
"owner": "GNOME", "owner": "GNOME",
"repo": "gnome-shell", "repo": "gnome-shell",
"rev": "ef02db02bf0ff342734d525b5767814770d85b49", "rev": "ef02db02bf0ff342734d525b5767814770d85b49",
"type": "gitlab" "type": "github"
}, },
"original": { "original": {
"host": "gitlab.gnome.org",
"owner": "GNOME", "owner": "GNOME",
"ref": "gnome-49",
"repo": "gnome-shell", "repo": "gnome-shell",
"type": "gitlab" "rev": "ef02db02bf0ff342734d525b5767814770d85b49",
"type": "github"
} }
}, },
"home-manager": { "home-manager": {
@@ -275,16 +273,15 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1774559029, "lastModified": 1776777932,
"narHash": "sha256-deix7yg3j6AhjMPnFDCmWB3f83LsajaaULP5HH2j34k=", "narHash": "sha256-0R3Yow/NzSeVGUke5tL7CCkqmss4Vmi6BbV6idHzq/8=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "a0bb0d11514f92b639514220114ac8063c72d0a3", "rev": "5d5640599a0050b994330328b9fd45709c909720",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nix-community", "owner": "nix-community",
"ref": "release-25.11",
"repo": "home-manager", "repo": "home-manager",
"type": "github" "type": "github"
} }
@@ -320,11 +317,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1774742707, "lastModified": 1776729909,
"narHash": "sha256-a3FjZJxDOn0t18VwtIAgpNuUNaIEl6T+Awu5tXifQQw=", "narHash": "sha256-wGu/N42PJqrj8ju9GoXdppg4rwaKzZqdAjsgxJbCvfY=",
"owner": "nix-community", "owner": "nix-community",
"repo": "neovim-nightly-overlay", "repo": "neovim-nightly-overlay",
"rev": "7966a9c203276bea3b7e8dd2e125fd2b4c8b6753", "rev": "ff21a18bde28b4c8ca0bc1f9a5b7186a1b89a3d1",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -336,11 +333,11 @@
"neovim-src": { "neovim-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1774725909, "lastModified": 1776727374,
"narHash": "sha256-aOiiQCmjCrvo+jAUDO2oMa377FvOtU97aqvTm74ZRGU=", "narHash": "sha256-iP5SviNXW5W+ay4ZmwjDFsfQjfM+fYlUxRlLPHjpwWI=",
"owner": "neovim", "owner": "neovim",
"repo": "neovim", "repo": "neovim",
"rev": "d5516daf121aa718e79bcd423ee24c24492893c0", "rev": "901b3f0c394a53961781ebeee682e64ad690a242",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -351,11 +348,11 @@
}, },
"nixos-hardware": { "nixos-hardware": {
"locked": { "locked": {
"lastModified": 1774777275, "lastModified": 1775490113,
"narHash": "sha256-qogBiYFq8hZusDPeeKRqzelBAhZvREc7Cl+qlewGUCg=", "narHash": "sha256-2ZBhDNZZwYkRmefK5XLOusCJHnoeKkoN95hoSGgMxWM=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixos-hardware", "repo": "nixos-hardware",
"rev": "b8f81636927f1af0cca812d22c876bad0a883ccd", "rev": "c775c2772ba56e906cbeb4e0b2db19079ef11ff7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -367,11 +364,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1774610258, "lastModified": 1776329215,
"narHash": "sha256-HaThtroVD9wRdx7KQk0B75JmFcXlMUoEdDFNOMOlsOs=", "narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "832efc09b4caf6b4569fbf9dc01bec3082a00611", "rev": "b86751bc4085f48661017fa226dee99fab6c651b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -383,11 +380,11 @@
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1772328832, "lastModified": 1774748309,
"narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=", "narHash": "sha256-+U7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixpkgs.lib", "repo": "nixpkgs.lib",
"rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742", "rev": "333c4e0545a6da976206c74db8773a1645b5870a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -398,11 +395,11 @@
}, },
"nixpkgs-master": { "nixpkgs-master": {
"locked": { "locked": {
"lastModified": 1774809814, "lastModified": 1776807375,
"narHash": "sha256-Whds0FmLqkMftqIWMjugfP07WwBepbo09xkOxRmHi+U=", "narHash": "sha256-LDnHG0T54OEHyRydmGUlAND8ham0KrRNWjgoS+6GUd4=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "e75351d88d980df26a421789003f5d5c46c3f693", "rev": "553ecb1686a2edb75dee44c9f72e1674e6adc26a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -412,34 +409,34 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-unstable": { "nixpkgs-stable": {
"locked": { "locked": {
"lastModified": 1774701658, "lastModified": 1776560675,
"narHash": "sha256-CIS/4AMUSwUyC8X5g+5JsMRvIUL3YUfewe8K4VrbsSQ=", "narHash": "sha256-p68udKWWh7+V4ZPpcMDq0gTHWNZJnr4JPI+kHPPE40o=",
"owner": "nixos", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b63fe7f000adcfa269967eeff72c64cafecbbebe", "rev": "e07580dae39738e46609eaab8b154de2488133ce",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "NixOS",
"ref": "nixpkgs-unstable", "ref": "nixos-25.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1774388614, "lastModified": 1776548001,
"narHash": "sha256-tFwzTI0DdDzovdE9+Ras6CUss0yn8P9XV4Ja6RjA+nU=", "narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "1073dad219cb244572b74da2b20c7fe39cb3fa9e", "rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-25.11", "ref": "nixos-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@@ -456,11 +453,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1767886815, "lastModified": 1775228139,
"narHash": "sha256-pB2BBv6X9cVGydEV/9Y8+uGCvuYJAlsprs1v1QHjccA=", "narHash": "sha256-ebbeHmg+V7w8050bwQOuhmQHoLOEOfqKzM1KgCTexK4=",
"owner": "nix-community", "owner": "nix-community",
"repo": "NUR", "repo": "NUR",
"rev": "4ff84374d77ff62e2e13a46c33bfeb73590f9fef", "rev": "601971b9c89e0304561977f2c28fa25e73aa7132",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -519,7 +516,7 @@
"nixos-hardware": "nixos-hardware", "nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_2", "nixpkgs": "nixpkgs_2",
"nixpkgs-master": "nixpkgs-master", "nixpkgs-master": "nixpkgs-master",
"nixpkgs-unstable": "nixpkgs-unstable", "nixpkgs-stable": "nixpkgs-stable",
"nvim": "nvim", "nvim": "nvim",
"sops-nix": "sops-nix", "sops-nix": "sops-nix",
"stylix": "stylix" "stylix": "stylix"
@@ -553,11 +550,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1774760784, "lastModified": 1776771786,
"narHash": "sha256-D+tgywBHldTc0klWCIC49+6Zlp57Y4GGwxP1CqfxZrY=", "narHash": "sha256-DRFGPfFV6hbrfO9a1PH1FkCi7qR5FgjSqsQGGvk1rdI=",
"owner": "Mic92", "owner": "Mic92",
"repo": "sops-nix", "repo": "sops-nix",
"rev": "8adb84861fe70e131d44e1e33c426a51e2e0bfa5", "rev": "bef289e2248991f7afeb95965c82fbcd8ff72598",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -580,23 +577,21 @@
], ],
"nur": "nur", "nur": "nur",
"systems": "systems", "systems": "systems",
"tinted-foot": "tinted-foot",
"tinted-kitty": "tinted-kitty", "tinted-kitty": "tinted-kitty",
"tinted-schemes": "tinted-schemes", "tinted-schemes": "tinted-schemes",
"tinted-tmux": "tinted-tmux", "tinted-tmux": "tinted-tmux",
"tinted-zed": "tinted-zed" "tinted-zed": "tinted-zed"
}, },
"locked": { "locked": {
"lastModified": 1774194089, "lastModified": 1776170745,
"narHash": "sha256-SCczWhr8y8aaXVHG+gOGcRahNb0BU1Z5zYZuv9W/nA8=", "narHash": "sha256-Tl1aZVP5EIlT+k0+iAKH018GLHJpLz3hhJ0LNQOWxCc=",
"owner": "danth", "owner": "danth",
"repo": "stylix", "repo": "stylix",
"rev": "7c34241d80ea64dd2039bb3a786fb66b4c6261d9", "rev": "e3861617645a43c9bbefde1aa6ac54dd0a44bfa9",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "danth", "owner": "danth",
"ref": "release-25.11",
"repo": "stylix", "repo": "stylix",
"type": "github" "type": "github"
} }
@@ -616,23 +611,6 @@
"type": "github" "type": "github"
} }
}, },
"tinted-foot": {
"flake": false,
"locked": {
"lastModified": 1726913040,
"narHash": "sha256-+eDZPkw7efMNUf3/Pv0EmsidqdwNJ1TaOum6k7lngDQ=",
"owner": "tinted-theming",
"repo": "tinted-foot",
"rev": "fd1b924b6c45c3e4465e8a849e67ea82933fcbe4",
"type": "github"
},
"original": {
"owner": "tinted-theming",
"repo": "tinted-foot",
"rev": "fd1b924b6c45c3e4465e8a849e67ea82933fcbe4",
"type": "github"
}
},
"tinted-kitty": { "tinted-kitty": {
"flake": false, "flake": false,
"locked": { "locked": {
@@ -652,11 +630,11 @@
"tinted-schemes": { "tinted-schemes": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1767817087, "lastModified": 1772661346,
"narHash": "sha256-eGE8OYoK6HzhJt/7bOiNV2cx01IdIrHL7gXgjkHRdNo=", "narHash": "sha256-4eu3LqB9tPqe0Vaqxd4wkZiBbthLbpb7llcoE/p5HT0=",
"owner": "tinted-theming", "owner": "tinted-theming",
"repo": "schemes", "repo": "schemes",
"rev": "bd99656235aab343e3d597bf196df9bc67429507", "rev": "13b5b0c299982bb361039601e2d72587d6846294",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -668,11 +646,11 @@
"tinted-tmux": { "tinted-tmux": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1767489635, "lastModified": 1772934010,
"narHash": "sha256-e6nnFnWXKBCJjCv4QG4bbcouJ6y3yeT70V9MofL32lU=", "narHash": "sha256-x+6+4UvaG+RBRQ6UaX+o6DjEg28u4eqhVRM9kpgJGjQ=",
"owner": "tinted-theming", "owner": "tinted-theming",
"repo": "tinted-tmux", "repo": "tinted-tmux",
"rev": "3c32729ccae99be44fe8a125d20be06f8d7d8184", "rev": "c3529673a5ab6e1b6830f618c45d9ce1bcdd829d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -684,11 +662,11 @@
"tinted-zed": { "tinted-zed": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1767488740, "lastModified": 1772909925,
"narHash": "sha256-wVOj0qyil8m+ouSsVZcNjl5ZR+1GdOOAooAatQXHbuU=", "narHash": "sha256-jx/5+pgYR0noHa3hk2esin18VMbnPSvWPL5bBjfTIAU=",
"owner": "tinted-theming", "owner": "tinted-theming",
"repo": "base16-zed", "repo": "base16-zed",
"rev": "11abb0b282ad3786a2aae088d3a01c60916f2e40", "rev": "b4d3a1b3bcbd090937ef609a0a3b37237af974df",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -2,8 +2,8 @@
description = "matej's nix setup"; description = "matej's nix setup";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable"; nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.11";
nixpkgs-master.url = "github:nixos/nixpkgs/master"; nixpkgs-master.url = "github:nixos/nixpkgs/master";
nvim = { nvim = {
@@ -16,12 +16,12 @@
flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts.url = "github:hercules-ci/flake-parts";
home-manager = { home-manager = {
url = "github:nix-community/home-manager/release-25.11"; url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
stylix = { stylix = {
url = "github:danth/stylix/release-25.11"; url = "github:danth/stylix";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };

View File

@@ -8,6 +8,7 @@ let
pkgs.zlib pkgs.zlib
pkgs.openssl pkgs.openssl
pkgs.curl pkgs.curl
pkgs.libffi
]; ];
mkNode = nodejs: { mkNode = nodejs: {
@@ -24,12 +25,12 @@ let
packages = [ packages = [
python python
pkgs.uv pkgs.uv
pkgs.pkg-config
]; ];
libraries = pythonLibraries; libraries = pythonLibraries;
env = { env = {
UV_PYTHON_DOWNLOADS = "never"; UV_PYTHON_DOWNLOADS = "never";
UV_PYTHON_PREFERENCE = "only-system"; UV_PYTHON_PREFERENCE = "only-system";
UV_PYTHON = "${python}/bin/python";
}; };
shellHook = '' shellHook = ''
unset PYTHONPATH unset PYTHONPATH
@@ -44,27 +45,10 @@ let
uv_13 = mkUv pkgs.python313; uv_13 = mkUv pkgs.python313;
uv_14 = mkUv pkgs.python314; uv_14 = mkUv pkgs.python314;
pg_15 = {
packages = [ pkgs.postgresql_15 ];
};
pg_16 = {
packages = [ pkgs.postgresql_16 ];
};
pg_17 = {
packages = [ pkgs.postgresql_17 ];
};
pg_18 = {
packages = [ pkgs.postgresql_18 ];
};
node_20 = mkNode pkgs.nodejs_20; node_20 = mkNode pkgs.nodejs_20;
node_22 = mkNode pkgs.nodejs_22; node_22 = mkNode pkgs.nodejs_22;
node_24 = mkNode pkgs.nodejs_24; node_24 = mkNode pkgs.nodejs_24;
go = {
packages = [ pkgs.go ];
};
rust = { rust = {
packages = [ packages = [
pkgs.rustc pkgs.rustc
@@ -75,20 +59,14 @@ let
]; ];
}; };
cmake = {
packages = [
pkgs.cmake
pkgs.ninja
];
};
}; };
# build a single mkShell from one or more component names # build a single mkShell from one or more component names
mkComponentShell = mkComponentShell =
names: names: extraPackages:
let let
selected = map (n: components.${n}) names; selected = map (n: components.${n}) names;
allPackages = lib.concatMap (c: c.packages or [ ]) selected; allPackages = lib.concatMap (c: c.packages or [ ]) selected ++ extraPackages;
allLibraries = lib.concatMap (c: c.libraries or [ ]) selected; allLibraries = lib.concatMap (c: c.libraries or [ ]) selected;
allHooks = lib.concatMapStrings (c: c.shellHook or "") selected; allHooks = lib.concatMapStrings (c: c.shellHook or "") selected;
allEnvs = lib.foldl' (acc: c: acc // (c.env or { })) { } selected; allEnvs = lib.foldl' (acc: c: acc // (c.env or { })) { } selected;

View File

@@ -15,25 +15,29 @@ in
system = "x86_64-linux"; system = "x86_64-linux";
user = "matej"; user = "matej";
features = [ features = [
"openssh" "bootloader"
"localisation"
"gnupg"
"shell"
"desktop"
"sway"
"greeter"
"printing"
"networkmanager"
"docker"
"tailscale"
"nix-ld"
"yubikey"
"calibre"
"steam"
"direnv"
"neovim"
"dev"
"claude" "claude"
"desktop"
"dev"
"direnv"
"docker"
"gaming"
"git"
"gnupg"
"localisation"
"neovim"
"networkmanager"
"nix-ld"
"nix-settings"
"onepassword"
"openssh"
"power"
"printing"
"shell"
"sway"
"tailscale"
"udev"
"zsh"
]; ];
}; };
@@ -41,27 +45,30 @@ in
system = "x86_64-linux"; system = "x86_64-linux";
user = "matej"; user = "matej";
features = [ features = [
"openssh" "bootloader"
"localisation"
"gnupg"
"shell"
"desktop"
"sway"
"greeter"
"printing"
"networkmanager"
"docker"
"tailscale"
"nix-ld"
"yubikey"
"calibre"
"steam"
"initrd-ssh"
"direnv"
"neovim"
"dev"
"claude" "claude"
"desktop"
"dev"
"direnv"
"docker"
"gaming"
"git"
"gnupg"
"harmonia" "harmonia"
"initrd-ssh"
"localisation"
"neovim"
"networkmanager"
"nix-ld"
"nix-settings"
"onepassword"
"openssh"
"printing"
"shell"
"sway"
"tailscale"
"udev"
"zsh"
]; ];
}; };
@@ -70,7 +77,9 @@ in
system = "x86_64-linux"; system = "x86_64-linux";
user = "matej"; user = "matej";
features = [ features = [
"nix-settings"
"openssh" "openssh"
"zsh"
]; ];
}; };
@@ -78,11 +87,14 @@ in
system = "x86_64-linux"; system = "x86_64-linux";
user = "matej"; user = "matej";
features = [ features = [
"openssh" "bootloader"
"localisation" "localisation"
"nix-settings"
"openssh"
"remote-base"
"shell" "shell"
"tailscale" "tailscale"
"remote-base" "zsh"
]; ];
}; };
@@ -91,11 +103,30 @@ in
system = "x86_64-linux"; system = "x86_64-linux";
user = "matej"; user = "matej";
features = [ features = [
"openssh" "filedrop"
"localisation" "localisation"
"nix-settings"
"openssh"
"remote-base"
"shell" "shell"
"tailscale" "tailscale"
"remote-base" "zsh"
];
};
fortress = mkHost "fortress" {
system = "x86_64-linux";
user = "matej";
features = [
"bootloader"
"desktop"
"gnupg"
"localisation"
"networkmanager"
"nix-settings"
"sway"
"udev"
"zsh"
]; ];
}; };
@@ -103,16 +134,18 @@ in
system = "x86_64-linux"; system = "x86_64-linux";
user = "matej"; user = "matej";
features = [ features = [
"openssh"
"localisation"
"gnupg"
"shell"
"vm-guest"
"vm-9p-automount"
"docker"
"neovim"
"claude" "claude"
"dev" "dev"
"docker"
"git"
"gnupg"
"localisation"
"neovim"
"nix-settings"
"openssh"
"shell"
"vm-guest"
"zsh"
]; ];
}; };
}; };

View File

@@ -1,21 +1,5 @@
{ inputs, ... }: _:
{ {
flake.overlays.default = flake.overlays.default = _: _: { };
_: prev:
let
pkgs-unstable = import inputs.nixpkgs-unstable {
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;
# TODO:(@janezicmatej) 2026-03-09 error with stable for telegram-desktop
inherit (pkgs-unstable) telegram-desktop;
};
} }

View File

@@ -15,7 +15,6 @@ in
} }
{ {
inherit pkgs; inherit pkgs;
pkgs-unstable = inputs.nixpkgs-unstable.legacyPackages.${system};
pkgs-master = inputs.nixpkgs-master.legacyPackages.${system}; pkgs-master = inputs.nixpkgs-master.legacyPackages.${system};
}; };
}; };

View File

@@ -2,9 +2,6 @@
{ {
imports = [ inputs.disko.nixosModules.disko ]; imports = [ inputs.disko.nixosModules.disko ];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
disko.devices.disk.main = { disko.devices.disk.main = {
type = "disk"; type = "disk";
device = "/dev/nvme0n1"; device = "/dev/nvme0n1";
@@ -32,10 +29,5 @@
}; };
}; };
localisation = {
timeZone = "Europe/Ljubljana";
defaultLocale = "en_US.UTF-8";
};
system.stateVersion = "25.11"; system.stateVersion = "25.11";
} }

View File

@@ -5,6 +5,7 @@
... ...
}: }:
{ {
features.nix-settings.towerCache.enable = false;
# no hardware firmware needed in a VM # no hardware firmware needed in a VM
hardware.enableRedistributableFirmware = lib.mkForce false; hardware.enableRedistributableFirmware = lib.mkForce false;
hardware.wirelessRegulatoryDatabase = lib.mkForce false; hardware.wirelessRegulatoryDatabase = lib.mkForce false;
@@ -12,37 +13,93 @@
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
rawImage = import (modulesPath + "/../lib/make-disk-image.nix") {
inherit lib config pkgs; inherit lib config pkgs;
inherit (config.virtualisation) diskSize; inherit (config.virtualisation) diskSize;
inherit (config.image) baseName; inherit (config.image) baseName;
format = "qcow2-compressed"; format = "qcow2";
copyChannel = false; copyChannel = false;
partitionTableType = "legacy"; 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
''
); );
}; };
vm-guest.headless = true; # auto-login on serial console
services.getty.autologinUser = "matej";
vm-9p-automount.user = "matej"; # enable zsh in home-manager so starship init gets wired up
home-manager.users.matej.programs.zsh = {
localisation = { enable = true;
timeZone = "UTC"; dotDir = "/home/matej/.config/zsh";
defaultLocale = "en_US.UTF-8"; shellAliases.dsp = "claude --dangerously-skip-permissions";
}; };
home-manager.users.matej = { home-manager.users.matej.programs.starship = {
neovim.dotfiles = inputs.nvim; enable = true;
settings = {
add_newline = false;
format = "$username$hostname$directory$character";
hostname = {
ssh_only = false;
style = "bold blue";
format = "[@$hostname]($style)";
}; };
username = {
show_always = true;
style_user = "bold blue";
format = "[$user]($style)";
};
directory.format = " [$path]($style) ";
character = {
success_symbol = "[>](bold green)";
error_symbol = "[>](bold red)";
};
};
};
features.vm-guest.headless = true;
features.vm-guest.automount = {
enable = true;
user = "matej";
};
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
# https://www.mail-archive.com/qemu-devel@nongnu.org/msg1162844.html
# accept any ssh key (ephemeral localhost-only vm)
services.openssh.settings.AuthorizedKeysCommand =
let
acceptKey = pkgs.writeShellScript "ephvm-accept-key" ''echo "$1 $2"'';
in
"${acceptKey} %t %k";
services.openssh.settings.AuthorizedKeysCommandUser = "nobody";
# writable claude config via 9p # writable claude config via 9p
fileSystems."/home/matej/.config/claude" = { fileSystems."/home/matej/.config/claude" = {

View File

@@ -2,6 +2,8 @@
{ {
imports = [ inputs.disko.nixosModules.disko ]; imports = [ inputs.disko.nixosModules.disko ];
features.filedrop.sopsFile = ../../secrets/floo.yaml;
boot.loader.grub.enable = true; boot.loader.grub.enable = true;
disko.devices.disk.main = { disko.devices.disk.main = {
@@ -26,10 +28,5 @@
}; };
}; };
localisation = {
timeZone = "Europe/Ljubljana";
defaultLocale = "en_US.UTF-8";
};
system.stateVersion = "25.11"; system.stateVersion = "25.11";
} }

View File

@@ -0,0 +1,79 @@
{
pkgs,
inputs,
...
}:
{
imports = [
inputs.disko.nixosModules.disko
inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series
];
features.desktop = {
apps.enable = false;
internalCA.enable = false;
};
features.gnupg.yubikey.enable = true;
disko.devices.disk.main = {
type = "disk";
device = "/dev/sda";
content = {
type = "gpt";
partitions = {
esp = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
luks = {
size = "100%";
content = {
type = "luks";
name = "cryptlvm";
settings.allowDiscards = true;
content = {
type = "lvm_pv";
vg = "vg";
};
};
};
};
};
};
disko.devices.lvm_vg.vg = {
type = "lvm_vg";
lvs = {
root = {
size = "100%FREE";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
swap = {
size = "32G";
content = {
type = "swap";
};
};
};
};
networking.firewall.enable = true;
environment.systemPackages = with pkgs; [
google-chrome
firefox
vim
];
system.stateVersion = "25.11";
}

View File

@@ -0,0 +1,30 @@
{
config,
lib,
pkgs,
modulesPath,
...
}:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
hardware.firmware = [ pkgs.linux-firmware ];
boot.initrd.availableKernelModules = [
"nvme"
"xhci_pci"
"thunderbolt"
"usbhid"
"uas"
"sd_mod"
];
boot.initrd.kernelModules = [ "dm-snapshot" ];
boot.kernelModules = [ "kvm-amd" ];
boot.extraModulePackages = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View File

@@ -1,8 +1,7 @@
{ {
lib, lib,
pkgs,
inputs,
options, options,
inputs,
... ...
}: }:
@@ -11,37 +10,23 @@
inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series
]; ];
localisation = { features.bootloader.plymouth.enable = true;
timeZone = "Europe/Ljubljana"; features.desktop.bluetooth.enable = true;
defaultLocale = "en_US.UTF-8"; features.gnupg.yubikey.enable = true;
features.udev = {
ledger.enable = true;
keyboard-zsa.enable = true;
}; };
features.power.resumeDevice = "/dev/disk/by-uuid/ff4750e7-3a9f-42c2-bb68-c458a6560540";
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.kernelParams = [ "pcie_aspm.policy=powersupersave" ]; boot.kernelParams = [ "pcie_aspm.policy=powersupersave" ];
boot.resumeDevice = "/dev/disk/by-uuid/ff4750e7-3a9f-42c2-bb68-c458a6560540";
services.logind.settings.Login = {
HandleLidSwitch = "suspend-then-hibernate";
HandlePowerKey = "suspend-then-hibernate";
IdleAction = "suspend-then-hibernate";
IdleActionSec = "15min";
};
systemd.sleep.extraConfig = ''
HibernateDelaySec=30min
'';
programs.nix-ld.libraries = options.programs.nix-ld.libraries.default; programs.nix-ld.libraries = options.programs.nix-ld.libraries.default;
services.gnome.gnome-keyring.enable = true; services.gnome.gnome-keyring.enable = true;
services.teamviewer.enable = true; services.teamviewer.enable = true;
services.hardware.bolt.enable = true; services.hardware.bolt.enable = true;
hardware.keyboard.zsa.enable = true;
hardware.ledger.enable = true;
hardware.bluetooth.powerOnBoot = true; hardware.bluetooth.powerOnBoot = true;
hardware.inputmodule.enable = true; hardware.inputmodule.enable = true;

View File

@@ -1,15 +1,13 @@
{ lib, ... }: { lib, userKeys, ... }:
{ {
features.nix-settings.towerCache.enable = false;
image.modules.iso-installer = { image.modules.iso-installer = {
isoImage.squashfsCompression = "zstd -Xcompression-level 6"; isoImage.squashfsCompression = "zstd -Xcompression-level 6";
}; };
# live iso: passwordless login and sudo # live iso: passwordless login and sudo
users.users.matej.initialHashedPassword = ""; users.users.matej.initialHashedPassword = "";
users.users.root.openssh.authorizedKeys.keys = [ users.users.root.openssh.authorizedKeys.keys = userKeys.sshAuthorizedKeys;
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICQGLdINKzs+sEy62Pefng0bcedgU396+OryFgeH99/c janezicmatej"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDk00+Km03epQXQs+xEwwH3zcurACzkEH+kDOPBw6RQe openpgp:0xB095D449"
];
services.openssh.settings.PermitRootLogin = lib.mkForce "prohibit-password"; services.openssh.settings.PermitRootLogin = lib.mkForce "prohibit-password";
security.sudo.wheelNeedsPassword = false; security.sudo.wheelNeedsPassword = false;

View File

@@ -1,38 +1,33 @@
{ {
config, config,
lib,
inputs,
userKeys, userKeys,
... ...
}: }:
{ {
imports = [ features.nix-settings.towerCache.enable = false;
inputs.lanzaboote.nixosModules.lanzaboote features.bootloader = {
]; mode = "lanzaboote";
plymouth.enable = true;
};
features.desktop.bluetooth.enable = true;
features.gnupg.yubikey.enable = true;
features.udev = {
ledger.enable = true;
keyboard-zsa.enable = true;
};
features.initrd-ssh = {
networkModule = "r8169";
authorizedKeys = userKeys.sshAuthorizedKeys;
};
# nix store signing # nix store signing
sops.secrets.nix-signing-key.sopsFile = ../../secrets/tower.yaml; sops.secrets.nix-signing-key.sopsFile = ../../secrets/tower.yaml;
nix.settings.secret-key-files = [ config.sops.secrets.nix-signing-key.path ]; nix.settings.secret-key-files = [ config.sops.secrets.nix-signing-key.path ];
localisation = {
timeZone = "Europe/Ljubljana";
defaultLocale = "en_US.UTF-8";
};
initrd-ssh = {
networkModule = "r8169";
authorizedKeys = userKeys.sshAuthorizedKeys;
};
# lanzaboote secure boot
boot.kernelParams = [ "btusb.reset=1" ]; boot.kernelParams = [ "btusb.reset=1" ];
boot.loader.efi.canTouchEfiVariables = true; # early kms so plymouth lands on amdgpu, not simpledrm
boot.loader.systemd-boot.enable = lib.mkForce false; hardware.amdgpu.initrd.enable = true;
boot.lanzaboote = {
enable = true;
pkiBundle = "/var/lib/sbctl";
};
services.udisks2.enable = true; services.udisks2.enable = true;

View File

@@ -27,7 +27,19 @@ let
hostConfig = ../hosts/${name}/configuration.nix; hostConfig = ../hosts/${name}/configuration.nix;
hostHWConfig = ../hosts/${name}/hardware-configuration.nix; hostHWConfig = ../hosts/${name}/hardware-configuration.nix;
# load feature with path check # auto-discover all features, excluding user-* and default.nix
featureDir = builtins.readDir ../features;
allFeatureNames = lib.pipe featureDir [
(lib.filterAttrs (
n: t:
(t == "regular" && lib.hasSuffix ".nix" n && n != "default.nix" && !lib.hasPrefix "user-" n)
|| (t == "directory" && builtins.pathExists ../features/${n}/default.nix)
))
builtins.attrNames
(map (n: lib.removeSuffix ".nix" n))
];
# load all features unconditionally
loadFeature = loadFeature =
f: f:
assert assert
@@ -35,7 +47,7 @@ let
|| throw "feature '${f}' not found at ${toString (featurePath f)}"; || throw "feature '${f}' not found at ${toString (featurePath f)}";
import (featurePath f); import (featurePath f);
loadedFeatures = map loadFeature features; loadedFeatures = map loadFeature allFeatureNames;
# load user feature with path check # load user feature with path check
userFeature = userFeature =
@@ -55,17 +67,27 @@ let
# collect nixos and home modules from all features # collect nixos and home modules from all features
nixosMods = map (f: f.nixos) (builtins.filter (f: f ? nixos) allFeatures); nixosMods = map (f: f.nixos) (builtins.filter (f: f ? nixos) allFeatures);
homeMods = map (f: f.home) (builtins.filter (f: f ? home) allFeatures); homeMods = map (f: f.home) (builtins.filter (f: f ? home) allFeatures);
# translate features list to enable flags
featureEnableModule =
{ lib, ... }:
{
config.features = lib.genAttrs features (_: {
enable = true;
});
};
in in
nixpkgs.lib.nixosSystem { nixpkgs.lib.nixosSystem {
inherit system; inherit system;
modules = [ modules = [
../nix.nix
inputs.sops-nix.nixosModules.sops inputs.sops-nix.nixosModules.sops
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
hostConfig hostConfig
] ]
++ lib.optional (builtins.pathExists hostHWConfig) hostHWConfig ++ lib.optional (builtins.pathExists hostHWConfig) hostHWConfig

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,23 +27,44 @@ info() {
# globals for cleanup trap # globals for cleanup trap
CLEANUP_OVERLAY="" CLEANUP_OVERLAY=""
CLEANUP_TMPDIR=""
QEMU_PID=""
VM_READY=false
cleanup() { cleanup() {
[ -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]
Options: Options:
--mount <path> Mount host directory into VM (repeatable) --mount <path> Mount host directory into VM (repeatable)
--claude Mount claude config dir (requires CLAUDE_CONFIG_DIR) --no-claude Skip mounting claude config dir
--disk-size <size> Resize guest disk (e.g. 50G) --disk-size <size> Resize guest disk (e.g. 50G)
--memory <size> VM memory (default: 8G) --memory <size> VM memory (default: 4G)
--cpus <n> VM CPUs (default: 4) --cpus <n> VM CPUs (default: 2)
--ssh-port <port> SSH port forward (default: 2222) --ssh-port <port> Use specific SSH port (default: auto)
--serial Attach to serial console instead of SSH
-h, --help Show usage -h, --help Show usage
EOF EOF
exit "${1:-0}" exit "${1:-0}"
@@ -52,7 +73,9 @@ EOF
main() { main() {
setup_colors setup_colors
local ssh_port=2222 memory=8G cpus=4 claude=false disk_size="" [ "$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=() local -a mounts=()
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
@@ -61,8 +84,8 @@ main() {
mounts+=("$2") mounts+=("$2")
shift 2 shift 2
;; ;;
--claude) --no-claude)
claude=true claude=false
shift shift
;; ;;
--disk-size) --disk-size)
@@ -81,6 +104,10 @@ main() {
ssh_port="$2" ssh_port="$2"
shift 2 shift 2
;; ;;
--serial)
serial=true
shift
;;
-h | --help) usage ;; -h | --help) usage ;;
*) *)
echo "${red}error:${reset} unknown option: $1" >&2 echo "${red}error:${reset} unknown option: $1" >&2
@@ -91,7 +118,9 @@ main() {
info "building ephvm image..." info "building ephvm image..."
local image_dir image local image_dir image
image_dir=$(nix build --no-link --print-out-paths .#nixosConfigurations.ephvm.config.system.build.images.qemu) [ -n "${EPHVM_FLAKE:-}" ] || die "EPHVM_FLAKE must be set to the flake directory"
local flake="$EPHVM_FLAKE"
image_dir=$(nix build --no-link --print-out-paths "${flake}#nixosConfigurations.ephvm.config.system.build.images.qemu")
image=$(find "$image_dir" -name '*.qcow2' -print -quit) image=$(find "$image_dir" -name '*.qcow2' -print -quit)
[ -n "$image" ] || die "no qcow2 image found in $image_dir" [ -n "$image" ] || die "no qcow2 image found in $image_dir"
@@ -101,31 +130,49 @@ 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
local accel="tcg" command -v qemu-system-x86_64 &>/dev/null || die "qemu-system-x86_64 not found"
[ -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
ssh_port=10022
while ss -tln | grep -q ":${ssh_port}\b"; do
ssh_port=$((ssh_port + 1))
done
fi
local nic_arg="user,model=virtio-net-pci"
if [ -n "$ssh_port" ]; then
nic_arg="user,model=virtio-net-pci,hostfwd=tcp:127.0.0.1:${ssh_port}-:22"
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"
-nic "user,hostfwd=tcp::${ssh_port}-:22" -device "virtio-blk-pci,drive=hd0"
-device virtio-rng-pci
-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+=(
@@ -135,10 +182,13 @@ main() {
done done
if [ "$claude" = true ]; then if [ "$claude" = true ]; then
[ -n "${CLAUDE_CONFIG_DIR:-}" ] || die "--claude requires CLAUDE_CONFIG_DIR to be set" [ -n "${CLAUDE_CONFIG_DIR:-}" ] || die "CLAUDE_CONFIG_DIR must be set (use --no-claude to skip)"
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}"
@@ -147,10 +197,42 @@ main() {
fi fi
info "---" info "---"
info "Accel: $accel | SSH: ssh -p $ssh_port matej@localhost" [ -n "$ssh_port" ] && info "SSH: ssh -p $ssh_port matej@localhost"
info "---" info "---"
if [ "$serial" = true ]; then
exec "${qemu_args[@]}" exec "${qemu_args[@]}"
fi
CLEANUP_TMPDIR=$(mktemp -d)
local qemu_log="$CLEANUP_TMPDIR/qemu.log"
# start qemu in background and auto-ssh
"${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
# 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 120 ] && die "vm did not become ready in 60s"
kill -0 "$QEMU_PID" 2>/dev/null || die "qemu exited unexpectedly"
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 \
matej@localhost
} }
main "$@" main "$@"

28
secrets/floo.yaml Normal file
View File

@@ -0,0 +1,28 @@
filedrop-authorized-keys: ENC[AES256_GCM,data:3zg0ZZR/EfmffhT+5hiiCawhHW0Y8VOcsMRwPq50AgSvM8DJO/fOK5RhPMlHmOOXSbYYal9QoPILP5rSHDMszk6QSRqmvAbpkpJhgfW4jx8XbLTFxO4lUKe/hd968ryqP2pXtZzUBnOp4vSI29LcYms6e8fSwS8ANtSIjCLkEsY=,iv:EOjsWB7uxjqI5NXot586Q0997SOmkAMwVkxm6VLplDc=,tag:Q4rB6KFibV+F79/rs5m0dA==,type:str]
sops:
age:
- recipient: age1hksdq2lc89thnpth49sw44f0pmkp950plrhhnttj4petvnfy04tsydz6fl
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTRk1qOGNGNFp4VS9GOUs0
dTQ0K3A2Y3VXY3NSV0RyU0VxV3VCa0dDOG5zClAvOElXcHhYaWNCamxFZHMvV2Iy
WFRwNFRjaHpKSDdkak5UK05hd0hYMFUKLS0tIGRQeVJGdk8vYVdQdS9BYVd3TEhn
UWxzeHlaY2pvdS9tbW9vaVE5NTNwRFEKKieIA5Sn6oN5qjDwh5/usaKwLdYPClmS
d+hBdcn4/mtQnrm9dnbRVHd/B1MOuQxoXEB1kc4nzFKvCEqRdRIlYQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-04-09T08:57:26Z"
mac: ENC[AES256_GCM,data:XHC5cBvQuDi9byVgDymx9qSbplDlHwFTSLaGfWTRQJZeioBelDgBwUstbgWDeNPj1RzGGaSa3+kDOa054DuXi/mw2nDnLGuQDFAmJ66kepJE1mw4F6i4+YnbSE+y7GTbTkUkvbmiNV7uGO4Fq9jy/gNb1wq3IHzDVaKNjNbkKAk=,iv:qK/tgbAkxGpfgJAjBrqDwO/lVkD79pY9S3hzXGGycvM=,tag:oHURU988sW4iN7fXwurOtQ==,type:str]
pgp:
- created_at: "2026-04-09T08:55:59Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hF4DPaEEpDtHdk8SAQdATk1lN0/WDX6S1oPje9jZloSll1qSNau3zgt67CrselMw
YlbenxVeY8G4qTvfimX9/qH1/SNHkL/B0jqMCEkw8EpeyA3oEIWuzEEEOA+W/Iri
0lwB26CTd8PKwvjuMwmvzTaZfQ9fk+ZsvIjtQaj//WA2utfU4b9T2E+M2Jb5vyki
INcWT4PJkNSDxm5NabcTqyetcorDGaU1oN/T1p7pvRBvGCSHItYthVvq/RC0bw==
=pKJc
-----END PGP MESSAGE-----
fp: AF349EECC849D87B790E88FF6318FFB7DB374B7D
unencrypted_suffix: _unencrypted
version: 3.12.2