# Services & jobs

---

# peinit

_Peios / Using Peios / Services & jobs_

> peinit is PID 1 — the single service manager. Services, jobs, and operations; identity built in; and how it differs from the init systems you know.

**peinit** is the init system of Peios — the first userspace process, running at PID 1 for the lifetime of the machine. It is a single-threaded program with one job: manage services, from the first one started at boot to the last one stopped at shutdown. Every supervised process on a Peios system — a long-running daemon, a one-shot setup task, a health check, a scheduled job — is started, watched, and reaped by peinit.

What makes peinit its own thing is that **identity is built in**. peinit does not just launch processes; it launches them *as someone*. Each service runs under a [KACS token](/peios/security-fundamentals/tokens/overview.md) that decides what it can reach, and each service is itself a securable object with a [security descriptor](/peios/security-fundamentals/security-descriptors/overview.md) that decides who may start, stop, or query it. Service management and access control are the same system, not two systems bolted together.

This page sketches what peinit is responsible for, names the handful of ideas that make it unlike other init systems, and points at the pages that cover each one.

## peinit in one sentence

**peinit is the sole service manager and PID 1 — it reads service definitions from the registry, starts them in dependency order under per-service identities, supervises them through a defined state machine, and tears them down in reverse at shutdown.**

Everything in this topic is an elaboration of that sentence: what a *service definition* is, what *dependency order* means, what the *state machine* looks like, how *identity* is materialised, and how you drive the whole thing from a shell.

## Three kinds of object

peinit is easiest to understand through the three first-class objects it works with. Keeping them straight is most of the battle when you read a status output or an event log.

| Object | What it is | Lifetime |
|---|---|---|
| **Service** | A *definition* — a named unit of execution with identity, policy, and configuration, stored in the registry. It has a runtime state (the [state machine](/peios/using-peios/services-and-jobs/the-service-lifecycle.md)) and, when running, a process. | Long-lived. Persists across restarts and reboots. |
| **Job** | A single *process execution*. Every time peinit forks — a service's main process, a hook, a health check, an ad-hoc run — that is one job, with its own GUID and exit result. | Short-lived. A restart creates a *new* job. |
| **Operation** | A *requested action* on a service — start, stop, restart, reload, reset. Control commands create operations that are validated, queued, and executed. | Short-lived. Resolves to a terminal state, then is dropped. |

A service is the *what*; a job is *what actually ran*; an operation is *what someone asked for*. A single `restart` command, for example, is one **operation**, which stops the current **job** and starts a new one, all against one **service**. Jobs and operations both carry GUIDs and are emitted to [eventd](/peios/security-fundamentals/auditing/overview.md) as they complete, which is how the history of a service is reconstructed after the fact. They get their own page: [Jobs and operations](/peios/using-peios/services-and-jobs/jobs-and-operations.md).

Underneath all three sits the fourth thing peinit is always handling — the **token**, the identity a service runs under. Tokens are not peinit's invention; they are the [system-wide identity object](/peios/security-fundamentals/tokens/overview.md). peinit's role is to obtain the right token for each service and install it before the process runs. That is the subject of [Service identity and privileges](/peios/using-peios/services-and-jobs/identity-and-privileges.md).

## What peinit is not

peinit borrows vocabulary from init systems you may already know, and the familiarity is a trap. Clearing three wrong mental models is most of understanding what peinit actually is.

**It is not systemd.** There are no unit files. peinit does not read, parse, or translate `.service` files, and there is no migration shim. A service definition is a key in the [registry](/peios/using-peios/registry-concepts/overview.md) under `Machine\System\Services\`, made of typed registry values — not a text file under `/etc`. Some *interfaces* are deliberately compatible where that helps: peinit speaks the `sd_notify` readiness protocol, and timer schedules use systemd's `OnCalendar` calendar-expression format. The compatibility stops at those wire formats; the model underneath is different.

**It is not Windows SCM.** The influence is real — services as securable objects with per-service descriptors, token-based service identity, an access-controlled control interface, a structured state machine — but it is *architectural*, not interface-level. peinit does not implement the SCM RPC protocol, Windows service types, or Windows control codes.

**It is not a logging system.** peinit holds a service's `stdout`/`stderr` pipes at birth, so it controls *where* output goes — but storage, indexing, and queries are [eventd](/peios/security-fundamentals/auditing/overview.md)'s job. peinit forwards; eventd is the historian. The same split applies to job and operation history: peinit emits structured events and forgets them; eventd keeps them. See [Service output and logging](/peios/using-peios/services-and-jobs/output-and-logging.md).

A fourth, smaller correction: **peinit does not supervise forking daemons.** A service that double-forks to "daemonise" is solving a problem that does not exist when the manager tracks the process it spawned. peinit tracks every process by [pidfd](/peios/using-peios/services-and-jobs/service-types.md); a legacy binary that insists on double-forking must be wrapped at the package layer.

## The one operational invariant

peinit is **single-threaded PID 1**, and that one fact explains a surprising amount of its design. In a single-threaded PID 1, a blocking system call blocks *everything* — child reaping, watchdog expiry, shutdown signals, every other event stops while the call is stuck. So peinit's hard rule is that **its event loop never blocks on a userspace service.**

Two consequences you will meet repeatedly:

- **peinit operates on an in-memory snapshot of the registry, not a live view.** It reads all service definitions synchronously *once*, during boot, and thereafter works from an in-memory model that it updates from [change notifications](/peios/using-peios/registry-concepts/watches.md). Supervision never waits on the registry being available. This is why a configuration change does not always take effect immediately — see [Defining a service](/peios/using-peios/services-and-jobs/defining-a-service.md).
- **Anything that could block is pushed off the main loop.** Filesystem condition checks run in a short-lived forked helper rather than a `stat()` on the event loop; token requests to authd are driven by a timed state machine, not a blocking call; log delivery is best-effort and never exerts back-pressure on peinit.

You do not need to think about the event loop to operate peinit, but it is the reason behind several behaviours that would otherwise look arbitrary.

## Where peinit sits in the system

peinit is part of the [Trusted Computing Base](/peios/security-fundamentals/boot-and-trust-establishment/overview.md): a compromise of peinit is a compromise of the whole machine. It runs as [SYSTEM](/peios/security-fundamentals/identity/well-known-principals.md) with all privileges, and it never drops that identity. It depends on, and is depended on by, the other TCB components:

| peinit relies on | For |
|---|---|
| **KACS** | Service identity (installing tokens on children), control-interface authentication (peer token + AccessCheck), and per-service access control. See [Access decisions](/peios/security-fundamentals/access-decisions/overview.md). |
| **LCS / the registry** | Service definitions, boot configuration, and its own parameters. See [The registry](/peios/using-peios/registry-concepts/overview.md). |
| **registryd** | The registry *source* daemon — the first service peinit starts. peinit treats it as an opaque dependency. (`registryd` is an interface; the default implementation is `loregd` — see [Boot and boot modes](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md).) |
| **authd** | Minting tokens for non-platform services. peinit requests; authd mints. |
| **eventd** | Service logs (forwarded over a socket) and job/operation/audit events (emitted through KMES). |
| **JFS** | Delivering ad-hoc job submissions — a service asking peinit to run a process on a client's behalf. |

The bootstrapping puzzle — authd needs the registry, the registry needs to be started by *something*, and that something is peinit running before any of them exist — is resolved by the boot model in [Boot and boot modes](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md).

## Where to start

If you are configuring a system, start with **[Defining a service](/peios/using-peios/services-and-jobs/defining-a-service.md)** — what a service definition is, where it lives, and the rule that explains why some changes take effect immediately and others wait for a restart.

If you want to *operate* a running system — start, stop, query, and troubleshoot services — go to **[Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md)** for the command set and **[The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md)** to read what a status actually tells you.

If you care about *what runs as whom*, read **[Service identity and privileges](/peios/using-peios/services-and-jobs/identity-and-privileges.md)** and **[Who can manage a service](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md)** — the two independent halves of the security model.

If you are investigating boot behaviour, **[Boot and boot modes](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md)** covers Full, Safe, and Recovery modes and the escalation between them.

And when something is wrong, **[Troubleshooting peinit](/peios/using-peios/services-and-jobs/troubleshooting.md)** works backward from the symptom.

---

# Defining a service

_Peios / Using Peios / Services & jobs_

> A service is a registry key under Machine\System\Services\, made of typed values. The schema, and the mutability class that decides when a change lands.

A service is defined by a single key in the [registry](/peios/using-peios/registry-concepts/overview.md), under `Machine\System\Services\<name>`. The key's name *is* the service name, and the typed [values](/peios/using-peios/registry-concepts/keys-values-and-types.md) inside it are the definition — the binary, who it runs as, what it depends on, how it is supervised. There is no file under `/etc`; there is a registry key.

peinit reads these definitions in two situations: once at boot, to build the service graph, and on demand, when an administrator starts a service or runs `reload-config`. Between those reads it works from an in-memory copy. That last fact is the source of the most common "why didn't my change take effect?" question, and the second half of this page is devoted to it.

## Service names

A service name must be made of characters from `[A-Za-z0-9._-]` and be 1–128 bytes long. Anything else is a validation error. Two characters are pointedly excluded:

- **`/`** — names map directly onto cgroup ids and registry key names, and a slash would be ambiguous in both.
- **`:`** — reserved for peinit-internal synthetic names (for example, the way a hook job is labelled).

The name is how you refer to the service everywhere: in `peiosctl` commands, in another service's dependency list, in a [ServiceSecurity](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md) descriptor, and in the [per-service SID](/peios/using-peios/services-and-jobs/identity-and-privileges.md) derived from it.

## The definition schema

A definition is a set of typed registry values. Rather than list all of them in one wall of rows, the tables below group the fields by what they are *for*, with a pointer to the page that explains each group in depth. Every field is optional unless noted; the only hard requirement is `ImagePath`. The full type-and-default catalog lives in the [Registry key reference](/peios/using-peios/services-and-jobs/registry-key-reference.md).

**What to run** — the binary and its execution context.

| Field | Default | Purpose |
|---|---|---|
| `ImagePath` *(required)* | — | Absolute path to the service binary. |
| `Arguments` | — | Argument list passed to the binary. |
| `WorkingDirectory` | `/` | Working directory for the process. |
| `Environment` | — | `KEY=VALUE` pairs added to the environment. |
| `RuntimeDirectories` | — | Private directories created under `/run` just before the process starts. See [The execution environment](/peios/using-peios/services-and-jobs/execution-environment.md). |
| `LimitNOFILE`, `LimitCORE` | — | `RLIMIT_NOFILE` / `RLIMIT_CORE`. |

**What kind of service** — type and readiness. See [Simple and Oneshot services](/peios/using-peios/services-and-jobs/service-types.md).

| Field | Default | Purpose |
|---|---|---|
| `Type` | Simple | `Simple` (long-running) or `Oneshot` (run-to-completion). |
| `Readiness` | Notify | `Notify` (`READY=1` via sd_notify) or `Alive` (ready when the process exists). Ignored for Oneshot. |
| `RemainAfterExit` | 0 | Oneshot only — stay in `Completed` after a successful exit. |
| `SuccessExitCodes` | — | Non-zero exit codes to treat as success. |

**When to start** — triggers and conditions. See [Triggers and timers](/peios/using-peios/services-and-jobs/triggers-and-timers.md).

| Field | Default | Purpose |
|---|---|---|
| `Triggers` | — | `boot` and/or `timer:<schedule>`. Absent = demand-only. |
| `Disabled` | 0 | If 1, triggers must not activate the service (manual start still allowed). |
| `SafeMode` | 0 | If 1, attempt to start in [Safe mode](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md). |
| `Conditions`, `Asserts` | — | Start-time checks. A failed condition *skips*; a failed assert *fails*. |

**Who it runs as** — identity and privileges. See [Service identity and privileges](/peios/using-peios/services-and-jobs/identity-and-privileges.md).

| Field | Default | Purpose |
|---|---|---|
| `Identity` | `LocalService` | Principal name or SID for the service token. |
| `RequiredPrivileges` | — | Privilege allow-list; everything else is stripped from the token. |
| `HookIdentity` | service's `Identity` | Identity for `ExecStartPre`/`ExecStartPost` hooks. |

**How it relates to other services** — dependencies. See [Dependencies and ordering](/peios/using-peios/services-and-jobs/dependencies.md).

| Field | Purpose |
|---|---|
| `Requires` | Hard dependencies — must be satisfied first; their failure fails this service. |
| `Wants` | Soft dependencies — started first if present, but optional. |
| `BindsTo` | Runtime coupling — if the target stops, this stops too. |
| `Conflicts` | Mutual exclusion — starting this stops the named services. |
| `OnFailure` | Service to start when this one enters `Failed`. |

**How it is kept alive** — supervision and health. See [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md).

| Field | Default | Purpose |
|---|---|---|
| `ErrorControl` | Normal | `Normal` (stay Failed) or `Critical` (sync + reboot on irrecoverable failure). |
| `RestartPolicy` | OnFailure | `Never` / `OnFailure` / `Always`. |
| `RestartMaxRetries`, `RestartWindow`, `RestartDelay` | 5 / 120 / 1 | Restart budget, the window of health that resets it, and the backoff base. |
| `HealthCheck`, `HealthCheckInterval`, `HealthCheckTimeout`, `HealthCheckRetries` | — / 30 / 5 / 3 | Active health-check command and its timing. |
| `WatchdogTimeout` | 0 | Expected interval between `WATCHDOG=1` pings; 0 disables. |

**The transition phases** — hooks and timeouts. See [The execution environment](/peios/using-peios/services-and-jobs/execution-environment.md) and [The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md).

| Field | Default | Purpose |
|---|---|---|
| `ExecStartPre`, `ExecStartPost` | — | Commands run before the binary / after readiness. |
| `ExecReload` | (SIGHUP) | Reload command or `signal:<NAME>`. |
| `StartTimeout`, `StopTimeout` | 30 / 10 | Seconds for the whole start sequence / between SIGTERM and SIGKILL. |

**The remaining knobs** — scheduling, notify, fds, metadata.

| Field | Default | Purpose |
|---|---|---|
| `TimerPersistent`, `TimerJitter` | 1 / 0 | Catch up missed timer runs after reboot / random delay per firing. |
| `NotifyAccess` | Main | Who may send sd_notify messages (only `Main` is supported). |
| `FdStoreMax` | 0 | Size of the per-service fd store; 0 disables it. |
| `ServiceSecurity` | inherit | Security descriptor controlling who may manage the service. |
| `DisplayName`, `Description` | — | Human-readable labels for status output. |

### Forward compatibility

The schema version lives at `Machine\System\Services\SchemaVersion` (currently `1`). peinit is deliberately forward-compatible:

- **Unknown values are ignored.** A definition written for a newer peinit does not break an older one.
- **A newer schema version does not block boot.** peinit logs a warning and continues.
- **The schema only grows.** New capability arrives as new optional fields, never as a breaking change to an existing one.

One defensive rule cuts the other way: a *known* field must not appear more than once in a collected definition. A duplicated known field is a validation error, even though the registry would ordinarily give you at most one value per name.

## peinit works from a snapshot, not the live registry

Here is the idea that explains most surprises. peinit does **not** re-read the registry every time it touches a service. It reads definitions at well-defined moments and operates on an in-memory model in between.

Two layers of snapshotting stack on top of each other:

- **Boot generation.** At the start of boot, peinit reads *all* definitions, builds the dependency graph, and validates it. The whole boot runs against that one snapshot. Changes made *during* boot — by an install script, a post-hook — do not perturb the boot in progress.
- **Activation generation.** When peinit starts a specific service, it snapshots *that* service's definition for the entire start. Pre-exec hooks, the token request, the readiness timeout, the first health checks — all use the values captured at activation. Edit a field while the service is `Starting`, and the edit waits for the next start.

peinit learns about registry edits through [change notifications](/peios/using-peios/registry-concepts/watches.md): it subscribes to `Machine\System\Services\` and `Machine\System\Init\` at boot, and processes events in its event loop at a time of its choosing. If the notification queue overflows — a bulk admin operation, a flurry of scripted writes — peinit detects the overflow marker and does a full `reload-config` to resynchronise. You never have to think about the queue; you do have to know that *a change is picked up, not pushed*, and that when it takes *effect* depends on the field.

## Field mutability: when a change takes effect

Every field falls into one of four mutability classes. This table is the one to keep handy.

| Class | When a change takes effect | Fields |
|---|---|---|
| **Immutable at runtime** | Next **restart** only. | `ImagePath`, `Type`, `Identity`, `RequiredPrivileges`, `ErrorControl` |
| **Apply on next start** | Next **start** or explicit graph reload — not while running. | `Requires`, `Wants`, `BindsTo`, `Conflicts`, `OnFailure`, `Conditions`, `Asserts` |
| **Hot-reloaded** | Next relevant **event**, no restart. | `ServiceSecurity` (next control request) |
| **Reloadable at runtime** | Next relevant **operation** (restart, health-check cycle, …), no restart. | `Arguments`, `SuccessExitCodes`, all timeout/retry values, the health-check fields, `RestartPolicy`, `Environment`, `WorkingDirectory`, the hooks, `Readiness`, `NotifyAccess`, `LimitNOFILE`/`LimitCORE`, `FdStoreMax`, `SafeMode`, `DisplayName`, `Description` |

The practical reading: changing *what a process is or runs as* (`ImagePath`, `Identity`, `Type`, privileges, `ErrorControl`) is fundamental enough that it only applies when a fresh process starts — you must `restart`. Changing *policy that peinit consults each time it acts* (timeouts, restart behaviour, health checks) is picked up the next time peinit acts. And `ServiceSecurity` is special: it is re-read on every control request, so an access-control change takes effect on the very next command without touching the running service.

For inactive services the rules are simpler, because there is no running process to protect: a new service entry becomes available once the change notification is processed; a changed timer arms on the next evaluation; a changed dependency takes effect on the next start.

> [!NOTE]
> `reload-config` is the explicit, atomic way to make peinit re-read *everything*. It builds and validates a complete new graph snapshot and swaps it in only if validation passes; running services are not disturbed. It is covered with the rest of the command set in [Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md).

## Removing a service definition

Deleting a definition from the registry does **not** kill a running instance. peinit learns of the removal through the same notification path and behaves according to whether the service is running:

- **Not running** (Inactive, Failed, Completed, Skipped, Abandoned): peinit discards the in-memory entry immediately. There is nothing to supervise.
- **Running** (Active, Starting, Reloading, Backoff, Stopping): the running process is a [job](/peios/using-peios/services-and-jobs/jobs-and-operations.md), and a job outlives its definition. peinit marks the entry **definition-removed**, keeps the cached definition only to finish supervising the existing instance, and does *not* restart it when it exits. Once it exits, the entry — and any stored fds — are discarded.

While an entry is definition-removed it keeps satisfying its dependents (it is still running), `stop` still works so you can drain it cleanly, but `start`, `restart`, and `reload` are rejected with `UNKNOWN_SERVICE` — there is no definition to start from. A `status` query reports it with a `definition_removed: true` flag so the draining instance is never invisible.

> [!IMPORTANT]
> Removing a definition is not how you stop a service. It stops *future management*, not work in progress. To actually stop a running service, `stop` it. To prevent it from auto-starting, set `Disabled=1` (see [Triggers and timers](/peios/using-peios/services-and-jobs/triggers-and-timers.md)). To prevent it from being started at all, deny `SERVICE_START` in its [ServiceSecurity](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md) descriptor.

## Where to start

To understand the `Type` and `Readiness` fields and the Simple/Oneshot split, read [Simple and Oneshot services](/peios/using-peios/services-and-jobs/service-types.md).

To understand how a definition becomes a running, supervised process — and what each state in a `status` output means — read [The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md).

For the complete type-and-default catalog of every registry key peinit reads, see the [Registry key reference](/peios/using-peios/services-and-jobs/registry-key-reference.md).

---

# Simple and Oneshot services

_Peios / Using Peios / Services & jobs_

> peinit has exactly two service types — long-running Simple and run-to-completion Oneshot — plus the readiness models that decide when each counts as started.

A service's **type** answers one question: does the process keep running, or does it run once and exit? peinit supports exactly two answers — **Simple** and **Oneshot** — set by the `Type` field (default Simple). The type is independent of *when* the service starts; a Oneshot can be boot-triggered, timer-triggered, or demand-only, exactly like a Simple service.

## Simple services

A **Simple** service is a long-running daemon. peinit forks, installs the [token](/peios/using-peios/services-and-jobs/identity-and-privileges.md), and execs the binary. The process *is* the service: while it runs, the service is running; when it exits, the service has stopped. This is the default and covers the large majority of services — `registryd`, `authd`, `sshd`, application daemons.

The interesting question for a Simple service is *when does it count as started* — the moment its [dependents](/peios/using-peios/services-and-jobs/dependencies.md) are allowed to begin. That is the **readiness** model, set by the `Readiness` field.

### Readiness: Notify vs Alive

| Readiness | Ready when… | Use for |
|---|---|---|
| **Notify** *(default)* | The service sends `READY=1` via [sd_notify](#the-sd-notify-contract). | Any service that has meaningful startup work — opening a socket, loading state, connecting to a backend. The dependent genuinely should wait. |
| **Alive** | The process exists — readiness is immediate. | Services with no startup handshake, where "the process is up" is as good a signal as you will get. |

The distinction matters because it is a *promise to dependents*. With **Notify**, a service that declares itself a `Requires` target is telling peinit "do not start anything that needs me until I say `READY=1`." With **Alive**, peinit can only promise that the process was forked — not that it is functional.

> [!WARNING]
> Using `Readiness=Alive` on a service that other services `Requires` is a common configuration smell. peinit will let it boot, but it logs a validation **warning**: Alive readiness gives no guarantee the service is actually serving before its dependents start. If a dependent connects on boot and gets connection-refused, this is usually why. Prefer Notify wherever the service can signal it.

Once ready, a Simple service transitions to **Active** and stays there until its process exits or it is stopped. What happens on exit — clean exit, crash, restart — is the subject of [The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) and [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md).

## Oneshot services

A **Oneshot** service is a run-to-completion task. peinit forks, installs the token, execs the binary, and waits for it to exit. It is the right type for setup work: creating directories, initialising a database, running a schema migration, applying a one-time fixup at boot.

For a Oneshot, **readiness is exit, not a signal**. The `Readiness` field is ignored entirely — `READY=1` is meaningless for a process whose whole job is to finish. Success and failure are decided by the exit code:

- **Exit 0** (or any code listed in `SuccessExitCodes`) → the service **succeeded**.
- **Any other exit, or death by signal** → the service **failed**, and the [failure policy](/peios/using-peios/services-and-jobs/supervision.md) applies.

`SuccessExitCodes` is for binaries that signal a meaningful outcome with a non-zero code — for example, a tool that exits `2` to mean "nothing to do." Each entry is a decimal `0`–`255`; code `0` is always success and need not be listed.

### Completed, and RemainAfterExit

A successful Oneshot transitions to **Completed**, and what happens next depends on `RemainAfterExit`:

| `RemainAfterExit` | After a successful exit |
|---|---|
| **0** *(default)* | The service passes *through* `Completed` — releasing its dependents — then transitions to `Inactive`. |
| **1** | The service *stays* in `Completed`. |

Both forms satisfy dependents; the only difference is what a later `status` query shows. Use `RemainAfterExit=1` when "this task is done" is a state worth seeing — a boot-time migration you want to confirm ran, say — rather than a service that quietly returns to `Inactive`.

### Oneshot timing and hooks

Two details distinguish a Oneshot's start sequence from a Simple one:

- **`StartTimeout` covers the entire run.** For a Simple service the timeout covers startup until readiness; for a Oneshot it covers everything from the first pre-hook to process exit. A long-running Oneshot must raise `StartTimeout` accordingly (or send `EXTEND_TIMEOUT_USEC` as it works — see [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md)).
- **`ExecStartPost` runs only on success.** Post-hooks fire after the successful exit. If the Oneshot fails, `ExecStartPost` does not run.

### Restarting a Oneshot

A Oneshot is not restarted on *success*, regardless of `RestartPolicy` — even `Always`. A successful exit is the goal, not a failure to retry. `RestartPolicy` governs only the response to *failure* (a non-zero exit). To re-run a Oneshot on a schedule, give it a [timer trigger](/peios/using-peios/services-and-jobs/triggers-and-timers.md); that is the intended mechanism.

## Simple vs Oneshot at a glance

| | Simple | Oneshot |
|---|---|---|
| Process lifetime | Long-running; *is* the service | Runs once, exits |
| Readiness | `Notify` (`READY=1`) or `Alive` | Successful exit — `Readiness` ignored |
| Satisfies dependents when | Active | Completed (with or without `RemainAfterExit`) |
| Successful end state | Active (until stopped) | Completed, then Inactive (unless `RemainAfterExit=1`) |
| `ExecStartPost` fires | After readiness | After successful exit only |
| `StartTimeout` covers | Startup until readiness | The whole execution |
| Watchdog & health checks | Supervised while Active | None — `WatchdogTimeout`/`HealthCheck` are inert |
| Restart on success | n/a | Never (use a timer to re-run) |

## The sd_notify contract

A Notify-readiness service signals readiness by sending `READY=1` over the [sd_notify](/peios/using-peios/services-and-jobs/output-and-logging.md) protocol — a datagram to the socket named in the `NOTIFY_SOCKET` environment variable, which peinit sets for *every* service. `READY=1` is only the most visible message; the same channel carries watchdog keepalives (`WATCHDOG=1`), graceful-stop notice (`STOPPING=1`), timeout extensions (`EXTEND_TIMEOUT_USEC`), status strings (`STATUS=`), and the fd store. These are covered where they belong — readiness here, the rest in [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md) and [Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md).

Readiness is always **per start generation**. A `READY=1` left over from a previous incarnation of the process is never valid for the current start — peinit ties every notify message to the specific process it is currently supervising, verified by [pidfd](#no-forking-daemons).

Who is allowed to send these messages is governed by `NotifyAccess`, which currently has a single value: `Main` (`0`, the default). Under `Main`, only the tracked main PID — the process peinit forked and supervises — is authorised to send sd_notify readiness and status messages; a message arriving from any other PID (a child, a helper, an unrelated process) is ignored. So if a worker your service spawns sends `READY=1`, peinit will not act on it — the readiness signal must come from the main process itself.

## No forking daemons

peinit does **not** support services that double-fork to background themselves. The whole reason a traditional daemon double-forks — to detach from the launcher — is moot when the launcher tracks the process it spawned. peinit obtains a **pidfd** for every process at fork time, which is a kernel handle to that exact process, immune to PID-reuse races. There is no `MAINPID=` mechanism for a service to redirect supervision onto a different process; peinit supervises what it forked.

If a legacy binary insists on daemonising, the fix lives at the [packaging](/pekit/recipes/packages.md) layer — wrap it so it stays in the foreground (commonly a `--no-daemon`/`--foreground` flag) — not at the init layer.

## Where to start

To control *when* either type of service starts, read [Triggers and timers](/peios/using-peios/services-and-jobs/triggers-and-timers.md).

To follow a service from definition to running process and read its states, read [The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md).

To configure restart, health checks, and watchdogs for a Simple service, read [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md).

---

# Triggers and timers

_Peios / Using Peios / Services & jobs_

> Boot and timer triggers, the Disabled flag, and the full OnCalendar grammar — persistent catch-up, jitter, and what happens when the clock jumps.

A **trigger** decides *when* a service starts on its own. A service's triggers are independent of its [type](/peios/using-peios/services-and-jobs/service-types.md): a boot-triggered Oneshot and a timer-triggered Simple service are both perfectly ordinary. Triggers live in the `Triggers` field as a list, each entry written `type` or `type:argument`.

| Trigger | Form | Meaning |
|---|---|---|
| **Boot** | `boot` | Start during the Phase 2 [boot](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) sequence, in dependency order. |
| **Timer** | `timer:<schedule>` | Start on a schedule. The schedule is a [calendar expression](#calendar-expressions). |

A service with **no** triggers is **demand-only**: it never starts itself, and is brought up only by an explicit [control command](/peios/using-peios/services-and-jobs/controlling-services.md) or because another service [depends](/peios/using-peios/services-and-jobs/dependencies.md) on it. Many services are demand-only by design.

## Waiting for the boot to settle

`boot:settled` is `boot` with one difference: the service starts once the Phase 2 boot set has stopped moving — every service in it reached a state it will not leave on its own — rather than during it.

It exists for services that write to a terminal. peinit reports its progress to `/dev/console`, and a service holding that same terminal writes there too, so a prompt started mid-boot gets written over: `login` emits `Username: ` with no newline and waits, peinit appends a service line, and the result is unreadable until you press Enter and login redraws.

The wait is capped (`Machine\System\Boot\SettleTimeout`, 5 seconds by default). A service stuck in Starting therefore costs a deferred service a short delay rather than preventing it — which matters most exactly when something is broken and a console prompt is what you need.

A `boot:settled` service is deliberately **not** part of the boot plan. It does not consume the [parallel-start budget](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md), is not counted towards boot success, and cannot block another service. Waiting for quiet is a preference; it must not change what the boot means.

> [!NOTE]
> Reach for this only to keep console output legible. If a service genuinely needs something to be running first, that is a [dependency](/peios/using-peios/services-and-jobs/dependencies.md) — say so with `Requires`. A console login uses both: `boot:settled` for *when*, and `Requires` on its authority for *what must be true*.

You can list more than one trigger, including more than one of the same type. `["timer:*-*-* 02:00:00", "timer:*-*-* 14:00:00"]` runs at 2 am and 2 pm. The model is built to grow — future trigger types (path, device, event) will slot into the same list without a schema change.

## The Disabled flag

`Disabled=1` suppresses **automatic activation**. A disabled service will not be started by *any* trigger — boot, timer, or anything added later — but it can still be started by hand through the control interface. It is the switch for "configured, but not running on its own right now."

Two related controls are easy to confuse with it:

| To… | Use |
|---|---|
| Stop a service from auto-starting, but keep manual start | `Disabled=1` |
| Stop a service from being started *at all*, even manually | Deny `SERVICE_START` in its [ServiceSecurity](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md) descriptor |
| Stop a *running* service | The [`stop` command](/peios/using-peios/services-and-jobs/controlling-services.md) |

Enabling and disabling are registry writes performed by admin tooling, not peinit commands. peinit picks up a `Disabled` change through a [registry notification](/peios/using-peios/registry-concepts/watches.md) or on `reload-config`. A disabled service's definition is still loaded into peinit's model, so it is ready for an on-demand start the instant you ask.

## Calendar expressions

Timer schedules are written in systemd's `OnCalendar` format. peinit's grammar is normative and self-contained — what follows is the whole language. The general shape is:

```
DayOfWeek Year-Month-Day Hour:Minute:Second Timezone
```

Every field is optional and has a default, so most real schedules are short.

| Field | Form | If omitted |
|---|---|---|
| DayOfWeek | weekday name(s) | any day (`*`) |
| Date | `Year-Month-Day` | `*-*-*` (any date) |
| Time | `Hour:Minute:Second` | `00:00:00` |
| Second | the `:Second` of Time | `:00` |
| Timezone | IANA zone name | system-local time |

So `02:00:00` is a complete expression — date defaults to *every day*, and it means "every day at 02:00." `Hour:Minute` with no seconds defaults the seconds to `00`.

### Component syntax

Each numeric component — year, month, day, hour, minute, second — and the weekday accept the same operators:

| Operator | Example | Matches |
|---|---|---|
| **Wildcard** | `*` | any value |
| **List** | `1,15` | any listed value |
| **Range** | `Mon..Fri`, `8..17` | the inclusive range |
| **Repetition** | `0/15` | `0`, then every multiple of the step — 0, 15, 30, 45 |
| **Range + step** | `8..17/2` | `8`, `10`, `12`, … up to and including `17` |

Weekdays are English names, case-insensitive, abbreviated (`Mon`) or full (`Monday`), and take lists and ranges.

### Last day of the month

A `~` in place of the `-` between Month and Day counts the day **from the end** of the month: `~01` is the last day, `~02` the second-to-last, and so on. So `*-*~01` is the last day of every month. Repetition combines with it — `Mon *-05~07/1` is "the last Monday in May."

### Named shortcuts

These stand in for the expression shown:

| Shortcut | Equivalent |
|---|---|
| `minutely` | `*-*-* *:*:00` |
| `hourly` | `*-*-* *:00:00` |
| `daily` | `*-*-* 00:00:00` |
| `weekly` | `Mon *-*-* 00:00:00` |
| `monthly` | `*-*-01 00:00:00` |
| `quarterly` | `*-01,04,07,10-01 00:00:00` |
| `semiannually` | `*-01,07-01 00:00:00` |
| `yearly` / `annually` | `*-01-01 00:00:00` |

### Timezones and precision

A timezone specifier follows the IANA database — `Europe/London`, `US/Eastern`, `UTC`. With none, the expression is interpreted in system-local time.

Precision is **second-level**. Unlike systemd, peinit does not accept sub-second fractions; a fractional second is a parse error. Service scheduling has no use for finer granularity.

### Examples

| Expression | Meaning |
|---|---|
| `*-*-* 02:00:00` | Every day at 2 am (system-local) |
| `Mon *-*-* 00:00:00` | Every Monday at midnight |
| `*-*-1,15 12:00:00` | 1st and 15th of each month at noon |
| `*-*~01 00:00:00` | Last day of each month at midnight |
| `Mon..Fri *-*-* 09:00:00` | Weekdays at 9 am |
| `*-*-* *:00/15:00` | Every 15 minutes |
| `*-*-* 02:00:00 Europe/London` | Every day at 2 am London time |

## What happens when a timer fires

A timer firing does not blindly start the service — peinit considers the service's current state first, so a firing never collides with a run already in progress.

| Type | Current state | Action |
|---|---|---|
| Oneshot | Inactive / Completed / Failed | Start it (operation source `Timer`). |
| Oneshot | Active / Starting | Set a single **pending** flag — one catch-up run when the current one finishes. |
| Simple | Inactive / Failed | Start it. |
| Simple | Active / Starting | No-op; the firing is logged. |

The Oneshot "pending" flag is deliberately not a queue: many firings that land while a Oneshot is busy collapse into *one* catch-up run, taken when the current run ends. A service cannot stampede itself.

That single flag is shared across *all* of a Oneshot's timers, not one per timer. If a Oneshot has several timer triggers and two of them fire while it is busy, at most **one** run is still pending when the current one finishes. The timers are otherwise fully independent — each keeps its own next-firing time and its own last-run timestamp — but they can only ever queue up a single shared catch-up between them.

Each timer firing creates a normal [start operation](/peios/using-peios/services-and-jobs/jobs-and-operations.md), so a timer-started service is observable exactly like an admin-started one — same GUIDs, same events.

## Persistent timers and catch-up

`TimerPersistent` (default `1`) controls whether runs **missed while the machine was off** are caught up after a reboot.

peinit records the last-run timestamp of each timer in the registry. On boot, for each persistent timer it reads that timestamp, computes the next firing after it, and — if that time is already in the past — **fires once, immediately**, then resumes the normal schedule.

The catch-up is always a *single* run, never one-per-missed-occurrence. A daily timer that was off for five days fires once on the next boot, not five times. A non-persistent timer (`TimerPersistent=0`) ignores history entirely and simply computes its next future firing from now.

One subtlety worth knowing: the last-run timestamp is keyed by the **schedule string**. If you change a timer's schedule, the old timestamp is orphaned and the new schedule has no history, which causes exactly one spurious catch-up run. Schedule changes are rare and the cost is one extra run, so peinit accepts it rather than tracking trigger identity.

> [!NOTE]
> The timestamp is written *when the timer fires and the start is initiated*, not when the run completes. A service that crashes mid-run is not re-triggered on the next boot — the run was attempted, not missed.

## Jitter

`TimerJitter` (default `0`) adds a random delay of 0 to `TimerJitter` seconds to each firing, recomputed every time. A daily timer with `TimerJitter=900` fires at a slightly different moment between 00:00 and 00:15 each day. Jitter only ever delays — a timer never fires *before* its scheduled time. It is the standard remedy for a fleet of machines all hammering the same backend at exactly midnight.

## When the clock jumps

Calendar timers are **wall-clock** schedules: `*-*-* 02:00:00` means "02:00 on the wall," not "every 86,400 seconds." peinit arms them as absolute real-time timers that are cancelled and recomputed whenever the system clock is stepped, so the schedule stays anchored to the wall clock across corrections.

| Event | Behaviour |
|---|---|
| **NTP step or manual clock set** | Armed calendar timers are cancelled and recomputed against the new wall clock. A backward step pushes the next firing later; a forward step that crosses a missed time fires it once. |
| **Suspend / resume** | A scheduled time that elapsed while suspended fires once on resume — once, not once per missed occurrence. |
| **A missed occurrence within one uptime** | Fire once, then resume the normal schedule. peinit never replays every occurrence in the gap. |
| **Daylight-saving transition** (timezone-aware timer only) | A scheduled time in the *skipped* hour never fires; a scheduled time in the *repeated* hour fires once. See below. |

Daylight-saving is the case most likely to surprise you, and it only affects timers that name an IANA zone that observes DST (a bare `02:30:00` in system-local time follows whatever the system does). Say you schedule `*-*-* 02:30:00 Europe/London`:

- **Spring forward** — the clock jumps from 01:59 straight to 03:00, so 02:30 *does not exist* on that day. The timer **does not fire**; it simply resumes at the next valid `02:30`.
- **Fall back** — the clock reaches 02:30, then rewinds and reaches 02:30 a second time. The timer fires **exactly once**, on the first occurrence, not twice.

This is distinct from peinit's *interval* timers — the watchdog, health-check intervals, restart backoff, and the start/stop/reload phase timeouts. Those are genuine durations measured on the monotonic clock, so "wait 30 seconds" means 30 elapsed seconds and never lurches when the wall clock is set.

There is one boot-time case worth calling out on its own. When peinit reads a timer's stored last-run timestamp at boot and finds it is *in the future* relative to the current wall clock — meaning the clock has gone backwards since that run — it can't trust the timestamp, so it treats the timer as having an **unknown last run** and fires the persistent catch-up immediately. This only applies to the boot-time check; the runtime handling above is unaffected.

> [!CAUTION]
> If a machine boots with a wildly wrong clock (a dead RTC battery) and NTP corrects it only later, the boot-time persistent catch-up decision is made against the wrong time and may fire spuriously or not at all. The *runtime* half self-heals once NTP steps the clock, but the boot-time decision is already made by then. This is a known edge case short of NTP-aware rescheduling — worth knowing if you run timers on hardware with unreliable clocks.

## Where to start

To see how a timer-started run appears as a job and an operation, read [Jobs and operations](/peios/using-peios/services-and-jobs/jobs-and-operations.md).

To understand the states a timer-started service moves through, read [The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md).

For the exact registry keys that store last-run timestamps, see the [Registry key reference](/peios/using-peios/services-and-jobs/registry-key-reference.md).

---

# The service lifecycle

_Peios / Using Peios / Services & jobs_

> The ten service states, the transitions that connect them, and the cause taxonomy that says why a service ended up where it is.

A service managed by peinit is, at every instant, in **exactly one state**. The state is what a `status` query reports, what gates dependents, and what decides which commands are valid. Alongside the state, peinit records the **cause** of the most recent transition — the *why* behind the *where*. Reading a status is reading these two things together: "Failed, because RestartBudgetExhausted" tells a very different story from "Failed, because ValidationError."

This page is the reference for both. It is worth internalising before [Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md) and [Troubleshooting](/peios/using-peios/services-and-jobs/troubleshooting.md), because both lean on it.

## The states

| State | Process? | Satisfies dependents? | What it means when you see it |
|---|---|---|---|
| **Inactive** | No | No | Not started, or stopped cleanly and not set to restart. The neutral resting state. |
| **Starting** | Maybe | No | Activation in progress — conditions, hooks, fork, or readiness wait. Dependents are blocked. |
| **Active** | Yes | **Yes** | Running and ready. The normal state of a healthy Simple service. |
| **Reloading** | Yes | **Yes** | Re-reading configuration. Still counts as running. |
| **Stopping** | Yes (briefly) | No | SIGTERM sent; waiting for exit or SIGKILL escalation. |
| **Completed** | No | **Yes** | Oneshot finished successfully. Stays here only with `RemainAfterExit=1`. |
| **Backoff** | No | No | A restart is pending; the service is waiting out its backoff delay before the next attempt. |
| **Failed** | No | No | Exited abnormally and restart policy is exhausted or not configured. |
| **Abandoned** | Yes (unkillable) | No | SIGKILL was sent but the process survived in uninterruptible sleep (D-state). peinit has given up supervising it; its cgroup is leaked. |
| **Skipped** | No | **Yes** | A start-time [condition](/peios/using-peios/services-and-jobs/execution-environment.md) was not met. The service does not apply here. |

Three of these — **Active**, **Completed**, **Skipped** — satisfy dependents. Everything else blocks them. That single column is the rule the whole [dependency](/peios/using-peios/services-and-jobs/dependencies.md) system turns on: a service waiting on a `Requires` target does not move until that target reaches one of those three.

## The common path

Most of a service's life is a small loop. The diagram below shows the states a healthy Simple service moves through, plus the two ways it leaves Active.

```mermaid
stateDiagram-v2
    [*] --> Inactive
    Inactive --> Starting: start / trigger / dependency
    Starting --> Active: ready (READY=1 or alive)
    Active --> Stopping: stop / shutdown / conflict
    Stopping --> Inactive: exited (clean stop / shutdown)
    Stopping --> Failed: exited (conflict / BindsTo eviction)
    Active --> Backoff: crash + restart allowed
    Backoff --> Starting: backoff delay elapsed
    Active --> Failed: crash + no restart
    Starting --> Failed: timeout / hook / setup failure
    Failed --> Starting: explicit start
```

A few things this picture makes concrete:

- An automatic restart always routes **through Backoff**, never through Failed. `Backoff → Starting` is the retry; `Failed` is reached only when restarts are exhausted or disabled. This is why [`OnFailure`](/peios/using-peios/services-and-jobs/supervision.md) fires once at the end, not on every retry.
- `Starting` can fail *before any process exists* — a parent-side setup error, a failed pre-hook, a condition or assert. The cause records which.
- `Failed → Starting` is how a manual `start` (or a recovery path) revives a dead service; automatic restarts never originate from `Failed`.
- `Stopping` has **two** exits. A clean stop or a shutdown lands in `Inactive`. But a service stopped because it lost a [`Conflicts`](/peios/using-peios/services-and-jobs/dependencies.md) race (`ConflictEviction`) or because its [`BindsTo`](/peios/using-peios/services-and-jobs/dependencies.md) target went away (`BindsToPropagation`) comes to rest in `Failed`, carrying that cause — so an evicted or bound-out service shows as Failed in `status` and needs a `reset` (or, for a bound service, its target returning) before it starts again. It was not shut down on purpose, so peinit does not treat it as cleanly Inactive.

Oneshot services follow a parallel path through **Completed** instead of Active: `Starting → Completed`, and then either staying there (`RemainAfterExit=1`) or passing through to `Inactive`. See [Simple and Oneshot services](/peios/using-peios/services-and-jobs/service-types.md).

> [!NOTE]
> **A crash while `Reloading` is still a crash.** If a service's main process *dies* while it is re-reading its configuration, peinit treats that as an ordinary `ProcessCrash` and routes it through the [restart policy](/peios/using-peios/services-and-jobs/supervision.md) — `Reloading → Backoff` if a restart is warranted, or `Reloading → Failed` if it is not. This is why you can see a service jump straight from Reloading into Backoff or Failed. It is *distinct* from an `ExecReload` **command** failing: a failed reload command leaves the running process untouched and the service stays `Active`, reporting the failure without changing state.

## Transition causes

Every transition carries a cause. peinit keeps the cause of the *most recent* transition on the service, and emits all of them to [eventd](/peios/security-fundamentals/auditing/overview.md). The full taxonomy, grouped by what kind of thing happened:

**Why a service started**

| Cause | Meaning |
|---|---|
| `ExplicitStart` | An administrator or a trigger started it. |
| `DependencyStart` | Started to satisfy another service's dependency. |
| `RestartPolicy` | An automatic restart, after a backoff delay. |
| `BindsToRecovery` | A [bound](/peios/using-peios/services-and-jobs/dependencies.md) target returned to Active; the dependent is auto-restarted. Does **not** consume the restart budget. |

**Why a service stopped**

| Cause | Meaning |
|---|---|
| `ExplicitStop` | An administrator requested stop. |
| `ConflictEviction` | A [conflicting](/peios/using-peios/services-and-jobs/dependencies.md) service started and won. |
| `BindsToPropagation` | A bound target stopped, so this one stops too. |
| `ShutdownWave` | The system is shutting down. |

**Why a service failed or restarted** — the diagnostic causes

| Cause | Meaning |
|---|---|
| `ProcessCrash` | The main process exited non-zero or died on a signal. |
| `CleanExit` | A Simple process exited *successfully*, and policy is not Always — not a crash; goes straight to Inactive. |
| `CleanExitRestart` | A Simple process exited successfully but `RestartPolicy=Always`, so it is restarted. Logged clearly as *not* a crash. |
| `ReadinessTimeout` | `StartTimeout` expired before the service became ready. |
| `WatchdogTimeout` | A `WATCHDOG=1` keepalive did not arrive in time. |
| `HealthCheckFailure` | `HealthCheckRetries` consecutive health checks failed. |
| `PreHookFailure` | An `ExecStartPre` hook exited non-zero. |
| `ParentSetupFailure` | peinit could not even fork — resource exhaustion, cgroup error. No child was created. |
| `PreExecFailure` | Setup after fork but before exec failed (token install, rlimits, environment). |
| `RestartBudgetExhausted` | `RestartMaxRetries` reached within the window; no more retries. |

**Why a service was rejected** — definition and graph problems

| Cause | Meaning |
|---|---|
| `ValidationError` | The definition failed validation (bad field, unresolvable conflict, illegal health-check timing…). |
| `CycleDetected` | The service is part of a dependency cycle. |
| `DependencyFailure` | A `Requires` dependency entered Failed or does not exist. |
| `AssertionError` | A start-time `Assert` failed. |
| `ConditionSkipped` | A start-time `Condition` was not met → Skipped (this is *not* a failure). |
| `ProcessUnkillable` | The process survived SIGKILL (D-state) → Abandoned. |
| `ExplicitReset` | An administrator cleared Failed/Abandoned/Skipped without starting. |

The grouping matters for what peinit does next: the diagnostic causes are mostly **restart-eligible** (peinit consults [restart policy](/peios/using-peios/services-and-jobs/supervision.md)), whereas the definition/graph causes are **never restarted** — retrying a broken definition cannot help. The distinction is spelled out in [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md).

> [!TIP]
> When a service is `Failed`, the cause is the first thing to read. A `ProcessCrash` or `WatchdogTimeout` points at the service's own code or health; a `DependencyFailure` points at something it needs; a `ValidationError` points at its definition. [Troubleshooting peinit](/peios/using-peios/services-and-jobs/troubleshooting.md) is organised around exactly this lookup.

## Readiness gating during boot

The dependent-satisfaction rule is what makes boot orderly. A service's dependents do not start until it satisfies them:

- A **Simple** service satisfies dependents when it signals `READY=1` (Notify) or when its process exists (Alive).
- A **Oneshot** service satisfies dependents when it exits successfully and reaches Completed.
- A **Skipped** service satisfies dependents immediately — it succeeded by not needing to run.

If a service does not reach readiness within its `StartTimeout`, its dependents diverge by relationship: `Requires` dependents transition to Failed (`DependencyFailure`); `Wants` dependents start anyway. That difference is the whole point of the two relationship types — see [Dependencies and ordering](/peios/using-peios/services-and-jobs/dependencies.md).

## The Abandoned state

`Abandoned` is the one state that reflects a kernel-level problem rather than a service-level one. peinit reaches it when it has sent SIGKILL to a service's process group but the processes are still there after a grace period — the post-kill timeout, **5 seconds** by default. If the service's cgroup has not emptied within it, the processes are wedged in **uninterruptible kernel sleep** (D-state), typically behind a hung mount or a broken storage controller, and the service is marked Abandoned (its cgroup leaked).

peinit cannot kill a D-state process; nothing in userspace can. So it stops trying: it marks the service Abandoned, leaks the cgroup (it cannot be removed while populated), and moves on rather than hanging. The leak is never silent — it shows up in the service's `warnings` and, on a later start, peinit creates a fresh "generational" cgroup so the new instance is unaffected by the stuck old one.

An Abandoned service is cleared with `reset`, which re-checks the cgroup: if it finally emptied, peinit cleans up; if it is still populated, peinit leaves it leaked and warns you. An Abandoned service is a sign of an underlying I/O fault that needs investigating, not something to paper over with a restart.

## Invariants

A handful of rules hold without exception:

1. A service is in **exactly one** state at any moment.
2. Only the transitions peinit defines are valid — there are no others.
3. **Only peinit** changes a service's runtime state. No external process can reach in and set it.
4. A service's *securable identity* (its [ServiceSecurity](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md) descriptor — who can manage it) is independent of its *process identity* (its [token](/peios/using-peios/services-and-jobs/identity-and-privileges.md) — what it can access).
5. Readiness is **per start generation** — a `READY=1` from a previous incarnation is never honoured for the current start.

## Where to start

To understand what happens *after* a service crashes — restart policy, backoff, health checks, watchdogs — read [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md).

To understand how dependents block on and are released by these states, read [Dependencies and ordering](/peios/using-peios/services-and-jobs/dependencies.md).

To see which commands are valid in which state, read the command × state matrix in [Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md).

---

# Keeping services running

_Peios / Using Peios / Services & jobs_

> Restart policy with backoff and a budget, active health checks, the watchdog, timeout extension, OnFailure, and the Critical reboot path.

Once a service is [Active](/peios/using-peios/services-and-jobs/the-service-lifecycle.md), peinit's job becomes *keeping it that way* — or deciding, deliberately, when to stop trying. That decision is governed by a small set of policies: a restart policy with a throttling budget, optional active health checks, an optional watchdog, and an error-control level that decides how serious a final failure is. This page covers all of them.

## Restart policy

When a service fails in a way that *might* be transient, peinit consults `RestartPolicy`:

| Policy | Value | Behaviour |
|---|---|---|
| **Never** | 0 | Never restart. The service goes straight to Failed. |
| **OnFailure** *(default)* | 1 | Restart on a crash or runtime failure. An exit code in `SuccessExitCodes` is *not* a failure and is not restarted. |
| **Always** | 2 | Restart on any failure, *and* — for a Simple service — restart even a successful clean exit (see [clean exits](#clean-exits)). |

Not every failure is restart-eligible. peinit splits causes into three groups:

- **Restart-eligible** — peinit consults the policy and budget: `ProcessCrash`, `WatchdogTimeout`, `HealthCheckFailure`, `ReadinessTimeout`, `PreHookFailure`, `PreExecFailure`, `ParentSetupFailure`. (The startup failures are eligible on purpose — a pre-hook that failed on a momentarily-missing mount deserves another try.)
- **Never restarted** — retrying cannot help: an explicit stop or reset, shutdown, conflict eviction, a broken definition (`ValidationError`, `CycleDetected`, `DependencyFailure`, `AssertionError`), an already-exhausted budget, or an unkillable process.
- **Budget-exempt** — `BindsToRecovery`, where a [bound](/peios/using-peios/services-and-jobs/dependencies.md) service came back and its dependents are revived. The dependent did not fail on its own, so this restart does not count against its budget.

## The budget and backoff

Restarting an instantly-crashing service in a tight loop is worse than useless, so every restart is throttled by two mechanisms working together.

**Exponential backoff.** Before each restart the service sits in the [Backoff](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) state for a delay that *doubles* on each consecutive failure, starting from `RestartDelay` (default 1 s) and capped at 60 s. So a crash-looping service backs off 1 s, 2 s, 4 s, 8 s, … up to a minute between attempts.

**The budget.** A consecutive-failure counter tracks how many times the service has failed in a row. Once it reaches `RestartMaxRetries` (default 5), the *next* failure is not restarted: the service transitions to Failed with cause `RestartBudgetExhausted`.

**The reset.** The counter resets to zero once the service stays Active for `RestartWindow` seconds (default 120) without failing. This is the crucial detail: the budget is **not** "N restarts ever" — it is "N restarts faster than the service can sustain `RestartWindow` of health." A service that crashes once a day and recovers cleanly each time never exhausts its budget, because every crash starts from a counter of zero. Only failures recurring faster than the service can stay healthy accumulate.

```mermaid
flowchart LR
    A["Active"] -->|crash| B{"budget left?"}
    B -->|yes| C["Backoff<br/>delay doubles"]
    C -->|delay elapsed| D["Starting"]
    D -->|ready, stays Active<br/>RestartWindow| A
    B -->|no| E["Failed<br/>RestartBudgetExhausted"]
```

> [!TIP]
> If a service flaps — restarting endlessly without ever sticking — and never hits its budget, the usual cause is that it briefly reaches Active each time and resets the counter. Either it is recovering long enough to reset (raise `RestartWindow`) or you are looking at a [health-check flap](#health-checks), which has its own guardrail below.

### Clean exits

For a **Simple** service, exiting with code 0 (or a `SuccessExitCodes` match) is *success*, not a crash — a daemon chose to quit. What peinit does next depends on the policy:

- Under `Never` or `OnFailure`, the cause is `CleanExit` and the service goes straight to **Inactive**, with no restart-policy consultation at all.
- Under `Always`, the cause is `CleanExitRestart` and the service is restarted — but through the *same* backoff and budget as a failure, so a daemon that exits cleanly in a tight loop cannot bypass throttling. Logs and status make clear it exited successfully and was restarted only because the policy is Always.

(A **Oneshot** clean exit is never restart-eligible regardless of policy — see [service types](/peios/using-peios/services-and-jobs/service-types.md).)

## Health checks

A live process is not always a working one — it can lose its database connection, wedge in a bad state, or return errors while still "up." An active **health check** closes that gap by running a command periodically and treating its exit code as a verdict: 0 is healthy, non-zero is a failure.

Health checks apply to **Simple** services only. peinit starts a service's health-check timer once the service becomes Active; a [Oneshot](/peios/using-peios/services-and-jobs/service-types.md) has no long-running process to probe, so it never has one. `HealthCheck` and its companion fields are inert on a Oneshot — no timer is started — so don't set them there expecting supervision.

| Field | Default | Controls |
|---|---|---|
| `HealthCheck` | — | The command to run (runs under the service's own [token](/peios/using-peios/services-and-jobs/identity-and-privileges.md)). |
| `HealthCheckInterval` | 30 | Seconds between runs. |
| `HealthCheckTimeout` | 5 | Seconds before a check is killed and counted as failed. |
| `HealthCheckRetries` | 3 | Consecutive failures before the service is declared unhealthy. |

`HealthCheckRetries` consecutive failures mark the service unhealthy and restart it through the normal restart policy — backoff and budget included. A single success resets the failure count. If a check is still running when the next interval fires, the new one is skipped rather than stacked.

The `health` field in a `status` response reflects this: `healthy`, `unhealthy` (failing but not yet failed out), `unknown` (configured, no result yet), or `null` (no health check).

**The flap guardrail.** Health-check throttling only works if a failure cycle is shorter than the restart window — otherwise the counter resets between failures and the service restarts forever. peinit enforces this at validation time as a hard rule:

```
HealthCheckRetries × HealthCheckInterval < RestartWindow
```

A definition that violates it is a **validation error**, not a warning — the service is marked Failed with `ValidationError` rather than allowed to restart indefinitely.

> [!CAUTION]
> Be extremely cautious with health checks on `ErrorControl=Critical` services. A false-positive health-check failure on a Critical service will, after exhausting the restart budget, **reboot the machine** — and a flaky check can do this repeatedly. Critical platform services are better served by a passive [watchdog](#the-watchdog) with a generous timeout: the service itself knows whether it is healthy and simply stops sending keepalives if not. peinit applies the same health-check semantics to Critical services as to any other — there is no special-casing to save you.

A health check stuck in D-state is handled like any other stuck helper: its sub-cgroup is leaked and surfaced as a warning, but — unlike a stuck *main* process — it does **not** push the service to Abandoned. A health check holds no service resources; it is only a probe.

## The watchdog

The watchdog inverts the health check: instead of peinit asking the service if it is alive, the service must periodically tell peinit. Set `WatchdogTimeout` (seconds; 0 disables) and the service must send `WATCHDOG=1` via [sd_notify](/peios/using-peios/services-and-jobs/output-and-logging.md) at least that often. Miss the deadline and peinit treats it as a failure (cause `WatchdogTimeout`) and applies the restart policy.

Like health checks, the watchdog is a **Simple**-only supervision mechanism. peinit arms the watchdog timer only once a Simple service becomes Active; on a [Oneshot](/peios/using-peios/services-and-jobs/service-types.md) `WatchdogTimeout` is inert — no timer is started, and setting it changes nothing.

A service can adjust its own watchdog at runtime by sending `WATCHDOG_USEC=<microseconds>`: a positive value re-arms the timer with the new interval immediately; `0` disables the watchdog. This is for services whose phases have different latencies — a database might run a tight 5 s watchdog normally but widen it to 60 s during a compaction pass, because it knows its own phases better than the schema author did. The runtime value does **not** persist: a restart reverts to the schema's `WatchdogTimeout`.

## Timeout extension

A service that does genuinely slow work during a transition can ask for more time by sending `EXTEND_TIMEOUT_USEC=<microseconds>` while it is Starting, Stopping, or Reloading. Each message *replaces* the current phase deadline (it is not additive); a service that keeps sending them keeps proving it is making progress, and if it stops, the last deadline fires and peinit escalates normally.

The extension is capped to prevent a buggy service from extending forever:

| Phase | Base timeout | Maximum deadline |
|---|---|---|
| Starting | `StartTimeout` | `StartTimeout` × 4 |
| Stopping | `StopTimeout` | `StopTimeout` × 4 |
| Reloading | `StartTimeout` | `StartTimeout` × 4 |

During [shutdown](/peios/using-peios/services-and-jobs/shutdown.md) a second, global cap also applies — the deadline can never exceed the remaining time in the global shutdown timeout, and the stricter of the two caps wins. A message received outside a transition (the service is Active, Failed, …) is ignored — there is no timeout to extend.

## OnFailure

When a service enters **Failed**, an `OnFailure` field names another service to start in response — a fallback for *graceful degradation* (the main web UI failed; bring up a minimal emergency endpoint). It is not a monitoring or alerting hook; that is [eventd](/peios/security-fundamentals/auditing/overview.md)'s job.

`OnFailure` fires for runtime failures — `ProcessCrash`, `WatchdogTimeout`, `HealthCheckFailure`, the startup-failure causes, and non-Critical budget exhaustion. It deliberately does **not** fire for:

- **`ShutdownWave`** — nothing new starts during shutdown.
- **`ValidationError`, `CycleDetected`, `DependencyFailure`, `AssertionError`** — these are definition or graph breakage, not a running service degrading. The fallback would likely sit in the same broken graph anyway.

Because a fallback can itself fail and carry its own `OnFailure`, peinit bounds the chain: it tracks which services it has already started for one originating failure, refuses to start one twice, and never follows the chain beyond a depth of 16. When the guard trips, it logs the loop and stops.

## ErrorControl: when failure is not an option

`ErrorControl` decides how serious an *irrecoverable* failure is:

| Level | Value | On irrecoverable failure |
|---|---|---|
| **Normal** *(default)* | 0 | The service stays in Failed. The rest of the system carries on. |
| **Critical** | 1 | peinit **syncs filesystems and reboots immediately.** |

A Critical service is one the system genuinely cannot run without — the platform daemons `registryd`, `authd`, `lpsd`, `eventd` are all Critical. "Irrecoverable" means the restart budget was exhausted: peinit tried, backed off, retried up to `RestartMaxRetries`, and the service still would not stay healthy. At that point the fastest path to a known-good system is a reboot.

A few consequences worth holding onto:

- **Critical failure outranks `OnFailure`.** When a Critical service exhausts its budget, peinit reboots; it does *not* start the `OnFailure` handler.
- **`ErrorControl=Critical` implies `SafeMode`.** A Critical service is always eligible to start in [Safe mode](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md).
- **Critical services are OOM-protected.** peinit sets their `oom_score_adj` to `-1000`, so the kernel will not pick them as out-of-memory victims.
- **A runtime Critical crash does not enter Safe mode** — it follows the reboot path. Safe mode is only for boot-time *configuration* errors (a cycle or conflict involving a Critical service). The escalation between reboots, the boot-attempt counter, and Recovery mode are covered in [Boot and boot modes](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md).

> [!WARNING]
> A Critical service that fails the same way on every boot becomes a **reboot loop**, which the boot-attempt counter is designed to break by escalating to Recovery mode after a few attempts. If you see a machine rebooting repeatedly, a crashing Critical service is the first suspect. See [Troubleshooting peinit](/peios/using-peios/services-and-jobs/troubleshooting.md).

## Where to start

To see exactly which states these policies move a service through, read [The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md).

To understand how a failed service propagates to the ones that depend on it, read [Dependencies and ordering](/peios/using-peios/services-and-jobs/dependencies.md).

When a service will not stay up, work backward from the symptom in [Troubleshooting peinit](/peios/using-peios/services-and-jobs/troubleshooting.md).

---

# Dependencies and ordering

_Peios / Using Peios / Services & jobs_

> Requires, Wants, BindsTo, and Conflicts — start order, stop order, failure propagation, graph validation, and parallel start.

Services rarely stand alone. A web app needs its database; a network daemon needs the loopback interface up; two implementations of the same role must never run at once. peinit expresses all of this with four relationship fields — `Requires`, `Wants`, `BindsTo`, and `Conflicts` — that together decide the order services start in, what happens when one stops, and how a failure spreads to its neighbours.

The relationships are declared on the *dependent* (the service that needs something), naming the *target* (the service it needs) by name. `Conflicts` is the exception — it is symmetric, and one side declaring it is enough.

## The four relationships

### Requires — a hard dependency

If A `Requires` B:

- **Start.** B must reach a [satisfying state](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) before A starts. If B is not running, peinit starts it first (cause `DependencyStart`). If B fails, A is marked Failed with `DependencyFailure` and never even attempts to start.
- **Stop.** Stopping B does **not** stop A. Requires is a *start-ordering* constraint, not a runtime leash — A keeps running.
- **Runtime failure.** If B crashes while A is already Active, A is unaffected. B's own [restart policy](/peios/using-peios/services-and-jobs/supervision.md) handles B's recovery.

Requires is the workhorse: "I need this to have started, and if it can't, neither can I."

### Wants — a soft dependency

If A `Wants` B:

- **Start.** peinit starts B before A *if* B exists and is not [Disabled](/peios/using-peios/services-and-jobs/triggers-and-timers.md) — but if B fails to start, or does not exist, **A starts anyway.**
- **Stop / failure.** No effect either way. A and B are runtime-independent.

Wants is best-effort ordering: "start this first if you can, but I work without it."

### BindsTo — a runtime coupling

If A `BindsTo` B:

- **Start.** Identical to Requires — B must satisfy before A starts.
- **Stop.** If B stops for *any* reason — explicit stop, conflict, crash, shutdown — A is stopped too, transitioning to Stopping with cause `BindsToPropagation`. When A finishes stopping it comes to rest in **Failed** (carrying `BindsToPropagation`), *not* Inactive — so a `status` query shows the bound service as Failed, making it clear it was taken down by its target rather than shut down cleanly.
- **Recovery.** When B returns to Active, peinit **automatically restarts** A from that Failed state (cause `BindsToRecovery`). This is reactive — peinit watches B's transitions — and it is **budget-exempt**: A did not fail on its own, so the restart does not count against its [budget](/peios/using-peios/services-and-jobs/supervision.md). If B never comes back, A stays Failed until you `reset` (or start) it.

BindsTo is Requires plus a runtime leash: "I need this to start, *and* I should not outlive it." It is the right choice for a sidecar that is meaningless without its principal. `BindsTo` implies `Requires`; listing both for the same target is harmless, and BindsTo semantics win.

### Conflicts — mutual exclusion

If A `Conflicts` with B:

- **Start.** Starting A while B is Active creates a stop operation for B (source `ConflictResolution`), evicting it (cause `ConflictEviction`) before A starts — and vice versa. If the loser will not stop within its `StopTimeout`, [SIGKILL escalation](/peios/using-peios/services-and-jobs/shutdown.md) applies. The evicted loser does **not** land in Inactive: it comes to rest in **Failed** (carrying `ConflictEviction`), so it shows as Failed in `status` and needs a `reset` before it will start again. That is deliberate — an evicted service is not a clean stop, and leaving it Failed stops it from quietly restarting straight back into the conflict.
- **Symmetry.** Conflicts is two-way. If A declares `Conflicts=["B"]`, starting *either* stops the other; B need not declare it back.

Conflicts is for true mutual exclusion — two services binding the same port, or two implementations of one role where exactly one must run — not for ordinary resource contention.

### At a glance

| | Start order | Target failure stops dependent? | Target stop stops dependent? |
|---|---|---|---|
| **Requires** | target first; dependent fails if target fails | At start time only | No |
| **Wants** | target first if present; dependent starts regardless | No | No |
| **BindsTo** | target first; dependent fails if target fails | Yes (and auto-recovers) | Yes (and auto-recovers) |
| **Conflicts** | starting one evicts the other | n/a | n/a |

## Graph validation

Before peinit starts *anything*, it builds the dependency graph and validates it. Validation runs once per graph build — at boot for the whole boot graph, and per request for an [on-demand start](#on-demand-starts)'s transitive closure. It is not incremental.

**Cycles.** peinit topologically sorts the graph; if the sort fails, there is a cycle. Every service in the cycle is marked Failed with `CycleDetected`, and peinit logs the full path (`dependency cycle: A → B → C → A`) so you can find and break it. A service may not depend on itself — a self-reference is rejected as a cycle. If any service in the cycle is `ErrorControl=Critical`, peinit downgrades to [Safe mode](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) rather than rebooting, because a reboot would just hit the same cycle.

**Missing targets.** What a missing target means depends on the relationship:

| Relationship | Missing or disabled target |
|---|---|
| `Requires` | Dependent marked Failed (`DependencyFailure`). |
| `BindsTo` | Treated as a missing Requires — Failed (`DependencyFailure`). |
| `Wants` | Silently ignored — the entry is dropped. |
| `Conflicts` | Silently ignored — nothing to conflict with. |

**Unresolvable conflicts.** If two *boot-triggered* services conflict with each other, there is no way to honour both, so both are marked Failed with `ValidationError`. As with cycles, if either is Critical, Safe mode applies.

**Warnings.** Some conditions are logged but do not block boot — most notably a `Readiness=Alive` service that others `Requires` (see [service types](/peios/using-peios/services-and-jobs/service-types.md)). Warnings appear in the logs and do not change state.

**Validation errors.** Some conditions *do* fail the service — for example a health-check configuration that violates the [flap constraint](/peios/using-peios/services-and-jobs/supervision.md). These mark the service Failed with `ValidationError`.

When more than one finding applies to the same service, peinit records a single primary cause by precedence — `CycleDetected` > `ValidationError` > `DependencyFailure` — but it logs *all* findings, so the primary cause never hides the others.

## Parallel start

After validation, peinit starts services whose dependencies are all satisfied, and it does so **in parallel** up to a configurable limit:

| Registry key | Default | Meaning |
|---|---|---|
| `Machine\System\Boot\MaxParallelStarts` | 10 | Maximum services starting concurrently. |

The scheduler is simple: every service with no unsatisfied dependencies is eligible; peinit starts up to `MaxParallelStarts` of them; as each one reaches a [satisfying state](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) its dependents become eligible and join the queue. The result is that independent subtrees of the graph come up at the same time, while ordering constraints are still honoured exactly.

> [!NOTE]
> Boot order is *emergent*, not hardcoded. The typical sequence — `eudev`, `lpsd`, `authd`, `eventd`, networking, then application and login services — is simply what the standard role definitions' dependencies produce. Change the dependencies and you get a different order. The platform daemons come up first because everything else, directly or transitively, requires them.

## Failure propagation

When a service enters Failed during graph execution, the failure spreads along `Requires` and `BindsTo` edges — and only those:

1. Every service that `Requires` the failed one transitions to Failed with `DependencyFailure`.
2. Every service that merely `Wants` it is **unaffected** and starts normally.
3. Propagation is **transitive**: if A requires B and B requires C, and C fails, then B fails, then A fails — each with `DependencyFailure`.

This is the payoff of the Requires/Wants distinction. A hard dependency failing takes its dependents down with it; a soft one failing is shrugged off. Choosing the right relationship is choosing how far a failure is allowed to travel.

## On-demand starts

When you start a service explicitly rather than at boot, peinit does the same graph work on a smaller scope — the requested service's transitive closure:

1. Collect all transitive `Requires` and `BindsTo` dependencies (and best-effort `Wants`).
2. Validate that sub-graph (cycles, missing targets).
3. Resolve `Conflicts` — stop anything that conflicts.
4. Start the sub-graph with the same parallel scheduler.

Anything already in a satisfying state (Active, Completed, Skipped) is left alone — its dependency is already met, so there is no needless restart. Dependencies pulled in this way start with cause `DependencyStart`. If two on-demand starts need the same dependency at once, their start operations [merge](/peios/using-peios/services-and-jobs/jobs-and-operations.md) rather than racing.

## Shutdown reverses the graph

peinit does not need a separate stop-ordering configuration. [Shutdown](/peios/using-peios/services-and-jobs/shutdown.md) simply **reverses** the dependency graph: services with no dependents stop first, and services that others depend on stop last. A service is never stopped until everything that `Requires` or `BindsTo` it has already stopped.

The very last services to stop are the TCB daemons everything rests on — `eventd`, then `authd`, then `lpsd`, then `registryd` — with `registryd` stopped dead last, mirroring its position as the first service ever started. The full shutdown sequence is in [Shutdown](/peios/using-peios/services-and-jobs/shutdown.md).

## Where to start

To see the states services move through as they start and stop in order, read [The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md).

To understand how a target's failure interacts with the dependent's own restart policy, read [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md).

To follow how dependency-driven starts appear as operations, read [Jobs and operations](/peios/using-peios/services-and-jobs/jobs-and-operations.md).

---

# Service identity and privileges

_Peios / Using Peios / Services & jobs_

> How a service token is materialised — minted for SYSTEM, requested from authd otherwise — plus the per-service SID and RequiredPrivileges trimming.

Every service process runs under a [KACS token](/peios/security-fundamentals/tokens/overview.md) — the kernel object that carries identity into every system call and is the input to every access decision. peinit's responsibility is to get the *right* token for a service and install it on the child process *before* the binary execs, so the service runs as the intended principal from its very first instruction.

One rule frames everything else: **peinit never shares its own SYSTEM token with a service.** Even a service configured `Identity=SYSTEM` gets a *separately materialised* token of its own — never peinit's. This keeps every service's identity independent and auditable, and it is one of peinit's [security invariants](#security-invariants).

## How a token is materialised

The `Identity` field decides where the token comes from:

| `Identity` value | Token source |
|---|---|
| `SYSTEM` | **Minted by peinit** from its own SYSTEM identity. |
| Any other principal name or SID | **Requested from [authd](/peios/security-fundamentals/boot-and-trust-establishment/authd-handoff.md).** |
| Absent or empty | authd, defaulting to `LocalService`. |

### The SYSTEM path

For a `SYSTEM` service, peinit mints an independent SYSTEM token using its own token as the template: it reads its own identity (user SID `S-1-5-18`, the group list, the privilege set) and mints a fresh primary token with the same identity, adding the service's [per-service SID](#the-per-service-sid). The minted token is fully independent — the [privilege trimming](#privilege-restriction) that follows affects only this token, never peinit's.

This path exists because of a bootstrapping problem: the platform services that *make* the normal token flow possible cannot use it, because they have to start *before* it exists. `registryd`, `authd`, `lpsd`, and `eventd` all run as SYSTEM and are all minted this way during [boot](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md), before authd is available to mint anything.

> [!NOTE]
> There is no allow-list restricting which services *may* be `SYSTEM`. The security boundary is the [registry key descriptor](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md) on `Machine\System\Services\` — an administrator who can create service definitions is already trusted to assign any identity, so a second gate would add nothing. The dangerous case is never *implicit*, though: `SYSTEM` must be written out explicitly; an empty `Identity` defaults to the minimal `LocalService`, never to SYSTEM.

### The authd path

For any other identity, peinit asks [authd](/peios/security-fundamentals/boot-and-trust-establishment/authd-handoff.md) for a token: it sends the `Identity` value verbatim, and authd routes it to the right source —

- **Well-known principals** (`LocalService`, `NetworkService`, …) → a built-in identity with a predefined minimal privilege set;
- **Local service accounts** → [lpsd](/peios/security-fundamentals/identity/overview.md), the local principal store;
- **Domain accounts** → the directory connector;

— resolves the principal's SIDs, mints a token, creates a logon session, and hands the token back to peinit to install. peinit neither knows nor cares whether the identity is local or domain; routing is authd's job.

> [!IMPORTANT]
> Every non-SYSTEM service start depends on authd. peinit interacts with it over a non-blocking, timed channel — if authd is unreachable or unresponsive, the service start *fails* rather than hanging PID 1. This is why a broken authd takes down all non-platform service starts but leaves the SYSTEM-minted platform services unaffected. The authd wire interface is owned by authd, not by peinit, and is still being specified.

## The per-service SID

Every service token — minted or authd-issued — carries a **per-service SID** in its group list. It uses authority `S-1-5-80` and is derived deterministically from the service name: the SHA-1 of the uppercased name (as UTF-16LE), with the digest split into five sub-authorities. authd adds it automatically for the tokens it mints; peinit computes and adds it itself for SYSTEM tokens, no authd involvement needed.

The point of the per-service SID is **fine-grained access control even when services share an identity.** A dozen services might all run as `LocalService`, but each has a *unique* service SID, so an ACL can grant access to exactly one of them without inventing a dedicated account. It is also what keeps the platform services — all running as SYSTEM — distinguishable to [AccessCheck](/peios/security-fundamentals/access-decisions/overview.md). See [Well-known principals](/peios/security-fundamentals/identity/well-known-principals.md) for the SID landscape this fits into.

## Privilege restriction

A token comes from its source (authd or the SYSTEM mint) with a default set of [privileges](/peios/security-fundamentals/privileges/overview.md). `RequiredPrivileges` lets a definition trim that set down to only what the service needs:

- peinit reads the `RequiredPrivileges` allow-list and **removes every privilege not on it** from the token before exec.
- Restriction is **purely subtractive.** peinit removes privileges; it never adds them. A service cannot acquire a privilege its token's source did not grant.
- If `RequiredPrivileges` is absent, the token's default privilege set is used unchanged.

This is least-privilege made concrete: `eudev`, for instance, runs as SYSTEM (it needs device access during early boot) but with its privilege set stripped to the minimum it actually uses, so a compromise of `eudev` does not hand an attacker the full SYSTEM privilege set. Removal is permanent for the life of that token — it is not a disable that the service can re-enable. See [Privileges](/peios/security-fundamentals/privileges/overview.md) for what the individual privileges grant.

## Which identity runs what

A service is not just its main process — it has hooks, health checks, and a reload command, and each runs under a defined identity:

| Context | Runs as |
|---|---|
| **Main process** | The service's `Identity`. |
| **`ExecStartPre` / `ExecStartPost`** | `HookIdentity` if set, otherwise the service's `Identity`. |
| **Health checks** | The service's `Identity`, always. |
| **`ExecReload`** (command form) | The service's `Identity`, always — `HookIdentity` does not apply. |
| **[Ad-hoc jobs](/peios/using-peios/services-and-jobs/jobs-and-operations.md)** | The token captured by JFS — the submitter's (possibly impersonated) identity. |

Token materialisation for hooks follows the same rules: a `HookIdentity` of `SYSTEM` is minted; any other principal is requested from authd, at the point the hook runs.

`HookIdentity` exists so hooks can run with *different* — usually higher — privileges than the service itself: creating directories in privileged locations, or running a database migration as an admin identity, before the long-running daemon drops to a lesser identity.

> [!WARNING]
> peinit does **not** validate filesystem permissions on hook binaries. If `HookIdentity` grants elevated privileges, it is the administrator's responsibility to ensure the hook binary and every parent directory are not writable by a lower-privileged identity — otherwise a less-trusted principal could replace the hook and run code as the elevated identity. ([FACS](/peios/security-fundamentals/file-access/overview.md) enforces KACS descriptors on managed filesystems, but peinit itself performs no such check — protecting hook binaries still depends on correct SDs from packaging and controlled paths.)

## Security invariants

The identity model rests on a few rules peinit never breaks:

1. **peinit never shares its SYSTEM token.** Even `Identity=SYSTEM` services get a separately minted token.
2. **peinit never drops its own SYSTEM identity.** PID 1 runs as SYSTEM for the life of the system — its identity is axiomatic, granted by the kernel at boot.
3. **Privileges are subtractive only.** `RequiredPrivileges` removes; it can never add.
4. **Identity is deterministic.** Every service runs as a known principal. `SYSTEM` must be declared explicitly; an empty `Identity` is the minimal `LocalService`, never SYSTEM.

## Where to start

`Identity` decides what a service *can do*; the [ServiceSecurity descriptor](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md) decides *who can manage it* — two independent concerns. Read [Who can manage a service](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md) for the other half.

To see how the token is installed alongside the rest of the process setup, read [The execution environment](/peios/using-peios/services-and-jobs/execution-environment.md).

For the identity primitives themselves — tokens, SIDs, privileges — start at [Tokens](/peios/security-fundamentals/tokens/overview.md).

---

# The execution environment

_Peios / Using Peios / Services & jobs_

> What a service process starts with — its cgroup tree, stdio, layered environment, limits, hooks, conditions and asserts, and the fd store.

When peinit starts a service, the process it hands you is built deliberately — a clean context with a known identity, a known environment, and only the resources peinit chose to give it. This page covers everything about that context except the [token](/peios/using-peios/services-and-jobs/identity-and-privileges.md) (its own page): the cgroup the process lives in, its standard streams, the environment it sees, the limits on it, and the hooks and checks that run *around* the start.

## A clean context

A service inherits **only** what peinit explicitly hands it — its standard streams and any [stored file descriptors](#the-fd-store). Every other descriptor peinit holds — the control socket, the notify socket, the event-loop fd, the registry and authd and eventd connections — is opened close-on-exec, so it closes automatically at exec and can never leak into a service. peinit also resets the child's signal state to defaults (it runs with all signals blocked for its own [signalfd](/peios/using-peios/services-and-jobs/shutdown.md); a service must not inherit that). The guarantee is that a service starts from a clean slate, not from peinit's privileged one.

## The cgroup tree

Every service runs in its own [cgroup](/peios/using-peios/threads-and-processes/process-lifecycle.md) tree under `/sys/fs/cgroup/peinit/`, with separate sub-cgroups for the main process, hooks, and health checks:

```
/sys/fs/cgroup/peinit/<service>/
                       ├── main/     the main process
                       ├── hooks/    pre/post hooks
                       └── health/   health checks
```

peinit uses cgroups for two things only: **tracking** every process a service spawns (so a child that forks its own children is still accounted to the service), and **clean kill** (terminating the entire tree at once, so nothing is orphaned). It does *not* use cgroups for resource accounting or limits.

The split into sub-cgroups serves a real purpose: it satisfies cgroup v2's "no internal processes" rule, and it lets peinit kill a service's *hooks* or *health checks* without touching the main process.

When an old tree cannot be removed — because a process survived SIGKILL in [D-state](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) and leaked it — peinit creates a fresh **generational** tree (`<service>.gen<N>`) for the next start, so a stuck old instance never blocks a new one. Leaks are surfaced in the service's `warnings`; they are a sign of an underlying I/O fault, covered in [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md).

## Standard streams

peinit wires a service's standard streams before exec:

- **stdin** is redirected to `/dev/null`. peinit never gives a service an interactive input channel; a service that needs input must obtain it explicitly (a socket, a stored fd).
- **stdout** and **stderr** are redirected to pipes peinit holds, so it can capture and forward every line. This is the subject of [Service output and logging](/peios/using-peios/services-and-jobs/output-and-logging.md).

### Attaching a terminal

A service that sets **`TTYPath`** to an absolute terminal path — `/dev/console`, `/dev/tty1`, a serial line — gets that terminal on all three standard streams instead of the wiring above, and becomes a session leader owning it as its **controlling terminal**. That last part is what makes job control work: Ctrl-C interrupts, `fg`/`bg` behave, and a hangup reaches the process group. It is what a boot shell or a `login` prompt needs.

It carries a path rather than a flag because which terminal is a per-service question: a maintenance shell on `tty1` while the kernel console is a serial line is an ordinary thing to want.

> [!WARNING]
> Attaching a terminal **suppresses log capture** — the pipes eventd would read are closed, so the service's output goes to the terminal and nowhere else. Don't set `TTYPath` on a service whose output you expect to find in the logs.
>
> Two services pointed at the same terminal will fight over it: both read the same input, and neither is told about the other. peinit does not currently detect this. Until it does, treat "one service per terminal" as a rule you enforce yourself, or declare a `Conflicts` between them.

## The environment

peinit constructs each service's environment in layers, lowest precedence first — it does **not** pass through its own near-empty startup environment.

1. **Compiled-in base.** A fixed floor peinit always provides — currently just `PATH`:
   `/bin`.
2. **Global `EnvVars`.** Each value under `Machine\System\Init\EnvVars\` becomes a variable (value name → variable name). `EnvVars\PATH` overrides the base `PATH`; other names add. Every service gets this layer except **registryd**, which serves the key itself — see the warning below for why that exemption exists.
3. **Per-service `Environment`.** The definition's own `KEY=VALUE` entries, which override layers 1–2.
4. **Protocol variables.** `NOTIFY_SOCKET` (always) and `LISTEN_FDS` / `LISTEN_FDNAMES` (only when [stored fds](#the-fd-store) are injected). These have the **highest** precedence and cannot be overridden — a service that clobbered `NOTIFY_SOCKET` would break [sd_notify](/peios/using-peios/services-and-jobs/service-types.md).

A change to `EnvVars\` takes effect on a service's *next start*, like the per-service `Environment` field — it is not pushed into running services.

peinit deliberately does **not** set `HOME`, `USER`, `LOGNAME`, `SHELL`, or `TERM`. Peios identity is a [token](/peios/using-peios/services-and-jobs/identity-and-privileges.md) (a SID), not a passwd entry, so there is no canonical home directory or login shell to fill in. A service that needs any of these supplies it through `EnvVars\` or its own `Environment`.

> [!WARNING]
> Write access to `Machine\System\Init\EnvVars\` is equivalent to compromising **every service peinit starts** — a principal who can set it can inject `LD_PRELOAD`, `LD_LIBRARY_PATH`, and the like into all of them. peinit deliberately does not filter variable names; the key's [security descriptor](/peios/using-peios/registry-security/access-control.md) is the control boundary. Keep it tight — the recommended default is SYSTEM full, Administrators read-only.
>
> This is also why **registryd** is the one service that never receives this layer: it is the daemon that *serves* the key. Letting the key inject into the process that enforces who may write it would make the control boundary self-referential. Every other service, platform daemons included, gets the ordinary layering.

## Working directory and limits

| Field | Effect |
|---|---|
| `WorkingDirectory` | The process's working directory. Must be a non-empty absolute path; defaults to `/`. |
| `LimitNOFILE` | `RLIMIT_NOFILE` — the maximum number of open file descriptors. |
| `LimitCORE` | `RLIMIT_CORE` — the maximum core-dump size, in bytes. |

peinit also sets `oom_score_adj`: `-1000` (OOM-immune) for `ErrorControl=Critical` services, and the default `0` for everyone else — so the kernel never picks a Critical platform daemon as an out-of-memory victim.

## Runtime directories

`RuntimeDirectories` gives a service private scratch space under `/run` without an `ExecStartPre` that has to `mkdir` it by hand. Each name you list becomes a directory created **directly under `/run`** — `RuntimeDirectories=myapp` yields `/run/myapp` — set up immediately before the main process starts. Each one gets a [security descriptor](/peios/using-peios/registry-security/access-control.md) granting full access to SYSTEM, Administrators, and the service's own [SID](/peios/using-peios/services-and-jobs/identity-and-privileges.md), so the service can write to its directory while other principals cannot. If a directory can't be created, or its descriptor can't be applied, the start fails with `ParentSetupFailure`.

peinit does **not** remove these directories when the service stops. `/run` is a boot-scoped tmpfs, so they simply disappear at the next boot rather than being torn down on each stop — a restarting service finds its runtime directory (though not necessarily its contents) already in place.

For the exact naming rules on each entry and the field's type and default, see the [Registry key reference](/peios/using-peios/services-and-jobs/registry-key-reference.md).

## Pre-exec and post-exec hooks

Hooks let a service do work *around* its main process without baking it into the binary.

- **`ExecStartPre`** runs *before* the main binary. Each command runs in sequence, in the `hooks/` sub-cgroup; **any** hook exiting non-zero aborts the start (the whole cgroup is killed and the service Fails with `PreHookFailure`). Use it for prerequisites that must hold before the service starts — creating a runtime directory, waiting on a precondition.
- **`ExecStartPost`** runs *after* readiness (Simple) or *after a successful exit* (Oneshot). A post-hook failure is **logged but does not fail the service** — by the time it runs, the service is already up. (A Oneshot that *fails* never runs its post-hooks.)

Hooks run under [`HookIdentity`](/peios/using-peios/services-and-jobs/identity-and-privileges.md) if set, otherwise the service's own identity. Their output is captured and tagged like the main process's, labelled with the hook (e.g. `jellyfin/ExecStartPre[0]`).

### Command string parsing

The command fields — `ExecStartPre`, `ExecStartPost`, `ExecReload`, `HealthCheck` — are parsed into an argument list with simple, predictable rules. **peinit never invokes a shell.**

- Whitespace splits the command into arguments; double quotes group text containing spaces into one argument and are not retained (`--name="hello world"` → one argument `--name=hello world`).
- There is **no** shell expansion, variable substitution, or globbing. Backslash is literal; a single quote is an ordinary character.
- The first argument is the executable and **must be an absolute path** beginning with `/`. Relative names and PATH search are validation errors.
- An empty or whitespace-only command, or an unclosed quote, is a validation error.

If a command genuinely needs shell features, wrap it in a script and point the field at the script.

`ExecReload` can also be a signal, written `signal:<NAME>` — for example `signal:SIGHUP`. The name must be an exact, canonical Linux signal name (uppercase, no aliases, no numbers); `SIGKILL` and `SIGSTOP` are rejected because they cannot be handled as a reload. With no `ExecReload` at all, reload sends `SIGHUP`. The reload lifecycle is covered in [Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md).

## Conditions and asserts

Before peinit runs any hook or the main process, it evaluates **conditions** and **asserts** — start-time checks in the same `type:argument` form. The difference is what failure *means*:

- A failed **Condition** *skips* the service — it transitions to [Skipped](/peios/using-peios/services-and-jobs/the-service-lifecycle.md), which **satisfies its dependents** (it succeeded by not needing to run). Conditions express "only run here if it makes sense."
- A failed **Assert** *fails* the service — it transitions to Failed with `AssertionError`. Asserts express "this was expected to run, and a precondition is missing."

Conditions are checked first; only if all pass are asserts checked. All entries are AND-ed. The check types:

| Type | Form | Passes when |
|---|---|---|
| `path` | `path:<path>` | the path exists (any type) |
| `file` | `file:<path>` | a regular file exists |
| `directory` | `directory:<path>` | a directory exists |
| `registry` | `registry:<key>` | the registry key exists |

Two constraints follow from peinit being single-threaded PID 1, which [must never block](/peios/using-peios/services-and-jobs/overview.md):

- **`registry:` checks are cache-only.** A `registry:` check may only name a key peinit already caches (under `Machine\System\Services\` or `Machine\System\Init\`); it is evaluated against the in-memory model, never a live read. Naming any other key is a validation error.
- **Filesystem checks run off the main loop.** `path:`/`file:`/`directory:` checks are performed by a short-lived forked helper, not a `stat()` on the event loop, because `stat()` can wedge on a hung mount.

That timeout is the `PreStartCheckTimeout` field, which defaults to **5 seconds**. A filesystem check that does not finish within it — say a `stat()` wedged on a hung mount — is treated as **not satisfied**, fail-safe: peinit kills the helper and treats the check as unmet, so a Condition skips the service and an Assert fails it, rather than letting the event loop hang.

## The fd store

The fd store lets a service preserve file descriptors **across restarts** — most usefully a listening socket, so a stateful daemon can restart without dropping connections. It is opt-in: `FdStoreMax` (default 0) sets the maximum number of fds peinit will hold; 0 disables it.

The mechanics ride on [sd_notify](/peios/using-peios/services-and-jobs/service-types.md):

- A service stores an fd by sending `FDSTORE=1` with the fd attached (and optionally `FDNAME=<name>`; the default name is `stored`). peinit holds it.
- It removes one with `FDSTOREREMOVE=1` and `FDNAME=<name>`.
- On the **next start**, peinit injects the stored fds starting at fd 3, sets `LISTEN_FDS` to the count and `LISTEN_FDNAMES` to the colon-separated names, then clears the store. (This is the same `LISTEN_FDS` convention systemd uses, so software with existing fd-passing support works unmodified.)

The store **survives an automatic restart** (crash → restart policy → new start) — that is the whole point. It is **cleared** on an *explicit* stop or shutdown (the service is not coming back), and when a [removed definition](/peios/using-peios/services-and-jobs/defining-a-service.md) is finally discarded.

## Where to start

For the identity half of the launch — how the token is chosen and installed — read [Service identity and privileges](/peios/using-peios/services-and-jobs/identity-and-privileges.md).

For what happens to the stdout/stderr pipes peinit holds, read [Service output and logging](/peios/using-peios/services-and-jobs/output-and-logging.md).

For how conditions, asserts, and hooks fit into the start sequence and its states, read [The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md).

---

# Controlling services

_Peios / Using Peios / Services & jobs_

> peiosctl and the control socket — the verbs and their rights, wait semantics, the command-by-state matrix, status output, and error codes.

`peiosctl` is the command-line tool for driving **peinit** at runtime — starting and stopping services, querying their state, reloading configuration, and shutting the system down.

```
peiosctl <command> [service] [flags]
```

```
$ peiosctl status jellyfin        # current state of one service
$ peiosctl start jellyfin         # start it, wait until Active or Failed
$ peiosctl list                   # every service you can query
$ peiosctl shutdown reboot        # graceful reboot
```

Underneath, `peiosctl` is a thin client over peinit's **control socket** at `/run/services/peinit/control.sock`. The wire protocol — not the CLI — is the normative interface, so everything here (commands, rights, semantics) holds regardless of which front-end you use.

## How the control interface works

The socket speaks **newline-delimited JSON**: one request object per line, one response object per line. A request and its success response look like this:

```json
{"command": "start", "service": "jellyfin", "wait": true}
{"status": "ok", "operation_id": "a1b2c3d4-...", "service": "jellyfin", "state": "active", "cause": "explicit_start", "warnings": []}
```

Two properties are worth knowing even if you only ever use `peiosctl`:

- **Every command is access-controlled.** When you connect, peinit captures your [token](/peios/using-peios/services-and-jobs/identity-and-privileges.md) from the kernel and runs [AccessCheck](/peios/security-fundamentals/access-decisions/overview.md) against the target service's descriptor for *every* command. There is no "trust localhost," no override. Who may do what is the subject of [Who can manage a service](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md).
- **Lifecycle commands create [operations](/peios/using-peios/services-and-jobs/jobs-and-operations.md).** A `start`/`stop`/`restart`/`reload`/`reset` returns an `operation_id` — a GUID you can poll. Conflict resolution between concurrent commands happens at the operation layer, which is why two simultaneous `start`s merge instead of colliding.

## Service commands

| Command | Does | Required right |
|---|---|---|
| `start` | Run the service through its full start sequence. | `SERVICE_START` |
| `stop` | SIGTERM, then SIGKILL after `StopTimeout`. | `SERVICE_STOP` |
| `restart` | Stop then start, as one operation. | `SERVICE_STOP` + `SERVICE_START` |
| `reload` | Re-read configuration (`ExecReload`, or SIGHUP). | `SERVICE_INTERROGATE` |
| `reset` | Clear `Failed`/`Abandoned`/`Skipped` → `Inactive`. | `SERVICE_STOP` |
| `status` | Report state, cause, PID, uptime, health, current job and operation, warnings. | `SERVICE_QUERY_STATUS` |
| `list` | List services and states (filtered to what you can query). | (per-service `SERVICE_QUERY_STATUS`) |

```
$ peiosctl restart jellyfin
$ peiosctl reload nginx
$ peiosctl reset failed-migration     # clear a Failed state without starting
```

## System commands

| Command | Does | Required right |
|---|---|---|
| `shutdown <type>` | Graceful [shutdown](/peios/using-peios/services-and-jobs/shutdown.md). `type` is `poweroff`, `reboot`, or `halt`. | `SYSTEM_SHUTDOWN` |
| `reload-config` | Re-read *all* definitions and rebuild the graph (atomic). | `SYSTEM_RELOAD_CONFIG` |
| `operation-status <id>` | Report the state of an operation by GUID. | `SERVICE_QUERY_STATUS` on its target |

```
$ peiosctl shutdown poweroff
$ peiosctl reload-config
$ peiosctl operation-status a1b2c3d4-...
```

## Wait semantics

By default, a lifecycle command **blocks until its operation reaches a terminal state** — `start` waits for Active (or Failed), `stop` waits for Inactive, and so on. Pass `--no-wait` to get the `operation_id` back immediately and poll with `operation-status` instead.

| Command | Default | Waits for |
|---|---|---|
| `start` | wait | Active (Simple) / Completed or Inactive (Oneshot), or Failed |
| `stop` | wait | Inactive |
| `restart` | wait | the successful start target, or Failed |
| `reload` | **no-wait** | (with `--wait`) the Reloading state to resolve |
| `reset` | immediate | — |

`reload` is the exception — it returns immediately by default, because a reload may have no observable completion. With `--wait`, the response carries a `mode`:

| `mode` | Meaning |
|---|---|
| `confirmed` | The service signalled `READY=1` (and, for a command reload, the command exited 0). |
| `advisory` | The reload was issued and the detection window elapsed without explicit confirmation. |
| `failed` | The `ExecReload` command exited non-zero or timed out. **The service stays Active** — a failed reload never takes down a running service. |

The detection window is a **fixed 2 seconds** — it is built in and is *not* configurable via the registry. If the service says nothing within that window, the reload resolves as `advisory` and the service stays Active.

> [!NOTE]
> A connection blocked on a `--wait` operation is *not* counted as idle, so it is not closed by `ConnectionTimeout`. It stays open until the operation resolves, bounded by the operation's own timeout (e.g. `StartTimeout`).

## The command × state matrix

A command sent to a service in an unexpected state returns an **error**, not a silent no-op. This matrix is the authority on what each command does in each [state](/peios/using-peios/services-and-jobs/the-service-lifecycle.md):

| Command | Inactive | Starting | Active | Reloading | Stopping | Completed | Backoff | Failed | Abandoned | Skipped |
|---|---|---|---|---|---|---|---|---|---|---|
| **start** | Start | MERGE | ALREADY | ALREADY | QUEUE | Start | DEFER | Start | ERROR | Start |
| **stop** | NOOP | Cancel+Stop | Stop | Stop | MERGE | Clear | Cancel | NOOP | ERROR | NOOP |
| **restart** | Start | QUEUE | Restart | Restart | QUEUE | Start | Restart | Start | ERROR | Start |
| **reload** | ERROR | ERROR | Reload | MERGE | ERROR | ERROR | ERROR | ERROR | ERROR | ERROR |
| **reset** | NOOP | ERROR | ERROR | ERROR | ERROR | ERROR | ERROR | Clear | Clear | Clear |
| **status** | OK | OK | OK | OK | OK | OK | OK | OK | OK | OK |

Legend:

- **MERGE** — an operation of this type is already running; your command merges into it and you get its GUID.
- **DEFER** — your `start` is accepted and creates a Pending start operation, but it does *not* run yet: it waits out the service's existing backoff deadline and then starts. If a deferred start is already waiting, you merge into it and get its GUID.
- **ALREADY** — already in the target state; returns the current status, not an error.
- **QUEUE** — queued as Pending; runs after the current operation finishes.
- **NOOP** — no effect; returns the current status.
- **Clear** / **Cancel** — clear the state to Inactive / abort the current operation, then proceed.
- **ERROR** — invalid for this state; returns an error with an explanation.

The [Backoff](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) column is the subtle one: the service is down with an automatic restart pending, so `start` is *deferred* — it creates a Pending start that honours the remaining backoff delay and only runs once that deadline expires (a second `start` merges into the one already waiting), `stop` cancels the pending restart and any deferred start (the service goes Inactive), `restart` cancels the automatic one and does an admin restart, and `reload`/`reset` are invalid because no process exists.

## Reading status

`status` returns the full picture for one service:

```json
{
    "status": "ok",
    "service": "jellyfin",
    "state": "active",
    "cause": "explicit_start",
    "status_text": "Listening on port 8096",
    "current_job": {"id": "a1b2...", "type": "service_main", "pid": 1234, "started_at": "...", "identity": "jellyfin-svc"},
    "current_operation": {"id": "e5f6...", "type": "start", "source": "admin"},
    "health": "healthy",
    "uptime_seconds": 86400,
    "definition_removed": false,
    "warnings": []
}
```

- `state` and `cause` are the [lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) pair — read them together.
- `status_text` is the latest `STATUS=` string the service sent via sd_notify (`null` if never sent; cleared on each restart).
- `current_job` and `current_operation` are the [job and operation](/peios/using-peios/services-and-jobs/jobs-and-operations.md) GUIDs, or `null`.
- `health` is `healthy`, `unhealthy`, `unknown`, or `null` (no health check).
- `definition_removed` is `true` when the definition was deleted but an instance is still [draining](/peios/using-peios/services-and-jobs/defining-a-service.md).
- `warnings` lists leaked sub-cgroups and other operator-relevant notices.

`list` returns a compact summary of every service you can query — services you lack `SERVICE_QUERY_STATUS` on are simply **omitted**, not denied:

```json
{"status": "ok", "services": [
    {"service": "jellyfin", "state": "active", "cause": "explicit_start", "health": "healthy"},
    {"service": "registryd", "state": "active", "cause": "dependency_start", "health": null}
]}
```

`operation-status` returns one operation by GUID; an unknown or expired GUID is the `UNKNOWN_OPERATION` error. (Operations are dropped after a short retention grace once terminal — long enough for a polling client to read the result, not forever.)

## Error codes

An error response is `{"status": "error", "code": "...", "message": "..."}`. The `code` is one of:

| Code | Meaning |
|---|---|
| `ACCESS_DENIED` | AccessCheck denied the command against the target descriptor. |
| `UNKNOWN_SERVICE` | No such service definition (also returned for `start`/`restart`/`reload` on a [definition-removed](/peios/using-peios/services-and-jobs/defining-a-service.md) service). |
| `UNKNOWN_OPERATION` | No such operation GUID — never existed, or dropped after its retention grace. |
| `MALFORMED_REQUEST` | The request line is not a single valid JSON object. |
| `REQUEST_TOO_LARGE` | The request exceeds `MaxRequestSize`. |
| `INVALID_COMMAND` | The `command` field is missing or unknown. |
| `INVALID_ARGUMENTS` | A required field is missing or malformed (e.g. `shutdown` with no valid `type`). |
| `INVALID_STATE` | Not valid for the service's current state (an `ERROR` cell above), or rejected because the system is already shutting down. |
| `OPERATION_TIMEOUT` | A `--wait` operation did not reach a terminal state within its timeout. |
| `INTERNAL_ERROR` | peinit hit an internal failure executing the command. |

The `message` is human-readable and non-normative — read it for context, key off the `code`.

## Connection limits

peinit enforces hard limits on the control socket, all tunable under `Machine\System\Init\`:

| Key | Default | Limits |
|---|---|---|
| `MaxControlConnections` | 32 | Concurrent client connections (excess are refused at connect). |
| `MaxRequestSize` | 65536 | Bytes per request. |
| `ConnectionTimeout` | 30 | Seconds an *idle* connection may sit before it is closed. |

## Exit status

| Code | Meaning |
|---|---|
| `0` | The command succeeded. |
| `1` | A usage error. |
| non-zero | The command failed — an access denial, unknown service, invalid state, or timeout. |

## Where to start

To understand the states the matrix refers to, read [The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md).

To understand the operations every lifecycle command creates and how to poll them, read [Jobs and operations](/peios/using-peios/services-and-jobs/jobs-and-operations.md).

To configure who may run each command, read [Who can manage a service](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md).

---

# Who can manage a service

_Peios / Using Peios / Services & jobs_

> Every control command runs AccessCheck against the service's ServiceSecurity descriptor. The rights, the defaults, and the system control descriptor.

A service is a *securable object*. Just as a [file](/peios/security-fundamentals/file-access/overview.md) or a [registry key](/peios/using-peios/registry-security/access-control.md) carries a [security descriptor](/peios/security-fundamentals/security-descriptors/overview.md) that decides who may touch it, a service carries one that decides who may **start, stop, query, or reload** it. peinit enforces it on *every* [control command](/peios/using-peios/services-and-jobs/controlling-services.md) — there is no command that skips the check.

This is the second, independent half of the security model. The first half, [Service identity and privileges](/peios/using-peios/services-and-jobs/identity-and-privileges.md), is about what a service *can do* (its token). This page is about who can *manage the service* — a completely separate question, answered by a completely separate descriptor.

## Two descriptors, two questions

A service is associated with **two** descriptors that are easy to conflate but answer different questions and are enforced by different components:

| Descriptor | Question | Stored | Enforced by |
|---|---|---|---|
| **Registry key SD** | Who can *read or edit the definition*? | On the `Machine\System\Services\<name>` key | [LCS](/peios/using-peios/registry-security/access-control.md), at key-open time |
| **ServiceSecurity SD** | Who can *manage the running service*? | As the `ServiceSecurity` binary value on that key | **peinit**, on every control command |

The registry key SD is ordinary [registry access control](/peios/using-peios/registry-security/access-control.md) and not peinit's concern — peinit reads definitions as SYSTEM, which has full access. The **ServiceSecurity** SD is peinit's domain, and the rest of this page is about it.

They are genuinely independent. An administrator might be able to *query a service's status* (ServiceSecurity grants `SERVICE_QUERY_STATUS`) but not *read its configuration* (the registry key SD denies read) — or the reverse. Runtime control and configuration access are separate concerns, and both combinations are valid.

## Service access rights

The ServiceSecurity descriptor grants these rights:

| Right | Bit | Grants |
|---|---|---|
| `SERVICE_QUERY_STATUS` | 0x0001 | Query state, PID, cause, health, warnings. |
| `SERVICE_START` | 0x0002 | Start the service. |
| `SERVICE_STOP` | 0x0004 | Stop the service. |
| `SERVICE_INTERROGATE` | 0x0008 | Reload the service. |
| `SERVICE_ALL_ACCESS` | 0x000F | All of the above — the "full access" granted to SYSTEM by default. |

`restart` requires **both** `SERVICE_STOP` and `SERVICE_START`, since it is a stop followed by a start. `reset` requires `SERVICE_STOP`.

When peinit evaluates the descriptor it maps the generic rights as follows, so a descriptor written with generic rights behaves sensibly:

| Generic | Maps to |
|---|---|
| `GENERIC_READ` | `SERVICE_QUERY_STATUS` |
| `GENERIC_WRITE` | `SERVICE_START` \| `SERVICE_STOP` \| `SERVICE_INTERROGATE` |
| `GENERIC_EXECUTE` | `SERVICE_START` \| `SERVICE_STOP` \| `SERVICE_INTERROGATE` |
| `GENERIC_ALL` | `SERVICE_ALL_ACCESS` |

## How a command is authorised

Every control command runs the same gate:

1. peinit captures the caller's [token](/peios/using-peios/services-and-jobs/identity-and-privileges.md) from the kernel (`kacs_open_peer_token`) — the caller's *effective* identity at connection time, so if the caller is [impersonating](/peios/security-fundamentals/impersonation/overview.md), the impersonated identity is what is checked.
2. peinit resolves the target service and its ServiceSecurity descriptor.
3. peinit runs [AccessCheck](/peios/security-fundamentals/access-decisions/overview.md): the caller's token against the descriptor, for the right the command needs.
4. **Denied** → return `ACCESS_DENIED` and **log the attempt** (caller SID, target service, requested right).
5. **Granted** → execute the command.

> [!NOTE]
> Access denials are always logged — caller, target, and the right requested. Silent denial is a specification violation. If you are debugging a denial, the audit record has everything you need; see [Debugging a denial](/peios/security-fundamentals/access-decisions/debugging-a-denial.md).

## The default descriptor

If a service has no `ServiceSecurity` value, it **inherits** its parent key's. If no ancestor sets one either, peinit applies a built-in default:

- **SYSTEM** (`S-1-5-18`) — full access.
- **Administrators** (`S-1-5-32-544`) — query and stop only.

So out of the box, administrators can see and stop a service but not start or reload it unless a descriptor grants more — a conservative default that you widen deliberately.

ServiceSecurity is **hot-reloaded**: a change to the value in the registry takes effect on the **next control request**, with no service restart. peinit picks the change up through a [registry notification](/peios/using-peios/registry-concepts/watches.md). This is why ServiceSecurity is in its own [mutability class](/peios/using-peios/services-and-jobs/defining-a-service.md) — access policy should be able to change without disturbing a running service.

## The system control descriptor

Some operations are not about any one service — `shutdown` and `reload-config` act on the whole system. These are checked against **peinit's own** descriptor, stored at `Machine\System\Init\ControlSecurity`:

| Right | Bit | Grants |
|---|---|---|
| `SYSTEM_SHUTDOWN` | 0x0001 | Initiate poweroff, reboot, or halt. |
| `SYSTEM_RELOAD_CONFIG` | 0x0002 | Re-read all definitions and rebuild the graph. |

Its generic mapping deliberately gives `GENERIC_READ` *nothing* — there is no "read" of the system control object, only the two actions:

| Generic | Maps to |
|---|---|
| `GENERIC_READ` | (nothing) |
| `GENERIC_WRITE` | `SYSTEM_RELOAD_CONFIG` |
| `GENERIC_EXECUTE` | `SYSTEM_SHUTDOWN` |
| `GENERIC_ALL` | `SYSTEM_SHUTDOWN` \| `SYSTEM_RELOAD_CONFIG` |

The default grants **SYSTEM** full access and **Administrators** both rights. peinit loads this descriptor at boot and hot-reloads it on registry change, exactly like ServiceSecurity.

## The list command filters, it does not deny

`list` is access-control-aware in a quieter way: it returns only the services the caller has `SERVICE_QUERY_STATUS` on, and simply **omits** the rest. A caller with no query rights gets an empty list, not a denial. This means a low-privilege principal cannot even enumerate the services it cannot see — the existence of a service is itself information the descriptor controls.

## The boundaries that hold

A few invariants are worth stating outright, because they are what make this trustworthy:

- **peinit never bypasses AccessCheck for a control operation.** No backdoor, no override flag, no "trust localhost."
- **The descriptors are the only policy inputs.** peinit consults the ServiceSecurity and ControlSecurity descriptors and nothing else — not config files, not environment variables, not hardcoded lists.
- **One service's state is never exposed to another without a check.** `status` is per-service access-controlled; `list` filters.

## Where to start

For the *other* half of the security model — what a service can reach once it is running — read [Service identity and privileges](/peios/using-peios/services-and-jobs/identity-and-privileges.md).

For the commands these rights gate, read [Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md).

For the descriptor and AccessCheck machinery itself, read [Security descriptors](/peios/security-fundamentals/security-descriptors/overview.md) and [Access decisions](/peios/security-fundamentals/access-decisions/overview.md).

---

# Jobs and operations

_Peios / Using Peios / Services & jobs_

> A job is one process execution; an operation is one requested action. Their lifecycles, types, and GUIDs — plus ad-hoc jobs submitted through JFS.

A [service](/peios/using-peios/services-and-jobs/defining-a-service.md) is a long-lived *definition*. But when you read a status output or query the [event log](/peios/security-fundamentals/auditing/overview.md), two shorter-lived objects show up alongside it: the **job** (one process execution) and the **operation** (one requested action). They are what make peinit observable — every run and every command carries a GUID you can follow. This page covers both, and the special case of **ad-hoc jobs**.

## Jobs: what actually ran

Every time peinit forks a process — a service's main binary, a [pre- or post-hook](/peios/using-peios/services-and-jobs/execution-environment.md), a [health check](/peios/using-peios/services-and-jobs/supervision.md), or an [ad-hoc run](#ad-hoc-jobs) — that single execution is a **job**, with its own GUID and exit result. If a service is the *what*, a job is *what actually ran*. A restart does not reuse a job; it creates a **new** one. The service always tracks its *current* job GUID, and a `status` query reports it.

Jobs come in five types:

| Type | Created for |
|---|---|
| `ServiceMain` | A service's main process. |
| `PreExecHook` | One `ExecStartPre` command. |
| `PostExecHook` | One `ExecStartPost` command. |
| `HealthCheck` | One health-check run. |
| `AdHoc` | A process submitted via [JFS](#ad-hoc-jobs). |

Their lifecycle is simpler than a service's, because a job only tracks a process — not policy:

| State | Meaning |
|---|---|
| `Created` | The job object exists but the process is not forked yet (pre-hooks may be running). |
| `Running` | The process is alive. |
| `Completed` | The process exited successfully. |
| `Failed` | The process failed — or peinit classified the job failed before fork (a parent-side setup error). |
| `Abandoned` | The process survived SIGKILL (D-state). |

A job record carries the things you would want for forensics: the resolved identity and a token summary, the image path and arguments, created/started/ended timestamps, the exit code or signal, the cgroup, the [failure cause](/peios/using-peios/services-and-jobs/the-service-lifecycle.md), and the `operation_id` that created it (`null` for ad-hoc jobs). The clean division of ownership is: the **service** owns policy (restart, dependencies, health schedule, current state); the **job** owns the execution facts (PID, exit, timestamps, identity, cgroup, log correlation).

### Retention and log correlation

peinit keeps only *active* jobs in memory. When a job reaches a terminal state it **emits a structured event** (through KMES) carrying the full record, then drops the job. peinit keeps **no** job history — [eventd](/peios/security-fundamentals/auditing/overview.md) is the historian, consuming those events from the kernel ring buffer. The lifecycle events are `job.created`, `job.started`, and `job.ended`.

Separately, all of a job's `stdout`/`stderr` is [forwarded to eventd](/peios/using-peios/services-and-jobs/output-and-logging.md) tagged with the job's GUID. That is what lets a query like "show me the logs for job X" return exactly that execution's output — not interleaved with the run before or after it.

## Ad-hoc jobs

An **ad-hoc job** is an arbitrary process a *service* asks peinit to run on behalf of one of its clients. It is not tied to a persistent definition — it runs once, reports its result, and is cleaned up. The motivating case: a service has [impersonated](/peios/security-fundamentals/impersonation/overview.md) a user and wants a process run *as that user*, but supervised centrally by peinit rather than by the service itself.

The obstacle is identity delegation: a service cannot simply forward an impersonation token over a socket. **JFS** (the Job Forwarding Subsystem) solves this in the kernel — it captures the caller's effective token and delivers it to peinit along with the job request. peinit forks, installs that captured token, and supervises the result like any other job.

> [!IMPORTANT]
> An ad-hoc job runs with **the submitter's own (possibly impersonated) token** — there is no identity field in an ad-hoc request. A caller cannot name an arbitrary identity; it can only pass through a token it already holds. This is what prevents escalation: a service cannot manufacture a job running as an identity it does not already possess.

An ad-hoc request carries a deliberately small subset of the service fields — `ImagePath`, `Arguments`, `Environment`, `WorkingDirectory`, `Timeout`, `Description`. A couple are worth pinning down: `Timeout` is in **seconds**, and `0` means *no limit* (the job runs for as long as it needs). `WorkingDirectory` defaults to `/` when you leave it out. The policy fields (restart, dependencies, health checks, `ErrorControl`, triggers) are **not** available — those belong to persistent definitions. An ad-hoc job runs once in its own cgroup, routes its output to eventd, and on exit emits `job.ended` and is dropped. If it overruns its `Timeout`, peinit escalates SIGTERM → SIGKILL exactly as a service stop does.

Ad-hoc jobs **bypass the operation model entirely** — there is no service to "start," so the request creates a job directly. The job *is* the whole lifecycle.

> [!NOTE]
> JFS is a kernel subsystem with its own byte-level interface, still being specified. peinit is its consumer; the *shape* of the protocol is settled, but the wire details are owned by JFS, so this path is not fully implementable from the peinit specification alone.

## Operations: what was requested

An **operation** is a first-class object representing a *requested state-machine action* on a service. Rather than letting [control commands](/peios/using-peios/services-and-jobs/controlling-services.md) mutate state directly, peinit turns each one into an operation that is validated, queued, and executed by its event loop. This is what gives concurrent callers — admin tools, automated triggers, dependency propagation — explicit, observable conflict resolution instead of races.

There are five operation types — `Start`, `Stop`, `Restart`, `Reload`, `Reset` — and each carries a **source** recording *why* peinit created it:

| Source | Created by |
|---|---|
| `Admin` | A control client. |
| `Boot` | The Phase 2 boot lifecycle. |
| `Shutdown` | The shutdown lifecycle. |
| `DependencyPropagation` | A start pulling in a [dependency](/peios/using-peios/services-and-jobs/dependencies.md). |
| `RestartPolicy` | An automatic [restart](/peios/using-peios/services-and-jobs/supervision.md). |
| `Timer` | A [timer](/peios/using-peios/services-and-jobs/triggers-and-timers.md) firing. |
| `BindsToRecovery` | A bound target returning to Active. |
| `BindsToPropagation` | A bound target stopping. |
| `ConflictResolution` | A `Conflicts` eviction. |
| `OnFailure` | A failed service's fallback handler. |

The source is why a `status` showing `current_operation.source: "restart_policy"` tells you the service is being auto-restarted, while `"admin"` tells you a person asked.

### Operation lifecycle

```mermaid
flowchart LR
    P["Pending"] --> R["Running"]
    R --> C["Completed"]
    R --> F["Failed"]
    R --> A["Aborted"]
    P --> M["Merged"]
    P --> X["Cancelled"]
    P --> F
```

| State | Meaning |
|---|---|
| `Pending` | Validated and queued, waiting on a precondition (e.g. a prior stop to finish). |
| `Running` | Executing — the service is transitioning. |
| `Completed` | Achieved its goal (start reached Active/Completed; stop reached Inactive/Failed). |
| `Failed` | Did not achieve its goal — including timing out while still Pending. |
| `Merged` | Folded into an identical operation already in flight (records the survivor's GUID). |
| `Cancelled` | Terminated while still Pending — never ran. |
| `Aborted` | Terminated while Running — interrupted in progress. |

`Cancelled` and `Aborted` are the same idea at two points: cancelled never ran, aborted was running. The *reason* (admin action, supersession) is a property of the event, not the state.

### Conflict resolution and merging

When a command arrives for a service that already has an operation in flight, peinit resolves the collision deterministically — the same logic the [command × state matrix](/peios/using-peios/services-and-jobs/controlling-services.md) summarises:

- **Same type** (start over start, stop over stop, reload over reload) → **merge**. The new caller transparently receives the existing operation's GUID; from their side, their request is in progress.
- **Stop wins over start.** An explicit stop cancels a pending start or aborts a running one. The admin said stop.
- **Later supersedes earlier**, and a start while a stop is in flight is **queued** to run after the stop.

### Timeout and retention

An operation inherits its target's timeout as its maximum lifetime — `StartTimeout` for start/reload/reset, `StopTimeout` for stop, and the sum of both legs for restart. Crucially, **the clock starts at operation creation, including queue time** — from the caller's perspective they have been waiting since they sent the command, so a long queue can time an operation out before it even runs.

Terminal operations are emitted as events (`operation.requested`, `.started`, `.completed`, `.failed`, `.cancelled`, `.merged`, `.aborted`) and dropped from memory after a short grace (default 60 s — long enough for a polling client to read the result). As with jobs, peinit keeps no operation history; eventd does.

## How they surface

You meet jobs and operations in three places:

- **`status`** — `current_job` and `current_operation` give the GUIDs of the service's active execution and active action (or `null`).
- **`operation-status <id>`** — polls one operation to a terminal state; a `--wait` lifecycle command does this for you under the hood.
- **[eventd](/peios/security-fundamentals/auditing/overview.md)** — the durable history. Every job and operation lifecycle transition lands there as a structured event, which is how you reconstruct what happened after the objects themselves are gone from peinit's memory.

## Where to start

To create and poll operations from the command line, read [Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md).

To understand the service states an operation drives a service through, read [The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md).

To query the durable job and operation history, read [Auditing](/peios/security-fundamentals/auditing/overview.md).

---

# Service output and logging

_Peios / Using Peios / Services & jobs_

> peinit captures every service's stdout and stderr, buffers before eventd exists, and forwards after — best-effort logs, guaranteed audit events.

peinit is **not** a logging system — storage, indexing, and queries belong to [eventd](/peios/security-fundamentals/auditing/overview.md). But peinit holds the `stdout`/`stderr` pipes of every service at the moment it forks, so it is unavoidably in charge of *where output goes*, and it has to cope with the window early in boot before eventd even exists. This page is about that responsibility.

## Capturing output

When peinit forks a service it creates pipes for `stdout` and `stderr`, redirects the child's streams to the write ends, and keeps the read ends — monitoring them in its event loop. (The child's `stdin` is `/dev/null`; see [The execution environment](/peios/using-peios/services-and-jobs/execution-environment.md).) It reads output **line by line** and tags each line with:

- the **service name**,
- the **stream** (stdout or stderr),
- a **timestamp**,
- the **[job](/peios/using-peios/services-and-jobs/jobs-and-operations.md) GUID**.

The job GUID is the important one: it is what lets a later query return exactly one execution's output, never interleaved with the run before or after it. Hook output is captured too, labelled with the hook (e.g. `jellyfin/ExecStartPre[0]`), and so is health-check output — invaluable for diagnosing *why* a check failed.

## Logs are best-effort; audit events are not

This is the distinction to internalise, because it explains why some things survive a crash and others do not. peinit handles **two different streams of information** with two different guarantees:

| | Service logs | Audit events |
|---|---|---|
| What | `stdout`/`stderr` from services and hooks | Access denials, critical failures, recovery-mode entry, graph errors, job/operation lifecycle |
| Path | Forwarded to eventd's log socket | Emitted into the **KMES** kernel ring buffer |
| Guarantee | **Best-effort** — may be dropped under load | **Durable** — survive eventd restarts and reboots |
| Available from | Once eventd is up (buffered before) | The moment PKM loads — before eventd, before the registry |

Service logs are a convenience: useful, but lossy by design. **Audit events are part of the security posture** and go through [KMES](/peios/security-fundamentals/auditing/events-and-transport.md), which persists them in the kernel from the instant the module loads. So peinit keeps a pre-eventd *buffer* for logs but needs none for events — eventd picks events up from the ring buffer whenever it attaches, no matter how late.

## The pre-eventd buffer

Before eventd is running there is nowhere to send logs — its socket does not exist yet. peinit therefore buffers captured output in a fixed-size in-memory **pre-eventd buffer** (default 1 MB); when it fills, the oldest entries are dropped.

In practice the only services that run before eventd are `registryd`, `lpsd`, `authd`, and `eudev`. The first three are Peios-owned with controlled output; `eudev` is the real overrun risk (verbose device enumeration), and the [restart budget](/peios/using-peios/services-and-jobs/supervision.md) naturally bounds output from a crash loop.

## Flood protection

A noisy service must never starve peinit's event loop. Several limits enforce that:

| Key (`Machine\System\Init\`) | Default | Limits |
|---|---|---|
| `MaxLogLineLength` | 8192 | Bytes per line; longer lines are truncated with a `[truncated]` marker. |
| `MaxLogBufferPerService` | 65536 | Bytes buffered per service pipe before back-pressure. |

Two mechanisms back these up:

- **A per-iteration read budget.** Each pass of the event loop reads at most a bounded number of bytes from service pipes before moving on to other events — so no single chatty service can monopolise a loop iteration.
- **Back-pressure, not dropping.** If a service writes faster than peinit reads, the kernel pipe buffer fills and the service's own `write()` calls block. This is deliberate: the service slows down. While reading the pipe, peinit does **not** drop output — the no-silent-drop guarantee covers *reading the pipe*. It is only *downstream* (the pre-eventd buffer, and eventd's datagram socket) that delivery becomes lossy.

> [!NOTE]
> Signals are never starved. In every loop iteration, child reaping (SIGCHLD) and shutdown handling take priority over all other event sources, including log reading. A flood of service output can never delay peinit noticing that a service died or that the system is shutting down.

## The eventd handoff

When eventd reaches Active, peinit switches from buffering to forwarding:

1. It begins sending to eventd's log datagram socket (path from `Machine\System\eventd\LogSocketPath`).
2. It **replays the pre-eventd buffer**, oldest first, preserving each line's timestamp and metadata. This replay is best-effort — the records are datagrams on a loss-tolerant socket and some may be dropped under load; peinit never blocks waiting to deliver them.
3. It switches to real-time forwarding — new output goes out as it arrives.
4. It clears the buffer.

From then on peinit is a pipe relay: read a line, tag it, forward it as a datagram. The log socket is **non-blocking and loss-tolerant** — if eventd cannot drain it fast enough the kernel drops further datagrams silently, because log ingestion must never exert back-pressure on the whole system through PID 1. peinit keeps no unbounded outbound buffer and never blocks on a send to eventd.

If eventd itself crashes after starting, peinit (which supervises it like any service) notices, **re-enables the pre-eventd buffer**, and repeats the handoff when eventd comes back. There is a log gap across the restart, bounded by the buffer size — but audit **events** are unaffected, because they were going to KMES the whole time and eventd resumes consuming them from the last persisted sequence.

## Console output

peinit writes its **own** operational messages to `/dev/console`:

- Phase 1 progress (mount results, registryd start),
- Phase 2 progress (service starts and failures, dependency errors),
- shutdown progress,
- recovery-mode entry,
- critical service failures.

Service `stdout`/`stderr` is **not** echoed to the console by default — the console is for peinit's status messages only. This keeps the console legible during boot and, crucially, usable in [Recovery mode](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md), where it may be the only interface an administrator has.

## Where to start

To see how the pipes are wired and what else the process inherits, read [The execution environment](/peios/using-peios/services-and-jobs/execution-environment.md).

To follow the job GUID that tags every log line, read [Jobs and operations](/peios/using-peios/services-and-jobs/jobs-and-operations.md).

For where the logs and events actually go — storage, indexing, and queries — read [Auditing](/peios/security-fundamentals/auditing/overview.md).

---

# Boot and boot modes

_Peios / Using Peios / Services & jobs_

> The two-phase boot — hardcoded bootstrap, then registry-driven — and the Full, Safe, and Recovery modes with the boot-attempt counter between them.

peinit's boot has two phases and a chicken-and-egg problem to solve. The problem: peinit reads everything it does from the [registry](/peios/using-peios/registry-concepts/overview.md), but the registry is served by a daemon that something has to start first — and the identity authority that would mint that daemon's token does not exist yet either. The solution is to split boot into a **hardcoded** bootstrap that needs no registry, and a **registry-driven** phase that starts once the registry is up. The boundary between them is `registryd`.

## Where peinit takes over

peinit is PID 1, but it is not the *first* thing that runs. The [initramfs](/peios/security-fundamentals/boot-and-trust-establishment/initramfs-stage.md) assembles and mounts the real root — decryption, RAID/LVM, the root filesystem itself — and then hands control to peinit. The contract peinit relies on is narrow:

- The real root is already mounted **read-write**, and `/proc`, `/sys`, `/dev` are mounted and moved into it.
- peinit is exec'd as PID 1 from a fixed path on the real root.

peinit **does not** assemble, decrypt, repair, or even re-mount the root — those need tools and configuration that belong to the initramfs. It also does not `fsck` the root or mount non-root storage (a data partition is mounted by an ordinary Oneshot service, not by peinit). For the trust and identity side of this handoff — signatures, the SYSTEM token peinit inherits — see [peinit at PID 1](/peios/security-fundamentals/boot-and-trust-establishment/peinit-pid-1.md).

## Phase 1: the hardcoded bootstrap

Phase 1 is compiled into peinit. It cannot change at runtime and touches no registry. It does the minimum to make Phase 2 possible:

1. **Confirm the root is writable** with a single probe write. registryd's storage needs a writable root even for reads, so a read-only root cannot support Phase 2 → Recovery.
2. **Mount the remaining virtual filesystems** — `/dev/pts`, `/dev/shm`, `/run`, `/sys/fs/cgroup` — mounting each only if absent. A failure here → Recovery.
3. **Restore the persisted random seed** from `/var/state/peinit/random-seed`, mixing it into the kernel's entropy pool early so anything that needs randomness during boot gets it. A missing seed is normal — first boots and stateless live boots have none — so peinit just carries on; a seed problem is never fatal and never sends boot to Recovery.
4. **Establish the machine-id** from `/lcl/etc/machine-id` — a stable, opaque identifier for this install (used for log correlation, instance identity, and software compatibility). It is *not* a security principal: it is not a SID, an account, or a credential, and no authorisation decision depends on it. If the file is missing, empty, or malformed, peinit generates a fresh 128-bit ID and writes it before continuing.
5. **Set the clock from the hardware RTC**, so early timestamps and the boot counter are meaningful. A failure here → Recovery.
6. **Start registryd** and wait for it to signal readiness, then **probe-read** the schema-version key to confirm it is actually serving reads. Any failure → Recovery — there is no Phase 2 without a registry.
7. **Provision boot-time paths.** With registryd up and before Phase 2 starts, peinit applies the entries under `Machine\System\Init\ProvisionedPaths\` — the registry-driven equivalent of tmpfiles.d, creating directories and files (with Peios security descriptors) that no single service owns. Best-effort entries that fail are logged and skipped, but an entry marked `Required=1` that cannot be provisioned sends boot → Recovery. The individual keys are cataloged in the [registry key reference](/peios/using-peios/services-and-jobs/registry-key-reference.md).
8. **Infrastructure setup** — create the [control socket](/peios/using-peios/services-and-jobs/controlling-services.md), open the JFS device for [ad-hoc jobs](/peios/using-peios/services-and-jobs/jobs-and-operations.md), bring up the loopback interface. Control-socket failure → Recovery; the other two are logged as warnings and boot continues.

Most Phase 1 failures are fatal to a normal boot, because none of the later machinery can run without this foundation — the only outcome is [Recovery mode](#recovery-mode). The exceptions are the fail-soft steps called out above: a missing or unusable random seed, a regenerated machine-id, and best-effort provisioned paths all let boot continue.

> [!CAUTION]
> Packaged images, VM templates, and live ISOs must not ship a populated `/lcl/etc/machine-id` or a `/var/state/peinit/random-seed` file. A shipped machine-id gives every clone the same identity, and a shipped seed is a public value that is not acceptable entropy. Clone and reset tooling should remove or truncate `/lcl/etc/machine-id` so peinit generates a fresh ID on the next boot, and should never bake a seed into the image — if you need strong first-boot randomness for a stateless image, provide a real kernel entropy source (hardware RNG or virtio-rng) instead.

### registryd and loregd

`registryd` is an **interface**, not a specific program. It is the path peinit execs to get a registry source daemon — the component that implements the registry's persistent storage and answers peinit's reads. The *implementation* behind that interface can vary; by default it is **loregd**.

This split matters in exactly one place: **Recovery mode**. In normal operation you only ever deal with the `registryd` abstraction — peinit starts it, treats it as opaque, and reads the registry through it. But when the registry *itself* is what broke, you need tools that work *without* a running registry, and those tools talk to the implementation directly. That is why the recovery tooling is named `loregd` (`loregd --inspector`, `--recover-from-backup`, …): in recovery you are working with the storage implementation, not the registry abstraction. It is the one context where the distinction is visible to an administrator.

## Phase 2: the registry-driven boot

With registryd serving reads, peinit boots the rest of the system from the registry:

1. **Read all definitions** under `Machine\System\Services\`. (A registry read timing out here → Recovery.)
2. **Build and validate the dependency graph** from the boot-triggered services and their transitive [dependency closure](/peios/using-peios/services-and-jobs/dependencies.md). Validation runs *before* anything starts.
3. **Start services in dependency order**, in [parallel](/peios/using-peios/services-and-jobs/dependencies.md) up to `MaxParallelStarts`, with [readiness gating](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) releasing each service's dependents as it becomes satisfied.

Only services with a `boot` trigger are start candidates; demand-only services are pulled in only if something boot-triggered depends on them, and [Disabled](/peios/using-peios/services-and-jobs/triggers-and-timers.md) services are excluded from the graph (but kept in the model for on-demand start). The whole boot runs against one [snapshot](/peios/using-peios/services-and-jobs/defining-a-service.md) — mid-boot registry edits do not perturb it.

The platform daemons come up first because everything rests on them. They are all SYSTEM, all minted by peinit (no authd yet), and all `ErrorControl=Critical`:

| Service | Phase | Identity source | Readiness | ErrorControl |
|---|---|---|---|---|
| registryd | 1 | minted by peinit | sd_notify | Critical |
| eudev | 2 | minted by peinit (privileges stripped) | process alive | Normal |
| lpsd | 2 | minted by peinit | sd_notify | Critical |
| authd | 2 | minted by peinit | sd_notify | Critical |
| eventd | 2 | minted by peinit | sd_notify | Critical |
| networking | 2 | authd | sd_notify | Normal |
| sshd | 2 | authd | process alive | Normal |
| application services | 2 | authd | per-service | Normal |

Once authd is up, every subsequent service gets its token through the [normal authd flow](/peios/using-peios/services-and-jobs/identity-and-privileges.md). The order above is *emergent* from the standard role definitions' dependencies, not hardcoded — change the dependencies and the order changes. In practice login services such as `sshd` come up last, so the system is fully operational before it starts accepting user sessions.

## The three boot modes

The three modes form an escalation path from normal operation to last-resort maintenance.

```mermaid
flowchart TD
    F["Full boot"] -->|success| OK["operational<br/>counter reset"]
    F -->|cycle/conflict w/ Critical| S["Safe mode<br/>(no reboot)"]
    F -->|Critical failure| RB["sync + reboot"]
    S -->|success| OKS["operational (reduced)<br/>counter reset"]
    S -->|Critical failure| RB
    RB --> CNT["counter increments"]
    CNT -->|counter ≥ N| REC["Recovery mode"]
    F -.->|Phase 1 failure| REC
```

### Full mode

The default. All boot-triggered services start in dependency order. A boot is **successful** — and the [boot-attempt counter](#the-boot-attempt-counter) resets to 0 — when every Critical service has reached and *held* a dependent-satisfying state (Active, Completed, or Skipped) for a grace period (`BootSuccessGrace`, default 30 s). The criterion is "satisfying," not "Active," so that a Critical Oneshot that reaches Completed can still mark the boot good.

### Safe mode

Safe mode starts a **reduced** set of boot-triggered services and rebuilds the graph from scratch using only those, dropping dependencies on excluded services. Two categories are eligible:

- **Critical services** (`ErrorControl=Critical`) — must start; a Critical failure here still follows the normal reboot path.
- **SafeMode services** (`SafeMode=1`) — best-effort; if they fail, Safe mode carries on without them.

Everything else is skipped. Safe mode is entered when:

- graph validation finds a **cycle involving a Critical service**, or
- an **unresolvable conflict involving a Critical service**, or
- the kernel command line says `peios.safemode=1`.

The first two are *configuration* errors — rebooting would just hit them again — so peinit downgrades **without** rebooting. Safe mode is **not** entered by a Critical service *crashing at runtime*; that follows the reboot path. And it is purely a boot-sequencing concern: once booted, you can start any service by hand exactly as in Full mode. It is the mode for "the configuration is broken but the TCB is healthy."

> [!NOTE]
> `SafeMode=1` and `ErrorControl=Critical` are *filters within the boot-triggered set*. They do not make a demand-only service auto-start in Safe mode — a service with no `boot` trigger stays demand-only regardless.

### Recovery mode

Recovery mode is the last resort, and it offers **no TCB guarantee** — the administrator gets an unrestricted SYSTEM shell on the console and must treat it with corresponding care. It is a maintenance *environment*, not a degraded boot.

In Recovery, peinit completes the Phase 1 basics, *tries* to start registryd (ignoring failure — the shell must appear regardless), skips all Phase 2 services, and execs a SYSTEM shell on `/dev/console` (`/bin/recsh` if present, else `/bin/sh`), respawning it if it exits. It is entered when:

- the **boot-attempt counter reaches N** (default 3),
- the kernel command line says `peios.recovery=1`, or
- **registryd fails during Phase 1** — entered immediately, with no reboot and no counter increment, because there is no Phase 2 to attempt.

Because the registry itself may be what broke, recovery provides tools that work without it — talking to the [loregd implementation](#registryd-and-loregd) directly:

| Tool | Does |
|---|---|
| `loregd --inspector` | Read the storage database directly for diagnosis. |
| `loregd --recover-from-backup` | Restore from an automatic backup taken on every registryd startup. |
| `loregd --dangerously-clear-database` | Wipe the registry entirely. Recoverable, because role definitions are the source of truth for service config. |

> [!IMPORTANT]
> Recovery mode requires console access — physical, IPMI, or serial. There is no remote recovery in the current design (emergency SSH and registry historical reversion are noted as post-v1 work). Plan console access for any machine you need to be able to recover.

## The boot-attempt counter

The counter is what turns a crash-looping Critical service into an eventual Recovery shell instead of an infinite reboot loop. It is a plain integer in a file at `/.peinit/boot-attempts` — *not* in the registry, because the registry may be the very thing that is broken.

- peinit **reads** it at startup, before choosing a mode. The Recovery threshold (`counter ≥ N`) is checked against this pre-increment value, so the default N of 3 admits exactly three attempts before Recovery. A missing file counts as 0; a corrupt or unreadable one → Recovery. Override N with `peios.bootattempts=N` on the kernel command line, or set it to `0` to disable the check when the counter is itself the fault.
- peinit **increments** it once per boot, right after confirming the root is writable and before Phase 2 — never before the root is known writable, or a read-only root would silently lose the increment and defeat escalation.
- The counter is **reset to 0** on a successful Full or Safe boot (after the grace period).
- If the counter file cannot be *written* (disk full), peinit treats it as 0 and continues — a write failure must not by itself trigger Recovery.

A [Critical service](/peios/using-peios/services-and-jobs/supervision.md) exhausting its restart budget — at boot or at runtime — triggers a sync and reboot, which increments the counter on the next boot. Repeat that enough and the counter crosses N, and peinit stops trying and hands you a Recovery shell. The counter deliberately does *not* try to catch a peinit too broken to reach its own increment; that is a binary-integrity problem, not a boot-loop problem.

## Boot configuration

| Key | Default | Controls |
|---|---|---|
| `Machine\System\Boot\MaxParallelStarts` | 10 | Services starting concurrently (must be > 0; invalid → Recovery). |
| `Machine\System\Boot\BootSuccessGrace` | 30 | Seconds a Critical service must hold a satisfying state before boot counts as successful. |
| `Machine\System\Boot\ShutdownTimeout` | 90 | Maximum seconds for the whole [shutdown](/peios/using-peios/services-and-jobs/shutdown.md) sequence. |
| `Machine\System\Boot\PostKillTimeout` | 5 | Seconds a service cgroup may take to drain after SIGKILL before it counts as stuck. |

## The kernel command line

peinit reads four `peios.*` tokens. They are deliberately few: everything peinit can read *after* registryd is serving belongs in the registry instead, where it can be inspected, secured and changed without editing a boot entry. What is left is either a per-boot mode decision or a Phase 1 value — one peinit needs before there is a registry to ask.

| Token | Effect |
|---|---|
| `peios.safemode=1` | Force [Safe mode](#safe-mode). |
| `peios.recovery=1` | Force [Recovery mode](#recovery-mode), whatever the counter says. |
| `peios.bootattempts=N` | Override the boot-attempt threshold; `0` disables the check. |
| `peios.notifysocket=PATH` | Move the sd_notify socket. Phase 1, because registryd is the first service to use it and it must be bound before registryd starts. |
| `peios.quiet=N` | How much peinit may write to the console: `0` write everything, `1` (default) stay out of a terminal a service owns, `2` also drop ordinary progress. See below. |

### Console noise: `peios.quiet`

peinit reports its progress to `/dev/console`, and so does any service that claims that terminal with [`TTYPath`](/peios/using-peios/services-and-jobs/execution-environment.md). Two writers, one device: a login prompt started while peinit is still narrating gets written over, and the reader cannot tell input from log. `peios.quiet` decides who yields.

It sets **two independent rules**, which is why it is a level and not a flag:

| | Terminal a service owns | Everywhere else |
|---|---|---|
| `peios.quiet=0` | peinit writes anyway | everything |
| `peios.quiet=1` *(default)* | only messages that mean the machine is about to be lost | everything |
| `peios.quiet=2` | only messages that mean the machine is about to be lost | errors only |

The rules **stack rather than scale**: errors are never less visible at `2` than at `1`. An error overrides a requested blackout — silence was a preference, and an error is news — but it does not override terminal ownership, which is not peinit's to override. Only losing the machine (dropping to [Recovery](#recovery-mode), halting with no shell, a Critical service about to force a reboot) is worth one corrupted line of somebody else's session.

[prelude](/peios/security-fundamentals/boot-and-trust-establishment/initramfs-stage.md) honours `peios.quiet=2` as well, since it writes to the same console. The other two levels mean nothing there — prelude exits before the first service starts, so no terminal has an owner yet.

> [!WARNING]
> Suppressed messages are **dropped, not stored**. Until peinit can forward them to [eventd](/peios/security-fundamentals/auditing/overview.md), `peios.quiet=1` means you lose peinit's narrative from the moment a login prompt appears. On a machine you are debugging, boot with `peios.quiet=0`.

A few lines escape all of this: whatever peinit and prelude print *before* they have read the command line. Neither can honour a preference it has not seen yet, and those lines are also the only evidence either started at all.

Unknown `peios.*` tokens are ignored, as is a malformed value on either of the two valued tokens — this parser runs before anything exists to report a diagnostic to, and refusing to boot over a typo in a tuning knob is the worse outcome.

> [!NOTE]
> There is no token that selects services. Which services start is decided entirely by what is defined under `Machine\System\Services` — see [Defining a service](/peios/using-peios/services-and-jobs/defining-a-service.md). A console shell or a login prompt is an ordinary service definition with a [`TTYPath`](/peios/using-peios/services-and-jobs/execution-environment.md), not a boot flag.

## Where to start

To understand the graph validation and parallel start that drive Phase 2, read [Dependencies and ordering](/peios/using-peios/services-and-jobs/dependencies.md).

To understand the Critical-failure reboot path that feeds the boot counter, read [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md).

For the trust and token side of early boot, read [peinit at PID 1](/peios/security-fundamentals/boot-and-trust-establishment/peinit-pid-1.md).

---

# Shutdown

_Peios / Using Peios / Services & jobs_

> Shutdown is boot in reverse — SIGTERM to SIGKILL in reverse dependency order, bounded by a global timeout, then seed, unmount, sync, and power off.

Shutdown is [boot](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) run in reverse. peinit stops services in **reverse [dependency](/peios/using-peios/services-and-jobs/dependencies.md) order** — dependents before the things they depend on — escalating from a polite SIGTERM to a forced SIGKILL, all bounded by a global timeout, and then unmounts, syncs, and performs the final power action. This page covers how it is triggered and how it runs.

## How shutdown is triggered

There are four paths into shutdown, and they are not equal — three are graceful, one is not.

**A control command.** An administrator with `SYSTEM_SHUTDOWN` runs `peiosctl shutdown <type>`:

| Type | Result |
|---|---|
| `poweroff` | Stop everything, unmount, power off. |
| `reboot` | Stop everything, unmount, reboot. |
| `halt` | Stop everything, unmount, halt (CPU stopped, power stays on). |

**Signals.** As PID 1, peinit treats a handful of signals as shutdown requests:

| Signal | Meaning |
|---|---|
| SIGINT | Reboot — the kernel sends this on Ctrl-Alt-Del. |
| SIGTERM | Poweroff. |
| SIGPWR | Poweroff — a compatibility path for environments that surface power failure or power-button policy as a signal. |

**The power button.** On Linux, a physical power-button press is a graceful `poweroff` — peinit watches the machine's input devices under `/dev/input` and treats a `KEY_POWER` press (the key going down, not its release or auto-repeat) as a shutdown request. This path is deliberately minimal and **fail-soft**: if `/dev/input` is missing, an input device cannot be opened, or a device stops responding, peinit just keeps running and drops that device — the button quietly stops working, but the machine stays up and you can still shut down over the control socket or with a signal. It is not a full power-management policy engine; a future acpid/logind-style daemon can layer richer policy on top by sending control-socket commands.

**A Critical service failure** — the ungraceful path. When an `ErrorControl=Critical` service exhausts its [restart budget](/peios/using-peios/services-and-jobs/supervision.md), peinit **syncs and reboots immediately**. There is no service-stop ordering: the system is in an undefined state and the fastest route to a known one is a reboot. This is what feeds the [boot-attempt counter](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md), and a Critical service that fails identically every boot becomes a reboot loop that the counter eventually breaks by escalating to Recovery.

## The graceful sequence

For a `poweroff`, `reboot`, or `halt`, peinit runs an ordered sequence:

1. **Enter the shutdown state.** A flag is set, and while it holds: **no new services may start**, timer triggers are disarmed, and new control commands are rejected with `INVALID_STATE` — *except* `status` queries, which keep working so you can watch progress.
2. **Suspend Critical-failure semantics.** If a Critical service fails *during* shutdown, it is logged but does **not** trigger a reboot — the system is already going down, and rebooting would loop.
3. **Clear Completed services.** Oneshot services sitting in [Completed](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) (with `RemainAfterExit`) have no process; peinit moves them to Inactive so their dependents can be stopped cleanly.
4. **Stop services in reverse dependency order**, in waves. Each graceful-stop-eligible service (those in Active or Reloading) gets SIGTERM and `StopTimeout` seconds to exit; if it does not, peinit SIGKILLs its whole cgroup. A service is never stopped until everything that `Requires` or `BindsTo` it has already stopped. Services that are Starting are not stopped gracefully — their startup is cancelled and the cgroup SIGKILLed.
5. **Enforce the global timeout.** The whole sequence is bounded by `ShutdownTimeout` (default 90 s). When it expires, all remaining services are SIGKILLed; any whose cgroups do not empty within the post-kill timeout (default 5 s) become [Abandoned](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) (leaked), and shutdown continues regardless.
6. **Save the random seed.** With every service stopped and before any filesystem is touched, peinit writes a fresh seed drawn from the kernel's CSPRNG to `/var/state/peinit/random-seed` — this is the entropy the *next* boot starts from. The write is crash-conscious (written to a temporary file, flushed, then atomically swapped in), and it is best-effort: if it fails, peinit logs it and carries on. Shutdown never stalls on it, and the forced and Critical-reboot paths skip it entirely in favour of getting the machine down fast.
7. **Unmount filesystems.** peinit snapshots the mount table and tries to unmount **every** remaining non-root mount in its namespace — not just the ones it mounted itself — working from the deepest mount points outward. That includes the Phase 1 set (`/proc`, `/sys`, `/dev`, `/dev/pts`, `/dev/shm`, `/run`, `/sys/fs/cgroup`) wherever those are still mounted. A mount that is already gone counts as done. If one won't unmount because it is busy, peinit falls back to remounting it read-only; anything it still can't clean up is logged and left for the final `sync()` and the kernel, never blocking shutdown. The root filesystem (`/`) is never unmounted — once the rest are handled, peinit remounts root read-only.
8. **Sync and finish.** `sync()` to flush pending writes, then the final action — power off, reboot, or halt.

The reverse ordering falls straight out of the [dependency graph](/peios/using-peios/services-and-jobs/dependencies.md); there is no separate stop-order configuration. The last services to stop are the TCB daemons everything rests on — `eventd`, then `authd`, then `lpsd`, then `registryd` dead last, mirroring its position as the first ever started.

## STOPPING=1 and timeout extension

A service that begins shutting down on its own — say, in response to an internal error — can tell peinit by sending `STOPPING=1` via [sd_notify](/peios/using-peios/services-and-jobs/service-types.md). peinit acknowledges it and, importantly, **stops sending SIGTERM** to that service: it is already on its way down, and a redundant signal could interfere.

`STOPPING=1` is a courtesy, **not** a timeout extension. The `StopTimeout` keeps running; if the process has not exited when it expires, peinit escalates to SIGKILL regardless. A service that genuinely needs longer must say so with `EXTEND_TIMEOUT_USEC` (see [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md)) — and during shutdown those extensions are capped not only by the per-service `StopTimeout` × 4 but also by the remaining global `ShutdownTimeout`, whichever is smaller.

## Forced shutdown

When something is wedged and you need the machine down *now*, **repeated SIGINT** forces it: three Ctrl-Alt-Del presses within five seconds skip the graceful sequence entirely — SIGKILL everything, sync, reboot. It is the escape hatch for a graceful shutdown that is itself stuck behind an unresponsive service.

## Shutdown during boot

A shutdown requested while peinit is still in Phase 2 boot takes effect immediately: services still Starting are SIGKILLed (they never reached Active, so there is nothing to stop gracefully), services that did reach Active are stopped per the normal sequence, and the boot is abandoned.

## How peinit handles signals

peinit handles **all** signals through a `signalfd` read from its event loop — every signal is blocked and read as data, so there are no async signal handlers and no async-safety hazards. PID 1 cannot be killed by any signal; the kernel protects it.

| Signal | Behaviour |
|---|---|
| SIGCHLD | Reap children; match exits to services and jobs. Also reaps orphaned processes the system reparented to PID 1. |
| SIGINT | Reboot request. Repeated within the window → forced reboot. |
| SIGTERM | Poweroff request. |
| SIGPWR | Poweroff request — compatibility path for power-button/power-failure policy. |
| SIGHUP | Ignored — PID 1 has no controlling terminal. |
| SIGPIPE | Ignored — a broken control-socket pipe must never crash PID 1. |

All other signals are ignored.

> [!NOTE]
> Reaping orphans matters: when any process anywhere on the system dies leaving children, those children are reparented to PID 1. peinit reaps them so they do not accumulate as zombies — even though they were never its services. This is part of the job of being PID 1, distinct from supervising services.

## Where to start

To understand the reverse ordering and which relationships gate it, read [Dependencies and ordering](/peios/using-peios/services-and-jobs/dependencies.md).

To understand the Critical-failure path that bypasses graceful shutdown, read [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md).

For the boot side of the lifecycle and the reboot/recovery escalation, read [Boot and boot modes](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md).

---

# Troubleshooting peinit

_Peios / Using Peios / Services & jobs_

> A symptom-first guide — a service that won't start or keeps restarting, a change that didn't land, an access denial, and a machine stuck rebooting.

Almost every peinit problem is diagnosed the same way: **read the state and the cause.** A `status` query gives you both, and the [cause taxonomy](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) tells you what the cause means. This page works backward from common symptoms to the cause and the fix.

```
$ peiosctl status <service>      # state + cause + health + warnings
$ peiosctl list                  # everything you can see, at a glance
```

For history beyond what peinit holds in memory — past jobs, prior failures, the exact log line a service died on — query [eventd](/peios/security-fundamentals/auditing/overview.md); peinit emits every [job and operation](/peios/using-peios/services-and-jobs/jobs-and-operations.md) transition there.

## A service won't start

`status` shows it `Failed` or `Skipped`. The **cause** says why:

| Cause | What happened | What to do |
|---|---|---|
| `ValidationError` | The definition is malformed — a bad field, an unresolvable conflict, an illegal health-check timing. | Check the logs for the specific field. Fix the definition; the message names what is wrong. |
| `DependencyFailure` | A `Requires`/`BindsTo` target failed or does not exist. | Fix the *target* first — `status` it. The dependent recovers once the target can start. |
| `ConditionSkipped` (→ Skipped) | A start-time [condition](/peios/using-peios/services-and-jobs/execution-environment.md) was not met. **Not a failure** — the service decided it does not apply here. | If it *should* run, the condition's premise is false (a missing path/file/key). This is often correct behaviour. |
| `AssertionError` | A start-time [assert](/peios/using-peios/services-and-jobs/execution-environment.md) failed — a required precondition is missing. | Create the missing precondition (path, file, directory, registry key), then start again. |
| `PreHookFailure` | An `ExecStartPre` hook exited non-zero. | Run the hook command by hand as the [hook identity](/peios/using-peios/services-and-jobs/identity-and-privileges.md) to see why. |
| `ParentSetupFailure` | peinit could not even fork — fd/PID exhaustion, a cgroup error. No process was created. | A system-resource problem, not the service's fault. Check for fd/PID limits and cgroup health. |
| `PreExecFailure` | Setup after fork failed — token install, rlimits, environment. | Usually identity: check `Identity`, that authd is up, and that `RequiredPrivileges` names real privileges. |
| `ReadinessTimeout` | The process started but never became [ready](/peios/using-peios/services-and-jobs/service-types.md) within `StartTimeout`. | Either the service is genuinely slow (raise `StartTimeout`, or have it send `EXTEND_TIMEOUT_USEC`) or it never sends `READY=1` (wrong `Readiness`, or a bug). |

> [!TIP]
> A startup failure (`ReadinessTimeout`, `PreHookFailure`, `PreExecFailure`, `ParentSetupFailure`) is **restart-eligible** — peinit will have retried it through [Backoff](/peios/using-peios/services-and-jobs/supervision.md) before landing in Failed. So by the time you see `Failed`, it has already failed repeatedly. The logs show each attempt.

## A service keeps restarting

The service flaps — up, down, up, down. This is [restart policy](/peios/using-peios/services-and-jobs/supervision.md) doing its job, but it points at an unstable service.

- **It eventually settles in `Failed` with `RestartBudgetExhausted`.** It crashed `RestartMaxRetries` times faster than it could stay healthy for `RestartWindow`. The fix is the *service*, not the policy — read its logs for the crash. Raising the budget only delays the inevitable.
- **It flaps forever and never exhausts the budget.** It is briefly reaching Active each cycle and resetting the counter. Either genuinely stabilise it, or — if a [health check](/peios/using-peios/services-and-jobs/supervision.md) is failing it after it starts — check the health command; a flaky check restarts a perfectly good service.
- **It restarts on a clean exit you did not expect.** `RestartPolicy=Always` restarts even successful exits (cause `CleanExitRestart`). If the service is *meant* to exit, use `OnFailure` instead.

## The machine keeps rebooting

A reboot loop almost always means a **Critical service is failing**. A [`ErrorControl=Critical`](/peios/using-peios/services-and-jobs/supervision.md) service that exhausts its budget makes peinit sync and reboot; if it fails the same way every boot, you loop.

The [boot-attempt counter](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) is designed to break this: after N attempts (default 3) peinit stops and drops you into [Recovery mode](#booted-into-recovery-mode). From the Recovery shell, find the failing Critical service (its logs are in [eventd](/peios/security-fundamentals/auditing/overview.md), which persists across reboots), fix or disable it, and reboot. If you need to intervene before the counter trips, boot with `peios.recovery=1` on the kernel command line.

## A service is Abandoned

`Abandoned` means peinit SIGKILLed the process but it survived — stuck in uninterruptible kernel sleep (**D-state**), the signature of hung I/O (a dead NFS mount, a failing disk). Nothing in userspace can kill a D-state process.

- The underlying **I/O fault is the real problem** — investigate the storage or mount, not peinit.
- The service's cgroup is **leaked** and shows in `warnings`. A later start uses a fresh [generational cgroup](/peios/using-peios/services-and-jobs/execution-environment.md), so the new instance is unaffected.
- `peiosctl reset <service>` clears the Abandoned state and re-checks the cgroup: if the stuck process finally died, peinit cleans up; if not, it stays leaked and warns you. Leaked cgroups clear fully only on reboot.

## I changed the config and nothing happened

peinit works from an [in-memory snapshot](/peios/using-peios/services-and-jobs/defining-a-service.md), and each field has a **mutability class** that decides when a change applies:

- **Immutable at runtime** (`ImagePath`, `Type`, `Identity`, `RequiredPrivileges`, `ErrorControl`) → takes effect only on `restart`.
- **Apply on next start** (dependencies, conditions, `OnFailure`) → next start or graph reload.
- **Reloadable at runtime** (timeouts, restart policy, health checks, environment, hooks…) → next time peinit acts on the service.
- **Hot-reloaded** (`ServiceSecurity`) → next control request.

If a change is not landing, check its class first. For a wholesale re-read of every definition, run `peiosctl reload-config` — it rebuilds and validates the whole graph atomically and swaps it in only if validation passes (running services are untouched). And remember peinit *pulls* changes from [registry notifications](/peios/using-peios/registry-concepts/watches.md) rather than having them pushed, so there can be a brief lag.

## ACCESS_DENIED

A control command returns `ACCESS_DENIED`. peinit ran [AccessCheck](/peios/security-fundamentals/access-decisions/overview.md) on your token against the target's descriptor and the right was not granted:

- A *service* command needs the matching right in the service's [ServiceSecurity descriptor](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md) (`start` needs `SERVICE_START`, `restart` needs both stop and start, …).
- `shutdown` and `reload-config` need `SYSTEM_SHUTDOWN` / `SYSTEM_RELOAD_CONFIG` in peinit's control descriptor.
- The check uses your **effective** identity at connect time — if you are [impersonating](/peios/security-fundamentals/impersonation/overview.md), that is what is checked.

Every denial is logged with the caller SID, target, and requested right. Walk it through [Debugging a denial](/peios/security-fundamentals/access-decisions/debugging-a-denial.md).

> [!NOTE]
> If `list` shows fewer services than you expect, that is not a bug — `list` **omits** services you lack `SERVICE_QUERY_STATUS` on rather than denying them. You are seeing exactly what your token can see.

## A dependent never started

You started (or booted) a service, but something that depends on it never came up:

- A **`Requires`/`BindsTo` dependent stays blocked** until its target reaches a [satisfying state](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) (Active, Completed, or Skipped). If the target is stuck in Starting or Failed, so is the dependent — fix the target.
- A dependent that connected to its target on boot and got *connection refused* usually means the target uses **`Readiness=Alive`** — peinit only waited for the process to exist, not to be serving. Switch the target to `Readiness=Notify` so it signals `READY=1` when actually ready. (peinit logs this as a validation warning at boot.)
- A **`Wants` dependent starts regardless** of its target — if you needed it to wait, `Wants` was the wrong relationship; use `Requires`.

## Booted into Safe mode

[Safe mode](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) starts only Critical and `SafeMode=1` services. You land here when graph validation finds a **cycle or unresolvable conflict involving a Critical service**, or when `peios.safemode=1` is on the kernel command line. The TCB is healthy; the *configuration* is broken.

Fix the graph: the logs name the cycle path (`A → B → C → A`) or the conflicting pair. Once the cycle is broken or the conflict resolved, a normal boot returns. You can start any service by hand from Safe mode in the meantime — it is only auto-start that is restricted.

## Booted into Recovery mode

[Recovery mode](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) gives you a SYSTEM shell on the console and starts nothing else. You reach it when the boot counter hits N, when `peios.recovery=1` is set, or when **registryd failed in Phase 1** (entered immediately, no counter).

If the registry itself is the problem, use the offline tools that bypass it — they talk to the [loregd implementation](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) directly:

```
$ loregd --inspector               # read the storage directly to diagnose
$ loregd --recover-from-backup     # restore the backup taken each registryd start
$ loregd --dangerously-clear-database   # last resort — wipe; roles re-supply config
```

Recovery has **no TCB protections** — it is a SYSTEM shell, full stop. Treat it accordingly, and remember it needs console access (physical, IPMI, or serial); there is no remote recovery yet.

## Where to start

To read states and causes fluently, keep [The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) handy.

For restart, backoff, and Critical-reboot behaviour, see [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md).

For the commands and their outputs, see [Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md).

---

# Registry key reference

_Peios / Using Peios / Services & jobs_

> The complete catalog — every service-definition field with its registry type and default, and every key peinit reads or writes.

This page is the reference catalog for everything peinit reads from or writes to the [registry](/peios/using-peios/registry-concepts/overview.md): the full service-definition schema with types and defaults, and every key peinit touches outside the service definitions. For the *meaning* of each item, follow the link in its row; this page is for looking up a type, a default, or a path.

> [!NOTE]
> The registry stores a value's type but not its meaning — that is what [`regman`](/peios/using-peios/registry-administration/regman.md) and these docs are for. A value peinit accepts at write time can still be rejected when peinit reads and validates it; this page lists the schema, not the validation rules (those live on each field's page).

## Registry value types

Service definition fields map onto registry [value types](/peios/using-peios/registry-concepts/keys-values-and-types.md) as follows:

| Schema type | Registry type | Is |
|---|---|---|
| string | `REG_SZ` | A UTF-8 string. |
| multi_string | `REG_MULTI_SZ` | An ordered list of strings. |
| dword | `REG_DWORD` | A 32-bit unsigned integer. |
| binary | `REG_BINARY` | A raw byte sequence. |
| (timestamp) | `REG_QWORD` | A 64-bit value — used for timer last-run timestamps. |

## The service definition schema

Every service is a key under `Machine\System\Services\<name>`; these are the values inside it. Only `ImagePath` is required. Fields are grouped by purpose; for full semantics see [Defining a service](/peios/using-peios/services-and-jobs/defining-a-service.md) and the linked pages.

**Execution** — see [The execution environment](/peios/using-peios/services-and-jobs/execution-environment.md)

| Field | Type | Default | Meaning |
|---|---|---|---|
| `ImagePath` | string | *(required)* | Absolute path to the service binary. |
| `Arguments` | multi_string | — | Command-line arguments. |
| `WorkingDirectory` | string | `/` | Working directory (non-empty absolute path). |
| `TTYPath` | string | — | Absolute path to a terminal to attach as the service's stdio and controlling terminal, e.g. `/dev/console` or `/dev/tty1`. Absent or empty means the daemon default (`/dev/null` stdin, captured output). Attaching a terminal suppresses log capture. |
| `Environment` | multi_string | — | `KEY=VALUE` pairs added to the environment. |
| `RuntimeDirectories` | multi_string | — | Directories created directly under `/run` just before the main process starts, each secured for SYSTEM, Administrators, and the service's own SID. |
| `LimitNOFILE` | dword | — | `RLIMIT_NOFILE`. |
| `LimitCORE` | dword | — | `RLIMIT_CORE`, in bytes. |

> [!NOTE]
> Each `RuntimeDirectories` entry is a plain relative name — just `foo`, never a path — and must not contain `/`, `\`, `.`, `..`, NUL, or control characters. For an entry `foo`, peinit creates `/run/foo` immediately before launching the main process and applies a Peios file security descriptor granting full access to SYSTEM, Administrators, and this service's own SID. If the directory can't be created or the descriptor can't be applied, the start fails with `ParentSetupFailure`. peinit does *not* remove these directories when the service stops — `/run` is a boot-scoped tmpfs that the next boot clears for you.

**Type and readiness** — see [Simple and Oneshot services](/peios/using-peios/services-and-jobs/service-types.md)

| Field | Type | Default | Meaning |
|---|---|---|---|
| `Type` | dword | 0 (Simple) | 0 = Simple, 1 = Oneshot. |
| `Readiness` | dword | 0 (Notify) | 0 = Notify (`READY=1`), 1 = Alive. Ignored for Oneshot. |
| `RemainAfterExit` | dword | 0 | Oneshot only — stay Completed after a successful exit. |
| `SuccessExitCodes` | multi_string | — | Non-zero exit codes treated as success (each 0–255). |

**Activation** — see [Triggers and timers](/peios/using-peios/services-and-jobs/triggers-and-timers.md)

| Field | Type | Default | Meaning |
|---|---|---|---|
| `Triggers` | multi_string | — | `boot` and/or `timer:<schedule>`. Absent = demand-only. |
| `Disabled` | dword | 0 | If 1, triggers must not activate the service. |
| `SafeMode` | dword | 0 | If 1, attempt to start in Safe mode. Critical implies SafeMode. |
| `Conditions` | multi_string | — | Start-time checks; a failure *skips* the service. |
| `Asserts` | multi_string | — | Start-time checks; a failure *fails* the service. |
| `PreStartCheckTimeout` | dword | 5 | Seconds allowed for the helper that evaluates filesystem `Conditions`/`Asserts`; if it overruns, it is killed and the check counts as *not satisfied* (a Condition skips, an Assert fails). |
| `TimerPersistent` | dword | 1 | Catch up a missed timer run after a reboot. |
| `TimerJitter` | dword | 0 | Maximum random delay (seconds) added to each firing. |

**Identity** — see [Service identity and privileges](/peios/using-peios/services-and-jobs/identity-and-privileges.md)

| Field | Type | Default | Meaning |
|---|---|---|---|
| `Identity` | string | `LocalService` | Principal name or SID for the service token. |
| `RequiredPrivileges` | multi_string | — | Privilege allow-list; all others are removed from the token. |
| `HookIdentity` | string | (service's `Identity`) | Identity for `ExecStartPre`/`ExecStartPost`. |

**Dependencies** — see [Dependencies and ordering](/peios/using-peios/services-and-jobs/dependencies.md)

| Field | Type | Default | Meaning |
|---|---|---|---|
| `Requires` | multi_string | — | Hard dependencies. |
| `Wants` | multi_string | — | Soft dependencies. |
| `BindsTo` | multi_string | — | Runtime coupling. |
| `Conflicts` | multi_string | — | Mutual exclusion. |
| `OnFailure` | string | — | Service to start when this one enters Failed. |

**Supervision and health** — see [Keeping services running](/peios/using-peios/services-and-jobs/supervision.md)

| Field | Type | Default | Meaning |
|---|---|---|---|
| `ErrorControl` | dword | 0 (Normal) | 0 = Normal, 1 = Critical (sync + reboot on irrecoverable failure). |
| `RestartPolicy` | dword | 1 (OnFailure) | 0 = Never, 1 = OnFailure, 2 = Always. |
| `RestartMaxRetries` | dword | 5 | Consecutive restarts before Failed. |
| `RestartWindow` | dword | 120 | Seconds of Active health that resets the restart counter. |
| `RestartDelay` | dword | 1 | Backoff base (doubles per failure, capped at 60). |
| `HealthCheck` | string | — | Periodic health-check command. |
| `HealthCheckInterval` | dword | 30 | Seconds between health checks. |
| `HealthCheckTimeout` | dword | 5 | Seconds before a check is killed and failed. |
| `HealthCheckRetries` | dword | 3 | Consecutive failures before unhealthy. |
| `WatchdogTimeout` | dword | 0 | Seconds between expected `WATCHDOG=1` pings; 0 disables. |

**Transition phases** — see [The service lifecycle](/peios/using-peios/services-and-jobs/the-service-lifecycle.md) and [Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md)

| Field | Type | Default | Meaning |
|---|---|---|---|
| `ExecStartPre` | multi_string | — | Commands run before the binary (sequential; any failure aborts start). |
| `ExecStartPost` | multi_string | — | Commands run after readiness / successful exit (failure logged, not fatal). |
| `ExecReload` | string | — (SIGHUP) | Reload command, or `signal:<NAME>`. |
| `StartTimeout` | dword | 30 | Seconds for the whole start sequence. |
| `StopTimeout` | dword | 10 | Seconds between SIGTERM and SIGKILL. |

**Notify, fds, security, metadata**

| Field | Type | Default | Meaning |
|---|---|---|---|
| `NotifyAccess` | dword | 0 (Main) | Who may send sd_notify (only Main is supported). |
| `FdStoreMax` | dword | 0 | Max fds in the [fd store](/peios/using-peios/services-and-jobs/execution-environment.md); 0 disables it. |
| `ServiceSecurity` | binary | inherit parent | [Descriptor](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md) controlling who may manage the service. |
| `DisplayName` | string | — | Human-readable name for status display. |
| `Description` | string | — | Description of the service. |

## Service definition keys

| Key | Type | Purpose | See |
|---|---|---|---|
| `Machine\System\Services\` | (parent key) | Parent of all service definitions; each child key is a service. | [Defining a service](/peios/using-peios/services-and-jobs/defining-a-service.md) |
| `Machine\System\Services\SchemaVersion` | dword | Schema version guard (currently 1). | [Defining a service](/peios/using-peios/services-and-jobs/defining-a-service.md) |
| `Machine\System\Services\<name>\LastTimerRun` | `REG_QWORD` | Last-run timestamp for a single-trigger persistent timer. Written by peinit. | [Triggers and timers](/peios/using-peios/services-and-jobs/triggers-and-timers.md) |
| `Machine\System\Services\<name>\TimerState\` | (subkey) | Per-trigger last-run timestamps for multi-trigger services. Each value is named by the percent-encoded schedule string and holds a `REG_QWORD`. | [Triggers and timers](/peios/using-peios/services-and-jobs/triggers-and-timers.md) |

## Boot configuration

Under `Machine\System\Boot\`:

| Key | Type | Default | Purpose | See |
|---|---|---|---|---|
| `MaxParallelStarts` | dword (> 0) | 10 | Maximum services starting concurrently. Missing → default; zero/invalid → Recovery. | [Boot and boot modes](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) |
| `BootSuccessGrace` | dword | 30 | Seconds a Critical service must hold a satisfying state before boot is successful. | [Boot and boot modes](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) |
| `ShutdownTimeout` | dword | 90 | Maximum seconds for the whole graceful shutdown. | [Shutdown](/peios/using-peios/services-and-jobs/shutdown.md) |
| `SettleTimeout` | dword | 5 | Seconds to wait for the boot set to settle before starting [`boot:settled`](/peios/using-peios/services-and-jobs/triggers-and-timers.md) services anyway. | [Triggers and timers](/peios/using-peios/services-and-jobs/triggers-and-timers.md) |
| `PostKillTimeout` | dword | 5 | Seconds peinit waits for a service cgroup to drain after SIGKILL before treating it as stuck. `ShutdownTimeout` bounds the shutdown as a whole; this bounds one service's last stage of it. | [Shutdown](/peios/using-peios/services-and-jobs/shutdown.md) |

## Operational parameters

Under `Machine\System\Init\`:

| Key | Type | Default | Purpose | See |
|---|---|---|---|---|
| `ControlSecurity` | binary | SYSTEM full; Administrators shutdown + reload-config | Descriptor for system-level control operations. | [Who can manage a service](/peios/using-peios/services-and-jobs/who-can-manage-a-service.md) |
| `MaxControlConnections` | dword | 32 | Maximum concurrent control-socket connections. | [Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md) |
| `MaxRequestSize` | dword | 65536 | Maximum control-socket request size (bytes). | [Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md) |
| `ConnectionTimeout` | dword | 30 | Seconds before an idle control connection is closed. | [Controlling services](/peios/using-peios/services-and-jobs/controlling-services.md) |
| `MaxLogLineLength` | dword | 8192 | Maximum bytes per service output line before truncation. | [Service output and logging](/peios/using-peios/services-and-jobs/output-and-logging.md) |
| `MaxLogBufferPerService` | dword | 65536 | Maximum bytes buffered per service pipe before back-pressure. | [Service output and logging](/peios/using-peios/services-and-jobs/output-and-logging.md) |
| `LogReadBytesPerEvent` | dword | 16384 | Bytes drained from a service's output pipe per readable event. Larger favours throughput on chatty services, smaller favours fairness between them. | [Service output and logging](/peios/using-peios/services-and-jobs/output-and-logging.md) |
| `PreEventdBuffer` | dword | 1048576 | Total bytes of service output held in memory before eventd is up to receive it. Overflow drops the oldest output; it never blocks a service. | [Service output and logging](/peios/using-peios/services-and-jobs/output-and-logging.md) |
| `EnvVars\` | (parent key) | empty | Default environment variables injected into Phase 2 services (value name = variable, `REG_SZ` data = value). The descriptor here is security-critical. | [The execution environment](/peios/using-peios/services-and-jobs/execution-environment.md) |
| `ProvisionedPaths\` | (parent key) | empty | Boot-time path provisioning: each child key names one directory or file that peinit creates and secures after registryd starts and before Phase 2 — the registry-backed equivalent of tmpfiles.d. | [Boot and boot modes](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) |
| `ProvisionedPaths\<name>\Kind` | string | *(required)* | `directory` or `file` — what to create at `Path`. | [Boot and boot modes](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) |
| `ProvisionedPaths\<name>\Path` | string | *(required)* | Absolute path to create or verify. | [Boot and boot modes](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) |
| `ProvisionedPaths\<name>\Security` | binary | SYSTEM + Administrators full, users read/traverse | Peios file security descriptor to apply to the object. | [Boot and boot modes](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) |
| `ProvisionedPaths\<name>\Required` | dword | 0 | If 1, a failure to provision this entry sends boot to [Recovery mode](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) before Phase 2 starts. | [Boot and boot modes](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) |

> [!NOTE]
> A few `ProvisionedPaths` rules worth knowing before you rely on them: `Kind=file` never truncates an existing file (it only makes sure the file is present), and peinit does *not* create missing parent directories for you — define each directory you need as its own entry, or depend on the package that owns the parent. A malformed entry is logged and skipped rather than blocking boot; only an entry you mark `Required=1` can send boot to [Recovery mode](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md), and only when it genuinely can't be created or secured.

## Keys peinit reads from other subsystems

| Key | Type | Purpose | See |
|---|---|---|---|
| `Machine\System\eventd\LogSocketPath` | string | Path of eventd's log datagram socket, where peinit forwards service output. | [Service output and logging](/peios/using-peios/services-and-jobs/output-and-logging.md) |

## Where to start

For how these fields combine into a working definition and when changes take effect, read [Defining a service](/peios/using-peios/services-and-jobs/defining-a-service.md).

To look up the meaning, valid range, and effect-timing of any key on a live system, use [`regman`](/peios/using-peios/registry-administration/regman.md).
