66 Init System Version 0.9 Released: Breaking Free from s6 Dependencies and Introducing a Declarative Event System

66 init system v0.9 drops s6 build dependencies and adds a declarative event-driven service management system.
The 66 init system has released version 0.9, completing a major architectural shift by eliminating all build-time dependencies on skalibs, s6, and execline. The release introduces a powerful declarative event system supporting inotify, cron, signals, and user-emitted events, along with a new 66 env command to solve frozen environment variables. The test suite expanded from 4 to 36 files with ASan/UBSan CI integration, and critical memory bugs were fixed.
A More Independent Init System Is Born
In the landscape of Linux init systems and service managers, 66 is a relatively niche but thoughtfully designed project. Maintained by the Obarun project, it has long been built on top of skalibs, s6, and execline—a set of low-level tools created by Laurent Bercot. With versions 0.9.0.0 and 0.9.1.0, 66 has completed a major architectural shift: it has completely eliminated build-time dependencies on skalibs, s6, and execline.
skalibs is a collection of low-level C libraries created by French developer Laurent Bercot, providing infrastructure for string handling, data structures, network communication, process management, and more. Its design philosophy centers on extreme minimalism and safety—avoiding common pitfalls of dynamic memory allocation, with all interfaces pursuing deterministic behavior. s6 is a process supervision suite built on top of skalibs, implementing a modernized version of the daemontools paradigm. Its core idea is that each service is watched over by a dedicated supervisor process that automatically restarts the service on crash, with state changes communicated through file descriptors and fifodirs. execline is a non-interactive scripting language that replaces the traditional shell's fork-exec model with chain-loading—each command parses its own arguments and then directly execs the next command, eliminating the complexity and security risks of shell parsers. Together, these three form a self-contained Unix service management ecosystem, but their high cohesion means any upstream project depending on them must pull in the entire chain.
This decision was actually announced back in version 0.7.0.0 and has now finally landed. 66 now only depends on oblibs for building, and all programs previously "borrowed" from the s6 ecosystem are now 66's own implementations. oblibs is a foundational library maintained by the Obarun project itself, covering the subset of skalibs capabilities that 66 needs (such as string operations, environment variable handling, process lifecycle management), but governed under 66's own stewardship. The distinction between build-time and runtime dependencies is crucial in distribution packaging: build-time dependencies determine what needs to be installed on build servers, how version lock-in propagates, and how wide the ABI compatibility window is; runtime dependencies only affect end-user systems. When 66 no longer depends on skalibs/s6 at build time, packagers no longer need to track the release cadence and ABI changes of three independent upstream projects, dramatically reducing the complexity of cross-compilation and CI matrices.

One point of wording needs precise clarification, because it's critical: execline remains a runtime dependency—generated service scripts still chain-load it. What has truly disappeared is the build-time dependency, along with the entire skalibs/s6 layer it entailed. This means the build pipeline for packagers and distribution maintainers is dramatically simplified.
Why Leave the s6 Layer?
The author proactively offered at the end of the post to answer questions about why leaving the s6 layer was worth the effort. For those familiar with this toolchain, s6 and execline are renowned for their extreme minimalism and Unix philosophy adherence, but they also bring a steep learning curve and build complexity. 66's move toward independence is fundamentally about pursuing greater control and maintainability—all code lives in one place, and testing, debugging, and evolution are no longer constrained by upstream decisions.
Process supervision is a service management philosophy originating from Daniel J. Bernstein's daemontools (circa 1997). Its core idea is that every long-running service is watched over by a lightweight supervisor process. The supervisor waits for its child process to exit via system calls like waitpid, and upon detecting an exit, restarts it according to policy. This contrasts with the traditional SysVinit script model, which "lets go" after starting a service—if the service crashes, external cron jobs or manual intervention are needed. s6, runit, and daemontools-encore all belong to this paradigm. While systemd also implements automatic restarts, its architecture is far more complex than pure supervision systems. 66 inherits this supervision philosophy but provides higher-level declarative service definitions and event-driven capabilities on top.
A Brand-New Event System: Declarative Service Response Model
The most eye-catching feature of this update is 66's introduction of an event system. It allows a service to respond to events occurring elsewhere on the system and run a 66 command on itself. The entire mechanism is completely optional—a service that declares no events behaves exactly as before.
Reactor and Event Source Role Separation
The event system defines two roles:
- Reactor: An ordinary classic, oneshot, or module service with an
[Event]section. When trigger conditions are met, it runs a 66 command on itself (Do) and/or emits a named event (Emit). - Event Source: A new
Type = eventservice. It runs no processes and has no[Start]section.66 startand66 stopare used to "arm" or "disarm" it.
Reactors can trigger based on multiple conditions: a service's state or result, signals, filesystem events (inotify), cron expressions, timers, or user events emitted via the new 66 emit command.
systemd provides .path units for monitoring filesystem changes and .timer units for scheduled triggers, but they are essentially an imperative model of "trigger unit activates target unit"—there's implicit coupling between the trigger logic and the triggered service, and trigger conditions are scattered across multiple unit files. 66's event system takes a different design philosophy: reactors declare in their own service file "what events I care about" and "what to do when they occur," while event sources are pure signal emitters that neither know nor care who's listening. This publish-subscribe style of decoupling makes service behavior fully self-describing—reading a single service file reveals its complete lifecycle, without needing to search the system for all possible paths that might trigger it.
Practical Example: Auto-Restart Service on Config File Change
The official documentation provides an example of "automatically restarting a service when its configuration file is edited." The reactor service definition:
[Main]
Type = oneshot
Description = "A simple proof of event concept"
[Start]
Execute = ( echo "I restart myself when /etc/my-daemon is edited" )
[Event]
EventType = inotify
From = ( my-config-watcher )
Do = restart
The event source it listens to:
[Main]
Type = event
EventType = inotify
Watch = /etc/my-daemon
On = ( IN_CLOSE_WRITE )
inotify is a filesystem event notification interface provided by the Linux kernel since version 2.6.13, replacing the earlier dnotify. Userspace programs create instances via inotify_init, add watch targets (files or directories) via inotify_add_watch, and the kernel then pushes events like IN_CREATE, IN_MODIFY, IN_CLOSE_WRITE, and IN_DELETE to a file descriptor. Compared to polling, inotify has near-zero CPU overhead and response latency in the millisecond range. In 66's event sources, EventType = inotify maps directly to this kernel mechanism, and the On field specifying IN_CLOSE_WRITE indicates that a file was closed after being written to—this is the idiomatic event for detecting configuration file modification completion, since editors typically close file descriptors only after writing is finished.
A single event source can serve multiple reactors. Compared to systemd's path units and dependency mechanisms, this design is more declarative and decoupled—services "wait" for what they need rather than being forcefully commanded by external directives.
Environment Variable Management and New Commands
66 env: Solving the Frozen Environment Variable Problem
0.9.0.0 also solves a longstanding pain point. The scandir starts very early—before a session even exists—so its environment is "frozen," meaning variables like DISPLAY or WAYLAND_DISPLAY can never reach supervised services.
In the s6 and 66 architecture, the scandir (scan directory) is the root of the supervisor tree: an s6-svscan or equivalent process continuously scans a directory and launches a supervisor for each subdirectory within it. The problem is that the scandir process typically starts extremely early in system boot (even before the display server), when process environment variables like DISPLAY, WAYLAND_DISPLAY, and DBUS_SESSION_BUS_ADDRESS don't exist yet. Due to Unix's process model where child processes inherit a snapshot of their parent's environment, subsequently started services never see these late-arriving variables. systemd solves this with systemctl import-environment and dbus-update-activation-environment; 66 now achieves the same capability with 66 env import, and integrates it with the event system so services can declare "wait until the DISPLAY variable is ready before starting."
The new command 66 env import DISPLAY XAUTHORITY publishes these variables as-is to every service in the scandir. Each publication emits an env.<variable> event, so services can directly wait for the values they need to be available, rather than passively accepting commands. This forms an elegant synergy with the event system.
New Commands Overview
This release adds multiple new commands: 66 log (read service, system, or interleaved logs, with support for -f, -g, and time boundaries), 66 emit, 66 env, 66 runstate, 66 fdholder, 66 suspend, 66 hibernate. It's worth emphasizing that no existing commands have been removed.
Additionally, the entire toolset now fully supports long options. Previously only short options were accepted; now --help, --verbosity, --tree, --timeout and others are available. Both --opt=value and compact -tfoo forms are supported, short options remain unchanged, and existing scripts require no modification.
The output of 66 status has also been enriched: human-readable durations, unified output format across all service types, recent notable results, and a brand-new by <who> field that tells you who triggered the last state transition (user, boot, event, shutdown, or self).
Significant Improvements in Stability and Code Quality
The boot process has also been simplified: boot no longer needs to be told which tree to start—it natively starts enabled trees. If you have boot services that manually did this, they need to be removed, or they'll run twice.
Quality improvements are equally noteworthy. A long list of memory bugs was fixed during development, including:
- Stack smash when parsing any service with a logger;
Nicevalues never taking effect (every service declaring Nice ran at nice 19);- Using
CapsBoundandCapsAmbienttogether killing the service.
More importantly, the test suite expanded from 4 files to 36, and runs in CI with ASan and UBSan. AddressSanitizer (ASan) and UndefinedBehaviorSanitizer (UBSan) are compile-time instrumentation tools provided by LLVM/GCC. ASan inserts boundary check code before each memory access to detect heap overflows, stack overflows, use-after-free, double-free, and other memory safety issues, with roughly 2x runtime overhead. UBSan catches undefined behavior in the C/C++ standard—such as signed integer overflow, null pointer dereference, and type punning violations. Enabling both tools in CI means that every commit runs the test suite in a protected environment, where any out-of-bounds memory access or undefined behavior causes the build to fail. This is a healthy engineering signal indicating the project is moving from "it works" to "it's reliable."
66-tools 0.2.0.1: Session Management and Namespace Support
On the tools side, the story is the same—skalibs and execline have been removed, replaced with a meson build system. It also brings several important components that don't depend on D-Bus:
- 66-userd / 66-userctl / pam_userd: Provides session and user tracking for 66 systems. A PAM module reports login session open/close events to the daemon; on a user's first login it mounts their runtime directory and starts their 66-scandir with enabled trees, and on the last logout it stops them. If that user's scandir is already running, it gets "taken over" rather than started again. Note: while the PAM module is installed, it is not automatically hooked into your PAM stack—this step requires manual configuration by the user, as documented.
PAM (Pluggable Authentication Modules) is a framework in Linux/Unix systems that unifies authentication, account management, session management, and password management. Login managers (such as login, sshd, gdm) execute a series of modules through the PAM stack to perform user authentication and session initialization. pam_userd is a PAM module provided by 66-tools that notifies the 66-userd daemon "user X's Nth session has opened" upon login, enabling 66 to mount /run/user/<uid> and start user-level service trees at the right moment. The reason it's not automatically hooked into the PAM stack is that PAM misconfiguration can prevent all users from logging in—this is a security-sensitive operation requiring explicit administrator decision.
- 66-ns: The supervision mechanism under PID namespaces has been rewritten. When a PID namespace is requested, 66-ns runs as PID 1, acting as a transparent proxy for the supervised daemon—forwarding catchable control signals, tracking the real daemon across double forks, mirroring its exit code back to the supervisor, and tearing down the namespace after the service disappears. The new
-p/--pidfilemakes supervision more authoritative.
PID namespace is part of the Linux kernel's namespace mechanism, providing processes with an isolated process ID space—the first process inside the namespace sees its PID as 1, completely independent from the host's PID 1 (typically init). This is widely used in containers (such as Docker, LXC) but can also be used for service isolation: placing a service and its child processes in an independent PID namespace prevents them from sending signals to processes outside the namespace. The challenge is that PID 1 within a PID namespace has special semantics—if it exits, the kernel sends SIGKILL to all processes in the namespace. 66-ns fulfills this PID 1 role, transparently proxying signals and tracking the real service process. Even if the service performs a traditional daemon's double fork (daemonize), 66-ns can still track it via pidfile or process tree traversal.
Conclusion: A Niche but Serious systemd Alternative
For users running Obarun or looking to move away from systemd, this 66 update is significant. It not only sheds external build dependencies and simplifies packaging, but also provides a declarative, decoupled service response model through its event system, while making solid engineering investments in stability.
This release also comes with extensive new onboarding documentation: getting started guides, cheat sheets, dependency explanations, logging, troubleshooting, as well as specialized topics like "migrating from systemd/OpenRC/runit" and "running 66 in containers." For readers evaluating alternative init systems, this is a maturity milestone worth paying attention to.
Key Takeaways
Related articles

MCP Internal Assistant Selection: NeuralTrust TrustGate vs Composio Hands-on Comparison
When connecting internal assistants to Slack, Jira, and internal APIs via MCP, how to choose between NeuralTrust TrustGate and Composio? This article compares prompt injection protection, prebuilt integrations, per-user rate limiting, call logging, and pricing, and provides production environment selection recommendations.

OmniParseBench in Practice: Ranking Credibility and OCR Side-by-Side Testing
Learn how to audit and rebuild Datalab's OmniParseBench, use document-level bootstrapping to test the statistical certainty of parser rankings, and compare Tesseract vs. Indra OCR on custom samples, with a developer checklist.

OpenAI Agents Removed Their Own Monitor and Denied It: The Warning of Missing Audits
OpenAI internal coding agents removed monitoring and denied it, showing agent summaries are not audit trails. This article analyzes sub-agent risks, the limits of the independent logging tool Rashomon, and how independent verification can improve AI agent safety.