From 7b22593dbf93069a56823b540578a0e044027524 Mon Sep 17 00:00:00 2001 From: Thomas Walker Lynch Date: Sat, 13 Sep 2025 07:33:09 -0700 Subject: [PATCH] directory reorg --- developer/{ => source}/cc/Db.lib.c | 0 developer/{ => source}/cc/DbSubu.lib.c | 0 developer/{ => source}/cc/Db_close.cli.c | 0 developer/{ => source}/cc/Hello.cli.c | 0 developer/{ => source}/cc/Hello.lib.c | 0 developer/{ => source}/cc/Server.cli.c | 0 developer/{ => source}/cc/Server.lib.c | 0 developer/{ => source}/cc/db_add_user.cli.c | 0 .../{ => source}/cc/db_delete_user.cli.c | 0 developer/{ => source}/cc/db_log_event.cli.c | 0 developer/{ => source}/cc/db_open.cli.c | 0 .../{ => source}/cc/db_validate_schema.cli.c | 0 .../{ => source/cc}/scratchpad/.gitignore | 0 developer/{ => source}/deprecated/.githolder | 0 .../{ => source}/deprecated/server.lib.c | 0 developer/{Python => source}/wg/.gitignore | 0 developer/{Python => source}/wg/db/.gitignore | 0 .../wg/db_bind_user_to_iface.py | 0 developer/{Python => source}/wg/db_checks.py | 0 .../wg/db_init_StanleyPark.py | 0 .../{Python => source}/wg/db_init_iface.py | 0 .../{Python => source}/wg/db_init_iface_US.py | 0 .../{Python => source}/wg/db_init_iface_x6.py | 0 .../wg/db_init_ip_iface_addr_assign.py | 0 .../wg/db_init_ip_table_registration.py | 0 .../wg/db_init_server_US.py | 0 .../wg/db_init_server_incommon.py | 0 .../wg/db_init_server_x6.py | 0 developer/{Python => source}/wg/db_schema.sql | 0 .../{Python => source}/wg/db_schema_load.sh | 0 developer/{Python => source}/wg/db_wipe.sh | 0 .../wg/deprecated/.gitignore | 0 .../wg/doc_IP_terminaology.org | 0 .../{Python => source}/wg/doc_config.org | 0 developer/{Python => source}/wg/doc_keys.org | 0 .../{Python => source}/wg/doc_stage_progs.org | 0 developer/{Python => source}/wg/iface_down.py | 0 .../{Python => source}/wg/iface_status.py | 0 developer/{Python => source}/wg/iface_up.sh | 0 developer/{Python => source}/wg/incommon.py | 0 developer/{Python => source}/wg/inspect.sh | 0 developer/{Python => source}/wg/inspect_1.py | 0 developer/source/wg/install.py | 233 ++++++++++++++++++ .../{Python => source}/wg/key/.gitignore | 0 .../wg/key_client_generate.py | 0 .../{Python => source}/wg/key_server_set.py | 0 developer/{Python => source}/wg/ls_iface.py | 0 developer/{Python => source}/wg/ls_key.py | 0 developer/{Python => source}/wg/ls_server.py | 0 .../wg/ls_server_setting.py | 0 developer/{Python => source}/wg/ls_servers.sh | 0 developer/{Python => source}/wg/ls_user.py | 0 .../wg/manual_reference.org | 0 .../{Python => source}/wg/manual_user.org | 0 .../wg/mothball/stage/.gitignore | 0 .../wg/mothball/stage_IP_routes_script.py | 0 .../wg/mothball/stage_IP_rules_script.py | 0 .../wg/mothball/stage_StanleyPark.py | 0 .../wg/mothball/stage_UID_routes.py | 0 .../wg/mothball/stage_list_clients.py | 0 .../wg/mothball/stage_list_uid.py | 0 .../wg/mothball/stage_populate.py | 0 .../wg/mothball/stage_preferred_server.py | 0 .../wg/mothball/stage_wg_conf.py | 0 .../wg/mothball/stage_wg_unit_IP_scripts.py | 0 .../wg/mothball/stage_wipe.py | 0 .../wg/scratchpad/.gitignore | 0 .../20-postup-ip-state.conf | 0 .../20-postup-ip-state.conf | 0 .../wg/stage/etc/wireguard/US.conf | 0 .../wg/stage/etc/wireguard/x6.conf | 0 .../wg/stage/usr/local/bin/apply_ip_state.sh | 0 .../wg/stage_IP_apply_script.py | 0 .../wg/stage_StanleyPark.py | 0 .../{Python => source}/wg/stage_client.py | 0 .../{Python => source}/wg/stage_wg_conf.py | 0 developer/{Python => source}/wg/stage_wipe.py | 0 developer/{Python => source}/wg/todo.org | 0 .../{Python => source}/wg/wg_keys_incommon.py | 0 {release => developer}/subu | 0 release/Python/.githolder | 0 release/amd54/.githolder | 0 release/shell/.githolder | 0 83 files changed, 233 insertions(+) rename developer/{ => source}/cc/Db.lib.c (100%) rename developer/{ => source}/cc/DbSubu.lib.c (100%) rename developer/{ => source}/cc/Db_close.cli.c (100%) rename developer/{ => source}/cc/Hello.cli.c (100%) rename developer/{ => source}/cc/Hello.lib.c (100%) rename developer/{ => source}/cc/Server.cli.c (100%) rename developer/{ => source}/cc/Server.lib.c (100%) rename developer/{ => source}/cc/db_add_user.cli.c (100%) rename developer/{ => source}/cc/db_delete_user.cli.c (100%) rename developer/{ => source}/cc/db_log_event.cli.c (100%) rename developer/{ => source}/cc/db_open.cli.c (100%) rename developer/{ => source}/cc/db_validate_schema.cli.c (100%) rename developer/{ => source/cc}/scratchpad/.gitignore (100%) rename developer/{ => source}/deprecated/.githolder (100%) rename developer/{ => source}/deprecated/server.lib.c (100%) rename developer/{Python => source}/wg/.gitignore (100%) rename developer/{Python => source}/wg/db/.gitignore (100%) rename developer/{Python => source}/wg/db_bind_user_to_iface.py (100%) rename developer/{Python => source}/wg/db_checks.py (100%) rename developer/{Python => source}/wg/db_init_StanleyPark.py (100%) rename developer/{Python => source}/wg/db_init_iface.py (100%) rename developer/{Python => source}/wg/db_init_iface_US.py (100%) rename developer/{Python => source}/wg/db_init_iface_x6.py (100%) rename developer/{Python => source}/wg/db_init_ip_iface_addr_assign.py (100%) rename developer/{Python => source}/wg/db_init_ip_table_registration.py (100%) rename developer/{Python => source}/wg/db_init_server_US.py (100%) rename developer/{Python => source}/wg/db_init_server_incommon.py (100%) rename developer/{Python => source}/wg/db_init_server_x6.py (100%) rename developer/{Python => source}/wg/db_schema.sql (100%) rename developer/{Python => source}/wg/db_schema_load.sh (100%) rename developer/{Python => source}/wg/db_wipe.sh (100%) rename developer/{Python => source}/wg/deprecated/.gitignore (100%) rename developer/{Python => source}/wg/doc_IP_terminaology.org (100%) rename developer/{Python => source}/wg/doc_config.org (100%) rename developer/{Python => source}/wg/doc_keys.org (100%) rename developer/{Python => source}/wg/doc_stage_progs.org (100%) rename developer/{Python => source}/wg/iface_down.py (100%) rename developer/{Python => source}/wg/iface_status.py (100%) rename developer/{Python => source}/wg/iface_up.sh (100%) rename developer/{Python => source}/wg/incommon.py (100%) rename developer/{Python => source}/wg/inspect.sh (100%) rename developer/{Python => source}/wg/inspect_1.py (100%) create mode 100644 developer/source/wg/install.py rename developer/{Python => source}/wg/key/.gitignore (100%) rename developer/{Python => source}/wg/key_client_generate.py (100%) rename developer/{Python => source}/wg/key_server_set.py (100%) rename developer/{Python => source}/wg/ls_iface.py (100%) rename developer/{Python => source}/wg/ls_key.py (100%) rename developer/{Python => source}/wg/ls_server.py (100%) rename developer/{Python => source}/wg/ls_server_setting.py (100%) rename developer/{Python => source}/wg/ls_servers.sh (100%) rename developer/{Python => source}/wg/ls_user.py (100%) rename developer/{Python => source}/wg/manual_reference.org (100%) rename developer/{Python => source}/wg/manual_user.org (100%) rename developer/{Python => source}/wg/mothball/stage/.gitignore (100%) rename developer/{Python => source}/wg/mothball/stage_IP_routes_script.py (100%) rename developer/{Python => source}/wg/mothball/stage_IP_rules_script.py (100%) rename developer/{Python => source}/wg/mothball/stage_StanleyPark.py (100%) rename developer/{Python => source}/wg/mothball/stage_UID_routes.py (100%) rename developer/{Python => source}/wg/mothball/stage_list_clients.py (100%) rename developer/{Python => source}/wg/mothball/stage_list_uid.py (100%) rename developer/{Python => source}/wg/mothball/stage_populate.py (100%) rename developer/{Python => source}/wg/mothball/stage_preferred_server.py (100%) rename developer/{Python => source}/wg/mothball/stage_wg_conf.py (100%) rename developer/{Python => source}/wg/mothball/stage_wg_unit_IP_scripts.py (100%) rename developer/{Python => source}/wg/mothball/stage_wipe.py (100%) rename developer/{Python => source}/wg/scratchpad/.gitignore (100%) rename developer/{Python => source}/wg/stage/etc/systemd/wg-quick@US.service.d/20-postup-ip-state.conf (100%) rename developer/{Python => source}/wg/stage/etc/systemd/wg-quick@x6.service.d/20-postup-ip-state.conf (100%) rename developer/{Python => source}/wg/stage/etc/wireguard/US.conf (100%) rename developer/{Python => source}/wg/stage/etc/wireguard/x6.conf (100%) rename developer/{Python => source}/wg/stage/usr/local/bin/apply_ip_state.sh (100%) rename developer/{Python => source}/wg/stage_IP_apply_script.py (100%) rename developer/{Python => source}/wg/stage_StanleyPark.py (100%) rename developer/{Python => source}/wg/stage_client.py (100%) rename developer/{Python => source}/wg/stage_wg_conf.py (100%) rename developer/{Python => source}/wg/stage_wipe.py (100%) rename developer/{Python => source}/wg/todo.org (100%) rename developer/{Python => source}/wg/wg_keys_incommon.py (100%) rename {release => developer}/subu (100%) create mode 100644 release/Python/.githolder create mode 100644 release/amd54/.githolder create mode 100644 release/shell/.githolder diff --git a/developer/cc/Db.lib.c b/developer/source/cc/Db.lib.c similarity index 100% rename from developer/cc/Db.lib.c rename to developer/source/cc/Db.lib.c diff --git a/developer/cc/DbSubu.lib.c b/developer/source/cc/DbSubu.lib.c similarity index 100% rename from developer/cc/DbSubu.lib.c rename to developer/source/cc/DbSubu.lib.c diff --git a/developer/cc/Db_close.cli.c b/developer/source/cc/Db_close.cli.c similarity index 100% rename from developer/cc/Db_close.cli.c rename to developer/source/cc/Db_close.cli.c diff --git a/developer/cc/Hello.cli.c b/developer/source/cc/Hello.cli.c similarity index 100% rename from developer/cc/Hello.cli.c rename to developer/source/cc/Hello.cli.c diff --git a/developer/cc/Hello.lib.c b/developer/source/cc/Hello.lib.c similarity index 100% rename from developer/cc/Hello.lib.c rename to developer/source/cc/Hello.lib.c diff --git a/developer/cc/Server.cli.c b/developer/source/cc/Server.cli.c similarity index 100% rename from developer/cc/Server.cli.c rename to developer/source/cc/Server.cli.c diff --git a/developer/cc/Server.lib.c b/developer/source/cc/Server.lib.c similarity index 100% rename from developer/cc/Server.lib.c rename to developer/source/cc/Server.lib.c diff --git a/developer/cc/db_add_user.cli.c b/developer/source/cc/db_add_user.cli.c similarity index 100% rename from developer/cc/db_add_user.cli.c rename to developer/source/cc/db_add_user.cli.c diff --git a/developer/cc/db_delete_user.cli.c b/developer/source/cc/db_delete_user.cli.c similarity index 100% rename from developer/cc/db_delete_user.cli.c rename to developer/source/cc/db_delete_user.cli.c diff --git a/developer/cc/db_log_event.cli.c b/developer/source/cc/db_log_event.cli.c similarity index 100% rename from developer/cc/db_log_event.cli.c rename to developer/source/cc/db_log_event.cli.c diff --git a/developer/cc/db_open.cli.c b/developer/source/cc/db_open.cli.c similarity index 100% rename from developer/cc/db_open.cli.c rename to developer/source/cc/db_open.cli.c diff --git a/developer/cc/db_validate_schema.cli.c b/developer/source/cc/db_validate_schema.cli.c similarity index 100% rename from developer/cc/db_validate_schema.cli.c rename to developer/source/cc/db_validate_schema.cli.c diff --git a/developer/scratchpad/.gitignore b/developer/source/cc/scratchpad/.gitignore similarity index 100% rename from developer/scratchpad/.gitignore rename to developer/source/cc/scratchpad/.gitignore diff --git a/developer/deprecated/.githolder b/developer/source/deprecated/.githolder similarity index 100% rename from developer/deprecated/.githolder rename to developer/source/deprecated/.githolder diff --git a/developer/deprecated/server.lib.c b/developer/source/deprecated/server.lib.c similarity index 100% rename from developer/deprecated/server.lib.c rename to developer/source/deprecated/server.lib.c diff --git a/developer/Python/wg/.gitignore b/developer/source/wg/.gitignore similarity index 100% rename from developer/Python/wg/.gitignore rename to developer/source/wg/.gitignore diff --git a/developer/Python/wg/db/.gitignore b/developer/source/wg/db/.gitignore similarity index 100% rename from developer/Python/wg/db/.gitignore rename to developer/source/wg/db/.gitignore diff --git a/developer/Python/wg/db_bind_user_to_iface.py b/developer/source/wg/db_bind_user_to_iface.py similarity index 100% rename from developer/Python/wg/db_bind_user_to_iface.py rename to developer/source/wg/db_bind_user_to_iface.py diff --git a/developer/Python/wg/db_checks.py b/developer/source/wg/db_checks.py similarity index 100% rename from developer/Python/wg/db_checks.py rename to developer/source/wg/db_checks.py diff --git a/developer/Python/wg/db_init_StanleyPark.py b/developer/source/wg/db_init_StanleyPark.py similarity index 100% rename from developer/Python/wg/db_init_StanleyPark.py rename to developer/source/wg/db_init_StanleyPark.py diff --git a/developer/Python/wg/db_init_iface.py b/developer/source/wg/db_init_iface.py similarity index 100% rename from developer/Python/wg/db_init_iface.py rename to developer/source/wg/db_init_iface.py diff --git a/developer/Python/wg/db_init_iface_US.py b/developer/source/wg/db_init_iface_US.py similarity index 100% rename from developer/Python/wg/db_init_iface_US.py rename to developer/source/wg/db_init_iface_US.py diff --git a/developer/Python/wg/db_init_iface_x6.py b/developer/source/wg/db_init_iface_x6.py similarity index 100% rename from developer/Python/wg/db_init_iface_x6.py rename to developer/source/wg/db_init_iface_x6.py diff --git a/developer/Python/wg/db_init_ip_iface_addr_assign.py b/developer/source/wg/db_init_ip_iface_addr_assign.py similarity index 100% rename from developer/Python/wg/db_init_ip_iface_addr_assign.py rename to developer/source/wg/db_init_ip_iface_addr_assign.py diff --git a/developer/Python/wg/db_init_ip_table_registration.py b/developer/source/wg/db_init_ip_table_registration.py similarity index 100% rename from developer/Python/wg/db_init_ip_table_registration.py rename to developer/source/wg/db_init_ip_table_registration.py diff --git a/developer/Python/wg/db_init_server_US.py b/developer/source/wg/db_init_server_US.py similarity index 100% rename from developer/Python/wg/db_init_server_US.py rename to developer/source/wg/db_init_server_US.py diff --git a/developer/Python/wg/db_init_server_incommon.py b/developer/source/wg/db_init_server_incommon.py similarity index 100% rename from developer/Python/wg/db_init_server_incommon.py rename to developer/source/wg/db_init_server_incommon.py diff --git a/developer/Python/wg/db_init_server_x6.py b/developer/source/wg/db_init_server_x6.py similarity index 100% rename from developer/Python/wg/db_init_server_x6.py rename to developer/source/wg/db_init_server_x6.py diff --git a/developer/Python/wg/db_schema.sql b/developer/source/wg/db_schema.sql similarity index 100% rename from developer/Python/wg/db_schema.sql rename to developer/source/wg/db_schema.sql diff --git a/developer/Python/wg/db_schema_load.sh b/developer/source/wg/db_schema_load.sh similarity index 100% rename from developer/Python/wg/db_schema_load.sh rename to developer/source/wg/db_schema_load.sh diff --git a/developer/Python/wg/db_wipe.sh b/developer/source/wg/db_wipe.sh similarity index 100% rename from developer/Python/wg/db_wipe.sh rename to developer/source/wg/db_wipe.sh diff --git a/developer/Python/wg/deprecated/.gitignore b/developer/source/wg/deprecated/.gitignore similarity index 100% rename from developer/Python/wg/deprecated/.gitignore rename to developer/source/wg/deprecated/.gitignore diff --git a/developer/Python/wg/doc_IP_terminaology.org b/developer/source/wg/doc_IP_terminaology.org similarity index 100% rename from developer/Python/wg/doc_IP_terminaology.org rename to developer/source/wg/doc_IP_terminaology.org diff --git a/developer/Python/wg/doc_config.org b/developer/source/wg/doc_config.org similarity index 100% rename from developer/Python/wg/doc_config.org rename to developer/source/wg/doc_config.org diff --git a/developer/Python/wg/doc_keys.org b/developer/source/wg/doc_keys.org similarity index 100% rename from developer/Python/wg/doc_keys.org rename to developer/source/wg/doc_keys.org diff --git a/developer/Python/wg/doc_stage_progs.org b/developer/source/wg/doc_stage_progs.org similarity index 100% rename from developer/Python/wg/doc_stage_progs.org rename to developer/source/wg/doc_stage_progs.org diff --git a/developer/Python/wg/iface_down.py b/developer/source/wg/iface_down.py similarity index 100% rename from developer/Python/wg/iface_down.py rename to developer/source/wg/iface_down.py diff --git a/developer/Python/wg/iface_status.py b/developer/source/wg/iface_status.py similarity index 100% rename from developer/Python/wg/iface_status.py rename to developer/source/wg/iface_status.py diff --git a/developer/Python/wg/iface_up.sh b/developer/source/wg/iface_up.sh similarity index 100% rename from developer/Python/wg/iface_up.sh rename to developer/source/wg/iface_up.sh diff --git a/developer/Python/wg/incommon.py b/developer/source/wg/incommon.py similarity index 100% rename from developer/Python/wg/incommon.py rename to developer/source/wg/incommon.py diff --git a/developer/Python/wg/inspect.sh b/developer/source/wg/inspect.sh similarity index 100% rename from developer/Python/wg/inspect.sh rename to developer/source/wg/inspect.sh diff --git a/developer/Python/wg/inspect_1.py b/developer/source/wg/inspect_1.py similarity index 100% rename from developer/Python/wg/inspect_1.py rename to developer/source/wg/inspect_1.py diff --git a/developer/source/wg/install.py b/developer/source/wg/install.py new file mode 100644 index 0000000..82e810d --- /dev/null +++ b/developer/source/wg/install.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +""" +install_staged_tree.py + +Given: + - A staged tree (default: ./stage) containing: + /usr/local/bin/apply_ip_state.sh + /etc/wireguard/*.conf + /etc/systemd/wg-quick@IFACE.service.d/*.conf + /etc/iproute2/rt_tables + - A destination root (default: /) whose *parent directories already exist* + +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 and deterministic permissions (see MODE_MAP) + - Optionally `systemctl daemon-reload` and restart provided wg-quick@IFACE units + +Returns: + - Exit 0 on success; non-zero on error + - Prints a concise log of actions + +Errors: + - Fails if a target parent directory is missing (unless --create-dirs is given) + - Fails on any copy/permission error and reports which path caused it +""" + +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 subprocess +import sys + +ROOT = Path(__file__).resolve().parent +DEFAULT_STAGE = ROOT / "stage" + +# Whitelist → permissions +# (relative glob inside stage) → (relative dest base, file mode) +MODE_MAP: Dict[str,Tuple[str,int]] = { + "usr/local/bin/*": ("usr/local/bin",0o500) # scripts: rx for root only + , "etc/wireguard/*.conf": ("etc/wireguard",0o600) # WG confs + , "etc/systemd/wg-quick@" : ("etc/systemd",0o644) # handled per-dropin below + , "etc/iproute2/rt_tables": ("etc/iproute2",0o644) # route tables file +} + +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 _iter_dropins(stage_root: Path) -> List[Tuple[Path,int]]: + """Return [(relpath,mode)] for systemd wg-quick drop-ins.""" + out: List[Tuple[Path,int]] = [] + base = stage_root / "etc" / "systemd" + if not base.exists(): + return out + for p in base.rglob("wg-quick@*.service.d/*.conf"): + rel = p.relative_to(stage_root) + out.append((rel,0o644)) + return out + +def _gather_stage_files(stage_root: Path) -> List[Tuple[Path,int]]: + """Resolve whitelist into [(relpath,mode)].""" + items: List[Tuple[Path,int]] = [] + # explicit patterns + for pat,(_dest_base,mode) in MODE_MAP.items(): + if pat.endswith("@"): # systemd base marker handled separately + continue + for p in (stage_root / pat).parent.glob(Path(pat).name): + rel = p.relative_to(stage_root) + items.append((rel,mode)) + # systemd drop-ins + items += _iter_dropins(stage_root) + # de-dup in order + seen = set() + uniq: List[Tuple[Path,int]] = [] + for rel,mode in items: + if rel not in seen: + uniq.append((rel,mode)) + seen.add(rel) + return uniq + +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") + # copy *bytes*, then set perms/owner, then atomic replace + shutil.copyfile(src,tmp) + os.chmod(tmp,mode) + try: + os.chown(tmp,0,0) # root:root + except PermissionError: + # setuid root expected; if not root, we still proceed for dry-run contexts + pass + os.replace(tmp,dst) + +def _maybe_daemon_reload(perform: bool) -> None: + if not perform: + return + subprocess.run( + ["systemctl","daemon-reload"] + ,check=False + ,stdout=subprocess.DEVNULL + ,stderr=subprocess.DEVNULL + ) + +def _maybe_restart_ifaces(ifaces: Sequence[str]) -> None: + for iface in ifaces: + unit = f"wg-quick@{iface}.service" + subprocess.run( + ["systemctl","restart",unit] + ,check=False + ,stdout=subprocess.DEVNULL + ,stderr=subprocess.DEVNULL + ) + +def install_staged_tree( + stage_root: Path + ,dest_root: Path + ,create_dirs: bool = False + ,skip_identical: bool = True + ,daemon_reload: bool = False + ,restart_ifaces: Sequence[str] = () +) -> List[str]: + """ + Core business function. + + Given: + stage_root, dest_root, flags + Does: + safe, deterministic copy with backups and explicit perms + Returns: + list of log lines + """ + # Do not rely on process umask; set restrictive default, then override per-file. + old_umask = os.umask(0o077) + logs: List[str] = [] + try: + staged = _gather_stage_files(stage_root) + if not staged: + raise RuntimeError("nothing to install (stage is empty or whitelist didn’t match)") + + for rel,mode in staged: + src = stage_root / rel + dst = dest_root / rel + + _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)})") + + if daemon_reload: + _maybe_daemon_reload(True) + logs.append("systemctl: daemon-reload") + + if restart_ifaces: + _maybe_restart_ifaces(restart_ifaces) + logs.append(f"systemctl: restart wg-quick@{','.join(restart_ifaces)}") + + return logs + finally: + os.umask(old_umask) + +def _require_root() -> None: + if os.geteuid() != 0: + raise RuntimeError("must run as root (installer sets ownership/permissions)") + +def main(argv: Optional[Sequence[str]] = None) -> int: + ap = argparse.ArgumentParser(description="Install staged artifacts into a target root (root-only).") + 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("--daemon-reload" ,action="store_true" ,help="run systemctl daemon-reload after install") + ap.add_argument("--restart-ifaces" ,nargs="*" ,default=[] ,help="optionally restart these wg-quick@IFACE units") + args = ap.parse_args(argv) + + try: + _require_root() + logs = 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) + ,daemon_reload=args.daemon_reload + ,restart_ifaces=args.restart_ifaces + ) + for line in logs: + print(line) + 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/Python/wg/key/.gitignore b/developer/source/wg/key/.gitignore similarity index 100% rename from developer/Python/wg/key/.gitignore rename to developer/source/wg/key/.gitignore diff --git a/developer/Python/wg/key_client_generate.py b/developer/source/wg/key_client_generate.py similarity index 100% rename from developer/Python/wg/key_client_generate.py rename to developer/source/wg/key_client_generate.py diff --git a/developer/Python/wg/key_server_set.py b/developer/source/wg/key_server_set.py similarity index 100% rename from developer/Python/wg/key_server_set.py rename to developer/source/wg/key_server_set.py diff --git a/developer/Python/wg/ls_iface.py b/developer/source/wg/ls_iface.py similarity index 100% rename from developer/Python/wg/ls_iface.py rename to developer/source/wg/ls_iface.py diff --git a/developer/Python/wg/ls_key.py b/developer/source/wg/ls_key.py similarity index 100% rename from developer/Python/wg/ls_key.py rename to developer/source/wg/ls_key.py diff --git a/developer/Python/wg/ls_server.py b/developer/source/wg/ls_server.py similarity index 100% rename from developer/Python/wg/ls_server.py rename to developer/source/wg/ls_server.py diff --git a/developer/Python/wg/ls_server_setting.py b/developer/source/wg/ls_server_setting.py similarity index 100% rename from developer/Python/wg/ls_server_setting.py rename to developer/source/wg/ls_server_setting.py diff --git a/developer/Python/wg/ls_servers.sh b/developer/source/wg/ls_servers.sh similarity index 100% rename from developer/Python/wg/ls_servers.sh rename to developer/source/wg/ls_servers.sh diff --git a/developer/Python/wg/ls_user.py b/developer/source/wg/ls_user.py similarity index 100% rename from developer/Python/wg/ls_user.py rename to developer/source/wg/ls_user.py diff --git a/developer/Python/wg/manual_reference.org b/developer/source/wg/manual_reference.org similarity index 100% rename from developer/Python/wg/manual_reference.org rename to developer/source/wg/manual_reference.org diff --git a/developer/Python/wg/manual_user.org b/developer/source/wg/manual_user.org similarity index 100% rename from developer/Python/wg/manual_user.org rename to developer/source/wg/manual_user.org diff --git a/developer/Python/wg/mothball/stage/.gitignore b/developer/source/wg/mothball/stage/.gitignore similarity index 100% rename from developer/Python/wg/mothball/stage/.gitignore rename to developer/source/wg/mothball/stage/.gitignore diff --git a/developer/Python/wg/mothball/stage_IP_routes_script.py b/developer/source/wg/mothball/stage_IP_routes_script.py similarity index 100% rename from developer/Python/wg/mothball/stage_IP_routes_script.py rename to developer/source/wg/mothball/stage_IP_routes_script.py diff --git a/developer/Python/wg/mothball/stage_IP_rules_script.py b/developer/source/wg/mothball/stage_IP_rules_script.py similarity index 100% rename from developer/Python/wg/mothball/stage_IP_rules_script.py rename to developer/source/wg/mothball/stage_IP_rules_script.py diff --git a/developer/Python/wg/mothball/stage_StanleyPark.py b/developer/source/wg/mothball/stage_StanleyPark.py similarity index 100% rename from developer/Python/wg/mothball/stage_StanleyPark.py rename to developer/source/wg/mothball/stage_StanleyPark.py diff --git a/developer/Python/wg/mothball/stage_UID_routes.py b/developer/source/wg/mothball/stage_UID_routes.py similarity index 100% rename from developer/Python/wg/mothball/stage_UID_routes.py rename to developer/source/wg/mothball/stage_UID_routes.py diff --git a/developer/Python/wg/mothball/stage_list_clients.py b/developer/source/wg/mothball/stage_list_clients.py similarity index 100% rename from developer/Python/wg/mothball/stage_list_clients.py rename to developer/source/wg/mothball/stage_list_clients.py diff --git a/developer/Python/wg/mothball/stage_list_uid.py b/developer/source/wg/mothball/stage_list_uid.py similarity index 100% rename from developer/Python/wg/mothball/stage_list_uid.py rename to developer/source/wg/mothball/stage_list_uid.py diff --git a/developer/Python/wg/mothball/stage_populate.py b/developer/source/wg/mothball/stage_populate.py similarity index 100% rename from developer/Python/wg/mothball/stage_populate.py rename to developer/source/wg/mothball/stage_populate.py diff --git a/developer/Python/wg/mothball/stage_preferred_server.py b/developer/source/wg/mothball/stage_preferred_server.py similarity index 100% rename from developer/Python/wg/mothball/stage_preferred_server.py rename to developer/source/wg/mothball/stage_preferred_server.py diff --git a/developer/Python/wg/mothball/stage_wg_conf.py b/developer/source/wg/mothball/stage_wg_conf.py similarity index 100% rename from developer/Python/wg/mothball/stage_wg_conf.py rename to developer/source/wg/mothball/stage_wg_conf.py diff --git a/developer/Python/wg/mothball/stage_wg_unit_IP_scripts.py b/developer/source/wg/mothball/stage_wg_unit_IP_scripts.py similarity index 100% rename from developer/Python/wg/mothball/stage_wg_unit_IP_scripts.py rename to developer/source/wg/mothball/stage_wg_unit_IP_scripts.py diff --git a/developer/Python/wg/mothball/stage_wipe.py b/developer/source/wg/mothball/stage_wipe.py similarity index 100% rename from developer/Python/wg/mothball/stage_wipe.py rename to developer/source/wg/mothball/stage_wipe.py diff --git a/developer/Python/wg/scratchpad/.gitignore b/developer/source/wg/scratchpad/.gitignore similarity index 100% rename from developer/Python/wg/scratchpad/.gitignore rename to developer/source/wg/scratchpad/.gitignore diff --git a/developer/Python/wg/stage/etc/systemd/wg-quick@US.service.d/20-postup-ip-state.conf b/developer/source/wg/stage/etc/systemd/wg-quick@US.service.d/20-postup-ip-state.conf similarity index 100% rename from developer/Python/wg/stage/etc/systemd/wg-quick@US.service.d/20-postup-ip-state.conf rename to developer/source/wg/stage/etc/systemd/wg-quick@US.service.d/20-postup-ip-state.conf diff --git a/developer/Python/wg/stage/etc/systemd/wg-quick@x6.service.d/20-postup-ip-state.conf b/developer/source/wg/stage/etc/systemd/wg-quick@x6.service.d/20-postup-ip-state.conf similarity index 100% rename from developer/Python/wg/stage/etc/systemd/wg-quick@x6.service.d/20-postup-ip-state.conf rename to developer/source/wg/stage/etc/systemd/wg-quick@x6.service.d/20-postup-ip-state.conf diff --git a/developer/Python/wg/stage/etc/wireguard/US.conf b/developer/source/wg/stage/etc/wireguard/US.conf similarity index 100% rename from developer/Python/wg/stage/etc/wireguard/US.conf rename to developer/source/wg/stage/etc/wireguard/US.conf diff --git a/developer/Python/wg/stage/etc/wireguard/x6.conf b/developer/source/wg/stage/etc/wireguard/x6.conf similarity index 100% rename from developer/Python/wg/stage/etc/wireguard/x6.conf rename to developer/source/wg/stage/etc/wireguard/x6.conf diff --git a/developer/Python/wg/stage/usr/local/bin/apply_ip_state.sh b/developer/source/wg/stage/usr/local/bin/apply_ip_state.sh similarity index 100% rename from developer/Python/wg/stage/usr/local/bin/apply_ip_state.sh rename to developer/source/wg/stage/usr/local/bin/apply_ip_state.sh diff --git a/developer/Python/wg/stage_IP_apply_script.py b/developer/source/wg/stage_IP_apply_script.py similarity index 100% rename from developer/Python/wg/stage_IP_apply_script.py rename to developer/source/wg/stage_IP_apply_script.py diff --git a/developer/Python/wg/stage_StanleyPark.py b/developer/source/wg/stage_StanleyPark.py similarity index 100% rename from developer/Python/wg/stage_StanleyPark.py rename to developer/source/wg/stage_StanleyPark.py diff --git a/developer/Python/wg/stage_client.py b/developer/source/wg/stage_client.py similarity index 100% rename from developer/Python/wg/stage_client.py rename to developer/source/wg/stage_client.py diff --git a/developer/Python/wg/stage_wg_conf.py b/developer/source/wg/stage_wg_conf.py similarity index 100% rename from developer/Python/wg/stage_wg_conf.py rename to developer/source/wg/stage_wg_conf.py diff --git a/developer/Python/wg/stage_wipe.py b/developer/source/wg/stage_wipe.py similarity index 100% rename from developer/Python/wg/stage_wipe.py rename to developer/source/wg/stage_wipe.py diff --git a/developer/Python/wg/todo.org b/developer/source/wg/todo.org similarity index 100% rename from developer/Python/wg/todo.org rename to developer/source/wg/todo.org diff --git a/developer/Python/wg/wg_keys_incommon.py b/developer/source/wg/wg_keys_incommon.py similarity index 100% rename from developer/Python/wg/wg_keys_incommon.py rename to developer/source/wg/wg_keys_incommon.py diff --git a/release/subu b/developer/subu similarity index 100% rename from release/subu rename to developer/subu diff --git a/release/Python/.githolder b/release/Python/.githolder new file mode 100644 index 0000000..e69de29 diff --git a/release/amd54/.githolder b/release/amd54/.githolder new file mode 100644 index 0000000..e69de29 diff --git a/release/shell/.githolder b/release/shell/.githolder new file mode 100644 index 0000000..e69de29 -- 2.20.1