Compare commits
113 Commits
installer
...
e47e30df19
| Author | SHA1 | Date | |
|---|---|---|---|
| e47e30df19 | |||
| e077c6e30c | |||
| 6f8d499ec2 | |||
| d0859d4c5c | |||
| 9b582bec7b | |||
| 8c4223af18 | |||
| b95992735e | |||
| 0d1562d150 | |||
| 49de78d47f | |||
| 832d0b5542 | |||
| 4d4c05ea70 | |||
| 0bd08c4a91 | |||
| a86888a2c7 | |||
| dc1ec3bf5a | |||
| 823ed83252 | |||
| 300103f2ba | |||
| d33f32ce5b | |||
| 14d9bba4eb | |||
| e63cee7b09 | |||
| a8318d3de2 | |||
| aab4a193ae | |||
| 541102f683 | |||
| 805590a705 | |||
| f8dbd99a7b | |||
| 90cc2d53f1 | |||
| b044504938 | |||
| 98ccc23ef5 | |||
| d7e8ca52a0 | |||
| f1cc0fa25c | |||
| 43828ad34c | |||
| 36d7e4a7e3 | |||
| 2f7d5d3908 | |||
| f3a0ca3f57 | |||
| 023b3f6605 | |||
| ecd3a46591 | |||
| 6bcca599e2 | |||
| 93529c578b | |||
| bb32784962 | |||
| 6ffa35c414 | |||
| 3cc5448410 | |||
| ee4178ee2d | |||
| 2bf18319c9 | |||
| a394b9124a | |||
| 5bc48d33a3 | |||
| 365ef5d49d | |||
| 0206d52fa2 | |||
| 5526e07e65 | |||
| dde682390f | |||
| 4ec59a64ce | |||
| c9c788e261 | |||
| 21c24216b4 | |||
| 2ecd350fcc | |||
| 3aa873d52a | |||
| 0a8ad18de8 | |||
| b6b94fea5a | |||
| de9762b272 | |||
| 8d1f4d51d0 | |||
| f1dc04ec4b | |||
| 7951c777cb | |||
| b031840f81 | |||
| 45861bef08 | |||
| 4433395125 | |||
| 3306aaab5e | |||
| 4480302c65 | |||
| be7fb5a243 | |||
| 25758bae08 | |||
| 7bfe9ad697 | |||
| 7db5e18974 | |||
| 20b7da74bf | |||
| adaf8b6a83 | |||
| 1f145334f3 | |||
| abf9f1b465 | |||
| f0740741a4 | |||
| 0c0b66b8db | |||
| bdf3c04595 | |||
| 02795a6ee4 | |||
| 8fa4a7ee60 | |||
| 773674d879 | |||
| 12c5ca126d | |||
| b38a2a07e2 | |||
| 0dc474887f | |||
| c8bd63ec3e | |||
| d7522f3f97 | |||
| 58c76f822f | |||
| 31bcde23b8 | |||
| fc2fa0666e | |||
| 854cc48479 | |||
| 85a4b124e5 | |||
| f322f3ebac | |||
| bc74fb4968 | |||
| 584abd4991 | |||
| 05074a1fd9 | |||
| 69060dfbff | |||
| 8e288a9e2a | |||
| bb03b6fa76 | |||
| fd92cfae6e | |||
| 25267d09a2 | |||
| f02f538ab2 | |||
| d319657680 | |||
| dff5a4e6d8 | |||
| 2a8ced0fec | |||
| 36c7096120 | |||
| adfcf2f848 | |||
| a3870a4293 | |||
| 8f4b61fc2b | |||
| 44e3a3011a | |||
| 45c972cca9 | |||
| 7bd5b8cbdf | |||
| d1eb9cc981 | |||
| 7a2ebf6872 | |||
| 72b8bd089c | |||
| cff229f487 | |||
| f3ac3cd67f |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(nix eval:*)",
|
||||
"Bash(nix flake check:*)",
|
||||
"Bash(nix build:*)",
|
||||
"Bash(check-system:*)",
|
||||
"Bash(build-system:*)",
|
||||
"Bash(build-home:*)",
|
||||
"Bash(git status:*)",
|
||||
"Bash(git diff:*)",
|
||||
"Bash(git log:*)",
|
||||
"Bash(git show:*)",
|
||||
"Bash(drill:*)",
|
||||
"Bash(ping:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -4,3 +4,4 @@ result*
|
||||
!/.vms/.gitkeep
|
||||
/.keys/*.key
|
||||
*.swp
|
||||
/.claude/settings.local.json
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file provides guidance to coding agents when working with code in this repository.
|
||||
|
||||
`CLAUDE.md` at the repo root is a symlink to this file — edit `AGENTS.md`, not the symlink (some
|
||||
tools refuse to write through a symlink and will error on `CLAUDE.md`).
|
||||
|
||||
**Prefer this file over agent memory.** When you learn something durable about this repo — a
|
||||
convention, a workflow gotcha, a design rationale — record it here (or in a repo doc this file points
|
||||
to, e.g. `home-switches.md`), not in agent memory. AGENTS.md is versioned and shared; memory is not.
|
||||
|
||||
Claude Code permissions live in two files: `.claude/settings.json` (versioned, shared — the
|
||||
committed allow list of safe-to-auto-approve commands) and `.claude/settings.local.json` (personal,
|
||||
gitignored — where interactive "always allow" grants accumulate). Put durable, generally-safe
|
||||
commands in the shared file; leave one-off or machine-specific grants in the local one.
|
||||
|
||||
## 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). Pass `--boot` to stage a config as the boot default **without** live-switching
|
||||
(`deploy --boot .#<host>`) — the box keeps running its current generation until it reboots. Use this
|
||||
when a live `switch` would break connectivity mid-change (e.g. a router's WAN VLAN rework), then
|
||||
reboot to cut over.
|
||||
- `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`.
|
||||
If outbound SSH hangs at the publickey step (flaky `ssh-agent`), disable the agent for the call:
|
||||
`SSH_AUTH_SOCK= ssh-machine …` (or add `-o IdentityAgent=none` to a raw `ssh`).
|
||||
- `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`.
|
||||
|
||||
For DNS lookups use **`drill`** (ldns) — `dig` isn't installed in this environment (it fails with
|
||||
exit 127, which is easy to miss if stderr is redirected). E.g. `drill -Q @<resolver> <name> A`.
|
||||
|
||||
For privilege escalation use **`doas`**, not `sudo` — the boxes don't install `sudo` (it fails with
|
||||
`command not found`). E.g. `doas ip link set <if> up`.
|
||||
|
||||
## 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`).
|
||||
|
||||
### Home switches (`jim` / `dave` / `brian`)
|
||||
The home boxes and the Digiweb WAN hang off hand-configured switches that are **not** managed by
|
||||
this flake: `jim` and `dave` (MikroTik, RouterOS) and `brian` (Ubiquiti, UniFi). The full topology,
|
||||
VLAN map, and the ONT/WAN path live in **`home-switches.md`** at the repo root — read it before
|
||||
touching anything WAN/VLAN-related, and update it when the switch layout changes.
|
||||
- **Access:** the switches resolve by **short hostname** on the home network (the routers serve
|
||||
their records in the home zone — `routing-common/dns.nix`: `jim`/`dave`/`brian`). From a home box,
|
||||
SSH to the MikroTiks as `admin`/`admin` (e.g. `ssh admin@jim`); `brian` is configured via the
|
||||
UniFi controller, not a CLI.
|
||||
- **Changing switch config is out-of-band and hard to revert — always confirm before applying:**
|
||||
print the affected menu, make the change, then re-verify. The nix config and the switches must
|
||||
agree on VLAN numbering (e.g. `lib.my.c.home.vlans`), so a switch-side change usually pairs with a
|
||||
box change; `home-switches.md` documents the switch layout and per-switch config for the WAN design.
|
||||
|
||||
### Home wireless APs (`vibe` / `wave`)
|
||||
The home Wi-Fi APs are also **not** managed by this flake: `vibe` (MikroTik cAP ax, RouterOS) and
|
||||
`wave` (Cudy AX3000 running OpenWrt/UCI). They are dumb APs — bridge clients onto the right VLAN,
|
||||
routers do DHCP/RA/firewall. The trunk/VLAN design, the OpenWrt flash + config for `wave`, and the
|
||||
per-AP management addressing live in **`home-aps.md`** at the repo root — read it before touching AP
|
||||
config, and update it when an AP changes. Only the DNS records live in the flake
|
||||
(`routing-common/dns.nix`).
|
||||
|
||||
## 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.
|
||||
- Comment where it genuinely aids understanding, but not for trivial/obvious code — match the file's
|
||||
existing (fairly sparse) comment density. When adding something general, comment its general
|
||||
purpose, not the specific change or one-off reason it was introduced for.
|
||||
- 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.
|
||||
+31
-2
@@ -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
|
||||
deploy "$O" $f
|
||||
if [ -n "''${O:-}" ]; then
|
||||
deploy "$O" $f
|
||||
else
|
||||
deploy $f
|
||||
fi
|
||||
done
|
||||
'';
|
||||
}
|
||||
|
||||
Generated
+448
-193
@@ -8,14 +8,14 @@
|
||||
"ragenix",
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": "systems_7"
|
||||
"systems": "systems_8"
|
||||
},
|
||||
"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,77 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"bun2nix": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": [
|
||||
"pi-agent",
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": [
|
||||
"pi-agent",
|
||||
"systems"
|
||||
],
|
||||
"treefmt-nix": "treefmt-nix_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778446047,
|
||||
"narHash": "sha256-oQvcadh2BCkrog+SGrG6YffKJrveYpjj3TdQJWaKhaM=",
|
||||
"owner": "nix-community",
|
||||
"repo": "bun2nix",
|
||||
"rev": "f2bc12af1a6369648aac41041ceeaa0b866599c6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "2.1.0",
|
||||
"repo": "bun2nix",
|
||||
"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 +155,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 +178,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 +212,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 +231,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 +255,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 +271,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": {
|
||||
@@ -222,6 +284,28 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": [
|
||||
"pi-agent",
|
||||
"bun2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777988971,
|
||||
"narHash": "sha256-qIoWPDs+0/8JecyYgE3gpKQxW/4bLW/gp45vow9ioCQ=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "0678d8986be1661af6bb555f3489f2fdfc31f6ff",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
@@ -244,6 +328,24 @@
|
||||
"inputs": {
|
||||
"systems": "systems_10"
|
||||
},
|
||||
"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_11"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705309234,
|
||||
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||
@@ -258,7 +360,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_11": {
|
||||
"flake-utils_12": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
@@ -292,24 +394,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 +408,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_5": {
|
||||
"flake-utils_4": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
@@ -339,7 +423,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,49 +492,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": {
|
||||
"inputs": {
|
||||
"systems": "systems_8"
|
||||
},
|
||||
"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_9": {
|
||||
"inputs": {
|
||||
"systems": "systems_9"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709126324,
|
||||
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -408,6 +510,50 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"harmonia": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"nix": "nix",
|
||||
"nixpkgs": [
|
||||
"nixpkgs-unstable"
|
||||
],
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781128165,
|
||||
"narHash": "sha256-97WpKZkaNAL5g7MtASLwqnrJrvrLpQRr6cXWiRNLiXQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "harmonia",
|
||||
"rev": "f0dd1094cdc8d72e038cf9347cacfa9272a8f72d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"home-manager": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -417,11 +563,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 +583,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 +603,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 +616,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 +636,66 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-github-actions": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"boardie",
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"jail-nix": {
|
||||
"locked": {
|
||||
"lastModified": 1703863825,
|
||||
"narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "5163432afc817cf8bd1f031418d1869e4c9d5547",
|
||||
"lastModified": 1776230864,
|
||||
"narHash": "sha256-YsEjjdOsGEzTeD+iT7ONh071BqWAOQWpzYVei3okAXE=",
|
||||
"owner": "~alexdavid",
|
||||
"repo": "jail.nix",
|
||||
"rev": "404e7da9da5ab9aa643666682b2ba1312fa5fbe8",
|
||||
"type": "sourcehut"
|
||||
},
|
||||
"original": {
|
||||
"owner": "~alexdavid",
|
||||
"repo": "jail.nix",
|
||||
"type": "sourcehut"
|
||||
}
|
||||
},
|
||||
"libnetRepo": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"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 +722,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 +738,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 +754,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 +783,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 +798,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 +844,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_5": {
|
||||
"nixpkgs_6": {
|
||||
"locked": {
|
||||
"lastModified": 1674990008,
|
||||
"narHash": "sha256-4zOyp+hFW2Y7imxIpZqZGT8CEqKmDjwgfD6BzRUE0mQ=",
|
||||
@@ -669,44 +860,66 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"pi-agent": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_3",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"systems": "systems_4",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
"bun2nix": "bun2nix",
|
||||
"jail-nix": "jail-nix",
|
||||
"nixpkgs": [
|
||||
"nixpkgs-unstable"
|
||||
],
|
||||
"systems": "systems_7"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1718726452,
|
||||
"narHash": "sha256-w4hJSYvACz0i5XHtxc6XNyHwbxpisN13M2kA2Y7937o=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "53e534a08c0cd2a9fa7587ed1c3e7f6aeb804a2c",
|
||||
"lastModified": 1784984457,
|
||||
"narHash": "sha256-y7jv+RTP0TkcJaDm7/D2xIFF9JDWKc6g5L3X3EUu3Eo=",
|
||||
"owner": "lukasl-dev",
|
||||
"repo": "pi.nix",
|
||||
"rev": "fd2c62853f66a5803dae621769570f1165e59b80",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"owner": "lukasl-dev",
|
||||
"repo": "pi.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pyproject-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"boardie",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1756395552,
|
||||
"narHash": "sha256-5aJM14MpoLk2cdZAetu60OkLQrtFLWTICAyn1EP7ZpM=",
|
||||
"owner": "pyproject-nix",
|
||||
"repo": "pyproject.nix",
|
||||
"rev": "030dffc235dcf240d918c651c78dc5f158067b51",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"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,17 +933,22 @@
|
||||
"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",
|
||||
"nixpkgs-stable": "nixpkgs-stable",
|
||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||
"pi-agent": "pi-agent",
|
||||
"ragenix": "ragenix",
|
||||
"sharry": "sharry"
|
||||
}
|
||||
@@ -743,11 +961,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 +976,8 @@
|
||||
},
|
||||
"sbt": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_11",
|
||||
"nixpkgs": "nixpkgs_5"
|
||||
"flake-utils": "flake-utils_12",
|
||||
"nixpkgs": "nixpkgs_6"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1698464090,
|
||||
@@ -778,18 +996,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": {
|
||||
@@ -828,6 +1046,21 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_11": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
@@ -868,8 +1101,9 @@
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "systems",
|
||||
"type": "indirect"
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_5": {
|
||||
@@ -950,17 +1184,38 @@
|
||||
"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": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"pi-agent",
|
||||
"bun2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775636079,
|
||||
"narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -971,14 +1226,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": {
|
||||
|
||||
@@ -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,27 @@
|
||||
|
||||
# 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";
|
||||
pi-agent.url = "github:lukasl-dev/pi.nix";
|
||||
pi-agent.inputs.nixpkgs.follows = "nixpkgs-unstable";
|
||||
};
|
||||
|
||||
outputs =
|
||||
@@ -57,7 +73,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 +104,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 +119,7 @@
|
||||
(_: path: mkDefaultSystemsPkgs path (_: {
|
||||
overlays = [
|
||||
pkgsLibOverlay
|
||||
|
||||
myPkgsOverlay
|
||||
];
|
||||
|
||||
@@ -126,6 +144,7 @@
|
||||
nixos/boxes/home/palace
|
||||
nixos/boxes/home/castle
|
||||
nixos/boxes/britway
|
||||
nixos/boxes/britnet.nix
|
||||
nixos/boxes/kelder
|
||||
|
||||
# Homes
|
||||
@@ -156,7 +175,7 @@
|
||||
# Platform independent stuff
|
||||
{
|
||||
nixpkgs = pkgs';
|
||||
inherit lib nixfiles;
|
||||
inherit inputs lib nixfiles;
|
||||
|
||||
overlays.default = myPkgsOverlay;
|
||||
|
||||
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
# Home wireless APs
|
||||
|
||||
Reference for the home Wi-Fi access points. Like the switches (`home-switches.md`), these are **not**
|
||||
managed by this flake — they are configured on-device (RouterOS on the MikroTik, OpenWrt/UCI on the
|
||||
Cudy). This file documents the shared VLAN/trunk design and each AP.
|
||||
|
||||
Only the DNS records live in the flake (`nixos/boxes/home/routing-common/dns.nix`, `h.nul.ie` zone).
|
||||
Everything else here is applied by hand on the device.
|
||||
|
||||
## The APs
|
||||
|
||||
| | vibe | wave |
|
||||
|---|---|---|
|
||||
| Model | MikroTik **cAP ax** (`cAPGi-5HaxD2HaxD`) | Cudy **AX3000** (OpenWrt id `cudy,ap3000-v1`) |
|
||||
| OS | RouterOS 7.x | OpenWrt 25.12.x (MT7981B / Filogic 820) |
|
||||
| Radio | 2×2 both bands | 2×2 both bands (2 spatial streams) |
|
||||
| Uplink | trunk (multi-port; `ether2` is a wired LAN port) | single **2.5 GbE** trunk |
|
||||
| Management | core/hi/lo `.15` | core/lo `.14` (**not** `hi`; see below) |
|
||||
|
||||
Both mirror the same two SSIDs. `wave` replaced an older AP of the same name; the new one is the
|
||||
Cudy running OpenWrt.
|
||||
|
||||
> **Note on "AX3000":** MT7981B is 2×2 (2 spatial streams). The "3000" is *aggregate* Mbps —
|
||||
> 574 (2.4 GHz 2ss) + 2402 (5 GHz 2ss @ **160 MHz**) — not three streams. `iw` confirms 2×2
|
||||
> (`Available Antennas TX/RX 0x3`; "3 streams: not supported"). 160 MHz is what earns the "3000".
|
||||
|
||||
## Shared design (dumb AP)
|
||||
|
||||
Every AP is a **dumb AP**: it bridges wireless clients onto the right VLAN and does **no** routing,
|
||||
DHCP, RA or firewalling. The home routers (`river`/`stream`) own DHCP/RA/gateway (per-VLAN VRRP VIPs)
|
||||
and firewalling. The uplink is a **tagged trunk**:
|
||||
|
||||
| VLAN | `lib.my.c.home.vlans` | Role | AP use |
|
||||
|---|---|---|---|
|
||||
| — (native) | `core` | switch/fabric management (1500) | backup management (untagged) |
|
||||
| 100 | `hi` | trusted LAN, **high-MTU** (jumbo 9000) | `vibe` management (it's jumbo-capable) |
|
||||
| 110 | `lo` | trusted LAN (1500) | main SSID `wlan0`; `wave` management |
|
||||
| 120 | `untrusted` | guest network | guest SSID `wlan1` |
|
||||
|
||||
`hi` and `lo` are both **trusted** client VLANs — the only difference is MTU (`hi` carries jumbo
|
||||
9000, `lo` is standard 1500). An AP puts its own management on whichever it can do: `vibe` (jumbo)
|
||||
sits on `hi`, `wave` (eth0 capped at 2026) sits on `lo`. The main SSID lands on `lo` because Wi-Fi
|
||||
clients are 1500 regardless.
|
||||
|
||||
### SSIDs
|
||||
|
||||
| SSID | Bands | Security | VLAN |
|
||||
|---|---|---|---|
|
||||
| `wlan0` (main) | 5 GHz + 2.4 GHz | WPA2/WPA3-PSK (`sae-mixed`) | 110 (`lo`) |
|
||||
| `wlan1` (guest) | 2.4 GHz | WPA2-PSK (`psk2`) | 120 (`untrusted`) |
|
||||
|
||||
**Passphrases are never stored in this repo.** `vibe` is the source of truth; read them out-of-band
|
||||
with `ssh admin@vibe '/interface wifi export show-sensitive'` (`.passphrase=` prints **unquoted**).
|
||||
|
||||
## vibe (MikroTik cAP ax)
|
||||
|
||||
RouterOS, one hardware-offloaded bridge `main` with `vlan-filtering=yes`. Access: `ssh admin@vibe`
|
||||
— key auth for `admin` is installed (`~/.ssh/id_rsa`), with `admin`/`admin` as a fallback.
|
||||
|
||||
- **Uplink `ether1`** — trunk, tagged VLANs 100/110/120; native/untagged is the default VLAN 1
|
||||
(PVID, no IP). `ether2` is a wired **access port** on VLAN 110 (`lo`). `l2mtu 9214`.
|
||||
- **Radios** — `wifi1` (5 GHz, 20/40/80) + `wifi2` (2.4 GHz, 20/40) both broadcast `wlan0`
|
||||
(WPA2/WPA3-PSK), untagged onto VLAN 110. `wifi3` is a virtual AP on `wifi2` broadcasting `wlan1`
|
||||
(WPA2-PSK), untagged onto VLAN 120. `country=Ireland`.
|
||||
- **Bridge VLANs** — 100 tagged `main,ether1`; 110 tagged `main,ether1` + untagged
|
||||
`ether2,wifi1,wifi2`; 120 tagged `main,ether1` + untagged `wifi3`.
|
||||
- **Management** — `jim`/`dave`-style (core/hi/lo), on host `.15`: `192.168.64.15` on core
|
||||
(native/untagged, backup), `192.168.68.15/22` + `2a0e:97c0:4d0:1::1:6` on the `hi` VLAN-100
|
||||
interface (holds the default route, via the hi VIP `192.168.71.254`), and
|
||||
`192.168.72.15/21` + `2a0e:97c0:4d0:2::1:6` on `lo` VLAN 110. No IP on `untrusted`.
|
||||
`l2mtu 9214`, so `hi` carries jumbo (9000) here — `vibe` sits on `hi` because it *can* jumbo,
|
||||
unlike `wave` (see its MTU note).
|
||||
- **Roaming** — 802.11k/v via a `/interface wifi steering` profile (`rrm=yes wnm=yes`,
|
||||
`neighbor-group=home-aps`) assigned to `wifi1`/`wifi2`/`wifi3`.
|
||||
- **Resolver** — the hi VIP `192.168.71.254` / `2a0e:97c0:4d0:1::ffff`.
|
||||
|
||||
## wave (Cudy AX3000, OpenWrt)
|
||||
|
||||
Single-port AP, so the port is a VLAN **trunk** carrying management + both SSIDs.
|
||||
|
||||
### Management addressing
|
||||
|
||||
`wave` takes host `.14`, on **`lo` (primary) and `core` (backup)** — deliberately **not** `hi`,
|
||||
unlike the switches and `vibe`. `hi` is the jumbo (9000) VLAN, but `wave`'s eth0 caps at 2026 (see
|
||||
MTU note), so there's no reason to put it there; `lo` is 1500 with a proper VRRP VIP for the default
|
||||
route + resolver, and `core` has no VIP/v6 so it can only be a backup. No IP on `untrusted`. Records
|
||||
in `dns.nix`:
|
||||
|
||||
| Name | VLAN | Address |
|
||||
|---|---|---|
|
||||
| `wave-core` | core (native/untagged) | `192.168.64.14/24` — backup, like the switches (no VIP → backup only) |
|
||||
| `wave` | lo 110 | `192.168.72.14/21`, `2a0e:97c0:4d0:2::1:5` — primary; holds the default route + resolver (lo VIP `192.168.79.254` / `2a0e:97c0:4d0:2::ffff`) |
|
||||
|
||||
**Firewall:** management (SSH/LuCI) reachable from `core`/`lo` only; `untrusted` is a separate
|
||||
zone with `input REJECT` (and `wave` has no IP there) — **no management via the guest VLAN**.
|
||||
|
||||
### brian switch port
|
||||
|
||||
`wave` hangs off **brian** (UniFi). Its port is a **trunk**: tagged VLAN **110/120** (`lo` + guest),
|
||||
and **native/untagged = core** (the fabric's management VLAN, carrying `wave-core`). VLAN 100 (`hi`)
|
||||
is **not** needed here — `wave` isn't on `hi` (see Management addressing). Configure via the UniFi
|
||||
controller (brian has no CLI); see `home-switches.md`.
|
||||
|
||||
### Flashing OpenWrt (Cudy AX3000 / `cudy_ap3000-v1`)
|
||||
|
||||
Hardware: MT7981B, 512 MB RAM, 256 MB SPI-NAND, 1× 2.5 GbE (RTL8221B), 2×2 WiFi 6.
|
||||
|
||||
> ⚠️ **Serial caveat:** units with a serial starting `2543…` (post ~Nov 2025) use a different flash
|
||||
> chip and can brick with older firmware. Match firmware to the unit.
|
||||
|
||||
OpenWrt can't be flashed directly over stock. Two-stage, via a Cudy **transition** firmware (Cudy
|
||||
OpenWrt download page / `support@cudy.com`; `warnning.txt` in that bundle has the steps):
|
||||
1. Stock Cudy UI: update to **≥ 2.4.7** (adds TFTP `recovery.bin` recovery), then flash the Cudy
|
||||
**intermediate** firmware (`cudy_ap3000-v1-sysupgrade_*.bin`), "keep settings" **unchecked**.
|
||||
It reboots into an OpenWrt-based build at `192.168.1.1` (SSH `root`, empty password).
|
||||
2. From there, `sysupgrade -n` to vanilla OpenWrt (`…-cudy_ap3000-v1-squashfs-sysupgrade.bin` from
|
||||
`downloads.openwrt.org`; this release ships **no** factory image — sysupgrade only).
|
||||
|
||||
Stock default (out of box) is a DHCP client falling back to **`192.168.10.254`**; the stock UI is a
|
||||
customised LuCI (only 80/443, no SSH) with a first-boot "create admin password" wizard — so the
|
||||
stock-side flashing is done from a browser, not headless.
|
||||
|
||||
### On-device config notes
|
||||
|
||||
- Package manager is **`apk`** (not `opkg`). WiFi runs **`wpad-mbedtls`** (full — swapped from the
|
||||
default `wpad-basic-mbedtls`, which lacks 802.11v). **802.11k + 802.11v** (`ieee80211k` +
|
||||
`bss_transition`) are enabled on all SSIDs. ⚠️ Swapping wpad **live** leaves the mac80211 vifs
|
||||
stuck in a start→teardown loop (`nl80211 ... No such device`); a `wifi reload`/`network restart`
|
||||
won't recover it — **reboot** after `apk add wpad-mbedtls`.
|
||||
- Radios: `radio0` = 2.4 GHz, `radio1` = 5 GHz (keyed by `band`, don't assume). 5 GHz is pinned to
|
||||
**channel 36 / HE160** (any 160 MHz block in IE is DFS; ch36 has the shortest ~60 s CAC).
|
||||
- Bridge: `br-lan` with `vlan_filtering`, single port `eth0` — tagged `110/120`, untagged/PVID
|
||||
VLAN 1 (= native/core). SSIDs attach via `network` = `lo`/`untrusted` (= `br-lan.110`/`.120`).
|
||||
- Dumb-AP: no DHCP pools, `odhcpd.maindhcp=0`, `delegate=0` on the L3 interfaces.
|
||||
- **MTU:** all interfaces are **1500**. The `mtk_eth_soc` 2.5 GbE (`eth0`) caps at **2026 bytes**
|
||||
(`ip link set eth0 mtu 9000` → `SIOCSIFMTU: Invalid argument`), so `wave` can't join `hi`'s jumbo
|
||||
(9000) fabric like `vibe` does — which is precisely **why `wave` is managed on `lo`, not `hi`**
|
||||
(see Management addressing). Nothing on `wave` needs > 1500.
|
||||
- **LuCI:** enabled, login `root` / `admin`. **SSH:** key-only (`PasswordAuth`/`RootPasswordAuth off`).
|
||||
- `iperf3` installed for throughput testing.
|
||||
|
||||
### Access
|
||||
|
||||
- SSH: `ssh root@wave` (key-only; `wave`/`wave-core` resolve once `dns.nix` is deployed).
|
||||
- LuCI: `http://192.168.72.14/` (or `http://wave/`), `root` / `admin`.
|
||||
@@ -8,6 +8,8 @@ let
|
||||
inherit (lib.my) mkOpt' dummyOption;
|
||||
in
|
||||
{
|
||||
imports = [ inputs.pi-agent.homeModules.default ];
|
||||
|
||||
options = with lib.types; {
|
||||
my = {
|
||||
isStandalone = mkOption {
|
||||
@@ -27,6 +29,7 @@ in
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkMerge [
|
||||
{
|
||||
my = {
|
||||
@@ -66,7 +69,7 @@ in
|
||||
|
||||
lsd = {
|
||||
enable = mkDefault true;
|
||||
enableAliases = mkDefault true;
|
||||
enableFishIntegration = mkDefault true;
|
||||
};
|
||||
|
||||
starship = {
|
||||
@@ -132,38 +135,38 @@ in
|
||||
|
||||
ssh = {
|
||||
enable = mkDefault true;
|
||||
matchBlocks = {
|
||||
enableDefaultConfig = false;
|
||||
settings = {
|
||||
nix-dev-vm = {
|
||||
user = "dev";
|
||||
hostname = "localhost";
|
||||
port = 2222;
|
||||
extraOptions = {
|
||||
StrictHostKeyChecking = "no";
|
||||
UserKnownHostsFile = "/dev/null";
|
||||
};
|
||||
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 = {
|
||||
@@ -213,6 +216,7 @@ in
|
||||
jq
|
||||
yq-go
|
||||
nix-tree
|
||||
treemd
|
||||
];
|
||||
|
||||
sessionVariables = {
|
||||
@@ -226,9 +230,10 @@ 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
|
||||
inputs.pi-agent.overlays.default
|
||||
];
|
||||
config = {
|
||||
allowUnfree = true;
|
||||
|
||||
+520
@@ -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
|
||||
@@ -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
|
||||
@@ -52,19 +71,33 @@ in
|
||||
|
||||
python3Packages.python-lsp-server
|
||||
nil # nix language server
|
||||
nixd # another nix language server
|
||||
zls # zig language server
|
||||
rust-analyzer
|
||||
pyright
|
||||
|
||||
cowsay
|
||||
fortune
|
||||
jp2a
|
||||
terminaltexteffects
|
||||
screenfetch
|
||||
neofetch
|
||||
fastfetch
|
||||
cmatrix
|
||||
doomsaver
|
||||
|
||||
ffmpeg-full
|
||||
xournalpp
|
||||
|
||||
(pkgs.symlinkJoin {
|
||||
name = "pi-coding-agent";
|
||||
buildInputs = [ pkgs.makeWrapper ];
|
||||
paths = [ pkgs.pi-coding-agent-bun ];
|
||||
postBuild = ''
|
||||
wrapProgram $out/bin/pi \
|
||||
--set NPM_CONFIG_PREFIX ${config.home.homeDirectory}/.pi/npm/ \
|
||||
--prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.nodejs_latest ]}
|
||||
'';
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
@@ -102,12 +135,6 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
termite = {
|
||||
enable = true;
|
||||
font = "${font.name} ${toString font.size}";
|
||||
backgroundColor = "rgba(0, 0, 0, 0.8)";
|
||||
};
|
||||
|
||||
foot = {
|
||||
enable = true;
|
||||
settings = {
|
||||
@@ -141,6 +168,19 @@ in
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
claude-code = {
|
||||
enable = true;
|
||||
settings = {
|
||||
model = "opus";
|
||||
theme = "auto";
|
||||
statusLine = {
|
||||
type = "command";
|
||||
command = ./claude-statusline.sh;
|
||||
padding = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -367,6 +407,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 +439,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 +542,7 @@ in
|
||||
userDirs = {
|
||||
enable = true;
|
||||
createDirectories = true;
|
||||
setSessionVariables = true;
|
||||
desktop = "$HOME/desktop";
|
||||
documents = "$HOME/documents";
|
||||
download = "$HOME/downloads";
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
# Home switches
|
||||
|
||||
Reference for the two MikroTik switches on the home network — **jim** and **dave** — plus the
|
||||
Ubiquiti switch **brian**, and how the home boxes and the Digiweb WAN hang off them. These switches
|
||||
are **not** managed by this flake; they are configured by hand (RouterOS on jim/dave, UniFi on
|
||||
brian). It covers the physical topology, the VLAN map, and how the Digiweb WAN reaches river.
|
||||
|
||||
In short: the Digiweb ISP VLAN (10) is trunked straight through to river (which runs PPPoE on it),
|
||||
and the ONT's untagged management is PVID'd onto VLAN 140 at brian, its edge switch. VLAN 10 is
|
||||
carried untranslated because a single ONT makes it unique on the fabric — see
|
||||
[the WAN path](#the-digiweb-wan-path-trunked-vlan-10--pvid-140) and
|
||||
[why not translation](#why-not-translation-for-one-ont).
|
||||
|
||||
## The switches
|
||||
|
||||
| | jim | dave | brian |
|
||||
|---|---|---|---|
|
||||
| Identity | `jim-sw` | `dave-sw` | (UniFi) |
|
||||
| Model | CRS326-24G-2S+ | CRS504-4XQ | Ubiquiti Switch Pro XG 8 PoE |
|
||||
| Switch chip | Marvell 98DX3236 | Marvell 98DX4310 (+ Atheros 8227 for the 1G mgmt port) | — |
|
||||
| OS | RouterOS 7.18 | RouterOS 7.18 | UniFi |
|
||||
| Ports | 24×1G + 2×SFP+ | 4×QSFP28 (100G, breakout-capable) + 1G mgmt | 8×10GBASE-T PoE + 2×25G SFP28 |
|
||||
| Bridge | `main`, `vlan-filtering=yes` | `main`, `vlan-filtering=yes` | UniFi VLAN profiles |
|
||||
|
||||
jim and dave run a single hardware-offloaded bridge (`main`) with VLAN filtering. Access to the
|
||||
MikroTiks is SSH as `admin` / `admin` by short hostname (see [Accessing the switches](#accessing-the-switches)).
|
||||
Only jim and dave can do hardware VLAN translation (`/interface ethernet switch rule` on the Marvell
|
||||
chips); brian cannot rewrite tags, only trunk/PVID them.
|
||||
|
||||
## Physical topology
|
||||
|
||||
The ONT terminates on brian; jim's `wan-pon-in` (`sfp-sfpplus2`) is a spare SFP+ port.
|
||||
|
||||
```
|
||||
Virgin Media cable modem
|
||||
│ VLAN 130 (wan1 / wan2 / wan-in)
|
||||
┌───────────────┴──────────────────────────────────────┐
|
||||
│ jim CRS326-24G-2S+ (Marvell 98DX3236) │
|
||||
│ 1G edge: fort, pronter, laptop-dock, palace-kvm, │
|
||||
│ ups, ether15-20, wan1/wan2/wan-in │
|
||||
│ wan-pon-in = sfp-sfpplus2 (spare SFP+) │
|
||||
└───────┬──────────────────────────────────────────────┘
|
||||
│ dave-uplink = sfp-sfpplus1 (10G trunk)
|
||||
│ also: palace, stream (1G secondaries),
|
||||
│ castle (2.5G, normally down)
|
||||
┌───────┴──────────────────────────────────────────────┐
|
||||
│ dave CRS504-4XQ (Marvell 98DX4310) │
|
||||
│ jim-downlink = qsfp28-3-1 │
|
||||
└──┬──────────────────┬───────────────────┬────────────┘
|
||||
│ palace │ castle │ brian-downlink
|
||||
│ = qsfp28-1-1 │ (100G) │ 802.3ad LAG
|
||||
│ (100G) │ │ (brian1 + brian2)
|
||||
│ … │
|
||||
┌───────┴────────┐ ┌───────┴─────────────┐
|
||||
│ palace host │ │ brian │
|
||||
│ └ river (VM) │ │ Switch Pro XG 8 PoE │
|
||||
└────────────────┘ └───────┬─────────────┘
|
||||
river WAN + LAN ride the 100G link │ hosts the ONT
|
||||
│
|
||||
┌───────┴─────────────┐
|
||||
│ ONT (Digiweb) │ untagged mgmt
|
||||
│ PPPoE via ONT │ 192.168.100.1
|
||||
└─────────────────────┘ + VLAN 10; PVID 140
|
||||
```
|
||||
|
||||
Notes:
|
||||
- **river** runs as a VM on the **palace** host; its uplink is dave's 100G `palace` port. jim also
|
||||
has 1G `palace`/`stream` ports, but those are secondary links and do **not** carry the WAN.
|
||||
- **stream** (the second router box) is dual-homed to both jim and dave (STP picks the active path).
|
||||
- **castle** is dual-homed but **not** via STP: its primary uplink is dave's **100G** `castle` port
|
||||
(`et100g`, active), and it has a secondary **2.5G** link to jim's `castle` edge port (`et2.5g`,
|
||||
**normally down** — no live failover). ⚠️ **castle's root disk is NVMe-oF over the fabric** (via
|
||||
`et100g`→dave), so rebooting **dave** — or downing castle's `et100g` — freezes castle mid-I/O.
|
||||
Do dave maintenance (upgrades/reboots) from a host that doesn't depend on dave for storage or
|
||||
network, or with castle cleanly powered off; don't drive it from castle.
|
||||
- **brian** is a Ubiquiti **Switch Pro XG 8 PoE** (8×10GBASE-T), downlinked from dave over an
|
||||
**802.3ad LAG** (`brian-downlink` = `brian1` + `brian2`, layer-2 hash). It hosts the ONT.
|
||||
|
||||
## VLANs
|
||||
|
||||
| VLAN | Name | Purpose |
|
||||
|---|---|---|
|
||||
| — (native) | core | Switch management, `192.168.64.0/24` (jim `.10`, dave `.11`, brian `.13`) |
|
||||
| 100 | hi | High-performance / jumbo network (MTU 9000) |
|
||||
| 110 | lo | Standard LAN |
|
||||
| 120 | untrusted | Guest / untrusted network |
|
||||
| 130 | wan | **stream's WAN** — Virgin Media cable modem (untagged on jim's `wan1`/`wan2`/`wan-in`) |
|
||||
| 140 | wan-pon-ont | ONT management, `192.168.100.0/24` (PVID'd at the ONT edge) |
|
||||
| 10 | pon-isp | Digiweb ISP transport — **trunked straight through** to river, PPPoE runs on it |
|
||||
| 141 | wan-pon-isp | **Reserved** — the translated ISP VLAN for the future multi-ONT design |
|
||||
|
||||
Switch L3 presence (`/interface vlan` on `main`) exists **only** for VLANs the switch is managed
|
||||
from — `hi` (100) and `lo` (110), plus native core. WAN and guest VLANs deliberately have no switch
|
||||
L3 interface.
|
||||
|
||||
## The Digiweb WAN path (trunked VLAN 10 + PVID 140)
|
||||
|
||||
The ONT presents two things on one wire:
|
||||
- **untagged** management traffic (`192.168.100.x`), and
|
||||
- **tagged VLAN 10** carrying the Digiweb ISP session (the BRAS requires VLAN 10).
|
||||
|
||||
With a **single ONT** there's no reason to translate anything — VLAN 10 is unique on the fabric, so
|
||||
we just carry it end to end and let river run PPPoE directly on it:
|
||||
|
||||
1. **Untagged mgmt → VLAN 140, at the ONT's edge switch (brian).** brian sets the ONT port's PVID to
|
||||
140 so the untagged management traffic becomes VLAN 140, and allows tagged VLAN 10 through the
|
||||
same port. river takes `192.168.100.100/24` on VLAN 140 (matching stream's modem-mgmt `.100`) to
|
||||
reach the ONT web UI at `192.168.100.1`. Doing the PVID at the ONT-facing edge keeps it clean —
|
||||
the untagged frames never share a domain with anything else.
|
||||
|
||||
2. **VLAN 10 (ISP) trunked straight through, untranslated.** brian → dave → palace carry tagged
|
||||
VLAN 10 by ordinary bridge-VLAN membership. No `/interface ethernet switch rule`, no pinning, no
|
||||
asymmetric-learning issues — it's just a normal tagged VLAN. river attaches PPPoE to VLAN 10
|
||||
directly (`wan-pon-isp` netdev = VLAN `pon-isp` = 10; baby-jumbo MTU 1508 so PPP nets a clean
|
||||
1500).
|
||||
|
||||
Net result: **river runs PPPoE single-tagged on VLAN 10 and holds a VLAN 140 address to reach the
|
||||
ONT.** See `nixos/boxes/home/palace/vms/river.nix` for the river side.
|
||||
|
||||
```
|
||||
ONT ──(untagged + VLAN10)── brian ──(VLAN140 + VLAN10)── dave ──(VLAN140 + VLAN10)── river
|
||||
ONT port │ PVID140 + tagged 10 │ plain bridging
|
||||
└─ brian-downlink LAG ── dave ┘
|
||||
```
|
||||
|
||||
### Why not translation (for one ONT)?
|
||||
|
||||
Translation would swap VLAN 10 → 141 with two pinned hardware ACL rules to keep VLAN 10 off the rest
|
||||
of the fabric. That buys nothing with a single ONT — VLAN 10 is already unique, so trunking it is
|
||||
simpler and rule-free. Translation only earns its keep when **two** ONTs both deliver VLAN 10 and
|
||||
would collide (below).
|
||||
|
||||
## Switch configuration
|
||||
|
||||
How each switch is set up for the Digiweb WAN path. **Confirm any change on the box before applying**
|
||||
(see [Accessing the switches](#accessing-the-switches)).
|
||||
|
||||
**brian (UniFi)** — hosts the ONT:
|
||||
- The ONT port has **native/untagged network = VLAN 140** (PVID) and is a **tagged member of VLAN 10**,
|
||||
so the ONT's untagged management lands on 140 and its tagged ISP frames pass through.
|
||||
- The `brian-downlink` LAG up to dave trunks **tagged 140 + tagged 10** (alongside the LAN VLANs).
|
||||
|
||||
**dave (RouterOS)** — trunks both WAN-pon VLANs to `brian-downlink` and `palace`. The ISP VLAN 10 row:
|
||||
```
|
||||
/interface bridge vlan add bridge=main vlan-ids=10 tagged=brian-downlink,palace
|
||||
```
|
||||
VLAN 140 also spans `brian-downlink,palace` (it carries a few other members too). No switch rules —
|
||||
this is plain tagged bridging.
|
||||
|
||||
**jim (RouterOS)** — carries **none** of the Digiweb WAN path: no translation rules, and no VLAN
|
||||
10/140/141 rows. `wan-pon-in` (`sfp-sfpplus2`) sits at `pvid=1` as a spare port. jim only handles
|
||||
stream's VLAN-130 WAN and the LAN VLANs.
|
||||
|
||||
## Future: multiple ONTs (per-port VLAN translation)
|
||||
|
||||
If a second ONT arrives (e.g. a Digiweb line for stream, or a second river), trunking breaks: both
|
||||
ONTs deliver **tagged VLAN 10**, and plain bridge-VLAN filtering can't tell them apart. That's when
|
||||
translation earns its place — a switch rule matches on the **ingress port**, so each ONT's VLAN 10
|
||||
becomes a *distinct* fabric VLAN:
|
||||
|
||||
- ONT-A port: VLAN 10 → **141** (→ river)
|
||||
- ONT-B port: VLAN 10 → **142** (→ stream / second river)
|
||||
- mgmt: PVID each ONT port onto its own VLAN (140, 143, …) so both ONTs' `192.168.100.1` stay in
|
||||
separate L2/L3 domains.
|
||||
|
||||
The forward direction isolates naturally (each ONT maps to a different fabric VLAN). The **return**
|
||||
direction is where port targeting is mandatory: both translate *back* to VLAN 10, so bridge VLAN 10
|
||||
now has two members and a plain FDB-miss flood would leak one ONT's upstream to the other. Each
|
||||
return must be pinned to its port with `new-dst-ports`:
|
||||
```
|
||||
# ONT-A: 141 in on palace → 10, forced out ONT-A's port
|
||||
# ONT-B: 142 in on stream → 10, forced out ONT-B's port
|
||||
```
|
||||
Each ONT port must also be a tagged member of bridge VLAN 10 for correct egress tagging (the missing
|
||||
piece that otherwise shows up as pppd "Timeout waiting for PADO"). The pins bypass the FDB, so the
|
||||
two ISP sessions never mix.
|
||||
|
||||
**Why a new switch:** jim (the only box with spare SFP+ *and* the translation feature) has just
|
||||
**one** free SFP+ port, so it can't host two ONTs. The plan is a dedicated
|
||||
**CRS305-1G-4S+** (4×SFP+, same Marvell rule support) to land multiple ONTs and do the per-port
|
||||
translation there, feeding distinct fabric VLANs up to dave.
|
||||
|
||||
## Accessing the switches
|
||||
|
||||
The switches resolve by **short hostname** on the home network — the home routers serve their
|
||||
records in the home zone (`nixos/boxes/home/routing-common/dns.nix`: `jim` → hi `.10`, `dave` → hi
|
||||
`.11`, `brian` → core `.13`). From a box on the home network just `ssh admin@jim` / `admin@dave`.
|
||||
|
||||
**Key auth** for `admin` is installed on jim/dave (and the `vibe` AP) — `ssh -i ~/.ssh/id_rsa
|
||||
admin@jim` works keyless (imported via `/user ssh-keys import`). Password `admin`/`admin` remains as
|
||||
a fallback. Non-interactive password pattern (avoids the ssh-agent hang) if the key isn't available:
|
||||
|
||||
```
|
||||
sshpass -p admin ssh -o IdentityAgent=none -o PubkeyAuthentication=no \
|
||||
-o PreferredAuthentications=password -o StrictHostKeyChecking=accept-new \
|
||||
-o UserKnownHostsFile=/tmp/sw_known_hosts admin@jim
|
||||
```
|
||||
|
||||
**Always confirm config changes on the switch** (print the affected menu, apply, re-verify). brian
|
||||
is UniFi — configured through its controller, not RouterOS CLI.
|
||||
|
||||
## Management IPs
|
||||
|
||||
| | core (`192.168.64.0/24`) | hi (`192.168.68.0/22`) | lo (`192.168.72.0/21`) |
|
||||
|---|---|---|---|
|
||||
| jim | `.10` (on `main`) | `.10` | `.10` |
|
||||
| dave | `.11` (on `management`, the 1G Atheros port) | `.11` | `.11` |
|
||||
| brian | `.13` (core) | — | — |
|
||||
+94
-6
@@ -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,16 @@ rec {
|
||||
lo = 110;
|
||||
untrusted = 120;
|
||||
wan = 130;
|
||||
|
||||
# Digiweb delivers the ISP VLAN (pon-isp, 10) single-tagged at the ONT alongside the ONT's
|
||||
# own untagged management traffic. With a single ONT we trunk pon-isp (10) straight through
|
||||
# the switches to river (PPPoE runs directly on it), and the switch at the ONT edge PVIDs the
|
||||
# untagged management port onto wan-pon-ont (140). wan-pon-isp (141) is reserved for the
|
||||
# future multi-ONT case, where per-port VLAN translation on a dedicated switch swaps each
|
||||
# ONT's VLAN 10 to a distinct fabric VLAN (see home-switches.md).
|
||||
pon-isp = 10;
|
||||
wan-pon-ont = 140;
|
||||
wan-pon-isp = 141;
|
||||
};
|
||||
hiMTU = 9000;
|
||||
routers = [
|
||||
@@ -267,14 +329,17 @@ rec {
|
||||
"stream"
|
||||
];
|
||||
routersPubV4 = [
|
||||
"109.255.31.155"
|
||||
"109.255.252.63"
|
||||
"84.203.124.128" # river: Digiweb static
|
||||
"109.255.108.121"
|
||||
];
|
||||
|
||||
prefixes = with lib.my.net.cidr; rec {
|
||||
modem = {
|
||||
v4 = "192.168.0.0/24";
|
||||
};
|
||||
ont = {
|
||||
v4 = "192.168.100.0/24";
|
||||
};
|
||||
all = {
|
||||
v4 = "192.168.64.0/18";
|
||||
v6 = "2a0e:97c0:4d0::/60";
|
||||
@@ -334,6 +399,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 +457,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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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}
|
||||
}
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
'';
|
||||
|
||||
@@ -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 = ''
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,45 +44,55 @@ in
|
||||
};
|
||||
|
||||
pdns-recursor = {
|
||||
dns = {
|
||||
address = [
|
||||
"127.0.0.1" "::1"
|
||||
assignments.base.ipv4.address assignments.base.ipv6.address
|
||||
];
|
||||
allowFrom = [
|
||||
"127.0.0.0/8" "::1/128"
|
||||
prefixes.all.v4 prefixes.all.v6
|
||||
] ++ (with lib.my.c.tailscale.prefix; [ v4 v6 ]);
|
||||
};
|
||||
|
||||
settings = {
|
||||
query-local-address = [
|
||||
assignments.internal.ipv4.address
|
||||
assignments.internal.ipv6.address
|
||||
assignments.base.ipv6.address
|
||||
];
|
||||
forward-zones = map (z: "${z}=127.0.0.1:5353") authZones;
|
||||
incoming = {
|
||||
listen = [
|
||||
"127.0.0.1" "::1"
|
||||
assignments.base.ipv4.address assignments.base.ipv6.address
|
||||
];
|
||||
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" ];
|
||||
# DNS NOTIFY messages override TTL
|
||||
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 = [
|
||||
assignments.internal.ipv4.address
|
||||
assignments.internal.ipv6.address
|
||||
assignments.base.ipv6.address
|
||||
];
|
||||
};
|
||||
|
||||
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}.")
|
||||
dq.rcode = 0
|
||||
dq.followupFunction = "followCNAMERecords"
|
||||
return true
|
||||
recursor = {
|
||||
forward_zones = map (z: {
|
||||
zone = z;
|
||||
forwarders = [ "127.0.0.1:5353" ];
|
||||
}) authZones;
|
||||
|
||||
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}.")
|
||||
dq.rcode = 0
|
||||
dq.followupFunction = "followCNAMERecords"
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
'';
|
||||
};
|
||||
|
||||
return false
|
||||
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@@
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,45 +271,49 @@ 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;
|
||||
};
|
||||
encryption = {
|
||||
allow = true;
|
||||
default = true;
|
||||
require = true;
|
||||
enabled = true;
|
||||
};
|
||||
permissions = {
|
||||
"@dev:nul.ie" = "admin";
|
||||
};
|
||||
};
|
||||
double_puppet = {
|
||||
secrets."nul.ie" = "$MAU_FBM_DOUBLE_PUPPET_TOKEN";
|
||||
};
|
||||
encryption = {
|
||||
allow = true;
|
||||
default = true;
|
||||
require = true;
|
||||
};
|
||||
matrix = {
|
||||
delivery_receipts = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -291,45 +323,50 @@ 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;
|
||||
};
|
||||
encryption = {
|
||||
allow = true;
|
||||
default = true;
|
||||
require = true;
|
||||
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;
|
||||
};
|
||||
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,8 +39,18 @@ 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; {
|
||||
@@ -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 = {
|
||||
enable = true;
|
||||
signKeyPaths = [ config.age.secrets."nix-cache.key".path ];
|
||||
settings = {
|
||||
priority = 30;
|
||||
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 {
|
||||
|
||||
@@ -217,6 +217,7 @@ in
|
||||
toot = {};
|
||||
waffletail = {};
|
||||
qclk = {};
|
||||
gam = {};
|
||||
};
|
||||
in
|
||||
mkMerge [
|
||||
|
||||
@@ -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 |
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -172,12 +172,23 @@
|
||||
};
|
||||
memory = 32768;
|
||||
cleanShutdown.timeout = 120;
|
||||
networks.netboot = {
|
||||
bridge = "lan-lo";
|
||||
waitOnline = "carrier";
|
||||
mac = "52:54:00:a5:7e:93";
|
||||
extraOptions.bootindex = 1;
|
||||
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"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,9 +8,23 @@
|
||||
|
||||
configuration = { lib, modulesPath, pkgs, config, assignments, allAssignments, ... }:
|
||||
let
|
||||
inherit (lib.my) networkdAssignment mkVLAN;
|
||||
inherit (builtins) elemAt;
|
||||
inherit (lib) mkForce mkMerge;
|
||||
inherit (lib.my) net networkdAssignment mkVLAN;
|
||||
inherit (lib.my.c) networkd;
|
||||
inherit (lib.my.c.home) vlans domain prefixes roceBootModules;
|
||||
inherit (lib.my.c.home) vlans domain prefixes roceBootModules routersPubV4;
|
||||
|
||||
# river reaches the ONT over its 100G `lan` uplink to the dave switch (which downlinks to
|
||||
# brian, where the ONT lands). Digiweb delivers the ISP VLAN (pon-isp, 10) single-tagged at
|
||||
# the ONT alongside the ONT's untagged management traffic. With a single ONT there's no VLAN
|
||||
# collision, so the switches simply trunk the ISP's VLAN 10 straight through to river (PPPoE
|
||||
# runs directly on it) and PVID the ONT's untagged management port onto wan-pon-ont (140).
|
||||
# river takes .100 in the ONT's /24 (matching stream's modem-mgmt .100) to reach its web
|
||||
# UI at 192.168.100.1. (See home-switches.md for the switch side and the multi-ONT plan.)
|
||||
ontV4 = net.cidr.host 100 prefixes.ont.v4;
|
||||
|
||||
# river is routing-common index 0; the Digiweb static IP we request via IPCP
|
||||
pubV4 = elemAt routersPubV4 0;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
@@ -71,11 +85,75 @@
|
||||
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-pon-isp
|
||||
name "digiweb@nga.digiweb.ie"
|
||||
password "digiweb"
|
||||
# request our static IP as the local address in IPCP (local:remote, remote left open)
|
||||
${pubV4}:
|
||||
# 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 wan-pon-isp (the switch's
|
||||
# swap of the ISP's 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 [
|
||||
(mkVLAN "wan-pon-ont" vlans.wan-pon-ont)
|
||||
# The ISP VLAN is trunked through untranslated, so this is the raw pon-isp (10)
|
||||
(mkVLAN "wan-pon-isp" 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 +161,7 @@
|
||||
PermanentMACAddress = "e0:d5:5e:68:0c:6e";
|
||||
};
|
||||
linkConfig = {
|
||||
Name = "wan";
|
||||
Name = "wan-old";
|
||||
RxBufferSize = 4096;
|
||||
TxBufferSize = 4096;
|
||||
};
|
||||
@@ -101,8 +179,38 @@
|
||||
};
|
||||
};
|
||||
|
||||
# So we don't drop the IP we use to connect to NVMe-oF!
|
||||
networks."60-lan-hi".networkConfig.KeepConfiguration = "static";
|
||||
networks = {
|
||||
"55-lan" = {
|
||||
# both WAN VLANs arrive single-tagged on the 100G uplink to dave: wan-pon-ont (140,
|
||||
# the ONT's management, PVID-tagged at the brian edge) and the ISP's VLAN 10, trunked
|
||||
# straight through
|
||||
vlan = [ "wan-pon-ont" "wan-pon-isp" ];
|
||||
};
|
||||
# So we don't drop the IP we use to connect to NVMe-oF!
|
||||
"60-lan-hi".networkConfig.KeepConfiguration = "static";
|
||||
|
||||
# ONT management: the brian edge PVIDs the ONT's untagged port onto wan-pon-ont, so give
|
||||
# ourselves an address in its /24 to reach the ONT's web UI at 192.168.100.1.
|
||||
"70-wan-pon-ont" = {
|
||||
matchConfig.Name = "wan-pon-ont";
|
||||
address = [ "${ontV4}/24" ];
|
||||
linkConfig = {
|
||||
RequiredForOnline = "no";
|
||||
MTUBytes = "1500";
|
||||
};
|
||||
};
|
||||
# pppd attaches PPPoE to this; just needs to be up with no L3. This is the ISP's
|
||||
# VLAN 10 trunked straight through from the ONT (no switch translation; see "55-lan").
|
||||
"71-wan-pon-isp" = {
|
||||
matchConfig.Name = "wan-pon-isp";
|
||||
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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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,9 +142,39 @@ 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 = {
|
||||
secrets = {
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAAaav5Se1E/AbqEXmADryVszYfNDscyP6jrWioN57R7";
|
||||
@@ -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 [
|
||||
|
||||
@@ -11,12 +11,6 @@ in
|
||||
{
|
||||
nixos.systems."${name}" = {
|
||||
assignments = {
|
||||
modem = {
|
||||
ipv4 = {
|
||||
address = net.cidr.host (254 - index) prefixes.modem.v4;
|
||||
gateway = null;
|
||||
};
|
||||
};
|
||||
core = {
|
||||
name = "${name}-core";
|
||||
inherit domain;
|
||||
@@ -100,9 +94,11 @@ in
|
||||
|
||||
configuration = { lib, pkgs, config, assignments, allAssignments, ... }:
|
||||
let
|
||||
inherit (lib) mkIf mkMerge mkForce;
|
||||
inherit (lib.my) networkdAssignment;
|
||||
inherit (lib) mkIf mkMerge mkForce optionalString concatStringsSep;
|
||||
inherit (lib.my) mkOpt' networkdAssignment;
|
||||
inherit (lib.my.c) networkd;
|
||||
|
||||
cfg = config.my.homeRouter;
|
||||
in
|
||||
{
|
||||
imports = map (m: import m index) [
|
||||
@@ -112,6 +108,20 @@ in
|
||||
./kea.nix
|
||||
];
|
||||
|
||||
# Per-box WAN-management specifics: the Virgin Media modem on stream lives on the `wan`
|
||||
# interface itself, whereas river's ONT sits on its own interface. Declared as options the
|
||||
# box sets so routing-common itself carries no modem/ONT knowledge.
|
||||
options.my.homeRouter = with lib.types; {
|
||||
dns.wanSkipBroadcasts = mkOpt' (listOf str) [ ] ''
|
||||
Broadcast addresses to exclude when auto-selecting the router's own `wan` A record,
|
||||
for extra static subnets that share the `wan` interface.
|
||||
'';
|
||||
firewall.untrustedRejectV4 = mkOpt' (listOf str) [ ] ''
|
||||
IPv4 prefixes untrusted clients must be explicitly rejected from reaching. Only needed
|
||||
for subnets sharing the `wan` interface, since `wan` egress is otherwise accepted.
|
||||
'';
|
||||
};
|
||||
|
||||
config = {
|
||||
environment = {
|
||||
systemPackages = with pkgs; [
|
||||
@@ -121,11 +131,9 @@ in
|
||||
};
|
||||
|
||||
services = {
|
||||
resolved = {
|
||||
llmnr = "false";
|
||||
extraConfig = ''
|
||||
MulticastDNS=false
|
||||
'';
|
||||
resolved.settings.Resolve = {
|
||||
LLMNR = false;
|
||||
MulticastDNS = false;
|
||||
};
|
||||
|
||||
iperf3 = {
|
||||
@@ -141,8 +149,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 +162,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 +238,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" ];
|
||||
@@ -367,7 +348,7 @@ in
|
||||
return
|
||||
}
|
||||
chain filter-untrusted {
|
||||
ip daddr ${prefixes.modem.v4} reject
|
||||
${optionalString (cfg.firewall.untrustedRejectV4 != [ ]) "ip daddr { ${concatStringsSep ", " cfg.firewall.untrustedRejectV4} } reject"}
|
||||
oifname wan accept
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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,52 +35,88 @@ 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 = [
|
||||
"127.0.0.1" "::1"
|
||||
assignments.hi.ipv4.address assignments.hi.ipv6.address
|
||||
assignments.lo.ipv4.address assignments.lo.ipv6.address
|
||||
];
|
||||
allowFrom = [
|
||||
"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;
|
||||
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
|
||||
];
|
||||
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 ]);
|
||||
|
||||
# DNS NOTIFY messages override TTL
|
||||
allow-notify-for = authZones;
|
||||
allow-notify-from = [ "127.0.0.0/8" "::1/128" ];
|
||||
# DNS NOTIFY messages override TTL
|
||||
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 = {
|
||||
# Query authoritative servers over IPv4 only. Our IPv6 default route runs over the
|
||||
# as211024 mesh (`ip -6 route show default`), a proto-static route that isn't
|
||||
# withdrawn when the mesh flaps (e.g. during ipsec churn) — it just blackholes. With
|
||||
# "::" here the recursor keeps picking IPv6 to reach NS, stalls on timeouts, and
|
||||
# takes recursion down with it. IPv4 upstream goes out the WAN directly and stays up;
|
||||
# we still serve AAAA records regardless of transport.
|
||||
source_address = [ "0.0.0.0" ];
|
||||
};
|
||||
|
||||
lua-dns-script = pkgs.writeText "pdns-script.lua" ''
|
||||
-- Disney+ doesn't like our IP space...
|
||||
function preresolve(dq)
|
||||
local name = dq.qname:toString()
|
||||
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
|
||||
recursor = {
|
||||
forward_zones = map (z: {
|
||||
zone = z;
|
||||
forwarders = [ "127.0.0.1:5353" ];
|
||||
}) authZones;
|
||||
|
||||
lua_dns_script = pkgs.writeText "pdns-script.lua" ''
|
||||
blocklist = newDS()
|
||||
|
||||
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
|
||||
|
||||
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" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -163,7 +206,7 @@ in
|
||||
${name} IN LUA ${lib.my.dns.ifaceA {
|
||||
inherit pkgs;
|
||||
iface = "wan";
|
||||
skipBroadcasts = [ (lib.my.netBroadcast prefixes.modem.v4) ];
|
||||
skipBroadcasts = config.my.homeRouter.dns.wanSkipBroadcasts;
|
||||
}}
|
||||
${otherName} IN LUA ${lib.my.dns.lookupIP {
|
||||
inherit pkgs;
|
||||
@@ -197,15 +240,23 @@ 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}
|
||||
vibe IN A ${net.cidr.host 13 prefixes.hi.v4}
|
||||
vibe IN AAAA ${net.cidr.host (65536+4) prefixes.hi.v6}
|
||||
vibe-core IN A ${net.cidr.host 15 prefixes.core.v4}
|
||||
vibe IN A ${net.cidr.host 15 prefixes.hi.v4}
|
||||
vibe IN AAAA ${net.cidr.host (65536+6) prefixes.hi.v6}
|
||||
vibe-lo IN A ${net.cidr.host 15 prefixes.lo.v4}
|
||||
vibe-lo IN AAAA ${net.cidr.host (65536+6) prefixes.lo.v6}
|
||||
wave-core IN A ${net.cidr.host 14 prefixes.core.v4}
|
||||
wave IN A ${net.cidr.host 14 prefixes.lo.v4}
|
||||
wave IN AAAA ${net.cidr.host (65536+5) prefixes.lo.v6}
|
||||
|
||||
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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
'';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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} {};
|
||||
};
|
||||
'';
|
||||
|
||||
@@ -8,7 +8,14 @@
|
||||
|
||||
configuration = { lib, pkgs, config, ... }:
|
||||
let
|
||||
inherit (lib);
|
||||
inherit (lib) mkMerge;
|
||||
inherit (lib.my) net;
|
||||
inherit (lib.my.c) networkd;
|
||||
inherit (lib.my.c.home) prefixes;
|
||||
|
||||
# Static address on the Virgin Media modem's management subnet. Kept as a plain interface
|
||||
# address (not a network assignment) since it's local to this box's WAN uplink.
|
||||
modemV4 = net.cidr.host 100 prefixes.modem.v4;
|
||||
in
|
||||
{
|
||||
imports = [ ./routing-common/mstpd.nix ];
|
||||
@@ -45,12 +52,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 +82,32 @@
|
||||
};
|
||||
};
|
||||
|
||||
# wan carries a permanent static modem-management address (modemV4)
|
||||
# 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 +180,43 @@
|
||||
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" = {
|
||||
matchConfig.Name = "wan";
|
||||
# Static modem-management address alongside the DHCP public lease. It has no
|
||||
# gateway, so the wan-wait-online gate keys off the DHCP default route instead.
|
||||
address = [ "${modemV4}/24" ];
|
||||
DHCP = "ipv4";
|
||||
dns = [ "127.0.0.1" "::1" ];
|
||||
dhcpV4Config.UseDNS = false;
|
||||
# IPv4-only WAN (public IPv6 arrives over the tunnel, not this link).
|
||||
networkConfig.IPv6AcceptRA = false;
|
||||
|
||||
qdiscConfig = {
|
||||
Parent = "ingress";
|
||||
Handle = "0xffff";
|
||||
};
|
||||
extraConfig = ''
|
||||
[CAKE]
|
||||
Parent=root
|
||||
Bandwidth=48M
|
||||
RTTSec=50ms
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -155,6 +225,12 @@
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPYTB4zeAqotrEJ8M+AiGm/s9PFsWlAodz3hYSROGuDb";
|
||||
};
|
||||
server.enable = true;
|
||||
# The modem's management subnet shares the `wan` interface: skip its address when
|
||||
# picking our own wan A record, and reject untrusted clients from reaching it.
|
||||
homeRouter = {
|
||||
dns.wanSkipBroadcasts = [ (lib.my.netBroadcast prefixes.modem.v4) ];
|
||||
firewall.untrustedRejectV4 = [ prefixes.modem.v4 ];
|
||||
};
|
||||
# deploy.node.hostname = "192.168.68.2";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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 = ''
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
|
||||
|
||||
@@ -221,8 +221,8 @@ in
|
||||
memorySize = dummyOption;
|
||||
qemu.options = dummyOption;
|
||||
};
|
||||
image.baseName = dummyOption;
|
||||
isoImage = {
|
||||
isoBaseName = dummyOption;
|
||||
volumeID = dummyOption;
|
||||
edition = dummyOption;
|
||||
appendToMenuLabel = dummyOption;
|
||||
|
||||
+27
-13
@@ -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,14 @@ 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
|
||||
inputs.pi-agent.overlays.default
|
||||
];
|
||||
config = {
|
||||
allowUnfree = true;
|
||||
@@ -135,6 +150,7 @@ in
|
||||
bash-completion
|
||||
git
|
||||
unzip
|
||||
tcpdump
|
||||
]
|
||||
(mkIf config.services.netdata.enable [ netdata ])
|
||||
];
|
||||
@@ -152,16 +168,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 +256,7 @@ in
|
||||
};
|
||||
}
|
||||
(mkIf config.services.kmscon.enable {
|
||||
fonts.fonts = with pkgs; [
|
||||
fonts.packages = with pkgs; [
|
||||
nerd-fonts.sauce-code-pro
|
||||
];
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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,23 +17,25 @@ let
|
||||
substituteAll ${./menu.ipxe} "$out"
|
||||
'';
|
||||
|
||||
bootBuilder = pkgs.substituteAll {
|
||||
bootBuilder = pkgs.replaceVarsWith {
|
||||
src = ./netboot-loader-builder.py;
|
||||
isExecutable = true;
|
||||
|
||||
inherit (pkgs) python3;
|
||||
bootspecTools = pkgs.bootspec;
|
||||
nix = config.nix.package.out;
|
||||
replacements = {
|
||||
inherit (pkgs) python3;
|
||||
bootspecTools = pkgs.bootspec;
|
||||
nix = config.nix.package.out;
|
||||
|
||||
inherit (config.system.nixos) distroName;
|
||||
systemName = config.system.name;
|
||||
inherit (cfg.client) configurationLimit;
|
||||
checkMountpoints = pkgs.writeShellScript "check-mountpoints" ''
|
||||
if ! ${pkgs.util-linuxMinimal}/bin/findmnt /boot > /dev/null; then
|
||||
echo "/boot is not a mounted partition. Is the path configured correctly?" >&2
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
inherit (config.system.nixos) distroName;
|
||||
systemName = config.system.name;
|
||||
inherit (cfg.client) configurationLimit;
|
||||
checkMountpoints = pkgs.writeShellScript "check-mountpoints" ''
|
||||
if ! ${pkgs.util-linuxMinimal}/bin/findmnt /boot > /dev/null; then
|
||||
echo "/boot is not a mounted partition. Is the path configured correctly?" >&2
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
|
||||
@@ -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, ®, 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, ®, FCN_XX_CORE_STAT_REG_MAC );
|
||||
+ }
|
||||
|
||||
has_phyxs = ( efab->phy_op->mmds & ( 1 << MDIO_MMD_PHYXS ) );
|
||||
if ( link_ok && has_phyxs ) {
|
||||
@@ -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 ];
|
||||
# Explicitly unset fallback DNS (Nix module will not allow for a blank config)
|
||||
extraConfig = ''
|
||||
FallbackDNS=
|
||||
Cache=no-negative
|
||||
'';
|
||||
settings.Resolve = {
|
||||
Domains = [ config.networking.domain ];
|
||||
# Explicitly unset fallback DNS (Nix module will not allow for a blank config)
|
||||
FallbackDNS = "";
|
||||
Cache = "no-negative";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
];
|
||||
})
|
||||
]))
|
||||
]);
|
||||
|
||||
|
||||
@@ -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") ++
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -11,5 +11,4 @@ in
|
||||
chocolate-doom2xx = callPackage ./chocolate-doom2xx { };
|
||||
windowtolayer = callPackage ./windowtolayer.nix { };
|
||||
swaylock-plugin = callPackage ./swaylock-plugin.nix { };
|
||||
terminaltexteffects = callPackage ./terminaltexteffects.nix { };
|
||||
}
|
||||
|
||||
@@ -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
@@ -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=";
|
||||
}
|
||||
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
|
||||
@@ -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-----
|
||||
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -1,12 +0,0 @@
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyA4bGQr
|
||||
dzJZbU4reWI1Sk45QTR3UkRoc0NvaDBFcUwrVWlCYWUrcWtYZGpNCm1PWjVzRVlt
|
||||
UDFKY2ZSZTg4S2pVZ0pDNzR1WklYQ3pEclJZLy9kKzdGTGsKLT4gWDI1NTE5IHFO
|
||||
TXVRK2d3azg2cHpRanhUNXp6YnRsZW1MUDJqc3l1bnNYNUhHaTIzMVkKWXp1M09H
|
||||
TnJIazRmb0tOSnE1Q0E1dERiaHZCQkh2YzE1cS9zRUhwaEovMAotPiBzPj5nLWdy
|
||||
ZWFzZQozSThRWnJCcVFFRHpoSi9tZnZMdnJoRlFud2VISHBHSThMem9qZVVWdS9C
|
||||
VFBDVEVzbUVCdFU2Qy9PaGdyc0FaCmk3UFZma2ZiR3hmWG1sa053bDBnY04yZ1VZ
|
||||
TW9jZwotLS0gMDVSaE5aakxHenFPVXpXa1JxczlWQ2x2VGNuQzdwaWZFTTFTaUp6
|
||||
cnRmZwoomylfwjD5A3N21/mk1Wtt8f4bsK747iZz7KT34kqmoX597rbGYxyip5lg
|
||||
VLZV6CY4LLRjnnSKoC2hIXU0dgudAmvxhztuaQ42fOc=
|
||||
-----END AGE ENCRYPTED 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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -0,0 +1,11 @@
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGhNYTRudyA4Qk8z
|
||||
SkhFWWMyaGM1c2luVzZzbWNvT0ZsS09yN0w1N01oY0tldWFXZnhVCkM0YzBVZUM1
|
||||
MmRzdFlCL1o5ZTdkTjkxQ1YyQ0kwbnJ0eVRuYUpQNWw2c0EKLT4gWDI1NTE5IGli
|
||||
dkp5RjZRSmROMEtQeWJiVkt2UktxdTAwUzZBRW9XajFDblFqZ3ZVR0EKYzFubnp0
|
||||
UHo5WlViYTNwbXFBd01qM0R4Wk82MTV1TzJsVVczdGRNSFRBQQotPiAnLWdyZWFz
|
||||
ZSAuPzVDCm1SZkdDMHRjT2NBVXI5ektKZ1R3dXhMUEVRblhBdC9mclFZSitSODI0
|
||||
OHZzZThEQnlBY25lVnFTQXRaV2FIYTIKeHkrY1NRCi0tLSBhUFgvd1BUbFlJeEFO
|
||||
RWRBQzcrT0ZFMG5SZVliNlZnK2N2VDJVV05UTkhBCrZM7RtMrOVIGIpod8aU4GLn
|
||||
0KBGTSq6kE01+f1kmTZDAKHx/LhiWgHYKLxTLW4VpYnUCg==
|
||||
-----END AGE ENCRYPTED 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-----
|
||||
|
||||
@@ -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
@@ -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-----
|
||||
|
||||
Reference in New Issue
Block a user