Compare commits

...

87 Commits

Author SHA1 Message Date
ab25c07f69 An attempt was made 2024-01-09 21:56:10 +00:00
aad8adf5da nixos/middleman: Add hack for working Gitea Docker image pull
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 26m52s
2024-01-09 18:50:11 +00:00
205a948486 nixos/middleman: Fix HedgeDoc websockets 2024-01-09 17:35:34 +00:00
39e7c703ba pkgs: Add modrinth-app
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 34m35s
2024-01-09 17:19:13 +00:00
d07ef96d28 envrc: Use watch_file instead of nix_direnv_watch_file 2024-01-09 13:33:00 +00:00
1a29a7d589 nixos/simpcraft: Staging server running
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 26m57s
2024-01-09 00:22:17 +00:00
ca6fe534dc nixos/git: Use separate nginx 2024-01-08 23:31:06 +00:00
e277cce3bc nixos/object: Add HedgeDoc
Some checks failed
CI / Check, build and cache Nix flake (push) Has been cancelled
2024-01-08 21:40:20 +00:00
c9ce57e2c5 nixos/middleman: Add public directory
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 24m53s
2024-01-07 03:40:33 +00:00
04dfc89f07 nixos/simpcraft: Add ToTheMoonStar to whitelist
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 24m53s
2024-01-06 14:59:57 +00:00
cfbbed8285 nixos/simpcraft: Upgrade to 0.1.1 2024-01-05 20:07:48 +00:00
066c87d3d6 nixos/simpcraft: Add mods
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 23m39s
2024-01-05 02:13:15 +00:00
e24ac05bb2 nixos/home/routing-common: Add vibe DNS
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 23m7s
2024-01-03 03:10:44 +00:00
a2b146e8ba nixos: Add librespeed
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 22m56s
2024-01-03 01:07:12 +00:00
a03fdbdbdd nixos/simpcraft: Add Eefah98 to whitelist
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 23m44s
2024-01-02 22:13:06 +00:00
5915f664cc nixos/simpcraft: Add hynge_ to whitelist 2024-01-02 20:49:08 +00:00
42111c530e nixos/colony: Reduce memory for mail VM
Some checks failed
CI / Check, build and cache Nix flake (push) Failing after 35m28s
2024-01-01 21:45:54 +00:00
a741e3eea2 nixos/whale2: Minecraft server updates
Some checks failed
CI / Check, build and cache Nix flake (push) Failing after 1h14m44s
2024-01-01 20:32:15 +00:00
7a4372dfe7 nixos/whale2: Add Minecraft server
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 25m30s
2024-01-01 16:28:04 +00:00
65917bad5c nixos/kelder: Disable minecraft-server 2024-01-01 16:26:45 +00:00
16c7fd7659 nixos/kelder: Update Minecraft and Nextcloud
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 23m55s
2023-12-31 20:21:02 +00:00
2fffefd22d Update river public IP
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 23m24s
2023-12-31 15:15:22 +00:00
c14aebf4a3 nixos/colony: Only start needed LVs in initrd to prevent race
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 23m2s
2023-12-30 21:07:12 +00:00
677f3f26ab home-manager/common: Use vi bindings for tmux 2023-12-29 21:04:07 +00:00
c55600c5af lib: Make awaitPostgres code early in preStart 2023-12-29 19:05:28 +00:00
64c3e2d720 nixos/colony: Give 8GiB more RAM to git
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 53m46s
Installer / Build installer (push) Successful in 4m43s
2023-12-28 18:54:43 +00:00
20d5fa29ae nixos/deploy: Minor improvements 2023-12-28 18:33:55 +00:00
046937de27 Update inputs and add custom NixOS branding
Some checks failed
CI / Check, build and cache Nix flake (push) Failing after 34m39s
2023-12-28 17:39:14 +00:00
bba87ef73b nixos/home/routing-common: Add trusted AS211024 to input chain
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 20m12s
2023-12-23 12:33:10 +00:00
4e3ff0a466 nixos/home/routing-common: Add dynamic DNS update script
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 20m48s
2023-12-23 01:22:41 +00:00
b1af3dbf18 nixos/tower: Add wireshark and Tailscale shell abbrev 2023-12-23 00:49:24 +00:00
f58b71e8d3 nixos/britway: Use internal addresses for DNS 2023-12-23 00:49:02 +00:00
0a86a649a6 nixos: Add SFH VM config
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 21m29s
2023-12-22 01:34:28 +00:00
85189e74f8 Add missing trusted AS211024 prefixes
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 20m12s
2023-12-20 23:43:04 +00:00
e760569b3e Don't blindly trust as211024
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 20m22s
2023-12-20 23:06:27 +00:00
0fe863844f nixos/estuary: Don't announce home prefix for now (MTU issues) 2023-12-20 22:51:01 +00:00
d44fdcfe6a nixos/home/routing-common: Restrict SSH access 2023-12-20 20:41:19 +00:00
b48e7b1c33 nixos: Initial waffletail
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 20m52s
2023-12-20 19:21:31 +00:00
d0b155d786 nixos/middleman: Wait for psql before starting nginx 2023-12-20 18:38:22 +00:00
01cb95de6d nixos/containers: Fix initial dummy deploy 2023-12-20 18:29:08 +00:00
46df9b8aa8 nixos/britway: Setup split DNS
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 19m27s
2023-12-20 16:43:20 +00:00
b2342c7a05 nixos/tower: Add tailscale
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 19m54s
2023-12-20 10:16:07 +00:00
a572be0708 nixos/britway: Use AS211024 source address for SNAT
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 19m26s
2023-12-20 01:44:24 +00:00
68bf705e85 Intra-AS211024 routing
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 19m5s
2023-12-20 01:30:27 +00:00
21136e98b2 nixos/britway: Add tailscale
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 19m49s
2023-12-20 00:21:39 +00:00
b537524e5a nixos/britway: Add headscale 2023-12-19 23:40:54 +00:00
10769a4441 nixos/britway: Export route to Dublin
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 19m2s
2023-12-19 16:59:45 +00:00
9b05e7cd8d nixos/britway: Add BGP
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 19m10s
2023-12-19 15:51:16 +00:00
eda0cdbe0e nixos: Add initial britway
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 19m34s
2023-12-19 14:30:27 +00:00
f321a039f3 nixos/home/routing-common: Send MTU DHCP option
Some checks reported warnings
CI / Check, build and cache Nix flake (push) Has been cancelled
Installer / Build installer (push) Successful in 4m36s
2023-12-17 15:19:14 +00:00
fc6c4f461f Add netboot archive (including to installer workflow)
Some checks reported warnings
CI / Check, build and cache Nix flake (push) Has been cancelled
Installer / Build installer (push) Successful in 4m31s
2023-12-17 14:56:39 +00:00
22bf75d0a0 deploy-rs: Default to skipping checks and disabling auto / magic rollback 2023-12-17 13:40:25 +00:00
318972a086 nixos/home/routing-common: Split DHCP pools 2023-12-17 13:39:55 +00:00
9fa8299b71 nixos/home/routing-common: Add missing radvd search domain
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 17m45s
2023-12-16 19:33:53 +00:00
490413c24b nixos/routing-common: Working DHCP
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 18m48s
2023-12-16 18:50:51 +00:00
9cec5051bf nixos/estuary: Remvoe efero upstream 2023-12-16 16:53:55 +00:00
70f49c8438 nixos/home/routing-common: Working IPv6 router 2023-12-16 15:59:33 +00:00
8b0db3ac7f nixos/home/routing-common: Add route to other router's public IPv4 2023-12-16 13:00:10 +00:00
cc07964fac nixos/palace: Add BindsTo= dependency for river on cellar 2023-12-16 12:54:10 +00:00
4624480c8b home-manager/common: Fix Nix cache config for users
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 18m32s
2023-12-13 22:57:54 +00:00
3bc8befb7d nixos/cellar: Enable SPDK dynamic scheduler
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 17m37s
2023-12-13 21:53:10 +00:00
1b853d405c nixos/palace: Set up virtual IOMMU for cellar
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 17m49s
2023-12-13 11:23:47 +00:00
82b24c3c55 nixos/cellar: Move SPDK config to separate module
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 17m56s
2023-12-13 02:19:04 +00:00
2f2764a364 Add json2nix util 2023-12-13 00:40:36 +00:00
4b48d7e788 nixos/nvme: Add module
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 17m30s
2023-12-12 01:37:14 +00:00
5686aa1a01 nixos/shill: Replicate port forwards for internal routing
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 17m23s
2023-12-11 16:53:09 +00:00
20a3873d25 nixos/colony: Replicate port forwards for internal routing 2023-12-11 16:51:43 +00:00
d9d7a714cd nixos/firewall: Add ability to forward per external IP 2023-12-11 14:59:40 +00:00
93892224b7 nixos/home/routing-common: Allow VRRP traffic
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 17m34s
2023-12-11 02:31:26 +00:00
5e5f70501c nixos/river: Initial NVMe-oF booting river :)
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 17m32s
2023-12-11 01:55:02 +00:00
33eded0626 nixos/cellar: Working NVMe-oF
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 16m40s
2023-12-10 02:29:53 +00:00
9268256309 nixos/vms: Extract vfio-pci-bind to separate package 2023-12-09 21:17:51 +00:00
027cf2af6b nixos/git: Fix container network access
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 30m54s
2023-12-09 17:22:17 +00:00
54f628d3a5 nixos/git: Fix for local access to git.nul.ie
Some checks failed
CI / Check, build and cache Nix flake (push) Failing after 1m43s
2023-12-09 16:55:21 +00:00
56704821b8 nixos/palace: Enable AER
Some checks failed
CI / Check, build and cache Nix flake (push) Failing after 1m2s
2023-12-09 15:22:49 +00:00
ca3547b27a nixos/user: Use impermanence's users options 2023-12-09 15:22:15 +00:00
88b6e00f93 nixos: Add Gitea VM 2023-12-09 15:22:01 +00:00
b4d0d9aff9 nixos/castle: Add libvirt
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 17m33s
Installer ISO / Build installer ISO (push) Successful in 3m11s
2023-12-05 23:27:16 +00:00
4ee66cdca7 nixos/build: Disable systemd initrd in ISO
Some checks reported warnings
CI / Check, build and cache Nix flake (push) Has been cancelled
2023-12-05 23:26:51 +00:00
1b72739000 devshell: Add update-installer command
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 16m49s
2023-12-05 16:58:20 +00:00
c28acb339b Installer workflow: Create ISO instead of build artifact
Some checks reported warnings
CI / Check, build and cache Nix flake (push) Has been cancelled
Installer ISO / Build installer ISO (push) Successful in 3m13s
2023-12-05 15:36:08 +00:00
3d7a1b8e6c Add workflow to build installer ISO
Some checks reported warnings
CI / Check, build and cache Nix flake (push) Has been cancelled
Installer ISO / Build installer ISO (push) Successful in 6m14s
2023-12-05 14:22:03 +00:00
373fb45831 nixos: Switch to systemd initrd
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 17m17s
2023-12-04 23:00:25 +00:00
c4e6896a0c nixos: Add initial palace
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 16m35s
2023-12-03 22:58:28 +00:00
a1778e0f1e Upgrade nixpkgs and NixOS stable to 23.11
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 26m58s
2023-12-03 15:06:11 +00:00
0cc35547f2 nixos: Working l2mesh with IPsec
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 17m15s
2023-11-26 01:29:44 +00:00
7404779c6d nixos/home/routing-common: Add initial VRRP 2023-11-25 17:18:34 +00:00
132 changed files with 11611 additions and 1475 deletions

2
.envrc
View File

@@ -1,2 +1,2 @@
nix_direnv_watch_file devshell/{default,commands,install,vm-tasks}.nix
watch_file devshell/{default,commands,install,vm-tasks}.nix
use flake

View File

@@ -0,0 +1,50 @@
name: Installer
on:
push:
tags: [installer]
jobs:
installer:
name: Build installer
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: https://github.com/actions/setup-go@v4
with:
go-version: '>=1.20.1'
- uses: cachix/install-nix-action@v23
with:
github_access_token: ${{ secrets.GH_PULL_TOKEN }}
extra_nix_config: |
# Make sure we're using sandbox
sandbox-fallback = false
extra-substituters = https://nix-cache.nul.ie/main
extra-trusted-public-keys = main:mMChkG8LwXrFirVfudqjSHasK1jV31OVElYD3eImYl8=
- name: Set up attic
id: setup
run: |
nix run .#nixpkgs.mine.x86_64-linux.attic-client -- \
login --set-default colony https://nix-cache.nul.ie "${{ secrets.NIX_CACHE_TOKEN }}"
echo "short_rev=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
- name: Build installer ISO
run: |
nix build .#nixfiles.config.nixos.systems.installer.configuration.config.my.buildAs.iso
ln -s "$(readlink result)"/iso/jackos-installer.iso \
jackos-installer-${{ steps.setup.outputs.short_rev }}.iso
- name: Build installer netboot archive
run: |
nix build .#nixfiles.config.nixos.systems.installer.configuration.config.my.buildAs.netbootArchive
ln -s "$(readlink result)" \
jackos-installer-netboot-${{ steps.setup.outputs.short_rev }}.tar
- name: Create release
uses: https://gitea.com/actions/release-action@main
with:
title: Latest installer
files: |
jackos-installer-${{ steps.setup.outputs.short_rev }}.iso
jackos-installer-netboot-${{ steps.setup.outputs.short_rev }}.tar

View File

@@ -47,8 +47,8 @@ in
(nodesFor homes)
);
autoRollback = true;
magicRollback = true;
autoRollback = false;
magicRollback = false;
};
# Filter out null values so deploy merges overriding options correctly

View File

@@ -48,6 +48,12 @@ 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 = "json2nix";
category = "utilities";
help = "Convert JSON to formatted Nix";
command = "nix eval --impure --expr 'builtins.fromJSON (builtins.readFile /dev/stdin)' | ${pkgs.nixfmt}/bin/nixfmt";
}
{
name = "fmt";
@@ -91,6 +97,18 @@ in
help = "Build NixOS configuration into an ISO";
command = ''nix build "''${@:2}" ".#nixfiles.config.nixos.systems.\"$1\".configuration.config.my.buildAs.iso"'';
}
{
name = "build-kexec";
category = "tasks";
help = "Build NixOS configuration as kexec tree";
command = ''nix build "''${@:2}" ".#nixfiles.config.nixos.systems.\"$1\".configuration.config.my.buildAs.kexecTree"'';
}
{
name = "build-netboot";
category = "tasks";
help = "Build NixOS configuration as netboot archive";
command = ''nix build "''${@:2}" ".#nixfiles.config.nixos.systems.\"$1\".configuration.config.my.buildAs.netbootArchive"'';
}
{
name = "build-home";
category = "tasks";
@@ -121,5 +139,11 @@ in
help = "Update home-manager flake inputs";
command = ''update-inputs home-manager-{unstable,stable}'';
}
{
name = "update-installer";
category = "tasks";
help = "Update installer tag (to trigger new release)";
command = ''git tag -f installer && git push -f origin installer'';
}
];
}

View File

@@ -24,7 +24,9 @@ in
coreutils
nixVersions.stable
rage
deploy-rs.deploy-rs
(pkgs.writeShellScriptBin "deploy" ''
exec ${deploy-rs.deploy-rs}/bin/deploy --skip-checks "$@"
'')
home-manager
attic-client
];

158
flake.lock generated
View File

@@ -35,11 +35,11 @@
]
},
"locked": {
"lastModified": 1698258239,
"narHash": "sha256-qnhoYYIJ0L/P7H/f56lQUEvpzNlXh4sxuHpRERV+B44=",
"lastModified": 1702969472,
"narHash": "sha256-IJP9sC+/gLUdWhm6TsnWpw6A1zQWUfn53ym63KeLXvU=",
"owner": "zhaofengli",
"repo": "attic",
"rev": "e9918bc6be268da6fa97af6ced15193d8a0421c0",
"rev": "bdafd64910bb2b861cf90fa15f1fc93318b6fbf6",
"type": "github"
},
"original": {
@@ -95,26 +95,17 @@
},
"crane": {
"inputs": {
"flake-compat": [
"attic",
"flake-compat"
],
"flake-utils": [
"attic",
"flake-utils"
],
"nixpkgs": [
"attic",
"nixpkgs"
],
"rust-overlay": "rust-overlay"
]
},
"locked": {
"lastModified": 1677892403,
"narHash": "sha256-/Wi0L1spSWLFj+UQxN3j0mPYMoc7ZoAujpUF/juFVII=",
"lastModified": 1702918879,
"narHash": "sha256-tWJqzajIvYcaRWxn+cLUB9L9Pv4dQ3Bfit/YjU5ze3g=",
"owner": "ipetkov",
"repo": "crane",
"rev": "105e27adb70a9890986b6d543a67761cbc1964a2",
"rev": "7195c00c272fdd92fc74e7d5a0a2844b9fadb2fb",
"type": "github"
},
"original": {
@@ -185,11 +176,11 @@
"utils": "utils"
},
"locked": {
"lastModified": 1695052866,
"narHash": "sha256-agn7F9Oww4oU6nPiw+YiYI9Xb4vOOE73w8PAoBRP4AA=",
"lastModified": 1703087360,
"narHash": "sha256-0VUbWBW8VyiDRuimMuLsEO4elGuUw/nc2WDeuO1eN1M=",
"owner": "serokell",
"repo": "deploy-rs",
"rev": "e3f41832680801d0ee9e2ed33eb63af398b090e9",
"rev": "b709d63debafce9f5645a5ba550c9e0983b3d1f7",
"type": "github"
},
"original": {
@@ -241,14 +232,14 @@
"nixpkgs": [
"nixpkgs-unstable"
],
"systems": "systems_3"
"systems": "systems_4"
},
"locked": {
"lastModified": 1698410321,
"narHash": "sha256-MphuSlgpmKwtJncGMohryHiK55J1n6WzVQ/OAfmfoMc=",
"lastModified": 1701787589,
"narHash": "sha256-ce+oQR4Zq9VOsLoh9bZT8Ip9PaMLcjjBUHVPzW5d7Cw=",
"owner": "numtide",
"repo": "devshell",
"rev": "1aed986e3c81a4f6698e85a7452cbfcc4b31a36e",
"rev": "44ddedcbcfc2d52a76b64fb6122f209881bd3e1e",
"type": "github"
},
"original": {
@@ -276,11 +267,11 @@
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1668681692,
"narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "009399224d5e398d03b22badca40a37ac85412a1",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
@@ -418,14 +409,14 @@
},
"flake-utils_7": {
"inputs": {
"systems": "systems_4"
"systems": "systems_5"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
@@ -451,7 +442,7 @@
},
"flake-utils_9": {
"inputs": {
"systems": "systems_5"
"systems": "systems_6"
},
"locked": {
"lastModified": 1681202837,
@@ -474,16 +465,16 @@
]
},
"locked": {
"lastModified": 1695108154,
"narHash": "sha256-gSg7UTVtls2yO9lKtP0yb66XBHT1Fx5qZSZbGMpSn2c=",
"lastModified": 1703367386,
"narHash": "sha256-FMbm48UGrBfOWGt8+opuS+uLBLQlRfhiYXhHNcYMS5k=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "07682fff75d41f18327a871088d20af2710d4744",
"rev": "d5824a76bc6bb93d1dce9ebbbcb09a9b6abcc224",
"type": "github"
},
"original": {
"id": "home-manager",
"ref": "release-23.05",
"ref": "release-23.11",
"type": "indirect"
}
},
@@ -494,11 +485,11 @@
]
},
"locked": {
"lastModified": 1698670511,
"narHash": "sha256-jQIu3UhBMPHXzVkHQO1O2gg8SVo5lqAVoC6mOaLQcLQ=",
"lastModified": 1703754036,
"narHash": "sha256-JpJdcj9Tg4lMuYikXDpajA8wOp+rHyn9RD2rKBEM4cQ=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "8e5416b478e465985eec274bc3a018024435c106",
"rev": "c24c298562fe41b39909f632c5a7151bbf6b4628",
"type": "github"
},
"original": {
@@ -508,11 +499,11 @@
},
"impermanence": {
"locked": {
"lastModified": 1697303681,
"narHash": "sha256-caJ0rXeagaih+xTgRduYtYKL1rZ9ylh06CIrt1w5B4g=",
"lastModified": 1703656108,
"narHash": "sha256-hCSUqdFJKHHbER8Cenf5JRzjMlBjIdwdftGQsO0xoJs=",
"owner": "nix-community",
"repo": "impermanence",
"rev": "0f317c2e9e56550ce12323eb39302d251618f5b5",
"rev": "033643a45a4a920660ef91caa391fbffb14da466",
"type": "github"
},
"original": {
@@ -531,13 +522,13 @@
"locked": {
"lastModified": 1685908677,
"narHash": "sha256-E4zUPEUFyVWjVm45zICaHRpfGepfkE9Z2OECV9HXfA4=",
"owner": "guibou",
"owner": "nix-community",
"repo": "nixGL",
"rev": "489d6b095ab9d289fe11af0219a9ff00fe87c7c5",
"type": "github"
},
"original": {
"owner": "guibou",
"owner": "nix-community",
"repo": "nixGL",
"type": "github"
}
@@ -560,11 +551,11 @@
},
"nixpkgs-mine": {
"locked": {
"lastModified": 1700347575,
"narHash": "sha256-wHdY7YFRepLNtPRh7gBP8EDJRbqC/hwYWupxTof7PQ8=",
"lastModified": 1703756459,
"narHash": "sha256-ztEMyPQZh3Pb+LOoWl5lbIK2LenP59sOUBC86CDmLio=",
"owner": "devplayer0",
"repo": "nixpkgs",
"rev": "72cc1ce8a7e476a724de861bbd066a1cb700e39b",
"rev": "e80160eb2ac3a7111d07cc43a15c16b9edca01ea",
"type": "github"
},
"original": {
@@ -576,11 +567,11 @@
},
"nixpkgs-mine-stable": {
"locked": {
"lastModified": 1700347610,
"narHash": "sha256-NLRu2yPRc6BRIIcI0KG9csLGiAhmZG2JXLrJI+gLJQk=",
"lastModified": 1703756491,
"narHash": "sha256-9VL34e0gzomwqRnryRn23V2ImYcaZIQdp7CsWg5TmlE=",
"owner": "devplayer0",
"repo": "nixpkgs",
"rev": "8b2769b59113858ecf4cf24ddae9ab1b8dd7920d",
"rev": "36611f5f7cfd401f51ad4ca76fd6ee85a714bb74",
"type": "github"
},
"original": {
@@ -592,26 +583,26 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1698562188,
"narHash": "sha256-9nkxGnA/T+jLhHAMFRW157Qi/zfbf5dF1q7HfKROl3o=",
"lastModified": 1703467016,
"narHash": "sha256-/5A/dNPhbQx/Oa2d+Get174eNI3LERQ7u6WTWOlR1eQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3e10c80821dedb93592682379f476745f370a58e",
"rev": "d02d818f22c777aa4e854efc3242ec451e5d462a",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-23.05",
"ref": "nixos-23.11",
"type": "indirect"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1698611440,
"narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=",
"lastModified": 1703438236,
"narHash": "sha256-aqVBq1u09yFhL7bj1/xyUeJjzr92fXVvQSSEx6AdB1M=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735",
"rev": "5f64a12a728902226210bf01d25ec6cbb9d9265b",
"type": "github"
},
"original": {
@@ -678,7 +669,7 @@
"nixpkgs": [
"nixpkgs-unstable"
],
"rust-overlay": "rust-overlay_2"
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1682237245,
@@ -715,33 +706,6 @@
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"attic",
"crane",
"flake-utils"
],
"nixpkgs": [
"attic",
"crane",
"nixpkgs"
]
},
"locked": {
"lastModified": 1675391458,
"narHash": "sha256-ukDKZw922BnK5ohL9LhwtaDAdCsJL7L6ScNEyF1lO9w=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "383a4acfd11d778d5c2efcf28376cbd845eeaedf",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"rust-overlay_2": {
"inputs": {
"flake-utils": [
"ragenix",
@@ -862,13 +826,31 @@
"type": "github"
}
},
"utils": {
"systems_6": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems_3"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {

View File

@@ -7,13 +7,13 @@
devshell.inputs.nixpkgs.follows = "nixpkgs-unstable";
nixpkgs-unstable.url = "nixpkgs/nixos-unstable";
nixpkgs-stable.url = "nixpkgs/nixos-23.05";
nixpkgs-stable.url = "nixpkgs/nixos-23.11";
nixpkgs-mine.url = "github:devplayer0/nixpkgs/devplayer0";
nixpkgs-mine-stable.url = "github:devplayer0/nixpkgs/devplayer0-stable";
home-manager-unstable.url = "home-manager";
home-manager-unstable.inputs.nixpkgs.follows = "nixpkgs-unstable";
home-manager-stable.url = "home-manager/release-23.05";
home-manager-stable.url = "home-manager/release-23.11";
home-manager-stable.inputs.nixpkgs.follows = "nixpkgs-stable";
# Stuff used by the flake for build / deployment
@@ -26,7 +26,7 @@
impermanence.url = "github:nix-community/impermanence";
boardie.url = "github:devplayer0/boardie";
boardie.inputs.nixpkgs.follows = "nixpkgs-unstable";
nixGL.url = "github:guibou/nixGL";
nixGL.url = "github:nix-community/nixGL";
nixGL.inputs.nixpkgs.follows = "nixpkgs-unstable";
# Packages not in nixpkgs
@@ -63,7 +63,7 @@
flake = flake-utils.lib;
};
pkgsLibOverlay = final: prev: { lib = prev.lib.extend libOverlay; };
myPkgsOverlay = final: prev: import ./pkgs { lib = prev.lib; pkgs = prev; };
myPkgsOverlay = final: prev: import ./pkgs { lib = final.lib; pkgs = prev; };
# Override the flake-level lib since we're going to use it for non-config specific stuff
pkgsFlakes = mapAttrs (_: pkgsFlake: pkgsFlake // { lib = pkgsFlake.lib.extend libOverlay; }) {
@@ -117,6 +117,8 @@
nixos/boxes/tower
nixos/boxes/castle
nixos/boxes/home/stream.nix
nixos/boxes/home/palace
nixos/boxes/britway
nixos/boxes/kelder
# Homes
@@ -127,7 +129,7 @@
modules = [
{
_module.args = {
inherit lib pkgsFlakes hmFlakes inputs;
inherit lib pkgsFlakes hmFlakes self inputs;
pkgs' = configPkgs';
};

View File

@@ -47,9 +47,14 @@ in
nix = {
package = mkIf (!(versionAtLeast config.home.stateVersion "22.11")) pkgs.nix;
settings = {
settings = with lib.my.c.nix; {
experimental-features = [ "nix-command" "flakes" "ca-derivations" ];
max-jobs = mkDefault "auto";
extra-substituters = cache.substituters;
extra-trusted-public-keys = cache.keys;
connect-timeout = 5;
fallback = true;
};
};
@@ -75,6 +80,7 @@ in
tmux = {
enable = true;
keyMode = "vi";
};
bash = {
@@ -235,12 +241,6 @@ in
exact = true;
};
};
settings = with lib.my.c.nix; {
extra-substituters = cache.substituters;
extra-trusted-public-keys = cache.keys;
connect-timeout = 5;
fallback = true;
};
};
})
(mkIf config.my.isStandalone {

View File

@@ -61,6 +61,7 @@ in
settings = {
background_opacity = "0.8";
tab_bar_edge = "top";
shell_integration = "no-sudo";
};
};

View File

@@ -70,13 +70,17 @@ in
"swaync/config.json" = mkIf (cfg.settings != { }) {
source = configSource;
onChange = ''
${cfg.package}/bin/swaync-client --reload-config
if ${pkgs.systemd}/bin/systemctl --user is-active --quiet swaync; then
${cfg.package}/bin/swaync-client --reload-config
fi
'';
};
"swaync/style.css" = mkIf (cfg.style != null) {
source = styleSource;
onChange = ''
${cfg.package}/bin/swaync-client --reload-css
if ${pkgs.systemd}/bin/systemctl --user is-active --quiet swaync; then
${cfg.package}/bin/swaync-client --reload-css
fi
'';
};
};

View File

@@ -10,16 +10,23 @@ rec {
gitea-runner = 401;
jellyseerr = 402;
atticd = 403;
kea = 404;
};
gids = {
matrix-syncv3 = 400;
gitea-runner = 401;
jellyseerr = 402;
atticd = 403;
kea = 404;
};
};
nginx = {
kernel = {
lts = pkgs: pkgs.linuxKernel.packages.linux_6_1;
latest = pkgs: pkgs.linuxKernel.packages.linux_6_6;
};
nginx = rec {
proxyHeaders = ''
# Setting any proxy_header in a child (e.g. location) will nuke the parents...
proxy_set_header X-Origin-URI $request_uri;
@@ -33,6 +40,55 @@ rec {
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Scheme $scheme;
'';
baseHttpConfig = ''
# NixOS provides a logrotate config that auto-compresses :)
log_format main
'$remote_addr - $remote_user [$time_local] $scheme "$host" "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log main;
# optimisation
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# gzip
gzip on;
gzip_proxied any;
gzip_comp_level 5;
gzip_types
application/atom+xml
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
gzip_vary on;
# proxying
proxy_buffering off;
proxy_redirect off;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_http_version 1.1;
${proxyHeaders}
'';
};
networkd = {
noL3 = {
LinkLocalAddressing = "no";
DHCP = "no";
LLDP = false;
EmitLLDP = false;
IPv6AcceptRA = false;
};
};
nix = {
@@ -53,6 +109,7 @@ rec {
pubDomain = "nul.ie";
colony = {
domain = "ams1.int.${pubDomain}";
pubV4 = "94.142.240.44";
prefixes = with lib.my.net.cidr; rec {
all = {
v4 = "10.100.0.0/16";
@@ -90,13 +147,72 @@ rec {
vip1 = "94.142.241.224/30";
vip2 = "94.142.242.254/31";
vip3 = "94.142.241.117/32";
as211024 = {
v4 = subnet 8 50 all.v4;
v6 = "2a0e:97c0:4df::/64";
};
home.v6 = "2a0e:97c0:4d0::/48";
};
firewallForwards = aa: [
{
port = "http";
dst = aa.middleman.internal.ipv4.address;
}
{
port = "https";
dst = aa.middleman.internal.ipv4.address;
}
{
port = 8448;
dst = aa.middleman.internal.ipv4.address;
}
{
port = 25565;
dst = aa.simpcraft-oci.internal.ipv4.address;
}
{
port = 25566;
dst = aa.simpcraft-staging-oci.internal.ipv4.address;
}
{
port = 25575;
dst = aa.simpcraft-oci.internal.ipv4.address;
}
{
port = 2456;
dst = aa.valheim-oci.internal.ipv4.address;
proto = "udp";
}
{
port = 2457;
dst = aa.valheim-oci.internal.ipv4.address;
proto = "udp";
}
{
port = 41641;
dst = aa.waffletail.internal.ipv4.address;
proto = "udp";
}
{
port = 25565;
dst = aa.simpcraft-oci.internal.ipv4.address;
proto = "udp";
}
];
fstrimConfig = {
enable = true;
# backup happens at 05:00
interval = "04:45";
};
};
home = rec {
domain = "h.${pubDomain}";
vlans = {
@@ -110,6 +226,11 @@ rec {
"river"
"stream"
];
routersPubV4 = [
"109.255.1.246"
"109.255.252.63"
];
prefixes = with lib.my.net.cidr; rec {
modem = {
v4 = "192.168.0.0/24";
@@ -124,15 +245,19 @@ rec {
hi = {
v4 = subnet 4 1 all.v4;
v6 = subnet 4 1 all.v6;
mtu = hiMTU;
};
lo = {
v4 = subnet 3 1 all.v4;
v6 = subnet 4 2 all.v6;
mtu = 1500;
};
untrusted = {
v4 = subnet 6 16 all.v4;
v6 = subnet 4 3 all.v6;
mtu = 1500;
};
inherit (colony.prefixes) as211024;
};
vips = with lib.my.net.cidr; {
hi = {
@@ -147,8 +272,54 @@ rec {
v4 = host 254 prefixes.untrusted.v4;
v6 = host 65535 prefixes.untrusted.v6;
};
as211024 = {
v4 = host 4 prefixes.as211024.v4;
v6 = host ((1*65536*65536*65536) + 65535) prefixes.as211024.v6;
};
};
};
britway = {
domain = "lon1.int.${pubDomain}";
pubV4 = "45.76.141.188";
prefixes = {
vultr = {
v6 = "2001:19f0:7402:128b::/64";
};
inherit (colony.prefixes) as211024;
};
# Need to use this IP as the source address for BGP
assignedV6 = "2001:19f0:7402:128b:5400:04ff:feac:6e06";
};
tailscale = {
prefix = {
v4 = "100.64.0.0/10";
v6 = "fd7a:115c:a1e0::/48";
};
};
as211024 = rec {
trusted = {
v4 = [
colony.prefixes.as211024.v4
colony.prefixes.all.v4
home.prefixes.all.v4
tailscale.prefix.v4
];
v6 = [
colony.prefixes.as211024.v6
colony.prefixes.all.v6
home.prefixes.all.v6
tailscale.prefix.v6
];
};
nftTrust = ''
iifname as211024 ip saddr { ${concatStringsSep ", " trusted.v4} } accept
iifname as211024 ip6 saddr { ${concatStringsSep ", " trusted.v6} } accept
'';
};
kelder = {
groups = {
storage = 2000;

View File

@@ -1,10 +1,11 @@
{ lib }:
let
inherit (builtins) length match elemAt filter;
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;
showWarnings concatStringsSep flatten unique optionalAttrs
mkBefore;
inherit (lib.flake) defaultSystems;
in
rec {
@@ -123,6 +124,21 @@ rec {
home-manager = mkOpt' (enum [ "unstable" "stable" "mine" "mine-stable" ]) "unstable" "Branch of home-manager to use.";
};
nft = rec {
ipEscape = replaceStrings ["." ":"] ["-" "-"];
natFilterChain = ip: "filter-fwd-${ipEscape ip}";
dnatChain = ip: "fwd-${ipEscape ip}";
};
mkVLAN = name: vid: {
"25-${name}" = {
netdevConfig = {
Name = name;
Kind = "vlan";
};
vlanConfig.Id = vid;
};
};
networkdAssignment = iface: a: {
matchConfig.Name = iface;
address =
@@ -137,6 +153,9 @@ rec {
LLDP = true;
EmitLLDP = "customer-bridge";
};
linkConfig = optionalAttrs (a.mtu != null) {
MTUBytes = toString a.mtu;
};
ipv6AcceptRAConfig = {
Token = mkIf (a.ipv6.iid != null) "static:${a.ipv6.iid}";
UseDNS = true;
@@ -148,13 +167,32 @@ rec {
systemdAwaitPostgres = pkg: host: {
after = [ "systemd-networkd-wait-online.service" ];
preStart = ''
preStart = mkBefore ''
until ${pkg}/bin/pg_isready -h ${host}; do
sleep 0.5
done
'';
};
vm = rec {
lvmDisk' = name: lv: {
inherit name;
backend = {
driver = "host_device";
filename = "/dev/main/${lv}";
# It appears this needs to be set on the backend _and_ the format
discard = "unmap";
};
format = {
driver = "raw";
discard = "unmap";
};
frontend = "virtio-blk";
};
lvmDisk = lv: lvmDisk' lv lv;
disk = vm: lv: lvmDisk' lv "vm-${vm}-${lv}";
};
deploy-rs =
with types;
let
@@ -202,4 +240,18 @@ rec {
filterOpts = filterAttrsRecursive (_: v: v != null);
};
versionOverlay = { self, pkgsFlake }: final: prev:
let
date = substring 0 8 (self.lastModifiedDate or self.lastModified or "19700101");
revCode = flake: flake.shortRev or "dirty";
in
{
trivial = prev.trivial // {
release = "23.12:u-${prev.trivial.release}";
codeName = "Amogus";
revisionWithDefault = default: self.rev or default;
versionSuffix = ".${date}.${revCode self}:u-${revCode pkgsFlake}";
};
};
}

197
nixos/boxes/britway/bgp.nix Normal file
View File

@@ -0,0 +1,197 @@
{ lib, pkgs, config, assignments, ... }:
let
inherit (lib.my.c.britway) assignedV6;
securebitSpace = "2a0e:97c0:4d0::/44";
intnet6 = "2a0e:97c0:4df::/48";
amsnet6 = "2a0e:97c0:4d2::/48";
homenet6 = "2a0e:97c0:4d0::/48";
in
{
config = {
my = {
secrets.files."britway/bgp-password-vultr.conf" = {
owner = "bird2";
group = "bird2";
};
};
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" ];
network = {
config.networkConfig.ManageForeignRoutes = false;
};
};
services = {
bird2 = {
enable = true;
preCheckConfig = ''
echo '"dummy"' > vultr-password.conf
'';
# TODO: Clean up and modularise
config = ''
define OWNAS = 211024;
define OWNIP4 = ${assignments.vultr.ipv4.address};
define OWNNETSET4 = [ ${assignments.vultr.ipv4.address}/32 ];
define INTNET6 = ${intnet6};
define AMSNET6 = ${amsnet6};
define HOMENET6 = ${homenet6};
define OWNIP6 = ${assignments.vultr.ipv6.address};
define OWNNETSET6 = [ ${intnet6}, ${amsnet6}, ${homenet6} ];
#define TRANSSET6 = [ ::1/128 ];
define DUB1IP6 = ${lib.my.c.home.vips.as211024.v6};
define PREFIXP = 110;
define PREFPEER = 120;
filter bgp_import {
if net !~ OWNNETSET4 && net !~ OWNNETSET6 then accept; else reject;
}
filter bgp_export {
if net ~ OWNNETSET4 || net ~ OWNNETSET6 then accept; else reject;
}
router id from "veth0";
protocol device {}
protocol direct {
interface "veth0";
ipv4;
ipv6;
}
protocol static static4 {
ipv4 {
import all;
export none;
};
}
protocol static static6 {
# Special case: We have to do the routing on behalf of this _internal_ next-hop
route INTNET6 via "as211024";
route HOMENET6 via DUB1IP6;
ipv6 {
import all;
export none;
};
}
protocol kernel kernel4 {
ipv4 {
import none;
export none;
};
}
protocol kernel kernel6 {
ipv6 {
import none;
export filter {
if net = HOMENET6 then accept;
reject;
};
};
}
protocol bgp bgptools {
local as OWNAS;
multihop;
description "bgp.tools monitoring";
neighbor 2a0c:2f07:9459::b11 as 212232;
source address OWNIP6;
ipv4 {
import none;
export all;
add paths tx;
};
ipv6 {
import none;
export all;
add paths tx;
};
}
template bgp base_bgp4 {
local as OWNAS;
direct;
allow local as;
ipv4 {
import keep filtered;
export none;
};
}
template bgp upstream_bgp4 from base_bgp4 {
ipv4 {
#import none;
import filter bgp_import;
};
}
template bgp peer_bgp4 from base_bgp4 {
ipv4 {
import filter bgp_import;
preference PREFPEER;
};
}
template bgp ixp_bgp4 from base_bgp4 {
ipv4 {
import filter bgp_import;
preference PREFIXP;
};
}
template bgp base_bgp6 {
local ${assignedV6} as OWNAS;
direct;
# So we can see routes we announce from other routers
allow local as;
ipv6 {
import keep filtered;
export filter bgp_export;
};
}
template bgp upstream_bgp6 from base_bgp6 {
ipv6 {
#import none;
import filter bgp_import;
};
}
template bgp peer_bgp6 from base_bgp6 {
ipv6 {
import filter bgp_import;
preference PREFPEER;
};
}
template bgp ixp_bgp6 from base_bgp6 {
ipv6 {
import filter bgp_import;
preference PREFIXP;
};
}
protocol bgp upstream4_vultr from upstream_bgp4 {
description "Vultr transit (IPv4)";
neighbor 169.254.169.254 as 64515;
multihop 2;
password
include "vultr-password.conf";;
}
protocol bgp upstream6_vultr from upstream_bgp6 {
description "Vultr transit (IPv6)";
neighbor 2001:19f0:ffff::1 as 64515;
multihop 2;
password
include "vultr-password.conf";;
}
'';
};
};
};
}

View File

@@ -0,0 +1,174 @@
{ lib, ... }:
let
inherit (lib.my) net;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.britway) prefixes domain pubV4 assignedV6;
in
{
nixos.systems.britway = {
system = "x86_64-linux";
nixpkgs = "mine";
assignments = {
vultr = {
inherit domain;
ipv4 = {
address = pubV4;
mask = 23;
gateway = "45.76.140.1";
};
ipv6 = {
iid = "::1";
address = "2001:19f0:7402:128b::1";
};
};
as211024 = {
ipv4 = {
address = net.cidr.host 5 prefixes.as211024.v4;
gateway = null;
};
ipv6.address = net.cidr.host ((2*65536*65536*65536) + 1) prefixes.as211024.v6;
};
};
configuration = { lib, pkgs, modulesPath, config, assignments, allAssignments, ... }:
let
inherit (lib) mkMerge mkForce;
inherit (lib.my) networkdAssignment;
in
{
imports = [
"${modulesPath}/profiles/qemu-guest.nix"
./bgp.nix
./nginx.nix
./tailscale.nix
];
config = mkMerge [
{
boot = {
initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "sr_mod" ];
loader = {
systemd-boot.enable = false;
grub = {
enable = true;
device = "/dev/vda";
};
};
};
fileSystems = {
"/boot" = {
device = "/dev/disk/by-partuuid/c557ef12-da44-41d1-84f5-d32a711feefd";
fsType = "ext4";
};
"/nix" = {
device = "/dev/disk/by-partuuid/d42d0853-b054-4104-8afd-6d36287c7ca3";
fsType = "ext4";
};
"/persist" = {
device = "/dev/disk/by-partuuid/f14fbcf4-5242-456b-a4db-ef15d053d62e";
fsType = "ext4";
neededForBoot = true;
};
};
services = {
iperf3 = {
enable = true;
openFirewall = true;
};
};
networking = { inherit domain; };
systemd.network = {
config = {
routeTables.ts-extra = 1337;
};
links = {
"10-veth0" = {
matchConfig.PermanentMACAddress = "56:00:04:ac:6e:06";
linkConfig.Name = "veth0";
};
};
networks = {
"20-veth0" = mkMerge [
(networkdAssignment "veth0" assignments.vultr)
{
address = [ "${assignedV6}/64" ];
}
];
"90-l2mesh-as211024" = mkMerge [
(networkdAssignment "as211024" assignments.as211024)
{
matchConfig.Name = "as211024";
networkConfig.IPv6AcceptRA = mkForce false;
routes = map (r: { routeConfig = r; }) [
{
Destination = lib.my.c.colony.prefixes.all.v4;
Gateway = allAssignments.estuary.as211024.ipv4.address;
}
{
Destination = lib.my.c.home.prefixes.all.v4;
Gateway = lib.my.c.home.vips.as211024.v4;
}
{
# Just when routing traffic from Tailscale nodes, otherwise use WAN
Destination = lib.my.c.colony.prefixes.all.v6;
Gateway = allAssignments.estuary.as211024.ipv6.address;
Table = "ts-extra";
}
];
routingPolicyRules = map (r: { routingPolicyRuleConfig = r; }) [
{
IncomingInterface = "tailscale0";
To = lib.my.c.colony.prefixes.all.v6;
Table = "ts-extra";
}
];
}
];
};
};
my = {
server.enable = true;
secrets = {
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAmXC9egI46Qtaiifhq2I+rv2s1yPyzTlO4BHzUb+3Su";
files = {
"l2mesh/as211024.key" = {};
};
};
vpns = {
l2.pskFiles = {
as211024 = config.age.secrets."l2mesh/as211024.key".path;
};
};
firewall = {
trustedInterfaces = [ "tailscale0" ];
extraRules = ''
table inet filter {
chain forward {
${lib.my.c.as211024.nftTrust}
oifname as211024 accept
}
}
table inet nat {
chain postrouting {
iifname tailscale0 oifname veth0 snat ip to ${assignments.vultr.ipv4.address}
iifname tailscale0 oifname veth0 snat ip6 to ${assignments.as211024.ipv6.address}
}
}
'';
};
};
}
];
};
};
}

View File

@@ -0,0 +1,109 @@
{ lib, pkgs, config, ... }:
let
inherit (builtins) mapAttrs;
inherit (lib) mkMerge mkDefault;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.nginx) baseHttpConfig proxyHeaders;
in
{
config = {
my = {
secrets.files = {
"dhparams.pem" = {
owner = "acme";
group = "acme";
mode = "440";
};
"britway/cloudflare-credentials.conf" = {
owner = "acme";
group = "acme";
};
};
firewall = {
tcp.allowed = [ "http" "https" ];
};
};
users = {
users = {
nginx.extraGroups = [ "acme" ];
};
};
security.acme = {
acceptTerms = true;
defaults = {
email = "dev@nul.ie";
server = "https://acme-v02.api.letsencrypt.org/directory";
reloadServices = [ "nginx" ];
dnsResolver = "8.8.8.8";
};
certs = {
"${pubDomain}" = {
extraDomainNames = [
"*.${pubDomain}"
];
dnsProvider = "cloudflare";
credentialsFile = config.age.secrets."britway/cloudflare-credentials.conf".path;
};
};
};
services = {
nginx = {
enable = true;
enableReload = true;
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 = ''
${baseHttpConfig}
# caching
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=512m;
'';
virtualHosts =
let
hosts = {
"_" = {
default = true;
forceSSL = true;
onlySSL = false;
locations = {
"/".root = "${pkgs.nginx}/html";
};
};
"ts.${pubDomain}" = {
locations."/" = {
proxyPass = "http://localhost:${toString config.services.headscale.port}";
proxyWebsockets = true;
extraConfig = ''
proxy_buffering off;
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
'';
};
};
};
defaultsFor = mapAttrs (n: _: {
onlySSL = mkDefault true;
useACMEHost = mkDefault pubDomain;
kTLS = mkDefault true;
http2 = mkDefault true;
});
in
mkMerge [
hosts
(defaultsFor hosts)
];
};
};
};
}

View File

@@ -0,0 +1,110 @@
{ lib, pkgs, config, assignments, allAssignments, ... }:
let
inherit (lib) concatStringsSep;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.britway) prefixes domain;
# Can't use overrideAttrs because we need to override `vendorHash` within `buildGoModule`
headscale = pkgs.headscale.override {
buildGoModule = args: pkgs.buildGoModule (args // rec {
version = "0.23.0-alpha2";
src = pkgs.fetchFromGitHub {
owner = "juanfont";
repo = "headscale";
rev = "v${version}";
hash = "sha256-sz+uQyyq/5YYDe5I44x5x2nvd48swAhNlInB8KZYvDo=";
};
vendorHash = "sha256-u9AmJguQ5dnJpfhOeLN43apvMHuraOrJhvlEIp9RoIc=";
});
};
advRoutes = concatStringsSep "," [
lib.my.c.home.prefixes.all.v4
lib.my.c.home.prefixes.all.v6
];
pubNameservers = [
"1.1.1.1"
"1.0.0.1"
"2606:4700:4700::1111"
"2606:4700:4700::1001"
];
in
{
config = {
environment.systemPackages = [
# For CLI
config.services.headscale.package
];
services = {
headscale = {
enable = true;
package = headscale;
settings = {
disable_check_updates = true;
unix_socket_permission = "0770";
server_url = "https://ts.${pubDomain}";
db_type = "sqlite3";
db_path = "/var/lib/headscale/db.sqlite3";
noise.private_key_path = "/var/lib/headscale/noise_private.key";
ip_prefixes = with lib.my.c.tailscale.prefix; [ v4 v6 ];
dns_config = {
# Use IPs that will route inside the VPN to prevent interception
# (e.g. DNS rebinding filtering)
restricted_nameservers = {
"${domain}" = pubNameservers;
"${lib.my.c.colony.domain}" = with allAssignments.estuary.base; [
ipv4.address ipv6.address
];
"${lib.my.c.home.domain}" = with allAssignments; [
river.hi.ipv4.address
river.hi.ipv6.address
stream.hi.ipv4.address
stream.hi.ipv6.address
];
};
magic_dns = true;
base_domain = "ts.${pubDomain}";
override_local_dns = false;
};
oidc = {
only_start_if_oidc_is_available = true;
issuer = "https://accounts.google.com";
client_id = "545475967061-l45cln081mp8t4li2c34v7t7b8la6f4f.apps.googleusercontent.com";
client_secret_path = config.age.secrets."britway/oidc-secret.txt".path;
scope = [ "openid" "profile" "email" ];
allowed_users = [ "jackos1998@gmail.com" ];
};
};
};
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://ts.nul.ie"
"--netfilter-mode=off"
"--advertise-exit-node"
"--advertise-routes=${advRoutes}"
"--accept-routes=false"
];
};
};
my = {
secrets = {
files = {
"britway/oidc-secret.txt" = {
owner = "headscale";
group = "headscale";
mode = "440";
};
"tailscale-auth.key" = {};
};
};
};
};
}

View File

@@ -1,12 +1,33 @@
{ lib, ... }: {
{ lib, ... }:
let
inherit (lib.my) net;
inherit (lib.my.c.home) domain vlans prefixes;
in
{
nixos.systems.castle = {
system = "x86_64-linux";
nixpkgs = "mine";
home-manager = "mine";
assignments = {
hi = {
inherit domain;
ipv4 = {
address = net.cidr.host 40 prefixes.hi.v4;
mask = 22;
gateway = null;
};
ipv6 = {
iid = "::3:1";
address = net.cidr.host (65536*3+1) prefixes.hi.v6;
};
};
};
configuration = { lib, pkgs, modulesPath, config, systems, assignments, allAssignments, ... }:
let
inherit (lib) mkIf mkMerge mkForce;
inherit (lib.my) mkVLAN networkdAssignment;
in
{
hardware = {
@@ -25,7 +46,7 @@
efi.canTouchEfiVariables = false;
timeout = 10;
};
kernelPackages = pkgs.linuxKernel.packages.linux_6_5;
kernelPackages = lib.my.c.kernel.latest pkgs;
kernelModules = [ "kvm-amd" ];
kernelParams = [ "amd_iommu=on" "amd_pstate=passive" ];
kernelPatches = [
@@ -83,6 +104,15 @@
blueman.enable = true;
};
programs = {
virt-manager.enable = true;
wireshark = {
enable = true;
package = pkgs.wireshark-qt;
};
};
virtualisation.libvirtd.enable = true;
networking = {
domain = "h.${lib.my.c.pubDomain}";
firewall.enable = false;
@@ -117,6 +147,9 @@
systemd = {
network = {
wait-online.enable = false;
netdevs = mkMerge [
(mkVLAN "lan-hi" vlans.hi)
];
links = {
"10-et2.5g" = {
matchConfig.MACAddress = "c8:7f:54:6e:17:0f";
@@ -127,15 +160,33 @@
linkConfig.Name = "et10g";
};
"12-et100g" = {
matchConfig.MACAddress = "24:8a:07:a8:fe:3a";
linkConfig.Name = "et100g";
matchConfig.PermanentMACAddress = "24:8a:07:a8:fe:3a";
linkConfig = {
Name = "et100g";
MTUBytes = "9000";
};
};
};
networks = {
"50-lan" = {
matchConfig.Name = "et2.5g";
DHCP = "yes";
DHCP = "no";
address = [ "10.16.7.1/16" ];
};
"50-et100g" = {
matchConfig.Name = "et100g";
vlan = [ "lan-hi" ];
networkConfig.IPv6AcceptRA = false;
};
"60-lan-hi" = mkMerge [
(networkdAssignment "lan-hi" assignments.hi)
{
DHCP = "yes";
matchConfig.Name = "lan-hi";
linkConfig.MTUBytes = "9000";
}
];
};
};
};
@@ -196,6 +247,7 @@
secrets = {
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMlVuTzKObeaUuPocCF41IO/8X+443lzUJLuCIclt2vr";
};
nvme.uuid = "2230b066-a674-4f45-a1dc-f7727b3a9e7b";
firewall = {
enable = false;

View File

@@ -1,7 +1,7 @@
{ lib, ... }:
let
inherit (lib.my) net;
inherit (lib.my.c.colony) domain prefixes;
inherit (lib.my.c.colony) domain prefixes firewallForwards;
in
{
imports = [ ./vms ];
@@ -57,7 +57,7 @@ in
};
boot = {
kernelPackages = pkgs.linuxKernel.packages.linux_6_1.extend (self: super: {
kernelPackages = (lib.my.c.kernel.lts pkgs).extend (self: super: {
kernel = super.kernel.override {
structuredExtraConfig = with lib.kernel; {
#SOME_OPT = yes;
@@ -66,10 +66,21 @@ in
};
});
kernelModules = [ "kvm-amd" ];
kernelParams = [ "amd_iommu=on" "console=ttyS0,115200n8" "console=ttyS1,115200n8" "console=tty0" ];
kernelParams = [
"amd_iommu=on"
"console=ttyS0,115200n8" "console=ttyS1,115200n8" "console=tty0"
"systemd.setenv=SYSTEMD_SULOGIN_FORCE=1"
];
initrd = {
kernelModules = [ "dm-raid" ];
availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" "sr_mod" ];
systemd = {
enable = true;
# Onlu activate volumes needed for boot to prevent thin check from getting killed while switching root
contents."/etc/lvm/lvm.conf".text = ''
activation/auto_activation_volume_list = [ "main/colony-nix" "main/colony-persist" ]
'';
};
};
};
@@ -137,6 +148,15 @@ in
services = {
"serial-getty@ttyS0".enable = true;
"serial-getty@ttyS1".enable = true;
lvm-activate-main = {
description = "Activate remaining LVs";
before = [ "local-fs-pre.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.lvm2.bin}/bin/vgchange -aay main";
};
wantedBy = [ "sysinit.target" ];
};
rsync-lvm-meta = {
description = "rsync lvm metadata backups / archives to rsync.net";
@@ -248,6 +268,14 @@ in
Destination = allAssignments.shill.internal.ipv4.address;
Gateway = allAssignments.shill.routing.ipv4.address;
}
{
Destination = lib.my.c.tailscale.prefix.v4;
Gateway = allAssignments.shill.routing.ipv4.address;
}
{
Destination = lib.my.c.tailscale.prefix.v6;
Gateway = allAssignments.shill.internal.ipv6.address;
}
{
Destination = prefixes.oci.v4;
@@ -261,6 +289,11 @@ in
Destination = allAssignments.whale2.internal.ipv4.address;
Gateway = allAssignments.whale2.routing.ipv4.address;
}
{
Destination = allAssignments.git.internal.ipv4.address;
Gateway = allAssignments.git.routing.ipv4.address;
}
];
}
];
@@ -346,6 +379,7 @@ in
firewall = {
trustedInterfaces = [ "vms" ];
nat.forwardPorts."${allAssignments.estuary.internal.ipv4.address}" = firewallForwards allAssignments;
extraRules = ''
define cust = { vm-mail, vm-darts }
table inet filter {
@@ -372,6 +406,7 @@ in
"vm-estuary-persist"
"vm-whale2-persist"
"vm-mail-data"
"vm-git-persist"
"git"
];
compression = "zstd,5";

View File

@@ -3,37 +3,22 @@
./estuary
./shill
./whale2
./git
];
nixos.systems.colony.configuration = { lib, pkgs, config, systems, ... }:
let
inherit (lib) mkIf mkMerge optionals;
lvmDisk' = name: lv: {
inherit name;
backend = {
driver = "host_device";
filename = "/dev/main/${lv}";
# It appears this needs to be set on the backend _and_ the format
discard = "unmap";
};
format = {
driver = "raw";
discard = "unmap";
};
frontend = "virtio-blk";
};
lvmDisk = lv: lvmDisk' lv lv;
vmLVM = vm: lv: lvmDisk' lv "vm-${vm}-${lv}";
inherit (lib.my) vm;
installerDisk = {
name = "installer";
backend = {
driver = "file";
#filename = "${systems.installer.configuration.config.my.buildAs.iso}/iso/nixos-installer-devplayer0.iso";
#filename = "/persist/home/dev/nixos-installer-devplayer0.iso";
filename = "/persist/home/dev/nixos-installer-devplayer0-b4d0d9a.iso";
#filename = "/persist/home/dev/debian-12.1.0-amd64-netinst.iso";
filename = "/persist/home/dev/ubuntu-22.04.3-live-server-amd64.iso";
# filename = "/persist/home/dev/ubuntu-22.04.3-live-server-amd64.iso";
read-only = "on";
};
format.driver = "raw";
@@ -116,9 +101,9 @@
};
};
drives = [ ] ++ (optionals (!config.my.build.isDevVM) [
(mkMerge [ (vmLVM "estuary" "esp") { frontendOpts.bootindex = 0; } ])
(vmLVM "estuary" "nix")
(vmLVM "estuary" "persist")
(mkMerge [ (vm.disk "estuary" "esp") { frontendOpts.bootindex = 0; } ])
(vm.disk "estuary" "nix")
(vm.disk "estuary" "persist")
]);
hostDevices = {
net-wan0 = {
@@ -135,18 +120,17 @@
cpus = 12;
threads = 2;
};
memory = 65536;
memory = 40960;
networks.vms.mac = "52:54:00:27:3d:5c";
cleanShutdown.timeout = 120;
drives = [ ] ++ (optionals (!config.my.build.isDevVM) [
(mkMerge [ (vmLVM "shill" "esp") { frontendOpts.bootindex = 0; } ])
(vmLVM "shill" "nix")
(vmLVM "shill" "persist")
(mkMerge [ (vm.disk "shill" "esp") { frontendOpts.bootindex = 0; } ])
(vm.disk "shill" "nix")
(vm.disk "shill" "persist")
(lvmDisk "media")
(lvmDisk "minio")
(lvmDisk "nix-atticd")
(lvmDisk "git")
(vm.lvmDisk "media")
(vm.lvmDisk "minio")
(vm.lvmDisk "nix-atticd")
]);
};
@@ -157,19 +141,39 @@
cpus = 8;
threads = 2;
};
memory = 32768;
memory = 16384;
networks.vms.mac = "52:54:00:d5:d9:c6";
cleanShutdown.timeout = 120;
drives = [ ] ++ (optionals (!config.my.build.isDevVM) [
(mkMerge [ (vmLVM "whale2" "esp") { frontendOpts.bootindex = 0; } ])
(vmLVM "whale2" "nix")
(vmLVM "whale2" "persist")
(mkMerge [ (vm.disk "whale2" "esp") { frontendOpts.bootindex = 0; } ])
(vm.disk "whale2" "nix")
(vm.disk "whale2" "persist")
(lvmDisk "oci")
(lvmDisk "gitea-actions-cache")
(vm.lvmDisk "oci")
]);
};
git = {
uuid = "c0659fdc-3356-4717-a6a1-5f289ef03c4a";
cpu = "host,topoext";
smp = {
cpus = 12;
threads = 2;
};
memory = 40960;
networks.vms.mac = "52:54:00:75:78:a8";
cleanShutdown.timeout = 120;
drives = [
(mkMerge [ (vm.disk "git" "esp") { frontendOpts.bootindex = 0; } ])
(vm.disk "git" "nix")
(vm.disk "git" "persist")
(vm.disk "git" "oci")
(vm.lvmDisk "git")
(vm.lvmDisk "gitea-actions-cache")
];
};
mail = {
uuid = "fd95fe0f-c204-4dd5-b16f-2b808e14a43a";
cpu = "host,topoext";
@@ -177,15 +181,15 @@
cpus = 3;
threads = 2;
};
memory = 8192;
memory = 6144;
networks.public = {
bridge = null;
mac = "52:54:00:a8:d1:03";
};
cleanShutdown.timeout = 120;
drives = [
(mkMerge [ (vmLVM "mail" "root") { frontendOpts.bootindex = 0; } ])
(vmLVM "mail" "data")
(mkMerge [ (vm.disk "mail" "root") { frontendOpts.bootindex = 0; } ])
(vm.disk "mail" "data")
];
};
@@ -203,8 +207,8 @@
};
cleanShutdown.timeout = 120;
drives = [
(mkMerge [ (vmLVM "darts" "root") { frontendOpts.bootindex = 0; } ])
(lvmDisk' "media" "darts-media")
(mkMerge [ (vm.disk "darts" "root") { frontendOpts.bootindex = 0; } ])
(vm.lvmDisk' "media" "darts-media")
];
};
};

View File

@@ -16,20 +16,23 @@ in
define CCVIP1 = ${lib.my.c.colony.prefixes.vip1};
define CCVIP2 = ${lib.my.c.colony.prefixes.vip2};
define CCVIP3 = ${lib.my.c.colony.prefixes.vip3};
define OWNIP4 = ${assignments.internal.ipv4.address};
define OWNNETSET4 = [ ${assignments.internal.ipv4.address}/32 ];
define CCNETSET4 = [ ${lib.my.c.colony.prefixes.vip1}, ${lib.my.c.colony.prefixes.vip2} ];
define CCNETSET4 = [ ${lib.my.c.colony.prefixes.vip1}, ${lib.my.c.colony.prefixes.vip2}, ${lib.my.c.colony.prefixes.vip3} ];
define INTNET6 = ${intnet6};
define AMSNET6 = ${amsnet6};
define HOMENET6 = ${homenet6};
define OWNIP6 = ${assignments.base.ipv6.address};
define OWNNETSET6 = [ ${intnet6}, ${amsnet6}, ${homenet6} ];
# we have issues with sending ICMPv6 too big back on the wrong interface right now...
define OWNNETSET6 = [ ${intnet6}, ${amsnet6} ];
define CCNETSET6 = [ ];
#define TRANSSET6 = [ ::1/128 ];
define DUB1IP6 = 2a0e:97c0:4df:0:2::1;
define DUB1IP6 = ${lib.my.c.home.vips.as211024.v6};
define PREFIXP = 110;
define PREFPEER = 120;
@@ -41,7 +44,7 @@ in
if net ~ OWNNETSET4 || net ~ OWNNETSET6 then accept; else reject;
}
filter bgp_export_cc {
if net ~ OWNNETSET4 || net ~ OWNNETSET6 || net ~ CCNETSET4 then accept; else reject;
if net ~ OWNNETSET4 || net ~ OWNNETSET6 || net ~ CCNETSET4 || net ~ CCNETSET6 then accept; else reject;
}
router id from "wan";
@@ -55,6 +58,7 @@ in
protocol static static4 {
route CCVIP1 via "base";
route CCVIP2 via "base";
route CCVIP3 via "base";
ipv4 {
import all;
@@ -186,10 +190,12 @@ in
protocol bgp upstream6_coloclue_eun2 from upstream_bgp6 {
description "ColoClue euNetworks 2 (IPv6)";
neighbor 2a02:898:0:20::e2 as 8283;
ipv6 { export filter bgp_export_cc; };
}
protocol bgp upstream6_coloclue_eun3 from upstream_bgp6 {
description "ColoClue euNetworks 3 (IPv6)";
neighbor 2a02:898:0:20::e1 as 8283;
ipv6 { export filter bgp_export_cc; };
}
protocol bgp upstream6_ifog from upstream_bgp6 {
@@ -202,14 +208,15 @@ in
neighbor 2001:7f8:10f::1b1b:154 as 6939;
}
protocol bgp upstream4_fogixp_efero from upstream_bgp4 {
description "efero transit (on FogIXP, IPv4)";
neighbor 185.1.147.107 as 208431;
}
protocol bgp upstream6_fogixp_efero from upstream_bgp6 {
description "efero transit (on FogIXP, IPv6)";
neighbor 2001:7f8:ca:1::107 as 208431;
}
# Not working so well lately...
# protocol bgp upstream4_fogixp_efero from upstream_bgp4 {
# description "efero transit (on FogIXP, IPv4)";
# neighbor 185.1.147.107 as 208431;
# }
# protocol bgp upstream6_fogixp_efero from upstream_bgp6 {
# description "efero transit (on FogIXP, IPv6)";
# neighbor 2001:7f8:ca:1::107 as 208431;
# }
protocol bgp peer4_cc_luje from peer_bgp4 {
description "LUJE.net (on ColoClue, IPv4)";

View File

@@ -1,9 +1,8 @@
{ lib, ... }:
let
inherit (lib.my) net;
inherit (lib.my.c.colony) domain prefixes;
pubV4 = "94.142.240.44";
inherit (builtins) elemAt;
inherit (lib.my) net mkVLAN;
inherit (lib.my.c.colony) pubV4 domain prefixes firewallForwards;
in
{
nixos = {
@@ -11,9 +10,12 @@ in
l2 = {
as211024 = {
vni = 211024;
security.enable = true;
peers = {
estuary.addr = pubV4;
home.addr = "188.141.75.2";
river.addr = elemAt lib.my.c.home.routersPubV4 0;
stream.addr = elemAt lib.my.c.home.routersPubV4 1;
britway.addr = lib.my.c.britway.pubV4;
};
};
};
@@ -53,10 +55,10 @@ in
};
as211024 = {
ipv4 = {
address = "10.255.3.1";
address = net.cidr.host 1 prefixes.as211024.v4;
gateway = null;
};
ipv6.address = "2a0e:97c0:4df:0:3::1";
ipv6.address = net.cidr.host 1 prefixes.as211024.v6;
};
};
@@ -64,6 +66,7 @@ in
let
inherit (lib) flatten mkIf mkMerge mkForce;
inherit (lib.my) networkdAssignment;
inherit (lib.my.c) networkd;
in
{
imports = [ "${modulesPath}/profiles/qemu-guest.nix" ./dns.nix ./bgp.nix ];
@@ -90,6 +93,7 @@ in
environment = {
systemPackages = with pkgs; [
ethtool
conntrack-tools
wireguard-tools
];
};
@@ -114,34 +118,19 @@ in
};
systemd = {
services = {
# Use this as a way to make sure the router always knows we're here (NDP seems kindy funky)
ipv6-neigh-keepalive =
let
waitOnline = "systemd-networkd-wait-online@wan.service";
in
{
description = "Frequent ICMP6 neighbour solicitations";
enable = false;
requires = [ waitOnline ];
after = [ waitOnline ];
script = ''
while true; do
${pkgs.ndisc6}/bin/ndisc6 ${assignments.internal.ipv6.gateway} wan
sleep 10
done
'';
wantedBy = [ "multi-user.target" ];
};
bird2 =
let
waitOnline = "systemd-networkd-wait-online@wan.service";
in
{
services =
let
waitOnline = "systemd-networkd-wait-online@wan.service";
in
{
bird2 = {
after = [ waitOnline ];
# requires = [ waitOnline ];
};
ipsec = {
after = [ waitOnline ];
requires = [ waitOnline ];
};
};
};
@@ -154,19 +143,7 @@ in
};
};
netdevs =
let
mkVLAN = name: vid: {
"25-${name}" = {
netdevConfig = {
Name = name;
Kind = "vlan";
};
vlanConfig.Id = vid;
};
};
in
mkMerge [
netdevs = mkMerge [
(mkVLAN "ifog" 409)
(mkVLAN "frys-ix" 701)
@@ -271,13 +248,7 @@ in
Kind = "vlan";
};
vlan = [ "frys-ix" "nl-ix" "fogixp" "ifog-transit" ];
networkConfig = {
LinkLocalAddressing = "no";
DHCP = "no";
LLDP = false;
EmitLLDP = false;
IPv6AcceptRA = false;
};
networkConfig = networkd.noL3;
};
"85-ifog-transit" = {
matchConfig.Name = "ifog-transit";
@@ -315,6 +286,10 @@ in
Destination = prefixes.vip1;
Gateway = allAssignments.colony.routing.ipv4.address;
}
{
Destination = prefixes.vip3;
Gateway = allAssignments.colony.routing.ipv4.address;
}
{
Destination = prefixes.darts.v4;
Gateway = allAssignments.colony.routing.ipv4.address;
@@ -323,6 +298,15 @@ in
Destination = prefixes.cust.v6;
Gateway = allAssignments.colony.internal.ipv6.address;
}
{
Destination = lib.my.c.tailscale.prefix.v4;
Gateway = allAssignments.colony.routing.ipv4.address;
}
{
Destination = lib.my.c.tailscale.prefix.v6;
Gateway = allAssignments.colony.internal.ipv6.address;
}
] ++
(map (pName: [
{
@@ -337,14 +321,19 @@ in
}
];
"90-l2mesh-as211024" = {
matchConfig.Name = "as211024";
address = with assignments.as211024; [
(with ipv4; "${address}/${toString mask}")
(with ipv6; "${address}/${toString mask}")
];
networkConfig.IPv6AcceptRA = false;
};
"90-l2mesh-as211024" = mkMerge [
(networkdAssignment "as211024" assignments.as211024)
{
matchConfig.Name = "as211024";
networkConfig.IPv6AcceptRA = mkForce false;
routes = map (r: { routeConfig = r; }) [
{
Destination = lib.my.c.home.prefixes.all.v4;
Gateway = lib.my.c.home.vips.as211024.v4;
}
];
}
];
"95-kelder" = {
matchConfig.Name = "kelder";
routes = [
@@ -366,43 +355,23 @@ in
"estuary/kelder-wg.key" = {
owner = "systemd-network";
};
"l2mesh/as211024.key" = {};
};
};
server.enable = true;
vpns = {
l2.pskFiles = {
as211024 = config.age.secrets."l2mesh/as211024.key".path;
};
};
firewall = {
trustedInterfaces = [ "as211024" ];
udp.allowed = [ 5353 lib.my.c.kelder.vpn.port ];
tcp.allowed = [ 5353 "bgp" ];
nat = {
enable = true;
externalInterface = "wan";
externalIP = assignments.internal.ipv4.address;
forwardPorts = [
{
port = "http";
dst = allAssignments.middleman.internal.ipv4.address;
}
{
port = "https";
dst = allAssignments.middleman.internal.ipv4.address;
}
{
port = 8448;
dst = allAssignments.middleman.internal.ipv4.address;
}
{
port = 2456;
dst = allAssignments.valheim-oci.internal.ipv4.address;
proto = "udp";
}
{
port = 2457;
dst = allAssignments.valheim-oci.internal.ipv4.address;
proto = "udp";
}
];
forwardPorts."${assignments.internal.ipv4.address}" = firewallForwards allAssignments;
};
extraRules =
let
@@ -424,12 +393,16 @@ in
# Safe enough to allow all SSH
tcp dport ssh accept
${matchInet "tcp dport { http, https, 8448 } accept" "middleman"}
${matchInet "udp dport { 2456-2457 } accept" "valheim-oci"}
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-staging-oci.internal.ipv6.address} tcp dport 25565 accept
return
}
chain routing-udp {
ip6 daddr ${aa.valheim-oci.internal.ipv6.address} udp dport { 2456-2457 } accept
ip6 daddr ${aa.waffletail.internal.ipv6.address} udp dport 41641 accept
ip6 daddr ${aa.simpcraft-oci.internal.ipv6.address} udp dport 25565 accept
return
}
chain filter-routing {
@@ -446,7 +419,8 @@ in
}
chain forward {
iifname { wan, $ixps } oifname base jump filter-routing
${lib.my.c.as211024.nftTrust}
iifname { wan, as211024, $ixps } oifname base jump filter-routing
oifname $ixps jump ixp
iifname base oifname { base, wan, $ixps } accept
oifname { as211024, kelder } accept
@@ -459,11 +433,9 @@ in
table inet nat {
chain prerouting {
${matchInet "meta l4proto { udp, tcp } th dport domain redirect to :5353" "estuary"}
ip daddr ${allAssignments.shill.internal.ipv4.address} tcp dport { http, https } dnat to ${allAssignments.middleman.internal.ipv4.address}
ip6 daddr ${allAssignments.shill.internal.ipv6.address} tcp dport { http, https } dnat to ${allAssignments.middleman.internal.ipv6.address}
}
chain postrouting {
ip saddr ${prefixes.all.v4} snat to ${assignments.internal.ipv4.address}
ip saddr ${prefixes.all.v4} oifname != as211024 snat to ${assignments.internal.ipv4.address}
}
}
'';

View File

@@ -52,7 +52,7 @@ in
allowFrom = [
"127.0.0.0/8" "::1/128"
prefixes.all.v4 prefixes.all.v6
];
] ++ (with lib.my.c.tailscale.prefix; [ v4 v6 ]);
};
settings = {
@@ -145,9 +145,14 @@ in
http IN A ${assignments.internal.ipv4.address}
http IN AAAA ${allAssignments.middleman.internal.ipv6.address}
librespeed IN CNAME http.${config.networking.domain}.
valheim IN A ${assignments.internal.ipv4.address}
valheim IN AAAA ${allAssignments.valheim-oci.internal.ipv6.address}
simpcraft IN A ${assignments.internal.ipv4.address}
simpcraft IN AAAA ${allAssignments.simpcraft-oci.internal.ipv6.address}
simpcraft-staging IN A ${assignments.internal.ipv4.address}
simpcraft-staging IN AAAA ${allAssignments.simpcraft-staging-oci.internal.ipv6.address}
mail-vm IN A ${net.cidr.host 0 prefixes.mail.v4}
mail-vm IN AAAA ${net.cidr.host 1 prefixes.mail.v6}

View File

@@ -0,0 +1,241 @@
{ lib, ... }:
let
inherit (builtins) mapAttrs;
inherit (lib) mkMerge mkDefault;
inherit (lib.my) net;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.colony) domain prefixes;
inherit (lib.my.c.nginx) baseHttpConfig proxyHeaders;
in
{
nixos.systems.git = {
system = "x86_64-linux";
nixpkgs = "mine";
assignments = {
routing = {
name = "git-vm-routing";
inherit domain;
ipv4.address = net.cidr.host 4 prefixes.vms.v4;
};
internal = {
name = "git-vm";
inherit domain;
ipv4 = {
address = net.cidr.host 0 prefixes.vip3;
mask = 32;
gateway = null;
genPTR = false;
};
ipv6 = {
iid = "::4";
address = net.cidr.host 4 prefixes.vms.v6;
};
};
};
configuration = { lib, pkgs, modulesPath, config, assignments, allAssignments, ... }:
let
inherit (lib) mkMerge;
inherit (lib.my) networkdAssignment;
in
{
imports = [
"${modulesPath}/profiles/qemu-guest.nix"
./gitea.nix
./gitea-actions.nix
];
config = mkMerge [
{
boot = {
kernelParams = [ "console=ttyS0,115200n8" ];
};
fileSystems = {
"/boot" = {
device = "/dev/disk/by-label/ESP";
fsType = "vfat";
};
"/nix" = {
device = "/dev/disk/by-label/nix";
fsType = "ext4";
};
"/persist" = {
device = "/dev/disk/by-label/persist";
fsType = "ext4";
neededForBoot = true;
};
"/var/lib/containers" = {
device = "/dev/disk/by-label/oci";
fsType = "xfs";
options = [ "pquota" ];
};
};
users = {
users = {
nginx.extraGroups = [ "acme" ];
};
};
security.acme = {
acceptTerms = true;
defaults = {
email = "dev@nul.ie";
server = "https://acme-v02.api.letsencrypt.org/directory";
reloadServices = [ "nginx" ];
dnsResolver = "8.8.8.8";
};
certs = {
"${pubDomain}" = {
extraDomainNames = [
"*.${pubDomain}"
];
dnsProvider = "cloudflare";
credentialsFile = config.age.secrets."middleman/cloudflare-credentials.conf".path;
};
};
};
services = {
fstrim = lib.my.c.colony.fstrimConfig;
# Hacks for Jsch (Minecraft FastBack) to work
openssh = {
hostKeys = [
{
bits = 4096;
path = "/etc/ssh/ssh_host_rsa_key";
type = "rsa";
}
{
path = "/etc/ssh/ssh_host_ed25519_key";
type = "ed25519";
}
{
type = "ecdsa-sha2-nistp256";
path = "/etc/ssh/ssh_host_ecdsa_key";
}
];
settings = {
Macs = [
"hmac-sha2-512-etm@openssh.com"
"hmac-sha2-256-etm@openssh.com"
"umac-128-etm@openssh.com"
"hmac-sha2-256"
];
};
};
netdata.enable = true;
nginx = {
enable = true;
enableReload = true;
logError = "stderr info";
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 = ''
${baseHttpConfig}
# caching
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=512m;
'';
virtualHosts =
let
hosts = {
"_" = {
default = true;
forceSSL = true;
onlySSL = false;
locations = {
"/".root = "${pkgs.nginx}/html";
};
};
"git.${pubDomain}" = {
locations."/".proxyPass = "http://localhost:3000";
};
};
defaultsFor = mapAttrs (n: _: {
onlySSL = mkDefault true;
useACMEHost = mkDefault pubDomain;
kTLS = mkDefault true;
http2 = mkDefault true;
});
in
mkMerge [
hosts
(defaultsFor hosts)
];
};
};
virtualisation = {
podman = {
enable = true;
};
oci-containers = {
backend = "podman";
};
containers.containersConf.settings.network.default_subnet = "10.88.0.0/16";
};
systemd.network = {
links = {
"10-vms" = {
matchConfig.MACAddress = "52:54:00:75:78:a8";
linkConfig.Name = "vms";
};
};
networks = {
"80-vms" = mkMerge [
(networkdAssignment "vms" assignments.routing)
(networkdAssignment "vms" assignments.internal)
];
};
};
my = {
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";
};
};
};
server.enable = true;
firewall = {
tcp.allowed = [ 19999 "http" "https" ];
extraRules = ''
table inet filter {
chain forward {
ip saddr 10.88.0.0/16 accept
}
}
'';
};
};
}
];
};
};
}

View File

@@ -6,7 +6,7 @@ let
cfgFile = pkgs.writeText "gitea-actions-runner.yaml" (toJSON {
container = {
network = "colony";
network = "podman";
privileged = true;
};
cache = {

View File

@@ -1,5 +1,6 @@
{ lib, pkgs, config, assignments, allAssignments, ... }:
let
inherit (lib) mkMerge;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.colony) prefixes;
in
@@ -25,20 +26,25 @@ in
systemd = {
services = {
gitea.preStart =
let
repSec = "${pkgs.replace-secret}/bin/replace-secret";
confPath = "${config.services.gitea.customDir}/conf/app.ini";
in
''
gitea_extra_setup() {
chmod u+w '${confPath}'
${repSec} '#miniosecret#' '${config.age.secrets."gitea/minio.txt".path}' '${confPath}'
chmod u-w '${confPath}'
}
gitea = mkMerge [
(lib.my.systemdAwaitPostgres pkgs.postgresql "colony-psql")
{
preStart =
let
repSec = "${pkgs.replace-secret}/bin/replace-secret";
confPath = "${config.services.gitea.customDir}/conf/app.ini";
in
''
gitea_extra_setup() {
chmod u+w '${confPath}'
${repSec} '#miniosecret#' '${config.age.secrets."gitea/minio.txt".path}' '${confPath}'
chmod u-w '${confPath}'
}
(umask 027; gitea_extra_setup)
'';
(umask 027; gitea_extra_setup)
'';
}
];
};
};
@@ -123,21 +129,6 @@ in
"gitea/minio.txt" = ownedByGit;
};
};
firewall.extraRules = ''
table inet filter {
chain input {
ip saddr ${prefixes.all.v4} tcp dport 3000 accept
ip6 saddr ${prefixes.all.v6} tcp dport 3000 accept
}
}
table inet nat {
chain prerouting {
ip daddr ${assignments.internal.ipv4.address} tcp dport { http, https } dnat to ${allAssignments.middleman.internal.ipv4.address}
ip6 daddr ${assignments.internal.ipv6.address} tcp dport { http, https } dnat to ${allAssignments.middleman.internal.ipv6.address}
}
}
'';
};
};
}

View File

@@ -7,5 +7,6 @@
./jackflix
./object.nix
./toot.nix
./waffletail.nix
];
}

View File

@@ -2,6 +2,7 @@
let
inherit (lib.my) net;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.nginx) baseHttpConfig;
inherit (lib.my.c.colony) domain prefixes;
in
{
@@ -65,6 +66,7 @@ in
owner = "nginx";
group = "nginx";
};
"librespeed.toml" = { };
};
};
@@ -121,6 +123,19 @@ in
baseURL = "https://sso.${pubDomain}";
};
};
librespeed = {
frontend.servers = [
{
name = "Amsterdam, Netherlands";
server = "//librespeed.${domain}";
}
];
backend = {
enable = true;
extraSettingsFile = config.age.secrets."librespeed.toml".path;
};
};
};
users = {
@@ -131,6 +146,10 @@ in
systemd = {
network.networks."80-container-host0" = networkdAssignment "host0" assignments.internal;
services = {
# HACK: nginx seems to get stuck not being able to DNS early...
nginx = lib.my.systemdAwaitPostgres pkgs.postgresql "colony-psql";
};
};
security = {
@@ -231,43 +250,9 @@ in
# Based on recommended*Settings, but probably better to be explicit about these
appendHttpConfig = ''
# NixOS provides a logrotate config that auto-compresses :)
log_format main
'$remote_addr - $remote_user [$time_local] $scheme "$host" "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log main;
${baseHttpConfig}
# optimisation
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# gzip
gzip on;
gzip_proxied any;
gzip_comp_level 5;
gzip_types
application/atom+xml
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
gzip_vary on;
# proxying
proxy_buffering off;
proxy_redirect off;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_http_version 1.1;
${lib.my.c.nginx.proxyHeaders}
resolver_timeout 5s;
# caching
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=4g;

View File

@@ -318,59 +318,12 @@ in
useACMEHost = pubDomain;
};
"toot.nul.ie" =
let
mkAssetLoc = name: {
tryFiles = "$uri =404";
extraConfig = ''
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubpubDomains";
'';
"toot.nul.ie" = {
locations."/" = {
proxyPass = "http://toot-ctr.${domain}:80";
proxyWebsockets = true;
extraConfig = proxyHeaders;
};
in
{
root = "${pkgs.mastodon}/public";
locations = mkMerge [
(genAttrs [
"= /sw.js"
"~ ^/assets/"
"~ ^/avatars/"
"~ ^/emoji/"
"~ ^/headers/"
"~ ^/packs/"
"~ ^/shortcuts/"
"~ ^/sounds/"
] mkAssetLoc)
{
"/".tryFiles = "$uri @proxy";
"^~ /api/v1/streaming" = {
proxyPass = "http://toot-ctr.${domain}:55000";
proxyWebsockets = true;
extraConfig = ''
${proxyHeaders}
proxy_set_header Proxy "";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
'';
};
"@proxy" = {
proxyPass = "http://toot-ctr.${domain}:55001";
proxyWebsockets = true;
extraConfig = ''
${proxyHeaders}
proxy_set_header Proxy "";
proxy_pass_header Server;
proxy_cache CACHE;
proxy_cache_valid 200 7d;
proxy_cache_valid 410 24h;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cached $upstream_cache_status;
'';
};
}
];
useACMEHost = pubDomain;
};
@@ -394,9 +347,41 @@ in
};
useACMEHost = pubDomain;
};
"public.${pubDomain}" = {
serverAliases = [ "p.${pubDomain}" ];
locations."/" = {
root = "/mnt/media/public";
extraConfig = ''
fancyindex on;
fancyindex_show_dotfiles on;
'';
};
useACMEHost = pubDomain;
};
"git.${pubDomain}" = {
locations."/".proxyPass = "http://shill-vm.${domain}:3000";
"mc-map.${pubDomain}" = {
locations."/".proxyPass = "http://simpcraft-oci.${domain}:8100";
useACMEHost = pubDomain;
};
"mc-rail.${pubDomain}" = {
locations."/".proxyPass = "http://simpcraft-staging-oci.${domain}:3876";
useACMEHost = pubDomain;
};
"librespeed.${domain}" = {
locations."/".proxyPass = "http://localhost:8989";
};
"speed.${pubDomain}" = {
locations."/".proxyPass = "http://localhost:8989";
useACMEHost = pubDomain;
};
"md.${pubDomain}" = {
locations."/" = {
proxyPass = "http://object-ctr.${domain}:3000";
proxyWebsockets = true;
extraConfig = proxyHeaders;
};
useACMEHost = pubDomain;
};
};
@@ -434,7 +419,22 @@ in
"s3.${pubDomain}" = {
serverAliases = [ "*.s3.${pubDomain}" ];
inherit extraConfig;
locations."/".proxyPass = s3Upstream;
locations = {
"/".proxyPass = s3Upstream;
"/gitea/packages/" = {
proxyPass = s3Upstream;
# HACK: Docker images need the MIME type to be correct for the manifest but Gitea
# doesn't tell S3... By hiding the header we can use add_header to set Content-Type
# (normally can't be set directly)
extraConfig = ''
proxy_hide_header Content-Type;
add_header Content-Type $upstream_http_content_type always;
if ($args ~ "response-content-disposition=.+filename%3D%22manifest\.json%22") {
add_header Content-Type "application/vnd.docker.distribution.manifest.v2+json";
}
'';
};
};
useACMEHost = pubDomain;
};

View File

@@ -48,11 +48,17 @@ in
group = config.my.user.config.group;
};
"object/atticd.env" = {};
"object/hedgedoc.env" = {};
};
};
firewall = {
tcp.allowed = [ 9000 9001 config.services.sharry.config.bind.port 8069 ];
tcp.allowed = [
9000 9001
config.services.sharry.config.bind.port
8069
config.services.hedgedoc.settings.port
];
};
user.homeConfig = {
@@ -194,6 +200,26 @@ in
};
};
};
hedgedoc = {
enable = true;
environmentFile = config.age.secrets."object/hedgedoc.env".path;
settings = {
domain = "md.${pubDomain}";
protocolUseSSL = true;
db = {
dialect = "postgresql";
username = "hedgedoc";
database = "hedgedoc";
host = "colony-psql";
};
host = "::";
allowAnonymous = false;
allowAnonymousEdits = true;
email = true;
allowEmailRegister = false;
};
};
};
}
(mkIf config.my.build.isDevVM {

View File

@@ -1,5 +1,6 @@
{ lib, ... }:
let
inherit (lib) mkForce;
inherit (lib.my) net;
inherit (lib.my.c.colony) domain prefixes;
in
@@ -54,8 +55,7 @@ in
tcp.allowed = [
19999
config.services.mastodon.webPort
config.services.mastodon.streamingPort
"http"
];
};
};
@@ -78,10 +78,13 @@ in
services = {
netdata.enable = true;
mastodon = mkMerge [
{
rec {
enable = true;
localDomain = "nul.ie";
extraConfig.WEB_DOMAIN = "toot.nul.ie";
localDomain = extraConfig.WEB_DOMAIN; # for nginx config
extraConfig = {
LOCAL_DOMAIN = "nul.ie";
WEB_DOMAIN = "toot.nul.ie";
};
secretKeyBaseFile = config.age.secrets."toot/secret-key.txt".path;
otpSecretFile = config.age.secrets."toot/otp-secret.txt".path;
@@ -90,9 +93,8 @@ in
"vapid-pubkey.txt"
"BAyRyD2pnLQtMHr3J5AzjNMll_HDC6ra1ilOLAUmKyhkEdbm7_OwKZUgw1UefY4CHEcv4OOX9TnnN2DOYYuPZu8=");
enableUnixSocket = false;
configureNginx = false;
trustedProxy = allAssignments.middleman.internal.ipv6.address;
streamingProcesses = 4;
configureNginx = true;
database = {
createLocally = false;
@@ -134,13 +136,31 @@ in
};
}
];
# Override some stuff since we are proxying upstream
nginx = {
recommendedProxySettings = mkForce false;
virtualHosts."${config.services.mastodon.localDomain}" =
let
extraConfig = ''
proxy_set_header Host $host;
'';
in
{
forceSSL = false;
enableACME = false;
locations = {
"@proxy" = { inherit extraConfig; };
"/api/v1/streaming/" = { inherit extraConfig; };
};
};
};
};
}
(mkIf config.my.build.isDevVM {
virtualisation = {
forwardPorts = with config.services.mastodon; [
{ from = "host"; guest.port = webPort; }
{ from = "host"; guest.port = streamingPort; }
];
};
})

View File

@@ -0,0 +1,100 @@
{ lib, ... }:
let
inherit (lib.my) net;
inherit (lib.my.c.colony) domain prefixes;
in
{
nixos.systems.waffletail = { config, ... }: {
system = "x86_64-linux";
nixpkgs = "mine";
rendered = config.configuration.config.my.asContainer;
assignments = {
internal = {
name = "waffletail-ctr";
inherit domain;
ipv4.address = net.cidr.host 9 prefixes.ctrs.v4;
ipv6 = {
iid = "::9";
address = net.cidr.host 9 prefixes.ctrs.v6;
};
};
tailscale = with lib.my.c.tailscale; {
ipv4 = {
address = net.cidr.host 5 prefix.v4;
mask = 32;
gateway = null;
};
ipv6 = {
address = net.cidr.host 5 prefix.v6;
mask = 128;
};
};
};
configuration = { lib, config, assignments, ... }:
let
inherit (lib) concatStringsSep mkMerge mkIf mkForce;
inherit (lib.my) networkdAssignment;
in
{
config = {
my = {
deploy.enable = false;
server.enable = true;
secrets = {
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICZc88lcSQ9zzQzDITdE/T5ty++TxFQUAED7p9YfFBiR";
files = {
"tailscale-auth.key" = {};
};
};
firewall = {
trustedInterfaces = [ "tailscale0" ];
extraRules = ''
table inet filter {
chain forward {
iifname host0 oifname tailscale0 ip saddr ${prefixes.all.v4} accept
iifname host0 oifname tailscale0 ip6 saddr ${prefixes.all.v6} accept
}
}
table inet nat {
chain postrouting {
iifname tailscale0 ip daddr != ${prefixes.all.v4} snat to ${assignments.internal.ipv4.address}
iifname tailscale0 ip6 daddr != ${prefixes.all.v6} snat ip6 to ${assignments.internal.ipv6.address}
}
}
'';
};
};
systemd = {
network.networks."80-container-host0" = networkdAssignment "host0" assignments.internal;
};
services = {
tailscale =
let
advRoutes = concatStringsSep "," (with prefixes.all; [ v4 v6 ]);
in
{
enable = true;
authKeyFile = config.age.secrets."tailscale-auth.key".path;
port = 41641;
openFirewall = true;
interfaceName = "tailscale0";
extraUpFlags = [
"--operator=${config.my.user.config.name}"
"--login-server=https://ts.nul.ie"
"--netfilter-mode=off"
"--advertise-exit-node"
"--advertise-routes=${advRoutes}"
"--accept-routes=false"
];
};
};
};
};
};
}

View File

@@ -1,7 +1,7 @@
{ lib, ... }:
let
inherit (lib.my) net;
inherit (lib.my.c.colony) domain prefixes;
inherit (lib.my) net nft;
inherit (lib.my.c.colony) domain prefixes firewallForwards;
in
{
imports = [ ./containers ];
@@ -49,7 +49,7 @@ in
inherit (lib.my) networkdAssignment;
in
{
imports = [ "${modulesPath}/profiles/qemu-guest.nix" ./gitea.nix ];
imports = [ "${modulesPath}/profiles/qemu-guest.nix" ];
config = mkMerge [
{
@@ -139,6 +139,16 @@ in
ipv6PrefixConfig.Prefix = prefixes.ctrs.v6;
}
];
routes = map (r: { routeConfig = r; }) [
{
Destination = lib.my.c.tailscale.prefix.v4;
Gateway = allAssignments.waffletail.internal.ipv4.address;
}
{
Destination = lib.my.c.tailscale.prefix.v6;
Gateway = allAssignments.waffletail.internal.ipv6.address;
}
];
}
];
};
@@ -151,6 +161,7 @@ in
firewall = {
tcp.allowed = [ 19999 ];
trustedInterfaces = [ "ctrs" ];
nat.forwardPorts."${allAssignments.estuary.internal.ipv4.address}" = firewallForwards allAssignments;
extraRules = ''
table inet filter {
chain forward {
@@ -158,6 +169,17 @@ in
iifname vms oifname ctrs accept
}
}
table inet nat {
# Hack to fix our NAT situation with internal routing
# We need to snat to our public IP, otherwise on the return path from e.g. middleman it will
# try to forward packet directly with its own IP, bypassing our carefully crafted DNAT...
chain ${nft.dnatChain allAssignments.estuary.internal.ipv4.address} {
ct mark set 0x1337
}
chain postrouting {
ct mark 0x1337 snat ip to ${assignments.internal.ipv4.address}
}
}
'';
};
@@ -184,6 +206,7 @@ in
};
};
toot = {};
waffletail = {};
};
in
mkMerge [

View File

@@ -50,6 +50,8 @@ in
};
}) {
valheim-oci = 2;
simpcraft-oci = 3;
simpcraft-staging-oci = 4;
};
configuration = { lib, pkgs, modulesPath, config, assignments, allAssignments, ... }:
@@ -63,7 +65,7 @@ in
"${modulesPath}/profiles/qemu-guest.nix"
./valheim.nix
./gitea-actions.nix
./minecraft
];
config = mkMerge [

View File

@@ -0,0 +1,121 @@
{ lib, pkgs, config, allAssignments, ... }:
let
inherit (lib) concatStringsSep;
inherit (lib.my) dockerNetAssignment;
# devplayer0
op = "6d7d971b-ce10-435b-85c5-c99c0d8d288c";
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
"f105bbe6-eda6-4a13-a8cf-894e77cab77b" # Adzerq
"1fc94979-41fb-497a-81e9-34ae24ca537a" # johnnyscrims
"d53c91df-b6e6-4463-b106-e8427d7a8d01" # BossLonus
"f439f64d-91c9-4c74-9ce5-df4d24cd8e05" # hynge_
"d6ec4c91-5da2-44eb-b89d-71dc8fe017a0" # Eefah98
"096a7348-fabe-4b2d-93fc-fd1fd5608fb0" # ToTheMoonStar
];
fastback = {
gitConfig = pkgs.writeText "git-config" ''
[user]
email = "simpcraft@nul.ie"
name = "Simpcraft bot"
'';
knownHosts = pkgs.writeText "known_hosts" ''
git.nul.ie ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBD023ECzYmLeXIpcGVaciPjq6UN/Sjmsys5HP/Nei5GkrUZqPa3OJ2uSXKLUSKGYdeNhxaFTPJe8Yx3TsZxMme8=
'';
};
in
{
config = {
virtualisation.oci-containers.containers = {
simpcraft = {
image = "ghcr.io/itzg/minecraft-server:2023.12.2-java17-alpine";
environment = {
TYPE = "MODRINTH";
EULA = "true";
ENABLE_QUERY = "true";
MOTD = "§4§k----- §9S§ai§bm§cp§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/cdj2bSKg/Simpcraft-0.1.2.mrpack";
TZ = "Europe/Dublin";
};
volumes = [
"minecraft_data:/data"
"${./icon.png}:/ext/icon.png:ro"
];
extraOptions = [
''--network=colony:${dockerNetAssignment allAssignments "simpcraft-oci"}''
];
};
simpcraft-staging = {
image = "git.nul.ie/dev/craftblock:2024.1.0-java17-alpine";
environment = {
TYPE = "MODRINTH";
EULA = "true";
ENABLE_QUERY = "true";
ENABLE_RCON = "true";
MOTD = "§4§k----- §9S§ai§bm§cp§dc§er§fa§6f§5t [staging] §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 = "4G";
MODRINTH_MODPACK = "https://cdn.modrinth.com/data/CIYf3Hk8/versions/Ym3sIi6H/Simpcraft-0.2.0.mrpack";
TZ = "Europe/Dublin";
};
environmentFiles = [ config.age.secrets."whale2/simpcraft.env".path ];
volumes = [
"minecraft_staging_data:/data"
"${./icon.png}:/ext/icon.png:ro"
"${fastback.gitConfig}:/data/.config/git/config:ro"
"${fastback.knownHosts}:/data/.ssh/known_hosts:ro"
"${config.age.secrets."whale2/simpcraft-git.key".path}:/data/.ssh/id_rsa"
];
extraOptions = [
''--network=colony:${dockerNetAssignment allAssignments "simpcraft-staging-oci"}''
];
};
};
my = {
secrets.files = {
"whale2/simpcraft.env" = {};
"whale2/simpcraft-git.key" = {
owner = "1000";
};
};
};
};
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1,220 @@
{ lib, ... }:
let
inherit (lib.my) net mkVLAN;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.home) domain vlans prefixes vips hiMTU;
in
{
imports = [ ./vms ];
nixos.systems.palace = {
system = "x86_64-linux";
nixpkgs = "mine-stable";
home-manager = "mine-stable";
assignments = {
hi = {
inherit domain;
mtu = hiMTU;
ipv4 = {
address = net.cidr.host 22 prefixes.hi.v4;
mask = 22;
gateway = vips.hi.v4;
};
ipv6 = {
iid = "::2:1";
address = net.cidr.host (65536*2+1) prefixes.hi.v6;
};
};
core = {
inherit domain;
name = "palace-core";
mtu = 1500;
ipv4 = {
address = net.cidr.host 20 prefixes.core.v4;
gateway = null;
};
};
};
configuration = { lib, pkgs, modulesPath, config, systems, assignments, allAssignments, ... }:
let
inherit (lib) mkForce mkMerge;
inherit (lib.my) networkdAssignment;
inherit (lib.my.c) networkd;
in
{
boot = {
kernelPackages = (lib.my.c.kernel.lts pkgs).extend (self: super: {
kernel = super.kernel.override {
structuredExtraConfig = with lib.kernel; {
ACPI_APEI_PCIEAER = yes;
PCIEAER = yes;
};
};
});
kernelModules = [ "kvm-amd" ];
kernelParams = [ "amd_iommu=on" ];
initrd = {
availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" "sr_mod" ];
};
};
hardware = {
enableRedistributableFirmware = true;
cpu = {
amd.updateMicrocode = true;
};
};
fileSystems = {
"/boot" = {
device = "/dev/disk/by-partuuid/c06a8d24-2af9-4416-bf5e-cfe6defdbd47";
fsType = "vfat";
};
"/nix" = {
device = "/dev/disk/by-uuid/450e1f72-238a-4160-98b8-b5e6d0d6fdf6";
fsType = "ext4";
};
"/persist" = {
device = "/dev/disk/by-uuid/9d6d53a8-dff8-49e0-9bc3-fb5f7c6760d0";
fsType = "ext4";
neededForBoot = true;
};
};
services = {
lvm = {
boot.thin.enable = true;
dmeventd.enable = true;
};
smartd = {
enable = true;
autodetect = true;
extraOptions = [ "-A /var/log/smartd/" "--interval=600" ];
};
udev.extraRules = ''
ACTION=="add", SUBSYSTEM=="net", ENV{ID_NET_DRIVER}=="mlx5_core", ENV{ID_PATH}=="pci-0000:44:00.0", ATTR{device/sriov_numvfs}="3"
'';
};
environment.systemPackages = with pkgs; [
pciutils
usbutils
partclone
lm_sensors
linuxPackages.cpupower
smartmontools
mstflint
ethtool
hwloc
];
networking = { inherit domain; };
systemd = {
tmpfiles.rules = [
"d /var/log/smartd 0755 root root"
];
network = {
links = {
"10-et1g0" = {
matchConfig = {
PermanentMACAddress = "e0:d5:5e:68:0c:6e";
Driver = "igb";
};
linkConfig.Name = "et1g0";
};
"10-lan-core" = {
matchConfig.PermanentMACAddress = "e0:d5:5e:68:0c:70";
linkConfig.Name = "lan-core";
};
"10-et100g" = {
matchConfig = {
PermanentMACAddress = "24:8a:07:ac:59:c0";
Driver = "mlx5_core";
};
linkConfig = {
Name = "et100g";
MTUBytes = toString hiMTU;
};
};
};
netdevs = mkMerge [
(mkVLAN "lan-hi" vlans.hi)
(mkVLAN "lan-lo-phy" vlans.lo)
{
"25-lan-lo".netdevConfig = {
Name = "lan-lo";
Kind = "bridge";
};
}
];
networks = {
"50-lan-core" = mkMerge [
(networkdAssignment "lan-core" assignments.core)
{
matchConfig.Name = "lan-core";
vlan = [ "lan-lo-phy" ];
networkConfig.IPv6AcceptRA = mkForce false;
}
];
"50-et100g" = {
matchConfig.Name = "et100g";
vlan = [ "lan-hi" ];
networkConfig = networkd.noL3;
linkConfig.RequiredForOnline = "no";
extraConfig = ''
# cellar
[SR-IOV]
VirtualFunction=0
VLANId=${toString vlans.hi}
LinkState=yes
MACAddress=52:54:00:cc:3e:70
# river
[SR-IOV]
VirtualFunction=1
LinkState=yes
MACAddress=52:54:00:8a:8a:f2
# sfh
[SR-IOV]
VirtualFunction=2
VLANId=${toString vlans.hi}
LinkState=yes
MACAddress=52:54:00:ac:15:a9
'';
};
"60-lan-hi" = networkdAssignment "lan-hi" assignments.hi;
"50-lan-lo-phy" = {
matchConfig.Name = "lan-lo-phy";
networkConfig = {
Bridge = "lan-lo";
} // networkd.noL3;
};
"60-lan-lo" = {
matchConfig.Name = "lan-lo";
linkConfig.RequiredForOnline = "no";
networkConfig = networkd.noL3;
};
};
};
};
my = {
secrets = {
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHzVJpoDz/AAYLJGzU8t6DgZ2sY3oehRqrlSO7C+GWiK";
};
server.enable = true;
deploy.node.hostname = "192.168.68.22";
};
};
};
}

View File

@@ -0,0 +1,98 @@
{ lib, ... }:
let
inherit (lib.my) net;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.home) domain prefixes vips hiMTU;
in
{
nixos.systems.cellar = {
system = "x86_64-linux";
nixpkgs = "mine";
assignments = {
hi = {
inherit domain;
mtu = hiMTU;
ipv4 = {
address = net.cidr.host 80 prefixes.hi.v4;
mask = 22;
gateway = vips.hi.v4;
};
ipv6 = {
iid = "::4:1";
address = net.cidr.host (65536*4+1) prefixes.hi.v6;
};
};
};
configuration = { lib, pkgs, modulesPath, config, assignments, allAssignments, ... }:
let
inherit (lib) mkMerge;
inherit (lib.my) networkdAssignment;
in
{
imports = [
"${modulesPath}/profiles/qemu-guest.nix"
./spdk.nix
];
config = mkMerge [
{
boot = {
kernelParams = [ "console=ttyS0,115200n8" "intel_iommu=on" ];
};
fileSystems = {
"/boot" = {
device = "/dev/disk/by-partuuid/f7562ee6-34c1-4e94-8ae7-c6e71794d563";
fsType = "vfat";
};
"/nix" = {
device = "/dev/disk/by-uuid/f31f6abd-0832-4014-a761-f3c3126d5739";
fsType = "ext4";
};
"/persist" = {
device = "/dev/disk/by-uuid/620364e3-3a30-4704-be80-8593516e7482";
fsType = "ext4";
neededForBoot = true;
};
};
networking = { inherit domain; };
environment.systemPackages = with pkgs; [
pciutils
partclone
];
services = {
netdata.enable = true;
};
systemd.network = {
links = {
"10-lan-hi" = {
matchConfig.PermanentMACAddress = "52:54:00:cc:3e:70";
linkConfig.Name = "lan-hi";
};
};
networks = {
"80-lan-hi" = networkdAssignment "lan-hi" assignments.hi;
};
};
my = {
secrets.key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDcklmJp8xVRddNDU1DruKV+Ipim3Jtl6nE1oCWmpmZH";
server.enable = true;
deploy.node.hostname = "192.168.68.80";
firewall = {
tcp.allowed = [ 19999 ];
};
};
}
];
};
};
}

View File

@@ -0,0 +1,137 @@
{ lib, pkgs, config, assignments, ... }:
let
inherit (lib) mapAttrsToList;
in
{
config = {
boot.blacklistedKernelModules = [ "nvme" ];
systemd.services = {
spdk-tgt.after = [ "systemd-networkd-wait-online@lan-hi.service" ];
};
my = {
spdk = {
enable = true;
extraArgs = "--mem-channels 2 --cpumask 0xffff";
debugCommands = ''
spdk-rpc bdev_nvme_attach_controller -t pcie -a 02:00.0 -b NVMe0
spdk-rpc bdev_nvme_attach_controller -t pcie -a 03:00.0 -b NVMe1
spdk-rpc bdev_nvme_attach_controller -t pcie -a 04:00.0 -b NVMe2
spdk-rpc bdev_raid_create -n NVMeRaid -z 64 -r 0 -b 'NVMe0n1 NVMe1n1 NVMe2n1'
spdk-rpc ublk_create_target
spdk-rpc ublk_start_disk NVMeRaid 1
'';
config.subsystems =
let
nvmeAttaches = mapAttrsToList (name: bdf: {
method = "bdev_nvme_attach_controller";
params = {
hostnqn =
"nqn.2014-08.org.nvmexpress:uuid:2b16606f-b82c-49f8-9b20-a589dac8b775";
trtype = "PCIe";
inherit name;
traddr = bdf;
};
}) {
"NVMe0" = "02:00.0";
"NVMe1" = "03:00.0";
"NVMe2" = "04:00.0";
};
nvmfListener = nqn: {
method = "nvmf_subsystem_add_listener";
params = {
inherit nqn;
listen_address = {
adrfam = "IPv4";
traddr = assignments.hi.ipv4.address;
trsvcid = "4420";
trtype = "RDMA";
};
secure_channel = false;
};
};
nvmfBdev = { nqn, hostnqn, bdev, serial }: [
{
method = "nvmf_create_subsystem";
params = {
inherit nqn;
serial_number = serial;
};
}
(nvmfListener nqn)
{
method = "nvmf_subsystem_add_host";
params = {
inherit nqn;
host = hostnqn;
};
}
{
method = "nvmf_subsystem_add_ns";
params = {
inherit nqn;
namespace = {
bdev_name = bdev;
nsid = 1;
};
};
}
];
in
{
scheduler = [
{
method = "framework_set_scheduler";
params.name = "dynamic";
}
];
bdev = [
{
method = "bdev_set_options";
params.bdev_auto_examine = false;
}
] ++ nvmeAttaches ++ [
{
method = "bdev_raid_create";
params = {
base_bdevs = [ "NVMe0n1" "NVMe1n1" "NVMe2n1" ];
name = "NVMeRaid";
raid_level = "raid0";
strip_size_kb = 64;
};
}
{
method = "bdev_examine";
params.name = "NVMeRaid";
}
{ method = "bdev_wait_for_examine"; }
];
nvmf = [
{
method = "nvmf_create_transport";
params.trtype = "RDMA";
}
(nvmfListener "nqn.2014-08.org.nvmexpress.discovery")
] ++ (nvmfBdev {
bdev = "NVMeRaidp1";
nqn = "nqn.2016-06.io.spdk:river";
hostnqn =
"nqn.2014-08.org.nvmexpress:uuid:12b52d80-ccb6-418d-9b2e-2be34bff3cd9";
serial = "SPDK00000000000001";
}) ++ (nvmfBdev {
bdev = "NVMeRaidp2";
nqn = "nqn.2016-06.io.spdk:castle";
hostnqn =
"nqn.2014-08.org.nvmexpress:uuid:2230b066-a674-4f45-a1dc-f7727b3a9e7b";
serial = "SPDK00000000000002";
});
};
};
};
};
}

View File

@@ -0,0 +1,191 @@
{
imports = [
./cellar
./river.nix
];
nixos.systems.palace.configuration = { lib, pkgs, config, systems, allAssignments, ... }:
let
inherit (lib) mkMerge;
inherit (lib.my) vm;
inherit (lib.my.c) networkd;
installerDisk = {
name = "installer";
backend = {
driver = "file";
filename = "/persist/home/dev/nixos-installer-devplayer0.iso";
read-only = "on";
};
format.driver = "raw";
frontend = "ide-cd";
frontendOpts = {
bootindex = 1;
};
};
in
{
systemd.network = {
netdevs = {
"25-vm-et1g0" = {
netdevConfig = {
Name = "vm-et1g0";
Kind = "macvtap";
};
# TODO: Upstream this missing section
extraConfig = ''
[MACVTAP]
Mode=passthru
'';
};
};
networks = {
"75-et1g0" = {
matchConfig.Name = "et1g0";
linkConfig.RequiredForOnline = "no";
networkConfig = {
MACVTAP = "vm-et1g0";
} // networkd.noL3;
};
"75-vm-et1g0" = {
matchConfig.Name = "vm-et1g0";
linkConfig.RequiredForOnline = "no";
networkConfig = networkd.noL3;
};
};
};
systemd.services =
let
awaitCellar = {
after = [ "vm@cellar.service" ];
bindsTo = [ "vm@cellar.service" ];
preStart = ''
until ${pkgs.netcat}/bin/nc -w1 -z ${allAssignments.cellar.hi.ipv4.address} 22; do
sleep 1
done
'';
};
in
{
"vm@cellar" = {
serviceConfig = {
CPUAffinity = "numa";
NUMAPolicy = "bind";
NUMAMask = "1";
};
};
"vm@river" =
let
vtapUnit = "sys-subsystem-net-devices-vm\\x2det1g0.device";
in
mkMerge [
awaitCellar
{
requires = [ vtapUnit ];
after = [ vtapUnit ];
}
];
"vm@sfh" = awaitCellar;
};
my = {
vms = {
instances = {
cellar = {
uuid = "b126d135-9fc1-415a-b675-aaf727bf2f38";
cpu = "host,topoext";
smp = {
cpus = 8;
threads = 2;
};
memory = 16384;
cleanShutdown.timeout = 120;
drives = [
(mkMerge [ (vm.disk "cellar" "esp") { frontendOpts.bootindex = 0; } ])
(vm.disk "cellar" "nix")
(vm.disk "cellar" "persist")
];
hostDevices = {
et100g0vf0 = {
index = 0;
hostBDF = "44:00.1";
};
nvme0 = {
index = 1;
hostBDF = "41:00.0";
};
nvme1 = {
index = 2;
hostBDF = "42:00.0";
};
nvme2 = {
index = 3;
hostBDF = "43:00.0";
};
};
qemuFlags = [
"machine kernel-irqchip=split"
"device intel-iommu,caching-mode=on,device-iotlb=on,intremap=on"
];
};
river = {
uuid = "12b52d80-ccb6-418d-9b2e-2be34bff3cd9";
cpu = "host,topoext";
smp = {
cpus = 3;
threads = 2;
};
memory = 4096;
cleanShutdown.timeout = 60;
networks = {
et1g0 = {
ifname = "vm-et1g0";
bridge = null;
tapFD = 100;
# Real hardware MAC
mac = "e0:d5:5e:68:0c:6e";
waitOnline = false;
};
};
drives = [
installerDisk
(mkMerge [ (vm.disk "river" "esp") { frontendOpts.bootindex = 0; } ])
];
hostDevices = {
et100g0vf1 = {
index = 0;
hostBDF = "44:00.2";
};
};
};
sfh = {
uuid = "82ec149d-577c-421a-93e2-a9307c756cd8";
cpu = "host,topoext";
smp = {
cpus = 8;
threads = 2;
};
memory = 32768;
cleanShutdown.timeout = 120;
networks.netboot = {
bridge = "lan-lo";
waitOnline = "carrier";
mac = "52:54:00:a5:7e:93";
extraOptions.bootindex = 1;
};
hostDevices = {
et100g0vf2 = {
index = 0;
hostBDF = "44:00.3";
};
};
};
};
};
};
};
}

View File

@@ -0,0 +1,142 @@
{
imports = [ (import ../../routing-common 0) ];
config.nixos.systems.river = {
system = "x86_64-linux";
nixpkgs = "mine";
home-manager = "mine";
configuration = { lib, modulesPath, pkgs, config, assignments, allAssignments, ... }:
let
inherit (lib.my) networkdAssignment mkVLAN;
inherit (lib.my.c) networkd;
inherit (lib.my.c.home) vlans;
lanLink = {
matchConfig = {
Driver = "mlx5_core";
PermanentMACAddress = "52:54:00:8a:8a:f2";
};
linkConfig = {
Name = "lan";
MTUBytes = toString lib.my.c.home.hiMTU;
};
};
in
{
imports = [
"${modulesPath}/profiles/qemu-guest.nix"
];
config = {
boot = {
kernelModules = [ "kvm-intel" ];
kernelParams = [ "console=ttyS0,115200n8" ];
initrd = {
availableKernelModules = [
"virtio_pci" "ahci" "sr_mod" "virtio_blk"
"ib_core" "ib_uverbs" "mlx5_core" "mlx5_ib" "8021q"
"rdma_cm" "iw_cm" "ib_cm" "nvme_core" "nvme_rdma"
];
kernelModules = [ "dm-snapshot" "nvme-fabrics" ];
systemd = {
extraBin = with pkgs; {
dmesg = "${util-linux}/bin/dmesg";
ip = "${iproute2}/bin/ip";
};
extraConfig = ''
DefaultTimeoutStartSec=50
DefaultDeviceTimeoutSec=50
'';
network = {
enable = true;
wait-online.enable = true;
links."10-lan" = lanLink;
netdevs = mkVLAN "lan-hi" vlans.hi;
networks = {
"20-lan" = {
matchConfig.Name = "lan";
vlan = [ "lan-hi" ];
linkConfig.RequiredForOnline = "no";
networkConfig = networkd.noL3;
};
"30-lan-hi" = networkdAssignment "lan-hi" assignments.hi;
};
};
};
};
};
hardware = {
enableRedistributableFirmware = true;
cpu = {
intel.updateMicrocode = true;
};
};
fileSystems = {
"/boot" = {
device = "/dev/disk/by-partuuid/3ec6c49e-b485-40cb-8eff-315581ac6fe9";
fsType = "vfat";
};
"/nix" = {
device = "/dev/main/nix";
fsType = "ext4";
};
"/persist" = {
device = "/dev/main/persist";
fsType = "ext4";
neededForBoot = true;
};
};
services = {
lvm = {
boot.thin.enable = true;
dmeventd.enable = true;
};
};
systemd.network = {
links = {
"10-wan" = {
matchConfig = {
# Matching against MAC address seems to break VLAN interfaces
# (since they share the same MAC address)
Driver = "virtio_net";
PermanentMACAddress = "e0:d5:5e:68:0c:6e";
};
linkConfig = {
Name = "wan";
RxBufferSize = 4096;
TxBufferSize = 4096;
};
};
"10-lan" = lanLink;
};
# So we don't drop the IP we use to connect to NVMe-oF!
networks."60-lan-hi".networkConfig.KeepConfiguration = "static";
};
my = {
secrets = {
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP9uFa4z9WPuXRFVA+PClQSitQCSPckhKTxo1Hq585Oa";
};
server.enable = true;
nvme = {
uuid = "12b52d80-ccb6-418d-9b2e-2be34bff3cd9";
boot = {
nqn = "nqn.2016-06.io.spdk:river";
address = "192.168.68.80";
};
};
deploy.node.hostname = "192.168.68.1";
};
};
};
};
}

View File

@@ -1,11 +1,12 @@
index: { lib, ... }:
index: { lib, allAssignments, ... }:
let
inherit (builtins) elemAt;
inherit (lib.my) net;
inherit (lib.my) net mkVLAN;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.home) domain vlans prefixes routers;
inherit (lib.my.c.home) domain vlans prefixes vips routers routersPubV4;
name = elemAt routers index;
otherIndex = 1 - index;
in
{
nixos.systems."${name}" = {
@@ -19,14 +20,16 @@ in
core = {
name = "${name}-core";
inherit domain;
mtu = 1500;
ipv4 = {
address = net.cidr.host (index + 1) prefixes.core.v4;
gateway = null;
};
};
hi = {
inherit domain;
name = "${name}-hi";
inherit domain;
mtu = 9000;
ipv4 = {
address = net.cidr.host (index + 1) prefixes.hi.v4;
mask = 22;
@@ -37,6 +40,7 @@ in
lo = {
name = "${name}-lo";
inherit domain;
mtu = 1500;
ipv4 = {
address = net.cidr.host (index + 1) prefixes.lo.v4;
mask = 21;
@@ -47,6 +51,7 @@ in
untrusted = {
name = "${name}-ut";
inherit domain;
mtu = 1500;
ipv4 = {
address = net.cidr.host (index + 1) prefixes.untrusted.v4;
mask = 24;
@@ -54,23 +59,64 @@ in
};
ipv6.address = net.cidr.host (index + 1) prefixes.untrusted.v6;
};
as211024 = {
ipv4 = {
address = net.cidr.host (index + 2) prefixes.as211024.v4;
gateway = null;
};
ipv6 = {
address = net.cidr.host ((1*65536*65536*65536) + index + 1) prefixes.as211024.v6;
gateway = net.cidr.host ((2*65536*65536*65536) + 1) prefixes.as211024.v6;
};
};
};
extraAssignments = {
router-hi.hi = {
name = "router-hi";
inherit domain;
ipv4 = {
address = vips.hi.v4;
mask = 22;
};
ipv6.address = vips.hi.v6;
};
router-lo.lo = {
name = "router-lo";
inherit domain;
ipv4 = {
address = vips.lo.v4;
mask = 21;
};
ipv6.address = vips.lo.v6;
};
router-ut.untrusted = {
name = "router-ut";
inherit domain;
ipv4.address = vips.untrusted.v4;
ipv6.address = vips.untrusted.v6;
};
};
configuration = { lib, pkgs, config, assignments, allAssignments, ... }:
let
inherit (lib) mkIf mkMerge mkForce;
inherit (lib.my) networkdAssignment;
inherit (lib.my.c) networkd;
in
{
imports = map (m: import m index) [
./mstpd.nix
./keepalived.nix
./dns.nix
./radvd.nix
./kea.nix
];
config = {
environment = {
systemPackages = with pkgs; [
ethtool
conntrack-tools
];
};
@@ -95,8 +141,8 @@ in
onState = [ "configured" ];
script = ''
#!${pkgs.runtimeShell}
if [ $IFACE = "wan-phy-ifb" ]; then
${pkgs.iproute2}/bin/tc filter add dev wan-phy 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
'';
};
@@ -106,6 +152,17 @@ in
networking.domain = "h.${pubDomain}";
systemd.services = {
ipsec =
let
waitOnline = "systemd-networkd-wait-online@wan.service";
in
{
after = [ waitOnline ];
requires = [ waitOnline ];
};
};
systemd.network = {
wait-online.enable = false;
config = {
@@ -114,38 +171,12 @@ in
};
};
netdevs =
let
mkVLAN = name: vid: {
"25-${name}" = {
netdevConfig = {
Name = name;
Kind = "vlan";
};
vlanConfig.Id = vid;
};
};
in
mkMerge [
netdevs = mkMerge [
{
"25-wan-phy-ifb".netdevConfig = {
Name = "wan-phy-ifb";
"25-wan-ifb".netdevConfig = {
Name = "wan-ifb";
Kind = "ifb";
};
"25-wan".netdevConfig = {
Name = "wan";
Kind = "bridge";
};
"25-lan" = {
netdevConfig = {
Name = "lan";
Kind = "bridge";
};
extraConfig = ''
[Bridge]
STP=true
'';
};
"30-lan-core".netdevConfig = {
Name = "lan-core";
Kind = "macvlan";
@@ -156,12 +187,11 @@ in
(mkVLAN "lan-hi" vlans.hi)
(mkVLAN "lan-lo" vlans.lo)
(mkVLAN "lan-untrusted" vlans.untrusted)
(mkVLAN "wan-tunnel" vlans.wan)
];
networks =
let
mkVLANConfig = name: mtu:
mkVLANConfig = name:
let
iface = "lan-${name}";
in
@@ -169,54 +199,18 @@ in
"60-${iface}" = mkMerge [
(networkdAssignment iface assignments."${name}")
{
linkConfig.MTUBytes = toString mtu;
dns = [ "127.0.0.1" "::1" ];
domains = [ config.networking.domain ];
networkConfig = {
IPv6AcceptRA = mkForce false;
# IPv6SendRA = true;
};
ipv6SendRAConfig = {
DNS = [
(net.cidr.host 1 prefixes."${name}".v4)
(net.cidr.host 2 prefixes."${name}".v4)
(net.cidr.host 1 prefixes."${name}".v6)
(net.cidr.host 2 prefixes."${name}".v6)
];
Domains = [ config.networking.domain ];
};
ipv6Prefixes = [
{
ipv6PrefixConfig.Prefix = prefixes."${name}".v6;
}
];
networkConfig.IPv6AcceptRA = mkForce false;
}
];
};
in
mkMerge [
{
"50-wan-phy" = {
matchConfig.Name = "wan-phy";
networkConfig.Bridge = "wan";
qdiscConfig = {
Parent = "ingress";
Handle = "0xffff";
};
extraConfig = ''
[CAKE]
Parent=root
Bandwidth=24M
RTTSec=1ms
'';
};
"50-wan-phy-ifb" = {
matchConfig.Name = "wan-phy-ifb";
networkConfig = {
LinkLocalAddressing = "no";
IPv6AcceptRA = false;
LLDP = false;
EmitLLDP = false;
};
"50-wan-ifb" = {
matchConfig.Name = "wan-ifb";
networkConfig = networkd.noL3;
extraConfig = ''
[CAKE]
Bandwidth=235M
@@ -228,12 +222,6 @@ in
CompensationMode=none
'';
};
"50-wan-tunnel" = {
matchConfig.Name = "wan-tunnel";
networkConfig.Bridge = "wan";
linkConfig.MTUBytes = "1500";
};
"50-wan" = mkMerge [
(networkdAssignment "wan" assignments.modem)
{
@@ -241,33 +229,25 @@ in
DHCP = "ipv4";
dns = [ "127.0.0.1" "::1" ];
dhcpV4Config.UseDNS = false;
routes = map (r: { routeConfig = r; }) [
# {
# Destination = prefixes.ctrs.v4;
# Gateway = allAssignments.shill.routing.ipv4.address;
# }
];
qdiscConfig = {
Parent = "ingress";
Handle = "0xffff";
};
extraConfig = ''
[CAKE]
Parent=root
Bandwidth=24M
RTTSec=1ms
'';
}
];
"50-lan-jim" = {
matchConfig.Name = "lan-jim";
networkConfig.Bridge = "lan";
};
"50-lan-dave" = {
matchConfig.Name = "lan-dave";
networkConfig.Bridge = "lan";
};
"55-lan" = {
matchConfig.Name = "lan";
vlan = [ "lan-hi" "lan-lo" "lan-untrusted" "wan-tunnel" ];
macvlan = [ "lan-core" ];
networkConfig = {
LinkLocalAddressing = "no";
IPv6AcceptRA = false;
LLDP = false;
EmitLLDP = false;
};
networkConfig = networkd.noL3;
};
"60-lan-core" = mkMerge [
(networkdAssignment "lan-core" assignments.core)
@@ -276,23 +256,60 @@ in
networkConfig.IPv6AcceptRA = mkForce false;
}
];
"90-l2mesh-as211024" = mkMerge [
(networkdAssignment "as211024" assignments.as211024)
{
matchConfig.Name = "as211024";
networkConfig.IPv6AcceptRA = mkForce false;
routes = map (r: { routeConfig = r; }) [
{
Destination = lib.my.c.colony.prefixes.all.v4;
Gateway = allAssignments.estuary.as211024.ipv4.address;
}
{
Destination = lib.my.c.tailscale.prefix.v4;
Gateway = allAssignments.britway.as211024.ipv4.address;
}
{
Destination = lib.my.c.tailscale.prefix.v6;
Gateway = allAssignments.britway.as211024.ipv6.address;
}
];
}
];
}
(mkVLANConfig "hi" 9000)
(mkVLANConfig "lo" 1500)
(mkVLANConfig "untrusted" 1500)
(mkVLANConfig "hi")
(mkVLANConfig "lo")
(mkVLANConfig "untrusted")
{
"60-lan-hi" = {
routes = map (r: { routeConfig = r; }) [
{
Destination = elemAt routersPubV4 otherIndex;
Gateway = net.cidr.host (otherIndex + 1) prefixes.hi.v4;
}
];
};
}
];
};
my = {
secrets = {
files = {
# "estuary/kelder-wg.key" = {
# owner = "systemd-network";
# };
"l2mesh/as211024.key" = {};
};
};
vpns = {
l2.pskFiles = {
as211024 = config.age.secrets."l2mesh/as211024.key".path;
};
};
firewall = {
trustedInterfaces = [ "lan-hi" "lan-lo" ];
udp.allowed = [ 5353 ];
@@ -300,31 +317,30 @@ in
nat = {
enable = true;
externalInterface = "wan";
# externalIP = assignments.internal.ipv4.address;
forwardPorts = [
# {
# port = "http";
# dst = allAssignments.middleman.internal.ipv4.address;
# }
];
};
extraRules =
let
aa = allAssignments;
matchInet = rule: sys: ''
ip daddr ${aa."${sys}".hi.ipv4.address} ${rule}
ip6 daddr ${aa."${sys}".hi.ipv6.address} ${rule}
'';
in
''
table inet filter {
chain input {
${lib.my.c.as211024.nftTrust}
iifname base meta l4proto { udp, tcp } th dport domain accept
iifname lan-core meta l4proto vrrp accept
}
chain routing-tcp {
# Safe enough to allow all SSH
tcp dport ssh accept
ip daddr {
${aa.castle.hi.ipv4.address},
${aa.cellar.hi.ipv4.address},
${aa.palace.hi.ipv4.address}
} tcp dport ssh accept
ip6 daddr {
${aa.castle.hi.ipv6.address},
${aa.cellar.hi.ipv6.address},
${aa.palace.hi.ipv6.address}
} tcp dport ssh accept
return
}
@@ -343,14 +359,17 @@ in
}
chain forward {
${lib.my.c.as211024.nftTrust}
iifname lan-untrusted jump filter-untrusted
iifname { wan, lan-untrusted } oifname { lan-hi, lan-lo } jump filter-routing
iifname { wan, as211024, lan-untrusted } oifname { lan-hi, lan-lo } jump filter-routing
oifname as211024 accept
}
chain output { }
}
table inet nat {
chain prerouting {
${matchInet "meta l4proto { udp, tcp } th dport domain redirect to :5353" name}
ip daddr ${elemAt routersPubV4 index} meta l4proto { udp, tcp } th dport domain redirect to :5353
ip6 daddr ${assignments.as211024.ipv6.address} meta l4proto { udp, tcp } th dport domain redirect to :5353
}
chain postrouting {
oifname wan masquerade

View File

@@ -2,6 +2,7 @@ index: { lib, pkgs, config, assignments, allAssignments, ... }:
let
inherit (builtins) attrNames elemAt;
inherit (lib.my) net;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.home) prefixes vips routers;
name = elemAt routers index;
@@ -22,6 +23,7 @@ in
owner = "pdns-recursor";
group = "pdns-recursor";
};
"home/ddclient-cloudflare.key" = {};
};
pdns.recursor = {
@@ -42,17 +44,13 @@ in
"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 = [
# TODO: IPv6
"0.0.0.0"
# TODO: Dynamic IPv4 WAN address?
# assignments.internal.ipv4.address
# assignments.internal.ipv6.address
# assignments.hi.ipv6.address
"::"
];
forward-zones = map (z: "${z}=127.0.0.1:5353") authZones;
@@ -67,14 +65,42 @@ in
};
};
systemd.services = {
# Add AF_NETLINK to allow pulling IP from network interfaces
pdns.serviceConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
systemd = {
services = {
# Add AF_NETLINK to allow pulling IP from network interfaces
pdns.serviceConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
ddns-update = {
description = "DNS update script";
after = [ "network.target" ];
path = [
(pkgs.python3.withPackages (ps: [ ps.cloudflare ]))
pkgs.ldns
];
serviceConfig = {
Type = "oneshot";
ExecStart =
''${./dns_update.py} -k ${config.age.secrets."home/ddclient-cloudflare.key".path} '' +
''${pubDomain} ns${toString (index + 1)}.${config.networking.domain}'';
};
wantedBy = [ "multi-user.target" ];
};
};
timers = {
ddns-update = {
description = "Periodically update DNS";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "5min";
OnUnitInactiveSec = "5min";
};
};
};
};
# For rec_control
environment.systemPackages = with pkgs; [
# For rec_control
pdns-recursor
sqlite
];
my.pdns.auth = {
@@ -97,11 +123,15 @@ in
webserver = true;
webserver-address = "::";
webserver-allow-from = [ "127.0.0.1" "::1" ];
dnsupdate = true;
launch = [ "gsqlite3" ];
gsqlite3-database = "/var/lib/pdns/dynamic.sqlite3";
};
bind.zones =
let
names = [ "core" "hi" "lo" ];
names = [ "core" "hi" "lo" "untrusted" ];
i = toString (index + 1);
in
{
@@ -135,15 +165,30 @@ in
ns1 IN ALIAS ${elemAt routers 0}.${config.networking.domain}.
ns2 IN ALIAS ${elemAt routers 1}.${config.networking.domain}.
dyn IN NS ns1.dyn.h.nul.ie.
dyn IN NS ns2.dyn.h.nul.ie.
ns1.dyn.h.nul.ie. IN ALIAS ${elemAt routers 0}.${config.networking.domain}.
ns2.dyn.h.nul.ie. IN ALIAS ${elemAt routers 1}.${config.networking.domain}.
jim-core IN A ${net.cidr.host 10 prefixes.core.v4}
jim IN A ${net.cidr.host 10 prefixes.hi.v4}
jim IN AAAA ${net.cidr.host (65536+1) prefixes.hi.v6}
jim-lo IN A ${net.cidr.host 10 prefixes.lo.v4}
jim-lo IN AAAA ${net.cidr.host (65536+1) prefixes.lo.v6}
dave-core IN A ${net.cidr.host 11 prefixes.core.v4}
dave IN A ${net.cidr.host 11 prefixes.hi.v4}
dave IN AAAA ${net.cidr.host (65536+2) prefixes.hi.v6}
dave-lo IN A ${net.cidr.host 11 prefixes.lo.v4}
dave-lo IN AAAA ${net.cidr.host (65536+2) prefixes.lo.v6}
;ap0 IN A ${net.cidr.host 12 prefixes.hi.v4}
;ap0 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}
ups IN A ${net.cidr.host 20 prefixes.lo.v4}
palace-kvm IN A ${net.cidr.host 21 prefixes.lo.v4}
${lib.my.dns.fwdRecords {
inherit allAssignments names;

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python3
import argparse
import subprocess
import CloudFlare
def main():
parser = argparse.ArgumentParser(description='Cloudflare DNS update script')
parser.add_argument('-k', '--api-token-file', help='Cloudflare API token file')
parser.add_argument('zone', help='Cloudflare Zone')
parser.add_argument('record', help='Cloudflare record name')
args = parser.parse_args()
address = subprocess.check_output(
['drill', '-Q', '-p5353', '@127.0.0.1', args.record, 'A'],
encoding='utf8').strip()
cf_token = None
if args.api_token_file:
with open(args.api_token_file) as f:
cf_token = f.readline().strip()
cf = CloudFlare.CloudFlare(token=cf_token)
zones = cf.zones.get(params={'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 records, f'Record {args.record} not found in zone {args.zone}'
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})
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,149 @@
index: { lib, pkgs, assignments, ... }:
let
inherit (lib) mkForce;
inherit (lib.my) net;
inherit (lib.my.c.home) domain prefixes vips hiMTU;
dns-servers = [
{
ip-address = net.cidr.host 1 prefixes.core.v4;
port = 5353;
}
{
ip-address = net.cidr.host 2 prefixes.core.v4;
port = 5353;
}
];
in
{
users = with lib.my.c.ids; {
users.kea= {
isSystemUser = true;
uid = uids.kea;
group = "kea";
};
groups.kea.gid = gids.kea;
};
systemd.services = {
kea-dhcp4-server.serviceConfig.DynamicUser = mkForce false;
kea-dhcp-ddns-server.serviceConfig.DynamicUser = mkForce false;
};
services = {
kea = {
dhcp4 = {
enable = true;
settings = {
interfaces-config = {
interfaces = [
"lan-hi/${assignments.hi.ipv4.address}"
"lan-lo/${assignments.lo.ipv4.address}"
"lan-untrusted/${assignments.untrusted.ipv4.address}"
];
};
lease-database = {
type = "memfile";
persist = true;
name = "/var/lib/kea/dhcp.leases";
};
option-data = [
{
name = "domain-name";
data = domain;
}
{
name = "domain-search";
data = "${domain}, dyn.${domain}, ${lib.my.c.colony.domain}, ${lib.my.c.britway.domain}";
always-send = true;
}
];
subnet4 = [
{
id = 1;
subnet = prefixes.hi.v4;
interface = "lan-hi";
option-data = [
{
name = "routers";
data = vips.hi.v4;
}
{
name = "domain-name-servers";
data = "${net.cidr.host 1 prefixes.hi.v4}, ${net.cidr.host 2 prefixes.hi.v4}";
}
{
name = "interface-mtu";
data = toString hiMTU;
}
];
pools = [
{
pool = if index == 0
then "192.168.68.120 - 192.168.69.255"
else "192.168.70.0 - 192.168.71.240";
}
];
reservations = [
{
# castle
hw-address = "24:8a:07:a8:fe:3a";
ip-address = net.cidr.host 40 prefixes.hi.v4;
}
];
}
{
id = 2;
subnet = prefixes.lo.v4;
interface = "lan-lo";
option-data = [
{
name = "routers";
data = vips.lo.v4;
}
{
name = "domain-name-servers";
data = "${net.cidr.host 1 prefixes.lo.v4}, ${net.cidr.host 2 prefixes.lo.v4}";
}
];
pools = [
{
pool = if index == 0
then "192.168.72.120 - 192.168.75.255"
else "192.168.76.0 - 192.168.79.240";
}
];
reservations = [
{
# castle
hw-address = "24:8a:07:a8:fe:3a";
ip-address = net.cidr.host 40 prefixes.lo.v4;
}
];
}
];
ddns-send-updates = true;
ddns-replace-client-name = "when-not-present";
ddns-qualifying-suffix = "dyn.${domain}";
ddns-generated-prefix = "ip";
ddns-update-on-renew = true;
dhcp-ddns.enable-updates = true;
};
};
dhcp-ddns = {
enable = true;
settings = {
forward-ddns.ddns-domains = [
{
name = "dyn.${domain}.";
inherit dns-servers;
}
];
};
};
};
};
}

View File

@@ -0,0 +1,52 @@
index: { lib, pkgs, config, ... }:
let
inherit (builtins) attrNames concatMap;
inherit (lib) optional;
inherit (lib.my) net;
inherit (lib.my.c.home) prefixes vips;
vlanIface = vlan: if vlan == "as211024" then vlan else "lan-${vlan}";
vrrpIPs = family: concatMap (vlan: [
{
addr = "${vips.${vlan}.${family}}/${toString (net.cidr.length prefixes.${vlan}.${family})}";
dev = vlanIface vlan;
}
] ++ (optional (family == "v6") {
addr = "fe80::1/64";
dev = vlanIface vlan;
})) (attrNames vips);
mkVRRP = family: routerId: {
state = if index == 0 then "MASTER" else "BACKUP";
interface = "lan-core";
priority = 255 - index;
virtualRouterId = routerId;
virtualIps = vrrpIPs family;
extraConfig = ''
notify_master "${config.systemd.package}/bin/systemctl start radvd.service"
notify_backup "${config.systemd.package}/bin/systemctl stop radvd.service"
'';
};
in
{
services = {
keepalived = {
enable = true;
extraGlobalDefs = ''
vrrp_version 3
nftables keepalived
'';
vrrpInstances = {
v4 = mkVRRP "v4" 51;
v6 = mkVRRP "v6" 52;
};
extraConfig = ''
vrrp_sync_group main {
group {
v4
v6
}
}
'';
};
};
}

View File

@@ -1,4 +1,4 @@
index: { lib, pkgs, ... }:
{ lib, pkgs, ... }:
let
# TODO: Move into nixpkgs
mstpd = pkgs.mstpd.overrideAttrs {

View File

@@ -0,0 +1,28 @@
index: { lib, pkgs, ... }:
let
inherit (lib) mkForce concatMapStringsSep;
inherit (lib.my) net;
inherit (lib.my.c.home) domain prefixes;
mkInterface = name: ''
interface lan-${name} {
AdvSendAdvert on;
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} {};
DNSSL ${domain} dyn.${domain} ${lib.my.c.colony.domain} ${lib.my.c.britway.domain} {};
};
'';
in
{
# To be started by keepalived
systemd.services.radvd.wantedBy = mkForce [ ];
services = {
radvd = {
enable = true;
config = concatMapStringsSep "\n" mkInterface [ "hi" "lo" "untrusted" ];
};
};
}

View File

@@ -11,6 +11,8 @@
inherit (lib);
in
{
imports = [ ./routing-common/mstpd.nix ];
config = {
boot = {
kernelModules = [ "kvm-intel" ];
@@ -42,8 +44,20 @@
};
systemd.network = {
netdevs = {
"25-lan" = {
netdevConfig = {
Name = "lan";
Kind = "bridge";
};
extraConfig = ''
[Bridge]
STP=true
'';
};
};
links = {
"10-wan-phy" = {
"10-wan" = {
matchConfig = {
# Matching against MAC address seems to break VLAN interfaces
# (since they share the same MAC address)
@@ -51,7 +65,7 @@
PermanentMACAddress = "00:f0:cb:ee:ca:dd";
};
linkConfig = {
Name = "wan-phy";
Name = "wan";
RxBufferSize = 4096;
TxBufferSize = 4096;
};
@@ -92,6 +106,16 @@
linkConfig.Name = "et5";
};
};
networks = {
"50-lan-jim" = {
matchConfig.Name = "lan-jim";
networkConfig.Bridge = "lan";
};
"50-lan-dave" = {
matchConfig.Name = "lan-dave";
networkConfig.Bridge = "lan";
};
};
};
my = {
@@ -99,7 +123,7 @@
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPYTB4zeAqotrEJ8M+AiGm/s9PFsWlAodz3hYSROGuDb";
};
server.enable = true;
deploy.node.hostname = "192.168.72.2";
deploy.node.hostname = "192.168.68.2";
};
};
};

View File

@@ -92,7 +92,7 @@ in
nextcloud = {
enable = true;
package = pkgs.nextcloud27;
package = pkgs.nextcloud28;
datadir = "/mnt/storage/nextcloud";
hostName = "cloud.${domain}";
https = true;

View File

@@ -2,7 +2,7 @@
let
inherit (builtins) mapAttrs;
inherit (lib) mkMerge mkIf mkDefault;
inherit (lib.my.c.nginx) proxyHeaders;
inherit (lib.my.c.nginx) baseHttpConfig proxyHeaders;
inherit (lib.my.c.kelder) domain;
in
{
@@ -39,43 +39,7 @@ in
# Based on recommended*Settings, but probably better to be explicit about these
appendHttpConfig = ''
# NixOS provides a logrotate config that auto-compresses :)
log_format main
'$remote_addr - $remote_user [$time_local] $scheme "$host" "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log main;
# optimisation
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# gzip
gzip on;
gzip_proxied any;
gzip_comp_level 5;
gzip_types
application/atom+xml
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
gzip_vary on;
# proxying
proxy_buffering off;
proxy_redirect off;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_http_version 1.1;
${proxyHeaders}
${baseHttpConfig}
# caching
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=4g;

View File

@@ -54,7 +54,7 @@ in
efi.canTouchEfiVariables = true;
timeout = 5;
};
kernelPackages = pkgs.linuxKernel.packages.linux_6_1;
kernelPackages = lib.my.c.kernel.lts pkgs;
kernelModules = [ "kvm-intel" ];
kernelParams = [ "intel_iommu=on" ];
initrd = {
@@ -135,12 +135,14 @@ in
samba-wsdd.enable = true;
minecraft-server = {
enable = true;
package = pkgs.minecraftServers.vanilla-1-19;
enable = false;
package = pkgs.minecraftServers.vanilla-1-20;
declarative = true;
eula = true;
whitelist = {
devplayer0 = "6d7d971b-ce10-435b-85c5-c99c0d8d288c";
Elderlypug = "dcd2ecb9-2b5e-49cb-9d4f-f5a76162df56";
shr3kas0ras = "1d366062-12c0-4e29-aba7-6ab5d8c6bb05";
};
serverProperties = {
motd = "Simpcraft";

View File

@@ -25,23 +25,20 @@
efi.canTouchEfiVariables = true;
timeout = 10;
};
kernelPackages = pkgs.linuxKernel.packages.linux_6_5;
kernelPackages = lib.my.c.kernel.latest pkgs;
kernelModules = [ "kvm-intel" ];
kernelParams = [ "intel_iommu=on" ];
initrd = {
availableKernelModules = [ "nvme" "xhci_pci" "usb_storage" "usbhid" "thunderbolt" ];
luks = {
reusePassphrases = true;
devices = {
persist = {
device = "/dev/disk/by-uuid/27840c6f-445c-4b95-8c39-e69d07219f33";
allowDiscards = true;
preLVM = false;
};
home = {
device = "/dev/disk/by-uuid/c16c5038-7883-42c3-960a-a085a99364eb";
allowDiscards = true;
preLVM = false;
};
};
};
@@ -110,10 +107,19 @@
fprintd.enable = true;
blueman.enable = true;
tailscale = {
enable = true;
openFirewall = true;
};
};
programs = {
steam.enable = true;
wireshark = {
enable = true;
package = pkgs.wireshark-qt;
};
};
networking = {
@@ -169,6 +175,14 @@
packages = with pkgs; [ ];
};
programs = {
fish = {
shellAbbrs = {
tsup = "doas tailscale up --login-server=https://ts.nul.ie --accept-routes";
};
};
};
services = {
blueman-applet.enable = true;
};

View File

@@ -1,4 +1,4 @@
{ lib, pkgsFlakes, hmFlakes, inputs, pkgs', config, ... }:
{ self, lib, pkgsFlakes, hmFlakes, inputs, pkgs', config, ... }:
let
inherit (builtins) attrValues mapAttrs;
inherit (lib)
@@ -25,10 +25,14 @@ let
modules' = [ hmFlakes.${config'.home-manager}.nixosModule ] ++ (attrValues cfg.modules);
in
pkgsFlake.lib.nixosSystem {
# Import eval-config ourselves since the flake now force-sets lib
import "${pkgsFlake}/nixos/lib/eval-config.nix" {
# Gotta override lib here unforunately, eval-config.nix likes to import its own (unextended) lib. We explicitly
# don't pass pkgs so that it'll be imported with modularly applied config and overlays.
lib = pkgs.lib;
lib = pkgs.lib.extend (lib.my.versionOverlay { inherit self pkgsFlake; });
# Set to null since we pass modularly
system = null;
# Put the inputs in specialArgs to avoid infinite recursion when modules try to do imports
specialArgs = { inherit inputs pkgsFlakes pkgsFlake allAssignments; inherit (cfg) systems; };
@@ -51,7 +55,7 @@ let
pkgs' = allPkgs;
};
system.name = name;
system = { inherit name; };
networking = {
domain = let d = config'.assignments.internal.domain or null; in mkIf (d != null) (mkDefault' d);
hostName = mkDefault (config'.assignments.internal.name or name);
@@ -86,6 +90,8 @@ let
pkgsPath = toString pkgsFlakes.${config'.hmNixpkgs};
pkgs' = allPkgs;
};
home.enableNixpkgsReleaseCheck = false;
}
(homeStateVersion config'.home-manager)
];
@@ -100,6 +106,7 @@ let
altNames = mkOpt' (listOf str) [ ] "Extra names to assign.";
visible = mkBoolOpt' true "Whether or not this assignment should be visible.";
domain = mkOpt' (nullOr str) null "Domain for this assignment.";
mtu = mkOpt' (nullOr ints.unsigned) null "Interface MTU.";
ipv4 = {
address = mkOpt' net.types.ipv4 null "IPv4 address.";
mask = mkOpt' ints.u8 24 "Network mask.";
@@ -125,6 +132,9 @@ let
l2MeshOpts = with lib.types; { name, ... }: {
options = {
interface = mkOpt' str name "Name of VXLAN interface.";
ipv6 = mkBoolOpt' false "Whether this mesh's underlay operates over IPv6.";
baseMTU = mkOpt' ints.unsigned 1500 "Base MTU to calculate VXLAN MTU with.";
l3Overhead = mkOpt' ints.unsigned 40 "Overhead of L3 header (to calculate MTU).";
firewall = mkBoolOpt' true "Whether to generate firewall rules.";
vni = mkOpt' ints.unsigned 1 "VXLAN VNI.";
peers = mkOpt' (attrsOf (submodule l2PeerOpts)) { } "Peers.";
@@ -181,6 +191,11 @@ in
secretsPath = mkOpt' path null "Path to encrypted secret files.";
modules = mkOpt' (attrsOf commonOpts.moduleType) { } "NixOS modules to be exported by nixfiles.";
systems = mkOpt' (attrsOf (submodule systemOpts)) { } "NixOS systems to be exported by nixfiles.";
allAssignments = mkOption {
type = attrsOf (attrsOf (submodule assignmentOpts));
description = "All network assignments.";
readOnly = true;
};
vpns = {
l2 = mkOpt' (attrsOf (submodule l2MeshOpts)) { } "Layer 2 meshes.";
};
@@ -206,5 +221,9 @@ in
message = "Duplicate assignments: ${toString dupIPs}";
}
];
nixos = {
inherit allAssignments;
};
};
}

View File

@@ -32,7 +32,8 @@
};
isoImage = {
isoBaseName = "nixos-installer-devplayer0";
isoBaseName = "jackos-installer";
volumeID = "jackos-${config.system.nixos.release}-${pkgs.stdenv.hostPlatform.uname.processor}";
edition = "devplayer0";
appendToMenuLabel = " /dev/player0 Installer";
};

View File

@@ -17,5 +17,8 @@
gui = ./gui.nix;
l2mesh = ./l2mesh.nix;
borgthin = ./borgthin.nix;
nvme = ./nvme;
spdk = ./spdk.nix;
librespeed = ./librespeed;
};
}

View File

@@ -18,6 +18,9 @@ let
"${modulesPath}/installer/cd-dvd/iso-image.nix"
allHardware
{
# Doesn't work right now... (missing /dev/root)
boot.initrd.systemd.enable = false;
isoImage = {
makeEfiBootable = true;
makeUsbBootable = true;
@@ -40,6 +43,16 @@ let
modules = flatten [
"${modulesPath}/installer/netboot/netboot.nix"
allHardware
({ pkgs, config, ... }: {
system.build.netbootArchive = pkgs.runCommand "netboot-${config.system.name}-archive.tar" { } ''
${pkgs.gnutar}/bin/tar -rvC "${config.system.build.kernel}" \
-f "$out" "${config.system.boot.loader.kernelFile}"
${pkgs.gnutar}/bin/tar -rvC "${config.system.build.netbootRamdisk}" \
-f "$out" initrd
${pkgs.gnutar}/bin/tar -rvC "${config.system.build.netbootIpxeScript}" \
-f "$out" netboot.ipxe
'';
})
];
};
@@ -79,6 +92,7 @@ in
};
isoImage = {
isoBaseName = dummyOption;
volumeID = dummyOption;
edition = dummyOption;
appendToMenuLabel = dummyOption;
};
@@ -96,6 +110,7 @@ in
iso = config.my.asISO.config.system.build.isoImage;
container = config.my.asContainer.config.system.build.toplevel;
kexecTree = config.my.asKexecTree.config.system.build.kexecTree;
netbootArchive = config.my.asKexecTree.config.system.build.netbootArchive;
};
};
};

View File

@@ -1,7 +1,7 @@
{ lib, pkgs, pkgs', inputs, config, ... }:
let
inherit (lib) mkIf mkDefault mkMerge;
inherit (lib.my) mkBoolOpt' dummyOption;
inherit (lib.my) mkDefault';
in
{
options = with lib.types; {
@@ -88,12 +88,13 @@ in
boot = {
# Use latest LTS release by default
kernelPackages = mkDefault pkgs.linuxKernel.packages.linux_6_1;
kernelPackages = mkDefault (lib.my.c.kernel.lts pkgs);
kernel = {
sysctl = {
"net.ipv6.route.max_size" = mkDefault 16384;
};
};
loader = {
efi = {
efiSysMountPoint = mkDefault "/boot";
@@ -111,12 +112,24 @@ in
memtest86.enable = mkDefault true;
};
};
initrd = {
systemd = {
enable = mkDefault true;
emergencyAccess = mkDefault true;
};
services.lvm.enable = mkDefault true;
};
};
system = {
nixos = {
distroName = mkDefault' "JackOS";
};
};
environment.systemPackages = with pkgs; mkMerge [
[
bash-completion
vim
git
unzip
]
@@ -129,6 +142,7 @@ in
fish.enable = mkDefault true;
# TODO: This is expecting to look up the channel for the database...
command-not-found.enable = mkDefault false;
vim.defaultEditor = true;
};
services = {
@@ -142,6 +156,7 @@ in
font-name=SauceCodePro Nerd Font Mono
'';
};
getty.greetingLine = mkDefault' ''<<< Welcome to ${config.system.nixos.distroName} ${config.system.nixos.label} (\m) - \l >>>'';
openssh = {
enable = mkDefault true;

View File

@@ -208,7 +208,17 @@ in
mkdir -p -m 0755 "$root"/sbin "$root"/etc
touch "$root"/etc/os-release
ln -sf "${containerSystem}"/init "$root"/sbin/init
${if system == sysProfile then ''
if [ -e "${sysProfile}"/prepare-root ]; then
initSource="${containerSystem}"/prepare-root
else
initSource="${containerSystem}"/init
fi
ln -sf "$initSource" "$root"/sbin/init
'' else ''
ln -sf "${containerSystem}/prepare-root" "$root"/sbin/init
''}
'';
postStop =
''

View File

@@ -15,16 +15,20 @@ let
# Based on https://github.com/serokell/deploy-rs/blob/master/flake.nix
nixosActivate = cfg': base: (pkgs.deploy-rs.lib.activate.custom // {
dryActivate = "$PROFILE/bin/switch-to-configuration dry-activate";
boot = "$PROFILE/bin/switch-to-configuration boot";
boot = ''
$PROFILE/bin/switch-to-configuration boot
${keepGensSnippet "$PROFILE" cfg'.keepGenerations}
'';
}) base.config.system.build.toplevel ''
# work around https://github.com/NixOS/nixpkgs/issues/73404
cd /tmp
"$PROFILE"/bin/switch-to-configuration ${cfg'.mode}
"$PROFILE"/bin/switch-to-configuration switch
# https://github.com/serokell/deploy-rs/issues/31
${with base.config.boot.loader;
optionalString ((cfg'.mode == "switch" || cfg'.mode == "boot") && systemd-boot.enable)
optionalString systemd-boot.enable
"sed -i '/^default /d' ${efi.efiSysMountPoint}/loader/loader.conf"}
${keepGensSnippet "$PROFILE" cfg'.keepGenerations}
@@ -59,7 +63,11 @@ let
{
name = "container-${n}";
value = {
path = pkgs.deploy-rs.lib.activate.custom ctrConfig.my.buildAs.container ''
path = (pkgs.deploy-rs.lib.activate.custom // {
boot = ''
echo "Next systemd-nspawn@${n}.service restart / reload will load config"
'';
}) ctrConfig.my.buildAs.container ''
source ${systemdUtil}/bin/systemd-util.sh
${if c.hotReload then ''
if (! systemctl show -p ActiveState systemd-nspawn@${n} | grep -q "ActiveState=active") || \

View File

@@ -1,6 +1,9 @@
{ lib, options, config, ... }:
let
inherit (lib) optionalString concatStringsSep concatMapStringsSep optionalAttrs mkIf mkDefault mkMerge mkOverride;
inherit (builtins) typeOf attrNames;
inherit (lib)
optionalString concatStringsSep concatMapStringsSep mapAttrsToList optionalAttrs mkIf
mkDefault mkMerge mkOverride;
inherit (lib.my) isIPv6 mkOpt' mkBoolOpt';
allowICMP = ''
@@ -63,8 +66,8 @@ in
nat = with options.networking.nat; {
enable = mkBoolOpt' true "Whether to enable IP forwarding and NAT.";
inherit externalInterface externalIP;
forwardPorts = mkOpt' (listOf (submodule forwardOpts)) [ ] "List of port forwards.";
inherit externalInterface;
forwardPorts = mkOpt' (either (listOf (submodule forwardOpts)) (attrsOf (listOf (submodule forwardOpts)))) [ ] "IPv4 port forwards";
};
};
@@ -131,9 +134,15 @@ in
chain prerouting {
type nat hook prerouting priority dstnat;
}
chain output {
type nat hook output priority dstnat;
}
chain postrouting {
type nat hook postrouting priority srcnat;
}
chain input {
type nat hook input priority srcnat;
}
}
${cfg.extraRules}
@@ -141,11 +150,16 @@ in
};
};
}
(mkIf cfg.nat.enable {
(mkIf cfg.nat.enable (
let
iifForward = typeOf cfg.nat.forwardPorts == "list" && cfg.nat.forwardPorts != [ ];
dipForward = typeOf cfg.nat.forwardPorts == "set" && cfg.nat.forwardPorts != { };
in
{
assertions = [
{
assertion = with cfg.nat; (forwardPorts != [ ]) -> (externalInterface != null);
message = "my.firewall.nat.forwardPorts requires my.firewall.nat.external{Interface,IP}";
assertion = with cfg.nat; iifForward -> (externalInterface != null);
message = "my.firewall.nat.forwardPorts as list requires my.firewall.nat.externalInterface";
}
];
@@ -168,43 +182,75 @@ in
my.firewall.extraRules =
let
inherit (lib.my.nft) natFilterChain dnatChain;
ipK = ip: "ip${optionalString (isIPv6 ip) "6"}";
makeFilter = f:
let
v6 = isIPv6 f.dst;
in
"ip${optionalString v6 "6"} daddr ${f.dst} ${f.proto} dport ${toString f.dstPort} accept";
"${ipK f.dst} daddr ${f.dst} ${f.proto} dport ${toString f.dstPort} accept";
makeForward = f:
let
v6 = isIPv6 f.dst;
in
"${f.proto} dport ${toString f.port} dnat ip${optionalString v6 "6"} to ${f.dst}:${toString f.dstPort}";
"${f.proto} dport ${toString f.port} dnat ${ipK f.dst} to ${f.dst}:${toString f.dstPort}";
dnatJumps = ''
${optionalString
iifForward
"iifname ${cfg.nat.externalInterface} jump iif-port-forward"}
${optionalString
dipForward
(concatMapStringsSep "\n " (ip: "${ipK ip} daddr ${ip} jump ${dnatChain ip}") (attrNames cfg.nat.forwardPorts))}
'';
in
''
table inet filter {
chain filter-port-forwards {
${concatMapStringsSep "\n " makeFilter cfg.nat.forwardPorts}
return
}
${optionalString iifForward ''
chain filter-iif-port-forwards {
${concatMapStringsSep "\n " makeFilter cfg.nat.forwardPorts}
return
}
''}
${optionalString
dipForward
(concatStringsSep "\n" (mapAttrsToList (ip: fs: ''
chain ${natFilterChain ip} {
${concatMapStringsSep "\n " makeFilter fs}
return
}
'') cfg.nat.forwardPorts))}
chain forward {
${optionalString
(cfg.nat.externalInterface != null)
"iifname ${cfg.nat.externalInterface} jump filter-port-forwards"}
iifForward
"iifname ${cfg.nat.externalInterface} jump filter-iif-port-forwards"}
${optionalString
dipForward
(concatMapStringsSep "\n " (ip: "jump ${natFilterChain ip}") (attrNames cfg.nat.forwardPorts))}
}
}
table inet nat {
chain port-forward {
${concatMapStringsSep "\n " makeForward cfg.nat.forwardPorts}
return
}
${optionalString iifForward ''
chain iif-port-forward {
${concatMapStringsSep "\n " makeForward cfg.nat.forwardPorts}
return
}
''}
${optionalString
dipForward
(concatStringsSep "\n" (mapAttrsToList (ip: fs: ''
chain ${dnatChain ip} {
${concatMapStringsSep "\n " makeForward fs}
return
}
'') cfg.nat.forwardPorts))}
chain prerouting {
${optionalString
(cfg.nat.externalInterface != null)
"${if (cfg.nat.externalIP != null) then "ip daddr ${cfg.nat.externalIP}" else "iifname ${cfg.nat.externalInterface}"} jump port-forward"}
${dnatJumps}
}
chain output {
${dnatJumps}
}
}
'';
})
}))
]);
meta.buildDocsInSandbox = false;

View File

@@ -57,7 +57,7 @@ in
programs.dconf.enable = true;
fonts.fonts = with pkgs; [
fonts.packages = with pkgs; [
dejavu_fonts
freefont_ttf
gyre-fonts # TrueType substitutes for standard PostScript fonts
@@ -69,8 +69,19 @@ in
xdg = {
portal = {
enable = true;
extraPortals = with pkgs; [
xdg-desktop-portal-gtk
];
# For sway
wlr.enable = true;
configPackages = [
(pkgs.writeTextDir "share/xdg-desktop-portal/sway-portals.conf" ''
[preferred]
default=gtk
org.freedesktop.impl.portal.Screenshot=wlr
org.freedesktop.impl.portal.ScreenCast=wlr
'')
];
};
};
};

View File

@@ -1,7 +1,8 @@
{ lib, pkgs, config, vpns, ... }:
{ lib, config, vpns, ... }:
let
inherit (lib) optionalString mapAttrsToList concatStringsSep filterAttrs mkIf mkMerge;
inherit (lib.my) isIPv6;
inherit (builtins) any attrValues;
inherit (lib) optionalString mapAttrsToList concatStringsSep concatMapStringsSep filterAttrs mkIf mkMerge;
inherit (lib.my) isIPv6 mkOpt';
vxlanPort = 4789;
@@ -24,38 +25,34 @@ let
Local = ownAddr;
MacLearning = true;
DestinationPort = vxlanPort;
PortRange = "${toString vxlanPort}-${toString (vxlanPort + 1)}";
Independent = true;
};
};
links."20-l2mesh-${name}" = {
matchConfig.Name = mesh.interface;
# TODO: ipv6? ipsec?
linkConfig.MTUBytes = "1450";
};
networks."90-l2mesh-${name}" = {
matchConfig.Name = mesh.interface;
extraConfig = concatStringsSep "\n" (mapAttrsToList (n: peer: ''
[BridgeFDB]
MACAddress=00:00:00:00:00:00
Destination=${peer.addr}
'') otherPeers);
linkConfig.MTUBytes =
let
espOverhead =
if (!mesh.security.enable) then 0
else
# SPI + seq + IV + pad / header + ICV
4 + 4 + (if mesh.security.encrypt then 8 else 0) + 2 + 16;
# UDP + VXLAN + Ethernet + L3 (IPv4/IPv6)
overhead = espOverhead + 8 + 8 + 14 + mesh.l3Overhead;
in
toString (mesh.baseMTU - overhead);
bridgeFDBs = mapAttrsToList (n: peer: {
bridgeFDBConfig = {
MACAddress = "00:00:00:00:00:00";
Destination = peer.addr;
};
}) otherPeers;
};
};
mkLibreswanConfig = name: mesh: with info mesh; {
enable = true;
# TODO: finish this...
connections."l2mesh-${name}" = ''
keyexchange=ike
type=transport
left=${ownAddr}
auto=start
phase2=esp
ikev2=yes
'';
};
vxlanAllow = vni: "udp dport ${toString vxlanPort} @th,96,24 ${toString vni} accept";
mkFirewallConfig = name: mesh: with info mesh;
let
netProto = if (isIPv6 ownAddr) then "ip6" else "ip";
@@ -63,8 +60,11 @@ let
''
table inet filter {
chain l2mesh-${name} {
${optionalString mesh.security.enable "meta l4proto esp accept"}
udp dport ${toString vxlanPort} @th,96,24 ${toString mesh.vni} accept
${optionalString mesh.security.enable ''
udp dport isakmp accept
meta l4proto esp accept
''}
${optionalString (!mesh.security.enable) (vxlanAllow mesh.vni)}
return
}
chain input {
@@ -72,12 +72,63 @@ let
}
}
'';
mkLibreswanConfig = name: mesh: with info mesh; {
enable = true;
connections = mkMerge (mapAttrsToList
(pName: peer: {
"l2mesh-${name}-${pName}" = ''
keyexchange=ike
hostaddrfamily=ipv${if mesh.ipv6 then "6" else "4"}
type=transport
left=${ownAddr}
leftprotoport=udp/${toString vxlanPort}
right=${peer.addr}
rightprotoport=udp/${toString vxlanPort}
rightupdown=
auto=start
authby=secret
phase2=esp
esp=${if mesh.security.encrypt then "aes_gcm256" else "null-sha256"}
ikev2=yes
modecfgpull=no
'';
})
otherPeers);
};
genSecrets = name: mesh: with info mesh; concatMapStringsSep "\n" (p: ''
echo "${ownAddr} ${p.addr} : PSK \"$(< "${config.my.vpns.l2.pskFiles.${name}}")\"" >> /run/l2mesh.secrets
'') (attrValues otherPeers);
anySecurity = any (c: c.security.enable) (attrValues memberMeshes);
in
{
options = {
my.vpns.l2 = with lib.types; {
pskFiles = mkOpt' (attrsOf str) { } "PSK files for secured meshes.";
};
};
config = {
systemd.network = mkMerge (mapAttrsToList mkNetConfig memberMeshes);
# TODO: finish this...
#services.libreswan = mkMerge (mapAttrsToList mkLibreswanConfig (filterAttrs (_: c: c.security.enable) memberMeshes));
environment.etc."ipsec.d/l2mesh.secrets" = mkIf anySecurity {
source = "/run/l2mesh.secrets";
};
systemd.services.ipsec = mkIf anySecurity {
preStart = ''
oldUmask="$(umask)"
umask 006
> /run/l2mesh.secrets
${concatStringsSep "\n" (mapAttrsToList genSecrets memberMeshes)}
umask "$oldUmask"
'';
};
services.libreswan = mkMerge (mapAttrsToList mkLibreswanConfig (filterAttrs (_: c: c.security.enable) memberMeshes));
my.firewall.extraRules = concatStringsSep "\n" (mapAttrsToList mkFirewallConfig (filterAttrs (_: c: c.firewall) memberMeshes));
};
}

View File

@@ -0,0 +1,76 @@
{ lib, pkgs, config, ... }:
let
inherit (builtins) toJSON;
inherit (lib) mkOption mkMerge mkIf mkDefault;
inherit (lib.my) mkOpt' mkBoolOpt';
cfg = config.my.librespeed;
serversConf = map (s: s // {
dlURL = "backend/garbage";
ulURL = "backend/empty";
pingURL = "backend/empty";
getIpURL = "backend/getIP";
}) cfg.frontend.servers;
frontendTree = pkgs.runCommand "librespeed-frontend" {
speedtestServers = toJSON serversConf;
} ''
mkdir "$out"
cp "${pkgs.librespeed-go}"/assets/* "$out"/
substitute ${./index.html} "$out"/index.html --subst-var speedtestServers
'';
backendConf = pkgs.writers.writeTOML "librespeed.toml" cfg.backend.settings;
generateBackendSettings = base: dst: if (cfg.backend.extraSettingsFile != null) then ''
oldUmask="$(umask)"
umask 006
cat "${base}" "${cfg.backend.extraSettingsFile}" > "${dst}"
umask "$oldUmask"
'' else ''
cp "${base}" "${dst}"
'';
in
{
options.my.librespeed = with lib.types; {
frontend = {
servers = mkOpt' (listOf (attrsOf unspecified)) { } "Server configs.";
webroot = mkOption {
description = "Frontend webroot.";
type = package;
readOnly = true;
};
};
backend = {
enable = mkBoolOpt' false "Whether to enable librespeed backend.";
settings = mkOpt' (attrsOf unspecified) { } "Backend settings.";
extraSettingsFile = mkOpt' (nullOr str) null "Extra settings file.";
};
};
config = mkMerge [
(mkIf (cfg.frontend.servers != { }) {
my.librespeed.frontend.webroot = frontendTree;
})
(mkIf cfg.backend.enable {
my.librespeed.backend.settings = {
assets_path = frontendTree;
database_type = mkDefault "bolt";
database_file = mkDefault "/var/lib/librespeed-go/speedtest.db";
};
systemd.services.librespeed = {
description = "LibreSpeed Go backend";
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
preStart = generateBackendSettings backendConf "/run/librespeed-go/settings.toml";
serviceConfig = {
ExecStart = "${pkgs.librespeed-go}/bin/speedtest -c /run/librespeed-go/settings.toml";
RuntimeDirectory = "librespeed-go";
StateDirectory = "librespeed-go";
};
wantedBy = [ "multi-user.target" ];
};
})
];
}

View File

@@ -0,0 +1,491 @@
<!DOCTYPE html>
<html>
<head>
<link rel="shortcut icon" href="favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<meta charset="UTF-8" />
<script type="text/javascript" src="speedtest.js"></script>
<script type="text/javascript">
function I(i){return document.getElementById(i);}
//LIST OF TEST SERVERS. See documentation for details if needed
var SPEEDTEST_SERVERS=@speedtestServers@;
// var SPEEDTEST_SERVERS=[
// { //this server doesn't actually exist, remove it
// name:"Example Server 1", //user friendly name for the server
// server:"//test1.mydomain.com/", //URL to the server. // at the beginning will be replaced with http:// or https:// automatically
// dlURL:"backend/garbage.php", //path to download test on this server (garbage.php or replacement)
// ulURL:"backend/empty.php", //path to upload test on this server (empty.php or replacement)
// pingURL:"backend/empty.php", //path to ping/jitter test on this server (empty.php or replacement)
// getIpURL:"backend/getIP.php" //path to getIP on this server (getIP.php or replacement)
// },
// { //this server doesn't actually exist, remove it
// name:"Example Server 2", //user friendly name for the server
// server:"//test2.example.com/", //URL to the server. // at the beginning will be replaced with http:// or https:// automatically
// dlURL:"garbage.php", //path to download test on this server (garbage.php or replacement)
// ulURL:"empty.php", //path to upload test on this server (empty.php or replacement)
// pingURL:"empty.php", //path to ping/jitter test on this server (empty.php or replacement)
// getIpURL:"getIP.php" //path to getIP on this server (getIP.php or replacement)
// }
// //add other servers here, comma separated
// ];
//INITIALIZE SPEEDTEST
var s=new Speedtest(); //create speed test object
s.setParameter("telemetry_level","basic"); //enable telemetry
//SERVER AUTO SELECTION
function initServers(){
var noServersAvailable=function(){
I("message").innerHTML="No servers available";
}
var runServerSelect=function(){
s.selectServer(function(server){
if(server!=null){ //at least 1 server is available
I("loading").className="hidden"; //hide loading message
//populate server list for manual selection
for(var i=0;i<SPEEDTEST_SERVERS.length;i++){
if(SPEEDTEST_SERVERS[i].pingT==-1) continue;
var option=document.createElement("option");
option.value=i;
option.textContent=SPEEDTEST_SERVERS[i].name;
if(SPEEDTEST_SERVERS[i]===server) option.selected=true;
I("server").appendChild(option);
}
//show test UI
I("testWrapper").className="visible";
initUI();
}else{ //no servers are available, the test cannot proceed
noServersAvailable();
}
});
}
if(typeof SPEEDTEST_SERVERS === "string"){
//need to fetch list of servers from specified URL
s.loadServerList(SPEEDTEST_SERVERS,function(servers){
if(servers==null){ //failed to load server list
noServersAvailable();
}else{ //server list loaded
SPEEDTEST_SERVERS=servers;
runServerSelect();
}
});
}else{
//hardcoded server list
s.addTestPoints(SPEEDTEST_SERVERS);
runServerSelect();
}
}
var meterBk=/Trident.*rv:(\d+\.\d+)/i.test(navigator.userAgent)?"#EAEAEA":"#80808040";
var dlColor="#6060AA",
ulColor="#616161";
var progColor=meterBk;
//CODE FOR GAUGES
function drawMeter(c,amount,bk,fg,progress,prog){
var ctx=c.getContext("2d");
var dp=window.devicePixelRatio||1;
var cw=c.clientWidth*dp, ch=c.clientHeight*dp;
var sizScale=ch*0.0055;
if(c.width==cw&&c.height==ch){
ctx.clearRect(0,0,cw,ch);
}else{
c.width=cw;
c.height=ch;
}
ctx.beginPath();
ctx.strokeStyle=bk;
ctx.lineWidth=12*sizScale;
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,Math.PI*0.1);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle=fg;
ctx.lineWidth=12*sizScale;
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,amount*Math.PI*1.2-Math.PI*1.1);
ctx.stroke();
if(typeof progress !== "undefined"){
ctx.fillStyle=prog;
ctx.fillRect(c.width*0.3,c.height-16*sizScale,c.width*0.4*progress,4*sizScale);
}
}
function mbpsToAmount(s){
return 1-(1/(Math.pow(1.3,Math.sqrt(s))));
}
function format(d){
d=Number(d);
if(d<10) return d.toFixed(2);
if(d<100) return d.toFixed(1);
return d.toFixed(0);
}
//UI CODE
var uiData=null;
function startStop(){
if(s.getState()==3){
//speed test is running, abort
s.abort();
data=null;
I("startStopBtn").className="";
I("server").disabled=false;
initUI();
}else{
//test is not running, begin
I("startStopBtn").className="running";
I("shareArea").style.display="none";
I("server").disabled=true;
s.onupdate=function(data){
uiData=data;
};
s.onend=function(aborted){
I("startStopBtn").className="";
I("server").disabled=false;
updateUI(true);
if(!aborted){
//if testId is present, show sharing panel, otherwise do nothing
try{
var testId=uiData.testId;
if(testId!=null){
var shareURL=window.location.href.substring(0,window.location.href.lastIndexOf("/"))+"/results/?id="+testId;
I("resultsImg").src=shareURL;
I("resultsURL").value=shareURL;
I("testId").innerHTML=testId;
I("shareArea").style.display="";
}
}catch(e){}
}
};
s.start();
}
}
//this function reads the data sent back by the test and updates the UI
function updateUI(forced){
if(!forced&&s.getState()!=3) return;
if(uiData==null) return;
var status=uiData.testState;
I("ip").textContent=uiData.clientIp;
I("dlText").textContent=(status==1&&uiData.dlStatus==0)?"...":format(uiData.dlStatus);
drawMeter(I("dlMeter"),mbpsToAmount(Number(uiData.dlStatus*(status==1?oscillate():1))),meterBk,dlColor,Number(uiData.dlProgress),progColor);
I("ulText").textContent=(status==3&&uiData.ulStatus==0)?"...":format(uiData.ulStatus);
drawMeter(I("ulMeter"),mbpsToAmount(Number(uiData.ulStatus*(status==3?oscillate():1))),meterBk,ulColor,Number(uiData.ulProgress),progColor);
I("pingText").textContent=format(uiData.pingStatus);
I("jitText").textContent=format(uiData.jitterStatus);
}
function oscillate(){
return 1+0.02*Math.sin(Date.now()/100);
}
//update the UI every frame
window.requestAnimationFrame=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||(function(callback,element){setTimeout(callback,1000/60);});
function frame(){
requestAnimationFrame(frame);
updateUI();
}
frame(); //start frame loop
//function to (re)initialize UI
function initUI(){
drawMeter(I("dlMeter"),0,meterBk,dlColor,0);
drawMeter(I("ulMeter"),0,meterBk,ulColor,0);
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
}
</script>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#loading{
background-color:#FFFFFF;
color:#404040;
text-align:center;
}
span.loadCircle{
display:inline-block;
width:2em;
height:2em;
vertical-align:middle;
background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAP1BMVEUAAAB2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZyFzwnAAAAFHRSTlMAEvRFvX406baecwbf0casimhSHyiwmqgAAADpSURBVHja7dbJbQMxAENRahnN5lkc//5rDRAkDeRgHszXgACJoKiIiIiIiIiIiIiIiIiIiIj4HHspsrpAVhdVVguzrA4OWc10WcEqpwKbnBo0OU1Q5NSpsoJFTgOecrrdEag85DRgktNqfoEdTjnd7hrEHMEJvmRUYJbTYk5Agy6nau6Abp5Cm7mDBtRdPi9gyKdU7w4p1fsLvyqs8hl4z9/w3n/Hmr9WoQ65lAU4d7lMYOz//QboRR5jBZibLMZdAR6O/Vfa1PlxNr3XdS3HzK/HVPRu/KnLs8iAOh993VpRRERERMT/fAN60wwWaVyWwAAAAABJRU5ErkJggg==');
background-size:2em 2em;
margin-right:0.5em;
animation: spin 0.6s linear infinite;
}
@keyframes spin{
0%{transform:rotate(0deg);}
100%{transform:rotate(359deg);}
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#serverArea{
margin-top:1em;
}
#server{
font-size:1em;
padding:0.2em;
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:16em;
height:12.5em;
position:relative;
box-sizing:border-box;
}
div.testArea2{
display:inline-block;
width:14em;
height:7em;
position:relative;
box-sizing:border-box;
text-align:center;
}
div.testArea div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.testArea2 div.testName{
display:block;
text-align:center;
font-size:1.4em;
}
div.testArea div.meterText{
position:absolute;
bottom:1.55em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
div.testArea2 div.meterText{
display:inline-block;
font-size:2.5em;
}
div.meterText:empty:before{
content:"0.00";
}
div.testArea div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testArea2 div.unit{
display:inline-block;
}
div.testArea canvas{
position:absolute;
top:0; left:0; width:100%; height:100%;
z-index:1;
}
div.testGroup{
display:block;
margin: 0 auto;
}
#shareArea{
width:95%;
max-width:40em;
margin:0 auto;
margin-top:2em;
}
#shareArea > *{
display:block;
width:100%;
height:auto;
margin: 0.25em 0;
}
#privacyPolicy{
position:fixed;
top:2em;
bottom:2em;
left:2em;
right:2em;
overflow-y:auto;
width:auto;
height:auto;
box-shadow:0 0 3em 1em #000000;
z-index:999999;
text-align:left;
background-color:#FFFFFF;
padding:1em;
}
a.privacy{
text-align:center;
font-size:0.8em;
color:#808080;
padding: 0 3em;
}
div.closePrivacyPolicy {
width: 100%;
text-align: center;
}
div.closePrivacyPolicy a.privacy {
padding: 1em 3em;
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
}
div.visible{
animation: fadeIn 0.4s;
display:block;
}
div.hidden{
animation: fadeOut 0.4s;
display:none;
}
@keyframes fadeIn{
0%{
opacity:0;
}
100%{
opacity:1;
}
}
@keyframes fadeOut{
0%{
display:block;
opacity:1;
}
100%{
display:block;
opacity:0;
}
}
</style>
<title>/dev/player0's speedtest</title>
</head>
<body onload="initServers()">
<h1>/dev/player0's speedtest</h1>
<div id="loading" class="visible">
<p id="message"><span class="loadCircle"></span>Selecting a server...</p>
</div>
<div id="testWrapper" class="hidden">
<div id="startStopBtn" onclick="startStop()"></div><br/>
<a class="privacy" href="#" onclick="I('privacyPolicy').style.display=''">Privacy</a>
<div id="serverArea">
Server: <select id="server" onchange="s.setSelectedServer(SPEEDTEST_SERVERS[this.value])"></select>
</div>
<div id="test">
<div class="testGroup">
<div class="testArea2">
<div class="testName">Ping</div>
<div id="pingText" class="meterText" style="color:#AA6060"></div>
<div class="unit">ms</div>
</div>
<div class="testArea2">
<div class="testName">Jitter</div>
<div id="jitText" class="meterText" style="color:#AA6060"></div>
<div class="unit">ms</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<canvas id="dlMeter" class="meter"></canvas>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbit/s</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<canvas id="ulMeter" class="meter"></canvas>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbit/s</div>
</div>
</div>
<div id="ipArea">
<span id="ip"></span>
</div>
<div id="shareArea" style="display:none">
<h3>Share results</h3>
<p>Test ID: <span id="testId"></span></p>
<input type="text" value="" id="resultsURL" readonly="readonly" onclick="this.select();this.focus();this.select();document.execCommand('copy');alert('Link copied')"/>
<img src="" id="resultsImg" />
</div>
</div>
<a href="https://github.com/librespeed/speedtest">Source code</a>
</div>
<div id="privacyPolicy" style="display:none">
<h2>Privacy Policy</h2>
<p>This HTML5 speed test server is configured with telemetry enabled.</p>
<h4>What data we collect</h4>
<p>
At the end of the test, the following data is collected and stored:
<ul>
<li>Test ID</li>
<li>Time of testing</li>
<li>Test results (download and upload speed, ping and jitter)</li>
<li>IP address</li>
<li>ISP information</li>
<li>Approximate location (inferred from IP address, not GPS)</li>
<li>User agent and browser locale</li>
<li>Test log (contains no personal information)</li>
</ul>
</p>
<h4>How we use the data</h4>
<p>
Data collected through this service is used to:
<ul>
<li>Allow sharing of test results (sharable image for forums, etc.)</li>
<li>To improve the service offered to you (for instance, to detect problems on our side)</li>
</ul>
No personal information is disclosed to third parties.
</p>
<h4>Your consent</h4>
<p>
By starting the test, you consent to the terms of this privacy policy.
</p>
<h4>Data removal</h4>
<p>
If you want to have your information deleted, you need to provide either the ID of the test or your IP address. This is the only way to identify your data, without this information we won't be able to comply with your request.<br/><br/>
Contact this email address for all deletion requests: <a href="mailto:dev@nul.ie">dev@nul.ie</a>.
</p>
<br/><br/>
<div class="closePrivacyPolicy">
<a class="privacy" href="#" onclick="I('privacyPolicy').style.display='none'">Close</a>
</div>
<br/>
</div>
</body>
</html>

View File

@@ -0,0 +1,58 @@
{ lib, pkgs, config, ... }:
let
inherit (lib) mkIf;
inherit (lib.my) mkOpt';
cfg = config.my.nvme;
nvme-cli = pkgs.nvme-cli.override {
libnvme = pkgs.libnvme.overrideAttrs (o: {
patches = o.patches ++ [ ./libnvme-hostconf.patch ];
});
};
hostNQN = "nqn.2014-08.org.nvmexpress:uuid:${cfg.uuid}";
etc = prefix: {
"${prefix}nvme/hostnqn".text = hostNQN;
"${prefix}nvme/hostid".text = cfg.uuid;
};
in
{
options.my.nvme = with lib.types; {
uuid = mkOpt' (nullOr str) null "NVMe host ID";
boot = {
nqn = mkOpt' (nullOr str) null "NQN to connect to on boot";
address = mkOpt' str null "Address of NVMe-oF target.";
};
};
config = mkIf (cfg.uuid != null) {
environment = {
systemPackages = [
nvme-cli
];
etc = etc "";
};
boot.initrd.systemd = mkIf (cfg.boot.nqn != null) {
contents = etc "/etc/";
extraBin.nvme = "${nvme-cli}/bin/nvme";
services.connect-nvme = {
description = "Connect NVMe-oF";
before = [ "initrd-root-device.target" ];
after = [ "systemd-networkd-wait-online.service" ];
requires = [ "systemd-networkd-wait-online.service" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${nvme-cli}/bin/nvme connect -t rdma -a ${cfg.boot.address} -n ${cfg.boot.nqn}";
Restart = "on-failure";
RestartSec = 10;
};
wantedBy = [ "initrd-root-device.target" ];
};
};
};
}

View File

@@ -0,0 +1,15 @@
diff --git a/src/nvme/fabrics.c b/src/nvme/fabrics.c
index 21fb292..f9090d1 100644
--- a/src/nvme/fabrics.c
+++ b/src/nvme/fabrics.c
@@ -41,8 +41,8 @@
#define NVMF_HOSTID_SIZE 37
-#define NVMF_HOSTNQN_FILE SYSCONFDIR "/nvme/hostnqn"
-#define NVMF_HOSTID_FILE SYSCONFDIR "/nvme/hostid"
+#define NVMF_HOSTNQN_FILE "/etc/nvme/hostnqn"
+#define NVMF_HOSTID_FILE "/etc/nvme/hostid"
const char *nvmf_dev = "/dev/nvme-fabrics";

View File

@@ -268,6 +268,9 @@ in
# For pdns_control etc
systemPackages = with pkgs; [
pdns
(pkgs.writeShellScriptBin "pu" ''
${pdns}/bin/pdnsutil --config-dir /run/pdns "$@"
'')
pdns-file-record
];

94
nixos/modules/spdk.nix Normal file
View File

@@ -0,0 +1,94 @@
{ lib, pkgs, config, ... }:
let
inherit (builtins) toJSON;
inherit (lib) optional optionalAttrs mapAttrsToList mkIf withFeature;
inherit (lib.my) mkOpt' mkBoolOpt';
rpcOpts = with lib.types; {
options = {
method = mkOpt' str null "RPC method name.";
params = mkOpt' (attrsOf unspecified) { } "RPC params";
};
};
cfg = config.my.spdk;
config' = {
subsystems = mapAttrsToList (subsystem: c: {
inherit subsystem;
config = map (rpc: {
inherit (rpc) method;
} // (optionalAttrs (rpc.params != { }) { inherit (rpc) params; })) c;
}) cfg.config.subsystems;
};
configJSON = pkgs.writeText "spdk-config.json" (toJSON config');
spdk = pkgs.spdk.overrideAttrs (o: {
configureFlags = o.configureFlags ++ (map (withFeature true) [ "rdma" "ublk" ]);
buildInputs = o.buildInputs ++ (with pkgs; [ liburing ]);
});
spdk-rpc = (pkgs.writeShellScriptBin "spdk-rpc" ''
exec ${pkgs.python3}/bin/python3 ${spdk.src}/scripts/rpc.py "$@"
'');
spdk-setup = (pkgs.writeShellScriptBin "spdk-setup" ''
exec ${spdk.src}/scripts/setup.sh "$@"
'');
spdk-debug = pkgs.writeShellApplication {
name = "spdk-debug";
runtimeInputs = [ spdk ];
text = ''
set -m
if [ "$(id -u)" -ne 0 ]; then
echo "I need to be root!"
exit 1
fi
spdk_tgt ${cfg.extraArgs} --wait-for-rpc &
until spdk-rpc spdk_get_version > /dev/null; do
sleep 0.5
done
spdk-rpc bdev_set_options --disable-auto-examine
spdk-rpc framework_start_init
${cfg.debugCommands}
fg %1
'';
};
in
{
options.my.spdk = with lib.types; {
enable = mkBoolOpt' false "Whether to enable SPDK target.";
extraArgs = mkOpt' str "" "Extra arguments to pass to spdk_tgt.";
debugCommands = mkOpt' lines "" "Commands to run with the spdk-debug script.";
config.subsystems = mkOpt' (attrsOf (listOf (submodule rpcOpts))) { } "Subsystem config / RPCs.";
};
config = mkIf cfg.enable {
boot.kernelModules = [ "ublk_drv" ];
environment.systemPackages = [
spdk
spdk-setup
spdk-rpc
] ++ (optional (cfg.debugCommands != "") spdk-debug);
systemd.services = {
spdk-tgt = {
description = "SPDK target";
path = with pkgs; [
bash
python3
kmod
gawk
util-linux
];
serviceConfig = {
ExecStartPre = "${spdk.src}/scripts/setup.sh";
ExecStart = "${spdk}/bin/spdk_tgt ${cfg.extraArgs} -c ${configJSON}";
};
wantedBy = [ "multi-user.target" ];
};
};
};
}

View File

@@ -187,6 +187,9 @@ in
(mkIf config.networking.resolvconf.enable {
my.tmproot.unsaved.ignore = [ "/etc/resolv.conf" ];
})
(mkIf config.networking.nftables.enable {
my.tmproot.unsaved.ignore = [ "/var/lib/nftables/deletions.nft" ];
})
(mkIf config.security.doas.enable {
my.tmproot.unsaved.ignore = [ "/etc/doas.conf" ];
})
@@ -221,8 +224,9 @@ in
# Catch non-existent source directories that are needed for boot (see `pathsNeededForBoot` in
# nixos/lib/util.nix). We do this by monkey-patching the `waitDevice` function that would otherwise hang.
boot.initrd.postDeviceCommands =
''
# Seems like systemd initrd doesn't care because it uses the systemd.mount units
# ("If this mount is a bind mount and the specified path does not exist yet it is created as directory.")
boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) ''
ensurePersistSource() {
[ -e "/mnt-root$1" ] && return
echo "Persistent source directory $1 does not exist, creating..."
@@ -459,6 +463,35 @@ in
}
];
})
(mkIf config.virtualisation.libvirtd.enable {
my.tmproot.persistence.config.directories = [ "/var/lib/libvirt" ];
})
(mkIf (with config.services.kea; (dhcp4.enable || dhcp6.enable || dhcp-ddns.enable)) {
my.tmproot.persistence.config.directories = [
{
directory = "/var/lib/kea";
mode = "0750";
user = "kea";
group = "kea";
}
];
})
(persistSimpleSvc "headscale")
(mkIf config.services.tailscale.enable {
my.tmproot.persistence.config.directories = [ "/var/lib/tailscale" ];
})
(mkIf config.my.librespeed.backend.enable {
my.tmproot.persistence.config.directories = [ "/var/lib/librespeed-go" ];
})
(mkIf config.services.hedgedoc.enable {
my.tmproot.persistence.config.directories = [
{
directory = "/var/lib/hedgedoc";
user = "hedgedoc";
group = "hedgedoc";
}
];
})
]))
]);

View File

@@ -35,8 +35,11 @@ in
isNormalUser = true;
uid = mkDefault 1000;
extraGroups =
[ "wheel" "kvm" ] ++
(optional config.networking.networkmanager.enable "networkmanager");
[ "wheel" "kvm" "dialout" ] ++
(optional config.networking.networkmanager.enable "networkmanager") ++
(optional config.virtualisation.libvirtd.enable "libvirtd") ++
(optional config.programs.wireshark.enable "wireshark") ++
(with config.services.headscale; (optional enable group));
password = mkIf (cfg.passwordSecret == null) (mkDefault "hunter2");
shell =
let shell = cfg.homeConfig.my.shell;
@@ -57,33 +60,17 @@ in
"/home/${user'.name}/.config/fish/fish_variables"
];
persistence.config =
let
perms = {
mode = "0700";
user = user.name;
group = user.group;
};
in
{
files = (map (file: {
inherit file;
parentDirectory = perms;
}) [
"/home/${user'.name}/.bash_history"
"/home/${user'.name}/.lesshst"
]) ++ [
# Just to make sure we get correct default perms
"/home/.tmproot.dummy"
persistence.config.users."${user'.name}" = {
files = [
".bash_history"
".lesshst"
];
directories = map (directory: {
inherit directory;
} // perms) [
directories = [
# Persist all of fish; it's not easy to persist just the history fish won't let you move it to a different
# directory. Also it does some funny stuff and can't really be a symlink it seems.
"/home/${user'.name}/.local/share/fish"
".local/share/fish"
"/home/${user'.name}/.cache/nix"
".cache/nix"
];
};
};
@@ -99,7 +86,7 @@ in
(mkIf (cfg.passwordSecret != null) {
my = {
secrets.files."${cfg.passwordSecret}" = {};
user.config.passwordFile = config.age.secrets."${cfg.passwordSecret}".path;
user.config.hashedPasswordFile = config.age.secrets."${cfg.passwordSecret}".path;
};
})
]);

View File

@@ -48,29 +48,6 @@ let
asyncio.run(main())
'';
# TODO: Upstream or something...
vfio-pci-bind = pkgs.stdenv.mkDerivation rec {
pname = "vfio-pci-bind";
version = "b41e4545b21de434fc51a34a9bf1d72e3ac66cc8";
src = fetchGit {
url = "https://github.com/andre-richter/vfio-pci-bind";
rev = version;
};
prePatch = ''
substituteInPlace vfio-pci-bind.sh \
--replace modprobe ${pkgs.kmod}/bin/modprobe
substituteInPlace 25-vfio-pci-bind.rules \
--replace vfio-pci-bind.sh "$out"/bin/vfio-pci-bind.sh
'';
installPhase = ''
mkdir -p "$out"/bin/ "$out"/lib/udev/rules.d
cp vfio-pci-bind.sh "$out"/bin/
cp 25-vfio-pci-bind.rules "$out"/lib/udev/rules.d/
'';
};
cfg = config.my.vms;
netOpts = with lib.types; { name, iName, ... }: {
@@ -139,7 +116,7 @@ let
});
default = { };
};
drives = mkOpt' (listOf (submodule driveOpts)) { } "Drives to attach to VM.";
drives = mkOpt' (listOf (submodule driveOpts)) [ ] "Drives to attach to VM.";
hostDevices = mkOpt' (attrsOf (submodule hostDevOpts)) { } "Host PCI devices to pass to the VM.";
};
};
@@ -149,6 +126,8 @@ let
(map
(i: mapAttrsToList (name: c: c // { inherit name; }) i.hostDevices)
(attrValues cfg.instances));
anyVfioDevs = any (d: d.bindVFIO);
vfioHostDevs = filter (d: d.bindVFIO);
mkQemuScript = n: i:
let
@@ -184,6 +163,7 @@ let
else "ifname=${c.ifname},script=no,downscript=no"))
("device ${c.model},netdev=${nn},mac=${c.mac}" + (extraQEMUOpts c.extraOptions))
]) i.networks)) ++
(optional (i.networks == { }) "nic none") ++
(flatten (map (d: [
"blockdev node-name=${d.name}-backend,${d.backend}"
"blockdev node-name=${d.name}-format,${d.formatBackendProp}=${d.name}-backend,${d.format}"
@@ -224,15 +204,15 @@ in
services.udev = {
packages =
optionals
(any (d: d.bindVFIO) allHostDevs)
(anyVfioDevs allHostDevs)
[
vfio-pci-bind
pkgs.vfio-pci-bind
(pkgs.writeTextDir
"etc/udev/rules.d/20-vfio-tags.rules"
(concatMapStringsSep
"\n"
(d: ''ACTION=="add", SUBSYSTEM=="pci", KERNEL=="0000:${d.hostBDF}", TAG="vfio-pci-bind"'')
(filter (d: d.bindVFIO) allHostDevs)))
(vfioHostDevs allHostDevs)))
];
};
@@ -281,10 +261,30 @@ in
};
preStart =
let
hostDevs = attrValues i.hostDevices;
in
''
if [ ! -e "$STATE_DIRECTORY"/ovmf_vars.bin ]; then
cp "${cfg.ovmfPackage.fd}"/FV/OVMF_VARS.fd "$STATE_DIRECTORY"/ovmf_vars.bin
fi
${optionalString (anyVfioDevs hostDevs) ''
iommu_group() {
g=/sys/bus/pci/devices/0000:$1/iommu_group
until [ -e $g ]; do
sleep 0.1
done
basename $(readlink $g)
}
wait_vfio() {
until [ -e /dev/vfio/$(iommu_group $1) ]; do
sleep 0.1
done
}
${concatMapStringsSep "\n" (d: "wait_vfio ${d.hostBDF}") (vfioHostDevs hostDevs) }
''}
'';
script = mkQemuScript n i;
postStart =

View File

@@ -5,4 +5,7 @@ in
{
# yeah turns out this is in nixpkgs now... we'll leave it as a sample i guess lol
monocraft' = callPackage ./monocraft.nix { };
}
vfio-pci-bind = callPackage ./vfio-pci-bind.nix { };
librespeed-go = callPackage ./librespeed-go.nix { };
modrinth-app = callPackage ./modrinth-app { };
}

26
pkgs/librespeed-go.nix Normal file
View File

@@ -0,0 +1,26 @@
{ lib, fetchFromGitHub, buildGoModule, ... }:
let
webSrc = fetchFromGitHub {
owner = "librespeed";
repo = "speedtest";
rev = "5.3.0";
hash = "sha256-OgKGLQcfWX/sBLzaHI6TcJHxX1Wol6K7obLf0+CHrC8=";
};
in
buildGoModule rec {
pname = "librespeed-go";
version = "1.1.5";
src = fetchFromGitHub {
owner = "librespeed";
repo = "speedtest-go";
rev = "v${version}";
hash = "sha256-ywGrodl/mj/WB25F0TKVvaV0PV4lgc+KEj0x/ix9HT8=";
};
vendorHash = "sha256-ev5TEv8u+tx7xIvNaK8b5iq2XXF6I37Fnrr8mb+N2WM=";
postInstall = ''
mkdir -p "$out"/assets
cp "${webSrc}"/{speedtest.js,speedtest_worker.js,favicon.ico} "$out"/assets/
'';
}

6239
pkgs/modrinth-app/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,97 @@
{ lib
, fetchFromGitHub
, rustPlatform
, pkg-config
, openssl
, libsoup
, dbus
, glib
, glib-networking
, gtk3
, webkitgtk
, libayatana-appindicator
, librsvg
, wrapGAppsHook
, stdenvNoCC
, jq
, moreutils
, nodePackages
, cacert
}:
rustPlatform.buildRustPackage rec {
pname = "modrinth-app";
version = "0.6.3";
src = fetchFromGitHub {
owner = "modrinth";
repo = "theseus";
rev = "v${version}";
hash = "sha256-gFQXcTqHgSKfne6+v837ENXYYiEYu/Yks9TpnfBCPnA=";
};
cargoLock = {
lockFile = ./Cargo.lock;
outputHashes = {
"tauri-plugin-single-instance-0.0.0" = "sha256-G4h2OXKPpZMmradutdUWxGG5axL9XMz2ACAe8AQ40eg=";
};
};
nativeBuildInputs = [
pkg-config
nodePackages.pnpm
wrapGAppsHook
];
buildInputs = [
openssl
libsoup
dbus
glib
glib-networking
gtk3
webkitgtk
libayatana-appindicator
librsvg
];
pnpm-deps = stdenvNoCC.mkDerivation {
pname = "${pname}-pnpm-deps";
inherit src version;
sourceRoot = "${src.name}/theseus_gui";
nativeBuildInputs = [
jq
moreutils
nodePackages.pnpm
cacert
];
installPhase = ''
export HOME=$(mktemp -d)
pnpm config set store-dir $out
pnpm install --ignore-scripts
# Remove timestamp and sort the json files
rm -rf $out/v3/tmp
for f in $(find $out -name "*.json"); do
sed -i -E -e 's/"checkedAt":[0-9]+,//g' $f
jq --sort-keys . $f | sponge $f
done
'';
dontFixup = true;
outputHashMode = "recursive";
outputHash = "sha256-9HtTdIotG3sNIlWhd76v7Ia6P69ufp/FFqZfINXSkVc=";
};
preBuild = ''
cd theseus_gui
export HOME=$(mktemp -d)
pnpm config set store-dir ${pnpm-deps}
pnpm install --ignore-scripts --offline
chmod -R +w node_modules
pnpm rebuild
pnpm build
cd ..
'';
}

22
pkgs/vfio-pci-bind.nix Normal file
View File

@@ -0,0 +1,22 @@
{ stdenv, kmod, ... }:
stdenv.mkDerivation rec {
pname = "vfio-pci-bind";
version = "b41e4545b21de434fc51a34a9bf1d72e3ac66cc8";
src = fetchGit {
url = "https://github.com/andre-richter/vfio-pci-bind";
rev = version;
};
prePatch = ''
substituteInPlace vfio-pci-bind.sh \
--replace modprobe ${kmod}/bin/modprobe
substituteInPlace 25-vfio-pci-bind.rules \
--replace vfio-pci-bind.sh "$out"/bin/vfio-pci-bind.sh
'';
installPhase = ''
mkdir -p "$out"/bin/ "$out"/lib/udev/rules.d
cp vfio-pci-bind.sh "$out"/bin/
cp 25-vfio-pci-bind.rules "$out"/lib/udev/rules.d/
'';
}

View File

@@ -0,0 +1,10 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IERMTWVGZyBJYzdr
b1d2aEtVd3lVb3NBamRwVFpTaTRjZFlCczk4MVIwQUoxWklwN0NrCmFFekpPYUlO
YlgvVlQ2WDR6amZDN0ZSY0Q3WWtTME5pUmhQeks1c3dGOVUKLT4gWDI1NTE5IHVK
bUZvUVAvL0NmQzFkY3BuYm1wMjcwbFpLRUNpZjJCaW15SEVDUDV0REkKRXFMQ2d1
NlFBejBvTlJrcUtCYWZxSDBkbmxIdExBNTU0aC8zRW5OVWllVQotPiBDLWdyZWFz
ZSBkeC4oTFUgO0FtJG9+RyB2IF1QXGhxVwpJS213MXBRMWY1cXlHZwotLS0gRUF2
ZkswNlhvcDhTc2tybCs1dldwa2l6SDZZVmNkNUNjNGlkV1ZVa09pOAp88dxHGxVU
vuFQO6JcroY9MF5Te/YV+wMc3hVxksibMnH1TWGh207prwcOWNOEz2iEkZY=
-----END AGE ENCRYPTED FILE-----

View File

@@ -0,0 +1,15 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IERMTWVGZyBLVlg2
ZUdtUlFUQXBLT3B5YmkycWJnMXJyKzltbyt6K2dJK1RBUm5ydWwwCmRUdjJFSEYz
Wnp4cGtzTXZwc0s0SUJQUjRlOUJlQUoyQ3BETXZmY0ppWG8KLT4gWDI1NTE5IGR5
QUJzeG1DQTRLUmRwNnNSRUJRSFFtUDl5VjB0clVLYUp3R1g4TEVKaTgKb09tVlhF
R0tmcmIxMHVHcG80RGxRTEhBcngvT3MrdnNPbmpCTlRyZW5xQQotPiA6RzEtZ3Jl
YXNlCldSUlN2ejB0MGpyUHYzNS9OQmJTK3MvcXRDakdBVlhuUEt6SFE4QmhnWVN0
V1JMOE1oaEp5QQotLS0gYW1OMTcxNWJrUndmR2h0ZmRDWk5CYnJjc29pSERUMnNk
d2VhTGdpOW4yUQrEeH2E9m0YycuW60QrdlRetO1kNU5FaKXRQt46iA9lACIWD4rC
Cx6WxhCBgz3hvm9x6iuYiiQxZzgJNK7qXcV2MBeQdivazeEbC7blAKVPhwD/dl+b
PzBDXRXG3c3jMjeZFT69fIBGJfZrrLGKpTALVE4dTVXmQmVVQDTp19IC+jUXHBti
Pj6Dpc7452s8zPkzZyRbasO9b4PYTwq8IHT6X7ITwbzZZm8gexDYe2SzRZ5VcPz3
El+yoULZ92WbXPSQIP/Slx4BEZjmsQS+sm0N8AnBRNZkWVbHPF9IZRg3VjDAn2i8
F3un5js=
-----END AGE ENCRYPTED FILE-----

View File

@@ -0,0 +1,13 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IERMTWVGZyAyMWox
cEFDWWUrVzJSM1o2azJqei9HV2xFY25oQXV5SzM3akxEaUJiN1J3CjkrQVhjWHY5
Uk01eGp5Q0RKNVREVXJVZytndTM0SXNZSGIvUVp1TnRiNXMKLT4gWDI1NTE5IFZn
WjAyR1RMajEyMnFSYnNGT2EyekROVWNoakFJQVZxaFc0YThOMHVyanMKWldrdlAv
cVg0OTFHL2loeGJ3RHA0MnlnckN3czF4RUgyc1NjYXVOSXF5cwotPiBvTFQtZ3Jl
YXNlIDRsSDp6eikgT1ojIGEzOFZMIFc3MUZGCkttZ3ZLNGxucnlJc01kRXFZTGpC
eCsvRzl5WnFUMUIyTWNVWGk4SjQyN3V0bXQ4VzFuM1RjSzVMcDJkaW1JYXMKQkE4
YWI1Mmg3MkpjdVpVYWJkczJQMnM1SVMvNkZhOXBDVEZPCi0tLSB3SVIyU3M5RHBE
VFRtMFU4OUFLbkNjbkJmVmhlc2VsYzdsd0pFaFkrUmkwCs+h2KlXoyZ8U++A9wmV
kh4N2YyI/a84yPGDNoFdSlXaU8jxUzolnadImXSXyB4CroqQLvGEbkZZlWqPLVfh
Iav7Ja8=
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,11 +1,13 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpCM2U2USBvaUQ4
L2IybE1EZEtXSmdXTnM5V0Mxd2tQUWtmZ2wweERwcGU0bU4xU25JCmY3ZGtNY3pj
NHlDUVJGYzM1MitmMGlZamdqaVNQRlJ3SWFQSVdUK0NZN0EKLT4gWDI1NTE5IGxi
cVRCMDJaeTB2a3U1dS9RMHVwczZTbmE2VlZzYTd4RVB1L0s3dGJ1bmcKK21jajBV
RytOSzZiZklKTkZ6RGFTUzlYYkZkWlhtQXA0QkxhNW11ZlF0WQotPiB2eTJhVihr
ay1ncmVhc2UgNUNiYCBHLApQSUZIbVB1VC9EME9XdUh3YUVua21DTVJLVXZYCi0t
LSA2SnJjeUY2MXBla1crZGwwRysrZVBUMWg1ZW5QaUpsbHhLbTY1TityUnQ4ChJH
wXMLGclaIT1UAe4u/G4+UUS+zOGdLcvIMygEPNugccysWXBNW2uLjAIksWGXrF7A
R3EdsIYyYM8Pq1baHbfUH6BJN4SGXzBY/nQ8jAkhBLcmnVy9/L+B3kU=
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpCM2U2USB2WFY2
MzFnVFYvb0wzc3grRDBrUi9teW1tNmx5dVBIRVc5OUdlcjdvTDJFCmpJL2tlYXND
bVJKbzd2S3JkSFVMdG40MS84V0lPTVJSNHVBQlNXSkN1eTAKLT4gWDI1NTE5IFBM
ZGo3L2VlNUZqOEE1UHBuMHdqc1pyYlQ3R29ucE9pajU0bHMzamlXRDAKcG1Qdmw2
cUlncDFWNXBOWnpIeDNZSFA3d1E1bjNaVVpKU3lMRjRaSHNtMAotPiB2XUh8eF4t
Z3JlYXNlICUrO0cxIH4gaTRoIF81SEpTN0Q5CnpWdEZpb1hZa2t5YkE5RnJFMHVZ
WkhkQ2o0eWtyOE9ueDJkeGd2aUhmLzRUUGs1aUc1NURIOTYxczZhOEVmT0EKd2xk
TXFHN051d25PQmtNUVZkVEFGUVliZjdmZDF3RWFkaEhNTzd3ZVd5N3dlNzQKLS0t
IGZDR1Mxd24zOW05bitzQnN5WWVOOGtCNEc5aXIraEF4eXFUQm5CZUdCV2MKd44C
/Trgg0OEZ89/jqbj56z/Hia1Ka3ZsEv6bXPI/kcRvFDBFTgtvG3KWCgMBtTUHXzY
TKBPoQqrUf7plH7a/mTx3KR+4Y+yF+1i86s7TzYjD8d1xfFH3BsVtg==
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,28 +1,30 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpCM2U2USBmR2JR
ZTJCZ0IyeDBNYTFJcjN6cWVEcWlvU05MTjVacVF3VDJ5M0M3QUcwCmZyZjR3bW5X
NXRQTXkraEJkUXNKbmR0ejV3bnNFTnlqS0R1SlBrc3NSVUUKLT4gWDI1NTE5IEYy
bmFzZmd5S0RiZzZEMTNWd3liMkV1S0MrcTNCRkRpRnJSbUd2LzM1elUKZ0d1SCth
UnhMcnIxMWVTUjEzaFJWTmc3ZE04SitNbmUxNUNaRTUreE5XRQotPiAxR01EUlBq
LWdyZWFzZQp6bklwRjI5Ry94a3dDcnVRV0U0eTZVdS9wVlBjL2Q5czdBCi0tLSBY
dGJZTkcrb0sxODFseVI0UkVIUGZ5bXlUYlphNDdDUjhlSVh2MC9obEdvCjcWripY
nhjDDf/v0jPRA9frtP73z5WPOFgeq6EqOFIZgJarvjdSh1bSBz2LvuAdK1b8uf4M
b/ehJ54u1sG65H32MWbDwoXUIoVzUvNg37Vze5VGBtIq7+2k3Stl++lkXze2hTPe
ztpM72FOL6iz6NA5K5eVq5wsWOIVHhVOsW9BKc9xjJfweZDDZ3fGgKn+VYLi1q6X
SsSXqaa0dBgd/wo1v+8YCUTMvcHdmMxJhC7RpyNwRZQbd1ju3e7G02D7B2uLhKoD
g5kPPEJ6mf9QHpTv1TedyylPjgt3T3vjBIXnkutBIIgwGpW7sfs/gK8yYVqbw5JU
j6ro71pPW+SxPBabeF9jfo8E+GatCr7WA3xzOa7+rx5ZYUIuWcsvOhcr1u8L3Xqq
IpIqKYqqZbGda7mvZxEFsTC2gLhqBk0Ndhv+KSRMUJYHlBi3VXldS7yLqlV8W5Tr
eppSsXw3dfQcKnDxRBihPPBIYpHDyJxiXN5oslcFO+0ITwmsjBEZK/CcqOUkVVJe
s/BmseckbfQVaIrAmBUjUBZx0w/g1dgkfb1YcNkotWfFKxN1RWnH0YgIS1l+NTG1
7KeiSyw4m7Q7b7lyZvK1I3kholQ0wfNb2jXyBqvP12wqCMUE0LCaf3M8S+gq2Los
mUwHZfo8E9mUzo3PFerAIHd7fKKkqymgbzpXMSmO8VKXOTVuo+5wQ8iNhU0jTCzL
W+B4IMKK0QuRiMxdO/UW5VBwDT+5j9moZze332z9zgoQsrxBYdg8HIVb/kzrnmiB
Z38uqgzRMPBxh43yd2vmS1cSdty4z8eDOj0SvIIdVNQNSIYEvMMR51WdyoIbqPw3
/8cwG4bKYJ/VAlk2ksdIZWzKabdqQ8fcgh4Esti57Dt3ghljDOyHqZH0eojb0k8s
1yz3kUKloq0rB8E3Zz7JiFjXLHv68bgYtX8bmBCS0YnUZQJ539xCbIeVb/W4hntf
6VDlFfs9HNMtvV2ryq4CDOhSTvqixrXqO6YRtyH/JpU3IOm090e+IkUsPZ2u5nLB
azRuhWq3zzJFfL97dHmkCO+Xy5whMwbQABCmWT+bVDFhJCHNIZjwJoLga3u48GDk
mTxu0+XbWugXekSvWnSB2tcTo5YRPUxFn6uCbyvI0vNZmaFQZXHRqNlE4GzWEfkv
+jFJjr3OZFSm8G7j8CPftuW1
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpCM2U2USBuQjBY
K1ZTUzR0SmFoYUZLdzUzNUt2M1B4RFdPZzFkcW8rMWJLUjVHOUN3Cjd3ZHdMSzdp
OWswditVWUhYNjZCdjV6M1d6U3pNak5hUlZjTHNqSkZPSk0KLT4gWDI1NTE5IGNG
ZytHazJQWTExMjNkZWV2aDUwcnZjUGcwcXRkZWl0M0F2R2JpUVlJekUKWTFlbkEz
YlN6dkgrMmFFcmlZMjQ0b2oySjlEMC85cUF5QlA4NGF3blBKRQotPiA8M1hbWS1n
cmVhc2UgOmlZJFYmJgo3UExzUHhSVi9lbVRjNW4xTnVaUTVGSGVDNzRUNlJ2TnVu
U0wrWU5kd3dyR2phcUNVUHJCYjdzeWhjNVFOV011CmZMOVN0WDk5eUg3ajhwbStQ
aHExT0EwblpreTBXVHh6Zk1HRkhzcmFQNnoyVUlaeStMUkgKLS0tIG1aY3Zadko3
V3pDMGswSDZCeit4SVhSRCsrZVRVZVkvQkNsQWN1MXFMUGcKcK74YULWqWw9VvkI
NH38VHfDPW5ueSiyHHKn8MI9YosPr/TmkwgKd/DOIMVB74ahPalo1QUeg+eaBVnq
Uncsbx5ecW3JswthkAhiktWHcdHmioGD6hCcgbWtfA3VvbAYc2gtAF2plfDVH6BD
c8veai3B15ZmDmq3b62sgVA9JUgbFaB1eRqcaPXmNgQJR4c8J+CZWXGQ/TKMqtAg
chCDuczjLX8IxhMPF1gdCggSt5GtMhY47fpZJRbBUw01l8koqjm63KvEeg2ejFGU
+tXonI4jm2JUxlDTqmNSrVpmAgtPoEPszHc+GsnyNjAz4IXMm5UrQAZyDPgxtwQW
ZAn0IdOB8rSL/AnRGUpPrecuauINQjONe8CapijjJPQK7g1AHscfxr+OJD1j2eV3
O6TNlz8vKW4t/V7CUf46ykxxExhA0mKJ53ksaXy2+HWoro2+c4nao/bEld5gt08U
uCpJtjPKB64X6vdzbY21/l87VRDyxbb3poENfrXlawqS0Z7i2gAbHN5EuiMv+35o
sBaR0BfcbTn6VagC3i8HEEOO964FrW65pkqmGJcJMgUdcZSl+Y7gHMjWyodqGPOY
evT6xj4iyeM49vXynV2vrJRu5rr02hOS/8rPphV9c9q9ju5n8xbhS6IiybAelPwt
sa+xM+w0n+fxVksiXeFj7qD1LDN4+kwqhYP9SlzbA4fT3AkYYrxthTww4IFAweyQ
IJkpVL0/IZEIEfRzpr0lWLprAaoyPc64pecl0z/gBJDgz1kSG6iEh6K4qx7ahGde
kBBpS2I2ZsdBkvmQxDSM7tYYBzPxcUNZlSFOIeUy3Xl+OGPlKgtMofbnjV8AFqUf
tOpgKpLUEkTgXbGUKBxHLj+8pbw7zOFp4sJ00i48ZzLP5D9jom+jS19wGshRc7cH
dK92CctvrfndQ85yO0vqlyOdMTqjh/z3P2KmhL5SW6P51q5mseTvj7FNM7331DTs
rq7XWaBLE61eWWQ/dZKnqh5YbkSlU1+08Rl6H/vCU5hTC8fht3KTQSWPofkrP+5f
We/Pe62LyhV/MbLRA0nmU7Sf4IAnpHfa4kLtlYeB3xiqKd0McM//qzjuk5NoIgE4
nL0T8YXdGt8K01w+nt+j5bo5gFoRz5+1/ZZ9BgN2DOo4SClYnJWQ/x80X061yJ33
0SGv4eAC3vFi6xE=
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,14 +1,16 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpCM2U2USBYdEYx
QlZJaldncVZVQWF5S3dIR3ZpbXBZdUZoNnpjakgvbUs2TGlDeTBJCkRNSks4a2M4
MHVrNkNyVisweVdxMDA1NGxBVlMzdFl5U0pyQXMzWVFsYkEKLT4gWDI1NTE5IHJ2
TmZKdXpKOE03MFRJT3B4NlB3dktoV3JpUUloREhNMXFrVkR1MkM2RmsKOVQ2dGNB
TlM3aE9hN1g4UkpyM3JFYUdjbWZTOUt5QUxFdEM1ck1vWnowUQotPiAkRExcQUtF
OS1ncmVhc2UgVCBIe24rVz98bApsejlsdW15Lzk0dVBYTHp0b1dISmtpc3duWCsw
aXVwN2RpcjhHT1hEdk41emR3Ci0tLSBBakFqdnlYMWUrNFc0R2MydjR2czc0eVNq
dUdkWUR5dWFpQ3dXWlNTclNBCkqHl+vYAsHNKPq/180XcAZ2ERmKSRWGVdlGr8VH
T6Gf8PHML8SbS+5kSdoPHOfz7/dlJ82KEdmihO1cWaCAVJTxYbOZR1lShNipMk8V
qAO+r+7Lm8L68ig0LC3y4qRngYif3tCbFA3J7Ukw3CgAVynxLh293yM2CXRPoX8O
2i6gJ28OlDpvfHXhR5sTQS2yAHYCYevGoAduEqxVRBt7mWJvno0K0i2MTug3pt3u
3l2cR8jUv2qFLroZP4XKLjxaMFXDPkzcTwH9aah+jPkNgAwir4Iw2g9Xnd2w7C4=
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpCM2U2USA3MEFh
YURySHY5RjVQa1c0QmMvTkZtZElNd1BIeDFpR1U1YXZ5dndnclUwClA2M0ZYMk90
RmlCZHVISkxZQTZXeEhZVUROSVRGRVg4TmZKZytSL05FcTgKLT4gWDI1NTE5IFY2
V2k3S0VQdGlQZ0hkU2ZoSWdOUG9rQ0p5Ukg3ZHU3MXVvNHlWYVN5VVUKcWtoWDJh
LzRwcktWUGQ1bnJvRzVBVlpHU3NhdzYwUDBPTmx3MFNrdWNQYwotPiBKQil6Km0t
Z3JlYXNlIFdlRTlUU2cgVlxLIzUgJlVyQiJ8Cng3NzF2b3d6MnlEdURQWHgrSFJv
Y1BMUkZyelJSdDJvUUUwY1k5R3ZBMnJaa1R4NWxlUll2azNseW5MQlpvVWUKYUdN
d1ZscXVxd1dxSGpzMjBYeWhhYyt5ZlNkSjhmV1ZTTDBTCi0tLSBBTG45VUJOSXZv
N3lYNVlyZldMVkpZdUMvS21NdSswVmU3VThZK2M2eis4CjwfWWJVR9Ty5dBXEr5s
U+QrifXvyMJJvqOKEfMYYomFLpt/VtbDAUwNlqLnFIk1VZ2xPUBisXPtReiCK3gq
P+i6bUONSmPYvmF82VgiuKDuz/kmWqVGg2iSBPIXAGLNIEojndQwmhnrsN9ex4Xy
lBHkvVsN8EamUJwZ+FEZe4+PjA5yEnANWpeTELOt2gA92/jwwnNIsr07eVTlkh8X
UmQ2owGwktXP7it6//NV3C7sAdazs2bX65WSnog2E8WPNAorKWI8RCYs33CshVFt
zzclZ4hRmlAmqMHYqFY=
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,12 +1,11 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGo2N0ZYUSBqbzA5
UG9WdzRKMFRCd1BPQ2FvdmhXeW43eDcxQ1NSeis3QzFmUVRFVWo4CjNmY2k2bGow
TFVtR1VTanpMdEt5RmwrYmxzbjJKYWFERDViYk8rc3JRc3MKLT4gWDI1NTE5IE5Y
dUhBaTZWdE92QmdIcGczZ205VDR5TEFCM2dZVlRLUDViSTRYNlBYVXMKTlF6Rnps
V1ljT0ZDTi9HVXA1akZTeFdKZVpEUTI0cVMrb3Q2MzUzNUlNMAotPiBpRnNkeGUt
Z3JlYXNlIHg/SyxwND5UIGtmc0NfID9YJz1WCkFrd241UDJ4TGRaMERWR3hjaytQ
U3JtZGZsY1ZxWGxiNDkrRjFyYm55em9wbVBMcUVWNjlFR0EKLS0tIGZzeE50cGlF
UzZ2ZStzUG1sVE5ieEtFMjE0SkZIRWtQK0Q2U2MzWHJITVkKsokrFMnFLDaR3HSe
fabmNuirYnT5MAkkYCYgCBR9PQmQ5CIThDqjEh0u6KK6TSnqWluMV1pG7O3OxS9g
LlOrVb0vJhewY/MSZARfrU7Hq6ii7Tln0NLOHoNy5w1TfqYwJg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGo2N0ZYUSBoYlRq
ckk2c21uMVZSR05SdlJpck5MT0FZeUpLTmpTRVN2WDkxWWxmVTBvCjlHZWtEQ2Jh
YUdybjJLWkNsZkxabTVIR0JnMno5YlMvVmhodDU1bktrSzgKLT4gWDI1NTE5IFZT
Um9lWXhqRFFLTWdNaTR3Sm1acmY4N0l6WE1FSW54ZEtQcEE5RHBRRHcKcE5KYmp1
WVdCVk0wMHZ2SCtUR0xKWU10N0xETXlXZmgxcmZZOFBXYWtBawotPiBlRm1gKGxU
LWdyZWFzZSAqXF8gOkU8Tm96IGA4IC1TCjY3NUgKLS0tIDhjaTNrbG1iQ05iTWRS
UzIvNzA5TmVGS2ozZmYrYjlBY3J4Z1RRUGRNeWcKUTIEhWqr0fOODu86MDll7k3U
ThgmS9nlcUY3fMgXzZLtpHIJ/4ZSI+miu8RmLMaeC61qv6xNThGdx+MvU4tMBWKA
Hv3XGi2MYL1jdHh2KYg5PgdqchYuHrFuBPS7c/tQow==
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,80 +1,82 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGo2N0ZYUSBWYi84
UU4zeC9KQ1NQeFJHR21CU09kdVNmMVo4MXBqdlFTb0FaVnNSbW1VCkxxaUU1RHJz
S3pDS3NwQzFHTVJxbDhjbEtHak1yNlBOZnVIaGhpdmpZRXMKLT4gWDI1NTE5IGJW
aVFmb3hVWU1hOXludUdId3J6VExXamNHOWhQMTR4bDZwM2JremNpVW8Ka0dkWTZY
UFdFWDIveFFjZFAraTRQQm9xYXhpWVpySzFtS3N1ell1d1d6ZwotPiBRfU99e3Uu
MC1ncmVhc2UKU0t3MVJYQ1VWbjBNcVBRZ2czNEszOU5ld3VWcWYrVzhEUEFQUG4z
YU9nCi0tLSBPMnV0ZmhBQnl6N3Zxd08rM0ZYQnorQTJnYUpNMGtJVEVSTXBvRUtv
VjBzCjpxu1swTIZcnullNvM2TOzCJ8DGF7lyNLGtPKZ0T7ZD5/9M4r1Gbey02Mm5
uU+WBJXEa2vNzMU0SihCryCcQsvKZENO1RNX0+Up/YWuxl0fMVYYYlwblEDtZT0k
hLnXtrxKEfCaoAkZVwYLAuExm6WzzqzJIqPUaqhgt2tYaGLt9wxxGHPQfDC7S5+U
7r4OQVOLiJ9UKx4gskTByvFwJ36HCnjJqC23+m2rb1XeeObujLe05VLd6C+FyJ4g
NYUK9Cg3VZ6ccnajfyBoC6rxwRUrCrSm1JH0Z+st7Oq4U79+01M8Xm4TvI+olPJk
A0MUXKLDvrrR1RDVKMQxQRkT8PRwW+5txYslhbqqGabPDZF/oLO2JUUxawkBlm+B
LlKVXTJ8MkHTLU4qcTlTftamWHHBqrqSQjN36Z+Zd7z9f83rjYzA6twJPOXwV+fj
dxab4lJqi6hGhJNfAYlb3WijwPYVSdRLTKC1RogxdZZQcgyjUBUSxq+LKLP+CB6b
Fdhc0j2PStCUZ8YALQFQw4eTiU43T2g14iHMO4mTgwwvKKlkB4DX+MU9lQhc+Uxo
ZNKgc0A1lww82VmeC5ammpFZHcJB4ZBvjQBowg8hHbUPOKUycxI0jWXjuoyf0ho1
lnbZJKI1aKCid5NN6DKpwHaN04ywMshcX6+sukKUw4h6RwetdFZfFvdPZ+J/+Skv
pp6KOUS9iOYwhakxtg7SDTuPZfCBsOulKVSRqQNW2NwRR2JPibE2KXQRJB+OJzZX
H2y2Q8zPfDvPmOecc3BKpPw/XEWAZrZfU5HqdFhLzQDV42YEcHB0LdCFdaYJTiqB
5eME27sn11JrlBIubEZTMsBp+Ghox3uU5ZatErFIuutj32VmUXnbeGmL0mrKYUxt
eC0sUlprGr02xfQCW4bUYJ9MsU/BCcZWwhI/6ltW79DSg/+YvSXVBuEvfUuZgvZY
b/ZhsNG8eNk18b57dEyeZ8NC2x+ReNYovP5uGGVyomWsSZZukvJJ8ivEOc37tbv7
dWl69WfrVVScspv+frdTOvY+tzzuejyhsj7pFEhJb4yWApijeQrHreea4JJLm5h/
DtjML7lkgQ+4uXI+bYljmsuO0RI3JL1Y1mOTq+sBuhkNj4k0IflPCl0QhDTSM0eI
1gJpGDHg0JS6ofgvjhaXwuDfUpUVHolXngDmcid8AxIXBlrTRAhgG2voh4Ol7vKv
FtuvK756Z+HO4nqS3Cy4M23blhm9/6+8tdF7v6/IUZXv0jKerVplowP6TUA/FQ35
hAcvRCuotCjZcxvOe6UDMS4AzqeRwDLVNuOjgtGC10OzybUSlnIHbsTrhkCyItJB
abDRqpP5Ym4gWc3/EYNepv8qXqU+zASuufDAp6qNgeEqIXttxbGEPDE0uYlsljSB
b3wqncE76TfEAovyiPDwAAsNfBrqE30KPDKeFJPUYq5/DO7Q+KdddXESdB2rRmJC
LSipqfG6IGD3vCZXCuqTwNNTzfkuvGJm6B6twfB06+alMNZfrWcZVxvMfzj4GInu
kGEo28/yHmua06fz02gBZBDfjiStLD/+AnvMzxn8JXXzexhd3B/TohyCjhVeXnV5
Cm4mf8k6NjOTBnmTor36BxRCWus59lZ1ukAKnnzUoaIUYYaiG/xVl2aiZ4Pm5x/7
Dswe4QxyGD1mbLV0FAtgsROPasGmhSKYYY6EUPlLEtbV//lTUieMfAdOnnRM/U/D
BJJdNaWvPzUyyx97u5ZhdSlGn0RiLOXFXYI9kjYuta76VgqM02+FctgPRMtf3DaS
y07ib4udXlnB8zWf6tuRxsd8mFLVKbUC2sJZFNGh9e7ztvN7gNs58uZjBc+j00Gx
9htxfPgEaj1fLyXKA+EELEbd59G2eGAXRnbhYi30rz6rhGjUVJUIL97Dc7FNPMiZ
q3vFcB42fd2oTu6qT/CUibrYpKao7cxZ0Xrzc2yOWpEE8i04bOF2Bm9CzDgG/JV3
1rjRQYrvNNoautX99mruW9WXd3XfMSzk1I+KnPM21d50ZPlsKCWPNWUndShJlzoi
cWTJveGNSsrBJiOJoLh0EJOfD/xbnj+G1nIf1VeBQtfHWajOM20wr/LWth8/+QSB
X5Eso6bEgBpZFvVwgylruZGJa94XJwxp4WvNPdy8nP3uH6UC5sA8o5yaQRCPuQEF
3Svj03mPzKGwaqSNeloPojyKk1VlBJGfnAgNALABLQIsDz5zhyLw9tFevdldozqT
E9h0JKw0ofKUwgbs9itHUv9kleAK/deEBGHc6hwXZYdUaUpez/7tGR93nkJbHp8e
Ho8prUglI1nSiDGdVwKNkmPWteff1085bwgsFRFubEorIi1a5cSgiFQloMLqP2IA
wTpQxB0Ukb0t5a3/vJqUgiITvhMk9xbJLs/neUguVH0NHrdoU9SE+HJ4YFk5HR+Q
ks/mv4F7HAnQMngBq1oZXhUoZKB9XXUvolIIB+Q8Cd84demDDcBBBPaMc8FXfJuy
pWXOnGiZ803paDrrO4IPLtP+q6E5uuuRQYi+XXqbGwS2heFMF62RjNtz6SX/0zd1
Qii0gxTuyB6/ECTNr6OzZKR0+wu3bLfNlo01MXHm7x4mdoXlzSvREtlaMgV3ucfK
QrQD9CKD68msHwlkgXMpvYpElkjHyllU3chxDuu7FlvJtePDey6stbblsYfIGMA5
+68PWvWBsFJtWsphX+33omyYRFXH+z3/4LK1d2tcQ4sNslyf+Ho8CjnC3mv3m5rm
aXyf80mY+6BKNCT3Tt6lm//ln9G25AfGk2fhdlpgLcJnlZmEGBuWBVG3ESmwhWVA
SWhIHUejUp1gF+4ThXUPsT72mc13+6EaK4b3SYoZvANzNHAWug9CP0VevMz2qIen
+U5ONaaxxajQhlzLG9f00rR/ryPQOep/gFdL61oENqkky2rO4XekVKMdFef+brvo
KluK83F83H/0be0sC37ElnPiwhfo+qu+TtYMYqxEQh9esyk2vdq4AGHkAqo+qQDL
Nuwsx32TzvgOIPDDq5ujym2M+SSY8mjvKff6BuGQ6lYnB5haPdiImdkI/G6fUN/G
8hzrog0KNLQ/SjKUYirIyrWIodtA0DkccjrmfXMA7QwWpRDAmKuICDyZ5VuhhuRb
rYie0kQV9vMSc1Vm+2pQTXvbjl+vVSwwzI8nd1u4wAQYaKd++yliwhos14il2lqL
MI1Ayf1gMCjqaDTQkzCP02vnQ7cgrCT6fIFQ97eEfTE+35SZ+wZq5LsKux6cOKlr
n5pUQY6ZW+ylLZHt/dLdOFcN0F6HBMu21i4cHC6JlHda/Nf8+NnLXzriyc09JW5h
phRdx01ZZYLWbRW+oPYIBDcZq+N5cjF1uFEhmCoWhhRIi+bYdVLkgwSwg3eMUlnv
CXAt+lusaI2OsnpVR2MKHzqNSppseML43FZxgbiKv5w/4M7UE4RjBTv6uNsqpWsz
KV5NdVudBYN0FT3KDU/ODzeZJZiisFkcmGab8naZIJN6XVZ9psLRoRo8YES9RB1W
sTVJaNnApQ2uOn4BzfI85MrXSthGuyoEOBmNlY210HqyZeEFj4nnXDMrkffUrolk
S5xxs6eB8Wrjb2UCD7tqtVQrBiF34XEXAVhuxpmwMdKRRMZ6Tok3qZZZ2RHdHyAy
8fV9daMjhZs8XNAiH2hYmj0YaaVPai6nLIp5t/XzzDl0Q7eVrzkhXi66j5SsbjsC
eY8FTVIhLFpAd8IozOkTB1CltGqvlQPQxBo8sT13EQpn5gw0h0ifWgG2dnuHuHFZ
6qjsoqFwnFb0x26+4Mw8oJ1HXPk7uhBb4HPNxkH3Er69fDA4V9u1kp7goaxkHjHr
e/YlfZd96+mcj1sXxrlYkajbgS+a+f8DohXmMr3F39vmMVDW8C9PkYwuJR/MuXBS
s9Z0CmseWME4NwTitYMDdES3ropzN/+4gnjktRblSvj4rb9O/ToIpgL8MFYniF7W
qo9Gz/IwRKQ0eJyF+elhOPY9C5JfkmI9SJ69egbpdZVbqvFIQ8bzaEGjuCMwyeOG
lG17uckAUTngT0/9cJfBNTCgjHEBkek4SAZudahnn533MIjwUoGCk4xH0oMj09tN
nrMRSZ6RKrHB751l6gzgsQllDTvOrB/WiaGIW6aDeq/p1bZq/5yHfozdwzTpUdbD
4QuUJ9JOI9RKQBM1fh8oArt9MqVXuU9TgoThA/0pCgvYSaa802aJEw8Es0ZXFoZR
iSDTegGqFhtM9Q+QiUSVMt5KWfwGUJOm7cY2+ZFxjrU62H5CaHdxGYibiYtQGQ8S
N4FDAuZysAqBpQyjO5xMyLjnc58EQPYBH8iVL/ddzkNyBaFCd2xIqIt9Sxmflcq6
1jddUhrsH6CAvOwfsv+IsVlm5sTefp4Ux1tM056BC11dbLWqf6eWNkGj0YByy0tL
BEJAFWZmcBZd/XRNgLPVk+b7cJRinFQ+h+KAPsmkoJABbxxfLbk3vyn/JzeoJT43
8oTEIiC7OPsAbwQU8xx10SVxZC0I5/YDqudLJBGzpVUmGllsZlQv4dKqg5GF
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGo2N0ZYUSBZVkFh
RDNNZCtySlNQTTZFbzJvZmF2TEZKVUM5VjFjQWNSeHlNc1JNZ2hNClVac2pEdWdw
OVpOTmhWWklVRDA3UzZWWk9QVG1ySUFmTzRSVlFReVdFVFEKLT4gWDI1NTE5IDVD
eWN2S29aWVNVTTdldXFxZG1vWUNYK2RrajI0OGNGaklzdFByTzlPUkUKTmsvdTE3
ZFBvalZsdENxR0VnV3FVSnFxTitBRlFWR1BzcjJWd1h0U3hpYwotPiAyOkUlJC1n
cmVhc2UgIWFOMiogVDc8IFZETk9BSDVTIHEKQ01rc1ZoNXQxUDdtWGRQUlRCV3k1
djNZeEtOaUl6Tkl5TDA1S2ZXWTFQZEFiV2pWMDJpZ2dkYXdBOGhSTVA2ego4UnBz
ZGhXcklMd0NVWlhsc0Voem1wUzMyREViUllCRAotLS0gMU5GQjdJaVdlUFJGMG82
YnZCUm05ak9qcmVyUVJSS0tHaWRRTld3ZkNPMArRgojrBQvlyjMhChn8jgUNDysj
9Nh3BVqf44fiUNMKARTrFTz3wVMlioo6MmKRzJ/yj0fWkZ2h80X68Nq3rQvy+0UK
4PNPRbwY5ib0nf7n3itFKjehrYr8HnOjreQps4it52bT5re2TWIC9ogZhNKBLRvB
qv14HjcF5bGnia8TgSU/8nUZJuIaLwHvS5sc9GOtmV4lDwiSTEJQEtGh716Jzuvs
XxQLPEVVufAjZUKLp+jWUhXGkvUEeY5bxE2aOLuB8kLfHPC/5p2rl/n2sW3oEDFn
Fw+XcNnCHWBzk3DlzKZFXwIaZL6Ocqrt+p8CBpWoNhbssuKyOtEbWQOmJxX3UPMt
sBFNdKSKFhoBQ4ukqkKDhgcm0i+8wG4eBbNIMIDwzPBxK0QiVriwlPQHl7RS2M/J
LgGeS7qrKC3SgmLu57CdUVUlnogJ4oOg7FPh6x+jQBIIv7gtYCzJb040TUZWZWvi
cfKWBBwLqLa+7Y6kEhcosLUgw2c9Et5P4rvNjU598q7qrZq7uK6Mr+PMwD3Pd66O
SQyT4nYhhXBnaIsLXZfima2nRzPocaBmfsuXQwlnLJC8JB6yzqEViK0xYKfl0fIi
jxuYBw/8Y+MWQhIkF1pP8z+Kw51QdK4CIV33mlLBJnGjlizewsjj04EIgpzQ0+3I
ZRyee/SzFmGBBk/pMGN0DizA8LS9yZ9DRGrc13rYVUTN6G5xT5AT1eEtFbQ6DH/n
q13Tur4QJ8AqJmwv1cnX+faH61dXahfbinveXWqB5S95/HE3OGnZfAiS5bs1jW4U
D3BLaBiFMYD+ccPedg5MLXRgEryVaOWlQ5wuXxqmaCG0KS6YLxl8o0bRR3lKXNZP
B7Ye0Px69aYtOs58dTOq0Peu039Fi5iNC0beocdKOjig4tZ6p41uz0jPGMUoDebZ
MYHax+a7Qpi5M6ZVGVpF1ieW2hqnm0lfb18cEYHuXjoI/YlgVhFPCGSzHJpuNiDU
qJSfOuD8HUB2iXFeEgAi+V3EKTsyjlS+R1zWxkr2awqAk7Uf3svLGWZNINHNoJRe
CPPANHzTSHlNPSelQ1CyOhl0W/eMCEJuJ9/6kZsmUC3zvzxEMGofG+Ub9RRBW/+/
TkrVvMrQv7/Va/4dxMPCqrdOQ+3/LkVzGX7qg6tQR76Su0q7aqh38Ki9qWY47bTm
lq5I2EcQApt/O1cGQJTFowdXVJXx/qQv436p/jL1mymQ8241iQ6XX3DUjbnqdI33
4XcPa/B7naRkgSG1Z55Vv+IIBOzcjzUMzeQVP/WM3SFRNYT79mJnV0SsKvUFtUOM
1OxMGEx+ZxfNIZqdp+qo0+uIU00ciokldh4URo9u8EIHjFVcV3Eyb7EbbCxzDiU9
iwoAAaJjwBlyaRQFWYsKhY2J3NEay142WAyAiHmnNBNhZM9TKkCpjton4ehxeRUC
hoXpzh4J2lMhdcmXXZ675C3MYiLDpajgLS1dwRp+mAUXwjvQG5rmJd7UWdgpbdpJ
K6wABNkl601XgKv3/nV9touE0KQjHg1fzo9vzmf8zVEViTgXxH3NECZHctObwgoo
SlbUOW8vihCi2HU9G3tqH7dwkUsgB2a22JFovgwwW8ePKJQpdvwAV/jyFMLL6jyj
6Tf0Gsc/Iut6Mh5TYiTWJdCzvEBjcybOVnppj2zR32/A/vAXUrJQaq0kwKkCwG+e
TL0hAQuA2Su27qTR9Xy2QKpQ5NppRH7V9YxCpjGfcLbcCh7XH7AEZaYfrrX8NoP5
zPalFA+7E1IuDRtT29wFNRVb3lAet9t5q0LUHf3x+Qk23dQ7Mdq2JIf9RIo5xZ6X
nsKa9eBQ0O5OAv1VGWVvbrVCMpmyiklYV2rcIT+dbrhrgiTu/BvODSvtmeANIkhC
uF112/+QW1UNPCENioYq9OWPcEwby+s9JkQf5nTub2o/0lTFKJxNgXa1QBcKKVyC
9aTCRhcxXsfuR7YzbCXRcK2hUW203iS8+UgAfZBVe5GhskOd0ZR0WSkse+jGzPqI
tnH5F0yQgLn9emebpL6dNFUVvssjpiMlvkijXFJv1tWSKLb3TxJd2BE/w5Zj7Haz
iv7Wz6o5+bY4v2YL2Ev7hGzfSaef+ip7/BYZ9TGuzSoZEHMYa7dNT8kRR18IhfXv
Y3wa2xlOhB+WE3AjtYy1oTi2c6Vmd9UKASZy/Eb++j1MSPtxZQLNaj+svCmqqfvO
/gElVfGlgkomYwc2EfJvR1lt9u9YGepxKrafPsgvjR6bYPwLF7eHKkKyEiqGCYnW
mcNJz/B0egxhFjoGd0U17tNuZcYRD/Tsj9ugGF+4/q+IaEV2YzZTdGzupI3lW17o
Q4H+EksxkWYDr0WRlYKn7VT1gTThuggEz77JskjNP4jK29EOIEO9IqGMh39tXsud
mhL2Z6XMX6sgxSjSkYxLpFnS1mRZ6uQnSptxHTfnG6jYhq//MTjGX7xmYBj5EoTC
duHuaCqjRXc3yHWoNm8jepkpbe1PPbwEbL6RDK16G3g0WVREjZopj+66C2xlY8Bq
ZCmHhud22QHs+5r1LLSIynsUlGIOvq30DZ0F2/f9Gm7uayIYbp8gA4z7M7RjmHoZ
+XHYSPc4kH4a3T9MucQQGzJMl1k/bifBAWLbu9uPcDUe7Cglfz0wHnPemOu8HZXY
6qS1n6PAHyyGlqX/pNWxR2vQrHJRksdTCvjl4g6256PhaMDop3QAHuQj9meYKIqP
MiZexiuOW3KjPJ30Gx1Q7PnUc+w3SjjsKaZEvRgeWKa37rvj1ICTnBQUPq2HV6y8
zaGnJcfd0ENAmFnRaCIbdU6TRonnIAAuSfE2gtLni/PJbmUpznuq82W//kxvH1tR
oPx6LuM2+hRncMoyXUTWSUVO1DAUPXgZA+j7fkkwmosi14d/5xe3wO4U3dGZ6wAY
D+zlzhq4d/4vIvLIL0NoBcp8yM+xWxPTtvj7HUJ7BOVV06ICnilUlv2wjR+dZLZK
DyjWhMoqk8r2TDZbAQr8MNX+sSlp1JxQEgBijpqBvRdF/ulqraF/GFDFRLcEi7D+
AkHPg4TrmaNB5ixsj72j0xruFJxgQJGj8fwQtemJGu8QcuJbCvJ74TI3vu0Pac/C
MRov1TAVJB7+iVvVNEgCMlzimJCSCUYh1Zgk5Ci2CDFMFRAEcGoHywIW6v5V8j5o
v30mJKZCVFc4Yibivjj1aGhQVO72vgiog4L++i7CeXHIu6Fe1Jmlve5iuxrQA2aM
9vjCbFJIi8k5vQTVcKPRcsto7/qbyxvWvahKBBbHl53XnSb0WtLUyTEaXJ+0l5gy
Epxfl6ZRNkt+YBYBqCZL9aK6TNpCR+zTC7OhrKcZntqFYCeRB25YEfc1z0lTH4SE
TPymGSMOeUX8YEIr+XNYG0H/I7ZtEdNGBEXaUrC4YUNrvojpq3PS9bhfLhAf5D11
wmI2tS9cqxAuS2zkaBr9Qo5xNEVsyGNFd1K0q0JJosiMnVZDzOA4wJTYhOw5gwcW
T1Kh/LDvK4RE0CV0jRqN81YO8V6Jq+/c2kgtwTLzrx1GCcdHRsY2kPhCEWOTLKb8
Jn5nyyvOOjMIkAEYYqoRRxl05UbNWNS9SuO2g9HF6JpNJmw0cmzT8erIIjVvaUs5
dd0Bd5EgkkLq3RMPNTiidQCyIy8QtoElmxYfM11mbf+JMgxsB3sDcRGN4lauUhho
JlayuFUfrQEwtHFwyULiJwUC55faVqOQzyvcQ8C9AJezDXS7//ku1kvEgbzK0nFm
3bCCiuog7pG9XlldSReYvlJtnqTmp6E5+m9YaP+yB2E+swKBORHkbCfBu1CuX6R+
aryBYIDeLaIiJs5GWJN//37TT0hVUCipY9QISfTiZ5tIy0aNKRu9syjWphcoTJuv
YBaRxKNdZs81ppFTa3sXB824+QXwzRJJPjBndXOtyX/a2U68NK3dqau/A5190VAn
bn7jnJ68rhcL9ewwydQRm72qwyxBbRHFXnEXpkJhzYRw6fosglqbOJMxFiy36HWD
RkVluvTInw8Em/DhhWgn+UrxLlQN6JnVdpkT9hwrlFWooiM56UKAPO2a1FM2XKXe
TqsolY07uWnA+ouiljqIpsaFlVDBcz/sGZWqy4aKKPBTcZlreW88PMsS0c9nixVi
nKnmI00HuzeL85zVHGo0IKYfktVVjuZNPzJ6+w18XpKIi8TxHpxELC9M2kVOCnuy
uwuntJlRty+GpXVvx2VLi1wBKyLpXUxhyg1tSi2sZyjIdVt4yEOKfajZYOp73JZx
IcInD7Cby9olkMsa25YhloljY8YELfW8EWutF13m8tsjGP9c6aOuMIXhrrpwLgYy
E27iFh9tHq0mjCMibwQnHld7Ccd4OqO4AKfPofHDxWoY9+ivArOBAXjsCxhxWWWp
aLqLJz+JH+idcsVDlw8jJzFW6pQFbM3VxXObvCg9ou5+P+Pc5XYyALJzIlmoOrN+
ns5Z+U/2XKGyySQASUyFXUNml6csSrTd+ejz1QvEX9POU1nLmvS1+aojgnptgdpn
sAtksQHMt1Njo1oRug3+/0iC6XWEig==
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,28 +1,35 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGdTeFAwUSBwVysw
SjB2L21leVY2blhaQkx2a3RQNVRuTlFtTzJWdVpkaTNOMDRZMm4wCmlWVUVNK1VM
UFFmaXhDcllxaUo1eElpcE5DMTcwVXp0VEVjZkVOakNYVzQKLT4gc3NoLWVkMjU1
MTkgVkZjdzVnIDRsVUNrTW81UUR5b2lNQURVTUEvTmUvRnpGV2dON2oyamVxSFk2
QkRWa2cKMmc3a20xNFF5UHdldkRwWXJ3UjIvY3FnZVFUSitaTTlHMjYwRlZCWndB
NAotPiBYMjU1MTkgNXYwU0tpUTN6eEJKVjZ6em5mU0huaEtFMEFuNDY2UkNiOVNy
UUJYQyt5YworeFZXV2V2TlJXYUhEWTg0ZEFUOUFGeEV4b3hDRVc4QVhJWEZnYWdr
YlJFCi0+IHtlI3NQLWdyZWFzZSBLYCBdPCBOSApVMjJweHBFTkNZQzJvQQotLS0g
Q1NFbDVIUEJNWURPUjlycVlxS3VzNUhmdVNGQncvNitWMG9FeVc3M1lXawrC8dSz
xEFTDaITaS0DaYMrqlX45Hrzjw0sPV/lLNvBWHxX57dWwGpCo7lc7rmfcGSPOfsW
gi0Sql5avPRrarXhGxGaruEbQf76qvZoBh9I2j8zWDZ818Nri1FqhUfTLcmZtb7T
Jm0miOLcTye8RD08LCO/tSCdxucbQ98Y6mjZH20LRv7JCEzCuUowcNZi+bU+z/ZS
xzfaT9HXeEA//9blWm64R5NRLwNrkzZxyIxZOz5i+Q2O395OIVmh6WmylGxKQ6GP
WweodKuqUt5eGVrrMPraaPXMhSA7R2tm3PSB5uDMIQbO5fJS6iJqC1e+2v30GRga
TwWgsVmAgdfcVO7q0bb5nsbcIOoNWBtXbR+a9rkhFiEZ9w60DQjntC4LYEB/eSIm
1tDCBsC64YZxhh/8oZ6hNpd/2N6ptQCV0lty/R9EM14HN2eoRSNYT93/OclNKkHU
ErxoaQgOC/v+RjKQhQj0Pt7+DqUsZt+elCxIUBZ4+P8imMf4eF21ERo8ExZ5MW5D
e/6RTG0sNlufypGXZ4SVi1aENO1dh/njOb51kuFaXqu7earX+0Fyl+nZdlCxfaWH
wb1Gkw3JdNebeL5z7Nse1gmEUDNcPdJX/IoQFrzpNdojLogampeyGX6u1u7+pzU9
Tb5FqhndWFy0q0i+yQdJNKRmSOXZopbGKAGWDF7dyvbom88LgEWeb3q8N9pTiVHX
/SOW6yf0mmZZcFUXehYjrYyQOFi368pIYMO9s5iVZz8AUkQFN+Hu9OrBtNyoEkoY
cR9CjN5UfVc4dXYOLAigdCjB0GnKDwHV7LfKPpIkTN4uYmL/92HdzqBDpUeK5KfD
nNOMRRGkX98cYH7PqB9HOXZBW/Td/uxWEd5FaOxnVN4f2B+PnT1avoKxn7x+eWYJ
IXHU3i8dEkBRBfXDQ+amC0n8y9sheyra15Ob1Ujwud0Oq1F7Okec3AifbZAAn8M9
hTspHjnl3vacuE21dzE1368ZVPvIPOuGM88MQGADjP5CBRlolpU5hnBKN6zQOjuh
Gpam+y/kbcopy5vIdnFGPH0vMAEnmEF6ER4OA/0=
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IERMTWVGZyB3QTU1
TEdvbFNaUmE4U1lCNzI1dlloVDBTNXdURjZ2akRMV0R6dm5ST0FNCk9YclRSakhT
NlV2UVJwak9Fa3dQNTNBK0xKWGlkNXdwZTBEMm4xZzc3azQKLT4gc3NoLWVkMjU1
MTkgakk4UkFnIGNBMHNrdG84cUQxd1lqQmJIczlScU1jNk52Y3BtR2tXeDNWdWRX
cEFCR0UKVU5vem5MZUFPbGhoQzJTYnF4OTd3OW9jYTRkazdocVJkZ0pRRGNLek93
cwotPiBzc2gtZWQyNTUxOSBnU3hQMFEgQ2RUaEUyV1ZGbGRtZnlIUEtTQXk5MUZF
djYxZ0hBUThlV2tXTHNvdXpVSQpMdiswMy9QNUtCb1hkbGRqR0Nia3FXTzE1ajZL
UXljSTZqM2YvbzVuWUFzCi0+IHNzaC1lZDI1NTE5IFZGY3c1ZyBYL1NrRjRaRnFn
RlNLdWJ3cThteDM3WmNaY210d3RmZzhCYmNpVXBwVGlVCjBkM0IzZC9zNjhmTTZV
R25ySkVoQWxQQ252WWFlWWZFMlc5dWtUN3VvVTgKLT4gWDI1NTE5IE8yUU1pWGFr
NzUvZVpwalB6aklkWmJGWUQzTnJiRjNzdzY2MUp6MkY5M1UKR1VIWDVyTFpxem5F
TmRNWE9zZStLanRTdlU5d0NOSEJKdWhDUjBCdy9vOAotPiBTJCd2NyFYey1ncmVh
c2UgfE4mYSBXfms0TC5FPiBiYmx8IGRFV35mciNoCjVZdHRiVUtMMWxEMlJ4b1F1
TGgxY01XTlZpTEtndkg0T0hRVjlqVUJGMUpSaE5tVlZWR3VGenpkUXY2eXJtbmkK
ZCtqRVN4dnZENXdoYkpjNHRWYXkvN2laY1p4YjV0WQotLS0gOHlEVTVOSmlNemNQ
TW5ISk1DeDlEM1RoQ2JtUFlyTXRKQjdnT0hiend2VQqrUFvr+76sKn0ldBmZMlEW
U2k85DLo2KU+/+GtbkZwVXxxIZHMLpoJgghHk9ptdalUgLGcl0X15x9jVaw8aeta
hbeOHotRHY7bC3z0S74riTk3xDMR1eT0QGhDMWHjfo8SkCftOYBlFfhTftevdep3
pKMZsuQMwH9JzxgUfcxIcWE975cZzrEJ85nfWMGvdSjcg51KNxP/UUPRxDlcbCEf
9XX5apSzNsTI3ibGD1n6Qwq8bdVYDMHmy5pAhw4l8L+SdoU1tGdw7JOA16sMCJbx
T4bV0ky/PGRonjJuCyDBj8oe9vMe1ZI1O/ITtktekS+wocxBs6QXlY7pIZMlGUn2
6m59ZEEaf7R4/MdnmBDNDkQuyXaKc7SaTc6h5sKWzXdYScGUKvgUQ7U/WJ2ItUTC
N/Xq07GkZZMt5MYBlyEr+/mKWlcy+ylJPGb7EswvQWaHoeM1QF0XLZ1v+W/Xsso0
seIoz+geSu9a02kwfsa8WvWXdIAT5X2pNGPClVNzjQ23pfQfQuW8ZQrGmIFR4g5A
58T1K+vGLdShqqVGyJFMVrSuOzqX5FVmZalu7/++1IQfiRGUlrHKoPlKWnCfFEOu
AYjaPeEFX2ByxcqfMK1YVPvUufdISUQeaQOO7mXGE3FqB0oUqmRIUiWZATwhq3Pw
p5QdcySTnmMpD/w05hvwski77kCdmYuHlMlLZez/kfhTnIGXris+Vwi/V19bsZ8G
zwaZ/Xr6WNC+df5JqSfTGREnXZPFRDkaTt3ri5/eEm6BqliuYjGbuiKsDECi4+JX
bHpH6LBBoKQ6ms7jCAn0Ls4cUKF37PcjGAOuWnzCSBU+REht1EDfHzx4C7hNiP8X
87NjEqJbwE9lORho0hQJRTn8uriQcidlVoB3se2SYKbMy8UA4NNnxN9PTj0TuQjL
OD3LtqHBElqNPbGNyyEAAJmMBmmkUvPPXlGQ0D99b1+jIdHzYSRtOLshBFykqWYQ
LJD61duhGqcQqcLx4+JdQ+oVcfAI2nG7YINnHB0OmS2DOZvvwqQ7ASScSujUWIjA
LNQxu3ruMz+bw/G0tYZBBiE=
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,12 +1,11 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyBpeWMr
Q2U1RUFYMmY1cW13bzZUNHBZQ1VEQzRFcHpIMnpWWjNhcHhQdmhJCk0vT211aFo2
RTFvY2JiblBUTkFWelVDQzdQZXhxUkxoQkFIVWQ0eTFNTVUKLT4gWDI1NTE5IFV6
UndBc3pwVUlqUHVPSE1rRTN2Y0FBT0duYUxNY3ZOTGtnWTRxTDZ0UUkKeTVBUE5O
dS9RN1l0Mjh6cU5GVXA5UFNCODVCL0hESVg4bzY5WlVuTFhXWQotPiB0LlVsXy1n
cmVhc2UgNWpFcSA9SGtwbnxDbSBBK00gMl4qJ2wKaGxPUmFQZkliRWI4SEV0YVl2
Sm1DNGZFN1pjZjNnbDFHeTFOVHI4cUJNL3BVUVNiS3RjVXJvSjF3bUVyUFEKLS0t
IFFVbHNUSjEvelA5WTBwQWo4SnlvTHgyc29RcytBbzI2SWhmYXU0WVMvdkEKmIk5
Orx8hL+TESnZ0RXl6T/UfQKJQfuNJB+1XFwo/OHp2FX4INjoxSGEq/wmR17+OxIk
yXNZJTxXmbjDm+Pf5S2NSselO9yh228EGNg=
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyBEcDNm
ajc3a0gwYmVSdkowak1aakJ3VWZ1Rk50S1JIS0ExVktVSW9OVWljCjJqbUEySlNK
a0xyT3NPK1BBMjZzN0RrLzhwVitvS0pzQ25iSnJSc3FmM0EKLT4gWDI1NTE5IHk0
M2pkWHgyMHR1WHc5bE4xWTFDTndKdkhRdTg5bzZIWEU2cm02UDZBQjgKUUY3dS9p
U3h1L1ZvTkFPdURLZ2tSYXJXOGNZZ21KVGdIbXdhSUJrd2puYwotPiAoL0RtO0lH
OS1ncmVhc2UKVGthN0ZSU0ZFTTg5YW9UOXMwa3RnSzFlMjE2VTN3Ci0tLSBwMEMx
Q0tQckd3SzBwUUE4SndMV01kUjVrOTdDWmxlcWs2Zy9TZk5yMzhvCrBXyLBZGuSD
dJodNI13obTM3UvX6hSgQ8Su+J3fOKr5NibkhQ0Auvlr2tUXhhDm2WOUlHjqVTq+
jWLSyhioDlIEyBgk8Zrl7KGeDzBi
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,14 +1,15 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyBrVVBF
elo4ZmFnZVRkYzhIWTJ4c3g4bEhmUDNXejh1REJnd3dzT2QvL0hVCkExM0xEVWY0
a1FVTGt3aXEydi84NkxFcE5RbnFQRkVuYU5lYkkrMHhFc1kKLT4gWDI1NTE5IFp4
Ty8vdVV5T3RpVURYV0xXNFhQcXpzc2ZBZ2kwQ2JoY0gzNWNLZmNzQ0UKdStpd3JB
TTYvMVQra09Ma25xM0ZJODdoY1Q3VkdVVlF3dXc0V1gzN0x2RQotPiAxJGFfezso
LWdyZWFzZSByeCh8K0cKck5FTE85MW5mNzg0ckVOM3lNUWhIeVhOYjRaYmdjWXVE
aWVCK3M3cnJ6eFhPVU9GbGZtc1pWV0crdHVzakdWUApJU21CZWY4QUxpTmgzZWVu
bUVKWE50REJTKzhNMXgzbE5pSzZQQQotLS0gVGFEUGJEaE5MSzgyQ1RRSTRWVmJs
Y0xXYy9WWmVKT1p2ZWR3ZVpKMitUdwoj0dhAT0GqfI3GQtsiApILEWh59elSbB77
dq1FWJowbJeNRcXwRI+54Umt1awJ1ilafkTpKnqSof4GBTbRrj7qHAOU6GPvBz4v
jj8U3xOClJ8pGvN3HTVACYyHouOJa2IznFsV8DUc1J2Eq8TY6AZ2IwsVXrakvZET
nwzpAUL6xDVaNRDEvFxr7qLWMVheif3TOucY
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyBzaEo5
RzNndHBLMUl3Z2txdVZoOVJBbWsrTVJwY01WeHQ0VTBaSm5VclY0CnJLYStYNEI5
VTN3dEQ0ZjBKaU1PZSt3d0lldkE5VC9wNHdWazNWV2F6WWMKLT4gWDI1NTE5IEJ5
amlzTDhCOFF0TllPd2RsME5Cb1lRQUZPTGNvOEZnL1J2Wlpnb29GblUKT2t1NFZy
blNZSHdxZjlHTlZmaTFYT0laQ1IwOCtDZ21vTjhxM0owdTAvawotPiBLYS1ncmVh
c2UgRX0nVGtpSCUgNVNDWCVwIDBuPjM3MwplK2cwc2htQ2RLcFhUZ0RSc21uQlFV
Y1QwSStOK3lKYmVVQ0J5RXA3Zytaa0JoMWtlWU9qMHBLNktFZWxtbFpDCnJyS0JJ
WEcwbndYbERreFhLZlRyQ3E0czB5NkREancxZk5BTGdWRkR6N2NrZmFrbnhSYlZZ
NElHNEEKLS0tIGVLdXAxU0ZaZVkxaHV4dEZNNUVuc2J4N3VRMzZzTktSMDY3bDVB
bWtmUVkKigIZ3J0s23vNzmbzJGjSMGBXK6o6xnsA9HXeQZ13VgKv5Qv+UHu+Z0g2
TeKdQSrHbDB3ydIxaiXsi2ivULdrIMCyd96rEJFxrkVuVqSZE0ehG5j+o8lKk4OU
PDe70slbZrVYDSu+OOUOCVRSopZreCURlYn2Pc4rjvrMAn5r1r+/AxjZMdkmmQZ0
l0wMBTus5zZzKg==
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,12 +1,15 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyAraW1l
RHIwOVJscnB6dm56VTVqRkhGQ1FGbFNQRENqUk9DTWFHR2xwc1dNCjRHbjZhanRU
Z3ppblJ4L09kVVQrMEtHbkFTL0REY21pL0Rkd3dIdFdHVjAKLT4gWDI1NTE5IHUv
QnV1NzN1K25Sb2R6Y09wNnZpMTVld29OV0pJNTRrcEt2cDNmaThHSDQKRjNmN2tG
by9JMXhhYTFRZDVDOUJSTUIzVHpFTlQwRXVQc0c1QldBblhDYwotPiA3PHotZ3Jl
YXNlIGFhe1M1Ti8gNUEpQWMgTl0KWWtuTTVKOAotLS0gZk9BMHJFT0YwOXBPR2Za
a2ZDMXJJbzVETEN6NmtZaXU2blhwMG0vbW1GOApWIqThRiZNsfg1onVuyNpfxwPl
w2dxnKKGn57z0hPzoi+yDmxZhHWEO+rYS593Jo/RDRBBFTxJslKvPPyIPrR3rslr
Y6/VyfmdxDkLBnjwlLkREJQy4buK0jDYv6Un/yR3psSp2ufcsW+2k57fD1uv0kpc
HB1eGAwRXE3cfAEABTeXSIsX7gCGPWjRPswtgUx2wPXx
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyBoZUI4
bEdWSW1pcDdMYVRJLzdQOWhsdTlZMmlyZjRVeEFwU3Z3NzVFa3lBCkxRZ1FYeU9D
L2dramp4WFFlREZ3NTFnWUs3clVEcnBhLzExclVac1M2SG8KLT4gWDI1NTE5IHVZ
bk9QYy94Tk50c1dVanNvNzhpYWNqeFMzVVR0eTQ5OXozMmp2VFlad2cKaEdvUnR5
ZjhsQ1FRaittQXFkbnRZei85MmtWb3pXRVFsc0RzNzBPMXFMWQotPiAzSXJZb0RE
LWdyZWFzZSBudCBGIDklTWIjVnwgM3YvCjRVYUd2TForc0gycnRBR05MbU90QmNu
dXlmQVB2bnZyME5heXlyMUdGTmR6SVd5SHcrTThrZEJYRlpnUmdmWnkKU1lyNjQ3
T0J6b3NPQ1FYZnhxTjFrYmN1UUJzMjEwcDBwSTJkTnlmeHl3WmFFTXB4eUVMWm9i
NExUZzM2SHlLLwpiZjQKLS0tIHdqakpMSUNOdnlBcVA4RGJHR2d5QnJNT2dVclZN
djd4WXBLSUhORktzZ2sKzMjxqL3UPrtGmXDijdfu0AwxLJooK7ZKauYvXWSuZUrR
vl5i1QPMEEBYEl+NaZIFgxAFqWpAXHQ1VxSijMWxNjKUd1Chq0tbLrpbh9wXJflK
SW19Wyqc9eTb8BnFoOSYWpDvAs2Lbyr2kJkOj2TNrwMv8nDJdOB8XIPlV5drCj1q
Dx/1zWqm6NMYxsep6eZkv4AzxA==
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,12 +1,13 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyBlNU1a
dFNpdWEyeFpXbFNQWHN2YjJlWjdDNmhvaG5WRzh1b3M1VXVkVG1JClVXOHZ4ZUpL
UGpDY1E1VUg4Zk96SVhFTm1iNllBYnNUbWphYWNyYXFISVUKLT4gWDI1NTE5IEFV
UDhuNkJ6anRrM1RDS09RZk5HYVdYMTRhdUhFTHpua2F1WEVNL1lIQlkKVEZOaHNM
UDBER3RmZXh2TEo4bm1NekZwQXdpd28yR1lYL3JadjczcFowdwotPiBNNW5QJlBH
Uy1ncmVhc2UKWXVNYkV3OGtkYkRucGFtZW5NcFF3aDVGdER4ZkRnd1VxdzFONldo
NW0wV2owVG8yck5XUWEvaUxkMHRWTU9DegpXSWJkNmJaaElhdWhsR0pmeTB5ZUlw
TlROVVNXCi0tLSBJeDJqcFJWS05LSVJXVDI4a2Q5Wi9YZi9qL3Fyc3NwL3h5L1Vp
ZHRReXBJCnhKAU+NGUjF/Kzo60E+TfxVyQv1I8JCOTBcTBr/VyC0YaB/8jJGY1C1
ynYfUAT/MLEeuscQC4lSz6zCncKL6xWSwCzGSEmycPR+wg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyBrd0pM
TGw1anJNaGoyQ3lMUis1cHVtRWFPcFFNZk9Cdm9TMEh1QTdKc25ZCm9VNzVQcUxE
Njh2Wi80RWszSU5NczRNcHpVYXpRMzJNcDRsdGxyWVZsUDgKLT4gWDI1NTE5IFpT
MEJVUkcycVFJaVFOUURoL29VT3R0MVJCWW04NXVRVEpKY3hlY1lWRkEKcENya1Jx
VnBCMVBrZkc4VDkvMnZoTllCL09vQ0VOZytuMnRvYVQxL2FldwotPiAzLWdyZWFz
ZSBCNX0gIiB7InIsfCAkOT9uM2UKUWpSYTZ1dUo3SHNaUlFibTl0UDhTaFh5Mk1n
bFp2cjM5RGhqU0s0Qm50Zk1pUVZmT0R6ZjBoUG1EUGlKbHFzVwozQ2t1cWVDVVAv
Q21QTjF3NW5UdTBZRldnL0RmSlV3QndHcDB0d0lvZ1RHTTByS1ZwNkNVQnU3WmVn
ZwotLS0gb0Fxb2FsYzgwRjJwSUF3ei9hZVR2Vk1ORDlIMWoyZ2RTd09hUmtvMWpD
cwrEjdaYfoGZ9i/S97xL9QvA/yii+sJLeuUzzv7a3DE661eQ5ezurV8Qz1tIhxWG
RsOppaaj1podFx3U1x7QQbLO6zQbJA458RMjYgc=
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,11 +1,13 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyB6cExv
amxYQkFFT0tuZ0d6TDM3Z0pqR2VJRW1zU3kxeU5KdVdrSEJFQVNZCnpLeFZMVG5j
ZC9UVm9tVG5pYW1rNndia1l5Ni9vVUVHVDJrTTMzWm1mNzQKLT4gWDI1NTE5IDJE
Y2V6dStNQW5NeTBWdlB6MnpmclZXS1AwL1VBeGpzK2owbG0yNGluMFkKbE52cm14
R29YNkZXa0JmWldOeGNqYnM0Y1RxbTRHK1N6U0VFVXhzOTJOdwotPiA7LWdyZWFz
ZQp0UWpNSEwvNUxOeWZGRzRjM3ducTRSUmlmQXB4M0QycXdScVJRNGRUS1c0MENn
Ci0tLSB0bTE1ek1uYzhxMFFBS1R5M2Q2dE5Cd1VFaDFycDVnQVQvbG1tK1RGa1lJ
Cjydk7j73bl0Alw3tnls5qh3LIMgXabTio9Nk+qVGm2HYaxgza9bsDqDZ8cI0ttD
+lXtlYMEl+/ZiHEleXlnY/Af3sUv5b6rAdwWzg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IG44Q3BVdyBHNWg0
WGRGZnlTdEhsRm96T0ZMOWx4UUxkTitJa3hMWDE1dVkreEtEa0RVClpvR2I5NGpz
aUF0N1VZU05oWXUzTEI1TVBLYlVVang3Zzgwbi9teDVlQlEKLT4gWDI1NTE5IG4v
OXRIbzhaZEdXc1pjTnRRWHVxc2ZTcmozSlJkY3lyOGtvd1BVeGprVUkKdUNqSW94
SUIyOWtsNU83cnVOTUxNR1BvWHJPczdzUWc4aGp1MjlpZm5ESQotPiA2KSV2PVx5
LWdyZWFzZSBqezVxOHc9biA+aD4tP20mXwpwRCtMUDhmcVhGNXpCZlFmSllpdDVp
SmFZelNhZnJlR25DS2l2MlQ3ZGFtdGxkZEdEWVNrRlk1VEZBRm9GMHBFCkVlQ1hp
WnhOTGl2R2s5RDRKN0p5TmF6Y0cyN3ZlR2pDZlhMVjQ1c0FJN3hCbFEKLS0tIEFT
bjFiRStXMmJueHdsRm1nU084dDRpS0tBT21ENzZFclJXbE11NWJETm8KyMHU+tZY
QELtZCbXKWnP8QC6V84JIFAxoRslACwsIJZpogcZO/IFIV2RGunGjCJk6QBmhOPV
kJXRcGO/ndYjWfuU0U3+9HtPocnO
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,12 +1,11 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IC9FSlh2ZyBHZFA4
c1BSN2hlZXF3R2JHYU1PVnVyMncwVEt5aHJMNjYwSWxzbTZBc1UwClZZNWpMMGZS
cVM1NlpSc0hxLzcrV3REdkdiSjIwUFlsVVFtdE1HUVlaVDAKLT4gWDI1NTE5IEVW
cEdSeEx1bVhJb1ZUdngxRk9OTmZ2ZXp4UWtXbEFPR1RxdDUrT0tVRFkKNkZ4UGlS
Sng5cFN2WmQzbDkrclZKTXN1OUFaQUVPSnNqRzBvTnppUURrMAotPiBnZy1ncmVh
c2UgU1lXNHcgZWMuPCBycnJVQSBtClp6MXU1QnF4OEN3WktBVENneERnc2FLaU9l
YTFNbkMwUnVWNXdCUWhETWZOa29Nc0xseVB4UXo4MlFFbmJ4enQKWFgrSjNwcjJh
WklWTUVpdwotLS0gdFRhQkNBaFhDbG5DUC9QcE5xKy9yVFdYQklRMWtWRXlVTkNF
M0I1bE51YwqBt/qRzgVI1cCuPn7IODeAF/MLbSzAUCGeqqM15vY+P+oBuPHptNG2
/uPhBBQ5ymuPR9akl0NrLDgsfLoY7JmyNdKXFsGkyGRlpgZpAXGc
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGpJOFJBZyBOZ1RV
VVpOZ0Jhd2x4YklMOHdkNkd0MEM4ekZiYXFKMjRIdXlZVDFkOTFZCkRreDZlK3Bo
WmJ1MDBxSU5UYzRuUDMvdnpVc0prNFF4MjM0M1FaZmg4V1EKLT4gWDI1NTE5IEkz
THNsVDVCbU9KYWpXL010OVRmNjBVVmVyVzhRZW5McVFJcnA3ZWJvZzQKOE1lQnVX
Nk1rWVAvS1lpbDEzb2ViUUE4RlB5NnJOZmNoUGdUMGNuMk1iMAotPiBuYVwtZ3Jl
YXNlIHdYXzh3MSB0TyREIGpdTjheeQpJZ2pYUyt6UC9vVmRNZU1uaDdvR2ZUNG0K
LS0tIHNCRno2V21tZU9XV1UwR2IvZDdkWEMzZDI4V25Yb0lvdmJadXNEZFV6TEEK
81uT8S3QJNe+mVadi/VpXSPEP0Ygzm0/+1pB5qqYlSQEWTHqS55gCyFCwu+sjaDq
DpYTSm1JAk5ql9NRj4fJvCS53lJZ4zo+5c0iJKmuRg==
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,11 +1,11 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IEJhUWxSZyBoT0s5
eVFJQWpjZFNoS0hTL0pMeEMvb2JsTEFmQW1WeDZqOW5PUTVDZURZCmlXd2tqM2Zw
QlhJcmRHajlsTk8rNEgyWEtGbENBaGhwUElBK1VWRHJoOHMKLT4gWDI1NTE5IE41
V1poMTlPYkt2d2JFU0hnZHZBVXZJTC91d2QxOGMwSXRXb3pPb2h3R1kKbVFpZFNN
NjA2b1R4YkRtZjlER0VUMThDU3lDbTNvYTBzcnFqVmt3YjliRQotPiBnWTc+QG8t
Z3JlYXNlIFtqYSBYKXBxW3BgQiBrIHhXWU8KMjdUUHN4VFQ3K2dEQmVYTWV3VG80
N1FicFZTeW50cnJkZUMrMHhlN096Um5PUQotLS0gM1pCZDEyOGdiakRJTlRWZzMr
Y2ZtcXRaK3RCcG9OV2hrOG43UkJiRlJLdwoDxkSGj2lFD+ewF0tFCx/oCPanyZ34
pkIDm9ptOluOUTxlmz9gYDZcMv57wfQ+zhli
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGpJOFJBZyBBOVJi
SXdGVDlXWmVGZUM0OTJPUWFYem5qZzAwZWxhNzhpT2t1VjdZU0ZrCjYxWUE2VW5o
cGFLZ3g3Y3BpUWlWOUtUYTg4MGZXVHZvdVV3eU9iZW0ybE0KLT4gWDI1NTE5IDIw
QmxaWTY0WjJFZUd6TUxqaHhRelpRQ1hGZVBEcm43d3JYUVhpTWp4aGcKVzhJdFFu
SEhUUUZVNVk1N2tzekpzUS9RazRCcFBhb2xxbkhRNEwzVys5cwotPiBkeE19PHEm
LWdyZWFzZQpwR0xsb05JVlFXQTZZQkJSWHY0akNRdjh1eXFnbmFDWUlCM0xLWXBi
QkVuanloRQotLS0gZTkrT212MXdsZy9Kb1AwMkFHU3VsTElweGNlYkZ2UWVXRzkr
dnB0SHRnYwrveLSY6SdUDO+QH7WGniLIOPcECTQ7CiTj9lwD5Hm0rYLdvizolb33
CsGX/kSEI2bD
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,11 +1,12 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IEJhUWxSZyBjeXUw
cHJLL20rYW9PUVBUMjk1RWtoS2FJMG5PMUJ4VkZ1b0ErSE1kR1h3CnB5VXZMbklz
RU9Bam5kaFRFZ25xTUpzVFRaU0ZqM2pCQnppWUpmb3JWVlUKLT4gWDI1NTE5IFRp
Tk9lSVU5eVpQd0w1T01xTkdNZnlhLzJjVVMxWTZuSTJ3c25Xd29WaVEKUENlR1FQ
eHQ3em9URDRqWWNaSjE2WWtGM2g1Q0grK0lGMDlkVnhTc3hYMAotPiBKXi1ncmVh
c2UgZHlfRWgKUXpTOTVRcDBGOUN4MjY2MktheEFVaWg4SWVRUEVNYkNWMkd4VGkz
M0F5SEF2WmhnTEltK3VPMnVKdwotLS0gak5vb0labGZxaEJJeExMZG9DMTBLcFhu
b1NUVGFPVWVDMlZkcGZqQmVIYwrDGRcof+Y7YXHDVIAehITHSDlmRAh+/EcyZqF3
8JPmV2/tb1xsGuY7ykyElnbzSi3m
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGpJOFJBZyBrTW1E
ZDFSRXIwbXZXUlNJbHZCVjFzMzRxTmdtbDQ5eWVOWWp3SEdSUERrClVMeFRLOGUx
U0dmT2ZUUXlCSGhJdE5ibkZLMmZ2VmZFWU9PQXpGZFRPQkkKLT4gWDI1NTE5IDIw
VXd5N2EwTVdhdkRYeVF3c201SnV3TmxnUGpOZWY5bFJaQWRWMWxQVmMKR2RNc2pE
aUs4ZW1XSGU1RzM4bHZYQks2cHZWUkhZN1hob2d1QnM3cCtlMAotPiB0XENxMSct
Z3JlYXNlIC5LIEIoJ1w6Ci93eE5lMUxDQjE3dWI4a05wUE5sV2dWN21ZN2lFRThv
YVJGNXNWaEtmbjVMaXhnemVuTXhOMXFtNkQvbGNuS3oKalZ6bEtGdHcxYnZNb0Zy
YnlGTDdoVVdyeTJpYjJjZkc3alUyV2ZGa0taeEFIYTdiRnZmYmRjR3BzNUp2Ci0t
LSBROCtSMzFNR3hTK1p5NVhGcFpqM0U0L0MwbWpzNUNTUWUyNEVXbzFFMGpjCnot
A6mYbp+jhpoyjZidXQfzLVcu6y34WqAfJZsfT6l5SJONVfSvSw+iP7XXW2T5OnE=
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,11 +1,12 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IEJhUWxSZyBEbWZv
R01SaC9JdWcvSXdJS0F3T3pRVkxnZHBJd2lLeUN5N2U2Ui9OZUh3CnI4cXpMeEp1
dlZQNVJCNkM3c1RxZ2ZYczBmaWtkVzZ3RUk3TDZ5RFcrREUKLT4gWDI1NTE5IHZU
dVJzMUdDdXltREh2VjdlT3F5TGpoei82SWZRd0hHcGxCa1FSK1RtaEkKb1ZKSmwy
ZDlBblpCejRRVGhadVdjMWRmSXd4THFHMkNpMTBRV0E2U1gzOAotPiBsIW44eC1n
cmVhc2UgNmB1byB7VG00NywKSGdHR29PejdDNE1senk3L3Z5SEdYN3RVUmdCdC85
NjluQkEKLS0tIENzajVxNFZjM29kQU1ON1J4WStRZmJBLzViQTBwM1BTT29XU3NG
V3YzbWcKDH97AxK1rbC1vyV4CmnsicPyk2xRNL/lrtt9Z0Syuu9VXJzL5gdW2PuS
ayp71Qho1w==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGpJOFJBZyA3dDEx
SUw4QVh5T3QySHFFemo2N1JWTjNCSGRiS1lmRTkxVEQ3S1NKR2dNClZNM1Vwc3dF
elB0Unk1OWpUb1RQMVpSTUpJUDNrZzFMWkhKK0l1dXVBYlkKLT4gWDI1NTE5IGRF
TTZlMjdaamlwTUJzYlVkcVIxbmtybkFwakVtK2craXVBNVZraGZ1VlEKN2VvaU5r
VjNRS2I5WE9kVDhES2dWTUV2cEVJaXZxNzRUcFo5blhGTDBaawotPiAxXCJQemxa
dS1ncmVhc2UgcDphKHFVTGUgIUonYydBPgowQnc2Wk5yS0E1bVBCWmpOc25MWWs2
UTR1dVlJbnNXYUVJSkx5bWFIcmtpT1lPSVE3b2Zpd0JaTGZWcEtjelFDCitQcy83
bEVvM3FNTEhyWVVFS21tS0VQWG5OOHFza28KLS0tIEZZeUZwVGxUdEdtQTZuYk5K
SHJaUDhHZ0JqbGNFSjJCaVlQTVo0OStkMWcKz/w0SnoHxnw71gr5DbXgMl59Kgjy
SW4tzNGeRcX2j4YdRjr77TP5UAzpQE30tEcrtw==
-----END AGE ENCRYPTED FILE-----

View File

@@ -0,0 +1,13 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpOcUlvZyBNalB5
RUZRNE1CTUJsbW1kSkxBSWVIcG1RUnBKd1gvcnRQVkZCUXFOQmhvClJUN2ltbnNk
T1grdVJSTzIyNTBTTGVEckVGQXdYNHdwOU5NbW1md3lGM0kKLT4gc3NoLWVkMjU1
MTkgcytxUmZnIHZ4bFZSS0huWFBDbUhNcTd2MFhvV0lOY1l3d3ZXNU4vT3dwMmlI
emhoV0kKcDF4M0FPK0JpclI5Q3Q5WGxpZWVYbHVWbkNWdTArclZsN09XK3VJSXc1
awotPiBYMjU1MTkgRjRCNVZmcXVnQnJ4KzZoM1ZkdWxYUkJTM1JuK3ZlRWJYdkFR
WXpFSmR4NApTbU5qR3ZuN0ZmbzIvMTFsMkdNSGJXSVlrVmZPdnZvcHFiZW45SW9I
endJCi0+IDEoIjlcJi1ncmVhc2UgJUE4IWl5ODkgfGVdLihEfT4gWCAreSduPS4K
bkI2Wm9LRGJXdW11aDl2VgotLS0gTENqYjZEUUZaWVZEcWQvWW5yTzJEdHRLeDJm
QUl5aytXdDE5QVMwVHZVSQo+aDbaGNOrz+hTSUQ4IAjDC9EfNwrlXDZtBqw8HkRv
1/Rr737scjrM7Bgt9zuKn6CB0zdeHTW5u685V2hCW/3aTy1eppWMWj3r
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,12 +1,16 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IHMrcVJmZyBpaDdW
UnBhRU5GdjRJYUJUSWZ5SER2clJkcUNuT2lBNHcrQ1RFOGdYRDF3CitVeUVWdmdz
Z2tiK3ozL0MyYWNIYUxXQ3dKOTA2dVJ4ZXdnVS8rUmV4S0UKLT4gWDI1NTE5IGF3
VTBrUVQzL2NtbDl0S0hmb1VMbGtQMXBsdXEyTDNmOXVOQkcwZlZrVEUKOXROY2Mx
dzZhMzdpR0pIZERmQmJwZ3BhemU0amhxQXBaR2ZGS0dVQlQwdwotPiAuLWdyZWFz
ZSBuOVBkIGBXKjJKcDcgT0w2ZCBNCnBEY2VPYUN0Q2Rvd2tSeWc1aGhtbUxMa1RJ
cE1LQkZPSEwwZHM3NEdXbTRVWXBKM05YbwotLS0gNVlxRElEN0owQzNFUEhEOWRE
TDhOeWE4aTF5R3ViTk9rSGpmaEJsa1k0NAoAFgMMjCVFT2ZAXX+aIQdGNZ7Rpf6j
A2KeGkhJ58vlLBM3wIKsyAXsQ4MuAb87JHhwWRGkqN1z6qzkGw+C+2qARyqXm8+7
soYKs7I=
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpOcUlvZyBFb2hL
MCt6c0I4Nm5SMFhEaWNZRTRtNkpYeVQvVGFVZzVqd0E3L2dZdzFBCiszUXZSVENo
cmxqdVFwMFA0elpFOGZoM3B6SW80MEhCNlJoRXV0WXpUeG8KLT4gc3NoLWVkMjU1
MTkgcytxUmZnIHQxR2MrakZmRFJMcjhabGFON2xQU2RBSDNvRCtuV3NwNGJ2bjZF
b2lCQVUKcFNzM3paS1ZnWk8xY1VzVmdTWndMK0JCTU14bUJvM3E2bjR2TmlTY2tF
NAotPiBYMjU1MTkgY1MrT3ZkN1pUd0JVb0JWSDByNUNRd1NUd2ZiNVJrc0JCb0J5
NENrU0MySQp0bFpwRXRZcHRVdnN3eitkNHlWc0c0a0NmUjVYSVFXSVNFVHI0b0ZB
U3kwCi0+IHQmKm9ERFdfLWdyZWFzZSBJIFkvRG5JIC9ZI002bmkgNX1hQHcKc2l4
S0N4YzgvK2xqZm1YVkl4ZlF1REVOTGRWOVZRQm80R2NnczlsdFlhTEd1RmVoNjZa
KzlkVXNpbGZsNTRybAp4RDlIWmRqR0t3VjF6WVlSeTJ4aGZBd1dDNEpMTUhZenZS
WjZCK0FZcXJORkJwc1piS1FvSlkzc3R5T2s3Vk0KLS0tIDFPODlkZ1BSWEhHUHJK
QjdZOE1KcFNvcUYxYlFkL1FLNVJETTkySVNYRTgKOipmWGTV9SvGE4KVqgQqGw4e
CLP4PYlgdSmOATTIg32G/GVTM8NlvaII3q2GNS0Enx7Y8YwnwS2dGkYKVN3Da3b2
WJwMiBZRu/PC
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,12 +1,15 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IHMrcVJmZyB2Nlll
Y2FvWHB5ck55dWVaR2lEQkpvNHdyQWxqRFJlM0xFVktDTjJDSENBCjJ6WlAweTc1
bHVmZVNDWDRsbE1BbkJibTV3ZkdPK3h6ZUE2NDdLZ3ErL2cKLT4gWDI1NTE5IFE3
SXBWSWpteWpaREtCdG5EQnRtUFY5Uy8xUHo2R0RsdlJJdnVUV2k5WFUKZTRidWk5
RDE2bmxaV3M5cXlQeWRXUXROMlNtSXIvTC9lWVRDOU5QWVJTawotPiBAYDNcNy1n
cmVhc2UgbSA0Zy9yIyA2ZjJ4blAgSi1gXFZXRHEKZElIWXpHWUhacUVMMEUzazZS
UDIrUWlIdzFQdjRIZjZ1SHR0U3JIUXRsMHhLNXpvRzUxVS8vdnhCdwotLS0gZElq
VVhnV1ZPQnN3UVJTdGNJR0hFaTZ6aFBKVmR4eWxqdHpkTnYzTlRvYwrUjxxTMSxc
07SzREkDIC7QgfCf1eFURkihesdoII10t6tyythWPGPZy99tMTFnSwN7p0aWkO6q
qJgsbysI2/FQSZx7YUdnLWn9Gvg=
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpOcUlvZyBjbUF2
VXYyVkRud3JFU0tDQThjQlVMTTFaaW5uTkt3K2dmd1pleTFOQm5jCkNUeEVqSXVX
RGUxcU14Z04zMlRubVIxV0VwazhlZlFtTCtXT2dVS3l4b2MKLT4gc3NoLWVkMjU1
MTkgcytxUmZnIG5SRTFOWWVIMU5yK3VTdkVtc0VYNnFkRXFrdEpiNTJIRHBvVnky
OHRyWEUKVXVvZlBBR0dsUXFMa2dSZ1FWVWpvZGlwcHJ3YXRscnBoaU1FWlVFYTdt
dwotPiBYMjU1MTkgMWlBWCtSU0I4Q1I0T0JrZ1ErYzlHRTZsUHhZQkpYQVJGNnFy
U3ZPeU9RTQoxaGZDYnRaNjBRRGQzMGNsTmZwcjhBRXdLeEFqVWxoT3hNNHk5cU9C
Qk00Ci0+ICF8Q0EuPSIhLWdyZWFzZSBKcCp6MgpaTXQwdEllbmhRVDhOQTdpb0RU
T1VGZmdZK1VEMWdPUXduYWQ2YWx5aDFTQ3ZzRnRWbFRGN0lWUU5iQWdPakpZCnl6
MnI0SE5sS0x2MUZibW96SllDQVVOK2grRldPOWo0VSs3SkFUN1dqS3RqTTdPZG1M
eHI0T1BHK1F1cWlINAotLS0gckFnWkoydklhWHZhZHBkSkN5ZmdadVdiMU1QOUZW
VkJENWlHVWNXcEVsWQqYscIBmSi//ev3IN2ax0Ei7p8Atu4nYQui7yoY/1fiyGQL
DB5+R9Dm4YUNHt3bjrBYclLohDGdLUnOB00BXUqNmlLm4psL4Ey5Go8=
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,13 +1,11 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGhNYTRudyBmR1BL
a3ExRUZvS25raU92YkRWY3hQWWJoYnB2Ry9Da1ZwWHE1Y0tQdjBZCldDZkVwYTZl
WkRGMkprekQ4SlNoTEpWUkxSbVBPRDFCZnYycnpacjB1OVUKLT4gWDI1NTE5IDVM
SDNqczJkSXczZXYwdkpiWm93Q1BqaTZRcTA2N0RGMSsxS2dJNm5paFkKMk0rUDhn
b2x3bFJsMmJ5SE8xUXJvQ281VmZ5NVhnbk9lL09qb25wMzQ3VQotPiAhfGNKdidv
LWdyZWFzZSAvZiA2OHIgSDsgKy0yXAo0czFhQS9wV0lUN3JBUVo3RjlySGxBSHZZ
U3NIUjREZmNHRFBSakpIRmR3cHprUXh6ditzWDROT1dqaHJQTUZICjhFR1dsVVVR
c1cyZG1UZGt3T1NuK2RnWktjbmcKLS0tIHRrZHFuUkk0R0lIaitYS1NPVWxBcjIw
aVR3d3ZkSU5kMGNGazQ3MksyMWMKiSNhBy5dvcGolFSkFHcWwzqnratvRNsbKaYp
Nt2DziTf6EyitXsktaBNi+0+SwtCQaRb9FMT3ff6zVTP9qweYAg2uqxLT3q1FoNg
Qa8=
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGhNYTRudyB1N0V0
UW5ZKzQrSlFUMnR5QUE5N2Mzd0FvK3JCeFM2RVRtUklVQkNkU0dNCnJuUUxjQjcw
NURFOFRWRG5WSnBPT1dOVjFNQWZVUjJUSU9WVTExeS9XZkEKLT4gWDI1NTE5IHdq
eDhJSTZKN1lQRXNaZzNIWEx4NTZINzZpdFRPZWJGcys5SUV6bmc0Q1EKeGlqTjF6
b3Y3ZWNhcjF4WlpOcXh1OHBucmNCZmp6VTI1MXRjN1FlNlA3VQotPiBtQ01Dey8t
Z3JlYXNlCnFKKys4Y0I3RkE3bzl2NDJ2aDNMeTRTUUU4dFFwelV3SjVReExnCi0t
LSA1QSthdHR0cVE4bXhiczZqUTNJdzkzdnZ2TEpESWwwQ3BWNnoyNmp4ZXdrCl1g
oNwCvlTrPx5b5l0OUymxMo1HMCV9fsQ9zyaoJoPFN1hv7l4hjjX5oVoj/IdTJbbe
ZMs1yqa7YbR4+HG9GG8nPhU36hchSN2whoz8
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,11 +1,11 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGhNYTRudyA1dUxB
Q0VjQkdvamFldms1RXlXU21KdDJVVG9lcGVLMTFuWEpBWGFFSWpJCmVBRFF4a3pk
NDBBdGJkZGJNVTErWG9zYis3SnVDbW9PejZuMG12WURCMlEKLT4gWDI1NTE5IDJl
SEpoQ3pZRkpNVGVoSWp1ODVoZXpCUHZMOU5RMVo0V3hYOFduWUVoUlEKRG1zblgz
SStaUlVYbnQ3ZUx0RzlXMm1nSGRWUDRVVHlOYUF6UUJ5ZURaTQotPiBNJS1ncmVh
c2UgbylbCmx2RHpZRHg5dGlVUnZpT0taR3p1bXd4NTlZclh3M0hRL083akJFMlM0
d0gzZVVBCi0tLSBDc3ZDcitIS0s3NVR6YmQyb0Q2bHoxNUpwU0R6TlBlVjhpdVAx
ZnoxMzVjCjB+mmwhKRmYzzjnyLDhQad/GGijeEN9L3zcpcdQtd9CdRyVrEH9qOpt
Nck07iGllfjDmELLIWr6obuol9/GplFiR8eSHteiOheFIYsk
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGhNYTRudyB6dGZN
VDF3ME9IWWJMQUVrbmlKeXhZbzE1THUwVWhBc2ZBQXdUUkpod0FzCkVncGlyTXVv
bmxPN0F3ODdvUmw3Sit1cERNTXN6YWZlU2IvYUhoUThER3cKLT4gWDI1NTE5IGQ2
RzdCWUw2N1BHWVhiOGhvNEdzQVlUanR3emdDV3FUTFRXUDB3RDdrMmsKZDkvWWgv
MFZhbHZtVDVQSWVEQ2NxQUZrOXFMVmYxbEU0STFJTVBzZm16ZwotPiB+Ty1ncmVh
c2UgMnpCPlV9ClY2TzJGcnBrbUJTU3lyNVlrNDdwYTd3Ci0tLSBHaU9ZVnZoUkx5
QnE4UXhMdEg1elE1dDRoQWx4bDhBMHNwS1BlQkRaUm93CspT9YnuzfpKxC9y6SWJ
JRyT8aFEJTjoDEqN2I/DBwRikSxKyspHi7grCwFaoofylqJzsP/In7Xlf91xbMXz
njjXbBQQP9PG3Z2c0OHk
-----END AGE ENCRYPTED FILE-----

View File

@@ -1,12 +1,12 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IDdXUTlQQSBDT2Q5
d1FJVnEyK04zNFBqc1FhRDhxOUFwRUdNcVdJRnI3MDdTeTA3cDJBCjQzUFQvbEFn
YmJ2eHRkTjFVKzFpM3hNSit4Z0hzVXVJZzFnSEM1Z0dNRVkKLT4gWDI1NTE5IGxC
clhCMFlVRnR2cCtOcUpBSVFvYUozcTc3dElOaXRJQ1ZMZUtNa0FWSGMKN2QrdEhq
SWllUFdRdGY3QTdTdGhKaUFrR1ZXQjNyVkFJWlFlV2hUZkFyTQotPiAjMyItZ3Jl
YXNlCnIrZnQwMk03b01NV0JyRUdhOEk0ODd3UG5kaFo1ZExDa2dqTUdjMEkzalJu
U2lQRlJaVFR1MjgrUi9YTkFIYWwKcWNZCi0tLSBnNThPdkJqSDc3MjBQQnI0azlX
NWRjazI2a1AxdnZscUpZOTAyM1liY1c4Csp02Ywz7dQmII/sZoDj1Q/XjcXuQYbc
E2YMSbw/CPXUNoX8vKbL4Rsz1rErPQuBu8/r4Bg7o+Xo4Q5dvHOk+dBofz/hwyZY
UdxWbRjh
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IDdXUTlQQSBaUm1C
WGJDSUNtTUYxbHVnWU44VkRnY3F6cTR0Tm1EUEdDYmltWHBrL1dvCnMzSEFqZTdS
K25tMkdkNEZ1dGhBMVhSUkR2M21EY0NtbnJjb0R5RVoyTDAKLT4gWDI1NTE5IHhv
RlJOd3dCa3ZaeE5JS0ovV3ZiY0N5QThNSi9LaXQ5d1JIa1UyeVFJekUKd216RGw2
Szg3VG1PWUVweE9udFpta0gxdWx1NEU5ZmNwK3hMdHdZNlB6SQotPiB3dlxKIXxG
LWdyZWFzZSBCLGsgWCU+UWwKWXVYQTBqVkRMKzNsU3JuWmlQdVpzdWJWcVdwVUM5
aHpmZFVPbWlidHJEYlN2M3NvUWNqVgotLS0gZHhwUlE3YXJSdHFkdFpkTnh0bC9a
TytZcmtxd0pldUg2YUEzMFJ4QjR5dwqaUnjT3oaUunudOqNfh9twKyaRttf4sk9G
uiiKoEa314HbI1vgS4iCNX4vG+468SECiF9llZL9U1w+1MSF1y1BKy7XrDCsp7Xs
HiA2aA==
-----END AGE ENCRYPTED FILE-----

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