declared network.nix
This commit is contained in:
@@ -1,15 +1,14 @@
|
|||||||
<!--
|
<!--
|
||||||
Sync Impact Report
|
Sync Impact Report
|
||||||
- Version change: template -> 1.0.0
|
- Version change: 1.0.0 -> 1.1.0
|
||||||
- Modified principles:
|
- Modified principles:
|
||||||
- Template Principle 1 -> I. Constitution Authority
|
- Template Principle 1 -> I. Constitution Authority
|
||||||
- Template Principle 2 -> II. Module and Host Boundaries
|
- 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 4 -> IV. Nix Structure and Ordering
|
||||||
- Template Principle 5 -> V. Secure Host and Secrets Discipline
|
- Template Principle 5 -> V. Secure Host and Secrets Discipline
|
||||||
- Added sections:
|
- Added sections:
|
||||||
- Repository Constraints
|
- None
|
||||||
- Delivery Workflow
|
|
||||||
- Removed sections:
|
- Removed sections:
|
||||||
- None
|
- None
|
||||||
- Templates requiring updates:
|
- 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
|
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.
|
MUST NOT be placed in an unrelated host or module file for convenience.
|
||||||
|
|
||||||
### III. Host-Local Firewall Ownership
|
### III. Host-Local Network Ownership
|
||||||
Any host that contains firewall rules MUST keep firewall-related logic in
|
Any host that owns host-local networking behavior MUST keep that logic in
|
||||||
`hosts/<name>/firewall.nix`. Host `configuration.nix` files MAY import that
|
`hosts/<name>/network.nix`. Host `configuration.nix` files MAY import that
|
||||||
file, but MUST NOT become the long-term home for firewall rule definitions,
|
file, but MUST NOT become the long-term home for host-specific firewall rules,
|
||||||
NAT rules, nftables tables, forward-port rules, or other firewall-specific
|
NAT rules, nftables tables, forward-port rules, WireGuard interface
|
||||||
logic. Firewall changes in specs, plans, and task lists MUST reference the
|
configuration, policy-routing services, or other host-local networking logic.
|
||||||
host-local `firewall.nix` path explicitly.
|
Networking changes in specs, plans, and task lists MUST reference the
|
||||||
|
host-local `network.nix` path explicitly.
|
||||||
|
|
||||||
### IV. Nix Structure and Ordering
|
### IV. Nix Structure and Ordering
|
||||||
Nix code MUST preserve grouped parents when they have multiple children and
|
Nix code MUST preserve grouped parents when they have multiple children and
|
||||||
@@ -67,24 +67,25 @@ gating, and host-local boundaries.
|
|||||||
## Repository Constraints
|
## Repository Constraints
|
||||||
|
|
||||||
- Host definitions live in `hosts/<name>/configuration.nix` with optional
|
- 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`,
|
- Module categories remain `apps`, `dev`, `scripts`, `servers`, `services`,
|
||||||
`shell`, `websites`, `network`, `users`, and `nix`, with feature directories
|
`shell`, `websites`, `network`, `users`, and `nix`, with feature directories
|
||||||
preferred over new flat modules.
|
preferred over new flat modules.
|
||||||
- Service ports intrinsic to a server module SHOULD live with that module;
|
- Service ports intrinsic to a server module SHOULD live with that module;
|
||||||
miscellaneous shared ports SHOULD live in `my.ports`.
|
miscellaneous shared ports SHOULD live in `my.ports`.
|
||||||
- Firewall rules, NAT, nftables tables, and forward-port declarations for a
|
- Host-local firewall rules, NAT, nftables tables, WireGuard interfaces, and
|
||||||
host MUST be reviewed as one unit inside that host's `firewall.nix`.
|
policy-routing services MUST be reviewed as one unit inside that host's
|
||||||
|
`network.nix`.
|
||||||
|
|
||||||
## Delivery Workflow
|
## Delivery Workflow
|
||||||
|
|
||||||
- Every plan MUST include a constitution check that validates module ownership,
|
- Every plan MUST include a constitution check that validates module ownership,
|
||||||
host ownership, secure-host impact, and whether firewall work belongs in
|
host ownership, secure-host impact, and whether networking work belongs in
|
||||||
`hosts/<name>/firewall.nix`.
|
`hosts/<name>/network.nix`.
|
||||||
- Every spec that changes networking or exposure MUST state which host owns the
|
- Every spec that changes networking or exposure MUST state which host owns the
|
||||||
change and which firewall file is affected.
|
change and which host-local network file is affected.
|
||||||
- Every task list that includes firewall work MUST name the concrete
|
- Every task list that includes networking work MUST name the concrete
|
||||||
`hosts/<name>/firewall.nix` path.
|
`hosts/<name>/network.nix` path.
|
||||||
- Runtime guidance docs that describe repository structure MUST be updated when
|
- Runtime guidance docs that describe repository structure MUST be updated when
|
||||||
host boundary rules change.
|
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,
|
change required behavior. Compliance review is mandatory for every plan, spec,
|
||||||
and tasks artifact that claims alignment with this constitution.
|
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 each change lives in the directory that owns the behavior.
|
||||||
- Confirm shared logic stays in `modules/` and host-specific assembly stays in
|
- Confirm shared logic stays in `modules/` and host-specific assembly stays in
|
||||||
`hosts/<name>/`.
|
`hosts/<name>/`.
|
||||||
- Confirm any firewall, NAT, nftables, or port-forwarding work is scoped to
|
- Confirm any host-local firewall, NAT, nftables, WireGuard, or policy-routing
|
||||||
`hosts/<name>/firewall.nix` for the affected host.
|
work is scoped to `hosts/<name>/network.nix` for the affected host.
|
||||||
- Confirm any secret-dependent behavior respects `config.my.secureHost`.
|
- Confirm any secret-dependent behavior respects `config.my.secureHost`.
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|||||||
@@ -89,8 +89,8 @@
|
|||||||
- **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"]
|
- **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-004**: System MUST [data requirement, e.g., "persist user preferences"]
|
||||||
- **FR-005**: System MUST [behavior, e.g., "log all security events"]
|
- **FR-005**: System MUST [behavior, e.g., "log all security events"]
|
||||||
- **FR-006**: If the feature changes host firewall behavior, the spec MUST name
|
- **FR-006**: If the feature changes host-local networking behavior, the spec
|
||||||
the affected `hosts/<name>/firewall.nix` file explicitly.
|
MUST name the affected `hosts/<name>/network.nix` file explicitly.
|
||||||
|
|
||||||
*Example of marking unclear requirements:*
|
*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)
|
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||||
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
||||||
- Include exact file paths in descriptions
|
- Include exact file paths in descriptions
|
||||||
- If firewall behavior changes, tasks MUST reference `hosts/<name>/firewall.nix`
|
- If host-local networking behavior changes, tasks MUST reference
|
||||||
instead of only `hosts/<name>/configuration.nix`
|
`hosts/<name>/network.nix` instead of only `hosts/<name>/configuration.nix`
|
||||||
|
|
||||||
## Path Conventions
|
## 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
|
- [ ] T007 Create base models/entities that all stories depend on
|
||||||
- [ ] T008 Configure error handling and logging infrastructure
|
- [ ] T008 Configure error handling and logging infrastructure
|
||||||
- [ ] T009 Setup environment configuration management
|
- [ ] 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`
|
and import wiring in `hosts/<name>/configuration.nix`
|
||||||
|
|
||||||
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
|
**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`.
|
- 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.
|
- 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.
|
- 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.
|
- 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).
|
- 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.
|
- 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.
|
- 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
|
## 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-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.
|
||||||
- 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.
|
|
||||||
- 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.
|
- 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.
|
- 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).
|
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.
|
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`.
|
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.
|
5. Avoid adding secrets for hosts with `secureHost = false`; instead route the workload to a secure host or skip enablement.
|
||||||
- Validation:
|
- Validation:
|
||||||
- Secret lives in the correct file and encrypts with SOPS; file ownership matches service user where applicable.
|
- Secret lives in the correct file and encrypts with SOPS; file ownership matches service user where applicable.
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
## Steps
|
## Steps
|
||||||
1. Add the peer IP to `my.ips` in `modules/modules.nix`.
|
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`.
|
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`.
|
3. If the peer is a guest/friend, ensure `allowedIPs` includes the relevant subnets in `hosts/server/network.nix`.
|
||||||
4. Add or adjust VPS firewall rules in `hosts/vps/configuration.nix` (`networking.firewall.extraForwardRules`) to allow the requested ports.
|
4. Add or adjust VPS networking rules in `hosts/vps/network.nix` (`networking.firewall.extraForwardRules`) to allow the requested ports.
|
||||||
5. Rebuild both hosts:
|
5. Rebuild both hosts:
|
||||||
- `nixos-rebuild switch --flake .#vps`
|
- `nixos-rebuild switch --flake .#vps`
|
||||||
- `nixos-rebuild switch --flake .#server`
|
- `nixos-rebuild switch --flake .#server`
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
## Hosts and Roles
|
## Hosts and Roles
|
||||||
- NixOS configs: `hosts/<name>/configuration.nix` with toggles in `hosts/<name>/toggles.nix`.
|
- 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`.
|
- 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 NixOS hosts: `workstation`, `server`, `miniserver`, `galaxy`, `emacs`, `vps`.
|
||||||
- Active Home Manager hosts: `mac`.
|
- 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`.
|
- 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`.
|
- 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.
|
||||||
- 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
|
## Secrets Map
|
||||||
- Files and purposes:
|
- Files and purposes:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
|
./network.nix
|
||||||
../../config/base.nix
|
../../config/base.nix
|
||||||
../../config/stylix.nix
|
../../config/stylix.nix
|
||||||
];
|
];
|
||||||
@@ -31,13 +32,7 @@
|
|||||||
(buildMachine "workstation" 8 40)
|
(buildMachine "workstation" 8 40)
|
||||||
(buildMachine "server" 6 17)
|
(buildMachine "server" 6 17)
|
||||||
];
|
];
|
||||||
networking = {
|
networking.hostName = "miniserver";
|
||||||
hostName = "miniserver";
|
|
||||||
firewall = {
|
|
||||||
allowedTCPPorts = [ 2049 ];
|
|
||||||
allowedUDPPorts = [ 2049 ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
services = {
|
services = {
|
||||||
btrfs.autoScrub = {
|
btrfs.autoScrub = {
|
||||||
enable = true;
|
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
|
let
|
||||||
lidarrMbGapId = 968;
|
lidarrMbGapId = 968;
|
||||||
qbittorrentRouteTable = 200;
|
|
||||||
qbitUser = config.services.qbittorrent.user;
|
|
||||||
serverInterface = config.my.interfaces.server;
|
|
||||||
wgInterface = "wg0";
|
|
||||||
wgServerIp = config.my.ips.wg-server;
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
inputs.lidarr-mb-gap.nixosModules.lidarr-mb-gap
|
inputs.lidarr-mb-gap.nixosModules.lidarr-mb-gap
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
|
./network.nix
|
||||||
../../config/base.nix
|
../../config/base.nix
|
||||||
../../config/stylix.nix
|
../../config/stylix.nix
|
||||||
];
|
];
|
||||||
@@ -29,19 +25,6 @@ in
|
|||||||
"nixminiserver"
|
"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 = [
|
nix.buildMachines = [
|
||||||
{
|
{
|
||||||
@@ -73,114 +56,7 @@ in
|
|||||||
path = "${usr.home}/.ssh/ed25519_lidarr-mb-gap";
|
path = "${usr.home}/.ssh/ed25519_lidarr-mb-gap";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
networking = {
|
networking.hostName = "server";
|
||||||
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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
users = {
|
users = {
|
||||||
groups.lidarr-mb-gap.gid = lidarrMbGapId;
|
groups.lidarr-mb-gap.gid = lidarrMbGapId;
|
||||||
users = {
|
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,
|
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 = [
|
imports = [
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
|
./network.nix
|
||||||
./nginx-nextcloud.nix
|
./nginx-nextcloud.nix
|
||||||
../../config/base.nix
|
../../config/base.nix
|
||||||
];
|
];
|
||||||
@@ -128,72 +51,10 @@ in
|
|||||||
keyFile = "/var/lib/sops-nix/key.txt";
|
keyFile = "/var/lib/sops-nix/key.txt";
|
||||||
sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
};
|
};
|
||||||
networking = {
|
networking.hostName = "vps";
|
||||||
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
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
services = {
|
services = {
|
||||||
smartd.enable = lib.mkForce false;
|
smartd.enable = lib.mkForce false;
|
||||||
openssh.ports = [ ports.ssh ];
|
openssh.ports = [ config.my.ports.ssh ];
|
||||||
};
|
};
|
||||||
users = {
|
users = {
|
||||||
groups = {
|
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 = [
|
imports = [
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
|
./network.nix
|
||||||
../../config/base.nix
|
../../config/base.nix
|
||||||
../../config/stylix.nix
|
../../config/stylix.nix
|
||||||
../../environments/gnome.nix
|
../../environments/gnome.nix
|
||||||
@@ -55,51 +56,7 @@ in
|
|||||||
settings.term = "xterm-256color";
|
settings.term = "xterm-256color";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
networking = {
|
networking.hostName = "workstation";
|
||||||
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}
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
users = {
|
users = {
|
||||||
groups.ai = { };
|
groups.ai = { };
|
||||||
users.jawz.packages = [
|
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.
|
- **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).
|
- **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 12 (2026-04-02): Host-local network 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.
|
- **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 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.
|
- **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 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).
|
- **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