100 Commits

Author SHA1 Message Date
jackos1998 dc1ec3bf5a docs: Ask before running fmt
CI / Check, build and cache nixfiles (push) Successful in 45m46s
fmt reindents non-canonically-formatted files wholesale, burying logical
changes in whitespace churn. Note to match surrounding style by hand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 16:11:25 +01:00
jackos1998 823ed83252 nixos/home: Unstack river WAN to single-tag VLAN 10
Digiweb's BRAS answers PADI only on a single-tagged ISP VLAN (pon-isp,
10); the double-stacked wan-pon (131) transport never got a PADO because
the UniFi access switch can't push the outer tag onto already-tagged
ingress frames (no QinQ/tag-stacking). Attach VLAN 10 directly to the
physical `lan` link for now.

Kept reversible via a `wanStacked` flag: flip it back to true to restore
the 131->10 stack once a QinQ-capable device sits in front of the ONT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 16:11:25 +01:00
jackos1998 300103f2ba nixos/home: Correct river wan-vlan-outer MTU to 1512
CI / Check, build and cache nixfiles (push) Successful in 45m9s
The outer WAN VLAN carries the inner VLAN's frames, whose 4B 802.1Q tag
counts as payload at this layer. To fit the inner's 1508B baby-jumbo
payload plus that tag it needs 1512, not 1508. Harmless before only
because the physical `lan` (9000) absorbed the extra bytes; this makes
the outer's MTU literally reflect what it transports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 10:57:13 +01:00
jackos1998 d33f32ce5b docs: Document home-router HA pattern and boxes terminology
CI / Check, build and cache nixfiles (push) Successful in 45m40s
Capture what this session surfaced: the routing-common indexed-module
active/backup pattern, VRRP VIPs for gateway+DNS, the wan-online.target
convention, the networkd helper locations, and the "boxes not fleet"
terminology. Also note the fish login shell caveat for ssh scripting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 22:44:22 +01:00
jackos1998 14d9bba4eb nixos/home: Serve DNS on VRRP VIP and fix ipsec WAN ordering
Clients were handed both routers' real addresses as resolvers, so a downed
router meant per-query resolver timeouts. Serve pdns-recursor on the VRRP
VIPs (with non-local bind so the backup can pre-bind them) and advertise the
VIP via kea and radvd, so DNS follows the master. untrusted advertises
Cloudflare over v6 to match its v4 config.

ipsec started before the WAN's public IP was up: stream's wan carries a
static modem address that satisfies wait-online before the DHCP lease, so
libreswan loaded its mesh conns (left=<public IP>) unoriented and never
initiated. Gate stream's wan-online.target on the DHCP default route
instead, and mkForce ipsec onto wan-online.target only (dropping the
strongswan/libreswan multi-user.target pull-in) so the gate actually holds
on both boxes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 22:38:02 +01:00
jackos1998 e63cee7b09 nixos/home: Migrate river WAN to PPPoE over Digiweb
river's WAN moves to a PPPoE session (Digiweb, over VLAN 10 with baby-jumbo
1508 MTU) driven by pppd, with its ip-up/ip-down hooks toggling a new
wan-online.target. stream keeps its DHCP WAN, so the existing 50-wan /
50-wan-ifb networkd config moves out of routing-common into stream, and the
shared consumers (ipsec, ipv6-clear-default-route) now attach to
wan-online.target instead of depending on systemd-networkd-wait-online@wan
directly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 22:37:05 +01:00
jackos1998 a8318d3de2 nixos/home: Separate HDD LV for frigate
CI / Check, build and cache nixfiles (push) Successful in 51m19s
2026-07-19 11:47:37 +01:00
jackos1998 aab4a193ae nixos/home: Add brian
CI / Check, build and cache nixfiles (push) Failing after 3h2m50s
2026-07-15 02:22:15 +01:00
jackos1998 541102f683 nixos/netboot: Switch to iPXE snp.efi
CI / Check, build and cache nixfiles (push) Successful in 45m47s
2026-07-14 22:42:04 +01:00
jackos1998 805590a705 nixos/routing-common: Fix mstpd missing bridge-stp script
CI / Check, build and cache nixfiles (push) Successful in 46m37s
2026-07-12 18:40:38 +01:00
jackos1998 f8dbd99a7b nixos/routing-common: Add missing ttl param to DDNS update 2026-07-12 17:15:57 +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
94 changed files with 4648 additions and 2223 deletions
+3 -3
View File
@@ -7,10 +7,10 @@ on:
jobs:
check:
name: Check, build and cache nixfiles
runs-on: ubuntu-22.04
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
+143
View File
@@ -0,0 +1,143 @@
# 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 — always called **"boxes"**, never "fleet". 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`). Boxes default to the `fish` login shell, so pipe multi-statement remote
scripts through `bash` (e.g. `ssh-machine <name> bash -s < script.sh`) rather than `&&`/`for`.
- `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.
### Home routers (`nixos/boxes/home/routing-common`)
The two home routers, `river` and `stream`, share `routing-common`, which is a **function of an
`index`** (`import ../../routing-common 0` for river, `1` for stream). The index derives per-box
addresses, keepalived VRRP priorities/state, DNS `ns` numbering, etc., so the two boxes are an
active/backup HA pair from one definition. They differ where hardware/uplink differ: `stream` has a
DHCP WAN, `river` runs PPPoE (`services.pppd`, Digiweb) — box-specific bits live in the respective
box file, not `routing-common`.
- **HA is VRRP (`keepalived`).** Per-VLAN floating **VIPs** (`lib.my.c.home.vips`) are what clients
use as both gateway *and* DNS server. `kea` (DHCP) and `radvd` (RAs; started only on the master)
hand out the VIP, and `pdns-recursor` binds the VIPs (with `net.ipv*.ip_nonlocal_bind` so the
backup can pre-bind). Point client-facing services at the VIP, not a box's real address, so
failover follows the master instead of relying on client resolver timeouts.
- **`wan-online.target`** is a shared abstract target meaning "the public WAN/IPv4 route is up".
`routing-common` only declares it; each box wires *how it is reached* (`stream`: a oneshot that
waits for the DHCP default route; `river`: the pppd `ip-up`/`ip-down` hooks). Services that need
the WAN attach **to** it via `wantedBy` + `partOf` + `after` (not `requires`/`wants`), so an empty
target is never pulled in and prematurely activated, and they re-load on WAN flap.
- networkd helpers used heavily here: `lib.my.networkdAssignment` and `lib.my.mkVLAN` live under
**`lib.my`**, while networkd snippet constants like `networkd.noL3` live under **`lib.my.c`** —
easy to mix up. Set an interface MTU via the `.network`'s `linkConfig.MTUBytes` (`[Link]`), not
`netdevConfig` (`[NetDev]` rejects `MTUBytes`).
## 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`.
**Ask before running `fmt`** — some files aren't canonically formatted, so `fmt` can reindent a
whole file and bury a logical change in whitespace churn. Match the surrounding style by hand and
leave formatting to the user unless they ask.
- 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.
- Call the machines **"boxes"**, never "fleet".
- Commit subjects follow `area/scope: Capitalized summary` (e.g. `nixos/home: ...`); keep logically
distinct changes in separate commits.
+30 -1
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,6 +92,12 @@ 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";
@@ -144,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
'';
}
Generated
+313 -184
View File
@@ -11,11 +11,11 @@
"systems": "systems_7"
},
"locked": {
"lastModified": 1723293904,
"narHash": "sha256-b+uqzj+Wa6xgMS9aNbX4I+sXeb5biPDi39VgvSFqFvU=",
"lastModified": 1761656077,
"narHash": "sha256-lsNWuj4Z+pE7s0bd2OKicOFq9bK86JE0ZGeKJbNqb94=",
"owner": "ryantm",
"repo": "agenix",
"rev": "f6291c5935fdc4e0bef208cfc0dcab7e3f7a1c41",
"rev": "9ba0d85de3eaa7afeab493fed622008b6e4924f5",
"type": "github"
},
"original": {
@@ -31,29 +31,27 @@
"nixpkgs": [
"nixpkgs-unstable"
],
"poetry2nix": "poetry2nix"
"pyproject-nix": "pyproject-nix"
},
"locked": {
"lastModified": 1718746012,
"narHash": "sha256-sp9vGl3vWXvD/C2JeMDi5nbW6CkKIC3Q2JMGKwexYEs=",
"ref": "refs/heads/master",
"rev": "ea24100bd4a914b9e044a2085a3785a6bd3a3833",
"revCount": 5,
"type": "git",
"url": "https://git.nul.ie/dev/boardie"
"lastModified": 1757170758,
"narHash": "sha256-FyO+Brz5eInmdAkG8B2rJAfrNGMCsDQ8BPflKV2+r5g=",
"owner": "devplayer0",
"repo": "boardie",
"rev": "ed5fd520d5bf122871b5508dd3c1eda28d6e515d",
"type": "github"
},
"original": {
"type": "git",
"url": "https://git.nul.ie/dev/boardie"
"owner": "devplayer0",
"repo": "boardie",
"type": "github"
}
},
"borgthin": {
"inputs": {
"devshell": "devshell_2",
"flake-utils": "flake-utils_5",
"nixpkgs": [
"nixpkgs-mine"
]
"flake-utils": "flake-utils_4",
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1732994213,
@@ -69,13 +67,49 @@
"type": "github"
}
},
"copyparty": {
"inputs": {
"flake-utils": "flake-utils_5",
"nixpkgs": [
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1781351267,
"narHash": "sha256-86HFs1K+LRlx8t4AjaMdU5qlg4O7kLz1VlnNapKZIuY=",
"owner": "9001",
"repo": "copyparty",
"rev": "90639de9840d7dcc2d9000026fe547f666c1d550",
"type": "github"
},
"original": {
"owner": "9001",
"repo": "copyparty",
"type": "github"
}
},
"crane": {
"locked": {
"lastModified": 1725409566,
"narHash": "sha256-PrtLmqhM6UtJP7v7IGyzjBFhbG4eOAHT6LPYOFmYfbk=",
"lastModified": 1780532242,
"narHash": "sha256-D+BsdpxmtUwtqGoY0IXPhHgTlmqgcZKCEo1oMyn7ep0=",
"owner": "ipetkov",
"repo": "crane",
"rev": "7e4586bad4e3f8f97a9271def747cf58c4b68f3c",
"rev": "59a82a1222dd3b2080b5cc52a1a2e8d5f1b77f37",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"crane_2": {
"locked": {
"lastModified": 1760924934,
"narHash": "sha256-tuuqY5aU7cUkR71sO2TraVKK2boYrdW3gCSXUkF4i44=",
"owner": "ipetkov",
"repo": "crane",
"rev": "c6b4d5308293d0d04fcfeee92705017537cad02f",
"type": "github"
},
"original": {
@@ -93,11 +127,11 @@
]
},
"locked": {
"lastModified": 1700795494,
"narHash": "sha256-gzGLZSiOhf155FW7262kdHo2YDeugp3VuIFb4/GGng0=",
"lastModified": 1744478979,
"narHash": "sha256-dyN+teG9G82G+m+PX/aSAagkC+vUv0SgUw3XkPhQodQ=",
"owner": "lnl7",
"repo": "nix-darwin",
"rev": "4b9b83d5a92e8c1fbfd8eb27eda375908c11ec4d",
"rev": "43975d782b418ebf4969e9ccba82466728c2851b",
"type": "github"
},
"original": {
@@ -116,11 +150,11 @@
"utils": "utils"
},
"locked": {
"lastModified": 1727447169,
"narHash": "sha256-3KyjMPUKHkiWhwR91J1YchF6zb6gvckCAY1jOE+ne0U=",
"lastModified": 1781023725,
"narHash": "sha256-Gt+qFANcrDRjl3xzidLYrAUQCd3808iuAsLwZbYYAEU=",
"owner": "serokell",
"repo": "deploy-rs",
"rev": "aa07eb05537d4cd025e2310397a6adcedfe72c76",
"rev": "2ce9051767ee4d1a3c43b52ba327431783bfd463",
"type": "github"
},
"original": {
@@ -150,8 +184,8 @@
},
"devshell-tools": {
"inputs": {
"flake-utils": "flake-utils_9",
"nixpkgs": "nixpkgs_4"
"flake-utils": "flake-utils_10",
"nixpkgs": "nixpkgs_5"
},
"locked": {
"lastModified": 1710099997,
@@ -169,8 +203,8 @@
},
"devshell_2": {
"inputs": {
"flake-utils": "flake-utils_4",
"nixpkgs": "nixpkgs_3"
"flake-utils": "flake-utils_3",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1671489820,
@@ -193,11 +227,11 @@
]
},
"locked": {
"lastModified": 1728330715,
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
"lastModified": 1768818222,
"narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
"owner": "numtide",
"repo": "devshell",
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
"rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
"type": "github"
},
"original": {
@@ -209,11 +243,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
@@ -241,6 +275,24 @@
}
},
"flake-utils_10": {
"inputs": {
"systems": "systems_9"
},
"locked": {
"lastModified": 1709126324,
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_11": {
"inputs": {
"systems": "systems_10"
},
@@ -258,7 +310,7 @@
"type": "github"
}
},
"flake-utils_11": {
"flake-utils_12": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
@@ -292,24 +344,6 @@
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_3"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_4": {
"locked": {
"lastModified": 1642700792,
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
@@ -324,7 +358,7 @@
"type": "github"
}
},
"flake-utils_5": {
"flake-utils_4": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
@@ -339,7 +373,58 @@
"type": "github"
}
},
"flake-utils_5": {
"locked": {
"lastModified": 1678901627,
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_6": {
"inputs": {
"systems": "systems_4"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_7": {
"inputs": {
"systems": "systems_5"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_8": {
"inputs": {
"systems": "systems_6"
},
@@ -357,31 +442,16 @@
"type": "github"
}
},
"flake-utils_7": {
"locked": {
"lastModified": 1659877975,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_8": {
"flake-utils_9": {
"inputs": {
"systems": "systems_8"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@@ -390,21 +460,47 @@
"type": "github"
}
},
"flake-utils_9": {
"harmonia": {
"inputs": {
"systems": "systems_9"
"crane": "crane",
"nix": "nix",
"nixpkgs": [
"nixpkgs-unstable"
],
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1709126324,
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
"lastModified": 1781128165,
"narHash": "sha256-97WpKZkaNAL5g7MtASLwqnrJrvrLpQRr6cXWiRNLiXQ=",
"owner": "nix-community",
"repo": "harmonia",
"rev": "f0dd1094cdc8d72e038cf9347cacfa9272a8f72d",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"owner": "nix-community",
"repo": "harmonia",
"type": "github"
}
},
"hass-west-wood": {
"inputs": {
"flake-utils": "flake-utils_7",
"nixpkgs": [
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1781402797,
"narHash": "sha256-pBdDca7xv1nuP0kj+gC5g5AcR/DV+9Zy3CS6uDOMdJ4=",
"owner": "devplayer0",
"repo": "hass-west-wood",
"rev": "3e6ef7a9084e4053c82dea20127a775e7bcf77a5",
"type": "github"
},
"original": {
"owner": "devplayer0",
"repo": "hass-west-wood",
"type": "github"
}
},
@@ -417,11 +513,11 @@
]
},
"locked": {
"lastModified": 1703113217,
"narHash": "sha256-7ulcXOk63TIT2lVDSExj7XzFx09LpdSAPtvgtM7yQPE=",
"lastModified": 1745494811,
"narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "3bfaacf46133c037bb356193bd2f1765d9dc82c1",
"rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be",
"type": "github"
},
"original": {
@@ -437,16 +533,16 @@
]
},
"locked": {
"lastModified": 1732466619,
"narHash": "sha256-T1e5oceypZu3Q8vzICjv1X/sGs9XfJRMW5OuXHgpB3c=",
"lastModified": 1781319724,
"narHash": "sha256-ZGuxexEMo4Xv28KJ0dX/m/PHN4oZIOnxHZpNTyrvx4M=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "f3111f62a23451114433888902a55cf0692b408d",
"rev": "8355f0a16b2dbb06a97959a918af5b239bbe05ae",
"type": "github"
},
"original": {
"id": "home-manager",
"ref": "release-24.11",
"ref": "release-26.05",
"type": "indirect"
}
},
@@ -457,11 +553,11 @@
]
},
"locked": {
"lastModified": 1732884235,
"narHash": "sha256-r8j6R3nrvwbT1aUp4EPQ1KC7gm0pu9VcV1aNaB+XG6Q=",
"lastModified": 1781305496,
"narHash": "sha256-g8Vv4Qfc7n+lgov97REu3X6BeJtvYY0hlSUZR1GrGQQ=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "819f682269f4e002884702b87e445c82840c68f2",
"rev": "c87a39aa979acc4848016d2220c6238390d84779",
"type": "github"
},
"original": {
@@ -470,12 +566,18 @@
}
},
"impermanence": {
"inputs": {
"home-manager": [
"home-manager-unstable"
],
"nixpkgs": "nixpkgs_4"
},
"locked": {
"lastModified": 1731242966,
"narHash": "sha256-B3C3JLbGw0FtLSWCjBxU961gLNv+BOOBC6WvstKLYMw=",
"lastModified": 1769548169,
"narHash": "sha256-03+JxvzmfwRu+5JafM0DLbxgHttOQZkUtDWBmeUkN8Y=",
"owner": "nix-community",
"repo": "impermanence",
"rev": "3ed3f0eaae9fcc0a8331e77e9319c8a4abd8a71a",
"rev": "7b1d382faf603b6d264f58627330f9faa5cba149",
"type": "github"
},
"original": {
@@ -484,41 +586,51 @@
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"boardie",
"poetry2nix",
"nixpkgs"
]
},
"libnetRepo": {
"flake": false,
"locked": {
"lastModified": 1703863825,
"narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "5163432afc817cf8bd1f031418d1869e4c9d5547",
"lastModified": 1776595118,
"narHash": "sha256-6bIEi8q5hXCHU9nApTbQXvpljMWldg3QipCD+jkOGK8=",
"owner": "oddlama",
"repo": "nixos-extra-modules",
"rev": "84207afebb794be7b53cfc9768730f37c64f4a13",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"owner": "oddlama",
"repo": "nixos-extra-modules",
"type": "github"
}
},
"nix": {
"flake": false,
"locked": {
"lastModified": 1780652321,
"narHash": "sha256-o/6YXRB6AbeL4SYtSHlJ9oEROl6Wmf7yheJNa3fAv2I=",
"owner": "nixos",
"repo": "nix",
"rev": "d1f04a798cf4276da59567c07a3bf4a628669288",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nix",
"type": "github"
}
},
"nixGL": {
"inputs": {
"flake-utils": "flake-utils_7",
"flake-utils": "flake-utils_8",
"nixpkgs": [
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1713543440,
"narHash": "sha256-lnzZQYG0+EXl/6NkGpyIz+FEOc/DSEG57AP1VsdeNrM=",
"lastModified": 1762090880,
"narHash": "sha256-fbRQzIGPkjZa83MowjbD2ALaJf9y6KMDdJBQMKFeY/8=",
"owner": "nix-community",
"repo": "nixGL",
"rev": "310f8e49a149e4c9ea52f1adf70cdc768ec53f8a",
"rev": "b6105297e6f0cd041670c3e8628394d4ee247ed5",
"type": "github"
},
"original": {
@@ -545,11 +657,11 @@
},
"nixpkgs-mine": {
"locked": {
"lastModified": 1732985787,
"narHash": "sha256-6rSJ9L4QywpHLi/xvpOHdTuPm6/eOJcXxnYzDbP3U1k=",
"lastModified": 1781356656,
"narHash": "sha256-Ygkl3ZBJ434/WhwdK1FyvPMeHvNPAopg3KE/1HtcJuk=",
"owner": "devplayer0",
"repo": "nixpkgs",
"rev": "a28c46933ef5038fb7a2dd483b85152a539c7969",
"rev": "a15e20705db295f621cb5bb63613f03a9373323f",
"type": "github"
},
"original": {
@@ -561,11 +673,11 @@
},
"nixpkgs-mine-stable": {
"locked": {
"lastModified": 1732985894,
"narHash": "sha256-YYuQQCcSF6KjgtAenZJiBmqt5jqP3UvYgC424VQ+22s=",
"lastModified": 1781356876,
"narHash": "sha256-s8ed+zuk5wrbyhtDQpkxycAcLmhQH9umGRuVRBNKUbU=",
"owner": "devplayer0",
"repo": "nixpkgs",
"rev": "e0a3f4e2bbc5f7b681e344b389dcbab23f2e92a8",
"rev": "2eb8bacf9f641d4510fc43ba7fc0eea7dfdf5b24",
"type": "github"
},
"original": {
@@ -577,26 +689,26 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1732824227,
"narHash": "sha256-fYNXgpu1AEeLyd3fQt4Ym0tcVP7cdJ8wRoqJ+CtTRyY=",
"lastModified": 1780902259,
"narHash": "sha256-q8yYEC5f1mFlQO9RGna4LTc9QrcvWunX6FYp83munkQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c71ad5c34d51dcbda4c15f44ea4e4aa6bb6ac1e9",
"rev": "bd0ff2d3eac24699c3664d5966b9ef36f388e2ca",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-24.11",
"ref": "nixos-26.05",
"type": "indirect"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1732758367,
"narHash": "sha256-RzaI1RO0UXqLjydtz3GAXSTzHkpb/lLD1JD8a0W4Wpo=",
"lastModified": 1781074563,
"narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "fa42b5a5f401aab8a32bd33c9a4de0738180dc59",
"rev": "9ae611a455b90cf061d8f332b977e387bda8e1ca",
"type": "github"
},
"original": {
@@ -606,22 +718,6 @@
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1718632497,
"narHash": "sha256-YtlyfqOdYMuu7gumZtK0Kg7jr4OKfHUhJkZfNUryw68=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c58b4a9118498c1055c5908a5bbe666e56abe949",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable-small",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1643381941,
"narHash": "sha256-pHTwvnN4tTsEKkWlXQ8JMY423epos8wUOhthpwJjtpc=",
@@ -637,7 +733,37 @@
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1673606088,
"narHash": "sha256-wdYD41UwNwPhTdMaG0AIe7fE1bAdyHe6bB4HLUqUvck=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "37b97ae3dd714de9a17923d004a2c5b5543dfa6d",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1768564909,
"narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_5": {
"locked": {
"lastModified": 1709309926,
"narHash": "sha256-VZFBtXGVD9LWTecGi6eXrE0hJ/mVB3zGUlHImUs2Qak=",
@@ -653,7 +779,7 @@
"type": "github"
}
},
"nixpkgs_5": {
"nixpkgs_6": {
"locked": {
"lastModified": 1674990008,
"narHash": "sha256-4zOyp+hFW2Y7imxIpZqZGT8CEqKmDjwgfD6BzRUE0mQ=",
@@ -669,44 +795,43 @@
"type": "github"
}
},
"poetry2nix": {
"pyproject-nix": {
"inputs": {
"flake-utils": "flake-utils_3",
"nix-github-actions": "nix-github-actions",
"nixpkgs": "nixpkgs_2",
"systems": "systems_4",
"treefmt-nix": "treefmt-nix"
"nixpkgs": [
"boardie",
"nixpkgs"
]
},
"locked": {
"lastModified": 1718726452,
"narHash": "sha256-w4hJSYvACz0i5XHtxc6XNyHwbxpisN13M2kA2Y7937o=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "53e534a08c0cd2a9fa7587ed1c3e7f6aeb804a2c",
"lastModified": 1756395552,
"narHash": "sha256-5aJM14MpoLk2cdZAetu60OkLQrtFLWTICAyn1EP7ZpM=",
"owner": "pyproject-nix",
"repo": "pyproject.nix",
"rev": "030dffc235dcf240d918c651c78dc5f158067b51",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "poetry2nix",
"owner": "pyproject-nix",
"repo": "pyproject.nix",
"type": "github"
}
},
"ragenix": {
"inputs": {
"agenix": "agenix",
"crane": "crane",
"flake-utils": "flake-utils_8",
"crane": "crane_2",
"flake-utils": "flake-utils_9",
"nixpkgs": [
"nixpkgs-unstable"
],
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1731774781,
"narHash": "sha256-vwsUUYOIs8J6weeSK1n1mbZf8fgvygGUMsadx0JmG70=",
"lastModified": 1764843533,
"narHash": "sha256-ovPNJh3Yws59Z8QHXAK+JkoftpdsUh1Ifxu2k+A/xxM=",
"owner": "devplayer0",
"repo": "ragenix",
"rev": "ec4115da7b67c783b1091811e17dbcba50edd1c6",
"rev": "e1a7bce819be6966ff369b7c0100c2713f8a9b32",
"type": "github"
},
"original": {
@@ -720,12 +845,16 @@
"inputs": {
"boardie": "boardie",
"borgthin": "borgthin",
"copyparty": "copyparty",
"deploy-rs": "deploy-rs",
"devshell": "devshell_3",
"flake-utils": "flake-utils_6",
"harmonia": "harmonia",
"hass-west-wood": "hass-west-wood",
"home-manager-stable": "home-manager-stable",
"home-manager-unstable": "home-manager-unstable",
"impermanence": "impermanence",
"libnetRepo": "libnetRepo",
"nixGL": "nixGL",
"nixpkgs-mine": "nixpkgs-mine",
"nixpkgs-mine-stable": "nixpkgs-mine-stable",
@@ -743,11 +872,11 @@
]
},
"locked": {
"lastModified": 1725675754,
"narHash": "sha256-hXW3csqePOcF2e/PYnpXj72KEYyNj2HzTrVNmS/F7Ug=",
"lastModified": 1761791894,
"narHash": "sha256-myRIDh+PxaREz+z9LzbqBJF+SnTFJwkthKDX9zMyddY=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "8cc45e678e914a16c8e224c3237fb07cf21e5e54",
"rev": "59c45eb69d9222a4362673141e00ff77842cd219",
"type": "github"
},
"original": {
@@ -758,8 +887,8 @@
},
"sbt": {
"inputs": {
"flake-utils": "flake-utils_11",
"nixpkgs": "nixpkgs_5"
"flake-utils": "flake-utils_12",
"nixpkgs": "nixpkgs_6"
},
"locked": {
"lastModified": 1698464090,
@@ -778,18 +907,18 @@
"sharry": {
"inputs": {
"devshell-tools": "devshell-tools",
"flake-utils": "flake-utils_10",
"flake-utils": "flake-utils_11",
"nixpkgs": [
"nixpkgs-unstable"
],
"sbt": "sbt"
},
"locked": {
"lastModified": 1720592125,
"narHash": "sha256-vR89LefkY8mBPWxDTQ8SNg6Z7/J6Yga80T4kSb6MNdk=",
"lastModified": 1741328331,
"narHash": "sha256-OtsHm9ykxfAOMRcgFDsqFBBy5Wu0ag7eq1qmTIluVcw=",
"owner": "eikek",
"repo": "sharry",
"rev": "604b20517150599cb05dbe178cd35cd10659aa4c",
"rev": "6203b90f9a76357d75c108a27ad00f323d45c1d0",
"type": "github"
},
"original": {
@@ -868,8 +997,9 @@
"type": "github"
},
"original": {
"id": "systems",
"type": "indirect"
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_5": {
@@ -950,17 +1080,16 @@
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"boardie",
"poetry2nix",
"harmonia",
"nixpkgs"
]
},
"locked": {
"lastModified": 1718522839,
"narHash": "sha256-ULzoKzEaBOiLRtjeY3YoGFJMwWSKRYOic6VNw2UyTls=",
"lastModified": 1780220602,
"narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "68eb1dc333ce82d0ab0c0357363ea17c31ea1f81",
"rev": "db947814a175b7ca6ded66e21383d938df01c227",
"type": "github"
},
"original": {
@@ -971,14 +1100,14 @@
},
"utils": {
"inputs": {
"systems": "systems_5"
"systems": "systems_3"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
+24 -7
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-24.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-24.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,16 +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";
# 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 =
@@ -57,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; };
@@ -88,10 +102,11 @@
(_: 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)
];
}))
@@ -102,6 +117,7 @@
(_: path: mkDefaultSystemsPkgs path (_: {
overlays = [
pkgsLibOverlay
myPkgsOverlay
];
@@ -126,6 +142,7 @@
nixos/boxes/home/palace
nixos/boxes/home/castle
nixos/boxes/britway
nixos/boxes/britnet.nix
nixos/boxes/kelder
# Homes
@@ -156,7 +173,7 @@
# Platform independent stuff
{
nixpkgs = pkgs';
inherit lib nixfiles;
inherit inputs lib nixfiles;
overlays.default = myPkgsOverlay;
+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
+78 -22
View File
@@ -1,7 +1,8 @@
{ lib, pkgs', pkgs, config, ... }:
let
inherit (lib) genAttrs mkIf mkMerge mkForce mapAttrs mkOptionDefault;
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 [
@@ -44,7 +63,7 @@ in
font.package
nerd-fonts.sauce-code-pro
nerd-fonts.droid-sans-mono
noto-fonts-emoji
noto-fonts-color-emoji
grim
slurp
@@ -60,10 +79,11 @@ in
jp2a
terminaltexteffects
screenfetch
neofetch
fastfetch
cmatrix
doomsaver
ffmpeg-full
xournalpp
];
};
@@ -102,12 +122,6 @@ in
};
};
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;
};
};
};
};
}
@@ -367,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;
@@ -395,29 +426,53 @@ 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 = {
package = pkgs.rofi-wayland;
enable = true;
font = "${font.name} ${toString font.size}";
plugins = with pkgs; (map (p: p.override { rofi-unwrapped = rofi-wayland-unwrapped; }) [
plugins = with pkgs; [
rofi-calc
]) ++ [
rofi-emoji-wayland
rofi-emoji
];
extraConfig = {
modes = "window,run,ssh,filebrowser,calc,emoji";
@@ -474,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'
+84 -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_12;
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,6 +258,21 @@ 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;
@@ -244,6 +290,12 @@ rec {
dst = aa.qclk.internal.ipv4.address;
proto = "udp";
}
{
port = 7777;
dst = aa.gam.internal.ipv4.address;
proto = "udp";
}
];
fstrimConfig = {
@@ -260,6 +312,9 @@ rec {
lo = 110;
untrusted = 120;
wan = 130;
wan-pon = 131;
pon-isp = 10;
};
hiMTU = 9000;
routers = [
@@ -267,8 +322,8 @@ rec {
"stream"
];
routersPubV4 = [
"109.255.31.155"
"109.255.252.63"
"109.255.108.88"
"109.255.108.121"
];
prefixes = with lib.my.net.cidr; rec {
@@ -334,6 +389,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";
@@ -378,6 +447,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;
+11 -9
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
{
@@ -175,11 +175,11 @@ rec {
};
vm = rec {
lvmDisk' = name: lv: {
lvmDisk'' = name: vg: lv: {
inherit name;
backend = {
driver = "host_device";
filename = "/dev/main/${lv}";
filename = "/dev/${vg}/${lv}";
# It appears this needs to be set on the backend _and_ the format
discard = "unmap";
};
@@ -189,7 +189,8 @@ rec {
};
frontend = "virtio-blk";
};
lvmDisk = lv: lvmDisk' lv lv;
lvmDisk' = vg: lv: lvmDisk'' lv vg lv;
lvmDisk = lvmDisk' "main";
disk = vm: lv: lvmDisk' lv "vm-${vm}-${lv}";
};
@@ -248,12 +249,13 @@ rec {
in
{
trivial = prev.trivial // {
release = "24.12:u-${prev.trivial.release}";
codeName = "Epic";
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
'';
+1 -7
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 = ''
+1 -16
View File
@@ -4,22 +4,6 @@ let
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
@@ -52,6 +36,7 @@ in
noise.private_key_path = "/var/lib/headscale/noise_private.key";
prefixes = with lib.my.c.tailscale.prefix; { inherit v4 v6; };
dns = {
override_local_dns = false;
# Use IPs that will route inside the VPN to prevent interception
# (e.g. DNS rebinding filtering)
nameservers.split = {
+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;
+87 -9
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;
@@ -171,6 +169,44 @@ in
];
};
}
{
"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;
}
];
};
}
];
links = {
@@ -218,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")
{
@@ -346,6 +382,26 @@ in
}
];
};
"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";
}
];
};
} ];
};
@@ -356,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" = {};
};
};
@@ -367,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;
@@ -399,8 +467,12 @@ 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 {
@@ -408,6 +480,10 @@ in
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 {
@@ -428,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
@@ -440,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}
}
}
+37 -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" ];
};
};
};
};
@@ -154,6 +164,14 @@ in
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}
@@ -167,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 {
+10 -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,8 +18,8 @@ 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;
@@ -39,6 +27,14 @@ in
runner = {
timeout = "8h";
};
container = {
network = "podman";
privileged = true;
};
cache = {
enabled = true;
dir = "/var/cache/gitea-runner";
};
};
};
};
@@ -66,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";
};
};
@@ -198,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;
@@ -243,44 +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";
};
username_template = "fbm2_{{.}}";
};
network = {
mode = "messenger";
displayname_template = ''{{or .DisplayName .Username "Unknown user"}} (FBM)'';
};
bridge = {
username_template = "fbm2_{{.}}";
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;
};
};
};
@@ -291,44 +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";
};
username_template = "ig_{{.}}";
};
network = {
mode = "instagram";
displayname_template = ''{{or .DisplayName .Username "Unknown user"}} (IG)'';
};
bridge = {
username_template = "ig_{{.}}";
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
@@ -94,19 +111,12 @@ in
};
};
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 = {
netdata.enable = true;
transmission = {
enable = true;
package = pkgs.transmission_4;
downloadDirPermissions = null;
performanceNetParameters = true;
settings = {
@@ -131,10 +141,11 @@ in
};
};
flaresolverr.enable = true;
jackett.enable = true;
radarr.enable = true;
sonarr.enable = true;
jellyseerr = {
seerr = {
enable = true;
openFirewall = true;
};
@@ -158,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 [
@@ -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}
@@ -49,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
{
@@ -79,6 +80,10 @@ in
sha256 = "018wh6ps19n7323fi44njzj9yd4wqslc90dykbwfyscv7bgxhlar";
};
}
{
name = "ssh.pub";
path = lib.my.c.sshKeyFiles.me;
}
];
}
wellKnown
@@ -322,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";
@@ -333,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";
@@ -363,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";
};
@@ -413,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,18 @@ in
{
users = {
harmonia = {
isSystemUser = true;
group = "harmonia";
shell = pkgs.bashInteractive;
openssh.authorizedKeys.keyFiles = [
lib.my.c.sshKeyFiles.harmonia
lib.my.c.sshKeyFiles.me
];
};
};
groups = {
harmonia = { };
};
}
];
@@ -127,13 +134,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 +163,9 @@ in
];
};
# TODO/FIXME: this is bad...
nixpkgs.config.permittedInsecurePackages = [ "minio-2025-10-15T17-29-55Z" ];
services = {
minio = {
enable = true;
@@ -235,11 +256,20 @@ in
};
};
harmonia = {
harmonia-dev = {
daemon = {
enable = true;
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 {
+1
View File
@@ -217,6 +217,7 @@ in
toot = {};
waffletail = {};
qclk = {};
gam = {};
};
in
mkMerge [
+4 -1
View File
@@ -53,6 +53,9 @@ in
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, ... }:
@@ -112,7 +115,7 @@ in
};
containers.containersConf.settings.network = {
network_backend = "netavark";
firewall_driver = "none";
firewall_driver = mkForce "none";
};
};
@@ -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 = {
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+29 -7
View File
@@ -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 = {
+15 -3
View File
@@ -53,7 +53,7 @@ in
};
};
});
kernelModules = [ "kvm-amd" ];
kernelModules = [ "dm-raid" "kvm-amd" ];
kernelParams = [ "amd_iommu=on" ];
initrd = {
availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" "sr_mod" ];
@@ -126,9 +126,9 @@ in
};
linkConfig.Name = "et1g0";
};
"10-lan-core" = {
"10-lan-core-phy" = {
matchConfig.PermanentMACAddress = "e0:d5:5e:68:0c:70";
linkConfig.Name = "lan-core";
linkConfig.Name = "lan-core-phy";
};
"10-et100g" = {
matchConfig = {
@@ -145,6 +145,12 @@ in
netdevs = mkMerge [
(mkVLAN "lan-hi" vlans.hi)
(mkVLAN "lan-lo-phy" vlans.lo)
{
"25-lan-core".netdevConfig = {
Name = "lan-core";
Kind = "bridge";
};
}
{
"25-lan-lo".netdevConfig = {
Name = "lan-lo";
@@ -199,6 +205,12 @@ in
};
"60-lan-hi" = networkdAssignment "lan-hi" assignments.hi;
"50-lan-core-phy" = {
matchConfig.Name = "lan-core-phy";
networkConfig = {
Bridge = "lan-core";
} // networkd.noL3;
};
"50-lan-lo-phy" = {
matchConfig.Name = "lan-lo-phy";
networkConfig = {
+19 -1
View File
@@ -172,12 +172,23 @@
};
memory = 32768;
cleanShutdown.timeout = 120;
networks.netboot = {
networks = {
netboot = {
bridge = "lan-lo";
waitOnline = "carrier";
mac = "52:54:00:a5:7e:93";
extraOptions.bootindex = 1;
};
core = {
bridge = "lan-core";
ifname = "vm-sfh-core";
waitOnline = "carrier";
mac = "52:54:00:72:67:51";
};
};
drives = [
(vm.lvmDisk' "hdds" "frigate")
];
hostDevices = {
et100g0vf2 = {
index = 0;
@@ -188,6 +199,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"
];
};
};
};
+97 -3
View File
@@ -8,9 +8,16 @@
configuration = { lib, modulesPath, pkgs, config, assignments, allAssignments, ... }:
let
inherit (lib) mkForce mkMerge mkIf;
inherit (lib.my) networkdAssignment mkVLAN;
inherit (lib.my.c) networkd;
inherit (lib.my.c.home) vlans domain prefixes roceBootModules;
# Digiweb currently delivers the ISP VLAN (pon-isp, 10) single-tagged, so PPPoE runs on a
# VLAN 10 sitting directly on the physical WAN link. Flip this to true to nest it back
# inside the wan-pon (131) transport VLAN — double-stacking also needs QinQ (tag-stacking)
# on the switch feeding the ONT, or the BRAS never answers PADI.
wanStacked = false;
in
{
imports = [
@@ -71,11 +78,72 @@
dmeventd.enable = true;
};
fstrim.enable = true;
# TODO: re-enable once scheduling is tested
networkd-dispatcher.enable = mkForce false;
pppd = {
enable = true;
peers.digiweb = {
autostart = true;
enable = true;
# Password is shared across all Digiweb customers, so no need for a secret
config = ''
plugin pppoe.so wan-vlan-inner
name "digiweb@nga.digiweb.ie"
password "digiweb"
noipdefault
# no usepeerdns: we ignore Digiweb's resolvers and use the local recursive resolver
lcp-echo-interval 1
lcp-echo-failure 4
noauth
persist
maxfail 0
holdoff 5
mtu 1500
mru 1500
noaccomp
default-asyncmap
ifname wan
'';
};
};
};
# PPPoE WAN (Digiweb): pppd owns the `wan` interface on top of VLAN 10, and its
# ip-up/ip-down hooks toggle the shared wan-online.target. Nothing else Wants the
# target, so it stays inactive until the link is actually up.
systemd.targets.wan-online.unitConfig.DefaultDependencies = false;
environment.etc = {
ppp-up = {
target = "ppp/ip-up";
mode = "0755";
text = ''
#!${pkgs.runtimeShell}
${pkgs.iproute2}/bin/ip route add default dev wan scope link metric 100
${config.systemd.package}/bin/systemctl --no-block start wan-online.target
'';
};
ppp-down = {
target = "ppp/ip-down";
mode = "0755";
text = ''
#!${pkgs.runtimeShell}
${config.systemd.package}/bin/systemctl --no-block stop wan-online.target
${pkgs.iproute2}/bin/ip route del default dev wan scope link metric 100
'';
};
};
systemd.network = {
netdevs = mkMerge [
(mkIf wanStacked (mkVLAN "wan-vlan-outer" vlans.wan-pon))
(mkVLAN "wan-vlan-inner" vlans.pon-isp)
];
links = {
"10-wan" = {
"10-wan-old" = {
matchConfig = {
# Matching against MAC address seems to break VLAN interfaces
# (since they share the same MAC address)
@@ -83,7 +151,7 @@
PermanentMACAddress = "e0:d5:5e:68:0c:6e";
};
linkConfig = {
Name = "wan";
Name = "wan-old";
RxBufferSize = 4096;
TxBufferSize = 4096;
};
@@ -101,8 +169,34 @@
};
};
networks = {
"55-lan" = {
# outer transport VLAN when stacked, otherwise the ISP VLAN directly on lan
vlan = [ (if wanStacked then "wan-vlan-outer" else "wan-vlan-inner") ];
};
# So we don't drop the IP we use to connect to NVMe-oF!
networks."60-lan-hi".networkConfig.KeepConfiguration = "static";
"60-lan-hi".networkConfig.KeepConfiguration = "static";
"70-wan-vlan-outer" = mkIf wanStacked {
matchConfig.Name = "wan-vlan-outer";
vlan = [ "wan-vlan-inner" ];
networkConfig = networkd.noL3;
# baby jumbo: carries the inner VLAN's frames, whose 4B tag counts as payload
# at this layer, so it needs 1512 (inner's 1508B payload + the inner 802.1Q tag)
linkConfig.MTUBytes = "1512";
};
# pppd attaches PPPoE to this; just needs to be up with no L3. Hangs off
# wan-vlan-outer when stacked, otherwise directly off lan (see "55-lan").
"71-wan-vlan-inner" = {
matchConfig.Name = "wan-vlan-inner";
linkConfig = {
RequiredForOnline = "no";
# baby jumbo: PPPoE's 8B overhead leaves a clean 1500 on ppp
MTUBytes = "1508";
};
networkConfig = networkd.noL3;
};
};
};
my = {
@@ -1,5 +1,6 @@
{
imports = [
./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";
}
];
};
};
};
};
};
};
}
@@ -24,6 +24,15 @@ in
address = net.cidr.host (65536*5+1) prefixes.hi.v6;
};
};
core = {
inherit domain;
name = "unifi-ctr-core";
mtu = 1500;
ipv4 = {
address = net.cidr.host 21 prefixes.core.v4;
gateway = null;
};
};
};
configuration = { lib, config, pkgs, assignments, ... }:
@@ -48,15 +57,18 @@ in
};
systemd = {
network.networks."80-container-host0" = networkdAssignment "host0" assignments.hi;
network.networks = {
"80-container-host0" = networkdAssignment "host0" assignments.hi;
"80-lan-core" = networkdAssignment "lan-core" assignments.core;
};
};
services = {
unifi = {
enable = true;
openFirewall = true;
unifiPackage = pkgs.unifi8;
mongodbPackage = pkgs.mongodb-6_0;
unifiPackage = pkgs.unifi;
mongodbPackage = pkgs.mongodb-7_0;
};
};
};
+69 -1
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;
@@ -72,6 +72,10 @@ in
fsType = "ext4";
neededForBoot = true;
};
"/mnt/frigate" = {
device = "/dev/disk/by-label/frigate";
fsType = "ext4";
};
};
networking = { inherit domain; };
@@ -83,6 +87,12 @@ in
};
};
environment = {
systemPackages = with pkgs; [
usbutils
];
};
systemd.network = {
links = {
"10-lan-hi" = {
@@ -105,6 +115,20 @@ in
MTUBytes = toString lib.my.c.home.hiMTU;
};
};
"10-lan-core-ctrs" = {
matchConfig = {
Driver = "virtio_net";
PermanentMACAddress = "52:54:00:72:67:51";
};
linkConfig.Name = "lan-core-ctrs";
};
"10-lan-lo-ctrs" = {
matchConfig = {
Driver = "virtio_net";
PermanentMACAddress = "52:54:00:a5:7e:93";
};
linkConfig.Name = "lan-lo-ctrs";
};
};
networks = {
@@ -118,7 +142,37 @@ in
linkConfig.RequiredForOnline = "no";
networkConfig = networkd.noL3;
};
"30-lan-core-ctrs" = {
matchConfig.Name = "lan-core-ctrs";
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";
};
};
unifi = {
networkConfig = {
MACVLAN = mkForce "lan-hi-ctrs:host0 lan-core-ctrs:lan-core";
};
};
};
systemd.services = {
"systemd-nspawn@hass".serviceConfig.DeviceAllow = [
"char-ttyUSB rw"
"char-video4linux rw"
];
};
my = {
@@ -142,6 +196,20 @@ in
let
instances = {
unifi = {};
hass = {
bindMounts = {
"/mnt/frigate" = {
mountPoint = "/var/lib/frigate";
readOnly = false;
};
"/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 [
+23 -52
View File
@@ -121,11 +121,9 @@ in
};
services = {
resolved = {
llmnr = "false";
extraConfig = ''
MulticastDNS=false
'';
resolved.settings.Resolve = {
LLMNR = false;
MulticastDNS = false;
};
iperf3 = {
@@ -141,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
'';
};
@@ -154,26 +152,34 @@ in
networking = { inherit domain; };
systemd.services =
let
waitOnline = "systemd-networkd-wait-online@wan.service";
in
{
# Uniform "WAN is up" gate. Consumers attach to this target (via wantedBy +
# partOf) rather than depending on it, so it is never pulled in / prematurely
# activated. Each box wires up how the target actually gets reached: stream
# gates it on networkd's wait-online, river drives it from the pppd hooks.
systemd.targets.wan-online.description = "WAN is online";
systemd.services = {
ipsec = {
after = [ waitOnline ];
requires = [ waitOnline ];
after = [ "wan-online.target" ];
# strongswan/libreswan force wantedBy=multi-user.target; drop it so the
# target is a true gate rather than mere ordering. This matters most on
# river, where the target is hook-driven and not in the boot transaction,
# so plain ordering wouldn't hold ipsec back at all. partOf re-loads ipsec
# (re-orienting its connections) whenever the WAN drops and returns.
wantedBy = mkForce [ "wan-online.target" ];
partOf = [ "wan-online.target" ];
};
ipv6-clear-default-route = {
description = "Clear IPv6 RA default route";
after = [ waitOnline ];
requires = [ waitOnline ];
after = [ "wan-online.target" ];
wantedBy = [ "wan-online.target" ];
partOf = [ "wan-online.target" ];
script = ''
# Seems like we can sometimes pick up a default route somehow...
${pkgs.iproute2}/bin/ip -6 route del default via fe80::1 || true
'';
serviceConfig.Type = "oneshot";
wantedBy = [ "multi-user.target" ];
};
};
@@ -222,41 +228,6 @@ in
in
mkMerge [
{
"50-wan-ifb" = {
matchConfig.Name = "wan-ifb";
networkConfig = networkd.noL3;
extraConfig = ''
[CAKE]
Bandwidth=490M
RTTSec=50ms
PriorityQueueingPreset=besteffort
# DOCSIS preset
OverheadBytes=18
MPUBytes=64
CompensationMode=none
'';
};
"50-wan" = mkMerge [
(networkdAssignment "wan" assignments.modem)
{
matchConfig.Name = "wan";
DHCP = "ipv4";
dns = [ "127.0.0.1" "::1" ];
dhcpV4Config.UseDNS = false;
qdiscConfig = {
Parent = "ingress";
Handle = "0xffff";
};
extraConfig = ''
[CAKE]
Parent=root
Bandwidth=48M
RTTSec=50ms
'';
}
];
"55-lan" = {
matchConfig.Name = "lan";
vlan = [ "lan-hi" "lan-lo" "lan-untrusted" "wan-tunnel" ];
@@ -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
+61 -20
View File
@@ -13,13 +13,20 @@ let
in
{
config = {
# Let pdns-recursor bind the VRRP VIPs even on the backup, where the addresses
# aren't present locally
boot.kernel.sysctl = {
"net.ipv4.ip_nonlocal_bind" = 1;
"net.ipv6.ip_nonlocal_bind" = 1;
};
my = {
secrets.files = {
"home/pdns/auth.conf" = {
owner = "pdns";
group = "pdns";
};
"home/pdns/recursor.conf" = {
"home/pdns/recursor.yml" = {
owner = "pdns-recursor";
group = "pdns-recursor";
};
@@ -28,53 +35,83 @@ 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
# VRRP VIPs: DNS follows the master, so clients only ever have one
# (always-live) resolver address and never hang on a dead router
vips.hi.v4 vips.hi.v6
vips.lo.v4 vips.lo.v6
];
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" ];
};
};
};
};
@@ -197,6 +234,7 @@ in
dave-lo IN AAAA ${net.cidr.host (65536+2) prefixes.lo.v6}
shytzel IN A ${net.cidr.host 12 prefixes.core.v4}
brian IN A ${net.cidr.host 13 prefixes.core.v4}
wave IN A ${net.cidr.host 12 prefixes.hi.v4}
; wave IN AAAA ${net.cidr.host (65536+3) prefixes.hi.v6}
@@ -206,6 +244,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, ttl=60,
type='A', content=address)
if __name__ == '__main__':
main()
+57 -2
View File
@@ -83,7 +83,8 @@ in
}
{
name = "domain-name-servers";
data = "${net.cidr.host 1 prefixes.hi.v4}, ${net.cidr.host 2 prefixes.hi.v4}";
# VRRP VIP so DNS follows the master and clients never hit a dead router
data = vips.hi.v4;
}
{
name = "interface-mtu";
@@ -116,7 +117,8 @@ in
}
{
name = "domain-name-servers";
data = "${net.cidr.host 1 prefixes.lo.v4}, ${net.cidr.host 2 prefixes.lo.v4}";
# VRRP VIP so DNS follows the master and clients never hit a dead router
data = vips.lo.v4;
}
];
pools = [
@@ -132,6 +134,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";
}
];
}
];
@@ -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;
@@ -64,6 +61,9 @@ in
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
'';
+7 -4
View File
@@ -3,11 +3,14 @@ let
# TODO: Move into nixpkgs
mstpd = pkgs.mstpd.overrideAttrs {
patches = [ ./mstpd.patch ];
# Delete postInstall since it nukes the bridge-stp script we need
postInstall = "";
};
in
{
environment = {
systemPackages = [
# For kernel to call bridge-stp (see ./pkgs/os-specific/linux/kernel/bridge-stp-helper.patch)
mstpd
];
etc = {
@@ -24,8 +27,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
'';
};
@@ -39,8 +42,8 @@ in
before = [ "network-pre.target" ];
serviceConfig = {
Type = "forking";
ExecStart = "${mstpd}/sbin/bridge-stp restart";
ExecReload = "${mstpd}/sbin/bridge-stp restart_config";
ExecStart = "${mstpd}/bin/bridge-stp restart";
ExecReload = "${mstpd}/bin/bridge-stp restart_config";
PIDFile = "/run/mstpd.pid";
Restart = "always";
PrivateTmp = true;
+9 -2
View File
@@ -2,7 +2,14 @@ index: { lib, pkgs, ... }:
let
inherit (lib) mkForce concatMapStringsSep;
inherit (lib.my) net;
inherit (lib.my.c.home) domain prefixes;
inherit (lib.my.c.home) domain prefixes vips;
# untrusted uses external (Cloudflare) resolvers, matching the v4 kea config;
# trusted VLANs use the internal recursor via its floating VRRP VIP
rdnss = name:
if name == "untrusted"
then "2606:4700:4700::1111 2606:4700:4700::1001"
else vips."${name}".v6;
mkInterface = name: ''
interface lan-${name} {
@@ -10,7 +17,7 @@ let
AdvRASrcAddress { fe80::1; };
AdvLinkMTU ${toString prefixes."${name}".mtu};
prefix ${prefixes."${name}".v6} {};
RDNSS ${net.cidr.host 1 prefixes."${name}".v6} ${net.cidr.host 2 prefixes."${name}".v6} {};
RDNSS ${rdnss name} {};
DNSSL ${domain} dyn.${domain} ${lib.my.c.colony.domain} ${lib.my.c.britway.domain} {};
};
'';
+67 -4
View File
@@ -6,9 +6,11 @@
nixpkgs = "mine";
home-manager = "mine";
configuration = { lib, pkgs, config, ... }:
configuration = { lib, pkgs, config, assignments, ... }:
let
inherit (lib);
inherit (lib) mkMerge;
inherit (lib.my) networkdAssignment;
inherit (lib.my.c) networkd;
in
{
imports = [ ./routing-common/mstpd.nix ];
@@ -45,12 +47,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 = {
@@ -75,6 +77,32 @@
};
};
# wan carries a permanent static modem-management address (assignments.modem)
# alongside the DHCP public IP, so wait-online@wan reports "online" as soon as
# the static address is up - before the DHCP lease arrives. ipsec's left= is the
# public IP, so gating on wait-online lets it start unoriented and never connect.
# Gate instead on the DHCP default route, which only exists once the public lease
# is up (the static modem address has no gateway).
systemd.services.wan-wait-online = {
description = "Wait for the wan default route (public DHCP lease)";
after = [ "systemd-networkd.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
TimeoutStartSec = "300";
};
script = ''
until [ -n "$(${pkgs.iproute2}/bin/ip -4 route show default dev wan)" ]; do
sleep 1
done
'';
};
systemd.targets.wan-online = {
requires = [ "wan-wait-online.service" ];
after = [ "wan-wait-online.service" ];
wantedBy = [ "multi-user.target" ];
};
systemd.network = {
netdevs = {
"25-lan" = {
@@ -147,6 +175,41 @@
matchConfig.Name = "lan-dave";
networkConfig.Bridge = "lan";
};
"50-wan-ifb" = {
matchConfig.Name = "wan-ifb";
networkConfig = networkd.noL3;
extraConfig = ''
[CAKE]
Bandwidth=490M
RTTSec=50ms
PriorityQueueingPreset=besteffort
# DOCSIS preset
OverheadBytes=18
MPUBytes=64
CompensationMode=none
'';
};
"50-wan" = mkMerge [
(networkdAssignment "wan" assignments.modem)
{
matchConfig.Name = "wan";
DHCP = "ipv4";
dns = [ "127.0.0.1" "::1" ];
dhcpV4Config.UseDNS = false;
qdiscConfig = {
Parent = "ingress";
Handle = "0xffff";
};
extraConfig = ''
[CAKE]
Parent=root
Bandwidth=48M
RTTSec=50ms
'';
}
];
};
};
@@ -29,7 +29,7 @@ in
hardware.graphics = {
enable = true;
extraPackages = with pkgs; [
vaapiIntel
intel-vaapi-driver
intel-ocl
];
};
@@ -73,8 +73,8 @@ in
RootDirectory = lib.mkForce "";
};
radarr.serviceConfig.UMask = "0002";
sonarr.serviceConfig.UMask = "0002";
radarr.serviceConfig.UMask = lib.mkForce "0002";
sonarr.serviceConfig.UMask = lib.mkForce "0002";
};
};
@@ -89,6 +89,7 @@ in
services = {
transmission = {
enable = true;
package = pkgs.transmission_4;
downloadDirPermissions = null;
performanceNetParameters = true;
settings = {
@@ -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 = ''
+1 -7
View File
@@ -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;
};
};
+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" {
+4 -2
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";
@@ -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.
+1 -1
View File
@@ -14,7 +14,7 @@
network = ./network.nix;
pdns = ./pdns.nix;
nginx-sso = ./nginx-sso.nix;
gui = ./gui.nix;
gui = ./gui;
l2mesh = ./l2mesh.nix;
borgthin = ./borgthin.nix;
nvme = ./nvme;
+4 -2
View File
@@ -1,4 +1,4 @@
{ lib, pkgs, config, ... }:
{ inputs, lib, pkgs, config, ... }:
let
inherit (builtins) substring match;
inherit (lib)
@@ -127,7 +127,9 @@ in
enable = mkBoolOpt' false "Whether to enable borgthin jobs";
lvmPackage = mkOpt' package pkgs.lvm2 "Packge containing LVM tools";
thinToolsPackage = mkOpt' package pkgs.thin-provisioning-tools "Package containing thin-provisioning-tools";
package = mkOpt' package pkgs.borgthin "borgthin package";
# Really we should use the version from the overlay, but the package is quite far behind...
# Not bothering to update until Borg 2.0 releases
package = mkOpt' package inputs.borgthin.packages.${config.nixpkgs.system}.borgthin "borgthin package";
jobs = mkOpt' (attrsOf jobType) { } "borgthin jobs";
};
+1 -1
View File
@@ -221,8 +221,8 @@ in
memorySize = dummyOption;
qemu.options = dummyOption;
};
image.baseName = dummyOption;
isoImage = {
isoBaseName = dummyOption;
volumeID = dummyOption;
edition = dummyOption;
appendToMenuLabel = dummyOption;
+25 -12
View File
@@ -9,9 +9,11 @@ in
};
imports = [
inputs.impermanence.nixosModule
inputs.impermanence.nixosModules.default
inputs.ragenix.nixosModules.age
inputs.sharry.nixosModules.default
inputs.copyparty.nixosModules.default
inputs.harmonia.nixosModules.harmonia
];
config = mkMerge [
@@ -36,6 +38,15 @@ in
enable = mkDefault true;
wheelNeedsPassword = mkDefault false;
};
# TODO: Add this to fix login
# pam = {
# services = {
# kmscon.rules. = mkIf config.services.kmscon.config.libseat {
# };
# };
# };
};
nix = {
@@ -65,10 +76,13 @@ in
};
nixpkgs = {
overlays = [
inputs.deploy-rs.overlay
inputs.deploy-rs.overlays.default
inputs.sharry.overlays.default
inputs.borgthin.overlays.default
# TODO: Re-enable when borgthin is updated
# inputs.borgthin.overlays.default
inputs.boardie.overlays.default
inputs.copyparty.overlays.default
inputs.hass-west-wood.overlays.default
];
config = {
allowUnfree = true;
@@ -135,6 +149,7 @@ in
bash-completion
git
unzip
tcpdump
]
(mkIf config.services.netdata.enable [ netdata ])
];
@@ -152,16 +167,14 @@ in
};
services = {
kmscon = {
# As it turns out, kmscon hasn't been updated in years and has some bugs...
# TODO: Remove if-else when 26.11 releases
kmscon = if (config.system.nixos.release == "26.06:u-26.11") then {
enable = mkDefault false;
hwRender = mkDefault true;
extraOptions = "--verbose";
extraConfig =
''
font-name=SauceCodePro Nerd Font Mono
'';
config = {
hwaccel = config.hardware.graphics.enable;
font-name = "SauceCodePro Nerd Font Mono";
};
} else { };
getty.greetingLine = mkDefault' ''<<< Welcome to ${config.system.nixos.distroName} ${config.system.nixos.label} (\m) - \l >>>'';
openssh = {
@@ -242,7 +255,7 @@ in
};
}
(mkIf config.services.kmscon.enable {
fonts.fonts = with pkgs; [
fonts.packages = with pkgs; [
nerd-fonts.sauce-code-pro
];
})
+1
View File
@@ -15,6 +15,7 @@ let
passAsFile = [ "code" ];
code = ''
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <systemd/sd-daemon.h>
+2 -1
View File
@@ -45,8 +45,9 @@ let
journalctl -o cat --no-pager -n 0 -f -u "$unit" &
jPid=$!
# shellcheck disable=SC2329
cleanup() {
# shellcheck disable=SC2317
kill "$jPid"
}
trap cleanup EXIT
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,15 @@
{ lib, pkgs, config, ... }:
let
inherit (lib) optional mkIf mkDefault mkMerge;
inherit (lib) optional mkIf mkDefault mkMerge mkOverride;
inherit (lib.my) mkBoolOpt';
cfg = config.my.gui;
androidUdevRules = pkgs.runCommand "udev-rules-android" {
rulesFile = ./android-udev.rules;
} ''
install -D "$rulesFile" "$out"/lib/udev/rules.d/51-android.rules
'';
in
{
options.my.gui = with lib.types; {
@@ -26,12 +32,29 @@ in
pam.services.swaylock-plugin = {};
};
users = {
groups = {
adbusers.gid = lib.my.c.ids.gids.adbusers;
};
};
environment.systemPackages = with pkgs; [
# for pw-jack
pipewire.jack
swaylock-plugin
];
services = {
resolved = {
settings.Resolve = {
FallbackDNS = mkOverride 99 (
"1.1.1.1#cloudflare-dns.com 8.8.8.8#dns.google " +
"1.0.0.1#cloudflare-dns.com 8.8.4.4#dns.google " +
"2606:4700:4700::1111#cloudflare-dns.com 2001:4860:4860::8888#dns.google " +
"2606:4700:4700::1001#cloudflare-dns.com 2001:4860:4860::8844#dns.google" );
LLMNR = "resolve";
};
};
pipewire = {
enable = true;
alsa.enable = true;
@@ -44,8 +67,12 @@ in
gnome = {
gnome-keyring.enable = true;
};
udisks2.enable = true;
udev = {
packages = [
androidUdevRules
];
extraRules = ''
# Nvidia
SUBSYSTEM=="usb", ATTR{idVendor}=="0955", MODE="0664", GROUP="wheel"
@@ -67,7 +94,7 @@ in
gyre-fonts # TrueType substitutes for standard PostScript fonts
liberation_ttf
unifont
noto-fonts-emoji
noto-fonts-color-emoji
];
xdg = {
@@ -88,5 +115,13 @@ in
];
};
};
my = {
user = {
config = {
extraGroups = [ "adbusers" ];
};
};
};
};
}
+4 -11
View File
@@ -5,19 +5,10 @@ let
cfg = config.my.netboot;
ipxe = pkgs.ipxe.overrideAttrs (o: rec {
version = "1.21.1-unstable-2024-06-27";
src = pkgs.fetchFromGitHub {
owner = "ipxe";
repo = "ipxe";
rev = "b66e27d9b29a172a097c737ab4d378d60fe01b05";
hash = "sha256-TKZ4WjNV2oZIYNefch7E7m1JpeoC/d7O1kofoNv8G40=";
};
});
tftpRoot = pkgs.linkFarm "tftp-root" [
{
name = "ipxe-x86_64.efi";
path = "${ipxe}/ipxe.efi";
path = "${pkgs.ipxe}/snp.efi";
}
];
menuFile = pkgs.runCommand "menu.ipxe" {
@@ -26,10 +17,11 @@ let
substituteAll ${./menu.ipxe} "$out"
'';
bootBuilder = pkgs.substituteAll {
bootBuilder = pkgs.replaceVarsWith {
src = ./netboot-loader-builder.py;
isExecutable = true;
replacements = {
inherit (pkgs) python3;
bootspecTools = pkgs.bootspec;
nix = config.nix.package.out;
@@ -44,6 +36,7 @@ let
fi
'';
};
};
in
{
options.my.netboot = with lib.types; {
@@ -0,0 +1,48 @@
From 7f75d320f6d8ac7ec5185b2145da87f698aec273 Mon Sep 17 00:00:00 2001
From: Michael Brown <mcb30@ipxe.org>
Date: Mon, 2 Sep 2024 12:24:57 +0100
Subject: [PATCH] [etherfabric] Fix use of uninitialised variable in
falcon_xaui_link_ok()
The link status check in falcon_xaui_link_ok() reads from the
FCN_XX_CORE_STAT_REG_MAC register only on production hardware (where
the FPGA version reads as zero), but modifies the value and writes
back to this register unconditionally. This triggers an uninitialised
variable warning on newer versions of gcc.
Fix by assuming that the register exists only on production hardware,
and so moving the "modify-write" portion of the "read-modify-write"
operation to also be covered by the same conditional check.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
---
src/drivers/net/etherfabric.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/src/drivers/net/etherfabric.c b/src/drivers/net/etherfabric.c
index b40596beae7..be30b71f79f 100644
--- a/src/drivers/net/etherfabric.c
+++ b/src/drivers/net/etherfabric.c
@@ -2225,13 +2225,16 @@ falcon_xaui_link_ok ( struct efab_nic *efab )
sync = ( sync == FCN_XX_SYNC_STAT_DECODE_SYNCED );
link_ok = align_done && sync;
- }
- /* Clear link status ready for next read */
- EFAB_SET_DWORD_FIELD ( reg, FCN_XX_COMMA_DET, FCN_XX_COMMA_DET_RESET );
- EFAB_SET_DWORD_FIELD ( reg, FCN_XX_CHARERR, FCN_XX_CHARERR_RESET);
- EFAB_SET_DWORD_FIELD ( reg, FCN_XX_DISPERR, FCN_XX_DISPERR_RESET);
- falcon_xmac_writel ( efab, &reg, FCN_XX_CORE_STAT_REG_MAC );
+ /* Clear link status ready for next read */
+ EFAB_SET_DWORD_FIELD ( reg, FCN_XX_COMMA_DET,
+ FCN_XX_COMMA_DET_RESET );
+ EFAB_SET_DWORD_FIELD ( reg, FCN_XX_CHARERR,
+ FCN_XX_CHARERR_RESET );
+ EFAB_SET_DWORD_FIELD ( reg, FCN_XX_DISPERR,
+ FCN_XX_DISPERR_RESET );
+ falcon_xmac_writel ( efab, &reg, FCN_XX_CORE_STAT_REG_MAC );
+ }
has_phyxs = ( efab->phy_op->mmds & ( 1 << MDIO_MMD_PHYXS ) );
if ( link_ok && has_phyxs ) {
+6 -14
View File
@@ -1,6 +1,6 @@
{ lib, pkgs, config, ... }:
let
inherit (lib) flatten optional mkIf mkDefault mkMerge;
inherit (lib) flatten optional mkIf mkDefault mkMerge versionAtLeast;
in
{
config = mkMerge [
@@ -12,21 +12,13 @@ in
useNetworkd = mkDefault true;
};
systemd = {
additionalUpstreamSystemUnits = [
# TODO: NixOS has its own version of this, but with `network` instead of `networkd`. Is this just a typo? It
# hasn't been updated in 2 years...
"systemd-networkd-wait-online@.service"
];
};
services.resolved = {
domains = [ config.networking.domain ];
settings.Resolve = {
Domains = [ config.networking.domain ];
# Explicitly unset fallback DNS (Nix module will not allow for a blank config)
extraConfig = ''
FallbackDNS=
Cache=no-negative
'';
FallbackDNS = "";
Cache = "no-negative";
};
};
}
+7 -19
View File
@@ -4,19 +4,6 @@ let
inherit (lib.my) mkOpt';
cfg = config.my.nvme;
nvme-cli = pkgs.nvme-cli.override {
libnvme = pkgs.libnvme.overrideAttrs (o: rec {
# TODO: Remove when 1.11.1 releases (see https://github.com/linux-nvme/libnvme/pull/914)
version = "1.11.1";
src = pkgs.fetchFromGitHub {
owner = "linux-nvme";
repo = "libnvme";
rev = "v${version}";
hash = "sha256-CEGr7PDOVRi210XvICH8iLYDKn8S9bGruBO4tycvsT8=";
};
patches = (if (o ? patches) then o.patches else [ ]) ++ [ ./libnvme-hostconf.patch ];
});
};
hostNQN = "nqn.2014-08.org.nvmexpress:uuid:${cfg.uuid}";
etc = prefix: {
@@ -36,7 +23,7 @@ in
config = mkIf (cfg.uuid != null) {
environment = {
systemPackages = [
nvme-cli
pkgs.nvme-cli
];
etc = etc "";
};
@@ -52,16 +39,17 @@ in
ip = "${iproute2}/bin/ip";
nvme = "${nvme-cli}/bin/nvme";
};
extraConfig = ''
DefaultTimeoutStartSec=20
DefaultDeviceTimeoutSec=20
'';
network = {
enable = true;
wait-online.enable = true;
};
settings.Manager = {
DefaultTimeoutStartSec = 20;
DefaultDeviceTimeoutSec = 20;
};
services.connect-nvme = {
description = "Connect NVMe-oF";
before = [ "initrd-root-device.target" ];
@@ -70,7 +58,7 @@ in
serviceConfig = {
Type = "oneshot";
ExecStart = "${nvme-cli}/bin/nvme connect -t rdma -a ${cfg.boot.address} -n ${cfg.boot.nqn}";
ExecStart = "${pkgs.nvme-cli}/bin/nvme connect -t rdma -a ${cfg.boot.address} -n ${cfg.boot.nqn} -q ${hostNQN}";
Restart = "on-failure";
RestartSec = 10;
};
+12 -4
View File
@@ -1,7 +1,7 @@
{ lib, pkgs, config, ... }:
let
inherit (builtins) isList;
inherit (lib) mkMerge mkIf mkDefault mapAttrsToList concatMapStringsSep concatStringsSep;
inherit (lib) mkMerge mkIf mkDefault mapAttrsToList concatMapStringsSep concatStringsSep getExe;
inherit (lib.my) mkBoolOpt' mkOpt';
# Yoinked from nixos/modules/services/networking/pdns-recursor.nix
@@ -165,7 +165,7 @@ let
extraSettingsOpt = with lib.types; mkOpt' (nullOr str) null "Path to extra settings (e.g. for secrets).";
baseAuthSettings = pkgs.writeText "pdns.conf" (settingsToLines cfg.auth.settings);
baseRecursorSettings = pkgs.writeText "pdns-recursor.conf" (settingsToLines config.services.pdns-recursor.settings);
baseRecursorSettings = (pkgs.formats.yaml { }).generate "pdns-recursor.yaml" config.services.pdns-recursor.settings;
generateSettings = type: base: dst: if (cfg."${type}".extraSettingsFile != null) then ''
oldUmask="$(umask)"
umask 006
@@ -174,6 +174,14 @@ let
'' else ''
cp "${base}" "${dst}"
'';
generateYamlSettings = type: base: dst: if (cfg."${type}".extraSettingsFile != null) then ''
oldUmask="$(umask)"
umask 006
${getExe pkgs.yaml-merge} "${base}" "${cfg."${type}".extraSettingsFile}" > "${dst}"
umask "$oldUmask"
'' else ''
cp "${base}" "${dst}"
'';
namedConf = pkgs.writeText "pdns-named.conf" ''
options {
@@ -315,9 +323,9 @@ in
(mkIf cfg.recursor.enable {
systemd.services.pdns-recursor = {
preStart = ''
${generateSettings "recursor" baseRecursorSettings "/run/pdns-recursor/recursor.conf"}
${generateYamlSettings "recursor" baseRecursorSettings "/run/pdns-recursor/recursor.yml"}
'';
serviceConfig.ExecStart = [ "" "${pkgs.pdns-recursor}/bin/pdns_recursor --config-dir=/run/pdns-recursor" ];
serviceConfig.ExecStart = [ "" "${pkgs.pdns-recursor}/bin/pdns_recursor --config-dir=/run/pdns-recursor --daemon=no --write-pid=no --disable-syslog --log-timestamp=no" ];
};
services.pdns-recursor = {
+1 -6
View File
@@ -11,8 +11,7 @@ in
config = mkIf cfg.enable {
services = {
getty.autologinUser = mkDefault uname;
kmscon.autologinUser = mkDefault uname;
resolved.llmnr = mkDefault "false";
resolved.settings.Resolve.LLMNR = mkDefault "false";
};
systemd = {
timers = {
@@ -35,10 +34,6 @@ in
};
documentation.nixos.enable = mkDefault' false;
environment.systemPackages = with pkgs; [
tcpdump
];
};
meta.buildDocsInSandbox = false;
+67 -4
View File
@@ -145,6 +145,7 @@ in
# Also the script runs before impermanence does.
"/var/lib/nixos"
"/var/lib/systemd"
"/var/cache/man"
{ directory = "/root/.cache/nix"; mode = "0700"; }
# Including these unconditionally due to infinite recursion problems...
@@ -336,13 +337,13 @@ in
(persistSimpleSvc "jackett")
(persistSimpleSvc "radarr")
(persistSimpleSvc "sonarr")
(mkIf config.services.jellyseerr.enable {
(mkIf config.services.seerr.enable {
my.tmproot.persistence.config.directories = [
{
directory = "/var/lib/jellyseerr";
directory = "/var/lib/seerr";
mode = "0750";
user = "jellyseerr";
group = "jellyseerr";
user = "seerr";
group = "seerr";
}
];
})
@@ -551,6 +552,68 @@ in
];
});
})
(mkIf (config.services ? "bluesky-pds" && config.services.bluesky-pds.enable) {
my.tmproot.persistence.config.directories = [
{
directory = "/var/lib/pds";
mode = "0750";
user = "pds";
group = "pds";
}
];
})
(mkIf config.services.home-assistant.enable {
my.tmproot.persistence.config.directories = [
{
directory = config.services.home-assistant.configDir;
mode = "0750";
user = "hass";
group = "hass";
}
];
})
(mkIf config.services.frigate.enable {
my.tmproot.persistence.config.directories = [
{
directory = "/var/lib/frigate";
mode = "0755";
user = "frigate";
group = "frigate";
}
{
directory = "/var/cache/frigate";
mode = "0755";
user = "frigate";
group = "frigate";
}
];
})
(mkIf config.services.copyparty.enable {
my.tmproot.persistence.config.directories = [
{
directory = "/var/lib/copyparty";
mode = "0755";
user = "copyparty";
group = "copyparty";
}
{
directory = "/var/cache/copyparty";
mode = "0755";
user = "copyparty";
group = "copyparty";
}
];
})
(mkIf config.services.terraria.enable {
my.tmproot.persistence.config.directories = [
{
directory = config.services.terraria.dataDir;
mode = "0755";
user = "terraria";
group = "terraria";
}
];
})
]))
]);
+1 -1
View File
@@ -35,7 +35,7 @@ in
isNormalUser = true;
uid = mkDefault 1000;
extraGroups =
[ "wheel" "kvm" "dialout" ] ++
[ "wheel" "kvm" "dialout" "disk" ] ++
(optional config.networking.networkmanager.enable "networkmanager") ++
(optional config.virtualisation.libvirtd.enable "libvirtd") ++
(optional config.programs.wireshark.enable "wireshark") ++
+6 -2
View File
@@ -1,4 +1,4 @@
{ lib, stdenv, autoreconfHook, pkg-config, SDL, SDL_mixer, SDL_net
{ lib, stdenv, autoreconfHook, pkg-config, SDL1, SDL_mixer, SDL_net
, fetchFromGitHub, fetchpatch, python3 }:
stdenv.mkDerivation rec {
@@ -23,6 +23,10 @@ stdenv.mkDerivation rec {
./demoloopi.patch
];
configureFlags = [
"CFLAGS=-std=gnu17"
];
outputs = [ "out" "man" ];
postPatch = ''
@@ -35,7 +39,7 @@ stdenv.mkDerivation rec {
# for documentation
python3
];
buildInputs = [ (SDL.override { cacaSupport = true; }) SDL_mixer SDL_net ];
buildInputs = [ (SDL1.override { cacaSupport = true; }) SDL_mixer SDL_net ];
enableParallelBuilding = true;
meta = {
-1
View File
@@ -11,5 +11,4 @@ in
chocolate-doom2xx = callPackage ./chocolate-doom2xx { };
windowtolayer = callPackage ./windowtolayer.nix { };
swaylock-plugin = callPackage ./swaylock-plugin.nix { };
terminaltexteffects = callPackage ./terminaltexteffects.nix { };
}
-19
View File
@@ -1,19 +0,0 @@
{ lib
, python3Packages
, fetchPypi
}:
python3Packages.buildPythonApplication rec {
pname = "terminaltexteffects";
version = "0.10.1";
pyproject = true;
src = fetchPypi {
inherit pname version;
hash = "sha256-NyWPfdgLeXAxKPJOzB7j4aT+zjrURN59CGcv0Vt99y0=";
};
build-system = with python3Packages; [
poetry-core
];
}
+11 -4
View File
@@ -1,18 +1,25 @@
{ lib
, fetchFromGitLab
, rustPlatform
, python3
, rustfmt
}:
rustPlatform.buildRustPackage rec {
pname = "windowtolayer";
version = "a5b89c3c";
version = "97ebd079";
nativeBuildInputs = [
python3
rustfmt
];
src = fetchFromGitLab {
domain = "gitlab.freedesktop.org";
owner = "mstoeckl";
repo = pname;
rev = "a5b89c3c047297fd574932860a6c89e9ea02ba5d";
hash = "sha256-rssL2XkbTqUvJqfUFhzULeE4/VBzjeBC5iZWSJ8MJ+M=";
rev = "97ebd0790b13bf00afb0c53a768397882fd2e831";
hash = "sha256-XjbhZEoE5NPBofyJe7OSsE7MWgzjyRjBqiEzaQEuRrU=";
};
cargoHash = "sha256-XHmLsx9qdjlBz4xJFFiO24bR9CMw1o5368K+YMpMIBA=";
cargoHash = "sha256-M0BVSUEFGvjgX+vSpwzvaEGs0i80XOTCzvbV4SzYpLc=";
}
+13
View File
@@ -0,0 +1,13 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IHNqUFR5ZyBVVkI0
dE5YN1pJWExzLzltcmhna2tJUmdRNjZ1Y1hwbzdtRE0wa2hReTNBCk4ydmNFK0FF
b0RUdVl3a3d4amhKSEVhZWZPeHZDenBiTXpkVVFiNXFXNGsKLT4gWDI1NTE5IG9i
K0ZrNEc5SVlyWU1EbXdlbWppRG1DdjFRbTBCREY2OUxrMmVqNHhSazQKVnRaVmVn
MFBRL1dWeFNOaEwyU2szb1lOVzF1enQwdmVZZWRJcHd5MHdFbwotPiB2Wy1gUV8/
LWdyZWFzZSBdSDFebHsgKkBkVzl+KnggJTEKdlhrdzVpMHYxUUliQnhaYXNaVWNR
S3NxbjhFMEFGamZkRU1RNURhcmwzOGxFbGxXelhOdDBWTHBSY1hBcGFtUwpkampi
WnhzMDcxTk1seWZ6VURZb1l1QU1GdwotLS0gRFNpcXpDUFZLTXFJN3Z0bEJQd280
WGROWUVvdSt3ZUdBbmRNcGFhRE9BWQoDDlPEY/t2eapa4Xbv8FcW6gdLzQn7Y2cH
5UwD+0CTF3JdUpxWUIx9RWFleHekkt8j1+2/oO+m7+24yCg5mdqTJ3ZIwu9uk1eI
0As8IA==
-----END AGE ENCRYPTED FILE-----
+14 -13
View File
@@ -1,15 +1,16 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpCM2U2USB1QUhX
dTlNY3h2SElIMytCTGlmY2syMmlTL1VMeDFnanN2UE9QK3RvRlUwCkVMM3ZKMU52
ZmIrZ1VxUDNWZm1lU2MrWnpkUzBLcDB5cUFCSXZmQ3RXR0UKLT4gWDI1NTE5IE5W
cE5uSlpRd3dIRXEyRnpSZ0VHZ253OE1ZVTd1OTJQYkZVd1NRdnh2aE0KNTFTaHJM
OE9tc0VkMHFHUGN2dFZVSGFBdTh5Tzk0TGU4SWwzYTBOb3FtdwotPiB9WnwtZ3Jl
YXNlIHZVYHxaIEBSbmZ9XiAhfF52YCNwCld0K0RCTFlQamNLeitWM0kwUnVJdXFV
QjNFcEYxWFBkVjJ3QlAwTGVvTE1GTkxCTEdDcmlZMC9xbUcrSHp2V3cKdzJLb011
aEM3a0V4eXBTMjhnCi0tLSBOWHozZTRjRldrOXBmM05BZ1lPRDYwaDFCcHBPNHhQ
SzlaWGM2RXY1THI4Cj4RzG3G1yGkmDyqxCBciqMNPAQYge4mXOib7mqOuyIbkT+k
qrJ8fLnW7Jbnb7+Rzr7BAEC5/dpOfjkyY9JPeg0FRutlkKyPIhRMAEhSsNvUFzXg
uwNbnSBI+9Str6nTKI5c9IWT5eT/8zsKwtK1/pr39mApRY/y6NlA5X7ZRIFeku3K
6I/bCI5cT0qhIMJbF5D0KiCnMCnMdmRLrwLzM2zcJoisxRAvj4dNfLktsUQOauZ+
Gta96qQkAewvrdfz8Z4lTYJQmEqeOs1vTA6sAMf4iLp5EhPE0jN0n6D8v7Lq7+2b
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpCM2U2USB2M2I1
MmhzNzA5WVRBbmJGeUdPM3Vxb0hDWjdOOE9ndFh5Y3NmTGpTS3hZClRjWkJOREFK
WkE4bzhXSXZKZk5Xc3RsOHlzZUNycXl2My9xVjl4UGZMVXMKLT4gWDI1NTE5IFAv
ZnBZUnZKU1JqS3FlaTBlWTdwZG16anN5cEhyZEdJdi9UN1ZOL0VpMWMKTjlwckJn
Z2V6alBXNDZXNllxZ0hJU0wyeDJJTklMTjFZQXNDTnl0Zm5JVQotPiAvbWM3Ykct
Z3JlYXNlIDt7QmkgNFllCkpEblFjRVVYZ2VlY3UwZndab1loRnJFanZGZTRDK3dD
OUJvV2VZK1hNMXlDaFNPeHVHUXVIb0QvdkpDSzdjb1oKK0txZnJLM1BJUTFqSkRU
UXRjYjdiWGpJSG9iZ2QxT0tQL2JoRXh1WVRXOEc1cHB6VHVmT3RqMGs0bHFOU2Z3
SgpJWEkKLS0tIEgzcnR1TUFWUGJBdDV1VzhRQXlkRjJtYTJIcHNZUHR2ZGlnL3h1
bXdTb3cKnr02hEHUj47xiPTSVgNTq6sj1Ydn1TtCnYzHpo6zJyDUHN65v97W4veN
TOHDUT3IilO4FJ8ZvzlgW/8PzUs35IelUdpfshnRbHySqqLOmyweStoKNtgS19Jp
Pxafk3IU7hcFouJRtpZS4bddZFbOC2RjHbxvzBuEwrSBBX8PF1OZTHOevyqtSbwG
8jICfMciD/y7GdxuNqCH+uMb5x9BFrf4wOMPgvq77ydt+63JBZGU/u4mXGS7QY2v
NXYBbBKMjlvc2WJzfUp0e44a17KyQvR32lAnL3KOt8aDnWwxwEWntmw=
-----END AGE ENCRYPTED FILE-----
+12 -12
View File
@@ -1,14 +1,14 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpCM2U2USBJbWVs
bkt4a1RBaU14cjJzRm9KUGhydnZlWGU0NGVYK0NmWXRsclpjVUU0CmNiVDJTODFw
TnloNW1LMUliVmlnUGt0SFFzWmxVTWxZQ3crNWVHVy9IL0EKLT4gWDI1NTE5IDcw
TUQva3UxN3o1MXJ5QmVmQWVmeTFLejVwOVdQZks3a0dVQUJTdUdZdzgKajBFK2dX
R1NNR3l5d010MnJGUktNWldwVjY2cDIwbU1laEMwOWJITEg0NAotPiBeLS1ncmVh
c2UgJjhQIF0pcSB4Ci90SVhIZzRGVlcrd0hRCi0tLSBKdVgwZ0pidC93aEFscm9w
Mm5GbEc3RDdZRDRpeTdONHpWN0xHYXkvVU5zCjjFfwumbetHbq8MKrBIx18YXmny
z+0ifVbksJhYLYk7hViSUDy+F3RRad23dSPvDdd7JCEpj+2PvcezBLkwfUkFdQe7
uQhUJJ9c62oKSX7PMbQeW1tyX10SVkt/P20z6HPXvBlY715lsXspjZERi+DheZk0
ZDc3dPYMeyoAIWicf40ZxH8C6rWl5HOLIaTMbCOKwpD+wPjpzeZHat605YWReFxR
Yd3cKCRqSb2QdAdtZ0XUDSPl7HVwkw0xobCsvtPzmObA3YbLopBq7Dvrvh7XW5ns
BPlTaI8WnrI03CplGP00
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpCM2U2USBLTTkw
Zm90MnE4cDZCdUxldXdqZVlUS1BEbXovalA4eEpyWjZwMnZwVFVJCks0ays0Tk1E
NitpYXp0RjB4SVJTcmlaZytuc0cxT0FqdTk4S2ltN0J1aG8KLT4gWDI1NTE5IHdp
c3ZqMytTb3ZGYkVXaXd6ZFZyTGdlakNqTmxMUE9QN25Ja0NvZVFEaXcKZGRlZmdM
Z1p4MUJEYmExVytadHBzMEdlQ2lVTDBINUJZZXJhZk5rajVuTQotPiAjQUo8LWdy
ZWFzZSBwVHplQQo3NVdkRldZblo1c2QrN2ZLNml3dFdFdXR6QUJzLzdhL25CNUtP
QQotLS0gVEJZY2w3OU5RaXdDMVMxS1Z1WUg2TUZZS1VzdHJXbTg5S0V0MzV0eWcr
Ywr8EtlNmRhwCgSmPWhUVEadEXDBXdhMzjttm1Qk9o3yLOE4oL4fOjp3zzyZx7NT
8dc0l5dRSKHZsO1Q5SmrwHZ68X0K78mJHmwSr/V8meIQhDvFd/StkFQE8i4bpVh4
FjPaDUNzBb1b+S4Vc9wG2plcEhy7cJKSja/PwY6IvY37LxjkhQH6klq0283GgvgD
wcxgTbXYbAmTlfDBbv9BdfLPmFRbJdGfO2qxovijND6Z8QR0y7RWyDhywSzE5K3W
F3zqZs9noq3mlAnq3qxoz16lT8GbPdjKLDK0qoGlctUyAMz9Iw==
-----END AGE ENCRYPTED FILE-----
+13
View File
@@ -0,0 +1,13 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyBSN09X
Nkc3cVJrMGl0K3BVRjlWWXpkMzdoS0Y2RjFVQ2VOaWlEczlCQ1d3CldGcmtVdzg2
dWR6WkRTOXFMeFRML2R3WFJPK2hpREVZS1FJdXJrVFRwOWcKLT4gWDI1NTE5IG0x
d29QalcweGhhWkYyUlNaeExmVXNZT0dlU21uamZYWkdTR1g5WnBPbkkKZXBZZmli
dEtMdHB5UlpkRFVzUUlnSnlCeFRiU3JuZzJWMTBnZVMwL2d0ZwotPiBgLWdyZWFz
ZSAwWSBjSmFnIDx2CjN1NVRpbnRJNHF3QlhheE9hL1RjemcvbjlBVnRTRmhlVXN1
Nng2TWlseGhrQ3FselhFcUZjV2FRNnZyQml2OWkKYkc3dUw5MVZObGt3RHQ2U0Fz
eUVtUGR5Q2lVRnFCWGhHUVVaWTkwcEt5VnlVYkhXCi0tLSBiWVFUVUI5aGZHSG5o
bWIwdGR5YjdjWXZNYXRqSktFREkwWFNlY2xqcEZjChor89j+2SFeCpVkx7CsST7l
0z4z6EGD2gDl3rRXbfqi50gVoAWJud2psglnKkGeiSfW5pIaI1DC1Wt6bEFEo9Lw
GzbIa6gYGcKW3J+w
-----END AGE ENCRYPTED FILE-----
+12
View File
@@ -0,0 +1,12 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyBvMndF
M21hR3p2VmEzUm16eDEya2NtSW54SElScnQzRVhTYnhRNC9oS3dVCnFsS3ZyLyt2
aVlsVEgySFpvKzA4cTd0ZnkwbGRHakJSL2JESU54KzFDNEkKLT4gWDI1NTE5IFQw
cTN5bjJJVUoyckpjWnllM3piV3llM1VRSlN3Tlk4cG0yRzlTU1ZnMzQKQ2s2d0xs
VjBjUlRkbUpHZDV0c2kwUGhUczhuVEV3ZE1WK2NxWndDQk9PWQotPiA+Oi1QYD47
LWdyZWFzZSBFTEJWRHkzIE0oOVJTJQp2THpheXJqYmdPRlpTRXhQTkYzeGsyZ0dG
aElRblgwWW1sT1NjZVNPUFNINXBPV1BxUldkCi0tLSBNOGhuUkNCV2NCZi9PdGxP
WitZYTNwcDZXdGNjbDUzQkVZUEtUK2JsZTN3CrxYEwDQAvqeCckfsLUKB1ixsTF1
rQNRYxioye5T7AZEnOrZg62qkOELmCwAD5UJt5tkNRrmHkm0JwiqNsThHX6qGnHl
iDgytz/Hymij
-----END AGE ENCRYPTED FILE-----
-12
View File
@@ -1,12 +0,0 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyA4bGQr
dzJZbU4reWI1Sk45QTR3UkRoc0NvaDBFcUwrVWlCYWUrcWtYZGpNCm1PWjVzRVlt
UDFKY2ZSZTg4S2pVZ0pDNzR1WklYQ3pEclJZLy9kKzdGTGsKLT4gWDI1NTE5IHFO
TXVRK2d3azg2cHpRanhUNXp6YnRsZW1MUDJqc3l1bnNYNUhHaTIzMVkKWXp1M09H
TnJIazRmb0tOSnE1Q0E1dERiaHZCQkh2YzE1cS9zRUhwaEovMAotPiBzPj5nLWdy
ZWFzZQozSThRWnJCcVFFRHpoSi9tZnZMdnJoRlFud2VISHBHSThMem9qZVVWdS9C
VFBDVEVzbUVCdFU2Qy9PaGdyc0FaCmk3UFZma2ZiR3hmWG1sa053bDBnY04yZ1VZ
TW9jZwotLS0gMDVSaE5aakxHenFPVXpXa1JxczlWQ2x2VGNuQzdwaWZFTTFTaUp6
cnRmZwoomylfwjD5A3N21/mk1Wtt8f4bsK747iZz7KT34kqmoX597rbGYxyip5lg
VLZV6CY4LLRjnnSKoC2hIXU0dgudAmvxhztuaQ42fOc=
-----END AGE ENCRYPTED FILE-----
+12
View File
@@ -0,0 +1,12 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyB0RnVq
RUpSZy9qZ29DemdnWjdscHMvelRYUGF0YVZlNDFtdzFRdVljbGpZCjdWcE50ck44
NVBsbVk3SmR6cHhHdkJ0TWI2S2ppOUtnK3pMK28xeTc0KzgKLT4gWDI1NTE5IHIy
SEUxOCtjK3VFMEozblB1T00yRTE3c3dRSXIydmJHdmEvL25yMStOUVEKbE1XdU4z
L1lLcEJWY2w2WXRWbmVIN0ZBRVl1R1dhNk10MWtheTRCYjloZwotPiBXXXRKcS1n
cmVhc2UgRUBEWyBwamZsOVQKZ09pVGdWUG02WmxUcGNBN3RnbE42V05xRDE2azMr
WHd3VEt0NmwvZnI1dXdiSjNvaStsNTZmUEhwbUp5cVlieQpSYmhUVm1GbEJVdXo3
ZwotLS0gSkFZMFdiRHBjWFJsR3pqcytYZkEza3dsZ0ZyaDkxdmliZ3RUOFErUXlt
cwq9gj+Fg4p2D1548J+bhvJ0re9uVm9TZ9lJSqmj5tMxWRS9aN1j9BlhmK/RnEG9
KcodvBiyqibzauS4KC18xLLu986hK2gn3857waXn/AIp+p8BIA2J9M2M
-----END AGE ENCRYPTED FILE-----
+13
View File
@@ -0,0 +1,13 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFY3bGdHdyBEZzFM
NFkyT2k0MmdkTkVhL1U3M2dEUXhhQk1VejNDMDBDZ2xSaWs2NXlzClJranFmWG14
NVZ2cXRSZzc5N1ZXM3ZaWWpsY3gxYjg4dnRWeHh5NHlNUU0KLT4gWDI1NTE5IGlh
ZmN5Ti9sakpwZUNna1RWOWtEU1VXVkY4cXhpeW9kdUdUMVJaK2dUelUKS2FCbFVF
VStaZENxRlVBRlk1ak9sb2Q4YWg4SmxSV0xRU2R3dGZ4TGNoVQotPiB1UTRIcGlZ
LWdyZWFzZSB9I1VoKG50JCBSCnp2blloZGhpVDk2YVB2ZDVmbDZBeWJxdmsyMUc5
NHk1cDg3cCtVclVnU2J5dUQ4UWh1bnJIMERyTVg4UzlFS0YKNjNLdmZIYk5qWXI0
UVEvcWJzbm1Da2RmdzVmRzhmbEltdURhMVhKL0g5S2hqTTJVU3Z0dnBoSQotLS0g
QUVQOEk0OGpuUnFEQkhkVnBrUUk2OWZ3MkxIVmF4b2QzYjlnMW5veVorQQqEjPN4
qAfHZxuujoneLQu98m+2zXuTGY6tvnnvaZLoqbr0dQgQ+ISHgipnhYM0b6FK6sbG
mgGYyg==
-----END AGE ENCRYPTED FILE-----
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFQrc2JHQSBmTlVp
RnlKNE9ySGo4L2h3VXBXRzIyZGdqU0RtUWk4ZmJVcGNKZ3BqTmpjCjJtQjUyTmJN
dkpsbVI3M01mQlNPSEI0U1lVeUJTMVlXUlpheGxVblhUbUkKLT4gWDI1NTE5IG5K
K0F3QWxJaW5CbW5TZElEVklIeUJxS0JCc2IxaFI5dVZrbDc3NDZGV2MKOVR6M0k5
eW5HWDQrT3Rtb0tIM1EyajI1V0dKbHBLb0tVNU9nb21OUjcxYwotPiA5anw6bk56
dC1ncmVhc2UgPCVeLiZyIH4KTGFRWHBGZFBJUElONUZLb3pJeXNZeXhoakYwT3BM
TW9kUXBhOGhNbHh1Q1RPRTlCRnhSckg5NEUxWk5MVHJucQp4YlFDcVRzK2V5bWVT
V0xLQjN1SjVTaWNJajJaTjRrQTd2VHlMRy82TExXbAotLS0gVE5YZVhTWXl4VUN2
WUpidkJLV1JDU0R2QkdHZE5ZbCt2K2FlbGNjK0ZlNApzDh+kgAy4SBqC51mJi+VX
ON8wbwLVTQRs1H30eyWNzt/3MO++eS4AoZUKQZUxURwXfhV0t0zd5/MlByBsqaHR
+W6O/9Dp8e/8GYSX3D892r1LKN0AYHgcKeKwEtJojt5CTNJS2IgU6UxZhTliqAEc
NkfxvcoAEHhGhPOudEIX2SgjrgVGJA8MYm6/46zAolZws3TWim3NEgJpb9tWXpvi
1f/MXuxiowplF+PqCsd1EGzpXKsvADq6Rwyxpo6CbJzrq+GhFrTHF+LRkzjWx6JE
LUsZwDqOZUY=
-----END AGE ENCRYPTED FILE-----
-14
View File
@@ -1,14 +0,0 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpOcUlvZyBlODZr
RHpJcFdFaVNYM0x1d3QxVmhKTFBkMndrakFYS3FxWk00YWxXejNzCkFqWFlCWUt2
cGluRGtxODQzbElhRWt1TGFVeTE1UU5SSkdZejJzNVdhZ3cKLT4gc3NoLWVkMjU1
MTkgcytxUmZnIFRpeVNsT2Jqc2I4dzB3cEFWK2ZEZlpJQklWSnhJM3o0Ukhsc1lz
REZ0VFkKWWJPckVSNlZHREJIVVgwNVNBSkpSbHRPcUxIWVo4ZTlyVkovTGRpZThL
MAotPiBYMjU1MTkgUGxhR2d3TVh1dnJwQjNoY0pjV09halZKZFhybVk3Vjk3Tmwr
bDd5ckp6MApBcW8zbUl1SnhmOXZwOFRNUG1EZUNacDlXdXJSbWFUeG5GNjM3eXJo
RmR3Ci0+IDstZ3JlYXNlICRoICVbfmU2fQpFYzNyZXBxVU5jT3JSY1NFMGEzUnVF
WFQ2MmR2SGQ0Vnd6V0VxQlp5bE5LZ2NML2hyd09LOEVPL2lGREdLR3FMCmVGN09J
OUNscVh1d0VSdwotLS0gbTB1NnZ4Q3B6WE1KVzJjbmVwL2dEVjc4WnRXZTlYbFBG
T3htUHBWang2awr0OgkUO6XPZji5ZBNpqGwOlwpa605t38QCmFSXvPQhvT4Gj/0+
rUvg7zWf5Yb4c86EDD05CsqGEUQTOKEz08z0lewN5kuFfZmrYQY=
-----END AGE ENCRYPTED FILE-----
+14
View File
@@ -0,0 +1,14 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpOcUlvZyBJSU1V
M1QyaWcvcFJlUlJEcDVvblJGcXJDWk11RDhCZzBkY2Qyc2lDQW5jCjJVRVBWMTZy
SHNTZG9ZSnZ3RjRqUE9Ub3JKZERkcTZpeEhSUlppTmVsZm8KLT4gc3NoLWVkMjU1
MTkgcytxUmZnIFFsbm1oNk1jNndoU0J3SkdFSkNhSGNVRjdQb2JSUFBnczVwdlF0
bDVhaVUKaFBzSHF3THNiL09Ib2ZhOFlyNVY0Q1ZwOUMwczZiQW9jVlNwanRYek1P
WQotPiBYMjU1MTkgcEppMVBpbmRGS1h5RUgwNkZPZ0dXWVpkdXlZcFc5S3dQaDA4
THdhUm9oTQpLbUpTaVVuQ29Zc3FuQ2MyaFcvTkxVK1l1T3V0L3FZSXZBS2dlY2hM
VUNVCi0+IENcLWdyZWFzZSAnNGJjfiB1IHB3QDpUIHsoQi57Ilw9CnkxdFRqWVZi
ZFdHQXJwNGZuNDg2Q3cKLS0tIDJSNmthczc1U2xxSlVKZDBLc1BHNnFMV3MwK3Qr
ckJDL204d210NW1Pb2sKCC+sa8uPupC3Rv+o12XT/wTmLsKhtaE/bbshPCDIHUFn
cpTwpY96JsCShAjSb6n7Xt6FgTKTFt2iDsGQ6+sLp0AJ2quxRaoxmqaFVsz4p8BL
gF0On7LgZg==
-----END AGE ENCRYPTED FILE-----
+11
View File
@@ -0,0 +1,11 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGhNYTRudyA4Qk8z
SkhFWWMyaGM1c2luVzZzbWNvT0ZsS09yN0w1N01oY0tldWFXZnhVCkM0YzBVZUM1
MmRzdFlCL1o5ZTdkTjkxQ1YyQ0kwbnJ0eVRuYUpQNWw2c0EKLT4gWDI1NTE5IGli
dkp5RjZRSmROMEtQeWJiVkt2UktxdTAwUzZBRW9XajFDblFqZ3ZVR0EKYzFubnp0
UHo5WlViYTNwbXFBd01qM0R4Wk82MTV1TzJsVVczdGRNSFRBQQotPiAnLWdyZWFz
ZSAuPzVDCm1SZkdDMHRjT2NBVXI5ektKZ1R3dXhMUEVRblhBdC9mclFZSitSODI0
OHZzZThEQnlBY25lVnFTQXRaV2FIYTIKeHkrY1NRCi0tLSBhUFgvd1BUbFlJeEFO
RWRBQzcrT0ZFMG5SZVliNlZnK2N2VDJVV05UTkhBCrZM7RtMrOVIGIpod8aU4GLn
0KBGTSq6kE01+f1kmTZDAKHx/LhiWgHYKLxTLW4VpYnUCg==
-----END AGE ENCRYPTED FILE-----
+16 -12
View File
@@ -1,14 +1,18 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IERMTWVGZyByYlJn
aERLcEhadS9jVUlyUmgxWEk5K0U2cE9WUlhCc0ZXbzhDRnZLTERvCmo2Vy9XeFhq
NTcwdG5PZjlDb1JIM3BYWEVzMlBFWHFmRWt2dkF2OEQ2TDQKLT4gc3NoLWVkMjU1
MTkgT0VxTXNnIHROaUlGUExERTZFaU5QL3dBcFpQVWNobGQwSEZ1YTU3NXJkekRi
c0RUMGsKUHg4V0hIdFJ0aGxwOTFhaVB6MUdVWE0wUFgrMjI2am5uZlhWL09ObjhB
VQotPiBYMjU1MTkgTWwyQjZjcUFYQ01KUHpoajRrVkpZd0czSzVrMTZxdjVHaHRh
bERCSjBqSQpYOXJibDZPM2Z6bkNCSGpMRExZT21UTzU0N0RiT2FNM0l3N1pnRkl6
WUJBCi0+IE0qLWdyZWFzZSB6TDVwIGRiQm0gajFFIEVqUXcKU3pEOFBqRVQ0dDZi
REszS1h0T2FnOFF6cHBrN2xtOHdEQkIrCi0tLSBTM3EwNHhDaEo1eldDOTN5dzQz
Q3Rpeno1K25KRU15L01wU21tczNmdlVJCqHBdFLovtLJGH9IY86pvc3xhpoLnfI/
OVAF5RdpR9T2oNCr3oAiVURkPocYXLHnbjZhLKoj3uDoSZAE52VN9l05jhyX1wwY
/Vfnp48kP8xfbQ==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IHNqUFR5ZyArUnBS
MCtjZERmK2IwTm16eGcrZFF5QlpYZU9VbUNzbHZ2VDBoZkJkam0wCndDdmhHc2pu
TFFiT3MzcU13YklrdFpiRW1ZSU4zUGFQbXF3ellUU3U3bUkKLT4gc3NoLWVkMjU1
MTkgRExNZUZnIE9EbUtYRFg0Z0xuVGNRM2pad3FFVGRDVTA3ZE50SHlvT1ZrU1NW
b3VYREkKL0dPV3RGMHYyUW9jSlJhTU5yTnR3L0pHVjZTNWpoaGJiSmlPVWlDYlFv
RQotPiBzc2gtZWQyNTUxOSBPRXFNc2cgRkwrZEY4RjAxYzhpbEE2eU0ya2N4emE5
T0NlUnJwUi8vdVlJWlVOWEZESQo0OFdldUdML0hoR0NENHp2UktCTFhOYkxUZyti
OGlhS3V1RnFUdHhVT0JvCi0+IFgyNTUxOSBOcnEzanBFWnltMUwwd3VBd3Jablk1
Z3hDU283RVJxSlkzKy9JQW1adVVVCmtnSjVTTSsxblpsczMzR2NldlFlTFk0S210
T1AxV1RQRjhDSU1CQ2p6M1UKLT4gVnNOLWdyZWFzZSB1fDAgYy1xRSBESjoyIDJz
CkdRcWxTa1NHVkJDcUVmeDlIVEZTcW13N0I4ek5jTjliQ2t6Zk9nRkloQmhSY3hG
TUdJekhXdlRzUGJ6WU8zRXgKZXFGUGgrTndSQmVyMFcyL2J0bEdKY09paTkzRHd0
R1ZWVVVuaDljWE4zK00rdllOdGRVTzVZTnFtT1p0WlZOYgpGdwotLS0gd3dvU08x
SzJkdjAvQys5Mnp0dDZQUWp1dzZ3U2tuYUpqR09xeTJnSzVDTQooXx8cndfMYlmf
7eCLssPnHKj7KKgUfiihj91X8pokJR/++wQSarMdRtFB0S0MpDs/khwgG0HkmrKp
XB1jureGwJs7gmJ6gafKCKSkBv9Jkaw=
-----END AGE ENCRYPTED FILE-----
+18
View File
@@ -0,0 +1,18 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IDYySmNjQSBONnFw
QVduaWJac2hVVDl0bHY5dXFQSkFUNGlWaTNUbGkxN3d1RWpSZGdRCmpBZ2pLZHZ0
V21EenE0U3lYblp3dTFyRlRrMGVjWGpxdVVRWW5pcnpCVlUKLT4gWDI1NTE5IEx0
QUM0aEVsbCtLd3ZmS0kyb0Q3d2RuVW1oc2pHSFpMbUZHY2VXYlhYR28KSHhraW9K
RXArS1lia0NsMWkvRFhTVEduM1M0c2JnYmduY0ZmSjhCN1M1YwotPiAlL1lJLWdy
ZWFzZSAhVCpkTAplMU5KckU1K2diWnBreG9LbERtbGJZQjZwK0lOZjJHcEJyMWZp
c1lxL1UvbTE5QzRIMm9wSXFmY2xUSzhBMEJiCmgxUQotLS0gOUhYVERseXJlVksr
SEZtby92YUIrTG4ra0hneklheFBERHhqSlFlT0YwVQr5gAYwgdPqUqW2XEtN7+ZR
VblX1NFXjMLljiGcW+ZlMXHIaKMxizPr+S/6U183e4wiUUqcpipnznnslhm/Zkny
iHmW37pnNC0T9kctqOXeEjqsQxAMo2YKFroxo1iK0YvN+VyoIDSYMDKu8uDe1Cna
rabi42KfdZNDjtPLrJyHSo2cCdnDUeWalAjQ3eQqn4y85gfPZq8kZcwvK6SmurDN
GkwxXpZpSd6MdY4fIaaBEwe7WY9hq4fE7WgcQaz5yG47F+ArCwWauAz38+309XHj
omsDSzj1jrN7T4kr2gjtUX227NrCw3REHYRNN6IQK/6fDNyPF1wbLFpXU4dnANLT
OdMRnsDRPafNLAOYn0pgCVcVs0KLpaJvy3KLevVt2MZEtSZe/S+ys28H3JJCB8qz
igaX3gw9+W8by4ET864fpFgufJrpufVvdz/MZ1207YHz1URQACWRtFKwnwfzP45+
l47Y4s+xy34V+IXLJduEQdQ0ZHqKmTv02BjEjqksBwZswjI0EbTvD3Nsiw==
-----END AGE ENCRYPTED FILE-----
+74 -70
View File
@@ -1,72 +1,76 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IERMTWVGZyBVUDR4
Um1XS00yUDFIRnYyZzg2KzYzanBIMWFNTmV1MVF6ei8rZDBiTXowCnBBRFEyQU14
ZU5MdSt0NnRJdUMyMyt6dVlOWHBqUnkvRWNmMjNRUENKeTgKLT4gc3NoLWVkMjU1
MTkgM2JCM1pnIHFyc2laWnBTQU0rcThOamJTcEtlUGNsSW8reTc2eTJjbVBkZlJu
cXEzbUUKcmFrTEVjaXY2a0lJNEtCWXNjTUsxNENkSWZmZUJhRm5ydWZ6WlJ1aDdR
RQotPiBzc2gtZWQyNTUxOSBxKzBYY3cgRVBuOEJ4K0NRVjdLdFhIU2Y3ZGQwL3F4
clFjMVNsOWNvTU8wVlRoNG5CZwpycFRlMzFjZ0drN0t5QXpoMkJ4aERMYkxVSFhU
STJTdUNzeWtkUmFMTHVBCi0+IHNzaC1lZDI1NTE5IFpCM2U2USBBRUhnNlNzbDVX
Q1ArRENrZzBrNkhhSUd5dEZnM2oxRUtmYWx2L1NtbG53ClZIalNsaUNBUUtKWGpT
dTM3VExldm0xRXJoSWZ0SU4vdWk5SDlZTEFPczQKLT4gc3NoLWVkMjU1MTkgajY3
RlhRIGR0VkhtNWxCK2xSYUNlS2hhdzRldEVZRDQwNmVnN0dtRTdOamFSM1Jqek0K
YS9uWGMyY3JzeUZCWkhLTzk4d1dxT0NkbEQ3UnlWOStCdUh0bkg3K2N3TQotPiBz
c2gtZWQyNTUxOSBjMFROYVEgYXJhZUdOeEphOGxkMTZmamJxdmMrTElkYkFScVA3
alExSC9TVTJzeUFqNApsZXo1cC9wdnp3Zml4bG52ekFHMEUyU29acFFJeU1VN3Mr
ZlorWC9VWDZzCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyBTenhVdjNncGxudDJ2Y3lw
K0JIOFJDd2VVQzFkWGc1STROdFZqbnUrYlJnCk5MTWxRYVRPcUFjMmdySEJ5Rndy
TzdnNGErNnBRa1dTSFVFekxQUitOYTAKLT4gc3NoLWVkMjU1MTkgakk4UkFnIGtL
c1psRWRRN1hNZUNiVHFmR3JGVm1jUWJtdm91ZVR6M01zNmhGdW9pRTAKNGpwek8w
QkRnSkZXUjhEMEpPaUdkeGwxZDRGbTRSMjg1Z1pMdEVSaTJEdwotPiBzc2gtZWQy
NTUxOSBoTWE0bncgYmRqR1FRaDdQQ09ZZHQwWmQxVUJ2QWdLYjdoRlNLU09GYUNi
ajJhMWx5QQo3ZWFNWjMvTzNxSXJjeTY5cTNMWmk0K0IzZ053Mmd6T1hhaVFTVVBj
NHBrCi0+IHNzaC1lZDI1NTE5IGV5cTNkZyBXbUdJKzdMZDF1NW1pTi94aUtjNGpo
aGVLbno0RzE1MXlURTJJQ3hRb1dVClg5K2FwRHBvcXIwVUl1U21GSnJsSmJmMGZN
cmdBcmRiRERzcjJmZzV4Q2sKLT4gc3NoLWVkMjU1MTkgN1dROVBBIEIrays1YUJN
TkRMS01oVzQyZEJuSjFPTTV4YkZSMDdTV0UvZE4rZ1U3SFEKWEZSL0g0dmFnelJC
S3VGZDlaTHhJQ3NaaEc2aUsvRmdKdjRNZ1VXMExmQQotPiBzc2gtZWQyNTUxOSBn
U3hQMFEgODNUUEg4M0hLL3RSUXk4M3dGV0tZNjJXQWxabmxLanF0Slc0WWMyUkNo
bwpCMGlaZDdodk4zeDROczVFc0FxM25qMFdicWZZSVpjb2tiT081bUVUTGFzCi0+
IHNzaC1lZDI1NTE5IFZGY3c1ZyBKanVnSDI0bUhvS3RVbzdSc0s2TmQzSVdEczRF
eU1CazZPM094eEt1ZGp3Cm1HWGluLzhoRUtNRDZOcVJDVUR1R3dneHNHa1M1VGpH
YWF3TDQ5cS9saFUKLT4gc3NoLWVkMjU1MTkgaGtidHZnIHJLN2dJQnA4eGo5SnU3
SkttSlM2YXNERXJOYjc1Tlo4NnhFakdYT0dqUWMKQllrZm83NHJrYmtWaytCc1VI
aVhESUtYeHpoT0JmdStSRURMZ0JldlQwYwotPiBzc2gtZWQyNTUxOSBldDJ6cFEg
d3NnSXpMRzU0QjBBL0c4SGw5Znl6d3hRdWxvbHdXZCtIeVdnU1F6MFVVQQpiQjVX
TSsycGZqMVNWajZHcFkyN2JwY2RqcGRlNitRWXgxWnN5TzlpU1lRCi0+IHNzaC1l
ZDI1NTE5IFpiTEpXQSB3VmFwR2ZqR2p4OXlpSnQrbExqTktkaEJ4emxLM2ZZbGdx
U0drOWtxUGprClgyYnd1M1NQem1rZkxwUk5tVXBLNGVDMFVjNjc5Lys4N0RsajZN
eG9LeEEKLT4gc3NoLWVkMjU1MTkgWk5xSW9nIFl3QUlPNnVHNXNwQ2sxRUEycFda
TkJsUmx0dCtRdnRVRVAzY3pPbm1LM0EKbVZDMHBSOFBiMFVQbkxHOGpkQjhrbDRJ
YUN0M2JPOW1PbjVtQURaUnVFbwotPiBzc2gtZWQyNTUxOSBxTGpxeVEgUXc5TUxn
YXk2ai9EbHdVeFVsUk96bHZIRFdlcDFqYkxLQ3FJaFBQVG93bwpTSFJ5dmJiN2tt
TVlLUlBhb3VmSG8zVHNYdC9HVjcwN3JUVVVWN3BFUkhvCi0+IHNzaC1lZDI1NTE5
IEJhUWxSZyAxYkNsekljV0s1ZWR2eVZnSk9Oc2QvWjE2a2dMaldDYzJRU0FWUVE0
Z0FvCnk5UlhrT0ZaK3FXTThVY0RKZlE0d0FTajJLRCtSNWdvWjd5V3hZNEg4dUkK
LT4gc3NoLWVkMjU1MTkgcytxUmZnIHA5cGpXWlMvTlVreDNremhCa1FDUlFVYk45
OHhjaUhYTWZVa3dySzNLeW8KNXZnZzFPNC8zMExuMG4yUTJFMDgxTFdGdDZ6VVl1
WEFGUC9zNVgrd2RRdwotPiBzc2gtZWQyNTUxOSA2MkpjY0EgMG51elJWRWRDNzRM
SERza2RiNFBoOHc1eCt0SWtmUy90dGl0VEd6QTJENApodnNBM1FkUlZ2ZjB6b1Np
QWNXdjVoNFlsa0NOQWp6TUw2TVQrU3VNRlVZCi0+IHNzaC1lZDI1NTE5IC9oeC9k
QSBxdlhXM3Rqb3J4YjVDUzdhUUVYQlFvSTJjZXA5MHBYY0NXWVR0VzllR2hzCkU2
K2xCY2tGeEJjK1dMYkhCZ29pR3EzYndWUXF4bWorNC83d1E3U3luMFUKLT4gc3No
LWVkMjU1MTkgV3pMR0hBIGg1MjIydFM3YlM3aWVFR0h4TytwRWxYWTVkTXN4VkdW
TnJ0bXQ0WTduQUEKemtad2lsTTlPUEtUaVpFLzNPVFhqd3VpeWJWbDFyayt2VVhy
Q0FSb01rRQotPiBzc2gtZWQyNTUxOSBISi9KN0EgTkdKZUx2U1NTODZzTlpJb2xT
VFptQ3hWOS9BMCsyZXdsM3ErMXhtaHlFQQoyUnp3RW81VUh6OVRQcGhJOXYxNXRR
NHNGT3ZIU2ZQb2c5aEg0UmhRcG13Ci0+IHNzaC1lZDI1NTE5IE9FcU1zZyBLMi9r
bmFyTnBCU1lsdUpDWTJsd3ltRzAxZmw5eDNqVUtjMkR0OGF1dVRjCndrNmVHcmYy
c0lQOFM5SjBjN1ZqZXk1Vkk3RzA0b3JtaWZrdDBmdmFrYXcKLT4gc3NoLWVkMjU1
MTkgL0VKWHZnIEV6eVNrNEZvVWhPMXppeFpmSEt1Y2NqcmtUOXAxQ1lOWVdtcnlm
R3B3VFEKVXJJRWlmOFVHZ3hyWWhLZE03VlNlM0M4ejFDYjM1b1c0YWhMMVcrRXlH
bwotPiBYMjU1MTkgUkRPY2JrSGZYeGNVWldVbTAzbkdtbHdUS1hoZXg2R2JEOGtC
ckZSOWV3TQpGejNQOUlxb05oWE9hRWdjbzI2a0NKVkpHMG1PMWlMWVZpYkVQNlpx
c2xRCi0+ICwlLDsrbWYtZ3JlYXNlIE8mcz1jaywgeiJbOE9FeyAjXFl4Ugo1c2VM
THdsOFlhODVMV3JsYzY3QU5Hb1BJTHBWNFEvalRHN3lXQlBBZFVvQXRIdXpXYVpU
b0NLRG40WWhMQ2hDCnZyS1d6SGxGekIzWUs2Uk5XSFRscTIrTTEwNzJKMExGcG5m
UWR0MWtBNnk4bDBYYStVQzFwZDlWRzRDNXJVZm0KajVrCi0tLSBkQ044Z3A5R0dt
S0htaUZaSzdPOTNCcXZrSWFVVHlTZk0zejBuT21yQzFBCo6rc9fznstf3eXBRUA8
73MZAYqSnJ5wVMrYrwGfT9lXvKbHCOvkgjUI6Ieo0nuw+aZpXoV3t9HfZv62UEll
ZZVu+ieRCZqOOqZKKZ3TCP24vdXun8Tu+3YK8fyn88QSRH/0ZMnqI9FXbtsUhsF8
2o7m7Fn48B0nVKy16HZyBsksknAuZCkfS/JOkgI=
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IHNqUFR5ZyBkWHB2
MHNUSnRIQkc4OENhN2dVdVlFZFRlWW5oVW9WbENaTGcwK2ZnQVFRCkQyYjdNaEtK
SXE5RExTMm9EZC9GSEJGcWNabUgyOERBZmFmV0VqRFVXWTgKLT4gc3NoLWVkMjU1
MTkgRExNZUZnIEhJNHN3c0lGL1lrMTA0ZHV2bHdPZ0dveDNBNVlzazEwbU9NcVZl
Z3RKelUKaWEwTTZvSTA0T2pKSGVoc3gwL3VPWjNPOFk3dTNjYVo5S2tPWUNtV3Fw
dwotPiBzc2gtZWQyNTUxOSAzYkIzWmcgMEN2dDJiRHYrQzRIb1JJVXp1V0ZyMkdu
RWNtKy93QVR4SVJkK1VXTlUyQQpNdUU1c1NOdi9ZbmFTNHJBWmNTZFp2NDZORWp1
ZzZzMzU3ZWFVYU1Lc1k0Ci0+IHNzaC1lZDI1NTE5IHErMFhjdyBiVS9ZeVE5Vytp
a3p2c2xYM28rM0Q1UWMyNkQxYWRScStGRGVhTTYvQWc4ClNvSksydmhid20yTzNC
ZHF2b252MWwzRG9Zdi9uRVpqL1BacGRaemo5OEkKLT4gc3NoLWVkMjU1MTkgWkIz
ZTZRIFVZUTVjNS9pak9JSnF4N2JOMEZINmtKTzU5OUxHbHRKeng2cldvK2NXU2sK
T0M3QTZJU2lqMk9nRGl2c3QwNTlWOEwyYVJUK3pleEtMZ0lYbm9TSmVUZwotPiBz
c2gtZWQyNTUxOSBqNjdGWFEgRzRXYTBxQWduN2QvZk84NXVWVHlQN2RiS0pkYXM2
U2cvM0JKRXZvTjAxdwpCbWdMZVpMaXBBNHRRZjN4aU5lNmhRNmMzSnBIWUNtbW1w
ZStLaXlUL09ZCi0+IHNzaC1lZDI1NTE5IGMwVE5hUSBxK09aSTRaUzZ4VnArMlF4
ZFZvNmUrR1hPKzB1SUdRS2Z2dmgyVlROOUVZCllUcDNCSEpVUmVUN2RSYjZ5aDdp
SWhCVFlwcWxJMkxodEwrQXNDOTh0TlUKLT4gc3NoLWVkMjU1MTkgbjhDcFV3IHJ4
Y1krN2hHTjhOakZSZ3VNNEZnYVVLb1BTZ01iT2dyRjdUdkRodit0QUkKRitpVFJB
WDU2eGw2aUhQZFcrMTB6MU5VTURPNE5HbUFYendOMnNDRXRQcwotPiBzc2gtZWQy
NTUxOSBWN2xnR3cgcUNRZkp6R0llR0o0RmhvanBoamdoNVBKRWl0Sm1sQVhBRGJw
MFFDcmdUbwpENlEvaTh4OUhVQ0VTeXBGMTBoajd1eE42YzYvc0ZvcWlVYU1HWnkr
R0FnCi0+IHNzaC1lZDI1NTE5IGpJOFJBZyBFdEpIT3F1MGVtZ0ppN05hVDBLRjZz
eTRWdjZkMGM0SWpqeVJpRXRGc2pRClVpTlB6Q3lCZ3FXd1g1eXU3Y3grRVBJRjBC
aERTanp4dDhGRFdteThNNTgKLT4gc3NoLWVkMjU1MTkgVCtzYkdBIEpselBValht
TTNtY2lTYWg3VHBUTWNIemdiQXpHd01jMS9BeERMclQ0RGcKYU85dnN3ZEJyQmZZ
ZE9ZYXl4Ym1BZlBkcm9jMXBhZ1J3aUlxR1dPNm96dwotPiBzc2gtZWQyNTUxOSBo
TWE0bncgcXdIK21EVDVMZGhMMEltQ3RNbkx5NDhGVWRVdU4wZlhUNDY0bEZTcEZG
QQpUSGZOb0FoSTgxckp0R3dxVjVPZkQ0b3Z5WXpjU1Q1Qy8zaGNVNzh4ZGJvCi0+
IHNzaC1lZDI1NTE5IGV5cTNkZyBwaGF3NXVNWUVQUUpuUm9pVHVRK1NoV1FmVFMz
dys1bE9WaDdNV0RXZFRVCmVJSUx0VkQvbDRjOWdvNlhCNm9RSnhnUHFpYnp0ZjVM
UG82UElhM3R6MFkKLT4gc3NoLWVkMjU1MTkgN1dROVBBIERKYnFGQm9pVlIzYWxu
M3Fza0RucE9SczQzRk5ialU2R215L1NwcVU2bW8Ka2hCL2JSU1c1bEhFS0t3VnEy
YWtaMG5mZHBxYjNkK0JQUjlmVHJYSmNUMAotPiBzc2gtZWQyNTUxOSBnU3hQMFEg
VHhRN0VEV0xGL0hJUHd1V0drWVJzaWtucXJ2c2xMUVpYMFc2TklLUGdYVQp0VWZj
UEZGeGhaRDh4SmFsek93ejBUM1A3OVorTWFmcWt0QkVCZmV0QU5ZCi0+IHNzaC1l
ZDI1NTE5IFZGY3c1ZyBmOHgraG50b2NiREZIcjRacTUwZ1Z3R2h0QXlHMTl6SFNJ
Qlc4MTFtT2k4CkVSdzY3cEVpR3J3L0szMXBVdmd2OUFsWGJwanRtSGl3QmRyREhJ
WjRwS2sKLT4gc3NoLWVkMjU1MTkgaGtidHZnIGVVNDZzenBDRlRmeW4ybUJqamRD
UFFpWDY1ZjJuK1VQNWJJSGIyaFJnbmcKcmg3b284YmZQUWt0clBjVDZVZk5CUUlo
aFRWWUwzVmVPcFBDaW1xKzRqSQotPiBzc2gtZWQyNTUxOSBldDJ6cFEgWEZpOXdx
bWo3NnZYSjFTdldoSDBBMVVobHRXYWJjZEd2RVBIanRrQUZROApkZG82RUZHSkRH
TGkyTG1tRUlRMEg5MVRwRHNjTE9UL1BMRUpDdXVLZ2tVCi0+IHNzaC1lZDI1NTE5
IFpiTEpXQSB0ZWtnRFVWKzRkSU45enFadkx6N3FpRmE1MnByZEljd3FyWGFyd2Ja
QVNvClpiZkxsQ04zR2pzOXBtODdnbjdSaHBtSVFVT3V2aFdyZ2FoaytISmNzR0UK
LT4gc3NoLWVkMjU1MTkgWk5xSW9nIHMvWElOMTgvR0ZveHZLSlVqNW9PSmNvdm92
ZW92dHhvbG8vRzl2NFdqMFEKUktLWEJuQ25LM1J6OXBvRlRlMEFEeXlhV3M1Yk85
Wk0rZWJMQ3U2SVYrcwotPiBzc2gtZWQyNTUxOSBxTGpxeVEgODlpYkhNQmFkNjlp
ZUYyZ0VDdzBsTmk5TGVPU0VTRnZlSUdMazN3cXB4awpGbEhWMVR0N0NzTGljeEpw
dWtrTXR0QW5CVHE5enA2dXZZNm55ajVSa3NVCi0+IHNzaC1lZDI1NTE5IEJhUWxS
ZyBJc2xSZUJkaEd5U1EyV0J6T2pUU1ZWMnRPSzYwNFRERndsdThYeFFQSUVJCmlT
OWh1dnpNRDU5WlNSaW5FTUZ5QVVHSmw0NUpQMklOb3JYU3FSM3ZTWEUKLT4gc3No
LWVkMjU1MTkgcytxUmZnIERvYU1PNXJkWEs0VkI5YWNuY3ZGK2xpVkpxN01zbHA2
cm9DUzk1WHIrRWsKZVRGWUwrdmVnOTh4clFHdm1yL0JuSVRpVldjWkMwUkdFTk5J
LzNlT0dZUQotPiBzc2gtZWQyNTUxOSA2MkpjY0EgTXI5QVZySXpsQlVoTE51c29j
MWltaUZHZUlPUEo1WXZNdUUzSWdWRU94MApMVUYzYlhNZDc5RTRzc0NxNW5PblU0
WndIV1FZTm51ZlhQdWFQa2NNS25FCi0+IHNzaC1lZDI1NTE5IC9oeC9kQSBWUkJS
ZmR2a3BDdHRrVUhqejFjZFI4cWw1MEkrRWUwdHZHZEloemtUT2pBCmFYU21lRllo
MTlic204cU41Vi95dFBMSSs2eWtVSzJndG1keWdVeUgxNFEKLT4gc3NoLWVkMjU1
MTkgSEovSjdBIGhhck53d1ZzYVY5ZjF5VHpXYVBDMGZ5SGdTL3B4NHQ2a3lENGti
NXhZVGMKV3B0d0IwNm5qM0xDUWdOTEZ3Q3ErWGdNRmtIeTBMSjV1eDYzMUVYVFpY
cwotPiBzc2gtZWQyNTUxOSBPRXFNc2cgcm9ZOS81emFxd2toNTRFUW1LRi9jU3FF
VUdYRzRWTm5uV0ZjclJPZmsyQQoxZnRtTzZ5YzRJTVRGalU4NFZhQmZlMjhtY2Nv
Q1oyZURhQUpjR2dvRVNRCi0+IHNzaC1lZDI1NTE5IC9FSlh2ZyA5aUNiQk1FZUtU
S0hta1lOMVlWL1RwdjUvQnl2MTg5VWQrMnNCVERkVkZrCkFraTR4UmFBbXpOR1lq
TTFCSmZmV0R1VjhWb2V0RXdiYkpaek0rdGVsRE0KLT4gWDI1NTE5IEpZSlNOZk5D
WlZ3VFFpVUx2RHlwblVEZHZyVVNGbHNrZ3hUY1FYQUJNMWMKYlo4dFVnS2hhYk1m
THZXMktudExKdEE1enlGWUgyM3FiMGpFbmR6RkNyawotPiBGImFlO2V5Ky1ncmVh
c2UKdTZXVjRtcTR2TDFITzB1d3J3RG1hejk1am15SEswR05PMFdoTXR2UVpoRE9H
dkMvQldlc1FPWlRFSURXN3ppSQoyU0pSRHFIS2I3d1dtTE5OTFFXSTYyR2tTbTZB
RDVROStRCi0tLSAvWEcyR09pQnRhSzN4d2kzSzduOUVtTXd2U25CWmZJdWt5NnNl
RHVieGNBCi411IjgmUKttjX6ljaZGWivstOajx2pkTVLV/zFiEj3jv+KDGy1psZQ
no+eatGMO8LeJhGJ6H7TBKOmJhFMfoQp1XKJA8OGY+FGZ98bit04djo3jqbVSOms
JSPRTvTxxQx+40yO+ETV+2qkRU1OdJTobz9YvuqGlHrJS8UNN30QMPT0ienu3QTY
Tbo=
-----END AGE ENCRYPTED FILE-----