Add the native Go client and finish M3 hardening

The new cmd/tunnel client keeps the API URL and tunnel identity in
~/.config/tunnel/config.json (0600), provisions the key/CSR and
certificate, terminates TLS, proxies to local routes, reloads routes
without a restart, and runs as a background service. `tunnel import`
takes over an existing official CLI profile, preserving its tunnel ID,
token, key and certificate.

Server hardening: per-IP and per-tunnel rate limits for creates and
binds, a CA cool-down after repeated ACME failures, a startup sweep for
stale _acme-challenge records, admin metrics, a 60-second half-close
timeout, and separate prioritized control / ordered data bridge queues
(with `end` ordered after the final bytes). The dashboard shows the new
metrics.
This commit is contained in:
2026-10-26 00:32:31 +08:00
parent cba7d2366b
commit ebae420ab1
27 changed files with 3745 additions and 56 deletions
+1 -1
View File
@@ -69,7 +69,7 @@
{#if !token}
<p class="dim">Enter the ADMIN_TOKEN to view the dashboard.</p>
{:else if overview}
<SummaryCards server={overview.server} ca={overview.ca} totals={overview.totals} />
<SummaryCards server={overview.server} ca={overview.ca} totals={overview.totals} metrics={overview.metrics} />
<h2>Tunnels</h2>
<TunnelsTable tunnels={overview.tunnels} />
<h2>Bridges</h2>
+20 -2
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import type { CaInfo, ServerInfo, Totals } from "./api";
import type { CaInfo, Metrics, ServerInfo, Totals } from "./api";
import { fmtDate, fmtUptime } from "./format";
let { server, ca, totals }: { server: ServerInfo; ca: CaInfo; totals: Totals } = $props();
let { server, ca, totals, metrics }: { server: ServerInfo; ca: CaInfo; totals: Totals; metrics: Metrics } = $props();
</script>
<div class="cards">
@@ -10,6 +10,19 @@
<div class="card"><div class="label">Online</div><div class="value">{totals.online}</div></div>
<div class="card"><div class="label">Bridges</div><div class="value">{totals.bridges}</div></div>
<div class="card"><div class="label">Visitors</div><div class="value">{totals.visitors}</div></div>
<div class="card">
<div class="label">Certificates</div>
<div class="value">{metrics.certificates_ready}</div>
<div class="detail">
{metrics.certificates_issuing} issuing · {metrics.certificates_failed} failed
{#if metrics.certificates_retrying > 0}· {metrics.certificates_retrying} retrying{/if}
</div>
</div>
<div class="card">
<div class="label">Issuances</div>
<div class="value">{metrics.issuance_ready}</div>
<div class="detail">{metrics.issuance_attempts} attempts · {metrics.rate_limited} limited</div>
</div>
<div class="card"><div class="label">Local CA expires</div><div class="value small">{fmtDate(ca.expires)}</div></div>
<div class="card"><div class="label">Uptime</div><div class="value small">{fmtUptime(server.uptime_seconds)}</div></div>
</div>
@@ -25,4 +38,9 @@
font-size: 14px;
margin-top: 8px;
}
.detail {
margin-top: 6px;
color: #8b949e;
font-size: 11px;
}
</style>
+14
View File
@@ -19,6 +19,19 @@ export interface Totals {
visitors: number;
}
export interface Metrics {
create_requests: number;
bind_requests: number;
rate_limited: number;
issuance_attempts: number;
issuance_ready: number;
issuance_failed: number;
certificates_ready: number;
certificates_issuing: number;
certificates_failed: number;
certificates_retrying: number;
}
export interface Tunnel {
id: string;
hostname: string;
@@ -46,6 +59,7 @@ export interface Overview {
server: ServerInfo;
ca: CaInfo;
totals: Totals;
metrics: Metrics;
tunnels: Tunnel[] | null;
bridges: Bridge[] | null;
}