Documentation
¶
Index ¶
- Constants
- func AuthenticatedHandler(token string, next http.Handler) http.Handler
- func DaemonAddressInUse(err error) bool
- func DaemonStartLogPath(root string) (string, error)
- func DaemonSystemdUnitName(getenv func(string) string) string
- func ListenLocal(root string) (net.Listener, error)
- func LocalHTTPClient(root, token string) (*http.Client, error)
- func OperationClient(ctx context.Context, deps Dependencies, root string, progress io.Writer) (daemonv1connect.DaemonServiceClient, error)
- func OptionalState(state daemon.State, found bool) *daemon.State
- func ProcessAlive(pid int) bool
- func ProcessStartedAt(pid int) time.Time
- func RequireLoopbackAddress(address string) error
- func RuntimeGuard(out io.Writer, ctx context.Context, address string, store daemon.Store, ...) error
- func RuntimeIntact(store daemon.Store) error
- func SignalContext(parent context.Context) (context.Context, context.CancelFunc)
- func StatePath(root string) (string, error)
- func WithOwnerToken(token string, base http.RoundTripper) http.RoundTripper
- type Controller
- func (controller Controller) AcquireStateLock() (func(), error)
- func (controller Controller) LoadState() (daemon.State, bool, error)
- func (controller Controller) MarkStoppedIfOwned(ownerToken string) error
- func (controller Controller) Provenance() (daemon.Provenance, error)
- func (controller Controller) RecoverLifecycleLock(ctx context.Context, apply bool) (RecoveryResult, error)
- func (controller Controller) RestartWithProgress(ctx context.Context, ifRunning bool, progress func(string), forceDetached bool) (Result, error)
- func (controller Controller) Start(ctx context.Context, listen string) (Result, error)
- func (controller Controller) StartWithProgress(ctx context.Context, listen string, progress func(string), forceDetached bool) (Result, error)
- func (controller Controller) Status(ctx context.Context) (Result, error)
- func (controller Controller) Stop(ctx context.Context) (Result, error)
- func (controller Controller) WithReplaceOtherRoot(replace bool) Controller
- type Dependencies
- type FileBridgeServer
- type HubEventMarker
- type HubRedeliverySweep
- type HubStatus
- type Identity
- type LegacyEndpoint
- type LifecycleBounds
- type Location
- type PublicQueue
- type PublicState
- type RecoveryResult
- type Result
Constants ¶
const (
DefaultListen = "127.0.0.1:8766"
)
const LaunchdLabel = "dev.sneat.wb.daemon"
const RPCBaseURL = "http://wb.local"
Variables ¶
This section is empty.
Functions ¶
func DaemonAddressInUse ¶
daemonAddressInUse reports whether a listener failure means the endpoint is already held. It is its own condition because the right response is to name the endpoint and stop, not to retry or to start a second daemon.
func DaemonStartLogPath ¶
daemonStartLogPath is the daemon's own log file. Everywhere but darwin the launcher opens it and redirects the child's output into it, so no supervisor unit records the path at all.
func DaemonSystemdUnitName ¶
func OperationClient ¶
func OperationClient(ctx context.Context, deps Dependencies, root string, progress io.Writer) (daemonv1connect.DaemonServiceClient, error)
func ProcessAlive ¶
ProcessAlive observes native process liveness directly without constructing a controller or starting a daemon.
func ProcessStartedAt ¶
func RequireLoopbackAddress ¶
func RuntimeGuard ¶
func RuntimeIntact ¶
func SignalContext ¶
func WithOwnerToken ¶
func WithOwnerToken(token string, base http.RoundTripper) http.RoundTripper
WithOwnerToken binds the owner bearer token to the existing request-cloning transport. It preserves the supplied transport and token without defaults or normalization.
Types ¶
type Controller ¶
type Controller struct {
// contains filtered or unexported fields
}
func NewController ¶
func NewController(deps Dependencies, root string) Controller
func (Controller) AcquireStateLock ¶
func (controller Controller) AcquireStateLock() (func(), error)
func (Controller) LoadState ¶
func (controller Controller) LoadState() (daemon.State, bool, error)
LoadState reads the controller's resolved lifecycle record without starting it.
func (Controller) MarkStoppedIfOwned ¶
func (controller Controller) MarkStoppedIfOwned(ownerToken string) error
func (Controller) Provenance ¶
func (controller Controller) Provenance() (daemon.Provenance, error)
func (Controller) RecoverLifecycleLock ¶
func (controller Controller) RecoverLifecycleLock(ctx context.Context, apply bool) (RecoveryResult, error)
func (Controller) RestartWithProgress ¶
func (Controller) StartWithProgress ¶
func (Controller) WithReplaceOtherRoot ¶
func (controller Controller) WithReplaceOtherRoot(replace bool) Controller
type Dependencies ¶
type Dependencies struct {
GuardTicker func(time.Duration) (<-chan time.Time, func())
UsageError func(string) error
Bounds func() LifecycleBounds
// listen binds the dashboard endpoint; nil means net.Listen. Tests hand it a
// listener whose bound address is not the one that was asked for.
Now func() time.Time
Executable func() (string, error)
Start func(string, []string, string) (int, error)
// checkOtherRoot runs at the top of launch, before any lifecycle state is
// written and before start is called. It refuses a start for projects root
// `root` when the platform's one fixed-label supervisor service is already
// registered for a different projects root, unless replace is true. A nil
// value skips the check (tests that do not exercise it).
CheckOtherRoot func(root string, replace bool) error
Alive func(int) bool
Stop func(pid int, supervisor daemon.Supervisor, supervisorLabel string) error
Sleep func(time.Duration)
// lockNow is a dedicated clock seam for stateLock's short retry deadline.
// It must never be `now` (which the production default strips to a
// UTC, non-monotonic reading via time.Time.UTC(), a wall-clock time that
// an NTP step or a VM resume can jump under a waiter's feet). The default
// is plain time.Now, whose monotonic reading makes the 5s deadline
// immune to wall-clock adjustments, matching Go's own recommendation for
// measuring elapsed time (see time.Since and the "Monotonic Clocks"
// section of the time package doc).
LockNow func() time.Time
Version func() buildinfo.Report
Token func() (string, error)
Health func(context.Context, string) error
OwnedHealth func(context.Context, string, int, uint64) error
BridgeHealth func(context.Context, string, string) error
RestartTicker func(time.Duration) (<-chan time.Time, func())
RawPolicy func(string) (bool, string, error)
LocalClient func(string, string) (*http.Client, error)
HubConfigPath func() string
HubHealth func(context.Context, string) (HubStatus, error)
// hubTuning is nil everywhere but the whole-journey end-to-end test; see
// the type's documentation.
// getenv, getpid, and getppid are the seams `daemon serve` reads a
// supervisor's own evidence through (INVOCATION_ID, SYSTEMD_EXEC_PID,
// XPC_SERVICE_NAME, and this process's own pid/ppid, which distinguish a
// supervisor's own child from a process that merely inherited its
// environment), so a test never needs a real systemd or launchd to
// exercise supervisor detection.
Getenv func(string) string
Getpid func() int
Getppid func() int
// observedSupervisor independently observes evidence about a running PID
// that this build did not itself write, for `wb daemon status` to compare
// against what that process recorded about its own start. See
// internal/daemon.ObservedCgroupSupervisor.
ObservedSupervisor func(int) (daemon.Supervisor, bool)
// processStartTime observes a process's own start time, so a stale
// hard-coded test PID cannot collide with a real, unrelated process this
// build did not create — the default reads the real OS the way
// daemon.ProcessStartTime already does; a test overrides it instead of
// hoping its fake PIDs are never real ones (sneat-dev/wb#622 review: tests
// must not depend on the real process table for correctness).
ProcessStartTime func(int) (time.Time, bool)
// supervisorPresent independently checks whether the recorded supervisor
// can still be shown to exist at all, so `wb daemon start`/`restart` do
// not refuse forever on a stale record naming a supervisor that is long
// gone (sneat-dev/wb#622 review item 4).
SupervisorPresent func(supervisor daemon.Supervisor, label string) (present bool, unitName string)
// systemdUnitName and systemdUnitState are the sneat-dev/wb#617 detector's
// own seams: systemdUnitName resolves which systemd user unit `wb daemon
// status` checks (config, or daemonDefaultSystemdUnit), and
// systemdUnitState queries that unit's own state directly
// (`systemctl --user show`), independent of cgroup membership — the
// detector that can see a live host's confirmed shape: an orphaned,
// unsupervised daemon (recorded supervisor=none, a session scope — not
// the unit's own cgroup) serving the port while its systemd unit sat
// failed and crash-looping (sneat-dev/wb#622 review item 2).
SystemdUnitName func() string
SystemdUnitState func(unit string) (daemon.SystemdUnitState, bool)
// observedCgroupUnit lets `daemon serve` record the systemd unit it is
// ACTUALLY running inside (from its own /proc/self/cgroup) at startup,
// so a later `wb daemon status` invocation can prefer that over its own
// configured/default guess — which is wrong whenever the status
// invocation's own environment does not carry the same
// WB_DAEMON_SYSTEMD_UNIT the daemon itself was supervised under
// (sneat-dev/wb#622 review round 3, item M3).
ObservedCgroupUnit func(pid int) (string, bool)
}
func DefaultDependencies ¶
func DefaultDependencies(usageError func(string) error) Dependencies
type FileBridgeServer ¶
type FileBridgeServer struct {
// contains filtered or unexported fields
}
func NewFileBridgeServer ¶
func NewFileBridgeServer(root, ownerToken, generation string, handler http.Handler) (*FileBridgeServer, error)
type HubEventMarker ¶
type HubRedeliverySweep ¶
type HubRedeliverySweep struct {
LastSweepAt *time.Time `json:"last_sweep_at,omitempty"`
Redelivered int `json:"redelivered"`
Abandoned int `json:"abandoned"`
Uncounted int `json:"uncounted"`
LastFailureAt *time.Time `json:"last_failure_at,omitempty"`
LastFailureClass string `json:"last_failure_class,omitempty"`
}
type HubStatus ¶
type HubStatus struct {
Mounted bool `json:"mounted"`
Engine string `json:"engine,omitempty"`
Store string `json:"store,omitempty"`
Listen string `json:"listen,omitempty"`
// Polling and PollInterval come from the same declaration a restart would
// act on. RepositoriesPolled and the delivery markers come from the
// running daemon's health endpoint, because only the serving process has
// them: reading the hub store from this process would open a second
// writer to the operator's inGitDB project.
Polling bool `json:"polling"`
PollInterval string `json:"poll_interval,omitempty"`
// Webhook and WebhookPublicURL also come from the declaration: an App is
// configured or it is not, and the URL is where the operator's tunnel must
// forward GitHub's deliveries.
Webhook bool `json:"webhook"`
WebhookPublicURL string `json:"webhook_public_url,omitempty"`
RepositoriesPolled int `json:"repositories_polled"`
LastEventReceived *HubEventMarker `json:"last_event_received,omitempty"`
LastEventAcknowledged *HubEventMarker `json:"last_event_acknowledged,omitempty"`
// WebhookRedelivery is the missed-webhook recovery sweep's last completed
// pass. Like RepositoriesPolled, it comes from the running daemon's
// health endpoint, because only the serving process has it.
WebhookRedelivery *HubRedeliverySweep `json:"webhook_redelivery,omitempty"`
}
type Identity ¶
type Identity string
daemonIdentity is what a reader could establish about *whose* daemon a lifecycle record describes. It is deliberately a different question from whether something answered on an endpoint: WB's two invisible daemon failures were both cases of "something answered, and it was not ours".
type LegacyEndpoint ¶
type LegacyEndpoint struct {
RuntimeDir string `json:"runtime_dir"`
SocketPath string `json:"socket_path,omitempty"`
SocketAnswers bool `json:"socket_accepts_connections"`
StatePath string `json:"state_path,omitempty"`
StateFound bool `json:"state_found"`
StatePID int `json:"state_pid,omitempty"`
StateLive bool `json:"state_live"`
}
daemonLegacyEndpoint is a daemon still serving the runtime directory WB used before it resolved its home. WB reports it and disturbs nothing: the socket and state file may belong to a live daemon whose supervisor still points at them.
type LifecycleBounds ¶
func DefaultLifecycleBounds ¶
func DefaultLifecycleBounds() LifecycleBounds
type Location ¶
daemonLocation is where this invocation resolves the daemon's runtime state: the one home, and the four paths under it that status must be able to name without starting a daemon.
func ResolveLocation ¶
type PublicQueue ¶
type PublicQueue struct {
SchemaVersion int `json:"schema_version"`
Generation uint64 `json:"generation"`
Owner daemon.Provenance `json:"owner"`
HandoffFrom *daemon.Provenance `json:"handoff_from,omitempty"`
HandoffAt *time.Time `json:"handoff_at,omitempty"`
}
type PublicState ¶
type PublicState struct {
SchemaVersion int `json:"schema_version"`
Status daemon.Status `json:"status"`
PID int `json:"pid,omitempty"`
Listen string `json:"listen"`
Provenance daemon.Provenance `json:"provenance"`
Queue PublicQueue `json:"queue"`
StartedAt time.Time `json:"started_at,omitempty"`
UpdatedAt time.Time `json:"updated_at"`
// WBHome, StatePath and StoppedReason are the record's own account of where
// it lives and, for a stop it did not choose, why. They are part of the
// public projection because identity is exactly what a reader must be able
// to check.
WBHome string `json:"wb_home,omitempty"`
StatePath string `json:"state_path,omitempty"`
StoppedReason string `json:"stopped_reason,omitempty"`
// Supervisor is what the reporting record's process observed about its own
// start (REQ: report-supervisor). It is always one of "systemd", "launchd",
// or "none" — never blank — so a reader never has to guess what an absent
// value means.
Supervisor daemon.Supervisor `json:"supervisor,omitempty"`
// SupervisorLabel is the supervisor's own identity for the job (currently
// only a launchd job label). See daemon.State.SupervisorLabel.
SupervisorLabel string `json:"supervisor_label,omitempty"`
}
func PublicStateOf ¶
func PublicStateOf(state daemon.State) PublicState
type RecoveryResult ¶
type RecoveryResult struct {
Action string `json:"action"`
Applied bool `json:"applied"`
LockPath string `json:"lock_path"`
OwnerPath string `json:"owner_path"`
LockPresent bool `json:"lock_present"`
Eligible bool `json:"eligible"`
OwnerPID int `json:"owner_pid,omitempty"`
OwnerAlive bool `json:"owner_alive"`
StateStatus daemon.Status `json:"state_status,omitempty"`
Reason string `json:"reason"`
Detail string `json:"detail,omitempty"`
}
type Result ¶
type Result struct {
Action string `json:"action"`
Managed bool `json:"managed"`
ProcessManagerRunning bool `json:"process_manager_running"`
Reachable bool `json:"reachable"`
ReachabilityError string `json:"reachability_error,omitempty"`
ReachabilityTransport string `json:"reachability_transport,omitempty"`
DirectTransportReachable bool `json:"direct_transport_reachable"`
DirectTransportError string `json:"direct_transport_error,omitempty"`
ProvenanceMatches bool `json:"provenance_matches_installed"`
State PublicState `json:"state,omitempty"`
AlreadyRunning bool `json:"already_running,omitempty"`
AutomaticVersionHandoff bool `json:"automatic_version_handoff,omitempty"`
Hub HubStatus `json:"hub"`
// Identity answers "whose daemon is this?" separately from "did something
// answer on the endpoint?". ReadyVerified is the only condition under which
// status may present a daemon as ready; ReportedState is what a reader
// should act on, and never claims ready that was not verified.
Identity Identity `json:"identity,omitempty"`
IdentityDetail string `json:"identity_detail,omitempty"`
ReadyVerified bool `json:"ready_verified"`
ReportedState string `json:"reported_state,omitempty"`
// The runtime location this invocation resolved. Status reports it so an
// operator can name the endpoint and the record without starting anything.
WBHome string `json:"wb_home,omitempty"`
RuntimeDir string `json:"runtime_path,omitempty"`
SocketPath string `json:"socket_path,omitempty"`
StatePath string `json:"state_path,omitempty"`
// LegacyRuntime names a daemon still serving the runtime directory WB used
// before it resolved its home. Its presence is why a start was refused.
LegacyRuntime *LegacyEndpoint `json:"legacy_runtime,omitempty"`
// SupervisorMismatch is set when the running daemon's recorded supervisor
// (State.Supervisor, self-reported at its own startup) disagrees with what
// this invocation could independently observe about it right now: either
// a specific systemd unit's own queried state (daemonObservedSystemdUnitState,
// `systemctl --user show`, the sneat-dev/wb#617 detector this feature
// exists for), or, when that is unavailable, the coarser cgroup-membership
// comparison (internal/daemon.ObservedCgroupSupervisor's documented
// limit).
SupervisorMismatch string `json:"supervisor_mismatch,omitempty"`
// Warning surfaces a non-fatal condition worth an operator's attention on
// an otherwise-successful result. Used when an implicit `wb daemon start`
// caller (`wb dashboard --local`, or an RPC client's own automatic
// bootstrap in daemon_rpc.go) finds a live, healthy, supervised daemon
// running a different executable than this invocation's own: touching it
// is refused, but the caller still gets a working connection back instead
// of an outright failure (sneat-dev/wb#622 review item 9).
Warning string `json:"warning,omitempty"`
}