Documentation
¶
Overview ¶
Package lifeops answers what a local billet deployment is actually doing.
IT PRINTS NOTHING. Operator output belongs to cmd/billet, the one package allowed to write to stdout; lifeops returns facts and lets the command render them. That split is what lets every judgment here be asserted by value rather than by scraping text out of a terminal.
The facts it gathers are the ones that differ silently. A host can have the right config, a valid App key and a running service, and still be executing a binary that was replaced hours ago, or a unit an override has quietly rewritten — and nothing on the happy path says so.
Index ¶
- Constants
- func Contained(dir, p string) bool
- type ConvergeOption
- type Converger
- func (c *Converger) ApplyOwnership(changes []OwnershipChange, uid, gid int) error
- func (c *Converger) CollateralNote() string
- func (c *Converger) Disable(ctx context.Context, unit string) error
- func (c *Converger) DisableCmd(unit string) string
- func (c *Converger) Enable(ctx context.Context, unit string) error
- func (c *Converger) EnabledNow(ctx context.Context, unit string) (Enablement, error)
- func (c *Converger) EnablementCmd(units ...string) string
- func (c *Converger) Identity(req UpRequest) (int, int, error)
- func (c *Converger) ManagerName() string
- func (c *Converger) Plan(ctx context.Context, req UpRequest) (UpPlan, error)
- func (c *Converger) ProveStable(ctx context.Context, unit string) error
- func (c *Converger) RepairPaths(dir string, targets []RepairTarget, uid, gid int) ([]string, error)
- func (c *Converger) RepairServerState(dir string, uid, gid int) ([]string, error)
- func (c *Converger) Revalidate(ctx context.Context, req UpRequest, want UnitPlan) error
- func (c *Converger) Running(ctx context.Context, req UpRequest) ([]RunningFacts, error)
- func (c *Converger) Services() (string, string)
- func (c *Converger) Snapshot(ctx context.Context, unit string) (string, error)
- func (c *Converger) StartAndProve(ctx context.Context, unit string) (string, error)
- func (c *Converger) StartTimer(ctx context.Context, unit string) error
- func (c *Converger) StopAndProve(ctx context.Context, unit string) (StopResult, error)
- type Enablement
- type FileFacts
- type Inspector
- type Option
- type OwnershipChange
- type Refusal
- type RepairTarget
- type Report
- type RunningFacts
- type ServiceFacts
- type StopResult
- type Tristate
- type UnitPlan
- type UpPlan
- type UpRequest
Constants ¶
const DefaultStabilityWait = 6 * time.Second
DefaultStabilityWait is how long a service must keep the process it started before `up` calls it up.
SIX SECONDS, matching the host role's own probe. It is not a health check and does not pretend to be: no finite window proves future life. What it catches is the failure this command would otherwise report as success — a service that reaches READY=1 and then dies, which Restart=on-failure turns into a crash loop that looks "active" at any single instant.
const DefaultTimeout = 30 * time.Second
DefaultTimeout bounds one systemctl call. Generous: these are local queries, and the cost of it being too small is a diagnostic that fails on a busy host rather than reporting what it found.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type ConvergeOption ¶
type ConvergeOption func(*Converger)
ConvergeOption configures a Converger.
func WithStabilityWait ¶
func WithStabilityWait(d time.Duration) ConvergeOption
WithStabilityWait sets how long a started service must hold its process.
type Converger ¶
type Converger struct {
// contains filtered or unexported fields
}
Converger changes a host. It is a separate type from Inspector because the two answer to different rules — an inspection may run anywhere and reports what it finds, while this refuses to act on anything it cannot establish.
func NewConverger ¶
func NewConverger(i *Inspector, opts ...ConvergeOption) *Converger
NewConverger builds one around an Inspector.
func (*Converger) ApplyOwnership ¶
func (c *Converger) ApplyOwnership(changes []OwnershipChange, uid, gid int) error
ApplyOwnership makes the changes a plan named.
THROUGH A DESCRIPTOR, NOT A PATHNAME. This runs as root, and os.Chown follows symlinks: a path that named a regular file when the plan was made and a symlink by the time it is applied would hand an arbitrary file's ownership to the service account. The file is opened without following, checked to be the same file the planner saw, and changed through the descriptor — so whatever happens to the NAME afterwards, the operation lands on the inode that was verified.
THE MODE IS TIGHTENED BEFORE THE OWNER IS TRANSFERRED. Reversed, a chown that succeeds and a chmod that fails leaves a private key owned by the service account with whatever permissions it had — which is how a 0644 App key becomes readable by every process running as that account's group.
func (*Converger) CollateralNote ¶
CollateralNote explains how enabling ONE service can commit another, in this manager's own terms.
IT IS THE BACKEND'S SENTENCE because the mechanism is: systemd has `[Install] Also=`, which writes links for a unit nobody named and is invisible in every property of the one that was. launchd has no such thing and would have a different sentence, or none. The shared command detects the collateral change the same way on both — by comparing before and after — and only the explanation differs.
func (*Converger) Disable ¶
Disable undoes an Enable this run performed.
ONLY WHAT THIS RUN DID. A failed `up` must not leave a unit committed to boot that nothing established can run — and must equally not disable one an operator had enabled before it arrived.
func (*Converger) DisableCmd ¶
DisableCmd renders how an operator undoes an enablement themselves.
func (*Converger) EnabledNow ¶
EnabledNow reports a unit's enablement state at this moment.
The plan's answer is from before `billet check` talked to GitHub, and another operator may have enabled the unit in between. Enabling is idempotent, so success proves nothing about who did it — and rolling back an enablement this run did not perform would disable a service somebody else just committed.
IT ANSWERS WITH THE STATE ITSELF, not a yes or no. Folding it to a boolean made `masked`, `static`, `linked`, an empty answer and every state systemd adds later mean what `disabled` means — permission to enable. Only the exact string is that, and the caller says so.
func (*Converger) EnablementCmd ¶
EnablementCmd renders the command an operator runs to see for themselves what billet just told them about enablement.
A REFUSAL AN OPERATOR CANNOT ACT ON IS A DEAD END, so every one of them carries the command that resolves it — and that command belongs to the service manager rather than to billet, which makes it the backend's to name.
func (*Converger) Identity ¶
Identity resolves the service account for a caller that already planned.
func (*Converger) ManagerName ¶
ManagerName is what billet calls this service manager in a sentence.
func (*Converger) Plan ¶
Plan decides what `up` would do, and every reason it would not.
IT COLLECTS REFUSALS RATHER THAN RETURNING THE FIRST. An operator who has to re-run a command to discover the next thing wrong with their host is being made to pay for a diagnostic that already knew.
func (*Converger) ProveStable ¶
ProveStable establishes that a unit is holding the process it has.
It exists separately from StartAndProve because a service that was ALREADY running when `up` arrived gets enabled too, and the sample the planner took is by then minutes old — `billet check` runs in between and talks to GitHub. Enabling on that sample would commit a crash loop to every future boot on the evidence of one instant that has since passed.
func (*Converger) RepairPaths ¶
func (c *Converger) RepairPaths( dir string, targets []RepairTarget, uid, gid int, ) ([]string, error)
RepairPaths gives the service account back a NAMED SET of entries a privileged command created in a state directory.
THE CALLER SUPPLIES THE SET, and for `billet local restore` that set is derived from the plan it just executed rather than from a list kept here. A hand-maintained second list is the failure this whole area keeps producing: preflightState covers what `billet check` creates and covers none of what a restore publishes — no `ca/`, no `authority-created` — so a restore run as root (which is what an operator restoring onto a packaged host IS, because the App key lands in root-owned /etc/billet) left every one of those files owned by root inside a service-owned directory, where systemd's StateDirectory= will never look at them again. Measured in scripts/restore-rehearsal.sh: the service account could not open the deployment root had just restored.
There is no walk, which is what makes it safe: nothing is discovered, so nothing planted can be reached, and every entry named is one this run put there.
func (*Converger) RepairServerState ¶
RepairServerState gives the server back the state a root-run preflight created underneath it.
MEASURED, NOT ASSUMED, and it is the reason this function exists at all: `billet check` opens the ledger as the invoking process, so running it as root creates billet.db and billet.lock owned by root. systemd's StateDirectory= repairs ownership RECURSIVELY when the top directory's owner is wrong — verified on systemd 255 — but does nothing at all when the directory is already correct, which is every run after the first. The result is a billet-owned directory holding a root-owned ledger, and a server that starts and cannot write.
Only entries owned by root are touched, only regular files, and only those with a single link: a hard link is the same inode under two names, and chowning one here would change the file it aliases wherever else that file lives.
func (*Converger) Revalidate ¶
Revalidate re-establishes that one unit is still what the plan decided about, immediately before acting on it.
THE PLAN IS OLD BY THE TIME ANYTHING HAPPENS. `billet check` talks to GitHub in between, which takes as long as the network takes, and in that window a unit can be edited and daemon-reloaded into something else — a different ExecStart, a different account, a privilege prefix — or a drain can begin, turning the inactive unit the plan saw into one that is deactivating. Acting on the older answer would start a service billet never validated, or start one through a drain that is waiting for the jobs on this host.
It cannot close the last instant; nothing short of holding systemd still can. It closes the seconds that `billet check` spends on the network, which is where the window actually is.
func (*Converger) Running ¶
Running reports what each wanted service is executing right now.
IT BELONGS TO THE BACKEND, not to a shared inspection. `down` refuses to act on an installation running a different build of billet, and the facts that refusal rests on — is it active, is the running process this build — are answered in a completely different way by each service manager. Getting them from a systemd inspector meant the refusal silently reported on systemd units on a host that has none.
func (*Converger) Services ¶
Services names this backend's two services, server first.
SHARED CODE MUST NOT KNOW THE UNIT NAMES. Every place the lifecycle commands talk about "the other service" — the bystander snapshot, the enablement comparison, the order `down` stops things in — needs a pair of identifiers, and reaching for deploy's systemd constants there quietly makes those commands systemd-only: a converger for another service manager would be handed `billet-server.service`, inspect a service that does not exist, find nothing, and report that nothing had changed. Asking the backend is what keeps the ORDER shared and the vocabulary local. The server is returned first, matching the order `up` starts them in.
func (*Converger) Snapshot ¶
Snapshot is what a unit looks like right now, as one comparable string.
It exists so a caller can prove that starting ONE service did not disturb another. billet refuses a unit that names the other one in its dependencies, but a transaction can reach further than that — a unit billet's own unit pulls in may itself conflict with the other service — and no practical amount of property-reading models what systemd will actually do. Comparing before and after does not need to: whatever the mechanism, a service that changed while billet was starting something else is a service billet disturbed.
func (*Converger) StartAndProve ¶
StartAndProve starts a unit and establishes that it stayed up.
START, THEN PROVE, THEN THE CALLER ENABLES — never `enable --now`. Enabling first commits a unit to every future boot before anything has established it can run at all, and unwinding that is a second mutation on a host that has just failed one.
The packaged units are Type=notify, so `systemctl start` does not return until billet has sent READY=1 or TimeoutStartSec has elapsed: its exit status IS the readiness signal. The window that follows exists for what readiness cannot cover — a service that reaches ready and then dies, which Restart=on-failure turns into a crash loop reading "active" at any instant.
func (*Converger) StartTimer ¶ added in v0.6.0
StartTimer starts a timer unit and proves it is armed.
NOT StartAndProve, which proves a PROCESS held its pid across a settle window: a timer has no process, and `active` is systemd saying the next elapse is scheduled, which is the whole fact a caller needs. Enable and start are two calls because `enable --now` commits a unit to every future boot before anything proved it can run, the rule every other unit here follows.
func (*Converger) StopAndProve ¶
StopAndProve stops a unit and establishes that it is actually gone.
THE EXIT STATUS IS NOT THE ANSWER, which is why this is not a one-line wrapper. `systemctl stop` returns when the job it queued completes, and a unit whose main process ignored SIGTERM is killed at TimeoutStopSec — after which the unit is inactive and `failed`. A caller about to report "this host is down" needs the state the manager holds now, not the return code of a command.
type Enablement ¶
type Enablement struct {
Enabled Tristate
// How is the manager's own word, for a diagnostic an operator can act on.
How string
}
Enablement is whether a service will start itself at the next boot.
A VERDICT AND A WORD, rather than the manager's raw string. systemd answers with one of a dozen words (`enabled`, `disabled`, `static`, `masked`, `linked`, `enabled-runtime`, `indirect`, …); launchd has no such string at all — a label is enabled when its plist is installed and its entry in the durable override database does not say `disabled`. Shared code comparing against the literal "disabled" is therefore code only systemd can satisfy, and a second backend would have to invent systemd's vocabulary to be understood.
Unknown is the common case rather than the exotic one: every systemd state except exactly `enabled` and exactly `disabled` lands there, because those are the only two billet has a rule for. That is deliberate — `up` acts only on a definite No, so anything it cannot classify refuses instead of being guessed at, including a state a future systemd adds.
type FileFacts ¶
type FileFacts struct {
Path string
Exists Tristate
Mode fs.FileMode
Owner string
Group string
// Info is what the stat returned, kept so a later privileged operation can
// prove it is acting on the same file rather than on the same NAME.
Info fs.FileInfo
// Err records why the facts are incomplete. A stat that failed for a reason
// other than absence leaves Exists Unknown and this set, so a caller reports
// the reason instead of inventing an answer.
Err error
}
FileFacts is what one path on disk is, as far as this process can see.
type Inspector ¶
type Inspector struct {
// contains filtered or unexported fields
}
Inspector reads a host. Every external dependency is a seam so the whole thing is testable without root, without systemd and without a real account database — none of which a unit test can supply.
func (*Inspector) Inspect ¶
func (i *Inspector) Inspect(ctx context.Context, configPath string, keyPaths []string) (Report, error)
Inspect gathers what this machine is doing with the given config.
It reports rather than refuses: a status command must answer on a host that is half-configured, which is exactly when somebody runs it. Callers that need a decision make it from these facts.
type Option ¶
type Option func(*Inspector)
Option configures an Inspector.
func WithSystemctl ¶
WithSystemctl names the systemctl binary, skipping the PATH lookup.
type OwnershipChange ¶
type OwnershipChange struct {
Path string
Owner string
Group string
Mode fs.FileMode
// Why names the failure this prevents, for the operator reading --dry-run.
Why string
// contains filtered or unexported fields
}
OwnershipChange is one file whose owner or mode `up` will correct.
type Refusal ¶
Refusal is a reason `up` will not proceed, and what to do about it. Both halves are required: a refusal an operator cannot act on is a dead end.
type RepairTarget ¶
RepairTarget is one entry a privileged billet command created inside a state directory, named relative to it.
Dir says the entry is a DIRECTORY rather than a file, and it is not cosmetic: a root-owned 0700 directory cannot even be TRAVERSED by the service account, so an authority restored under one is unreachable however its files are owned — and a directory's link count is always above one, which is the check that refuses a hard-linked file.
type Report ¶
type Report struct {
ConfigPath string
Config FileFacts
// AppKeys is one entry per target's key path, in the order given.
AppKeys []FileFacts
// Binary is the running billet's path, for display. The identity judgments
// deliberately do not use it: a pathname cannot survive being replaced.
Binary string
BinaryErr error
Server ServiceFacts
Node ServiceFacts
}
Report is what one inspection found.
type RunningFacts ¶
type RunningFacts struct {
Name string
// Active is whether a PROCESS IS RUNNING for this service — not whether the
// manager considers it healthy, and not whether it is settled. A service
// that is starting up or shutting down has a live process, and for every
// question this type is used to answer that is the same situation as one
// that is running.
//
// Three-valued because "billet could not tell" is an ordinary answer — a
// manager that did not reply, a state this build has no rule for — and it is
// NOT the same fact as "nothing is running". Collapsing it to a bool made
// the identity refusal skip a service it could not see, which is the
// direction that stops somebody else's job.
Active Tristate
// IsThisBuild is whether the process currently executing is the same build
// as the billet asking. Three-valued because "could not tell" is an ordinary
// answer — a process that exited between the two reads, a manager that
// reported no pid — and it is not the same fact as "no, it is different".
IsThisBuild Tristate
// Why explains the judgment, for a diagnostic that has to be actionable.
Why string
}
RunningFacts is what ANY service manager can say about a service that is running right now: which service, whether it is running at all, and whether the process it is running is this build of billet.
A DELIBERATELY NARROW SET, and the narrowness is the point. ServiceFacts carries a dozen properties that only systemd has — FragmentPath, DropInPaths, ReloadPending, ExecStartFlags, Namespace, Elevation — and every one of them exists because a systemd refusal is computed from it. Promoting those to a shared type gives a struct half of whose fields are permanently empty under the other manager, and a reader who has to know which half. These four are facts both managers genuinely answer, which is what makes them shareable.
type ServiceFacts ¶
type ServiceFacts struct {
Name string
LoadState string
ActiveState string
SubState string
UnitFileState string
Result string
Type string
User string
Group string
FragmentPath string
DropInPaths []string
MainPID int
NRestarts int
// ExecStart is the executable systemd WILL run, taken from systemd's own
// parse rather than from the unit file's text.
ExecStart string
// ExecStartCount is how many ExecStart directives the unit carries. More
// than one makes "which binary does this unit run" ambiguous, which a
// caller must treat as a refusal rather than reading the first.
ExecStartCount int
// ExecStartArgv is the whole command line systemd would run. The path alone
// does not say which ROLE a unit starts, and a billet that runs `server`
// under the node unit's name is a control plane nothing authorised.
ExecStartArgv string
// ExecExtra is every OTHER command the unit would run — ExecStartPre and
// its relatives. They are not covered by the ExecStart checks and several
// of them run before the main process, which for the server unit is the
// difference between an unprivileged control plane and a root one.
ExecExtra map[string]string
// Namespace is the set of properties that can replace the filesystem the
// unit sees. A RootDirectory or a bind mount makes the path in ExecStart
// name a different file than the one billet compared against.
Namespace map[string]string
// Elevation is the set of properties that change WHO the process is, past
// the User= and Group= the unit names. Measured on the packaged units: each
// of these is empty on both, and SupplementaryGroups is the sharpest —
// adding `docker` to the unprivileged server is root by another route.
//
// Environment is deliberately NOT here: an operator behind a proxy has a
// real reason to set it, and it cannot by itself change what runs or as
// whom.
Elevation map[string]string
// ExecStartFlags is what systemd's EXTENDED answer says about the command's
// prefixes, and it is the only trustworthy source for them.
//
// Measured on systemd 255: `ExecStart=+/usr/bin/billet server …` runs with
// full privileges whatever User= says, and `systemctl show -p ExecStart`
// renders it byte-identically to the unprefixed form — the prefix is simply
// not in that answer. `-p ExecStartEx` carries `flags=privileged` for the
// same unit and `flags=` for an ordinary one.
ExecStartFlags string
// ExecStartExCount is how many extended entries came back. Zero means
// systemd did not answer at all — the property landed in v246 — which is
// uncertainty about privilege rather than an absence of it.
ExecStartExCount int
// ExecStartFlagsKnown says whether the flags field was found at all.
ExecStartFlagsKnown bool
// Names is every name this unit answers to. An Alias= makes a second name
// resolve to the same unit, which is a way for `enable` to write links
// billet did not ask for.
Names []string
// Actions is the set of job settings that can act on the HOST or on other
// units when this one fails or finishes. Measured on the packaged units:
// OnFailureJobMode is "replace" and every other one is "none", so each has an
// ordinary value rather than being empty — `isolate` stops everything else,
// and `reboot` is exactly what it says.
Actions map[string]string
// Transaction is what starting or stopping this unit would do to OTHER
// units. `Conflicts=` on one billet unit stops the other as part of its own
// start, and `Requires=` starts it — neither is visible from anything else
// billet checks.
Transaction map[string]string
// StateDirectory and RuntimeDirectory are the directories systemd itself
// creates and makes writable for this unit, relative to /var/lib and /run.
// They are the authority on where a role may keep state: under
// ProtectSystem=strict everything else is read-only, so a config naming a
// different path is a service that will fail on its first write.
StateDirectory string
RuntimeDirectory string
// ReloadPending is systemd's own NeedDaemonReload: the unit file on disk has
// changed since the manager read it, so the bytes on disk and the unit the
// manager would run are different things.
ReloadPending Tristate
// ExecStartIsThisBuild answers whether the file the unit WOULD run is the
// same FILE as the running billet — by inode, so a hardlink or bind mount is
// recognised and a replacement at the same path is not mistaken for it.
ExecStartIsThisBuild Tristate
ExecStartWhy string
// RunningIsThisBuild answers the DIFFERENT question that matters once a
// service is up: whether the process systemd is running right now is this
// build. Replacing a binary without restarting leaves these two disagreeing,
// which is exactly the state nothing else on the host reports.
RunningIsThisBuild Tristate
RunningWhy string
// MatchesPackagedUnit answers whether the effective unit file is
// byte-identical to the one the PACKAGE ships. A difference is not an
// accusation: the Ansible role deliberately renders its own units, so a
// role-managed host differs here for good reasons.
MatchesPackagedUnit Tristate
}
ServiceFacts is what systemd says about one unit, plus the judgments a caller cannot make from the raw properties alone.
func (ServiceFacts) Active ¶
func (s ServiceFacts) Active() bool
Active reports whether systemd is running this unit right now.
func (ServiceFacts) Installed ¶
func (s ServiceFacts) Installed() bool
Installed reports whether systemd knows this unit at all.
func (ServiceFacts) PulledUnits ¶
func (s ServiceFacts) PulledUnits() []string
PulledUnits lists what starting this unit would pull in with it, so a caller can ask each of those what IT would do.
ONE LEVEL, DELIBERATELY. The full closure is systemd's to compute through units billet has never heard of, and trying to reproduce it was wrong in four successive attempts. One level covers the shape anyone has actually constructed — billet's own unit naming an ordinary-looking helper that conflicts with the other service — and what it cannot cover is caught after the fact by comparing the other service before and after.
func (ServiceFacts) Running ¶
func (s ServiceFacts) Running() RunningFacts
Running renders the subset of these facts that describes what the service is executing at this moment.
ONLY TWO STATES PROVE NOTHING IS RUNNING, and this is an allowlist for the same reason `StopAndProve` uses one. A denylist here was a defect: it read "not exactly active" as No, which made `deactivating` — a unit still stopping, with its process ALIVE — a definite "nothing is running". `down` waves a definite No through its identity refusal, so a foreign build caught mid-reload or mid-stop would have been stopped without anybody being asked, and stopping the server destroys the leases its listener holds.
An empty answer is systemd declining to say, and a state a future systemd adds is one billet has no rule for. Both are Unknown, which refuses.
type StopResult ¶
type StopResult struct {
// Gone is whether the service's process is PROVED gone.
//
// THREE-VALUED BECAUSE BOTH MANAGERS REALLY HAVE THE THIRD. An empty
// systemd ActiveState and a launchctl reply billet cannot parse are both
// "the manager did not tell us", which is uncertainty rather than absence.
// A two-valued type loses exactly that arm, and it is the one a caller must
// refuse on.
Gone Tristate
// How is the manager's own account of the ending, printed verbatim by the
// caller. systemd fills it from ActiveState and Result; launchd from whether
// the service is still in its domain and whether the pid it named is alive.
How string
}
StopResult is what a service manager can say about a service it was asked to stop, in terms EVERY service manager can answer honestly.
IT IS NOT systemd's VOCABULARY, deliberately. `ActiveState` and `Result` are systemd's words for systemd's states, and a second manager has no honest value for them: after a launchd `bootout` the service is simply GONE FROM ITS DOMAIN, which is a different fact from "inactive (success)". Rendering one as the other is not a translation, it is a fabricated proof — and it would be read as one by the code whose whole job is to refuse anything that is not proof. A NON-NIL ERROR NEVER ACCOMPANIES Gone: Yes. The two halves are one answer: a backend that proved the process gone reports no error, and a backend that returns an error has not proved anything. Callers act on the error first, so a `Yes` alongside one would be discarded — and a backend that did that would have its successful stops silently reported as failures.
type Tristate ¶
type Tristate int
Tristate is an answer that may be UNKNOWN, because "could not tell" is not "no". Collapsing the third state is how a refusal becomes a silent pass on the one host where the check mattered — a stat that fails on permissions is not evidence a file is absent, and an unreadable unit is not evidence it matches.
type UnitPlan ¶
type UnitPlan struct {
Name string
// Start is false when the unit is already active: `up` never restarts a
// running service, because a restart is a drain and a drain destroys jobs.
Start bool
// Enable is false when the unit is already enabled.
Enable bool
// EnableBeforeStart inverts the order of the two steps above, because some
// service managers cannot start a service that is not enabled.
//
// systemd can: `systemctl start` works on a disabled unit, which is exactly
// what lets `up` prove a service runs BEFORE committing it to every future
// boot. launchd cannot — a label carrying a disabled override refuses to
// bootstrap at all — so on that manager there is no order in which the proof
// comes first.
//
// WHERE THIS IS SET, THE PROOF NO LONGER PRECEDES THE COMMITMENT, and what
// protects the host instead is the unwinding: a run that fails afterwards
// undoes exactly the enablement it performed. That is a weaker guarantee
// than the Linux one, so `up` says which one it is giving rather than
// letting the two read alike.
EnableBeforeStart bool
// Detail is this manager's own phrase for what enabling a service does,
// printed verbatim.
Detail string
}
UnitPlan is what `up` will do with one service.
type UpPlan ¶
type UpPlan struct {
Refusals []Refusal
Ownership []OwnershipChange
Units []UnitPlan
// ServerState is the directory the server unit keeps its ledger in, taken
// from the unit's own StateDirectory=. Empty when no server is wanted.
ServerState string
}
UpPlan is everything `up` would do, computed before anything is touched so that --dry-run and the real run are the same decision.
type UpRequest ¶
type UpRequest struct {
ConfigPath string
// KeyPaths are every target's App key files. A deployment serving several
// GitHub targets holds one key per target, and each is inspected, contained
// and owned the same way.
KeyPaths []string
// ServiceUser and ServiceGroup are the identity the packaged units run as.
ServiceUser string
ServiceGroup string
// ServerStateDir and NodeStateDir are what the CONFIG says, so they can be
// checked against what the units actually make writable.
ServerStateDir string
NodeStateDir string
NodeLockDir string
// WantServer and WantNode follow the config's own sections: a host converges
// the roles its configuration defines, not a fixed pair.
WantServer bool
WantNode bool
// BinaryDir is the directory the services' executable lives in, which the
// updater the services start has to be able to rename into. Only a manager
// whose services run as an unprivileged account checks it; empty means no
// such check.
BinaryDir string
}
UpRequest is what the caller wants converged.