From 5722ddab9f8b7bb043b9715ca6740f44a3b56692 Mon Sep 17 00:00:00 2001 From: Thomas Walker Lynch Date: Sun, 14 Sep 2025 22:05:00 -0700 Subject: [PATCH] including the unbound scripts --- developer/source/DNS/README.md | 70 +++++ developer/source/DNS/deloy.sh | 8 + .../install_staged_tree.py | 0 .../DNS/stage/etc/nftables.d/30-dnsredir.nft | 14 + .../stage/etc/systemd/system/unbound@.service | 20 ++ .../DNS/stage/etc/unbound/unbound-US.conf | 40 +++ .../DNS/stage/etc/unbound/unbound-x6.conf | 40 +++ .../{mockup => source/cc}/sqlite/schema.sql | 0 developer/source/deprecated/.githolder | 0 developer/source/deprecated/.gitignore | 2 + .../{io => device_management}/bestow_audio.sh | 0 .../.gitignore | 0 .../db/.gitignore | 0 .../db_bind_user_to_iface.py | 0 .../db_checks.py | 0 .../db_init_StanleyPark.py | 0 .../db_init_iface.py | 0 .../db_init_iface_US.py | 0 .../db_init_iface_x6.py | 0 .../db_init_ip_iface_addr_assign.py | 0 .../db_init_ip_table_registration.py | 0 .../db_init_route_defaults.py | 0 .../db_init_server_US.py | 0 .../db_init_server_incommon.py | 0 .../db_init_server_x6.py | 0 .../db_schema.sql | 0 .../db_schema_load.sh | 0 .../db_wipe.py | 0 .../deploy_StanleyPark.py | 0 .../deprecated/.gitignore | 0 .../doc_IP_terminaology.org | 0 .../doc_StanleyPark.org | 0 .../doc_config.org | 0 .../doc_keys.org | 0 .../doc_stage_progs.org | 0 .../iface_down.py | 0 .../iface_status.py | 0 .../iface_up.sh | 0 .../incommon.py | 0 .../inspect.sh | 0 .../inspect_1.py | 0 .../inspect_client_public_key.py | 0 .../tunnel-client/install_staged_tree.py | 245 ++++++++++++++++++ .../key/.gitignore | 0 .../key_client_generate.py | 0 .../key_server_set.py | 0 .../ls_iface.py | 0 .../ls_key.py | 0 .../ls_server.py | 0 .../ls_server_setting.py | 0 .../ls_servers.sh | 0 .../ls_user.py | 0 .../manual_reference.org | 0 .../manual_user.org | 0 .../mothball/stage/.gitignore | 0 .../mothball/stage_IP_routes_script.py | 0 .../mothball/stage_IP_rules_script.py | 0 .../mothball/stage_StanleyPark.py | 0 .../mothball/stage_UID_routes.py | 0 .../mothball/stage_list_clients.py | 0 .../mothball/stage_list_uid.py | 0 .../mothball/stage_populate.py | 0 .../mothball/stage_preferred_server.py | 0 .../mothball/stage_wg_conf.py | 0 .../mothball/stage_wg_unit_IP_scripts.py | 0 .../mothball/stage_wipe.py | 0 .../scratchpad/.gitignore | 0 .../stage/.gitignore | 0 .../stage_IP_apply_script.py | 0 .../stage_StanleyPark.py | 0 .../stage_client.py | 0 .../stage_wg_conf.py | 0 .../stage_wipe.py | 0 .../start_iface.py | 0 .../stop_clean_iface.py | 0 .../todo.org | 0 .../wg_keys_incommon.py | 0 .../set_client_key.sh | 0 .../setup.sh | 0 79 files changed, 439 insertions(+) create mode 100644 developer/source/DNS/README.md create mode 100644 developer/source/DNS/deloy.sh rename developer/source/{network-client => DNS}/install_staged_tree.py (100%) create mode 100644 developer/source/DNS/stage/etc/nftables.d/30-dnsredir.nft create mode 100644 developer/source/DNS/stage/etc/systemd/system/unbound@.service create mode 100644 developer/source/DNS/stage/etc/unbound/unbound-US.conf create mode 100644 developer/source/DNS/stage/etc/unbound/unbound-x6.conf rename developer/{mockup => source/cc}/sqlite/schema.sql (100%) delete mode 100644 developer/source/deprecated/.githolder create mode 100644 developer/source/deprecated/.gitignore rename developer/source/{io => device_management}/bestow_audio.sh (100%) rename developer/source/{network-client => tunnel-client}/.gitignore (100%) rename developer/source/{network-client => tunnel-client}/db/.gitignore (100%) rename developer/source/{network-client => tunnel-client}/db_bind_user_to_iface.py (100%) rename developer/source/{network-client => tunnel-client}/db_checks.py (100%) rename developer/source/{network-client => tunnel-client}/db_init_StanleyPark.py (100%) rename developer/source/{network-client => tunnel-client}/db_init_iface.py (100%) rename developer/source/{network-client => tunnel-client}/db_init_iface_US.py (100%) rename developer/source/{network-client => tunnel-client}/db_init_iface_x6.py (100%) rename developer/source/{network-client => tunnel-client}/db_init_ip_iface_addr_assign.py (100%) rename developer/source/{network-client => tunnel-client}/db_init_ip_table_registration.py (100%) rename developer/source/{network-client => tunnel-client}/db_init_route_defaults.py (100%) rename developer/source/{network-client => tunnel-client}/db_init_server_US.py (100%) rename developer/source/{network-client => tunnel-client}/db_init_server_incommon.py (100%) rename developer/source/{network-client => tunnel-client}/db_init_server_x6.py (100%) rename developer/source/{network-client => tunnel-client}/db_schema.sql (100%) rename developer/source/{network-client => tunnel-client}/db_schema_load.sh (100%) rename developer/source/{network-client => tunnel-client}/db_wipe.py (100%) rename developer/source/{network-client => tunnel-client}/deploy_StanleyPark.py (100%) rename developer/source/{network-client => tunnel-client}/deprecated/.gitignore (100%) rename developer/source/{network-client => tunnel-client}/doc_IP_terminaology.org (100%) rename developer/source/{network-client => tunnel-client}/doc_StanleyPark.org (100%) rename developer/source/{network-client => tunnel-client}/doc_config.org (100%) rename developer/source/{network-client => tunnel-client}/doc_keys.org (100%) rename developer/source/{network-client => tunnel-client}/doc_stage_progs.org (100%) rename developer/source/{network-client => tunnel-client}/iface_down.py (100%) rename developer/source/{network-client => tunnel-client}/iface_status.py (100%) rename developer/source/{network-client => tunnel-client}/iface_up.sh (100%) rename developer/source/{network-client => tunnel-client}/incommon.py (100%) rename developer/source/{network-client => tunnel-client}/inspect.sh (100%) rename developer/source/{network-client => tunnel-client}/inspect_1.py (100%) rename developer/source/{network-client => tunnel-client}/inspect_client_public_key.py (100%) create mode 100755 developer/source/tunnel-client/install_staged_tree.py rename developer/source/{network-client => tunnel-client}/key/.gitignore (100%) rename developer/source/{network-client => tunnel-client}/key_client_generate.py (100%) rename developer/source/{network-client => tunnel-client}/key_server_set.py (100%) rename developer/source/{network-client => tunnel-client}/ls_iface.py (100%) rename developer/source/{network-client => tunnel-client}/ls_key.py (100%) rename developer/source/{network-client => tunnel-client}/ls_server.py (100%) rename developer/source/{network-client => tunnel-client}/ls_server_setting.py (100%) rename developer/source/{network-client => tunnel-client}/ls_servers.sh (100%) rename developer/source/{network-client => tunnel-client}/ls_user.py (100%) rename developer/source/{network-client => tunnel-client}/manual_reference.org (100%) rename developer/source/{network-client => tunnel-client}/manual_user.org (100%) rename developer/source/{network-client => tunnel-client}/mothball/stage/.gitignore (100%) rename developer/source/{network-client => tunnel-client}/mothball/stage_IP_routes_script.py (100%) rename developer/source/{network-client => tunnel-client}/mothball/stage_IP_rules_script.py (100%) rename developer/source/{network-client => tunnel-client}/mothball/stage_StanleyPark.py (100%) rename developer/source/{network-client => tunnel-client}/mothball/stage_UID_routes.py (100%) rename developer/source/{network-client => tunnel-client}/mothball/stage_list_clients.py (100%) rename developer/source/{network-client => tunnel-client}/mothball/stage_list_uid.py (100%) rename developer/source/{network-client => tunnel-client}/mothball/stage_populate.py (100%) rename developer/source/{network-client => tunnel-client}/mothball/stage_preferred_server.py (100%) rename developer/source/{network-client => tunnel-client}/mothball/stage_wg_conf.py (100%) rename developer/source/{network-client => tunnel-client}/mothball/stage_wg_unit_IP_scripts.py (100%) rename developer/source/{network-client => tunnel-client}/mothball/stage_wipe.py (100%) rename developer/source/{network-client => tunnel-client}/scratchpad/.gitignore (100%) rename developer/source/{network-client => tunnel-client}/stage/.gitignore (100%) rename developer/source/{network-client => tunnel-client}/stage_IP_apply_script.py (100%) rename developer/source/{network-client => tunnel-client}/stage_StanleyPark.py (100%) rename developer/source/{network-client => tunnel-client}/stage_client.py (100%) rename developer/source/{network-client => tunnel-client}/stage_wg_conf.py (100%) rename developer/source/{network-client => tunnel-client}/stage_wipe.py (100%) rename developer/source/{network-client => tunnel-client}/start_iface.py (100%) rename developer/source/{network-client => tunnel-client}/stop_clean_iface.py (100%) rename developer/source/{network-client => tunnel-client}/todo.org (100%) rename developer/source/{network-client => tunnel-client}/wg_keys_incommon.py (100%) rename developer/source/{network-server => tunnel-server}/set_client_key.sh (100%) rename developer/source/{network-server => tunnel-server}/setup.sh (100%) diff --git a/developer/source/DNS/README.md b/developer/source/DNS/README.md new file mode 100644 index 0000000..19eeda5 --- /dev/null +++ b/developer/source/DNS/README.md @@ -0,0 +1,70 @@ +# Unbound per-tunnel setup (US + x6) + +This bundle provides two Unbound instances that each egress via a specific +WireGuard tunnel, plus an nftables rule to steer DNS from specific UIDs to +the corresponding local stub resolver port. + +## Topology +- US instance + - listens: 127.0.0.1:5301 + - egress: 10.0.0.1 (WG US local address) + - intended UID: 2017 (e.g., user `Thomas-US`) +- x6 instance + - listens: 127.0.0.1:5302 + - egress: 10.8.0.2 (WG x6 local address) + - intended UID: 2018 (e.g., user `Thomas-x6`) + +Both instances bind ONLY on loopback (so they survive tunnel flaps) and set +`outgoing-interface` to the WG /32 address so queries exit via the tunnel. +IPv6 is disabled (consistent with your environment). + +## Install +Copy files: + + sudo cp stage/etc/unbound/unbound-US.conf /etc/unbound/ + sudo cp stage/etc/unbound/unbound-x6.conf /etc/unbound/ + sudo cp stage/etc/systemd/system/unbound@.service /etc/systemd/system/ + sudo mkdir -p /etc/nftables.d + sudo cp stage/etc/nftables.d/30-dnsredir.nft /etc/nftables.d/ + +Include the nft snippet and reload nftables: + + # add to /etc/nftables.conf (near end): + # include "/etc/nftables.d/30-dnsredir.nft" + sudo nft -f /etc/nftables.conf + +Systemd: + + sudo systemctl daemon-reload + sudo systemctl enable --now unbound@US unbound@x6 + +> The unit naturally waits for the matching tunnel: `After=wg-quick@%i.service`. + +## Optional (root hints + DNSSEC trust anchor) +Recommended once (before or after starting): + + sudo install -d -m 0755 /var/lib/unbound + sudo wget -O /var/lib/unbound/root.hints https://www.internic.net/domain/named.root + sudo unbound-anchor -a /var/lib/unbound/root.key + +The configs enable DNSSEC via `auto-trust-anchor-file`. + +## Test + + # US path + sudo -u Thomas-US dig @127.0.0.1 -p 5301 example.com +short + sudo -u Thomas-US curl -s ifconfig.co/country + + # x6 path + sudo -u Thomas-x6 dig @127.0.0.1 -p 5302 example.com +short + sudo -u Thomas-x6 curl -s ifconfig.co/country + + # Fail-closed example: stop a tunnel; queries from its UID should fail + sudo systemctl stop wg-quick@US + sudo -u Thomas-US dig example.com +short + +## Notes +- If resolv.conf is changed by NetworkManager or others, nftables redirection + still forces DNS to the right local stub (ports 5301/5302) per-UID. +- You can switch to forwarding instead of recursion by uncommenting the + `forward-zone` block in each config. diff --git a/developer/source/DNS/deloy.sh b/developer/source/DNS/deloy.sh new file mode 100644 index 0000000..7a033a8 --- /dev/null +++ b/developer/source/DNS/deloy.sh @@ -0,0 +1,8 @@ +sudo ./install_staged_tree.py +sudo systemctl daemon-reload +# enable nft snippet (once): +sudo sed -i '1{/table inet filter {/!{h;s/.*/include "\/etc\/nftables.d\/30-dnsredir.nft"/;H;x}}' /etc/nftables.conf || true +sudo nft -f /etc/nftables.conf + +# bring up Unbound instances (they wait for wg links): +sudo systemctl enable --now unbound@US unbound@x6 diff --git a/developer/source/network-client/install_staged_tree.py b/developer/source/DNS/install_staged_tree.py similarity index 100% rename from developer/source/network-client/install_staged_tree.py rename to developer/source/DNS/install_staged_tree.py diff --git a/developer/source/DNS/stage/etc/nftables.d/30-dnsredir.nft b/developer/source/DNS/stage/etc/nftables.d/30-dnsredir.nft new file mode 100644 index 0000000..8ab5249 --- /dev/null +++ b/developer/source/DNS/stage/etc/nftables.d/30-dnsredir.nft @@ -0,0 +1,14 @@ +# Redirect DNS traffic per-UID to local Unbound instances. +# US (uid 2017) -> 127.0.0.1:5301 +# x6 (uid 2018) -> 127.0.0.1:5302 +table inet nat { + chain output { + type nat hook output priority -100; + # US + meta skuid 2017 udp dport 53 redirect to :5301 + meta skuid 2017 tcp dport 53 redirect to :5301 + # x6 + meta skuid 2018 udp dport 53 redirect to :5302 + meta skuid 2018 tcp dport 53 redirect to :5302 + } +} diff --git a/developer/source/DNS/stage/etc/systemd/system/unbound@.service b/developer/source/DNS/stage/etc/systemd/system/unbound@.service new file mode 100644 index 0000000..4fa31d8 --- /dev/null +++ b/developer/source/DNS/stage/etc/systemd/system/unbound@.service @@ -0,0 +1,20 @@ +[Unit] +Description=Unbound DNS (%i) +Documentation=man:unbound(8) +After=network-online.target wg-quick@%i.service +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/usr/sbin/unbound -d -p -c /etc/unbound/unbound-%i.conf +Restart=on-failure +# Lock down a bit +CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID +AmbientCapabilities=CAP_NET_BIND_SERVICE +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=full +ProtectHome=true + +[Install] +WantedBy=multi-user.target diff --git a/developer/source/DNS/stage/etc/unbound/unbound-US.conf b/developer/source/DNS/stage/etc/unbound/unbound-US.conf new file mode 100644 index 0000000..6a799f7 --- /dev/null +++ b/developer/source/DNS/stage/etc/unbound/unbound-US.conf @@ -0,0 +1,40 @@ +server: + verbosity: 1 + username: "unbound" + directory: "/etc/unbound" + chroot: "" + + do-ip6: no + do-udp: yes + do-tcp: yes + prefer-ip6: no + + # Listen only on loopback (US instance) + interface: 127.0.0.1@5301 + access-control: 127.0.0.0/8 allow + + # Egress via US tunnel address (policy routing will carry it out the WG table) + outgoing-interface: 10.0.0.1 + + # Sensible hardening/cache + hide-identity: yes + hide-version: yes + harden-referral-path: yes + harden-dnssec-stripped: yes + qname-minimisation: yes + aggressive-nsec: yes + prefetch: yes + cache-min-ttl: 60 + cache-max-ttl: 86400 + + # DNSSEC TA (create with unbound-anchor) + auto-trust-anchor-file: "/var/lib/unbound/root.key" + # Optional root hints (download separately) + # root-hints: "/var/lib/unbound/root.hints" + +# To use forwarding instead of full recursion, uncomment and edit: +# forward-zone: +# name: "." +# forward-tls-upstream: no +# forward-addr: 9.9.9.9 +# forward-addr: 1.1.1.1 diff --git a/developer/source/DNS/stage/etc/unbound/unbound-x6.conf b/developer/source/DNS/stage/etc/unbound/unbound-x6.conf new file mode 100644 index 0000000..c34a068 --- /dev/null +++ b/developer/source/DNS/stage/etc/unbound/unbound-x6.conf @@ -0,0 +1,40 @@ +server: + verbosity: 1 + username: "unbound" + directory: "/etc/unbound" + chroot: "" + + do-ip6: no + do-udp: yes + do-tcp: yes + prefer-ip6: no + + # Listen only on loopback (x6 instance) + interface: 127.0.0.1@5302 + access-control: 127.0.0.0/8 allow + + # Egress via x6 tunnel address (policy routing will carry it out the WG table) + outgoing-interface: 10.8.0.2 + + # Sensible hardening/cache + hide-identity: yes + hide-version: yes + harden-referral-path: yes + harden-dnssec-stripped: yes + qname-minimisation: yes + aggressive-nsec: yes + prefetch: yes + cache-min-ttl: 60 + cache-max-ttl: 86400 + + # DNSSEC TA (create with unbound-anchor) + auto-trust-anchor-file: "/var/lib/unbound/root.key" + # Optional root hints (download separately) + # root-hints: "/var/lib/unbound/root.hints" + +# To use forwarding instead of full recursion, uncomment and edit: +# forward-zone: +# name: "." +# forward-tls-upstream: no +# forward-addr: 9.9.9.9 +# forward-addr: 1.1.1.1 diff --git a/developer/mockup/sqlite/schema.sql b/developer/source/cc/sqlite/schema.sql similarity index 100% rename from developer/mockup/sqlite/schema.sql rename to developer/source/cc/sqlite/schema.sql diff --git a/developer/source/deprecated/.githolder b/developer/source/deprecated/.githolder deleted file mode 100644 index e69de29..0000000 diff --git a/developer/source/deprecated/.gitignore b/developer/source/deprecated/.gitignore new file mode 100644 index 0000000..120f485 --- /dev/null +++ b/developer/source/deprecated/.gitignore @@ -0,0 +1,2 @@ +* +!/.gitignore diff --git a/developer/source/io/bestow_audio.sh b/developer/source/device_management/bestow_audio.sh similarity index 100% rename from developer/source/io/bestow_audio.sh rename to developer/source/device_management/bestow_audio.sh diff --git a/developer/source/network-client/.gitignore b/developer/source/tunnel-client/.gitignore similarity index 100% rename from developer/source/network-client/.gitignore rename to developer/source/tunnel-client/.gitignore diff --git a/developer/source/network-client/db/.gitignore b/developer/source/tunnel-client/db/.gitignore similarity index 100% rename from developer/source/network-client/db/.gitignore rename to developer/source/tunnel-client/db/.gitignore diff --git a/developer/source/network-client/db_bind_user_to_iface.py b/developer/source/tunnel-client/db_bind_user_to_iface.py similarity index 100% rename from developer/source/network-client/db_bind_user_to_iface.py rename to developer/source/tunnel-client/db_bind_user_to_iface.py diff --git a/developer/source/network-client/db_checks.py b/developer/source/tunnel-client/db_checks.py similarity index 100% rename from developer/source/network-client/db_checks.py rename to developer/source/tunnel-client/db_checks.py diff --git a/developer/source/network-client/db_init_StanleyPark.py b/developer/source/tunnel-client/db_init_StanleyPark.py similarity index 100% rename from developer/source/network-client/db_init_StanleyPark.py rename to developer/source/tunnel-client/db_init_StanleyPark.py diff --git a/developer/source/network-client/db_init_iface.py b/developer/source/tunnel-client/db_init_iface.py similarity index 100% rename from developer/source/network-client/db_init_iface.py rename to developer/source/tunnel-client/db_init_iface.py diff --git a/developer/source/network-client/db_init_iface_US.py b/developer/source/tunnel-client/db_init_iface_US.py similarity index 100% rename from developer/source/network-client/db_init_iface_US.py rename to developer/source/tunnel-client/db_init_iface_US.py diff --git a/developer/source/network-client/db_init_iface_x6.py b/developer/source/tunnel-client/db_init_iface_x6.py similarity index 100% rename from developer/source/network-client/db_init_iface_x6.py rename to developer/source/tunnel-client/db_init_iface_x6.py diff --git a/developer/source/network-client/db_init_ip_iface_addr_assign.py b/developer/source/tunnel-client/db_init_ip_iface_addr_assign.py similarity index 100% rename from developer/source/network-client/db_init_ip_iface_addr_assign.py rename to developer/source/tunnel-client/db_init_ip_iface_addr_assign.py diff --git a/developer/source/network-client/db_init_ip_table_registration.py b/developer/source/tunnel-client/db_init_ip_table_registration.py similarity index 100% rename from developer/source/network-client/db_init_ip_table_registration.py rename to developer/source/tunnel-client/db_init_ip_table_registration.py diff --git a/developer/source/network-client/db_init_route_defaults.py b/developer/source/tunnel-client/db_init_route_defaults.py similarity index 100% rename from developer/source/network-client/db_init_route_defaults.py rename to developer/source/tunnel-client/db_init_route_defaults.py diff --git a/developer/source/network-client/db_init_server_US.py b/developer/source/tunnel-client/db_init_server_US.py similarity index 100% rename from developer/source/network-client/db_init_server_US.py rename to developer/source/tunnel-client/db_init_server_US.py diff --git a/developer/source/network-client/db_init_server_incommon.py b/developer/source/tunnel-client/db_init_server_incommon.py similarity index 100% rename from developer/source/network-client/db_init_server_incommon.py rename to developer/source/tunnel-client/db_init_server_incommon.py diff --git a/developer/source/network-client/db_init_server_x6.py b/developer/source/tunnel-client/db_init_server_x6.py similarity index 100% rename from developer/source/network-client/db_init_server_x6.py rename to developer/source/tunnel-client/db_init_server_x6.py diff --git a/developer/source/network-client/db_schema.sql b/developer/source/tunnel-client/db_schema.sql similarity index 100% rename from developer/source/network-client/db_schema.sql rename to developer/source/tunnel-client/db_schema.sql diff --git a/developer/source/network-client/db_schema_load.sh b/developer/source/tunnel-client/db_schema_load.sh similarity index 100% rename from developer/source/network-client/db_schema_load.sh rename to developer/source/tunnel-client/db_schema_load.sh diff --git a/developer/source/network-client/db_wipe.py b/developer/source/tunnel-client/db_wipe.py similarity index 100% rename from developer/source/network-client/db_wipe.py rename to developer/source/tunnel-client/db_wipe.py diff --git a/developer/source/network-client/deploy_StanleyPark.py b/developer/source/tunnel-client/deploy_StanleyPark.py similarity index 100% rename from developer/source/network-client/deploy_StanleyPark.py rename to developer/source/tunnel-client/deploy_StanleyPark.py diff --git a/developer/source/network-client/deprecated/.gitignore b/developer/source/tunnel-client/deprecated/.gitignore similarity index 100% rename from developer/source/network-client/deprecated/.gitignore rename to developer/source/tunnel-client/deprecated/.gitignore diff --git a/developer/source/network-client/doc_IP_terminaology.org b/developer/source/tunnel-client/doc_IP_terminaology.org similarity index 100% rename from developer/source/network-client/doc_IP_terminaology.org rename to developer/source/tunnel-client/doc_IP_terminaology.org diff --git a/developer/source/network-client/doc_StanleyPark.org b/developer/source/tunnel-client/doc_StanleyPark.org similarity index 100% rename from developer/source/network-client/doc_StanleyPark.org rename to developer/source/tunnel-client/doc_StanleyPark.org diff --git a/developer/source/network-client/doc_config.org b/developer/source/tunnel-client/doc_config.org similarity index 100% rename from developer/source/network-client/doc_config.org rename to developer/source/tunnel-client/doc_config.org diff --git a/developer/source/network-client/doc_keys.org b/developer/source/tunnel-client/doc_keys.org similarity index 100% rename from developer/source/network-client/doc_keys.org rename to developer/source/tunnel-client/doc_keys.org diff --git a/developer/source/network-client/doc_stage_progs.org b/developer/source/tunnel-client/doc_stage_progs.org similarity index 100% rename from developer/source/network-client/doc_stage_progs.org rename to developer/source/tunnel-client/doc_stage_progs.org diff --git a/developer/source/network-client/iface_down.py b/developer/source/tunnel-client/iface_down.py similarity index 100% rename from developer/source/network-client/iface_down.py rename to developer/source/tunnel-client/iface_down.py diff --git a/developer/source/network-client/iface_status.py b/developer/source/tunnel-client/iface_status.py similarity index 100% rename from developer/source/network-client/iface_status.py rename to developer/source/tunnel-client/iface_status.py diff --git a/developer/source/network-client/iface_up.sh b/developer/source/tunnel-client/iface_up.sh similarity index 100% rename from developer/source/network-client/iface_up.sh rename to developer/source/tunnel-client/iface_up.sh diff --git a/developer/source/network-client/incommon.py b/developer/source/tunnel-client/incommon.py similarity index 100% rename from developer/source/network-client/incommon.py rename to developer/source/tunnel-client/incommon.py diff --git a/developer/source/network-client/inspect.sh b/developer/source/tunnel-client/inspect.sh similarity index 100% rename from developer/source/network-client/inspect.sh rename to developer/source/tunnel-client/inspect.sh diff --git a/developer/source/network-client/inspect_1.py b/developer/source/tunnel-client/inspect_1.py similarity index 100% rename from developer/source/network-client/inspect_1.py rename to developer/source/tunnel-client/inspect_1.py diff --git a/developer/source/network-client/inspect_client_public_key.py b/developer/source/tunnel-client/inspect_client_public_key.py similarity index 100% rename from developer/source/network-client/inspect_client_public_key.py rename to developer/source/tunnel-client/inspect_client_public_key.py diff --git a/developer/source/tunnel-client/install_staged_tree.py b/developer/source/tunnel-client/install_staged_tree.py new file mode 100755 index 0000000..e1225d5 --- /dev/null +++ b/developer/source/tunnel-client/install_staged_tree.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +""" +install_staged_tree.py + +A dumb installer: copy staged files into the target root with backups and +deterministic permissions. No systemd stop/start, no daemon-reload. + +Given: + - A staged tree (default: ./stage) containing any of: + /usr/local/bin/apply_ip_state.sh + /etc/wireguard/*.conf + /etc/systemd/system/wg-quick@IFACE.service.d/*.conf + /etc/iproute2/rt_tables + - A destination root (default: /). Parent dirs may be created with --create-dirs. + +Does: + - For each whitelisted staged file: + * if a target already exists, copy it back into the stage as a timestamped backup + * atomically replace target with staged version + * set root:root ownership (best-effort) and explicit permissions + - Prints a summary and suggests next steps (e.g., ./start_iface.py ) + +Returns: + - Exit 0 on success; non-zero on error +""" + +from __future__ import annotations +from pathlib import Path +from typing import Dict, Iterable, List, Optional, Sequence, Tuple +import argparse +import datetime as dt +import hashlib +import os +import shutil +import sys + +ROOT = Path(__file__).resolve().parent +DEFAULT_STAGE = ROOT / "stage" + +# Whitelisted install targets → mode +# (These are *relative* to the stage root) +MODE_RULES: List[Tuple[str, int]] = [ + ("usr/local/bin", 0o500), # files under here (scripts) + ("etc/wireguard", 0o600), # *.conf + ("etc/systemd/system", 0o644), # wg-quick@*.service.d/*.conf + ("etc/iproute2", 0o644), # rt_tables +] + +def _sha256(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1<<20), b""): + h.update(chunk) + return h.hexdigest() + +def _ensure_parents(dest_root: Path, rel: Path, create: bool) -> None: + parent = (dest_root / rel).parent + if parent.exists(): + return + if not create: + raise RuntimeError(f"missing parent directory: {parent}") + parent.mkdir(parents=True, exist_ok=True) + +def _backup_existing_to_stage(stage_root: Path, dest_root: Path, rel: Path) -> Optional[Path]: + """If target exists, copy it back into stage/_backups// and return backup path.""" + target = dest_root / rel + if not target.exists(): + return None + ts = dt.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ") + backup = stage_root / "_backups" / ts / rel + backup.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(target, backup) + return backup + +def _atomic_install(src: Path, dst: Path, mode: int) -> None: + tmp = dst.with_suffix(dst.suffix + ".tmp") + shutil.copyfile(src, tmp) + os.chmod(tmp, mode) + try: + os.chown(tmp, 0, 0) # best-effort; may fail if not root + except PermissionError: + pass + os.replace(tmp, dst) + +def _mode_for_rel(rel: Path) -> Optional[int]: + """Choose a mode based on the relative path bucket.""" + s = str(rel) + if s.startswith("usr/local/bin/"): + return 0o500 + if s.startswith("etc/wireguard/") and rel.suffix == ".conf": + return 0o600 + if s == "etc/iproute2/rt_tables": + return 0o644 + if s.startswith("etc/systemd/system/") and s.endswith(".conf"): + return 0o644 + return None + +def _iter_stage_targets(stage_root: Path) -> List[Path]: + """Return a list of *relative* paths under stage that match our whitelist.""" + rels: List[Path] = [] + + # /usr/local/bin/* + bin_dir = stage_root / "usr" / "local" / "bin" + if bin_dir.is_dir(): + for p in sorted(bin_dir.glob("*")): + if p.is_file(): + rels.append(p.relative_to(stage_root)) + + # /etc/wireguard/*.conf + wg_dir = stage_root / "etc" / "wireguard" + if wg_dir.is_dir(): + for p in sorted(wg_dir.glob("*.conf")): + rels.append(p.relative_to(stage_root)) + + # /etc/systemd/system/wg-quick@*.service.d/*.conf + sysd_dir = stage_root / "etc" / "systemd" / "system" + if sysd_dir.is_dir(): + for p in sorted(sysd_dir.rglob("wg-quick@*.service.d/*.conf")): + rels.append(p.relative_to(stage_root)) + + # /etc/iproute2/rt_tables + rt = stage_root / "etc" / "iproute2" / "rt_tables" + if rt.is_file(): + rels.append(rt.relative_to(stage_root)) + + return rels + +def _discover_ifaces_from_stage(stage_root: Path) -> List[str]: + """Peek into staged artifacts to guess iface names (for friendly next-steps).""" + names = set() + + # from /etc/wireguard/.conf + wg_dir = stage_root / "etc" / "wireguard" + if wg_dir.is_dir(): + for p in wg_dir.glob("*.conf"): + names.add(p.stem) + + # from /etc/systemd/system/wg-quick@.service.d/ + sysd = stage_root / "etc" / "systemd" / "system" + if sysd.is_dir(): + for d in sysd.glob("wg-quick@*.service.d"): + name = d.name + # name looks like: wg-quick@X.service.d + at = name.find("@") + dot = name.find(".service.d") + if at != -1 and dot != -1 and dot > at: + names.add(name[at+1:dot]) + + return sorted(names) + +def install_staged_tree( + stage_root: Path, + dest_root: Path, + create_dirs: bool = False, + skip_identical: bool = True, +) -> Tuple[List[str], List[str]]: + """ + Copy files from stage_root to dest_root. + Returns (logs, detected_ifaces). + """ + old_umask = os.umask(0o077) + logs: List[str] = [] + try: + staged = _iter_stage_targets(stage_root) + if not staged: + raise RuntimeError("nothing to install (stage is empty or whitelist didn’t match)") + + for rel in staged: + src = stage_root / rel + dst = dest_root / rel + + mode = _mode_for_rel(rel) + if mode is None: + logs.append(f"skip (not whitelisted): {rel}") + continue + + _ensure_parents(dest_root, rel, create_dirs) + + backup = _backup_existing_to_stage(stage_root, dest_root, rel) + if backup: + logs.append(f"backup: {dst} -> {backup}") + + if skip_identical and dst.exists(): + try: + if _sha256(src) == _sha256(dst): + logs.append(f"identical: skip {rel}") + continue + except Exception: + pass + + _atomic_install(src, dst, mode) + logs.append(f"install: {rel} (mode {oct(mode)})") + + ifaces = _discover_ifaces_from_stage(stage_root) + return (logs, ifaces) + finally: + os.umask(old_umask) + +def _require_root(allow_nonroot: bool) -> None: + if not allow_nonroot and os.geteuid() != 0: + raise RuntimeError("must run as root (use --force-nonroot to override)") + +def main(argv: Optional[Sequence[str]] = None) -> int: + ap = argparse.ArgumentParser(description="Install staged artifacts into a target root. No service control.") + ap.add_argument("--stage", default=str(DEFAULT_STAGE)) + ap.add_argument("--root", default="/") + ap.add_argument("--create-dirs", action="store_true", help="create missing parent directories") + ap.add_argument("--no-skip-identical", action="store_true", help="always replace even if content identical") + ap.add_argument("--force-nonroot", action="store_true", help="allow non-root install (ownership may be wrong)") + args = ap.parse_args(argv) + + try: + _require_root(allow_nonroot=args.force_nonroot) + logs, ifaces = install_staged_tree( + stage_root=Path(args.stage), + dest_root=Path(args.root), + create_dirs=args.create_dirs, + skip_identical=(not args.no_skip_identical), + ) + for line in logs: + print(line) + + # Summary + suggested next steps + print("\n=== Summary ===") + print(f"Installed {sum(1 for l in logs if l.startswith('install:'))} file(s).") + if ifaces: + lst = " ".join(ifaces) + print(f"Detected interfaces from stage: {lst}") + print(f"\nNext steps:") + print(f" # (optional) verify configs") + print(f" sudo wg-quick strip /etc/wireguard/{ifaces[0]}.conf >/dev/null 2>&1 || true") + print(f"\n # start interfaces") + print(f" sudo ./start_iface.py {lst}") + else: + print("No interfaces detected in staged artifacts.") + print("\nNext steps:") + print(" # start your interface(s)") + print(" sudo ./start_iface.py [more ifaces]") + return 0 + except Exception as e: + print(f"❌ install failed: {e}", file=sys.stderr) + return 2 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/developer/source/network-client/key/.gitignore b/developer/source/tunnel-client/key/.gitignore similarity index 100% rename from developer/source/network-client/key/.gitignore rename to developer/source/tunnel-client/key/.gitignore diff --git a/developer/source/network-client/key_client_generate.py b/developer/source/tunnel-client/key_client_generate.py similarity index 100% rename from developer/source/network-client/key_client_generate.py rename to developer/source/tunnel-client/key_client_generate.py diff --git a/developer/source/network-client/key_server_set.py b/developer/source/tunnel-client/key_server_set.py similarity index 100% rename from developer/source/network-client/key_server_set.py rename to developer/source/tunnel-client/key_server_set.py diff --git a/developer/source/network-client/ls_iface.py b/developer/source/tunnel-client/ls_iface.py similarity index 100% rename from developer/source/network-client/ls_iface.py rename to developer/source/tunnel-client/ls_iface.py diff --git a/developer/source/network-client/ls_key.py b/developer/source/tunnel-client/ls_key.py similarity index 100% rename from developer/source/network-client/ls_key.py rename to developer/source/tunnel-client/ls_key.py diff --git a/developer/source/network-client/ls_server.py b/developer/source/tunnel-client/ls_server.py similarity index 100% rename from developer/source/network-client/ls_server.py rename to developer/source/tunnel-client/ls_server.py diff --git a/developer/source/network-client/ls_server_setting.py b/developer/source/tunnel-client/ls_server_setting.py similarity index 100% rename from developer/source/network-client/ls_server_setting.py rename to developer/source/tunnel-client/ls_server_setting.py diff --git a/developer/source/network-client/ls_servers.sh b/developer/source/tunnel-client/ls_servers.sh similarity index 100% rename from developer/source/network-client/ls_servers.sh rename to developer/source/tunnel-client/ls_servers.sh diff --git a/developer/source/network-client/ls_user.py b/developer/source/tunnel-client/ls_user.py similarity index 100% rename from developer/source/network-client/ls_user.py rename to developer/source/tunnel-client/ls_user.py diff --git a/developer/source/network-client/manual_reference.org b/developer/source/tunnel-client/manual_reference.org similarity index 100% rename from developer/source/network-client/manual_reference.org rename to developer/source/tunnel-client/manual_reference.org diff --git a/developer/source/network-client/manual_user.org b/developer/source/tunnel-client/manual_user.org similarity index 100% rename from developer/source/network-client/manual_user.org rename to developer/source/tunnel-client/manual_user.org diff --git a/developer/source/network-client/mothball/stage/.gitignore b/developer/source/tunnel-client/mothball/stage/.gitignore similarity index 100% rename from developer/source/network-client/mothball/stage/.gitignore rename to developer/source/tunnel-client/mothball/stage/.gitignore diff --git a/developer/source/network-client/mothball/stage_IP_routes_script.py b/developer/source/tunnel-client/mothball/stage_IP_routes_script.py similarity index 100% rename from developer/source/network-client/mothball/stage_IP_routes_script.py rename to developer/source/tunnel-client/mothball/stage_IP_routes_script.py diff --git a/developer/source/network-client/mothball/stage_IP_rules_script.py b/developer/source/tunnel-client/mothball/stage_IP_rules_script.py similarity index 100% rename from developer/source/network-client/mothball/stage_IP_rules_script.py rename to developer/source/tunnel-client/mothball/stage_IP_rules_script.py diff --git a/developer/source/network-client/mothball/stage_StanleyPark.py b/developer/source/tunnel-client/mothball/stage_StanleyPark.py similarity index 100% rename from developer/source/network-client/mothball/stage_StanleyPark.py rename to developer/source/tunnel-client/mothball/stage_StanleyPark.py diff --git a/developer/source/network-client/mothball/stage_UID_routes.py b/developer/source/tunnel-client/mothball/stage_UID_routes.py similarity index 100% rename from developer/source/network-client/mothball/stage_UID_routes.py rename to developer/source/tunnel-client/mothball/stage_UID_routes.py diff --git a/developer/source/network-client/mothball/stage_list_clients.py b/developer/source/tunnel-client/mothball/stage_list_clients.py similarity index 100% rename from developer/source/network-client/mothball/stage_list_clients.py rename to developer/source/tunnel-client/mothball/stage_list_clients.py diff --git a/developer/source/network-client/mothball/stage_list_uid.py b/developer/source/tunnel-client/mothball/stage_list_uid.py similarity index 100% rename from developer/source/network-client/mothball/stage_list_uid.py rename to developer/source/tunnel-client/mothball/stage_list_uid.py diff --git a/developer/source/network-client/mothball/stage_populate.py b/developer/source/tunnel-client/mothball/stage_populate.py similarity index 100% rename from developer/source/network-client/mothball/stage_populate.py rename to developer/source/tunnel-client/mothball/stage_populate.py diff --git a/developer/source/network-client/mothball/stage_preferred_server.py b/developer/source/tunnel-client/mothball/stage_preferred_server.py similarity index 100% rename from developer/source/network-client/mothball/stage_preferred_server.py rename to developer/source/tunnel-client/mothball/stage_preferred_server.py diff --git a/developer/source/network-client/mothball/stage_wg_conf.py b/developer/source/tunnel-client/mothball/stage_wg_conf.py similarity index 100% rename from developer/source/network-client/mothball/stage_wg_conf.py rename to developer/source/tunnel-client/mothball/stage_wg_conf.py diff --git a/developer/source/network-client/mothball/stage_wg_unit_IP_scripts.py b/developer/source/tunnel-client/mothball/stage_wg_unit_IP_scripts.py similarity index 100% rename from developer/source/network-client/mothball/stage_wg_unit_IP_scripts.py rename to developer/source/tunnel-client/mothball/stage_wg_unit_IP_scripts.py diff --git a/developer/source/network-client/mothball/stage_wipe.py b/developer/source/tunnel-client/mothball/stage_wipe.py similarity index 100% rename from developer/source/network-client/mothball/stage_wipe.py rename to developer/source/tunnel-client/mothball/stage_wipe.py diff --git a/developer/source/network-client/scratchpad/.gitignore b/developer/source/tunnel-client/scratchpad/.gitignore similarity index 100% rename from developer/source/network-client/scratchpad/.gitignore rename to developer/source/tunnel-client/scratchpad/.gitignore diff --git a/developer/source/network-client/stage/.gitignore b/developer/source/tunnel-client/stage/.gitignore similarity index 100% rename from developer/source/network-client/stage/.gitignore rename to developer/source/tunnel-client/stage/.gitignore diff --git a/developer/source/network-client/stage_IP_apply_script.py b/developer/source/tunnel-client/stage_IP_apply_script.py similarity index 100% rename from developer/source/network-client/stage_IP_apply_script.py rename to developer/source/tunnel-client/stage_IP_apply_script.py diff --git a/developer/source/network-client/stage_StanleyPark.py b/developer/source/tunnel-client/stage_StanleyPark.py similarity index 100% rename from developer/source/network-client/stage_StanleyPark.py rename to developer/source/tunnel-client/stage_StanleyPark.py diff --git a/developer/source/network-client/stage_client.py b/developer/source/tunnel-client/stage_client.py similarity index 100% rename from developer/source/network-client/stage_client.py rename to developer/source/tunnel-client/stage_client.py diff --git a/developer/source/network-client/stage_wg_conf.py b/developer/source/tunnel-client/stage_wg_conf.py similarity index 100% rename from developer/source/network-client/stage_wg_conf.py rename to developer/source/tunnel-client/stage_wg_conf.py diff --git a/developer/source/network-client/stage_wipe.py b/developer/source/tunnel-client/stage_wipe.py similarity index 100% rename from developer/source/network-client/stage_wipe.py rename to developer/source/tunnel-client/stage_wipe.py diff --git a/developer/source/network-client/start_iface.py b/developer/source/tunnel-client/start_iface.py similarity index 100% rename from developer/source/network-client/start_iface.py rename to developer/source/tunnel-client/start_iface.py diff --git a/developer/source/network-client/stop_clean_iface.py b/developer/source/tunnel-client/stop_clean_iface.py similarity index 100% rename from developer/source/network-client/stop_clean_iface.py rename to developer/source/tunnel-client/stop_clean_iface.py diff --git a/developer/source/network-client/todo.org b/developer/source/tunnel-client/todo.org similarity index 100% rename from developer/source/network-client/todo.org rename to developer/source/tunnel-client/todo.org diff --git a/developer/source/network-client/wg_keys_incommon.py b/developer/source/tunnel-client/wg_keys_incommon.py similarity index 100% rename from developer/source/network-client/wg_keys_incommon.py rename to developer/source/tunnel-client/wg_keys_incommon.py diff --git a/developer/source/network-server/set_client_key.sh b/developer/source/tunnel-server/set_client_key.sh similarity index 100% rename from developer/source/network-server/set_client_key.sh rename to developer/source/tunnel-server/set_client_key.sh diff --git a/developer/source/network-server/setup.sh b/developer/source/tunnel-server/setup.sh similarity index 100% rename from developer/source/network-server/setup.sh rename to developer/source/tunnel-server/setup.sh -- 2.20.1