carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1// The agent's wiring for the host half of the reverse channel (issue #53 / C6
2// Step 3). The dial/serve/backoff machinery itself lives in
3// internal/tunnelclient — a leaf package, so the edge's acceptance test can
4// drive the SAME loop this agent runs (internal/host imports internal/edge,
5// so the edge tests can never import this package). What is left here is the
6// part that is genuinely agent-shaped: reading the env, and publishing the
7// state the status heartbeat reports.
8package host
9
10import (
11 "context"
12 "log"
13 "net/http"
14 "os"
15 "sync/atomic"
16
17 "github.com/carlosframework/platform/internal/tunnelclient"
18)
19
20// tunnelStateVal is "" (disabled/never started), "connected", or
21// "reconnecting" — read by statusLoop's heartbeat probe (probes must fail
22// toward honest values; "" means the loop isn't running at all).
23var tunnelStateVal atomic.Value
24
25func tunnelState() string {
26 if v, ok := tunnelStateVal.Load().(string); ok {
27 return v
28 }
29 return ""
30}
31
32// tunnelLoop dials CARLOS_TUNNEL_URL and serves handler over the inverted
33// conn, forever, with jittered capped backoff. Inert unless all three env
34// vars are set — the same convention as every other optional agent loop.
35func tunnelLoop(handler http.Handler) {
36 rawURL := os.Getenv("CARLOS_TUNNEL_URL")
37 token := os.Getenv("CARLOS_TUNNEL_TOKEN")
38 name := os.Getenv("CARLOS_TUNNEL_NAME")
39 if rawURL == "" || token == "" || name == "" {
40 if rawURL != "" || token != "" || name != "" {
41 log.Printf("agent: tunnel loop disabled: need all of CARLOS_TUNNEL_URL, CARLOS_TUNNEL_TOKEN, CARLOS_TUNNEL_NAME")
42 }
43 return
44 }
45 log.Printf("agent: tunnel loop armed (%s as %q)", rawURL, name)
46 // context.Background(): the agent's tunnel runs for the life of the
47 // process and is torn down with it. The ctx exists for callers that need
48 // to stop the loop deterministically (the edge's e2e), not for this one.
49 tunnelclient.Loop(context.Background(), tunnelclient.Config{
50 URL: rawURL,
51 Name: name,
52 Token: token,
53 Handler: handler,
54 OnState: func(s string) { tunnelStateVal.Store(s) },
55 // The "agent: " prefix is what operators grep the journal for, so it
56 // is preserved here rather than baked into the leaf package.
57 Logf: func(format string, args ...any) { log.Printf("agent: "+format, args...) },
58 })
59}
60