feat(agent): auto-renew the host cert in the native run-loop (A5)

Wire createCertRotator/renewCert into superviseNative so the frp-client cert
renews silently at ~2/3 TTL over mTLS /renew and frpc restarts onto the fresh
leaf; failures retry with backoff, never crash the supervisor. mTLS transport
has a 15s timeout + 64KB response cap. 281 tests pass.
This commit is contained in:
Yaojia Wang
2026-07-18 13:32:05 +02:00
parent fff011bb7f
commit 9a5909f672
8 changed files with 815 additions and 6 deletions

View File

@@ -23,6 +23,7 @@ import { runTunnel } from '../transport/runTunnel.js'
import { buildNativeFrpcToml } from '../transport/frpcToml.js'
import { superviseFrpc } from '../transport/frpSupervise.js'
import { provisionFrpc } from '../provision/frpcBinary.js'
import { startNativeAutoRenew } from '../certs/nativeRenew.js'
import {
probeLoopbackBaseApp,
renderHealthStatus,
@@ -156,9 +157,12 @@ export function readFrpcLog(stateDir: string): string {
}
/**
* Native run-loop (B4/H4): supervise the pinned frpc child with restart-on-exit backoff while a
* Native run-loop (B4/H4 + A5): supervise the pinned frpc child with restart-on-exit backoff while a
* periodic health probe (frpc alive, base-app loopback reachable, proxy-started, cert-not-expiring)
* logs NON-SECRET status only (INV9). Resolves when the supervisor stops (SIGTERM/SIGINT).
* logs NON-SECRET status only (INV9), AND auto-renew the frp-client leaf at ~2/3 TTL so the tunnel
* never drops on cert expiry (A5): a successful renewal restarts frpc onto the fresh leaf, a 403
* revoke tears the tunnel down, and a failed renewal retries with backoff without crashing the
* supervisor. Resolves when the supervisor stops (SIGTERM/SIGINT).
*/
function superviseNative(cfg: AgentConfig, ks: Keystore): Promise<number> {
const logger = createLogger('info')
@@ -184,12 +188,28 @@ function superviseNative(cfg: AgentConfig, ks: Keystore): Promise<number> {
for (const line of renderHealthStatus(ids, report)) logger.log('info', line)
},
)
// A5: silently renew the leaf before it expires. Restart frpc onto the fresh cert on rotation;
// stop the whole supervisor on a 403 revoke (INV12). Null ⇒ unenrolled (auto-renew disabled).
const autoRenew = startNativeAutoRenew(
cfg,
ks,
{
restartChild: () => handle.restartChild(),
stop: () => {
void handle.stop()
},
},
logger,
)
const onSignal = (): void => {
void handle.stop()
}
process.once('SIGTERM', onSignal)
process.once('SIGINT', onSignal)
return handle.done.finally(() => monitor.stop())
return handle.done.finally(() => {
monitor.stop()
autoRenew?.stop()
})
}
/** Build the concrete CliDeps used by the real CLI entrypoint. */