Compare commits

..

4 Commits

58 changed files with 1224 additions and 2065 deletions

View File

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

View File

@@ -1,173 +0,0 @@
{
nixos =
{
config,
lib,
pkgs,
inputs,
...
}:
let
cfg = config.features.bootloader;
keyDir = "/etc/secrets/initrd";
mkIpString =
{
address,
gateway,
netmask,
interface,
...
}:
"${address}::${gateway}:${netmask}::${interface}:none";
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";
};
configurationLimit = lib.mkOption {
type = lib.types.int;
default = 10;
};
consoleFont = lib.mkOption {
type = lib.types.str;
default = "ter-v32n";
};
resumeDevice = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
initrdSsh = {
enable = lib.mkEnableOption "remote LUKS unlock via ssh in initrd";
networkModule = lib.mkOption {
type = lib.types.str;
};
ip = {
enable = lib.mkEnableOption "static IP for initrd (otherwise DHCP)";
address = lib.mkOption {
type = lib.types.str;
};
gateway = lib.mkOption {
type = lib.types.str;
};
netmask = lib.mkOption {
type = lib.types.str;
default = "255.255.255.0";
};
interface = lib.mkOption {
type = lib.types.str;
};
};
authorizedKeys = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
};
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
boot.loader.efi.canTouchEfiVariables = true;
# lanzaboote inherits editor + configurationLimit from systemd-boot.*
boot.loader.systemd-boot = {
editor = false;
inherit (cfg) configurationLimit;
};
boot.initrd.systemd.enable = true;
# block simpledrm so fbcon defers until the gpu driver binds; avoids
# the simpledrm -> real-driver fbcon transition that mangles console
# text and leaves the luks prompt typing offset from the visible
# surface. hosts must put the gpu driver in initrd (nixos-hardware
# does this for amd; manual hardware.amdgpu.initrd.enable on others)
boot.kernelParams = [ "initcall_blacklist=simpledrm_platform_driver_init" ];
# verbose boot: kernel messages and systemd unit lines visible end
# to end. trade-off: the luks prompt will be interleaved with the
# last few "Starting/Started ..." lines (no upstream fix exists
# without plymouth). boot.initrd.verbose is a no-op under
# systemd-initrd, so not set here.
# readable luks prompt at panel-native dpi
console = {
earlySetup = true;
font = cfg.consoleFont;
packages = [ pkgs.terminus_font ];
};
}
(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.resumeDevice != null) {
boot.resumeDevice = cfg.resumeDevice;
})
(lib.mkIf cfg.initrdSsh.enable {
boot.initrd.systemd.settings.Manager.DefaultDeviceTimeoutSec = "infinity";
boot.initrd.availableKernelModules = [ cfg.initrdSsh.networkModule ];
boot.kernelParams = lib.mkIf cfg.initrdSsh.ip.enable [
"ip=${mkIpString cfg.initrdSsh.ip}"
];
boot.initrd.network = {
enable = true;
ssh = {
enable = true;
port = 22;
hostKeys = [
"${keyDir}/ssh_host_rsa_key"
"${keyDir}/ssh_host_ed25519_key"
];
inherit (cfg.initrdSsh) authorizedKeys;
};
};
# forward LUKS password prompt to the ssh session (systemd-initrd idiom)
boot.initrd.systemd.users.root.shell = "/bin/systemd-tty-ask-password-agent";
boot.initrd.systemd.network.networks = lib.mkIf (!cfg.initrdSsh.ip.enable) {
"10-initrd" = {
matchConfig.Driver = cfg.initrdSsh.networkModule;
networkConfig.DHCP = "yes";
};
};
})
]
);
};
}

22
features/calibre.nix Normal file
View File

@@ -0,0 +1,22 @@
{
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,28 +1,10 @@
{ {
nixos =
{ lib, ... }:
{
options.features.claude.enable = lib.mkEnableOption "claude";
};
home = home =
{ pkgs, ... }:
{ {
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 = [
packages.claude-code pkgs.claude-code
pkgs.mcp-nixos pkgs.mcp-nixos
]; ];
}; };
};
} }

View File

@@ -1,130 +1,58 @@
{ {
nixos = nixos =
{ pkgs, inputs, ... }:
{ {
config, imports = [ inputs.stylix.nixosModules.stylix ];
lib,
pkgs,
inputs,
...
}:
let
cfg = config.features.desktop;
in
{
options.features.desktop = {
enable = lib.mkEnableOption "desktop environment";
audio.enable = lib.mkOption { # audio
type = lib.types.bool; services.pipewire = {
default = true; enable = true;
pulse.enable = true;
}; };
bluetooth.enable = lib.mkOption { # bluetooth
type = lib.types.bool; hardware.bluetooth.enable = true;
default = false; services.blueman.enable = true;
};
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;
extraPortals = with pkgs; [ xdgOpenUsePortal = true;
xdg-desktop-portal-wlr extraPortals = [
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;
inherit (cfg.theme) polarity; polarity = "dark";
image = cfg.theme.wallpaper; image = "${inputs.assets}/wallpaper.png";
base16Scheme = "${pkgs.base16-schemes}/share/themes/${cfg.theme.scheme}.yaml"; base16Scheme = "${pkgs.base16-schemes}/share/themes/gruvbox-material-dark-medium.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;
# TEMP:(@janezicmatej) workaround for nixpkgs#514705, fix in nixpkgs#517250
withApplet = false;
};
})
# 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
calibre pavucontrol
bolt-launcher bolt-launcher
libnotify libnotify
bibata-cursors bibata-cursors
discord vesktop
rocketchat-desktop rocketchat-desktop
telegram-desktop telegram-desktop
slack slack
@@ -136,142 +64,21 @@
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,36 +1,25 @@
{ {
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.osc pkgs.mdbook
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.qemu pkgs.osc
]; ];
};
}; };
} }

View File

@@ -1,18 +1,7 @@
{ {
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";
@@ -24,15 +13,8 @@
}; };
}; };
}; };
};
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;
@@ -41,5 +23,4 @@
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,28 +1,16 @@
# 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 --extra cairo pkg-config # usage in .envrc: use dev uv_14 pg_18
# 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="" extra_list="" in_extra=0 local nix_list=""
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
@@ -76,7 +64,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 ] [${extra_list} ]; devShells.\${system}.default = devLib.mkComponentShell [$nix_list ];
}; };
} }
DEVFLAKE DEVFLAKE

View File

@@ -1,18 +1,7 @@
{ {
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";
@@ -20,5 +9,4 @@
users.users.${user}.extraGroups = [ "docker" ]; users.users.${user}.extraGroups = [ "docker" ];
}; };
};
} }

View File

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

View File

@@ -1,26 +0,0 @@
{
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 ];
};
};
}

View File

@@ -1,29 +0,0 @@
{
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,43 +1,9 @@
{ {
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;
})
]
);
}; };
} }

28
features/greeter.nix Normal file
View File

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

75
features/initrd-ssh.nix Normal file
View File

@@ -0,0 +1,75 @@
{
nixos =
{ lib, config, ... }:
let
keyDir = "/etc/secrets/initrd";
mkIpString =
{
address,
gateway,
netmask,
interface,
...
}:
"${address}::${gateway}:${netmask}::${interface}:none";
in
{
options = {
initrd-ssh = {
ip = {
enable = lib.mkEnableOption "static IP for initrd (otherwise DHCP)";
address = lib.mkOption {
type = lib.types.str;
};
gateway = lib.mkOption {
type = lib.types.str;
};
netmask = lib.mkOption {
type = lib.types.str;
default = "255.255.255.0";
};
interface = lib.mkOption {
type = lib.types.str;
};
};
authorizedKeys = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
networkModule = lib.mkOption {
type = lib.types.str;
};
};
};
config = {
boot.initrd.kernelModules = [ config.initrd-ssh.networkModule ];
boot.kernelParams = lib.mkIf config.initrd-ssh.ip.enable [
"ip=${mkIpString config.initrd-ssh.ip}"
];
boot.initrd.network = {
enable = true;
ssh = {
enable = true;
port = 22;
hostKeys = [
"${keyDir}/ssh_host_rsa_key"
"${keyDir}/ssh_host_ed25519_key"
];
inherit (config.initrd-ssh) authorizedKeys;
};
postCommands = ''
echo 'cryptsetup-askpass' >> /root/.profile
'';
};
};
};
}

View File

@@ -1,30 +1,25 @@
{ {
nixos = nixos =
{ lib, config, ... }: { lib, config, ... }:
let
cfg = config.features.localisation;
in
{ {
options.features.localisation = { options = {
enable = lib.mkEnableOption "localisation"; 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 = lib.mkIf cfg.enable { config = {
time.timeZone = cfg.timeZone; time.timeZone = config.localisation.timeZone;
i18n.defaultLocale = cfg.defaultLocale; i18n.defaultLocale = config.localisation.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 = cfg.timeZone; environment.variables.TZ = config.localisation.timeZone;
}; };
}; };
} }

View File

@@ -1,17 +1,4 @@
{ {
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,
@@ -19,27 +6,28 @@
lib, lib,
pkgs, pkgs,
inputs, inputs,
osConfig,
... ...
}: }:
let
cfg = osConfig.features.neovim;
in
{ {
config = lib.mkIf cfg.enable ( options = {
lib.mkMerge [ neovim.dotfiles = lib.mkOption {
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 # disable stylix neovim target when stylix is present (loaded by desktop feature)
stylix.targets.neovim.enable = false; stylix.targets.neovim.enable = false;
}) })
{ {
xdg.configFile."nvim" = lib.mkIf (cfg.dotfiles != null) { xdg.configFile."nvim" = lib.mkIf (config.neovim.dotfiles != null) {
source = cfg.dotfiles; source = config.neovim.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;
@@ -76,7 +64,6 @@
]; ];
}; };
} }
] ];
);
}; };
} }

View File

@@ -1,18 +1,9 @@
{ {
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"
]; ];
}; };
};
} }

141
features/niri.nix Normal file
View File

@@ -0,0 +1,141 @@
{
nixos =
{ pkgs, inputs, ... }:
{
imports = [ inputs.niri.nixosModules.niri ];
nixpkgs.overlays = [ inputs.niri.overlays.niri ];
programs.niri.enable = true;
environment.systemPackages = with pkgs; [
swayidle
grim
slurp
wl-clipboard
brightnessctl
playerctl
pamixer
pulseaudio
cliphist
satty
wlsunset
wayland-pipewire-idle-inhibit
foot
jq
zenity
];
};
home =
{ inputs, ... }:
{
imports = [ inputs.noctalia.homeModules.default ];
programs.noctalia-shell = {
enable = true;
systemd.enable = true;
settings = {
colorSchemes.predefinedScheme = "gruvbox";
};
};
programs.niri.settings = {
environment = {
NIXOS_OZONE_WL = "1";
_JAVA_AWT_WM_NONREPARENTING = "1";
};
spawn-at-startup = [
{ argv = [ "swayidle" "-w" ]; }
{ argv = [ "wlsunset" ]; }
{ argv = [ "wl-paste" "--watch" "cliphist" "store" ]; }
];
input.keyboard.xkb = { };
binds = {
# apps
"Mod+Return".action.spawn = "ghostty";
"Mod+Space".action.spawn = [ "sh" "-c" "qs -c noctalia-shell ipc call launcher toggle" ];
"Mod+S".action.spawn = [ "sh" "-c" "qs -c noctalia-shell ipc call controlcenter toggle" ];
# window management
"Mod+Shift+Q".action.close-window = [ ];
"Mod+H".action.focus-column-left = [ ];
"Mod+J".action.focus-window-down = [ ];
"Mod+K".action.focus-window-up = [ ];
"Mod+L".action.focus-column-right = [ ];
"Mod+Shift+H".action.move-column-left = [ ];
"Mod+Shift+J".action.move-window-down = [ ];
"Mod+Shift+K".action.move-window-up = [ ];
"Mod+Shift+L".action.move-column-right = [ ];
"Mod+F".action.maximize-column = [ ];
"Mod+Shift+F".action.fullscreen-window = [ ];
# workspaces
"Mod+1".action.focus-workspace = 1;
"Mod+2".action.focus-workspace = 2;
"Mod+3".action.focus-workspace = 3;
"Mod+4".action.focus-workspace = 4;
"Mod+5".action.focus-workspace = 5;
"Mod+6".action.focus-workspace = 6;
"Mod+7".action.focus-workspace = 7;
"Mod+8".action.focus-workspace = 8;
"Mod+9".action.focus-workspace = 9;
"Mod+0".action.focus-workspace = 10;
"Mod+Shift+1".action.move-window-to-workspace = 1;
"Mod+Shift+2".action.move-window-to-workspace = 2;
"Mod+Shift+3".action.move-window-to-workspace = 3;
"Mod+Shift+4".action.move-window-to-workspace = 4;
"Mod+Shift+5".action.move-window-to-workspace = 5;
"Mod+Shift+6".action.move-window-to-workspace = 6;
"Mod+Shift+7".action.move-window-to-workspace = 7;
"Mod+Shift+8".action.move-window-to-workspace = 8;
"Mod+Shift+9".action.move-window-to-workspace = 9;
"Mod+Shift+0".action.move-window-to-workspace = 10;
# media (allow-when-locked for use on lock screen)
"XF86AudioRaiseVolume" = {
action.spawn = [ "pamixer" "-i" "5" ];
allow-when-locked = true;
};
"XF86AudioLowerVolume" = {
action.spawn = [ "pamixer" "-d" "5" ];
allow-when-locked = true;
};
"XF86AudioMute" = {
action.spawn = [ "pamixer" "-t" ];
allow-when-locked = true;
};
"XF86MonBrightnessUp" = {
action.spawn = [ "brightnessctl" "set" "+5%" ];
allow-when-locked = true;
};
"XF86MonBrightnessDown" = {
action.spawn = [ "brightnessctl" "set" "5%-" ];
allow-when-locked = true;
};
"XF86AudioPlay" = {
action.spawn = [ "playerctl" "play-pause" ];
allow-when-locked = true;
};
"XF86AudioNext" = {
action.spawn = [ "playerctl" "next" ];
allow-when-locked = true;
};
"XF86AudioPrev" = {
action.spawn = [ "playerctl" "previous" ];
allow-when-locked = true;
};
# screenshot
"Print".action.screenshot = { };
"Shift+Print".action.screenshot-screen = { };
# session
"Mod+Shift+E".action.quit = { };
"Mod+Shift+P".action.power-off-monitors = [ ];
};
};
};
}

View File

@@ -1,14 +1,5 @@
{ {
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;
}; };
};
} }

View File

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

View File

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

View File

@@ -1,50 +0,0 @@
{
nixos =
{ config, lib, ... }:
let
cfg = config.features.power;
in
{
options.features.power = {
enable = lib.mkEnableOption "laptop power management";
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 {
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,13 +1,5 @@
{ {
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;
@@ -15,5 +7,4 @@
openFirewall = true; openFirewall = true;
}; };
}; };
};
} }

View File

@@ -1,18 +1,7 @@
{ {
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;
@@ -21,5 +10,4 @@
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,23 +1,16 @@
{ {
nixos = nixos = _: {
{ lib, ... }: programs.zsh.enable = true;
{ environment.etc."zshenv".text = ''
options.features.shell.enable = lib.mkEnableOption "shell extras"; export ZDOTDIR=$HOME/.config/zsh
'';
}; };
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
@@ -29,5 +22,4 @@
tmux tmux
]; ];
}; };
};
} }

10
features/steam.nix Normal file
View File

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

View File

@@ -1,39 +1,7 @@
{ {
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;
@@ -41,11 +9,11 @@
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
''; '';
}; };
xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-wlr ];
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
waybar waybar
mako mako
@@ -68,33 +36,5 @@
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,17 +1,8 @@
{ {
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";
}; };
}; };
};
} }

View File

@@ -1,59 +0,0 @@
{
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,11 +9,16 @@ 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;
}; };
@@ -25,6 +30,6 @@ in
}; };
home = _: { home = _: {
home.stateVersion = "26.05"; home.stateVersion = "24.11";
}; };
} }

View File

@@ -0,0 +1,72 @@
{
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,52 +6,28 @@
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.features.vm-guest = { options = {
enable = lib.mkEnableOption "qemu vm guest"; vm-guest.headless = lib.mkOption {
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;
}; };
prefix = lib.mkOption { config = {
type = lib.types.str; services.qemuGuest.enable = true;
default = "m_"; services.spice-vdagentd.enable = lib.mkIf (!config.vm-guest.headless) true;
};
basePath = lib.mkOption { boot.kernelParams = lib.mkIf config.vm-guest.headless [ "console=ttyS0,115200" ];
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;
@@ -64,49 +40,8 @@
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
'';
}; };
}; };
})
]
);
};
} }

12
features/yubikey.nix Normal file
View File

@@ -0,0 +1,12 @@
{
nixos =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
yubikey-personalization
yubikey-manager
];
services.pcscd.enable = true;
};
}

View File

@@ -1,55 +0,0 @@
{
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 ];
};
};
}

330
flake.lock generated
View File

@@ -54,11 +54,11 @@
"base16-helix": { "base16-helix": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1776754714, "lastModified": 1760703920,
"narHash": "sha256-E3OAK27smtATTmX45uoTSRsVD+Y+ZiVVfgM/tjpbtYg=", "narHash": "sha256-m82fGUYns4uHd+ZTdoLX2vlHikzwzdu2s2rYM2bNwzw=",
"owner": "tinted-theming", "owner": "tinted-theming",
"repo": "base16-helix", "repo": "base16-helix",
"rev": "4d508123037e7851ad36ebf7d9c48b0e9e1eb581", "rev": "d646af9b7d14bff08824538164af99d0c521b185",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -106,11 +106,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1777713215, "lastModified": 1773889306,
"narHash": "sha256-8GzXDOXckDWwST8TY5DbwYFjdvQLlP7K9CLSVx6iTTo=", "narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=",
"owner": "nix-community", "owner": "nix-community",
"repo": "disko", "repo": "disko",
"rev": "63b4e7e6cf75307c1d26ac3762b886b5b0247267", "rev": "5ad85c82cc52264f4beddc934ba57f3789f28347",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -122,11 +122,11 @@
"firefox-gnome-theme": { "firefox-gnome-theme": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1776136500, "lastModified": 1775176642,
"narHash": "sha256-r0gN2brVWA351zwMV0Flmlcd6SGMvYqFbvC3DfKFM8Y=", "narHash": "sha256-2veEED0Fg7Fsh81tvVDNYR6SzjqQxa7hbi18Jv4LWpM=",
"owner": "rafaelmardojai", "owner": "rafaelmardojai",
"repo": "firefox-gnome-theme", "repo": "firefox-gnome-theme",
"rev": "0f8ba203d475587f477e7ae12661bd8459e225b7", "rev": "179704030c5286c729b5b0522037d1d51341022c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -156,11 +156,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1777988971, "lastModified": 1775087534,
"narHash": "sha256-qIoWPDs+0/8JecyYgE3gpKQxW/4bLW/gp45vow9ioCQ=", "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "0678d8986be1661af6bb555f3489f2fdfc31f6ff", "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -177,11 +177,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1777988971, "lastModified": 1772408722,
"narHash": "sha256-qIoWPDs+0/8JecyYgE3gpKQxW/4bLW/gp45vow9ioCQ=", "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "0678d8986be1661af6bb555f3489f2fdfc31f6ff", "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -273,11 +273,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1778248595, "lastModified": 1775320414,
"narHash": "sha256-dhFgEjoeJMYN/7OY6xfxS799YB4IjbbYXTjyGIJyLpc=", "narHash": "sha256-pIDPHus8udcxO4lT+zUULBfvue2D08E73abzVEJNE+8=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "fdb2ccba9d5e1238d32e0c4a3ec1a277efa80c1d", "rev": "5ee3b3ef63e469c84639c2c9e282726352c86069",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -317,11 +317,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1778285091, "lastModified": 1775138742,
"narHash": "sha256-4YwkGkjvLD0EB7rQGCRA9J/zgwrnTL20dJd7Wmnicj0=", "narHash": "sha256-cyipL9p0VzyZT0wa+3KkeJYvtEYzyI1PjBOnGU8z3gI=",
"owner": "nix-community", "owner": "nix-community",
"repo": "neovim-nightly-overlay", "repo": "neovim-nightly-overlay",
"rev": "cca2a2d1c03f763fdcd7066791363d792313c641", "rev": "0fff256cfbc6978e711c43555df210d46940e943",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -333,11 +333,11 @@
"neovim-src": { "neovim-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1778266020, "lastModified": 1774915197,
"narHash": "sha256-qoydKalrn/QGsGYVRicz0Hzb7bfGmV7Z9CnVONXN/Lc=", "narHash": "sha256-yor+eo8CVi7wBp7CjAMQnVoK+m197gsl7MvUzaqicns=",
"owner": "neovim", "owner": "neovim",
"repo": "neovim", "repo": "neovim",
"rev": "b7d8a41d91dcfebe9a5f3d0cf2f0bb0b8d59e32e", "rev": "dbc4800dda2b0dc3290dc79955f857256e0694e2",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -346,13 +346,69 @@
"type": "github" "type": "github"
} }
}, },
"niri": {
"inputs": {
"niri-stable": "niri-stable",
"niri-unstable": "niri-unstable",
"nixpkgs": "nixpkgs_2",
"nixpkgs-stable": "nixpkgs-stable",
"xwayland-satellite-stable": "xwayland-satellite-stable",
"xwayland-satellite-unstable": "xwayland-satellite-unstable"
},
"locked": {
"lastModified": 1775089852,
"narHash": "sha256-zcX3hf2cTOQ06kRP+FCYbuaWhy+FLruLFp+D79Enjo4=",
"owner": "sodiboo",
"repo": "niri-flake",
"rev": "cfa91d98691d91cc604ab8f4e224819524c496ad",
"type": "github"
},
"original": {
"owner": "sodiboo",
"repo": "niri-flake",
"type": "github"
}
},
"niri-stable": {
"flake": false,
"locked": {
"lastModified": 1756556321,
"narHash": "sha256-RLD89dfjN0RVO86C/Mot0T7aduCygPGaYbog566F0Qo=",
"owner": "YaLTeR",
"repo": "niri",
"rev": "01be0e65f4eb91a9cd624ac0b76aaeab765c7294",
"type": "github"
},
"original": {
"owner": "YaLTeR",
"ref": "v25.08",
"repo": "niri",
"type": "github"
}
},
"niri-unstable": {
"flake": false,
"locked": {
"lastModified": 1774616418,
"narHash": "sha256-z+dLkAS4bqytIlOI4h2MnjBJrSP4d1Awx0n+IV5YA3Y=",
"owner": "YaLTeR",
"repo": "niri",
"rev": "8f48f56fe19918b5cfa02e5d68a47ebaf7bf3dee",
"type": "github"
},
"original": {
"owner": "YaLTeR",
"repo": "niri",
"type": "github"
}
},
"nixos-hardware": { "nixos-hardware": {
"locked": { "locked": {
"lastModified": 1778143761, "lastModified": 1775203647,
"narHash": "sha256-lkesY6x2X2qxlqLM7CT2iM/0rP2JB7fruPN3h8POXmI=", "narHash": "sha256-6MWaMLXK9QMndI94CIxeiPafi3wuO+imCtK9tfhsZdw=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixos-hardware", "repo": "nixos-hardware",
"rev": "3bcaa367d4c550d687a17ac792fd5cda214ee871", "rev": "80afbd13eea0b7c4ac188de949e1711b31c2b5f0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -364,11 +420,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1778124196, "lastModified": 1774701658,
"narHash": "sha256-pYEytCNic/czazbV9r3tbQ6BZzqRBg/41x2dIC5ymOo=", "narHash": "sha256-CIS/4AMUSwUyC8X5g+5JsMRvIUL3YUfewe8K4VrbsSQ=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "68a8af93ff4297686cb68880845e61e5e2e41d92", "rev": "b63fe7f000adcfa269967eeff72c64cafecbbebe",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -380,11 +436,11 @@
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1777168982, "lastModified": 1774748309,
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=", "narHash": "sha256-+U7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixpkgs.lib", "repo": "nixpkgs.lib",
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14", "rev": "333c4e0545a6da976206c74db8773a1645b5870a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -395,11 +451,11 @@
}, },
"nixpkgs-master": { "nixpkgs-master": {
"locked": { "locked": {
"lastModified": 1778360830, "lastModified": 1775331405,
"narHash": "sha256-tD44tgf123UcERx3cC91rwefFmGmlTd2M1QdL6d5iLc=", "narHash": "sha256-S3MglA43hvGCKybF9qlhMr7wSwrpI5rlWhPz3nhj68Y=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "82cbc979e10cf2b893566a0f259daf5e1f26c887", "rev": "0ea469d0e38c16acf20acd24ce8285f005441c42",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -411,11 +467,27 @@
}, },
"nixpkgs-stable": { "nixpkgs-stable": {
"locked": { "locked": {
"lastModified": 1778003029, "lastModified": 1775002709,
"narHash": "sha256-q/nkKLDtHIyLjZpKhWk3cSK5IYsFqtMd6UtXF3ddjgA=", "narHash": "sha256-d3Yx83vSrN+2z/loBh4mJpyRqr9aAJqlke4TkpFmRJA=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "0c88e1f2bdb93d5999019e99cb0e61e1fe2af4c5", "rev": "bcd464ccd2a1a7cd09aa2f8d4ffba83b761b1d0e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable_2": {
"locked": {
"lastModified": 1775002709,
"narHash": "sha256-d3Yx83vSrN+2z/loBh4mJpyRqr9aAJqlke4TkpFmRJA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "bcd464ccd2a1a7cd09aa2f8d4ffba83b761b1d0e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -427,11 +499,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1777954456, "lastModified": 1775036866,
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=", "narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1", "rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -441,6 +513,66 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_3": {
"locked": {
"lastModified": 1775036866,
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"noctalia": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"noctalia-qs": "noctalia-qs"
},
"locked": {
"lastModified": 1775330311,
"narHash": "sha256-/Ig/jRYPj/MY+aEgE344UTd3xGyPRTbigVAYE41Njis=",
"owner": "noctalia-dev",
"repo": "noctalia-shell",
"rev": "f02ecc9acd4bb53b5c035159176deb5a1b18e2c5",
"type": "github"
},
"original": {
"owner": "noctalia-dev",
"repo": "noctalia-shell",
"type": "github"
}
},
"noctalia-qs": {
"inputs": {
"nixpkgs": [
"noctalia",
"nixpkgs"
],
"systems": "systems",
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1775135550,
"narHash": "sha256-79JP2QTdvp1jg7HGxAW+xzhzhLnlKUi8yGXq9nDCeH0=",
"owner": "noctalia-dev",
"repo": "noctalia-qs",
"rev": "e7224b756dcd10eec040df818a4c7a0fda5d6eff",
"type": "github"
},
"original": {
"owner": "noctalia-dev",
"repo": "noctalia-qs",
"type": "github"
}
},
"nur": { "nur": {
"inputs": { "inputs": {
"flake-parts": [ "flake-parts": [
@@ -453,11 +585,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1777598946, "lastModified": 1775228139,
"narHash": "sha256-X239dAGaU1+gfDj8jKH8GzlqKMcxaVfXOio+uzBOkeE=", "narHash": "sha256-ebbeHmg+V7w8050bwQOuhmQHoLOEOfqKzM1KgCTexK4=",
"owner": "nix-community", "owner": "nix-community",
"repo": "NUR", "repo": "NUR",
"rev": "5d55af01c0f86be583931fe99207fc56c14134b3", "rev": "601971b9c89e0304561977f2c28fa25e73aa7132",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -513,10 +645,12 @@
"home-manager": "home-manager", "home-manager": "home-manager",
"lanzaboote": "lanzaboote", "lanzaboote": "lanzaboote",
"neovim-nightly-overlay": "neovim-nightly-overlay", "neovim-nightly-overlay": "neovim-nightly-overlay",
"niri": "niri",
"nixos-hardware": "nixos-hardware", "nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_2", "nixpkgs": "nixpkgs_3",
"nixpkgs-master": "nixpkgs-master", "nixpkgs-master": "nixpkgs-master",
"nixpkgs-stable": "nixpkgs-stable", "nixpkgs-stable": "nixpkgs-stable_2",
"noctalia": "noctalia",
"nvim": "nvim", "nvim": "nvim",
"sops-nix": "sops-nix", "sops-nix": "sops-nix",
"stylix": "stylix" "stylix": "stylix"
@@ -550,11 +684,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1777944972, "lastModified": 1775188331,
"narHash": "sha256-VfGRo1qTBKOe3s2gOv8LSoA6Fk19PvBlwQ1ECN0Evn8=", "narHash": "sha256-/0BoSi0Dg0ON7IW0oscM12WSPBaMSCn36XTt0lHZoy8=",
"owner": "Mic92", "owner": "Mic92",
"repo": "sops-nix", "repo": "sops-nix",
"rev": "c591bf665727040c6cc5cb409079acb22dcce33c", "rev": "8f093d0d2f08f37317778bd94db5951d6cce6c46",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -576,18 +710,18 @@
"nixpkgs" "nixpkgs"
], ],
"nur": "nur", "nur": "nur",
"systems": "systems", "systems": "systems_2",
"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": 1778104276, "lastModified": 1775247334,
"narHash": "sha256-/DSSnU0LLmOTG/OCgGwYpxP6+5YvxRx2g/GhI4x6aCU=", "narHash": "sha256-eVKt8wpQqg6Hq/UdHQkV1izXGloGQxdlE4SSk9/X27s=",
"owner": "danth", "owner": "danth",
"repo": "stylix", "repo": "stylix",
"rev": "18ed8d270231e067fe2739998479ed5d7c659c2c", "rev": "6d0502ef7447090abf8b00362b5cda8ac64595b4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -597,6 +731,21 @@
} }
}, },
"systems": { "systems": {
"locked": {
"lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
"owner": "nix-systems",
"repo": "default-linux",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default-linux",
"type": "github"
}
},
"systems_2": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
@@ -630,11 +779,11 @@
"tinted-schemes": { "tinted-schemes": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1777041405, "lastModified": 1772661346,
"narHash": "sha256-BAGZ7ObFV/9Z61OJZun7ifPyhkuHqNuW1QIhQ8LuzCo=", "narHash": "sha256-4eu3LqB9tPqe0Vaqxd4wkZiBbthLbpb7llcoE/p5HT0=",
"owner": "tinted-theming", "owner": "tinted-theming",
"repo": "schemes", "repo": "schemes",
"rev": "5f868b3a338b6904c47f3833b9c411be641983a8", "rev": "13b5b0c299982bb361039601e2d72587d6846294",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -646,11 +795,11 @@
"tinted-tmux": { "tinted-tmux": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1777169200, "lastModified": 1772934010,
"narHash": "sha256-h7dDbIzP5hDr9v97w9PL6jdAgXawmj6krcH+959rqpU=", "narHash": "sha256-x+6+4UvaG+RBRQ6UaX+o6DjEg28u4eqhVRM9kpgJGjQ=",
"owner": "tinted-theming", "owner": "tinted-theming",
"repo": "tinted-tmux", "repo": "tinted-tmux",
"rev": "f798c2dce44ef815bb6b8f05a82135c7942d35ac", "rev": "c3529673a5ab6e1b6830f618c45d9ce1bcdd829d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -662,11 +811,11 @@
"tinted-zed": { "tinted-zed": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1777463218, "lastModified": 1772909925,
"narHash": "sha256-Bhkozqtq3BKLqWTlmKm8uAptfX4aRGI8QX3eEL54Vpc=", "narHash": "sha256-jx/5+pgYR0noHa3hk2esin18VMbnPSvWPL5bBjfTIAU=",
"owner": "tinted-theming", "owner": "tinted-theming",
"repo": "base16-zed", "repo": "base16-zed",
"rev": "5768d08ed2e7944a26a958868cdb073cb8856dae", "rev": "b4d3a1b3bcbd090937ef609a0a3b37237af974df",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -674,6 +823,61 @@
"repo": "base16-zed", "repo": "base16-zed",
"type": "github" "type": "github"
} }
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"noctalia",
"noctalia-qs",
"nixpkgs"
]
},
"locked": {
"lastModified": 1773297127,
"narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "71b125cd05fbfd78cab3e070b73544abe24c5016",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
},
"xwayland-satellite-stable": {
"flake": false,
"locked": {
"lastModified": 1755491097,
"narHash": "sha256-m+9tUfsmBeF2Gn4HWa6vSITZ4Gz1eA1F5Kh62B0N4oE=",
"owner": "Supreeeme",
"repo": "xwayland-satellite",
"rev": "388d291e82ffbc73be18169d39470f340707edaa",
"type": "github"
},
"original": {
"owner": "Supreeeme",
"ref": "v0.7",
"repo": "xwayland-satellite",
"type": "github"
}
},
"xwayland-satellite-unstable": {
"flake": false,
"locked": {
"lastModified": 1773622265,
"narHash": "sha256-wToKwH7IgWdGLMSIWksEDs4eumR6UbbsuPQ42r0oTXQ=",
"owner": "Supreeeme",
"repo": "xwayland-satellite",
"rev": "a879e5e0896a326adc79c474bf457b8b99011027",
"type": "github"
},
"original": {
"owner": "Supreeeme",
"repo": "xwayland-satellite",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View File

@@ -47,6 +47,13 @@
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
niri.url = "github:sodiboo/niri-flake";
noctalia = {
url = "github:noctalia-dev/noctalia-shell";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = outputs =

View File

@@ -8,7 +8,6 @@ let
pkgs.zlib pkgs.zlib
pkgs.openssl pkgs.openssl
pkgs.curl pkgs.curl
pkgs.libffi
]; ];
mkNode = nodejs: { mkNode = nodejs: {
@@ -25,12 +24,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
@@ -45,10 +44,27 @@ 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
@@ -59,14 +75,20 @@ 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: extraPackages: names:
let let
selected = map (n: components.${n}) names; selected = map (n: components.${n}) names;
allPackages = lib.concatMap (c: c.packages or [ ]) selected ++ extraPackages; allPackages = lib.concatMap (c: c.packages or [ ]) selected;
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,29 +15,26 @@ in
system = "x86_64-linux"; system = "x86_64-linux";
user = "matej"; user = "matej";
features = [ features = [
"bootloader"
"claude"
"desktop"
"dev"
"direnv"
"docker"
"gaming"
"git"
"gnupg"
"localisation"
"neovim"
"networkmanager"
"nix-ld"
"nix-settings"
"onepassword"
"openssh" "openssh"
"power" "localisation"
"printing" "gnupg"
"shell" "shell"
"desktop"
"sway" "sway"
"niri"
"greeter"
"printing"
"networkmanager"
"docker"
"tailscale" "tailscale"
"udev" "nix-ld"
"zsh" "yubikey"
"calibre"
"steam"
"direnv"
"neovim"
"dev"
"claude"
]; ];
}; };
@@ -45,29 +42,28 @@ in
system = "x86_64-linux"; system = "x86_64-linux";
user = "matej"; user = "matej";
features = [ features = [
"bootloader"
"claude"
"desktop"
"dev"
"direnv"
"docker"
"gaming"
"git"
"gnupg"
"harmonia"
"localisation"
"neovim"
"networkmanager"
"nix-ld"
"nix-settings"
"onepassword"
"openssh" "openssh"
"printing" "localisation"
"gnupg"
"shell" "shell"
"desktop"
"sway" "sway"
"niri"
"greeter"
"printing"
"networkmanager"
"docker"
"tailscale" "tailscale"
"udev" "nix-ld"
"zsh" "yubikey"
"calibre"
"steam"
"initrd-ssh"
"direnv"
"neovim"
"dev"
"claude"
"harmonia"
]; ];
}; };
@@ -76,9 +72,7 @@ in
system = "x86_64-linux"; system = "x86_64-linux";
user = "matej"; user = "matej";
features = [ features = [
"nix-settings"
"openssh" "openssh"
"zsh"
]; ];
}; };
@@ -86,14 +80,11 @@ in
system = "x86_64-linux"; system = "x86_64-linux";
user = "matej"; user = "matej";
features = [ features = [
"bootloader"
"localisation"
"nix-settings"
"openssh" "openssh"
"remote-base" "localisation"
"shell" "shell"
"tailscale" "tailscale"
"zsh" "remote-base"
]; ];
}; };
@@ -102,31 +93,11 @@ in
system = "x86_64-linux"; system = "x86_64-linux";
user = "matej"; user = "matej";
features = [ features = [
"filedrop"
"localisation"
"nix-settings"
"openssh" "openssh"
"remote-base" "localisation"
"shell" "shell"
"tailscale" "tailscale"
"zsh" "remote-base"
];
};
fortress = mkHost "fortress" {
system = "x86_64-linux";
user = "matej";
features = [
"bootloader"
"desktop"
"gnupg"
"localisation"
"networkmanager"
"nix-settings"
"onepassword"
"sway"
"udev"
"zsh"
]; ];
}; };
@@ -134,18 +105,16 @@ in
system = "x86_64-linux"; system = "x86_64-linux";
user = "matej"; user = "matej";
features = [ features = [
"claude"
"dev"
"docker"
"git"
"gnupg"
"localisation"
"neovim"
"nix-settings"
"openssh" "openssh"
"localisation"
"gnupg"
"shell" "shell"
"vm-guest" "vm-guest"
"zsh" "vm-9p-automount"
"docker"
"neovim"
"claude"
"dev"
]; ];
}; };
}; };

View File

@@ -1,7 +1,19 @@
{ inputs, ... }: { inputs, ... }:
{ {
flake.overlays.default = final: _prev: { flake.overlays.default =
inherit (inputs.nixpkgs-stable.legacyPackages.${final.stdenv.hostPlatform.system}) mcp-nixos; _: 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

@@ -2,6 +2,9 @@
{ {
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";
@@ -29,5 +32,10 @@
}; };
}; };
localisation = {
timeZone = "Europe/Ljubljana";
defaultLocale = "en_US.UTF-8";
};
system.stateVersion = "25.11"; system.stateVersion = "25.11";
} }

View File

@@ -5,7 +5,6 @@
... ...
}: }:
{ {
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;
@@ -13,93 +12,37 @@
documentation.enable = false; documentation.enable = false;
environment.defaultPackages = [ ]; environment.defaultPackages = [ ];
# qcow2, no channel copy; post-processed with parallel zstd on qcow2 v3 # compressed qcow2, no channel copy
# (~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 (
let import (modulesPath + "/../lib/make-disk-image.nix") {
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"; format = "qcow2-compressed";
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
''
); );
}; };
# auto-login on serial console vm-guest.headless = true;
services.getty.autologinUser = "matej";
# enable zsh in home-manager so starship init gets wired up vm-9p-automount.user = "matej";
home-manager.users.matej.programs.zsh = {
enable = true; localisation = {
dotDir = "/home/matej/.config/zsh"; timeZone = "UTC";
shellAliases.dsp = "claude --dangerously-skip-permissions"; defaultLocale = "en_US.UTF-8";
}; };
home-manager.users.matej.programs.starship = { home-manager.users.matej = {
enable = true; neovim.dotfiles = inputs.nvim;
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 0700 matej users -" ]; systemd.tmpfiles.rules = [ "d /home/matej/.config 0755 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,8 +2,6 @@
{ {
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 = {
@@ -28,5 +26,10 @@
}; };
}; };
localisation = {
timeZone = "Europe/Ljubljana";
defaultLocale = "en_US.UTF-8";
};
system.stateVersion = "25.11"; system.stateVersion = "25.11";
} }

View File

@@ -1,79 +0,0 @@
{
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

@@ -1,30 +0,0 @@
{
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,7 +1,8 @@
{ {
lib, lib,
options, pkgs,
inputs, inputs,
options,
... ...
}: }:
@@ -10,20 +11,37 @@
inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series
]; ];
features.bootloader.resumeDevice = "/dev/mapper/vg0-swap"; localisation = {
features.desktop.bluetooth.enable = true; timeZone = "Europe/Ljubljana";
features.gnupg.yubikey.enable = true; defaultLocale = "en_US.UTF-8";
features.udev = {
ledger.enable = true;
keyboard-zsa.enable = true;
}; };
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
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

@@ -37,7 +37,10 @@
fileSystems."/boot" = { fileSystems."/boot" = {
device = "/dev/disk/by-uuid/42D9-FAFD"; device = "/dev/disk/by-uuid/42D9-FAFD";
fsType = "vfat"; fsType = "vfat";
options = [ "umask=0077" ]; options = [
"fmask=0022"
"dmask=0022"
];
}; };
swapDevices = [ swapDevices = [

View File

@@ -1,13 +1,15 @@
{ lib, userKeys, ... }: { lib, ... }:
{ {
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 = userKeys.sshAuthorizedKeys; users.users.root.openssh.authorizedKeys.keys = [
"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,34 +1,38 @@
{ {
config, config,
lib,
inputs,
userKeys, userKeys,
... ...
}: }:
{ {
features.nix-settings.towerCache.enable = false; imports = [
features.bootloader = { inputs.lanzaboote.nixosModules.lanzaboote
mode = "lanzaboote"; ];
initrdSsh = {
enable = true;
networkModule = "r8169";
authorizedKeys = userKeys.sshAuthorizedKeys;
};
};
features.desktop.bluetooth.enable = true;
features.gnupg.yubikey.enable = true;
features.udev = {
ledger.enable = true;
keyboard-zsa.enable = true;
};
# 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" ];
# pairs with bootloader's simpledrm initcall blacklist: amdgpu owns fbcon boot.loader.efi.canTouchEfiVariables = true;
# from the start, no driver-swap mode-set 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

@@ -2,20 +2,31 @@
default: default:
@just --list @just --list
# rebuild the system # rebuild and switch
rebuild op="switch" host=`hostname`: switch config="":
nixos-rebuild {{op}} --flake .#{{host}} --sudo nixos-rebuild switch --flake .{{ if config != "" { "#" + config } else { "" } }} --sudo
# fetch flake inputs
sync:
nix flake prefetch-inputs
# update flake inputs # update flake inputs
update: update:
nix flake update nix flake update
# update flake inputs, rebuild and switch
bump: update switch
# update a package to latest version
update-package pkg:
bash packages/{{pkg}}/update.sh
# update all packages with update scripts # update all packages with update scripts
update-package: update-package-all:
@for script in packages/*/update.sh; do bash "$script"; done @for script in packages/*/update.sh; do bash "$script"; done
# build all packages and hosts # build all packages and hosts
check: build:
nix flake check nix flake check
# build installation iso # build installation iso
@@ -26,6 +37,10 @@ iso:
ephvm *ARGS: ephvm *ARGS:
bash scripts/ephvm-run.sh {{ARGS}} bash scripts/ephvm-run.sh {{ARGS}}
# ssh into running ephemeral VM
ephvm-ssh port="2222":
ssh -p {{port}} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null matej@localhost
# provision a host with nixos-anywhere # provision a host with nixos-anywhere
provision host ip: provision host ip:
#!/usr/bin/env bash #!/usr/bin/env bash
@@ -44,9 +59,9 @@ provision host ip:
ssh root@{{ip}} reboot ssh root@{{ip}} reboot
# deploy config to a remote host # deploy config to a remote host
deploy op="switch" host=`hostname` remote=host: deploy host remote=host:
nixos-rebuild {{op}} --flake .#{{host}} --target-host {{remote}} --sudo --ask-sudo-password nixos-rebuild switch --flake .#{{host}} --target-host {{remote}} --sudo --ask-sudo-password
# garbage collect old generations # garbage collect old generations
clean host=`hostname`: clean:
sudo nix-collect-garbage $(nix eval --raw .#nixosConfigurations.{{host}}.config.nix.gc.options) sudo nix-collect-garbage $(nix eval --raw -f ./nix.nix nix.gc.options)

View File

@@ -27,19 +27,7 @@ let
hostConfig = ../hosts/${name}/configuration.nix; hostConfig = ../hosts/${name}/configuration.nix;
hostHWConfig = ../hosts/${name}/hardware-configuration.nix; hostHWConfig = ../hosts/${name}/hardware-configuration.nix;
# auto-discover all features, excluding user-* and default.nix # load feature with path check
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
@@ -47,7 +35,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 allFeatureNames; loadedFeatures = map loadFeature features;
# load user feature with path check # load user feature with path check
userFeature = userFeature =
@@ -67,38 +55,17 @@ 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; }
# TEMP:(@janezicmatej) temporary mitigation for dirty frag
# blocks esp4/esp6 (CVE-2026-43284) and rxrpc (CVE-2026-43500)
# remove once nixpkgs ships a kernel with f4c50a4034e6 and the rxrpc fix
{
boot.blacklistedKernelModules = [
"esp4"
"esp6"
"rxrpc"
];
}
featureEnableModule
hostConfig hostConfig
] ]
++ lib.optional (builtins.pathExists hostHWConfig) hostHWConfig ++ lib.optional (builtins.pathExists hostHWConfig) hostHWConfig

33
nix.nix Normal file
View File

@@ -0,0 +1,33 @@
{
nix = {
settings = {
experimental-features = [
"nix-command"
"flakes"
];
download-buffer-size = 2 * 1024 * 1024 * 1024;
warn-dirty = false;
substituters = [
"https://cache.nixos.org"
"https://nix-community.cachix.org?priority=45"
"http://tower:5000?priority=50"
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
"matej.nix-1:TdbemLVYblvAxqJcwb3mVKmmr3cfzXbMcZHE5ILnZDE="
];
};
gc = {
automatic = true;
dates = "monthly";
options = "--delete-older-than 30d";
};
optimise = {
automatic = true;
dates = [ "monthly" ];
};
};
}

View File

@@ -1,91 +0,0 @@
{ pkgs, ... }:
let
inherit (pkgs) stdenv lib;
version = "2.1.138";
# 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-MGYEPPO4O84Egb5Ym/9f56l+TzPqogpSabosvHTIJZg=";
};
"aarch64-linux" = {
slug = "linux-arm64";
hash = "sha256-LWBtOAjPDFtLP93TNrsd8bPHJd7VKK6J90CRxUp1/XQ=";
};
"x86_64-darwin" = {
slug = "darwin-x64";
hash = "sha256-tkupKzb+XAPmdCRNoT90cfVLKUar3FCTRgufiMVuVPc=";
};
"aarch64-darwin" = {
slug = "darwin-arm64";
hash = "sha256-jmB4t11BI1LKanuuXRJv5IBe8a9gSrFvTMP3KarsioU=";
};
};
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;
};
}

View File

@@ -1,53 +0,0 @@
#!/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,44 +27,23 @@ 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)
--no-claude Skip mounting claude config dir --claude Mount claude config dir (requires 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: 4G) --memory <size> VM memory (default: 8G)
--cpus <n> VM CPUs (default: 2) --cpus <n> VM CPUs (default: 4)
--ssh-port <port> Use specific SSH port (default: auto) --ssh-port <port> SSH port forward (default: 2222)
--serial Attach to serial console instead of SSH
-h, --help Show usage -h, --help Show usage
EOF EOF
exit "${1:-0}" exit "${1:-0}"
@@ -73,9 +52,7 @@ EOF
main() { main() {
setup_colors setup_colors
[ "$EUID" -eq 0 ] && die "ephvm-run.sh must not run as root" local ssh_port=2222 memory=8G cpus=4 claude=false disk_size=""
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
@@ -84,8 +61,8 @@ main() {
mounts+=("$2") mounts+=("$2")
shift 2 shift 2
;; ;;
--no-claude) --claude)
claude=false claude=true
shift shift
;; ;;
--disk-size) --disk-size)
@@ -104,10 +81,6 @@ 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
@@ -118,9 +91,7 @@ main() {
info "building ephvm image..." info "building ephvm image..."
local image_dir image local image_dir image
[ -n "${EPHVM_FLAKE:-}" ] || die "EPHVM_FLAKE must be set to the flake directory" image_dir=$(nix build --no-link --print-out-paths .#nixosConfigurations.ephvm.config.system.build.images.qemu)
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"
@@ -130,49 +101,31 @@ 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="if=none,id=hd0,file=$overlay,format=qcow2,cache=writeback,aio=threads,discard=unmap,detect-zeroes=unmap" drive_arg="file=$overlay,format=qcow2"
else else
drive_arg="if=none,id=hd0,file=$image,format=qcow2,snapshot=on,cache=writeback,aio=threads,discard=unmap,detect-zeroes=unmap" drive_arg="file=$image,format=qcow2,snapshot=on"
fi fi
command -v qemu-system-x86_64 &>/dev/null || die "qemu-system-x86_64 not found" local accel="tcg"
[ -r /dev/kvm ] || die "/dev/kvm not readable; kvm is required" [ -r /dev/kvm ] && accel="kvm"
# 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 kvm -accel "$accel"
-cpu host
-m "$memory" -m "$memory"
-smp "$cpus" -smp "$cpus"
-drive "$drive_arg" -drive "$drive_arg"
-device "virtio-blk-pci,drive=hd0" -nic "user,hostfwd=tcp::${ssh_port}-:22"
-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+=(
@@ -182,13 +135,10 @@ main() {
done done
if [ "$claude" = true ]; then if [ "$claude" = true ]; then
[ -n "${CLAUDE_CONFIG_DIR:-}" ] || die "CLAUDE_CONFIG_DIR must be set (use --no-claude to skip)" [ -n "${CLAUDE_CONFIG_DIR:-}" ] || die "--claude requires CLAUDE_CONFIG_DIR to be set"
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}"
@@ -197,42 +147,10 @@ main() {
fi fi
info "---" info "---"
[ -n "$ssh_port" ] && info "SSH: ssh -p $ssh_port matej@localhost" info "Accel: $accel | 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 "$@"

View File

@@ -1,28 +0,0 @@
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