28 Commits

Author SHA1 Message Date
mjallen18
33385d5275 testing done 2025-08-21 20:54:43 -05:00
mjallen18
19bf815be8 end test 2025-08-21 20:52:13 -05:00
mjallen18
0152438472 more cleanup 2025-08-21 20:05:58 -05:00
mjallen18
f9b07deb19 cleanup 2025-08-21 19:45:03 -05:00
mjallen18
6e55d375d2 user updates 2025-08-21 19:40:32 -05:00
mjallen18
0e066cb4d7 move some apps to namespace 2025-08-21 19:06:51 -05:00
mjallen18
bd64283f04 idk 2025-08-21 16:17:17 -05:00
mjallen18
6025b6c4f1 bcfs root? 2025-08-21 16:10:04 -05:00
mjallen18
92b04773b2 move python-steam 2025-08-21 14:26:50 -05:00
mjallen18
783a7a3390 README 2025-08-21 14:17:33 -05:00
mjallen18
0ef4354c1a aarch 2025-08-21 13:24:04 -05:00
mjallen18
192a978d46 fix proxies 2025-08-21 11:45:06 -05:00
mjallen18
a4519904b6 fix pi4 hostname 2025-08-21 11:45:06 -05:00
mjallen18
83a6e45bf4 test 2025-08-20 22:02:24 -05:00
mjallen18
2ba6f3466f clev 2025-08-20 21:30:18 -05:00
mjallen18
b3f5b4b406 more pi stuff 2025-08-20 21:30:18 -05:00
mjallen18
2e680f2519 fix lol 2025-08-20 21:30:18 -05:00
mjallen18
445183f826 pi stuff 2025-08-20 21:30:18 -05:00
mjallen18
aec980e6fe sops but idk 2025-08-20 21:30:18 -05:00
mjallen18
68f732ec4b cleanup 2025-08-20 21:30:18 -05:00
mjallen18
dc382dcfcc cache 2025-08-20 21:30:18 -05:00
mjallen18
b1a06034f1 disable gnome 2025-08-20 21:30:18 -05:00
mjallen18
aa3e8cc263 finally update traefik 2025-08-20 21:30:18 -05:00
mjallen18
b680255bc5 macos 2025-08-20 21:30:18 -05:00
mjallen18
a3f7af4e39 sops 2025-08-20 21:30:18 -05:00
mjallen18
cd5c8a0034 no splash 2025-08-20 21:30:18 -05:00
mjallen18
1f14f020ed ssh 2025-08-20 21:30:18 -05:00
mjallen18
05affb6b1f test 2025-08-18 20:54:03 -05:00
86 changed files with 2121 additions and 2005 deletions

View File

@@ -10,8 +10,8 @@ keys:
- &pi5 age1t2d5scrukk0guva5sr97a8tge5j8kd865adezrcru7p269pzwvpsamkgje - &pi5 age1t2d5scrukk0guva5sr97a8tge5j8kd865adezrcru7p269pzwvpsamkgje
- &deck age1c8qw59ffcq9l77gfmtyc3djtvt3md0u6dwhrjcgsm98ntyf72ufqugj7cg - &deck age1c8qw59ffcq9l77gfmtyc3djtvt3md0u6dwhrjcgsm98ntyf72ufqugj7cg
- &steamdeck age1er5qucsc2mugrzrr7n3xhzv7kemkrqrw4m84r544fkk7nkg5g5eswxkqj0 - &steamdeck age1er5qucsc2mugrzrr7n3xhzv7kemkrqrw4m84r544fkk7nkg5g5eswxkqj0
- &matt_macbook-pro age1xg6mvj3x6s3t8058c6rsk3q4kskvm6nsffwckxkkjzhyn7r6tczqgkj23p - &matt_macbook-pro age19daqsncuzeh3j6cwk8uxp6yfj8h0qtz02jxlwwy4v8j0mfgznsvq30440g
- &macbook-pro age1rdn39ywgzmc8wlsl5lrfe77e652wzjmjx58gx4k2ydghd35kdqvqscrf3h - &macbook-pro age19w4zafpwnq9yhzuf8r5te2yhq7xlqj76rcgzcz935hllyrz4yvws4jn6ca
- &nuc age1wurzgc20e6ye79wsg85vvqk4aj3mmc0llxshcy9532ex8f4c6dqql76c78 - &nuc age1wurzgc20e6ye79wsg85vvqk4aj3mmc0llxshcy9532ex8f4c6dqql76c78
- &admin_nuc age1luyejgmqjj0esydlr2jxqkg48vexmx57gdz7cy5gq7rz8kf5cups2rnfa9 - &admin_nuc age1luyejgmqjj0esydlr2jxqkg48vexmx57gdz7cy5gq7rz8kf5cups2rnfa9
creation_rules: creation_rules:

156
README.md
View File

@@ -1,50 +1,118 @@
# nixOS Config # NixOS Configuration Repository
### Common Files This repository contains my personal NixOS configurations for multiple systems, managed using [Snowfall Lib](https://github.com/snowfallorg/lib) and the Nix Flakes system.
* [flake.nix](./flake.nix)
* [impermenance.nix](./share/impermanence/default.nix) ## Overview
* [share](./share)
* [overlays](./overlays) This repository provides a centralized, declarative configuration for all my systems, including:
- Desktop PC (AMD)
- NAS server
- Steam Deck
- Intel NUC
- Raspberry Pi 4
- Raspberry Pi 5
- MacBook Pro (NixOS on Apple Silicon)
- MacBook Pro (Darwin/macOS)
## Repository Structure
```
.
├── checks/ # Pre-commit hooks and other checks
├── flake.nix # Main flake configuration
├── homes/ # Home-manager configurations for users
│ ├── aarch64-darwin/ # macOS home configurations
│ ├── aarch64-linux/ # ARM Linux home configurations
│ └── x86_64-linux/ # x86 Linux home configurations
├── modules/ # Reusable configuration modules
│ ├── home/ # Home-manager modules
│ └── nixos/ # NixOS system modules
├── overlays/ # Nixpkgs overlays
├── packages/ # Custom package definitions
├── secrets/ # Encrypted secrets (managed with sops-nix)
└── systems/ # System-specific configurations
├── aarch64-darwin/ # macOS system configurations
├── aarch64-linux/ # ARM Linux system configurations
└── x86_64-linux/ # x86 Linux system configurations
```
## Key Features
- **Modular Design**: Reusable modules for various system components
- **Multi-System Support**: Configurations for different hardware platforms
- **Home Manager Integration**: User environment management
- **Secret Management**: Encrypted secrets with sops-nix
- **Disk Management**: Declarative disk partitioning with disko
- **State Management**: Persistent state management with impermanence
- **Desktop Environments**: Support for GNOME, Hyprland, and COSMIC
- **Hardware-Specific Optimizations**: Tailored configurations for different hardware
## Key Technologies
- [Nix](https://nixos.org/) and [NixOS](https://nixos.org/)
- [Nix Flakes](https://nixos.wiki/wiki/Flakes)
- [Snowfall Lib](https://github.com/snowfallorg/lib)
- [Home Manager](https://github.com/nix-community/home-manager)
- [sops-nix](https://github.com/Mic92/sops-nix)
- [disko](https://github.com/nix-community/disko)
- [impermanence](https://github.com/nix-community/impermanence)
- [lanzaboote](https://github.com/nix-community/lanzaboote) (Secure Boot)
## Notable System Configurations
### Desktop ### Desktop
* [boot.nix](./hosts/desktop/boot.nix)
* [configuration.nix](./hosts/desktop/configuration.nix) A powerful AMD-based desktop with gaming capabilities, featuring:
* [hardware-configuration.nix](./hosts/desktop/hardware-configuration.nix) - AMD CPU and GPU optimizations
* [filesystems.nix](./hosts/desktop/filesystems.nix) - Multiple desktop environment options (GNOME, Hyprland, COSMIC)
* [home.nix](./hosts/desktop/home.nix) - Gaming setup with Steam and related tools
* [sops.nix](./hosts/desktop/sops.nix)
* [specialisations.hyprland](./hosts/desktop/hyprland)
* [specialisations.gnome](./hosts/desktop/gnome)
* [specialisations.cosmic](./hosts/desktop/cosmic)
### NAS ### NAS
* [boot.nix](./hosts/nas/boot.nix)
* [configuration.nix](./hosts/nas/configuration.nix)
* [hardware-configuration.nix](./hosts/nas/hardware-configuration.nix)
* [impermenance.nix](./hosts/nas/impermenance.nix)
* [apps.nix](./hosts/desktop/apps.nix)
* [home.nix](./hosts/desktop/home.nix)
* [networking.nix](./hosts/desktop/networking.nix)
* [services.nix](./hosts/desktop/services.nix)
* [sops.nix](./hosts/desktop/sops.nix)
* [ups.nix](./hosts/desktop/ups.nix)
* [samba](./modules/samba)
* nas-apps
* [arrs](./hosts/nas/apps/arrs/default.nix)
* [free-games-claimer](./modules/apps/free-games-claimer)
* [jackett](./modules/apps/jackett)
* [jellyfin](./hosts/nas/apps/jellyfin/default.nix)
* [jellyseerr](./hosts/nas/apps/jellyseerr/default.nix)
* [jackett](./modules/apps/manyfold)
* [mariadb](./modules/apps/mariadb)
* [mealie](./modules/apps/mealie)
* [nextcloud+onlyoffice](./hosts/nas/apps/nextcloud/default.nix)
* [ollama](./hosts/nas/apps/ollama/default.nix)
* [paperless](./hosts/nas/apps/paperless/default.nix)
* [tdarr](./modules/apps/tdarr)
* [traefik](./hosts/nas/apps/traefik/default.nix)
* [wireguard](./modules/apps/your-spotify)
### Raspberry Pi 4 A home server with various self-hosted services:
* [configuration.nix](./hosts/pi4/configuration.nix) - Media management (Jellyfin, Jellyseerr)
* [hardware-configuration.nix](./hosts/pi4/hardware-configuration.nix) - Download automation (Sonarr, Radarr, etc.)
- Document management (Paperless)
- File sharing (Samba, Nextcloud)
- AI services (Ollama)
### Raspberry Pi
Configurations for both Pi 4 and Pi 5:
- Hardware-specific optimizations
- Disk partitioning suitable for ARM devices
- Bluetooth and wireless support
### Steam Deck
Custom NixOS configuration for the Steam Deck:
- Integration with Jovian for Steam Deck compatibility
- Gaming optimizations
- Steam ROM Manager
### MacBook Pro
Configurations for both:
- NixOS on Apple Silicon
- nix-darwin for macOS
## Usage
### Building a System Configuration
```bash
# Build and activate a system configuration
sudo nixos-rebuild switch --flake .#hostname
```
### Building a Home Configuration
```bash
# Build and activate a home configuration
home-manager switch --flake .#username@hostname
```
## License
This project is licensed under the MIT License - see the LICENSE file for details.

11
flake.lock generated
View File

@@ -180,16 +180,15 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1742690494, "lastModified": 1755519972,
"narHash": "sha256-SFacEbSRMoTyWG5VXh4ieofJGge+cLq9lH8ifB+zjBg=", "narHash": "sha256-bU4nqi3IpsUZJeyS8Jk85ytlX61i4b0KCxXX9YcOgVc=",
"owner": "nvmd", "owner": "nix-community",
"repo": "disko", "repo": "disko",
"rev": "9dc58d4d49c9f74623a06e2fc20cdfd8bb3cbe8b", "rev": "4073ff2f481f9ef3501678ff479ed81402caae6d",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nvmd", "owner": "nix-community",
"ref": "gpt-attrs",
"repo": "disko", "repo": "disko",
"type": "github" "type": "github"
} }

View File

@@ -43,8 +43,8 @@
disko = { disko = {
# the fork is needed for partition attributes support # the fork is needed for partition attributes support
url = "github:nvmd/disko/gpt-attrs"; # url = "github:nvmd/disko/gpt-attrs";
# url = "github:nix-community/disko"; url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };

View File

@@ -1,18 +1,22 @@
{ lib, ... }: { lib, ... }:
let
shellAliases = {
ll = "ls -alh";
update-boot = "sudo nixos-rebuild boot --max-jobs 10 --build-host admin@10.0.1.3";
update-switch = "sudo nixos-rebuild switch --max-jobs 10 --build-host admin@10.0.1.3";
update-flake = "nix flake update pi4-nixpkgs pi4-home-manager pi4-impermanence pi4-sops-nix pi4-nixos-hardware pi4-nixos-raspberrypi pi4-disko --flake /etc/nixos";
update-nas = "nixos-rebuild switch --use-remote-sudo --target-host admin@10.0.1.3 --build-host admin@10.0.1.3 --flake ~/nix-config#jallen-nas";
nas-ssh = "kitten ssh admin@10.0.1.3";
ducks = "du -cksh * | sort -hr | head -n 15";
};
in
{ {
home.username = "matt"; home.username = "matt";
mjallen = {
shell-aliases = {
enable = true;
flakeInputs = [
"pi4-nixpkgs"
"pi4-home-manager"
"pi4-impermanence"
"pi4-sops-nix"
"pi4-nixos-hardware"
"pi4-nixos-raspberrypi"
"pi4-disko"
];
};
};
sops = { sops = {
age.keyFile = "/home/matt/.config/sops/age/keys.txt"; age.keyFile = "/home/matt/.config/sops/age/keys.txt";
defaultSopsFile = "/etc/nixos/secrets/secrets.yaml"; defaultSopsFile = "/etc/nixos/secrets/secrets.yaml";
@@ -50,10 +54,13 @@ in
programs = { programs = {
mangohud.enable = lib.mkForce true; mangohud.enable = lib.mkForce true;
zsh.shellAliases = shellAliases;
}; };
services = { services = {
nextcloud-client.enable = lib.mkForce true; nextcloud-client.enable = lib.mkForce false;
kdeconnect = {
enable = false;
indicator = false;
};
}; };
} }

View File

@@ -57,4 +57,12 @@ in
programs = { programs = {
zsh.shellAliases = shellAliases; zsh.shellAliases = shellAliases;
}; };
services = {
nextcloud-client.enable = false;
kdeconnect = {
enable = false;
indicator = false;
};
};
} }

View File

@@ -15,4 +15,12 @@ in
programs = { programs = {
zsh.shellAliases = shellAliases; zsh.shellAliases = shellAliases;
}; };
services = {
nextcloud-client.enable = lib.mkForce false;
kdeconnect = {
enable = false;
indicator = false;
};
};
} }

View File

@@ -1,16 +1,27 @@
{ pkgs, ... }: { pkgs, ... }:
let
shellAliases = {
update-boot = "sudo nixos-rebuild boot --max-jobs 10";
update-switch = "sudo nixos-rebuild switch --max-jobs 10";
update-flake = "nix flake update nas-nixpkgs nas-authentik-nix nas-cosmic nas-crowdsec nas-home-manager nas-impermanence nas-lanzaboote nas-nixos-hardware nas-sops-nix --flake /etc/nixos";
};
in
{ {
home.username = "admin"; home.username = "admin";
# mjallen.home.enable = true; # mjallen.home.enable = true;
mjallen = {
shell-aliases = {
enable = true;
buildHost = ""; # NAS builds locally
flakeInputs = [
"nas-nixpkgs"
"nas-authentik-nix"
"nas-cosmic"
"nas-crowdsec"
"nas-home-manager"
"nas-impermanence"
"nas-lanzaboote"
"nas-nixos-hardware"
"nas-sops-nix"
];
};
};
sops = { sops = {
age.keyFile = "/home/admin/.config/sops/age/keys.txt"; age.keyFile = "/home/admin/.config/sops/age/keys.txt";
defaultSopsFile = "/etc/nixos/secrets/secrets.yaml"; defaultSopsFile = "/etc/nixos/secrets/secrets.yaml";
@@ -60,8 +71,6 @@ in
} }
]; ];
}; };
zsh.shellAliases = shellAliases;
}; };
# services.nixai = { # services.nixai = {

View File

@@ -78,6 +78,6 @@ in
mgba mgba
prismlauncher prismlauncher
ryujinx-greemdev ryujinx-greemdev
vmware-horizon-client omnissa-horizon-client
]; ];
} }

View File

@@ -1,12 +1,4 @@
{ pkgs, ... }: { pkgs, ... }:
let
shellAliases = {
update-boot = "sudo nixos-rebuild boot --max-jobs 10 --build-host admin@10.0.1.3";
update-switch = "sudo nixos-rebuild switch --max-jobs 10 --build-host admin@10.0.1.3";
update-flake = "nix flake update desktop-nixpkgs desktop-chaotic desktop-home-manager desktop-impermanence desktop-lanzaboote desktop-nixos-hardware desktop-sops-nix desktop-steam-rom-manager --flake /etc/nixos";
update-nas = "nixos-rebuild switch --use-remote-sudo --target-host admin@10.0.1.3 --build-host admin@10.0.1.3 --flake ~/nix-config#jallen-nas";
};
in
{ {
home.username = "matt"; home.username = "matt";
@@ -14,6 +6,19 @@ in
sops = { sops = {
enable = true; enable = true;
}; };
shell-aliases = {
enable = true;
flakeInputs = [
"desktop-nixpkgs"
"desktop-chaotic"
"desktop-home-manager"
"desktop-impermanence"
"desktop-lanzaboote"
"desktop-nixos-hardware"
"desktop-sops-nix"
"desktop-steam-rom-manager"
];
};
}; };
services = { services = {
@@ -25,8 +30,6 @@ in
programs = { programs = {
password-store.enable = true; password-store.enable = true;
zsh.shellAliases = shellAliases;
}; };
home.packages = with pkgs; [ home.packages = with pkgs; [

View File

@@ -292,7 +292,8 @@ in
"tag +waydroid, class:([Ww]aydroid.*)" "tag +waydroid, class:([Ww]aydroid.*)"
"float, tag:waydroid" "float, tag:waydroid"
"pin, tag:waydroid" "pin, tag:waydroid"
] ++ cfg.windowRule; ]
++ cfg.windowRule;
plugin = { plugin = {
touch_gestures = { touch_gestures = {
@@ -395,16 +396,15 @@ in
}; };
}; };
extraConfig = extraConfig = ''
'' exec-once = dbus-update-activation-environment --systemd --all
exec-once = dbus-update-activation-environment --systemd --all exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP exec-once = /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
exec-once = /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 exec-once = xhost +SI:localuser:root
exec-once = xhost +SI:localuser:root exec-once = nwg-look -a
exec-once = nwg-look -a exec-once = nwg-dock-hyprland -d
exec-once = nwg-dock-hyprland -d ''
'' + cfg.extraConfig or '''';
+ cfg.extraConfig or '''';
}; };
}; };
} }

View File

@@ -12,7 +12,7 @@ in
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
programs.hyprlock = { programs.hyprlock = {
enable = false; enable = true;
settings = { settings = {
background = [ background = [
{ {

View File

@@ -52,10 +52,14 @@
nh = { nh = {
enable = true; enable = true;
flake = "/etc/nixos"; flake = "/etc/nixos";
clean = {
enable = true;
extraArgs = "--keep 5";
};
}; };
micro = { micro = {
enable = true; enable = lib.mkDefault true;
settings = { settings = {
autoindent = true; autoindent = true;
autosu = true; autosu = true;
@@ -66,7 +70,7 @@
}; };
tmux = { tmux = {
enable = true; enable = lib.mkDefault true;
terminal = "screen-256color"; terminal = "screen-256color";
sensibleOnTop = true; sensibleOnTop = true;
focusEvents = true; focusEvents = true;
@@ -109,8 +113,8 @@
nextcloud-client.enable = lib.mkDefault true; nextcloud-client.enable = lib.mkDefault true;
pass-secret-service.enable = lib.mkDefault true; pass-secret-service.enable = lib.mkDefault true;
kdeconnect = { kdeconnect = {
enable = true; enable = lib.mkDefault true;
indicator = true; indicator = lib.mkDefault true;
}; };
}; };
} }

View File

@@ -2,6 +2,7 @@
config, config,
pkgs, pkgs,
system, system,
namespace,
... ...
}: }:
let let
@@ -9,6 +10,7 @@ let
x86_only = with pkgs; [ x86_only = with pkgs; [
vscode-extensions.redhat.vscode-xml vscode-extensions.redhat.vscode-xml
]; ];
open-remote-ssh = pkgs.${namespace}.open-remote-ssh;
in in
{ {
programs = { programs = {
@@ -39,7 +41,7 @@ in
# open-remote-ssh # open-remote-ssh
# nix-vscode-extensions.open-vsx.jeanp413.open-remote-ssh # nix-vscode-extensions.open-vsx.jeanp413.open-remote-ssh
# open-vsx.jeanp413.open-remote-ssh open-remote-ssh
] ]
++ (if !isArm then x86_only else [ ]) ++ (if !isArm then x86_only else [ ])
++ pkgs.vscode-utils.extensionsFromVscodeMarketplace [ ++ pkgs.vscode-utils.extensionsFromVscodeMarketplace [
@@ -138,6 +140,8 @@ in
"*.db" = "default"; "*.db" = "default";
}; };
}; };
"enable-proposed-api" = [ "jeanp413.open-remote-ssh" ];
}; };
}; };
}; };

View File

@@ -80,9 +80,9 @@ in
"custom/right-end" "custom/right-end"
]; ];
# modules-right = [ # modules-right = [
# "tray" # "tray"
# "custom/left-end" ] ++ # "custom/left-end" ] ++
# cfg.modules-right ++ # cfg.modules-right ++
# [ "custom/right-end" ]; # [ "custom/right-end" ];
@@ -323,262 +323,262 @@ in
format = "&nbsp"; format = "&nbsp";
tooltip = false; tooltip = false;
}; };
} // cfg.extraModules; }
// cfg.extraModules;
}; };
# * { font-size: 13px; } # * { font-size: 13px; }
# window.eDP-1 * { font-size: 10px; } # window.eDP-1 * { font-size: 10px; }
style = style = ''
'' .blink_me {
.blink_me { animation: blinker 1s linear infinite;
animation: blinker 1s linear infinite; }
@keyframes blinker {
50% {
color: ${nord.aurora.nord11};
} }
}
@keyframes blinker { * {
50% { font-family:
color: ${nord.aurora.nord11}; Jetbrains Mono Nerd Font,
} monospace;
} font-size: 14px;
min-height: 0;
}
* { #waybar {
font-family: background: transparent;
Jetbrains Mono Nerd Font, color: ${nord.snowStorm.nord6};
monospace; margin: 5px 5px;
font-size: 14px; }
min-height: 0;
}
#waybar { #workspaces {
background: transparent; background-color: ${nord.polarNight.nord0};
color: ${nord.snowStorm.nord6}; ${defaultBorderRadius}
margin: 5px 5px; ${defaultOpacity}
} ${defaultCenterOptions}
margin-left: 0.6rem;
}
#workspaces { #workspaces button {
background-color: ${nord.polarNight.nord0}; color: ${nord.frost.nord10};
${defaultBorderRadius} ${defaultBorderRadius}
${defaultOpacity} padding: 0.4rem;
${defaultCenterOptions} }
margin-left: 0.6rem;
}
#workspaces button { #workspaces button.active {
color: ${nord.frost.nord10}; color: ${nord.frost.nord8};
${defaultBorderRadius} ${defaultBorderRadius}
padding: 0.4rem; }
}
#workspaces button.active { #workspaces button:hover {
color: ${nord.frost.nord8}; color: ${nord.frost.nord7};
${defaultBorderRadius} ${defaultBorderRadius}
} }
#workspaces button:hover { #workspaces button.focused {
color: ${nord.frost.nord7}; color: ${nord.snowStorm.nord6};
${defaultBorderRadius} background: ${nord.aurora.nord13};
} ${defaultBorderRadius}
}
#workspaces button.focused { #workspaces button.urgent {
color: ${nord.snowStorm.nord6}; color: ${nord.polarNight.nord0};
background: ${nord.aurora.nord13}; background: ${nord.snowStorm.nord6};
${defaultBorderRadius} ${defaultBorderRadius}
} }
#workspaces button.urgent { #tooltip {
color: ${nord.polarNight.nord0}; background: ${nord.polarNight.nord0};
background: ${nord.snowStorm.nord6}; border-color: ${nord.polarNight.nord0};
${defaultBorderRadius} ${defaultBorderRadius}
} border-width: 1rem;
border-style: solid;
}
#tooltip { #window {
background: ${nord.polarNight.nord0}; color: ${nord.aurora.nord15};
border-color: ${nord.polarNight.nord0}; background-color: ${nord.polarNight.nord0};
${defaultBorderRadius} ${defaultOpacity}
border-width: 1rem; ${defaultBorderRadius}
border-style: solid; ${defaultCenterOptions}
} margin-left: 4rem;
margin-right: ${toString cfg.windowOffset}rem;
}
#window { /* make window module transparent when no windows present */
color: ${nord.aurora.nord15}; #window.empty {
background-color: ${nord.polarNight.nord0}; background-color: transparent;
${defaultOpacity} }
${defaultBorderRadius}
${defaultCenterOptions}
margin-left: 4rem;
margin-right: ${toString cfg.windowOffset}rem;
}
/* make window module transparent when no windows present */ #custom-weather {
#window.empty { color: ${nord.frost.nord10};
background-color: transparent; background-color: ${nord.polarNight.nord0};
} ${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#custom-weather { #battery {
color: ${nord.frost.nord10}; color: ${nord.aurora.nord15};
background-color: ${nord.polarNight.nord0}; background-color: ${nord.polarNight.nord0};
${defaultOpacity} ${defaultOpacity}
${defaultCenterOptions} ${defaultCenterOptions}
border-radius: 0; border-radius: 0;
} min-width: 3rem;
}
#battery { #clock {
color: ${nord.aurora.nord15}; color: ${nord.frost.nord9};
background-color: ${nord.polarNight.nord0}; background-color: ${nord.polarNight.nord0};
${defaultOpacity} ${defaultOpacity}
${defaultCenterOptions} ${defaultCenterOptions}
border-radius: 0; border-radius: 0;
min-width: 3rem; }
}
#clock { /* ------------- */
color: ${nord.frost.nord9};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
/* ------------- */ #idle_inhibitor {
color: ${nord.frost.nord10};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
padding-right: 1rem;
}
#idle_inhibitor { #idle_inhibitor:hover {
color: ${nord.frost.nord10}; background: ${nord.polarNight.nord3};
background-color: ${nord.polarNight.nord0}; }
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
padding-right: 1rem;
}
#idle_inhibitor:hover { #network {
background: ${nord.polarNight.nord3}; color: ${nord.aurora.nord15};
} background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
padding-right: 15px;
}
#network { #network:hover {
color: ${nord.aurora.nord15}; background: ${nord.polarNight.nord3};
background-color: ${nord.polarNight.nord0}; }
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
padding-right: 15px;
}
#network:hover { #bluetooth {
background: ${nord.polarNight.nord3}; color: ${nord.frost.nord9};
} background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#bluetooth { #bluetooth:hover {
color: ${nord.frost.nord9}; background: ${nord.polarNight.nord3};
background-color: ${nord.polarNight.nord0}; }
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#bluetooth:hover { #wireplumber.source {
background: ${nord.polarNight.nord3}; color: ${nord.frost.nord8};
} background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#wireplumber.source { #wireplumber.source.muted {
color: ${nord.frost.nord8}; animation-name: blinker;
background-color: ${nord.polarNight.nord0}; animation-duration: 2s;
${defaultOpacity} animation-timing-function: linear;
${defaultCenterOptions} animation-iteration-count: infinite;
border-radius: 0; padding-right: 1rem;
} }
#wireplumber.source.muted { #wireplumber.source:hover {
animation-name: blinker; background: ${nord.polarNight.nord3};
animation-duration: 2s; }
animation-timing-function: linear;
animation-iteration-count: infinite;
padding-right: 1rem;
}
#wireplumber.source:hover { #wireplumber.sink {
background: ${nord.polarNight.nord3}; color: ${nord.frost.nord7};
} background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#wireplumber.sink { #wireplumber.sink.muted {
color: ${nord.frost.nord7}; animation-name: blinker;
background-color: ${nord.polarNight.nord0}; animation-duration: 5s;
${defaultOpacity} animation-timing-function: linear;
${defaultCenterOptions} animation-iteration-count: infinite;
border-radius: 0; }
}
#wireplumber.sink.muted { #wireplumber.sink:hover {
animation-name: blinker; background: ${nord.polarNight.nord3};
animation-duration: 5s; }
animation-timing-function: linear;
animation-iteration-count: infinite;
}
#wireplumber.sink:hover { #keyboard-state.numlock {
background: ${nord.polarNight.nord3}; color: ${nord.frost.nord8};
} background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#keyboard-state.numlock { #keyboard-state.capslock {
color: ${nord.frost.nord8}; color: ${nord.frost.nord9};
background-color: ${nord.polarNight.nord0}; background-color: ${nord.polarNight.nord0};
${defaultOpacity} ${defaultOpacity}
${defaultCenterOptions} ${defaultCenterOptions}
border-radius: 0; border-radius: 0;
} }
#keyboard-state.capslock { #temperature.gpu {
color: ${nord.frost.nord9}; color: ${nord.frost.nord10};
background-color: ${nord.polarNight.nord0}; background-color: ${nord.polarNight.nord0};
${defaultOpacity} ${defaultOpacity}
${defaultCenterOptions} ${defaultCenterOptions}
border-radius: 0; border-radius: 0;
} }
#temperature.gpu { #temperature.gpu:hover {
color: ${nord.frost.nord10}; background: ${nord.polarNight.nord3};
background-color: ${nord.polarNight.nord0}; }
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0;
}
#temperature.gpu:hover { #temperature {
background: ${nord.polarNight.nord3}; color: ${nord.frost.nord9};
} background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0
}
#temperature { /* ------------- */
color: ${nord.frost.nord9};
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
border-radius: 0
}
/* ------------- */ #tray {
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
${defaultBorderRadius}
margin-right: 0.6rem;
}
#tray { /* ------------- */
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${defaultCenterOptions}
${defaultBorderRadius}
margin-right: 0.6rem;
}
/* ------------- */ #custom-left-end {
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${borderLeft}
}
#custom-left-end { #custom-right-end {
background-color: ${nord.polarNight.nord0}; background-color: ${nord.polarNight.nord0};
${defaultOpacity} ${defaultOpacity}
${borderLeft} ${borderRight}
} }
''
#custom-right-end { + cfg.extraModulesStyle or '''';
background-color: ${nord.polarNight.nord0};
${defaultOpacity}
${borderRight}
}
''
+ cfg.extraModulesStyle or '''';
}; };
}; };
} }

View File

@@ -0,0 +1,57 @@
{
config,
lib,
...
}:
let
cfg = config.mjallen.shell-aliases;
in
{
options.mjallen.shell-aliases = {
enable = lib.mkEnableOption "Common shell aliases";
buildHost = lib.mkOption {
type = lib.types.str;
default = "admin@10.0.1.3";
description = "Build host for nixos-rebuild commands";
};
flakeInputs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "List of flake inputs to update";
};
extraAliases = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = "Additional host-specific aliases";
};
};
config = lib.mkIf cfg.enable {
programs.zsh.shellAliases = {
# Common file operations
ll = "ls -alh";
ducks = "du -cksh * | sort -hr | head -n 15";
# NixOS rebuild commands
update-boot =
"sudo nixos-rebuild boot --max-jobs 10"
+ lib.optionalString (cfg.buildHost != "") " --build-host ${cfg.buildHost}";
update-switch =
"sudo nixos-rebuild switch --max-jobs 10"
+ lib.optionalString (cfg.buildHost != "") " --build-host ${cfg.buildHost}";
# Flake update command
update-flake = lib.mkIf (
cfg.flakeInputs != [ ]
) "nix flake update ${lib.concatStringsSep " " cfg.flakeInputs} --flake /etc/nixos";
# NAS management
update-nas = "nixos-rebuild switch --use-remote-sudo --target-host admin@10.0.1.3 --build-host admin@10.0.1.3 --flake ~/nix-config#jallen-nas";
nas-ssh = "kitten ssh admin@10.0.1.3";
}
// cfg.extraAliases;
};
}

View File

@@ -65,20 +65,19 @@ in
]; ];
home = { home = {
file = file = {
{ "Desktop/.keep".text = "";
"Desktop/.keep".text = ""; "Documents/.keep".text = "";
"Documents/.keep".text = ""; "Downloads/.keep".text = "";
"Downloads/.keep".text = ""; "Music/.keep".text = "";
"Music/.keep".text = ""; "Pictures/.keep".text = "";
"Pictures/.keep".text = ""; "Videos/.keep".text = "";
"Videos/.keep".text = ""; }
} // lib.optionalAttrs (cfg.icon != null) {
// lib.optionalAttrs (cfg.icon != null) { ".face".source = cfg.icon;
".face".source = cfg.icon; ".face.icon".source = cfg.icon;
".face.icon".source = cfg.icon; "Pictures/${cfg.icon.fileName or (builtins.baseNameOf cfg.icon)}".source = cfg.icon;
"Pictures/${cfg.icon.fileName or (builtins.baseNameOf cfg.icon)}".source = cfg.icon; };
};
homeDirectory = mkDefault cfg.home; homeDirectory = mkDefault cfg.home;

View File

@@ -2,11 +2,12 @@
config, config,
pkgs, pkgs,
lib, lib,
namespace,
... ...
}: }:
with lib; with lib;
let let
cfg = config.nas-apps.actual; cfg = config.${namespace}.services.actual;
dataDir = "/data"; dataDir = "/data";
hostAddress = "10.0.1.3"; hostAddress = "10.0.1.3";
actualUserId = config.users.users.nix-apps.uid; actualUserId = config.users.users.nix-apps.uid;

View File

@@ -1,7 +1,7 @@
{ lib, ... }: { lib, namespace, ... }:
with lib; with lib;
{ {
options.nas-apps.actual = { options.${namespace}.services.actual = {
enable = mkEnableOption "actual service"; enable = mkEnableOption "actual service";
port = mkOption { port = mkOption {

View File

@@ -1,7 +1,12 @@
{ lib, config, ... }: {
lib,
config,
namespace,
...
}:
with lib; with lib;
let let
cfg = config.nas-apps.free-games-claimer; cfg = config.${namespace}.services.free-games-claimer;
in in
{ {
imports = [ ./options.nix ]; imports = [ ./options.nix ];

View File

@@ -1,7 +1,7 @@
{ lib, ... }: { lib, namespace, ... }:
with lib; with lib;
{ {
options.nas-apps.free-games-claimer = { options.${namespace}.services.free-games-claimer = {
enable = mkEnableOption "free-games-claimer docker service"; enable = mkEnableOption "free-games-claimer docker service";
autoStart = mkOption { autoStart = mkOption {

View File

@@ -1,7 +1,12 @@
{ lib, config, ... }: {
lib,
config,
namespace,
...
}:
with lib; with lib;
let let
cfg = config.nas-apps.manyfold; cfg = config.${namespace}.services.manyfold;
in in
{ {
imports = [ ./options.nix ]; imports = [ ./options.nix ];

View File

@@ -1,7 +1,7 @@
{ lib, ... }: { lib, namespace, ... }:
with lib; with lib;
{ {
options.nas-apps.manyfold = { options.${namespace}.services.manyfold = {
enable = mkEnableOption "manyfold docker service"; enable = mkEnableOption "manyfold docker service";
autoStart = mkOption { autoStart = mkOption {

View File

@@ -1,7 +1,12 @@
{ lib, config, ... }: {
lib,
config,
namespace,
...
}:
with lib; with lib;
let let
cfg = config.nas-apps.mongodb; cfg = config.${namespace}.services.mongodb;
in in
{ {
imports = [ ./options.nix ]; imports = [ ./options.nix ];

View File

@@ -1,7 +1,7 @@
{ lib, ... }: { lib, namespace, ... }:
with lib; with lib;
{ {
options.nas-apps.mongodb = { options.${namespace}.services.mongodb = {
enable = mkEnableOption "mongodb docker service"; enable = mkEnableOption "mongodb docker service";
autoStart = mkOption { autoStart = mkOption {

View File

@@ -1,7 +1,12 @@
{ lib, config, ... }: {
lib,
config,
namespace,
...
}:
with lib; with lib;
let let
cfg = config.nas-apps.tdarr; cfg = config.${namespace}.services.tdarr;
in in
{ {
imports = [ ./options.nix ]; imports = [ ./options.nix ];

View File

@@ -1,7 +1,7 @@
{ lib, ... }: { lib, namespace, ... }:
with lib; with lib;
{ {
options.nas-apps.tdarr = { options.${namespace}.services.tdarr = {
enable = mkEnableOption "tdarr docker service"; enable = mkEnableOption "tdarr docker service";
autoStart = mkOption { autoStart = mkOption {

View File

@@ -1,7 +1,12 @@
{ lib, config, ... }: {
lib,
config,
namespace,
...
}:
with lib; with lib;
let let
cfg = config.nas-apps.your_spotify; cfg = config.${namespace}.services.your_spotify;
in in
{ {
imports = [ ./options.nix ]; imports = [ ./options.nix ];

View File

@@ -1,7 +1,7 @@
{ lib, ... }: { lib, namespace, ... }:
with lib; with lib;
{ {
options.nas-apps.your_spotify = { options.${namespace}.services.your_spotify = {
enable = mkEnableOption "your_spotify docker service"; enable = mkEnableOption "your_spotify docker service";
autoStart = mkOption { autoStart = mkOption {

View File

@@ -2,11 +2,12 @@
config, config,
pkgs, pkgs,
lib, lib,
namespace,
... ...
}: }:
with lib; with lib;
let let
cfg = config.nas-apps.arrs; cfg = config.${namespace}.services.arrs;
radarrDataDir = "/var/lib/radarr"; radarrDataDir = "/var/lib/radarr";
downloadDir = "/downloads"; downloadDir = "/downloads";
incompleteDir = "/downloads-incomplete"; incompleteDir = "/downloads-incomplete";

View File

@@ -1,7 +1,7 @@
{ lib, ... }: { lib, namespace, ... }:
with lib; with lib;
{ {
options.nas-apps.arrs = { options.${namespace}.services.arrs = {
enable = mkEnableOption "arrs services"; enable = mkEnableOption "arrs services";
radarr = { radarr = {

View File

@@ -2,11 +2,12 @@
config, config,
lib, lib,
pkgs, pkgs,
namespace,
... ...
}: }:
with lib; with lib;
let let
cfg = config.nas-apps.crowdsec; cfg = config.${namespace}.services.crowdsec;
in in
{ {
imports = [ ./options.nix ]; imports = [ ./options.nix ];

View File

@@ -1,7 +1,7 @@
{ lib, ... }: { lib, namespace, ... }:
with lib; with lib;
{ {
options.nas-apps.crowdsec = { options.${namespace}.services.crowdsec = {
enable = mkEnableOption "crowdsec service"; enable = mkEnableOption "crowdsec service";
port = mkOption { port = mkOption {

View File

@@ -28,7 +28,7 @@ in
programs = { programs = {
kdeconnect = { kdeconnect = {
enable = true; enable = lib.mkDefault true;
package = pkgs.gnomeExtensions.gsconnect; package = pkgs.gnomeExtensions.gsconnect;
}; };
}; };

View File

@@ -0,0 +1,92 @@
{
config,
lib,
pkgs,
namespace,
...
}:
let
cfg = config.${namespace}.development;
in
{
options.${namespace}.development = {
enable = lib.mkEnableOption "Common development tools and packages";
includeLanguages = lib.mkOption {
type = lib.types.listOf (
lib.types.enum [
"python"
"c"
"rust"
"nodejs"
]
);
default = [
"python"
"c"
];
description = "Programming languages to include tools for";
};
includeContainers = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Include container development tools";
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages =
with pkgs;
[
# Version control
git
# Build tools
cmake
ninja
binutils
# System utilities
jq
# Text processing
]
++ lib.optionals (builtins.elem "python" cfg.includeLanguages) [
python3
python3Packages.pip
]
++ lib.optionals (builtins.elem "c" cfg.includeLanguages) [
gcc
gdb
]
++ lib.optionals (builtins.elem "rust" cfg.includeLanguages) [
rustc
cargo
]
++ lib.optionals (builtins.elem "nodejs" cfg.includeLanguages) [
nodejs
npm
]
++ lib.optionals cfg.includeContainers [
docker-compose
podman-compose
];
# Enable container support if requested
virtualisation.podman = lib.mkIf cfg.includeContainers {
enable = true;
dockerCompat = true;
autoPrune.enable = true;
defaultNetwork.settings = {
dns_enabled = true;
};
};
# Common development programs
programs = {
nix-ld.enable = lib.mkDefault true;
};
};
}

View File

@@ -2,23 +2,78 @@
config, config,
lib, lib,
system, system,
namespace,
... ...
}: }:
let let
cfg = config.${namespace}.hardware.disko;
isArm = builtins.match "aarch64*" system != null; isArm = builtins.match "aarch64*" system != null;
rootDisk = "/dev/nvme0n1"; rootDisk = "/dev/nvme0n1";
# BTRFS root partition configuration
btrfsRoot = {
name = "btrfs-root";
size = "100%";
content = {
type = "btrfs";
extraArgs = [ "-f" ]; # Override existing partition
# Subvolumes must set a mountpoint in order to be mounted,
# unless their parent is mounted
subvolumes = {
"home" = {
mountOptions = [ "compress=zstd" ];
mountpoint = "/home";
};
"root" = {
mountOptions = [
"compress=zstd"
"noatime"
];
mountpoint = "/root";
};
"nix" = {
mountOptions = [
"compress=zstd"
"noatime"
];
mountpoint = "/nix";
};
"etc" = {
mountOptions = [
"compress=zstd"
"noatime"
];
mountpoint = "/etc";
};
"log" = {
mountOptions = [
"compress=zstd"
"noatime"
];
mountpoint = "/var/log";
};
};
};
};
# BCacheFS root partition configuration
bcachefsRoot = {
size = "100%";
content = {
type = "bcachefs";
# This refers to a filesystem in the `bcachefs_filesystems` attrset below.
filesystem = "mounted_subvolumes_in_multi";
label = "ssd.ssd1";
extraFormatArgs = [
"--discard"
];
};
};
in in
{ {
config = lib.mkIf isArm { imports = [ ../options.nix ];
config = lib.mkIf (isArm && cfg.enable) {
disko.devices = { disko.devices = {
nodev."/" = {
fsType = "tmpfs";
mountOptions = [
"mode=755"
"defaults"
"size=2G"
];
};
# root disk setup # root disk setup
disk.main = { disk.main = {
type = "disk"; type = "disk";
@@ -58,58 +113,33 @@ in
}; };
}; };
root = { root = if cfg.filesystem == "btrfs" then btrfsRoot else bcachefsRoot;
name = "btrfs-root"; };
size = "100%"; };
content = { };
type = "btrfs";
extraArgs = [ "-f" ]; # Override existing partition bcachefs_filesystems = lib.mkIf (cfg.filesystem == "bcachefs") {
# Subvolumes must set a mountpoint in order to be mounted, mounted_subvolumes_in_multi = {
# unless their parent is mounted type = "bcachefs_filesystem";
subvolumes = { # passwordFile = "/etc/nixos/pool.jwe";
"home" = { extraFormatArgs = [
mountOptions = [ "compress=zstd" ]; "--compression=zstd"
mountpoint = "/home"; ];
}; subvolumes = {
"root" = { "/root" = {
mountOptions = [ mountpoint = "/";
"compress=zstd" };
"noatime" "/persistent" = {
]; mountpoint = "/persistent";
mountpoint = "/root"; };
}; "/nix" = {
"nix" = { mountOptions = [
mountOptions = [ "noatime"
"compress=zstd" ];
"noatime" mountpoint = "/nix";
];
mountpoint = "/nix";
};
"etc" = {
mountOptions = [
"compress=zstd"
"noatime"
];
mountpoint = "/etc";
};
"tmp" = {
mountOptions = [
"compress=zstd"
"noatime"
];
mountpoint = "/tmp";
};
"log" = {
mountOptions = [
"compress=zstd"
"noatime"
];
mountpoint = "/var/log";
};
};
};
}; };
}; };
mountpoint = "/partition-root";
}; };
}; };
}; };

View File

@@ -3,5 +3,13 @@ with lib;
{ {
options.${namespace}.hardware.disko = { options.${namespace}.hardware.disko = {
enable = mkEnableOption "enable disko"; enable = mkEnableOption "enable disko";
filesystem = mkOption {
type = types.enum [
"bcachefs"
"btrfs"
];
default = "btrfs";
description = "Filesystem to use for the root partition";
};
}; };
} }

View File

@@ -9,91 +9,134 @@ let
cfg = config.${namespace}.hardware.disko; cfg = config.${namespace}.hardware.disko;
isArm = builtins.match "aarch64*" system != null; isArm = builtins.match "aarch64*" system != null;
rootDisk = "/dev/nvme0n1"; rootDisk = "/dev/nvme0n1";
in
{
imports = [ ../options.nix ];
config = lib.mkIf (cfg.enable && !isArm) { # BTRFS root partition configuration
disko.devices = { btrfsRoot = {
nodev."/" = { name = "btrfs-root";
fsType = "tmpfs"; size = "100%";
mountOptions = [ content = {
"mode=755" type = "btrfs";
"defaults" extraArgs = [ "-f" ]; # Override existing partition
"size=25%" # Subvolumes must set a mountpoint in order to be mounted,
]; # unless their parent is mounted
}; subvolumes = {
# root disk setup "home" = {
disk.main = { mountOptions = [ "compress=zstd" ];
type = "disk"; mountpoint = "/home";
device = rootDisk; };
imageSize = "32G"; "root" = {
content = { mountOptions = [
type = "gpt"; "compress=zstd"
# specify partitions "noatime"
partitions = { ];
# /boot mountpoint = "/root";
ESP = { };
priority = 1; "nix" = {
name = "ESP"; mountOptions = [
start = "1M"; "compress=zstd"
end = "1G"; "noatime"
type = "EF00"; ];
content = { mountpoint = "/nix";
type = "filesystem"; };
format = "vfat"; "etc" = {
mountpoint = "/boot"; mountOptions = [
mountOptions = [ "umask=0077" ]; "compress=zstd"
}; "noatime"
}; ];
mountpoint = "/etc";
root = { };
name = "btrfs-root"; "log" = {
size = "100%"; mountOptions = [
content = { "compress=zstd"
type = "btrfs"; "noatime"
extraArgs = [ "-f" ]; # Override existing partition ];
# Subvolumes must set a mountpoint in order to be mounted, mountpoint = "/var/log";
# unless their parent is mounted
subvolumes = {
"home" = {
mountOptions = [ "compress=zstd" ];
mountpoint = "/home";
};
"root" = {
mountOptions = [
"compress=zstd"
"noatime"
];
mountpoint = "/root";
};
"nix" = {
mountOptions = [
"compress=zstd"
"noatime"
];
mountpoint = "/nix";
};
"etc" = {
mountOptions = [
"compress=zstd"
"noatime"
];
mountpoint = "/etc";
};
"log" = {
mountOptions = [
"compress=zstd"
"noatime"
];
mountpoint = "/var/log";
};
};
};
};
};
}; };
}; };
}; };
}; };
# BCacheFS root partition configuration
bcachefsRoot = {
size = "100%";
content = {
type = "bcachefs";
# This refers to a filesystem in the `bcachefs_filesystems` attrset below.
filesystem = "mounted_subvolumes_in_multi";
label = "ssd.ssd1";
extraFormatArgs = [
"--discard"
];
};
};
in
{
imports = [ ../options.nix ];
config = lib.mkIf (!isArm && cfg.enable) {
disko.devices = lib.mkMerge [
{
disk = {
main = {
device = rootDisk;
type = "disk";
imageSize = "32G";
content = {
type = "gpt";
partitions = {
ESP = {
type = "EF00";
size = "100M";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
root = if cfg.filesystem == "btrfs" then btrfsRoot else bcachefsRoot;
};
};
};
};
bcachefs_filesystems = lib.mkIf (cfg.filesystem == "bcachefs") {
mounted_subvolumes_in_multi = {
type = "bcachefs_filesystem";
# passwordFile = "/etc/nixos/pool.jwe";
extraFormatArgs = [
"--compression=zstd"
];
subvolumes = {
"subvolumes/root" = {
mountpoint = "/";
mountOptions = [
"verbose"
];
};
"subvolumes/persistent" = {
mountpoint = "/persistent";
};
"subvolumes/nix" = {
mountOptions = [
"noatime"
];
mountpoint = "/nix";
};
};
};
};
}
(lib.mkIf (cfg.filesystem == "btrfs") {
nodev."/" = {
fsType = "tmpfs";
mountOptions = [
"mode=755"
"defaults"
"size=25%"
];
};
})
];
};
} }

View File

@@ -1,7 +1,12 @@
{ config, lib, ... }: {
config,
lib,
namespace,
...
}:
with lib; with lib;
let let
cfg = config.nas-apps.gitea; cfg = config.${namespace}.services.gitea;
hostAddress = "10.0.1.3"; hostAddress = "10.0.1.3";
# localAddress = "10.0.4.18"; # localAddress = "10.0.4.18";
# httpPort = 3000; # httpPort = 3000;

View File

@@ -1,7 +1,7 @@
{ lib, ... }: { lib, namespace, ... }:
with lib; with lib;
{ {
options.nas-apps.gitea = { options.${namespace}.services.gitea = {
enable = mkEnableOption "gitea service"; enable = mkEnableOption "gitea service";
httpPort = mkOption { httpPort = mkOption {

View File

@@ -9,21 +9,6 @@ let
cfg = config.${namespace}.services.home-assistant; cfg = config.${namespace}.services.home-assistant;
mosquittoPort = 1883; mosquittoPort = 1883;
zigbee2mqttPort = 8080; zigbee2mqttPort = 8080;
# In configuration.nix or a separate file
python-steam = pkgs.python3Packages.buildPythonPackage rec {
pname = "steam";
version = "1.4.4";
pyproject = false;
src = pkgs.fetchPypi {
inherit pname version;
sha256 = "sha256-K1vWkRwNSnMS9EG40WK52NR8i+u478bIhnOTsDI/pS4=";
};
buildInputs = with pkgs.python3Packages; [ setuptools ];
doCheck = false; # no tests in the PyPI tarball
};
in in
{ {
imports = [ ./options.nix ]; imports = [ ./options.nix ];
@@ -66,6 +51,7 @@ in
"nws" "nws"
"ollama" "ollama"
"onedrive" "onedrive"
"open_router"
"ping" "ping"
"radio_browser" "radio_browser"
"samsungtv" "samsungtv"
@@ -198,7 +184,7 @@ in
gehomesdk gehomesdk
onedrive-personal-sdk onedrive-personal-sdk
python-roborock python-roborock
python-steam pkgs.${namespace}.python-steam
apple-weatherkit apple-weatherkit
samsungctl samsungctl
@@ -234,7 +220,7 @@ in
trusted_proxies = [ trusted_proxies = [
"172.30.33.0/24" "172.30.33.0/24"
"10.0.1.4" "10.0.1.4"
"10.0.4.2" "10.0.1.3"
"10.0.1.18" "10.0.1.18"
"10.0.1.0/24" "10.0.1.0/24"
]; ];
@@ -256,7 +242,7 @@ in
openhasp = { openhasp = {
plate = { plate = {
objects = [ objects = [
{ {
obj = "p0b1"; # temperature label on all pages obj = "p0b1"; # temperature label on all pages
properties = { properties = {
"text" = ''{{ states("sensor.thermostat_current_temperature") }}°F''; "text" = ''{{ states("sensor.thermostat_current_temperature") }}°F'';
@@ -265,8 +251,8 @@ in
{ {
obj = "p1b2"; # light-switch toggle button obj = "p1b2"; # light-switch toggle button
properties = { properties = {
"val" = ''{{ 1 if states("light.living_room_lights") == "on" else 0 }}''; "val" = ''{{ 1 if states("light.living_room_lights") == "on" else 0 }}'';
"text" = ''{{ "\uE6E8" if is_state("light.living_room_lights", "on") else "\uE335" | e }}''; "text" = ''{{ "\uE6E8" if is_state("light.living_room_lights", "on") else "\uE335" | e }}'';
}; };
event = { event = {
"up" = { "up" = {

View File

@@ -1,73 +1,196 @@
{ ... }:
{ {
# Set up impernance configuration for things like bluetooth config,
# In this configuration with /etc and /var/log being persistent, only directories outside of that need to be done here. See hardware configuration for all mountpoints. lib,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.impermanence;
in
{
imports = [ ./options.nix ];
environment.persistence."/nix/persist/system" = { config = mkIf cfg.enable {
hideMounts = true; security.sudo.extraConfig = ''
directories = [ # rollback results in sudo lectures after each reboot
"/var/lib/bluetooth" Defaults lecture = never
"/var/lib/iwd" '';
"/var/lib/nixos"
"/var/lib/libvirt" system.activationScripts = {
"/var/lib/waydroid" "var-lib-private-permissions" = {
"/var/lib/systemd/coredump" deps = [ "createPersistentStorageDirs" ];
"/etc/NetworkManager/system-connections" text = ''
"/var/lib/tailscale" mkdir -p /var/lib/private
"/var/lib/homeassistant" chmod 0700 /var/lib/private
"/var/lib/mosquitto" '';
"/var/lib/music-assistant" };
"/var/lib/postgresql" };
"/var/lib/zigbee2mqtt"
boot.initrd.systemd.services.rootfs-cleanup = {
description = "Clean file system root";
wantedBy = [
"initrd.target"
];
after = [
"initrd-root-device.target"
];
before = [
"sysroot.mount"
];
unitConfig.DefaultDependencies = "no";
serviceConfig.Type = "oneshot";
script =
if (hasAttr "/" config.fileSystems) && (config.fileSystems."/".fsType == "btrfs") then
''
# workaround for machines without working rtc battery
# The time may not yet be correctly set, so wait until it is
if [[ $(date '+%s') -lt 1730469314 ]]; then
sleep 30 # this should hopefully be enough
fi
mkdir /btrfs_tmp
mount ${config.fileSystems."/".device} -t btrfs /btrfs_tmp
if [[ -e /btrfs_tmp/root ]]; then
mkdir -p /btrfs_tmp/old_roots
timestamp=$(date --date="@$(stat -c %X /btrfs_tmp/root)" "+%Y-%m-%d_%H:%M:%S")
mv /btrfs_tmp/root "/btrfs_tmp/old_roots/$timestamp"
fi
delete_subvolume_recursively() {
IFS=$'\n'
for i in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do
delete_subvolume_recursively "/btrfs_tmp/$i"
done
btrfs subvolume delete "$1" || rm -rf "$1"
}
for i in $(find /btrfs_tmp/old_roots/ -maxdepth 1 -atime +30); do
delete_subvolume_recursively "$i"
done
btrfs subvolume create /btrfs_tmp/root
umount /btrfs_tmp
''
else if (hasAttr "/" config.fileSystems) && (config.fileSystems."/".fsType == "bcachefs") then
''
# workaround for machines without working rtc battery
# The time may not yet be correctly set, so wait until it is
if [[ $(date '+%s') -lt 1730469314 ]]; then
sleep 30 # this should hopefully be enough
fi
if [[ -e /root_tmp/root ]]; then
mkdir -p /root_tmp/old_roots
timestamp=$(date --date="@$(stat -c %X /root_tmp/root)" "+%Y-%m-%d_%H:%M:%S")
mv /root_tmp/root "/root_tmp/old_roots/$timestamp"
fi
for i in $(find /root_tmp/old_roots/ -maxdepth 1 -atime +30); do
bcachefs subvolume delete $i
done
bcachefs subvolume create /root_tmp/root
''
else
# For tmpfs or other filesystems, do nothing
"";
};
assertions = [
{ {
directory = "/var/lib/colord"; assertion = hasAttr "/" config.fileSystems;
user = "colord"; message = "To use impermanence, you need to define a root volume";
group = "colord"; }
mode = "u=rwx,g=rx,o=";
{
assertion =
if hasAttr "/" config.fileSystems then
config.fileSystems."/".fsType == "btrfs"
|| config.fileSystems."/".fsType == "bcachefs"
|| config.fileSystems."/".fsType == "tmpfs"
else
false;
message = "rootfs must be btrfs, bcachefs, or tmpfs; not " + config.fileSystems."/".fsType;
}
{
assertion =
if
hasAttr "/" config.fileSystems
&& (config.fileSystems."/".fsType == "btrfs" || config.fileSystems."/".fsType == "bcachefs")
then
any (
t: t == "subvol=root" || t == "subvol=/root" || t == "X-mount.subdir=subvolumes/root"
) config.fileSystems."/".options
else
true;
message = "btrfs or bcachefs rootfs must mount subvolume root";
} }
{ {
directory = "/etc/nix"; assertion = !config.boot.isContainer;
user = "root"; message = "impermanence is not supported in containers";
group = "root";
mode = "u=rwx,g=rx,o=rx";
}
{
directory = "/var/lib/private/authentik/media";
user = "authentik";
group = "authentik";
mode = "u=rwx,g=,o=";
}
{
directory = "/var/lib/private";
mode = "u=rwx,g=rx,o=";
}
{
directory = "/media/nas";
user = "nas-apps";
group = "jallen-nas";
mode = "u=rwx,g=rx,o=rx";
}
{
directory = "/var/lib/crowdsec";
user = "crowdsec";
group = "crowdsec";
mode = "u=rwx,g=rwx,o=rx";
}
{
directory = "/plugins-storage";
user = "traefik";
group = "traefik";
mode = "u=rwx,g=rwx,o=rx";
} }
]; ];
files = [
"/etc/machine-id" environment.persistence.${cfg.persistencePath} = {
]; hideMounts = true;
directories = [
"/var/lib/bluetooth"
"/var/lib/iwd"
"/var/lib/nixos"
"/var/lib/libvirt"
"/var/lib/waydroid"
"/var/lib/systemd/coredump"
"/etc/NetworkManager/system-connections"
"/var/lib/tailscale"
"/var/lib/homeassistant"
"/var/lib/mosquitto"
"/var/lib/music-assistant"
"/var/lib/postgresql"
"/var/lib/zigbee2mqtt"
{
directory = "/var/lib/colord";
user = "colord";
group = "colord";
mode = "u=rwx,g=rx,o=";
}
{
directory = "/etc/nix";
user = "root";
group = "root";
mode = "u=rwx,g=rx,o=rx";
}
{
directory = "/var/lib/private/authentik/media";
user = "authentik";
group = "authentik";
mode = "u=rwx,g=,o=";
}
{
directory = "/var/lib/private";
mode = "u=rwx,g=rx,o=";
}
{
directory = "/media/nas";
user = "nas-apps";
group = "jallen-nas";
mode = "u=rwx,g=rx,o=rx";
}
{
directory = "/var/lib/crowdsec";
user = "crowdsec";
group = "crowdsec";
mode = "u=rwx,g=rwx,o=rx";
}
{
directory = "/plugins-storage";
user = "traefik";
group = "traefik";
mode = "u=rwx,g=rwx,o=rx";
}
];
files = [
"/etc/machine-id"
];
};
}; };
security.sudo.extraConfig = ''
# rollback results in sudo lectures after each reboot
Defaults lecture = never
'';
} }

View File

@@ -0,0 +1,12 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.impermanence = {
enable = mkEnableOption "enable impermanence";
persistencePath = mkOption {
type = types.str;
default = "/nix/persist/system";
description = "Path to the persistence directory";
};
};
}

View File

@@ -0,0 +1,53 @@
{
config,
lib,
pkgs,
namespace,
...
}:
let
cfg = config.${namespace}.monitoring;
in
{
options.${namespace}.monitoring = {
enable = lib.mkEnableOption "Common monitoring and system tools";
includeNetworkTools = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Include network monitoring tools";
};
includePerformanceTools = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Include performance monitoring tools";
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages =
with pkgs;
[
# Basic system monitoring
htop
]
++ lib.optionals cfg.includePerformanceTools [
glances
nmon
iotop
]
++ lib.optionals cfg.includeNetworkTools [
speedtest-cli
iftop
nethogs
tcpdump
wireshark-cli
];
# Enable common system services for monitoring
programs.screen.enable = lib.mkDefault true;
};
}

View File

@@ -9,113 +9,45 @@ let
cfg = config.${namespace}.network; cfg = config.${namespace}.network;
in in
{ {
options.${namespace}.network = with types; { imports = [
hostName = lib.mkOption { ./options.nix
type = str; ];
default = "nixos";
description = "The hostname of the system.";
};
ipv4 = {
method = mkOption {
type = types.str;
default = "auto";
};
address = lib.mkOption {
type = types.str;
default = "10.0.1.1";
};
gateway = lib.mkOption {
type = types.str;
default = "10.0.1.1";
};
dns = lib.mkOption {
type = types.str;
default = "10.0.1.1";
};
};
};
config = { config = {
networking = { networking = {
hostName = lib.mkForce cfg.hostName; hostName = lib.mkForce cfg.hostName;
# Enable Network Manager # Use networkd if enabled
networkmanager = { useNetworkd = lib.mkIf cfg.useNetworkd true;
enable = true;
wifi.powersave = lib.mkDefault false;
settings.connectivity.uri = lib.mkDefault "http://nmcheck.gnome.org/check_network_status.txt";
ensureProfiles = {
environmentFiles = [
config.sops.secrets.wifi.path
];
profiles = { # Set default gateway and nameservers if in manual mode
"Joey's Jungle 6G" = { defaultGateway = lib.mkIf (cfg.ipv4.method == "manual") {
connection = { address = cfg.ipv4.gateway;
id = "Joey's Jungle 6G"; interface = lib.mkIf (cfg.ipv4.interface != "") cfg.ipv4.interface;
type = "wifi";
};
ipv4 =
if (cfg.ipv4.method == "auto") then
{
method = "auto";
}
else
{
address1 = cfg.ipv4.address;
dns = cfg.ipv4.dns;
gateway = cfg.ipv4.gateway;
method = "manual";
};
ipv6 = {
addr-gen-mode = "stable-privacy";
method = "auto";
};
wifi = {
mode = "infrastructure";
ssid = "Joey's Jungle 6G";
};
wifi-security = {
key-mgmt = "sae";
psk = "$PSK";
};
};
"Joey's Jungle 5G" = {
connection = {
id = "Joey's Jungle 5G";
type = "wifi";
};
ipv4 =
if (cfg.ipv4.method == "auto") then
{
method = "auto";
}
else
{
address1 = cfg.ipv4.address;
dns = cfg.ipv4.dns;
gateway = cfg.ipv4.gateway;
method = "manual";
};
ipv6 = {
addr-gen-mode = "stable-privacy";
method = "auto";
};
wifi = {
mode = "infrastructure";
ssid = "Joey's Jungle 5G";
};
wifi-security = {
key-mgmt = "sae";
psk = "$PSK";
};
};
};
};
}; };
nameservers = lib.mkIf (cfg.ipv4.method == "manual") [ cfg.ipv4.dns ];
# Set hostId if provided
hostId = lib.mkIf (cfg.hostId != "") cfg.hostId;
# Configure NAT if enabled
nat = lib.mkIf cfg.nat.enable {
enable = true;
internalInterfaces = cfg.nat.internalInterfaces;
externalInterface = cfg.nat.externalInterface;
enableIPv6 = cfg.nat.enableIPv6;
};
# Configure firewall
firewall = { firewall = {
enable = cfg.firewall.enable;
allowPing = cfg.firewall.allowPing;
allowedTCPPorts = cfg.firewall.allowedTCPPorts;
allowedUDPPorts = cfg.firewall.allowedUDPPorts;
trustedInterfaces = cfg.firewall.trustedInterfaces;
# Default port ranges for KDE Connect
allowedTCPPortRanges = [ allowedTCPPortRanges = [
{ {
from = 1714; from = 1714;
@@ -123,7 +55,70 @@ in
} }
]; ];
allowedUDPPortRanges = config.networking.firewall.allowedTCPPortRanges; allowedUDPPortRanges = config.networking.firewall.allowedTCPPortRanges;
# Extra firewall commands
extraCommands = lib.mkIf (cfg.extraFirewallCommands != "") cfg.extraFirewallCommands;
}; };
# Configure iwd if enabled
wireless.iwd = lib.mkIf cfg.iwd.enable {
enable = true;
settings = cfg.iwd.settings;
};
# Configure NetworkManager
networkmanager = mkMerge [
# Disable NetworkManager when iwd is enabled
(mkIf cfg.iwd.enable {
enable = mkForce false;
wifi.backend = mkForce "iwd";
})
# Enable NetworkManager when wifi is enabled and iwd is disabled
(mkIf (cfg.wifi.enable && !cfg.iwd.enable) {
enable = true;
wifi.powersave = cfg.wifi.powersave;
settings.connectivity.uri = mkDefault "http://nmcheck.gnome.org/check_network_status.txt";
# Configure WiFi profiles if any are defined
ensureProfiles = mkIf (cfg.wifi.profiles != { }) {
environmentFiles = [
config.sops.secrets.wifi.path
];
profiles = mapAttrs (name: profile: {
connection = {
id = name;
type = "wifi";
};
ipv4 =
if (cfg.ipv4.method == "auto") then
{
method = "auto";
}
else
{
address1 = cfg.ipv4.address;
dns = cfg.ipv4.dns;
gateway = cfg.ipv4.gateway;
method = "manual";
};
ipv6 = {
addr-gen-mode = "stable-privacy";
method = "auto";
};
wifi = {
mode = "infrastructure";
ssid = profile.ssid;
};
wifi-security = {
key-mgmt = profile.keyMgmt;
psk = profile.psk;
};
}) cfg.wifi.profiles;
};
})
];
}; };
}; };
} }

View File

@@ -0,0 +1,162 @@
{
lib,
namespace,
...
}:
with lib;
{
options.${namespace}.network = with types; {
hostName = lib.mkOption {
type = str;
default = "nixos";
description = "The hostname of the system.";
};
ipv4 = {
method = mkOption {
type = types.str;
default = "auto";
description = "Method for IPv4 configuration (auto or manual).";
};
address = lib.mkOption {
type = types.str;
default = "10.0.1.1/24";
description = "IPv4 address with subnet mask (e.g., 10.0.1.1/24).";
};
gateway = lib.mkOption {
type = types.str;
default = "10.0.1.1";
description = "IPv4 default gateway.";
};
interface = lib.mkOption {
type = types.str;
default = "";
description = "Interface for the default gateway (required when using networkd).";
};
dns = lib.mkOption {
type = types.str;
default = "10.0.1.1";
description = "IPv4 DNS server.";
};
};
useNetworkd = mkOption {
type = types.bool;
default = false;
description = "Whether to use systemd-networkd for networking.";
};
nat = {
enable = mkOption {
type = types.bool;
default = false;
description = "Whether to enable NAT.";
};
internalInterfaces = mkOption {
type = types.listOf types.str;
default = [ ];
description = "List of internal interfaces for NAT.";
};
externalInterface = mkOption {
type = types.str;
default = "";
description = "External interface for NAT.";
};
enableIPv6 = mkOption {
type = types.bool;
default = false;
description = "Whether to enable IPv6 NAT.";
};
};
firewall = {
enable = mkOption {
type = types.bool;
default = true;
description = "Whether to enable the firewall.";
};
allowPing = mkOption {
type = types.bool;
default = true;
description = "Whether to allow ICMP ping.";
};
allowedTCPPorts = mkOption {
type = types.listOf types.port;
default = [ ];
description = "List of allowed TCP ports.";
};
allowedUDPPorts = mkOption {
type = types.listOf types.port;
default = [ ];
description = "List of allowed UDP ports.";
};
trustedInterfaces = mkOption {
type = types.listOf types.str;
default = [ ];
description = "List of trusted interfaces.";
};
};
wifi = {
enable = mkOption {
type = types.bool;
default = true;
description = "Whether to enable WiFi configuration.";
};
powersave = mkOption {
type = types.bool;
default = false;
description = "Whether to enable WiFi power saving.";
};
profiles = mkOption {
type = types.attrsOf (
types.submodule {
options = {
ssid = mkOption {
type = types.str;
description = "SSID of the WiFi network.";
};
psk = mkOption {
type = types.str;
default = "$PSK";
description = "PSK environment variable for the WiFi password.";
};
keyMgmt = mkOption {
type = types.str;
default = "sae";
description = "Key management type (e.g., sae, wpa-psk).";
};
};
}
);
default = { };
description = "WiFi network profiles.";
};
};
hostId = mkOption {
type = types.str;
default = "";
description = "Host ID for ZFS and other services.";
};
iwd = {
enable = mkOption {
type = types.bool;
default = false;
description = "Whether to enable iwd for wireless networking.";
};
settings = mkOption {
type = types.attrs;
default = { };
description = "Settings for iwd.";
};
};
extraFirewallCommands = mkOption {
type = types.str;
default = "";
description = "Extra commands for the firewall.";
};
};
}

View File

@@ -3,10 +3,12 @@
nix = { nix = {
settings = { settings = {
substituters = [ substituters = [
"https://nixos-raspberrypi.cachix.org"
"https://nix-community.cachix.org" "https://nix-community.cachix.org"
"https://cache.nixos.org/" "https://cache.nixos.org/"
]; ];
trusted-public-keys = [ trusted-public-keys = [
"nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
]; ];
warn-dirty = lib.mkForce false; warn-dirty = lib.mkForce false;

View File

@@ -8,18 +8,6 @@
with lib; with lib;
let let
cfg = config.${namespace}.services.ollama; cfg = config.${namespace}.services.ollama;
llamaPackage = pkgs.llama-cpp.overrideAttrs (_old: {
src = pkgs.fetchFromGitHub {
owner = "ggml-org";
repo = "llama.cpp";
rev = "b4920";
sha256 = "sha256-SnQIeY74JpAPRMxWcpklDH5D4CQvAgi0GYx5+ECk2J4=";
};
# Optionally override other attributes if you need to
# version = "my-fork-version";
# pname = "llama-cpp-custom";
});
in in
{ {
imports = [ ./options.nix ]; imports = [ ./options.nix ];

View File

@@ -1,11 +1,12 @@
{ {
lib, lib,
config, config,
namespace,
... ...
}: }:
with lib; with lib;
let let
cfg = config.nas-apps.orca-slicer; cfg = config.${namespace}.services.orca-slicer;
in in
{ {
imports = [ ./options.nix ]; imports = [ ./options.nix ];

View File

@@ -1,7 +1,7 @@
{ lib, ... }: { lib, namespace, ... }:
with lib; with lib;
{ {
options.nas-apps.orca-slicer = { options.${namespace}.services.orca-slicer = {
enable = mkEnableOption "orca slicer docker service"; enable = mkEnableOption "orca slicer docker service";
autoStart = mkOption { autoStart = mkOption {

View File

@@ -0,0 +1,92 @@
{
config,
lib,
pkgs,
namespace,
...
}:
let
cfg = config.${namespace}.hardware.raspberry-pi;
in
{
options.${namespace}.hardware.raspberry-pi = {
enable = lib.mkEnableOption "Raspberry Pi common configuration";
variant = lib.mkOption {
type = lib.types.enum [
"4"
"5"
];
description = "Raspberry Pi variant (4 or 5)";
};
};
config = lib.mkIf cfg.enable {
# Common Raspberry Pi packages
environment.systemPackages =
with pkgs;
[
libraspberrypi
raspberrypi-eeprom
raspberrypifw
raspberrypiWirelessFirmware
raspberrypi-armstubs
]
++ lib.optionals (cfg.variant == "4") [
i2c-tools
]
++ lib.optionals (cfg.variant == "5") [
erofs-utils
fex
squashfuse
squashfsTools
];
# Common nixpkgs overlays for Raspberry Pi
nixpkgs.overlays = lib.mkAfter [
(_self: super: {
# This is used in (modulesPath + "/hardware/all-firmware.nix") when at least
# enableRedistributableFirmware is enabled
inherit (super) raspberrypiWirelessFirmware;
# Some derivations want to use it as an input,
# e.g. raspberrypi-dtbs, omxplayer, sd-image-* modules
inherit (super) raspberrypifw;
})
];
# Common Bluetooth configuration
systemd.services.btattach = {
before = [ "bluetooth.service" ];
after = [ "dev-ttyAMA0.device" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.bluez}/bin/btattach -B /dev/ttyAMA0 -P bcm -S 3000000";
};
};
# Common hardware settings
hardware.i2c.enable = lib.mkIf (cfg.variant == "4") true;
# Pi 5 specific settings
hardware.graphics.enable32Bit = lib.mkIf (cfg.variant == "5") (lib.mkForce false);
zramSwap.enable = lib.mkIf (cfg.variant == "5") true;
# Pi 5 specific system tags
system.nixos.tags = lib.mkIf (cfg.variant == "5") (
let
bootCfg = config.boot.loader.raspberry-pi;
in
[
"raspberry-pi-${bootCfg.variant}"
bootCfg.bootloader
config.boot.kernelPackages.kernel.version
]
);
# Common programs
programs.kdeconnect.enable = lib.mkDefault false;
# Root user shell configuration
users.users.root.shell = pkgs.zsh;
};
}

View File

@@ -1,7 +1,7 @@
{ lib, ... }: { lib, ... }:
let let
defaultSops = (lib.snowfall.fs.get-file "secrets/pi4-secrets.yaml"); # defaultSops = (lib.snowfall.fs.get-file "secrets/pi4-secrets.yaml");
# sharedSops = (lib.snowfall.fs.get-file "secrets/secrets.yaml"); defaultSops = (lib.snowfall.fs.get-file "secrets/secrets.yaml");
in in
{ {
# Permission modes are in octal representation (same as chmod), # Permission modes are in octal representation (same as chmod),

View File

@@ -19,7 +19,7 @@ let
cacheUrl = "http://${serverIp}:9012"; cacheUrl = "http://${serverIp}:9012";
cloudUrl = "http://${config.containers.nextcloud.localAddress}:80"; cloudUrl = "http://${config.containers.nextcloud.localAddress}:80";
giteaUrl = "http://${config.containers.gitea.localAddress}:${toString config.containers.gitea.config.services.gitea.settings.server.HTTP_PORT}"; giteaUrl = "http://${config.containers.gitea.localAddress}:${toString config.containers.gitea.config.services.gitea.settings.server.HTTP_PORT}";
hassUrl = "http://homeassistant.local:8123"; hassUrl = "http://nuc-nixos.local:8123";
immichUrl = "http://${serverIp}:${toString config.services.immich.port}"; immichUrl = "http://${serverIp}:${toString config.services.immich.port}";
jellyfinUrl = "http://${serverIp}:8096"; jellyfinUrl = "http://${serverIp}:8096";
jellyseerrUrl = "http://${config.containers.jellyseerr.localAddress}:${toString config.containers.jellyseerr.config.services.jellyseerr.port}"; jellyseerrUrl = "http://${config.containers.jellyseerr.localAddress}:${toString config.containers.jellyseerr.config.services.jellyseerr.port}";

View File

@@ -8,6 +8,18 @@
with lib; with lib;
let let
cfg = config.${namespace}.user; cfg = config.${namespace}.user;
isRoot = (cfg.name == "root");
# Common SSH keys used across systems
commonSshKeys = [
# MacBook
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCw9zq8DLGByI5v2gAn95hKNyOsm3g61a2buxu2BBMFysQJgmZPCCLUqRJKhSM5Vm/JOgsAmdpRBRZQoHD+6S844CJHb4v4VIbjkyQgYCuM7Rst2IOZ5QybvsA2/D0nwytZ+HXQqDj2AagUYDbz0gyyIHkDQ5YGBMkvkWz/h1Vci6aoBM7VihEDM4KlWoTVuPeASGM8r5IZ2FS83Djbqo4ov6AYvLMrKB9Z7hmFgH6R3LE0gxOkzbGVXtSuvJyrjvgytoT22UhATjjxSQ9D+YJXXkQoB3lUdg8OoIquUPjMZpl4mR8ffvseWPfcvD1XlD5t+TOHFqKpESO547tlOBYhdpew+NSgAXpamCU6oyV8tDCywLQu2ucxHRn78u6WXzWHkDtffdhzmk6TZaPhWqVHuTGjR4higBgGqUfSaKOMszt+FDRZAr3HtuQ2+zJ8bowK9fW5OqilTtK2HtQqroD9ApegDNbqOz6kGy5IycSXvqPURy/M4lxZxbtBPuemcJs= mattjallen@MacBook-Pro.local"
# Desktop Windows
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDZ2PYPjZddOzR8OJj16G88KcUhCDLkvrEmpUQP0wKHDUuA27HQQ2ORo66asadwGHY3k1VDZ1ei9l9H++SIIeKOaaUr5yZdktvj4POUNtbd9ZhcS7sZU7BSF+NMDM+h3tImh6z0S7mWvRQOUv3ZM+ZER+5xTWJVG1OOJEpb1drxJk6Qz0wbZKSR7TPNFBLLXlVy7hkNYf07RtDyhCCxNB3hJfa8c+oztnWumwDhDQWLqiUXWIU2QH6iRLGl/WYnujtNvVVaV/Hn3JJkS6MM9dnV3cpoIO0+J7+WfsN9rZ0wXt5yY3GhiGXwmcO5eYVli8lHlLWtK7aYSETyry6CBsLbojzOQO5rSqhpwfF2njAAFAQU0UjLc8PahisIuFKCwHH4iyXXOagiv5K1Mc/0Ak+WhhMPee6vV2p7NTyNpXRvouDbWy5cSRH31WgQ9fK5mIGe5v8nGGqtEhUubUkiOgP+H3UbT2V/nTv/TFKdJcKw+WmizvTrxBmaMjWALlkYl+s= mattl@Jallen-PC"
# Desktop NixOS
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTBMydhOc6SnOdB5WrEd7X07DrboAtagCUgXiOJjLov matt@matt-nixos"
];
in in
{ {
options.${namespace}.user = with types; { options.${namespace}.user = with types; {
@@ -41,11 +53,58 @@ in
default = null; default = null;
description = "Path to the password file for this user account"; description = "Path to the password file for this user account";
}; };
sshKeys = lib.mkOption {
type = listOf str;
default = [ ];
description = "List of SSH public keys for the user.";
};
enableCommonSshKeys = lib.mkOption {
type = bool;
default = true;
description = "Whether to include common SSH keys used across systems.";
};
uid = lib.mkOption {
type = int;
default = if isRoot then ids.uids.root else 1000;
description = "The user ID for the user account.";
};
packages = lib.mkOption {
type = listOf package;
default = [ ];
description = "List of packages to install for this user.";
};
linger = lib.mkOption {
type = bool;
default = false;
description = "Whether to enable systemd user service persistence.";
};
password = lib.mkOption {
type = nullOr str;
default = null;
description = "Plain text password for the user (development only).";
};
hashedPassword = lib.mkOption {
type = nullOr str;
default = null;
description = "Hashed password for the user.";
};
mutableUsers = lib.mkOption {
type = bool;
default = false;
description = "Whether users are mutable (can be modified after creation).";
};
}; };
config = { config = {
users.mutableUsers = cfg.mutableUsers;
users.users.${cfg.name} = { users.users.${cfg.name} = {
inherit (cfg) name; inherit (cfg)
name
uid
linger
packages
;
extraGroups = [ extraGroups = [
"wheel" "wheel"
@@ -64,14 +123,27 @@ in
"power" "power"
"nix" "nix"
"i2c" "i2c"
] ++ cfg.extraGroups; ]
++ cfg.extraGroups;
group = "users"; group = "users";
home = "/home/${cfg.name}"; home = "/home/${cfg.name}";
isNormalUser = true; isNormalUser = (!isRoot);
isSystemUser = isRoot;
shell = lib.mkForce pkgs.zsh; shell = lib.mkForce pkgs.zsh;
uid = 1000;
hashedPasswordFile = cfg.passwordFile; # SSH keys - combine user-specific and common keys
} // cfg.extraOptions; openssh.authorizedKeys.keys = cfg.sshKeys ++ (lib.optionals cfg.enableCommonSshKeys commonSshKeys);
# Authentication - priority: passwordFile > hashedPassword > password
hashedPasswordFile = lib.mkIf (cfg.passwordFile != null) cfg.passwordFile;
hashedPassword = lib.mkIf (
cfg.passwordFile == null && cfg.hashedPassword != null
) cfg.hashedPassword;
password = lib.mkIf (
cfg.passwordFile == null && cfg.hashedPassword == null && cfg.password != null
) cfg.password;
}
// cfg.extraOptions;
}; };
} }

View File

@@ -0,0 +1,45 @@
{
lib,
fetchurl,
vscode-utils,
pkgs,
}:
let
version = "0.0.49";
publisher = "jeanp413";
name = "open-remote-ssh";
in
vscode-utils.buildVscodeMarketplaceExtension {
mktplcRef = {
inherit name publisher version;
};
vsix = fetchurl {
url = "https://open-vsx.org/api/${publisher}/${name}/${version}/file/${publisher}.${name}-${version}.vsix";
sha256 = "sha256-QfJnAAx+kO2iJ1EzWoO5HLogJKg3RiC3hg1/u2Jm6t4=";
};
unpackPhase = ''
${pkgs.unzip}/bin/unzip -q $src
'';
meta = with lib; {
description = "Use any remote machine with a SSH server as your development environment";
longDescription = ''
The Open Remote SSH extension allows you to open a remote folder on any
remote machine, virtual machine, or container with a running SSH server
and take full advantage of VS Code's feature set. This is an open-source
alternative to Microsoft's proprietary Remote SSH extension, designed to
work with VSCodium and other open-source VS Code variants.
Note: This extension requires enabling proposed APIs in VSCodium/Code-OSS.
You need to add "jeanp413.open-remote-ssh" to the "enable-proposed-api"
array in ~/.vscode-oss/argv.json
'';
homepage = "https://github.com/jeanp413/open-remote-ssh";
changelog = "https://github.com/jeanp413/open-remote-ssh/releases";
license = licenses.mit;
platforms = platforms.all;
};
}

View File

@@ -1,5 +1,5 @@
{ python3Packages, fetchFromGitHub, ... }: { python3Packages, fetchFromGitHub, ... }:
python3Packages.buildPythonPackage rec { python3Packages.buildPythonPackage {
pname = "pipewire-python"; pname = "pipewire-python";
version = "0.2.3"; version = "0.2.3";
format = "pyproject"; format = "pyproject";
@@ -12,6 +12,10 @@ python3Packages.buildPythonPackage rec {
}; };
buildInputs = with python3Packages; [ flit-core ]; buildInputs = with python3Packages; [ flit-core ];
nativeBuildInputs = with python3Packages; [ build wheel ]; nativeBuildInputs = with python3Packages; [
doCheck = false; # no tests in the PyPI tarball build
wheel
];
doCheck = false;
} }

View File

@@ -0,0 +1,16 @@
{ python3Packages, fetchPypi, ... }:
python3Packages.buildPythonPackage rec {
pname = "steam";
version = "1.4.4";
pyproject = false;
src = fetchPypi {
inherit pname version;
sha256 = "sha256-K1vWkRwNSnMS9EG40WK52NR8i+u478bIhnOTsDI/pS4=";
};
buildInputs = with python3Packages; [ setuptools ];
doCheck = false; # no tests in the PyPI tarball
}

View File

@@ -11,7 +11,10 @@ python3Packages.buildPythonPackage rec {
sha256 = "sha256-rEmWsCIBGNmDEecVT8O9O5/E0WVpTfA7amFI70DEmiI="; sha256 = "sha256-rEmWsCIBGNmDEecVT8O9O5/E0WVpTfA7amFI70DEmiI=";
}; };
buildInputs = with python3Packages; [ poetry-core hatchling ]; buildInputs = with python3Packages; [
poetry-core
hatchling
];
nativeBuildInputs = with python3Packages; [ nativeBuildInputs = with python3Packages; [
aiodns aiodns

View File

@@ -1,5 +1,6 @@
jallen-nas: jallen-nas:
admin_password: ENC[AES256_GCM,data:0XUblR800UyliA8JfYUZbncDRxiU6eoTaf3i80+OCwJ/31oBhSqj9OtgYeRg3IyURwik1Nk/609IuHjIhly3mgTjOD6Hpzxpag==,iv:0yO3z8ItHRQFeI9JOnFTKhKVHi5u9cMtpglFRlkvYLE=,tag:iUd79iWAJQ9iqP0qolSwfA==,type:str] admin_password: ENC[AES256_GCM,data:0XUblR800UyliA8JfYUZbncDRxiU6eoTaf3i80+OCwJ/31oBhSqj9OtgYeRg3IyURwik1Nk/609IuHjIhly3mgTjOD6Hpzxpag==,iv:0yO3z8ItHRQFeI9JOnFTKhKVHi5u9cMtpglFRlkvYLE=,tag:iUd79iWAJQ9iqP0qolSwfA==,type:str]
nas_pool: ENC[AES256_GCM,data:sx78UwPhgklzf8e/Tjw/2esAspMFTnSj3ahEtv3btVLpotwE9UDMtFGPkkh8r82mGLmzC3dRsWYaOAFW4ioVBpmiochpdAR+QfmOBgsOFQPKdqlF0DDXcElT+qxhJ9Uoo26v0XZwaTdplI9f8OTxCPZtihX4Y27sFNjhCM9loN+IJKqxPFNaK4RRJtFQL5sG2OaiPkSd/Wf082RTW12CNWYRY2RSTCqzKB7YclTcAAwdj72uJTER7uU8Fr4/PpUN7AJ/GGK9OetY5QbkieelwESBqQR12CLCF1/d82CaN/lL8jtuc3Q7QFsRGr28fHBNqOywkJ6aNxxZCKS0fjQGpasroQtt5fv8pNMs7Gu67SiuKGb/z0wKdd5c03sxD/42bSf2aM1vUW8Bm6PGGVk8OT0UWMA9FhbWeVr3HSb6gFktOlC3oRLGPUU3WcdkLAmqCWtSQuVHNx6/s/JDj8UyWqyDCu/7afxKX9gt+I6G+3EWmVs/fi91+tpYd5hyhTBFmOoC+wooVqnGVXtM7cUbWTJS4Ua82zcroHj+KKoX5sOOzOXfI9TVRLccPYlbdSY46467+ycEXsc7cxnigKerO29GhFFoC3pGxwwBcMYWDl8466H7cCDpYohPdY/q9dYuB3LBEzFizmANQ/kGEkZt1I1fydO+sN5ZcTyhnOCHQFI9lYPDsQ6Gztb4n8PEHYdHbp1Zue0p82H8hhgC4v2ne2GezdiYRCl1y2MZvF8CgkwAw/PkX2cb8CAMuG0ZHtOLFwUPjPk2KCe8G6yK0SekE/Zm36wlPz8yLd9S2dLRBcxXZiQEHXqONjVGf3fhbE8QyDZ83xITlRTnbQt1rF0vPrgeJD/m3u4QMo1BrF6EIsCCesQrNPLRRGddh7uyhVX9OKy633OKxLy2Gt1VjfuE77ZaPmNgIjk1geCXuKUFD/BC9DJiknVuajS8tFHIIh7YYUBZY/Q=,iv:ZvI+1L4Zwgwz0t++fvVxX7HXXuS8G8DcKz7WDlq9oS8=,tag:sbXluJh9CQhJH11gk2Ohfg==,type:str]
ups_password: ENC[AES256_GCM,data:tYuJ9nU3E2/Ko6Y=,iv:lQq+g68lKCp1rmPvS/84xGIXHxD9zY5zZrrjEJlY8Hs=,tag:p6McEr+sXGAQyMAz1Kaxfw==,type:str] ups_password: ENC[AES256_GCM,data:tYuJ9nU3E2/Ko6Y=,iv:lQq+g68lKCp1rmPvS/84xGIXHxD9zY5zZrrjEJlY8Hs=,tag:p6McEr+sXGAQyMAz1Kaxfw==,type:str]
authentik-env: ENC[AES256_GCM,data:AzHHGyhoyMp/ebnK6LQ5apBUhQT04SPJrtA6XcdaQ38C+fYuG2ph2iWFb+giafxCe8IXWAYT8CWoeqcspM7CPSAAKgqfVaPhMvjXqLxCY/rpegb5jBD1U6tURhPsH3ADrERk+kCmTV2eUpuV+nluiGM+fRdwhB0zu378HKwhXCpSO4L24aXhe9pxxxaTQzncWH6zW5iaRdouDVr1bAUzLi9BpnmS0ZK/rfLq2whErCeN++Srx6aCgwJ7jaqetBglQkIl3YG6flS8u3vsKtI+RVaNJ5tzrWR/qv0vBy8y1PZEuuXZdiHjn1hjiPE1T31j2+aQdbX70RaJfIt6E4lVtArQHv8PTUDxUoxcnUv52xLTStT5/UdIlNoZjPMwvaknpK7Z0uw9w4j76gmgk06xsxoCpnXIGTm1QpGqviBhgfNs5Va/qi4MBfByaym3UAz9LPHs4keuvJNN8dS0q5OMnRswl14PjIb1MIKB/QCVHvb4hO7eIRiWOkA7nb9LP/y1mjAYslr+I+GNpU8oIYTAvKoMS7ZgC49RoLWytAXUru2I7CqDR9zgPzlDQ9gLPoFKw2uKulpAy0ayQWPcgPA2CFmF+5zdINNSNKn0gRZ/2RTc3DiWmzo4P13EmrOwvkWCkiswFu1d6ctKZFhQnfPuj9LRGp/Os55JpLrreSyRJug6lgR4bPdC3x8sbxNmb5S2Y+4aFfgPXfdCdXs5b+8j28d1d4EoOO/arUzNADz9ODD5esb2g8UC2QtQd0RRYX/qmiM=,iv:YKvFxz3M8HKlg56JfN6uv8hvCFlEbhBkaSQz1v9l3zk=,tag:rz7UixSDqOXH7Ga6mkVYAw==,type:str] authentik-env: ENC[AES256_GCM,data:AzHHGyhoyMp/ebnK6LQ5apBUhQT04SPJrtA6XcdaQ38C+fYuG2ph2iWFb+giafxCe8IXWAYT8CWoeqcspM7CPSAAKgqfVaPhMvjXqLxCY/rpegb5jBD1U6tURhPsH3ADrERk+kCmTV2eUpuV+nluiGM+fRdwhB0zu378HKwhXCpSO4L24aXhe9pxxxaTQzncWH6zW5iaRdouDVr1bAUzLi9BpnmS0ZK/rfLq2whErCeN++Srx6aCgwJ7jaqetBglQkIl3YG6flS8u3vsKtI+RVaNJ5tzrWR/qv0vBy8y1PZEuuXZdiHjn1hjiPE1T31j2+aQdbX70RaJfIt6E4lVtArQHv8PTUDxUoxcnUv52xLTStT5/UdIlNoZjPMwvaknpK7Z0uw9w4j76gmgk06xsxoCpnXIGTm1QpGqviBhgfNs5Va/qi4MBfByaym3UAz9LPHs4keuvJNN8dS0q5OMnRswl14PjIb1MIKB/QCVHvb4hO7eIRiWOkA7nb9LP/y1mjAYslr+I+GNpU8oIYTAvKoMS7ZgC49RoLWytAXUru2I7CqDR9zgPzlDQ9gLPoFKw2uKulpAy0ayQWPcgPA2CFmF+5zdINNSNKn0gRZ/2RTc3DiWmzo4P13EmrOwvkWCkiswFu1d6ctKZFhQnfPuj9LRGp/Os55JpLrreSyRJug6lgR4bPdC3x8sbxNmb5S2Y+4aFfgPXfdCdXs5b+8j28d1d4EoOO/arUzNADz9ODD5esb2g8UC2QtQd0RRYX/qmiM=,iv:YKvFxz3M8HKlg56JfN6uv8hvCFlEbhBkaSQz1v9l3zk=,tag:rz7UixSDqOXH7Ga6mkVYAw==,type:str]
traefik: traefik:
@@ -142,7 +143,7 @@ sops:
WmFoMmlNbXUvREZESnU3ZzFMbkNGVjAKGLZWo3E9098b2kOn77U/CGQpo4/mqSXY WmFoMmlNbXUvREZESnU3ZzFMbkNGVjAKGLZWo3E9098b2kOn77U/CGQpo4/mqSXY
5+rZJuC7Iqh+VDW519Cf1go+0k05rgree3IqHXN2/KHX+pC0L6CkyA== 5+rZJuC7Iqh+VDW519Cf1go+0k05rgree3IqHXN2/KHX+pC0L6CkyA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-06-11T03:10:55Z" lastmodified: "2025-08-20T23:28:44Z"
mac: ENC[AES256_GCM,data:01jMwSJK3zW0vltFj06veiAsJBBSKrN5SJuU87/sfhjrfZHWQ/fOITK4Ihz/d9/J2kA9bMJEM50C3cSBaakdYW4RDkL1P83aOsJLjp/c3WjJ8BApDdzoQzUvkjIVt7qd/jNg13c8d+ofBkeSsZ8T3COrOHCc3RgG961T4Ij9WBY=,iv:cNJUQ8ZEPKPKfUlTYD/Zlfomev3ZC/mnP90Me3ycjMc=,tag:LluaNhFpQbuql208sEyPkA==,type:str] mac: ENC[AES256_GCM,data:ArFy5LYoBr6AhBQkUxGqe2k1wQn/XwRkZYRDTVmFIuCpNWYgTRtUYYBnbvn2FPvpq4avm65/iUKTkvkSnSWMPmj4oCAVOTgZxG0eOlC8c6XEFq0ir9Nz6K8oyStxTcDWfiYlHAxx7EBeGIBrj8TcQHuE2P/PxdPYEJDbnohCRnY=,iv:1tyLcAdfvO/kDE2yZ4kU3l3xCMUVEf9Z2z9X8gjacLY=,tag:H0vcvlmzTaatgtnoiyDvSw==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.10.2 version: 3.10.2

View File

@@ -38,128 +38,128 @@ sops:
- recipient: age157jemphjzg6zmk373vpccuguyw6e75qnkqmz8pcnn2yue85p939swqqhy0 - recipient: age157jemphjzg6zmk373vpccuguyw6e75qnkqmz8pcnn2yue85p939swqqhy0
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkcmtYM1FKMXd1Y1EvMVhk YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSZnFYM2JnRXd2emJFSm5h
UFNSZTVWUGFiKzRsUVhvQWJGa2VJVkxjNkJBCmZBTjNTZlN2NGRZNm8xQkZKazU2 Q3RSYktiLzMxRlBKZnpVOStLaXNHNmtLVVgwCkM5UnZEN24wOUVuNnZyMVhoM3Bx
UkRwZjFtYnRTcUlva2k1S05qNzVhMUkKLS0tIDZHR2tWS0RvZGNEZ3hpeVJjUkxN SFZhQWhEbHB3Q1NRSUt5S3lBYmtWTFEKLS0tIE5uTTB6Q0hrWUtodXkzZkZkSHpG
OHlHdVdGcnpaYnkrRHU4b2xzclN0MlUK53/V+ITz49SUgxVeaxBSh7yITBEqWPlB Q0t2cVV3WFAzZjNrdDZiS1Q5YjJybU0K6biA2WO5121mlrOXI7cuhsfFUCB5fAOi
uUIkdKVq93XFfpTG4st00Uj6oow9M+QB43bG/hXjfiv7ALDEfT3XaA== scwVKg77FjgECrjJIWwlzGrznW5pJaQ5ZIfHMW+wt5zVBTHfwU9Oyw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age13g9a4d4jrvckfddpgn8sm4kjtzajr67le56pfdg78ktr5pd09phq32j89u - recipient: age13g9a4d4jrvckfddpgn8sm4kjtzajr67le56pfdg78ktr5pd09phq32j89u
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkRjVWY0pKNzB3QmpXRGJM YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNZGxsMHl0cHFObkJRL2Vu
MEticE10YWhKNm94R1VIbWpwUklUV0R5TkZVCkR1bmZEV0hOQWt6WXRlMk9mYmlx MThuK24vQ2JtL2ZucG5LYXBzL2pJTjMwcURFClZsMkZCWHptZlpDakl3NW1TRVhN
Q3cwMEdGeGs3RGd2NGIzTUhFb2tWVjgKLS0tIFNmVjdXdjBmZGZTYy9RZWxQbFo1 Q3dkK0FVblZxamhETEhza2dyV0hoQzAKLS0tIFlhK0c3czRCbGU4eWZNRXRNcjN4
YUp0SHhqejc3dndlOFA4dDB4aXFUb0kKcKAVGwr8t2nsFNcmrkwpYAhdjfdsV9oN MTkvZ3pVa2VrOGlWZzI1MEh6azJXRUUK1g78WdC8kugPyfSNmE/abme0OAux8r6F
YZqicWUc07Lu05vMS8MomwupAtkwxy+MswWd6jD0nzp8CIfhOgOpjA== HzLnPgFdqbmxMqgEx1kqV10dAdp6JAy31zXHoX/Gb0dK271gRo/1Wg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1wpvfpv5n32lruk7c0da4uaeapsmhjxdvg8z4ljehn06l6g2y0e0sum404l - recipient: age1wpvfpv5n32lruk7c0da4uaeapsmhjxdvg8z4ljehn06l6g2y0e0sum404l
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNQUpETHZvM0Y3ZHQ1a082 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2R3lHQ3hzQW9SNWNiS3RZ
b01xWDI5d0NpREhOd3JVQ1cyRlhzakFTR0FRCmJrMTRYTWhhWERlbFU0cGlqZ010 VTFUemJRUnRLcHNWdVVvblRrMk1rVHRDY0JnCjVSWFRYTGorWTRDOU9SMXNhV3BP
QzdHSWlxZEtodVB0cXdiYi9MM0h4WW8KLS0tIGNLNlVERk1RY3haaWpRd1RjTkY0 akRUaytZYThHMS9HK2lQUTBBQ1FWWGMKLS0tIERnTzBuSlJLVmtSNW8wcHhxWVlt
NkhqampldGlwNi85NUxLUFNKaXBwRW8K6DLM1IE1pxpuQ/NA/ywLCocqVxQ/4a9a TnJmQTNFWWtRZ0VQdkZmZ0pjS0dLMm8K5szoKD6/RvBrkUL2P4asRzCq+718jDaz
e8aNYcwXaD628b5HLotHtgzrjA37txAzfqElzvamOxL8/1IgvZVauQ== wuTQ+6Jqcso3caHbJpcf1VlkE/HjaI64cVMlt97j53XT02JyZmxiWw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1jv8ap5zwa49ftv0gg7wqf5ps0e68uuwxe2fekjsn0zkyql964unqyc58rf - recipient: age1jv8ap5zwa49ftv0gg7wqf5ps0e68uuwxe2fekjsn0zkyql964unqyc58rf
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSdUF5VWhVNXJWMWxwb2hI YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKRnpyRy9Ra2RSeU96K2Qz
Mis5aGtXNUdWVnNBMjg2L050eXhYUWRVL0hFCmlSQWtLTlFGNFVITmt6dDcvQ0Rp MWFjcjZVQnk0NE9QcDhSengzaGVSczFkNFUwCkNuMEhQNENTZUJwYUVVN2FYem5F
cEhVTS83eEpoRitPZ29mQlZnT3RJVmsKLS0tIC94ZHBNRmpQdW5EM1NwQ2tMWFp0 OWIrKzRILys5T1FmSDZYRExxT3A1N0EKLS0tIFVsT2NiamY3S0dKL2FwTjZCSFJ6
RWIxTnVEZDY4TnZXeDRFeXBkSWluZGcKtvV5fbMxUKZ2dp/unsZcsv+V51vNZkjm SjJIVElqeGRqem1PQ2g4WWczSExRQ1kKrV69W1SSfKfE+MicJ7bCTsTTy3op+tWG
LanIBZBYfbeki2C8b+VPE0ELwtzRQNnGz7DrEzKIV3J2RIscAwwRGQ== in6fgwL24wmk5WYClVQuXhYzPH/GyRTLptzYad+lvOUj2CorwUJ4UA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1pm3fehmmk0vmnrscz9vm96rakn46aaldr5ydpscmde3v9x0k3faswwdzxs - recipient: age1pm3fehmmk0vmnrscz9vm96rakn46aaldr5ydpscmde3v9x0k3faswwdzxs
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZUXRFS1RuMytoSmhEY1Uy YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiODBnN2lXUEl2RTVqV3lH
b2pzMHhmcmVrcE5QeVd2d3JmN05QQzZ2ZmhzCmcyZDkrSVlpWjRZNTJlek02c29U ZEJMbG5sT0Nnd09rNlZOczhxSnNsZXM2SW1vCkR3Zlkvci9UK1d0R2tqbmZlU21l
b3ZHdWpSNWhjT1BrbGRXVDRUdW9aZTQKLS0tIG53Y0xwZnd5OVJvb0FMNmQzTS9u U2V6S3FhVXVUQXVnc3ZvZW1LYkNOdjgKLS0tIGtoRVRlRVJRTkZCVE9CWTdsb3ZB
aTR0THNqQ3ZGMjMwb2c5aUJVNTRoVGcKusofba5Zk+GQYtvPVFk8kkQjinF4gn3L Umo1RzBLbkk2c2RmdGN5WWpJeEljMFUKeOqAB6CbuDYc8aNlcQQhxIO1Ms2ding4
rzLJuPql7U/nnFx4obRJEQhrJFXlVrwMmvEyi0Tyz55yH1Y6vZ39aA== JeC/SGoHYIpaKR4oIXz0Lr/Xlu5t8IVfZVlc7CPKdu0UO41qAeQiWw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1mn2afyp9my7y7hcyzum0wdwt49zufnkt8swnyy8pj30cwzs4zvgsthj0lt - recipient: age1mn2afyp9my7y7hcyzum0wdwt49zufnkt8swnyy8pj30cwzs4zvgsthj0lt
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGbU1uN2JBU3FzNkFiS1N2 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWMGFLVGJYTlVjNmZQdmtp
cE11L2NZUEwrNGkwbVhvRVUveFF2aXhDdTFJCnRBS3VxZmFWQStJS3pTcGpmelhy TkVjRWx4Z1R2QTAwVmJMTWNCd0pza0NPU2xVCm9UcFQxVzI3RG9WeXJ4VUQraVMv
Tm5xQXdDT2JraTN0M1JKaUVLNjMydmMKLS0tIHJDWXVPT1dwdzA3TkpIT2xTczVL TUQxRWNFbG1ndzNNMjZvVlhXU1ozU0UKLS0tIGdWeExjWGtJdWhGTWxmS2V3Q0ND
N3g2elREZEVCUDc0VFZBaEZWNWVhaDAKrnqiYwyFjQJnzNd1t1iy82JC9CJrBYEG UHl6b2J4U1BNWHZQK0Z1RGtYRVdzNncK+UhQlaLlYcrZl17jBVHLfyEMMG82pn61
34DFwUhiPQcXjYtW8DhTOXyhQbDrjf1r2cD5+JId+dpIAmG8MDWRxA== V2wtdOZPk1NFNVa1/kTntmfxjK/mV883gVHuUru0HpVIRD8x/lw8Rw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1ykkjw57t3z3deup3gtp7dujyaslskn74e0d9hsmqaha2pj3rvazqgndw5a - recipient: age1ykkjw57t3z3deup3gtp7dujyaslskn74e0d9hsmqaha2pj3rvazqgndw5a
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnKzFyMWt1K2t0UXNpeG9G YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxaTVCZmR3QjhBTlhjQkpH
SlAzMVBSZXI5TUxaMzk4OVY2czBwbUFYTWo4CkdGUjdva3B5Z2ZYYXAyVmJYZWdC R0R4ZXNrQms3MmI5cS92Q0l0OG5IS05iQ1JrCitpRnpiY0wxaXc0NzlBZnhqSFpj
Nmt1S2E3RTdNcXMzY3FWMXhIblRZUTgKLS0tIEpCZDBUTXpHL1FrMXVpUzJFMmw1 bWFocnRKRDErUTlqNml5OEFTcDkxK1UKLS0tIHgxakcxdTE5TDBrOEFJNGlTRDNz
U3VqVklnUlFrUndXSEs0QTlYMklTdU0K67wrP/l3xaryQTHZB+UZItMmwt3Pm0nB OHF1ZUFGaFVRNlRPRGhDbTlIeE5PdzQKzhiyVOJ0eB6RcVoiKhRykXrMB0msTO1p
l/aJXyIqEXcAuqP36RvzBXQexhFS0q7PbIwjEtAfqK8DGntEsF3PHw== oErds6/XYa5SDABpc7cCMeFX5QYWA6XCquJ1zQ2yquPiBrj1D80Z8g==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1t2d5scrukk0guva5sr97a8tge5j8kd865adezrcru7p269pzwvpsamkgje - recipient: age1t2d5scrukk0guva5sr97a8tge5j8kd865adezrcru7p269pzwvpsamkgje
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCV3UwSWxveDkyUDNtcGFt YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhZ0xVaHlaSTVnb3hKSkhO
YVFQOS81a3NJUng1NmpjVFNncnhTNU5XZjJJCkpKUzV3Kzc2cVlVUDNNWjJ3a0s0 NThvL3pzd0lCdjUvZGlUcHlWb1JCRWFUVFNRCnVmVzFvY2F6L0VnNStzNVZ6WjNW
dm95R1lyVCszaGovMjliUmplWGIvN28KLS0tIEN5dHA3bE1WUjJjTlFUTUx1Rm9r NnFjblNMakVKUWlqd3JGWENOQmtVOGMKLS0tIGV5R0htSVJTRXNnd1hnZk4yOWIv
SkhOUXVzbVFRQlRzQTFUYUZjUmM2TkkKZ6OSRYcgUiB0gQLs5xGlrLpjgxYuYXKt bk9BSVJxRGZpbldheW5pNzU2UmdsT2cKh18db9FTk5/Ji/SNXGVamyB1yrIo+Xsi
d3MaGf7cL6QeNEADGPeReXgSBJljI5QLTwyrQVCM4WyFL3hGo6v8Ng== kmeROZP87ALKg7oDX0yvemhhLqfyotzkeOilZEf4cuQwsR2Gq4BZ/w==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1c8qw59ffcq9l77gfmtyc3djtvt3md0u6dwhrjcgsm98ntyf72ufqugj7cg - recipient: age1c8qw59ffcq9l77gfmtyc3djtvt3md0u6dwhrjcgsm98ntyf72ufqugj7cg
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBybnVTRjRDS1RNbzU2ZjY4 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAveFRnL3NhMElqY1ArQS9O
RElxMmcyMVJCbU91cnJock93TFlxREJ5YmxJCml2b1FBWFNMc2VQT1gzK0dUQ1Qy Q1o0ME1sQm1vckFOM0ZkcUF1UzQ4VmhKTlV3CnBIOVZ1Qk40VEFPbHJld1JkQVhs
cDBEbyt0WXRyNVFFemNiV2wwQXV3Z2MKLS0tIHBxbEgxQnRKUTFZT2xldnplZXJ5 OEdPVG1TWlZlT1lZMUYvYVFJUTdXK0kKLS0tIFM4N2pONXhWa2hIMGlOcStuZUJ0
dGdJR2JVb1ExWGRQcGR2SWNOVU53eFEKAFyNit0rGZ+z63I/EnJcAmvphZC9aPrJ dmk1cnJvalUzSUdESldjTVdpQVV5ZVEKNr+5rSc1pQbMIQxtNJyNIm4bFvl5b6pa
7+PY7WNLa5VmgxBmwmUW1vvuZq9cFRU7yXPI9LbMtzTy7ByBxztZyw== DvuAmZd3dSTCRPGwv9m2tkbpSlE5Yp3gDe9Ivi82EAIx9ob8aImXBw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1er5qucsc2mugrzrr7n3xhzv7kemkrqrw4m84r544fkk7nkg5g5eswxkqj0 - recipient: age1er5qucsc2mugrzrr7n3xhzv7kemkrqrw4m84r544fkk7nkg5g5eswxkqj0
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1M1dYSTV0UGxtTmh6dHZX YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMcHpLWWx3cm00ZGp0bkhn
bXB1eElEMlNOUmhoRjNnZmw4STh2d0x4UHg4CnlhdFEwMjlPWWNuMndQMEJCQUtJ QjN2dVJyRVl5cElObmtWZVdidWYrTTRnd2lrCmVVdERvR0ZzR3FZb0p0YzNQdFFK
YzN3TG54R2JXNldZQ2loT0xUN3Q5YjAKLS0tIGdSRW1zZmlpOTRFTmFCT0RUMWps NStuMEx6QzIvN2lPSUtseGVtRlU4eHcKLS0tIC9NdzQrVDQzV1JWTXNCbDBRN2Fo
Zk40WkZtZkVVeXcwOXFUVEJINU02RmcK2OK9FnYsvgTG4x0RZFt6NYDlG6fiaVsB V3dKbnBuNUZyalIvWER4dlRZaDdUL3MKONyd0ZgC5L06aJKohJE+zS3Zy+Msr3Uy
eVs34Ll3xuddxeGBVuQdeX5kxxzqxe1fBKE+IEzsRSMLQgN2Mdqi7g== DIM84V0OTuzw05ni61L5pqRL51Kyi61hOjCSNF8+Gm3XalL9NEKrfg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1xg6mvj3x6s3t8058c6rsk3q4kskvm6nsffwckxkkjzhyn7r6tczqgkj23p - recipient: age19daqsncuzeh3j6cwk8uxp6yfj8h0qtz02jxlwwy4v8j0mfgznsvq30440g
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBreTg0SmtBeHB3MHd2M2w3 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoS1dJenhHZG1jK1hndzFw
YmUvRTM5ZWFwSVNvUDVDRGVHQmxDZ1FkeXprCmxrREpML09FTzFGOFkwOUUwbjVz UjErT1VMTTNaMHdad09wM1o0QWl1UHVOdG1BCndiajR4aHp2bCtLb1FpWTZLQU9k
ditIODE1b0o0NG5ZekIrTlFoTFFyL2MKLS0tIGVtSTFmTG04MW9QWGJZM0lOdlhM dVI2NnpXT3Jnc2FTZG5vNW4rbWVFbUkKLS0tIDRrYUxPZTE1WU9LVmNRVExia0xN
djlTNTVralMrM2wrZ0Znb0psSkZrejAKr7ydGdN+LQlIgMjWAfGR3EXADwBbhYrd R0JXd3FCUmJTQXkxMXBmcjFNY09YQW8Khz3AliRA5RzNelz+/f6j62qcS2VNJyRg
WsT4TIMH8pemXPAdthgIBSvbBtmTfM2jKFiDXxTEkSmyp8hLxxKJag== SwYg0l2BBALvnI2VDj4X4YYQM5YxgNCtSdsb//ijVkypkDm/wTKp3w==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1rdn39ywgzmc8wlsl5lrfe77e652wzjmjx58gx4k2ydghd35kdqvqscrf3h - recipient: age19w4zafpwnq9yhzuf8r5te2yhq7xlqj76rcgzcz935hllyrz4yvws4jn6ca
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLZko4M0xZMVNOb2ZTOXJp YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMNlhYS0dFd203UXBLMzJa
THNUMUpobWIxR2VtTGZzS0p2ekd0VmYwaUhvCjh5Q2hIRXA1dDY3UFhiaDEzZzg1 YUVrZnZYMSt2QXRlbXU3UDhobGFVdmhkUVdJCklpUzZUbFFHNGgxaGltalRjZVlQ
bTduMTVwK2pIUFdpTmNZdUhzSjJpNDAKLS0tIDFFc3grWG5yak1jT0Q1aER4UXd3 QjU4QXVHbzUzM1JPREJoZUJJdVpLaFEKLS0tIFVRN09qd0NzVzhuSWN0YmQyZ3l4
SGtpQm9sZkEySWo2TS9UZ0xlTjE1MXcKC2T7uEnnDr06tt4sajTAuwKECQWIxa21 TkdzdlIyYVRpRE9OaG5EeE1KNDMwTDQKfy3OEtd6iT6md5AIkwuy61xXtjy2XrVL
alsXZe2lCxN6b5kA1jkQO1/y+zEzvn+z9ui1ZaRuDavkMEUj+QH2qg== BZOwFI4qtDN8SgpCTpkIE+Iyv5XvWMTw8v8+MNuas2CYX0GsN5ODyg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1luyejgmqjj0esydlr2jxqkg48vexmx57gdz7cy5gq7rz8kf5cups2rnfa9 - recipient: age1luyejgmqjj0esydlr2jxqkg48vexmx57gdz7cy5gq7rz8kf5cups2rnfa9
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvVzFybisva1lJTm9DaU5L YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnaDZaMnBZc0FzcHlQR2t1
Y2JlWERmTEY3ZS84NU80RkwzaGVQc0dlMldjCllXU0dkY2R5dVNpblptTGl6bGtE QVhWTDMyZy9qOXB5RDlMVjh1Ly94RzZPNDJBCjNTbG94bTFJdVpEcXAydVlVbHYy
Q0hwaFdFRHhnMTZwdytjU2xOSWRzeGMKLS0tIHlsSVRnNVlLRzFnbU54L2ppZXFl WWV5YTZqeHhJeDYzQWpUUlhnb1dQb28KLS0tIHNjaWx3eTk0ZDJwdWFsZ0k1U1BR
MFNLVkdtQkxRbDVBV1pGMU5IZzRNdmMKH5o9sT1uerA9ANpJ8nVB15AERNn61s9h NGdjV1dsRjlZbjhUWllsSU9LQmdkMHMK+7GWHmYubQ2m2YjCjo6VibRHCMHE0SUV
l514Enz5GwkKjITsWeb6J+k4VtGd2wfoX2YySDOMNUsXnlH1BvFFBw== mercB7H9IyoHqDyH+VmfMfsKQyhUzNGx21xNwA/AxkrECl781FJMVw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1wurzgc20e6ye79wsg85vvqk4aj3mmc0llxshcy9532ex8f4c6dqql76c78 - recipient: age1wurzgc20e6ye79wsg85vvqk4aj3mmc0llxshcy9532ex8f4c6dqql76c78
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXcDVKdW12dFFEd0tVWVBz YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCaDBrcW9UNitUSTJJSXJD
QmJpblJXMWZhbFprZnNvTGhCQzQ3S3Yva0NRCmUyZzJRSVd1SzFzTGN3a01icVJK Y09ZWmZtTDVaNGphYkV0aWdlRjZVWGQrbEFNClpXaUJpZ2pXeHdKbVZTWjMzdEtW
c3RxRnZ2MU1sRDU2UmUrZ2Znd0pCTVkKLS0tIFI3V3BrclBITFZGSjFmTjBhS2c1 SGlleEY4a2lnbTVrV2FqQnBQeXRnVnMKLS0tIGh2MDdpcVk0SVZ0eU02bWV4TTc0
U1VIMUEyTTdQd25zMVU5OGd1aUhuVkUK8x6JppexvYSvZe1hn7DuO0t/scXlVU07 VE9jKzk2ZkNNMklDTWh3a2NDakxJNFEKITKH9DQKJYN2PCidfZ5A3ypHe0kurphx
an4u0gtwNlmn2hz/QxHa1cIxEc/awQIU3AkotjlzhYug2wbtyZTxjw== 4qtUgLbGMEcuGfHXCMUtzJAzh8k74Ld6AzLGSTHEjjjmIRmw1o6LhQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-05-29T02:27:34Z" lastmodified: "2025-05-29T02:27:34Z"
mac: ENC[AES256_GCM,data:GqBTWeckU/ERKV/5OiPuFOSfUXUTEN7OpKKhGbWCl5oTUE1/CkmMheWJy5WiTlk89KAd66+gBK5kG29PQhEOkUcjoLZTdwghOiJVi90+zPdfz1fGkjs570GtNRulEBL13Ld9KRMHbRSOijM33jmgSpHY8Tcb7RzFTVQs6ZpJ6Fk=,iv:groe/8rbUW9PJTyI457u+LQsiBEzc0YKvKpNToTBrdQ=,tag:OvUbSTSR664p+hBa+BQ++A==,type:str] mac: ENC[AES256_GCM,data:GqBTWeckU/ERKV/5OiPuFOSfUXUTEN7OpKKhGbWCl5oTUE1/CkmMheWJy5WiTlk89KAd66+gBK5kG29PQhEOkUcjoLZTdwghOiJVi90+zPdfz1fGkjs570GtNRulEBL13Ld9KRMHbRSOijM33jmgSpHY8Tcb7RzFTVQs6ZpJ6Fk=,iv:groe/8rbUW9PJTyI457u+LQsiBEzc0YKvKpNToTBrdQ=,tag:OvUbSTSR664p+hBa+BQ++A==,type:str]

View File

@@ -4,7 +4,6 @@
}: }:
{ {
imports = [ imports = [
./nix.nix
./homebrew.nix ./homebrew.nix
./programs.nix ./programs.nix
./system.nix ./system.nix

View File

@@ -1,17 +0,0 @@
{ ... }:
{
# Auto upgrade nix package and the daemon service.
# services.nix-daemon.enable = true;
# nix.package = pkgs.nix;
# Necessary for using flakes on this system.
nix = {
settings.experimental-features = "nix-command flakes";
};
# The platform the configuration will be used on.
nixpkgs = {
config.allowUnfree = true;
hostPlatform = "aarch64-darwin";
};
}

View File

@@ -15,18 +15,36 @@ in
imports = [ imports = [
./boot.nix ./boot.nix
./hardware-configuration.nix ./hardware-configuration.nix
./networking.nix # ./networking.nix - moved to modules/nixos/network
./services.nix ./services.nix
]; ];
hardware.asahi = { hardware.asahi = {
enable = true; enable = true;
useExperimentalGPUDriver = true;
peripheralFirmwareDirectory = ./firmware; peripheralFirmwareDirectory = ./firmware;
setupAsahiSound = true; setupAsahiSound = true;
}; };
${namespace} = { ${namespace} = {
user = {
name = "matt";
extraGroups = [
"ratbagd"
"input"
"scanner"
"lp"
"video"
"i2c"
];
packages = with pkgs; [
firefox
tree
git
box64
prismlauncher
distrobox
];
};
desktop = { desktop = {
hyprland = { hyprland = {
enable = true; enable = true;
@@ -59,36 +77,33 @@ in
}; };
network = { network = {
hostName = "macbook-pro-nixos"; hostName = "macbook-pro-nixos";
wifi.enable = false;
iwd = {
enable = true;
settings = {
General = {
EnableNetworkConfiguration = true;
};
Rank = {
BandModifier2_4GHz = 1.0;
BandModifier5GHz = 5.0;
BandModifier6GHz = 10.0;
};
Network = {
AutoConnect = true;
};
};
};
extraFirewallCommands = ''
iptables -I INPUT -m pkttype --pkt-type multicast -j ACCEPT
iptables -A INPUT -m pkttype --pkt-type multicast -j ACCEPT
iptables -I INPUT -p udp -m udp --match multiport --dports 1990,2021 -j ACCEPT
'';
}; };
}; };
nixpkgs.config.allowUnsupportedSystem = true; nixpkgs.config.allowUnsupportedSystem = true;
# Define a user account. Don't forget to set a password with passwd.
users.users.matt = {
isNormalUser = true;
extraGroups = [
"wheel"
"keys"
"networkmanager"
"ratbagd"
"input"
"scanner"
"lp"
"video"
"i2c"
]; # Enable sudo for the user.
shell = pkgs.zsh;
packages = with pkgs; [
firefox
tree
git
box64
prismlauncher
distrobox
];
};
virtualisation = { virtualisation = {
containers.enable = true; containers.enable = true;
podman.enable = true; podman.enable = true;

View File

@@ -1,7 +1,11 @@
# Do not modify this file! It was generated by nixos-generate-config # Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes # and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead. # to /etc/nixos/configuration.nix instead.
{ lib, modulesPath, ... }: {
lib,
modulesPath,
...
}:
{ {
imports = [ imports = [
@@ -9,7 +13,7 @@
]; ];
boot.initrd.availableKernelModules = [ boot.initrd.availableKernelModules = [
"uas" "usb_storage"
"sdhci_pci" "sdhci_pci"
]; ];
boot.initrd.kernelModules = [ ]; boot.initrd.kernelModules = [ ];
@@ -19,69 +23,11 @@
fileSystems."/" = { fileSystems."/" = {
device = "none"; device = "none";
fsType = "tmpfs"; fsType = "tmpfs";
}; options = [ "mode=755" ];
fileSystems."/root" = {
device = "/dev/disk/by-uuid/adcc14fa-8bf7-4b4b-a9e4-b038993b96cc";
fsType = "btrfs";
options = [
"compress=zstd"
"noatime"
"subvol=root"
];
};
fileSystems."/etc" = {
device = "/dev/disk/by-uuid/adcc14fa-8bf7-4b4b-a9e4-b038993b96cc";
fsType = "btrfs";
options = [
"compress=zstd"
"noatime"
"subvol=etc"
];
};
fileSystems."/tmp" = {
device = "/dev/disk/by-uuid/adcc14fa-8bf7-4b4b-a9e4-b038993b96cc";
fsType = "btrfs";
options = [
"compress=zstd"
"noatime"
"subvol=tmp"
];
};
fileSystems."/nix" = {
device = "/dev/disk/by-uuid/adcc14fa-8bf7-4b4b-a9e4-b038993b96cc";
fsType = "btrfs";
options = [
"compress=zstd"
"noatime"
"subvol=nix"
];
};
fileSystems."/var/log" = {
device = "/dev/disk/by-uuid/adcc14fa-8bf7-4b4b-a9e4-b038993b96cc";
fsType = "btrfs";
options = [
"compress=zstd"
"noatime"
"subvol=log"
];
};
fileSystems."/home" = {
device = "/dev/disk/by-uuid/adcc14fa-8bf7-4b4b-a9e4-b038993b96cc";
fsType = "btrfs";
options = [
"compress=zstd"
"subvol=home"
];
}; };
fileSystems."/boot" = { fileSystems."/boot" = {
device = "/dev/disk/by-uuid/23FA-AD3E"; device = "/dev/disk/by-uuid/E66E-1A03";
fsType = "vfat"; fsType = "vfat";
options = [ options = [
"fmask=0022" "fmask=0022"
@@ -89,12 +35,37 @@
]; ];
}; };
# swapDevices = [ fileSystems."/root" = {
# { device = "/dev/disk/by-uuid/335f1bb3-6fdb-474e-972c-77b64e930d03";
# device = "/tmp/swapfile"; fsType = "btrfs";
# randomEncryption.enable = true; options = [ "subvol=root" ];
# } };
# ];
fileSystems."/etc" = {
device = "/dev/disk/by-uuid/335f1bb3-6fdb-474e-972c-77b64e930d03";
fsType = "btrfs";
options = [ "subvol=etc" ];
};
fileSystems."/nix" = {
device = "/dev/disk/by-uuid/335f1bb3-6fdb-474e-972c-77b64e930d03";
fsType = "btrfs";
options = [ "subvol=nix" ];
};
fileSystems."/var/log" = {
device = "/dev/disk/by-uuid/335f1bb3-6fdb-474e-972c-77b64e930d03";
fsType = "btrfs";
options = [ "subvol=log" ];
};
fileSystems."/home" = {
device = "/dev/disk/by-uuid/335f1bb3-6fdb-474e-972c-77b64e930d03";
fsType = "btrfs";
options = [ "subvol=home" ];
};
swapDevices = [ ];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking # 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 # (the default) this is the recommended approach. When using systemd-networkd it's

View File

@@ -3,118 +3,58 @@
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`). # https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
{ {
config,
lib,
pkgs,
namespace, namespace,
... ...
}: }:
let
user = "matt";
# password = config.sops.secrets."pi4/matt-password".path;
kernelBundle = pkgs.linuxAndFirmware.latest;
in
{ {
imports = [ imports = [
./adguard.nix ./adguard.nix
./boot.nix ./boot.nix
./networking.nix # ./networking.nix - moved to modules/nixos/network
./sops.nix ./sops.nix
]; ];
${namespace} = { ${namespace} = {
hardware.disko.enable = true; hardware = {
}; disko.enable = true;
raspberry-pi = {
nix = { enable = true;
settings = { variant = "4";
substituters = [
"https://nixos-raspberrypi.cachix.org"
"https://cache.mjallen.dev"
];
trusted-public-keys = [
"nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI="
"cache.mjallen.dev-1:IzFmKCd8/gggI6lcCXsW65qQwiCLGFFN9t9s2iw7Lvc="
];
};
};
# Configure nixpkgs
nixpkgs = {
overlays = lib.mkAfter [
(_self: _super: {
# This is used in (modulesPath + "/hardware/all-firmware.nix") when at least
# enableRedistributableFirmware is enabled
# I know no easier way to override this package
inherit (kernelBundle) raspberrypiWirelessFirmware;
# Some derivations want to use it as an input,
# e.g. raspberrypi-dtbs, omxplayer, sd-image-* modules
inherit (kernelBundle) raspberrypifw;
})
];
};
programs.zsh.enable = true;
hardware.i2c.enable = true;
services = {
openssh = {
enable = true;
authorizedKeysFiles = [
config.sops.secrets."ssh-keys-public/pi5".path
];
hostKeys = [ ];
};
};
systemd.services.btattach = {
before = [ "bluetooth.service" ];
after = [ "dev-ttyAMA0.device" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.bluez}/bin/btattach -B /dev/ttyAMA0 -P bcm -S 3000000";
};
};
environment = {
systemPackages = with pkgs; [
i2c-tools
libraspberrypi
raspberrypi-eeprom
raspberrypifw
raspberrypiWirelessFirmware
raspberrypi-armstubs
];
};
users = {
mutableUsers = false;
users = {
"${user}" = {
isNormalUser = true;
# hashedPasswordFile = password;
password = lib.mkForce "BogieDudie1";
extraGroups = [
"wheel"
"docker"
"video"
];
shell = pkgs.zsh;
openssh.authorizedKeys.keys = [
# macBook
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCw9zq8DLGByI5v2gAn95hKNyOsm3g61a2buxu2BBMFysQJgmZPCCLUqRJKhSM5Vm/JOgsAmdpRBRZQoHD+6S844CJHb4v4VIbjkyQgYCuM7Rst2IOZ5QybvsA2/D0nwytZ+HXQqDj2AagUYDbz0gyyIHkDQ5YGBMkvkWz/h1Vci6aoBM7VihEDM4KlWoTVuPeASGM8r5IZ2FS83Djbqo4ov6AYvLMrKB9Z7hmFgH6R3LE0gxOkzbGVXtSuvJyrjvgytoT22UhATjjxSQ9D+YJXXkQoB3lUdg8OoIquUPjMZpl4mR8ffvseWPfcvD1XlD5t+TOHFqKpESO547tlOBYhdpew+NSgAXpamCU6oyV8tDCywLQu2ucxHRn78u6WXzWHkDtffdhzmk6TZaPhWqVHuTGjR4higBgGqUfSaKOMszt+FDRZAr3HtuQ2+zJ8bowK9fW5OqilTtK2HtQqroD9ApegDNbqOz6kGy5IycSXvqPURy/M4lxZxbtBPuemcJs= mattjallen@MacBook-Pro.local"
# desktop windows
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDZ2PYPjZddOzR8OJj16G88KcUhCDLkvrEmpUQP0wKHDUuA27HQQ2ORo66asadwGHY3k1VDZ1ei9l9H++SIIeKOaaUr5yZdktvj4POUNtbd9ZhcS7sZU7BSF+NMDM+h3tImh6z0S7mWvRQOUv3ZM+ZER+5xTWJVG1OOJEpb1drxJk6Qz0wbZKSR7TPNFBLLXlVy7hkNYf07RtDyhCCxNB3hJfa8c+oztnWumwDhDQWLqiUXWIU2QH6iRLGl/WYnujtNvVVaV/Hn3JJkS6MM9dnV3cpoIO0+J7+WfsN9rZ0wXt5yY3GhiGXwmcO5eYVli8lHlLWtK7aYSETyry6CBsLbojzOQO5rSqhpwfF2njAAFAQU0UjLc8PahisIuFKCwHH4iyXXOagiv5K1Mc/0Ak+WhhMPee6vV2p7NTyNpXRvouDbWy5cSRH31WgQ9fK5mIGe5v8nGGqtEhUubUkiOgP+H3UbT2V/nTv/TFKdJcKw+WmizvTrxBmaMjWALlkYl+s= mattl@Jallen-PC"
# desktop nixos
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTBMydhOc6SnOdB5WrEd7X07DrboAtagCUgXiOJjLov matt@matt-nixos"
];
};
root = {
isSystemUser = true;
isNormalUser = false;
shell = pkgs.zsh;
}; };
}; };
user = {
name = "matt";
password = "BogieDudie1";
mutableUsers = false;
extraGroups = [
"docker"
"video"
];
};
network = {
hostName = "pi4";
ipv4 = {
method = "manual";
address = "10.0.1.2/24";
gateway = "10.0.1.1";
dns = "1.1.1.1";
};
firewall = {
enable = true;
allowPing = true;
allowedTCPPorts = [ 53 ];
allowedUDPPorts = [ 53 ];
};
wifi = {
enable = true;
powersave = false;
};
};
};
# Root user configuration - explicit to avoid conflicts with home-manager
users.users.root = {
isSystemUser = true;
isNormalUser = false;
}; };
} }

View File

@@ -1,11 +1,8 @@
{ lib, config, ... }: { lib, config, ... }:
let
hostname = "pi4";
in
{ {
# Networking configs # Networking configs
networking = { networking = {
hostName = hostname; # hostName = lib.mkForce hostname;
defaultGateway.address = "10.0.1.1"; defaultGateway.address = "10.0.1.1";
nameservers = [ "10.0.1.1" ]; nameservers = [ "10.0.1.1" ];

View File

@@ -3,121 +3,47 @@
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`). # https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
{ {
config,
lib,
pkgs,
namespace, namespace,
... ...
}: }:
let
user = "matt";
password = config.sops.secrets."pi5/matt-password".path;
kernelBundle = pkgs.linuxAndFirmware.latest;
in
{ {
imports = [ imports = [
./boot.nix ./boot.nix
./networking.nix # ./networking.nix - moved to modules/nixos/network
./services.nix ./services.nix
./sops.nix ./sops.nix
]; ];
${namespace} = { ${namespace} = {
hardware.disko.enable = true; hardware = {
disko.enable = true;
raspberry-pi = {
enable = true;
variant = "5";
};
};
desktop.hyprland.enable = false; desktop.hyprland.enable = false;
network = { user = {
hostName = "pi5"; name = "matt";
}; password = "BogieDudie1";
}; mutableUsers = false;
extraGroups = [ "docker" ];
# Enable nix flakes and nix-command tools sshKeys = [
nix = {
settings = {
substituters = [
"https://nixos-raspberrypi.cachix.org"
# "https://cache.mjallen.dev"
];
trusted-public-keys = [
"nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI="
# "cache.mjallen.dev-1:IzFmKCd8/gggI6lcCXsW65qQwiCLGFFN9t9s2iw7Lvc="
];
};
};
# Configure nixpkgs
nixpkgs = {
overlays = lib.mkAfter [
(_self: _super: {
# This is used in (modulesPath + "/hardware/all-firmware.nix") when at least
# enableRedistributableFirmware is enabled
# I know no easier way to override this package
inherit (kernelBundle) raspberrypiWirelessFirmware;
# Some derivations want to use it as an input,
# e.g. raspberrypi-dtbs, omxplayer, sd-image-* modules
inherit (kernelBundle) raspberrypifw;
})
];
};
system.nixos.tags =
let
cfg = config.boot.loader.raspberry-pi;
in
[
"raspberry-pi-${cfg.variant}"
cfg.bootloader
config.boot.kernelPackages.kernel.version
];
systemd.services.btattach = {
before = [ "bluetooth.service" ];
after = [ "dev-ttyAMA0.device" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.bluez}/bin/btattach -B /dev/ttyAMA0 -P bcm -S 3000000";
};
};
environment = {
systemPackages = with pkgs; [
erofs-utils
fex
libraspberrypi
raspberrypi-eeprom
raspberrypifw
raspberrypiWirelessFirmware
raspberrypi-armstubs
squashfuse
squashfsTools
];
};
hardware.graphics.enable32Bit = lib.mkForce false;
users = {
mutableUsers = false;
users."${user}" = {
isNormalUser = true;
# hashedPasswordFile = password;
password = lib.mkForce "BogieDudie1";
extraGroups = [
"wheel"
"docker"
];
openssh.authorizedKeys.keys = [
# macBook
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCw9zq8DLGByI5v2gAn95hKNyOsm3g61a2buxu2BBMFysQJgmZPCCLUqRJKhSM5Vm/JOgsAmdpRBRZQoHD+6S844CJHb4v4VIbjkyQgYCuM7Rst2IOZ5QybvsA2/D0nwytZ+HXQqDj2AagUYDbz0gyyIHkDQ5YGBMkvkWz/h1Vci6aoBM7VihEDM4KlWoTVuPeASGM8r5IZ2FS83Djbqo4ov6AYvLMrKB9Z7hmFgH6R3LE0gxOkzbGVXtSuvJyrjvgytoT22UhATjjxSQ9D+YJXXkQoB3lUdg8OoIquUPjMZpl4mR8ffvseWPfcvD1XlD5t+TOHFqKpESO547tlOBYhdpew+NSgAXpamCU6oyV8tDCywLQu2ucxHRn78u6WXzWHkDtffdhzmk6TZaPhWqVHuTGjR4higBgGqUfSaKOMszt+FDRZAr3HtuQ2+zJ8bowK9fW5OqilTtK2HtQqroD9ApegDNbqOz6kGy5IycSXvqPURy/M4lxZxbtBPuemcJs= mattjallen@MacBook-Pro.local"
# desktop windows
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDZ2PYPjZddOzR8OJj16G88KcUhCDLkvrEmpUQP0wKHDUuA27HQQ2ORo66asadwGHY3k1VDZ1ei9l9H++SIIeKOaaUr5yZdktvj4POUNtbd9ZhcS7sZU7BSF+NMDM+h3tImh6z0S7mWvRQOUv3ZM+ZER+5xTWJVG1OOJEpb1drxJk6Qz0wbZKSR7TPNFBLLXlVy7hkNYf07RtDyhCCxNB3hJfa8c+oztnWumwDhDQWLqiUXWIU2QH6iRLGl/WYnujtNvVVaV/Hn3JJkS6MM9dnV3cpoIO0+J7+WfsN9rZ0wXt5yY3GhiGXwmcO5eYVli8lHlLWtK7aYSETyry6CBsLbojzOQO5rSqhpwfF2njAAFAQU0UjLc8PahisIuFKCwHH4iyXXOagiv5K1Mc/0Ak+WhhMPee6vV2p7NTyNpXRvouDbWy5cSRH31WgQ9fK5mIGe5v8nGGqtEhUubUkiOgP+H3UbT2V/nTv/TFKdJcKw+WmizvTrxBmaMjWALlkYl+s= mattl@Jallen-PC"
# desktop nixos
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTBMydhOc6SnOdB5WrEd7X07DrboAtagCUgXiOJjLov matt@matt-nixos"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOTha0FbV1tkpnJr7xVH78S5MetJH+0o2YrEcuvhL692 root@jallen-nas" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOTha0FbV1tkpnJr7xVH78S5MetJH+0o2YrEcuvhL692 root@jallen-nas"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIwoHWOLSTGVif9hAhaMLl0qDA4roIzCNuyR6kyIXDOj admin@jallen-nas" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIwoHWOLSTGVif9hAhaMLl0qDA4roIzCNuyR6kyIXDOj admin@jallen-nas"
]; ];
shell = pkgs.zsh;
}; };
users.root.shell = pkgs.zsh; network = {
hostName = "pi5";
ipv4 = {
method = "manual";
gateway = "10.0.1.1";
dns = "10.0.1.1";
};
firewall = {
enable = true;
allowPing = true;
};
};
}; };
zramSwap.enable = true;
} }

View File

@@ -1,8 +1,6 @@
{ ... }: { ... }:
{ {
services = { services = {
xserver.desktopManager.gnome.enable = true;
shairport-sync = { shairport-sync = {
enable = false; enable = false;
openFirewall = true; openFirewall = true;

View File

@@ -51,17 +51,6 @@ let
]; ];
in in
{ {
# nix = {
# settings = {
# substituters = [
# "https://cache.mjallen.dev"
# ];
# trusted-public-keys = [
# "cache.mjallen.dev-1:IzFmKCd8/gggI6lcCXsW65qQwiCLGFFN9t9s2iw7Lvc="
# ];
# };
# };
chaotic.mesa-git.enable = false; chaotic.mesa-git.enable = false;
# Environment configuration # Environment configuration
@@ -84,6 +73,7 @@ in
brscan5.enable = false; brscan5.enable = false;
# extraBackends = [ pkgsVersion.brscan5 ]; # extraBackends = [ pkgsVersion.brscan5 ];
}; };
flipperzero.enable = true;
}; };
# Common Configuration # Common Configuration

View File

@@ -28,7 +28,7 @@ in
./configuration.nix ./configuration.nix
./filesystems.nix ./filesystems.nix
./hardware-configuration.nix ./hardware-configuration.nix
# ./networking.nix # ./networking.nix - moved to modules/nixos/network
./nix.nix ./nix.nix
./sops.nix ./sops.nix
@@ -38,10 +38,22 @@ in
]; ];
${namespace} = { ${namespace} = {
hardware.disko.enable = false;
bootloader.lanzaboote.enable = true; bootloader.lanzaboote.enable = true;
impermanence.enable = true;
desktop.gnome.enable = true; desktop.gnome.enable = true;
network = { network = {
hostName = "matt-nixos"; hostName = "matt-nixos";
wifi = {
enable = true;
powersave = false;
profiles = {
"Joey's Jungle 6G" = {
ssid = "Joey's Jungle 6G";
keyMgmt = "sae";
};
};
};
}; };
user = { user = {
passwordFile = passwordFile; passwordFile = passwordFile;

View File

@@ -27,7 +27,8 @@ in
fsType = "bcachefs"; fsType = "bcachefs";
options = [ options = [
"noatime" "noatime"
] ++ defaultLocalOptions; ]
++ defaultLocalOptions;
}; };
# Network shares # Network shares

View File

@@ -32,7 +32,8 @@ in
options = [ options = [
"subvol=nix" "subvol=nix"
"noatime" "noatime"
] ++ defeaultBtrfsOptions; ]
++ defeaultBtrfsOptions;
}; };
fileSystems."/etc" = { fileSystems."/etc" = {
@@ -41,7 +42,8 @@ in
options = [ options = [
"subvol=etc" "subvol=etc"
"noatime" "noatime"
] ++ defeaultBtrfsOptions; ]
++ defeaultBtrfsOptions;
}; };
fileSystems."/root" = { fileSystems."/root" = {
@@ -50,7 +52,8 @@ in
options = [ options = [
"subvol=root" "subvol=root"
"noatime" "noatime"
] ++ defeaultBtrfsOptions; ]
++ defeaultBtrfsOptions;
}; };
fileSystems."/var/log" = { fileSystems."/var/log" = {
@@ -59,7 +62,8 @@ in
options = [ options = [
"subvol=log" "subvol=log"
"noatime" "noatime"
] ++ defeaultBtrfsOptions; ]
++ defeaultBtrfsOptions;
}; };
fileSystems."/home" = { fileSystems."/home" = {
@@ -67,7 +71,8 @@ in
fsType = "btrfs"; fsType = "btrfs";
options = [ options = [
"subvol=home" "subvol=home"
] ++ defeaultBtrfsOptions; ]
++ defeaultBtrfsOptions;
}; };
fileSystems."/boot" = { fileSystems."/boot" = {

View File

@@ -8,4 +8,4 @@
environment.variables = { environment.variables = {
LSFG_DLL_PATH = "/media/matt/data/steam/steamapps/common/Lossless Scaling/Lossless.dll"; LSFG_DLL_PATH = "/media/matt/data/steam/steamapps/common/Lossless Scaling/Lossless.dll";
}; };
} }

View File

@@ -2,6 +2,7 @@
{ {
${namespace} = { ${namespace} = {
services = { services = {
# Existing properly namespaced services
immich.enable = true; immich.enable = true;
jellyfin.enable = true; jellyfin.enable = true;
jellyseerr.enable = true; jellyseerr.enable = true;
@@ -11,93 +12,92 @@
paperless.enable = true; paperless.enable = true;
traefik.enable = true; traefik.enable = true;
wyoming.enable = true; wyoming.enable = true;
# Newly migrated services
actual = {
enable = true;
port = 3333;
localAddress = "10.0.3.18";
dataDir = "/media/nas/main/nix-app-data/actual";
reverseProxy = {
enable = true;
host = "actual.mjallen.dev";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
};
};
arrs = {
enable = true;
localAddress = "10.0.1.51";
downloadsDir = "/media/nas/main/ssd_app_data/downloads";
incompleteDownloadsDir = "/media/nas/main/ssd_app_data/downloads-incomplete";
moviesDir = "/media/nas/main/movies";
tvDir = "/media/nas/main/tv";
isosDir = "/media/nas/main/isos";
radarr = {
enable = true;
port = 7878;
dataDir = "/media/nas/main/nix-app-data/radarr";
};
sonarr = {
enable = true;
port = 8989;
dataDir = "/media/nas/main/nix-app-data/sonarr";
};
sabnzbd = {
enable = true;
port = 8280;
dataDir = "/media/nas/main/nix-app-data/sabnzbd";
};
deluge = {
enable = true;
port = 8112;
};
jackett = {
enable = true;
port = 9117;
dataDir = "/media/nas/main/nix-app-data/jackett";
};
};
crowdsec = {
enable = true;
port = 9898;
apiAddress = "10.0.1.3";
apiKey = "1daH89qmJ41r2Lpd9hvDw4sxtOAtBzaj3aKFOFqE";
dataDir = "/media/nas/main/nix-app-data/crowdsec";
};
gitea = {
enable = true;
httpPort = 3000;
sshPort = 2222;
localAddress = "10.0.4.18";
dataDir = "/media/nas/main/nix-app-data/gitea";
reverseProxy = {
enable = true;
host = "gitea.mjallen.dev";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
};
};
free-games-claimer.enable = true;
manyfold.enable = true;
orca-slicer = {
enable = true;
httpPort = "3100";
httpsPort = "3101";
};
tdarr.enable = true;
}; };
}; };
nas-apps = {
actual = {
enable = true;
port = 3333;
localAddress = "10.0.3.18";
dataDir = "/media/nas/main/nix-app-data/actual";
reverseProxy = {
enable = true;
host = "actual.mjallen.dev";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
};
};
arrs = {
enable = true;
localAddress = "10.0.1.51";
downloadsDir = "/media/nas/main/ssd_app_data/downloads";
incompleteDownloadsDir = "/media/nas/main/ssd_app_data/downloads-incomplete";
moviesDir = "/media/nas/main/movies";
tvDir = "/media/nas/main/tv";
isosDir = "/media/nas/main/isos";
radarr = {
enable = true;
port = 7878;
dataDir = "/media/nas/main/nix-app-data/radarr";
};
sonarr = {
enable = true;
port = 8989;
dataDir = "/media/nas/main/nix-app-data/sonarr";
};
sabnzbd = {
enable = true;
port = 8280;
dataDir = "/media/nas/main/nix-app-data/sabnzbd";
};
deluge = {
enable = true;
port = 8112;
};
jackett = {
enable = true;
port = 9117;
dataDir = "/media/nas/main/nix-app-data/jackett";
};
};
crowdsec = {
enable = true;
port = 9898;
apiAddress = "10.0.1.3";
apiKey = "1daH89qmJ41r2Lpd9hvDw4sxtOAtBzaj3aKFOFqE";
dataDir = "/media/nas/main/nix-app-data/crowdsec";
};
gitea = {
enable = true;
httpPort = 3000;
sshPort = 2222;
localAddress = "10.0.4.18";
dataDir = "/media/nas/main/nix-app-data/gitea";
reverseProxy = {
enable = true;
host = "gitea.mjallen.dev";
middlewares = [
"crowdsec"
"whitelist-geoblock"
];
};
};
free-games-claimer.enable = true;
manyfold.enable = true;
orca-slicer = {
enable = true;
httpPort = "3100";
httpsPort = "3101";
};
tdarr.enable = true;
};
} }

View File

@@ -1,51 +0,0 @@
{ lib, ... }:
let
inherit (lib) types mkOption;
in
{
options.nas-apps = mkOption {
type = types.attrsOf (
types.submodule (
{ ... }:
{
options = {
enable = mkOption {
type = types.bool;
default = false;
};
port = mkOption {
type = types.int;
default = 80;
};
localAddress = mkOption {
type = types.str;
default = "127.0.0.1";
};
dataDir = mkOption {
type = types.str;
default = "";
};
reverseProxy = {
enable = mkOption {
type = types.bool;
default = false;
};
host = mkOption {
type = types.str;
default = "";
};
middlewares = mkOption {
type = with types; listOf str;
default = [ ];
};
};
};
}
)
);
};
}

View File

@@ -1,4 +1,9 @@
{ pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
configLimit = 50; configLimit = 50;
kernel = pkgs.linuxPackages; # linuxPackages_latest; kernel = pkgs.linuxPackages; # linuxPackages_latest;
@@ -35,6 +40,8 @@ in
consoleLogLevel = 3; consoleLogLevel = 3;
bootspec.enable = true; bootspec.enable = true;
plymouth.enable = lib.mkForce false;
initrd = { initrd = {
kernelModules = [ kernelModules = [
"tpm" "tpm"
@@ -50,7 +57,7 @@ in
clevis = { clevis = {
enable = true; enable = true;
devices = { devices = {
# "/dev/sde:/dev/sdf:/dev/sdh:/dev/sdi:/dev/sdj".secretFile = "../../../pool.jwe"; "/dev/disk/by-label/nas_pool".secretFile = config.sops.secrets."jallen-nas/nas_pool".path;
}; };
}; };
}; };
@@ -59,6 +66,8 @@ in
binfmt.emulatedSystems = [ "aarch64-linux" ]; # --argstr system aarch64-linux binfmt.emulatedSystems = [ "aarch64-linux" ]; # --argstr system aarch64-linux
}; };
environment.etc."clevis/nas_pool.jwe".source = config.sops.secrets."jallen-nas/nas_pool".path;
zramSwap = { zramSwap = {
enable = true; enable = true;
}; };

View File

@@ -1,4 +1,3 @@
# Edit this configuration file to define what should be installed on # Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page, on # your system. Help is available in the configuration.nix(5) man page, on
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`). # https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
@@ -6,7 +5,6 @@
{ {
config, config,
pkgs, pkgs,
lib,
namespace, namespace,
... ...
}: }:
@@ -18,8 +16,7 @@
./boot.nix ./boot.nix
./apps.nix ./apps.nix
./grafana.nix ./grafana.nix
./networking.nix # ./networking.nix - moved to modules/nixos/network
./nixpkgs.nix
./ups.nix ./ups.nix
./users.nix ./users.nix
./samba.nix ./samba.nix
@@ -34,6 +31,15 @@
${namespace} = { ${namespace} = {
bootloader.lanzaboote.enable = true; bootloader.lanzaboote.enable = true;
desktop.cosmic.enable = false; desktop.cosmic.enable = false;
development = {
enable = true;
includeLanguages = [
"python"
"c"
];
includeContainers = true;
};
monitoring.enable = true;
hardware.nvidia = { hardware.nvidia = {
enable = true; enable = true;
enableBeta = true; enableBeta = true;
@@ -46,10 +52,72 @@
ipv4 = { ipv4 = {
address = "10.0.1.3/24"; address = "10.0.1.3/24";
method = "manual"; method = "manual";
gateway = "10.0.1.1";
interface = "wlp6s0";
};
useNetworkd = true;
hostId = "4b501480";
nat = {
enable = true;
internalInterfaces = [ "ve-+" ];
externalInterface = "wlp6s0";
enableIPv6 = true;
};
firewall = {
enable = true;
allowPing = true;
allowedTCPPorts = [
8008 # restic
9000 # authentik
2342 # grafana
51820 # wireguard
1025
1143
10200
10300
8127
9980 # onlyoffice
4000 # netbootxyz
4080 # netbootxyz
3000 # gitea
2222 # gitea ssh
3300
9898
6754 # lubelogger
2283 # immich
4444 # code-server
9012
8192
];
allowedUDPPorts = [
8008 # restic
9000 # authentik
2342 # grafana
51820 # wireguard
1025
1143
10200
10300
8127
9980 # onlyoffice
4000 # netbootxyz
4080 # netbootxyz
3000 # gitea
2222 # gitea ssh
3300
9898
6754 # lubelogger
2283 # immich
4444 # code-server
9012
8192
];
trustedInterfaces = [ "tailscale0" ];
}; };
}; };
user = { user = {
name = "admin"; name = "admin";
linger = true;
}; };
}; };
@@ -65,34 +133,26 @@
systemPackages = with pkgs; [ systemPackages = with pkgs; [
attic-client attic-client
binutils bcachefs-tools
cryptsetup cryptsetup
clevis clevis
cmake
deconz deconz
duperemove duperemove
efibootmgr efibootmgr
ffmpeg ffmpeg
gcc
glances
ipset ipset
jq
llama-cpp llama-cpp
ninja
# inputs.nas-nixai.packages.x86_64-linux.nixai # inputs.nas-nixai.packages.x86_64-linux.nixai
networkmanagerapplet networkmanagerapplet
nmon
nut nut
packagekit packagekit
pass pass
protonmail-bridge protonmail-bridge
protonvpn-cli protonvpn-cli
python3
python3Packages.llama-cpp-python python3Packages.llama-cpp-python
qrencode qrencode
rcon rcon
sbctl sbctl
speedtest-cli
tigervnc tigervnc
tpm2-tools tpm2-tools
tpm2-tss tpm2-tss
@@ -102,8 +162,6 @@
# Configure programs # Configure programs
programs = { programs = {
virt-manager.enable = true; virt-manager.enable = true;
nix-ld.enable = true;
screen.enable = true;
coolercontrol = { coolercontrol = {
enable = true; enable = true;
nvidiaSupport = true; nvidiaSupport = true;
@@ -152,58 +210,6 @@
''; '';
}; };
# Virtualisation # Additional virtualization beyond what's in development module
virtualisation = { virtualisation.libvirtd.enable = true;
podman = {
enable = true;
dockerCompat = true;
autoPrune.enable = true;
defaultNetwork.settings = {
dns_enabled = true;
};
};
libvirtd.enable = true;
};
# Enable nix flakes and nix-command tools
nix = {
settings = {
substituters = [
"https://nix-community.cachix.org"
"https://cache.nixos.org/"
];
trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
warn-dirty = lib.mkForce false;
experimental-features = lib.mkForce [
"nix-command"
"flakes"
];
trusted-users = [ "@wheel" ];
};
# Garbage collect automatically every week
gc.automatic = lib.mkDefault true;
gc.options = lib.mkDefault "--delete-older-than 30d";
optimise.automatic = lib.mkDefault true;
};
# Nixpkgs configuration
nixpkgs = {
config = {
allowUnfree = lib.mkForce true;
allowUnsupportedSystem = true;
permittedInsecurePackages = [
# ...
];
};
};
nixpkgs.config.allowUnfreePredicate =
pkg:
builtins.elem (lib.getName pkg) [
"vscode-extension-github-copilot"
];
} }

View File

@@ -1,11 +1,8 @@
{ ... }: { ... }:
let
defaultOptions = [ "compress=zstd" ];
in
{ {
fileSystems."/mnt" = { fileSystems."/media/nas/main" = {
label = "nas_pool"; label = "nas_pool";
# device = "/dev/sde:/dev/sdf:/dev/sdh:/dev/sdi:/dev/:sdj"; # device = "/dev/sde:/dev/sdf:/dev/sdh:/dev/sdi:/dev/sdj:/dev/nmve0n1:/dev/nvme1n1";
fsType = "bcachefs"; fsType = "bcachefs";
mountPoint = "/media/nas/main"; mountPoint = "/media/nas/main";
}; };

View File

@@ -1,25 +0,0 @@
{ ... }:
{
# Configure nixpkgs
nixpkgs = {
config = {
# Enable non free
allowUnfree = true;
# enable cuda support
cudaSupport = true;
allowUnfreePredicate =
p:
builtins.all (
license:
license.free
|| builtins.elem license.shortName [
"CUDA EULA"
"cuDNN EULA"
"cuTENSOR EULA"
"NVidia OptiX EULA"
]
) (if builtins.isList p.meta.license then p.meta.license else [ p.meta.license ]);
};
};
}

View File

@@ -1,36 +0,0 @@
Common Settings:
INTERVAL=10
Settings of hwmon6/pwm5: -- chipset?
Depends on hwmon6/temp9_input
Controls hwmon6/fan5_input
MINTEMP=20
MAXTEMP=60
MINSTART=16
MINSTOP=14
MINPWM=14
Settings of hwmon6/pwm4: -- case?
Depends on hwmon2/temp1_input
Controls hwmon6/fan4_input
MINTEMP=20
MAXTEMP=90
MINSTART=60
MINSTOP=45
Settings of hwmon6/pwm3: -- cpu?
Depends on hwmon2/temp1_input
Controls hwmon6/fan3_input
MINTEMP=20
MAXTEMP=90
MINSTART=150
MINSTOP=0
MAXPWM=30
Settings of hwmon6/pwm2: -- cpu?
Depends on hwmon2/temp1_input
Controls hwmon6/fan2_input
MINTEMP=20
MAXTEMP=90
MINSTART=105
MINSTOP=0

View File

@@ -34,6 +34,12 @@ in
group = config.users.users."${user}".group; group = config.users.users."${user}".group;
}; };
"jallen-nas/nas_pool" = {
mode = "0600";
owner = config.users.users."${user}".name;
group = config.users.users."${user}".group;
};
"wifi" = { "wifi" = {
sopsFile = sharedSops; sopsFile = sharedSops;
}; };

View File

@@ -1,12 +1,11 @@
{ {
lib,
namespace, namespace,
... ...
}: }:
{ {
imports = [ imports = [
./boot.nix ./boot.nix
./networking.nix # ./networking.nix - moved to modules/nixos/network
./users.nix ./users.nix
./sops.nix ./sops.nix
]; ];
@@ -17,43 +16,36 @@
${namespace} = { ${namespace} = {
services.home-assistant.enable = true; services.home-assistant.enable = true;
hardware.disko.enable = true; hardware.disko = {
network.hostName = "nuc-nixos"; enable = true;
}; # filesystem = "bcachefs";
# Enable nix flakes and nix-command tools
nix = {
settings = {
substituters = [
"https://nix-community.cachix.org"
"https://cache.nixos.org/"
];
trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
warn-dirty = lib.mkForce false;
experimental-features = lib.mkForce [
"nix-command"
"flakes"
];
trusted-users = [ "@wheel" ];
}; };
impermanence.enable = true;
# Garbage collect automatically every week network = {
gc.automatic = lib.mkDefault true; hostName = "nuc-nixos";
gc.options = lib.mkDefault "--delete-older-than 30d"; useNetworkd = false;
ipv4 = {
optimise.automatic = lib.mkDefault true; method = "manual";
}; address = "10.0.1.4/24";
gateway = "10.0.1.1";
# Nixpkgs configuration dns = "10.0.1.1";
nixpkgs = { };
config = { wifi = {
allowUnfree = lib.mkForce true; enable = true;
allowUnsupportedSystem = true; profiles = {
permittedInsecurePackages = [ "Joey's Jungle 6G" = {
# ... ssid = "Joey's Jungle 6G";
]; keyMgmt = "sae";
};
};
};
firewall = {
enable = true;
allowPing = true;
allowedTCPPorts = [ 8192 ];
allowedUDPPorts = [ 8192 ];
};
}; };
}; };
} }

View File

@@ -60,9 +60,5 @@ in
# Further reduce systemd output # Further reduce systemd output
systemd = { systemd = {
services.systemd-udev-settle.enable = false; services.systemd-udev-settle.enable = false;
extraConfig = ''
ShowStatus=no
DefaultTimeoutStartSec=15s
'';
}; };
} }

View File

@@ -9,8 +9,6 @@
... ...
}: }:
{ {
nixpkgs.config.allowUnfree = lib.mkForce true;
# Define a user account. Don't forget to set a password with passwd. # Define a user account. Don't forget to set a password with passwd.
users.users = { users.users = {
deck = { deck = {

View File

@@ -15,19 +15,30 @@
./boot.nix ./boot.nix
./configuration.nix ./configuration.nix
./jovian.nix ./jovian.nix
./networking.nix # ./networking.nix - moved to modules/nixos/network
./sops.nix ./sops.nix
]; ];
nixpkgs.config.allowUnfree = true;
${namespace} = { ${namespace} = {
hardware.disko.enable = true; hardware.disko.enable = true;
impermanence.enable = true;
bootloader.lanzaboote.enable = true; bootloader.lanzaboote.enable = true;
desktop.gnome.enable = true; desktop.gnome.enable = true;
user = { user = {
name = "deck"; name = "deck";
}; };
network.hostName = "steamdeck"; network = {
hostName = "steamdeck";
wifi = {
enable = true;
powersave = false;
profiles = {
"Joey's Jungle 5G" = {
ssid = "Joey's Jungle 5G";
keyMgmt = "sae";
};
};
};
};
}; };
} }

View File

@@ -1,551 +0,0 @@
#!/usr/bin/env python3
"""
Automatic Nix package update checker
Auto-discovers and checks GitHub-based Nix packages for updates
"""
import re
import json
import argparse
import requests
import subprocess
import shutil
from pathlib import Path
from typing import Dict, List, Optional, Tuple, NamedTuple
from dataclasses import dataclass
import sys
class PackageInfo(NamedTuple):
owner: str
repo: str
version: str
rev: str
current_hash: str
package_name: str
file_path: Path
@dataclass
class UpdateResult:
name: str
current_version: str
latest_version: str
has_update: bool
file_path: Path
repo_url: str
current_hash: Optional[str] = None
new_hash: Optional[str] = None
error: Optional[str] = None
class NixPackageChecker:
def __init__(self, search_paths: List[str] = None, max_depth: int = 3):
self.search_paths = search_paths or ["."]
self.max_depth = max_depth
self.session = requests.Session()
self.session.headers.update({'User-Agent': 'nix-package-checker'})
def find_nix_packages(self) -> List[Path]:
"""Auto-discover Nix package files with fetchFromGitHub"""
packages = []
for search_path in self.search_paths:
base_path = Path(search_path)
if not base_path.exists():
continue
# Find .nix files up to max_depth
for depth in range(self.max_depth + 1):
pattern = "**/" * depth + "*.nix"
for nix_file in base_path.glob(pattern):
if self._is_github_package(nix_file):
packages.append(nix_file)
return sorted(set(packages))
def _is_github_package(self, nix_file: Path) -> bool:
"""Check if a .nix file contains fetchFromGitHub"""
try:
content = nix_file.read_text(encoding='utf-8')
return 'fetchFromGitHub' in content and any(
pattern in content for pattern in ['owner =', 'repo =', 'version =']
)
except (UnicodeDecodeError, PermissionError):
return False
def compare_versions(self, current: str, latest: str) -> bool:
"""Compare versions, return True if latest is newer"""
if current == latest:
return False
# Handle HACS-X format
hacs_current = re.match(r'HACS-(\d+)', current)
hacs_latest = re.match(r'HACS-(\d+)', latest)
if hacs_current and hacs_latest:
return int(hacs_latest.group(1)) > int(hacs_current.group(1))
# Handle semantic versioning vX.Y.Z
sem_current = re.match(r'v?(\d+)\.(\d+)\.(\d+)', current)
sem_latest = re.match(r'v?(\d+)\.(\d+)\.(\d+)', latest)
if sem_current and sem_latest:
curr_parts = tuple(map(int, sem_current.groups()))
lat_parts = tuple(map(int, sem_latest.groups()))
return lat_parts > curr_parts
# Fallback to string comparison
return latest > current
def parse_nix_file(self, nix_file: Path) -> Optional[PackageInfo]:
"""Extract package information from a .nix file"""
try:
content = nix_file.read_text(encoding='utf-8')
except (UnicodeDecodeError, PermissionError) as e:
print(f"❌ Error reading {nix_file}: {e}")
return None
# Patterns to extract fields
patterns = {
'owner': r'owner\s*=\s*"([^"]+)"',
'repo': r'repo\s*=\s*"([^"]+)"',
'version': r'version\s*=\s*"([^"]+)"',
'rev': r'rev\s*=\s*(?:"([^"]+)"|([^;"\s]+))',
'hash': r'hash\s*=\s*"([^"]+)"',
# Package name patterns (in order of preference)
'domain': r'domain\s*=\s*"([^"]+)"', # Home Assistant components
'pname': r'pname\s*=\s*"([^"]+)"', # Standard Nix convention
'name': r'name\s*=\s*"([^"]+)"' # Older convention
}
extracted = {}
for field, pattern in patterns.items():
match = re.search(pattern, content)
if match:
if field == 'rev':
# Handle both quoted and unquoted rev values
extracted[field] = match.group(1) or match.group(2)
else:
extracted[field] = match.group(1)
# Validate required fields
required = ['owner', 'repo', 'version']
if not all(field in extracted for field in required):
missing = [f for f in required if f not in extracted]
print(f"⚠️ {nix_file.name}: Missing fields: {missing}")
return None
# Handle rev = version case
rev = extracted.get('rev', extracted['version'])
if rev == 'version':
rev = extracted['version']
# Extract current hash (may not exist for all packages)
current_hash = extracted.get('hash', '')
# Determine package name (priority: domain > pname > name > repo > directory)
package_name = None
for name_field in ['domain', 'pname', 'name']:
if name_field in extracted:
package_name = extracted[name_field]
break
if not package_name:
# Fall back to repo name
package_name = extracted['repo']
# If still no name and it's in a subdirectory, use directory name
if not package_name or package_name == extracted['repo']:
parent_dir = nix_file.parent.name
if parent_dir != '.' and parent_dir != nix_file.parent.parent.name:
package_name = f"{parent_dir}-{extracted['repo']}" if package_name == extracted['repo'] else parent_dir
return PackageInfo(
owner=extracted['owner'],
repo=extracted['repo'],
version=extracted['version'],
rev=rev,
current_hash=current_hash,
package_name=package_name,
file_path=nix_file
)
def get_latest_release(self, owner: str, repo: str) -> Optional[str]:
"""Get latest GitHub release tag"""
url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
try:
response = self.session.get(url, timeout=10)
if response.status_code == 200:
return response.json().get('tag_name')
elif response.status_code == 404:
# Try getting tags if no releases
return self._get_latest_tag(owner, repo)
else:
print(f"⚠️ API error for {owner}/{repo}: {response.status_code}")
return None
except requests.RequestException as e:
print(f"⚠️ Network error for {owner}/{repo}: {e}")
return None
def _get_latest_tag(self, owner: str, repo: str) -> Optional[str]:
"""Fallback to get latest tag if no releases"""
url = f"https://api.github.com/repos/{owner}/{repo}/tags"
try:
response = self.session.get(url, timeout=10)
if response.status_code == 200:
tags = response.json()
return tags[0]['name'] if tags else None
except requests.RequestException:
pass
return None
def get_github_hash(self, owner: str, repo: str, rev: str) -> Optional[str]:
"""Get hash for GitHub source using nix-prefetch-url or nix-prefetch-github"""
# Try nix-prefetch-url first (more commonly available)
if shutil.which('nix-prefetch-url'):
return self._get_hash_with_prefetch_url(owner, repo, rev)
# Fall back to nix-prefetch-github
elif shutil.which('nix-prefetch-github'):
return self._get_hash_with_prefetch_github(owner, repo, rev)
else:
print("⚠️ Neither nix-prefetch-url nor nix-prefetch-github found.")
print(" nix-prefetch-url is included in nix by default")
print(" nix-prefetch-github: nix-env -iA nixpkgs.nix-prefetch-github")
return None
def _get_hash_with_prefetch_url(self, owner: str, repo: str, rev: str) -> Optional[str]:
"""Get hash using nix-prefetch-url with GitHub archive URL"""
# GitHub archive URL format
url = f"https://github.com/{owner}/{repo}/archive/{rev}.tar.gz"
try:
# Use --unpack to match fetchFromGitHub behavior
cmd = ['nix-prefetch-url', '--unpack', url]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
if result.returncode == 0:
# nix-prefetch-url outputs the hash directly (sha256)
hash_value = result.stdout.strip()
return hash_value
else:
print(f"⚠️ nix-prefetch-url failed for {owner}/{repo}@{rev}:")
print(f" URL: {url}")
print(f" Error: {result.stderr.strip()}")
return None
except subprocess.TimeoutExpired:
print(f"⚠️ Timeout fetching hash for {owner}/{repo}@{rev} (60s limit)")
return None
except subprocess.SubprocessError as e:
print(f"⚠️ Error with nix-prefetch-url for {owner}/{repo}@{rev}: {e}")
return None
def _get_hash_with_prefetch_github(self, owner: str, repo: str, rev: str) -> Optional[str]:
"""Get hash using nix-prefetch-github (fallback method)"""
try:
cmd = ['nix-prefetch-github', owner, repo, '--rev', rev]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
if result.returncode == 0:
# Parse JSON output to get sha256
data = json.loads(result.stdout)
return data.get('sha256')
else:
print(f"⚠️ nix-prefetch-github failed for {owner}/{repo}@{rev}:")
print(f" {result.stderr.strip()}")
return None
except subprocess.TimeoutExpired:
print(f"⚠️ Timeout fetching hash for {owner}/{repo}@{rev}")
return None
except (subprocess.SubprocessError, json.JSONDecodeError) as e:
print(f"⚠️ Error with nix-prefetch-github for {owner}/{repo}@{rev}: {e}")
return None
def update_nix_file(self, pkg_info: PackageInfo, new_version: str, new_hash: str) -> bool:
"""Update a .nix file with new version and hash"""
try:
content = pkg_info.file_path.read_text(encoding='utf-8')
# Create backup
backup_path = pkg_info.file_path.with_suffix('.nix.backup')
backup_path.write_text(content, encoding='utf-8')
# Update version
content = re.sub(
r'(version\s*=\s*)"[^"]+";',
f'\\1"{new_version}";',
content
)
# Update hash
if pkg_info.current_hash:
content = re.sub(
r'(hash\s*=\s*)"[^"]+";',
f'\\1"sha256-{new_hash}";',
content
)
# Write updated content
pkg_info.file_path.write_text(content, encoding='utf-8')
return True
except Exception as e:
print(f"❌ Error updating {pkg_info.file_path}: {e}")
return False
"""Compare versions, return True if latest is newer"""
if current == latest:
return False
# Handle HACS-X format
hacs_current = re.match(r'HACS-(\d+)', current)
hacs_latest = re.match(r'HACS-(\d+)', latest)
if hacs_current and hacs_latest:
return int(hacs_latest.group(1)) > int(hacs_current.group(1))
# Handle semantic versioning vX.Y.Z
sem_current = re.match(r'v?(\d+)\.(\d+)\.(\d+)', current)
sem_latest = re.match(r'v?(\d+)\.(\d+)\.(\d+)', latest)
if sem_current and sem_latest:
curr_parts = tuple(map(int, sem_current.groups()))
lat_parts = tuple(map(int, sem_latest.groups()))
return lat_parts > curr_parts
# Fallback to string comparison
return latest > current
def check_package(self, pkg_info: PackageInfo, fetch_hash: bool = False) -> UpdateResult:
"""Check a single package for updates"""
latest_version = self.get_latest_release(pkg_info.owner, pkg_info.repo)
if latest_version is None:
return UpdateResult(
name=pkg_info.package_name,
current_version=pkg_info.version,
latest_version="unknown",
has_update=False,
file_path=pkg_info.file_path,
repo_url=f"https://github.com/{pkg_info.owner}/{pkg_info.repo}",
current_hash=pkg_info.current_hash,
error="Could not fetch latest release"
)
has_update = self.compare_versions(pkg_info.version, latest_version)
new_hash = None
# Fetch new hash if update available and requested
if has_update and fetch_hash:
print(f" 🔄 Fetching hash for {pkg_info.package_name} ({pkg_info.owner}/{pkg_info.repo}@{latest_version})...")
new_hash = self.get_github_hash(pkg_info.owner, pkg_info.repo, latest_version)
return UpdateResult(
name=pkg_info.package_name,
current_version=pkg_info.version,
latest_version=latest_version,
has_update=has_update,
file_path=pkg_info.file_path,
repo_url=f"https://github.com/{pkg_info.owner}/{pkg_info.repo}",
current_hash=pkg_info.current_hash,
new_hash=new_hash
)
def check_all_packages(self, fetch_hash: bool = False, auto_update: bool = False) -> List[UpdateResult]:
"""Check all discovered packages"""
nix_files = self.find_nix_packages()
if not nix_files:
print("No Nix package files found")
return []
print(f"Found {len(nix_files)} package files")
# Show which hash fetching tool is available
if fetch_hash or auto_update:
if shutil.which('nix-prefetch-url'):
print("Hash fetching: using nix-prefetch-url")
elif shutil.which('nix-prefetch-github'):
print("Hash fetching: using nix-prefetch-github")
else:
print("⚠️ No hash fetching tool available")
results = []
for nix_file in nix_files:
pkg_info = self.parse_nix_file(nix_file)
if pkg_info:
result = self.check_package(pkg_info, fetch_hash=fetch_hash)
results.append(result)
# Auto-update if requested and update available
if auto_update and result.has_update and result.new_hash:
print(f" 🔄 Auto-updating {result.name}...")
if self.update_nix_file(pkg_info, result.latest_version, result.new_hash):
print(f" ✅ Updated {result.file_path}")
else:
print(f" ❌ Failed to update {result.file_path}")
return results
def print_results(results: List[UpdateResult], show_all: bool = True, show_hashes: bool = False):
"""Print results in a nice format"""
if not results:
return
updates_available = [r for r in results if r.has_update]
if show_all:
print(f"\n{'Package':<25} {'Current':<15} {'Latest':<15} {'Status'}")
print("-" * 70)
for result in results:
if result.error:
status = f"{result.error}"
elif result.has_update:
status = "🔄 Update available"
else:
status = "✅ Up to date"
print(f"{result.name:<25} {result.current_version:<15} {result.latest_version:<15} {status}")
# Show file path for default.nix files or when there might be confusion
if result.file_path.name == 'default.nix' or len([r for r in results if r.name == result.name]) > 1:
rel_path = result.file_path.relative_to(Path.cwd()) if result.file_path.is_absolute() else result.file_path
print(f"{'':>25} File: {rel_path}")
# Show hash information if available and requested
if show_hashes and result.has_update and result.new_hash:
print(f"{'':>25} Current hash: {result.current_hash[:16]}..." if result.current_hash else "")
print(f"{'':>25} New hash: sha256-{result.new_hash[:16]}...")
# Summary
print(f"\nSummary:")
print(f" Total packages: {len(results)}")
print(f" Updates available: {len(updates_available)}")
if updates_available:
print(f"\nPackages with updates:")
for result in updates_available:
rel_path = result.file_path.relative_to(Path.cwd()) if result.file_path.is_absolute() else result.file_path
print(f"{result.name}: {result.current_version}{result.latest_version}")
print(f" File: {rel_path}")
print(f" Repo: {result.repo_url}")
if show_hashes and result.new_hash:
print(f" New hash: sha256-{result.new_hash}")
def print_updates_only(results: List[UpdateResult], show_hashes: bool = False):
"""Print only packages with updates"""
updates = [r for r in results if r.has_update]
if not updates:
print("No updates available")
return
print("Updates available:")
for result in updates:
rel_path = result.file_path.relative_to(Path.cwd()) if result.file_path.is_absolute() else result.file_path
print(f" {result.name}: {result.current_version}{result.latest_version}")
print(f" File: {rel_path}")
if show_hashes and result.new_hash:
print(f" New hash: sha256-{result.new_hash}")
elif show_hashes:
print(f" Hash: (not fetched)")
def output_json(results: List[UpdateResult]):
"""Output results as JSON"""
data = {}
for result in results:
data[result.name] = {
"current_version": result.current_version,
"latest_version": result.latest_version,
"has_update": result.has_update,
"file_path": str(result.file_path),
"repo_url": result.repo_url,
"current_hash": result.current_hash,
"new_hash": result.new_hash,
"error": result.error
}
print(json.dumps(data, indent=2))
def main():
parser = argparse.ArgumentParser(
description="Automatically check Nix packages for GitHub updates",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s # Check all packages in current directory
%(prog)s --updates # Show only packages with updates
%(prog)s --fetch-hash # Also fetch new hashes for updates
%(prog)s --auto-update # Automatically update .nix files
%(prog)s --json # Output as JSON
%(prog)s --path ./packages # Check specific directory
%(prog)s --depth 5 # Search deeper directory levels
Requirements:
For hash fetching: nix-prefetch-url (part of nix) or nix-prefetch-github
nix-prefetch-url is preferred and usually already available
"""
)
parser.add_argument('--updates', action='store_true',
help='Show only packages with updates available')
parser.add_argument('--fetch-hash', action='store_true',
help='Fetch new hashes for packages with updates (requires nix-prefetch-url or nix-prefetch-github)')
parser.add_argument('--auto-update', action='store_true',
help='Automatically update .nix files with new versions and hashes')
parser.add_argument('--json', action='store_true',
help='Output results as JSON')
parser.add_argument('--path', action='append', default=[],
help='Search path for .nix files (can be used multiple times)')
parser.add_argument('--depth', type=int, default=3,
help='Maximum directory depth to search (default: 3)')
parser.add_argument('--list', action='store_true',
help='List discovered package files without checking updates')
args = parser.parse_args()
# Auto-update implies fetch-hash
if args.auto_update:
args.fetch_hash = True
# Use provided paths or default to current directory
search_paths = args.path if args.path else ["."]
checker = NixPackageChecker(search_paths=search_paths, max_depth=args.depth)
if args.list:
# Just list discovered files
nix_files = checker.find_nix_packages()
print(f"Discovered {len(nix_files)} package files:")
for nix_file in nix_files:
pkg_info = checker.parse_nix_file(nix_file)
if pkg_info:
rel_path = nix_file.relative_to(Path.cwd()) if nix_file.is_absolute() else nix_file
print(f" {pkg_info.package_name:<25} {pkg_info.owner}/{pkg_info.repo} ({pkg_info.version}) - {rel_path}")
else:
rel_path = nix_file.relative_to(Path.cwd()) if nix_file.is_absolute() else nix_file
print(f" {'(parse failed)':<25} - {rel_path}")
return
# Check for updates
results = checker.check_all_packages(
fetch_hash=args.fetch_hash,
auto_update=args.auto_update
)
if not results:
print("No packages found to check")
return
# Output results
if args.json:
output_json(results)
elif args.updates:
print_updates_only(results, show_hashes=args.fetch_hash)
else:
print_results(results, show_all=True, show_hashes=args.fetch_hash)
# Set exit code based on updates available
updates_available = any(r.has_update for r in results)
sys.exit(1 if updates_available else 0)
if __name__ == "__main__":
main()