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,31 +1,8 @@
{ lib, ... }:
{
inputs,
pkgs,
...
}:
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
imports = [
./programs/dev
];
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 = {
enable = true;
config = {
user = {
name = "fwastring";
email = "fredrik@wastring.com";
};
pull = {
rebase = true;
};
url."git@github.com:".insteadOf = "https://github.com/";
};
};
imports = [
./programs/git
];
features.git.enable = lib.mkDefault true;
}

View file

@ -1,34 +1,8 @@
# This is your system's configuration file.
# Use this to configure your system environment (it replaces /etc/nixos/configuration.nix)
{ lib, ... }:
{
inputs,
lib,
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
# ]))
imports = [
./services/network
];
features.network.enable = lib.mkDefault true;
}

View file

@ -1,144 +1,8 @@
{ lib, ... }:
{
inputs,
lib,
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; [
# 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
imports = [
./programs/base
];
features.base.enable = lib.mkDefault true;
}

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;
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 {
type = types.nullOr types.str;
default = null;
@ -209,6 +224,13 @@ in
"-rc_mode VBR -b:v ${toString streamCfg.bitrateKbps}k -maxrate ${toString maxrateKbps}k -bufsize ${toString bufsizeKbps}k"
else
"-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 =
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 ${
@ -230,6 +252,22 @@ in
Restart = "always";
RestartSec = "2";
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}";
};
}

View file

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

View file

@ -1,39 +1,8 @@
{ lib, ... }:
{
pkgs,
...
}:
{
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
imports = [
./services/system
];
features.system.enable = lib.mkDefault true;
}

View file

@ -1,31 +1,8 @@
{ lib, ... }:
{
config,
pkgs,
...
}:
{
# 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 = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpJBGPIfPB1BwSG7aoKqwfccyZSaU7J3xpJ8behMp9N fw@core"
];
};
};
};
};
imports = [
./services/users
];
features.users.enable = lib.mkDefault true;
}