Compare commits

..

4 Commits

55 changed files with 1094 additions and 1968 deletions

View File

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

View File

@@ -1,73 +0,0 @@
{
nixos =
{
config,
lib,
inputs,
...
}:
let
cfg = config.features.bootloader;
in
{
imports = [ inputs.lanzaboote.nixosModules.lanzaboote ];
options.features.bootloader = {
enable = lib.mkEnableOption "bootloader";
mode = lib.mkOption {
type = lib.types.enum [
"systemd-boot"
"lanzaboote"
];
default = "systemd-boot";
};
plymouth.enable = lib.mkEnableOption "plymouth boot splash";
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
boot.loader.efi.canTouchEfiVariables = true;
# request the largest framebuffer uefi offers; plymouth inherits it
boot.loader.systemd-boot.consoleMode = "max";
}
(lib.mkIf (cfg.mode == "systemd-boot") {
boot.loader.systemd-boot.enable = true;
})
(lib.mkIf (cfg.mode == "lanzaboote") {
boot.loader.systemd-boot.enable = lib.mkForce false;
boot.lanzaboote = {
enable = true;
pkiBundle = "/var/lib/sbctl";
};
})
(lib.mkIf cfg.plymouth.enable {
# plymouth needs systemd-initrd to render the luks prompt cleanly
boot.initrd.systemd.enable = true;
# host is responsible for early-KMS so plymouth lands on the gpu driver,
# not simpledrm (e.g. hardware.amdgpu.initrd.enable on amd hosts)
boot.plymouth.enable = true;
stylix.targets.plymouth.logoAnimated = false;
boot.kernelParams = [
"quiet"
"splash"
"loglevel=3"
"rd.systemd.show_status=false"
"rd.udev.log_level=3"
"udev.log_priority=3"
"plymouth.force-scale=1"
];
boot.consoleLogLevel = 0;
boot.initrd.verbose = false;
})
]
);
};
}

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 =
{ 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 = [
packages.claude-code
pkgs.claude-code
pkgs.mcp-nixos
];
};
};
}

View File

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

View File

@@ -1,18 +1,7 @@
{
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 = {
from = {
type = "indirect";
@@ -24,15 +13,8 @@
};
};
};
};
home =
{ lib, osConfig, ... }:
let
cfg = osConfig.features.direnv;
in
{
config = lib.mkIf cfg.enable {
home = _: {
programs.direnv = {
enable = true;
nix-direnv.enable = true;
@@ -41,5 +23,4 @@
xdg.configFile."direnv/lib/use_dev.sh".source = ./use_dev.sh;
};
};
}

View File

@@ -1,28 +1,16 @@
# shellcheck shell=bash
# 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
use_dev() {
local nix_list="" extra_list="" in_extra=0
local nix_list=""
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
log_error "use_dev: invalid component name: $c"
return 1
fi
nix_list="$nix_list \"$c\""
fi
done
local system
@@ -76,7 +64,7 @@ use_dev() {
pkgs = nixpkgs.legacyPackages.\${system};
devLib = import "\${dev}/flake/dev-components.nix" { inherit pkgs; lib = nixpkgs.lib; };
in {
devShells.\${system}.default = devLib.mkComponentShell [$nix_list ] [${extra_list} ];
devShells.\${system}.default = devLib.mkComponentShell [$nix_list ];
};
}
DEVFLAKE

View File

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

View File

@@ -2,7 +2,6 @@
nixos =
{ lib, config, ... }:
let
cfg = config.features.initrd-ssh;
keyDir = "/etc/secrets/initrd";
mkIpString =
@@ -16,9 +15,8 @@
"${address}::${gateway}:${netmask}::${interface}:none";
in
{
options.features.initrd-ssh = {
enable = lib.mkEnableOption "initrd ssh";
options = {
initrd-ssh = {
ip = {
enable = lib.mkEnableOption "static IP for initrd (otherwise DHCP)";
@@ -49,19 +47,14 @@
type = lib.types.str;
};
};
};
config = lib.mkIf cfg.enable {
boot.initrd.availableKernelModules = [ cfg.networkModule ];
boot.initrd.kernelModules = [ cfg.networkModule ];
boot.kernelParams = lib.mkIf cfg.ip.enable [
"ip=${mkIpString cfg.ip}"
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.systemd.enable = true;
# remote unlock may take a while; don't let device units give up
boot.initrd.systemd.settings.Manager.DefaultDeviceTimeoutSec = "infinity";
boot.initrd.network = {
enable = true;
ssh = {
@@ -71,20 +64,12 @@
"${keyDir}/ssh_host_rsa_key"
"${keyDir}/ssh_host_ed25519_key"
];
inherit (cfg) authorizedKeys;
inherit (config.initrd-ssh) authorizedKeys;
};
postCommands = ''
echo 'cryptsetup-askpass' >> /root/.profile
'';
};
# systemd-networkd retries DHCP indefinitely, unlike udhcpc
boot.initrd.systemd.network.networks = lib.mkIf (!cfg.ip.enable) {
"10-initrd" = {
matchConfig.Driver = cfg.networkModule;
networkConfig.DHCP = "yes";
};
};
# forward LUKS password prompt to the SSH session
boot.initrd.systemd.users.root.shell = "/bin/systemd-tty-ask-password-agent";
};
};
}

View File

@@ -1,30 +1,25 @@
{
nixos =
{ lib, config, ... }:
let
cfg = config.features.localisation;
in
{
options.features.localisation = {
enable = lib.mkEnableOption "localisation";
options = {
localisation = {
timeZone = lib.mkOption {
type = lib.types.str;
default = "Europe/Ljubljana";
};
defaultLocale = lib.mkOption {
type = lib.types.str;
default = "en_US.UTF-8";
};
};
};
config = lib.mkIf cfg.enable {
time.timeZone = cfg.timeZone;
i18n.defaultLocale = cfg.defaultLocale;
config = {
time.timeZone = config.localisation.timeZone;
i18n.defaultLocale = config.localisation.defaultLocale;
# 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 =
{
config,
@@ -19,27 +6,28 @@
lib,
pkgs,
inputs,
osConfig,
...
}:
let
cfg = osConfig.features.neovim;
in
{
config = lib.mkIf cfg.enable (
lib.mkMerge [
options = {
neovim.dotfiles = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
};
};
config = lib.mkMerge [
(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;
})
{
xdg.configFile."nvim" = lib.mkIf (cfg.dotfiles != null) {
source = cfg.dotfiles;
xdg.configFile."nvim" = lib.mkIf (config.neovim.dotfiles != null) {
source = config.neovim.dotfiles;
};
programs.neovim = {
enable = true;
sideloadInitLua = true;
vimAlias = true;
defaultEditor = true;
package = inputs.neovim-nightly-overlay.packages.${pkgs.stdenv.hostPlatform.system}.default;
@@ -76,7 +64,6 @@
];
};
}
]
);
];
};
}

View File

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

View File

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

View File

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

View File

@@ -1,18 +1,7 @@
{
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 = {
sopsFile = ../secrets/common.yaml;
neededForUsers = true;
@@ -21,5 +10,4 @@
users.mutableUsers = false;
users.users.${user}.hashedPasswordFile = config.sops.secrets.user-password.path;
};
};
}

View File

@@ -1,23 +1,16 @@
{
nixos =
{ lib, ... }:
{
options.features.shell.enable = lib.mkEnableOption "shell extras";
nixos = _: {
programs.zsh.enable = true;
environment.etc."zshenv".text = ''
export ZDOTDIR=$HOME/.config/zsh
'';
};
home =
{ pkgs, ... }:
{
pkgs,
lib,
osConfig,
...
}:
let
cfg = osConfig.features.shell;
in
{
config = lib.mkIf cfg.enable {
home.packages = with pkgs; [
starship
fzf
htop
jc
@@ -29,5 +22,4 @@
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 =
{ 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 = {
enable = true;
package = pkgs.swayfx;
@@ -41,11 +9,11 @@
extraSessionCommands = ''
# fix for java awt apps not rendering
export _JAVA_AWT_WM_NONREPARENTING=1
# propagate XDG_DATA_DIRS to dbus/systemd for d-bus activated apps
dbus-update-activation-environment --systemd XDG_DATA_DIRS
'';
};
xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-wlr ];
environment.systemPackages = with pkgs; [
waybar
mako
@@ -68,33 +36,5 @@
cliphist
zenity
];
}
# greeter
(lib.mkIf cfg.greeter.enable {
programs.regreet = {
enable = true;
cageArgs = [
"-s"
"-m"
"last"
];
font = {
name = lib.mkForce "JetBrainsMono Nerd Font";
size = lib.mkForce 14;
};
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 =
{ config, lib, ... }:
let
cfg = config.features.tailscale;
in
{
options.features.tailscale.enable = lib.mkEnableOption "tailscale";
config = lib.mkIf cfg.enable {
nixos = _: {
services.tailscale = {
enable = true;
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;
};
nixos = _: {
nixos =
{ pkgs, ... }:
{
programs.zsh.enable = true;
users.users.matej = {
uid = 1000;
isNormalUser = true;
home = "/home/matej";
shell = pkgs.zsh;
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = sshKeys;
};
@@ -25,6 +30,6 @@ in
};
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,
...
}:
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 = {
enable = lib.mkEnableOption "qemu vm guest";
headless = lib.mkOption {
options = {
vm-guest.headless = lib.mkOption {
type = lib.types.bool;
default = false;
};
automount = {
enable = lib.mkEnableOption "9p share automount";
user = lib.mkOption {
type = lib.types.str;
};
prefix = lib.mkOption {
type = lib.types.str;
default = "m_";
};
config = {
services.qemuGuest.enable = true;
services.spice-vdagentd.enable = lib.mkIf (!config.vm-guest.headless) true;
basePath = lib.mkOption {
type = lib.types.str;
default = "${autoHome}/mnt";
};
};
};
boot.kernelParams = lib.mkIf config.vm-guest.headless [ "console=ttyS0,115200" ];
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 = [
"9p"
"9pnet_virtio"
];
boot.kernelModules = [
"9p"
"9pnet_virtio"
];
networking = {
useDHCP = true;
@@ -64,49 +40,8 @@
curl
wget
htop
sshfs
];
}
(lib.mkIf cfg.automount.enable {
systemd.services.vm-9p-automount = {
description = "Auto-discover and mount 9p shares";
after = [
"local-fs.target"
"nss-user-lookup.target"
"systemd-modules-load.service"
];
wants = [ "systemd-modules-load.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "vm-9p-automount" ''
BASE="${cfg.automount.basePath}"
PREFIX="${cfg.automount.prefix}"
mkdir -p "$BASE"
chown ${autoUser}:${autoGroup} "$BASE"
for tagfile in $(find /sys/devices -name mount_tag 2>/dev/null); do
[ -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 ];
};
};
}

282
flake.lock generated
View File

@@ -106,11 +106,11 @@
]
},
"locked": {
"lastModified": 1776613567,
"narHash": "sha256-gC9Cp5ibBmGD5awCA9z7xy6MW6iJufhazTYJOiGlCUI=",
"lastModified": 1773889306,
"narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=",
"owner": "nix-community",
"repo": "disko",
"rev": "32f4236bfc141ae930b5ba2fb604f561fed5219d",
"rev": "5ad85c82cc52264f4beddc934ba57f3789f28347",
"type": "github"
},
"original": {
@@ -177,11 +177,11 @@
]
},
"locked": {
"lastModified": 1775087534,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"type": "github"
},
"original": {
@@ -273,11 +273,11 @@
]
},
"locked": {
"lastModified": 1776777932,
"narHash": "sha256-0R3Yow/NzSeVGUke5tL7CCkqmss4Vmi6BbV6idHzq/8=",
"lastModified": 1775320414,
"narHash": "sha256-pIDPHus8udcxO4lT+zUULBfvue2D08E73abzVEJNE+8=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "5d5640599a0050b994330328b9fd45709c909720",
"rev": "5ee3b3ef63e469c84639c2c9e282726352c86069",
"type": "github"
},
"original": {
@@ -317,11 +317,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1776729909,
"narHash": "sha256-wGu/N42PJqrj8ju9GoXdppg4rwaKzZqdAjsgxJbCvfY=",
"lastModified": 1775138742,
"narHash": "sha256-cyipL9p0VzyZT0wa+3KkeJYvtEYzyI1PjBOnGU8z3gI=",
"owner": "nix-community",
"repo": "neovim-nightly-overlay",
"rev": "ff21a18bde28b4c8ca0bc1f9a5b7186a1b89a3d1",
"rev": "0fff256cfbc6978e711c43555df210d46940e943",
"type": "github"
},
"original": {
@@ -333,11 +333,11 @@
"neovim-src": {
"flake": false,
"locked": {
"lastModified": 1776727374,
"narHash": "sha256-iP5SviNXW5W+ay4ZmwjDFsfQjfM+fYlUxRlLPHjpwWI=",
"lastModified": 1774915197,
"narHash": "sha256-yor+eo8CVi7wBp7CjAMQnVoK+m197gsl7MvUzaqicns=",
"owner": "neovim",
"repo": "neovim",
"rev": "901b3f0c394a53961781ebeee682e64ad690a242",
"rev": "dbc4800dda2b0dc3290dc79955f857256e0694e2",
"type": "github"
},
"original": {
@@ -346,13 +346,69 @@
"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": {
"locked": {
"lastModified": 1775490113,
"narHash": "sha256-2ZBhDNZZwYkRmefK5XLOusCJHnoeKkoN95hoSGgMxWM=",
"lastModified": 1775203647,
"narHash": "sha256-6MWaMLXK9QMndI94CIxeiPafi3wuO+imCtK9tfhsZdw=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "c775c2772ba56e906cbeb4e0b2db19079ef11ff7",
"rev": "80afbd13eea0b7c4ac188de949e1711b31c2b5f0",
"type": "github"
},
"original": {
@@ -364,11 +420,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1776329215,
"narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=",
"lastModified": 1774701658,
"narHash": "sha256-CIS/4AMUSwUyC8X5g+5JsMRvIUL3YUfewe8K4VrbsSQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b86751bc4085f48661017fa226dee99fab6c651b",
"rev": "b63fe7f000adcfa269967eeff72c64cafecbbebe",
"type": "github"
},
"original": {
@@ -395,11 +451,11 @@
},
"nixpkgs-master": {
"locked": {
"lastModified": 1776807375,
"narHash": "sha256-LDnHG0T54OEHyRydmGUlAND8ham0KrRNWjgoS+6GUd4=",
"lastModified": 1775331405,
"narHash": "sha256-S3MglA43hvGCKybF9qlhMr7wSwrpI5rlWhPz3nhj68Y=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "553ecb1686a2edb75dee44c9f72e1674e6adc26a",
"rev": "0ea469d0e38c16acf20acd24ce8285f005441c42",
"type": "github"
},
"original": {
@@ -411,11 +467,27 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1776560675,
"narHash": "sha256-p68udKWWh7+V4ZPpcMDq0gTHWNZJnr4JPI+kHPPE40o=",
"lastModified": 1775002709,
"narHash": "sha256-d3Yx83vSrN+2z/loBh4mJpyRqr9aAJqlke4TkpFmRJA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e07580dae39738e46609eaab8b154de2488133ce",
"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"
},
"original": {
@@ -427,11 +499,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1776548001,
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
"lastModified": 1775036866,
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
"rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
"type": "github"
},
"original": {
@@ -441,6 +513,66 @@
"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": {
"inputs": {
"flake-parts": [
@@ -513,10 +645,12 @@
"home-manager": "home-manager",
"lanzaboote": "lanzaboote",
"neovim-nightly-overlay": "neovim-nightly-overlay",
"niri": "niri",
"nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_2",
"nixpkgs": "nixpkgs_3",
"nixpkgs-master": "nixpkgs-master",
"nixpkgs-stable": "nixpkgs-stable",
"nixpkgs-stable": "nixpkgs-stable_2",
"noctalia": "noctalia",
"nvim": "nvim",
"sops-nix": "sops-nix",
"stylix": "stylix"
@@ -550,11 +684,11 @@
]
},
"locked": {
"lastModified": 1776771786,
"narHash": "sha256-DRFGPfFV6hbrfO9a1PH1FkCi7qR5FgjSqsQGGvk1rdI=",
"lastModified": 1775188331,
"narHash": "sha256-/0BoSi0Dg0ON7IW0oscM12WSPBaMSCn36XTt0lHZoy8=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "bef289e2248991f7afeb95965c82fbcd8ff72598",
"rev": "8f093d0d2f08f37317778bd94db5951d6cce6c46",
"type": "github"
},
"original": {
@@ -576,18 +710,18 @@
"nixpkgs"
],
"nur": "nur",
"systems": "systems",
"systems": "systems_2",
"tinted-kitty": "tinted-kitty",
"tinted-schemes": "tinted-schemes",
"tinted-tmux": "tinted-tmux",
"tinted-zed": "tinted-zed"
},
"locked": {
"lastModified": 1776170745,
"narHash": "sha256-Tl1aZVP5EIlT+k0+iAKH018GLHJpLz3hhJ0LNQOWxCc=",
"lastModified": 1775247334,
"narHash": "sha256-eVKt8wpQqg6Hq/UdHQkV1izXGloGQxdlE4SSk9/X27s=",
"owner": "danth",
"repo": "stylix",
"rev": "e3861617645a43c9bbefde1aa6ac54dd0a44bfa9",
"rev": "6d0502ef7447090abf8b00362b5cda8ac64595b4",
"type": "github"
},
"original": {
@@ -597,6 +731,21 @@
}
},
"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": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
@@ -674,6 +823,61 @@
"repo": "base16-zed",
"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",

View File

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

View File

@@ -8,7 +8,6 @@ let
pkgs.zlib
pkgs.openssl
pkgs.curl
pkgs.libffi
];
mkNode = nodejs: {
@@ -25,12 +24,12 @@ let
packages = [
python
pkgs.uv
pkgs.pkg-config
];
libraries = pythonLibraries;
env = {
UV_PYTHON_DOWNLOADS = "never";
UV_PYTHON_PREFERENCE = "only-system";
UV_PYTHON = "${python}/bin/python";
};
shellHook = ''
unset PYTHONPATH
@@ -45,10 +44,27 @@ let
uv_13 = mkUv pkgs.python313;
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_22 = mkNode pkgs.nodejs_22;
node_24 = mkNode pkgs.nodejs_24;
go = {
packages = [ pkgs.go ];
};
rust = {
packages = [
pkgs.rustc
@@ -59,14 +75,20 @@ let
];
};
cmake = {
packages = [
pkgs.cmake
pkgs.ninja
];
};
};
# build a single mkShell from one or more component names
mkComponentShell =
names: extraPackages:
names:
let
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;
allHooks = lib.concatMapStrings (c: c.shellHook or "") selected;
allEnvs = lib.foldl' (acc: c: acc // (c.env or { })) { } selected;

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,6 @@
...
}:
{
features.nix-settings.towerCache.enable = false;
# no hardware firmware needed in a VM
hardware.enableRedistributableFirmware = lib.mkForce false;
hardware.wirelessRegulatoryDatabase = lib.mkForce false;
@@ -13,93 +12,37 @@
documentation.enable = false;
environment.defaultPackages = [ ];
# qcow2, no channel copy; post-processed with parallel zstd on qcow2 v3
# (~half the size of zlib v2, faster decompress)
# compressed qcow2, no channel copy
image.modules.qemu =
{ config, modulesPath, ... }:
{
system.build.image = lib.mkForce (
let
rawImage = import (modulesPath + "/../lib/make-disk-image.nix") {
import (modulesPath + "/../lib/make-disk-image.nix") {
inherit lib config pkgs;
inherit (config.virtualisation) diskSize;
inherit (config.image) baseName;
format = "qcow2";
format = "qcow2-compressed";
copyChannel = false;
partitionTableType = "legacy";
};
inherit (config.image) baseName;
in
pkgs.runCommand baseName { nativeBuildInputs = [ pkgs.qemu-utils ]; } ''
mkdir -p $out
# qemu-img caps -m at 16
cores="''${NIX_BUILD_CORES:-4}"
[ "$cores" -gt 0 ] || cores=4
[ "$cores" -gt 16 ] && cores=16
qemu-img convert \
-f qcow2 \
-O qcow2 \
-c \
-o compression_type=zstd \
-m "$cores" \
${rawImage}/${baseName}.qcow2 \
$out/${baseName}.qcow2
''
}
);
};
# auto-login on serial console
services.getty.autologinUser = "matej";
vm-guest.headless = true;
# enable zsh in home-manager so starship init gets wired up
home-manager.users.matej.programs.zsh = {
enable = true;
dotDir = "/home/matej/.config/zsh";
shellAliases.dsp = "claude --dangerously-skip-permissions";
vm-9p-automount.user = "matej";
localisation = {
timeZone = "UTC";
defaultLocale = "en_US.UTF-8";
};
home-manager.users.matej.programs.starship = {
enable = true;
settings = {
add_newline = false;
format = "$username$hostname$directory$character";
hostname = {
ssh_only = false;
style = "bold blue";
format = "[@$hostname]($style)";
home-manager.users.matej = {
neovim.dotfiles = inputs.nvim;
};
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
systemd.tmpfiles.rules = [ "d /home/matej/.config 0700 matej users -" ];
# TODO:(@janezicmatej) replace ssh with virtio-console (hvc0) when qemu 11.0 lands
# https://www.mail-archive.com/qemu-devel@nongnu.org/msg1162844.html
# accept any ssh key (ephemeral localhost-only vm)
services.openssh.settings.AuthorizedKeysCommand =
let
acceptKey = pkgs.writeShellScript "ephvm-accept-key" ''echo "$1 $2"'';
in
"${acceptKey} %t %k";
services.openssh.settings.AuthorizedKeysCommandUser = "nobody";
systemd.tmpfiles.rules = [ "d /home/matej/.config 0755 matej users -" ];
# writable claude config via 9p
fileSystems."/home/matej/.config/claude" = {

View File

@@ -2,8 +2,6 @@
{
imports = [ inputs.disko.nixosModules.disko ];
features.filedrop.sopsFile = ../../secrets/floo.yaml;
boot.loader.grub.enable = true;
disko.devices.disk.main = {
@@ -28,5 +26,10 @@
};
};
localisation = {
timeZone = "Europe/Ljubljana";
defaultLocale = "en_US.UTF-8";
};
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,
options,
pkgs,
inputs,
options,
...
}:
@@ -10,23 +11,37 @@
inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series
];
features.bootloader.plymouth.enable = true;
features.desktop.bluetooth.enable = true;
features.gnupg.yubikey.enable = true;
features.udev = {
ledger.enable = true;
keyboard-zsa.enable = true;
localisation = {
timeZone = "Europe/Ljubljana";
defaultLocale = "en_US.UTF-8";
};
features.power.resumeDevice = "/dev/disk/by-uuid/ff4750e7-3a9f-42c2-bb68-c458a6560540";
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.kernelParams = [ "pcie_aspm.policy=powersupersave" ];
boot.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;
services.gnome.gnome-keyring.enable = true;
services.teamviewer.enable = true;
services.hardware.bolt.enable = true;
hardware.keyboard.zsa.enable = true;
hardware.ledger.enable = true;
hardware.bluetooth.powerOnBoot = true;
hardware.inputmodule.enable = true;

View File

@@ -1,13 +1,15 @@
{ lib, userKeys, ... }:
{ lib, ... }:
{
features.nix-settings.towerCache.enable = false;
image.modules.iso-installer = {
isoImage.squashfsCompression = "zstd -Xcompression-level 6";
};
# live iso: passwordless login and sudo
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";
security.sudo.wheelNeedsPassword = false;

View File

@@ -1,33 +1,38 @@
{
config,
lib,
inputs,
userKeys,
...
}:
{
features.nix-settings.towerCache.enable = false;
features.bootloader = {
mode = "lanzaboote";
plymouth.enable = true;
};
features.desktop.bluetooth.enable = true;
features.gnupg.yubikey.enable = true;
features.udev = {
ledger.enable = true;
keyboard-zsa.enable = true;
};
features.initrd-ssh = {
networkModule = "r8169";
authorizedKeys = userKeys.sshAuthorizedKeys;
};
imports = [
inputs.lanzaboote.nixosModules.lanzaboote
];
# nix store signing
sops.secrets.nix-signing-key.sopsFile = ../../secrets/tower.yaml;
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" ];
# early kms so plymouth lands on amdgpu, not simpledrm
hardware.amdgpu.initrd.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.systemd-boot.enable = lib.mkForce false;
boot.lanzaboote = {
enable = true;
pkiBundle = "/var/lib/sbctl";
};
services.udisks2.enable = true;

View File

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

View File

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

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
CLEANUP_OVERLAY=""
CLEANUP_TMPDIR=""
QEMU_PID=""
VM_READY=false
cleanup() {
[ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null && wait "$QEMU_PID" 2>/dev/null
[ -n "$CLEANUP_OVERLAY" ] && rm -rf "$CLEANUP_OVERLAY"
# preserve tmpdir on abnormal exit so the qemu log survives for inspection
if [ -n "$CLEANUP_TMPDIR" ]; then
if [ "$VM_READY" = true ]; then
rm -rf "$CLEANUP_TMPDIR"
else
echo "qemu log preserved: $CLEANUP_TMPDIR/qemu.log" >&2
fi
fi
return 0
}
trap cleanup EXIT
# returns 0 once the guest's sshd is speaking (first bytes are "SSH-")
awaiting_ssh_banner() {
local port="$1"
local banner
banner=$(timeout 2 bash -c "exec 3<>/dev/tcp/localhost/$port; head -c 4 <&3" 2>/dev/null) || return 1
[ "$banner" = "SSH-" ]
}
usage() {
cat <<EOF
Usage: ephvm-run.sh [options]
Options:
--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)
--memory <size> VM memory (default: 4G)
--cpus <n> VM CPUs (default: 2)
--ssh-port <port> Use specific SSH port (default: auto)
--serial Attach to serial console instead of SSH
--memory <size> VM memory (default: 8G)
--cpus <n> VM CPUs (default: 4)
--ssh-port <port> SSH port forward (default: 2222)
-h, --help Show usage
EOF
exit "${1:-0}"
@@ -73,9 +52,7 @@ EOF
main() {
setup_colors
[ "$EUID" -eq 0 ] && die "ephvm-run.sh must not run as root"
local ssh_port="" memory=4G cpus=2 claude=true disk_size="" serial=false
local ssh_port=2222 memory=8G cpus=4 claude=false disk_size=""
local -a mounts=()
while [ $# -gt 0 ]; do
@@ -84,8 +61,8 @@ main() {
mounts+=("$2")
shift 2
;;
--no-claude)
claude=false
--claude)
claude=true
shift
;;
--disk-size)
@@ -104,10 +81,6 @@ main() {
ssh_port="$2"
shift 2
;;
--serial)
serial=true
shift
;;
-h | --help) usage ;;
*)
echo "${red}error:${reset} unknown option: $1" >&2
@@ -118,9 +91,7 @@ main() {
info "building ephvm image..."
local image_dir image
[ -n "${EPHVM_FLAKE:-}" ] || die "EPHVM_FLAKE must be set to the flake directory"
local flake="$EPHVM_FLAKE"
image_dir=$(nix build --no-link --print-out-paths "${flake}#nixosConfigurations.ephvm.config.system.build.images.qemu")
image_dir=$(nix build --no-link --print-out-paths .#nixosConfigurations.ephvm.config.system.build.images.qemu)
image=$(find "$image_dir" -name '*.qcow2' -print -quit)
[ -n "$image" ] || die "no qcow2 image found in $image_dir"
@@ -130,49 +101,31 @@ main() {
CLEANUP_OVERLAY=$(mktemp -d)
local overlay="$CLEANUP_OVERLAY/overlay.qcow2"
qemu-img create -f qcow2 -b "$(realpath "$image")" -F qcow2 "$overlay" "$disk_size"
drive_arg="if=none,id=hd0,file=$overlay,format=qcow2,cache=writeback,aio=threads,discard=unmap,detect-zeroes=unmap"
drive_arg="file=$overlay,format=qcow2"
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
command -v qemu-system-x86_64 &>/dev/null || die "qemu-system-x86_64 not found"
[ -r /dev/kvm ] || die "/dev/kvm not readable; kvm is required"
# 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 accel="tcg"
[ -r /dev/kvm ] && accel="kvm"
local -a qemu_args=(
qemu-system-x86_64
-accel kvm
-cpu host
-accel "$accel"
-m "$memory"
-smp "$cpus"
-drive "$drive_arg"
-device "virtio-blk-pci,drive=hd0"
-device virtio-rng-pci
-nic "$nic_arg"
-nic "user,hostfwd=tcp::${ssh_port}-:22"
-nographic
-sandbox "on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny"
)
if [ "$accel" != "tcg" ]; then
qemu_args+=(-cpu host)
fi
local fs_id=0 mount_path name tag
for mount_path in "${mounts[@]}"; do
[ -e "$mount_path" ] || die "--mount path does not exist: $mount_path"
mount_path=$(realpath "$mount_path")
# qemu parses -virtfs as csv, a comma in the path would inject options
case "$mount_path" in
*,*) die "--mount path may not contain commas: $mount_path" ;;
esac
name=$(basename "$mount_path")
tag="m_${name:0:29}"
qemu_args+=(
@@ -182,13 +135,10 @@ main() {
done
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"
local claude_dir
claude_dir=$(realpath "$CLAUDE_CONFIG_DIR")
case "$claude_dir" in
*,*) die "claude config dir may not contain commas: $claude_dir" ;;
esac
qemu_args+=(
-virtfs "local,path=$claude_dir,mount_tag=claude,security_model=none,id=fs${fs_id}"
@@ -197,42 +147,10 @@ main() {
fi
info "---"
[ -n "$ssh_port" ] && info "SSH: ssh -p $ssh_port matej@localhost"
info "Accel: $accel | SSH: ssh -p $ssh_port matej@localhost"
info "---"
if [ "$serial" = true ]; then
exec "${qemu_args[@]}"
fi
CLEANUP_TMPDIR=$(mktemp -d)
local qemu_log="$CLEANUP_TMPDIR/qemu.log"
# start qemu in background and auto-ssh
"${qemu_args[@]}" &>"$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 "$@"

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