furtka-gateway's wg-easy v15 client (wgeasy_v15.py) generates a
PresharedKey per WireGuard client by default, which the server enforces
even if the box's own conf omits it — dropping it here breaks the
handshake silently. Include it in [Peer] when present; v14 has no
preshared_key field at all, so this stays a no-op for that path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3jVwAqESRjPdW9P9nvfem
Found by actually running furtka serve and clicking through the new
Settings UI: /api/gateway/status crashed the whole request with a raw
PermissionError traceback because conf_path.is_file() doesn't swallow
every OSError (confirmed directly, not assumed) when the parent directory
isn't accessible. A read-only status check must degrade to "not
configured" instead of 500ing; set_tunnel_enabled() now raises a clean
WireguardError instead of leaking an unhandled exception.
Also gives wireguard.py an env-var-overridable default path
(FURTKA_WIREGUARD_CONF / default_conf_path()), matching every other
runtime path in the codebase, instead of a single hardcoded constant.
furtka.gateway: register/rotate-token/deregister against a furtka-gateway's
/v1/boxes/* API (stdlib urllib.request, same convention as
_release_common.py/updater.py). No local keypair generation — the
gateway's registration response carries the full WireGuard interface
config including the private key, since stock wg-easy always generates
the keypair itself (see furtka-gateway's wgeasy.py). Config persists to
paths.gateway_config_file() (0600, atomic write, same pattern as
auth.save_users).
furtka.wireguard: direct structural mirror of https.py's
snippet-write-plus-reload pattern for the wg-quick@furtka-gw systemd unit
— write/remove the .conf, flip the unit, revert the conf on failure so a
bad config can't leave the tunnel wedged.
paths.py gains gateway_config_file() and internet_routes_file()
(FURTKA_GATEWAY_CONFIG / FURTKA_INTERNET_ROUTES_FILE), following the
existing env-var-override convention.