Compare commits
2 Commits
861bbbbeb0
...
throwaway/
| Author | SHA1 | Date | |
|---|---|---|---|
| 4598993dfe | |||
| 680cdb75eb |
@@ -99,6 +99,5 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla
|
||||
- The current Steam desktop client is an XWayland application.
|
||||
Its CEF windows do not support Ozone and Steam composites them into an SDL surface with X11 extensions, so SDL Wayland selectors do not make the visible client native Wayland.
|
||||
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 new reverse-proxy guest.
|
||||
That affects non-HTTP ports too: `git.alexion.dev:2022` resolves to the reverse-proxy guest on LAN, so Git SSH times out unless the reverse-proxy guest also handles the Gitea SSH TCP stream or AdGuard has a more specific exception.
|
||||
A temporary command workaround is `GIT_SSH_COMMAND='ssh -o HostName=10.23.20.45 -o HostKeyAlias=[git.alexion.dev]:2022' git fetch origin main`.
|
||||
- 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.
|
||||
|
||||
40
flake.nix
40
flake.nix
@@ -173,46 +173,6 @@
|
||||
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";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
fleetRoutes = self.lib.reverseProxyRoutes.collectFleetRoutes {
|
||||
inherit routeHost;
|
||||
pikachu = self.nixosConfigurations.pikachu;
|
||||
};
|
||||
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"
|
||||
)
|
||||
) "exit 1"
|
||||
}
|
||||
touch $out
|
||||
'';
|
||||
|
||||
reverse-proxy-secondary-path-requires-root =
|
||||
let
|
||||
testConfig =
|
||||
|
||||
@@ -43,6 +43,10 @@ in
|
||||
enable = true;
|
||||
inherit (cfg) accountName stateDir;
|
||||
passwordFile = "/run/secrets/${cfg.passwordSecret}";
|
||||
reverseProxy = {
|
||||
enable = cfg.reverseProxy.enable;
|
||||
inherit (cfg.reverseProxy) trustedSources;
|
||||
};
|
||||
volumes = lib.mapAttrs (_urlPath: volume: {
|
||||
inherit (volume) path access flags;
|
||||
}) cfg.volumes;
|
||||
@@ -77,6 +81,12 @@ in
|
||||
description = "Host path bind-mounted as Copyparty's state directory.";
|
||||
};
|
||||
|
||||
reverseProxy.trustedSources = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "IP ranges that may supply Copyparty's forwarded client IP header.";
|
||||
};
|
||||
|
||||
volumes = lib.mkOption {
|
||||
type = lib.types.attrsOf volumeType;
|
||||
default = { };
|
||||
|
||||
@@ -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 ];
|
||||
|
||||
@@ -97,6 +97,12 @@
|
||||
vlan = 20;
|
||||
mac = "02:23:20:39:23:01";
|
||||
statePath = "/pikachu/data/copyparty";
|
||||
reverseProxy = {
|
||||
enable = true;
|
||||
host = "files.alexion.dev";
|
||||
backend = "http://10.23.20.126:3923";
|
||||
trustedSources = [ "10.23.20.109/32" ];
|
||||
};
|
||||
volumes = {
|
||||
"/inbox" = {
|
||||
hostPath = "/pikachu/__inbox";
|
||||
|
||||
@@ -34,9 +34,8 @@ let
|
||||
);
|
||||
|
||||
specialArgs = {
|
||||
inherit inputs self;
|
||||
inherit inputs;
|
||||
my = self.lib;
|
||||
reverseProxyFleetRoutes = self.lib.reverseProxyRoutes.collectFleetRoutes self.nixosConfigurations;
|
||||
};
|
||||
|
||||
bridgeName = id: "br-vlan${toString id}";
|
||||
|
||||
@@ -64,18 +64,9 @@ let
|
||||
websockets
|
||||
;
|
||||
};
|
||||
|
||||
collectFleetRoutes =
|
||||
hosts:
|
||||
lib.concatMapAttrs (
|
||||
hostName: host:
|
||||
lib.mapAttrs' (
|
||||
routeName: route: lib.nameValuePair "${hostName}-${routeName}" route
|
||||
) host.config.modules.reverse-proxy.routes
|
||||
) hosts;
|
||||
in
|
||||
{
|
||||
inherit collectFleetRoutes mkRouteOptions routeFields;
|
||||
inherit mkRouteOptions routeFields;
|
||||
|
||||
routeType = lib.types.submodule (
|
||||
{ name, ... }:
|
||||
|
||||
@@ -72,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;
|
||||
|
||||
@@ -48,6 +48,14 @@ let
|
||||
''}
|
||||
'';
|
||||
|
||||
renderTrustedSource = source: " xff-src: ${source}";
|
||||
|
||||
renderedReverseProxy = lib.optionalString cfg.reverseProxy.enable ''
|
||||
rproxy: 1
|
||||
xff-hdr: x-forwarded-for
|
||||
${lib.concatStringsSep "\n" (map renderTrustedSource cfg.reverseProxy.trustedSources)}
|
||||
'';
|
||||
|
||||
renderedVolumes = lib.concatStringsSep "\n" (lib.mapAttrsToList renderVolume cfg.volumes);
|
||||
|
||||
configScript = pkgs.writeShellScript "copyparty-config" ''
|
||||
@@ -68,7 +76,7 @@ let
|
||||
ses-db: ${cfg.stateDir}/sessions.db
|
||||
chpw-db: ${cfg.stateDir}/chpw.json
|
||||
shr-db: ${cfg.stateDir}/shares.db
|
||||
|
||||
${renderedReverseProxy}
|
||||
[accounts]
|
||||
${cfg.accountName}: $password
|
||||
|
||||
@@ -128,6 +136,16 @@ in
|
||||
default = { };
|
||||
description = "Copyparty volumes keyed by their URL path.";
|
||||
};
|
||||
|
||||
reverseProxy = {
|
||||
enable = lib.mkEnableOption "reverse-proxy trust settings for Copyparty";
|
||||
|
||||
trustedSources = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "IP ranges that may supply Copyparty's forwarded client IP header.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
@@ -136,6 +154,10 @@ in
|
||||
assertion = cfg.volumes != { };
|
||||
message = "modules.services.copyparty.volumes must declare at least one served volume.";
|
||||
}
|
||||
{
|
||||
assertion = !cfg.reverseProxy.enable || cfg.reverseProxy.trustedSources != [ ];
|
||||
message = "modules.services.copyparty.reverseProxy.trustedSources must name at least one trusted proxy source when reverse-proxy support is enabled.";
|
||||
}
|
||||
];
|
||||
|
||||
users.users.${cfg.user} = {
|
||||
|
||||
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"
|
||||
Reference in New Issue
Block a user