package host import ( "strings" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/status" ) // systemChannelEnv is the box's platform channel: canary or stable. Set in // host.env, read through the getenv seam like every other CARLOS_* value; // the ROOT apply reads it from its own unit environment, never from the // agent's request (spec §6 step 2). const systemChannelEnv = "CARLOS_SYSTEM_CHANNEL" // A value that is not a channel at all — a typo, a shell that expanded to // nothing useful, a name with a slash too many — falls back to stable // rather than travelling into a bucket key and an on-box state file to ask // for a pointer nobody publishes. It comes back as invalid so the caller // can say so ONCE: this is read per tick, and a line per tick is a line // nobody reads. func systemChannel(getenv func(string) string) (channel, invalid string) { c := strings.TrimSpace(getenv(systemChannelEnv)) switch { case c == "": return "stable", "" case !release.ValidChannel(c): return "stable", c } return c, "" } // boxSystemChannel is systemChannel for the caller that has nowhere to log: // the status tick, which publishes this every five minutes. func boxSystemChannel(getenv func(string) string) string { c, _ := systemChannel(getenv) return c } // systemStateProbe is root's result.json as the heartbeat carries it, or nil // when this box has never been asked to update. func systemStateProbe(optDir string) *status.SystemState { r, err := readSystemResult(optDir) if err != nil || r == nil { return nil } return &status.SystemState{Channel: r.Channel, Version: r.Version, PromotedAt: r.PromotedAt, Outcome: r.Outcome, Reason: r.Reason, At: r.At} }