Compare commits
31 Commits
6a7549e025
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ac2ca3c8be | |||
| 9f7431bb36 | |||
| b80316267f | |||
| e64c3d1310 | |||
| 75be43140f | |||
| 38bd5b63f5 | |||
| 31de8eccb0 | |||
| cb8835bbfb | |||
| ea79663c96 | |||
| 2e9ea868b6 | |||
| f806506d9a | |||
|
62c70dab19
|
|||
|
4ee5658053
|
|||
|
f489259af9
|
|||
|
0893f80600
|
|||
|
582faec359
|
|||
|
205a1ccc8f
|
|||
|
5116faf0d3
|
|||
|
c5f771bd53
|
|||
|
0457d618e1
|
|||
|
305c0c16ba
|
|||
| 8b6bd110c1 | |||
|
f180cd6230
|
|||
|
4e4818343b
|
|||
|
70881cb0cb
|
|||
|
154486074f
|
|||
|
3ce6c956f6
|
|||
|
2ea4490891
|
|||
|
1649160981
|
|||
|
b419708566
|
|||
|
c8042ef095
|
@@ -0,0 +1,66 @@
|
|||||||
|
# Source: adapted from the official cachix/install-nix-action "Flakes CI workflow" example
|
||||||
|
# https://github.com/cachix/install-nix-action
|
||||||
|
# (README: "Flakes CI workflow with nix build and flake check")
|
||||||
|
# Every action used here (actions/checkout) is from an official GitHub repo.
|
||||||
|
#
|
||||||
|
# Adaptations for Gitea Actions:
|
||||||
|
# * runs-on: native - Gitea's self-hosted native runner. cachix/install-nix-action
|
||||||
|
# explicitly supports self-hosted runners, and this runner's host already provides
|
||||||
|
# Nix (Lix), so the installer step is omitted and flakes are enabled via NIX_CONFIG
|
||||||
|
# (identical to the action's `extra_nix_config: experimental-features = nix-command flakes`).
|
||||||
|
# * The native runner only puts its `hostPackages` on PATH, which does NOT include Nix.
|
||||||
|
# Each job therefore exports the host's system Nix (/run/current-system/sw/bin) onto
|
||||||
|
# PATH before invoking `nix`. This uses the host's actual Lix rather than installing a
|
||||||
|
# second Nix client that would mismatch the running Lix daemon.
|
||||||
|
# * Gitea context vars (gitea.workflow / gitea.head_ref / gitea.sha) for concurrency.
|
||||||
|
# * matrix over x86_64-linux + aarch64-linux.
|
||||||
|
# All jobs use --no-build (eval-only) because the runner has 2 cores / 4GB RAM:
|
||||||
|
# building nix derivations would exhaust memory and crash the server.
|
||||||
|
# Pre-commit hooks run locally via git-hooks-nix on the developer's machine.
|
||||||
|
# * aarch64 builds need QEMU binfmt (boot.binfmt.emulatedSystems) + nix extra-platforms,
|
||||||
|
# which require a nixos-rebuild switch not yet applied. Flake-check for aarch64
|
||||||
|
# runs eval-only (no build) and passes.
|
||||||
|
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
# Least-privilege by default; jobs opt into what they need.
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ gitea.workflow }}-${{ gitea.head_ref || gitea.sha }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
env:
|
||||||
|
NIX_CONFIG: |
|
||||||
|
experimental-features = nix-command flakes
|
||||||
|
extra-platforms = aarch64-linux
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
flake-check:
|
||||||
|
name: Flake check (${{ matrix.system }})
|
||||||
|
runs-on: native
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
system:
|
||||||
|
- x86_64-linux
|
||||||
|
- aarch64-linux
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Flake check (${{ matrix.system }})
|
||||||
|
run: |
|
||||||
|
export PATH=/run/current-system/sw/bin:$PATH
|
||||||
|
nix flake check --no-build --system ${{ matrix.system }}
|
||||||
@@ -1,4 +1,2 @@
|
|||||||
.direnv
|
.direnv
|
||||||
.pre-commit-config.yaml
|
.pre-commit-config.yaml
|
||||||
secrets/id_ed25519
|
|
||||||
secrets/id_ed25519.pub
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
creation_rules:
|
||||||
|
- path_regex: secrets/secrets\.yaml$
|
||||||
|
age: age1vtlutzzhgsy4ef8jd97jaay30zwygj68jwgn0zx64m2d5u57sqzsngy7lf
|
||||||
@@ -1,177 +1,151 @@
|
|||||||
<p align="center"><img src="https://i.imgur.com/X5zKxvp.png" width=300px></p>
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://hyprland.org/">
|
<a href="https://github.com/YaLTeR/niri/">
|
||||||
<img src="https://img.shields.io/static/v1?label=NIRI&message=latest&style=flat&logo=hyprland&colorA=24273A&colorB=8AADF4&logoColor=CAD3F5"/>
|
<img src="https://img.shields.io/static/v1?label=WM&message=Niri&style=flat&logo=hyprland&colorA=24273A&colorB=8AADF4&logoColor=CAD3F5"/>
|
||||||
</a>
|
|
||||||
<a href="https://github.com/zemmsoares/awesome-rices">
|
|
||||||
<img src="https://raw.githubusercontent.com/zemmsoares/awesome-rices/main/assets/awesome-rice-badge.svg" alt="awesome-rice-badge">
|
|
||||||
</a>
|
</a>
|
||||||
<a href="https://nixos.wiki/wiki/Flakes">
|
<a href="https://nixos.wiki/wiki/Flakes">
|
||||||
<img src="https://img.shields.io/static/v1?label=Nix-Flake&message=check&style=flat&logo=nixos&colorA=24273A&colorB=9173ff&logoColor=CAD3F5">
|
<img src="https://img.shields.io/static/v1?label=Nix&message=Flakes&style=flat&logo=nixos&colorA=24273A&colorB=9173ff&logoColor=CAD3F5"/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://nixos.org/">
|
<a href="https://nixos.org/">
|
||||||
<img src="https://img.shields.io/badge/NixOS-unstable-informational.svg?style=flat&logo=nixos&logoColor=CAD3F5&colorA=24273A&colorB=8AADF4">
|
<img src="https://img.shields.io/badge/NixOS-unstable-informational.svg?style=flat&logo=nixos&logoColor=CAD3F5&colorA=24273A&colorB=8AADF4"/>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1 align="center">芯 (Shin)</h1>
|
<h1 align="center">芯 (Shin)</h1>
|
||||||
|
|
||||||
### ⚠ <sup><sub><samp>PLEASE RESPECT THE CREDITS IF YOU USE SOMETHING FROM MY DESKTOP/SETUP.</samp></sub></sup>
|
**Personal NixOS + Home Manager flake.** Builds for `x86_64-linux` and `aarch64-linux`. Uses [flake-parts](https://flake.parts/).
|
||||||
|
|
||||||
> **Note:** This configuration has been refactored to remove Home Manager to reduce evaluation overhead. While projects like `hjem` and `hjem-rum` were considered, a pure NixOS approach was chosen for simplicity and performance.
|
| Host | Target | WM | Notes |
|
||||||
|
|------|--------|-----|-------|
|
||||||
|
| `desktop` | x86_64 | Niri | Full graphical |
|
||||||
|
| `laptop` | x86_64 | Niri | Battery, bluetooth, location |
|
||||||
|
| `tty` | x86_64 | — | Headless server |
|
||||||
|
| `hetzner` | x86_64 (24.05) | — | Isolated server, disko + sops-nix |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<pre align="center"><p align="center"><a href="#seedling--setup">SETUP</a> • <a href="#herb--guides">GUIDES</a> • <a href="#four_leaf_clover--key-bindings">KEYBINDS</a> • <a href="https://linu.dev/kaku">GALLERY</a></p></pre>
|
## Quick Install (with disko — recommended)
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<a href="#octocat--hi-there-thanks-for-dropping-by">
|
|
||||||
<picture>
|
|
||||||
<img alt="" align="right" width="400px" src="/assets/6.png"/>
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
- **Window Manager** • [Niri](https://github.com/YaLTeR/niri/)🎨 Scrolleable WM!
|
|
||||||
- **Shell** • [Fish](https://fishshell.com/) 🐟 with
|
|
||||||
[starship](https://github.com/starship/starship) Cross Shell Platform!
|
|
||||||
- **Terminal** • [Ghostty](https://ghostty.org/) 💻 Powerful Hyped term
|
|
||||||
- **Panel** • [Noctalia!](https://noctalia.dev/) 🍧 Beautiful and minimalist desktop shell
|
|
||||||
- **File Manager** • [Yazi](https://github.com/sxyazi/yazi) 🔖 Rustacean File
|
|
||||||
Manager!
|
|
||||||
- **GUI Basic-IDE** • [Helix](https://docs.helix-editor.com/) ✴️ Rustacean vim
|
|
||||||
version!
|
|
||||||
- **GTK Theme** • [GTK](https://github.com/someone/Colloid-gtk-theme) 🐾 My
|
|
||||||
Fork of colloid
|
|
||||||
|
|
||||||
## 🌼 <samp>INSTALLATION (NixOS)</samp>
|
|
||||||
|
|
||||||
> Request:
|
|
||||||
> [NixOs](https://channels.nixos.org/nixos-25.05/latest-nixos-minimal-x86_64-linux.iso)
|
|
||||||
|
|
||||||
- Download ISO.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wget -O https://channels.nixos.org/nixos-24.05/latest-nixos-minimal-x86_64-linux.iso
|
|
||||||
```
|
|
||||||
|
|
||||||
- Boot Into the Installer.
|
|
||||||
|
|
||||||
- Switch to Root: `sudo -i`
|
|
||||||
|
|
||||||
- Partitions:
|
|
||||||
|
|
||||||
_I prefer to use 1GB on the EFI partition. Specifically because the
|
|
||||||
'generations' list may become very long, and to avoid overloading the
|
|
||||||
partition._
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Replace nvme with your disk partition
|
|
||||||
gdisk /dev/nvme0n1
|
|
||||||
```
|
|
||||||
|
|
||||||
- `o` (create new partition table)
|
|
||||||
- `n` (add partition, 512M, type ef00 EFI)
|
|
||||||
- `n` (add partition, remaining space, type 8300 Linux)
|
|
||||||
`w` (write partition table and exit)
|
|
||||||
|
|
||||||
- Format Partitions:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkfs.fat -F 32 -n EFI /dev/nvme0n1p1
|
|
||||||
mkfs.xfs -L NIXOS /dev/nvme0n1p2
|
|
||||||
```
|
|
||||||
|
|
||||||
- Mount Partitions:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mount /dev/disk/by-label/NIXOS /mnt
|
|
||||||
mkdir -p /mnt/boot
|
|
||||||
mount /dev/disk/by-label/EFI /mnt/boot
|
|
||||||
```
|
|
||||||
|
|
||||||
- Enable nixFlakes
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix-shell -p nixVersions.stable git
|
|
||||||
```
|
|
||||||
|
|
||||||
- Clone my Dotfiles
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Boot NixOS minimal ISO, then:
|
||||||
|
sudo -i
|
||||||
git clone --depth 1 https://github.com/someone/shin /mnt/etc/nixos
|
git clone --depth 1 https://github.com/someone/shin /mnt/etc/nixos
|
||||||
```
|
|
||||||
|
|
||||||
- Generate your Own Nix Hardware Settings:
|
# Auto-partition, format, mount (replace 'laptop' with 'desktop'/'tty')
|
||||||
|
nix --extra-experimental-features "nix-command flakes" \
|
||||||
|
run github:nix-community/disko -- \
|
||||||
|
--mode destroy,format,mount /mnt/etc/nixos/hosts/aesthetic/disko-config.nix
|
||||||
|
|
||||||
### ⚠ <sup><sub><samp>DON'T FORGET IT</samp></sub></sup>
|
# Generate hardware config
|
||||||
|
nixos-generate-config --dir /mnt/etc/nixos/hosts/aesthetic
|
||||||
```bash
|
rm /mnt/etc/nixos/hosts/aesthetic/configuration.nix
|
||||||
sudo nixos-generate-config --dir /mnt/etc/nixos/hosts/aesthetic
|
|
||||||
|
|
||||||
# Remove configuration.nix
|
|
||||||
rm -rf /mnt/etc/nixos/hosts/aesthetic/configuration.nix
|
|
||||||
```
|
|
||||||
|
|
||||||
- Install Dotfiles Using Flake
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Move to folder
|
|
||||||
cd mnt/etc/nixos
|
|
||||||
|
|
||||||
# Install
|
# Install
|
||||||
nixos-install --flake .#aesthetic
|
cd /mnt/etc/nixos
|
||||||
|
nixos-install --flake .#laptop
|
||||||
```
|
```
|
||||||
|
|
||||||
- Reboot
|
**BIOS vs UEFI:** Set in `hosts/aesthetic/default.nix`:
|
||||||
|
```nix
|
||||||
|
boot.loader.limine.bootMode = "bios"; # or "uefi"
|
||||||
|
```
|
||||||
|
|
||||||
### 🐙 <sup><sub><samp>Remember <strong>Default</strong> User & password are: nixos</samp></sub></sup>
|
---
|
||||||
|
|
||||||
- Change Default password for User.
|
## Post-Install: Secrets
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
passwd YourUser
|
# Reboot into new system, then:
|
||||||
|
sops secrets/secrets.yaml
|
||||||
|
# Fill in:
|
||||||
|
# hetzner_server — SSH private key for root@severijnse.eu
|
||||||
|
# gitea_laptop — SSH private key for git@git.severijnse.eu:222
|
||||||
|
# discordo, openrouter, github, twt, gemini, context7, exa — API tokens
|
||||||
|
sudo nixos-rebuild switch --flake /etc/nixos#laptop
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🌻 <samp>TODO LIST</samp>
|
---
|
||||||
|
|
||||||
### 🧩 <samp>ADDITIONAL TIPS</samp>
|
## Daily Workflow
|
||||||
|
|
||||||
If you're using this NixOS configuration flake locally, you can simplify the process of switching and managing your system using [`nh`](https://github.com/viperML/nh), a CLI helper for Nix Flakes.
|
|
||||||
|
|
||||||
To switch your system configuration with `nh`, use:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
NH_FLAKE=/home/someone/Dev/kaku/ nh os switch
|
# Rebuild after changes
|
||||||
|
sudo nixos-rebuild switch --flake /etc/nixos#laptop
|
||||||
|
|
||||||
|
# Or with nh (cleaner)
|
||||||
|
NH_FLAKE=/etc/nixos nh os switch
|
||||||
|
|
||||||
|
# Update flake inputs
|
||||||
|
nix flake update --flake /etc/nixos
|
||||||
|
|
||||||
|
# Format Nix code
|
||||||
|
alejandra /etc/nixos
|
||||||
```
|
```
|
||||||
|
|
||||||
This avoids needing to type out the full `nixos-rebuild` command manually and provides a cleaner workflow when iterating on your setup.
|
---
|
||||||
|
|
||||||
> 💡 Make sure `nh` is installed in your system environment or user profile.
|
## Key Components
|
||||||
|
|
||||||
## 🍀 <samp>KEY BINDINGS</samp>
|
| Layer | Tool | Config |
|
||||||
|
|-------|------|--------|
|
||||||
|
| WM | Niri | `home/window-managers/niri/` |
|
||||||
|
| Shell | Fish + Starship | `home/terminal/shell/` |
|
||||||
|
| Terminal | Ghostty | `home/terminal/emulators/ghostty.nix` |
|
||||||
|
| Panel | Noctalia | `home/services/wayland/noctalia.nix` |
|
||||||
|
| Files | Yazi | `home/terminal/software/yazi.nix` |
|
||||||
|
| Editor | Helix | `home/editors/helix/` |
|
||||||
|
| Secrets | sops-nix (age via SSH host key) | `secrets/secrets.yaml` |
|
||||||
|
| Boot | Limine (BIOS/UEFI) | `system/core/limine.nix` |
|
||||||
|
|
||||||
## 💐 <samp>ACKNOWLEDGEMENTS</samp>
|
---
|
||||||
|
|
||||||
| | | Inspiration and Resources | | |
|
## Flake Structure
|
||||||
| :-: | :-: | :------------------------------------------ | :-- | :-: |
|
|
||||||
| | 1 | [owl4ce](https://github.com/owl4ce) | | |
|
|
||||||
| | 2 | [Ilham25](https://github.com/ilham25) | | |
|
|
||||||
| | 3 | [Siduck](https://github.com/siduck) | | |
|
|
||||||
| | 4 | [NvChad](https://github.com/NvChad) | | |
|
|
||||||
| | 5 | [Rxyhn](https://github.com/rxyhn) | | |
|
|
||||||
| | 6 | [HeinzDev](https://github.com/HeinzDev) | | |
|
|
||||||
| | 7 | [fufexan](https://github.com/fufexan) | | |
|
|
||||||
| | 8 | [AmitGolden](https://github.com/AmitGolden) | | |
|
|
||||||
| | | | | |
|
|
||||||
|
|
||||||
## 🌳 <samp>CONTRIBUTING</samp>
|
```
|
||||||
|
├── flake.nix # Inputs, outputs, perSystem
|
||||||
|
├── hosts/
|
||||||
|
│ ├── default.nix # Host definitions (desktop, laptop, tty, hetzner)
|
||||||
|
│ └── aesthetic/ # Laptop/desktop hardware config
|
||||||
|
├── home/ # Home Manager (user config)
|
||||||
|
│ ├── terminal/ # Shell, emulators, CLI tools
|
||||||
|
│ ├── editors/ # Helix, Zed, Neovim
|
||||||
|
│ ├── window-managers/ # Niri, keybinds
|
||||||
|
│ └── services/ # User systemd services
|
||||||
|
├── system/ # NixOS modules
|
||||||
|
│ ├── core/ # Boot, kernel, security
|
||||||
|
│ └── services/ # System services
|
||||||
|
├── servers/
|
||||||
|
│ └── hetzner/ # Isolated server (24.05, disko, sops)
|
||||||
|
├── secrets/
|
||||||
|
│ ├── secrets.yaml # sops-encrypted (age via SSH host key)
|
||||||
|
│ ├── age.key # Age private key (for sops decryption)
|
||||||
|
│ └── .sops.yaml # sops creation rules
|
||||||
|
└── pkgs/ # Custom packages/overlays
|
||||||
|
```
|
||||||
|
|
||||||
WIP
|
---
|
||||||
|
|
||||||
## 🎃 <samp>SECURITY POLICY</samp>
|
## sops-nix Details
|
||||||
|
|
||||||
<pre align="center">
|
- Encrypts `secrets/secrets.yaml` with **age** using the SSH host ed25519 key (`/etc/ssh/ssh_host_ed25519_key`)
|
||||||
<a href="#readme">BACK TO TOP</a>
|
- Auto-decrypts to `/run/secrets/` at activation
|
||||||
</pre>
|
- Fish loads secrets from `/run/secrets/` via `home/terminal/shell/fish.nix`
|
||||||
|
- SSH config references `/run/secrets/hetzner_server` and `/run/secrets/gitea_laptop`
|
||||||
|
|
||||||
|
To re-encrypt for a new host key:
|
||||||
|
```bash
|
||||||
|
cd /etc/nixos
|
||||||
|
nix shell nixpkgs#sops -c sops --encrypt --age "$(nix shell nixpkgs#ssh-to-age -c ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub)" secrets/secrets.yaml > secrets/secrets.yaml.new
|
||||||
|
mv secrets/secrets.yaml.new secrets/secrets.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Inspired by: [hjem](https://github.com/nix-community/hjem), [owl4ce](https://github.com/owl4ce), [Siduck](https://github.com/siduck), [Rxyhn](https://github.com/rxyhn).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT — use freely, credits appreciated.
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
Generated
+226
-66
@@ -1,49 +1,22 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"agenix": {
|
"disko": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"darwin": "darwin",
|
|
||||||
"home-manager": "home-manager",
|
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixos-24-05"
|
||||||
],
|
|
||||||
"systems": [
|
|
||||||
"systems"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770165109,
|
"lastModified": 1781152676,
|
||||||
"narHash": "sha256-9VnK6Oqai65puVJ4WYtCTvlJeXxMzAp/69HhQuTdl/I=",
|
"narHash": "sha256-RxWs5ND31KzTG7wvMM+PMfUjyNpmIEr999lqNARaM5o=",
|
||||||
"owner": "ryantm",
|
"owner": "nix-community",
|
||||||
"repo": "agenix",
|
"repo": "disko",
|
||||||
"rev": "b027ee29d959fda4b60b57566d64c98a202e0feb",
|
"rev": "ff8702b4de27f72b4c78573dfb89ec74e36abdf1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "ryantm",
|
"owner": "nix-community",
|
||||||
"repo": "agenix",
|
"repo": "disko",
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"darwin": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"agenix",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1744478979,
|
|
||||||
"narHash": "sha256-dyN+teG9G82G+m+PX/aSAagkC+vUv0SgUw3XkPhQodQ=",
|
|
||||||
"owner": "lnl7",
|
|
||||||
"repo": "nix-darwin",
|
|
||||||
"rev": "43975d782b418ebf4969e9ccba82466728c2851b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "lnl7",
|
|
||||||
"ref": "master",
|
|
||||||
"repo": "nix-darwin",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -62,6 +35,22 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flake-compat_2": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767039857,
|
||||||
|
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flake-parts": {
|
"flake-parts": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs-lib": [
|
"nixpkgs-lib": [
|
||||||
@@ -69,11 +58,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1772408722,
|
"lastModified": 1782949081,
|
||||||
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
|
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
|
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -102,19 +91,59 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"git-hooks-nix": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat_2",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1783008725,
|
||||||
|
"narHash": "sha256-jGiy6+sxjNWXSjp25uoJuNfyH9zBK1PEDY0lVoL4ibQ=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"rev": "bca82caa46d5ec0f5d422c61fb1e30bc51313cbe",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"helium": {
|
"helium": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
|
"nixpkgs-darwin": "nixpkgs-darwin",
|
||||||
"utils": "utils"
|
"utils": "utils"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1775098031,
|
"lastModified": 1783756748,
|
||||||
"narHash": "sha256-OfHMljT9Z053S4Tuw3YCaicXZy5ROzvmzCpBqRlSYa0=",
|
"narHash": "sha256-hB8uUHBDsaZafd19ilbNVntTzaF7Sr2zGQDDb1tk8pE=",
|
||||||
"owner": "schembriaiden",
|
"owner": "schembriaiden",
|
||||||
"repo": "helium-browser-nix-flake",
|
"repo": "helium-browser-nix-flake",
|
||||||
"rev": "d79ff6e1c30e93f621ac0d27b0057ff9ee811c1a",
|
"rev": "2efc1e5eaabb81ffcbd66ded45a18bf3aa1c21c5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -123,34 +152,34 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"home-manager": {
|
"himmelblau": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"agenix",
|
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1745494811,
|
"lastModified": 1783966245,
|
||||||
"narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=",
|
"narHash": "sha256-yFLScy0bxU6GLf4ZUoYCzZiyh5AGTT85evjw6xpG++g=",
|
||||||
"owner": "nix-community",
|
"owner": "himmelblau-idm",
|
||||||
"repo": "home-manager",
|
"repo": "himmelblau",
|
||||||
"rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be",
|
"rev": "00b27a924c2a42d54320b9ebc012c22d8dd95e28",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-community",
|
"owner": "himmelblau-idm",
|
||||||
"repo": "home-manager",
|
"ref": "main",
|
||||||
|
"repo": "himmelblau",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"import-tree": {
|
"import-tree": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773693634,
|
"lastModified": 1778781969,
|
||||||
"narHash": "sha256-BtZ2dtkBdSUnFPPFc+n0kcMbgaTxzFNPv2iaO326Ffg=",
|
"narHash": "sha256-Jjuz5CmSkur8KvLDoGa+vylEp+RkQtv4mt/qcMznpH0=",
|
||||||
"owner": "vic",
|
"owner": "vic",
|
||||||
"repo": "import-tree",
|
"repo": "import-tree",
|
||||||
"rev": "c41e7d58045f9057880b0d85e1152d6a4430dbf1",
|
"rev": "d321337efd0f23a9eb14a42adb7b2c29313ab274",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -159,6 +188,27 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mdatp": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1747306795,
|
||||||
|
"narHash": "sha256-PF4ptTqa3gLzb9ac9SGrFyy4KHd2Z47aUUzkaYmrtvM=",
|
||||||
|
"owner": "epetousis",
|
||||||
|
"repo": "nix-mdatp",
|
||||||
|
"rev": "30936772fbc654604004b7049b727f42a0da6c2f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "epetousis",
|
||||||
|
"repo": "nix-mdatp",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nix-index-db": {
|
"nix-index-db": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -166,11 +216,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773552174,
|
"lastModified": 1783835045,
|
||||||
"narHash": "sha256-mHSRNrT1rjeYBgkAlj07dW3+1nFEgAd8Gu6lgyfT9DU=",
|
"narHash": "sha256-yL4Ptl/aH7kJK/4HtLEzpSE40zdMNdXolfgwwGY3bmk=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "nix-index-database",
|
"repo": "nix-index-database",
|
||||||
"rev": "8faeb68130df077450451b6734a221ba0d6cde42",
|
"rev": "9b7545740d701a28a6d46de0665a685191205ba1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -179,13 +229,61 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixos-24-05": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773734432,
|
"lastModified": 1735563628,
|
||||||
"narHash": "sha256-IF5ppUWh6gHGHYDbtVUyhwy/i7D261P7fWD1bPefOsw=",
|
"narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "cda48547b432e8d3b18b4180ba07473762ec8558",
|
"rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-24.05",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1783776592,
|
||||||
|
"narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "e7a3ca8092b61ff85b6a45bf863ea2b2d6a661b3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-darwin": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1783707519,
|
||||||
|
"narHash": "sha256-VJ+eK/EB7aIZ246bXjkYalr4YWQAtHRJeh15Qw2LxUs=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "2b35450a66cf6ee8492099a6eb1903796965d32d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-26.05-darwin",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-unstable": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1783776592,
|
||||||
|
"narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "e7a3ca8092b61ff85b6a45bf863ea2b2d6a661b3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -197,18 +295,65 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"agenix": "agenix",
|
"disko": "disko",
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
|
"git-hooks-nix": "git-hooks-nix",
|
||||||
"helium": "helium",
|
"helium": "helium",
|
||||||
|
"himmelblau": "himmelblau",
|
||||||
"import-tree": "import-tree",
|
"import-tree": "import-tree",
|
||||||
|
"mdatp": "mdatp",
|
||||||
"nix-index-db": "nix-index-db",
|
"nix-index-db": "nix-index-db",
|
||||||
|
"nixos-24-05": "nixos-24-05",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"systems": "systems_2",
|
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||||
|
"rustlock": "rustlock",
|
||||||
|
"sops-nix": "sops-nix",
|
||||||
|
"systems": "systems_3",
|
||||||
"zen-browser": "zen-browser"
|
"zen-browser": "zen-browser"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rustlock": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1782663473,
|
||||||
|
"narHash": "sha256-wPpiuL3EVvNb3+9QgRGtopZXbebb9uUs3Slwuigvh8w=",
|
||||||
|
"owner": "JorySeverijnse",
|
||||||
|
"repo": "rustlock",
|
||||||
|
"rev": "76039750623825172fbee93adae8bb48d4ad36ad",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "JorySeverijnse",
|
||||||
|
"repo": "rustlock",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sops-nix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixos-24-05"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1783174389,
|
||||||
|
"narHash": "sha256-aCWC8ngycU7OdJrU2+Je3qf+1a2ykuBvpPhZT/9tXMc=",
|
||||||
|
"owner": "Mic92",
|
||||||
|
"repo": "sops-nix",
|
||||||
|
"rev": "f1406619a3884cd5c47992a70b8b35c9c0fcb4c9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "Mic92",
|
||||||
|
"repo": "sops-nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"systems": {
|
"systems": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1681028828,
|
"lastModified": 1681028828,
|
||||||
@@ -225,6 +370,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems_2": {
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_3": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1689347949,
|
"lastModified": 1689347949,
|
||||||
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
|
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
|
||||||
@@ -264,11 +424,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773722260,
|
"lastModified": 1783668749,
|
||||||
"narHash": "sha256-U8MsSiqqYZHR3oKE+cCZ2AJt3b4LcXs+oQuNLipaO78=",
|
"narHash": "sha256-EDJjJYGT5pQKTBqmz+OA2sqE20kjj6mP699JFGDzse4=",
|
||||||
"owner": "youwen5",
|
"owner": "youwen5",
|
||||||
"repo": "zen-browser-flake",
|
"repo": "zen-browser-flake",
|
||||||
"rev": "0e6e43342fb3e9a3439fafa777d8cdf98e4ffc49",
|
"rev": "e8041a3571e8cadb57dc18a3d6362d753510b94a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
inputs.flake-parts.lib.mkFlake {inherit inputs;} {
|
inputs.flake-parts.lib.mkFlake {inherit inputs;} {
|
||||||
systems = ["x86_64-linux" "aarch64-linux"];
|
systems = ["x86_64-linux" "aarch64-linux"];
|
||||||
|
|
||||||
imports = [./hosts ./pkgs];
|
imports = [./hosts ./pkgs inputs.git-hooks-nix.flakeModule];
|
||||||
|
|
||||||
perSystem = {
|
perSystem = {
|
||||||
config,
|
config,
|
||||||
@@ -21,6 +21,37 @@
|
|||||||
};
|
};
|
||||||
# Nix Formatter
|
# Nix Formatter
|
||||||
formatter = pkgs.alejandra;
|
formatter = pkgs.alejandra;
|
||||||
|
|
||||||
|
# Pre-commit hooks (flake-parts module from cachix/git-hooks.nix).
|
||||||
|
# `nix build .#checks.<system>.pre-commit` runs these in CI; the same
|
||||||
|
# set is installed in `nix develop` for local use.
|
||||||
|
# alejandra.settings.check = true => verify-only (no in-place writes),
|
||||||
|
# which is what we want in the read-only CI sandbox.
|
||||||
|
pre-commit.settings.hooks = {
|
||||||
|
alejandra = {
|
||||||
|
enable = true;
|
||||||
|
settings.check = true;
|
||||||
|
};
|
||||||
|
statix = {
|
||||||
|
enable = true;
|
||||||
|
# hardware-configuration.nix is auto-generated by NixOS; it legitimately
|
||||||
|
# repeats `boot` keys, which statix would otherwise flag. Exclude it here.
|
||||||
|
settings.ignore = ["hardware-configuration.nix"];
|
||||||
|
# Lint config (statix.toml at repo root). Disables `empty_pattern`, which
|
||||||
|
# flags the standard NixOS `{ ... }:` module pattern that nixpkgs likewise
|
||||||
|
# permits.
|
||||||
|
settings.config = "./statix.toml";
|
||||||
|
};
|
||||||
|
deadnix.enable = true;
|
||||||
|
actionlint.enable = true;
|
||||||
|
trim-trailing-whitespace.enable = true;
|
||||||
|
end-of-file-fixer.enable = true;
|
||||||
|
check-yaml.enable = true;
|
||||||
|
check-toml.enable = true;
|
||||||
|
check-added-large-files.enable = true;
|
||||||
|
check-merge-conflicts.enable = true;
|
||||||
|
detect-private-keys.enable = true;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,17 +71,17 @@
|
|||||||
inputs.nixpkgs-lib.follows = "nixpkgs";
|
inputs.nixpkgs-lib.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Nix-native pre-commit framework (maintained successor to
|
||||||
|
# pre-commit-hooks.nix). Follows our nixpkgs to avoid version skew
|
||||||
|
# with the rest of the flake.
|
||||||
|
git-hooks-nix = {
|
||||||
|
url = "github:cachix/git-hooks.nix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
|
||||||
# rest of inputs, alphabetical order
|
# rest of inputs, alphabetical order
|
||||||
agenix = {
|
|
||||||
url = "github:ryantm/agenix";
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.follows = "nixpkgs";
|
|
||||||
systems.follows = "systems";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
import-tree.url = "github:vic/import-tree";
|
import-tree.url = "github:vic/import-tree";
|
||||||
|
|
||||||
zen-browser = {
|
zen-browser = {
|
||||||
@@ -63,9 +94,44 @@
|
|||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
himmelblau = {
|
||||||
|
url = "github:himmelblau-idm/himmelblau/main";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
mdatp = {
|
||||||
|
url = "github:epetousis/nix-mdatp";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
rustlock = {
|
||||||
|
url = "github:JorySeverijnse/rustlock";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
nix-index-db = {
|
nix-index-db = {
|
||||||
url = "github:Mic92/nix-index-database";
|
url = "github:Mic92/nix-index-database";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# --- Server (hetzner) inputs ---
|
||||||
|
# Pinned to 24.05 to match the deployed Hetzner server exactly.
|
||||||
|
nixos-24-05 = {
|
||||||
|
url = "github:NixOS/nixpkgs/nixos-24.05";
|
||||||
|
};
|
||||||
|
|
||||||
|
nixpkgs-unstable = {
|
||||||
|
url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
disko = {
|
||||||
|
url = "github:nix-community/disko";
|
||||||
|
inputs.nixpkgs.follows = "nixos-24-05";
|
||||||
|
};
|
||||||
|
|
||||||
|
sops-nix = {
|
||||||
|
url = "github:Mic92/sops-nix";
|
||||||
|
inputs.nixpkgs.follows = "nixos-24-05";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,4 @@
|
|||||||
"NEWS.md": "11866"
|
"NEWS.md": "11866"
|
||||||
},
|
},
|
||||||
"version": 8
|
"version": 8
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
indent_type = "Spaces"
|
indent_type = "Spaces"
|
||||||
indent_width = 2
|
indent_width = 2
|
||||||
column_width = 120
|
column_width = 120
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
username = "someone";
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
username = "someone";
|
||||||
|
in {
|
||||||
users.users.${username}.packages = with pkgs; [
|
users.users.${username}.packages = with pkgs; [
|
||||||
neovim
|
neovim
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
config,
|
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
configFile = "zed/settings.json";
|
configFile = "zed/settings.json";
|
||||||
languageConfig = import ./_settings.nix {inherit pkgs lib;};
|
languageConfig = import ./_settings.nix {inherit pkgs lib;};
|
||||||
settingsJSON = builtins.toJSON ({
|
toJSON = (pkgs.formats.json {}).generate;
|
||||||
|
settingsJSON = toJSON "settings.json" ({
|
||||||
node = {
|
node = {
|
||||||
path = "${pkgs.nodejs}/bin/node";
|
path = "${pkgs.nodejs}/bin/node";
|
||||||
npm_path = "${pkgs.nodejs}/bin/npm";
|
npm_path = "${pkgs.nodejs}/bin/npm";
|
||||||
@@ -146,7 +146,7 @@ in {
|
|||||||
zedWithLSP
|
zedWithLSP
|
||||||
];
|
];
|
||||||
xdg.configFile."${configFile}" = {
|
xdg.configFile."${configFile}" = {
|
||||||
text = settingsJSON;
|
source = settingsJSON;
|
||||||
mutable = true;
|
mutable = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{pkgs, ...}: let
|
||||||
|
kotlin-lsp = pkgs.runCommand "kotlin-lsp" {} ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
ln -s ${pkgs.kotlin-language-server}/bin/kotlin-language-server $out/bin/kotlin-lsp
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
users.users.someone.packages = with pkgs; [
|
||||||
|
# android development
|
||||||
|
jdk17
|
||||||
|
gradle
|
||||||
|
android-tools
|
||||||
|
kotlin-lsp
|
||||||
|
ktlint
|
||||||
|
scrcpy
|
||||||
|
(pkgs.androidenv.composeAndroidPackages {
|
||||||
|
buildToolsVersions = ["36.0.0" "35.0.0" "34.0.0" "29.0.3"];
|
||||||
|
platformVersions = ["29" "34" "35" "36"];
|
||||||
|
includeNDK = true;
|
||||||
|
ndkVersions = ["27.0.12077973"];
|
||||||
|
cmakeVersions = ["3.22.1"];
|
||||||
|
}).androidsdk
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -12,11 +12,11 @@ in {
|
|||||||
system.activationScripts.zenArkenfoxUserJs = lib.stringAfter ["users"] ''
|
system.activationScripts.zenArkenfoxUserJs = lib.stringAfter ["users"] ''
|
||||||
zen_dir="${homeDir}/.config/zen"
|
zen_dir="${homeDir}/.config/zen"
|
||||||
if [ -d "$zen_dir" ]; then
|
if [ -d "$zen_dir" ]; then
|
||||||
for profile in "$zen_dir"/*/; do
|
for profile in "$zen_dir"/*; do
|
||||||
if [ -f "$profile/prefs.js" ]; then
|
[ -f "$profile/prefs.js" ] && (
|
||||||
runuser -u someone -- ${pkgs.curl}/bin/curl -A Lix -sL -o "$profile/user.js" \
|
runuser -u someone -- ${pkgs.curl}/bin/curl -A Lix -sL -o "$profile/user.js" \
|
||||||
"https://git.severijnse.eu/jory/user.js/raw/branch/master/user.js"
|
"https://git.severijnse.eu/jory/user.js/raw/branch/master/user.js"
|
||||||
fi
|
)
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|||||||
+10
-8
@@ -8,16 +8,18 @@
|
|||||||
gsettings-desktop-schemas
|
gsettings-desktop-schemas
|
||||||
];
|
];
|
||||||
|
|
||||||
programs.dconf.enable = true;
|
programs.dconf.enable = true;
|
||||||
|
|
||||||
programs.dconf.profiles.user.databases = [{
|
programs.dconf.profiles.user.databases = [
|
||||||
settings = {
|
{
|
||||||
"org/gnome/desktop/interface" = {
|
settings = {
|
||||||
color-scheme = "prefer-dark";
|
"org/gnome/desktop/interface" = {
|
||||||
gtk-application-prefer-dark-theme = true;
|
color-scheme = "prefer-dark";
|
||||||
|
gtk-application-prefer-dark-theme = true;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
}];
|
];
|
||||||
|
|
||||||
environment.sessionVariables = {
|
environment.sessionVariables = {
|
||||||
XDG_ICON_DIR = "${pkgs.whitesur-icon-theme}/share/icons/WhiteSur";
|
XDG_ICON_DIR = "${pkgs.whitesur-icon-theme}/share/icons/WhiteSur";
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
}:
|
}: let
|
||||||
|
configFile = "mpv/mpv.conf";
|
||||||
let
|
|
||||||
configFile = "mpv/mpv.conf";
|
|
||||||
mpvInputFile = "mpv/input.conf";
|
mpvInputFile = "mpv/input.conf";
|
||||||
in
|
in {
|
||||||
{
|
|
||||||
users.users.someone.packages = with pkgs; [
|
users.users.someone.packages = with pkgs; [
|
||||||
mpv
|
mpv
|
||||||
mpvScripts.mpris
|
mpvScripts.mpris
|
||||||
@@ -16,12 +13,11 @@ in
|
|||||||
|
|
||||||
xdg.configFile."${configFile}".text =
|
xdg.configFile."${configFile}".text =
|
||||||
lib.generators.toKeyValue {
|
lib.generators.toKeyValue {
|
||||||
mkKeyValue = k: v:
|
mkKeyValue = k: v: "${lib.escapeShellArg k}=${lib.escapeShellArg v}";
|
||||||
"${lib.escapeShellArg k}=${lib.escapeShellArg v}";
|
|
||||||
listsAsDuplicateKeys = true;
|
listsAsDuplicateKeys = true;
|
||||||
} {
|
} {
|
||||||
profile = "gpu-hq";
|
profile = "gpu-hq";
|
||||||
volume = "100";
|
volume = "100";
|
||||||
};
|
};
|
||||||
|
|
||||||
xdg.configFile."${mpvInputFile}".text = ''
|
xdg.configFile."${mpvInputFile}".text = ''
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
{pkgs, ...}: let
|
{pkgs, ...}: let
|
||||||
configFile = "pipewire/pipewire.conf.d/99-input-denoising.conf";
|
configFile = "pipewire/pipewire.conf.d/99-input-denoising.conf";
|
||||||
|
toJSON = (pkgs.formats.json {}).generate;
|
||||||
in {
|
in {
|
||||||
users.users.someone.packages = with pkgs; [
|
users.users.someone.packages = with pkgs; [
|
||||||
rnnoise
|
rnnoise
|
||||||
rnnoise-plugin
|
rnnoise-plugin
|
||||||
];
|
];
|
||||||
|
|
||||||
xdg.configFile."${configFile}".text = builtins.toJSON {
|
xdg.configFile."${configFile}".source = toJSON "99-input-denoising.conf" {
|
||||||
"context.modules" = [
|
"context.modules" = [
|
||||||
{
|
{
|
||||||
"name" = "libpipewire-module-filter-chain";
|
"name" = "libpipewire-module-filter-chain";
|
||||||
@@ -18,9 +19,9 @@ in {
|
|||||||
{
|
{
|
||||||
"type" = "ladspa";
|
"type" = "ladspa";
|
||||||
"name" = "rnnoise";
|
"name" = "rnnoise";
|
||||||
"plugin" = "${pkgs.rnnoise-plugin}/lib/ladspa/librnnoise_ladspa.so";
|
"plugin" = "librnnoise_ladspa";
|
||||||
"label" = "noise_suppressor_stereo";
|
"label" = "noise_suppressor_stereo";
|
||||||
"control" = {"VAD Threshold (%)" = 70.0;};
|
"control" = {"VAD Threshold (%)" = 50.0;};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
+12
-14
@@ -1,8 +1,4 @@
|
|||||||
{
|
{pkgs, ...}: {
|
||||||
inputs,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}: {
|
|
||||||
users.users.someone.packages = with pkgs; [
|
users.users.someone.packages = with pkgs; [
|
||||||
# screenshot
|
# screenshot
|
||||||
grim
|
grim
|
||||||
@@ -26,7 +22,7 @@
|
|||||||
bun
|
bun
|
||||||
fastfetch
|
fastfetch
|
||||||
act
|
act
|
||||||
protonvpn-gui
|
proton-vpn
|
||||||
|
|
||||||
wl-mirror
|
wl-mirror
|
||||||
pavucontrol
|
pavucontrol
|
||||||
@@ -34,17 +30,19 @@
|
|||||||
croc
|
croc
|
||||||
qbittorrent
|
qbittorrent
|
||||||
weathr
|
weathr
|
||||||
|
distrobox
|
||||||
|
ffmpeg
|
||||||
|
|
||||||
|
## DEV ##
|
||||||
|
# javascript
|
||||||
|
elmPackages.nodejs
|
||||||
|
# python
|
||||||
|
python314
|
||||||
|
uv
|
||||||
# gnome
|
# gnome
|
||||||
dconf-editor
|
dconf-editor
|
||||||
file-roller
|
|
||||||
gnome-control-center
|
|
||||||
gnome-text-editor
|
|
||||||
nautilus
|
|
||||||
(papers.override {supportNautilus = true;})
|
(papers.override {supportNautilus = true;})
|
||||||
inkscape
|
awww
|
||||||
scrcpy
|
|
||||||
android-tools
|
|
||||||
swww
|
|
||||||
openvpn
|
openvpn
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{pkgs}: {
|
{...}: {
|
||||||
"XF86AudioPlay" = {
|
"XF86AudioPlay" = {
|
||||||
_props.allow-when-locked = true;
|
_props.allow-when-locked = true;
|
||||||
spawn._args = ["playerctl" "play-pause"];
|
spawn._args = ["playerctl" "play-pause"];
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
};
|
};
|
||||||
"XF86AudioMute" = {
|
"XF86AudioMute" = {
|
||||||
_props.allow-when-locked = true;
|
_props.allow-when-locked = true;
|
||||||
spawn._args = ["wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"];
|
spawn._args = ["sh" "-c" "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle && /etc/nixos/scripts/volume-notify.sh"];
|
||||||
};
|
};
|
||||||
"XF86AudioMicMute" = {
|
"XF86AudioMicMute" = {
|
||||||
_props.allow-when-locked = true;
|
_props.allow-when-locked = true;
|
||||||
@@ -25,25 +25,25 @@
|
|||||||
};
|
};
|
||||||
"XF86AudioRaiseVolume" = {
|
"XF86AudioRaiseVolume" = {
|
||||||
_props.allow-when-locked = true;
|
_props.allow-when-locked = true;
|
||||||
spawn._args = ["wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "5%+"];
|
spawn._args = ["sh" "-c" "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+ && /etc/nixos/scripts/volume-notify.sh"];
|
||||||
};
|
};
|
||||||
"XF86AudioLowerVolume" = {
|
"XF86AudioLowerVolume" = {
|
||||||
_props.allow-when-locked = true;
|
_props.allow-when-locked = true;
|
||||||
spawn._args = ["wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "5%-"];
|
spawn._args = ["sh" "-c" "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- && /etc/nixos/scripts/volume-notify.sh"];
|
||||||
};
|
};
|
||||||
"XF86MonBrightnessUp" = {
|
"XF86MonBrightnessUp" = {
|
||||||
_props.allow-when-locked = true;
|
_props.allow-when-locked = true;
|
||||||
spawn._args = ["brightnessctl" "set" "10%+"];
|
spawn._args = ["sh" "-c" "brightnessctl set 10%+ && /etc/nixos/scripts/brightness-notify.sh"];
|
||||||
};
|
};
|
||||||
"XF86MonBrightnessDown" = {
|
"XF86MonBrightnessDown" = {
|
||||||
_props.allow-when-locked = true;
|
_props.allow-when-locked = true;
|
||||||
spawn._args = ["brightnessctl" "set" "10%-"];
|
spawn._args = ["sh" "-c" "brightnessctl set 10%- && /etc/nixos/scripts/brightness-notify.sh"];
|
||||||
};
|
};
|
||||||
"Print".spawn._args = ["sh" "-c" "grim -g \"$(slurp)\" - | satty --filename - --output-filename ~/Pictures/Screenshots/satty-$(date +%Y%m%d-%H%M%S).png"];
|
"Print".spawn._args = ["sh" "-c" "grim -g \"$(slurp)\" - | satty --filename - --output-filename ~/Pictures/Screenshots/satty-$(date +%Y%m%d-%H%M%S).png"];
|
||||||
"Mod+B".spawn._args = ["zen"];
|
"Mod+B".spawn._args = ["zen"];
|
||||||
"Mod+Return".spawn._args = ["alacritty"];
|
"Mod+Return".spawn._args = ["alacritty"];
|
||||||
"Mod+Q".close-window = {};
|
"Mod+Q".close-window = {};
|
||||||
"Mod+Shift+P".spawn._args = ["rofi-powermenu"];
|
"Mod+Shift+P".spawn._args = ["rustlock" "--screenshots" "--clock" "--effect-blur" "7x5" "--effect-vignette" "0.5:0.5"];
|
||||||
"Mod+S".switch-preset-column-width = {};
|
"Mod+S".switch-preset-column-width = {};
|
||||||
"Mod+F".fullscreen-window = {};
|
"Mod+F".fullscreen-window = {};
|
||||||
"Mod+1".set-column-width = "25%";
|
"Mod+1".set-column-width = "25%";
|
||||||
@@ -71,5 +71,5 @@
|
|||||||
"Mod+Shift+Up".move-column-to-workspace-up = {};
|
"Mod+Shift+Up".move-column-to-workspace-up = {};
|
||||||
"Mod+Shift+Down".move-column-to-workspace-down = {};
|
"Mod+Shift+Down".move-column-to-workspace-down = {};
|
||||||
"Mod+P" = {spawn-sh = "wl-mirror $(niri msg --json focused-output | jq -r .name)";};
|
"Mod+P" = {spawn-sh = "wl-mirror $(niri msg --json focused-output | jq -r .name)";};
|
||||||
# "Mod+Alt".toggle-overview = { repeat=false; };
|
# "Mod+Alt".toggle-overview = { repeat=false; };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{
|
{
|
||||||
geometry-corner-radius._args = [8.0 8.0 12.0 12.0];
|
geometry-corner-radius._args = [8.0 8.0 12.0 12.0];
|
||||||
clip-to-geometry = true;
|
clip-to-geometry = true;
|
||||||
draw-border-with-background = false;
|
draw-border-with-background = false;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
match._props = {is-floating = true;};
|
match._props = {is-floating = true;};
|
||||||
@@ -168,6 +168,10 @@
|
|||||||
match._props = {title = "Error";};
|
match._props = {title = "Error";};
|
||||||
open-floating = true;
|
open-floating = true;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
match._props = {app-id = "Yazi:";};
|
||||||
|
open-floating = true;
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
layer-rule = [
|
layer-rule = [
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ in {
|
|||||||
spawn-at-startup = [
|
spawn-at-startup = [
|
||||||
["wl-paste" "--watch" "cliphist" "store"]
|
["wl-paste" "--watch" "cliphist" "store"]
|
||||||
["wl-paste" "--type" "text" "--watch" "cliphist" "store"]
|
["wl-paste" "--type" "text" "--watch" "cliphist" "store"]
|
||||||
["dunst"]
|
["mako"]
|
||||||
["ashell"]
|
["ashell"]
|
||||||
["elephant"]
|
["elephant"]
|
||||||
["copyq"]
|
["copyq"]
|
||||||
["swww-daemon"]
|
["awww-daemon"]
|
||||||
["swww" "img" "/etc/nixos/background.jpg" "--transition-type" "grow" "--transition-pos" "center" "--transition-duration" "3"]
|
["awww" "img" "/etc/nixos/background.jpg" "--transition-type" "grow" "--transition-pos" "center" "--transition-duration" "3"]
|
||||||
];
|
];
|
||||||
|
|
||||||
input = {
|
input = {
|
||||||
@@ -48,7 +48,7 @@ in {
|
|||||||
|
|
||||||
screenshot-path = "~/Pictures/Screenshots/Screenshot-from-%Y-%m-%d-%H-%M-%S.png";
|
screenshot-path = "~/Pictures/Screenshots/Screenshot-from-%Y-%m-%d-%H-%M-%S.png";
|
||||||
|
|
||||||
overview = {
|
overview = {
|
||||||
workspace-shadow.off = {};
|
workspace-shadow.off = {};
|
||||||
backdrop-color = "transparent";
|
backdrop-color = "transparent";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,10 +36,11 @@
|
|||||||
(
|
(
|
||||||
if typeOf element == "null"
|
if typeOf element == "null"
|
||||||
then "null"
|
then "null"
|
||||||
else if element == false
|
else if typeOf element == "bool"
|
||||||
then "false"
|
then
|
||||||
else if element == true
|
if element
|
||||||
then "true"
|
then "true"
|
||||||
|
else "false"
|
||||||
else if typeOf element == "string"
|
else if typeOf element == "string"
|
||||||
then ''"${sanitizeString element}"''
|
then ''"${sanitizeString element}"''
|
||||||
else toString element
|
else toString element
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
binds = import ./_binds.nix {inherit pkgs;};
|
binds = import ./_binds.nix {inherit pkgs;};
|
||||||
rules = import ./_rules.nix;
|
rules = import ./_rules.nix;
|
||||||
|
|
||||||
finalConfig = toKDL.generate "niri-config.kdl" (settings // {binds = binds;} // rules);
|
finalConfig = toKDL.generate "niri-config.kdl" (settings // {inherit binds;} // rules);
|
||||||
in {
|
in {
|
||||||
environment.sessionVariables = {
|
environment.sessionVariables = {
|
||||||
NIRI_CONFIG = "$HOME/.config/niri/config.kdl";
|
NIRI_CONFIG = "$HOME/.config/niri/config.kdl";
|
||||||
|
|||||||
@@ -1,46 +1,40 @@
|
|||||||
{
|
{pkgs, ...}: let
|
||||||
config,
|
toTOML = (pkgs.formats.toml {}).generate;
|
||||||
pkgs,
|
in {
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: {
|
|
||||||
users.users.someone.packages = [pkgs.ashell];
|
users.users.someone.packages = [pkgs.ashell];
|
||||||
|
|
||||||
xdg.configFile."ashell/config.toml".text = ''
|
xdg.configFile."ashell/config.toml".source = toTOML "config.toml" {
|
||||||
[modules]
|
modules = {
|
||||||
left = [ [ "appLauncher", "Updates", "Workspaces", "ScreenMirror" ], "MediaPlayer" ]
|
left = [["appLauncher" "Updates" "Workspaces" "ScreenMirror"] "MediaPlayer"];
|
||||||
center = [ "WindowTitle" ]
|
center = ["WindowTitle"];
|
||||||
right = [ "SystemInfo", [ "Tray", "Clock", "Privacy", "Settings" ] ]
|
right = ["SystemInfo" ["Tray" "Clock" "Privacy" "Settings"]];
|
||||||
|
};
|
||||||
|
system_info = {
|
||||||
[system_info]
|
indicators = ["Cpu" "Memory" "Temperature"];
|
||||||
indicators = [ "Cpu", "Memory", "Temperature" ]
|
cpu = {
|
||||||
|
warn_threshold = 60;
|
||||||
[system_info.cpu]
|
alert_threshold = 80;
|
||||||
warn_threshold = 60
|
};
|
||||||
alert_threshold = 80
|
memory = {
|
||||||
|
warn_threshold = 70;
|
||||||
[system_info.memory]
|
alert_threshold = 85;
|
||||||
warn_threshold = 70
|
};
|
||||||
alert_threshold = 85
|
temperature = {
|
||||||
|
warn_threshold = 60;
|
||||||
[system_info.temperature]
|
alert_threshold = 80;
|
||||||
warn_threshold = 60
|
};
|
||||||
alert_threshold = 80
|
};
|
||||||
|
tempo = {
|
||||||
[tempo]
|
clock_format = "%a %d %b %R";
|
||||||
clock_format = "%a %d %b %R"
|
weather_location = {City = "Amsterdam";};
|
||||||
weather_location = { City = "Amsterdam" }
|
};
|
||||||
|
appearance.style = "Islands";
|
||||||
[appearance]
|
ScreenMirror = [
|
||||||
style = "Islands"
|
{
|
||||||
|
name = "ScreenMirror";
|
||||||
[[ScreenMirror]]
|
icon = "";
|
||||||
name = "ScreenMirror"
|
command = "bash /etc/nixos/scripts/screen-mirror.sh";
|
||||||
icon = ""
|
}
|
||||||
command = "bash /etc/nixos/scripts/screen-mirror.sh"
|
];
|
||||||
|
};
|
||||||
|
|
||||||
'';
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: {
|
|
||||||
users.users.someone.packages = [pkgs.dunst];
|
|
||||||
|
|
||||||
xdg.configFile."dunst/dunstrc".text = ''
|
|
||||||
[global]
|
|
||||||
width = 300
|
|
||||||
height = 100
|
|
||||||
offset = 10x10
|
|
||||||
origin = top-right
|
|
||||||
frame_width = 0
|
|
||||||
frame_color = "#1f1d2e"
|
|
||||||
separator_color = "#313244"
|
|
||||||
font = "Source Code Pro 10"
|
|
||||||
line_height = 4
|
|
||||||
icon_position = left
|
|
||||||
max_icon_size = 48
|
|
||||||
min_icon_size = 24
|
|
||||||
show_age_threshold = 60
|
|
||||||
word_wrap = yes
|
|
||||||
ellipsize = middle
|
|
||||||
ignore_newline = no
|
|
||||||
stack_duplicates = true
|
|
||||||
hide_duplicate_count = false
|
|
||||||
show_indicators = yes
|
|
||||||
enable_recursive_icon_lookup = true
|
|
||||||
always_merge_stack_duplicates = false
|
|
||||||
icon_theme = "Papirus-Dark"
|
|
||||||
corner_radius = 8
|
|
||||||
scale = 1
|
|
||||||
monitor = 0
|
|
||||||
sort = yes
|
|
||||||
notification_limit = 0
|
|
||||||
|
|
||||||
[urgency_low]
|
|
||||||
background = "#1f1d2e"
|
|
||||||
foreground = "#cdd6f4"
|
|
||||||
frame_color = "#89b4fa"
|
|
||||||
|
|
||||||
[urgency_normal]
|
|
||||||
background = "#1f1d2e"
|
|
||||||
foreground = "#cdd6f4"
|
|
||||||
frame_color = "#89b4fa"
|
|
||||||
|
|
||||||
[urgency_critical]
|
|
||||||
background = "#1f1d2e"
|
|
||||||
foreground = "#f38ba8"
|
|
||||||
frame_color = "#f38ba8"
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
@@ -30,16 +30,11 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
xdg.configFile."${configFile}".source = toINI "config.ini" {
|
xdg.configFile."${configFile}".source = toINI "config.ini" {
|
||||||
manual = {
|
|
||||||
lat = "-34.58";
|
|
||||||
lon = "-58.64";
|
|
||||||
};
|
|
||||||
|
|
||||||
general = {
|
general = {
|
||||||
brightness-day = "1.0";
|
brightness-day = "1.0";
|
||||||
brightness-night = "0.5";
|
brightness-night = "0.5";
|
||||||
adjustment-method = "wayland";
|
adjustment-method = "wayland";
|
||||||
location-provider = "manual";
|
location-provider = "geoclue2";
|
||||||
temp-day = "5500";
|
temp-day = "5500";
|
||||||
temp-night = "3500";
|
temp-night = "3500";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{pkgs, ...}: {
|
||||||
|
users.users.someone.packages = [pkgs.mako];
|
||||||
|
|
||||||
|
xdg.configFile."mako/config".text = ''
|
||||||
|
background-color=#282828
|
||||||
|
text-color=#FAB387
|
||||||
|
border-color=#ebdbb2
|
||||||
|
progress-color=#FAB387
|
||||||
|
|
||||||
|
font=SauceCodePro Nerd Font 10
|
||||||
|
width=300
|
||||||
|
height=100
|
||||||
|
margin=20
|
||||||
|
padding=15
|
||||||
|
border-size=2
|
||||||
|
border-radius=2
|
||||||
|
icons=1
|
||||||
|
max-icon-size=48
|
||||||
|
default-timeout=5000
|
||||||
|
group-by=category
|
||||||
|
|
||||||
|
[urgency=low]
|
||||||
|
border-color=#928374
|
||||||
|
|
||||||
|
[urgency=normal]
|
||||||
|
border-color=#b8bb26
|
||||||
|
|
||||||
|
[urgency=critical]
|
||||||
|
border-color=#fb4934
|
||||||
|
default-timeout=0
|
||||||
|
|
||||||
|
[category=osd]
|
||||||
|
anchor=bottom-center
|
||||||
|
width=200
|
||||||
|
height=60
|
||||||
|
margin=0,0,100,0
|
||||||
|
padding=10
|
||||||
|
default-timeout=2000
|
||||||
|
text-alignment=center
|
||||||
|
border-color=#b8bb26
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -1,34 +1,32 @@
|
|||||||
{
|
{pkgs, ...}: let
|
||||||
config,
|
toTOML = (pkgs.formats.toml {}).generate;
|
||||||
pkgs,
|
in {
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: {
|
|
||||||
users.users.someone.packages = [pkgs.walker pkgs.elephant];
|
users.users.someone.packages = [pkgs.walker pkgs.elephant];
|
||||||
|
|
||||||
xdg.configFile."walker/config.toml".text = ''
|
xdg.configFile."walker/config.toml".source = toTOML "config.toml" {
|
||||||
[keys]
|
keys = {
|
||||||
launcher = "Mod+Space"
|
launcher = "Mod+Space";
|
||||||
|
quit = {
|
||||||
[keys.quit]
|
quit = "Mod+Shift+Q";
|
||||||
quit = "Mod+Shift+Q"
|
reload = "Mod+Shift+R";
|
||||||
reload = "Mod+Shift+R"
|
};
|
||||||
|
};
|
||||||
[look]
|
look = {
|
||||||
all_monitors = true
|
all_monitors = true;
|
||||||
background = "#1f1d2e"
|
background = "#1f1d2e";
|
||||||
foreground = "#cdd6f4"
|
foreground = "#cdd6f4";
|
||||||
selection = "#45475a"
|
selection = "#45475a";
|
||||||
active = "#89b4fa"
|
active = "#89b4fa";
|
||||||
urgent = "#f38ba8"
|
urgent = "#f38ba8";
|
||||||
alt_background = "#181825"
|
alt_background = "#181825";
|
||||||
icon_theme = "Papirus-Dark"
|
icon_theme = "Papirus-Dark";
|
||||||
terminal = "alacritty"
|
terminal = "alacritty";
|
||||||
|
font = {
|
||||||
[look.font]
|
name = "Source Code Pro";
|
||||||
name = "Source Code Pro"
|
size = 14;
|
||||||
size = 14
|
};
|
||||||
'';
|
};
|
||||||
|
};
|
||||||
|
|
||||||
systemd.user.services.elephant = {
|
systemd.user.services.elephant = {
|
||||||
wantedBy = ["graphical-session.target"];
|
wantedBy = ["graphical-session.target"];
|
||||||
|
|||||||
@@ -1,64 +1,60 @@
|
|||||||
{
|
{pkgs, ...}: let
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
alacritty-wrapped = pkgs.writeShellScriptBin "alacritty-wayland" ''
|
alacritty-wrapped = pkgs.writeShellScriptBin "alacritty-wayland" ''
|
||||||
export WAYLAND_DISPLAY="wayland-1"
|
export WAYLAND_DISPLAY="wayland-1"
|
||||||
export XDG_CURRENT_DESKTOP="Niri"
|
export XDG_CURRENT_DESKTOP="Niri"
|
||||||
export XDG_SESSION_TYPE="wayland"
|
export XDG_SESSION_TYPE="wayland"
|
||||||
exec ${pkgs.alacritty}/bin/alacritty "$@"
|
exec ${pkgs.alacritty}/bin/alacritty "$@"
|
||||||
'';
|
'';
|
||||||
|
toTOML = (pkgs.formats.toml {}).generate;
|
||||||
in {
|
in {
|
||||||
users.users.someone.packages = with pkgs; [
|
users.users.someone.packages = with pkgs; [
|
||||||
alacritty
|
alacritty
|
||||||
alacritty-wrapped
|
alacritty-wrapped
|
||||||
];
|
];
|
||||||
|
|
||||||
xdg.configFile."alacritty/alacritty.toml".text = ''
|
xdg.configFile."alacritty/alacritty.toml".source = toTOML "alacritty.toml" {
|
||||||
[colors.bright]
|
colors = {
|
||||||
black = "0x928374"
|
bright = {
|
||||||
blue = "0x7daea3"
|
black = "0x928374";
|
||||||
cyan = "0x89b482"
|
blue = "0x7daea3";
|
||||||
green = "0xa9b665"
|
cyan = "0x89b482";
|
||||||
magenta = "0xd3869b"
|
green = "0xa9b665";
|
||||||
red = "0xea6962"
|
magenta = "0xd3869b";
|
||||||
white = "0xdfbf8e"
|
red = "0xea6962";
|
||||||
yellow = "0xe3a84e"
|
white = "0xdfbf8e";
|
||||||
|
yellow = "0xe3a84e";
|
||||||
[colors.normal]
|
};
|
||||||
black = "0x665c54"
|
normal = {
|
||||||
blue = "0x7daea3"
|
black = "0x665c54";
|
||||||
cyan = "0x89b482"
|
blue = "0x7daea3";
|
||||||
green = "0xa9b665"
|
cyan = "0x89b482";
|
||||||
magenta = "0xd3869b"
|
green = "0xa9b665";
|
||||||
red = "0xea6962"
|
magenta = "0xd3869b";
|
||||||
white = "0xdfbf8e"
|
red = "0xea6962";
|
||||||
yellow = "0xe78a4e"
|
white = "0xdfbf8e";
|
||||||
|
yellow = "0xe78a4e";
|
||||||
[colors.primary]
|
};
|
||||||
background = "0x1f0d00"
|
primary = {
|
||||||
foreground = "0xdfbf8e"
|
background = "0x1f0d00";
|
||||||
|
foreground = "0xdfbf8e";
|
||||||
[env]
|
};
|
||||||
EDITOR = "nvim"
|
};
|
||||||
XDG_CURRENT_DESKTOP = "Niri"
|
env = {
|
||||||
XDG_SESSION_TYPE = "wayland"
|
EDITOR = "nvim";
|
||||||
|
XDG_CURRENT_DESKTOP = "Niri";
|
||||||
[font]
|
XDG_SESSION_TYPE = "wayland";
|
||||||
size = 13
|
};
|
||||||
|
font = {
|
||||||
[font.normal]
|
size = 13;
|
||||||
family = "SauceCodePro Nerd Font"
|
normal = {
|
||||||
|
family = "SauceCodePro Nerd Font";
|
||||||
[window]
|
};
|
||||||
decorations = "full"
|
};
|
||||||
opacity = 1.0
|
window = {
|
||||||
|
decorations = "full";
|
||||||
[terminal.shell]
|
opacity = 1.0;
|
||||||
program = "fish"
|
};
|
||||||
|
terminal.shell.program = "fish";
|
||||||
[scrolling]
|
scrolling.history = 5000;
|
||||||
history = 5000
|
};
|
||||||
'';
|
|
||||||
}
|
}
|
||||||
|
|||||||
+163
-150
@@ -7,121 +7,135 @@
|
|||||||
'')
|
'')
|
||||||
];
|
];
|
||||||
xdg.configFile = {
|
xdg.configFile = {
|
||||||
"fish/config.fish" = {
|
# ── Modular fish conf.d ──────────────────────────────────────────────────
|
||||||
text = ''
|
# Each file under fish/conf.d/ is sourced automatically by fish at startup,
|
||||||
for secret in discordo openrouter github twt gemini context7 exa
|
# sorted lexicographically. We use numeric prefixes for ordering.
|
||||||
if test -f /run/agenix/$secret
|
|
||||||
set -l val (cat /run/agenix/$secret)
|
# 00: Environment variables
|
||||||
set -l up (string upper $secret)
|
"fish/conf.d/00-env.fish".text = ''
|
||||||
switch $secret
|
set -gx GNUPGHOME $HOME/.config/gnupg
|
||||||
case discordo
|
set -gx NIXPKGS_ALLOW_UNFREE 1
|
||||||
set -gx DISCORDO_TOKEN $val
|
set -gx NIXPKGS_ALLOW_INSECURE 1
|
||||||
set -gx OXICORD_TOKEN $val
|
set -gx EDITOR nvim
|
||||||
case openrouter gemini context7 exa
|
set -gx VISUAL nvim
|
||||||
set -gx "$up"_API_KEY $val
|
set -g fish_greeting
|
||||||
case '*'
|
'';
|
||||||
set -gx "$up"_TOKEN $val
|
|
||||||
end
|
# 01: SOPS secret loader
|
||||||
|
"fish/conf.d/01-secrets.fish".text = ''
|
||||||
|
for secret in discordo openrouter github twt gemini context7 exa
|
||||||
|
if test -f /run/secrets/$secret
|
||||||
|
set -l val (cat /run/secrets/$secret)
|
||||||
|
set -l up (string upper $secret)
|
||||||
|
switch $secret
|
||||||
|
case discordo
|
||||||
|
set -gx DISCORDO_TOKEN $val
|
||||||
|
set -gx OXICORD_TOKEN $val
|
||||||
|
case openrouter gemini context7 exa
|
||||||
|
set -gx "$up"_API_KEY $val
|
||||||
|
case '*'
|
||||||
|
set -gx "$up"_TOKEN $val
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
set -gx GNUPGHOME $HOME/.config/gnupg
|
end
|
||||||
set -gx NIXPKGS_ALLOW_UNFREE 1
|
'';
|
||||||
set -gx NIXPKGS_ALLOW_INSECURE 1
|
|
||||||
set -gx EDITOR nvim
|
# 02: Vi key bindings and cursor shapes
|
||||||
set -gx VISUAL nvim
|
"fish/conf.d/02-key-bindings.fish".text = ''
|
||||||
set -g fish_greeting
|
fish_vi_key_bindings
|
||||||
# Vi keybindings
|
|
||||||
fish_vi_key_bindings
|
function fish_user_key_bindings
|
||||||
# Custom key bindings function (REQUIRED to properly unbind keys)
|
for mode in insert default
|
||||||
function fish_user_key_bindings
|
bind -M $mode ctrl-backspace backward-kill-word
|
||||||
# Custom bindings
|
bind -M $mode ctrl-delete kill-word
|
||||||
for mode in insert default
|
bind -M $mode alt-backspace backward-kill-token
|
||||||
bind -M $mode ctrl-backspace backward-kill-word
|
bind -M $mode alt-delete kill-token
|
||||||
bind -M $mode ctrl-delete kill-word
|
bind -M $mode ctrl-z undo
|
||||||
bind -M $mode alt-backspace backward-kill-token
|
|
||||||
bind -M $mode alt-delete kill-token
|
|
||||||
bind -M $mode ctrl-z undo
|
|
||||||
end
|
|
||||||
bind -M insert \cx\ce edit_command_buffer
|
|
||||||
bind -M default \cx\ce edit_command_buffer
|
|
||||||
bind -M default up history-prefix-search-backward
|
|
||||||
bind -M default down history-prefix-search-forward
|
|
||||||
end
|
end
|
||||||
# Cursor shapes per mode
|
bind -M insert \cx\ce edit_command_buffer
|
||||||
set fish_cursor_default block
|
bind -M default \cx\ce edit_command_buffer
|
||||||
set fish_cursor_insert line
|
bind -M default up history-prefix-search-backward
|
||||||
set fish_cursor_replace_one underscore
|
bind -M default down history-prefix-search-forward
|
||||||
set fish_cursor_visual block
|
end
|
||||||
# Syntax colors
|
|
||||||
set -g fish_color_autosuggestion brblack
|
set fish_cursor_default block
|
||||||
set -g fish_color_command blue
|
set fish_cursor_insert line
|
||||||
set -g fish_color_error red
|
set fish_cursor_replace_one underscore
|
||||||
set -g fish_color_param normal
|
set fish_cursor_visual block
|
||||||
# Search highlight
|
'';
|
||||||
set -g fish_color_search_match --background=normal
|
|
||||||
# Plugin settings
|
# 03: Syntax highlighting colors
|
||||||
set -Ux fifc_editor nvim
|
"fish/conf.d/03-syntax.fish".text = ''
|
||||||
set -U fifc_keybinding \cv
|
set -g fish_color_autosuggestion brblack
|
||||||
set -g __done_min_cmd_duration 10000
|
set -g fish_color_command blue
|
||||||
'';
|
set -g fish_color_error red
|
||||||
};
|
set -g fish_color_param normal
|
||||||
|
set -g fish_color_search_match --background=normal
|
||||||
|
'';
|
||||||
|
|
||||||
|
# 04: Plugin settings
|
||||||
|
"fish/conf.d/04-plugins.fish".text = ''
|
||||||
|
set -Ux fifc_editor nvim
|
||||||
|
set -U fifc_keybinding \cv
|
||||||
|
set -g __done_min_cmd_duration 10000
|
||||||
|
'';
|
||||||
"fish/functions/extract.fish" = {
|
"fish/functions/extract.fish" = {
|
||||||
text = ''
|
text = ''
|
||||||
function extract
|
function extract
|
||||||
if test -f "$argv[1]"
|
if test -f "$argv[1]"
|
||||||
set file "$argv[1]"
|
set file "$argv[1]"
|
||||||
set filename (basename "$file")
|
set filename (basename "$file")
|
||||||
set dirname (string replace -r '\.tar\.bz2$|\.tar\.gz$|\.tbz2$|\.tgz$|\.tar$|\.bz2$|\.gz$|\.zip$|\.Z$|\.7z$|\.xz$|\.rar$' "" "$filename")
|
set dirname (string replace -r '\.tar\.bz2$|\.tar\.gz$|\.tbz2$|\.tgz$|\.tar$|\.bz2$|\.gz$|\.zip$|\.Z$|\.7z$|\.xz$|\.rar$' "" "$filename")
|
||||||
mkdir -p "$dirname"
|
mkdir -p "$dirname"
|
||||||
switch $file
|
switch $file
|
||||||
case "*.tar.bz2"
|
case "*.tar.bz2"
|
||||||
tar xjf "$file" -C "$dirname"
|
tar xjf "$file" -C "$dirname"
|
||||||
case "*.tar.gz"
|
case "*.tar.gz"
|
||||||
tar xzf "$file" -C "$dirname"
|
tar xzf "$file" -C "$dirname"
|
||||||
case "*.tbz2"
|
case "*.tbz2"
|
||||||
tar xjf "$file" -C "$dirname"
|
tar xjf "$file" -C "$dirname"
|
||||||
case "*.tgz"
|
case "*.tgz"
|
||||||
tar xzf "$file" -C "$dirname"
|
tar xzf "$file" -C "$dirname"
|
||||||
case "*.tar"
|
case "*.tar"
|
||||||
tar xf "$file" -C "$dirname"
|
tar xf "$file" -C "$dirname"
|
||||||
case "*.bz2"
|
case "*.bz2"
|
||||||
bunzip2 -c "$file" >"$dirname/$dirname"
|
bunzip2 -c "$file" >"$dirname/$dirname"
|
||||||
case "*.gz"
|
case "*.gz"
|
||||||
gunzip -c "$file" >"$dirname/$dirname"
|
gunzip -c "$file" >"$dirname/$dirname"
|
||||||
case "*.zip"
|
case "*.zip"
|
||||||
unzip "$file" -d "$dirname"
|
unzip "$file" -d "$dirname"
|
||||||
case "*.Z"
|
case "*.Z"
|
||||||
uncompress -c "$file" >"$dirname/$dirname"
|
uncompress -c "$file" >"$dirname/$dirname"
|
||||||
case "*.7z"
|
case "*.7z"
|
||||||
7z x "$file" -o"$dirname"
|
7z x "$file" -o"$dirname"
|
||||||
case "*.xz"
|
case "*.xz"
|
||||||
xz -dc "$file" >"$dirname/$dirname"
|
xz -dc "$file" >"$dirname/$dirname"
|
||||||
case "*.rar"
|
case "*.rar"
|
||||||
unrar x "$file" "$dirname"
|
unrar x "$file" "$dirname"
|
||||||
case "*"
|
case "*"
|
||||||
echo "'$file' cannot be extracted via extract()"
|
echo "'$file' cannot be extracted via extract()"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
echo "'$argv[1]' is not a valid file"
|
echo "'$argv[1]' is not a valid file"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
"fish/functions/weather.fish" = {
|
"fish/functions/weather.fish" = {
|
||||||
text = ''
|
text = ''
|
||||||
function weather
|
function weather
|
||||||
if test "$argv[1]" = week
|
if test "$argv[1]" = week
|
||||||
wthrr -f w $argv[2]
|
wthrr -f w $argv[2]
|
||||||
else if test -z "$argv[1]"
|
else if test -z "$argv[1]"
|
||||||
wthrr -f d
|
wthrr -f d
|
||||||
wthrr -f t
|
wthrr -f t
|
||||||
else
|
else
|
||||||
wthrr -f d $argv[1]
|
wthrr -f d $argv[1]
|
||||||
wthrr -f t $argv[1]
|
wthrr -f t $argv[1]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
"fish/functions/fcd.fish" = {
|
"fish/functions/fcd.fish" = {
|
||||||
text = ''
|
text = ''
|
||||||
function fcd
|
function fcd
|
||||||
@@ -166,47 +180,46 @@ end
|
|||||||
end
|
end
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
"fish/conf.d/aliases.fish" = {
|
# 05: Aliases
|
||||||
text = ''
|
"fish/conf.d/05-aliases.fish".text = ''
|
||||||
alias cleanup="sudo nix-collect-garbage --delete-older-than 1d"
|
alias cleanup="sudo nix-collect-garbage --delete-older-than 1d"
|
||||||
alias listgen="sudo nix-env -p /nix/var/nix/profiles/system --list-generations"
|
alias listgen="sudo nix-env -p /nix/var/nix/profiles/system --list-generations"
|
||||||
alias nixremove="nix-store --gc"
|
alias nixremove="nix-store --gc"
|
||||||
alias bloat="nix path-info -Sh /run/current-system"
|
alias bloat="nix path-info -Sh /run/current-system"
|
||||||
alias cleanram="sudo sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches'"
|
alias cleanram="sudo sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches'"
|
||||||
alias trimall="sudo fstrim -va"
|
alias trimall="sudo fstrim -va"
|
||||||
alias c="clear"
|
alias c="clear"
|
||||||
alias add="git add ."
|
alias add="git add ."
|
||||||
alias commit="git commit"
|
alias commit="git commit"
|
||||||
alias push="git push"
|
alias push="git push"
|
||||||
alias pull="git pull"
|
alias pull="git pull"
|
||||||
alias diff="git diff --staged"
|
alias diff="git diff --staged"
|
||||||
alias gcld="git clone --depth 1"
|
alias gcld="git clone --depth 1"
|
||||||
alias koji="meteor"
|
alias koji="meteor"
|
||||||
alias gitui="lazygit"
|
alias gitui="lazygit"
|
||||||
alias ls="eza -lah --grid -s modified --smart-group --group-directories-first --icons"
|
alias ls="eza -lah --grid -s modified --smart-group --group-directories-first --icons"
|
||||||
alias l="eza -ah --grid -s modified --smart-group --group-directories-first --icons"
|
alias l="eza -ah --grid -s modified --smart-group --group-directories-first --icons"
|
||||||
alias tree="eza --tree --icons --tree"
|
alias tree="eza --tree --icons --tree"
|
||||||
alias cat="${pkgs.bat}/bin/bat --paging=never --theme gruvbox-dark"
|
alias cat="${pkgs.bat}/bin/bat --paging=never --theme gruvbox-dark"
|
||||||
alias us="systemctl --user"
|
alias us="systemctl --user"
|
||||||
alias rs="sudo systemctl"
|
alias rs="sudo systemctl"
|
||||||
alias zed="zeditor"
|
alias zed="zeditor"
|
||||||
alias nl="nl -ba"
|
alias nl="nl -ba"
|
||||||
alias cp="cp -riv"
|
alias cp="cp -riv"
|
||||||
alias mv="mv -iv"
|
alias mv="mv -iv"
|
||||||
alias mkdir="mkdir -pv"
|
alias mkdir="mkdir -pv"
|
||||||
alias less="bat -p --theme gruvbox-dark"
|
alias less="bat -p --theme gruvbox-dark"
|
||||||
alias myip="curl ip.severijnse.eu"
|
alias myip="curl ip.severijnse.eu"
|
||||||
alias pscpu="ps -eo pid,ppid,cmd,%mem,%cpu,etime --sort=-%cpu | head -n 21"
|
alias pscpu="ps -eo pid,ppid,cmd,%mem,%cpu,etime --sort=-%cpu | head -n 21"
|
||||||
alias psmem="ps -eo pid,ppid,cmd,%mem,%cpu,etime --sort=-%mem | head -n 21"
|
alias psmem="ps -eo pid,ppid,cmd,%mem,%cpu,etime --sort=-%mem | head -n 21"
|
||||||
alias weer="weather"
|
alias weer="weather"
|
||||||
alias v="nvim"
|
alias v="nvim"
|
||||||
alias copy="wl-copy"
|
alias copy="wl-copy"
|
||||||
alias send="croc"
|
alias send="croc"
|
||||||
alias img="loupe"
|
alias img="loupe"
|
||||||
alias cat-md="glow"
|
alias cat-md="glow"
|
||||||
alias du="dust"
|
alias du="dust"
|
||||||
alias df="duf"
|
alias df="duf"
|
||||||
'';
|
'';
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
{ config, pkgs, ... }:
|
{
|
||||||
|
config,
|
||||||
let
|
pkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
configFile = "starship/starship.toml";
|
configFile = "starship/starship.toml";
|
||||||
toTOML = (pkgs.formats.toml {}).generate;
|
toTOML = (pkgs.formats.toml {}).generate;
|
||||||
in
|
in {
|
||||||
{
|
|
||||||
environment.sessionVariables = {
|
environment.sessionVariables = {
|
||||||
STARSHIP_CONFIG = "${config.xdg.configHome}/${configFile}";
|
STARSHIP_CONFIG = "${config.xdg.configHome}/${configFile}";
|
||||||
STARSHIP_LOG = "error";
|
STARSHIP_LOG = "error";
|
||||||
};
|
};
|
||||||
|
|
||||||
users.users.someone.packages = [ pkgs.starship ];
|
users.users.someone.packages = [pkgs.starship];
|
||||||
|
|
||||||
xdg.configFile = {
|
xdg.configFile = {
|
||||||
"${configFile}".source = toTOML "starship.toml" {
|
"${configFile}".source = toTOML "starship.toml" {
|
||||||
|
|
||||||
"$schema" = "https://starship.rs/config-schema.json";
|
"$schema" = "https://starship.rs/config-schema.json";
|
||||||
|
|
||||||
add_newline = true;
|
add_newline = true;
|
||||||
@@ -137,7 +137,6 @@ in
|
|||||||
starship init fish | source
|
starship init fish | source
|
||||||
'';
|
'';
|
||||||
|
|
||||||
"fish/completions/starship.fish".source =
|
"fish/completions/starship.fish".source = "${pkgs.starship}/share/fish/vendor_completions.d/starship.fish";
|
||||||
"${pkgs.starship}/share/fish/vendor_completions.d/starship.fish";
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,40 @@
|
|||||||
}: let
|
}: let
|
||||||
configFile = "git/config";
|
configFile = "git/config";
|
||||||
ignoreFile = "git/ignore";
|
ignoreFile = "git/ignore";
|
||||||
|
hooksDir = "git/hooks";
|
||||||
toINI = (pkgs.formats.ini {}).generate;
|
toINI = (pkgs.formats.ini {}).generate;
|
||||||
|
|
||||||
|
# Hook that auto-generates a Conventional Commit message from staged diff
|
||||||
|
prepareCommitMsg = pkgs.writeShellScript "prepare-commit-msg" ''
|
||||||
|
# Only run if there are staged changes
|
||||||
|
if git diff --cached --quiet 2>/dev/null; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Generating commit message from staged changes..." >&2
|
||||||
|
|
||||||
|
# Generate Conventional Commit message using opencode run
|
||||||
|
git diff --cached | timeout 30 ${pkgs.opencode}/bin/opencode run \
|
||||||
|
-m opencode/north-mini-code-free \
|
||||||
|
"You are an expert software engineer writing professional Git commit messages.
|
||||||
|
|
||||||
|
Create a clean Conventional Commit for the provided diff.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Format: type(optional scope): imperative description
|
||||||
|
- Allowed types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
|
||||||
|
- First line: maximum 72 characters, starts with capital letter, imperative present tense
|
||||||
|
- If relevant, add a blank line followed by a short body explaining the motivation and key changes
|
||||||
|
- Be concise and professional. No emojis, no markdown.
|
||||||
|
|
||||||
|
Output ONLY the commit message. Do not add any extra text, quotes, or explanations." \
|
||||||
|
2>/dev/null > "$1"
|
||||||
|
|
||||||
|
# Fallback if generation failed or timed out
|
||||||
|
if [ ! -s "$1" ]; then
|
||||||
|
echo "chore: auto-generated commit message" > "$1"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
in {
|
in {
|
||||||
users.users.someone.packages = with pkgs; [
|
users.users.someone.packages = with pkgs; [
|
||||||
git
|
git
|
||||||
@@ -36,6 +69,7 @@ in {
|
|||||||
};
|
};
|
||||||
core = {
|
core = {
|
||||||
editor = "nvim";
|
editor = "nvim";
|
||||||
|
hooksPath = "${config.xdg.configHome}/git/hooks";
|
||||||
pager = "${pkgs.delta}/bin/delta";
|
pager = "${pkgs.delta}/bin/delta";
|
||||||
whitespace = "fix,-indent-with-non-tab,trailing-space,cr-at-eol";
|
whitespace = "fix,-indent-with-non-tab,trailing-space,cr-at-eol";
|
||||||
excludesFile = "${config.xdg.configHome}/${ignoreFile}";
|
excludesFile = "${config.xdg.configHome}/${ignoreFile}";
|
||||||
@@ -124,4 +158,6 @@ in {
|
|||||||
AGENTS.md
|
AGENTS.md
|
||||||
.sisyphus
|
.sisyphus
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
xdg.configFile."${hooksDir}/prepare-commit-msg".source = prepareCommitMsg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ in {
|
|||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
astro-language-server
|
astro-language-server
|
||||||
biome
|
biome
|
||||||
|
kotlin-language-server
|
||||||
marksman
|
marksman
|
||||||
nil
|
nil
|
||||||
tailwindcss-language-server
|
tailwindcss-language-server
|
||||||
@@ -59,6 +60,10 @@ in {
|
|||||||
command = [lspBins.nil];
|
command = [lspBins.nil];
|
||||||
extensions = ["nix"];
|
extensions = ["nix"];
|
||||||
};
|
};
|
||||||
|
kotlin = {
|
||||||
|
command = ["kotlin-language-server"];
|
||||||
|
extensions = ["kt" "kts"];
|
||||||
|
};
|
||||||
marksman = {
|
marksman = {
|
||||||
command = [lspBins.marksman];
|
command = [lspBins.marksman];
|
||||||
extensions = ["md"];
|
extensions = ["md"];
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
|
|
||||||
opencodeEnv = pkgs.buildEnv {
|
opencodeEnv = pkgs.buildEnv {
|
||||||
name = "opencode-env";
|
name = "opencode-env";
|
||||||
paths =
|
paths = languages.packages ++ skills.packages;
|
||||||
languages.packages
|
|
||||||
++ skills.packages;
|
|
||||||
};
|
};
|
||||||
opencodeInitScript = pkgs.writeShellScript "opencode-init" ''
|
opencodeInitScript = pkgs.writeShellScript "opencode-init" ''
|
||||||
mkdir -p "$HOME/.local/cache/opencode/node_modules/@opencode-ai"
|
mkdir -p "$HOME/.local/cache/opencode/node_modules/@opencode-ai"
|
||||||
@@ -31,82 +29,117 @@
|
|||||||
--prefix PATH : ${opencodeEnv}/bin \
|
--prefix PATH : ${opencodeEnv}/bin \
|
||||||
--prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath [pkgs.stdenv.cc.cc.lib]}"
|
--prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath [pkgs.stdenv.cc.cc.lib]}"
|
||||||
'';
|
'';
|
||||||
configFile = "opencode/config.json";
|
configFile = "opencode/config.json";
|
||||||
tuiFile = "opencode/tui.json";
|
tuiFile = "opencode/tui.json";
|
||||||
in {
|
toJSON = (pkgs.formats.json {}).generate;
|
||||||
users.users.someone.packages = [
|
in {
|
||||||
opencodeWrapped
|
users.users.someone.packages = [
|
||||||
];
|
opencodeWrapped
|
||||||
xdg.configFile = {
|
];
|
||||||
"${configFile}".text = builtins.toJSON {
|
xdg.configFile = {
|
||||||
"$schema" = "https://opencode.ai/config.json";
|
"${configFile}".source = toJSON "config.json" {
|
||||||
plugin = [
|
"$schema" = "https://opencode.ai/config.json";
|
||||||
|
plugin = [
|
||||||
"opencode-antigravity-auth@latest"
|
"opencode-antigravity-auth@latest"
|
||||||
"@tarquinen/opencode-dcp@latest"
|
"@tarquinen/opencode-dcp@latest"
|
||||||
"oh-my-openagent@latest"
|
"oh-my-openagent@latest"
|
||||||
];
|
];
|
||||||
small_model = "google/gemma-3n-e4b-it:free";
|
model = "google/antigravity-gemini-3-flash";
|
||||||
autoupdate = false;
|
small_model = "opencode/big-pickle";
|
||||||
share = "disabled";
|
autoupdate = false;
|
||||||
disabled_providers = [
|
agent = {
|
||||||
"amazon-bedrock"
|
build = {model = "opencode/nemotron-3-super-free";};
|
||||||
"anthropic"
|
plan = {model = "opencode/big-pickle";};
|
||||||
"azure-openai"
|
task = {model = "opencode/nemotron-3-super-free";};
|
||||||
"azure-cognitive-services"
|
"sisyphus-junior" = {model = "opencode/nemotron-3-super-free";};
|
||||||
"baseten"
|
explore = {model = "opencode/minimax-m2.5-free";};
|
||||||
"cerebras"
|
general = {model = "opencode/big-pickle";};
|
||||||
"cloudflare-ai-gateway"
|
oracle = {model = "opencode/nemotron-3-super-free";};
|
||||||
"cortecs"
|
"ultrabrain" = {model = "opencode/nemotron-3-super-free";};
|
||||||
"deep-infra"
|
"deep" = {model = "opencode/nemotron-3-super-free";};
|
||||||
"fireworks-ai"
|
"quick" = {model = "opencode/gpt-5-nano";};
|
||||||
"google-vertex-ai"
|
"artistry" = {model = "opencode/nemotron-3-super-free";};
|
||||||
"groq"
|
"visual-engineering" = {model = "opencode/hy3-preview-free";};
|
||||||
"hugging-face"
|
"writing" = {model = "opencode/hy3-preview-free";};
|
||||||
"helicone"
|
};
|
||||||
"llama.cpp"
|
share = "disabled";
|
||||||
"io-net"
|
disabled_providers = [
|
||||||
"lmstudio"
|
"amazon-bedrock"
|
||||||
"moonshot-ai"
|
"anthropic"
|
||||||
"nebius-token-factory"
|
"azure-openai"
|
||||||
"ollama"
|
"azure-cognitive-services"
|
||||||
"ollama-cloud"
|
"baseten"
|
||||||
"openai"
|
"cerebras"
|
||||||
"sap-ai-core"
|
"cloudflare-ai-gateway"
|
||||||
"ovhcloud-ai-endpoints"
|
"cortecs"
|
||||||
"together-ai"
|
"deep-infra"
|
||||||
"venice-ai"
|
"fireworks-ai"
|
||||||
"xai"
|
"google-vertex-ai"
|
||||||
"zai"
|
"groq"
|
||||||
"zenmux"
|
"hugging-face"
|
||||||
];
|
"helicone"
|
||||||
enabled_providers = ["opencode" "google" "openrouter"];
|
"llama.cpp"
|
||||||
mcp = {
|
"io-net"
|
||||||
gh_grep = {
|
"lmstudio"
|
||||||
type = "remote";
|
"moonshot-ai"
|
||||||
url = "https://mcp.grep.app/";
|
"nebius-token-factory"
|
||||||
enabled = true;
|
"ollama"
|
||||||
timeout = 10000;
|
"ollama-cloud"
|
||||||
};
|
"openai"
|
||||||
deepwiki = {
|
"sap-ai-core"
|
||||||
type = "remote";
|
"ovhcloud-ai-endpoints"
|
||||||
url = "https://mcp.deepwiki.com/mcp";
|
"together-ai"
|
||||||
enabled = true;
|
"venice-ai"
|
||||||
timeout = 10000;
|
"xai"
|
||||||
};
|
"zai"
|
||||||
context7 = {
|
"zenmux"
|
||||||
type = "remote";
|
];
|
||||||
url = "https://mcp.context7.com/mcp";
|
enabled_providers = ["opencode" "google" "openrouter" "deepseek" "nvidia" "alibaba" "mistral" "cloudflare-workers-ai"];
|
||||||
enabled = true;
|
mcp = {
|
||||||
timeout = 10000;
|
gh_grep = {
|
||||||
};
|
type = "remote";
|
||||||
};
|
url = "https://mcp.grep.app/";
|
||||||
inherit (languages) formatter lsp;
|
enabled = true;
|
||||||
provider = providers.config;
|
timeout = 10000;
|
||||||
};
|
};
|
||||||
"${tuiFile}".text = builtins.toJSON {
|
deepwiki = {
|
||||||
"$schema" = "https://opencode.ai/tui.json";
|
type = "remote";
|
||||||
theme = "gruvbox";
|
url = "https://mcp.deepwiki.com/mcp";
|
||||||
};
|
enabled = true;
|
||||||
"opencode/skill".source = skills.skillsSource + "/skill";
|
timeout = 10000;
|
||||||
};
|
};
|
||||||
}
|
context7 = {
|
||||||
|
type = "remote";
|
||||||
|
url = "https://mcp.context7.com/mcp";
|
||||||
|
enabled = true;
|
||||||
|
timeout = 10000;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
inherit (languages) formatter lsp;
|
||||||
|
provider = providers.config;
|
||||||
|
};
|
||||||
|
|
||||||
|
"opencode/oh-my-openagent.json".source = toJSON "oh-my-openagent.json" {
|
||||||
|
"$schema" = "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/master/assets/oh-my-opencode.schema.json";
|
||||||
|
agents = {
|
||||||
|
"sisyphus-junior" = {model = "opencode/nemotron-3-super-free";};
|
||||||
|
};
|
||||||
|
categories = {
|
||||||
|
"quick" = {model = "opencode/gpt-5-nano";};
|
||||||
|
"ultrabrain" = {model = "opencode/nemotron-3-super-free";};
|
||||||
|
"deep" = {model = "opencode/nemotron-3-super-free";};
|
||||||
|
"visual-engineering" = {model = "opencode/hy3-preview-free";};
|
||||||
|
"writing" = {model = "opencode/hy3-preview-free";};
|
||||||
|
"unspecified-low" = {model = "opencode/gpt-5-nano";};
|
||||||
|
"unspecified-high" = {model = "opencode/nemotron-3-super-free";};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
"${tuiFile}".source = toJSON "tui.json" {
|
||||||
|
"$schema" = "https://opencode.ai/tui.json";
|
||||||
|
theme = "gruvbox";
|
||||||
|
};
|
||||||
|
|
||||||
|
"opencode/skill".source = skills.skillsSource + "/skill";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,27 +1,47 @@
|
|||||||
{
|
{
|
||||||
config,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
|
config,
|
||||||
...
|
...
|
||||||
}: let
|
}: {
|
||||||
sshConfigFile = "ssh/config";
|
|
||||||
in {
|
|
||||||
users.users.someone.packages = with pkgs; [
|
users.users.someone.packages = with pkgs; [
|
||||||
openssh
|
openssh
|
||||||
];
|
];
|
||||||
|
|
||||||
xdg.configFile."ssh/config" = {
|
home.file.".ssh/config".text = ''
|
||||||
source = config.age.secrets.ssh_config.path;
|
AddKeysToAgent yes
|
||||||
};
|
CheckHostIP yes
|
||||||
|
Compression no
|
||||||
|
ControlMaster no
|
||||||
|
ControlPath ~/.ssh/master-%r@%n:%p
|
||||||
|
ControlPersist no
|
||||||
|
HashKnownHosts yes
|
||||||
|
IdentitiesOnly yes
|
||||||
|
PasswordAuthentication no
|
||||||
|
ForwardAgent no
|
||||||
|
ForwardX11 no
|
||||||
|
ForwardX11Trusted no
|
||||||
|
ServerAliveInterval 60
|
||||||
|
ServerAliveCountMax 3
|
||||||
|
UserKnownHostsFile ~/.ssh/known_hosts
|
||||||
|
|
||||||
home.file.".ssh/config" = {
|
Host git.severijnse.eu
|
||||||
source = config.xdg.configHome + "/ssh/config";
|
HostName git.severijnse.eu
|
||||||
mutable = false;
|
Port 222
|
||||||
};
|
User git
|
||||||
|
IdentityFile /run/secrets/gitea_laptop
|
||||||
|
|
||||||
environment.sessionVariables = {
|
Host hetzner
|
||||||
SSH_AUTH_SOCK = "${config.xdg.runtimeDir}/gnupg/S.gpg-agent.ssh";
|
HostName severijnse.eu
|
||||||
SSH_CONFIG = "${config.xdg.configHome}/ssh/config";
|
User admin
|
||||||
};
|
IdentityFile /run/secrets/hetzner_server
|
||||||
|
|
||||||
|
Host github.com
|
||||||
|
HostName github.com
|
||||||
|
User git
|
||||||
|
IdentityFile /run/secrets/github_laptop
|
||||||
|
|
||||||
|
Include ~/.ssh/config.d/*
|
||||||
|
'';
|
||||||
|
|
||||||
systemd.user.services.ssh-agent = {
|
systemd.user.services.ssh-agent = {
|
||||||
description = "SSH agent service";
|
description = "SSH agent service";
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
{pkgs, ...}: {
|
{pkgs, ...}: let
|
||||||
|
toJSON = (pkgs.formats.json {}).generate;
|
||||||
|
in {
|
||||||
users.users.someone.packages = with pkgs; [
|
users.users.someone.packages = with pkgs; [
|
||||||
television
|
television
|
||||||
nix-search-tv
|
nix-search-tv
|
||||||
];
|
];
|
||||||
|
|
||||||
xdg.configFile = {
|
xdg.configFile = {
|
||||||
"nix-search-tv/config.json".text = builtins.toJSON {
|
"nix-search-tv/config.json".source = toJSON "config.json" {
|
||||||
indexes = ["nixos" "nixpkgs" "nur"];
|
indexes = ["nixos" "nixpkgs" "nur"];
|
||||||
};
|
};
|
||||||
"fish/completions/tv.fish".source = "${pkgs.television}/share/fish/vendor_completions.d/tv.fish";
|
"fish/completions/tv.fish".source = "${pkgs.television}/share/fish/vendor_completions.d/tv.fish";
|
||||||
|
|||||||
@@ -1,39 +1,35 @@
|
|||||||
{
|
{pkgs, ...}: {
|
||||||
inputs,
|
users.users.someone.packages = with pkgs; [
|
||||||
pkgs,
|
# archives
|
||||||
...
|
zip
|
||||||
}: {
|
unzip
|
||||||
users.users.someone.packages = with pkgs;
|
unrar
|
||||||
[
|
ouch
|
||||||
# archives
|
|
||||||
zip
|
|
||||||
unzip
|
|
||||||
unrar
|
|
||||||
ouch
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
mako
|
mako
|
||||||
fontconfig
|
libnotify
|
||||||
|
fontconfig
|
||||||
|
|
||||||
# utils
|
# utils
|
||||||
dust
|
dust
|
||||||
duf
|
duf
|
||||||
fd
|
fd
|
||||||
file
|
file
|
||||||
killall
|
killall
|
||||||
jq
|
jq
|
||||||
ps_mem
|
ps_mem
|
||||||
inshellisense
|
inshellisense
|
||||||
|
|
||||||
fum
|
fum
|
||||||
gtt
|
gtt
|
||||||
meteor-git
|
meteor-git
|
||||||
nix-search-tv
|
nix-search-tv
|
||||||
scope-tui
|
scope-tui
|
||||||
tuicam
|
tuicam
|
||||||
wiremix
|
wiremix
|
||||||
zfxtop
|
zfxtop
|
||||||
opencode
|
opencode
|
||||||
loupe
|
loupe
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,27 @@
|
|||||||
XDG_PICTURES_DIR="$HOME/Pictures"
|
XDG_PICTURES_DIR="$HOME/Pictures"
|
||||||
XDG_VIDEOS_DIR="$HOME/Videos"
|
XDG_VIDEOS_DIR="$HOME/Videos"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
yaziWrapper = pkgs.writeShellScriptBin "yazi-wrapper" ''
|
||||||
|
set -e
|
||||||
|
|
||||||
|
multiple="$1"
|
||||||
|
directory="$2"
|
||||||
|
save="$3"
|
||||||
|
path="$4"
|
||||||
|
out="$5"
|
||||||
|
|
||||||
|
# We use alacritty directly to run yazi
|
||||||
|
# For saving (save=1), yazi is passed the directory and we write the final path output
|
||||||
|
# For directories (directory=1), we pass yazi the dir selection flag
|
||||||
|
if [ "$save" = "1" ]; then
|
||||||
|
exec ${pkgs.alacritty}/bin/alacritty -e ${pkgs.yazi}/bin/yazi --chooser-file="$out" "$path"
|
||||||
|
elif [ "$directory" = "1" ]; then
|
||||||
|
exec ${pkgs.alacritty}/bin/alacritty -e ${pkgs.yazi}/bin/yazi --chooser-file="$out" --cwd-file="$out"".1" "$path"
|
||||||
|
else
|
||||||
|
exec ${pkgs.alacritty}/bin/alacritty -e ${pkgs.yazi}/bin/yazi --chooser-file="$out" "$path"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
in {
|
in {
|
||||||
users.users.someone.packages = with pkgs; [
|
users.users.someone.packages = with pkgs; [
|
||||||
xdg-utils
|
xdg-utils
|
||||||
@@ -59,12 +80,21 @@ in {
|
|||||||
enable = true;
|
enable = true;
|
||||||
defaultApplications = associations;
|
defaultApplications = associations;
|
||||||
};
|
};
|
||||||
configFile."user-dirs.dirs".source = userDirsConfig;
|
configFile = {
|
||||||
configFile."mimeapps.list".text = ''
|
"xdg-desktop-portal-termfilechooser/config".text = ''
|
||||||
[Default Applications]
|
[filechooser]
|
||||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k}=${lib.concatStringsSep ";" v}") associations)}
|
cmd=${yaziWrapper}/bin/yazi-wrapper
|
||||||
[Added Associations]
|
default_dir=$HOME
|
||||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k}=${lib.concatStringsSep ";" v}") associations)}
|
open_mode=suggested
|
||||||
'';
|
save_mode=suggested
|
||||||
|
'';
|
||||||
|
"user-dirs.dirs".source = userDirsConfig;
|
||||||
|
"mimeapps.list".text = ''
|
||||||
|
[Default Applications]
|
||||||
|
${lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k}=${lib.concatStringsSep ";" v}") associations)}
|
||||||
|
[Added Associations]
|
||||||
|
${lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k}=${lib.concatStringsSep ";" v}") associations)}
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -159,7 +159,7 @@ in {
|
|||||||
${lib.concatStringsSep "\n" (
|
${lib.concatStringsSep "\n" (
|
||||||
lib.flatten (
|
lib.flatten (
|
||||||
lib.mapAttrsToList (
|
lib.mapAttrsToList (
|
||||||
user: userCfg:
|
_user: userCfg:
|
||||||
(lib.mapAttrsToList (mkLinkScript cfg.configHome) userCfg.configFiles)
|
(lib.mapAttrsToList (mkLinkScript cfg.configHome) userCfg.configFiles)
|
||||||
++ (lib.mapAttrsToList (mkLinkScript cfg.cacheHome) userCfg.cacheFiles)
|
++ (lib.mapAttrsToList (mkLinkScript cfg.cacheHome) userCfg.cacheFiles)
|
||||||
++ (lib.mapAttrsToList (mkLinkScript cfg.dataHome) userCfg.dataFiles)
|
++ (lib.mapAttrsToList (mkLinkScript cfg.dataHome) userCfg.dataFiles)
|
||||||
|
|||||||
+51
-48
@@ -1,9 +1,8 @@
|
|||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
inputs,
|
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
self,
|
inputs,
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
imports = [
|
imports = [
|
||||||
@@ -144,6 +143,10 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Workaround: aa-remove-unknown references missing rc.apparmor.functions in apparmor-parser 5.0.0
|
||||||
|
# Reload via restart instead (stop → start, reloads all profiles)
|
||||||
|
systemd.services.apparmor.reloadIfChanged = lib.mkForce false;
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
# for SSD/NVME
|
# for SSD/NVME
|
||||||
fstrim.enable = true;
|
fstrim.enable = true;
|
||||||
@@ -157,52 +160,52 @@
|
|||||||
|
|
||||||
# Additional systemd hardening
|
# Additional systemd hardening
|
||||||
systemd = {
|
systemd = {
|
||||||
coredump.extraConfig = ''
|
coredump.settings = {
|
||||||
Storage=none
|
Coredump.Storage = "none";
|
||||||
ProcessSizeMax=0
|
Coredump.ProcessSizeMax = 0;
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.systemPackages = [pkgs.cryptsetup pkgs.age inputs.agenix.packages.${pkgs.stdenv.hostPlatform.system}.default];
|
|
||||||
|
|
||||||
age.identityPaths = ["/etc/ssh/ssh_host_ed25519_key"];
|
|
||||||
|
|
||||||
age.secrets = {
|
|
||||||
gitea_laptop = {
|
|
||||||
file = ../../secrets/gitea_laptop.age;
|
|
||||||
mode = "600";
|
|
||||||
owner = "someone";
|
|
||||||
group = "users";
|
|
||||||
};
|
|
||||||
hetzner_server = {
|
|
||||||
file = ../../secrets/hetzner_server.age;
|
|
||||||
mode = "600";
|
|
||||||
owner = "someone";
|
|
||||||
group = "users";
|
|
||||||
};
|
|
||||||
aur = {
|
|
||||||
file = ../../secrets/aur.age;
|
|
||||||
mode = "600";
|
|
||||||
owner = "someone";
|
|
||||||
group = "users";
|
|
||||||
};
|
|
||||||
ssh_config = {
|
|
||||||
file = ../../secrets/ssh_config.age;
|
|
||||||
mode = "600";
|
|
||||||
owner = "someone";
|
|
||||||
group = "users";
|
|
||||||
};
|
|
||||||
github_key = {
|
|
||||||
file = ../../secrets/github_key.age;
|
|
||||||
mode = "600";
|
|
||||||
owner = "someone";
|
|
||||||
group = "users";
|
|
||||||
};
|
|
||||||
sporestack_server = {
|
|
||||||
file = ../../secrets/sporestack_server.age;
|
|
||||||
mode = "600";
|
|
||||||
owner = "someone";
|
|
||||||
group = "users";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
boot.loader.limine.bootMode = lib.mkDefault "bios";
|
||||||
|
|
||||||
|
# sops-nix configuration — uses SSH host keys automatically, no age key file needed
|
||||||
|
sops = {
|
||||||
|
defaultSopsFile = ./../../secrets/secrets.yaml;
|
||||||
|
secrets = {
|
||||||
|
gitea_laptop = {
|
||||||
|
owner = "someone";
|
||||||
|
group = "users";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
github_laptop = {
|
||||||
|
owner = "someone";
|
||||||
|
group = "users";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
hetzner_server = {
|
||||||
|
owner = "someone";
|
||||||
|
group = "users";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
nixpkgs.config.allowUnfree = true;
|
||||||
|
|
||||||
|
nixpkgs.overlays = [
|
||||||
|
(_self: super: {
|
||||||
|
cisco-secure-client = super.callPackage ../../pkgs/cisco-secure-client {};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
security.pam.services.rustlock.text = "auth include login";
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
cryptsetup
|
||||||
|
age
|
||||||
|
nixd
|
||||||
|
apparmor-parser
|
||||||
|
xdg-desktop-portal-wlr
|
||||||
|
inputs.rustlock.packages.${pkgs.stdenv.hostPlatform.system}.default
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{lib, ...}: {
|
||||||
|
disko.devices = {
|
||||||
|
disk.main = {
|
||||||
|
device = lib.mkDefault "/dev/nvme0n1";
|
||||||
|
type = "disk";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
esp = {
|
||||||
|
name = "ESP";
|
||||||
|
size = "512M";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot/efi";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
root = {
|
||||||
|
name = "root";
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
+72
-3
@@ -5,6 +5,9 @@
|
|||||||
}: let
|
}: let
|
||||||
# shorten paths
|
# shorten paths
|
||||||
inherit (inputs.nixpkgs.lib) nixosSystem;
|
inherit (inputs.nixpkgs.lib) nixosSystem;
|
||||||
|
# Server uses its own pinned 24.05 nixpkgs (kept isolated from the laptop's unstable)
|
||||||
|
nixosSystem24 = inputs.nixos-24-05.lib.nixosSystem;
|
||||||
|
unstablePkgs = import inputs.nixpkgs-unstable {system = "x86_64-linux";};
|
||||||
mod = "${self}/system";
|
mod = "${self}/system";
|
||||||
home = "${self}/home";
|
home = "${self}/home";
|
||||||
|
|
||||||
@@ -14,13 +17,14 @@
|
|||||||
# get these into the module system
|
# get these into the module system
|
||||||
specialArgs = {inherit inputs self;};
|
specialArgs = {inherit inputs self;};
|
||||||
|
|
||||||
# shared modules for all configurations
|
# shared modules for all configurations (personal + work)
|
||||||
sharedModules = [
|
sharedModules = [
|
||||||
./aesthetic
|
./aesthetic
|
||||||
"${mod}/services/gnome-services.nix"
|
"${mod}/services/gnome-services.nix"
|
||||||
"${mod}/core/limine.nix"
|
"${mod}/core/limine.nix"
|
||||||
"${home}"
|
"${home}"
|
||||||
inputs.agenix.nixosModules.default
|
"${self}/work"
|
||||||
|
inputs.sops-nix.nixosModules.sops
|
||||||
];
|
];
|
||||||
in {
|
in {
|
||||||
flake.nixosConfigurations = {
|
flake.nixosConfigurations = {
|
||||||
@@ -39,7 +43,72 @@ in {
|
|||||||
# Laptop: laptop with GUI + battery + bluetooth
|
# Laptop: laptop with GUI + battery + bluetooth
|
||||||
laptop = nixosSystem {
|
laptop = nixosSystem {
|
||||||
inherit specialArgs;
|
inherit specialArgs;
|
||||||
modules = laptop ++ sharedModules;
|
modules =
|
||||||
|
laptop
|
||||||
|
++ sharedModules
|
||||||
|
++ [
|
||||||
|
"${mod}/services/location.nix"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Laptop UEFI: explicit UEFI boot mode
|
||||||
|
laptop-uefi = nixosSystem {
|
||||||
|
inherit specialArgs;
|
||||||
|
modules =
|
||||||
|
laptop
|
||||||
|
++ sharedModules
|
||||||
|
++ [
|
||||||
|
"${mod}/services/location.nix"
|
||||||
|
({...}: {
|
||||||
|
boot.loader.limine.bootMode = "uefi";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Laptop BIOS: explicit BIOS boot mode
|
||||||
|
laptop-bios = nixosSystem {
|
||||||
|
inherit specialArgs;
|
||||||
|
modules =
|
||||||
|
laptop
|
||||||
|
++ sharedModules
|
||||||
|
++ [
|
||||||
|
"${mod}/services/location.nix"
|
||||||
|
({...}: {
|
||||||
|
boot.loader.limine.bootMode = "bios";
|
||||||
|
boot.loader.limine.biosDevice = "/dev/nvme0n1";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Laptop work: laptop profile + work modules (himmelblau, cisco, mdatp)
|
||||||
|
laptop-work = nixosSystem {
|
||||||
|
inherit specialArgs;
|
||||||
|
modules =
|
||||||
|
laptop
|
||||||
|
++ sharedModules
|
||||||
|
++ [
|
||||||
|
"${mod}/services/location.nix"
|
||||||
|
{
|
||||||
|
work.cisco.enable = true;
|
||||||
|
work.himmelblau.enable = true;
|
||||||
|
work.mdatp.enable = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Server: severijnse.eu (Hetzner) — fully isolated under servers/hetzner/.
|
||||||
|
# Uses nixos-24.05 + disko + sops-nix and does NOT inherit the laptop's shared modules.
|
||||||
|
hetzner = nixosSystem24 {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
specialArgs = {
|
||||||
|
inherit inputs self unstablePkgs;
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
inputs.disko.nixosModules.disko
|
||||||
|
inputs.sops-nix.nixosModules.sops
|
||||||
|
"${self}/servers/hetzner/hosts/hetzner/hardware-configuration.nix"
|
||||||
|
"${self}/servers/hetzner/hosts/hetzner/default.nix"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -3487,4 +3487,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
|
||||||
{
|
|
||||||
nixpkgs.overlays = [
|
|
||||||
(final: prev: {
|
|
||||||
bitwig-studio = prev.bitwig-studio.overrideAttrs (oldAttrs: {
|
|
||||||
version = "6.0";
|
|
||||||
src = pkgs.fetchurl {
|
|
||||||
url = "https://www.bitwig.com/dl/Bitwig%20Studio/6.0/installer_linux/";
|
|
||||||
hash = "8eb09381ac5f79685f2b02de28b9aa4d0592ed155b5671ea2742270a2a669bc9";
|
|
||||||
};
|
|
||||||
# Inherit the original package's meta information
|
|
||||||
meta = oldAttrs.meta;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
fetchurl,
|
||||||
|
dpkg,
|
||||||
|
makeWrapper,
|
||||||
|
patchelf,
|
||||||
|
coreutils,
|
||||||
|
systemd,
|
||||||
|
glib,
|
||||||
|
zlib,
|
||||||
|
xz,
|
||||||
|
curl,
|
||||||
|
libxml2,
|
||||||
|
}: let
|
||||||
|
version = "5.1.17.3394";
|
||||||
|
# libxml2's "out" output has lib/libxml2.so.16
|
||||||
|
libxml2_out = libxml2.out;
|
||||||
|
|
||||||
|
# System library RPATH for all bundled ELF binaries (cisco's own lib dir is
|
||||||
|
# appended during fixupPhase via $out)
|
||||||
|
sysRpath = lib.makeLibraryPath [
|
||||||
|
systemd
|
||||||
|
glib
|
||||||
|
zlib
|
||||||
|
xz
|
||||||
|
stdenv.cc.cc.lib
|
||||||
|
curl
|
||||||
|
libxml2_out
|
||||||
|
];
|
||||||
|
in
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
pname = "cisco-secure-client";
|
||||||
|
inherit version;
|
||||||
|
|
||||||
|
src = fetchurl {
|
||||||
|
url = "https://archive.org/download/cisco-secure-client-linux64-${version}/cisco-secure-client-linux64-${version}-predeploy-deb-k9.tgz";
|
||||||
|
sha256 = "5c4cafb4694e64cbf041481f5df3d70389399926f8aa2a469d480c0555b58c2c";
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = [dpkg makeWrapper patchelf];
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
systemd
|
||||||
|
glib
|
||||||
|
zlib
|
||||||
|
xz
|
||||||
|
stdenv.cc.cc.lib
|
||||||
|
curl
|
||||||
|
libxml2_out
|
||||||
|
];
|
||||||
|
|
||||||
|
dontStrip = true;
|
||||||
|
dontAutoPatchelf = true;
|
||||||
|
|
||||||
|
unpackPhase = ''
|
||||||
|
tar xzf "$src"
|
||||||
|
DEB_FILE=$(ls cisco-secure-client-vpn-cli_*_amd64.deb 2>/dev/null || true)
|
||||||
|
if [ -z "$DEB_FILE" ]; then
|
||||||
|
DEB_FILE=$(ls cisco-secure-client-vpn_*_amd64.deb 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
if [ -z "$DEB_FILE" ]; then
|
||||||
|
echo "ERROR: No .deb file found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
dpkg-deb -x "$DEB_FILE" .
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
mkdir -p "$out"
|
||||||
|
cp -r opt/* "$out/"
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
# fixupPhase: patchelf corrupts Cisco's embedded code signatures on ALL
|
||||||
|
# shipped ELF binaries and .so plugins. We skip patchelf entirely and
|
||||||
|
# instead rely on LD_LIBRARY_PATH wrappers for system library resolution.
|
||||||
|
# The binaries' original RPATH of /opt/cisco/secureclient/lib resolves
|
||||||
|
# correctly at runtime via the /opt/cisco/secureclient -> store symlink.
|
||||||
|
fixupPhase = ''
|
||||||
|
runHook preFixup
|
||||||
|
|
||||||
|
# Fix absolute symlinks — the deb assumes install under /opt/cisco/secureclient/
|
||||||
|
# but nix puts it in the store. Convert to relative symlinks.
|
||||||
|
for link in $(find "$out" -type l); do
|
||||||
|
target=$(readlink "$link")
|
||||||
|
if echo "$target" | grep -q "^/opt/"; then
|
||||||
|
rel=$(basename "$target")
|
||||||
|
ln -sf "$rel" "$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for f in "$out"/cisco/secureclient/bin/* "$out"/cisco/secureclient/lib/*.so*; do
|
||||||
|
chmod +x "$f" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Create libxml2.so.2 symlink (SONAME mismatch: Cisco wants .2, nixpkgs provides .16)
|
||||||
|
libxml2_so=$(find ${libxml2_out}/lib -name "libxml2.so.16*" 2>/dev/null | head -1)
|
||||||
|
if [ -n "$libxml2_so" ]; then
|
||||||
|
ln -sf "$libxml2_so" "$out/cisco/secureclient/lib/libxml2.so.2"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# NO patchelf on Cisco ELFs — their embedded code signatures are
|
||||||
|
# verified at runtime (especially plugins loaded by vpnagentd).
|
||||||
|
# All ELFs keep their original RPATH /opt/cisco/secureclient/lib
|
||||||
|
# which resolves via the tmpfiles symlink.
|
||||||
|
# System libs are provided via LD_LIBRARY_PATH in wrappers below.
|
||||||
|
|
||||||
|
ldPath="$out/cisco/secureclient/lib:${sysRpath}"
|
||||||
|
|
||||||
|
# Wrap vpn CLI — wrapProgram renames the original to .vpn-wrapped
|
||||||
|
wrapProgram "$out/cisco/secureclient/bin/vpn" \
|
||||||
|
--prefix LD_LIBRARY_PATH : "$ldPath" \
|
||||||
|
--prefix PATH : ${lib.makeBinPath [coreutils]}
|
||||||
|
|
||||||
|
# Create $out/bin/ wrappers for all user-facing executables
|
||||||
|
mkdir -p "$out/bin"
|
||||||
|
|
||||||
|
makeWrapper "$out/cisco/secureclient/bin/vpn" "$out/bin/vpn" \
|
||||||
|
--prefix LD_LIBRARY_PATH : "$ldPath" \
|
||||||
|
--prefix PATH : ${lib.makeBinPath [coreutils]}
|
||||||
|
|
||||||
|
makeWrapper "$out/cisco/secureclient/bin/vpnagentd" "$out/bin/vpnagentd" \
|
||||||
|
--prefix LD_LIBRARY_PATH : "$ldPath" \
|
||||||
|
--prefix PATH : ${lib.makeBinPath [coreutils]}
|
||||||
|
|
||||||
|
for cli in acinstallhelper manifesttool_vpn vpndownloader-cli; do
|
||||||
|
if [ -f "$out/cisco/secureclient/bin/$cli" ]; then
|
||||||
|
makeWrapper "$out/cisco/secureclient/bin/$cli" "$out/bin/$cli" \
|
||||||
|
--prefix LD_LIBRARY_PATH : "$ldPath"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
runHook postFixup
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Cisco Secure Client (AnyConnect successor) VPN client";
|
||||||
|
homepage = "https://www.cisco.com/site/us/en/products/security/secure-client/index.html";
|
||||||
|
sourceProvenance = with sourceTypes; [binaryNativeCode];
|
||||||
|
license = {
|
||||||
|
name = "Cisco Secure Client EULA — proprietary, not redistributable";
|
||||||
|
url = "https://www.cisco.com/c/en/us/products/security/secure-client/eula.html";
|
||||||
|
};
|
||||||
|
platforms = ["x86_64-linux"];
|
||||||
|
maintainers = [];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/nix/store/p2h0fr7k47yrx3x0qkr3rwsp5nf5bj30-nixos-system-debian-4gb-fsn1-1-24.05.20241230.b134951
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
BRIGHTNESS=$(brightnessctl -m | cut -d, -f4 | tr -d '%')
|
||||||
|
|
||||||
|
notify-send -h string:x-canonical-private-synchronous:brightness \
|
||||||
|
-h "int:value:$BRIGHTNESS" \
|
||||||
|
-c "osd" \
|
||||||
|
"Brightness: ${BRIGHTNESS}%" -i display-brightness
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
VOLUME=$(wpctl get-volume @DEFAULT_AUDIO_SINK@ | awk '{print $2 * 100}')
|
||||||
|
MUTE=$(wpctl get-volume @DEFAULT_AUDIO_SINK@ | grep -c "MUTED")
|
||||||
|
|
||||||
|
if [ "$MUTE" -ne 0 ]; then
|
||||||
|
notify-send -h string:x-canonical-private-synchronous:volume \
|
||||||
|
-h "int:value:0" \
|
||||||
|
-c "osd" \
|
||||||
|
"Muted" -i audio-volume-muted
|
||||||
|
else
|
||||||
|
notify-send -h string:x-canonical-private-synchronous:volume \
|
||||||
|
-h "int:value:$VOLUME" \
|
||||||
|
-c "osd" \
|
||||||
|
"Volume: ${VOLUME}%" -i audio-volume-high
|
||||||
|
fi
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +0,0 @@
|
|||||||
age-encryption.org/v1
|
|
||||||
-> ssh-ed25519 o3KszQ O4xOtTDK/DH+r81x55jFRFtPAFpTpvCiUcUBy/bm320
|
|
||||||
K8kjoDS2ODYJdNZCUmhTfQOWFHEFCkb90d9K5fEnxYo
|
|
||||||
--- j1a/Uo7QXWC0dnbSGKaRj/T9GkChC7BQf6kYC0HN8f4
|
|
||||||
êßçé}N
|
|
||||||
,âôxoT#c„d¸b,¥Á›�o¶wšah@Z˜&àWU]S6T �þôá”ó¾þº)Yõg
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
let
|
|
||||||
host = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICL0B9moxOPY4XYim8826M+Tf0sWErwWljz1gkDZPwuF";
|
|
||||||
in {
|
|
||||||
"gitea_laptop.age".publicKeys = [
|
|
||||||
host
|
|
||||||
];
|
|
||||||
"hetzner_server.age".publicKeys = [
|
|
||||||
host
|
|
||||||
];
|
|
||||||
"aur.age".publicKeys = [
|
|
||||||
host
|
|
||||||
];
|
|
||||||
"ssh_config.age".publicKeys = [
|
|
||||||
host
|
|
||||||
];
|
|
||||||
"github_key.age".publicKeys = [
|
|
||||||
host
|
|
||||||
];
|
|
||||||
"sporestack_server.age".publicKeys = [
|
|
||||||
host
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
gitea_laptop: ENC[AES256_GCM,data:nfiUKIe/+4CuHcv9oBNRFL+gGx2XEvjZlllOVhQYKd00rlffCh1k2BHChdXYziaPVo7SvFkZUX1OLLYqtKC90qMocfSq+6j7hJiTO7RH+DUaAzUEY1bANd6mwh2gl63Xcq3Z65VFRnYvXigJS3QZGykeXNancXjs3TK5pjJZDXgXef7gKzs7yLlnr41Ntk2eiOv08334aa046Qjr6kDjtGd/uUkTzuetMPfGadr/I0VBf0S/SjwSRSzM12CTa7vIz+AbY4pOrZYV+s0bHtuVfE0Xv9WqjC2rA9rqhzrcVfzdbhIEIpADoyhezImcZTzUFQFgaPRoH6ko8GhZKUXPRAmLeMvuzHy1y4kXjsfSixMM2doEfh2lKJTAf1DtAt4OrzXLYt+n5gxCuXdcIMHGP/wfbfmC+Lb1iPZ1ywzICBmBXugNw1KoIMnnr/DjRdqjNQiGQtEd8ylu3VpK7a5OHb0/kW6YQCvtPy5xgYn19LYJUIp8xb4bA2Ty2Bi0FbnfDQbtejUK1bfgz14SQXK6,iv:O+8iWBbXSiyagkfRjzYDxMukBZHfCgxxsEx6C1OBjjQ=,tag:KyzATKtgjutfDmHjR5Gq9g==,type:str]
|
||||||
|
github_laptop: ENC[AES256_GCM,data:sIiuxSVQyxNIM1BDXx/Wjtdjl7CS9Hdq8tQu9Qdu9fcoDVwNpuidflhb7K7jXmynLJDcAgqSJIBuVngU6mf7nA/k/EmWc8T/+Pl8RUqR/Z85F0Bby/PEULf8pljxiqbolm9fIe9ayThxFJux6tq+cfXiN/omHWxJuMNWgroX6xoxmBla/TDI8VdO1z60wogAJDd0XWp766tFX11iXMuTJhFUKO4Ah3CCHTuIvmXhfMrqdykrulbRgkhoOl/ufNZnJA0cMeg3hjb/yZU29TNaKHXZyeIbP2B/dvEJjNgBQnVoqEL3K4ZZOp4Bo52pdw+wLQ9bQY1hN/XYwi5qNC6G5A0i9r3tmYGeR+6lHIWGaQ0LbFiGmnLQGIeYOU0b7CweJFqJzBDkmfPnglnsRNJtVb7UgBJYGFcLPS0XfVYUVjN0OTqUugeTJSUCAMnWnBlsIgWbrclwe9ilNRPELf7bqXC+Lfcl5vgH1JJRB1Q8m//v+2j764m04Oo4uVZRHqkhA0Jdq/58E2NQpBSM1UNY,iv:cFyGuGa9dWITfJY5rvYe6WIZDouU1ZQ1Lj5x9mU/9wc=,tag:dH5XDlbi4LE8uyAVDMT6dw==,type:str]
|
||||||
|
hetzner_server: ENC[AES256_GCM,data:YW3eiBJ+4hqBY3BVIcoJicSRz2L+9iZzwVdhpRl/BARtwkkPEohRUUIXY15HqS/DqhcKaojh1YqlUzFh5Z+FKTnizhPNHwWN+Dnz4d9dNTyIdzRkKOp31uYTJi2v2uwoYc60y4wkhHO5dAvWKdHz2qdvXCS9uspfaVpc2k/HasnBmpUODZSfUnLq2bInifDf+lPuJqTVAH0QXo93Nx6IqbgOA0WLfdi6xC3PemzwtqL6dhXUPehF3N4aULfbph/JHnxqQ3LqvDnr8wQsqBfePpFfxpod0rtYnYOwQ/TDEErLIG8d7Idvlzgmr8WFBpgYQuoMyYuDr9PYenpNnbeIF3JesCocsBEw4jpoQT7K1Pd0iVTuvp/41X7gPmYrPviVbWx9X99FeHzUDxhOcNSc/QZ4UtPoLMprlmPK2XQPbQBJyy9KXGNMBW/jERE7Et0cmlx/TVA7tN8T6/Y2vrBlgtmE3m6Fn7ZhtiNFZs4GMbK9KGtmgxwN75yJML3x77Wsleco6nrBcYEghf3LoviwQVjw9KKgwSVLJzW+,iv:rhp4eqmAzmQOAtPy9+ATqth5IKKzk3evJyVw5UtDrSc=,tag:TP/2plD04GU9zKfpesLqeA==,type:str]
|
||||||
|
sops:
|
||||||
|
age:
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYVGxBMmhqdDBmRjlSY2Jp
|
||||||
|
TVlFKzc3dnM4YUwzZm1iTXY0Q1ROazZQTWc4CjZQZzRIeFYxaGlrSmZTVEFNb21T
|
||||||
|
M2xGN1ozaEdOak5kQTRYRENXZFpNRmcKLS0tIFhCNHVWejFpeGtCL1cwNi9lU0pq
|
||||||
|
dzJLbGRzZTlnZVAra2x0SXAvclcrelkKZdnhAPvrqixSbUNfn1Tj7ZYaeCUxfZnf
|
||||||
|
T9KxEukEKRXMPfCANiQvlG3bM6pPM7L2l8TjTzWOSN/M31cgbiLjyg==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1vtlutzzhgsy4ef8jd97jaay30zwygj68jwgn0zx64m2d5u57sqzsngy7lf
|
||||||
|
lastmodified: "2026-07-12T19:19:12Z"
|
||||||
|
mac: ENC[AES256_GCM,data:iF2a+wQXiWw4vqOedlZyWda3x66lonwh6WFcB5DdZyXbZzxq8Nvz4POGVYS4ZDwDlWrDhS9RTCX/UyclrHxBslu+rkSUqnOITOABWlnwFPTjGRz4EVMk1MGpRHXL9c8N2O1SS0LnkL8O6+9qsIROkNQVi4nyo4zIYTEfcdxDx+w=,iv:qPf6pl8d4egPitSCheccfiek+WPJVx33cZe81HggBio=,tag:4zXI+g6+vPo0YWOoyud8vw==,type:str]
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.13.2
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,11 @@
|
|||||||
|
keys:
|
||||||
|
- &admin age1yd59qp5km4cxt99rlfjehnsucrjn9lmj0su4h3avhf6vrtjvnyjqstldl5
|
||||||
|
- &local age15rhqdpwejyf3r6ww70qgv6hqmkpsqraakn26kc49wlauhaceaeqsmuwrdd
|
||||||
|
- &server age1xekdrkjfu82hkxltydm72tllzgayyvfavvudeks3xjuujm5wt5hq6g55v0
|
||||||
|
creation_rules:
|
||||||
|
- path_regex: secrets/.*\.yaml
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *local
|
||||||
|
- *server
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Deploy NixOS to a Hetzner VPS using nixos-anywhere
|
||||||
|
# Usage: ./deploy.sh [user@host]
|
||||||
|
#
|
||||||
|
# IMPORTANT: Run this from a SEPARATE machine with Nix installed
|
||||||
|
# (laptop, another VPS, etc.), NOT from the target VPS itself.
|
||||||
|
# nixos-anywhere uses kexec to reboot the target; you can't reboot
|
||||||
|
# the machine you're running on.
|
||||||
|
#
|
||||||
|
# Steps:
|
||||||
|
# 1. git clone <this-repo> && cd nixos-config
|
||||||
|
# 2. Add your SSH pubkey to ./hosts/hetzner/users.nix
|
||||||
|
# 3. ./deploy.sh root@<vps-ip>
|
||||||
|
#
|
||||||
|
# This wipes the target disk and installs NixOS fresh.
|
||||||
|
|
||||||
|
HOST=${1:-root@localhost}
|
||||||
|
|
||||||
|
echo "=== Deploying NixOS to $HOST ==="
|
||||||
|
echo "WARNING: This will WIPE the target disk!"
|
||||||
|
read -rp "Continue? [y/N] " confirm
|
||||||
|
[[ "$confirm" =~ ^[Yy]$ ]] || exit 1
|
||||||
|
|
||||||
|
nix run github:nix-community/nixos-anywhere -- \
|
||||||
|
--generate-hardware-config nixos-generate-config ./hosts/hetzner/hardware-configuration.nix \
|
||||||
|
--flake ".#hetzner" "$HOST"
|
||||||
|
|
||||||
|
# After deploy, the generated hw config MUST be imported to flake.nix
|
||||||
|
echo ""
|
||||||
|
echo "=== CRITICAL: Post-deploy steps! ==="
|
||||||
|
echo "1. Import the generated hw config into flake.nix:"
|
||||||
|
echo " git add hosts/hetzner/hardware-configuration.nix"
|
||||||
|
echo " # In flake.nix, add to modules array:"
|
||||||
|
echo " ./hosts/hetzner/hardware-configuration.nix"
|
||||||
|
echo ""
|
||||||
|
echo "2. Copy the age key to the new system for sops-nix:"
|
||||||
|
echo " # From the deploy machine:"
|
||||||
|
echo " ssh root@<new-ip> 'mkdir -p /etc/age'"
|
||||||
|
echo " scp /home/admin/age/keys.txt root@<new-ip>:/etc/age/keys.txt"
|
||||||
|
echo ""
|
||||||
|
echo "3. IMPORTANT: The old gitea_db Docker Postgres needs migration."
|
||||||
|
echo " The pg_dump is at /tmp/gitea-db-dump-YYYY-MM-DD.sql.gz"
|
||||||
|
echo " Restore with:"
|
||||||
|
echo " sudo -u postgres psql -c \"CREATE DATABASE gitea;\""
|
||||||
|
echo " sudo -u postgres psql -c \"CREATE USER gitea WITH PASSWORD 'gitea';\""
|
||||||
|
echo " sudo -u postgres psql -c \"GRANT ALL PRIVILEGES ON DATABASE gitea TO gitea;\""
|
||||||
|
echo " zcat /tmp/gitea-db-dump-YYYY-MM-DD.sql.gz | sudo -u postgres psql gitea"
|
||||||
|
echo ""
|
||||||
|
echo "4. Restore Caddy certs for the mailserver:"
|
||||||
|
echo " # The old Debian Caddy certs were backed up at /home/admin/backups/caddy-certs-mail.tar.gz"
|
||||||
|
echo " # On NixOS Caddy stores at /var/lib/caddy/certificates/..."
|
||||||
|
echo ""
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{...}: {
|
||||||
|
imports = [
|
||||||
|
./networking.nix
|
||||||
|
./users.nix
|
||||||
|
./hardware.nix
|
||||||
|
./disko-config.nix
|
||||||
|
../../modules/system/env.nix
|
||||||
|
../../modules/security/acme.nix
|
||||||
|
../../modules/services/caddy.nix
|
||||||
|
../../modules/services/coredns.nix
|
||||||
|
../../modules/services/gitea.nix
|
||||||
|
../../modules/services/wireguard.nix
|
||||||
|
../../modules/services/fail2ban.nix
|
||||||
|
../../modules/services/vaultwarden.nix
|
||||||
|
../../modules/services/rustdesk.nix
|
||||||
|
../../modules/services/wrxproxy.nix
|
||||||
|
../../modules/services/watchtower.nix
|
||||||
|
../../modules/services/tlsa-updater.nix
|
||||||
|
../../modules/services/shkeeper.nix
|
||||||
|
../../modules/services/backup.nix
|
||||||
|
../../modules/services/stalwart.nix
|
||||||
|
../../modules/services/virtualcam.nix
|
||||||
|
../../modules/services/netdata.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
# Only 4GB RAM — limit nix builds to one core at a time to avoid OOM
|
||||||
|
nix.settings = {
|
||||||
|
cores = 1;
|
||||||
|
max-jobs = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
system.stateVersion = "24.05";
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{lib, ...}: {
|
||||||
|
disko.devices = {
|
||||||
|
disk.disk1 = {
|
||||||
|
device = lib.mkDefault "/dev/sda";
|
||||||
|
type = "disk";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
boot = {
|
||||||
|
name = "boot";
|
||||||
|
size = "1M";
|
||||||
|
type = "EF02";
|
||||||
|
};
|
||||||
|
esp = {
|
||||||
|
name = "ESP";
|
||||||
|
size = "256M";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot/efi";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
root = {
|
||||||
|
name = "root";
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Zone file for severijnse.eu
|
||||||
|
# This is the authoritative DNS zone served by CoreDNS
|
||||||
|
# Update serial number on changes
|
||||||
|
{
|
||||||
|
serial = "2026071004";
|
||||||
|
adminEmail = "abuse.severijnse.eu";
|
||||||
|
nameservers = ["ns1.severijnse.eu" "ns2.severijnse.eu"];
|
||||||
|
ipv4 = "49.13.92.205";
|
||||||
|
ipv6 = "2a01:4f8:c014:2585::1";
|
||||||
|
mailIpv4 = "49.13.92.205";
|
||||||
|
mailIpv6 = "2a01:4f8:c014:2585::1";
|
||||||
|
dkimRecord = ''
|
||||||
|
v=DKIM1; k=rsa;
|
||||||
|
p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAskpG7m4kninxRE4JF5KzpnBhLbOlGJL2RO/iDfzdz6sHEvxe78q9c5UnZ0OQddOSuQo4q48dJkXR/XzqY7Ak109lhoAx+Kr1neYsi8/
|
||||||
|
JaoTC8OURk365+/aBSmWXUCCBphCx43QWfC9h8GMQ6PUIqawkz5CcGPT7X7hPdwHQcd5Vn3CqmABptxdwshdkBjZs
|
||||||
|
oi79BOo9ZrQSTY7iiLcOP7hVVC9Ad+ydlZ4MWGfy5BxgyTGrrtuSuLcM219oqdovIvr2EtXs8AMx5fyXplKE3R/
|
||||||
|
YlwF2Jcy50Gmb5y/E9pOaFjVv8HXUmKsvuhA2b8K+rt0WVHNc3dvbgZUl8bGAQIDAQAB
|
||||||
|
'';
|
||||||
|
dnssecKeyTag = "20930";
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||||
|
# and may be overwritten by future invocations. Please make changes
|
||||||
|
# to /etc/nixos/configuration.nix instead.
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
modulesPath,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
imports = [
|
||||||
|
(modulesPath + "/profiles/qemu-guest.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = ["ahci" "xhci_pci" "virtio_pci" "virtio_scsi" "sd_mod" "sr_mod"];
|
||||||
|
boot.initrd.kernelModules = [];
|
||||||
|
boot.kernelModules = [];
|
||||||
|
boot.extraModulePackages = [];
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{...}: {
|
||||||
|
boot = {
|
||||||
|
loader = {
|
||||||
|
grub = {
|
||||||
|
enable = true;
|
||||||
|
device = "/dev/sda";
|
||||||
|
efiSupport = true;
|
||||||
|
efiInstallAsRemovable = true;
|
||||||
|
};
|
||||||
|
efi = {
|
||||||
|
efiSysMountPoint = "/boot/efi";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
initrd.availableKernelModules = ["ata_piix" "virtio_pci" "virtio_scsi" "virtio_blk"];
|
||||||
|
};
|
||||||
|
|
||||||
|
swapDevices = [
|
||||||
|
{
|
||||||
|
device = "/swap";
|
||||||
|
size = 4096;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
# Enable Podman OCI container support
|
||||||
|
virtualisation = {
|
||||||
|
podman = {
|
||||||
|
enable = true;
|
||||||
|
dockerCompat = true;
|
||||||
|
defaultNetwork.settings = {
|
||||||
|
dns_enabled = true;
|
||||||
|
ipv6_enabled = true;
|
||||||
|
subnets = [
|
||||||
|
{
|
||||||
|
subnet = "10.88.0.0/16";
|
||||||
|
gateway = "10.88.0.1";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
subnet = "fd00:dead:beef::/48";
|
||||||
|
gateway = "fd00:dead:beef::1";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
oci-containers.backend = "podman";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
{...}: {
|
||||||
|
networking = {
|
||||||
|
hostName = "debian-4gb-fsn1-1";
|
||||||
|
domain = "severijnse.eu";
|
||||||
|
useDHCP = true;
|
||||||
|
dhcpcd.enable = true;
|
||||||
|
nameservers = [
|
||||||
|
"2a01:4ff:ff00::add:2"
|
||||||
|
"2a01:4ff:ff00::add:1"
|
||||||
|
"185.12.64.2"
|
||||||
|
];
|
||||||
|
search = ["severijnse.eu"];
|
||||||
|
interfaces."enp1s0" = {
|
||||||
|
ipv6.addresses = [
|
||||||
|
{
|
||||||
|
address = "2a01:4f8:c014:2585::1";
|
||||||
|
prefixLength = 64;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
ipv6.routes = [
|
||||||
|
{
|
||||||
|
address = "::";
|
||||||
|
prefixLength = 0;
|
||||||
|
via = "fe80::1";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
firewall = {
|
||||||
|
enable = true;
|
||||||
|
allowedTCPPorts = [
|
||||||
|
22 # SSH
|
||||||
|
53 # DNS (CoreDNS)
|
||||||
|
80 # HTTP (Caddy)
|
||||||
|
443 # HTTPS (Caddy)
|
||||||
|
465 # SMTP over TLS
|
||||||
|
587 # SMTP (STARTTLS)
|
||||||
|
993 # IMAP over TLS
|
||||||
|
143 # IMAP
|
||||||
|
25 # SMTP
|
||||||
|
222 # Gitea SSH
|
||||||
|
51821 # WG-Easy web UI
|
||||||
|
];
|
||||||
|
allowedUDPPorts = [
|
||||||
|
53 # DNS
|
||||||
|
51820 # WireGuard
|
||||||
|
];
|
||||||
|
};
|
||||||
|
nat = {
|
||||||
|
enable = true;
|
||||||
|
externalInterface = "enp1s0";
|
||||||
|
internalInterfaces = ["wg0"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
boot.kernel.sysctl = {
|
||||||
|
"net.ipv4.ip_forward" = 1;
|
||||||
|
"net.ipv4.conf.all.src_valid_mark" = 1;
|
||||||
|
"net.ipv6.conf.all.forwarding" = 1;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{pkgs, ...}: {
|
||||||
|
users.users = {
|
||||||
|
root = {
|
||||||
|
openssh.authorizedKeys.keys = [
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGYboWHsP50CXzMpz4FogC3ecFchjZARCdY4wSTc/Et0 someone@archlinux"
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKAlpdPnqihMxE6XPBUQ9Kb6MrvWUWw/BYx6kf2kTPD6 admin@severijnse.eu"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
admin = {
|
||||||
|
isNormalUser = true;
|
||||||
|
uid = 1000;
|
||||||
|
description = "Jory";
|
||||||
|
extraGroups = ["wheel" "docker" "podman"];
|
||||||
|
openssh.authorizedKeys.keys = [
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGYboWHsP50CXzMpz4FogC3ecFchjZARCdY4wSTc/Et0 someone@archlinux"
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKAlpdPnqihMxE6XPBUQ9Kb6MrvWUWw/BYx6kf2kTPD6 admin@severijnse.eu"
|
||||||
|
];
|
||||||
|
shell = pkgs.fish;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
PermitRootLogin = "prohibit-password";
|
||||||
|
PasswordAuthentication = false;
|
||||||
|
PubkeyAuthentication = true;
|
||||||
|
};
|
||||||
|
hostKeys = [
|
||||||
|
{
|
||||||
|
path = "/etc/ssh/ssh_host_ed25519_key";
|
||||||
|
type = "ed25519";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{...}: {
|
||||||
|
# NOTE: Caddy handles all TLS natively via its ACME integration.
|
||||||
|
# This module is kept as a fallback for non-Caddy services.
|
||||||
|
# Currently NOT imported in default.nix — uncomment there to activate.
|
||||||
|
security.acme = {
|
||||||
|
acceptTerms = true;
|
||||||
|
defaults = {
|
||||||
|
email = "jory@severijnse.eu";
|
||||||
|
group = "caddy";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
# sops-encrypted secrets (single file holds all service secrets).
|
||||||
|
secretsFile = ../../secrets/secrets.yaml;
|
||||||
|
# Root-only runtime files restic reads from (0600 root).
|
||||||
|
runtimeDir = "/var/lib/restic";
|
||||||
|
passwordFile = "/var/lib/restic/.password";
|
||||||
|
environmentFile = "/var/lib/restic/environment";
|
||||||
|
# Backblaze B2 backend, per restic docs: b2:bucketname.
|
||||||
|
repo = "b2:hetzner-severijnse";
|
||||||
|
|
||||||
|
# Materialize the restic password and B2 credentials from sops into
|
||||||
|
# root-only files, so secrets are never world-readable in the Nix store.
|
||||||
|
writeSecrets = pkgs.writeShellScript "restic-write-secrets" ''
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p ${runtimeDir}
|
||||||
|
${pkgs.sops}/bin/sops \
|
||||||
|
--decrypt --extract '["restic_password"]' \
|
||||||
|
--input-type yaml --output-type yaml ${secretsFile} \
|
||||||
|
| tr -d '\n' > "${passwordFile}"
|
||||||
|
chmod 0600 "${passwordFile}"
|
||||||
|
|
||||||
|
: > "${environmentFile}"
|
||||||
|
chmod 0600 "${environmentFile}"
|
||||||
|
${pkgs.sops}/bin/sops --decrypt --input-type yaml --output-type yaml ${secretsFile} |
|
||||||
|
${pkgs.gnused}/bin/sed -nE \
|
||||||
|
's/^b2_key_id: (.*)/B2_ACCOUNT_ID=\1/p; s/^b2_application_key: (.*)/B2_ACCOUNT_KEY=\1/p' \
|
||||||
|
>> "${environmentFile}"
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
systemd.services.restic-password = {
|
||||||
|
description = "Materialize restic repository password and B2 credentials from sops";
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
# The age key lives in /etc/age/keys.txt; the service must know where it is
|
||||||
|
# and needs a HOME for age to report its user config directory.
|
||||||
|
environment.SOPS_AGE_KEY_FILE = "/etc/age/keys.txt";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
Environment = ["HOME=/root"];
|
||||||
|
ExecStart = "${writeSecrets}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# B2 credentials are supplied via environmentFile (B2_ACCOUNT_ID / B2_ACCOUNT_KEY),
|
||||||
|
# matching the official module example which combines `repository` and `environmentFile`.
|
||||||
|
services.restic.backups.localbackup = {
|
||||||
|
repository = "b2:hetzner-severijnse";
|
||||||
|
environmentFile = environmentFile;
|
||||||
|
passwordFile = passwordFile;
|
||||||
|
initialize = true;
|
||||||
|
paths = [
|
||||||
|
"/home/admin"
|
||||||
|
"/var/lib/postgresql"
|
||||||
|
"/var/lib/gitea"
|
||||||
|
"/var/lib/caddy"
|
||||||
|
"/var/lib/virtualcam"
|
||||||
|
"/var/lib/stalwart"
|
||||||
|
"/var/lib/bulwark"
|
||||||
|
"/var/lib/coredns"
|
||||||
|
"/etc/nixos"
|
||||||
|
];
|
||||||
|
exclude = [
|
||||||
|
"/home/admin/backups"
|
||||||
|
"/home/admin/.opencode"
|
||||||
|
"/home/admin/.local"
|
||||||
|
"/home/admin/.npm"
|
||||||
|
"/home/admin/.config"
|
||||||
|
"*.log"
|
||||||
|
"*.log.*"
|
||||||
|
"**/.cache"
|
||||||
|
];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "Mon *-*-* 03:00:00";
|
||||||
|
Persistent = true;
|
||||||
|
RandomizedDelaySec = "15m";
|
||||||
|
};
|
||||||
|
pruneOpts = [
|
||||||
|
"--keep-daily 7"
|
||||||
|
"--keep-weekly 4"
|
||||||
|
"--keep-monthly 6"
|
||||||
|
];
|
||||||
|
runCheck = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# The backup must never run before the secrets exist.
|
||||||
|
systemd.services."restic-backups-localbackup" = {
|
||||||
|
requires = ["restic-password.service"];
|
||||||
|
after = ["restic-password.service"];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
{unstablePkgs, ...}: let
|
||||||
|
antiScrape = ''
|
||||||
|
@bad_bot {
|
||||||
|
header_regexp User-Agent "(?i)(scrapy|cpython-requests|python-requests|curl|wget|go-http-client|ltx71|petalbot|bytespider|dotbot|ahrefsbot|semrushbot|mj12bot|dataforseo|facebookexternalhit|claudebot|anthropic-ai|perplexity|gptbot|chatgpt-user|omnisci|imgproxy|ccbot|exabot|360spider|baiduspider|sogou|duckduckgo|amazonbot|cohere-ai|diffbot|imagesiftbot).*"
|
||||||
|
}
|
||||||
|
respond @bad_bot "" 444
|
||||||
|
header {
|
||||||
|
X-Robots-Tag "noindex, nofollow, noai, noimageai"
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
services.caddy = {
|
||||||
|
enable = true;
|
||||||
|
group = "caddy";
|
||||||
|
package = unstablePkgs.caddy;
|
||||||
|
dataDir = "/var/lib/caddy";
|
||||||
|
logDir = "/var/log/caddy";
|
||||||
|
globalConfig = ''
|
||||||
|
email jory@severijnse.eu
|
||||||
|
servers {
|
||||||
|
trusted_proxies static private_ranges
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
# Global Caddyfile snippets (shared across all virtual hosts).
|
||||||
|
extraConfig = ''
|
||||||
|
(admin_gate) {
|
||||||
|
@notvpn not remote_ip 127.0.0.1 ::1 10.8.0.0/24 fd10:8::/64 49.13.92.205 2a01:4f8:c014:2585::1
|
||||||
|
respond @notvpn "Forbidden" 403
|
||||||
|
}
|
||||||
|
# Restrict access to the server itself (loopback + public IPs) or the
|
||||||
|
# WireGuard VPN. Gitea reaches this from the server when publishing a
|
||||||
|
# release, and VPN clients can download from it; the public cannot.
|
||||||
|
(release_gate) {
|
||||||
|
@deny not remote_ip 127.0.0.1 ::1 10.8.0.0/24 fd10:8::/64 49.13.92.205 2a01:4f8:c014:2585::1
|
||||||
|
respond @deny "Forbidden" 403
|
||||||
|
}
|
||||||
|
# Security headers applied to every response of every site that
|
||||||
|
# imports this snippet (covers all current and future hosts).
|
||||||
|
# For proxied hosts whose upstream sets its own copies, strip them
|
||||||
|
# with `header_down` inside the reverse_proxy block instead.
|
||||||
|
(security_headers) {
|
||||||
|
header {
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-XSS-Protection "0"
|
||||||
|
X-Frame-Options "SAMEORIGIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(csp) {
|
||||||
|
header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; connect-src 'self' wss: ws:"
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
virtualHosts = {
|
||||||
|
"severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
${antiScrape}
|
||||||
|
@logo path /logo.svg
|
||||||
|
handle @logo {
|
||||||
|
root * /srv
|
||||||
|
file_server
|
||||||
|
header Content-Type image/svg+xml
|
||||||
|
header Cache-Control "public, immutable, max-age=31536000"
|
||||||
|
header X-Content-Type-Options nosniff
|
||||||
|
}
|
||||||
|
handle {
|
||||||
|
redir https://jory.severijnse.eu{uri} permanent
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"www.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
${antiScrape}
|
||||||
|
redir https://jory.severijnse.eu{uri} permanent
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"jory.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
${antiScrape}
|
||||||
|
root * /srv/jory
|
||||||
|
file_server
|
||||||
|
try_files {path} /index.html
|
||||||
|
encode zstd gzip
|
||||||
|
header Strict-Transport-Security "max-age=31536000;"
|
||||||
|
@robots path /robots.txt
|
||||||
|
handle @robots {
|
||||||
|
header Content-Type text/plain
|
||||||
|
respond `User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Googlebot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Bingbot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Twitterbot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: facebookexternalhit
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: GPTBot
|
||||||
|
User-agent: ChatGPT-User
|
||||||
|
User-agent: OAI-SearchBot
|
||||||
|
User-agent: ClaudeBot
|
||||||
|
User-agent: Claude-Web
|
||||||
|
User-agent: anthropic-ai
|
||||||
|
User-agent: PerplexityBot
|
||||||
|
User-agent: Bytespider
|
||||||
|
User-agent: Amazonbot
|
||||||
|
User-agent: CCBot
|
||||||
|
User-agent: Google-Extended
|
||||||
|
User-agent: Applebot-Extended
|
||||||
|
Disallow: /
|
||||||
|
` 200
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"mta-sts.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
root * /srv
|
||||||
|
file_server
|
||||||
|
header Content-Type text/plain
|
||||||
|
header Cache-Control "public, max-age=300"
|
||||||
|
header X-Content-Type-Options nosniff
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"vault.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
${antiScrape}
|
||||||
|
header Strict-Transport-Security "max-age=31536000;"
|
||||||
|
reverse_proxy 127.0.0.1:1001
|
||||||
|
encode zstd gzip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"git.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
${antiScrape}
|
||||||
|
reverse_proxy 127.0.0.1:3000
|
||||||
|
encode zstd gzip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"mail.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
${antiScrape}
|
||||||
|
# Stalwart JMAP API (http-management listener on 8080): required so the
|
||||||
|
# browser can reach /.well-known/jmap, /jmap/session and the JMAP
|
||||||
|
# upload/download/eventsource/ws paths at the public origin.
|
||||||
|
handle /.well-known/jmap {
|
||||||
|
reverse_proxy 127.0.0.1:8080
|
||||||
|
}
|
||||||
|
# NOTE: use handle (not handle_path) so the /jmap prefix is preserved.
|
||||||
|
handle /jmap {
|
||||||
|
reverse_proxy 127.0.0.1:8080
|
||||||
|
}
|
||||||
|
handle /jmap/* {
|
||||||
|
reverse_proxy 127.0.0.1:8080
|
||||||
|
}
|
||||||
|
handle {
|
||||||
|
# Bulwark webmail (JMAP client for Stalwart), running on host port 3002.
|
||||||
|
reverse_proxy 127.0.0.1:3002
|
||||||
|
}
|
||||||
|
encode zstd gzip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"admin.mail.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
${antiScrape}
|
||||||
|
# Stalwart webadmin UI (served by the http-management listener on 8080).
|
||||||
|
reverse_proxy 127.0.0.1:8080
|
||||||
|
encode zstd gzip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"vpn.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
reverse_proxy 127.0.0.1:51821
|
||||||
|
encode zstd gzip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"mine.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
${antiScrape}
|
||||||
|
reverse_proxy 127.0.0.1:81
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"music.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
${antiScrape}
|
||||||
|
reverse_proxy 127.0.0.1:4321
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"fail2ban.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
${antiScrape}
|
||||||
|
import admin_gate
|
||||||
|
reverse_proxy 127.0.0.1:8080
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"automate.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
${antiScrape}
|
||||||
|
import admin_gate
|
||||||
|
reverse_proxy 127.0.0.1:5678
|
||||||
|
encode zstd gzip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"pay.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
${antiScrape}
|
||||||
|
reverse_proxy 127.0.0.1:5000
|
||||||
|
encode zstd gzip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"virtualcam.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
${antiScrape}
|
||||||
|
basic_auth {
|
||||||
|
chan $2a$14$7ZeNpGT0L68uZwzdWBcF0OulzhrYbfAs232Ojt//LHQ1qvXU4x32O
|
||||||
|
}
|
||||||
|
reverse_proxy 127.0.0.1:3001
|
||||||
|
encode zstd gzip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"api.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
# Machine-facing license API for the Android app: no antiScrape
|
||||||
|
# bot-blocking (it would drop legitimate non-browser clients) and no
|
||||||
|
# admin_gate (endpoints are protected by the bearer token).
|
||||||
|
reverse_proxy 127.0.0.1:3004
|
||||||
|
encode zstd gzip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"releases.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
root * /srv/releases
|
||||||
|
file_server browse
|
||||||
|
encode zstd gzip
|
||||||
|
|
||||||
|
handle /private* {
|
||||||
|
import release_gate
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"http://ip.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
header Content-Type text/plain
|
||||||
|
respond {client_ip} 200
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /srv 0755 caddy caddy -"
|
||||||
|
"d /srv/jory 0755 caddy caddy -"
|
||||||
|
"d /srv/releases 0755 admin caddy -"
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
{pkgs, ...}: let
|
||||||
|
zoneFile = pkgs.writeText "severijnse.eu.db" ''
|
||||||
|
$ORIGIN severijnse.eu.
|
||||||
|
$TTL 3600
|
||||||
|
severijnse.eu. 3600 IN SOA ns1.severijnse.eu. abuse.severijnse.eu. 2026071004 3600 1800 1209600 86400
|
||||||
|
IN NS ns1.severijnse.eu.
|
||||||
|
IN NS ns2.severijnse.eu.
|
||||||
|
|
||||||
|
@ IN A 49.13.92.205
|
||||||
|
www IN A 49.13.92.205
|
||||||
|
ns1 IN A 49.13.92.205
|
||||||
|
ns2 IN A 49.13.92.205
|
||||||
|
mail IN A 49.13.92.205
|
||||||
|
admin.mail IN A 49.13.92.205
|
||||||
|
|
||||||
|
@ IN AAAA 2a01:4f8:c014:2585::1
|
||||||
|
www IN AAAA 2a01:4f8:c014:2585::1
|
||||||
|
ns1 IN AAAA 2a01:4f8:c014:2585::1
|
||||||
|
ns2 IN AAAA 2a01:4f8:c014:2585::1
|
||||||
|
mail IN AAAA 2a01:4f8:c014:2585::1
|
||||||
|
admin.mail IN AAAA 2a01:4f8:c014:2585::1
|
||||||
|
|
||||||
|
*.severijnse.eu. IN A 49.13.92.205
|
||||||
|
*.severijnse.eu. IN AAAA 2a01:4f8:c014:2585::1
|
||||||
|
|
||||||
|
@ IN MX 10 mail.severijnse.eu.
|
||||||
|
|
||||||
|
mail._domainkey.severijnse.eu. 300 IN TXT (
|
||||||
|
"v=DKIM1; k=rsa; "
|
||||||
|
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAskpG7m4kninxRE4JF5KzpnBhLbOlGJL2RO/iDfzdz6sHEvxe78q9c5UnZ0OQddOSuQo4q48dJkXR/XzqY7Ak109lhoAx+Kr1neYsi8K/"
|
||||||
|
"JaoTC8OURk365+/aBSmWXUCCBphCx43QWfC9h8GMQ6PUIqawkz5CcGPT7X7hPdwHQcd5Vn3CqmABptxdwshdkBjZs"
|
||||||
|
"oi79BOo9ZrQSTY7iiLcOP7hVVC9Ad+ydlZ4MWGfy5BxgyTGrrtuSuLcM219oqdovIvr2EtXs8AMx5fyXplKE3R/"
|
||||||
|
"YlwF2Jcy50Gmb5y/E9pOaFjVv8HXUmKsvuhA2b8K+rt0WVHNc3dvbgZUl8bGAQIDAQAB"
|
||||||
|
)
|
||||||
|
_dmarc.severijnse.eu. IN TXT "v=DMARC1; p=reject; rua=mailto:abuse@severijnse.eu"
|
||||||
|
severijnse.eu. IN TXT "v=spf1 mx ip4:49.13.92.205 -all"
|
||||||
|
|
||||||
|
default._bimi.severijnse.eu. IN TXT "v=BIMI1; l=https://severijnse.eu/logo.svg; avp=personal;"
|
||||||
|
|
||||||
|
severijnse.eu. IN CAA 0 issue "letsencrypt.org"
|
||||||
|
severijnse.eu. IN CAA 0 issuewild "letsencrypt.org"
|
||||||
|
severijnse.eu. IN CAA 0 iodef "mailto:abuse@severijnse.eu"
|
||||||
|
|
||||||
|
_smtp._tls.severijnse.eu. IN TXT "v=TLSRPTv1; rua=mailto:abuse@severijnse.eu"
|
||||||
|
_mta-sts.severijnse.eu. IN TXT "v=STSv1; id=2024120501"
|
||||||
|
|
||||||
|
severijnse.eu. 300 IN TXT "google-site-verification=H0HHB7zNQ10uom1zH5f8CEtHcVVcWiuu41ZQv348T5U"
|
||||||
|
|
||||||
|
; TLSA records updated dynamically by mail-cert-sync service.
|
||||||
|
; The placeholders below mirror the current live certificate so the zone is
|
||||||
|
; correct even if the sync service has not yet run (e.g. a failed boot).
|
||||||
|
_25._tcp.mail.severijnse.eu. 3600 IN TLSA 3 1 1 15ec4d8823874c8363af004188338f76e6e6c5878faf25cdf40556b44b26677d
|
||||||
|
_465._tcp.mail.severijnse.eu. 3600 IN TLSA 3 1 1 15ec4d8823874c8363af004188338f76e6e6c5878faf25cdf40556b44b26677d
|
||||||
|
_993._tcp.mail.severijnse.eu. 3600 IN TLSA 3 1 1 15ec4d8823874c8363af004188338f76e6e6c5878faf25cdf40556b44b26677d
|
||||||
|
|
||||||
|
severijnse.eu. IN DS 20930 13 2 B0D9B13DCE5FA0D41589239EB5166D124B0C9D3060A538726DDCCBFC91E8DBD8
|
||||||
|
|
||||||
|
@ IN DNSKEY 257 3 13 kOc88RGHKdWa7YLjNs7mljux7cT9/9wVNgcp+8jaVe6zle4cHAk6+Sub9wKEVa7Q4FlQYzS1KFL2HOW05Azq1A==
|
||||||
|
@ IN DNSKEY 256 3 13 QTFplRlXes5NExKLvuCJmX0l8FhulFNS+sJw+pvcAq4+0T3cCzT6hmcJPjd4BVl6KvvyS78Vij2f7HjrcjyHfw==
|
||||||
|
@ IN CDS 20930 13 2 B0D9B13DCE5FA0D41589239EB5166D124B0C9D3060A538726DDCCBFC91E8DBD8
|
||||||
|
@ IN CDNSKEY 257 3 13 kOc88RGHKdWa7YLjNs7mljux7cT9/9wVNgcp+8jaVe6zle4cHAk6+Sub9wKEVa7Q4FlQYzS1KFL2HOW05Azq1A==
|
||||||
|
|
||||||
|
severijnse.eu IN SSHFP 1 1 7f5235b2997e0c621fe773acaaae45b9551049ce
|
||||||
|
severijnse.eu IN SSHFP 1 2 2ec1d37df094915253c0b059762052ae19e66cd97e82d7a58ac1a0d1bd5d5e52
|
||||||
|
severijnse.eu IN SSHFP 3 1 240418eb7cc2a3969257c30363d16103dbfd5822
|
||||||
|
severijnse.eu IN SSHFP 3 2 43dbfdd8b131efda4c98844e1434e4f35e773f472bdb355ede94f8a9f7001c35
|
||||||
|
severijnse.eu IN SSHFP 4 1 efbd7836208684d110f3a72543eba74ad8e54b44
|
||||||
|
severijnse.eu IN SSHFP 4 2 c02f4b13aef78579f3466851fcc741c0169eaa63237463ceacc9bb72a2be0519
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
systemd = {
|
||||||
|
services = {
|
||||||
|
decrypt-coredns-keys = {
|
||||||
|
description = "Decrypt CoreDNS DNSSEC signing keys";
|
||||||
|
before = ["coredns.service"];
|
||||||
|
wantedBy = ["coredns.service"];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
Environment = "SOPS_AGE_KEY_FILE=/etc/age/keys.txt";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
SOPS_FILE=${../../secrets/coredns-keys.yaml}
|
||||||
|
KEYS_DIR=/var/lib/coredns/keys
|
||||||
|
mkdir -p "$KEYS_DIR"
|
||||||
|
|
||||||
|
${pkgs.sops}/bin/sops --decrypt --extract '["coredns_ksk_key"]' "$SOPS_FILE" > "$KEYS_DIR/Kseverijnse.eu.+013+20930.key"
|
||||||
|
${pkgs.sops}/bin/sops --decrypt --extract '["coredns_ksk_private"]' "$SOPS_FILE" > "$KEYS_DIR/Kseverijnse.eu.+013+20930.private"
|
||||||
|
${pkgs.sops}/bin/sops --decrypt --extract '["coredns_zsk_key"]' "$SOPS_FILE" > "$KEYS_DIR/Kseverijnse.eu.+013+38678.key"
|
||||||
|
${pkgs.sops}/bin/sops --decrypt --extract '["coredns_zsk_private"]' "$SOPS_FILE" > "$KEYS_DIR/Kseverijnse.eu.+013+38678.private"
|
||||||
|
|
||||||
|
chmod 644 "$KEYS_DIR/"*
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
coredns = {
|
||||||
|
after = ["decrypt-coredns-keys.service" "network-online.target"];
|
||||||
|
wants = ["network-online.target"];
|
||||||
|
requires = ["decrypt-coredns-keys.service"];
|
||||||
|
|
||||||
|
# Copy zone file from Nix store to writable location on service start
|
||||||
|
# TLSA updater will modify the writable copy at runtime
|
||||||
|
preStart = ''
|
||||||
|
cp -f ${zoneFile} /var/lib/coredns/zones/severijnse.eu.db
|
||||||
|
chown coredns:coredns /var/lib/coredns/zones/severijnse.eu.db
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
tmpfiles.rules = [
|
||||||
|
"d /var/lib/coredns 0750 coredns coredns -"
|
||||||
|
"d /var/lib/coredns/zones 0750 coredns coredns -"
|
||||||
|
"d /var/lib/coredns/keys 0750 coredns coredns -"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
services.coredns = {
|
||||||
|
enable = true;
|
||||||
|
config = ''
|
||||||
|
severijnse.eu {
|
||||||
|
bind 127.0.0.1 49.13.92.205 2a01:4f8:c014:2585::1
|
||||||
|
file /var/lib/coredns/zones/severijnse.eu.db {
|
||||||
|
reload 300s
|
||||||
|
}
|
||||||
|
dnssec {
|
||||||
|
key file /var/lib/coredns/keys/Kseverijnse.eu.+013+38678
|
||||||
|
key file /var/lib/coredns/keys/Kseverijnse.eu.+013+20930
|
||||||
|
}
|
||||||
|
log
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
|
||||||
|
. {
|
||||||
|
bind 127.0.0.1
|
||||||
|
forward . 1.1.1.1 8.8.8.8
|
||||||
|
log
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{...}: {
|
||||||
|
# Keep fail2ban as OCI container to preserve the web UI
|
||||||
|
virtualisation.oci-containers.containers.fail2ban = {
|
||||||
|
image = "crazymax/fail2ban:latest";
|
||||||
|
autoStart = true;
|
||||||
|
volumes = [
|
||||||
|
"/home/admin/fail2ban/data:/data:Z"
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
TZ = "Europe/Amsterdam";
|
||||||
|
};
|
||||||
|
extraOptions = [
|
||||||
|
"--network=host"
|
||||||
|
"--cap-add=NET_ADMIN"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
unstablePkgs,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
services = {
|
||||||
|
postgresql = {
|
||||||
|
enable = true;
|
||||||
|
package = pkgs.postgresql_14;
|
||||||
|
ensureDatabases = ["gitea"];
|
||||||
|
ensureUsers = [
|
||||||
|
{
|
||||||
|
name = "gitea";
|
||||||
|
ensureDBOwnership = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
gitea = {
|
||||||
|
enable = true;
|
||||||
|
package = unstablePkgs.gitea;
|
||||||
|
database = {
|
||||||
|
type = "postgres";
|
||||||
|
name = "gitea";
|
||||||
|
user = "gitea";
|
||||||
|
};
|
||||||
|
appName = "Jory's Git";
|
||||||
|
lfs.enable = true;
|
||||||
|
settings = {
|
||||||
|
server = {
|
||||||
|
DOMAIN = "git.severijnse.eu";
|
||||||
|
ROOT_URL = "https://git.severijnse.eu/";
|
||||||
|
HTTP_PORT = 3000;
|
||||||
|
SSH_PORT = 222;
|
||||||
|
SSH_LISTEN_PORT = 2222;
|
||||||
|
START_SSH_SERVER = true;
|
||||||
|
SSH_USER = "git";
|
||||||
|
BUILTIN_SSH_SERVER_USER = "git";
|
||||||
|
LANDING_PAGE = "explore";
|
||||||
|
};
|
||||||
|
service = {
|
||||||
|
DISABLE_REGISTRATION = true;
|
||||||
|
REQUIRE_SIGNIN_VIEW = false;
|
||||||
|
};
|
||||||
|
repository = {
|
||||||
|
DEFAULT_BRANCH = "main";
|
||||||
|
};
|
||||||
|
actions = {
|
||||||
|
ENABLED = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
gitea-actions-runner = {
|
||||||
|
# nixos-24-05's gitea-actions-runner module hardcodes bin/act_runner,
|
||||||
|
# but the current upstream package (1.0.3, matching gitea 1.26) ships
|
||||||
|
# bin/gitea-runner. Wrap it so both names resolve.
|
||||||
|
package = pkgs.runCommand "gitea-actions-runner-wrapped" {} ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
ln -s ${unstablePkgs.gitea-actions-runner}/bin/gitea-runner $out/bin/act_runner
|
||||||
|
'';
|
||||||
|
instances.default = {
|
||||||
|
enable = true;
|
||||||
|
name = "hetzner";
|
||||||
|
url = "https://git.severijnse.eu";
|
||||||
|
tokenFile = "/var/lib/secrets/gitea-runner-token";
|
||||||
|
labels = ["native:host"];
|
||||||
|
hostPackages = with pkgs; [
|
||||||
|
bash
|
||||||
|
coreutils
|
||||||
|
curl
|
||||||
|
gawk
|
||||||
|
gitMinimal
|
||||||
|
gnused
|
||||||
|
nodejs
|
||||||
|
wget
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Gitea connects to local Postgres via Unix socket (peer auth).
|
||||||
|
# No password needed — the socket is at /run/postgresql by default.
|
||||||
|
# createDatabase = true ensures the DB + user are set up automatically.
|
||||||
|
|
||||||
|
# Gitea built-in SSH server: listens on high port 2222 (no privileged-cap needed),
|
||||||
|
# while clone URLs advertise port 222. Firewall redirects 222 -> 2222.
|
||||||
|
networking.firewall.allowedTCPPorts = [222 2222];
|
||||||
|
|
||||||
|
# Redirect external git SSH (222) to Gitea's internal listener (2222)
|
||||||
|
networking.firewall.extraCommands = ''
|
||||||
|
${pkgs.nftables}/bin/nft add table inet gitea-redirect 2>/dev/null || true
|
||||||
|
${pkgs.nftables}/bin/nft flush chain inet gitea-redirect prerouting 2>/dev/null || true
|
||||||
|
${pkgs.nftables}/bin/nft add chain inet gitea-redirect prerouting '{ type nat hook prerouting priority dstnat; }' 2>/dev/null || true
|
||||||
|
${pkgs.nftables}/bin/nft add rule inet gitea-redirect prerouting tcp dport 222 redirect to :2222 2>/dev/null || true
|
||||||
|
'';
|
||||||
|
|
||||||
|
# --- Gitea Actions self-hosted CI runner ---
|
||||||
|
# The native runner only exposes `hostPackages` on PATH (see the list above),
|
||||||
|
# which intentionally omits Nix. CI steps export the host's system Nix
|
||||||
|
# (/run/current-system/sw/bin, i.e. Lix) onto PATH rather than installing a
|
||||||
|
# second Nix client, so the running Lix daemon is used directly.
|
||||||
|
# aarch64 builds run under QEMU user-emulation via boot.binfmt below.
|
||||||
|
boot.binfmt.emulatedSystems = ["aarch64-linux"];
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{...}: {
|
||||||
|
virtualisation.oci-containers.containers.mailserver = {
|
||||||
|
image = "ghcr.io/docker-mailserver/docker-mailserver:latest";
|
||||||
|
autoStart = true;
|
||||||
|
hostname = "mail.severijnse.eu";
|
||||||
|
ports = [
|
||||||
|
"25:25"
|
||||||
|
"[::]:25:25"
|
||||||
|
"143:143"
|
||||||
|
"[::]:143:143"
|
||||||
|
"465:465"
|
||||||
|
"[::]:465:465"
|
||||||
|
"587:587"
|
||||||
|
"[::]:587:587"
|
||||||
|
"993:993"
|
||||||
|
"[::]:993:993"
|
||||||
|
];
|
||||||
|
volumes = [
|
||||||
|
"/home/admin/dms/mail-data/:/var/mail/:Z"
|
||||||
|
"/home/admin/dms/mail-state/:/var/mail-state/:Z"
|
||||||
|
"/home/admin/dms/mail-logs/:/var/log/mail/:Z"
|
||||||
|
"/home/admin/dms/config/:/tmp/docker-mailserver/:Z"
|
||||||
|
"/etc/localtime:/etc/localtime:ro"
|
||||||
|
# On the running Debian, Caddy v2 stores certs at /home/admin/caddy/data/caddy/certificates/...
|
||||||
|
# On NixOS, Caddy stores certs at /var/lib/caddy/certificates/...
|
||||||
|
"/var/lib/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.severijnse.eu/:/etc/letsencrypt/live/mail.severijnse.eu/:ro"
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
OVERRIDE_HOSTNAME = "";
|
||||||
|
LOG_LEVEL = "info";
|
||||||
|
SPOOF_PROTECTION = "1";
|
||||||
|
MOVE_SPAM_TO_JUNK = "1";
|
||||||
|
};
|
||||||
|
environmentFiles = ["/home/admin/mailserver.env"];
|
||||||
|
extraOptions = [
|
||||||
|
"--label=com.centurylinklabs.watchtower.enable=true"
|
||||||
|
"--dns=1.1.1.1"
|
||||||
|
"--dns=8.8.8.8"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
#
|
||||||
|
# Netdata
|
||||||
|
#
|
||||||
|
services.netdata = {
|
||||||
|
enable = true;
|
||||||
|
config = {
|
||||||
|
global = {
|
||||||
|
"memory mode" = "ram";
|
||||||
|
"debug log" = "none";
|
||||||
|
"access log" = "none";
|
||||||
|
"error log" = "syslog";
|
||||||
|
};
|
||||||
|
web = {
|
||||||
|
"bind to" = "127.0.0.1:19999";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.caddy.virtualHosts."netdata.severijnse.eu" = {
|
||||||
|
extraConfig = ''
|
||||||
|
import security_headers
|
||||||
|
import csp
|
||||||
|
reverse_proxy 127.0.0.1:19999
|
||||||
|
encode zstd gzip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{...}: {
|
||||||
|
virtualisation.oci-containers.containers = {
|
||||||
|
hbbr = {
|
||||||
|
image = "rustdesk/rustdesk-server:latest";
|
||||||
|
cmd = ["hbbr"];
|
||||||
|
autoStart = true;
|
||||||
|
volumes = [
|
||||||
|
"/home/admin/rustdesk:/root:Z"
|
||||||
|
];
|
||||||
|
extraOptions = [
|
||||||
|
"--network=host"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
hbbs = {
|
||||||
|
image = "rustdesk/rustdesk-server:latest";
|
||||||
|
cmd = ["hbbs"];
|
||||||
|
autoStart = true;
|
||||||
|
volumes = [
|
||||||
|
"/home/admin/rustdesk:/root:Z"
|
||||||
|
];
|
||||||
|
dependsOn = ["hbbr"];
|
||||||
|
extraOptions = [
|
||||||
|
"--network=host"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{...}: {
|
||||||
|
virtualisation.oci-containers.containers.shkeeper = {
|
||||||
|
image = "vsyshost/shkeeper:2.5.29";
|
||||||
|
autoStart = true;
|
||||||
|
ports = ["5000:5000"];
|
||||||
|
volumes = [
|
||||||
|
"/home/admin/shkeeper-data:/app/data:Z"
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
SHKEEPER_HOST = "0.0.0.0";
|
||||||
|
SHKEEPER_PORT = "5000";
|
||||||
|
};
|
||||||
|
extraOptions = [
|
||||||
|
"--env-file=/home/admin/shkeeper.env"
|
||||||
|
"--label=com.centurylinklabs.watchtower.enable=true"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
unstablePkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
# Caddy's dist dir (see tlsa-updater.nix): cert 0644, key 0640 root:root.
|
||||||
|
# The Stalwart service runs as "stalwart"; grant it read access to the key.
|
||||||
|
certDir = "/var/lib/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.severijnse.eu";
|
||||||
|
# sops-encrypted secrets (single file holds all service secrets), same as backup.nix.
|
||||||
|
secretsFile = ../../secrets/secrets.yaml;
|
||||||
|
# Root-only runtime file holding the fallback-admin password hash (0600 root).
|
||||||
|
adminHashFile = "/var/lib/stalwart/.admin-hash";
|
||||||
|
|
||||||
|
# Materialize the fallback-admin password hash from sops into a root-only file.
|
||||||
|
writeAdminHash = pkgs.writeShellScript "stalwart-write-admin-hash" ''
|
||||||
|
set -euo pipefail
|
||||||
|
install -d -o root -g root -m 0755 "$(dirname ${adminHashFile})"
|
||||||
|
${pkgs.sops}/bin/sops \
|
||||||
|
--decrypt --extract '["stalwart_admin_hash"]' \
|
||||||
|
--input-type yaml --output-type yaml ${secretsFile} \
|
||||||
|
| tr -d '\n' > "${adminHashFile}"
|
||||||
|
chmod 0600 "${adminHashFile}"
|
||||||
|
'';
|
||||||
|
|
||||||
|
# The outbound DKIM key is the existing opendkim key from docker-mailserver
|
||||||
|
# (selector "mail", domain severijnse.eu, PKCS#8 RSA 2048). Install it into the
|
||||||
|
# stalwart-owned data dir so the server can read it and it is covered by the
|
||||||
|
# /var/lib/stalwart backup.
|
||||||
|
dkimKeyDir = "/var/lib/stalwart/dkim/severijnse.eu";
|
||||||
|
dkimKeySrc = "/home/admin/dms/config/opendkim/keys/severijnse.eu/mail.private";
|
||||||
|
writeDkimKey = pkgs.writeShellScript "stalwart-write-dkim-key" ''
|
||||||
|
set -euo pipefail
|
||||||
|
install -d -o stalwart -g stalwart -m 0750 ${dkimKeyDir}
|
||||||
|
install -o stalwart -g stalwart -m 0640 ${dkimKeySrc} ${dkimKeyDir}/mail.private
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
# The hetzner host is built with nixos-24.05, which ships its own
|
||||||
|
# `services.stalwart-mail` module (for the old 0.8.x package). We want the
|
||||||
|
# 0.15.5 module from the locked nixpkgs-unstable instead, so we must exclude
|
||||||
|
# the 24.05 default module (which defines the same option namespace) to avoid
|
||||||
|
# the rename-based infinite recursion, and import the unstable one in its place.
|
||||||
|
disabledModules = [
|
||||||
|
"services/mail/stalwart-mail.nix"
|
||||||
|
];
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
"${unstablePkgs.path}/nixos/modules/services/mail/stalwart.nix"
|
||||||
|
];
|
||||||
|
|
||||||
|
services.stalwart = {
|
||||||
|
enable = true;
|
||||||
|
stateVersion = "26.05";
|
||||||
|
package = unstablePkgs.stalwart;
|
||||||
|
|
||||||
|
# Public listeners. The host firewall (networking.nix) already allows
|
||||||
|
# 25/143/465/587/993, so openFirewall stays off: the module would also open
|
||||||
|
# every other listener port incl. the 8080 webadmin.
|
||||||
|
openFirewall = false;
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
# EHLO / hostname for the server (docs server.hostname).
|
||||||
|
server.hostname = "mail.severijnse.eu";
|
||||||
|
|
||||||
|
# Public origin the JMAP/webadmin API is served from, advertised in the
|
||||||
|
# JMAP session (docs server/core/network.md "http.url"). Value must be a
|
||||||
|
# JScript expression, hence the single-quoted string literal. Without this
|
||||||
|
# Stalwart advertises http://mail.severijnse.eu:8080, which the browser
|
||||||
|
# refuses to fetch and breaks Bulwark webmail.
|
||||||
|
http.url = "'https://mail.severijnse.eu'";
|
||||||
|
|
||||||
|
certificate."mail-severijnse-eu" = {
|
||||||
|
cert = "%{file:${certDir}/mail.severijnse.eu.crt}%";
|
||||||
|
private-key = "%{file:${certDir}/mail.severijnse.eu.key}%";
|
||||||
|
# Docs server/tls/certificates.md: used when the client sends no SNI.
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
server.tls = {
|
||||||
|
certificate = "mail-severijnse-eu";
|
||||||
|
enable = true;
|
||||||
|
implicit = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Public listeners (docs server/listener.md + protocol, tls.implicit override).
|
||||||
|
# Bind "[::]:port" for dual-stack IPv4+IPv6 (docs: "to bind a listener to
|
||||||
|
# all interfaces"); listing both 0.0.0.0 and [::] makes the [::] bind fail
|
||||||
|
# with EADDRINUSE on kernels with net.ipv6.bindv6only=0.
|
||||||
|
# 143/587 use STARTTLS (server.tls.implicit=false default), 993/465 the
|
||||||
|
# implicit-TLS variants, 25 the plain (STARTTLS) MX port.
|
||||||
|
server.listener = {
|
||||||
|
"imap" = {
|
||||||
|
bind = ["[::]:143"];
|
||||||
|
protocol = "imap";
|
||||||
|
};
|
||||||
|
"imaps" = {
|
||||||
|
bind = ["[::]:993"];
|
||||||
|
protocol = "imap";
|
||||||
|
tls.implicit = true;
|
||||||
|
};
|
||||||
|
"smtp" = {
|
||||||
|
bind = ["[::]:25"];
|
||||||
|
protocol = "smtp";
|
||||||
|
};
|
||||||
|
"smtp-submission" = {
|
||||||
|
bind = ["[::]:587"];
|
||||||
|
protocol = "smtp";
|
||||||
|
};
|
||||||
|
"smtp-submissions" = {
|
||||||
|
bind = ["[::]:465"];
|
||||||
|
protocol = "smtp";
|
||||||
|
tls.implicit = true;
|
||||||
|
};
|
||||||
|
"http-management" = {
|
||||||
|
bind = ["127.0.0.1:8080"];
|
||||||
|
protocol = "http";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Auth per docs mta/inbound/auth.md (AUTH stage): authentication is
|
||||||
|
# disabled on the plain SMTP listener (port 25) and required everywhere
|
||||||
|
# else (IMAP + submission). Only offer PLAIN/LOGIN over TLS, so clear-text
|
||||||
|
# listeners (143/587 pre-STARTTLS) advertise no SASL mechanisms. This
|
||||||
|
# mirrors the code defaults (crates/common/src/config/smtp/session.rs).
|
||||||
|
# The JMAP/webadmin "http-management" listener (bind 127.0.0.1:8080) is
|
||||||
|
# plain HTTP and reachable only from localhost, where bulwark connects;
|
||||||
|
# its basic-auth uses the "plain" mechanism, so it must be exempted from
|
||||||
|
# the is_tls gate or webmail logins fail with "Authentication not allowed".
|
||||||
|
session.auth.mechanisms = [
|
||||||
|
{
|
||||||
|
"if" = "local_port != 25 && (is_tls || listener == 'http-management')";
|
||||||
|
"then" = "[plain, login]";
|
||||||
|
}
|
||||||
|
{"else" = false;}
|
||||||
|
];
|
||||||
|
session.auth.directory = [
|
||||||
|
{
|
||||||
|
"if" = "listener != 'smtp'";
|
||||||
|
"then" = "'internal'";
|
||||||
|
}
|
||||||
|
{"else" = false;}
|
||||||
|
];
|
||||||
|
session.auth.require = [
|
||||||
|
{
|
||||||
|
"if" = "listener != 'smtp'";
|
||||||
|
"then" = true;
|
||||||
|
}
|
||||||
|
{"else" = false;}
|
||||||
|
];
|
||||||
|
|
||||||
|
# Outbound DKIM signing (docs mta/authentication/dkim/sign): sign with the
|
||||||
|
# "mail" signature on everything submitted via non-25 listeners; do not sign
|
||||||
|
# inbound mail received on the plain "smtp" listener.
|
||||||
|
auth.dkim.sign = [
|
||||||
|
{
|
||||||
|
"if" = "listener != 'smtp'";
|
||||||
|
"then" = "['mail']";
|
||||||
|
}
|
||||||
|
{"else" = false;}
|
||||||
|
];
|
||||||
|
|
||||||
|
# ARC sealing uses the same "mail" signature (docs mta/authentication/arc).
|
||||||
|
# The code default ('rsa-' + report.domain) would reference a signature
|
||||||
|
# name that does not exist and log "ARC sealer not found".
|
||||||
|
auth.arc.seal = "'mail'";
|
||||||
|
|
||||||
|
# Every *downstream* sign rule defaults to signing with
|
||||||
|
# ['rsa-<report.domain>', 'ed25519-<report.domain>'] (queue.rs/report.rs),
|
||||||
|
# names that do not exist here and log "DKIM signer not found" on DSNs and
|
||||||
|
# reports. Point them all at the real "mail" signature.
|
||||||
|
report.dsn.sign = "['mail']";
|
||||||
|
report.spf.sign = "['mail']";
|
||||||
|
report.dmarc.sign = "['mail']";
|
||||||
|
report.dmarc.aggregate.sign = "['mail']";
|
||||||
|
report.tls.aggregate.sign = "['mail']";
|
||||||
|
|
||||||
|
# Reuse the existing opendkim key (selector mail) so no DNS change is needed.
|
||||||
|
signature.mail = {
|
||||||
|
private-key = "%{file:/var/lib/stalwart/dkim/severijnse.eu/mail.private}%";
|
||||||
|
domain = "severijnse.eu";
|
||||||
|
selector = "mail";
|
||||||
|
headers = ["From" "To" "Date" "Subject" "Message-Id"];
|
||||||
|
algorithm = "rsa-sha256";
|
||||||
|
canonicalization = "relaxed/relaxed";
|
||||||
|
set-body-length = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Fallback admin (auth/authorization/administrator.md): bootstrap admin with
|
||||||
|
# every permission, used to create the internal-directory accounts via the
|
||||||
|
# management REST API / CLI. Secret is a SHA-512-crypt hash, injected via
|
||||||
|
# LoadCredential (services.stalwart.credentials) so no secret lands in the
|
||||||
|
# Nix store.
|
||||||
|
authentication."fallback-admin" = {
|
||||||
|
user = "admin";
|
||||||
|
secret = "%{file:/run/credentials/stalwart.service/stalwart-admin}%";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Route docs mta/outbound/routing.md + strategy.md: local domains → local
|
||||||
|
# store, everything else → MX. The strategy names are defined explicitly
|
||||||
|
# (docs define queue.route.mx/local; the built-in fallback in
|
||||||
|
# core.rs:get_route_or_default only kicks in for undeclared names).
|
||||||
|
queue.strategy.route = [
|
||||||
|
{
|
||||||
|
"if" = "is_local_domain('', rcpt_domain)";
|
||||||
|
"then" = "'local'";
|
||||||
|
}
|
||||||
|
{"else" = "'mx'";}
|
||||||
|
];
|
||||||
|
queue.route."mx" = {
|
||||||
|
type = "mx";
|
||||||
|
ip-lookup = "ipv4_then_ipv6";
|
||||||
|
};
|
||||||
|
queue.route."local" = {
|
||||||
|
type = "local";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# The module's service runs as user/group "stalwart" (ProtectHome=true,
|
||||||
|
# ProtectSystem=strict). The TLS key tlsa-updater installs is 0640 root:root;
|
||||||
|
# regrant it to the stalwart group after every cert sync so stalwart can serve TLS.
|
||||||
|
systemd.services.stalwart = {
|
||||||
|
after = ["tlsa-update.service" "stalwart-admin-secret.service" "stalwart-dkim.service"];
|
||||||
|
requires = ["tlsa-update.service" "stalwart-admin-secret.service" "stalwart-dkim.service"];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Make the management CLI available for account creation and maildir import
|
||||||
|
# (docs management/cli/). Version-pinned to the locked unstable nixpkgs.
|
||||||
|
environment.systemPackages = [unstablePkgs.stalwart-cli];
|
||||||
|
|
||||||
|
# Materialize the fallback-admin password hash from sops before stalwart starts.
|
||||||
|
# The admin hash is a SHA-512-crypt value, safe to pass through a root-only file.
|
||||||
|
systemd.services.stalwart-admin-secret = {
|
||||||
|
description = "Materialize Stalwart fallback-admin password hash from sops";
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
before = ["stalwart.service"];
|
||||||
|
# The age key lives in /etc/age/keys.txt; the service must know where it is
|
||||||
|
# and needs a HOME for age to report its user config directory.
|
||||||
|
environment.SOPS_AGE_KEY_FILE = "/etc/age/keys.txt";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
Environment = ["HOME=/root"];
|
||||||
|
ExecStart = "${writeAdminHash}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Install the outbound DKIM key into the stalwart data dir before the service starts.
|
||||||
|
systemd.services.stalwart-dkim = {
|
||||||
|
description = "Install Stalwart outbound DKIM key";
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
before = ["stalwart.service"];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${writeDkimKey}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# LoadCredential: expose the materialized hash to stalwart only at
|
||||||
|
# /run/credentials/stalwart.service/stalwart-admin (see `credentials` option
|
||||||
|
# in the upstream module; the value is the source path on disk).
|
||||||
|
services.stalwart.credentials.stalwart-admin = adminHashFile;
|
||||||
|
|
||||||
|
systemd.services.stalwart-cert-perm = {
|
||||||
|
description = "Grant stalwart read access to its TLS private key";
|
||||||
|
# Belt-and-suspenders: tlsa-update already chgrps the key after every sync;
|
||||||
|
# this guarantees the group grant also exists at first boot, before stalwart
|
||||||
|
# starts (previously ordered after stalwart, so a fresh sync could leave a
|
||||||
|
# root:root key and webadmin reload would fail with EACCES).
|
||||||
|
after = ["tlsa-update.service"];
|
||||||
|
before = ["stalwart.service"];
|
||||||
|
partOf = ["tlsa-update.service"];
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
path = [pkgs.coreutils];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${pkgs.coreutils}/bin/chgrp stalwart ${certDir}/mail.severijnse.eu.key";
|
||||||
|
ExecStartPost = "${pkgs.coreutils}/bin/chmod 0640 ${certDir}/mail.severijnse.eu.key";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Bulwark webmail (self-hosted JMAP webmail for Stalwart). Serving on
|
||||||
|
# mail.severijnse.eu behind Caddy (see caddy.nix). It connects to Stalwart's
|
||||||
|
# JMAP endpoint at 127.0.0.1:8080, so uses host networking. Next.js defaults
|
||||||
|
# to POST_SIZE/etc via env; JMAP_SERVER_URL points at the Stalwart http
|
||||||
|
# listener which serves JMAP at /jmap.
|
||||||
|
virtualisation.oci-containers.containers.bulwark = {
|
||||||
|
image = "ghcr.io/bulwarkmail/webmail:latest";
|
||||||
|
autoStart = true;
|
||||||
|
volumes = [
|
||||||
|
"/var/lib/bulwark:/app/data:Z"
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
# JMAP_SERVER_URL is the public origin (the browser uses it verbatim for
|
||||||
|
# /.well-known/jmap + the session apiUrl). HOSTNAME stays 127.0.0.1 so
|
||||||
|
# Next.js binds to loopback (caddy reverse_proxy's 127.0.0.1:3002); binding
|
||||||
|
# to the public hostname made the container unreachable for caddy (502).
|
||||||
|
JMAP_SERVER_URL = "https://mail.severijnse.eu";
|
||||||
|
HOSTNAME = "127.0.0.1";
|
||||||
|
PORT = "3002";
|
||||||
|
};
|
||||||
|
extraOptions = [
|
||||||
|
"--network=host"
|
||||||
|
"--label=com.centurylinklabs.watchtower.enable=true"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /var/lib/bulwark 0755 1001 1001 - -"
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
{pkgs, ...}: let
|
||||||
|
# Caddy's canonical certificate storage (XDG data dir). Renewals land here,
|
||||||
|
# owned caddy:caddy 0600 — stalwart cannot read it directly, so we copy it
|
||||||
|
# into a distribution dir stalwart's service (user "stalwart") can reach.
|
||||||
|
caddyCertDir = "/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.severijnse.eu";
|
||||||
|
# Distribution dir read by stalwart (cert 0644, private key regranted to the
|
||||||
|
# "stalwart" group by systemd.services.stalwart-cert-perm).
|
||||||
|
distCertDir = "/var/lib/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.severijnse.eu";
|
||||||
|
zoneFile = "/var/lib/coredns/zones/severijnse.eu.db";
|
||||||
|
# Records the SPKI hash applied at the last restart of stalwart, so cert
|
||||||
|
# renewals trigger exactly one restart and unchanged certs never do.
|
||||||
|
stateFile = "/var/lib/tlsa-update/.last-spki";
|
||||||
|
|
||||||
|
syncScript = pkgs.writeShellScript "tlsa-update" ''
|
||||||
|
set -euo pipefail
|
||||||
|
OPENSSL="${pkgs.openssl}/bin/openssl"
|
||||||
|
|
||||||
|
SRC_CERT="${caddyCertDir}/mail.severijnse.eu.crt"
|
||||||
|
SRC_KEY="${caddyCertDir}/mail.severijnse.eu.key"
|
||||||
|
DST_CERT="${distCertDir}/mail.severijnse.eu.crt"
|
||||||
|
DST_KEY="${distCertDir}/mail.severijnse.eu.key"
|
||||||
|
|
||||||
|
# Caddy has not obtained the certificate yet (e.g. first boot before HTTP-01).
|
||||||
|
if [ ! -f "$SRC_CERT" ] || [ ! -f "$SRC_KEY" ]; then
|
||||||
|
echo "tlsa-update: certificate not found at $caddyCertDir, skipping" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1) Propagate Caddy's renewed certificate into the distribution dir stalwart
|
||||||
|
# reads. Caddy stores certs 0600 caddy:caddy. The cert is world-readable;
|
||||||
|
# the private key is restricted to root (0640) and stalwart-cert-perm
|
||||||
|
# regrants it to the "stalwart" group so it is not exposed to other local
|
||||||
|
# users.
|
||||||
|
install -D -m 0644 "$SRC_CERT" "$DST_CERT"
|
||||||
|
install -D -m 0640 "$SRC_KEY" "$DST_KEY"
|
||||||
|
# Stalwart reads the key as user "stalwart" via %{file:...}%; regrant the
|
||||||
|
# group immediately so every sync leaves it readable (0640 root:stalwart)
|
||||||
|
# and webadmin config reload never fails with EACCES.
|
||||||
|
chgrp stalwart "$DST_KEY"
|
||||||
|
|
||||||
|
# 2) TLSA 3 1 1 = SHA-256 of the certificate's SubjectPublicKeyInfo (SPKI),
|
||||||
|
# NOT the whole certificate. Matching type 1 = SHA-256 of the SPKI DER.
|
||||||
|
HEX=$("$OPENSSL" x509 -in "$DST_CERT" -noout -pubkey 2>/dev/null | "$OPENSSL" pkey -pubin -outform DER 2>/dev/null | "$OPENSSL" dgst -sha256 | cut -d' ' -f2)
|
||||||
|
|
||||||
|
# 3) Update the CoreDNS zone. coredns.service regenerates this file from the
|
||||||
|
# Nix store on every start, so this unit (which is partOf coredns and runs
|
||||||
|
# after it) re-applies the correct TLSA after each rebuild/restart.
|
||||||
|
if [ -f "${zoneFile}" ]; then
|
||||||
|
sed -i -E "s/^(_25\._tcp\.mail.*TLSA 3 1 1).*/\1 $HEX/" "${zoneFile}"
|
||||||
|
sed -i -E "s/^(_465\._tcp\.mail.*TLSA 3 1 1).*/\1 $HEX/" "${zoneFile}"
|
||||||
|
sed -i -E "s/^(_993\._tcp\.mail.*TLSA 3 1 1).*/\1 $HEX/" "${zoneFile}"
|
||||||
|
chown coredns:coredns "${zoneFile}"
|
||||||
|
chmod 0640 "${zoneFile}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4) Reload services so the changes take effect immediately.
|
||||||
|
systemctl reload coredns.service || true
|
||||||
|
# Stalwart reads its TLS certs ($certDir) via %{file:...}% placeholders at
|
||||||
|
# startup only; there is no signal-based reload (management API reload needs
|
||||||
|
# admin credentials we must not store). Restart it, but only when the cert
|
||||||
|
# actually changed (SPKI hash differs from the last applied run), so the
|
||||||
|
# daily timer and inotify events for unchanged certs do not drop connections.
|
||||||
|
# NOTE: stalwart.service `requires` this unit, so the restart must be issued
|
||||||
|
# with --no-block (async): a synchronous restart waits for stalwart to come
|
||||||
|
# back up, which in turn waits for this unit to finish — a deadlock. The state
|
||||||
|
# file is updated BEFORE the restart so the tlsa-update run that stalwart's
|
||||||
|
# required-activation re-triggers sees a matching hash and exits immediately.
|
||||||
|
if [ "$(cat "${stateFile}" 2>/dev/null || true)" != "$HEX" ]; then
|
||||||
|
printf '%s\n' "$HEX" > "${stateFile}"
|
||||||
|
# Restarting stalwart re-runs its Requires=tlsa-update dependency; guard
|
||||||
|
# with is-active so a boot-time run never races stalwart's initial start.
|
||||||
|
if systemctl is-active --quiet stalwart.service 2>/dev/null; then
|
||||||
|
systemctl --no-block restart stalwart.service || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "tlsa-update: TLSA set to $HEX"
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
systemd = {
|
||||||
|
# Ensure the distribution dir exists (Caddy does not write here).
|
||||||
|
tmpfiles.rules = [
|
||||||
|
"d ${distCertDir} 0755 root root - -"
|
||||||
|
"d /var/lib/tlsa-update 0755 root root - -"
|
||||||
|
];
|
||||||
|
|
||||||
|
services.tlsa-update = {
|
||||||
|
description = "Sync Caddy TLS certificate to mail server and update DANE/TLSA records";
|
||||||
|
after = ["caddy.service" "coredns.service"];
|
||||||
|
partOf = ["coredns.service"];
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
path = with pkgs; [openssl coreutils gnused systemd];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${syncScript}";
|
||||||
|
User = "root";
|
||||||
|
Group = "root";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Fire as soon as Caddy rewrites the certificate on renewal (the atomic rewrite
|
||||||
|
# changes the directory mtime), eliminating the up-to-24h DANE drift window.
|
||||||
|
paths.tlsa-update = {
|
||||||
|
description = "Watch Caddy certificate directory for renewal";
|
||||||
|
wantedBy = ["paths.target"];
|
||||||
|
pathConfig = {
|
||||||
|
PathModified = [caddyCertDir];
|
||||||
|
Unit = "tlsa-update.service";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Fallback in case a renewal event is missed (e.g. inotify overflow).
|
||||||
|
timers.tlsa-update = {
|
||||||
|
wantedBy = ["timers.target"];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "daily";
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{...}: {
|
||||||
|
virtualisation.oci-containers.containers.vaultwarden = {
|
||||||
|
image = "vaultwarden/server:latest";
|
||||||
|
autoStart = true;
|
||||||
|
ports = ["1001:80"];
|
||||||
|
volumes = [
|
||||||
|
"/home/admin/vw-data:/data:Z"
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
DOMAIN = "https://vault.severijnse.eu";
|
||||||
|
LOG_FILE = "/data/vaultwarden.log";
|
||||||
|
};
|
||||||
|
extraOptions = [
|
||||||
|
"--label=com.centurylinklabs.watchtower.enable=true"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
--- a/src/app/api/checkout/route.ts
|
||||||
|
+++ b/src/app/api/checkout/route.ts
|
||||||
|
@@ -7,6 +7,8 @@
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
+const BYPASS_PAYMENTS = process.env.BYPASS_PAYMENTS === "true";
|
||||||
|
+
|
||||||
|
export async function POST(req: Request) {
|
||||||
|
// Each checkout hits the payment provider, so cap order creation per user
|
||||||
|
// and per IP to prevent order spam and provider-API abuse.
|
||||||
|
@@ -86,6 +88,20 @@
|
||||||
|
const paid = await prisma.order.findUnique({
|
||||||
|
where: { id: order.id },
|
||||||
|
include: { license: true },
|
||||||
|
+ });
|
||||||
|
+ return NextResponse.json({
|
||||||
|
+ ok: true,
|
||||||
|
+ orderId: order.id,
|
||||||
|
+ licenseKey: paid?.license?.key ?? null,
|
||||||
|
+ tier: tier.slug,
|
||||||
|
+ });
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (BYPASS_PAYMENTS) {
|
||||||
|
+ await markOrderPaid({ orderId: order.id, providerRef: "bypass-test" });
|
||||||
|
+ const paid = await prisma.order.findUnique({
|
||||||
|
+ where: { id: order.id },
|
||||||
|
+ include: { license: true },
|
||||||
|
});
|
||||||
|
return NextResponse.json({
|
||||||
|
ok: true,
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
unstablePkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
rev = "6e351ddc732421eb775eb9843ccd2389d6525edd";
|
||||||
|
apiRev = "390878d126185b21a14479f85c70134a155c61c6";
|
||||||
|
|
||||||
|
# Private repositories are fetched over SSH (port 2222). nix-daemon runs as
|
||||||
|
# root and uses /root/.ssh (identity materialized by the git-ssh-key unit),
|
||||||
|
# so the source ends up in the store without any Nix-native credentials.
|
||||||
|
src = builtins.fetchGit {
|
||||||
|
url = "ssh://git@git.severijnse.eu:2222/jory/virtualcam-website.git";
|
||||||
|
rev = rev;
|
||||||
|
};
|
||||||
|
|
||||||
|
srcPatched = pkgs.applyPatches {
|
||||||
|
name = "virtualcam-website-patched";
|
||||||
|
src = src;
|
||||||
|
patches = [./virtualcam-checkout.patch];
|
||||||
|
};
|
||||||
|
|
||||||
|
apiSrc = builtins.fetchGit {
|
||||||
|
url = "ssh://git@git.severijnse.eu:2222/jory/virtualcam-api.git";
|
||||||
|
rev = apiRev;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Build the Next.js app entirely in Nix (offline npm deps from the lockfile).
|
||||||
|
app = unstablePkgs.buildNpmPackage {
|
||||||
|
pname = "virtualcam-website";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = srcPatched;
|
||||||
|
npmDepsHash = "sha256-GodWQKtOtsLOjjiwzxun+wTPhtvjR2uCV91n+wiZHw4=";
|
||||||
|
nodejs = unstablePkgs.nodejs;
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
npx prisma generate
|
||||||
|
npm run build
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
mkdir -p $out
|
||||||
|
cp -r .next node_modules public prisma src package.json package-lock.json \
|
||||||
|
prisma.config.ts next.config.ts tsconfig.json postcss.config.mjs $out/
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
APP_URL = "https://virtualcam.severijnse.eu";
|
||||||
|
GOOGLE_CLIENT_ID = "754775011707-c699m092tv3icmovhk5qa106v3q6eh7c.apps.googleusercontent.com";
|
||||||
|
# Only used to satisfy prisma generate / next build metadata resolution.
|
||||||
|
DATABASE_URL = "postgresql://virtualcam@localhost/virtualcam?host=/run/postgresql&schema=public";
|
||||||
|
# Use the nixpkgs-bundled Prisma engine so the sandboxed offline build does
|
||||||
|
# not need to download it from binaries.prisma.sh. Version matches ^7.8.0.
|
||||||
|
PRISMA_SCHEMA_ENGINE_BINARY = "${unstablePkgs.prisma-engines}/bin/schema-engine";
|
||||||
|
NEXT_TELEMETRY_DISABLED = "1";
|
||||||
|
CI = "true";
|
||||||
|
};
|
||||||
|
|
||||||
|
# License validation / admin API (Go/Fiber), serving on host port 3004.
|
||||||
|
# go.mod demands go 1.26.5 but the pinned nixpkgs only has 1.26.4; the code
|
||||||
|
# uses nothing newer, so the directive is relaxed to match the toolchain.
|
||||||
|
api = unstablePkgs.buildGoModule {
|
||||||
|
pname = "virtualcam-api";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = apiSrc;
|
||||||
|
vendorHash = "sha256-uvHClXHw9ycoIf6qBZmV2O3CSyIxCgnSPCSALM07qg8=";
|
||||||
|
go = unstablePkgs.go_1_26;
|
||||||
|
postPatch = ''
|
||||||
|
sed -i 's/^go 1\.26\.5$/go 1.26.4/' go.mod
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
dbUrl = "postgresql://virtualcam@localhost/virtualcam?host=/run/postgresql&schema=public";
|
||||||
|
# lib/pq parses the DSN differently from node-postgres: a hostname in the URL
|
||||||
|
# authority wins over a `host=` query param (so it would go over TCP and fail
|
||||||
|
# password auth), and lib/pq rejects unknown URL params like `schema`. Use a
|
||||||
|
# keyword DSN: unix-socket + peer auth + no SSL, matching the OS user.
|
||||||
|
apiDbUrl = "host=/run/postgresql user=virtualcam dbname=virtualcam sslmode=disable";
|
||||||
|
|
||||||
|
# Root-only runtime environment file holding the secrets both services need.
|
||||||
|
secretsFile = ../../secrets/secrets.yaml;
|
||||||
|
envFile = "/var/lib/virtualcam/environment";
|
||||||
|
|
||||||
|
# Materialize the license signing key and admin token from sops into a
|
||||||
|
# root-only file (0600). systemd reads environmentFiles before dropping
|
||||||
|
# privileges, so the service users never need to read it themselves.
|
||||||
|
writeSecrets = pkgs.writeShellScript "virtualcam-write-secrets" ''
|
||||||
|
set -euo pipefail
|
||||||
|
install -d -o virtualcam -g virtualcam -m 0750 /var/lib/virtualcam
|
||||||
|
: > "${envFile}"
|
||||||
|
chmod 0600 "${envFile}"
|
||||||
|
${pkgs.sops}/bin/sops --decrypt --input-type yaml --output-type yaml ${secretsFile} |
|
||||||
|
${pkgs.gnused}/bin/sed -nE \
|
||||||
|
's/^virtualcam_license_signing_key: (.*)/LICENSE_SIGNING_KEY=\1/p; s/^virtualcam_admin_token: (.*)/ADMIN_TOKEN=\1/p; s/^virtualcam_smtp_pass: (.*)/SMTP_PASS=\1/p; s/^virtualcam_google_OAuth_secret: (.*)/GOOGLE_CLIENT_SECRET=\1/p; s/^shkeeper_api_key: (.*)/SHKEEPER_API_KEY=\1/p' \
|
||||||
|
>> "${envFile}"
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Materialize the nix-daemon's SSH identity so private-repo fetches keep
|
||||||
|
# working after the one-off bootstrap copy in /root/.ssh.
|
||||||
|
writeGitSshKey = pkgs.writeShellScript "git-ssh-key" ''
|
||||||
|
set -euo pipefail
|
||||||
|
install -d -m 0700 /root/.ssh
|
||||||
|
${pkgs.sops}/bin/sops --decrypt --extract '["git_ssh_key_b64"]' \
|
||||||
|
--input-type yaml --output-type yaml ${secretsFile} |
|
||||||
|
${pkgs.coreutils}/bin/base64 -d > /root/.ssh/id_ed25519
|
||||||
|
chmod 0600 /root/.ssh/id_ed25519
|
||||||
|
cat > /root/.ssh/known_hosts <<'EOF'
|
||||||
|
git.severijnse.eu ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAzIVo9Jdp8kwnWmTn26Fj68baJjwDphYw/0HTH5BzYY
|
||||||
|
[git.severijnse.eu]:2222 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDGcPKlvdxApYRCZuQxrljEdETrxAu0JsEJ+28zFaMfA3P69J9vEkqqAZZ0Uq3rtE8ZqPBxeDh7JYn2XChb+QVGjOHVWMCWqcdD43ws26eY91aHGYnO2kW+sKSxIh803tdifIp2q8VRnaHVXcLHLx2rmwmvqIfMLnEEShxAt4Q846DianBoA7S8heoBvMLi8AAO09sVEgMPWm8ULkoRq0poHslsKkdYKlWzecP85tPo/tlCEjQq5cC5+xidH9TS4ILxVIXGUfErZzbWZq5LtaRmqiU5U1+zzdb83oJvHFtTZ8wHwZX+VE4jZzKw7wdFPk+dJZp/3ZAVva6fLSjlpyyDFNWMcf2/18Gs6YioD1n2fr1dXruZiXNMnI19Dzyp+8yWIPbAeWyVx3BoCrcjIApEDMLMo7ut0l6RfNy80hYpY91VBrpsjSRLB4uCVDsqhTLPTrFUlu+9YY9lSHEUVLRylOLK54TbY089hbHOB/UdpyCiXpI0ds3ww1UmnDQSFjwNknc+wTXRvzEz+VsX2nAzfO+5NWq2jHNn4wH6GuAinAxSVg1A7Ub9VSmPelRiTzK6ZnplPm762MxcPFmo+y/5cszumUwypcyTS8MJIC57S/kabfsnjdt97K+9eT9X2LDZ2uIQgDS8qEgvbnvfXVmMoqakgBLYG7Embqc8BDzNsQ==
|
||||||
|
EOF
|
||||||
|
chmod 0600 /root/.ssh/known_hosts
|
||||||
|
test -f /root/.ssh/id_ed25519.pub || ${pkgs.openssh}/bin/ssh-keygen -y -f /root/.ssh/id_ed25519 > /root/.ssh/id_ed25519.pub
|
||||||
|
'';
|
||||||
|
|
||||||
|
# The repo's seed uses tsx (a devDependency buildNpmPackage drops) plus the
|
||||||
|
# "@/..." path alias. nixpkgs ships tsx, which honours tsconfig paths, so we
|
||||||
|
# add it to the service path rather than working around the missing dep.
|
||||||
|
seed = pkgs.writeShellScript "virtualcam-seed" ''
|
||||||
|
set -euo pipefail
|
||||||
|
export DATABASE_URL="${dbUrl}"
|
||||||
|
export PRISMA_SCHEMA_ENGINE_BINARY="${unstablePkgs.prisma-engines}/bin/schema-engine"
|
||||||
|
export HOME=/var/lib/virtualcam
|
||||||
|
cd ${app}
|
||||||
|
tsx prisma/seed.ts
|
||||||
|
'';
|
||||||
|
|
||||||
|
# One shared PostgreSQL server (existing system postgres). Each service gets
|
||||||
|
# its own database + role. virtualcam authenticates over the Unix socket via
|
||||||
|
# peer auth: the systemd services run as OS user `virtualcam`, which matches
|
||||||
|
# the database role `virtualcam`, so no password is stored anywhere.
|
||||||
|
migrate = pkgs.writeShellScript "virtualcam-migrate" ''
|
||||||
|
set -euo pipefail
|
||||||
|
export DATABASE_URL="${dbUrl}"
|
||||||
|
# Use the local Prisma engine; no network download needed at runtime.
|
||||||
|
export PRISMA_SCHEMA_ENGINE_BINARY="${unstablePkgs.prisma-engines}/bin/schema-engine"
|
||||||
|
cd ${app}
|
||||||
|
./node_modules/.bin/prisma migrate deploy
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
users = {
|
||||||
|
users.virtualcam = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "virtualcam";
|
||||||
|
description = "virtualcamera services user";
|
||||||
|
};
|
||||||
|
groups.virtualcam = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.postgresql = {
|
||||||
|
ensureDatabases = ["virtualcam"];
|
||||||
|
ensureUsers = [
|
||||||
|
{
|
||||||
|
name = "virtualcam";
|
||||||
|
ensureDBOwnership = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd = {
|
||||||
|
services = {
|
||||||
|
git-ssh-key = {
|
||||||
|
description = "Materialize nix-daemon git SSH key from sops";
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
environment.SOPS_AGE_KEY_FILE = "/etc/age/keys.txt";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
Environment = ["HOME=/root"];
|
||||||
|
ExecStart = "${writeGitSshKey}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualcam-secrets = {
|
||||||
|
description = "Materialize virtualcam secrets from sops";
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
# The age key lives in /etc/age/keys.txt; the service must know where it
|
||||||
|
# is and needs a HOME for age to report its user config directory.
|
||||||
|
environment.SOPS_AGE_KEY_FILE = "/etc/age/keys.txt";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
Environment = ["HOME=/root"];
|
||||||
|
ExecStart = "${writeSecrets}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualcam-migrate = {
|
||||||
|
description = "Virtualcam Prisma migrations";
|
||||||
|
after = ["postgresql.service"];
|
||||||
|
requires = ["postgresql.service"];
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "virtualcam";
|
||||||
|
Group = "virtualcam";
|
||||||
|
StateDirectory = "virtualcam";
|
||||||
|
StateDirectoryMode = "0750";
|
||||||
|
ExecStart = "${migrate}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualcam-seed = {
|
||||||
|
description = "Virtualcam catalog seed";
|
||||||
|
after = ["virtualcam-migrate.service"];
|
||||||
|
requires = ["virtualcam-migrate.service"];
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
path = [unstablePkgs.nodejs unstablePkgs.tsx];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "virtualcam";
|
||||||
|
Group = "virtualcam";
|
||||||
|
ExecCondition = "!/var/lib/virtualcam/.seeded";
|
||||||
|
ExecStart = "${seed}";
|
||||||
|
ExecStartPost = "${pkgs.coreutils}/bin/touch /var/lib/virtualcam/.seeded";
|
||||||
|
StateDirectory = "virtualcam";
|
||||||
|
StateDirectoryMode = "0750";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualcam = {
|
||||||
|
description = "Virtualcamera website (Next.js)";
|
||||||
|
after = ["postgresql.service" "podman-shkeeper.service" "virtualcam-migrate.service" "virtualcam-seed.service" "virtualcam-secrets.service"];
|
||||||
|
requires = ["postgresql.service" "podman-shkeeper.service" "virtualcam-migrate.service" "virtualcam-seed.service" "virtualcam-secrets.service"];
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
path = [unstablePkgs.nodejs];
|
||||||
|
serviceConfig = {
|
||||||
|
User = "virtualcam";
|
||||||
|
Group = "virtualcam";
|
||||||
|
WorkingDirectory = "${app}";
|
||||||
|
EnvironmentFile = [envFile];
|
||||||
|
ExecStart = "${app}/node_modules/.bin/next start -p 3001 -H 127.0.0.1";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 5;
|
||||||
|
StateDirectory = "virtualcam";
|
||||||
|
StateDirectoryMode = "0750";
|
||||||
|
Environment = [
|
||||||
|
"DATABASE_URL=${dbUrl}"
|
||||||
|
"APP_URL=https://virtualcam.severijnse.eu"
|
||||||
|
"GOOGLE_CLIENT_ID=754775011707-c699m092tv3icmovhk5qa106v3q6eh7c.apps.googleusercontent.com"
|
||||||
|
"PAYMENTS_MODE=shkeeper"
|
||||||
|
"BYPASS_PAYMENTS=false"
|
||||||
|
"SHKEEPER_URL=https://pay.severijnse.eu"
|
||||||
|
"SHKEEPER_CRYPTO=BTC"
|
||||||
|
"SHKEEPER_FIAT=USD"
|
||||||
|
"ADMIN_EMAILS=jory@severijnse.eu"
|
||||||
|
"SMTP_HOST=mail.severijnse.eu"
|
||||||
|
"SMTP_PORT=587"
|
||||||
|
"SMTP_USER=no-reply@severijnse.eu"
|
||||||
|
"SMTP_FROM=no-reply@severijnse.eu"
|
||||||
|
"NODE_ENV=production"
|
||||||
|
"NEXT_TELEMETRY_DISABLED=1"
|
||||||
|
"HOME=/var/lib/virtualcam"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualcam-api = {
|
||||||
|
description = "Virtualcamera license API (Go/Fiber)";
|
||||||
|
after = ["postgresql.service" "podman-shkeeper.service" "virtualcam-migrate.service" "virtualcam-secrets.service"];
|
||||||
|
requires = ["postgresql.service" "podman-shkeeper.service" "virtualcam-migrate.service" "virtualcam-secrets.service"];
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
serviceConfig = {
|
||||||
|
User = "virtualcam";
|
||||||
|
Group = "virtualcam";
|
||||||
|
WorkingDirectory = "${api}";
|
||||||
|
EnvironmentFile = [envFile];
|
||||||
|
ExecStart = "${api}/bin/virtualcam-api";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 5;
|
||||||
|
StateDirectory = "virtualcam";
|
||||||
|
StateDirectoryMode = "0750";
|
||||||
|
Environment = [
|
||||||
|
"PORT=3004"
|
||||||
|
"DATABASE_URL=${apiDbUrl}"
|
||||||
|
"CORS_ORIGINS=https://virtualcam.severijnse.eu"
|
||||||
|
"HOME=/var/lib/virtualcam"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Served behind Caddy (virtualHosts wired in caddy.nix).
|
||||||
|
networking.firewall.allowedTCPPorts = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{...}: {
|
||||||
|
virtualisation.oci-containers.containers.watchtower = {
|
||||||
|
image = "ghcr.io/nicholas-fedor/watchtower:latest";
|
||||||
|
autoStart = true;
|
||||||
|
volumes = [
|
||||||
|
"/var/run/podman/podman.sock:/var/run/docker.sock:ro"
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
WATCHTOWER_CLEANUP = "true";
|
||||||
|
WATCHTOWER_POLL_INTERVAL = "86400";
|
||||||
|
WATCHTOWER_INCLUDE_STOPPED = "true";
|
||||||
|
WATCHTOWER_REVIVE_STOPPED = "true";
|
||||||
|
TZ = "Europe/Amsterdam";
|
||||||
|
};
|
||||||
|
cmd = ["--label-enable"];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
{pkgs, ...}: let
|
||||||
|
# sops-encrypted secrets (single file holds all service secrets), same as backup.nix.
|
||||||
|
secretsFile = ../../secrets/secrets.yaml;
|
||||||
|
# Root-only env file wg-easy reads the admin password from (0600 root).
|
||||||
|
wgEnvFile = "/var/lib/wg-easy/environment";
|
||||||
|
|
||||||
|
# Materialize the wg-easy admin password from sops into a root-only env file,
|
||||||
|
# so the secret never lands in the Nix store.
|
||||||
|
writeSecrets = pkgs.writeShellScript "wg-easy-write-secrets" ''
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p "$(dirname ${wgEnvFile})"
|
||||||
|
PASSWORD="$(${pkgs.sops}/bin/sops \
|
||||||
|
--decrypt --extract '["wg_admin_password"]' \
|
||||||
|
--input-type yaml --output-type yaml ${secretsFile} | tr -d '\n')"
|
||||||
|
printf 'INIT_PASSWORD=%s\n' "$PASSWORD" > "${wgEnvFile}"
|
||||||
|
chmod 0600 "${wgEnvFile}"
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
boot.kernelModules = ["ip6table_nat"];
|
||||||
|
|
||||||
|
virtualisation.oci-containers.containers.wg-easy = {
|
||||||
|
image = "ghcr.io/wg-easy/wg-easy:15";
|
||||||
|
autoStart = true;
|
||||||
|
volumes = [
|
||||||
|
"/home/admin/config:/etc/wireguard:Z"
|
||||||
|
];
|
||||||
|
environmentFiles = [wgEnvFile];
|
||||||
|
environment = {
|
||||||
|
INSECURE = "true";
|
||||||
|
INIT_ENABLED = "true";
|
||||||
|
INIT_USERNAME = "admin";
|
||||||
|
INIT_HOST = "severijnse.eu";
|
||||||
|
INIT_PORT = "51820";
|
||||||
|
INIT_DNS = "1.1.1.1,2606:4700:4700::1111";
|
||||||
|
INIT_IPV4_CIDR = "10.8.0.0/24";
|
||||||
|
INIT_IPV6_CIDR = "fd10:8::/64";
|
||||||
|
INIT_ALLOWED_IPS = "0.0.0.0/0, ::/0";
|
||||||
|
};
|
||||||
|
extraOptions = [
|
||||||
|
"--cap-add=NET_ADMIN"
|
||||||
|
"--cap-add=SYS_MODULE"
|
||||||
|
"--cap-add=NET_RAW"
|
||||||
|
"--network=host"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Materialize the wg-easy admin password from sops before the container starts.
|
||||||
|
systemd.services.wg-easy-secrets = {
|
||||||
|
description = "Materialize wg-easy admin password from sops";
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
# The age key lives in /etc/age/keys.txt; the service must know where it is
|
||||||
|
# and needs a HOME for age to report its user config directory.
|
||||||
|
environment.SOPS_AGE_KEY_FILE = "/etc/age/keys.txt";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
Environment = ["HOME=/root"];
|
||||||
|
ExecStart = "${writeSecrets}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services."podman-wg-easy" = {
|
||||||
|
requires = ["wg-easy-secrets.service"];
|
||||||
|
after = ["wg-easy-secrets.service"];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.wg-nat66 = {
|
||||||
|
description = "NAT66 for WireGuard IPv6";
|
||||||
|
after = ["network.target" "podman-wg-easy.service"];
|
||||||
|
wants = ["podman-wg-easy.service"];
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
${pkgs.iptables}/bin/ip6tables -t nat -C POSTROUTING -s fd10:8::/64 -o enp1s0 -j MASQUERADE 2>/dev/null || \
|
||||||
|
${pkgs.iptables}/bin/ip6tables -t nat -A POSTROUTING -s fd10:8::/64 -o enp1s0 -j MASQUERADE
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{...}: {
|
||||||
|
virtualisation.oci-containers.containers.wrxproxy = {
|
||||||
|
image = "localhost/wrxproxy:latest";
|
||||||
|
autoStart = false;
|
||||||
|
ports = ["81:81"];
|
||||||
|
volumes = [
|
||||||
|
"/home/admin/WRXproxy/config.json:/app/config.json:ro"
|
||||||
|
];
|
||||||
|
extraOptions = [
|
||||||
|
"--label=com.centurylinklabs.watchtower.enable=true"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{pkgs, ...}: {
|
||||||
|
time.timeZone = "Europe/Amsterdam";
|
||||||
|
|
||||||
|
i18n.defaultLocale = "en_US.UTF-8";
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
vim
|
||||||
|
git
|
||||||
|
curl
|
||||||
|
wget
|
||||||
|
htop
|
||||||
|
iotop
|
||||||
|
btop
|
||||||
|
bind.dnsutils # provides dig, nslookup, host
|
||||||
|
jq
|
||||||
|
yq
|
||||||
|
fish
|
||||||
|
podman-compose
|
||||||
|
];
|
||||||
|
|
||||||
|
programs.fish.enable = true;
|
||||||
|
programs.bash.enableCompletion = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
keys:
|
||||||
|
- &admin age1yd59qp5km4cxt99rlfjehnsucrjn9lmj0su4h3avhf6vrtjvnyjqstldl5
|
||||||
|
- &local age15rhqdpwejyf3r6ww70qgv6hqmkpsqraakn26kc49wlauhaceaeqsmuwrdd
|
||||||
|
- &server age1xekdrkjfu82hkxltydm72tllzgayyvfavvudeks3xjuujm5wt5hq6g55v0
|
||||||
|
creation_rules:
|
||||||
|
- path_regex: secrets/.*\.yaml
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *local
|
||||||
|
- *server
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Secrets
|
||||||
|
|
||||||
|
This directory contains encrypted secrets managed with sops-nix.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
1. Generate an age key on the target machine:
|
||||||
|
```
|
||||||
|
mkdir -p /etc/age
|
||||||
|
age-keygen -o /etc/age/keys.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add the public key to `.sops.yaml`
|
||||||
|
|
||||||
|
3. Create/edit secrets:
|
||||||
|
```
|
||||||
|
sops secrets/mail-accounts.yaml
|
||||||
|
sops secrets/env.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `env.yaml` - Environment variables (WG_HOST, PASSWORD_HASH, etc.)
|
||||||
|
- `mail-accounts.yaml` - Mail server account credentials
|
||||||
|
- `wireguard-keys.yaml` - WireGuard private keys
|
||||||
|
- `ssh-keys.yaml` - SSH private keys (mail.private, etc.)
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
coredns_ksk_key: ENC[AES256_GCM,data:mPjyv74ujSVnbJ6cjFdO7XtkThDv4clF+K+d485njvvx7IBrC4t+DMMtJxqL7qad8Cf4g/vx3cO0dsB8UbsbuyVAEPpgmGnqTJ+Ec4zbP73g7u4W/HcgT+ZcGY2fzuz/K7uETRN852KfE6RIKkTZkOfBfo1YsOglpRTiSQMh4Jnso9Nihdh7hfbQQ0yXjhQMhGFTUGYR4AQM3ZUFkjY2hN0Ys34Tbwg4HnHwcmWMnwGj7Ab29HU/UP//AAb1rlUNcde6MuWAGlObj6P/poxjeVyWL7lfAveAEqZiHyUDUE2939cCROT1TLZ+f3t2hinK4bzTfp3n9fxhVt4LG7WnDDms97+7M9rI2dVSxDnDTAElyU8NGlYmDcUOOzeTBccxnVegUuRNHMhT2Hrut3K+TGgpTdDNA93PXNB/cCodvNDWtZGrZuRtANZPVMTe7E/3fiw6urrxR59G,iv:fByY/Z4kzxAFXobtrV8Y59IdqzN4W/HsvEPGOhBGI+E=,tag:FoB4jrxsLXNKnhFykX6F/g==,type:str]
|
||||||
|
coredns_ksk_private: ENC[AES256_GCM,data:yaVhVVR1QHXshePY2x9qbOJszl3tj3ne71W7TP+JG2gF1BiU+EBp8QJNQBcj58sFwun88RPIUK5OUgJ/n9dyEWOCf3jfDO4yRD7XLqJVp3tf15Ou2FytHd7hg52id+1gJxSRmhxHWw5of1v9Jo6cocET6tnVnygsPmxJEufoW3DyFZrPscba7rsJhpjJNINEcvgcD44ySy47gisA2s1oIgXp/UJmoZ/H6IXm9/HL66bvfl0ubZ0rxa0Rlg==,iv:pXl8ppb1Pxl5mRBY2sa595ChSAD/zdbKK+H4xeeMG+I=,tag:ez8xqxXfzf3U15YHbaxUZg==,type:str]
|
||||||
|
coredns_zsk_key: ENC[AES256_GCM,data:AdGcUU1qjdRSlI1j6LJGsrqXgUqMfHJGETaVJETPN8+JEjdHvLXCpuiqnJE20hqXDi6gIrBaUQMruJG2KfSZTfxSPoqIYoAlbBQBpTsGwpFazWIsggMJNlqUYKSnBr4QsBTj6A/IZYR4KzcJX+vo7pL0nLz+bpH1/o6ry7lghqt7sIjS9Nx1CKW6WpMPePjBViGp8i5jQWijsUCj+DRjQatycBKhpQWCqlYqn+CM99/QWjy/qXWFmgBiiFjQ8lT1AjOIIL0Icger06ReWCl2387xB/JqHbtsq65WOLuJiyxBBw02wOQz09UaKsDsoM59w3SINKqK+YyCkonHXycLuMAuE7tasHVRPEdWsr6os18cystLQO8qp1oVSGtUSVM07TkM8OowMlOmN44veIkgON4s8pN7S9V9N46N0BSC6w7A3u2VqCfAgPLUjPWNieIfir8P3p+gQSPWuw==,iv:Kvc8N+fp2rVd1TTsZHbk67fL2nOlGhqDE+TvH+aU0VE=,tag:1wkgkV1gqddhkkOs1NoztA==,type:str]
|
||||||
|
coredns_zsk_private: ENC[AES256_GCM,data:BZxC5iYY8wh6kXbXpNheVrvBh6TB0968ivw06sJ1driytCt3RK6ZtNcr7PMxjxbKCdZKllLIdCABJDrj8eQCLLLMRspYVXLWquJvLHCCGqteY+Ha4b5bdsUO64I+HkDGagZSj7mIsaD7+aGOI2FN4bFhd364qGc1hGACePBUWlUi8AHSQY9KgadXBX2+ud6BIsoyoEN8AhjNK6zSADZYeA3JMJ1HTP1MbAB6owbmQMieunMHwudi6fi6eQ==,iv:lSjLOF6u1gLUv2IQ6rqG9nNCkFa6Yruknx0Lc+E6vzY=,tag:QQv1+47UtX+ZxrYwsrHLSg==,type:str]
|
||||||
|
sops:
|
||||||
|
age:
|
||||||
|
- recipient: age1yd59qp5km4cxt99rlfjehnsucrjn9lmj0su4h3avhf6vrtjvnyjqstldl5
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSUVZKYWFmSU5xbWo2N0Qy
|
||||||
|
c0VPeVIxQ1lNMTB5VkI4dVdsTXlkSEo3azN3CjJtR0lqclZOUjFkVjFWSDM2SGF1
|
||||||
|
OWNKcFNFSGpzd3A3MUZyS09CUWUyMlEKLS0tIG5VdmU1b0YwdWxUTXBzaGR2d2tR
|
||||||
|
UjI1K1pYaC9pTnNNcldXQ3B3endUTDgKHISJEvZrn6VO/voH2ORzeJbpVjpX8MxP
|
||||||
|
qGJ+G7sMM8trCOpkR3wjqSaZVU1hric+0R5oHiMKDLcb3hGN9mgJFA==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
- recipient: age15rhqdpwejyf3r6ww70qgv6hqmkpsqraakn26kc49wlauhaceaeqsmuwrdd
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5RzlaWXU3ZVo0Y0JiV0xk
|
||||||
|
RlBYR3RHdklSdVhoY0UzYmFxeGl3YkFITW5BCmhITk9DMjRmekFyd0hQbnRCWEY5
|
||||||
|
NFRlUkdRT2lyZzdxbFJxelAyY1ZLYm8KLS0tIEdWc0lOQWU1ZFdHQjVIeTFVdGpH
|
||||||
|
WWxTeW96TC9UWTRMOXdkNVNzd1Rra0kKzkam9SYaIbkSOE+g2FIzIV7nyDHHt+gk
|
||||||
|
Uvhb6dvKW3omRYlh5ln7xDRcHiX83rz/TfPh2GdtXs/J0Oxkeo7drg==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
- recipient: age1xekdrkjfu82hkxltydm72tllzgayyvfavvudeks3xjuujm5wt5hq6g55v0
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWWTc0ZUJrL0dmallFb2Nv
|
||||||
|
TVR0VU9ZdGpzK3FNdjlaR1pmc3RucGZLcXhrClhmWHNjQk5kZ1ZBT2xYek9JM0x1
|
||||||
|
M0krWE8yUVVZMmVCbk83TGhMSUoyVWsKLS0tIDRocWI3L1ZVMmppSU1rUytDTDJz
|
||||||
|
dm5SYXc0VTdWSE1hQ2JtczdUenF1RUkKNfLtmhzZBFqGJKT4MMMVdKG/MmqNqUgF
|
||||||
|
DFPkw2uLlPTW1Wws37ac9u53G7AfcE1zRHiIzGYz9O2Cqc+xKjRpDQ==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
lastmodified: "2026-07-10T13:18:54Z"
|
||||||
|
mac: ENC[AES256_GCM,data:La/qXs87taoMI4KT3Mnb/MMyVshP3c+sgS5jzSZSGhsT02pOsE3AvG8UFlgcz2gZvD0aTsm3P54B3bHjq61OCcYOKAcS9dJjqDwC91utRf3CL+1hezIJUUjXNMUMTwVbRmNPuP6gZMKsdqztkAqvCxRwqXQWkC10ds2IfzKCQD4=,iv:e/LTO8wzKDw2DP4b/HdSTakcz7lPkLGqcdeU6N5dDDA=,tag:ASw3jQj7mrcVrOdhjlbwng==,type:str]
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.12.2
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
shkeeper_api_key: ENC[AES256_GCM,data:kUymtefYJOllK5cxKHhD3HnXO/uo1w==,iv:OH9ZaQJZsFfo00BIl5MlRw1XBzHUmFi1FW54/J6O/8Q=,tag:HQJyuEoz5fsDi7iMq3PEzw==,type:str]
|
||||||
|
restic_password: ENC[AES256_GCM,data:OHQlxUpNnTqMQm6A/o3ID/3F91NpVKOrsFYdLqrtI4vp+6TWHR8=,iv:bBd+gfi78lgTaTv0QUWYGQWPsurrzx90VvOzA2d2euA=,tag:qsHSdAHx1auwdZCgn2O5Qw==,type:str]
|
||||||
|
b2_key_id: ENC[AES256_GCM,data:4B9rvg06baH6aNiT,iv:Qk1ToF3lMYLTrZdzpfaoGVzdiKYs492w9fzn4/TbNfQ=,tag:djxfqwgNZmRGqvteTX9G3w==,type:str]
|
||||||
|
b2_application_key: ENC[AES256_GCM,data:xGAbBqx+6ErK7sy3FR0yza6mJU5oc5uQQGcwKtcPp1Ef4XEVd8do/wN7,iv:CojcoykDRBfvN8yqMMrPJq9mtAwxyswqXXVBKPupxDs=,tag:BkfFHAGJvtCDUpFoxXVP3Q==,type:str]
|
||||||
|
stalwart_admin_hash: ENC[AES256_GCM,data:WqRfogOC+LrlSmg8/A4kLaUVJRdGgfrCnbuAzwyf4SMed37vsialN5QPR1IxiY02ueqz12dZ1VObittLMGxK+tUIIAwv3+F3uyKboDSyP9+wGAhBkcwTFTMjc6RMzaJ5VHW4OGaSvijiMQ==,iv:jvzoEI8evtFxuZ7W4P3nkQ0KwccVLj0j/s0s/MMXmIo=,tag:KopbV++Y/aABh1c8zfo1dw==,type:str]
|
||||||
|
wg_admin_password: ENC[AES256_GCM,data:X/+YJoNoFFU7P/2HCpTI,iv:FXDS3xuFB9jxjpQhsX2EKn0YFVzL23c2sZO6RqpKTiw=,tag:ccyQVzqNc1HEY0rxJSLlKw==,type:str]
|
||||||
|
virtualcam_license_signing_key: ENC[AES256_GCM,data:zk8YDER9d9mBOdXgEKKC6dubSMfz6UMMb758ZC9SvjkYIv+R2EVd+CJwL93MCs0l62lUDxoBDCgTLbKM55kcaThkkqfgZCYPrndXevYZ7kquNQEMkoYg/pyGAWhAQ9trvT5QXfBES9UDdYi6J9taCVKCofutiMEdoJM8olgUwKsP4i+fbt3idYkdWNGelastPnvTuUK/M3/dOKR5eheiy5oIntG+sECOz/2eKHJ8cU3ja18uAEwcpd9VojuOa0fvfui8AujQcMZNSMv8VsLGnoR854iX3g1MvHkTBSzv3qaDzRC54MueXYR4WOzm0o+yv6Otmw1n9trWtk+WLUimmiKxDdW6nSFaddeisWzy47+QYzrSsd7iovNYEIHnW4kpnT5W2g5lwaJwA0J0pBOqvV9gziv8oBNeEbLurBxi78KwHBqG,iv:svx+hC4tS26xqI05+yo6N2d3uHzO7ULM0e76dMbShjs=,tag:EJ9fjHEi1wUhLkxy8sexww==,type:str]
|
||||||
|
virtualcam_admin_token: ENC[AES256_GCM,data:JC5zSFBCoEapxv/Kcs4VXk3HAkiqwXVrCv1rlqFsXVx3LGspnQdzjOIahlM=,iv:GRVfMNGkwC3xXR00ww29rFbhTrpuViad+H05yDhR9BA=,tag:OBnxBD/8KU4mn17FFTcdng==,type:str]
|
||||||
|
git_ssh_key_b64: ENC[AES256_GCM,data:LaUcR005w9iCd/DifDhzLM7tU4AuTiFM802gMU1HMtvFN2iSffucaFJWxpKv2zFxDxz0QpA3XwM1M5yJC4K3YFMxPkPXcWLYN4yHcc1yXuoB3SseQXjcJnPMqZvJfVYhHep+6CVVlVDQs1Y+miBvbAhrvCEblwUu5LhD7yvoJLwTnEpT15WGDvqezzYKzNSyQGNRa8KPjsD/7gJN3RyUPzKo6GsrIp8d8eOTuCZwEcJVDKE0ACXs4V6cd+akqIWZkNPyilnMTuK5cqg2YuqSpwu9NncKED6SjmPiozPKKgTVaWhNCRJUTBfIw85fdXizvq7PNbNRxxBsscAWd0ixXiLqg1RTipfv85wL4Nru3r3qcQGAWmOpTHKW5fA9kR4ygdNxEVkOgQ70m9U0wev+vdIJUoRYj09Ikobej370NKppOP6+oeEDE7NpkFH7tzRk1oHtwoc1dwP9Kq1T8XdxttCKFyb1pAJ6gMdMThCfJlC9/Uu/WYbYVu5bCj6BAmrdrmhbPrsEOylBeC9j9RhyRrFEqLd8AZ01Zg8oKcj9WtCWEMf9Y4biv2xE43JbBGRvLAx3sH9MRplu2hN2W/bRxa/EPj9GHgbUjScDBkm/QVbMeV+QOgi09sCHDTeSX3BHdZ8ZLNjuNUwYnWZq4lPix0iYS5MufiixNdQmncPeHq0UuBtVifEl2Jmjq1COLyeAQe47AKQ5zDjT91xtBRZVZEVk0kGBYHtBEJ9ezPwckSw=,iv:cbIpwwhmyJVvvTMB95YBzt+RQLOji6yJh+SpcJc3Q1Y=,tag:t8WxU1IPDfkB8muTdBLUhg==,type:str]
|
||||||
|
virtualcam_smtp_pass: ENC[AES256_GCM,data:D5kDTQP9/YV5BjAWeTn0G2zKe+s=,iv:7dZJprrs0S2ECtbDQuvhUWsU0Xzpld5/XZqdbyefg0o=,tag:uWe811KAisBJ5M1EhJjrcQ==,type:str]
|
||||||
|
virtualcam_google_OAuth_secret: ENC[AES256_GCM,data:kPJIP4iH2vFo+VRojvhjzrQhDWidFq7kjC7ZaFcwJ7dNHlo=,iv:qKWClwSIRPU2Xz50bGYqU+eMR/iFxqnaKkb18eL3aMU=,tag:78jGQgL1myDBMiZP8GZ80g==,type:str]
|
||||||
|
sops:
|
||||||
|
kms: []
|
||||||
|
gcp_kms: []
|
||||||
|
azure_kv: []
|
||||||
|
hc_vault: []
|
||||||
|
age:
|
||||||
|
- recipient: age1yd59qp5km4cxt99rlfjehnsucrjn9lmj0su4h3avhf6vrtjvnyjqstldl5
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBycGNTU1cvbDFhMXRmb2xj
|
||||||
|
Sm83dEdRV2Nsc28vWGwvWDRoYUxnaC9BVWdBClh0a1lLMEhIa0NGVmNQL0NTT05y
|
||||||
|
Y1g1eU1jUEo3bFg5OWw2a1JNdkozanMKLS0tIFVXL1hueGcwQ2ZuMXg0alVxSEZM
|
||||||
|
eDhBZ1UweEJ1UlZJdzB0RDhiRG1MeDgKLm2QHJAi++C5C86DDrl7dM0MSpYu11mn
|
||||||
|
PhD9ElDJO9dFVCh+X+CSJwKEslR1aAraE2iQSHHgWxbMw7MB6AA9uQ==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
- recipient: age15rhqdpwejyf3r6ww70qgv6hqmkpsqraakn26kc49wlauhaceaeqsmuwrdd
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPdUQ3aVYxWElRc3ZBVENn
|
||||||
|
L0dud1ViVi9uZHJ6NXcxN0VVd2VlNGZhUXdnClZpRWhJcC9sYjBxVk1kdkdGZmVE
|
||||||
|
QS9LL2ZtNnJIdmhpa2NUTlJqQk5jSUkKLS0tIER2emlCbkJ1ZFdHdmdPNndZT09I
|
||||||
|
MFhYTUpJaUhDVmlrVzNocWhRZ0t1Q00KV1rgDAOoqlzEuO7xoo2ZYL20dF3f1pCj
|
||||||
|
bNllMkJ0u+hSZin6aUIRV31ExCYtgivDVD3Jx09PKwuYqkINU6ui8A==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
- recipient: age1xekdrkjfu82hkxltydm72tllzgayyvfavvudeks3xjuujm5wt5hq6g55v0
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuYWlFTTlHMWFaeGEvNGhz
|
||||||
|
YVhEaVdOWXAvNTZrMjhRUWo1WUtNRWlyTGd3CkkrYnFIYnJncDdQZzdPcWR2UDJB
|
||||||
|
TUw3ckF1eEdlTDA3SXVsdmVKMUpDamMKLS0tIGVQK2hsWUlwTUhzSG5keFROWEMz
|
||||||
|
MHJrVVpDYWdJNmxtUkozSzR4Nmt3R28KrhYi830HUFAPfg8WvPad7BAuNe1mYOWt
|
||||||
|
WEFIquuX/H/N+y/7uQcBDbvnBzyropE1hW8aNrxSKMeawvQZWNXkZA==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
lastmodified: "2026-08-19T10:30:19Z"
|
||||||
|
mac: ENC[AES256_GCM,data:JOWFguh2iE4+OyU00l+2gn7GWv2yej37OLLhVVO6Yb7Kv4vgk4NhZKJLqopteQK1o6dPeWbeIydHG1Qmef0Sm0NmCyi7i6BCTplVizf/puaGjpkU5YAs90+HiEa9cKlNQ5brfNH3YecJeN9dK+d7o8bMO/xPv3raqFMBBoVtgu4=,iv:A7kCOzyWwiVaXKVMKXQ4j86zz2f65wAjOtaVEuLa81I=,tag:bcbPsmRRFRHX7qi4WdYb0g==,type:str]
|
||||||
|
pgp: []
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.8.1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
disabled = ["empty_pattern", "repeated_keys"]
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{ config, pkgs, lib, ... }:
|
|
||||||
|
|
||||||
{
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
boot = {
|
boot = {
|
||||||
bootspec.enable = true;
|
|
||||||
|
|
||||||
initrd.systemd.enable = true;
|
initrd.systemd.enable = true;
|
||||||
|
|
||||||
supportedFilesystems = [ "ntfs" ];
|
supportedFilesystems = ["ntfs"];
|
||||||
|
|
||||||
kernelPackages = pkgs.linuxPackages_latest;
|
kernelPackages = pkgs.linuxPackages_latest;
|
||||||
|
|
||||||
|
|||||||
+17
-7
@@ -1,18 +1,28 @@
|
|||||||
{ lib, pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
{
|
||||||
boot.loader = {
|
lib,
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.boot.loader.limine;
|
||||||
|
in {
|
||||||
|
options.boot.loader.limine.bootMode = lib.mkOption {
|
||||||
|
type = lib.types.enum ["bios" "uefi"];
|
||||||
|
default = "uefi";
|
||||||
|
description = "Boot mode: BIOS (MBR) or UEFI";
|
||||||
|
};
|
||||||
|
|
||||||
|
config.boot.loader = {
|
||||||
limine = {
|
limine = {
|
||||||
enable = true;
|
enable = true;
|
||||||
efiSupport = true;
|
efiSupport = cfg.bootMode == "uefi";
|
||||||
biosSupport = true;
|
biosSupport = cfg.bootMode == "bios";
|
||||||
biosDevice = "/dev/nvme1n1";
|
biosDevice = lib.mkIf (cfg.bootMode == "bios") (lib.mkDefault "/dev/nvme1n1");
|
||||||
maxGenerations = 10;
|
maxGenerations = 10;
|
||||||
style.wallpapers = [
|
style.wallpapers = [
|
||||||
pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader.gnomeFilePath
|
pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader.gnomeFilePath
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
grub.enable = lib.mkForce false;
|
grub.enable = lib.mkForce false;
|
||||||
systemd-boot.enable = lib.mkForce false;
|
systemd-boot.enable = lib.mkForce false;
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-5
@@ -15,15 +15,16 @@ let
|
|||||||
|
|
||||||
# GUI-specific modules (display manager, GPU, pipewire)
|
# GUI-specific modules (display manager, GPU, pipewire)
|
||||||
gui = [
|
gui = [
|
||||||
./core/boot.nix # plymouth boot splash
|
./core/boot.nix # plymouth boot splash
|
||||||
./hardware/graphics.nix # GPU drivers
|
./hardware/graphics.nix # GPU drivers
|
||||||
./services/greetd.nix # display manager
|
./services/greetd.nix # display manager
|
||||||
./services/pipewire.nix # audio
|
./services/pipewire.nix # audio
|
||||||
];
|
];
|
||||||
|
|
||||||
# Laptop-specific modules (battery, bluetooth)
|
# Laptop-specific modules (battery, bluetooth, fingerprint)
|
||||||
laptop = [
|
laptop = [
|
||||||
./hardware/bluetooth.nix
|
./hardware/bluetooth.nix
|
||||||
|
./hardware/fingerprint.nix
|
||||||
./services/power.nix
|
./services/power.nix
|
||||||
];
|
];
|
||||||
in {
|
in {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
# Fingerprint scanner configuration
|
||||||
|
#
|
||||||
|
# Enable on laptops with a fingerprint reader:
|
||||||
|
# hardware.fingerprint.enable = true;
|
||||||
|
#
|
||||||
|
# For some sensors (Goodix, Elan, Synaptics), you may also need to set
|
||||||
|
# the TOD driver:
|
||||||
|
# hardware.fingerprint.todDriver = pkgs.libfprint-2-tod1-goodix;
|
||||||
|
#
|
||||||
|
# Reference: https://wiki.nixos.org/wiki/Fingerprint_scanner
|
||||||
|
let
|
||||||
|
cfg = config.hardware.fingerprint;
|
||||||
|
in {
|
||||||
|
options.hardware.fingerprint = {
|
||||||
|
enable = lib.mkEnableOption "fingerprint scanner support (fprintd)";
|
||||||
|
|
||||||
|
todDriver = lib.mkOption {
|
||||||
|
description = "Touch OEM Drivers (TOD) package to use for the fingerprint sensor";
|
||||||
|
type = lib.types.nullOr lib.types.package;
|
||||||
|
default = null;
|
||||||
|
example = lib.literalExpression "pkgs.libfprint-2-tod1-goodix";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.fprintd.enable = true;
|
||||||
|
|
||||||
|
services.fprintd.tod = lib.mkIf (cfg.todDriver != null) {
|
||||||
|
enable = true;
|
||||||
|
driver = cfg.todDriver;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
services.avahi = {
|
services.avahi = {
|
||||||
enable = true;
|
enable = true;
|
||||||
nssmdns4 = true;
|
nssmdns4 = true;
|
||||||
|
denyInterfaces = ["docker0" "veth*" "virbr*"];
|
||||||
publish = {
|
publish = {
|
||||||
enable = true;
|
enable = true;
|
||||||
domain = true;
|
domain = true;
|
||||||
|
|||||||
@@ -17,6 +17,11 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# MAC address randomization via wpa_supplicant
|
||||||
|
wireless.extraConfig = ''
|
||||||
|
mac_addr=1
|
||||||
|
'';
|
||||||
|
|
||||||
useDHCP = false;
|
useDHCP = false;
|
||||||
dhcpcd.enable = false;
|
dhcpcd.enable = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,6 +19,16 @@
|
|||||||
in {
|
in {
|
||||||
package = pkgs.lix;
|
package = pkgs.lix;
|
||||||
|
|
||||||
|
gc = {
|
||||||
|
automatic = true;
|
||||||
|
dates = "weekly";
|
||||||
|
options = "--delete-older-than 14d";
|
||||||
|
};
|
||||||
|
|
||||||
|
optimise = {
|
||||||
|
automatic = true;
|
||||||
|
};
|
||||||
|
|
||||||
# pin the registry to avoid downloading and evaling a new nixpkgs version every time
|
# pin the registry to avoid downloading and evaling a new nixpkgs version every time
|
||||||
registry = lib.mapAttrs (_: v: {flake = v;}) flakeInputs;
|
registry = lib.mapAttrs (_: v: {flake = v;}) flakeInputs;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ _: {
|
|||||||
permittedInsecurePackages = [
|
permittedInsecurePackages = [
|
||||||
"electron-38.7.1"
|
"electron-38.7.1"
|
||||||
];
|
];
|
||||||
|
android_sdk.accept_license = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ _: {
|
|||||||
programs = {
|
programs = {
|
||||||
dconf.enable = true;
|
dconf.enable = true;
|
||||||
seahorse.enable = true;
|
seahorse.enable = true;
|
||||||
|
nix-ld.enable = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-7
@@ -2,23 +2,35 @@
|
|||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}: {
|
}: let
|
||||||
|
# Patch termfilechooser .portal to add Niri to UseIn
|
||||||
|
termfilechooser-niri = pkgs.xdg-desktop-portal-termfilechooser.overrideAttrs (old: {
|
||||||
|
postFixup =
|
||||||
|
(old.postFixup or "")
|
||||||
|
+ ''
|
||||||
|
substituteInPlace $out/share/xdg-desktop-portal/portals/termfilechooser.portal \
|
||||||
|
--replace-fail "wlroots;" "wlroots;Niri;"
|
||||||
|
'';
|
||||||
|
});
|
||||||
|
in {
|
||||||
xdg.portal = {
|
xdg.portal = {
|
||||||
enable = true;
|
enable = true;
|
||||||
config = {
|
config = {
|
||||||
common = {
|
common = {
|
||||||
default = ["gnome" "gtk"];
|
default = ["wlr" "gtk"];
|
||||||
"org.freedesktop.impl.portal.ScreenCast" = "gnome";
|
"org.freedesktop.impl.portal.ScreenCast" = "wlr";
|
||||||
"org.freedesktop.impl.portal.Screenshot" = "gnome";
|
"org.freedesktop.impl.portal.Screenshot" = "wlr";
|
||||||
"org.freedesktop.impl.portal.RemoteDesktop" = "gnome";
|
"org.freedesktop.impl.portal.RemoteDesktop" = "wlr";
|
||||||
"org.freedesktop.impl.portal.Secret" = ["gnome-keyring"];
|
"org.freedesktop.impl.portal.Secret" = ["gnome-keyring"];
|
||||||
"org.freedesktop.impl.portal.OpenURI" = "gtk";
|
"org.freedesktop.impl.portal.OpenURI" = "gtk";
|
||||||
"org.freedesktop.impl.portal.OpenFile" = "gtk";
|
# FileChooser covers both OpenFile and SaveFile use-cases
|
||||||
|
"org.freedesktop.impl.portal.FileChooser" = "termfilechooser";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
extraPortals = [
|
extraPortals = [
|
||||||
pkgs.xdg-desktop-portal-gtk
|
pkgs.xdg-desktop-portal-gtk
|
||||||
pkgs.xdg-desktop-portal-gnome
|
pkgs.xdg-desktop-portal-wlr
|
||||||
|
termfilechooser-niri
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{pkgs, ...}: {
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}: {
|
|
||||||
services = {
|
services = {
|
||||||
printing = {
|
printing = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -5,13 +5,30 @@
|
|||||||
# provide location
|
# provide location
|
||||||
services.geoclue2 = {
|
services.geoclue2 = {
|
||||||
enable = true;
|
enable = true;
|
||||||
geoProviderUrl = "https://beacondb.net/v1/geolocate";
|
geoProviderUrl = "https://api.beacondb.net/v1/geolocate";
|
||||||
submissionUrl = "https://beacondb.net/v2/geosubmit";
|
submissionUrl = "https://api.beacondb.net/v2/geosubmit";
|
||||||
submissionNick = "geoclue";
|
submissionNick = "geoclue";
|
||||||
|
|
||||||
appConfig.gammastep = {
|
appConfig = {
|
||||||
isAllowed = true;
|
gammastep = {
|
||||||
isSystem = false;
|
isAllowed = true;
|
||||||
|
isSystem = false;
|
||||||
|
};
|
||||||
|
"xdg-desktop-portal" = {
|
||||||
|
isAllowed = true;
|
||||||
|
isSystem = true;
|
||||||
|
};
|
||||||
|
"gnome-weather" = {
|
||||||
|
isAllowed = true;
|
||||||
|
isSystem = true;
|
||||||
|
};
|
||||||
|
"gnome-clocks" = {
|
||||||
|
isAllowed = true;
|
||||||
|
isSystem = true;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Automatically update system timezone based on location
|
||||||
|
services.automatic-timezoned.enable = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user