Compare commits

..

79 Commits

Author SHA1 Message Date
6772afb845 merge: harden ephvm 2026-04-24 14:14:29 +02:00
e9755d41c6 feat: tighten ephvm perms, zstd compress qcow2 2026-04-24 14:13:01 +02:00
68411d9459 feat: prune vm-guest module 2026-04-24 14:12:57 +02:00
7fd5b790ff feat: ephvm-run.sh virtio devices, require kvm 2026-04-24 14:12:52 +02:00
37bca1fdd1 feat: ephvm-run.sh resilience 2026-04-24 14:12:48 +02:00
75ca09949c feat: harden ephvm-run.sh 2026-04-24 14:12:42 +02:00
2fcdee5d81 feat: set XDPW_PERSIST_MODE="permanent" 2026-04-23 23:14:45 +02:00
c01f797e79 chore: bump lockfile 2026-04-22 00:10:04 +02:00
59a2bfa126 chore: update claude-code to v2.1.116 2026-04-22 00:08:31 +02:00
e486bb28b0 feat: enable hM.neovim.sidloadInitLua 2026-04-22 00:06:16 +02:00
d33fd60ce4 feat: switch from vesktop to discord 2026-04-21 23:39:57 +02:00
37428d922b feat: add plymouth option to bootloader 2026-04-21 22:43:13 +02:00
b1cfe1e31b feat: initrd infinite default device timeout 2026-04-21 22:42:33 +02:00
df2bc27f54 chore: blame ignore 77236af589 2026-04-21 22:11:33 +02:00
77236af589 chore: run format 2026-04-21 22:09:39 +02:00
f71d156ea8 feat: enable cache fallback 2026-04-21 10:08:08 +02:00
0c517e0957 chore: bump lockfile 2026-04-20 07:33:32 +02:00
37620c76fe chore: bump claude-code to v2.1.114 2026-04-20 07:32:02 +02:00
ac76b8c842 feat: systemd-networkd during initrd 2026-04-16 23:36:10 +02:00
df7c4cec83 feat: bump claude-code to v2.1.112 2026-04-16 23:10:00 +02:00
5b52e41496 feat: self-package claude-code 2026-04-16 22:59:01 +02:00
a60b40eeac feat: propagate XDG_DATA_DIRS to dbus/systemd 2026-04-15 09:45:21 +02:00
b341f7f4fc feat: setup mime apps 2026-04-15 00:25:07 +02:00
571fb2ff99 chore: blame ignore 2204b12fad 2026-04-13 23:04:46 +02:00
2204b12fad chore: run lint & format 2026-04-13 23:04:16 +02:00
df2ee459a1 feat: improve cache-builder service trigger 2026-04-13 11:53:41 +02:00
f7d86e7718 feat: improve ephvm ux 2026-04-13 11:48:18 +02:00
2e5eb92e32 chore: bump lockfile 2026-04-13 00:22:51 +02:00
f30b03cc04 fix: enable dhcp in initrd if no static ip 2026-04-13 00:20:36 +02:00
c7fb218511 merge: feature improvements 2026-04-12 23:33:33 +02:00
3caace87d3 refactor: update hosts and feature lists for new architecture 2026-04-12 23:33:14 +02:00
8793f97a04 feat: add udev, onepassword, bootloader, power features 2026-04-12 23:33:07 +02:00
b8509196d5 feat: merge greeter into sway, yubikey into gnupg, automount into vm-guest 2026-04-12 23:33:01 +02:00
e1d136bd2f feat: merge desktop + desktop-minimal + calibre with sub-options 2026-04-12 23:32:55 +02:00
898751576d feat: add zsh + git features, redesign shell 2026-04-12 23:32:47 +02:00
6770bc76a2 refactor: mkHost load-all infrastructure and convert simple features 2026-04-12 23:32:28 +02:00
3ff4583017 merge: fortress host 2026-04-10 22:51:26 +02:00
f85980190e feat: prepare initial fortress host 2026-04-10 16:40:55 +02:00
8e5557921d feat: prepare minimal features for fortress 2026-04-10 16:05:34 +02:00
216328927d feat: improve dev-components with --extra 2026-04-09 17:05:33 +02:00
86e8fe7397 feat: filedrop via sftp 2026-04-09 13:44:28 +02:00
0fa91d4f40 feat: migrate home-manager to 26.05 2026-04-07 13:54:50 +02:00
07334db7ee chore: bump lockfile 2026-04-07 13:54:44 +02:00
a08f824d0e feat: rename steam to gaming and add prismlauncher 2026-04-07 13:28:51 +02:00
dd4fc4eff3 feat: switch to unstable channel 2026-04-05 14:43:26 +02:00
89e36c5096 chore: bump lockfile 2026-04-02 12:56:34 +02:00
c29f4d0624 merge: harmonia 2026-04-02 00:12:30 +02:00
cffd90db69 feat: add harmonia and nix-community substituters 2026-04-02 00:12:06 +02:00
7faddd053b feat: add harmonia cache server 2026-04-02 00:11:51 +02:00
71cd268f79 feat: restructure dev-components/dev-registry with direnv 2026-04-01 22:50:00 +02:00
5a37795151 feat: disable mutableUsers for remote hosts 2026-03-30 01:39:28 +02:00
36106c37fd feat: allow root ssh login for iso 2026-03-30 01:31:16 +02:00
3b80f90b99 feat: improve just provison to wait for reboot 2026-03-30 01:29:24 +02:00
35d0db6bf0 merge: cube host 2026-03-30 01:29:04 +02:00
cba2f63f01 feat: add cube hardware configuration 2026-03-30 01:28:49 +02:00
4f901d4367 feat: wire up sops for cube and reencrypt secrets 2026-03-30 01:25:43 +02:00
7d18c2713f feat: prepare initial cube host 2026-03-30 01:22:36 +02:00
18105107a6 feat: pregenerate ssh-keys and wire up sops in just provision 2026-03-30 01:22:31 +02:00
36630d98a3 feat: add remote-base with default user password via sops 2026-03-30 01:11:50 +02:00
27b7b2abf2 feat: add nix store signing for remote deploys 2026-03-30 00:21:37 +02:00
29053f4ec2 merge: sops-nix setup 2026-03-29 23:27:42 +02:00
b50c574342 feat: add initial sops config 2026-03-29 23:27:19 +02:00
666f7f35a6 feat: add sops and ssh-to-age to devshell 2026-03-29 23:12:43 +02:00
50533cc737 feat: wire sops into mkHost 2026-03-29 23:12:11 +02:00
42c2a1604c feat: add sops-nix flake input 2026-03-29 23:09:15 +02:00
e696417e63 chore: bump lockfile 2026-03-29 20:58:56 +02:00
94198293ff merge: floo host 2026-03-28 00:50:52 +01:00
16eb3dcf00 feat: add floo hardware configuration 2026-03-28 00:50:24 +01:00
21a757232d feat: add provision and deploy just recipes 2026-03-28 00:49:57 +01:00
99fea4e9db feat: prepare initial floo host 2026-03-28 00:49:37 +01:00
dc2e7bf1aa feat: add disko input 2026-03-27 23:11:27 +01:00
baeb10a48e chore: use features in iso 2026-03-27 22:12:19 +01:00
315de3696a fix: ephvm setup 2026-03-27 22:11:50 +01:00
38cdcebddd feat: dev-registry for dev environments 2026-03-27 15:05:09 +01:00
c40fbaec57 feat: enable nix-ld and steam for tower 2026-03-26 23:37:03 +01:00
4b43196c0e merge: dendritic pattern with flake-parts 2026-03-26 23:34:45 +01:00
404b6431ce feat: update flake and hosts for new structure 2026-03-26 23:23:52 +01:00
8c6fefb95b feat: migrate from modules to features 2026-03-26 23:10:56 +01:00
76e74f4939 feat: switch from flake-utils to flake-parts 2026-03-26 22:35:23 +01:00
89 changed files with 2982 additions and 1331 deletions

View File

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

36
.sops.yaml Normal file
View File

@@ -0,0 +1,36 @@
keys:
- &matej AF349EECC849D87B790E88FF6318FFB7DB374B7D
# host age keys (via: ssh-keyscan <host> | ssh-to-age)
- &tower age1frwe9fpt9vh969aqnggvq8pfypp6hl98guwfmgttucp7gr55r42sqy2t65
- &fw16 age19qj2aaryx869cvcqp77gs9x5hcv4dqjxunkmyre78upsxda6ss7s5vquz4
- &floo age1hksdq2lc89thnpth49sw44f0pmkp950plrhhnttj4petvnfy04tsydz6fl
- &cube age15cktenavt5v7zm84se36jtly740syca5nw8em8edx404n5x2ddws8jn29g
creation_rules:
# per-host secrets
- path_regex: ^secrets/tower\.yaml$
key_groups:
- pgp: [*matej]
age: [*tower]
- path_regex: ^secrets/fw16\.yaml$
key_groups:
- pgp: [*matej]
age: [*fw16]
- path_regex: ^secrets/floo\.yaml$
key_groups:
- pgp: [*matej]
age: [*floo]
- path_regex: ^secrets/cube\.yaml$
key_groups:
- pgp: [*matej]
age: [*cube]
# shared secrets (all hosts)
- path_regex: ^secrets/common\.yaml$
key_groups:
- pgp: [*matej]
age: [*tower, *fw16, *floo, *cube]

73
features/bootloader.nix Normal file
View File

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

28
features/claude.nix Normal file
View File

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

273
features/desktop.nix Normal file
View File

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

36
features/dev.nix Normal file
View File

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

View File

@@ -0,0 +1,45 @@
{
nixos =
{
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";
id = "dev";
};
to = {
type = "path";
path = inputs.self.outPath;
};
};
};
};
home =
{ lib, osConfig, ... }:
let
cfg = osConfig.features.direnv;
in
{
config = lib.mkIf cfg.enable {
programs.direnv = {
enable = true;
nix-direnv.enable = true;
config.global.hide_env_diff = true;
};
xdg.configFile."direnv/lib/use_dev.sh".source = ./use_dev.sh;
};
};
}

View File

@@ -0,0 +1,92 @@
# shellcheck shell=bash
# composable nix devshell from matej.nix
# usage in .envrc: use dev uv_14 pg_18 --extra cairo pkg-config
# generates a flake and delegates to use_flake at the calling scope
use_dev() {
local nix_list="" extra_list="" in_extra=0
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
case "$(uname -s)-$(uname -m)" in
Linux-x86_64) system="x86_64-linux" ;;
Linux-aarch64) system="aarch64-linux" ;;
Darwin-x86_64) system="x86_64-darwin" ;;
Darwin-arm64) system="aarch64-darwin" ;;
esac
if [[ -z "$system" ]]; then
log_error "use_dev: unsupported platform: $(uname -s)-$(uname -m)"
return 1
fi
local dev_path nixpkgs_path registry_filter
# shellcheck disable=SC2016 # $id is a jq variable, not shell
registry_filter='.flakes[] | select(.from.id == $id) | .to.path'
local registry_file="${XDG_CONFIG_HOME:-$HOME/.config}/nix/registry.json"
if [[ ! -f "$registry_file" ]]; then
registry_file="/etc/nix/registry.json"
fi
dev_path="$(jq -re --arg id dev "$registry_filter" "$registry_file" 2>/dev/null)"
nixpkgs_path="$(jq -re --arg id nixpkgs "$registry_filter" "$registry_file" 2>/dev/null)"
if [[ -z "$dev_path" ]]; then
log_error "use_dev: 'dev' not found in nix registry"
return 1
fi
if [[ -z "$nixpkgs_path" ]]; then
log_error "use_dev: 'nixpkgs' not found in nix registry"
return 1
fi
local components_hash project_hash cache_dir
components_hash="$(sha256sum "$dev_path/flake/dev-components.nix" 2>/dev/null | cut -c1-16)"
project_hash="$(echo "$PWD" | sha256sum | cut -c1-16)"
cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/dev-flakes/$project_hash"
mkdir -p "$cache_dir"
cat >"$cache_dir/flake.nix.tmp" <<DEVFLAKE
# dev-components: $components_hash
{
inputs.dev = { url = "path:${dev_path}"; flake = false; };
inputs.nixpkgs.url = "path:${nixpkgs_path}";
outputs = { dev, nixpkgs, ... }:
let
system = "${system}";
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} ];
};
}
DEVFLAKE
if ! cmp -s "$cache_dir/flake.nix.tmp" "$cache_dir/flake.nix" 2>/dev/null; then
mv "$cache_dir/flake.nix.tmp" "$cache_dir/flake.nix"
rm -f "$cache_dir/flake.lock"
else
rm "$cache_dir/flake.nix.tmp"
fi
use_flake "path:$cache_dir"
}

24
features/docker.nix Normal file
View File

@@ -0,0 +1,24 @@
{
nixos =
{
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";
};
users.users.${user}.extraGroups = [ "docker" ];
};
};
}

61
features/filedrop.nix Normal file
View File

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

26
features/gaming.nix Normal file
View File

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

29
features/git.nix Normal file
View File

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

43
features/gnupg.nix Normal file
View File

@@ -0,0 +1,43 @@
{
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 = {
enable = true;
enableSSHSupport = true;
enableExtraSocket = true;
};
}
(lib.mkIf cfg.yubikey.enable {
environment.systemPackages = with pkgs; [
yubikey-personalization
yubikey-manager
];
services.pcscd.enable = true;
})
]
);
};
}

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -uo pipefail
build() {
local name="$1" ref="$2" link="$3"
echo "building $name..." >&2
if nix build "$ref" --out-link "$link"; then
return 0
else
echo "FAILED: $name" >&2
return 1
fi
}
main() {
mkdir -p "$GC_ROOT_DIR"
local failed=0
for host in $HOSTS; do
build "$host" \
"$FLAKE_REF#nixosConfigurations.$host.config.system.build.toplevel" \
"$GC_ROOT_DIR/$host" || failed=1
done
build "ephvm-image" \
"$FLAKE_REF#nixosConfigurations.ephvm.config.system.build.images.qemu" \
"$GC_ROOT_DIR/ephvm-image" || failed=1
return $failed
}
main "$@"

View File

@@ -0,0 +1,52 @@
{
nixos =
{
config,
lib,
pkgs,
inputs,
...
}:
let
cfg = config.features.harmonia;
hosts = [
"fw16"
"tower"
"cube"
"floo"
"ephvm"
];
flakeRef = inputs.self.outPath;
in
{
options.features.harmonia.enable = lib.mkEnableOption "harmonia";
config = lib.mkIf cfg.enable {
services.harmonia.cache = {
enable = true;
signKeyPaths = [ config.sops.secrets.nix-signing-key.path ];
};
networking.firewall.interfaces."tailscale0".allowedTCPPorts = [ 5000 ];
systemd.services.cache-builder = {
description = "Build all host closures for binary cache";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.bash}/bin/bash ${./cache-builder.sh}";
};
environment = {
FLAKE_REF = flakeRef;
HOSTS = builtins.concatStringsSep " " hosts;
GC_ROOT_DIR = "/nix/var/nix/gcroots/cache-builder";
};
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
'';
};
};
}

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

@@ -0,0 +1,90 @@
{
nixos =
{ lib, config, ... }:
let
cfg = config.features.initrd-ssh;
keyDir = "/etc/secrets/initrd";
mkIpString =
{
address,
gateway,
netmask,
interface,
...
}:
"${address}::${gateway}:${netmask}::${interface}:none";
in
{
options.features.initrd-ssh = {
enable = lib.mkEnableOption "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 = 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}"
];
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 = {
enable = true;
port = 22;
hostKeys = [
"${keyDir}/ssh_host_rsa_key"
"${keyDir}/ssh_host_ed25519_key"
];
inherit (cfg) authorizedKeys;
};
};
# 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";
};
};
}

30
features/localisation.nix Normal file
View File

@@ -0,0 +1,30 @@
{
nixos =
{ lib, config, ... }:
let
cfg = config.features.localisation;
in
{
options.features.localisation = {
enable = lib.mkEnableOption "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;
# NOTE:(@janezicmatej) some apps (e.g. java) need TZ env var explicitly
environment.variables.TZ = cfg.timeZone;
};
};
}

82
features/neovim.nix Normal file
View File

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

View File

@@ -0,0 +1,18 @@
{
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.nameservers = [
"1.1.1.1"
"8.8.8.8"
];
};
};
}

14
features/nix-ld.nix Normal file
View File

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

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

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

23
features/onepassword.nix Normal file
View File

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

30
features/openssh.nix Normal file
View File

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

57
features/power.nix Normal file
View File

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

19
features/printing.nix Normal file
View File

@@ -0,0 +1,19 @@
{
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.avahi = {
enable = true;
nssmdns4 = true;
openFirewall = true;
};
};
};
}

25
features/remote-base.nix Normal file
View File

@@ -0,0 +1,25 @@
{
nixos =
{
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;
};
users.mutableUsers = false;
users.users.${user}.hashedPasswordFile = config.sops.secrets.user-password.path;
};
};
}

33
features/shell.nix Normal file
View File

@@ -0,0 +1,33 @@
{
nixos =
{ lib, ... }:
{
options.features.shell.enable = lib.mkEnableOption "shell extras";
};
home =
{
pkgs,
lib,
osConfig,
...
}:
let
cfg = osConfig.features.shell;
in
{
config = lib.mkIf cfg.enable {
home.packages = with pkgs; [
fzf
htop
jc
jq
openssl
pv
ripgrep
fd
tmux
];
};
};
}

100
features/sway.nix Normal file
View File

@@ -0,0 +1,100 @@
{
nixos =
{
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;
wrapperFeatures.gtk = true;
extraSessionCommands = ''
# fix for java awt apps not rendering
export _JAVA_AWT_WM_NONREPARENTING=1
# propagate XDG_DATA_DIRS to dbus/systemd for d-bus activated apps
dbus-update-activation-environment --systemd XDG_DATA_DIRS
'';
};
environment.systemPackages = with pkgs; [
waybar
mako
wob
playerctl
brightnessctl
foot
grim
pulseaudio
swayidle
swaylock-effects
jq
slurp
wl-clipboard
pamixer
wlsunset
satty
wayland-pipewire-idle-inhibit
fuzzel
cliphist
zenity
];
}
# greeter
(lib.mkIf cfg.greeter.enable {
programs.regreet = {
enable = true;
cageArgs = [
"-s"
"-m"
"last"
];
font = {
name = lib.mkForce "JetBrainsMono Nerd Font";
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;
};
};
};
})
]
);
};
}

17
features/tailscale.nix Normal file
View File

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

59
features/udev.nix Normal file
View File

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

30
features/user-matej.nix Normal file
View File

@@ -0,0 +1,30 @@
let
sshKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICQGLdINKzs+sEy62Pefng0bcedgU396+OryFgeH99/c janezicmatej"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDk00+Km03epQXQs+xEwwH3zcurACzkEH+kDOPBw6RQe openpgp:0xB095D449"
];
in
{
keys = {
sshAuthorizedKeys = sshKeys;
};
nixos = _: {
users.users.matej = {
uid = 1000;
isNormalUser = true;
home = "/home/matej";
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = sshKeys;
};
users.groups.matej = {
gid = 1000;
members = [ "matej" ];
};
};
home = _: {
home.stateVersion = "26.05";
};
}

112
features/vm-guest.nix Normal file
View File

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

55
features/zsh.nix Normal file
View File

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

244
flake.lock generated
View File

@@ -99,14 +99,34 @@
"type": "github"
}
},
"disko": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1776613567,
"narHash": "sha256-gC9Cp5ibBmGD5awCA9z7xy6MW6iJufhazTYJOiGlCUI=",
"owner": "nix-community",
"repo": "disko",
"rev": "32f4236bfc141ae930b5ba2fb604f561fed5219d",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "disko",
"type": "github"
}
},
"firefox-gnome-theme": {
"flake": false,
"locked": {
"lastModified": 1764873433,
"narHash": "sha256-1XPewtGMi+9wN9Ispoluxunw/RwozuTRVuuQOmxzt+A=",
"lastModified": 1775176642,
"narHash": "sha256-2veEED0Fg7Fsh81tvVDNYR6SzjqQxa7hbi18Jv4LWpM=",
"owner": "rafaelmardojai",
"repo": "firefox-gnome-theme",
"rev": "f7ffd917ac0d253dbd6a3bf3da06888f57c69f92",
"rev": "179704030c5286c729b5b0522037d1d51341022c",
"type": "github"
},
"original": {
@@ -133,17 +153,14 @@
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"neovim-nightly-overlay",
"nixpkgs"
]
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"lastModified": 1775087534,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github"
},
"original": {
@@ -155,16 +172,16 @@
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": [
"stylix",
"neovim-nightly-overlay",
"nixpkgs"
]
},
"locked": {
"lastModified": 1767609335,
"narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=",
"lastModified": 1775087534,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "250481aafeb741edfe23d29195671c19b36b6dca",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github"
},
"original": {
@@ -173,21 +190,24 @@
"type": "github"
}
},
"flake-utils": {
"flake-parts_3": {
"inputs": {
"systems": "systems"
"nixpkgs-lib": [
"stylix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"lastModified": 1775087534,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
@@ -232,20 +252,18 @@
"gnome-shell": {
"flake": false,
"locked": {
"host": "gitlab.gnome.org",
"lastModified": 1767737596,
"narHash": "sha256-eFujfIUQDgWnSJBablOuG+32hCai192yRdrNHTv0a+s=",
"owner": "GNOME",
"repo": "gnome-shell",
"rev": "ef02db02bf0ff342734d525b5767814770d85b49",
"type": "gitlab"
"type": "github"
},
"original": {
"host": "gitlab.gnome.org",
"owner": "GNOME",
"ref": "gnome-49",
"repo": "gnome-shell",
"type": "gitlab"
"rev": "ef02db02bf0ff342734d525b5767814770d85b49",
"type": "github"
}
},
"home-manager": {
@@ -255,16 +273,15 @@
]
},
"locked": {
"lastModified": 1774274588,
"narHash": "sha256-dnHvv5EMUgTzGZmA+3diYjQU2O6BEpGLEOgJ1Qe9LaY=",
"lastModified": 1776777932,
"narHash": "sha256-0R3Yow/NzSeVGUke5tL7CCkqmss4Vmi6BbV6idHzq/8=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "cf9686ba26f5ef788226843bc31fda4cf72e373b",
"rev": "5d5640599a0050b994330328b9fd45709c909720",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "release-25.11",
"repo": "home-manager",
"type": "github"
}
@@ -295,16 +312,16 @@
},
"neovim-nightly-overlay": {
"inputs": {
"flake-parts": "flake-parts",
"flake-parts": "flake-parts_2",
"neovim-src": "neovim-src",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1774483626,
"narHash": "sha256-8VAX9GXNfv4eBj0qBEf/Rc2/E6G0SBEpuo2A5plw34I=",
"lastModified": 1776729909,
"narHash": "sha256-wGu/N42PJqrj8ju9GoXdppg4rwaKzZqdAjsgxJbCvfY=",
"owner": "nix-community",
"repo": "neovim-nightly-overlay",
"rev": "5deaa19e80e1c0695f7fa8a16e13a704fd08f96e",
"rev": "ff21a18bde28b4c8ca0bc1f9a5b7186a1b89a3d1",
"type": "github"
},
"original": {
@@ -316,11 +333,11 @@
"neovim-src": {
"flake": false,
"locked": {
"lastModified": 1774472446,
"narHash": "sha256-Hp4A0llEmBvvNuw5uKOz+BA86X7TmXZ1vUK0StiMdVs=",
"lastModified": 1776727374,
"narHash": "sha256-iP5SviNXW5W+ay4ZmwjDFsfQjfM+fYlUxRlLPHjpwWI=",
"owner": "neovim",
"repo": "neovim",
"rev": "c9e961994b16ed841be43541ef550bf3d3f043ec",
"rev": "901b3f0c394a53961781ebeee682e64ad690a242",
"type": "github"
},
"original": {
@@ -331,11 +348,11 @@
},
"nixos-hardware": {
"locked": {
"lastModified": 1774465523,
"narHash": "sha256-4v7HPm63Q90nNn4fgkgKsjW1AH2Klw7XzPtHJr562nM=",
"lastModified": 1775490113,
"narHash": "sha256-2ZBhDNZZwYkRmefK5XLOusCJHnoeKkoN95hoSGgMxWM=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "de895be946ad1d8aafa0bb6dfc7e7e0e9e466a29",
"rev": "c775c2772ba56e906cbeb4e0b2db19079ef11ff7",
"type": "github"
},
"original": {
@@ -347,11 +364,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1774273680,
"narHash": "sha256-a++tZ1RQsDb1I0NHrFwdGuRlR5TORvCEUksM459wKUA=",
"lastModified": 1776329215,
"narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "fdc7b8f7b30fdbedec91b71ed82f36e1637483ed",
"rev": "b86751bc4085f48661017fa226dee99fab6c651b",
"type": "github"
},
"original": {
@@ -361,13 +378,28 @@
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1774748309,
"narHash": "sha256-+U7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "333c4e0545a6da976206c74db8773a1645b5870a",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs-master": {
"locked": {
"lastModified": 1774515149,
"narHash": "sha256-avcNAzwJC2lOyMNKVro2u5d6RLGiquXXxQVVmAEQ2+U=",
"lastModified": 1776807375,
"narHash": "sha256-LDnHG0T54OEHyRydmGUlAND8ham0KrRNWjgoS+6GUd4=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "01fcaaa08a9a44ec63c52bf0ee8d9a29593fee07",
"rev": "553ecb1686a2edb75dee44c9f72e1674e6adc26a",
"type": "github"
},
"original": {
@@ -377,34 +409,34 @@
"type": "github"
}
},
"nixpkgs-unstable": {
"nixpkgs-stable": {
"locked": {
"lastModified": 1774273680,
"narHash": "sha256-a++tZ1RQsDb1I0NHrFwdGuRlR5TORvCEUksM459wKUA=",
"owner": "nixos",
"lastModified": 1776560675,
"narHash": "sha256-p68udKWWh7+V4ZPpcMDq0gTHWNZJnr4JPI+kHPPE40o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "fdc7b8f7b30fdbedec91b71ed82f36e1637483ed",
"rev": "e07580dae39738e46609eaab8b154de2488133ce",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1774244481,
"narHash": "sha256-4XfMXU0DjN83o6HWZoKG9PegCvKvIhNUnRUI19vzTcQ=",
"lastModified": 1776548001,
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4590696c8693fea477850fe379a01544293ca4e2",
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
@@ -421,11 +453,11 @@
]
},
"locked": {
"lastModified": 1767886815,
"narHash": "sha256-pB2BBv6X9cVGydEV/9Y8+uGCvuYJAlsprs1v1QHjccA=",
"lastModified": 1775228139,
"narHash": "sha256-ebbeHmg+V7w8050bwQOuhmQHoLOEOfqKzM1KgCTexK4=",
"owner": "nix-community",
"repo": "NUR",
"rev": "4ff84374d77ff62e2e13a46c33bfeb73590f9fef",
"rev": "601971b9c89e0304561977f2c28fa25e73aa7132",
"type": "github"
},
"original": {
@@ -476,15 +508,17 @@
"root": {
"inputs": {
"assets": "assets",
"flake-utils": "flake-utils",
"disko": "disko",
"flake-parts": "flake-parts",
"home-manager": "home-manager",
"lanzaboote": "lanzaboote",
"neovim-nightly-overlay": "neovim-nightly-overlay",
"nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_2",
"nixpkgs-master": "nixpkgs-master",
"nixpkgs-unstable": "nixpkgs-unstable",
"nixpkgs-stable": "nixpkgs-stable",
"nvim": "nvim",
"sops-nix": "sops-nix",
"stylix": "stylix"
}
},
@@ -509,6 +543,26 @@
"type": "github"
}
},
"sops-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1776771786,
"narHash": "sha256-DRFGPfFV6hbrfO9a1PH1FkCi7qR5FgjSqsQGGvk1rdI=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "bef289e2248991f7afeb95965c82fbcd8ff72598",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
},
"stylix": {
"inputs": {
"base16": "base16",
@@ -516,30 +570,28 @@
"base16-helix": "base16-helix",
"base16-vim": "base16-vim",
"firefox-gnome-theme": "firefox-gnome-theme",
"flake-parts": "flake-parts_2",
"flake-parts": "flake-parts_3",
"gnome-shell": "gnome-shell",
"nixpkgs": [
"nixpkgs"
],
"nur": "nur",
"systems": "systems_2",
"tinted-foot": "tinted-foot",
"systems": "systems",
"tinted-kitty": "tinted-kitty",
"tinted-schemes": "tinted-schemes",
"tinted-tmux": "tinted-tmux",
"tinted-zed": "tinted-zed"
},
"locked": {
"lastModified": 1774194089,
"narHash": "sha256-SCczWhr8y8aaXVHG+gOGcRahNb0BU1Z5zYZuv9W/nA8=",
"lastModified": 1776170745,
"narHash": "sha256-Tl1aZVP5EIlT+k0+iAKH018GLHJpLz3hhJ0LNQOWxCc=",
"owner": "danth",
"repo": "stylix",
"rev": "7c34241d80ea64dd2039bb3a786fb66b4c6261d9",
"rev": "e3861617645a43c9bbefde1aa6ac54dd0a44bfa9",
"type": "github"
},
"original": {
"owner": "danth",
"ref": "release-25.11",
"repo": "stylix",
"type": "github"
}
@@ -559,38 +611,6 @@
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"tinted-foot": {
"flake": false,
"locked": {
"lastModified": 1726913040,
"narHash": "sha256-+eDZPkw7efMNUf3/Pv0EmsidqdwNJ1TaOum6k7lngDQ=",
"owner": "tinted-theming",
"repo": "tinted-foot",
"rev": "fd1b924b6c45c3e4465e8a849e67ea82933fcbe4",
"type": "github"
},
"original": {
"owner": "tinted-theming",
"repo": "tinted-foot",
"rev": "fd1b924b6c45c3e4465e8a849e67ea82933fcbe4",
"type": "github"
}
},
"tinted-kitty": {
"flake": false,
"locked": {
@@ -610,11 +630,11 @@
"tinted-schemes": {
"flake": false,
"locked": {
"lastModified": 1767817087,
"narHash": "sha256-eGE8OYoK6HzhJt/7bOiNV2cx01IdIrHL7gXgjkHRdNo=",
"lastModified": 1772661346,
"narHash": "sha256-4eu3LqB9tPqe0Vaqxd4wkZiBbthLbpb7llcoE/p5HT0=",
"owner": "tinted-theming",
"repo": "schemes",
"rev": "bd99656235aab343e3d597bf196df9bc67429507",
"rev": "13b5b0c299982bb361039601e2d72587d6846294",
"type": "github"
},
"original": {
@@ -626,11 +646,11 @@
"tinted-tmux": {
"flake": false,
"locked": {
"lastModified": 1767489635,
"narHash": "sha256-e6nnFnWXKBCJjCv4QG4bbcouJ6y3yeT70V9MofL32lU=",
"lastModified": 1772934010,
"narHash": "sha256-x+6+4UvaG+RBRQ6UaX+o6DjEg28u4eqhVRM9kpgJGjQ=",
"owner": "tinted-theming",
"repo": "tinted-tmux",
"rev": "3c32729ccae99be44fe8a125d20be06f8d7d8184",
"rev": "c3529673a5ab6e1b6830f618c45d9ce1bcdd829d",
"type": "github"
},
"original": {
@@ -642,11 +662,11 @@
"tinted-zed": {
"flake": false,
"locked": {
"lastModified": 1767488740,
"narHash": "sha256-wVOj0qyil8m+ouSsVZcNjl5ZR+1GdOOAooAatQXHbuU=",
"lastModified": 1772909925,
"narHash": "sha256-jx/5+pgYR0noHa3hk2esin18VMbnPSvWPL5bBjfTIAU=",
"owner": "tinted-theming",
"repo": "base16-zed",
"rev": "11abb0b282ad3786a2aae088d3a01c60916f2e40",
"rev": "b4d3a1b3bcbd090937ef609a0a3b37237af974df",
"type": "github"
},
"original": {

150
flake.nix
View File

@@ -2,14 +2,10 @@
description = "matej's nix setup";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.11";
nixpkgs-master.url = "github:nixos/nixpkgs/master";
# dotfiles = {
# url = "git+https://git.janezic.dev/janezicmatej/.dotfiles.git";
# flake = false;
# };
nvim = {
url = "git+https://git.janezic.dev/janezicmatej/nvim.git";
flake = false;
@@ -17,15 +13,15 @@
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
flake-utils.url = "github:numtide/flake-utils";
flake-parts.url = "github:hercules-ci/flake-parts";
home-manager = {
url = "github:nix-community/home-manager/release-25.11";
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
stylix = {
url = "github:danth/stylix/release-25.11";
url = "github:danth/stylix";
inputs.nixpkgs.follows = "nixpkgs";
};
@@ -41,117 +37,47 @@
neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay";
disko = {
url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs";
};
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
inputs@{
flake-parts,
nixpkgs,
flake-utils,
self,
...
}:
let
my-lib = import ./lib { inherit (nixpkgs) lib; };
overlays = [
(
_: prev:
let
pkgs-unstable = import inputs.nixpkgs-unstable {
inherit (prev.stdenv.hostPlatform) system;
inherit (prev) config;
};
pkgs-master = import inputs.nixpkgs-master {
inherit (prev.stdenv.hostPlatform) system;
inherit (prev) config;
};
in
{
inherit (pkgs-master) claude-code;
# TODO:(@janezicmatej) 2026-03-09 error with stable for telegram-desktop
inherit (pkgs-unstable) telegram-desktop;
}
)
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
./flake/overlays.nix
./flake/packages.nix
./flake/devshell.nix
./flake/hosts.nix
];
mkHost = my-lib.mkHost {
inherit
nixpkgs
overlays
inputs
;
systems = [ "x86_64-linux" ];
perSystem =
{ system, ... }:
{
_module.args.pkgs = import nixpkgs {
inherit system;
overlays = [ self.overlays.default ];
config.allowUnfree = true;
};
};
flake = {
lib = import ./lib { inherit (nixpkgs) lib; };
};
in
{
lib = my-lib;
nixosConfigurations = {
fw16 = mkHost "fw16" {
system = "x86_64-linux";
user = "matej";
};
tower = mkHost "tower" {
system = "x86_64-linux";
user = "matej";
};
# nixos-rebuild build-image --image-variant install-iso --flake .#iso
iso = mkHost "iso" {
system = "x86_64-linux";
};
ephvm = mkHost "ephvm" {
system = "x86_64-linux";
user = "matej";
};
};
nixosModules = import ./modules/nixos {
inherit my-lib;
inherit (nixpkgs) lib;
} { };
homeManagerModules = import ./modules/home-manager {
inherit my-lib;
inherit (nixpkgs) lib;
} { };
nixosProfiles = import ./profiles {
inherit my-lib;
inherit (nixpkgs) lib;
} { };
}
// flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
packages =
import ./packages
{
inherit my-lib;
inherit (nixpkgs) lib;
}
{
pkgs = nixpkgs.legacyPackages.${system};
pkgs-unstable = inputs.nixpkgs-unstable.legacyPackages.${system};
pkgs-master = inputs.nixpkgs-master.legacyPackages.${system};
};
formatter = pkgs.nixfmt-tree;
devShells.default = pkgs.mkShell {
packages = [
pkgs.pre-commit
pkgs.statix
pkgs.shellcheck
pkgs.shfmt
pkgs.qemu
];
};
}
);
};
}

89
flake/dev-components.nix Normal file
View File

@@ -0,0 +1,89 @@
# composable dev environment components
# imported by generated per-project flakes via use_dev
{ pkgs, lib }:
let
# libraries needed by python native extensions
pythonLibraries = [
pkgs.stdenv.cc.cc.lib
pkgs.zlib
pkgs.openssl
pkgs.curl
pkgs.libffi
];
mkNode = nodejs: {
packages = [
nodejs
pkgs.corepack
];
env = {
COREPACK_ENABLE_STRICT = "0";
};
};
mkUv = python: {
packages = [
python
pkgs.uv
pkgs.pkg-config
];
libraries = pythonLibraries;
env = {
UV_PYTHON_DOWNLOADS = "never";
UV_PYTHON_PREFERENCE = "only-system";
};
shellHook = ''
unset PYTHONPATH
export UV_PROJECT_ENVIRONMENT="''${XDG_DATA_HOME:-$HOME/.local/share}/dev-venvs/$(basename "$PWD")-$(echo "$PWD" | sha256sum | cut -c1-8)"
'';
};
components = {
uv_10 = mkUv pkgs.python310;
uv_11 = mkUv pkgs.python311;
uv_12 = mkUv pkgs.python312;
uv_13 = mkUv pkgs.python313;
uv_14 = mkUv pkgs.python314;
node_20 = mkNode pkgs.nodejs_20;
node_22 = mkNode pkgs.nodejs_22;
node_24 = mkNode pkgs.nodejs_24;
rust = {
packages = [
pkgs.rustc
pkgs.cargo
pkgs.rust-analyzer
pkgs.openssl
pkgs.pkg-config
];
};
};
# build a single mkShell from one or more component names
mkComponentShell =
names: extraPackages:
let
selected = map (n: components.${n}) names;
allPackages = lib.concatMap (c: c.packages or [ ]) selected ++ extraPackages;
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;
libPath = lib.makeLibraryPath allLibraries;
in
pkgs.mkShell (
{
packages = allPackages;
shellHook =
(lib.optionalString (allLibraries != [ ]) ''
export LD_LIBRARY_PATH="${libPath}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
'')
+ allHooks;
}
// lib.optionalAttrs (allEnvs != { }) { env = allEnvs; }
);
in
{
inherit components mkComponentShell;
}

21
flake/devshell.nix Normal file
View File

@@ -0,0 +1,21 @@
_: {
perSystem =
{ pkgs, ... }:
{
formatter = pkgs.nixfmt-tree;
devShells = {
default = pkgs.mkShell {
packages = [
pkgs.pre-commit
pkgs.statix
pkgs.shellcheck
pkgs.shfmt
pkgs.qemu
pkgs.sops
pkgs.ssh-to-age
];
};
};
};
}

152
flake/hosts.nix Normal file
View File

@@ -0,0 +1,152 @@
{ inputs, self, ... }:
let
inherit (inputs) nixpkgs;
my-lib = import ../lib { inherit (nixpkgs) lib; };
mkHost = my-lib.mkHost {
inherit nixpkgs inputs;
overlays = [ self.overlays.default ];
};
in
{
flake.nixosConfigurations = {
fw16 = mkHost "fw16" {
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"
"shell"
"sway"
"tailscale"
"udev"
"zsh"
];
};
tower = mkHost "tower" {
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"
"shell"
"sway"
"tailscale"
"udev"
"zsh"
];
};
# nixos-rebuild build-image --image-variant install-iso --flake .#iso
iso = mkHost "iso" {
system = "x86_64-linux";
user = "matej";
features = [
"nix-settings"
"openssh"
"zsh"
];
};
cube = mkHost "cube" {
system = "x86_64-linux";
user = "matej";
features = [
"bootloader"
"localisation"
"nix-settings"
"openssh"
"remote-base"
"shell"
"tailscale"
"zsh"
];
};
# nix run github:nix-community/nixos-anywhere -- --flake .#floo root@<ip>
floo = mkHost "floo" {
system = "x86_64-linux";
user = "matej";
features = [
"filedrop"
"localisation"
"nix-settings"
"openssh"
"remote-base"
"shell"
"tailscale"
"zsh"
];
};
fortress = mkHost "fortress" {
system = "x86_64-linux";
user = "matej";
features = [
"bootloader"
"desktop"
"gnupg"
"localisation"
"networkmanager"
"nix-settings"
"sway"
"udev"
"zsh"
];
};
ephvm = mkHost "ephvm" {
system = "x86_64-linux";
user = "matej";
features = [
"claude"
"dev"
"docker"
"git"
"gnupg"
"localisation"
"neovim"
"nix-settings"
"openssh"
"shell"
"vm-guest"
"zsh"
];
};
};
}

5
flake/overlays.nix Normal file
View File

@@ -0,0 +1,5 @@
_:
{
flake.overlays.default = _: _: { };
}

21
flake/packages.nix Normal file
View File

@@ -0,0 +1,21 @@
{ inputs, ... }:
let
my-lib = import ../lib { inherit (inputs.nixpkgs) lib; };
in
{
perSystem =
{ pkgs, system, ... }:
{
packages =
import ../packages
{
inherit my-lib;
inherit (inputs.nixpkgs) lib;
}
{
inherit pkgs;
pkgs-master = inputs.nixpkgs-master.legacyPackages.${system};
};
};
}

View File

@@ -0,0 +1,33 @@
{ inputs, ... }:
{
imports = [ inputs.disko.nixosModules.disko ];
disko.devices.disk.main = {
type = "disk";
device = "/dev/nvme0n1";
content = {
type = "gpt";
partitions = {
esp = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
system.stateVersion = "25.11";
}

View File

@@ -0,0 +1,31 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{
config,
lib,
pkgs,
modulesPath,
...
}:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [
"xhci_pci"
"ahci"
"nvme"
"usb_storage"
"sd_mod"
"sdhci_pci"
];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View File

@@ -2,14 +2,10 @@
pkgs,
lib,
inputs,
config,
...
}:
{
networking.hostName = "ephvm";
profiles.base.enable = true;
features.nix-settings.towerCache.enable = false;
# no hardware firmware needed in a VM
hardware.enableRedistributableFirmware = lib.mkForce false;
hardware.wirelessRegulatoryDatabase = lib.mkForce false;
@@ -17,49 +13,93 @@
documentation.enable = false;
environment.defaultPackages = [ ];
# compressed qcow2, no channel copy
# qcow2, no channel copy; post-processed with parallel zstd on qcow2 v3
# (~half the size of zlib v2, faster decompress)
image.modules.qemu =
{ config, modulesPath, ... }:
{
system.build.image = lib.mkForce (
import (modulesPath + "/../lib/make-disk-image.nix") {
inherit lib config pkgs;
inherit (config.virtualisation) diskSize;
let
rawImage = import (modulesPath + "/../lib/make-disk-image.nix") {
inherit lib config pkgs;
inherit (config.virtualisation) diskSize;
inherit (config.image) baseName;
format = "qcow2";
copyChannel = false;
partitionTableType = "legacy";
};
inherit (config.image) baseName;
format = "qcow2-compressed";
copyChannel = false;
partitionTableType = "legacy";
}
in
pkgs.runCommand baseName { nativeBuildInputs = [ pkgs.qemu-utils ]; } ''
mkdir -p $out
# qemu-img caps -m at 16
cores="''${NIX_BUILD_CORES:-4}"
[ "$cores" -gt 0 ] || cores=4
[ "$cores" -gt 16 ] && cores=16
qemu-img convert \
-f qcow2 \
-O qcow2 \
-c \
-o compression_type=zstd \
-m "$cores" \
${rawImage}/${baseName}.qcow2 \
$out/${baseName}.qcow2
''
);
};
vm-guest = {
# auto-login on serial console
services.getty.autologinUser = "matej";
# enable zsh in home-manager so starship init gets wired up
home-manager.users.matej.programs.zsh = {
enable = true;
headless = true;
dotDir = "/home/matej/.config/zsh";
shellAliases.dsp = "claude --dangerously-skip-permissions";
};
vm-9p-automount = {
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)";
};
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";
};
localisation = {
timeZone = "UTC";
defaultLocale = "en_US.UTF-8";
};
virtualisation.docker = {
enable = true;
logDriver = "json-file";
};
# TODO:(@janezicmatej) move neovim dotfiles wiring to a cleaner place
home-manager.users.matej = {
neovim.dotfiles = inputs.nvim;
};
features.neovim.dotfiles = inputs.nvim;
# ensure .config exists with correct ownership before automount
systemd.tmpfiles.rules = [ "d /home/matej/.config 0755 matej users -" ];
systemd.tmpfiles.rules = [ "d /home/matej/.config 0700 matej users -" ];
# TODO:(@janezicmatej) replace ssh with virtio-console (hvc0) when qemu 11.0 lands
# https://www.mail-archive.com/qemu-devel@nongnu.org/msg1162844.html
# 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
fileSystems."/home/matej/.config/claude" = {

View File

@@ -0,0 +1,32 @@
{ inputs, ... }:
{
imports = [ inputs.disko.nixosModules.disko ];
features.filedrop.sopsFile = ../../secrets/floo.yaml;
boot.loader.grub.enable = true;
disko.devices.disk.main = {
type = "disk";
device = "/dev/sda";
content = {
type = "gpt";
partitions = {
boot = {
size = "1M";
type = "EF02";
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
system.stateVersion = "25.11";
}

View File

@@ -0,0 +1,37 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{
config,
lib,
pkgs,
modulesPath,
...
}:
{
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
];
boot.initrd.availableKernelModules = [
"ahci"
"xhci_pci"
"virtio_pci"
"virtio_scsi"
"sd_mod"
"sr_mod"
];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
# networking.interfaces.enp1s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

View File

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

View File

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

View File

@@ -1,81 +1,32 @@
{
config,
lib,
pkgs,
inputs,
options,
userKeys,
inputs,
...
}:
let
packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system};
in
{
imports = [
inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series
inputs.stylix.nixosModules.stylix
];
profiles.desktop.enable = true;
localisation = {
timeZone = "Europe/Ljubljana";
defaultLocale = "en_US.UTF-8";
features.bootloader.plymouth.enable = true;
features.desktop.bluetooth.enable = true;
features.gnupg.yubikey.enable = true;
features.udev = {
ledger.enable = true;
keyboard-zsa.enable = true;
};
stylix = {
enable = true;
polarity = "dark";
image = "${inputs.assets}/wallpaper.png";
base16Scheme = "${pkgs.base16-schemes}/share/themes/gruvbox-material-dark-medium.yaml";
};
# neovim manages its own theme
home-manager.users.matej.stylix.targets.neovim.enable = false;
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
features.power.resumeDevice = "/dev/disk/by-uuid/ff4750e7-3a9f-42c2-bb68-c458a6560540";
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
'';
# WARN:(@janezicmatej) nix-ld for running pip-installed binaries outside nix, probably want to drop this
programs.nix-ld.enable = true;
programs.nix-ld.libraries = options.programs.nix-ld.libraries.default;
security.pki.certificateFiles = [ packages.ca-matheo-si ];
services.gnome.gnome-keyring.enable = true;
services.teamviewer.enable = true;
programs.thunderbird.enable = true;
programs._1password.enable = true;
programs._1password-gui.enable = true;
programs.firefox.enable = true;
programs.steam = {
enable = true;
remotePlay.openFirewall = true;
dedicatedServer.openFirewall = true;
localNetworkGameTransfers.openFirewall = true;
};
services.hardware.bolt.enable = true;
hardware.keyboard.zsa.enable = true;
hardware.ledger.enable = true;
hardware.bluetooth.powerOnBoot = true;
hardware.inputmodule.enable = true;
@@ -85,21 +36,7 @@ in
SUBSYSTEM=="usb", DRIVERS=="usb", ATTRS{idVendor}=="32ac", ATTRS{idProduct}=="0014", ATTR{power/wakeup}="disabled"
'';
programs.nm-applet.enable = true;
networking = {
hostName = "fw16";
networkmanager.enable = true;
firewall.enable = false;
nameservers = [
"1.1.1.1"
"8.8.8.8"
];
};
xdg.mime.defaultApplications = {
"application/pdf" = "org.pwmt.zathura.desktop";
};
networking.firewall.enable = false;
system.stateVersion = "24.11";
}

View File

@@ -1,43 +1,15 @@
{ lib, userKeys, ... }:
{
pkgs,
lib,
inputs,
userKeys,
...
}:
{
openssh.enable = true;
features.nix-settings.towerCache.enable = false;
image.modules.iso-installer = {
isoImage.squashfsCompression = "zstd -Xcompression-level 6";
};
fileSystems."/" = lib.mkDefault {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
};
boot.loader.grub.device = lib.mkDefault "/dev/sda";
networking.firewall.allowedTCPPorts = [ 22 ];
users = {
groups.matej = {
gid = 1000;
};
users.matej = {
group = "matej";
uid = 1000;
isNormalUser = true;
home = "/home/matej";
createHome = true;
password = "burek123";
extraGroups = [
"wheel"
"users"
];
openssh.authorizedKeys.keys = userKeys.sshAuthorizedKeys or [ ];
};
};
# live iso: passwordless login and sudo
users.users.matej.initialHashedPassword = "";
users.users.root.openssh.authorizedKeys.keys = userKeys.sshAuthorizedKeys;
services.openssh.settings.PermitRootLogin = lib.mkForce "prohibit-password";
security.sudo.wheelNeedsPassword = false;
system.stateVersion = "25.05";
}

View File

@@ -1,58 +1,36 @@
{
config,
lib,
pkgs,
inputs,
options,
userKeys,
...
}:
{
imports = [
inputs.stylix.nixosModules.stylix
inputs.lanzaboote.nixosModules.lanzaboote
];
profiles.desktop.enable = true;
initrd-ssh = {
enable = true;
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;
};
localisation = {
timeZone = "Europe/Ljubljana";
defaultLocale = "en_US.UTF-8";
};
# nix store signing
sops.secrets.nix-signing-key.sopsFile = ../../secrets/tower.yaml;
nix.settings.secret-key-files = [ config.sops.secrets.nix-signing-key.path ];
stylix = {
enable = true;
polarity = "dark";
image = "${inputs.assets}/wallpaper.png";
base16Scheme = "${pkgs.base16-schemes}/share/themes/gruvbox-material-dark-medium.yaml";
};
# neovim manages its own theme
home-manager.users.matej.stylix.targets.neovim.enable = false;
# lanzaboote secure boot
boot.kernelParams = [ "btusb.reset=1" ];
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.systemd-boot.enable = lib.mkForce false;
boot.lanzaboote = {
enable = true;
pkiBundle = "/var/lib/sbctl";
};
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
# early kms so plymouth lands on amdgpu, not simpledrm
hardware.amdgpu.initrd.enable = true;
services.udisks2.enable = true;
programs._1password.enable = true;
programs._1password-gui.enable = true;
# higher sample rate for audio equipment
services.pipewire.extraConfig.pipewire.adjust-sample-rate = {
"context.properties" = {
@@ -61,15 +39,5 @@
};
};
environment.systemPackages = with pkgs; [
easyeffects
];
networking.hostName = "tower";
xdg.mime.defaultApplications = {
"application/pdf" = "org.pwmt.zathura.desktop";
};
system.stateVersion = "25.05";
}

View File

@@ -41,6 +41,27 @@ ephvm *ARGS:
ephvm-ssh port="2222":
ssh -p {{port}} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null matej@localhost
# provision a host with nixos-anywhere
provision host ip:
#!/usr/bin/env bash
set -euo pipefail
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
install -d -m 755 "$tmpdir/etc/ssh"
ssh-keygen -t ed25519 -f "$tmpdir/etc/ssh/ssh_host_ed25519_key" -N ""
age_key=$(ssh-to-age < "$tmpdir/etc/ssh/ssh_host_ed25519_key.pub")
echo "age key: $age_key"
echo "add this key to .sops.yaml, re-encrypt secrets, then press enter to continue"
read -r
nix run github:nix-community/nixos-anywhere -- --no-reboot --flake .#{{host}} --extra-files "$tmpdir" --generate-hardware-config nixos-generate-config ./hosts/{{host}}/hardware-configuration.nix root@{{ip}}
echo "remove USB and press enter to reboot"
read -r
ssh root@{{ip}} reboot
# deploy config to a remote host
deploy host remote=host:
nixos-rebuild switch --flake .#{{host}} --target-host {{remote}} --sudo --ask-sudo-password
# garbage collect old generations
clean:
sudo nix-collect-garbage $(nix eval --raw -f ./nix.nix nix.gc.options)

View File

@@ -8,52 +8,101 @@ name:
{
system,
user ? null,
features ? [ ],
}:
let
hostConfig = ../hosts/${name}/configuration.nix;
hostHWConfig = ../hosts/${name}/hardware-configuration.nix;
hasHWConfig = builtins.pathExists hostHWConfig;
inherit (nixpkgs) lib;
hasUser = user != null;
userKeys = if hasUser then import ../users/${user}/keys.nix else { };
# path helpers
featurePath =
f:
let
file = ../features/${f}.nix;
dir = ../features/${f};
in
if builtins.pathExists file then file else dir;
userFeaturePath = u: ../features/user-${u}.nix;
hostConfig = ../hosts/${name}/configuration.nix;
hostHWConfig = ../hosts/${name}/hardware-configuration.nix;
# auto-import all nixos modules and profiles
nixosModuleList = builtins.attrValues inputs.self.nixosModules;
nixosProfileList = builtins.attrValues inputs.self.nixosProfiles;
# 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))
];
# auto-import all home-manager modules
hmModuleList = builtins.attrValues inputs.self.homeManagerModules;
# load all features unconditionally
loadFeature =
f:
assert
builtins.pathExists (featurePath f)
|| throw "feature '${f}' not found at ${toString (featurePath f)}";
import (featurePath f);
loadedFeatures = map loadFeature allFeatureNames;
# load user feature with path check
userFeature =
if hasUser then
assert
builtins.pathExists (userFeaturePath user)
|| throw "user feature 'user-${user}' not found at ${toString (userFeaturePath user)}";
import (userFeaturePath user)
else
null;
allFeatures = loadedFeatures ++ lib.optional (userFeature != null) userFeature;
# extract keys from user feature for specialArgs
userKeys = if userFeature != null then (userFeature.keys or { }) else { };
# 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
]
++ nixpkgs.lib.optional hasHWConfig hostHWConfig
++ nixosModuleList
++ nixosProfileList
++ nixpkgs.lib.optional (
hasUser && builtins.pathExists ../users/${user}/nixos.nix
) ../users/${user}/nixos.nix
++ [
++ lib.optional (builtins.pathExists hostHWConfig) hostHWConfig
++ nixosMods
++ lib.optionals hasUser [
inputs.home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.backupFileExtension = "backup";
home-manager.users = nixpkgs.lib.mkIf hasUser {
${user} = import ../users/${user}/home-manager.nix;
};
home-manager.sharedModules = hmModuleList;
home-manager.users.${user}.imports = homeMods;
home-manager.extraSpecialArgs = { inherit inputs; };
}
];
specialArgs = { inherit inputs userKeys; };
specialArgs = {
inherit inputs userKeys user;
};
}

View File

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

View File

@@ -1 +0,0 @@
{ lib, my-lib }: args: (my-lib.autoDir ./.)

View File

@@ -1,38 +0,0 @@
{
config,
lib,
pkgs,
inputs,
...
}:
{
options = {
desktop.enable = lib.mkEnableOption "desktop gui applications";
};
config = lib.mkIf config.desktop.enable {
home.packages = with pkgs; [
ghostty
google-chrome
zathura
pavucontrol
bolt-launcher
libnotify
bibata-cursors
vesktop
rocketchat-desktop
telegram-desktop
slack
jellyfin-media-player
cider-2
protonmail-bridge
ledger-live-desktop
mpv
ffmpeg
wf-recorder
wl-mirror
];
home.file.".assets".source = inputs.assets;
};
}

View File

@@ -1,39 +0,0 @@
{
config,
lib,
pkgs,
inputs,
...
}:
let
packages = inputs.self.outputs.packages.${pkgs.stdenv.hostPlatform.system};
in
{
options = {
dev.enable = lib.mkEnableOption "development tools";
};
config = lib.mkIf config.dev.enable {
home.packages = [
pkgs.git
packages.git-linearize
packages.ggman
pkgs.go
pkgs.python3
pkgs.mdbook
pkgs.marksman
pkgs.mdformat
pkgs.google-cloud-sdk
pkgs.google-cloud-sql-proxy
packages.ahab
pkgs.just
pkgs.presenterm
pkgs.osc
];
programs.direnv = {
enable = true;
nix-direnv.enable = true;
};
};
}

View File

@@ -1,70 +0,0 @@
{
config,
lib,
pkgs,
...
}:
{
options = {
neovim = {
enable = lib.mkEnableOption "neovim nightly with lsp support";
package = lib.mkPackageOption pkgs "neovim" { };
dotfiles = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "path to neovim config directory";
};
};
};
config = lib.mkIf config.neovim.enable (
lib.mkMerge [
(lib.mkIf (config.neovim.dotfiles != null) {
xdg.configFile."nvim".source = config.neovim.dotfiles;
})
{
programs.neovim = {
enable = true;
vimAlias = true;
defaultEditor = true;
inherit (config.neovim) package;
extraPackages = with pkgs; [
# runtime deps
gcc
luajit
nodejs_22
tree-sitter
gnumake
osc
# search and diff
fd
ripgrep
bat
delta
# language servers
pyright
typescript-language-server
lua-language-server
gopls
nil
nixd
# formatters
nixpkgs-fmt
stylua
];
extraWrapperArgs = [
"--suffix"
"LD_LIBRARY_PATH"
":"
"${lib.makeLibraryPath [ pkgs.stdenv.cc.cc.lib ]}"
];
};
}
]
);
}

View File

@@ -1,26 +0,0 @@
{
config,
lib,
pkgs,
...
}:
{
options = {
shell.enable = lib.mkEnableOption "shell utilities";
};
config = lib.mkIf config.shell.enable {
home.packages = with pkgs; [
starship
fzf
htop
jc
jq
openssl
pv
ripgrep
fd
tmux
];
};
}

View File

@@ -1,32 +0,0 @@
{
lib,
config,
pkgs,
...
}:
{
options = {
calibre = {
enable = lib.mkEnableOption "Calibre e-book management with Kindle support";
};
};
config = lib.mkIf config.calibre.enable {
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 +0,0 @@
{ lib, my-lib }: args: (my-lib.autoDir ./.)

View File

@@ -1,44 +0,0 @@
{
lib,
config,
pkgs,
...
}:
{
options = {
desktop = {
enable = lib.mkEnableOption "base desktop environment";
};
};
config = lib.mkIf config.desktop.enable {
services.pipewire = {
enable = true;
pulse.enable = true;
};
hardware.bluetooth.enable = true;
services.blueman.enable = true;
security.polkit.enable = true;
services.dbus.enable = true;
services.playerctld.enable = true;
xdg.portal = {
enable = true;
xdgOpenUsePortal = true;
extraPortals = [
pkgs.xdg-desktop-portal-wlr
pkgs.xdg-desktop-portal-gtk
];
};
fonts.packages = with pkgs; [
font-awesome
nerd-fonts.jetbrains-mono
maple-mono.NF
];
};
}

View File

@@ -1,20 +0,0 @@
{
lib,
config,
...
}:
{
options = {
gnupg = {
enable = lib.mkEnableOption "GnuPG agent with SSH support";
};
};
config = lib.mkIf config.gnupg.enable {
programs.gnupg.agent = {
enable = true;
enableSSHSupport = true;
enableExtraSocket = true;
};
};
}

View File

@@ -1,37 +0,0 @@
{
lib,
config,
pkgs,
inputs,
...
}:
{
options = {
greeter.enable = lib.mkEnableOption "greetd with regreet";
};
config = lib.mkIf config.greeter.enable {
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,83 +0,0 @@
{
lib,
config,
...
}:
let
# generate host keys for new machines: ./scripts/initrd-ssh-keygen.sh
keyDir = "/etc/secrets/initrd";
mkIpString =
{
address,
gateway,
netmask,
interface,
...
}:
"${address}::${gateway}:${netmask}::${interface}:none";
in
{
options = {
initrd-ssh = {
enable = lib.mkEnableOption "SSH in initrd for remote LUKS unlock";
ip = {
enable = lib.mkEnableOption "static IP for initrd (otherwise DHCP)";
address = lib.mkOption {
type = lib.types.str;
example = "10.222.0.247";
};
gateway = lib.mkOption {
type = lib.types.str;
example = "10.222.0.1";
};
netmask = lib.mkOption {
type = lib.types.str;
default = "255.255.255.0";
};
interface = lib.mkOption {
type = lib.types.str;
example = "enp5s0";
};
};
authorizedKeys = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
networkModule = lib.mkOption {
type = lib.types.str;
example = "r8169";
};
};
};
config = lib.mkIf config.initrd-ssh.enable {
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,28 +0,0 @@
{
lib,
config,
...
}:
{
options = {
localisation = {
enable = lib.mkEnableOption "localisation defaults";
timeZone = lib.mkOption {
type = lib.types.str;
};
defaultLocale = lib.mkOption {
type = lib.types.str;
};
};
};
config = lib.mkIf config.localisation.enable {
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 = config.localisation.timeZone;
};
}

View File

@@ -1,23 +0,0 @@
{
lib,
config,
...
}:
{
options = {
nvidia.enable = lib.mkEnableOption "NVIDIA GPU support";
};
config = lib.mkIf config.nvidia.enable {
hardware.graphics.enable = true;
services.xserver.videoDrivers = [ "nvidia" ];
hardware.nvidia = {
modesetting.enable = true;
open = true;
nvidiaSettings = true;
package = config.boot.kernelPackages.nvidiaPackages.stable;
};
};
}

View File

@@ -1,29 +0,0 @@
{
lib,
config,
...
}:
{
options = {
openssh = {
enable = lib.mkEnableOption "hardened SSH server";
port = lib.mkOption {
type = lib.types.port;
default = 22;
};
};
};
config = lib.mkIf config.openssh.enable {
services.openssh = {
enable = true;
ports = [ config.openssh.port ];
settings = {
PasswordAuthentication = false;
AllowUsers = null;
PermitRootLogin = "no";
StreamLocalBindUnlink = "yes";
};
};
};
}

View File

@@ -1,21 +0,0 @@
{
lib,
config,
...
}:
{
options = {
printing = {
enable = lib.mkEnableOption "CUPS printing with Avahi discovery";
};
};
config = lib.mkIf config.printing.enable {
services.printing.enable = true;
services.avahi = {
enable = true;
nssmdns4 = true;
openFirewall = true;
};
};
}

View File

@@ -1,54 +0,0 @@
{
pkgs,
lib,
config,
...
}:
{
options = {
sway = {
enable = lib.mkEnableOption "enable sway module";
cmdFlags = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
};
};
config = lib.mkIf config.sway.enable {
programs.sway = {
enable = true;
package = pkgs.swayfx;
wrapperFeatures.gtk = true;
extraOptions = config.sway.cmdFlags;
extraSessionCommands = ''
# fix for java awt apps not rendering
export _JAVA_AWT_WM_NONREPARENTING=1
'';
};
environment.systemPackages = with pkgs; [
waybar
mako
wob
playerctl
brightnessctl
foot
grim
pulseaudio
swayidle
swaylock-effects
jq
slurp
wl-clipboard
pamixer
wlsunset
satty
wayland-pipewire-idle-inhibit
fuzzel
cliphist
zenity
];
};
}

View File

@@ -1,74 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
inherit (config.vm-9p-automount) user;
inherit (config.users.users.${user}) home group;
in
{
options = {
vm-9p-automount = {
enable = lib.mkEnableOption "auto-discover and mount 9p shares";
user = lib.mkOption {
type = lib.types.str;
description = "user to own the mount points";
};
prefix = lib.mkOption {
type = lib.types.str;
default = "m_";
description = "9p mount tag prefix to match";
};
basePath = lib.mkOption {
type = lib.types.str;
default = "${home}/mnt";
description = "directory to mount shares under";
};
};
};
config = lib.mkIf config.vm-9p-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="${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

@@ -1,49 +0,0 @@
{
pkgs,
lib,
config,
...
}:
{
options = {
vm-guest = {
enable = lib.mkEnableOption "VM guest configuration";
headless = lib.mkOption {
type = lib.types.bool;
default = false;
description = "run without display, serial console only";
};
};
};
config = lib.mkIf config.vm-guest.enable {
services.qemuGuest.enable = true;
services.spice-vdagentd.enable = lib.mkIf (!config.vm-guest.headless) true;
boot.kernelParams = lib.mkIf config.vm-guest.headless [ "console=ttyS0,115200" ];
# 9p for host file mounting
boot.initrd.availableKernelModules = [
"9p"
"9pnet_virtio"
];
boot.kernelModules = [
"9p"
"9pnet_virtio"
];
networking = {
useDHCP = true;
firewall.allowedTCPPorts = [ 22 ];
};
security.sudo.wheelNeedsPassword = false;
environment.systemPackages = with pkgs; [
curl
wget
htop
sshfs
];
};
}

View File

@@ -1,31 +0,0 @@
{
lib,
config,
pkgs,
...
}:
{
options = {
workstation = {
enable = lib.mkEnableOption "workstation utilities";
};
};
config = lib.mkIf config.workstation.enable {
programs.nix-ld.enable = true;
virtualisation.docker = {
enable = true;
logDriver = "json-file";
};
services.tailscale = {
enable = true;
useRoutingFeatures = "both";
};
environment.systemPackages = with pkgs; [
smartmontools
];
};
}

View File

@@ -1,23 +0,0 @@
{
pkgs,
lib,
config,
...
}:
{
options = {
yubikey = {
enable = lib.mkEnableOption "enable yubikey module";
};
};
config = lib.mkIf config.yubikey.enable {
environment.systemPackages = with pkgs; [
yubikey-personalization
yubikey-manager
];
services.pcscd.enable = true;
};
}

View File

@@ -1,19 +0,0 @@
{
lib,
config,
...
}:
{
options = {
zsh = {
enable = lib.mkEnableOption "zsh with ZDOTDIR in ~/.config/zsh";
};
};
config = lib.mkIf config.zsh.enable {
programs.zsh.enable = true;
environment.etc."zshenv".text = ''
export ZDOTDIR=$HOME/.config/zsh
'';
};
}

10
nix.nix
View File

@@ -7,6 +7,16 @@
];
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 = {

View File

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

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

@@ -0,0 +1,53 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p curl jq nix
# shellcheck shell=bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PKG_FILE="$SCRIPT_DIR/package.nix"
# keep in sync with the `sources` attrset in package.nix
PLATFORMS=(linux-x64 linux-arm64 darwin-x64 darwin-arm64)
prefetch() {
local url="$1"
nix --extra-experimental-features 'nix-command flakes' \
store prefetch-file --unpack --json "$url" 2>/dev/null | jq -r '.hash'
}
main() {
echo "fetching latest version from npm..."
local latest current
latest=$(curl -sf "https://registry.npmjs.org/@anthropic-ai/claude-code/latest" | jq -r '.version')
current=$(grep 'version = ' "$PKG_FILE" | head -1 | sed 's/.*"\(.*\)".*/\1/')
if [[ "$current" == "$latest" ]]; then
echo "claude-code already at $latest"
return 0
fi
echo "updating claude-code: $current -> $latest"
sed -i "s|version = \"$current\"|version = \"$latest\"|" "$PKG_FILE"
local slug url new_hash old_hash
for slug in "${PLATFORMS[@]}"; do
url="https://registry.npmjs.org/@anthropic-ai/claude-code-${slug}/-/claude-code-${slug}-${latest}.tgz"
echo " prefetching $slug..."
new_hash=$(prefetch "$url")
old_hash=$(awk -v slug="$slug" '
$0 ~ "slug = \"" slug "\";" { found=1; next }
found && /hash = "sha256-/ {
match($0, /sha256-[A-Za-z0-9+\/]+=*/)
print substr($0, RSTART, RLENGTH)
exit
}
' "$PKG_FILE")
sed -i "s|$old_hash|$new_hash|" "$PKG_FILE"
echo " $new_hash"
done
echo "claude-code updated to $latest"
}
main "$@"

View File

@@ -1,17 +0,0 @@
{
lib,
config,
...
}:
{
options = {
profiles.base.enable = lib.mkEnableOption "base profile for all machines";
};
config = lib.mkIf config.profiles.base.enable {
openssh.enable = lib.mkDefault true;
zsh.enable = lib.mkDefault true;
localisation.enable = lib.mkDefault true;
gnupg.enable = lib.mkDefault true;
};
}

View File

@@ -1 +0,0 @@
{ lib, my-lib }: args: (my-lib.autoDir ./.)

View File

@@ -1,21 +0,0 @@
{
lib,
config,
...
}:
{
options = {
profiles.desktop.enable = lib.mkEnableOption "desktop profile (sway, audio, printing)";
};
config = lib.mkIf config.profiles.desktop.enable {
profiles.base.enable = lib.mkDefault true;
desktop.enable = lib.mkDefault true;
sway.enable = lib.mkDefault true;
greeter.enable = lib.mkDefault true;
printing.enable = lib.mkDefault true;
workstation.enable = lib.mkDefault true;
yubikey.enable = lib.mkDefault true;
calibre.enable = lib.mkDefault true;
};
}

View File

@@ -1,15 +0,0 @@
{
lib,
config,
...
}:
{
options = {
profiles.server.enable = lib.mkEnableOption "headless server profile";
};
config = lib.mkIf config.profiles.server.enable {
profiles.base.enable = lib.mkDefault true;
workstation.enable = lib.mkDefault true;
};
}

View File

@@ -27,23 +27,44 @@ 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)
--claude Mount claude config dir (requires CLAUDE_CONFIG_DIR)
--no-claude Skip mounting claude config dir
--disk-size <size> Resize guest disk (e.g. 50G)
--memory <size> VM memory (default: 8G)
--cpus <n> VM CPUs (default: 4)
--ssh-port <port> SSH port forward (default: 2222)
--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
-h, --help Show usage
EOF
exit "${1:-0}"
@@ -52,7 +73,9 @@ EOF
main() {
setup_colors
local ssh_port=2222 memory=8G cpus=4 claude=false disk_size=""
[ "$EUID" -eq 0 ] && die "ephvm-run.sh must not run as root"
local ssh_port="" memory=4G cpus=2 claude=true disk_size="" serial=false
local -a mounts=()
while [ $# -gt 0 ]; do
@@ -61,8 +84,8 @@ main() {
mounts+=("$2")
shift 2
;;
--claude)
claude=true
--no-claude)
claude=false
shift
;;
--disk-size)
@@ -81,6 +104,10 @@ main() {
ssh_port="$2"
shift 2
;;
--serial)
serial=true
shift
;;
-h | --help) usage ;;
*)
echo "${red}error:${reset} unknown option: $1" >&2
@@ -91,7 +118,9 @@ main() {
info "building ephvm image..."
local image_dir image
image_dir=$(nix build --no-link --print-out-paths .#nixosConfigurations.ephvm.config.system.build.images.qemu)
[ -n "${EPHVM_FLAKE:-}" ] || die "EPHVM_FLAKE must be set to the flake directory"
local flake="$EPHVM_FLAKE"
image_dir=$(nix build --no-link --print-out-paths "${flake}#nixosConfigurations.ephvm.config.system.build.images.qemu")
image=$(find "$image_dir" -name '*.qcow2' -print -quit)
[ -n "$image" ] || die "no qcow2 image found in $image_dir"
@@ -101,31 +130,49 @@ main() {
CLEANUP_OVERLAY=$(mktemp -d)
local overlay="$CLEANUP_OVERLAY/overlay.qcow2"
qemu-img create -f qcow2 -b "$(realpath "$image")" -F qcow2 "$overlay" "$disk_size"
drive_arg="file=$overlay,format=qcow2"
drive_arg="if=none,id=hd0,file=$overlay,format=qcow2,cache=writeback,aio=threads,discard=unmap,detect-zeroes=unmap"
else
drive_arg="file=$image,format=qcow2,snapshot=on"
drive_arg="if=none,id=hd0,file=$image,format=qcow2,snapshot=on,cache=writeback,aio=threads,discard=unmap,detect-zeroes=unmap"
fi
local accel="tcg"
[ -r /dev/kvm ] && accel="kvm"
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 -a qemu_args=(
qemu-system-x86_64
-accel "$accel"
-accel kvm
-cpu host
-m "$memory"
-smp "$cpus"
-drive "$drive_arg"
-nic "user,hostfwd=tcp::${ssh_port}-:22"
-device "virtio-blk-pci,drive=hd0"
-device virtio-rng-pci
-nic "$nic_arg"
-nographic
-sandbox "on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny"
)
if [ "$accel" != "tcg" ]; then
qemu_args+=(-cpu host)
fi
local fs_id=0 mount_path name tag
for mount_path in "${mounts[@]}"; do
[ -e "$mount_path" ] || die "--mount path does not exist: $mount_path"
mount_path=$(realpath "$mount_path")
# qemu parses -virtfs as csv, a comma in the path would inject options
case "$mount_path" in
*,*) die "--mount path may not contain commas: $mount_path" ;;
esac
name=$(basename "$mount_path")
tag="m_${name:0:29}"
qemu_args+=(
@@ -135,10 +182,13 @@ main() {
done
if [ "$claude" = true ]; then
[ -n "${CLAUDE_CONFIG_DIR:-}" ] || die "--claude requires CLAUDE_CONFIG_DIR to be set"
[ -n "${CLAUDE_CONFIG_DIR:-}" ] || die "CLAUDE_CONFIG_DIR must be set (use --no-claude to skip)"
mkdir -p "$CLAUDE_CONFIG_DIR"
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}"
@@ -147,10 +197,42 @@ main() {
fi
info "---"
info "Accel: $accel | SSH: ssh -p $ssh_port matej@localhost"
[ -n "$ssh_port" ] && info "SSH: ssh -p $ssh_port matej@localhost"
info "---"
exec "${qemu_args[@]}"
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 "$@"

0
secrets/.gitkeep Normal file
View File

55
secrets/common.yaml Normal file
View File

@@ -0,0 +1,55 @@
user-password: ENC[AES256_GCM,data:c7y3RZSikVS32w7RTY5nBSWxDWbwNI5FhLIEoXcru5lpCUu3YqKjHNm8eMI7oeAg1VQIW/1axv0LPHM+bb7wn7SSHy49EvGyda4AU8hdVnsO9gNBul9WQy9Q6RM1PR5vW+IbX1HBFPTTOQ==,iv:oNsDzDugNq2E1CJ89BCXZ/ieCGV+evOwsOuKlKsotBg=,tag:jU8g9fIgexw2bm3E+ow3wA==,type:str]
sops:
age:
- recipient: age1frwe9fpt9vh969aqnggvq8pfypp6hl98guwfmgttucp7gr55r42sqy2t65
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQOU9BTksxbXdjTkExRDJV
aFhVbUFGd1ZSeHFBL0lJRjdSRDRjcjl6ZEQ0Cjg2TFlNZENUWTh5aWNGck52TWFx
SC9LS0FrelFCWUI3RUZjdCs3cXF6aDQKLS0tIEwwTWkzOXgxUC9iTFgrQ0szRW8v
cGFMa2Rqd1VvWjU3Z2pUdExsdnJUT1EK9iQiW5qZszu65b0wEeq+9JnzzhiAS7fo
BmR9OWbDA4GZJBEPBJFD8KxIcO/BYFOjfW2A9PZaTsTpa50Z6+zWxw==
-----END AGE ENCRYPTED FILE-----
- recipient: age19qj2aaryx869cvcqp77gs9x5hcv4dqjxunkmyre78upsxda6ss7s5vquz4
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPczdWeTRxalA1M3R0S0Qr
eGdtZnJxaHllZ2lKMXJUZTN3VWhhTnU1TFRnCjM5OHoxMnEzKzdQelZEQ3ZZRFpt
aWRzaTg4dUc5OXpQUngrVmtSRkk1Q3MKLS0tIEZOd0FyMFRlRElWbjlHOVVkZlZP
eEhwRVRrcVgvQUx6bi85YWxDYjJZa28K7Hrk4fAqbjeYJfPJODvsth1p8JYbsfMf
a6gTckyeQWTNlE+1Tw6g18lvMP1dzIAYRPHtyzmxeCaETVMmSn7XxQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1hksdq2lc89thnpth49sw44f0pmkp950plrhhnttj4petvnfy04tsydz6fl
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPbUh0U1ZDc0ZOKzhxZ3Yv
ZHRwbzBIdVNsbEJmU0h4ZnpuTlNHSFdwRlQ0Ck1BREhlVFQ3bnVKajVlTGUzRjhN
cWpna25Ya1hoZG1ybFZ0REpCNTFTZmcKLS0tIG5ZQzNIWmd4a3J6YWxDQjYyY2px
Y085TkhubS9MSjJtMmZDakdZd2RhR3cKlGH906WAhXNDKwaWqHRoYO9bgiZau0ay
8ph3OLOVmrENPW3Othf17NDRet/nATFYZghBU/CI5CvZjr9n9SDYMg==
-----END AGE ENCRYPTED FILE-----
- recipient: age15cktenavt5v7zm84se36jtly740syca5nw8em8edx404n5x2ddws8jn29g
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwRG5zRkxOT0RyeXVpTDJu
TSt2SFFtSlFrSTk1YUZBMnAxTEM3dnFnQkFrClA0cnVpMkdsQWxCakNEZm16OEVG
dHFIUDA3TDJLdytySEJJMSsrMGZHcTgKLS0tIHhvdWNXaUthbDJqMWVYeWxuOGpL
L0lEZ3FVbmlOcndGUXUybXA4RDA2alUKQo5ctVmARPNY0POf2Ft6AxjwIN1N06C7
ft4YX+B4D61tUZ+uvFqHzmKsNpvDdoV81zxvGnnCnv0nSXwNghPFxw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-03-29T23:11:10Z"
mac: ENC[AES256_GCM,data:XY5wElDn+YD4UHSIGd9Ru8ob39gJVE8VE5gqJJkmzF/xERXp7re/d/6RXxoYDgYS0qUnn8c2VFzJxCvakmV/lPLA8YulFk/ZDysEVn+U3CbfTIkjXcJzewJNz0N+hQKeVaCzPfWeB5oaGtB8bjxOg+GYz2TmSvEAT+kO1U/4Klg=,iv:QOlZ4O+eqvOS9/guc+RmWgVDgPzskb4WIlzyT/14MVM=,tag:ziJE9Yytlr680EpSnBGmdQ==,type:str]
pgp:
- created_at: "2026-03-29T23:25:01Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hF4DPaEEpDtHdk8SAQdAPlvxgVq9o2boPPXWWwV6X3TjHZEl3lm9OcOj7lbsQxsw
5PTrX1rIV73XbRQUdFlnoYpUAwxh3UPULyA4+19fvCooC3L0FxA8e4wTiAdw6SKE
0l4BImy4sTiM8hNHXqB6u4rj3LbykCjesQve5C3fut62RV8x4cqUJHAB/aumQINT
QRXErylKmqo3h7ReRrCm2oOELauv4JFKNPi/cTE0MNh1+w9JxjoASoBufozDOxe4
=vIK+
-----END PGP MESSAGE-----
fp: AF349EECC849D87B790E88FF6318FFB7DB374B7D
unencrypted_suffix: _unencrypted
version: 3.12.1

28
secrets/floo.yaml Normal file
View File

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

28
secrets/tower.yaml Normal file
View File

@@ -0,0 +1,28 @@
nix-signing-key: ENC[AES256_GCM,data:V/mFaYQazqn3KkbDSt5Fnrl/IFvS9kEe10uhkPHeBluZGjFphKD+2dFCQrPPcXreX0UWklQA9Dokd2cGQBGZIUihJE9o9lH+Q6nrmqk3xsi1fzPS5l8zbn4RITmL3rNkmycXBw==,iv:g/jbUS88IBXnb9e6jGiWYHGfCZtdgI1X167hNmzUQEY=,tag:vO5kiN01FzU7s5jOCGW3Fg==,type:str]
sops:
age:
- recipient: age1frwe9fpt9vh969aqnggvq8pfypp6hl98guwfmgttucp7gr55r42sqy2t65
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjbkdXUW5YSTA4c3MyZzdi
ZlF0L2FQZmttbFBaVmlaWWppaXUxUVdYZEZZCmJHT25IZVBESHVqUWE2bnBYWXQ5
UTFLeXg3eUpyWngxc1FXUzhXRCs3R2MKLS0tIGxkbzFMaEUycCtpOC9mTitpVEZh
c0pROVJpMjJ6bHd1aEQ2QVE5MUUwdnMK/3tXEStP8JF/2c5nAJ19uA+P1cMG1X+v
H5b49uBJ+0UUGMzUpCLgMKz8bq+L8Se0b92iMW5bGW1Fdg/zwJWXOw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-03-29T21:47:29Z"
mac: ENC[AES256_GCM,data:573t4NH/764zZKzhhpVbzNzpN4QrBjwesIBMyHe7aB47ptGceLhnm+cHOhty3J89VBgn8jgHv5WCBzXFER0LDuQUMFPg6snJ0DK+IgRwuAwNbZdKdSR6VnjqOSBnaijU/Wx93kd/gcMqerYo6rEOLNjVadKgs+NYPLKC/dY4sVs=,iv:kOTr9CIvp6haV8BxTpQfdndYTjZRcmyg+7yjPjHRNLU=,tag:1odj8DYHSnOatRnqyZAcgg==,type:str]
pgp:
- created_at: "2026-03-29T21:46:47Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hF4DPaEEpDtHdk8SAQdA4NO+XFIyWa8YNV24yrosJKMQ60rmiEWYLjFdIkPrKz8w
cj1x62iDXeO6DYvyCZnw2h0WstIrXziX6PySveTVnCri90QdLl3jsolIW+V13b8V
0lEB5LFvx7OdZJPzrs32qiPv+ofleSMKAokPEhSTKccFI2GbyUiIw7ge2vHSjNpT
T9E3tA7HOglyopKTjFw/ujEhKDSRGXwdD2VEYH426Dt8JjU=
=E3fO
-----END PGP MESSAGE-----
fp: AF349EECC849D87B790E88FF6318FFB7DB374B7D
unencrypted_suffix: _unencrypted
version: 3.12.1

View File

@@ -1,22 +0,0 @@
{
pkgs,
inputs,
osConfig,
...
}:
{
home.stateVersion = "24.11";
# always-on
shell.enable = true;
dev.enable = true;
neovim = {
enable = true;
package = inputs.neovim-nightly-overlay.packages.${pkgs.stdenv.hostPlatform.system}.default;
};
claude.enable = true;
# desktop-conditional
desktop.enable = osConfig.desktop.enable;
}

View File

@@ -1,6 +0,0 @@
{
sshAuthorizedKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICQGLdINKzs+sEy62Pefng0bcedgU396+OryFgeH99/c janezicmatej"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDk00+Km03epQXQs+xEwwH3zcurACzkEH+kDOPBw6RQe openpgp:0xB095D449"
];
}

View File

@@ -1,27 +0,0 @@
{
lib,
config,
pkgs,
...
}:
let
keys = import ./keys.nix;
in
{
users.users.matej = {
uid = 1000;
isNormalUser = true;
home = "/home/matej";
shell = pkgs.zsh;
extraGroups = [
"wheel"
"docker"
];
openssh.authorizedKeys.keys = keys.sshAuthorizedKeys;
};
users.groups.matej = {
gid = 1000;
members = [ "matej" ];
};
}