57 Commits

Author SHA1 Message Date
Danilo Reyes
95c6cefd24 new wireguard connections
Some checks failed
MCP Tests / mcp-tests (push) Failing after 5s
2026-02-06 19:22:36 -06:00
Danilo Reyes
a8dda9d32d playbook 2026-02-06 19:18:37 -06:00
Danilo Reyes
7a5f577806 dnscrypt listening subnets 2026-02-06 18:40:31 -06:00
Danilo Reyes
a7482ee146 fix 2026-02-06 12:19:38 -06:00
Danilo Reyes
5382bf7251 vps known_host for lidarr_mb_gap 2026-02-06 11:49:43 -06:00
Danilo Reyes
ead7e5a379 re-encrypted sops 2026-02-06 11:07:42 -06:00
Danilo Reyes
416e8a4edc new disk uuids for vps 2026-02-06 11:00:24 -06:00
Danilo Reyes
2da1278b37 production ip 2026-02-06 09:24:14 -06:00
Danilo Reyes
60ccc776de redundant port assignation 2026-02-06 09:22:09 -06:00
f7bef14c19 Merge pull request '004-vps-migration' (#5) from 004-vps-migration into main
Some checks failed
MCP Tests / mcp-tests (push) Failing after 2s
Reviewed-on: #5
2026-02-06 09:20:18 -06:00
ed85b66017 Merge branch 'main' into 004-vps-migration
Some checks failed
MCP Tests / mcp-tests (pull_request) Failing after 2s
2026-02-06 09:20:02 -06:00
Danilo Reyes
a2cb88c970 knownhosts for lidarr-mb-gap
Some checks failed
MCP Tests / mcp-tests (pull_request) Failing after 2s
2026-02-06 09:11:21 -06:00
Danilo Reyes
b7ce1866d0 tmp files and lidarr-mb-gap 2026-02-06 08:59:44 -06:00
Danilo Reyes
6d5422f447 nginx fixes 2026-02-06 08:27:58 -06:00
Danilo Reyes
41298f0980 oops 2026-02-06 08:24:40 -06:00
Danilo Reyes
b7c4e38148 doc remediation 2026-02-06 08:22:40 -06:00
Danilo Reyes
005addff1b create www-data 2026-02-06 08:21:24 -06:00
Danilo Reyes
17cd7ba593 websites init + docu revision 2026-02-06 08:13:37 -06:00
Danilo Reyes
893bb199b1 temp disable lidarr-mb-gap 2026-02-06 07:50:46 -06:00
Danilo Reyes
44e39fda6c plausible ip 2026-02-06 07:44:31 -06:00
Danilo Reyes
229b989902 format document 2026-02-06 07:26:26 -06:00
Danilo Reyes
00a43a5a48 subnet parameters 2026-02-06 07:16:22 -06:00
Danilo Reyes
788ea5ad26 rules fixup 2026-02-06 06:59:59 -06:00
Danilo Reyes
1fd29a5f4f nat table 2026-02-06 06:44:47 -06:00
Danilo Reyes
a15db616b4 removed windows_vm key 2026-02-06 06:12:10 -06:00
Danilo Reyes
7cedfba30d dont even remember 2026-02-06 05:21:51 -06:00
Danilo Reyes
c50c98e7b2 firewall tweaks 2026-02-05 18:25:45 -06:00
Danilo Reyes
6079e6446c working version firewall 2026-02-05 17:49:11 -06:00
Danilo Reyes
afbffaa203 ip declarations 2026-02-05 17:02:20 -06:00
Danilo Reyes
c09268891e firewall migration 2026-02-05 12:45:39 -06:00
Danilo Reyes
e1f7c2291a testing on lebubu 2026-02-05 12:06:28 -06:00
Danilo Reyes
9e64325f5e nextcloud uses different proxy 2026-02-05 11:12:37 -06:00
Danilo Reyes
6603fac1c4 nextcloud nginx split 2026-02-05 10:58:35 -06:00
Danilo Reyes
cb1776d670 fixing 2026-02-05 10:41:29 -06:00
Danilo Reyes
3517e394c6 nextcloud proxy logic attempt 2026-02-05 06:54:14 -06:00
Danilo Reyes
81f9025dc9 documentation update 2026-02-05 06:36:09 -06:00
Danilo Reyes
2ef113bc0e synapse cert logic 2026-02-05 06:30:45 -06:00
Danilo Reyes
d14a7ba395 private certificate fix 2026-02-05 06:26:40 -06:00
Danilo Reyes
eddef549e7 hmmm 2026-02-05 06:18:42 -06:00
Danilo Reyes
4ba0fa0dd5 nextcloud nginx logic needs to exists in two place 2026-02-05 06:04:42 -06:00
Danilo Reyes
08cc3379ad use merge to segment the complex nginx proxy settings 2026-02-05 05:32:46 -06:00
Danilo Reyes
2a290f2fe2 it was the nginx module... 2026-02-05 05:16:43 -06:00
Danilo Reyes
0c7e745e55 plausible actually ran on server im dumb 2026-02-05 05:06:43 -06:00
Danilo Reyes
caf7fbc590 nginx ip fix attempt 2026-02-05 04:58:41 -06:00
Danilo Reyes
ee11d72de8 domain sandbox 2026-02-05 04:16:21 -06:00
Danilo Reyes
dce2142794 proper ip assignation for nginx 2026-02-05 03:39:27 -06:00
Danilo Reyes
237e120124 working 2026-02-04 19:16:04 -06:00
Danilo Reyes
afdb5bfd99 chichis 2026-02-04 15:03:46 -06:00
Danilo Reyes
d7f9ea971c vps keys fix 2026-02-04 12:39:33 -06:00
Danilo Reyes
f01817a15f iptables 2026-02-04 11:42:39 -06:00
Danilo Reyes
917e741b7f rg_filter 2026-02-04 11:21:35 -06:00
Danilo Reyes
0997fad0c6 plausible + other fixes 2026-02-04 11:16:45 -06:00
Danilo Reyes
ba4cf6c86b root logic 2026-02-04 06:34:40 -06:00
Danilo Reyes
3f13527e51 "fixes" 2026-02-04 06:31:41 -06:00
Danilo Reyes
efe5cb0f99 remediations 2 2026-02-03 20:44:09 -06:00
Danilo Reyes
86557548db remediations 2026-02-03 20:43:25 -06:00
Danilo Reyes
a74adc7f95 init 2026-02-03 20:35:44 -06:00
47 changed files with 1723 additions and 687 deletions

View File

@@ -2,7 +2,7 @@ keys:
- &devkey age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
- &workstation age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
- &server age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
- &vps age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
- &vps age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
creation_rules:
- path_regex: secrets/secrets.yaml$
key_groups:

View File

@@ -7,6 +7,8 @@ Auto-generated from feature plans. Last updated: 2026-01-30
- None (in-memory tool definitions; filesystem access for repo interactions) (002-mcp-server)
- Nix (flakes; nixpkgs 25.11) + nixpkgs, flake-parts, sops-nix (003-vps-image-migration)
- N/A (configuration repo) (003-vps-image-migration)
- Nix (flakes; nixpkgs 25.11) + NixOS modules, sops-nix, nginx, wireguard, openssh, nftables (004-vps-migration)
- Files (configuration and secrets) (004-vps-migration)
- Documentation set (AI-facing constitution and playbooks) in Markdown (001-ai-docs)
@@ -28,9 +30,9 @@ specs/001-ai-docs/ # Planning artifacts (plan, research, tasks, data model
- Keep language business-level and technology-agnostic in AI-facing docs.
## Recent Changes
- 004-vps-migration: Added Nix (flakes; nixpkgs 25.11) + NixOS modules, sops-nix, nginx, wireguard, openssh, iptables
- 003-vps-image-migration: Added Nix (flakes; nixpkgs 25.11) + nixpkgs, flake-parts, sops-nix
- 003-vps-image-migration: Added [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
- 003-vps-image-migration: Added Nix (flakes; nixpkgs 25.11) + nixpkgs, flake-parts, sops-nix
<!-- MANUAL ADDITIONS START -->

View File

@@ -79,7 +79,6 @@ in
"galaxy"
"phone"
"vps"
"windows_vm"
];
};
}

View File

@@ -7,10 +7,10 @@
## Repository Overview
- Architecture: Flake-based repo using `flake-parts` with inputs for pkgs (stable/unstable), stylix, home-manager, sops-nix, and service overlays. Common modules are composed through `parts/core.nix` and `parts/hosts.nix`.
- Module auto-import: `modules/modules.nix` auto-imports `.nix` files under `modules/apps`, `modules/dev`, `modules/scripts`, `modules/servers`, `modules/services`, `modules/shell`, and `modules/network`, excluding `librewolf.nix`. Factories live in `modules/factories/` (`mkserver`, `mkscript`), and shared options are in `modules/nix` and `modules/users`.
- Module auto-import: `modules/modules.nix` auto-imports `.nix` files under `modules/apps`, `modules/dev`, `modules/scripts`, `modules/servers`, `modules/services`, `modules/shell`, `modules/websites`, and `modules/network`, excluding `librewolf.nix`. Factories live in `modules/factories/` (`mkserver`, `mkscript`), and shared options are in `modules/nix` and `modules/users`.
- Hosts and toggles: Host definitions live in `hosts/<name>/configuration.nix` with host-specific toggles in `hosts/<name>/toggles.nix`. The `my` namespace carries toggles for apps/dev/scripts/services/shell, feature flags like `enableProxy` and `enableContainers`, and per-host `interfaces` and `ips` maps.
- Main server and proxies: `my.mainServer` selects the host that should serve traffic by default (default `miniserver`; overridden to `server` in `hosts/server/toggles.nix`). Reverse proxies use helpers in `parts/core.nix` (`proxy`, `proxyReverse`, `proxyReverseFix`, `proxyReversePrivate`) and pick IPs from `my.ips` plus the hostName/ip set by `mkserver` options.
- Secure hosts and secrets: `my.secureHost` gates SOPS secrets. Secure hosts load secrets from `secrets/*.yaml` and wireguard definitions; non-secure hosts (e.g., `hosts/emacs`) skip secret-dependent services. Default SOPS file is `secrets/secrets.yaml` via `config/base.nix`.
- Main server and proxies: `my.mainServer` selects the host that should serve traffic by default (default `vps`). Reverse proxies use helpers in `parts/core.nix` (`proxy`, `proxyReverse`, `proxyReverseFix`, `proxyReversePrivate`) and pick IPs from `my.ips` plus the hostName/ip set by `mkserver` options. Nginx defaults to `proxyReverse` for any server with `enableProxy = true` unless `useDefaultProxy = false` or the server is listed in the Fix/Private proxy lists.
- Secure hosts and secrets: `my.secureHost` gates SOPS secrets. Secure hosts load secrets from `secrets/*.yaml` and wireguard definitions; non-secure hosts (e.g., `hosts/emacs`) skip secret-dependent services. Default SOPS file is `secrets/secrets.yaml` via `config/base.nix`. Proxy-only services that need private certificates must still define their cert secrets when `enableProxy = true`.
## Coding Conventions
- No blank lines between code blocks; keep markdown examples tight.
@@ -33,7 +33,8 @@ config.services = {
- Factory: Shared option constructors in `modules/factories/` (use `mkserver` for server modules, `mkscript` for script units).
- Options: Settings under the `my` namespace (e.g., `my.services.<service>`, `my.scripts.<script>`).
- Toggles: Enablement maps in `hosts/<name>/toggles.nix` controlling categories (apps/dev/shell/scripts/services/servers/units) and features (`enableProxy`, `enableContainers`).
- Servers: Reverse-proxied services under `modules/servers/`, normally created with `mkserver` options.
- Servers: Reverse-proxied services under `modules/servers/`, normally created with `mkserver` options (including `useDefaultProxy` to opt out of default proxyReverse).
- Websites: Static nginx vhosts under `modules/websites/` (portfolio/blog, mb-report), gated by `my.websites.*.enableProxy`.
- Scripts: Units defined via `mkscript` with `enable`, `install`, `service`, `users`, `timer`, and `package` fields.
- Playbooks: Workflow guides under `docs/playbooks/` for repeatable tasks.
- Reference map: Navigation index under `docs/reference/index.md` for paths and responsibilities.
@@ -45,7 +46,7 @@ config.services = {
- VPS enrollment flow: The vps host generates its own key on first boot, then operators enroll the public key, re-encrypt secrets, and redeploy. Follow `docs/playbooks/enroll-vps.md`.
## Module Categories and Active Hosts
- Module categories: apps, dev, scripts, servers, services, shell, network, users, nix, patches. Factories sit in `modules/factories/` and are imported explicitly.
- Module categories: apps, dev, scripts, servers, services, shell, websites, network, users, nix, patches. Factories sit in `modules/factories/` and are imported explicitly.
- Active hosts: `workstation`, `server`, `miniserver`, `galaxy`, `emacs`. Host roles and secure status are defined in `hosts/<name>/configuration.nix` and toggles in `hosts/<name>/toggles.nix`.
## Precedence and Conflict Resolution

View File

@@ -0,0 +1,25 @@
# Playbook: Add WireGuard Peer (Friend or Guest)
## When to use
- Adding a new WireGuard peer in the friends (10.8.0.0/24) or guests (10.9.0.0/24) subnet.
- Updating firewall rules to allow access to specific ports for that peer.
## Inputs
- Peer name (e.g., `friend5`, `guest2`)
- Peer public key (WireGuard)
- Peer IP address (e.g., `10.8.0.6` or `10.9.0.3`)
- Access scope (ports/services the peer should reach)
## Steps
1. Add the peer IP to `my.ips` in `modules/modules.nix`.
2. Add the peer to the VPS WireGuard peers list in `modules/services/wireguard.nix`.
3. If the peer is a guest/friend, ensure `allowedIPs` includes the relevant subnets in `hosts/server/configuration.nix`.
4. Add or adjust VPS firewall rules in `hosts/vps/configuration.nix` (`networking.firewall.extraForwardRules`) to allow the requested ports.
5. Rebuild both hosts:
- `nixos-rebuild switch --flake .#vps`
- `nixos-rebuild switch --flake .#server`
## Verification
- On VPS: `sudo wg show`
- On VPS: `sudo nft list ruleset | rg -n "<peer ip>|<port>"`
- From peer: confirm access to allowed endpoints (HTTP/TCP/ICMP as defined).

View File

@@ -7,6 +7,7 @@
- servers → `modules/servers/` (reverse-proxied services built via `mkserver`)
- services → `modules/services/` (supporting services like syncthing, wireguard)
- shell → `modules/shell/` (shell customizations and CLI tooling)
- websites → `modules/websites/` (static nginx vhosts for portfolio/blog and reports)
- network → `modules/network/` (networking rules, firewall helpers)
- users → `modules/users/` (user-related options)
- nix → `modules/nix/` (Nix configuration and helpers)
@@ -23,8 +24,8 @@
- Active hosts: `workstation`, `server`, `miniserver`, `galaxy`, `emacs`, `vps`.
- Roles:
- workstation: developer desktop; provides build power for distributed builds.
- server: primary services host (overrides `my.mainServer = "server"` and enables proxies/containers).
- miniserver: small-footprint server; default `mainServer` in shared options.
- server: primary services host; runs most services and WireGuard targets.
- miniserver: small-footprint server.
- galaxy: small server variant using nixpkgs-small.
- emacs: VM profile, `my.secureHost = false` for secret-free usage.
- vps: Linode VPS image target, secure host with enrollment-based secrets.
@@ -32,8 +33,9 @@
## Proxy, Firewall, and Networking
- Proxy enablement: `my.enableProxy` toggles Nginx reverse proxy; assertions require at least one `my.servers.*.enableProxy` when enabled.
- Proxy helpers: use `parts/core.nix` helpers (`proxy`, `proxyReverse`, `proxyReverseFix` for header preservation, `proxyReversePrivate` for mutual TLS). `mkserver` supplies `host`, `ip`, `url`, and `enableProxy` defaults per service.
- Main server selection: `my.mainServer` chooses where services live by default; `mkserver` sets `isLocal` based on this and picks IPs from `my.ips`.
- Proxy helpers: use `parts/core.nix` helpers (`proxy`, `proxyReverse`, `proxyReverseFix` for header preservation, `proxyReversePrivate` for mutual TLS). `mkserver` supplies `host`, `ip`, `url`, `enableProxy`, and `useDefaultProxy`.
- Default proxying: any server with `enableProxy = true` gets a `proxyReverse` vhost unless `useDefaultProxy = false` or it is listed in `proxyReverseFix` / `proxyReversePrivate`.
- Main server selection: `my.mainServer` chooses where services live by default (default `vps`); `mkserver` sets `isLocal` based on this and picks IPs from `my.ips`.
- Firewall generation: `inputs.self.lib.generateFirewallPorts` combines static ports, additional ports, and service ports from `my.servers` (excluding native firewall services). Use `my.network.firewall` settings and `getServicesWithNativeFirewall` to derive open ports.
## Secrets Map
@@ -46,7 +48,7 @@
- `secrets/wireguard.yaml` → WireGuard peers and private keys.
- `secrets/secrets.yaml` → default SOPS file (general secrets, fallback when unspecified).
- `secrets/ssh/` → host SSH keys and related artifacts.
- secureHost: Only hosts with `my.secureHost = true` consume SOPS entries and WireGuard interfaces. Keep secret references behind `lib.mkIf config.my.secureHost`.
- secureHost: Only hosts with `my.secureHost = true` consume SOPS entries and WireGuard interfaces. Keep secret references behind `lib.mkIf config.my.secureHost`; proxy-only services that use private certs must still declare their cert secrets when `enableProxy = true`.
## Stylix and Theming
- Stylix module: `config/stylix.nix` and stylix inputs in `flake.nix` apply theming. Host toggle `my.stylix.enable` controls activation (see host toggles).
@@ -59,7 +61,7 @@
- MCP server reference: `docs/reference/mcp-server.md` (tool catalog, `nixos-mcp` wrapper, invocation, sync-docs)
## Quick Audit Checklist
- Module coverage: All categories (apps, dev, scripts, servers, services, shell, network, users, nix, patches) have corresponding entries and auto-import rules.
- Module coverage: All categories (apps, dev, scripts, servers, services, shell, websites, network, users, nix, patches) have corresponding entries and auto-import rules.
- Host coverage: Active hosts listed with roles and secureHost status; `mainServer` noted.
- Proxy rules: `enableProxy` usage, proxy helper selection, and `my.ips` mappings documented.
- Secrets map: Every secrets file and secureHost gating captured; new secret types aligned to file purposes.

View File

@@ -78,7 +78,8 @@ in
endpoint = "${config.my.ips.vps}:51820";
allowedIPs = [
"${config.my.ips.wg-vps}/32"
"${config.my.ips.wg-friends}/24" # all friends
config.my.subnets.wg-friends
config.my.subnets.wg-guests
];
persistentKeepalive = 25;
}
@@ -116,8 +117,11 @@ in
sshKeyFile = config.sops.secrets."private_keys/lidarr-mb-gap".path;
sshKnownHosts = {
vps = {
hostNames = [ config.my.ips.vps ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMvtTURGBtAFXxxfzMJVoNJrtWLykOloJ5XYjxGh1OUx";
hostNames = [
config.my.ips.vps
"[${config.my.ips.vps}]:3456"
];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPp0wAuZXk96OyA/+2YpQalokS9lZdacjJqY9zN8IScP";
};
};
};

View File

@@ -4,7 +4,6 @@ let
mkEnabledIp = inputs.self.lib.mkEnabledIp config.my.ips.wg-server;
in
{
mainServer = "server";
emacs = {
enable = true;
users = "jawz";
@@ -78,11 +77,11 @@ in
"radarr"
"sabnzbd"
"sonarr"
"yamtrack"
"stash"
"synapse"
"syncplay"
"unpackerr"
"yamtrack"
]
// enableList mkEnabledIp [
"audiobookshelf"

View File

@@ -1,14 +1,50 @@
{
config,
lib,
pkgs,
inputs,
...
}:
let
externalInterface = config.my.interfaces.${config.networking.hostName};
wgInterface = "wg0";
ips = {
homeServer = config.my.ips.wg-server;
wgFriend1 = config.my.ips.wg-friend1;
wgGuest1 = config.my.ips.wg-guest1;
wgGuest2 = config.my.ips.wg-guest2;
};
subnets = {
wgFriends = config.my.subnets.wg-friends;
wgGuests = config.my.subnets.wg-guests;
wgHomelab = config.my.subnets.wg-homelab;
};
ports = {
giteaSsh = 22;
ssh = 3456;
web = [
80
443
];
wg = 51820;
syncthing = 22000;
synapseFederation = 8448;
};
portsStr = {
giteaSsh = toString ports.giteaSsh;
syncthing = toString ports.syncthing;
synapseFederation = toString ports.synapseFederation;
synapseClient = toString config.my.servers.synapse.port;
syncplay = toString config.my.servers.syncplay.port;
stash = toString config.my.servers.stash.port;
};
in
{
imports = [
./hardware-configuration.nix
../../config/base.nix
];
my = {
my = import ./toggles.nix { inherit config inputs; } // {
secureHost = true;
users.nixremote = {
enable = true;
@@ -18,19 +54,116 @@
"nixminiserver"
];
};
services.network.enable = true;
interfaces = lib.mkMerge [
{
vps = "eth0";
}
];
};
image.modules.linode = { };
networking.hostName = "vps";
sops.age = {
generateKey = true;
keyFile = "/var/lib/sops-nix/key.txt";
sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
};
image.modules.linode = { };
environment.systemPackages = [ ];
networking = {
hostName = "vps";
nat = {
inherit externalInterface;
enable = true;
internalInterfaces = [ "wg0" ];
forwardPorts = [
{
sourcePort = ports.giteaSsh;
proto = "tcp";
destination = "${ips.homeServer}:${portsStr.giteaSsh}";
}
];
};
nftables = {
enable = true;
tables.vps-snat = {
family = "ip";
content = ''
chain postrouting {
type nat hook postrouting priority srcnat;
iifname "${externalInterface}" oifname "${wgInterface}" ip daddr ${ips.homeServer}/32 tcp dport ${portsStr.giteaSsh} masquerade comment "snat ssh forward"
}
'';
};
};
firewall = {
enable = true;
filterForward = true;
checkReversePath = "loose";
allowedTCPPorts = [ ports.ssh ] ++ ports.web;
allowedUDPPorts = [ ports.wg ];
extraForwardRules = ''
iifname "${wgInterface}" ip saddr ${subnets.wgFriends} ip daddr ${ips.homeServer}/32 tcp dport ${portsStr.syncthing} accept
iifname "${wgInterface}" ip saddr ${ips.homeServer}/32 ip daddr ${subnets.wgFriends} tcp dport ${portsStr.syncthing} accept
iifname "${wgInterface}" ip saddr ${subnets.wgFriends} ip daddr ${ips.homeServer}/32 tcp dport { ${portsStr.synapseClient}, ${portsStr.synapseFederation}, ${portsStr.syncplay} } accept
iifname "${wgInterface}" ip saddr ${subnets.wgFriends} ip daddr ${ips.homeServer}/32 icmp type echo-request accept
iifname "${wgInterface}" ip saddr ${ips.wgFriend1}/32 ip daddr ${ips.homeServer}/32 tcp dport ${portsStr.stash} accept
iifname "${wgInterface}" ip saddr ${ips.wgGuest1}/32 ip daddr ${ips.homeServer}/32 tcp dport ${portsStr.stash} accept
iifname "${wgInterface}" ip saddr ${ips.wgGuest2}/32 ip daddr ${ips.homeServer}/32 tcp dport ${portsStr.stash} accept
iifname "${wgInterface}" ip saddr ${subnets.wgGuests} ip daddr ${ips.homeServer}/32 icmp type echo-request accept
iifname "${wgInterface}" ip saddr ${subnets.wgFriends} oifname "${externalInterface}" accept
iifname "${wgInterface}" ip saddr ${subnets.wgGuests} oifname "${externalInterface}" accept
ip saddr ${subnets.wgFriends} ip daddr ${subnets.wgHomelab} drop
ip saddr ${subnets.wgHomelab} ip daddr ${subnets.wgFriends} drop
ip saddr ${subnets.wgGuests} ip daddr ${subnets.wgHomelab} drop
ip saddr ${subnets.wgHomelab} ip daddr ${subnets.wgGuests} drop
ip saddr ${subnets.wgGuests} ip daddr ${subnets.wgFriends} drop
ip saddr ${subnets.wgFriends} ip daddr ${subnets.wgGuests} drop
'';
};
};
security.sudo-rs.extraRules = [
{
users = [ "nixremote" ];
commands = [
{
command = "/run/current-system/sw/bin/nixos-rebuild";
options = [ "NOPASSWD" ];
}
];
}
];
systemd.tmpfiles.rules = [
"d /var/www/html 2775 deploy www-data -"
"d /var/www/html/portfolio 2775 deploy www-data -"
"d /var/www/html/blog 2775 deploy www-data -"
"d /var/www/html/lidarr-mb-gap 2775 lidarr-reports lidarr-reports -"
];
services = {
smartd.enable = lib.mkForce false;
openssh.ports = [ ports.ssh ];
};
users = {
groups = {
deploy = { };
lidarr-reports = { };
www-data = { };
};
users = {
nginx.extraGroups = [ "www-data" ];
deploy = {
isSystemUser = true;
group = "deploy";
home = "/var/lib/deploy";
createHome = true;
shell = pkgs.bashInteractive;
extraGroups = [ "www-data" ];
openssh.authorizedKeys.keyFiles = [ ../../secrets/ssh/ed25519_deploy.pub ];
};
lidarr-reports = {
isSystemUser = true;
group = "lidarr-reports";
home = "/var/lib/lidarr-reports";
createHome = true;
shell = pkgs.bashInteractive;
openssh.authorizedKeys.keyFiles = [ ../../secrets/ssh/ed25519_lidarr-reports.pub ];
};
};
};
}

View File

@@ -9,6 +9,10 @@
kernelModules = [ ];
extraModulePackages = [ ];
kernelParams = [ "console=ttyS0,19200n8" ];
kernel.sysctl = {
"net.ipv4.ip_forward" = 1;
"net.ipv4.conf.wg0.rp_filter" = 0;
};
initrd.availableKernelModules = [
"virtio_pci"
"virtio_scsi"
@@ -33,9 +37,7 @@
fsType = "ext4";
};
swapDevices = [
{
device = "/dev/disk/by-uuid/f1408ea6-59a0-11ed-bc9d-525400000001";
}
{ device = "/dev/disk/by-uuid/f1408ea6-59a0-11ed-bc9d-525400000001"; }
];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

64
hosts/vps/toggles.nix Normal file
View File

@@ -0,0 +1,64 @@
{ config, inputs }:
let
inherit (inputs.self.lib)
enableList
mkEnabled
mkEnabledWithUsers
;
wgServerIp = config.my.ips.wg-server;
mkEnabledProxyIp = inputs.self.lib.mkEnabledProxyIp wgServerIp;
in
{
enableProxy = true;
enableContainers = true;
apps.dictionaries.enable = true;
apps.dictionaries.users = "jawz";
services = enableList mkEnabled [
"network"
"wireguard"
];
shell = enableList mkEnabledWithUsers [
"multimedia"
"tools"
];
dev = enableList mkEnabledWithUsers [
"nix"
"sh"
];
websites = {
portfolio.enableProxy = true;
lidarrMbReport.enableProxy = true;
};
servers = {
nextcloud = {
enableProxy = true;
ip = wgServerIp;
};
}
// enableList mkEnabledProxyIp [
"audiobookshelf"
"bazarr"
"collabora"
"gitea"
"homepage"
"isso"
"jellyfin"
"kavita"
"keycloak"
"lidarr"
"linkwarden"
"maloja"
"mealie"
"metube"
"microbin"
"multi-scrobbler"
"oauth2-proxy"
"plausible"
"plex"
"prowlarr"
"radarr"
"sonarr"
"vaultwarden"
"yamtrack"
];
}

View File

@@ -48,6 +48,10 @@ let
type = lib.types.bool;
default = false;
};
useDefaultProxy = lib.mkOption {
type = lib.types.bool;
default = true;
};
certPath = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;

View File

@@ -15,6 +15,7 @@ in
++ inputs.self.lib.autoImport ./servers filterNames
++ inputs.self.lib.autoImport ./services filterNames
++ inputs.self.lib.autoImport ./shell filterNames
++ inputs.self.lib.autoImport ./websites filterNames
++ inputs.self.lib.autoImport ./network filterNames
++ [
./factories/mkscript.nix
@@ -29,7 +30,7 @@ in
};
localhost6 = lib.mkOption {
type = lib.types.str;
default = "::1";
default = "[::1]";
description = "The localhost ipv6 address.";
};
secureHost = lib.mkOption {
@@ -52,28 +53,49 @@ in
vps = "45.79.25.87";
wg-vps = "10.77.0.1";
wg-server = "10.77.0.2";
wg-g1 = "10.9.0.2";
wg-gs = "10.9.0.0";
wg-galaxy = "10.77.0.3";
wg-phone = "10.77.0.4";
wg-guest1 = "10.9.0.2";
wg-guest2 = "10.9.0.3";
wg-friend1 = "10.8.0.2";
wg-friend2 = "10.8.0.3";
wg-friend3 = "10.8.0.4";
wg-friend4 = "10.8.0.5";
wg-friends = "10.8.0.0";
wg-friend5 = "10.8.0.6";
};
description = "Set of IP's for all my computers.";
};
subnets = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = {
wg-homelab = "10.77.0.0/24";
wg-friends = "10.8.0.0/24";
wg-guests = "10.9.0.0/24";
};
description = "Set of subnets for WireGuard networks.";
};
wgInterfaces = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = {
wg-homelab = "10.77.0.1/24";
wg-friends = "10.8.0.1/24";
wg-guests = "10.9.0.1/24";
};
description = "WireGuard interface IPs for the VPS.";
};
interfaces = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = {
server = "enp0s31f6";
miniserver = "enp2s0";
workstation = "enp5s0";
vps = "eth0";
};
description = "Set of network interface names for all my computers.";
};
mainServer = lib.mkOption {
type = lib.types.str;
default = "miniserver";
default = "vps";
description = "The hostname of the main server.";
};
postgresSocket = lib.mkOption {

View File

@@ -5,44 +5,24 @@
...
}:
let
proxyReverseServices = [
"bazarr"
"firefox-syncserver"
"flame"
"flameSecret"
"isso"
"kavita"
"linkwarden"
"maloja"
"mealie"
"metube"
"microbin"
"multi-scrobbler"
"nix-serve"
"plausible"
"shiori"
"vaultwarden"
"yamtrack"
];
proxyReverseFixServices = [
"atticd"
"audiobookshelf"
"gitea"
"lidarr"
"ombi"
"prowlarr"
"radarr"
"sonarr"
"stash"
];
proxyReversePrivateServices = [
"homepage"
"prowlarr"
"stash"
];
mkServiceConfig =
type: services: lib.listToAttrs (map (name: lib.nameValuePair name { inherit type; }) services);
standardProxyServices =
(mkServiceConfig "proxyReverse" proxyReverseServices)
// (mkServiceConfig "proxyReverseFix" proxyReverseFixServices)
(mkServiceConfig "proxyReverseFix" proxyReverseFixServices)
// (mkServiceConfig "proxyReversePrivate" proxyReversePrivateServices);
generateProxyConfig =
serviceName: serviceConfig:
@@ -59,9 +39,21 @@ let
throw "Unknown proxy type: ${serviceConfig.type}";
in
lib.nameValuePair cfg.host (lib.mkIf cfg.enableProxy (proxyFunc cfg));
standardProxyNames = builtins.attrNames standardProxyServices;
customProxyServices =
config.my.servers
|> lib.filterAttrs (
name: srv:
(srv.enableProxy or false)
&& (srv.useDefaultProxy or true)
&& !(builtins.elem name standardProxyNames)
)
|> lib.mapAttrs (_name: _srv: { type = "proxyReverse"; });
in
{
config = lib.mkIf config.my.enableProxy {
services.nginx.virtualHosts = lib.mapAttrs' generateProxyConfig standardProxyServices;
services.nginx.virtualHosts = lib.mapAttrs' generateProxyConfig (
standardProxyServices // customProxyServices
);
};
}

View File

@@ -9,31 +9,33 @@ let
in
{
options.my.servers.homepage = setup.mkOptions "homepage" "home" 8082;
config = lib.mkIf config.my.secureHost {
sops.secrets = lib.mkIf cfg.enable {
homepage.sopsFile = ../../secrets/homepage.yaml;
"private-ca/pem" = {
config = lib.mkMerge [
(lib.mkIf (cfg.enable && config.my.secureHost) {
sops.secrets.homepage.sopsFile = ../../secrets/homepage.yaml;
services.homepage-dashboard = {
inherit (cfg) enable;
listenPort = cfg.port;
environmentFile = config.sops.secrets.homepage.path;
settings = {
providers.openweathermap = "{{HOMEPAGE_VAR_OPENWEATHERMAP_API_KEY}}";
layout = import ./homepage/layout.nix;
};
widgets = import ./homepage/widgets.nix;
services = import ./homepage/services.nix { inherit lib config; };
bookmarks =
builtins.readDir ./homepage/bookmarks
|> builtins.attrNames
|> builtins.filter (file: builtins.match ".*\\.nix" file != null)
|> map (file: import ./homepage/bookmarks/${file});
};
})
(lib.mkIf (cfg.enableProxy && config.my.enableProxy && config.my.secureHost) {
sops.secrets."private-ca/pem" = {
sopsFile = ../../secrets/certs.yaml;
owner = "nginx";
group = "nginx";
};
};
my.servers.homepage.certPath = config.sops.secrets."private-ca/pem".path;
services.homepage-dashboard = lib.mkIf cfg.enable {
inherit (cfg) enable;
listenPort = cfg.port;
environmentFile = config.sops.secrets.homepage.path;
settings = {
providers.openweathermap = "{{HOMEPAGE_VAR_OPENWEATHERMAP_API_KEY}}";
layout = import ./homepage/layout.nix;
};
widgets = import ./homepage/widgets.nix;
services = import ./homepage/services.nix { inherit lib config; };
bookmarks =
builtins.readDir ./homepage/bookmarks
|> builtins.attrNames
|> builtins.filter (file: builtins.match ".*\\.nix" file != null)
|> map (file: import ./homepage/bookmarks/${file});
};
};
my.servers.homepage.certPath = config.sops.secrets."private-ca/pem".path;
})
];
}

View File

@@ -23,22 +23,48 @@ let
in
{
options.my.servers.jellyfin = setup.mkOptions "jellyfin" "flix" 8096;
config = lib.mkIf (cfg.enable && config.my.secureHost) {
environment.systemPackages = [
pkgs.jellyfin-ffmpeg
]
++ (lib.optional cfg.enableCron [ sub-sync-path ]);
users.users.jellyfin = {
uid = 984;
group = "piracy";
isSystemUser = true;
};
services = {
jellyfin = {
config = lib.mkMerge [
(lib.mkIf (cfg.enable && config.my.secureHost) {
environment.systemPackages = [
pkgs.jellyfin-ffmpeg
]
++ (lib.optional cfg.enableCron [ sub-sync-path ]);
users.users.jellyfin = {
uid = 984;
group = "piracy";
isSystemUser = true;
};
services.jellyfin = {
inherit (cfg) enable;
group = "piracy";
};
nginx = lib.mkIf cfg.enableProxy {
systemd = lib.mkIf cfg.enableCron {
services.sub-sync = {
restartIfChanged = true;
description = "syncronizes subtitles downloaded & modified today";
wantedBy = [ "default.target" ];
path = sub-sync-path;
serviceConfig = {
Restart = "on-failure";
RestartSec = 30;
ExecStart = "${sub-sync}/bin/sub-sync all";
Type = "simple";
User = "root";
};
};
timers.sub-sync = {
enable = true;
description = "syncronizes subtitles downloaded & modified today";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "20:00";
};
};
};
})
(lib.mkIf (cfg.enableProxy && config.my.enableProxy) {
my.servers.jellyfin.useDefaultProxy = false;
services.nginx = {
appendHttpConfig = ''
# JELLYFIN
proxy_cache_path /var/cache/nginx/jellyfin levels=1:2 keys_zone=jellyfin:100m max_size=15g inactive=1d use_temp_path=off;
@@ -94,29 +120,6 @@ in
};
};
};
};
systemd = lib.mkIf cfg.enableCron {
services.sub-sync = {
restartIfChanged = true;
description = "syncronizes subtitles downloaded & modified today";
wantedBy = [ "default.target" ];
path = sub-sync-path;
serviceConfig = {
Restart = "on-failure";
RestartSec = 30;
ExecStart = "${sub-sync}/bin/sub-sync all";
Type = "simple";
User = "root";
};
};
timers.sub-sync = {
enable = true;
description = "syncronizes subtitles downloaded & modified today";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "20:00";
};
};
};
};
})
];
}

View File

@@ -10,35 +10,38 @@ let
in
{
options.my.servers.keycloak = setup.mkOptions "keycloak" "auth" 8090;
config = lib.mkIf (cfg.enable && config.my.secureHost) {
sops.secrets.postgres-password.sopsFile = ../../secrets/secrets.yaml;
sops.secrets.keycloak = {
sopsFile = ../../secrets/env.yaml;
restartUnits = [ "keycloak.service" ];
};
services.keycloak = {
inherit (cfg) enable;
database = {
type = "postgresql";
host = "localhost";
createLocally = false;
username = "keycloak";
name = "keycloak";
passwordFile = config.sops.secrets.postgres-password.path;
config = lib.mkMerge [
(lib.mkIf (cfg.enable && config.my.secureHost) {
sops.secrets.postgres-password.sopsFile = ../../secrets/secrets.yaml;
sops.secrets.keycloak = {
sopsFile = ../../secrets/env.yaml;
restartUnits = [ "keycloak.service" ];
};
settings = {
hostname = cfg.host;
hostname-strict = true;
hostname-strict-https = false;
http-enabled = true;
http-port = cfg.port;
http-host = cfg.ip;
proxy-headers = "xforwarded";
services.keycloak = {
inherit (cfg) enable;
database = {
type = "postgresql";
host = "localhost";
createLocally = false;
username = "keycloak";
name = "keycloak";
passwordFile = config.sops.secrets.postgres-password.path;
};
settings = {
hostname = cfg.host;
hostname-strict = true;
hostname-strict-https = false;
http-enabled = true;
http-port = cfg.port;
http-host = cfg.ip;
proxy-headers = "xforwarded";
};
};
};
systemd.services.keycloak.serviceConfig.EnvironmentFile = config.sops.secrets.keycloak.path;
services.nginx.virtualHosts.${cfg.host} = lib.mkIf (cfg.enableProxy && config.my.enableProxy) (
inputs.self.lib.proxyReverseFix cfg
);
};
systemd.services.keycloak.serviceConfig.EnvironmentFile = config.sops.secrets.keycloak.path;
})
(lib.mkIf (cfg.enableProxy && config.my.enableProxy) {
my.servers.keycloak.useDefaultProxy = false;
services.nginx.virtualHosts.${cfg.host} = inputs.self.lib.proxyReverseFix cfg;
})
];
}

View File

@@ -42,133 +42,137 @@ in
collabora = setup.mkOptions "collabora" "collabora" 9980;
go-vod.enable = lib.mkEnableOption "Go-VOD video transcoding service";
};
config = lib.mkIf (cfg.enable && config.my.servers.postgres.enable && config.my.secureHost) {
sops.secrets.nextcloud-adminpass = {
owner = config.users.users.nextcloud.name;
inherit (config.users.users.nextcloud) group;
};
nixpkgs.config.permittedInsecurePackages = [
"nodejs-14.21.3"
"openssl-1.1.1v"
];
users.groups.nextcloud = { inherit gid; };
users.users.nextcloud = {
inherit uid;
isSystemUser = true;
group = "nextcloud";
extraGroups = [ "render" ];
packages = builtins.attrValues {
inherit exiftool pytensorflow;
inherit (pkgs)
ffmpeg
mediainfo
nodejs
perl
;
config = lib.mkMerge [
{ my.servers.nextcloud.useDefaultProxy = false; }
(lib.mkIf (cfg.enable && config.my.servers.postgres.enable && config.my.secureHost) {
sops.secrets.nextcloud-adminpass = {
owner = config.users.users.nextcloud.name;
inherit (config.users.users.nextcloud) group;
};
};
services = {
nextcloud = {
enable = true;
https = false; # vps
package = pkgs.nextcloud32;
appstoreEnable = true;
configureRedis = true;
extraAppsEnable = true;
enableImagemagick = true;
maxUploadSize = "4096M";
hostName = cfg.host;
caching = {
redis = true;
memcached = true;
apcu = true;
nixpkgs.config.permittedInsecurePackages = [
"nodejs-14.21.3"
"openssl-1.1.1v"
];
users = {
groups.nextcloud = { inherit gid; };
users.nextcloud = {
inherit uid;
isSystemUser = true;
group = "nextcloud";
extraGroups = [ "render" ];
packages = builtins.attrValues {
inherit exiftool pytensorflow;
inherit (pkgs)
ffmpeg
mediainfo
nodejs
perl
;
};
};
config = {
adminpassFile = config.sops.secrets.nextcloud-adminpass.path;
dbtype = "pgsql";
dbhost = config.my.postgresSocket;
dbname = "nextcloud";
};
phpOptions = {
catch_workers_output = "yes";
display_errors = "stderr";
error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
expose_php = "Off";
preview_max_x = 2048;
preview_max_y = 2048;
short_open_tag = "Off";
"opcache.enable_cli" = "1";
"opcache.fast_shutdown" = "1";
"opcache.interned_strings_buffer" = "16";
"opcache.jit" = "1255";
"opcache.jit_buffer_size" = "256M";
"opcache.max_accelerated_files" = "10000";
"opcache.huge_code_pages" = "1";
"opcache.enable_file_override" = "1";
"opcache.memory_consumption" = "256";
"opcache.revalidate_freq" = "60";
"opcache.save_comments" = "1";
"opcache.validate_timestamps" = "0";
"openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
};
settings = {
log_type = "file";
loglevel = 1;
trusted_proxies = [
config.my.localhost
config.my.localhost6
config.my.ips.router
config.my.ips.wg-vps
};
services = {
nextcloud = {
enable = true;
https = false; # vps
package = pkgs.nextcloud32;
appstoreEnable = true;
configureRedis = true;
extraAppsEnable = true;
enableImagemagick = true;
maxUploadSize = "4096M";
hostName = cfg.host;
caching = {
redis = true;
memcached = true;
apcu = true;
};
config = {
adminpassFile = config.sops.secrets.nextcloud-adminpass.path;
dbtype = "pgsql";
dbhost = config.my.postgresSocket;
dbname = "nextcloud";
};
phpOptions = {
catch_workers_output = "yes";
display_errors = "stderr";
error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
expose_php = "Off";
preview_max_x = 2048;
preview_max_y = 2048;
short_open_tag = "Off";
"opcache.enable_cli" = "1";
"opcache.fast_shutdown" = "1";
"opcache.interned_strings_buffer" = "16";
"opcache.jit" = "1255";
"opcache.jit_buffer_size" = "256M";
"opcache.max_accelerated_files" = "10000";
"opcache.huge_code_pages" = "1";
"opcache.enable_file_override" = "1";
"opcache.memory_consumption" = "256";
"opcache.revalidate_freq" = "60";
"opcache.save_comments" = "1";
"opcache.validate_timestamps" = "0";
"openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
};
settings = {
log_type = "file";
loglevel = 1;
trusted_proxies = [
config.my.localhost
config.my.localhost6
config.my.ips.router
config.my.ips.wg-vps
];
trusted_domains = [
cfg.host
config.my.ips.${config.networking.hostName}
"localhost"
"cloud.rotehaare.art"
];
overwriteprotocol = "https";
"overwrite.cli.url" = "${cfg.url}";
forwarded_for_headers = [ "HTTP_X_FORWARDED_FOR" ];
default_phone_region = "MX";
allow_local_remote_servers = true;
mail_smtpmode = "sendmail";
mail_sendmailmode = "pipe";
preview_ffmpeg_path = "${pkgs.ffmpeg}/bin/ffmpeg";
"memories.exiftool" = "${exiftool}/bin/exiftool";
"memories.ffmpeg_path" = "${pkgs.ffmpeg}/bin/ffmpeg";
"memories.ffprobe_path" = "${pkgs.ffmpeg}/bin/ffprobe";
enabledPreviewProviders = [
"OC\\Preview\\AVI"
"OC\\Preview\\BMP"
"OC\\Preview\\GIF"
"OC\\Preview\\HEIC"
"OC\\Preview\\Image"
"OC\\Preview\\JPEG"
"OC\\Preview\\Krita"
"OC\\Preview\\MKV"
"OC\\Preview\\MP3"
"OC\\Preview\\MP4"
"OC\\Preview\\MarkDown"
"OC\\Preview\\Movie"
"OC\\Preview\\OpenDocument"
"OC\\Preview\\PNG"
"OC\\Preview\\TIFF"
"OC\\Preview\\TXT"
"OC\\Preview\\XBitmap"
];
};
phpExtraExtensions = all: [
all.pdlib
all.bz2
];
trusted_domains = [
cfg.host
config.my.ips.${config.networking.hostName}
"localhost"
};
nginx.virtualHosts.${cfg.host} = {
forceSSL = false;
enableACME = false;
http2 = false;
serverAliases = [
"cloud.rotehaare.art"
];
overwriteprotocol = "https";
"overwrite.cli.url" = "${cfg.url}";
forwarded_for_headers = [ "HTTP_X_FORWARDED_FOR" ];
default_phone_region = "MX";
allow_local_remote_servers = true;
mail_smtpmode = "sendmail";
mail_sendmailmode = "pipe";
preview_ffmpeg_path = "${pkgs.ffmpeg}/bin/ffmpeg";
"memories.exiftool" = "${exiftool}/bin/exiftool";
"memories.ffmpeg_path" = "${pkgs.ffmpeg}/bin/ffmpeg";
"memories.ffprobe_path" = "${pkgs.ffmpeg}/bin/ffprobe";
enabledPreviewProviders = [
"OC\\Preview\\AVI"
"OC\\Preview\\BMP"
"OC\\Preview\\GIF"
"OC\\Preview\\HEIC"
"OC\\Preview\\Image"
"OC\\Preview\\JPEG"
"OC\\Preview\\Krita"
"OC\\Preview\\MKV"
"OC\\Preview\\MP3"
"OC\\Preview\\MP4"
"OC\\Preview\\MarkDown"
"OC\\Preview\\Movie"
"OC\\Preview\\OpenDocument"
"OC\\Preview\\PNG"
"OC\\Preview\\TIFF"
"OC\\Preview\\TXT"
"OC\\Preview\\XBitmap"
];
};
phpExtraExtensions = all: [
all.pdlib
all.bz2
];
};
nginx.virtualHosts = {
"${cfg.host}" = lib.mkIf cfg.enableProxy {
forceSSL = false; # vps
enableACME = false; # vps
http2 = false; # vps
# default = true; #vps
#vps
listen = [
{
addr = config.my.ips.wg-server;
@@ -179,7 +183,86 @@ in
inherit (cfg) port;
}
];
#vps
};
};
virtualisation.oci-containers.containers = {
go-vod = lib.mkIf config.my.servers.go-vod.enable {
autoStart = true;
image = "radialapps/go-vod";
environment = {
TZ = config.my.timeZone;
NEXTCLOUD_HOST = "https://${config.services.nextcloud.hostName}";
NVIDIA_VISIBLE_DEVICES = "all";
};
volumes = [ "ncdata:/var/www/html:ro" ];
extraOptions = [
"--device=/dev/dri" # VA-API (omit for NVENC)
];
};
collabora = lib.mkIf cfgC.enable {
autoStart = true;
image = "collabora/code:latest";
ports = [ "${toString cfgC.port}:${toString cfgC.port}" ];
environment = {
TZ = config.my.timeZone;
domain = cfg.host;
aliasgroup1 = "${cfg.url}:443";
aliasgroup2 = "https://cloud.rotehaare.art:443";
server_name = cfgC.host;
dictionaries = "en_CA en_US es_MX es_ES fr_FR it pt_BR ru";
extra_params = ''
--o:ssl.enable=false
--o:ssl.termination=true
--o:remote_font_config.url=${cfg.url}/apps/richdocuments/settings/fonts.json
--o:logging.level=information
'';
DONT_GEN_SSL_CERT = "1";
SLEEPFORDEBUGGER = "0";
};
extraOptions = [
"--cap-add"
"MKNOD"
];
};
};
systemd = lib.mkIf cfg.enableCron {
services = {
nextcloud-cron.path = [ pkgs.perl ];
nextcloud-cronjob =
let
inherit (inputs.jawz-scripts.packages.x86_64-linux) nextcloud-cronjob;
in
{
description = "Runs various nextcloud-related cronjobs";
wantedBy = [ "multi-user.target" ];
path = [
pkgs.bash
nextcloud-cronjob
];
serviceConfig = {
Restart = "on-failure";
RestartSec = 30;
ExecStart = "${nextcloud-cronjob}/bin/nextcloud-cronjob";
};
};
};
timers.nextcloud-cronjob = {
enable = true;
description = "Runs various nextcloud-related cronjobs";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "*:0/10";
};
};
};
})
(lib.mkIf (cfg.enableProxy && config.my.enableProxy && config.networking.hostName == "vps") {
services.nginx.virtualHosts = {
"${cfg.host}" = {
forceSSL = true;
enableACME = true;
http2 = true;
default = true;
serverAliases = [ "cloud.rotehaare.art" ];
extraConfig = ''
add_header X-XSS-Protection "1; mode=block" always;
@@ -188,11 +271,16 @@ in
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
'';
locations = {
"/".proxyWebsockets = true;
"~ ^/nextcloud/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|oc[ms]-provider/.+|.+/richdocumentscode/proxy).php(?:$|/)" =
{ };
"/" = {
proxyPass = cfg.local;
proxyWebsockets = true;
};
};
};
"${cfgC.host}" = lib.mkIf cfgC.enableProxy {
@@ -243,76 +331,6 @@ in
};
};
};
};
virtualisation.oci-containers.containers = {
go-vod = lib.mkIf config.my.servers.go-vod.enable {
autoStart = true;
image = "radialapps/go-vod";
environment = {
TZ = config.my.timeZone;
NEXTCLOUD_HOST = "https://${config.services.nextcloud.hostName}";
NVIDIA_VISIBLE_DEVICES = "all";
};
volumes = [ "ncdata:/var/www/html:ro" ];
extraOptions = [
"--device=/dev/dri" # VA-API (omit for NVENC)
];
};
collabora = lib.mkIf cfgC.enable {
autoStart = true;
image = "collabora/code:latest";
ports = [ "9980:9980" ];
environment = {
TZ = config.my.timeZone;
domain = cfg.host;
aliasgroup1 = "${cfg.url}:443";
aliasgroup2 = "https://cloud.rotehaare.art:443";
server_name = cfgC.host;
dictionaries = "en_CA en_US es_MX es_ES fr_FR it pt_BR ru";
extra_params = ''
--o:ssl.enable=false
--o:ssl.termination=true
--o:remote_font_config.url=${cfg.url}/apps/richdocuments/settings/fonts.json
--o:logging.level=information
'';
DONT_GEN_SSL_CERT = "1";
SLEEPFORDEBUGGER = "0";
};
extraOptions = [
"--cap-add"
"MKNOD"
];
};
};
systemd = lib.mkIf cfg.enableCron {
services = {
nextcloud-cron.path = [ pkgs.perl ];
nextcloud-cronjob =
let
inherit (inputs.jawz-scripts.packages.x86_64-linux) nextcloud-cronjob;
in
{
description = "Runs various nextcloud-related cronjobs";
wantedBy = [ "multi-user.target" ];
path = [
pkgs.bash
nextcloud-cronjob
];
serviceConfig = {
Restart = "on-failure";
RestartSec = 30;
ExecStart = "${nextcloud-cronjob}/bin/nextcloud-cronjob";
};
};
};
timers.nextcloud-cronjob = {
enable = true;
description = "Runs various nextcloud-related cronjobs";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "*:0/10";
};
};
};
};
})
];
}

View File

@@ -40,7 +40,7 @@ in
secure = true;
expire = "168h";
refresh = "1h";
domain = ".lebubu.org";
domain = ".${config.my.domain}";
secret = config.sops.secrets.oauth2-proxy-cookie.path;
};
extraConfig = {
@@ -53,7 +53,7 @@ in
session-store-type = "cookie";
skip-provider-button = true;
code-challenge-method = "S256";
whitelist-domain = [ ".lebubu.org" ];
whitelist-domain = [ ".${config.my.domain}" ];
};
};
};

View File

@@ -9,51 +9,52 @@ let
in
{
options.my.servers.plex = setup.mkOptions "plex" "plex" 32400;
config = lib.mkIf (cfg.enable && config.my.secureHost) {
users.users.plex = {
uid = 193;
group = "piracy";
isSystemUser = true;
};
services = {
plex = {
config = lib.mkMerge [
(lib.mkIf (cfg.enable && config.my.secureHost) {
users.users.plex = {
uid = 193;
group = "piracy";
isSystemUser = true;
};
services.plex = {
inherit (cfg) enable;
group = "piracy";
};
nginx = lib.mkIf cfg.enableProxy {
virtualHosts."${cfg.host}" = {
forceSSL = true;
enableACME = true;
http2 = true;
serverAliases = [
"plex.rotehaare.art"
];
extraConfig = ''
# Some players don't reopen a socket and playback stops totally instead of resuming after an extended pause
send_timeout 100m;
# Plex headers
proxy_set_header X-Plex-Client-Identifier $http_x_plex_client_identifier;
proxy_set_header X-Plex-Device $http_x_plex_device;
proxy_set_header X-Plex-Device-Name $http_x_plex_device_name;
proxy_set_header X-Plex-Platform $http_x_plex_platform;
proxy_set_header X-Plex-Platform-Version $http_x_plex_platform_version;
proxy_set_header X-Plex-Product $http_x_plex_product;
proxy_set_header X-Plex-Token $http_x_plex_token;
proxy_set_header X-Plex-Version $http_x_plex_version;
proxy_set_header X-Plex-Nocache $http_x_plex_nocache;
proxy_set_header X-Plex-Provides $http_x_plex_provides;
proxy_set_header X-Plex-Device-Vendor $http_x_plex_device_vendor;
proxy_set_header X-Plex-Model $http_x_plex_model;
# Buffering off send to the client as soon as the data is received from Plex.
proxy_redirect off;
proxy_buffering off;
'';
locations."/" = {
proxyPass = cfg.local;
proxyWebsockets = true;
};
})
(lib.mkIf (cfg.enableProxy && config.my.enableProxy) {
my.servers.plex.useDefaultProxy = false;
services.nginx.virtualHosts."${cfg.host}" = {
forceSSL = true;
enableACME = true;
http2 = true;
serverAliases = [
"plex.rotehaare.art"
];
extraConfig = ''
# Some players don't reopen a socket and playback stops totally instead of resuming after an extended pause
send_timeout 100m;
# Plex headers
proxy_set_header X-Plex-Client-Identifier $http_x_plex_client_identifier;
proxy_set_header X-Plex-Device $http_x_plex_device;
proxy_set_header X-Plex-Device-Name $http_x_plex_device_name;
proxy_set_header X-Plex-Platform $http_x_plex_platform;
proxy_set_header X-Plex-Platform-Version $http_x_plex_platform_version;
proxy_set_header X-Plex-Product $http_x_plex_product;
proxy_set_header X-Plex-Token $http_x_plex_token;
proxy_set_header X-Plex-Version $http_x_plex_version;
proxy_set_header X-Plex-Nocache $http_x_plex_nocache;
proxy_set_header X-Plex-Provides $http_x_plex_provides;
proxy_set_header X-Plex-Device-Vendor $http_x_plex_device_vendor;
proxy_set_header X-Plex-Model $http_x_plex_model;
# Buffering off send to the client as soon as the data is received from Plex.
proxy_redirect off;
proxy_buffering off;
'';
locations."/" = {
proxyPass = cfg.local;
proxyWebsockets = true;
};
};
};
};
})
];
}

View File

@@ -1,22 +0,0 @@
{
config,
lib,
...
}:
let
setup = import ../factories/mkserver.nix { inherit lib config; };
cfg = config.my.websites.portfolio;
in
{
options.my.websites.portfolio = setup.mkOptions "portfolio" "portfolio" 0;
config.services.nginx.virtualHosts."danilo-reyes.com" = lib.mkIf cfg.enableProxy {
forceSSL = true;
enableACME = true;
http2 = true;
root = "/srv/www/danilo-reyes.com";
# index = "index.html";
locations."/".extraConfig = ''
try_files $uri $uri/ =404;
'';
};
}

View File

@@ -37,6 +37,7 @@ let
"mealie"
"nextcloud"
"paperless"
"plausible"
"shiori"
"sonarqube"
"vaultwarden"

View File

@@ -9,19 +9,29 @@ let
in
{
options.my.servers.prowlarr = setup.mkOptions "prowlarr" "indexer" 9696;
config = lib.mkIf cfg.enable {
users.users.prowlarr = {
uid = 987;
group = "piracy";
isSystemUser = true;
};
services = {
prowlarr = {
inherit (cfg) enable;
config = lib.mkMerge [
(lib.mkIf cfg.enable {
users.users.prowlarr = {
uid = 987;
group = "piracy";
isSystemUser = true;
};
flaresolverr = {
inherit (cfg) enable;
services = {
prowlarr = {
inherit (cfg) enable;
};
flaresolverr = {
inherit (cfg) enable;
};
};
};
};
})
(lib.mkIf (cfg.enableProxy && config.my.enableProxy && config.my.secureHost) {
sops.secrets."private-ca/pem" = {
sopsFile = ../../secrets/certs.yaml;
owner = "nginx";
group = "nginx";
};
my.servers.prowlarr.certPath = config.sops.secrets."private-ca/pem".path;
})
];
}

View File

@@ -29,46 +29,56 @@ let
in
{
options.my.servers.stash = setup.mkOptions "stash" "xxx" 9999;
config = lib.mkIf (cfg.enable && config.my.secureHost) {
sops.secrets = {
"stash/password".sopsFile = ../../secrets/secrets.yaml;
"stash/jwt".sopsFile = ../../secrets/secrets.yaml;
"stash/session".sopsFile = ../../secrets/secrets.yaml;
};
services.stash = {
inherit (cfg) enable;
group = "glue";
mutableSettings = true;
username = "Suing8150";
passwordFile = config.sops.secrets."stash/password".path;
jwtSecretKeyFile = config.sops.secrets."stash/jwt".path;
sessionStoreKeyFile = config.sops.secrets."stash/session".path;
settings = {
inherit (cfg) port;
host = "0.0.0.0";
stash = [
{
path = "/srv/pool/glue/";
}
];
config = lib.mkMerge [
(lib.mkIf (cfg.enable && config.my.secureHost) {
sops.secrets = {
"stash/password".sopsFile = ../../secrets/secrets.yaml;
"stash/jwt".sopsFile = ../../secrets/secrets.yaml;
"stash/session".sopsFile = ../../secrets/secrets.yaml;
};
};
systemd.services.stash = {
environment = {
PYTHONPATH = "/var/lib/stash/venv/lib/python3.12/site-packages";
LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib:${pkgs.glibc}/lib:${pkgs.zlib}/lib:${pkgs.libffi}/lib:${pkgs.openssl}/lib";
services.stash = {
inherit (cfg) enable;
group = "glue";
mutableSettings = true;
username = "Suing8150";
passwordFile = config.sops.secrets."stash/password".path;
jwtSecretKeyFile = config.sops.secrets."stash/jwt".path;
sessionStoreKeyFile = config.sops.secrets."stash/session".path;
settings = {
inherit (cfg) port;
host = "0.0.0.0";
stash = [
{
path = "/srv/pool/glue/";
}
];
};
};
serviceConfig = {
PrivateUsers = lib.mkForce false;
BindReadOnlyPaths = lib.mkForce [ ];
BindPaths = lib.mkIf (cfgS.settings != { }) (map (stash: "${stash.path}") cfgS.settings.stash);
systemd.services.stash = {
environment = {
PYTHONPATH = "/var/lib/stash/venv/lib/python3.12/site-packages";
LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib:${pkgs.glibc}/lib:${pkgs.zlib}/lib:${pkgs.libffi}/lib:${pkgs.openssl}/lib";
};
serviceConfig = {
PrivateUsers = lib.mkForce false;
BindReadOnlyPaths = lib.mkForce [ ];
BindPaths = lib.mkIf (cfgS.settings != { }) (map (stash: "${stash.path}") cfgS.settings.stash);
};
};
};
users.users.stash = {
uid = 974;
isSystemUser = true;
group = "glue";
packages = [ stashPythonFHS ];
};
};
users.users.stash = {
uid = 974;
isSystemUser = true;
group = "glue";
packages = [ stashPythonFHS ];
};
})
(lib.mkIf (cfg.enableProxy && config.my.enableProxy && config.my.secureHost) {
sops.secrets."private-ca/pem" = {
sopsFile = ../../secrets/certs.yaml;
owner = "nginx";
group = "nginx";
};
my.servers.stash.certPath = config.sops.secrets."private-ca/pem".path;
})
];
}

View File

@@ -25,42 +25,37 @@ in
synapse = setup.mkOptions "synapse" "pYLemuAfsrzNBaH77xSu" 8008;
element = setup.mkOptions "element" "55a608953f6d64c199" 5345;
};
config = lib.mkIf (cfg.enable && config.my.secureHost) {
my.servers = {
synapse = { inherit domain; };
element = { inherit domain; };
};
users.groups.matrix-synapse = { inherit gid; };
users.users.matrix-synapse = {
inherit uid;
isSystemUser = true;
group = "matrix-synapse";
};
sops.secrets = {
synapse = {
sopsFile = ../../secrets/env.yaml;
owner = "matrix-synapse";
config = lib.mkMerge [
(lib.mkIf (cfg.enable && config.my.secureHost) {
my.servers = {
synapse = { inherit domain; };
element = { inherit domain; };
};
users.groups.matrix-synapse = { inherit gid; };
users.users.matrix-synapse = {
inherit uid;
isSystemUser = true;
group = "matrix-synapse";
};
"iqQCY4iAWO-ca/pem" = {
sopsFile = ../../secrets/certs.yaml;
owner = "nginx";
group = "nginx";
sops.secrets = {
synapse = {
sopsFile = ../../secrets/env.yaml;
owner = "matrix-synapse";
group = "matrix-synapse";
};
"matrix/key" = {
sopsFile = ../../secrets/certs.yaml;
owner = "matrix-synapse";
group = "matrix-synapse";
};
"matrix/cert" = {
sopsFile = ../../secrets/certs.yaml;
owner = "matrix-synapse";
group = "matrix-synapse";
};
};
"matrix/key" = {
sopsFile = ../../secrets/certs.yaml;
owner = "matrix-synapse";
group = "matrix-synapse";
};
"matrix/cert" = {
sopsFile = ../../secrets/certs.yaml;
owner = "matrix-synapse";
group = "matrix-synapse";
};
};
networking.firewall.allowedTCPPorts = lib.mkIf (!cfg.isLocal) [ cfg.port ];
services = {
matrix-synapse = {
networking.firewall.allowedTCPPorts = lib.mkIf (!cfg.isLocal) [ cfg.port ];
services.matrix-synapse = {
inherit (cfg) enable;
extraConfigFiles = [
config.sops.secrets.synapse.path
@@ -100,7 +95,18 @@ in
];
};
};
nginx.virtualHosts = lib.mkIf cfg.enableProxy {
})
(lib.mkIf (cfg.enableProxy && config.my.enableProxy) {
sops.secrets."iqQCY4iAWO-ca/pem" = {
sopsFile = ../../secrets/certs.yaml;
owner = "nginx";
group = "nginx";
};
my.servers.synapse = {
useDefaultProxy = false;
certPath = config.sops.secrets."iqQCY4iAWO-ca/pem".path;
};
services.nginx.virtualHosts = {
"${cfgE.host}" = {
enableACME = true;
forceSSL = true;
@@ -125,13 +131,8 @@ in
"/_matrix".proxyPass = "http://[${config.my.localhost6}]:${toString cfg.port}";
"/_synapse/client".proxyPass = "http://[${config.my.localhost6}]:${toString cfg.port}";
};
# extraConfig = ''
# ssl_verify_client on;
# ssl_client_certificate ${config.sops.secrets."iqQCY4iAWO-ca/pem".path};
# error_page 403 /403.html;
# '';
};
};
};
};
})
];
}

View File

@@ -1,10 +1,23 @@
{ config, lib, ... }:
let
stripCidr = cidr: cidr |> lib.splitString "/" |> builtins.head;
wgListenIps = config.my.wgInterfaces |> builtins.attrValues;
wgListenAddrs = wgListenIps |> builtins.map (ip: "${stripCidr ip}:53");
in
{
options.my.services.network.enable = lib.mkEnableOption "network configuration and services";
config = lib.mkIf config.my.services.network.enable {
networking = {
enableIPv6 = true;
firewall.enable = true;
firewall = {
enable = true;
interfaces = lib.mkIf config.my.services.wireguard.enable {
wg0 = {
allowedTCPPorts = [ 53 ];
allowedUDPPorts = [ 53 ];
};
};
};
dhcpcd.extraConfig = "nohook resolv.conf";
networkmanager = {
enable = true;
@@ -19,6 +32,16 @@
settings = {
ipv6_servers = true;
require_dnssec = true;
log_level = 4;
listen_addresses = [
"${config.my.localhost}:53"
"${config.my.localhost6}:53"
]
++ lib.optionals config.my.services.wireguard.enable wgListenAddrs;
query_log = {
file = "/var/lib/dnscrypt-proxy/query.log";
format = "tsv";
};
sources.public-resolvers = {
urls = [
"https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md"

View File

@@ -1,51 +1,64 @@
{
config,
lib,
pkgs,
...
}:
let
port = 51820;
interface = config.my.interfaces.${config.networking.hostName};
in
{
options.my.services.wireguard.enable = lib.mkEnableOption "WireGuard VPN configuration";
config = lib.mkIf (config.my.services.wireguard.enable && config.my.secureHost) {
sops.secrets."wireguard/private".sopsFile = ../../secrets/wireguard.yaml;
sops.secrets."vps/server/private".sopsFile = ../../secrets/wireguard.yaml;
networking = {
firewall.allowedUDPPorts = [ port ];
nat = {
enable = true;
externalInterface = interface;
internalInterfaces = [ "wg0" ];
};
wireguard.interfaces.wg0 = {
ips = [ "10.100.0.1/24" ];
ips = [
config.my.wgInterfaces.wg-homelab
config.my.wgInterfaces.wg-friends
config.my.wgInterfaces.wg-guests
];
listenPort = port;
postSetup = ''
${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.100.0.0/24 -o ${interface} -j MASQUERADE
'';
postShutdown = ''
${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s 10.100.0.0/24 -o ${interface} -j MASQUERADE
'';
privateKeyFile = config.sops.secrets."wireguard/private".path;
postSetup = "";
postShutdown = "";
privateKeyFile = config.sops.secrets."vps/server/private".path;
peers = [
{
publicKey = "ciupBjCcIpd3K5vlzNMJC8iiyNqB9xXwkSC6UXPKP3g=";
allowedIPs = [ "10.100.0.2/32" ];
} # phone
publicKey = "OUiqluRaS4hmGvLJ3csQrnIM3Zzet50gsqtTABaUkH4=";
allowedIPs = [ "${config.my.ips.wg-server}/32" ];
}
{
publicKey = "JgeA1ElDwR7oLmyGn8RzvxiscMBhR8+L+mEjY1Cq7gk=";
allowedIPs = [ "10.100.0.3/32" ];
} # tablet
publicKey = "rFgT6TXzRazK6GMazMNGjtOvzAAPST0LvCfN7QXsLho=";
allowedIPs = [ "${config.my.ips.wg-friend1}/32" ];
}
{
publicKey = "giPVRUTLtqPGb57R4foGZMNS0tjIp2ry6lMKYtqHjn4=";
allowedIPs = [ "10.100.0.15/32" ];
} # jeancarlos
publicKey = "R1CTx5+CXivMI6ZEmRYsyFUFILhe6Qnub0iEIRvvrEY=";
allowedIPs = [ "${config.my.ips.wg-friend2}/32" ];
}
{
publicKey = "92JdW/NExg1tUE4cEyl6Yn+0Eex+iFVA37ahPRhRnRM=";
allowedIPs = [ "10.100.0.16/32" ];
} # gorilia
publicKey = "ecPNSacD6yVwpnLBs171z0xkw9M1DXKh/Kn70cIBcwA=";
allowedIPs = [ "${config.my.ips.wg-friend3}/32" ];
}
{
publicKey = "yg+2miZCrx89znFaUlU/le/7UIPgEAMY74fZfEwz8g4=";
allowedIPs = [ "${config.my.ips.wg-friend4}/32" ];
}
{
publicKey = "u4/6ZYO7lUJZ9QmSlFPUaadq25gwDljjhsfgs/p2amc=";
allowedIPs = [ "${config.my.ips.wg-friend5}/32" ];
}
{
publicKey = "NvhUnErIb0/hi+Hui/o5l5Pq4ZysFVIn1VBPsjoTeCk=";
allowedIPs = [ "${config.my.ips.wg-guest2}/32" ];
}
{
publicKey = "BwN4uCkMd6eAS5Ugld0oXnA16IhgEEQF8mOJ3+vHliA=";
allowedIPs = [ "${config.my.ips.wg-galaxy}/32" ];
}
{
publicKey = "R1xUFOuboQf/yy8ShiXqoCPaPcH3Cn0n4PAWB2rgHTs=";
allowedIPs = [ "${config.my.ips.wg-phone}/32" ];
}
];
};
};

View File

@@ -0,0 +1,44 @@
{
lib,
config,
...
}:
let
cfg = config.my.websites.lidarrMbReport;
mbSecurityHeaders = ''
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
'';
in
{
options.my.websites.lidarrMbReport = {
enableProxy = lib.mkEnableOption "lidarr mb report static site";
};
config = lib.mkIf (cfg.enableProxy && config.my.enableProxy) {
services.nginx.virtualHosts."mb-report.lebubu.org" = {
forceSSL = true;
enableACME = true;
root = "/var/www/html/lidarr-mb-gap";
locations = {
"/" = {
extraConfig = ''
try_files $uri /missing_albums.html;
${mbSecurityHeaders}
'';
};
"~* \\.html$" = {
extraConfig = ''
add_header Content-Type "text/html; charset=utf-8";
${mbSecurityHeaders}
'';
};
"~* \\.json$" = {
extraConfig = ''
add_header Content-Type "application/json";
${mbSecurityHeaders}
'';
};
};
};
};
}

View File

@@ -0,0 +1,98 @@
{
lib,
config,
...
}:
let
cfg = config.my.websites.portfolio;
issoCfg = config.my.servers.isso;
hugoSecurityHeaders = ''
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
'';
hugoLocations = {
"/" = {
extraConfig = ''
try_files $uri $uri/ /index.html;
${hugoSecurityHeaders}
'';
};
"~* \\.html$" = {
extraConfig = ''
try_files $uri $uri/ /index.html;
${hugoSecurityHeaders}
'';
};
"~* \\.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|xml)$" = {
extraConfig = ''
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
${hugoSecurityHeaders}
'';
};
"~ /\\.(?!well-known).*" = {
extraConfig = ''
return 404;
${hugoSecurityHeaders}
'';
};
"= /js/script.js" = {
proxyPass = "https://analytics.lebubu.org";
extraConfig = ''
proxy_set_header Host analytics.lebubu.org;
rewrite ^ /js/script.file-downloads.hash.outbound-links.js break;
${hugoSecurityHeaders}
'';
};
"= /api/event" = {
proxyPass = "https://analytics.lebubu.org";
extraConfig = ''
proxy_set_header Host analytics.lebubu.org;
${hugoSecurityHeaders}
'';
};
};
in
{
options.my.websites.portfolio = {
enableProxy = lib.mkEnableOption "portfolio and blog static sites";
};
config = lib.mkIf (cfg.enableProxy && config.my.enableProxy) {
services.nginx.virtualHosts = {
"www.danilo-reyes.com" = {
forceSSL = true;
enableACME = true;
globalRedirect = "danilo-reyes.com";
};
"www.blog.danilo-reyes.com" = {
forceSSL = true;
enableACME = true;
globalRedirect = "blog.danilo-reyes.com";
};
"danilo-reyes.com" = {
forceSSL = true;
enableACME = true;
root = "/var/www/html/portfolio";
locations = hugoLocations;
};
"blog.danilo-reyes.com" = {
forceSSL = true;
enableACME = true;
root = "/var/www/html/blog";
locations = hugoLocations // {
"^~ /isso" = {
proxyPass = "http://${issoCfg.ip}:${toString issoCfg.port}";
extraConfig = ''
rewrite ^/isso/?(.*)$ /$1 break;
proxy_set_header Host $host;
${hugoSecurityHeaders}
'';
};
};
};
};
};
}

View File

@@ -196,6 +196,13 @@ in
inherit ip;
};
};
mkEnabledProxyIp = ip: name: {
inherit name;
value = {
enableProxy = true;
inherit ip;
};
};
enableList = func: list: list |> map func |> builtins.listToAttrs;
mkPostgresDependency = config: serviceName: displayName: {
assertion = config.my.servers.${serviceName}.enable -> config.my.servers.postgres.enable;
@@ -217,7 +224,6 @@ in
nixworkstation = ../secrets/ssh/ed25519_nixworkstation.pub;
nixserver = ../secrets/ssh/ed25519_nixserver.pub;
nixminiserver = ../secrets/ssh/ed25519_nixminiserver.pub;
windows_vm = ../secrets/ssh/ed25519_windows_vm.pub;
};
getSshKeys = keyNames: keyNames |> map (name: inputs.self.lib.sshKeys.${name});
# Helper functions for multi-user toggle support

View File

@@ -22,38 +22,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5VUMzYjZ5WlZtQ05LdnVt
b3V3RmFyM0VZWmh4dC9YZFpsZkRIdC9TRzFrCnBuYnhSaUgwb3JuSUNFSWlwSmVq
bEoyQ09XSjNBMks3M2ZYdlh0eDFNYjAKLS0tIERpaGhISDFYd3RCYUV6Y0lmdGNQ
VTNibTBMN2RuN3doU3lYK1drNjVTVkkKMmRW0NtiYKBcUQ8kKjXcS6KjoPdVfN5d
6vczsKTTbUwI0n6T5xrwRdbVIFsP4HisjceQWxJIVBthR0u9dLfXGw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXdVVSeEtOTE1XQTBHVW5C
aUVVbUltOGMrV2l6VGhRQXFnbUR1NVpnYmgwCjEzWXB5SVBtbjBzMkx4OUhkUXll
b0FkcUl2b0d0YkEwQU9iNFZrcDJTV3MKLS0tIHNrY2JFbVEwNTFaWUdmdFJPZmJI
SnhZK1h2ejhQUUNtbzFINUJmNGhiYVkKCMeBiPt80A8/ynEWy2e881y1tVnqANK+
wU9Bn+oRwoudPb1io9LAoTdu7+IQpLByt1phAju8m243nM48hAkipA==
-----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvb1ZtMjV5TjlhMVRwdWNU
ME93Y0xhVGlxdGxmeWtXQ09EN3lORlJpV3k0CkJxdE14YXpwcytjbnZuMWpHVzZ3
dVVBYVE0RW1naWVVQ0JRY0NoWG9LZTQKLS0tIG1udE1GbEtTQ2o3bGl0SW9NZmtF
OFNqTncyaHFUSzBNRzZiSTVBdkhFWVkK2v81N8c8cU1Ig9fQZOn0fltqO+Ej8Wtk
D0nMQv2fbWp6YlyE17VYPgmhdEY6+Zstve6PlBG86iQE3LTAfjG3Uw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhUmQzMU9hTlpQbHBQMm9R
Y0d5Z0lGTkFvYnc5MkRDbjNTMlUxWmI0U2t3CkdRQzdGTTNjbmprWEdYc0Nkckpr
Z2xqYkhlcHlQNG0rRFVvVTFLdFQxWVUKLS0tIFNPS1o2UVZobU5xN0U2QnI4dXA5
WWR1MWNGMVIyTGFBZXFyZlhwM09qakUK8Q26phHWY9zN5j6ZxB7+kmSgmcukfgiv
qAAzIGdgsvnUiFZCEJHD1D686C+ZxvakD4p9sA/zEIyeIBtKCq3lIA==
-----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJSVdUWXRUa2tHVGczelhu
UWk5RFl6azRJTkdxZGxvbWlnSDc3K3NlNlgwCjBRZEVta3RuNW1DZmo4RXJyTTNk
cnpxTDRGL0kwQXJmc29LNE0wV01hUGsKLS0tIGgyTWZrOHVNTGExRWtYMzJ1aXhp
cURNZXBtbnp2OUZDZDZKeEMrZlN0TEEKznlmLKFHYDm/hv3EPcHjT0A8r06GL7if
tbuJei8aWWg+uuvCBTZjHqmPUyNR1ixt84vxy1HlwXVu3dYHcG0Wug==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHR05GcVViZTY1Q0NaR1RG
SWZzYjlxQUttS0tXc1ZDbDljbXczd1gxL25ZCkM3TVJ5NzlIdUx1dTc5d3R4U3BY
RVBLazRRZ2F0anJZRXl3bFdsbXJhdVkKLS0tIFVXN21DQmtqZ2hIRi9FM2dtTTlw
aHQvOUt5UGhhR2ZXMVFOOEtKMDRtZmcK0ZX5pF08o+HLztgL1/LocDGIcOGPKqXe
9bRBkUtr5QuIxQEYj6NenhkeIxRoPxK9Re/Vsqpphv4NqKpzyOujcw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
- recipient: age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZYnlaTkd3LzFRbldWL3RZ
N2ZneVIzMnRpVVNHVWRMdXJLdjQwSWFKS2pFCmlQZUZMbG03VFVuUXcxZ3NRWjVH
SHVPYzk5NGpkeUVSU1BmQnNuaWZnZFUKLS0tIFdQZEU1YnhHZWRIajNYWTYxMEwr
UVBjaDFtSWs1b29DR0R2WS9pSGh3OEkKmG34ldBy4s9nj3ng/HQr+gN0LHJCOPJ8
EWhh7cTLSF9AmZKP0sBsj7I4hHhZlOn85bvTM9RDiRVOSz8VrObXHA==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYcmxldjA2UllycEFyTnpW
c2dIa1NXYVJSU1lwb0EvckxQQ0J4ckhSeDFBCnBTZGRYUzdSK08xeWFmaUM3SEZ3
bXJSUG1OVEU1T0Z5VGRqYUloa3k0RmsKLS0tIHdRcXE3Q2lLZTRvL3ZCSnZtSk1K
TU5Iby9qamRIcEUwc2dTdERFVmNreTAKh55E4KbM6WeFhVx3KDI/pYq+1vCNwDj6
6zfXWJvyD9Icn2ZgqpK30wyJ/R/DzmpTDR8AtujXHT6/Uikn7M1fig==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-09-21T20:28:29Z"
mac: ENC[AES256_GCM,data:e267Kxv1Pyun/VOcLepBDBEKN6uSf8/iuY8KQ8u4xK58wsWkMdSDVcDKvO/iKF/Tj9hj+lZapkaKmp5SdeX+gjpyWiZi6QmUuKsCs0jlkV2NydLtZZt9vkmY/LCguIBRMmhDgidrNcfoghTxDDK5lng5H+2MBs0r2zLID65pHUQ=,iv:tr4YFdBltnsD4uTt+0NCam7r1QzhOmdoEbfz5/+JGPI=,tag:R2dDWTC1qrwPI9ghaf1FEw==,type:str]

View File

@@ -21,38 +21,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjcWpKQ3RqSVdxcDllajc2
UzVtdmxBWmJ2QkI3SGhYRlRadGJYaDU3UVN3CkpQYkxhVm5ZQ2djbldYL2VmQWsv
SEJmam0zMzlJSFpHS3JZWVorUmh5ZDgKLS0tIFdWdU44VlRDZllCYXRTQzNyajRy
cDJqNzA3ektRWll6SkFsVnFMd1FBUEEK0j9X4lYcFaj4MnVh4jnNwrTg2Sl5TTdZ
uFvTdE4ZNtZsh3nKmj+v2J3JM8dDUtw2NSooqpoqEvCYdDqwK1kDXQ==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhV0xoZEhJYlZIUDdOSlVv
RGlOQmdSSDROaVY4L2xHSEt3cVVpc3MrRkg0CktGQ204UDNYcXN6NDJqTXFpdmxQ
RUYrWDNCZzlObWNyYStWQlRqQ0VJQzQKLS0tIEY4bG41R1k4NDlabGhoUEl4VitI
YmYvaDNWRzRlMkdUdVBxM2lwd0N1bXcKp1iUENgs/0RL6PN7b/mwbBdIPuDFfWM4
9gXuoW7FiS5MYGdUY5Ub8WlSfA6iUww+t6FB/rBhK9TDXOfIKRYmgg==
-----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGSXlITkpxcHZqR0kzMlFY
TStOVitPSm0zTURZcE92NkM2ak8xcVF6OVZBCkRRbkpBNW9yek9rWFlOa1pLSk0r
ViszS3pMNFhLQlcwdW83R1hhTUJLT0UKLS0tIG9NTm5tNzlidlJmejdoOUkvUE9X
RzV2MUFEMnlHVmp3UmgvNmJKSDFrWHcKQ7y2W0PFLs/I6Tb0J/M91+toDP8XmgWh
LYuNc9lkjTs+ylIWuMTwtXdceI+kK8hJlELT47FyKl755DzuB1ufAg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWNEtaUndrMGszbHpuYWtx
RTRCc29YcmQwYWFKeTFtaHBpM1NjR1R1dGxjCklsTFVXSmp0OUxVUzVYeFJnSWI5
M2hEM0pjTXowbGZsQ0tHdnJYdkxjTncKLS0tIG0zUEJ6ZnNOVmd5UWF1K013N1JT
dk1HeEJ3bkVUWnhIakt4eEdNUi9aSkkK6Ug6dwtSEpzMpgKvozR8BO0ir1YeRBQd
jDtkNhpc32P5uZtx/kv74vIXgOT7KCSb03b7mSIl13J2IeHQDZTPBg==
-----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzYXRodjgzR1hoR2VDNHd6
NGJ3SXpqRmJKVlY5aTI0R0hBLzFGQ0VSYmpVCi9BakFwRGlXd1ZPbWpHY2h6RUo0
VGl0T0d1LzdaZGNOZ0pDekZxVVBWUlEKLS0tIEdtVDZlN2FrcFhEU2pTMUdiZ3NH
d3ZSMGdkNzNaczBYOHFuZWJmcEM4MXMK6ayh37HUhOYPryv2Y2WlE1U0CX7qZF89
PzvHQZYcbZ2gsRW2f1uU2VoJp/6XnSipD7fCjma3iNovoPlu2+A0yw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWMm5NRmg5Y1NYNDA4MkpR
M2RPM2JLZ2NMQm9uNURNc0lSVnJaMHE1MG1JCi9CY3U5V1pnQjhPOXl5NlN0eTJW
d3dXQ3hMRGNjWlBQZDAzc0ZDWWUzNm8KLS0tIHMxQWdvY0F4Mk9zaElhMlhManFu
TXNGcFZRd1hPdW1wWFpPRklScGZqVXMKwHv5CDSdlaGlXqFKoK9motAWNVMzerXy
6K2KVn3tmlAiBzkwuEVVa4jafQjd0t3J6bPx047DP6fPZVNLMElctQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
- recipient: age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4aUk0WVhTUTZXOVRqam9O
WkVNd1FKdDA1SWpwWndHbmVBRlNaSWI4aEVnCnJTTDNYTkRtNkR5cUl0SURVQWxh
d0c5cEhJVTZ2YXdLdHFQRk9KN04vcW8KLS0tIGF3Rmp2Z0pwM0x1WnpKaVBiUE5x
MVBONDBmQjI2enNIVFFQT1hyYm45YXMK2NXWvm8G+Yrvw1NAC6AiDaxA9UftuqYe
ZB7QpfkdCT3vS52lBgcEJrM1TbaVX2868trk5kB4gjqVMPVPYxcGHg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIaTNnYlFsdFBJeGFwdUNl
MVc4OXorOVhJcDBTWDZaT3pQbTg0UFlGNkhrCnVST0IrTnliREpmS0dPMzVDZUJm
aUg4SnhtNlcydFYyTkp3N0xaSzVCREEKLS0tIE9tZmZLUERnSzY0aDdkTnY0SXJz
UDltYm4zalM2VmxmQjJRSVQ0YWpuM3cKsYQOOppHVJT2tbQQ/jXy4NcUX6aWjQxT
Y/I40tBrkwnzVFpVvf6COS+oC6/yRISwWJYYvia9xVfC5+kss9cFIw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-02T03:55:24Z"
mac: ENC[AES256_GCM,data:+NN+RgkHAIox1IgUuC2ACHneRBzgn5FzsujpbPtmw1IecxeKMMXM7Wa1ZziSkWJSjjDCcBoanox57e+BoNWN5WhWuMdCed04AKcknfKlHAtHrKhoLCsi1sZnsQX7xBmTsA5qHD8788EWfIgPk4gToXkq5KkEfvEWLvalClRK7tY=,iv:kGyw9hk6vp5iu0iMHaCLgVqdcv1gNUBqBhZbRSCa4Ks=,tag:FdKL/5ZraejphDIE2ig8GQ==,type:str]

View File

@@ -5,38 +5,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4R05sUnl0UFd3T3pzRm1y
U3piNzlpTmpZeEhkeWJxRkMzRzBWRW9LQmtBCjRHTVg5ZlozUnpsVjhIK05xYjlz
c2dwbWVKWVNXWFhTWEtlUUFjVUw2RkkKLS0tIElaNXN2ZmROdHd4bWljM3FyMEh6
Szg3WTdrVlFmSUJ1S05xNXY5RlM1V1UK7YETep9hn49UqRUjbRv6oGFUT/8lRgXx
5O5eGB1X8kPCY8zXiGWSzfo6X8O5659vWIvqjoY8nZxekgvsISS/WA==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsNWNIek9yVUJIYlhSQmVZ
NENoS2dvRDVENit2bnVNQmJ3TVZGWTkwTFh3CnViekVsaTVFZ29iQXNXUTNPS2U0
Z2JsWWN4T2tUckVJU0tpNWFaYXpGVTAKLS0tIFowcE1tZDdPREY0ZGVzYS8xNFFp
elN4TnZjZUtGOGZ1c3FiU0h4YytLTmMK9wXfpIgMcPD4FpO5CNIXnJc0wJliB35g
v4wiDb4zU4VFfWzdimSXjgZrI/ZIqB4Bx/PPi6SPhuT4oQ6LSH5sKw==
-----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpT1oraHpJb0NUQndMZW9l
YWZrOVBqOG1KME5PYS9YVE4zd2VQb0hRN1dJCmVqSzhkbU5DVmc4MFVnSnVYTi9V
RUR2UDNEK3JGOEFUWVoraGtqQVFFWkUKLS0tIDVRdU8rV3diVXNUQSsrKzlBdmFN
Q0x5QXdaOXRMc211TUhqTndQOXR6ODAKtJYiAeVTYPOpS+GykBDOLx1g3VloFo2P
fDIkOCrINnAU4y07KPhGBxCV3/2cvOPhIgsd02XqxfZPCEU/cYdCgQ==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtSmsyR0N6SWMvQVl2RzRZ
TUpXbm9oTmswdXViczVBOFBLL0lhUmV4WXhvCkdqRG5pcStUVDE4S3FSL0R2TTlG
Sm1aZTcrejRHdzU2dFozSUZucHFtUHcKLS0tIDJTSmU1dVhSeUxMSnd1NGlkR1RC
cVVIcy9QRFArUkdIM05neG5aM2EyM0UKSCIv0iU/X9bVoQCRxcQXwMbr0GE7MGkb
pn420gXMiLFBE8OOhkHg7EEjuR3n9iB3f+pTgN5v6UkxZBmZ2Xr4yg==
-----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4dTIzeCttSGNVNmhPejdW
ZFkySng2T2ZYdkRrRGRXQVpER1NJMW5XN2xVCkc0VTdsbXdLUkg5d29zZ3VmY0hH
U1cybHNob3VkdzRWbGt1bFhNeW9XN0EKLS0tIDdoc2cyaEIybjBHOU5tdVRsTWFZ
TmdZTGNDOFovMDVPakF0WTdHaUpHeFUKl0ub1OOylE2JGJNpeReebiOaVdxbd0wv
nvJD7tYYXI666Pi31OHttWhsHR+xkL8TU9Dd6uDs4QxIRQfwy/VxcA==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlL3picmtSZE8vS0Jod1RV
aGNlK2t6b3RTZWRPUmNCZXN0KytSRnRuMHdNCkd1VjU3NEhBOE9jN3gvblM2NnZF
TDRhVGh1Y21YM2J4WTZtenFHSDBBNncKLS0tIHZqcnpRbldWN2cxT1I0Qzgva3R5
a2lsbG5SUFgvZXlHWUhOc0xQS1dxaHMKDMGQujRa0s4kjrQod11mn0otxO2Zl/bv
kHG8ufANpJS5RfKNLMhAK4piUtr1o97471MSGA0ebZAUSK01fQBNfA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
- recipient: age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGUldWbldqTTEwMGF5RVFV
ZkZ0Y24ycU1hVDlaTnpGQW1SeFlzaXc2a1FrCmtrUkxLcjNsVHNXemd1cWJJdXI5
bDFxUThzSFptNWtXMlNqM09aeklUMTgKLS0tIHR5KzE3dStMTXlhUWhtUWUwSkY0
ZldyVmtRVGppQ0d0SnN5Tld4cEtmQ28K1Yij+7OxQUpEsPt/GTnP+dhEErBH1HuL
pBFXqHLAwpqiEiiNhYnb0KVWeQnIqDo9WUnrbPavcWSrSkmCsszgxQ==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4WlgxL2xMR1p4eG1Ca3FM
c1JOcDBhaHNlUjRNZjFhVm9CdkN6NEdqdGtnCnUwUFBtLzYvM1kvd1NVREdwL3B5
MVc1UEdaSTlhWXFadSsvbHNTaUZKcDgKLS0tIDc3RDg3ZUkvRVFvRWhBWDFHZjV1
NGlweEtoVGdqT2J3UzNGaGt2RFM5eE0KUCFvcv39dFM0Vm6uDuntsnZyMq+LHfJW
Sts7AJwVIGTmOolImqoVTeKFYJZu5oeKZZNsEG+gvIZptxaR0jPtow==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-10-10T05:09:54Z"
mac: ENC[AES256_GCM,data:N/BwfrwWcnot36Kn6RFZjjpUIluzq5Upy5iVVV4XSs+/0PYdlZGytjoAB+E3gXyPsLZ93UqI0A9/5KbfXBuR2oY2F7iKsu5puzgyYWa0Gl2z9YcPnyDnk1dj7Ne77xJlqR9YquGzFKF8QdqFXFA9cdE3b/1usTFhP26oxofMXs0=,iv:Iz/LzS8yeKQgDiGchYdKNymBeekhopJtBWaQGOwRZlE=,tag:hMRwxJlKR21W7otW01GmGw==,type:str]

View File

@@ -4,38 +4,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDa3NpNG5tenhqWVQ5RFAv
bjhhRWJWK0NFQVk0cGVpcVJGS3BFeWlSQWc0Ci9IT05mQTVWbmk3SFFpWE9KUnh6
bHhCSktlbzVUQm1lOHp3cVpiSHU3MDgKLS0tIGg1UU4vVVo0SXRwMjJsVUZEZkFC
TC9Eb2JaVUFDSWRMYm5jR1BBa2lEamMK4V77WUVbMXcsw83FFdL2Rk30oR4cAkqQ
kc8Z0+5kNJFUFilFb54dnWTOh27K7KZvU1qIdhG3X9fuMIHSuPnyTw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWaEZKVnhKR3ZnalQ1dWZy
THJ1Qi9EK2NHQXUxYlg1NWZqd1VuRUY0a1FNCkg0UmphaktuWklNVE85c1RuT3FV
bUwrVzIxQ2JhZmNqdnR2SUlIcGNiazgKLS0tIEVDZk4rV2tTT0pLd3I2RXVIZ0pC
RWJCbkxHMm1DRFJKZkRhRldiRFVFd2cKQvohCMbXDJzOKzfAN72/1S4CXj5d0bbK
Ge+V8Ew9S4+UR39iLtQzs7lNYYCtDxNnayEm0V8LlVkgeEj2HnS0bA==
-----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDRVdYVTY3QzR2MFJPVW9j
SUtmTldMRCs0dTlJcGFoMDVnaWMyNlF4OHlnCjg0OFFrOERKRFZVMm9NREhBOGRs
dTEwc0NZUk9hOEtvNVJDRXl0TDhCaTQKLS0tIE5OWm5CNzc5SW9IdGFud1N6Vm1D
djhzM29HK0FIdXIvaGIrRXlOMisxaTgKVCAiniAmfqJuwwiUpcGAvoyqnUEZ9gOS
SyhXMzv2cbomuOb0NiALRkd2up/uX0TVuz9wuBQvYYjJhqpFuSnbRg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBheTV2MllUaFMzTWpiRnFw
a3hHeFhyNHZGek9tWVhySnVLUzNDVzV2VUIwCitWLzJ1ZG4rVHRwZElOdTdXMmdX
ZG9RVktFQ0VvdDViSi92YzRIZXJYOHMKLS0tIDZkRHYrMmtyZUh6bE5KaldvMDY2
MitQdXpDTkNxME1pV1BML1ZRRG1NaWsKUBHmQa1io7qNp+xkEmYsn7Q6XSpQ/566
KYVB7GMSyp5YYsJv1vA8tLHnavLDam4zMZ1t24dgk8pWOZpJ4T6T4g==
-----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1UlM2MC90SEM5elBtNmpm
cnpTQVF0VEpLUDJPZkEyeGNuYnl0cEY4M0U0CjhOY25BcERjOThkbkVhNnJtaVpv
N00zOUZPWnNYaEtYMzlXZk56dGVPeGsKLS0tIDVDcGY5cG1ETHk4eXRFN1hVOXhV
Y2xncFJuNUs5ZkhLSjJyc2pzdDZxbEkKn/8BtUXPQ0OdR35ZwiHWFB0AqaDtAlG7
N4Z7iztqiscuxn8G8VVVFdkQLBY3JcrXhxPYWK4xtJeEtpIMhegxeQ==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkanhPTzY4WEhVLzFvbjhR
emVmVmZIRmVDelpONmM1SUhuT2Zadi84aW5ZClRaVGwrbjlGYkZiOVR0V2RuVWR3
UVBlNG9RUlpUZlJ6VFhKRHQ3T1VMRVkKLS0tIGpUTzM5MVA1LzdsNW5IMGlZcitR
aG9pR0RRd1NBalhweEdGNnNCVHFvb0UKIiFruo9rV/VD6XykanHIpbI6G6D3cGG/
ZGSxH1HD9qIVYDQ5LpBfUy/dZxRnpTiBJ7Veg/Siemz7kmChlmVDQA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
- recipient: age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzd1VYanVSZTd5elNlZ2NC
bnd0V0VZVmtrVDhBbW5KUHJDMUkrekVZeldRCjBNY3g1SkVKUzhRL2xsbjloUERi
UXM4T1A0a1V2eEFlQWlTQ2tDdFdaZ1kKLS0tIFFtNDZzbzYyaE5UT3R4eDJzNnU1
RG9UbWM4YTVHcFpKblQwemNScDVteVEKA6fibq6Ozwrz/tg9Hrx4bH9LCadmW5fR
IkFalgD7nqew8KwS0keyKFk93i2p6sTDZPy2/t+WryMXBIc/y0iQ5Q==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4UWRTR2x6aWJEZ1p4aE1O
RGpSM0xVRVJpTDhVa0dhTWwwTVp1Z0NkTW1RCkxRVUhkMklZUVhDSEE0bkZHaFlu
UGRvYURuMWwzOUtoTzV2V05kblRpNnMKLS0tIEgrYXBpYysvenRGMENNcTY5L0dk
a1pWRTkzSkZGaTVtbVc4VjdHdkpneGMKq+3Pd2dOJAnC/PKEYijWbk1vQSes3ykt
1A88VIO2o/isCLr9643SVkZQ4WbISA4xvInG+peEdbja0oZNRQNU3w==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-01T22:31:04Z"
mac: ENC[AES256_GCM,data:gtTuLmgVd5t1Eic+ld6x3pmAlv2+SVf4OgUICu78DJ9L1YCtmJ+LsqIoHFueMdQAmubPA8c4xYsHWCDu2dbrUDUs/79BF2u4P9lbNkJx5cco8bnPdy2tmkhcLwb0HwRduVIbgcm0wzYKUMd76Y0ChxdCddkrkk+PjXkUE7OBNg8=,iv:Eqhoc6GjB1NOnIIeRIdVoQNQm51DguH3vEX4zRUgeBE=,tag:V25oIemZpdJDMRFcZkH4bA==,type:str]

View File

@@ -51,38 +51,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWRGVscDJsU0ErZ1VBRzVq
dE5aNUZvcmhVRHVjYUJFT09hdDd0UzhIS3hNCkRFRlphRXBTd3VFTE81RjJRaE5w
bzJSaCtsT0QwMkx2WDVyZ0FzeFphWk0KLS0tIGN5M0QyWmQ4Y3lCU0FXaU9vL0hv
MEp1ekxTdWp2b2g4dFd3OVNkUlZBMGMKzNGSzYgQsNW6HEvzTWmo73GShAAv/g8+
h3/6n/ObqlKsjDyVFgiOYop3LWfwPMzmOhx4S0wsOHit0UxdyoJwWA==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnVVQ3NG9lM0l0MzlYaVA4
dEpOajlJeUxYTmJlMXJJMlN3UnRwZHEyaHlVCmYya09LMU5UQ29pQ3JCeWJnRkI4
ZEpzRk93WUhXR25QK2c0UjRlTnZld0UKLS0tIHZPUGthU0tBTVNzRmFmVUxnSkda
RUVNLzM0QUZLRFRCOFpjTXY4eHprUWcKK0+r6kWEw+gC8P+afVvw31SY63PTKb1C
D1KCOugRHnNT+xOELiVg9jjFW5lTJc4U2OBe/IpsGBujleXrWKwpvQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLUGFaaHFtVWl1cG1XdlRT
TUh0MHZTa0JhdDFSTVJZOWJBd0F0SWI0N2tNCkdnaG5DcXdDT3dqRVJDcjlsZ3Fz
ZFFaeTB4UTBQRVYzcldndm1RSjhCTzQKLS0tIDJySFIvbGpBd0l4RzYwVUd1MWpF
ZHhxdERrd3VNUGpTTlZUM25RYzJwSjAKG2DZUyomWm8Nxn6mPDKbBh1YsEUr642a
nGYxmuRVBVINbOB3gBPwgLeD+S2Vlm4vrC/u2761fTgm8KFLC+txpQ==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZY0JpODFjNkhQbnFHNlJU
V0s0YWZSTC9OTWdXR2h3S0FkM21CN3NocURVCjM5TWNNYzhkUW5jcHVuSW40ejJs
MkgydVlpejhzWFlMZHNGMzdqaEpPcFEKLS0tIHdzQ3UySDFpeWhVMDk0dmswTW9N
U1M3aXlqSHQwaG1DZysza25KZVRDU2sKp6kZa/6/Or9zdLTfFf/lKWcoHDz4v6p7
UEAA3twa1VXAk7dqmDmp0Szngu8y7iF9BE5fS1nb5n+rUa9DrwWvng==
-----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXTnc0N3JWUGk3cWV0QXNK
L0ZWM0I0NVlVbTdsZmdRall2V3FUTllidlJnCjQwbFJ1TjVNQjl3NURQenBDZVhy
QXEybkIvc0RnV1dNL1Rhem9GajhzY2cKLS0tIFk0Nm9JK2ZvenJsYVF2RUJLVzVL
bzFWRnFjd01wbDVrQnhlb3NYampEVEkKWl3/oymEX/TdMHyxE8mOopIwu4Kots27
teyBmo6aVTAQ1zSxGDszI6kgK6PC3Z/WqaMaoJilGI6k8vCkOT3oMw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrU2Y1RC9aczBVWjZoS1Vp
VFp4VmtkME0vNVpnZXRYQmxmdHhZb01MelY4CjhHcnpVenVFd0YxOGJmT3pOOEF0
a1VBNGpSNSttblF3b3ljRDI2NjAwbzgKLS0tIEdiYWpucEY5N0JVN215ZWNDZmkr
SlJJaUFzaGdwdjhwdjJUWG1TdnZIWHMKGvQWCQNr83Z0CP5jGHc2wvqOIUdGC7+2
8buS4XK22o7EotL4bbKsEw5dgWQIBRXH+9XCq56RIUYR0T/T9UW0ew==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
- recipient: age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtaG1Ea0ZyZ1IrRGxEaUdw
TzlMRE84ZDBXRTNFWHcwNE81MDZlYStTZWdFCmxLbUxORFNVVHRGYXV4bDRvV1Ra
Rzg0YnpkaDJ3alhxalFFck10MjF4MG8KLS0tIDgwSEhReERtZHZ3U2RWcnFaaHlI
UmQzNEJVVTVPRHFqVlAraTR2bHNOdmsKKCVCzZ10sEA7rGRCUxbpYlaR6Y2jZvho
THbZe5MHY1a44L2XQSZe3I+1qOVBWVSL10KYTjJIBTxoeBtjlQJAVQ==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnblN2TkRudTZCN2Vvcldr
UDdBNXYyaUJVdkRDL1Zlb21vK1NlTnJyV1MwClFRMWpQYmo5amRWMTRCTlYySTRY
YStldXhHdnR4RUcvNFpVbUZPMGpFQU0KLS0tIEV4MGZ3YUJjOVdLNDF6RFhIOGs1
bmtmNDJ5OFlQYlZTWmQ5S2FmdEZ3clEKYRQ7nuP3G63vwyhW0wLQISrkiY98F3jx
7c9qMd2eGVvrOQr5M2OEPcjKexBa9Qt6O5t+dABrTmXCa42B251zWg==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-03T21:56:09Z"
mac: ENC[AES256_GCM,data:Bnjo3TFYoGbtB8HF1i+ZQLlfeBMOjq14lu8oLRqcZ6Fx5Am0uuh+/PHClWZ/JX5suC0Kb81+aBHg2QTsLoB6zdUrRpaqa0CUxTDoGw8tpo8m6zLWvSggpYLAuRgTYqBZ0lVK1QxAi9+qVJQ5AIhYwSPrf2oq/Mpq4tFGUoG/tzM=,iv:8JqAeBVYnZM8A+CPAlKN+6SDty0XQ4AKEBJLGV8Q738=,tag:CQXE5QsfJMiI7UQoCfE3dQ==,type:str]

View File

@@ -19,38 +19,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQTUEycms3ZkdMd3hpcXJz
R2pZZEc5STZ3dUdYbUdsSGJaRWI5TWNMK1RRCjVxR1pzY0ZVUmcwSjJFYktteWoz
YmlaVkFPRnZha3h5ckV1TVQyVWZKdGMKLS0tIFgvdWF5VEJwTTcwdXZ6SDRMU3BL
V2x6NlhyY0pmUVBsYmZITjArdjJRbEkKvzsJxs5EHR0uumwhZ36MhKuMS+WkogXU
nSVRQoc5TClzYwShY1ltHK+LCl0DlB4xFoMiO4GWwH1TySKe/ywpUQ==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCWm1RTldOekErU3pxcEpP
dWducG4vTGpuYkhlZDY2a1lLemRFaW9uNFRNCnNtRExLbVBXUVBXRjhhMW1NcjR2
dkR1MFBPdDhPMldaYzk2V2pYQjZWeHMKLS0tIFc3RDhLVXdtaC82RUpPWnVGdjc3
d2JyM043WFJSL0grR0FheldHdWFSTXMKxf4LZ1sKH+HKKCT4w8AmKk+DtVoSobtn
20acQeJsbuAng+/DIQccPSp//3+3YkfsBRfSGg90vQPNKzxxNmrY1Q==
-----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnQytNaUs1M0hiYi8vdDUx
V1NtZ3VGNFVjRHRWUzliR3M3Q2Z6K3RWSHo4ClQ1RE1PeHJ4REpubVJHb0lJcGJ2
SEFvT2YvNWhMc2lneWR5NmRYc2pzVE0KLS0tIGxkRWRRRTNtVDUzVXh2L0lEa3RK
YjFSUDJHUjFUeVBFbUlKOS8ya1ZhMW8KssRH3/XT1iCVgV+6Sh25Axp0c96aHtVX
/HXN3AwTm0GJZCQnZsVIIPtoCzhUZSza+bzGZIZODYtgtCIxtdzVSw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmSTA4Yk5TbmIyVzJWb3h1
bEFsb0l0Uk5jVDdvL0hMdlBFUzBJdTA3bEZvCmRMRmdyM3lieDVGVDZ0bjRpSngz
c0FqNjRWN29zdzFsRnhtcEhUeEtwb1EKLS0tIEkwcVdpWmhKZEVZM052WU16aXZi
UjFxUlExazVhc1hkcmZuT0ZadG1pTmcKADLIwbz9KlPgTrs3kxeWEgKsfh9K9Qyp
+PSLBc8OjORDBBqqRcFJ3D9paiqppegGAPKaZ9INCXVoWke+wEOL3g==
-----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIbkZpZFJCY21IRkJjNkRB
UEdEVlZhRWhRb1ZDMjJtMmpkUmpnY3ZvMGlFCnBLcHlkMWNyMy8wenYwT2pmRTZL
dWtiWFlaR1FrL21HQTFZM2N3a3BHYW8KLS0tIFlYZWVHb0VEeDU5NnRjbDk5M2po
K0xRRFhua09DRE04WUd6NlZuQldFbEEK2OgiawCbCtbrk8l45QdjVu8+VNWbrl4i
3U9iwek30JkQSZaWBXaCZlWLvbKNjIMpwTtxDOhxmu4DUh3Hx6In/g==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSM2h6Rkp3T1c3UTJXVGEw
ZHJJNndrUGdtVnZNOWJobzZFb2U5d09LekJzCm5mSUd6V05BUUZpMm9US2JhRUNP
YnZ4U1RBSUdMaHJnd2ZGNVFyT2hKeWMKLS0tIGpzUHlVU0JMbitmSzNjOWdaRFFI
YlJoZUVoQWFHMEg4Umo3WDZHUVppQncKL8HtEF3+uI/qm8K/u7V7IlEv8Lt0QwQv
SPzuq89L/aT7hK3LyB88B2pvAKE2Z1Kj/3Z3depQfujIQsulpIg1lA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
- recipient: age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAySjBmaC9rREpUQ3BvWWNU
MWEvM3ZGb2RXZ0dMdWxLRTJCR2VSdyt5VUhBCjBvL3MxZ3pTaFQ4aGdZVnAxUmd3
YUtoZkhEV01TU0drRUdDaFZ5M2tZLzAKLS0tIHpBL3NwV2NhN0QwcHdwbFpQWlZn
eUNjc2RPOUxLTGowTlRqN3lEdjRLU2cKTTEXmHyhnL/hZGDr8ONrmzdU6Or5xkKY
GHADDt+LCg8njcZom39Aj4kpCx+f7HlV65glKwr37vZ0sL9KE+O9+w==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiNWFNMXVsdWJKYXJaMCtM
NHRVeVV0d0dXQjhwTk9ZbkpINUxUTkNISXpzCjI3blp0bkZiM1pVcDBYaVkwaUVQ
ZTRicDhmdXpybzI1SjZSdDAyYmR1eEUKLS0tIDdUTGdvQzFXMDBMemJUMTc3MURD
S3FxRUI2eEg3bGs3Rjh2YXhiMnQ5eGcKAHlMDXwb1uULH+lLuWW4dMxofXSbKRMt
Ce/mfgDwqERw8h2yotOoSkNSFBQ2kPLu3/NeTsVAfbdSMyp/T3aJ5A==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-01-16T15:38:39Z"
mac: ENC[AES256_GCM,data:4xaoGvLq1UIdozNqQ7v+pORVPDCk+FZRsCRvZ3C5AZOwSaM+UfDYZcI32AI0K80yFyhVIrrjqylykvXghbpQGAju3mv7+7Tbn5p2gqXrB/m1FuyVe/ftw7SSn8FTGL14cdHuPPkQTvV/u7z1IfX4YAOEGqtWiEfOe4YoWT3xc3A=,iv:dygbKjQ0ljgBPyk2aEIa/Mpbs/At+UzuhYy8Sndx/nk=,tag:jYbROlRxeDxqF1YqrBGL8A==,type:str]

View File

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG6scNSRnOprOvqm5DSTSMORvh9c5z0S1GzX1D7u+gMw deploy@portfolio

View File

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKbCQ/f117hL7Z02Vog1RCaOVUi95beYf//Qppnqf2Ha lidarr-reports@lidarr-reports

View File

@@ -5,6 +5,7 @@ vps:
server:
private: ENC[AES256_GCM,data:wrP/069tuQs3ObYE8Q0MNVxe3+4vZ2HIImoIdZpj1uPgdBknboX1wmANv/k=,iv:FJL5KumHos8PoXra+BB2Uc6YedsF6MD3wWyuugXzJ+E=,tag:nVuTrW2P7JvnWnv6H1SmdQ==,type:str]
public: ENC[AES256_GCM,data:YnKOf9725v9FkzdNPDVf/iinMbY/YWn6ksqEz+mpB4KHVlOvpbV6vLSKRcs=,iv:aWQNy6mT4sxVbzaXKgRzZ9XVsiBCRsOlLORRqC+uiKE=,tag:mLWv6mr3VVfw0J5BrqByXg==,type:str]
#ENC[AES256_GCM,data:u5SEQfK0Hw==,iv:+qr9WmOzQowZ/JyN1KoWhoyHA2132fmmZzIQy7o5y6k=,tag:9TPVeQgoo2nWQ9dhuYULGw==,type:comment]
home:
private: ENC[AES256_GCM,data:YZ0jvBzkMv8Bwc9u3LDJzwSqQvPj8wPUxTIeBFiLYVQQIBjm8aS1dTYuPvo=,iv:mXuW7TVERxOMmGIit3a7Spmbk/EgYuGkO66AWJUnMF0=,tag:xM7C3F3JCiud/A9yPD5ydQ==,type:str]
public: ENC[AES256_GCM,data:DcwAHhHjIxFqRL5h7p/0nkFnWiI/iqR8Fws6AuFaxjgUHKYd/6l3D6q/O/0=,iv:bBJ0bsKRiGQUSlRmHqeLQWkOIUNfG5VVpuV6MOtKZO0=,tag:harMG6GDIfclmSq3D36bTw==,type:str]
@@ -13,40 +14,40 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlemJmbnAwUHZHT3ozdWxH
Njh1ZFUvVW8zcVV6SGxrVW1IWW9ZUFBaTEh3CnJsMnFnM0d5YnBKWE5CT2Flang0
TkNZb0xCY2c4Qk1kdXRkRXcvOU1TSW8KLS0tIE1VdGEraW03bnV4VEc5c0ZheFJ0
MFJpVTlvTGJ0YXBKSnFFbXhEUEwwSmMKxOtHLbRw5e6dRW4jvqFLsl6UzKZ+mvfR
hwKJ4KEbXuCqwtPQEWk/pF0i4vzrgUP1Cp1Y7BxGGyK9ufyV/CCQIg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXN25MZVQzTmora3o3YjJs
aUptdGxJY2YxZU5XSjBjczFnTFVVdHVsRkM0CkNFN2JoelQva1ZucUxNNUJsVk9z
cVZVU0MxL2Y3b2dNRnhJSzZrSVlaRWcKLS0tIG1vTHB1dHNWa0RLR1BRV0hFUVdx
blY1QTNhUGpKZ3EzRHNadStxaCtLb1kKtyXKpZGLtrUo1HE26IWhv8245Bjcwcqe
IR2WGv7qtnpWZoaFv76LNN7YY1JViy2k2AY+TdLmFQr0Vh2n5+tH1A==
-----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5cnE5VENCMUxxOVZUdC9X
QWFMRytGamhaWENZY1Q4STR5L0Jsdk90SlUwCis4ekFWYmMwN2dESXMrVFNIamFG
RzhET2ZGdGN6b1V1ZHkyOCtDNzBWVjQKLS0tIEF1NGdoU2lqYVdIN3hwRk13SFpP
RHNOeDBlSHFpays2VkRuR2RxaGpYZ1EKwxZfRZthZHVuJe3D5pamCSxYo3hyaaVc
I0UvMDMgcDRZuEzV9g1ZEYnaVXg5InyOO0dDZuCYX/HZqTLPiaOIxg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZbHVXV2hVTWZlemdCQmI0
SGwwK0hDYjNkWGJMMTVuWnNMVW1Ebldsd0RRCmNoN2dZN2JiSEpzUzNwcjU3eFVv
QmNnWERpQVByYlRqUDQrWEF4bkRPQm8KLS0tIHNOaDhQZExuOVJIVXZGQVdFeGhQ
QXRJRFlZWXJUVW9nVDhOaUFacjFlSzgKYSs6Woc/lAr2ECcrqoMCAwvIbXTpbtTr
J4ljY3BRCdSzHEMS9IFV2j9nGu8sUrHRsO7V/Kc8i+XmTGZP76LRJA==
-----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGaUhOcHV2TkYrZWxnOCtI
TzF1RVpFY3pSa1Y2MmJjVlpKcWZnWGtOOTJ3CmRnTUpyRms2aUtvS1ZvVXFsb0ZQ
U0RiYXM3S0RKQjVwL2hqYllhZENUdmsKLS0tIDNTRHR2ZU1VTzdNNXRDU0xkcTRM
ckowd2p5bitGYVhMNU9Qc0NUeFFJV3MKPKT1/06/fKpWPOMsRaU/fpyVUf7onWGB
0P22NBzP1i5caqSrFnVVeyuhgYxabC4oUKVmjU5QIj1R8Rqh7gworw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlRGwzbm9rY3hXeHZReWZW
VngyN3ZlZDJWekExNkNBdCtLT1J6d0ZYU0hrCi9RRWNYUyt3OGh1ZVNCNWI1U3Vn
dnByZnVpYmV1RndKQUxHbUtrS0F5L3cKLS0tIDJONlcvMllKOWRxdE5ZWUZmaFEx
S1JCM2x1WDF0Z3c0ODZNb3FKOGNhMlUKGP8P/PUcMM1c4VzXLjLNp/zThu8JCiyQ
iHdz0LBSAha/m23b316z72yg3YD5q+/qDP8KczAv1SG+VvgHDKxpCg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
- recipient: age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3SHdHTDhKQzFUQVdqM0hW
Tm9QdVozaHViQVRuTExhV1BpdWYvY012enk0CmhjODlUN0FkNldGRG94bVFSTVBv
QUNWZmszRStZN24vZWhnajhIcWdXVDgKLS0tIG9ueVZsT29KRE1iM2oreWtGWGVC
SG40OS8wMHlKNmxQa0VScHQrU2NmT2sKt9xw/8jsgnV1cZndqYNiHvIf8VdEJYCl
UUJ1KPz9mvUx3ny+rK50FSD61U8PHEZm2UC0w+/qkZwRtCx21Ku6dw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5Z3hyOGFJTlpxOWFQaWsx
WE9oYmJaaWxURjdmRlVJMUcxaUZKWDZCS1VJCmxWV01DRloyM3lLemJYc2FxcUdH
M1NZRGxjVUVEUExTWjFaazhRaDdCUXcKLS0tIFF6NW4vSGJSWjN3NHFlOXRUYXhM
NXZzQmlneDNEb1UvR2NGK0kyY1lsa1kK7IQmyuVxa2hmic4yTeiAcxN41RvMcIDV
Pofrhu7q8VvB/Cxb7FjVs3Ed5Hdz9xQ60mXUKsnJV/rIssm9wx4cfg==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-09-08T00:14:52Z"
mac: ENC[AES256_GCM,data:O2herKRy4k9ZMuPzzPF5QlBC2isXdRoIsbYLJ/6X7esxtxxgNuAljx4SCR6UMT7pl3G2E33cnnBEkuAIy6SMXOaZNfOuAEJXaCwpRwCXu26lrcTf6n7UdP36GWfIRsR4utD5/vv66ch6MqmQWkW7E5zydy5dOv+BJ4XS/50OUQs=,iv:TscYNQaeI+mBxyobxI1O4wUzRtA27pvjXz27kqMJhA0=,tag:zx/xrYAWJCxYz5HRTKzYfQ==,type:str]
lastmodified: "2026-02-04T18:37:11Z"
mac: ENC[AES256_GCM,data:AlrMK34dWDm5hfVwnQnzk3l8NIRbiVV6KHa6io9S9l07WvC3TYLTOJS6xOi4pkEz6sqQ7IpZU7RRdosxuQp50NmMEt2QYawTHFZIgzFYeKRbl5N5LCu9afC6yTtvG/sT7uenTMhh2qT1JBwebJiUdM9zNVUzWlW5d1SdxrHgIbs=,iv:dvqsDaC+trhY1kheYUEOEwHfCDz0Mu7N0LpfjnKko5g=,tag:tuqyK8vuwSrk1kf+Vi7MKg==,type:str]
unencrypted_suffix: _unencrypted
version: 3.10.2
version: 3.11.0

View File

@@ -0,0 +1,34 @@
# Specification Quality Checklist: VPS Migration
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-02-04
**Feature**: /home/jawz/Development/NixOS/specs/004-vps-migration/spec.md
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- All checks passed on first review.

View File

@@ -0,0 +1,38 @@
openapi: 3.0.3
info:
title: VPS Migration Verification API
version: 0.1.0
description: |
Optional verification endpoints for migration validation. These describe
checks that can be automated; if no API is implemented, treat as a checklist.
paths:
/verify/proxy:
get:
summary: Verify reverse proxy routing to host services
responses:
"200":
description: Proxy mappings resolve to services on host server
/verify/firewall:
get:
summary: Verify nftables parity against the iptables reference
responses:
"200":
description: Firewall flows match expected allow/deny behavior
/verify/vpn:
get:
summary: Verify VPN peer connectivity and address assignment
responses:
"200":
description: All peers connect with correct addresses
/verify/ssh:
get:
summary: Verify SSH access for authorized principals
responses:
"200":
description: Authorized keys allow expected access only
/verify/analytics:
get:
summary: Verify analytics data migrated successfully
responses:
"200":
description: Historical analytics data present on new server

View File

@@ -0,0 +1,41 @@
# Data Model: VPS Migration
## Host
- **Fields**: name, role (primary/secondary), publicIp, vpnEndpoint, services[], proxyMappings[], firewallRuleSet
- **Rules**: Exactly one primary host for reverse proxying.
## Service
- **Fields**: name, enabled, runsOnHost, proxyEnabled, domains[]
- **Rules**: Services remain on host server; proxyEnabled true on VPS for all enabled services.
## ProxyMapping
- **Fields**: domain, targetService, tlsRequired
- **Rules**: domain must be unique across mappings; domain must match service definitions.
## FirewallRuleSet
- **Fields**: sourceFile (iptables reference), rules[], appliedHost
- **Rules**: Ruleset must be applied as-is; no translation allowed.
## VPNPeer
- **Fields**: name, publicKeyRef, allowedIps[]
- **Rules**: allowedIps must be unique across peers; publicKeyRef must resolve via secrets system.
## VPNInterface
- **Fields**: addressRanges[], listenPort, privateKeyRef
- **Rules**: privateKeyRef stored in secrets system; listenPort exposed on VPS.
## ServiceUser
- **Fields**: username, group, authorizedKeys[]
- **Rules**: deploy uses ed25519_deploy.pub; lidarr-reports uses ed25519_lidarr-reports.pub.
## MigrationChecklistItem
- **Fields**: task, verificationStep, status
- **Rules**: each migration task must have a verification step.

View File

@@ -0,0 +1,52 @@
# Implementation Plan: VPS Migration
**Branch**: `004-vps-migration` | **Date**: 2026-02-04 | **Spec**: /home/jawz/Development/NixOS/specs/004-vps-migration/spec.md
**Input**: Feature specification from `/specs/004-vps-migration/spec.md`
## Summary
Migrate VPS responsibilities to the new NixOS host by making it the primary reverse-proxy host (nginx only), mirroring the existing iptables ruleset via nftables/NixOS equivalents, enabling wireguard with secret-managed keys, and restoring SSH/service-user access, while keeping all services running on the host server. Provide validation steps, review historical configs for gaps, and document analytics data migration.
## Technical Context
**Language/Version**: Nix (flakes; nixpkgs 25.11)
**Primary Dependencies**: NixOS modules, sops-nix, nginx, wireguard, openssh, nftables (iptables reference)
**Storage**: Files (configuration and secrets)
**Testing**: Manual validation steps (no automated test harness)
**Target Platform**: Linux server (NixOS)
**Project Type**: configuration repo
**Performance Goals**: N/A (configuration change)
**Constraints**: Services remain on host server; VPS only terminates proxy and exposes wireguard port; nftables parity required
**Scale/Scope**: Single VPS + host server, small set of VPN peers and admin SSH principals
## Constitution Check
No enforceable constitution rules are defined (placeholders only). Gate passes by default.
Post-design check: unchanged (no enforceable gates found).
## Project Structure
### Documentation (this feature)
```text
specs/004-vps-migration/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
└── tasks.md
```
### Source Code (repository root)
```text
hosts/
modules/
secrets/
iptables (reference ruleset)
scripts/
```
**Structure Decision**: Use the existing NixOS configuration layout (`hosts/`, `modules/`, `secrets/`) and the root `iptables` ruleset file as the reference for nftables parity.

View File

@@ -0,0 +1,107 @@
# Quickstart: VPS Migration
## Prerequisites
- Access to this repo and the new VPS host configuration
- Existing iptables ruleset file available at repo root (reference for nftables parity): `iptables`
- VPN keys present in the secrets system
- SSH public keys present in `secrets/ssh/`
## Steps
1. Review the spec and clarifications:
- `/home/jawz/Development/NixOS/specs/004-vps-migration/spec.md`
2. Ensure secrets are available:
- VPN private/public keys are stored in the secrets system
- `secrets/ssh/ed25519_deploy.pub` and `secrets/ssh/ed25519_lidarr-reports.pub` exist
3. Update host configuration:
- Set new VPS as primary reverse proxy host
- Enable proxying for all enabled services (services remain on host server)
- Apply nftables/NixOS firewall rules derived from the iptables reference
- Enable wireguard on VPS and expose port
- Add service users and admin SSH keys
- Update VPS public IP to `45.79.25.87` in SSH configuration
- Update host server VPN client to target the new VPS
4. Provide and review legacy proxy config snapshot:
- Supply caddy files for subdomain comparison
- Treat caddy as migration input only; nginx is the only proxy target for NixOS runtime
## Caddy vs Nix Subdomain Comparison (from provided caddy/ directory)
**Caddy-only domains (present in caddy, not found in current Nix server hosts):**
- danilo-reyes.com
- www.danilo-reyes.com
- blog.danilo-reyes.com
- www.blog.danilo-reyes.com
- mb-report.lebubu.org
- torrent.lebubu.org
**Nix-only domains (present in Nix server hosts, not in caddy config):**
- auth-proxy.lebubu.org
- comments.danilo-reyes.com
- flix.rotehaare.art
- 55a608953f6d64c199.lebubu.org
- pYLemuAfsrzNBaH77xSu.lebubu.org
- bookmarks.lebubu.org
- drpp.lebubu.org
- portfolio.lebubu.org
- qampqwn4wprhqny8h8zj.lebubu.org
- requests.lebubu.org
- start.lebubu.org
- sync.lebubu.org
- tranga.lebubu.org
**Notes:**
- `auth-proxy.lebubu.org` appears only in `15-private.caddyfile__` (not imported by Caddy), so it is currently inactive in caddy.
- `danilo-reyes.com` and `blog.danilo-reyes.com` are handled as static sites in caddy; Nix has `my.websites.portfolio` and `isso` which may need mapping to these domains.
- `mb-report.lebubu.org` and `torrent.lebubu.org` are present in caddy but no matching Nix server host was found.
5. Migrate analytics data:
- Identify the analytics system (e.g., Plausible) and its data store location or database
- Freeze writes during export (stop the analytics service or enable maintenance mode)
- Export analytics data from the existing server (db dump or data directory archive)
- Transfer the export to the new server using the secure path already used for secrets/config
- Import the data on the new server and restart the analytics service
- Validate historical data is present (date range coverage, dashboard counts, and sample events)
6. Run verification steps for each task (per spec FR-012).
## Clarification Candidates From History Review
- `opentracker` was installed and enabled (`systemctl enable --now opentracker`) with firewall rules for TCP/UDP `6969`; confirm if tracker service is still required on NixOS.
- `ip6tables` was enabled on Fedora (`systemctl enable ip6tables`); confirm if equivalent IPv6 policy is required on VPS.
- `net.ipv4.conf.wg0.rp_filter=0` was set during forwarding troubleshooting; confirm if this sysctl needs to be persisted on VPS.
- Fedora-specific SELinux SSH port handling (`semanage ssh_port_t`) appears in history; confirm it can remain excluded on NixOS.
## Verification Steps
- **T001**: `test -f ./iptables && test -f ./secrets/ssh/ed25519_deploy.pub && test -f ./secrets/ssh/ed25519_lidarr-reports.pub && test -f ./secrets/wireguard.yaml`
- **T002**: verify this section exists in `/home/jawz/Development/NixOS/specs/004-vps-migration/quickstart.md`
- **T003**: `rg -n "mainServer|enableProxy" hosts/server/toggles.nix modules/modules.nix`
- **T004**: `rg -n "wireguard|wg0|services.wireguard" modules/services/wireguard.nix hosts/vps/configuration.nix`
- **T005**: `rg -n "vps|45.79.25.87|programs.ssh" config/jawz.nix modules/modules.nix`
- **T006**: `rg -n "/etc/caddy/Caddyfile.d" sudo_hist jawz_hist`
- **T007**: `rg -n 'mainServer = "vps"' hosts/server/toggles.nix modules/modules.nix`
- **T008**: `rg -n "enableProxy = true" hosts/vps/toggles.nix hosts/vps/configuration.nix hosts/server/toggles.nix`
- **T009**: ensure Caddy vs Nix comparison section remains in this file
- **T010**: `rg -n "iqQCY4iAWO-ca/pem|certPath|proxyReversePrivate" modules/network/nginx.nix modules/servers`
- **T011**: `rg -n "nftables|forwardPorts|vps-snat" hosts/vps/configuration.nix`
- **T012**: `rg -n "services.wireguard.enable = true" hosts/vps/configuration.nix`
- **T013**: confirm `wireguard/private` exists in `secrets/wireguard.yaml`
- **T014**: `rg -n "10.77.0.1/24|10.8.0.1/24|10.9.0.1/24|AllowedIPs|allowedIPs" modules/services/wireguard.nix`
- **T015**: `rg -n "users\\.deploy|users\\.lidarr-reports|ed25519_deploy|ed25519_lidarr-reports" hosts/vps/configuration.nix`
- **T016**: `rg -n "workstation|server|deacero|galaxy" hosts/vps/configuration.nix`
- **T017**: `rg -n "ports = \\[ 3456 \\]|PermitRootLogin = \"no\"" hosts/vps/configuration.nix`
- **T018**: `rg -n "sudo-rs\\.extraRules|nixos-rebuild|nixremote" hosts/vps/configuration.nix`
- **T019**: `rg -n "nixworkstation" hosts/vps/configuration.nix`
- **T020**: `rg -n "45\\.33\\.0\\.228" modules/modules.nix config/jawz.nix`
- **T021**: `rg -n "endpoint = .*my\\.ips\\.vps" hosts/server/configuration.nix`
- **T022**: verify "Clarification Candidates From History Review" section exists in this file
- **T023**: `rg -n "Migrate analytics data|Export analytics|Import.*analytics|Validate historical data" /home/jawz/Development/NixOS/specs/004-vps-migration/quickstart.md`
- **T024**: verify each task from T001-T026 has a corresponding verification line in this section
- **T025**: `rg -n "caddy|Caddy" README.org docs || true` and confirm no active-proxy references remain outside legacy migration notes
- **T026**: `rg -n "T0[0-2][0-9]" /home/jawz/Development/NixOS/specs/004-vps-migration/tasks.md` and confirm each task mentions at least one concrete path
- **T027**: `rg -n "modules/websites|danilo-reyes.com|blog.danilo-reyes.com|mb-report.lebubu.org" modules/websites hosts/vps/toggles.nix`

View File

@@ -0,0 +1,31 @@
# Research: VPS Migration
## Decision 1: Reverse proxy role
- **Decision**: New VPS runs nginx as the primary reverse proxy; services remain on the host server.
- **Rationale**: Matches the clarified scope and minimizes service migration risk while restoring proxy functionality.
- **Alternatives considered**: Migrating services to VPS; keeping old proxy (caddy) on Fedora VPS.
## Decision 2: Firewall parity
- **Decision**: Use the existing iptables ruleset as the source of truth and implement equivalent nftables/NixOS rules on the new VPS.
- **Rationale**: Ensures exact behavioral parity for complex routing and hot-swap behavior.
- **Alternatives considered**: Translating to another firewall system; partial translation with mixed rules.
## Decision 3: VPN key handling
- **Decision**: Store VPN keys only in the existing secrets system; no plaintext keys in config.
- **Rationale**: Preserves confidentiality and aligns with encrypted secrets workflow.
- **Alternatives considered**: Plaintext inline keys; separate unmanaged secrets store.
## Decision 4: Admin SSH principals
- **Decision**: Limit admin SSH authorized_keys entries to workstation, server, deacero, and galaxy.
- **Rationale**: Keeps access scope bounded to explicitly requested principals.
- **Alternatives considered**: Auto-adding other hosts found in config; adding only after confirmation.
## Decision 5: Analytics (Plausible) migration
- **Decision**: Migrate existing analytics data to the new server.
- **Rationale**: Preserves historical reporting and continuity of metrics.
- **Alternatives considered**: Fresh start with no history; read-only legacy instance for history.

View File

@@ -0,0 +1,177 @@
# Feature Specification: VPS Migration
**Feature Branch**: `004-vps-migration`
**Created**: 2026-02-04
**Status**: Draft
**Input**: User description: "start feature branch 004, the git fetch command will fail, so force 004. Feature 003 added a new hosts vps, as a linode host, I want to now fully migrate my existing fedora vps to this new nixos vps. to do so I want to bring in the configurations fedora vps has. 1. right now the nginx logic of my servers is disabled, because I let the fedora vps handle the reverse proxy through caddy. But I dont want that caddy logic, on nixos I want to let nginx take care of the reverse proxies, plus the logic is already backed in, there is a isLocal logic to the factory, and I dont remember exactly the name of the code. but there is some flag under the my. options that specifies the mainHost, the constitution mentions that mainHost is the host handling nginx and because the vps will be it, then main host needs to become vps, I think before it was miniserver. This change means, that all the currently enabled servers on the toggles.nix from the host server, should have the enableProxy flag on vps (double check the logic) this should make it so, that nginx runs on vps, and the servers run on server. 2. Add a step to ask me for the caddy files, just to check that the subdomains caddy handles for each server match the subdomains on the servers/.*nix files. 3. I use iptables on the fedora vps, and the nixos vps, well I dont mind you using another firewall but there are some complex firewall rules that I need them to work 100% as the original vps, the rules will be on a file named iptables (treat this as the reference ruleset for nftables parity), this is perhaps the most important step, otherwise the complex network configuration this vps has wont be able to hot swap and serve my servers to the world.
4. modify the existing wireguard.nix module, doublecheck that isnt toggled anywhere, toggle it on vps and add this configuration to it
[Interface]
#DNS = 10.77.0.1
Address = 10.77.0.1/24, 10.8.0.1/24, 10.9.0.1/24
ListenPort = 51820
PrivateKey = aDQHN3DfAGEFjVHRKIJ34CJKPcKx7HdYzkEbRNBNWGw=
# me
[Peer]
PublicKey = OUiqluRaS4hmGvLJ3csQrnIM3Zzet50gsqtTABaUkH4=
AllowedIPs = 10.77.0.2/32
# friends
[Peer] # 7351
PublicKey = rFgT6TXzRazK6GMazMNGjtOvzAAPST0LvCfN7QXsLho=
AllowedIPs = 10.8.0.2/32
[Peer]
PublicKey = R1CTx5+CXivMI6ZEmRYsyFUFILhe6Qnub0iEIRvvrEY=
AllowedIPs = 10.8.0.3/32
[Peer]
PublicKey = ecPNSacD6yVwpnLBs171z0xkw9M1DXKh/Kn70cIBcwA=
AllowedIPs = 10.8.0.4/32
[Peer]
PublicKey = yg+2miZCrx89znFaUlU/le/7UIPgEAMY74fZfEwz8g4=
AllowedIPs = 10.8.0.5/32
# # gooners
# [Peer]
# PublicKey = GawtOvsZ75avelIri5CjGoPXd8AFpi9qlZ6dSsqUISE=
# AllowedIPs = 10.77.0.2/32, 10.9.0.2/32
can I use sops to encrypt the public and private keys? if so, on modules.nix you will see that the ips on that wireguard config correspond to wg-friend1...n when you get to this step pause and tell me to create the sops secrets for these public keys.
5. I have two cicds on this server
drwxrwxr-x. 11 deploy www-data 4096 Dec 26 20:47 blog
drwxr-xr-x. 2 lidarr-reports lidarr-reports 4096 Nov 11 17:52 lidarr-mb-gap
drwxrwxr-x. 12 deploy www-data 4096 Dec 26 21:01 portfolio
I need you to create the service users and groups for deploy and lidarr-reports.
in those, I need you to add ./secrets/ssh/ed25519_deploy.pub to authorized_keys for the user deploy
and for lidarr-reports ed25519_lidarr-reports.pub
6. similar to every other host, add ssh login authorized_keys for workstation, server, deacero, galaxy and check if Im missing one. Because this will replace the ssh vps on the ssh config, you need to replace the existing vps ip with 45.79.25.87. 7. change the configuration on the host server, so that its wireguard session, connects to this server (i think will ve done automagically when the ip changes right?) 8. Ive added sudo_hist and jawz_hist, which are a dump of the histfile of this server, just check if there is a configuration that Im missing, something I did on there that I missed, and add it to the clarification list, so when I run clarify I tell you if I want that or not, granted lots of those commands are trial and error, so I think I have everything. 9. I have setup a plausible server, write the steps necesary to migrate it, I dont know.
10. add verification steps for every task we did, when youre done and"
## Clarifications
### Session 2026-02-04
- Q: Are any services being migrated to the new VPS, and what does enableProxy do? → A: No services are migrated; enableProxy only configures nginx on the VPS, wireguard exposes the port, and services continue running on the host server.
- Q: How should the analytics service be migrated? → A: Migrate existing analytics data to the new server.
- Q: How should firewall parity be achieved on the new VPS? → A: Use the existing iptables ruleset as the source of truth and implement equivalent nftables/NixOS firewall rules; document any intentional deviations.
- Q: Where should VPN keys be stored? → A: Preserve keys only in the existing secrets system.
- Q: Which admin hosts should receive SSH authorized_keys entries? → A: Only the listed hosts (workstation, server, deacero, galaxy).
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Migrate VPS as Primary Host (Priority: P1)
As an operator, I want the new VPS to become the primary host for reverse proxying and networking while services continue running on the host server, so public traffic and internal tunnels continue working after the migration.
**Why this priority**: This is the core migration goal and failure would cause outages.
**Independent Test**: Can be fully tested by switching the primary host role to the new VPS and verifying proxy and tunnel connectivity without depending on the other stories.
**Acceptance Scenarios**:
1. **Given** the new VPS is designated as the primary host, **When** proxying is enabled, **Then** public endpoints resolve through the new VPS while services remain on the host server.
2. **Given** the previous VPS is no longer handling proxying, **When** traffic is routed through the new VPS, **Then** no service loses external access.
---
### User Story 2 - Preserve Firewall Behavior (Priority: P1)
As an operator, I want the firewall behavior on the new VPS to match the existing VPS so that all current network paths continue to function.
**Why this priority**: Firewall parity is critical to avoid breaking complex routing and hot-swap behavior.
**Independent Test**: Can be fully tested by comparing allowed/blocked traffic and confirming all required network paths remain functional.
**Acceptance Scenarios**:
1. **Given** the firewall rules are applied to the new VPS, **When** all known inbound and outbound paths are exercised, **Then** they behave identically to the existing VPS.
---
### User Story 3 - Restore Secure Access and VPN Peers (Priority: P2)
As an operator, I want VPN peers and SSH access to be configured on the new VPS so administration and CI/CD access remain available.
**Why this priority**: Secure access is required for operating and deploying services.
**Independent Test**: Can be fully tested by connecting each VPN peer and verifying SSH access for each authorized user.
**Acceptance Scenarios**:
1. **Given** the VPN configuration is enabled on the new VPS, **When** each peer connects, **Then** each peer receives the correct addresses and can reach intended resources.
2. **Given** service users and admin users are created on the new VPS, **When** their authorized keys are used, **Then** SSH access succeeds with the expected permissions.
---
### User Story 4 - Capture Migration Gaps and Validation (Priority: P3)
As an operator, I want a checklist of potential missing configuration from existing server history and clear verification steps so the migration is safe and complete.
**Why this priority**: This reduces risk of overlooked manual changes and provides confidence during cutover.
**Independent Test**: Can be fully tested by running the verification steps and confirming no missing items remain.
**Acceptance Scenarios**:
1. **Given** historical command logs are reviewed, **When** likely missing configurations are identified, **Then** they are listed as clarifications for user confirmation.
2. **Given** verification steps are provided for each task, **When** the operator executes them, **Then** each migration task can be validated.
---
### Edge Cases
- What happens when a subdomain mapping differs between the previous proxy configuration and the current service definitions?
- How does the system handle a firewall rule that is ambiguous or conflicts with existing policy?
- What happens if an SSH key file is missing or invalid for a service user?
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: The system MUST designate the new VPS as the primary host for reverse proxying and ensure all enabled services are routed through it without relocating the services.
- **FR-002**: The system MUST ensure proxy configuration is enabled for all services currently enabled on the host server so traffic flows through the new VPS while services remain on the host server.
- **FR-003**: The system MUST request existing proxy configuration files for verification and flag any subdomain mismatches against current service definitions.
- **FR-004**: The system MUST mirror the existing iptables behavior on the new VPS using nftables/NixOS firewall rules and document any intentional deviations from the source ruleset.
- **FR-005**: The system MUST enable the VPN configuration on the new VPS with the specified peer addresses and ensure each peer is uniquely identified.
- **FR-006**: The system MUST support encrypting sensitive VPN keys and pause for user-provided secret material when required.
- **FR-015**: The system MUST store VPN keys only in the existing secrets system and must not place them in plaintext configuration.
- **FR-007**: The system MUST create service users and groups for deployment workflows and grant SSH access via specified public keys.
- **FR-008**: The system MUST configure SSH access for all standard admin hosts and update the VPS connection target to the new public IP.
- **FR-016**: The system MUST grant SSH access only to workstation, server, deacero, and galaxy admin hosts.
- **FR-017**: The system MUST configure SSHD to use port 3456 and disable root/password authentication to match the existing VPS security posture.
- **FR-018**: The system MUST harden remote rebuild access by using a non-root SSH user with least-privilege access for rebuild operations.
- **FR-009**: The system MUST update dependent host configurations so existing VPN client connections target the new VPS.
- **FR-010**: The system MUST review provided history logs and produce a clarification list of potential missing configurations.
- **FR-011**: The system MUST document migration steps for the analytics service and include them in the migration plan.
- **FR-013**: The system MUST include analytics data migration as part of the analytics service migration steps.
- **FR-012**: The system MUST provide verification steps for each migration task performed.
### Key Entities *(include if feature involves data)*
- **Host**: A server instance that can be assigned primary or secondary roles and hosts services.
- **Service**: A deployable workload with external endpoints and internal configuration.
- **Proxy Mapping**: The set of subdomains and routing rules that map public traffic to services.
- **Firewall Rule Set**: The collection of allowed and blocked network flows required for the VPS.
- **VPN Peer**: A client identity with assigned addresses and access constraints.
- **SSH Key**: A public key used for authenticated access to a user account.
- **Migration Checklist**: A list of tasks and verification steps that confirm readiness.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: 100% of services previously reachable via the old VPS are reachable via the new VPS after cutover.
- **SC-002**: All documented firewall flows (inbound and outbound) pass or block with the same outcomes as the old VPS.
- **SC-003**: 100% of configured VPN peers can connect and reach required internal addresses.
- **SC-004**: 100% of authorized SSH users can authenticate using their specified keys.
- **SC-005**: Migration verification steps can be completed in a single run without unresolved failures.
## Assumptions
- The existing proxy configuration files will be provided by the user for comparison.
- The firewall rules from the existing VPS are authoritative and should be mirrored on the new VPS, even if implemented via nftables equivalents.
- The list of standard admin hosts for SSH access is complete unless the review identifies an omission.
- The analytics service migration steps are documentation-only and do not require immediate cutover.

View File

@@ -0,0 +1,93 @@
# Tasks: VPS Migration
**Branch**: `004-vps-migration`
**Date**: 2026-02-04
**Spec**: /home/jawz/Development/NixOS/specs/004-vps-migration/spec.md
**Plan**: /home/jawz/Development/NixOS/specs/004-vps-migration/plan.md
## Implementation Strategy
Deliver MVP as User Story 1 (primary host reverse proxy + keep services on host server). Then complete firewall parity (US2), secure access (US3), and migration gap review + verification (US4).
## Phase 1: Setup
- [x] T001 Confirm baseline files exist: iptables (reference ruleset), secrets/ssh/ed25519_deploy.pub, secrets/ssh/ed25519_lidarr-reports.pub, secrets system entries for VPN keys
- [x] T002 Create working checklist placeholder for verification steps in /home/jawz/Development/NixOS/specs/004-vps-migration/tasks.md (this file)
## Phase 2: Foundational
- [x] T003 [P] Review mainServer and enableProxy options in hosts/server/toggles.nix and modules/modules.nix
- [x] T004 [P] Review wireguard module in modules/services/wireguard.nix and VPS host config in hosts/vps/configuration.nix
- [x] T005 [P] Review SSH host/IP settings in config/jawz.nix and modules/modules.nix for vps IP updates
- [x] T006 [P] Review caddy file list references in ./jawz_hist and ./sudo_hist to prepare subdomain comparison inputs
## Phase 3: User Story 1 (P1) - Primary VPS reverse proxy
**Story goal**: New VPS is primary reverse-proxy host (nginx only) while services remain on host server.
**Independent test criteria**: Proxy mappings resolve through VPS to host server services without relocating services.
- [x] T007 [US1] Set mainServer to \"vps\" in hosts/server/toggles.nix
- [x] T008 [US1] Enable proxying on VPS by setting my.enableProxy = true in hosts/vps/configuration.nix and ensure services in hosts/server/toggles.nix have enableProxy = true
- [x] T009 [US1] Capture provided caddy config files (e.g., /etc/caddy/Caddyfile.d/*) and compare subdomains to modules/servers/*.nix domain definitions; document mismatches in specs/004-vps-migration/quickstart.md
- [x] T010 [US1] Add shared client certificate handling from modules/servers/synapse.nix into the factory or shared module and apply it to mTLS-protected sites (use secrets/certs.yaml for client CA)
## Phase 4: User Story 2 (P1) - Firewall parity
**Story goal**: Firewall behavior on new VPS matches old VPS by implementing nftables/NixOS rules derived from the iptables reference.
**Independent test criteria**: Known inbound/outbound flows match existing VPS behavior.
- [x] T011 [US2] Apply firewall parity to VPS configuration using nftables/NixOS rules derived from the repo root iptables reference and document any intentional deviations
## Phase 5: User Story 3 (P2) - Secure access and VPN peers
**Story goal**: Wireguard enabled on VPS with secrets-managed keys; SSH access for service users and admin hosts.
**Independent test criteria**: VPN peers connect with correct addresses; SSH keys authenticate as expected.
- [x] T012 [US3] Enable wireguard module on VPS in hosts/vps/configuration.nix (my.services.wireguard.enable = true) and ensure listen port exposed
- [x] T013 [US3] Add sops secrets entries for wireguard keys in secrets/wireguard.yaml and confirm user-provided key material
- [x] T014 [US3] Update wireguard peer configuration in modules/services/wireguard.nix using sops secrets refs for public/private keys (no plaintext)
- [x] T015 [US3] Add service users and groups deploy and lidarr-reports with authorized_keys in hosts/vps/configuration.nix using secrets/ssh/ed25519_deploy.pub and secrets/ssh/ed25519_lidarr-reports.pub
- [x] T016 [US3] Add admin SSH authorized_keys for workstation, server, deacero, galaxy in hosts/vps/configuration.nix
- [x] T017 [US3] Configure sshd port and auth settings in hosts/vps/configuration.nix to match: Port 3456, PermitRootLogin no, PasswordAuthentication no
- [x] T018 [US3] Harden remote rebuild access by switching to a non-root SSH user for rebuilds (nixremote) and requiring sudo for nixos-rebuild in hosts/vps/configuration.nix and modules/users/nixremote.nix
- [x] T019 [US3] Restrict SSH access for remote rebuilds by limiting allowed users/keys for nixremote (update inputs.self.lib.getSshKeys list in hosts/vps/configuration.nix)
- [x] T020 [US3] Update VPS IP to 45.79.25.87 in modules/modules.nix and config/jawz.nix SSH host entry
- [x] T021 [US3] Update host server wireguard client configuration in hosts/server/configuration.nix to target the new VPS endpoint
## Phase 6: User Story 4 (P3) - Migration gaps and verification
**Story goal**: Identify missing configuration from history logs and provide verification steps for every task.
**Independent test criteria**: Clarification list exists and each task has a verification step.
- [x] T022 [US4] Review sudo_hist and jawz_hist for missing configuration; record clarification list in specs/004-vps-migration/quickstart.md
- [x] T023 [US4] Document analytics data migration steps (export, import, validate) in specs/004-vps-migration/quickstart.md
- [x] T024 [US4] Add verification steps for each task in specs/004-vps-migration/quickstart.md
## Phase 7: Polish & Cross-Cutting Concerns
- [x] T025 [P] Update references to old VPS proxy logic (caddy) to ensure nginx is the only runtime proxy in README.org and docs/*.md
- [x] T026 [P] Validate all task descriptions include explicit file paths in specs/004-vps-migration/tasks.md and update mismatches
- [x] T027 [P] Move static site vhosts (portfolio/blog and mb-report) into modules/websites and enable them via host toggles
## Dependencies
- US1 → US2 → US3 → US4
## Parallel Execution Examples
- US1: T007, T008, T009 can proceed once T003 and T006 are reviewed.
- US2: T011 can proceed once the iptables reference ruleset location is identified.
- US3: T012, T016, T017, T018, and T020 can proceed after T004 and T005 review; T013 depends on user-provided secrets.
- US4: T022, T023, T024 can proceed independently once logs are reviewed and quickstart.md is open.
## Validation
- All tasks use the required checklist format with IDs, story labels, and explicit file paths.
## Verification Steps (Placeholder)
- To be filled during T024 with per-task verification steps.