okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
61c43ca
main
1package daemon 2 3import ( 4"context" 5"errors" 6"fmt" 7"net/http" 8"strings" 9"sync" 10"sync/atomic" 11) 12 13type ConnectionSnapshot struct { 14InstanceID string `json:"instance_id,omitempty"` 15Token string `json:"token"` 16Build string `json:"build,omitempty"` 17Digest string `json:"digest,omitempty"` 18Port int `json:"port"` 19Pid int `json:"pid"` 20Version int `json:"version"` 21} 22 23type Connection struct { 24state atomic.Pointer [connectionState ] 25httpClient * http.Client 26refreshGate chan struct {} 27rediscover Rediscovery 28httpOnce sync.Once 29refreshOnce sync.Once 30} 31 32type connectionState struct { 33endpoint ConnectionSnapshot 34capabilities map [string ]int64 35} 36 37// Rediscovery obtains an updated endpoint without owning API attachment. 38type Rediscovery func (context.Context ) (ConnectionSnapshot ,error ) 39 40// NewConnection creates an unattached client for endpoint inspection. Attach 41// validates the daemon and installs the capabilities needed by optional APIs. 42// A nil rediscover disables recovery. 43func NewConnection (snap ConnectionSnapshot ,rediscover Rediscovery )* Connection { 44c := & Connection {rediscover :rediscover } 45c .state .Store (& connectionState {endpoint :snap }) 46return c 47} 48 49func (c * Connection )Snapshot ()ConnectionSnapshot { 50if c == nil { 51return ConnectionSnapshot {} 52 } 53p := c .state .Load () 54if p == nil { 55return ConnectionSnapshot {} 56 } 57return p .endpoint 58} 59 60func (c * Connection )Port ()int { 61return c .Snapshot ().Port 62} 63 64func (c * Connection )BaseURL ()string { 65port := c .Port () 66if port <= 0 { 67return "" 68 } 69return fmt .Sprintf ("http://127.0.0.1:%d" ,port ) 70} 71 72// Local says the daemon runs on this machine, so an ssh master opened here 73// is one it can ride. 74func (c * Connection )Local ()bool { 75return strings .HasPrefix (c .BaseURL (),"http://127.0.0.1:" ) 76} 77 78func (c * Connection )Update (other * Connection ) { 79if c == nil || other == nil || c == other { 80return 81 } 82c .state .Store (other .state .Load ()) 83} 84 85// Refresh serializes rediscovery and installs only a compatible live endpoint. 86func (c * Connection )Refresh (ctx context.Context )error { 87if c == nil { 88return errors .New ("not connected to Albedo" ) 89 } 90if ctx == nil { 91ctx = context .Background () 92 } 93c .refreshOnce .Do (func () {c .refreshGate = make (chan struct {},1 ) }) 94select { 95case c .refreshGate <- struct {}{}: 96case <- ctx .Done (): 97return ctx .Err () 98 } 99defer func () {<- c .refreshGate }() 100if err := ctx .Err ();err != nil { 101return err 102 } 103if c .rediscover == nil { 104return errors .New ("daemon rediscovery is unavailable" ) 105 } 106snap ,err := c .rediscover (ctx ) 107if err != nil { 108return err 109 } 110attached ,err := Attach (ctx ,snap ,nil ) 111if err != nil { 112return err 113 } 114defer attached .HTTPClient ().CloseIdleConnections () 115c .Update (attached ) 116return nil 117} 118 119// HTTPClient returns the reusable HTTP client owned by this Connection. 120// Address and token updates retain the same client and pool. 121func (c * Connection )HTTPClient ()* http.Client { 122c .httpOnce .Do (func () {c .httpClient = newHTTPClient () }) 123return c .httpClient 124}