{"id":"446af6dc-2226-4b76-8da3-33c2b4e81d54","entityType":"agent","slug":"clawhub-agentx-icu-tox-tunnel-ops","name":"tox-tunnel-ops","canonicalUrl":"https://www.xpersona.co/agent/clawhub-agentx-icu-tox-tunnel-ops","canonicalPath":"/agent/clawhub-agentx-icu-tox-tunnel-ops","generatedAt":"2026-10-10T07:42:31.294Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T00:09:35.009Z","emptyReason":null},"description":"Encrypted P2P TCP tunneling for remote network access — a self-hosted VPN / ngrok / Tailscale alternative built on the Tox protocol (libsodium). No API keys, no accounts, no central servers, no port-forwarding. Solves NAT traversal, carrier-grade NAT, double NAT, intranet penetration (内网穿透), and remote machine access without router or firewall changes. Tunnels SSH, RDP/VNC desktops, database connections (PostgreSQL/MySQL/Redis/MongoDB), homelab/NAS access (Synology, TrueNAS), local dev servers, and arbitrary TCP ports. Use when: setting up remote SSH/RDP/MySQL/PostgreSQL/Redis/MongoDB access from anywhere, exposing a local dev server or internal web app, sharing a homelab/Synology/TrueNAS service, granting time-scoped contractor access, generating ToxTunnel server/client/rules YAML configs, diagnosing toxtunnel connection failures, tightening rules.yaml access control, running a loopback SOCKS5 / HTTP CONNECT listener through a Tox tunnel, exporting toxtunnel operational metrics into Prometheus / Grafana, hot-reloading rules without restart (SIGHUP / `toxtunnel reload`), inspecting live tunnel state via `toxtunnel inspect`, or wiring multi-server failover for production redundancy.","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.9K downloads reported by the source. Last updated 10/10/2026.","installCommand":"clawhub skill install s17ewfqx5ypm5882jq61sbrnyx83hg57:tox-tunnel-ops","sourceUrl":"https://clawhub.ai/agentx-icu/tox-tunnel-ops","homepage":"https://clawhub.ai/agentx-icu/skills/tox-tunnel-ops","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/agentx-icu/tox-tunnel-ops","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/agentx-icu/skills/tox-tunnel-ops","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":65,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"tox-tunnel-ops technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-10T00:09:35.009Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T00:09:35.009Z","emptyReason":null},"stars":null,"forks":null,"downloads":1850,"packageName":null,"latestVersion":"0.4.14","tractionLabel":"1.9K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T00:09:35.008Z","emptyReason":null},"lastUpdatedAt":"2026-10-10T00:09:35.009Z","lastCrawledAt":"2026-10-10T00:09:35.008Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-11T00:09:35.008Z","lastVerifiedAt":null,"highlights":[{"version":"0.4.14","createdAt":"2026-08-31T14:07:30.591Z","changelog":"Tracks toxtunnel v0.4.13, and fixes 25 findings from an independent review of this skill. Three of those would have made an agent act wrongly. Static forwards bind 0.0.0.0, but the skill presented local_port as loopback-only, so following the SSH or database examples silently exposed the service to the LAN - now every generated forward carries local_address: 127.0.0.1 on v0.4.13+, with the firewall/SOCKS5 path for older daemons. verify.sh could exit 0 after verification failed, while the workflow treats it as the final check; it now has a three-state exit (proven / failed / NOT PROVEN) and the probes that only prove a local accept say so instead of claiming end-to-end success. And Revoke immediately routed to hot reload, which does not close live tunnels - that is now split into blocking new sessions versus terminating current access. v0.4.13 product changes reflected here: forwards take local_address (numeric IP literal; the daemon warns only when the key is absent and the bind is non-loopback), and config check now resolves known-servers aliases, so the old alias false-blocker is scoped to v0.4.12 and older rather than stated as current. diagnose.sh distinguishes the bind provenances instead of lumping them: an absent key, an explicit IPv4 wildcard, an explicit ::, a specific non-loopback interface and loopback each get their own treatment, and an invalid literal like * or [::] is reported as a config error rather than a bind. It also gained the portable timeout wrapper verify.sh already had, which was turning the inspect probe into a false warning on stock macOS. Two judgement calls. Tox IDs are no longer treated as secrets - they are public credentials like an SSH public key, the server is default-deny, and the old wording was unsatisfiable in its own workflow since server_id, rules.yaml and known_servers.yaml must all persist them; the prohibition moved to tox_save.dat, where an encrypted operator-controlled backup is the one legitimate copy. And the default install no longer pipes a master-branch script into sudo sh, while stating accurately that installing a package still runs maintainer scripts as root and that GitHub does expose a per-asset digest to compare against. templates/*.tpl.yaml are now actually published: they were named *.yaml.tpl, and the publishing CLI only uploads a fixed set of text extensions, so every prior version shipped without them.","fileCount":21,"zipByteSize":129157},{"version":"0.4.13","createdAt":"2026-08-30T15:12:44.139Z","changelog":"Republish of 0.4.12, which shipped stale file content: its SKILL.md was the 0.4.10 text (43KB instead of 48KB) despite the correct changelog. Use this version, not 0.4.12. Content tracks toxtunnel v0.4.11 and v0.4.12. Byte rate limiting is implemented now. bytes_per_sec / bytes_burst were parsed but inert for the whole v0.4.x line, and this skill previously carried a routing rule telling the agent to refuse such requests and point at an OS-level shaper instead. That row now routes it: inbound TUNNEL_DATA from the friend, off/report/enforce, defer-and-replay rather than drop. It still points at tc or pf for the two cases this does not cover - capping what the host sends, and a hostile peer. TUNNEL_ERROR codes are disjoint as of v0.4.12: 1 policy denial, 2 general non-policy failure, 3 actually refused. A rate-limited open used to reach a SOCKS5 caller as 0x04 host unreachable, indistinguishable from a dead target; it now answers 0x02 / HTTP 403. Refusal detection no longer depends on the OS message language, which had it broken on non-English Windows. Also: the pid file, toxtunnel reload and inspect, systemctl reload, config check --strict for unknown keys, the data-dir exclusive lock, HTTP CONNECT status mapping alongside the SOCKS5 codes, the Windows sub-tick coalesce clamp, the OPEN/ACK outbound barrier, and an operator grep that could never match (the watchdog emits tox_thread wedge detected, with a space). Note: templates/*.yaml.tpl are absent from every published version - the CLI only uploads a fixed set of text extensions and .tpl is not among them. Fetch them from the GitHub repo.","fileCount":18,"zipByteSize":76937},{"version":"0.4.12","createdAt":"2026-08-30T15:01:11.846Z","changelog":"Tracks toxtunnel v0.4.11 and v0.4.12. Byte rate limiting is real now. bytes_per_sec / bytes_burst were parsed but inert for the whole v0.4.x line, and this skill said so - one of the three places was a routing rule telling the agent to refuse the request and point at tc or pf instead. That row now routes it properly: inbound TUNNEL_DATA from the friend, off/report/enforce, defer-and-replay rather than drop. It still points at an OS-level shaper for the two cases this genuinely does not cover - capping what the host sends, and a hostile peer - because a receiver-side deferral cannot hold an average rate against someone who ignores flow control. TUNNEL_ERROR codes are disjoint as of v0.4.12: 1 policy denial, 2 general non-policy failure, 3 actually refused. A rate-limited open used to reach a SOCKS5 caller as 0x04 host unreachable, indistinguishable from a dead target; it now answers 0x02 / 403. Refusal detection no longer depends on the OS message language, which had it broken on non-English Windows. The diagnose reference documented the old behaviour as a trap with a workaround; that is replaced. Also: the pid file, toxtunnel reload and inspect (v0.4.11+); systemctl reload; config check --strict for unknown keys; the data-dir exclusive lock and its override; HTTP CONNECT status mapping alongside the SOCKS5 codes; Windows treats a sub-tick coalesce delay as 0, so the 200us default batches nothing there; and the OPEN/ACK outbound barrier, which replaced the old note about OPEN_ACK sharing the manager retry queue. Fixed an operator grep that could never match: the watchdog emits tox_thread wedge detected with a space, not tox_thread_wedge.","fileCount":18,"zipByteSize":62964},{"version":"0.4.10","createdAt":"2026-07-10T06:17:10.318Z","changelog":"Docs for toxtunnel v0.4.9: warn that v0.4.8 Linux packages lose the server identity on every restart (manylinux fs::path toolchain bug, fixed+self-healing in v0.4.9); new diagnose entries — identity-changes-on-restart, print-id vs service-account identity mismatch, second lan-mode daemon can't LAN-bootstrap (port 33445); print-id service-account guidance; Windows-over-SSH process survival note (scheduled task / SCM service); MSI service-registration status updated for 0.4.8.","fileCount":18,"zipByteSize":62953},{"version":"0.4.9","createdAt":"2026-07-08T16:56:13.619Z","changelog":"0.4.9: correct GitHub owner URLs to agentx-icu across SKILL.md + references (the 0.4.8 publish had shipped stale anonymoussoft URLs). GitHub account renamed anonymoussoft → agentx-icu; repo/releases/install one-liners all repointed.","fileCount":18,"zipByteSize":61919},{"version":"0.4.8","createdAt":"2026-07-08T16:39:57.413Z","changelog":"0.4.8: repoint all GitHub URLs to the new agentx-icu owner (install one-liners, release downloads, homepage); add same-host local-loopback test walkthrough (examples/local-loopback-test.md); minor diagnose reference update. Aligns with tox-tcp-tunnel v0.4.8 release.","fileCount":17,"zipByteSize":58093},{"version":"0.4.7","createdAt":"2026-05-29T07:13:49.077Z","changelog":"v0.4.7: SKILL.md description rewrite to clear the ClawHub auto-classifier's 'Data & APIs' bucket and the resulting '🔑 API key required' badge. The skill genuinely doesn't need an API key — it needs the toxtunnel binary on PATH (declared in metadata.openclaw.requires.bins). The badge came from the classifier picking up Prometheus/SOCKS5/database-protocol mentions in the previous description. Description changes: - Now leads with 'encrypted P2P TCP tunneling for remote network access' (networking-first framing) - States 'No API keys, no accounts, no central servers, no port-forwarding' near the top - Replaces 'dynamic SOCKS5/HTTP CONNECT proxy' with 'loopback SOCKS5/HTTP CONNECT listener' - Replaces 'scraping Prometheus /metrics into Grafana' with 'exporting toxtunnel operational metrics into Prometheus/Grafana' No code changes. toxtunnel binary remains v0.4.6. PR: https://github.com/anonymoussoft/tox-tcp-tunnel/pull/12","fileCount":17,"zipByteSize":58027},{"version":"0.4.6","createdAt":"2026-05-29T05:22:19.321Z","changelog":"v0.4.6: control-frame SENDQ retry queue + log flush + client bytes_out + close-handshake hang fix. - Fixes 4 SENDQ-loss correctness bugs found via burst + bidirectional load testing of v0.4.5. - Plus 2 codex-review follow-ups: server affinity for queued frames during failover, and permanent-vs-transient send failure distinction (TunnelImpl::open now correctly rolls back on hard send failures). - Skill diagnose.md gains a new stuck-Connecting/Disconnecting symptom section pointing at the pending-queue cap-overflow log line and the tox_iterate_lag_milliseconds_max metric. - SKILL.md metric reference corrected from toxtunnel_tox_iterate_lag_ms to toxtunnel_tox_iterate_lag_milliseconds_max. Release: https://github.com/anonymoussoft/tox-tcp-tunnel/releases/tag/v0.4.6 PR: https://github.com/anonymoussoft/tox-tcp-tunnel/pull/11","fileCount":17,"zipByteSize":57951}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17ewfqx5ypm5882jq61sbrnyx83hg57:tox-tunnel-ops","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s17ewfqx5ypm5882jq61sbrnyx83hg57:tox-tunnel-ops` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/agentx-icu/tox-tunnel-ops before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-agentx-icu-tox-tunnel-ops/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-agentx-icu-tox-tunnel-ops/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-agentx-icu-tox-tunnel-ops/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-agentx-icu-tox-tunnel-ops/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-agentx-icu-tox-tunnel-ops/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-agentx-icu-tox-tunnel-ops/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-10T07:42:31.291Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-agentx-icu-tox-tunnel-ops/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-agentx-icu-tox-tunnel-ops/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-agentx-icu-tox-tunnel-ops/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-agentx-icu-tox-tunnel-ops/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T00:09:35.009Z","emptyReason":null},"readme":"Skill: tox-tunnel-ops\n\nOwner: agentx-icu\n\nSummary: Encrypted P2P TCP tunneling for remote network access — a self-hosted VPN / ngrok / Tailscale alternative built on the Tox protocol (libsodium). No API keys, no accounts, no central servers, no port-forwarding. Solves NAT traversal, carrier-grade NAT, double NAT, intranet penetration (内网穿透), and remote machine access without router or firewall changes. Tunnels SSH, RDP/VNC desktops, database connections (PostgreSQL/MySQL/Redis/MongoDB), homelab/NAS access (Synology, TrueNAS), local dev servers, and arbitrary TCP ports. Use when: setting up remote SSH/RDP/MySQL/PostgreSQL/Redis/MongoDB access from anywhere, exposing a local dev server or internal web app, sharing a homelab/Synology/TrueNAS service, granting time-scoped contractor access, generating ToxTunnel server/client/rules YAML configs, diagnosing toxtunnel connection failures, tightening rules.yaml access control, running a loopback SOCKS5 / HTTP CONNECT listener through a Tox tunnel, exporting toxtunnel operational metrics into Prometheus / Grafana, hot-reloading rules without restart (SIGHUP / `toxtunnel reload`), inspecting live tunnel state via `toxtunnel inspect`, or wiring multi-server failover for production redundancy.\n\nTags: latest:0.4.14\n\nVersion history:\n\nv0.4.14 | 2026-08-31T14:07:30.591Z | user\n\nTracks toxtunnel v0.4.13, and fixes 25 findings from an independent review of this skill.\n\nThree of those would have made an agent act wrongly. Static forwards bind 0.0.0.0, but the skill presented local_port as loopback-only, so following the SSH or database examples silently exposed the service to the LAN - now every generated forward carries local_address: 127.0.0.1 on v0.4.13+, with the firewall/SOCKS5 path for older daemons. verify.sh could exit 0 after verification failed, while the workflow treats it as the final check; it now has a three-state exit (proven / failed / NOT PROVEN) and the probes that only prove a local accept say so instead of claiming end-to-end success. And Revoke immediately routed to hot reload, which does not close live tunnels - that is now split into blocking new sessions versus terminating current access.\n\nv0.4.13 product changes reflected here: forwards take local_address (numeric IP literal; the daemon warns only when the key is absent and the bind is non-loopback), and config check now resolves known-servers aliases, so the old alias false-blocker is scoped to v0.4.12 and older rather than stated as current.\n\ndiagnose.sh distinguishes the bind provenances instead of lumping them: an absent key, an explicit IPv4 wildcard, an explicit ::, a specific non-loopback interface and loopback each get their own treatment, and an invalid literal like * or [::] is reported as a config error rather than a bind. It also gained the portable timeout wrapper verify.sh already had, which was turning the inspect probe into a false warning on stock macOS.\n\nTwo judgement calls. Tox IDs are no longer treated as secrets - they are public credentials like an SSH public key, the server is default-deny, and the old wording was unsatisfiable in its own workflow since server_id, rules.yaml and known_servers.yaml must all persist them; the prohibition moved to tox_save.dat, where an encrypted operator-controlled backup is the one legitimate copy. And the default install no longer pipes a master-branch script into sudo sh, while stating accurately that installing a package still runs maintainer scripts as root and that GitHub does expose a per-asset digest to compare against.\n\ntemplates/*.tpl.yaml are now actually published: they were named *.yaml.tpl, and the publishing CLI only uploads a fixed set of text extensions, so every prior version shipped without them.\n\nv0.4.13 | 2026-08-30T15:12:44.139Z | user\n\nRepublish of 0.4.12, which shipped stale file content: its SKILL.md was the 0.4.10 text (43KB instead of 48KB) despite the correct changelog. Use this version, not 0.4.12.\n\nContent tracks toxtunnel v0.4.11 and v0.4.12.\n\nByte rate limiting is implemented now. bytes_per_sec / bytes_burst were parsed but inert for the whole v0.4.x line, and this skill previously carried a routing rule telling the agent to refuse such requests and point at an OS-level shaper instead. That row now routes it: inbound TUNNEL_DATA from the friend, off/report/enforce, defer-and-replay rather than drop. It still points at tc or pf for the two cases this does not cover - capping what the host sends, and a hostile peer.\n\nTUNNEL_ERROR codes are disjoint as of v0.4.12: 1 policy denial, 2 general non-policy failure, 3 actually refused. A rate-limited open used to reach a SOCKS5 caller as 0x04 host unreachable, indistinguishable from a dead target; it now answers 0x02 / HTTP 403. Refusal detection no longer depends on the OS message language, which had it broken on non-English Windows.\n\nAlso: the pid file, toxtunnel reload and inspect, systemctl reload, config check --strict for unknown keys, the data-dir exclusive lock, HTTP CONNECT status mapping alongside the SOCKS5 codes, the Windows sub-tick coalesce clamp, the OPEN/ACK outbound barrier, and an operator grep that could never match (the watchdog emits tox_thread wedge detected, with a space).\n\nNote: templates/*.yaml.tpl are absent from every published version - the CLI only uploads a fixed set of text extensions and .tpl is not among them. Fetch them from the GitHub repo.\n\nv0.4.12 | 2026-08-30T15:01:11.846Z | user\n\nTracks toxtunnel v0.4.11 and v0.4.12.\n\nByte rate limiting is real now. bytes_per_sec / bytes_burst were parsed but inert for the whole v0.4.x line, and this skill said so - one of the three places was a routing rule telling the agent to refuse the request and point at tc or pf instead. That row now routes it properly: inbound TUNNEL_DATA from the friend, off/report/enforce, defer-and-replay rather than drop. It still points at an OS-level shaper for the two cases this genuinely does not cover - capping what the host sends, and a hostile peer - because a receiver-side deferral cannot hold an average rate against someone who ignores flow control.\n\nTUNNEL_ERROR codes are disjoint as of v0.4.12: 1 policy denial, 2 general non-policy failure, 3 actually refused. A rate-limited open used to reach a SOCKS5 caller as 0x04 host unreachable, indistinguishable from a dead target; it now answers 0x02 / 403. Refusal detection no longer depends on the OS message language, which had it broken on non-English Windows. The diagnose reference documented the old behaviour as a trap with a workaround; that is replaced.\n\nAlso: the pid file, toxtunnel reload and inspect (v0.4.11+); systemctl reload; config check --strict for unknown keys; the data-dir exclusive lock and its override; HTTP CONNECT status mapping alongside the SOCKS5 codes; Windows treats a sub-tick coalesce delay as 0, so the 200us default batches nothing there; and the OPEN/ACK outbound barrier, which replaced the old note about OPEN_ACK sharing the manager retry queue.\n\nFixed an operator grep that could never match: the watchdog emits tox_thread wedge detected with a space, not tox_thread_wedge.\n\nv0.4.10 | 2026-07-10T06:17:10.318Z | user\n\nDocs for toxtunnel v0.4.9: warn that v0.4.8 Linux packages lose the server identity on every restart (manylinux fs::path toolchain bug, fixed+self-healing in v0.4.9); new diagnose entries — identity-changes-on-restart, print-id vs service-account identity mismatch, second lan-mode daemon can't LAN-bootstrap (port 33445); print-id service-account guidance; Windows-over-SSH process survival note (scheduled task / SCM service); MSI service-registration status updated for 0.4.8.\n\nv0.4.9 | 2026-07-08T16:56:13.619Z | user\n\n0.4.9: correct GitHub owner URLs to agentx-icu across SKILL.md + references (the 0.4.8 publish had shipped stale anonymoussoft URLs). GitHub account renamed anonymoussoft → agentx-icu; repo/releases/install one-liners all repointed.\n\nv0.4.8 | 2026-07-08T16:39:57.413Z | user\n\n0.4.8: repoint all GitHub URLs to the new agentx-icu owner (install one-liners, release downloads, homepage); add same-host local-loopback test walkthrough (examples/local-loopback-test.md); minor diagnose reference update. Aligns with tox-tcp-tunnel v0.4.8 release.\n\nv0.4.7 | 2026-05-29T07:13:49.077Z | user\n\nv0.4.7: SKILL.md description rewrite to clear the ClawHub auto-classifier's 'Data & APIs' bucket and the resulting '🔑 API key required' badge.\n\nThe skill genuinely doesn't need an API key — it needs the toxtunnel binary on PATH (declared in metadata.openclaw.requires.bins). The badge came from the classifier picking up Prometheus/SOCKS5/database-protocol mentions in the previous description.\n\nDescription changes:\n- Now leads with 'encrypted P2P TCP tunneling for remote network access' (networking-first framing)\n- States 'No API keys, no accounts, no central servers, no port-forwarding' near the top\n- Replaces 'dynamic SOCKS5/HTTP CONNECT proxy' with 'loopback SOCKS5/HTTP CONNECT listener'\n- Replaces 'scraping Prometheus /metrics into Grafana' with 'exporting toxtunnel operational metrics into Prometheus/Grafana'\n\nNo code changes. toxtunnel binary remains v0.4.6.\n\nPR: https://github.com/anonymoussoft/tox-tcp-tunnel/pull/12\n\nv0.4.6 | 2026-05-29T05:22:19.321Z | user\n\nv0.4.6: control-frame SENDQ retry queue + log flush + client bytes_out + close-handshake hang fix.\n\n- Fixes 4 SENDQ-loss correctness bugs found via burst + bidirectional load testing of v0.4.5.\n- Plus 2 codex-review follow-ups: server affinity for queued frames during failover, and permanent-vs-transient send failure distinction (TunnelImpl::open now correctly rolls back on hard send failures).\n- Skill diagnose.md gains a new stuck-Connecting/Disconnecting symptom section pointing at the pending-queue cap-overflow log line and the tox_iterate_lag_milliseconds_max metric.\n- SKILL.md metric reference corrected from toxtunnel_tox_iterate_lag_ms to toxtunnel_tox_iterate_lag_milliseconds_max.\n\nRelease: https://github.com/anonymoussoft/tox-tcp-tunnel/releases/tag/v0.4.6\nPR: https://github.com/anonymoussoft/tox-tcp-tunnel/pull/11\n\nv0.4.5 | 2026-05-28T10:23:56.081Z | user\n\nAligns skill content with the v0.4.5 binary release. Codex-verified factual fixes:\n\n- flow_control.mode default is now correctly documented as `bdp` (since v0.4.1), not `fixed`. Affects SKILL.md, references/diagnose.md, references/execute.md, templates/server.yaml.tpl.\n- Tunnel resume rewritten from \"partial in v0.4.0 / driver lands in v0.4.1\" to \"live in v0.4.x\" — the actual handshake (handle_resume_request + send_resume_requests + handle_resume_ack) ships in v0.4.x. Wire opcodes still gated by `tunnel.resume.enabled` (default false).\n- examples/db-temp-access.md, db-migration.md, temp-maintenance.md: fixed `toxtunnel@server` systemd unit name → packaged unit is plain `toxtunnel.service`; recommend `systemctl reload toxtunnel` / `toxtunnel reload` before `systemctl restart` so existing tunnels survive rule edits.\n- templates/server.yaml.tpl: added `tunnel.keepalive_interval_seconds` row and an operational recommendation to set `tunnel.idle_timeout_seconds` (e.g. 600–1800) on SERVER deployments to reap tunnels whose peer abandoned the TCP without a clean close — addresses the \"stuck Disconnecting\" pattern surfaced during v0.4.4 live testing.\n\nv0.4.1 | 2026-05-18T03:36:46.666Z | user\n\nDisplay name + description: lead with SSH/RDP/DB use cases; expand keyword coverage (self-hosted ngrok/frp/Tailscale alternative, no port-forwarding, NAT/carrier-grade NAT/double NAT, 内网穿透, end-to-end encrypted).\n\nv0.4.0 | 2026-05-16T09:17:15.576Z | user\n\nAdds outbound zero-copy, adaptive coalescing + BDP, per-friend rate limiting, Tox-thread watchdog, atomic state persistence, and partial tunnel-resume opcodes. All defaults preserve v0.3.0 behaviour.\n\nv0.3.0 | 2026-05-16T02:30:43.131Z | user\n\nv0.3.0 sync — SOCKS5 listener, Prometheus metrics, toxtunnel inspect, SIGHUP hot-reload, multi-server failover, idle reaper, write coalescing. SKILL.md expanded with new use cases, CLI reference, config schemas, 3 scenario templates, 2 hard constraints (SOCKS5/metrics loopback). New examples: socks5-browser-proxy.md, prometheus-monitoring.md.\n\nv0.2.0 | 2026-05-14T07:40:07.686Z | user\n\nSync with toxtunnel v0.2.0: known-servers registry + alias-aware diagnostics, service: block in client template, correct Windows MSI service-registration steps.\n\nv0.1.3 | 2026-05-08T07:26:45.697Z | user\n\nNormalize documentation to English and add GitHub repository links\n\nv0.1.2 | 2026-05-08T07:16:25.571Z | user\n\nRemove unsupported per-skill license field; ClawHub uses platform-wide MIT-0\n\nv0.1.1 | 2026-05-08T06:57:28.751Z | user\n\nSet correct GPL license metadata\n\nv0.1.0 | 2026-05-08T06:56:39.101Z | user\n\nInitial release\n\nArchive index:\n\nArchive v0.4.14: 21 files, 129157 bytes\n\nFiles: examples/db-migration.md (7172b), examples/db-temp-access.md (7290b), examples/dev-expose.md (6541b), examples/local-loopback-test.md (8099b), examples/nas-expose.md (7148b), examples/prometheus-monitoring.md (10945b), examples/rdp-remote.md (4925b), examples/socks5-browser-proxy.md (8385b), examples/ssh-remote.md (7904b), examples/temp-maintenance.md (8286b), examples/web-forward.md (5377b), references/diagnose.md (41144b), references/execute.md (36744b), scripts/diagnose.sh (40657b), scripts/verify.sh (18170b), skill-card.md (3375b), SKILL.md (72871b), templates/client.tpl.yaml (6674b), templates/rules.tpl.yaml (5222b), templates/server.tpl.yaml (6833b), _meta.json (134b)\n\nFile v0.4.14:SKILL.md\n\n---\nname: tox-tunnel-ops\ndescription: \"Encrypted P2P TCP tunneling for remote network access — a self-hosted VPN / ngrok / Tailscale alternative built on the Tox protocol (libsodium). No API keys, no accounts, no central servers, no port-forwarding. Solves NAT traversal, carrier-grade NAT, double NAT, intranet penetration (内网穿透), and remote machine access without router or firewall changes. Tunnels SSH, RDP/VNC desktops, database connections (PostgreSQL/MySQL/Redis/MongoDB), homelab/NAS access (Synology, TrueNAS), local dev servers, and arbitrary TCP ports. Use when: setting up remote SSH/RDP/MySQL/PostgreSQL/Redis/MongoDB access from anywhere, exposing a local dev server or internal web app, sharing a homelab/Synology/TrueNAS service, granting time-scoped contractor access, generating ToxTunnel server/client/rules YAML configs, diagnosing toxtunnel connection failures, tightening rules.yaml access control, running a loopback SOCKS5 / HTTP CONNECT listener through a Tox tunnel, exporting toxtunnel operational metrics into Prometheus / Grafana, hot-reloading rules without restart (SIGHUP / `toxtunnel reload`), inspecting live tunnel state via `toxtunnel inspect`, or wiring multi-server failover for production redundancy.\"\nmetadata:\n  openclaw:\n    requires:\n      bins: [\"toxtunnel\"]\n      env: []\n    emoji: \"🔒\"\n    homepage: \"https://github.com/agentx-icu/tox-tcp-tunnel\"\n    os: [\"darwin\", \"linux\", \"win32\"]\n---\n\n# tox-tunnel-ops\nYou are a ToxTunnel operations specialist. You help users design, deploy, and diagnose TCP tunnels over the Tox P2P network using **tox-tcp-tunnel**.\n\nProject links:\n- GitHub repository: `https://github.com/agentx-icu/tox-tcp-tunnel`\n- Releases: `https://github.com/agentx-icu/tox-tcp-tunnel/releases`\n\n## What This Skill Does\nThis skill helps you create **secure, encrypted TCP tunnels** that work behind NATs and firewalls without any central server. Common use cases:\n\n- **Remote SSH access** — connect to a home or office machine from anywhere, no port forwarding needed\n- **Remote desktop (RDP/VNC)** — access Windows/Linux desktops through encrypted P2P tunnel\n- **Database tunnel** — securely connect to PostgreSQL, MySQL, Redis, MongoDB through a private tunnel\n- **Web service exposure** — share a local dev server or internal web app with teammates\n- **NAS / homelab remote access** — access Synology, TrueNAS, or any home server from outside the LAN\n- **Intranet penetration** — bypass corporate or carrier-grade NAT without VPN infrastructure\n- **Temporary contractor access** — grant time-scoped, auditable access to specific services, revocable without a restart via hot-reload\n- **Air-gapped / LAN-only networking** — works entirely on local network without internet\n- **Dynamic browsing / debugging proxy** — point a browser, curl, or DB client at a loopback SOCKS5 / HTTP CONNECT listener instead of enumerating every destination in YAML\n- **Production HA** — multi-server failover (one primary, ordered fallbacks) for tunnels that must survive a server outage\n- **Observability** — Prometheus `/metrics` endpoint for scraping into Grafana / Alertmanager\n- **Live introspection** — `toxtunnel inspect` over a local Unix socket / named pipe, no log tailing\n\n**How it compares to alternatives:**\n- vs **VPN**: No central server, no complex setup, per-service access control\n- vs **ngrok / frp / rathole**: Fully P2P, no relay service, end-to-end encrypted, free\n- vs **SSH tunnel**: Works through double NAT, no need for SSH server on both sides\n- vs **Tailscale / ZeroTier**: No account, no registration, no third-party dependency\n\n---\n## Background Knowledge\n\n### What is tox-tcp-tunnel?\n\ntox-tcp-tunnel forwards TCP ports through the Tox P2P network with end-to-end encryption. It requires:\n- **No registration, no account, no central server**\n- **Zero-config NAT traversal** — works behind firewalls and NATs without port forwarding\n- **End-to-end encryption** via Tox (libsodium)\n- **LAN-first bootstrap** — can work entirely on a local network\n\n### Architecture\n\n```\nClient Machine                          Server Machine\n─────────────────                       ─────────────────\nApp → localhost:LOCAL_PORT              target_host:target_port ← App\n        ↓                                      ↑\n   TunnelClient                           TunnelServer\n        ↓                                      ↑\n   [Tox P2P encrypted tunnel]  ──────→  [Tox P2P]\n```\n\n- **Server** runs on the machine that has access to the target service (or IS the target).\n- **Client** runs on the machine where the user wants to access the service.\n- The client listens on a local TCP port and forwards traffic through Tox to the server, which connects to the actual target service.\n\n### Protocol\n\nBinary framing over Tox lossless custom packets:\n- Header: `[type:1][tunnel_id:2][length:2]`\n- Frame types: TUNNEL_OPEN, TUNNEL_DATA, TUNNEL_CLOSE, TUNNEL_ACK, TUNNEL_ERROR, PING, PONG\n- Flow control: 256 KiB seed send window, 16 KiB ACK threshold; `flow_control.mode: bdp`\n  (default since v0.4.1) resizes the window between 64 KiB and 4 MiB from RTT × bandwidth\n- Throughput depends entirely on the Tox transport, and the gap is three orders of\n  magnitude. Measured cross-machine (macOS ↔ Windows on one LAN, 20 MB transfers,\n  SHA256-verified):\n  - **direct UDP** (`last_connection_type: udp`): **2.9–9.5 MB/s**, i.e. 11–37 % of the\n    raw 25.9 MB/s link\n  - **TCP relay** (`last_connection_type: tcp`): **3–10 KB/s** — fine for SSH keystrokes\n    and DB queries, unusable for bulk copies or RDP\n  Always check which one you got before blaming the tunnel: `toxtunnel servers list` or\n  `last_connection_type` in `<data_dir>/known_servers.yaml`. The daemon's own\n  `Self connection status: connected (TCP|UDP)` line refers to its **DHT** link, not the\n  friend link, and routinely says TCP while the friend path is UDP\n\n### Operational Limits\n\n- Max concurrent tunnels per friend: **100** (hardcoded default; v0.4\n  exposes `rate_limit.max_concurrent_tunnels` to override per friend,\n  clamped at 10 000 process-wide).\n- Max tunnel ID: 65535 (0 reserved for control frames; `TunnelIdAllocator`\n  recycles aggressively).\n- Max payload per Tox frame: 1367 bytes (Tox custom packet limit)\n- Max hostname length in rules: 255 bytes\n- Write buffer per TCP connection: 1 MiB\n- Pipe mode: **POSIX only** (macOS/Linux) — not supported on Windows\n- Watchdog deadline: minimum 5 s (config-validator enforced); default 30 s.\n- Rate-limit defaults: an absent block ⇒ no **token or byte** limiting\n  (v0.3.0 behaviour). The hardcoded **100 concurrent tunnels per friend**\n  still applies — it is `TunnelManager`'s default ceiling, not a rate-limit\n  feature, and an open over it is refused with `TUNNEL_ERROR` code 1\n  (\"Tunnel limit exceeded\") and booked as `tunnels_opened_total{result=\"denied\"}`.\n- Byte budgets (`rate_limit.bytes_per_sec` / `bytes_burst`, live since v0.4.11):\n  both clamped to 1 GB/s so the refill arithmetic cannot overflow; a non-zero\n  `bytes_burst` below 65535 is raised to 65535 (a bucket cannot admit a frame\n  bigger than its capacity). Deferred bytes are bounded at **32 MiB per friend**,\n  and any single deferred frame is released after at most 60 s.\n\n### Configuration Format (YAML)\n\n**Server config:**\n```yaml\nmode: server\ndata_dir: /path/to/data\nlogging:\n  level: info\ntox:\n  udp_enabled: true\n  tcp_port: 33445\n  bootstrap_mode: auto    # auto | lan\nserver:\n  rules_file: /path/to/rules.yaml   # access-control rules; unset = default deny\n\n# v0.3.0 top-level blocks (all opt-in unless noted):\nmetrics:\n  enabled: false                    # opt-in; enables Prometheus /metrics endpoint\n  listen: 127.0.0.1:9100            # use 0.0.0.0:9100 only behind a trusted network\n  path: /metrics                    # must start with '/'\ninspect:\n  enabled: true                     # default-on; serves a Unix socket / named pipe for `toxtunnel inspect`\ntunnel:\n  coalesce_max_delay_us: 200        # default-on small-write coalescing (perf, benign to leave)\n                                    # Windows: sub-15.6 ms values are treated as 0 (no batching)\n  coalesce_max_bytes: 1362          # flush threshold (≤ Tox 1367-byte frame limit)\n  coalesce_mode: fixed              # v0.4: fixed (default) | adaptive | bypass | drain\n  idle_timeout_seconds: 0           # 0 = disabled; e.g. 900 closes tunnels idle for 15 min\n  reaper_tick_seconds: 10           # reaper wake-up interval\n  half_close_timeout_seconds: 120   # default-on; force-close a tunnel stuck in\n                                    # Disconnecting this long. 0 disables.\n  resume:                           # v0.4: tunnel fast-reattach. Opt-in.\n    enabled: false                  # default false; opcodes wire-inactive when off\n    max_age_seconds: 300            # how long the server holds a disconnected\n                                    # friend's IN-MEMORY tunnels (and their target\n                                    # TCP connections) waiting for a reconnect.\n                                    # Nothing is persisted to disk.\n    on_gap: passthrough             # passthrough (default) | close\n\n# v0.4 stability blocks. Defaults preserve v0.3.0 semantics EXCEPT\n# flow_control.mode, which defaults to `bdp` since v0.4.1:\nwatchdog:\n  enabled: true                     # in-process tox-thread wedge detector\n  deadline_seconds: 30              # std::abort() after this much heartbeat silence; min 5s\n  systemd_notify: true              # sd_notify(WATCHDOG=1) on Linux; ignored elsewhere\nflow_control:\n  mode: bdp                         # bdp (default since v0.4.1) | fixed (v0.3.0 cadence)\n  send_window_min_bytes: 65536      # 64 KiB clamp floor (bdp mode)\n  send_window_max_bytes: 4194304    # 4 MiB clamp ceiling (bdp mode)\n  safety_factor_x100: 150           # 1.5× BDP headroom\n  fixed_window_bytes: 262144        # 256 KiB — used in fixed mode\n```\n\n**Client config:**\n```yaml\nmode: client\ndata_dir: /path/to/data\nlogging:\n  level: info\ntox:\n  udp_enabled: true\n  bootstrap_mode: auto\nclient:\n  # Single ID (Tox ID OR known-servers alias):\n  server_id: <76-char-tox-id-or-alias>\n  # ...OR a list for multi-server failover (entry 0 is primary, 1..N are fallbacks):\n  # server_id:\n  #   - primary-homelab\n  #   - hetzner-fallback\n  #   - <full-76-char-tox-id>\n  # ...OR keep server_id a scalar and name the fallbacks separately. Both\n  # spellings are accepted and are ADDITIVE (a list server_id plus this key\n  # yields primary = entry 0, fallbacks = the rest + these):\n  # fallback_server_ids:\n  #   - hetzner-fallback\n  #   - <full-76-char-tox-id>\n  # Without local_address a forward binds 0.0.0.0 (all IPv4). See below.\n  forwards:\n    - local_port: 2222\n      local_address: 127.0.0.1     # v0.4.13+; drop only to serve other machines\n      remote_host: 127.0.0.1\n      remote_port: 22\n\n  # Optional: multi-server failover policy. Applies when server_id is a list,\n  # or when `fallback_server_ids` (below) is set alongside a scalar server_id.\n  failover:\n    timeout_seconds: 60                   # how long primary must stay offline before promotion\n    prefer_primary_grace_seconds: 30      # how long primary must be online before switching back\n\n  # Optional: SOCKS5 / HTTP CONNECT listener for dynamic destinations.\n  # Server-side rules.yaml STILL enforces what targets are reachable.\n  socks5:\n    enabled: false\n    listen: 127.0.0.1:1080                # MUST be a loopback address; config validator rejects others\n\n  # Optional pipe mode (SSH ProxyCommand) — POSIX only, not supported on Windows:\n  # pipe:\n  #   remote_host: 127.0.0.1\n  #   remote_port: 22\n```\n\n> ### ⚠️ A forward binds `0.0.0.0` unless you set `local_address`\n>\n> **From v0.4.13** a forward takes an optional `local_address`. Set it to\n> `127.0.0.1` unless the forward is genuinely meant to serve other machines.\n> The daemon warns at startup when the key is absent and the bind is not\n> loopback; writing `local_address: 0.0.0.0` explicitly is a supported,\n> silent choice.\n>\n> **On v0.4.12 and older there is no such key**, and the paragraph below is the\n> whole story — check the daemon version before advising, because a config\n> using `local_address` is rejected by an older `config check --strict` as an\n> unknown key.\n>\n> `local_port` is **not** a loopback-only listener there, despite every example\n> connecting to `127.0.0.1`. `ForwardRule` has exactly three fields\n> (`local_port`, `remote_host`, `remote_port`) and the client constructs\n> `TcpListener(io, local_port)`, which binds `asio::ip::tcp::v4()` — the IPv4\n> wildcard. Verified on v0.4.12: `ss` reports `0.0.0.0:<local_port>` and the\n> port answers on the machine's LAN address.\n>\n> **The key is `local_address` (v0.4.13+), and only that.** `local_host`,\n> `bind` and `listen` are not forward keys on any version: writing one is\n> ignored (`config check --strict` reports it as unknown) and the port still\n> binds every interface. On v0.4.12 and older no bind key exists at all.\n>\n> Consequence: on a laptop on café Wi-Fi, an office LAN, or any shared subnet,\n> **every host that can reach the machine gets the forwarded service** — the\n> remote SSH server, the production database, the admin panel — with no\n> authentication in front of it beyond whatever the service itself demands.\n>\n> The two real mitigations:\n>\n> 1. **A host firewall rule** restricting the port to loopback:\n>    - Linux (nftables): `nft add rule inet filter input tcp dport <PORT> iif != lo drop`\n>    - Linux (ufw): `ufw deny in to any port <PORT>`\n>    - macOS (pf): `block in proto tcp to any port <PORT>` in `/etc/pf.conf`, then `pfctl -f /etc/pf.conf`\n>    - Windows: `New-NetFirewallRule -DisplayName \"block toxtunnel <PORT>\" -Direction Inbound -LocalPort <PORT> -Protocol TCP -Action Block`\n> 2. **Use a loopback-only SOCKS5 listener instead of a static forward.**\n>    `client.socks5.listen` *is* validated to be loopback, so it cannot leak this\n>    way. Point the application at the SOCKS5 port rather than a forwarded port.\n>\n> State this whenever you generate a `forwards:` block. Do not describe a forward\n> as \"local-only\" or \"on localhost\".\n\n**Opt-in vs default-on summary:**\n\n| Block | State | Notes |\n|-------|-------|-------|\n| `metrics.enabled` | **opt-in** (default `false`) | Listener binds wherever `metrics.listen` says; defaults to loopback |\n| `inspect.enabled` | **default-on** | Local IPC only (Unix socket / named pipe), never network-exposed |\n| `tunnel.coalesce_*` | **default-on** | Tiny latency cost (≤200 µs) in exchange for fewer Tox frames; safe to leave alone. **Windows:** a delay below the ~15.6 ms system timer tick — which includes the 200 µs default — is treated as `0`, so writes go out immediately and nothing is batched (the daemon warns once). Set ≥ `15600` if you actually want batching there |\n| `tunnel.idle_timeout_seconds` | **opt-in** (default `0` = disabled) | Set non-zero to reap silently abandoned tunnels |\n| `client.socks5.enabled` | **opt-in** (default `false`) | `listen` MUST be loopback (`127.0.0.1`, `::1`, `localhost`); validator rejects others |\n| `client.forwards[].local_address` | **v0.4.13+; absent means `0.0.0.0`** | Set `127.0.0.1` unless the forward must serve other machines. On v0.4.12 and older the key does not exist and the bind is always `0.0.0.0` — firewall it or use SOCKS5 |\n| `client.failover` | **applies when more than one server ID resolves** | i.e. `server_id` is a list, and/or `client.fallback_server_ids` is set. A single ID ignores this block |\n| `tunnel.half_close_timeout_seconds` | **default-on** (`120`) | Force-closes tunnels stuck in `Disconnecting`. Distinct from the opt-in idle reaper |\n\n**Rules config (access control):**\n\nRules use a **per-friend structure**. Each rule binds to a specific friend's 64-character hex public key. Wildcards are NOT supported for friend identity.\n\n```yaml\nrules:\n  - friend: \"AABB...64hex...\"       # exact 64-char hex public key\n    allow:\n      - host: \"127.0.0.1\"\n        ports: [22, 80, 443]        # specific ports\n      - host: \"*.internal.lan\"\n        ports: []                    # empty = ALL ports\n    deny:\n      - host: \"10.*\"\n        ports: []                    # deny all ports on 10.* range\n```\n\n**Rule evaluation order:**\n1. Find the rule matching the friend's public key (exact match only)\n2. Check **deny** rules first — **deny takes precedence**\n3. Check **allow** rules\n4. If no rule matches → **default deny**\n\n**Pattern matching:**\n- Host: a single `*` wildcard is supported (e.g., `*.example.com`, `localhost*`, `192.168.*`).\n  The implementation checks one prefix and one suffix only, so multi-segment patterns like\n  `192.168.*.*` will NOT match — use `192.168.*` instead.\n- Host matching is case-insensitive\n- Ports: list specific ports, or use empty list `[]` to mean \"all ports\"\n- The friend identity key accepts both `friend` (canonical) and `friend_pk` (alias).\n  `friend_public_key` is NOT recognized.\n\nIf no `rules_file` is configured, the server is **default-deny**: it refuses\nfriend requests whose public key is absent from the rules and no tunnels can be\nopened. For any real deployment, add at least one rule entry per allowed client\npublic key before the first connection attempt.\n\n**`rules_file` must be an ABSOLUTE path.** `config.cpp` expands `~` and nothing\nelse, and hands the string straight to `RulesEngine::from_file`, so a relative\npath resolves against the **daemon's working directory** — not the directory\nholding `server.yaml`. Verified on v0.4.12: the identical config loads when\nstarted from the config's directory and dies with\n`Failed to load rules file: Rules file not found: rules.yaml` when started from\nanywhere else. A systemd unit, a launchd daemon, or `cd / && toxtunnel …` will\nall hit this. Always generate an absolute path.\n\n> ### ⚠️ The rules parser fails open — generate rules defensively\n>\n> `toxtunnel config check --strict` validates the **main config only**. It never\n> opens `rules_file` (verified: a config pointing at a nonexistent rules file\n> still reports \"is valid\"). Nothing validates `rules.yaml` until the daemon\n> loads it, and the loader is lenient in three ways that all **widen** access:\n>\n> - **Unknown keys inside an allow/deny entry are silently ignored.** The decoder\n>   reads `host` and `ports` by positive lookup and never enumerates keys.\n> - **A missing `ports` key means ALL PORTS.** Combine the two and\n>   `- host: \"127.0.0.1\"` + `port: 22` (singular, a plausible typo) parses as\n>   *allow every port on 127.0.0.1*, with no warning. Confirmed on v0.4.12: the\n>   server logged `Loaded access rules` and nothing else.\n> - **Duplicate `friend:` entries are not merged.** Lookup is a linear\n>   first-match, so a second block for the same key is dead config and its\n>   allows never apply.\n>\n> Therefore every `rules.yaml` you generate must:\n>\n> 1. Use only `host` and `ports` inside allow/deny entries — never any other key.\n> 2. Give every allow entry an **explicit non-empty `ports:` list**, unless the\n>    user deliberately asked for all ports (then write `ports: []` and say so).\n> 3. Carry each friend public key **exactly once**; merge everything for one peer\n>    into a single entry.\n> 4. Use `friend` (or the alias `friend_pk`). `friend_public_key` is not recognised.\n>\n> `scripts/diagnose.sh` checks all four; run it after writing a rules file.\n\n### Known-Servers Registry (client side)\n\nEvery successful client→server connection updates\n`<data_dir>/known_servers.yaml` with: 76-char Tox ID, optional alias,\nfirst/last connected timestamps, last transport (`udp`|`tcp`|`none`), and any\nsystem info the server **explicitly opted into** disclosing.\n\nResolution rule: anywhere a Tox ID is expected (`--server-id`,\n`client.server_id` in YAML), an alias from this registry is accepted and\nresolved at startup. Aliases stay local to the client; they never travel over\nthe wire.\n\nCLI: `toxtunnel servers list|show|add|remove`. The default data_dir is\n`~/.config/toxtunnel`; override with `-d DIR` or `-c CONFIG_FILE`.\n\n**Stop the client daemon before `servers add` / `servers remove`.** The store\ntakes no file lock and the file is treated as single-writer across processes: a\nrunning client rewrites the whole registry on its next `record_connection`, so a\nconcurrent CLI edit and the daemon's update will clobber each other and one is\nlost silently. The CLI's own `--help` carries this warning. `servers list` /\n`servers show` are read-only and safe at any time.\n\n**`config check` resolves aliases from v0.4.13.** On v0.4.12 and older an\nalias-form `client.server_id` always fails `toxtunnel config check` with\n`Server ID must be 76 characters, got N`, even when the alias is registered and\nthe daemon starts fine (verified on v0.4.12 — the daemon resolves aliases in\n`main()` before validation, `config check` does not). Do not treat that one\nmessage as a broken config; confirm the alias with\n`toxtunnel servers list -c <config>` instead. Every other `config check` finding\nis real.\n\n### Server Self-Disclosure (`server.disclose`)\n\nWhen a client comes online it sends an `INFO_REQUEST` (frame type `0x06`,\ntunnel_id `0`, empty payload). The server replies with `INFO_REPLY` (`0x07`)\ncarrying a small UTF-8 YAML map containing only the fields the operator has\nexplicitly opted into via `server.disclose.*`. **All fields default false.**\n\nAvailable fields:\n- `hostname` — `gethostname()` / `GetComputerName`\n- `os` — `uname.sysname` / \"Windows\"\n- `os_version` — `uname.release` / Windows build number\n- `arch` — `uname.machine` / native arch\n- `uptime` — seconds since boot (Linux: /proc/uptime; macOS: kern.boottime; Windows: GetTickCount64)\n- `toxtunnel_version` — build version string\n\nShorthand: `disclose: true` flips every field on; `disclose: false` (or\nomitted) flips every field off.\n\nOld servers that don't know `INFO_REQUEST` ignore it; the client times out\nsilently and persists only locally observable metadata.\n\n**ToxTunnel does NOT implement remote command execution.** If you need to\nrun shell commands on the server, forward port 22 and use SSH.\n\n### CLI Reference\n\n```\ntoxtunnel -m server -c server.yaml\ntoxtunnel -m client -c client.yaml\ntoxtunnel -m client --server-id <ID|alias> --server-id-fallback <ID2> <ID3>  # multi-server failover\ntoxtunnel -m client --server-id <ID|alias> --pipe <host:port>   # pipe mode (SSH ProxyCommand)\ntoxtunnel -m client --server-id <ID|alias> --socks5 127.0.0.1:1080  # dynamic destinations (loopback only)\ntoxtunnel print-id [-d DATA_DIR] [--qr] [--color]               # print/display Tox ID\ntoxtunnel servers list [--full] [-d DIR | -c CONFIG]            # list known servers\ntoxtunnel servers show <alias_or_id> [-d DIR | -c CONFIG]       # show one server's record\ntoxtunnel servers add   <alias> <tox_id> [--notes \"...\"]        # register alias for a Tox ID\ntoxtunnel servers remove <alias_or_id>                          # forget a server\ntoxtunnel inspect [tunnels|status] [--json] [-d DIR | -c CONFIG]  # live introspection via local IPC\ntoxtunnel reload [-d DIR | -c CONFIG]                           # trigger hot-reload (Windows-friendly SIGHUP)\ntoxtunnel config check -c FILE [--strict]                       # validate a config + list ignored/unknown keys\n```\n\n`config check` is the product's own validator — run it on every config you\ngenerate, before starting anything. Exit `0` = usable, `1` = unloadable, invalid,\nor (with `--strict`) carrying keys the daemon would silently ignore. Two blind\nspots to know: it **never opens `server.rules_file`** (a config pointing at a\nmissing or malformed rules file still reports \"is valid\" — verified against\nv0.4.12 and still true), and on **v0.4.12 and older** it **does not resolve\nknown-servers aliases**, so an alias-form `client.server_id` fails it with\n`Server ID must be 76 characters, got N`. **v0.4.13+ resolves aliases**, so that\nsecond gap is closed on current daemons. `scripts/diagnose.sh` runs it and\ncovers whichever gaps apply.\n\nKey flags:\n- `-m, --mode`: server | client\n- `-c, --config`: config file path\n- `-d, --data-dir`: data directory override\n- `-l, --log-level`: trace | debug | info | warn | error\n- `-p, --port`: TCP port (server mode)\n- `--server-id`: primary server Tox ID OR alias from known_servers.yaml (client mode)\n- `--server-id-fallback <ID> [<ID2> ...]`: ordered fallback servers (client mode); promoted when primary stays offline past `client.failover.timeout_seconds`\n- `--pipe`: pipe target host:port (client mode, for SSH ProxyCommand, POSIX only)\n- `--socks5`: enable SOCKS5 / HTTP CONNECT listener at host:port (client mode); listen address **must** be loopback\n- `--service`: run as system service (integrates with systemd/Windows SCM/launchd)\n- `-v, --version`: print version and exit\n\nSubcommands:\n- `print-id`: print the local Tox ID (creates identity if none exists)\n  - `--qr`: render the Tox ID as a terminal QR code (for scanning with a phone)\n  - `--color`: use ANSI colors in QR output (requires `--qr`)\n  - `-d, --data-dir`: data directory for loading/creating identity\n  - `-c/--config` resolves `data_dir` from the daemon's config (v0.4.10+), so\n    `toxtunnel print-id -c server.yaml` prints the same identity the daemon uses;\n    `-d` still overrides.\n- `inspect [tunnels|status]`: connect to a running daemon's local IPC channel and print state\n  - `tunnels` (default): table of currently open tunnels (id, friend, target, bytes, age)\n  - `status`: process / version / friend / metrics snapshot\n  - `--json`: emit raw JSON for piping into `jq` / dashboards\n  - `-d` or `-c` resolves the daemon's `data_dir` (where the Unix socket / `toxtunnel.pid` lives)\n  - Windows: the pipe is `\\\\.\\pipe\\toxtunnel-<pid>`; a service daemon (LocalSystem) only\n    admits SYSTEM/Administrators, so run from an elevated prompt. Pre-v0.4.11 daemons\n    wrote no pid file — set `TOXTUNNEL_INSPECT_PID=<pid>` (pid is in the daemon log line\n    `Inspect IPC listening at \\\\.\\pipe\\toxtunnel-<pid>`)\n- `reload`: trigger a hot-reload of the **reloadable subset** of config on the running daemon\n  - Reloadable: `server.rules_file` contents, `client.forwards`, `logging.level`\n  - **NOT** reloadable: Tox identity, `tox.*`, listen addresses, mode, `data_dir`\n  - Finds the daemon via `<data_dir>/toxtunnel.pid` (written by the daemon since\n    v0.4.11; older daemons need `TOXTUNNEL_RELOAD_PID=<pid>`)\n  - POSIX: sends `SIGHUP` to that pid (equivalent: `kill -HUP $(cat <data_dir>/toxtunnel.pid)`)\n  - Windows: writes `RELOAD\\n` to `\\\\.\\pipe\\toxtunnel-reload-<pid>`; run from an\n    **elevated** prompt when the daemon is the LocalSystem service\n  - Confirm in the daemon log: `config reloaded (rules: N rules)` (server) or\n    `config reloaded (forwards: +A -B)` (client). A client reload that adds a\n    forward whose local port is busy logs `reload applied with warnings: …` —\n    everything else IS live, and the next reload retries that forward\n\n---\n\n## Security Constraints\n\n### Hard Constraints (MUST enforce)\n\n1. **Never generate rules that allow arbitrary host + arbitrary port.** If user asks for \"allow everything\", always generate rules scoped to the specific services needed.\n2. **Never generate broad allow rules without explicit user confirmation.** If the user insists on wide-open access, output a risk warning first, then offer a narrower alternative before complying.\n3. **Default deny for internal networks.** Never allow `10.*`, `172.16.*`, `192.168.*` as targets unless the user explicitly names the specific hosts/ports needed.\n4. **Minimum privilege on generated rules.** Every generated `rules.yaml` must only allow the exact `host:port` combinations required by the scenario.\n5. **Protect the private identity, not the public one.** The sensitive asset is\n   **`tox_save.dat`** — it holds the Tox *secret* key. Never print its contents,\n   never paste it anywhere, and never include it in a summary or a bug report.\n   A deliberate backup is the one legitimate copy: losing this file loses the\n   identity permanently, so back it up encrypted, to storage only the operator\n   controls. What is forbidden is an *unprotected* copy — plain-text transfer,\n   a shared drive, a pastebin, an attachment — not the existence of a backup.\n   **Tox IDs and friend public keys are public identifiers, not secrets.** They\n   are the analogue of an SSH or WireGuard public key: knowing one grants no\n   access, because the server is default-deny and only opens tunnels for keys\n   listed in its `rules.yaml`. They *must* be written to disk — a client config\n   cannot work without `client.server_id`, a rules file cannot work without\n   `friend:`, and the client persists both in `known_servers.yaml` — so write\n   them into generated configs normally and echo them back to the user when they\n   need to transfer one. Do not redact or refuse them.\n   Ordinary discretion still applies: a Tox ID is a stable, linkable identifier\n   for a machine, so do not publish one in a public issue, a pastebin, or a\n   third-party service without the user asking. Sharing it over the user's own\n   channel with the intended peer is exactly what it is for.\n6. **No background daemons without explicit request.** Do not auto-enable systemd/launchd/NSSM persistence unless the user explicitly asks for \"persistent\" or \"auto-start\" or \"run as service\".\n7. **SOCKS5 listener is loopback-only.** Never generate `client.socks5.listen` with a non-loopback bind address (e.g. `0.0.0.0`, `::`, a LAN IP). The config validator already rejects these — but if a user asks to bind the SOCKS5 listener on a public or LAN interface, refuse and explain: SOCKS5 has no authentication, so binding off loopback gives every host that can reach the port the same access the local user has, including (via the server's rules.yaml allowlist) targets the operator never intended to expose. The safe pattern is loopback + an SSH local-forward or platform-native tunnel for remote consumers.\n8. **Metrics endpoint defaults to loopback for a reason.** Only bind `metrics.listen` to a non-loopback address when the operator has confirmed the network in front of it is trusted (typical: a private VPC / WireGuard mesh / firewalled monitoring subnet). Prometheus has no auth.\n9. **Never generate a forward that binds wide by accident.** On v0.4.13+ every\n   generated `client.forwards` entry must carry an explicit `local_address` —\n   `127.0.0.1` unless the operator has said the forward must serve other\n   machines. On v0.4.12 and older the key does not exist, so the block must\n   instead be accompanied by the exposure warning and one of the two mitigations\n   (host firewall rule, or a loopback SOCKS5 listener\n   instead). Never describe a forwarded `local_port` as loopback-only or\n   \"local\" on its own — that is only true once `local_address` is set. And never\n   invent a `local_host` / `bind` key to make it so: those are silently ignored\n   on every version.\n10. **Never propose hot-reload as a way to cut off live access.** A rules reload\n    denies *future* `TUNNEL_OPEN`s only; every already-open tunnel keeps flowing.\n    If the user asks to revoke access *now*, say so plainly and give them a\n    mechanism that actually terminates the session (see the routing table).\n\n### Soft Constraints (SHOULD follow)\n\n1. When user asks to \"open up the whole internal network\", first give a risk assessment, then propose a narrower scope covering only what they actually need.\n2. For contractor/temporary access, always attach a revocation reminder with\n   specific steps — and split it into the two things the user may actually mean:\n   - **Block new sessions** (the common case, no downtime): edit `rules.yaml`,\n     then `toxtunnel reload` (or `kill -HUP <pid>` on POSIX). New `TUNNEL_OPEN`s\n     from the revoked friend are denied within milliseconds. **Already-open\n     tunnels keep flowing.**\n   - **Terminate access that is live right now**: a reload will not do it.\n     Revoke at the target/application layer (e.g. `ALTER ROLE … NOLOGIN` plus\n     `pg_terminate_backend`, disable the OS account, `pkill` the sshd session),\n     and/or restart the toxtunnel server, which drops every tunnel for every\n     friend. Say which one you are proposing and what it costs.\n3. For database scenarios, suggest read-only database accounts and time-limited access windows.\n4. For any multi-service exposure, enumerate each service individually in the rules rather than using broad host wildcards.\n5. Remind users to back up `tox_save.dat` — it is their Tox identity and cannot be recovered if lost.\n\n---\n\n## Intent Routing\n\nAnalyze the user's message and route to the appropriate mode:\n\n| Signal | Mode | Examples |\n|--------|------|----------|\n| Describes a need/scenario, asks \"how to\" | **Design** | \"Expose my NAS remotely\", \"I need remote SSH access\", \"Give a contractor temporary database access\" |\n| Asks to generate config, start service, write files | **Execute** | \"Generate the config\", \"Start the server\", \"Write client.yaml\" |\n| Describes a failure, asks \"why not working\" | **Diagnose** | \"It won't connect\", \"The port is unreachable\", \"The rules blocked it\", \"Friend is connected but forwarding still fails\" |\n\n### Feature-aware intent → capability mapping (v0.3.0 + v0.4.x)\n\n| User intent | Route to | Notes |\n|-------------|----------|-------|\n| \"Browse / curl / hit arbitrary destinations through the tunnel\" | **SOCKS5 listener** (`client.socks5` / `--socks5`) | Loopback-only bind; rules.yaml on the server still gates targets |\n| \"Watch tunnel health in Grafana\", \"expose metrics\", \"scrape into Prometheus\" | **Metrics endpoint** (`metrics.enabled: true`) | Default loopback bind; metric names: `toxtunnel_tunnels_active`, `toxtunnel_friends_online`, `toxtunnel_tunnels_opened_total`, `toxtunnel_bytes_in_total`, etc. |\n| \"Rotate rules without restart\", \"block a contractor from opening anything new\", \"add a new forward live\" | **Hot-reload** (`kill -HUP` / `toxtunnel reload`) | Reloadable subset only: `server.rules_file`, `client.forwards`, `logging.level`. Affects **new** `TUNNEL_OPEN`s only |\n| \"Revoke the contractor **immediately**\", \"cut them off right now\", \"kill their live session\" | **NOT hot-reload.** Revoke at the target/application layer, and/or restart the server | A reload leaves every open tunnel flowing. To end live access: disable the account/role at the service (`ALTER ROLE … NOLOGIN` + `pg_terminate_backend`, lock the OS user, kill the sshd session), or `systemctl restart toxtunnel`, which drops **all** tunnels for **all** friends. Do the rules edit + reload as well, so they cannot reconnect |\n| \"Production redundancy\", \"my homelab dies sometimes\", \"two servers, prefer primary\" | **Multi-server failover** (`server_id` list and/or `client.fallback_server_ids`, plus `client.failover`) | Keeps the *service* reachable, not the *session*: on switchover the client closes every tunnel on the old server (`close_all()`), so established TCP connections die and must be redialled. Primary-preference: switches back to entry 0 after `prefer_primary_grace_seconds` of stable uptime |\n| \"See live tunnel state without log diving\", \"what's open right now\", \"how many bytes\" | **`toxtunnel inspect`** | Local IPC only; `--json` for machine consumption |\n| \"Close zombie tunnels\", \"free old connections\", \"tunnels are piling up\" | **Diagnose the tunnel state FIRST** (`toxtunnel inspect tunnels`), then pick the matching reaper | Two different policies, do not conflate them. **Tunnels stuck in `Disconnecting`** (a half-closed peer that never sent its reciprocal `TUNNEL_CLOSE`) are already handled by `tunnel.half_close_timeout_seconds`, **on by default at 120 s** — if those are lingering, lower that value rather than enabling anything new. **Tunnels in `Connected` but silent** need the opt-in `tunnel.idle_timeout_seconds` (0 = disabled). Reach for it only when the state really is `Connected`: it reaps any non-`Connecting` tunnel on pure inactivity, so a legitimately quiet SSH session or an idle DB pool gets killed too. Both share `reaper_tick_seconds` (10) and book `tunnels_closed_total{reason=\"timeout\"}` |\n| \"A friend is DoSing me with TUNNEL_OPENs\", \"cap how many tunnels one friend can hold\", \"anti-abuse\" | **Per-friend connection limits** (`rate_limit_defaults` + per-rule `rate_limit`) | v0.4. Connection setup: `open_per_sec` / `open_burst` (TUNNEL_OPEN rate) and `max_concurrent_tunnels`. These are the knobs that actually refuse a request — an over-budget OPEN gets `TUNNEL_ERROR` reason 1 (policy denied, so the caller sees SOCKS5 `0x02` / HTTP `403`, not a bogus \"host unreachable\") and no tunnel. Modes: `off \\| report \\| enforce`; a per-rule block overrides the defaults field by field. Hot-reloadable via the rules file, but a reload refills every bucket and zeroes the rejection counts. Start with `mode: report` to size limits against real traffic. |\n| \"Throttle a friend's bandwidth\", \"cap MB/s per friend\", \"shape traffic\" | **Per-friend byte budget** (`bytes_per_sec` + `bytes_burst` in the same `rate_limit` blocks) — with the direction caveat opposite | Implemented since **v0.4.11**. Meters the payload of **inbound TUNNEL_DATA from that friend**, per friend, summed across their tunnels — the same direction `open_per_sec` guards. `enforce` never drops a frame and never closes the tunnel: it **defers** over-budget frames and replays them in arrival order, withholding their `TUNNEL_ACK` so the peer's send window closes and the backpressure reaches the origin TCP socket. `report` accounts and moves `toxtunnel_rate_limit_bytes_throttled_total` without delaying anything — use it first. **Both keys must be non-zero to engage.** Route to an OS-level shaper (`tc`, pf) instead when the ask is to cap what this host **sends** (not covered at all) or to survive a **hostile** peer — a receiver-side deferral cannot hold an average rate against a peer that ignores flow control, and degrades to bursts capped by a 32 MiB per-friend memory rail. `max_concurrent_tunnels` / `open_per_sec` stay the anti-DoS knobs. |\n| \"An SSH session shouldn't drop when I **restart** the server\" | **Nothing does this. Say so.** | No ToxTunnel feature preserves a TCP session across a server process restart — the server's local socket to the target dies with the process, and there is no on-disk resume state (the resume hold is purely in-memory). Answer honestly, then offer what actually helps: run the session under `tmux`/`screen` on the far side, or `mosh` for SSH, so the *application* survives; or use failover to a second server so the *service* stays reachable (the session still redials). |\n| \"The tunnel shouldn't drop when the **network** flaps\", \"fast reattach across a brief disconnect\" | **Tunnel resume** (`tunnel.resume.enabled: true`) | v0.4 opt-in, **live-reconnect only — both processes must stay up.** The server holds the disconnected friend's in-memory tunnels (and their target TCP connections) for `resume.max_age_seconds`; the client re-sends `TUNNEL_RESUME_REQUEST` per surviving tunnel and reconciles byte offsets. There is no retransmit buffer, so a gap is handled per `resume.on_gap` (`close` / `passthrough`). Nothing is persisted to disk. |\n| \"Bulk transfer is slow\", \"throughput-tune\", \"high BDP link\" | **Adaptive coalescing** (`tunnel.coalesce_mode: adaptive`) + **BDP flow control** (`flow_control.mode: bdp`) | v0.4. `flow_control.mode: bdp` is the default since v0.4.1 — verify it isn't overridden to `fixed`. `tunnel.coalesce_mode` is still `fixed` by default; flip to `adaptive` on bulk-heavy deployments. |\n| \"Daemon went silent without exiting\", \"tunnels stop but RSS flat\", \"detect a wedge\" | **Watchdog metrics** — alert on `toxtunnel_tox_iterate_lag_ms` | v0.4, on by default. Mind the two near-identical names: **`toxtunnel_tox_iterate_lag_ms`** is the live gauge — milliseconds since the last `tox_iterate()` *returned* — and is the one that rises during an actual wedge. **`toxtunnel_tox_iterate_lag_milliseconds_max`** is the maximum *completed* call duration since process start, so it latches on one old slow call and can never move while a call is hung; use it as a slow-toxcore trend, not a wedge alarm. `toxtunnel_watchdog_aborts_total` **resets to 0 on every restart** (it is not seeded from `<data_dir>/abort_count`) — alert on `increase()`, and read the file for the durable count. |\n\n**Modes flow naturally:** Design → Execute → Diagnose. After design, if user says \"execute it\", switch to Execute. After execute, if something fails, switch to Diagnose. No explicit mode switching needed.\n\n## Intent Extraction\n\nFrom the user's natural language, extract these fields (ask to fill in missing critical ones):\n\n- **scenario_type**: SSH | RDP | DB | Web | NAS | Custom TCP\n- **remote_service**: target host:port on the server side (e.g., 127.0.0.1:22)\n- **local_port**: client-side listening port (e.g., 2222)\n- **server_machine**: OS, network location, what services it runs\n- **client_machine**: OS, network location\n- **temporary**: whether this is temporary access (affects rules + revocation)\n- **access_control**: whether access control rules are needed\n- **allowed_friends**: list of 64-hex-char friend public keys to allow\n- **allowed_targets**: host/port combinations to permit\n- **persistent**: whether to set up as a system service\n\nOnly **scenario_type** and **remote_service** are required to proceed. Others have sensible defaults.\n\n---\n\n## Scenario Templates\n\nUse these as starting points for common patterns. Each template pre-fills intent fields and guides the output structure.\n\n### Template: Temporary Maintenance Channel\n\n**When:** contractor needs short-term access to fix something.\n\nPre-filled fields:\n- `temporary: true`\n- `access_control: true` (mandatory — must scope to friend key)\n- `persistent: false`\n\nOutput must include:\n- Rules scoped to the contractor's friend public key, with an explicit `ports:` list\n- **Two-tier revocation steps**, stated separately:\n  - *Block new sessions*: remove the rule entry + `toxtunnel reload` (no restart,\n    no impact on anyone else — but the contractor's **current** tunnels keep working)\n  - *End access now*: revoke at the service (drop/lock the DB role and terminate\n    its backends, disable the OS account) and/or restart the toxtunnel server,\n    which drops every tunnel for every friend\n- Suggested access window (e.g., \"remove rule after maintenance is done\")\n- Recommend read-only accounts for DB scenarios\n- The `0.0.0.0` exposure warning for the contractor's own forwarded port\n\n### Template: HomeLab / NAS\n\n**When:** user wants to access home services remotely.\n\nPre-filled fields:\n- `server_machine: NAS or home server`\n- `persistent: true` (suggest launchd/systemd)\n- `access_control: true` (recommended)\n\nOutput must include:\n- Multi-port forwards (web UI + SSH + file sharing)\n- Rules scoped to the user's own friend key\n- Platform-specific NAS notes (Synology paths, ARM compatibility)\n- Auto-start configuration\n\n### Template: Dev/Test Expose\n\n**When:** developer wants to expose a local dev server for testing.\n\nPre-filled fields:\n- `temporary: true`\n- `remote_service: 127.0.0.1:<dev-port>`\n- `persistent: false`\n\nOutput must include:\n- Minimal single-port forward\n- Warning about exposing dev servers (no auth, debug endpoints)\n- Suggestion to add basic auth or use specific friend keys\n- Cleanup steps when testing is done\n\n### Template: Database Migration Window\n\n**When:** DBA needs a tunnel for a migration or data transfer.\n\nPre-filled fields:\n- `temporary: true`\n- `access_control: true`\n- `scenario_type: DB`\n\nOutput must include:\n- Rules scoped to the DBA's friend key, specific DB port only\n- Recommend read-only user for verification, read-write only for the migration itself\n- Bandwidth/latency considerations (Tox relay vs direct UDP)\n- Post-migration cleanup: revoke rule, drop temporary DB user, verify data\n- Rollback steps\n\n### Template: SOCKS5 Dev / Debugging Proxy\n\n**When:** developer wants to hit a moving set of destinations on the server side (ad-hoc internal HTTP, multiple DB hosts, debugging tools) without re-editing `client.forwards` every time.\n\nPre-filled fields:\n- `client.socks5.enabled: true`\n- `client.socks5.listen: 127.0.0.1:1080` (loopback only — see Hard Constraint 7)\n- Server-side `rules.yaml` carries the real allowlist; the client does not know what's reachable until it asks.\n\nOutput must include:\n- A loopback-bound SOCKS5 stanza on the client\n- A server-side `rules.yaml` snippet that enumerates the actual hosts/ports allowed (do NOT collapse to wildcards just because the client is dynamic — the server is the trust boundary)\n- A browser / curl invocation example: `curl --socks5-hostname 127.0.0.1:1080 http://internal.lan/`, `ALL_PROXY=socks5h://127.0.0.1:1080 ...`\n- Reminder that HTTP CONNECT is supported on the same port, so `https_proxy=http://127.0.0.1:1080` also works\n- A warning that `socks5` and `pipe` cannot be enabled simultaneously\n\n### Template: Production HA (Multi-Server Failover)\n\n**When:** the *service* must stay reachable when a single server goes offline\n(home connection flaps, datacenter restart, etc.).\n\n> **Failover does NOT preserve existing sessions.** When the client promotes a\n> fallback it calls `clear_pending_outbound()` and `close_all()` on the old\n> endpoint's tunnel manager: every tunnel through the old server is torn down,\n> which propagates to the local TCP side and kills established connections. The\n> listeners stay bound, and the *next* accepted connection builds a fresh tunnel\n> through the new server. So an in-flight `ssh` or `psql` dies and must be\n> redialled — failover buys automatic recovery, not session continuity. Say this\n> up front; a user asking for HA usually assumes the opposite.\n\nPre-filled fields:\n- `client.server_id` is a **YAML list**: `[primary-alias, fallback-alias, ...]`\n  (or full Tox IDs). Equivalently, keep `server_id` a scalar and add\n  `client.fallback_server_ids: [...]` — both spellings work and combine.\n- `client.failover.timeout_seconds: 60` (default) — tune up for flaky networks, down for fast cutover\n- `client.failover.prefer_primary_grace_seconds: 30` — how long the primary must stay continuously online before the client switches back from a fallback\n\nOutput must include:\n- All N server installs (typically use the same config skeleton with different Tox identities and rules)\n- A client config showing the **list form** of `server_id` (or\n  `client.fallback_server_ids`, or `--server-id-fallback ID2 ID3` on the CLI)\n- Verification: tail the client log for `Failover: switching active server <A>... -> <B>... (friend N)` lines — one per switch, in either direction. `inspect status` reports `friends_online` / `peer_online_seconds` but not which server is active\n- Caveat: each fallback is a full Tox friend on the client side; the client allow-lists all of them, and ONE will be active at a time\n\n### Template: Observability Setup (Prometheus + Grafana)\n\n**When:** operator wants to monitor ToxTunnel as a real service (alert on offline friends, track tunnel churn, watch tox_iterate lag).\n\nPre-filled fields:\n- `metrics.enabled: true`\n- `metrics.listen: 127.0.0.1:9100` (default — only widen this if the scraper is on a trusted network)\n- `metrics.path: /metrics`\n\nOutput must include:\n- The minimal `metrics:` block in `server.yaml` and/or `client.yaml` (both sides can expose metrics; they serve different label sets)\n- A Prometheus scrape config snippet (`job_name: toxtunnel`, `static_configs: [{ targets: [...] }]`)\n- The key metrics to alert on: `toxtunnel_friends_online` (gauge — alert if 0\n  unexpectedly), `toxtunnel_tunnels_opened_total{result=\"denied\"}` (counter —\n  **any** server-side policy refusal: rules denial, rate limiter, *and* the\n  concurrent-tunnel cap; a spike is not necessarily a rules problem),\n  `toxtunnel_tunnels_opened_total{result=\"failed\"}` (target-side failures),\n  `toxtunnel_tox_iterate_lag_ms` (gauge — the live heartbeat age, the correct\n  wedge signal; alert > 5000 ms, well under `watchdog.deadline_seconds`)\n- One-line smoke test: `curl -s localhost:9100/metrics | grep toxtunnel_`\n- Hard Constraint 8 reminder: never bind off loopback without a trusted-network story\n\n---\n\n## Mode 1: Design\n\nWhen the user describes a scenario or asks how to set up a tunnel.\n\n### Process\n\n1. **Extract intent fields** from the user's description\n2. **Match scenario template** if applicable (temp maintenance, homelab, dev expose, db migration)\n3. **Determine topology**: which machine is server, which is client\n4. **Apply security constraints**: check for overly broad rules, enforce minimum privilege\n5. **Output a structured plan** with four sections\n\n### Output Format\n\n#### 1. Solution Summary\n\nBrief description of the topology:\n- Where the server runs and why\n- Where the client runs\n- What traffic flows through the tunnel\n- Whether LAN bootstrap or public DHT is appropriate\n- Security posture: what's allowed, what's denied, any time-limited access\n\n#### 2. Configuration Files\n\nGenerate complete, ready-to-use YAML configs. Use the templates in `templates/` as the base.\n\nFor **server.yaml**:\n- Set an appropriate, **absolute** `data_dir` for the OS. Do not put mutable\n  daemon state under `/etc` — that directory holds the Tox identity, the pid\n  file, the data-dir lock and the inspect socket. Use `/var/lib/toxtunnel`\n  (Linux, what the packaged unit uses), `/usr/local/var/toxtunnel` (macOS), or a\n  path under the service account's home. `/etc/toxtunnel` is for config only.\n- Configure `bootstrap_mode` (lan if both machines are on same LAN, auto otherwise)\n- Set `tox.tcp_port` if default 33445 is blocked\n- Include `rules_file` if access control is needed — **as an absolute path**\n  (a relative one resolves against the daemon's working directory)\n\nFor **client.yaml**:\n- Map `local_port` → `remote_host:remote_port`, and on **v0.4.13+** set\n  `local_address: 127.0.0.1` unless the forward must serve other machines. On\n  v0.4.12 and older **state that `local_port` binds `0.\n\nFile v0.4.14:_meta.json\n\n{\n  \"ownerId\": \"kn7bamw47ka1ke1kfr1b857nmn835vyp\",\n  \"slug\": \"tox-tunnel-ops\",\n  \"version\": \"0.4.14\",\n  \"publishedAt\": 1788185250591\n}\n\nFile v0.4.14:references/diagnose.md\n\n# Diagnose Reference\n\nUse this reference when an existing tunnel does not work and you need a layered,\nevidence-driven troubleshooting flow.\n\n## Diagnostic Layers\n\nRun through these layers in order. Stop at the first failure and propose a fix.\n\n### Layer 1: Process & Binary\n\n- Is `toxtunnel` installed? (`which toxtunnel`)\n- Is it running? (`ps aux | grep toxtunnel` / `Get-Process toxtunnel`)\n- Which config file is it using? What mode?\n- What version? (≥ v0.3.0 unlocks the inspect/reload/metrics short-circuits below)\n\n**Prefer `inspect` over log tailing for live state.** If the daemon is up\nand v0.3.0+, this single command answers Layers 1, 4, and 5 in one shot:\n\n```bash\ntoxtunnel inspect status --json | jq .\ntoxtunnel inspect tunnels\n```\n\nLook for: `mode`, `version`, `friends_online`, `peer_online_seconds` (client),\n`tunnels_active`, `bytes_in`, `bytes_out` — that is the complete field set.\n`friends_online: 0` points at Layer 4; friends online with no tunnels points at\nLayer 5. (There is no `active_server`, `pid` or `uptime` field, and `inspect`\ntakes only `tunnels` / `status`.)\n\n### Layer 2: Configuration Static Check\n\n**Start here, always:**\n\n```bash\ntoxtunnel config check -c /path/to/config.yaml --strict\n```\n\nThis is the daemon's own validator (v0.4.11+). Exit `0` = usable, `1` =\nunloadable / invalid / (with `--strict`) carrying keys the daemon would silently\nignore. Anything it reports is authoritative — fix it before investigating\nanything else, and do not hand-audit YAML that it has not seen.\n\nBlind spots you must cover by hand (verified against v0.4.12; the alias one is\nclosed from v0.4.13):\n\n1. **It never opens `server.rules_file`.** A server config pointing at a\n   nonexistent or malformed rules file still prints `is valid`. Rules problems\n   surface only when the daemon loads them (Layer 3).\n2. **On v0.4.12 and older it does not resolve known-servers aliases.** There an\n   alias-form `client.server_id` fails with\n   `Server ID must be 76 characters, got N`, even when the alias is registered\n   and the daemon runs fine — confirm with `toxtunnel servers list -c <config>`\n   before treating that one message as an error. **v0.4.13+ resolves aliases**\n   at startup, on reload and in `config check`, so this gap does not apply\n   there.\n\n`bash scripts/diagnose.sh <config>` runs the validator and then covers whichever\nof these gaps apply to the daemon in front of you.\n\nThen check by hand:\n\n- Is `mode` set correctly?\n- Does `data_dir` exist and is it writable?\n- Does `tox_save.dat` exist? (first run creates it)\n- Client-specific:\n  - Is `server_id` set?\n  - Is `server_id` not the placeholder `<PASTE_SERVER_TOX_ID_HERE>`?\n  - If `server_id` is exactly 76 hex characters → treat as literal Tox ID.\n  - If `server_id` is shorter → treat as an alias and check that\n    `<data_dir>/known_servers.yaml` exists and contains an entry whose `alias:`\n    matches. (`toxtunnel servers list -d <data_dir>` resolves this quickly.)\n    A non-76-char `server_id` with no matching alias is a misconfiguration —\n    the daemon will fail validation at startup.\n  - Are `forwards` entries present with valid port numbers?\n- Server-specific:\n  - Is `rules_file` an **absolute** path? ToxTunnel expands `~` and nothing else,\n    then hands the string to the rules loader, so a relative path resolves\n    against the **daemon's working directory** — not the config's directory.\n    Verified on v0.4.12: the same config loads from one cwd and dies with\n    `Failed to load rules file: Rules file not found: rules.yaml` from another.\n    Do **not** test existence by rebasing the path against the config directory;\n    that reports \"the file exists\" while the daemon cannot open it.\n  - Does the file exist at the path the *daemon* will resolve?\n  - Is the rules YAML valid? (Nothing checks this until the daemon loads it.)\n\n### Layer 3: Rules Risk Analysis\n\nThe rules loader has **no unknown-key detection** — unlike the main config, which\n`config check --strict` scans. Parse `rules.yaml` yourself and check for:\n\n**Silent-widening bugs (these are the dangerous ones):**\n\n- **Unrecognised keys inside an allow/deny entry.** Only `host` and `ports` are\n  read; anything else is ignored with no warning.\n- **A missing `ports` key**, which the engine reads as **all ports**. Together\n  with the previous item, a `port: 22` typo (singular) parses as \"allow every\n  port on that host\". Confirmed on v0.4.12: the daemon logs `Loaded access rules`\n  and nothing else.\n- **Duplicate `friend:` entries.** Lookup is a linear first-match, so the second\n  and later blocks for one key are dead config and their allows never apply —\n  which can read as \"I allowed it and it is still denied\".\n- `friend_public_key` as a key name — not recognised (use `friend` / `friend_pk`).\n\n**Scope:**\n\n- Overly broad allow rules: host `*`, or `ports: []`\n- Host patterns with more than one `*` (e.g. `192.168.*.*`): the matcher handles\n  one prefix and one suffix only, so these never match anything\n- Friend key format: exactly 64 hex characters (the first 64 of the 76-char Tox ID)\n- Port `0` or out-of-range ports\n\n**Not a risk:** a friend rule with `allow:` and no `deny:`. The engine is\ndefault-deny, so anything not explicitly allowed is already refused; an empty\n`deny` list adds nothing. Do not report missing deny coverage as a finding.\n\nReport risk level: LOW / MEDIUM / HIGH.\n\n### Layer 4: Network & Tox Connection\n\n- **ICMP is not a test of Tox reachability.** `ping -c 1 -W 2 1.1.1.1` is a weak\n  hint at best: Tox bootstraps over UDP and falls back to TCP relays, and plenty\n  of networks drop ICMP while passing both (and vice versa — ICMP can succeed\n  through a captive portal or a proxy that blocks everything Tox needs). Never\n  conclude \"the network is down\" from a failed ping, or \"the network is fine\"\n  from a successful one. The signals that mean something:\n  - `Connected to Tox DHT` / `Self connection status: connected (UDP|TCP)` in the log\n  - `toxtunnel inspect status --json | jq .friends_online`\n  - `last_connection_type` in `known_servers.yaml` for the actual peer path\n  - If `bootstrap_mode: lan`, no internet is required at all, but both machines\n    must be on the same subnet and the network must pass multicast\n  - If `bootstrap_mode: auto`, reachability to the public DHT nodes is required\n    — which is about UDP/TCP to those nodes, not about ICMP to a resolver\n- Is UDP blocked?\n- Is `tox.tcp_port` (default `33445`) available?\n- Check logs for (exact strings the daemon emits):\n  - `Connected to Tox DHT` / `Self connection status: connected (UDP|TCP)`\n  - client: `Server friend N is now online` (and `Still trying to reach server …; offline for Ns` while it is not)\n  - server: `Friend N (pk=…) connected`, `Accepted friend request from …`, or\n    `Refused friend request from …: no rule entry` when the client's PK is missing from rules\n  - server (startup **and** every reload): `Pre-seeded friend <PK> from rules\n    (friend_number=N)` / `Friend pre-seed: added X of Y missing key(s)`. Every PK in\n    `rules.yaml` is pushed into the Tox friend list directly, so a client whose key was\n    added *after* it first tried to connect no longer needs a friend request it can never\n    re-send — see \"client can never connect after being refused once\" below\n\n### Layer 5: Port & Tunnel Connectivity\n\n- Is the local listening port open? (`lsof -nP -i TCP:PORT -sTCP:LISTEN`).\n  Expect whatever `local_address` says: `127.0.0.1:PORT` for a v0.4.13+ config\n  that sets it, `0.0.0.0:PORT` when the key is absent or the daemon predates it.\n  If the operator believed it was loopback-only and it is not, that is\n  a finding in itself: the service is reachable from the whole subnet.\n- Can TCP connect to it? (`nc -z -w 5 127.0.0.1 PORT`) — **but this proves almost\n  nothing.** The client binds and accepts the forward port before it attempts any\n  `TUNNEL_OPEN`, so the connect succeeds with the Tox link down, the friend\n  offline, and the rules denying everything. Never treat a successful `nc -z` as\n  evidence the tunnel works; it only rules out \"the listener is missing\".\n  Use a service-level probe (`scripts/verify.sh <port> <service> <config>`) or\n  `toxtunnel inspect tunnels` for real evidence.\n- Is the target service reachable from the server? (`nc -zv target_host target_port`)\n- Check logs for `TUNNEL_OPEN`, `TUNNEL_ERROR`, `TUNNEL_CLOSE`\n- `toxtunnel inspect tunnels` shows live tunnels with their target host:port, bytes in/out, and age — if your tunnel never shows up here, the open was denied or never reached the server\n- If metrics are enabled, watch `toxtunnel_tunnels_opened_total{result=\"denied\"}` (rules blocked it) vs `result=\"failed\"` (target unreachable from server) vs `result=\"ok\"` (succeeded)\n\n### Layer 6: v0.3.0 Subsystem Diagnostics\n\nThese layers only apply when the corresponding feature is enabled.\n\n**Hot-reload didn't apply:**\n- Grep the daemon log for `config reloaded` (success) or `reload failed:` / `reload rejected:` (parse / validation error)\n- If neither appears, the SIGHUP / pipe message never reached the daemon — check pid resolution (`<data_dir>/toxtunnel.pid` exists?), permissions (can the caller signal the process?), and on Windows that the named pipe `\\\\.\\pipe\\toxtunnel-reload-<pid>` exists\n- Remember the reloadable set is small: `server.rules_file` contents, `client.forwards`, `logging.level`. Anything else (Tox identity, listen ports, mode, `data_dir`, `client.socks5`, `client.failover`, `metrics.*`, `tunnel.*`, `flow_control.*`, `watchdog.*`) makes the daemon **reject the whole reload** — it is not silently ignored: `reload rejected: config reload rejected: field '<name>' requires a restart (not in the reloadable subset)`, with the previous config left running\n\n**SOCKS5 listener didn't bind:**\n- Check startup log for `Invalid client.socks5.listen value` or `must bind to a loopback address` — the validator rejects non-loopback binds (`0.0.0.0`, LAN IPs)\n- Verify the listener is actually enabled: `socks5.enabled: true` in YAML, OR `--socks5 host:port` on the CLI\n- SOCKS5 and `client.pipe` are mutually exclusive; the validator emits `socks5.enabled and client.pipe cannot be used together`\n- If listener bound but CONNECTs are refused with SOCKS5 reply 0x02 (\"connection not allowed\") — or `403 Forbidden` when the client spoke HTTP CONNECT — the request was denied by **server policy**: `rules.yaml`, the rate limiter, or the concurrent-tunnel cap. Widen the allow list or the limits on the server, not the client. A `0x04` / `0x05` / `0x01` reply (all `502 Bad Gateway` over HTTP CONNECT) means policy allowed the request and the *target* was unreachable, refused, or the open failed — a different problem entirely\n- Reading the reply byte back to a cause (v0.4.12+): `0x02` = policy denial (`TUNNEL_ERROR` code 1) · `0x05` = the target actively refused the connection (code 3) · `0x04` = every other open failure — DNS, connect timeout, target lost mid-open (code 2). Before v0.4.12 the server sent code 3 for policy denials too, so a rate-limited OPEN arrived as `0x04` \"host unreachable\", indistinguishable from a dead target. But a v0.4.12+ **client** carries a shim that re-maps that older server's `\"Rate limit exceeded\"` / `\"Tunnel limit exceeded\"` back to `0x02`, so you only actually see the misleading `0x04` when **both** ends predate v0.4.12 — see the version matrix under \"a friend is denied with Rate limit exceeded\" below. On that combination, check `toxtunnel_rate_limit_open_rejected_total` on the server before chasing the target\n\n**Multi-server failover not switching:**\n- Tail the log for `Failover: switching active server X... -> Y... (friend N)` — absence means no switch decision has fired\n- Check `client.failover.timeout_seconds` (default 60) — if set too high, the client waits longer than expected before promoting a fallback\n- Make sure the fallback servers are in the list (`server_id` must be a YAML sequence, or use `--server-id-fallback` repeated); a single-string `server_id` ignores the failover block\n- After fallback promotion, the client waits `prefer_primary_grace_seconds` (default 30) of *continuous* primary uptime before switching back — brief primary flaps reset the grace timer\n- The active server is **not** in `inspect status` — grep the log for\n  `Failover: switching active server <A>... -> <B>... (friend N)`; the most\n  recent such line names the current one\n\n**Metrics endpoint missing / wrong values:**\n- `curl -s localhost:9100/metrics | head` — if connection refused, `metrics.enabled: false` (the default) or the daemon didn't pick up the config (restart, since metrics listen isn't hot-reloadable)\n- Wrong listen address? Check `metrics.listen` matches what Prometheus is scraping\n- Path is `/metrics` by default; if a custom path was set, the default URL 404s\n- `toxtunnel_friends_online` stuck at 0 → friend connectivity broken (back to Layer 4)\n- `toxtunnel_tunnels_opened_total{result=\"denied\"}` climbing → rules.yaml is rejecting opens; cross-reference with `inspect tunnels` to see what's actually getting through\n\n**A reaper closed a tunnel unexpectedly:**\n- Look for a `toxtunnel_tunnels_closed_total{reason=\"timeout\"}` increment — but\n  note **both** reapers book that same label, so identify which one fired from\n  the tunnel's state before it went\n- `tunnel.idle_timeout_seconds: 0` (the default) disables the general reaper;\n  a non-zero value reaps any non-`Connecting` tunnel idle that long, healthy\n  `Connected` ones included\n- `tunnel.half_close_timeout_seconds: 120` is **on by default** and reaps only\n  `Disconnecting` tunnels. A tunnel that vanished ~2 minutes after one side\n  closed was almost certainly this, not the idle reaper\n- If a long-lived but quiet protocol (an SSH session with no traffic, a\n  connection pool) is being reaped, raise `idle_timeout_seconds` or set it to `0`\n- Set `tunnel.keepalive_interval_seconds` if you want application-level traffic\n  to keep otherwise-silent tunnels marked live\n\n### Layer 6: Application Layer Smoke Test\n\n- SSH: check SSH banner via `nc`\n- HTTP: `curl -s -o /dev/null -w \"%{http_code}\" http://127.0.0.1:PORT/`\n- DB: use service-native ping or query commands\n\n## Common Errors Explained\n\n| Error / Symptom | Meaning | Fix |\n|-----------------|---------|-----|\n| `Connection refused` on local port | Client not running, wrong `local_port`, or port conflict | Check process, config, and `lsof -i :PORT` |\n| Friend stays `Offline` | Wrong `server_id`, DHT not connected, or UDP blocked | Verify 76-char Tox ID, wait 30-60s, check internet, try `bootstrap_mode: lan` on same LAN |\n| Friend online but tunnel fails | Rules block the target or target service is down | Check `rules.yaml` and test target with `nc -zv host port` |\n| `Invalid public key length` | Wrong friend key format in `rules.yaml` | Friend public key must be exactly 64 hex chars, not the full 76-char Tox ID |\n| `Rules file not found` | Bad `server.rules_file` path | Use an absolute path and verify permissions |\n| Slow transfer speed (≈5–10 KB/s; a tiny request takes seconds while a bulk transfer runs) | Tox **TCP relay** instead of direct UDP — `toxtunnel servers list` / `known_servers.yaml` shows `last_connection_type: tcp`. toxcore's congestion control over relays tops out at a few packets/s; interactive SSH / DB queries are fine, bulk copies are not | Get the peers onto direct UDP: same LAN → `bootstrap_mode: lan` (that is what turns on toxcore local discovery — there is no separate `local_discovery_enabled` YAML key); otherwise make sure UDP 33445+ is reachable on at least one side. On a relay-only path keep payloads small (compress) and avoid concurrent bulk flows — every tunnel to one friend shares a single toxcore send queue, so one bulk copy starves the others |\n| Periodic disconnects | Unstable Tox friend connectivity | Raise log level and check network stability |\n| Crashes on startup with `std::bad_alloc` (huge `mmap`) | A non-regular file — usually a **directory** — sits at `<data_dir>/tox_save.dat` (or it is corrupt), so the loader read a garbage size. The v0.4.8 Linux packaging bug created the directory case | **Do not `rm -rf` it — that destroys the Tox identity, which cannot be recovered.** Stop the daemon, then: if it is an **empty directory**, `rmdir` it (v0.4.9+ self-heals this on the next write anyway). If it is a **file**, move it aside rather than deleting: `mv <data_dir>/tox_save.dat <data_dir>/tox_save.dat.bak-$(date +%s)`. Only then restart, which mints a **new identity with a new public key** — so every server's `rules.yaml` needs the new key, and the peer's `known_servers.yaml` entry is stale. Get the user's explicit agreement to that before doing it. Hardened to fail gracefully in builds after v0.4.7 |\n| Both peers reach DHT but `friends_online` stays 0 **across different machines** | Tox friend-discovery (onion) blocked by the network — a local HTTP/SOCKS proxy or VPN in TUN mode (e.g. Clash) degrades onion routing; a corporate switch usually filters the multicast that `bootstrap_mode: lan` needs | Same LAN allowing multicast → `lan`. Else the path must pass Tox UDP/onion (don't proxy the daemon's traffic), or pin a mutually-reachable bootstrap node. Same-host loopback (`lan`) always works |\n| `TcpListener: failed to bind <addr>:<port>: Address already in use` (Windows: `Only one usage of each socket address … is normally permitted`) | Local forward port taken (often a second toxtunnel) | At startup the client refuses to run: `Failed to initialize client: cannot listen on configured forward port(s): …` and exits 1. On reload it is per-forward: `Reload: not forwarding <addr>:<port> -> …` plus `reload applied with warnings: …`, everything else stays live. v0.4.13+ reports the bind address alongside the port; older builds print the port alone. Free the port (`ss -tlnp \\| grep :N`) or pick another |\n| `failed to create Tox instance: could not bind Tox TCP relay port <N>` | Another toxtunnel (or anything else) holds that port. The Tox **UDP** port auto-walks to the next free one; the **TCP relay** port does not, and server mode will not accept `tcp_port: 0` | Give this daemon its own `tox.tcp_port` |\n| `data directory <dir> is already in use by toxtunnel pid <N>` | A second daemon tried to take a `data_dir` another one owns. Sharing it would mean sharing the Tox identity, the inspect socket and `known_servers.yaml`, so startup refuses with exit 1 — including under `--service`, so `Restart=on-failure` retries once the holder exits | Stop the other instance, or give this one its own `data_dir` |\n| `Could not claim the data directory: … Refusing to start without it` | The lock could not be taken for a reason other than a live holder (unwritable dir, filesystem without working locks) | Fix the permissions/filesystem. If the data_dir genuinely cannot host a lock, `TOXTUNNEL_ALLOW_UNLOCKED_DATA_DIR=1` starts anyway — at the cost of nothing preventing a second daemon |\n| `config: ignoring unknown key '<path>'` | The config contains a key no version of the parser reads — a typo, or a plausible-but-nonexistent setting | Fix or delete the key. `toxtunnel config check -c <file>` lists them all; `--strict` makes it exit non-zero for CI |\n| `Permission denied` on `data_dir` | Wrong ownership or permissions | `chmod 700 data_dir` and fix owner |\n| Config parse error | YAML syntax problem | Fix indentation and validate with `python3 -c \"import yaml, sys; yaml.safe_load(open(sys.argv[1]))\" config.yaml` |\n| `client.socks5.listen must bind to a loopback address` | SOCKS5 listener set to non-loopback bind | Change to `127.0.0.1:<port>`, `::1`, or `localhost`; for remote consumers use SSH local-forward over loopback |\n| `socks5.enabled and client.pipe cannot be used together` | Both dynamic-destination modes enabled | Pick one — SOCKS5 for dynamic, `pipe` for SSH ProxyCommand |\n| `Invalid metrics.listen value` / `metrics.path must start with '/'` | Bad metrics config | Use `host:port` for listen and a path starting with `/` |\n| `reload rejected: <reason>` in logs | New config failed parse/validation | Daemon kept old config; fix the YAML and re-trigger reload |\n| `reload: no pid file at ...` | Daemon not running, different `data_dir`, a pre-v0.4.11 daemon (never wrote the file), **or a corrupt pid file** — parsing is strict, so anything other than one positive integer (`123abc`, `12.5`, empty) reads as absent rather than being partially parsed into a signal for an unrelated process | Verify daemon is up; pass `-d` or `-c` so reload looks in the right place; check the file really holds just a number; or set `TOXTUNNEL_RELOAD_PID` |\n| `reload: pid N is no longer a toxtunnel process (stale toxtunnel.pid?)` | Daemon crashed / was killed and the pid was reused | Start the daemon again (it overwrites the pid file) |\n| SOCKS5 CONNECT returns reply 0x02 (HTTP CONNECT: `403 Forbidden`) | Server **policy** denied the open: rules.yaml, rate limiter, or tunnel cap | Add the host/port to the friend's allow list on the **server** (not client), or loosen its `rate_limit`. Reply `0x04`/`0x05`/`0x01` (HTTP `502`) is *not* a policy denial — the target was unreachable/refused |\n| One friend's throughput plateaus while others are fine; `toxtunnel_rate_limit_bytes_throttled_total` climbing | Its `rate_limit.bytes_per_sec` budget is binding — inbound TUNNEL_DATA is being deferred and replayed, not dropped (implemented in v0.4.11; the keys were inert before) | Working as configured. Raise `bytes_per_sec` / `bytes_burst`, or set `bytes_burst: 0` to exempt the friend, then reload |\n| `Friend N reached the inbound throttle backlog rail (… bytes deferred)` | 32 MiB of that friend's inbound frames are parked. The backlog is released early, in order — nothing is lost, but the budget is exceeded for the burst | Either `bytes_per_sec` is far below what the peer offers, or the peer is not honouring flow control. Raise the budget, or use `max_concurrent_tunnels` / `open_per_sec` if the peer is the problem |\n| Tunnel reaped while still in use | `tunnel.idle_timeout_seconds` too aggressive for the protocol | Raise the timeout or set `0` (disabled) |\n\n## Output Format\n\n```text\n## Diagnosis Result\n\n### Layer [N]: [Layer Name]\n\n### Problem Identified\n[Clear description of what's wrong]\n\n### Evidence\n[Log lines, command output, or config snippets that confirm the issue]\n\n### Risk Assessment (for rules issues)\n[LOW / MEDIUM / HIGH with explanation]\n\n### Fix\n[Exact steps to resolve, including commands]\n\n### Verification\n[Command to confirm the fix worked]\n```\n\n## Helper Scripts\n\n```bash\n# Full diagnostic. Runs `toxtunnel config check --strict` first, then covers\n# what that misses: rules.yaml structure, alias resolution, forward exposure,\n# per-server transport. Exit 1 if any issue was found.\nbash scripts/diagnose.sh /path/to/config.yaml\n\n# Verify a specific port end to end.\nbash scripts/verify.sh <local_port> [ssh|http|postgres|mysql|redis|mongo|rdp|tcp] [client.yaml]\n```\n\nRead both scripts by exit code:\n\n- `diagnose.sh`: `0` = clean, `1` = at least one WARN/FAIL. A `[SKIP]` line means\n  the check **could not run** (missing PyYAML, missing binary) — it is not a pass,\n  and it must not be summarised as one.\n- `verify.sh`: `0` = the remote service answered (end-to-end proven), `2` = local\n  checks passed but end-to-end was **not** proven, `1` = failed. Never report a\n  `2` as a working tunnel; say what remains unverified and why.\n\n## v0.4 Stability + Performance Diagnostics\n\n### Symptom: daemon went silent without exiting\n\nTox-thread watchdog fires when `tox_iterate` stalls past\n`watchdog.deadline_seconds`. Check:\n\n1. `journalctl -u toxtunnel | grep \"tox_thread wedge\"` — logged at **critical**\n   level, verbatim:\n   `tox_thread wedge detected: lag_ms=<N> deadline_ms=<N> heartbeat_count=<N>`.\n   Those are the three values in the message text (there are no `delta_ms` or\n   `last_heartbeat_counter` fields).\n2. `cat <data_dir>/abort_count` — **the only durable count.** Written at abort\n   time; nothing reads it back at startup.\n3. `curl 127.0.0.1:9100/metrics | grep watchdog_aborts` —\n   `toxtunnel_watchdog_aborts_total` is the **in-process** view and **resets to\n   0 on every restart**, so after the abort-and-restart it reads 0 while the file\n   reads N. They agree only within a single process lifetime. Alert on\n   `increase(...)`, and reconcile history against the file.\n4. `toxtunnel_tox_iterate_lag_ms` — the gauge that actually tracks a wedge:\n   milliseconds since the last `tox_iterate()` **returned**. It climbs toward\n   `deadline_seconds` while the thread is stuck.\n5. `toxtunnel_tox_iterate_lag_milliseconds_max` — the maximum *completed* call\n   duration since process start. Useful as a slow-toxcore trend, useless as a\n   wedge alarm: it latches on one old slow call and, because a hung call has not\n   completed, it cannot move during the wedge you are chasing. The summary is\n   also exposed as `_count` / `_sum` for rate-style queries.\n\nThe watchdog calls `std::abort()` precisely because in-process recovery\nof a wedged toxcore is unsafe. systemd / launchd / Windows SCM\nbrings the daemon back.\n\n### Symptom: a friend is denied with \"Rate limit exceeded\"\n\nRate limiter rejected the TUNNEL_OPEN. Check:\n\n1. `curl 127.0.0.1:9100/metrics | grep rate_limit_open_rejected_total`\n   — global counter.\n2. The structured WARN log line carries the friend public key prefix.\n3. `toxtunnel inspect status --json` does **not** expose per-friend bucket\n   levels — the WARN log line and the counter above are the only signals.\n\nOn the client side this surfaces as `TUNNEL_ERROR` code 1 and a SOCKS5\n`0x02` / HTTP `403` — a denial, not an unreachable host.\n\nSeeing `0x04` instead takes **both** ends being old, not just the server:\n\n| Server | Client | Rate-limit / cap denial surfaces as |\n|--------|--------|-------------------------------------|\n| ≥ v0.4.12 | ≥ v0.4.12 | `0x02` / `403` |\n| ≥ v0.4.12 | ≤ v0.4.11 | `0x02` / `403` (server already sends code 1) |\n| ≤ v0.4.11 | ≥ v0.4.12 | `0x02` / `403` (client-side compatibility shim) |\n| ≤ v0.4.11 | ≤ v0.4.11 | **`0x04` / `502`** — looks like an unreachable host |\n\nThe v0.4.12+ client shim re-maps code 3 to a denial when the description matches\n`\"Rate limit exceeded\"` or `\"Tunnel limit exceeded\"` **exactly**. So check both\nversions before concluding, and on the last row read\n`toxtunnel_rate_limit_open_rejected_total` on the server rather than chasing the\ntarget.\n\nLoosen `rate_limit_defaults` or add a per-friend override block in\n`rules.yaml` and `kill -HUP` to reload.\n\n### Symptom: one friend's transfers are slow but nothing is erroring\n\nNo `TUNNEL_ERROR`, no closed tunnels, no rules denial — that friend's bytes\njust arrive slower than the link allows. Check whether its byte budget is\nbinding (`rate_limit.bytes_per_sec` / `bytes_burst`, live since v0.4.11 —\nin earlier v0.4.x releases these keys parsed and did nothing, so a config\ncarried across the upgrade can start shaping traffic that never was before):\n\n1. `curl 127.0.0.1:9100/metrics | grep rate_limit_bytes_throttled_total`\n   — a climbing counter means frames are finding the bucket short. In\n   `enforce` that is the throttle working; in `report` nothing is being\n   delayed and the counter is pure measurement.\n2. Server log at startup / after reload:\n   `Inbound byte throttle engaged for friend <N> (rate_limit.bytes_per_sec)`,\n   or `Inbound byte throttle engaged|disengaged for friend <N>`.\n3. `toxtunnel inspect status --json` does **not** expose bucket levels;\n   the counter and the log lines are the only signals.\n\nRemember what this key does and does not cover before concluding anything:\nit meters **inbound TUNNEL_DATA from that friend** only. If the slow\ndirection is server → client, the byte budget is not the cause — look at\ntransport (UDP vs TCP relay) and flow control instead. Enforcement defers\nand replays in order; it never drops bytes and never closes a tunnel, so a\nthrottled tunnel is slow, not broken. Two rails release the backlog early\nrather than growing it (32 MiB per friend, and a per-frame deadline of at\nmost 60 s derived from the reaper timeouts) — both log at `warn` and both\nmean the configured rate was briefly exceeded, not that data was lost.\n\nFix: raise `bytes_per_sec` / `bytes_burst`, switch the friend to\n`mode: report` to confirm the budget is the cause, or set `bytes_burst: 0`\nto exempt it — then reload. A reload refills every bucket, so give it a\nmoment before re-measuring.\n\n### Symptom: adaptive coalescing is making bad decisions\n\nThe `toxtunnel_coalesce_policy_transitions_total` counter ticks on\nevery state-machine move. If it climbs fast under steady traffic, the\nEWMA is flapping. Workarounds:\n\n1. Pin the mode: `tunnel.coalesce_mode: fixed` to lock to the v0.3.0 cadence.\n   This is the right answer for a flapping EWMA in almost every case.\n2. For bulk-only workloads pin `bypass`; for trickle-only, where you want every\n   small write batched, pin `drain`.\n\nThe **only** valid values for `tunnel.coalesce_mode` are `fixed`, `adaptive`,\n`bypass`, `drain`. `batch` is an internal state the adaptive machine selects at\nruntime (and a metric label) — it is **not** a config value, and setting it makes\nthe daemon refuse to start:\n`Invalid tunnel.coalesce_mode 'batch': must be one of 'fixed', 'adaptive', 'bypass', 'drain'`.\n`toxtunnel config check -c <file>` catches it before you find out the hard way.\n\n### Symptom: high BDP link still capped at 256 KiB\n\n`flow_control.mode` defaults to `bdp` since v0.4.1 — check the config has\nnot pinned `mode: fixed`. In `bdp` mode the per-tunnel `BdpFlowControl`\nscales the window between `send_window_min_bytes` and\n`send_window_max_bytes` based on RTT × bandwidth EWMA. Inspect via the\n`toxtunnel_tunnel_send_window_bytes` summary. If the link is a TCP relay\nthe window is not the limiter — see \"Slow transfer speed\" above.\n\n### Symptom: tunnels die across server restart\n\nTunnel resume (`tunnel.resume.enabled: true`) holds a disconnected\nfriend's tunnels for `resume.max_age_seconds` and reattaches them on\nreconnect — but only while **both processes stay up** (live reconnect).\nA process restart loses the local TCP sockets, so tunnels always die\nacross a restart; that is by design, not a bug. With the default\n`enabled: false` the opcodes are wire-inactive.\n\n### Symptom (macOS 15+): every LAN target fails with \"No route to host\"\n\n`TCP connect failed: No route to host` for **other devices on the LAN**, while\n`127.0.0.1` targets and the Mac's *own* LAN addresses work, and `nc`/`curl` from\na shell on the same Mac reach the target fine. This is macOS **Local Network\nprivacy**, not a tunnel fault: the check is per responsible process, and a\ndaemon left running by `nohup … &` from an SSH session (re-parented to launchd)\ngets denied with no prompt and no log entry.\n\nReproduce without toxtunnel: run any small TCP-connect binary the same detached\nway — it fails identically. Fix: run the server as an approved launchd\ndaemon/agent (System Settings → Privacy & Security → Local Network), or keep\nits targets on loopback.\n\n### Symptom (Windows): daemon started from an SSH session dies silently\n\nA daemon launched with `Start-Process` inside an `ssh host \"...\"` command\nlogs `Client started` / `Server started` and then nothing: when the SSH\ncommand returns, Win32-OpenSSH tears down the session's job object and\nthe process is killed with no log line and no Event Log entry. Run it as\nthe service (`install-windows-service`), as a Scheduled Task, or via\n`Invoke-CimMethod -ClassName Win32_Process -MethodName Create`.\n\n### Symptom (Windows): `inspect` / `reload` say \"no pid file\" or \"cannot connect\"\n\n- Daemons before v0.4.11 never wrote `toxtunnel.pid`; set\n  `TOXTUNNEL_INSPECT_PID` / `TOXTUNNEL_RELOAD_PID` to the pid printed in\n  `Inspect IPC listening at \\\\.\\pipe\\toxtunnel-<pid>`.\n- `cannot connect … (error 5: access denied)` against the service:\n  the pipe only admits SYSTEM and Administrators — use an elevated prompt.\n- `error 2: no such pipe`: the pid is stale (daemon restarted) — re-read\n  the pid file / log.\n\n### Symptom: tunnels stuck in `Connecting` or `Disconnecting` after a burst\n\n`toxtunnel inspect tunnels` on the client shows N tunnels frozen in\n`Connecting`, while the server side shows the same IDs as `Connected`\n(or `Disconnecting`). Cause: a control frame (`TUNNEL_OPEN_ACK`,\n`TUNNEL_CLOSE`, `PING`, `PONG`) hit toxcore's lossless SENDQ while it\nwas full and got silently dropped. Fixed in v0.4.5+:\n\n- Control frames routed via `TunnelManager::send_frame` are parked in a\n  bounded FIFO retry queue (cap 4096) and re-emitted every 20 ms until\n  SENDQ drains. (v0.4.11 took the handshake frames back out of that\n  queue — see below.)\n- Control frames sent via the per-tunnel `on_send_to_tox` callback\n  (`TUNNEL_CLOSE`, `PING`/`PONG`, `INFO`, resume opcodes) inspect the\n  frame type at offset 0 and, on failure, hand off to the same retry\n  queue. `TUNNEL_DATA` frames keep using the per-tunnel coalesce-buffer\n  retry-on-timer path instead — routing them through the manager queue\n  would double-send. Without the per-tunnel-path fix, a `TUNNEL_CLOSE`\n  lost to SENDQ-full would leave the peer's tunnel hung in\n  `Disconnecting` (the bidirectional bulk-transfer close-handshake hang\n  seen in the v0.4.5 1 MB-echo soak).\n\nv0.4.11 closed the remaining hole on the handshake frames themselves:\n\n- The client's `TUNNEL_OPEN` and the server's OPEN_ACK (`TUNNEL_ACK`) are\n  no longer parked in the shared queue at all — the queue holds bare wire\n  bytes with no tunnel identity, so a parked handshake frame could be\n  delivered later against whatever tunnel had recycled that id. They now\n  report backpressure to their own driver, which retains and re-sends\n  them. A `TUNNEL_OPEN` refused by a full SENDQ is retried, not dropped;\n  `create_tunnel()` releases the id and reports failure rather than\n  handing back an id the peer never heard of.\n- Those two frame types also consult the outbound queue before being\n  handed to toxcore, so neither can overtake something already parked.\n  `TUNNEL_DATA` is covered transitively — it only flows once the OPEN or\n  the OPEN_ACK has gone out — so DATA can no longer arrive ahead of a\n  backpressured OPEN_ACK. The still-parked control frames (CLOSE, ERROR,\n  PING/PONG, INFO, resume) keep the older, weaker ordering; that is a\n  known and documented residual, not a bug to chase.\n\nIf you see a wedge anyway:\n\n1. Grep `journalctl -u toxtunnel | grep 'pending queue at cap'` — the\n   only path that drops a control frame today is overflow at the cap.\n2. The queue depth is not exposed anywhere; use `toxtunnel inspect tunnels`\n   and watch each tunnel's `BYTES_IN` / `BYTES_OUT` rather than just state —\n   a tunnel whose counters advance is not actually wedged.\n3. Pair with `toxtunnel_tox_iterate_lag_milliseconds_max`: a sustained\n   spike there usually precedes a backpressure pulse.\n\nOperational hardening — **check the tunnel state before choosing a knob**:\n\n- Tunnels stuck in **`Disconnecting`** are already covered by\n  `tunnel.half_close_timeout_seconds`, which is **on by default at 120 s** and\n  force-closes exactly this case. If they are still lingering, lower that value.\n  It is not true that the defaults leave stale tunnels around forever; that was\n  only so before the half-close cap existed.\n- Tunnels stuck in **`Connected`** but genuinely abandoned are what the opt-in\n  `tunnel.idle_timeout_seconds` (default `0`) is for. Enable it deliberately:\n  it reaps any non-`Connecting` tunnel purely on inactivity, so an idle-but-alive\n  SSH session or DB pool is killed just as readily as an abandoned one. Pick a\n  timeout longer than the longest legitimate silence in the workload.\n\nBoth fire from the same `reaper_tick_seconds` timer and book\n`tunnels_closed_total{reason=\"timeout\"}`, so the counter alone will not tell you\nwhich one acted — look at `toxtunnel inspect tunnels`. Under\nsustained *bidirectional* bulk transfer the close handshake can still\nbe slow because TUNNEL_DATA frames continuously fill the same shared\ntoxcore SENDQ that control frames need; bytes flow correctly but\nclose-completion latency grows. Confirm via `inspect tunnels`: an\nidle counter that resets means the tunnel is alive but slow, not\nwedged.\n\n\n### Symptom: a client can never connect after being refused once\n\nThe server logged `Refused friend request from <PK>: no rule entry for this Tox\nID`, the operator added `<PK>` to `rules.yaml`, and the client *still* never comes\nonline — reloading the server, restarting the server, and restarting the client\nall change nothing. Waiting does not help either; measured at 3.5+ minutes with a\nfull client restart in between.\n\nCause (fixed; on releases before this fix the only escape is destructive). The\nclient persists the server in its own `tox_save.dat` the moment it first adds it,\nso toxcore considers the friendship already requested and **never re-sends the\nfriend request**. On the server, `on_friend_request` used to be the one and only\npath into the friend list, and it refuses any key not yet in `rules.yaml`. Adding\nthe rule afterwards therefore fixes the access check for a friend request that\nwill never arrive again. The two sides deadlock permanently.\n\n- On a fixed build: nothing to do. The server pre-seeds every `rules.yaml` public\n  key into its Tox friend list at startup and after every reload, so `kill -HUP`\n  (or `toxtunnel reload`) is enough. Confirm with `Pre-seeded friend <PK> from\n  rules` in the server log; the client comes online within ~50 s.\n- On an affected older build the escape is destructive, so **upgrade instead if\n  you possibly can** — the pre-seed fix removes the need entirely. If you cannot:\n  the client's Tox identity has to be replaced. **Quarantine, never delete**, and\n  only with the user's explicit agreement, having told them it mints a **new\n  public key**:\n\n  ```bash\n  # 1. Stop the client. 2. Move the whole data dir aside — do not rm -rf it.\n  mv <client data_dir> <client data_dir>.bak-$(date +%s)\n  # 3. Start the client once to mint the new identity, note the new PK.\n  # 4. Replace the OLD PK with the NEW one in the server's rules.yaml.\n  # 5. Reload the server BEFORE starting the client again.\n  ```\n\n  Order matters on those builds: the client's PK must be in `rules.yaml` and the\n  server reloaded *before* the client's first connection attempt, or you\n  reproduce the same deadlock. The backup directory also preserves\n  `known_servers.yaml` and any aliases, which you will want to copy back.\n  Note that discarding the identity invalidates **every** server's `rules.yaml`\n  entry for this client, not just the one you are fixing.\n\nNote the pre-seed is deliberately one-way: removing a key from `rules.yaml` does\n**not** delete the friend. The rules engine default-denies every `TUNNEL_OPEN`\nfrom an unlisted key, so a leftover friend entry grants no access — while deleting\nit would invalidate the peer's saved friendship and recreate exactly this deadlock\nif the rule is ever restored. To actually drop a friend, stop the daemon and\nremove it explicitly.\n\n### Symptom: resume declines with \"no held tunnel\" after a long outage\n\nClient log shows `TUNNEL_RESUME_REQUEST` answered with a decline; server log has\n`RESUME_REQUEST from friend N (tunnel M): no held tunnel; declined (re-open)`,\nand there is **no** preceding `Holding tunnel manager for friend N for resume`.\n\nCause (fixed): toxcore does not guarantee a `disconnected` callback before the\nmatching `connected` one — after a long outage it can report the friend back\nonline with no disconnect in between. The resume hold only ever ran from the\n`disconnected` path, while the `connected` path unconditionally installed a fresh\n`TunnelManager`, silently destroying the live one along with every open tunnel and\nits target TCP connection. Measured failure rate before the fix: 2 of 3\nlong-disconnect trials.\n\nThe fixed server keeps the live manager and logs\n`Friend N reported connected while its tunnel manager is still live (no matching\ndisconnected event); keeping the existing manager and its tunnels` at **warn**.\nSeeing that line is normal and benign — the tunnels survive and the follow-up\n`RESUME_REQUEST` resolves against them. What it *does* tell you is that the peer\nwas away for a while without the server noticing, so:\n\n- Some of those tunnels may be reaped shortly afterwards if\n  `tunnel.idle_timeout_seconds` or `tunnel.half_close_timeout_seconds` elapsed\n  during the invisible outage. That is the reaper working as configured, not a\n  resume failure.\n- Repeated warns for the same friend point at a flapping Tox path (check\n  `last_connection_type` — relay-only links flap far more than direct UDP), not at\n  a server bug. Enabling `tunnel.keepalive_interval_seconds` makes the server\n  notice these outages itself and take the proper hold-for-resume path.\n\nFile v0.4.14:references/execute.md\n\n# Execute Reference\n\nUse this reference when the user wants to deploy a ToxTunnel setup, install the\nbinary, start processes, or configure service persistence.\n\n## Step 0: Environment Detection\n\nRun these checks before writing files or starting anything:\n\n### 1. Is `toxtunnel` installed?\n\n```bash\nwhich toxtunnel 2>/dev/null || where toxtunnel 2>nul\n```\n\nIf not found, prefer package installation over source builds.\n\n#### Preferred: version-pinned native package\n\nThe canonical, always-current install instructions are the \"Installation\"\nsection of the repo's [`README.md`](https://github.com/agentx-icu/tox-tcp-tunnel#installation);\nif this file and the README ever disagree, the README wins. The newest release\nat the time of writing is **v0.4.12** — check the Releases page for the current\none rather than trusting this number.\n\n**When you are installing on an operator's machine, do not pipe a script from a\nmutable branch into `sudo sh`.** You have already detected OS and architecture,\nso you can do the installer's actual job — pick the right asset, hand it to the\npackage manager — from a version-pinned URL. This avoids piping a mutable remote\nscript into a root shell. It is **not** \"no remote code as root\": installing a\nDEB/RPM/PKG/MSI still runs that package's maintainer scripts with privileges.\nWhat changes is that the code executed is the released package, pinned to a\nversion you chose, rather than whatever `master` holds at that moment:\n\n```bash\nVER=0.4.12; ARCH=x86_64          # or aarch64\nBASE=\"https://github.com/agentx-icu/tox-tcp-tunnel/releases/download/v${VER}\"\n\n# Linux (DEB - Ubuntu/Debian)\ncurl -fsSL -o \"/tmp/toxtunnel-${VER}.deb\" \"${BASE}/toxtunnel-${VER}-Linux-${ARCH}.deb\"\nsudo apt-get install -y \"/tmp/toxtunnel-${VER}.deb\"\n\n# Linux (RPM - Fedora/RHEL/CentOS)\ncurl -fsSL -o \"/tmp/toxtunnel-${VER}.rpm\" \"${BASE}/toxtunnel-${VER}-Linux-${ARCH}.rpm\"\nsudo rpm -i \"/tmp/toxtunnel-${VER}.rpm\"\n\n# macOS (ARCH=arm64 or x86_64)\ncurl -fsSL -o \"/tmp/toxtunnel-${VER}.pkg\" \"${BASE}/toxtunnel-${VER}-Darwin-${ARCH}.pkg\"\nsudo installer -pkg \"/tmp/toxtunnel-${VER}.pkg\" -target /\n```\n\n```powershell\n# Windows (Administrator PowerShell); ARM: toxtunnel-$VER-Windows-ARM64.msi\n$VER='0.4.12'\nirm \"https://github.com/agentx-icu/tox-tcp-tunnel/releases/download/v$VER/toxtunnel-$VER-Windows-AMD64.msi\" -OutFile \"$env:TEMP\\toxtunnel.msi\"\nmsiexec /i \"$env:TEMP\\toxtunnel.msi\" /qn\n```\n\n**Integrity, stated accurately.** No `.sha256` or signature assets are\npublished — the release carries the packages and their `-latest` aliases. But\nGitHub exposes a SHA-256 digest for every release asset regardless, so there IS\nsomething to verify against: compare the downloaded file's digest with the one\nGitHub reports for that asset. What is missing is independent\nsignature/provenance — the digest and the file come from the same party, so it\ndetects corruption and truncation, not a compromised release. Note also that a\nrelease URL is only immutable if the repository enabled immutable releases;\notherwise the tag is a convention, not a guarantee. Say exactly this if the\noperator asks about integrity, rather than implying either more or less\nassurance than exists.\n\nEach release also publishes stable `-latest` aliases\n(`toxtunnel-<System>-<arch>-latest.<ext>`). They are convenient but unpinned, and\nwhat they resolve to changes under you — prefer the versioned asset when the\ninstall needs to be reproducible.\n\n#### The one-line installer (only when the user asks for it)\n\nThe repo ships installer scripts that auto-detect arch, download the matching\nnative package from GitHub Releases, install it, and seed `config.yaml`\nbased on `--mode`. Client mode writes a config scaffold and leaves the system\nservice idled (exit 0) until the user fills in `client.server_id` and sets\n`service.allow_client_daemon: true`.\n\nIf the user explicitly wants this path, **pin it to a release tag, download it,\nlet them read it, and only then run it.** `master` is mutable: the `| sudo sh`\nform executes whatever landed on that branch today, as root, unreviewed.\n\n```bash\n# Download a pinned copy (tag URLs resolve; verified for v0.4.12)\ncurl -fsSL -o /tmp/toxtunnel-install.sh \\\n  https://raw.githubusercontent.com/agentx-icu/tox-tcp-tunnel/v0.4.12/scripts/install.sh\nless /tmp/toxtunnel-install.sh            # <- show the operator what will run as root\nsudo sh /tmp/toxtunnel-install.sh                        # server\nsudo sh /tmp/toxtunnel-install.sh --mode client          # client scaffold\n```\n\n```powershell\n# Windows (Administrator PowerShell)\nirm https://raw.githubusercontent.com/agentx-icu/tox-tcp-tunnel/v0.4.12/scripts/install.ps1 -OutFile \"$env:TEMP\\install.ps1\"\nGet-Content \"$env:TEMP\\install.ps1\" | more     # review first\n$env:TOXTUNNEL_MODE='client'; & \"$env:TEMP\\install.ps1\"\n```\n\nEnv vars / flags: `TOXTUNNEL_MODE`, `TOXTUNNEL_VERSION`, `TOXTUNNEL_REPO`. The\ninstaller is idempotent on the same mode and refuses to overwrite a\nuser-customized config (only rewrites the freshly seeded server template\nwhen switching to client).\n\nDo not run the `curl … | sudo sh` or `irm … | iex` form on the user's behalf.\n\n#### Unpinned `-latest` aliases (convenience only)\n\nIf reproducibility does not matter, the `-latest` aliases save looking up a\nversion number:\n\n```bash\nARCH=x86_64      # or aarch64 (Darwin: arm64 / x86_64)\nBASE=https://github.com/agentx-icu/tox-tcp-tunnel/releases/latest/download\nwget \"$BASE/toxtunnel-Linux-${ARCH}-latest.deb\"  && sudo dpkg -i \"toxtunnel-Linux-${ARCH}-latest.deb\"\nwget \"$BASE/toxtunnel-Linux-${ARCH}-latest.rpm\"  && sudo rpm -i  \"toxtunnel-Linux-${ARCH}-latest.rpm\"\nwget \"$BASE/toxtunnel-Darwin-${ARCH}-latest.pkg\" && sudo installer -pkg \"toxtunnel-Darwin-${ARCH}-latest.pkg\" -target /\n# Windows: $BASE/toxtunnel-Windows-AMD64-latest.msi (ARM64 variant available), run as Administrator\n```\n\n#### Build from source (only if no package fits)\n\n- macOS: `brew install libsodium && cd <project> && cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j$(sysctl -n hw.ncpu) && sudo cp build/toxtunnel /usr/local/bin/`\n- Linux (Debian/Ubuntu): `sudo apt install libsodium-dev build-essential cmake && cd <project> && cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j$(nproc) && sudo cp build/toxtunnel /usr/local/bin/`\n- Linux (Fedora/RHEL): `sudo dnf install libsodium-devel cmake gcc-c++ && ...`\n- Windows: build with MSVC + vcpkg or MSYS2 (see `BUILDING.md`)\n\n### 2. Is libsodium available?\n\n```bash\npkg-config --exists libsodium && echo \"OK\" || echo \"MISSING\"\n# or: ldconfig -p | grep libsodium   (Linux)\n# or: brew list libsodium            (macOS)\n```\n\n### 3. Are target ports available?\n\n```bash\nlsof -i :PORT -sTCP:LISTEN    # macOS/Linux\nnetstat -an | findstr :PORT   # Windows\n```\n\n### 4. Detect OS for path and service defaults\n\n- macOS (from `.pkg`): `binary: /usr/local/bin/toxtunnel`, example config at `/usr/local/share/toxtunnel/config.yaml.example`. The pkg postinstall automatically seeds `/usr/local/etc/toxtunnel/config.yaml` (from the example), installs `com.toxtunnel.daemon.plist` into `/Library/LaunchDaemons/`, and runs `launchctl bootstrap`.\n- macOS (manual/source build): `data_dir: ~/Library/Application Support/toxtunnel/` or `~/.config/toxtunnel/`, service: launchd user agent\n- Linux (from DEB/RPM): `binary: /usr/bin/toxtunnel`, `config: /etc/toxtunnel/config.yaml`, `data_dir: /var/lib/toxtunnel`, service: `toxtunnel.service` (Type=notify, `RemainAfterExit=yes`, enabled and started by postinst).\n- Linux (manual): `data_dir: ~/.config/toxtunnel/`, service: custom systemd unit\n- Windows (from MSI): `binary: C:\\Program Files\\ToxTunnel\\bin\\toxtunnel.exe`. **The MSI does NOT auto-register the SCM service** — the WiX patch is shelved (`cmake/Packaging.cmake` has the rationale). The user creates `C:\\ProgramData\\ToxTunnel\\config.yaml`, then runs `& 'C:\\Program Files\\ToxTunnel\\bin\\toxtunnel.exe' install-windows-service -c 'C:\\ProgramData\\ToxTunnel\\config.yaml'` from an Administrator PowerShell, then `sc start ToxTunnel`. The repo's own `scripts/install.ps1` (in the tox-tcp-tunnel repository, not this skill's `scripts/`) does all of this automatically — download and review it first.\n- Windows (manual): `data_dir: %APPDATA%\\toxtunnel\\`, service: NSSM or Task Scheduler\n\n## Step 1: Write Config Files\n\nGenerate and write:\n\n- `server.yaml`\n- `client.yaml`\n- `rules.yaml` when access control is needed\n\nUse the templates under `templates/` and enforce the minimum-privilege rules from\nthe main skill. Three things to get right at write time:\n\n1. **`server.rules_file` must be absolute.** A relative path resolves against the\n   daemon's working directory, not the config's directory, so a service unit\n   fails to start with `Rules file not found`.\n2. **`data_dir` must be absolute, writable by the service account, and not under\n   `/etc`** — it holds mutable state (identity, pid, lock, inspect socket). Use\n   `/var/lib/toxtunnel` on Linux.\n3. **A forward binds `0.0.0.0` unless `local_address` says otherwise.** On\n   **v0.4.13+** set `local_address: 127.0.0.1` unless it must serve other\n   machines; on v0.4.12 and older there is no such key, so emit the exposure\n   warning with the config and give the\n   operator either a host firewall rule for that port or a loopback-only SOCKS5\n   listener instead.\n\nThen validate before starting anything:\n\n```bash\ntoxtunnel config check -c server.yaml --strict\ntoxtunnel config check -c client.yaml --strict\nbash scripts/diagnose.sh server.yaml    # covers rules.yaml, which config check never opens\n```\n\n## Step 2: Startup Commands\n\n```bash\n# Server side\ntoxtunnel -m server -c /path/to/server.yaml\n\n# Client side\ntoxtunnel -m client -c /path/to/client.yaml\n```\n\nIf running on the current machine, only start processes after explicit user request.\n\n## Step 3: Service Persistence\n\nOnly do this when the user explicitly asks for persistent service management.\n\n### Linux DEB/RPM\n\nPostinst creates the `toxtunnel` system user, seeds `/etc/toxtunnel/config.yaml`\nfrom the example if missing, registers `toxtunnel.service`, and runs\n`systemctl enable --now`. The unit is `Type=notify` with `RemainAfterExit=yes`,\nso a daemon that gates itself off (client mode without `allow_client_daemon`,\nor missing config under `--service`) shows as `active (exited)` rather than\n`inactive (dead)`.\n\n```bash\nsudo vim /etc/toxtunnel/config.yaml      # already seeded; edit in place\nsudo systemctl restart toxtunnel         # apply changes\nsudo systemctl status toxtunnel\n```\n\n### macOS `.pkg`\n\nThe pkg postinstall (`packaging/macos/postinstall.sh`) seeds\n`/usr/local/etc/toxtunnel/config.yaml` from the example if missing, installs\n`com.toxtunnel.daemon.plist` into `/Library/LaunchDaemons/`, and runs\n`launchctl bootstrap system`. The plist's `KeepAlive { SuccessfulExit: false }`\nmeans a config-gated exit-0 daemon stays stopped (won't loop). On newer macOS\nversions, `launchctl bootstrap` may require user approval in System Settings →\nPrivacy & Security; the postinstall treats that failure as non-fatal.\n\n```bash\nsudo vim /usr/local/etc/toxtunnel/config.yaml          # already seeded; edit in place\nsudo launchctl kickstart -k system/com.toxtunnel.daemon  # apply changes\nsudo launchctl print system/com.toxtunnel.daemon | head\n```\n\n### Windows MSI\n\n**The MSI does NOT auto-register the SCM service** (the WiX patch is\nshelved — see `cmake/Packaging.cmake` for context). Workflow: install MSI →\ncreate config → register the service with the bundled subcommand → start it.\nAn in-place upgrade (`msiexec /i toxtunnel-<new>.msi /qn /norestart`) keeps\n`config.yaml`, `data\\` and the registered service; `Stop-Service ToxTunnel`\nbefore and `Start-Service ToxTunnel` after.\n\n```powershell\nmkdir 'C:\\ProgramData\\ToxTunnel' -Force\nnotepad 'C:\\ProgramData\\ToxTunnel\\config.yaml'\n\n# Register the service (run as Administrator):\n& 'C:\\Program Files\\ToxTunnel\\bin\\toxtunnel.exe' install-windows-service `\n    -c 'C:\\ProgramData\\ToxTunnel\\config.yaml'\n\nsc start ToxTunnel\nsc query ToxTunnel\nsc stop ToxTunnel\n```\n\n> The repo's one-line installer (`scripts/install.ps1` in the tox-tcp-tunnel\n> repository, not this skill's `scripts/`) automates all of the above. Use\n> it unless the user explicitly needs the manual flow. Remove the service with\n> `& 'C:\\Program Files\\ToxTunnel\\bin\\toxtunnel.exe' uninstall-windows-service`.\n>\n> `install-windows-service` also configures SCM recovery actions (restart after\n> 10 s / 30 s / 60 s, with `fFailureActionsOnNonCrashFailures` set so a clean\n> exit reporting an error counts). That is what makes Windows retry a startup\n> that failed for a transient reason — e.g. a restart racing the previous\n> instance for the data-directory lock — the way systemd's `Restart=on-failure`\n> and launchd's `KeepAlive` already do. Check it with `sc qfailure ToxTunnel`.\n\n### Manual source-build service templates\n\n#### Linux systemd\n\nModel it on the packaged unit (`packaging/linux/toxtunnel.service`), which runs\nas a dedicated account with systemd-managed state — never as root:\n\n```ini\n[Unit]\nDescription=ToxTunnel %i\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=notify\nExecStart=/usr/local/bin/toxtunnel -m %i -c /etc/toxtunnel/%i.yaml --service\nExecReload=/bin/kill -HUP $MAINPID\nRestart=on-failure\nRestartSec=5\nRemainAfterExit=yes\n# Never run this as root. StateDirectory creates and chowns\n# /var/lib/toxtunnel to the service account; point data_dir there.\nUser=toxtunnel\nGroup=toxtunnel\nStateDirectory=toxtunnel\nStateDirectoryMode=0750\nWorkingDirectory=/etc/toxtunnel\n\n[Install]\nWantedBy=multi-user.target\n```\n\n`WorkingDirectory` is set here only as a belt-and-braces measure — do **not**\nrely on it to resolve a relative `server.rules_file`. Write that path absolute\n(`/etc/toxtunnel/rules.yaml`); the daemon does no config-relative resolution and\nany change to the unit's working directory would break startup.\n\nThe packaged unit carries no sandboxing directives. If you are writing a unit\nfrom scratch for an exposed host, consider adding `NoNewPrivileges=yes`,\n`ProtectSystem=strict`, `ProtectHome=yes`, `PrivateTmp=yes`,\n`RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX` and\n`CapabilityBoundingSet=` — but test them, they are not what ships.\n\nInstall with:\n`sudo cp toxtunnel@.service /etc/systemd/system/ && sudo systemctl enable --now toxtunnel@server`\n\n#### macOS launchd\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n  \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>Label</key>\n    <string>com.toxtunnel.MODE</string>\n    <key>ProgramArguments</key>\n    <array>\n        <string>/usr/local/bin/toxtunnel</string>\n        <string>-m</string>\n        <string>MODE</string>\n        <string>-c</string>\n        <string>/usr/local/etc/toxtunnel/MODE.yaml</string>\n        <string>--service</string>\n    </array>\n    <key>RunAtLoad</key>\n    <true/>\n    <!-- Match the packaged plist: restart on failure, but NOT after a clean\n         exit. An unconditional <true/> here respawn-loops a daemon that has\n         gated itself off on purpose (client mode without allow_client_daemon,\n         or a missing config under --service), because those exit 0. -->\n    <key>KeepAlive</key>\n    <dict>\n        <key>SuccessfulExit</key>\n        <false/>\n    </dict>\n    <key>StandardOutPath</key>\n    <string>/usr/local/var/log/toxtunnel-MODE.log</string>\n    <key>StandardErrorPath</key>\n    <string>/usr/local/var/log/toxtunnel-MODE.log</string>\n</dict>\n</plist>\n```\n\nInstall with:\n`cp com.toxtunnel.MODE.plist ~/Library/LaunchAgents/ && launchctl load ~/Library/LaunchAgents/com.toxtunnel.MODE.plist`\n\n#### Windows `sc.exe` (raw)\n\n```cmd\nsc create ToxTunnel binPath= \"\\\"C:\\path\\to\\toxtunnel.exe\\\" -c \\\"C:\\path\\to\\config.yaml\\\" --service\" start= auto\nsc start ToxTunnel\n```\n\nOr use the bundled subcommand (same effect, fewer footguns around quoting):\n\n```cmd\n\"C:\\path\\to\\toxtunnel.exe\" install-windows-service -c \"C:\\path\\to\\config.yaml\"\nsc start ToxTunnel\n```\n\n#### Windows NSSM\n\n```cmd\nnssm install ToxTunnel-MODE \"C:\\path\\to\\toxtunnel.exe\" -m MODE -c \"C:\\path\\to\\MODE.yaml\"\nnssm set ToxTunnel-MODE AppStdout \"C:\\path\\to\\logs\\MODE.log\"\nnssm set ToxTunnel-MODE AppStderr \"C:\\path\\to\\logs\\MODE.log\"\nnssm start ToxTunnel-MODE\n```\n\n## Step 4: Lifecycle Operations\n\n### Start / Stop / Restart\n\n```bash\n# Direct process\ntoxtunnel -m server -c server.yaml &\nkill \"$(cat <data_dir>/toxtunnel.pid)\"      # pid file written by the daemon (v0.4.11+)\n# or: pkill -x toxtunnel   — never `pkill -f toxtunnel…`: -f also matches the\n#     shell / CI step / SSH wrapper whose command line mentions toxtunnel\n\n# systemd\nsudo systemctl start toxtunnel\nsudo systemctl stop toxtunnel\nsudo systemctl restart toxtunnel\nsudo systemctl start toxtunnel@server\nsudo systemctl stop toxtunnel@server\n\n# launchd\nsudo launchctl bootstrap system /Library/LaunchDaemons/com.toxtunnel.daemon.plist\nsudo launchctl bootout system /Library/LaunchDaemons/com.toxtunnel.daemon.plist\nlaunchctl start com.toxtunnel.server\nlaunchctl stop com.toxtunnel.server\n\n# Windows SCM\nsc start ToxTunnel\nsc stop ToxTunnel\n\n# Windows NSSM\nnssm start ToxTunnel-server\nnssm stop ToxTunnel-server\n```\n\n### View logs\n\n```bash\ntail -f /var/log/toxtunnel/server.log\njournalctl -u toxtunnel@server -f\ntail -f /usr/local/var/log/toxtunnel-server.log\ntoxtunnel -m server -c server.yaml -l debug\n```\n\n## Step 4.4: v0.3.0 Feature Recipes\n\n### Enable Prometheus `/metrics`\n\nAdd to **either** `server.yaml` **or** `client.yaml` (or both — they expose\ndifferent label sets):\n\n```yaml\nmetrics:\n  enabled: true\n  listen: 127.0.0.1:9100   # KEEP loopback unless the scraper is on a trusted network\n  path: /metrics\n```\n\nRestart the daemon (metrics listen is NOT a hot-reloadable field), then smoke-test:\n\n```bash\ncurl -s http://127.0.0.1:9100/metrics | grep '^toxtunnel_' | head -20\n```\n\nExpected metric families:\n`toxtunnel_build_info`, `toxtunnel_tunnels_active{role=...}`,\n`toxtunnel_tunnels_opened_total{result=\"ok|denied|failed\"}`,\n`toxtunnel_tunnels_closed_total{reason=\"local|remote|timeout|error\"}`,\n`toxtunnel_bytes_in_total`, `toxtunnel_bytes_out_total`,\n`toxtunnel_friends_online`, `toxtunnel_tox_iterate_lag_milliseconds_{count,sum,max}`.\n\nMinimal Prometheus scrape config:\n\n```yaml\nscrape_configs:\n  - job_name: toxtunnel\n    static_configs:\n      - targets: ['127.0.0.1:9100']\n    scrape_interval: 15s\n```\n\n### Enable SOCKS5 / HTTP CONNECT listener (client side)\n\nCLI flag form (no YAML edit, ephemeral):\n\n```bash\ntoxtunnel -m client --server-id homelab --socks5 127.0.0.1:1080\n```\n\nYAML form:\n\n```yaml\nclient:\n  server_id: homelab\n  socks5:\n    enabled: true\n    listen: 127.0.0.1:1080     # config validator REJECTS non-loopback binds\n```\n\nUse it from a browser / curl / pip:\n\n```bash\n# curl over SOCKS5 (DNS resolved on the server side via socks5-hostname / socks5h)\ncurl --socks5-hostname 127.0.0.1:1080 http://internal.example.lan/\n\n# HTTP CONNECT (same listener auto-detects)\nhttps_proxy=http://127.0.0.1:1080 curl https://internal.example.lan/\n\n# Firefox / Chrome: SOCKS host 127.0.0.1, port 1080, \"Proxy DNS when using SOCKS v5\"\n```\n\n**Server-side policy still gates which destinations succeed** — a SOCKS5\nCONNECT the friend's `rules.yaml` does not allow returns reply `0x02`\n(\"connection not allowed by ruleset\"), and so do the other policy rejections,\nthe rate limiter and the concurrent-tunnel cap; the same denial over HTTP\nCONNECT returns `403 Forbidden`. Everything else the open can fail with maps\nelsewhere — SOCKS5 `0x04` (host unreachable), `0x05` (connection refused),\n`0x01` (general failure), and `502 Bad Gateway` for all three over HTTP CONNECT\n— so the code tells you whether the **server** refused the request or simply\ncould not reach the target. SOCKS5 and `client.pipe` cannot be enabled at the\nsame time (validator error).\n\n**Reading a rate-limit / tunnel-cap denial depends on BOTH versions.** Before\nv0.4.12 the server sent `TUNNEL_ERROR` code 3 for those denials; from v0.4.12 it\nsends code 1. Independently, a v0.4.12+ client carries a compatibility shim that\nre-classifies code 3 as a denial when the description is exactly\n`\"Rate limit exceeded\"` or `\"Tunnel limit exceeded\"`. So:\n\n| Server | Client | Rate-limit / cap denial surfaces as |\n|--------|--------|-------------------------------------|\n| ≥ v0.4.12 | ≥ v0.4.12 | `0x02` / `403` — correct |\n| ≥ v0.4.12 | ≤ v0.4.11 | `0x02` / `403` — correct (the server already sends code 1) |\n| ≤ v0.4.11 | ≥ v0.4.12 | `0x02` / `403` — correct, via the client-side shim |\n| ≤ v0.4.11 | ≤ v0.4.11 | **`0x04` / `502`** — looks like an unreachable host |\n\nOnly the last row is misleading, and it needs *both* ends to be old. The shim\nmatches those two strings exactly, so a server that reworded them would fall\nback to `0x04` as well. When you are on that last row, check\n`toxtunnel_rate_limit_open_rejected_total` on the server before blaming the\ntarget.\n\n### Multi-server failover (production HA)\n\nYAML list form for `server_id`:\n\n```yaml\nclient:\n  server_id:\n    - homelab-primary       # entry 0 = preferred primary\n    - hetzner-fallback\n    - <full-76-char-tox-id> # raw IDs and aliases mix freely\n  failover:\n    timeout_seconds: 60               # primary offline this long -> promote next online candidate\n    prefer_primary_grace_seconds: 30  # primary must be online this long before we switch back\n  forwards:\n    - { local_port: 2222, local_address: 127.0.0.1, remote_host: 127.0.0.1, remote_port: 22 }\n```\n\nCLI flag form (one primary + repeated fallback):\n\n```bash\ntoxtunnel -m client \\\n  --server-id homelab-primary \\\n  --server-id-fallback hetzner-fallback aws-fallback \\\n  -c client.yaml\n```\n\nVerify failover behavior:\n\n```bash\n# Watch active server transitions in the log\njournalctl -u toxtunnel -f | grep -E 'Failover|active server'\n\n# Or query the running daemon directly\ntoxtunnel inspect status --json | jq '.friends_online, .peer_online_seconds'\n# (there is no .active_server field — the active server appears only in the\n#  `Failover: switching active server …` log line)\n```\n\n### Live inspection (`toxtunnel inspect`)\n\nThe daemon serves a local IPC channel — Unix socket on POSIX\n(`<data_dir>/toxtunnel.sock`), named pipe on Windows\n(`\\\\.\\pipe\\toxtunnel-<pid>`, with the pid published in\n`<data_dir>\\toxtunnel.pid`). Inspection is read-only and strictly local —\nnever network-exposed.\n\n```bash\n# Table of currently open tunnels: ID TARGET STATE BYTES_IN BYTES_OUT IDLE_S PEER\ntoxtunnel inspect tunnels\n\n# Process / version / friend / metrics snapshot\ntoxtunnel inspect status\n\n# Pipe JSON into jq for dashboards or scripting\ntoxtunnel inspect tunnels --json | jq '.tunnels[] | select(.bytes_in > 1000000)'\n\n# Point at a non-default data_dir (e.g. service install paths)\ntoxtunnel inspect status -c /etc/toxtunnel/server.yaml\ntoxtunnel inspect tunnels -d /var/lib/toxtunnel\n```\n\n`inspect.enabled` is **default-on**; set `inspect.enabled: false` to disable.\nThe switch is only honoured from **v0.4.11** — earlier daemons parsed the key\nand started the listener regardless, so on an older build the only way to keep\nthe IPC channel closed is not to run that build.\n\n### Hot-reload (no restart)\n\nReloadable subset only: **`server.rules_file` contents, `client.forwards`,\n`logging.level`**. Tox identity, listen ports, mode, and `data_dir` still\nrequire a full restart.\n\n```bash\n# POSIX (Linux/macOS): SIGHUP, either form works\ntoxtunnel reload -c /etc/toxtunnel/server.yaml   # reads <data_dir>/toxtunnel.pid, sends SIGHUP\nkill -HUP $(cat /var/lib/toxtunnel/toxtunnel.pid)\nsudo systemctl reload toxtunnel                  # packaged install\n```\n\n```powershell\n# Windows: writes RELOAD\\n to \\\\.\\pipe\\toxtunnel-reload-<pid>\n# (pid from <data_dir>\\toxtunnel.pid; use an elevated prompt for the service)\ntoxtunnel.exe reload -c 'C:\\ProgramData\\ToxTunnel\\config.yaml'\n```\n\nDaemons older than v0.4.11 wrote no pid file: set `TOXTUNNEL_RELOAD_PID=<pid>`\n(the pid is in the startup log line `Inspect IPC listening at ...toxtunnel-<pid>`).\n\nConfirm the reload landed by tailing the log for one of:\n\n```\nconfig reloaded (rules: N rules)\nconfig reloaded (forwards: +A -B)\n```\n\nIf the new config has a parse error or validation failure, the daemon\n**rejects the reload, keeps running the old config, and logs**\n`reload failed: <reason>` / `reload rejected: <reason>` — no downtime, no\npartial state.\n\n### Tunnel reapers — two distinct policies\n\n```yaml\ntunnel:\n  half_close_timeout_seconds: 120   # DEFAULT-ON. Disconnecting tunnels only.\n  idle_timeout_seconds: 0           # opt-in. ANY non-Connecting tunnel.\n  reaper_tick_seconds: 10           # shared wake-up interval\n```\n\n- **`half_close_timeout_seconds` (default 120, on)** reaps only tunnels in state\n  `Disconnecting` — a one-sided TCP close whose peer never sent the reciprocal\n  `TUNNEL_CLOSE`, which would otherwise pin a half-open fd forever. This is the\n  policy that already handles \"zombie\" tunnels. If half-closed tunnels linger,\n  **lower this**; do not enable the idle reaper for them.\n- **`idle_timeout_seconds` (default 0, off)** reaps *any* tunnel that is not in\n  `Connecting`, purely on time since the last `TUNNEL_DATA` in either direction.\n  That includes healthy `Connected` tunnels, so it will kill a quiet SSH session\n  or an idle connection pool. Enable it only after confirming with\n  `toxtunnel inspect tunnels` that the accumulating tunnels really are\n  `Connected` and genuinely abandoned, and pick a timeout longer than the\n  longest legitimate silence in your workload.\n\nBoth book `toxtunnel_tunnels_closed_total{reason=\"timeout\"}`, so that counter\ndoes not tell you which one fired — check the tunnel states instead.\n\n## Step 4.5: Known-Servers Registry (client side)\n\nAfter a successful client→server connection, the client persists an entry in\n`<data_dir>/known_servers.yaml`. Manage it from the CLI:\n\n```bash\ntoxtunnel servers list                       # compact list of saved servers\ntoxtunnel servers list --full                # show full 76-char Tox IDs\ntoxtunnel servers show <alias_or_tox_id>     # full record incl. info disclosed by server\ntoxtunnel servers add  <alias> <tox_id>      # name a Tox ID\ntoxtunnel servers remove <alias_or_tox_id>   # forget\n```\n\nAfter `servers add homelab DE47F2...`, both `--server-id homelab` and\n`client.server_id: homelab` resolve from the registry at startup.\n\nFor server-side info disclosure (defaults to nothing), uncomment the relevant\nfields under `server.disclose:` in `server.yaml`:\n\n```yaml\nserver:\n  rules_file: /etc/toxtunnel/rules.yaml   # absolute — see Step 1\n  disclose:\n    hostname: true\n    os: true\n    arch: true\n```\n\nThe disclosed snapshot is sent via `INFO_REPLY` (frame 0x07) when the client\nsends an `INFO_REQUEST` (frame 0x06) on first reaching online state.\n\n## Step 5: Post-Deploy Verification\n\n```bash\nbash scripts/verify.sh <local_port> <service_type> <client.yaml>\n```\n\n`service_type` is one of `ssh | http | postgres | mysql | redis | mongo | rdp |\ntcp`; an unrecognised value is rejected rather than silently treated as generic\nTCP. Passing the client config lets the script read `friends_online` from the\nrunning daemon instead of inferring liveness from a local TCP accept.\n\n**Judge it by the exit code, not the text:**\n\n| Exit | Meaning | What to report |\n|------|---------|----------------|\n| `0` | The remote service replied through the tunnel | Working |\n| `2` | Local checks passed, end-to-end **NOT** proven (no probe tool installed, or `tcp` has no protocol probe) | **Not** working-confirmed. Say what is still unverified |\n| `1` | A check failed | Broken; the output names the layer |\n\nA successful TCP connect to `127.0.0.1:<port>` is **not** evidence the tunnel\nworks: the client binds and accepts the local port before any `TUNNEL_OPEN` is\nattempted, so the port answers even with the Tox link down and the rules denying\neverything. Only a reply from the real remote service proves the path.\n\n## Output Format\n\n```text\n## Environment Check\n- toxtunnel: [installed at /usr/local/bin/toxtunnel | NOT FOUND]\n- libsodium: [OK | MISSING]\n- Port XXXX: [available | in use by PROCESS]\n- OS: [macOS / Linux / Windows]\n\n## Generated Files\n- server.yaml -> /path/to/server.yaml\n- client.yaml -> /path/to/client.yaml\n- rules.yaml  -> /path/to/rules.yaml  (if applicable)\n\n## Startup Commands\n[OS-specific commands]\n\n## Service Persistence\n[Only if requested: systemd/launchd/NSSM config]\n\n## Lifecycle Commands\n[start / stop / restart / logs]\n\n## Verification\n[Test command and expected output]\n```\n\n## v0.4 Optional Config Blocks\n\nOperators with extra capacity / hardening needs can opt into the new\nv0.4 blocks. Defaults preserve v0.3.0 behaviour, with two deliberate\nexceptions: `flow_control.mode` is `bdp` (since v0.4.1), and `watchdog`\nis on.\n\n### Watchdog (on by default)\n\n```yaml\nwatchdog:\n  enabled: true                # default\n  deadline_seconds: 30         # min 5 (validator-enforced)\n  systemd_notify: true         # ignored outside Linux\n```\n\nThe watchdog measures **one thing**: how long since the Tox thread last returned\nfrom `tox_iterate()`. It knows nothing about network reachability, so \"the\nnetwork is flaky\" is not a reason to raise `deadline_seconds` — a peer being\nunreachable does not stall `tox_iterate`. Raise it only when the *host* is slow\nenough that a legitimate iterate can exceed the deadline: heavy CPU oversubscription,\na frozen or swapping VM, a stalled disk. Otherwise leave it at 30.\n\nMonitoring it — mind the two similarly named metrics:\n\n- **`toxtunnel_tox_iterate_lag_ms`** — gauge, milliseconds since the last\n  successful `tox_iterate()` return. **This is the wedge signal.** Alert when it\n  approaches `deadline_seconds` (e.g. `> 5000`).\n- **`toxtunnel_tox_iterate_lag_milliseconds_max`** (plus `_count` / `_sum`) —\n  the maximum *completed* call duration since process start. It latches on one\n  historical slow call and can never move while a call is actually hung, so it\n  is a trend indicator, not an alarm.\n- **`toxtunnel_watchdog_aborts_total`** — **resets to 0 at every process start.**\n  It is not seeded from `<data_dir>/abort_count`; that file is the durable\n  count, written only at abort time. Alert on `increase(...)` over a window, and\n  read the file for history.\n\nThe fatal line is logged at **critical** level and reads:\n\n```text\ntox_thread wedge detected: lag_ms=<N> deadline_ms=<N> heartbeat_count=<N>\n```\n\n### Adaptive coalescing (opt-in)\n\n```yaml\ntunnel:\n  coalesce_mode: adaptive      # default fixed (v0.3.0); flip to adaptive\n                                # only after one release of soak\n```\n\n### BDP flow control (default since v0.4.1)\n\n```yaml\nflow_control:\n  mode: bdp                    # default; `fixed` locks the v0.3.0 256 KiB window\n  send_window_min_bytes: 65536\n  send_window_max_bytes: 4194304\n  safety_factor_x100: 150\n  fixed_window_bytes: 262144\n```\n\n### Per-friend rate limiting (opt-in)\n\nIn `rules.yaml`:\n\n```yaml\nrate_limit_defaults:\n  mode: report                 # start shadow; flip to enforce once tuned\n  open_per_sec: 10\n  open_burst: 50\n  max_concurrent_tunnels: 100\n  bytes_per_sec: 1048576       # inbound TUNNEL_DATA payload, bytes/sec\n  bytes_burst: 4194304         # BOTH byte keys must be non-zero to engage\n\nrules:\n  - friend: \"...64hex...\"\n    rate_limit:\n      # A per-friend block OVERRIDES ONLY the fields it names; everything else\n      # is inherited from rate_limit_defaults above (including `mode`). An\n      # explicit 0 means \"no limit for this friend on that field\".\n      max_concurrent_tunnels: 200\n      bytes_per_sec: 262144\n      bytes_burst: 1048576\n    allow:\n      - host: \"127.0.0.1\"\n        ports: [22]\n```\n\n#### Byte budgets: what `bytes_per_sec` / `bytes_burst` actually do\n\nImplemented since **v0.4.11**. Earlier v0.4.x releases parsed these keys and\nnever consulted them, so a rules file carried over from one of those will start\nshaping traffic on the first restart after upgrading — check the value is one\nyou want before rolling it out.\n\n- **Direction.** It meters the payload of **inbound `TUNNEL_DATA` frames from\n  that friend**, per friend, summed across all of that friend's tunnels. Same\n  direction `open_per_sec` guards: a server's `rules.yaml` describes what a peer\n  may do *to it*. Traffic the server sends back is **not** metered by this key.\n  If the operator wants to cap what this host *transmits*, say so plainly and\n  point at an OS-level shaper (`tc` on Linux, `pf`/`dnctl` on macOS) — nothing\n  in `rules.yaml` does that.\n- **`report`** accounts and increments\n  `toxtunnel_rate_limit_bytes_throttled_total` while nothing is delayed. This is\n  the \"measure before you enforce\" mode; size the budget here first. A limit\n  that never moves the counter is not binding; one that moves it constantly is\n  tighter than the link.\n- **`enforce`** does not drop and does not close the tunnel. Dropping a\n  `TUNNEL_DATA` frame would punch a hole in a lossless byte stream that neither\n  end can detect. Instead the server **defers** the frame into a per-friend FIFO\n  and replays it, in arrival order, as the bucket refills. Every byte arrives —\n  just later.\n- **Why the queue does not grow without bound.** A deferred frame never reaches\n  its tunnel, so no `TUNNEL_ACK` is emitted for it, so the peer's send window\n  fills and the peer stops sending. The throttle propagates back to the origin\n  TCP socket instead of being absorbed by local memory.\n- **Ordering.** Total for tunnel-lifecycle frames (`TUNNEL_OPEN`, `DATA`,\n  `CLOSE`, `ERROR`, resume opcodes) — a `TUNNEL_CLOSE` that overtook deferred\n  data would strand it. `PING` / `PONG` (keepalive, or a healthy peer gets\n  declared dead), `TUNNEL_ACK` (window credit for the *other* direction) and\n  `INFO_REQUEST` / `INFO_REPLY` deliberately bypass the queue.\n- **The limitation to state up front.** A receiver-side deferral cannot hold an\n  average rate against a peer that ignores flow control; against such a peer it\n  degrades to bursts capped by a 32 MiB per-friend memory rail. Both that rail\n  and a per-frame release deadline (derived from the reaper timeouts, ≤ 60 s)\n  fail **open**: the backlog is released early, in order, with a `warn` line —\n  the budget is briefly exceeded, nothing is lost. So this is a bandwidth budget\n  for cooperative peers, not a defence against a hostile one.\n  `max_concurrent_tunnels` and `open_per_sec` are the anti-DoS knobs.\n- **Engaging it.** Both keys must be non-zero — a refill rate with no capacity\n  holds no tokens. `bytes_burst: 0` is the way to exempt a friend; a non-zero\n  value below 65535 is raised to 65535, and both fields are clamped to 1 GB/s.\n  The daemon enforces the clamped values, not the numbers in the file, and it\n  does not echo them anywhere — `toxtunnel inspect` carries no rate-limit\n  state at all, so do the arithmetic yourself rather than expecting the daemon\n  to confirm it. (`docs/CONFIGURATION.md` claims `inspect` reports the clamped\n  budget; as of v0.4.11 it does not.)\n\nLog lines worth knowing:\n\n```\nInbound byte throttle engaged for friend <N> (rate_limit.bytes_per_sec)\nInbound byte throttle engaged|disengaged for friend <N>        # after a reload\nFriend <N> reached the inbound throttle backlog rail (<B> bytes deferred); ...\nFriend <N>: a deferred frame reached its release deadline ...\n```\n\n### Tunnel resume (opt-in, live in v0.4.x)\n\n```yaml\ntunnel:\n  resume:\n    enabled: false             # opt-in; default off. Live in v0.4.x: opcodes\n                                # 0x08 / 0x09 are wire-active only when enabled.\n    max_age_seconds: 300        # in-memory hold window (see below)\n    on_gap: passthrough\n```\n\n**What `max_age_seconds` actually governs:** when a friend disconnects, the\nserver keeps that friend's `TunnelManager` — its tunnels *and* their live target\nTCP connections — parked in memory behind a timer of this length, logged as\n`Holding tunnel manager for friend N for resume (up to Ns)`. If the friend\nreconnects inside the window, `TUNNEL_RESUME_REQUEST` reattaches to those live\nobjects. If the timer expires first, the manager is dropped and every held\ntunnel closed. It is **not** a pruning window over persisted entries.\n\n**Nothing about resume is written to disk.** There is no on-disk resume state,\nwhich is exactly why the feature cannot survive a process restart on either side\n— a restart destroys the held sockets along with the process. Live-reconnect\nonly. If a user wants an SSH session to survive a server restart, no ToxTunnel\nsetting delivers that; point them at `tmux`/`screen` or `mosh` instead.\n\nFile v0.4.14:examples/db-migration.md\n\n# Database Migration Window via ToxTunnel\n\n## Scenario\n\nA DBA needs a secure tunnel to a production or staging database for a migration,\ndata transfer, or bulk operation. The tunnel should be strictly time-limited and\nlogged.\n\n> **What \"audited\" means here.** ToxTunnel logs *tunnel* activity: which friend\n> opened a tunnel, to which `host:port`, when it closed, and how many bytes\n> flowed. Even at `level: debug` it never sees inside the stream — the payload is\n> an opaque TCP byte stream to it, so **no SQL statement, table name, row count\n> or transaction is ever recorded**. If the migration needs statement-level\n> accountability, turn on the database's own auditing (`pgaudit` or\n> `log_statement = 'all'` for PostgreSQL, the audit plugin / general query log\n> for MySQL) — that is the only place query-level evidence exists. Say this to\n> anyone who asks for \"an audit trail of the migration\".\n\n## Topology\n\n```\nDBA Workstation (client)              DB Server (server)\n────────────────────────              ──────────────────\npg_dump / migration tool              PostgreSQL on :5432\n  → 127.0.0.1:15432                          ↑\n        ↓                                    ↑\n  toxtunnel client                    toxtunnel server\n        ↓                                    ↑\n        └──── Tox P2P encrypted tunnel ──────┘\n```\n\n## Pre-Migration Checklist\n\n- [ ] Create a **temporary database user** with minimum required permissions\n  - Read-only for verification: `CREATE USER migration_ro WITH PASSWORD '...' LOGIN; GRANT SELECT ON ALL TABLES IN SCHEMA public TO migration_ro;`\n  - Read-write for migration: `CREATE USER migration_rw WITH PASSWORD '...' LOGIN; GRANT ALL ON ALL TABLES IN SCHEMA public TO migration_rw;`\n- [ ] Back up the database before starting\n- [ ] Agree on a maintenance window with stakeholders\n- [ ] Test the migration on a staging copy first\n\n## Server Config\n\n```yaml\nmode: server\ndata_dir: /var/lib/toxtunnel        # mutable state — NOT under /etc\nlogging:\n  level: debug                       # verbose TUNNEL-level record; not SQL\n  file: /var/log/toxtunnel/migration.log\ntox:\n  udp_enabled: true\n  bootstrap_mode: auto\nserver:\n  rules_file: /etc/toxtunnel/rules.yaml\n```\n\n> **`data_dir` holds mutable state, so keep it out of `/etc`.** That directory\n> carries the Tox identity (`tox_save.dat`), the pid file, the data-directory\n> lock and the inspect socket — all written at runtime. `/var/lib/toxtunnel` is\n> what the packaged Linux unit uses (`StateDirectory=toxtunnel`,\n> `StateDirectoryMode=0750`), owned by the dedicated `toxtunnel` user; macOS\n> equivalent is `/usr/local/var/toxtunnel`. Keep `/etc/toxtunnel` for\n> `server.yaml` and `rules.yaml` only.\n>\n> **Run the daemon as that unprivileged account, not as root.** Nothing here\n> needs root once the package is installed: the Tox port is 33445 and the targets\n> are ordinary services. The packaged unit already does this; a hand-written one\n> must set `User=`, `Group=` and `StateDirectory=` itself.\n\n\n## Rules (DBA's friend key, DB port only)\n\n```yaml\nrules:\n  # DBA: migration window — remove after completion\n  - friend: \"AABBCCDD...dba-64-char-hex-public-key...EEFF\"\n    allow:\n      - host: \"127.0.0.1\"\n        ports: [5432]\n```\n\n> The `friend` value is the **first 64 hex characters** of that peer's 76-char Tox ID\n> (from its `Client Tox ID: …` startup log line, or `toxtunnel print-id -c client.yaml`),\n> not the whole ID. A wrong length is rejected when the rules file loads\n> (`Invalid public key length: expected 64`).\n\n## Client Config (DBA's workstation)\n\n```yaml\nmode: client\ndata_dir: ~/.config/toxtunnel/client\nlogging:\n  level: info\ntox:\n  udp_enabled: true\n  bootstrap_mode: auto\nclient:\n  server_id: <PASTE_SERVER_TOX_ID_HERE>\n  forwards:\n    - local_port: 15432\n      local_address: 127.0.0.1\n      remote_host: 127.0.0.1\n      remote_port: 5432\n```\n\n> ### ⚠️ `local_port: 15432` binds `0.0.0.0` without `local_address` on the DBA workstation\n>\n> Without `local_address` (v0.4.13+) a forward has no bind restriction — the listener binds every IPv4\n> interface, so anyone on the DBA's network can reach the production database\n> through that port. On **v0.4.13+** set `local_address: 127.0.0.1` (the config above does); on v0.4.12 and older no such key exists — firewall it instead. Firewall it to loopback for\n> the duration of the migration window.\n\n## Migration Workflow\n\n### Phase 1: Verify Connectivity (read-only user)\n```bash\npsql -h 127.0.0.1 -p 15432 -U migration_ro -d mydb -c \"SELECT count(*) FROM important_table;\"\n```\n\n### Phase 2: Run Migration (read-write user)\n```bash\n# Example: run migration script\npsql -h 127.0.0.1 -p 15432 -U migration_rw -d mydb -f migration.sql\n\n# Example: pg_dump / pg_restore\npg_dump -h 127.0.0.1 -p 15432 -U migration_rw -d old_db | psql -h 127.0.0.1 -p 15432 -U migration_rw -d new_db\n```\n\n### Phase 3: Verify Results (read-only user)\n```bash\npsql -h 127.0.0.1 -p 15432 -U migration_ro -d mydb -c \"SELECT count(*) FROM migrated_table;\"\n```\n\n## Bandwidth Considerations\n\n- Tox tunnels have limited throughput compared to direct network connections\n- For large data transfers (>1 GB), consider:\n  - Tox may use a TCP relay if direct UDP is not established. Check which one you\n    got: `last_connection_type` in `<data_dir>/known_servers.yaml` (or\n    `toxtunnel servers list`) — `udp` is direct, `tcp` is a relay and caps bulk\n    throughput at roughly 5–10 KB/s. The log line\n    `Self connection status: connected (UDP|TCP)` describes this daemon's DHT\n    link, not the per-friend path.\n  - Compress data before transfer: `pg_dump ... | gzip | ...`\n  - Run during off-peak hours to minimize contention\n  - For very large migrations, consider a VPN or direct connection instead\n\n## Post-Migration Cleanup\n\n1. **Verify migration results** using the read-only user\n2. **Remove the DBA's rule** from `rules.yaml`\n3. **Reload the server** (preserves existing tunnels): `sudo systemctl reload toxtunnel` — only restart if a non-reloadable field changed.\n4. **Drop temporary users**:\n   ```sql\n   DROP USER migration_ro;\n   DROP USER migration_rw;\n   ```\n5. **Archive the migration log**: `cp /var/log/toxtunnel/migration.log /var/log/toxtunnel/migration-$(date +%Y%m%d).log`\n6. **Verify application connectivity** — ensure the app still works after migration\n\n## Rollback\n\nIf the migration fails:\n1. Stop the migration immediately\n2. Restore from the pre-migration backup\n3. Keep the tunnel open for debugging if needed\n4. After investigation, close the tunnel and revoke access\n\n## Verification\n\n```bash\nbash scripts/verify.sh 15432 postgres client.yaml\n```\n\nRun this from the skill root (the script lives at `scripts/verify.sh`).\nPassing the client config lets it read `friends_online` from the running\ndaemon instead of guessing from a local TCP accept.\n\n**Judge it by the exit code:** `0` = the remote service answered through the\ntunnel, `2` = local checks passed but end-to-end was **not** proven (do not\nreport this as working), `1` = a check failed.\n\nFile v0.4.14:examples/db-temp-access.md\n\n# Temporary Database Access via ToxTunnel\n\n## Scenario\n\nYou need to give a contractor or team member temporary access to a PostgreSQL (or MySQL/Redis/MongoDB) database on an internal server — without VPN setup, without opening firewall ports, and with the ability to revoke access easily.\n\n## Topology\n\n```\nContractor Laptop (client)            Internal DB Server (server)\n──────────────────────────            ──────────────────────────\npsql -h 127.0.0.1 -p 15432           PostgreSQL on :5432\n        ↓                                    ↑\n  toxtunnel client                    toxtunnel server\n        ↓                                    ↑\n        └──── Tox P2P encrypted tunnel ──────┘\n```\n\n## Server Config (on machine with DB access)\n\n```yaml\nmode: server\ndata_dir: /var/lib/toxtunnel        # mutable state — NOT under /etc\nlogging:\n  level: info\n  file: /var/log/toxtunnel/server.log\ntox:\n  udp_enabled: true\n  bootstrap_mode: auto\nserver:\n  rules_file: /etc/toxtunnel/rules.yaml\n```\n\n> **`data_dir` holds mutable state, so keep it out of `/etc`.** That directory\n> carries the Tox identity (`tox_save.dat`), the pid file, the data-directory\n> lock and the inspect socket — all written at runtime. `/var/lib/toxtunnel` is\n> what the packaged Linux unit uses (`StateDirectory=toxtunnel`,\n> `StateDirectoryMode=0750`), owned by the dedicated `toxtunnel` user; macOS\n> equivalent is `/usr/local/var/toxtunnel`. Keep `/etc/toxtunnel` for\n> `server.yaml` and `rules.yaml` only.\n>\n> **Run the daemon as that unprivileged account, not as root.** Nothing here\n> needs root once the package is installed: the Tox port is 33445 and the targets\n> are ordinary services. The packaged unit already does this; a hand-written one\n> must set `User=`, `Group=` and `StateDirectory=` itself.\n\n\n## Client Config (sent to contractor)\n\n```yaml\nmode: client\ndata_dir: ~/.config/toxtunnel/client\nlogging:\n  level: info\ntox:\n  udp_enabled: true\n  bootstrap_mode: auto\nclient:\n  server_id: <PASTE_SERVER_TOX_ID_HERE>\n  forwards:\n    - local_port: 15432\n      local_address: 127.0.0.1\n      remote_host: 127.0.0.1\n      remote_port: 5432\n```\n\n> ### ⚠️ `local_port: 15432` binds `0.0.0.0` without `local_address` on the contractor's laptop\n>\n> Without `local_address` the listener binds every IPv4\n> interface, so every host on whatever network the contractor is using can reach\n> your database through their machine, with only the DB's own authentication in\n> front of it. On **v0.4.13+** set `local_address: 127.0.0.1` (the config above does); on v0.4.12 and older no such key exists — firewall it instead.\n>\n> Require the contractor to firewall the port to loopback\n> (`sudo ufw deny in to any port 15432`, an nftables/pf rule, or\n> `New-NetFirewallRule … -Action Block`) as a condition of access, and keep the\n> DB credentials scoped and short-lived regardless.\n\n## Rules (locked down — contractor's friend key only)\n\n```yaml\nrules:\n  - friend: \"AABBCCDD...contractor-64-char-hex-public-key...EEFF\"\n    allow:\n      - host: \"127.0.0.1\"\n        ports: [5432]\n```\n\nThe `friend` value is the **first 64 hex characters** of the peer's 76-char Tox ID — not the whole ID. They get it from their own daemon's startup log line `Client Tox ID: <76 hex>`, or from `toxtunnel print-id -c client.yaml`. A wrong length is rejected when the rules file loads (`Invalid public key length: expected 64`), so the server refuses to start or hot-reload rather than silently denying.\n\n## Steps\n\n1. Start server with rules\n2. Share the server Tox ID with the contractor (via secure channel)\n3. Contractor installs toxtunnel and starts client. Optionally, the contractor\n   registers an alias once so subsequent runs only need the short name:\n   `toxtunnel servers add client-db <SERVER_TOX_ID>`\n4. Wait for friend connection\n5. Contractor connects: `psql -h 127.0.0.1 -p 15432 -U db_user -d mydb`\n\n## Revocation\n\n**To revoke access:**\n1. Remove the contractor's entry from `rules.yaml`\n2. **Reload** the server so existing tunnels survive but the next\n   TUNNEL_OPEN from the revoked friend is denied:\n   `sudo systemctl reload toxtunnel` (POSIX, sends SIGHUP) or\n   `toxtunnel reload` (cross-platform; uses the local IPC).\n   Use `sudo systemctl restart toxtunnel` only if the change touched a\n   non-reloadable field.\n3. The contractor's NEW tunnel attempts are denied immediately — but a reload\n   does **not** cut sessions that are already open. A `psql` connection the\n   contractor established before the reload keeps working until they\n   disconnect. If you need to end live access this second, restart the server\n   (`sudo systemctl restart toxtunnel`), which drops every tunnel, or revoke at\n   the database (`ALTER ROLE … NOLOGIN` + terminate their backends).\n\nAlso consider:\n- Drop the temporary database user\n- Review audit logs for any unexpected queries\n\n## Other Databases\n\n**MySQL:**\n```yaml\nforwards:\n  - local_port: 13306\n    local_address: 127.0.0.1\n    remote_host: 127.0.0.1\n    remote_port: 3306\n```\nRules ports: `[3306]`\nTest: `mysql -h 127.0.0.1 -P 13306 -u db_user -p`\n\n**Redis:**\n```yaml\nforwards:\n  - local_port: 16379\n    local_address: 127.0.0.1\n    remote_host: 127.0.0.1\n    remote_port: 6379\n```\nRules ports: `[6379]`\nTest: `redis-cli -h 127.0.0.1 -p 16379 ping`\n\n**MongoDB:**\n```yaml\nforwards:\n  - local_port: 17017\n    loca\n\nArchive v0.4.13: 18 files, 76937 bytes\n\nFiles: examples/db-migration.md (4697b), examples/db-temp-access.md (4473b), examples/dev-expose.md (4226b), examples/local-loopback-test.md (6907b), examples/nas-expose.md (3821b), examples/prometheus-monitoring.md (5804b), examples/rdp-remote.md (3416b), examples/socks5-browser-proxy.md (7068b), examples/ssh-remote.md (3586b), examples/temp-maintenance.md (3762b), examples/web-forward.md (3610b), references/diagnose.md (30931b), references/execute.md (26260b), scripts/diagnose.sh (18317b), scripts/verify.sh (6700b), skill-card.md (2613b), SKILL.md (48050b), _meta.json (134b)\n\nArchive v0.4.12: 18 files, 62964 bytes\n\nFiles: examples/db-migration.md (4101b), examples/db-temp-access.md (3807b), examples/dev-expose.md (3928b), examples/local-loopback-test.md (6527b), examples/nas-expose.md (3513b), examples/prometheus-monitoring.md (5798b), examples/rdp-remote.md (2928b), examples/socks5-browser-proxy.md (6094b), examples/ssh-remote.md (3031b), examples/temp-maintenance.md (3442b), examples/web-forward.md (3312b), references/diagnose.md (17170b), references/execute.md (20006b), scripts/diagnose.sh (15758b), scripts/verify.sh (5802b), skill-card.md (2448b), SKILL.md (43335b), _meta.json (134b)\n\nArchive v0.4.10: 18 files, 62953 bytes\n\nFiles: examples/db-migration.md (4101b), examples/db-temp-access.md (3807b), examples/dev-expose.md (3928b), examples/local-loopback-test.md (6527b), examples/nas-expose.md (3513b), examples/prometheus-monitoring.md (5798b), examples/rdp-remote.md (2928b), examples/socks5-browser-proxy.md (6094b), examples/ssh-remote.md (3031b), examples/temp-maintenance.md (3442b), examples/web-forward.md (3312b), references/diagnose.md (17170b), references/execute.md (20006b), scripts/diagnose.sh (15758b), scripts/verify.sh (5802b), skill-card.md (2440b), SKILL.md (43335b), _meta.json (134b)\n\nArchive v0.4.9: 18 files, 61919 bytes\n\nFiles: examples/db-migration.md (4101b), examples/db-temp-access.md (3807b), examples/dev-expose.md (3928b), examples/local-loopback-test.md (6527b), examples/nas-expose.md (3513b), examples/prometheus-monitoring.md (5798b), examples/rdp-remote.md (2928b), examples/socks5-browser-proxy.md (6094b), examples/ssh-remote.md (3031b), examples/temp-maintenance.md (3442b), examples/web-forward.md (3312b), references/diagnose.md (15536b), references/execute.md (19650b), scripts/diagnose.sh (15758b), scripts/verify.sh (5802b), skill-card.md (2923b), SKILL.md (42449b), _meta.json (133b)\n\nArchive v0.4.8: 17 files, 58093 bytes\n\nFiles: _meta.json (133b), examples/db-migration.md (4101b), examples/db-temp-access.md (3807b), examples/dev-expose.md (3928b), examples/nas-expose.md (3513b), examples/prometheus-monitoring.md (5798b), examples/rdp-remote.md (2928b), examples/socks5-browser-proxy.md (6094b), examples/ssh-remote.md (3031b), examples/temp-maintenance.md (3442b), examples/web-forward.md (3312b), references/diagnose.md (14706b), references/execute.md (19674b), scripts/diagnose.sh (15758b), scripts/verify.sh (5802b), skill-card.md (2884b), SKILL.md (42068b)\n\nArchive v0.4.7: 17 files, 58027 bytes\n\nFiles: examples/db-migration.md (4101b), examples/db-temp-access.md (3807b), examples/dev-expose.md (3928b), examples/nas-expose.md (3513b), examples/prometheus-monitoring.md (5798b), examples/rdp-remote.md (2928b), examples/socks5-browser-proxy.md (6094b), examples/ssh-remote.md (3031b), examples/temp-maintenance.md (3442b), examples/web-forward.md (3312b), references/diagnose.md (14706b), references/execute.md (19674b), scripts/diagnose.sh (15758b), scripts/verify.sh (5802b), skill-card.md (2494b), SKILL.md (42104b), _meta.json (133b)\n\nArchive v0.4.6: 17 files, 57951 bytes\n\nFiles: examples/db-migration.md (4101b), examples/db-temp-access.md (3807b), examples/dev-expose.md (3928b), examples/nas-expose.md (3513b), examples/prometheus-monitoring.md (5798b), examples/rdp-remote.md (2928b), examples/socks5-browser-proxy.md (6094b), examples/ssh-remote.md (3031b), examples/temp-maintenance.md (3442b), examples/web-forward.md (3312b), references/diagnose.md (14706b), references/execute.md (19674b), scripts/diagnose.sh (15758b), scripts/verify.sh (5802b), skill-card.md (2511b), SKILL.md (42068b), _meta.json (133b)\n\nArchive v0.4.5: 17 files, 56978 bytes\n\nFiles: examples/db-migration.md (4101b), examples/db-temp-access.md (3807b), examples/dev-expose.md (3928b), examples/nas-expose.md (3513b), examples/prometheus-monitoring.md (5798b), examples/rdp-remote.md (2928b), examples/socks5-browser-proxy.md (6094b), examples/ssh-remote.md (3031b), examples/temp-maintenance.md (3442b), examples/web-forward.md (3312b), references/diagnose.md (12266b), references/execute.md (19674b), scripts/diagnose.sh (15758b), scripts/verify.sh (5802b), skill-card.md (2730b), SKILL.md (42010b), _meta.json (133b)\n\nArchive v0.4.1: 16 files, 55067 bytes\n\nFiles: examples/db-migration.md (4029b), examples/db-temp-access.md (3547b), examples/dev-expose.md (3928b), examples/nas-expose.md (3513b), examples/prometheus-monitoring.md (5798b), examples/rdp-remote.md (2928b), examples/socks5-browser-proxy.md (6031b), examples/ssh-remote.md (2976b), examples/temp-maintenance.md (3449b), examples/web-forward.md (3312b), references/diagnose.md (12255b), references/execute.md (19671b), scripts/diagnose.sh (15728b), scripts/verify.sh (5802b), SKILL.md (41278b), _meta.json (133b)","readmeExcerpt":"Skill: tox-tunnel-ops Owner: agentx-icu Summary: Encrypted P2P TCP tunneling for remote network access — a self-hosted VPN / ngrok / Tailscale alternative built on the Tox protocol (libsodium). No API keys, no accounts, no central servers, no port-forwarding. Solves NAT traversal, carrier-grade NAT, double NAT, intranet penetration (内网穿透), and remote machine access without router or firewall changes. Tunnels SSH, RDP","codeSnippets":[],"executableExamples":[{"language":"text","snippet":"Client Machine                          Server Machine\n─────────────────                       ─────────────────\nApp → localhost:LOCAL_PORT              target_host:target_port ← App\n        ↓                                      ↑\n   TunnelClient                           TunnelServer\n        ↓                                      ↑\n   [Tox P2P encrypted tunnel]  ──────→  [Tox P2P]"},{"language":"yaml","snippet":"mode: server\ndata_dir: /path/to/data\nlogging:\n  level: info\ntox:\n  udp_enabled: true\n  tcp_port: 33445\n  bootstrap_mode: auto    # auto | lan\nserver:\n  rules_file: /path/to/rules.yaml   # access-control rules; unset = default deny\n\n# v0.3.0 top-level blocks (all opt-in unless noted):\nmetrics:\n  enabled: false                    # opt-in; enables Prometheus /metrics endpoint\n  listen: 127.0.0.1:9100            # use 0.0.0.0:9100 only behind a trusted network\n  path: /metrics                    # must start with '/'\ninspect:\n  enabled: true                     # default-on; serves a Unix socket / named pipe for `toxtunnel inspect`\ntunnel:\n  coalesce_max_delay_us: 200        # default-on small-write coalescing (perf, benign to leave)\n                                    # Windows: sub-15.6 ms values are treated as 0 (no batching)\n  coalesce_max_bytes: 1362          # flush threshold (≤ Tox 1367-byte frame limit)\n  coalesce_mode: fixed              # v0.4: fixed (default) | adaptive | bypass | drain\n  idle_timeout_seconds: 0           # 0 = disabled; e.g. 900 closes tunnels idle for 15 min\n  reaper_tick_seconds: 10           # reaper wake-up interval\n  half_close_timeout_seconds: 120   # default-on; force-close a tunnel stuck in\n                                    # Disconnecting this long. 0 disables.\n  resume:                           # v0.4: tunnel fast-reattach. Opt-in.\n    enabled: false                  # default false; opcodes wire-inactive when off\n    max_age_seconds: 300            # how long the server holds a disconnected\n                                    # friend's IN-MEMORY tunnels (and their target\n                                    # TCP connections) waiting for a reconnect.\n                                    # Nothing is persisted to disk.\n    on_gap: passthrough             # passthrough (default) | close\n\n# v0.4 stability blocks. Defaults preserve v0.3.0 semantics EXCEPT\n# flow_control.mode, which defaults to `bdp` since v0.4.1:\nwatchdog:\n  enable"},{"language":"yaml","snippet":"mode: client\ndata_dir: /path/to/data\nlogging:\n  level: info\ntox:\n  udp_enabled: true\n  bootstrap_mode: auto\nclient:\n  # Single ID (Tox ID OR known-servers alias):\n  server_id: <76-char-tox-id-or-alias>\n  # ...OR a list for multi-server failover (entry 0 is primary, 1..N are fallbacks):\n  # server_id:\n  #   - primary-homelab\n  #   - hetzner-fallback\n  #   - <full-76-char-tox-id>\n  # ...OR keep server_id a scalar and name the fallbacks separately. Both\n  # spellings are accepted and are ADDITIVE (a list server_id plus this key\n  # yields primary = entry 0, fallbacks = the rest + these):\n  # fallback_server_ids:\n  #   - hetzner-fallback\n  #   - <full-76-char-tox-id>\n  # Without local_address a forward binds 0.0.0.0 (all IPv4). See below.\n  forwards:\n    - local_port: 2222\n      local_address: 127.0.0.1     # v0.4.13+; drop only to serve other machines\n      remote_host: 127.0.0.1\n      remote_port: 22\n\n  # Optional: multi-server failover policy. Applies when server_id is a list,\n  # or when `fallback_server_ids` (below) is set alongside a scalar server_id.\n  failover:\n    timeout_seconds: 60                   # how long primary must stay offline before promotion\n    prefer_primary_grace_seconds: 30      # how long primary must be online before switching back\n\n  # Optional: SOCKS5 / HTTP CONNECT listener for dynamic destinations.\n  # Server-side rules.yaml STILL enforces what targets are reachable.\n  socks5:\n    enabled: false\n    listen: 127.0.0.1:1080                # MUST be a loopback address; config validator rejects others\n\n  # Optional pipe mode (SSH ProxyCommand) — POSIX only, not supported on Windows:\n  # pipe:\n  #   remote_host: 127.0.0.1\n  #   remote_port: 22"},{"language":"yaml","snippet":"rules:\n  - friend: \"AABB...64hex...\"       # exact 64-char hex public key\n    allow:\n      - host: \"127.0.0.1\"\n        ports: [22, 80, 443]        # specific ports\n      - host: \"*.internal.lan\"\n        ports: []                    # empty = ALL ports\n    deny:\n      - host: \"10.*\"\n        ports: []                    # deny all ports on 10.* range"},{"language":"text","snippet":"toxtunnel -m server -c server.yaml\ntoxtunnel -m client -c client.yaml\ntoxtunnel -m client --server-id <ID|alias> --server-id-fallback <ID2> <ID3>  # multi-server failover\ntoxtunnel -m client --server-id <ID|alias> --pipe <host:port>   # pipe mode (SSH ProxyCommand)\ntoxtunnel -m client --server-id <ID|alias> --socks5 127.0.0.1:1080  # dynamic destinations (loopback only)\ntoxtunnel print-id [-d DATA_DIR] [--qr] [--color]               # print/display Tox ID\ntoxtunnel servers list [--full] [-d DIR | -c CONFIG]            # list known servers\ntoxtunnel servers show <alias_or_id> [-d DIR | -c CONFIG]       # show one server's record\ntoxtunnel servers add   <alias> <tox_id> [--notes \"...\"]        # register alias for a Tox ID\ntoxtunnel servers remove <alias_or_id>                          # forget a server\ntoxtunnel inspect [tunnels|status] [--json] [-d DIR | -c CONFIG]  # live introspection via local IPC\ntoxtunnel reload [-d DIR | -c CONFIG]                           # trigger hot-reload (Windows-friendly SIGHUP)\ntoxtunnel config check -c FILE [--strict]                       # validate a config + list ignored/unknown keys"},{"language":"bash","snippet":"toxtunnel inspect status --json | jq .\ntoxtunnel inspect tunnels"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: tox-tunnel-ops\ndescription: \"Encrypted P2P TCP tunneling for remote network access — a self-hosted VPN / ngrok / Tailscale alternative built on the Tox protocol (libsodium). No API keys, no accounts, no central servers, no port-forwarding. Solves NAT traversal, carrier-grade NAT, double NAT, intranet penetration (内网穿透), and remote machine access without router or firewall changes. Tunnels SSH, RDP/VNC desktops, database connections (PostgreSQL/MySQL/Redis/MongoDB), homelab/NAS access (Synology, TrueNAS), local dev servers, and arbitrary TCP ports. Use when: setting up remote SSH/RDP/MySQL/PostgreSQL/Redis/MongoDB access from anywhere, exposing a local dev server or internal web app, sharing a homelab/Synology/TrueNAS service, granting time-scoped contractor access, generating ToxTunnel server/client/rules YAML configs, diagnosing toxtunnel connection failures, tightening rules.yaml access control, running a loopback SOCKS5 / HTTP CONNECT listener through a Tox tunnel, exporting toxtunnel operational metrics into Prometheus / Grafana, hot-reloading rules without restart (SIGHUP / `toxtunnel reload`), inspecting live tunnel state via `toxtunnel inspect`, or wiring multi-server failover for production redundancy.\"\nmetadata:\n  openclaw:\n    requires:\n      bins: [\"toxtunnel\"]\n      env: []\n    emoji: \"🔒\"\n    homepage: \"https://github.com/agentx-icu/tox-tcp-tunnel\"\n    os: [\"darwin\", \"linux\", \"win32\"]\n---\n\n# tox-tunnel-ops\nYou are a ToxTunnel operations specialist. You help users design, deploy, and diagnose TCP tunnels over the Tox P2P network using **tox-tcp-tunnel**.\n\nProject links:\n- GitHub repository: `https://github.com/agentx-icu/tox-tcp-tunnel`\n- Releases: `https://github.com/agentx-icu/tox-tcp-tunnel/releases`\n\n## What This Skill Does\nThis skill helps you create **secure, encrypted TCP tunnels** that work behind NATs and firewalls without any central server. Common use cases:\n\n- **Remote SSH access** — connect to a home or office machine from anywhere, no port forwarding needed\n- **Remote desktop (RDP/VNC)** — access Windows/Linux desktops through encrypted P2P tunnel\n- **Database tunnel** — securely connect to PostgreSQL, MySQL, Redis, MongoDB through a private tunnel\n- **Web service exposure** — share a local dev server or internal web app with teammates\n- **NAS / homelab remote access** — access Synology, TrueNAS, or any home server from outside the LAN\n- **Intranet penetration** — bypass corporate or carrier-grade NAT without VPN infrastructure\n- **Temporary contractor access** — grant time-scoped, auditable access to specific services, revocable without a restart via hot-reload\n- **Air-gapped / LAN-only networking** — works entirely on local network without internet\n- **Dynamic browsing / debugging proxy** — point a browser, curl, or DB client at a loopback SOCKS5 / HTTP CONNECT listener instead of enumerating every destination in YAML\n- **Production HA** — multi-server failover (one primary, ordered fallbacks) for tunnels tha"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7bamw47ka1ke1kfr1b857nmn835vyp\",\n  \"slug\": \"tox-tunnel-ops\",\n  \"version\": \"0.4.14\",\n  \"publishedAt\": 1788185250591\n}"},{"path":"references/diagnose.md","content":"# Diagnose Reference\n\nUse this reference when an existing tunnel does not work and you need a layered,\nevidence-driven troubleshooting flow.\n\n## Diagnostic Layers\n\nRun through these layers in order. Stop at the first failure and propose a fix.\n\n### Layer 1: Process & Binary\n\n- Is `toxtunnel` installed? (`which toxtunnel`)\n- Is it running? (`ps aux | grep toxtunnel` / `Get-Process toxtunnel`)\n- Which config file is it using? What mode?\n- What version? (≥ v0.3.0 unlocks the inspect/reload/metrics short-circuits below)\n\n**Prefer `inspect` over log tailing for live state.** If the daemon is up\nand v0.3.0+, this single command answers Layers 1, 4, and 5 in one shot:\n\n```bash\ntoxtunnel inspect status --json | jq .\ntoxtunnel inspect tunnels\n```\n\nLook for: `mode`, `version`, `friends_online`, `peer_online_seconds` (client),\n`tunnels_active`, `bytes_in`, `bytes_out` — that is the complete field set.\n`friends_online: 0` points at Layer 4; friends online with no tunnels points at\nLayer 5. (There is no `active_server`, `pid` or `uptime` field, and `inspect`\ntakes only `tunnels` / `status`.)\n\n### Layer 2: Configuration Static Check\n\n**Start here, always:**\n\n```bash\ntoxtunnel config check -c /path/to/config.yaml --strict\n```\n\nThis is the daemon's own validator (v0.4.11+). Exit `0` = usable, `1` =\nunloadable / invalid / (with `--strict`) carrying keys the daemon would silently\nignore. Anything it reports is authoritative — fix it before investigating\nanything else, and do not hand-audit YAML that it has not seen.\n\nBlind spots you must cover by hand (verified against v0.4.12; the alias one is\nclosed from v0.4.13):\n\n1. **It never opens `server.rules_file`.** A server config pointing at a\n   nonexistent or malformed rules file still prints `is valid`. Rules problems\n   surface only when the daemon loads them (Layer 3).\n2. **On v0.4.12 and older it does not resolve known-servers aliases.** There an\n   alias-form `client.server_id` fails with\n   `Server ID must be 76 characters, got N`, even when the alias is registered\n   and the daemon runs fine — confirm with `toxtunnel servers list -c <config>`\n   before treating that one message as an error. **v0.4.13+ resolves aliases**\n   at startup, on reload and in `config check`, so this gap does not apply\n   there.\n\n`bash scripts/diagnose.sh <config>` runs the validator and then covers whichever\nof these gaps apply to the daemon in front of you.\n\nThen check by hand:\n\n- Is `mode` set correctly?\n- Does `data_dir` exist and is it writable?\n- Does `tox_save.dat` exist? (first run creates it)\n- Client-specific:\n  - Is `server_id` set?\n  - Is `server_id` not the placeholder `<PASTE_SERVER_TOX_ID_HERE>`?\n  - If `server_id` is exactly 76 hex characters → treat as literal Tox ID.\n  - If `server_id` is shorter → treat as an alias and check that\n    `<data_dir>/known_servers.yaml` exists and contains an entry whose `alias:`\n    matches. (`toxtunnel servers list -d <data_dir>` resolves this quickly.)\n    A non-76-char `server_id` wit"},{"path":"references/execute.md","content":"# Execute Reference\n\nUse this reference when the user wants to deploy a ToxTunnel setup, install the\nbinary, start processes, or configure service persistence.\n\n## Step 0: Environment Detection\n\nRun these checks before writing files or starting anything:\n\n### 1. Is `toxtunnel` installed?\n\n```bash\nwhich toxtunnel 2>/dev/null || where toxtunnel 2>nul\n```\n\nIf not found, prefer package installation over source builds.\n\n#### Preferred: version-pinned native package\n\nThe canonical, always-current install instructions are the \"Installation\"\nsection of the repo's [`README.md`](https://github.com/agentx-icu/tox-tcp-tunnel#installation);\nif this file and the README ever disagree, the README wins. The newest release\nat the time of writing is **v0.4.12** — check the Releases page for the current\none rather than trusting this number.\n\n**When you are installing on an operator's machine, do not pipe a script from a\nmutable branch into `sudo sh`.** You have already detected OS and architecture,\nso you can do the installer's actual job — pick the right asset, hand it to the\npackage manager — from a version-pinned URL. This avoids piping a mutable remote\nscript into a root shell. It is **not** \"no remote code as root\": installing a\nDEB/RPM/PKG/MSI still runs that package's maintainer scripts with privileges.\nWhat changes is that the code executed is the released package, pinned to a\nversion you chose, rather than whatever `master` holds at that moment:\n\n```bash\nVER=0.4.12; ARCH=x86_64          # or aarch64\nBASE=\"https://github.com/agentx-icu/tox-tcp-tunnel/releases/download/v${VER}\"\n\n# Linux (DEB - Ubuntu/Debian)\ncurl -fsSL -o \"/tmp/toxtunnel-${VER}.deb\" \"${BASE}/toxtunnel-${VER}-Linux-${ARCH}.deb\"\nsudo apt-get install -y \"/tmp/toxtunnel-${VER}.deb\"\n\n# Linux (RPM - Fedora/RHEL/CentOS)\ncurl -fsSL -o \"/tmp/toxtunnel-${VER}.rpm\" \"${BASE}/toxtunnel-${VER}-Linux-${ARCH}.rpm\"\nsudo rpm -i \"/tmp/toxtunnel-${VER}.rpm\"\n\n# macOS (ARCH=arm64 or x86_64)\ncurl -fsSL -o \"/tmp/toxtunnel-${VER}.pkg\" \"${BASE}/toxtunnel-${VER}-Darwin-${ARCH}.pkg\"\nsudo installer -pkg \"/tmp/toxtunnel-${VER}.pkg\" -target /\n```\n\n```powershell\n# Windows (Administrator PowerShell); ARM: toxtunnel-$VER-Windows-ARM64.msi\n$VER='0.4.12'\nirm \"https://github.com/agentx-icu/tox-tcp-tunnel/releases/download/v$VER/toxtunnel-$VER-Windows-AMD64.msi\" -OutFile \"$env:TEMP\\toxtunnel.msi\"\nmsiexec /i \"$env:TEMP\\toxtunnel.msi\" /qn\n```\n\n**Integrity, stated accurately.** No `.sha256` or signature assets are\npublished — the release carries the packages and their `-latest` aliases. But\nGitHub exposes a SHA-256 digest for every release asset regardless, so there IS\nsomething to verify against: compare the downloaded file's digest with the one\nGitHub reports for that asset. What is missing is independent\nsignature/provenance — the digest and the file come from the same party, so it\ndetects corruption and truncation, not a compromised release. Note also that a\nrelease URL is only immutable if the repository enabled immutable relea"},{"path":"examples/db-migration.md","content":"# Database Migration Window via ToxTunnel\n\n## Scenario\n\nA DBA needs a secure tunnel to a production or staging database for a migration,\ndata transfer, or bulk operation. The tunnel should be strictly time-limited and\nlogged.\n\n> **What \"audited\" means here.** ToxTunnel logs *tunnel* activity: which friend\n> opened a tunnel, to which `host:port`, when it closed, and how many bytes\n> flowed. Even at `level: debug` it never sees inside the stream — the payload is\n> an opaque TCP byte stream to it, so **no SQL statement, table name, row count\n> or transaction is ever recorded**. If the migration needs statement-level\n> accountability, turn on the database's own auditing (`pgaudit` or\n> `log_statement = 'all'` for PostgreSQL, the audit plugin / general query log\n> for MySQL) — that is the only place query-level evidence exists. Say this to\n> anyone who asks for \"an audit trail of the migration\".\n\n## Topology\n\n```\nDBA Workstation (client)              DB Server (server)\n────────────────────────              ──────────────────\npg_dump / migration tool              PostgreSQL on :5432\n  → 127.0.0.1:15432                          ↑\n        ↓                                    ↑\n  toxtunnel client                    toxtunnel server\n        ↓                                    ↑\n        └──── Tox P2P encrypted tunnel ──────┘\n```\n\n## Pre-Migration Checklist\n\n- [ ] Create a **temporary database user** with minimum required permissions\n  - Read-only for verification: `CREATE USER migration_ro WITH PASSWORD '...' LOGIN; GRANT SELECT ON ALL TABLES IN SCHEMA public TO migration_ro;`\n  - Read-write for migration: `CREATE USER migration_rw WITH PASSWORD '...' LOGIN; GRANT ALL ON ALL TABLES IN SCHEMA public TO migration_rw;`\n- [ ] Back up the database before starting\n- [ ] Agree on a maintenance window with stakeholders\n- [ ] Test the migration on a staging copy first\n\n## Server Config\n\n```yaml\nmode: server\ndata_dir: /var/lib/toxtunnel        # mutable state — NOT under /etc\nlogging:\n  level: debug                       # verbose TUNNEL-level record; not SQL\n  file: /var/log/toxtunnel/migration.log\ntox:\n  udp_enabled: true\n  bootstrap_mode: auto\nserver:\n  rules_file: /etc/toxtunnel/rules.yaml\n```\n\n> **`data_dir` holds mutable state, so keep it out of `/etc`.** That directory\n> carries the Tox identity (`tox_save.dat`), the pid file, the data-directory\n> lock and the inspect socket — all written at runtime. `/var/lib/toxtunnel` is\n> what the packaged Linux unit uses (`StateDirectory=toxtunnel`,\n> `StateDirectoryMode=0750`), owned by the dedicated `toxtunnel` user; macOS\n> equivalent is `/usr/local/var/toxtunnel`. Keep `/etc/toxtunnel` for\n> `server.yaml` and `rules.yaml` only.\n>\n> **Run the daemon as that unprivileged account, not as root.** Nothing here\n> needs root once the package is installed: the Tox port is 33445 and the targets\n> are ordinary services. The packaged unit already does this; a hand-written one\n> must set `User=`, `Group=` and `StateDirectory=` i"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":3343,"uniquenessScore":38,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-10T00:09:35.009Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-10T00:09:35.009Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-10T07:42:31.294Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}