zyginit Implementation Plan
Status: Complete — All 7 phases implemented, plus post-phase enhancements
Date: 2026-03-06
Author: Chris Tusa / Claude
Project Layout
Following Coral's conventions (source_dirs = ["src"], entry = "src/main.reef"):
zyginit/
├── reef.toml # Reef manifest
├── SRCHEADER.txt # Leafscale source header template
├── CLAUDE.md # Project context
├── .hgignore # Mercurial ignore
├── src/
│ ├── main.reef # Entry point — event loop (Phase 5)
│ ├── config.reef # TOML parser + ServiceDef types (Phase 1)
│ ├── depgraph.reef # Dependency graph + topo sort (Phase 2)
│ ├── contract.reef # Hammerhead process contract FFI (Phase 3)
│ ├── supervisor.reef # Service lifecycle management (Phase 4)
│ └── socket.reef # Unix domain socket server (Phase 6)
├── docs/
│ ├── DESIGN.md # Architecture and design
│ └── IMPLEMENTATION_PLAN.md # This file
├── services/
│ └── examples/ # Example TOML service definitions
├── tests/
│ └── (test TOML files, test programs)
├── stub/ # Original Rust-like stubs (design reference only)
└── tools/
└── zygctl/ # Separate CLI binary
├── reef.toml
└── src/
└── main.reef
Phase Dependency Graph
Phase 1: config.reef (TOML parsing, ServiceDef types)
│
▼
Phase 2: depgraph.reef (dependency graph, topo sort)
│
├──────────────────────┐
▼ ▼
Phase 3: contract.reef Phase 6: socket.reef
│ │
▼ │
Phase 4: supervisor.reef ◄─┘
│
▼
Phase 5: main.reef (event loop wiring)
│
▼
Phase 7: tools/zygctl (separate binary)
Phase 1: config.reef — TOML Config Parser
Purpose: Parse /etc/zyginit/*.toml service definitions into ServiceDef structs.
Testable on Linux: Yes — fully portable.
Imports
import encoding.toml as toml
import io.file as file
import core.str
Types
type ServiceDef = struct
name: string
description: string
service_type: int // 1=daemon, 2=oneshot, 3=transient
start_cmd: string
stop_cmd: string // empty if not specified
stop_method: int // 1=contract, 2=exec
stop_signal: string // "TERM", "HUP", etc.
stop_timeout: int // seconds, default 60
requires: [string] // hard dependencies
requires_count: int
after: [string] // ordering dependencies
after_count: int
contract_param: [string]
contract_param_count: int
contract_fatal: [string]
contract_fatal_count: int
restart_policy: int // 1=always, 2=failure, 3=never
restart_delay: int // seconds, default 5
max_retries: int // -1 = unlimited, default 3
end ServiceDef
Functions
new_service_def(): ServiceDef— create with defaultsparse_toml_array(value, result, max): int— parse["a", "b"]inline arraysparse_service_type(type_str): int— "daemon"→1, "oneshot"→2, "transient"→3parse_restart_policy(policy_str): int— "always"→1, "failure"→2, "never"→3parse_stop_method(method_str): int— "contract"→1, "exec"→2parse_service(filepath, svc): bool— parse one TOML file into ServiceDefscan_enabled(config_dir, paths, max): int— list enabled.d/ entriesload_enabled_services(config_dir, services, max): int— load all enabled services
Key Implementation Notes
- Reef's TOML parser flattens sections:
[service]+name = "sshd"→ key"service.name" - Inline arrays like
requires = ["a", "b"]need custom parsing (Coral has proven pattern) - No readlink needed: symlink name in enabled.d maps directly to
<name>.toml
Phase 2: depgraph.reef — Dependency Graph + Topological Sort
Purpose: Build DAG from service dependencies, compute parallel boot tiers.
Testable on Linux: Yes — fully portable.
Imports
import collections.hashmap as hashmap
import core.str
Types
type DepGraph = struct
names: [string]
count: int
name_map: hashmap.HashMap[int] // name → index
deps: [int] // flattened adjacency (MAX_SERVICES * MAX_DEPS)
dep_count: [int] // deps per node
in_degree: [int] // for topo sort
end DepGraph
Functions
new_depgraph(): DepGraphadd_service(graph, name): int— returns assigned indexadd_dependency(graph, name, dep_name): boolbuild_graph(graph, services, count): bool— populate from ServiceDef arraytopo_sort(graph, tiers, tier_counts, max_tiers): int— Kahn's algorithmhas_cycle(graph): bool
Algorithm: Kahn's Topological Sort
- Find all nodes with in_degree == 0 → tier 0
- Remove those nodes, decrement in_degree of dependents
- Nodes reaching in_degree 0 form next tier
- Repeat until all placed
- Remaining nodes with in_degree > 0 → cycle error
Output: flat tiers[string] array + tier_counts[int] array. Services within a
tier can start in parallel.
Phase 3: contract.reef — Hammerhead Process Contract FFI
Purpose: Reef wrappers around libcontract for kernel-level service process tracking.
Testable on Linux: No — Hammerhead only. Compile-test only on Linux.
FFI Declarations
extern "C" fn ct_tmpl_activate(fd: int): int
extern "C" fn ct_tmpl_clear(fd: int): int
extern "C" fn ct_tmpl_set_informative(fd: int, events: int): int
extern "C" fn ct_tmpl_set_critical(fd: int, events: int): int
extern "C" fn ct_pr_tmpl_set_fatal(fd: int, events: int): int
extern "C" fn ct_pr_tmpl_set_param(fd: int, params: int): int
extern "C" fn ct_status_read(fd: int, detail: int, statp: pointer): int
extern "C" fn ct_status_free(statp: pointer)
extern "C" fn ct_status_get_id(statp: pointer): int
extern "C" fn ct_ctl_abandon(fd: int): int
extern "C" fn ct_ctl_adopt(fd: int): int
extern "C" fn sigsend(idtype: int, id: int, sig: int): int
Types
type Contract = struct
id: int
service_name: string
bundle_fd: int
end Contract
Functions
setup_template(): int— open/configure/activate contract templateget_latest_contract(): int— read contract ID after forkclear_template(tmpl_fd): intopen_bundle(): int— open bundle fd for poll()kill_contract(contract_id, sig): int— signal all processes in contractabandon_contract(contract_id): intadopt_contract(contract_id): int— re-adopt after zyginit restart
Build Note
Requires -l contract linker flag on Hammerhead.
Risk: ct_status_read pointer semantics
ct_status_read takes pointer-to-opaque-pointer. Needs unsafe block and careful
FFI handling. Must prototype on Hammerhead VM.
Phase 4: supervisor.reef — Service Lifecycle Management
Purpose: Fork/exec services within contracts, manage state machine, restart policies.
Testable on Linux: Partial — fork/exec works, contracts do not.
Imports
import config
import contract
import sys.process as process
import sys.fd as fd
import sys.signal as signal
import collections.hashmap as hashmap
import core.str
Types
// State constants: DISABLED=0, WAITING=1, STARTING=2, RUNNING=3,
// STOPPING=4, STOPPED=5, FAILED=6, MAINTENANCE=7
type ServiceRuntime = struct
def: config.ServiceDef
state: int
contract_id: int // -1 if none
pid: int // -1 if none
restart_count: int
last_exit_code: int // -1 if not exited
last_start_time: int
stop_requested_time: int
end ServiceRuntime
type ServiceTable = struct
runtimes: [ServiceRuntime]
count: int
name_map: hashmap.HashMap[int] // name → index
contract_map: hashmap.HashMap[int] // contract_id_str → index
end ServiceTable
Functions
new_service_table(max): ServiceTableadd_service(table, def): intfind_service(table, name): intfind_by_contract(table, contract_id): intstart_service(table, idx): bool— fork, contract setup, execstop_service(table, idx): bool— signal or exec stop commandhandle_contract_event(table, contract_id, exit_code)— apply restart policycheck_stop_timeouts(table)— escalate to SIGKILL if timeout exceededcheck_restart_delays(table)— restart services whose delay has elapsedstate_name(state): string— human-readable stateget_status_line(table, idx): string
start_service() Flow
contract.setup_template()→ template fdprocess.process_fork()→ pid- Child:
process.process_setsid(), parse args,process.process_exec() - Parent:
contract.get_latest_contract()→ contract_id contract.clear_template(tmpl_fd)- Update runtime: state=RUNNING, pid, contract_id, timestamp
Restart Policy Logic (handle_contract_event)
RESTART_ALWAYS→ schedule restart after delayRESTART_FAILURE→ restart only if exit_code != 0RESTART_NEVER→ mark STOPPED- Check max_retries → MAINTENANCE if exceeded
Phase 5: main.reef — Event Loop
Purpose: Wire all modules together into the main poll(2) event loop.
Testable on Linux: Partial — poll/signals/sockets yes, contracts no.
Imports
import config
import depgraph
import contract
import supervisor
import socket
import sys.process as process
import sys.signal as signal
import sys.poll as poll
import sys.fd as fd
Main Procedure Flow
- Load config:
config.load_enabled_services() - Build dependency graph:
depgraph.build_graph()+depgraph.topo_sort() - Create service table:
supervisor.new_service_table() - Start services tier by tier
- Set up event sources:
- Contract bundle fd:
contract.open_bundle() - Signal self-pipe:
signal.selfpipe_create()for SIGCHLD, SIGTERM, SIGHUP - Unix socket:
socket.create_socket()
- Contract bundle fd:
- Main loop:
poll.poll_wait()with 1s timeout- Contract events →
supervisor.handle_contract_event() - Signals → reap children, shutdown, reload
- Socket →
socket.handle_client() - Periodic: check stop timeouts, restart delays
- Contract events →
Build Command
reefc build --gc-signals rtmin # Linux dev
reefc build --gc-signals rtmin -l contract # Hammerhead production
Phase 6: socket.reef — Unix Domain Socket Server
Purpose: Accept zygctl connections, dispatch commands, send responses.
Testable on Linux: Yes — fully portable.
Imports
import net.unix as unix
import supervisor
import config
import core.str
Protocol
- Request:
COMMAND [ARG]\n(newline-terminated text) - Response: Plain text lines, connection closed after response
Commands
| Command | Description |
|---|---|
status |
List all services with state |
status <name> |
Status of one service |
start <name> |
Start a service (transient) |
stop <name> |
Stop a service |
restart <name> |
Stop then start |
enable <name> |
Create symlink in enabled.d |
disable <name> |
Remove symlink from enabled.d |
list |
List all known service definitions |
Functions
create_socket(): int— bind and listen on/var/run/zyginit.sockhandle_client(server_fd, table)— accept, read, dispatch, respond, closeparse_command(input, arg_out): stringcmd_status(table, arg): stringcmd_start(table, name): stringcmd_stop(table, name): stringcmd_restart(table, name): stringcmd_enable(name): stringcmd_disable(name): stringcmd_list(table): stringdestroy_socket()— cleanup
Phase 7: tools/zygctl — CLI Tool
Purpose: Admin CLI that connects to zyginit over Unix domain socket.
Testable on Linux: Yes.
Layout
tools/zygctl/
├── reef.toml # entry = "src/main.reef", output = "zygctl"
└── src/
└── main.reef
Imports
import sys.args as args
import net.unix as unix
import core.str
Usage
zygctl status # list all services
zygctl status <name> # status of one service
zygctl start <name> # start a service (transient)
zygctl stop <name> # stop a service
zygctl restart <name> # restart a service
zygctl enable <name> # enable at boot (creates symlink)
zygctl disable <name> # disable at boot (removes symlink)
zygctl list # list all service definitions
Implementation
- Parse args
- For enable/disable: operate locally on symlinks (no daemon needed)
- All other commands: connect to
/var/run/zyginit.sock, send command, print response
Platform Testing Strategy
| Phase | Linux | Hammerhead (standalone VM) |
|---|---|---|
| 1 | Full test with example TOML | Same |
| 2 | Full test with mock graphs | Same |
| 3 | Compile only | Full test |
| 4 | fork/exec without contracts | Full test |
| 5 | Poll loop without contracts | Full test |
| 6 | Full test | Same |
| 7 | Full test | Same |
Risk Items
- ct_status_read pointer semantics — pointer-to-pointer FFI, prototype on Hammerhead
- TOML inline array parsing — stdlib doesn't parse natively, use Coral's proven pattern
- Contract event binary format — reading from bundle fd needs Hammerhead struct parsing
- String splitting — verify
str.split()availability in Reef 0.4.0 stdlib - HashMap with struct values — may need index-based indirection instead of
HashMap[ServiceDef]