zyginit — Architecture and Design
Status: Implementation complete — testing on Hammerhead pending
Date: 2026-03-05
RFE Filed: reef-lang/bugs/RFE-001-init-system-requirements.md
Reef 0.4.0: Released 2026-03-05 — all 8 RFE-001 items implemented
Problem Statement
SMF (Service Management Facility) is architecturally overcomplex for Hammerhead's needs:
- XML service definitions — verbose, hard to read/write, depends on libxml2
- libxml2 dependency — maintenance status uncertain, large dependency for service config
- Architectural complexity — 3 cooperating daemons (svc.startd, svc.configd, master restarter)
plus SQLite repository, door-based IPC, and XML-based service manifests - Difficult to modify — tightly coupled components make changes risky
Proposed Replacement: zyginit
A single-process service manager and supervisor written in Reef, with:
- TOML-based service definitions — clean, readable, no XML
- Symlink-based enable/disable — no database, inspectable with
ls - Dependency graph resolution with parallel startup
- Service supervision with automatic restart
- Hammerhead process contract integration — kernel-level tracking of all service descendants
- Administrative CLI (
zygctl) for service control via Unix domain socket
Naming
| Component | Name | Description |
|---|---|---|
| Daemon | zyginit |
PID 1, the init process |
| CLI | zygctl |
Admin tool (start/stop/status/enable/disable) |
| Config dir | /etc/zyginit/ |
Service TOML definitions |
| Enable dir | /etc/zyginit/enabled.d/ |
Symlinks to enabled services |
| Socket | /var/run/zyginit.sock |
Unix domain socket for zygctl |
| Diagnostics | /var/run/zyginit/ |
Optional runtime state (tmpfs) |
Why Reef
- Compiles to native code via C/GCC — suitable for PID 1
- FFI to C is clean — can call libcontract and all POSIX APIs directly
- Result/Option error handling is correct for systems code
- Writing the init in Reef differentiates Zygaena/Hammerhead
- Reef already powers the Coral package manager — proven for systems programming
Architecture
+-----------+
| zygctl | (Reef CLI tool)
+-----+-----+
| Unix socket (/var/run/zyginit.sock)
+-----v-----+
| zyginit | (PID 1, Reef)
| |
| +-------+ |
| | event | | poll() on: contract bundle fd,
| | loop | | unix socket fd, signal pipe fd
| +---+---+ |
| | |
| +---v---+ |
| | dep | | topological sort of service graph
| | graph | | built from TOML at boot
| +---+---+ |
| | |
| +---v--------+ |
| | contract | | libcontract FFI:
| | manager | | template → fork → adopt → events
| +------------+ |
+-------+-------+
|
+-------+-------+-------+-------+
| | | | |
sshd cron fmd network ...
(contract) (contract) (contract) (contract)
Service Definition Format (TOML)
Service definitions are TOML files in /etc/zyginit/. Most services are purely
declarative. For complex startup logic (database initialization, network setup),
the exec.start field can point to a shell script in the service's subdirectory.
Simple service (declarative)
# /etc/zyginit/sshd.toml
[service]
name = "sshd"
description = "Secure Shell Daemon"
type = "daemon" # daemon | oneshot | transient
[exec]
start = "/usr/lib/ssh/sshd -D"
[stop]
method = "contract" # contract (default) | exec
signal = "TERM"
timeout = 60
[dependencies]
requires = ["network", "filesystem"]
after = ["name-services"]
conflicts = ["other-mta"] # mutually exclusive; never run together
[contract]
param = ["inherit", "noorphan"]
fatal = ["empty", "hwerr"]
[restart]
on = "failure" # always | failure | never
delay = 5
max_retries = 3
Complex service (script escape hatch)
For services requiring complex startup logic, the TOML definition points to
scripts in a subdirectory matching the service name:
# /etc/zyginit/postgresql.toml
[service]
name = "postgresql"
description = "PostgreSQL Database Server"
type = "daemon"
[exec]
start = "/etc/zyginit/postgresql/start.sh"
stop = "/etc/zyginit/postgresql/stop.sh"
[dependencies]
requires = ["filesystem", "network"]
/etc/zyginit/
├── postgresql.toml
└── postgresql/
├── start.sh # handles initdb, pg_upgrade, etc.
└── stop.sh # clean shutdown with pg_ctl
TOML schema fields (planned — needs further discussion)
Additional fields under consideration:
exec.user,exec.group— privilege separation (run as non-root)exec.working_directory— chdir before execexec.environment— env vars:["KEY=value", ...]security.zone,security.chroot— containmentlimits.max_fds,limits.max_memory— resource controlslogging.stdout,logging.stderr— output destination (file, syslog)service.milestone— grouping:single-user,multi-user,network
State Management
Design Rationale
Research of other init systems informed the design:
| System | Enable/Disable | State DB | Boot Order |
|---|---|---|---|
| systemd | Symlinks in .wants/ dirs |
None | In-memory from unit files |
| dinit | Symlinks in waits-for.d/ |
None | In-memory from service files |
| OpenRC | Symlinks in runlevel dirs | None | In-memory (cached to /run) |
| SMF | SQLite property | SQLite | In-memory from repository |
Every modern init except SMF uses symlinks for enable/disable state and builds
the dependency graph in memory at boot. SMF's SQLite repository is the #1 source
of boot failures ("repository corruption"). zyginit follows the simpler pattern.
Enabled/Disabled State (symlinks)
Enabled state is managed via symlinks in /etc/zyginit/enabled.d/:
/etc/zyginit/
├── sshd.toml
├── cron.toml
├── network.toml
├── filesystem.toml
├── postgresql.toml
├── postgresql/
│ ├── start.sh
│ └── stop.sh
└── enabled.d/ # symlinks = what starts at boot
├── sshd → ../sshd.toml
├── cron → ../cron.toml
├── network → ../network.toml
└── filesystem → ../filesystem.toml
| Operation | What happens |
|---|---|
zygctl enable sshd |
Creates symlink enabled.d/sshd → ../sshd.toml |
zygctl disable sshd |
Removes symlink enabled.d/sshd |
zygctl start sshd |
Starts service now (transient — does not persist across reboot) |
zygctl stop sshd |
Stops service now |
zygctl status |
Reads in-memory state from zyginit over Unix socket |
Benefits:
- Inspectable with
ls /etc/zyginit/enabled.d/ - Editable with
ln -sandrm— no special tools required - Always on root filesystem — available at PID 1 startup
- No corruption risk — no database to corrupt
Boot Order (derived from TOML)
No pre-computed boot order is stored. At boot, zyginit:
- Scans
enabled.d/symlinks → list of enabled services - Reads each service's TOML → extracts
[dependencies] - Topological sort → parallel startup order
- Starts services respecting dependency constraints
For <100 services, TOML parsing and topo sort complete in milliseconds.
Runtime State (in-memory + optional tmpfs)
Service runtime state (PIDs, running/stopped/failed, restart counters, exit codes)
lives in memory inside the zyginit process. Accessible via zygctl status
over the Unix domain socket.
Optionally, zyginit writes diagnostic state to /var/run/zyginit/ (tmpfs) for
crash recovery analysis. This is not required for operation — if zyginit restarts,
it re-scans TOML definitions and re-adopts existing process contracts.
Filesystem Summary
| Path | Filesystem | Available | Purpose |
|---|---|---|---|
/etc/zyginit/*.toml |
root | Always | Service definitions |
/etc/zyginit/enabled.d/ |
root | Always | Enabled symlinks |
/var/run/zyginit.sock |
tmpfs | After tmpfs mount | CLI communication |
/var/run/zyginit/ |
tmpfs | After tmpfs mount | Diagnostic state (optional) |
Hammerhead Process Contracts
Process contracts are a Hammerhead kernel mechanism (not available on Linux) accessed
via the contract filesystem (/system/contract/):
- Before fork(), open
/system/contract/process/templateand configure - After fork(), all child processes (and their children) belong to a contract
- Contract owner receives events: empty (all exited), core dump, hardware error, fatal signal
- Can signal ALL processes in a contract atomically — no orphaned grandchildren
- On init restart, existing contracts are re-adopted — services keep running
The C API (libcontract) is fd-based: open/ioctl/close on ctfs paths. No complex
struct layouts — integer fds, integer flags, and string FMRIs. Clean for Reef FFI.
Boot Sequence
- Kernel execs
/sbin/init→ zyginit (PID 1) - Scan
/etc/zyginit/enabled.d/→ list of enabled services - Read each service's TOML → extract
[dependencies] - Topological sort → boot order
- Start services in parallel, respecting dependency order
- Mount tmpfs, create
/var/run/zyginit.sock - Enter poll(2) event loop: contract events, Unix socket, signal pipe
Reef Language Requirements
Filed as RFE-001 with the Reef team (~/repos/reef-lang/bugs/RFE-001-init-system-requirements.md).
Response received (reef-lang/bugs/RFE-001-response.md): All 8 items accepted
for Reef 0.4.0. Estimated ~4 weeks. Key findings from Reef team:
- GC is not a blocker — no auto-triggered STW pauses; signal change is 2 lines
- fork/exec already exist in C runtime — just need stdlib wiring
- TOML parser already exists —
encoding/toml.reef(~560 lines) - Cooperative yield points already exist (
--quantum-checksflag)
Revised Priority (per Reef team analysis)
| # | Enhancement | Effort | Status |
|---|---|---|---|
| 1 | Process management (fork/exec/setsid) | Low | Reef 0.4.0 |
| 2 | File descriptor operations | Low | Reef 0.4.0 |
| 3 | String array marshaling | Low-Med | Reef 0.4.0 |
| 4 | Signal enhancements | Low-Med | Reef 0.4.0 |
| 5 | Unix domain sockets | Low | Reef 0.4.0 |
| 6 | Event loop / poll | Low | Reef 0.4.0 |
| 7 | TOML parser enhancements | Minimal | Reef 0.4.0 |
| 8 | GC mode (configurable signals + --no-gc) | Medium | Reef 0.4.0 |
Open Questions (from Reef team — resolved)
- poll(2) vs event ports: poll(2) in stdlib, event ports via
extern "C".
Poll is sufficient for 3-4 fds. - fork() + Active Objects: Single-threaded poll loop — no AO conflict.
- GC signal preference: SIGRTMIN+0/SIGRTMIN+1 — no collision with standard signals.
Reef Team Recommendations
- Use
extern "C"for Hammerhead-specific code (libcontract, ctfs) — keep in Hammerhead, not Reef stdlib - Start prototyping now with
process_spawn_shell()+ FFI declarations - Test with current GC — likely zero pauses for bounded working set
- Use existing TOML parser (
import encoding.toml) immediately
Phased Approach
Phase A: Interim (Now)
- Keep SMF operational — it works, it's built, it boots
- libxml2 now builds from source in hammerhead (commit 3b7533657)
- Begin zyginit prototype using existing Reef 0.4.0 capabilities
- Test TOML parser with service definition format
Phase B: Prototype
- Build zyginit prototype with basic service supervision
- Test with a handful of services on standalone VM
- Keep SMF as fallback
Phase C: Migration
- Convert all SMF service manifests to TOML definitions
- Boot with zyginit as PID 1
- Validate with hh-audit
Phase D: SMF Removal
- Remove svc.startd, svc.configd, master restarter
- Remove libscf, libxml2 dependency (if no other consumers)
- Remove SMF manifest infrastructure from build
FMA Integration
FMA (Fault Management Architecture) is independent of SMF and will be retained:
- Kernel drivers post ereport events via sysevent
- fmd daemon runs diagnosis engines (CPU retire, disk fault, ZFS degraded)
- fmd is a service managed by the init system — works with any init
- No architectural coupling to SMF
References
- Hammerhead
libcontract(3LIB),contract(5),process(5) cmd/svc/startd/contract.c,method.c,restarter.c- dinit comparison: https://davmac.org/projects/dinit/
- Reef RFE:
~/repos/reef-lang/bugs/RFE-001-init-system-requirements.md - Reef 0.4.0 changelog:
~/repos/reef-lang/docs/changelog-0.4.0.md