76 Commits

Author SHA1 Message Date
Danilo Reyes
2910dcb3ef emacs main repo parity
Some checks are pending
Weekly NixOS Build & Cache / build-and-cache (push) Has started running
2026-02-09 18:47:49 -06:00
Danilo Reyes
8d62cffc8e linode builds both image and host
Some checks failed
MCP Tests / mcp-tests (push) Successful in 23s
Weekly NixOS Build & Cache / build-and-cache (push) Failing after 9m18s
2026-02-09 00:13:59 -06:00
Danilo Reyes
7670f2fa94 time for mcp to build 2026-02-08 23:33:15 -06:00
Danilo Reyes
b5c7024ea4 test.......
All checks were successful
MCP Tests / mcp-tests (push) Successful in 15s
2026-02-08 15:15:02 -06:00
Danilo Reyes
6d29835303 ...5
Some checks failed
MCP Tests / mcp-tests (push) Failing after 13s
2026-02-08 15:11:59 -06:00
Danilo Reyes
67119653b5 ...4
Some checks failed
MCP Tests / mcp-tests (push) Failing after 10s
2026-02-08 15:09:34 -06:00
Danilo Reyes
f95cf4a546 ...3
Some checks failed
MCP Tests / mcp-tests (push) Failing after 9s
2026-02-08 15:08:11 -06:00
Danilo Reyes
dc94b8fc44 ...2
Some checks failed
MCP Tests / mcp-tests (push) Failing after 10s
2026-02-08 15:07:22 -06:00
Danilo Reyes
b46e2a6269 ...
Some checks failed
MCP Tests / mcp-tests (push) Failing after 4s
2026-02-08 15:06:13 -06:00
Danilo Reyes
fc62d7ab32 paths monitored
Some checks failed
MCP Tests / mcp-tests (push) Failing after 12s
2026-02-08 15:04:47 -06:00
Danilo Reyes
e0aa2b337e cicd fix 2026-02-08 15:00:30 -06:00
Danilo Reyes
4fb24672bf test temp dir
Some checks failed
MCP Tests / mcp-tests (push) Failing after 6s
2026-02-08 14:59:03 -06:00
Danilo Reyes
81318b3fb1 mcp test
Some checks failed
MCP Tests / mcp-tests (push) Failing after 16s
2026-02-08 14:54:30 -06:00
Danilo Reyes
ae1f68b105 atticd nginx 2026-02-08 14:44:21 -06:00
Danilo Reyes
b7a38d7634 fixing emacs daemon 2026-02-07 22:04:33 -06:00
Danilo Reyes
6cd22f0448 punk theme
All checks were successful
Build All Color Schemes / build-schemes (push) Successful in 1h1m21s
2026-02-07 20:44:51 -06:00
Danilo Reyes
9c73be46ac discord upgrades 2026-02-07 13:26:02 -06:00
Danilo Reyes
293f0a3096 ipv6 crashes synapse 2026-02-07 04:25:43 -06:00
Danilo Reyes
d8606ad2ed isso didnt need ip override
Some checks failed
MCP Tests / mcp-tests (push) Failing after 7s
2026-02-06 23:03:31 -06:00
Danilo Reyes
7671ec686f documentation audit 2026-02-06 22:58:20 -06:00
Danilo Reyes
5ed2ece05c isso and microbin migrated 2026-02-06 22:51:54 -06:00
Danilo Reyes
2b1424cfd2 piracy ports for vpn 2026-02-06 22:33:03 -06:00
Danilo Reyes
661629924e nextcloud nginx fixes 2026-02-06 22:23:16 -06:00
Danilo Reyes
d7191f7a02 websockets 2026-02-06 21:38:25 -06:00
Danilo Reyes
480c9a2a07 new readme 2026-02-06 20:09:52 -06:00
Danilo Reyes
5e2e2ab29b ill kick a baby 2026-02-06 20:04:34 -06:00
Danilo Reyes
b36c452c3a it works! 2026-02-06 19:51:04 -06:00
Danilo Reyes
95c6cefd24 new wireguard connections
Some checks failed
MCP Tests / mcp-tests (push) Failing after 5s
2026-02-06 19:22:36 -06:00
Danilo Reyes
a8dda9d32d playbook 2026-02-06 19:18:37 -06:00
Danilo Reyes
7a5f577806 dnscrypt listening subnets 2026-02-06 18:40:31 -06:00
Danilo Reyes
a7482ee146 fix 2026-02-06 12:19:38 -06:00
Danilo Reyes
5382bf7251 vps known_host for lidarr_mb_gap 2026-02-06 11:49:43 -06:00
Danilo Reyes
ead7e5a379 re-encrypted sops 2026-02-06 11:07:42 -06:00
Danilo Reyes
416e8a4edc new disk uuids for vps 2026-02-06 11:00:24 -06:00
Danilo Reyes
2da1278b37 production ip 2026-02-06 09:24:14 -06:00
Danilo Reyes
60ccc776de redundant port assignation 2026-02-06 09:22:09 -06:00
f7bef14c19 Merge pull request '004-vps-migration' (#5) from 004-vps-migration into main
Some checks failed
MCP Tests / mcp-tests (push) Failing after 2s
Reviewed-on: #5
2026-02-06 09:20:18 -06:00
ed85b66017 Merge branch 'main' into 004-vps-migration
Some checks failed
MCP Tests / mcp-tests (pull_request) Failing after 2s
2026-02-06 09:20:02 -06:00
Danilo Reyes
a2cb88c970 knownhosts for lidarr-mb-gap
Some checks failed
MCP Tests / mcp-tests (pull_request) Failing after 2s
2026-02-06 09:11:21 -06:00
Danilo Reyes
b7ce1866d0 tmp files and lidarr-mb-gap 2026-02-06 08:59:44 -06:00
Danilo Reyes
6d5422f447 nginx fixes 2026-02-06 08:27:58 -06:00
Danilo Reyes
41298f0980 oops 2026-02-06 08:24:40 -06:00
Danilo Reyes
b7c4e38148 doc remediation 2026-02-06 08:22:40 -06:00
Danilo Reyes
005addff1b create www-data 2026-02-06 08:21:24 -06:00
Danilo Reyes
17cd7ba593 websites init + docu revision 2026-02-06 08:13:37 -06:00
Danilo Reyes
893bb199b1 temp disable lidarr-mb-gap 2026-02-06 07:50:46 -06:00
Danilo Reyes
44e39fda6c plausible ip 2026-02-06 07:44:31 -06:00
Danilo Reyes
229b989902 format document 2026-02-06 07:26:26 -06:00
Danilo Reyes
00a43a5a48 subnet parameters 2026-02-06 07:16:22 -06:00
Danilo Reyes
788ea5ad26 rules fixup 2026-02-06 06:59:59 -06:00
Danilo Reyes
1fd29a5f4f nat table 2026-02-06 06:44:47 -06:00
Danilo Reyes
a15db616b4 removed windows_vm key 2026-02-06 06:12:10 -06:00
Danilo Reyes
7cedfba30d dont even remember 2026-02-06 05:21:51 -06:00
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
67 changed files with 1509 additions and 1240 deletions

View File

@@ -14,3 +14,8 @@ trust_level = "trusted"
[mcp_servers.nixos-mcp] [mcp_servers.nixos-mcp]
command = "nixos-mcp" command = "nixos-mcp"
cwd = "/home/jawz/Development/NixOS" cwd = "/home/jawz/Development/NixOS"
[mcp_servers.nixos]
command = "nix"
args = ["run", "github:utensils/mcp-nixos", "--"]
startup_timeout_sec = 300

View File

@@ -5,20 +5,30 @@ on:
branches: [ main ] branches: [ main ]
paths: paths:
- 'scripts/**' - 'scripts/**'
- 'docs/**' - 'scripts/mcp-server/**'
- 'parts/packages.nix'
- 'flake.nix'
- 'flake.lock'
- '.gitea/workflows/mcp-tests.yml' - '.gitea/workflows/mcp-tests.yml'
pull_request: pull_request:
paths: paths:
- 'scripts/**' - 'scripts/**'
- 'docs/**' - 'scripts/mcp-server/**'
- 'parts/packages.nix'
- 'flake.nix'
- 'flake.lock'
- '.gitea/workflows/mcp-tests.yml' - '.gitea/workflows/mcp-tests.yml'
jobs: jobs:
mcp-tests: mcp-tests:
runs-on: nixos runs-on: nixos
defaults:
run:
working-directory: ${{ github.workspace }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Run MCP lint/format/tests via nix-shell - name: Run MCP lint/format/tests via nix-shell
run: ./scripts/mcp-server/run-tests.sh run: |
nix run .#mcp-tests

View File

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

View File

@@ -7,7 +7,7 @@ Auto-generated from feature plans. Last updated: 2026-01-30
- None (in-memory tool definitions; filesystem access for repo interactions) (002-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) - Nix (flakes; nixpkgs 25.11) + nixpkgs, flake-parts, sops-nix (003-vps-image-migration)
- N/A (configuration repo) (003-vps-image-migration) - N/A (configuration repo) (003-vps-image-migration)
- Nix (flakes; nixpkgs 25.11) + NixOS modules, sops-nix, nginx, wireguard, openssh, iptables (004-vps-migration) - Nix (flakes; nixpkgs 25.11) + NixOS modules, sops-nix, nginx, wireguard, openssh, nftables (004-vps-migration)
- Files (configuration and secrets) (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)

View File

@@ -6,198 +6,72 @@
* Overview * Overview
This repository contains my personal NixOS configuration flake, managing This repository is a NixOS configuration flake with a local MCP server
multiple hosts with a modular approach. Designed the configuration for a (`nixos-mcp`) that helps Codex CLI work with the repos documentation and
self-hosted infrastructure with services and development environments. maintenance workflows. The README focuses on how to use the MCP server and
how the repo is structured.
* Architecture * MCP Server (nixos-mcp)
** Hosts ** What it is
- =workstation= :: Main development machine with GNOME desktop Local-only MCP server that exposes repo documentation helpers over stdio so
- =server= :: Primary server with containerized services Codex CLI can read the constitution, playbooks, and reference map without
- =miniserver= :: Secondary server for additional services manual navigation.
- =galaxy= :: Minimal configuration host
- =emacs= :: Development VM for Emacs configuration
** Key Features ** Tool Catalog
- Modular configuration system - =show-constitution= :: Open =docs/constitution.md=
- SOPS-based secrets management - =list-playbooks= :: List =docs/playbooks/=
- Container orchestration with Podman - =show-reference= :: Open =docs/reference/index.md=
- Automated builds and caching - =search-docs= :: Search the docs set
- Multi-language development environments - =list-mcp-tasks= :: Show MCP tasks from =specs/002-mcp-server/tasks.md=
- Self-hosted service stack - =sync-docs= :: Check tool catalog vs. docs anchors
* Quick Start ** Quick Start (new users)
** Prerequisites
- NixOS 23.05 or later
- SOPS configured with age keys
- SSH keys for remote builds
** Initial Setup
#+BEGIN_SRC bash #+BEGIN_SRC bash
# Clone the repository git clone <repository-url> /home/jawz/Development/NixOS # Enter the MCP dev shell (includes codex + nixos-mcp)
cd /home/jawz/Development/NixOS nix develop .#mcp
# Install dependencies nix flake update # Run the MCP server (stdio mode)
nixos-mcp
# Build and switch to configuration sudo nixos-rebuild switch --flake
.#<hostname>
#+END_SRC #+END_SRC
Configure Codex CLI to use a local stdio MCP endpoint and allowlist
=nixos-mcp= in =.codex/requirements.toml=. The dev shell sets
=CODEX_HOME=.codex= for a repo-local Codex config.
** Development Environment ** Reference
#+BEGIN_SRC bash Detailed invocation notes live in =docs/reference/mcp-server.md=.
# Enter development shell for specific language nix develop .#<language>
# Available languages: python, rust, go, haskell, javascript, julia, zig, sh, * Repository Structure
cc, nix
#+END_SRC
* Configuration Structure ** Docs and Guidance
- =docs/constitution.md= :: AI constitution (authoritative rules)
- =docs/reference/index.md= :: Reference map
- =docs/reference/mcp-server.md= :: MCP server reference
- =docs/playbooks/= :: Repeatable workflows
- =specs/002-mcp-server/= :: MCP feature plan + tasks
** Core Configuration ** MCP Server
- =config/base.nix= :: Common system configuration - =scripts/mcp-server/= :: Python MCP server implementation, tests, and CLI
- =config/jawz.nix= :: User and SSH configuration - =modules/dev/mcp.nix= :: Dev shell and optional global install
- =config/stylix.nix= :: Theming configuration
- =config/schemes.nix= :: Color scheme definitions
** Host Configurations ** Flake Parts
- =hosts/<hostname>/configuration.nix= :: Host-specific settings - =parts/core.nix= :: Shared library + base composition
- =hosts/<hostname>/hardware-configuration.nix= :: Hardware-specific config - =parts/hosts.nix= :: Host definitions
- =hosts/<hostname>/toggles.nix= :: Feature toggles - =parts/packages.nix= :: Package outputs
- =parts/devshells.nix= :: Dev shells (including MCP)
** Modules * Flake Inputs (high level)
- =modules/apps/= :: Application packages and configurations
- =modules/dev/= :: Development environment modules
- =modules/servers/= :: Self-hosted service configurations
- =modules/services/= :: System service configurations
- =modules/scripts/= :: Custom scripts and utilities
- =modules/shell/= :: Shell and terminal configurations
* Services ** Core Inputs
- =nixpkgs= (25.11) and =nixpkgs-small= (25.11-small)
** Core Services - =nixpkgs-unstable= (rolling)
- PostgreSQL 17 :: Database backend - =flake-parts= (structure)
- Nginx :: Reverse proxy and web server - =home-manager= (user configs)
- Podman :: Container runtime - =stylix= (theming)
- Syncthing :: File synchronization - =sops-nix= (secrets integration)
- WireGuard :: VPN connectivity - =nur=, =nix-gaming=, =hyprland= (extra packages)
- Content inputs: =wallpapers=, =fonts=, =qbit_manage=
** Self-Hosted Applications - Repo inputs: =jawz-scripts=, =prem2resolve=, =lidarr-mb-gap=
- Nextcloud :: File sharing and collaboration
- Gitea :: Git repository hosting
- Jellyfin :: Media server
- Plex :: Media streaming
- Sonarr/Radarr/Lidarr :: Media management
- Vaultwarden :: Password manager
- Homepage :: Service dashboard
- And more...
* Development
** Available Development Shells
The configuration provides development shells for my favorite programming
languages:
#+BEGIN_SRC bash
# Python development nix develop .#python
# Rust development nix develop .#rust
# Go development nix develop .#go
# JavaScript/Node.js development nix develop .#javascript
# Haskell development nix develop .#haskell
# Julia development nix develop .#julia
# Zig development nix develop .#zig
# Shell scripting nix develop .#sh
# C/C++ development nix develop .#cc
# Nix development nix develop .#nix
#+END_SRC
** Adding New Modules
1. Create module file in appropriate directory under =modules/=
2. Add module to =modules/modules.nix= if needed
3. Enable module in host configuration or toggles
** Adding New Hosts
1. Create host directory under =hosts/<hostname>/
2. Add =configuration.nix= and =hardware-configuration.nix=
3. Add host to =flake.nix= outputs
4. Create =toggles.nix= for feature management
* Secrets Management
** SOPS Configuration
Manage secrets using SOPS with age encryption:
- =secrets/secrets.yaml= :: Main secrets file
- =secrets/keys.yaml= :: SSH and encryption keys
- =secrets/env.yaml= :: Environment variables
- =secrets/wireguard.yaml= :: VPN configuration
- =secrets/certs.yaml= :: SSL certificates
** Adding New Secrets
#+BEGIN_SRC bash
# Edit secrets file sops secrets/secrets.yaml
# Add new secret sops -i -a 'new-secret: "value"' secrets/secrets.yaml
#+END_SRC
* CI/CD
** GitHub Actions
The repository includes automated workflows:
- =weekly-build-cache.yml= :: Weekly builds and cache updates
- =build-schemes.yml= :: Color scheme builds
** Build Cache
Builds are automatically cached using Atticd for faster rebuilds.
* Customization
** Theming
The configuration uses Stylix for theming. Define color schemes in
=config/schemes.nix= and can set them via the =config/stylix.nix= file.
** Adding New Services
1. Create service module in =modules/servers/=
2. Add service configuration
3. Enable service in host toggles
4. Add to homepage if needed
** Custom Scripts
Scripts are in =modules/scripts/= and toggle them per host.
* Troubleshooting
** Common Issues
*** Build Failures
- Check flake inputs are up to date: =nix flake update=
- Verify all required secrets are present
- Check host-specific configuration
*** Service Issues
- Check service status: =systemctl status <service>=
- View logs: =journalctl -u <service>=
- Verify firewall rules
*** Development Environment
- Rebuild development shell: =nix develop .#<language>=
- Check available packages: =nix search nixpkgs <package>=
** Getting Help
- Check NixOS documentation
- Review module documentation
- Check service-specific documentation - Check service-specific documentation
* Maintenance * Maintenance

View File

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

View File

@@ -116,5 +116,28 @@ in
base0F = "#dc143c"; # crimson base0F = "#dc143c"; # crimson
}; };
}; };
punk = mkScheme {
color = "red";
polarity = "light";
image = "${wallpapers}/punk.jpeg";
base16Scheme = {
base00 = "#f7f3ee";
base01 = "#efe6dc";
base02 = "#e1d3c6";
base03 = "#c8b2a0";
base04 = "#a18673";
base05 = "#6e5646";
base06 = "#3f2f28";
base07 = "#1a1512";
base08 = "#c7423a";
base09 = "#d28b61";
base0A = "#c9a24c";
base0B = "#7d8b6a";
base0C = "#5f8f8a";
base0D = "#4f6a86";
base0E = "#8a5b6a";
base0F = "#8b5a3c";
};
};
}; };
} }

View File

@@ -9,7 +9,7 @@ let
schemesFile = import ./schemes.nix { schemesFile = import ./schemes.nix {
inherit pkgs inputs; inherit pkgs inputs;
}; };
scheme = schemesFile.schemes.space; scheme = schemesFile.schemes.punk;
cfg = config.my.stylix; cfg = config.my.stylix;
gnomeEnabled = config.services.desktopManager.gnome.enable; gnomeEnabled = config.services.desktopManager.gnome.enable;
in in

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.
@@ -45,8 +46,8 @@ config.services = {
- VPS enrollment flow: The vps host generates its own key on first boot, then operators enroll the public key, re-encrypt secrets, and redeploy. Follow `docs/playbooks/enroll-vps.md`. - 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. Factories sit in `modules/factories/` and are imported explicitly; patch artifacts live at the repo root in `patches/`.
- 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`, `vps`. 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
- Precedence: This constitution is authoritative for AI. Human docs must be updated to match. If conflicts are found, align human docs to the constitution and log the resolution in `specs/001-ai-docs/research.md`. - Precedence: This constitution is authoritative for AI. Human docs must be updated to match. If conflicts are found, align human docs to the constitution and log the resolution in `specs/001-ai-docs/research.md`.

View File

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

View File

@@ -7,24 +7,26 @@
- 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)
- patches → `patches/` (patch artifacts referenced by modules)
- factories → `modules/factories/` (`mkserver.nix`, `mkscript.nix` shared helpers) - factories → `modules/factories/` (`mkserver.nix`, `mkscript.nix` shared helpers)
## Root Directories
- patches → `patches/` (patch artifacts referenced by modules)
## Auto-Import Rules ## Auto-Import Rules
- Source: `modules/modules.nix` uses `inputs.self.lib.autoImport` to load `.nix` files from module directories. - Source: `modules/modules.nix` uses `inputs.self.lib.autoImport` to load `.nix` files from module directories.
- Filter: Excludes `librewolf.nix`; all other `.nix` files in target dirs are loaded automatically. - Filter: Excludes `librewolf.nix`; all other `.nix` files in target dirs are loaded automatically.
- Implication: Place new modules in the correct category directory with a `.nix` filename; no manual import wiring required unless adding a new factory. - Implication: Place new modules in the correct category directory with a `.nix` filename; no manual import wiring required unless adding a new factory. Patch artifacts under `patches/` are not auto-imported.
## 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`, `vps`. - 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. - vps: Linode VPS image target, secure host with enrollment-based secrets.
@@ -32,8 +34,9 @@
## 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
@@ -46,7 +49,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).
@@ -59,7 +62,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) have corresponding entries and auto-import rules; `patches/` is documented as a root directory.
- 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

@@ -43,6 +43,7 @@
;;neotree ; a project drawer, like NERDTree for vim ;;neotree ; a project drawer, like NERDTree for vim
ophints ; highlight the region an operation acts on ophints ; highlight the region an operation acts on
(popup +defaults) ; tame sudden yet inevitable temporary windows (popup +defaults) ; tame sudden yet inevitable temporary windows
(smooth-scroll +interpolate) ; So smooth you won't believe it's not butter
;;tabs ; a tab bar for Emacs ;;tabs ; a tab bar for Emacs
(treemacs +lsp) ; a project drawer, like neotree but cooler (treemacs +lsp) ; a project drawer, like neotree but cooler
;;unicode ; extended unicode support for various languages ;;unicode ; extended unicode support for various languages
@@ -64,6 +65,7 @@
;;parinfer ; turn lisp into python, sort of ;;parinfer ; turn lisp into python, sort of
rotate-text ; cycle region at point between text candidates rotate-text ; cycle region at point between text candidates
snippets ; my elves. They type so I don't have to snippets ; my elves. They type so I don't have to
(whitespace +guess +trim) ; a butler for your whitespace
;;word-wrap ; soft wrapping with language-aware indent ;;word-wrap ; soft wrapping with language-aware indent
:emacs :emacs
@@ -71,6 +73,7 @@
electric ; smarter, keyword-based electric-indent electric ; smarter, keyword-based electric-indent
eww ; the internet is gross eww ; the internet is gross
ibuffer ; interactive buffer management ibuffer ; interactive buffer management
tramp ; remote files at your arthritic fingertips
(undo +tree) ; persistent, smarter undo for your inevitable mistakes (undo +tree) ; persistent, smarter undo for your inevitable mistakes
vc ; version-control and Emacs, sitting in a tree vc ; version-control and Emacs, sitting in a tree
@@ -96,21 +99,24 @@
;;ein ; tame Jupyter notebooks with emacs ;;ein ; tame Jupyter notebooks with emacs
(eval +overlay) ; run code, run (also, repls) (eval +overlay) ; run code, run (also, repls)
(lookup +dictionary + offline) ; navigate your code and its documentation (lookup +dictionary + offline) ; navigate your code and its documentation
llm ; when I said you needed friends, I didn't mean...
(lsp +peek) ; M-x vscode (lsp +peek) ; M-x vscode
magit ; a git porcelain for Emacs magit ; a git porcelain for Emacs
;;make ; run make tasks from Emacs ;;make ; run make tasks from Emacs
;;pass ; password manager for nerds ;;pass ; password manager for nerds
;;pdf ; pdf enhancements ;;pdf ; pdf enhancements
;;prodigy ; FIXME managing external services & code builders ;;prodigy ; FIXME managing external services & code builders
;;terraform ; infrastructure as code (terraform +lsp) ; infrastructure as code
tmux ; an API for interacting with tmux tmux ; an API for interacting with tmux
;; tree-sitter ; syntax and parsing, sitting in a tree... ;; tree-sitter ; syntax and parsing, sitting in a tree...
upload ; map local to remote projects via ssh/ftp upload ; map local to remote projects via ssh/ftp
:os :os
;;(:if (featurep :system 'macos) macos) ; improve compatibility with macOS tty ; improve the terminal Emacs experience ;;(:if (featurep :system 'macos) macos) ; improve compatibility with macOS tty ; improve the terminal Emacs experience
tty ; improve the terminal Emacs experience
:lang :lang
;;ada ; In strong typing we (blindly) trust
;;agda ; types of types of types of types... ;;agda ; types of types of types of types...
;;beancount ; mind the GAAP ;;beancount ; mind the GAAP
(cc +lsp) ; C > C++ == 1 (cc +lsp) ; C > C++ == 1
@@ -139,6 +145,7 @@
;;hy ; readability of scheme w/ speed of python ;;hy ; readability of scheme w/ speed of python
;;idris ; a language you can depend on ;;idris ; a language you can depend on
(json +lsp) ; At least it ain't XML (json +lsp) ; At least it ain't XML
;;janet ; Fun fact: Janet is me!
;;(java +lsp) ; the poster child for carpal tunnel syndrome ;;(java +lsp) ; the poster child for carpal tunnel syndrome
(javascript +lsp) ; all(hope(abandon(ye(who(enter(here)))))) (javascript +lsp) ; all(hope(abandon(ye(who(enter(here))))))
(julia +lsp) ; a better, faster MATLAB (julia +lsp) ; a better, faster MATLAB

View File

@@ -49,15 +49,9 @@
;; ...Or *all* packages (NOT RECOMMENDED; will likely break things) ;; ...Or *all* packages (NOT RECOMMENDED; will likely break things)
;(unpin! t) ;(unpin! t)
;; (package! nixos-options) ;; enable when migrating to nixos
;; (package! quick-preview) ;; preview files with sushi
;; (package! codeium :recipe (:host github :repo "Exafunction/codeium.el"))
(package! config-general-mode)
(package! dired-open) (package! dired-open)
(package! dired-subtree) (package! dired-subtree)
;; (package! doom-modeline-now-playing) (package! expand-region)
(package! ini-mode) (package! ini-mode)
(package! insert-esv) ;; bible passages (package! insert-esv) ;; bible passages
(package! olivetti) ;; writing mode centering text, looks like word (package! olivetti) ;; writing mode centering text, looks like word
@@ -68,11 +62,14 @@
(package! peep-dired) ;; kind of cool but never could make it work (package! peep-dired) ;; kind of cool but never could make it work
(package! php-cs-fixer) (package! php-cs-fixer)
(package! systemd) (package! systemd)
;; :recipe (:host github :repo "tecosaur/ox-chameleon"))
;; (package! 2048-game) ;; (package! 2048-game)
;; (package! academic-phrases) ;; (package! academic-phrases)
;; (package! caddyfile-mode) ;; (package! caddyfile-mode)
;; (package! clippy) ;; (package! clippy)
;; (package! codeium :recipe (:host github :repo "Exafunction/codeium.el"))
;; (package! crontab-mode) ;; crontab colors ;; (package! crontab-mode) ;; crontab colors
;; (package! doom-modeline-now-playing)
;; (package! evil-tutor) ;; vim tutorial ;; (package! evil-tutor) ;; vim tutorial
;; (package! ewal) ;; theme colors based on pywal ;; (package! ewal) ;; theme colors based on pywal
;; (package! ewal-doom-themes) ;; (package! ewal-doom-themes)
@@ -81,16 +78,14 @@
;; (package! flycheck-aspell) ;; (package! flycheck-aspell)
;; (package! ivy-posframe) ;; (package! ivy-posframe)
;; (package! mw-thesaurus) ;; (package! mw-thesaurus)
;; (package! nixos-options) ;; enable when migrating to nixos
;; (package! org-appear) ;; couldn't get it to work ;; (package! org-appear) ;; couldn't get it to work
;; (package! org-recur) ;; works but I want to keep org vanilla ;; (package! org-recur) ;; works but I want to keep org vanilla
;; (package! ox-chameleon ;; (package! ox-chameleon
;; :recipe (:host github :repo "tecosaur/ox-chameleon")) ;; (package! quick-preview) ;; preview files with sushi
;; (package! renpy) ;; (package! renpy)
;; (package! resize-window) ;; (package! resize-window)
;; (package! tldr) ;; (package! tldr)
;; (package! typit) ;; type speed test ;; (package! typit) ;; type speed test
;; (package! vimgolf) ;; vim puzzles ;; (package! vimgolf) ;; vim puzzles
;; (package! wc-mode) ;; displays character count of buffer ;; (package! wc-mode) ;; displays character count of buffer
(package! expand-region)
(package! gptel :recipe (:nonrecursive t))

62
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": {
@@ -885,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": {
@@ -901,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": {
@@ -917,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": {
@@ -960,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": {
@@ -1088,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": {
@@ -1122,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": {
@@ -1331,11 +1331,11 @@
"wallpapers": { "wallpapers": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1759463031, "lastModified": 1770517108,
"narHash": "sha256-9goyp+g9N4+9PA9V2QerTsxlVy9MA4LXufJaruj2MMs=", "narHash": "sha256-QYvx6j7r1ItxaaKN/wI0nGB6r5aWG46zfcoXCTOgK18=",
"ref": "refs/heads/main", "ref": "refs/heads/main",
"rev": "0212af5b70347f0721cfe88c25e1efb77b645a2d", "rev": "dcb86c4c592b8cf838a0fd6d139254619cbbd869",
"revCount": 2, "revCount": 3,
"type": "git", "type": "git",
"url": "https://git.lebubu.org/jawz/wallpapers.git" "url": "https://git.lebubu.org/jawz/wallpapers.git"
}, },

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,9 @@ 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-homelab
config.my.subnets.wg-friends
config.my.subnets.wg-guests
]; ];
persistentKeepalive = 25; persistentKeepalive = 25;
} }
@@ -116,8 +118,11 @@ 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 = [
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMvtTURGBtAFXxxfzMJVoNJrtWLykOloJ5XYjxGh1OUx"; config.my.ips.vps
"[${config.my.ips.vps}]:3456"
];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPp0wAuZXk96OyA/+2YpQalokS9lZdacjJqY9zN8IScP";
}; };
}; };
}; };

View File

@@ -4,7 +4,6 @@ let
mkEnabledIp = inputs.self.lib.mkEnabledIp config.my.ips.wg-server; mkEnabledIp = inputs.self.lib.mkEnabledIp config.my.ips.wg-server;
in in
{ {
mainServer = "vps";
emacs = { emacs = {
enable = true; enable = true;
users = "jawz"; users = "jawz";
@@ -68,7 +67,6 @@ in
"maloja" "maloja"
"mealie" "mealie"
"metube" "metube"
"microbin"
"multi-scrobbler" "multi-scrobbler"
"paperless" "paperless"
"plex" "plex"
@@ -78,18 +76,18 @@ in
"radarr" "radarr"
"sabnzbd" "sabnzbd"
"sonarr" "sonarr"
"yamtrack"
"stash" "stash"
"synapse" "synapse"
"syncplay" "syncplay"
"unpackerr" "unpackerr"
"yamtrack"
] ]
// enableList mkEnabledIp [ // enableList mkEnabledIp [
"audiobookshelf" "audiobookshelf"
"isso"
"keycloak" "keycloak"
"linkwarden" "linkwarden"
"oauth2-proxy" "oauth2-proxy"
"plausible"
"vaultwarden" "vaultwarden"
]; ];
} }

View File

@@ -1,41 +1,132 @@
{ {
config, config,
lib, lib,
inputs,
pkgs, pkgs,
inputs,
... ...
}: }:
let
externalInterface = config.my.interfaces.${config.networking.hostName};
wgInterface = "wg0";
ips = {
homeServer = config.my.ips.wg-server;
wgFriend1 = config.my.ips.wg-friend1;
wgGuest1 = config.my.ips.wg-guest1;
wgGuest2 = config.my.ips.wg-guest2;
};
subnets = {
wgFriends = config.my.subnets.wg-friends;
wgGuests = config.my.subnets.wg-guests;
wgHomelab = config.my.subnets.wg-homelab;
};
ports = {
giteaSsh = 22;
ssh = 3456;
web = [
80
443
];
wg = 51820;
syncthing = 22000;
synapseFederation = 8448;
};
portsStr = {
giteaSsh = toString ports.giteaSsh;
syncthing = toString ports.syncthing;
synapseFederation = toString ports.synapseFederation;
synapseClient = toString config.my.servers.synapse.port;
syncplay = toString config.my.servers.syncplay.port;
stash = toString config.my.servers.stash.port;
jellyfin = toString config.my.servers.jellyfin.port;
audiobookshelf = toString config.my.servers.audiobookshelf.port;
kavita = toString config.my.servers.kavita.port;
};
in
{ {
imports = [ imports = [
./hardware-configuration.nix ./hardware-configuration.nix
./nginx-nextcloud.nix
../../config/base.nix ../../config/base.nix
]; ];
my = import ./toggles.nix { inherit config inputs; } // { my =
secureHost = true; import ./toggles.nix {
users.nixremote = { inherit config inputs lib;
enable = true; }
authorizedKeys = inputs.self.lib.getSshKeys [ // {
"nixworkstation" secureHost = true;
"nixserver" users.nixremote = {
"nixminiserver" enable = true;
]; authorizedKeys = inputs.self.lib.getSshKeys [
}; "nixworkstation"
}; "nixserver"
environment.etc."iptables.rules".source = ../../iptables; "nixminiserver"
networking.firewall.enable = lib.mkForce false; ];
networking.nftables.enable = false; };
systemd.services.iptables-restore = {
description = "Apply iptables ruleset";
wantedBy = [ "multi-user.target" ];
after = [ "network-pre.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${pkgs.iptables}/bin/iptables-restore --wait /etc/iptables.rules";
}; };
sops.age = {
generateKey = true;
keyFile = "/var/lib/sops-nix/key.txt";
sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
}; };
image.modules.linode = { }; image.modules.linode = { };
networking.hostName = "vps"; 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 ${subnets.wgGuests} ip daddr ${ips.homeServer}/32 tcp dport { ${portsStr.stash}, ${portsStr.jellyfin}, ${portsStr.audiobookshelf}, ${portsStr.kavita} } accept
iifname "${wgInterface}" ip saddr ${subnets.wgGuests} ip daddr ${ips.homeServer}/32 icmp type echo-request accept
iifname "${wgInterface}" ip saddr ${subnets.wgHomelab} ip daddr ${ips.homeServer}/32 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 = [ security.sudo-rs.extraRules = [
{ {
users = [ "nixremote" ]; users = [ "nixremote" ];
@@ -47,29 +138,46 @@
]; ];
} }
]; ];
services.openssh.ports = [ 3456 ]; systemd.tmpfiles.rules = [
sops.age = { "d /var/www/html 2775 deploy www-data -"
generateKey = true; "d /var/www/html/portfolio 2775 deploy www-data -"
keyFile = "/var/lib/sops-nix/key.txt"; "d /var/www/html/blog 2775 deploy www-data -"
sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; "d /var/www/html/lidarr-mb-gap 2775 lidarr-reports lidarr-reports -"
];
services = {
smartd.enable = lib.mkForce false;
openssh.ports = [ ports.ssh ];
}; };
users = { users = {
groups = { groups = {
deploy = { }; deploy = { };
lidarr-reports = { }; lidarr-reports = { };
www-data = { };
}; };
users = { users = {
nginx = lib.mkIf config.my.secureHost {
extraGroups = [
"www-data"
"lidarr-reports"
];
};
deploy = { deploy = {
isSystemUser = true; isSystemUser = true;
group = "deploy"; group = "deploy";
home = "/var/lib/deploy";
createHome = true;
shell = pkgs.bashInteractive;
extraGroups = [ "www-data" ];
openssh.authorizedKeys.keyFiles = [ ../../secrets/ssh/ed25519_deploy.pub ]; openssh.authorizedKeys.keyFiles = [ ../../secrets/ssh/ed25519_deploy.pub ];
}; };
lidarr-reports = { lidarr-reports = {
isSystemUser = true; isSystemUser = true;
group = "lidarr-reports"; group = "lidarr-reports";
home = "/var/lib/lidarr-reports";
createHome = true;
shell = pkgs.bashInteractive;
openssh.authorizedKeys.keyFiles = [ ../../secrets/ssh/ed25519_lidarr-reports.pub ]; openssh.authorizedKeys.keyFiles = [ ../../secrets/ssh/ed25519_lidarr-reports.pub ];
}; };
}; };
}; };
environment.systemPackages = [ ];
} }

View File

@@ -1,5 +1,6 @@
{ {
lib, lib,
config,
modulesPath, modulesPath,
... ...
}: }:
@@ -9,7 +10,10 @@
kernelModules = [ ]; kernelModules = [ ];
extraModulePackages = [ ]; extraModulePackages = [ ];
kernelParams = [ "console=ttyS0,19200n8" ]; kernelParams = [ "console=ttyS0,19200n8" ];
kernel.sysctl."net.ipv4.conf.wg0.rp_filter" = 0; kernel.sysctl = {
"net.ipv4.ip_forward" = 1;
"net.ipv4.conf.wg0.rp_filter" = 0;
};
initrd.availableKernelModules = [ initrd.availableKernelModules = [
"virtio_pci" "virtio_pci"
"virtio_scsi" "virtio_scsi"
@@ -30,13 +34,17 @@
}; };
}; };
fileSystems."/" = { fileSystems."/" = {
device = "/dev/disk/by-uuid/f222513b-ded1-49fa-b591-20ce86a2fe7f"; device = lib.mkForce (
if config.my.build.baseImage then
"/dev/sda"
else
"/dev/disk/by-uuid/f222513b-ded1-49fa-b591-20ce86a2fe7f"
);
fsType = "ext4"; fsType = "ext4";
}; };
swapDevices = [ swapDevices = lib.mkMerge [
{ [ { device = "/dev/disk/by-uuid/f1408ea6-59a0-11ed-bc9d-525400000001"; } ]
device = "/dev/disk/by-uuid/f1408ea6-59a0-11ed-bc9d-525400000001"; (lib.mkIf config.my.build.baseImage [ { device = "/dev/sdb"; } ])
}
]; ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
} }

View File

@@ -0,0 +1,116 @@
{ config, lib, ... }:
let
cfg = config.my.servers.nextcloud;
in
{
config = lib.mkIf (cfg.enableProxy && config.my.enableProxy && config.my.secureHost) {
services.nginx.virtualHosts.${cfg.host} = {
forceSSL = true;
enableACME = true;
http2 = true;
default = true;
serverAliases = [ "cloud.rotehaare.art" ];
extraConfig = ''
index index.php index.html /index.php$request_uri;
add_header X-Content-Type-Options nosniff always;
add_header X-Robots-Tag "noindex, nofollow" always;
add_header X-Permitted-Cross-Domain-Policies none always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy no-referrer always;
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
'';
locations = {
"= /robots.txt" = {
priority = 100;
extraConfig = ''
allow all;
access_log off;
'';
};
"= /" = {
priority = 100;
proxyPass = cfg.local;
proxyWebsockets = true;
extraConfig = ''
if ( $http_user_agent ~ ^DavClnt ) {
return 302 /remote.php/webdav/$is_args$args;
}
'';
};
"= /.well-known/carddav" = {
priority = 210;
extraConfig = ''
return 301 /remote.php/dav/;
'';
};
"= /.well-known/caldav" = {
priority = 210;
extraConfig = ''
return 301 /remote.php/dav/;
'';
};
"~ ^/\\.well-known/(?!acme-challenge|pki-validation)" = {
priority = 210;
extraConfig = ''
return 301 /index.php$request_uri;
'';
};
"^~ /.well-known" = {
priority = 210;
extraConfig = ''
try_files $uri $uri/ =404;
'';
};
"~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)" = {
priority = 450;
extraConfig = ''
return 404;
'';
};
"~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = {
priority = 450;
extraConfig = ''
return 404;
'';
};
"~ \\.php(?:$|/)" = {
priority = 500;
proxyPass = cfg.local;
proxyWebsockets = true;
extraConfig = ''
rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode(_arm64)?\/proxy) /index.php$request_uri;
'';
};
"~ \\.(?:css|js|mjs|svg|gif|ico|jpg|jpeg|png|webp|wasm|tflite|map|html|ttf|bcmap|mp4|webm|ogg|flac)$" =
{
proxyPass = cfg.local;
extraConfig = ''
expires 6M;
access_log off;
'';
};
"~ ^\\/(?:updater|ocs-provider)(?:$|\\/)" = {
proxyPass = cfg.local;
extraConfig = ''
try_files $uri/ =404;
index index.php;
'';
};
"/remote" = {
priority = 1500;
extraConfig = ''
return 301 /remote.php$request_uri;
'';
};
"/" = {
priority = 1600;
proxyPass = cfg.local;
proxyWebsockets = true;
extraConfig = ''
try_files $uri $uri/ /index.php$request_uri;
'';
};
};
};
};
}

View File

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

126
iptables
View File

@@ -1,126 +0,0 @@
# Generated by iptables-save v1.8.11 (nf_tables) on Fri Jan 2 03:44:23 2026
*mangle
:PREROUTING ACCEPT [95853893:179831236298]
:INPUT ACCEPT [94316554:179510512585]
:FORWARD ACCEPT [1536524:320567864]
:OUTPUT ACCEPT [49857522:93072472240]
:POSTROUTING ACCEPT [51393797:93393029789]
COMMIT
# Completed on Fri Jan 2 03:44:23 2026
# Generated by iptables-save v1.8.11 (nf_tables) on Fri Jan 2 03:44:23 2026
*raw
:PREROUTING ACCEPT [95853893:179831236298]
:OUTPUT ACCEPT [49857522:93072472240]
COMMIT
# Completed on Fri Jan 2 03:44:23 2026
# Generated by iptables-save v1.8.11 (nf_tables) on Fri Jan 2 03:44:23 2026
*filter
:INPUT ACCEPT [94315678:179510353216]
:FORWARD ACCEPT [46534:2774394]
:OUTPUT ACCEPT [49857520:93072471971]
# --- Incoming (INPUT) rules for VPS itself ---
# Accept SSH on port 3456 (new SSH port)
# allow SSH to VPS
-A INPUT -p tcp --dport 3456 -m conntrack --ctstate NEW -j ACCEPT
# allow established connections (responses)
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# (Optionally, add other INPUT rules for any services the VPS itself runs, if any, like HTTP/HTTPS if needed)
# If a default DROP policy is desired on INPUT, or an explicit drop rule:
# -A INPUT -j DROP # (optional: lock down any other input)
# --- Forwarding (FORWARD) rules for VPN traffic ---
# allow return traffic for established sessions
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Syncthing between 10.8.0.2 and home server
# 10.8.0.2 -> 10.77.0.0 Syncthing
-A FORWARD -s 10.8.0.2/32 -d 10.77.0.2/32 -p tcp --dport 22000 -j ACCEPT
-A FORWARD -s 10.8.0.3/32 -d 10.77.0.2/32 -p tcp --dport 22000 -j ACCEPT
-A FORWARD -s 10.8.0.4/32 -d 10.77.0.2/32 -p tcp --dport 22000 -j ACCEPT
-A FORWARD -s 10.8.0.5/32 -d 10.77.0.2/32 -p tcp --dport 22000 -j ACCEPT
# home -> 10.8.0.0 Syncthing
-A FORWARD -s 10.77.0.2/32 -d 10.8.0.2/32 -p tcp --dport 22000 -j ACCEPT
-A FORWARD -s 10.77.0.2/32 -d 10.8.0.3/32 -p tcp --dport 22000 -j ACCEPT
-A FORWARD -s 10.77.0.2/32 -d 10.8.0.4/32 -p tcp --dport 22000 -j ACCEPT
-A FORWARD -s 10.77.0.2/32 -d 10.8.0.5/32 -p tcp --dport 22000 -j ACCEPT
# Matrix/Synapse access from 10.8 subnet to home server
# allow Matrix client port
-A FORWARD -s 10.8.0.0/24 -d 10.77.0.2/32 -p tcp --dport 8008 -j ACCEPT
# allow Matrix federation port
-A FORWARD -s 10.8.0.0/24 -d 10.77.0.2/32 -p tcp --dport 8448 -j ACCEPT
# allow TURN/other (if used)
-A FORWARD -s 10.8.0.0/24 -d 10.77.0.2/32 -p tcp --dport 8999 -j ACCEPT
# ICMP between 10.8 subnet and home
# ping home from 10.8 clients
-A FORWARD -s 10.8.0.0/24 -d 10.77.0.2/32 -p icmp -j ACCEPT
# ping 10.8 clients from home
-A FORWARD -s 10.77.0.2/32 -d 10.8.0.0/24 -p icmp -j ACCEPT
# New Friend's subnet (10.9) access rule
# allow new subnet to access port 9999 on home
-A FORWARD -s 10.9.0.2/24 -d 10.77.0.2/32 -p tcp --dport 9999 -j ACCEPT
# allow ping to home
-A FORWARD -s 10.9.0.2/24 -d 10.77.0.2/32 -p icmp -j ACCEPT
# allow ping reply from home
-A FORWARD -s 10.77.0.2/32 -d 10.9.0.2/24 -p icmp -j ACCEPT
# Allow VPN subnets to reach Internet (MASQUERADE will SNAT them)
# 10.8 clients to internet
-A FORWARD -s 10.8.0.0/24 -o eth0 -j ACCEPT
# 10.9 clients to internet
-A FORWARD -s 10.9.0.2/24 -o eth0 -j ACCEPT
# Drop all other traffic between these subnets and home or between subnets (isolation)
# drop any 10.8 -> home not allowed
-A FORWARD -s 10.8.0.0/24 -d 10.77.0.0/24 -j DROP
# drop any home -> 10.8 not allowed
-A FORWARD -s 10.77.0.0/24 -d 10.8.0.0/24 -j DROP
# drop any 10.9 -> home not allowed (except 9999/ping above)
-A FORWARD -s 10.9.0.0/24 -d 10.77.0.0/24 -j DROP
# drop any home -> 10.9 not allowed
-A FORWARD -s 10.77.0.0/24 -d 10.9.0.0/24 -j DROP
# drop 10.9 -> 10.8 (no client-to-client)
-A FORWARD -s 10.9.0.0/24 -d 10.8.0.0/24 -j DROP
# drop 10.8 -> 10.9
-A FORWARD -s 10.8.0.0/24 -d 10.9.0.0/24 -j DROP
COMMIT
*nat
:PREROUTING ACCEPT [3368888:178175988]
:INPUT ACCEPT [3348703:174454011]
:OUTPUT ACCEPT [30120:1902454]
:POSTROUTING ACCEPT [32339:2018208]
# Port forwarding (DNAT) rules:
# forward SSH (port 22) to home server
-A PREROUTING -p tcp --dport 22 -j DNAT --to-destination 10.77.0.2:22
# forward port 51412 to home (TCP)
-A PREROUTING -p tcp --dport 51412 -j DNAT --to-destination 10.77.0.2:51412
# forward port 51412 to home (UDP)
-A PREROUTING -p udp --dport 51412 -j DNAT --to-destination 10.77.0.2:51412
# (Remove the above 51412 rules if not used; keep 22 as its for Giteas SSH access)
# Masquerade (SNAT) rules:
# masquerade replies from home for SSH
-A POSTROUTING -d 10.77.0.2/32 -p tcp --dport 22 -j MASQUERADE
# masquerade replies for 51412 (TCP)
-A POSTROUTING -d 10.77.0.2/32 -p tcp --dport 51412 -j MASQUERADE
#masquerade replies for 51412 (UDP)
-A POSTROUTING -d 10.77.0.2/32 -p udp --dport 51412 -j MASQUERADE
# (If 51412 rules removed above, remove their masquerade lines too)
# NAT for 10.8.0.x clients to internet
-A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
# NAT for 10.9.0.x clients to internet
-A POSTROUTING -s 10.9.0.0/24 -o eth0 -j MASQUERADE
COMMIT

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
@@ -29,7 +30,7 @@ in
}; };
localhost6 = lib.mkOption { localhost6 = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "::1"; default = "[::1]";
description = "The localhost ipv6 address."; description = "The localhost ipv6 address.";
}; };
secureHost = lib.mkOption { secureHost = lib.mkOption {
@@ -49,19 +50,39 @@ 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.33.0.228"; vps = "45.79.25.87";
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-galaxy = "10.77.0.3";
wg-gs = "10.9.0.0"; wg-phone = "10.77.0.4";
wg-guest1 = "10.9.0.2";
wg-guest2 = "10.9.0.3";
wg-friend1 = "10.8.0.2"; wg-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"; wg-friend5 = "10.8.0.6";
}; };
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 = {
@@ -109,6 +130,11 @@ in
}; };
enableContainers = lib.mkEnableOption "container services (Docker/Podman)"; enableContainers = lib.mkEnableOption "container services (Docker/Podman)";
enableProxy = lib.mkEnableOption "nginx reverse proxy for services"; enableProxy = lib.mkEnableOption "nginx reverse proxy for services";
build.baseImage = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to enable base image settings for this host build.";
};
toggleUsers = lib.mkOption { toggleUsers = lib.mkOption {
type = lib.types.attrsOf (lib.types.either lib.types.str (lib.types.listOf lib.types.str)); type = lib.types.attrsOf (lib.types.either lib.types.str (lib.types.listOf lib.types.str));
default = { default = {
@@ -196,7 +222,6 @@ in
config.my.servers.go-vod.enable config.my.servers.go-vod.enable
config.my.servers.tranga.enable config.my.servers.tranga.enable
config.my.servers.drpp.enable config.my.servers.drpp.enable
config.my.servers.plex-discord-bot.enable
]); ]);
message = "Container services are enabled but enableContainers is false"; message = "Container services are enabled but enableContainers is false";
} }

View File

@@ -16,7 +16,6 @@ let
"drpp" "drpp"
"metube" "metube"
"multi-scrobbler" "multi-scrobbler"
"plex-discord-bot"
]; ];
nativeServicesWithOpenFirewall = inputs.self.lib.getServicesWithNativeFirewall config firewallBlacklist; nativeServicesWithOpenFirewall = inputs.self.lib.getServicesWithNativeFirewall config firewallBlacklist;
servicesConfig = lib.listToAttrs ( servicesConfig = lib.listToAttrs (

View File

@@ -5,25 +5,6 @@
... ...
}: }:
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"
@@ -41,8 +22,7 @@ let
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

@@ -21,7 +21,7 @@ in
virtualisation.oci-containers.containers = lib.mkIf enable { virtualisation.oci-containers.containers = lib.mkIf enable {
flame = lib.mkIf cfg.enable { flame = lib.mkIf cfg.enable {
autoStart = true; autoStart = true;
image = "pawelmalak/flame"; image = "pawelmalak/flame:latest";
ports = [ "${toString cfg.port}:${toString cfg.port}" ]; ports = [ "${toString cfg.port}:${toString cfg.port}" ];
volumes = [ volumes = [
"${config.my.containerData}/flame:/app/data" "${config.my.containerData}/flame:/app/data"
@@ -36,7 +36,7 @@ in
}; };
flame-nsfw = lib.mkIf cfgS.enable { flame-nsfw = lib.mkIf cfgS.enable {
autoStart = true; autoStart = true;
image = "pawelmalak/flame"; image = "pawelmalak/flame:latest";
ports = [ "${toString cfgS.port}:${toString cfg.port}" ]; ports = [ "${toString cfgS.port}:${toString cfg.port}" ];
volumes = [ "${config.my.containerData}/flame-nsfw:/app/data" ]; volumes = [ "${config.my.containerData}/flame-nsfw:/app/data" ];
environmentFiles = [ config.sops.secrets.flame.path ]; environmentFiles = [ config.sops.secrets.flame.path ];

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 (cfg.enable && config.my.secureHost) { config = lib.mkMerge [
sops.secrets = { (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 = { ];
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

@@ -11,7 +11,7 @@ in
options.my.servers.lidarr = setup.mkOptions "lidarr" "music" 8686; options.my.servers.lidarr = setup.mkOptions "lidarr" "music" 8686;
config.virtualisation.oci-containers.containers.lidarr = lib.mkIf cfg.enable { config.virtualisation.oci-containers.containers.lidarr = lib.mkIf cfg.enable {
autoStart = true; autoStart = true;
image = "linuxserver/lidarr:version-3.0.1.4866"; image = "linuxserver/lidarr:latest";
ports = [ "${toString cfg.port}:${toString cfg.port}" ]; ports = [ "${toString cfg.port}:${toString cfg.port}" ];
environment = { environment = {
TZ = config.my.timeZone; TZ = config.my.timeZone;

View File

@@ -12,7 +12,7 @@ in
config = lib.mkIf (cfg.enable && config.my.secureHost) { config = lib.mkIf (cfg.enable && config.my.secureHost) {
sops.secrets.maloja.sopsFile = ../../secrets/env.yaml; sops.secrets.maloja.sopsFile = ../../secrets/env.yaml;
virtualisation.oci-containers.containers.maloja = { virtualisation.oci-containers.containers.maloja = {
image = "krateng/maloja:3.2.4"; image = "krateng/maloja:latest";
ports = [ "${toString cfg.port}:${toString cfg.port}" ]; ports = [ "${toString cfg.port}:${toString cfg.port}" ];
environmentFiles = [ config.sops.secrets.maloja.path ]; environmentFiles = [ config.sops.secrets.maloja.path ];
environment = { environment = {

View File

@@ -10,7 +10,7 @@ in
{ {
options.my.servers.metube = setup.mkOptions "metube" "bajameesta" 8881; options.my.servers.metube = setup.mkOptions "metube" "bajameesta" 8881;
config.virtualisation.oci-containers.containers.metube = lib.mkIf cfg.enable { config.virtualisation.oci-containers.containers.metube = lib.mkIf cfg.enable {
image = "ghcr.io/alexta69/metube:2026.01.02"; image = "ghcr.io/alexta69/metube:latest";
ports = [ "${toString cfg.port}:8081" ]; ports = [ "${toString cfg.port}:8081" ];
volumes = [ volumes = [
"${config.my.containerData}/metube:/downloads" "${config.my.containerData}/metube:/downloads"

View File

@@ -12,7 +12,7 @@ in
config = lib.mkIf (cfg.enable && config.my.secureHost) { config = lib.mkIf (cfg.enable && config.my.secureHost) {
sops.secrets.multi-scrobbler.sopsFile = ../../secrets/env.yaml; sops.secrets.multi-scrobbler.sopsFile = ../../secrets/env.yaml;
virtualisation.oci-containers.containers.multi-scrobbler = { virtualisation.oci-containers.containers.multi-scrobbler = {
image = "foxxmd/multi-scrobbler:0.10.0"; image = "foxxmd/multi-scrobbler:latest";
ports = [ "${toString cfg.port}:${toString cfg.port}" ]; ports = [ "${toString cfg.port}:${toString cfg.port}" ];
environmentFiles = [ config.sops.secrets.multi-scrobbler.path ]; environmentFiles = [ config.sops.secrets.multi-scrobbler.path ];
environment = { environment = {

View File

@@ -38,281 +38,272 @@ 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
serverAliases = [ "cloud.rotehaare.art" ];
extraConfig = ''
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
'';
locations = {
"/".proxyWebsockets = true;
"~ ^/nextcloud/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|oc[ms]-provider/.+|.+/richdocumentscode/proxy).php(?:$|/)" =
{ };
};
}; };
"${cfgC.host}" = lib.mkIf cfgC.enableProxy { };
forceSSL = true; virtualisation.oci-containers.containers = {
enableACME = true; go-vod = lib.mkIf config.my.servers.go-vod.enable {
http2 = true; autoStart = true;
locations = { image = "radialapps/go-vod:latest";
# static files environment = {
"^~ /browser" = { TZ = config.my.timeZone;
proxyPass = cfgC.local; NEXTCLOUD_HOST = "https://${config.services.nextcloud.hostName}";
extraConfig = commonProxyConfig; NVIDIA_VISIBLE_DEVICES = "all";
}; };
# Legacy static files (for compatibility) volumes = [ "ncdata:/var/www/html:ro" ];
"^~ /loleaflet" = { extraOptions = [
proxyPass = cfgC.local; "--device=/dev/dri" # VA-API (omit for NVENC)
extraConfig = commonProxyConfig; ];
}; };
# WOPI discovery URL collabora = lib.mkIf cfgC.enable {
"^~ /hosting/discovery" = { autoStart = true;
proxyPass = cfgC.local; image = "collabora/code:latest";
extraConfig = commonProxyConfig; ports = [ "${toString cfgC.port}:${toString cfgC.port}" ];
}; environment = {
# Capabilities TZ = config.my.timeZone;
"^~ /hosting/capabilities" = { domain = cfg.host;
proxyPass = cfgC.local; aliasgroup1 = "${cfg.url}:443";
extraConfig = commonProxyConfig; aliasgroup2 = "https://cloud.rotehaare.art:443";
}; server_name = cfgC.host;
# download, presentation, image upload and websocket dictionaries = "en_CA en_US es_MX es_ES fr_FR it pt_BR ru";
"~ ^/cool" = { extra_params = ''
proxyPass = cfgC.local; --o:ssl.enable=false
extraConfig = commonWebsocketConfig; --o:ssl.termination=true
}; --o:remote_font_config.url=${cfg.url}/apps/richdocuments/settings/fonts.json
# Legacy websocket (for compatibility) --o:logging.level=information
"~ ^/lool" = { '';
proxyPass = cfgC.local; DONT_GEN_SSL_CERT = "1";
extraConfig = commonWebsocketConfig; SLEEPFORDEBUGGER = "0";
}; };
# Admin Console websocket extraOptions = [
"^~ /cool/adminws" = { "--cap-add"
proxyPass = cfgC.local; "MKNOD"
extraConfig = commonWebsocketConfig; ];
}; };
# Legacy Admin Console websocket (for compatibility) };
"^~ /lool/adminws" = { systemd = lib.mkIf cfg.enableCron {
proxyPass = cfgC.local; services = {
extraConfig = commonWebsocketConfig; 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";
}; };
}; };
}; };
}; })
virtualisation.oci-containers.containers = { (lib.mkIf (cfgC.enableProxy && config.my.enableProxy) {
go-vod = lib.mkIf config.my.servers.go-vod.enable { services.nginx.virtualHosts.${cfgC.host} = {
autoStart = true; forceSSL = true;
image = "radialapps/go-vod"; enableACME = true;
environment = { http2 = true;
TZ = config.my.timeZone; locations = {
NEXTCLOUD_HOST = "https://${config.services.nextcloud.hostName}"; # static files
NVIDIA_VISIBLE_DEVICES = "all"; "^~ /browser" = {
}; proxyPass = cfgC.local;
volumes = [ "ncdata:/var/www/html:ro" ]; extraConfig = commonProxyConfig;
extraOptions = [ };
"--device=/dev/dri" # VA-API (omit for NVENC) # Legacy static files (for compatibility)
]; "^~ /loleaflet" = {
}; proxyPass = cfgC.local;
collabora = lib.mkIf cfgC.enable { extraConfig = commonProxyConfig;
autoStart = true; };
image = "collabora/code:latest"; # WOPI discovery URL
ports = [ "9980:9980" ]; "^~ /hosting/discovery" = {
environment = { proxyPass = cfgC.local;
TZ = config.my.timeZone; extraConfig = commonProxyConfig;
domain = cfg.host; };
aliasgroup1 = "${cfg.url}:443"; # Capabilities
aliasgroup2 = "https://cloud.rotehaare.art:443"; "^~ /hosting/capabilities" = {
server_name = cfgC.host; proxyPass = cfgC.local;
dictionaries = "en_CA en_US es_MX es_ES fr_FR it pt_BR ru"; extraConfig = commonProxyConfig;
extra_params = '' };
--o:ssl.enable=false # download, presentation, image upload and websocket
--o:ssl.termination=true "~ ^/cool" = {
--o:remote_font_config.url=${cfg.url}/apps/richdocuments/settings/fonts.json proxyPass = cfgC.local;
--o:logging.level=information extraConfig = commonWebsocketConfig;
''; };
DONT_GEN_SSL_CERT = "1"; # Legacy websocket (for compatibility)
SLEEPFORDEBUGGER = "0"; "~ ^/lool" = {
}; proxyPass = cfgC.local;
extraOptions = [ extraConfig = commonWebsocketConfig;
"--cap-add" };
"MKNOD" # Admin Console websocket
]; "^~ /cool/adminws" = {
}; proxyPass = cfgC.local;
}; extraConfig = commonWebsocketConfig;
systemd = lib.mkIf cfg.enableCron { };
services = { # Legacy Admin Console websocket (for compatibility)
nextcloud-cron.path = [ pkgs.perl ]; "^~ /lool/adminws" = {
nextcloud-cronjob = proxyPass = cfgC.local;
let extraConfig = commonWebsocketConfig;
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

@@ -1,24 +0,0 @@
{
lib,
config,
...
}:
let
setup = import ../factories/mkserver.nix { inherit lib config; };
cfg = config.my.servers.plex-discord-bot;
name = "plex-discord-bot";
in
{
options.my.servers.plex-discord-bot = setup.mkOptions name name 0;
config.virtualisation.oci-containers.containers.plex-discord-bot = lib.mkIf cfg.enable {
image = "ghcr.io/phin05/discord-rich-presence-plex:latest";
environment = {
DRPP_UID = toString config.users.users.jawz.uid;
DRPP_GID = toString config.users.groups.users.gid;
};
volumes = [
"${config.my.containerData}/drpp:/app/data"
"/run/user/${toString config.users.users.jawz.uid}:/run/app"
];
};
}

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

@@ -9,27 +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 [
sops.secrets = lib.mkIf cfg.enable { (lib.mkIf cfg.enable {
"private-ca/pem" = { users.users.prowlarr = {
uid = 987;
group = "piracy";
isSystemUser = true;
};
services = {
prowlarr = {
inherit (cfg) enable;
};
flaresolverr = {
inherit (cfg) enable;
};
};
})
(lib.mkIf (cfg.enableProxy && config.my.enableProxy && config.my.secureHost) {
sops.secrets."private-ca/pem" = {
sopsFile = ../../secrets/certs.yaml; sopsFile = ../../secrets/certs.yaml;
owner = "nginx"; owner = "nginx";
group = "nginx"; group = "nginx";
}; };
}; my.servers.prowlarr.certPath = config.sops.secrets."private-ca/pem".path;
my.servers.prowlarr.certPath = config.sops.secrets."private-ca/pem".path; })
users.users.prowlarr = { ];
uid = 987;
group = "piracy";
isSystemUser = true;
};
services = {
prowlarr = {
inherit (cfg) enable;
};
flaresolverr = {
inherit (cfg) enable;
};
};
};
} }

View File

@@ -29,52 +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;
"private-ca/pem" = { "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/";
}
];
};
};
systemd.services.stash = {
environment = {
PYTHONPATH = "/var/lib/stash/venv/lib/python3.12/site-packages";
LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib:${pkgs.glibc}/lib:${pkgs.zlib}/lib:${pkgs.libffi}/lib:${pkgs.openssl}/lib";
};
serviceConfig = {
PrivateUsers = lib.mkForce false;
BindReadOnlyPaths = lib.mkForce [ ];
BindPaths = lib.mkIf (cfgS.settings != { }) (map (stash: "${stash.path}") cfgS.settings.stash);
};
};
users.users.stash = {
uid = 974;
isSystemUser = true;
group = "glue";
packages = [ stashPythonFHS ];
};
})
(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.stash.certPath = config.sops.secrets."private-ca/pem".path;
my.servers.stash.certPath = config.sops.secrets."private-ca/pem".path; })
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/";
}
];
};
};
systemd.services.stash = {
environment = {
PYTHONPATH = "/var/lib/stash/venv/lib/python3.12/site-packages";
LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib:${pkgs.glibc}/lib:${pkgs.zlib}/lib:${pkgs.libffi}/lib:${pkgs.openssl}/lib";
};
serviceConfig = {
PrivateUsers = lib.mkForce false;
BindReadOnlyPaths = lib.mkForce [ ];
BindPaths = lib.mkIf (cfgS.settings != { }) (map (stash: "${stash.path}") cfgS.settings.stash);
};
};
users.users.stash = {
uid = 974;
isSystemUser = true;
group = "glue";
packages = [ stashPythonFHS ];
};
};
} }

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
@@ -80,7 +75,6 @@ in
inherit (cfg) port; inherit (cfg) port;
bind_addresses = [ bind_addresses = [
config.my.localhost config.my.localhost
config.my.localhost6
config.my.ips.server config.my.ips.server
config.my.ips.wg-server config.my.ips.wg-server
]; ];
@@ -100,7 +94,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 +130,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

@@ -12,9 +12,9 @@ in
config = lib.mkIf (cfg.enable && config.my.servers.postgres.enable && config.my.secureHost) { config = lib.mkIf (cfg.enable && config.my.servers.postgres.enable && config.my.secureHost) {
sops.secrets.yamtrack.sopsFile = ../../secrets/env.yaml; sops.secrets.yamtrack.sopsFile = ../../secrets/env.yaml;
virtualisation.oci-containers.containers = { virtualisation.oci-containers.containers = {
yamtrack-redis.image = "redis:7-alpine"; yamtrack-redis.image = "redis:latest";
yamtrack = { yamtrack = {
image = "ghcr.io/fuzzygrim/yamtrack"; image = "ghcr.io/fuzzygrim/yamtrack:latest";
ports = [ "${toString cfg.port}:8000" ]; ports = [ "${toString cfg.port}:8000" ];
dependsOn = [ "yamtrack-redis" ]; dependsOn = [ "yamtrack-redis" ];
environmentFiles = [ config.sops.secrets.yamtrack.path ]; environmentFiles = [ config.sops.secrets.yamtrack.path ];

View File

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

View File

@@ -5,7 +5,6 @@
}: }:
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";
@@ -13,16 +12,11 @@ in
sops.secrets."vps/server/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 = [ ips = [
"${config.my.ips.wg-vps}/24" config.my.wgInterfaces.wg-homelab
"10.8.0.1/24" config.my.wgInterfaces.wg-friends
"10.9.0.1/24" config.my.wgInterfaces.wg-guests
]; ];
listenPort = port; listenPort = port;
postSetup = ""; postSetup = "";
@@ -33,6 +27,14 @@ in
publicKey = "OUiqluRaS4hmGvLJ3csQrnIM3Zzet50gsqtTABaUkH4="; publicKey = "OUiqluRaS4hmGvLJ3csQrnIM3Zzet50gsqtTABaUkH4=";
allowedIPs = [ "${config.my.ips.wg-server}/32" ]; allowedIPs = [ "${config.my.ips.wg-server}/32" ];
} }
{
publicKey = "BwN4uCkMd6eAS5Ugld0oXnA16IhgEEQF8mOJ3+vHliA=";
allowedIPs = [ "${config.my.ips.wg-galaxy}/32" ];
}
{
publicKey = "R1xUFOuboQf/yy8ShiXqoCPaPcH3Cn0n4PAWB2rgHTs=";
allowedIPs = [ "${config.my.ips.wg-phone}/32" ];
}
{ {
publicKey = "rFgT6TXzRazK6GMazMNGjtOvzAAPST0LvCfN7QXsLho="; publicKey = "rFgT6TXzRazK6GMazMNGjtOvzAAPST0LvCfN7QXsLho=";
allowedIPs = [ "${config.my.ips.wg-friend1}/32" ]; allowedIPs = [ "${config.my.ips.wg-friend1}/32" ];
@@ -49,6 +51,18 @@ in
publicKey = "yg+2miZCrx89znFaUlU/le/7UIPgEAMY74fZfEwz8g4="; publicKey = "yg+2miZCrx89znFaUlU/le/7UIPgEAMY74fZfEwz8g4=";
allowedIPs = [ "${config.my.ips.wg-friend4}/32" ]; allowedIPs = [ "${config.my.ips.wg-friend4}/32" ];
} }
{
publicKey = "u4/6ZYO7lUJZ9QmSlFPUaadq25gwDljjhsfgs/p2amc=";
allowedIPs = [ "${config.my.ips.wg-friend5}/32" ];
}
{
publicKey = "GawtOvsZ75avelIri5CjGoPXd8AFpi9qlZ6dSsqUISE=";
allowedIPs = [ "${config.my.ips.wg-guest1}/32" ];
}
{
publicKey = "NvhUnErIb0/hi+Hui/o5l5Pq4ZysFVIn1VBPsjoTeCk=";
allowedIPs = [ "${config.my.ips.wg-guest2}/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,16 +196,10 @@ in
inherit ip; inherit ip;
}; };
}; };
mkEnabledProxy = name: {
inherit name;
value = {
enableProxy = true;
};
};
mkEnabledProxyIp = ip: name: { mkEnabledProxyIp = ip: name: {
inherit name; inherit name;
value = { value = {
enable = true; enableProxy = true;
inherit ip; inherit ip;
}; };
}; };
@@ -230,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

@@ -26,11 +26,36 @@
exec ${mcpPython}/bin/python -m mcp_server.server exec ${mcpPython}/bin/python -m mcp_server.server
''; '';
}; };
vpsLinodeConfig = inputs.self.nixosConfigurations.vps.extendModules {
modules = [
(
{ lib, ... }:
{
my.secureHost = lib.mkForce false;
my.build.baseImage = true;
}
)
];
};
mcpTests = pkgs.writeShellApplication {
name = "mcp-tests";
runtimeInputs = with pkgs.python3Packages; [
black
click
mypy
pytest
ruff
];
text = ''
exec bash ${inputs.self}/scripts/mcp-server/run-tests.sh "$@"
'';
};
in in
{ {
packages = (inputs.jawz-scripts.packages.${system} or { }) // { packages = (inputs.jawz-scripts.packages.${system} or { }) // {
emacs-vm = inputs.self.nixosConfigurations.emacs.config.system.build.vm; emacs-vm = inputs.self.nixosConfigurations.emacs.config.system.build.vm;
vps-linode = inputs.self.nixosConfigurations.vps.config.system.build.images.linode; vps-linode = vpsLinodeConfig.config.system.build.images.linode;
mcp-tests = mcpTests;
nixos-mcp = nixosMcp; nixos-mcp = nixosMcp;
nixos-mcp-server = mcpServerPkg; nixos-mcp-server = mcpServerPkg;
}; };

View File

@@ -6,6 +6,16 @@ set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$here" cd "$here"
cache_root="${TMPDIR:-/tmp}/mcp-tests-cache"
mkdir -p "$cache_root"
export HOME="${TMPDIR:-/tmp}/mcp-tests-home"
mkdir -p "$HOME"
export XDG_CACHE_HOME="$cache_root/xdg"
export RUFF_CACHE_DIR="$cache_root/ruff"
export MYPY_CACHE_DIR="$cache_root/mypy"
export PYTEST_ADDOPTS="${PYTEST_ADDOPTS:-} -o cache_dir=$cache_root/pytest"
export PYTHONDONTWRITEBYTECODE=1
fix=false fix=false
for arg in "$@"; do for arg in "$@"; do
if [ "$arg" = "--fix" ]; then if [ "$arg" = "--fix" ]; then

View File

@@ -22,38 +22,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37 - recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5VUMzYjZ5WlZtQ05LdnVt YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXdVVSeEtOTE1XQTBHVW5C
b3V3RmFyM0VZWmh4dC9YZFpsZkRIdC9TRzFrCnBuYnhSaUgwb3JuSUNFSWlwSmVq aUVVbUltOGMrV2l6VGhRQXFnbUR1NVpnYmgwCjEzWXB5SVBtbjBzMkx4OUhkUXll
bEoyQ09XSjNBMks3M2ZYdlh0eDFNYjAKLS0tIERpaGhISDFYd3RCYUV6Y0lmdGNQ b0FkcUl2b0d0YkEwQU9iNFZrcDJTV3MKLS0tIHNrY2JFbVEwNTFaWUdmdFJPZmJI
VTNibTBMN2RuN3doU3lYK1drNjVTVkkKMmRW0NtiYKBcUQ8kKjXcS6KjoPdVfN5d SnhZK1h2ejhQUUNtbzFINUJmNGhiYVkKCMeBiPt80A8/ynEWy2e881y1tVnqANK+
6vczsKTTbUwI0n6T5xrwRdbVIFsP4HisjceQWxJIVBthR0u9dLfXGw== wU9Bn+oRwoudPb1io9LAoTdu7+IQpLByt1phAju8m243nM48hAkipA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvb1ZtMjV5TjlhMVRwdWNU YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhUmQzMU9hTlpQbHBQMm9R
ME93Y0xhVGlxdGxmeWtXQ09EN3lORlJpV3k0CkJxdE14YXpwcytjbnZuMWpHVzZ3 Y0d5Z0lGTkFvYnc5MkRDbjNTMlUxWmI0U2t3CkdRQzdGTTNjbmprWEdYc0Nkckpr
dVVBYVE0RW1naWVVQ0JRY0NoWG9LZTQKLS0tIG1udE1GbEtTQ2o3bGl0SW9NZmtF Z2xqYkhlcHlQNG0rRFVvVTFLdFQxWVUKLS0tIFNPS1o2UVZobU5xN0U2QnI4dXA5
OFNqTncyaHFUSzBNRzZiSTVBdkhFWVkK2v81N8c8cU1Ig9fQZOn0fltqO+Ej8Wtk WWR1MWNGMVIyTGFBZXFyZlhwM09qakUK8Q26phHWY9zN5j6ZxB7+kmSgmcukfgiv
D0nMQv2fbWp6YlyE17VYPgmhdEY6+Zstve6PlBG86iQE3LTAfjG3Uw== qAAzIGdgsvnUiFZCEJHD1D686C+ZxvakD4p9sA/zEIyeIBtKCq3lIA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJSVdUWXRUa2tHVGczelhu YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHR05GcVViZTY1Q0NaR1RG
UWk5RFl6azRJTkdxZGxvbWlnSDc3K3NlNlgwCjBRZEVta3RuNW1DZmo4RXJyTTNk SWZzYjlxQUttS0tXc1ZDbDljbXczd1gxL25ZCkM3TVJ5NzlIdUx1dTc5d3R4U3BY
cnpxTDRGL0kwQXJmc29LNE0wV01hUGsKLS0tIGgyTWZrOHVNTGExRWtYMzJ1aXhp RVBLazRRZ2F0anJZRXl3bFdsbXJhdVkKLS0tIFVXN21DQmtqZ2hIRi9FM2dtTTlw
cURNZXBtbnp2OUZDZDZKeEMrZlN0TEEKznlmLKFHYDm/hv3EPcHjT0A8r06GL7if aHQvOUt5UGhhR2ZXMVFOOEtKMDRtZmcK0ZX5pF08o+HLztgL1/LocDGIcOGPKqXe
tbuJei8aWWg+uuvCBTZjHqmPUyNR1ixt84vxy1HlwXVu3dYHcG0Wug== 9bRBkUtr5QuIxQEYj6NenhkeIxRoPxK9Re/Vsqpphv4NqKpzyOujcw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw - recipient: age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZYnlaTkd3LzFRbldWL3RZ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYcmxldjA2UllycEFyTnpW
N2ZneVIzMnRpVVNHVWRMdXJLdjQwSWFKS2pFCmlQZUZMbG03VFVuUXcxZ3NRWjVH c2dIa1NXYVJSU1lwb0EvckxQQ0J4ckhSeDFBCnBTZGRYUzdSK08xeWFmaUM3SEZ3
SHVPYzk5NGpkeUVSU1BmQnNuaWZnZFUKLS0tIFdQZEU1YnhHZWRIajNYWTYxMEwr bXJSUG1OVEU1T0Z5VGRqYUloa3k0RmsKLS0tIHdRcXE3Q2lLZTRvL3ZCSnZtSk1K
UVBjaDFtSWs1b29DR0R2WS9pSGh3OEkKmG34ldBy4s9nj3ng/HQr+gN0LHJCOPJ8 TU5Iby9qamRIcEUwc2dTdERFVmNreTAKh55E4KbM6WeFhVx3KDI/pYq+1vCNwDj6
EWhh7cTLSF9AmZKP0sBsj7I4hHhZlOn85bvTM9RDiRVOSz8VrObXHA== 6zfXWJvyD9Icn2ZgqpK30wyJ/R/DzmpTDR8AtujXHT6/Uikn7M1fig==
-----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

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

View File

@@ -5,38 +5,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37 - recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4R05sUnl0UFd3T3pzRm1y YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsNWNIek9yVUJIYlhSQmVZ
U3piNzlpTmpZeEhkeWJxRkMzRzBWRW9LQmtBCjRHTVg5ZlozUnpsVjhIK05xYjlz NENoS2dvRDVENit2bnVNQmJ3TVZGWTkwTFh3CnViekVsaTVFZ29iQXNXUTNPS2U0
c2dwbWVKWVNXWFhTWEtlUUFjVUw2RkkKLS0tIElaNXN2ZmROdHd4bWljM3FyMEh6 Z2JsWWN4T2tUckVJU0tpNWFaYXpGVTAKLS0tIFowcE1tZDdPREY0ZGVzYS8xNFFp
Szg3WTdrVlFmSUJ1S05xNXY5RlM1V1UK7YETep9hn49UqRUjbRv6oGFUT/8lRgXx elN4TnZjZUtGOGZ1c3FiU0h4YytLTmMK9wXfpIgMcPD4FpO5CNIXnJc0wJliB35g
5O5eGB1X8kPCY8zXiGWSzfo6X8O5659vWIvqjoY8nZxekgvsISS/WA== v4wiDb4zU4VFfWzdimSXjgZrI/ZIqB4Bx/PPi6SPhuT4oQ6LSH5sKw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpT1oraHpJb0NUQndMZW9l YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtSmsyR0N6SWMvQVl2RzRZ
YWZrOVBqOG1KME5PYS9YVE4zd2VQb0hRN1dJCmVqSzhkbU5DVmc4MFVnSnVYTi9V TUpXbm9oTmswdXViczVBOFBLL0lhUmV4WXhvCkdqRG5pcStUVDE4S3FSL0R2TTlG
RUR2UDNEK3JGOEFUWVoraGtqQVFFWkUKLS0tIDVRdU8rV3diVXNUQSsrKzlBdmFN Sm1aZTcrejRHdzU2dFozSUZucHFtUHcKLS0tIDJTSmU1dVhSeUxMSnd1NGlkR1RC
Q0x5QXdaOXRMc211TUhqTndQOXR6ODAKtJYiAeVTYPOpS+GykBDOLx1g3VloFo2P cVVIcy9QRFArUkdIM05neG5aM2EyM0UKSCIv0iU/X9bVoQCRxcQXwMbr0GE7MGkb
fDIkOCrINnAU4y07KPhGBxCV3/2cvOPhIgsd02XqxfZPCEU/cYdCgQ== pn420gXMiLFBE8OOhkHg7EEjuR3n9iB3f+pTgN5v6UkxZBmZ2Xr4yg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4dTIzeCttSGNVNmhPejdW YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlL3picmtSZE8vS0Jod1RV
ZFkySng2T2ZYdkRrRGRXQVpER1NJMW5XN2xVCkc0VTdsbXdLUkg5d29zZ3VmY0hH aGNlK2t6b3RTZWRPUmNCZXN0KytSRnRuMHdNCkd1VjU3NEhBOE9jN3gvblM2NnZF
U1cybHNob3VkdzRWbGt1bFhNeW9XN0EKLS0tIDdoc2cyaEIybjBHOU5tdVRsTWFZ TDRhVGh1Y21YM2J4WTZtenFHSDBBNncKLS0tIHZqcnpRbldWN2cxT1I0Qzgva3R5
TmdZTGNDOFovMDVPakF0WTdHaUpHeFUKl0ub1OOylE2JGJNpeReebiOaVdxbd0wv a2lsbG5SUFgvZXlHWUhOc0xQS1dxaHMKDMGQujRa0s4kjrQod11mn0otxO2Zl/bv
nvJD7tYYXI666Pi31OHttWhsHR+xkL8TU9Dd6uDs4QxIRQfwy/VxcA== kHG8ufANpJS5RfKNLMhAK4piUtr1o97471MSGA0ebZAUSK01fQBNfA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw - recipient: age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGUldWbldqTTEwMGF5RVFV YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4WlgxL2xMR1p4eG1Ca3FM
ZkZ0Y24ycU1hVDlaTnpGQW1SeFlzaXc2a1FrCmtrUkxLcjNsVHNXemd1cWJJdXI5 c1JOcDBhaHNlUjRNZjFhVm9CdkN6NEdqdGtnCnUwUFBtLzYvM1kvd1NVREdwL3B5
bDFxUThzSFptNWtXMlNqM09aeklUMTgKLS0tIHR5KzE3dStMTXlhUWhtUWUwSkY0 MVc1UEdaSTlhWXFadSsvbHNTaUZKcDgKLS0tIDc3RDg3ZUkvRVFvRWhBWDFHZjV1
ZldyVmtRVGppQ0d0SnN5Tld4cEtmQ28K1Yij+7OxQUpEsPt/GTnP+dhEErBH1HuL NGlweEtoVGdqT2J3UzNGaGt2RFM5eE0KUCFvcv39dFM0Vm6uDuntsnZyMq+LHfJW
pBFXqHLAwpqiEiiNhYnb0KVWeQnIqDo9WUnrbPavcWSrSkmCsszgxQ== Sts7AJwVIGTmOolImqoVTeKFYJZu5oeKZZNsEG+gvIZptxaR0jPtow==
-----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+IFgyNTUxOSBDa3NpNG5tenhqWVQ5RFAv YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWaEZKVnhKR3ZnalQ1dWZy
bjhhRWJWK0NFQVk0cGVpcVJGS3BFeWlSQWc0Ci9IT05mQTVWbmk3SFFpWE9KUnh6 THJ1Qi9EK2NHQXUxYlg1NWZqd1VuRUY0a1FNCkg0UmphaktuWklNVE85c1RuT3FV
bHhCSktlbzVUQm1lOHp3cVpiSHU3MDgKLS0tIGg1UU4vVVo0SXRwMjJsVUZEZkFC bUwrVzIxQ2JhZmNqdnR2SUlIcGNiazgKLS0tIEVDZk4rV2tTT0pLd3I2RXVIZ0pC
TC9Eb2JaVUFDSWRMYm5jR1BBa2lEamMK4V77WUVbMXcsw83FFdL2Rk30oR4cAkqQ RWJCbkxHMm1DRFJKZkRhRldiRFVFd2cKQvohCMbXDJzOKzfAN72/1S4CXj5d0bbK
kc8Z0+5kNJFUFilFb54dnWTOh27K7KZvU1qIdhG3X9fuMIHSuPnyTw== Ge+V8Ew9S4+UR39iLtQzs7lNYYCtDxNnayEm0V8LlVkgeEj2HnS0bA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDRVdYVTY3QzR2MFJPVW9j YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBheTV2MllUaFMzTWpiRnFw
SUtmTldMRCs0dTlJcGFoMDVnaWMyNlF4OHlnCjg0OFFrOERKRFZVMm9NREhBOGRs a3hHeFhyNHZGek9tWVhySnVLUzNDVzV2VUIwCitWLzJ1ZG4rVHRwZElOdTdXMmdX
dTEwc0NZUk9hOEtvNVJDRXl0TDhCaTQKLS0tIE5OWm5CNzc5SW9IdGFud1N6Vm1D ZG9RVktFQ0VvdDViSi92YzRIZXJYOHMKLS0tIDZkRHYrMmtyZUh6bE5KaldvMDY2
djhzM29HK0FIdXIvaGIrRXlOMisxaTgKVCAiniAmfqJuwwiUpcGAvoyqnUEZ9gOS MitQdXpDTkNxME1pV1BML1ZRRG1NaWsKUBHmQa1io7qNp+xkEmYsn7Q6XSpQ/566
SyhXMzv2cbomuOb0NiALRkd2up/uX0TVuz9wuBQvYYjJhqpFuSnbRg== KYVB7GMSyp5YYsJv1vA8tLHnavLDam4zMZ1t24dgk8pWOZpJ4T6T4g==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1UlM2MC90SEM5elBtNmpm YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkanhPTzY4WEhVLzFvbjhR
cnpTQVF0VEpLUDJPZkEyeGNuYnl0cEY4M0U0CjhOY25BcERjOThkbkVhNnJtaVpv emVmVmZIRmVDelpONmM1SUhuT2Zadi84aW5ZClRaVGwrbjlGYkZiOVR0V2RuVWR3
N00zOUZPWnNYaEtYMzlXZk56dGVPeGsKLS0tIDVDcGY5cG1ETHk4eXRFN1hVOXhV UVBlNG9RUlpUZlJ6VFhKRHQ3T1VMRVkKLS0tIGpUTzM5MVA1LzdsNW5IMGlZcitR
Y2xncFJuNUs5ZkhLSjJyc2pzdDZxbEkKn/8BtUXPQ0OdR35ZwiHWFB0AqaDtAlG7 aG9pR0RRd1NBalhweEdGNnNCVHFvb0UKIiFruo9rV/VD6XykanHIpbI6G6D3cGG/
N4Z7iztqiscuxn8G8VVVFdkQLBY3JcrXhxPYWK4xtJeEtpIMhegxeQ== ZGSxH1HD9qIVYDQ5LpBfUy/dZxRnpTiBJ7Veg/Siemz7kmChlmVDQA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw - recipient: age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzd1VYanVSZTd5elNlZ2NC YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4UWRTR2x6aWJEZ1p4aE1O
bnd0V0VZVmtrVDhBbW5KUHJDMUkrekVZeldRCjBNY3g1SkVKUzhRL2xsbjloUERi RGpSM0xVRVJpTDhVa0dhTWwwTVp1Z0NkTW1RCkxRVUhkMklZUVhDSEE0bkZHaFlu
UXM4T1A0a1V2eEFlQWlTQ2tDdFdaZ1kKLS0tIFFtNDZzbzYyaE5UT3R4eDJzNnU1 UGRvYURuMWwzOUtoTzV2V05kblRpNnMKLS0tIEgrYXBpYysvenRGMENNcTY5L0dk
RG9UbWM4YTVHcFpKblQwemNScDVteVEKA6fibq6Ozwrz/tg9Hrx4bH9LCadmW5fR a1pWRTkzSkZGaTVtbVc4VjdHdkpneGMKq+3Pd2dOJAnC/PKEYijWbk1vQSes3ykt
IkFalgD7nqew8KwS0keyKFk93i2p6sTDZPy2/t+WryMXBIc/y0iQ5Q== 1A88VIO2o/isCLr9643SVkZQ4WbISA4xvInG+peEdbja0oZNRQNU3w==
-----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

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

View File

@@ -19,38 +19,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37 - recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQTUEycms3ZkdMd3hpcXJz YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCWm1RTldOekErU3pxcEpP
R2pZZEc5STZ3dUdYbUdsSGJaRWI5TWNMK1RRCjVxR1pzY0ZVUmcwSjJFYktteWoz dWducG4vTGpuYkhlZDY2a1lLemRFaW9uNFRNCnNtRExLbVBXUVBXRjhhMW1NcjR2
YmlaVkFPRnZha3h5ckV1TVQyVWZKdGMKLS0tIFgvdWF5VEJwTTcwdXZ6SDRMU3BL dkR1MFBPdDhPMldaYzk2V2pYQjZWeHMKLS0tIFc3RDhLVXdtaC82RUpPWnVGdjc3
V2x6NlhyY0pmUVBsYmZITjArdjJRbEkKvzsJxs5EHR0uumwhZ36MhKuMS+WkogXU d2JyM043WFJSL0grR0FheldHdWFSTXMKxf4LZ1sKH+HKKCT4w8AmKk+DtVoSobtn
nSVRQoc5TClzYwShY1ltHK+LCl0DlB4xFoMiO4GWwH1TySKe/ywpUQ== 20acQeJsbuAng+/DIQccPSp//3+3YkfsBRfSGg90vQPNKzxxNmrY1Q==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnQytNaUs1M0hiYi8vdDUx YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmSTA4Yk5TbmIyVzJWb3h1
V1NtZ3VGNFVjRHRWUzliR3M3Q2Z6K3RWSHo4ClQ1RE1PeHJ4REpubVJHb0lJcGJ2 bEFsb0l0Uk5jVDdvL0hMdlBFUzBJdTA3bEZvCmRMRmdyM3lieDVGVDZ0bjRpSngz
SEFvT2YvNWhMc2lneWR5NmRYc2pzVE0KLS0tIGxkRWRRRTNtVDUzVXh2L0lEa3RK c0FqNjRWN29zdzFsRnhtcEhUeEtwb1EKLS0tIEkwcVdpWmhKZEVZM052WU16aXZi
YjFSUDJHUjFUeVBFbUlKOS8ya1ZhMW8KssRH3/XT1iCVgV+6Sh25Axp0c96aHtVX UjFxUlExazVhc1hkcmZuT0ZadG1pTmcKADLIwbz9KlPgTrs3kxeWEgKsfh9K9Qyp
/HXN3AwTm0GJZCQnZsVIIPtoCzhUZSza+bzGZIZODYtgtCIxtdzVSw== +PSLBc8OjORDBBqqRcFJ3D9paiqppegGAPKaZ9INCXVoWke+wEOL3g==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIbkZpZFJCY21IRkJjNkRB YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSM2h6Rkp3T1c3UTJXVGEw
UEdEVlZhRWhRb1ZDMjJtMmpkUmpnY3ZvMGlFCnBLcHlkMWNyMy8wenYwT2pmRTZL ZHJJNndrUGdtVnZNOWJobzZFb2U5d09LekJzCm5mSUd6V05BUUZpMm9US2JhRUNP
dWtiWFlaR1FrL21HQTFZM2N3a3BHYW8KLS0tIFlYZWVHb0VEeDU5NnRjbDk5M2po YnZ4U1RBSUdMaHJnd2ZGNVFyT2hKeWMKLS0tIGpzUHlVU0JMbitmSzNjOWdaRFFI
K0xRRFhua09DRE04WUd6NlZuQldFbEEK2OgiawCbCtbrk8l45QdjVu8+VNWbrl4i YlJoZUVoQWFHMEg4Umo3WDZHUVppQncKL8HtEF3+uI/qm8K/u7V7IlEv8Lt0QwQv
3U9iwek30JkQSZaWBXaCZlWLvbKNjIMpwTtxDOhxmu4DUh3Hx6In/g== SPzuq89L/aT7hK3LyB88B2pvAKE2Z1Kj/3Z3depQfujIQsulpIg1lA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw - recipient: age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAySjBmaC9rREpUQ3BvWWNU YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiNWFNMXVsdWJKYXJaMCtM
MWEvM3ZGb2RXZ0dMdWxLRTJCR2VSdyt5VUhBCjBvL3MxZ3pTaFQ4aGdZVnAxUmd3 NHRVeVV0d0dXQjhwTk9ZbkpINUxUTkNISXpzCjI3blp0bkZiM1pVcDBYaVkwaUVQ
YUtoZkhEV01TU0drRUdDaFZ5M2tZLzAKLS0tIHpBL3NwV2NhN0QwcHdwbFpQWlZn ZTRicDhmdXpybzI1SjZSdDAyYmR1eEUKLS0tIDdUTGdvQzFXMDBMemJUMTc3MURD
eUNjc2RPOUxLTGowTlRqN3lEdjRLU2cKTTEXmHyhnL/hZGDr8ONrmzdU6Or5xkKY S3FxRUI2eEg3bGs3Rjh2YXhiMnQ5eGcKAHlMDXwb1uULH+lLuWW4dMxofXSbKRMt
GHADDt+LCg8njcZom39Aj4kpCx+f7HlV65glKwr37vZ0sL9KE+O9+w== Ce/mfgDwqERw8h2yotOoSkNSFBQ2kPLu3/NeTsVAfbdSMyp/T3aJ5A==
-----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

@@ -14,38 +14,38 @@ sops:
- recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37 - recipient: age1lufn6t35gs4wgevyr2gud4eec7lvkn7pgnnv4tja64ww3hef7gqq8fas37
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlemJmbnAwUHZHT3ozdWxH YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXN25MZVQzTmora3o3YjJs
Njh1ZFUvVW8zcVV6SGxrVW1IWW9ZUFBaTEh3CnJsMnFnM0d5YnBKWE5CT2Flang0 aUptdGxJY2YxZU5XSjBjczFnTFVVdHVsRkM0CkNFN2JoelQva1ZucUxNNUJsVk9z
TkNZb0xCY2c4Qk1kdXRkRXcvOU1TSW8KLS0tIE1VdGEraW03bnV4VEc5c0ZheFJ0 cVZVU0MxL2Y3b2dNRnhJSzZrSVlaRWcKLS0tIG1vTHB1dHNWa0RLR1BRV0hFUVdx
MFJpVTlvTGJ0YXBKSnFFbXhEUEwwSmMKxOtHLbRw5e6dRW4jvqFLsl6UzKZ+mvfR blY1QTNhUGpKZ3EzRHNadStxaCtLb1kKtyXKpZGLtrUo1HE26IWhv8245Bjcwcqe
hwKJ4KEbXuCqwtPQEWk/pF0i4vzrgUP1Cp1Y7BxGGyK9ufyV/CCQIg== IR2WGv7qtnpWZoaFv76LNN7YY1JViy2k2AY+TdLmFQr0Vh2n5+tH1A==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz - recipient: age17jlsydpgl35qx5ahc3exu44jt8dfa63chymt6xqp9xx0r6dh347qpg55cz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5cnE5VENCMUxxOVZUdC9X YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZbHVXV2hVTWZlemdCQmI0
QWFMRytGamhaWENZY1Q4STR5L0Jsdk90SlUwCis4ekFWYmMwN2dESXMrVFNIamFG SGwwK0hDYjNkWGJMMTVuWnNMVW1Ebldsd0RRCmNoN2dZN2JiSEpzUzNwcjU3eFVv
RzhET2ZGdGN6b1V1ZHkyOCtDNzBWVjQKLS0tIEF1NGdoU2lqYVdIN3hwRk13SFpP QmNnWERpQVByYlRqUDQrWEF4bkRPQm8KLS0tIHNOaDhQZExuOVJIVXZGQVdFeGhQ
RHNOeDBlSHFpays2VkRuR2RxaGpYZ1EKwxZfRZthZHVuJe3D5pamCSxYo3hyaaVc QXRJRFlZWXJUVW9nVDhOaUFacjFlSzgKYSs6Woc/lAr2ECcrqoMCAwvIbXTpbtTr
I0UvMDMgcDRZuEzV9g1ZEYnaVXg5InyOO0dDZuCYX/HZqTLPiaOIxg== J4ljY3BRCdSzHEMS9IFV2j9nGu8sUrHRsO7V/Kc8i+XmTGZP76LRJA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5 - recipient: age15hx530yrqmhm80vsjmffyg9deq9gssj7hl5rsqdnsn3dwegj9qusv4sjf5
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGaUhOcHV2TkYrZWxnOCtI YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlRGwzbm9rY3hXeHZReWZW
TzF1RVpFY3pSa1Y2MmJjVlpKcWZnWGtOOTJ3CmRnTUpyRms2aUtvS1ZvVXFsb0ZQ VngyN3ZlZDJWekExNkNBdCtLT1J6d0ZYU0hrCi9RRWNYUyt3OGh1ZVNCNWI1U3Vn
U0RiYXM3S0RKQjVwL2hqYllhZENUdmsKLS0tIDNTRHR2ZU1VTzdNNXRDU0xkcTRM dnByZnVpYmV1RndKQUxHbUtrS0F5L3cKLS0tIDJONlcvMllKOWRxdE5ZWUZmaFEx
ckowd2p5bitGYVhMNU9Qc0NUeFFJV3MKPKT1/06/fKpWPOMsRaU/fpyVUf7onWGB S1JCM2x1WDF0Z3c0ODZNb3FKOGNhMlUKGP8P/PUcMM1c4VzXLjLNp/zThu8JCiyQ
0P22NBzP1i5caqSrFnVVeyuhgYxabC4oUKVmjU5QIj1R8Rqh7gworw== iHdz0LBSAha/m23b316z72yg3YD5q+/qDP8KczAv1SG+VvgHDKxpCg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1ml3smrs5mwz4ds84gk0eyss86nwsmp07qh0npxsuae7lfwwpsghssavytw - recipient: age1v2ahkl759cftpcdq4mla2cvmgz4jlnmgj7qtgc9732zxrfvxf3lq76zjpr
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3SHdHTDhKQzFUQVdqM0hW YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5Z3hyOGFJTlpxOWFQaWsx
Tm9QdVozaHViQVRuTExhV1BpdWYvY012enk0CmhjODlUN0FkNldGRG94bVFSTVBv WE9oYmJaaWxURjdmRlVJMUcxaUZKWDZCS1VJCmxWV01DRloyM3lLemJYc2FxcUdH
QUNWZmszRStZN24vZWhnajhIcWdXVDgKLS0tIG9ueVZsT29KRE1iM2oreWtGWGVC M1NZRGxjVUVEUExTWjFaazhRaDdCUXcKLS0tIFF6NW4vSGJSWjN3NHFlOXRUYXhM
SG40OS8wMHlKNmxQa0VScHQrU2NmT2sKt9xw/8jsgnV1cZndqYNiHvIf8VdEJYCl NXZzQmlneDNEb1UvR2NGK0kyY1lsa1kK7IQmyuVxa2hmic4yTeiAcxN41RvMcIDV
UUJ1KPz9mvUx3ny+rK50FSD61U8PHEZm2UC0w+/qkZwRtCx21Ku6dw== Pofrhu7q8VvB/Cxb7FjVs3Ed5Hdz9xQ60mXUKsnJV/rIssm9wx4cfg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-04T18:37:11Z" lastmodified: "2026-02-04T18:37:11Z"
mac: ENC[AES256_GCM,data:AlrMK34dWDm5hfVwnQnzk3l8NIRbiVV6KHa6io9S9l07WvC3TYLTOJS6xOi4pkEz6sqQ7IpZU7RRdosxuQp50NmMEt2QYawTHFZIgzFYeKRbl5N5LCu9afC6yTtvG/sT7uenTMhh2qT1JBwebJiUdM9zNVUzWlW5d1SdxrHgIbs=,iv:dvqsDaC+trhY1kheYUEOEwHfCDz0Mu7N0LpfjnKko5g=,tag:tuqyK8vuwSrk1kf+Vi7MKg==,type:str] mac: ENC[AES256_GCM,data:AlrMK34dWDm5hfVwnQnzk3l8NIRbiVV6KHa6io9S9l07WvC3TYLTOJS6xOi4pkEz6sqQ7IpZU7RRdosxuQp50NmMEt2QYawTHFZIgzFYeKRbl5N5LCu9afC6yTtvG/sT7uenTMhh2qT1JBwebJiUdM9zNVUzWlW5d1SdxrHgIbs=,iv:dvqsDaC+trhY1kheYUEOEwHfCDz0Mu7N0LpfjnKko5g=,tag:tuqyK8vuwSrk1kf+Vi7MKg==,type:str]

View File

@@ -14,7 +14,7 @@
### Reference Map ### Reference Map
- **Role**: Index mapping core concerns to repo paths for navigation and validation. - **Role**: Index mapping core concerns to repo paths for navigation and validation.
- **Key Fields**: category (apps, dev, scripts, servers, services, shell, network, users, nix, patches), hosts list (emacs, server, workstation, miniserver, galaxy), secrets files, proxy rules, auto-import rules, stylix/schemes, audit checklist entries, navigation links to constitution/playbooks. - **Key Fields**: category (apps, dev, scripts, servers, services, shell, network, users, nix), root paths (patches), hosts list (emacs, server, workstation, miniserver, galaxy, vps), secrets files, proxy rules, auto-import rules, stylix/schemes, audit checklist entries, navigation links to constitution/playbooks.
- **Relationships**: Anchors citations used by Constitution and Playbooks. - **Relationships**: Anchors citations used by Constitution and Playbooks.
## Constraints and States ## Constraints and States

View File

@@ -29,3 +29,8 @@
- **Decision**: Gate SOPS configuration behind `config.my.secureHost` so non-secure hosts skip secret loading. - **Decision**: Gate SOPS configuration behind `config.my.secureHost` so non-secure hosts skip secret loading.
- **Rationale**: Aligns `config/base.nix` behavior with the constitutions secureHost rules and avoids secret dependency on non-secure hosts. - **Rationale**: Aligns `config/base.nix` behavior with the constitutions secureHost rules and avoids secret dependency on non-secure hosts.
- **Alternatives considered**: (a) Leave SOPS enabled on all hosts (rejected: violates secureHost contract); (b) Duplicate SOPS logic per host (rejected: increases drift risk). - **Alternatives considered**: (a) Leave SOPS enabled on all hosts (rejected: violates secureHost contract); (b) Duplicate SOPS logic per host (rejected: increases drift risk).
## Decision 7 (2026-02-07): Module categories and patches location; active hosts update
- **Decision**: Treat `patches/` as a root-level directory (not a module category) and update active hosts to include `vps`.
- **Rationale**: Repo structure places patches at the root and hosts include `vps`; documentation must reflect actual paths and host inventory.
- **Alternatives considered**: (a) Move `patches/` under `modules/` (rejected: would change repo layout); (b) Keep `vps` undocumented (rejected: causes host list drift).

View File

@@ -89,6 +89,6 @@ An AI or contributor can update the constitution and use-case docs when repo rul
### Measurable Outcomes ### Measurable Outcomes
- **SC-001**: An AI with only these docs can describe the correct steps and file locations to add a new server module in under 2 minutes of reading time, matching existing patterns. - **SC-001**: An AI with only these docs can describe the correct steps and file locations to add a new server module in under 2 minutes of reading time, matching existing patterns.
- **SC-002**: The constitution explicitly enumerates 100% of current module categories (apps, dev, scripts, servers, services, shell, network, users, nix, patches) and active hosts (emacs, server, workstation) with their roles. - **SC-002**: The constitution explicitly enumerates 100% of current module categories (apps, dev, scripts, servers, services, shell, network, users, nix), documents the root `patches/` directory, and lists active hosts (emacs, server, workstation, miniserver, galaxy, vps) with their roles.
- **SC-003**: Guidance includes the full secrets file map (certs/env/gallery/homepage/keys/wireguard/secrets) and secureHost behavior with no omissions when audited against the repository. - **SC-003**: Guidance includes the full secrets file map (certs/env/gallery/homepage/keys/wireguard/secrets) and secureHost behavior with no omissions when audited against the repository.
- **SC-004**: Playbook locations and required fields are discoverable via the documented index in ≤2 navigation steps from the top of the spec. - **SC-004**: Playbook locations and required fields are discoverable via the documented index in ≤2 navigation steps from the top of the spec.

View File

@@ -14,7 +14,7 @@ paths:
description: Proxy mappings resolve to services on host server description: Proxy mappings resolve to services on host server
/verify/firewall: /verify/firewall:
get: get:
summary: Verify iptables ruleset parity summary: Verify nftables parity against the iptables reference
responses: responses:
"200": "200":
description: Firewall flows match expected allow/deny behavior description: Firewall flows match expected allow/deny behavior

View File

@@ -17,7 +17,7 @@
## FirewallRuleSet ## FirewallRuleSet
- **Fields**: sourceFile (iptables), rules[], appliedHost - **Fields**: sourceFile (iptables reference), rules[], appliedHost
- **Rules**: Ruleset must be applied as-is; no translation allowed. - **Rules**: Ruleset must be applied as-is; no translation allowed.
## VPNPeer ## VPNPeer

View File

@@ -5,18 +5,18 @@
## Summary ## Summary
Migrate VPS responsibilities to the new NixOS host by making it the primary reverse-proxy host (nginx only), mirroring the existing iptables ruleset, 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. 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 ## Technical Context
**Language/Version**: Nix (flakes; nixpkgs 25.11) **Language/Version**: Nix (flakes; nixpkgs 25.11)
**Primary Dependencies**: NixOS modules, sops-nix, nginx, wireguard, openssh, iptables **Primary Dependencies**: NixOS modules, sops-nix, nginx, wireguard, openssh, nftables (iptables reference)
**Storage**: Files (configuration and secrets) **Storage**: Files (configuration and secrets)
**Testing**: Manual validation steps (no automated test harness) **Testing**: Manual validation steps (no automated test harness)
**Target Platform**: Linux server (NixOS) **Target Platform**: Linux server (NixOS)
**Project Type**: configuration repo **Project Type**: configuration repo
**Performance Goals**: N/A (configuration change) **Performance Goals**: N/A (configuration change)
**Constraints**: Services remain on host server; VPS only terminates proxy and exposes wireguard port; iptables parity required **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 **Scale/Scope**: Single VPS + host server, small set of VPN peers and admin SSH principals
## Constitution Check ## Constitution Check
@@ -45,8 +45,8 @@ specs/004-vps-migration/
hosts/ hosts/
modules/ modules/
secrets/ secrets/
iptables iptables (reference ruleset)
scripts/ scripts/
``` ```
**Structure Decision**: Use the existing NixOS configuration layout (`hosts/`, `modules/`, `secrets/`) and the root `iptables` ruleset file. **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

@@ -3,7 +3,7 @@
## Prerequisites ## Prerequisites
- Access to this repo and the new VPS host configuration - Access to this repo and the new VPS host configuration
- Existing iptables ruleset file available at repo root: `iptables` - Existing iptables ruleset file available at repo root (reference for nftables parity): `iptables`
- VPN keys present in the secrets system - VPN keys present in the secrets system
- SSH public keys present in `secrets/ssh/` - SSH public keys present in `secrets/ssh/`
@@ -19,10 +19,10 @@
3. Update host configuration: 3. Update host configuration:
- Set new VPS as primary reverse proxy host - Set new VPS as primary reverse proxy host
- Enable proxying for all enabled services (services remain on host server) - Enable proxying for all enabled services (services remain on host server)
- Apply iptables ruleset as-is - Apply nftables/NixOS firewall rules derived from the iptables reference
- Enable wireguard on VPS and expose port - Enable wireguard on VPS and expose port
- Add service users and admin SSH keys - Add service users and admin SSH keys
- Update VPS public IP to `45.33.0.228` in SSH configuration - Update VPS public IP to `45.79.25.87` in SSH configuration
- Update host server VPN client to target the new VPS - Update host server VPN client to target the new VPS
4. Provide and review legacy proxy config snapshot: 4. Provide and review legacy proxy config snapshot:
@@ -60,9 +60,12 @@
- `mb-report.lebubu.org` and `torrent.lebubu.org` are present in caddy but no matching Nix server host was found. - `mb-report.lebubu.org` and `torrent.lebubu.org` are present in caddy but no matching Nix server host was found.
5. Migrate analytics data: 5. Migrate analytics data:
- Export data from existing server - Identify the analytics system (e.g., Plausible) and its data store location or database
- Import into new server - Freeze writes during export (stop the analytics service or enable maintenance mode)
- Validate historical data is present - 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). 6. Run verification steps for each task (per spec FR-012).
@@ -79,13 +82,13 @@
- **T002**: verify this section exists in `/home/jawz/Development/NixOS/specs/004-vps-migration/quickstart.md` - **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` - **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` - **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` - **T005**: `rg -n "vps|45.79.25.87|programs.ssh" config/jawz.nix modules/modules.nix`
- **T006**: `rg -n "/etc/caddy/Caddyfile.d" sudo_hist jawz_hist` - **T006**: `rg -n "/etc/caddy/Caddyfile.d" sudo_hist jawz_hist`
- **T007**: `rg -n 'mainServer = "vps"' hosts/server/toggles.nix modules/modules.nix` - **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` - **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 - **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` - **T010**: `rg -n "iqQCY4iAWO-ca/pem|certPath|proxyReversePrivate" modules/network/nginx.nix modules/servers`
- **T011**: `rg -n "iptables.rules|iptables-restore|networking.firewall.enable = false" hosts/vps/configuration.nix` - **T011**: `rg -n "nftables|forwardPorts|vps-snat" hosts/vps/configuration.nix`
- **T012**: `rg -n "services.wireguard.enable = true" hosts/vps/configuration.nix` - **T012**: `rg -n "services.wireguard.enable = true" hosts/vps/configuration.nix`
- **T013**: confirm `wireguard/private` exists in `secrets/wireguard.yaml` - **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` - **T014**: `rg -n "10.77.0.1/24|10.8.0.1/24|10.9.0.1/24|AllowedIPs|allowedIPs" modules/services/wireguard.nix`
@@ -97,7 +100,8 @@
- **T020**: `rg -n "45\\.33\\.0\\.228" modules/modules.nix config/jawz.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` - **T021**: `rg -n "endpoint = .*my\\.ips\\.vps" hosts/server/configuration.nix`
- **T022**: verify "Clarification Candidates From History Review" section exists in this file - **T022**: verify "Clarification Candidates From History Review" section exists in this file
- **T023**: intentionally skipped by operator for this implementation pass - **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 - **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 - **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 - **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

@@ -8,7 +8,7 @@
## Decision 2: Firewall parity ## Decision 2: Firewall parity
- **Decision**: Apply the existing iptables ruleset as-is on the new VPS. - **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. - **Rationale**: Ensures exact behavioral parity for complex routing and hot-swap behavior.
- **Alternatives considered**: Translating to another firewall system; partial translation with mixed rules. - **Alternatives considered**: Translating to another firewall system; partial translation with mixed rules.

View File

@@ -3,7 +3,7 @@
**Feature Branch**: `004-vps-migration` **Feature Branch**: `004-vps-migration`
**Created**: 2026-02-04 **Created**: 2026-02-04
**Status**: Draft **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, 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. **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 4. modify the existing wireguard.nix module, doublecheck that isnt toggled anywhere, toggle it on vps and add this configuration to it
[Interface] [Interface]
@@ -46,7 +46,7 @@ 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 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 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. 6. similar to every other host, add ssh login authorized_keys for workstation, server, deacero, galaxy and check if Im missing one. Because this will replace the ssh vps on the ssh config, you need to replace the existing vps ip with 45.79.25.87. 7. change the configuration on the host server, so that its wireguard session, connects to this server (i think will ve done automagically when the ip changes right?) 8. Ive added sudo_hist and jawz_hist, which are a dump of the histfile of this server, just check if there is a configuration that Im missing, something I did on there that I missed, and add it to the clarification list, so when I run clarify I tell you if I want that or not, granted lots of those commands are trial and error, so I think I have everything. 9. I have setup a plausible server, write the steps necesary to migrate it, I dont know.
10. add verification steps for every task we did, when youre done and" 10. add verification steps for every task we did, when youre done and"
@@ -56,7 +56,7 @@ and for lidarr-reports ed25519_lidarr-reports.pub
- 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: 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 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-is. - 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: 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). - Q: Which admin hosts should receive SSH authorized_keys entries? → A: Only the listed hosts (workstation, server, deacero, galaxy).
@@ -134,7 +134,7 @@ As an operator, I want a checklist of potential missing configuration from exist
- **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-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-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-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 apply the existing iptables ruleset as-is on the new VPS to match the existing VPS behavior for all documented inbound and outbound flows. - **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-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-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-015**: The system MUST store VPN keys only in the existing secrets system and must not place them in plaintext configuration.
@@ -172,6 +172,6 @@ As an operator, I want a checklist of potential missing configuration from exist
## Assumptions ## Assumptions
- The existing proxy configuration files will be provided by the user for comparison. - 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. - 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 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. - The analytics service migration steps are documentation-only and do not require immediate cutover.

View File

@@ -11,7 +11,7 @@ Deliver MVP as User Story 1 (primary host reverse proxy + keep services on host
## Phase 1: Setup ## Phase 1: Setup
- [x] T001 Confirm baseline files exist: iptables, secrets/ssh/ed25519_deploy.pub, secrets/ssh/ed25519_lidarr-reports.pub, secrets system entries for VPN keys - [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) - [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 ## Phase 2: Foundational
@@ -34,11 +34,11 @@ Deliver MVP as User Story 1 (primary host reverse proxy + keep services on host
## Phase 4: User Story 2 (P1) - Firewall parity ## Phase 4: User Story 2 (P1) - Firewall parity
**Story goal**: Firewall behavior on new VPS matches old VPS by applying iptables ruleset as-is. **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. **Independent test criteria**: Known inbound/outbound flows match existing VPS behavior.
- [x] T011 [US2] Apply iptables ruleset as-is to VPS configuration in hosts/vps/configuration.nix using the repo root iptables file - [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 ## Phase 5: User Story 3 (P2) - Secure access and VPN peers
@@ -54,7 +54,7 @@ Deliver MVP as User Story 1 (primary host reverse proxy + keep services on host
- [x] T017 [US3] Configure sshd port and auth settings in hosts/vps/configuration.nix to match: Port 3456, PermitRootLogin no, PasswordAuthentication no - [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] 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] 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] T020 [US3] Update VPS IP to 45.79.25.87 in modules/modules.nix and config/jawz.nix SSH host entry
- [x] T021 [US3] Update host server wireguard client configuration in hosts/server/configuration.nix to target the new VPS endpoint - [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 ## Phase 6: User Story 4 (P3) - Migration gaps and verification
@@ -64,13 +64,14 @@ Deliver MVP as User Story 1 (primary host reverse proxy + keep services on host
**Independent test criteria**: Clarification list exists and each task has a verification step. **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] T022 [US4] Review sudo_hist and jawz_hist for missing configuration; record clarification list in specs/004-vps-migration/quickstart.md
- [ ] T023 [US4] Document analytics data migration steps (export, import, validate) 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 - [x] T024 [US4] Add verification steps for each task in specs/004-vps-migration/quickstart.md
## Phase 7: Polish & Cross-Cutting Concerns ## 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] 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] 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 ## Dependencies
@@ -79,7 +80,7 @@ Deliver MVP as User Story 1 (primary host reverse proxy + keep services on host
## Parallel Execution Examples ## Parallel Execution Examples
- US1: T007, T008, T009 can proceed once T003 and T006 are reviewed. - US1: T007, T008, T009 can proceed once T003 and T006 are reviewed.
- US2: T011 can proceed once iptables application location is identified. - 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. - 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. - US4: T022, T023, T024 can proceed independently once logs are reviewed and quickstart.md is open.