declared network.nix
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
<!--
|
||||
Sync Impact Report
|
||||
- Version change: template -> 1.0.0
|
||||
- Version change: 1.0.0 -> 1.1.0
|
||||
- Modified principles:
|
||||
- Template Principle 1 -> I. Constitution Authority
|
||||
- Template Principle 2 -> II. Module and Host Boundaries
|
||||
- Template Principle 3 -> III. Host-Local Firewall Ownership
|
||||
- III. Host-Local Firewall Ownership -> III. Host-Local Network Ownership
|
||||
- Template Principle 4 -> IV. Nix Structure and Ordering
|
||||
- Template Principle 5 -> V. Secure Host and Secrets Discipline
|
||||
- Added sections:
|
||||
- Repository Constraints
|
||||
- Delivery Workflow
|
||||
- None
|
||||
- Removed sections:
|
||||
- None
|
||||
- Templates requiring updates:
|
||||
@@ -41,13 +40,14 @@ factory helpers belong under `modules/factories/`; repo-wide shared options
|
||||
belong under `modules/modules.nix` or the relevant shared module. New behavior
|
||||
MUST NOT be placed in an unrelated host or module file for convenience.
|
||||
|
||||
### III. Host-Local Firewall Ownership
|
||||
Any host that contains firewall rules MUST keep firewall-related logic in
|
||||
`hosts/<name>/firewall.nix`. Host `configuration.nix` files MAY import that
|
||||
file, but MUST NOT become the long-term home for firewall rule definitions,
|
||||
NAT rules, nftables tables, forward-port rules, or other firewall-specific
|
||||
logic. Firewall changes in specs, plans, and task lists MUST reference the
|
||||
host-local `firewall.nix` path explicitly.
|
||||
### III. Host-Local Network Ownership
|
||||
Any host that owns host-local networking behavior MUST keep that logic in
|
||||
`hosts/<name>/network.nix`. Host `configuration.nix` files MAY import that
|
||||
file, but MUST NOT become the long-term home for host-specific firewall rules,
|
||||
NAT rules, nftables tables, forward-port rules, WireGuard interface
|
||||
configuration, policy-routing services, or other host-local networking logic.
|
||||
Networking changes in specs, plans, and task lists MUST reference the
|
||||
host-local `network.nix` path explicitly.
|
||||
|
||||
### IV. Nix Structure and Ordering
|
||||
Nix code MUST preserve grouped parents when they have multiple children and
|
||||
@@ -67,24 +67,25 @@ gating, and host-local boundaries.
|
||||
## Repository Constraints
|
||||
|
||||
- Host definitions live in `hosts/<name>/configuration.nix` with optional
|
||||
imports such as `hosts/<name>/firewall.nix` and `hosts/<name>/toggles.nix`.
|
||||
imports such as `hosts/<name>/network.nix` and `hosts/<name>/toggles.nix`.
|
||||
- Module categories remain `apps`, `dev`, `scripts`, `servers`, `services`,
|
||||
`shell`, `websites`, `network`, `users`, and `nix`, with feature directories
|
||||
preferred over new flat modules.
|
||||
- Service ports intrinsic to a server module SHOULD live with that module;
|
||||
miscellaneous shared ports SHOULD live in `my.ports`.
|
||||
- Firewall rules, NAT, nftables tables, and forward-port declarations for a
|
||||
host MUST be reviewed as one unit inside that host's `firewall.nix`.
|
||||
- Host-local firewall rules, NAT, nftables tables, WireGuard interfaces, and
|
||||
policy-routing services MUST be reviewed as one unit inside that host's
|
||||
`network.nix`.
|
||||
|
||||
## Delivery Workflow
|
||||
|
||||
- Every plan MUST include a constitution check that validates module ownership,
|
||||
host ownership, secure-host impact, and whether firewall work belongs in
|
||||
`hosts/<name>/firewall.nix`.
|
||||
host ownership, secure-host impact, and whether networking work belongs in
|
||||
`hosts/<name>/network.nix`.
|
||||
- Every spec that changes networking or exposure MUST state which host owns the
|
||||
change and which firewall file is affected.
|
||||
- Every task list that includes firewall work MUST name the concrete
|
||||
`hosts/<name>/firewall.nix` path.
|
||||
change and which host-local network file is affected.
|
||||
- Every task list that includes networking work MUST name the concrete
|
||||
`hosts/<name>/network.nix` path.
|
||||
- Runtime guidance docs that describe repository structure MUST be updated when
|
||||
host boundary rules change.
|
||||
|
||||
@@ -98,4 +99,4 @@ principles or materially expanded rules, PATCH for clarifications that do not
|
||||
change required behavior. Compliance review is mandatory for every plan, spec,
|
||||
and tasks artifact that claims alignment with this constitution.
|
||||
|
||||
**Version**: 1.0.0 | **Ratified**: 2026-04-01 | **Last Amended**: 2026-04-01
|
||||
**Version**: 1.1.0 | **Ratified**: 2026-04-01 | **Last Amended**: 2026-04-02
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
- Confirm each change lives in the directory that owns the behavior.
|
||||
- Confirm shared logic stays in `modules/` and host-specific assembly stays in
|
||||
`hosts/<name>/`.
|
||||
- Confirm any firewall, NAT, nftables, or port-forwarding work is scoped to
|
||||
`hosts/<name>/firewall.nix` for the affected host.
|
||||
- Confirm any host-local firewall, NAT, nftables, WireGuard, or policy-routing
|
||||
work is scoped to `hosts/<name>/network.nix` for the affected host.
|
||||
- Confirm any secret-dependent behavior respects `config.my.secureHost`.
|
||||
|
||||
## Project Structure
|
||||
|
||||
@@ -89,8 +89,8 @@
|
||||
- **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"]
|
||||
- **FR-004**: System MUST [data requirement, e.g., "persist user preferences"]
|
||||
- **FR-005**: System MUST [behavior, e.g., "log all security events"]
|
||||
- **FR-006**: If the feature changes host firewall behavior, the spec MUST name
|
||||
the affected `hosts/<name>/firewall.nix` file explicitly.
|
||||
- **FR-006**: If the feature changes host-local networking behavior, the spec
|
||||
MUST name the affected `hosts/<name>/network.nix` file explicitly.
|
||||
|
||||
*Example of marking unclear requirements:*
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ description: "Task list template for feature implementation"
|
||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
||||
- Include exact file paths in descriptions
|
||||
- If firewall behavior changes, tasks MUST reference `hosts/<name>/firewall.nix`
|
||||
instead of only `hosts/<name>/configuration.nix`
|
||||
- If host-local networking behavior changes, tasks MUST reference
|
||||
`hosts/<name>/network.nix` instead of only `hosts/<name>/configuration.nix`
|
||||
|
||||
## Path Conventions
|
||||
|
||||
@@ -70,7 +70,7 @@ Examples of foundational tasks (adjust based on your project):
|
||||
- [ ] T007 Create base models/entities that all stories depend on
|
||||
- [ ] T008 Configure error handling and logging infrastructure
|
||||
- [ ] T009 Setup environment configuration management
|
||||
- [ ] T010 If networking changes, update the affected `hosts/<name>/firewall.nix`
|
||||
- [ ] T010 If networking changes, update the affected `hosts/<name>/network.nix`
|
||||
and import wiring in `hosts/<name>/configuration.nix`
|
||||
|
||||
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
- Module auto-import: `modules/modules.nix` auto-imports legacy flat modules under `modules/apps`, `modules/dev`, `modules/scripts`, `modules/servers`, `modules/services`, `modules/shell`, `modules/websites`, and `modules/network`, excluding `librewolf.nix`, and also discovers nested `nixos.nix` files under those trees. `config/base.nix` registers `modules/home-manager.nix` as a Home Manager shared module, which discovers nested `home.nix` files under `modules/` for every Home Manager user. Factories live in `modules/factories/` (`mkserver`, `mkscript`), and shared options are in `modules/nix` and `modules/users`.
|
||||
- Home Manager helper layer: Common Home Manager wrapper logic belongs in `parts/core.nix` helpers under `inputs.self.lib` when it is repeated across multiple `home.nix` modules. Current helpers include split-loader support plus `hmModule`, `hmShellType`, and `hmOnlyUser` for shared enablement and shell-selection patterns.
|
||||
- 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.
|
||||
- Host-local firewall ownership: Hosts that define firewall rules MUST keep firewall-related logic in `hosts/<name>/firewall.nix`, imported from `hosts/<name>/configuration.nix` as needed. Host `configuration.nix` files are the assembly point, not the long-term home for firewall rule definitions.
|
||||
- Host-local network ownership: Hosts that own host-local networking behavior MUST keep that logic in `hosts/<name>/network.nix`, imported from `hosts/<name>/configuration.nix` as needed. Host `configuration.nix` files are the assembly point, not the long-term home for host-specific firewall rules, NAT, nftables tables, WireGuard interfaces, or policy-routing services.
|
||||
- Standalone Home Manager hosts: Home-only hosts may live under `hosts/<name>/home.nix` with `hosts/<name>/toggles.nix`, and should only enable modules that have a `home.nix` surface or are otherwise known to be Home Manager-compatible on that platform.
|
||||
- Port assignment: Service ports should live with the service module when the port is intrinsic to a server definition under `modules/servers/`. Miscellaneous or host-specific ports that do not belong to a server module should be centralized in `my.ports` in `modules/modules.nix` and referenced via `config.my.ports.*` (use `toString config.my.ports.*` where a string is required).
|
||||
- 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.
|
||||
@@ -79,8 +79,7 @@ config.services = {
|
||||
- Conflict handling steps: identify the divergent rule, cite the source files, decide the authoritative rule per this constitution, update both the source file and the relevant doc, and record the decision and timestamp.
|
||||
|
||||
## Maintenance Triggers and Update Process
|
||||
- Triggers: New factory/helper, new module category, new host, new toggle set, new proxy rule, new secret category/file, change to `my.mainServer` or `my.ips`, stylix scheme changes, or new auto-import filters/import trees.
|
||||
- Triggers: New factory/helper, new module category, new host, new toggle set, new proxy rule, new host firewall ownership rule or `hosts/<name>/firewall.nix` layout change, new secret category/file, change to `my.mainServer` or `my.ips`, stylix scheme changes, or new auto-import filters/import trees.
|
||||
- Triggers: New factory/helper, new module category, new host, new toggle set, new proxy rule, new host-local network ownership rule or `hosts/<name>/network.nix` layout change, new secret category/file, change to `my.mainServer` or `my.ips`, stylix scheme changes, or new auto-import filters/import trees.
|
||||
- Update flow: (1) Amend the relevant module or toggle files; (2) Update `docs/constitution.md` for rules/terminology changes; (3) Update playbooks under `docs/playbooks/` affected by the change; (4) Update `docs/reference/index.md` for navigation paths; (5) Note the decision in `specs/001-ai-docs/research.md` and refresh `quickstart.md` if discoverability shifts.
|
||||
- Validation: Confirm discoverability within two clicks (constitution → reference map/playbook), secrets map completeness, and alignment with success criteria SC-001–SC-004.
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
1. Choose the correct secrets file from the map in `docs/constitution.md` and add the entry there (YAML, encrypted via sops-nix).
|
||||
2. If a private key or file path is required, specify `owner`, `group`, and target path consistent with the consuming module.
|
||||
3. In the consuming module, reference the secret under `config.sops.secrets.<name>` and guard with `lib.mkIf config.my.secureHost`.
|
||||
4. For WireGuard entries, update `secrets/wireguard.yaml` and corresponding interface configuration under the target host.
|
||||
4. For WireGuard entries, update `secrets/wireguard.yaml` and the
|
||||
corresponding host-local network configuration under the target host.
|
||||
5. Avoid adding secrets for hosts with `secureHost = false`; instead route the workload to a secure host or skip enablement.
|
||||
- Validation:
|
||||
- Secret lives in the correct file and encrypts with SOPS; file ownership matches service user where applicable.
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
## 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.
|
||||
3. If the peer is a guest/friend, ensure `allowedIPs` includes the relevant subnets in `hosts/server/network.nix`.
|
||||
4. Add or adjust VPS networking rules in `hosts/vps/network.nix` (`networking.firewall.extraForwardRules`) to allow the requested ports.
|
||||
5. Rebuild both hosts:
|
||||
- `nixos-rebuild switch --flake .#vps`
|
||||
- `nixos-rebuild switch --flake .#server`
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
## Hosts and Roles
|
||||
- NixOS configs: `hosts/<name>/configuration.nix` with toggles in `hosts/<name>/toggles.nix`.
|
||||
- Firewall-bearing hosts: keep firewall logic in `hosts/<name>/firewall.nix` and import it from `hosts/<name>/configuration.nix`.
|
||||
- Network-owning hosts: keep host-local networking logic in `hosts/<name>/network.nix` and import it from `hosts/<name>/configuration.nix`.
|
||||
- Standalone Home Manager configs: `hosts/<name>/home.nix` with optional toggles in `hosts/<name>/toggles.nix`.
|
||||
- Active NixOS hosts: `workstation`, `server`, `miniserver`, `galaxy`, `emacs`, `vps`.
|
||||
- Active Home Manager hosts: `mac`.
|
||||
@@ -65,7 +65,7 @@
|
||||
- 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.
|
||||
- Host firewall placement: host-specific firewall rules, NAT, nftables tables, and forward-port definitions belong in `hosts/<name>/firewall.nix`.
|
||||
- Host network placement: host-specific firewall rules, NAT, nftables tables, forward-port definitions, WireGuard interfaces, and policy-routing services belong in `hosts/<name>/network.nix`.
|
||||
|
||||
## Secrets Map
|
||||
- Files and purposes:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
./network.nix
|
||||
../../config/base.nix
|
||||
../../config/stylix.nix
|
||||
];
|
||||
@@ -31,13 +32,7 @@
|
||||
(buildMachine "workstation" 8 40)
|
||||
(buildMachine "server" 6 17)
|
||||
];
|
||||
networking = {
|
||||
hostName = "miniserver";
|
||||
firewall = {
|
||||
allowedTCPPorts = [ 2049 ];
|
||||
allowedUDPPorts = [ 2049 ];
|
||||
};
|
||||
};
|
||||
networking.hostName = "miniserver";
|
||||
services = {
|
||||
btrfs.autoScrub = {
|
||||
enable = true;
|
||||
|
||||
6
hosts/miniserver/network.nix
Normal file
6
hosts/miniserver/network.nix
Normal file
@@ -0,0 +1,6 @@
|
||||
_: {
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [ 2049 ];
|
||||
allowedUDPPorts = [ 2049 ];
|
||||
};
|
||||
}
|
||||
@@ -7,16 +7,12 @@
|
||||
}:
|
||||
let
|
||||
lidarrMbGapId = 968;
|
||||
qbittorrentRouteTable = 200;
|
||||
qbitUser = config.services.qbittorrent.user;
|
||||
serverInterface = config.my.interfaces.server;
|
||||
wgInterface = "wg0";
|
||||
wgServerIp = config.my.ips.wg-server;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
inputs.lidarr-mb-gap.nixosModules.lidarr-mb-gap
|
||||
./hardware-configuration.nix
|
||||
./network.nix
|
||||
../../config/base.nix
|
||||
../../config/stylix.nix
|
||||
];
|
||||
@@ -29,19 +25,6 @@ in
|
||||
"nixminiserver"
|
||||
];
|
||||
};
|
||||
network.firewall = {
|
||||
enabledServicePorts = true;
|
||||
additionalPorts = [
|
||||
2049
|
||||
config.my.ports.syncthingGui
|
||||
config.my.ports.syncthingRelay
|
||||
config.my.ports.sonarqube
|
||||
config.my.ports.synapseSsl
|
||||
config.my.ports.tdarr
|
||||
config.my.ports.mediaMap
|
||||
config.my.ports.qbittorrent
|
||||
];
|
||||
};
|
||||
};
|
||||
nix.buildMachines = [
|
||||
{
|
||||
@@ -73,114 +56,7 @@ in
|
||||
path = "${usr.home}/.ssh/ed25519_lidarr-mb-gap";
|
||||
};
|
||||
};
|
||||
networking = {
|
||||
hostName = "server";
|
||||
iproute2.rttablesExtraConfig = ''
|
||||
${toString qbittorrentRouteTable} qbittorrent
|
||||
'';
|
||||
wireguard.interfaces.${wgInterface} = lib.mkIf config.my.secureHost {
|
||||
allowedIPsAsRoutes = false;
|
||||
ips = [ "${wgServerIp}/32" ];
|
||||
privateKeyFile = config.sops.secrets."server/private".path;
|
||||
peers = [
|
||||
{
|
||||
publicKey = "dFbiSekBwnZomarcS31o5+w6imHjMPNCipkfc2fZ3GY=";
|
||||
endpoint = "${config.my.ips.vps}:51820";
|
||||
allowedIPs = [
|
||||
"0.0.0.0/0"
|
||||
"${config.my.ips.wg-vps}/32"
|
||||
config.my.subnets.wg-homelab
|
||||
config.my.subnets.wg-friends
|
||||
config.my.subnets.wg-guests
|
||||
];
|
||||
persistentKeepalive = 25;
|
||||
}
|
||||
];
|
||||
};
|
||||
firewall = {
|
||||
allowedUDPPorts = config.networking.firewall.allowedTCPPorts;
|
||||
interfaces.${wgInterface}.allowedTCPPorts = [ config.my.servers.nextcloud.port ];
|
||||
};
|
||||
};
|
||||
systemd.services = {
|
||||
qbittorrent-vpn-routing = lib.mkIf (config.my.secureHost && config.services.qbittorrent.enable) {
|
||||
description = "Route qBittorrent user traffic through the VPS WireGuard tunnel";
|
||||
before = [ "qbittorrent.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [
|
||||
"network-online.target"
|
||||
"wireguard-${wgInterface}.service"
|
||||
];
|
||||
wants = [
|
||||
"network-online.target"
|
||||
"wireguard-${wgInterface}.service"
|
||||
];
|
||||
path = with pkgs; [
|
||||
coreutils
|
||||
gnugrep
|
||||
iproute2
|
||||
shadow
|
||||
];
|
||||
script = ''
|
||||
qbit_uid="$(id -u ${qbitUser})"
|
||||
|
||||
for _ in $(seq 1 30); do
|
||||
if ip -4 addr show dev ${wgInterface} >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if ! ip -4 addr show dev ${wgInterface} >/dev/null 2>&1; then
|
||||
echo "${wgInterface} is not available"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ip -4 route replace ${config.my.subnets.wg-homelab} dev ${wgInterface} src ${wgServerIp}
|
||||
ip -4 route replace ${config.my.subnets.wg-friends} dev ${wgInterface} src ${wgServerIp}
|
||||
ip -4 route replace ${config.my.subnets.wg-guests} dev ${wgInterface} src ${wgServerIp}
|
||||
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-homelab} dev ${wgInterface} src ${wgServerIp}
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-friends} dev ${wgInterface} src ${wgServerIp}
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-guests} dev ${wgInterface} src ${wgServerIp}
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} 10.88.0.0/16 dev podman0 src 10.88.0.1
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} 192.168.100.0/24 dev ${serverInterface} src ${config.my.ips.server}
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.ips.vps}/32 via ${config.my.ips.router} dev ${serverInterface} src ${config.my.ips.server}
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} default via ${config.my.ips.wg-vps} dev ${wgInterface} src ${wgServerIp}
|
||||
|
||||
while ip -4 rule show | grep -q "uidrange ''${qbit_uid}-''${qbit_uid} lookup ${toString qbittorrentRouteTable}"; do
|
||||
ip -4 rule del uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable}
|
||||
done
|
||||
|
||||
while ip -4 rule show | grep -q 'from ${wgServerIp} lookup ${toString qbittorrentRouteTable}'; do
|
||||
ip -4 rule del from ${wgServerIp}/32 lookup ${toString qbittorrentRouteTable}
|
||||
done
|
||||
|
||||
ip -4 rule add from ${wgServerIp}/32 lookup ${toString qbittorrentRouteTable} priority 9999
|
||||
ip -4 rule add uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable} priority 10000
|
||||
'';
|
||||
preStop = ''
|
||||
qbit_uid="$(id -u ${qbitUser})"
|
||||
|
||||
while ip -4 rule show | grep -q 'from ${wgServerIp} lookup ${toString qbittorrentRouteTable}'; do
|
||||
ip -4 rule del from ${wgServerIp}/32 lookup ${toString qbittorrentRouteTable} || true
|
||||
done
|
||||
|
||||
while ip -4 rule show | grep -q "uidrange ''${qbit_uid}-''${qbit_uid} lookup ${toString qbittorrentRouteTable}"; do
|
||||
ip -4 rule del uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable} || true
|
||||
done
|
||||
|
||||
ip -4 route del ${config.my.subnets.wg-homelab} dev ${wgInterface} || true
|
||||
ip -4 route del ${config.my.subnets.wg-friends} dev ${wgInterface} || true
|
||||
ip -4 route del ${config.my.subnets.wg-guests} dev ${wgInterface} || true
|
||||
ip -4 route flush table ${toString qbittorrentRouteTable} || true
|
||||
'';
|
||||
serviceConfig = {
|
||||
RemainAfterExit = true;
|
||||
Type = "oneshot";
|
||||
};
|
||||
};
|
||||
};
|
||||
networking.hostName = "server";
|
||||
users = {
|
||||
groups.lidarr-mb-gap.gid = lidarrMbGapId;
|
||||
users = {
|
||||
|
||||
135
hosts/server/network.nix
Normal file
135
hosts/server/network.nix
Normal file
@@ -0,0 +1,135 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
qbittorrentRouteTable = 200;
|
||||
qbitUser = config.services.qbittorrent.user;
|
||||
serverInterface = config.my.interfaces.server;
|
||||
wgInterface = "wg0";
|
||||
wgServerIp = config.my.ips.wg-server;
|
||||
in
|
||||
{
|
||||
my.network.firewall = {
|
||||
enabledServicePorts = true;
|
||||
additionalPorts = [
|
||||
2049
|
||||
config.my.ports.syncthingGui
|
||||
config.my.ports.syncthingRelay
|
||||
config.my.ports.sonarqube
|
||||
config.my.ports.synapseSsl
|
||||
config.my.ports.tdarr
|
||||
config.my.ports.mediaMap
|
||||
config.my.ports.qbittorrent
|
||||
];
|
||||
};
|
||||
networking = {
|
||||
iproute2.rttablesExtraConfig = ''
|
||||
${toString qbittorrentRouteTable} qbittorrent
|
||||
'';
|
||||
wireguard.interfaces.${wgInterface} = lib.mkIf config.my.secureHost {
|
||||
allowedIPsAsRoutes = false;
|
||||
ips = [ "${wgServerIp}/32" ];
|
||||
privateKeyFile = config.sops.secrets."server/private".path;
|
||||
peers = [
|
||||
{
|
||||
publicKey = "dFbiSekBwnZomarcS31o5+w6imHjMPNCipkfc2fZ3GY=";
|
||||
endpoint = "${config.my.ips.vps}:51820";
|
||||
allowedIPs = [
|
||||
"0.0.0.0/0"
|
||||
"${config.my.ips.wg-vps}/32"
|
||||
config.my.subnets.wg-homelab
|
||||
config.my.subnets.wg-friends
|
||||
config.my.subnets.wg-guests
|
||||
];
|
||||
persistentKeepalive = 25;
|
||||
}
|
||||
];
|
||||
};
|
||||
firewall = {
|
||||
allowedUDPPorts = config.networking.firewall.allowedTCPPorts;
|
||||
interfaces.${wgInterface}.allowedTCPPorts = [ config.my.servers.nextcloud.port ];
|
||||
};
|
||||
};
|
||||
systemd.services.qbittorrent-vpn-routing =
|
||||
lib.mkIf (config.my.secureHost && config.services.qbittorrent.enable)
|
||||
{
|
||||
description = "Route qBittorrent user traffic through the VPS WireGuard tunnel";
|
||||
before = [ "qbittorrent.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [
|
||||
"network-online.target"
|
||||
"wireguard-${wgInterface}.service"
|
||||
];
|
||||
wants = [
|
||||
"network-online.target"
|
||||
"wireguard-${wgInterface}.service"
|
||||
];
|
||||
path = with pkgs; [
|
||||
coreutils
|
||||
gnugrep
|
||||
iproute2
|
||||
shadow
|
||||
];
|
||||
script = ''
|
||||
qbit_uid="$(id -u ${qbitUser})"
|
||||
|
||||
for _ in $(seq 1 30); do
|
||||
if ip -4 addr show dev ${wgInterface} >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if ! ip -4 addr show dev ${wgInterface} >/dev/null 2>&1; then
|
||||
echo "${wgInterface} is not available"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ip -4 route replace ${config.my.subnets.wg-homelab} dev ${wgInterface} src ${wgServerIp}
|
||||
ip -4 route replace ${config.my.subnets.wg-friends} dev ${wgInterface} src ${wgServerIp}
|
||||
ip -4 route replace ${config.my.subnets.wg-guests} dev ${wgInterface} src ${wgServerIp}
|
||||
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-homelab} dev ${wgInterface} src ${wgServerIp}
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-friends} dev ${wgInterface} src ${wgServerIp}
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-guests} dev ${wgInterface} src ${wgServerIp}
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} 10.88.0.0/16 dev podman0 src 10.88.0.1
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} 192.168.100.0/24 dev ${serverInterface} src ${config.my.ips.server}
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.ips.vps}/32 via ${config.my.ips.router} dev ${serverInterface} src ${config.my.ips.server}
|
||||
ip -4 route replace table ${toString qbittorrentRouteTable} default via ${config.my.ips.wg-vps} dev ${wgInterface} src ${wgServerIp}
|
||||
|
||||
while ip -4 rule show | grep -q "uidrange ''${qbit_uid}-''${qbit_uid} lookup ${toString qbittorrentRouteTable}"; do
|
||||
ip -4 rule del uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable}
|
||||
done
|
||||
|
||||
while ip -4 rule show | grep -q 'from ${wgServerIp} lookup ${toString qbittorrentRouteTable}'; do
|
||||
ip -4 rule del from ${wgServerIp}/32 lookup ${toString qbittorrentRouteTable}
|
||||
done
|
||||
|
||||
ip -4 rule add from ${wgServerIp}/32 lookup ${toString qbittorrentRouteTable} priority 9999
|
||||
ip -4 rule add uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable} priority 10000
|
||||
'';
|
||||
preStop = ''
|
||||
qbit_uid="$(id -u ${qbitUser})"
|
||||
|
||||
while ip -4 rule show | grep -q 'from ${wgServerIp} lookup ${toString qbittorrentRouteTable}'; do
|
||||
ip -4 rule del from ${wgServerIp}/32 lookup ${toString qbittorrentRouteTable} || true
|
||||
done
|
||||
|
||||
while ip -4 rule show | grep -q "uidrange ''${qbit_uid}-''${qbit_uid} lookup ${toString qbittorrentRouteTable}"; do
|
||||
ip -4 rule del uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable} || true
|
||||
done
|
||||
|
||||
ip -4 route del ${config.my.subnets.wg-homelab} dev ${wgInterface} || true
|
||||
ip -4 route del ${config.my.subnets.wg-friends} dev ${wgInterface} || true
|
||||
ip -4 route del ${config.my.subnets.wg-guests} dev ${wgInterface} || true
|
||||
ip -4 route flush table ${toString qbittorrentRouteTable} || true
|
||||
'';
|
||||
serviceConfig = {
|
||||
RemainAfterExit = true;
|
||||
Type = "oneshot";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -5,87 +5,10 @@
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
externalInterface = config.my.interfaces.${config.networking.hostName};
|
||||
wgInterface = "wg0";
|
||||
ips = {
|
||||
homeServer = config.my.ips.wg-server;
|
||||
wgWorkstation = config.my.ips.wg-workstation;
|
||||
wgFriend1 = config.my.ips.wg-friend1;
|
||||
wgFriend6 = config.my.ips.wg-friend6;
|
||||
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 = {
|
||||
inherit (config.my.ports)
|
||||
giteaSsh
|
||||
qbittorrent
|
||||
ssh
|
||||
wg
|
||||
;
|
||||
web = [
|
||||
80
|
||||
443
|
||||
];
|
||||
syncthing = config.my.ports.syncthingRelay;
|
||||
synapseFederation = config.my.ports.synapseSsl;
|
||||
};
|
||||
portsStr = {
|
||||
syncthing = toString ports.syncthing;
|
||||
synapseFederation = toString ports.synapseFederation;
|
||||
synapseClient = toString config.my.servers.synapse.port;
|
||||
syncplay = toString config.my.servers.syncplay.port;
|
||||
synctube = toString config.my.servers.synctube.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;
|
||||
openWebui = toString config.my.ports.openWebui;
|
||||
sillytavern = toString config.my.ports.sillytavern;
|
||||
ollama = toString config.my.ports.ollama;
|
||||
comfyui = toString config.my.ports.comfyui;
|
||||
};
|
||||
forwardedPorts = [
|
||||
{
|
||||
comment = "snat ssh forward";
|
||||
port = ports.giteaSsh;
|
||||
proto = "tcp";
|
||||
}
|
||||
{
|
||||
comment = "snat qbittorrent tcp forward";
|
||||
port = ports.qbittorrent;
|
||||
proto = "tcp";
|
||||
}
|
||||
{
|
||||
comment = "snat qbittorrent udp forward";
|
||||
port = ports.qbittorrent;
|
||||
proto = "udp";
|
||||
}
|
||||
];
|
||||
mkForwardPort =
|
||||
{ port, proto, ... }:
|
||||
{
|
||||
sourcePort = port;
|
||||
inherit proto;
|
||||
destination = "${ips.homeServer}:${toString port}";
|
||||
};
|
||||
mkSnatRule =
|
||||
{
|
||||
comment,
|
||||
port,
|
||||
proto,
|
||||
...
|
||||
}:
|
||||
''iifname "${externalInterface}" oifname "${wgInterface}" ip daddr ${ips.homeServer}/32 ${proto} dport ${toString port} masquerade comment "${comment}"'';
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
./network.nix
|
||||
./nginx-nextcloud.nix
|
||||
../../config/base.nix
|
||||
];
|
||||
@@ -128,72 +51,10 @@ in
|
||||
keyFile = "/var/lib/sops-nix/key.txt";
|
||||
sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||
};
|
||||
networking = {
|
||||
hostName = "vps";
|
||||
nat = {
|
||||
inherit externalInterface;
|
||||
enable = true;
|
||||
internalInterfaces = [ wgInterface ];
|
||||
forwardPorts = map mkForwardPort forwardedPorts;
|
||||
};
|
||||
nftables = {
|
||||
enable = true;
|
||||
tables.vps-snat = {
|
||||
family = "ip";
|
||||
content = ''
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority srcnat;
|
||||
iifname "${wgInterface}" oifname "${externalInterface}" ip saddr ${subnets.wgHomelab} masquerade comment "snat homelab egress"
|
||||
${lib.concatStringsSep "\n " (map mkSnatRule forwardedPorts)}
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
firewall = {
|
||||
enable = true;
|
||||
filterForward = true;
|
||||
checkReversePath = "loose";
|
||||
allowedTCPPorts = [
|
||||
ports.ssh
|
||||
ports.qbittorrent
|
||||
]
|
||||
++ ports.web;
|
||||
allowedUDPPorts = [
|
||||
ports.wg
|
||||
ports.qbittorrent
|
||||
];
|
||||
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}, ${portsStr.synctube} } accept
|
||||
|
||||
iifname "${wgInterface}" ip saddr ${subnets.wgFriends} ip daddr ${ips.homeServer}/32 icmp type echo-request accept
|
||||
iifname "${wgInterface}" ip saddr ${ips.wgFriend1}/32 ip daddr ${ips.homeServer}/32 tcp dport ${portsStr.stash} accept
|
||||
iifname "${wgInterface}" ip saddr ${ips.wgFriend6}/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.wgHomelab} ip daddr ${ips.wgWorkstation}/32 tcp dport { ${portsStr.openWebui}, ${portsStr.sillytavern}, ${portsStr.ollama}, ${portsStr.comfyui} } accept
|
||||
iifname "${wgInterface}" ip saddr ${ips.wgWorkstation}/32 ip daddr ${subnets.wgHomelab} tcp sport { ${portsStr.openWebui}, ${portsStr.sillytavern}, ${portsStr.ollama}, ${portsStr.comfyui} } accept
|
||||
|
||||
iifname "${wgInterface}" ip saddr ${subnets.wgHomelab} oifname "${externalInterface}" 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
|
||||
'';
|
||||
};
|
||||
};
|
||||
networking.hostName = "vps";
|
||||
services = {
|
||||
smartd.enable = lib.mkForce false;
|
||||
openssh.ports = [ ports.ssh ];
|
||||
openssh.ports = [ config.my.ports.ssh ];
|
||||
};
|
||||
users = {
|
||||
groups = {
|
||||
|
||||
145
hosts/vps/network.nix
Normal file
145
hosts/vps/network.nix
Normal file
@@ -0,0 +1,145 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
externalInterface = config.my.interfaces.${config.networking.hostName};
|
||||
wgInterface = "wg0";
|
||||
ips = {
|
||||
homeServer = config.my.ips.wg-server;
|
||||
wgWorkstation = config.my.ips.wg-workstation;
|
||||
wgFriend1 = config.my.ips.wg-friend1;
|
||||
wgFriend6 = config.my.ips.wg-friend6;
|
||||
};
|
||||
subnets = {
|
||||
wgFriends = config.my.subnets.wg-friends;
|
||||
wgGuests = config.my.subnets.wg-guests;
|
||||
wgHomelab = config.my.subnets.wg-homelab;
|
||||
};
|
||||
ports = {
|
||||
inherit (config.my.ports)
|
||||
giteaSsh
|
||||
qbittorrent
|
||||
ssh
|
||||
wg
|
||||
;
|
||||
web = [
|
||||
80
|
||||
443
|
||||
];
|
||||
syncthing = config.my.ports.syncthingRelay;
|
||||
synapseFederation = config.my.ports.synapseSsl;
|
||||
};
|
||||
portsStr = {
|
||||
syncthing = toString config.my.ports.syncthingRelay;
|
||||
synapseFederation = toString config.my.ports.synapseSsl;
|
||||
synapseClient = toString config.my.servers.synapse.port;
|
||||
syncplay = toString config.my.servers.syncplay.port;
|
||||
synctube = toString config.my.servers.synctube.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;
|
||||
openWebui = toString config.my.ports.openWebui;
|
||||
sillytavern = toString config.my.ports.sillytavern;
|
||||
ollama = toString config.my.ports.ollama;
|
||||
comfyui = toString config.my.ports.comfyui;
|
||||
};
|
||||
forwardedPorts = [
|
||||
{
|
||||
comment = "snat ssh forward";
|
||||
port = ports.giteaSsh;
|
||||
proto = "tcp";
|
||||
}
|
||||
{
|
||||
comment = "snat qbittorrent tcp forward";
|
||||
port = ports.qbittorrent;
|
||||
proto = "tcp";
|
||||
}
|
||||
{
|
||||
comment = "snat qbittorrent udp forward";
|
||||
port = ports.qbittorrent;
|
||||
proto = "udp";
|
||||
}
|
||||
];
|
||||
mkForwardPort =
|
||||
{ port, proto, ... }:
|
||||
{
|
||||
sourcePort = port;
|
||||
inherit proto;
|
||||
destination = "${ips.homeServer}:${toString port}";
|
||||
};
|
||||
mkSnatRule =
|
||||
{
|
||||
comment,
|
||||
port,
|
||||
proto,
|
||||
...
|
||||
}:
|
||||
''iifname "${externalInterface}" oifname "${wgInterface}" ip daddr ${ips.homeServer}/32 ${proto} dport ${toString port} masquerade comment "${comment}"'';
|
||||
in
|
||||
{
|
||||
networking = {
|
||||
nat = {
|
||||
inherit externalInterface;
|
||||
enable = true;
|
||||
internalInterfaces = [ wgInterface ];
|
||||
forwardPorts = map mkForwardPort forwardedPorts;
|
||||
};
|
||||
nftables = {
|
||||
enable = true;
|
||||
tables.vps-snat = {
|
||||
family = "ip";
|
||||
content = ''
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority srcnat;
|
||||
iifname "${wgInterface}" oifname "${externalInterface}" ip saddr ${subnets.wgHomelab} masquerade comment "snat homelab egress"
|
||||
${lib.concatStringsSep "\n " (map mkSnatRule forwardedPorts)}
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
firewall = {
|
||||
enable = true;
|
||||
filterForward = true;
|
||||
checkReversePath = "loose";
|
||||
allowedTCPPorts = [
|
||||
ports.ssh
|
||||
ports.qbittorrent
|
||||
]
|
||||
++ ports.web;
|
||||
allowedUDPPorts = [
|
||||
ports.wg
|
||||
ports.qbittorrent
|
||||
];
|
||||
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}, ${portsStr.synctube} } accept
|
||||
|
||||
iifname "${wgInterface}" ip saddr ${subnets.wgFriends} ip daddr ${ips.homeServer}/32 icmp type echo-request accept
|
||||
iifname "${wgInterface}" ip saddr ${ips.wgFriend1}/32 ip daddr ${ips.homeServer}/32 tcp dport ${portsStr.stash} accept
|
||||
iifname "${wgInterface}" ip saddr ${ips.wgFriend6}/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.wgHomelab} ip daddr ${ips.wgWorkstation}/32 tcp dport { ${portsStr.openWebui}, ${portsStr.sillytavern}, ${portsStr.ollama}, ${portsStr.comfyui} } accept
|
||||
iifname "${wgInterface}" ip saddr ${ips.wgWorkstation}/32 ip daddr ${subnets.wgHomelab} tcp sport { ${portsStr.openWebui}, ${portsStr.sillytavern}, ${portsStr.ollama}, ${portsStr.comfyui} } accept
|
||||
|
||||
iifname "${wgInterface}" ip saddr ${subnets.wgHomelab} oifname "${externalInterface}" 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
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -20,6 +20,7 @@ in
|
||||
{
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
./network.nix
|
||||
../../config/base.nix
|
||||
../../config/stylix.nix
|
||||
../../environments/gnome.nix
|
||||
@@ -55,51 +56,7 @@ in
|
||||
settings.term = "xterm-256color";
|
||||
};
|
||||
};
|
||||
networking = {
|
||||
hostName = "workstation";
|
||||
wireguard.interfaces.wg0 = lib.mkIf config.my.secureHost {
|
||||
ips = [ "${config.my.ips.wg-workstation}/32" ];
|
||||
privateKeyFile = config.sops.secrets."workstation/private".path;
|
||||
peers = [
|
||||
{
|
||||
publicKey = "dFbiSekBwnZomarcS31o5+w6imHjMPNCipkfc2fZ3GY=";
|
||||
endpoint = "${config.my.ips.vps}:51820";
|
||||
persistentKeepalive = 25;
|
||||
allowedIPs = [
|
||||
"${config.my.ips.wg-vps}/32"
|
||||
config.my.subnets.wg-homelab
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
firewall = {
|
||||
allowedTCPPorts = [
|
||||
config.my.ports.nsUsbloader
|
||||
config.my.ports.syncthingGui
|
||||
];
|
||||
allowedTCPPortRanges = [
|
||||
{
|
||||
from = 1714;
|
||||
to = 1764;
|
||||
}
|
||||
];
|
||||
interfaces.wg0.allowedTCPPorts = [
|
||||
config.services.ollama.port
|
||||
config.services.open-webui.port
|
||||
config.services.sillytavern.port
|
||||
config.my.ports.comfyui
|
||||
];
|
||||
};
|
||||
nftables.tables.wg-local-redirect = {
|
||||
family = "ip";
|
||||
content = ''
|
||||
chain prerouting {
|
||||
type nat hook prerouting priority dstnat;
|
||||
iifname "wg0" ip daddr ${config.my.ips.wg-workstation}/32 tcp dport ${toString config.my.ports.sillytavern} redirect to :${toString config.my.ports.sillytavern}
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
networking.hostName = "workstation";
|
||||
users = {
|
||||
groups.ai = { };
|
||||
users.jawz.packages = [
|
||||
|
||||
55
hosts/workstation/network.nix
Normal file
55
hosts/workstation/network.nix
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
wgInterface = "wg0";
|
||||
wgWorkstationIp = config.my.ips.wg-workstation;
|
||||
in
|
||||
{
|
||||
networking = {
|
||||
wireguard.interfaces.${wgInterface} = lib.mkIf config.my.secureHost {
|
||||
ips = [ "${wgWorkstationIp}/32" ];
|
||||
privateKeyFile = config.sops.secrets."workstation/private".path;
|
||||
peers = [
|
||||
{
|
||||
publicKey = "dFbiSekBwnZomarcS31o5+w6imHjMPNCipkfc2fZ3GY=";
|
||||
endpoint = "${config.my.ips.vps}:51820";
|
||||
persistentKeepalive = 25;
|
||||
allowedIPs = [
|
||||
"${config.my.ips.wg-vps}/32"
|
||||
config.my.subnets.wg-homelab
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
firewall = {
|
||||
allowedTCPPorts = [
|
||||
config.my.ports.nsUsbloader
|
||||
config.my.ports.syncthingGui
|
||||
];
|
||||
allowedTCPPortRanges = [
|
||||
{
|
||||
from = 1714;
|
||||
to = 1764;
|
||||
}
|
||||
];
|
||||
interfaces.${wgInterface}.allowedTCPPorts = [
|
||||
config.services.ollama.port
|
||||
config.services.open-webui.port
|
||||
config.services.sillytavern.port
|
||||
config.my.ports.comfyui
|
||||
];
|
||||
};
|
||||
nftables.tables.wg-local-redirect = {
|
||||
family = "ip";
|
||||
content = ''
|
||||
chain prerouting {
|
||||
type nat hook prerouting priority dstnat;
|
||||
iifname "${wgInterface}" ip daddr ${wgWorkstationIp}/32 tcp dport ${toString config.my.ports.sillytavern} redirect to :${toString config.my.ports.sillytavern}
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -55,7 +55,7 @@
|
||||
- **Rationale**: This keeps modules scan-friendly, reduces unnecessary indentation, and makes the high-signal contract (`options`) appear before implementation (`config`) consistently across the repo.
|
||||
- **Alternatives considered**: (a) Leave structure to formatter defaults only (rejected: formatters do not enforce these semantic grouping rules); (b) prefer fully flattened attrpaths everywhere (rejected: harms readability once a parent has multiple children); (c) keep `config` before `options` when it was written first (rejected: makes module interfaces harder to scan).
|
||||
|
||||
## Decision 12 (2026-04-01): Host-local firewall files
|
||||
- **Decision**: Any host that owns firewall rules MUST keep firewall-related logic in `hosts/<name>/firewall.nix`, with `hosts/<name>/configuration.nix` importing that file rather than accumulating the firewall logic inline.
|
||||
- **Rationale**: Firewall behavior is a distinct host concern that becomes hard to review and maintain when mixed into general host assembly. A dedicated `firewall.nix` preserves ownership boundaries and makes networking changes easier to audit.
|
||||
- **Alternatives considered**: (a) Keep firewall rules inline in `configuration.nix` (rejected: mixes host assembly with a dense, security-sensitive subsystem); (b) centralize all firewall logic under `modules/network/` (rejected: hides host-specific rule ownership and deployment context).
|
||||
## Decision 12 (2026-04-02): Host-local network files
|
||||
- **Decision**: Any host that owns host-local networking behavior MUST keep that logic in `hosts/<name>/network.nix`, with `hosts/<name>/configuration.nix` importing that file rather than accumulating the networking logic inline.
|
||||
- **Rationale**: Firewall behavior, NAT, nftables tables, WireGuard interfaces, and policy-routing services form one host-owned networking surface that becomes hard to review and maintain when spread across general host assembly. A dedicated `network.nix` preserves ownership boundaries and makes host networking changes easier to audit.
|
||||
- **Alternatives considered**: (a) Keep host networking rules inline in `configuration.nix` (rejected: mixes host assembly with a dense, security-sensitive subsystem); (b) keep only firewall rules in a dedicated file and leave the rest inline (rejected: splits one host-owned networking surface across files); (c) centralize all host networking logic under `modules/network/` (rejected: hides host-specific ownership and deployment context).
|
||||
|
||||
Reference in New Issue
Block a user