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
574 changed files with 13713 additions and 40491 deletions

10
.gitignore vendored
View File

@@ -1,14 +1,10 @@
hosts/nas/*.conf
hosts/nas/*.users
result*
result
*.raw
.codegpt
.direnv
shell.nix
.vscode
.envrc
.DS_Store
*.qcow2
keys
iso-*
**/*/__pycache__
**/*/*.py
.envrc

View File

@@ -1,31 +1,30 @@
# See https://github.com/Mic92/dotfiles/blob/d6114726d859df36ccaa32891c4963ae5717ef7f/nixos/.sops.yaml
keys:
- &matt-pgp CBCB9B18A6B8930B0B6ABFD1CCB8CBEB30633684
- &matt age157jemphjzg6zmk373vpccuguyw6e75qnkqmz8pcnn2yue85p939swqqhy0
- &matt_pi4 age13g9a4d4jrvckfddpgn8sm4kjtzajr67le56pfdg78ktr5pd09phq32j89u
- &matt_pi5 age1wpvfpv5n32lruk7c0da4uaeapsmhjxdvg8z4ljehn06l6g2y0e0sum404l
- &desktop age1jv8ap5zwa49ftv0gg7wqf5ps0e68uuwxe2fekjsn0zkyql964unqyc58rf
- &admin age1pm3fehmmk0vmnrscz9vm96rakn46aaldr5ydpscmde3v9x0k3faswwdzxs
- &jallen-nas age1mn2afyp9my7y7hcyzum0wdwt49zufnkt8swnyy8pj30cwzs4zvgsthj0lt
- &pi4 age1ykkjw57t3z3deup3gtp7dujyaslskn74e0d9hsmqaha2pj3rvazqgndw5a
- &pi5 age1t2d5scrukk0guva5sr97a8tge5j8kd865adezrcru7p269pzwvpsamkgje
- &deck age1c8qw59ffcq9l77gfmtyc3djtvt3md0u6dwhrjcgsm98ntyf72ufqugj7cg
- &steamdeck age1er5qucsc2mugrzrr7n3xhzv7kemkrqrw4m84r544fkk7nkg5g5eswxkqj0
- &matt_macbook-pro age12gu9hqhd56yl5x3t5yenkn9yg57du08h77vzjqsmnu5hdppne38qcur5a0
- &macbook-pro age1t7378n8kmd3f32fkye2gw3jj6qswv3exjdx0dq8kl0xra3tmcdnsvddq3u
- &nuc age102el4snus37dj807rwvsmlvwu2sg2d8rw3vfmtntgczfkz04l9nshetcq0
- &admin_nuc age1yn82e39pxt0d0pgny34ux4lkge4ff7wxvsye8ragvwngehemt4ps27phyw
- &matt_allyx age18z4ctyyj7eq0cmt23eelfzjuacq4fa6hsplyg779d3rdg7ac2q5q2njxqh
- &allyx age1er5qucsc2mugrzrr7n3xhzv7kemkrqrw4m84r544fkk7nkg5g5eswxkqj0
- &matt_macbook-pro age19daqsncuzeh3j6cwk8uxp6yfj8h0qtz02jxlwwy4v8j0mfgznsvq30440g
- &macbook-pro age19w4zafpwnq9yhzuf8r5te2yhq7xlqj76rcgzcz935hllyrz4yvws4jn6ca
- &nuc age1wurzgc20e6ye79wsg85vvqk4aj3mmc0llxshcy9532ex8f4c6dqql76c78
- &admin_nuc age1luyejgmqjj0esydlr2jxqkg48vexmx57gdz7cy5gq7rz8kf5cups2rnfa9
creation_rules:
- path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
key_groups:
- pgp:
- *matt-pgp
age:
- age:
- *matt
- *matt_pi4
- *matt_pi5
- *desktop
- *admin
- *jallen-nas
- *pi4
- *pi5
- *deck
- *steamdeck
@@ -33,55 +32,54 @@ creation_rules:
- *macbook-pro
- *admin_nuc
- *nuc
- *matt_allyx
- *allyx
- path_regex: nas-secrets/[^/]+\.(yaml|json|env|ini)$
key_groups:
- pgp:
- *matt-pgp
age:
- age:
- *matt
- *desktop
- *admin
- *jallen-nas
- path_regex: desktop-secrets/[^/]+\.(yaml|json|env|ini)$
key_groups:
- pgp:
- *matt-pgp
age:
- age:
- *matt
- *desktop
- *admin
- *jallen-nas
- path_regex: allyx-secrets/[^/]+\.(yaml|json|env|ini)$
- path_regex: steamdeck-secrets/[^/]+\.(yaml|json|env|ini)$
key_groups:
- pgp:
- *matt-pgp
age:
- age:
- *matt
- *desktop
- *deck
- *steamdeck
- *admin
- *jallen-nas
- *matt_allyx
- *allyx
- path_regex: pi5-secrets/[^/]+\.(yaml|json|env|ini)$
- path_regex: pi4-secrets/[^/]+\.(yaml|json|env|ini)$
key_groups:
- pgp:
- *matt-pgp
age:
- age:
- *matt
- *matt_pi4
- *matt_pi5
- *desktop
- *pi4
- *pi5
- *admin
- *jallen-nas
- path_regex: pi5-secrets/[^/]+\.(yaml|json|env|ini)$
key_groups:
- age:
- *matt
- *matt_pi4
- *matt_pi5
- *desktop
- *pi4
- *pi5
- *admin
- *jallen-nas
- path_regex: mac-secrets/[^/]+\.(yaml|json|env|ini)$
key_groups:
- pgp:
- *matt-pgp
age:
- age:
- *matt
- *matt_pi5
- *desktop

303
AGENTS.md
View File

@@ -1,303 +0,0 @@
# Agent Guide
## Directory Structure
```
/etc/nixos/
├── flake.nix # Main flake configuration
├── flake.lock # Locked versions
├── AGENTS.md # This file
├── treefmt.nix # Code formatting config
├── qemu.nix # QEMU testing config
├── systems/ # System configurations by architecture
│ ├── aarch64-linux/
│ │ ├── macbook-pro-nixos/ # Apple Silicon MacBook
│ │ │ ├── default.nix
│ │ │ ├── boot.nix
│ │ │ ├── services.nix # logind, gdm, gnome, flatpak, etc.
│ │ │ ├── filesystems.nix
│ │ │ ├── hardware-configuration.nix
│ │ │ └── firmware/ # Asahi firmware
│ │ └── pi5/ # Raspberry Pi 5
│ │ ├── default.nix
│ │ ├── boot.nix
│ │ ├── adguard.nix
│ │ └── sops.nix
│ ├── x86_64-linux/
│ │ ├── matt-nixos/ # Desktop AMD system
│ │ │ ├── default.nix
│ │ │ ├── boot.nix
│ │ │ ├── filesystems.nix
│ │ │ ├── sops.nix
│ │ │ └── services/
│ │ │ ├── lsfg-vk/
│ │ │ ├── ratbagd/
│ │ │ └── restic/
│ │ ├── allyx/ # ASUS ROG Ally X
│ │ │ ├── default.nix
│ │ │ └── boot.nix
│ │ ├── nuc-nixos/ # Intel NUC
│ │ ├── jallen-nas/ # NAS server
│ │ └── iso-minimal/
│ └── aarch64-darwin/
│ └── macbook-pro/ # macOS (nix-darwin)
├── homes/ # Home-manager configurations
│ ├── aarch64-linux/
│ │ └── matt@macbook-pro-nixos/
│ │ └── default.nix
│ ├── x86_64-linux/
│ └── aarch64-darwin/
├── modules/ # Shared modules
│ ├── nixos/ # NixOS system modules
│ ├── home/ # Home-manager modules
│ └── darwin/ # nix-darwin modules
├── packages/ # Custom package overlays
│ ├── omnissa/
│ ├── bcachefs/
│ ├── raspberrypi/
│ ├── comfyui/
│ ├── homeassistant/
│ ├── librepods-beta/
│ └── ...
└── secrets/ # SOPS secrets
├── secrets.yaml # Master key config
└── *-secrets.yaml # Per-host secrets
```
## System Configurations
### macbook-pro-nixos (Apple Silicon MacBook)
- **Path**: `systems/aarch64-linux/macbook-pro-nixos/`
- **Key files**:
- `services.nix:72-81` - logind/sleep settings
- `default.nix` - main config, imports all parts
- `boot.nix` - systemd-boot, kernel params
- **Features**: Asahi Linux, GNOME, Hyprland option, battery management
### matt-nixos (AMD Desktop)
- **Path**: `systems/x86_64-linux/matt-nixos/`
- **Features**: AMD GPU (LACT), GNOME, gaming, Lanzaboote
### allyx (ASUS ROG Ally X)
- **Path**: `systems/x86_64-linux/allyx/`
- **Features**: Jovian NixOS, Steam, handheld-daemon, AMD GPU
### pi5 (Raspberry Pi 5)
- **Path**: `systems/aarch64-linux/pi5/`
- **Features**: Headless, AdGuard, Docker, static IP, UEFI boot
### jallen-nas (NAS Server)
- **Path**: `systems/x86_64-linux/jallen-nas/`
- **Features**: Headless, VPN, bcachefs, restic backups
## NixOS Modules (`modules/nixos/`)
### Desktop Environments
- `desktop/gnome/default.nix` - GNOME configuration
- `desktop/hyprland/default.nix` - Hyprland configuration
- `desktop/cosmic/default.nix` - Cosmic DE configuration
### Hardware
- `hardware/amd/default.nix` - AMD GPU (LACT)
- `hardware/nvidia/default.nix` - NVIDIA GPU
- `hardware/battery/default.nix` - Battery management
- `hardware/raspberry-pi/` - Raspberry Pi support
### Boot & System
- `boot/common/` - Common boot settings
- `boot/lanzaboote/` - Lanzaboote (secure boot)
- `boot/systemd-boot/` - Systemd-boot config
- `boot/plymouth/` - Plymouth splash screen
### Networking
- `network/default.nix` - Network configuration (hostname, firewall, NM)
- `network/options.nix` - Network module options
### Other Services
- `headless/default.nix` - Headless server config (watchdog, no suspend)
- `gaming/default.nix` - Steam, Gamescope, Gamemode
- `programs/default.nix` - System programs (nix-index, gnupg, etc.)
## Home-Manager Modules (`modules/home/`)
### Programs
- `programs/waybar/` - Wayland bar
- `programs/hyprland/` - Hyprland config
- `programs/kitty/` - Kitty terminal
- `programs/wofi/` - Wofi launcher
- `programs/wlogout/` - Logout menu
- `programs/btop/` - System monitor
- `programs/git/` - Git configuration
- `programs/zsh/` - Zsh configuration
- `programs/mako/` - Notification daemon
### Desktop
- `desktop/gnome/` - GNOME settings
- `desktop/stylix/` - Stylix theming
### Services
- `services/sops/` - SOPS integration
## Custom Packages (`packages/`)
- `omnissa/` - Omnissa Horizon client
- `bcachefs/` - Bcachefs tools
- `raspberrypi/` - Raspberry Pi firmware/tools
- `comfyui/` - ComfyUI packages
- `homeassistant/` - Home Assistant components
- `librepods-beta/` - LibrePODS beta (AirPods support)
## Common Patterns
### Enable a desktop environment
```nix
${namespace}.desktop.gnome.enable = true;
${namespace}.desktop.hyprland.enable = true;
```
### Enable SOPS
```nix
${namespace}.sops.enable = true;
```
### Enable headless mode
```nix
${namespace}.headless.enable = true;
```
### System imports
```nix
imports = [
./boot.nix
./filesystems.nix
./hardware-configuration.nix
./services.nix
];
```
### Namespace options (flake.nix:253)
```nix
namespace = "mjallen";
```
## SOPS Secrets
Secrets are encrypted with SOPS. Each system has its own secrets file:
- `secrets/mac-secrets.yaml` - macbook-pro-nixos
- `secrets/pi5-secrets.yaml` - pi5
- `secrets/allyx-secrets.yaml` - allyx
- `secrets/nuc-secrets.yaml` - nuc-nixos
- `secrets/nas-secrets.yaml` - jallen-nas
## Flake Inputs (flake.nix)
Key inputs:
- `nixpkgs-unstable` - Unstable channel
- `nixpkgs-stable` - Stable channel (25.11)
- `home-manager-unstable` - Home-manager
- `nixos-apple-silicon` - Apple Silicon support
- `nixos-hardware` - Common hardware configs
- `disko` - Disk partitioning
- `sops-nix` - Secrets management
- `lanzaboote` - Secure boot
- `jovian` - Steam Deck support (allyx)
## Lib Module (`lib/`)
Custom utility library exposed via `lib.mjallen.*` through Snowfall Lib. Used for creating modules and managing versions.
### Directory Structure
```
lib/
├── default.nix # Entry point: exports module, file, versioning
├── README.md # Detailed documentation
├── module/ # Module creation helpers
│ └── default.nix
├── file/ # File/path utilities
│ └── default.nix
└── versioning/ # Multi-source version pinning
└── default.nix
```
### Module Utilities (`lib.mjallen.module`)
**`mkModule`** - Create NixOS service modules with standardized options:
```nix
lib.mjallen.module.mkModule {
config, name, description, options, moduleConfig, domain ? "services"
}
```
Standard options: `enable`, `port`, `reverseProxy`, `firewall`, `createUser`, `configureDb`, `redis`, `puid`, `pgid`, `timeZone`, etc.
**`mkContainerService`** - For Podman/OCI containers (auto-generates container definition):
```nix
lib.mjallen.module.mkContainerService {
config, name, image, internalPort, description, options, volumes, environment
}
```
**`mkSopsEnvFile`** - Generate SOPS secrets + template env-file:
```nix
lib.mjallen.module.mkSopsEnvFile {
secrets, name, content, restartUnit, owner, group, mode, sopsFile
}
```
**Option Helpers:**
- `mkOpt type default description` - Standard option
- `mkBoolOpt default description` - Boolean option
- `mkReverseProxyOpt name` - Caddy reverse proxy sub-options
**Convenience Shorthands:**
- `enabled` = `{ enable = true; }`
- `disabled` = `{ enable = false; }`
### Home Manager Utilities
**`mkHomeModule`** - Create Home Manager modules:
```nix
lib.mjallen.module.mkHomeModule {
config, domain, name, description, options, moduleConfig
}
```
### File Utilities (`lib.mjallen.file`)
- `readFile path` - Read file contents
- `pathExists path` - Check if path exists
- `safeImport path default` - Safe Nix import
- `getFile relativePath` - Get path relative to flake root
- `importModulesRecursive path` - Recursively discover Nix modules
- `scanSystems systemsPath` - Discover system configurations
- `filterNixOSSystems systems` - Filter for Linux systems
- `filterDarwinSystems systems` - Filter for macOS systems
- `scanHomes homesPath` - Parse home-manager configurations
### Versioning Utilities (`lib.mjallen.versioning`)
For packages with `version.json` (multi-variant source pinning):
- `selectVariant spec variantName system` - Select variant from spec
- `render value variables` - Template substitution (`${var}`)
- `mkSrc pkgs comp variables` - Build single source
- `mkAllSources pkgs selected` - Build all sources for selected variant
See `lib/versioning/default.nix` for full API and `docs/version.schema.json` for schema.
### Usage in Packages
Create `packages/<name>/version.json` with variant definitions, then use:
```nix
let
versioning = inputs.self.lib.mjallen.versioning;
spec = inputs.self.lib.mjallen.file.readFile ./version.json;
selected = versioning.selectVariant spec variantName system;
sources = versioning.mkAllSources pkgs selected;
in
# Use sources.componentName for each source
```

129
README.md
View File

@@ -71,7 +71,7 @@ A powerful AMD-based desktop with gaming capabilities, featuring:
### NAS
A home server with various self-hosted services:
- Media management (Jellyfin, seerr)
- Media management (Jellyfin, Jellyseerr)
- Download automation (Sonarr, Radarr, etc.)
- Document management (Paperless)
- File sharing (Samba, Nextcloud)
@@ -113,133 +113,6 @@ sudo nixos-rebuild switch --flake .#hostname
home-manager switch --flake .#username@hostname
```
## Secrets Management
Secrets are managed with [sops-nix](https://github.com/Mic92/sops-nix). Each secret file is encrypted with [age](https://age-encryption.org/), using the SSH host key (`/etc/ssh/ssh_host_ed25519_key`) of each machine as a recipient, so that machine can decrypt its own secrets at boot without any passphrase.
### How age keys work
sops-nix derives an age key from the machine's ed25519 SSH host key automatically. The corresponding age **public key** must be added to `.sops.yaml` before you can encrypt secrets for that machine.
To get the age public key for a machine:
```bash
# On the target machine (or from its host key file):
nix-shell -p ssh-to-age --run \
'ssh-keyscan localhost 2>/dev/null | ssh-to-age'
# Or directly from the key file:
nix-shell -p ssh-to-age --run \
'ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub'
```
### Adding a new machine
1. **Get the age public key** for the new machine using the command above.
2. **Add it to `.sops.yaml`**:
```yaml
keys:
- &new-machine age1<public-key-here>
creation_rules:
- path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
key_groups:
- age:
- *new-machine
# ... existing recipients
```
3. **Re-encrypt all secret files** so the new machine becomes a recipient:
```bash
find secrets/ -name '*.yaml' -exec sops updatekeys {} \;
```
### Adding a new secret
To add a secret to an existing file:
```bash
# Edit the file interactively (sops decrypts, opens $EDITOR, re-encrypts on save)
sops secrets/nas-secrets.yaml
```
To create a new secrets file:
```bash
sops secrets/mymachine-secrets.yaml
```
The `.sops.yaml` `creation_rules` determine which keys encrypt the file based on its path.
### Generating Nebula VPN certificates
The Nebula module (`mjallen.services.nebula`) expects three secrets per host under a configurable prefix:
- `<prefix>/ca-cert` — the CA certificate (shared across all nodes)
- `<prefix>/host-cert` — this node's signed certificate
- `<prefix>/host-key` — this node's private key
**Step 1 — Create the CA** (once per network, on a trusted machine):
```bash
nebula-cert ca -name "jallen-nebula"
# Produces: ca.crt, ca.key
```
**Step 2 — Sign a certificate for each node**:
```bash
# Lighthouse (assign an overlay IP, e.g. 10.1.1.1)
nebula-cert sign -name "pi5" -ip "10.1.1.1/24" \
-ca-crt ca.crt -ca-key ca.key \
-out-crt lighthouse.crt -out-key lighthouse.key
# Regular node (assign a unique overlay IP, e.g. 10.1.1.2)
nebula-cert sign -name "nas" -ip "10.1.1.2/24" \
-ca-crt ca.crt -ca-key ca.key \
-out-crt nas.crt -out-key nas.key
```
**Step 3 — Add the secrets to SOPS**:
```bash
# Edit the target host's secrets file
sops secrets/pi5-secrets.yaml
```
Add the certificate contents under the configured prefix (e.g. `pi5/nebula`):
```yaml
pi5:
nebula:
ca-cert: |
<contents of ca.crt>
lighthouse-cert: |
<contents of lighthouse.crt>
lighthouse-key: |
<contents of lighthouse.key>
```
The key name for the cert/key pair matches the `hostSecretName` option (e.g. `hostSecretName = "lighthouse"` → looks for `lighthouse-cert` / `lighthouse-key`).
**Step 4 — Shred the plaintext key files** once they are in SOPS:
```bash
shred -u ca.key lighthouse.key nas.key
```
> Keep `ca.crt` accessible if you need to sign more nodes later, but store `ca.key` only in SOPS.
## Documentation
Comprehensive documentation is available in the [docs](./docs) directory:
- [Getting Started](./docs/getting-started.md) - Instructions for setting up new systems
- [Architecture](./docs/architecture.md) - Overview of the repository structure
- [System Configurations](./docs/systems/README.md) - Details about each system
- [Home Assistant](./docs/home-assistant/README.md) - Home Assistant setup and automations
- [Custom Modules](./docs/modules/README.md) - Details about reusable configuration modules
- [Troubleshooting](./docs/troubleshooting.md) - Common issues and solutions
## License
This project is licensed under the MIT License - see the LICENSE file for details.

View File

@@ -1,383 +0,0 @@
# Workarounds, Overrides & Temporary Fixes
This document tracks all known workarounds, patches, and temporary overrides in this flake.
Each entry includes the file location, reason, and whether it is still required.
**Status legend:**
- `ACTIVE` — still required, upstream fix not available
- `REDUNDANT` — upstream has fixed the issue; this override can be removed
- `UPSTREAM PENDING` — waiting on an upstream PR/issue
- `INTENTIONAL` — permanent design decision, not a workaround
---
## Overlays (upstream package overrides)
### `overlays/cosmic-settings-daemon/default.nix`
**Status:** `ACTIVE — UPSTREAM PENDING`
`cosmic-settings-daemon 1.0.8` has a buggy `Cargo.lock` that references
`https://github.com/pop-os/dbus-settings-bindings` at two different commits
(`3b86984` for `cosmic-dbus-a11y`/`locale1`/`upower_dbus`, and `0fa672f8`
for the `cosmic-settings-daemon` subcrate). `cargoSetupHook` (used by
`fetchCargoVendor`/`cargoHash`) rejects this: *"Sources are not allowed to be
defined multiple times."*
The fix overrides `cargoDeps` with `rustPlatform.importCargoLock`, which uses
a different vendoring strategy that handles multiple commits from the same repo.
**Removal condition:** When nixpkgs updates `cosmic-settings-daemon` past 1.0.8
with a fixed `Cargo.lock`, or applies `cargoLock` in its own package definition.
---
### `overlays/cosmic-applets/default.nix`
**Status:** `ACTIVE — UPSTREAM PENDING`
`cosmic-applets 1.0.8` has the same class of bug: its `Cargo.lock` references
`https://github.com/pop-os/cosmic-settings` at two different commits (`b46a55d`
for `cosmic-pipewire` and `cosmic-settings-sound-subscription`, and `55b502d`
for `cosmic-settings-a11y-manager-subscription` and several other crates).
`cargoSetupHook` rejects this with the same "Sources are not allowed to be
defined multiple times" error.
Same fix as `cosmic-settings-daemon`: overrides `cargoDeps` with
`rustPlatform.importCargoLock`.
**Removal condition:** When nixpkgs updates `cosmic-applets` past 1.0.8 with a
fixed `Cargo.lock`, or applies `cargoLock` in its own package definition.
---
### ~~`overlays/waybar/default.nix`~~ — REMOVED
**Status:** `REMOVED`
Previously added `-Dexperimental=true` to waybar's meson flags. nixpkgs now
includes `-Dexperimental=true` in its waybar definition, making the overlay
redundant. Removed.
---
### `overlays/radios/default.nix`
**Status:** `ACTIVE` (protective — needed after next `flake update`)
`radios` requires `pycountry>=24.0.0,<25.0.0` (PEP 440: `^24.0.0`). The
current locked nixpkgs has `pycountry 24.6.1` (in range), but nixpkgs HEAD
has already bumped `pycountry` to `26.2.16`, which will break `radios` after
the next `flake update`. The overlay applies `pythonRelaxDepsHook` to loosen
the upper bound.
**Removal condition:** When the upstream `radios` package (`frenck/python-radios`)
or nixpkgs relaxes the pycountry version constraint.
---
### `overlays/redis/default.nix`
**Status:** `INTENTIONAL`
Replaces `redis` with `valkey` (the Redis community fork) globally. This is a
deliberate preference for the open-source fork over the Redis 7.x+ license change.
---
### `overlays/stable/default.nix`
**Status:** `INTENTIONAL`
Injects `pkgs.stable` as an attribute pointing to the stable nixpkgs channel,
so modules can selectively pull in stable packages. Not a workaround.
---
## Flake Inputs (forks and custom branches)
### `nixpkgs-otbr` — `github:mrene/nixpkgs/openthread-border-router`
**File:** `flake.nix:8`
**Status:** `ACTIVE — UPSTREAM PENDING`
`openthread-border-router` is not yet packaged in nixpkgs-unstable. A community
member's nixpkgs fork provides the package, used by
`modules/nixos/homeassistant/services/thread/default.nix`.
The fork is ~52,000 commits behind `nixos-unstable`, so it is pulled
only via `pkgs.callPackage` from the fork's path, not as a full channel overlay.
**Removal condition:** When `openthread-border-router` is merged into nixpkgs.
Check: https://github.com/NixOS/nixpkgs/pulls?q=openthread-border-router
---
### `snowfall-lib` — `github:mjallen18/snowfall-lib`
**File:** `flake.nix:26`
**Status:** `INTENTIONAL`
Personal fork of `snowfallorg/lib` with 46 commits ahead of upstream, including:
- `fix: pass namespace argument to overlays`
- `fix: pass namespace argument to home-manager modules`
- `feat: support same username across multiple targets`
- `feat: enable per-channel configuration and fix pkgs selection`
- Performance improvements and additional features
These are custom changes required by this flake's structure that have not been
upstreamed.
---
### `steam-rom-manager` — `github:mjallen18/nix-steam-rom-manager`
**File:** `flake.nix:41`
**Status:** `INTENTIONAL`
Personal fork/packaging of nix-steam-rom-manager. The upstream
(`nix-community/nix-steam-rom-manager`) may or may not exist; this is a
maintained fork.
---
### Commented-out: `nvmd/disko` fork
**File:** `flake.nix:59-61`
**Status:** `REDUNDANT` (already disabled)
```nix
# the fork is needed for partition attributes support
# url = "github:nvmd/disko/gpt-attrs";
```
A community fork of disko with GPT partition attribute support was previously
used but has since been switched back to upstream `nix-community/disko`. The
comment can be cleaned up if the feature is no longer needed.
---
## Build Fixes & postPatch
### `packages/edk2-basetools/default.nix` — OpenSSL vendoring FIXME
**File:** `packages/edk2-basetools/default.nix:50-52`
**Status:** `UPSTREAM PENDING` (verify PR reference)
```nix
# FIXME: unvendor OpenSSL again once upstream updates
# to a compatible version.
# Upstream PR: https://github.com/tianocore/edk2/pull/10946
```
The comment references tianocore/edk2 PR #10946, but that PR's title is
*"update to openssl 3.5.1"* (now merged), not an unvendoring PR. The FIXME
comment may be referencing the wrong PR number or the issue may have evolved.
The edk2 build still vendor-patches OpenSSL compatibility; this should be
re-evaluated against the current edk2 upstream.
**Action:** Verify whether the OpenSSL vendoring is still needed with current
edk2, and update or remove the FIXME comment.
---
### `packages/bcachefs/default.nix` — Tests disabled
**File:** `packages/bcachefs/default.nix:100`
**Status:** `ACTIVE`
```nix
# FIXME: Try enabling this once the default linux kernel is at least 6.7
doCheck = false; # needs bcachefs module loaded on builder
```
Tests require a live bcachefs kernel module in the build sandbox, which is
not available. The comment mentions kernel ≥ 6.7, which is now the case
(nixpkgs is on 6.12+), but the underlying sandbox limitation still applies —
the build sandbox cannot load kernel modules regardless of kernel version.
**Action:** The `6.7` condition is now met but irrelevant; update the comment
to reflect that the real blocker is sandbox access to kernel modules.
---
### `packages/raspberrypi/linux-rpi/default.nix` — Failed kernel attempts
**File:** `packages/raspberrypi/linux-rpi/default.nix:25-43`
**Status:** `ACTIVE` (informational)
Four newer RPi kernel versions (6.15.11 through 6.19.0-rc5) are commented out
because they "fail for various reasons." The active version is pinned to an
older working commit.
**Action:** Periodically attempt to enable a newer kernel tag. The comments
serve as a history of failed attempts.
---
### `packages/raspberrypi/linux-rpi/default.nix` — DTB aliasing hack
**File:** `packages/raspberrypi/linux-rpi/default.nix:110-148`
**Status:** `ACTIVE`
```nix
# Make copies of the DTBs named after the upstream names so that U-Boot finds them.
# This is ugly as heck, but I don't know a better solution so far.
```
RPi's kernel ships DTBs with non-standard names (e.g. `bcm2708-rpi-zero-w.dtb`);
U-Boot expects canonical upstream names (e.g. `bcm2835-rpi-zero.dtb`). DTBs
are duplicated in `postFixup`.
---
### `packages/homeassistant/ha-mail-and-packages/default.nix` — Hardcoded paths
**File:** `packages/homeassistant/ha-mail-and-packages/default.nix:25-37`
**Status:** `ACTIVE`
The upstream HA integration hard-codes paths relative to its source directory,
which breaks in the Nix store. `postPatch` rewrites them to
`/var/lib/homeassistant/images/`.
---
### `packages/homeassistant/ha-wyzeapi/default.nix` — Version pin relaxation
**File:** `packages/homeassistant/ha-wyzeapi/default.nix:24-27`
**Status:** `ACTIVE`
Relaxes the minimum `wyzeapy` version pin from `0.5.28` to `0.5.27` to match
the version packaged in this flake.
---
## Raspberry Pi — Structural Overrides
### `modules/nixos/hardware/raspberry-pi/default.nix` — jemalloc 16K pages
**File:** `modules/nixos/hardware/raspberry-pi/default.nix:458-470`
**Status:** `ACTIVE` (structural)
```nix
# https://github.com/nvmd/nixos-raspberrypi/issues/64
jemalloc = prev.jemalloc.overrideAttrs (old: {
configureFlags = ... ++ [ "${pageSizeFlag}=14" ];
});
```
RPi5 uses 16K memory pages (2^14). jemalloc must be compiled with
`--with-lg-page=14`, otherwise it will use incorrect page size assumptions
and likely crash or corrupt memory.
**References:** https://github.com/nvmd/nixos-raspberrypi/issues/64
---
### `systems/aarch64-linux/pi5/boot.nix` — 16K page kernel
**File:** `systems/aarch64-linux/pi5/boot.nix:22-35`
**Status:** `ACTIVE` (structural)
Forces `CONFIG_ARM64_16K_PAGES=y` in the kernel config via `linux_6_19.override`.
`ignoreConfigErrors = true` is required because some kernel config options are
unavailable and would otherwise fail validation.
---
### `packages/raspberrypi/ffmpeg-rpi/default.nix` — RPi hardware codec ffmpeg
**File:** `packages/raspberrypi/ffmpeg-rpi/default.nix`
**Status:** `ACTIVE` (structural)
Custom ffmpeg build from `jc-kynesim/rpi-ffmpeg` fork with RPi hardware codec
support (`--enable-v4l2-request`, `--enable-sand`, etc.). Tests disabled
(`doCheck = false`) because the `imgutils` test fails on this build.
---
## systemd Service Overrides
### `systems/x86_64-linux/matt-nixos/default.nix` — networkd-wait-online
**File:** `systems/x86_64-linux/matt-nixos/default.nix:78`
**Status:** `INTENTIONAL`
```nix
systemd.services.systemd-networkd-wait-online.enable = lib.mkForce false;
```
The `systemd-networkd-wait-online` service times out on this desktop,
blocking boot. Standard workaround for desktop systems that don't require
all interfaces to be up before proceeding.
---
### `systems/x86_64-linux/allyx/default.nix` — Jovian NixOS conflicts
**File:** `systems/x86_64-linux/allyx/default.nix:121-123`
**Status:** `ACTIVE`
```nix
systemd-networkd-wait-online.enable = lib.mkForce false;
power-profiles-daemon.enable = lib.mkForce false;
inputplumber.enable = lib.mkForce false;
```
On the ASUS ROG Ally X with Jovian NixOS and `handheld-daemon`:
- `power-profiles-daemon` conflicts with `handheld-daemon`'s power management
- `inputplumber` conflicts with `handheld-daemon`'s input handling
- `systemd-networkd-wait-online` times out as on matt-nixos
---
### `modules/nixos/services/crowdsec/default.nix` — DynamicUser conflict
**File:** `modules/nixos/services/crowdsec/default.nix:133-143`
**Status:** `ACTIVE — UPSTREAM PENDING`
```nix
systemd.services.crowdsec.serviceConfig.DynamicUser = lib.mkForce false;
systemd.services.crowdsec-firewall-bouncer.serviceConfig.DynamicUser = lib.mkForce false;
systemd.services.crowdsec-firewall-bouncer-register.serviceConfig.DynamicUser = lib.mkForce false;
```
The upstream NixOS crowdsec module uses `ReadWritePaths` (not `StateDirectory`)
on the main `crowdsec.service`, expecting `/var/lib/crowdsec` to be a real
directory. However, `crowdsec-firewall-bouncer-register` declares
`StateDirectory=crowdsec` with `DynamicUser=true`, which conflicts — it tries
to create `/var/lib/private/crowdsec` and symlink `/var/lib/crowdsec` to it,
but the directory already exists as a real path. Disabling `DynamicUser` on
all three services resolves the conflict by using the real `crowdsec` user.
Additionally, `crowdsec-firewall-bouncer-register` calls `cscli` without `-c`,
expecting `/etc/crowdsec/config.yaml` to exist, but the upstream NixOS module
uses a Nix store path via `-c` and never creates that file. The module works
around this by extracting the store path at eval time.
**Removal condition:** When the NixOS crowdsec module is fixed upstream to
handle state directory ownership consistently.
---
## Incomplete / TODO Items
These are not workarounds but known incomplete configurations:
| File | Line | Description |
|------|------|-------------|
| `systems/x86_64-linux/jallen-nas/sops.nix` | 89, 113 | Collabora and MariaDB secrets not configured |
| `systems/x86_64-linux/jallen-nas/apps.nix` | 47 | Authentik environment secrets file not wired up |
| `modules/nixos/services/sparky-fitness/default.nix` | — | ~~DB passwords not yet moved to SOPS~~ — resolved; secrets now via `mkSopsEnvFile`; run `sops secrets/nas-secrets.yaml` to add real values for `jallen-nas/sparky-fitness/{db-password,api-encryption-key,auth-secret}` |
| `modules/nixos/services/your-spotify/default.nix` | 36 | Spotify API keys not yet moved to SOPS |
| `modules/nixos/services/booklore/default.nix` | 28 | Database password not yet a SOPS secret |
| `packages/raspberrypi/udev-rules/default.nix` | 33 | `15-i2c-modprobe.rules` disabled; `i2cprobe` script not ported |
| `modules/nixos/homeassistant/services/homeassistant/default.nix` | 214 | `roborock` integration marked broken |
---
## Kernel Boot Parameters
### `systems/x86_64-linux/matt-nixos/boot.nix` — NVMe power saving
**File:** `systems/x86_64-linux/matt-nixos/boot.nix:46-48`
**Status:** `ACTIVE`
```nix
"nvme_core.default_ps_max_latency_us=0"
"pcie_aspm=off"
```
NVMe SSD power-saving states cause latency/stability issues on this machine.
Disabling ASPM and NVMe power states is a standard workaround for affected
hardware.
---
### `systems/aarch64-linux/macbook-pro-nixos/boot.nix` — Fan control
**File:** `systems/aarch64-linux/macbook-pro-nixos/boot.nix:28`
**Status:** `ACTIVE`
```nix
"melt_my_mac=1"
```
Undocumented Asahi Linux kernel parameter that enables fan control on Apple
Silicon Macs. The name is intentional (set by the Asahi kernel developers).

View File

@@ -1,62 +0,0 @@
#!/usr/bin/env bash
disk=/dev/mapper/nuc-nixos-cryptroot
# sudo mkfs.vfat "$disk"1
# sudo bcachefs format --label ssd.ssd1 --compression=zstd --discard "$disk"
sudo mount -t tmpfs -o mode=755 none /mnt
sudo mkdir -p /mnt/{boot,home,root,etc,nix,var/log,tmp,persist}
sudo mount /dev/disk/by-partlabel/disk-main-nuc-nixos-EFI /mnt/boot
# sudo mkdir -p /mnt/boot/firmware
# sudo mount "$disk"2 /mnt/boot/firmware
# sudo mount "$disk"2 -o compress=zstd,subvol=home /mnt/home
# sudo mount "$disk"2 -o compress=zstd,noatime,subvol=root /mnt/root
# sudo mount "$disk"2 -o compress=zstd,noatime,subvol=etc /mnt/etc
# sudo mount "$disk"2 -o compress=zstd,noatime,subvol=nix /mnt/nix
# sudo mount "$disk"2 -o compress=zstd,noatime,subvol=log /mnt/var/log
# bcachefs unlock -k session /dev/disk/by-partlabel/disk-main-nuc-nixos-bcachefs-root
sudo cryptsetup open /dev/disk/by-partlabel/disk-main-nuc-nixos-cryptroot nuc-nixos-cryptroot
# sudo bcachefs unlock -k session "$disk"2
# sudo mount "$disk" /mnt/tmp
# cd /mnt/tmp
# ls -alh
# sudo bcachefs subvolume create nix
# sudo bcachefs subvolume create etc
# sudo bcachefs subvolume create log
# sudo bcachefs subvolume create root
# sudo bcachefs subvolume create persist
# sudo bcachefs subvolume create home
# ls -alh
# cd /etc/nixos
# sudo umount /mnt/tmp
sudo mount -o noatime,X-mount.subdir=nix "$disk" /mnt/nix
sudo mount -o noatime,X-mount.subdir=etc "$disk" /mnt/etc
sudo mount -o noatime,X-mount.subdir=log "$disk" /mnt/var/log
sudo mount -o noatime,X-mount.subdir=root "$disk" /mnt/root
sudo mount -o noatime,X-mount.subdir=persist "$disk" /mnt/persist
sudo mount -o X-mount.subdir=home "$disk" /mnt/home
# tree /mnt
# sudo nixos-install --flake /etc/nixos#nuc-nixos
# sudo umount /mnt/boot
# sudo umount /mnt/var/log
# sudo umount /mnt/persist
# sudo umount /mnt/home
# sudo umount /mnt/root
# sudo umount /mnt/etc
# sudo umount /mnt/nix
# sudo umount /mnt
# wpa_passphrase "Joey's Jungle 5G" "kR8v&3Qd" > 5g.conf
# wpa_supplicant -i wlp6s0 -c 5g.conf -B
# dhcpcd
# keyctl link @u @s
# clevis decrypt < "/etc/clevis/nas_pool.jwe" | bcachefs unlock /dev/disk/by-label/nas_pool

22
checks/pre-commit-hooks/default.nix Executable file → Normal file
View File

@@ -7,16 +7,10 @@
let
inherit (inputs) pre-commit-hooks-nix;
in
pre-commit-hooks-nix.lib.${pkgs.stdenv.hostPlatform.system}.run {
pre-commit-hooks-nix.lib.${pkgs.system}.run {
src = ../..;
hooks = {
pre-commit-hook-ensure-sops = {
enable = true;
excludes = [
"secrets/.*\\.jwe$"
"secrets/.*\\.key$"
];
};
pre-commit-hook-ensure-sops.enable = true;
treefmt = {
enable = lib.mkForce true;
settings.fail-on-change = lib.mkForce false;
@@ -24,17 +18,5 @@ pre-commit-hooks-nix.lib.${pkgs.stdenv.hostPlatform.system}.run {
lib.snowfall.fs.get-file "treefmt.nix"
);
};
nixfmt-rfc-style = {
enable = true;
package = pkgs.nixfmt;
};
# statix disabled - too many false positives (manual_inherit warnings)
# statix = {
# enable = true;
# args = [
# "--config"
# (lib.snowfall.fs.get-file "statix.toml")
# ];
# };
};
}

View File

@@ -1,12 +0,0 @@
# Documentation
This directory contains comprehensive documentation for the NixOS configuration.
## Contents
- [Getting Started](./getting-started.md) - Instructions for setting up new systems
- [System Configurations](./systems/README.md) - Detailed information about each system
- [Home Assistant](./home-assistant/README.md) - Documentation for the Home Assistant setup
- [Custom Modules](./modules/README.md) - Information about reusable modules
- [Architecture](./architecture.md) - Overview of the repository architecture
- [Troubleshooting](./troubleshooting.md) - Common issues and solutions

View File

@@ -1,180 +0,0 @@
# Repository Architecture
This document provides an overview of the repository architecture, explaining how the various components fit together.
## Overview
This NixOS configuration repository is built using [Nix Flakes](https://nixos.wiki/wiki/Flakes) and [Snowfall Lib](https://github.com/snowfallorg/lib) to provide a modular, maintainable configuration for multiple systems. The Snowfall namespace is `mjallen`, so all custom options are accessed as `mjallen.<domain>.<name>`.
## Directory Structure
```
.
├── flake.nix # Main flake — inputs, outputs, Snowfall config
├── flake.lock # Locked dependency versions
├── .sops.yaml # SOPS key management rules
├── treefmt.nix # Code formatter configuration
├── qemu.nix # QEMU VM testing config
├── checks/ # Pre-commit hooks and CI checks
├── docs/ # Documentation (this directory)
├── homes/ # Home Manager configurations
│ ├── aarch64-darwin/ # macOS user configs
│ ├── aarch64-linux/ # ARM Linux user configs
│ └── x86_64-linux/ # x86 Linux user configs
├── lib/ # Custom Nix library utilities
│ ├── module/ # mkModule, mkOpt, mkBoolOpt helpers
│ ├── file/ # File/path utilities
│ └── versioning/ # Package version pinning helpers
├── modules/ # Reusable configuration modules
│ ├── home/ # Home Manager modules
│ ├── nixos/ # NixOS system modules
│ └── darwin/ # nix-darwin modules (macOS)
├── overlays/ # Nixpkgs overlays
├── packages/ # Custom package definitions
├── secrets/ # SOPS-encrypted secret files
└── systems/ # Per-host system configurations
├── aarch64-darwin/ # macOS (nix-darwin) hosts
├── aarch64-linux/ # ARM Linux hosts
├── x86_64-install-iso/# Install ISO configurations
└── x86_64-linux/ # x86_64 Linux hosts
```
## Flake Inputs
| Input | Source | Purpose |
|---|---|---|
| `nixpkgs-unstable` | `github:NixOS/nixpkgs/nixos-unstable` | Primary package set |
| `nixpkgs-stable` | `github:NixOS/nixpkgs/nixos-25.11` | Stable package set |
| `nixpkgs-otbr` | `github:mrene/nixpkgs` (fork) | OpenThread Border Router packages |
| `home-manager-unstable` | `github:nix-community/home-manager` | User environment management |
| `snowfall-lib` | `github:mjallen18/snowfall-lib` | Flake structure library (personal fork) |
| `impermanence` | `github:nix-community/impermanence` | Ephemeral root filesystem support |
| `lanzaboote` | `github:nix-community/lanzaboote/v1.0.0` | Secure Boot |
| `nixos-hardware` | `github:NixOS/nixos-hardware` | Hardware-specific NixOS configs |
| `sops-nix` | `github:Mic92/sops-nix` | Secret management |
| `disko` | `github:nix-community/disko` | Declarative disk partitioning |
| `cosmic` | `github:lilyinstarlight/nixos-cosmic` | COSMIC desktop environment |
| `jovian` | `github:Jovian-Experiments/Jovian-NixOS` | Steam Deck / handheld support |
| `nixos-apple-silicon` | `github:nix-community/nixos-apple-silicon` | Asahi Linux / Apple Silicon |
| `darwin` | `github:nix-darwin/nix-darwin` | macOS system configuration |
| `nix-homebrew` | `github:zhaofengli/nix-homebrew` | Declarative Homebrew (macOS) |
| `stylix` | `github:nix-community/stylix` | System-wide theming |
| `nix-vscode-extensions` | `github:nix-community/nix-vscode-extensions` | VS Code extension packages |
| `authentik-nix` | `github:nix-community/authentik-nix` | Authentik SSO |
| `nix-cachyos-kernel` | `github:xddxdd/nix-cachyos-kernel` | CachyOS optimised kernels |
| `lsfg-vk` | `github:pabloaul/lsfg-vk-flake` | Lossless Scaling frame generation (Linux) |
| `nix-index-database` | `github:nix-community/nix-index-database` | Pre-built nix-index database |
| `steam-rom-manager` | `github:mjallen18/nix-steam-rom-manager` | Steam ROM Manager package |
| `nix-plist-manager` | `github:sushydev/nix-plist-manager` | macOS plist management |
| `nix-rosetta-builder` | `github:cpick/nix-rosetta-builder` | Rosetta build support (macOS) |
| `pre-commit-hooks-nix` | `github:cachix/pre-commit-hooks.nix` | Pre-commit hooks |
| `treefmt-nix` | `github:numtide/treefmt-nix` | Code formatting |
`nixpkgs` and `home-manager` are aliases pointing to the unstable variants.
## Module System
### Structure
All modules follow a standard Snowfall Lib pattern and are automatically discovered. Each module exposes options under the `mjallen` namespace:
```nix
# Enable a module
mjallen.services.jellyfin.enable = true;
mjallen.desktop.gnome.enable = true;
mjallen.hardware.amd.enable = true;
```
### `mkModule` helper
Most service modules are built with `lib.mjallen.mkModule` (`lib/module/default.nix`), which provides a standard set of options:
| Option | Default | Description |
|---|---|---|
| `enable` | `false` | Enable/disable the module |
| `port` | `80` | Service listen port |
| `listenAddress` | `"0.0.0.0"` | Bind address |
| `openFirewall` | `true` | Open firewall ports |
| `configDir` | `/var/lib/<name>` | Config directory |
| `dataDir` | `/var/lib/<name>/data` | Data directory |
| `createUser` | `false` | Create a dedicated system user |
| `configureDb` | `false` | Create a PostgreSQL database |
| `environmentFile` | `null` | Path to an env-file |
| `reverseProxy.enable` | `false` | Add a Caddy reverse proxy block |
| `reverseProxy.subdomain` | `<name>` | Caddy subdomain |
| `redis.enable` | `false` | Create a dedicated Redis instance |
### NixOS modules (`modules/nixos/`)
| Category | Paths | Description |
|---|---|---|
| Boot | `boot/common/`, `boot/lanzaboote/`, `boot/plymouth/`, `boot/systemd-boot/` | Bootloader configurations |
| Desktop | `desktop/gnome/`, `desktop/hyprland/`, `desktop/cosmic/` | Desktop environments |
| Development | `development/` | Dev tools, language support, containers |
| Hardware | `hardware/amd/`, `hardware/nvidia/`, `hardware/battery/`, `hardware/raspberry-pi/`, `hardware/openrgb/`, ... | Hardware-specific configs |
| Headless | `headless/` | Headless server profile (watchdog, no suspend) |
| Home Assistant | `homeassistant/` | Smart home automation suite |
| Impermanence | `impermanence/` | Ephemeral root + persistent state |
| Monitoring | `monitoring/` | Prometheus/Grafana metrics |
| Network | `network/` | Hostname, firewall, NetworkManager, static IP |
| Power | `power/` | UPS support |
| Programs | `programs/` | System-wide programs (nix-index, gnupg, etc.) |
| Security | `security/common/`, `security/tpm/` | Common hardening, TPM unlock |
| Services | `services/<name>/` | ~50 self-hosted service modules (see below) |
| SOPS | `sops/` | Secret management setup |
| System | `system/` | Miscellaneous system settings |
| User | `user/` | User account management |
| Virtualization | `virtualization/` | libvirt, containers |
### Home Manager modules (`modules/home/`)
| Category | Paths | Description |
|---|---|---|
| Desktop | `desktop/gnome/`, `desktop/theme/` | GNOME and theming |
| GPG | `gpg/` | GPG agent configuration |
| Programs | `programs/btop/`, `programs/git/`, `programs/zsh/`, `programs/kitty/`, `programs/waybar/`, `programs/hyprland/`, `programs/wofi/`, `programs/mako/`, `programs/wlogout/`, `programs/librewolf/`, `programs/opencode/`, `programs/update-checker/`, ... | User applications |
| Services | `services/pass/` | Password store integration |
| Shell | `shell-aliases/` | Common shell aliases |
| SOPS | `sops/` | User-level secret integration |
| Stylix | `stylix/` | System-wide theming |
| User | `user/` | User environment defaults |
## Secrets Management
Secrets are encrypted with [SOPS](https://github.com/getsops/sops) using age keys derived from each machine's SSH host key (`/etc/ssh/ssh_host_ed25519_key`). The `.sops.yaml` file maps secret file path patterns to the set of age recipients that can decrypt them.
Each host has its own secrets file:
| File | Host |
|---|---|
| `secrets/secrets.yaml` | Shared (all hosts) |
| `secrets/nas-secrets.yaml` | jallen-nas |
| `secrets/pi5-secrets.yaml` | pi5 |
| `secrets/allyx-secrets.yaml` | allyx |
| `secrets/nuc-secrets.yaml` | nuc-nixos |
| `secrets/mac-secrets.yaml` | macbook-pro-nixos |
| `secrets/desktop-secrets.yaml` | matt-nixos |
See the [Secrets Management](../README.md#secrets-management) section of the root README for full details on generating keys and adding secrets.
## Deployment
```bash
# NixOS system
sudo nixos-rebuild switch --flake .#hostname
# macOS (nix-darwin)
darwin-rebuild switch --flake .#hostname
# Home Manager only
home-manager switch --flake .#username@hostname
```

View File

@@ -1,348 +0,0 @@
# Flake Improvement Suggestions
A methodical review of the flake against what Snowfall Lib provides and what the codebase currently does. Suggestions are grouped by theme and ordered roughly from highest to lowest impact.
---
## 1. Flake-level: HM module registration — single source of truth via snowfall-lib fix
**Root cause discovered**: Snowfall Lib's `mkFlake` previously merged `systems.modules.home` into `homes` only for standalone `homeConfigurations`. The `homes` attrset passed to `create-systems` (which builds `nixosConfigurations`) was the raw unmerged value, so `systems.modules.home` had no effect on NixOS-integrated homes.
**Fix applied**: Patched the personal snowfall-lib fork (`github:mjallen18/snowfall-lib`) to extract the merge into a shared `homes-with-system-modules` binding and pass it to both `create-homes` (standalone) and `create-systems` (NixOS-integrated). `flake.lock` updated to the new commit.
`modules/nixos/home/default.nix` no longer needs `sharedModules``systems.modules.home` in `flake.nix` is now the single authoritative list for all contexts.
---
## 2. Flake-level: Duplicated Darwin HM module registration
**Problem**: Same issue as above for Darwin. `flake.nix:160167` registers Darwin HM modules via `systems.modules.darwin`, but none of those are actually Home Manager modules — `nix-homebrew`, `home-manager.darwinModules.home-manager`, `nix-plist-manager`, `nix-rosetta-builder`, `nix-index-database`, and `stylix.darwinModules.stylix` are all NixOS-style Darwin system modules, not HM `sharedModules`. This is the correct place for them. The `modules/darwin/home/default.nix` module handles the Darwin-side HM bridge.
**No change needed here**, but add a comment to clarify why this list stays in `flake.nix` while the `modules.home` list should move:
```nix
# Common darwin system-level modules (not HM sharedModules — those live in modules/darwin/home/)
modules.darwin = with inputs; [ ... ];
```
---
## 3. System-level: Repeated nebula lighthouse config
**Problem**: Three systems (`matt-nixos`, `allyx`, `macbook-pro-nixos`) each independently spell out the same lighthouse peer config:
```nix
# Repeated verbatim in 3 files:
lighthouses = [ "10.1.1.1" ];
staticHostMap = {
"10.1.1.1" = [ "mjallen.dev:4242" ];
};
port = 4242;
```
**Suggestion**: Add defaults to `modules/nixos/services/nebula/default.nix` options so that non-lighthouse nodes don't need to spell this out. Since this is a personal network with one lighthouse, the defaults can encode that:
```nix
# In nebula/default.nix options:
lighthouses = lib.mjallen.mkOpt (types.listOf types.str) [ "10.1.1.1" ]
"Nebula overlay IPs of lighthouse nodes";
staticHostMap = lib.mjallen.mkOpt (types.attrsOf (types.listOf types.str))
{ "10.1.1.1" = [ "mjallen.dev:4242" ]; }
"Static host map";
port = lib.mjallen.mkOpt types.port 4242 "Nebula listen port";
```
Client systems can then reduce to:
```nix
services.nebula = {
enable = true;
secretsPrefix = "matt-nixos/nebula";
secretsFile = lib.snowfall.fs.get-file "secrets/desktop-secrets.yaml";
hostSecretName = "matt-nixos";
};
```
The lighthouse (`pi5`) already overrides `isLighthouse = true` and doesn't set `lighthouses`/`staticHostMap`, so it would be unaffected.
---
## 4. System-level: `systemd-networkd-wait-online` scattered disablement
**Problem**: `systemd.services.systemd-networkd-wait-online.enable = lib.mkForce false` appears in:
- `systems/x86_64-linux/matt-nixos/default.nix:92`
- `systems/x86_64-linux/allyx/default.nix:135`
`modules/nixos/network/default.nix` already disables `NetworkManager-wait-online` and `systemd.network.wait-online`, but not `systemd-networkd-wait-online`. These are the same underlying concern.
**Suggestion**: Add `systemd.services.systemd-networkd-wait-online.enable = lib.mkForce false;` unconditionally to `modules/nixos/network/default.nix` alongside the existing `NetworkManager-wait-online` disablement (line 89). Remove the per-system overrides.
---
## 5. System-level: `coolercontrol` and GNOME desktop environment variables
**Problem**: Two systems (`matt-nixos:91`, `allyx:82`) share identical config blocks:
```nix
programs.coolercontrol.enable = true;
environment.variables = {
GDK_SCALE = "1";
EDITOR = "${lib.getExe' pkgs.vscodium "codium"} --wait";
VISUAL = "${lib.getExe' pkgs.vscodium "codium"} --wait";
};
```
These belong to a desktop AMD gaming profile, not to the system configs themselves.
**Suggestions** (pick one or both):
- **A** — Add a `coolercontrol.enable` option to `modules/nixos/hardware/amd/default.nix` (default `false`) and wire `programs.coolercontrol.enable` inside it. Each system opts in with `hardware.amd.coolercontrol.enable = true`.
- **B** — Add `vscodium` as the default `EDITOR`/`VISUAL` to `modules/nixos/desktop/gnome/default.nix` behind a `vscodium.enable` option (default `false`). The two systems that want it set `desktop.gnome.vscodium.enable = true`.
- **C** — Create a shared `modules/nixos/desktop/common/default.nix` (or `profiles/desktop.nix`) that both GNOME and Hyprland modules consume, and put `GDK_SCALE` there.
---
## 6. System-level: `networking.networkmanager.wifi.backend = "iwd"` bypass
**Problem**: `matt-nixos:100` and `allyx:140` set `networking.networkmanager.wifi.backend = "iwd"` directly, bypassing the `${namespace}.network.iwd.enable` option that the `network` module already provides.
Looking at `modules/nixos/network/default.nix:143154`, enabling `cfg.iwd.enable` does set this value via `mkForce`, but it also forces `networkmanager.enable = mkForce false` — which is unwanted on these systems that use NetworkManager with the iwd backend.
**Root cause**: The module conflates "use iwd" (the WiFi daemon) with "disable NetworkManager" (the connection manager). These are separate concerns. NetworkManager can use iwd as its WiFi backend while still being the connection manager.
**Suggestion**: Restructure the `network` module's iwd handling:
```nix
# Instead of forcing NM off when iwd is enabled:
networking = {
wireless.iwd.enable = cfg.iwd.enable;
networkmanager = mkIf cfg.networkmanager.enable {
enable = true;
wifi.backend = mkIf cfg.iwd.enable "iwd";
# ... rest of NM config
};
};
```
Then the per-system lines become:
```nix
${namespace}.network = {
hostName = "matt-nixos";
iwd.enable = true;
networkmanager.enable = true;
};
```
---
## 7. System-level: `fileSystems."/etc".neededForBoot` not in impermanence module
**Problem**: `fileSystems."/etc".neededForBoot = true` is set manually in four system configs (`nuc-nixos`, `pi5`, `jallen-nas`, `graphical`). This is a prerequisite of impermanence (tmpfs root), not a per-system choice.
**Suggestion**: Add to `modules/nixos/impermanence/default.nix`:
```nix
config = mkIf cfg.enable {
fileSystems."/etc".neededForBoot = true;
# ... existing config
};
```
Then remove the manual setting from each system. (`macbook-pro-nixos` and `matt-nixos` may already have this in their `filesystems.nix` — verify and remove duplicates there too.)
---
## 8. System-level: `system.stateVersion` and `time.timeZone` should be module options
**Problem**: In `modules/nixos/system/default.nix`:
- Line 3: `timezone = "America/Chicago"` is hardcoded
- Line 54: `system.stateVersion = "23.11"` is hardcoded
Both are set unconditionally for every system with no way to override without using `lib.mkForce`.
**Suggestions**:
```nix
# modules/nixos/system/default.nix
{ config, lib, namespace, pkgs, system, ... }:
let
cfg = config.${namespace}.system;
in
{
options.${namespace}.system = {
timezone = lib.mkOption {
type = lib.types.str;
default = "America/Chicago";
description = "System timezone";
};
stateVersion = lib.mkOption {
type = lib.types.str;
default = "23.11";
description = "NixOS state version. Should match the version used when the system was first installed.";
};
};
config = {
time.timeZone = cfg.timezone;
system.stateVersion = cfg.stateVersion;
# ... packages
};
}
```
This maintains the current default for all systems (no change required) while allowing any system to say `${namespace}.system.stateVersion = "24.05"` cleanly.
---
## 9. Module-level: Darwin and NixOS `nix` modules share ~90% of their content
**Problem**: `modules/darwin/nix/default.nix` and `modules/nixos/nix/default.nix` differ only in:
- Darwin lacks `daemonCPUSchedPolicy`/`daemonIOSchedClass`/`daemonIOSchedPriority`
- Darwin lacks the `systemd.services.nix-gc.serviceConfig` block
- Darwin lacks `cudaSupport`/`rocmSupport` in `nixpkgs.config`
- Darwin's substituters list omits `attic.xuyh0120.win/lantian`
Everything else — substituters, trusted keys, `warn-dirty`, `experimental-features`, `trusted-users`, `builders-use-substitutes`, `connect-timeout`, `fallback`, `log-lines`, `max-free`, `min-free`, GC settings, `optimise` — is identical.
**Suggestion**: Extract a shared Nix attrset into `lib/nix-settings/default.nix` (or a plain `.nix` file imported by both):
```nix
# lib/nix-settings/default.nix
{ lib }:
{
commonSubstituters = [
"http://jallen-nas.local:9012/nas-cache"
"https://nixos-apple-silicon.cachix.org"
"https://nixos-raspberrypi.cachix.org"
"https://nix-community.cachix.org"
"https://cache.nixos.org/"
];
commonTrustedPublicKeys = [ ... ];
commonSettings = { warn-dirty = ...; experimental-features = ...; ... };
commonGc = { automatic = true; options = "--delete-older-than 30d"; };
}
```
Both modules import and spread this. The NixOS module adds scheduler policies and systemd GC service tweaks on top.
---
## 10. Module-level: Home SOPS configuration is inconsistent across homes
**Problem**: Three different patterns are used to configure SOPS in home configs:
1. **`${namespace}.sops.enable = true`** — uses the module at `modules/home/sops/default.nix` (macbook-pro-nixos home, jallen-nas home)
2. **Inline SOPS config** — sets `sops.*` directly (allyx home, pi5 home)
3. **Nothing** — some homes don't configure sops at all (matt-nixos home relies on system-level secrets only)
The `modules/home/sops/default.nix` module already handles the `age.keyFile` path, `defaultSopsFile`, and SSH key setup. The inline patterns duplicate this.
**Suggestion**: Migrate all homes that configure sops inline to use `${namespace}.sops.enable = true`. If the home needs a different `defaultSopsFile` (e.g. pi5 uses `secrets/pi5-secrets.yaml`), that should be a module option:
```nix
# modules/home/sops/default.nix — add option:
options.${namespace}.sops = {
enable = lib.mkEnableOption "home sops";
defaultSopsFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null; # falls back to global secrets.yaml
description = "Override the default SOPS file for this home";
};
};
```
---
## 11. Module-level: `modules/nixos/home/default.nix` — `home-manager` input key coupling
**Problem**: `systems.modules.nixos` in `flake.nix:147` explicitly includes `home-manager.nixosModules.home-manager`. However Snowfall Lib **automatically** injects the home-manager NixOS module when the `home-manager` input is present and there are home configurations (Snowfall Lib `system/default.nix` lines 265270).
**Suggestion**: Verify (by temporarily removing the explicit entry) whether `home-manager.nixosModules.home-manager` can be dropped from `systems.modules.nixos`. If Snowfall Lib handles this automatically, removing it eliminates the manual coupling.
---
## 12. System-level: `nuc-nixos` — large monolithic default.nix
**Problem**: `systems/x86_64-linux/nuc-nixos/default.nix` is over 330 lines and contains everything inline: disk config, networking, Home Assistant dashboard definitions (~170 lines of inline Nix), kernel config, user setup, and services. Every other complex system (jallen-nas) already uses a split structure with `apps.nix`, `services.nix`, `nas-defaults.nix`, etc.
**Suggestion**: Extract into separate files following the jallen-nas pattern:
```
systems/x86_64-linux/nuc-nixos/
├── default.nix # thin: imports + top-level options
├── boot.nix # disk/luks/filesystem config
├── dashboard.nix # Home Assistant dashboard card definitions
├── services.nix # postgres, redis, HA, OTBR etc.
└── sops.nix # (or reuse the shared module)
```
The dashboard in particular (currently lines ~88260) should be isolated so HA configuration changes don't require touching system-level config.
---
## 13. System-level: Verify `admin@jallen-nas` steam-rom-manager double-import
**Problem**: `homes/x86_64-linux/admin@jallen-nas/default.nix:16` explicitly imports `steam-rom-manager.homeManagerModules.default`. This same module is injected globally via `modules/nixos/home/default.nix:92` for all x86_64 systems (the ARM guard is `!isArm`, and jallen-nas is x86_64).
**Suggestion**: Remove the explicit import from `admin@jallen-nas/default.nix`. If it was added for standalone `home-manager switch` builds (without NixOS), document that reason in a comment rather than keeping a potentially conflicting double-import.
---
## 14. Flake-level: `pi5` host entry with empty modules list
**Problem**: `flake.nix:218221` defines:
```nix
pi5 = {
modules = [ ];
};
```
An empty modules list is the default behavior — this entry has no effect and can be removed. The comment `# disko is already in systems.modules.nixos above` is incorrect (disko is global for all systems, not specific to pi5). The comment itself is misleading.
**Suggestion**: Remove the `pi5` host entry from `flake.nix` entirely. If the comment is meant to remind future maintainers that disko is global, move that context to `AGENTS.md` or a comment near the global `systems.modules.nixos` list.
---
## 15. Flake-level: `home-manager-stable` input is pulled in but never used
**Problem**: `flake.nix:1013` defines `home-manager-stable` but `home-manager = home-manager-unstable` is the alias (line 21). No system or module references `home-manager-stable` directly. It adds to lock file churn and evaluation time.
**Suggestion**: Remove `home-manager-stable` unless there is a concrete plan to use it for a stable-channel system. If stable Home Manager support is desired in the future, add it back at that point.
---
## 16. Flake-level: Consider using Snowfall Lib `alias` for formatter output
**Problem**: The `outputs-builder` in `flake.nix:277280` is used only to register the `treefmt` formatter. Snowfall Lib supports an `alias` mechanism and also allows `outputs-builder` to be used, but this is the only use of `outputs-builder` in the entire flake.
**Suggestion**: This is fine as-is, but note that `outputs-builder` output can be overridden by auto-discovery. Since the formatter isn't auto-discovered, `outputs-builder` is the correct approach. No change needed — but the comment on line 279 about the mjallen-lib overlay being auto-discovered is accurate and good to keep.
---
## Summary Table
| # | Location | Type | Effort | Impact |
|---|----------|------|--------|--------|
| 1 | `flake.nix` | Deduplication | Low | High — removes confusing double-registration |
| 2 | `flake.nix` | Documentation | Low | Low |
| 3 | `nebula/default.nix` | Better defaults | Low | Medium — 3 systems simplified |
| 4 | `network/default.nix` | Consolidation | Low | Medium — remove per-system workarounds |
| 5 | `hardware/amd` + `desktop/gnome` | New options | Medium | Medium — DRY gaming desktop profile |
| 6 | `network/default.nix` | Bug fix / refactor | Medium | High — current iwd handling is incorrect |
| 7 | `impermanence/default.nix` | Consolidation | Low | Medium — remove 4 manual entries |
| 8 | `system/default.nix` | New options | Low | Medium — allows per-system overrides cleanly |
| 9 | `lib/` + `darwin/nix` + `nixos/nix` | Extraction | Medium | Medium — single source of truth for nix config |
| 10 | `homes/*/` + `modules/home/sops` | Consistency | Low | Low — consistency improvement |
| 11 | `flake.nix` | Simplification | Low | Low — possible dead entry |
| 12 | `systems/nuc-nixos/` | Refactor | Medium | High — maintainability |
| 13 | `homes/admin@jallen-nas` | Bug fix | Trivial | Low — potential double-import |
| 14 | `flake.nix` | Cleanup | Trivial | Low — dead code |
| 15 | `flake.nix` | Cleanup | Trivial | Low — reduces lock churn |
| 16 | `flake.nix` | N/A | None | No change needed |

View File

@@ -1,175 +0,0 @@
# Getting Started
This guide will help you get started with this NixOS configuration repository.
## Prerequisites
- Basic knowledge of NixOS and the Nix language
- Git installed on your system
- Physical or SSH access to the target machine
## Cloning the Repository
```bash
git clone ssh://nix-apps@localhost:2222/mjallen/nix-config.git
cd nix-config
```
## Installing on a New Machine
### Option 1: Using an existing system configuration
If the machine matches an existing configuration (e.g. reinstalling `jallen-nas`):
1. Boot from a NixOS installation ISO
2. Partition and mount disks (or use `disko`):
```bash
nix run github:nix-community/disko -- --mode disko /path/to/disko-config.nix
```
3. Clone this repo into the target:
```bash
mkdir -p /mnt/etc/nixos
git clone <repo-url> /mnt/etc/nixos
```
4. Install:
```bash
nixos-install --flake /mnt/etc/nixos#hostname
```
### Option 2: Adding a new system configuration
1. **Create the system directory** under the appropriate architecture:
```bash
mkdir -p systems/x86_64-linux/new-hostname
```
2. **Write the configuration** — at minimum a `default.nix`:
```nix
{ namespace, ... }:
{
mjallen = {
sops.enable = true;
network.hostName = "new-hostname";
user.name = "admin";
};
}
```
3. **Generate hardware configuration** (on the target machine):
```bash
nixos-generate-config --no-filesystems --dir systems/x86_64-linux/new-hostname/
```
4. **Add SOPS secrets** for the new host — see [Secrets Management](../README.md#secrets-management).
5. **Build and switch**:
```bash
sudo nixos-rebuild switch --flake .#new-hostname
```
## Day-to-Day Usage
### Applying configuration changes
```bash
# On the local machine
sudo nixos-rebuild switch --flake .#$(hostname)
# On a remote machine
nixos-rebuild switch --flake .#hostname --target-host user@host --use-remote-sudo
```
### Updating flake inputs
```bash
# Update all inputs
nix flake update
# Update a single input
nix flake lock --update-input nixpkgs
# Apply after updating
sudo nixos-rebuild switch --flake .#$(hostname)
```
### Garbage collection
```bash
# Remove old generations and unreferenced store paths
sudo nix-collect-garbage -d
# Keep the last N generations
sudo nix-collect-garbage --delete-older-than 30d
```
## Enabling a Module
Most functionality is exposed through the `mjallen` namespace. To enable a module, set it in the system's `default.nix` (or a relevant sub-file):
```nix
mjallen = {
desktop.gnome.enable = true;
hardware.amd.enable = true;
gaming.enable = true;
services.jellyfin = {
enable = true;
port = 8096;
reverseProxy.enable = true;
};
};
```
See [Custom Modules](./modules/README.md) for the full list of available modules and options.
## Adding a New Service Module
1. **Create the module directory**:
```bash
mkdir -p modules/nixos/services/my-service
```
2. **Write `default.nix`** using the `mkModule` helper:
```nix
{ config, lib, namespace, pkgs, ... }:
let
name = "my-service";
nebulaConfig = lib.${namespace}.mkModule {
inherit config name;
description = "my service description";
options = { };
moduleConfig = {
services.my-service = {
enable = true;
port = config.${namespace}.services.${name}.port;
};
};
};
in
{ imports = [ nebulaConfig ]; }
```
3. **Enable it** in a system configuration:
```nix
mjallen.services.my-service = {
enable = true;
port = 1234;
};
```
## Adding a New Package
1. Create a directory under `packages/`:
```bash
mkdir packages/my-package
```
2. Write a `default.nix` that returns a derivation. The package will be available as `pkgs.mjallen.my-package` in all configurations.
## Secrets
See the [Secrets Management](../README.md#secrets-management) section of the root README for:
- How age keys are derived from SSH host keys
- Adding a new machine as a SOPS recipient
- Adding/editing secrets
- Generating Nebula VPN certificates

View File

@@ -1,188 +0,0 @@
# Home Assistant Configuration
This document provides comprehensive information about the Home Assistant setup in this NixOS configuration.
## Overview
Home Assistant is configured as a NixOS service with custom components, integrations, and automations. The configuration uses a modular approach with separate files for different aspects of the setup.
## Module Structure
The Home Assistant configuration is organized in the following structure:
```
modules/nixos/homeassistant/
├── automations/ # Automation configurations
│ ├── lightswitch/ # Light switch automations
│ └── motion-light/ # Motion-activated light automations
├── default.nix # Main module configuration
├── options.nix # Module options definition
└── services/ # Related service configurations
├── govee2mqtt/ # Govee integration via MQTT
├── homeassistant/ # Core Home Assistant service
├── music-assistant/ # Music Assistant integration
├── thread/ # Thread border router
└── zigbee2mqtt/ # Zigbee to MQTT bridge
```
## Installation
The Home Assistant module is enabled in the system configuration by setting:
```nix
mjallen.services.home-assistant.enable = true;
```
This activates Home Assistant and related services such as MQTT, Zigbee2MQTT, and the Matter server.
## Configuration Options
The module provides several configuration options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enable` | boolean | `false` | Enable Home Assistant and related services |
| `mosquittoPort` | integer | `1883` | Port for the MQTT broker |
| `zigbee2mqttPort` | integer | `8080` | Port for the Zigbee2MQTT web interface |
| `zigbeeDevicePath` | string | `/dev/ttyUSB0` | Path to the Zigbee USB device |
## Core Services
### Home Assistant
The main Home Assistant service is configured in `services/homeassistant/default.nix` with:
- PostgreSQL database backend
- Custom components
- Custom Lovelace modules
- HTTPS access with authentication
- Integration with other services
### MQTT
MQTT is used as a messaging protocol for various smart home devices. The Mosquitto MQTT broker is automatically configured when Home Assistant is enabled.
### Zigbee2MQTT
Zigbee2MQTT allows integration with Zigbee devices. It's configured with:
- Automatic discovery for Home Assistant
- OTA updates for Zigbee devices
- Web interface for management
### Thread Border Router
The Thread Border Router provides integration with Thread-based devices like Matter devices.
## Custom Components
The following custom components are included:
- `ha-anycubic` - Anycubic 3D printer integration
- `ha-bambulab` - Bambu Lab 3D printer integration
- `ha-bedjet` - BedJet climate control integration
- `ha-gehome` - GE Home appliance integration
- `ha-icloud3` - Enhanced iCloud device tracking
- `ha-local-llm` - Local LLM integration
- `ha-mail-and-packages` - Mail and package delivery tracking
- `ha-nanokvm` - NanoKVM integration
- `ha-openhasp` - openHASP integration for DIY displays
- `ha-overseerr` - Overseerr media request integration
- `ha-petlibro` - PetLibro pet feeder integration
- `ha-wyzeapi` - Wyze device integration
## Automations
### Light Switch Automations
The light switch automations handle physical switch inputs for controlling smart lights.
### Motion Light Automations
Motion light automations turn lights on when motion is detected and off after a period of inactivity.
### Custom Automations
Additional automations are placed in the `/etc/hass` directory and are included in the Home Assistant configuration. These include:
- `fountain_automation.yaml` - Toggles the water dispensing mode on the Dockstream Smart RFID Fountain every 15 minutes between constant and intermittent flow.
## Smart Home Devices
The configuration includes support for various smart home devices:
### Lighting
- Various smart lights throughout the home
### Climate
- Smart thermostat
- Humidifier control
### Pet Care
- Dockstream Smart RFID Fountain with scheduling
- Smart pet feeders for pets named Joey and Luci
- Litter-Robot 4 smart litter box
### Media
- Google Cast devices
- Smart TVs
- Media players
### Sensors
- Temperature, humidity, and motion sensors
- Door and window sensors
- Presence detection
## Integration with Other Services
Home Assistant is integrated with:
- **Music Assistant** - For enhanced music streaming capabilities
- **Govee Integration** - For Govee smart devices
- **Matter** - For Matter-compatible devices
## Adding New Automations
To add a new automation:
1. Create a YAML file with the automation definition
2. Place it in `/etc/hass`
3. The automation will be automatically included in Home Assistant
Example automation format:
```yaml
alias: "Automation Name"
description: "Description of what the automation does"
trigger:
- platform: state
entity_id: binary_sensor.motion_sensor
to: "on"
condition: []
action:
- service: light.turn_on
target:
entity_id: light.living_room
mode: single
```
## Troubleshooting
### Common Issues
1. **Zigbee Device Pairing Issues**
- Make sure the Zigbee coordinator is properly connected
- Check the Zigbee2MQTT logs for errors
2. **Service Unavailable**
- Check if all related services are running
- Verify firewall rules allow access to the services
3. **Database Issues**
- Check PostgreSQL service status
- Verify database connection settings

View File

@@ -1,148 +0,0 @@
# Home Assistant Automations
This document details the automations configured in the Home Assistant setup.
## Automation Types
Automations in this configuration are managed in several ways:
1. **Module-Based Automations**: Defined in Nix modules within the `modules/nixos/homeassistant/automations/` directory
2. **YAML Automations**: Defined in YAML files and included via the `automation manual` directive
3. **UI-Created Automations**: Created through the Home Assistant UI and stored in `automations.yaml`
## Module-Based Automations
### Light Switch Automations
**Location**: `modules/nixos/homeassistant/automations/lightswitch/`
These automations link physical light switches to smart lights:
- **Bedroom Light Switch**: Controls the bedroom lights
- **Living Room Light Switch**: Controls the living room lights
- **Bedroom Closet Lights**: Controls the closet lights
### Motion-Activated Light Automations
**Location**: `modules/nixos/homeassistant/automations/motion-light/`
These automations turn lights on when motion is detected and off after a period of inactivity.
## YAML Automations
### Fountain Cycling Automation
**Location**: `/etc/nixos/fountain_automation.yaml`
This automation toggles the water dispensing mode on the Dockstream Smart RFID Fountain every 15 minutes:
```yaml
alias: "Fountain Cycle Mode"
description: "Toggles fountain water mode every 15 minutes between constant and intermittent flow"
trigger:
- platform: time_pattern
minutes: "/15" # Every 15 minutes
condition: []
action:
- service: select.select_next
target:
entity_id: select.dockstream_smart_rfid_fountain_water_dispensing_mode
mode: single
id: fountain_cycle_mode
```
This automation:
1. Triggers every 15 minutes
2. Uses the `select.select_next` service to toggle between the two available options:
- "Flowing Water (Constant)"
- "Intermittent Water (Scheduled)"
The fountain is also configured with:
- Water Interval: 10 minutes
- Water Dispensing Duration: 15 minutes
## Creating New Automations
### Method 1: Module-Based Automation
For reusable, complex automations that should be managed in code:
1. Create a new directory in `modules/nixos/homeassistant/automations/`
2. Create a `default.nix` file with the automation logic
Example:
```nix
{ config, lib, ... }:
{
config = {
services.home-assistant.config."automation manual" = [
{
alias = "Example Automation";
description = "Example automation created via Nix module";
trigger = [
{
platform = "state";
entity_id = "binary_sensor.example_sensor";
to = "on";
}
];
action = [
{
service = "light.turn_on";
target.entity_id = "light.example_light";
}
];
mode = "single";
}
];
};
}
```
### Method 2: YAML Automation
For simpler automations:
1. Create a YAML file with the automation definition
2. Place it in `/etc/hass/`
Example:
```yaml
alias: "Example Automation"
description: "Example automation in YAML"
trigger:
- platform: state
entity_id: binary_sensor.example_sensor
to: "on"
action:
- service: light.turn_on
target:
entity_id: light.example_light
mode: single
```
### Method 3: UI Creation
For quick prototyping or simple automations:
1. Go to Home Assistant UI > Settings > Automations & Scenes
2. Click "+ Add Automation"
3. Configure using the UI editor
## Testing Automations
To test an automation:
1. In the Home Assistant UI, go to Developer Tools > Services
2. Select `automation.trigger` as the service
3. Enter the entity_id of your automation in the service data field
4. Click "Call Service" to trigger the automation manually
## Troubleshooting
If an automation isn't working as expected:
1. Check the Home Assistant logs for errors
2. Verify entity names and service calls are correct
3. Test individual triggers and actions separately
4. Use the "Debug" section in the automation editor to trace execution

View File

@@ -1,96 +0,0 @@
# Pet Fountain Automation
This document details the automation for the Dockstream Smart RFID Fountain device.
## Overview
The Dockstream Smart RFID Fountain is a smart pet fountain controlled through Home Assistant. A custom automation has been created to toggle the water dispensing mode between constant flow and intermittent flow every 15 minutes. This cycling helps keep the water fresh while reducing energy consumption.
## Fountain Configuration
The Dockstream Smart RFID Fountain has the following settings in Home Assistant:
| Setting | Entity ID | Value | Description |
|---------|-----------|-------|-------------|
| Water Dispensing Mode | `select.dockstream_smart_rfid_fountain_water_dispensing_mode` | Toggles between modes | Controls how water flows |
| Water Interval | `number.dockstream_smart_rfid_fountain_water_interval` | 10 minutes | Time between water dispensing in intermittent mode |
| Water Dispensing Duration | `number.dockstream_smart_rfid_fountain_water_dispensing_duration` | 15 minutes | How long water flows in intermittent mode |
| Cleaning Cycle | `number.dockstream_smart_rfid_fountain_cleaning_cycle` | 14 days | Reminder interval for cleaning |
## Available Modes
The fountain supports two water dispensing modes:
1. **Flowing Water (Constant)** - Water flows continuously
2. **Intermittent Water (Scheduled)** - Water flows according to the interval and duration settings
## Automation Details
The fountain cycling automation is defined in `/etc/nixos/fountain_automation.yaml`:
```yaml
alias: "Fountain Cycle Mode"
description: "Toggles fountain water mode every 15 minutes between constant and intermittent flow"
trigger:
- platform: time_pattern
minutes: "/15" # Every 15 minutes
condition: []
action:
- service: select.select_next
target:
entity_id: select.dockstream_smart_rfid_fountain_water_dispensing_mode
mode: single
id: fountain_cycle_mode
```
### How It Works
1. **Trigger**: The automation runs every 15 minutes based on the time pattern trigger
2. **Action**: It uses the `select.select_next` service to toggle to the next available option
3. **Mode**: Set to "single" to prevent multiple executions if triggers overlap
## Installation
The automation is included in Home Assistant via the `automation manual` directive in the Home Assistant configuration:
```yaml
"automation manual" = "!include_dir_merge_list /etc/hass";
```
The YAML file needs to be placed in the `/etc/hass` directory to be loaded.
## Testing
To manually test the automation:
1. In Home Assistant UI, go to Developer Tools > Services
2. Select `automation.trigger` as the service
3. Enter the following service data:
```yaml
entity_id: automation.fountain_cycle_mode
```
4. Click "Call Service" to trigger the automation
## Customizing
To adjust the cycling interval:
1. Edit the YAML file at `/etc/nixos/fountain_automation.yaml`
2. Change the `minutes` value in the trigger section (e.g., from `"/15"` to `"/30"` for every 30 minutes)
3. Save the file
4. Restart Home Assistant or reload automations
To adjust fountain settings:
1. In Home Assistant UI, go to Settings > Devices & Services
2. Find the Dockstream Smart RFID Fountain device
3. Adjust the water interval or dispensing duration settings
## Troubleshooting
If the automation is not working as expected:
1. Check that the entity ID is correct and the fountain is online
2. Verify that Home Assistant is including the automation file correctly
3. Look for errors in the Home Assistant logs related to the automation or the fountain
4. Try manually controlling the fountain to ensure it responds to commands

View File

@@ -1,295 +0,0 @@
# Custom Modules
This directory contains documentation for the custom modules used in this NixOS configuration.
## Overview
Modules are split into three categories:
- **NixOS modules** (`modules/nixos/`) — system-level configuration
- **Home Manager modules** (`modules/home/`) — user-level configuration
- **Darwin modules** (`modules/darwin/`) — macOS-specific configuration
All modules are auto-discovered by Snowfall Lib and expose options under the `mjallen` namespace.
## NixOS Modules
### Boot (`modules/nixos/boot/`)
| Module | Description |
|---|---|
| `boot/common/` | Shared boot defaults (quiet boot, Plymouth) |
| `boot/lanzaboote/` | Secure Boot via Lanzaboote |
| `boot/systemd-boot/` | systemd-boot (non-secure-boot systems) |
| `boot/plymouth/` | Plymouth splash screen |
### Desktop (`modules/nixos/desktop/`)
| Module | Description |
|---|---|
| `desktop/gnome/` | GNOME desktop environment |
| `desktop/hyprland/` | Hyprland compositor |
| `desktop/cosmic/` | COSMIC desktop environment |
### Development (`modules/nixos/development/`)
Enables development tools and language support. Options:
```nix
mjallen.development = {
enable = true;
includeLanguages = [ "python" "c" ];
includeContainers = true;
};
```
### Hardware (`modules/nixos/hardware/`)
| Module | Description |
|---|---|
| `hardware/amd/` | AMD GPU (AMDGPU driver, LACT) |
| `hardware/nvidia/` | NVIDIA GPU |
| `hardware/battery/` | Battery charge threshold management |
| `hardware/raspberry-pi/` | Raspberry Pi hardware support and DT overlays |
| `hardware/openrgb/` | OpenRGB for LED control |
| `hardware/btrfs/` | btrfs-specific settings |
| `hardware/common/` | Common hardware defaults |
### Headless (`modules/nixos/headless/`)
Server profile — disables suspend/hibernate, enables systemd watchdog, no display manager.
```nix
mjallen.headless.enable = true;
```
### Home Assistant (`modules/nixos/homeassistant/`)
Full smart home stack. See [Home Assistant docs](../home-assistant/README.md) for details.
```nix
mjallen.services.home-assistant.enable = true;
```
### Impermanence (`modules/nixos/impermanence/`)
Ephemeral root filesystem with explicit persistence declarations.
```nix
mjallen.impermanence = {
enable = true;
extraDirectories = [ { directory = "/var/lib/myapp"; user = "myapp"; } ];
};
```
### Monitoring (`modules/nixos/monitoring/`)
Prometheus metrics and Grafana dashboards.
```nix
mjallen.monitoring.enable = true;
```
### Network (`modules/nixos/network/`)
Hostname, firewall, NetworkManager profiles, static IP configuration.
```nix
mjallen.network = {
hostName = "my-host";
ipv4 = {
method = "manual";
address = "10.0.1.5/24";
gateway = "10.0.1.1";
dns = "1.1.1.1";
interface = "eth0";
};
firewall = {
enable = true;
allowedTCPPorts = [ 80 443 ];
};
};
```
### Power (`modules/nixos/power/`)
UPS (NUT) support.
```nix
mjallen.power.ups.enable = true;
```
### Security (`modules/nixos/security/`)
| Module | Description |
|---|---|
| `security/common/` | Common hardening (kernel params, etc.) |
| `security/tpm/` | TPM2 — Clevis disk unlock |
### Services (`modules/nixos/services/`)
~50 self-hosted service modules, all built with `mkModule`. Each exposes at minimum `enable`, `port`, `reverseProxy`, and `openFirewall`. Common usage pattern:
```nix
mjallen.services.jellyfin = {
enable = true;
port = 8096;
reverseProxy.enable = true;
};
```
Available services:
`actual`, `ai`, `appimage`, `arrs`, `attic`, `authentik`, `authentikRac`, `booklore`, `caddy`, `calibre`, `calibre-web`, `cockpit`, `code-server`, `collabora`, `coturn`, `crowdsec`, `dispatcharr`, `free-games-claimer`, `gitea`, `glance`, `glances`, `grafana`, `guacd`, `headscale`, `immich`, `jellyfin`, `seerr`, `lubelogger`, `manyfold`, `matrix`, `minecraft`, `mongodb`, `nebula`, `netbootxyz`, `nextcloud`, `ntfy`, `onlyoffice`, `opencloud`, `orca`, `paperless`, `paperless-ai`, `protonmail-bridge`, `restic`, `samba`, `sparky-fitness`, `sparky-fitness-server`, `sunshine`, `tdarr`, `termix`, `tunarr`, `unmanic`, `uptime-kuma`, `wyoming`, `your-spotify`
#### Nebula VPN (`services/nebula/`)
Unified module for both lighthouse and node roles:
```nix
# Lighthouse
mjallen.services.nebula = {
enable = true;
isLighthouse = true;
port = 4242;
secretsPrefix = "pi5/nebula";
secretsFile = lib.snowfall.fs.get-file "secrets/pi5-secrets.yaml";
hostSecretName = "lighthouse";
};
# Node
mjallen.services.nebula = {
enable = true;
port = 4242;
lighthouses = [ "10.1.1.1" ];
staticHostMap = { "10.1.1.1" = [ "mjallen.dev:4242" ]; };
secretsPrefix = "mymachine/nebula";
secretsFile = lib.snowfall.fs.get-file "secrets/mymachine-secrets.yaml";
hostSecretName = "mymachine";
};
```
See [Secrets Management](../../README.md#generating-nebula-vpn-certificates) for how to generate the required certificates.
### SOPS (`modules/nixos/sops/`)
Configures sops-nix to decrypt secrets using the machine's SSH host key as an age key.
```nix
mjallen.sops = {
enable = true;
sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; # default
};
```
### User (`modules/nixos/user/`)
System user account management.
```nix
mjallen.user = {
name = "matt";
mutableUsers = false;
extraGroups = [ "docker" "video" ];
};
```
---
## Home Manager Modules
### Desktop
| Module | Description |
|---|---|
| `desktop/gnome/` | GNOME user settings (extensions, keybindings, etc.) |
| `desktop/theme/` | Theme configuration |
### Programs
| Module | Description |
|---|---|
| `programs/btop/` | btop system monitor |
| `programs/code/` | VS Code / VSCodium settings |
| `programs/git/` | Git user config |
| `programs/hyprland/` | Hyprland compositor config |
| `programs/kitty/` | Kitty terminal config |
| `programs/librewolf/` | LibreWolf browser settings |
| `programs/mako/` | Mako notification daemon |
| `programs/nwg-dock/` | nwg-dock panel |
| `programs/nwg-drawer/` | nwg-drawer app launcher |
| `programs/nwg-panel/` | nwg-panel bar |
| `programs/opencode/` | OpenCode AI coding assistant |
| `programs/update-checker/` | Automatic flake update checker |
| `programs/waybar/` | Waybar status bar |
| `programs/wlogout/` | Logout menu |
| `programs/wofi/` | Wofi launcher |
| `programs/zsh/` | Zsh shell config |
### Other
| Module | Description |
|---|---|
| `gpg/` | GPG agent configuration |
| `services/pass/` | Password store |
| `shell-aliases/` | Common shell aliases |
| `sops/` | User-level SOPS secrets |
| `stylix/` | System-wide theming (colours, fonts, wallpaper) |
| `user/` | User environment defaults |
---
## Module Development
### Using `mkModule`
The `lib.mjallen.mkModule` helper (`lib/module/default.nix`) creates a fully-featured NixOS module from a minimal spec:
```nix
{ config, lib, namespace, pkgs, ... }:
let
name = "my-service";
cfg = config.${namespace}.services.${name};
serviceConfig = lib.${namespace}.mkModule {
inherit config name;
description = "my service";
options = {
# extra options beyond the standard set
myOption = lib.${namespace}.mkOpt lib.types.str "default" "Description";
};
moduleConfig = {
services.my-service = {
enable = true;
port = cfg.port;
};
};
};
in
{ imports = [ serviceConfig ]; }
```
Standard options provided by `mkModule` for free: `enable`, `port`, `listenAddress`, `openFirewall`, `configDir`, `dataDir`, `createUser`, `configureDb`, `environmentFile`, `reverseProxy.*`, `redis.*`, `extraEnvironment`, `hashedPassword`, `puid`, `pgid`, `timeZone`.
### Using `mkContainerService`
For Podman/OCI container services, use `mkContainerService` instead:
```nix
lib.${namespace}.mkContainerService {
inherit config name;
image = "ghcr.io/example/my-app:latest";
internalPort = 8080;
volumes = [ "${cfg.configDir}:/config" ];
};
```
### Option helpers
```nix
lib.mjallen.mkOpt types.str "default" "description"
lib.mjallen.mkBoolOpt false "description"
lib.mjallen.mkOpt' types.int 80 # no description
lib.mjallen.enabled # { enable = true; }
lib.mjallen.disabled # { enable = false; }
```

View File

@@ -1,190 +0,0 @@
# Home Assistant Module
This document details the Home Assistant module configuration.
## Module Structure
The Home Assistant module is organized in the following structure:
```
modules/nixos/homeassistant/
├── automations/ # Automation configurations
│ ├── lightswitch/ # Light switch automations
│ └── motion-light/ # Motion-activated light automations
├── default.nix # Main module configuration
├── options.nix # Module options definition
└── services/ # Related service configurations
├── govee2mqtt/ # Govee integration via MQTT
├── homeassistant/ # Core Home Assistant service
├── music-assistant/ # Music Assistant integration
├── thread/ # Thread border router
└── zigbee2mqtt/ # Zigbee to MQTT bridge
```
## Module Options
The module is configured through options defined in `options.nix`:
```nix
options.${namespace}.services.home-assistant = {
enable = mkEnableOption "enable home-assistant";
mosquittoPort = mkOpt types.int 1883 "Port for MQTT";
zigbee2mqttPort = mkOpt types.int 8080 "Port for zigbee2mqtt web interface";
zigbeeDevicePath = mkOpt types.str "/dev/ttyUSB0" "Path to zigbee usb device";
};
```
## Main Configuration
The main module configuration in `default.nix` includes:
1. **Activation Scripts** - For setting up custom components
2. **Service Configurations** - For Matter, PostgreSQL, etc.
3. **Firewall Rules** - For allowing required ports
```nix
config = lib.mkIf cfg.enable {
# Activation script for custom components
system.activationScripts.installCustomComponents = ''
chown -R hass:hass ${config.services.home-assistant.configDir}
chmod -R 750 ${config.services.home-assistant.configDir}
'';
# Service configurations
services = {
matter-server.enable = true;
postgresql = {
enable = false;
ensureDatabases = [ "hass" ];
ensureUsers = [
{
name = "hass";
ensureDBOwnership = true;
}
];
};
};
# Firewall rules
networking.firewall.allowedTCPPorts = [
cfg.mosquittoPort
cfg.zigbee2mqttPort
8095 # music-assistant
8097 # home-assistant
5580 # matter-server
];
};
```
## Home Assistant Service
The core Home Assistant service configuration in `services/homeassistant/default.nix` includes:
1. **Package Selection** - Using the standard Home Assistant package
2. **Component Configuration** - Enabling required components
3. **Custom Components** - Adding custom components from packages
4. **Lovelace Modules** - Adding custom UI components
5. **Integration Configuration** - Setting up integrations with other systems
```nix
services.home-assistant = {
enable = true;
package = pkgs.home-assistant;
openFirewall = true;
configDir = "/var/lib/homeassistant";
configWritable = true;
# Components
extraComponents = [
"mqtt"
"zha"
"homekit"
# ... many more components
];
# Custom components
customComponents = [
# ... custom components
];
# Lovelace modules
customLovelaceModules = [
# ... custom UI modules
];
# Configuration
config = {
# ... Home Assistant configuration
};
};
```
## Related Services
### Zigbee2MQTT
The Zigbee2MQTT service in `services/zigbee2mqtt/default.nix` connects Zigbee devices to MQTT:
```nix
services.zigbee2mqtt = {
enable = true;
settings = {
mqtt = {
server = "mqtt://localhost:${toString cfg.mosquittoPort}";
};
serial = {
port = cfg.zigbeeDevicePath;
};
# ... additional settings
};
};
```
### MQTT
MQTT is configured as a dependency for the Home Assistant module.
### Thread Border Router
The Thread Border Router in `services/thread/default.nix` provides Thread network connectivity for Matter devices.
## Automations
The module includes predefined automations in the `automations/` directory:
1. **Light Switch Automations** - For controlling lights via physical switches
2. **Motion Light Automations** - For motion-activated lighting
## Using the Module
To use this module in a system configuration:
```nix
{ config, ... }:
{
mjallen.services.home-assistant = {
enable = true;
# Optional: customize ports and device paths
mosquittoPort = 1883;
zigbee2mqttPort = 8080;
zigbeeDevicePath = "/dev/ttyUSB0";
};
}
```
## Extending the Module
### Adding Custom Components
To add a custom component:
1. Add the package to `packages/`
2. Add it to the `customComponents` list in `services/homeassistant/default.nix`
### Adding Custom Automations
To add a custom automation:
1. Create a new directory in `automations/`
2. Implement the automation in `default.nix`
3. Import it in the system configuration

View File

@@ -1,90 +0,0 @@
# Services
All services are derived from `lib.mjallen.network` (`lib/network/default.nix`).
Domain: `mjallen.dev`
Services are grouped by host. The **URL** column is only present when a reverse proxy
is configured (i.e. `reverseProxy.enable = true`) or a well-known public URL exists.
Services without a public URL are accessible only on the LAN or internally.
---
## NAS (`jallen-nas` — `10.0.1.3`)
| Service | Enabled | Port | URL |
|---------|---------|------|-----|
| actual | No | 3333 | https://actual.mjallen.dev |
| ai (Ollama + llama.cpp + Open-WebUI) | Yes | 8127 / 11434 / various | https://chat.mjallen.dev |
| arrs (Sonarr + Radarr + SABnzbd) | Yes | 8989 / 7878 / 8280 | — |
| attic | Yes | 9012 | https://cache.mjallen.dev |
| authentik | Yes | 9000 | https://authentik.mjallen.dev |
| authentikRac | Yes | 4823 | — |
| caddy | Yes | 80 / 443 | — |
| calibre | No | 8084 | https://calibre.mjallen.dev |
| calibre-web | No | 8083 | https://calibre-web.mjallen.dev |
| cockpit | Yes | 9091 | — |
| code-server | Yes | 4444 | https://code.mjallen.dev |
| collabora | Yes | 9980 | https://office.mjallen.dev |
| coturn | Yes | 3478 | — |
| crowdsec | Yes | 8181 | — |
| dispatcharr | No | 9191 | https://dispatcharr.mjallen.dev |
| free-games-claimer | No | 6080 | — |
| gitea | Yes | 3000 / SSH 2222 | https://gitea.mjallen.dev |
| glance | Yes | 5555 | https://glance.mjallen.dev |
| glances | Yes | 61208 | https://glances.mjallen.dev |
| grafana | Yes | 9999 | https://grafana.mjallen.dev |
| grimmory | No | 6066 | https://grimmory.mjallen.dev |
| guacd | Yes | 4822 | — |
| headscale | No | 2112 | https://headscale.mjallen.dev |
| immich | Yes | 2283 | https://immich.mjallen.dev |
| jellyfin | Yes | 8096 | https://jellyfin.mjallen.dev |
| seerr | Yes | 5055 | https://seerr.mjallen.dev |
| kavita | Yes | 5000 | — |
| lemonade | No | 8001 | — |
| lubelogger | Yes | 6754 | https://lubelogger.mjallen.dev |
| manyfold | Yes | 3214 | — |
| matrix | Yes | 8448 | https://matrix.mjallen.dev |
| minecraft | No | 25565 | — |
| mongodb | No | 27017 | — |
| nebula | Yes | 4242 | — |
| netbootxyz | No | 4000 | https://netbootxyz.mjallen.dev |
| nextcloud | Yes | 9988 | https://cloud.mjallen.dev |
| ntfy | Yes | 2586 | https://ntfy.mjallen.dev |
| ocis | No | 9200 | — |
| onlyoffice | No | 9943 | — |
| opencloud | No | 9200 | — |
| orca-slicer | No | 3100 | https://orca-slicer.mjallen.dev |
| paperless | Yes | 28981 | — |
| paperless-ai | Yes | 28982 | — |
| protonmail-bridge | Yes | SMTP 1025 / IMAP 1143 | — |
| restic-server | Yes | 8008 | — |
| sparky-fitness (frontend) | Yes | 3004 | https://sparky.mjallen.dev |
| sparky-fitness-server (backend) | Yes | 3010 | — |
| sunshine | Yes | 47989 | — |
| tdarr | No | 8265 / 8266 | https://tdarr.mjallen.dev |
| termix | Yes | 7777 | https://termix.mjallen.dev |
| tunarr | Yes | 8000 | https://tunarr.mjallen.dev |
| unmanic | Yes | 8265 | https://unmanic.mjallen.dev |
| uptime-kuma | Yes | 3001 | — |
| wyoming (Whisper + Piper) | Yes | 10300 / 10200 | — |
---
## NUC (`nuc-nixos` — `10.0.1.4`)
| Service | Enabled | Port | URL |
|---------|---------|------|-----|
| home-assistant | Yes | 8123 | https://hass.mjallen.dev |
| esphome | Yes | 6052 | — |
| otbr (OpenThread Border Router) | Yes | 8880 / REST 8881 | — |
| mosquitto (MQTT) | Yes | 1883 | — |
---
## Pi5 (`pi5` — `10.0.1.2`)
| Service | Enabled | Port | URL |
|---------|---------|------|-----|
| adguard | Yes | 3000 | — |
| nebula (lighthouse) | Yes | 4242 | — |
| dns | Yes | 53 | — |

View File

@@ -1,37 +0,0 @@
# System Configurations
This directory contains documentation for each system configuration in this repository.
## Systems
| Host | Architecture | OS | Role |
|---|---|---|---|
| [matt-nixos](./matt-nixos.md) | x86_64-linux | NixOS | Primary AMD desktop |
| [jallen-nas](./jallen-nas.md) | x86_64-linux | NixOS | Home server / NAS |
| [nuc-nixos](./nuc-nixos.md) | x86_64-linux | NixOS | Intel NUC — Home Assistant hub |
| [allyx](./allyx.md) | x86_64-linux | NixOS | ASUS ROG Ally X handheld |
| [pi5](./pi5.md) | aarch64-linux | NixOS | Raspberry Pi 5 — network services |
| [macbook-pro-nixos](./macbook-pro-nixos.md) | aarch64-linux | NixOS (Asahi) | Apple Silicon MacBook Pro |
| [macbook-pro](./macbook-pro.md) | aarch64-darwin | nix-darwin | macOS on the same MacBook Pro |
There are also two ISO targets (`x86_64-install-iso/graphical`, `x86_64-linux/iso-minimal`) used for installation media builds.
## Network
All hosts are on the `10.0.1.0/24` LAN with static IPs:
| Host | LAN IP | Overlay (Nebula) |
|---|---|---|
| pi5 | 10.0.1.2 | 10.1.1.1 (lighthouse) |
| jallen-nas | 10.0.1.3 | 10.1.1.x (node) |
| nuc-nixos | 10.0.1.4 | — |
## Common Configuration
All systems share:
- SOPS secret management (age keys from SSH host keys)
- Impermanence (ephemeral root, explicit persistence)
- Nix flake-based configuration via Snowfall Lib
- The `mjallen` module namespace
Each system then layers its own modules and hardware configuration on top.

View File

@@ -1,57 +0,0 @@
# ASUS ROG Ally X (allyx)
`systems/x86_64-linux/allyx/`
## Hardware
- **Device**: ASUS ROG Ally X handheld gaming PC
- **CPU/GPU**: AMD (LACT, CoolerControl)
- **Disk**: NVMe with LUKS encryption
- **Security**: Lanzaboote (Secure Boot)
## Key Features
- Jovian NixOS for Steam Deck-compatible experience
- Steam auto-starts into Game Mode on boot
- Decky Loader for Steam Deck plugins
- Handheld Daemon for power/TDP/fan control
- GNOME available as a desktop session (selectable from Steam)
- SDDM (Wayland) as display manager — GDM disabled
- Gaming enabled (Gamemode, Gamescope, etc.)
- AMD GPU management via LACT
- CoolerControl for fan curves
- iwd as the Wi-Fi backend
- Impermanence (ephemeral root)
## Jovian NixOS
The allyx uses [Jovian NixOS](https://github.com/Jovian-Experiments/Jovian-NixOS) to provide Steam Deck compatibility:
```nix
jovian.steam = {
enable = true;
autoStart = true;
desktopSession = "gnome"; # fall-through desktop session
};
jovian.decky-loader = {
enable = true;
extraPackages = [ pkgs.python3 pkgs.systemd ];
};
```
## Network
- **Hostname**: allyx
- **Wi-Fi backend**: iwd (via NetworkManager)
## Configuration Files
| File | Purpose |
|---|---|
| `default.nix` | Main config — Jovian, gaming, hardware |
| `boot.nix` | Lanzaboote, kernel |
## Secrets
Secrets are in `secrets/allyx-secrets.yaml`, encrypted for: `matt`, `desktop`, `deck`, `steamdeck`, `admin`, `jallen-nas`, `matt_allyx`, `allyx`.

View File

@@ -1,104 +0,0 @@
# NAS Server (jallen-nas)
`systems/x86_64-linux/jallen-nas/`
## Hardware
- **CPU**: AMD (x86_64)
- **GPU**: AMD (LACT for fan/power control)
- **Disk**: NVMe system drive + bcachefs NAS pool
- **Security**: TPM2 (Clevis disk unlock), Lanzaboote (Secure Boot)
## Key Features
- bcachefs storage pool mounted at `/media/nas/main`
- Clevis-based TPM disk unlock at boot (no passphrase required)
- Impermanence — root is ephemeral; state persists to `/media/nas/main/persist`
- Samba shares (Windows file sharing, Time Machine)
- Nebula VPN node (overlay peer, lighthouse at pi5)
- ~40 self-hosted services behind a Caddy reverse proxy
- Authentik SSO protecting most web UIs
- CrowdSec for intrusion detection
- Restic backups
## Network
- **LAN IP**: 10.0.1.3 (static, `enp197s0`)
- **Gateway**: 10.0.1.1
- **Nebula**: overlay peer, lighthouse at `mjallen.dev:4242`
## Storage
| Mount | Filesystem | Description |
|---|---|---|
| `/media/nas/main` | bcachefs | Primary NAS pool (media, appdata, documents) |
| `/media/nas/test` | bcachefs | Secondary test pool |
### Samba Shares
| Share | Time Machine |
|---|---|
| `3d_printer` | no |
| `Backup` | no |
| `Documents` | no |
| `isos` | no |
| `app_data` | no |
| `TimeMachine` | yes (max 1 TB) |
## Enabled Services
| Service | Port | Notes |
|---|---|---|
| Caddy | 443/80 | Reverse proxy for all services |
| Authentik | 9000 | SSO / identity provider |
| Attic | 9012 | Nix binary cache (`cache.mjallen.dev`) |
| Immich | 2283 | Photo management |
| Jellyfin | 8096 | Media server |
| Seerr | 5055 | Media request manager |
| Nextcloud | 9988 | Cloud storage |
| Paperless | 28981 | Document management |
| Paperless AI | 28982 | AI-assisted document tagging |
| Gitea | 3000 | Self-hosted Git |
| Matrix | 8448 | Matrix homeserver |
| Ntfy | 2586 | Push notifications |
| Glance | 5555 | Dashboard |
| Immich | 2283 | Photo library |
| Uptime Kuma | 3001 | Uptime monitoring |
| Code Server | 4444 | VS Code in the browser |
| Cockpit | 9090 | System management UI |
| Collabora | 9980 | Online office suite |
| CrowdSec | 8181 | Intrusion detection |
| Glances | 61208 | System stats |
| Coturn | 3478 | TURN/STUN server |
| Nebula | 4242 | Overlay VPN node |
| Restic | 8008 | Backup service |
| Sunshine | 47989 | Remote desktop (Moonlight) |
| Unmanic | 8265 | Media transcoding |
| Lubelogger | 6754 | Vehicle maintenance log |
| Manyfold | 3214 | 3D model library |
| Booklore | 6066 | Book library |
| Tunarr | 8000 | Virtual TV channels |
| Termix | 7777 | Web terminal |
| Sparky Fitness | 3004/3010 | Fitness tracking |
| Protonmail Bridge | 1025/1143 | SMTP/IMAP bridge |
| Arrs | various | Sonarr, Radarr, etc. |
| AI | various | Ollama, etc. |
| Wyoming | various | Voice assistant pipeline |
## Configuration Files
| File | Purpose |
|---|---|
| `default.nix` | Main config — network, hardware, filesystems, packages |
| `apps.nix` | All service enable/disable declarations |
| `nas-defaults.nix` | Sets `configDir`/`dataDir` defaults for all services |
| `boot.nix` | Lanzaboote, kernel, initrd |
| `services.nix` | Home Assistant, samba, and other platform services |
| `users.nix` | User accounts (`admin`, `nix-apps`) |
| `sops.nix` | Secret declarations |
| `vpn.nix` | Nebula VPN configuration |
| `disabled.nix` | Services explicitly disabled |
## Secrets
Secrets are in `secrets/nas-secrets.yaml`, encrypted for: `matt`, `desktop`, `admin`, `jallen-nas`.

View File

@@ -1,69 +0,0 @@
# MacBook Pro — NixOS / Asahi Linux (macbook-pro-nixos)
`systems/aarch64-linux/macbook-pro-nixos/`
## Hardware
- **Device**: Apple Silicon MacBook Pro (M-series)
- **OS**: NixOS via [Asahi Linux](https://asahilinux.org/) (`nixos-apple-silicon`)
- **Boot**: Asahi boot chain (not traditional EFI)
## Key Features
- Asahi Linux kernel with full Apple Silicon support (sound, GPU, etc.)
- GNOME as the primary desktop; Hyprland available but disabled
- x86_64 emulation via binfmt (enables running x86 binaries)
- Waydroid and libvirtd available (Waydroid disabled by default)
- Battery management — charge threshold set via `macsmc-battery`
- Omnissa Horizon client (custom package) for remote desktop
- Distrobox for containerised Linux environments
- iwd as the Wi-Fi backend
## x86_64 Emulation
```nix
nix.settings.extra-platforms = [ "x86_64-linux" ];
boot.binfmt.emulatedSystems = [ "x86_64-linux" ];
```
This allows building and running x86_64 packages on the ARM host.
## Asahi Hardware
The Asahi hardware module provides:
- Firmware loading from `./firmware/`
- Sound setup (`setupAsahiSound = true`)
- Apple-specific kernel patches and device drivers
Useful packages installed:
`asahi-bless`, `asahi-btsync`, `asahi-nvram`, `asahi-wifisync`, `apfs-fuse`, `apfsprogs`, `muvm`, `fex`
## Network
- **Hostname**: macbook-pro-nixos
- **Wi-Fi backend**: iwd (via NetworkManager)
- Firewall: extra rules for multicast (ports 1990, 2021)
## Battery Management
```nix
mjallen.hardware.battery = {
enable = true;
chargeLimitPath = "/sys/class/power_supply/macsmc-battery/charge_control_end_threshold";
};
```
## Configuration Files
| File | Purpose |
|---|---|
| `default.nix` | Main config — Asahi hardware, users, network |
| `boot.nix` | Asahi boot configuration |
| `filesystems.nix` | Disk layout |
| `hardware-configuration.nix` | Generated hardware config |
| `services.nix` | logind, GDM, GNOME, Flatpak, power settings |
| `firmware/` | Asahi firmware blobs |
## Secrets
Secrets are in `secrets/mac-secrets.yaml`, encrypted for: `matt`, `matt_pi5`, `desktop`, `pi5`, `admin`, `jallen-nas`, `matt_macbook-pro`, `macbook-pro`.

View File

@@ -1,40 +0,0 @@
# MacBook Pro — macOS / nix-darwin (macbook-pro)
`systems/aarch64-darwin/macbook-pro/`
## Overview
This is the [nix-darwin](https://github.com/nix-darwin/nix-darwin) configuration for the same MacBook Pro running macOS. It provides declarative macOS system management alongside Homebrew.
## Key Features
- Touch ID for `sudo`
- Declarative Homebrew (casks and formulae managed via `nix-homebrew`)
- `nh` for easy NixOS/darwin rebuilds
- `attic-client` for accessing the Nix binary cache
- `macpm` for Apple Silicon power monitoring
- Rosetta builder available (disabled, on-demand)
- Linux builder available (disabled)
## Configuration Files
| File | Purpose |
|---|---|
| `default.nix` | Main config — packages, users, environment |
| `homebrew.nix` | Declarative Homebrew casks and formulae |
| `programs.nix` | macOS program settings |
| `system.nix` | System defaults (dock, finder, etc.) |
## User
- **Username**: `mattjallen`
- **Home**: `/Users/mattjallen`
- **Flake path**: `/Users/mattjallen/nix-config` (set via `NH_OS_FLAKE`)
## Rebuilding
```bash
darwin-rebuild switch --flake .#macbook-pro
# or using nh:
nh darwin switch
```

View File

@@ -1,50 +0,0 @@
# Desktop (matt-nixos)
`systems/x86_64-linux/matt-nixos/`
## Hardware
- **CPU**: AMD
- **GPU**: AMD (LACT for fan/power control, OpenRGB)
- **Disk**: NVMe with LUKS encryption (disko)
- **Security**: TPM2, Lanzaboote (Secure Boot)
## Key Features
- GNOME as the primary desktop (Hyprland available but disabled)
- COSMIC available as a specialisation (`nixos-rebuild switch --specialisation cosmic`)
- Gaming — Steam, Gamemode, Gamescope, Lossless Scaling (`lsfg-vk`)
- AMD GPU management via LACT
- CoolerControl for fan curves
- Impermanence (ephemeral root)
- iwd as the Wi-Fi backend
- VSCodium as `$EDITOR`/`$VISUAL`
## Desktop Specialisations
| Specialisation | Description |
|---|---|
| *(default)* | GNOME |
| `cosmic` | COSMIC DE (enables `mjallen.desktop.cosmic`, disables GNOME/Hyprland) |
## Network
- **Hostname**: matt-nixos
- **Wi-Fi backend**: iwd (via NetworkManager)
## Configuration Files
| File | Purpose |
|---|---|
| `default.nix` | Main config |
| `boot.nix` | Lanzaboote, kernel |
| `filesystems.nix` | Disk layout |
| `sops.nix` | Secret declarations |
| `wifi-fixer.nix` | NetworkManager Wi-Fi workaround |
| `services/lsfg-vk/` | Lossless Scaling frame generation |
| `services/ratbagd/` | Gaming mouse config (libratbag) |
| `services/restic/` | Restic backup jobs |
## Secrets
Secrets are in `secrets/desktop-secrets.yaml`, encrypted for: `matt`, `desktop`, `admin`, `jallen-nas`.

View File

@@ -1,57 +0,0 @@
# Intel NUC (nuc-nixos)
`systems/x86_64-linux/nuc-nixos/`
## Hardware
- **Device**: Intel NUC
- **Disk**: btrfs with LUKS encryption
- **Security**: TPM2, Lanzaboote (Secure Boot)
- **Kernel**: CachyOS `linux-cachyos-lto` (x86_64-v4 build)
## Key Features
- Headless server (no display manager, watchdog enabled)
- Home Assistant — the primary smart home controller
- OpenThread Border Router (OTBR) for Matter/Thread devices
- Impermanence (ephemeral root, persistent state for HA and related services)
- btrfs filesystem (unlike the bcachefs-based NAS and Pi5)
## Network
- **LAN IP**: 10.0.1.4 (static, `enp2s0`)
- **Gateway / DNS**: 10.0.1.1
- **Firewall**: 1883 (MQTT), 8880/8881 (OTBR), 8192
## Services
| Service | Port | Description |
|---|---|---|
| Home Assistant | 8097 | Smart home controller |
| Mosquitto (MQTT) | 1883 | IoT message broker |
| Zigbee2MQTT | 8080 | Zigbee device bridge |
| Music Assistant | 8095 | Music streaming |
| OTBR | 8880/8881 | OpenThread Border Router (Matter/Thread) |
| ESPHome | — | ESP microcontroller firmware |
| PostgreSQL | — | HA database backend |
## Persistent Directories
The following directories survive reboots via impermanence:
- `/esphome`
- `/var/lib/homeassistant`
- `/var/lib/mosquitto`
- `/var/lib/music-assistant`
- `/var/lib/postgresql`
- `/var/lib/zigbee2mqtt`
## Configuration Files
| File | Purpose |
|---|---|
| `default.nix` | All config in one file — HA, OTBR, network, hardware, impermanence |
## Secrets
Secrets are in `secrets/nuc-secrets.yaml`, encrypted for: `nuc`, `admin_nuc`, `matt`, `admin`, `jallen-nas`.

View File

@@ -1,62 +0,0 @@
# Raspberry Pi 5 (pi5)
`systems/aarch64-linux/pi5/`
## Hardware
- **Board**: Raspberry Pi 5
- **Boot**: UEFI (via `rpi5-uefi`)
- **Storage**: bcachefs
- **Connectivity**: Ethernet (`end0`); Wi-Fi and Bluetooth disabled via device tree overlays
## Key Features
- Headless server (no display, no desktop)
- Nebula VPN **lighthouse** — the central relay for the `jallen-nebula` overlay network
- AdGuard Home DNS server (port 53)
- Docker
- Impermanence (ephemeral root)
- Extensive Raspberry Pi device tree overlays configured (I²C, SPI, UART, SDIO, etc.)
## Network
- **LAN IP**: 10.0.1.2 (static, `end0`)
- **Gateway**: 10.0.1.1
- **DNS**: 1.1.1.1
- **Nebula**: lighthouse at `10.1.1.1`, listening on UDP 4242 (public: `mjallen.dev:4242`)
- Firewall: TCP/UDP 53 open (DNS)
## Nebula Lighthouse
The pi5 acts as the Nebula VPN lighthouse for the whole network. All other Nebula nodes connect to it to discover peers.
```nix
mjallen.services.nebula = {
enable = true;
isLighthouse = true;
port = 4242;
secretsPrefix = "pi5/nebula";
secretsFile = lib.snowfall.fs.get-file "secrets/pi5-secrets.yaml";
hostSecretName = "lighthouse";
};
```
## Services
| Service | Port | Description |
|---|---|---|
| AdGuard Home | 53 | DNS ad-blocking |
| Nebula | 4242 (UDP) | VPN lighthouse |
## Configuration Files
| File | Purpose |
|---|---|
| `default.nix` | Main config |
| `boot.nix` | UEFI boot, kernel |
| `adguard.nix` | AdGuard Home configuration |
| `sops.nix` | Secret declarations (SSH keys, system keys) |
## Secrets
Secrets are in `secrets/pi5-secrets.yaml`, encrypted for: `matt`, `matt_pi5`, `desktop`, `pi5`, `admin`, `jallen-nas`.

View File

@@ -1,217 +0,0 @@
# Troubleshooting Guide
Common issues and solutions for this NixOS configuration.
## Build Failures
### `nixos-rebuild switch` fails
1. **Syntax error** — the error message includes the file and line number. Common causes: missing `;`, unmatched `{`, wrong type passed to an option.
2. **Evaluation error** — read the full error trace. Often caused by a module option receiving the wrong type, or a missing `cfg.enable` guard.
3. **Fetch failure** — a flake input or package source can't be downloaded. Check network connectivity, or try:
```bash
nix flake update --update-input <input-name>
```
4. **Disk space** — build sandbox fills up. Free space:
```bash
sudo nix-collect-garbage -d
df -h /nix
```
### Assertion failures
If you see `assertion failed`, read the `message` field. For example:
```
error: assertion failed at …/nebula/sops.nix
mjallen.services.nebula.secretsPrefix must be set
```
Set the required option in the system configuration.
## Boot Issues
### System won't boot after a config change
1. At the boot menu, select a previous generation.
2. Once booted, revert the change:
```bash
cd /etc/nixos
git revert HEAD
sudo nixos-rebuild switch --flake .#$(hostname)
```
### Booting from installation media to recover
```bash
# Mount the system (adjust device paths as needed)
sudo mount /dev/disk/by-label/nixos /mnt
sudo mount /dev/disk/by-label/boot /mnt/boot
# Chroot in
sudo nixos-enter --root /mnt
cd /etc/nixos
# Revert and rebuild
git revert HEAD
nixos-rebuild switch --flake .#hostname --install-bootloader
```
### Lanzaboote / Secure Boot issues
If Secure Boot enrolment fails or the system won't verify:
```bash
# Check enrolled keys
sbctl status
# Re-enrol if needed (run as root)
sbctl enrol-keys --microsoft
# Sign bootloader files manually
sbctl sign -s /boot/EFI/systemd/systemd-bootx64.efi
```
## SOPS / Secrets Issues
### `secret not found` or permission denied at boot
1. Verify the secret key path matches what's declared in the module's `sops.nix`.
2. Check the secret exists in the SOPS file:
```bash
sops --decrypt secrets/nas-secrets.yaml | grep "the-key"
```
3. Check the `owner`/`group` set on the secret matches the service user.
### Can't decrypt — wrong age key
The machine's age key is derived from `/etc/ssh/ssh_host_ed25519_key`. If the host key was regenerated, the age key changed and existing secrets can no longer be decrypted.
To fix: re-encrypt the secrets file with the new public key:
```bash
# Get the new public key
nix-shell -p ssh-to-age --run 'ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub'
# Update .sops.yaml with the new key, then:
sops updatekeys secrets/nas-secrets.yaml
```
### Adding a new secret to an existing file
```bash
sops secrets/nas-secrets.yaml
# Editor opens with decrypted YAML — add your key, save, sops re-encrypts
```
## Nebula VPN Issues
### Peers can't connect
1. Verify the lighthouse is reachable on its public address:
```bash
nc -zvu mjallen.dev 4242
```
2. Check the nebula service on both hosts:
```bash
systemctl status nebula@jallen-nebula
journalctl -u nebula@jallen-nebula -n 50
```
3. Confirm the CA cert, host cert, and host key are all present and owned by the `nebula-jallen-nebula` user:
```bash
ls -la /run/secrets/pi5/nebula/
```
4. Verify the host cert was signed by the same CA as the other nodes:
```bash
nebula-cert verify -ca ca.crt -crt host.crt
```
### Certificate expired
Re-sign the host certificate:
```bash
nebula-cert sign -name "hostname" -ip "10.1.1.x/24" \
-ca-crt ca.crt -ca-key ca.key \
-out-crt host.crt -out-key host.key
# Update SOPS, rebuild
```
## Impermanence Issues
### Service fails because its data directory is missing after reboot
If a service stores state in a path that isn't in the persistence list, it will be wiped on reboot. Add it to `impermanence.extraDirectories`:
```nix
mjallen.impermanence.extraDirectories = [
{ directory = "/var/lib/my-service"; user = "my-service"; group = "my-service"; mode = "0750"; }
];
```
Then move the existing data if needed:
```bash
cp -a /var/lib/my-service /persist/var/lib/my-service
```
## Flake Input Issues
### Input update breaks a build
Roll back the specific input:
```bash
git checkout HEAD^ -- flake.lock
```
Or pin the input to a specific revision in `flake.nix`:
```nix
nixpkgs-unstable.url = "github:NixOS/nixpkgs/abc123def";
```
## Service Issues
### Service won't start
```bash
systemctl status <service>
journalctl -u <service> -n 100 --no-pager
```
### Caddy reverse proxy not routing
1. Check that `reverseProxy.enable = true` is set on the service.
2. Verify the subdomain matches: `reverseProxy.subdomain = "myapp"` → `myapp.mjallen.dev`.
3. Check Caddy logs:
```bash
journalctl -u caddy -n 50
```
### PostgreSQL database missing for a service
If `configureDb = true` is set, the database is created automatically. If it's missing:
```bash
sudo -u postgres createdb my-service
sudo -u postgres psql -c "GRANT ALL ON DATABASE my-service TO my-service;"
```
## Network Issues
### Firewall blocking a service
Check which ports are open:
```bash
sudo nft list ruleset | grep accept
```
Add ports in the system config:
```nix
mjallen.network.firewall.allowedTCPPorts = [ 8080 ];
```
Or if using `mkModule`, set `openFirewall = true` (it's the default).
## Getting Help
- NixOS manual: `nixos-help` or https://nixos.org/manual/nixos/stable/
- NixOS Wiki: https://nixos.wiki/
- NixOS Discourse: https://discourse.nixos.org/
- Nix package search: https://search.nixos.org/packages

View File

@@ -1,210 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://example.invalid/version.schema.json",
"title": "Unified Package Version Schema",
"description": "Schema for a unified version.json used by packages/",
"type": "object",
"additionalProperties": false,
"required": [
"schemaVersion",
"sources"
],
"properties": {
"schemaVersion": {
"type": "integer",
"enum": [1],
"description": "Schema version. Start at 1; bump on breaking changes."
},
"variables": {
"type": "object",
"description": "Common variables available for template substitution in string fields.",
"additionalProperties": {
"type": "string"
}
},
"defaultVariant": {
"type": "string",
"description": "Optional default variant name for consumers."
},
"sources": {
"type": "object",
"description": "Base component sources keyed by component name.",
"minProperties": 1,
"additionalProperties": {
"$ref": "#/$defs/SourceSpec"
}
},
"variants": {
"type": "object",
"description": "Optional variants/channels/flavors; each overlays the base.",
"additionalProperties": {
"$ref": "#/$defs/VariantSpec"
}
},
"notes": {
"type": "object",
"description": "Optional free-form human notes/documentation.",
"additionalProperties": true
}
},
"$defs": {
"SourceSpecBase": {
"type": "object",
"additionalProperties": false,
"properties": {
"fetcher": {
"type": "string",
"enum": ["github", "git", "url", "pypi", "none"],
"description": "Fetcher type for this source."
},
"hash": {
"type": "string",
"pattern": "^sha[0-9]+-",
"description": "SRI hash for the fetched artifact. Required unless fetcher is 'none'."
},
"version": {
"type": "string",
"description": "Optional version string metadata for this component."
},
"extra": {
"type": "object",
"description": "Optional free-form metadata for consumer logic.",
"additionalProperties": true
},
"owner": { "type": "string", "description": "GitHub owner/org (github fetcher)." },
"repo": { "type": "string", "description": "GitHub repository (github fetcher)." },
"tag": { "type": "string", "description": "Git tag (github fetcher). Mutually exclusive with 'rev'." },
"rev": { "type": "string", "description": "Commit revision (github/git fetchers)." },
"branch": { "type": "string", "description": "Branch to track for HEAD-commit updates (github/git fetchers). Stored alongside 'rev' to record which branch the pinned commit came from. Has no effect on the Nix fetcher itself — only used by the version management tooling." },
"submodules": { "type": "boolean", "description": "Whether to fetch submodules (github/git fetchers)." },
"url": { "type": "string", "description": "Final URL (url fetcher). May be templated." },
"urlTemplate": { "type": "string", "description": "Template for URL (url fetcher); supports ${var}." },
"name": { "type": "string", "description": "PyPI dist name (pypi fetcher)." }
}
},
"SourceSpec": {
"allOf": [
{ "$ref": "#/$defs/SourceSpecBase" },
{
"if": {
"properties": { "fetcher": { "const": "github" } },
"required": ["fetcher"]
},
"then": {
"required": ["owner", "repo"],
"oneOf": [
{ "required": ["tag"] },
{ "required": ["rev"] }
]
}
},
{
"if": {
"properties": { "fetcher": { "const": "git" } },
"required": ["fetcher"]
},
"then": {
"required": ["url", "rev"]
}
},
{
"if": {
"properties": { "fetcher": { "const": "url" } },
"required": ["fetcher"]
},
"then": {
"oneOf": [
{ "required": ["url"] },
{ "required": ["urlTemplate"] }
]
}
},
{
"if": {
"properties": { "fetcher": { "const": "pypi" } },
"required": ["fetcher"]
},
"then": {
"required": ["name", "version"]
}
},
{
"if": {
"properties": { "fetcher": { "enum": ["github", "git", "url", "pypi"] } },
"required": ["fetcher"]
},
"then": {
"required": ["hash"]
}
}
]
},
"SourceOverride": {
"type": "object",
"additionalProperties": false,
"description": "Partial override of a source within a variant. All fields optional.",
"properties": {
"fetcher": { "type": "string", "enum": ["github", "git", "url", "pypi", "none"] },
"hash": { "type": "string", "pattern": "^sha[0-9]+-" },
"version": { "type": "string" },
"extra": { "type": "object", "additionalProperties": true },
"owner": { "type": "string" },
"repo": { "type": "string" },
"tag": { "type": "string" },
"rev": { "type": "string" },
"branch": { "type": "string" },
"submodules": { "type": "boolean" },
"url": { "type": "string" },
"urlTemplate": { "type": "string" },
"name": { "type": "string" }
}
},
"VariantSpec": {
"type": "object",
"additionalProperties": false,
"properties": {
"inherits": {
"type": "string",
"description": "Optional base variant to inherit from."
},
"variables": {
"type": "object",
"description": "Variant-level variables that overlay top-level variables.",
"additionalProperties": { "type": "string" }
},
"sources": {
"type": "object",
"description": "Per-component overrides for this variant.",
"additionalProperties": { "$ref": "#/$defs/SourceOverride" }
},
"platforms": {
"type": "object",
"description": "Optional per-system overrides to support differing hashes/fields by platform.",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"properties": {
"sources": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/SourceOverride" }
},
"variables": {
"type": "object",
"additionalProperties": { "type": "string" }
}
}
}
}
}
}
}
}

1313
flake.lock generated Executable file → Normal file

File diff suppressed because it is too large Load Diff

268
flake.nix Executable file → Normal file
View File

@@ -1,54 +1,45 @@
{
inputs = rec {
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.11";
# Fork required: openthread-border-router is not yet in nixpkgs-unstable.
# Used by modules/nixos/homeassistant/services/thread/default.nix
nixpkgs-otbr.url = "github:mrene/nixpkgs/openthread-border-router";
home-manager-unstable = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
nixpkgs = nixpkgs-unstable;
home-manager = home-manager-unstable;
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.05";
# The name "snowfall-lib" is required due to how Snowfall Lib processes your
# flake's inputs. Using a personal fork for custom changes.
# flake's inputs.
snowfall-lib = {
url = "github:mjallen18/snowfall-lib";
url = "github:snowfallorg/lib";
inputs.nixpkgs.follows = "nixpkgs";
};
chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
impermanence.url = "github:nix-community/impermanence";
lanzaboote.url = "github:nix-community/lanzaboote/v1.0.0";
lanzaboote.url = "github:nix-community/lanzaboote/v0.4.2";
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
sops-nix.url = "github:Mic92/sops-nix";
nix-cachyos-kernel.url = "github:xddxdd/nix-cachyos-kernel/release";
steam-rom-manager = {
url = "github:mjallen18/nix-steam-rom-manager";
inputs.nixpkgs.follows = "nixpkgs";
inputs.home-manager.follows = "home-manager";
};
cosmic = {
url = "github:lilyinstarlight/nixos-cosmic";
inputs.nixpkgs.follows = "nixpkgs";
};
cosmic.url = "github:lilyinstarlight/nixos-cosmic";
nix-vscode-extensions.url = "github:nix-community/nix-vscode-extensions";
authentik-nix = {
url = "github:nix-community/authentik-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
authentik-nix.url = "github:nix-community/authentik-nix";
crowdsec.url = "git+https://codeberg.org/kampka/nix-flake-crowdsec.git";
nixai.url = "github:olafkfreund/nix-ai-help";
disko = {
# the fork is needed for partition attributes support
@@ -57,7 +48,11 @@
inputs.nixpkgs.follows = "nixpkgs";
};
darwin.url = "github:nix-darwin/nix-darwin/master";
nixos-raspberrypi.url = "github:mjallen18/nixos-raspberrypi";
jovian.url = "github:Jovian-Experiments/Jovian-NixOS";
darwin.url = "github:LnL7/nix-darwin";
nix-homebrew.url = "github:zhaofengli/nix-homebrew";
@@ -73,10 +68,7 @@
nixos-apple-silicon.url = "github:nix-community/nixos-apple-silicon";
pre-commit-hooks-nix = {
url = "github:cachix/pre-commit-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
pre-commit-hooks-nix.url = "github:cachix/pre-commit-hooks.nix";
treefmt-nix = {
url = "github:numtide/treefmt-nix";
@@ -91,37 +83,9 @@
};
lsfg-vk = {
url = "github:pabloaul/lsfg-vk-flake";
url = "github:mjallen18/lsfg-vk-flake/main";
inputs.nixpkgs.follows = "nixpkgs";
};
nix-plist-manager.url = "github:sushydev/nix-plist-manager";
nix-rosetta-builder = {
url = "github:cpick/nix-rosetta-builder";
inputs.nixpkgs.follows = "nixpkgs";
};
stylix = {
url = "github:nix-community/stylix";
inputs.nixpkgs.follows = "nixpkgs";
};
jovian = {
url = "github:Jovian-Experiments/Jovian-NixOS";
inputs.nixpkgs.follows = "nixpkgs";
};
llama-cpp = {
url = "github:ggml-org/llama.cpp";
inputs.nixpkgs.follows = "nixpkgs";
};
plasma-manager = {
url = "github:nix-community/plasma-manager";
inputs.nixpkgs.follows = "nixpkgs";
inputs.home-manager.follows = "home-manager";
};
};
# We will handle this in the next section.
@@ -136,73 +100,37 @@
# Nix files to a separate directory.
src = ./.;
overlays = with inputs; [
nix-vscode-extensions.overlays.default
nix-cachyos-kernel.overlays.default
# writeShellApplication uses lib.toShellVar which generates unquoted
# variable assignments for simple strings (e.g. username=admin).
# shellcheck SC2209 flags this as a warning, breaking the build when
# the value matches a command name. Exclude SC2209 globally.
(_final: prev: {
writeShellApplication =
args:
prev.writeShellApplication (
args
// {
excludeShellChecks = (args.excludeShellChecks or [ ]) ++ [ "SC2209" ];
}
);
})
];
# Add a module to a specific host.
systems = {
# common modules
modules = {
nixos = with inputs; [
authentik-nix.nixosModules.default
disko.nixosModules.disko
impermanence.nixosModules.impermanence
lanzaboote.nixosModules.lanzaboote
sops-nix.nixosModules.sops
home-manager.nixosModules.home-manager
nix-index-database.nixosModules.nix-index
stylix.nixosModules.stylix
];
modules.nixos = with inputs; [
authentik-nix.nixosModules.default
chaotic.nixosModules.default
crowdsec.nixosModules.crowdsec
crowdsec.nixosModules.crowdsec-firewall-bouncer
disko.nixosModules.disko
impermanence.nixosModules.impermanence
lanzaboote.nixosModules.lanzaboote
sops-nix.nixosModules.sops
home-manager.nixosModules.home-manager
];
# External HM modules injected into ALL homes — both standalone
# homeConfigurations and homes embedded in nixosConfigurations.
# The snowfall-lib fork patches create-systems to pass systems.modules.home
# into create-home-system-modules so both paths are covered from here.
# The ARM guard for steam-rom-manager is handled by that module itself.
home = with inputs; [
nix-index-database.homeModules.nix-index
steam-rom-manager.homeManagerModules.default
sops-nix.homeManagerModules.sops
stylix.homeModules.stylix
plasma-manager.homeModules.plasma-manager
];
darwin = with inputs; [
nix-homebrew.darwinModules.nix-homebrew
home-manager.darwinModules.home-manager
nix-plist-manager.darwinModules.default
nix-rosetta-builder.darwinModules.default
nix-index-database.darwinModules.nix-index
stylix.darwinModules.stylix
];
};
# common darwin modules
modules.darwin = with inputs; [
nix-homebrew.darwinModules.nix-homebrew
home-manager.darwinModules.home-manager
];
# Host config
hosts = {
# ######################################################
# Desktop #
# ######################################################
matt-nixos = {
desktop = {
modules = with inputs; [
nixos-hardware.nixosModules.common-cpu-amd
nixos-hardware.nixosModules.common-cpu-amd-pstate
# nixos-hardware.nixosModules.common-cpu-amd-zenpower
nixos-hardware.nixosModules.common-cpu-amd-zenpower
nixos-hardware.nixosModules.common-gpu-amd
nixos-hardware.nixosModules.common-hidpi
nixos-hardware.nixosModules.common-pc
@@ -213,32 +141,75 @@
# ######################################################
# NAS #
# ######################################################
jallen-nas = {
# home-manager is already in systems.modules.nixos above
nas = {
modules = with inputs; [
nixos-hardware.nixosModules.common-pc
nixos-hardware.nixosModules.common-cpu-amd
nixos-hardware.nixosModules.common-cpu-amd-pstate
# nixos-hardware.nixosModules.common-cpu-amd-zenpower
nixos-hardware.nixosModules.common-cpu-amd-zenpower
nixos-hardware.nixosModules.common-hidpi
home-manager.nixosModules.home-manager
];
# overlays = with inputs; [ crowdsec.overlays.default ];
};
# ######################################################
# Steamdeck #
# ######################################################
steamdeck = {
modules = with inputs; [
disko.nixosModules.disko
jovian.nixosModules.jovian
nixos-hardware.nixosModules.common-cpu-amd
nixos-hardware.nixosModules.common-cpu-amd-pstate
nixos-hardware.nixosModules.common-cpu-amd-zenpower
nixos-hardware.nixosModules.common-gpu-amd
nixos-hardware.nixosModules.common-hidpi
nixos-hardware.nixosModules.common-pc
lsfg-vk.nixosModules.default
];
};
# ######################################################
# NUC #
# ######################################################
nuc-nixos = {
# disko is already in systems.modules.nixos above
nuc = {
modules = with inputs; [
disko.nixosModules.disko
nixos-hardware.nixosModules.common-cpu-amd
nixos-hardware.nixosModules.common-cpu-amd-pstate
# nixos-hardware.nixosModules.common-cpu-amd-zenpower
nixos-hardware.nixosModules.common-cpu-amd-zenpower
nixos-hardware.nixosModules.common-gpu-amd
nixos-hardware.nixosModules.common-hidpi
nixos-hardware.nixosModules.common-pc
];
};
# ######################################################
# Pi4 #
# ######################################################
pi4 = {
modules = with inputs; [
disko.nixosModules.disko
nixos-raspberrypi.nixosModules.raspberry-pi-4.base
nixos-raspberrypi.nixosModules.raspberry-pi-4.display-vc4
nixos-raspberrypi.lib.inject-overlays
];
};
# ######################################################
# Pi5 #
# ######################################################
pi5 = {
modules = with inputs; [
disko.nixosModules.disko
nixos-raspberrypi.nixosModules.raspberry-pi-5.base
nixos-raspberrypi.nixosModules.raspberry-pi-5.display-vc4
nixos-raspberrypi.nixosModules.raspberry-pi-5.bluetooth
nixos-raspberrypi.lib.inject-overlays
];
};
# ######################################################
# Mac #
# ######################################################
@@ -248,21 +219,31 @@
lsfg-vk.nixosModules.default
];
};
};
# ######################################################
# AllyX #
# ######################################################
allyx = {
modules = with inputs; [
nixos-hardware.nixosModules.common-cpu-amd
nixos-hardware.nixosModules.common-cpu-amd-pstate
# nixos-hardware.nixosModules.common-cpu-amd-zenpower
nixos-hardware.nixosModules.common-gpu-amd
nixos-hardware.nixosModules.common-hidpi
nixos-hardware.nixosModules.common-pc
lsfg-vk.nixosModules.default
jovian.nixosModules.jovian
];
overlays = with inputs; [ nix-vscode-extensions.overlays.default ];
homes = {
modules = with inputs; [
nix-index-database.homeModules.nix-index
sops-nix.homeManagerModules.sops
];
overlays = with inputs; [
nix-vscode-extensions.overlays.default
];
users = {
# "matt@desktop" = {
# modules = with inputs; [
# sops-nix.homeManagerModules.sops
# ];
# };
"deck@steamdeck" = {
modules = with inputs; [
steam-rom-manager.homeManagerModules.default
];
};
};
};
};
@@ -283,23 +264,8 @@
};
};
channels-config = {
allowUnfree = true;
allowUnsupportedSystem = true;
permittedInsecurePackages = [
# ...
# "libsoup-2.74.3"
# "mbedtls-2.28.10"
# ecdsa is pulled in by srp → ha-icloud3 custom component.
# CVE-2024-23342 applies to timing-sensitive cryptographic use cases,
# not the SRP authentication usage here.
"python3.14-ecdsa-0.19.1"
];
};
outputs-builder = channels: {
formatter = inputs.treefmt-nix.lib.mkWrapper channels.nixpkgs ./treefmt.nix;
# mjallen-lib overlay is auto-discovered from overlays/mjallen-lib/default.nix
};
};
}

View File

@@ -1,11 +1,10 @@
{
lib,
pkgs,
namespace,
home,
...
}:
let
inherit (lib.${namespace}) enabled disabled;
shellAliases = {
update-switch = "darwin-rebuild switch --flake ~/nix-config";
update-flake = "nix flake update ~/nix-config";
@@ -15,11 +14,8 @@ let
age
cpufetch
deadnix
iproute2mac
nebula
nixfmt
nodejs_25
uv
direnv
nixfmt-rfc-style
sops
tree
wget
@@ -33,7 +29,7 @@ in
homeDirectory = "/Users/mattjallen";
packages = lib.mkForce packages;
sessionVariables = {
NH_DARWIN_FLAKE = lib.mkForce "/Users/mattjallen/nix-config";
NH_DARWIN_FLAKE = "${home.homeDirectory}/nix-config#mac";
};
};
@@ -44,23 +40,19 @@ in
};
# Manage bug in compilations - who uses manpages in 2024 anyways? :P
manual.manpages = enabled;
manual.manpages.enable = false;
# Override defaults that arent supported
programs = {
mangohud = lib.mkForce disabled;
mangohud.enable = lib.mkForce false;
nh = {
flake = lib.mkForce "/Users/mattjallen/nix-config";
flake = "${home.homeDirectory}/nix-config";
};
};
services = {
pass-secret-service = lib.mkForce disabled;
nextcloud-client = lib.mkForce disabled;
kdeconnect = {
enable = false;
indicator = false;
};
pass-secret-service.enable = lib.mkForce false;
nextcloud-client.enable = lib.mkForce false;
};
}

View File

@@ -1,11 +1,14 @@
{
lib,
pkgs,
namespace,
...
}:
{ pkgs, lib, ... }:
let
inherit (lib.${namespace}) enabled disabled;
theme = import (lib.snowfall.fs.get-file "modules/home/desktop/theme/nord.nix");
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 mac-nixpkgs mac-nixos-apple-silicon mac-home-manager mac-impermanence mac-sops-nix --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";
};
fontName = "JetBrainsMono NFM";
fontPackage = pkgs.nerd-fonts.jetbrains-mono;
# Displays
display = {
input = "eDP-1";
@@ -15,36 +18,21 @@ let
in
{
home = {
username = "matt";
homeDirectory = "/home/matt";
stateVersion = "23.11";
};
home.username = "matt";
home.homeDirectory = "/home/matt";
home.stateVersion = "23.11";
${namespace} = {
desktop.plasma = lib.mkForce enabled;
programs.hyprland = {
enable = false;
mjallen = {
desktop.hyprland = {
enable = true;
primaryDisplay = "eDP-1";
debug.disableScaleChecks = true;
monitorv2 = [
{
name = display.input;
mode = "${display.resolution}@${display.refreshRate}";
position = "0x0";
scale = 1.25;
extra = [
"bitdepth"
"10"
"cm"
"hdr"
"sdrbrightness"
"1.2"
"sdrsaturation"
"0.98"
];
}
wallpaper = [
"${display.input}, /run/wallpaper.jpg"
];
monitor = [
"${display.input},${display.resolution}@${display.refreshRate},0x0,1.25,bitdepth,10,cm,hdr,sdrbrightness,1.2,sdrsaturation,0.98"
];
workspace = [
@@ -54,50 +42,51 @@ in
];
windowRule = [
# "size 2160 3356, tag:horizonrdp"
"size 2160 3356, tag:horizonrdp"
];
hyprpaper = {
wallpaperPath = "/run/wallpaper.jpg";
};
keybinds = {
bind = [
"$mod, A, exec, chromium --app=\"https://music.apple.com\""
"SHIFT, XF86MonBrightnessUp, exec, lightctl -D kbd_backlight up"
"SHIFT, XF86MonBrightnessDown, exec, lightctl -D kbd_backlight down"
];
};
defaultApps = {
browser = pkgs.firefox;
};
extraConfig = ''
exec-once = brightnessctl -d kbd_backlight s 50%
'';
};
programs = {
btop = enabled;
calibre = enabled;
kitty = disabled;
mako = disabled;
nwg-dock = disabled;
nwg-drawer = disabled;
nwg-panel = disabled;
opencode = enabled;
thunderbird = enabled;
vesktop = enabled;
btop.enable = true;
kitty = {
enable = true;
font = {
name = fontName;
package = fontPackage;
};
};
mako = {
enable = true;
fontName = fontName;
};
nwg-dock.enable = true;
nwg-drawer.enable = true;
nwg-panel = {
enable = true;
defaultApps = {
browser = pkgs.firefox;
};
};
waybar = {
enable = false;
enable = true;
layer = "bottom";
temperature = {
cpu = enabled;
gpu = enabled;
};
modules-right = [
"temperature"
"temperature#gpu"
"keyboard-state#capslock"
"keyboard-state#numlock"
"wireplumber#sink"
"bluetooth"
"network"
"idle_inhibitor"
"clock"
"battery"
"custom/weather"
];
extraModules = {
"custom/lights" = {
@@ -112,79 +101,34 @@ in
extraModulesStyle = ''
#custom-lights {
color: @base0C;
opacity: 0.85;
background-color: @base00;
color: ${theme.frost.nord8};
background-color: ${theme.polarNight.nord0};
${theme.defaultOpacity}
${theme.borderLeft}
}
#custom-lights:hover {
background: @base03;
background: ${theme.polarNight.nord3};
}
'';
windowOffset = 75;
};
wlogout = disabled;
wofi = disabled;
};
services = {
protonmail = enabled;
wlogout.enable = true;
wofi.enable = true;
};
};
sops = {
secrets = {
"protonmail-password" = {
sopsFile = lib.snowfall.fs.get-file "secrets/mac-secrets.yaml";
};
};
};
home.packages =
with pkgs.${namespace};
[
# librepods
librepods-beta
]
++ (with pkgs; [
bolt-launcher
bottles
iw
iwd
orca-slicer
rpi-imager
vscodium
]);
# Override the shared Plasma panel to add a standalone battery widget
# (laptop-specific — not needed on desktop systems)
programs.plasma.panels = lib.mkForce [
{
location = "bottom";
floating = true;
height = 44;
widgets = [
"org.kde.plasma.kickoff"
"org.kde.plasma.icontasks"
"org.kde.plasma.marginsseparator"
{ battery = { }; }
"org.kde.plasma.systemtray"
"org.kde.plasma.digitalclock"
];
}
home.packages = with pkgs; [
iw
iwd
orca-slicer
vscodium
];
services = {
kdeconnect = {
enable = lib.mkForce true;
indicator = lib.mkForce true;
};
};
programs = {
password-store = enabled;
};
password-store.enable = true;
zsh.shellAliases = shellAliases;
};
}

View File

@@ -0,0 +1,66 @@
{ lib, ... }:
{
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 = {
age.keyFile = "/home/matt/.config/sops/age/keys.txt";
defaultSopsFile = "/etc/nixos/secrets/secrets.yaml";
validateSopsFiles = false;
secrets = {
"ssh-keys-public/pi4" = {
path = "/home/matt/.ssh/id_ed25519.pub";
mode = "0644";
};
"ssh-keys-private/pi4" = {
path = "/home/matt/.ssh/id_ed25519";
mode = "0600";
};
# "ssh-keys-public/desktop-nixos" = {
# path = "/home/matt/.ssh/authorized_keys";
# mode = "0600";
# };
# "ssh-keys-public/desktop-nixos-root" = {
# path = "/home/matt/.ssh/authorized_keys2";
# mode = "0600";
# };
# "ssh-keys-public/desktop-windows" = {
# path = "/home/matt/.ssh/authorized_keys3";
# mode = "0600";
# };
# "ssh-keys-public/macbook-macos" = {
# path = "/home/matt/.ssh/authorized_keys4";
# mode = "0600";
# };
};
};
programs = {
mangohud.enable = lib.mkForce true;
};
services = {
nextcloud-client.enable = lib.mkForce false;
kdeconnect = {
enable = false;
indicator = false;
};
};
}

View File

@@ -1,19 +1,24 @@
{
config,
lib,
namespace,
...
}:
let
inherit (lib.${namespace}) disabled;
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 pi5-nixpkgs pi5-home-manager pi5-impermanence pi5-nixos-hardware pi5-sops-nix nixos-raspberrypi --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";
};
in
{
home.username = "matt";
${namespace}.sops.enable = true;
sops = {
age.keyFile = "/home/matt/.config/sops/age/keys.txt";
defaultSopsFile = "/etc/nixos/secrets/secrets.yaml";
validateSopsFiles = false;
secrets = {
"ssh-keys-public/pi5" = {
path = "/home/matt/.ssh/id_ed25519.pub";
@@ -49,8 +54,12 @@ in
};
};
programs = {
zsh.shellAliases = shellAliases;
};
services = {
nextcloud-client = lib.mkForce disabled;
nextcloud-client.enable = false;
kdeconnect = {
enable = false;
indicator = false;

View File

@@ -0,0 +1,26 @@
{ config, lib, ... }:
let
shellAliases = {
update-boot = "nixos-rebuild boot --max-jobs 10";
update-switch = "nixos-rebuild switch --max-jobs 10";
};
in
{
home = {
username = "root";
homeDirectory = lib.mkForce "/${config.home.username}";
enableNixpkgsReleaseCheck = false;
};
programs = {
zsh.shellAliases = shellAliases;
};
services = {
nextcloud-client.enable = lib.mkForce false;
kdeconnect = {
enable = false;
indicator = false;
};
};
}

View File

@@ -1,113 +0,0 @@
{
pkgs,
lib,
inputs,
namespace,
...
}:
let
inherit (lib.${namespace}) enabled;
in
{
# steam-rom-manager is also injected globally via modules/nixos/home/default.nix
# sharedModules for x86_64 NixOS builds. This explicit import ensures it is
# also available for standalone `home-manager switch` runs (where sharedModules
# are not applied). NixOS's module system deduplicates the import when both
# paths resolve to the same derivation.
imports = [
inputs.steam-rom-manager.homeManagerModules.default
];
home = {
username = "admin";
packages =
with pkgs;
[
heroic
python3
python3Packages.requests
python3Packages.mcp
jq
]
++ (with pkgs.${namespace}; [
moondeck-buddy
]);
};
${namespace} = {
sops.enable = true;
programs.opencode = enabled;
# desktop.plasma = enabled;
};
sops.secrets = {
"ssh-keys-public/jallen-nas" = {
path = "/home/admin/.ssh/id_ed25519.pub";
mode = "0644";
};
"ssh-keys-private/jallen-nas" = {
path = "/home/admin/.ssh/id_ed25519";
mode = "0600";
};
"ssh-keys-public/desktop-nixos" = {
path = "/home/admin/.ssh/authorized_keys";
mode = "0600";
};
"ssh-keys-public/desktop-nixos-root" = {
path = "/home/admin/.ssh/authorized_keys2";
mode = "0600";
};
"ssh-keys-public/desktop-windows" = {
path = "/home/admin/.ssh/authorized_keys3";
mode = "0600";
};
"ssh-keys-public/macbook-macos" = {
path = "/home/admin/.ssh/authorized_keys4";
mode = "0600";
};
};
programs = {
bash = {
shellAliases = {
"llama-status" =
"curl -s http://localhost:8127/health 2>/dev/null && echo 'LLaMA.cpp server is running' || echo 'LLaMA.cpp server is not responding'";
};
};
neovim = {
enable = true;
viAlias = true;
vimAlias = true;
defaultEditor = true;
plugins = [
pkgs.vimPlugins.nvim-tree-lua
{
plugin = pkgs.vimPlugins.vim-startify;
config = "let g:startify_change_to_vcs_root = 0";
}
];
};
steam-rom-manager = {
enable = true;
steamUsername = "mjallen18";
# Optional: override default paths if needed
environmentVariables = {
romsDirectory = "/home/admin/Emulation/roms";
steamDirectory = "/home/admin/.local/share/Steam";
};
emulators = {
"Non-SRM Shortcuts" = {
enable = true;
parserType = "Non-SRM Shortcuts";
extraArgs = "";
};
};
};
};
}

View File

@@ -0,0 +1,86 @@
{ pkgs, ... }:
{
home.username = "admin";
# 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 = {
age.keyFile = "/home/admin/.config/sops/age/keys.txt";
defaultSopsFile = "/etc/nixos/secrets/secrets.yaml";
validateSopsFiles = false;
secrets = {
"ssh-keys-public/jallen-nas" = {
path = "/home/admin/.ssh/id_ed25519.pub";
mode = "0644";
};
"ssh-keys-private/jallen-nas" = {
path = "/home/admin/.ssh/id_ed25519";
mode = "0600";
};
"ssh-keys-public/desktop-nixos" = {
path = "/home/admin/.ssh/authorized_keys";
mode = "0600";
};
"ssh-keys-public/desktop-nixos-root" = {
path = "/home/admin/.ssh/authorized_keys2";
mode = "0600";
};
"ssh-keys-public/desktop-windows" = {
path = "/home/admin/.ssh/authorized_keys3";
mode = "0600";
};
"ssh-keys-public/macbook-macos" = {
path = "/home/admin/.ssh/authorized_keys4";
mode = "0600";
};
};
};
programs = {
neovim = {
enable = true;
viAlias = true;
vimAlias = true;
defaultEditor = true;
plugins = [
pkgs.vimPlugins.nvim-tree-lua
{
plugin = pkgs.vimPlugins.vim-startify;
config = "let g:startify_change_to_vcs_root = 0";
}
];
};
};
# services.nixai = {
# enable = true;
# mcp = {
# enable = true;
# # Optional: custom socket path (uses `$HOME` expansion)
# socketPath = "$HOME/.local/share/nixai/mcp.sock";
# };
# # Optional: integrate with VS Code
# vscodeIntegration = true;
# };
}

View File

@@ -1,37 +0,0 @@
{
lib,
pkgs,
namespace,
...
}:
let
inherit (lib.${namespace}) disabled;
in
{
home.username = "admin";
# Configure systemd user service for protonmail-bridge
systemd.user.services.protonmail-bridge = {
Service = {
Environment = [
"GNUPGHOME=/home/admin/.gnupg"
"PASSWORD_STORE_DIR=/home/admin/.local/password-store"
];
};
};
services = {
nextcloud-client = lib.mkForce disabled;
kdeconnect = {
enable = false;
indicator = false;
};
protonmail-bridge = {
enable = true;
extraPackages = with pkgs; [
pass
libsecret
];
};
};
}

View File

@@ -0,0 +1,15 @@
{ ... }:
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";
programs = {
zsh.shellAliases = shellAliases;
};
}

View File

@@ -0,0 +1,83 @@
{ 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";
update-flake = "nix flake update steamdeck-nixpkgs steamdeck-chaotic steamdeck-home-manager steamdeck-impermanence steamdeck-jovian steamdeck-lanzaboote steamdeck-nixos-hardware steamdeck-sops-nix steamdeck-steam-rom-manager --flake /etc/nixos";
nas-ssh = "ssh admin@10.0.1.3";
};
in
{
home.username = "deck";
mjallen.desktop.gnome.enable = true;
sops = {
age.keyFile = "/home/deck/.config/sops/age/keys.txt";
defaultSopsFile = "/etc/nixos/secrets/secrets.yaml";
validateSopsFiles = false;
secrets = {
"ssh-keys-public/deck" = {
path = "/home/deck/.ssh/id_ed25519.pub";
mode = "0644";
};
"ssh-keys-private/deck" = {
path = "/home/deck/.ssh/id_ed25519";
mode = "0600";
};
};
};
programs = {
steam-rom-manager = {
enable = true;
steamUsername = "mjallen18";
# Optional: override default paths if needed
environmentVariables = {
romsDirectory = "/home/deck/Emulation/roms";
steamDirectory = "/home/deck/.local/share/Steam";
};
emulators = {
ryujinx.enable = true;
dolphin-gamecube = {
enable = true;
package = pkgs.dolphin-emu;
romFolder = "gc";
fileTypes = [
".iso"
".ISO"
".gcm"
".GCM"
".ciso"
".CISO"
"rvz"
];
extraArgs = "-b -e \"\${filePath}\"";
};
pcsx2.enable = true;
mgba.enable = true;
"Non-SRM Shortcuts" = {
enable = true;
parserType = "Non-SRM Shortcuts";
extraArgs = "";
};
};
};
zsh.shellAliases = shellAliases;
};
home.packages = with pkgs; [
dolphin-emu
heroic
mgba
prismlauncher
ryujinx-greemdev
omnissa-horizon-client
];
}

View File

@@ -1,93 +0,0 @@
{
lib,
pkgs,
namespace,
...
}:
let
inherit (lib.${namespace}) enabled;
in
{
home.username = "matt";
${namespace} = {
desktop.gnome = enabled;
sops.enable = true;
programs = {
vesktop = enabled;
};
};
sops.secrets = {
"ssh-keys-public/matt" = {
path = "/home/matt/.ssh/id_ed25519.pub";
mode = "0644";
};
"ssh-keys-private/matt" = {
path = "/home/matt/.ssh/id_ed25519";
mode = "0600";
};
};
programs = {
steam-rom-manager = {
enable = true;
steamUsername = "mjallen18";
environmentVariables = {
romsDirectory = "/media/sdcard/Emulation/roms";
steamDirectory = "/home/matt/.local/share/Steam";
};
enabledProviders = [
"sgdb"
"steamCDN"
];
imageProviderSettings.sgdb = {
nsfw = false;
humor = false;
imageMotionTypes = [ "static" ];
};
emulators = {
# --- Nintendo ---
ryujinx.enable = true; # Switch (ryubing fork)
yuzu.enable = true; # Switch (eden fork)
dolphin-emu.enable = true; # GameCube / Wii
cemu.enable = true; # Wii U
melonDS.enable = true; # DS
citra.enable = true; # 3DS (azahar fork)
mgba.enable = true; # Game Boy / GBC
mgba-gba.enable = true; # Game Boy Advance
# --- Sony ---
duckstation.enable = false; # PS1
pcsx2.enable = true; # PS2
rpcs3.enable = true; # PS3
ppsspp.enable = true; # PSP
# --- Microsoft ---
xemu.enable = true; # Xbox
# --- Platform parsers (no ROM scanning; artwork only / launcher integration) ---
"Non-SRM Shortcuts".enable = true;
};
};
};
home.packages =
with pkgs;
[
dolphin-emu
heroic
mgba
moonlight-qt
prismlauncher
ryubing
omnissa-horizon-client
]
++ (with pkgs.${namespace}; [
discord-krisp
# librepods-beta
]);
}

View File

@@ -0,0 +1,60 @@
{ pkgs, ... }:
{
home.username = "matt";
mjallen = {
sops = {
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 = {
remmina = {
enable = true;
addRdpMimeTypeAssoc = true;
};
};
programs = {
password-store.enable = true;
};
home.packages = with pkgs; [
bottles
compose2nix
discord
distrobox
heroic
omnissa-horizon-client
jq
lutris
lzip
morph
orca-slicer
piper
prismlauncher
protontricks
protonvpn-gui
python3
runelite
smile
unigine-heaven
via
virt-manager
vorta
waydroid-helper
];
}

View File

@@ -1,224 +0,0 @@
{
lib,
pkgs,
namespace,
...
}:
let
inherit (lib.${namespace}) enabled disabled;
displayLeft = {
input = "DP-1";
resolution = "3840x2160";
refreshRate = "120.00000";
};
displayRight = {
input = "DP-2";
resolution = "3840x2160";
refreshRate = "240.00000";
};
in
{
home.username = "matt";
${namespace} = {
sops = {
enable = true;
};
shell-aliases = {
enable = true;
};
desktop.plasma = enabled;
programs = {
vesktop = enabled;
opencode = enabled;
thunderbird = enabled;
hyprland = {
enable = false;
primaryDisplay = "DP-1";
monitorv2 = [
{
name = displayLeft.input;
mode = "${displayLeft.resolution}@${displayLeft.refreshRate}";
position = "0x0";
scale = 1.0;
extra = [
# "bitdepth"
# "10"
# "cm"
# "hdredid"
# "sdrbrightness"
# "1.2"
# "sdrsaturation"
# "0.98"
];
}
{
name = displayRight.input;
mode = "${displayRight.resolution}@${displayRight.refreshRate}";
position = "3840x0";
scale = 1.0;
extra = [
# "bitdepth"
# "10"
# "cm"
# "hdredid"
# "sdrbrightness"
# "1.5"
# "sdrsaturation"
# "0.98"
];
}
];
workspace = [
"name:firefox, monitor:${displayRight.input}, default:false, special, class:(.*firefox.*)"
"name:discord, monitor:${displayRight.input}, default:true, special, title:(.*vesktop.*), title:(.*Apple Music.*)"
"name:steam, monitor:${displayLeft.input}, default:false, special, class:(.*[Ss]team.*)"
];
windowRule = [
"match:tag horizonrdp, size 2160 7680"
];
autostartCommands = [
"[silent] firefox"
"[silent] discord"
"[silent] chromium --app=\"https://music.apple.com\""
"[silent] steam"
];
hyprpaper = {
wallpaperPath = "/run/wallpaper.jpg";
};
keybinds = {
bind = [
"$mod, A, exec, chromium --app=\"https://music.apple.com\""
"$mod, C, exec, discord"
"$mod, G, exec, steam"
];
};
defaultApps = {
browser = pkgs.firefox;
};
};
btop = enabled;
kitty = disabled;
mako = disabled;
nwg-dock = disabled;
nwg-drawer = disabled;
nwg-panel = disabled;
waybar = {
enable = false;
layer = "bottom";
network.interface = "wlp9s0";
temperature = {
cpu = enabled;
gpu = enabled;
};
extraModules = {
"custom/lights" = {
tooltip = false;
exec = "waybar-hass --get_light light.living_room_lights";
interval = "once";
format = "{text}"; # "󱉓";
on-click = "waybar-hass --toggle_light light.living_room_lights";
return-type = "json";
};
};
extraModulesStyle = ''
#custom-lights {
color: @base0C;
background-color: @base00;
opacity: 0.85;
border-left: 5px solid @base0C;
}
#custom-lights:hover {
background: @base03;
}
'';
};
wlogout = disabled;
wofi = disabled;
};
};
services = {
remmina = {
enable = true;
addRdpMimeTypeAssoc = true;
};
};
programs = {
password-store = enabled;
};
home.packages =
with pkgs;
[
atlauncher
bolt-launcher
clevis
compose2nix
distrobox
goverlay
heroic
home-manager
omnissa-horizon-client
jq
lzip
morph
orca-slicer
piper
prismlauncher
protontricks
runelite
smile
via
virt-manager
vorta
waydroid-helper
# winboat
]
++ (with pkgs.${namespace}; [
discord-krisp
# librepods
]);
specialisation = {
"gnome".configuration = {
${namespace} = {
desktop = {
plasma = lib.mkForce disabled;
gnome = lib.mkForce enabled;
};
};
};
"cosmic".configuration = {
${namespace} = {
desktop.plasma = lib.mkForce disabled;
programs = {
hyprland = lib.mkForce disabled;
kitty = lib.mkForce disabled;
mako = lib.mkForce disabled;
nwg-dock = lib.mkForce disabled;
nwg-drawer = lib.mkForce disabled;
nwg-panel = lib.mkForce disabled;
waybar = lib.mkForce disabled;
wlogout = lib.mkForce disabled;
wofi = lib.mkForce disabled;
};
};
};
};
}

View File

@@ -0,0 +1,17 @@
{ config, lib, ... }:
let
shellAliases = {
update-boot = "nixos-rebuild boot --max-jobs 10";
update-switch = "nixos-rebuild switch --max-jobs 10";
};
in
{
home = {
username = "root";
homeDirectory = lib.mkForce "/${config.home.username}";
};
programs = {
zsh.shellAliases = shellAliases;
};
}

View File

@@ -1,189 +0,0 @@
# mjallen-lib Utility Functions
Utility functions for the NixOS/nix-darwin configuration. Exposed via Snowfall Lib as `lib.mjallen.*`.
## Directory Structure
- `default.nix` — Main entry point; exports `module`, `file`, and `versioning`
- `module/` — Module creation helpers (`mkModule`, `mkContainerService`, `mkSopsEnvFile`, `mkOpt`, etc.)
- `file/` — File and path utilities
- `versioning/` — Multi-source version pinning helpers (used by packages)
---
## Module Utilities (`lib.mjallen.module`)
### `mkModule`
Creates a NixOS service module with a standard set of options. All config is gated behind `cfg.enable`.
```nix
lib.mjallen.mkModule {
config # NixOS config attrset (pass-through from module args)
name # Service name — used for option path and systemd unit
description # Text for mkEnableOption (defaults to name)
options # Extra options merged into the submodule
moduleConfig # NixOS config body (applied when cfg.enable = true)
domain # Option namespace domain (default: "services")
serviceName # Systemd service name (default: name)
}
```
**Standard options provided for free:**
| Option | Type | Default | Description |
|---|---|---|---|
| `enable` | bool | `false` | Enable/disable the service |
| `port` | int | `80` | Service listen port |
| `listenAddress` | str | `"0.0.0.0"` | Bind address |
| `openFirewall` | bool | `true` | Open TCP+UDP firewall ports |
| `configDir` | str | `/var/lib/<name>` | Config directory |
| `dataDir` | str | `/var/lib/<name>/data` | Data directory |
| `createUser` | bool | `false` | Create a dedicated system user |
| `configureDb` | bool | `false` | Create a PostgreSQL database |
| `environmentFile` | str\|null | `null` | Path to an env-file |
| `extraEnvironment` | attrs | `{}` | Extra environment variables |
| `hashedPassword` | str\|null | `null` | Hashed password for web auth |
| `puid` / `pgid` | str | `"911"` / `"100"` | UID/GID for container services |
| `timeZone` | str | `"UTC"` | Timezone for container services |
| `redis.enable` | bool | `false` | Create a Redis instance for this service |
| `redis.port` | int | `6379` | Redis port |
| `reverseProxy.enable` | bool | `false` | Add a Caddy reverse proxy block |
| `reverseProxy.subdomain` | str | `<name>` | Caddy subdomain |
| `reverseProxy.domain` | str | `"mjallen.dev"` | Caddy base domain |
| `reverseProxy.upstreamUrl` | str\|null | `null` | Override upstream URL |
| `reverseProxy.extraCaddyConfig` | lines | `""` | Extra Caddyfile directives |
**Default behaviour when enabled:**
- Adds Caddy reverse proxy block (if `reverseProxy.enable = true`)
- Opens firewall ports (if `openFirewall = true`)
- Creates system user/group (if `createUser = true`)
- Creates PostgreSQL database (if `configureDb = true`)
- Creates Redis instance (if `redis.enable = true`)
- Adds `RequiresMountsFor` systemd dependency for `configDir` and `dataDir`
---
### `mkContainerService`
Wraps `mkModule` for Podman/OCI container services. Generates the full container definition automatically.
```nix
lib.mjallen.mkContainerService {
config # NixOS config attrset
name # Service/container name
image # OCI image reference (e.g. "ghcr.io/example/app:latest")
internalPort # Port the container listens on internally
description # Human-readable description (defaults to name)
options # Extra mkModule options
volumes # Extra volume mount strings
environment # Extra environment variables (merged with PUID/PGID/TZ)
environmentFiles # List of env-file paths (e.g. SOPS template paths)
extraOptions # Extra --opt strings for the container runtime
devices # Device mappings
extraConfig # Extra NixOS config merged into moduleConfig
}
```
The systemd service is named `podman-<name>`, and the port binding is `<cfg.port>:<internalPort>`.
---
### `mkSopsEnvFile`
Generates a SOPS secrets block and a SOPS template env-file in a single call. Useful for services that need secrets injected as environment variables.
```nix
lib.mjallen.mkSopsEnvFile {
secrets # attrset: sops-key → extra attrs (owner, group, etc.)
name # Template file name, e.g. "myapp.env"
content # Template body (use config.sops.placeholder."key")
restartUnit # Systemd unit to restart when secrets change
owner # File owner (default: "nix-apps")
group # File group (default: "jallen-nas")
mode # File permissions (default: "660")
sopsFile # Default SOPS file for all secrets
}
```
---
### Option helpers
| Function | Signature | Description |
|---|---|---|
| `mkOpt` | `type → default → description → mkOption` | Standard option shorthand |
| `mkOpt'` | `type → default → mkOption` | `mkOpt` without description |
| `mkBoolOpt` | `default → description → mkOption` | Boolean option shorthand |
| `mkBoolOpt'` | `default → mkOption` | Boolean option without description |
| `mkReverseProxyOpt` | `name → attrset` | Standard Caddy reverse proxy sub-options |
---
### Convenience shorthands
| Value | Expands to |
|---|---|
| `enabled` | `{ enable = true; }` |
| `disabled` | `{ enable = false; }` |
---
### Attribute utilities
| Function | Description |
|---|---|
| `default-attrs` | Apply `lib.mkDefault` to every value in an attrset |
| `force-attrs` | Apply `lib.mkForce` to every value in an attrset |
| `nested-default-attrs` | Apply `default-attrs` one level deeper |
| `nested-force-attrs` | Apply `force-attrs` one level deeper |
---
### String utilities
| Function | Description |
|---|---|
| `capitalize` | Capitalise the first character of a string |
---
### Boolean utilities
| Function | Description |
|---|---|
| `boolToNum` | `true → 1`, `false → 0` |
---
## Home Manager Utilities (`lib.mjallen.module`)
### `mkHomeModule`
Creates a Home Manager module with a standard `enable` option.
```nix
lib.mjallen.mkHomeModule {
config # HM config attrset
domain # Option namespace domain, e.g. "programs" or "desktop"
name # Module name, e.g. "btop"
description # Text for mkEnableOption (defaults to name)
options # Extra options merged into the submodule
moduleConfig # HM config body (applied when cfg.enable = true)
}
```
---
## File Utilities (`lib.mjallen.file`)
Helpers for resolving paths relative to the flake root. Primarily used internally by modules and packages.
---
## Versioning Utilities (`lib.mjallen.versioning`)
Used by packages that track multiple upstream variants (e.g. `proton-cachyos`, `linux-rpi`). Reads a `version.json` file and resolves sources with optional variable substitution and per-variant overrides.
See `lib/versioning/default.nix` for the full API and `docs/version.schema.json` for the `version.json` schema.

View File

@@ -1,12 +0,0 @@
{ inputs, ... }:
{
mjallen-lib = {
module = import ./module { inherit inputs; };
file = import ./file { inherit inputs; };
inherit (inputs.nixpkgs) lib;
versioning = import ./versioning {
inherit (inputs.nixpkgs) lib;
inherit inputs;
};
};
}

View File

@@ -1,127 +0,0 @@
{ inputs, ... }@args:
let
# Get self from args or default to ../.. (the flake root)
self = args.self or ../..;
inherit (inputs.nixpkgs.lib)
genAttrs
filterAttrs
hasPrefix
foldl'
;
in
{
# Read a file and return its contents
readFile = path: builtins.readFile path;
# Check if a file exists
pathExists = path: builtins.pathExists path;
# Import a nix file with error handling
safeImport = path: default: if builtins.pathExists path then import path else default;
# Scan a directory and return directory names
scanDir = path: builtins.attrNames (builtins.readDir path);
# Get a file path relative to the flake root (similar to Snowfall's get-file)
getFile = relativePath: self + "/${relativePath}";
# Recursively discover and import all Nix modules in a directory tree
importModulesRecursive =
path:
let
# Helper function to recursively walk directories
walkDir =
currentPath:
let
currentEntries = builtins.readDir currentPath;
entryNames = builtins.attrNames currentEntries;
# Get all directories that contain default.nix
directoriesWithDefault = builtins.filter (
name:
currentEntries.${name} == "directory" && builtins.pathExists (currentPath + "/${name}/default.nix")
) entryNames;
# Get ALL directories (to recurse into)
allDirectories = builtins.filter (name: currentEntries.${name} == "directory") entryNames;
# Import directories that have default.nix
directoryImports = map (name: currentPath + "/${name}") directoriesWithDefault;
# Recursively walk ALL subdirectories
subDirImports = builtins.concatLists (map (dir: walkDir (currentPath + "/${dir}")) allDirectories);
in
directoryImports ++ subDirImports;
in
walkDir path;
# Recursively scan systems directory structure
scanSystems =
systemsPath:
let
systemArchs = builtins.attrNames (builtins.readDir systemsPath);
generateSystemConfigs =
system:
let
systemPath = systemsPath + "/${system}";
hosts = builtins.attrNames (builtins.readDir systemPath);
in
genAttrs hosts (hostname: {
inherit system hostname;
path = systemPath + "/${hostname}";
});
in
foldl' (acc: system: acc // generateSystemConfigs system) { } systemArchs;
# Filter systems for NixOS (Linux)
filterNixOSSystems =
systems:
filterAttrs (
_name: { system, ... }: hasPrefix "x86_64-linux" system || hasPrefix "aarch64-linux" system
) systems;
# Filter systems for Darwin (macOS)
filterDarwinSystems =
systems:
filterAttrs (
_name: { system, ... }: hasPrefix "aarch64-darwin" system || hasPrefix "x86_64-darwin" system
) systems;
# Scan homes directory structure for home configurations
scanHomes =
homesPath:
let
systemArchs = builtins.attrNames (builtins.readDir homesPath);
generateHomeConfigs =
system:
let
systemPath = homesPath + "/${system}";
userAtHosts = builtins.attrNames (builtins.readDir systemPath);
parseUserAtHost =
userAtHost:
let
# Split "username@hostname" into parts
parts = builtins.split "@" userAtHost;
username = builtins.head parts;
hostname = builtins.elemAt parts 2; # After split: [username, "@", hostname]
in
{
inherit
system
username
hostname
userAtHost
;
path = systemPath + "/${userAtHost}";
};
in
genAttrs userAtHosts parseUserAtHost;
in
foldl' (acc: system: acc // generateHomeConfigs system) { } systemArchs;
}

View File

@@ -1,382 +0,0 @@
{
inputs,
lib,
namespace,
}:
let
inherit (inputs.nixpkgs.lib)
mapAttrs
mkOption
types
toUpper
substring
stringLength
mkDefault
mkForce
;
in
rec {
# ---------------------------------------------------------------------------
# NixOS service module helpers
# ---------------------------------------------------------------------------
# Create a NixOS module with standard options (enable, port, reverseProxy,
# firewall, user, postgresql, redis) and optional caller-supplied options and
# config. All config is gated behind `cfg.enable`.
mkModule =
{
name,
description ? "",
options ? { },
moduleConfig ? { },
domain ? "services",
config,
serviceName ? name,
}:
let
cfg = config.${namespace}.${domain}.${name};
upstreamUrl =
if cfg.reverseProxy.upstreamUrl != null then
cfg.reverseProxy.upstreamUrl
else
"http://127.0.0.1:${toString cfg.port}";
fqdn = "${cfg.reverseProxy.subdomain}.${cfg.reverseProxy.domain}";
defaultConfig = {
# Caddy reverse proxy: when reverseProxy.enable = true, contribute this
# service's named-matcher block into the shared wildcard virtual host.
services.caddy.virtualHosts."*.${cfg.reverseProxy.domain}" = lib.mkIf cfg.reverseProxy.enable {
extraConfig = ''
@${name} host ${fqdn}
handle @${name} {
reverse_proxy ${upstreamUrl}
${cfg.reverseProxy.extraCaddyConfig}
}
'';
};
networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
allowedUDPPorts = [ cfg.port ];
};
users = lib.mkIf cfg.createUser {
users.${name} = {
isSystemUser = true;
group = name;
home = cfg.configDir;
};
groups.${name} = { };
};
# RequiresMountsFor is silently ignored when the paths live on the root
# filesystem, so this is safe on non-NAS hosts too.
systemd.services.${serviceName}.unitConfig.RequiresMountsFor = [
cfg.configDir
cfg.dataDir
];
services = {
postgresql = lib.mkIf cfg.configureDb {
enable = true;
ensureDatabases = [ name ];
ensureUsers = [
{
name = name;
ensureDBOwnership = true;
}
];
};
redis.servers.${name} = lib.mkIf cfg.redis.enable {
inherit (cfg.redis) enable port;
};
};
};
in
{ lib, ... }:
{
imports = [
{ config = lib.mkIf cfg.enable defaultConfig; }
{ config = lib.mkIf cfg.enable moduleConfig; }
];
options.${namespace}.${domain}.${name} = lib.mkOption {
type = lib.types.submodule {
options = {
enable = lib.mkEnableOption description;
port = mkOpt types.int 80 "Port for ${name} to listen on";
configDir = mkOpt types.str "/var/lib/${name}" "Path to the config directory";
dataDir = mkOpt types.str "/var/lib/${name}/data" "Path to the data directory";
createUser = mkBoolOpt false "Create a dedicated system user for this service";
configureDb = mkBoolOpt false "Manage a PostgreSQL database for this service";
environmentFile =
mkOpt (types.nullOr types.str) null
"Path to an environment file (EnvironmentFile=)";
puid = mkOpt types.str "911" "User ID for container-based services";
pgid = mkOpt types.str "100" "Group ID for container-based services";
timeZone = mkOpt types.str "UTC" "Timezone for container-based services";
listenAddress = mkOpt types.str "0.0.0.0" "Listen address";
openFirewall = mkBoolOpt true "Open firewall ports for this service";
redis = {
enable = lib.mkEnableOption "a dedicated Redis server for this service";
port = mkOpt types.int 6379 "Redis port for ${name}";
};
hashedPassword =
mkOpt (types.nullOr types.str) null
"Hashed password (e.g. for web-based authentication)";
extraEnvironment =
mkOpt (types.attrsOf types.str) { }
"Extra environment variables passed to the service";
reverseProxy = mkReverseProxyOpt name;
hostedService = {
enable =
mkOpt types.bool cfg.reverseProxy.enable
"Expose this service in Glance dashboard (auto-enabled when reverseProxy is on)";
title = mkOpt types.str name "Display title in Glance";
icon = mkOpt types.str "si:glance" "Icon identifier for Glance (e.g. si:actualbudget)";
group = mkOpt types.str "Services" "Glance group/category for this service";
url = mkOpt types.str (
if cfg.reverseProxy.enable then
"https://${cfg.reverseProxy.subdomain}.${cfg.reverseProxy.domain}"
else
"http://127.0.0.1:${toString cfg.port}"
) "Service URL for Glance (auto-derived from reverseProxy if enabled)";
basicAuth = mkOpt types.bool false "Require basic auth for this service in Glance";
};
}
// options;
};
default = { };
};
};
# Wraps mkModule for Podman/OCI container services. Generates all the
# standard mkModule options plus the container definition. The serviceName
# is set to "podman-<name>" automatically.
#
# Required args:
# config — the NixOS config attrset (pass through from the module args)
# name — service name (used for the container name and option path)
# image — OCI image reference string
# internalPort — port the container listens on internally
#
# Optional args:
# description — human-readable description (defaults to name)
# options — extra mkModule options attrset
# volumes — extra volume strings (in addition to none)
# environment — extra environment variables (merged with PUID/PGID/TZ)
# environmentFiles — list of paths to env-files (e.g. sops template paths)
# extraOptions — list of extra --opt strings passed to the container runtime
# devices — list of device mappings
# extraConfig — extra NixOS config merged into moduleConfig
mkContainerService =
{
config,
name,
image,
internalPort,
description ? name,
options ? { },
volumes ? [ ],
environment ? { },
environmentFiles ? [ ],
extraOptions ? [ ],
devices ? [ ],
extraConfig ? { },
}:
let
cfg = config.${namespace}.services.${name};
in
mkModule {
inherit
config
name
description
options
;
serviceName = "podman-${name}";
moduleConfig = lib.recursiveUpdate {
virtualisation.oci-containers.containers.${name} = {
autoStart = true;
inherit
image
volumes
environmentFiles
extraOptions
devices
;
ports = [ "${toString cfg.port}:${toString internalPort}" ];
environment = {
PUID = cfg.puid;
PGID = cfg.pgid;
TZ = cfg.timeZone;
}
// environment;
};
} extraConfig;
};
# Generates a sops secrets block + a sops template env-file in a single call.
#
# secrets — attrset of sops secret keys → extra attrs (e.g. owner/group).
# The sopsFile is set automatically to nas-secrets.yaml unless
# overridden per-secret via { sopsFile = ...; }.
# name — template file name, e.g. "glance.env"
# content — the template body string (use config.sops.placeholder."key")
# restartUnit — systemd unit to restart when the secret changes
# owner, group, mode — file ownership/permissions (defaults match NAS convention)
# sopsFile — default sops file for all secrets (can be overridden per-secret)
mkSopsEnvFile =
{
secrets,
name,
content,
restartUnit,
owner ? "nix-apps",
group ? "jallen-nas",
mode ? "660",
sopsFile ? lib.snowfall.fs.get-file "secrets/nas-secrets.yaml",
}:
{
sops.secrets = mapAttrs (_key: extra: { inherit sopsFile; } // extra) secrets;
sops.templates.${name} = {
inherit
mode
owner
group
content
;
restartUnits = [ restartUnit ];
};
};
# ---------------------------------------------------------------------------
# Home Manager module helper
# ---------------------------------------------------------------------------
# Create a Home Manager module with a standard enable option and optional
# extra options, gating all config behind `cfg.enable`.
#
# domain — option namespace domain, e.g. "programs" or "desktop"
# name — module name, e.g. "btop"
# description — text for mkEnableOption (defaults to name)
# options — attrset of extra options merged into the submodule
# config — the NixOS/HM config attrset passed through from module args
# moduleConfig — the Home Manager config body (already gated behind cfg.enable)
mkHomeModule =
{
config,
domain,
name,
description ? name,
options ? { },
moduleConfig,
}:
let
cfg = config.${namespace}.${domain}.${name};
in
{ lib, ... }:
{
options.${namespace}.${domain}.${name} = lib.mkOption {
type = lib.types.submodule {
options = {
enable = lib.mkEnableOption description;
}
// options;
};
default = { };
};
config = lib.mkIf cfg.enable moduleConfig;
};
# ---------------------------------------------------------------------------
# Option creation helpers
# ---------------------------------------------------------------------------
# Option creation helpers
# ---------------------------------------------------------------------------
mkOpt =
type: default: description:
mkOption { inherit type default description; };
mkOpt' = type: default: mkOpt type default "";
mkBoolOpt = mkOpt types.bool;
mkBoolOpt' = mkOpt' types.bool;
mkReverseProxyOpt = name: {
enable = mkBoolOpt false "Enable Caddy reverse proxy for this service";
subdomain = mkOpt types.str name "Subdomain for the service (default: service name)";
domain = mkOpt types.str "mjallen.dev" "Base domain for the reverse proxy";
upstreamUrl =
mkOpt (types.nullOr types.str) null
"Override upstream URL (e.g. for a service on a different host). Defaults to http://127.0.0.1:<port>.";
extraCaddyConfig = mkOpt types.lines "" "Extra Caddyfile directives inside this virtual host block";
};
# ---------------------------------------------------------------------------
# Convenience shorthands
# ---------------------------------------------------------------------------
enabled = {
enable = true;
};
disabled = {
enable = false;
};
# ---------------------------------------------------------------------------
# String utilities
# ---------------------------------------------------------------------------
capitalize =
s:
let
len = stringLength s;
in
if len == 0 then "" else (toUpper (substring 0 1 s)) + (substring 1 len s);
# ---------------------------------------------------------------------------
# Boolean utilities
# ---------------------------------------------------------------------------
boolToNum = bool: if bool then 1 else 0;
# ---------------------------------------------------------------------------
# Attribute manipulation utilities
# ---------------------------------------------------------------------------
default-attrs = mapAttrs (_key: mkDefault);
force-attrs = mapAttrs (_key: mkForce);
nested-default-attrs = mapAttrs (_key: default-attrs);
nested-force-attrs = mapAttrs (_key: force-attrs);
}

View File

@@ -1,185 +0,0 @@
# Central network topology registry.
#
# Exposed as lib.<namespace>.network.* (Snowfall Lib merges lib/ sub-directories
# automatically, so this file is reachable as lib.mjallen.network inside any
# NixOS module, home-manager module, or package that receives `lib`).
#
# Usage examples:
#
# let net = lib.mjallen.network; in
# net.hosts.nas.lan # "10.0.1.3"
# net.hosts.nas.lan4 # "10.0.1.3/24" (CIDR notation)
# net.hosts.nuc.lan # "10.0.1.4"
# net.hosts.pi5.nebula # "10.1.1.1"
# net.subnet.lan # "10.0.1.0/24"
# net.subnet.nebula # "10.1.1.0/24"
# net.ports.nas.nextcloud # 9988
# net.domain # "mjallen.dev"
#
# All attributes intentionally use plain strings / ints so they can be
# interpolated with `toString` or used directly in any context.
{
network = {
# -----------------------------------------------------------------------
# Global domain
# -----------------------------------------------------------------------
domain = "mjallen.dev";
# -----------------------------------------------------------------------
# Subnets
# -----------------------------------------------------------------------
subnet = {
lan = "10.0.1.0/24";
nebula = "10.1.1.0/24";
# Docker / container bridge used by Home Assistant
docker = "172.30.33.0/24";
};
# -----------------------------------------------------------------------
# Hosts
# Each host exposes:
# lan — bare IPv4 address (no prefix length)
# lan4 — IPv4 address with /24 CIDR suffix (for static IP config)
# nebula — Nebula overlay IP (where applicable)
# -----------------------------------------------------------------------
hosts = {
# ---- Raspberry Pi 5 (pi5) — DNS / Nebula lighthouse ----------------
pi5 = {
hostname = "pi5";
lan = "10.0.1.2";
lan4 = "10.0.1.2/24";
nebula = "10.1.1.1";
gateway = "10.0.1.1";
};
# ---- NAS (jallen-nas) — primary server -----------------------------
nas = {
hostname = "jallen-nas";
lan = "10.0.1.3";
lan4 = "10.0.1.3/24";
nebula = "10.1.1.3";
gateway = "10.0.1.1";
};
# ---- Intel NUC (nuc-nixos) — Home Assistant host -------------------
nuc = {
hostname = "nuc-nixos";
lan = "10.0.1.4";
lan4 = "10.0.1.4/24";
nebula = "10.1.1.4";
gateway = "10.0.1.1";
};
# ---- MacBook Pro (macbook-pro-nixos) — Apple Silicon laptop --------
macbook = {
hostname = "macbook-pro-nixos";
nebula = "10.1.1.8";
};
# ---- ASUS ROG Ally X (allyx) ----------------------------------------
allyx = {
hostname = "allyx";
nebula = "10.1.1.10";
};
# ---- Router / gateway / AdGuard upstream ---------------------------
router = {
hostname = "router";
lan = "10.0.1.1";
lan4 = "10.0.1.1/24";
};
};
# -----------------------------------------------------------------------
# Service ports
# Grouped by host. Every entry matches the port set in apps.nix / the
# corresponding mkModule option so there is a single source of truth.
# -----------------------------------------------------------------------
ports = {
# ---- pi5 services --------------------------------------------------
pi5 = {
adguard = 3000;
attic = 9012;
nebula = 4242;
dns = 53;
};
# ---- NAS services --------------------------------------------------
nas = {
actual = 3333;
attic = 9012;
authentik = 9000;
authentikRac = 4823;
calibre = 8084;
calibreWeb = 8083;
codeServer = 4444;
cockpit = 9091;
collabora = 9980;
coturn = 3478;
crowdsec = 8181;
dispatcharr = 9191;
elasticsearch = 9200;
gitea = 3000;
giteaSsh = 2222;
glance = 5555;
glances = 61208;
grafana = 9999;
grimmory = 6066;
guacd = 4822;
headscale = 2112;
immich = 2283;
jellyfin = 8096;
seerr = 5055;
kavita = 5000;
llamaCpp = 8127;
lubelogger = 6754;
manyfold = 3214;
mariadb = 3306;
matrix = 8448;
mongodb = 27017;
nebula = 4242;
netbootxyz = 4000;
netbootxyzWeb = 4080;
nextcloud = 9988;
ntfy = 2586;
nutUpsd = 3493;
ocis = 9200;
onlyoffice = 9943;
opencloud = 9200;
orcaSlicer = 3100;
paperless = 28981;
paperlessAi = 28982;
postgresql = 5432;
protonmailSmtp = 1025;
protonmailImap = 1143;
redisCcache = 6363;
redisManyfold = 6380;
redisOnlyoffice = 6381;
resticServer = 8008;
sabnzbd = 8280;
sonarr = 8989;
radarr = 7878;
sparkyFitnessFe = 3004;
sparkyFitnessBe = 3010;
sunshine = 47989;
tdarr = 8265;
tdarrServer = 8266;
termix = 7777;
tunarr = 8000;
unmanic = 8265;
uptimeKuma = 3001;
wyomingPiper = 10200;
wyomingWhisper = 10300;
};
# ---- NUC services --------------------------------------------------
nuc = {
homeAssistant = 8123;
mqtt = 1883;
otbr = 8880;
otbrRest = 8881;
esphome = 6052;
};
};
};
}

View File

@@ -1,54 +0,0 @@
# Shared nix daemon / nixpkgs settings used by both the NixOS and nix-darwin
# modules (modules/nixos/nix/default.nix and modules/darwin/nix/default.nix).
#
# Snowfall Lib discovers this file and merges its return value into
# lib.<namespace>.* — the nested attrset is accessed as:
# lib.${namespace}.nixSettings.commonSubstituters
# lib.${namespace}.nixSettings.commonTrustedPublicKeys
# lib.${namespace}.nixSettings.commonSettings
# lib.${namespace}.nixSettings.commonGc
{ lib, ... }:
let
net = lib.mjallen.network;
in
{
nixSettings = {
commonSubstituters = [
"http://${net.hosts.nas.lan}:${toString net.ports.nas.attic}/nas-cache"
"https://nixos-apple-silicon.cachix.org"
"https://nixos-raspberrypi.cachix.org"
"https://nix-community.cachix.org"
"https://cache.nixos.org/"
];
commonTrustedPublicKeys = [
"nas-cache:eK0eRVAt9QNwbkLIyOo9N5Z5+zi6ukI4mSlL196C7Yg="
"nixos-apple-silicon.cachix.org-1:8psDu5SA5dAD7qA0zMy5UT292TxeEPzIz8VVEr2Js20="
"nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
commonSettings = {
warn-dirty = lib.mkForce false;
experimental-features = lib.mkForce [
"nix-command"
"flakes"
];
trusted-users = [
"@wheel"
"@admin"
];
builders-use-substitutes = true;
connect-timeout = lib.mkDefault 5;
fallback = true;
log-lines = lib.mkDefault 25;
max-free = lib.mkDefault (3000 * 1024 * 1024);
min-free = lib.mkDefault (512 * 1024 * 1024);
};
commonGc = {
automatic = lib.mkDefault true;
options = lib.mkDefault "--delete-older-than 30d";
};
};
}

View File

@@ -1,198 +0,0 @@
{
lib,
}:
let
inherit (builtins)
isAttrs
isList
isString
hasAttr
getAttr
attrNames
replaceStrings
;
inherit (lib) mapAttrs recursiveUpdate;
# Deep-merge attrsets (right-biased).
deepMerge = a: b: recursiveUpdate a b;
# Merge component sources: base.sources overlaid by overrides (component-wise deep merge).
mergeSources =
baseSources: overrides:
baseSources
// mapAttrs (
name: ov: if hasAttr name baseSources then deepMerge (getAttr name baseSources) ov else ov
) overrides;
# Apply a single variant overlay (variables + sources).
applyVariantOnce =
selected: variant:
let
vVars = variant.variables or { };
vSrcs = variant.sources or { };
in
{
variables = selected.variables // vVars;
sources = mergeSources selected.sources vSrcs;
};
# Apply platform-specific overrides if present for the given system.
applyPlatforms =
selected: variant: system:
if system == null || !(variant ? platforms) || !(hasAttr system variant.platforms) then
selected
else
let
p = variant.platforms.${system};
pVars = p.variables or { };
pSrcs = p.sources or { };
in
{
variables = selected.variables // pVars;
sources = mergeSources selected.sources pSrcs;
};
# Resolve variant chain via inherits (ancestor first), then apply platforms.
resolveVariant =
spec: baseSelected: variantName: system:
if variantName == null || !(spec ? variants) || !(hasAttr variantName spec.variants) then
baseSelected
else
let
v = spec.variants.${variantName};
parentSelected =
if v ? inherits then resolveVariant spec baseSelected v.inherits system else baseSelected;
withVariant = applyVariantOnce parentSelected v;
in
applyPlatforms withVariant v system;
# Render ${var} substitutions in any string within attrs/lists.
renderValue =
value: vars:
if isString value then
let
keys = attrNames vars;
patterns = map (k: "\${" + k + "}") keys;
replacements = map (k: toString (getAttr k vars)) keys;
in
replaceStrings patterns replacements value
else if isAttrs value then
mapAttrs (_: v: renderValue v vars) value
else if isList value then
map (v: renderValue v vars) value
else
value;
# Decide fetcher for URL type based on optional extra.unpack hint.
useFetchZip = comp: comp ? extra && comp.extra ? unpack && comp.extra.unpack == "zip";
# Build a single src from a rendered component spec, using the given pkgs for fetchers.
mkSrcFromRendered' =
pkgs': comp:
let
fetcher = comp.fetcher or "none";
in
if fetcher == "github" then
pkgs'.fetchFromGitHub (
{
inherit (comp) owner repo hash;
# Allow tag as rev (ignore null/empty tag)
rev = if comp ? tag && comp.tag != null && comp.tag != "" then comp.tag else comp.rev;
fetchSubmodules = comp.submodules or false;
}
// lib.optionalAttrs (comp ? name) { inherit (comp) name; }
)
else if fetcher == "git" then
pkgs'.fetchgit {
inherit (comp) url rev hash;
fetchSubmodules = comp.submodules or false;
}
else if fetcher == "url" then
let
url = comp.url or comp.urlTemplate;
in
if useFetchZip comp then
pkgs'.fetchzip (
{
inherit (comp) hash;
inherit url;
}
// lib.optionalAttrs (comp ? extra && comp.extra ? stripRoot) { inherit (comp.extra) stripRoot; }
)
else
pkgs'.fetchurl {
inherit (comp) hash;
inherit url;
}
else if fetcher == "pypi" then
pkgs'.python3Packages.fetchPypi {
inherit (comp) version hash;
pname = comp.name;
}
else
# fetcher == "none": pass-through (e.g., linux version/hash consumed by custom logic)
comp;
in
rec {
/*
Select a variant from a loaded version.json specification.
Usage:
let selected = versioning.selectVariant spec variantName system;
- spec: attrset from lib.importJSON ./version.json
- variantName: string or null (when null, uses spec.defaultVariant if present)
- system: string like "x86_64-linux" or null (to apply platforms overrides)
*/
selectVariant =
spec: variantName: system:
let
chosen = if variantName != null then variantName else (spec.defaultVariant or null);
baseSelected = {
variables = spec.variables or { };
sources = spec.sources or { };
};
in
resolveVariant spec baseSelected chosen system;
/*
Render ${var} template substitutions across any value using provided variables.
Strings, attrsets, and lists are traversed.
*/
render = value: variables: renderValue value variables;
/*
Render a component with variables and then build its src (or pass-through for fetcher "none").
Prefer using mkAllSources, which handles rendering for all components.
pkgs: the nixpkgs instance to use for fetchers (must match the target system).
*/
mkSrc =
pkgs': comp: variables:
let
rendered = renderValue comp variables;
in
mkSrcFromRendered' pkgs' rendered;
/*
Produce an attrset of all sources for a selected spec:
mkAllSources pkgs selected
Where:
pkgs: the nixpkgs instance to use for fetchers (must match the target system).
selected = selectVariant spec variantName system
Returns:
{ componentName = src | renderedComp (for "none"); ... }
*/
mkAllSources =
pkgs': selected:
mapAttrs (
_name: comp:
if comp ? fetcher && comp.fetcher == "none" then
renderValue comp selected.variables
else
mkSrc pkgs' (renderValue comp selected.variables) selected.variables
) selected.sources;
# Expose deepMerge for convenience (right-biased).
inherit deepMerge;
}

View File

@@ -1,54 +0,0 @@
{
lib,
options,
namespace,
inputs,
...
}:
{
options.${namespace}.home = with lib.types; {
configFile = lib.mkOption {
type = attrs;
default = { };
description = "A set of files to be managed by home-manager's <option>xdg.configFile</option>.";
};
extraOptions = lib.mkOption {
type = attrs;
default = { };
description = "Options to pass directly to home-manager.";
};
file = lib.mkOption {
type = attrs;
default = { };
description = "A set of files to be managed by home-manager's <option>home.file</option>.";
};
};
config = {
home-manager = {
# enables backing up existing files instead of erroring if conflicts exist
backupFileExtension = "backup";
useGlobalPkgs = true;
useUserPackages = true;
# Pass inputs so external modules can access them
extraSpecialArgs = {
inherit inputs namespace;
};
# Make ALL external HM modules available globally
sharedModules = with inputs; [
sops-nix.homeManagerModules.sops
nix-plist-manager.homeManagerModules.default
nix-index-database.homeModules.nix-index
stylix.homeModules.stylix
# Add any other external HM modules here
];
users."mattjallen" = lib.mkAliasDefinitions options.${namespace}.home.extraOptions;
verbose = true;
};
};
}

View File

@@ -1,31 +0,0 @@
{
lib,
namespace,
...
}:
let
inherit (lib.${namespace}) nixSettings;
in
{
nix = {
settings = nixSettings.commonSettings // {
inherit (nixSettings)
commonSubstituters
commonTrustedPublicKeys
;
};
gc = nixSettings.commonGc;
optimise.automatic = lib.mkDefault true;
};
nixpkgs = {
config = {
# CUDA and ROCm are not applicable on Darwin; those hardware modules are
# NixOS-only. Unfree allowance is handled globally via channels-config in
# the flake.
allowUnsupportedSystem = true;
};
};
}

View File

@@ -1,12 +0,0 @@
_: {
config = {
programs.ssh.knownHosts = {
desktop = {
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTBMydhOc6SnOdB5WrEd7X07DrboAtagCUgXiOJjLov matt@matt-nixos";
};
nas = {
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIwoHWOLSTGVif9hAhaMLl0qDA4roIzCNuyR6kyIXDOj admin@jallen-nas";
};
};
};
}

View File

@@ -1,47 +0,0 @@
{
config,
lib,
pkgs,
...
}:
{
accounts = {
email.accounts = {
gmail = {
primary = true;
realName = "Matt Jallen";
address = "matt.l.jallen@gmail.com";
userName = "matt.l.jallen@gmail.com";
passwordCommand = "${pkgs.uutils-coreutils-noprefix}/bin/cat ${
config.sops.secrets."gmail-smtp-password".path
}";
flavor = "gmail.com";
thunderbird.enable = true;
smtp = {
tls = {
enable = false;
useStartTls = true;
};
host = "smtp.gmail.com";
port = lib.mkForce 465;
};
};
protonmail = {
realName = "Matt Jallen";
address = "jalle008@protonmail.com";
userName = "jalle008@protonmail.com";
passwordCommand = "${pkgs.uutils-coreutils-noprefix}/bin/cat ${
config.sops.secrets."protonmail-password".path
}";
smtp = {
tls = {
enable = false;
useStartTls = true;
};
host = "127.0.0.1";
port = lib.mkForce 1025;
};
};
};
};
}

63
modules/home/desktop/gnome/default.nix Executable file → Normal file
View File

@@ -2,11 +2,10 @@
config,
lib,
pkgs,
namespace,
...
}:
let
cfg = config.${namespace}.desktop.gnome;
cfg = config.mjallen.desktop.gnome;
in
{
imports = [ ./options.nix ];
@@ -23,38 +22,29 @@ in
gnomeExtensions.bing-wallpaper-changer
gnomeExtensions.dash-to-dock
gnomeExtensions.dash-to-panel
gnomeExtensions.caffeine
gnomeExtensions.gsconnect
gnomeExtensions.nasa-apod
gnomeExtensions.random-wallpaper
gnomeExtensions.tiling-assistant
gnomeExtensions.user-themes
gnomeExtensions.wikiart-wallpaper
gnomeExtensions.boatman-winboat-monitor
papirus-icon-theme
pop-gtk-theme
pkgs.mjallen.gnome-nebula-vpn
];
dconf = {
enable = true;
settings = {
"io/missioncenter/MissionCenter".performance-page-cpu-graph = 2; # logical processors
"org/gnome/desktop/interface" = {
clock-format = "12h";
color-scheme = "prefer-dark";
cursor-theme = lib.mkDefault "macOS";
enable-hot-corners = false;
font-antialiasing = "grayscale";
font-hinting = "slight";
gtk-theme = lib.mkDefault "Colloid-Dark";
icon-theme = lib.mkDefault "Colloid-Dark";
};
"org/gnome/desktop/interface".clock-format = "12h";
"org/gnome/desktop/interface".color-scheme = "prefer-dark";
"org/gnome/desktop/interface".cursor-theme = lib.mkDefault "macOS";
"org/gnome/desktop/interface".enable-hot-corners = false;
"org/gnome/desktop/interface".font-antialiasing = "grayscale";
"org/gnome/desktop/interface".font-hinting = "slight";
"org/gnome/desktop/interface".gtk-theme = lib.mkDefault "Colloid-Dark";
"org/gnome/desktop/interface".icon-theme = lib.mkDefault "Colloid-Dark";
"org/gnome/desktop/peripherals/mouse".accel-profile = "flat";
"org/gnome/desktop/peripherals/touchpad" = {
two-finger-scrolling-enabled = true;
tap-to-click = true;
};
"org/gnome/desktop/peripherals/touchpad".two-finger-scrolling-enabled = true;
"org/gnome/desktop/peripherals/touchpad".tap-to-click = true;
"org/gnome/mutter".experimental-features = [
"scale-monitor-framebuffer"
"variable-refresh-rate"
@@ -63,37 +53,20 @@ in
"org/gnome/shell".enabled-extensions = [
"allowlockedremotedesktop@kamens.us"
"appindicatorsupport@rgcjonas.gmail.com"
"caffeine@patapon.info"
"user-theme@gnome-shell-extensions.gcampax.github.com"
"tiling-assistant@leleat-on-github"
"dash-to-dock@micxgx.gmail.com"
"BingWallpaper@ineffable-gmail.com"
"gsconnect@andyholmes.github.io"
"nebula-vpn-status@mjallen"
];
"org/gnome/shell/extensions/bingwallpaper" = {
override-lockscreen-blur = true;
random-mode-enabled = false;
selected-image = "current";
revert-to-current-image = false;
};
"org/gnome/shell/extensions/caffeine" = {
enable-fullscreen = true;
enable-mpris = true;
inhibit-apps = [
"horizon-client.desktop"
];
};
"org/gnome/shell/extensions/dash-to-panel" = {
primary-monitor = 1;
multi-monitors = false;
};
"org/gnome/shell/extensions/gsconnect" = {
id = "4db35bd2-0dcd-42a3-9f77-ef3e8bb83182";
name = "matt-nixos";
};
"org/gnome/shell/extensions/bingwallpaper".override-lockscreen-blur = true;
"org/gnome/shell/extensions/bingwallpaper".random-mode-enabled = true;
"org/gnome/shell/extensions/bingwallpaper".revert-to-current-image = false;
"org/gnome/shell/extensions/dash-to-panel".primary-monitor = 1;
"org/gnome/shell/extensions/dash-to-panel".multi-monitors = false;
"org/gnome/shell/extensions/gsconnect".id = "4db35bd2-0dcd-42a3-9f77-ef3e8bb83182";
"org/gnome/shell/extensions/gsconnect".name = "matt-nixos";
"org/gnome/shell/extensions/user-theme".name = lib.mkDefault "Colloid-Dark";
"org/gnome/system/location".enabled = true;
"org/gtk/settings/file-chooser".clock-format = "12h";
};
};

4
modules/home/desktop/gnome/options.nix Executable file → Normal file
View File

@@ -1,7 +1,7 @@
{ lib, namespace, ... }:
{ lib, ... }:
with lib;
{
options.${namespace}.desktop.gnome = {
options.mjallen.desktop.gnome = {
enable = mkEnableOption "enable gnome settings";
};
}

View File

@@ -0,0 +1,410 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.mjallen.desktop.hyprland;
drawer = "nwg-drawer -fm nautilus -term kitty -mb 10 -mt 10 -ml 10 -mr 10 -pbuseicontheme -i ${cfg.iconThemeName}";
in
{
imports = [
./options.nix
./packages.nix
./theme.nix
./variables.nix
];
config = mkIf cfg.enable {
services = {
hyprpolkitagent.enable = true;
};
programs = {
vscode.profiles.default.userSettings."window"."titleBarStyle" = "custom";
};
wayland.windowManager.hyprland = {
enable = true;
xwayland.enable = true;
systemd.enable = true;
plugins = with pkgs.hyprlandPlugins; [
hyprgrass
];
settings = {
"$mod" = "SUPER";
# Mouse
# mouse_[up|down] - scroll wheel
# middle_mouse - 274
# thumb_up - 276
# thumb_down - 275
# l -> locked, will also work when an input inhibitor (e.g. a lockscreen) is active.
# r -> release, will trigger on release of a key.
# e -> repeat, will repeat when held.
# n -> non-consuming, key/mouse events will be passed to the active window in addition to triggering the dispatcher.
# m -> mouse, see below.
# t -> transparent, cannot be shadowed by other binds.
# i -> ignore mods, will ignore modifiers.
# s -> separate, will arbitrarily combine keys between each mod/key, see [Keysym combos](#keysym-combos) above.
# d -> has description, will allow you to write a description for your bind.
# p -> bypasses the app's requests to inhibit keybinds.
# https://wiki.hyprland.org/Configuring/Binds/
# https://wiki.hyprland.org/Configuring/Binds/#mouse-buttons
bind = [
"$mod, Return, exec, ${cfg.defaultApps.terminal.pname}"
"$mod, SPACE, exec, wofi --show drun"
", xf86Search, exec, wofi --show drun"
"$mod, Q, killactive, "
"$mod, M, exec, wlogout --protocol layer-shell"
"$mod, E, exec, ${cfg.defaultApps.fileExplorer.pname}"
"$mod, V, togglefloating, "
"$mod, D, exec, ${drawer}"
"$mod, P, pseudo, " # dwindle
"$mod, S, togglesplit, " # dwindle
"$mod SHIFT, Q, exec, hyprlock"
"$mod SHIFT, 4, exec, hyprshot -m region --clipboard-only"
"$mod, F, fullscreen, 1"
"$mod SHIFT, F, fullscreen, 0"
"$mod SHIFT, E, exec, smile"
"$mod, mouse:276, movecurrentworkspacetomonitor, ${cfg.display1.input}"
"$mod, mouse:275, movecurrentworkspacetomonitor, ${cfg.display2.input}"
# alt-tab between workspaces on active monitor
"$mod, Tab, workspace, m+1"
"$mod SHIFT, Tab, workspace, m-1"
"$mod, h, movefocus, l"
"$mod, l, movefocus, r"
"$mod, k, movefocus, u"
"$mod, j, movefocus, d"
"$mod, 1, workspace, 1"
"$mod, 2, workspace, 2"
"$mod, 3, workspace, 3"
"$mod, 4, workspace, 4"
"$mod, 5, workspace, 5"
"$mod, 6, workspace, 6"
"$mod, 7, workspace, 7"
"$mod, 8, workspace, 8"
"$mod, 9, workspace, 9"
"$mod, 0, workspace, 10"
"$mod ALT, 1, movetoworkspace, 1"
"$mod ALT, 2, movetoworkspace, 2"
"$mod ALT, 3, movetoworkspace, 3"
"$mod ALT, 4, movetoworkspace, 4"
"$mod ALT, 5, movetoworkspace, 5"
"$mod ALT, 6, movetoworkspace, 6"
"$mod ALT, 7, movetoworkspace, 7"
"$mod ALT, 8, movetoworkspace, 8"
"$mod ALT, 9, movetoworkspace, 9"
"$mod ALT, 0, movetoworkspace, discord"
"$mod CTRL, l, resizeactive, 10 0"
"$mod CTRL, h, resizeactive, -10 0"
"$mod CTRL, k, resizeactive, 0 -10"
"$mod CTRL, j, resizeactive, 0 10"
"$mod SHIFT, l, movewindow, r"
"$mod SHIFT, h, movewindow, l"
"$mod SHIFT, k, movewindow, u"
"$mod SHIFT, j, movewindow, d"
"$mod, b, exec, ${cfg.defaultApps.browser.pname}"
];
bindm = [
# Move/resize windows with mod + LMB/RMB and dragging
"$mod, mouse:272, movewindow"
"$mod, mouse:273, resizewindow"
# middle mouse will grab a window, mod + middle mouse will close it
"$mod SHIFT, mouse:274, movewindow"
];
bindel = [
", XF86AudioRaiseVolume, exec, wpctl set-volume -l 1.5 @DEFAULT_AUDIO_SINK@ 5%+"
", XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"
];
bindl = [
", XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"
", XF86AudioPlay, exec, playerctl play-pause"
", XF86AudioPrev, exec, playerctl previous"
", XF86AudioNext, exec, playerctl next"
", XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"
", XF86MonBrightnessUp, exec, brightnessctl set +5%"
", XF86MonBrightnessDown, exec, brightnessctl set 5%-"
"$mod, XF86MonBrightnessUp, exec, brightnessctl -d kbd_backlight set +10%"
"$mod, XF86MonBrightnessDown, exec, brightnessctl -d kbd_backlight set 10%-"
];
monitor = cfg.monitor;
monitorv2 = cfg.monitorv2 or { };
render = {
cm_fs_passthrough = 1;
};
misc = {
vrr = 1;
};
general = {
gaps_in = 5;
gaps_out = 10;
border_size = 1;
"col.active_border" = "rgb(8aadf4) rgb(24273A) rgb(24273A) rgb(8aadf4) 45deg";
"col.inactive_border" = "rgb(24273A) rgb(24273A) rgb(24273A) rgb(24273A) 45deg";
layout = "dwindle";
allow_tearing = true;
};
decoration = {
rounding = 10;
blur = {
enabled = true;
size = 2;
passes = 2;
new_optimizations = true;
xray = false;
};
# drop_shadow = "yes";
# shadow_range = 4;
# shadow_render_power = "3";
# "col.shadow" = "rgba(1a1a1aee)";
};
animations = {
enabled = "yes";
bezier = [
"overshot, 0.05, 0.9, 0.1, 1.05"
"smoothOut, 0.36, 0, 0.66, -0.56"
"smoothIn, 0.25, 1, 0.5, 1"
];
animation = [
"windows, 1, 5, overshot, slide"
"windowsOut, 1, 4, smoothOut, slide"
"windowsMove, 1, 4, default"
"border, 1, 10, default"
"fade, 1, 10, smoothIn"
"fadeDim, 1, 10, smoothIn"
"workspaces, 1, 6, default"
];
};
dwindle = {
pseudotile = "yes";
preserve_split = "yes";
};
misc = {
force_default_wallpaper = 0;
};
workspace = cfg.workspace;
windowrule = [
"float, title:(file_progress)"
"float, title:(.*[Cc]onfirm.*)"
"float, title:(.*[Dd]ialog.*)"
"float, title:(.*[Dd]ownload.*)"
"float, title:(.*[Nn]otification.*)"
"float, title:(.*[Ee]rror.*)"
"float, title:(.*[Ss]plash.*)"
"float, title:(.*[Cc]onfirmreset.*)"
"float, title:(.*[Ss]ign [Ii]n - .*)"
"float, title:(.*[Oo]pen [Ff]ile.*)"
"float, title:(.*branchdialog.*)"
"float, class:(.*pavucontrol.*)"
"move onscreen cursor 0% 0%, class:(.*pavucontrol.*)"
"float, class:(.*[Oo]verskride.*)"
"float, class:(.*FileRoller.*)"
"float, class:(.*wlogout.*)"
"idleinhibit stayfocused, title:(.*mpv.*)"
"float, class:(.*nm-connection-editor.*)"
"move onscreen cursor 0% 0%, class:(.*nm-connection-editor.*)"
"float, title:(Media viewer)"
"float, class:(it.mijorus.smile),title:(Smile)"
"float, class:(.blueman-manager-wrapped)$,title:(Bluetooth Devices)"
# Picture in picture windows
"float, title:(.*Picture-in-Picture.*)"
"pin, title::(.*Picture-in-Picture.*)"
# discord/vesktop
"workspace: name:discord, class:(.*vesktop)"
"float, class:(.*vesktop),title:(.*Discord Popout.*)"
"pin, class:(.*vesktop),title:(.*Discord Popout.*)"
# Music
"workspace: name:discord, class:(Apple Music.*)"
# Steam
"float, class:(.*[Ss]team), title:(.*[Ss]team.*)$"
"workspace name:steam silent, class:(.*[Ss]team), title:(.*[Ss]team.*)$"
"tile, class:(.*[Ss]team), title:(.*[Ss]team.*)$"
"float, class:(.*steam),title:(.*Friends List.*)"
# Code
"pin, class:(.*codium.*),title:(Save As)"
"float, class:(.*codium.*),title:(Save As)"
"float, class:(xdg-desktop-portal-gtk),title:(Open Workspace from File)"
# Game Tearing??? https://wiki.hyprland.org/Configuring/Tearing/
"immediate, class:(.*gamescope)"
# vmware
# this tag will set the below options to the vdi window
# this will have it auto open as a 2160x7680 window
# and makes multi-monitor work
"tag +horizonrdp, class:(.*[Hh]orizon-client),title:(USPS Next VDI)"
"noanim, tag:horizonrdp"
"noblur, tag:horizonrdp"
"norounding, tag:horizonrdp"
"noshadow, tag:horizonrdp"
"immediate, tag:horizonrdp"
"allowsinput, tag:horizonrdp"
"noborder, tag:horizonrdp"
"nodim, tag:horizonrdp"
"nomaxsize, tag:horizonrdp"
"renderunfocused, tag:horizonrdp"
"idleinhibit, tag:horizonrdp"
"float, tag:horizonrdp"
# "size 2160 7680, tag:horizonrdp"
# "move onscreen 0 0, tag:horizonrdp"
# float the vmware window cause its annoying to use in fullscreen
"float, class:(.*[Hh]orizon-client),title:([Oo]mnissa [Hh]orizon [Cc]lient)"
"tag +waydroid, class:([Ww]aydroid.*)"
"float, tag:waydroid"
"pin, tag:waydroid"
]
++ cfg.windowRule;
plugin = {
touch_gestures = {
# The default sensitivity is probably too low on tablet screens,
# I recommend turning it up to 4.0
sensitivity = "4.0";
# must be >= 3
workspace_swipe_fingers = "3";
# switching workspaces by swiping from an edge, this is separate from workspace_swipe_fingers
# and can be used at the same time
# possible values: l, r, u, or d
# to disable it set it to anything else
workspace_swipe_edge = "d";
# in milliseconds
long_press_delay = "400";
# resize windows by long-pressing on window borders and gaps.
# If general:resize_on_border is enabled, general:extend_border_grab_area is used for floating
# windows
resize_on_border_long_press = true;
# in pixels, the distance from the edge that is considered an edge
edge_margin = "10";
# emulates touchpad swipes when swiping in a direction that does not trigger workspace swipe.
# ONLY triggers when finger count is equal to workspace_swipe_fingers
#
# might be removed in the future in favor of event hooks
emulate_touchpad_swipe = false;
experimental = {
# send proper cancel events to windows instead of hacky touch_up events,
# NOT recommended as it crashed a few times, once it's stabilized I'll make it the default
send_cancel = "0";
};
hyprgrass-bind = [
# swipe left from right edge
", edge:r:l, workspace, +1"
# swipe up from bottom edge
", edge:d:u, exec, ${cfg.defaultApps.browser.pname}"
# swipe down from left edge
", edge:l:d, exec, pactl set-sink-volume @DEFAULT_SINK@ -4%"
# swipe down with 4 fingers
", swipe:4:d, killactive"
# swipe diagonally left and down with 3 fingers
# l (or r) must come before d and u
", swipe:3:ld, exec, foot"
# tap with 3 fingers
", tap:3, exec, foot"
# longpress can trigger mouse binds:
", longpress:2, movewindow"
", longpress:3, resizewindow"
];
};
};
gestures = {
workspace_swipe = true;
workspace_swipe_cancel_ratio = "0.15";
};
input = {
kb_layout = "us";
kb_variant = "";
kb_model = "";
kb_options = "";
kb_rules = "";
numlock_by_default = true;
follow_mouse = 1;
touchpad = {
clickfinger_behavior = 1;
natural_scroll = "yes";
};
sensitivity = 0; # -1.0 - 1.0, 0 means no modification.
};
experimental = {
xx_color_management_v4 = true;
};
debug = {
full_cm_proto = true;
disable_logs = true;
disable_scale_checks = true;
};
};
extraConfig = ''
exec-once = dbus-update-activation-environment --systemd --all
exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
exec-once = /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
exec-once = xhost +SI:localuser:root
exec-once = nwg-look -a
exec-once = nwg-dock-hyprland -d
''
+ cfg.extraConfig or '''';
};
};
}

View File

@@ -0,0 +1,147 @@
{ lib, pkgs, ... }:
with lib;
{
options.mjallen.desktop.hyprland = {
enable = mkEnableOption "enable hyprland desktop";
primaryDisplay = mkOption {
type = types.str;
default = "DP-1";
};
display1 = {
input = mkOption {
type = types.str;
default = "DP-1";
};
resolution = mkOption {
type = types.str;
default = "3840x2160";
};
refreshRate = mkOption {
type = types.str;
default = "240.00000";
};
};
display2 = {
input = mkOption {
type = types.str;
default = "DP-1";
};
resolution = mkOption {
type = types.str;
default = "3840x2160";
};
refreshRate = mkOption {
type = types.str;
default = "240.00000";
};
};
wallpaper = mkOption {
type = with types; listOf str;
default = [ ];
description = "list of hyprland wallpaper configs";
};
monitor = mkOption {
type = with types; listOf str;
default = [ ];
description = "list of hyprland monitor configs";
};
monitorv2 = mkOption {
type = with types; listOf str;
default = [ ];
description = "list of hyprland monitorv2 configs";
};
workspace = mkOption {
type = with types; listOf str;
default = [ ];
description = "list of hyprland workspace definitions";
};
windowRule = mkOption {
type = with types; listOf str;
default = [ ];
description = "list of hyprland window rules";
};
extraConfig = mkOption {
type = with types; str;
default = '''';
description = "any extra options";
};
iconThemeName = mkOption {
type = types.str;
default = "Colloid-Dark";
};
gtkThemeName = mkOption {
type = types.str;
default = "Colloid-Dark";
};
defaultApps = mkOption {
type = types.submodule {
options = {
browser = mkOption {
type = types.package;
default = pkgs.firefox;
};
editor = mkOption {
type = types.package;
default = pkgs.micro;
};
fileExplorer = mkOption {
type = types.package;
default = pkgs.nemo;
};
visual = mkOption {
type = types.package;
default = pkgs.vscodium;
};
terminal = mkOption {
type = types.package;
default = pkgs.kitty;
};
office = mkOption {
type = types.package;
default = pkgs.onlyoffice-bin_latest;
};
video = mkOption {
type = types.package;
default = pkgs.vlc;
};
imageViewer = mkOption {
type = types.package;
default = pkgs.nomacs;
};
};
};
description = "Default applications used across the system.";
};
hyprIdle = {
lockScreenTimer = mkOption {
type = with types; int;
default = 300;
};
screenOffTimer = mkOption {
type = with types; int;
default = 900;
};
suspendTimer = mkOption {
type = with types; int;
default = 1800;
};
};
};
}

View File

@@ -0,0 +1,74 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.mjallen.desktop.hyprland;
in
{
config = mkIf cfg.enable {
home.packages = with pkgs; [
box64
brightnessctl
ddcutil
dunst
egl-wayland
file-roller
glib
gnome-calculator
gnome-calendar
gnome-disk-utility
gnome-firmware
gnome-firmware-updater
gnome-font-viewer
gnome-logs
gnome-photos
gnome-tweaks
gnome-weather
gsettings-desktop-schemas
hyprcursor
hyprland
hyprpaper
hyprshot
hyprsysteminfo
kdePackages.qtmultimedia
libnotify
libz
mako
meson
nautilus
networkmanagerapplet
nm-tray
nomacs
nwg-look
overskride
pamixer
pavucontrol
playerctl
polkit
polkit_gnome
qt5.qtwayland
qt6.qtwayland
rofi-wayland
waybar
wayland-protocols
wayland-utils
waypaper
wev
wl-clipboard
wlogout
wlroots
xdg-desktop-portal-hyprland
xdg-desktop-portal-gtk
xdg-desktop-portal-wlr
xorg.xhost
xsettingsd
xwayland
pkgs.mjallen.pipewire-python
];
};
}

View File

@@ -0,0 +1,46 @@
{
config,
lib,
namespace,
...
}:
let
cfg = config.${namespace}.desktop.hyprland;
in
{
imports = [ ../../options.nix ];
config = lib.mkIf cfg.enable {
services.hypridle = {
enable = true;
settings = {
general = {
before_sleep_cmd = "loginctl lock-session"; # lock before suspend.
after_sleep_cmd = "hyprctl dispatch dpms on"; # to avoid having to press a key twice to turn on the display.
ignore_dbus_inhibit = false;
lock_cmd = "pidof hyprlock || hyprlock"; # avoid starting multiple hyprlock instances.
};
listener = [
# {
# timeout = 300; # 5min
# on-timeout = "brightnessctl -s set 10"; # set monitor backlight to minimum, avoid 0 on OLED monitor.
# on-resume = "brightnessctl -r"; # monitor backlight restore.
# }
{
timeout = cfg.hyprIdle.lockScreenTimer;
on-timeout = "loginctl lock-session"; # lock screen when timeout has passed
}
{
timeout = cfg.hyprIdle.screenOffTimer;
on-timeout = "hyprctl dispatch dpms off"; # screen off when timeout has passed
on-resume = "hyprctl dispatch dpms on"; # screen on when activity is detected after timeout has fired.
}
{
timeout = cfg.hyprIdle.suspendTimer;
on-timeout = "systemctl suspend"; # suspend pc
}
];
};
};
};
}

View File

@@ -0,0 +1,52 @@
{
config,
lib,
namespace,
...
}:
let
cfg = config.${namespace}.desktop.hyprland;
in
{
imports = [ ../../options.nix ];
config = lib.mkIf cfg.enable {
programs.hyprlock = {
enable = true;
settings = {
background = [
{
monitor = "";
path = cfg.wallpaper; # supports png, jpg, webp (no animations, though)
color = "rgba(25, 20, 20, 1.0)";
# all these options are taken from hyprland, see https://wiki.hyprland.org/Configuring/Variables/#blur for explanations
blur_passes = "3"; # 0 disables blurring
blur_size = "7";
noise = "0.0117";
contrast = "0.8916";
brightness = "0.8172";
vibrancy = "0.1696";
vibrancy_darkness = "0.0";
}
];
input-field = [
{
size = "200, 50";
position = "0, -80";
monitor = cfg.primaryDisplay;
dots_center = true;
fade_on_empty = true;
font_color = "rgb(202, 211, 245)";
inner_color = "rgb(91, 96, 120)";
outer_color = "rgb(24, 25, 38)";
bothlock_color = -1;
outline_thickness = 5;
placeholder_text = ''<span foreground="##cad3f5">Password...</span>'';
shadow_passes = 2;
}
];
};
};
};
}

View File

@@ -0,0 +1,23 @@
{
config,
lib,
namespace,
...
}:
let
cfg = config.${namespace}.desktop.hyprland;
in
{
imports = [ ../../options.nix ];
config = lib.mkIf cfg.enable {
services.hyprpaper = {
enable = true;
settings = {
preload = [ "/run/wallpaper.jpg" ];
wallpaper = ",/run/wallpaper.jpg";
splash = false;
};
};
};
}

View File

@@ -0,0 +1,103 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.mjallen.desktop.hyprland;
themeSize = "compact"; # [ "standard" "compact" ]
themeAccent = "all"; # [ "default" "purple" "pink" "red" "orange" "yellow" "green" "teal" "grey" "all" ]
themeVariant = "nord"; # [ "nord" "dracula" "gruvbox" "everforest" "catppuccin" "all" "black" "rimless" "normal" "float" ]
themeColor = "dark"; # [ "standard" "light" "dark" ]
iconThemeVariant = "all"; # [ "default" "purple" "pink" "red" "orange" "yellow" "green" "teal" "grey" "all" ]
iconScheme = "nord"; # [ "default" "nord" "dracula" "gruvbox" "everforest" "catppuccin" "all" ]
# Cursor
cursorTheme = "macOS";
cursorThemePkg = pkgs.apple-cursor;
cursorSize = 24;
# GTK
# gtkThemeSize = themeSize;
# gtkThemeAccent = themeAccent;
# gtkThemeVariant = themeVariant;
# gtkThemeColor = themeColor;
gtkTheme = "Colloid-Dark-Compact-Nord";
gtkThemePkg = pkgs.colloid-gtk-theme.override {
sizeVariants = [ themeSize ];
colorVariants = [ themeColor ];
themeVariants = [ themeAccent ];
tweaks = [ themeVariant ];
};
# Icons
# iconThemeScheme = iconScheme;
iconTheme = "Colloid-Nord-Dark";
iconThemePkg = pkgs.colloid-icon-theme.override {
schemeVariants = [ iconScheme ];
colorVariants = [ iconThemeVariant ];
};
# Fonts
fontName = "JetBrainsMono NFM";
fontPackage = pkgs.nerd-fonts.jetbrains-mono;
fontSize = 12;
in
{
config = mkIf cfg.enable {
home = {
pointerCursor = {
gtk.enable = true;
package = cursorThemePkg;
name = cursorTheme;
size = cursorSize;
};
};
dconf = {
enable = true;
settings = {
"org/gnome/desktop/interface".color-scheme = "prefer-dark";
"org/gnome/desktop/interface".cursor-theme = cursorTheme;
"org/gnome/desktop/interface".gtk-theme = gtkTheme;
"org/gnome/desktop/interface".icon-theme = iconTheme;
};
};
gtk = {
enable = true;
cursorTheme = {
name = cursorTheme;
package = cursorThemePkg;
};
theme = {
name = gtkTheme;
package = gtkThemePkg;
};
iconTheme = {
name = iconTheme;
package = iconThemePkg;
};
gtk3.extraConfig = {
gtk-application-prefer-dark-theme = true;
};
gtk4.extraConfig = {
gtk-application-prefer-dark-theme = true;
};
font = {
name = fontName;
package = fontPackage;
size = fontSize;
};
};
};
}

View File

@@ -0,0 +1,39 @@
{ config, lib, ... }:
with lib;
let
cfg = config.mjallen.desktop.hyprland;
in
{
config = mkIf cfg.enable {
home.sessionVariables = {
BROWSER = "${cfg.defaultApps.browser.pname}";
CLUTTER_BACKEND = "wayland";
EDITOR = "${cfg.defaultApps.editor.pname}";
VISUAL = "${cfg.defaultApps.visual.pname}";
ICON_THEME = cfg.iconThemeName;
GTK_CSD = "0";
GTK_THEME = cfg.gtkThemeName;
GTK_USE_PORTAL = "1";
HYPRCURSOR_THEME = config.home.pointerCursor.name;
HYPRCURSOR_SIZE = config.home.pointerCursor.size;
MOZ_ENABLE_WAYLAND = "1";
NIXOS_OZONE_WL = "1";
NIXOS_XDG_OPEN_USE_PORTAL = "1";
QT_AUTO_SCREEN_SCALE_FACTOR = "1";
QT_QPA_PLATFORM = "wayland-egl";
QT_QPA_PLATFORMTHEME = "gtk3";
QT_SCALE_FACTOR = "1";
QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
SDL_VIDEODRIVER = "wayland";
TERMINAL = "${cfg.defaultApps.terminal.pname}";
XCURSOR_THEME = config.home.pointerCursor.name;
XCURSOR_SIZE = config.home.pointerCursor.size;
XDG_CACHE_HOME = "\${HOME}/.cache";
XDG_CONFIG_HOME = "\${HOME}/.config";
XDG_CURRENT_DESKTOP = "Hyprland";
XDG_DATA_HOME = "\${HOME}/.local/share";
XDG_SESSION_DESKTOP = "Hyprland";
XDG_SESSION_TYPE = "wayland";
};
};
}

View File

@@ -1,100 +0,0 @@
{
config,
lib,
pkgs,
namespace,
...
}:
let
cfg = config.${namespace}.desktop.plasma;
in
{
imports = [ ./options.nix ];
config = lib.mkIf cfg.enable {
home.packages =
(with pkgs.kdePackages; [
plasma-browser-integration
kdeplasma-addons
])
++ [
# Caffeine-like tray applet: inhibits screensaver and sleep on demand
pkgs.caffeine-ng
];
programs.plasma = {
enable = true;
workspace = {
colorScheme = "BreezeDark";
cursor = {
theme = "breeze_cursors";
size = 24;
};
iconTheme = "breeze-dark";
theme = "breeze-dark";
lookAndFeel = "org.kde.breezedark.desktop";
# Explicitly set Breeze to prevent QT_STYLE_OVERRIDE=kvantum (set by
# Stylix's qt6ct target) from being picked up by KWin/plasmashell, which
# would cause a fatal "module kvantum is not installed" QML error and
# leave the desktop blank.
widgetStyle = "Breeze";
};
# input.mice and input.touchpads require device-specific vendorId/productId
# identifiers — configure those per-host in the home config instead.
kscreenlocker = {
autoLock = true;
timeout = 10;
};
kwin = {
effects = {
shakeCursor.enable = false;
};
nightLight = {
enable = true;
mode = "automatic";
temperature = {
day = 6500;
night = 3500;
};
};
virtualDesktops = {
number = 4;
rows = 1;
};
};
panels = [
{
location = "bottom";
floating = true;
height = 44;
widgets = [
"org.kde.plasma.kickoff"
"org.kde.plasma.icontasks"
"org.kde.plasma.marginsseparator"
"org.kde.plasma.systemtray"
"org.kde.plasma.digitalclock"
];
}
];
shortcuts = {
kwin = {
"Switch to Desktop 1" = "Meta+1";
"Switch to Desktop 2" = "Meta+2";
"Switch to Desktop 3" = "Meta+3";
"Switch to Desktop 4" = "Meta+4";
"Window to Desktop 1" = "Meta+Shift+1";
"Window to Desktop 2" = "Meta+Shift+2";
"Window to Desktop 3" = "Meta+Shift+3";
"Window to Desktop 4" = "Meta+Shift+4";
"Toggle Overview" = "Meta+Tab";
};
};
};
};
}

View File

@@ -1,7 +0,0 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.desktop.plasma = {
enable = mkEnableOption "KDE Plasma 6 home-manager configuration via plasma-manager";
};
}

View File

@@ -0,0 +1,62 @@
{
# Nord colors
# Opacity Hex alpha
# 100% FF
# 75% BF
# 50% 80
# 25% 40
# 10% 1A
# 0% 00
polarNight = {
nord0 = "#2e3440";
nord1 = "#3b4252";
nord2 = "#434c5e";
nord3 = "#4c566a";
};
snowStorm = {
nord4 = "#d8dee9";
nord5 = "#e5e9f0";
nord6 = "#eceff4";
};
frost = {
nord7 = "#8fbcbb";
nord8 = "#88c0d0";
nord9 = "#81a1c1";
nord10 = "#5e81ac";
};
aurora = {
nord11 = "#bf616a";
nord12 = "#d08770";
nord13 = "#ebcb8b";
nord14 = "#a3be8c";
nord15 = "#b48ead";
};
defaultOpacity = "opacity: 0.85;";
defaultBorderRadius = "border-radius: 1rem;";
defaultCenterOptions = ''
padding-top: 0.2rem;
padding-bottom: 0.2rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
margin: 3px 0;
'';
borderRight = ''
padding-top: 0.2rem;
padding-bottom: 0.2rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
margin: 3px 0;
border-radius: 0rem 1rem 1rem 0rem;
margin-right: 0.5rem;
'';
borderLeft = ''
padding-top: 0.2rem;
padding-bottom: 0.2rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
margin: 3px 0;
border-radius: 1rem 0rem 0rem 1rem;
margin-left: 0.5rem;
'';
}

View File

@@ -1,79 +0,0 @@
_: {
programs = {
gpg = {
enable = true;
scdaemonSettings = {
disable-ccid = true;
pcsc-shared = true;
};
publicKeys = [
{
text = ''
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGipyhUBEADCal3wKbTCJHZ7qBTxe2zrJPGV9vu6V4+x/MBQq07jd33RrdgI
5R/YSviZLyTjY84xk+XTpxWe6PNxcrNlPrJgqR48UNYiG7rH2VWg073KQZaAEeX4
DRmfANtds9m9N9CadMv1XcHvVkEz+UlD3yYGNwrd1ZnWVBpUobIyZvFrVjpGBtOg
jgW3NepPn9SfguF+PV4t3Tn2iAFsDa/U3RpkMvYtfxgtxJPvbaVNc3Zq7UDWaXta
zZ7y/o5figLUn3q/2JrbzeXmNpShkTixXJHJwSjoD/27I6fVFz4EfwkBfyo8B9yX
goekY00wym0oIT2Jb5ZLFh8b60mX/ezUJcMhDkqTG77caRDRxw76yVxc0Jp+r+xZ
3ckcrdm+/UtnnD3VO0ktvhb4pU93Pj0PJhrm7qVZS/SEJ67vUx8Aem3aMWgSRsl5
51h08US7yfPaMNqKX9d2KaLSTGnQ2BSoXJEmbIqvlvazUTqVk85TDWZEYXgc2S2K
phyDfJiVmmUWekgrYE/Fyx0QJkCA6z8cNQWwpRe7oJG1VrnRjhIcuL+EpJb9Ytd9
NT39/6/rBot2iGmn64vdyqirji2shuLwrp34A9KWY3XUC5zv0GlHCFsF8lraFzKz
AsYv2cLRRja4kyuzoSwoHgXYu1MsifZHgF2+Y0sFW+odRqlo0A9ziXkB9QARAQAB
tCNNYXR0aGV3IEphbGxlbiA8amFsbGUwMDhAcHJvdG9uLm1lPokCVwQTAQgAQRYh
BMvLmximuJMLC2q/0cy4y+swYzaEBQJoqcoVAhsDBQkSzAMABQsJCAcCAiICBhUK
CQgLAgQWAgMBAh4HAheAAAoJEMy4y+swYzaEsrkP/3c/kW0QNv79dEgEHhpJy7nP
XyB8fTGr804Gq0ffaPgmn0G8iNzg9pgjgrA8oHpoP0ZbmKaO7JkpHsS0+fpMNuFE
dhthWgsYLAnC6rZb4Ib/b+wbUH19CJJIt6Rj9uqH3vDCxeG5TfiJnVA+x3O6VaM1
K2oyrXaXDrUKxFlLlnMOsdfRZwXoyYPuJm8Lip5GawIakHGV17FdwdZOcfy0JtdH
CwI0K5D390lQ2WNjIJx0vI8E3QYubciOjDzS+KNB12q4sSY6O3fKTXsi80JZ/y4C
z1dwsRxMlndBbK1SBJFmczRy4dZC0WbNaxEF2qnM7RENDpB+73HQ1/mSsFaTu609
9clDDjwCAUu4UrbL5veWLB+b+xUNy6Sgkbal1Q+pgGSTF9H8plGN0ymijC9xNmvX
JeeSTvfnzAThiJubIpZYLkoMmlyEiGrc4CQDdfbsVR9xFGZcau5akcW3e45kiOZC
is1H55HsXlBpezanvJCnX7ZEQlRP8vdquHADvgERf3D/qlVAgD/lGE8bzBKJxt1P
kxXvGZFxF0BCN/GCoYib1w3WJeTyxGlf9wHkwa/COjasEBGzsnkLToET637WdmHr
hgnTVm6CQQW4EUw6gOznDNQvd1EipAwJB7iEm2PneEAwauWeQN38+hc7QVIEqXRk
wdfxlU/9WD+rh6FfQ7d3uQINBGipyhUBEADUGJQW/FUWo3R6X7+Quv3TqZfVXJB/
bjX9Uv8SKOomUBAjQK9TklaZJvNiaD4qYV93kPfpclGhxdGahXVBC7SdGenaFig4
4WwUe5vrKIGbbfcC+dBW/8eAqueKjCWe6wFqXbKat1/4Y1atsFZX5Lf/GEi1VNXu
0NkSPKAqTdXyXrTuC7D5wARGdavbxO3BcF1NqpIPHiROQoOyP10jx8xcMYGoVIIr
uscckoqhdbyEua9m9zFesYyqiCfJeSCK8O4w7BU3dgHUz53hfeuNuGBPb/dQ9Ent
qKwrMxsPWJC9qN19Wvvsgy89QLn0CWmcUsmc/f4mWnKoSH9AZCuVCSgoSjknXGj0
MPBI6jY1gaqZAuyMH24ekUAdUtNK5IuPPPxrlE1ljAWSxvFGG7Dj4p78ELJizlT7
XXOtAPggwCFfXauQD+v43ILg5Zxz6fjTd0TZ8iSypqiZIQgQoiUUGcDZmjHhRHNj
GHlqmRECyef6mZVhb3zlyzAcQycnZvHtXFMmyrvfFs21pX9WEF3lQFDapnMAwODE
2BLQWrh2ed++/An3DJOh/Iwv9WBatDagMObJi3lsUszJ0l5iwteJOfXMR72zV+m7
7QzaEdGFDlNRSMmtjni7FLWbDA/CkihRsUtqgrXgj91oqLga0GmjlNnIZGwhcDUx
Yuo+kIRRM4DSDwARAQABiQI8BBgBCAAmFiEEy8ubGKa4kwsLar/RzLjL6zBjNoQF
AmipyhUCGwwFCRLMAwAACgkQzLjL6zBjNoS5/Q//SPmdM26wUq50A8QTuQ3r8xpc
3oGCdBAP7QJk6+W1kJcGVP4r2bFylbWUGyHWuhKTs47r6fh3Sd9MtmGQgtEEbGFW
a4pxaYBRPYGHvJuVwb4IvOzDuVj8yabk0c688jz2Wv1DW3+z4623TI2JeOfEoOqg
u4DGySVZkXA/F01HShP0QsGEGRkklEvysD101GbJrQuQ6ssHiFVF1m+/w+I8fYzP
F9BC61DOEabm62YjELf/xvKQ6aT8rB0F2p9udOmIlw9lkAJMsEz4mB5f4Bq/1vGt
aZ7K0tytT3aeTFFgLwtBPcjWvHavEp4/CDXUL58v/X5R+E6Z03zsWwspsACacblq
RW15f3aKI2j7u/w6oJ/iJbEXtZq/Hv1XACyZ/kD9WcYIPsNikCS6vSsnj9Hk/o88
yG0rQ6v9L7s9gqWm1IajnDQD6g7n3H4pXGJsE4sBGg6FfE0OJDcd4VqZGJGMz9rr
+V+3zUxs/RIVWTCdoVPTLBWrtsKbwObgE7SdkaUt4/1uhtdkR++brhr2uz3il0Kt
2QZAXJDcK2xzcOEz41lhclgMxUzjYTggQvpD8lu70xlGqgm94NJCBctPx6xJAN9f
H6dELHDuLAwB+vKB9YJIvMVaPNKRam8wGnZ0EKHUFvmIqqSOE8GR1LmgslYRNidv
ywgEIPcOpJGzt3TQprM=
=i8do
-----END PGP PUBLIC KEY BLOCK-----
'';
trust = "ultimate";
}
];
};
};
services = {
gpg-agent = {
enable = true;
enableZshIntegration = true;
enableSshSupport = true;
};
};
}

145
modules/home/home/default.nix Executable file → Normal file
View File

@@ -2,112 +2,58 @@
config,
lib,
pkgs,
namespace,
hasDestopEnvironment ? true,
system,
...
}:
let
inherit (lib.${namespace}) enabled;
isArm = ("aarch64-linux" == system) || ("aarch64-darwin" == system);
# Non-login / system accounts (root, nixos installer, etc.) should not get
# desktop packages, tmux, nh, kdeconnect, nextcloud-client, etc.
# Detect them by username so individual host home files are not needed.
isSystemUser = lib.elem config.home.username [
"root"
"nixos"
];
in
{
home = {
enableNixpkgsReleaseCheck = lib.mkDefault false;
homeDirectory = lib.mkDefault (
if config.home.username == "root" then "/root" else "/home/${config.home.username}"
);
packages =
with pkgs;
(
if isSystemUser then
[ ]
else
[
age
clinfo
cpufetch
dbus
deadnix
lm_sensors
nano
nebula
nix-prefetch-scripts
nixfmt
pciutils
proton-pass-cli
proton-vpn-cli
proton-vpn
protonup-ng
rsync
smartmontools
sops
tailscale
tree
usbutils
vim
vulkan-tools
wget
]
++ (
if hasDestopEnvironment then
[
boxbuddy
cider-2
stable.chromium
firefox
gamescope
gamescope-wsi
gparted
mission-center
parted
vesktop
]
++ (
if isArm then
[ ]
else
[
proton-pass
]
)
else
[ ]
)
);
file = {
".face".source = "${pkgs.${namespace}.profile-pic}/profile-pic";
};
homeDirectory = lib.mkDefault "/home/${config.home.username}";
packages = with pkgs; [
age
chromium
clinfo
cpufetch
deadnix
firefox
gamescope
gamescope-wsi
goverlay
gparted
lm_sensors
mission-center
nano
nixfmt-rfc-style
pciutils
protonup
rsync
smartmontools
sops
tailscale
tree
usbutils
vesktop
vim
vulkan-tools
wget
];
stateVersion = lib.mkDefault "23.11";
};
programs = {
# nix-index-database is not available in all home configs (e.g. iso-minimal
# standalone homes don't load the nix-index-database HM module).
# Set it per-host in homes that explicitly load the module.
btop = {
enable = lib.mkDefault (!isSystemUser);
package = pkgs.btop;
btop.enable = lib.mkDefault true;
fastfetch.enable = lib.mkDefault true;
home-manager.enable = lib.mkDefault true;
java = {
enable = lib.mkDefault true;
};
fastfetch.enable = lib.mkDefault (!isSystemUser);
home-manager = lib.mkDefault enabled;
java.enable = lib.mkDefault (!isSystemUser);
mangohud.enable = lib.mkDefault (hasDestopEnvironment && !isSystemUser);
password-store.enable = lib.mkDefault (!isSystemUser);
mangohud.enable = lib.mkDefault true;
password-store.enable = true;
nh = {
enable = lib.mkDefault (!isSystemUser);
enable = true;
flake = "/etc/nixos";
clean = {
enable = lib.mkDefault (!isSystemUser);
enable = true;
extraArgs = "--keep 5";
};
};
@@ -124,7 +70,7 @@ in
};
tmux = {
enable = lib.mkDefault (!isSystemUser);
enable = lib.mkDefault true;
terminal = "screen-256color";
sensibleOnTop = true;
focusEvents = true;
@@ -146,7 +92,7 @@ in
tmuxPlugins.better-mouse-mode
];
extraConfig = ''
set -g status-right '#[fg=black,bg=color15] #{cpu_percentage} %H:%M '
set -g status-right '#[fg=black,bg=color15] #{cpu_percentage} %H:%M '
run-shell ${pkgs.tmuxPlugins.cpu}/share/tmux-plugins/cpu/cpu.tmux
set -g default-terminal "xterm-256color"
set -ga terminal-overrides ",*256col*:Tc"
@@ -164,14 +110,11 @@ in
};
services = {
# nextcloud-client is disabled by default for all users; systems that
# want it enabled must opt in explicitly in their home configuration.
nextcloud-client.enable = lib.mkDefault false;
pass-secret-service.enable = lib.mkDefault (!isSystemUser);
nextcloud-client.enable = lib.mkDefault true;
pass-secret-service.enable = lib.mkDefault true;
kdeconnect = {
enable = lib.mkDefault (hasDestopEnvironment && !isSystemUser);
indicator = lib.mkDefault (hasDestopEnvironment && !isSystemUser);
package = pkgs.kdePackages.kdeconnect-kde;
enable = lib.mkDefault true;
indicator = lib.mkDefault true;
};
};
}

View File

@@ -1,84 +1,126 @@
{ config, lib, ... }:
with lib;
let
cfg = config.mjallen.programs.btop;
nord = import (lib.snowfall.fs.get-file "modules/home/desktop/theme/nord.nix");
in
{
config,
lib,
namespace,
...
}:
{
imports = [
(lib.${namespace}.mkHomeModule {
inherit config;
domain = "programs";
name = "btop";
moduleConfig = {
programs.btop = {
enable = true;
settings = {
truecolor = true;
force_tty = false;
presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty";
vim_keys = true;
rounded_corners = true;
graph_symbol = "braille";
graph_symbol_cpu = "default";
graph_symbol_mem = "default";
graph_symbol_net = "default";
graph_symbol_proc = "default";
shown_boxes = "cpu mem net proc";
update_ms = 2000;
proc_sorting = "cpu lazy";
proc_reversed = false;
proc_tree = false;
proc_colors = true;
proc_gradient = true;
proc_per_core = false;
proc_mem_bytes = true;
proc_cpu_graphs = true;
proc_info_smaps = false;
proc_left = false;
proc_filter_kernel = false;
cpu_graph_upper = "total";
cpu_graph_lower = "total";
cpu_invert_lower = true;
cpu_single_graph = false;
cpu_bottom = false;
show_uptime = true;
check_temp = true;
cpu_sensor = "Auto";
show_coretemp = true;
cpu_core_map = "";
temp_scale = "celsius";
base_10_sizes = false;
show_cpu_freq = true;
clock_format = "%X";
background_update = true;
custom_cpu_name = "";
disks_filter = "";
mem_graphs = true;
mem_below_net = false;
zfs_arc_cached = true;
show_swap = true;
swap_disk = true;
show_disks = true;
only_physical = true;
use_fstab = true;
zfs_hide_datasets = false;
disk_free_priv = false;
show_io_stat = true;
io_mode = false;
io_graph_combined = false;
io_graph_speeds = "";
net_download = 100;
net_upload = 100;
net_auto = true;
net_sync = true;
net_iface = "";
show_battery = true;
selected_battery = "Auto";
log_level = "WARNING";
};
};
imports = [ ./options.nix ];
config = mkIf cfg.enable {
programs.btop = {
enable = true;
settings = {
color_theme = "nord"; # todo
theme_background = true;
truecolor = true;
force_tty = false;
presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty";
vim_keys = true;
rounded_corners = true;
graph_symbol = "braille";
graph_symbol_cpu = "default";
graph_symbol_mem = "default";
graph_symbol_net = "default";
graph_symbol_proc = "default";
shown_boxes = "cpu mem net proc";
update_ms = 2000;
proc_sorting = "cpu lazy";
proc_reversed = false;
proc_tree = false;
proc_colors = true;
proc_gradient = true;
proc_per_core = false;
proc_mem_bytes = true;
proc_cpu_graphs = true;
proc_info_smaps = false;
proc_left = false;
proc_filter_kernel = false;
cpu_graph_upper = "total";
cpu_graph_lower = "total";
cpu_invert_lower = true;
cpu_single_graph = false;
cpu_bottom = false;
show_uptime = true;
check_temp = true;
cpu_sensor = "Auto";
show_coretemp = true;
cpu_core_map = "";
temp_scale = "celsius";
base_10_sizes = false;
show_cpu_freq = true;
clock_format = "%X";
background_update = true;
custom_cpu_name = "";
disks_filter = "";
mem_graphs = true;
mem_below_net = false;
zfs_arc_cached = true;
show_swap = true;
swap_disk = true;
show_disks = true;
only_physical = true;
use_fstab = true;
zfs_hide_datasets = false;
disk_free_priv = false;
show_io_stat = true;
io_mode = false;
io_graph_combined = false;
io_graph_speeds = "";
net_download = 100;
net_upload = 100;
net_auto = true;
net_sync = true;
net_iface = "";
show_battery = true;
selected_battery = "Auto";
log_level = "WARNING";
};
})
];
themes = {
nord = ''
theme[main_bg]="${nord.polarNight.nord0}"
theme[main_fg]="${nord.snowStorm.nord6}"
theme[title]="${nord.snowStorm.nord6}"
theme[hi_fg]="${nord.frost.nord7}"
theme[selected_bg]="${nord.polarNight.nord1}"
theme[selected_fg]="${nord.frost.nord7}"
theme[inactive_fg]="${nord.polarNight.nord2}"
theme[graph_text]="${nord.snowStorm.nord6}"
theme[meter_bg]="${nord.polarNight.nord1}"
theme[proc_misc]="${nord.snowStorm.nord6}"
theme[cpu_box]="${nord.aurora.nord15}"
theme[mem_box]="${nord.aurora.nord14}"
theme[net_box]="${nord.aurora.nord12}"
theme[proc_box]="${nord.aurora.nord11}"
theme[div_line]="${nord.polarNight.nord1}"
theme[temp_start]="${nord.aurora.nord14}"
theme[temp_mid]="${nord.aurora.nord13}"
theme[temp_end]="${nord.aurora.nord11}"
theme[cpu_start]="${nord.aurora.nord15}"
theme[cpu_mid]="${nord.aurora.nord12}"
theme[cpu_end]="${nord.aurora.nord11}"
theme[free_start]="${nord.aurora.nord14}"
theme[free_mid]="${nord.aurora.nord13}"
theme[free_end]="${nord.aurora.nord12}"
theme[cached_start]="${nord.aurora.nord14}"
theme[cached_mid]="${nord.aurora.nord13}"
theme[cached_end]="${nord.aurora.nord12}"
theme[available_start]="${nord.snowStorm.nord6}"
theme[available_mid]="${nord.aurora.nord11}"
theme[available_end]="${nord.aurora.nord11}"
theme[used_start]="${nord.aurora.nord14}"
theme[used_mid]="${nord.aurora.nord13}"
theme[used_end]="${nord.aurora.nord11}"
theme[download_start]="${nord.frost.nord8}"
theme[download_mid]="${nord.frost.nord8}"
theme[download_end]="${nord.aurora.nord12}"
theme[upload_start]="${nord.frost.nord7}"
theme[upload_mid]="${nord.frost.nord7}"
theme[upload_end]="${nord.aurora.nord12}"
theme[process_start]="${nord.aurora.nord15}"
theme[process_mid]="${nord.aurora.nord12}"
theme[process_end]="${nord.aurora.nord11}"
'';
};
};
};
}

View File

@@ -0,0 +1,7 @@
{ lib, ... }:
with lib;
{
options.mjallen.programs.btop = {
enable = mkEnableOption "enable btop";
};
}

View File

@@ -1,24 +0,0 @@
{
config,
lib,
pkgs,
namespace,
...
}:
{
imports = [
(lib.${namespace}.mkHomeModule {
inherit config;
domain = "programs";
name = "calibre";
moduleConfig = {
programs.calibre = {
enable = true;
plugins = with pkgs.${namespace}; [
dedrm
];
};
};
})
];
}

72
modules/home/programs/code/default.nix Executable file → Normal file
View File

@@ -2,25 +2,20 @@
config,
pkgs,
system,
hasDestopEnvironment ? true,
namespace,
...
}:
let
isArm = ("aarch64-linux" == system) || ("aarch64-darwin" == system);
isArm = "aarch64-linux" == system;
x86_only = with pkgs; [
vscode-extensions.redhat.vscode-xml
];
open-remote-ssh = pkgs.${namespace}.open-remote-ssh;
in
{
home.packages = with pkgs; [
nodejs_25
uv
];
programs = {
vscode = {
enable = hasDestopEnvironment;
enable = true;
package = pkgs.vscodium;
mutableExtensionsDir = false;
profiles = {
@@ -34,7 +29,7 @@ in
vscode-extensions.bbenoist.nix
vscode-extensions.brettm12345.nixfmt-vscode
vscode-extensions.cweijan.vscode-database-client2
# vscode-extensions.dendron.dendron-markdown-preview-enhanced
vscode-extensions.dendron.dendron-markdown-preview-enhanced
vscode-extensions.jnoortheen.nix-ide
vscode-extensions.mkhl.direnv
vscode-extensions.ms-python.debugpy
@@ -44,37 +39,38 @@ in
vscode-extensions.redhat.vscode-yaml
vscode-extensions.yy0931.vscode-sqlite3-editor
nix-vscode-extensions.open-vsx.jeanp413.open-remote-ssh
# open-remote-ssh
# nix-vscode-extensions.open-vsx.jeanp413.open-remote-ssh
open-remote-ssh
]
++ (if !isArm then x86_only else [ ]);
# ++ (if !isDarwin then [ open-remote-ssh ] else [ ]);
# ++ pkgs.vscode-utils.extensionsFromVscodeMarketplace [
# {
# name = "copilot-mcp";
# publisher = "automatalabs";
# version = "0.0.49";
# sha256 = "sha256-+G2OQl5SCN7bh7MzGdYiRclIZefBE7lWnGg1kNpCvnA=";
# }
# {
# name = "mcp-server-runner";
# publisher = "zebradev";
# version = "0.1.0";
# sha256 = "sha256-StydVt3VzQUSS/pYp76jnIwtZlEj8gWAGzOARs93J+E=";
# }
# {
# name = "claude-dev";
# publisher = "saoudrizwan";
# version = "3.17.9";
# sha256 = "sha256-y3bFtMe5vZrO3DFb31KDvkzjD2jM76wK89mKhgJXC70=";
# }
# ];
++ (if !isArm then x86_only else [ ])
++ pkgs.vscode-utils.extensionsFromVscodeMarketplace [
{
name = "copilot-mcp";
publisher = "automatalabs";
version = "0.0.49";
sha256 = "sha256-+G2OQl5SCN7bh7MzGdYiRclIZefBE7lWnGg1kNpCvnA=";
}
{
name = "mcp-server-runner";
publisher = "zebradev";
version = "0.1.0";
sha256 = "sha256-StydVt3VzQUSS/pYp76jnIwtZlEj8gWAGzOARs93J+E=";
}
{
name = "claude-dev";
publisher = "saoudrizwan";
version = "3.17.9";
sha256 = "sha256-y3bFtMe5vZrO3DFb31KDvkzjD2jM76wK89mKhgJXC70=";
}
];
userSettings = {
"database-client.autoSync" = true;
"editor" = {
"defaultFormatter" = "brettm12345.nixfmt-vscode";
"fontFamily" = "fira-code-nerd, FiraCode Nerd Font, Consolas, 'Courier New', monospace";
"fontLigatures" = true;
"renderWhitespace" = "all";
};
@@ -83,10 +79,6 @@ in
"confirmDragAndDrop" = false;
};
"extensions." = {
"autoCheckUpdates" = false;
};
"git" = {
"confirmSync" = false;
"enableSmartCommit" = true;
@@ -142,8 +134,6 @@ in
"security.workspace.trust.untrustedFiles" = "open";
"update.mode" = "none";
"workbench" = {
"colorCustomizations" = null;
"editorAssociations" = {

View File

@@ -1,46 +0,0 @@
# Shared defaultApps submodule options, used by both the hyprland and
# nwg-panel modules. Import this file and pass the result as the `options`
# argument to types.submodule to avoid duplicating the definition.
{ pkgs, lib, ... }:
{
browser = lib.mkOption {
type = lib.types.package;
default = pkgs.firefox;
description = "Default web browser";
};
editor = lib.mkOption {
type = lib.types.package;
default = pkgs.micro;
description = "Default text editor";
};
fileExplorer = lib.mkOption {
type = lib.types.package;
default = pkgs.nautilus;
description = "Default file explorer";
};
visual = lib.mkOption {
type = lib.types.package;
default = pkgs.vscodium;
description = "Default visual/IDE editor";
};
terminal = lib.mkOption {
type = lib.types.package;
default = pkgs.kitty;
description = "Default terminal emulator";
};
office = lib.mkOption {
type = lib.types.package;
default = pkgs.onlyoffice-desktopeditors;
description = "Default office suite";
};
video = lib.mkOption {
type = lib.types.package;
default = pkgs.vlc;
description = "Default video player";
};
imageViewer = lib.mkOption {
type = lib.types.package;
default = pkgs.nomacs;
description = "Default image viewer";
};
}

14
modules/home/programs/git/default.nix Executable file → Normal file
View File

@@ -1,3 +1,4 @@
{ ... }:
let
gitAliases = {
co = "checkout";
@@ -13,15 +14,8 @@ in
{
programs.git = {
enable = true;
settings = {
user = {
name = "mjallen18";
email = "matt.l.jallen@gmail.com";
};
alias = gitAliases;
};
# The default value of `programs.git.signing.format` has changed from `"openpgp"` to `null`.
# You are currently using the legacy default (`"openpgp"`) because `home.stateVersion` is less than "25.05".
signing.format = "openpgp";
userName = "mjallen18";
userEmail = "matt.l.jallen@gmail.com";
aliases = gitAliases;
};
}

View File

@@ -1,25 +0,0 @@
{
config,
namespace,
lib,
...
}:
let
cfg = config.${namespace}.programs.hyprland;
in
{
config = lib.mkIf cfg.enable {
services.avizo = {
enable = true;
settings = {
default = {
time = 1.0;
y-offset = 0.5;
fade-in = 0.1;
fade-out = 0.2;
padding = 10;
};
};
};
};
}

View File

@@ -1,709 +0,0 @@
{
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.programs.hyprland;
drawer = "nwg-drawer -fm nautilus -term kitty -mb 10 -mt 10 -ml 10 -mr 10 -pbuseicontheme -i ${config.stylix.icons.dark}";
in
{
imports = [
./avizo.nix
./options.nix
];
config = mkIf cfg.enable {
# Home packages
home.packages =
with pkgs;
(
[
box64
brightnessctl
ddcutil
egl-wayland
file-roller
glib
gnome-calculator
gnome-calendar
gnome-disk-utility
gnome-firmware
gnome-font-viewer
gnome-logs
gnome-photos
gnome-tweaks
gnome-weather
gsettings-desktop-schemas
hyprcursor
hyprland
hyprpaper
hyprshot
hyprsysteminfo
kdePackages.qtmultimedia
libnotify
libz
meson
nautilus
nomacs
nwg-look
overskride
pamixer
pavucontrol
playerctl
qt5.qtwayland
qt6.qtwayland
waybar
wayland-protocols
wayland-utils
waypaper
wev
wl-clipboard
wlogout
wlroots
xhost
xsettingsd
xwayland
]
++ (if cfg.notificationDaemon == "mako" then [ mako ] else [ dunst ])
++ (if cfg.launcher == "wofi" then [ wofi ] else [ rofi ])
++ (with pkgs.${namespace}; [ pipewire-python ])
);
# Session variables
home.sessionVariables = {
BROWSER = "${cfg.defaultApps.browser.pname}";
CLUTTER_BACKEND = "wayland";
EDITOR = "${cfg.defaultApps.editor.pname}";
VISUAL = "${cfg.defaultApps.visual.pname}";
ICON_THEME = config.gtk.iconTheme.name;
GTK_CSD = "0";
GTK_THEME = config.gtk.theme.name;
GTK_USE_PORTAL = "1";
HYPRCURSOR_THEME = config.stylix.cursor.name;
HYPRCURSOR_SIZE = config.stylix.cursor.size;
MOZ_ENABLE_WAYLAND = "1";
NIXOS_OZONE_WL = "1";
NIXOS_XDG_OPEN_USE_PORTAL = "1";
QT_AUTO_SCREEN_SCALE_FACTOR = "1";
QT_QPA_PLATFORM = "wayland-egl";
QT_QPA_PLATFORMTHEME = lib.mkDefault "gtk3";
QT_SCALE_FACTOR = "1";
QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
SDL_VIDEODRIVER = "wayland";
TERMINAL = "${cfg.defaultApps.terminal.pname}";
XCURSOR_THEME = config.stylix.cursor.name;
XCURSOR_SIZE = config.stylix.cursor.size;
XDG_CACHE_HOME = "\${HOME}/.cache";
XDG_CONFIG_HOME = "\${HOME}/.config";
XDG_CURRENT_DESKTOP = "Hyprland";
XDG_DATA_HOME = "\${HOME}/.local/share";
XDG_SESSION_DESKTOP = "Hyprland";
XDG_SESSION_TYPE = "wayland";
};
# Services
services = {
hyprpolkitagent.enable = true;
hyprpaper = {
enable = true;
settings = {
preload = [ cfg.hyprpaper.wallpaperPath ];
wallpaper = [
{
monitor = "";
path = cfg.hyprpaper.wallpaperPath;
}
];
splash = false;
};
};
hypridle = {
enable = true;
settings = {
general = {
before_sleep_cmd = "loginctl lock-session"; # lock before suspend.
after_sleep_cmd = "hyprctl dispatch dpms on"; # to avoid having to press a key twice to turn on the display.
ignore_dbus_inhibit = false;
lock_cmd = "pidof hyprlock || hyprlock"; # avoid starting multiple hyprlock instances.
};
listener = [
# {
# timeout = 300; # 5min
# on-timeout = "brightnessctl -s set 10"; # set monitor backlight to minimum, avoid 0 on OLED monitor.
# on-resume = "brightnessctl -r"; # monitor backlight restore.
# }
{
timeout = cfg.hyprIdle.lockScreenTimer;
on-timeout = "loginctl lock-session"; # lock screen when timeout has passed
}
{
timeout = cfg.hyprIdle.screenOffTimer;
on-timeout = "hyprctl dispatch dpms off"; # screen off when timeout has passed
on-resume = "hyprctl dispatch dpms on"; # screen on when activity is detected after timeout has fired.
}
{
timeout = cfg.hyprIdle.suspendTimer;
on-timeout = "systemctl suspend"; # suspend pc
}
];
};
};
};
# Programs
programs = {
vscode.profiles.default.userSettings."window"."titleBarStyle" = "custom";
hyprlock = {
enable = true;
settings = {
background = mkForce [
{
monitor = "";
path = "/run/wallpaper.jpg"; # supports png, jpg, webp (no animations, though)
color = mkDefault "rgba(25, 20, 20, 1.0";
# all these options are taken from hyprland, see https://wiki.hyprland.org/Configuring/Variables/#blur for explanations
blur_passes = mkDefault "3"; # 0 disables blurring
blur_size = mkDefault "7";
noise = "0.0117";
contrast = "0.8916";
brightness = mkDefault "0.8172";
vibrancy = "0.1696";
vibrancy_darkness = "0.0";
}
];
label = [
# Date display
{
monitor = cfg.primaryDisplay;
text = "cmd[update:1000] echo -e \"$(LC_TIME=en_US.UTF-8 date +\"%A, %B %d\")\"";
color = config.lib.stylix.colors.base06;
font_size = "25";
font_family = lib.mkDefault config.stylix.fonts.monospace.name;
position = "0, 350";
halign = "center";
valign = "center";
}
# Time display
{
monitor = cfg.primaryDisplay;
text = "cmd[update:1000] echo \"<span>$(date +\"%I:%M\")</span>\"";
color = config.lib.stylix.colors.base06;
font_size = "120";
font_family = lib.mkDefault "${config.stylix.fonts.monospace.name} Bold";
position = "0, 230";
halign = "center";
valign = "center";
}
{
monitor = cfg.primaryDisplay;
text = "$USER";
color = config.lib.stylix.colors.base06;
outline_thickness = 2;
dots_size = 0.2;
dots_spacing = 0.2;
dots_center = true;
font_size = 18;
font_family = lib.mkDefault "${config.stylix.fonts.monospace.name} Bold";
position = "0, 0";
halign = "center";
valign = "center";
}
# weather
{
monitor = cfg.primaryDisplay;
text = "cmd[update:30000] waybar-weather --hyprlock";
color = config.lib.stylix.colors.base06;
font_size = "25";
font_family = lib.mkDefault config.stylix.fonts.monospace.name;
position = "-100, 100";
halign = "right";
valign = "bottom";
}
# media
{
monitor = cfg.primaryDisplay;
text = "cmd[update:1000] waybar-media";
color = config.lib.stylix.colors.base06;
font_size = "15";
font_family = lib.mkDefault config.stylix.fonts.monospace.name;
position = "100, 100";
halign = "left";
valign = "bottom";
}
];
# user box
shape = [
{
monitor = cfg.primaryDisplay;
size = "200, 50";
color = "rgba(46, 52, 64, .25";
rounding = -1;
border_size = "0";
position = "0, 0";
halign = "center";
valign = "center";
}
];
input-field = [
{
size = "200, 50";
position = "0, -80";
font_family = lib.mkDefault config.stylix.fonts.monospace.name;
monitor = cfg.primaryDisplay;
dots_center = true;
fade_on_empty = true;
font_color = config.lib.stylix.colors.base06;
inner_color = config.lib.stylix.colors.base03;
outer_color = config.lib.stylix.colors.base00;
bothlock_color = -1;
outline_thickness = 5;
placeholder_text = ''<span foreground="#${config.lib.stylix.colors.base00}">Password...</span>'';
shadow_passes = 2;
}
];
image = [
{
monitor = cfg.primaryDisplay;
# path = "/tmp/hyprlock-art";
reload_cmd = "waybar-media-art";
reload_time = 3;
size = 150;
rounding = 0;
position = "100, 150";
halign = "left";
valign = "bottom";
}
];
};
};
};
# Wayland configuration
wayland.windowManager.hyprland = {
enable = true;
xwayland.enable = true;
systemd = {
enable = true;
enableXdgAutostart = true;
};
plugins = with pkgs.hyprlandPlugins; [
# hyprgrass
];
settings =
let
useMonitorV2 = (lib.versionAtLeast pkgs.hyprland.version "0.40.0") && (cfg.monitorv2 != [ ]);
names =
if useMonitorV2 then
map (m: m.name) cfg.monitorv2
else
[
cfg.display1.input
cfg.display2.input
];
firstMonitor = builtins.elemAt names 0;
secondMonitor = if builtins.length names > 1 then builtins.elemAt names 1 else firstMonitor;
in
{
inherit (cfg) workspace;
"$mod" = cfg.modKey;
# Mouse
# mouse_[up|down] - scroll wheel
# middle_mouse - 274
# thumb_up - 276
# thumb_down - 275
# l -> locked, will also work when an input inhibitor (e.g. a lockscreen) is active.
# r -> release, will trigger on release of a key.
# e -> repeat, will repeat when held.
# n -> non-consuming, key/mouse events will be passed to the active window in addition to triggering the dispatcher.
# m -> mouse, see below.
# t -> transparent, cannot be shadowed by other binds.
# i -> ignore mods, will ignore modifiers.
# s -> separate, will arbitrarily combine keys between each mod/key, see [Keysym combos](#keysym-combos) above.
# d -> has description, will allow you to write a description for your bind.
# p -> bypasses the app's requests to inhibit keybinds.
# https://wiki.hyprland.org/Configuring/Binds/
# https://wiki.hyprland.org/Configuring/Binds/#mouse-buttons
bind = [
"$mod, Return, exec, ${cfg.defaultApps.terminal.pname}"
"$mod, SPACE, exec, ${if cfg.launcher == "wofi" then "wofi --show drun" else "rofi -show drun"}"
", xf86Search, exec, ${if cfg.launcher == "wofi" then "wofi --show drun" else "rofi -show drun"}"
"$mod, Q, killactive, "
"$mod, M, exec, wlogout --protocol layer-shell"
"$mod, E, exec, ${cfg.defaultApps.fileExplorer.pname}"
"$mod, V, togglefloating, "
"$mod, D, exec, ${drawer}"
"$mod, P, pseudo, " # dwindle
"$mod, S, togglesplit, " # dwindle
"$mod SHIFT, Q, exec, hyprlock"
"$mod SHIFT, 4, exec, hyprshot -m region --clipboard-only"
"$mod, F, fullscreen, 1"
"$mod SHIFT, F, fullscreen, 0"
"$mod SHIFT, E, exec, smile"
"$mod, mouse:276, movecurrentworkspacetomonitor, ${firstMonitor}"
"$mod, mouse:275, movecurrentworkspacetomonitor, ${secondMonitor}"
# alt-tab between workspaces on active monitor
"$mod, Tab, workspace, m+1"
"$mod SHIFT, Tab, workspace, m-1"
"$mod, h, movefocus, l"
"$mod, l, movefocus, r"
"$mod, k, movefocus, u"
"$mod, j, movefocus, d"
"$mod, 1, workspace, 1"
"$mod, 2, workspace, 2"
"$mod, 3, workspace, 3"
"$mod, 4, workspace, 4"
"$mod, 5, workspace, 5"
"$mod, 6, workspace, 6"
"$mod, 7, workspace, 7"
"$mod, 8, workspace, 8"
"$mod, 9, workspace, 9"
"$mod, 0, workspace, 10"
"$mod ALT, 1, movetoworkspace, 1"
"$mod ALT, 2, movetoworkspace, 2"
"$mod ALT, 3, movetoworkspace, 3"
"$mod ALT, 4, movetoworkspace, 4"
"$mod ALT, 5, movetoworkspace, 5"
"$mod ALT, 6, movetoworkspace, 6"
"$mod ALT, 7, movetoworkspace, 7"
"$mod ALT, 8, movetoworkspace, 8"
"$mod ALT, 9, movetoworkspace, 9"
"$mod ALT, 0, movetoworkspace, discord"
"$mod CTRL, l, resizeactive, 10 0"
"$mod CTRL, h, resizeactive, -10 0"
"$mod CTRL, k, resizeactive, 0 -10"
"$mod CTRL, j, resizeactive, 0 10"
"$mod SHIFT, l, movewindow, r"
"$mod SHIFT, h, movewindow, l"
"$mod SHIFT, k, movewindow, u"
"$mod SHIFT, j, movewindow, d"
"$mod, b, exec, ${cfg.defaultApps.browser.pname}"
]
++ cfg.keybinds.bind;
bindm = [
# Move/resize windows with mod + LMB/RMB and dragging
"$mod, mouse:272, movewindow"
"$mod, mouse:273, resizewindow"
# middle mouse will grab a window, mod + middle mouse will close it
"$mod SHIFT, mouse:274, movewindow"
]
++ cfg.keybinds.bindm;
bindel = [
", XF86AudioRaiseVolume, exec, volumectl -u up"
", XF86AudioLowerVolume, exec, volumectl -u down"
]
++ cfg.keybinds.bindel;
bindl = [
", XF86AudioMute, exec, volumectl toggle-mute"
", XF86AudioPlay, exec, playerctl play-pause"
", XF86AudioPrev, exec, playerctl previous"
", XF86AudioNext, exec, playerctl next"
", XF86AudioMicMute, exec, volumectl -m toggle-mute"
", XF86MonBrightnessUp, exec, lightctl up"
", XF86MonBrightnessDown, exec, lightctl down"
]
++ cfg.keybinds.bindl;
monitor =
cfg.monitor
++ map (
m:
if m.disabled then
"${m.name}, disable"
else if m.mirrorOf != null then
"${m.name}, mirror, ${m.mirrorOf}"
else
let
mode = if m.mode == null then "preferred" else m.mode;
position = if m.position == null then "0x0" else m.position;
scale = if m.scale == null then "1" else (toString m.scale);
parts = [
m.name
mode
position
scale
];
# Append transform only when set, as "transform, <value>"
transformTokens =
if m.transform == null then
[ ]
else
[
"transform"
m.transform
];
tokens = parts ++ transformTokens ++ m.extra;
in
builtins.concatStringsSep ", " tokens
) cfg.monitorv2;
render = {
cm_fs_passthrough = 1;
};
misc = {
vrr = if cfg.enableVRR then 1 else 0;
force_default_wallpaper = 0;
};
general = {
gaps_in = 5;
gaps_out = 10;
border_size = 1;
# "col.active_border" = "rgb(8aadf4) rgb(24273A) rgb(24273A) rgb(8aadf4) 45deg";
# "col.inactive_border" = "rgb(24273A) rgb(24273A) rgb(24273A) rgb(24273A) 45deg";
layout = "dwindle";
allow_tearing = cfg.allowTearing;
};
decoration = {
rounding = 10;
blur = {
enabled = true;
size = 2;
passes = 2;
new_optimizations = true;
xray = false;
};
};
animations = {
enabled = "yes";
bezier = [
"overshot, 0.05, 0.9, 0.1, 1.05"
"smoothOut, 0.36, 0, 0.66, -0.56"
"smoothIn, 0.25, 1, 0.5, 1"
];
animation = [
"windows, 1, 5, overshot, slide"
"windowsOut, 1, 4, smoothOut, slide"
"windowsMove, 1, 4, default"
"border, 1, 10, default"
"fade, 1, 10, smoothIn"
"fadeDim, 1, 10, smoothIn"
"workspaces, 1, 6, default"
];
};
dwindle = {
pseudotile = "yes";
preserve_split = "yes";
};
windowrule = [
"match:title file_progress, float 1"
"match:title .*[Cc]onfirm.*, float 1"
"match:title .*[Dd]ialog.*, float 1"
"match:title .*[Dd]ownload.*, float 1"
"match:title .*[Nn]otification.*, float 1"
"match:title .*[Ee]rror.*, float 1"
"match:title .*[Ss]plash.*, float 1"
"match:title .*[Cc]onfirmreset.*, float 1"
"match:title .*[Ss]ign [Ii]n - .*, float 1"
"match:title .*[Oo]pen [Ff]ile.*, float 1"
"match:title .*branchdialog.*, float 1"
"match:class .*pavucontrol.*, float 1"
"match:class .*pavucontrol.*, move onscreen cursor 0% 0%"
"match:class .*[Oo]verskride.*, float 1"
"match:class .*FileRoller.*, float 1"
"match:class .*wlogout.*, float 1"
"match:title .*mpv.*, idle_inhibit stayfocused"
"match:class .*nm-connection-editor.*, float 1"
"match:class .*nm-connection-editor.*, move onscreen cursor 0% 0%"
"match:title Media viewer, float 1"
"match:class it.mijorus.smile),match:title Smile, float 1"
"match:class .blueman-manager-wrapped)$,match:title Bluetooth Devices, float 1"
# Picture in picture windows
"match:title .*Picture-in-Picture.*, float 1"
"match:title .*Picture-in-Picture.*, pin 1"
# discord/vesktop
# "workspace: name:discord, match:class .*vesktop"
# "match:class .*vesktop),match:title .*Discord Popout.*, float 1"
# "pin, match:class .*vesktop),match:title .*Discord Popout.*"
# Music
# "workspace: name:discord, match:class Apple Music.*"
# Steam
"match:class .*[Ss]team, match:title .*[Ss]team.*, float 1"
"match:class .*[Ss]team, match:title .*[Ss]team.*, workspace name:steam silent"
"match:class .*[Ss]team, match:title .*[Ss]team.*, tile 1"
"match:class .*steam,match:title .*Friends List.*, float 1"
# Code
"match:class .*codium.*, match:title Save As, pin 1"
"match:class .*codium.*, match:title Save As, float 1"
"match:class xdg-desktop-portal-gtk, match:title Open Workspace from File, float 1"
# Game Tearing??? https://wiki.hypr.land/Configuring/Tearing/
"match:class .*gamescope.*, idle_inhibit fullscreen, content game, immediate 1"
"match:xdg_tag proton-game, idle_inhibit fullscreen, content game, immediate 1"
"match:class steam_app_.*, idle_inhibit fullscreen, content game, immediate 1"
# vmware
# this tag will set the below options to the vdi window
# this will have it auto open as a 2160x7680 window
# and makes multi-monitor work
"match:class .*[Hh]orizon-client, match:title USPS Next VDI, tag +horizonrdp"
"match:tag horizonrdp, no_anim 1"
"match:tag horizonrdp, no_blur 1"
"match:tag horizonrdp, rounding 0"
"match:tag horizonrdp, no_shadow 1"
"match:tag horizonrdp, immediate 1"
"match:tag horizonrdp, allows_input 1"
"match:tag horizonrdp, border_size 0"
"match:tag horizonrdp, max_size 2160 7680"
"match:tag horizonrdp, min_size 1920 1080"
"match:tag horizonrdp, render_unfocused 1"
"match:tag horizonrdp, idle_inhibit 1"
"match:tag horizonrdp, float 1"
# float the vmware window cause its annoying to use in fullscreen
"match:class .*[Hh]orizon-client),match:title [Oo]mnissa [Hh]orizon [Cc]lient, float 1"
]
++ cfg.windowRule;
plugin = {
# touch_gestures = {
# # The default sensitivity is probably too low on tablet screens,
# # I recommend turning it up to 4.0
# sensitivity = "4.0";
# # must be >= 3
# workspace_swipe_fingers = "3";
# # switching workspaces by swiping from an edge, this is separate from workspace_swipe_fingers
# # and can be used at the same time
# # possible values: l, r, u, or d
# # to disable it set it to anything else
# workspace_swipe_edge = "d";
# # in milliseconds
# long_press_delay = "400";
# # resize windows by long-pressing on window borders and gaps.
# # If general:resize_on_border is enabled, general:extend_border_grab_area is used for floating
# # windows
# resize_on_border_long_press = true;
# # in pixels, the distance from the edge that is considered an edge
# edge_margin = "10";
# # emulates touchpad swipes when swiping in a direction that does not trigger workspace swipe.
# # ONLY triggers when finger count is equal to workspace_swipe_fingers
# #
# # might be removed in the future in favor of event hooks
# emulate_touchpad_swipe = false;
# experimental = {
# # send proper cancel events to windows instead of hacky touch_up events,
# # NOT recommended as it crashed a few times, once it's stabilized I'll make it the default
# send_cancel = "0";
# };
# hyprgrass-bind = [
# # swipe left from right edge
# ", edge:r:l, workspace, +1"
# # swipe up from bottom edge
# ", edge:d:u, exec, ${cfg.defaultApps.browser.pname}"
# # swipe down from left edge
# ", edge:l:d, exec, pactl set-sink-volume @DEFAULT_SINK@ -4%"
# # swipe down with 4 fingers
# ", swipe:4:d, killactive"
# # swipe diagonally left and down with 3 fingers
# # l (or r) must come before d and u
# ", swipe:3:ld, exec, foot"
# # tap with 3 fingers
# ", tap:3, exec, foot"
# # longpress can trigger mouse binds:
# ", longpress:2, movewindow"
# ", longpress:3, resizewindow"
# ];
# };
};
gesture = [
"3, horizontal, scale: 0.75, workspace" # swipe 3 fingers to change workspace
"3, pinch, mod: SUPER, resize"
"4, pinch, fullscreen"
];
input = {
kb_layout = "us";
kb_variant = "";
kb_model = "";
kb_options = "";
kb_rules = "";
numlock_by_default = true;
follow_mouse = 1;
touchpad = {
clickfinger_behavior = 1;
natural_scroll = "yes";
};
sensitivity = 0; # -1.0 - 1.0, 0 means no modification.
};
# experimental = {
# xx_color_management_v4 = true;
# };
debug = {
# full_cm_proto = cfg.debug.fullCmProto;
disable_logs = cfg.debug.disableLogs;
disable_scale_checks = cfg.debug.disableScaleChecks;
};
};
extraConfig =
let
autostarts = builtins.concatStringsSep "\n" (map (cmd: "exec-once = ${cmd}") cfg.autostartCommands);
in
''
exec-once = dbus-update-activation-environment --systemd --all
exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
exec-once = xhost +SI:localuser:root
''
+ autostarts
+ "\n"
+ (cfg.extraConfig or "");
};
};
}

View File

@@ -1,286 +0,0 @@
{
lib,
namespace,
...
}:
with lib;
{
options.${namespace}.programs.hyprland = {
enable = mkEnableOption "enable hyprland";
primaryDisplay = mkOption {
type = types.str;
default = "DP-1";
description = "Primary display identifier";
};
display1 = {
input = mkOption {
type = types.str;
default = "DP-1";
description = "First display identifier (Deprecated: prefer monitorv2)";
};
resolution = mkOption {
type = types.str;
default = "3840x2160";
description = "First display resolution (Deprecated: prefer monitorv2)";
};
refreshRate = mkOption {
type = types.str;
default = "240.00000";
description = "First display refresh rate (Deprecated: prefer monitorv2)";
};
};
display2 = {
input = mkOption {
type = types.str;
default = "DP-1";
description = "Second display identifier (Deprecated: prefer monitorv2)";
};
resolution = mkOption {
type = types.str;
default = "3840x2160";
description = "Second display resolution (Deprecated: prefer monitorv2)";
};
refreshRate = mkOption {
type = types.str;
default = "240.00000";
description = "Second display refresh rate (Deprecated: prefer monitorv2)";
};
};
# Deprecated: prefer hyprpaper.* options
wallpaper = mkOption {
type = with types; listOf str;
default = [ ];
description = "Deprecated: no longer used; prefer hyprpaper.wallpaperPath and hyprpaper.usePerMonitor.";
};
hyprpaper = mkOption {
type = types.submodule {
options = {
wallpaperPath = mkOption {
type = types.str;
default = "/run/wallpaper.jpg";
description = "Path to the wallpaper used by hyprpaper.";
};
usePerMonitor = mkOption {
type = types.bool;
default = true;
description = "If true, generate one wallpaper entry per monitor (monitorv2 preferred, falls back to display1/display2).";
};
};
};
default = { };
description = "Hyprpaper configuration.";
};
monitor = mkOption {
type = with types; listOf str;
default = [ ];
description = "List of Hyprland monitor configs (legacy). Example: [ \"eDP-1, 1920x1080@60, 0x0, 1\" ]";
};
monitorv2 = mkOption {
type =
with types;
listOf (
types.submodule {
options = {
name = mkOption {
type = types.str;
description = "Monitor name (e.g., DP-1, eDP-1).";
};
mode = mkOption {
type = types.nullOr types.str;
default = null;
description = "Resolution@Hz or keyword (e.g., \"3840x2160@144\" or \"preferred\").";
};
position = mkOption {
type = types.nullOr types.str;
default = null;
description = "Position like \"0x0\" or \"auto\".";
};
scale = mkOption {
type = types.nullOr types.float;
default = null;
description = "Scale factor (e.g., 1.0).";
};
transform = mkOption {
type = types.nullOr types.str;
default = null;
description = "Rotation/transform (e.g., \"normal\", \"90\", \"180\", \"270\", \"flipped\").";
};
disabled = mkOption {
type = types.bool;
default = false;
description = "Disable this monitor.";
};
mirrorOf = mkOption {
type = types.nullOr types.str;
default = null;
description = "Mirror another monitor by name.";
};
extra = mkOption {
type = with types; listOf str;
default = [ ];
description = "Additional monitorv2 flags appended as-is.";
};
};
}
);
default = [ ];
description = "Hyprland monitorv2 entries as structured options; rendered to lines like \"name, mode, position, scale, transform, ...\".";
};
workspace = mkOption {
type = with types; listOf str;
default = [ ];
description = "List of hyprland workspace definitions";
};
windowRule = mkOption {
type = with types; listOf str;
default = [ ];
description = "List of hyprland window rules";
};
extraConfig = mkOption {
type = with types; str;
default = "";
description = "Any extra configuration options";
};
defaultApps = mkOption {
type = types.submodule (import ../common/default-apps.nix);
description = "Default applications used across the system";
};
autostartCommands = mkOption {
type = with types; listOf str;
default = [
"nwg-look -a"
"nwg-dock-hyprland -x"
];
description = "Commands to run via Hyprland exec-once";
};
launcher = mkOption {
type = types.enum [
"wofi"
"rofi"
];
default = "wofi";
description = "Application launcher to use in keybinds and packages.";
};
notificationDaemon = mkOption {
type = types.enum [
"mako"
"dunst"
];
default = "mako";
description = "Notification daemon to install.";
};
modKey = mkOption {
type = types.str;
default = "SUPER";
description = "Modifier key used for Hyprland binds (e.g., SUPER, ALT).";
};
enableVRR = mkOption {
type = types.bool;
default = true;
description = "Enable variable refresh rate (maps to Hyprland misc.vrr).";
};
allowTearing = mkOption {
type = types.bool;
default = true;
description = "Allow tearing (maps to Hyprland general.allow_tearing).";
};
debug = mkOption {
type = types.submodule {
options = {
disableLogs = mkOption {
type = types.bool;
default = false;
description = "Set Hyprland debug.disable_logs.";
};
fullCmProto = mkOption {
type = types.bool;
default = false;
description = "Set Hyprland debug.full_cm_proto.";
};
disableScaleChecks = mkOption {
type = types.bool;
default = false;
description = "Set Hyprland debug.disable_scale_checks.";
};
};
};
default = { };
description = "Hyprland debug flags.";
};
keybinds = mkOption {
type = types.submodule {
options = {
bind = mkOption {
type = with types; listOf str;
default = [ ];
description = "Hyprland bind entries.";
};
bindm = mkOption {
type = with types; listOf str;
default = [ ];
description = "Hyprland bindm entries.";
};
bindel = mkOption {
type = with types; listOf str;
default = [ ];
description = "Hyprland bindel entries.";
};
bindl = mkOption {
type = with types; listOf str;
default = [ ];
description = "Hyprland bindl entries.";
};
};
};
default = { };
description = "Keybinding lists for Hyprland; useful for host-level overrides.";
};
gestures = mkOption {
type = with types; listOf str;
default = [ ];
description = "Hyprland gesture entries.";
};
hyprIdle = {
lockScreenTimer = mkOption {
type = with types; int;
default = 300;
description = "Time in seconds before locking the screen";
};
screenOffTimer = mkOption {
type = with types; int;
default = 900;
description = "Time in seconds before turning off the screen";
};
suspendTimer = mkOption {
type = with types; int;
default = 1800;
description = "Time in seconds before suspending";
};
};
};
}

View File

@@ -1,30 +1,106 @@
{ lib, config, ... }:
with lib;
let
cfg = config.mjallen.programs.kitty;
nord = import (lib.snowfall.fs.get-file "modules/home/desktop/theme/nord.nix");
in
{
config,
lib,
namespace,
...
}:
{
imports = [
(lib.${namespace}.mkHomeModule {
inherit config;
domain = "programs";
name = "kitty";
moduleConfig = {
programs.kitty = {
enable = true;
shellIntegration.enableZshIntegration = true;
settings = {
bold_font = "auto";
italic_font = "auto";
bold_italic_font = "auto";
mouse_hide_wait = "2.0";
cursor_shape = "block";
url_style = "dotted";
confirm_os_window_close = "0";
};
};
imports = [ ./options.nix ];
config = mkIf cfg.enable {
programs.kitty = {
enable = true;
shellIntegration.enableZshIntegration = true;
font = {
name = cfg.font.name;
package = cfg.font.package;
size = cfg.font.size;
};
})
];
settings = {
bold_font = "auto";
italic_font = "auto";
bold_italic_font = "auto";
mouse_hide_wait = "2.0";
cursor_shape = "block";
url_style = "dotted";
confirm_os_window_close = "0";
background_opacity = "0.85";
# The basic colors
foreground = nord.snowStorm.nord6;
background = nord.polarNight.nord0;
selection_foreground = nord.polarNight.nord0;
selection_background = nord.aurora.nord15;
# Cursor colors
cursor = nord.aurora.nord15;
cursor_text_color = nord.polarNight.nord0;
# URL underline color when hovering with mouse
url_color = nord.aurora.nord15;
# Kitty window border colors
active_border_color = nord.frost.nord10;
inactive_border_color = nord.polarNight.nord1;
bell_border_color = nord.aurora.nord13;
# OS Window titlebar colors
wayland_titlebar_color = nord.polarNight.nord0;
macos_titlebar_color = nord.polarNight.nord0;
# Tab bar colors
active_tab_foreground = nord.polarNight.nord3;
active_tab_background = nord.aurora.nord15;
inactive_tab_foreground = nord.snowStorm.nord6;
inactive_tab_background = nord.polarNight.nord1;
tab_bar_background = nord.polarNight.nord3;
# Colors for marks (marked text in the terminal)
mark1_foreground = nord.polarNight.nord0;
mark1_background = nord.frost.nord10;
mark2_foreground = nord.polarNight.nord0;
mark2_background = nord.aurora.nord15;
mark3_foreground = nord.polarNight.nord0;
mark3_background = nord.frost.nord8;
# The 16 terminal colors
# black
color0 = nord.polarNight.nord0;
# Autosuggestion
color8 = nord.frost.nord10;
# red
color1 = nord.aurora.nord11;
color9 = nord.aurora.nord11;
# green
color2 = nord.aurora.nord14;
color10 = nord.aurora.nord14;
# yellow
color3 = nord.aurora.nord13;
color11 = nord.aurora.nord13;
# blue
color4 = nord.frost.nord10;
color12 = nord.frost.nord10;
# magenta
color5 = nord.aurora.nord15;
color13 = nord.aurora.nord15;
# cyan
color6 = nord.frost.nord8;
color14 = nord.frost.nord8;
# white
color7 = nord.snowStorm.nord5;
color15 = nord.snowStorm.nord4;
};
};
};
}

View File

@@ -0,0 +1,27 @@
{ lib, ... }:
with lib;
{
options.mjallen.programs.kitty = {
enable = mkEnableOption "enable kitty terminal";
font = {
name = mkOption {
type = types.str;
default = "DejaVu Sans";
};
package = mkOption {
type = types.package;
default = pkgs.dejavu_fonts;
};
size = mkOption {
type = with types; int;
default = 12;
};
};
theme = mkOption {
type = types.attrs;
default = import (lib.snowfall.fs.get-file "modules/home/desktop/theme/nord.nix");
};
};
}

1
modules/home/programs/librewolf/default.nix Executable file → Normal file
View File

@@ -1,3 +1,4 @@
{ ... }:
{
programs.librewolf = {
enable = false;

View File

@@ -1,40 +1,32 @@
{ config, lib, ... }:
with lib;
let
cfg = config.mjallen.programs.mako;
nord = import (lib.snowfall.fs.get-file "modules/home/desktop/theme/nord.nix");
in
{
config,
lib,
namespace,
...
}:
{
imports = [
(lib.${namespace}.mkHomeModule {
inherit config;
domain = "programs";
name = "mako";
options = {
fontName = lib.mkOption {
type = lib.types.str;
default = "DejaVu Sans";
description = "Font name for mako notifications.";
};
imports = [ ./options.nix ];
config = mkIf cfg.enable {
services.mako = {
enable = true;
settings = {
font = cfg.fontName;
icons = true;
ignore-timeout = true;
sort = "-time";
width = 500;
height = 110;
layer = "overlay";
border-radius = 15;
border-size = 1;
max-icon-size = 64;
default-timeout = 5000;
background-color = nord.polarNight.nord0;
text-color = nord.snowStorm.nord6;
border-color = nord.frost.nord10;
progress-color = "over ${nord.frost.nord8}";
};
moduleConfig = {
services.mako = {
enable = true;
settings = {
font = lib.mkDefault config.${namespace}.programs.mako.fontName;
icons = true;
ignore-timeout = true;
sort = "-time";
width = 500;
height = 110;
layer = "overlay";
border-radius = 15;
border-size = 1;
max-icon-size = 64;
default-timeout = 5000;
};
};
};
})
];
};
};
}

View File

@@ -0,0 +1,12 @@
{ lib, ... }:
with lib;
{
options.mjallen.programs.mako = {
enable = mkEnableOption "enable mako";
fontName = mkOption {
type = types.str;
default = "DejaVu Sans";
};
};
}

168
modules/home/programs/nwg-dock/default.nix Executable file → Normal file
View File

@@ -2,134 +2,64 @@
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
cfg = config.mjallen.programs.nwg-dock;
nord = import (lib.snowfall.fs.get-file "modules/home/desktop/theme/nord.nix");
in
{
imports = [
(lib.${namespace}.mkHomeModule {
inherit config;
domain = "programs";
name = "nwg-dock";
moduleConfig = {
home.packages = with pkgs; [ nwg-dock-hyprland ];
imports = [ ./options.nix ];
home.file = {
".config/nwg-dock-hyprland/config.json".text = ''
{
"position": "bottom",
"anchor": "center",
"margin": 12,
"icon_size": 48,
"icon_size_hover": 64,
"spacing": 6,
"padding": 8,
"autohide": false,
"autohide_timeout": 0.3,
"exclusive": true,
"layer": "top",
"height": 72,
"background_alpha": 0.55,
"rounded_corners": 16,
"show_labels": false,
"show_running": true,
"show_pinned": true,
"pinned": [
"firefox.desktop",
"org.wezfurlong.wezterm.desktop",
"codium.desktop",
"org.gnome.Nautilus.desktop"
]
}
'';
config = mkIf cfg.enable {
home.packages = with pkgs; [ nwg-dock-hyprland ];
".config/nwg-dock-hyprland/style.css".text = ''
window {
background: #36364f;
border-radius: 10px;
border-style: none;
border-width: 1px;
border-color: rgba(156, 142, 122, 0.7)
}
home.file = {
".config/nwg-dock-hyprland/drawer.css".text = ''
window {
background: ${nord.polarNight.nord0};
border-radius: 10px;
border-style: none;
border-width: 1px;
border-color: ${nord.aurora.nord15}b0
}
#box {
padding: 10px
}
#box {
/* Define attributes of the box surrounding icons here */
padding: 10px
}
#active {
border-bottom: solid 1px;
border-color: rgba(255, 255, 255, 0.3)
}
active {
/* This is to underline the button representing the currently active window */
border-bottom: solid 1px;
border-color: ${nord.aurora.nord14}1a
}
button, image {
background: none;
border-style: none;
box-shadow: none;
color: #999
}
button, image {
background: none;
border-style: none;
box-shadow: none;
color: ${nord.frost.nord10}
}
button {
padding: 4px;
margin-left: 4px;
margin-right: 4px;
color: #eee;
font-size: 12px
}
button {
padding: 4px;
margin-left: 4px;
margin-right: 4px;
color: #eee;
font-size: 12px
}
button:hover {
background-color: rgba(255, 255, 255, 0.15);
border-radius: 2px;
}
button:hover {
background-color: ${nord.polarNight.nord0}1a;
border-radius: 2px;
}
button:focus {
box-shadow: none
}
'';
".config/nwg-dock-hyprland/drawer.css".text = ''
window {
background: ${config.lib.stylix.colors.base00};
border-radius: 10px;
border-style: none;
border-width: 1px;
border-color: ${config.lib.stylix.colors.base0E}b0
}
#box {
padding: 10px
}
active {
border-bottom: solid 1px;
border-color: ${config.lib.stylix.colors.base0B}1a
}
button, image {
background: none;
border-style: none;
box-shadow: none;
color: ${config.lib.stylix.colors.base0F}
}
button {
padding: 4px;
margin-left: 4px;
margin-right: 4px;
color: #eee;
font-size: 12px
}
button:hover {
background-color: ${config.lib.stylix.colors.base00}1a;
border-radius: 2px;
}
button:focus {
box-shadow: none
}
'';
};
};
})
];
button:focus {
box-shadow: none
}
'';
};
};
}

View File

@@ -0,0 +1,7 @@
{ lib, ... }:
with lib;
{
options.mjallen.programs.nwg-dock = {
enable = mkEnableOption "enable nwg-dock";
};
}

81
modules/home/programs/nwg-drawer/default.nix Executable file → Normal file
View File

@@ -2,53 +2,56 @@
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
cfg = config.mjallen.programs.nwg-drawer;
nord = import (lib.snowfall.fs.get-file "modules/home/desktop/theme/nord.nix");
in
{
imports = [
(lib.${namespace}.mkHomeModule {
inherit config;
domain = "programs";
name = "nwg-drawer";
moduleConfig = {
home.packages = with pkgs; [ nwg-drawer ];
imports = [ ./options.nix ];
home.file.".config/nwg-drawer/drawer.css".text = ''
window {
background-color: ${config.lib.stylix.colors.base00}bf;
color: ${config.lib.stylix.colors.base05}00
}
config = mkIf cfg.enable {
home.packages = with pkgs; [ nwg-drawer ];
entry {
background-color: ${config.lib.stylix.colors.base01}0f
}
home.file = {
".config/nwg-drawer/drawer.css".text = ''
window {
background-color: ${nord.polarNight.nord0}bf;
color: ${nord.snowStorm.nord5}00
}
button, image {
background: none;
border: none
}
/* search entry */
entry {
background-color: ${nord.polarNight.nord1}0f
}
button:hover {
background-color: ${config.lib.stylix.colors.base0F}1a
}
button, image {
background: none;
border: none
}
#category-button {
margin: 0 10px 0 10px
}
button:hover {
background-color: ${nord.frost.nord10}1a
}
#pinned-box {
padding-bottom: 5px;
border-bottom: 1px dotted ${config.lib.stylix.colors.base03}
}
/* in case you wanted to give category buttons a different look */
#category-button {
margin: 0 10px 0 10px
}
#files-box {
padding: 5px;
border: 1px dotted ${config.lib.stylix.colors.base03};
border-radius: 15px
}
'';
};
})
];
#pinned-box {
padding-bottom: 5px;
border-bottom: 1px dotted ${nord.polarNight.nord3}
}
#files-box {
padding: 5px;
border: 1px dotted ${nord.polarNight.nord3};
border-radius: 15px
}
'';
};
};
}

View File

@@ -0,0 +1,7 @@
{ lib, ... }:
with lib;
{
options.mjallen.programs.nwg-drawer = {
enable = mkEnableOption "enable nwg-drawer";
};
}

9
modules/home/programs/nwg-panel/default.nix Executable file → Normal file
View File

@@ -1,12 +1,7 @@
{
config,
lib,
namespace,
...
}:
{ config, lib, ... }:
with lib;
let
cfg = config.${namespace}.programs.nwg-panel;
cfg = config.mjallen.programs.nwg-panel;
in
{
imports = [ ./options.nix ];

50
modules/home/programs/nwg-panel/options.nix Executable file → Normal file
View File

@@ -1,14 +1,46 @@
{ lib, pkgs, ... }:
with lib;
{
lib,
namespace,
...
}:
{
options.${namespace}.programs.nwg-panel = {
enable = lib.mkEnableOption "nwg-panel";
options.mjallen.programs.nwg-panel = {
enable = mkEnableOption "enable nwg-panel";
defaultApps = lib.mkOption {
type = lib.types.submodule (import ../common/default-apps.nix);
defaultApps = mkOption {
type = types.submodule {
options = {
browser = mkOption {
type = types.package;
default = pkgs.firefox;
};
editor = mkOption {
type = types.package;
default = pkgs.micro;
};
fileExplorer = mkOption {
type = types.package;
default = pkgs.nemo;
};
visual = mkOption {
type = types.package;
default = pkgs.vscodium;
};
terminal = mkOption {
type = types.package;
default = pkgs.kitty;
};
office = mkOption {
type = types.package;
default = pkgs.onlyoffice-bin_latest;
};
video = mkOption {
type = types.package;
default = pkgs.vlc;
};
imageViewer = mkOption {
type = types.package;
default = pkgs.gnome-photos;
};
};
};
description = "Default applications used across the system.";
};
};

11
modules/home/programs/onlyoffice/default.nix Executable file → Normal file
View File

@@ -1,15 +1,10 @@
{
lib,
system,
hasDestopEnvironment ? true,
...
}:
{ lib, system, ... }:
let
isArm = "aarch64-linux" == system;
isArm = builtins.match "aarch64*" system != null;
in
{
programs.onlyoffice = {
enable = lib.mkDefault (!isArm && hasDestopEnvironment);
enable = lib.mkDefault (!isArm);
settings = {
UITheme = "theme-contrast-dark";
forcedRtl = false;

View File

@@ -1,88 +0,0 @@
{
config,
lib,
pkgs,
namespace,
...
}:
let
cfg = config.${namespace}.programs.opencode;
net = lib.${namespace}.network;
in
{
options.${namespace}.programs.opencode = {
enable = lib.mkEnableOption "opencode";
};
config = lib.mkIf cfg.enable {
sops.secrets."hass-mcp/token" = { };
sops.templates."hass-mcp.env" = {
mode = "0600";
content = ''
HA_URL=http://${net.hosts.nuc.lan}:${toString net.ports.nuc.homeAssistant}
HA_TOKEN=${config.sops.placeholder."hass-mcp/token"}
'';
};
programs.opencode = {
enable = true;
enableMcpIntegration = true;
settings = {
provider = {
nas = {
npm = "@ai-sdk/openai-compatible";
name = "llama-server (local)";
options = {
baseURL = "http://${net.hosts.nas.lan}:${toString net.ports.nas.llamaCpp}/v1";
};
models = {
Qwen3-Coder-Next-Q4_0 = {
name = "Qwen3 Coder (local)";
modalities = {
input = [
"image"
"text"
];
output = [ "text" ];
};
limit = {
context = 131072;
output = 32768;
};
};
};
};
};
};
};
programs.mcp = {
enable = true;
servers = {
nixos = {
command = "nix";
args = [
"run"
"github:utensils/mcp-nixos"
"--"
];
};
hass-mcp = {
command = "bash";
args = [
"-c"
"set -a; source ${config.sops.templates."hass-mcp.env".path}; set +a; exec uvx hass-mcp"
];
};
mcp-server-code-runner = {
command = "${pkgs.nodejs_24}/bin/npm";
args = [
"-y"
"@iflow-mcp/mcp-server-code-runner"
];
};
};
};
};
}

View File

@@ -1,9 +0,0 @@
{
# The default value of `programs.password-store.settings` has changed from `{ PASSWORD_STORE_DIR = "$XDG_DATA_HOME/password-store"; }` to `{ }`.
# You are currently using the legacy default (`{ PASSWORD_STORE_DIR = "$XDG_DATA_HOME/password-store"; }`) because `home.stateVersion` is less than "25.11".
# To silence this warning and keep legacy behavior, set:
# programs.password-store.settings = { PASSWORD_STORE_DIR = "$XDG_DATA_HOME/password-store"; };
programs.password-store = {
settings = { };
};
}

View File

@@ -1,29 +0,0 @@
{
config,
lib,
namespace,
...
}:
let
cfg = config.${namespace}.programs.thunderbird;
in
{
options.${namespace}.programs.thunderbird = {
enable = lib.mkEnableOption "thunderbird";
};
config = lib.mkIf cfg.enable {
programs.thunderbird = {
enable = true;
profiles = {
mjallen = {
isDefault = true;
accountsOrder = [
"gmail"
"protonmail"
];
};
};
};
};
}

View File

@@ -1,289 +0,0 @@
{
config,
pkgs,
...
}:
let
git-token = config.sops.secrets."github-token".path;
update-checker = pkgs.writeScriptBin "update-checker" ''
#!/usr/bin/env nix-shell
#! nix-shell -i python3 --pure
#! nix-shell -p python3 python3Packages.pygithub python3Packages.feedparser python3Packages.requests nix-prefetch-scripts nix
import os
import json
import subprocess
from github import Github
from github import Auth
import feedparser
import requests
token = None
with open('${git-token}', 'r') as token_file:
token = token_file.readline()
auth = Auth.Token(token)
def check_github(owner, repo, version):
try:
release = None
result = None
prefetch = None
ghub = Github(auth=auth)
print(' getting repo ' + owner + '/' + repo)
repo = ghub.get_repo(owner + '/' + repo)
if '-b' in version:
release = repo.get_releases()[0]
latest_version = release.name
else:
try:
release = repo.get_latest_release()
latest_version = release.tag_name
except:
tags = repo.get_tags()
try:
if tags is not None:
latest_version = tags[0].name
except:
commits = repo.get_commits()
latest_version = commits[0].sha
if latest_version is not None:
if latest_version.replace('v',''\'') != version.replace('v',''\''):
print(' update found')
print(' Current version: ' + version)
print(' Latest version: ' + latest_version)
result = subprocess.check_output(['nix-prefetch-git', '--quiet', repo.clone_url, '--rev', latest_version])
prefetch = json.loads(result)
print(' New hash: ' + prefetch.get('hash'))
else:
print(' no update')
ghub.close()
except Exception as e:
print(e)
def check_codeberg(owner, repo, version):
feed = feedparser.parse('https://codeberg.org/{0}/{1}/releases.rss'.format(owner, repo))
if feed.status == 200:
entry = feed.entries[0]
if entry.title.replace('v',''\'') != version.replace('v',''\''):
print(' update found')
print(' Current version: ' + version)
print(' Latest version: ' + entry.title)
sha256 = subprocess.check_output(['nix-prefetch-url', url.replace(''\'''\${version}', entry.title.replace('v', ''\''))])
prefetch = subprocess.check_output(['nix', 'hash', 'convert', '--hash-algo', 'sha256', str(sha256.decode('utf-8').strip())])
print(' New hash: ' + prefetch.decode('utf-8').strip())
else:
print(' no update')
def check_open_vsx(publisher, name, version):
open_vsx = requests.get('https://open-vsx.org/api/' + publisher + '/' + name)
if open_vsx.status_code == 200:
extension = open_vsx.json()
latest_version = extension.get('version')
url = extension.get('files').get('download')
if latest_version.replace('v',''\'') != version.replace('v',''\''):
print(' update found')
print(' Current version: ' + version)
print(' Latest version: ' + latest_version)
sha256 = subprocess.check_output(['nix-prefetch-url', url])
prefetch = subprocess.check_output(['nix', 'hash', 'convert', '--hash-algo', 'sha256', str(sha256.decode('utf-8').strip())])
print(' New hash: ' + prefetch.decode('utf-8').strip())
else:
print(' no update')
def parse_nix(package_spec):
version = None
url = None
current_hash = None
owner = None
repo = None
pname = None
name = None
publisher = None
for line in package_spec.readlines():
if 'owner = "' in line and owner is None:
owner = line.split(' = ')[-1].replace('"', ''\'').replace(';\n', ''\'')
if 'repo = "' in line and repo is None:
repo = line.split(' = ')[-1].replace('"', ''\'').replace(';\n', ''\'')
if 'version = "' in line and version is None:
version = line.split(' = ')[-1].replace('"', ''\'').replace(';\n', ''\'')
if 'rev = "' in line and ''\'''\${version}' not in line:
version = line.split(' = ')[-1].replace('"', ''\'').replace(';\n', ''\'')
if 'url = "' in line and url is None:
url = line.split(' = ')[-1].replace('"', ''\'').replace(';\n', ''\'')
if 'sha256 = "' in line or ' hash = "' in line and current_hash is None:
current_hash = line.split(' = ')[-1].replace('"', ''\'').replace(';\n', ''\'')
if 'pname = "' in line and pname is None:
pname = line.split(' = ')[-1].replace('"', ''\'').replace(';\n', ''\'')
if ' name = "' in line and name is None:
name = line.split(' = ')[-1].replace('"', ''\'').replace(';\n', ''\'')
if 'publisher = "' in line and publisher is None:
publisher = line.split(' = ')[-1].replace('"', ''\'').replace(';\n', ''\'')
if url is None and repo is not None:
if 'pname' in repo:
repo = repo.replace(''\'''\${pname}', pname)
url = 'https://github.com/{0}/{1}/releases/tag/{2}'.format(owner, repo, version)
if url is not None and repo is None and 'github' in url:
owner = url.split('github.com/')[-1].split('/')[0]
repo = url.split('github.com/')[-1].split('/')[1]
if url is not None and repo is None and 'codeberg' in url:
owner = url.split('codeberg.org/')[-1].split('/')[0]
repo = url.split('codeberg.org/')[-1].split('/')[1]
if url is not None and version is None:
version = url.split('/')[-1].replace('.tar.gz', ''\'')
if url is not None and publisher is not None:
url = url.replace(''\'''\${publisher}', publisher).replace(''\'''\${name}', name)
return url, current_hash, owner, repo, pname, name, publisher, version
def parse_json(json_versions, flavor=''\''):
versions = json.load(json_versions)
linux_versions = versions.get('linux')
config_versions = versions.get('config')
patch_versions = versions.get('patches')
zfs_versions = versions.get('zfs')
check_kernel(linux_versions, flavor)
check_cachy_config(config_versions, flavor)
check_patch_versions(patch_versions, flavor)
check_zfs_versions(zfs_versions, flavor)
def check_kernel(linux_versions, flavor=''\''):
srcinfo = requests.get('https://raw.githubusercontent.com/CachyOS/linux-cachyos/master/linux-cachyos' + flavor + '/.SRCINFO')
for line in srcinfo.text.split('\n'):
if 'pkgver = ' in line:
kernel_version = line.split('=')[-1].strip()
if kernel_version[-2:] == '.0':
kernel_version = kernel_version[:-2]
if flavor in [''\'', '-lts', '-server', '-gcc', '-hardened']:
release_src = 'https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-' + kernel_version + '.tar.xz'
if flavor == '-rc':
release_src = 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/snapshot/linux-' + kernel_version.replace('.rc', '-rc') + '.tar.gz'
sha256 = subprocess.check_output(['nix-prefetch-url', release_src])
prefetch = subprocess.check_output(['nix', 'hash', 'convert', '--hash-algo', 'sha256', str(sha256.decode('utf-8').strip())])
current_version = linux_versions.get('version')
current_hash = linux_versions.get('hash')
latest_hash = prefetch.decode('utf-8').strip()
print(' Checking linux versions...')
if current_hash != latest_hash:
print(' Current rev: ' + current_version)
print(' Current hash: ' + current_hash)
print(' New rev: ' + kernel_version)
print(' New hash: ' + latest_hash)
else:
print(' no update')
def check_cachy_config(config_versions, flavor=''\''):
result = subprocess.check_output(['nix-prefetch-git', '--quiet', 'https://github.com/CachyOS/linux-cachyos.git'])
prefetch = json.loads(result)
current_version = config_versions.get('rev')
latest_version = prefetch.get('rev')
print(' Checking config versions...')
if current_version != latest_version:
print(' Current rev: ' + current_version)
print(' New rev: ' + latest_version)
print(' New hash: ' + prefetch.get('hash'))
else:
print(' no update')
def check_patch_versions(patch_versions, flavor=''\''):
result = subprocess.check_output(['nix-prefetch-git', '--quiet', 'https://github.com/CachyOS/kernel-patches.git'])
prefetch = json.loads(result)
current_version = patch_versions.get('rev')
latest_version = prefetch.get('rev')
print(' Checking patch versions...')
if current_version != latest_version:
print(' Current rev: ' + current_version)
print(' New rev: ' + latest_version)
print(' New hash: ' + prefetch.get('hash'))
else:
print(' no update')
def kconfig_to_nix(flavor=''\''):
kconfig_result = subprocess.check_output(['nix', 'build', '.#nixosConfigurations.jallen-nas.pkgs.linuxPackages_cachyos' + flavor + '.kernel.kconfigToNix', '--no-link', '--print-out-paths'])
config_file = kconfig_result.decode('utf-8').strip()
if flavor == ''\'':
cachy_flavor = '-gcc'
result = subprocess.check_output(['cat', config_file])
with open('/etc/nixos/packages/linux-cachyos/config-nix/cachyos' + cachy_flavor + '.x86_64-linux.nix', 'w') as config:
config.write(result.decode('utf-8').strip())
def check_zfs_versions(zfs_versions, flavor=''\''):
result = requests.get('https://raw.githubusercontent.com/CachyOS/linux-cachyos/master/linux-cachyos' + flavor + '/PKGBUILD')
for line in result.text.split('\n'):
if 'git+https://github.com/cachyos/zfs.git#commit=' in line:
zfs_rev = line.split('zfs.git#commit=')[-1].replace('")', ''\'')
result = subprocess.check_output(['nix-prefetch-git', '--quiet', 'https://github.com/CachyOS/zfs.git', '--rev', zfs_rev])
prefetch = json.loads(result)
current_version = zfs_versions.get('rev')
latest_version = prefetch.get('rev')
print(' Checking zfs versions...')
if current_version != latest_version:
print(' Current rev: ' + current_version)
print(' New rev: ' + latest_version)
print(' New hash: ' + prefetch.get('hash'))
else:
print(' no update')
for (root,dirs,files) in os.walk('/etc/nixos/packages',topdown=True):
if 'default.nix' in files and 'versions.json' not in files:
print(root.split('/')[-1])
with open(root + '/default.nix', 'r') as package_spec:
url, current_hash, owner, repo, pname, name, publisher, version = parse_nix(package_spec)
if owner is not None and repo is not None and 'codeberg' in url:
check_codeberg(owner, repo, version)
elif owner is not None and repo is not None and 'github' in url:
check_github(owner, repo, version)
elif publisher is not None and 'open-vsx' in url:
check_open_vsx(publisher, name, version)
else:
if url is not None:
print(url)
if 'default.nix' in files and 'versions.json' in files:
with open(root + '/versions.json', 'r') as json_versions:
print('Checking Linux CachyOS')
parse_json(json_versions)
with open(root + '/versions-rc.json', 'r') as json_versions:
print('Checking Linux CachyOS RC')
parse_json(json_versions, '-rc')
with open(root + '/versions-lts.json', 'r') as json_versions:
print('Checking Linux CachyOS LTS')
parse_json(json_versions, '-lts')
with open(root + '/versions-hardened.json', 'r') as json_versions:
print('Checking Linux CachyOS Hardened')
parse_json(json_versions, '-hardened')
'';
in
{
config = {
sops = {
age.keyFile = "/home/${config.home.username}/.config/sops/age/keys.txt";
defaultSopsFile = "/etc/nixos/secrets/secrets.yaml";
validateSopsFiles = false;
secrets = {
"github-token" = { };
};
templates = {
".env".content = ''
GITHUB_TOKEN = "${config.sops.placeholder.github-token}"
'';
};
};
home.packages = [ update-checker ];
};
}

View File

@@ -1,51 +0,0 @@
{
config,
lib,
namespace,
...
}:
let
cfg = config.${namespace}.programs.vesktop;
in
{
options.${namespace}.programs.vesktop = {
enable = lib.mkEnableOption "vesktop";
};
config = lib.mkIf cfg.enable {
programs.vesktop = {
enable = true;
settings = {
appBadge = false;
arRPC = true;
checkUpdates = false;
customTitleBar = false;
disableMinSize = true;
minimizeToTray = true;
tray = true;
splashBackground = "#000000";
splashColor = "#ffffff";
splashTheming = true;
staticTitle = true;
hardwareAcceleration = true;
discordBranch = "stable";
};
vencord = {
settings = {
autoUpdate = false;
autoUpdateNotification = false;
notifyAboutUpdates = false;
useQuickCss = true;
disableMinSize = true;
plugins = {
MessageLogger = {
enabled = false;
ignoreSelf = true;
};
FakeNitro.enabled = false;
};
};
};
};
};
}

File diff suppressed because it is too large Load Diff

360
modules/home/programs/waybar/options.nix Executable file → Normal file
View File

@@ -1,333 +1,69 @@
{ lib, namespace, ... }:
{ lib, ... }:
with lib;
let
inherit (types)
str
int
bool
listOf
attrs
path
nullOr
submodule
;
in
{
options.${namespace}.programs.waybar = {
enable = mkEnableOption "Waybar status bar";
options.mjallen.programs.waybar = {
enable = mkEnableOption "enable waybar";
# Legacy/compat options (kept for backwards compatibility)
layer = mkOption {
type = str;
type = types.str;
default = "top";
description = "Waybar layer (compat). Prefer layout + feature flags.";
};
modules-right = mkOption {
type = with types; listOf str;
default = [ ];
};
networkInterface = mkOption {
type = types.str;
default = "wlan0";
};
extraModules = mkOption {
type = attrs;
type = types.attrs;
default = { };
description = "Extra settings bars at top-level (compat with older module).";
};
extraModulesStyle = mkOption {
type = str;
type = types.str;
default = "";
description = "Extra CSS appended (compat). Prefer extra.style.";
};
windowOffset = mkOption {
type = int;
type = types.int;
default = 4;
description = "Right margin offset for the hyprland/window module (in rem).";
};
# Layout
layout = mkOption {
type = submodule {
options = {
left = mkOption {
type = listOf str;
default = [ "hyprland/workspaces" ];
description = "Modules shown on the left.";
};
center = mkOption {
type = listOf str;
default = [ "hyprland/window" ];
description = "Modules shown in the center.";
};
right = mkOption {
type = listOf str;
default = [
"tray"
"custom/left-end"
"temperature"
"temperature#gpu"
"keyboard-state#capslock"
"keyboard-state#numlock"
"wireplumber#sink"
"bluetooth"
"network"
"idle_inhibitor"
"custom/right-end"
"custom/left-end"
"clock"
"battery"
"custom/notifications"
"custom/weather"
"custom/power"
"custom/right-end"
];
description = "Modules shown on the right.";
};
};
};
default = { };
description = "Waybar module layout.";
};
# Network
network = mkOption {
type = submodule {
options = {
interface = mkOption {
type = str;
default = "wlan0";
description = "Primary network interface name.";
};
};
};
default = { };
description = "Network configuration.";
};
# Temperatures
temperature = mkOption {
type = submodule {
options = {
cpu = mkOption {
type = submodule {
options = {
enable = mkOption {
type = bool;
default = true;
description = "Enable CPU temperature module.";
};
hwmonPath = mkOption {
type = str;
default = "/sys/devices/pci0000:00/0000:00:18.3/hwmon";
description = "CPU temperature hwmon path.";
};
hwmonFile = mkOption {
type = str;
default = "temp1_input";
description = "CPU temperature hwmon file.";
};
};
};
default = { };
};
gpu = mkOption {
type = submodule {
options = {
enable = mkOption {
type = bool;
default = true;
description = "Enable GPU temperature module.";
};
hwmonPath = mkOption {
type = str;
default = "/sys/devices/pci0000:00/0000:00:01.1/0000:01:00.0/0000:02:00.0/0000:03:00.0/hwmon";
description = "GPU temperature hwmon path.";
};
hwmonFile = mkOption {
type = str;
default = "temp1_input";
description = "GPU temperature hwmon file.";
};
};
};
default = { };
};
};
};
default = { };
description = "Temperature module configuration.";
};
# Features
features = mkOption {
type = submodule {
options = {
tray = mkOption {
type = submodule {
options = {
enable = mkOption {
type = bool;
default = true;
};
};
};
default = { };
};
bluetooth = mkOption {
type = submodule {
options = {
enable = mkOption {
type = bool;
default = true;
};
};
};
default = { };
};
idleInhibitor = mkOption {
type = submodule {
options = {
enable = mkOption {
type = bool;
default = true;
};
};
};
default = { };
};
keyboardIndicators = mkOption {
type = submodule {
options = {
enable = mkOption {
type = bool;
default = true;
};
};
};
default = { };
};
audio = mkOption {
type = submodule {
options = {
sink = mkOption {
type = submodule {
options = {
enable = mkOption {
type = bool;
default = true;
};
};
};
default = { };
};
source = mkOption {
type = submodule {
options = {
enable = mkOption {
type = bool;
default = false;
};
};
};
default = { };
};
};
};
default = { };
};
weather = mkOption {
type = submodule {
options = {
enable = mkOption {
type = bool;
default = true;
};
};
};
default = { };
};
hass = mkOption {
type = submodule {
options = {
enable = mkOption {
type = bool;
default = false;
};
};
};
default = { };
};
clock = mkOption {
type = submodule {
options = {
enable = mkOption {
type = bool;
default = true;
};
};
};
default = { };
};
battery = mkOption {
type = submodule {
options = {
enable = mkOption {
type = bool;
default = true;
};
};
};
default = { };
};
};
};
default = { };
description = "Toggle optional Waybar features.";
};
# Styling
style = mkOption {
type = submodule {
options = {
file = mkOption {
type = nullOr path;
default = null;
description = "Optional external CSS file to use instead of the inline style.";
};
fragmentsDir = mkOption {
type = nullOr path;
default = null;
description = "Optional directory of CSS fragments to append.";
};
};
};
default = { };
description = "Styling configuration.";
};
# Extra overrides
extra = mkOption {
type = submodule {
options = {
settings = mkOption {
type = attrs;
default = { };
description = "Extra settings merged into settings.mainBar.";
};
style = mkOption {
type = str;
default = "";
description = "Extra CSS appended to the computed style.";
};
};
};
default = { };
description = "Extra settings/style hooks.";
};
# Waybar modules config
# modules = mkOption {
# type = types.submodule {
# options = {
# # Modules
# window = mkOption {
# type = types.submodule {
# options = {
# # Waybar Module CSS
# margin-right = mkOption {
# type = types.str;
# default = "4";
# };
# };
# };
# };
# temperature = mkOption {
# type = types.submodule {
# options = {
# # Waybar Module CSS
# margin-right = mkOption {
# type = types.str;
# default = "4";
# };
# };
# };
# };
# };
# };
# default = { };
# };
};
}

View File

@@ -1,40 +0,0 @@
{
config,
lib,
namespace,
pkgs,
...
}:
let
cfg = config.${namespace}.programs.waybar;
waybar-audio = pkgs.writeScriptBin "waybar-audio" ''
#!/usr/bin/env bash
# Extract sink entries: "ID Description"
choices=$(pw-dump | jq -r '
.. | objects
| select(.["media.class"] == "Audio/Sink")
| "\(.["object.id"]) \(.["node.description"])"
'
)
# Show wofi menu
selected=$(printf "%s\n" "$choices" | wofi --dmenu --prompt "Audio Output")
# Extract ID
id=$(echo "$selected" | awk '{print $1}')
# Switch to the selected device
if [ -n "$id" ]; then
wpctl set-default "$id"
fi
'';
in
{
imports = [ ../options.nix ];
config = lib.mkIf cfg.enable {
home.packages = [ waybar-audio ];
};
}

View File

@@ -6,13 +6,11 @@
...
}:
let
cfg = config.${namespace}.programs.waybar;
cfg = config.mjallen.programs.waybar;
pythonEnv = pkgs.python3.withPackages (
_ps: with pkgs.${namespace}; [
homeassistant-api
]
);
pythonEnv = pkgs.python3.withPackages (_ps: [
pkgs.${namespace}.homeassistant-api
]);
waybar-hass = pkgs.writeScriptBin "waybar-hass" ''
#!${pythonEnv}/bin/python

View File

@@ -1,61 +0,0 @@
{
config,
lib,
namespace,
pkgs,
...
}:
let
cfg = config.${namespace}.programs.waybar;
waybar-media = pkgs.writeScriptBin "waybar-media" ''
#!/usr/bin/env bash
# Get current playing song from playerctl
if command -v playerctl &> /dev/null; then
# Check if any player is running
if playerctl status &> /dev/null; then
artist=$(playerctl metadata xesam:artist 2>/dev/null)
title=$(playerctl metadata xesam:title 2>/dev/null)
if [[ -n "$artist" && -n "$title" ]]; then
echo " $artist - $title"
elif [[ -n "$title" ]]; then
echo " ''\${title//&/&amp;}"
else
echo " Music Playing"
fi
else
echo ""
fi
else
echo ""
fi
'';
waybar-media-art = pkgs.writeScriptBin "waybar-media-art" ''
#!/usr/bin/env bash
# Get current playing song from playerctl
if command -v playerctl &> /dev/null; then
# Check if any player is running
if playerctl status &> /dev/null; then
art=$(playerctl metadata mpris:artUrl 2>/dev/null)
if [[ -n "$art" ]]; then
echo ''\${art#file://}
fi
fi
fi
'';
in
{
imports = [ ../options.nix ];
config = lib.mkIf cfg.enable {
home.packages = [
waybar-media
waybar-media-art
];
};
}

Some files were not shown because too many files have changed in this diff Show More