Documentation
¶
Overview ¶
Package daemon implements the long-lived ppz daemon: IPC server, on-disk state, NATS connection, and HTTP client to ppz-server.
Index ¶
- Variables
- func Call(sock, method string, params, result any) error
- func CursorKey(accountID, handle, channel string) string
- func IsAlive(pid int) bool
- func PIDFromHome(home string) int
- type Credentials
- type Daemon
- type NATSEvent
- type NATSEventRing
- type RefreshFn
- type RefreshLoop
- func (r *RefreshLoop) Current() (jwt, seed string)
- func (r *RefreshLoop) LastRefreshAt() time.Time
- func (r *RefreshLoop) RefreshNowIfDue(ctx context.Context, now time.Time) (bool, error)
- func (r *RefreshLoop) Start(ctx context.Context, jwt, seed string, expUnix int64) error
- func (r *RefreshLoop) Stop()
- type State
- func (s *State) AccountID() string
- func (s *State) AccountName() string
- func (s *State) ClearCurrent(session string) error
- func (s *State) ClearCurrentForHandle(handle string) error
- func (s *State) Credentials() (*Credentials, bool)
- func (s *State) Current(session string) string
- func (s *State) ForgetPipe(handle string)
- func (s *State) Home() string
- func (s *State) KeyPrefix() string
- func (s *State) KnowsPipe(handle string) bool
- func (s *State) LoadFromDisk() error
- func (s *State) LoginCheck() string
- func (s *State) RememberPipe(handle string)
- func (s *State) ResetPipes(handles []string)
- func (s *State) SetAccountID(id string)
- func (s *State) SetCurrent(session, handle string) error
- func (s *State) SetLogin(creds Credentials, accountID, accountName, keyPrefix string) error
- func (s *State) SetLoginCheck(state string)
Constants ¶
This section is empty.
Variables ¶
var ErrCredsRequired = errors.New("credentials required")
ErrUnauthorized is what RefreshFn returns when the bearer was revoked / expired — distinct from transient network failures (which the loop retries). Triggers OnUnauthorized + stop.
Functions ¶
func Call ¶
Call sends one request to the daemon over a fresh connection and decodes either result or error.
func PIDFromHome ¶
PIDFromHome reads $PPZ_HOME/daemon.pid. Returns 0 if absent.
Types ¶
type Credentials ¶
type Credentials struct {
URL string `json:"url"`
APIKey string `json:"api_key"`
AccountID string `json:"account_id,omitempty"`
AccountName string `json:"account_name,omitempty"`
// Auth V2 Phase 3 — short-lived NATS user credentials.
// Re-fetched periodically by the daemon's refresh goroutine.
NATSUserJWT string `json:"nats_user_jwt,omitempty"`
NATSUserSeed string `json:"nats_user_seed,omitempty"`
}
Credentials are persisted at $PPZ_HOME/credentials (mode 0600). AccountID + AccountName are stored alongside URL+APIKey so a SIGHUP / file-poller reload doesn't drop the resolved org info (the alternative would be re-calling /auth/exchange after every reload).
type Daemon ¶
type Daemon struct {
Home string
Sock string
State *State
HTTP *http.Client
NC *nats.Conn // nil until Login
NATSURL string
Cursors *cursors
// Phase 3.5 — JWT refresh loop. Started on Login (and restored
// in ensureNATS for daemon restarts), holds the latest minted
// (jwt, seed) for the current org, and re-runs /auth/exchange
// at exp-30s. nats.Connect's UserJWT callback reads from
// Refresh.Current() so reconnects pick up fresh credentials.
// Stopped on Logout / replaced on Login.
Refresh *RefreshLoop
// Phase 0 (agent hardening) — short tail of NATS connection-state
// transitions. Surfaced by `ppz status` (latest state) and
// `ppz diag` (full ring). Initialised in New() so the handlers
// registered on the very first nats.Connect have a non-nil ring
// to append to.
NATSEvents *NATSEventRing
}
Daemon is the singleton process per $PPZ_HOME.
type NATSEvent ¶ added in v0.26.0
type NATSEvent struct {
Type string `json:"type"`
At time.Time `json:"at"`
Reason string `json:"reason,omitempty"`
}
NATSEvent is one entry in the daemon's NATS connection-state log. Type is one of "connect", "disconnect", "reconnect", "closed" — matching the nats.go handler set we register. At is the moment the handler fired. Reason captures the error string for disconnect / closed events (empty for the others). Phase 0 is observe-only — these events drive `ppz status` and `ppz diag` output but do not influence reconnect behaviour.
type NATSEventRing ¶ added in v0.26.0
type NATSEventRing struct {
// contains filtered or unexported fields
}
NATSEventRing is a fixed-capacity, append-only, drop-oldest ring of NATSEvent records. Used by the daemon to surface a recent tail of connection-state transitions through `ppz diag`.
Thread-safe: every accessor takes mu. The ring lives on Daemon and is initialised in New() — before any nats.Connect call — so the handlers we register can append without nil-checking.
func (*NATSEventRing) Append ¶ added in v0.26.0
func (r *NATSEventRing) Append(typ, reason string, at time.Time)
Append records one event. When the ring is full the oldest entry is dropped — diag's value is precisely catching transient events that happened "a few minutes ago", so we keep the tail and lose the head.
func (*NATSEventRing) Snapshot ¶ added in v0.26.0
func (r *NATSEventRing) Snapshot() []NATSEvent
Snapshot returns a copy of the current events in chronological order (oldest first). Safe to retain — the returned slice is independent of the ring's internal storage.
type RefreshFn ¶
type RefreshFn func(ctx context.Context, accountID string) (jwt, seed string, expUnix int64, err error)
RefreshFn is the work the refresh loop calls when a JWT is about to expire. Implementations re-run POST /api/v1/auth/exchange and return the new (jwt, seed). accountID lets a multi-org daemon route to the right account.
type RefreshLoop ¶
type RefreshLoop struct {
AccountID string
Refresh RefreshFn
// contains filtered or unexported fields
}
RefreshLoop monitors one (org, JWT) pair and refreshes it before expiry. Concurrency: Current() may be called from any goroutine; Start/Stop must be called from the same goroutine.
func (*RefreshLoop) Current ¶
func (r *RefreshLoop) Current() (jwt, seed string)
Current returns the freshest (jwt, seed) — used by nats.UserJWT() callbacks on every NATS (re)connect.
func (*RefreshLoop) LastRefreshAt ¶ added in v0.17.0
func (r *RefreshLoop) LastRefreshAt() time.Time
LastRefreshAt returns when the loop last accepted fresh credentials. Start counts as the first refresh because its credentials came from /auth/exchange immediately before the loop was started.
func (*RefreshLoop) RefreshNowIfDue ¶ added in v0.17.9
RefreshNowIfDue refreshes synchronously when the cached credential is already inside its refresh window. It covers machines waking from sleep: the timer goroutine may not have run yet, but the next command must not continue with an expired JWT.
func (*RefreshLoop) Start ¶
Start begins the refresh goroutine with an initial credential. expUnix is the JWT's `exp` claim in unix seconds.
func (*RefreshLoop) Stop ¶
func (r *RefreshLoop) Stop()
Stop halts the refresh goroutine. Idempotent.
type State ¶
type State struct {
// contains filtered or unexported fields
}
State is the daemon's in-memory mirror of on-disk credentials + current handle. "current" is keyed by session id (tty / $PPZ_SESSION) so each terminal window has its own current source — same scoping as cursors, avoids the "new terminal silently inherits a stale current set hours ago in another window" footgun.
func (*State) AccountName ¶ added in v0.30.0
func (*State) ClearCurrent ¶
ClearCurrent drops this session's current. Used by `ppz disconnect`. Idempotent — clearing an already-clear session is a no-op.
func (*State) ClearCurrentForHandle ¶ added in v0.21.0
ClearCurrentForHandle drops every session whose current equals handle. Called after a source destroy so stale per-session pointers don't linger.
func (*State) Credentials ¶
func (s *State) Credentials() (*Credentials, bool)
func (*State) ForgetPipe ¶ added in v0.21.0
ForgetPipe removes a handle from the known-pipes cache. Called after a source is destroyed so the cache doesn't return stale hits.
func (*State) LoadFromDisk ¶
LoadFromDisk reads credentials and current from $PPZ_HOME. Called at startup and on SIGHUP. Missing files mean "not logged in" / "no current".
`current.json` is the session→handle map. The legacy plain-text `current` file (pre-per-session) is migrated into session "default" if both exist.
func (*State) LoginCheck ¶
LoginCheck returns the cached server-validation result. "" means "not observed yet" — callers (e.g. status) should probe.
func (*State) RememberPipe ¶
func (*State) ResetPipes ¶
func (*State) SetAccountID ¶ added in v0.30.0
func (*State) SetCurrent ¶
func (*State) SetLogin ¶
func (s *State) SetLogin(creds Credentials, accountID, accountName, keyPrefix string) error
func (*State) SetLoginCheck ¶
SetLoginCheck records the latest server-validation result. Called from callServer based on response status. Idempotent — same value twice is fine, but transitions (ok→invalid, invalid→ok) are kept.