///|
#warnings("-alert_internal")
fn uuid_v4() -> @uuid.UUID raise {
let bytes = @tls.rand_bytes(16)
@uuid.from_bytes(bytes).as_version(@uuid.V4)
}
///|
async fn main {
guard CliArgs::parse() is Some(args) else { return }
// tun-server bridges the launching terminal into the shared session,
// so both stdin and stdout must be a tty. Backgrounded invocations
// (nohup / systemd / `docker exec` without -t / `ssh -T`) are
// rejected up front — wrap them in tmux / screen if you need to
// detach.
let in_fd = @async/stdio.stdin.fd()
let out_fd = @async/stdio.stdout.fd()
if !@tty.isatty(in_fd) || !@tty.isatty(out_fd) {
@log.error(
"tun-server requires a tty on stdin and stdout; run it inside a terminal (tmux/screen if you need to background it)",
)
return
}
@log.info("Child argv: \{args.child_argv}")
let credential = CredentialStore::for_relay(args.relay_url) catch {
e => {
@log.error("failed to derive credential path: \{e}")
return
}
}
// Fetch the access token from `credential.key`, running the device
// flow on first-run. A failure here is fatal — the device flow
// prints its own errors and we just propagate.
let token = credential.acquire() catch {
err if @async.is_being_cancelled() => raise err
e => {
@log.error("failed to acquire credential: \{e}")
return
}
}
// Process-lifetime identity for this tun-server invocation. The same
// `task_uuid` is re-sent across reconnects so the relay's "newer
// wins" eviction keeps reconnection semantics intact.
let task_uuid = uuid_v4() catch {
e => {
@log.error("failed to generate task uuid: \{e}")
return
}
}
let hostname = @os.gethostname()
let cwd = getcwd() catch {
GetcwdFailed => {
@log.error("failed to read current directory")
return
}
}
@log.info("task_uuid=\{task_uuid} hostname=\{hostname} cwd=\{cwd}")
let config : ConnectConfig = {
url: args.relay_url,
headers: { "Authorization": "Bearer \{token}" },
task_uuid,
hostname,
cwd,
}
// Reconnect forever with exponential backoff: 1s → 2s → 4s → ... →
// 30s max. Auth failures (401/403) are fatal — don't retry. Proxy
// errors (502/503) or connection failures are transient.
let first_ws = connect_retry_loop(config, credential) catch {
err if @async.is_being_cancelled() => raise err
e => {
@log.error("failed to establish relay session: \{e}")
return
}
}
guard first_ws is Some(ws) else { return }
run_bridge(config, credential, args.child_argv, ws)
}