Compare commits
1 Commits
6f963920ec
...
throwaway/
| Author | SHA1 | Date | |
|---|---|---|---|
| 4598993dfe |
@@ -101,5 +101,3 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla
|
||||
Keep fractional scaling sharp with Hyprland's `xwayland.force_zero_scaling` and Steam's own `STEAM_FORCE_DESKTOPUI_SCALING` instead.
|
||||
- AdGuard's LAN wildcard rewrite for `*.alexion.dev` points names at the reverse-proxy guest, including non-HTTP ports.
|
||||
Keep `git.alexion.dev:2022` working by proxying that TCP stream through the reverse-proxy guest to Gitea.
|
||||
- Remote deploy helpers that run target-side `sudo` over SSH need a TTY.
|
||||
Use `ssh -t` unless the target command is guaranteed not to prompt for sudo credentials.
|
||||
|
||||
329
flake.nix
329
flake.nix
@@ -118,79 +118,6 @@
|
||||
touch $out
|
||||
'';
|
||||
|
||||
copyparty-reverse-proxy-trust-config =
|
||||
let
|
||||
pikachuConfig = self.nixosConfigurations.pikachu.config;
|
||||
serviceConfig = pikachuConfig.containers.copyparty.config.systemd.services.copyparty.serviceConfig;
|
||||
configScript = builtins.readFile serviceConfig.ExecStartPre;
|
||||
plainConfig =
|
||||
(lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
modules = [
|
||||
./modules/services/copyparty.nix
|
||||
{
|
||||
modules.services.copyparty = {
|
||||
enable = true;
|
||||
passwordFile = "/run/password";
|
||||
volumes."/" = {
|
||||
path = "/srv/files";
|
||||
access.A = [ "alexion" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
}).config;
|
||||
plainScript = builtins.readFile plainConfig.systemd.services.copyparty.serviceConfig.ExecStartPre;
|
||||
in
|
||||
pkgs.runCommand "copyparty-reverse-proxy-trust-config-check" { } ''
|
||||
${
|
||||
lib.optionalString (
|
||||
!(
|
||||
lib.hasInfix " rproxy: 1" configScript
|
||||
&& lib.hasInfix " xff-hdr: x-forwarded-for" configScript
|
||||
&& lib.hasInfix " xff-src: 10.23.20.109/32" configScript
|
||||
&& !(lib.hasInfix "rproxy" plainScript)
|
||||
&& !(lib.hasInfix "xff-hdr" plainScript)
|
||||
&& !(lib.hasInfix "xff-src" plainScript)
|
||||
)
|
||||
) "exit 1"
|
||||
}
|
||||
touch $out
|
||||
'';
|
||||
|
||||
copyparty-reverse-proxy-trust-requires-source =
|
||||
let
|
||||
testConfig =
|
||||
(lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
modules = [
|
||||
./modules/services/copyparty.nix
|
||||
{
|
||||
modules.services.copyparty = {
|
||||
enable = true;
|
||||
passwordFile = "/run/password";
|
||||
reverseProxy.enable = true;
|
||||
volumes."/" = {
|
||||
path = "/srv/files";
|
||||
access.A = [ "alexion" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
}).config;
|
||||
hasFailedTrustedSourcesAssertion = lib.any (
|
||||
assertion:
|
||||
assertion.assertion == false
|
||||
&& assertion.message == "modules.services.copyparty.reverseProxy.trustedSources must name at least one trusted proxy source when reverse-proxy support is enabled."
|
||||
) testConfig.assertions;
|
||||
in
|
||||
pkgs.runCommand "copyparty-reverse-proxy-trust-requires-source-check" { } ''
|
||||
${lib.optionalString (!hasFailedTrustedSourcesAssertion) "exit 1"}
|
||||
touch $out
|
||||
'';
|
||||
|
||||
reverse-proxy-basic-route =
|
||||
let
|
||||
testConfig =
|
||||
@@ -223,254 +150,24 @@
|
||||
|
||||
reverse-proxy-actualbudget-guest-route =
|
||||
let
|
||||
testConfig = self.nixosConfigurations.pikachu.config;
|
||||
testConfig =
|
||||
(self.nixosConfigurations.pikachu.extendModules {
|
||||
modules = [
|
||||
{
|
||||
guests.actualbudget.reverseProxy = {
|
||||
enable = true;
|
||||
host = "budget.alexion.dev";
|
||||
backend = "http://10.23.20.42:5006";
|
||||
};
|
||||
}
|
||||
];
|
||||
}).config;
|
||||
route = testConfig.modules.reverse-proxy.routes.actualbudget;
|
||||
vhost = testConfig.containers.reverse-proxy.config.services.nginx.virtualHosts."budget.alexion.dev";
|
||||
in
|
||||
pkgs.runCommand "reverse-proxy-actualbudget-guest-route-check" { } ''
|
||||
${
|
||||
lib.optionalString (
|
||||
!(
|
||||
route.host == "budget.alexion.dev"
|
||||
&& route.backend == null
|
||||
&& route.endpoint == "pikachu-actualbudget"
|
||||
&& route.port == 5006
|
||||
&& route.tls
|
||||
&& route.aliases == [ ]
|
||||
&& route.path == "/"
|
||||
&& !route.websockets
|
||||
&& vhost.locations."/".proxyPass == "http://10.23.20.42:5006"
|
||||
)
|
||||
) "exit 1"
|
||||
}
|
||||
touch $out
|
||||
'';
|
||||
|
||||
generic-registry-fleet-view =
|
||||
let
|
||||
hosts = {
|
||||
alpha = lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.registry = registry;
|
||||
modules = [
|
||||
self.lib.registry.module
|
||||
{
|
||||
registry.address.alpha.service = "10.0.0.5";
|
||||
system.stateVersion = "26.11";
|
||||
}
|
||||
];
|
||||
};
|
||||
beta = lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.registry = registry;
|
||||
modules = [
|
||||
self.lib.registry.module
|
||||
(
|
||||
{ registry, ... }:
|
||||
{
|
||||
environment.etc."registry-address-check".text = registry.address.alpha.service;
|
||||
system.stateVersion = "26.11";
|
||||
}
|
||||
)
|
||||
];
|
||||
};
|
||||
};
|
||||
registry = self.lib.registry.collect { inherit hosts; };
|
||||
betaConfig = hosts.beta.config;
|
||||
in
|
||||
pkgs.runCommand "generic-registry-fleet-view-check" { } ''
|
||||
${
|
||||
lib.optionalString (
|
||||
!(
|
||||
registry.address.alpha.service == "10.0.0.5"
|
||||
&& betaConfig.environment.etc."registry-address-check".text == "10.0.0.5"
|
||||
&& !(registry.address ? alpha-service)
|
||||
)
|
||||
) "exit 1"
|
||||
}
|
||||
touch $out
|
||||
'';
|
||||
|
||||
network-endpoint-fleet-registry =
|
||||
let
|
||||
hostEndpointConfig =
|
||||
lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
modules = [
|
||||
./modules/network.nix
|
||||
{
|
||||
modules.network.endpoint.address = "10.23.50.146";
|
||||
}
|
||||
];
|
||||
};
|
||||
guestEndpointConfig =
|
||||
lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
modules = [
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
./modules/network.nix
|
||||
./modules/reverse-proxy.nix
|
||||
(self.lib.guest { name = "copyparty"; })
|
||||
{
|
||||
modules.network.vlans = [ 20 ];
|
||||
guests.copyparty = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
endpoint.address = "10.23.20.126";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
fleetEndpoints = self.lib.networkEndpoints.collectFleetEndpoints {
|
||||
neogaia = hostEndpointConfig;
|
||||
pikachu = guestEndpointConfig;
|
||||
};
|
||||
in
|
||||
pkgs.runCommand "network-endpoint-fleet-registry-check" { } ''
|
||||
${
|
||||
lib.optionalString (
|
||||
!(
|
||||
fleetEndpoints.neogaia.address == "10.23.50.146"
|
||||
&& fleetEndpoints."pikachu-copyparty".address == "10.23.20.126"
|
||||
&& !(fleetEndpoints ? pikachu)
|
||||
&& guestEndpointConfig.config.containers.copyparty.config.systemd.network.networks."20-eth0".networkConfig.DHCP == "yes"
|
||||
)
|
||||
) "exit 1"
|
||||
}
|
||||
touch $out
|
||||
'';
|
||||
|
||||
reverse-proxy-fleet-routes =
|
||||
let
|
||||
routeHost =
|
||||
lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
modules = [
|
||||
./modules/reverse-proxy.nix
|
||||
{
|
||||
modules.reverse-proxy.routes.app = {
|
||||
host = "worker.alexion.dev";
|
||||
backend = "http://10.23.20.126:3923";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
endpoints = self.lib.networkEndpoints.collectFleetEndpoints {
|
||||
pikachu = self.nixosConfigurations.pikachu;
|
||||
};
|
||||
fleetRoutes = self.lib.reverseProxyRoutes.collectFleetRoutes {
|
||||
inherit routeHost;
|
||||
pikachu = self.nixosConfigurations.pikachu;
|
||||
} endpoints;
|
||||
testConfig =
|
||||
(self.nixosConfigurations.pikachu.extendModules {
|
||||
specialArgs.reverseProxyFleetRoutes = fleetRoutes;
|
||||
}).config;
|
||||
vhosts = testConfig.containers.reverse-proxy.config.services.nginx.virtualHosts;
|
||||
in
|
||||
pkgs.runCommand "reverse-proxy-fleet-routes-check" { } ''
|
||||
${
|
||||
lib.optionalString (
|
||||
!(
|
||||
fleetRoutes ? routeHost-app
|
||||
&& fleetRoutes ? pikachu-actualbudget
|
||||
&& vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
|
||||
&& vhosts."budget.alexion.dev".locations."/".proxyPass == "http://10.23.20.42:5006"
|
||||
&& vhosts."files.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
|
||||
)
|
||||
) "exit 1"
|
||||
}
|
||||
touch $out
|
||||
'';
|
||||
|
||||
reverse-proxy-endpoint-backends =
|
||||
let
|
||||
endpointHost =
|
||||
lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
modules = [
|
||||
./modules/network.nix
|
||||
{
|
||||
modules.network.endpoint.address = "10.23.50.146";
|
||||
}
|
||||
];
|
||||
};
|
||||
routeHost =
|
||||
lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
modules = [
|
||||
./modules/reverse-proxy.nix
|
||||
{
|
||||
modules.reverse-proxy.routes.app = {
|
||||
host = "worker.alexion.dev";
|
||||
endpoint = "worker";
|
||||
port = 8080;
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
guestRouteHost =
|
||||
lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
modules = [
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
./modules/network.nix
|
||||
./modules/reverse-proxy.nix
|
||||
(self.lib.guest { name = "files"; })
|
||||
{
|
||||
networking.hostName = "pikachu";
|
||||
modules.network.vlans = [ 20 ];
|
||||
guests.files = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
endpoint.address = "10.23.20.126";
|
||||
reverseProxy = {
|
||||
enable = true;
|
||||
host = "files.alexion.dev";
|
||||
port = 3923;
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
endpoints = self.lib.networkEndpoints.collectFleetEndpoints {
|
||||
worker = endpointHost;
|
||||
pikachu = guestRouteHost;
|
||||
};
|
||||
fleetRoutes = self.lib.reverseProxyRoutes.collectFleetRoutes {
|
||||
inherit routeHost;
|
||||
pikachu = guestRouteHost;
|
||||
} endpoints;
|
||||
testConfig =
|
||||
(lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
modules = [
|
||||
./modules/reverse-proxy.nix
|
||||
{
|
||||
modules.reverse-proxy = {
|
||||
enable = true;
|
||||
routes = fleetRoutes;
|
||||
};
|
||||
}
|
||||
];
|
||||
}).config;
|
||||
vhosts = testConfig.services.nginx.virtualHosts;
|
||||
in
|
||||
pkgs.runCommand "reverse-proxy-endpoint-backends-check" { } ''
|
||||
${
|
||||
lib.optionalString (
|
||||
!(
|
||||
vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.50.146:8080"
|
||||
&& fleetRoutes."pikachu-files".endpoint == "pikachu-files"
|
||||
&& vhosts."files.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
|
||||
)
|
||||
!(route.host == "budget.alexion.dev" && route.backend == "http://10.23.20.42:5006")
|
||||
) "exit 1"
|
||||
}
|
||||
touch $out
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
config,
|
||||
lib,
|
||||
my,
|
||||
reverseProxyFleetRoutes ? config.modules.reverse-proxy.routes,
|
||||
...
|
||||
}:
|
||||
let
|
||||
@@ -35,7 +34,7 @@ in
|
||||
interior = {
|
||||
modules.reverse-proxy = {
|
||||
enable = true;
|
||||
routes = reverseProxyFleetRoutes // legacyRoutes;
|
||||
routes = config.modules.reverse-proxy.routes // legacyRoutes;
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 2022 ];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ config, pkgs, ... }:
|
||||
{ pkgs, ... }:
|
||||
# pikachu — AZW ME Pro server.
|
||||
# Disk layout is in ./disk.nix.
|
||||
# `fileSystems` for the root disk are derived from it.
|
||||
@@ -79,11 +79,10 @@
|
||||
vlan = 20;
|
||||
mac = "BC:24:11:C1:CD:28";
|
||||
dataPath = "/pikachu/data/actualbudget";
|
||||
endpoint.address = "10.23.20.42";
|
||||
reverseProxy = {
|
||||
enable = true;
|
||||
host = "budget.alexion.dev";
|
||||
port = 5006;
|
||||
backend = "http://10.23.20.42:5006";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -91,7 +90,6 @@
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
mac = "02:98:64:5a:07:4f";
|
||||
endpoint.address = "10.23.20.109";
|
||||
};
|
||||
|
||||
guests.copyparty = {
|
||||
@@ -99,12 +97,11 @@
|
||||
vlan = 20;
|
||||
mac = "02:23:20:39:23:01";
|
||||
statePath = "/pikachu/data/copyparty";
|
||||
endpoint.address = "10.23.20.126";
|
||||
reverseProxy = {
|
||||
enable = true;
|
||||
host = "files.alexion.dev";
|
||||
port = 3923;
|
||||
trustedSources = [ "${config.modules.network.endpoints."reverse-proxy".address}/32" ];
|
||||
backend = "http://10.23.20.126:3923";
|
||||
trustedSources = [ "10.23.20.109/32" ];
|
||||
};
|
||||
volumes = {
|
||||
"/inbox" = {
|
||||
|
||||
@@ -14,8 +14,6 @@ let
|
||||
;
|
||||
|
||||
serviceIdentityRegistry = import ./service-identities.nix { inherit lib; };
|
||||
registry = import ./registry.nix { inherit lib self; };
|
||||
networkEndpoints = import ./network-endpoints.nix { inherit lib; };
|
||||
reverseProxyRoutes = import ./reverse-proxy-routes.nix { inherit lib; };
|
||||
|
||||
collectNixFiles =
|
||||
@@ -35,16 +33,10 @@ let
|
||||
) (builtins.readDir dir)
|
||||
);
|
||||
|
||||
specialArgs =
|
||||
let
|
||||
networkFleetEndpoints = self.lib.networkEndpoints.collectFleetEndpoints self.nixosConfigurations;
|
||||
in
|
||||
{
|
||||
inherit inputs networkFleetEndpoints self;
|
||||
my = self.lib;
|
||||
registry = self.lib.registry.collect { hosts = self.nixosConfigurations; };
|
||||
reverseProxyFleetRoutes = self.lib.reverseProxyRoutes.collectFleetRoutes self.nixosConfigurations networkFleetEndpoints;
|
||||
};
|
||||
specialArgs = {
|
||||
inherit inputs;
|
||||
my = self.lib;
|
||||
};
|
||||
|
||||
bridgeName = id: "br-vlan${toString id}";
|
||||
|
||||
@@ -73,7 +65,6 @@ let
|
||||
inputs.disko.nixosModules.disko
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
inputs.stylix.nixosModules.stylix
|
||||
registry.module
|
||||
(self + "/system.nix")
|
||||
(self + "/hosts/${hostName}")
|
||||
{ networking.hostName = hostName; }
|
||||
@@ -93,8 +84,6 @@ in
|
||||
mkHost
|
||||
mkHosts
|
||||
bridgeName
|
||||
registry
|
||||
networkEndpoints
|
||||
reverseProxyRoutes
|
||||
;
|
||||
|
||||
|
||||
@@ -131,18 +131,6 @@ let
|
||||
guest takes its address by DHCP, keeping IP management at the router.
|
||||
'';
|
||||
};
|
||||
endpoint = {
|
||||
address = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "10.23.20.126";
|
||||
description = ''
|
||||
Expected IP address assigned outside NixOS for this guest's
|
||||
network endpoint. Left null, this guest contributes no endpoint to
|
||||
the fleet endpoint registry.
|
||||
'';
|
||||
};
|
||||
};
|
||||
reverseProxy = reverseProxyRoutes.mkRouteOptions {
|
||||
subject = "guest route";
|
||||
enableDefault = false;
|
||||
@@ -290,17 +278,8 @@ let
|
||||
config = lib.mkIf cfg.enable {
|
||||
sops.secrets = lib.genAttrs cfg.secrets (_: { });
|
||||
|
||||
modules.network.endpoints.${machineName} = lib.mkIf (cfg.endpoint.address != null) {
|
||||
inherit (cfg.endpoint) address;
|
||||
};
|
||||
|
||||
modules.reverse-proxy.routes.${machineName} = lib.mkIf cfg.reverseProxy.enable (
|
||||
reverseProxyRoutes.routeFields (
|
||||
cfg.reverseProxy
|
||||
// lib.optionalAttrs (cfg.reverseProxy.backend == null && cfg.reverseProxy.endpoint == null) {
|
||||
endpoint = "${config.networking.hostName}-${machineName}";
|
||||
}
|
||||
)
|
||||
reverseProxyRoutes.routeFields cfg.reverseProxy
|
||||
);
|
||||
|
||||
assertions = [
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
{ lib }:
|
||||
let
|
||||
endpointOptions = {
|
||||
address = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "10.23.20.126";
|
||||
description = "Expected IP address assigned outside NixOS for this network endpoint.";
|
||||
};
|
||||
};
|
||||
|
||||
endpointType = lib.types.submodule {
|
||||
options = endpointOptions;
|
||||
};
|
||||
|
||||
endpointFields = endpoint: {
|
||||
inherit (endpoint) address;
|
||||
};
|
||||
|
||||
collectFleetEndpoints =
|
||||
hosts:
|
||||
lib.concatMapAttrs (
|
||||
hostName: host:
|
||||
let
|
||||
cfg = host.config.modules.network;
|
||||
hostEndpoint = lib.optionalAttrs (cfg.endpoint.address != null) {
|
||||
${hostName} = endpointFields cfg.endpoint;
|
||||
};
|
||||
guestEndpoints = lib.mapAttrs' (
|
||||
endpointName: endpoint: lib.nameValuePair "${hostName}-${endpointName}" (endpointFields endpoint)
|
||||
) cfg.endpoints;
|
||||
in
|
||||
hostEndpoint // guestEndpoints
|
||||
) hosts;
|
||||
in
|
||||
{
|
||||
inherit
|
||||
collectFleetEndpoints
|
||||
endpointFields
|
||||
endpointOptions
|
||||
endpointType
|
||||
;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
{ lib, self }:
|
||||
let
|
||||
inherit (lib)
|
||||
filterAttrs
|
||||
hasSuffix
|
||||
mapAttrs
|
||||
mapAttrs'
|
||||
mkOption
|
||||
nameValuePair
|
||||
removeSuffix
|
||||
types
|
||||
;
|
||||
|
||||
registryName = fileName: removeSuffix ".nix" fileName;
|
||||
|
||||
registryOption = registry: mkOption {
|
||||
type = types.attrsOf (types.attrsOf registry.type);
|
||||
default = { };
|
||||
description = ''
|
||||
Lazily collected fleet-visible registry entries, nested by host and entry name.
|
||||
'';
|
||||
};
|
||||
|
||||
definitions = mapAttrs' (
|
||||
name: type:
|
||||
let
|
||||
path = self + "/registry/${name}";
|
||||
in
|
||||
nameValuePair (registryName name) (import path { inherit lib; })
|
||||
) (filterAttrs (name: type: type == "regular" && hasSuffix ".nix" name) (builtins.readDir (self + "/registry")));
|
||||
in
|
||||
{
|
||||
inherit definitions;
|
||||
|
||||
module = {
|
||||
options.registry = mapAttrs (_name: registry: registryOption registry) definitions;
|
||||
};
|
||||
|
||||
collect =
|
||||
{ hosts }:
|
||||
mapAttrs (
|
||||
registryName: _registry:
|
||||
mapAttrs (hostName: host: host.config.registry.${registryName}.${hostName} or { }) hosts
|
||||
) definitions;
|
||||
}
|
||||
@@ -37,28 +37,8 @@ let
|
||||
};
|
||||
|
||||
backend = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Full HTTP backend URL that receives proxied traffic.";
|
||||
};
|
||||
|
||||
endpoint = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Fleet network endpoint key used to derive the backend URL.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.ints.between 1 65535);
|
||||
default = null;
|
||||
example = 3923;
|
||||
description = "Backend TCP port used with the fleet network endpoint.";
|
||||
};
|
||||
|
||||
scheme = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http";
|
||||
description = "Backend URL scheme used with the fleet network endpoint.";
|
||||
description = "Full HTTP backend URL that receives proxied traffic.";
|
||||
};
|
||||
|
||||
tls = lib.mkOption {
|
||||
@@ -80,45 +60,13 @@ let
|
||||
aliases
|
||||
path
|
||||
backend
|
||||
endpoint
|
||||
port
|
||||
scheme
|
||||
tls
|
||||
websockets
|
||||
;
|
||||
};
|
||||
|
||||
endpointBackend = endpoints: route:
|
||||
let
|
||||
endpoint = endpoints.${route.endpoint} or (
|
||||
throw "Unknown reverse-proxy endpoint `${route.endpoint}`. Known endpoints: ${lib.concatStringsSep ", " (lib.attrNames endpoints)}"
|
||||
);
|
||||
in
|
||||
"${route.scheme}://${endpoint.address}:${toString route.port}";
|
||||
|
||||
resolvedRouteFields = endpoints: route:
|
||||
let
|
||||
routeWithBackend =
|
||||
if route.backend != null then
|
||||
route
|
||||
else if route.endpoint != null && route.port != null then
|
||||
route // { backend = endpointBackend endpoints route; }
|
||||
else
|
||||
throw "Reverse-proxy route `${route.host}${route.path}` needs either backend or endpoint plus port.";
|
||||
in
|
||||
routeFields routeWithBackend;
|
||||
|
||||
collectFleetRoutes =
|
||||
hosts: endpoints:
|
||||
lib.concatMapAttrs (
|
||||
hostName: host:
|
||||
lib.mapAttrs' (
|
||||
routeName: route: lib.nameValuePair "${hostName}-${routeName}" (resolvedRouteFields endpoints route)
|
||||
) host.config.modules.reverse-proxy.routes
|
||||
) hosts;
|
||||
in
|
||||
{
|
||||
inherit collectFleetRoutes mkRouteOptions resolvedRouteFields routeFields;
|
||||
inherit mkRouteOptions routeFields;
|
||||
|
||||
routeType = lib.types.submodule (
|
||||
{ name, ... }:
|
||||
|
||||
@@ -11,38 +11,6 @@ let
|
||||
|
||||
# The hyprlock this module installs, used by the lock keybind below.
|
||||
hyprlock = "${config.home-manager.users.${user}.programs.hyprlock.package}/bin/hyprlock";
|
||||
|
||||
sleepIcon = builtins.fromJSON ''"\udb81\udcb2"'';
|
||||
sleepOffIcon = builtins.fromJSON ''"\udb81\udcb3"'';
|
||||
|
||||
idleLockToggle = pkgs.writeShellScript "lockscreen-idle-inhibit-toggle" ''
|
||||
unit=lockscreen-idle-inhibit.service
|
||||
|
||||
refresh_waybar() {
|
||||
${pkgs.procps}/bin/pkill -RTMIN+9 waybar >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
if ${pkgs.systemd}/bin/systemctl --user is-active --quiet "$unit"; then
|
||||
${pkgs.systemd}/bin/systemctl --user stop "$unit"
|
||||
refresh_waybar
|
||||
${pkgs.swayosd}/bin/swayosd-client --custom-message "${sleepIcon}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ${pkgs.systemd}/bin/systemd-run \
|
||||
--user \
|
||||
--collect \
|
||||
--unit=lockscreen-idle-inhibit \
|
||||
--description="Disable idle-triggered screen locking" \
|
||||
${pkgs.systemd}/bin/systemd-inhibit \
|
||||
--what=idle \
|
||||
--who=lockscreen-hotkey \
|
||||
--why="lock screen disabled by hotkey" \
|
||||
${pkgs.coreutils}/bin/sleep infinity >/dev/null; then
|
||||
refresh_waybar
|
||||
${pkgs.swayosd}/bin/swayosd-client --custom-message "${sleepOffIcon}"
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.modules.desktop.hyprland.hyprlock.enable = lib.mkEnableOption "the hyprlock lock screen";
|
||||
@@ -87,7 +55,6 @@ in
|
||||
# hyprlock never stacks over the first.
|
||||
wayland.windowManager.hyprland.settings.bind = [
|
||||
"$mod, X, exec, ${pkgs.procps}/bin/pidof hyprlock || ${hyprlock}"
|
||||
"$mod CTRL, X, exec, ${idleLockToggle}"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -12,7 +12,6 @@ let
|
||||
# A Nerd Font Private-Use-Area codepoint as a literal character, decoded
|
||||
# through JSON so the glyph survives as bytes rather than an editor paste.
|
||||
g = code: builtins.fromJSON ''"\u${code}"'';
|
||||
sleepOffIcon = builtins.fromJSON ''"\udb81\udcb3"'';
|
||||
|
||||
# Renders and toggles mako's do-not-disturb mode.
|
||||
# With no mako daemon answering, the status reads "notifications on", keeping
|
||||
@@ -39,14 +38,6 @@ let
|
||||
printf '{"text":"","class":"idle"}\n'
|
||||
fi
|
||||
'';
|
||||
|
||||
idleLockStatus = pkgs.writeShellScript "waybar-idle-lock-status" ''
|
||||
if ${pkgs.systemd}/bin/systemctl --user is-active --quiet lockscreen-idle-inhibit.service; then
|
||||
printf '{"text":"${sleepOffIcon}","tooltip":"Idle locking inhibited","class":"inhibited"}\n'
|
||||
else
|
||||
printf '{"text":"","class":"idle"}\n'
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.modules.desktop.waybar.enable = lib.mkEnableOption "the Waybar status bar";
|
||||
@@ -72,7 +63,6 @@ in
|
||||
modules-center = [ "clock" ];
|
||||
modules-right = [
|
||||
"custom/recording"
|
||||
"custom/idle-lock"
|
||||
"mpris"
|
||||
"wireplumber"
|
||||
"network"
|
||||
@@ -165,13 +155,6 @@ in
|
||||
exec = "${recStatus}";
|
||||
interval = 1;
|
||||
};
|
||||
|
||||
"custom/idle-lock" = {
|
||||
return-type = "json";
|
||||
exec = "${idleLockStatus}";
|
||||
interval = "once";
|
||||
signal = 9;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -69,7 +69,6 @@ in
|
||||
vim = "nvim";
|
||||
cp = "cp -v";
|
||||
tmx = "tmux new-session -A -s";
|
||||
hs = "herdr --session";
|
||||
|
||||
tarnow = "tar -acf ";
|
||||
untar = "tar -zxvf ";
|
||||
@@ -121,11 +120,6 @@ in
|
||||
description = "Copy a file, or recursively copy a source directory into a destination";
|
||||
body = builtins.readFile ./functions/copy.fish;
|
||||
};
|
||||
|
||||
deploy-to = {
|
||||
description = "Deploy the current dotfiles branch to a host through target-local nixos-rebuild";
|
||||
body = builtins.readFile ./functions/deploy-to.fish;
|
||||
};
|
||||
};
|
||||
|
||||
# Rendered by home-manager into ~/.config/fish/config.fish.
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
if test (count $argv) -lt 1; or test (count $argv) -gt 2
|
||||
echo "usage: deploy-to <host> [branch]" >&2
|
||||
return 2
|
||||
end
|
||||
|
||||
set host $argv[1]
|
||||
set branch $argv[2]
|
||||
|
||||
if test -z "$branch"
|
||||
set branch (git branch --show-current)
|
||||
end
|
||||
|
||||
if test -z "$branch"
|
||||
echo "deploy-to: could not determine the current branch" >&2
|
||||
return 2
|
||||
end
|
||||
|
||||
git rev-parse --show-toplevel >/dev/null 2>&1
|
||||
or begin
|
||||
echo "deploy-to: run this from a git checkout" >&2
|
||||
return 2
|
||||
end
|
||||
|
||||
set dirty_lines (git status --porcelain)
|
||||
if test (count $dirty_lines) -gt 0
|
||||
echo "deploy-to: refusing to deploy with a dirty local worktree" >&2
|
||||
return 1
|
||||
end
|
||||
|
||||
git fetch origin
|
||||
or return $status
|
||||
|
||||
set remote_ref refs/remotes/origin/$branch
|
||||
git rev-parse --verify --quiet $remote_ref >/dev/null
|
||||
or begin
|
||||
echo "deploy-to: origin/$branch does not exist" >&2
|
||||
return 1
|
||||
end
|
||||
|
||||
set local_head (git rev-parse HEAD)
|
||||
set remote_head (git rev-parse $remote_ref)
|
||||
|
||||
if test "$local_head" != "$remote_head"
|
||||
echo "deploy-to: local HEAD differs from origin/$branch" >&2
|
||||
echo "deploy-to: push or reset the branch before deploying" >&2
|
||||
return 1
|
||||
end
|
||||
|
||||
set escaped_branch (string escape -- $branch)
|
||||
set escaped_host (string escape -- $host)
|
||||
set remote_command "cd ~/wrk/dotfiles && git fetch origin && git switch -- $escaped_branch && git reset --hard origin/$escaped_branch && sudo nixos-rebuild switch --flake .#$escaped_host --print-build-logs"
|
||||
|
||||
ssh -t $host $remote_command
|
||||
@@ -43,28 +43,6 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
endpoint = {
|
||||
address = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "10.23.50.146";
|
||||
description = ''
|
||||
Expected IP address assigned outside NixOS for this host's network
|
||||
endpoint. Left null, this host contributes no host endpoint to the
|
||||
fleet endpoint registry.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
endpoints = lib.mkOption {
|
||||
type = lib.types.attrsOf my.networkEndpoints.endpointType;
|
||||
default = { };
|
||||
description = ''
|
||||
Network endpoint declarations contributed by placed workloads on this
|
||||
host, keyed by their local stable endpoint name.
|
||||
'';
|
||||
};
|
||||
|
||||
management = {
|
||||
vlan = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.ints.between 1 4094);
|
||||
|
||||
@@ -20,8 +20,6 @@ let
|
||||
in
|
||||
lib.length paths != lib.length (lib.unique paths);
|
||||
|
||||
hasUnresolvedBackend = route: route.backend == null;
|
||||
|
||||
mkLocation = route: {
|
||||
proxyPass = route.backend;
|
||||
proxyWebsockets = route.websockets;
|
||||
@@ -36,9 +34,7 @@ let
|
||||
enableACME = rootRoute.tls;
|
||||
serverAliases = rootRoute.aliases;
|
||||
locations = lib.listToAttrs (
|
||||
map (route: lib.nameValuePair route.path (mkLocation route)) (
|
||||
builtins.filter (route: route.backend != null) routes
|
||||
)
|
||||
map (route: lib.nameValuePair route.path (mkLocation route)) routes
|
||||
);
|
||||
};
|
||||
in
|
||||
@@ -62,11 +58,7 @@ in
|
||||
++ lib.mapAttrsToList (host: routes: {
|
||||
assertion = !hasDuplicatePaths routes;
|
||||
message = "modules.reverse-proxy.routes for ${host} must not declare the same path more than once.";
|
||||
}) routesByHost
|
||||
++ map (route: {
|
||||
assertion = !hasUnresolvedBackend route;
|
||||
message = "modules.reverse-proxy.routes for ${route.host}${route.path} must resolve to a backend URL before actualization.";
|
||||
}) (builtins.filter hasUnresolvedBackend (lib.attrValues enabledRoutes));
|
||||
}) routesByHost;
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
80
|
||||
@@ -80,13 +72,6 @@ in
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
commonHttpConfig = ''
|
||||
server {
|
||||
listen 0.0.0.0:443 ssl default_server;
|
||||
listen [::0]:443 ssl default_server;
|
||||
ssl_reject_handshake on;
|
||||
}
|
||||
'';
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
|
||||
105
prototypes/copyparty-rproxy-config/run.sh
Executable file
105
prototypes/copyparty-rproxy-config/run.sh
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
bold=$'\033[1m'
|
||||
reset=$'\033[0m'
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
mkdir -p "$tmp/state" "$tmp/root"
|
||||
|
||||
printf '%sQuestion%s\n' "$bold" "$reset"
|
||||
printf 'Does the proposed copyparty reverse-proxy config parse and make a proxied HTTPS-origin login-shaped request pass the CORS gate?\n\n'
|
||||
|
||||
printf '%sNix-generated config fragment%s\n' "$bold" "$reset"
|
||||
config_script=$(nix eval --raw '.#nixosConfigurations.pikachu.config.containers.copyparty.config.systemd.services.copyparty.serviceConfig.ExecStartPre')
|
||||
awk '/\[global\]/{show=1} /\[accounts\]/{show=0} show {print}' "$config_script" | grep -E 'rproxy|xff-hdr|xff-src' || true
|
||||
|
||||
pkg=$(nix eval --raw '.#nixosConfigurations.pikachu.config.containers.copyparty.config.modules.services.copyparty.package.outPath')
|
||||
|
||||
make_conf() {
|
||||
local file=$1 port=$2 rproxy=$3
|
||||
cat > "$file" <<EOF
|
||||
[global]
|
||||
i: 127.0.0.1
|
||||
p: $port
|
||||
usernames
|
||||
http-only
|
||||
no-crt
|
||||
no-thumb
|
||||
no-snap
|
||||
no-rescan
|
||||
ses-db: $tmp/state/sessions-$port.db
|
||||
chpw-db: $tmp/state/chpw-$port.json
|
||||
shr-db: $tmp/state/shares-$port.db
|
||||
$rproxy
|
||||
|
||||
[accounts]
|
||||
alexion: prototype-password
|
||||
|
||||
[/]
|
||||
$tmp/root
|
||||
accs:
|
||||
A: alexion
|
||||
EOF
|
||||
}
|
||||
|
||||
start_server() {
|
||||
local conf=$1 log=$2
|
||||
"$pkg/bin/copyparty" -c "$conf" >"$log" 2>&1 &
|
||||
local pid=$!
|
||||
for _ in {1..50}; do
|
||||
if grep -q 'listening' "$log"; then
|
||||
printf '%s' "$pid"
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
cat "$log"
|
||||
kill "$pid" 2>/dev/null || true
|
||||
return 1
|
||||
}
|
||||
|
||||
request() {
|
||||
local label=$1 port=$2 host=$3 proto=${4:-}
|
||||
local headers="$tmp/$label.headers"
|
||||
local body="$tmp/$label.body"
|
||||
local args=(
|
||||
-sS
|
||||
-D "$headers"
|
||||
-o "$body"
|
||||
-X POST
|
||||
-H "Host: $host"
|
||||
-H 'Origin: https://files.alexion.dev'
|
||||
-H 'X-Forwarded-For: 10.23.20.20'
|
||||
)
|
||||
if [[ -n "$proto" ]]; then
|
||||
args+=(-H "X-Forwarded-Proto: $proto")
|
||||
fi
|
||||
curl "${args[@]}" "http://127.0.0.1:$port/?pw=alexion:prototype-password" >/dev/null || true
|
||||
printf '%-24s %s %s\n' "$label" "$(head -1 "$headers" | tr -d '\r')" "$(head -1 "$body")"
|
||||
}
|
||||
|
||||
rproxy_config=' rproxy: 1
|
||||
xff-hdr: x-forwarded-for
|
||||
xff-src: 127.0.0.1/32'
|
||||
make_conf "$tmp/rproxy.conf" 39234 "$rproxy_config"
|
||||
pid=$(start_server "$tmp/rproxy.conf" "$tmp/rproxy.log")
|
||||
|
||||
printf '\n%sCORS gate probes%s\n' "$bold" "$reset"
|
||||
request with-forwarded-https 39234 files.alexion.dev https
|
||||
request missing-forwarded-proto 39234 files.alexion.dev
|
||||
request wrong-forwarded-host 39234 '10.23.20.126:3923' https
|
||||
|
||||
kill "$pid" 2>/dev/null || true
|
||||
wait "$pid" 2>/dev/null || true
|
||||
|
||||
printf '\n%sRelevant server log%s\n' "$bold" "$reset"
|
||||
grep -E 'cors-reject|POST|http405|got proxied request|untrusted source|listening' "$tmp/rproxy.log" || true
|
||||
|
||||
printf '\n%sNginx proxy headers%s\n' "$bold" "$reset"
|
||||
toplevel=$(nix build --print-out-paths '.#nixosConfigurations.pikachu.config.containers.reverse-proxy.config.system.build.toplevel' --no-link)
|
||||
nginx_conf=$(readlink -f "$toplevel/etc/nginx/nginx.conf")
|
||||
include_path=$(awk '/server_name files\.alexion\.dev;/{in_server=1} in_server && /include .*recommended-proxy_set_header/{print $2; exit}' "$nginx_conf" | tr -d ';')
|
||||
cat "$include_path"
|
||||
@@ -1,4 +0,0 @@
|
||||
{ lib }:
|
||||
{
|
||||
type = lib.types.str;
|
||||
}
|
||||
Reference in New Issue
Block a user