pub struct ServiceOperations { /* private fields */ }Expand description
Install, uninstall, inspect, and switch start mode.
This is the whole of the library contract f3 builds service install,
service uninstall and service status on. Nothing here prints; every
operation returns a value with a fmt::Display a command can write, so the
same facts are available to the TUI and to a JSON document without being
re-derived from text.
Implementations§
Source§impl ServiceOperations
impl ServiceOperations
Sourcepub fn on_this_host(paths: AppPaths) -> Self
pub fn on_this_host(paths: AppPaths) -> Self
Operates on this host’s real service managers.
Sourcepub fn with_controls(
paths: AppPaths,
identity: ServiceIdentity,
controls: Arc<dyn ControlFactory>,
) -> Self
pub fn with_controls( paths: AppPaths, identity: ServiceIdentity, controls: Arc<dyn ControlFactory>, ) -> Self
Operates on the controls a caller supplies.
Both other arguments are explicit for the same reason: the privileged
installer tests register a real service under a
ServiceIdentity::fixture name, against a disposable
AppPaths::rooted_at tree, and must not be able to touch an operator’s
installation even by mistake.
Sourcepub fn with_runner_root(self, root: LocalAbsolutePath) -> Self
pub fn with_runner_root(self, root: LocalAbsolutePath) -> Self
Points runner-root preparation at a directory the caller owns, for the current account.
Only a ServiceIdentity::fixture registration is allowed to move
it, and the guard is here rather than at the call site. A product
registration ignores the override entirely and resolves
crate::runner_root::default_runner_root itself, which is what keeps
“custom roots are never re-ACLed” true no matter what a caller passes.
It exists because the directory the product uses is
%SystemDrive%\rman, and a smoke test that created and re-permissioned
that would be editing the machine it runs on from outside its own
fixture. So a fixture aims this at a temporary directory it created and
will delete — and “and will delete” is why the override is always
reconciled for the calling account rather than for the account the start
mode obliges. A boot-mode root admits SY and BA only, which an
ordinary filtered token is neither, so a test that supplied one could
not then inspect or remove its own temporary directory.
Sourcepub const fn identity(&self) -> &ServiceIdentity
pub const fn identity(&self) -> &ServiceIdentity
The registration this operates on.
Sourcepub fn install(
&self,
request: &InstallRequest,
) -> Result<Installed, ServiceError>
pub fn install( &self, request: &InstallRequest, ) -> Result<Installed, ServiceError>
Registers daemon run with the operating system.
The order is the contract, and each step is a requirement:
- the four directories are created, because item 2 is about writing them;
- the single-instance lock is taken — item 1. It is held for the whole of the install rather than probed and released, so a daemon that starts halfway through cannot end up racing a registration;
- neither start-mode domain already holds a registration — item 6’s record would otherwise describe one of two;
- the binary path is resolved and confirmed to be a file — item 6;
- the platform registers it;
- the record is written.
§Errors
ServiceError::LockHeld when an agent is already running,
ServiceError::AlreadyInstalled, ServiceError::BinaryMissing, and
whatever the platform reports.
Sourcepub fn uninstall(&self) -> Result<Uninstalled, ServiceError>
pub fn uninstall(&self) -> Result<Uninstalled, ServiceError>
Deregisters, and deletes nothing else.
§Errors
Whatever the platform reports. A missing registration is not an
error: service uninstall on a host that has none should say so and
exit cleanly, because an operator running it twice has not made a
mistake.
Sourcepub fn set_start_mode(
&self,
to: StartMode,
) -> Result<StartModeChange, ServiceError>
pub fn set_start_mode( &self, to: StartMode, ) -> Result<StartModeChange, ServiceError>
Switches between boot and login without re-resolving anything.
05-infrastructure.md item 7. Everything the new registration carries —
the absolute binary path, the arguments, the restart policy, the four
directories — comes from the existing record, so the operator is not
asked to reinstall the product and a binary that has since moved is not
silently swapped for whichever one happens to be running this command.
On Windows the registration necessarily moves between two different Windows facilities, because Windows has no service that starts at logon; see this module’s documentation. That is still not a reinstall: no file is downloaded, replaced, or re-resolved.
The caller must persist the new mode onto this host’s Host record so
that host show reports it — StartModeChange::to is the value, and
f1 already emits host.service_start_mode from that field.
§Errors
ServiceError::NotInstalled when there is no record to switch, and
whatever the platform reports.
Sourcepub fn start(&self) -> Result<(), ServiceError>
pub fn start(&self) -> Result<(), ServiceError>
Sourcepub fn stop(&self) -> Result<bool, ServiceError>
pub fn stop(&self) -> Result<bool, ServiceError>
Sourcepub fn status(&self) -> Result<ServiceStatus, ServiceError>
pub fn status(&self) -> Result<ServiceStatus, ServiceError>
Everything Journey 5 step 4 asks service status to report.
§Errors
ServiceError::Record when local state cannot be read, and whatever
the platform reports. A stale binary path is deliberately not an
error here: it is a reported state, because a status command that
refused to print anything else would hide the very facts an operator
needs in order to fix it.
A record this account may not read is reported for the same reason
and is the second exception. On a boot-mode host the record was written
by sudo service install, and a version before this one wrote it 0600
— so the operator’s own service status ended with Permission denied
and printed nothing at all, on a host whose registration the service
manager would have described perfectly well. What launchd, systemd or
the SCM says is a separate fact from the record, and it is still worth
having.
§A registration made from another data root
The service manager holds one registration per machine under one name,
and every account on the machine sees it. When its command line names
application-data directories that are not this account’s, it is
another installation’s service, and comparing this account’s
install record against it would report a stale binary, a mismatched
start mode and a secret store in the wrong scope, each with a remedy
(service uninstall, service install, auth login) that, followed
from this account, breaks a healthy service. So it is reported as what
it is: see ForeignService.
Sourcepub fn definition_drift(&self) -> Result<Option<DefinitionDrift>, ServiceError>
pub fn definition_drift(&self) -> Result<Option<DefinitionDrift>, ServiceError>
The installed definition, when it is not what this build renders for
the recorded installation. See [definition_drift].
§Errors
Whatever reading the install record reports.
Sourcepub fn foreign_service(&self) -> Option<ForeignService>
pub fn foreign_service(&self) -> Option<ForeignService>
The machine’s service and the installation it belongs to, when that is
not this account’s, without reading anything of that installation’s
beyond its path. None when the service is this account’s, when there
is none, or when the service manager cannot be asked.
Sourcepub fn forget_local(
&self,
at: DateTime<Utc>,
) -> Result<ForgottenLocal, ServiceError>
pub fn forget_local( &self, at: DateTime<Utc>, ) -> Result<ForgottenLocal, ServiceError>
Moves this account’s own configuration aside, when no registration on
this machine runs against it: host forget-local.
Everything in the configuration directory – the install record, the
database with its host row and policies, runner.env and the rest – is
renamed into a forgotten-<timestamp> directory beside it, never
deleted, so moving it back restores it exactly. The state, runtime and
log directories are left alone even where the platform puts them inside
the configuration directory (macOS does), and so is every secret store
and every directory a registration names.
Both domains are asked, strictly: a domain that cannot be asked might hold this account’s own registration, so it refuses rather than reads as empty.
§Errors
ServiceError::LocalConfigurationInUse when a registration on this
machine runs against these directories, when a registration from
another data root names a directory overlapping them, or when an agent
holds this account’s single-instance lock. ServiceError::Record when
the move fails; whatever had already moved is put back first, and
anything that could not be put back is named.