daemonruntime

package
v0.182.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2026 License: Apache-2.0 Imports: 38 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DefaultListen = "127.0.0.1:8766"
)
View Source
const LaunchdLabel = "dev.sneat.wb.daemon"
View Source
const RPCBaseURL = "http://wb.local"

Variables

This section is empty.

Functions

func AuthenticatedHandler

func AuthenticatedHandler(token string, next http.Handler) http.Handler

func DaemonAddressInUse

func DaemonAddressInUse(err error) bool

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

func DaemonStartLogPath(root string) (string, error)

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 DaemonSystemdUnitName(getenv func(string) string) string

func ListenLocal

func ListenLocal(root string) (net.Listener, error)

func LocalHTTPClient

func LocalHTTPClient(root, token string) (*http.Client, error)

func OperationClient

func OperationClient(ctx context.Context, deps Dependencies, root string, progress io.Writer) (daemonv1connect.DaemonServiceClient, error)

func OptionalState

func OptionalState(state daemon.State, found bool) *daemon.State

func ProcessAlive

func ProcessAlive(pid int) bool

ProcessAlive observes native process liveness directly without constructing a controller or starting a daemon.

func ProcessStartedAt

func ProcessStartedAt(pid int) time.Time

func RequireLoopbackAddress

func RequireLoopbackAddress(address string) error

func RuntimeGuard

func RuntimeGuard(out io.Writer, ctx context.Context, address string, store daemon.Store, owned daemon.State, ownerToken string, tickerFactory func(time.Duration) (<-chan time.Time, func())) error

func RuntimeIntact

func RuntimeIntact(store daemon.Store) error

func SignalContext

func SignalContext(parent context.Context) (context.Context, context.CancelFunc)

func StatePath

func StatePath(root string) (string, error)

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 Controller) RestartWithProgress(ctx context.Context, ifRunning bool, progress func(string), forceDetached bool) (Result, error)

func (Controller) Start

func (controller Controller) Start(ctx context.Context, listen string) (Result, error)

func (Controller) StartWithProgress

func (controller Controller) StartWithProgress(ctx context.Context, listen string, progress func(string), forceDetached bool) (Result, error)

func (Controller) Status

func (controller Controller) Status(ctx context.Context) (Result, error)

func (Controller) Stop

func (controller Controller) Stop(ctx context.Context) (Result, error)

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)

func (*FileBridgeServer) Serve

func (server *FileBridgeServer) Serve(ctx context.Context) error

type HubEventMarker

type HubEventMarker struct {
	ID         string    `json:"id,omitempty"`
	Event      string    `json:"event,omitempty"`
	OccurredAt time.Time `json:"occurred_at"`
}

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

type LifecycleBounds struct{ Ready, Stop, SupervisorRestart, SupervisorPoll time.Duration }

func DefaultLifecycleBounds

func DefaultLifecycleBounds() LifecycleBounds

type Location

type Location struct {
	Home       string
	RuntimeDir string
	SocketPath string
	StatePath  string
}

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

func ResolveLocation(root string) (Location, error)

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"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL