118 Commits

Author SHA1 Message Date
jackos1998 a7ea91f529 docs: Document the boxes
Add a top-level `README.md` mapping the boxes and per-machine docs under
`docs/boxes/` (grouped `colony/`, `home/`, `misc/`), one file per host, VM and
container documenting role, services and networking with source pointers.

Also point `AGENTS.md` at the new docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 22:45:20 +01:00
jackos1998 90cc2d53f1 Update CI workflow to Ubuntu 26.04
CI / Check, build and cache nixfiles (push) Successful in 46m23s
2026-06-14 20:37:53 +01:00
jackos1998 b044504938 nixos/git: Update Gitea Actions runner
Bump runner labels to node 24 / Trixie and Ubuntu 26.04. The upstream
module now generates the runner config from the `settings` option and
wires `ExecStart` itself, so drop the hand-written config file and
`ExecStart` override.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:37:36 +01:00
jackos1998 98ccc23ef5 devshell: Add check-system and ssh-machine utilities
`check-system` evaluates a NixOS config without building it; `ssh-machine`
SSHs to a system or home by name, resolving the target and ssh options
from its `deploy-rs` node. Document both in `AGENTS.md`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:36:15 +01:00
jackos1998 d7e8ca52a0 Add AGENTS.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:31:59 +01:00
jackos1998 f1cc0fa25c nixos/estuary: Add Meta NL-ix peering
CI / Check, build and cache nixfiles (push) Has been cancelled
2026-06-14 03:15:28 +01:00
jackos1998 43828ad34c nixos/hass: Add West Wood integration
CI / Check, build and cache nixfiles (push) Has been cancelled
2026-06-14 03:08:33 +01:00
jackos1998 36d7e4a7e3 home-manager/gui: Add Claude status line 2026-06-14 03:08:00 +01:00
jackos1998 2f7d5d3908 nixos/castle: Add claude... 2026-06-14 00:24:05 +01:00
jackos1998 f3a0ca3f57 devshell: Allow deploy-multi without options
CI / Check, build and cache nixfiles (push) Has been cancelled
2026-06-13 22:11:23 +01:00
jackos1998 023b3f6605 nixos/tmproot: Add manpage cache 2026-06-13 21:53:29 +01:00
jackos1998 ecd3a46591 home-manager/gui: Add easyeffects
CI / Check, build and cache nixfiles (push) Failing after 1h52m48s
2026-06-13 20:42:12 +01:00
jackos1998 6bcca599e2 devshell: Fix json2nix warning 2026-06-13 20:36:46 +01:00
jackos1998 93529c578b "Release" 26.06 Irritating
CI / Check, build and cache nixfiles (push) Successful in 2h20m51s
2026-06-13 16:00:18 +01:00
jackos1998 bb32784962 nixos/jackflix: Update external port
CI / Check, build and cache nixfiles (push) Successful in 1h11m55s
2026-06-06 19:48:02 +01:00
jackos1998 6ffa35c414 nixos/chatterbox: Bump mautrix bridges
CI / Check, build and cache nixfiles (push) Successful in 1h14m35s
2026-06-01 20:19:51 +01:00
jackos1998 3cc5448410 nixos/user: Add to group disk
CI / Check, build and cache nixfiles (push) Successful in 1h4m36s
2026-04-18 21:50:36 +01:00
jackos1998 ee4178ee2d nixos/chatterbox: Update mautrix-whatsapp to 26.03
CI / Check, build and cache nixfiles (push) Successful in 1h7m26s
2026-04-12 18:36:06 +01:00
jackos1998 2bf18319c9 nixos/routing-common: Fix keepalived link-local addresses
CI / Check, build and cache nixfiles (push) Successful in 1h17m1s
2026-03-16 15:12:46 +00:00
jackos1998 a394b9124a nixos/routing-common: Fix Cloudflare 2026-03-16 13:37:16 +00:00
jackos1998 5bc48d33a3 nixos: Add tcpdump on all machines 2026-03-16 13:33:08 +00:00
jackos1998 365ef5d49d Update nixpkgs for terraria-server
CI / Check, build and cache nixfiles (push) Successful in 1h3m49s
2026-03-10 21:27:14 +00:00
jackos1998 0206d52fa2 nixos/netboot: Remove pinned iPXE
CI / Check, build and cache nixfiles (push) Successful in 1h15m47s
2026-03-08 14:36:07 +00:00
jackos1998 5526e07e65 Update harmonia
CI / Check, build and cache nixfiles (push) Failing after 2h25m26s
2026-03-07 17:09:50 +00:00
jackos1998 dde682390f nixos/castle: Add lan-lo
CI / Check, build and cache nixfiles (push) Failing after 6m27s
2026-03-04 21:30:53 +00:00
jackos1998 4ec59a64ce nixos/home/routing-common: Add DHCP pool for untrusted LAN 2026-03-03 20:15:45 +00:00
jackos1998 c9c788e261 nixos/estuary: Add john-valorant
CI / Check, build and cache nixfiles (push) Failing after 6m15s
2026-03-01 22:57:03 +00:00
jackos1998 21c24216b4 nixos/whale2: Update Graeme difficulty
CI / Check, build and cache nixfiles (push) Failing after 6m28s
2026-02-17 22:47:25 +00:00
jackos1998 2ecd350fcc nixos/whale2: Update graeme whitelist 2026-02-17 21:54:02 +00:00
jackos1998 3aa873d52a nixos/whale2: Add graeme
CI / Check, build and cache nixfiles (push) Failing after 6m31s
2026-02-16 22:49:22 +00:00
jackos1998 0a8ad18de8 nixos/jackflix: Add ffmpeg
CI / Check, build and cache nixfiles (push) Failing after 6m16s
2026-02-15 19:56:54 +00:00
jackos1998 b6b94fea5a Update nixpkgs-mine for Terraria 1.4.5.5
CI / Check, build and cache nixfiles (push) Failing after 6m18s
2026-02-11 22:16:07 +00:00
jackos1998 de9762b272 shill: Add Terraria server
CI / Check, build and cache nixfiles (push) Failing after 7m4s
2026-02-09 00:06:13 +00:00
jackos1998 8d1f4d51d0 nixos/deploy-rs: Fix systemd-util shellcheck 2026-02-08 21:17:00 +00:00
jackos1998 f1dc04ec4b Update inputs
CI / Check, build and cache nixfiles (push) Failing after 6m18s
2026-01-20 15:13:27 +00:00
jackos1998 7951c777cb nixos/sfh: Disable unifi
CI / Check, build and cache nixfiles (push) Successful in 1h13m56s
2025-12-06 21:59:34 +00:00
jackos1998 b031840f81 nixos/object: Fix harmonia
CI / Check, build and cache nixfiles (push) Failing after 3h13m40s
2025-12-06 15:39:47 +00:00
jackos1998 45861bef08 "Release" 25.11 Hooray 2025-12-06 15:18:36 +00:00
jackos1998 4433395125 nixos/chatterbox: Restore old WhatsApp display name template
CI / Check, build and cache nixfiles (push) Failing after 19s
2025-12-04 10:06:06 +00:00
jackos1998 3306aaab5e nixos/chatterbox: Upgrade mautrix bridges to v25.11 2025-12-04 10:06:06 +00:00
jackos1998 4480302c65 nixos/estuary: Remove old Frys-IX subnet
CI / Check, build and cache nixfiles (push) Failing after 21s
2025-12-01 22:41:13 +00:00
jackos1998 be7fb5a243 home-manager/common: Fix overlays
CI / Check, build and cache nixfiles (push) Successful in 2h39m0s
2025-10-06 10:54:12 +01:00
jackos1998 25758bae08 nixos/middleman: SNAT IPv6 to assigned address
CI / Check, build and cache nixfiles (push) Successful in 2h38m2s
2025-10-01 11:10:41 +01:00
jackos1998 7bfe9ad697 nixos/estuary: Add hillcrest WireGuard
CI / Check, build and cache nixfiles (push) Successful in 2h27m17s
2025-09-27 02:24:35 +01:00
jackos1998 7db5e18974 nixos/jackflix: copyparty: Move /pub to / and put stuff at /priv
CI / Check, build and cache nixfiles (push) Successful in 2h25m8s
2025-09-09 14:34:54 +01:00
jackos1998 20b7da74bf nixos/jackflix: Remove unnecessary insecure packages exception
CI / Check, build and cache nixfiles (push) Successful in 2h28m59s
2025-09-08 23:29:51 +01:00
jackos1998 adaf8b6a83 nixos/jackflix: Add copyparty 2025-09-08 23:28:31 +01:00
jackos1998 1f145334f3 nixos/britway: Disable override_local_dns for headscale
CI / Check, build and cache nixfiles (push) Failing after 12m38s
2025-09-08 21:29:20 +01:00
jackos1998 abf9f1b465 home-manager/gui: Add ffmpeg
CI / Check, build and cache nixfiles (push) Successful in 2h30m16s
2025-09-06 21:56:19 +01:00
jackos1998 f0740741a4 nixos/home/routing-common: Fix mstpd shellcheck
CI / Check, build and cache nixfiles (push) Successful in 2h44m21s
2025-09-06 21:40:05 +01:00
jackos1998 0c0b66b8db nixos/jackflix: Add FlareSolverr 2025-09-06 19:49:12 +01:00
jackos1998 bdf3c04595 nixos/git: Add NAT rules 2025-09-06 19:35:33 +01:00
jackos1998 02795a6ee4 nixos/nvme: Specify Host NQN on command line
CI / Check, build and cache nixfiles (push) Failing after 2h24m47s
2025-09-06 18:02:18 +01:00
jackos1998 8fa4a7ee60 "Release" 25.09 Giving
CI / Check, build and cache nixfiles (push) Failing after 31m46s
2025-09-06 17:14:09 +01:00
jackos1998 773674d879 nixos/chatterbox: Add adzerq to Instagram bridge
CI / Check, build and cache nixfiles (push) Failing after 29m47s
2025-07-23 19:42:35 +01:00
jackos1998 12c5ca126d nixos/middleman: kinkcraft Bluemap
CI / Check, build and cache nixfiles (push) Failing after 30m2s
2025-06-07 23:28:20 +01:00
jackos1998 b38a2a07e2 nixos/estuary: Update FrysIX BGP config to new /23
CI / Check, build and cache nixfiles (push) Failing after 30m16s
2025-06-03 11:06:58 +01:00
jackos1998 0dc474887f Add kinkcraft Minecraft server
CI / Check, build and cache nixfiles (push) Failing after 30m20s
2025-05-29 20:51:56 +01:00
jackos1998 c8bd63ec3e nixos: Add nixlight static IP and WLED hass integration
CI / Check, build and cache nixfiles (push) Failing after 29m45s
2025-05-26 23:25:05 +01:00
jackos1998 d7522f3f97 nixos/whale2: Op kev in kevcraft
CI / Check, build and cache nixfiles (push) Failing after 30m31s
2025-04-24 22:04:45 +01:00
jackos1998 58c76f822f home-manager/gui: Use tmux kill-session in brainrot screensavers
CI / Check, build and cache nixfiles (push) Failing after 29m19s
2025-04-14 13:27:01 +01:00
jackos1998 31bcde23b8 nixos/gui: Enable udisks2
CI / Check, build and cache nixfiles (push) Failing after 5m13s
2025-04-07 23:18:29 +01:00
jackos1998 fc2fa0666e nixos/middleman: Increase worker_processes
CI / Check, build and cache nixfiles (push) Failing after 5m10s
2025-03-28 16:42:54 +00:00
jackos1998 854cc48479 home-manager/gui: Add Brainrot story mode screensaver
CI / Check, build and cache nixfiles (push) Failing after 29m6s
2025-03-28 11:01:46 +00:00
jackos1998 85a4b124e5 pkgs: Remove own terminaltexteffects
CI / Check, build and cache nixfiles (push) Failing after 5m7s
2025-03-27 11:58:36 +00:00
jackos1998 f322f3ebac home-manager/gui: Longer and looping brainrot screensavers
CI / Check, build and cache nixfiles (push) Failing after 29m7s
2025-03-25 10:56:08 +00:00
jackos1998 bc74fb4968 home-manager/gui: Add brainrot screensavers
CI / Check, build and cache nixfiles (push) Failing after 30m15s
2025-03-24 15:09:46 +00:00
jackos1998 584abd4991 nixos/home/hass: Add USB webcam
CI / Check, build and cache nixfiles (push) Successful in 1h12m19s
2025-03-15 01:43:44 +00:00
jackos1998 05074a1fd9 nixos/home/hass: Basic Reolink camera setup
CI / Check, build and cache nixfiles (push) Has been cancelled
2025-03-15 01:07:12 +00:00
jackos1998 69060dfbff nixos/home/routing-common: Add static lease for hass-panel 2025-03-14 22:53:36 +00:00
jackos1998 8e288a9e2a nixos/home/hass: Include scenes.yaml
CI / Check, build and cache nixfiles (push) Successful in 1h17m11s
2025-03-14 17:48:18 +00:00
jackos1998 bb03b6fa76 nixos/home/hass: Add HEOS
CI / Check, build and cache nixfiles (push) Successful in 1h0m24s
2025-03-12 01:55:46 +00:00
jackos1998 fd92cfae6e nixos/home/hass: Include scripts.yaml
CI / Check, build and cache nixfiles (push) Successful in 1h1m12s
2025-03-11 14:35:10 +00:00
jackos1998 25267d09a2 nixos/home/hass: Add androidtv_remote and alarmo
CI / Check, build and cache nixfiles (push) Successful in 59m46s
2025-03-11 02:12:16 +00:00
jackos1998 f02f538ab2 nixos/home/routing-common: Add media DHCP reservations 2025-03-10 22:33:48 +00:00
jackos1998 d319657680 nixos/netboot: Use older iPXE with patch
CI / Check, build and cache nixfiles (push) Successful in 1h3m31s
2025-03-10 22:23:08 +00:00
jackos1998 dff5a4e6d8 nixos/home/hass: Add Irish Rail integration
CI / Check, build and cache nixfiles (push) Successful in 1h9m21s
2025-03-10 14:04:22 +00:00
jackos1998 2a8ced0fec nixos/home/routing-common: Add DNS blocklist
CI / Check, build and cache nixfiles (push) Successful in 1h6m52s
2025-03-10 10:46:21 +00:00
jackos1998 36c7096120 nixos/home/hass: Home Assistant CLI and automation fix
CI / Check, build and cache nixfiles (push) Successful in 2h47m3s
2025-03-10 01:28:14 +00:00
jackos1998 adfcf2f848 nixos/home/hass: Initial Home Assistant setup
CI / Check, build and cache nixfiles (push) Has been cancelled
2025-03-09 22:59:59 +00:00
jackos1998 a3870a4293 nixos/home/sfh: Introduce hass container
CI / Check, build and cache nixfiles (push) Has been cancelled
2025-03-09 20:07:28 +00:00
jackos1998 8f4b61fc2b Update inputs 2025-03-09 20:00:35 +00:00
jackos1998 44e3a3011a nixos/stream: Disable octoprint for now
CI / Check, build and cache nixfiles (push) Failing after 3m16s
2025-03-02 14:21:31 +00:00
jackos1998 45c972cca9 lib: Update public IPs 2025-03-02 13:40:22 +00:00
jackos1998 7bd5b8cbdf nixos/whale2: Add kevcraft
CI / Check, build and cache nixfiles (push) Failing after 2m33s
2025-02-18 17:15:03 +00:00
jackos1998 d1eb9cc981 nixos/toot: Add BlueSky PDS
CI / Check, build and cache nixfiles (push) Failing after 3m4s
2025-01-31 14:54:40 +00:00
jackos1998 7a2ebf6872 nixos: Add ADB stuff
CI / Check, build and cache nixfiles (push) Successful in 1h3m46s
2025-01-26 18:33:04 +00:00
jackos1998 72b8bd089c nixos/uk: Add WireGuard VPN for access
CI / Check, build and cache nixfiles (push) Successful in 1h15m33s
2025-01-22 19:19:03 +00:00
jackos1998 cff229f487 nixos: Add britway
CI / Check, build and cache nixfiles (push) Successful in 1h3m58s
2025-01-19 23:58:51 +00:00
jackos1998 f3ac3cd67f nixos/middleman: Add pubkey and HTTP access to p.nul.ie
CI / Check, build and cache nixfiles (push) Successful in 51m34s
2025-01-16 15:20:57 +00:00
jackos1998 820bb2de5b lib: River IP update
CI / Check, build and cache nixfiles (push) Successful in 1h4m34s
Installer / Build installer (push) Successful in 5m54s
2025-01-01 19:14:04 +00:00
jackos1998 7d3ad52a44 devshell: Add git config safe.directory for build-n-switch
CI / Check, build and cache nixfiles (push) Successful in 1h2m7s
2024-12-23 10:32:13 +00:00
jackos1998 2cdb98e898 nixos/common: Disable channels
CI / Check, build and cache nixfiles (push) Successful in 53m0s
2024-12-12 12:38:01 +00:00
jackos1998 b717b1ceb4 nixos/gui: Add /dev/player0 VID
CI / Check, build and cache nixfiles (push) Successful in 1h1m16s
2024-12-11 17:17:33 +00:00
jackos1998 f31ce61c2b Update borgthin
CI / Check, build and cache nixfiles (push) Successful in 2h31m34s
2024-11-30 19:31:58 +00:00
jackos1998 aec22942f7 Update latest Linux kernel to 6.12 2024-11-30 19:31:43 +00:00
jackos1998 fc8676c3bb devshell: Remove deprecated Nix command stuff 2024-11-30 19:19:23 +00:00
jackos1998 2915e42a1d ci: Group CI jobs
CI / Check, build and cache nixfiles (push) Failing after 33m39s
2024-11-30 18:05:22 +00:00
jackos1998 5783d3a51e Update nixpkgs-stable to 24.11 2024-11-30 17:45:59 +00:00
jackos1998 2fe94bba23 nixos/git: Add longer timeout for Gitea actions runner
CI / Check, build and cache Nix flake (push) Successful in 2h29m25s
2024-11-27 12:29:04 +00:00
jackos1998 4b42960d26 home-manager/gui: Update alacritty import setting to new section
CI / Check, build and cache Nix flake (push) Failing after 3h1m26s
2024-11-26 23:19:58 +00:00
jackos1998 56e9abf945 ci: Build and grab path for jobs in separate calls
CI / Check, build and cache Nix flake (push) Has been cancelled
The old build-n-parse seemed to output null sometimes.....
2024-11-26 22:45:19 +00:00
jackos1998 4e2c2f92f0 nixos/middleman: Remove config for Matrix sliding sync proxy
CI / Check, build and cache Nix flake (push) Failing after 6m29s
2024-11-26 22:15:53 +00:00
jackos1998 caa208b288 nixos/netboot: Use older version of iPXE for now
CI / Check, build and cache Nix flake (push) Failing after 6m33s
2024-11-26 22:01:42 +00:00
jackos1998 9e6f885c17 ci: Tweak log messages 2024-11-26 22:00:17 +00:00
jackos1998 d8ca87bfd8 pkgs: Remove glfw-wayland-minecraft
CI / Check, build and cache Nix flake (push) Failing after 6m15s
2024-11-26 21:23:50 +00:00
jackos1998 e9467e0cc7 ci: Build and cache CI jobs individually
CI / Check, build and cache Nix flake (push) Failing after 6m27s
2024-11-26 12:37:47 +00:00
jackos1998 6c98ef8944 Revert "nixos/home/routing-common: Move Tailscale to home routers"
CI / Check, build and cache Nix flake (push) Failing after 1h15m14s
This reverts commit 7c05b6158f.
2024-11-26 00:04:43 +00:00
jackos1998 18981e240b nixos/nvme: Update to libnvme v1.11.1 to fix LTS kernels 2024-11-25 23:58:15 +00:00
jackos1998 df7e5953eb Update nixpkgs-unstable (and other inputs)
CI / Check, build and cache Nix flake (push) Has been cancelled
2024-11-25 23:10:24 +00:00
jackos1998 71d1c3f9c2 Ensure borgbackup cache / config is persisted
CI / Check, build and cache Nix flake (push) Successful in 32m28s
2024-11-16 18:46:09 +00:00
jackos1998 1453a755c3 Add (now unused) Enshrouded server 2024-11-16 18:08:10 +00:00
jackos1998 970af805e9 home-manager/gui: Swap swaysome container binds
CI / Check, build and cache Nix flake (push) Successful in 23m18s
2024-11-08 11:47:35 +00:00
jackos1998 383e9a9b1e home-manager/gui: Add swaysome
CI / Check, build and cache Nix flake (push) Successful in 23m43s
2024-11-07 18:21:09 +00:00
jackos1998 26a16d0629 home-manager/gui: Disable ligatures in kitty
CI / Check, build and cache Nix flake (push) Successful in 29m30s
2024-11-06 13:16:56 +00:00
jackos1998 208de7654e home-manager/gui: Use rofi-wayland 2024-11-06 13:16:51 +00:00
jackos1998 f577e7d58a nixos/routing-common: Increase bandwidth for CAKE
CI / Check, build and cache Nix flake (push) Successful in 28m44s
2024-10-31 22:27:55 +00:00
jackos1998 6130ee73be nixos/tower: Add brightness keybinds
CI / Check, build and cache Nix flake (push) Successful in 29m59s
2024-10-26 20:24:27 +01:00
140 changed files with 5806 additions and 2617 deletions
+20 -12
View File
@@ -6,11 +6,11 @@ on:
jobs:
check:
name: Check, build and cache Nix flake
runs-on: ubuntu-22.04
name: Check, build and cache nixfiles
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v27
- uses: actions/checkout@v6
- uses: cachix/install-nix-action@v31
with:
# Gitea will supply a token in GITHUB_TOKEN, which this action will
# try to pass to Nix when downloading from GitHub
@@ -25,15 +25,23 @@ jobs:
extra-trusted-public-keys = nix-cache.nul.ie-1:BzH5yMfF4HbzY1C977XzOxoPhEc9Zbu39ftPkUbH+m4=
- name: Check flake
run: nix flake check
- name: Build the world
id: build
run: |
path=$(nix build --no-link .#ci.x86_64-linux --json | jq -r .[0].outputs.out)
echo "path=$path" >> "$GITHUB_OUTPUT"
run: nix flake check --no-build
- name: Push to cache
- name: Build (and cache) the world
id: build
env:
HARMONIA_SSH_KEY: ${{ secrets.HARMONIA_SSH_KEY }}
run: |
ci/push-to-cache.sh "${{ steps.build.outputs.path }}"
nix eval --json --apply "builtins.attrNames" .#ci.x86_64-linux | jq -cr '.[]' | while read job; do
echo "::group::Build $job"
nix build --no-link .#ci.x86_64-linux."$job"
echo "::endgroup::"
echo "::group::Cache $job"
ci/push-to-cache.sh "$(nix eval --raw .#ci.x86_64-linux."$job")"
echo "::endgroup::"
done
echo "Building and caching CI derivation"
nix build --no-link .#ciDrv.x86_64-linux
UPDATE_PROFILE=1 ci/push-to-cache.sh "$(nix eval --raw .#ciDrv.x86_64-linux)"
+118
View File
@@ -0,0 +1,118 @@
# AGENTS.md
This file provides guidance to coding agents when working with code in this repository.
## Overview
Personal Nix flake managing NixOS systems and home-manager configurations for a set of
machines ("boxes": servers, home machines, routers). It is built around a **custom module system** layered
on top of NixOS/home-manager, not the stock flake `nixosConfigurations` pattern.
## Commands
This repo provides a `numtide/devshell` (entered via `direnv` / `use flake`). The shell defines
named commands — prefer them over raw `nix` invocations. Run a bare command name with no args to
see its help, or browse `devshell/commands.nix` / `devshell/install.nix` / `devshell/vm-tasks.nix`.
Common ones:
- `fmt` — format Nix with `nixpkgs-fmt` (the canonical formatter here).
- `build-system <host> [nix args]` — build a NixOS system's `toplevel`.
- `build-n-switch <args>` — wraps `doas nixos-rebuild --flake .`.
- `build-home <name>` / `home-switch` — build / switch a home-manager config.
- `run-vm <host>` — build & boot a system as a dev VM (installs `.keys/dev.key` into the VM).
- `build-iso` / `build-kexec` / `build-netboot <host>` — alternate build outputs via `config.my.buildAs.*`.
- `check-system <host> [nix args]` — evaluate a system (catches eval errors without a full build).
**Prefer this over `build-system` to validate a config change** — evaluation surfaces module/option
errors quickly and cheaply; only do a full build when you specifically need the built artifact.
- `deploy <host>` and `deploy-multi <hosts...>` — deploy-rs deployment (uses `.keys/deploy.key`, `--skip-checks`).
Pass the flake-qualified node, e.g. `deploy .#git`. The deploy node name is **always** the system
name (`deploy-rs.nix` keys nodes directly off `nixos.systems` / `home-manager.homes`); a system is
only a deploy target when `config.my.deploy.enable` is true (defaults true; auto-disabled for dev
VMs and containers).
- `ssh-machine <name> [cmd]` — SSH to a NixOS system or home-manager config by name. Resolves the
target and ssh options (identity, port) from its deploy-rs node, so it needs `my.deploy.enable`
(same gate as `deploy`).
- `ragenix` — edit age secrets using `.keys/dev.key` as identity (see Secrets).
- `repl``nix repl .#`.
- `update-nixpkgs` / `update-home-manager` — bump pinned inputs.
Check everything (what CI runs): `nix flake check --no-build`.
CI builds each attr of `.#ci.x86_64-linux` (systems, homes, packages, shell) and pushes to the
Harmonia binary cache; see `.gitea/workflows/ci.yaml` and `ci/push-to-cache.sh`.
## Architecture
### The custom module system
`flake.nix` does **not** call `nixosSystem` per host directly. Instead it `evalModules` over
`./nixos`, `./home-manager`, `./deploy-rs.nix`, and the per-host files listed in the `configs`
list in `flake.nix`. That evaluation produces a top-level config (`self.nixfiles`) from which the
real flake outputs are derived:
- `nixos.systems.<name>``nixosConfigurations.<name>`
- `home-manager.homes.<name>``homeConfigurations.<name>`
- `nixos.modules` / `home-manager.modules``nixosModules` / `homeModules`
- `deploy-rs.rendered``deploy`
`nixos/default.nix` and `home-manager/default.nix` define the `systemOpts` / `homeOpts` submodules
and the `mkSystem` / `mkHome` functions that actually invoke `eval-config.nix` /
`homeManagerConfiguration`. **To add a new host:** create a box file that sets
`nixos.systems.<name> = { ... }`, then add its path to the `configs` list in `flake.nix`.
### Multiple nixpkgs channels
Four pkgs sets are threaded everywhere as `pkgsFlakes` / `pkgs'` (and `hmFlakes` for home-manager):
`unstable`, `stable`, `mine` (a personal nixpkgs fork), `mine-stable`. Each system/home picks its
channel via `nixpkgs` / `home-manager` / `hmNixpkgs` options (e.g. `nixpkgs = "mine-stable"`).
Modules receive `pkgs'` = an attrset of all channels for the current system.
### `lib.my` and the `my` option namespace
`lib/default.nix` extends `lib` with a `my` attrset (helpers like `mkOpt'`, `mkBoolOpt'`,
`mkDefault'`, `inlineModule'`, `mkDefaultSystemsPkgs`, `homeStateVersion`). It also pulls in:
- `lib.my.net` — network/CIDR helpers from the `libnetRepo` input. Used heavily for IP math.
- `lib.my.c` — shared constants from `lib/constants.nix` (UIDs/GIDs, kernel package selection,
nginx snippets, per-network domains/prefixes, etc.). Reuse these rather than hardcoding.
- `lib.my.dns` — DNS helpers (`lib/dns.nix`).
Custom modules add options under the `my.*` namespace (e.g. `my.secrets`, `my.build`,
`my.tmproot`, `my.server`). Use `mkOpt'`/`mkBoolOpt'` for option declarations to match style.
### Modules and module lists
Module sets are registered in `nixos/modules/_list.nix` and `home-manager/modules/_list.nix`
(name → path), which become `nixos.modules` / `home-manager.modules` and are applied to every
system/home. To add a shared module, drop the file in `nixos/modules/` (or `home-manager/modules/`)
and add an entry to the relevant `_list.nix`.
### Network assignments
Each system declares `assignments.<name>` (in its `nixos.systems.<host>` block) with IPv4/IPv6
addresses, gateways, domains, MTU, etc. These are aggregated into `allAssignments` (passed to every
module) and there is an assertion that fails on duplicate IPs. Host networking
(`networking.hostName`, `domain`) defaults from the `internal` assignment.
### Hosts / "boxes"
Per-host configs live under `nixos/boxes/<host>` (some are single `.nix` files, some directories
with nested VMs/containers under e.g. `colony/vms`). Many "systems" are VMs or containers managed
via the `vms` / `containers` modules and the `l2mesh` VXLAN module.
For a human-readable map of what is actually deployed (per-machine roles, services and networking),
see `README.md` and the per-machine docs under `docs/boxes/` (grouped `docs/boxes/colony/`,
`docs/boxes/home/`, `docs/boxes/misc/`). Keep these in sync when adding, removing or repurposing
a machine or service.
## Secrets
age-encrypted secrets in `secrets/`, managed with **ragenix**. Each module declares
`my.secrets.files.<name>` and `my.secrets.key` (the host pubkey to encrypt for). `secrets.nix`
(the ragenix rules file) is generated by reading every system's declared secrets and computing the
recipient key list (always including `.keys/dev.pub`). Edit secrets with the `ragenix` devshell
command, which supplies `.keys/dev.key` as the identity. The `.keys/` directory (dev + deploy
private keys) is required for editing secrets, deploying, and running dev VMs.
## Conventions
- Format with `nixpkgs-fmt` (`fmt`). 2-space indent, `inherit (...)` blocks at the top of `let`.
- Prefer `lib.my` helpers (`mkOpt'`, `mkBoolOpt'`, `mkDefault'`) and `lib.my.c` constants over
reimplementing.
- New shared functionality → a module in `*/modules/` + entry in `_list.nix`, options under `my.*`.
- New host → box file under `nixos/boxes/` + entry in the `configs` list in `flake.nix`.
- Custom packages live in `pkgs/` and are registered in `pkgs/default.nix`; the overlay is exposed
as `overlays.default`.
- In prose and commit messages, quote code-like identifiers (commands, options, paths, package and
attribute names) in backticks.
+80
View File
@@ -0,0 +1,80 @@
# nixfiles
Personal Nix flake managing every machine I run: hosted servers, home
infrastructure, routers, a remote site, VPSes and personal workstations. It is
built around a **custom module system** layered on top of NixOS and
home-manager rather than the stock per-host `nixosConfigurations` pattern.
For day-to-day commands and a deeper explanation of the module system,
conventions and secrets, see [`AGENTS.md`](AGENTS.md). This README is the map of
**what is actually deployed**; the per-machine details live under [`docs/boxes/`](docs/boxes).
> **Note:** This documentation (the README and everything under `docs/`) is a
> work in progress and was **agent-generated** from the repository. It may be
> incomplete or out of date — treat the Nix configuration as the source of truth.
## The boxes at a glance
Machines are grouped by deployment/location. Each group has its own directory
under `docs/boxes/` with a `README.md` overview and one file per machine.
| Group | What it is | Docs |
| --- | --- | --- |
| **colony** | Hosted dedicated server in Amsterdam (`ams1`). A VM host running the public-facing infrastructure: routing, web, git, media, object storage, chat, game servers. | [`docs/boxes/colony/`](docs/boxes/colony) |
| **home** | Home network: a VM host (`palace`), the home routers, storage, Home Assistant, and personal desktops. | [`docs/boxes/home/`](docs/boxes/home) |
| **misc** | Everything else: edge VPSes (`britway`, `britnet`), the remote `kelder` site, the `tower` workstation, and the installer image. | [`docs/boxes/misc/`](docs/boxes/misc) |
## The "big machine" pattern
The larger sites (`colony`, `home`) all follow the same shape:
```
physical host (VM host)
├── VM ── thing impractical to containerise (router, storage, podman host, …)
├── VM ── container host ──┬── NixOS container ── application
│ ├── NixOS container ──┬── application
│ │ ├── application
│ │ └── application
│ └── …
└── VM ── …
```
- A **physical host** (`colony`, `palace`) does little itself beyond running
**VMs** via the custom `my.vms` module (QEMU + systemd units, LVM-backed
disks).
- **VMs** exist for things that are impractical to put in a container — kernel
features, separate networking, podman/OCI workloads, foreign OSes.
- One VM is usually a **container host** (`shill` on colony, `sfh` on home; and
`kelder` directly). It runs **NixOS containers** via the custom `my.containers`
module (systemd-nspawn based, each with its own address on a bridge).
- **Most applications live in those NixOS containers.** A container isn't limited
to a single application — it commonly hosts a **group of related applications**
that belong together (e.g. `jackflix` runs Jellyfin, the *arr stack,
Transmission, PhotoPrism and copyparty; `object` runs MinIO, Harmonia, HedgeDoc
and wastebin). Each container is a first-class entry in `docs/boxes/`.
Networking between everything is largely defined by per-system `assignments`
(IPs/prefixes) plus an L2 VXLAN mesh (`my.vpns.l2`, AS211024) that ties the edge
routers together. See [`AGENTS.md`](AGENTS.md) for the mechanics.
## Repo layout
```
README.md <- you are here
nixos/
boxes/ per-machine configuration ("boxes")
colony/ colony host + its VMs (vms/) + shill's containers
home/ palace host + its VMs, plus stream, castle
britway/ britnet.nix, kelder/, tower/, installer.nix …
modules/ shared NixOS modules (my.* options); registered in _list.nix
home-manager/ home-manager modules + configs
lib/ lib.my helpers, constants (lib.my.c), net/dns helpers
pkgs/ custom packages (overlays.default)
secrets/ age-encrypted secrets (ragenix)
devshell/ devshell commands (build/deploy/check/ssh helpers)
docs/boxes/ per-machine documentation (colony/, home/, misc/)
```
A machine is wired into the flake by adding its box file to the `configs` list
in `flake.nix`. See [`AGENTS.md`](AGENTS.md#architecture) for how `evalModules`
turns these into `nixosConfigurations`, `homeConfigurations` and `deploy` nodes.
+6 -4
View File
@@ -22,8 +22,10 @@ path="$1"
echo "Pushing $path to cache..."
nix copy --no-check-sigs --to "$STORE_URI" "$path"
echo "Updating profile..."
remote_cmd nix-env -p "$REMOTE_STORE"/nix/var/nix/profiles/nixfiles --set "$path"
if [ -n "$UPDATE_PROFILE" ]; then
echo "Updating profile..."
remote_cmd nix-env -p "$REMOTE_STORE"/nix/var/nix/profiles/nixfiles --set "$path"
echo "Collecting garbage..."
remote_cmd nix-collect-garbage --delete-older-than 30d
echo "Collecting garbage..."
remote_cmd nix-collect-garbage --delete-older-than 60d
fi
+38 -16
View File
@@ -48,11 +48,30 @@ in
help = "Print the ed25519 pubkey for a host";
command = "${pkgs.openssh}/bin/ssh-keyscan -t ed25519 \"$1\" 2> /dev/null | awk '{ print $2 \" \" $3 }'";
}
{
name = "ssh-machine";
category = "utilities";
help = "SSH to a machine by NixOS system or home-manager config name";
command = ''
name="$1"
shift
# Run from the project root so deploy's relative identity path (.keys/deploy.key) resolves
cd "$PRJ_ROOT"
# deploy-rs node names are the system/home name, with `@` mangled to `-at-`
node="''${name//@/-at-}"
# Single eval: resolve `user@host` plus merged (global + node) deploy-rs ssh options
info="$(nix eval --raw .#deploy --apply 'd: let n = d.nodes."'"$node"'"; in "''${n.sshUser}@''${n.hostname} ''${builtins.concatStringsSep " " (d.sshOpts ++ n.sshOpts)}"')"
target="''${info%% *}"
opts="''${info#* }"
# shellcheck disable=SC2086
exec ${pkgs.openssh}/bin/ssh $opts "$target" "$@"
'';
}
{
name = "json2nix";
category = "utilities";
help = "Convert JSON to formatted Nix";
command = "nix eval --impure --expr 'builtins.fromJSON (builtins.readFile /dev/stdin)' | ${pkgs.nixfmt-rfc-style}/bin/nixfmt";
command = "nix eval --impure --expr 'builtins.fromJSON (builtins.readFile /dev/stdin)' | ${pkgs.nixfmt}/bin/nixfmt -";
}
{
@@ -73,11 +92,22 @@ in
help = "Build NixOS configuration";
command = ''nix build "''${@:2}" ".#nixosConfigurations.\"$1\".config.system.build.toplevel"'';
}
{
name = "check-system";
category = "utilities";
help = "Evaluate NixOS configuration (check validity without building)";
command = ''nix eval "''${@:2}" ".#nixosConfigurations.\"$1\".config.system.build.toplevel.drvPath"'';
}
{
name = "build-n-switch";
category = "tasks";
help = "Shortcut to nixos-rebuild for this flake";
command = ''doas nixos-rebuild --flake . "$@"'';
command = ''
# HACK: Upstream changes in Git + Nix makes this necessary
# https://github.com/NixOS/nix/issues/10202
doas git config --global --add safe.directory "$PWD"
doas nixos-rebuild --flake . "$@"
'';
}
{
name = "run-vm";
@@ -115,29 +145,17 @@ in
help = "Build home-manager configuration";
command = ''nix build "''${@:2}" ".#homeConfigurations.\"$1\".activationPackage"'';
}
{
name = "update-inputs";
category = "tasks";
help = "Update flake inputs";
command = ''
args=()
for f in "$@"; do
args+=(--update-input "$f")
done
nix flake lock "''${args[@]}"
'';
}
{
name = "update-nixpkgs";
category = "tasks";
help = "Update nixpkgs flake inputs";
command = ''update-inputs nixpkgs-{unstable,stable,mine,mine-stable}'';
command = ''nix flake update nixpkgs-{unstable,stable,mine,mine-stable}'';
}
{
name = "update-home-manager";
category = "tasks";
help = "Update home-manager flake inputs";
command = ''update-inputs home-manager-{unstable,stable}'';
command = ''nix flake update home-manager-{unstable,stable}'';
}
{
name = "update-installer";
@@ -151,7 +169,11 @@ in
help = "Deploy multiple flakes at once";
command = ''
for f in $@; do
if [ -n "''${O:-}" ]; then
deploy "$O" $f
else
deploy $f
fi
done
'';
}
+1 -1
View File
@@ -11,7 +11,7 @@ in
NIX_USER_CONF_FILES = toString (pkgs.writeText "nix.conf"
''
experimental-features = nix-command flakes ca-derivations repl-flake
experimental-features = nix-command flakes ca-derivations
connect-timeout = 5
fallback = true
${lib.my.c.nix.cache.conf}
+68
View File
@@ -0,0 +1,68 @@
# colony
The hosted dedicated server in Amsterdam (`ams1`). This is the public-facing
half of the boxes: almost everything reachable from the internet lives here.
- **Internal domain:** `ams1.int.nul.ie`
- **Public domain:** `nul.ie` (public services are published as `*.nul.ie`)
- **Source:** [`nixos/boxes/colony/`](../../../nixos/boxes/colony)
## Shape
`colony` is the physical VM host. It runs the VMs below; one of them (`shill`)
is itself a NixOS container host where most applications run.
```
colony (physical VM host)
├── estuary ── edge router / firewall / DNS / BGP
├── shill ──── NixOS container host ──┬── middleman (reverse proxy, ACME, SSO)
│ ├── colony-psql (shared PostgreSQL)
│ ├── vaultwarden (password manager)
│ ├── chatterbox (Matrix + bridges)
│ ├── toot (Mastodon)
│ ├── jackflix (media stack)
│ ├── object (MinIO, Nix cache, …)
│ ├── waffletail (Tailscale subnet router)
│ ├── qclk (clock service)
│ └── gam (game servers)
├── whale2 ─── podman/OCI host (game servers)
├── git ────── Gitea + Actions runner
├── mail ───── Debian VM running Mailcow (not NixOS — configured out of repo)
└── darts ──── third-party/customer VM (opaque to this repo)
```
## Machines
| Machine | Role | Docs |
| --- | --- | --- |
| `colony` | Physical VM host (AMD, LVM-thin, borgthin backups → rsync.net) | [colony.md](colony.md) |
| `estuary` | Edge router: WAN, firewall/NAT, DNS, BGP, IXP peering, WireGuard | [estuary.md](estuary.md) |
| `shill` | NixOS container host (see containers below) | [shill.md](shill.md) |
| `whale2` | podman/OCI host for game servers | [whale2.md](whale2.md) |
| `git` | Gitea + Gitea Actions runner | [git.md](git.md) |
### shill containers
| Container | Role | Docs |
| --- | --- | --- |
| `middleman` | Front-end nginx reverse proxy, ACME certs, nginx-sso, librespeed | [middleman.md](middleman.md) |
| `colony-psql` | Shared PostgreSQL (14) for colony services | [colony-psql.md](colony-psql.md) |
| `vaultwarden` | Vaultwarden (Bitwarden-compatible password manager) | [vaultwarden.md](vaultwarden.md) |
| `chatterbox` | Matrix homeserver + bridges (heisenbridge, mautrix-*) | [chatterbox.md](chatterbox.md) |
| `toot` | Bluesky PDS (Mastodon disabled) | [toot.md](toot.md) |
| `jackflix` | Media: Jellyfin, *arr stack, Transmission, PhotoPrism, copyparty | [jackflix.md](jackflix.md) |
| `object` | MinIO (S3), Harmonia (Nix cache), HedgeDoc, wastebin | [object.md](object.md) |
| `waffletail` | Tailscale subnet router (advertises colony prefixes into the tailnet) | [waffletail.md](waffletail.md) |
| `qclk` | `qclk` clock service (reachable over WireGuard) | [qclk.md](qclk.md) |
| `gam` | Game servers (Terraria, …) | [gam.md](gam.md) |
## Non-NixOS VMs
These run on `colony` but are **not** managed by this repo (no NixOS config). The
QEMU instances are still declared in `colony`'s `my.vms.instances`, and colony's
networking routes/firewalls traffic to them:
- **`mail`** — a Debian VM running [Mailcow](https://mailcow.email/) (`mail.nul.ie`).
ACME certs are pushed to it from `middleman` (see [middleman.md](middleman.md)).
- **`darts`** — a third-party/customer VM; opaque to this repo, given a routed
prefix and otherwise left alone.
+19
View File
@@ -0,0 +1,19 @@
# chatterbox
The Matrix homeserver (`nul.ie`) and its chat-network bridges.
- **Source:** [`shill/containers/chatterbox.nix`](../../../nixos/boxes/colony/vms/shill/containers/chatterbox.nix)
- **Host:** NixOS container on `shill`
## Role
- **Matrix homeserver** for `server_name = "nul.ie"`.
- **Bridges** to other chat networks:
- `heisenbridge` (IRC),
- `mautrix-whatsapp` (WhatsApp),
- `mautrix-meta` / `mautrix-messenger` (Facebook Messenger / Instagram).
- Fronted by `middleman` (federation on `:8448`).
## Networking
- `internal` assignment on the `ctrs` network (alt name `chatterbox-ctr`).
+18
View File
@@ -0,0 +1,18 @@
# colony-psql
The shared PostgreSQL instance for colony. Several other containers
(`middleman`, `chatterbox`, `toot`, `git`, …) connect here rather than each
running their own database.
- **Source:** [`shill/containers/colony-psql.nix`](../../../nixos/boxes/colony/vms/shill/containers/colony-psql.nix)
- **Host:** NixOS container on `shill`
## Role
- **PostgreSQL 14** serving the other colony services over the `ctrs` network.
Consumers wait for it to be ready via the `systemdAwaitPostgres` helper.
- netdata for monitoring.
## Networking
- `internal` assignment on the `ctrs` network (alt name `colony-psql-ctr`).
+39
View File
@@ -0,0 +1,39 @@
# colony (host)
The physical dedicated server in Amsterdam and the VM host for everything in
this group.
- **Source:** [`nixos/boxes/colony/default.nix`](../../../nixos/boxes/colony/default.nix)
(VM instances in [`nixos/boxes/colony/vms/default.nix`](../../../nixos/boxes/colony/vms/default.nix))
- **nixpkgs:** `mine-stable`
## Role
Bare-metal AMD host. It does little application work itself — its job is to run
the VMs and provide them with storage, networking and backups.
- **Virtualisation:** QEMU/KVM (`kvm-amd`, IOMMU on) via the `my.vms` module. VM
disks are LVM logical volumes (`vm-<name>-<disk>`) in the `main` volume group;
`estuary` additionally gets a WAN NIC by PCI passthrough.
- **Storage:** LVM-thin (`services.lvm.boot.thin`), `/persist` for state,
`/mnt/backup` for the local borg repo. `smartd` + `rasdaemon` for health.
- **Backups:** `my.borgthin` snapshots the persist/data LVs of the host and its
VMs into `/mnt/backup/main`, which is then `rsync`'d (along with LVM metadata)
to rsync.net (`zh2855.rsync.net`).
- **Monitoring:** netdata (with freeipmi), smartd.
## Networking
- Two bridges: `base` (the colony "base" network, shared with `estuary`) and
`vms` (the VM network). Dummy interfaces keep the bridges up so dependent VMs
can start.
- Default gateway / edge is `estuary`; `colony` itself holds the `routing` and
`internal` (a.k.a. `vm`) assignments and routes container/OCI/Tailscale
prefixes to `shill` and `whale2`.
- `my.firewall` trusts the `vms` interface and forwards customer prefixes
(`vm-mail`, `vm-darts`) through.
## VMs hosted here
`estuary`, `shill`, `whale2`, `git` (all NixOS, documented in this directory),
plus the non-NixOS `mail` and `darts` (see [README](README.md#non-nixos-vms)).
+41
View File
@@ -0,0 +1,41 @@
# estuary
The colony edge router and firewall — the machine that holds colony's public
IPs and connects everything else to the internet.
- **Source:** [`nixos/boxes/colony/vms/estuary/`](../../../nixos/boxes/colony/vms/estuary)
(`default.nix`, `bgp.nix`, `dns.nix`, `bandwidth.nix`)
- **nixpkgs:** `mine`
- **Host:** VM on `colony` (gets the WAN NIC by PCI passthrough)
## Role
- **Edge routing / firewall / NAT:** owns the colony public IPv4/IPv6
(`94.142.241.x` / `2a02:898:0:20::`), does NAT and port-forwarding for the
internal services (`my.firewall.nat.forwardPorts` driven by
`firewallForwards`). Forwards HTTP/S to `middleman`, git to `git`, game ports
to the OCI game servers on `whale2`, etc.
- **BGP:** runs BIRD2 ([`bgp.nix`](../../../nixos/boxes/colony/vms/estuary/bgp.nix))
announcing AS211024, over VLANs on the WAN link:
- peers at the IXPs **Frys-IX**, **NL-ix** and **FogIXP**;
- plus **iFog transit** (`ifog-transit`) — an upstream transit provider from
iFog, **not** an IXP.
- **DNS:** authoritative/recursive DNS ([`dns.nix`](../../../nixos/boxes/colony/vms/estuary/dns.nix)),
redirected to port 5353 locally.
- **VPNs:**
- Part of the AS211024 **L2 VXLAN mesh** (`my.vpns.l2`) with `river`, `stream`
and `britway`.
- WireGuard endpoints for the remote `kelder` site, `hillcrest`, and
`john-valorant`.
- **Misc:** iperf3 server. (A bandwidth-accounting script,
[`bandwidth.py`](../../../nixos/boxes/colony/vms/estuary/bandwidth.py), exists but
is **legacy and not currently used**.)
## Networking
- `wan` — the passed-through igb NIC (9000 MTU), carrying the upstream uplink and
tagged IXP VLANs (`ifog` 409 → `frys-ix`/`nl-ix`/`fogixp`/`ifog-transit`).
- `base` — colony base network; sends RAs and provides DNS to the base prefix,
routes the VM/container/OCI/Tailscale prefixes back to `colony`.
- `as211024` — the L2 mesh interface.
- Assignments: `internal` (public, alt name `fw`), `base`, `as211024`.
+17
View File
@@ -0,0 +1,17 @@
# gam
A game-server container (the lightweight counterpart to the OCI game servers on
`whale2`).
- **Source:** [`shill/containers/gam.nix`](../../../nixos/boxes/colony/vms/shill/containers/gam.nix)
- **Host:** NixOS container on `shill`
## Role
- Hosts game servers run directly as NixOS services — currently **Terraria**
(config/world from secrets). Exposed to the internet via `estuary`'s port
forwards (`:7777`).
## Networking
- `internal` assignment on the `ctrs` network (alt name `gam-ctr`).
+38
View File
@@ -0,0 +1,38 @@
# git
The Gitea VM — source hosting and CI for the boxes (`git.nul.ie`).
- **Source:** [`nixos/boxes/colony/vms/git/`](../../../nixos/boxes/colony/vms/git)
(`default.nix`, `gitea.nix`, `gitea-actions.nix`)
- **nixpkgs:** `mine`
- **Host:** VM on `colony`
## Role
- **Gitea** ([`gitea.nix`](../../../nixos/boxes/colony/vms/git/gitea.nix)) — the Git
forge (`git.nul.ie`). PostgreSQL-backed (the shared `colony-psql`), LFS
enabled, with object storage backed by MinIO on `object` (a MinIO secret is
spliced into `app.ini` at startup).
- **Gitea Actions runner**
([`gitea-actions.nix`](../../../nixos/boxes/colony/vms/git/gitea-actions.nix)) — a
Docker-mode runner (`main-docker`) using podman. Labels provide Debian/node-24
(Trixie) and Ubuntu 26.04 images; runner config comes from the upstream
module's `settings` option. The Actions cache lives on a dedicated disk
(`/var/cache/gitea-runner`). Runs as a fixed `gitea-runner` user (not
`DynamicUser`) so it can read its token.
- **nginx** — terminates TLS for `git.nul.ie` and proxies to Gitea on `:3000`.
ACME certs for `nul.ie` / `*.nul.ie` via the Cloudflare DNS challenge.
- **podman** — also hosts the OCI registry/build images; `/var/lib/containers`
is an XFS data disk.
## Networking
- `vms` interface with `routing` / `internal` assignments.
- HTTP/HTTPS forwarded in from `estuary`; podman default subnet `10.88.0.0/16` is
allowed to forward.
## CI
This runner is what executes the repo's own `.gitea/workflows/ci.yaml`, building
each `.#ci.x86_64-linux` attribute and pushing to the Harmonia binary cache. See
[`AGENTS.md`](../../../AGENTS.md#commands).
+25
View File
@@ -0,0 +1,25 @@
# jackflix
The media stack — acquisition, library and streaming.
- **Source:** [`shill/containers/jackflix/`](../../../nixos/boxes/colony/vms/shill/containers/jackflix)
(`default.nix`, `networking.nix`)
- **Host:** NixOS container on `shill`
## Role
- **Streaming:** Jellyfin.
- **Acquisition (*arr stack):** Transmission, Jackett, FlareSolverr, Radarr,
Sonarr, and Jellyseerr (`seerr`) for requests.
- **Photos:** PhotoPrism (`photos.nul.ie`).
- **File sharing:** copyparty (`:3923`) serving public + private media volumes.
- Media lives on the shared `/mnt/media` volume (bind-mounted read-write from
`shill`). Downloaders bind to a VPN interface
([`networking.nix`](../../../nixos/boxes/colony/vms/shill/containers/jackflix/networking.nix)),
so torrent traffic only flows while `systemd-networkd-wait-online@vpn` is up.
- A shared `media` group (gid 2000) gives the apps coordinated access.
## Networking
- `internal` assignment on the `ctrs` network (alt name `jackflix-ctr`), plus its
own VPN interface for the download clients.
+63
View File
@@ -0,0 +1,63 @@
# middleman
The front-end reverse proxy for all public colony web services — the single
ingress that `estuary` forwards HTTP/HTTPS to.
- **Source:** [`shill/containers/middleman/`](../../../nixos/boxes/colony/vms/shill/containers/middleman)
(`default.nix`, `vhosts.nix`)
- **Host:** NixOS container on `shill`
## Role
- **nginx** reverse proxy ([`vhosts.nix`](../../../nixos/boxes/colony/vms/shill/containers/middleman/vhosts.nix)
holds the per-service vhosts) with VTS stats, fancyindex, brotli, caching, and
a dynamic resolver pointed at `estuary` so upstreams can be re-resolved at
runtime. It is the single public ingress for almost every web service — colony,
home, and beyond.
- **ACME** — issues the wildcard certificates that **its own** vhosts are served
with (it is not a shared CA for the other boxes; `git`, `britway`, `kelder-spoder`, etc. each
run their own ACME):
- `nul.ie` / `*.nul.ie` (+ `*.s3.nul.ie`) via the Cloudflare DNS challenge,
- the internal `ams1.int.nul.ie` / `*` via an `exec` challenge that calls
`estuary`'s pdns over SSH.
- As a one-off consumer, it then pushes the public cert to the `mail` (Mailcow)
VM via `scp` + a remote `mailcow-ssl-reload`.
- **nginx-sso** — single-sign-on (`sso.nul.ie`) with Google OAuth and a simple
username/password provider; protects the SSO-gated vhosts below.
- **librespeed** — speed-test frontend + backend (`librespeed.${domain}` /
`speed.nul.ie`).
## Published vhosts
All under `*.nul.ie` with the wildcard cert unless noted. Upstreams are addressed
by their internal container/VM hostnames. "SSO" = gated behind nginx-sso.
| Host(s) | Upstream | Notes |
| --- | --- | --- |
| `nul.ie` (default `_`) | static | landing page (CV, SSH pubkey) + Matrix/atproto `.well-known` |
| `sso.nul.ie` | nginx-sso | SSO endpoint |
| `pass.nul.ie` | `vaultwarden` | password manager |
| `matrix.nul.ie` (+`:8448`) | `chatterbox` | Matrix client + federation |
| `element.nul.ie` | element-web | Matrix web client |
| `toot.nul.ie` | `toot` :80 | Mastodon (currently disabled — see [toot.md](toot.md)) |
| `pds.nul.ie` | `toot` :3000 | Bluesky PDS |
| `jackflix.nul.ie` | `jackflix` Jellyfin | streaming |
| `torrents` / `jackett` / `radarr` / `sonarr` `.nul.ie` | `jackflix` | *arr stack (**SSO**) |
| `gib.nul.ie` | `jackflix` Jellyseerr | requests |
| `photos.nul.ie` | `jackflix` PhotoPrism | |
| `stuff` / `public` / `p.nul.ie` | `jackflix` copyparty + `/mnt/media` | file sharing / index |
| `share.nul.ie` | `object` :9090 | |
| `minio` / `s3` / `*.s3.nul.ie` | `object` MinIO | S3 + console (Docker manifest MIME hack) |
| `nix-cache.nul.ie` | `object` Harmonia | Nix binary cache (immutable cache headers) |
| `md.nul.ie` / `pb.nul.ie` | `object` | HedgeDoc / wastebin |
| `mc-map` / `mc-rail` / `mc-map-kink` `.nul.ie` | `whale2` OCI | Minecraft maps |
| `netdata-colony.nul.ie` | many hosts :19999 | netdata fan-out (**SSO**) |
| `pront.nul.ie` | `stream-hi` (home) | print/webcam (**SSO**) |
| `hass.nul.ie` | `hass` (home) | Home Assistant |
| `hass-john.nul.ie` | `john-valorant-tun` | remote HASS over WireGuard tunnel |
## Networking
- `internal` assignment on the `ctrs` network; bind-mounts `/mnt/media` for
serving static/media content.
- nginx waits for `colony-psql` before starting (DNS bootstrap hack).
+24
View File
@@ -0,0 +1,24 @@
# object
Object storage and Nix binary cache, plus a couple of small self-hosted web
apps.
- **Source:** [`shill/containers/object.nix`](../../../nixos/boxes/colony/vms/shill/containers/object.nix)
- **Host:** NixOS container on `shill`
## Role
- **MinIO** — S3-compatible object storage (`s3.nul.ie` / `*.s3.nul.ie`). Backs
several other services (Gitea LFS/artifacts, social-media uploads, …). Stored on the
`/mnt/minio` volume (XFS, bind-mounted from `shill`).
- **Harmonia** — serves the Nix binary cache for all the boxes (`nix-cache.nul.ie`), backed
by the `/mnt/nix-cache` volume.
- **atticd** — an alternative Nix cache (stores into MinIO/S3). **Currently
disabled** — present in the config but not running.
- **HedgeDoc** — collaborative markdown notes.
- **wastebin** — pastebin.
## Networking
- `internal` assignment on the `ctrs` network (alt name `object-ctr`).
- `/mnt/minio` and `/mnt/nix-cache` bind-mounted read-write from `shill`.
+22
View File
@@ -0,0 +1,22 @@
# qclk
The `qclk` service container.
- **Source:** [`shill/containers/qclk/`](../../../nixos/boxes/colony/vms/shill/containers/qclk)
- **Host:** NixOS container on `shill`
## Role
- Runs the custom `qclk` service, exposing an API that is reached over a
dedicated WireGuard **`management`** network. Managed devices are configured as
WireGuard peers (each gets an address in the `qclk` prefix), and AS211024
trusted hosts are allowed to reach the API.
- `shill` routes the `qclk` prefix to this container.
## Networking
- `internal` assignment on the `ctrs` network (alt name `qclk-ctr`), plus the
`management` WireGuard interface carrying the `qclk` prefix.
> Check `qclk/default.nix` for the current peer list and exactly what the service
> does — this entry intentionally stays high-level.
+48
View File
@@ -0,0 +1,48 @@
# shill
The colony **NixOS container host**. Most colony applications run as
systemd-nspawn containers on `shill`.
- **Source:** [`nixos/boxes/colony/vms/shill/`](../../../nixos/boxes/colony/vms/shill)
(`default.nix`, `containers-ext.nix`, `hercules.nix`, `containers/`)
- **nixpkgs:** `mine`
- **Host:** VM on `colony` (large: 12 cores, 40 GiB RAM)
## Role
- Runs the colony NixOS containers via `my.containers.instances`, each attached
to the `ctrs` bridge with its own address.
- Provides shared data volumes to those containers via bind mounts from
LVM-backed disks: `/mnt/media` (→ `middleman`, `jackflix`), `/mnt/minio` and
`/mnt/nix-cache` (→ `object`).
- Acts as the router between the `vms` network and the `ctrs` container network
(sends RAs on `ctrs`, routes Tailscale prefixes via `waffletail` and the
`qclk` prefix via `qclk`). Includes an nftables `ct mark` hack to make
internal DNAT return paths work.
- Tuned sysctls for high connection counts / torrent traffic; netdata.
## Containers
Defined in [`shill/containers/`](../../../nixos/boxes/colony/vms/shill/containers)
and wired up in `shill`'s `my.containers.instances`:
| Container | Role |
| --- | --- |
| [`middleman`](middleman.md) | Front-end nginx reverse proxy, ACME, nginx-sso, librespeed |
| [`colony-psql`](colony-psql.md) | Shared PostgreSQL |
| [`vaultwarden`](vaultwarden.md) | Password manager |
| [`chatterbox`](chatterbox.md) | Matrix homeserver + bridges |
| [`toot`](toot.md) | Bluesky PDS (Mastodon disabled) |
| [`jackflix`](jackflix.md) | Media stack |
| [`object`](object.md) | MinIO / Harmonia / HedgeDoc / wastebin |
| [`waffletail`](waffletail.md) | Tailscale subnet router |
| [`qclk`](qclk.md) | Clock service |
| [`gam`](gam.md) | Game servers |
## Notes
- Container systems set `my.deploy.enable = false` (they are deployed as part of
`shill`'s container profiles, not as standalone deploy nodes) and render via
`my.asContainer`.
- `hercules.nix` configures Hercules CI agent bits;
`containers-ext.nix` holds extra per-container host wiring.
+19
View File
@@ -0,0 +1,19 @@
# toot
A federated-social container. Despite the name (Mastodon = "toots"), it currently
hosts a **Bluesky PDS**; the Mastodon instance is disabled.
- **Source:** [`shill/containers/toot.nix`](../../../nixos/boxes/colony/vms/shill/containers/toot.nix)
- **Host:** NixOS container on `shill`
## Role
- **Bluesky PDS** (Personal Data Server) — the active service, published as
`pds.nul.ie` (proxied by `middleman` to `:3000`).
- **Mastodon** — **currently disabled**. The config is still present and
`toot.nul.ie` still maps to `:80`, but the instance is not running. (It was
backed by the shared `colony-psql` and MinIO/S3 on `object`.)
## Networking
- `internal` assignment on the `ctrs` network (alt name `toot-ctr`).
+15
View File
@@ -0,0 +1,15 @@
# vaultwarden
[Vaultwarden](https://github.com/dani-garcia/vaultwarden), a Bitwarden-compatible
password manager.
- **Source:** [`shill/containers/vaultwarden.nix`](../../../nixos/boxes/colony/vms/shill/containers/vaultwarden.nix)
- **Host:** NixOS container on `shill`
## Role
- Runs Vaultwarden, fronted by `middleman` and published under `nul.ie`.
## Networking
- `internal` assignment on the `ctrs` network (alt name `vaultwarden-ctr`).
+19
View File
@@ -0,0 +1,19 @@
# waffletail
The colony Tailscale node / subnet router.
- **Source:** [`shill/containers/waffletail.nix`](../../../nixos/boxes/colony/vms/shill/containers/waffletail.nix)
- **Host:** NixOS container on `shill`
## Role
- Joins the Tailscale tailnet (auth key from secrets) and **advertises the colony
prefixes** into it, acting as the subnet router so tailnet clients can reach
colony services and vice-versa.
- nftables rules SNAT/forward between `host0` and `tailscale0` for the colony
v4/v6 ranges. `shill` routes the Tailscale prefixes here.
## Networking
- `internal` assignment on the `ctrs` network (alt name `waffletail-ctr`); owns
the `tailscale0` interface.
+27
View File
@@ -0,0 +1,27 @@
# whale2
A podman/OCI host on colony dedicated to game servers (kept off `shill` so the
container churn and resource use stay isolated).
- **Source:** [`nixos/boxes/colony/vms/whale2/`](../../../nixos/boxes/colony/vms/whale2)
(`default.nix`, `valheim.nix`, `minecraft/`, `enshrouded.nix`)
- **nixpkgs:** `mine`
- **Host:** VM on `colony`
## Role
- Runs OCI containers via **podman** (`virtualisation.oci-containers`, netavark
backend) on a dedicated `colony` bridge network (`oci`) with both IPv4 and
IPv6, so each game server gets its own routable address.
- Game servers configured in-repo: **Valheim**, **Minecraft** (several worlds —
see `extraAssignments`: `simpcraft`, `simpcraft-staging`, `kevcraft`,
`kinkcraft`, `graeme`), and **Enshrouded** (currently commented out).
- `/var/lib/containers` is an XFS data disk (project quotas).
## Networking
- `vms` interface with `routing` / `internal` (alt name `oci`) assignments.
- An `oci` bridge carrying the `prefixes.oci` v4/v6 ranges; per-game addresses
are handed out via `extraAssignments` (`valheim-oci`, `simpcraft-oci`, …) and
exposed to the internet through `estuary`'s port forwards.
- Firewall trusts the `oci` interface and forwards `vms → oci`.
+44
View File
@@ -0,0 +1,44 @@
# home
The home network. A VM host (`palace`), a redundant pair of routers, a storage
server, Home Assistant, and personal desktops.
- **Domain:** `h.nul.ie`
- **Source:** [`nixos/boxes/home/`](../../../nixos/boxes/home)
## Shape
```
palace (physical VM host)
├── river ──── home router (HA pair with stream)
├── cellar ── NVMe-oF storage server (SPDK)
└── sfh ───── NixOS container host ──┬── hass (Home Assistant)
└── unifi (UniFi controller — defined, currently disabled)
stream ── standalone home router (HA pair with river)
castle ── desktop workstation (boots its disks over NVMe-oF from cellar)
```
The two routers `river` (a VM on `palace`) and `stream` (standalone hardware)
share the [`routing-common`](../../../nixos/boxes/home/routing-common) config and
form a **keepalived/VRRP high-availability pair**: DHCP (kea), router
advertisements (radvd), DNS with blocklists, NAT, and the AS211024 L2 mesh link
back to colony.
## Machines
| Machine | Role | Docs |
| --- | --- | --- |
| `palace` | Physical VM host | [palace.md](palace.md) |
| `river` | Home router (VM; VRRP pair with `stream`) | [river.md](river.md) |
| `cellar` | NVMe-oF storage server (SPDK) | [cellar.md](cellar.md) |
| `sfh` | NixOS container host | [sfh.md](sfh.md) |
| `stream` | Home router (standalone hardware; VRRP pair with `river`) | [stream.md](stream.md) |
| `castle` | Desktop workstation | [castle.md](castle.md) |
### sfh containers
| Container | Role | Docs |
| --- | --- | --- |
| `hass` | Home Assistant | [hass.md](hass.md) |
| `unifi` | UniFi network controller (defined, **currently not imported**) | [unifi.md](unifi.md) |
+19
View File
@@ -0,0 +1,19 @@
# castle
A desktop workstation.
- **Source:** [`nixos/boxes/home/castle/default.nix`](../../../nixos/boxes/home/castle/default.nix)
## Role
- AMD desktop running the GUI environment (`my.gui.enable`, Sway / Wayland via
home-manager).
- **Diskless-style boot:** its `/nix`, `/persist` and `/home` are NVMe-oF volumes
served by [`cellar`](cellar.md) (`/dev/nvmeof/{nix,persist,home}`). The
networking is careful not to drop the IP used for the NVMe-oF connection.
- Uses the `my.nvme` module for the NVMe-oF client setup.
## Networking
- Sits on the home network; depends on the high-speed link to `cellar` for its
root storage.
+39
View File
@@ -0,0 +1,39 @@
# cellar
The home storage server. Exports fast NVMe storage over the network so other
machines (notably `castle`) can boot and run from it.
- **Source:** [`nixos/boxes/home/palace/vms/cellar/`](../../../nixos/boxes/home/palace/vms/cellar)
(`default.nix`, `spdk.nix`)
- **Host:** VM on `palace` (NVMe drives passed through)
- **Deploy address:** `192.168.68.80`
## Role
- Runs **SPDK** ([`spdk.nix`](../../../nixos/boxes/home/palace/vms/cellar/spdk.nix)) as
a userspace storage target. The kernel `nvme` driver is blacklisted so SPDK can
drive the NVMe devices directly (attached by PCI BDF).
- Builds a **RAID0** (`NVMeRaid`) across three NVMe drives, partitioned into three
namespaces, and exports each as an **NVMe-oF target over RDMA** (port 4420) on
the high-speed home network — one namespace per consumer:
| Namespace | NQN | Consumer |
| --- | --- | --- |
| `NVMeRaidp1` | `nqn.2016-06.io.spdk:river` | [`river`](river.md) |
| `NVMeRaidp2` | `nqn.2016-06.io.spdk:castle` | [`castle`](castle.md) |
| `NVMeRaidp3` | `nqn.2016-06.io.spdk:sfh` | [`sfh`](sfh.md) |
Each client is pinned by `hostnqn`, so `river`, `castle` and `sfh` all run their
storage off `cellar` over the network.
## Networking
- Exports on the high-speed home network (`lan-hi` / the `hi` assignment) over
RDMA; the SPDK target waits for that link to be online before starting.
## Notes
- The `ublk_*` calls in `my.spdk.debugCommands` are **only a debugging script**
they let you create a local ublk block device to mount and inspect the RAID on
`cellar` itself. They are **not** how storage is exported to clients; that is the
`nvmf` config above.
+18
View File
@@ -0,0 +1,18 @@
# hass
[Home Assistant](https://www.home-assistant.io/) — home automation.
- **Source:** [`sfh/containers/hass.nix`](../../../nixos/boxes/home/palace/vms/sfh/containers/hass.nix)
- **Host:** NixOS container on `sfh`
## Role
- Runs Home Assistant plus supporting services in the container. The `hass-cli`
is wired up against the local server for convenience.
- Integrations/automations are configured here (see commit history for things
like the "West Wood" integration).
## Networking
- `internal` assignment (alt name `hass-ctr`), plus a loopback assignment
(`hass-ctr-lo`) used internally.
+21
View File
@@ -0,0 +1,21 @@
# palace (host)
The physical VM host for the home network — the home equivalent of `colony`.
- **Source:** [`nixos/boxes/home/palace/default.nix`](../../../nixos/boxes/home/palace/default.nix)
(VM instances in [`palace/vms/default.nix`](../../../nixos/boxes/home/palace/vms/default.nix))
## Role
- Bare-metal host whose job is to run the home VMs via the `my.vms` module:
`river` (router), `cellar` (storage), `sfh` (container host).
- Provides the bridged networking those VMs sit on and passes through hardware
where needed (e.g. NVMe drives to `cellar`, NICs to `river`).
## VMs hosted here
| VM | Role | Docs |
| --- | --- | --- |
| `river` | Home router (VRRP pair with `stream`) | [river.md](river.md) |
| `cellar` | NVMe-oF storage server | [cellar.md](cellar.md) |
| `sfh` | NixOS container host (Home Assistant, …) | [sfh.md](sfh.md) |
+25
View File
@@ -0,0 +1,25 @@
# river
One of the two home routers. `river` is a VM on `palace`; it forms a
high-availability pair with the standalone `stream`.
- **Source:** [`nixos/boxes/home/palace/vms/river.nix`](../../../nixos/boxes/home/palace/vms/river.nix),
built from [`routing-common`](../../../nixos/boxes/home/routing-common) (instance `0`)
- **Host:** VM on `palace`
- **Deploy address:** `192.168.68.1`
## Role
Everything in [`routing-common`](../../../nixos/boxes/home/routing-common):
- **VRRP/keepalived** failover with `stream` (`keepalived.nix`) — one router is
master at a time, sharing virtual IPs.
- **DHCP** via kea (`kea.nix`), **router advertisements** via radvd
(`radvd.nix`).
- **DNS** (`dns.nix`) — local resolver with a blocklist
(`dns-blocklist.txt`) and a periodic update script.
- **NAT / firewall** for the home LAN, with policy routing.
- **AS211024 L2 mesh** link back to colony/`estuary` (and the other edge
routers), so home and colony networks interconnect.
See [stream.md](stream.md) for the other half of the pair.
+24
View File
@@ -0,0 +1,24 @@
# sfh
The home **NixOS container host** ("smart from home" / home services).
- **Source:** [`nixos/boxes/home/palace/vms/sfh/`](../../../nixos/boxes/home/palace/vms/sfh)
(`default.nix`, `containers/`)
- **Host:** VM on `palace`
## Role
- Runs the home NixOS containers via `my.containers.instances`, in the same way
`shill` does on colony.
- Sits on the home network and connects to NVMe-oF storage (`cellar`) where
needed.
## Containers
Defined in [`sfh/containers/`](../../../nixos/boxes/home/palace/vms/sfh/containers)
and imported from its `containers/default.nix`:
| Container | Role | Docs |
| --- | --- | --- |
| `hass` | Home Assistant | [hass.md](hass.md) |
| `unifi` | UniFi controller — **defined but currently commented out** of `containers/default.nix` | [unifi.md](unifi.md) |
+19
View File
@@ -0,0 +1,19 @@
# stream
One of the two home routers. `stream` is standalone hardware; it forms a
high-availability pair with `river` (a VM on `palace`).
- **Source:** [`nixos/boxes/home/stream.nix`](../../../nixos/boxes/home/stream.nix),
built from [`routing-common`](../../../nixos/boxes/home/routing-common) (instance `1`)
- **Deploy address:** `192.168.68.2`
## Role
- Same [`routing-common`](../../../nixos/boxes/home/routing-common) role as
[`river`](river.md): keepalived/VRRP, kea DHCP, radvd, DNS + blocklist, NAT and
the AS211024 L2 mesh link to colony.
- Additionally pulls in `mstpd` (`routing-common/mstpd.nix`) for spanning-tree on
its bridged ports — `stream` is the one wired into the physical switching, so
it manages the L2 topology.
See [river.md](river.md) for the other half of the pair.
+23
View File
@@ -0,0 +1,23 @@
# unifi
The UniFi network controller.
- **Source:** [`sfh/containers/unifi.nix`](../../../nixos/boxes/home/palace/vms/sfh/containers/unifi.nix)
- **Host:** NixOS container on `sfh`
## Status
> **Currently disabled.** The system is still defined (`nixos.systems.unifi`),
> but its import is commented out in
> [`sfh/containers/default.nix`](../../../nixos/boxes/home/palace/vms/sfh/containers/default.nix),
> so it is not deployed as a container right now. Re-enable by uncommenting
> `./unifi.nix` there.
## Role
- Runs the UniFi controller (`services.unifi`) to manage the home UniFi network
gear.
## Networking
- `internal` assignment (alt name `unifi-ctr`).
+21
View File
@@ -0,0 +1,21 @@
# misc
Everything that isn't part of the `colony` or `home` sites: the edge VPSes, the
remote `kelder` site, a workstation, and the installer image.
| Machine | What it is | Docs |
| --- | --- | --- |
| `britway` | Vultr VPS (London, `lon1`): Headscale, Tailscale exit node, BGP edge, nginx | [britway.md](britway.md) |
| `britnet` | VPS (Birmingham, `bhx1`): Tailscale/WireGuard gateway | [britnet.md](britnet.md) |
| `kelder` | Remote site host (`hentai.engineer`): NixOS container host | [kelder.md](kelder.md) |
| `tower` | Framework Laptop 13 (12th-gen Intel) workstation | [tower.md](tower.md) |
| `installer` | Custom NixOS installer image | [installer.md](installer.md) |
## kelder containers
`kelder` is itself a container host (like `shill`/`sfh`):
| Container | Role | Docs |
| --- | --- | --- |
| `kelder-acquisition` | Media stack (Jellyfin + *arr + Transmission) | [kelder-acquisition.md](kelder-acquisition.md) |
| `kelder-spoder` | nginx web host | [kelder-spoder.md](kelder-spoder.md) |
+21
View File
@@ -0,0 +1,21 @@
# britnet
A VPS in Birmingham (`bhx1`) acting as a Tailscale/WireGuard gateway node.
- **Source:** [`nixos/boxes/britnet.nix`](../../../nixos/boxes/britnet.nix)
- **Internal domain:** `bhx1.int.nul.ie`
## Role
- **Tailscale node** + **WireGuard** (`wg0`) gateway: provides a second egress /
entry point into the boxes' overlay networks.
- nftables SNATs traffic arriving on `tailscale0` / `wg0` out of the provider
interface (`veth0`), using the `allhost` assignment addresses.
## Networking
- Provider uplink with gateways `77.74.199.1` (v4) / `2a12:ab46:5344::1` (v6).
- `tailscale0` and `wg0` overlay interfaces; `allhost` assignment for SNAT.
> `britnet` is a separate machine from [`britway`](britway.md) — different
> provider/site, narrower role (gateway rather than control plane + BGP edge).
+27
View File
@@ -0,0 +1,27 @@
# britway
A Vultr VPS in London (`lon1`) acting as a network edge node: the Tailscale
control plane, an exit node, and a BGP speaker in the AS211024 mesh.
- **Source:** [`nixos/boxes/britway/`](../../../nixos/boxes/britway)
(`default.nix`, `bgp.nix`, `nginx.nix`, `tailscale.nix`)
- **Internal domain:** `lon1.int.nul.ie`
## Role
- **Headscale** ([`tailscale.nix`](../../../nixos/boxes/britway/tailscale.nix)) — the
self-hosted Tailscale control server (`hs.nul.ie`) the rest of the boxes log
into.
- **Tailscale node** — advertises itself as an **exit node** and advertises the
tailnet routes, so tailnet clients can egress / reach internal prefixes via
britway.
- **BGP** ([`bgp.nix`](../../../nixos/boxes/britway/bgp.nix)) — part of the AS211024
L2 VXLAN mesh (`my.vpns.l2`) alongside `estuary`, `river` and `stream`.
- **nginx** ([`nginx.nix`](../../../nixos/boxes/britway/nginx.nix)) — reverse proxy /
web front-end with ACME certs.
## Networking
- `vultr` assignment on the provider interface; `as211024` on the mesh.
- A `veth0`/`tailscale0` setup with SNAT so tailnet traffic egresses via the VPS
public IP.
+19
View File
@@ -0,0 +1,19 @@
# installer
The custom NixOS installer image used to bootstrap new boxes.
- **Source:** [`nixos/installer.nix`](../../../nixos/installer.nix)
## Role
- Defines `nixos.systems.installer`, a minimal system whose `my.buildAs.*`
outputs produce installable artifacts — primarily a bootable **ISO**
(`isoImage`), and the same base is reused for kexec/netboot trees.
- Build it with the devshell commands (see [`AGENTS.md`](../../../AGENTS.md#commands)):
- `build-iso installer`
- `build-kexec installer` / `build-netboot installer`
- A released ISO is what `colony`'s VM definitions reference as install media; the
`update-installer` devshell command tags a release to trigger a rebuild.
This is a build target rather than a deployed machine — there is no running
`installer` host.
+20
View File
@@ -0,0 +1,20 @@
# kelder-acquisition
The media stack for the `kelder` site — a slimmer cousin of colony's
[`jackflix`](../colony/jackflix.md).
- **Source:** [`kelder/containers/acquisition/`](../../../nixos/boxes/kelder/containers/acquisition)
(`default.nix`, `networking.nix`)
- **Host:** NixOS container on `kelder`
## Role
- **Jellyfin** for streaming (with hardware transcoding — the `jellyfin` user is
in the `render` group, `jellyfin-ffmpeg`).
- **Acquisition:** Transmission, Jackett, Radarr, Sonarr.
- Runs under the site's shared `kontent` user.
## Networking
- `internal` assignment (alt name `acquisition-ctr`) on the kelder container
network; download client networking in `networking.nix`.
+17
View File
@@ -0,0 +1,17 @@
# kelder-spoder
An nginx web host on the `kelder` site.
- **Source:** [`kelder/containers/spoder/`](../../../nixos/boxes/kelder/containers/spoder)
(`default.nix`, `nginx.nix`)
- **Host:** NixOS container on `kelder`
## Role
- Serves web content via **nginx** ([`nginx.nix`](../../../nixos/boxes/kelder/containers/spoder/nginx.nix)),
with ACME-managed certificates (nginx in the `acme` group, reloads on renewal).
- Runs under the site's shared `kontent` user.
## Networking
- `internal` assignment (alt name `spoder-ctr`) on the kelder container network.
+24
View File
@@ -0,0 +1,24 @@
# kelder
A host at a remote site ("kelder" = cellar/basement), linked back to the rest of
the other boxes over WireGuard. It is itself a **NixOS container host**.
- **Source:** [`nixos/boxes/kelder/`](../../../nixos/boxes/kelder)
(`default.nix`, `boot.nix`, `containers/`, `plymouth/`)
- **Domain:** `hentai.engineer`
## Role
- **Site uplink:** connects to colony's `estuary` over **WireGuard**
(`kelder` peer; see [estuary.md](../colony/estuary.md)), so the remote site is
reachable through the colony edge. A periodic `dns_update.py` keeps DNS current.
- **Container host:** runs NixOS containers via `my.containers.instances`
(`acquisition`, `spoder`).
- Custom boot/splash (`boot.nix`, Plymouth theme in `plymouth/`).
## Containers
| Container | Role | Docs |
| --- | --- | --- |
| `kelder-acquisition` | Media stack (Jellyfin + *arr + Transmission) | [kelder-acquisition.md](kelder-acquisition.md) |
| `kelder-spoder` | nginx web host | [kelder-spoder.md](kelder-spoder.md) |
+16
View File
@@ -0,0 +1,16 @@
# tower
A laptop workstation — a Framework Laptop 13 (12th-gen Intel).
- **Source:** [`nixos/boxes/tower/default.nix`](../../../nixos/boxes/tower/default.nix)
## Role
- Framework Laptop 13 (12th-gen Intel) running the GUI environment
(`my.gui.enable`) with home-manager on the `mine` channel.
- Joins the tailnet via the self-hosted Headscale on [`britway`](britway.md)
(`tailscale up --login-server=https://hs.nul.ie --accept-routes`).
- Local virtualisation enabled (`kvm-intel`, IOMMU on).
> Unlike [`castle`](../home/castle.md), `tower` lives outside the `home/` box
> tree and boots from local disks rather than NVMe-oF.
Generated
+343 -337
View File
File diff suppressed because it is too large Load Diff
+27 -13
View File
@@ -3,17 +3,22 @@
inputs = {
flake-utils.url = "github:numtide/flake-utils";
# libnet.url = "github:reo101/nix-lib-net";
libnetRepo = {
url = "github:oddlama/nixos-extra-modules";
flake = false;
};
devshell.url = "github:numtide/devshell";
devshell.inputs.nixpkgs.follows = "nixpkgs-unstable";
nixpkgs-unstable.url = "nixpkgs/nixos-unstable";
nixpkgs-stable.url = "nixpkgs/nixos-23.11";
nixpkgs-stable.url = "nixpkgs/nixos-26.05";
nixpkgs-mine.url = "github:devplayer0/nixpkgs/devplayer0";
nixpkgs-mine-stable.url = "github:devplayer0/nixpkgs/devplayer0-stable";
home-manager-unstable.url = "home-manager";
home-manager-unstable.inputs.nixpkgs.follows = "nixpkgs-unstable";
home-manager-stable.url = "home-manager/release-23.11";
home-manager-stable.url = "home-manager/release-26.05";
home-manager-stable.inputs.nixpkgs.follows = "nixpkgs-stable";
# Stuff used by the flake for build / deployment
@@ -25,19 +30,25 @@
# Stuff used by systems
impermanence.url = "github:nix-community/impermanence";
boardie.url = "git+https://git.nul.ie/dev/boardie";
impermanence.inputs.home-manager.follows = "home-manager-unstable";
boardie.url = "github:devplayer0/boardie";
boardie.inputs.nixpkgs.follows = "nixpkgs-unstable";
nixGL.url = "github:nix-community/nixGL";
nixGL.inputs.nixpkgs.follows = "nixpkgs-unstable";
harmonia.url = "github:nix-community/harmonia";
# harmonia.url = "github:devplayer0/harmonia/cache-config-daemon-store";
harmonia.inputs.nixpkgs.follows = "nixpkgs-unstable";
# Packages not in nixpkgs
sharry.url = "github:eikek/sharry";
sharry.inputs.nixpkgs.follows = "nixpkgs-unstable";
borgthin.url = "github:devplayer0/borg";
borgthin.inputs.nixpkgs.follows = "nixpkgs-mine";
attic.url = "github:zhaofengli/attic";
attic.inputs.nixpkgs.follows = "nixpkgs-unstable";
attic.inputs.nixpkgs-stable.follows = "nixpkgs-stable";
# TODO: Update borgthin so this works
# borgthin.inputs.nixpkgs.follows = "nixpkgs-mine";
copyparty.url = "github:9001/copyparty";
copyparty.inputs.nixpkgs.follows = "nixpkgs-unstable";
hass-west-wood.url = "github:devplayer0/hass-west-wood";
hass-west-wood.inputs.nixpkgs.follows = "nixpkgs-unstable";
};
outputs =
@@ -60,7 +71,7 @@
# Extend a lib with extras that _must not_ internally reference private nixpkgs. flake-utils doesn't, but many
# other flakes (e.g. home-manager) probably do internally.
libOverlay = final: prev: {
my = import ./lib { lib = final; };
my = import ./lib { inherit inputs; lib = final; };
flake = flake-utils.lib;
};
pkgsLibOverlay = final: prev: { lib = prev.lib.extend libOverlay; };
@@ -91,12 +102,12 @@
(_: path: mkDefaultSystemsPkgs path (system: {
overlays = [
pkgsLibOverlay
myPkgsOverlay
inputs.devshell.overlays.default
inputs.ragenix.overlays.default
inputs.deploy-rs.overlay
inputs.deploy-rs.overlays.default
(flakePackageOverlay inputs.home-manager-unstable system)
inputs.attic.overlays.default
];
}))
pkgsFlakes;
@@ -106,6 +117,7 @@
(_: path: mkDefaultSystemsPkgs path (_: {
overlays = [
pkgsLibOverlay
myPkgsOverlay
];
@@ -130,6 +142,7 @@
nixos/boxes/home/palace
nixos/boxes/home/castle
nixos/boxes/britway
nixos/boxes/britnet.nix
nixos/boxes/kelder
# Homes
@@ -160,7 +173,7 @@
# Platform independent stuff
{
nixpkgs = pkgs';
inherit lib nixfiles;
inherit inputs lib nixfiles;
overlays.default = myPkgsOverlay;
@@ -208,8 +221,9 @@
systems' = mapAttrs' (n: v: nameValuePair "system-${n}" v) systems;
packages' = mapAttrs' (n: v: nameValuePair "package-${n}" v) packages;
in
pkgs.linkFarm "ci" (homes' // systems' // packages' // {
homes' // systems' // packages' // {
inherit shell;
});
};
ciDrv = pkgs.linkFarm "ci" ci;
}));
}
+21 -21
View File
@@ -66,7 +66,7 @@ in
lsd = {
enable = mkDefault true;
enableAliases = mkDefault true;
enableFishIntegration = mkDefault true;
};
starship = {
@@ -132,38 +132,38 @@ in
ssh = {
enable = mkDefault true;
matchBlocks = {
enableDefaultConfig = false;
settings = {
nix-dev-vm = {
user = "dev";
hostname = "localhost";
port = 2222;
extraOptions = {
User = "dev";
HostName = "localhost";
Port = 2222;
StrictHostKeyChecking = "no";
UserKnownHostsFile = "/dev/null";
};
};
"rsync.net" = {
host = "rsyncnet";
user = "16413";
hostname = "ch-s010.rsync";
Host = "rsyncnet";
User = "16413";
HostName = "ch-s010.rsync";
};
shoe = {
host = "shoe.netsoc.tcd.ie shoe";
user = "netsoc";
Host = "shoe.netsoc.tcd.ie shoe";
User = "netsoc";
};
netsocBoxes = {
host = "cube spoon napalm gandalf saruman";
user = "root";
Host = "cube spoon napalm gandalf saruman";
User = "root";
};
"*" = {
IdentityFile = [
"~/.ssh/id_rsa"
"~/.ssh/borg"
];
};
};
extraConfig =
''
IdentityFile ~/.ssh/id_rsa
IdentityFile ~/.ssh/netsoc
IdentityFile ~/.ssh/borg
'';
};
direnv = {
@@ -226,7 +226,7 @@ in
# Note: If globalPkgs mode is on, then these will be overridden by the NixOS equivalents of these options
nixpkgs = {
overlays = [
inputs.deploy-rs.overlay
inputs.deploy-rs.overlays.default
inputs.boardie.overlays.default
inputs.nixGL.overlays.default
];
+520
View File
@@ -0,0 +1,520 @@
#!/usr/bin/env bash
# Source: https://github.com/daniel3303/ClaudeCodeStatusLine
# Single line: Model | tokens | %used | %remain | think | 5h bar @reset | 7d bar @reset | extra
set -f # disable globbing
VERSION="1.4.4"
input=$(cat)
if [ -z "$input" ]; then
printf "Claude"
exit 0
fi
# ANSI colors matching oh-my-posh theme
blue='\033[38;2;0;153;255m'
orange='\033[38;2;255;176;85m'
green='\033[38;2;0;160;0m'
cyan='\033[38;2;46;149;153m'
red='\033[38;2;255;85;85m'
yellow='\033[38;2;230;200;0m'
purple='\033[38;2;167;139;250m'
white='\033[38;2;220;220;220m'
dim='\033[2m'
reset='\033[0m'
# Format token counts (e.g., 50k / 200k)
format_tokens() {
local num=$1
if [ "$num" -ge 1000000 ]; then
awk "BEGIN {v=sprintf(\"%.1f\",$num/1000000)+0; if(v==int(v)) printf \"%dm\",v; else printf \"%.1fm\",v}"
elif [ "$num" -ge 1000 ]; then
awk "BEGIN {printf \"%.0fk\", $num / 1000}"
else
printf "%d" "$num"
fi
}
# Format number with commas (e.g., 134,938)
format_commas() {
printf "%'d" "$1"
}
# Return color escape based on usage percentage
# Usage: usage_color <pct>
usage_color() {
local pct=$1
if [ "$pct" -ge 90 ]; then echo "$red"
elif [ "$pct" -ge 70 ]; then echo "$orange"
elif [ "$pct" -ge 50 ]; then echo "$yellow"
else echo "$green"
fi
}
# Resolve config directory: CLAUDE_CONFIG_DIR (set by alias) or default ~/.claude
claude_config_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
# Return 0 (true) if $1 > $2 using semantic versioning
version_gt() {
local a="${1#v}" b="${2#v}"
local IFS='.'
read -r a1 a2 a3 <<< "$a"
read -r b1 b2 b3 <<< "$b"
a1=${a1:-0}; a2=${a2:-0}; a3=${a3:-0}
b1=${b1:-0}; b2=${b2:-0}; b3=${b3:-0}
[ "$a1" -gt "$b1" ] 2>/dev/null && return 0
[ "$a1" -lt "$b1" ] 2>/dev/null && return 1
[ "$a2" -gt "$b2" ] 2>/dev/null && return 0
[ "$a2" -lt "$b2" ] 2>/dev/null && return 1
[ "$a3" -gt "$b3" ] 2>/dev/null && return 0
return 1
}
# ===== Extract data from JSON =====
model_name=$(echo "$input" | jq -r '.model.display_name // "Claude"')
model_name=$(echo "$model_name" | sed 's/ *(\([0-9.]*[kKmM]*\) context)/ \1/') # "(1M context)" → "1M"
# Context window
size=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
[ "$size" -eq 0 ] 2>/dev/null && size=200000
# Token usage
input_tokens=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0')
cache_create=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')
cache_read=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')
current=$(( input_tokens + cache_create + cache_read ))
used_tokens=$(format_tokens $current)
total_tokens=$(format_tokens $size)
if [ "$size" -gt 0 ]; then
pct_used=$(( current * 100 / size ))
else
pct_used=0
fi
pct_remain=$(( 100 - pct_used ))
used_comma=$(format_commas $current)
remain_comma=$(format_commas $(( size - current )))
settings_path="$claude_config_dir/settings.json"
effort_level=""
stdin_effort=$(echo "$input" | jq -r '.effort.level // empty' 2>/dev/null)
if [ -n "$stdin_effort" ]; then
effort_level="$stdin_effort"
elif [ -n "$CLAUDE_CODE_EFFORT_LEVEL" ]; then
effort_level="$CLAUDE_CODE_EFFORT_LEVEL"
elif [ -f "$settings_path" ]; then
effort_val=$(jq -r '.effortLevel // empty' "$settings_path" 2>/dev/null)
[ -n "$effort_val" ] && effort_level="$effort_val"
fi
[ -z "$effort_level" ] && effort_level="medium"
# ===== Claude CLI version (cached, 1h TTL) =====
cli_version_cache="/tmp/claude/statusline-cli-version"
cli_version=""
cli_version_max_age=3600
if [ -f "$cli_version_cache" ]; then
cv_mtime=$(stat -c %Y "$cli_version_cache" 2>/dev/null || stat -f %m "$cli_version_cache" 2>/dev/null)
cv_now=$(date +%s)
cv_age=$(( cv_now - cv_mtime ))
if [ "$cv_age" -lt "$cli_version_max_age" ]; then
cli_version=$(cat "$cli_version_cache" 2>/dev/null)
fi
fi
if [ -z "$cli_version" ]; then
cli_version=$(claude --version 2>/dev/null | awk '{print $1}')
if [ -n "$cli_version" ]; then
mkdir -p /tmp/claude 2>/dev/null
echo "$cli_version" > "$cli_version_cache"
fi
fi
# ===== Build single-line output =====
out=""
out+="${blue}${model_name}${reset}"
# Current working directory
cwd=$(echo "$input" | jq -r '.cwd // empty')
if [ -n "$cwd" ]; then
display_dir="${cwd##*/}"
git_branch=$(git -C "${cwd}" rev-parse --abbrev-ref HEAD 2>/dev/null)
out+=" ${dim}|${reset} "
out+="${cyan}${display_dir}${reset}"
if [ -n "$git_branch" ]; then
out+="${dim}@${reset}${green}${git_branch}${reset}"
git_stat=$(git -C "${cwd}" diff --numstat 2>/dev/null | awk '{a+=$1; d+=$2} END {if (a+d>0) printf "+%d -%d", a, d}')
[ -n "$git_stat" ] && out+=" ${dim}(${reset}${green}${git_stat%% *}${reset} ${red}${git_stat##* }${reset}${dim})${reset}"
fi
fi
out+=" ${dim}|${reset} "
out+="${orange}${used_tokens}/${total_tokens}${reset} ${dim}(${reset}${green}${pct_used}%${reset}${dim})${reset}"
out+=" ${dim}|${reset} "
out+="effort: "
case "$effort_level" in
low) out+="${dim}${effort_level}${reset}" ;;
medium) out+="${orange}med${reset}" ;;
high) out+="${green}${effort_level}${reset}" ;;
xhigh) out+="${purple}${effort_level}${reset}" ;;
max) out+="${red}${effort_level}${reset}" ;;
*) out+="${green}${effort_level}${reset}" ;;
esac
# ===== Cross-platform OAuth token resolution (from statusline.sh) =====
# Tries credential sources in order: env var → macOS Keychain → Linux creds file → GNOME Keyring
get_oauth_token() {
local token=""
# 1. Explicit env var override
if [ -n "$CLAUDE_CODE_OAUTH_TOKEN" ]; then
echo "$CLAUDE_CODE_OAUTH_TOKEN"
return 0
fi
# 2. macOS Keychain (Claude Code appends a SHA256 hash of CLAUDE_CONFIG_DIR to the service name)
if command -v security >/dev/null 2>&1; then
local keychain_svc="Claude Code-credentials"
if [ -n "$CLAUDE_CONFIG_DIR" ]; then
local dir_hash
dir_hash=$(echo -n "$CLAUDE_CONFIG_DIR" | shasum -a 256 | cut -c1-8)
keychain_svc="Claude Code-credentials-${dir_hash}"
fi
local blob
blob=$(security find-generic-password -s "$keychain_svc" -w 2>/dev/null)
if [ -n "$blob" ]; then
token=$(echo "$blob" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null)
if [ -n "$token" ] && [ "$token" != "null" ]; then
echo "$token"
return 0
fi
fi
fi
# 3. Linux credentials file
local creds_file="${claude_config_dir}/.credentials.json"
if [ -f "$creds_file" ]; then
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$creds_file" 2>/dev/null)
if [ -n "$token" ] && [ "$token" != "null" ]; then
echo "$token"
return 0
fi
fi
# 4. GNOME Keyring via secret-tool
if command -v secret-tool >/dev/null 2>&1; then
local blob
blob=$(timeout 2 secret-tool lookup service "Claude Code-credentials" 2>/dev/null)
if [ -n "$blob" ]; then
token=$(echo "$blob" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null)
if [ -n "$token" ] && [ "$token" != "null" ]; then
echo "$token"
return 0
fi
fi
fi
echo ""
}
# ===== LINE 2 & 3: Usage limits with progress bars =====
# First, try to use rate_limits data provided directly by Claude Code in the JSON input.
# This is the most reliable source — no OAuth token or API call required.
builtin_five_hour_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
builtin_five_hour_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
builtin_seven_day_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
builtin_seven_day_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
use_builtin=false
if [ -n "$builtin_five_hour_pct" ] || [ -n "$builtin_seven_day_pct" ]; then
use_builtin=true
fi
# Cache setup — shared across all Claude Code instances to avoid rate limits
claude_config_dir_hash=$(echo -n "$claude_config_dir" | shasum -a 256 2>/dev/null || echo -n "$claude_config_dir" | sha256sum 2>/dev/null)
claude_config_dir_hash=$(echo "$claude_config_dir_hash" | cut -c1-8)
cache_file="/tmp/claude/statusline-usage-cache-${claude_config_dir_hash}.json"
cache_max_age=60 # seconds between API calls
mkdir -p /tmp/claude
needs_refresh=true
usage_data=""
# Always load cache — used as primary source for API path, and as fallback when builtin reports zero
if [ -f "$cache_file" ] && [ -s "$cache_file" ]; then
cache_mtime=$(stat -c %Y "$cache_file" 2>/dev/null || stat -f %m "$cache_file" 2>/dev/null)
now=$(date +%s)
cache_age=$(( now - cache_mtime ))
if [ "$cache_age" -lt "$cache_max_age" ]; then
needs_refresh=false
fi
usage_data=$(cat "$cache_file" 2>/dev/null)
fi
# When builtin values are all zero AND reset timestamps are missing, it likely indicates
# an API failure on Claude's side — fall through to cached data instead of displaying
# misleading 0%. Genuine zero responses (after a billing reset) still include valid
# resets_at timestamps, so we trust those.
effective_builtin=false
if $use_builtin; then
# Trust builtin if any percentage is non-zero
if { [ -n "$builtin_five_hour_pct" ] && [ "$(printf '%.0f' "$builtin_five_hour_pct" 2>/dev/null)" != "0" ]; } || \
{ [ -n "$builtin_seven_day_pct" ] && [ "$(printf '%.0f' "$builtin_seven_day_pct" 2>/dev/null)" != "0" ]; }; then
effective_builtin=true
fi
# Also trust if reset timestamps are present — genuine zero responses include valid reset times
if ! $effective_builtin; then
if { [ -n "$builtin_five_hour_reset" ] && [ "$builtin_five_hour_reset" != "null" ] && [ "$builtin_five_hour_reset" != "0" ]; } || \
{ [ -n "$builtin_seven_day_reset" ] && [ "$builtin_seven_day_reset" != "null" ] && [ "$builtin_seven_day_reset" != "0" ]; }; then
effective_builtin=true
fi
fi
fi
# Refresh API cache when stale — runs regardless of builtin rate_limits because
# extra_usage is only exposed through the OAuth usage endpoint (not stdin JSON).
# Throttled to cache_max_age and stampede-locked via touch for shared panes.
if $needs_refresh; then
touch "$cache_file" # stampede lock: prevent parallel panes from fetching simultaneously
token=$(get_oauth_token)
if [ -n "$token" ] && [ "$token" != "null" ]; then
response=$(curl -s --max-time 10 \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-H "anthropic-beta: oauth-2025-04-20" \
-H "User-Agent: claude-code/2.1.34" \
"https://api.anthropic.com/api/oauth/usage" 2>/dev/null)
# Only cache valid usage responses (not error/rate-limit JSON)
if [ -n "$response" ] && echo "$response" | jq -e '.five_hour' >/dev/null 2>&1; then
usage_data="$response"
echo "$response" > "$cache_file"
fi
fi
# Remove the stampede sentinel if the fetch failed to produce valid JSON —
# otherwise an empty cache file would suppress retries for a full cache_max_age window.
[ -f "$cache_file" ] && [ ! -s "$cache_file" ] && rm -f "$cache_file"
fi
# Cross-platform ISO to epoch conversion
# Converts ISO 8601 timestamp (e.g. "2025-06-15T12:30:00Z" or "2025-06-15T12:30:00.123+00:00") to epoch seconds.
# Properly handles UTC timestamps and converts to local time.
iso_to_epoch() {
local iso_str="$1"
# Try GNU date first (Linux) — handles ISO 8601 format automatically
local epoch
epoch=$(date -d "${iso_str}" +%s 2>/dev/null)
if [ -n "$epoch" ]; then
echo "$epoch"
return 0
fi
# BSD date (macOS) - handle various ISO 8601 formats
local stripped="${iso_str%%.*}" # Remove fractional seconds (.123456)
stripped="${stripped%%Z}" # Remove trailing Z
stripped="${stripped%%+*}" # Remove timezone offset (+00:00)
stripped="${stripped%%-[0-9][0-9]:[0-9][0-9]}" # Remove negative timezone offset
# Check if timestamp is UTC (has Z or +00:00 or -00:00)
if [[ "$iso_str" == *"Z"* ]] || [[ "$iso_str" == *"+00:00"* ]] || [[ "$iso_str" == *"-00:00"* ]]; then
# For UTC timestamps, parse with timezone set to UTC
epoch=$(env TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$stripped" +%s 2>/dev/null)
else
epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$stripped" +%s 2>/dev/null)
fi
if [ -n "$epoch" ]; then
echo "$epoch"
return 0
fi
return 1
}
# Format ISO reset time to compact local time
# Usage: format_reset_time <iso_string> <style: time|datetime|date>
format_reset_time() {
local iso_str="$1"
local style="$2"
{ [ -z "$iso_str" ] || [ "$iso_str" = "null" ]; } && return
# Parse ISO datetime and convert to local time (cross-platform)
local epoch
epoch=$(iso_to_epoch "$iso_str")
[ -z "$epoch" ] && return
# Format based on style
# Try GNU date first (Linux), then BSD date (macOS)
# Previous implementation piped BSD date through sed/tr, which always returned
# exit code 0 from the last pipe stage, preventing the GNU date fallback from
# ever executing on Linux.
local formatted=""
case "$style" in
time)
formatted=$(date -d "@$epoch" +"%H:%M" 2>/dev/null) || \
formatted=$(date -j -r "$epoch" +"%H:%M" 2>/dev/null)
;;
datetime)
formatted=$(date -d "@$epoch" +"%a %b %-d, %H:%M" 2>/dev/null) || \
formatted=$(date -j -r "$epoch" +"%a %b %-d, %H:%M" 2>/dev/null)
;;
*)
formatted=$(date -d "@$epoch" +"%b %-d" 2>/dev/null) || \
formatted=$(date -j -r "$epoch" +"%b %-d" 2>/dev/null)
;;
esac
[ -n "$formatted" ] && echo "$formatted"
}
sep=" ${dim}|${reset} "
# Render extra_usage segment from API usage data (not available via stdin rate_limits).
# Appends to the global $out. No-op when data is missing or is_enabled is false.
render_extra_usage() {
local data="$1"
[ -z "$data" ] && return
local enabled
enabled=$(echo "$data" | jq -r '.extra_usage.is_enabled // false' 2>/dev/null)
[ "$enabled" != "true" ] && return
local pct used limit
pct=$(echo "$data" | jq -r '.extra_usage.utilization // 0' | awk '{printf "%.0f", $1}')
used=$(echo "$data" | jq -r '.extra_usage.used_credits // 0' | LC_NUMERIC=C awk '{printf "%.2f", $1/100}')
limit=$(echo "$data" | jq -r '.extra_usage.monthly_limit // 0' | LC_NUMERIC=C awk '{printf "%.2f", $1/100}')
if [ -n "$used" ] && [ -n "$limit" ] && [[ "$used" != *'$'* ]] && [[ "$limit" != *'$'* ]]; then
local color
color=$(usage_color "$pct")
out+="${sep}${white}extra${reset} ${color}\$${used}/\$${limit}${reset}"
else
out+="${sep}${white}extra${reset} ${green}enabled${reset}"
fi
}
if $effective_builtin; then
# ---- Use rate_limits data provided directly by Claude Code in JSON input ----
# resets_at values are Unix epoch integers in this source
if [ -n "$builtin_five_hour_pct" ]; then
five_hour_pct=$(printf "%.0f" "$builtin_five_hour_pct")
five_hour_color=$(usage_color "$five_hour_pct")
out+="${sep}${white}5h${reset} ${five_hour_color}${five_hour_pct}%${reset}"
if [ -n "$builtin_five_hour_reset" ] && [ "$builtin_five_hour_reset" != "null" ]; then
five_hour_reset=$(date -j -r "$builtin_five_hour_reset" +"%H:%M" 2>/dev/null || date -d "@$builtin_five_hour_reset" +"%H:%M" 2>/dev/null)
[ -n "$five_hour_reset" ] && out+=" ${dim}@${five_hour_reset}${reset}"
fi
fi
if [ -n "$builtin_seven_day_pct" ]; then
seven_day_pct=$(printf "%.0f" "$builtin_seven_day_pct")
seven_day_color=$(usage_color "$seven_day_pct")
out+="${sep}${white}7d${reset} ${seven_day_color}${seven_day_pct}%${reset}"
if [ -n "$builtin_seven_day_reset" ] && [ "$builtin_seven_day_reset" != "null" ]; then
seven_day_reset=$(date -j -r "$builtin_seven_day_reset" +"%a %b %-d, %H:%M" 2>/dev/null || date -d "@$builtin_seven_day_reset" +"%a %b %-d, %H:%M" 2>/dev/null)
[ -n "$seven_day_reset" ] && out+=" ${dim}@${seven_day_reset}${reset}"
fi
fi
# Render extra_usage from API cache (stdin rate_limits doesn't expose it)
render_extra_usage "$usage_data"
# Cache builtin values so they're available as fallback when API is unavailable.
# Convert epoch resets_at to ISO 8601 for compatibility with the API-format cache parser.
# Preserve extra_usage from prior API response so we don't clobber it.
_fh_reset_json="null"
if [ -n "$builtin_five_hour_reset" ] && [ "$builtin_five_hour_reset" != "null" ] && [ "$builtin_five_hour_reset" != "0" ]; then
_fh_iso=$(date -u -r "$builtin_five_hour_reset" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
date -u -d "@$builtin_five_hour_reset" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
[ -n "$_fh_iso" ] && _fh_reset_json="\"$_fh_iso\""
fi
_sd_reset_json="null"
if [ -n "$builtin_seven_day_reset" ] && [ "$builtin_seven_day_reset" != "null" ] && [ "$builtin_seven_day_reset" != "0" ]; then
_sd_iso=$(date -u -r "$builtin_seven_day_reset" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
date -u -d "@$builtin_seven_day_reset" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
[ -n "$_sd_iso" ] && _sd_reset_json="\"$_sd_iso\""
fi
_extra_json=$(echo "$usage_data" | jq -c '.extra_usage // null' 2>/dev/null)
[ -z "$_extra_json" ] && _extra_json="null"
printf '{"five_hour":{"utilization":%s,"resets_at":%s},"seven_day":{"utilization":%s,"resets_at":%s},"extra_usage":%s}' \
"${builtin_five_hour_pct:-0}" "$_fh_reset_json" \
"${builtin_seven_day_pct:-0}" "$_sd_reset_json" \
"$_extra_json" > "$cache_file" 2>/dev/null
elif [ -n "$usage_data" ] && echo "$usage_data" | jq -e '.five_hour' >/dev/null 2>&1; then
# ---- Fall back: API-fetched usage data ----
# ---- 5-hour (current) ----
five_hour_pct=$(echo "$usage_data" | jq -r '.five_hour.utilization // 0' | awk '{printf "%.0f", $1}')
five_hour_reset_iso=$(echo "$usage_data" | jq -r '.five_hour.resets_at // empty')
five_hour_reset=$(format_reset_time "$five_hour_reset_iso" "time")
five_hour_color=$(usage_color "$five_hour_pct")
out+="${sep}${white}5h${reset} ${five_hour_color}${five_hour_pct}%${reset}"
[ -n "$five_hour_reset" ] && out+=" ${dim}@${five_hour_reset}${reset}"
# ---- 7-day (weekly) ----
seven_day_pct=$(echo "$usage_data" | jq -r '.seven_day.utilization // 0' | awk '{printf "%.0f", $1}')
seven_day_reset_iso=$(echo "$usage_data" | jq -r '.seven_day.resets_at // empty')
seven_day_reset=$(format_reset_time "$seven_day_reset_iso" "datetime")
seven_day_color=$(usage_color "$seven_day_pct")
out+="${sep}${white}7d${reset} ${seven_day_color}${seven_day_pct}%${reset}"
[ -n "$seven_day_reset" ] && out+=" ${dim}@${seven_day_reset}${reset}"
render_extra_usage "$usage_data"
else
# No valid usage data — show placeholders
out+="${sep}${white}5h${reset} ${dim}-${reset}"
out+="${sep}${white}7d${reset} ${dim}-${reset}"
fi
# ===== Update check (cached, 24h TTL) =====
# Set STATUSLINE_CHECK_UPDATES=false to disable the update check (no network calls).
update_line=""
if [ "${STATUSLINE_CHECK_UPDATES:-true}" != "false" ]; then
version_cache_file="/tmp/claude/statusline-version-cache.json"
version_cache_max_age=86400 # 24 hours
version_needs_refresh=true
version_data=""
if [ -f "$version_cache_file" ]; then
vc_mtime=$(stat -c %Y "$version_cache_file" 2>/dev/null || stat -f %m "$version_cache_file" 2>/dev/null)
vc_now=$(date +%s)
vc_age=$(( vc_now - vc_mtime ))
if [ "$vc_age" -lt "$version_cache_max_age" ]; then
version_needs_refresh=false
fi
version_data=$(cat "$version_cache_file" 2>/dev/null)
fi
if $version_needs_refresh; then
touch "$version_cache_file" 2>/dev/null
vc_response=$(curl -s --max-time 5 \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/daniel3303/ClaudeCodeStatusLine/releases/latest" 2>/dev/null)
if [ -n "$vc_response" ] && echo "$vc_response" | jq -e '.tag_name' >/dev/null 2>&1; then
version_data="$vc_response"
echo "$vc_response" > "$version_cache_file"
elif [ ! -s "$version_cache_file" ]; then
rm -f "$version_cache_file" 2>/dev/null
fi
fi
if [ -n "$version_data" ]; then
latest_tag=$(echo "$version_data" | jq -r '.tag_name // empty')
if [ -n "$latest_tag" ] && version_gt "$latest_tag" "$VERSION"; then
update_line="\n${dim}Update available: ${latest_tag} → Tell Claude: \"Find my installed status bar and update it\"${reset}"
fi
fi
fi
# Append CLI version as last segment
if [ -n "$cli_version" ]; then
out+=" ${dim}|${reset} ${orange}v${cli_version}${reset}"
fi
# Output
printf "%b" "$out$update_line"
exit 0
+186 -33
View File
@@ -1,7 +1,8 @@
{ lib, pkgs', pkgs, config, ... }:
let
inherit (lib) genAttrs mkIf mkMerge mkForce;
inherit (lib.my) mkBoolOpt';
inherit (lib) genAttrs mkIf mkMerge mkForce mapAttrs mkOptionDefault mkDefault;
inherit (lib.my) mkOpt' mkBoolOpt';
inherit (lib.my.c) pubDomain;
cfg = config.my.gui;
@@ -15,24 +16,42 @@ let
url = "https://distro.ibiblio.org/slitaz/sources/packages/d/doom1.wad";
hash = "sha256-HX1DvlAeZ9kn5BXguPPinDvzMHXoWXIYFvZSpSbKx3E=";
};
subwaySurfers = pkgs.fetchurl {
url = "https://p.${pubDomain}/video/subway-surfers-smol.mkv";
hash = "sha256-fMe7TDRNTymRHIJOi7qG3trzu4GP8a3gCDz+FMkX1dY=";
};
minecraftParkour = pkgs.fetchurl {
url = "https://p.${pubDomain}/video/minecraft-parkour-smol.mkv";
hash = "sha256-723pRm4AsIjY/WFUyAHzTJp+JvH4Pn5hvzF9wHTnOPA=";
};
doomsaver = pkgs.runCommand "doomsaver" {
inherit (pkgs) windowtolayer;
genLipsum = pkgs.writeScript "lipsum" ''
#!${pkgs.python3.withPackages (ps: [ ps.python-lorem ])}/bin/python
import lorem
print(lorem.get_paragraph(count=5, sep='\n\n'))
'';
doomsaver' = brainrotTextCommand: pkgs.runCommand "doomsaver" {
inherit (pkgs) windowtolayer tmux terminaltexteffects;
chocoDoom = pkgs.chocolate-doom2xx;
ffmpeg = pkgs.ffmpeg-full;
python = pkgs.python3.withPackages (ps: [ ps.filelock ]);
inherit doomWad;
enojy = ./enojy.jpg;
inherit brainrotTextCommand subwaySurfers minecraftParkour;
} ''
mkdir -p "$out"/bin
substituteAll ${./screensaver.py} "$out"/bin/doomsaver
chmod +x "$out"/bin/doomsaver
'';
doomsaver = doomsaver' cfg.screensaver.brainrotTextCommand;
in
{
options.my.gui = {
options.my.gui = with lib.types; {
enable = mkBoolOpt' true "Enable settings and packages meant for graphical systems";
manageGraphical = mkBoolOpt' false "Configure the graphical session";
standalone = mkBoolOpt' false "Enable settings for fully Nix managed systems";
screensaver.brainrotTextCommand = mkOpt' (either path str) genLipsum "Command to generate brainrot text.";
};
config = mkIf cfg.enable (mkMerge [
@@ -42,10 +61,9 @@ in
xdg-utils
font.package
(nerdfonts.override {
fonts = [ "DroidSansMono" "SourceCodePro" ];
})
noto-fonts-emoji
nerd-fonts.sauce-code-pro
nerd-fonts.droid-sans-mono
noto-fonts-color-emoji
grim
slurp
@@ -61,10 +79,11 @@ in
jp2a
terminaltexteffects
screenfetch
neofetch
fastfetch
cmatrix
doomsaver
ffmpeg-full
xournalpp
];
};
@@ -80,7 +99,7 @@ in
alacritty = {
enable = true;
settings = {
import = [ ./alacritty-xterm.toml ];
general.import = [ ./alacritty-xterm.toml ];
font = {
size = font.size;
@@ -99,15 +118,10 @@ in
background_opacity = "0.65";
tab_bar_edge = "top";
shell_integration = "no-sudo";
font_features = "${font.name} -liga";
};
};
termite = {
enable = true;
font = "${font.name} ${toString font.size}";
backgroundColor = "rgba(0, 0, 0, 0.8)";
};
foot = {
enable = true;
settings = {
@@ -141,6 +155,19 @@ in
};
};
};
claude-code = {
enable = true;
settings = {
model = "opus";
theme = "auto";
statusLine = {
type = "command";
command = ./claude-statusline.sh;
padding = 0;
};
};
};
};
}
@@ -186,6 +213,7 @@ in
wl-clipboard
wev
wdisplays
swaysome
pavucontrol
libsecret
@@ -208,9 +236,36 @@ in
xsession.preferStatusNotifierItems = true;
wayland = {
windowManager = {
sway = {
sway =
let
cfg = config.wayland.windowManager.sway.config;
mod = cfg.modifier;
renameWs = pkgs.writeShellScript "sway-rename-ws" ''
focused_ws="$(swaymsg -t get_workspaces | jq ".[] | select(.focused)")"
focused_num="$(jq -r ".num" <<< "$focused_ws")"
focused_name="$(jq -r ".name" <<< "$focused_ws")"
placeholder="$(sed -E 's/[0-9]+: //' <<< "$focused_name")"
name="$(rofi -dmenu -p "rename ws $focused_num" -theme+entry+placeholder "\"$placeholder\"")"
if [ -n "$name" ]; then
swaymsg rename workspace "$focused_name" to "$focused_num: $name"
fi
'';
clearWsName = pkgs.writeShellScript "sway-clear-ws-name" ''
focused_ws="$(swaymsg -t get_workspaces | jq ".[] | select(.focused)")"
focused_num="$(jq -r ".num" <<< "$focused_ws")"
focused_name="$(jq -r ".name" <<< "$focused_ws")"
swaymsg rename workspace "$focused_name" to "$focused_num"
'';
in
{
enable = true;
xwayland = true;
extraConfigEarly = ''
set $mod ${mod}
'';
config = {
input = {
"type:touchpad" = {
@@ -225,23 +280,87 @@ in
modifier = "Mod4";
terminal = "kitty";
keybindings =
let
cfg = config.wayland.windowManager.sway.config;
mod = cfg.modifier;
in
lib.mkOptionDefault {
keybindings = mapAttrs (k: mkOptionDefault) {
"${mod}+Left" = "focus left";
"${mod}+Down" = "focus down";
"${mod}+Up" = "focus up";
"${mod}+Right" = "focus right";
"${mod}+Shift+Left" = "move left";
"${mod}+Shift+Down" = "move down";
"${mod}+Shift+Up" = "move up";
"${mod}+Shift+Right" = "move right";
"${mod}+b" = "splith";
"${mod}+v" = "splitv";
"${mod}+f" = "fullscreen toggle";
"${mod}+a" = "focus parent";
"${mod}+s" = "layout stacking";
"${mod}+w" = "layout tabbed";
"${mod}+e" = "layout toggle split";
"${mod}+Shift+space" = "floating toggle";
"${mod}+space" = "focus mode_toggle";
"${mod}+1" = "workspace number 1";
"${mod}+2" = "workspace number 2";
"${mod}+3" = "workspace number 3";
"${mod}+4" = "workspace number 4";
"${mod}+5" = "workspace number 5";
"${mod}+6" = "workspace number 6";
"${mod}+7" = "workspace number 7";
"${mod}+8" = "workspace number 8";
"${mod}+9" = "workspace number 9";
"${mod}+0" = "workspace number 10";
"${mod}+Shift+1" =
"move container to workspace number 1";
"${mod}+Shift+2" =
"move container to workspace number 2";
"${mod}+Shift+3" =
"move container to workspace number 3";
"${mod}+Shift+4" =
"move container to workspace number 4";
"${mod}+Shift+5" =
"move container to workspace number 5";
"${mod}+Shift+6" =
"move container to workspace number 6";
"${mod}+Shift+7" =
"move container to workspace number 7";
"${mod}+Shift+8" =
"move container to workspace number 8";
"${mod}+Shift+9" =
"move container to workspace number 9";
"${mod}+Shift+0" =
"move container to workspace number 10";
"${mod}+Shift+minus" = "move scratchpad";
"${mod}+minus" = "scratchpad show";
"${mod}+Return" = "exec ${cfg.terminal}";
"${mod}+r" = "mode resize";
"${mod}+d" = null;
"${mod}+l" = "exec ${doomsaver}/bin/doomsaver";
"${mod}+q" = "kill";
"${mod}+Shift+c" = "reload";
"${mod}+Shift+q" = "exec swaynag -t warning -m 'bruh you really wanna kill sway?' -b 'ye' 'systemctl --user stop graphical-session.target && swaymsg exit'";
# rofi
"${mod}+x" = "exec ${cfg.menu}";
"${mod}+Shift+x" = "exec rofi -show drun";
"${mod}+q" = "kill";
"${mod}+Shift+q" = "exec swaynag -t warning -m 'bruh you really wanna kill sway?' -b 'ye' 'systemctl --user stop graphical-session.target && swaymsg exit'";
"${mod}+Shift+d" = ''exec grim - | swappy -f -'';
"${mod}+Shift+s" = ''exec grim -g "$(slurp)" - | swappy -f -'';
"${mod}+Shift+e" = "exec rofi -show emoji";
# Config for this doesn't seem to work :/
"${mod}+c" = ''exec rofi -show calc -calc-command "echo -n '{result}' | ${pkgs.wl-clipboard}/bin/wl-copy"'';
"${mod}+Shift+r" = "exec ${renameWs}";
"${mod}+Shift+n" = "exec ${clearWsName}";
# Screenshots
"${mod}+Shift+d" = ''exec grim - | swappy -f -'';
"${mod}+Shift+s" = ''exec grim -g "$(slurp)" - | swappy -f -'';
"XF86MonBrightnessDown" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-";
"XF86MonBrightnessUp" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set +5%";
"XF86AudioRaiseVolume" = "exec ${pkgs.pamixer}/bin/pamixer -i 5";
"XF86AudioLowerVolume" = "exec ${pkgs.pamixer}/bin/pamixer -d 5";
@@ -258,6 +377,9 @@ in
menu = "rofi -show run";
bars = mkForce [ ];
};
extraConfig = ''
include ${./swaysome.conf}
'';
swaynag = {
enable = true;
@@ -272,6 +394,10 @@ in
name = "Numix";
package = pkgs.numix-gtk-theme;
};
gtk4.theme = {
name = "Numix";
package = pkgs.numix-gtk-theme;
};
iconTheme = {
name = "Numix";
package = pkgs.numix-icon-theme;
@@ -300,19 +426,45 @@ in
device_type = "computer";
};
};
easyeffects = {
enable = mkDefault false;
preset = mkDefault "moar-bass";
extraPresets = {
moar-bass = {
output = {
"bass_enhancer#0" = {
amount = 3;
blend = 0;
bypass = false;
floor = 20;
floor-active = false;
harmonics = 8.5;
input-gain = 0;
output-gain = 0;
scope = 100;
};
blocklist = [ ];
plugins_order = [ "bass_enhancer#0" ];
};
};
};
};
};
programs = {
git = {
enable = true;
diff-so-fancy.enable = true;
userEmail = "jackos1998@gmail.com";
userName = "Jack O'Sullivan";
lfs.enable = true;
extraConfig = {
settings = {
user = {
email = "jackos1998@gmail.com";
name = "Jack O'Sullivan";
};
pull.rebase = true;
};
lfs.enable = true;
};
diff-so-fancy.enable = true;
waybar = import ./waybar.nix { inherit lib pkgs config font; };
rofi = {
@@ -377,6 +529,7 @@ in
userDirs = {
enable = true;
createDirectories = true;
setSessionVariables = true;
desktop = "$HOME/desktop";
documents = "$HOME/documents";
download = "$HOME/downloads";
+49 -1
View File
@@ -73,7 +73,7 @@ class TTESaver(Screensaver):
def wait(self):
while self.running:
effect_cmd = ['tte', random.choice(self.effects)]
effect_cmd = ['@terminaltexteffects@/bin/tte', random.choice(self.effects)]
print(f"$ {self.cmd} | {' '.join(effect_cmd)}")
content = subprocess.check_output(self.cmd, shell=True, env=self.env, stderr=subprocess.DEVNULL)
@@ -86,6 +86,51 @@ class TTESaver(Screensaver):
self.running = False
self.proc.terminate()
class FFmpegCACASaver(Screensaver):
@staticmethod
def command(video, size):
return ['@ffmpeg@/bin/ffmpeg', '-hide_banner', '-loglevel', 'error',
'-stream_loop', '-1', '-i', video,
'-pix_fmt', 'rgb24', '-window_size', f'{size}x{size}',
'-f', 'caca', '-']
def __init__(self, video, weight=2):
cols, lines = os.get_terminal_size()
# IDK if it's reasonable to do this as "1:1"
size = lines - 4
super().__init__(
self.command(video, size),
env={'CACA_DRIVER': 'ncurses'},
weight=weight,
)
def stop(self):
super().stop(kill=True)
class BrainrotStorySaver(Screensaver):
def __init__(self, video, text_command, weight=2):
cols, lines = os.get_terminal_size()
video_size = lines - 1
video_command = ' '.join(FFmpegCACASaver.command(video, video_size))
text_command = (
f'while true; do {text_command} | '
f'@terminaltexteffects@/bin/tte --wrap-text --canvas-width=80 --canvas-height={video_size//2} --anchor-canvas=c '
'print --final-gradient-stops=ffffff; clear; done' )
self.tmux_session = f'screensaver-{os.urandom(4).hex()}'
super().__init__(
['@tmux@/bin/tmux', 'new-session', '-s', self.tmux_session, '-n', 'brainrot',
text_command, ';', 'split-window', '-hbl', str(lines), video_command],
# ['sh', '-c', text_command],
env={
'CACA_DRIVER': 'ncurses',
'SHELL': '/bin/sh',
},
weight=weight,
)
def stop(self):
subprocess.check_call(['@tmux@/bin/tmux', 'kill-session', '-t', self.tmux_session])
class MultiSaver:
savers = [
DoomSaver(0),
@@ -100,6 +145,9 @@ class MultiSaver:
TTESaver('ss -nltu'),
TTESaver('ss -ntu'),
TTESaver('jp2a --width=100 @enojy@'),
BrainrotStorySaver('@subwaySurfers@', '@brainrotTextCommand@'),
BrainrotStorySaver('@minecraftParkour@', '@brainrotTextCommand@'),
]
state_filename = 'screensaver.json'
+66
View File
@@ -0,0 +1,66 @@
# Use (un)bindcode or (un)bindsym, depending on what you used in your main sway config file.
# The `--no-warn` setting is only added to shortcuts that exist in the default config. You may want to add or remove
# that flag on some bindings depending on your config.
# Change focus between workspaces
bindsym $mod+Alt+1 exec "swaysome focus 1"
bindsym $mod+Alt+2 exec "swaysome focus 2"
bindsym $mod+Alt+3 exec "swaysome focus 3"
bindsym $mod+Alt+4 exec "swaysome focus 4"
bindsym $mod+Alt+5 exec "swaysome focus 5"
bindsym $mod+Alt+6 exec "swaysome focus 6"
bindsym $mod+Alt+7 exec "swaysome focus 7"
bindsym $mod+Alt+8 exec "swaysome focus 8"
bindsym $mod+Alt+9 exec "swaysome focus 9"
bindsym $mod+Alt+0 exec "swaysome focus 0"
# Focus workspace groups
bindsym --no-warn $mod+1 exec "swaysome focus-group 1"
bindsym --no-warn $mod+2 exec "swaysome focus-group 2"
bindsym --no-warn $mod+3 exec "swaysome focus-group 3"
bindsym --no-warn $mod+4 exec "swaysome focus-group 4"
bindsym --no-warn $mod+5 exec "swaysome focus-group 5"
bindsym --no-warn $mod+6 exec "swaysome focus-group 6"
bindsym --no-warn $mod+7 exec "swaysome focus-group 7"
bindsym --no-warn $mod+8 exec "swaysome focus-group 8"
bindsym --no-warn $mod+9 exec "swaysome focus-group 9"
bindsym --no-warn $mod+0 exec "swaysome focus-group 0"
# Move containers between workspaces
bindsym $mod+Alt+Shift+1 exec "swaysome move 1"
bindsym $mod+Alt+Shift+2 exec "swaysome move 2"
bindsym $mod+Alt+Shift+3 exec "swaysome move 3"
bindsym $mod+Alt+Shift+4 exec "swaysome move 4"
bindsym $mod+Alt+Shift+5 exec "swaysome move 5"
bindsym $mod+Alt+Shift+6 exec "swaysome move 6"
bindsym $mod+Alt+Shift+7 exec "swaysome move 7"
bindsym $mod+Alt+Shift+8 exec "swaysome move 8"
bindsym $mod+Alt+Shift+9 exec "swaysome move 9"
bindsym $mod+Alt+Shift+0 exec "swaysome move 0"
# Move containers to other workspace groups
bindsym --no-warn $mod+Shift+1 exec "swaysome move-to-group 1"
bindsym --no-warn $mod+Shift+2 exec "swaysome move-to-group 2"
bindsym --no-warn $mod+Shift+3 exec "swaysome move-to-group 3"
bindsym --no-warn $mod+Shift+4 exec "swaysome move-to-group 4"
bindsym --no-warn $mod+Shift+5 exec "swaysome move-to-group 5"
bindsym --no-warn $mod+Shift+6 exec "swaysome move-to-group 6"
bindsym --no-warn $mod+Shift+7 exec "swaysome move-to-group 7"
bindsym --no-warn $mod+Shift+8 exec "swaysome move-to-group 8"
bindsym --no-warn $mod+Shift+9 exec "swaysome move-to-group 9"
bindsym --no-warn $mod+Shift+0 exec "swaysome move-to-group 0"
# Move focused container to next output
bindsym $mod+Alt+Right exec "swaysome next-output"
# Move focused container to previous output
bindsym $mod+Alt+Left exec "swaysome prev-output"
# Move focused workspace group to next output
bindsym $mod+Shift+Alt+Right exec "swaysome workspace-group-next-output"
# Move focused workspace group to previous output
bindsym $mod+Shift+Alt+Left exec "swaysome workspace-group-prev-output"
# Init workspaces for every screen
exec "swaysome init 1"
+92 -6
View File
@@ -13,6 +13,7 @@ rec {
kea = 404;
keepalived_script = 405;
photoprism = 406;
copyparty = 408;
};
gids = {
matrix-syncv3 = 400;
@@ -22,12 +23,14 @@ rec {
kea = 404;
keepalived_script = 405;
photoprism = 406;
adbusers = 407;
copyparty = 408;
};
};
kernel = {
lts = pkgs: pkgs.linuxKernel.packages.linux_6_6;
latest = pkgs: pkgs.linuxKernel.packages.linux_6_9;
lts = pkgs: pkgs.linuxKernel.packages.linux_6_18;
latest = pkgs: pkgs.linuxKernel.packages.linux_7_0;
};
nginx = rec {
@@ -139,6 +142,16 @@ rec {
v4 = subnet 8 4 all.v4;
};
p2pTunnels = {
v4 = subnet 8 5 all.v4;
};
hillcrest = {
v4 = subnet 6 0 p2pTunnels.v4;
};
john-valorant = {
v4 = subnet 6 1 p2pTunnels.v4;
};
cust = {
v4 = subnet 8 100 all.v4; # single ip for routing only
v6 = "2a0e:97c0:4d2:2000::/56";
@@ -199,10 +212,28 @@ rec {
port = 25566;
dst = aa.simpcraft-staging-oci.internal.ipv4.address;
}
{
port = 25567;
dst = aa.kevcraft-oci.internal.ipv4.address;
}
{
port = 25568;
dst = aa.kinkcraft-oci.internal.ipv4.address;
}
{
port = 25569;
dst = aa.graeme-oci.internal.ipv4.address;
}
# RCON... unsafe?
# {
# port = 25575;
# dst = aa.simpcraft-oci.internal.ipv4.address;
# }
{
port = 25575;
dst = aa.simpcraft-oci.internal.ipv4.address;
port = 7777;
dst = aa.gam.internal.ipv4.address;
}
{
@@ -227,12 +258,44 @@ rec {
dst = aa.simpcraft-oci.internal.ipv4.address;
proto = "udp";
}
{
port = 25567;
dst = aa.kevcraft-oci.internal.ipv4.address;
proto = "udp";
}
{
port = 25568;
dst = aa.kinkcraft-oci.internal.ipv4.address;
proto = "udp";
}
{
port = 25569;
dst = aa.graeme-oci.internal.ipv4.address;
proto = "udp";
}
{
port = 15636;
dst = aa.enshrouded-oci.internal.ipv4.address;
proto = "udp";
}
{
port = 15637;
dst = aa.enshrouded-oci.internal.ipv4.address;
proto = "udp";
}
{
port = qclk.wgPort;
dst = aa.qclk.internal.ipv4.address;
proto = "udp";
}
{
port = 7777;
dst = aa.gam.internal.ipv4.address;
proto = "udp";
}
];
fstrimConfig = {
@@ -256,8 +319,8 @@ rec {
"stream"
];
routersPubV4 = [
"80.111.122.16"
"109.255.252.63"
"109.255.108.88"
"109.255.108.121"
];
prefixes = with lib.my.net.cidr; rec {
@@ -323,6 +386,20 @@ rec {
assignedV6 = "2001:19f0:7402:128b:5400:04ff:feac:6e06";
};
britnet = {
domain = "bhx1.int.${pubDomain}";
pubV4 = "77.74.199.67";
vpn = {
port = 51820;
};
prefixes = with lib.my.net.cidr; rec {
vpn = {
v4 = "10.200.0.0/24";
v6 = "fdfb:5ebf:6e84::/64";
};
};
};
tailscale = {
prefix = {
v4 = "100.64.0.0/10";
@@ -367,6 +444,15 @@ rec {
ctrs.v4 = subnet 4 0 all.v4;
};
};
hillcrest = {
vpn.port = 51822;
};
john-valorant = {
vpn.port = 51823;
};
sshKeyFiles = {
me = ../.keys/me.pub;
deploy = ../.keys/deploy.pub;
+7 -6
View File
@@ -1,11 +1,11 @@
{ lib }:
{ inputs, lib }:
let
inherit (builtins) length match elemAt filter replaceStrings substring;
inherit (lib)
genAttrs mapAttrsToList filterAttrsRecursive nameValuePair types
mkOption mkOverride mkForce mkIf mergeEqualOption optional
showWarnings concatStringsSep flatten unique optionalAttrs
mkBefore toLower;
mkBefore toLower splitString last;
inherit (lib.flake) defaultSystems;
in
rec {
@@ -23,7 +23,7 @@ rec {
attrsToNVList = mapAttrsToList nameValuePair;
inherit (import ./net.nix { inherit lib; }) net;
inherit ((import "${inputs.libnetRepo}/lib/netu.nix" { inherit lib; }).lib) net;
dns = import ./dns.nix { inherit lib; };
c = import ./constants.nix { inherit lib; };
@@ -53,7 +53,7 @@ rec {
in mkApp "${app}/bin/${app.meta.mainProgram}";
flakePackageOverlay' = flake: pkg: system: (final: prev:
let
pkg' = if pkg != null then flake.packages.${system}.${pkg} else flake.defaultPackage.${system};
pkg' = if pkg != null then flake.packages.${system}.${pkg} else flake.packages.${system}.default;
name = if pkg != null then pkg else pkg'.name;
in
{
@@ -248,12 +248,13 @@ rec {
in
{
trivial = prev.trivial // {
release = "24.07:u-${prev.trivial.release}";
codeName = "Diffed";
release = "26.06:u-${prev.trivial.release}";
codeName = "Irritating";
revisionWithDefault = default: self.rev or default;
versionSuffix = ".${date}.${revCode self}:u-${revCode pkgsFlake}";
};
};
upstreamRelease = last (splitString "-" lib.trivial.release);
netbootKeaClientClasses = { tftpIP, hostname, systems }:
let
-1322
View File
File diff suppressed because it is too large Load Diff
+191
View File
@@ -0,0 +1,191 @@
{ lib, ... }:
let
inherit (lib.my) net;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.britnet) domain pubV4 prefixes;
in
{
nixos.systems.britnet = {
system = "x86_64-linux";
nixpkgs = "mine";
assignments = {
allhost = {
inherit domain;
ipv4 = {
address = pubV4;
mask = 24;
gateway = "77.74.199.1";
};
ipv6 = {
address = "2a12:ab46:5344:99::a";
gateway = "2a12:ab46:5344::1";
};
};
vpn = {
ipv4 = {
address = net.cidr.host 1 prefixes.vpn.v4;
gateway = null;
};
ipv6.address = net.cidr.host 1 prefixes.vpn.v6;
};
};
configuration = { lib, pkgs, modulesPath, config, assignments, allAssignments, ... }:
let
inherit (lib) mkMerge mkForce;
inherit (lib.my) networkdAssignment;
in
{
imports = [
"${modulesPath}/profiles/qemu-guest.nix"
];
config = mkMerge [
{
boot = {
initrd.availableKernelModules = [
"ata_piix" "uhci_hcd" "virtio_pci" "virtio_scsi" "ahci" "sr_mod" "virtio_blk"
];
loader = {
systemd-boot.enable = false;
grub = {
enable = true;
device = "/dev/vda";
};
};
};
fileSystems = {
"/boot" = {
device = "/dev/disk/by-uuid/457444a1-81dd-4934-960c-650ad16c92b5";
fsType = "ext4";
};
"/nix" = {
device = "/dev/disk/by-uuid/992c0c79-5be6-45b6-bc30-dc82e3ec082a";
fsType = "ext4";
};
"/persist" = {
device = "/dev/disk/by-uuid/f020a955-54d5-4098-98ba-d3615781d96a";
fsType = "ext4";
neededForBoot = true;
};
};
environment = {
systemPackages = with pkgs; [
wireguard-tools
];
};
services = {
iperf3 = {
enable = true;
openFirewall = true;
};
tailscale = {
enable = true;
authKeyFile = config.age.secrets."tailscale-auth.key".path;
openFirewall = true;
interfaceName = "tailscale0";
extraUpFlags = [
"--operator=${config.my.user.config.name}"
"--login-server=https://hs.nul.ie"
"--netfilter-mode=off"
"--advertise-exit-node"
"--accept-routes=false"
];
};
};
networking = { inherit domain; };
systemd.network = {
netdevs = {
"30-wg0" = {
netdevConfig = {
Name = "wg0";
Kind = "wireguard";
};
wireguardConfig = {
PrivateKeyFile = config.age.secrets."britnet/wg.key".path;
ListenPort = lib.my.c.britnet.vpn.port;
};
wireguardPeers = [
{
PublicKey = "EfPwREfZ/q3ogHXBIqFZh4k/1NRJRyq4gBkBXtegNkE=";
AllowedIPs = [
(net.cidr.host 10 prefixes.vpn.v4)
(net.cidr.host 10 prefixes.vpn.v6)
];
}
];
};
};
links = {
"10-veth0" = {
matchConfig.PermanentMACAddress = "00:db:d9:62:68:1a";
linkConfig.Name = "veth0";
};
};
networks = {
"20-veth0" = mkMerge [
(networkdAssignment "veth0" assignments.allhost)
{
dns = [ "1.1.1.1" "1.0.0.1" ];
routes = [
{
# Gateway is on a different network for some reason...
Destination = "2a12:ab46:5344::1";
Scope = "link";
}
];
}
];
"30-wg0" = mkMerge [
(networkdAssignment "wg0" assignments.vpn)
{
networkConfig.IPv6AcceptRA = mkForce false;
}
];
};
};
my = {
server.enable = true;
secrets = {
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJIEx+1EC/lN6WKIaOB+O5LJgVHRK962YpZEPQg/m78O";
files = {
"tailscale-auth.key" = {};
"britnet/wg.key" = {
owner = "systemd-network";
};
};
};
firewall = {
udp.allowed = [ lib.my.c.britnet.vpn.port ];
trustedInterfaces = [ "tailscale0" ];
extraRules = ''
table inet filter {
chain forward {
iifname wg0 oifname veth0 accept
}
}
table inet nat {
chain postrouting {
iifname { tailscale0, wg0 } oifname veth0 snat ip to ${assignments.allhost.ipv4.address}
iifname { tailscale0, wg0 } oifname veth0 snat ip6 to ${assignments.allhost.ipv6.address}
}
}
'';
};
};
}
];
};
};
}
+5 -4
View File
@@ -11,23 +11,24 @@ in
config = {
my = {
secrets.files."britway/bgp-password-vultr.conf" = {
owner = "bird2";
group = "bird2";
owner = "bird";
group = "bird";
};
};
environment.etc."bird/vultr-password.conf".source = config.age.secrets."britway/bgp-password-vultr.conf".path;
systemd = {
services.bird2.after = [ "systemd-networkd-wait-online@veth0.service" ];
services.bird.after = [ "systemd-networkd-wait-online@veth0.service" ];
network = {
config.networkConfig.ManageForeignRoutes = false;
};
};
services = {
bird2 = {
bird = {
enable = true;
package = pkgs.bird2;
preCheckConfig = ''
echo '"dummy"' > vultr-password.conf
'';
+2 -2
View File
@@ -106,7 +106,7 @@ in
{
matchConfig.Name = "as211024";
networkConfig.IPv6AcceptRA = mkForce false;
routes = map (r: { routeConfig = r; }) [
routes = [
{
Destination = lib.my.c.colony.prefixes.all.v4;
Gateway = allAssignments.estuary.as211024.ipv4.address;
@@ -123,7 +123,7 @@ in
Table = "ts-extra";
}
];
routingPolicyRules = map (r: { routingPolicyRuleConfig = r; }) [
routingPolicyRules = [
{
IncomingInterface = "tailscale0";
To = lib.my.c.colony.prefixes.all.v6;
+2 -8
View File
@@ -9,11 +9,6 @@ in
config = {
my = {
secrets.files = {
"dhparams.pem" = {
owner = "acme";
group = "acme";
mode = "440";
};
"britway/cloudflare-credentials.conf" = {
owner = "acme";
group = "acme";
@@ -45,7 +40,7 @@ in
"*.${pubDomain}"
];
dnsProvider = "cloudflare";
credentialsFile = config.age.secrets."britway/cloudflare-credentials.conf".path;
environmentFile = config.age.secrets."britway/cloudflare-credentials.conf".path;
};
};
};
@@ -58,7 +53,6 @@ in
logError = "stderr info";
recommendedTlsSettings = true;
serverTokens = true;
sslDhparam = config.age.secrets."dhparams.pem".path;
# Based on recommended*Settings, but probably better to be explicit about these
appendHttpConfig = ''
@@ -80,7 +74,7 @@ in
};
};
"ts.${pubDomain}" = {
"hs.${pubDomain}" = {
locations."/" = {
proxyPass = "http://localhost:${toString config.services.headscale.port}";
proxyWebsockets = true;
+11 -22
View File
@@ -1,24 +1,13 @@
{ lib, pkgs, config, assignments, allAssignments, ... }:
let
inherit (lib) concatStringsSep;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.britway) prefixes domain;
# Can't use overrideAttrs because we need to override `vendorHash` within `buildGoModule`
headscale = (pkgs.headscale.override {
buildGoModule = args: pkgs.buildGoModule (args // rec {
version = "0.23.0-alpha12";
src = pkgs.fetchFromGitHub {
owner = "juanfont";
repo = "headscale";
rev = "v${version}";
hash = "sha256-kZZK0cXnFARxblSMz01TDcBbTorkHGAwGpR+a4/mYfU=";
};
patches = [];
vendorHash = "sha256-EorT2AVwA3usly/LcNor6r5UIhLCdj3L4O4ilgTIC2o=";
doCheck = false;
});
});
advRoutes = concatStringsSep "," [
lib.my.c.home.prefixes.all.v4
lib.my.c.home.prefixes.all.v6
];
pubNameservers = [
"1.1.1.1"
"1.0.0.1"
@@ -36,21 +25,21 @@ in
services = {
headscale = {
enable = true;
package = headscale;
settings = {
disable_check_updates = true;
unix_socket_permission = "0770";
server_url = "https://ts.${pubDomain}";
server_url = "https://hs.${pubDomain}";
database = {
type = "sqlite3";
sqlite.path = "/var/lib/headscale/db.sqlite3";
};
noise.private_key_path = "/var/lib/headscale/noise_private.key";
prefixes = with lib.my.c.tailscale.prefix; { inherit v4 v6; };
dns_config = {
dns = {
override_local_dns = false;
# Use IPs that will route inside the VPN to prevent interception
# (e.g. DNS rebinding filtering)
restricted_nameservers = {
nameservers.split = {
"${domain}" = pubNameservers;
"${lib.my.c.colony.domain}" = with allAssignments.estuary.base; [
ipv4.address ipv6.address
@@ -64,7 +53,6 @@ in
};
magic_dns = true;
base_domain = "ts.${pubDomain}";
override_local_dns = false;
};
oidc = {
only_start_if_oidc_is_available = true;
@@ -84,9 +72,10 @@ in
interfaceName = "tailscale0";
extraUpFlags = [
"--operator=${config.my.user.config.name}"
"--login-server=https://ts.nul.ie"
"--login-server=https://hs.nul.ie"
"--netfilter-mode=off"
"--advertise-exit-node"
"--advertise-routes=${advRoutes}"
"--accept-routes=false"
];
};
+6 -6
View File
@@ -252,10 +252,10 @@ in
};
ipv6Prefixes = [
{
ipv6PrefixConfig.Prefix = prefixes.vms.v6;
Prefix = prefixes.vms.v6;
}
];
routes = map (r: { routeConfig = r; }) [
routes = [
{
Destination = prefixes.ctrs.v4;
Gateway = allAssignments.shill.routing.ipv4.address;
@@ -327,10 +327,10 @@ in
};
ipv6Prefixes = [
{
ipv6PrefixConfig.Prefix = prefixes.mail.v6;
Prefix = prefixes.mail.v6;
}
];
routes = map (r: { routeConfig = r; }) [
routes = [
{
Destination = prefixes.mail.v4;
Scope = "link";
@@ -350,10 +350,10 @@ in
};
ipv6Prefixes = [
{
ipv6PrefixConfig.Prefix = prefixes.darts.v6;
Prefix = prefixes.darts.v6;
}
];
routes = map (r: { routeConfig = r; }) [
routes = [
{
Destination = prefixes.darts.v4;
Scope = "link";
+83 -53
View File
@@ -8,8 +8,9 @@ in
{
config = {
services = {
bird2 = {
bird = {
enable = true;
package = pkgs.bird2;
# TODO: Clean up and modularise
config = ''
define OWNAS = 211024;
@@ -232,59 +233,88 @@ in
neighbor 2a07:cd40:1::9 as 202413;
}
protocol bgp ixp4_frysix_rs1 from ixp_bgp4 {
description "Frys-IX route server 1 (IPv4)";
neighbor 185.1.203.253 as 56393;
protocol bgp ixp4_frysix_rs3 from ixp_bgp4 {
description "Frys-IX route server 3 (IPv4)";
neighbor 185.1.160.255 as 56393;
}
protocol bgp ixp6_frysix_rs1 from ixp_bgp6 {
description "Frys-IX route server 1 (IPv6)";
neighbor 2001:7f8:10f::dc49:253 as 56393;
protocol bgp ixp6_frysix_rs3 from ixp_bgp6 {
description "Frys-IX route server 3 (IPv6)";
neighbor 2001:7f8:10f::dc49:1 as 56393;
}
protocol bgp ixp4_frysix_rs2 from ixp_bgp4 {
description "Frys-IX route server 2 (IPv4)";
neighbor 185.1.203.254 as 56393;
protocol bgp ixp4_frysix_rs4 from ixp_bgp4 {
description "Frys-IX route server 4 (IPv4)";
neighbor 185.1.161.0 as 56393;
}
protocol bgp ixp6_frysix_rs2 from ixp_bgp6 {
description "Frys-IX route server 2 (IPv6)";
neighbor 2001:7f8:10f::dc49:254 as 56393;
protocol bgp ixp6_frysix_rs4 from ixp_bgp6 {
description "Frys-IX route server 4 (IPv6)";
neighbor 2001:7f8:10f::dc49:2 as 56393;
}
protocol bgp peer4_frysix_luje from peer_bgp4 {
description "LUJE.net (on Frys-IX, IPv4)";
neighbor 185.1.203.152 as 212855;
neighbor 185.1.160.152 as 212855;
}
protocol bgp peer6_frysix_luje from peer_bgp6 {
description "LUJE.net (on Frys-IX, IPv6)";
neighbor 2001:7f8:10f::3:3f95:152 as 212855;
}
protocol bgp peer4_frysix_he from peer_bgp4 {
description "Hurricane Electric (on Frys-IX, IPv4)";
neighbor 185.1.203.154 as 6939;
neighbor 185.1.160.154 as 6939;
}
protocol bgp peer4_frysix_cloudflare from peer_bgp4 {
description "Cloudflare (on Frys-IX, IPv4)";
neighbor 185.1.203.217 as 13335;
protocol bgp peer4_frysix_cloudflare1 from peer_bgp4 {
description "Cloudflare 1 (on Frys-IX, IPv4)";
neighbor 185.1.160.217 as 13335;
}
protocol bgp peer6_frysix_cloudflare from peer_bgp6 {
description "Cloudflare (on Frys-IX, IPv6)";
protocol bgp peer4_frysix_cloudflare2 from peer_bgp4 {
description "Cloudflare 2 (on Frys-IX, IPv4)";
neighbor 185.1.160.109 as 13335;
}
protocol bgp peer6_frysix_cloudflare1 from peer_bgp6 {
description "Cloudflare 1 (on Frys-IX, IPv6)";
neighbor 2001:7f8:10f::3417:217 as 13335;
}
protocol bgp peer6_frysix_cloudflare2 from peer_bgp6 {
description "Cloudflare 2 (on Frys-IX, IPv6)";
neighbor 2001:7f8:10f::3417:109 as 13335;
}
protocol bgp peer4_frysix_jurrian from peer_bgp4 {
description "AS212635 aka jurrian (on Frys-IX, IPv4)";
neighbor 185.1.203.134 as 212635;
neighbor 185.1.160.134 as 212635;
}
protocol bgp peer6_frysix_jurrian from peer_bgp6 {
description "AS212635 aka jurrian (on Frys-IX, IPv6)";
neighbor 2001:7f8:10f::3:3e9b:134 as 212635;
}
protocol bgp peer4_nlix_meta1 from peer_bgp4 {
description "Meta 1 (on NL-ix, IPv4)";
neighbor 193.239.116.147 as 32934;
}
protocol bgp peer4_nlix_meta2 from peer_bgp4 {
description "Meta 2 (on NL-ix, IPv4)";
neighbor 193.239.116.148 as 32934;
}
protocol bgp peer6_nlix_meta1 from peer_bgp6 {
description "Meta 1 (on NL-ix, IPv6)";
neighbor 2001:7f8:13::a503:2934:1 as 32934;
}
protocol bgp peer6_nlix_meta2 from peer_bgp6 {
description "Meta 2 (on NL-ix, IPv6)";
neighbor 2001:7f8:13::a503:2934:2 as 32934;
}
protocol bgp peer4_frysix_meta1 from peer_bgp4 {
description "Meta 1 (on Frys-IX, IPv4)";
neighbor 185.1.203.225 as 32934;
neighbor 185.1.160.225 as 32934;
}
protocol bgp peer4_frysix_meta2 from peer_bgp4 {
description "Meta 2 (on Frys-IX, IPv4)";
neighbor 185.1.203.226 as 32934;
neighbor 185.1.160.226 as 32934;
}
protocol bgp peer6_frysix_meta1 from peer_bgp6 {
description "Meta 1 (on Frys-IX, IPv6)";
@@ -317,36 +347,36 @@ in
ipv6 { preference (PREFIXP-1); };
}
protocol bgp peer4_nlix_cloudflare1 from peer_bgp4 {
description "Cloudflare NL-ix 1 (IPv4)";
neighbor 193.239.117.14 as 13335;
ipv4 { preference (PREFPEER-1); };
}
protocol bgp peer4_nlix_cloudflare2 from peer_bgp4 {
description "Cloudflare NL-ix 2 (IPv4)";
neighbor 193.239.117.114 as 13335;
ipv4 { preference (PREFPEER-1); };
}
protocol bgp peer4_nlix_cloudflare3 from peer_bgp4 {
description "Cloudflare NL-ix 3 (IPv4)";
neighbor 193.239.118.138 as 13335;
ipv4 { preference (PREFPEER-1); };
}
protocol bgp peer6_nlix_cloudflare1 from peer_bgp6 {
description "Cloudflare NL-ix 1 (IPv6)";
neighbor 2001:7f8:13::a501:3335:1 as 13335;
ipv6 { preference (PREFPEER-1); };
}
protocol bgp peer6_nlix_cloudflare2 from peer_bgp6 {
description "Cloudflare NL-ix 2 (IPv6)";
neighbor 2001:7f8:13::a501:3335:2 as 13335;
ipv6 { preference (PREFPEER-1); };
}
protocol bgp peer6_nlix_cloudflare3 from peer_bgp6 {
description "Cloudflare NL-ix 3 (IPv6)";
neighbor 2001:7f8:13::a501:3335:3 as 13335;
ipv6 { preference (PREFPEER-1); };
}
# protocol bgp peer4_nlix_cloudflare1 from peer_bgp4 {
# description "Cloudflare NL-ix 1 (IPv4)";
# neighbor 193.239.117.14 as 13335;
# ipv4 { preference (PREFPEER-1); };
# }
# protocol bgp peer4_nlix_cloudflare2 from peer_bgp4 {
# description "Cloudflare NL-ix 2 (IPv4)";
# neighbor 193.239.117.114 as 13335;
# ipv4 { preference (PREFPEER-1); };
# }
# protocol bgp peer4_nlix_cloudflare3 from peer_bgp4 {
# description "Cloudflare NL-ix 3 (IPv4)";
# neighbor 193.239.118.138 as 13335;
# ipv4 { preference (PREFPEER-1); };
# }
# protocol bgp peer6_nlix_cloudflare1 from peer_bgp6 {
# description "Cloudflare NL-ix 1 (IPv6)";
# neighbor 2001:7f8:13::a501:3335:1 as 13335;
# ipv6 { preference (PREFPEER-1); };
# }
# protocol bgp peer6_nlix_cloudflare2 from peer_bgp6 {
# description "Cloudflare NL-ix 2 (IPv6)";
# neighbor 2001:7f8:13::a501:3335:2 as 13335;
# ipv6 { preference (PREFPEER-1); };
# }
# protocol bgp peer6_nlix_cloudflare3 from peer_bgp6 {
# description "Cloudflare NL-ix 3 (IPv6)";
# neighbor 2001:7f8:13::a501:3335:3 as 13335;
# ipv6 { preference (PREFPEER-1); };
# }
protocol bgp peer4_nlix_jurrian from peer_bgp4 {
description "AS212635 aka jurrian (on NL-ix, IPv4)";
neighbor 193.239.117.55 as 212635;
+90 -16
View File
@@ -104,11 +104,9 @@ in
lvm = {
dmeventd.enable = true;
};
resolved = {
llmnr = "false";
extraConfig = ''
MulticastDNS=false
'';
resolved.settings.Resolve = {
LLMNR = false;
MulticastDNS = false;
};
netdata.enable = true;
@@ -164,12 +162,48 @@ in
};
wireguardPeers = [
{
wireguardPeerConfig = {
PublicKey = "7N9YdQaCMWWIwAnW37vrthm9ZpbnG4Lx3gheHeRYz2E=";
AllowedIPs = [ allAssignments.kelder.estuary.ipv4.address ];
PersistentKeepalive = 25;
}
];
};
}
{
"30-hillcrest" = {
netdevConfig = {
Name = "hillcrest";
Kind = "wireguard";
};
wireguardConfig = {
PrivateKeyFile = config.age.secrets."estuary/hillcrest-wg.key".path;
ListenPort = lib.my.c.hillcrest.vpn.port;
};
wireguardPeers = [
{
PublicKey = "+67Ks+ZRk1ssNCfg5BFKmIE9NtLasAxRE6XMqufx5GY=";
AllowedIPs = [ (net.cidr.host 2 prefixes.hillcrest.v4) ];
PersistentKeepalive = 25;
}
];
};
}
{
"30-john-valorant" = {
netdevConfig = {
Name = "john-valorant";
Kind = "wireguard";
};
wireguardConfig = {
PrivateKeyFile = config.age.secrets."estuary/john-valorant-wg.key".path;
ListenPort = lib.my.c.john-valorant.vpn.port;
};
wireguardPeers = [
{
PublicKey = "xyqKF0yOAv1bObN1paL2vATFh77pdFfvN+JmuAxaTCk=";
AllowedIPs = [ (net.cidr.host 2 prefixes.john-valorant.v4) ];
PersistentKeepalive = 25;
}
];
};
}
@@ -220,7 +254,7 @@ in
in
mkMerge
[
(mkIXPConfig "frys-ix" "185.1.203.196/24" "2001:7f8:10f::3:3850:196/64")
(mkIXPConfig "frys-ix" "185.1.160.196/23" "2001:7f8:10f::3:3850:196/64")
(mkIXPConfig "nl-ix" "193.239.116.145/22" "2001:7f8:13::a521:1024:1/64")
(mkIXPConfig "fogixp" "185.1.147.159/24" "2001:7f8:ca:1::159/64")
{
@@ -278,11 +312,10 @@ in
};
ipv6Prefixes = [
{
ipv6PrefixConfig.Prefix = prefixes.base.v6;
Prefix = prefixes.base.v6;
}
];
routes = map (r: { routeConfig = r; }) (flatten
([
routes = flatten ([
{
Destination = prefixes.vip1;
Gateway = allAssignments.colony.routing.ipv4.address;
@@ -323,7 +356,7 @@ in
Destination = prefixes."${pName}".v6;
Gateway = allAssignments.colony.internal.ipv6.address;
}
]) [ "vms" "ctrs" "oci" ])));
]) [ "vms" "ctrs" "oci" ]));
}
];
@@ -332,7 +365,7 @@ in
{
matchConfig.Name = "as211024";
networkConfig.IPv6AcceptRA = mkForce false;
routes = map (r: { routeConfig = r; }) [
routes = [
{
Destination = lib.my.c.home.prefixes.all.v4;
Gateway = lib.my.c.home.vips.as211024.v4;
@@ -344,10 +377,28 @@ in
matchConfig.Name = "kelder";
routes = [
{
routeConfig = {
Destination = allAssignments.kelder.estuary.ipv4.address;
Scope = "link";
}
];
};
"95-hillcrest" = {
matchConfig.Name = "hillcrest";
address = [ "${net.cidr.host 1 prefixes.hillcrest.v4}/32" ];
routes = [
{
Destination = net.cidr.host 2 prefixes.hillcrest.v4;
Scope = "link";
}
];
};
"95-john-valorant" = {
matchConfig.Name = "john-valorant";
address = [ "${net.cidr.host 1 prefixes.john-valorant.v4}/32" ];
routes = [
{
Destination = net.cidr.host 2 prefixes.john-valorant.v4;
Scope = "link";
}
];
};
@@ -361,6 +412,12 @@ in
"estuary/kelder-wg.key" = {
owner = "systemd-network";
};
"estuary/hillcrest-wg.key" = {
owner = "systemd-network";
};
"estuary/john-valorant-wg.key" = {
owner = "systemd-network";
};
"l2mesh/as211024.key" = {};
};
};
@@ -372,7 +429,13 @@ in
};
};
firewall = {
udp.allowed = [ 5353 lib.my.c.kelder.vpn.port ];
udp.allowed = [
5353
lib.my.c.kelder.vpn.port
lib.my.c.hillcrest.vpn.port
lib.my.c.john-valorant.vpn.port
];
tcp.allowed = [ 5353 "bgp" ];
nat = {
enable = true;
@@ -404,14 +467,23 @@ in
ip6 daddr ${aa.middleman.internal.ipv6.address} tcp dport { http, https, 8448 } accept
${matchInet "tcp dport { http, https } accept" "git"}
ip6 daddr ${aa.simpcraft-oci.internal.ipv6.address} tcp dport { 25565, 25575 } accept
ip6 daddr ${aa.simpcraft-oci.internal.ipv6.address} tcp dport 25565 accept
ip6 daddr ${aa.simpcraft-staging-oci.internal.ipv6.address} tcp dport 25565 accept
ip6 daddr ${aa.kevcraft-oci.internal.ipv6.address} tcp dport 25567 accept
ip6 daddr ${aa.kinkcraft-oci.internal.ipv6.address} tcp dport 25568 accept
ip6 daddr ${aa.graeme-oci.internal.ipv6.address} tcp dport 25569 accept
ip6 daddr ${aa.gam.internal.ipv6.address} tcp dport 7777 accept
return
}
chain routing-udp {
ip6 daddr ${aa.valheim-oci.internal.ipv6.address} udp dport { 2456-2457 } accept
ip6 daddr ${aa.waffletail.internal.ipv6.address} udp dport 41641 accept
ip6 daddr ${aa.simpcraft-oci.internal.ipv6.address} udp dport 25565 accept
ip6 daddr ${aa.enshrouded-oci.internal.ipv6.address} udp dport { 15636-15637 } accept
ip6 daddr ${aa.kevcraft-oci.internal.ipv6.address} udp dport 25567 accept
ip6 daddr ${aa.kinkcraft-oci.internal.ipv6.address} udp dport 25568 accept
ip6 daddr ${aa.graeme-oci.internal.ipv6.address} udp dport 25569 accept
ip6 daddr ${aa.gam.internal.ipv6.address} udp dport 7777 accept
return
}
chain filter-routing {
@@ -432,7 +504,7 @@ in
iifname { wan, as211024, $ixps } oifname base jump filter-routing
oifname $ixps jump ixp
iifname base oifname { base, wan, $ixps } accept
oifname { as211024, kelder } accept
oifname { as211024, kelder, hillcrest, john-valorant } accept
}
chain output {
oifname ifog ether type != vlan reject
@@ -444,6 +516,8 @@ in
${matchInet "meta l4proto { udp, tcp } th dport domain redirect to :5353" "estuary"}
}
chain postrouting {
oifname hillcrest snat ip to ${net.cidr.host 1 prefixes.hillcrest.v4}
oifname john-valorant snat ip to ${net.cidr.host 1 prefixes.john-valorant.v4}
ip saddr ${prefixes.all.v4} oifname != as211024 snat to ${assignments.internal.ipv4.address}
}
}
+38 -16
View File
@@ -14,7 +14,7 @@ in
owner = "pdns";
group = "pdns";
};
"estuary/pdns/recursor.conf" = {
"estuary/pdns/recursor.yml" = {
owner = "pdns-recursor";
group = "pdns-recursor";
};
@@ -31,7 +31,7 @@ in
pdns.recursor = {
enable = true;
extraSettingsFile = config.age.secrets."estuary/pdns/recursor.conf".path;
extraSettingsFile = config.age.secrets."estuary/pdns/recursor.yml".path;
};
};
@@ -44,34 +44,37 @@ in
};
pdns-recursor = {
dns = {
address = [
settings = {
incoming = {
listen = [
"127.0.0.1" "::1"
assignments.base.ipv4.address assignments.base.ipv6.address
];
allowFrom = [
allow_from = [
"127.0.0.0/8" "::1/128"
prefixes.all.v4 prefixes.all.v6
] ++ (with lib.my.c.tailscale.prefix; [ v4 v6 ]);
# DNS NOTIFY messages override TTL
allow_notify_for = authZones;
allow_notify_from = [ "127.0.0.0/8" "::1/128" ];
};
settings = {
query-local-address = [
outgoing = {
source_address = [
assignments.internal.ipv4.address
assignments.internal.ipv6.address
assignments.base.ipv6.address
];
forward-zones = map (z: "${z}=127.0.0.1:5353") authZones;
};
# DNS NOTIFY messages override TTL
allow-notify-for = authZones;
allow-notify-from = [ "127.0.0.0/8" "::1/128" ];
recursor = {
forward_zones = map (z: {
zone = z;
forwarders = [ "127.0.0.1:5353" ];
}) authZones;
webserver = true;
webserver-address = "::";
webserver-allow-from = [ "127.0.0.1" "::1" ];
lua-dns-script = pkgs.writeText "pdns-script.lua" ''
lua_dns_script = pkgs.writeText "pdns-script.lua" ''
function preresolve(dq)
if dq.qname:equal("nix-cache.nul.ie") then
dq:addAnswer(pdns.CNAME, "http.${config.networking.domain}.")
@@ -84,6 +87,13 @@ in
end
'';
};
webservice = {
webserver = true;
address = "::";
allow_from = [ "127.0.0.1" "::1" ];
};
};
};
};
@@ -153,6 +163,15 @@ in
simpcraft IN AAAA ${allAssignments.simpcraft-oci.internal.ipv6.address}
simpcraft-staging IN A ${assignments.internal.ipv4.address}
simpcraft-staging IN AAAA ${allAssignments.simpcraft-staging-oci.internal.ipv6.address}
enshrouded IN A ${assignments.internal.ipv4.address}
kevcraft IN A ${assignments.internal.ipv4.address}
kevcraft IN AAAA ${allAssignments.kevcraft-oci.internal.ipv6.address}
kinkcraft IN A ${assignments.internal.ipv4.address}
kinkcraft IN AAAA ${allAssignments.kinkcraft-oci.internal.ipv6.address}
graeme IN A ${assignments.internal.ipv4.address}
graeme IN AAAA ${allAssignments.graeme-oci.internal.ipv6.address}
terraria IN A ${assignments.internal.ipv4.address}
terraria IN AAAA ${allAssignments.gam.internal.ipv6.address}
mail-vm IN A ${net.cidr.host 0 prefixes.mail.v4}
mail-vm IN AAAA ${net.cidr.host 1 prefixes.mail.v6}
@@ -166,6 +185,9 @@ in
jam-fwd IN A ${allAssignments.shill.internal.ipv4.address}
jam-cust IN AAAA ${net.cidr.host 1 prefixes.jam.v6}
hillcrest-tun IN A ${net.cidr.host 2 prefixes.hillcrest.v4}
john-valorant-tun IN A ${net.cidr.host 2 prefixes.john-valorant.v4}
$TTL 3
_acme-challenge IN LUA TXT @@FILE@@
+3 -8
View File
@@ -4,7 +4,7 @@ let
inherit (lib) mkMerge mkDefault;
inherit (lib.my) net;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.colony) domain prefixes;
inherit (lib.my.c.colony) domain prefixes firewallForwards;
inherit (lib.my.c.nginx) baseHttpConfig proxyHeaders;
in
{
@@ -95,7 +95,7 @@ in
"*.${pubDomain}"
];
dnsProvider = "cloudflare";
credentialsFile = config.age.secrets."middleman/cloudflare-credentials.conf".path;
environmentFile = config.age.secrets."middleman/cloudflare-credentials.conf".path;
};
};
};
@@ -111,7 +111,6 @@ in
recommendedTlsSettings = true;
clientMaxBodySize = "0";
serverTokens = true;
sslDhparam = config.age.secrets."dhparams.pem".path;
# Based on recommended*Settings, but probably better to be explicit about these
appendHttpConfig = ''
@@ -182,11 +181,6 @@ in
secrets = {
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP+KINpHLMduBuW96JzfSRDLUzkI+XaCBghu5/wHiW5R";
files = {
"dhparams.pem" = {
owner = "acme";
group = "acme";
mode = "440";
};
"middleman/cloudflare-credentials.conf" = {
owner = "acme";
group = "acme";
@@ -197,6 +191,7 @@ in
firewall = {
tcp.allowed = [ 19999 "http" "https" ];
nat.forwardPorts."${allAssignments.estuary.internal.ipv4.address}" = firewallForwards allAssignments;
extraRules = ''
table inet filter {
chain forward {
+15 -15
View File
@@ -1,19 +1,7 @@
{ lib, pkgs, config, ... }:
let
inherit (builtins) toJSON;
inherit (lib) mkForce;
inherit (lib.my.c) pubDomain;
cfgFile = pkgs.writeText "gitea-actions-runner.yaml" (toJSON {
container = {
network = "podman";
privileged = true;
};
cache = {
enabled = true;
dir = "/var/cache/gitea-runner";
};
});
in
{
config = {
@@ -30,11 +18,24 @@ in
enable = true;
name = "main-docker";
labels = [
"debian-node-bullseye:docker://node:18-bullseye"
"ubuntu-22.04:docker://git.nul.ie/dev/actions-ubuntu:22.04"
"debian-node-trixie:docker://node:24-trixie"
"ubuntu-26.04:docker://git.nul.ie/dev/actions-ubuntu:26.04"
];
url = "https://git.${pubDomain}";
tokenFile = config.age.secrets."gitea/actions-runner.env".path;
settings = {
runner = {
timeout = "8h";
};
container = {
network = "podman";
privileged = true;
};
cache = {
enabled = true;
dir = "/var/cache/gitea-runner";
};
};
};
};
};
@@ -61,7 +62,6 @@ in
DynamicUser = mkForce false;
User = "gitea-runner";
Group = "gitea-runner";
ExecStart = mkForce "${config.services.gitea-actions-runner.package}/bin/act_runner -c ${cfgFile} daemon";
};
};
};
@@ -13,6 +13,7 @@ in
"/var/lib/machines/jam" = {
device = "/mnt/jam";
options = [ "bind" ];
fsType = "none";
};
};
@@ -47,10 +48,10 @@ in
};
ipv6Prefixes = [
{
ipv6PrefixConfig.Prefix = prefixes.jam.v6;
Prefix = prefixes.jam.v6;
}
];
routes = map (r: { routeConfig = r; }) [
routes = [
{
Destination = prefixes.jam.v4;
Scope = "link";
@@ -50,11 +50,6 @@ in
group = "matrix-synapse";
};
"chatterbox/syncv3.env" = {
owner = "matrix-syncv3";
group = "matrix-syncv3";
};
"chatterbox/mautrix-whatsapp.env" = {
owner = "mautrix-whatsapp";
group = "mautrix-whatsapp";
@@ -80,32 +75,21 @@ in
matrix-synapse.extraGroups = [
"mautrix-whatsapp"
];
matrix-syncv3 = {
isSystemUser = true;
uid = uids.matrix-syncv3;
group = "matrix-syncv3";
};
};
groups = {
matrix-syncv3.gid = gids.matrix-syncv3;
};
groups = { };
};
systemd = {
network.networks."80-container-host0" = networkdAssignment "host0" assignments.internal;
services = {
matrix-sliding-sync.serviceConfig = {
# Needs to be able to read its secrets
DynamicUser = mkForce false;
User = "matrix-syncv3";
Group = "matrix-syncv3";
};
} // (genAttrs [ "mautrix-whatsapp" "mautrix-meta-messenger" "mautrix-meta-instagram" ] (_: {
services = { } // (genAttrs [ "mautrix-whatsapp" "mautrix-meta-messenger" "mautrix-meta-instagram" ] (_: {
# ffmpeg needed to convert GIFs to video
path = with pkgs; [ ffmpeg ];
}));
};
# TODO/FIXME: https://github.com/NixOS/nixpkgs/issues/336052
nixpkgs.config.permittedInsecurePackages = [ "olm-3.2.16" ];
services = {
netdata.enable = true;
matrix-synapse = {
@@ -193,20 +177,10 @@ in
app_service_config_files = [
"/var/lib/heisenbridge/registration.yml"
config.age.secrets."chatterbox/doublepuppet.yaml".path
"/var/lib/mautrix-whatsapp/whatsapp-registration.yaml"
];
};
};
matrix-sliding-sync = {
enable = true;
createDatabase = false;
environmentFile = config.age.secrets."chatterbox/syncv3.env".path;
settings = {
SYNCV3_BINDADDR = "[::]:8009";
SYNCV3_SERVER = "http://localhost:8008";
};
};
heisenbridge = {
enable = true;
@@ -224,44 +198,72 @@ in
mautrix-whatsapp = {
enable = true;
# package = pkgs.mautrix-whatsapp.overrideAttrs (o: rec {
# version = "26.05";
# tag = "v0.2605.0";
# src = pkgs.fetchFromGitHub {
# owner = "mautrix";
# repo = "whatsapp";
# inherit tag;
# hash = "sha256-WlVfGQoP9e/wl98hUJei8O2JMcOKijoEY8XuU/z69Qk=";
# };
# vendorHash = "sha256-Hi/dZHJHoTTCnxLXgbkcYzuzis4fl5kxb5wMd9fKTY8=";
# });
environmentFile = config.age.secrets."chatterbox/mautrix-whatsapp.env".path;
settings = {
database = {
type = "postgres";
uri = "$MAU_WAPP_PSQL_URI";
};
homeserver = {
address = "http://localhost:8008";
domain = "nul.ie";
};
appservice = {
database = {
type = "postgres";
uri = "$MAU_WAPP_PSQL_URI";
};
id = "whatsapp2";
bot = {
username = "whatsapp2";
displayname = "WhatsApp Bridge Bot";
};
username_template = "wapp2_{{.}}";
};
bridge = {
username_template = "wapp2_{{.}}";
displayname_template = "{{or .BusinessName .PushName .JID}} (WA)";
personal_filtering_spaces = true;
delivery_receipts = true;
allow_user_invite = true;
url_previews = true;
command_prefix = "!wa";
login_shared_secret_map."nul.ie" = "$MAU_WAPP_DOUBLE_PUPPET_TOKEN";
encryption = {
allow = true;
default = true;
require = true;
};
permissions = {
"@dev:nul.ie" = "admin";
};
};
double_puppet = {
secrets."nul.ie" = "$MAU_WAPP_DOUBLE_PUPPET_TOKEN";
};
encryption = {
allow = true;
default = true;
require = true;
pickle_key = "maunium.net/go/mautrix-whatsapp";
};
matrix = {
delivery_receipts = true;
};
network = {
displayname_template = ''{{or .BusinessName .PushName .FullName .Phone "Unknown user"}} (WA)'';
url_previews = true;
};
};
};
# mautrix-meta.package = pkgs.mautrix-meta.overrideAttrs (o: rec {
# version = "26.05.1";
# tag = "v0.2605.1";
# src = pkgs.fetchFromGitHub {
# owner = "mautrix";
# repo = "meta";
# inherit tag;
# hash = "sha256-zpolDtwGulDTiojJPnkj9O0D5b4rgPYQX6A28rvuvM0=";
# };
# vendorHash = "sha256-+i45bXBhlXPXX24VMS9IJLLX+i4VPnqy5RAH4j88sTA=";
# });
mautrix-meta.instances = {
messenger = {
enable = true;
@@ -269,42 +271,48 @@ in
dataDir = "mautrix-messenger";
environmentFile = config.age.secrets."chatterbox/mautrix-messenger.env".path;
settings = {
database = {
type = "postgres";
uri = "$MAU_FBM_PSQL_URI";
};
homeserver = {
address = "http://localhost:8008";
domain = "nul.ie";
};
appservice = {
database = {
type = "postgres";
uri = "$MAU_FBM_PSQL_URI";
};
id = "fbm2";
bot = {
username = "messenger2";
displayname = "Messenger Bridge Bot";
avatar = "mxc://maunium.net/ygtkteZsXnGJLJHRchUwYWak";
};
};
meta.mode = "messenger";
bridge = {
username_template = "fbm2_{{.}}";
};
network = {
mode = "messenger";
displayname_template = ''{{or .DisplayName .Username "Unknown user"}} (FBM)'';
};
bridge = {
personal_filtering_spaces = true;
delivery_receipts = true;
management_room_text.welcome = "Hello, I'm a Messenger bridge bot.";
# management_room_text.welcome = "Hello, I'm a Messenger bridge bot.";
command_prefix = "!fbm";
login_shared_secret_map."nul.ie" = "$MAU_FBM_DOUBLE_PUPPET_TOKEN";
backfill = {
history_fetch_pages = 5;
enabled = true;
};
permissions = {
"@dev:nul.ie" = "admin";
};
};
double_puppet = {
secrets."nul.ie" = "$MAU_FBM_DOUBLE_PUPPET_TOKEN";
};
encryption = {
allow = true;
default = true;
require = true;
};
permissions = {
"@dev:nul.ie" = "admin";
};
matrix = {
delivery_receipts = true;
};
};
};
@@ -315,42 +323,49 @@ in
dataDir = "mautrix-instagram";
environmentFile = config.age.secrets."chatterbox/mautrix-instagram.env".path;
settings = {
database = {
type = "postgres";
uri = "$MAU_IG_PSQL_URI";
};
homeserver = {
address = "http://localhost:8008";
domain = "nul.ie";
};
appservice = {
database = {
type = "postgres";
uri = "$MAU_IG_PSQL_URI";
};
id = "instagram";
bot = {
username = "instagram";
displayname = "Instagram Bridge Bot";
avatar = "mxc://maunium.net/JxjlbZUlCPULEeHZSwleUXQv";
};
};
meta.mode = "instagram";
bridge = {
username_template = "ig_{{.}}";
};
network = {
mode = "instagram";
displayname_template = ''{{or .DisplayName .Username "Unknown user"}} (IG)'';
};
bridge = {
personal_filtering_spaces = true;
delivery_receipts = true;
management_room_text.welcome = "Hello, I'm an Instagram bridge bot.";
# management_room_text.welcome = "Hello, I'm an Instagram bridge bot.";
command_prefix = "!ig";
login_shared_secret_map."nul.ie" = "$MAU_IG_DOUBLE_PUPPET_TOKEN";
backfill = {
history_fetch_pages = 5;
enabled = true;
};
permissions = {
"@dev:nul.ie" = "admin";
"@adzerq:nul.ie" = "user";
};
};
double_puppet = {
secrets."nul.ie" = "$MAU_IG_DOUBLE_PUPPET_TOKEN";
};
encryption = {
allow = true;
default = true;
require = true;
};
permissions = {
"@dev:nul.ie" = "admin";
};
matrix = {
delivery_receipts = true;
};
};
};
@@ -9,5 +9,6 @@
./toot.nix
./waffletail.nix
./qclk
./gam.nix
];
}
@@ -0,0 +1,72 @@
{ lib, ... }:
let
inherit (lib.my) net;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.colony) domain prefixes;
in
{
nixos.systems.gam = { config, ... }: {
system = "x86_64-linux";
nixpkgs = "mine";
rendered = config.configuration.config.my.asContainer;
assignments = {
internal = {
name = "gam-ctr";
inherit domain;
ipv4.address = net.cidr.host 11 prefixes.ctrs.v4;
ipv6 = {
iid = "::11";
address = net.cidr.host 11 prefixes.ctrs.v6;
};
};
};
configuration = { lib, pkgs, config, assignments, allAssignments, ... }:
let
inherit (lib) mkMerge mkIf mkForce;
inherit (lib.my) networkdAssignment;
in
{
config = mkMerge [
{
my = {
deploy.enable = false;
server.enable = true;
secrets = {
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAvDlH3nT1kve741gBluYmn5KQs8yz7FAEt8qLt+f0K6";
files = {
"gam/terraria.conf" = {
owner = "terraria";
group = "terraria";
};
};
};
};
systemd = {
network.networks."80-container-host0" = networkdAssignment "host0" assignments.internal;
};
services = {
terraria = {
enable = true;
noUPnP = true;
messageOfTheDay = "sup gamers";
autoCreatedWorldSize = "large";
worldPath = "/var/lib/terraria/NotWorld.wld";
configFile = config.age.secrets."gam/terraria.conf".path;
openFirewall = true;
};
};
}
(mkIf config.my.build.isDevVM {
virtualisation = {
forwardPorts = [ ];
};
})
];
};
};
}
@@ -23,7 +23,7 @@ in
};
};
configuration = { lib, pkgs, config, ... }:
configuration = { lib, pkgs, config, allAssignments, ... }:
let
inherit (lib) mkForce;
in
@@ -39,10 +39,20 @@ in
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPUv1ntVrZv5ripsKpcOAnyDQX2PHjowzyhqWK10Ml53";
files = {
"jackflix/photoprism-pass.txt" = {};
"jackflix/copyparty-pass.txt" = {
owner = "copyparty";
group = "copyparty";
};
};
};
firewall = {
tcp.allowed = [
3923
];
};
};
users = with lib.my.c.ids; {
users = {
"${config.my.user.config.name}".extraGroups = [ "media" ];
@@ -50,21 +60,26 @@ in
transmission.extraGroups = [ "media" ];
radarr.extraGroups = [ "media" ];
sonarr.extraGroups = [ "media" ];
jellyseerr = {
seerr = {
isSystemUser = true;
uid = uids.jellyseerr;
group = "jellyseerr";
group = "seerr";
};
photoprism = {
isSystemUser = true;
uid = uids.photoprism;
group = "photoprism";
};
copyparty = {
uid = uids.copyparty;
extraGroups = [ "media" ];
};
};
groups = {
media.gid = 2000;
jellyseerr.gid = gids.jellyseerr;
seerr.gid = gids.jellyseerr;
photoprism.gid = gids.photoprism;
copyparty.gid = gids.copyparty;
};
};
@@ -73,13 +88,15 @@ in
jackett.bindsTo = [ "systemd-networkd-wait-online@vpn.service" ];
transmission.bindsTo = [ "systemd-networkd-wait-online@vpn.service" ];
radarr.serviceConfig.UMask = "0002";
sonarr.serviceConfig.UMask = "0002";
jellyseerr.serviceConfig = {
radarr.serviceConfig.UMask = mkForce "0002";
radarr.path = with pkgs; [ ffmpeg ];
sonarr.serviceConfig.UMask = mkForce "0002";
sonarr.path = with pkgs; [ ffmpeg ];
seerr.serviceConfig = {
# Needs to be able to read its secrets
DynamicUser = mkForce false;
User = "jellyseerr";
Group = "jellyseerr";
User = "seerr";
Group = "seerr";
};
# https://github.com/NixOS/nixpkgs/issues/258793#issuecomment-1748168206
@@ -99,6 +116,7 @@ in
transmission = {
enable = true;
package = pkgs.transmission_4;
downloadDirPermissions = null;
performanceNetParameters = true;
settings = {
@@ -123,10 +141,11 @@ in
};
};
flaresolverr.enable = true;
jackett.enable = true;
radarr.enable = true;
sonarr.enable = true;
jellyseerr = {
seerr = {
enable = true;
openFirewall = true;
};
@@ -150,6 +169,50 @@ in
PHOTOPRISM_DATABASE_DRIVER = "sqlite";
};
};
copyparty = {
enable = true;
package = pkgs.copyparty.override {
withMagic = true;
};
settings = {
name = "dev-stuff";
no-reload = true;
j = 8; # cores
http-only = true;
xff-src =
with allAssignments.middleman.internal;
[ "${ipv4.address}/32" prefixes.ctrs.v6 ];
rproxy = 1; # get if from x-forwarded-for
magic = true; # enable checking file magic on upload
hist = "/var/cache/copyparty";
shr = "/share"; # enable share creation
ed = true; # enable dotfiles
chmod-f = "664";
chmod-d = "775";
e2dsa = true; # file indexing
e2t = true; # metadata indexing
og-ua = "(Discord|Twitter|Slack)bot"; # embeds
theme = 6;
};
accounts.dev.passwordFile = config.age.secrets."jackflix/copyparty-pass.txt".path;
volumes = {
"/" = {
path = "/mnt/media/public";
access = {
A = "dev";
"r." = "*";
};
flags = {
shr_who = "no"; # no reason to have shares here
};
};
"/priv" = {
path = "/mnt/media/stuff";
access.A = "dev"; # dev has admin access
};
};
};
};
};
};
@@ -12,7 +12,7 @@ let
};
# Forwarded in AirVPN config
transmissionPeerPort = 47016;
transmissionPeerPort = 28457;
in
{
config = mkMerge [
@@ -71,14 +71,12 @@ in
RouteTable = routeTable;
};
wireguardPeers = [
{
# AirVPN NL
wireguardPeerConfig = {
{
Endpoint = "2a00:1678:1337:2329:e5f:35d4:4404:ef9f:1637";
PublicKey = "PyLCXAQT8KkM4T+dUsOQfn+Ub3pGxfGlxkIApuig+hk=";
PresharedKeyFile = config.age.secrets."${pskFile}".path;
AllowedIPs = [ "0.0.0.0/0" "::/0" ];
};
}
];
};
@@ -94,7 +92,7 @@ in
matchConfig.Name = "vpn";
address = [ "10.182.97.37/32" "fd7d:76ee:e68f:a993:735d:ef5e:6907:b122/128" ];
dns = [ "10.128.0.1" "fd7d:76ee:e68f:a993::1" ];
routingPolicyRules = map (r: { routingPolicyRuleConfig = r; }) [
routingPolicyRules = [
{
Family = "both";
SuppressPrefixLength = 0;
@@ -40,11 +40,6 @@ in
secrets = {
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAQM9U1e/XcUCyMJITrpAHjAGahpqkZCmtX6pJkYzuks";
files = {
"dhparams.pem" = {
owner = "acme";
group = "acme";
mode = "440";
};
"pdns-file-records.key" = {
owner = "acme";
group = "acme";
@@ -72,6 +67,13 @@ in
firewall = {
tcp.allowed = [ "http" "https" 8448 ];
extraRules = ''
table inet nat {
chain postrouting {
oifname host0 snat ip6 to ${assignments.internal.ipv6.address}
}
}
'';
};
nginx-sso = {
@@ -169,7 +171,7 @@ in
"*.${config.networking.domain}"
];
dnsProvider = "exec";
credentialsFile =
environmentFile =
let
script = pkgs.writeShellScript "lego-update-int.sh" ''
case "$1" in
@@ -200,7 +202,7 @@ in
"*.s3.${pubDomain}"
];
dnsProvider = "cloudflare";
credentialsFile = config.age.secrets."middleman/cloudflare-credentials.conf".path;
environmentFile = config.age.secrets."middleman/cloudflare-credentials.conf".path;
postRun =
let
sshKey = config.age.secrets."middleman/mailcow-ssh.key".path;
@@ -249,8 +251,10 @@ in
valid = "5s";
};
proxyResolveWhileRunning = true;
sslDhparam = config.age.secrets."dhparams.pem".path;
appendConfig = ''
worker_processes auto;
'';
# Based on recommended*Settings, but probably better to be explicit about these
appendHttpConfig = ''
${baseHttpConfig}
@@ -35,7 +35,6 @@ let
# For clients
(mkWellKnown "matrix/client" (toJSON {
"m.homeserver".base_url = "https://matrix.nul.ie";
"org.matrix.msc3575.proxy".url = "https://matrix-syncv3.nul.ie";
}))
];
};
@@ -50,6 +49,7 @@ let
"/.well-known/webfinger".return = "301 https://toot.nul.ie$request_uri";
"/.well-known/nodeinfo".return = "301 https://toot.nul.ie$request_uri";
"/.well-known/host-meta".return = "301 https://toot.nul.ie$request_uri";
"/.well-known/atproto-did".return = "301 https://pds.nul.ie$request_uri";
};
in
{
@@ -80,6 +80,10 @@ in
sha256 = "018wh6ps19n7323fi44njzj9yd4wqslc90dykbwfyscv7bgxhlar";
};
}
{
name = "ssh.pub";
path = lib.my.c.sshKeyFiles.me;
}
];
}
wellKnown
@@ -182,10 +186,6 @@ in
];
useACMEHost = pubDomain;
};
"matrix-syncv3.${pubDomain}" = {
locations."/".proxyPass = "http://chatterbox-ctr.${domain}:8009";
useACMEHost = pubDomain;
};
"element.${pubDomain}" =
let
@@ -327,6 +327,15 @@ in
useACMEHost = pubDomain;
};
"pds.nul.ie" = {
locations."/" = {
proxyPass = "http://toot-ctr.${domain}:3000";
proxyWebsockets = true;
extraConfig = proxyHeaders;
};
useACMEHost = pubDomain;
};
"share.${pubDomain}" = {
locations."/" = {
proxyPass = "http://object-ctr.${domain}:9090";
@@ -338,16 +347,13 @@ in
"stuff.${pubDomain}" = {
locations."/" = {
basicAuthFile = config.age.secrets."middleman/htpasswd".path;
root = "/mnt/media/stuff";
extraConfig = ''
fancyindex on;
fancyindex_show_dotfiles on;
'';
proxyPass = "http://jackflix-ctr.${domain}:3923";
};
useACMEHost = pubDomain;
};
"public.${pubDomain}" = {
onlySSL = false;
addSSL = true;
serverAliases = [ "p.${pubDomain}" ];
locations."/" = {
root = "/mnt/media/public";
@@ -368,6 +374,11 @@ in
useACMEHost = pubDomain;
};
"mc-map-kink.${pubDomain}" = {
locations."/".proxyPass = "http://kinkcraft-oci.${domain}:8100";
useACMEHost = pubDomain;
};
"librespeed.${domain}" = {
locations."/".proxyPass = "http://localhost:8989";
};
@@ -418,6 +429,22 @@ in
}
(ssoServer "generic")
];
"hass.${pubDomain}" = {
locations."/" = {
proxyPass = "http://hass-ctr.${home.domain}:8123";
proxyWebsockets = true;
extraConfig = proxyHeaders;
};
useACMEHost = pubDomain;
};
"hass-john.${pubDomain}" = {
locations."/" = {
proxyPass = "http://john-valorant-tun.${domain}:8123";
proxyWebsockets = true;
extraConfig = proxyHeaders;
};
useACMEHost = pubDomain;
};
};
minio =
@@ -35,6 +35,7 @@ in
"/var/lib/harmonia" = {
device = "/mnt/nix-cache";
options = [ "bind" ];
fsType = "none";
};
};
@@ -89,12 +90,17 @@ in
{
users = {
harmonia = {
isSystemUser = true;
group = "harmonia";
shell = pkgs.bashInteractive;
openssh.authorizedKeys.keyFiles = [
lib.my.c.sshKeyFiles.harmonia
];
};
};
groups = {
harmonia = { };
};
}
];
@@ -127,13 +133,24 @@ in
};
}
];
harmonia = {
environment.NIX_REMOTE = "/var/lib/harmonia";
harmonia-dev = {
# environment.RUST_LOG = mkForce "trace";
# serviceConfig = {
# StateDirectory = "harmonia";
# DynamicUser = mkForce false;
# };
};
harmonia-daemon = {
# environment.RUST_LOG = mkForce "trace";
preStart = ''
${config.nix.package}/bin/nix store ping
${config.nix.package}/bin/nix store info --store /var/lib/harmonia
'';
serviceConfig = {
User = "harmonia";
Group = "harmonia";
StateDirectory = "harmonia";
DynamicUser = mkForce false;
};
};
};
@@ -145,6 +162,9 @@ in
];
};
# TODO/FIXME: this is bad...
nixpkgs.config.permittedInsecurePackages = [ "minio-2025-10-15T17-29-55Z" ];
services = {
minio = {
enable = true;
@@ -216,7 +236,7 @@ in
atticd = {
enable = false;
credentialsFile = config.age.secrets."object/atticd.env".path;
environmentFile = config.age.secrets."object/atticd.env".path;
settings = {
listen = "[::]:8069";
allowed-hosts = [ "nix-cache.${pubDomain}" ];
@@ -235,11 +255,20 @@ in
};
};
harmonia = {
harmonia-dev = {
daemon = {
enable = true;
signKeyPath = config.age.secrets."nix-cache.key".path;
storeDir = "/nix/store";
dbPath = "/var/lib/harmonia/nix/var/nix/db/db.sqlite";
};
cache = {
enable = true;
signKeyPaths = [ config.age.secrets."nix-cache.key".path ];
settings = {
priority = 30;
virtual_nix_store = "/nix/store";
real_nix_store = "/var/lib/harmonia/nix/store";
};
};
};
@@ -26,6 +26,8 @@ in
let
inherit (lib) mkMerge mkIf genAttrs;
inherit (lib.my) networkdAssignment systemdAwaitPostgres;
pdsPort = 3000;
in
{
config = mkMerge [
@@ -36,7 +38,7 @@ in
secrets = {
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILSslLkDe54AKYzxdtKD70zcU72W0EpYsfbdJ6UFq0QK";
files = genAttrs
files = (genAttrs
(map (f: "toot/${f}") [
"postgres-password.txt"
"secret-key.txt"
@@ -48,7 +50,12 @@ in
(_: with config.services.mastodon; {
owner = user;
inherit group;
});
})) // {
"toot/pds.env" = {
owner = "pds";
group = "pds";
};
};
};
firewall = {
@@ -56,6 +63,7 @@ in
19999
"http"
pdsPort
];
};
};
@@ -79,7 +87,7 @@ in
netdata.enable = true;
mastodon = mkMerge [
rec {
enable = true;
enable = false;
localDomain = extraConfig.WEB_DOMAIN; # for nginx config
extraConfig = {
LOCAL_DOMAIN = "nul.ie";
@@ -87,7 +95,9 @@ in
};
secretKeyBaseFile = config.age.secrets."toot/secret-key.txt".path;
otpSecretFile = config.age.secrets."toot/otp-secret.txt".path;
# TODO: This was removed at some point.
# If we want to bring Mastodon back, this will probably need to be addressd.
# otpSecretFile = config.age.secrets."toot/otp-secret.txt".path;
vapidPrivateKeyFile = config.age.secrets."toot/vapid-key.txt".path;
vapidPublicKeyFile = toString (pkgs.writeText
"vapid-pubkey.txt"
@@ -155,6 +165,32 @@ in
};
};
};
bluesky-pds = {
enable = true;
environmentFiles = [ config.age.secrets."toot/pds.env".path ];
settings = {
PDS_HOSTNAME = "pds.nul.ie";
PDS_PORT = pdsPort;
PDS_BLOBSTORE_DISK_LOCATION = null;
PDS_BLOBSTORE_S3_BUCKET = "pds";
PDS_BLOBSTORE_S3_ENDPOINT = "https://s3.nul.ie/";
PDS_BLOBSTORE_S3_REGION = "eu-central-1";
PDS_BLOBSTORE_S3_ACCESS_KEY_ID = "pds";
PDS_BLOB_UPLOAD_LIMIT = "52428800";
PDS_EMAIL_FROM_ADDRESS = "pds@nul.ie";
PDS_DID_PLC_URL = "https://plc.directory";
PDS_INVITE_REQUIRED = "true";
PDS_BSKY_APP_VIEW_URL = "https://api.bsky.app";
PDS_BSKY_APP_VIEW_DID = "did:web:api.bsky.app";
PDS_REPORT_SERVICE_URL = "https://mod.bsky.app";
PDS_REPORT_SERVICE_DID = "did:plc:ar7c4by46qjdydhdevvrndac";
PDS_CRAWLERS = "https://bsky.network";
};
};
};
}
(mkIf config.my.build.isDevVM {
@@ -99,6 +99,8 @@ in
};
borgbackup.jobs.vaultwarden = {
readWritePaths = [ "/var/lib/borgbackup" "/var/cache/borgbackup" ];
paths = [ vwData ];
repo = "zh2855@zh2855.rsync.net:borg/vaultwarden2";
doInit = true;
@@ -86,7 +86,7 @@ in
interfaceName = "tailscale0";
extraUpFlags = [
"--operator=${config.my.user.config.name}"
"--login-server=https://ts.nul.ie"
"--login-server=https://hs.nul.ie"
"--netfilter-mode=off"
"--advertise-exit-node"
"--advertise-routes=${advRoutes}"
+3 -2
View File
@@ -140,10 +140,10 @@ in
};
ipv6Prefixes = [
{
ipv6PrefixConfig.Prefix = prefixes.ctrs.v6;
Prefix = prefixes.ctrs.v6;
}
];
routes = map (r: { routeConfig = r; }) [
routes = [
{
Destination = lib.my.c.tailscale.prefix.v4;
Gateway = allAssignments.waffletail.internal.ipv4.address;
@@ -217,6 +217,7 @@ in
toot = {};
waffletail = {};
qclk = {};
gam = {};
};
in
mkMerge [
+6 -1
View File
@@ -52,6 +52,10 @@ in
valheim-oci = 2;
simpcraft-oci = 3;
simpcraft-staging-oci = 4;
enshrouded-oci = 5;
kevcraft-oci = 6;
kinkcraft-oci = 7;
graeme-oci = 8;
};
configuration = { lib, pkgs, modulesPath, config, assignments, allAssignments, ... }:
@@ -66,6 +70,7 @@ in
./valheim.nix
./minecraft
# ./enshrouded.nix
];
config = mkMerge [
@@ -110,7 +115,7 @@ in
};
containers.containersConf.settings.network = {
network_backend = "netavark";
firewall_driver = "none";
firewall_driver = mkForce "none";
};
};
@@ -0,0 +1,35 @@
{ lib, config, allAssignments, ... }:
let
inherit (lib) concatStringsSep;
inherit (lib.my) dockerNetAssignment;
in
{
config = {
virtualisation.oci-containers.containers = {
enshrouded = {
image = "sknnr/enshrouded-dedicated-server@sha256:f163e8ba9caa2115d8a0a7b16c3696968242fb6fba82706d9a77a882df083497";
environment = {
SERVER_NAME = "UWUshrouded";
# SERVER_IP = "::"; # no IPv6?? :(
TZ = "Europe/Dublin";
};
environmentFiles = [ config.age.secrets."whale2/enshrouded.env".path ];
volumes = [
"enshrouded:/home/steam/enshrouded/savegame"
];
extraOptions = [
''--network=colony:${dockerNetAssignment allAssignments "enshrouded-oci"}''
];
};
};
my = {
secrets.files = {
"whale2/enshrouded.env" = {};
};
};
};
}
@@ -5,12 +5,13 @@ let
# devplayer0
op = "6d7d971b-ce10-435b-85c5-c99c0d8d288c";
kev = "703b378a-09f9-4c1d-9876-1c9305728c49";
whitelist = concatStringsSep "," [
op
"dcd2ecb9-2b5e-49cb-9d4f-f5a76162df56" # Elderlypug
"fcb26db2-c3ce-41aa-b588-efec79d37a8a" # Jesthral_
"1d366062-12c0-4e29-aba7-6ab5d8c6bb05" # shr3kas0ras
"703b378a-09f9-4c1d-9876-1c9305728c49" # OROURKEIRE
kev
"f105bbe6-eda6-4a13-a8cf-894e77cab77b" # Adzerq
"1fc94979-41fb-497a-81e9-34ae24ca537a" # johnnyscrims
"d53c91df-b6e6-4463-b106-e8427d7a8d01" # BossLonus
@@ -104,6 +105,130 @@ in
# ''--network=colony:${dockerNetAssignment allAssignments "simpcraft-staging-oci"}''
# ];
# };
kevcraft = {
# 2025.2.1-java21-alpine
image = "itzg/minecraft-server@sha256:57e319c15e9fee63f61029a65a33acc3de85118b21a2b4bb29f351cf4a915027";
environment = {
TYPE = "VANILLA";
VERSION = "1.20.1";
SERVER_PORT = "25567";
QUERY_PORT = "25567";
EULA = "true";
ENABLE_QUERY = "true";
ENABLE_RCON = "true";
MOTD = "§4§k----- §9K§ae§bv§cc§dr§ea§ff§6t §4§k-----";
ICON = "/ext/icon.png";
EXISTING_WHITELIST_FILE = "SYNCHRONIZE";
WHITELIST = whitelist;
EXISTING_OPS_FILE = "SYNCHRONIZE";
OPS = concatStringsSep "," [ op kev ];
DIFFICULTY = "normal";
SPAWN_PROTECTION = "0";
# VIEW_DISTANCE = "20";
MAX_MEMORY = "4G";
TZ = "Europe/Dublin";
};
environmentFiles = [ config.age.secrets."whale2/simpcraft.env".path ];
volumes = [
"kevcraft_data:/data"
"${./kev.png}:/ext/icon.png:ro"
];
extraOptions = [
''--network=colony:${dockerNetAssignment allAssignments "kevcraft-oci"}''
];
};
kinkcraft = {
# 2025.5.1-java21-alpine
image = "itzg/minecraft-server@sha256:de26c7128e3935f3be48fd30283f0b5a6da1b3d9f1a10c9f92502ee1ba072f7b";
environment = {
TYPE = "MODRINTH";
SERVER_PORT = "25568";
QUERY_PORT = "25568";
EULA = "true";
ENABLE_QUERY = "true";
ENABLE_RCON = "true";
MOTD = "§4§k----- §9K§ai§bn§ck§dc§er§fa§6f§5t §4§k-----";
ICON = "/ext/icon.png";
EXISTING_WHITELIST_FILE = "SYNCHRONIZE";
WHITELIST = whitelist;
EXISTING_OPS_FILE = "SYNCHRONIZE";
OPS = op;
DIFFICULTY = "normal";
SPAWN_PROTECTION = "0";
VIEW_DISTANCE = "20";
MAX_MEMORY = "6G";
MODRINTH_MODPACK = "https://cdn.modrinth.com/data/CIYf3Hk8/versions/NGutsQSd/Simpcraft-0.2.1.mrpack";
TZ = "Europe/Dublin";
};
environmentFiles = [ config.age.secrets."whale2/simpcraft.env".path ];
volumes = [
"kinkcraft_data:/data"
"${./icon.png}:/ext/icon.png:ro"
];
extraOptions = [
''--network=colony:${dockerNetAssignment allAssignments "kinkcraft-oci"}''
];
};
graeme = {
# 2026.2.1-java21-alpine
image = "itzg/minecraft-server@sha256:82adaddfe0156f07c34228f1c1065cdbd298abc174de0a9961abb068b11beebb";
environment = {
TYPE = "VANILLA";
SERVER_PORT = "25569";
QUERY_PORT = "25569";
EULA = "true";
ENABLE_QUERY = "true";
ENABLE_RCON = "false";
MOTD = "§4§k----- §9G§ar§ba§ce§dm§ee §4§k-----";
ICON = "/ext/icon.png";
EXISTING_WHITELIST_FILE = "SYNCHRONIZE";
WHITELIST = concatStringsSep "," [
op
"fffa146c-0bc8-421c-9e3a-3635c0aca2ea" # Scarlehh
"1ea05f48-76cc-4034-bcd3-2fa1fc5a7375" # Dario
"4bf837b1-01db-4491-a0e0-700d98542833" # JoeSpencer
"d07a9554-1b05-4b0b-b558-27e4a86e1f53" # AmyClover
];
EXISTING_OPS_FILE = "SYNCHRONIZE";
OPS = op;
DIFFICULTY = "hard";
SPAWN_PROTECTION = "0";
VIEW_DISTANCE = "20";
MAX_MEMORY = "4G";
TZ = "Europe/Dublin";
};
volumes = [
"graeme_data:/data"
"${./graeme.png}:/ext/icon.png:ro"
];
extraOptions = [
''--network=colony:${dockerNetAssignment allAssignments "graeme-oci"}''
];
};
};
services = {
@@ -123,6 +248,7 @@ in
within = "12H";
hourly = 48;
};
readWritePaths = [ "/var/lib/borgbackup" "/var/cache/borgbackup" ];
# Avoid Minecraft poking the files while we back up
preHook = rconCommand "save-off";
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+30 -8
View File
@@ -36,7 +36,7 @@ in
cpu = {
amd.updateMicrocode = true;
};
opengl.extraPackages = with pkgs; [
graphics.extraPackages = with pkgs; [
intel-media-driver
];
bluetooth.enable = true;
@@ -109,11 +109,7 @@ in
};
fstrim.enable = true;
resolved = {
enable = true;
extraConfig = mkForce "";
dnssec = "false";
};
resolved.settings.Resolve.LLMNR = mkForce true;
pipewire.extraConfig.pipewire = {
"10-buffer"."context.properties" = {
@@ -122,13 +118,14 @@ in
};
};
blueman.enable = true;
avahi.enable = true;
};
programs = {
virt-manager.enable = true;
wireshark = {
enable = true;
package = pkgs.wireshark-qt;
package = pkgs.wireshark;
};
};
virtualisation.libvirtd.enable = true;
@@ -150,6 +147,7 @@ in
mstflint
qperf
ethtool
android-tools
];
nix = {
@@ -164,6 +162,7 @@ in
network = {
netdevs = mkMerge [
(mkVLAN "lan-hi" vlans.hi)
(mkVLAN "lan-lo" vlans.lo)
];
links = {
"10-et2.5g" = {
@@ -185,7 +184,7 @@ in
networks = {
"30-et100g" = {
matchConfig.Name = "et100g";
vlan = [ "lan-hi" ];
vlan = [ "lan-hi" "lan-lo" ];
networkConfig.IPv6AcceptRA = false;
};
"40-lan-hi" = mkMerge [
@@ -193,6 +192,22 @@ in
# So we don't drop the IP we use to connect to NVMe-oF!
{ networkConfig.KeepConfiguration = "static"; }
];
"45-lan-lo" = {
matchConfig.Name = "lan-lo";
networkConfig = {
DHCP = "ipv4";
IPv6AcceptRA = true;
UseDomains = false;
};
dhcpV4Config = {
UseDNS = false;
UseGateway = false;
};
ipv6AcceptRAConfig = {
UseDNS = false;
UseGateway = false;
};
};
};
};
};
@@ -215,8 +230,15 @@ in
];
};
programs = {
claude-code = {
enable = true;
};
};
services = {
blueman-applet.enable = true;
easyeffects.enable = true;
};
wayland.windowManager.sway = {
+7
View File
@@ -188,6 +188,13 @@
hostBDF = "44:00.4";
};
};
qemuFlags = [
"device qemu-xhci,id=xhci"
# Front-right port?
"device usb-host,hostbus=1,hostport=4"
# Front-left port
"device usb-host,hostbus=1,hostport=3"
];
};
};
};
@@ -1,5 +1,6 @@
{
imports = [
./unifi.nix
# ./unifi.nix
./hass.nix
];
}
@@ -0,0 +1,263 @@
{ lib, ... }:
let
inherit (lib.my) net;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.home) domain prefixes vips hiMTU;
in
{
nixos.systems.hass = { config, ... }: {
system = "x86_64-linux";
nixpkgs = "mine";
rendered = config.configuration.config.my.asContainer;
assignments = {
hi = {
name = "hass-ctr";
altNames = [ "frigate" ];
inherit domain;
mtu = hiMTU;
ipv4 = {
address = net.cidr.host 103 prefixes.hi.v4;
mask = 22;
gateway = vips.hi.v4;
};
ipv6 = {
iid = "::5:3";
address = net.cidr.host (65536*5+3) prefixes.hi.v6;
};
};
lo = {
name = "hass-ctr-lo";
inherit domain;
mtu = 1500;
ipv4 = {
address = net.cidr.host 103 prefixes.lo.v4;
mask = 21;
gateway = null;
};
ipv6 = {
iid = "::5:3";
address = net.cidr.host (65536*5+3) prefixes.lo.v6;
};
};
};
configuration = { lib, config, pkgs, assignments, allAssignments, ... }:
let
inherit (lib) mkMerge mkIf mkForce;
inherit (lib.my) networkdAssignment;
hassCli = pkgs.writeShellScriptBin "hass-cli" ''
export HASS_SERVER="http://localhost:${toString config.services.home-assistant.config.http.server_port}"
export HASS_TOKEN="$(< ${config.age.secrets."hass/cli-token.txt".path})"
exec ${pkgs.home-assistant-cli}/bin/hass-cli "$@"
'';
in
{
config = {
my = {
deploy.enable = false;
server.enable = true;
secrets = {
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpYX2WbYwUqHp8bFFf0eHFrqrR8xp8IheguA054F8V4";
files = {
"hass/cli-token.txt" = {
owner = config.my.user.config.name;
};
};
};
firewall = {
tcp.allowed = [ "http" 1883 ];
};
};
environment = {
systemPackages = with pkgs; [
usbutils
hassCli
];
};
systemd = {
network.networks = {
"80-container-host0" = networkdAssignment "host0" assignments.hi;
"80-container-lan-lo" = networkdAssignment "lan-lo" assignments.lo;
};
};
services = {
mosquitto = {
enable = true;
listeners = [
{
omitPasswordAuth = true;
settings = {
allow_anonymous = true;
};
}
];
};
go2rtc = {
enable = true;
settings = {
streams = {
reolink_living_room = [
# "http://reolink-living-room.${domain}/flv?port=1935&app=bcs&stream=channel0_main.bcs&user=admin#video=copy#audio=copy#audio=opus"
"rtsp://admin:@reolink-living-room:554/h264Preview_01_main"
];
webcam_office = [
"ffmpeg:device?video=/dev/video0&video_size=1024x576#video=h264"
];
};
};
};
frigate = {
enable = true;
hostname = "frigate.${domain}";
settings = {
mqtt = {
enabled = true;
host = "localhost";
topic_prefix = "frigate";
};
cameras = {
reolink_living_room = {
ffmpeg.inputs = [
{
path = "rtsp://127.0.0.1:8554/reolink_living_room";
input_args = "preset-rtsp-restream";
roles = [ "record" "detect" ];
}
];
detect = {
enabled = false;
};
record = {
enabled = true;
retain.days = 1;
};
};
webcam_office = {
ffmpeg.inputs = [
{
path = "rtsp://127.0.0.1:8554/webcam_office";
input_args = "preset-rtsp-restream";
roles = [ "record" "detect" ];
}
];
detect.enabled = false;
record = {
enabled = true;
retain.days = 1;
};
};
};
};
};
home-assistant =
let
cfg = config.services.home-assistant;
pyirishrail = ps: ps.buildPythonPackage rec {
pname = "pyirishrail";
version = "0.0.2";
src = pkgs.fetchFromGitHub {
owner = "ttroy50";
repo = "pyirishrail";
tag = version;
hash = "sha256-NgARqhcXP0lgGpgBRiNtQaSn9JcRNtCcZPljcL7t3Xc=";
};
dependencies = with ps; [
requests
];
pyproject = true;
build-system = [ ps.setuptools ];
};
in
{
enable = true;
extraComponents = [
"default_config"
"esphome"
"google_translate"
"met"
"zha"
"denonavr"
"webostv"
"androidtv_remote"
"heos"
"mqtt"
"wled"
];
extraPackages = python3Packages: with python3Packages; [
zlib-ng
isal
gtts
(pyirishrail python3Packages)
];
customComponents = with pkgs.home-assistant-custom-components; [
alarmo
frigate
west_wood_club
];
configWritable = false;
openFirewall = true;
config = {
default_config = {};
homeassistant = {
name = "Home";
unit_system = "metric";
currency = "EUR";
country = "IE";
time_zone = "Europe/Dublin";
external_url = "https://hass.${pubDomain}";
internal_url = "http://hass-ctr.${domain}:${toString cfg.config.http.server_port}";
};
http = {
use_x_forwarded_for = true;
trusted_proxies = with allAssignments.middleman.internal; [
ipv4.address
ipv6.address
];
ip_ban_enabled = false;
};
automation = "!include automations.yaml";
script = "!include scripts.yaml";
scene = "!include scenes.yaml";
sensor = [
{
platform = "irish_rail_transport";
name = "To Work from Home";
station = "Glenageary";
stops_at = "Dublin Connolly";
direction = "Northbound";
}
{
platform = "irish_rail_transport";
name = "To Home from Work";
station = "Dublin Connolly";
stops_at = "Glenageary";
direction = "Southbound";
}
];
};
};
};
};
};
};
}
@@ -55,7 +55,8 @@ in
unifi = {
enable = true;
openFirewall = true;
unifiPackage = pkgs.unifi8;
unifiPackage = pkgs.unifi;
mongodbPackage = pkgs.mongodb-7_0;
};
};
};
+45 -2
View File
@@ -29,7 +29,7 @@ in
configuration = { lib, modulesPath, pkgs, config, assignments, allAssignments, ... }:
let
inherit (lib) mapAttrs mkMerge;
inherit (lib) mapAttrs mkMerge mkForce;
inherit (lib.my) networkdAssignment;
inherit (lib.my.c) networkd;
inherit (lib.my.c.home) domain;
@@ -83,6 +83,12 @@ in
};
};
environment = {
systemPackages = with pkgs; [
usbutils
];
};
systemd.network = {
links = {
"10-lan-hi" = {
@@ -105,6 +111,13 @@ in
MTUBytes = toString lib.my.c.home.hiMTU;
};
};
"10-lan-lo-ctrs" = {
matchConfig = {
Driver = "virtio_net";
PermanentMACAddress = "52:54:00:a5:7e:93";
};
linkConfig.Name = "lan-lo-ctrs";
};
};
networks = {
@@ -118,8 +131,28 @@ in
linkConfig.RequiredForOnline = "no";
networkConfig = networkd.noL3;
};
"30-lan-lo-ctrs" = {
matchConfig.Name = "lan-lo-ctrs";
linkConfig.RequiredForOnline = "no";
networkConfig = networkd.noL3;
};
};
};
systemd.nspawn = {
hass = {
networkConfig = {
MACVLAN = mkForce "lan-hi-ctrs:host0 lan-lo-ctrs:lan-lo";
};
};
};
systemd.services = {
"systemd-nspawn@hass".serviceConfig.DeviceAllow = [
"char-ttyUSB rw"
"char-video4linux rw"
];
};
my = {
secrets = {
@@ -141,7 +174,17 @@ in
containers.instances =
let
instances = {
unifi = {};
# unifi = {};
hass = {
bindMounts = {
"/dev/bus/usb/001/002".readOnly = false;
"/dev/video0".readOnly = false;
"/dev/serial/by-id/usb-Nabu_Casa_Home_Assistant_Connect_ZBT-1_ce549704fe38ef11a2c2e5d154516304-if00-port0" = {
readOnly = false;
mountPoint = "/dev/ttyUSB0";
};
};
};
};
in
mkMerge [
+19 -36
View File
@@ -1,7 +1,6 @@
index: { lib, allAssignments, ... }:
let
inherit (builtins) elemAt;
inherit (lib) concatStringsSep;
inherit (lib.my) net mkVLAN;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.home) domain vlans prefixes vips routers routersPubV4;
@@ -122,11 +121,9 @@ in
};
services = {
resolved = {
llmnr = "false";
extraConfig = ''
MulticastDNS=false
'';
resolved.settings.Resolve = {
LLMNR = false;
MulticastDNS = false;
};
iperf3 = {
@@ -142,8 +139,8 @@ in
onState = [ "configured" ];
script = ''
#!${pkgs.runtimeShell}
if [ $IFACE = "wan-ifb" ]; then
${pkgs.iproute2}/bin/tc filter add dev wan parent ffff: matchall action mirred egress redirect dev $IFACE
if [ "$IFACE" = "wan-ifb" ]; then
${pkgs.iproute2}/bin/tc filter add dev wan parent ffff: matchall action mirred egress redirect dev "$IFACE"
fi
'';
};
@@ -151,28 +148,6 @@ in
};
nginx.enable = true;
tailscale =
let
advRoutes = concatStringsSep "," [
prefixes.all.v4
prefixes.all.v6
];
in
{
enable = true;
authKeyFile = config.age.secrets."tailscale-auth.key".path;
openFirewall = true;
interfaceName = "tailscale0";
extraUpFlags = [
"--operator=${config.my.user.config.name}"
"--login-server=https://ts.nul.ie"
"--netfilter-mode=off"
"--advertise-exit-node"
"--advertise-routes=${advRoutes}"
"--accept-routes=false"
];
};
};
networking = { inherit domain; };
@@ -250,7 +225,7 @@ in
networkConfig = networkd.noL3;
extraConfig = ''
[CAKE]
Bandwidth=235M
Bandwidth=490M
RTTSec=50ms
PriorityQueueingPreset=besteffort
# DOCSIS preset
@@ -274,7 +249,7 @@ in
extraConfig = ''
[CAKE]
Parent=root
Bandwidth=24M
Bandwidth=48M
RTTSec=50ms
'';
}
@@ -299,11 +274,20 @@ in
{
matchConfig.Name = "as211024";
networkConfig.IPv6AcceptRA = mkForce false;
routes = map (r: { routeConfig = r; }) [
routes = [
{
Destination = lib.my.c.colony.prefixes.all.v4;
Gateway = allAssignments.estuary.as211024.ipv4.address;
}
{
Destination = lib.my.c.tailscale.prefix.v4;
Gateway = allAssignments.britway.as211024.ipv4.address;
}
{
Destination = lib.my.c.tailscale.prefix.v6;
Gateway = allAssignments.britway.as211024.ipv6.address;
}
];
}
];
@@ -315,7 +299,7 @@ in
{
"60-lan-hi" = {
routes = map (r: { routeConfig = r; }) [
routes = [
{
Destination = elemAt routersPubV4 otherIndex;
Gateway = net.cidr.host (otherIndex + 1) prefixes.hi.v4;
@@ -330,7 +314,6 @@ in
secrets = {
files = {
"l2mesh/as211024.key" = {};
"tailscale-auth.key" = {};
};
};
@@ -340,7 +323,7 @@ in
};
};
firewall = {
trustedInterfaces = [ "lan-hi" "lan-lo" "tailscale0" ];
trustedInterfaces = [ "lan-hi" "lan-lo" ];
udp.allowed = [ 5353 ];
tcp.allowed = [ 5353 ];
nat = {
@@ -0,0 +1,74 @@
# Blocklist for LG WebOS Services (US)
ad.lgappstv.com
ibis.lgappstv.com
info.lgsmartad.com
lgtvsdp.com
ngfts.lge.com
rdx2.lgtvsdp.com
smartshare.lgtvsdp.com
lgappstv.com
us.ad.lgsmartad.com
us.ibs.lgappstv.com
us.info.lgsmartad.com
us.lgtvsdp.com
# Community Contributions
lgad.cjpowercast.com
edgesuite.net
yumenetworks.com
smartclip.net
smartclip.com
# Non-US Entries
rdx2.lgtvsdp.com
info.lgsmartad.com
ibs.lgappstv.com
lgtvsdp.com
lgappstv.com
smartshare.lgtvsdp.com
# Full Block for Europe and Other Regions
de.ad.lgsmartad.com
de.emp.lgsmartplatform.com
de.ibs.lgappstv.com
de.info.lgsmartad.com
de.lgeapi.com
de.lgtvsdp.com
de.rdx2.lgtvsdp.com
eu.ad.lgsmartad.com
eu.ibs.lgappstv.com
eu.info.lgsmartad.com
app-lgwebos.pluto.tv
it.lgtvsdp.com
it.lgeapi.com
it.emp.lgsmartplatform.com
# LG ThinQ Services
eic.common.lgthinq.com
eic.iotservice.lgthinq.com
eic.service.lgthinq.com
eic.ngfts.lge.com
eic.svc-lgthinq-com.aws-thinq-prd.net
eic.cdpsvc.lgtvcommon.com
eic.cdpbeacon.lgtvcommon.com
eic.cdplauncher.lgtvcommon.com
eic.homeprv.lgtvcommon.com
eic.lgtviot.com
eic.nudge.lgtvcommon.com
eic.rdl.lgtvcommon.com
eic.recommend.lgtvcommon.com
eic.service.lgtvcommon.com
gb-lgeapi-com.esi-prd.net
gb.lgeapi.com
lgtvonline.lge.com
lg-channelplus-de-beacons.xumo.com
lg-channelplus-de-mds.xumo.com
lg-channelplus-eu-beacons.xumo.com
lg-channelplus-eu-mds.xumo.com
kr-op-v2.lgthinqhome.com
ngfts.lge.com
noti.lgthinq.com
objectcontent.lgthinq.com
# Update Server Block
#snu.lge.com
+49 -20
View File
@@ -19,7 +19,7 @@ in
owner = "pdns";
group = "pdns";
};
"home/pdns/recursor.conf" = {
"home/pdns/recursor.yml" = {
owner = "pdns-recursor";
group = "pdns-recursor";
};
@@ -28,53 +28,79 @@ in
pdns.recursor = {
enable = true;
extraSettingsFile = config.age.secrets."home/pdns/recursor.conf".path;
extraSettingsFile = config.age.secrets."home/pdns/recursor.yml".path;
};
};
services = {
pdns-recursor = {
dns = {
address = [
settings = {
incoming = {
listen = [
"127.0.0.1" "::1"
assignments.hi.ipv4.address assignments.hi.ipv6.address
assignments.lo.ipv4.address assignments.lo.ipv6.address
];
allowFrom = [
allow_from = [
"127.0.0.0/8" "::1/128"
prefixes.hi.v4 prefixes.hi.v6
prefixes.lo.v4 prefixes.lo.v6
] ++ (with lib.my.c.tailscale.prefix; [ v4 v6 ]);
};
settings = {
query-local-address = [
"0.0.0.0"
"::"
];
forward-zones = map (z: "${z}=127.0.0.1:5353") authZones;
# DNS NOTIFY messages override TTL
allow-notify-for = authZones;
allow-notify-from = [ "127.0.0.0/8" "::1/128" ];
allow_notify_for = authZones;
allow_notify_from = [ "127.0.0.0/8" "::1/128" ];
};
webserver = true;
webserver-address = "::";
webserver-allow-from = [ "127.0.0.1" "::1" ];
outgoing = {
source_address = [ "0.0.0.0" "::" ];
};
recursor = {
forward_zones = map (z: {
zone = z;
forwarders = [ "127.0.0.1:5353" ];
}) authZones;
lua_dns_script = pkgs.writeText "pdns-script.lua" ''
blocklist = newDS()
lua-dns-script = pkgs.writeText "pdns-script.lua" ''
-- Disney+ doesn't like our IP space...
function preresolve(dq)
local name = dq.qname:toString()
-- Disney+ doesn't like our IP space...
if dq.qtype == pdns.AAAA and (string.find(name, "disneyplus") or string.find(name, "disney-plus") or string.find(name , "disney.api")) then
dq.rcode = 0
return true
end
if blocklist:check(dq.qname) then
if dq.qtype == pdns.A then
dq:addAnswer(dq.qtype, "127.0.0.1")
elseif dq.qtype == pdns.AAAA then
dq:addAnswer(dq.qtype, "::1")
end
return true
end
return false
end
for line in io.lines("${./dns-blocklist.txt}") do
entry = line:gsub("%s+", "")
if entry ~= "" and string.sub(entry, 1, 1) ~= "#" then
blocklist:add(entry)
end
end
'';
};
webservice = {
webserver = true;
address = "::";
allow_from = [ "127.0.0.1" "::1" ];
};
};
};
};
@@ -206,6 +232,9 @@ in
ups IN A ${net.cidr.host 20 prefixes.lo.v4}
palace-kvm IN A ${net.cidr.host 21 prefixes.lo.v4}
reolink-living-room IN A ${net.cidr.host 45 prefixes.lo.v4}
nixlight IN A ${net.cidr.host 46 prefixes.lo.v4}
${lib.my.dns.fwdRecords {
inherit allAssignments names;
domain = config.networking.domain;
+12 -7
View File
@@ -2,7 +2,7 @@
import argparse
import subprocess
import CloudFlare
import cloudflare
def main():
parser = argparse.ArgumentParser(description='Cloudflare DNS update script')
@@ -19,17 +19,22 @@ def main():
if args.api_token_file:
with open(args.api_token_file) as f:
cf_token = f.readline().strip()
cf = cloudflare.Cloudflare(api_token=cf_token)
cf = CloudFlare.CloudFlare(token=cf_token)
zones = cf.zones.get(params={'name': args.zone})
zones = list(cf.zones.list(name=args.zone))
assert zones, f'Zone {args.zone} not found'
records = cf.zones.dns_records.get(zones[0]['id'], params={'name': args.record})
assert len(zones) == 1, f'More than one zone found for {args.zone}'
zone = zones[0]
records = list(cf.dns.records.list(zone_id=zone.id, name=args.record, type='A'))
assert records, f'Record {args.record} not found in zone {args.zone}'
assert len(records) == 1, f'More than one record found for {args.record}'
record = records[0]
print(f'Updating {args.record} -> {address}')
cf.zones.dns_records.patch(
zones[0]['id'], records[0]['id'],
data={'type': 'A', 'name': args.record, 'content': address})
cf.dns.records.edit(
zone_id=zone.id, dns_record_id=record.id, name=args.record,
type='A', content=address)
if __name__ == '__main__':
main()
+53
View File
@@ -132,6 +132,59 @@ in
hw-address = "24:8a:07:a8:fe:3a";
ip-address = net.cidr.host 40 prefixes.lo.v4;
}
{
# avr
hw-address = "8c:a9:6f:30:03:6b";
ip-address = net.cidr.host 41 prefixes.lo.v4;
}
{
# tv
hw-address = "00:a1:59:b8:4d:86";
ip-address = net.cidr.host 42 prefixes.lo.v4;
}
{
# android tv
hw-address = "b8:7b:d4:95:c6:74";
ip-address = net.cidr.host 43 prefixes.lo.v4;
}
{
# hass-panel
hw-address = "80:30:49:cd:d7:51";
ip-address = net.cidr.host 44 prefixes.lo.v4;
}
{
# reolink-living-room
hw-address = "ec:71:db:30:69:a4";
ip-address = net.cidr.host 45 prefixes.lo.v4;
}
{
# nixlight
hw-address = "00:4b:12:3b:d3:14";
ip-address = net.cidr.host 46 prefixes.lo.v4;
}
];
}
{
id = 3;
subnet = prefixes.untrusted.v4;
interface = "lan-untrusted";
option-data = [
{
name = "routers";
data = vips.untrusted.v4;
}
{
name = "domain-name-servers";
data = "1.1.1.1, 1.0.0.1";
}
];
pools = [
{
pool = if index == 0
then "192.168.80.10 - 192.168.80.127"
else "192.168.80.128 - 192.168.80.250";
}
];
}
];
+5 -10
View File
@@ -20,10 +20,7 @@ let
};
vlanIface = vlan: if vlan == "as211024" then vlan else "lan-${vlan}";
vrrpIPs = family: concatMap (vlan: (optional (family == "v6") {
addr = "fe80::1/64";
dev = vlanIface vlan;
}) ++ [
vrrpIPs = family: concatMap (vlan: [
{
addr = "${vips.${vlan}.${family}}/${toString (net.cidr.length prefixes.${vlan}.${family})}";
dev = vlanIface vlan;
@@ -61,14 +58,12 @@ in
v6Alive = pingScriptFor "v6" [ "2606:4700:4700::1111" "2001:4860:4860::8888" "2600::" ];
};
vrrpInstances = {
v4 = mkVRRP "v4" 51 // {
extraConfig = ''
notify_master "${config.systemd.package}/bin/systemctl start tailscaled.service" root
notify_backup "${config.systemd.package}/bin/systemctl stop tailscaled.service" root
'';
};
v4 = mkVRRP "v4" 51;
v6 = (mkVRRP "v6" 52) // {
extraConfig = ''
virtual_ipaddress_excluded {
${concatMapStringsSep "\n" (vlan: "fe80::1/64 dev ${vlanIface vlan}") (attrNames vips)}
}
notify_master "${config.systemd.package}/bin/systemctl start radvd.service" root
notify_backup "${config.systemd.package}/bin/systemctl stop radvd.service" root
'';
+2 -2
View File
@@ -24,8 +24,8 @@ in
onState = [ "routable" ];
script = ''
#!${pkgs.runtimeShell}
if [ $IFACE = "lan" ]; then
${mstpd}/sbin/mstpctl setforcevers $IFACE rstp
if [ "$IFACE" = "lan" ]; then
${mstpd}/sbin/mstpctl setforcevers "$IFACE" rstp
fi
'';
};
+2 -2
View File
@@ -45,12 +45,12 @@
services = {
mjpg-streamer = {
enable = true;
enable = false;
inputPlugin = "input_uvc.so";
outputPlugin = "output_http.so -w @www@ -n -p 5050";
};
octoprint = {
enable = true;
enable = false;
host = "::";
extraConfig = {
plugins = {
@@ -26,10 +26,10 @@ in
config = {
# Hardware acceleration for Jellyfin
hardware.opengl = {
hardware.graphics = {
enable = true;
extraPackages = with pkgs; [
vaapiIntel
intel-vaapi-driver
intel-ocl
];
};
@@ -73,14 +73,23 @@ in
RootDirectory = lib.mkForce "";
};
radarr.serviceConfig.UMask = "0002";
sonarr.serviceConfig.UMask = "0002";
radarr.serviceConfig.UMask = lib.mkForce "0002";
sonarr.serviceConfig.UMask = lib.mkForce "0002";
};
};
nixpkgs.config.permittedInsecurePackages = [
# FIXME: This is needed for Sonarr
"aspnetcore-runtime-wrapped-6.0.36"
"aspnetcore-runtime-6.0.36"
"dotnet-sdk-wrapped-6.0.428"
"dotnet-sdk-6.0.428"
];
services = {
transmission = {
enable = true;
package = pkgs.transmission_4;
downloadDirPermissions = null;
performanceNetParameters = true;
settings = {
@@ -73,14 +73,12 @@ in
RouteTable = routeTable;
};
wireguardPeers = [
{
# AirVPN IE
wireguardPeerConfig = {
{
Endpoint = "146.70.94.2:1637";
PublicKey = "PyLCXAQT8KkM4T+dUsOQfn+Ub3pGxfGlxkIApuig+hk=";
PresharedKeyFile = config.age.secrets."${pskFile}".path;
AllowedIPs = [ "0.0.0.0/0" "::/0" ];
};
}
];
};
@@ -97,7 +95,7 @@ in
matchConfig.Name = "vpn";
address = [ "10.161.170.28/32" "fd7d:76ee:e68f:a993:b12d:6d15:c80a:9516/128" ];
dns = [ "10.128.0.1" "fd7d:76ee:e68f:a993::1" ];
routingPolicyRules = map (r: { routingPolicyRuleConfig = r; }) [
routingPolicyRules = [
{
Family = "both";
SuppressPrefixLength = 0;
@@ -60,7 +60,7 @@ in
"*.${domain}"
];
dnsProvider = "cloudflare";
credentialsFile = config.age.secrets."kelder/cloudflare-credentials.conf".path;
environmentFile = config.age.secrets."kelder/cloudflare-credentials.conf".path;
};
};
};
@@ -88,16 +88,18 @@ in
};
services = {
resolved.extraConfig = mkForce "";
resolved.settings.Resolve = mkForce { };
nextcloud = {
enable = true;
package = pkgs.nextcloud29;
# TODO: Might need to do some bullshit to go from Nextcloud 28 (?) to 32
package = pkgs.nextcloud32;
datadir = "/mnt/storage/nextcloud";
hostName = "cloud.${domain}";
https = true;
config = {
adminpassFile = config.age.secrets."kelder/nextcloud-root.txt".path;
dbtype = "sqlite";
};
settings = {
updatechecker = false;
@@ -13,11 +13,6 @@ in
owner = "nginx";
group = "nginx";
};
"dhparams.pem" = {
owner = "acme";
group = "acme";
mode = "440";
};
};
firewall = {
@@ -35,7 +30,6 @@ in
recommendedTlsSettings = true;
clientMaxBodySize = "0";
serverTokens = true;
sslDhparam = config.age.secrets."dhparams.pem".path;
# Based on recommended*Settings, but probably better to be explicit about these
appendHttpConfig = ''
+4 -5
View File
@@ -121,8 +121,7 @@ in
samba = {
enable = true;
enableNmbd = true;
shares = {
settings = {
storage = {
path = "/mnt/storage";
browseable = "yes";
@@ -131,6 +130,8 @@ in
"directory mask" = "0775";
};
};
nmbd.enable = true;
};
samba-wsdd.enable = true;
@@ -180,12 +181,10 @@ in
};
wireguardPeers = [
{
wireguardPeerConfig = {
PublicKey = "bP1XUNxp9i8NLOXhgPaIaRzRwi5APbam44/xjvYcyjU=";
Endpoint = "${allAssignments.estuary.internal.ipv4.address}:${toString lib.my.c.kelder.vpn.port}";
AllowedIPs = [ "0.0.0.0/0" ];
PersistentKeepalive = 25;
};
}
];
};
@@ -213,7 +212,7 @@ in
address = with assignments.estuary; [
(with ipv4; "${address}/${toString mask}")
];
routingPolicyRules = map (r: { routingPolicyRuleConfig = r; }) [
routingPolicyRules = [
{
Family = "both";
SuppressPrefixLength = 0;
+3 -13
View File
@@ -14,7 +14,7 @@
cpu = {
intel.updateMicrocode = true;
};
opengl.extraPackages = with pkgs; [
graphics.extraPackages = with pkgs; [
intel-media-driver
];
bluetooth.enable = true;
@@ -99,12 +99,6 @@
};
};
resolved = {
enable = true;
extraConfig = mkForce "";
dnssec = "false";
};
fprintd.enable = true;
blueman.enable = true;
@@ -118,7 +112,7 @@
steam.enable = true;
wireshark = {
enable = true;
package = pkgs.wireshark-qt;
package = pkgs.wireshark;
};
};
@@ -177,7 +171,7 @@
programs = {
fish = {
shellAbbrs = {
tsup = "doas tailscale up --login-server=https://ts.nul.ie --accept-routes";
tsup = "doas tailscale up --login-server=https://hs.nul.ie --accept-routes";
};
};
};
@@ -190,10 +184,6 @@
config = {
input."1:1:AT_Translated_Set_2_keyboard".xkb_layout = "ie";
output.eDP-1.scale = "1";
keybindings = {
"XF86MonBrightnessDown" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-";
"XF86MonBrightnessUp" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set +5%";
};
};
};
+1 -1
View File
@@ -23,7 +23,7 @@ let
pkgs = pkgs'.${config'.nixpkgs}.${config'.system};
allPkgs = mapAttrs (_: p: p.${config'.system}) pkgs';
modules' = [ hmFlakes.${config'.home-manager}.nixosModule ] ++ (attrValues cfg.modules);
modules' = [ hmFlakes.${config'.home-manager}.nixosModules.default ] ++ (attrValues cfg.modules);
in
# Import eval-config ourselves since the flake now force-sets lib
import "${pkgsFlake}/nixos/lib/eval-config.nix" {
+6 -4
View File
@@ -31,8 +31,10 @@
server.enable = true;
};
image = {
baseName = "jackos-installer";
};
isoImage = {
isoBaseName = "jackos-installer";
volumeID = "jackos-${config.system.nixos.release}-${pkgs.stdenv.hostPlatform.uname.processor}";
edition = "devplayer0";
appendToMenuLabel = " /dev/player0 Installer";
@@ -61,8 +63,8 @@
};
networking = {
# Will be set dynamically
hostName = "";
# Will be set dynamically, but need something to satisfy `/etc/os-release` stuff
hostName = "installer";
useNetworkd = false;
};
@@ -97,7 +99,7 @@
# Enable wpa_supplicant, but don't start it by default.
networking.wireless.enable = mkDefault true;
networking.wireless.userControlled.enable = true;
networking.wireless.userControlled = true;
systemd.services.wpa_supplicant.wantedBy = mkForce [];
# Tell the Nix evaluator to garbage collect more aggressively.

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