67 Commits

Author SHA1 Message Date
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
NixOS Builder Bot
ef9a11d76b Weekly flake update: 2026-02-06 11:02 UTC 2026-02-06 05:02:07 -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
542fd2485c further declare nextcloud port
All checks were successful
Weekly NixOS Build & Cache / build-and-cache (push) Successful in 1h31m31s
2026-02-05 04:59:23 -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
b8ab2171dc nextcloud declarativedly set port 2026-02-05 03:51:16 -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
b5e358ee22 Merge pull request '003-vps-image-migration' (#4) from 003-vps-image-migration into main
Some checks failed
MCP Tests / mcp-tests (push) Failing after 6s
Reviewed-on: #4
2026-02-03 19:54:21 -06:00
Danilo Reyes
f845699845 meh
Some checks failed
MCP Tests / mcp-tests (pull_request) Failing after 8s
2026-02-03 19:53:15 -06:00
Danilo Reyes
47910ab3a0 vps hardware 2026-02-03 17:53:00 -06:00
Danilo Reyes
26dcef64ca new sops 2026-02-03 17:43:14 -06:00
Danilo Reyes
d99da36f3e syncthing function parameters 2026-02-03 17:35:42 -06:00
Danilo Reyes
a90eb89af2 hmmm 2026-02-03 17:29:14 -06:00
Danilo Reyes
59c8234d3c fix? 2026-02-03 17:24:05 -06:00
Danilo Reyes
b07d867d78 linode-image imports 2026-02-03 17:21:29 -06:00
Danilo Reyes
2f535cc91a linode setup 2026-02-03 17:02:16 -06:00
Danilo Reyes
42b39513a1 finish linode image 2026-02-03 16:28:26 -06:00
Danilo Reyes
592eb04e66 vps ssh keys 2026-02-03 16:21:55 -06:00
Danilo Reyes
dbd3af3d0f new hosts vps 2026-02-03 15:31:47 -06:00
Danilo Reyes
f6b1a01438 removed nixos-generators 2026-02-03 15:17:18 -06:00
Danilo Reyes
979bb915a6 init 2026-02-03 15:13:56 -06:00
Danilo Reyes
da352265f6 whitelist syncthing 2026-02-03 15:09:04 -06:00
Danilo Reyes
d2f8e279d1 branch fixes
Some checks failed
MCP Tests / mcp-tests (push) Failing after 8s
2026-02-03 13:41:25 -06:00
Danilo Reyes
6fcb1b50b4 yamtrack apis 2026-02-03 13:25:10 -06:00
72 changed files with 2252 additions and 783 deletions

View File

@@ -2,7 +2,7 @@ keys:
- &devkey age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37 - &devkey age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
- &workstation age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - &workstation age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
- &server age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - &server age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
- &miniserver age13w4elx3x6afrte2d82lak59mwr2k25wfz3hx79tny6sfdk66lqjq989dzl - &vps age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
creation_rules: creation_rules:
- path_regex: secrets/secrets.yaml$ - path_regex: secrets/secrets.yaml$
key_groups: key_groups:
@@ -10,46 +10,46 @@ creation_rules:
- *devkey - *devkey
- *workstation - *workstation
- *server - *server
- *miniserver - *vps
- path_regex: secrets/keys.yaml$ - path_regex: secrets/keys.yaml$
key_groups: key_groups:
- age: - age:
- *devkey - *devkey
- *workstation - *workstation
- *server - *server
- *miniserver - *vps
- path_regex: secrets/env.yaml$ - path_regex: secrets/env.yaml$
key_groups: key_groups:
- age: - age:
- *devkey - *devkey
- *workstation - *workstation
- *server - *server
- *miniserver - *vps
- path_regex: secrets/gallery.yaml$ - path_regex: secrets/gallery.yaml$
key_groups: key_groups:
- age: - age:
- *devkey - *devkey
- *workstation - *workstation
- *server - *server
- *miniserver - *vps
- path_regex: secrets/wireguard.yaml$ - path_regex: secrets/wireguard.yaml$
key_groups: key_groups:
- age: - age:
- *devkey - *devkey
- *workstation - *workstation
- *server - *server
- *miniserver - *vps
- path_regex: secrets/homepage.yaml$ - path_regex: secrets/homepage.yaml$
key_groups: key_groups:
- age: - age:
- *devkey - *devkey
- *workstation - *workstation
- *server - *server
- *miniserver - *vps
- path_regex: secrets/certs.yaml$ - path_regex: secrets/certs.yaml$
key_groups: key_groups:
- age: - age:
- *devkey - *devkey
- *workstation - *workstation
- *server - *server
- *miniserver - *vps

View File

@@ -3,8 +3,12 @@
Auto-generated from feature plans. Last updated: 2026-01-30 Auto-generated from feature plans. Last updated: 2026-01-30
## Active Technologies ## Active Technologies
- Python 3.12 + MCP server library (Python, JSON-RPC/stdin transport), click for CLI entrypoint, pytest + coverage for tests, ruff/black for lint/format (001-mcp-server) - Python 3.12 + MCP server library (Python, JSON-RPC/stdin transport), click for CLI entrypoint, pytest + coverage for tests, ruff/black for lint/format (002-mcp-server)
- None (in-memory tool definitions; filesystem access for repo interactions) (001-mcp-server) - 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) - Documentation set (AI-facing constitution and playbooks) in Markdown (001-ai-docs)
@@ -26,9 +30,10 @@ specs/001-ai-docs/ # Planning artifacts (plan, research, tasks, data model
- Keep language business-level and technology-agnostic in AI-facing docs. - Keep language business-level and technology-agnostic in AI-facing docs.
## Recent Changes ## Recent Changes
- 001-mcp-server: Added Python 3.12 + MCP server library (Python, JSON-RPC/stdin transport), click for CLI entrypoint, pytest + coverage for tests, ruff/black for lint/format - 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]
- 001-ai-docs: Documentation-focused stack; added docs/ for constitution/playbooks and specs/001-ai-docs/ for planning outputs.
<!-- MANUAL ADDITIONS START --> <!-- MANUAL ADDITIONS START -->
<!-- MANUAL ADDITIONS END --> <!-- MANUAL ADDITIONS END -->

View File

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

View File

@@ -7,10 +7,10 @@
## Repository Overview ## 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`. - 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. - 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. - 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`. - 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 ## Coding Conventions
- No blank lines between code blocks; keep markdown examples tight. - 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). - 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>`). - 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`). - 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. - Scripts: Units defined via `mkscript` with `enable`, `install`, `service`, `users`, `timer`, and `package` fields.
- Playbooks: Workflow guides under `docs/playbooks/` for repeatable tasks. - Playbooks: Workflow guides under `docs/playbooks/` for repeatable tasks.
- Reference map: Navigation index under `docs/reference/index.md` for paths and responsibilities. - Reference map: Navigation index under `docs/reference/index.md` for paths and responsibilities.
@@ -42,9 +43,10 @@ config.services = {
- Secrets files: `secrets/certs.yaml`, `secrets/env.yaml`, `secrets/gallery.yaml`, `secrets/homepage.yaml`, `secrets/keys.yaml`, `secrets/wireguard.yaml`, `secrets/secrets.yaml`, plus `secrets/ssh/` for host keys. - Secrets files: `secrets/certs.yaml`, `secrets/env.yaml`, `secrets/gallery.yaml`, `secrets/homepage.yaml`, `secrets/keys.yaml`, `secrets/wireguard.yaml`, `secrets/secrets.yaml`, plus `secrets/ssh/` for host keys.
- Placement rules: Keep secrets aligned to their file purpose (certificates → `certs.yaml`; environment/service env vars → `env.yaml`; media/gallery creds → `gallery.yaml`; homepage widgets → `homepage.yaml`; SSH/private keys → `keys.yaml`; WireGuard peers → `wireguard.yaml`; misc defaults → `secrets.yaml`). - Placement rules: Keep secrets aligned to their file purpose (certificates → `certs.yaml`; environment/service env vars → `env.yaml`; media/gallery creds → `gallery.yaml`; homepage widgets → `homepage.yaml`; SSH/private keys → `keys.yaml`; WireGuard peers → `wireguard.yaml`; misc defaults → `secrets.yaml`).
- secureHost gating: Only hosts with `my.secureHost = true` load SOPS secrets and WireGuard interfaces. Hosts with `secureHost = false` must avoid secret-dependent services and skip SOPS entries. - secureHost gating: Only hosts with `my.secureHost = true` load SOPS secrets and WireGuard interfaces. Hosts with `secureHost = false` must avoid secret-dependent services and skip SOPS entries.
- 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 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`. - 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 ## Precedence and Conflict Resolution

View File

@@ -0,0 +1,16 @@
# Playbook: Enroll VPS Secrets
- Name: Enroll VPS secrets after first boot
- Purpose: Enroll the vps host key and re-encrypt secrets so services can start.
- Prerequisites: vps host booted and reachable; secure host; SOPS access on operator machine.
- Inputs: vps host public key; secrets files under `secrets/`; repo checkout.
- Steps:
1. Retrieve the vps host public key from the running instance.
2. Add the vps public key to SOPS recipients for the relevant secrets files.
3. Re-encrypt secrets and commit updates as needed.
4. Rebuild the vps host from an explicitly authorized operator machine.
- Validation:
- Services that require secrets start successfully after the rebuild.
- SOPS decrypt succeeds on the vps host without manual intervention.
- Outputs: Updated secrets files with the vps recipient; vps host with secrets available.
- References: `docs/constitution.md` (Secrets Map and secureHost), `docs/reference/index.md` (Hosts and Roles)

View File

@@ -0,0 +1,15 @@
# Playbook: Rebuild VPS
- Name: Remote rebuild of vps
- Purpose: Apply configuration changes to the vps host from an explicitly authorized operator machine.
- Prerequisites: Operator machine authorized; vps reachable via SSH; repo checkout.
- Inputs: vps hostname or IP; flake path; target profile `vps`.
- Steps:
1. Ensure the operator machine is in the authorized key list for `nixremote`.
2. Run the rebuild helper script with the target host details.
3. Monitor the rebuild for completion and errors.
- Validation:
- vps reports the new configuration after rebuild.
- Remote access remains available after the update.
- Outputs: Updated vps host configuration.
- References: `docs/constitution.md` (Hosts and Roles, secureHost), `docs/reference/index.md` (Hosts and Roles)

View File

@@ -7,6 +7,7 @@
- servers → `modules/servers/` (reverse-proxied services built via `mkserver`) - servers → `modules/servers/` (reverse-proxied services built via `mkserver`)
- services → `modules/services/` (supporting services like syncthing, wireguard) - services → `modules/services/` (supporting services like syncthing, wireguard)
- shell → `modules/shell/` (shell customizations and CLI tooling) - 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) - network → `modules/network/` (networking rules, firewall helpers)
- users → `modules/users/` (user-related options) - users → `modules/users/` (user-related options)
- nix → `modules/nix/` (Nix configuration and helpers) - nix → `modules/nix/` (Nix configuration and helpers)
@@ -20,19 +21,21 @@
## Hosts and Roles ## Hosts and Roles
- Configs: `hosts/<name>/configuration.nix` with toggles in `hosts/<name>/toggles.nix`. - Configs: `hosts/<name>/configuration.nix` with toggles in `hosts/<name>/toggles.nix`.
- Active hosts: `workstation`, `server`, `miniserver`, `galaxy`, `emacs`. - Active hosts: `workstation`, `server`, `miniserver`, `galaxy`, `emacs`, `vps`.
- Roles: - Roles:
- workstation: developer desktop; provides build power for distributed builds. - workstation: developer desktop; provides build power for distributed builds.
- server: primary services host (overrides `my.mainServer = "server"` and enables proxies/containers). - server: primary services host; runs most services and WireGuard targets.
- miniserver: small-footprint server; default `mainServer` in shared options. - miniserver: small-footprint server.
- galaxy: small server variant using nixpkgs-small. - galaxy: small server variant using nixpkgs-small.
- emacs: VM profile, `my.secureHost = false` for secret-free usage. - emacs: VM profile, `my.secureHost = false` for secret-free usage.
- vps: Linode VPS image target, secure host with enrollment-based secrets.
- Network maps: `my.ips` and `my.interfaces` declared in `modules/modules.nix`; host toggles may override. - Network maps: `my.ips` and `my.interfaces` declared in `modules/modules.nix`; host toggles may override.
## Proxy, Firewall, and Networking ## Proxy, Firewall, and Networking
- Proxy enablement: `my.enableProxy` toggles Nginx reverse proxy; assertions require at least one `my.servers.*.enableProxy` when enabled. - 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. - 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`.
- Main server selection: `my.mainServer` chooses where services live by default; `mkserver` sets `isLocal` based on this and picks IPs from `my.ips`. - 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. - 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 ## Secrets Map
@@ -45,7 +48,7 @@
- `secrets/wireguard.yaml` → WireGuard peers and private keys. - `secrets/wireguard.yaml` → WireGuard peers and private keys.
- `secrets/secrets.yaml` → default SOPS file (general secrets, fallback when unspecified). - `secrets/secrets.yaml` → default SOPS file (general secrets, fallback when unspecified).
- `secrets/ssh/` → host SSH keys and related artifacts. - `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 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). - Stylix module: `config/stylix.nix` and stylix inputs in `flake.nix` apply theming. Host toggle `my.stylix.enable` controls activation (see host toggles).
@@ -58,7 +61,7 @@
- MCP server reference: `docs/reference/mcp-server.md` (tool catalog, `nixos-mcp` wrapper, invocation, sync-docs) - MCP server reference: `docs/reference/mcp-server.md` (tool catalog, `nixos-mcp` wrapper, invocation, sync-docs)
## Quick Audit Checklist ## 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. - Host coverage: Active hosts listed with roles and secureHost status; `mainServer` noted.
- Proxy rules: `enableProxy` usage, proxy helper selection, and `my.ips` mappings documented. - 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. - Secrets map: Every secrets file and secureHost gating captured; new secret types aligned to file purposes.

View File

@@ -27,8 +27,8 @@
- Inputs: `query` (string). - Inputs: `query` (string).
- Docs anchor: `docs/reference/mcp-server.md``#search-docs`. - Docs anchor: `docs/reference/mcp-server.md``#search-docs`.
### list-mcp-tasks ### list-mcp-tasks
- Purpose: Show MCP feature task list from `specs/001-mcp-server/tasks.md`. - Purpose: Show MCP feature task list from `specs/002-mcp-server/tasks.md`.
- Docs anchor: `specs/001-mcp-server/tasks.md``#tasks-mcp-server-for-repo-maintenance`. - Docs anchor: `specs/002-mcp-server/tasks.md``#tasks-mcp-server-for-repo-maintenance`.
### sync-docs ### sync-docs
- Purpose: Compare tool catalog against documented anchors for drift reporting. - Purpose: Compare tool catalog against documented anchors for drift reporting.
- Docs anchor: `docs/reference/mcp-server.md``#sync-docs`. - Docs anchor: `docs/reference/mcp-server.md``#sync-docs`.

91
flake.lock generated
View File

@@ -422,11 +422,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1769580047, "lastModified": 1770260404,
"narHash": "sha256-tNqCP/+2+peAXXQ2V8RwsBkenlfWMERb+Uy6xmevyhM=", "narHash": "sha256-3iVX1+7YUIt23hBx1WZsUllhbmP2EnXrV8tCRbLxHc8=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "366d78c2856de6ab3411c15c1cb4fb4c2bf5c826", "rev": "0d782ee42c86b196acff08acfbf41bb7d13eed5b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -513,11 +513,11 @@
"xdph": "xdph" "xdph": "xdph"
}, },
"locked": { "locked": {
"lastModified": 1769965155, "lastModified": 1770330959,
"narHash": "sha256-KAgGtueNnwLtekIh9tnmLAUDFbRxSff0OZwQwSi05Nk=", "narHash": "sha256-OPmJ6dBL615GGX7ENJXtJm4zeMv5uXDjmO8WB1MI5wM=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "Hyprland", "repo": "Hyprland",
"rev": "a0ec2e4daf8e508761f6bc53fc163fbb92ac7aa1", "rev": "562171ab668e7ee98a9d2bbb62a9477ad2b1e24e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -806,11 +806,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1769914377, "lastModified": 1770345361,
"narHash": "sha256-8wH3ZYNs36V0A3f/ikraqdoVE++BfnXg9Ql8nAuUkHw=", "narHash": "sha256-/kldWxogKCw1ykliO6lLwLE4aqUQ+yZOS9fHiFdI7u0=",
"owner": "fufexan", "owner": "fufexan",
"repo": "nix-gaming", "repo": "nix-gaming",
"rev": "f7d17740ed90663b11ae907d33b3fed9fc9e15a9", "rev": "2b6e7914c9ba6ad7effd53fab40d7acf34873069",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -819,42 +819,6 @@
"type": "github" "type": "github"
} }
}, },
"nixlib": {
"locked": {
"lastModified": 1736643958,
"narHash": "sha256-tmpqTSWVRJVhpvfSN9KXBvKEXplrwKnSZNAoNPf/S/s=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "1418bc28a52126761c02dd3d89b2d8ca0f521181",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixos-generators": {
"inputs": {
"nixlib": "nixlib",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1769813415,
"narHash": "sha256-nnVmNNKBi1YiBNPhKclNYDORoHkuKipoz7EtVnXO50A=",
"owner": "nix-community",
"repo": "nixos-generators",
"rev": "8946737ff703382fda7623b9fab071d037e897d5",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixos-generators",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1743576891, "lastModified": 1743576891,
@@ -921,11 +885,11 @@
}, },
"nixpkgs-small": { "nixpkgs-small": {
"locked": { "locked": {
"lastModified": 1769983561, "lastModified": 1770235692,
"narHash": "sha256-Hv7yml1x0gU14H1zxGATCXb+ueeBH7DFBUTIMkLWD4A=", "narHash": "sha256-VvqTQ2RYZE4PUWiefJKAPKW3H4fahCbp3L8yv4c7B8s=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "8dcf33716a525a7a0b6815cd8d0f4fa9b13abb1b", "rev": "39dfe6111bc5b19a710bcf409a9b179688f381eb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -937,11 +901,11 @@
}, },
"nixpkgs-unstable": { "nixpkgs-unstable": {
"locked": { "locked": {
"lastModified": 1769789167, "lastModified": 1770197578,
"narHash": "sha256-kKB3bqYJU5nzYeIROI82Ef9VtTbu4uA3YydSk/Bioa8=", "narHash": "sha256-AYqlWrX09+HvGs8zM6ebZ1pwUqjkfpnv8mewYwAo+iM=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "62c8382960464ceb98ea593cb8321a2cf8f9e3e5", "rev": "00c21e4c93d963c50d4c0c89bfa84ed6e0694df2",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -953,11 +917,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1769900590, "lastModified": 1770136044,
"narHash": "sha256-I7Lmgj3owOTBGuauy9FL6qdpeK2umDoe07lM4V+PnyA=", "narHash": "sha256-tlFqNG/uzz2++aAmn4v8J0vAkV3z7XngeIIB3rM3650=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "41e216c0ca66c83b12ab7a98cc326b5db01db646", "rev": "e576e3c9cf9bad747afcddd9e34f51d18c855b4e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -996,11 +960,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1770024533, "lastModified": 1770368476,
"narHash": "sha256-EXQWqlbhkkune23d6xq/0bz3iyJzkVklvDsSlYE3n3Y=", "narHash": "sha256-myWSpD+v5TmQ47grn+gbLou1dNy5hCXvfvc6VGF72kk=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nur", "repo": "nur",
"rev": "cdb7c463555d7c6de5ba7251f9b4940249b651e8", "rev": "2959831b0338e196f2d864fb5e6bb309fa1c99c1",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -1104,7 +1068,6 @@
"jawz-scripts": "jawz-scripts", "jawz-scripts": "jawz-scripts",
"lidarr-mb-gap": "lidarr-mb-gap", "lidarr-mb-gap": "lidarr-mb-gap",
"nix-gaming": "nix-gaming", "nix-gaming": "nix-gaming",
"nixos-generators": "nixos-generators",
"nixpkgs": "nixpkgs_2", "nixpkgs": "nixpkgs_2",
"nixpkgs-small": "nixpkgs-small", "nixpkgs-small": "nixpkgs-small",
"nixpkgs-unstable": "nixpkgs-unstable", "nixpkgs-unstable": "nixpkgs-unstable",
@@ -1125,11 +1088,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1769921679, "lastModified": 1770145881,
"narHash": "sha256-twBMKGQvaztZQxFxbZnkg7y/50BW9yjtCBWwdjtOZew=", "narHash": "sha256-ktjWTq+D5MTXQcL9N6cDZXUf9kX8JBLLBLT0ZyOTSYY=",
"owner": "Mic92", "owner": "Mic92",
"repo": "sops-nix", "repo": "sops-nix",
"rev": "1e89149dcfc229e7e2ae24a8030f124a31e4f24f", "rev": "17eea6f3816ba6568b8c81db8a4e6ca438b30b7c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -1159,11 +1122,11 @@
"tinted-zed": "tinted-zed" "tinted-zed": "tinted-zed"
}, },
"locked": { "locked": {
"lastModified": 1769885983, "lastModified": 1770308890,
"narHash": "sha256-jLS7410B58f+3WfZ4PQ28aaaTONnmxlfAbDPdNuciLc=", "narHash": "sha256-7bx8Bn9B2g/loBaz+uLwdKI2rUW+RhDPyP/MqAgvrxU=",
"owner": "danth", "owner": "danth",
"repo": "stylix", "repo": "stylix",
"rev": "fe06391a1e1905fc7e6c13443ea439a89695ca69", "rev": "7e7fa955abac04a8e118b1cedf930a8fd41c34a6",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -50,10 +50,6 @@
url = "github:nyawox/nixtendo-switch"; url = "github:nyawox/nixtendo-switch";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
nixos-generators = {
url = "github:nix-community/nixos-generators";
inputs.nixpkgs.follows = "nixpkgs";
};
wallpapers = { wallpapers = {
url = "git+https://git.lebubu.org/jawz/wallpapers.git"; url = "git+https://git.lebubu.org/jawz/wallpapers.git";
flake = false; flake = false;

View File

@@ -67,7 +67,7 @@ in
hostName = "server"; hostName = "server";
firewall = { firewall = {
allowedUDPPorts = config.networking.firewall.allowedTCPPorts; allowedUDPPorts = config.networking.firewall.allowedTCPPorts;
interfaces.wg0.allowedTCPPorts = [ 8081 ]; interfaces.wg0.allowedTCPPorts = [ config.my.servers.nextcloud.port ];
}; };
wireguard.interfaces.wg0 = lib.mkIf config.my.secureHost { wireguard.interfaces.wg0 = lib.mkIf config.my.secureHost {
ips = [ "${config.my.ips.wg-server}/32" ]; ips = [ "${config.my.ips.wg-server}/32" ];
@@ -78,7 +78,8 @@ in
endpoint = "${config.my.ips.vps}:51820"; endpoint = "${config.my.ips.vps}:51820";
allowedIPs = [ allowedIPs = [
"${config.my.ips.wg-vps}/32" "${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; persistentKeepalive = 25;
} }
@@ -116,7 +117,10 @@ in
sshKeyFile = config.sops.secrets."private_keys/lidarr-mb-gap".path; sshKeyFile = config.sops.secrets."private_keys/lidarr-mb-gap".path;
sshKnownHosts = { sshKnownHosts = {
vps = { vps = {
hostNames = [ config.my.ips.vps ]; hostNames = [
config.my.ips.vps
"[${config.my.ips.vps}]:3456"
];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMvtTURGBtAFXxxfzMJVoNJrtWLykOloJ5XYjxGh1OUx"; publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMvtTURGBtAFXxxfzMJVoNJrtWLykOloJ5XYjxGh1OUx";
}; };
}; };

View File

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

167
hosts/vps/configuration.nix Normal file
View File

@@ -0,0 +1,167 @@
{
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;
};
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 = import ./toggles.nix { inherit config inputs; } // {
secureHost = true;
users.nixremote = {
enable = true;
authorizedKeys = inputs.self.lib.getSshKeys [
"nixworkstation"
"nixserver"
"nixminiserver"
];
};
};
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 ${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 deploy www-data -"
];
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

@@ -0,0 +1,45 @@
{
lib,
modulesPath,
...
}:
{
imports = [ (modulesPath + "/profiles/qemu-guest.nix") ];
boot = {
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"
"ahci"
"sd_mod"
];
loader = {
timeout = 10;
grub = {
device = "nodev";
forceInstall = true;
extraConfig = ''
serial --speed=19200 --unit=0 --word=8 --parity=no --stop=1;
terminal_input serial;
terminal_output serial
'';
};
};
};
fileSystems."/" = {
device = "/dev/disk/by-uuid/f222513b-ded1-49fa-b591-20ce86a2fe7f";
fsType = "ext4";
};
swapDevices = [
{
device = "/dev/disk/by-uuid/f1408ea6-59a0-11ed-bc9d-525400000001";
}
];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

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

@@ -0,0 +1,65 @@
{ 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;
port = 8081;
};
}
// 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; type = lib.types.bool;
default = false; default = false;
}; };
useDefaultProxy = lib.mkOption {
type = lib.types.bool;
default = true;
};
certPath = lib.mkOption { certPath = lib.mkOption {
type = lib.types.nullOr lib.types.path; type = lib.types.nullOr lib.types.path;
default = null; default = null;

View File

@@ -15,6 +15,7 @@ in
++ inputs.self.lib.autoImport ./servers filterNames ++ inputs.self.lib.autoImport ./servers filterNames
++ inputs.self.lib.autoImport ./services filterNames ++ inputs.self.lib.autoImport ./services filterNames
++ inputs.self.lib.autoImport ./shell filterNames ++ inputs.self.lib.autoImport ./shell filterNames
++ inputs.self.lib.autoImport ./websites filterNames
++ inputs.self.lib.autoImport ./network filterNames ++ inputs.self.lib.autoImport ./network filterNames
++ [ ++ [
./factories/mkscript.nix ./factories/mkscript.nix
@@ -49,31 +50,48 @@ in
server = "192.168.100.15"; server = "192.168.100.15";
miniserver = "192.168.1.100"; miniserver = "192.168.1.100";
workstation = "192.168.100.18"; workstation = "192.168.100.18";
vps = "45.79.25.87"; vps = "45.33.0.228";
wg-vps = "10.77.0.1"; wg-vps = "10.77.0.1";
wg-server = "10.77.0.2"; wg-server = "10.77.0.2";
wg-g1 = "10.9.0.2"; wg-guest1 = "10.9.0.2";
wg-gs = "10.9.0.0";
wg-friend1 = "10.8.0.2"; wg-friend1 = "10.8.0.2";
wg-friend2 = "10.8.0.3"; wg-friend2 = "10.8.0.3";
wg-friend3 = "10.8.0.4"; wg-friend3 = "10.8.0.4";
wg-friend4 = "10.8.0.5"; wg-friend4 = "10.8.0.5";
wg-friends = "10.8.0.0";
}; };
description = "Set of IP's for all my computers."; 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 { interfaces = lib.mkOption {
type = lib.types.attrsOf lib.types.str; type = lib.types.attrsOf lib.types.str;
default = { default = {
server = "enp0s31f6"; server = "enp0s31f6";
miniserver = "enp2s0"; miniserver = "enp2s0";
workstation = "enp5s0"; workstation = "enp5s0";
vps = "eth0";
}; };
description = "Set of network interface names for all my computers."; description = "Set of network interface names for all my computers.";
}; };
mainServer = lib.mkOption { mainServer = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "miniserver"; default = "vps";
description = "The hostname of the main server."; description = "The hostname of the main server.";
}; };
postgresSocket = lib.mkOption { postgresSocket = lib.mkOption {

View File

@@ -5,44 +5,24 @@
... ...
}: }:
let let
proxyReverseServices = [
"bazarr"
"firefox-syncserver"
"flame"
"flameSecret"
"isso"
"kavita"
"linkwarden"
"maloja"
"mealie"
"metube"
"microbin"
"multi-scrobbler"
"nix-serve"
"plausible"
"shiori"
"vaultwarden"
"yamtrack"
];
proxyReverseFixServices = [ proxyReverseFixServices = [
"atticd" "atticd"
"audiobookshelf" "audiobookshelf"
"gitea" "gitea"
"lidarr" "lidarr"
"ombi" "ombi"
"prowlarr"
"radarr" "radarr"
"sonarr" "sonarr"
"stash"
]; ];
proxyReversePrivateServices = [ proxyReversePrivateServices = [
"homepage" "homepage"
"prowlarr"
"stash"
]; ];
mkServiceConfig = mkServiceConfig =
type: services: lib.listToAttrs (map (name: lib.nameValuePair name { inherit type; }) services); type: services: lib.listToAttrs (map (name: lib.nameValuePair name { inherit type; }) services);
standardProxyServices = standardProxyServices =
(mkServiceConfig "proxyReverse" proxyReverseServices) (mkServiceConfig "proxyReverseFix" proxyReverseFixServices)
// (mkServiceConfig "proxyReverseFix" proxyReverseFixServices)
// (mkServiceConfig "proxyReversePrivate" proxyReversePrivateServices); // (mkServiceConfig "proxyReversePrivate" proxyReversePrivateServices);
generateProxyConfig = generateProxyConfig =
serviceName: serviceConfig: serviceName: serviceConfig:
@@ -59,9 +39,21 @@ let
throw "Unknown proxy type: ${serviceConfig.type}"; throw "Unknown proxy type: ${serviceConfig.type}";
in in
lib.nameValuePair cfg.host (lib.mkIf cfg.enableProxy (proxyFunc cfg)); 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 in
{ {
config = lib.mkIf config.my.enableProxy { 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 in
{ {
options.my.servers.homepage = setup.mkOptions "homepage" "home" 8082; options.my.servers.homepage = setup.mkOptions "homepage" "home" 8082;
config = lib.mkIf config.my.secureHost { config = lib.mkMerge [
sops.secrets = lib.mkIf cfg.enable { (lib.mkIf (cfg.enable && config.my.secureHost) {
homepage.sopsFile = ../../secrets/homepage.yaml; sops.secrets.homepage.sopsFile = ../../secrets/homepage.yaml;
"private-ca/pem" = { 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; sopsFile = ../../secrets/certs.yaml;
owner = "nginx"; owner = "nginx";
group = "nginx"; group = "nginx";
}; };
}; my.servers.homepage.certPath = config.sops.secrets."private-ca/pem".path;
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});
};
};
} }

View File

@@ -209,7 +209,7 @@
icon = "${cfg.name}.png"; icon = "${cfg.name}.png";
href = cfg.url; href = cfg.url;
widget = { widget = {
url = "http://${config.my.ips.wg-server}:8081"; url = "http://${config.my.ips.wg-server}:${toString cfg.port}";
type = cfg.name; type = cfg.name;
username = "{{HOMEPAGE_VAR_NEXTCLOUD_USERNAME}}"; username = "{{HOMEPAGE_VAR_NEXTCLOUD_USERNAME}}";
password = "{{HOMEPAGE_VAR_NEXTCLOUD_PASSWORD}}"; password = "{{HOMEPAGE_VAR_NEXTCLOUD_PASSWORD}}";

View File

@@ -23,22 +23,48 @@ let
in in
{ {
options.my.servers.jellyfin = setup.mkOptions "jellyfin" "flix" 8096; options.my.servers.jellyfin = setup.mkOptions "jellyfin" "flix" 8096;
config = lib.mkIf (cfg.enable && config.my.secureHost) { config = lib.mkMerge [
environment.systemPackages = [ (lib.mkIf (cfg.enable && config.my.secureHost) {
pkgs.jellyfin-ffmpeg environment.systemPackages = [
] pkgs.jellyfin-ffmpeg
++ (lib.optional cfg.enableCron [ sub-sync-path ]); ]
users.users.jellyfin = { ++ (lib.optional cfg.enableCron [ sub-sync-path ]);
uid = 984; users.users.jellyfin = {
group = "piracy"; uid = 984;
isSystemUser = true; group = "piracy";
}; isSystemUser = true;
services = { };
jellyfin = { services.jellyfin = {
inherit (cfg) enable; inherit (cfg) enable;
group = "piracy"; 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 = '' appendHttpConfig = ''
# JELLYFIN # JELLYFIN
proxy_cache_path /var/cache/nginx/jellyfin levels=1:2 keys_zone=jellyfin:100m max_size=15g inactive=1d use_temp_path=off; 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 in
{ {
options.my.servers.keycloak = setup.mkOptions "keycloak" "auth" 8090; options.my.servers.keycloak = setup.mkOptions "keycloak" "auth" 8090;
config = lib.mkIf (cfg.enable && config.my.secureHost) { config = lib.mkMerge [
sops.secrets.postgres-password.sopsFile = ../../secrets/secrets.yaml; (lib.mkIf (cfg.enable && config.my.secureHost) {
sops.secrets.keycloak = { sops.secrets.postgres-password.sopsFile = ../../secrets/secrets.yaml;
sopsFile = ../../secrets/env.yaml; sops.secrets.keycloak = {
restartUnits = [ "keycloak.service" ]; 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;
}; };
settings = { services.keycloak = {
hostname = cfg.host; inherit (cfg) enable;
hostname-strict = true; database = {
hostname-strict-https = false; type = "postgresql";
http-enabled = true; host = "localhost";
http-port = cfg.port; createLocally = false;
http-host = cfg.ip; username = "keycloak";
proxy-headers = "xforwarded"; 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;
systemd.services.keycloak.serviceConfig.EnvironmentFile = config.sops.secrets.keycloak.path; })
services.nginx.virtualHosts.${cfg.host} = lib.mkIf (cfg.enableProxy && config.my.enableProxy) ( (lib.mkIf (cfg.enableProxy && config.my.enableProxy) {
inputs.self.lib.proxyReverseFix cfg my.servers.keycloak.useDefaultProxy = false;
); services.nginx.virtualHosts.${cfg.host} = inputs.self.lib.proxyReverseFix cfg;
}; })
];
} }

View File

@@ -38,148 +38,231 @@ let
in in
{ {
options.my.servers = { options.my.servers = {
nextcloud = setup.mkOptions "nextcloud" "cloud" 80; nextcloud = setup.mkOptions "nextcloud" "cloud" 8081;
collabora = setup.mkOptions "collabora" "collabora" 9980; collabora = setup.mkOptions "collabora" "collabora" 9980;
go-vod.enable = lib.mkEnableOption "Go-VOD video transcoding service"; go-vod.enable = lib.mkEnableOption "Go-VOD video transcoding service";
}; };
config = lib.mkIf (cfg.enable && config.my.servers.postgres.enable && config.my.secureHost) { config = lib.mkMerge [
sops.secrets.nextcloud-adminpass = { { my.servers.nextcloud.useDefaultProxy = false; }
owner = config.users.users.nextcloud.name; (lib.mkIf (cfg.enable && config.my.servers.postgres.enable && config.my.secureHost) {
inherit (config.users.users.nextcloud) group; sops.secrets.nextcloud-adminpass = {
}; owner = config.users.users.nextcloud.name;
nixpkgs.config.permittedInsecurePackages = [ inherit (config.users.users.nextcloud) group;
"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
;
}; };
}; nixpkgs.config.permittedInsecurePackages = [
services = { "nodejs-14.21.3"
nextcloud = { "openssl-1.1.1v"
enable = true; ];
https = false; # vps users = {
package = pkgs.nextcloud32; groups.nextcloud = { inherit gid; };
appstoreEnable = true; users.nextcloud = {
configureRedis = true; inherit uid;
extraAppsEnable = true; isSystemUser = true;
enableImagemagick = true; group = "nextcloud";
maxUploadSize = "4096M"; extraGroups = [ "render" ];
hostName = cfg.host; packages = builtins.attrValues {
caching = { inherit exiftool pytensorflow;
redis = true; inherit (pkgs)
memcached = true; ffmpeg
apcu = true; mediainfo
nodejs
perl
;
};
}; };
config = { };
adminpassFile = config.sops.secrets.nextcloud-adminpass.path; services = {
dbtype = "pgsql"; nextcloud = {
dbhost = config.my.postgresSocket; enable = true;
dbname = "nextcloud"; https = false; # vps
}; package = pkgs.nextcloud32;
phpOptions = { appstoreEnable = true;
catch_workers_output = "yes"; configureRedis = true;
display_errors = "stderr"; extraAppsEnable = true;
error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT"; enableImagemagick = true;
expose_php = "Off"; maxUploadSize = "4096M";
preview_max_x = 2048; hostName = cfg.host;
preview_max_y = 2048; caching = {
short_open_tag = "Off"; redis = true;
"opcache.enable_cli" = "1"; memcached = true;
"opcache.fast_shutdown" = "1"; apcu = true;
"opcache.interned_strings_buffer" = "16"; };
"opcache.jit" = "1255"; config = {
"opcache.jit_buffer_size" = "256M"; adminpassFile = config.sops.secrets.nextcloud-adminpass.path;
"opcache.max_accelerated_files" = "10000"; dbtype = "pgsql";
"opcache.huge_code_pages" = "1"; dbhost = config.my.postgresSocket;
"opcache.enable_file_override" = "1"; dbname = "nextcloud";
"opcache.memory_consumption" = "256"; };
"opcache.revalidate_freq" = "60"; phpOptions = {
"opcache.save_comments" = "1"; catch_workers_output = "yes";
"opcache.validate_timestamps" = "0"; display_errors = "stderr";
"openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt"; error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
}; expose_php = "Off";
settings = { preview_max_x = 2048;
log_type = "file"; preview_max_y = 2048;
loglevel = 1; short_open_tag = "Off";
trusted_proxies = [ "opcache.enable_cli" = "1";
config.my.localhost "opcache.fast_shutdown" = "1";
config.my.localhost6 "opcache.interned_strings_buffer" = "16";
config.my.ips.router "opcache.jit" = "1255";
config.my.ips.wg-vps "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 nginx.virtualHosts.${cfg.host} = {
config.my.ips.${config.networking.hostName} forceSSL = false;
"localhost" enableACME = false;
http2 = false;
serverAliases = [
"cloud.rotehaare.art" "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 = [ listen = [
{ {
addr = config.my.ips.wg-server; addr = config.my.ips.wg-server;
port = 8081; inherit (cfg) port;
} }
{ {
addr = config.my.localhost; addr = config.my.localhost;
port = 8081; 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" ]; serverAliases = [ "cloud.rotehaare.art" ];
extraConfig = '' extraConfig = ''
add_header X-XSS-Protection "1; mode=block" always; add_header X-XSS-Protection "1; mode=block" always;
@@ -188,11 +271,16 @@ in
add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header X-Permitted-Cross-Domain-Policies "none" 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 = { 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 { "${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; secure = true;
expire = "168h"; expire = "168h";
refresh = "1h"; refresh = "1h";
domain = ".lebubu.org"; domain = ".${config.my.domain}";
secret = config.sops.secrets.oauth2-proxy-cookie.path; secret = config.sops.secrets.oauth2-proxy-cookie.path;
}; };
extraConfig = { extraConfig = {
@@ -53,7 +53,7 @@ in
session-store-type = "cookie"; session-store-type = "cookie";
skip-provider-button = true; skip-provider-button = true;
code-challenge-method = "S256"; code-challenge-method = "S256";
whitelist-domain = [ ".lebubu.org" ]; whitelist-domain = [ ".${config.my.domain}" ];
}; };
}; };
}; };

View File

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

View File

@@ -9,19 +9,29 @@ let
in in
{ {
options.my.servers.prowlarr = setup.mkOptions "prowlarr" "indexer" 9696; options.my.servers.prowlarr = setup.mkOptions "prowlarr" "indexer" 9696;
config = lib.mkIf cfg.enable { config = lib.mkMerge [
users.users.prowlarr = { (lib.mkIf cfg.enable {
uid = 987; users.users.prowlarr = {
group = "piracy"; uid = 987;
isSystemUser = true; group = "piracy";
}; isSystemUser = true;
services = {
prowlarr = {
inherit (cfg) enable;
}; };
flaresolverr = { services = {
inherit (cfg) enable; 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 in
{ {
options.my.servers.stash = setup.mkOptions "stash" "xxx" 9999; options.my.servers.stash = setup.mkOptions "stash" "xxx" 9999;
config = lib.mkIf (cfg.enable && config.my.secureHost) { config = lib.mkMerge [
sops.secrets = { (lib.mkIf (cfg.enable && config.my.secureHost) {
"stash/password".sopsFile = ../../secrets/secrets.yaml; sops.secrets = {
"stash/jwt".sopsFile = ../../secrets/secrets.yaml; "stash/password".sopsFile = ../../secrets/secrets.yaml;
"stash/session".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/";
}
];
}; };
}; services.stash = {
systemd.services.stash = { inherit (cfg) enable;
environment = { group = "glue";
PYTHONPATH = "/var/lib/stash/venv/lib/python3.12/site-packages"; mutableSettings = true;
LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib:${pkgs.glibc}/lib:${pkgs.zlib}/lib:${pkgs.libffi}/lib:${pkgs.openssl}/lib"; 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 = { systemd.services.stash = {
PrivateUsers = lib.mkForce false; environment = {
BindReadOnlyPaths = lib.mkForce [ ]; PYTHONPATH = "/var/lib/stash/venv/lib/python3.12/site-packages";
BindPaths = lib.mkIf (cfgS.settings != { }) (map (stash: "${stash.path}") cfgS.settings.stash); 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 = {
users.users.stash = { uid = 974;
uid = 974; isSystemUser = true;
isSystemUser = true; group = "glue";
group = "glue"; packages = [ stashPythonFHS ];
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; synapse = setup.mkOptions "synapse" "pYLemuAfsrzNBaH77xSu" 8008;
element = setup.mkOptions "element" "55a608953f6d64c199" 5345; element = setup.mkOptions "element" "55a608953f6d64c199" 5345;
}; };
config = lib.mkIf (cfg.enable && config.my.secureHost) { config = lib.mkMerge [
my.servers = { (lib.mkIf (cfg.enable && config.my.secureHost) {
synapse = { inherit domain; }; my.servers = {
element = { inherit domain; }; synapse = { inherit domain; };
}; element = { inherit domain; };
users.groups.matrix-synapse = { inherit gid; }; };
users.users.matrix-synapse = { users.groups.matrix-synapse = { inherit gid; };
inherit uid; users.users.matrix-synapse = {
isSystemUser = true; inherit uid;
group = "matrix-synapse"; isSystemUser = true;
};
sops.secrets = {
synapse = {
sopsFile = ../../secrets/env.yaml;
owner = "matrix-synapse";
group = "matrix-synapse"; group = "matrix-synapse";
}; };
"iqQCY4iAWO-ca/pem" = { sops.secrets = {
sopsFile = ../../secrets/certs.yaml; synapse = {
owner = "nginx"; sopsFile = ../../secrets/env.yaml;
group = "nginx"; 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" = { networking.firewall.allowedTCPPorts = lib.mkIf (!cfg.isLocal) [ cfg.port ];
sopsFile = ../../secrets/certs.yaml; services.matrix-synapse = {
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 = {
inherit (cfg) enable; inherit (cfg) enable;
extraConfigFiles = [ extraConfigFiles = [
config.sops.secrets.synapse.path 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}" = { "${cfgE.host}" = {
enableACME = true; enableACME = true;
forceSSL = true; forceSSL = true;
@@ -125,13 +131,8 @@ in
"/_matrix".proxyPass = "http://[${config.my.localhost6}]:${toString cfg.port}"; "/_matrix".proxyPass = "http://[${config.my.localhost6}]:${toString cfg.port}";
"/_synapse/client".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

@@ -71,7 +71,7 @@ in
phone.id = "OSOX2VZ-AO2SA3C-BFB6NKF-K6CR6WX-64TDBKW-RRKEKJ4-FKZE5CV-J2RGJAJ"; phone.id = "OSOX2VZ-AO2SA3C-BFB6NKF-K6CR6WX-64TDBKW-RRKEKJ4-FKZE5CV-J2RGJAJ";
wg-friend1 = mkWgDevice "wg-friend1" "XBIYCD4-EFKS5SK-WFF73CU-P37GXVH-OMWEIA4-6KC5F3L-U5UQWSF-SYNNRQF"; wg-friend1 = mkWgDevice "wg-friend1" "XBIYCD4-EFKS5SK-WFF73CU-P37GXVH-OMWEIA4-6KC5F3L-U5UQWSF-SYNNRQF";
wg-friend2 = mkWgDevice "wg-friend2" "XBIYCD4-EFKS5SK-WFF73CU-P37GXVH-OMWEIA4-6KC5F3L-U5UQWSF-SYNNRQF"; wg-friend2 = mkWgDevice "wg-friend2" "XBIYCD4-EFKS5SK-WFF73CU-P37GXVH-OMWEIA4-6KC5F3L-U5UQWSF-SYNNRQF";
wg-friend3 = mkWgDevice "wg-friend3" "XBIYCD4-EFKS5SK-WFF73CU-P37GXVH-OMWEIA4-6KC5F3L-U5UQWSF-SYNNRQF"; wg-friend3 = mkWgDevice "wg-friend3" "3XE2ZG5-E5IKNI2-VJWSGDX-BW73BOZ-UFFI3GL-DYE6KOV-PTBWLQJ-YOBRFQ3";
wg-friend4 = mkWgDevice "wg-friend4" "7YPUQ4Y-2UVEAXI-KBQVU7R-B6R5O36-GDQPTOY-3R3OG7H-BVWVOTD-EX52VQM"; wg-friend4 = mkWgDevice "wg-friend4" "7YPUQ4Y-2UVEAXI-KBQVU7R-B6R5O36-GDQPTOY-3R3OG7H-BVWVOTD-EX52VQM";
}; };
folders = { folders = {

View File

@@ -1,51 +1,48 @@
{ {
config, config,
lib, lib,
pkgs,
... ...
}: }:
let let
port = 51820; port = 51820;
interface = config.my.interfaces.${config.networking.hostName};
in in
{ {
options.my.services.wireguard.enable = lib.mkEnableOption "WireGuard VPN configuration"; options.my.services.wireguard.enable = lib.mkEnableOption "WireGuard VPN configuration";
config = lib.mkIf (config.my.services.wireguard.enable && config.my.secureHost) { 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 = { networking = {
firewall.allowedUDPPorts = [ port ]; firewall.allowedUDPPorts = [ port ];
nat = {
enable = true;
externalInterface = interface;
internalInterfaces = [ "wg0" ];
};
wireguard.interfaces.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; listenPort = port;
postSetup = '' postSetup = "";
${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.100.0.0/24 -o ${interface} -j MASQUERADE postShutdown = "";
''; privateKeyFile = config.sops.secrets."vps/server/private".path;
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;
peers = [ peers = [
{ {
publicKey = "ciupBjCcIpd3K5vlzNMJC8iiyNqB9xXwkSC6UXPKP3g="; publicKey = "OUiqluRaS4hmGvLJ3csQrnIM3Zzet50gsqtTABaUkH4=";
allowedIPs = [ "10.100.0.2/32" ]; allowedIPs = [ "${config.my.ips.wg-server}/32" ];
} # phone }
{ {
publicKey = "JgeA1ElDwR7oLmyGn8RzvxiscMBhR8+L+mEjY1Cq7gk="; publicKey = "rFgT6TXzRazK6GMazMNGjtOvzAAPST0LvCfN7QXsLho=";
allowedIPs = [ "10.100.0.3/32" ]; allowedIPs = [ "${config.my.ips.wg-friend1}/32" ];
} # tablet }
{ {
publicKey = "giPVRUTLtqPGb57R4foGZMNS0tjIp2ry6lMKYtqHjn4="; publicKey = "R1CTx5+CXivMI6ZEmRYsyFUFILhe6Qnub0iEIRvvrEY=";
allowedIPs = [ "10.100.0.15/32" ]; allowedIPs = [ "${config.my.ips.wg-friend2}/32" ];
} # jeancarlos }
{ {
publicKey = "92JdW/NExg1tUE4cEyl6Yn+0Eex+iFVA37ahPRhRnRM="; publicKey = "ecPNSacD6yVwpnLBs171z0xkw9M1DXKh/Kn70cIBcwA=";
allowedIPs = [ "10.100.0.16/32" ]; allowedIPs = [ "${config.my.ips.wg-friend3}/32" ];
} # gorilia }
{
publicKey = "yg+2miZCrx89znFaUlU/le/7UIPgEAMY74fZfEwz8g4=";
allowedIPs = [ "${config.my.ips.wg-friend4}/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; inherit ip;
}; };
}; };
mkEnabledProxyIp = ip: name: {
inherit name;
value = {
enableProxy = true;
inherit ip;
};
};
enableList = func: list: list |> map func |> builtins.listToAttrs; enableList = func: list: list |> map func |> builtins.listToAttrs;
mkPostgresDependency = config: serviceName: displayName: { mkPostgresDependency = config: serviceName: displayName: {
assertion = config.my.servers.${serviceName}.enable -> config.my.servers.postgres.enable; assertion = config.my.servers.${serviceName}.enable -> config.my.servers.postgres.enable;
@@ -217,7 +224,6 @@ in
nixworkstation = ../secrets/ssh/ed25519_nixworkstation.pub; nixworkstation = ../secrets/ssh/ed25519_nixworkstation.pub;
nixserver = ../secrets/ssh/ed25519_nixserver.pub; nixserver = ../secrets/ssh/ed25519_nixserver.pub;
nixminiserver = ../secrets/ssh/ed25519_nixminiserver.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}); getSshKeys = keyNames: keyNames |> map (name: inputs.self.lib.sshKeys.${name});
# Helper functions for multi-user toggle support # Helper functions for multi-user toggle support

View File

@@ -6,5 +6,6 @@
server = inputs.self.lib.createConfig "server" inputs.nixpkgs-small; server = inputs.self.lib.createConfig "server" inputs.nixpkgs-small;
galaxy = inputs.self.lib.createConfig "galaxy" inputs.nixpkgs-small; galaxy = inputs.self.lib.createConfig "galaxy" inputs.nixpkgs-small;
emacs = inputs.self.lib.createConfig "emacs" inputs.nixpkgs; emacs = inputs.self.lib.createConfig "emacs" inputs.nixpkgs;
vps = inputs.self.lib.createConfig "vps" inputs.nixpkgs-small;
}; };
} }

View File

@@ -29,15 +29,8 @@
in in
{ {
packages = (inputs.jawz-scripts.packages.${system} or { }) // { packages = (inputs.jawz-scripts.packages.${system} or { }) // {
emacs-vm = inputs.nixos-generators.nixosGenerate { emacs-vm = inputs.self.nixosConfigurations.emacs.config.system.build.vm;
inherit system; vps-linode = inputs.self.nixosConfigurations.vps.config.system.build.images.linode;
modules = inputs.self.lib.commonModules "emacs";
format = "vm";
specialArgs = {
inherit inputs;
outputs = inputs.self;
};
};
nixos-mcp = nixosMcp; nixos-mcp = nixosMcp;
nixos-mcp-server = mcpServerPkg; nixos-mcp-server = mcpServerPkg;
}; };

View File

@@ -131,7 +131,7 @@ def search_docs(params: Mapping[str, str]) -> tuple[str, str, list[str]]:
def list_tasks(_: Mapping[str, str]) -> tuple[str, str, list[str]]: def list_tasks(_: Mapping[str, str]) -> tuple[str, str, list[str]]:
"""Return MCP task list contents.""" """Return MCP task list contents."""
tasks_file = RepoPath / "specs" / "001-mcp-server" / "tasks.md" tasks_file = RepoPath / "specs" / "002-mcp-server" / "tasks.md"
return ("ok", _read_text(tasks_file) or "Tasks not found.", []) return ("ok", _read_text(tasks_file) or "Tasks not found.", [])
@@ -177,7 +177,7 @@ def tool_catalog() -> tuple[Tool, ...]:
summary="Search across docs for maintenance topics", summary="Search across docs for maintenance topics",
) )
anchor_tasks = DocsAnchor( anchor_tasks = DocsAnchor(
path=RepoPath / "specs" / "001-mcp-server" / "tasks.md", path=RepoPath / "specs" / "002-mcp-server" / "tasks.md",
anchor="tasks-mcp-server-for-repo-maintenance", anchor="tasks-mcp-server-for-repo-maintenance",
summary="Implementation tasks for MCP feature", summary="Implementation tasks for MCP feature",
) )

15
scripts/rebuild-vps.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
if [ "${1:-}" = "" ] || [ "${2:-}" = "" ]; then
echo "Usage: scripts/rebuild-vps.sh <host> <flake-path>" >&2
exit 1
fi
host="$1"
flake_path="$2"
nixos-rebuild switch \
--flake "${flake_path}#vps" \
--target-host "${host}" \
--use-remote-sudo

View File

@@ -22,38 +22,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37 - recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsbWtvSXZ2MVdpdldmNUhx YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5VUMzYjZ5WlZtQ05LdnVt
OXlXSkxQUEdrY2wyMXZFdDNoR0VXU3hhODFzCldQOXFpamRsSmJrMXpDSU45aE55 b3V3RmFyM0VZWmh4dC9YZFpsZkRIdC9TRzFrCnBuYnhSaUgwb3JuSUNFSWlwSmVq
QzVESG9mdWN2Z2JvdEJzbElud2hWQTAKLS0tIHQvWkxRdXJlRGp0NGhoZWFaRHE5 bEoyQ09XSjNBMks3M2ZYdlh0eDFNYjAKLS0tIERpaGhISDFYd3RCYUV6Y0lmdGNQ
N1NHa25pT1FscmJ0WUowcXluaDg2WGMKigU7SPfaPWuW0gNF6yQIVWMDkddYWK+/ VTNibTBMN2RuN3doU3lYK1drNjVTVkkKMmRW0NtiYKBcUQ8kKjXcS6KjoPdVfN5d
BETBlD1+yyFk8pF4IfR9iU2JgWLSCzMK5JDZXjm095eoDS5xTQHj3g== 6vczsKTTbUwI0n6T5xrwRdbVIFsP4HisjceQWxJIVBthR0u9dLfXGw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAweGpWcDczZFFoTXNDc2xV YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvb1ZtMjV5TjlhMVRwdWNU
b01BYVJiYjlvQy80NlF6K0lRTWN1Y0pYcUZrCklsbzAyMFFqNXVRK0x4NU1zc2JL ME93Y0xhVGlxdGxmeWtXQ09EN3lORlJpV3k0CkJxdE14YXpwcytjbnZuMWpHVzZ3
WXA1OUhPQzZMNDhxMkU5K2pvc1lCOUEKLS0tIGo1aHA0b2lSdW9HM3ZPTU92Q3VU dVVBYVE0RW1naWVVQ0JRY0NoWG9LZTQKLS0tIG1udE1GbEtTQ2o3bGl0SW9NZmtF
dVgyamc5bzJ2T1M3TXh3dEg1d2xlbVEKvEWuB9hPQXkI8AQ5oKs0AU8v9bE4PpLu OFNqTncyaHFUSzBNRzZiSTVBdkhFWVkK2v81N8c8cU1Ig9fQZOn0fltqO+Ej8Wtk
x35YD4Wvfva9l21o1d1474bk9+nQnksj1ofgQKYilvKSetH11KkuQA== D0nMQv2fbWp6YlyE17VYPgmhdEY6+Zstve6PlBG86iQE3LTAfjG3Uw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmem1iZmRqSXhVcVA3djBo YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJSVdUWXRUa2tHVGczelhu
SFVQemRuS211Q3kvZEZzVkIxSmIrbGdtcDE0ClFwN0NydUNYU0Vpaml5bmhXSDJN UWk5RFl6azRJTkdxZGxvbWlnSDc3K3NlNlgwCjBRZEVta3RuNW1DZmo4RXJyTTNk
QlNMWExNRFNUMEYwa0QrbWUyUGFtNjQKLS0tIDcwYzVHYXBOejhHN0Z4Njk3OHNL cnpxTDRGL0kwQXJmc29LNE0wV01hUGsKLS0tIGgyTWZrOHVNTGExRWtYMzJ1aXhp
SzJoUVArZ2xkOGpYZG5pWEpGejVyUlEK5VRrn6jp40iXOdoDDLxk4DhcprKBZd8v cURNZXBtbnp2OUZDZDZKeEMrZlN0TEEKznlmLKFHYDm/hv3EPcHjT0A8r06GL7if
yHp6GBf7mFWxkvw77fl2/q7J6krlwix2sC5TLlk26zfgSaISz/mR1w== tbuJei8aWWg+uuvCBTZjHqmPUyNR1ixt84vxy1HlwXVu3dYHcG0Wug==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age13w4elx3x6afrte2d82lak59mwr2k25wfz3hx79tny6sfdk66lqjq989dzl - recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQOXRFVjBENnJqY21ZNkw1 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZYnlaTkd3LzFRbldWL3RZ
WXIvRURMSGRJUU9WMkRtOVAycHVGQkZnZkZRCnZlYUhYLzYwaEF1UTBCck9lV2c2 N2ZneVIzMnRpVVNHVWRMdXJLdjQwSWFKS2pFCmlQZUZMbG03VFVuUXcxZ3NRWjVH
Q2pmS1hVR2xkeitGSEpGNXptdDk2cEEKLS0tIDJURXNKUjV4S2VXbXdyNVRJWVhj SHVPYzk5NGpkeUVSU1BmQnNuaWZnZFUKLS0tIFdQZEU1YnhHZWRIajNYWTYxMEwr
Y2FnZXZYZzNrZkZubCtneGNHVlVKUHMKTasbVdxTpuK3UYmeAXWt4Gs+M9NnodWF UVBjaDFtSWs1b29DR0R2WS9pSGh3OEkKmG34ldBy4s9nj3ng/HQr+gN0LHJCOPJ8
fGuCUVkGNrXHiLBYUjomvmtYIul22xiGzes0xHzSBE9jiZuVnu4qlA== EWhh7cTLSF9AmZKP0sBsj7I4hHhZlOn85bvTM9RDiRVOSz8VrObXHA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-09-21T20:28:29Z" 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] mac: ENC[AES256_GCM,data:e267Kxv1Pyun/VOcLepBDBEKN6uSf8/iuY8KQ8u4xK58wsWkMdSDVcDKvO/iKF/Tj9hj+lZapkaKmp5SdeX+gjpyWiZi6QmUuKsCs0jlkV2NydLtZZt9vkmY/LCguIBRMmhDgidrNcfoghTxDDK5lng5H+2MBs0r2zLID65pHUQ=,iv:tr4YFdBltnsD4uTt+0NCam7r1QzhOmdoEbfz5/+JGPI=,tag:R2dDWTC1qrwPI9ghaf1FEw==,type:str]

View File

@@ -6,7 +6,7 @@ oauth2-proxy: ENC[AES256_GCM,data:MnAMX4adm8joZGaxZhgMDGf/15U2tk3dE/0dHFwETIi4Jd
cloudflare-api: ENC[AES256_GCM,data:iNUMlY8rz5yHVitpK4HGaFSK7j+c8Pm7rOQMOQGmSJ3a8ASyrtouPgLbcnoPY/jalsJYAj991dSiui+Vwqs=,iv:qWONG/KLd9/F4tqrWF5T25Zxst3bk+kOYaOFBFSBAAY=,tag:gRFxar8KS8gnX8oaCD156Q==,type:str] cloudflare-api: ENC[AES256_GCM,data:iNUMlY8rz5yHVitpK4HGaFSK7j+c8Pm7rOQMOQGmSJ3a8ASyrtouPgLbcnoPY/jalsJYAj991dSiui+Vwqs=,iv:qWONG/KLd9/F4tqrWF5T25Zxst3bk+kOYaOFBFSBAAY=,tag:gRFxar8KS8gnX8oaCD156Q==,type:str]
synapse: ENC[AES256_GCM,data:IR0pFwQBEM4O8mzzYXrPe2FjulSUGuitzLDLms2uovr6gEU82mCkRO/UCQOybNm03iOQeXX0Whz739kpYSGSInEyx69BNG/etH+bMu+GbYeMdrTEyXHSa7kcH4Ug,iv:Vn2ILYXnCj+Op/E2kWoxV+2ZtlxYJxO6XK3Ql41KW6w=,tag:9wogJFLlmfM5PRgPdwFlcw==,type:str] synapse: ENC[AES256_GCM,data:IR0pFwQBEM4O8mzzYXrPe2FjulSUGuitzLDLms2uovr6gEU82mCkRO/UCQOybNm03iOQeXX0Whz739kpYSGSInEyx69BNG/etH+bMu+GbYeMdrTEyXHSa7kcH4Ug,iv:Vn2ILYXnCj+Op/E2kWoxV+2ZtlxYJxO6XK3Ql41KW6w=,tag:9wogJFLlmfM5PRgPdwFlcw==,type:str]
readeck: ENC[AES256_GCM,data:TsIkHLji37dDHQRt78SquBhoSREHDgvgbc6+M1k2MLrgMGJ/Ejfy5AZXCIp/Qj5sXDzKP4j6Y6xFvGLswCqe02XjqGCpX13gZVCFPuKr8Nq051Xg,iv:Rc/pjYP+Vd/DvLCYsfJjDrnAlAiUlZOcNeeYzE6O3UY=,tag:OvR+CXMmrUFbsrHvduhnjA==,type:str] readeck: ENC[AES256_GCM,data:TsIkHLji37dDHQRt78SquBhoSREHDgvgbc6+M1k2MLrgMGJ/Ejfy5AZXCIp/Qj5sXDzKP4j6Y6xFvGLswCqe02XjqGCpX13gZVCFPuKr8Nq051Xg,iv:Rc/pjYP+Vd/DvLCYsfJjDrnAlAiUlZOcNeeYzE6O3UY=,tag:OvR+CXMmrUFbsrHvduhnjA==,type:str]
yamtrack: ENC[AES256_GCM,data:bmDBNenbzuYWxoXuRl3Mkd9zlgFy0ebprCYK6Xvwf4x/xoHVic1cG+IdKYCOx83aWQtHNq9SHw9vytN+XBzy9xJKS+r2j39ECvQ41cIbepEj785JNV1Esixash6h5QZhCLdPmOqalTBAPWu3pIK55Ka7MRXKQKophy/6lRkgFKZ0XVa/gPiDsP0uIOk9PEiL8F19hcORfMKVUtiata9eXIK9Gj12P7PE5eVWsWbRlI6Xux9H2H9dHnqhAueeriuJoA/y/3OpUpdC9u0yYKn0wJHDvD90lAz3GzcK3rHu+rviUJv/Mn4k+mmiqcfARMuDxlSBrmIyooHKn/WgOkoNyeFUYsB7xGn+pZGDO2luM58/pAat4H+nXtLYj5vLKF9Bhl8blgRK77XS1WtZc3w+9J6pxD9uHSk3YmBYK4F+7USwVOi0TmzRH9NDFUL9yKinjH3BfzXD/Hg+WwIUA6ajAhOvp13zdg7EhCBGab9qHilcdPE55jTPks74n7Qxw659Rl9aiw7i0TKlifu4PNXzOjm/OAYWhWChc/BgZ+2Akf2XyXh3UfE4W2bGySPpGrZC/S62d0xDSA/1c/EEXtai+GSLGL+mhPN0HRvf0m+md78cZnTaPLlVC0L5U5Czs0i0m1Kuhg1TCIG1mUxj6R/oTVm2ROYK2ewfzXVLA/soh8R3S4wGH7JJktinwzN/Ofc1OvJmuAIrTmujrV5rlJmanSNR4u311Kb6v7xjYczR7HC3lQnbTVh+OA2zUKaXJvmjXmhav84MYA6Syh+14HyphVulqPBe/FeaPe750daEV62CDjwSr6nHiBBzdTSyrJOg2vicsdb1yVVBCH6/g41jBtQ6CM0LfWMCQfczH7YSKgTXbSi7VWU8ZsyVZIFLUYOie/c/fP2ROwUcwfzET7q4x78VaWa4CLlPeDk+rlY8+ZkfLzzPgzrPl2WIxiKaH9QnqAnuRJ2QO8jNdxm65PNo/Zlz0B5A6q0bo/8nPGU4Z1exVzEy21HzduwXsd5ARkyxxfunHcA7pA==,iv:0RJcr1l6hyDhqakhFNSkYuZPsdhHef+O7ith4G1zx24=,tag:GqndS/sMbmQNXHuxiByDHA==,type:str] yamtrack: ENC[AES256_GCM,data:Rqisots808pJQIVSIkOjd0oHF48zDSd662SUmp5sQOo5z+EQjSQusb48vDUkBE6leT/S+vB/xluiv6sKLPT+TkVTx/cXVmaGY8ursXDKJc6QoE5nop0eFj1S0eSIeDYLn4tlN8y8uzPl2NjDk356j1THr6XwOqCMxYaKdhQZcF6tZIlbSNrQsnC/Jgzclloxu5KSSTj7d8QbSvXhE53c9YHereEXY7HMD+aVcbwAWF8onjMekSTnL6/VUua0urwUEa+BpdL/V3qBTzSJHHxlmusmo0xdUVDE9gw8htKv16ltVQ0CkKtAtFyeaGF+js3Zm1/IQxhMvDh0OZyooFpxk30VqGlcq8kO3ErixKkUwwmgypxNsGtHyrs2qBGjalwc2zqCY4MRCvjfYZ7LfhXReQtmWTuABJE0WL1spbSeWPKwQEGKS0/8sVyFPwuKY8C0CJO0W3AHq/+Q/cGgNHqckeivQ5pyQNBniGY5ukZtIgA7tBP4pbyEyMhohPKXEi3V6I9j50RKi6o8H6zTysQtZXUAFM+AcOUJBFYjLR20J9Dqsn6WFuTNZUc3RfIi4so3STWmAx47irC6Liq5hkPLgDZkLP7LCCV1SsWabLzKB9abP6Kct5jtJA8FltWbtAXImkyFt38IULywzoyjsUAfj3mENJLGLhi2jZEA1Js02u+TbZnw9B4D67IXtyTYu7kKt8U7//B4R31AjYLOkmE0AyLbPbhQt0cBJysawewOKMT+omMNv0BazfCrIwpjry04um8cg6LMrUD7uN7N7rqKfOaf67aToN0D3wIG8Zq4wRVZZOcYlTeORZie5tVEP3FpCXGtF+P3,iv:PMZCRPb+08lmaT9bJmaIQ5cTCtOy6kzdewxkX/3bNX4=,tag:Ut6aOzXKImLSVLbZx6ac0g==,type:str]
keycloak: ENC[AES256_GCM,data:BmwZxuJaOB8F7zmBNAf42lkw36s5TepimtdyT2xjdGVyuHgRHbTZqeVen7/0II39qrJjko4agZJgToIZ1uhaC/gpGSoHZlib3rJozPCqmBc42nO6SOtpIO8=,iv:kPModK85937/liNk6iLIRiQ/G5yB7S7h24ZzPb8A1zo=,tag:lWvDQAHVRiBz8XZUoADKvw==,type:str] keycloak: ENC[AES256_GCM,data:BmwZxuJaOB8F7zmBNAf42lkw36s5TepimtdyT2xjdGVyuHgRHbTZqeVen7/0II39qrJjko4agZJgToIZ1uhaC/gpGSoHZlib3rJozPCqmBc42nO6SOtpIO8=,iv:kPModK85937/liNk6iLIRiQ/G5yB7S7h24ZzPb8A1zo=,tag:lWvDQAHVRiBz8XZUoADKvw==,type:str]
linkwarden: ENC[AES256_GCM,data:G73i29pEyjmcHqo9NbHFUL6XMyLRzxln8WJyon+pk1uqb4I+eqYWlxk+uHNARPXOg0vXfDkDXDGPP7ogCa1En4yOZoY7ApuC0iTUOxicZY3/E8WQGXDEsvOlbr8yPiNLWQGj9aDtSMOOMv/NMv0GN2d7AfT5Kso9Rjrza4bUeq29DMttwa2Nfoum+zykGS3/zbsVH+aHYLJU3dCyY7RSdq84JfVBPaINVgBG+akeO7Uz3ArUOBn5sjmva9Ve5pbY6c5pBLnC//ypmGkqu4sb9Fy84XUw739Ay2kOZeB3oiZ59GwIdoUmx4JLVDaq2ykqJ09YFDf9OdKnMjYel8iHr7zq/+fgvfefiUz9riYuhQ9DLzQO+WQwQMYJL2SX8jeNiACNNTF0zoPitZqXKbZZLb542wUKN9ucbR/w,iv:Yc04FHnaZfbhOmDyaY3/hePmjgWvjWmtt+B8lB8e0xQ=,tag:1nwtm1bDbVRx8frgbLNh5Q==,type:str] linkwarden: ENC[AES256_GCM,data:G73i29pEyjmcHqo9NbHFUL6XMyLRzxln8WJyon+pk1uqb4I+eqYWlxk+uHNARPXOg0vXfDkDXDGPP7ogCa1En4yOZoY7ApuC0iTUOxicZY3/E8WQGXDEsvOlbr8yPiNLWQGj9aDtSMOOMv/NMv0GN2d7AfT5Kso9Rjrza4bUeq29DMttwa2Nfoum+zykGS3/zbsVH+aHYLJU3dCyY7RSdq84JfVBPaINVgBG+akeO7Uz3ArUOBn5sjmva9Ve5pbY6c5pBLnC//ypmGkqu4sb9Fy84XUw739Ay2kOZeB3oiZ59GwIdoUmx4JLVDaq2ykqJ09YFDf9OdKnMjYel8iHr7zq/+fgvfefiUz9riYuhQ9DLzQO+WQwQMYJL2SX8jeNiACNNTF0zoPitZqXKbZZLb542wUKN9ucbR/w,iv:Yc04FHnaZfbhOmDyaY3/hePmjgWvjWmtt+B8lB8e0xQ=,tag:1nwtm1bDbVRx8frgbLNh5Q==,type:str]
ryot: ENC[AES256_GCM,data:VMWf3VqcUdyJu2Ygd3XmoqGNWY/W/VJ4213ej0FrA95kAoX+S+j0+4a4B65NtW9UheDSxD1swTXebyenJCIN/tEZwH2wj9I12akNNvSDpt/LG3d1/BZ62cvLCb5n9vyE/vcXgJVfPUqmc67pYDWLpEV/vkKjpqwNH4Y8vnapVo1ytIgsjkTuBb7VFbnRPvYs6J1M0rnaTtkVhOBoRxv+Xg3pWYCgFEXdM/Pg/WKqdHpyh+tJqR74Z91Mwv6G56ZYEDQmAp+Cn+Kk2zZ+t44UAu1SQOgYXPLep+4/PgWw/vQMuyN7GNNP6TrsX3g+ONtJtkdmGu6ArcfbRAky4vM14DxlQP4xSjYSu+FDWGJL/J4TMw6IVDuw/TDVNpMrhBmZdPujYLUW1c6GCCEchBknNfw/Wt+NyTjOzCmZLVw760jY05Fa9kcW2kz+P0iAGTviY7yJZWDctP6PrVNtG1cXc4noJqV/uJ9sQmuGWCiTzaCIIZEhwRKnvjpvZNisKPhx4tctZMWm8l9gKO/TJC/SHMIhvEazmH4v0AzCiRUzdTfnWQZGTNenDrCUetztPh/UUJbLZjhFBH3QR26w/3I5oNpUzUDhfDhcEYtfWuB7ckbkXT8nyYMfe0OR16yJTfQCdnIPBhAUi1g1ZV3jFg+OhYWxk73lPiqC1ADRNh01L1k90PMMWtLXXm6aQ28cB+iQTvvgKbDrr76U8bXoZUyEl30waOQ2HT6nDG61OBUtQHTu6/cFhfhrnU6poAD/k+L7SyqcBoMYAZJN6Us1y3SKhV/3mXVKjRwSl5XZSW+ZpcRe/Cg4bonxFBYsZyY3VjK0LC4Cj8ijh4LpYWrGWtVmWOt/gg7UQPTd81A=,iv:Oa2pvfDpfPr3pqeAg2kYIzjf8KUK9ckMfbVymM78FyE=,tag:XyjYEvWo46BliYXdDH8QrQ==,type:str] ryot: ENC[AES256_GCM,data:VMWf3VqcUdyJu2Ygd3XmoqGNWY/W/VJ4213ej0FrA95kAoX+S+j0+4a4B65NtW9UheDSxD1swTXebyenJCIN/tEZwH2wj9I12akNNvSDpt/LG3d1/BZ62cvLCb5n9vyE/vcXgJVfPUqmc67pYDWLpEV/vkKjpqwNH4Y8vnapVo1ytIgsjkTuBb7VFbnRPvYs6J1M0rnaTtkVhOBoRxv+Xg3pWYCgFEXdM/Pg/WKqdHpyh+tJqR74Z91Mwv6G56ZYEDQmAp+Cn+Kk2zZ+t44UAu1SQOgYXPLep+4/PgWw/vQMuyN7GNNP6TrsX3g+ONtJtkdmGu6ArcfbRAky4vM14DxlQP4xSjYSu+FDWGJL/J4TMw6IVDuw/TDVNpMrhBmZdPujYLUW1c6GCCEchBknNfw/Wt+NyTjOzCmZLVw760jY05Fa9kcW2kz+P0iAGTviY7yJZWDctP6PrVNtG1cXc4noJqV/uJ9sQmuGWCiTzaCIIZEhwRKnvjpvZNisKPhx4tctZMWm8l9gKO/TJC/SHMIhvEazmH4v0AzCiRUzdTfnWQZGTNenDrCUetztPh/UUJbLZjhFBH3QR26w/3I5oNpUzUDhfDhcEYtfWuB7ckbkXT8nyYMfe0OR16yJTfQCdnIPBhAUi1g1ZV3jFg+OhYWxk73lPiqC1ADRNh01L1k90PMMWtLXXm6aQ28cB+iQTvvgKbDrr76U8bXoZUyEl30waOQ2HT6nDG61OBUtQHTu6/cFhfhrnU6poAD/k+L7SyqcBoMYAZJN6Us1y3SKhV/3mXVKjRwSl5XZSW+ZpcRe/Cg4bonxFBYsZyY3VjK0LC4Cj8ijh4LpYWrGWtVmWOt/gg7UQPTd81A=,iv:Oa2pvfDpfPr3pqeAg2kYIzjf8KUK9ckMfbVymM78FyE=,tag:XyjYEvWo46BliYXdDH8QrQ==,type:str]
@@ -21,40 +21,40 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37 - recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDclRxNVVzaC9lazNQSEdp YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjcWpKQ3RqSVdxcDllajc2
UzNBaTRnNzhzM0dLaVk1QlBaK2ZUelhoWmcwCjAzcnNsakxONSs2UThpNjhMMGpr UzVtdmxBWmJ2QkI3SGhYRlRadGJYaDU3UVN3CkpQYkxhVm5ZQ2djbldYL2VmQWsv
TGtnY21OTnd5NXdvdlpKamNCdXNjbzAKLS0tIFVxbGNLNWhudFRoRjBOblNrdW9k SEJmam0zMzlJSFpHS3JZWVorUmh5ZDgKLS0tIFdWdU44VlRDZllCYXRTQzNyajRy
VkhOV1BScVQ0RkF2bDBabUs1a2toMTQKDAeEu3+vuVKcpm27igmQuBvFfsMd7o9H cDJqNzA3ektRWll6SkFsVnFMd1FBUEEK0j9X4lYcFaj4MnVh4jnNwrTg2Sl5TTdZ
Wbinft1NiaQhc+7KtDEx51+tS+cgaGzObkWabyQutDqWEa/2PZLZLA== uFvTdE4ZNtZsh3nKmj+v2J3JM8dDUtw2NSooqpoqEvCYdDqwK1kDXQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4RC9Ea2VSZy95Q3JJWlhB YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGSXlITkpxcHZqR0kzMlFY
VFVBVGxnQit0WC9Vc29Ic0g1aDNBNWFySmxzCngyTDg3R292c3VNUkhvUWNXaThE TStOVitPSm0zTURZcE92NkM2ak8xcVF6OVZBCkRRbkpBNW9yek9rWFlOa1pLSk0r
NjVjTVlEZHhVODlFeklKNU9peWdad2MKLS0tIFhVTHZoeHV4eVVGOWNHeml0b2JE ViszS3pMNFhLQlcwdW83R1hhTUJLT0UKLS0tIG9NTm5tNzlidlJmejdoOUkvUE9X
ZVZiemVkYmZxMFVEQmVvVkZnaU81OUUKPHdwj8s0Ju2Y0Vh31jnR83nQ3jpqjkhr RzV2MUFEMnlHVmp3UmgvNmJKSDFrWHcKQ7y2W0PFLs/I6Tb0J/M91+toDP8XmgWh
4z5OxYJk2d0uO9f1jNaiIVLRxCdbj3h84f4fQqoQv5csrc5H9mg7Rg== LYuNc9lkjTs+ylIWuMTwtXdceI+kK8hJlELT47FyKl755DzuB1ufAg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlNkNLTzcxa3d0M0pJbXlp YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzYXRodjgzR1hoR2VDNHd6
b2V1alhBUFY1VVZIZUY3ZHYyVmFKQW5tbGdjCnJXSHpmeDdTWWtHTWt3TVlCR3BU NGJ3SXpqRmJKVlY5aTI0R0hBLzFGQ0VSYmpVCi9BakFwRGlXd1ZPbWpHY2h6RUo0
TXFXZDVabjF3d0JYUk5Mb1c1dkVjMTgKLS0tIDFFbHBCSXlPVlM5YUk4MUNiNWdx VGl0T0d1LzdaZGNOZ0pDekZxVVBWUlEKLS0tIEdtVDZlN2FrcFhEU2pTMUdiZ3NH
bjg3aWdMbkNDMVd1cTU3NGxPU3cwVjQK4zDOWDUHhK0JVjiYTMTSmGej7yXb5X6G d3ZSMGdkNzNaczBYOHFuZWJmcEM4MXMK6ayh37HUhOYPryv2Y2WlE1U0CX7qZF89
SLPWPbrB8WLGyK/gdxDrZAxucxe/n/O0CsR5DQubmetfUSowk9RIIw== PzvHQZYcbZ2gsRW2f1uU2VoJp/6XnSipD7fCjma3iNovoPlu2+A0yw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age13w4elx3x6afrte2d82lak59mwr2k25wfz3hx79tny6sfdk66lqjq989dzl - recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4WlF0WkxIRkpnR1RhcVJX YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4aUk0WVhTUTZXOVRqam9O
b05ZYzk3YU84TDI0cUpBdnRpNGxEQmFIMEVNCkxrTkdkUzBnUDdDQ1RqV3hnamYy WkVNd1FKdDA1SWpwWndHbmVBRlNaSWI4aEVnCnJTTDNYTkRtNkR5cUl0SURVQWxh
c0owbnVHbjFPY3JsOGIzN0xIZHp5dmsKLS0tIFJwZ1ZFbG5SSmNoMVFYYlNXNWx1 d0c5cEhJVTZ2YXdLdHFQRk9KN04vcW8KLS0tIGF3Rmp2Z0pwM0x1WnpKaVBiUE5x
QXRUYWtGcWZCVW11U3VYRktuUjlCbDgKsTK4WhUza/JuoDTU3uATa6fq/8eYzxtb MVBONDBmQjI2enNIVFFQT1hyYm45YXMK2NXWvm8G+Yrvw1NAC6AiDaxA9UftuqYe
9BUK1ddzx9Mghea9XBMS17YGtGmW800OsLBomb3SINnOFvejcnKf8Q== ZB7QpfkdCT3vS52lBgcEJrM1TbaVX2868trk5kB4gjqVMPVPYxcGHg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-02T01:45:35Z" lastmodified: "2026-02-02T03:55:24Z"
mac: ENC[AES256_GCM,data:B9zBOpztJu2HYLh8k1UeA4UWb2gppHErEATiOB+mYMhcw2JnvEVHqN3X5S2y40M/ZRdR2V7y6MFG1PBxNYlQJrcjrojTz7stshQd8vj8saDttUXVtB8CwTD9tey2HK/K4980dUBqpXtjSi68RyoDlJW9Zz56ud8bPGXCHJFQ0i8=,iv:t8y2ItY8rTW1sQscTqEDOY1w+7Fo5e+Pk8gd2ZH8qC8=,tag:WuLeQwqLE/wVAFR6XIFTOg==,type:str] mac: ENC[AES256_GCM,data:+NN+RgkHAIox1IgUuC2ACHneRBzgn5FzsujpbPtmw1IecxeKMMXM7Wa1ZziSkWJSjjDCcBoanox57e+BoNWN5WhWuMdCed04AKcknfKlHAtHrKhoLCsi1sZnsQX7xBmTsA5qHD8788EWfIgPk4gToXkq5KkEfvEWLvalClRK7tY=,iv:kGyw9hk6vp5iu0iMHaCLgVqdcv1gNUBqBhZbRSCa4Ks=,tag:FdKL/5ZraejphDIE2ig8GQ==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.11.0 version: 3.11.0

View File

@@ -5,38 +5,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37 - recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIY05VY1FPOU5FTFFnazlQ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4R05sUnl0UFd3T3pzRm1y
RStQVExNdWIySE5qSVMxMFd3NFM0L2VCRWxzClhleTEzNTVOaVl1cGovM1hmWEoy U3piNzlpTmpZeEhkeWJxRkMzRzBWRW9LQmtBCjRHTVg5ZlozUnpsVjhIK05xYjlz
eGNxZ2E4U1pRNlBaTDZ0ZW4wbVZjT0EKLS0tIEJ0ZXR5blBlckIxSVlmT0hxY1Bz c2dwbWVKWVNXWFhTWEtlUUFjVUw2RkkKLS0tIElaNXN2ZmROdHd4bWljM3FyMEh6
TGVGRFgzaHI5VW5GdjJvcmswUWFvaWMKQCK47p7OQUXq45aYo9BkkcGrzmPKCJOI Szg3WTdrVlFmSUJ1S05xNXY5RlM1V1UK7YETep9hn49UqRUjbRv6oGFUT/8lRgXx
OKu/+W4xYOnfIo03GGL6f4LrbCaKr1mdtsRnuHmaFXiXdaKbZFDEhw== 5O5eGB1X8kPCY8zXiGWSzfo6X8O5659vWIvqjoY8nZxekgvsISS/WA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJb0MzQVZvY0ZCNlAwT2Qw YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpT1oraHpJb0NUQndMZW9l
RnJOUXJISFg1Smt4VWdoYy9PT2hQNG1MNm5ZCmVhUFI5UGpQUkR4MTA4VktuVyt1 YWZrOVBqOG1KME5PYS9YVE4zd2VQb0hRN1dJCmVqSzhkbU5DVmc4MFVnSnVYTi9V
TXlVZ3haNjd4OHNYNE4rVzd2MkNGTkEKLS0tICtkZDRvODBZaGRCTmdlUkRESjMv RUR2UDNEK3JGOEFUWVoraGtqQVFFWkUKLS0tIDVRdU8rV3diVXNUQSsrKzlBdmFN
bElZc21OSXJsZnZaSHF5ZTBDSlNXaHcKixDNfM98AqYagtidcYE3lgkFM9XTIrVg Q0x5QXdaOXRMc211TUhqTndQOXR6ODAKtJYiAeVTYPOpS+GykBDOLx1g3VloFo2P
gbYoSOk5rL9Hi2rvP+BCEgsrRSuExGKVvdqODYltD+nNfTI1zcnTFg== fDIkOCrINnAU4y07KPhGBxCV3/2cvOPhIgsd02XqxfZPCEU/cYdCgQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKZncwdllnQjYyc284RXVm YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4dTIzeCttSGNVNmhPejdW
VVVJTHI1Z25FWXBhY3o1SmgyVW01alRlcVVVCklDNDYvMktDU1U4L0RTMVgvaU0v ZFkySng2T2ZYdkRrRGRXQVpER1NJMW5XN2xVCkc0VTdsbXdLUkg5d29zZ3VmY0hH
d0NlK3pqYzZ4NFRUd3V1WHZTTkVpK00KLS0tIHVQSmRDekcrK093QUJQVHNZcUg3 U1cybHNob3VkdzRWbGt1bFhNeW9XN0EKLS0tIDdoc2cyaEIybjBHOU5tdVRsTWFZ
WGVJQm5MdGhMbzd5RkNPU1VuNTZVeFkKQq/WyqLOOde86NNYnVq0Lw31YB2OcLY/ TmdZTGNDOFovMDVPakF0WTdHaUpHeFUKl0ub1OOylE2JGJNpeReebiOaVdxbd0wv
h/HtFN4GynmBOYcTuqIvBJ/TksXs30kWFKW2XSY0jP0JSY7Yo0BxhA== nvJD7tYYXI666Pi31OHttWhsHR+xkL8TU9Dd6uDs4QxIRQfwy/VxcA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age13w4elx3x6afrte2d82lak59mwr2k25wfz3hx79tny6sfdk66lqjq989dzl - recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGZU0zK3V6M2IyMkFOdm5U YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGUldWbldqTTEwMGF5RVFV
UG1oVi9IMzM0SllUQUMwMlh4NkF2V2pCcWtvCk1kR0QxVWRPM1pyWmdVOE1UdWxs ZkZ0Y24ycU1hVDlaTnpGQW1SeFlzaXc2a1FrCmtrUkxLcjNsVHNXemd1cWJJdXI5
NldjZXBOZU1uK1JELzF1blhTQy83Zm8KLS0tIFFVRjVScVVGa09sbEdBdjNXNTZR bDFxUThzSFptNWtXMlNqM09aeklUMTgKLS0tIHR5KzE3dStMTXlhUWhtUWUwSkY0
d0YvYk8vNitDbzNCQ1VqS20xUWx6ZDgK+kIRATTtC0Vd7/uPf8E4pIans79Ksh6J ZldyVmtRVGppQ0d0SnN5Tld4cEtmQ28K1Yij+7OxQUpEsPt/GTnP+dhEErBH1HuL
Y77+owFFw1AvQ3KvaI7QVfKW61MzxI+S1bWqI3ZNOJ19Qv4ZoVhnVg== pBFXqHLAwpqiEiiNhYnb0KVWeQnIqDo9WUnrbPavcWSrSkmCsszgxQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-10-10T05:09:54Z" lastmodified: "2025-10-10T05:09:54Z"
mac: ENC[AES256_GCM,data:N/BwfrwWcnot36Kn6RFZjjpUIluzq5Upy5iVVV4XSs+/0PYdlZGytjoAB+E3gXyPsLZ93UqI0A9/5KbfXBuR2oY2F7iKsu5puzgyYWa0Gl2z9YcPnyDnk1dj7Ne77xJlqR9YquGzFKF8QdqFXFA9cdE3b/1usTFhP26oxofMXs0=,iv:Iz/LzS8yeKQgDiGchYdKNymBeekhopJtBWaQGOwRZlE=,tag:hMRwxJlKR21W7otW01GmGw==,type:str] 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 - recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMdnhZNkx1S1IyWmwvdkVh YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDa3NpNG5tenhqWVQ5RFAv
VGxNM1lUczd4Z0JKaHVlM3N6RWRWS1dBN0IwCmcvcHJnQ2h4WVV0S01OelA3eldE bjhhRWJWK0NFQVk0cGVpcVJGS3BFeWlSQWc0Ci9IT05mQTVWbmk3SFFpWE9KUnh6
Q0lNR0w2Z0owWnNjR3hXWGF6UzhyOTgKLS0tIFMvbW1rd1A1VDRJWW9TemJzQUl5 bHhCSktlbzVUQm1lOHp3cVpiSHU3MDgKLS0tIGg1UU4vVVo0SXRwMjJsVUZEZkFC
d2hISHVLUnpBVlAydEd1eHo4WGxLSG8K4uAVlEvgrohFbpvLexcfom5HRXMwTYrv TC9Eb2JaVUFDSWRMYm5jR1BBa2lEamMK4V77WUVbMXcsw83FFdL2Rk30oR4cAkqQ
ftuFhDAyNHlTNABiPH/dmjy/A86Veb1LKXF0Y1r/RPWRHaxyw5f23g== kc8Z0+5kNJFUFilFb54dnWTOh27K7KZvU1qIdhG3X9fuMIHSuPnyTw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwQkFPN3owRjRuZjJ5M3JX YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDRVdYVTY3QzR2MFJPVW9j
U3FaMk5XL2xoUmo3eFFGRXc3Q0Nwd2gyV1FjCjFjL2pUaWJyVXZIUHI2OWhPYnlt SUtmTldMRCs0dTlJcGFoMDVnaWMyNlF4OHlnCjg0OFFrOERKRFZVMm9NREhBOGRs
ODdFVjVvMDhGSnRGejNTWFRUdXdleHMKLS0tIHpCZlc0TTVxYk1UUUk2NkVpcm1M dTEwc0NZUk9hOEtvNVJDRXl0TDhCaTQKLS0tIE5OWm5CNzc5SW9IdGFud1N6Vm1D
NjFnY2JqNkh0NFJkcU40NEFsNjRuTW8KMRIBZVBnxe+Drs5VqGzBLI6AsVJj2Vka djhzM29HK0FIdXIvaGIrRXlOMisxaTgKVCAiniAmfqJuwwiUpcGAvoyqnUEZ9gOS
bmPFMl5ZJ97HxpdqQ1xkUqjoebp9KT5osOSglSK3CTkMRTEtyWQ11A== SyhXMzv2cbomuOb0NiALRkd2up/uX0TVuz9wuBQvYYjJhqpFuSnbRg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKQkxwV2lQRzNPRUtvclVE YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1UlM2MC90SEM5elBtNmpm
Tkl5MVQyUUtxUVJpbnZmVTVNRThBd1JYZUVjCk9GSklFWUJBU2owVDVxTjdncEtI cnpTQVF0VEpLUDJPZkEyeGNuYnl0cEY4M0U0CjhOY25BcERjOThkbkVhNnJtaVpv
WXp3bkRtS2NEazd1KzZTZmlMZ3Q5U1kKLS0tIFhGby9NV0tidU9MdWRnY0JNNTZ2 N00zOUZPWnNYaEtYMzlXZk56dGVPeGsKLS0tIDVDcGY5cG1ETHk4eXRFN1hVOXhV
enphU0dnNE84Qkc2V2hxZWRqOUg5QmcKk3qdK28b9072s7bPj+TgqeYVS2lnR8uf Y2xncFJuNUs5ZkhLSjJyc2pzdDZxbEkKn/8BtUXPQ0OdR35ZwiHWFB0AqaDtAlG7
R9BUS6c72aJjxPm11JqNW8UPu0ODhZrVMyyv+p+KY1J2iaCNGNdvXw== N4Z7iztqiscuxn8G8VVVFdkQLBY3JcrXhxPYWK4xtJeEtpIMhegxeQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age13w4elx3x6afrte2d82lak59mwr2k25wfz3hx79tny6sfdk66lqjq989dzl - recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiTEZFalVYbUdNb2YvOW95 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzd1VYanVSZTd5elNlZ2NC
M1doTEp1ZHRjUFJSNm14V1VWNE5hTWRpemdZCk1GMTdrck04N2Zydm5aYmQvTzdH bnd0V0VZVmtrVDhBbW5KUHJDMUkrekVZeldRCjBNY3g1SkVKUzhRL2xsbjloUERi
TEhrK2dES1lWVGJaOU5CUUY3a0ZtSTAKLS0tIHJXdzRGY1laZnJ2em02ejB4RUpQ UXM4T1A0a1V2eEFlQWlTQ2tDdFdaZ1kKLS0tIFFtNDZzbzYyaE5UT3R4eDJzNnU1
N3BtMkE1Y3d6Tk50ald2clJ5VVZaVG8K6BDcM8UAtBf0eBYosTvrRmi0Fcw05q4a RG9UbWM4YTVHcFpKblQwemNScDVteVEKA6fibq6Ozwrz/tg9Hrx4bH9LCadmW5fR
FOltP/mH09OQBHYJ466s8eaPj0TwqMl3524Byr4vTPYTy0keRN9EWQ== IkFalgD7nqew8KwS0keyKFk93i2p6sTDZPy2/t+WryMXBIc/y0iQ5Q==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-01T22:31:04Z" lastmodified: "2026-02-01T22:31:04Z"
mac: ENC[AES256_GCM,data:gtTuLmgVd5t1Eic+ld6x3pmAlv2+SVf4OgUICu78DJ9L1YCtmJ+LsqIoHFueMdQAmubPA8c4xYsHWCDu2dbrUDUs/79BF2u4P9lbNkJx5cco8bnPdy2tmkhcLwb0HwRduVIbgcm0wzYKUMd76Y0ChxdCddkrkk+PjXkUE7OBNg8=,iv:Eqhoc6GjB1NOnIIeRIdVoQNQm51DguH3vEX4zRUgeBE=,tag:V25oIemZpdJDMRFcZkH4bA==,type:str] mac: ENC[AES256_GCM,data:gtTuLmgVd5t1Eic+ld6x3pmAlv2+SVf4OgUICu78DJ9L1YCtmJ+LsqIoHFueMdQAmubPA8c4xYsHWCDu2dbrUDUs/79BF2u4P9lbNkJx5cco8bnPdy2tmkhcLwb0HwRduVIbgcm0wzYKUMd76Y0ChxdCddkrkk+PjXkUE7OBNg8=,iv:Eqhoc6GjB1NOnIIeRIdVoQNQm51DguH3vEX4zRUgeBE=,tag:V25oIemZpdJDMRFcZkH4bA==,type:str]

View File

@@ -9,6 +9,7 @@ public_keys:
phone: ENC[AES256_GCM,data:PvSqRnz2qGQU5kdZZpeqb3Eg2psLYrMoV/168CKMWpc1h5TZi7TeWkCQa6ktPR556NT4Ny2m6rBzADtYZkjFIKtDLXdhTYCeL2eFWB3VbSGFHsHgvxXHbae+zg==,iv:XGO9d0QZXbP7vuNDY4/Z/YhRCPKwj3RoQBx5daQO/xI=,tag:zayb0RYQj6UOi6FKJbhhRg==,type:str] phone: ENC[AES256_GCM,data:PvSqRnz2qGQU5kdZZpeqb3Eg2psLYrMoV/168CKMWpc1h5TZi7TeWkCQa6ktPR556NT4Ny2m6rBzADtYZkjFIKtDLXdhTYCeL2eFWB3VbSGFHsHgvxXHbae+zg==,iv:XGO9d0QZXbP7vuNDY4/Z/YhRCPKwj3RoQBx5daQO/xI=,tag:zayb0RYQj6UOi6FKJbhhRg==,type:str]
emacs: ENC[AES256_GCM,data:JBdqrtYy/1oVzea3WfvAX077R/8KECe+nziqHM7sZSMSq8nVxMeTIqXuowYsp15Dr9I1hezgedC+IfvkKyu9pCfS3Smzs91o+HEPB5T+nx5Kgn4pwNzw/4ahiA==,iv:OQfL/6UmhWcX2nbyWHZnN1+a5EP0AYAqTIdxn5KLvRE=,tag:JDL3IVYy2jAsDWOObTBFLw==,type:str] emacs: ENC[AES256_GCM,data:JBdqrtYy/1oVzea3WfvAX077R/8KECe+nziqHM7sZSMSq8nVxMeTIqXuowYsp15Dr9I1hezgedC+IfvkKyu9pCfS3Smzs91o+HEPB5T+nx5Kgn4pwNzw/4ahiA==,iv:OQfL/6UmhWcX2nbyWHZnN1+a5EP0AYAqTIdxn5KLvRE=,tag:JDL3IVYy2jAsDWOObTBFLw==,type:str]
lidarr-mb-gap: ENC[AES256_GCM,data:KuaF98xCy4fK+mrWZQXPpZ0BMyZ/zblJzkZRFVlSF+G948Rql8+NmhlxpBxJ3A/SvFNIvfjzE+UZUnex4gbgxrtvP/ylWuScjYaKdAa0iWfCOxmIAK4gOR6svBMZxIJ1UA==,iv:4Op/XfSbpNxlaGWUMMYR1pa2GkGK77iF2jUmF07CYck=,tag:hS0d6kJxCrOfvGJ4A3BiHg==,type:str] lidarr-mb-gap: ENC[AES256_GCM,data:KuaF98xCy4fK+mrWZQXPpZ0BMyZ/zblJzkZRFVlSF+G948Rql8+NmhlxpBxJ3A/SvFNIvfjzE+UZUnex4gbgxrtvP/ylWuScjYaKdAa0iWfCOxmIAK4gOR6svBMZxIJ1UA==,iv:4Op/XfSbpNxlaGWUMMYR1pa2GkGK77iF2jUmF07CYck=,tag:hS0d6kJxCrOfvGJ4A3BiHg==,type:str]
vps: ENC[AES256_GCM,data:irYKlykCixl0kTvE34+OHhzH4FUor079Mjjn8cdfqnEYUT9jT/5Y6P+q5PKNu61ggaddcPkRjjFwmVaFz0LaVJoJa7D5S/UG4wFnw8D7nfcUPNV32vmuLomgFEhgvNYbf9AdURM81Y6pSwhWl5OM,iv:b9C0SLW4S7IUXfJFLxLHmyws8tAs3LJ+Yy0mvOBA7d0=,tag:BnafiWiTJz3CnFrdPtH6kQ==,type:str]
private_keys: private_keys:
age: ENC[AES256_GCM,data:YZtCasGFuxj4mVnhDtNzXgrzvdjwvHVtaBj2gDJf3dzA7dCe+n7MC5mYowHnS+oZpGFt9P0ImKw30yAYRGZgvxJqL32ACbob4oqcnwmJgswx2ZCwboz/I1PKkAb5uo1Bhc3tyhEshiA9wk99CoI7ug+TGOC5eyFw8RM145uJdxaYJjFmYdSorbjYd5JpijYi3u7p5buIVtr8VRt3tuWNUzarW18d+ZjxtCAF68W9jqICtuODdFakt3pZ9/2byKR5KG1sBKdc38DvJFkRq3wlDE0U2PwPyc/RDdlLAnYGYlZD/GsC1YSWEW/ygxO5lpkoJlxF+/02Qs+lyZVEJd7buYCMU3xkR3kGOQB2dZXa7T6F+Jrr6Ek7svTOQRaId68KhQW33piNUXN8d6YnqIRj537exrcmVCNrRwJmlJ1M8pcYN4IGj6OFICw/lwiTcN51OL4UZOOQZZ60jbgdwWj5gM9OFmxLm0PkIRt6KMQVx58jIzmXqh8SF0+mkymbQdVaoDD5JJ+ltTQgIEBaPe7sGVEK4lGH6qbgtm4F,iv:coRTCK6BSI8QFtfjTg8IAdwumSt6fuQryTxF5g+GF9k=,tag:K06p6t3Gso30DTY/Nk5EDA==,type:str] age: ENC[AES256_GCM,data:YZtCasGFuxj4mVnhDtNzXgrzvdjwvHVtaBj2gDJf3dzA7dCe+n7MC5mYowHnS+oZpGFt9P0ImKw30yAYRGZgvxJqL32ACbob4oqcnwmJgswx2ZCwboz/I1PKkAb5uo1Bhc3tyhEshiA9wk99CoI7ug+TGOC5eyFw8RM145uJdxaYJjFmYdSorbjYd5JpijYi3u7p5buIVtr8VRt3tuWNUzarW18d+ZjxtCAF68W9jqICtuODdFakt3pZ9/2byKR5KG1sBKdc38DvJFkRq3wlDE0U2PwPyc/RDdlLAnYGYlZD/GsC1YSWEW/ygxO5lpkoJlxF+/02Qs+lyZVEJd7buYCMU3xkR3kGOQB2dZXa7T6F+Jrr6Ek7svTOQRaId68KhQW33piNUXN8d6YnqIRj537exrcmVCNrRwJmlJ1M8pcYN4IGj6OFICw/lwiTcN51OL4UZOOQZZ60jbgdwWj5gM9OFmxLm0PkIRt6KMQVx58jIzmXqh8SF0+mkymbQdVaoDD5JJ+ltTQgIEBaPe7sGVEK4lGH6qbgtm4F,iv:coRTCK6BSI8QFtfjTg8IAdwumSt6fuQryTxF5g+GF9k=,tag:K06p6t3Gso30DTY/Nk5EDA==,type:str]
workstation: ENC[AES256_GCM,data:QrM6MwsStFeOH9bFaAuNPtxVWB7rlXXV0PD2Em5Nswf7PIPuzYagQaqBF5nV/AteeJjwsz6KLuBceMZ3O/WlccxvyfY6i00DuRvzJBi+5gZl2rfM4OR5sHC93bzcGmyU1dQUA0nEeGFYUfd4+ZM4BFRgD5OyhpjrqaNYw5kES6WZMCYiR8NAPE2Ca8MqCX3KVQp1AAzgFq/nN0cvuWIflYVIngR4PzAqDGXjgWaPT58rmcWk/3KS2nOKRX5tQ/CgJl4FLdrjuR4VLvoupeUqv1yNeSPSljX+gEK8Sn9vONFd5k0bifLzQd+zCLWyEdJgNvSPf7bnXcuqU8RLSmjckMRAP8YVBlyqsNY++JidXuXukV23aB63dUp44yhIYEkt49/ISJb2qerj3U/Sy97VTw/1WNwY1evzHPlobrUjt3ilxWoxAdzjrqJXWultYYBEk0crmKRRvnABMzaHrZaqaSrHsSfvE4E27m+L9HNwMyq7KywlwrB0KAog52iCi17Gbnrva9aEGrn8Mne2VCvwcrKSEciV1soKpQgy,iv:2+xsS/4+vfQ0UBsHgLVCeV6GOU8giclqNpPXoi43shE=,tag:YVSiY79mHJ2LE9Ab05VE1g==,type:str] workstation: ENC[AES256_GCM,data:QrM6MwsStFeOH9bFaAuNPtxVWB7rlXXV0PD2Em5Nswf7PIPuzYagQaqBF5nV/AteeJjwsz6KLuBceMZ3O/WlccxvyfY6i00DuRvzJBi+5gZl2rfM4OR5sHC93bzcGmyU1dQUA0nEeGFYUfd4+ZM4BFRgD5OyhpjrqaNYw5kES6WZMCYiR8NAPE2Ca8MqCX3KVQp1AAzgFq/nN0cvuWIflYVIngR4PzAqDGXjgWaPT58rmcWk/3KS2nOKRX5tQ/CgJl4FLdrjuR4VLvoupeUqv1yNeSPSljX+gEK8Sn9vONFd5k0bifLzQd+zCLWyEdJgNvSPf7bnXcuqU8RLSmjckMRAP8YVBlyqsNY++JidXuXukV23aB63dUp44yhIYEkt49/ISJb2qerj3U/Sy97VTw/1WNwY1evzHPlobrUjt3ilxWoxAdzjrqJXWultYYBEk0crmKRRvnABMzaHrZaqaSrHsSfvE4E27m+L9HNwMyq7KywlwrB0KAog52iCi17Gbnrva9aEGrn8Mne2VCvwcrKSEciV1soKpQgy,iv:2+xsS/4+vfQ0UBsHgLVCeV6GOU8giclqNpPXoi43shE=,tag:YVSiY79mHJ2LE9Ab05VE1g==,type:str]
@@ -16,16 +17,19 @@ private_keys:
miniserver: ENC[AES256_GCM,data:OdKoGmxZYBp/XTQIviGXcRrz8sY/Ea1KwqtwmYnDmejGZFQuotyr9IHDBy7nyC38Rq18jtRGK0DvQTs+0z/jaBrtAbnk3/aodRpy0q/82XOpUDJ+oRPIbuC1KEyN+vzzTe3jd5Auwg7dy/W7gKgAoR7vhkNeAY4TPbE2ybqsbPDIhDWmCMK43DIxKcWO8LrtYJ9/h+XR1XthGjICRXygOeMA80Ip4BWGMvXnfGcayKTIlotIWNasYGZNn5E/bfb6uH58zPvCAWavleRm113zOeMjlgbtAyl4IyM1StGvZtJq0ud9Txza4AkWmTvYLcAB0xaiph/Vm01GBlaGSWZmOqo8ybGEEQgh6JBlYzHwJ0swqg3uCW59jxeQcGOnaW0YrYPix8twd/2GQafxx2denWFgvoNk5itIdOr07BF+JYKd5FU/Kajo9P5mpV2uFR8sok8/Q68thYvfOEK4wOysWM46lHS/H/P1qeJvCbO6w0hGt+VN/1lbqm+pcVTDMVsfWOkA1Rmm0aWozoYOAPRXH/PjG1NTiqJnecsyw+vZLlnvaDNHKHWhxnWVJw51hSH2fTg7nVz+WqwC24eOmGpOG7UUCdQ4V8L3wb8qDPTmLmo=,iv:FxxpTqtde+v9c/+xDfWimYlgkhJSI5GFIOAwoSrjNsg=,tag:LcLxjKaQ/5JT3hJnBgzmqQ==,type:str] miniserver: ENC[AES256_GCM,data:OdKoGmxZYBp/XTQIviGXcRrz8sY/Ea1KwqtwmYnDmejGZFQuotyr9IHDBy7nyC38Rq18jtRGK0DvQTs+0z/jaBrtAbnk3/aodRpy0q/82XOpUDJ+oRPIbuC1KEyN+vzzTe3jd5Auwg7dy/W7gKgAoR7vhkNeAY4TPbE2ybqsbPDIhDWmCMK43DIxKcWO8LrtYJ9/h+XR1XthGjICRXygOeMA80Ip4BWGMvXnfGcayKTIlotIWNasYGZNn5E/bfb6uH58zPvCAWavleRm113zOeMjlgbtAyl4IyM1StGvZtJq0ud9Txza4AkWmTvYLcAB0xaiph/Vm01GBlaGSWZmOqo8ybGEEQgh6JBlYzHwJ0swqg3uCW59jxeQcGOnaW0YrYPix8twd/2GQafxx2denWFgvoNk5itIdOr07BF+JYKd5FU/Kajo9P5mpV2uFR8sok8/Q68thYvfOEK4wOysWM46lHS/H/P1qeJvCbO6w0hGt+VN/1lbqm+pcVTDMVsfWOkA1Rmm0aWozoYOAPRXH/PjG1NTiqJnecsyw+vZLlnvaDNHKHWhxnWVJw51hSH2fTg7nVz+WqwC24eOmGpOG7UUCdQ4V8L3wb8qDPTmLmo=,iv:FxxpTqtde+v9c/+xDfWimYlgkhJSI5GFIOAwoSrjNsg=,tag:LcLxjKaQ/5JT3hJnBgzmqQ==,type:str]
emacs: ENC[AES256_GCM,data:jBLy+gemP+4b15WGaYBMV3fdlPJpZeZfgPSWbj6nnsN0J4b7RIOZQXl2NoDsNS6yi7aO2J6LoQ6WSJX7si05OlCXJESCu3ghmNAdOQq3lWs2taQMISBV3g0S1OlLXFdDCohBC62y55nMp0/yenL7WzfhmtTmBoFxkp1VcSjI5B+MlcEW/0BzVb7YESCXB5GVSlThecGQ9BEEMBxfhKsSwmLQXIrxdtl6nmyZh8lTselwDwRZ5E8LFXLVwccujAI1zYM5H4qYjBnaizs7f4h7fHh2qs1DeZwFaELFtjgU207IDUQ2CKfVBKDL3XES5gjUnHix4l8lj62ZCWniexGQtVUFAxRerRGS5AQRGDtovC6xPZaylF3JoXEeKgMusPygIz9nzyeB1mXq7FqT+Tv9OG0biCqVEphcQ0GEc2RVPq2ftID0iWyoony9T061nWSkY7QCGlPTmp0PVGLbLpTcd3ulVbuoXKsTLrUXJrAA+5xQe62bS22P2FdKlMP93SvRkM5JoSBI/KBEzmWssuLdxyDopNGpoHgL5thc,iv:qDLbsIvW3pBPXTvPGRDzqeXEoWhhcwgNtHBVe9/NeLA=,tag:GejDD6cBIGYhHY+ixLbVWQ==,type:str] emacs: ENC[AES256_GCM,data:jBLy+gemP+4b15WGaYBMV3fdlPJpZeZfgPSWbj6nnsN0J4b7RIOZQXl2NoDsNS6yi7aO2J6LoQ6WSJX7si05OlCXJESCu3ghmNAdOQq3lWs2taQMISBV3g0S1OlLXFdDCohBC62y55nMp0/yenL7WzfhmtTmBoFxkp1VcSjI5B+MlcEW/0BzVb7YESCXB5GVSlThecGQ9BEEMBxfhKsSwmLQXIrxdtl6nmyZh8lTselwDwRZ5E8LFXLVwccujAI1zYM5H4qYjBnaizs7f4h7fHh2qs1DeZwFaELFtjgU207IDUQ2CKfVBKDL3XES5gjUnHix4l8lj62ZCWniexGQtVUFAxRerRGS5AQRGDtovC6xPZaylF3JoXEeKgMusPygIz9nzyeB1mXq7FqT+Tv9OG0biCqVEphcQ0GEc2RVPq2ftID0iWyoony9T061nWSkY7QCGlPTmp0PVGLbLpTcd3ulVbuoXKsTLrUXJrAA+5xQe62bS22P2FdKlMP93SvRkM5JoSBI/KBEzmWssuLdxyDopNGpoHgL5thc,iv:qDLbsIvW3pBPXTvPGRDzqeXEoWhhcwgNtHBVe9/NeLA=,tag:GejDD6cBIGYhHY+ixLbVWQ==,type:str]
lidarr-mb-gap: ENC[AES256_GCM,data:53XIXJGZA8ss3dZ4EkNibl6SCo0NRGedRz0Vm6icxSXpDGz9+m8lC0gXlB2bw1/x+pv8pp97r6CTt4vmpfHipCoyUpAPo22EzYHuqr1DxKO9lX5IJ/etzS6igoGklyTZ5P2bKQ9PxdusrT7hLiif0zZEVwakorTHckuryuUr7B/2687RGsrsOFU+4Fo7wfw+JT5nkjzQh4i1nMToL+7JF/2ZCFzPQUHbP5QEcMceogv0fl4CyVaKB2ZRwy1LuUA1vlQnMQnP5UJHQh1/iFCHDG8uzj3G5oL+U2nco9aJYiucGKRsIWe0DIlK+EZscMMXUby3vU1VqYd60FTj9T6e3aI3KX15V6QHaflLmFX1G7IewTJPE6nQZFyZ6HwDavsPBpdY94X2sp8bGCIO0UYM8QfS9Z5x+VRs9+sUq9gHzEL5/++viMaa/0DKgo4zsJOmDVj3n7W4GdtWWN2ZAkEHsz0AUcdkSqbCZN5c5LyAom1BiUIoVEkfPL4VPryA4g1NHQsoH6lVUHV7o65E27GfkB27up+w2dig/YyG,iv:fzUD4VHr/g5l/GzP/7ote2tNtjvZlmgrwbAGMoaGpjg=,tag:ZxVQWTHZkQuUP9UAdR9Mzw==,type:str] lidarr-mb-gap: ENC[AES256_GCM,data:53XIXJGZA8ss3dZ4EkNibl6SCo0NRGedRz0Vm6icxSXpDGz9+m8lC0gXlB2bw1/x+pv8pp97r6CTt4vmpfHipCoyUpAPo22EzYHuqr1DxKO9lX5IJ/etzS6igoGklyTZ5P2bKQ9PxdusrT7hLiif0zZEVwakorTHckuryuUr7B/2687RGsrsOFU+4Fo7wfw+JT5nkjzQh4i1nMToL+7JF/2ZCFzPQUHbP5QEcMceogv0fl4CyVaKB2ZRwy1LuUA1vlQnMQnP5UJHQh1/iFCHDG8uzj3G5oL+U2nco9aJYiucGKRsIWe0DIlK+EZscMMXUby3vU1VqYd60FTj9T6e3aI3KX15V6QHaflLmFX1G7IewTJPE6nQZFyZ6HwDavsPBpdY94X2sp8bGCIO0UYM8QfS9Z5x+VRs9+sUq9gHzEL5/++viMaa/0DKgo4zsJOmDVj3n7W4GdtWWN2ZAkEHsz0AUcdkSqbCZN5c5LyAom1BiUIoVEkfPL4VPryA4g1NHQsoH6lVUHV7o65E27GfkB27up+w2dig/YyG,iv:fzUD4VHr/g5l/GzP/7ote2tNtjvZlmgrwbAGMoaGpjg=,tag:ZxVQWTHZkQuUP9UAdR9Mzw==,type:str]
vps: ENC[AES256_GCM,data:TwWwU1h1ALYjCv2UpXDW8Lq2CI+GUWUEWlaUPBT8llvhRcH57MzdqqjfYxqTmERKywsiekngUEeWER6EsO/t9P9+lMPZCBwvZG+lDdY3ion2MfJOLFcWJnIe1lvHvAWf4Fju7LgEEvEyG5Tn8/hLsJwbH/y37AkfLoSWiKVRrKRcWpSb1/2Aycu3Yn5YeFXeELeaG9ZUeGP+fqUfDSvOfFphEEG1cGWqAXRh41azB+/t8yN7J1HSjjx86jzTCRxlr0b4YA3wu4YgZowSy5fETSxCnAk/gHcTgl6XHDsseFT95CJyOeLBLAz88TfTu7hqXswSdA6InSIPh9uLVuA5yiCYBemUXw/bMR8nESi/OCgzM4jF9ZvkYHngAJWPFJqwnGEGUen0qwmp6wdI+cVsqALsrG79rivtGHZbkJUMzh9ta2yA9LkOurqXi8ciSQqGVWqf01H/lhjPyfqzgASMMD85NdiYVc2dEk04YXzt0oOcgpex/VEhWx2DJfIR7mtAyaIQUei8gS6y6htg0b6jHYJtfYAG8WqE3wZ1XaTzUUavVewziAz2OFj9xbn4FFRjxKm0ONMWbVQ/AhWptY2N45CFFk3HaUMZdxGrOVEuEC7c/G8YAWswjBzcQG733SyVLi76Cg==,iv:gEbyoSt8l6vexUcovwGGt2J3YntkMEeSMf2nYsx5Fpk=,tag:N9woepMdByGZR4JD+2Ep7Q==,type:str]
git_public_keys: git_public_keys:
workstation: ENC[AES256_GCM,data:VqyW8OFJ4450Okf/CVa8peYPVLjkfW8M+ykpiteTpXhlgXLPRfHdW2QrGXTMOIfRYDZD33Fx3JqGJZ17Sn7/wToLO+uY8i8JPYyYXWrQMqI0Xf/NR9JvMCycVoAT/oWG9w==,iv:VM5cBPHe3CPpiOozy+hsQcwGokQIVB97oFbVr5o6+Vo=,tag:0w4r5zrdNdpVDNcvbJ8bdA==,type:str] workstation: ENC[AES256_GCM,data:VqyW8OFJ4450Okf/CVa8peYPVLjkfW8M+ykpiteTpXhlgXLPRfHdW2QrGXTMOIfRYDZD33Fx3JqGJZ17Sn7/wToLO+uY8i8JPYyYXWrQMqI0Xf/NR9JvMCycVoAT/oWG9w==,iv:VM5cBPHe3CPpiOozy+hsQcwGokQIVB97oFbVr5o6+Vo=,tag:0w4r5zrdNdpVDNcvbJ8bdA==,type:str]
server: ENC[AES256_GCM,data:WMnUqMgIQ0j4F7G/LppKsN1C+Uoq12DRcYWIEQecTzq9v9+xxe8mAusGenV7SWqz50wrkkjGThmSiXzrdao7Ri4v/BKBX6d+Cql0Us0OOKNplSy1GQ98ML+LfHU=,iv:F/SPXw/BC5JE2u1m9x26qYWrSu/b10QzNPelQN6NBvc=,tag:0YU6dba8y349UvrpeqpbOA==,type:str] server: ENC[AES256_GCM,data:WMnUqMgIQ0j4F7G/LppKsN1C+Uoq12DRcYWIEQecTzq9v9+xxe8mAusGenV7SWqz50wrkkjGThmSiXzrdao7Ri4v/BKBX6d+Cql0Us0OOKNplSy1GQ98ML+LfHU=,iv:F/SPXw/BC5JE2u1m9x26qYWrSu/b10QzNPelQN6NBvc=,tag:0YU6dba8y349UvrpeqpbOA==,type:str]
miniserver: ENC[AES256_GCM,data:M5p2My3d4rOZMj1j4CFMUdHoM2f3BK9y0ikg3NwMs36A2PUzbN39dWzvfhdqoq6stypHbEzmaI4VtUZySPFWaGclBKPea5ujZTxkkZOdt9V6/lvDMdl9O5MUrPBmXYyc,iv:PyVj4OT6ZEqyQDH/K0OtOflGoomUarF25hx95loOgJU=,tag:xZs6wd34LqqqWvRMfUgJbg==,type:str] miniserver: ENC[AES256_GCM,data:M5p2My3d4rOZMj1j4CFMUdHoM2f3BK9y0ikg3NwMs36A2PUzbN39dWzvfhdqoq6stypHbEzmaI4VtUZySPFWaGclBKPea5ujZTxkkZOdt9V6/lvDMdl9O5MUrPBmXYyc,iv:PyVj4OT6ZEqyQDH/K0OtOflGoomUarF25hx95loOgJU=,tag:xZs6wd34LqqqWvRMfUgJbg==,type:str]
emacs: ENC[AES256_GCM,data:jnCEEpEB5tZAs7Y5LT3zQeFZYRqsBcQY5ZASU6p23jRzr9F4wv9ksqezTdZEYGnY7cv8w9gC7Lc0819OTHJyWP0+A45SRZPb16Ii88Omu/Erp0f69wXQCk2rvm2QnZXzGg==,iv:zlglY4hcSdw24O+aM/0BR1/1MRXNYwTcSVZJEItQgMg=,tag:PWrT0LCzs7GBcj+CFFqfNQ==,type:str] emacs: ENC[AES256_GCM,data:jnCEEpEB5tZAs7Y5LT3zQeFZYRqsBcQY5ZASU6p23jRzr9F4wv9ksqezTdZEYGnY7cv8w9gC7Lc0819OTHJyWP0+A45SRZPb16Ii88Omu/Erp0f69wXQCk2rvm2QnZXzGg==,iv:zlglY4hcSdw24O+aM/0BR1/1MRXNYwTcSVZJEItQgMg=,tag:PWrT0LCzs7GBcj+CFFqfNQ==,type:str]
vps: ENC[AES256_GCM,data:ljr2eG76JFVBGTSQZ67ViEJRd+q4ocCY9BIOF+Xs4PiqRF9XtmNxIkQZGXYBWcPIRgKouf259frGPAIqyRHS2pJglAYOAbOWxLb1CgfGxWl6jhZXSBINBu8=,iv:XAixV3SwBIGhhaN/AdTjnT2TB/pD6+oxY+nhd+NDM0M=,tag:FfXBde5TURpWpsEaPMev5g==,type:str]
git_private_keys: git_private_keys:
workstation: ENC[AES256_GCM,data:LZQq96EGcEQsu1hIq0GiPKyfbBXULCXI3umT2NgNJc8YAgYjmfedNZm985GQG+YJeGKYdwlsWnQ59BNc97jQalXGIjvPCI5RAc1bd28VqlUTXZ2ipaHz0nxprgctqMUUSyst18qhumcAdiWT7euInaZiqBhp4xBvIBjK0nMutKfHER/pNV8SYwYPeiKPxB43f6jhKVEYVjItWDsM/C4JvTmwsOvvLUoNMnXtyIKaGDsRhhGSwWcOEj13LWOML6zwB5IUgavvRjKBvnlWy83vH/hFxp1IZNXU4eiePyTAj3Ydne9bmGN1EpF3FyTWZAiWC2uGiML6ZEZUoK94B8mvc8oNwcQPGMzBf0oEBrslWMFOzwItmbNUh5qiVXGUD9a4GiBQzuhPrYX3O0OgFTjveftNcaml4s/qc9MQsiUnYJEIsBVu1959HUS1kEu49LwQzzAIVZQn1dirVaNlq/nbvCv2TYQmgz6v6c/yI7FUDcasjx6hHBUnayGP/xkb0hge6hqGD/T8cZQ8ORKQyj/+R5DvyiHqEtUDNitq,iv:zZOowKGPi7l45djp4IqGdTSf/XDOJACcwpsFGHc8hzQ=,tag:8UQAwcGf3qpDpNoQCVV61A==,type:str] workstation: ENC[AES256_GCM,data:LZQq96EGcEQsu1hIq0GiPKyfbBXULCXI3umT2NgNJc8YAgYjmfedNZm985GQG+YJeGKYdwlsWnQ59BNc97jQalXGIjvPCI5RAc1bd28VqlUTXZ2ipaHz0nxprgctqMUUSyst18qhumcAdiWT7euInaZiqBhp4xBvIBjK0nMutKfHER/pNV8SYwYPeiKPxB43f6jhKVEYVjItWDsM/C4JvTmwsOvvLUoNMnXtyIKaGDsRhhGSwWcOEj13LWOML6zwB5IUgavvRjKBvnlWy83vH/hFxp1IZNXU4eiePyTAj3Ydne9bmGN1EpF3FyTWZAiWC2uGiML6ZEZUoK94B8mvc8oNwcQPGMzBf0oEBrslWMFOzwItmbNUh5qiVXGUD9a4GiBQzuhPrYX3O0OgFTjveftNcaml4s/qc9MQsiUnYJEIsBVu1959HUS1kEu49LwQzzAIVZQn1dirVaNlq/nbvCv2TYQmgz6v6c/yI7FUDcasjx6hHBUnayGP/xkb0hge6hqGD/T8cZQ8ORKQyj/+R5DvyiHqEtUDNitq,iv:zZOowKGPi7l45djp4IqGdTSf/XDOJACcwpsFGHc8hzQ=,tag:8UQAwcGf3qpDpNoQCVV61A==,type:str]
server: ENC[AES256_GCM,data:88xWCYNz+gaCUw6EV/i1PTCaPUTU+6qGme5O3r2zz+ebCfR/QXIG80W7rehZgQ0I7YfD9eFydOgq8+I21L8LjF+AalREWOUZVxjERtVyjVpABcNspogBUgJCD/7s7A8DwDk92V4fiUQF8C2Sp2adfs8V7ef4IhfLC2xhFOQA3z5YZl7y2CNhYPhl0k+tpUW6yYTSTvccJLlpqzzZCPdBSnkI5pJJ2ftpoh+PG0GIzBeta+Pq3I4YMpprzxogaVwlLGn8AtTWkwwWvJpzeELfE7WLgJ9f/X9N5o4zojjvVelxHDMQayo9oeqpIUrCE+RStdLNrYrvLmq2dGTDc7DgLBon0h3led5hqBOBoY11XqaKiGKL9xwZjWM0cDhzOBgFOY9GC3el68Zri+alSIVNccybmXoM78ZFc0rbo2zYKX3r8wO1umTi1/KvGr6q35GY7AXCkLh/qVpvDc8/4NUdiVnNwPxukZMiNTWHPaaUd71zA+sphaykhzs7ex5UMQPsiF6/unUtP3bIZ4taf0mO,iv:1nx2USITQFqiYcva2f1WOjxwK7iYVsWRpAmgU87Iqqw=,tag:GbnajMHjuZNkGjYZapaOTw==,type:str] server: ENC[AES256_GCM,data:88xWCYNz+gaCUw6EV/i1PTCaPUTU+6qGme5O3r2zz+ebCfR/QXIG80W7rehZgQ0I7YfD9eFydOgq8+I21L8LjF+AalREWOUZVxjERtVyjVpABcNspogBUgJCD/7s7A8DwDk92V4fiUQF8C2Sp2adfs8V7ef4IhfLC2xhFOQA3z5YZl7y2CNhYPhl0k+tpUW6yYTSTvccJLlpqzzZCPdBSnkI5pJJ2ftpoh+PG0GIzBeta+Pq3I4YMpprzxogaVwlLGn8AtTWkwwWvJpzeELfE7WLgJ9f/X9N5o4zojjvVelxHDMQayo9oeqpIUrCE+RStdLNrYrvLmq2dGTDc7DgLBon0h3led5hqBOBoY11XqaKiGKL9xwZjWM0cDhzOBgFOY9GC3el68Zri+alSIVNccybmXoM78ZFc0rbo2zYKX3r8wO1umTi1/KvGr6q35GY7AXCkLh/qVpvDc8/4NUdiVnNwPxukZMiNTWHPaaUd71zA+sphaykhzs7ex5UMQPsiF6/unUtP3bIZ4taf0mO,iv:1nx2USITQFqiYcva2f1WOjxwK7iYVsWRpAmgU87Iqqw=,tag:GbnajMHjuZNkGjYZapaOTw==,type:str]
miniserver: ENC[AES256_GCM,data:uhnZ/QmMMT+EmtaRgWDvZEvkf8TewV99zYR639e8K16xII3FM6FfrTF4rr1XwnXrh4xmT6Cr0kgHcPFEel8tUEMWqqlaqD2Vzn7VT/NyveEzCbyZ9GdAP2iMAb/xVdg4TjKxUXKZ8l9k7cDNVKKVkNcEfFQF9eTr84YAs0ZLyX5n+NiYlOcVKq0s3EDEOD5+/x7MdnUtdxOrGG3Ob9fRMfQfTVFwVQ/eMeAQXMAzU9MEcE2xFLaoK2POfu233gMf8E4X9ctt18q7S8DB97K1uhJhEsKiAriNJEyxXsQ+LO4mg1lsKDWr85qa2WoXWnq16kHdVdi1Fw/C3dkHW8NZQ7Eem8nBb9yoNNeYP1/GKFUAv9NA2uZQRGYNdiIA63Vp6Yts3rbJ5reUGrrm+NUMwIy5rC4EoCAYL9tIbYvSlk9QNEpELZniW8wf00uCkdpREMJobgQeTBZII5R4oXNqq28AWEGbEfL8nOspiKdoG6LUKMa5mJTBpmuTBo1EvrYnptf1egjQSvy7aH2WDcQOvuxxUzFmx8bLZ+wq,iv:l7raR36S6EHsuw620ch0q8HuWiyJzJaByyWZUrCLXx8=,tag:xDdJ+LVV3KVIaEjWX1YnjQ==,type:str] miniserver: ENC[AES256_GCM,data:uhnZ/QmMMT+EmtaRgWDvZEvkf8TewV99zYR639e8K16xII3FM6FfrTF4rr1XwnXrh4xmT6Cr0kgHcPFEel8tUEMWqqlaqD2Vzn7VT/NyveEzCbyZ9GdAP2iMAb/xVdg4TjKxUXKZ8l9k7cDNVKKVkNcEfFQF9eTr84YAs0ZLyX5n+NiYlOcVKq0s3EDEOD5+/x7MdnUtdxOrGG3Ob9fRMfQfTVFwVQ/eMeAQXMAzU9MEcE2xFLaoK2POfu233gMf8E4X9ctt18q7S8DB97K1uhJhEsKiAriNJEyxXsQ+LO4mg1lsKDWr85qa2WoXWnq16kHdVdi1Fw/C3dkHW8NZQ7Eem8nBb9yoNNeYP1/GKFUAv9NA2uZQRGYNdiIA63Vp6Yts3rbJ5reUGrrm+NUMwIy5rC4EoCAYL9tIbYvSlk9QNEpELZniW8wf00uCkdpREMJobgQeTBZII5R4oXNqq28AWEGbEfL8nOspiKdoG6LUKMa5mJTBpmuTBo1EvrYnptf1egjQSvy7aH2WDcQOvuxxUzFmx8bLZ+wq,iv:l7raR36S6EHsuw620ch0q8HuWiyJzJaByyWZUrCLXx8=,tag:xDdJ+LVV3KVIaEjWX1YnjQ==,type:str]
emacs: ENC[AES256_GCM,data:FgJUD7p9MxQVvsz+To0Dx2kJ1WZqyNsX/zqOLAtw7pGCAZRh/FiltTOq6t4/2qJsMuVevaNkri4hUMDMhRReZ1BrPuJZHphdSb9U9JLAywTpDWrfyHdvCwNehWiccoANMz+Hjr3xBh1z7fd9apojMVnF3pAbZj/v3EaPsJzzzyY9I32yHDERXn4yUfuwHWfV/TeodFTV2MMz9Bs2mC5UH9mJhktv6xIn9NhITKWKkKzK4/JNICF9GQfjuDteBfIHmXPNeVfsD4nTle74zBsC2eXfC2Ax1wvvpQzNfrh+B121dWgZzBh0XDOtV7ejjxsN/48HMQCJC2A4XQJXK2IJeEeCz1lZK2dyqT0SMmOaxBHSjamzj9fyo2L0r1iz1vopA7zMRwFTq32x47D7TXWwN1yTSrWhXyT/6/YHqNFWDfuvTHYHF0h51W8zl/KKIjKlJEV2rKIoMjnWzfhPmuB4GRFc967ViKV0LjngpX5ummKIM8MnGY6u4VP4MDRJ/JKh1H3XzWlFBERa4xeNjArmd5cka9eL2tZIzwmh,iv:K7z+vxjyj6IOI/mv31Ngj6iufAHY0EoQPwv9jJyWaC4=,tag:jWSFvIFBGOZfDuqYIhMgFw==,type:str] emacs: ENC[AES256_GCM,data:FgJUD7p9MxQVvsz+To0Dx2kJ1WZqyNsX/zqOLAtw7pGCAZRh/FiltTOq6t4/2qJsMuVevaNkri4hUMDMhRReZ1BrPuJZHphdSb9U9JLAywTpDWrfyHdvCwNehWiccoANMz+Hjr3xBh1z7fd9apojMVnF3pAbZj/v3EaPsJzzzyY9I32yHDERXn4yUfuwHWfV/TeodFTV2MMz9Bs2mC5UH9mJhktv6xIn9NhITKWKkKzK4/JNICF9GQfjuDteBfIHmXPNeVfsD4nTle74zBsC2eXfC2Ax1wvvpQzNfrh+B121dWgZzBh0XDOtV7ejjxsN/48HMQCJC2A4XQJXK2IJeEeCz1lZK2dyqT0SMmOaxBHSjamzj9fyo2L0r1iz1vopA7zMRwFTq32x47D7TXWwN1yTSrWhXyT/6/YHqNFWDfuvTHYHF0h51W8zl/KKIjKlJEV2rKIoMjnWzfhPmuB4GRFc967ViKV0LjngpX5ummKIM8MnGY6u4VP4MDRJ/JKh1H3XzWlFBERa4xeNjArmd5cka9eL2tZIzwmh,iv:K7z+vxjyj6IOI/mv31Ngj6iufAHY0EoQPwv9jJyWaC4=,tag:jWSFvIFBGOZfDuqYIhMgFw==,type:str]
vps: ENC[AES256_GCM,data:JByRbAuQqu4Ciw1kCMDgUrqwUIGWM1QbAlDAJZAUfx57I74FcSEuQSz7iAGYFZi1DWy5hdzi4CjJU6X9GWBkgUq757OqiF0p7pZxJOQdc4EviO9mXLSLI1oVTnt9vcOfKqUVIrEFlz8n9AQSd878bNoisJtCp0qztdowzLPPPswpAvb8hBvxcSfGwGYpsXUngpdaBbs30UuXVtGF0YxPnO9rYqGwYh5kn00T7Ut1Q1h8NGCLhlwHVsxZZMcNxdlL7PjJlAVTH4Gfxphgc4jLdPz+UlPiyxCnG78BTDiWH30eskwm7RUagb6RNAMFMrZekusnu0tInaAe0OYPluYoIe4g9cD4zhhfio5y/8l0b8r3fBf2CeFEkXtXXs8fxCXB5s2BKnW4VXBYBmnT1dZMXuMHBCgEHip3uAr754lUDFu7lKjfmhnST4BRKNt1EXDAQ8jOOA2WGlWi1hqkfxzqyhS5lEv45y9c88sXtc9LbUXg0a97VZiIA+173TtrJ6O9/fghmNycD+7L7tLE+SCx0jUmGFWcy6TtwCjC,iv:PhWCv+qGXljm3I1u5FMKNheaZmIfcUb1OZ2bmgHpyXI=,tag:FyH/bZ68QPL/iZQzLbpv6w==,type:str]
certificates: certificates:
qbit_cert: ENC[AES256_GCM,data:RQNgCoh/kC/Bi+pamonNaAhniBLD7d5Ilc7YDe02jLnGYWgvtgQCcWflSbnxuLNgIDX1tBEmR7J8hjRyetNF6mQj7z8SmvfG+Mn53qRhiqa+RoSrhL18xtHKjWkNbgwXJtrSFa4g2dMLsqkrp8mK3Xzap2Ge+bCfi8iE17NOd7U/8NwH3ApdjFFz3nmic/gsMZwJE2CvefF4OXLiLl4COeuf+yj6k7C8/e4rP3C418lF3C2HZWwOoTU57LRmfiG3GaTg5mD3Ir1OFC28G4R2MDXh/anSuJEnGQdTgXxQMUa9R8Ec2eCANEwtK7UFvnuLtUEErlJDMuW5h7E3pMXP0iWsqwP5IJrkSDxMwm6TQHlmSx62WhP0wZVGXiIeOdIQgWTgkBPk/3EpkGML9/WH+FAtZ1mhi6YLaRPlSLcKnLd2/YM2JEgq/A52No09BHw15PEMWPrEld2HLdG03MroCazci3shqeVMLk/G/CrXQ7EKUlmvfrR7/6T8/VolyCzezAtmk8LjtWQw3bes/xg+ON+MjHEGAj7H0EkDsZcgbEJZVYEyUI1y75x4TVP+LQpOd7QynlUoj0qkw7QvvMj4j94qAMbAFkTrqk0LwYytcha5A0nwfurMFkTfPGkaAF9TktVtpMCQ/ghCfhWSKAbpuFR0Gpl9Atofs6RfOWXJ4oh0vh3mQNJc4oOQxN+2vlLPjRxVUULNhCTUpffZTaBvbU/gTAKK2XOI54od09YzNwV4oBMS0opg2fi3JA1LFzDDSyCeoA91mq2iWR1NtSwB/T5zRlsnt8dujcNfGPIoyNkjzxaK9hzDdm0Sh80ZGGt6pcibyOCCCuKNt60kgbDGXP58+//Kj0bKp5PTjZz4q1fUVssMQP8cnPte4CZCTgRCrpAlYiPxz58iGo3D0MZrTIcrj/SGaV61m+7W52OXnMPDXHZ2kJ27kdCVK/hDVeBTWqSHg3qYG8AdFG1QSkDS0HkmJ+bQJtXksh36asbDfVBo4I372mI7oYC7o+U/xUDTo3wf3TsP6fzq3jTymripJfcXdNn+3Y7ZfYJTX24eh9oCMTTnlc/HdX3IiYGugcTBpQwSIu0Vx7L1Jz6GpB6hCRzTdvutnH6u+nWOgp5r5s7E3/PTTFFKwY+wxhAauSlhWYoBQcnjnkoW0saLldTpF2rAozCPQuHai/zYvWI0YHmetUJNmYOjxMHcmy/DNohN51ecg6782p4wk5E01LT2+22thAxfAe8jA7Gt87Jxx6fVNb7/r+cPhpciWfOlpbkmLjmLX909HXSekLJa1cq4VIgjJhbGbSty9ooetY4kZSoBosSizxQx5VeNZkjPOvJ/K6QOlDfJAmC5QzJVcgOF7XHSSeeaMdcPWK/TyEGCFBkutsIrYMHefyqH+/InqOco3wUdpvGEWWB8j3DfV89HgKT+Nt95rF+tpOsvt2v2QkzPzULTJ/ZRxJ6i86qJBJZrriKx8t+ns99lmao+aoBa25he6xBAyGVuBwK3eS5fJznGBF2lDg83FOac0KxrcJgOiYNyDwucFMfeY4i1p5X+zALBfVk16XICRRiYjub2+3V2kTQOLaNdPqkQ0DZPaPwwhg2v0WGdOIuAf2iB4bm1T00f1sMPKZ6MtuoXjnYP3E4gTRP9vZAyfB35xOlXmd42fw==,iv:5xKwtvNM1MOwk24m9yl7kEQaTAmFZqHWcE6TkKhmsJI=,tag:ikVouFR7x9cMFoSy/A9c4A==,type:str] qbit_cert: ENC[AES256_GCM,data:RQNgCoh/kC/Bi+pamonNaAhniBLD7d5Ilc7YDe02jLnGYWgvtgQCcWflSbnxuLNgIDX1tBEmR7J8hjRyetNF6mQj7z8SmvfG+Mn53qRhiqa+RoSrhL18xtHKjWkNbgwXJtrSFa4g2dMLsqkrp8mK3Xzap2Ge+bCfi8iE17NOd7U/8NwH3ApdjFFz3nmic/gsMZwJE2CvefF4OXLiLl4COeuf+yj6k7C8/e4rP3C418lF3C2HZWwOoTU57LRmfiG3GaTg5mD3Ir1OFC28G4R2MDXh/anSuJEnGQdTgXxQMUa9R8Ec2eCANEwtK7UFvnuLtUEErlJDMuW5h7E3pMXP0iWsqwP5IJrkSDxMwm6TQHlmSx62WhP0wZVGXiIeOdIQgWTgkBPk/3EpkGML9/WH+FAtZ1mhi6YLaRPlSLcKnLd2/YM2JEgq/A52No09BHw15PEMWPrEld2HLdG03MroCazci3shqeVMLk/G/CrXQ7EKUlmvfrR7/6T8/VolyCzezAtmk8LjtWQw3bes/xg+ON+MjHEGAj7H0EkDsZcgbEJZVYEyUI1y75x4TVP+LQpOd7QynlUoj0qkw7QvvMj4j94qAMbAFkTrqk0LwYytcha5A0nwfurMFkTfPGkaAF9TktVtpMCQ/ghCfhWSKAbpuFR0Gpl9Atofs6RfOWXJ4oh0vh3mQNJc4oOQxN+2vlLPjRxVUULNhCTUpffZTaBvbU/gTAKK2XOI54od09YzNwV4oBMS0opg2fi3JA1LFzDDSyCeoA91mq2iWR1NtSwB/T5zRlsnt8dujcNfGPIoyNkjzxaK9hzDdm0Sh80ZGGt6pcibyOCCCuKNt60kgbDGXP58+//Kj0bKp5PTjZz4q1fUVssMQP8cnPte4CZCTgRCrpAlYiPxz58iGo3D0MZrTIcrj/SGaV61m+7W52OXnMPDXHZ2kJ27kdCVK/hDVeBTWqSHg3qYG8AdFG1QSkDS0HkmJ+bQJtXksh36asbDfVBo4I372mI7oYC7o+U/xUDTo3wf3TsP6fzq3jTymripJfcXdNn+3Y7ZfYJTX24eh9oCMTTnlc/HdX3IiYGugcTBpQwSIu0Vx7L1Jz6GpB6hCRzTdvutnH6u+nWOgp5r5s7E3/PTTFFKwY+wxhAauSlhWYoBQcnjnkoW0saLldTpF2rAozCPQuHai/zYvWI0YHmetUJNmYOjxMHcmy/DNohN51ecg6782p4wk5E01LT2+22thAxfAe8jA7Gt87Jxx6fVNb7/r+cPhpciWfOlpbkmLjmLX909HXSekLJa1cq4VIgjJhbGbSty9ooetY4kZSoBosSizxQx5VeNZkjPOvJ/K6QOlDfJAmC5QzJVcgOF7XHSSeeaMdcPWK/TyEGCFBkutsIrYMHefyqH+/InqOco3wUdpvGEWWB8j3DfV89HgKT+Nt95rF+tpOsvt2v2QkzPzULTJ/ZRxJ6i86qJBJZrriKx8t+ns99lmao+aoBa25he6xBAyGVuBwK3eS5fJznGBF2lDg83FOac0KxrcJgOiYNyDwucFMfeY4i1p5X+zALBfVk16XICRRiYjub2+3V2kTQOLaNdPqkQ0DZPaPwwhg2v0WGdOIuAf2iB4bm1T00f1sMPKZ6MtuoXjnYP3E4gTRP9vZAyfB35xOlXmd42fw==,iv:5xKwtvNM1MOwk24m9yl7kEQaTAmFZqHWcE6TkKhmsJI=,tag:ikVouFR7x9cMFoSy/A9c4A==,type:str]
qbit_key: ENC[AES256_GCM,data:OE+GagWBxtqvnS0kVrbswpBDYWBNLOqEOFxWIIjwvU0WNjBuQNm2kh65WqaYu60rztyu0SFzhRBIARD17EGN0bYQFxIW48Y4sglKbv1WRuimU7YIO2dv0lqjkSlAkgdVWh8TB2AyZkoB/I9tea7nVwoyIUZkM+C8oRG92Wtad/T+oKplpOoe3GtFDc7/XeTacWP142iEGlkMXEIBIHlrZ8V5kDnMxugxkc6zv82dXyXEMLEO7yjcJK51ZmbBnb+HlWcDJkSW62HOuzurKt9qWbqCsfmhjjZXXJ0G7BVVTRosg38ld2dX+SkxkYCWi4xPbO4NGf6M7K8oY+mNMCzbrduyzz95YuverozqLR/f7wrozSN42mtB3XTQgA2Kg3LVhxu486Ox5aHjp9+acpC0YJCEZCwYQw9IK1As3bjUxRhhxSa8Jt+JKXtfUjYtpfC8dOblX69pwdVaQACJospRcqISq8h2+KbwwJPZadUddgQJ4BhIfZWQAFdZzJrD8PmqXjrKqdclBHxYjNYOin/xuHvxLuU06fOby/kKsQP2vs0Eep0uIFd2BVALGwPakEt8+2PpujPXGAPJD3owc3XPObjkTjI9SAP1tYQbQE9wpcMJLtRPOUWTNfbJzmSK1Uh3j+Z3dl+dI967PD9KkdkApiVeu8JKCiFudGksR8n2mzT0i6NVmiy9zTG0jBvI1RUPPp5VXJofWgyVDDobvEn+yn1gLv+cBlrm5RhwZXdtNKuNktNZ8aSYRUi5JXO1JPiPT8oBAqlBJR5GOp85IWVwzA8UQdttaVnTKTrmHULnajZPsrOwZLfTT6ogj4msQbMO+HKq88h4gdXjLmU3wPtgyao2CGRej1cUsnnh3gZvLwcj9p/Gl/OSozoCj0SD66jOYtG7I/oq0nyslcNAaH+HeGU54eCtj5RwM54B0o0JDjVtjo4JP3CRL/YwiJBFpmDdSVDFuQXB8qtYRw7afKE8uS5s6Xm7gJvarrG+LspBQ6zCzN8TqDaJtZ0tx4thevBJL7iEbDhyzcqrwCf7YShSPSvXtV+VtDwrth5SjRj9jh7i+sHJQGGDlC0gUruYosXKO13CDVD2MTYxKxTTo5+E3MMPtcSlr6qoeaEedzrC2c9la0L14xiQw7tOEv8lcY7XqFEjv6e+ATE30+96rbuJkQVUj0U0seeG42LDMHQaanan/asg30QgfPNns25fkTLxXE6Ps2KA7QBfwz0IqVfRGuxySYyqFdOptwxCFkmdT7uxRK86NNWbmAFLku6795GKVIxbFXwSNoK12uUcd8nXqAsnVQf+M1tC5hIqOCywMPHKMbHu1NsiST055L4xbcWv4bDkfkmKOxpsS9z1zfLQ/DpRQeAidc8XTWcvcfnHcFAMJTHxseLHgtnnopf/RRAp3nhygzmaHuWCE1YPK23/gWRh+Cj8ke8TnE7lzfKIE5GOu12ojLe0unzHNOnGvsv1vF5JJbSxT4119LX7SmfTXvOGqzywxoK7ejLuP1DfZR7GejJ2P5IC54FHR0xwrP9W9Y4TfxriA13l0LMKgRsARmM1zXwTwkioFiy0hCNmX0+yERs/5aLg+pvNgFRAtZnFoRVU9lKU+lpQuQd57BSa4hX1jFZ++8kSRWoHi49cp5LBTKD5+Z0HddbPJdaVumkLPs0CXSxMGzSok2VkVfc9tcTIyv4g6biB+7lNmXCBh+RSdnbdXvFe6fYzAXhT8ZWaRmAJ/btkfN4CCRHPN7gFX6ShF56K93nTLBzn4PA50pNToHc4bmF8SYokHNvzdP9oGc1TjSn1UttcBSoWSIO6KunAbZwaL6CH1vk0YLiUOSQOQQg2er/nR8aNd358ym4AX3399+TWL6oy4RrAzha/KcS6JrvtHTI0wH4W2qBeogfcyhwkVuOU4vr63xvoygI+33tkY2j2DkR+h1i2DVvm6NXAt0gZiCQLIe5j8dsfwOZUzXG5glktAl/w0sBrQy6h6MUpZGHzAoCxPQVfZrR/10JN9joEGmbsSc1syqkIVNIh3DhyucVof3cQ6hy0kOZ4zl7q9IlnY0K85C2Pm/NKrAWx18pp8E2Fk6QnUcG+/0f/ls5wUfFsUDFGPFuaUN0WLeMZjWIN4Nzt1THGjdYVbpMymQ0d9YtM+y7iisb0HKguU5KMo7m0gJW/BGkSzfr62VkvFro3ZlZ+r4C2oDK2NO+PsD0yc0NeN45alcgTFeQDtEuNLErp+a7hBae86YhR6UWgmKpuwzqIvHjb32VVKMWcbCnfQIN3,iv:jTER/Q2JKTeMs33IF65J9/OufVMdMsTtBWNY+CwgigI=,tag:CTB5rasvOtpey21jXtxx3Q==,type:str] qbit_key: ENC[AES256_GCM,data:OE+GagWBxtqvnS0kVrbswpBDYWBNLOqEOFxWIIjwvU0WNjBuQNm2kh65WqaYu60rztyu0SFzhRBIARD17EGN0bYQFxIW48Y4sglKbv1WRuimU7YIO2dv0lqjkSlAkgdVWh8TB2AyZkoB/I9tea7nVwoyIUZkM+C8oRG92Wtad/T+oKplpOoe3GtFDc7/XeTacWP142iEGlkMXEIBIHlrZ8V5kDnMxugxkc6zv82dXyXEMLEO7yjcJK51ZmbBnb+HlWcDJkSW62HOuzurKt9qWbqCsfmhjjZXXJ0G7BVVTRosg38ld2dX+SkxkYCWi4xPbO4NGf6M7K8oY+mNMCzbrduyzz95YuverozqLR/f7wrozSN42mtB3XTQgA2Kg3LVhxu486Ox5aHjp9+acpC0YJCEZCwYQw9IK1As3bjUxRhhxSa8Jt+JKXtfUjYtpfC8dOblX69pwdVaQACJospRcqISq8h2+KbwwJPZadUddgQJ4BhIfZWQAFdZzJrD8PmqXjrKqdclBHxYjNYOin/xuHvxLuU06fOby/kKsQP2vs0Eep0uIFd2BVALGwPakEt8+2PpujPXGAPJD3owc3XPObjkTjI9SAP1tYQbQE9wpcMJLtRPOUWTNfbJzmSK1Uh3j+Z3dl+dI967PD9KkdkApiVeu8JKCiFudGksR8n2mzT0i6NVmiy9zTG0jBvI1RUPPp5VXJofWgyVDDobvEn+yn1gLv+cBlrm5RhwZXdtNKuNktNZ8aSYRUi5JXO1JPiPT8oBAqlBJR5GOp85IWVwzA8UQdttaVnTKTrmHULnajZPsrOwZLfTT6ogj4msQbMO+HKq88h4gdXjLmU3wPtgyao2CGRej1cUsnnh3gZvLwcj9p/Gl/OSozoCj0SD66jOYtG7I/oq0nyslcNAaH+HeGU54eCtj5RwM54B0o0JDjVtjo4JP3CRL/YwiJBFpmDdSVDFuQXB8qtYRw7afKE8uS5s6Xm7gJvarrG+LspBQ6zCzN8TqDaJtZ0tx4thevBJL7iEbDhyzcqrwCf7YShSPSvXtV+VtDwrth5SjRj9jh7i+sHJQGGDlC0gUruYosXKO13CDVD2MTYxKxTTo5+E3MMPtcSlr6qoeaEedzrC2c9la0L14xiQw7tOEv8lcY7XqFEjv6e+ATE30+96rbuJkQVUj0U0seeG42LDMHQaanan/asg30QgfPNns25fkTLxXE6Ps2KA7QBfwz0IqVfRGuxySYyqFdOptwxCFkmdT7uxRK86NNWbmAFLku6795GKVIxbFXwSNoK12uUcd8nXqAsnVQf+M1tC5hIqOCywMPHKMbHu1NsiST055L4xbcWv4bDkfkmKOxpsS9z1zfLQ/DpRQeAidc8XTWcvcfnHcFAMJTHxseLHgtnnopf/RRAp3nhygzmaHuWCE1YPK23/gWRh+Cj8ke8TnE7lzfKIE5GOu12ojLe0unzHNOnGvsv1vF5JJbSxT4119LX7SmfTXvOGqzywxoK7ejLuP1DfZR7GejJ2P5IC54FHR0xwrP9W9Y4TfxriA13l0LMKgRsARmM1zXwTwkioFiy0hCNmX0+yERs/5aLg+pvNgFRAtZnFoRVU9lKU+lpQuQd57BSa4hX1jFZ++8kSRWoHi49cp5LBTKD5+Z0HddbPJdaVumkLPs0CXSxMGzSok2VkVfc9tcTIyv4g6biB+7lNmXCBh+RSdnbdXvFe6fYzAXhT8ZWaRmAJ/btkfN4CCRHPN7gFX6ShF56K93nTLBzn4PA50pNToHc4bmF8SYokHNvzdP9oGc1TjSn1UttcBSoWSIO6KunAbZwaL6CH1vk0YLiUOSQOQQg2er/nR8aNd358ym4AX3399+TWL6oy4RrAzha/KcS6JrvtHTI0wH4W2qBeogfcyhwkVuOU4vr63xvoygI+33tkY2j2DkR+h1i2DVvm6NXAt0gZiCQLIe5j8dsfwOZUzXG5glktAl/w0sBrQy6h6MUpZGHzAoCxPQVfZrR/10JN9joEGmbsSc1syqkIVNIh3DhyucVof3cQ6hy0kOZ4zl7q9IlnY0K85C2Pm/NKrAWx18pp8E2Fk6QnUcG+/0f/ls5wUfFsUDFGPFuaUN0WLeMZjWIN4Nzt1THGjdYVbpMymQ0d9YtM+y7iisb0HKguU5KMo7m0gJW/BGkSzfr62VkvFro3ZlZ+r4C2oDK2NO+PsD0yc0NeN45alcgTFeQDtEuNLErp+a7hBae86YhR6UWgmKpuwzqIvHjb32VVKMWcbCnfQIN3,iv:jTER/Q2JKTeMs33IF65J9/OufVMdMsTtBWNY+CwgigI=,tag:CTB5rasvOtpey21jXtxx3Q==,type:str]
@@ -47,40 +51,40 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37 - recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRcXFhSU03M0U4azM5VnJV YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWRGVscDJsU0ErZ1VBRzVq
UExReVBmRnpNaUx3WDViU2hLalpnbE4wTVFjCkkzQzhlVjcrVndaUmVRNUhmSWZT dE5aNUZvcmhVRHVjYUJFT09hdDd0UzhIS3hNCkRFRlphRXBTd3VFTE81RjJRaE5w
RlByQUxSSWtNeDJiTEJMR2JhWG1MM2sKLS0tIC9mUDVhNUtQei9VN3dJdmVBK0Y2 bzJSaCtsT0QwMkx2WDVyZ0FzeFphWk0KLS0tIGN5M0QyWmQ4Y3lCU0FXaU9vL0hv
NDM5SFhNbWp0WWdMYVc4NC9HdHhSR2cKGj8ur7g1F5OTv+XKg5pmFiSMgAcNL3b8 MEp1ekxTdWp2b2g4dFd3OVNkUlZBMGMKzNGSzYgQsNW6HEvzTWmo73GShAAv/g8+
PjhyPcZqxCB4J8utMf8yxmZkVqbyd3UjZRBUUXSgzg/i1nx0GTGcDA== h3/6n/ObqlKsjDyVFgiOYop3LWfwPMzmOhx4S0wsOHit0UxdyoJwWA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1V0JUSk9FOUkrSzBmZFNY YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLUGFaaHFtVWl1cG1XdlRT
M1JlZEMxSFVEV0d3NGttZFFrK0U3MWtlb1RBCjJQbmRGSVQ0M0p0NHdGK1ZHSlNo TUh0MHZTa0JhdDFSTVJZOWJBd0F0SWI0N2tNCkdnaG5DcXdDT3dqRVJDcjlsZ3Fz
TkVHS3lnN3VOUUNjTVI2V1B6bzlDb1EKLS0tIFRtdko2cjkzMlZyV1hRcWFnWFlv ZFFaeTB4UTBQRVYzcldndm1RSjhCTzQKLS0tIDJySFIvbGpBd0l4RzYwVUd1MWpF
TWVXMlpVUWJIZEhLOVVpblhwZjJDOGsKwgqjQZ1XzQNkFPItT+/gjBNnvxiYHbQ/ ZHhxdERrd3VNUGpTTlZUM25RYzJwSjAKG2DZUyomWm8Nxn6mPDKbBh1YsEUr642a
JP/cse3TR7VsC5dq0SGCFY8zPBPiZPvuU+f9Bq9wfJWDG79CintBnQ== nGYxmuRVBVINbOB3gBPwgLeD+S2Vlm4vrC/u2761fTgm8KFLC+txpQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuaFFlM2M5ZHZIM3FNSEYv YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXTnc0N3JWUGk3cWV0QXNK
bnlnbG01YWRPcFR1Z2tUNTdvSmdGZ0QrMjNZCkJPemFBYktBWldPWFdyVS9ZOVBv L0ZWM0I0NVlVbTdsZmdRall2V3FUTllidlJnCjQwbFJ1TjVNQjl3NURQenBDZVhy
ZU5zRWpqYXJ4MVVQdFdWcmQ4am5DSkkKLS0tIDNudUpUNnNJUHQyYTM3Y3pwb0FT QXEybkIvc0RnV1dNL1Rhem9GajhzY2cKLS0tIFk0Nm9JK2ZvenJsYVF2RUJLVzVL
VUY1c0ZtWDA0THZ3ekVmUFl4ZjgvaHcKuyh3cIwboc2wxectPk0La0CLRX7VvaBR bzFWRnFjd01wbDVrQnhlb3NYampEVEkKWl3/oymEX/TdMHyxE8mOopIwu4Kots27
XoBMk4PbfQLS1PuaavH+NLNAp3N7LmF9IlZBS3zFW26Dy1viqWbhFw== teyBmo6aVTAQ1zSxGDszI6kgK6PC3Z/WqaMaoJilGI6k8vCkOT3oMw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age13w4elx3x6afrte2d82lak59mwr2k25wfz3hx79tny6sfdk66lqjq989dzl - recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4T3krdSthSnhkVGk5RHg3 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtaG1Ea0ZyZ1IrRGxEaUdw
MUVWdXVqM0o3LzZtSzFsZURiSGlLTEd6SlhNCllyaW5BcHZueDRGNlMwWTNaQTNC TzlMRE84ZDBXRTNFWHcwNE81MDZlYStTZWdFCmxLbUxORFNVVHRGYXV4bDRvV1Ra
bTBMRWFRSG42WVg0cU9CR1F5ZmpTQ1kKLS0tIFdDaGloemJNWUJWcCtOeUhnMmlQ Rzg0YnpkaDJ3alhxalFFck10MjF4MG8KLS0tIDgwSEhReERtZHZ3U2RWcnFaaHlI
dklwODNxYVo4a2FaWDJFM0FnV1l3SlUKMnq/MAJRwR7iEri2KomPrMj0gTkMyhzH UmQzNEJVVTVPRHFqVlAraTR2bHNOdmsKKCVCzZ10sEA7rGRCUxbpYlaR6Y2jZvho
P5E4zheU7chJTAz5jf6iecyOvKAt6q5g9Q1MU0D6dkOcv2gzWSNAAw== THbZe5MHY1a44L2XQSZe3I+1qOVBWVSL10KYTjJIBTxoeBtjlQJAVQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-11-11T21:11:25Z" lastmodified: "2026-02-03T21:56:09Z"
mac: ENC[AES256_GCM,data:zhPKEB/u8x6mABVzrKlfSQdW/eCailqqb/JIyTzC21bF503ESfjrJIiTIb889rAjcGXQFfA0BJ398Y+8XLJ3WL25Imc1vF5/HIkeG1u7FZQx7XNVg2A8NxzG42F8Zei28Cf9PBqz/zsu8OyVgFdGWR5oAimli45PJcozcnKaWsU=,iv:G0zYmh9k5aayGY7szw5uf7bp9ss/Kg2UeALpIGIkByA=,tag:0pDYK8Wa7etc1wxDlMiddw==,type:str] mac: ENC[AES256_GCM,data:Bnjo3TFYoGbtB8HF1i+ZQLlfeBMOjq14lu8oLRqcZ6Fx5Am0uuh+/PHClWZ/JX5suC0Kb81+aBHg2QTsLoB6zdUrRpaqa0CUxTDoGw8tpo8m6zLWvSggpYLAuRgTYqBZ0lVK1QxAi9+qVJQ5AIhYwSPrf2oq/Mpq4tFGUoG/tzM=,iv:8JqAeBVYnZM8A+CPAlKN+6SDty0XQ4AKEBJLGV8Q738=,tag:CQXE5QsfJMiI7UQoCfE3dQ==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.11.0 version: 3.11.0

View File

@@ -19,38 +19,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37 - recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvSTFDNHN2cm5UMDkvb3h3 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQTUEycms3ZkdMd3hpcXJz
RUs3aEIrZmlhQ3JvcCtKa09WUkRpZ1o4b3pnCmtiaUJnYUVWcFdpRk9vdmNQRjJT R2pZZEc5STZ3dUdYbUdsSGJaRWI5TWNMK1RRCjVxR1pzY0ZVUmcwSjJFYktteWoz
R1NlMUJnRHQwdGRmQWJrc1NySmhPZW8KLS0tIFhnNmE4bGFUYW5GdVprc09PTTBt YmlaVkFPRnZha3h5ckV1TVQyVWZKdGMKLS0tIFgvdWF5VEJwTTcwdXZ6SDRMU3BL
N2VpQU5aeUJuRThyQVFwaEs3QnUwSDgKdgsuwN4/dfAVzXnJ7LPwhUpD8kuh3VxO V2x6NlhyY0pmUVBsYmZITjArdjJRbEkKvzsJxs5EHR0uumwhZ36MhKuMS+WkogXU
vB9iva29YN85E+CKZ7CryGdrnCy1a1fUC0YiAakbzQejon62fK2d5Q== nSVRQoc5TClzYwShY1ltHK+LCl0DlB4xFoMiO4GWwH1TySKe/ywpUQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQRnEvNzlxT0dWMDNZOEhS YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnQytNaUs1M0hiYi8vdDUx
TVpRSHpGM1JvZ0JQRW4zMXpXL3Rza3NiRVVNClovaGF0Z1hPdXltY3pTaGRKUTY2 V1NtZ3VGNFVjRHRWUzliR3M3Q2Z6K3RWSHo4ClQ1RE1PeHJ4REpubVJHb0lJcGJ2
MGJtYmFqaDQ4THRRTE1rUURhR0N1Y1UKLS0tIGtOOUxVNTdFZGZ3TS8zdUJFWWxO SEFvT2YvNWhMc2lneWR5NmRYc2pzVE0KLS0tIGxkRWRRRTNtVDUzVXh2L0lEa3RK
MG1yLzNRaTdmVEJaSnBlbGR0SjR0TlUK7iNC+uyUN3s5T7b1PD+BZ+LvlsKdOpbM YjFSUDJHUjFUeVBFbUlKOS8ya1ZhMW8KssRH3/XT1iCVgV+6Sh25Axp0c96aHtVX
pA2P4ZaUcBXCOEonmG4LnflEyUDXrxBoTkswkpBpG/SowF+yXe0Fwg== /HXN3AwTm0GJZCQnZsVIIPtoCzhUZSza+bzGZIZODYtgtCIxtdzVSw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwcU05d2R4a3k4Z2VGVlcr YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIbkZpZFJCY21IRkJjNkRB
VXJWeUZtWjZuY0lDM2dBNWFxbUxyaUdPVm1RCkxkNjFNbmh6L2ZMeitlY3ZwTEw4 UEdEVlZhRWhRb1ZDMjJtMmpkUmpnY3ZvMGlFCnBLcHlkMWNyMy8wenYwT2pmRTZL
MUhTVnBLdmRVblFOa09nWTlXVHNIWHcKLS0tIC91aHR5d3JlRDlBWFJtWDNsNFUw dWtiWFlaR1FrL21HQTFZM2N3a3BHYW8KLS0tIFlYZWVHb0VEeDU5NnRjbDk5M2po
QjhiSVNRMlgwTTAvNmE4SDdQOS8rNVUKIYVulp/SpDmewQkotisfUsSZFh0r1eNB K0xRRFhua09DRE04WUd6NlZuQldFbEEK2OgiawCbCtbrk8l45QdjVu8+VNWbrl4i
59ysWy09dse8Oed9lwMVMLI7B4DBT6CRWuefOU//urI/pB9itV6jvw== 3U9iwek30JkQSZaWBXaCZlWLvbKNjIMpwTtxDOhxmu4DUh3Hx6In/g==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age13w4elx3x6afrte2d82lak59mwr2k25wfz3hx79tny6sfdk66lqjq989dzl - recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyazBBS0xKakE0Z0hHRnZo YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAySjBmaC9rREpUQ3BvWWNU
R0VZUk5qSVF3L2NTb2p6Z29QMnp1MkIrVHowClJVZ3VzUTc4aDVha2tBUE93R2Nw MWEvM3ZGb2RXZ0dMdWxLRTJCR2VSdyt5VUhBCjBvL3MxZ3pTaFQ4aGdZVnAxUmd3
T29nakxRQkpidzlrdFZQTFlxMXFwOEkKLS0tIGJWRkdJaVpLWXBVNnZUQ2l3dm9Q YUtoZkhEV01TU0drRUdDaFZ5M2tZLzAKLS0tIHpBL3NwV2NhN0QwcHdwbFpQWlZn
RmRyZldlMjUwMEdUUEpDS2JSa2tDTTAKp/pT+0cNnCuKVL+Z0fEMiw1PL9PB/nSM eUNjc2RPOUxLTGowTlRqN3lEdjRLU2cKTTEXmHyhnL/hZGDr8ONrmzdU6Or5xkKY
QWVTo0Mt8Y6X0Xt0EAi9G5AYxADZ/mmEWPxB7RFgVAiMKtor5Gy1zw== GHADDt+LCg8njcZom39Aj4kpCx+f7HlV65glKwr37vZ0sL9KE+O9+w==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2026-01-16T15:38:39Z" 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] 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

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICDY0RAhoIM9q5xQCqLWJQimk3JAkfYAcabxGFnxmNBq jawz@workstation

View File

@@ -5,6 +5,7 @@ vps:
server: server:
private: ENC[AES256_GCM,data:wrP/069tuQs3ObYE8Q0MNVxe3+4vZ2HIImoIdZpj1uPgdBknboX1wmANv/k=,iv:FJL5KumHos8PoXra+BB2Uc6YedsF6MD3wWyuugXzJ+E=,tag:nVuTrW2P7JvnWnv6H1SmdQ==,type:str] 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] 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: home:
private: ENC[AES256_GCM,data:YZ0jvBzkMv8Bwc9u3LDJzwSqQvPj8wPUxTIeBFiLYVQQIBjm8aS1dTYuPvo=,iv:mXuW7TVERxOMmGIit3a7Spmbk/EgYuGkO66AWJUnMF0=,tag:xM7C3F3JCiud/A9yPD5ydQ==,type:str] 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] public: ENC[AES256_GCM,data:DcwAHhHjIxFqRL5h7p/0nkFnWiI/iqR8Fws6AuFaxjgUHKYd/6l3D6q/O/0=,iv:bBJ0bsKRiGQUSlRmHqeLQWkOIUNfG5VVpuV6MOtKZO0=,tag:harMG6GDIfclmSq3D36bTw==,type:str]
@@ -13,40 +14,40 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37 - recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlTXplR3BHYzl1bmxuSzlW YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlemJmbnAwUHZHT3ozdWxH
ZVQvTlg2amFnMCtTKzRoZXNYaXBNcmRyWGhZCmpLT1NqbGRtUFpxUzlTMFdYemRJ Njh1ZFUvVW8zcVV6SGxrVW1IWW9ZUFBaTEh3CnJsMnFnM0d5YnBKWE5CT2Flang0
ZXF6c2dhOG9LbXVkczU0N1RVK1lqajAKLS0tIHFmQ0FrbVQ2QldiUS9oT2J2RkU0 TkNZb0xCY2c4Qk1kdXRkRXcvOU1TSW8KLS0tIE1VdGEraW03bnV4VEc5c0ZheFJ0
N0pFQ095Uzdid2NmZXRVZ2l6N285bFUKG52XE8nf9GfESCfNfoP6L8GxLfvrihs4 MFJpVTlvTGJ0YXBKSnFFbXhEUEwwSmMKxOtHLbRw5e6dRW4jvqFLsl6UzKZ+mvfR
CaZSkRzkuZUsfBND0B2BX/UlrjVHWPQCYMqqTtMpLXoRSmRsvWYCTA== hwKJ4KEbXuCqwtPQEWk/pF0i4vzrgUP1Cp1Y7BxGGyK9ufyV/CCQIg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPdWpKeU90cTV6blNZckt0 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5cnE5VENCMUxxOVZUdC9X
a2hpWms2b1ZuKzEwZUZFbEp0bFlPellVaHdVCkF5RENObjMvalJNc2FNYXk1UUxR QWFMRytGamhaWENZY1Q4STR5L0Jsdk90SlUwCis4ekFWYmMwN2dESXMrVFNIamFG
anE0SUI5ZWY5ZUlteVArSVN4T01DS2MKLS0tIEpDWDkzWm1mampQZDkwRCt5STVk RzhET2ZGdGN6b1V1ZHkyOCtDNzBWVjQKLS0tIEF1NGdoU2lqYVdIN3hwRk13SFpP
RHg4UklFQUp1KzFWRnpDOEIzRVJWZ2sKyS6bXtqJ3J7FrCyTa16Ithy2JS4HdkOg RHNOeDBlSHFpays2VkRuR2RxaGpYZ1EKwxZfRZthZHVuJe3D5pamCSxYo3hyaaVc
NzTn/6RL+F61PLDGvEEa7Ypk/OGIjfJYxDQ5Sd9LODja47jIK5T6Aw== I0UvMDMgcDRZuEzV9g1ZEYnaVXg5InyOO0dDZuCYX/HZqTLPiaOIxg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBueWZlTThKV1d5UEpJUVBE YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGaUhOcHV2TkYrZWxnOCtI
SlFDMmFYSVREWXVvaDZYWk5TYXFRdTlpeVFZCnM4K3FYNk9hZ3R1K3c3Y0lURzZx TzF1RVpFY3pSa1Y2MmJjVlpKcWZnWGtOOTJ3CmRnTUpyRms2aUtvS1ZvVXFsb0ZQ
ZXdsWFNNSSt1VUtZdmRUUFdEK3BEdUkKLS0tIHB6ckZPMUkyM0ljK0RScWJSQlIz U0RiYXM3S0RKQjVwL2hqYllhZENUdmsKLS0tIDNTRHR2ZU1VTzdNNXRDU0xkcTRM
UzVRQ3JzS1Q3N3EzTkhpNDZwZEtPbm8K0BzKOk9ljAnc5eydHfNha/QPfq9Eltfb ckowd2p5bitGYVhMNU9Qc0NUeFFJV3MKPKT1/06/fKpWPOMsRaU/fpyVUf7onWGB
X/pNFkeW/b6FgLwo+3pc+NfgOFvpOuq7/bRWUCxGSJP/4w9+9q1a6A== 0P22NBzP1i5caqSrFnVVeyuhgYxabC4oUKVmjU5QIj1R8Rqh7gworw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age13w4elx3x6afrte2d82lak59mwr2k25wfz3hx79tny6sfdk66lqjq989dzl - recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkV1Fsb3FMZGxGZ1A5dk9y YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3SHdHTDhKQzFUQVdqM0hW
SllKMjZRby9KNzhVSUVpODh0MW1Ya1JzdzBjCjZmQUFoaCtTSS9ybE1hVjExaFVR Tm9QdVozaHViQVRuTExhV1BpdWYvY012enk0CmhjODlUN0FkNldGRG94bVFSTVBv
bWlKcFdlQmRIdEJrUE5jKzRlNFdQTVEKLS0tIEtMOW8xb2hLOGluMnVDaWxFMXQw QUNWZmszRStZN24vZWhnajhIcWdXVDgKLS0tIG9ueVZsT29KRE1iM2oreWtGWGVC
KzZFSWprL0l0MDdVdEVKbEV5eklZdTAK/1ZyGvElfp+LVloSR6aJUtvrgU0CrzaJ SG40OS8wMHlKNmxQa0VScHQrU2NmT2sKt9xw/8jsgnV1cZndqYNiHvIf8VdEJYCl
SQtO7vc4oDedkiTz6LKySta+uyn3e17Jzdyy9nU2D/Q5X+CpKGP3cg== UUJ1KPz9mvUx3ny+rK50FSD61U8PHEZm2UC0w+/qkZwRtCx21Ku6dw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-09-08T00:14:52Z" lastmodified: "2026-02-04T18:37:11Z"
mac: ENC[AES256_GCM,data:O2herKRy4k9ZMuPzzPF5QlBC2isXdRoIsbYLJ/6X7esxtxxgNuAljx4SCR6UMT7pl3G2E33cnnBEkuAIy6SMXOaZNfOuAEJXaCwpRwCXu26lrcTf6n7UdP36GWfIRsR4utD5/vv66ch6MqmQWkW7E5zydy5dOv+BJ4XS/50OUQs=,iv:TscYNQaeI+mBxyobxI1O4wUzRtA27pvjXz27kqMJhA0=,tag:zx/xrYAWJCxYz5HRTKzYfQ==,type:str] mac: ENC[AES256_GCM,data:AlrMK34dWDm5hfVwnQnzk3l8NIRbiVV6KHa6io9S9l07WvC3TYLTOJS6xOi4pkEz6sqQ7IpZU7RRdosxuQp50NmMEt2QYawTHFZIgzFYeKRbl5N5LCu9afC6yTtvG/sT7uenTMhh2qT1JBwebJiUdM9zNVUzWlW5d1SdxrHgIbs=,iv:dvqsDaC+trhY1kheYUEOEwHfCDz0Mu7N0LpfjnKko5g=,tag:tuqyK8vuwSrk1kf+Vi7MKg==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.10.2 version: 3.11.0

View File

@@ -2,7 +2,7 @@
**Purpose**: Validate specification completeness and quality before proceeding to planning **Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-01-30 **Created**: 2026-01-30
**Feature**: specs/001-mcp-server/spec.md **Feature**: specs/002-mcp-server/spec.md
## Content Quality ## Content Quality

View File

@@ -1,7 +1,7 @@
# Implementation Plan: MCP Server for Repo Maintenance # Implementation Plan: MCP Server for Repo Maintenance
**Branch**: `001-mcp-server` | **Date**: 2026-01-30 | **Spec**: specs/001-mcp-server/spec.md **Branch**: `002-mcp-server` | **Date**: 2026-01-30 | **Spec**: specs/002-mcp-server/spec.md
**Input**: Feature specification from `/specs/001-mcp-server/spec.md` **Input**: Feature specification from `/specs/002-mcp-server/spec.md`
## Summary ## Summary
@@ -34,7 +34,7 @@ Build a local-only MCP server under `scripts/` that Codex CLI can use to run doc
### Documentation (this feature) ### Documentation (this feature)
```text ```text
specs/001-mcp-server/ specs/002-mcp-server/
├── plan.md ├── plan.md
├── research.md ├── research.md
├── data-model.md ├── data-model.md
@@ -59,7 +59,7 @@ scripts/mcp-server/
└── test_docs_sync.py └── test_docs_sync.py
``` ```
**Structure Decision**: Single Python project under `scripts/mcp-server` with src/tests layout; documentation lives in `docs/` and spec artifacts in `specs/001-mcp-server/`. **Structure Decision**: Single Python project under `scripts/mcp-server` with src/tests layout; documentation lives in `docs/` and spec artifacts in `specs/002-mcp-server/`.
## Complexity Tracking ## Complexity Tracking

View File

@@ -1,6 +1,6 @@
# Feature Specification: MCP Server for Repo Maintenance # Feature Specification: MCP Server for Repo Maintenance
**Feature Branch**: `001-mcp-server` **Feature Branch**: `002-mcp-server`
**Created**: 2026-01-30 **Created**: 2026-01-30
**Status**: Draft **Status**: Draft
**Input**: User description: "build a mcp server under the directory /scripts the intention for this mcp server is to be consumed by codex-cli to help on modifying the repository by doing but not limited to, the tasks declared on the ai-oriented documentation found in /docs. as an extra, I want this mcp to have tests, which run on the gitea pipeline when any changes done to the mcp or docs directories are commited. expand the ai-documentation on /docs with info about the built mcp so that it is compliant with what of the available tools of the mcp can be called for what specific tasks, ensuring that the mcp provides the easiest up to date assistance to giving this repository maintenance. When it comes to the coding preferences for the server, I want: 1) indentation kept to the bare minimum 2) guard clauses & early returns 3) easy to read coding style, with no comments, but professional easy to maintain code structure 4) functions with docstrings, typehints, etc. 5) give preference to iteration tools such as lambdas, map, filters, as opposed to for loops and multiple ifs. 6) functional code, with reduced duplicated code 7) lint & format the code" **Input**: User description: "build a mcp server under the directory /scripts the intention for this mcp server is to be consumed by codex-cli to help on modifying the repository by doing but not limited to, the tasks declared on the ai-oriented documentation found in /docs. as an extra, I want this mcp to have tests, which run on the gitea pipeline when any changes done to the mcp or docs directories are commited. expand the ai-documentation on /docs with info about the built mcp so that it is compliant with what of the available tools of the mcp can be called for what specific tasks, ensuring that the mcp provides the easiest up to date assistance to giving this repository maintenance. When it comes to the coding preferences for the server, I want: 1) indentation kept to the bare minimum 2) guard clauses & early returns 3) easy to read coding style, with no comments, but professional easy to maintain code structure 4) functions with docstrings, typehints, etc. 5) give preference to iteration tools such as lambdas, map, filters, as opposed to for loops and multiple ifs. 6) functional code, with reduced duplicated code 7) lint & format the code"

View File

@@ -1,6 +1,6 @@
# Tasks: MCP Server for Repo Maintenance # Tasks: MCP Server for Repo Maintenance
**Input**: Design documents from `/specs/001-mcp-server/` **Input**: Design documents from `/specs/002-mcp-server/`
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/ **Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/
## Phase 1: Setup (Shared Infrastructure) ## Phase 1: Setup (Shared Infrastructure)

View File

@@ -0,0 +1,34 @@
# Specification Quality Checklist: VPS Image Migration
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: February 3, 2026
**Feature**: /home/jawz/Development/NixOS/specs/003-vps-image-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 checklist items pass based on the current spec.

View File

@@ -0,0 +1,3 @@
# API Contracts
This feature does not introduce or modify any external HTTP or RPC APIs. Operator actions (image build, provisioning, secrets enrollment, rebuild trigger) are performed via existing infrastructure workflows, so no API schema is required.

View File

@@ -0,0 +1,49 @@
# Data Model: VPS Image Migration
## Host Profile
- **Purpose**: Defines a named system configuration (e.g., vps).
- **Key fields**:
- `name` (string, unique)
- `target_environment` (string, e.g., Linode)
- `services_required` (list of service identifiers)
- `secrets_required` (list of secret identifiers)
## Image Artifact
- **Purpose**: Represents a build output used to provision a VPS.
- **Key fields**:
- `image_type` (string, Linode-compatible)
- `build_reference` (string, build timestamp or revision)
- `host_profile` (reference to Host Profile)
## Bootstrap Secret Material
- **Purpose**: Material required to unlock secrets on the host.
- **Key fields**:
- `bootstrap_method` (enum: generated-on-host)
- `recipient_public_key` (string)
- `enrollment_status` (enum: pending, enrolled)
## Deployment Target
- **Purpose**: The environment where the image is launched.
- **Key fields**:
- `provider` (string)
- `region` (string)
- `instance_id` (string)
## Rebuild Trigger
- **Purpose**: Represents an authorized rebuild action for the VPS.
- **Key fields**:
- `actor` (string)
- `requested_at` (datetime)
- `status` (enum: queued, running, succeeded, failed)
## Relationships
- Host Profile 1..* Image Artifact
- Host Profile 1..* Bootstrap Secret Material
- Deployment Target 1..1 Image Artifact
- Rebuild Trigger *..1 Host Profile

View File

@@ -0,0 +1,58 @@
# Implementation Plan: VPS Image Migration
**Branch**: `003-vps-image-migration` | **Date**: February 3, 2026 | **Spec**: /home/jawz/Development/NixOS/specs/003-vps-image-migration/spec.md
**Input**: Feature specification from `/specs/003-vps-image-migration/spec.md`
## Summary
Migrate image building away from the deprecated generator to the upstream NixOS image workflow, add a new vps host that produces a Linode-compatible image, and implement a secure two-phase secrets bootstrap that requires re-encryption after the host generates its own key. Provide a repeatable remote rebuild workflow limited to explicitly authorized operator machines.
## Technical Context
**Language/Version**: Nix (flakes; nixpkgs 25.11)
**Primary Dependencies**: nixpkgs, flake-parts, sops-nix
**Storage**: N/A (configuration repo)
**Testing**: Manual validation (image build, boot, network, secret availability, rebuild)
**Target Platform**: NixOS image for Linode VPS
**Project Type**: Infrastructure configuration (single repo)
**Performance Goals**: N/A
**Constraints**: No regressions for existing hosts; secrets must remain secure; first boot must be reachable for enrollment; rebuilds restricted to authorized operator machines
**Scale/Scope**: Small number of hosts, single vps target
## Constitution Check
No enforceable principles are defined in the current constitution file (placeholders only). Gate passes by default.
Post-design re-check: no changes; still pass.
## Project Structure
### Documentation (this feature)
```text
specs/003-vps-image-migration/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
└── tasks.md
```
### Source Code (repository root)
```text
flake.nix
parts/
hosts/
modules/
secrets/
scripts/
config/
environments/
```
**Structure Decision**: Use the existing Nix flake layout with host definitions in `hosts/`, shared logic in `modules/`, and flake assembly in `parts/`.
## Complexity Tracking
No constitution violations to track.

View File

@@ -0,0 +1,16 @@
# Research: VPS Image Migration
## Decision 1: Replace deprecated image generator usage
- **Decision**: Use NixOS's built-in image building workflow (`nixos-rebuild build-image`) for Linode-compatible images.
- **Rationale**: The NixOS manual documents `nixos-rebuild build-image` and lists Linode as a supported image target via `image.modules`, indicating the upstream path for image generation.
- **Alternatives considered**:
- Keep using nixos-generators (rejected due to deprecation and upstream migration).
## Decision 2: Secure-first secrets bootstrap for vps
- **Decision**: Use a two-phase bootstrap where the vps generates its own age key on first boot, then the host public key is added as a recipient and secrets are re-encrypted before the second deploy.
- **Rationale**: sops-nix supports generating an age key when missing and can use SSH host keys to derive age identities; this avoids embedding private keys in the image or repository.
- **Alternatives considered**:
- Bake a static age key into the image (rejected for security risk).
- Ship a fixed SSH host key in the image (rejected for key reuse across hosts).

View File

@@ -0,0 +1,103 @@
# Feature Specification: VPS Image Migration
**Feature Branch**: `003-vps-image-migration`
**Created**: February 3, 2026
**Status**: Draft
**Input**: User description: "Remove deprecated image generator usage, add a new vps host that builds a Linode image, ensure first-boot secrets are available, and support remote rebuilds for ongoing changes."
## Clarifications
### Session 2026-02-03
- Q: Who is allowed to trigger remote rebuilds? → A: Only explicitly authorized operator machines.
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Provision a VPS Image (Priority: P1)
As an operator, I want to build a Linode-compatible image for the new vps host so I can provision a replacement VPS that boots with network connectivity and remote access.
**Why this priority**: This is the core migration outcome; without a working image, the VPS replacement cannot proceed.
**Independent Test**: Can be fully tested by building the image, launching a Linode instance from it, and confirming network and remote access.
**Acceptance Scenarios**:
1. **Given** a clean repository state, **When** I build the vps image, **Then** the build completes and produces a Linode-compatible image artifact.
2. **Given** a Linode instance created from the vps image, **When** it boots, **Then** it has working network connectivity and remote access is available.
---
### User Story 2 - Secrets Available After Enrollment (Priority: P2)
As an operator, I want the vps to generate its own secrets key on first boot and then make required secrets available after enrollment so core services can start securely.
**Why this priority**: The VPS must remain secure; services should start only after the host is enrolled and secrets are re-encrypted for it.
**Independent Test**: Can be fully tested by provisioning from the image, enrolling the host key, and verifying required secrets become available after the follow-up deployment.
**Acceptance Scenarios**:
1. **Given** a freshly provisioned vps instance, **When** the system completes its first boot, **Then** it generates host-specific bootstrap key material and remains reachable for enrollment.
2. **Given** the host key is enrolled and secrets are re-encrypted, **When** a follow-up deployment runs, **Then** required secrets become available to services.
---
### User Story 3 - Remote Rebuild Workflow (Priority: P3)
As an operator, I want to trigger rebuilds of the vps host from any authorized system so updates (such as firewall changes) can be applied consistently.
**Why this priority**: Ongoing updates are essential for operations and security, and should not depend on a single workstation.
**Independent Test**: Can be fully tested by triggering a rebuild from a separate authorized system and verifying the changes apply on the VPS.
**Acceptance Scenarios**:
1. **Given** an explicitly authorized operator machine, **When** a rebuild is triggered, **Then** the vps host updates successfully and reflects the new configuration.
---
### Edge Cases
- What happens when the vps image build completes but the artifact is not compatible with the target environment?
- How does the system handle first-boot secret access when bootstrap material is missing or corrupted?
- What happens when a remote rebuild is triggered but the VPS is unreachable?
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: The system MUST stop using any deprecated image-generation dependency currently used for host images.
- **FR-002**: The system MUST define a new vps host configuration that produces a Linode-compatible image artifact.
- **FR-003**: A VPS provisioned from the image MUST boot with working network connectivity and remote access enabled.
- **FR-004**: The system MUST support a secure, two-phase bootstrap where the host generates key material on first boot and secrets become available after enrollment and re-deploy.
- **FR-005**: The system MUST provide a documented, repeatable way for explicitly authorized operator machines to trigger remote rebuilds of the vps host.
- **FR-006**: Existing hosts and images MUST continue to build and operate without regression after the migration.
### Key Entities *(include if feature involves data)*
- **Host Profile**: A named system configuration (e.g., vps) that defines the target environment behavior.
- **Image Artifact**: A deployable disk image produced from the host profile.
- **Bootstrap Secret Material**: Data required to unlock or access secrets on first boot.
- **Deployment Target**: The infrastructure environment where the image is launched.
- **Rebuild Trigger**: An authorized action that initiates a configuration update on the VPS.
## Assumptions
- The vps host can generate bootstrap key material on first boot and is reachable for enrollment.
- Operators already have a secure, authorized path for remote access to the VPS.
- The Linode environment can accept and boot the produced image artifact.
## Dependencies
- Access to the target environment needed to validate image compatibility and boot behavior.
- Existing secrets management process and data required for the vps host.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: A Linode instance provisioned from the vps image is reachable via remote access within 10 minutes of first boot in at least 95% of test provisions.
- **SC-002**: Required secrets for core services are available after enrollment and follow-up deployment in 100% of test provisions.
- **SC-003**: Existing host builds complete without new failures after the deprecated dependency is removed.
- **SC-004**: Remote rebuilds apply a configuration change to the vps host within 15 minutes in at least 90% of test runs.

View File

@@ -0,0 +1,151 @@
---
description: "Task list for VPS Image Migration"
---
# Tasks: VPS Image Migration
**Input**: Design documents from `/specs/003-vps-image-migration/`
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
**Tests**: Not requested.
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Project initialization and validation setup
- [X] T001 Review current image generation usage in `flake.nix` and `parts/packages.nix` and note all nixos-generators references
- [X] T002 [P] Review host structure in `hosts/` to mirror patterns for the new `hosts/vps/configuration.nix`
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Remove deprecated generator and ensure existing outputs are preserved
- [X] T003 Update `parts/packages.nix` to build `emacs-vm` from nixpkgs/NixOS outputs (remove nixos-generators usage)
- [X] T004 Remove nixos-generators input from `flake.nix`
- [X] T005 Update `flake.lock` to drop nixos-generators entries
- [X] T006 STOP: Ask user to validate `emacs-vm` build works without nixos-generators (confirm before proceeding) (reference `parts/packages.nix`)
**Checkpoint**: Foundation ready after user confirmation
---
## Phase 3: User Story 1 - Provision a VPS Image (Priority: P1) 🎯 MVP
**Goal**: Define a new vps host and produce a Linode-compatible image artifact
**Independent Test**: Build the vps image, launch a Linode instance from it, verify network connectivity and remote access
### Implementation for User Story 1
- [X] T007 [US1] Create `hosts/vps/configuration.nix` with base imports and minimal networking/remote access enablement
- [X] T008 [US1] Register vps host in `parts/hosts.nix` using existing `createConfig` pattern
- [X] T009 [US1] Add a Linode image build output for vps in `parts/packages.nix` using the upstream NixOS image workflow
- [X] T010 [US1] Document the vps host entry and image artifact location in `docs/reference/index.md`
- [X] T011 [US1] Add a manual validation checklist entry for vps boot connectivity and remote access in `specs/003-vps-image-migration/quickstart.md`
**Checkpoint**: vps image builds and can boot with connectivity
---
## Phase 4: User Story 2 - Secrets Available After Enrollment (Priority: P2)
**Goal**: Secure two-phase secrets bootstrap and enrollment workflow
**Independent Test**: Boot vps, generate host key, enroll key, re-encrypt secrets, redeploy, verify secrets available
### Implementation for User Story 2
- [X] T012 [US2] Set secure host posture for vps in `hosts/vps/configuration.nix` (secureHost enabled, secrets gated)
- [X] T013 [US2] Add vps-specific sops-nix bootstrap settings in `hosts/vps/configuration.nix` (generate key on first boot; no baked key)
- [X] T014 [US2] Document the enrollment and re-encryption steps in `docs/playbooks/enroll-vps.md`
- [X] T015 [US2] Update secrets guidance to reference the vps enrollment flow in `docs/constitution.md`
**Checkpoint**: vps can boot without secrets, then unlocks secrets after enrollment and redeploy
---
## Phase 5: User Story 3 - Remote Rebuild Workflow (Priority: P3)
**Goal**: Provide a documented, repeatable remote rebuild process
**Independent Test**: Trigger a rebuild from an explicitly authorized operator machine and verify applied config changes
### Implementation for User Story 3
- [X] T016 [US3] Add a rebuild helper script in `scripts/rebuild-vps.sh` with clear inputs and safety checks
- [X] T017 [US3] Document remote rebuild usage and prerequisites (explicitly authorized operator machines only) in `docs/playbooks/vps-rebuild.md`
**Checkpoint**: remote rebuild flow is repeatable and documented
---
## Phase 6: Polish & Cross-Cutting Concerns
**Purpose**: Final consistency checks and documentation polish
- [X] T018 [P] Ensure vps host is referenced in any host inventories or indexes in `docs/reference/index.md`
- [X] T019 Validate quickstart steps still match implementation in `specs/003-vps-image-migration/quickstart.md`
- [X] T020 Validate existing host/image builds after migration (document results in `specs/003-vps-image-migration/quickstart.md`)
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies - can start immediately
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
- **User Stories (Phase 3+)**: Depend on Foundational completion and user validation at T006
- **Polish (Final Phase)**: Depends on desired user stories being complete
### User Story Dependencies
- **User Story 1 (P1)**: Starts after Phase 2 and user validation at T006
- **User Story 2 (P2)**: Starts after Phase 2 and user validation at T006; depends on vps host existing (T007/T008)
- **User Story 3 (P3)**: Starts after Phase 2 and user validation at T006; can be done in parallel with US2
### Parallel Opportunities
- T002 can run in parallel with T001
- T018 and T019 can run in parallel in the Polish phase
- After T006, US2 and US3 can proceed in parallel once US1 host scaffolding exists
---
## Parallel Example: User Story 2
```bash
Task: "Set secure host posture for vps in hosts/vps/configuration.nix"
Task: "Document the enrollment and re-encryption steps in docs/playbooks/enroll-vps.md"
```
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Setup
2. Complete Phase 2: Foundational
3. Pause at T006 for user validation of emacs-vm
4. Complete Phase 3: User Story 1
5. Stop and validate the image boot and connectivity
### Incremental Delivery
1. Complete Setup + Foundational → user validates emacs-vm
2. Add User Story 1 → validate image build/boot
3. Add User Story 2 → validate secrets enrollment flow
4. Add User Story 3 → validate remote rebuild workflow
5. Polish and doc consistency checks

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.33.0.228` 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.33.0.228|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.33.0.228. 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.33.0.228 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.