1

System: Rename system/modules to system/systemmodules

This commit is contained in:
2026-01-18 15:34:46 +01:00
parent d12b247368
commit 25ae0f4b85
27 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1,14 @@
{
config,
lib,
mylib,
...
}: let
inherit (config.modules) TEMPLATE;
in {
options.modules.TEMPLATE = import ./options.nix {inherit lib mylib;};
config =
lib.mkIf TEMPLATE.enable {
};
}

View File

@ -0,0 +1,7 @@
{
lib,
mylib,
...
}: {
enable = lib.mkEnableOption "TEMPLATE";
}

View File

@ -0,0 +1,52 @@
{
config,
lib,
mylib,
pkgs,
username,
publicKeys,
...
}: let
inherit (config.modules) agenix;
in {
options.modules.agenix = import ./options.nix {inherit lib mylib;};
config = {
# NOTE: Add below snippet to home/christoph/default.nix to generate the secrets.nix file
# The user will be able to decrypt .age files using agenix.
# On each user/machine, this should generate a corresponding secrets.nix
# "${config.paths.nixflake}/system/modules/agenix/secrets.nix".text = let
# mkSecret = key: name: "\"${name}.age\".publicKeys = [\"${key}\"];";
# in ''
# # This file will contain keys depending on the host/by which user it was built on.
# {
# ${lib.optionalString
# # If this user defined any secrets...
# (builtins.hasAttr "${username}" nixosConfig.modules.agenix.secrets)
# # ...we will add them to the current secrets.nix,
# # s.t. agenix can be used to encrypt/access them.
# (builtins.concatStringsSep "\n"
# (builtins.map
# (mkSecret publicKeys.${username}.ssh)
# nixosConfig.modules.agenix.secrets.${username}))}
# }
# '';
# Register generated secrets to the age system module
age.secrets = let
mkSecretIfExists = name:
# If this user has already encrypted the secret...
if builtins.pathExists ./${name}.age
# ...we will register it with age...
then {${name}.file = ./${name}.age;}
# ...otherwise we link to a bogus file.
else {${name}.file = ./void.age;};
in
lib.mkIf
# If this user defined any secrets...
(builtins.hasAttr "${username}" agenix.secrets)
# ...we will register all secrets files that have already been generated.
(lib.mkMerge (builtins.map mkSecretIfExists agenix.secrets.${username}));
};
}

View File

@ -0,0 +1,22 @@
{
lib,
mylib,
...
}: {
secrets = lib.mkOption {
type = lib.types.attrs;
description = "The secret files managed by agenix (encrypted by SSH key)";
example = ''
{
christoph = [
"heidi-discord-token"
"kopia-password"
"kopia-server-username"
"kopia-server-password"
];
}
'';
default = {};
};
}

View File

@ -0,0 +1 @@
This secret has not been generated.

View File

@ -0,0 +1,76 @@
# TODO: Generate file with names for rofi
{
config,
nixosConfig,
lib,
mylib,
pkgs,
...
}:
with lib;
with mylib.virtualisation;
with mylib.modules; let
cfg = config.modules.containers;
in {
options.modules.containers = import ./options.nix {inherit lib mylib;};
# TODO: These need config options exposed through the module,
# e.g. to set paths/volumes/binds differently per system...
config = mkIf cfg.enable rec {
virtualisation.oci-containers.containers = {
# Examples how to use the mkOciContainer function:
# stablediffusion = mkIf cfg.stablediffusion.enable (mkOciContainer {
# image = "rocm/pytorch:rocm5.5_ubuntu20.04_py3.8_pytorch_1.13.1";
# vols = [
# "/home/christoph/NoSync/StableDiffusionWebUI:/webui-data"
# ];
# opts = [
# "--network=host"
# "--device=/dev/kfd"
# "--device=/dev/dri"
# "--group-add=video"
# "--ipc=host"
# "--cap-add=SYS_PTRACE"
# "--security-opt=seccomp=unconfined"
# ];
# extraConfig = {
# entrypoint = "/webui-data/launch.sh";
# };
# });
# sonarr = mkIf cfg.sonarr.enable (mkOciContainer {
# image = "linuxserver/sonarr:3.0.10";
# id-ports = [8989];
# vols = [
# "sonarr-config:/config:Z"
# "/media/Shows:/media/Shows"
# "/media/Usenet:/media/Usenet"
# ];
# netns = "wg0-de-115";
# netdns = "10.2.0.1";
# });
};
# Allow start/stop containers without root password
modules.polkit.allowedSystemServices = let
container-services =
virtualisation.oci-containers.containers
|> builtins.attrNames
|> builtins.filter (c: cfg.${c}.enable)
|> builtins.map (c: "podman-${c}.service");
in
container-services;
# Generate list of containers for rofi menu
environment.etc."rofi-containers".text = let
containers =
virtualisation.oci-containers.containers
|> builtins.attrNames
|> builtins.filter (c: cfg.${c}.enable)
|> builtins.concatStringsSep "\n";
in
containers;
};
}

View File

@ -0,0 +1,50 @@
# TODO: Rofi Integration
# - Hotkey through hyprland module
# - Menu through rofi module
# - Permissions through polkit module
{
lib,
mylib,
...
}:
with lib;
with mylib.modules; {
enable = mkEnableOption "Enable OCI Containers";
homeassistant = {
enable = mkEnableOption "Enable HomeAssistant Container";
};
stablediffusion = {
enable = mkEnableOption "Enable StableDiffusion Container with Automatic1111 WebUI";
};
jellyfin = {
enable = mkEnableOption "Enable Jellyfin Container";
};
fileflows = {
enable = mkEnableOption "Enable FileFlows Container";
};
sonarr = {
enable = mkEnableOption "Enable Sonarr Container";
};
radarr = {
enable = mkEnableOption "Enable Radarr Container";
};
hydra = {
enable = mkEnableOption "Enable Hydra Container";
};
sabnzbd = {
enable = mkEnableOption "Enable SabNzbd Container";
};
rofiIntegration = {
enable = mkEnableOption "Enable Rofi Menu for Container Servicing";
hotkey = mkOption {
type = types.str;
example = ''
"$mainMod, D"
'';
default = "$mainMod, D";
description = "What Key should trigger the Menu";
};
};
}

View File

@ -0,0 +1,51 @@
{
pkgs,
config,
lib,
mylib,
...
}: let
inherit (config.modules) bootloader;
in {
options.modules.bootloader = import ./options.nix {inherit lib mylib;};
config = lib.mkIf bootloader.enable (lib.mkMerge [
{
boot.loader = {
timeout = 10;
efi.canTouchEfiVariables = true;
efi.efiSysMountPoint = bootloader.systemd-boot.bootDevice;
};
}
(lib.mkIf (bootloader.loader == "systemd-boot") {
boot.loader.systemd-boot = {
enable = true;
configurationLimit = 5;
editor = false;
consoleMode = "max";
};
})
(lib.mkIf (bootloader.loader == "grub") {
boot.loader.grub = {
enable = true;
useOSProber = true;
device = bootloader.grub.bootDevice;
};
})
(lib.mkIf (bootloader.loader == "lanzaboote") {
environment.systemPackages = with pkgs; [
sbctl
];
# Lanzaboote replaces systemd-boot
boot.loader.systemd-boot.enable = lib.mkForce false;
boot.lanzaboote = {
enable = true;
# WARN: Make sure to persist this if using impermanence!
pkiBundle = "/var/lib/sbctl";
};
})
]);
}

View File

@ -0,0 +1,32 @@
{
lib,
mylib,
...
}: {
enable = lib.mkEnableOption "Enable boot loader configuration";
loader = lib.mkOption {
type = lib.types.enum [
"grub"
"systemd-boot"
"lanzaboote"
];
description = "What boot loader to use";
example = "systemd-boot";
default = "systemd-boot";
};
systemd-boot.bootDevice = lib.mkOption {
type = lib.types.str;
description = "The path to the boot partition";
example = "/boot/efi";
default = "/boot/efi";
};
grub.bootDevice = lib.mkOption {
type = lib.types.str;
description = "The path to the boot partition";
example = "/dev/sda";
default = "/dev/sda";
};
}

View File

@ -0,0 +1,13 @@
{...}: {
imports = [
./bootloader
./desktopportal
./docker
./fonts
./impermanence
./mime
./network
./polkit
./sops-nix
];
}

View File

@ -0,0 +1,66 @@
{
config,
lib,
mylib,
pkgs,
...
}: let
inherit (config.modules) desktopportal;
in {
options.modules.desktopportal = import ./options.nix {inherit lib mylib;};
config = lib.mkIf desktopportal.enable {
xdg = {
portal = {
enable = true;
xdgOpenUsePortal = true;
wlr.enable = true;
# TODO: Replace lib.optional(s) throughout the config with mkMerge
config = lib.mkMerge [
{
# https://discourse.nixos.org/t/clicked-links-in-desktop-apps-not-opening-browers/29114/26
common.default = ["*"];
}
(lib.mkIf desktopportal.termfilechooser.enable {
common."org.freedesktop.impl.portal.FileChooser" = ["termfilechooser"];
})
(lib.mkIf desktopportal.hyprland.enable {
hyprland.default = ["hyprland"];
})
(lib.mkIf
(desktopportal.hyprland.enable && desktopportal.termfilechooser.enable) {
hyprland."org.freedesktop.impl.portal.FileChooser" = ["termfilechooser"];
})
(lib.mkIf desktopportal.niri.enable {
niri.default = ["gtk" "gnome"];
})
(lib.mkIf (desktopportal.niri.enable && desktopportal.termfilechooser.enable) {
niri."org.freedesktop.impl.portal.FileChooser" = ["termfilechooser"];
})
];
extraPortals = with pkgs;
lib.mkMerge [
[
xdg-desktop-portal-gtk # Fallback
]
# We don't need to install that explicitly
# (lib.mkIf desktopportal.hyprland.enable [
# xdg-desktop-portal-hyprland
# ])
(lib.mkIf desktopportal.termfilechooser.enable [
xdg-desktop-portal-termfilechooser # Filechooser using yazi
])
];
};
};
};
}

View File

@ -0,0 +1,10 @@
{
lib,
mylib,
...
}: {
enable = lib.mkEnableOption "Enable XDG desktop portals";
termfilechooser.enable = lib.mkEnableOption "Enable xdg-desktop-portal-termfilechooser";
hyprland.enable = lib.mkEnableOption "Configure portals for Hyprland";
niri.enable = lib.mkEnableOption "Configure portals for Niri";
}

View File

@ -0,0 +1,146 @@
{
config,
lib,
mylib,
pkgs,
...
}: let
inherit (config.modules) docker;
in {
options.modules.docker = import ./options.nix {inherit lib mylib;};
config = lib.mkIf docker.enable {
environment.variables = lib.mkMerge [
(lib.mkIf ((!docker.podman) && docker.docker.buildkit) {
DOCKER_BUILDKIT = 1;
})
];
networking.firewall.trustedInterfaces = ["docker0" "podman0"];
# Needed for default bridge network to automatically work
# boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
# boot.kernel.sysctl."net.ipv6.ip_forward" = 1;
virtualisation = {
docker = {
enable = !docker.podman;
autoPrune.enable = true;
extraPackages = with pkgs; [docker-compose];
# TODO: Rootless docker has no internet?
rootless = {
enable = docker.docker.rootless;
setSocketVariable = true;
};
daemon.settings = {
# ipv6 = true;
# fixed-cidr-v6 = "2001::/80";
dns = [
"8.8.8.8"
# "2001:4860:4860::8888"
# "127.0.0.1"
# "192.168.86.25"
];
hosts = [
# Allow access to docker socket
"tcp://0.0.0.0:2375"
"unix:///var/run/docker.sock"
];
};
};
podman = {
enable = docker.podman;
autoPrune.enable = true;
dockerCompat = true;
dockerSocket.enable = true;
defaultNetwork.settings.dns_enabled = true;
extraPackages = with pkgs; [podman-compose];
};
oci-containers.backend =
if docker.podman
then "podman"
else "docker"; # "docker" or "podman"
libvirtd.enable = true;
};
systemd.services = let
cli =
if docker.podman
then "${config.virtualisation.podman.package}/bin/podman"
else "${config.virtualisation.docker.package}/bin/docker";
mkDockerNetwork = options:
builtins.concatStringsSep "\n" [
# Make sure to return true on fail to not crash
''
check=$(${cli} network inspect ${options.name} || true)
if [ -z "$check" ]; then
''
(builtins.concatStringsSep " " [
"${cli} network create"
# Disable masquerading
(lib.optionalString
options.disable_masquerade
''-o "com.docker.network.bridge.enable_ip_masquerade"="false"'')
# Enable ipv6
(lib.optionalString
options.ipv6.enable
"--ipv6")
(lib.optionalString
(!(builtins.isNull options.ipv6.gateway))
''--gateway="${options.ipv6.gateway}"'')
(lib.optionalString
(!(builtins.isNull options.ipv6.subnet))
''--subnet="${options.ipv6.subnet}"'')
"${options.name}"
])
''
else
echo "Network ${options.name} already exists!"
fi
''
];
mkPodmanNetwork = options:
builtins.concatStringsSep "\n" [
''
echo "Can't create Podman networks (yet)!"
''
];
mkSystemdNetworkService = options: let
toolName =
if docker.podman
then "podman"
else "docker";
in {
"${toolName}-create-${options.name}-network" = {
description = "Creates the ${toolName} network \"${options.name}\"";
after = ["network.target"];
wantedBy = ["multi-user.target"];
serviceConfig.Type = "oneshot";
script =
if docker.podman
then (mkPodmanNetwork options)
else (mkDockerNetwork options);
};
};
in
lib.mkMerge (builtins.map mkSystemdNetworkService docker.networks);
};
}

View File

@ -0,0 +1,61 @@
{
lib,
mylib,
...
}: {
enable = lib.mkEnableOption "Enable light virtualization using containers";
podman = lib.mkEnableOption "Use podman instead of docker";
docker.rootless = lib.mkEnableOption "Use rootless docker (no effect if podman is used)";
docker.buildkit = lib.mkEnableOption "Use Docker BuildKit (no effect if podman is used)";
networks = lib.mkOption {
type = lib.types.listOf (lib.types.submodule ({
lib,
mylib,
...
}: {
options = {
name = lib.mkOption {
type = lib.types.str;
description = "The name of the docker/podman network";
example = "behind-nginx";
};
disable_masquerade = lib.mkEnableOption "Disable IP masquerading for this network";
ipv6 = {
enable = lib.mkEnableOption "Enable IPv6 for this network";
gateway = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "The IPv6 gateway for this network";
example = "2000::1";
default = null;
};
subnet = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "The IVv6 subnet mask for this network";
example = "2000::/80";
default = null;
};
};
};
}));
description = "Docker/Podman networks to create";
example = ''
{
behind-nginx = {
disable_masquerade = false;
ipv6 = {
enable = true;
gateway = "2000::1";
subnet = "2000::/80";
};
}
}
'';
default = [];
};
}

View File

@ -0,0 +1,55 @@
{
config,
lib,
mylib,
pkgs,
...
}: let
inherit (config.modules) fonts;
in {
options.modules.fonts = import ./options.nix {inherit lib mylib;};
config = lib.mkIf fonts.enable {
fonts = {
# Some default fonts for unicode coverage
enableDefaultPackages = true;
# Puts fonts to /run/current-system/sw/share/X11/fonts
# https://wiki.nixos.org/wiki/Fonts#Flatpak_applications_can.27t_find_system_fonts
fontDir.enable = true;
# Font packages go here.
# They are installed system-wide so they land in fontdir,
# this is required for flatpak to find them.
packages = with pkgs; [
# Monospace fonts
nerd-fonts.jetbrains-mono
nerd-fonts.victor-mono
monolisa
# Sans/Serif fonts
noto-fonts
noto-fonts-color-emoji
noto-fonts-cjk-sans
lxgw-wenkai
];
fontconfig = {
enable = true;
antialias = true;
hinting.enable = true;
hinting.autohint = true;
cache32Bit = true;
# https://wiki.nixos.org/wiki/Fonts#Noto_Color_Emoji_doesn.27t_render_on_Firefox
useEmbeddedBitmaps = true;
defaultFonts = {
serif = [fonts.defaultSerifFont] ++ fonts.fallbackSerifFonts;
sansSerif = [fonts.defaultSansSerifFont] ++ fonts.fallbackSansSerifFonts;
monospace = [fonts.defaultMonoFont] ++ fonts.fallbackMonoFonts;
};
};
};
};
}

View File

@ -0,0 +1,69 @@
{
lib,
mylib,
...
}: {
enable = lib.mkEnableOption "Enable fonts configuration";
defaultSerifFont = lib.mkOption {
type = lib.types.str;
description = "Select default serif font";
example = ''
"Noto Serif CJK SC"
'';
default = "Noto Serif CJK SC";
};
defaultSansSerifFont = lib.mkOption {
type = lib.types.str;
description = "Select default sans-serif font";
example = ''
"Noto Sans CJK SC"
'';
default = "Noto Sans CJK SC";
};
defaultMonoFont = lib.mkOption {
type = lib.types.str;
description = "Select default monospace font";
example = ''
"MonoLisa Alt Script"
'';
default = "MonoLisa Alt Script";
};
fallbackSerifFonts = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Select fallback serif fonts";
example = ''
[
"Noto Serif CJK SC"
]
'';
default = [];
};
fallbackSansSerifFonts = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Select fallback sans-serif fonts";
example = ''
[
"Noto Sans CJK SC"
]
'';
default = [];
};
fallbackMonoFonts = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Select fallback monospace fonts";
example = ''
[
"JetBrainsMono Nerd Font Mono"
]
'';
default = [
"JetBrainsMono Nerd Font Mono"
];
};
}

View File

@ -0,0 +1,384 @@
{
config,
lib,
mylib,
username,
pkgs,
...
}: let
inherit (config.modules) impermanence;
in {
options.modules.impermanence = import ./options.nix {inherit lib mylib;};
config = let
# NOTE: Setting user/group/mode only has an effect if the
# directory is created by impermanence!
m777 = "u=rwx,g=rwx,o=rwx";
m755 = "u=rwx,g=rx,o=rx";
m711 = "u=rwx,g=x,o=x";
m700 = "u=rwx,g=,o=";
m644 = "u=rw,g=r,o=r";
m600 = "u=rw,g=,o=";
m444 = "u=r,g=r,o=r";
mkDir = user: directory: mode: {
inherit directory mode;
user = config.users.users.${user}.name;
group = config.users.users.${user}.group;
};
mkFile = user: file: mode: {
inherit file;
# This doesn't make much sense to set generally, e.g. when
# linking multiple files to ~/.config (they all have the same parent directory)
# parentDirectory = {
# inherit mode;
# user = config.users.users.${user}.name;
# group = config.users.users.${user}.group;
# };
};
mkRDir = mkDir "root";
mkRFile = mkFile "root";
mkUDir = mkDir "${username}";
mkUFile = mkFile "${username}";
# TODO: sth. like this, make options for configdirs/sharedirs/statedirs/homedirs
# populate options from respective modules, not here...
# mkConfigDirs = dirs:
# dirs
# |> builtins.map (dir: ".config/${dir}")
# |> builtins.map mkUDir # NOTE: mkUDir has wrong arg order
in
lib.mkIf impermanence.enable {
# TODO: Create options to allow host-specific impermanence setup
# inside the respective modules
environment.persistence."/persist" = {
hideMounts = false; # Sets x-gvfs-hide option
files = [
(mkRFile "/etc/adjtime" m644)
(mkRFile "/etc/machine-id" m444)
];
directories = [
(mkRDir "/etc/NetworkManager" m755)
(mkRDir "/etc/secureboot" m755)
(mkRDir "/etc/ssh" m755)
# https://github.com/nix-community/impermanence/issues/253
(mkRDir "/usr/systemd-placeholder" m755)
(mkRDir "/var/cache/restic-backups-synology" m755)
(mkRDir "/var/db/sudo" m711)
(mkRDir "/var/lib/bluetooth" m755) # m700
(mkRDir "/var/lib/btrfs" m755)
(mkRDir "/var/lib/containers" m755)
(mkRDir "/var/lib/flatpak" m755)
(mkRDir "/var/lib/libvirt" m755)
(mkRDir "/var/lib/NetworkManager" m755)
(mkRDir "/var/lib/nixos" m755)
(mkRDir "/var/lib/sbctl" m755)
(mkRDir "/var/lib/systemd" m755)
(mkRDir "/var/tmp" m777)
];
users.${username} = {
files = [
# NOTE: Don't put files generated/linked by HM here (they're already managed)
# TODO: Specifying files here (e.g. .config/QtProject.conf) doesn't seem to work
# They won't get mounted, also they can't be unmounted (because they're not mounted),
# which leads to /home not being unmounted correctly during shutdown...
];
directories = [
# Home directory
(mkUDir "Downloads" m755)
(mkUDir "Documents" m755)
(mkUDir "GitRepos" m755)
(mkUDir "NixFlake" m755)
(mkUDir "Notes" m755)
(mkUDir "Pictures" m755)
(mkUDir "Projects" m755)
(mkUDir "Public" m755)
# (mkUDir "Unity" m755)
(mkUDir "Videos" m755)
# Secrets
(mkUDir ".gnupg" m755) # m600
(mkUDir ".secrets" m755) # m644
(mkUDir ".ssh" m755) # m644
# The shit some applications add to ~/ without asking
# (mkUDir ".android" m755) # Unity
# (mkUDir ".gradle" m755) # Unity
# (mkUDir ".java" m755) # Unity/Rider
(mkUDir ".MakeMKV" m755)
(mkUDir ".mozilla/firefox" m755) # TODO: Remove this someday
(mkUDir ".mozilla/native-messaging-hosts" m755)
(mkUDir ".nix-package-search" m755)
# (mkUDir ".nv" m755) # Unity
(mkUDir ".ollama" m755)
# (mkUDir ".plastic4" m755) # Unity
(mkUDir ".var/app" m755)
(mkUDir ".vim/undo" m755)
(mkUDir ".zotero" m755)
# Cache that's actually useful
(mkUDir ".cache/fish/generated_completions" m755)
(mkUDir ".cache/nix-index" m755)
(mkUDir ".cache/nix-search-tv" m755)
(mkUDir ".cache/nvim" m755)
# Config
# (mkUDir ".config/.android" m755) # Unity
(mkUDir ".config/beets" m755)
(mkUDir ".config/blender" m755)
(mkUDir ".config/chromium" m755) # TODO: Remove this someday
(mkUDir ".config/Ferdium" m755)
(mkUDir ".config/fish/completions" m755)
(mkUDir ".config/impermanence" m755)
(mkUDir ".config/jellyfin-mpv-shim" m755)
(mkUDir ".config/JetBrains" m755)
(mkUDir ".config/kdeconnect" m755)
(mkUDir ".config/keepassxc" m755)
(mkUDir ".config/Msty" m755)
(mkUDir ".config/Nextcloud" m755)
(mkUDir ".config/niri/dms" m755)
(mkUDir ".config/obsidian" m755)
(mkUDir ".config/obs-studio" m755)
(mkUDir ".config/Signal" m755)
# (mkUDir ".config/singularitygroup-hotreload" m755) # Unity
(mkUDir ".config/TeamSpeak" m755)
(mkUDir ".config/tidal-hifi" m755)
(mkUDir ".config/tidal_dl_ng" m755)
# (mkUDir ".config/unity3d" m755) # Unity
# (mkUDir ".config/unityhub" m755) # Unity
(mkUDir ".config/vlc" m755)
(mkUDir ".config/Zeal" m755)
# Share
# (mkUDir ".local/share/containers" m755) # Rootless docker
(mkUDir ".local/share/direnv" m755)
(mkUDir ".local/share/docker" m755)
(mkUDir ".local/share/fish" m755)
(mkUDir ".local/share/flatpak" m755)
(mkUDir ".local/share/JetBrains" m755) # Unity
(mkUDir ".local/share/hyprland" m755)
(mkUDir ".local/share/keyrings" m755) # m700
(mkUDir ".local/share/LRCGET" m755)
(mkUDir ".local/share/mime" m755)
(mkUDir ".local/share/net.lrclib.lrcget" m755)
(mkUDir ".local/share/nix" m755)
(mkUDir ".local/share/nvim" m755)
(mkUDir ".local/share/qutebrowser" m755)
(mkUDir ".local/share/systemd" m755)
# (mkUDir ".local/share/unity3d" m755) # Unity
(mkUDir ".local/share/zoxide" m755)
# State
(mkUDir ".local/state/astal/notifd" m755)
(mkUDir ".local/state/home-manager/gc-roots" m755) # nix-flatpak stores its state there
(mkUDir ".local/state/lazygit" m755)
(mkUDir ".local/state/nix" m755)
(mkUDir ".local/state/nvim" m755)
(mkUDir ".local/state/wireplumber" m755)
];
};
};
# Add some helper scripts to identify files that might need persisting
environment.systemPackages = let
base = {
"root" = "/";
"home" = "/home/${username}";
};
ignore = {
"root" = "/home/${username}/.config/impermanence/fdignore-root";
"home" = "/home/${username}/.config/impermanence/fdignore-home";
};
move = {
"root" = "/persist/$(dirname {})";
"home" = "/persist/home/${username}/$(dirname {})";
};
mkHeader = "Press CTRL-R to reload, CTRL-M to move, CTRL-I to ignore file";
mkPreview = mode: "bat --color=always --theme=ansi --style=numbers --line-range=:100 ${base.${mode}}/{}";
mkReload = mode: "sudo fd --one-file-system --type f --hidden --base-directory ${base.${mode}} --ignore-file ${ignore.${mode}}";
mkIgnore = mode: "echo '{}' >> ${ignore.${mode}}";
mkMove = mode: "sudo mkdir -p ${move.${mode}} && sudo mv {} ${move.${mode}}";
mkScript = mode: ''
sudo ${mkReload mode} | \
sudo fzf \
--header "${mkHeader}" \
--preview "${mkPreview mode}" \
--bind "ctrl-r:reload:(${mkReload mode})" \
--bind "ctrl-i:execute:(${mkIgnore mode})" \
--bind "ctrl-m:execute:(${mkMove mode})"
'';
newroot = pkgs.writeShellScriptBin "newroot" (mkScript "root");
newhome = pkgs.writeShellScriptBin "newhome" (mkScript "home");
in [
newroot
newhome
];
# NOTE: This is REQUIRED for HM activation!
# Otherwise the home directory won't be writable!
systemd.services."impermanence-fix-home-ownership" = let
homeDir = "/home/${username}";
homeUser =
builtins.toString
config.users.users.${username}.uid;
homeGroup =
builtins.toString
config.users.groups.${config.users.users.${username}.group}.gid;
in {
description = "Fix impermanent home ownership";
wantedBy = ["home-manager-${username}.service"];
before = ["home-manager-${username}.service"];
after = ["home.mount"];
partOf = ["home.mount"];
serviceConfig.Type = "oneshot";
script = ''
# Don't chown if NFS shares are already mounted.
# This can happen outside of regular booting (e.g. nixos-rebuild switch),
# so we don't return an error.
# NOTE: Use || true as NixOS sets the damn -e, otherwise this unit fails on boot!
nfs_mounts=$(grep ' nfs4 ' /proc/mounts || true)
if [[ -n "$nfs_mounts" ]]; then
echo "NFS shares are mounted into the home directory, aborting:"
echo "$nfs_mounts"
exit 0
else
echo "No NFS shares are mounted into the home directory, continuing..."
fi
if [[ -d ${homeDir} ]]; then
chown -R ${homeUser}:${homeGroup} ${homeDir}
echo "Set ownership for ${homeDir} to ${homeUser}:${homeGroup}"
else
echo "ERROR: Home ${homeDir} does not exist!"
exit 1
fi
'';
};
# Because we have a LUKS encrypted drive
# we use a systemd service to cleanup the volumes
boot.initrd.systemd = {
enable = true;
services.impermanence-btrfs-cleanup = let
backupDuration = "7"; # Days
mountDir = "/btrfs_tmp";
persistDir = "${mountDir}/persist";
in {
description = "Clean impermanent btrfs subvolumes";
wantedBy = ["initrd.target"];
after = ["systemd-cryptsetup@crypted.service"];
before = ["sysroot.mount"];
unitConfig.DefaultDependencies = "no";
serviceConfig.Type = "oneshot";
# NOTE: If any single line of this script fails
# the entire system might be bricked.
# NixOS automatically sets "-e", so if unlucky,
# the subvolumes won'e exist for mounting...
script = let
mvSubvolToPersist = subvol: ''
if [[ -e ${mountDir}/${subvol} ]]; then
mkdir -p ${persistDir}/old_${subvol}s
timestamp=$(date --date="@$(stat -c %Y ${mountDir}/${subvol})" "+%Y-%m-%-d_%H:%M:%S")
mv ${mountDir}/${subvol} "${persistDir}/old_${subvol}s/$timestamp"
# Make the backup mutable (in case it is not, e.g. /var/empty)
# chattr -R -i -f "${persistDir}/old_${subvol}s/$timestamp"
echo "Backed up previous ${subvol} subvolume to ${persistDir}/old_${subvol}s/$timestamp"
fi
'';
mkNewSubvol = subvol: ''
if [[ ! -e ${mountDir}/${subvol} ]]; then
btrfs subvolume create ${mountDir}/${subvol}
echo "Created new subvolume ${mountDir}/${subvol}"
else
echo "Failed to move ${mountDir}/${subvol} (${mountDir}/${subvol} still exists), not creating new subvolume..."
fi
'';
# TODO: This fails and bricks the system
deleteOldBackups = subvol: ''
for old_${subvol} in $(find ${persistDir}/old_${subvol}s/ -maxdepth 1 -mtime +${backupDuration}); do
delete_subvolume_recursively "$old_${subvol}"
done
'';
in ''
# This dir will be created in the initrd ramdisk
mkdir -p ${mountDir}
# We mount the root subvolume. Because we're using a flat btrfs layout,
# "/" contains the subfolders (-volumes) home, log, nix, persist, root, swap, ...
mount -o subvol=/ /dev/mapper/crypted ${mountDir}
# Check if the persist dir exists so we can move stuff to it
if [[ ! -e ${persistDir} ]]; then
echo "${persistDir} doesn't exist, aborting..."
umount ${mountDir}
rmdir ${mountDir}
exit 0
fi
# Move root subvolume to backup location
${mvSubvolToPersist "root"}
# Move home subvolume to backup location
${mvSubvolToPersist "home"}
# Create new root subvolume
${mkNewSubvol "root"}
# Create new home subvolume
${mkNewSubvol "home"}
# TODO: Did this removal of old backups always brick the system?
# Delete a backed up subvolume
# delete_subvolume_recursively() {
# IFS=$'\n'
# # https://github.com/nix-community/impermanence/issues/258#issuecomment-2733383737
# # If we accidentally end up with a file or directory under old_roots,
# # the code will enumerate all subvolumes under the main volume.
# # We don't want to remove everything under true main volume. Only
# # proceed if this path is a btrfs subvolume (inode=256).
# if [ $(stat -c %i "$1") -ne 256 ]; then return; fi
# for subvol in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do
# delete_subvolume_recursively "${persistDir}/$subvol"
# done
# btrfs subvolume delete "$1"
# echo "Deleted old subvolume $1"
# }
# Delete old root backups
# $ {deleteOldBackups "root"}
# Delete old home backups
# $ {deleteOldBackups "home"}
umount ${mountDir}
rmdir ${mountDir}
'';
};
};
};
}

View File

@ -0,0 +1,9 @@
{
lib,
mylib,
...
}: {
enable = lib.mkEnableOption "Enable opt-in state using impermanence.";
# TODO: Options for host-specific config
}

View File

@ -0,0 +1,94 @@
{
config,
lib,
mylib,
...
}: let
inherit (config.modules) mime;
in {
options.modules.mime = import ./options.nix {inherit lib mylib;};
config = lib.mkIf mime.enable {
xdg = {
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
# Find .desktop files: fd ".*\.desktop" / | grep --color=auto -E neovide
mime = rec {
enable = true;
defaultApplications = let
associations =
{
${mime.defaultTextEditor} = mime.textTypes;
${mime.defaultFileBrowser} = ["inode/directory"];
${mime.defaultWebBrowser} = mime.webTypes;
${mime.defaultPdfViewer} = ["application/pdf"];
${mime.defaultImageViewer} = mime.imageTypes;
# If audio and video player are equal, we assign all types to the audio player,
# as multiple identical keys cannot exist in attrsets.
${mime.defaultAudioPlayer} =
mime.audioTypes
++ (lib.optionals
(mime.defaultAudioPlayer == mime.defaultVideoPlayer)
mime.videoTypes);
}
# If audio and video player are not equal, we associate the video types with
# the chosen video player. Otherwise video types will be included with audio.
// (lib.optionalAttrs (mime.defaultAudioPlayer != mime.defaultVideoPlayer) {
${mime.defaultVideoPlayer} = mime.videoTypes;
});
# Applied to a single app and a single type
# Result: { "image/jpg" = ["imv.desktop"]; }
mkAssociation = app: type: {${type} = [app];};
# Applied to a single app and a list of types
# Result: { "image/jpg" = ["imv.desktop"]; "image/png" = ["imv.desktop"]; ... }
mkAssociations = app: types:
lib.mergeAttrsList
(builtins.map (mkAssociation app) types);
in
# Apply to a list of apps each with a list of types
lib.mergeAttrsList (lib.mapAttrsToList mkAssociations associations);
addedAssociations = defaultApplications;
removedAssociations = let
# Applied to a list of apps and a single type
removeAssociation = apps: type: {${type} = apps;};
# Applied to a list of apps and a list of types:
# For each type the list of apps should be removed
removeAssociations = apps: types:
lib.mergeAttrsList
(builtins.map (removeAssociation apps) types);
# Only create if more than 0 apps are specified: (len from...Types) > 0
mkIfExists = apps: types:
lib.optionalAttrs
(builtins.lessThan 0 (builtins.length apps))
(removeAssociations apps types);
in
lib.mergeAttrsList [
{
"application/pdf" = [
"chromium-browser.desktop"
"com.google.Chrome.desktop"
"firefox.desktop"
];
"text/plain" = [
"firefox.desktop"
"code.desktop"
];
}
(mkIfExists mime.removedTextTypes mime.textTypes)
(mkIfExists mime.removedImageTypes mime.imageTypes)
(mkIfExists mime.removedAudioTypes mime.audioTypes)
(mkIfExists mime.removedVideoTypes mime.videoTypes)
(mkIfExists mime.removedWebTypes mime.webTypes)
];
};
};
};
}

View File

@ -0,0 +1,327 @@
{
lib,
mylib,
...
}: {
enable = lib.mkEnableOption "TEMPLATE";
defaultTextEditor = lib.mkOption {
type = lib.types.str;
description = "Default application to open text files";
example = ''
"neovide.desktop"
'';
default = "neovide.desktop";
};
defaultFileBrowser = lib.mkOption {
type = lib.types.str;
description = "Default application for file browsing";
example = ''
"yazi.desktop"
'';
default = "yazi.desktop";
};
defaultWebBrowser = lib.mkOption {
type = lib.types.str;
description = "Default web browser";
example = ''
"firefox.desktop"
'';
default = "firefox.desktop";
};
defaultPdfViewer = lib.mkOption {
type = lib.types.str;
description = "Default application to open PDF files";
example = ''
"org.pwmt.zathura.desktop"
'';
default = "org.pwmt.zathura.desktop";
};
defaultImageViewer = lib.mkOption {
type = lib.types.str;
description = "Default application to open image files";
example = ''
"imv-dir.desktop"
'';
default = "imv-dir.desktop";
};
defaultAudioPlayer = lib.mkOption {
type = lib.types.str;
description = "Default application to play audio files";
example = ''
"vlc.desktop"
'';
default = "vlc.desktop";
};
defaultVideoPlayer = lib.mkOption {
type = lib.types.str;
description = "Default application to play video files";
example = ''
"vlc.desktop"
'';
default = "vlc.desktop";
};
textTypes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Mime types that should be associated with a text editor";
example = ''
[
"text/css"
"text/csv"
"text/javascript"
"text/plain"
"text/xml"
"application/json"
"application/ld+json"
"application/x-sh"
"application/xml"
]
'';
default = [
"text/css"
"text/csv"
"text/javascript"
"text/plain"
"text/xml"
"application/json"
"application/ld+json"
"application/x-sh"
"application/xml"
];
};
imageTypes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Mime types that should be associated with an image viewer";
example = ''
[
"image/apng"
"image/avif"
"image/bmp"
"image/gif"
"image/jpeg"
"image/png"
"image/svg+xml"
"image/tiff"
"image/webp"
]
'';
default = [
"image/apng"
"image/avif"
"image/bmp"
"image/gif"
"image/jpeg"
"image/png"
"image/svg+xml"
"image/tiff"
"image/webp"
];
};
audioTypes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Mime types that should be associated with an audio player";
example = ''
[
"audio/aac"
"audio/flac"
"audio/mp4"
"audio/mpeg"
"audio/ogg"
"audio/opus"
"audio/wav"
"audio/webm"
"audio/3gpp"
"audio/3gpp2"
]
'';
default = [
"audio/aac"
"audio/flac"
"audio/mp4"
"audio/mpeg"
"audio/ogg"
"audio/opus"
"audio/wav"
"audio/webm"
"audio/3gpp"
"audio/3gpp2"
];
};
videoTypes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Mime types that should be associated with a video player";
example = ''
[
"video/x-msvideo"
"video/mp4"
"video/mpeg"
"video/ogg"
"video/mp2t"
"video/webm"
"video/3gpp"
"video/3gpp2"
]
'';
default = [
"video/x-msvideo"
"video/mp4"
"video/mpeg"
"video/ogg"
"video/mp2t"
"video/webm"
"video/3gpp"
"video/3gpp2"
];
};
webTypes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Mime types that should be associated with a web browser";
example = ''
[
"text/uri-list"
"text/x-uri"
"text/html"
"application/xhtml+xml"
"x-scheme-handler/https"
]
'';
default = [
"text/uri-list"
"text/x-uri"
"text/html"
"application/xhtml+xml"
"x-scheme-handler/https"
];
};
removedTextTypes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Applications that shouldn't be used to open text files";
example = ''
[
"nvim.desktop"
]
'';
default = [
"nvim.desktop"
];
};
removedImageTypes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Applications that shouldn't be used to view image files";
example = ''
[
"imv.desktop"
"org.inkscape.Inkscape.desktop"
"chromium-browser.desktop"
"org.kde.krita.desktop"
"krita.desktop"
"krita_svg.desktop"
"krita_raw.desktop"
"krita_heif.desktop"
"krita_webp.desktop"
"krita_gif.desktop"
"krita_brush.desktop"
"krita_xcf.desktop"
"krita_jpeg.desktop"
"krita_spriter.desktop"
"krita_jxl.desktop"
"krita_ora.desktop"
"krita_csv.desktop"
"krita_tga.desktop"
"krita_psd.desktop"
"krita_png.desktop"
"krita_tiff.desktop"
"krita_exr.desktop"
"krita_qimageio.desktop"
"krita_pdf.desktop"
"krita_jp2.desktop"
"krita_heightmap.desktop"
"krita_kra.desktop"
"krita_krz.desktop"
]
'';
default = [
"imv.desktop"
"chromium-browser.desktop"
"org.kde.krita.desktop"
"krita.desktop"
"krita_svg.desktop"
"krita_raw.desktop"
"krita_heif.desktop"
"krita_webp.desktop"
"krita_gif.desktop"
"krita_brush.desktop"
"krita_xcf.desktop"
"krita_jpeg.desktop"
"krita_spriter.desktop"
"krita_jxl.desktop"
"krita_ora.desktop"
"krita_csv.desktop"
"krita_tga.desktop"
"krita_psd.desktop"
"krita_png.desktop"
"krita_tiff.desktop"
"krita_exr.desktop"
"krita_qimageio.desktop"
"krita_pdf.desktop"
"krita_jp2.desktop"
"krita_heightmap.desktop"
"krita_kra.desktop"
"krita_krz.desktop"
];
};
removedAudioTypes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Applications that shouldn't be used to play audio files";
example = ''
[
"mpv.desktop"
]
'';
default = [
"mpv.desktop"
];
};
removedVideoTypes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Applications that shouldn't be used to play video files";
example = ''
[
"mpv.desktop"
]
'';
default = [
"mpv.desktop"
];
};
removedWebTypes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Web browsers that shouldn't be used for web types";
example = ''
[
"chromium-browser.desktop"
"com.google.Chrome.desktop"
]
'';
default = [
"chromium-browser.desktop"
"com.google.Chrome.desktop"
];
};
}

View File

@ -0,0 +1,120 @@
# TODO: Setup Wireless (IWD/Networkd?)
{
config,
lib,
mylib,
...
}:
with lib;
with mylib.networking;
with mylib.modules; let
cfg = config.modules.network;
in {
options.modules.network = import ./options.nix {inherit lib mylib;};
config = mkIf cfg.enable {
services.resolved = {
enable = true;
# llmnr = "false";
# extraConfig = ''
# DNSStubListener=no
# '';
settings.Resolve = {
DNS = config.networking.nameservers;
DNSOverTLS = false;
DNSSEC = false;
Domains = config.networking.search;
LLMNR = false;
DNSStubListener = false;
};
};
# Use the programs.nm-applet instead
# environment.systemPackages = with pkgs;
# builtins.concatLists [
# []
# (lib.optionals cfg.useNetworkManager [networkmanagerapplet]) # This is started by hyprland if enabled
# ];
programs.nm-applet.enable = cfg.useNetworkManager;
# Main Networks
systemd.network = {
enable = !cfg.useNetworkManager;
wait-online.timeout = 10;
# Don't wait for all networks to be configured, as e.g. wg0 will only be upon manual activation
wait-online.anyInterface = true;
# TODO: Apparently anyInterface doesn't work?
# wait-online.ignoredInterfaces = [
# "wg0"
# "wlp7s0"
# "enp5s0"
# ];
# networks = cfg.networks;
inherit (cfg) networks;
};
modules.polkit.allowedActions = mkIf cfg.useNetworkManager [
# List NM permissions by running "nmcli general permissions"
"org.freedesktop.NetworkManager.settings.modify.system"
];
# General Networking Settings
networking = {
# Gets inherited from flake in nixos mylib and passed through the module option
hostName = cfg.hostname; # Define your hostname.
enableIPv6 = false;
# Disable a lot of stuff not needed for systemd-networkd
networkmanager = {
enable = cfg.useNetworkManager;
ensureProfiles.profiles = cfg.profiles;
insertNameservers = [
"192.168.86.26"
"8.8.8.8"
];
wifi = {
backend = "iwd";
};
};
useDHCP = false; # Default: true, don't use with networkd
dhcpcd.enable = false; # Don't use with networkd
useNetworkd = false; # Only use this if the configuration can't be written in systemd.network completely. It translates some of the networking... options to systemd
# resolvconf.enable = true;
wireless = {
enable = false; # Enables wireless support via wpa_supplicant.
iwd.enable = true; # Use iwd instead of wpa_supplicant
};
# Open Ports
nftables.enable = true;
firewall = {
enable = true;
# networking.firewall.checkReversePath = "loose";
trustedInterfaces = [
"podman0"
"docker0"
];
# allowedTCPPorts = cfg.allowedTCPPorts;
# allowedTCPPortRanges = [];
# allowedUDPPorts = cfg.allowedUDPPorts;
# allowedUDPPortRanges = [];
inherit (cfg) allowedTCPPorts allowedUDPPorts;
};
};
# We need this (sadly), otherwise the nfs mounts don't work
systemd.services.NetworkManager-wait-online.enable = true;
};
}

View File

@ -0,0 +1,61 @@
{
lib,
mylib,
...
}:
with lib;
with mylib.modules; {
enable = mkEnableOption "Systemd Network Configuration";
useNetworkManager = mkEnableOption "Use NetworkManager instead of systemd-networkd";
hostname = mkOption {
type = types.str;
description = "The System's Hostname";
example = ''
"Nixinator"
'';
};
networks = mkOption {
type = types.attrs;
default = {};
description = "Systemd-Networkd Networks";
example = ''
{
"50-ether" = {
[...]
};
}
'';
};
profiles = mkOption {
type = types.attrs;
default = {};
description = "NetworkManager Profiles";
example = ''
"50-ether" = {
[...]
};
'';
};
allowedTCPPorts = mkOption {
type = types.listOf types.int;
default = [];
description = "Open TCP Ports in the Firewall";
example = ''
[22 80 443]
'';
};
allowedUDPPorts = mkOption {
type = types.listOf types.int;
default = [];
description = "Open UDP Ports in the Firewall";
example = ''
[22 80 443]
'';
};
}

View File

@ -0,0 +1,79 @@
{
config,
lib,
mylib,
username,
...
}:
with lib;
with mylib.modules; let
cfg = config.modules.polkit;
in {
options.modules.polkit = import ./options.nix {inherit lib mylib;};
config = mkIf cfg.enable {
security.polkit.enable = true;
# NOTE: Polkit stuff:
# - subject.active: The subject is part of the currently active session
# - subject.local: The subject is spawned from a local seat/session
# - subject.user == ${username}: Only unlock stuff for the user defined by this config
security.polkit.extraConfig = let
# Services that should always get a rule
always-services = [];
mkServicePredicate = service: "action.lookup(\"unit\") == \"${service}\"";
servicePredicates =
(cfg.allowedSystemServices ++ always-services)
|> builtins.map mkServicePredicate
|> builtins.concatStringsSep " ||\n";
# Actions that should always be allowed
always-actions = [];
mkActionPredicate = action: "action.id == \"${action}\"";
actionPredicates =
(cfg.allowedActions ++ always-actions)
|> builtins.map mkActionPredicate
|> builtins.concatStringsSep " ||\n";
in
lib.concatStrings [
''
// NixOS PolKit Rules Start
''
# Only add this ruleset if (len servicePredicates) > 0
(lib.optionalString (builtins.lessThan 0 (builtins.length cfg.allowedSystemServices)) ''
polkit.addRule(function(action, subject) {
if (
action.id == "org.freedesktop.systemd1.manage-units" &&
subject.user == "${username}" &&
subject.local &&
subject.active &&
(${servicePredicates})
) {
return polkit.Result.YES;
}
});
'')
# Only add this ruleset if (len actionPredicates) > 0
(lib.optionalString (builtins.lessThan 0 (builtins.length cfg.allowedActions)) ''
polkit.addRule(function(action, subject) {
if (
subject.user == "${username}" &&
subject.local &&
subject.active &&
(${actionPredicates})
) {
return polkit.Result.YES;
}
});
'')
''
// NixOS PolKit Rules End
''
];
};
}

View File

@ -0,0 +1,32 @@
{
lib,
mylib,
...
}:
with lib;
with mylib.modules; {
enable = mkEnableOption "Polkit";
allowedActions = mkOption {
type = types.listOf types.str;
description = "Actions that should be manageable by a User without Root Password";
example = ''
[
"org.freedesktop.NetworkManager.settings.modify.system"
]
'';
default = [];
};
allowedSystemServices = mkOption {
type = types.listOf types.str;
description = "System Services that should be manageable by a User without Root Password";
example = ''
[
"jellyfin"
"stablediffusion"
]
'';
default = [];
};
}

View File

@ -0,0 +1,64 @@
{
config,
lib,
mylib,
pkgs,
username,
...
}: let
inherit (config.modules) sops-nix;
in {
options.modules.sops-nix = import ./options.nix {inherit lib mylib;};
config = {
environment.systemPackages = with pkgs; [
sops
age
# ssh-to-age
];
environment.variables = {
# Set this environment variable to make "sops edit secrets.yaml" work
SOPS_AGE_KEY_FILE = config.sops.age.keyFile;
};
sops = {
defaultSopsFile = ./secrets.yaml;
age = {
keyFile = lib.mkDefault "/home/${username}/.secrets/age/age.key";
generateKey = false;
sshKeyPaths = [];
};
secrets = let
mkSecret = name: {
${name} = {
owner = config.users.users.${username}.name;
group = config.users.users.${username}.group;
};
};
mkBootSecret = name: {
${name} = {
# Make these secrets available before creating users.
# This means we can't set the owner or group.
neededForUsers = true;
};
};
in
lib.mkMerge [
(
if (builtins.hasAttr "${username}" sops-nix.secrets)
then lib.mergeAttrsList (builtins.map mkSecret sops-nix.secrets.${username})
else {}
)
(
if (builtins.hasAttr "${username}" sops-nix.bootSecrets)
then lib.mergeAttrsList (builtins.map mkBootSecret sops-nix.bootSecrets.${username})
else {}
)
];
};
};
}

View File

@ -0,0 +1,27 @@
{
lib,
mylib,
...
}: {
secrets = lib.mkOption {
type = lib.types.attrsOf (lib.types.listOf lib.types.str);
description = "The secrets to expose on this host";
example = ''
christoph = [
"docker-password"
];
'';
default = [];
};
bootSecrets = lib.mkOption {
type = lib.types.attrsOf (lib.types.listOf lib.types.str);
description = "The secrets to expose on this host earlier in the boot process";
example = ''
christoph = [
"user-password"
];
'';
default = [];
};
}

View File

@ -0,0 +1,39 @@
#
#ENC[AES256_GCM,data:mZKPbrWtgyRvOg==,iv:vLyN3JkWWrWS+0pndTuom8cNVfpb8SUC4dA6m7utXoE=,tag:YAy2gPot6KFS9/VLVAoSxw==,type:comment]
#
user-password: ENC[AES256_GCM,data:okgvaTTesCDwriI8PxhNdHZF8XgzB4yxapuFl2/CK8x4WNYxGFjuZqGKcu7pqfnBofNcF2ByuM+HLH9FKxpK0dMCoHD/laR1IA==,iv:ltExELuM7g7ydSAMj8ioF9Nb7N4xe5enhDQrVJ+k2jQ=,tag:AV165m5yKnX+uJnMyC3mxA==,type:str]
ssh-private-key: ENC[AES256_GCM,data:JrRarfeS3y6b9gxg4Za5GIc5Ci3aGR+OyZxQybj4dcv2mzxXmT/bm7KOwM1zkz1PFl1xW5X82T5jte+XQOKx0+6m4ovjUgUmQUMP4E/yosp8XSdi0+YlUKBEHEJx6HqCZy+v6qx5kfp9JC6fZqCbL1J6FIqWqAoKTFXoiou1YnhmBa2fM17Q++i6TflDWiVrUS7X9xjuZFq1hz1aQXS303uvJEUOEpXdqPyJvUKJWzVsFrAwpa9FG+reO70SSc+1hBbqdw1QjrzNWh3eNnztwZURauJtVFBYUZ5ozHmWBr4aVFjYvqz+t6G1SAunmBRbVqbH4bjBv9jXXjHAB4U0wanvkJN2C+EY1zxwjyx2fWckMdhoLr9gtC1FJKMbV49UFHJ3iXWNczKj1t7LrctehEKXJa0Eb3UogYuaRxbVYbC++kD8LvL4AY8ertgc9/pxQQZmogdINJmIxKN4HTlGbX8kSDLbohZLheOfzZ5ycTlrbOjfJ1EBMLo+mJcMUW0qhFySl1aamPqTeII7lvgTOE3xV/d/9VAQTFKsftWPNkfhAJIym51bYrrMPV8AVeFQnLhSid3d3zK4w20zIQKSYnq9A8zcNhM0keddiv4XC+M=,iv:7HP7VCFpMRZXRD6GD/zFzDSBO02V/DyxKLmuDCLXTLU=,tag:Ugx81JwCP8HmhtflYoevLg==,type:str]
nix-github-token: ENC[AES256_GCM,data:AXV0ODLhfa4M6+7clulfIKm0qCOeo3lQ+66iYgoDeR12RxZOV19UtA==,iv:1XECVKyzH3NumKwRSPKNlUwJMLFwptcG8DQ09U4LrGk=,tag:QdtvJNV8BttWjhH4v0RtRQ==,type:str]
docker-password: ENC[AES256_GCM,data:mK5YWEQPKWBtVCgRBZvwWTdVAi8MEGbLnLeP7hfDkcc=,iv:Az8+eAK6R6xssmmbhuEsDbLU+ks8lS+qzc4L33WfefA=,tag:NSXvRhbIuRZZqRR28Tu0PQ==,type:str]
#
#ENC[AES256_GCM,data:y5dlZFhK38dR+Q==,iv:1JYizUeyWeMR4KUblkj7kVSHPCL5l8mFpaQdo774BcM=,tag:kUTnBZb46KYQyi8bgIYSOQ==,type:comment]
#
makemkv-app-key: ENC[AES256_GCM,data:/pTxr4q4ucJLx5VI8ySzOgd4g1s+6lcZNe4crxRmidTYrhJ0I6V3CIhm4wLC105W+Xka6HIZTqPn8SbqcMC4Dt3wSus=,iv:aYsGobD+Vl/VUNAHcAxQb7HEmLT8aXyKNOELgzvKDH4=,tag:xhnVb/ns6VZEnTuoUv9w5A==,type:str]
restic-repo-key: ENC[AES256_GCM,data:lSFuhjbhdQq4cabAVFGQ4kuaJxb7EhXgBDlgoEQWJhs=,iv:7IhGDBYEwY1TwLvc/4DOkUBQ3eqSszZcKwnT7Lllfps=,tag:yJVlMi9X0W+Kh3zMkb0QuA==,type:str]
#
#ENC[AES256_GCM,data:Raagjz1qPvXC,iv:OSWTKaIlmo1paU2ZZn20XMeZ2gdM52pHmVZ3m2ngCdI=,tag:bPCdvjOFjpxxkrwA7Mhl5Q==,type:comment]
#
heidi-discord-token: ENC[AES256_GCM,data:FYvfUn8tG7glqIomSDj9rGyNQjnHSCsD/C3Kk/JR1vm/xkrxzXwP3rpyxAzqRQ7vd+zFBf2BJfV/zMk=,iv:b+aKcu98rxslEGSYf6t/jGwPfS256WQ3B/iuQ4Qeykk=,tag:e48Q0BraIvItyD2WBfbYEA==,type:str]
kopia-server-username: ENC[AES256_GCM,data:4onewFkWpi9g,iv:aA4WSS8T6KUcGbAIHDd8BjE0sRK/Qz0j4QvEnKdlt2U=,tag:FQlB0Wx2u8wT3TKIhMAyLg==,type:str]
kopia-server-password: ENC[AES256_GCM,data:6nMnhRA=,iv:Qz9qP+m0obzL+eHFmW1qVmc/0TR4Iw4X1GL4zACOSMk=,tag:v3v+33+g4y6se5q+b4e8mA==,type:str]
kopia-user-password: ENC[AES256_GCM,data:jPWeru4e2w9qzA==,iv:WpZS3Qmx8v12v3q1Lq1YrPnWw7BY0FhxurXYuaOdfwA=,tag:+8bQAnHRh55rUMdyoK6N8w==,type:str]
paperless-nextcloud-sync-password: ENC[AES256_GCM,data:pfLg3OVBqLsM4R7mSgLQEachj9gMkexPjBMSyzU=,iv:XBe1cdwlTjPfQW70NIEjD8CikK58iGErI9ZTlLWtCA4=,tag:qO35GdjljgS3/z5/1fCOFg==,type:str]
#
#ENC[AES256_GCM,data:Gdh/hjCaOuAE,iv:XjPXn3SskpUPUkDIEDl5701/g9QhuS83fACMaoPMiIM=,tag:Q7s8xZG/GsOtQrasekBnkQ==,type:comment]
#
wireguard-vps-private-key: ENC[AES256_GCM,data:B6IWYuzKV9YZ+G9GIjOsXVEVugwMY14PrwmYyHsFAJEb1OJRXMg8+zeFnqs=,iv:2QroGA10UVSmNIBHFSTeCgMBD3VjtiUnng3pkR/mPVQ=,tag:FGlCrmdccgsObyut6E5ggA==,type:str]
sops:
age:
- recipient: age14ph8vrj657e7s35d60xehzuq46t9zd6pzcm6pw4jragzrvf6xs9s77usnm
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWSVRhVDREMXduZElrRklE
R2s3Y081TGVTOCtKWVVWN0RLb0ZIT3lmMTNZCjFaNmRqb2hRVXFDSU12dFhDNmJo
UHUyTUlOWWpMaVZ6Tlg3ZkRhOHJDUUEKLS0tIHduRXdxQ1VCR1ZuV3hjVjM1REtY
SURMTmh1TGIrRmtENzc0Sk4rNFJNUE0KOpjN6jkEHO+lvdWdp4P++r9SNSPWaT0h
FAbbvZZ/EdIk/njLEcayFN7B4ftTcD/f4XJZiyosilZnIkk76bMOHA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-07-19T01:29:00Z"
mac: ENC[AES256_GCM,data:IzLYRuOlkUpry37sw7OB5MglntVflMjCcNiWpi7rvT2suOivLX9IT36qZFfYIbVIFXDmfsi1hsTvsPyekD7vVWQ1vkajAlGQYYTVpnO2cFrK3+TfWCyYjiD01rQBiRikybrR11zWRq6atieurDIxMUMEI7ypiqFOwpYaqSePAFc=,iv:9bc6rc4gjuiJWNjg1g0KfySqxnPjpzmlzDi/R+Iv2g4=,tag:tEwthVZAmdXbwRtoNykGrQ==,type:str]
unencrypted_suffix: _unencrypted
version: 3.10.2