huge refactor

This commit is contained in:
fwastring 2026-04-02 10:58:37 +02:00
parent 03e5a47910
commit 1d4c8455ee
30 changed files with 972 additions and 697 deletions

View file

@ -1,20 +0,0 @@
loki.relabel "journal" {
forward_to = []
rule {
source_labels = ["__journal__systemd_unit"]
target_label = "unit"
}
}
loki.source.journal "read" {
forward_to = [loki.write.endpoint.receiver]
relabel_rules = loki.relabel.journal.rules
labels = {component = "macmini"}
}
loki.write "endpoint" {
endpoint {
url ="http://192.168.1.143:3100/loki/api/v1/push"
}
}

View file

@ -1,83 +0,0 @@
# This is your system's configuration file.
# Use this to configure your system environment (it replaces /etc/nixos/configuration.nix)
{
inputs,
lib,
config,
pkgs,
myhostname,
...
}:
let
modulesDirectory = ../../moduler;
in
{
# You can import other NixOS modules here
imports = [
./hardware-configuration.nix
(modulesDirectory + /services/base)
../../moduler/users.nix
../../moduler/services/monitoring
];
alloy = {
enable = true;
configPath = ./alloy-systemd.yaml;
};
nixpkgs.config.permittedInsecurePackages = [
"broadcom-sta-6.30.223.271-59-6.12.58"
];
nix.settings = {
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc="
];
};
security.sudo.wheelNeedsPassword = false;
users.users.root.openssh.authorizedKeys.keys = [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDALsdpwvC0w/Aj+1fWtzJyyWoUrGkdh8o2thVHeQQBNo0D7cmVberYmi4Cv9gWGX6PaElrnOl0KRdGyro2wxOYokSxgk2VgWW67BFITAQAbKyG2NhXXPbhb4jccDo7WH7TtOG8IofuJTPRu1Duda6k4RN0I0CkyAN6LGX+zy49cq0qKf9ijXYhCDYNih3+Fu/ig0aW/SYmsVoUl2VFTWdI5x5/wLvIjTEZhmAtYIeYADaLnom356cFrUysZa++FUujQAz3Ow236BvP95XZdTsqvfWNZFNIpC9VYF72JeIDCs5wDIr0GFmanF2On1nar+jJpoOE8SdHt357p5g/PqXV5TisN2xQRkqVwO9tWtMl4sF84jA4ULnY2gQWv9jErMxymUQ1IwuPUzDDlbRHCtfexAtkBy7wv6xslKAzG1QahvF/btNs5Caj3LN31rgAuxyooCbKGKTeBP3kHPKcz1iupgidfbO/QqVXBRQJTEdGyAKa8hVmLQZZPC/XUhxESAk= fw@fw-nix"
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8ku8iCb7tXd/tfxYDW+Tj8K9kpfrYZciYUZ6tBpO80inm4EImtfyEeJTuqDWMKov2BftUKs8brNeTBCXUEvU1P0+cpOP9RtYA5tfBXf3su+iVSswJJStIxNboXHrEGKdJJRNsTv/9agshDSUBy6G5TI1cXhv/updornfA4fwOMqOmtlYEn6XCRnsrO6NBLc/uLckdbF75HOsoLvezRvuqTLjpapjaUKGVPrgNXiclIKHmuOx71kgD4FX3rSz9FgKjnfu3a7DBbrHsf/g+N9PjNF1muN9UOV6nK3WwiO9BMWi7NpAWfzJOeZg9chqzI+U6CcsqYVeESgL41so+dnv3 fw@laptop"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP34dnsZSnWdDvd+3BXDwcw7wP0PjPEx2eCdBQJyGD6O fw@laptop"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII60tdNsG0z9q2jHmoTKvkeLQE6OF0bmTsDX1bpqpoG7 fw@jobb"
];
# Restic
users.users.restic = {
isNormalUser = true;
createHome = true;
home = "/home/restic";
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP34dnsZSnWdDvd+3BXDwcw7wP0PjPEx2eCdBQJyGD6O fw@laptop"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII60tdNsG0z9q2jHmoTKvkeLQE6OF0bmTsDX1bpqpoG7 fw@jobb"
];
};
# Where repos will live (you can choose a different path/disk)
systemd.tmpfiles.rules = [
"d /srv/restic 0750 restic restic -"
];
networking.firewall.allowedUDPPorts = [
22000
21027
];
services = {
openssh = {
enable = true;
allowSFTP = true;
};
};
security.rtkit.enable = true;
networking.hostName = myhostname;
services.xserver.dpi = 100;
system.stateVersion = "24.11";
}

View file

@ -1,39 +0,0 @@
# 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 = [ "ohci_pci" "ehci_pci" "ahci" "firewire_ohci" "usb_storage" "usbhid" "sd_mod" "sr_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" "wl" ];
boot.extraModulePackages = [ config.boot.kernelPackages.broadcom_sta ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/1c7e7116-3486-45a8-90c0-d3deea8e96b0";
fsType = "ext4";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/B70D-941F";
fsType = "vfat";
options = [ "fmask=0077" "dmask=0077" ];
};
swapDevices = [ ];
# 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.enp4s0.useDHCP = lib.mkDefault true;
# networking.interfaces.wlp3s0b1.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View file

@ -12,14 +12,7 @@ in
{ {
imports = [ imports = [
./hardware-configuration.nix ./hardware-configuration.nix
(modulesDirectory + /features/standard)
(modulesDirectory + /users.nix)
(modulesDirectory + /git.nix)
(modulesDirectory + /network.nix)
(modulesDirectory + /programs.nix)
(modulesDirectory + /system.nix)
(modulesDirectory + /dev.nix)
(modulesDirectory + /sound.nix)
(modulesDirectory + /programs/kubernetes-tools.nix) (modulesDirectory + /programs/kubernetes-tools.nix)
(modulesDirectory + /services/base) (modulesDirectory + /services/base)
@ -30,6 +23,8 @@ in
]; ];
kubernetes-tools.enable = true; kubernetes-tools.enable = true;
features.profile = "desktop";
networking.hostName = myhostname; networking.hostName = myhostname;
security.pki.certificateFiles = [ security.pki.certificateFiles = [

View file

@ -1,122 +0,0 @@
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running nixos-help).
{
inputs,
pkgs,
myhostname,
...
}:
let
theme = "mocha";
modulesDirectory = ../../moduler;
in
{
imports = [
./hardware-configuration.nix
(modulesDirectory + /users.nix)
(modulesDirectory + /git.nix)
(modulesDirectory + /network.nix)
(modulesDirectory + /programs.nix)
(modulesDirectory + /system.nix)
(modulesDirectory + /dev.nix)
(modulesDirectory + /sound.nix)
(modulesDirectory + /services/base)
(modulesDirectory + /programs/hyprland)
(modulesDirectory + /programs/nixvim)
];
networking.networkmanager = {
enable = true;
plugins = with pkgs; [
networkmanager-openvpn
];
};
stylix = {
enable = true;
base16Scheme = "${pkgs.base16-schemes}/share/themes/catppuccin-${theme}.yaml";
};
nixvim = {
enable = true;
theme = theme;
};
hyprland = {
enable = true;
theme = theme;
};
home-manager.extraSpecialArgs = { inherit inputs pkgs; };
home-manager.users.fw = {
imports = [
./../../moduler/home.nix
./../../moduler/programs/waybar
inputs.catppuccin.homeModules.catppuccin
];
waybar = {
enable = true;
profile = "laptop";
theme = theme;
};
gtk = {
enable = true;
iconTheme = {
name = "oomox-gruvbox-dark";
package = pkgs.gruvbox-dark-icons-gtk;
};
};
kitty = {
enable = true;
theme = theme;
};
fish = {
theme = theme;
};
k9s = {
enable = true;
theme = theme;
};
oh-my-posh = {
enable = true;
theme = theme;
};
catppuccin = {
librewolf = {
enable = true;
flavor = theme;
accent = "peach";
};
};
programs.ranger.enable = true;
stylix.targets = {
lazygit.enable = false;
fish.enable = false;
kitty.enable = false;
waybar.enable = false;
tmux.enable = false;
k9s.enable = false;
};
};
security.sudo.wheelNeedsPassword = false;
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII60tdNsG0z9q2jHmoTKvkeLQE6OF0bmTsDX1bpqpoG7 fw@jobb"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpJBGPIfPB1BwSG7aoKqwfccyZSaU7J3xpJ8behMp9N fw@core"
];
services.upower = {
enable = true;
};
boot.kernelPackages = pkgs.linuxPackages_latest;
networking.hostName = myhostname;
system.stateVersion = "25.05";
}

View file

@ -1,18 +0,0 @@
# 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" ];
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

@ -1,35 +0,0 @@
{ lib, ... }:
{
disko.devices = {
disk = {
main = {
device = lib.mkDefault "/dev/sda";
type = "disk";
content = {
type = "gpt";
partitions = {
ESP = {
type = "EF00";
size = "1G";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
};
};
}

View file

@ -13,10 +13,7 @@ in
{ {
imports = [ imports = [
./hardware-configuration.nix ./hardware-configuration.nix
(modulesDirectory + /features/standard)
(modulesDirectory + /users.nix)
(modulesDirectory + /network.nix)
(modulesDirectory + /system.nix)
(modulesDirectory + /services/base) (modulesDirectory + /services/base)
(modulesDirectory + /services/webcam-rtsp) (modulesDirectory + /services/webcam-rtsp)
@ -26,6 +23,8 @@ in
enable = true; enable = true;
}; };
features.profile = "camera";
hardware.graphics = { hardware.graphics = {
enable = true; enable = true;
extraPackages = with pkgs; [ extraPackages = with pkgs; [

View file

@ -1,18 +0,0 @@
# 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" "nvme" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View file

@ -48,8 +48,7 @@ in
# You can import other NixOS modules here # You can import other NixOS modules here
imports = [ imports = [
./hardware-configuration.nix ./hardware-configuration.nix
(modulesDirectory + /features/standard)
(modulesDirectory + /users.nix)
(modulesDirectory + /kitchenowl.nix) (modulesDirectory + /kitchenowl.nix)
# (modulesDirectory + /radicale.nix) # (modulesDirectory + /radicale.nix)
(modulesDirectory + /vaultwarden.nix) (modulesDirectory + /vaultwarden.nix)
@ -73,6 +72,8 @@ in
]; ];
sops.defaultSopsFile = ../../secrets/sops.yaml; sops.defaultSopsFile = ../../secrets/sops.yaml;
features.profile = "server";
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
sops.secrets.gandi_key = { sops.secrets.gandi_key = {
path = "/run/secrets/gandi_key"; path = "/run/secrets/gandi_key";
@ -140,9 +141,9 @@ in
}; };
home-manager.users.fw = { home-manager.users.fw = {
# imports = [ imports = [
# (modulesDirectory + /programs/beets) (modulesDirectory + /programs/beets)
# ]; ];
home.username = "fw"; home.username = "fw";
home.homeDirectory = "/home/fw"; home.homeDirectory = "/home/fw";

View file

@ -16,14 +16,7 @@ in
{ {
imports = [ imports = [
./hardware-configuration.nix ./hardware-configuration.nix
(modulesDirectory + /features/standard)
(modulesDirectory + /users.nix)
(modulesDirectory + /git.nix)
(modulesDirectory + /network.nix)
(modulesDirectory + /programs.nix)
(modulesDirectory + /system.nix)
(modulesDirectory + /dev.nix)
(modulesDirectory + /sound.nix)
(modulesDirectory + /services/base) (modulesDirectory + /services/base)
@ -33,6 +26,8 @@ in
]; ];
kubernetes-tools.enable = true; kubernetes-tools.enable = true;
features.profile = "laptop";
networking.networkmanager = { networking.networkmanager = {
enable = true; enable = true;
}; };

View file

@ -1,35 +0,0 @@
{ lib, ... }:
{
disko.devices = {
disk = {
main = {
device = lib.mkDefault "/dev/nvme0n1";
type = "disk";
content = {
type = "gpt";
partitions = {
ESP = {
type = "EF00";
size = "1G";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
};
};
}

View file

@ -1,31 +1,8 @@
{ lib, ... }:
{ {
inputs, imports = [
pkgs, ./programs/dev
...
}:
let
azPkgs = inputs.nixpkgs-azure-cli.legacyPackages.${pkgs.stdenv.hostPlatform.system};
in
{
environment.systemPackages = with pkgs; [
nixfmt-rfc-style
lazydocker
gh
awscli
minio-client
opentofu
python3
(azPkgs.azure-cli.withExtensions (
with azPkgs.azure-cli.extensions;
[
# aks-preview
# ssh
fzf
]
))
yq
jq
git
]; ];
features.dev.enable = lib.mkDefault true;
} }

108
moduler/features/README.md Normal file
View file

@ -0,0 +1,108 @@
# Feature Profiles
This directory provides a profile + overrides model for host configuration.
## Imports
Use this in host `imports`:
```nix
(modulesDirectory + /features/standard)
```
`features/standard` imports:
- `features` profile module
- `services/users`
- `services/network`
- `services/system`
- `services/sound`
- `programs/base`
- `programs/git`
- `programs/dev`
## Profile Selection
Set one profile per host:
```nix
features.profile = "desktop";
```
Supported values:
- `custom`
- `desktop`
- `laptop`
- `server`
- `camera`
Profiles set `mkDefault` values, so you can override any option per host.
## Common Overrides
### Base packages
```nix
features.base = {
enable = true;
preset = "minimal";
packages.captureEnable = true;
};
```
### Dev tools
```nix
features.dev = {
enable = true;
preset = "minimal";
cloud.aws.enable = false;
cloud.azure.enable = false;
};
```
### Network
```nix
features.network = {
enable = true;
netbird.enable = false;
tooling.enable = false;
};
```
### Sound/Bluetooth
```nix
features.sound = {
enable = true;
bluetooth.enable = false;
};
```
### Git defaults
```nix
features.git = {
enable = true;
userName = "myuser";
userEmail = "me@example.com";
};
```
### Users
```nix
features.users = {
enable = true;
name = "fw";
fullName = "Fredrik Wastring";
extraGroups = [ "wheel" "networkmanager" ];
};
```
## Notes
- If you only import `features/default.nix`, profile defaults are applied only for options that exist in imported modules.
- `features/standard` is the easiest option for hosts that should expose all common feature knobs.

View file

@ -0,0 +1,81 @@
{
lib,
config,
options,
...
}:
with lib;
let
profile = config.features.profile;
has = path: lib.hasAttrByPath path options;
setIf = path: value: lib.optionalAttrs (has path) (lib.setAttrByPath path value);
in
{
options.features.profile = mkOption {
type = types.enum [
"custom"
"desktop"
"laptop"
"server"
"camera"
];
default = "custom";
description = "Host feature profile. Use custom for fully explicit per-feature settings.";
};
config = mkMerge [
(mkIf (profile == "desktop") (mkMerge [
(setIf [ "features" "base" "enable" ] (mkDefault true))
(setIf [ "features" "base" "preset" ] (mkDefault "full"))
(setIf [ "features" "git" "enable" ] (mkDefault true))
(setIf [ "features" "dev" "enable" ] (mkDefault true))
(setIf [ "features" "dev" "preset" ] (mkDefault "full"))
(setIf [ "features" "users" "enable" ] (mkDefault true))
(setIf [ "features" "network" "enable" ] (mkDefault true))
(setIf [ "features" "network" "tooling" "enable" ] (mkDefault true))
(setIf [ "features" "system" "enable" ] (mkDefault true))
(setIf [ "features" "system" "preset" ] (mkDefault "desktop"))
(setIf [ "features" "sound" "enable" ] (mkDefault true))
]))
(mkIf (profile == "laptop") (mkMerge [
(setIf [ "features" "base" "enable" ] (mkDefault true))
(setIf [ "features" "base" "preset" ] (mkDefault "standard"))
(setIf [ "features" "git" "enable" ] (mkDefault true))
(setIf [ "features" "dev" "enable" ] (mkDefault true))
(setIf [ "features" "dev" "preset" ] (mkDefault "standard"))
(setIf [ "features" "users" "enable" ] (mkDefault true))
(setIf [ "features" "network" "enable" ] (mkDefault true))
(setIf [ "features" "network" "tooling" "enable" ] (mkDefault true))
(setIf [ "features" "system" "enable" ] (mkDefault true))
(setIf [ "features" "system" "preset" ] (mkDefault "desktop"))
(setIf [ "features" "sound" "enable" ] (mkDefault true))
]))
(mkIf (profile == "server") (mkMerge [
(setIf [ "features" "users" "enable" ] (mkDefault true))
(setIf [ "features" "network" "enable" ] (mkDefault true))
(setIf [ "features" "network" "netbird" "enable" ] (mkDefault false))
(setIf [ "features" "network" "tooling" "enable" ] (mkDefault false))
(setIf [ "features" "system" "enable" ] (mkDefault true))
(setIf [ "features" "system" "preset" ] (mkDefault "minimal"))
(setIf [ "features" "base" "enable" ] (mkDefault false))
(setIf [ "features" "git" "enable" ] (mkDefault false))
(setIf [ "features" "dev" "enable" ] (mkDefault false))
(setIf [ "features" "sound" "enable" ] (mkDefault false))
]))
(mkIf (profile == "camera") (mkMerge [
(setIf [ "features" "users" "enable" ] (mkDefault true))
(setIf [ "features" "network" "enable" ] (mkDefault true))
(setIf [ "features" "network" "netbird" "enable" ] (mkDefault false))
(setIf [ "features" "network" "tooling" "enable" ] (mkDefault false))
(setIf [ "features" "system" "enable" ] (mkDefault true))
(setIf [ "features" "system" "preset" ] (mkDefault "minimal"))
(setIf [ "features" "base" "enable" ] (mkDefault false))
(setIf [ "features" "git" "enable" ] (mkDefault false))
(setIf [ "features" "dev" "enable" ] (mkDefault false))
(setIf [ "features" "sound" "enable" ] (mkDefault false))
]))
];
}

View file

@ -0,0 +1,13 @@
{ ... }:
{
imports = [
../default.nix
../../services/users
../../services/network
../../services/system
../../services/sound
../../programs/base
../../programs/git
../../programs/dev
];
}

View file

@ -1,16 +1,8 @@
{ pkgs, lib, ... }: { lib, ... }:
{ {
programs.git = { imports = [
enable = true; ./programs/git
config = { ];
user = {
name = "fwastring"; features.git.enable = lib.mkDefault true;
email = "fredrik@wastring.com";
};
pull = {
rebase = true;
};
url."git@github.com:".insteadOf = "https://github.com/";
};
};
} }

View file

@ -1,34 +1,8 @@
# This is your system's configuration file. { lib, ... }:
# Use this to configure your system environment (it replaces /etc/nixos/configuration.nix)
{ {
inputs, imports = [
lib, ./services/network
config,
pkgs,
...
}:
{
services.netbird = {
enable = true;
ui.enable = true;
};
systemd.services.tailscaled = {
after = [ "netbird.service" "network-online.target" ];
wants = [ "netbird.service" "network-online.target" ];
};
environment.systemPackages = with pkgs; [
# networkmanager
dnsutils
nmap
ipcalc
# iperf3
# networkmanagerapplet
# (octodns.withProviders (ps: [
# octodns-providers.gandi
# ]))
]; ];
features.network.enable = lib.mkDefault true;
} }

View file

@ -1,144 +1,8 @@
{ lib, ... }:
{ {
inputs, imports = [
lib, ./programs/base
config,
pkgs,
...
}:
{
services.udev = {
extraRules = ''
KERNEL=="ttyACM0", MODE:="666"
ACTION=="add", KERNEL=="sd[a-e][0-9]", ENV{ID_FS_UUID}=="3039-3932", RUN+="${pkgs.systemd}/bin/systemd-mount --no-block -A -G -o gid=users,fmask=113,dmask=002 /dev/%k /mnt/sdcard"
ACTION=="add", KERNEL=="sd[a-e]", ENV{ID_FS_UUID}=="66BA-4EBA", RUN+="${pkgs.systemd}/bin/systemd-mount --no-block -A -G -o gid=users,fmask=113,dmask=002 /dev/%k /mnt/kobo"
KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"
'';
packages = with pkgs; [
vial
via
]; ];
};
home-manager.users.fw = {
xdg.mimeApps = {
enable = true;
defaultApplications = {
"text/html" = "librewolf.desktop";
"x-scheme-handler/http" = "librewolf.desktop";
"x-scheme-handler/https" = "librewolf.desktop";
"x-scheme-handler/about" = "librewolf.desktop";
"x-scheme-handler/unknown" = "librewolf.desktop";
"text/plain" = "nvim.desktop";
"text/markdown" = "nvim.desktop";
"text/x-markdown" = "nvim.desktop";
"application/json" = "nvim.desktop";
"application/x-ndjson" = "nvim.desktop";
"application/x-yaml" = "nvim.desktop";
"text/yaml" = "nvim.desktop";
"text/x-shellscript" = "nvim.desktop";
"text/x-python" = "nvim.desktop";
"text/x-csrc" = "nvim.desktop";
"text/x-c++src" = "nvim.desktop";
"application/x-sql" = "nvim.desktop";
"text/xml" = "nvim.desktop";
"application/xml" = "nvim.desktop";
"application/pdf" = "org.gnome.Evince.desktop";
"image/jpeg" = "feh.desktop";
"image/png" = "feh.desktop";
"image/gif" = "feh.desktop";
"image/webp" = "feh.desktop";
"image/tiff" = "feh.desktop";
"image/bmp" = "feh.desktop";
"image/svg+xml" = "feh.desktop";
};
};
};
environment.sessionVariables.DEFAULT_BROWSER = "${pkgs.librewolf}/bin/librewolf";
environment.systemPackages = with pkgs; [ features.base.enable = lib.mkDefault true;
# GUI
feishin
vscode
signal-desktop
thunderbird
discord
slack
evince
spotify
firefox
ipcalc
vial
via
remmina
brightnessctl
speedcrunch
opencode
quickemu
virt-viewer
go-passbolt-cli
wf-recorder
slurp
bitwarden-desktop
bitwarden-cli
lagrange
jujutsu
rclone
dbeaver-bin
(
let
base = pkgs.appimageTools.defaultFhsEnvArgs;
in
pkgs.buildFHSEnv (
base
// {
name = "fhs";
targetPkgs =
pkgs:
# pkgs.buildFHSUserEnv provides only a minimal FHS environment,
# lacking many basic packages needed by most software.
# Therefore, we need to add them manually.
#
# pkgs.appimageTools provides basic packages required by most software.
(base.targetPkgs pkgs)
++ (with pkgs; [
pkg-config
ncurses
icu
# Feel free to add more packages here if needed.
]);
profile = "export FHS=1";
runScript = "bash";
extraOutputsToInstall = [ "dev" ];
}
)
)
# TUI
codex
# Browsers
librewolf
# Displaying
zathura
feh
mpv
# System
pavucontrol
pulseaudio
devour
caligula
ptouch-print
# Transforms
yt-dlp
imagemagick
pandoc
pinta
pastel
ffmpeg
# darktable
];
} }

View file

@ -0,0 +1,245 @@
{
lib,
config,
pkgs,
...
}:
with lib;
let
fhsEnv =
let
base = pkgs.appimageTools.defaultFhsEnvArgs;
in
pkgs.buildFHSEnv (
base
// {
name = "fhs";
targetPkgs =
pkgs:
(base.targetPkgs pkgs)
++ (with pkgs; [
pkg-config
ncurses
icu
]);
profile = "export FHS=1";
runScript = "bash";
extraOutputsToInstall = [ "dev" ];
}
);
in
{
options.features.base = {
enable = mkEnableOption "enable shared base programs";
preset = mkOption {
type = types.enum [
"minimal"
"standard"
"full"
];
default = "standard";
description = "Preset for desktop package groups.";
};
udevEnable = mkOption {
type = types.bool;
default = true;
description = "Enable custom udev rules and device packages.";
};
mimeEnable = mkOption {
type = types.bool;
default = true;
description = "Enable Home Manager MIME defaults.";
};
packages = {
communicationEnable = mkOption {
type = types.bool;
default = true;
description = "Install communication desktop apps.";
};
mediaEnable = mkOption {
type = types.bool;
default = true;
description = "Install media apps.";
};
browsersEnable = mkOption {
type = types.bool;
default = true;
description = "Install browsers.";
};
captureEnable = mkOption {
type = types.bool;
default = true;
description = "Install screen capture tools.";
};
creativeEnable = mkOption {
type = types.bool;
default = true;
description = "Install conversion/creative tools.";
};
devDesktopEnable = mkOption {
type = types.bool;
default = true;
description = "Install desktop dev helper tools and VMs.";
};
dbeaverEnable = mkOption {
type = types.bool;
default = true;
description = "Install dbeaver and FHS shell.";
};
};
};
config = mkMerge [
(mkIf (config.features.base.preset == "minimal") {
features.base.packages.communicationEnable = mkDefault false;
features.base.packages.mediaEnable = mkDefault false;
features.base.packages.browsersEnable = mkDefault true;
features.base.packages.captureEnable = mkDefault false;
features.base.packages.creativeEnable = mkDefault false;
features.base.packages.devDesktopEnable = mkDefault false;
features.base.packages.dbeaverEnable = mkDefault false;
})
(mkIf (config.features.base.preset == "full") {
features.base.packages.communicationEnable = mkDefault true;
features.base.packages.mediaEnable = mkDefault true;
features.base.packages.browsersEnable = mkDefault true;
features.base.packages.captureEnable = mkDefault true;
features.base.packages.creativeEnable = mkDefault true;
features.base.packages.devDesktopEnable = mkDefault true;
features.base.packages.dbeaverEnable = mkDefault true;
})
(mkIf config.features.base.enable {
services.udev = mkIf config.features.base.udevEnable {
extraRules = ''
KERNEL=="ttyACM0", MODE:="666"
ACTION=="add", KERNEL=="sd[a-e][0-9]", ENV{ID_FS_UUID}=="3039-3932", RUN+="${pkgs.systemd}/bin/systemd-mount --no-block -A -G -o gid=users,fmask=113,dmask=002 /dev/%k /mnt/sdcard"
ACTION=="add", KERNEL=="sd[a-e]", ENV{ID_FS_UUID}=="66BA-4EBA", RUN+="${pkgs.systemd}/bin/systemd-mount --no-block -A -G -o gid=users,fmask=113,dmask=002 /dev/%k /mnt/kobo"
KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"
'';
packages = with pkgs; [
vial
via
];
};
home-manager.users.fw = mkIf config.features.base.mimeEnable {
xdg.mimeApps = {
enable = true;
defaultApplications = {
"text/html" = "librewolf.desktop";
"x-scheme-handler/http" = "librewolf.desktop";
"x-scheme-handler/https" = "librewolf.desktop";
"x-scheme-handler/about" = "librewolf.desktop";
"x-scheme-handler/unknown" = "librewolf.desktop";
"text/plain" = "nvim.desktop";
"text/markdown" = "nvim.desktop";
"text/x-markdown" = "nvim.desktop";
"application/json" = "nvim.desktop";
"application/x-ndjson" = "nvim.desktop";
"application/x-yaml" = "nvim.desktop";
"text/yaml" = "nvim.desktop";
"text/x-shellscript" = "nvim.desktop";
"text/x-python" = "nvim.desktop";
"text/x-csrc" = "nvim.desktop";
"text/x-c++src" = "nvim.desktop";
"application/x-sql" = "nvim.desktop";
"text/xml" = "nvim.desktop";
"application/xml" = "nvim.desktop";
"application/pdf" = "org.gnome.Evince.desktop";
"image/jpeg" = "feh.desktop";
"image/png" = "feh.desktop";
"image/gif" = "feh.desktop";
"image/webp" = "feh.desktop";
"image/tiff" = "feh.desktop";
"image/bmp" = "feh.desktop";
"image/svg+xml" = "feh.desktop";
};
};
};
environment.sessionVariables.DEFAULT_BROWSER = "${pkgs.librewolf}/bin/librewolf";
environment.systemPackages =
(with pkgs; [
codex
ipcalc
remmina
brightnessctl
speedcrunch
lagrange
jujutsu
rclone
zathura
feh
pavucontrol
pulseaudio
devour
caligula
ptouch-print
])
++ optionals config.features.base.packages.communicationEnable (
with pkgs;
[
signal-desktop
thunderbird
discord
slack
]
)
++ optionals config.features.base.packages.mediaEnable (
with pkgs;
[
feishin
spotify
mpv
]
)
++ optionals config.features.base.packages.browsersEnable (
with pkgs;
[
librewolf
firefox
]
)
++ optionals config.features.base.packages.captureEnable (
with pkgs;
[
wf-recorder
slurp
]
)
++ optionals config.features.base.packages.creativeEnable (
with pkgs;
[
yt-dlp
imagemagick
pandoc
pinta
pastel
ffmpeg
]
)
++ optionals config.features.base.packages.devDesktopEnable (
with pkgs;
[
vscode
opencode
quickemu
virt-viewer
go-passbolt-cli
bitwarden-desktop
bitwarden-cli
]
)
++ optionals config.features.base.packages.dbeaverEnable (
with pkgs;
[
dbeaver-bin
fhsEnv
]
);
})
];
}

View file

@ -0,0 +1,109 @@
{
inputs,
pkgs,
lib,
config,
...
}:
let
azPkgs = inputs.nixpkgs-azure-cli.legacyPackages.${pkgs.stdenv.hostPlatform.system};
in
with lib;
{
options.features.dev = {
enable = mkEnableOption "enable development toolchain";
preset = mkOption {
type = types.enum [
"minimal"
"standard"
"full"
];
default = "standard";
description = "Preset for development package groups.";
};
cloud = {
aws.enable = mkOption {
type = types.bool;
default = true;
description = "Enable AWS CLI tools.";
};
azure.enable = mkOption {
type = types.bool;
default = true;
description = "Enable Azure CLI tools.";
};
minio.enable = mkOption {
type = types.bool;
default = true;
description = "Enable MinIO client.";
};
};
tools = {
docker.enable = mkOption {
type = types.bool;
default = true;
description = "Enable lazydocker.";
};
python.enable = mkOption {
type = types.bool;
default = true;
description = "Enable Python runtime package.";
};
opentofu.enable = mkOption {
type = types.bool;
default = true;
description = "Enable OpenTofu package.";
};
git.enable = mkOption {
type = types.bool;
default = true;
description = "Enable git package in dev toolchain.";
};
};
};
config = mkMerge [
(mkIf config.features.dev.enable {
environment.systemPackages =
(with pkgs; [
nixfmt-rfc-style
gh
yq
jq
])
++ optionals config.features.dev.tools.docker.enable (with pkgs; [ lazydocker ])
++ optionals config.features.dev.cloud.aws.enable (with pkgs; [ awscli ])
++ optionals config.features.dev.cloud.minio.enable (with pkgs; [ minio-client ])
++ optionals config.features.dev.tools.opentofu.enable (with pkgs; [ opentofu ])
++ optionals config.features.dev.tools.python.enable (with pkgs; [ python3 ])
++ optionals config.features.dev.cloud.azure.enable [
(azPkgs.azure-cli.withExtensions (
with azPkgs.azure-cli.extensions;
[
fzf
]
))
]
++ optionals config.features.dev.tools.git.enable (with pkgs; [ git ]);
})
(mkIf (config.features.dev.preset == "minimal") {
features.dev.cloud.aws.enable = mkDefault false;
features.dev.cloud.azure.enable = mkDefault false;
features.dev.cloud.minio.enable = mkDefault false;
features.dev.tools.docker.enable = mkDefault false;
features.dev.tools.opentofu.enable = mkDefault false;
features.dev.tools.python.enable = mkDefault false;
})
(mkIf (config.features.dev.preset == "full") {
features.dev.cloud.aws.enable = mkDefault true;
features.dev.cloud.azure.enable = mkDefault true;
features.dev.cloud.minio.enable = mkDefault true;
features.dev.tools.docker.enable = mkDefault true;
features.dev.tools.opentofu.enable = mkDefault true;
features.dev.tools.python.enable = mkDefault true;
features.dev.tools.git.enable = mkDefault true;
})
];
}

View file

@ -0,0 +1,45 @@
{ lib, config, ... }:
with lib;
{
options.features.git = {
enable = mkEnableOption "enable git defaults";
userName = mkOption {
type = types.str;
default = "fwastring";
description = "Default git user.name.";
};
userEmail = mkOption {
type = types.str;
default = "fredrik@wastring.com";
description = "Default git user.email.";
};
pullRebase = mkOption {
type = types.bool;
default = true;
description = "Enable pull.rebase by default.";
};
githubSshInsteadOfHttps = mkOption {
type = types.bool;
default = true;
description = "Use SSH for GitHub remotes when cloning with HTTPS URLs.";
};
};
config = mkIf config.features.git.enable {
programs.git = {
enable = true;
config = {
user = {
name = config.features.git.userName;
email = config.features.git.userEmail;
};
pull = {
rebase = config.features.git.pullRebase;
};
}
// optionalAttrs config.features.git.githubSshInsteadOfHttps {
url."git@github.com:".insteadOf = "https://github.com/";
};
};
};
}

View file

@ -0,0 +1,69 @@
{
lib,
config,
pkgs,
...
}:
with lib;
{
options.features.network = {
enable = mkEnableOption "enable network tooling and VPN services";
netbird = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable NetBird service.";
};
uiEnable = mkOption {
type = types.bool;
default = true;
description = "Enable NetBird UI component.";
};
};
tailscale = {
waitForNetbird = mkOption {
type = types.bool;
default = true;
description = "Add netbird ordering to tailscaled unit.";
};
};
tooling = {
enable = mkOption {
type = types.bool;
default = true;
description = "Install network troubleshooting CLI tools.";
};
packages = mkOption {
type = types.listOf types.package;
default = with pkgs; [
dnsutils
nmap
ipcalc
];
description = "Packages installed when network tooling is enabled.";
};
};
};
config = mkIf config.features.network.enable {
services.netbird = mkIf config.features.network.netbird.enable {
enable = true;
ui.enable = config.features.network.netbird.uiEnable;
};
systemd.services.tailscaled =
mkIf (config.features.network.tailscale.waitForNetbird && config.features.network.netbird.enable)
{
after = [
"netbird.service"
"network-online.target"
];
wants = [
"netbird.service"
"network-online.target"
];
};
environment.systemPackages = mkIf config.features.network.tooling.enable config.features.network.tooling.packages;
};
}

View file

@ -0,0 +1,71 @@
{ lib, config, ... }:
with lib;
{
options.features.sound = {
enable = mkEnableOption "enable sound and bluetooth stack";
pipewire = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable PipeWire audio stack.";
};
alsa32Bit = mkOption {
type = types.bool;
default = true;
description = "Enable 32-bit ALSA support.";
};
pulseCompat = mkOption {
type = types.bool;
default = true;
description = "Enable PulseAudio compatibility through PipeWire.";
};
};
bluetooth = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable Bluetooth subsystem.";
};
powerOnBoot = mkOption {
type = types.bool;
default = true;
description = "Power on Bluetooth at boot.";
};
disableHeadsetProfile = mkOption {
type = types.bool;
default = true;
description = "Disable headset profile for Bluetooth devices.";
};
bluemanEnable = mkOption {
type = types.bool;
default = true;
description = "Enable Blueman service.";
};
};
};
config = mkIf config.features.sound.enable {
services.pulseaudio.enable = false;
security.rtkit.enable = true;
services = {
pipewire = {
enable = config.features.sound.pipewire.enable;
alsa.enable = config.features.sound.pipewire.enable;
alsa.support32Bit = config.features.sound.pipewire.alsa32Bit;
pulse.enable = config.features.sound.pipewire.pulseCompat;
};
blueman.enable = config.features.sound.bluetooth.bluemanEnable;
};
hardware = {
bluetooth = mkIf config.features.sound.bluetooth.enable {
enable = config.features.sound.bluetooth.enable;
powerOnBoot = config.features.sound.bluetooth.powerOnBoot;
settings = {
General = mkIf config.features.sound.bluetooth.disableHeadsetProfile {
Disable = "Headset";
};
};
};
};
};
}

View file

@ -0,0 +1,78 @@
{
pkgs,
lib,
config,
...
}:
with lib;
{
options.features.system = {
enable = mkEnableOption "enable shared system utilities";
preset = mkOption {
type = types.enum [
"minimal"
"desktop"
"ops"
];
default = "desktop";
description = "Package profile for shared system tooling.";
};
corePackages = mkOption {
type = types.listOf types.package;
default = with pkgs; [
sops
unzip
zip
wget
htop
procps
fastfetch
bc
fzf
eza
rsync
ripgrep
fd
];
description = "Always-installed core system packages.";
};
desktopPackages = mkOption {
type = types.listOf types.package;
default = with pkgs; [
bluez
bluez-tools
poppler-utils
alsa-utils
libnotify
hyprpicker
];
description = "Additional packages for desktop-oriented hosts.";
};
opsPackages = mkOption {
type = types.listOf types.package;
default = with pkgs; [
grc
lazygit
];
description = "Additional packages for operations-heavy hosts.";
};
extras = mkOption {
type = types.listOf types.package;
default = with pkgs; [
lolcat
fortune
cowsay
typst
];
description = "Additional optional packages for all presets except minimal.";
};
};
config = mkIf config.features.system.enable {
environment.systemPackages =
config.features.system.corePackages
++ optionals (config.features.system.preset != "minimal") config.features.system.extras
++ optionals (config.features.system.preset == "desktop") config.features.system.desktopPackages
++ optionals (config.features.system.preset == "ops") config.features.system.opsPackages;
};
}

View file

@ -0,0 +1,62 @@
{
config,
pkgs,
lib,
...
}:
with lib;
{
options.features.users = {
enable = mkEnableOption "enable default users";
name = mkOption {
type = types.str;
default = "fw";
description = "Primary user account name.";
};
fullName = mkOption {
type = types.str;
default = "Fredrik Wastring";
description = "Primary user full name.";
};
initialPassword = mkOption {
type = types.str;
default = "password";
description = "Initial password for the primary user.";
};
extraGroups = mkOption {
type = types.listOf types.str;
default = [
"networkmanager"
"wheel"
"audio"
"docker"
"input"
];
description = "Additional groups for the primary user.";
};
sshAuthorizedKeys = mkOption {
type = types.listOf types.str;
default = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpJBGPIfPB1BwSG7aoKqwfccyZSaU7J3xpJ8behMp9N fw@core"
];
description = "SSH authorized keys for the primary user.";
};
};
config = mkIf config.features.users.enable {
users = {
defaultUserShell = pkgs.bash;
users = {
${config.features.users.name} = {
initialPassword = config.features.users.initialPassword;
isNormalUser = true;
description = config.features.users.fullName;
extraGroups = config.features.users.extraGroups;
openssh.authorizedKeys = {
keys = config.features.users.sshAuthorizedKeys;
};
};
};
};
};
}

View file

@ -100,6 +100,21 @@ in
default = 24; default = 24;
description = "VAAPI quality parameter (lower means better quality, higher CPU/bitrate)."; description = "VAAPI quality parameter (lower means better quality, higher CPU/bitrate).";
}; };
waitForRtsp = mkOption {
type = types.bool;
default = true;
description = "Wait for the RTSP destination to accept TCP connections before ffmpeg starts.";
};
waitForRtspTimeoutSec = mkOption {
type = types.int;
default = 30;
description = "Maximum seconds to wait for RTSP destination availability.";
};
waitForRtspIntervalSec = mkOption {
type = types.int;
default = 1;
description = "Seconds between RTSP availability checks.";
};
vaapiDriver = mkOption { vaapiDriver = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
@ -209,6 +224,13 @@ in
"-rc_mode VBR -b:v ${toString streamCfg.bitrateKbps}k -maxrate ${toString maxrateKbps}k -bufsize ${toString bufsizeKbps}k" "-rc_mode VBR -b:v ${toString streamCfg.bitrateKbps}k -maxrate ${toString maxrateKbps}k -bufsize ${toString bufsizeKbps}k"
else else
"-rc_mode CQP -qp ${toString streamCfg.vaapiQp}"; "-rc_mode CQP -qp ${toString streamCfg.vaapiQp}";
rtspUrlMatch = builtins.match "rtsp://([^/:]+)(:([0-9]+))?/.*" streamCfg.rtspUrl;
rtspHost = if rtspUrlMatch != null then builtins.elemAt rtspUrlMatch 0 else "127.0.0.1";
rtspPort =
if rtspUrlMatch != null && builtins.elemAt rtspUrlMatch 2 != null then
builtins.elemAt rtspUrlMatch 2
else
"8554";
videoCodecArgs = videoCodecArgs =
if streamCfg.useVaapi then if streamCfg.useVaapi then
"-vaapi_device ${streamCfg.vaapiDevice} -vf format=nv12,hwupload -c:v h264_vaapi -profile:v main -level:v 4.1 ${vaapiRateControlArgs} -g ${ "-vaapi_device ${streamCfg.vaapiDevice} -vf format=nv12,hwupload -c:v h264_vaapi -profile:v main -level:v 4.1 ${vaapiRateControlArgs} -g ${
@ -230,6 +252,22 @@ in
Restart = "always"; Restart = "always";
RestartSec = "2"; RestartSec = "2";
Environment = optional (streamCfg.vaapiDriver != null) "LIBVA_DRIVER_NAME=${streamCfg.vaapiDriver}"; Environment = optional (streamCfg.vaapiDriver != null) "LIBVA_DRIVER_NAME=${streamCfg.vaapiDriver}";
ExecStartPre = optional cfg.waitForRtsp (
pkgs.writeShellScript "wait-for-rtsp-${sanitizeName streamName}" ''
set -eu
retries=${toString cfg.waitForRtspTimeoutSec}
interval=${toString cfg.waitForRtspIntervalSec}
i=0
until ${pkgs.netcat-openbsd}/bin/nc -z -w1 ${rtspHost} ${rtspPort}; do
i=$((i + 1))
if [ "$i" -ge "$retries" ]; then
echo "Timed out waiting for RTSP server at ${rtspHost}:${rtspPort}" >&2
exit 1
fi
sleep "$interval"
done
''
);
ExecStart = "${pkgs.ffmpeg}/bin/ffmpeg -hide_banner -loglevel warning -f v4l2 -framerate ${toString streamCfg.framerate} -video_size ${streamCfg.videoSize} -i ${streamCfg.device} ${videoCodecArgs} -f rtsp -rtsp_transport tcp ${streamCfg.rtspUrl}"; ExecStart = "${pkgs.ffmpeg}/bin/ffmpeg -hide_banner -loglevel warning -f v4l2 -framerate ${toString streamCfg.framerate} -video_size ${streamCfg.videoSize} -i ${streamCfg.device} ${videoCodecArgs} -f rtsp -rtsp_transport tcp ${streamCfg.rtspUrl}";
}; };
} }

View file

@ -1,25 +1,8 @@
{ pkgs, lib, ... }: { lib, ... }:
{ {
services.pulseaudio.enable = false; imports = [
security.rtkit.enable = true; ./services/sound
services = { ];
pipewire = {
enable = true; features.sound.enable = lib.mkDefault true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
};
blueman.enable = true;
};
hardware = {
bluetooth = {
enable = true;
powerOnBoot = true;
settings = {
General = {
Disable = "Headset";
};
};
};
};
} }

View file

@ -1,39 +1,8 @@
{ lib, ... }:
{ {
pkgs, imports = [
... ./services/system
}:
{
environment.systemPackages = with pkgs; [
bluez
bluez-tools
poppler-utils
alsa-utils
sops
libnotify
unzip
zip
wget
htop
procps
grc
fastfetch
bc
fzf
eza
rsync
ripgrep
fd
lolcat
fortune
cowsay
lazygit
hyprpicker
typst
]; ];
features.system.enable = lib.mkDefault true;
} }

View file

@ -1,31 +1,8 @@
{ lib, ... }:
{ {
config, imports = [
pkgs, ./services/users
...
}:
{
# sops.secrets.user-password = { };
users = {
defaultUserShell = pkgs.bash;
users = {
fw = {
# hashedPasswordFile = config.sops.secrets.user-password.path;
initialPassword = "password";
isNormalUser = true;
description = "Fredrik Wastring";
extraGroups = [
"networkmanager"
"wheel"
"audio"
"docker"
"input"
]; ];
openssh.authorizedKeys = {
keys = [ features.users.enable = lib.mkDefault true;
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpJBGPIfPB1BwSG7aoKqwfccyZSaU7J3xpJ8behMp9N fw@core"
];
};
};
};
};
} }