# Vendored source tree extracted by hh-build at build time. # tools/vendored.conf records the version + sha256. source/ # Build artifacts. build/ # usr/src/zyginit/Makefile — Hammerhead-side build for the vendored # zyginit init system, zygctl admin CLI, and sysv-wrapper compat shim. # # zyginit ships the engine (binary + schema docs + man pages). Hammerhead # owns the policy: 50 service.toml definitions, the enabled.d/ default- # enable policy, and the example configs. The split was agreed with the # zyginit team on 2026-05-21; before then, ~22 service definitions came # from the upstream tarball and we layered a small overrides/ tree on # top of them. That two-source model is gone — see docs/bugs/zyginit- # 0.2-service-toml-paths.md for the motivating bug. # # Engine source comes from a tarball pinned in tools/vendored.conf, # extracted by hh-build into ./source/. The Makefile here is the only # committed build glue; ./source/ is gitignored. # # Three binaries land in /sbin/: # /sbin/zyginit PID 1 init daemon (with /sbin/init -> zyginit) # /sbin/zygctl admin CLI # /sbin/sysv-wrapper legacy compat (halt/reboot/poweroff/telinit symlinks) # # Plus 50 service definitions installed under /etc/zyginit/services//, # the enabled.d/ symlink set, examples, runtime dirs under /var/{log,run}/ # zyginit/, and three man pages. # Always use the staged reefc from tools/bootstrap/root-hh/. bootstrap-hh.sh # runs earlier in hh-build's build_toolchain; this is the version pinned # by tools/bootstrap.conf and built fresh, never the system reefc. HAMMERHEAD_ROOT = $(SRC)/../../.. REEFC = $(HAMMERHEAD_ROOT)/tools/bootstrap/root-hh/usr/bin/reefc # Vendored source tree (extracted by hh-build, gitignored). ZSRC = source # Built binaries (paths inside ZSRC). ZYGINIT_BIN = $(ZSRC)/build/zyginit ZYGCTL_BIN = $(ZSRC)/tools/zygctl/build/zygctl SYSVW_BIN = $(ZSRC)/tools/sysv-wrapper/sysv-wrapper # Install destinations under proto. ROOTSBIN = $(ROOT)/sbin ROOTETC_ZYGINIT = $(ROOT)/etc/zyginit ROOTETC_ZYGINIT_ENABLED = $(ROOT)/etc/zyginit/enabled.d ROOTETC_ZYGINIT_EX = $(ROOT)/etc/zyginit/examples ROOTVAR_LOG = $(ROOT)/var/log/zyginit ROOTVAR_RUN = $(ROOT)/var/run/zyginit ROOTMAN5 = $(ROOT)/usr/share/man/man5 ROOTMAN8 = $(ROOT)/usr/share/man/man8 .PHONY: all install clean clobber _msg \ check_source build_zyginit build_zygctl build_sysvwrapper \ build_console_login_helper \ install_bins install_init_link install_services \ install_examples install_enabled_d install_man install_runtime_dirs all: check_source build_zyginit build_zygctl build_sysvwrapper \ build_console_login_helper check_source: @if [ ! -d "$(ZSRC)" ]; then \ echo "ERROR: $(ZSRC)/ missing — hh-build's extract step did not run." >&2; \ echo " Source comes from tools/vendored.conf via hh-build." >&2; \ exit 1; \ fi @if [ ! -x "$(REEFC)" ]; then \ echo "ERROR: staged reefc missing at $(REEFC)" >&2; \ echo " bootstrap-hh.sh build must run before zyginit." >&2; \ exit 1; \ fi # zyginit — PID 1 init daemon. # helpers.c provides illumos-side syscall wrappers; zyginit links libcontract. build_zyginit: @mkdir -p $(ZSRC)/build cd $(ZSRC) && \ gcc -c src/helpers.c -o build/helpers.o && \ $(REEFC) build -l contract --obj build/helpers.o # zygctl — admin CLI. # symlink_wrapper.c handles argv[0] dispatch for the multi-name binary. build_zygctl: @mkdir -p $(ZSRC)/tools/zygctl/build cd $(ZSRC)/tools/zygctl && \ gcc -c src/symlink_wrapper.c -o build/symlink_wrapper.o && \ $(REEFC) build --obj build/symlink_wrapper.o # sysv-wrapper — plain C shim with its own Makefile. build_sysvwrapper: $(MAKE) -C $(ZSRC)/tools/sysv-wrapper # Per-service compiled helpers shipped under ./services//. # Today only console-login has one (write_login_utmpx — writes a # LOGIN_PROCESS utmpx entry before exec'ing ttymon; required for # login(1) to find a matching ut_pid). build_console_login_helper: gcc -O2 -Wall \ -o services/console-login/write_login_utmpx \ services/console-login/write_login_utmpx.c install: all install_bins install_init_link install_services \ install_examples install_enabled_d install_man install_runtime_dirs install_bins: mkdir -p $(ROOTSBIN) # cp + chmod (not install(1) — illumos's /usr/sbin/install is a broken # shell script that needs a richer PATH than hh-build provides). cp $(ZYGINIT_BIN) $(ROOTSBIN)/zyginit cp $(ZYGCTL_BIN) $(ROOTSBIN)/zygctl cp $(SYSVW_BIN) $(ROOTSBIN)/sysv-wrapper chmod 0555 $(ROOTSBIN)/zyginit $(ROOTSBIN)/zygctl $(ROOTSBIN)/sysv-wrapper # legacy-name symlinks → sysv-wrapper ln -sf sysv-wrapper $(ROOTSBIN)/halt ln -sf sysv-wrapper $(ROOTSBIN)/reboot ln -sf sysv-wrapper $(ROOTSBIN)/poweroff ln -sf sysv-wrapper $(ROOTSBIN)/telinit # /sbin/init -> /sbin/zyginit. The kernel exec's /sbin/init at boot, # so this symlink is what makes zyginit PID 1. install_init_link: rm -f $(ROOTSBIN)/init ln -sf zyginit $(ROOTSBIN)/init # Service definitions are Hammerhead-owned (not from the zyginit # tarball) as of 2026-05-21. zyginit ships only the engine binary + # schema docs + a handful of representative examples; Hammerhead owns # the policy: 50 services under ./services// each containing a # service.toml plus any helper scripts and helper-binary sources. # Layout matches the runtime tree at /etc/zyginit/services//. install_services: mkdir -p $(ROOTETC_ZYGINIT)/services @for d in services/*/; do \ [ -d "$$d" ] || continue; \ name=$$(basename $$d); \ dst=$(ROOTETC_ZYGINIT)/services/$$name; \ mkdir -p $$dst; \ for f in $$d*; do \ [ -e "$$f" ] || continue; \ base=$$(basename "$$f"); \ case "$$base" in \ *.c) continue ;; \ esac; \ cp "$$f" $$dst/; \ done; \ [ -f $$dst/service.toml ] && chmod 0644 $$dst/service.toml || true; \ find $$dst -type f -name '*.sh' -exec chmod 0755 {} \; ; \ done # Per-service compiled helpers (e.g. write_login_utmpx). -find $(ROOTETC_ZYGINIT)/services -type f -name 'write_login_utmpx' -exec chmod 0755 {} \; install_examples: mkdir -p $(ROOTETC_ZYGINIT_EX) -cp examples/* $(ROOTETC_ZYGINIT_EX)/ 2>/dev/null -chmod 0644 $(ROOTETC_ZYGINIT_EX)/* 2>/dev/null # enabled.d/ policy comes from THIS directory (Hammerhead-side decision # of which services run by default), NOT from the vendored tarball. # Each entry in ./enabled.d/ is a symlink whose basename is the service # name. With zyginit 0.2.x per-service-directory layout, the proto-side # link points at ../services// (the directory). The on-disk source # symlinks may still target the old .toml — we read only the # basename and synthesize the new target, so we don't depend on the # source symlink's literal target or on a base-system readlink(1). install_enabled_d: mkdir -p $(ROOTETC_ZYGINIT_ENABLED) @for f in enabled.d/*; do \ [ -L "$$f" ] || continue; \ name=$$(basename "$$f"); \ rm -f $(ROOTETC_ZYGINIT_ENABLED)/$$name; \ ln -sf "../services/$$name" $(ROOTETC_ZYGINIT_ENABLED)/$$name; \ done install_man: mkdir -p $(ROOTMAN5) $(ROOTMAN8) cp $(ZSRC)/docs/man/man5/zyginit.5 $(ROOTMAN5)/zyginit.5 cp $(ZSRC)/docs/man/man8/zyginit.8 $(ROOTMAN8)/zyginit.8 cp $(ZSRC)/docs/man/man8/zygctl.8 $(ROOTMAN8)/zygctl.8 chmod 0444 $(ROOTMAN5)/zyginit.5 $(ROOTMAN8)/zyginit.8 $(ROOTMAN8)/zygctl.8 install_runtime_dirs: mkdir -p $(ROOTVAR_LOG) $(ROOTVAR_RUN) clean: rm -rf $(ZSRC)/build $(ZSRC)/tools/zygctl/build rm -f $(ZSRC)/tools/sysv-wrapper/sysv-wrapper clobber: clean rm -rf $(ZSRC) # zyginit is not localized — no .po messages. _msg: enabled.d/ — default-enabled service list (build-time markers) ================================================================ The symlinks in this directory mark which services from ../services/ should be enabled by default on a fresh Hammerhead install. Only the BASENAMES matter here; the targets are intentionally dangling (they point at e.g. `../acpihpd.toml` which doesn't exist in the source tree). The Makefile's install_enabled_d target reads the basenames and writes REAL symlinks at install time, of the form: $(ROOT)/etc/zyginit/enabled.d/ -> ../services/ …which is what zyginit actually consults at runtime. So: * To add a service to the default-enabled set: create a symlink here with the service's name. Target doesn't matter (`ln -sf ../.toml ` matches the existing convention). * To remove from the default-enabled set: rm the symlink here. * The actual service definition lives in ../services// and is always installed regardless of whether it's enabled by default — enabled.d/ only controls the initial state. Admins can later `zygctl enable ` to flip the symlink in place on a running system. See ../Makefile (target install_enabled_d) for the rewrite logic. ../acpihpd.toml../acpihpd-check.toml../console-login.toml../cron.toml../crypto.toml../devfs.toml../devices-audio.toml../dlmgmtd.toml../filesystem.toml../hotplug.toml../identity.toml../ipmgmtd.toml../mdnsd.toml../ndp.toml../network.toml../pfexecd.toml../root-fs.toml../single-user-shell.toml../sshd.toml../swap.toml../sysconfig.toml../syseventd.toml../syslogd.toml../utmpd.toml# /etc/defaultrouter — one IPv4 default-gateway address per line. # Copy to /etc/defaultrouter on the target BE *only* when using the # static-IP sample; DHCP supplies the route automatically. # # 192.168.122.1 is libvirt's default-network virtual router. 192.168.122.1 # /etc/hostname.vioif0 — DHCP-acquired address for hh-prototest # Copy to /etc/hostname.vioif0 on the target BE. # # Caveat (spec §4.4): /sbin/dhcpagent links libipadm.so.1 which talks to # ipmgmtd's door. Without ipmgmtd running, lease application may degrade. # Use the static sample first to derisk; switch to this once iter 6 # proves DHCP-without-ipmgmtd works. dhcp # /etc/hostname.vioif0 — static IPv4 for hh-prototest libvirt VM. # Copy to /etc/hostname.vioif0 on the target BE. # # 192.168.122.50 is INSIDE libvirt's default dnsmasq pool # (192.168.122.2-192.168.122.254). Before iter 6 boot, prevent # collision via one of: # 1. Add a host reservation to the libvirt network XML: # virsh net-edit default # ... # 2. Shrink the dnsmasq range, e.g. # # 3. Use an address outside the libvirt subnet entirely (requires extra routing). # # See spec §4.4 for the full /etc/hostname. grammar. inet 192.168.122.50/24 # Precheck for the ACPI hotplug daemon. Runs before `acpihpd` and # disables it on platforms without ACPI hotplug (i440fx KVM, etc.) so # the daemon doesn't false-positive as "failed" at boot. On supported # platforms (bare metal, q35 KVM) this is a no-op and acpihpd starts # normally. [service] name = "acpihpd-check" description = "Disable acpihpd if no ACPI hotplug controller" type = "oneshot" [exec] start = "/etc/zyginit/services/acpihpd-check/start.sh" [dependencies] requires = ["filesystem"] [restart] on = "never" #!/bin/sh # Disable acpihpd if the kernel's ACPI hotplug controller node isn't # present. /devices/fw/acpidr@0 is created by the apix/acpi driver # stack on platforms that support ACPI hotplug — common on bare metal # and q35 KVM, absent on i440fx KVM and most cloud hypervisors. # # Splitting the prtconf check into its own oneshot service (instead of # baking it into acpihpd's start.sh as a self-disable) lets `acpihpd` # stay a pure type=daemon: when ACPI hotplug exists, acpihpd starts # normally and gets full supervisor tracking + restart-on-crash; when # it doesn't, the daemon never starts at all, so no "exit=-1" false # positive in the boot report. if /sbin/prtconf -D /devices/fw/acpidr@0 >/dev/null 2>&1; then exit 0 fi /bin/echo "ACPI hotplug controller absent; disabling acpihpd" >&2 /sbin/zygctl disable acpihpd >/dev/null 2>&1 || true exit 0 # ACPI hotplug daemon — handles ACPI-driven CPU/memory/PCI hotplug # events. Useful on KVM/cloud where vCPUs and memory may be hot-added. # Replaces /lib/svc/manifest/platform/amd64/acpihpd.xml. [service] name = "acpihpd" description = "ACPI hotplug daemon" type = "daemon" [exec] start = "/etc/zyginit/services/acpihpd/start.sh" [dependencies] # acpihpd-check disables this service on platforms without ACPI # hotplug, so by the time we get here we're on a supported platform. requires = ["filesystem", "acpihpd-check"] [restart] # A real crash on supported platforms is rare enough that manual # `zygctl restart` is fine. (Platform-conditional disable now lives # in acpihpd-check, so this is purely about runtime crash policy.) on = "never" [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] #!/bin/sh # Platform check lives in the acpihpd-check oneshot service, which # runs before this and disables acpihpd when /devices/fw/acpidr@0 is # absent. If we got here, the daemon is supported on this platform — # just exec it. # # Hammerhead flattens the platform-lib tree: cmd/acpihpd installs to # /usr/lib/acpihpd (via $(USR_PSM_LIB_DIR), which Hammerhead's # Makefile.psm redefines as $(ROOT)/usr/lib for Phase 2 flat paths). exec /usr/lib/acpihpd # BSM audit daemon. Folds in the auditset oneshot (which set kernel # preselection masks) by running it from start.sh before exec'ing # auditd. Replaces both /lib/svc/manifest/system/auditd.xml and # /lib/svc/manifest/system/auditset.xml. # # auditd forks to background by default; -s keeps it foreground for # proper supervision. [service] name = "auditd" description = "BSM audit daemon" type = "daemon" [exec] start = "/etc/zyginit/services/auditd/start.sh" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] #!/bin/sh # Audit setup + daemon. The auditset method (/usr/lib/svc/method/svc-auditset) # loads the kernel preselection masks; we run it once before exec'ing auditd. # Skipped if the kernel module isn't loaded (auditconfig -getcond fails). set -e # Bail cleanly if c2audit is not loaded — auditconfig returns non-zero. if ! /usr/sbin/auditconfig -getcond >/dev/null 2>&1; then /bin/echo "c2audit not loaded; skipping audit start" >&2 exit 0 fi # Validate config; fail fast if broken. /usr/sbin/audit -v >/dev/null 2>&1 || { /bin/echo "audit -v: misconfiguration" >&2 exit 1 } # Apply preselection masks (was the auditset oneshot in SMF). [ -x /usr/lib/svc/method/svc-auditset ] && /usr/lib/svc/method/svc-auditset # Optional one-time transition step from upstream. if [ -x /etc/security/audit_startup ]; then /etc/security/audit_startup || true /bin/mv /etc/security/audit_startup /etc/security/audit_startup._transitioned_ 2>/dev/null || true fi exec /usr/sbin/auditd # Software bridge stub. Upstream SMF instantiated one per-bridge # instance (`network/bridge:bridge0`, etc.); zyginit doesn't yet have # a multi-instance pattern, so this TOML is a placeholder. Admins who # need bridges should configure them via `dladm create-bridge` and # launch `bridged` directly until the zyginit team supports # multi-instance services. Replaces /lib/svc/manifest/network/bridge.xml. [service] name = "bridge" description = "Software bridge support (multi-instance, manual today)" type = "oneshot" [exec] start = "/usr/bin/true" [dependencies] requires = ["network"] [restart] on = "never" # Alternate console daemon (consadmd). Off by default; enable when # /etc/consadm.conf lists serial lines that should also act as the # console. Replaces /lib/svc/manifest/system/consadm.xml. [service] name = "consadm" description = "Alternate console daemon" type = "daemon" [exec] start = "/etc/zyginit/services/consadm/start.sh" [dependencies] requires = ["filesystem"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] #!/bin/sh # Skip cleanly when no /etc/consadm.conf is present — consadmd has # nothing to track without it. if [ ! -r /etc/consadm.conf ]; then /bin/echo "no /etc/consadm.conf; consadm skipped" >&2 exit 0 fi exec /usr/sbin/consadmd # Hammerhead override of services/hammerhead/console-login.toml shipped # by zyginit. Removes the syslogd dependency so that: # # - console-login starts in the earliest tier that satisfies utmpd # (so ttymon claims /dev/console as ctty before any other daemon) # - syslogd's override (overrides/syslogd.toml) declares the reverse # edge: requires = ["network", "console-login"] # # Without this override, both files together form a dependency cycle # (console-login → syslogd → console-login) and zyginit refuses to boot # with "depgraph: dependency cycle detected, cannot boot". # # Pairs with zyginit bug 001. Drop both overrides once the supervisor # is fixed upstream. [service] name = "console-login" description = "Console login (ttymon on /dev/console)" type = "daemon" [exec] start = "/etc/zyginit/services/console-login/start.sh" [dependencies] requires = ["utmpd"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] #!/bin/zsh # console-login — exec ttymon on /dev/console with a properly quoted prompt. # # Wrapping ttymon in a script (instead of inlining in exec.start) sidesteps # zyginit's str.split-on-space tokenizer, which doesn't honor shell quotes. # Inline the ttymon invocation here where the shell DOES quote correctly, # then exec so ttymon becomes the supervisor's direct child. set -e set -u # Write a LOGIN_PROCESS utmpx entry for the PID that's about to become # ttymon (this script's PID, since we use `exec` below). ttymon's # getty_account() (tmutmp.c) updates an existing entry — it does not # create one — so without this, login(1) walks utmpx, finds nothing # matching its PID, and exits with "No utmpx entry. You must exec # login from the lowest level shell." /etc/zyginit/services/console-login/write_login_utmpx co console $$ # -T sun-color: tell the login session to use the sun-color terminfo # entry, which matches illumos's in-kernel `tem` console emulator. # Without this, zshenv's fallback to TERM=xterm leaks raw ANSI escapes # ((B, [B, etc.) through the zsh prompt because tem doesn't implement # the full xterm spec. exec /usr/lib/saf/ttymon \ -g \ -d /dev/console \ -l console \ -m ldterm,ttcompat \ -T sun-color \ -h \ -p "hammerhead console login: " /* * write_login_utmpx — write a LOGIN_PROCESS utmpx entry for a target PID. * * Usage: write_login_utmpx * * Background: illumos `login(1)` walks utmpx and requires an entry whose * ut_pid matches login's own PID. ttymon updates an existing entry via * pututxline() — it does NOT create one. Standard illumos init writes * INIT_PROCESS entries when spawning getty/ttymon from inittab; zyginit * doesn't, so ttymon has nothing to update and login eventually errors * out with "No utmpx entry. You must exec login from the lowest level * shell." * * This helper writes a LOGIN_PROCESS entry for a target PID and exits. * Called from console-login/start.sh just before `exec ttymon`, with * $$ (the wrapper script's PID, which becomes ttymon's PID after exec) * as the third argument. */ #include #include #include #include #include int main(int argc, char **argv) { struct utmpx ux; pid_t target_pid; if (argc < 4) { (void) fprintf(stderr, "Usage: %s \n", argv[0]); return (1); } target_pid = (pid_t)atoi(argv[3]); (void) memset(&ux, 0, sizeof (ux)); (void) strncpy(ux.ut_id, argv[1], sizeof (ux.ut_id)); (void) strncpy(ux.ut_line, argv[2], sizeof (ux.ut_line)); (void) strncpy(ux.ut_user, "LOGIN", sizeof (ux.ut_user)); ux.ut_pid = target_pid; ux.ut_type = LOGIN_PROCESS; (void) time(&ux.ut_tv.tv_sec); setutxent(); if (pututxline(&ux) == NULL) { (void) fprintf(stderr, "%s: pututxline failed\n", argv[0]); endutxent(); return (1); } endutxent(); return (0); } [service] name = "cron" description = "cron daemon" type = "daemon" [exec] start = "/etc/zyginit/services/cron/start.sh" [dependencies] requires = ["syslogd"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] #!/bin/zsh # cron — start the cron daemon, cleaning up its stale FIFO first. # # /usr/sbin/cron creates /etc/cron.d/FIFO at startup and refuses with # "cannot start cron; FIFO exists" if the file is already there. The # canonical SMF svc-cron method removes this exact path before starting # cron — we mirror that. (Earlier we removed /var/run/cron_fifo which # is wrong: /var/run is tmpfs so it'd be empty anyway, and that's not # where cron actually puts the FIFO.) set -e set -u /bin/rm -f /etc/cron.d/FIFO # exec replaces this shell with cron — supervisor sees cron as the # direct child, so contract events and stop signals work cleanly. exec /usr/sbin/cron # crypto — Initialize Kernel Cryptographic Framework [service] name = "crypto" description = "Kernel cryptographic framework" type = "oneshot" [exec] start = "/etc/zyginit/services/crypto/start.sh" [runlevel] # Required if filesystem mounts include encrypted ZFS datasets — keys # must be loaded before the filesystem service can mount them. Cheap # enough to run unconditionally. mode = "always" [dependencies] requires = ["root-fs"] [restart] on = "never" #!/bin/zsh # crypto — configure the Kernel Cryptographic Framework # # The kernel crypto framework is loaded at boot via kcf module; this # oneshot applies userland configuration (kcfd persistent state). # cryptoadm refresh reloads the kernel policy from /etc/crypto/*.conf set -e set -u /usr/sbin/cryptoadm refresh exit 0 # devfs — populate /dev (devfsadm) and configure sockfs protocol modules # (soconfig). Without soconfig, socket(AF_INET) returns EAFNOSUPPORT and # every later ifconfig call — including lo0 plumb — fails. [service] name = "devfs" description = "Device and socket protocol configuration" type = "oneshot" [exec] start = "/etc/zyginit/services/devfs/start.sh" [runlevel] # Essential — /dev nodes (incl. /dev/dld for dlmgmtd) and sockfs # protocol map are needed for any meaningful userland operation. mode = "always" [dependencies] requires = ["root-fs"] [restart] on = "never" #!/bin/zsh # devfs/start.sh — populate /dev and configure socket protocol modules # # Mirrors SMF's system/device/local method (lib/svc/method/devices-local). # devfsadm configures hardware; soconfig loads /etc/sock2path.d into kernel # sockfs so socket(AF_INET, ...) and socket(AF_INET6, ...) actually return # a valid fd. Without soconfig, every socket() of those families fails # EAFNOSUPPORT and ifconfig cannot configure any interface — including lo0. set -e set -u # Hardware device configuration. Hammerhead's SMF runs this unconditionally # (upstream illumos only runs it on reconfigure boots, which leaves PCI # NICs unattached on fresh deploys); we follow Hammerhead's convention. /usr/sbin/devfsadm # Load minor_perm + device_policy (/etc/security/device_policy) into the # kernel. WITHOUT this, the kernel keeps its restrictive built-in default # (DEFAULT read_priv_set=all / write_priv_set=all), so a NON-root process # can't even open() /dev/null or /dev/zero — open() returns EACCES. That # breaks the non-root (zygaena) build (perl/OCaml/reefc all redirect to # /dev/null) and any non-root program. `devfsadm -P` is the only devfsadm # mode that loads these tables (illumos did it in SMF's devices-local # method; the zyginit port dropped it). Absolute path per the zyginit # boot-script convention. /usr/sbin/devfsadm -P # Clear the first-boot reconfigure flag. hh-deploy drops /reconfigure so a # fresh deploy does a full device enumeration on its first boot; the kernel # reads the flag at boot and performs a reconfigure boot (rebuilding the # device tree, ~15s to write /etc/devices/pci_unitaddr_persistent). illumos's # devices-local method removes the flag afterwards — the zyginit port dropped # that step, so WITHOUT this every boot reconfigures: slow boot + a long delay # before the console login prompt appears. devfsadm above has now processed # this boot's reconfigure, so it is safe to clear for subsequent boots. # Absolute path per the zyginit boot-script convention. /bin/rm -f /reconfigure # Force PCI bus enumeration in case devfsadm alone didn't trigger it. /usr/bin/ls /devices/pci@0,0/ > /dev/null 2>&1 || true # Load AF_* → STREAMS-module mappings into the kernel sockfs. THIS is the # step that makes socket(AF_INET, ...) work. /sbin/soconfig -d /etc/sock2path.d # Refresh kernel driver.conf cache. /usr/sbin/devfsadm -I exit 0 # Audio device init oneshot. Populates the initial /dev/sndstat # mapping by walking each audio device and plumbing it. Replaces # /lib/svc/manifest/system/device/devices-audio.xml. [service] name = "devices-audio" description = "Initialize audio devices" type = "oneshot" [exec] start = "/usr/bin/audioctl init-devices" [dependencies] requires = ["filesystem"] [restart] on = "never" # dlmgmtd — Datalink management daemon # # Runs in `-d` (debug) mode, which on dlmgmtd means "single foreground # process, no double-fork daemonize." Three reasons we use it: # 1. Skips the SMF_FMRI startup gate (debug mode bypasses the check). # 2. Keeps stderr connected to our log; without it dlmgmtd's daemonize # runs fdwalk(closefunc), closing fd 2 — any startup error then # goes to syslog, which isn't running yet at this tier. # 3. zyginit's contract tracks a single PID instead of dealing with # the parent-exits-0-after-handshake daemonize pattern. # Cosmetic effect: the cache file is named "dlmgmtd.debug.cache" instead # of "network-datalink-management:default.cache". Nothing external reads # this file — only dlmgmtd itself, for fast restart state recovery. [service] name = "dlmgmtd" description = "Datalink management daemon (foreground)" type = "daemon" [exec] start = "/sbin/dlmgmtd -d" [dependencies] requires = ["filesystem"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # filesystem — Mount all local filesystems from /etc/vfstab + ZFS [service] name = "filesystem" description = "Local filesystem mounts and ZFS imports" type = "oneshot" [exec] start = "/etc/zyginit/services/filesystem/start.sh" stop = "/etc/zyginit/services/filesystem/stop.sh" [runlevel] # /var, /usr, etc. mounts are needed in any operating mode — without # /var the recovery shell can't even read /var/adm/messages. mode = "always" [dependencies] requires = ["root-fs", "devfs"] [restart] on = "never" #!/bin/zsh # filesystem — mount local filesystems + import ZFS pools # # mountall -l mounts all local-type entries from /etc/vfstab (idempotent # — skips already-mounted). zpool import -a imports any pools on attached # disks. ZFS filesystems with mountpoint set auto-mount. set -e set -u # Mount everything in /etc/vfstab with mount-at-boot = yes, excluding / /usr/sbin/mountall -l # fdfs (file descriptor pseudo-fs) is in vfstab with mount-at-boot=no per # illumos convention, but is required by dtrace -C (USDT probe compilation # preprocesses .d scripts via fd 3 → /dev/fd/3). Mount it explicitly here; # idempotent (skips if already mounted). /usr/sbin/mount -F fd fd /dev/fd 2>/dev/null || true # Import all ZFS pools visible to the system /usr/sbin/zpool import -a || true # Auto-mount ZFS filesystems with canmount=on /usr/sbin/zfs mount -a exit 0 #!/bin/zsh # filesystem — umount everything except / before root-fs goes read-only # # umountall -l unmounts all local-type filesystems that are NOT /. # ZFS pools export is handled implicitly by umount of their mountpoints. set -e set -u # Best-effort: anything that can't umount (busy) is logged but doesn't # block shutdown — ZFS in-kernel state will still checkpoint correctly. /usr/sbin/umountall -l || true # Explicitly sync ZFS before root goes ro /usr/sbin/zpool sync || true exit 0 [service] name = "fmd" description = "Fault Manager Daemon" type = "daemon" [exec] start = "/usr/lib/fm/fmd/fmd" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # IP forwarding toggle — enables IPv4/IPv6 packet forwarding via # routeadm/ipadm. Off by default; enable when the host acts as a # router/gateway. Replaces /lib/svc/manifest/network/forwarding.xml. [service] name = "forwarding" description = "IPv4/IPv6 packet forwarding" type = "oneshot" [exec] start = "/etc/zyginit/services/forwarding/start.sh" stop = "/etc/zyginit/services/forwarding/stop.sh" [dependencies] requires = ["network"] [restart] on = "never" #!/bin/sh # Enable v4 and v6 forwarding on all interfaces. Mirrors what # `routeadm -ue ipv4-forwarding ipv6-forwarding` does at boot. /usr/sbin/ipadm set-prop -p forwarding=on ipv4 2>/dev/null || true /usr/sbin/ipadm set-prop -p forwarding=on ipv6 2>/dev/null || true exit 0 #!/bin/sh /usr/sbin/ipadm set-prop -p forwarding=off ipv4 2>/dev/null || true /usr/sbin/ipadm set-prop -p forwarding=off ipv6 2>/dev/null || true exit 0 # Hotplug daemon — handles USB/SCSI/PCI hotplug events for userland. # Replaces /lib/svc/manifest/system/hotplug.xml. [service] name = "hotplug" description = "Hotplug daemon" type = "daemon" [exec] start = "/etc/zyginit/services/hotplug/start.sh" [dependencies] requires = ["filesystem"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] #!/bin/sh # Stale door file from a previous run prevents hotplugd from starting. /bin/rm -f /var/run/hotplugd_door exec /usr/lib/hotplugd # identity — Set hostname, domain, and hostid from /etc files [service] name = "identity" description = "Hostname, domain, hostid" type = "oneshot" [exec] start = "/etc/zyginit/services/identity/start.sh" [dependencies] requires = ["filesystem"] [restart] on = "never" #!/bin/zsh # identity — configure hostname, domain, hostid # # Reads /etc/nodename, /etc/defaultdomain, /etc/hostid. All optional — # missing files fall back to system default. Does NOT generate missing # hostid — that's a separate first-boot setup concern out of scope here. set -e set -u if [[ -r /etc/nodename ]]; then /bin/hostname "$(cat /etc/nodename)" fi # domainname is NIS-era and isn't shipped on Hammerhead by default. # /etc/defaultdomain is read by getdomainname(3C) directly when needed, # so just leave the file in place; nothing to invoke here. if [[ -r /etc/defaultdomain ]]; then : # /etc/defaultdomain present; getdomainname(3C) reads it directly fi # hostid is already loaded by the kernel from /etc/hostid; nothing to do # unless we later want to generate it on first boot. Flag for follow-up. exit 0 # Identity mapping daemon — translates between Windows SIDs and # UNIX UIDs/GIDs. Required for SMB sharing and Kerberos-authenticated # NFSv4. Off by default; enable when SMB or NFSv4 idmap is needed. # Replaces /lib/svc/manifest/system/idmap.xml. [service] name = "idmap" description = "Identity mapping daemon (idmapd)" type = "daemon" [exec] start = "/usr/lib/idmapd" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] [service] name = "inetd" description = "Internet services daemon" type = "daemon" [exec] start = "/usr/lib/inet/inetd start" # inetd refuses to run from the command line ("inetd is now an smf(7) # managed service"); SMF_FMRI tells it to proceed. environment = ["SMF_FMRI=svc:/network/inetd:default"] [dependencies] requires = ["rpcbind", "syslogd"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # IP packet filter (ipfilter) — loads firewall + NAT rules into the # kernel via ipf/ipnat. Off by default; enable after writing rules # into /etc/ipf/ipf.conf and /etc/ipf/ipnat.conf. # Replaces /lib/svc/manifest/network/ipfilter.xml. [service] name = "ipfilter" description = "IP packet filter (firewall/NAT)" type = "oneshot" [exec] start = "/etc/zyginit/services/ipfilter/start.sh" stop = "/etc/zyginit/services/ipfilter/stop.sh" [dependencies] requires = ["network"] [restart] on = "never" #!/bin/sh # Load ipfilter rules + NAT, then enable the filter. Skips loading if # the corresponding config file is missing — admin enables piecemeal. set -e /usr/sbin/ipf -E 2>/dev/null || true # enable filter (idempotent) [ -s /etc/ipf/ipf.conf ] && /usr/sbin/ipf -Fa -f /etc/ipf/ipf.conf [ -s /etc/ipf/ipf6.conf ] && /usr/sbin/ipf -6 -Fa -f /etc/ipf/ipf6.conf [ -s /etc/ipf/ipnat.conf ] && /usr/sbin/ipnat -CF -f /etc/ipf/ipnat.conf [ -s /etc/ipf/ippool.conf ] && /usr/sbin/ippool -F && /usr/sbin/ippool -f /etc/ipf/ippool.conf exit 0 #!/bin/sh /usr/sbin/ipf -D 2>/dev/null || true # disable filter /usr/sbin/ipf -Fa 2>/dev/null || true # flush rules /usr/sbin/ipnat -CF 2>/dev/null || true exit 0 # ipmgmtd — IP interface management daemon # # Runs in `-f` (foreground) mode, the ipmgmtd analogue of dlmgmtd's `-d`: # skips the daemonize fork AND the SMF_FMRI startup gate. Door at # /etc/svc/volatile/ipadm/ipmgmt_door is still created identically; # libipadm (used by ifconfig inet ADDR up and ipadm) connects # transparently. zyginit's contract tracks one PID, no parent-exit dance. [service] name = "ipmgmtd" description = "IP interface management daemon (foreground)" type = "daemon" [exec] start = "/lib/inet/ipmgmtd -f" [dependencies] requires = ["filesystem", "dlmgmtd"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # Consolidated IPsec configuration. Drives algorithm table, static SAs, # and policy database from one oneshot. The original SMF split (algs + # manual-key + policy) supports independent enabling of each piece; # Hammerhead bundles them and skips steps where the config file is # absent, covering every real combination cleanly. # # Replaces: # /lib/svc/manifest/network/ipsec/ipsecalgs.xml # /lib/svc/manifest/network/ipsec/manual-key.xml # /lib/svc/manifest/network/ipsec/policy.xml [service] name = "ipsec" description = "IPsec algorithms, SAs, and policy" type = "oneshot" [exec] start = "/etc/zyginit/services/ipsec/start.sh" stop = "/etc/zyginit/services/ipsec/stop.sh" [dependencies] requires = ["network"] [restart] on = "never" #!/bin/sh # IPsec setup. Three steps, executed only when their input is present: # 1. ipsecalgs -s — load algorithm table from /etc/inet/ipsecalgs # 2. ipseckey -f ... — install static SAs (skip if no keyfile) # 3. ipsecconf -q -a — install policy DB (skip if no config) set -e /usr/sbin/ipsecalgs -s [ -s /etc/inet/secret/ipseckeys ] && /usr/sbin/ipseckey -f /etc/inet/secret/ipseckeys [ -s /etc/inet/ipsecinit.conf ] && /usr/sbin/ipsecconf -q -a /etc/inet/ipsecinit.conf exit 0 #!/bin/sh # Tear down IPsec policy and static SAs. Algorithm table cannot be # unloaded without a reboot, so it stays. /usr/sbin/ipsecconf -F 2>/dev/null /usr/sbin/ipseckey flush 2>/dev/null exit 0 # Multicast DNS responder (Bonjour/zero-conf). Lets services advertise # and discover each other on the local link without DNS. # Replaces /lib/svc/manifest/network/dns/multicast.xml. [service] name = "mdnsd" description = "Multicast DNS responder" type = "daemon" [exec] start = "/usr/lib/inet/mdnsd" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # IPv6 Neighbor Discovery — sends/receives Router Solicitations and # Advertisements, drives autoconfig and prefix discovery. Required for # IPv6 stateless autoconfig on a typical network. # # Replaces /lib/svc/manifest/network/routing/ndp.xml. The SMF method # reads three optional properties (stateless_addr_conf, debug, # config_file); we use in.ndpd's defaults (config /etc/inet/ndpd.conf # if present, no -a/-d/-f). Admins who need flags swap out this TOML. [service] name = "ndp" description = "IPv6 Neighbor Discovery daemon" type = "daemon" [exec] start = "/usr/lib/inet/in.ndpd" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # IP Multipathing — runs in.mpathd to monitor IPMP group members and # move addresses on link failure. Off by default; enable when IPMP # groups are configured (`ipadm create-ipmp`). # Replaces /lib/svc/manifest/network/network-ipmp.xml. [service] name = "network-ipmp" description = "IP Multipathing daemon" type = "daemon" [exec] start = "/usr/lib/inet/in.mpathd" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # IP Quality of Service — loads /etc/inet/ipqosconf.conf via # ipqosconf -a. Oneshot; the QoS state lives in the kernel. # Replaces /lib/svc/manifest/network/network-ipqos.xml. [service] name = "network-ipqos" description = "IP QoS configuration" type = "oneshot" [exec] start = "/usr/sbin/ipqosconf -a /etc/inet/ipqosconf.conf" [dependencies] requires = ["network"] [restart] on = "never" # IP tunnel persistence — recreates dladm-managed tunnel links from # /etc/dladm/iptun.conf on boot. Off by default; populated when admin # creates tunnels via `dladm create-iptun`. # Replaces /lib/svc/manifest/network/network-iptun.xml. [service] name = "network-iptun" description = "Recreate IP tunnel links from persistent config" type = "oneshot" [exec] start = "/usr/sbin/dladm init-tunnels" [dependencies] requires = ["network"] [restart] on = "never" # network — Bring up loopback, physical interfaces, routes (BSD-pivot v2) # # Reads /etc/hostname. for each physical interface (line-oriented # format documented in start.sh) and /etc/defaultrouter for static # default routes. DHCP is handled via `ifconfig dhcp start`, which # forks /sbin/dhcpagent directly. # # Requires both dlmgmtd and ipmgmtd: modern illumos `ifconfig plumb` # resolves link names through dlmgmtd, and `ifconfig inet ADDR up` # goes through libipadm → ipmgmtd. `dladm init-phys` (in start.sh) # registers physical links with dlmgmtd at boot. # # Replaces the v1 SMF stack (network/loopback, network/physical, # network/initial, network/netmask, network/service, network/routing-setup) # with a single oneshot. inetd / fmd / rpcbind remain dropped. [service] name = "network" description = "Bring up network interfaces and routes (BSD-style)" type = "oneshot" [exec] start = "/etc/zyginit/services/network/start.sh" stop = "/etc/zyginit/services/network/stop.sh" [dependencies] requires = ["identity", "dlmgmtd", "ipmgmtd"] [restart] on = "never" #!/bin/zsh # network/start.sh — BSD-style interface/route bring-up (v2) # # No ipmgmtd, no inetd. Uses /sbin/ifconfig directly for plumbing, # /sbin/dhcpagent (forked by ifconfig dhcp start) for DHCP, and # /usr/sbin/route for static routes from /etc/defaultrouter. dlmgmtd is # required: modern illumos `ifconfig plumb` resolves names through # it, so we keep it but skip the rest of the SMF networking stack. # # Walks /etc/hostname. files (one per interface to configure). Each # file is line-oriented; see the case statement below for supported syntax. set -e set -u # Enable [[:space:]]## "one or more" repetition in pattern matching. setopt EXTENDED_GLOB IFCONFIG=/sbin/ifconfig DLADM=/sbin/dladm ROUTE=/usr/sbin/route # --- Force-load IP STREAMS modules --- # zyginit's tier ordering runs network/start.sh before SMF's traditional # autoload triggers fire, so socket(PF_INET) returns EAFNOSUPPORT until # the ip / ip6 drivers are explicitly loaded. modload is idempotent: # returns success on already-loaded modules. /sbin/modload /kernel/drv/ip || true /sbin/modload /kernel/drv/ip6 || true # --- Datalink registration --- # Tell dlmgmtd about every physical link the kernel attached after dlmgmtd # started (e.g. virtio NICs that came up via devfsadm). Without this, # `ifconfig vioif0 plumb` returns "no such interface" because dlmgmtd # can't translate the link name to a DLPI device. Mirrors what SMF's # network/physical method runs. $DLADM init-phys || true # --- Loopback --- # Plumb lo0 explicitly (the kernel auto-attaches it but we want predictable # state). `ifconfig lo0 plumb` is idempotent — repeats just succeed. $IFCONFIG lo0 plumb || true $IFCONFIG lo0 inet 127.0.0.1 netmask 255.0.0.0 up || true $IFCONFIG lo0 inet6 ::1/128 up || true # --- Physical interfaces --- # Each /etc/hostname. file marks an interface to configure (BSD style). # The configure_interface() helper plumbs the named link and applies the # directives in the file. configure_interface() { local iface="$1" local cfgfile="/etc/hostname.${iface}" if [[ ! -e "$cfgfile" ]]; then # No config — leave the interface untouched. (Operator can plumb # by-hand or add a config file and re-run; this matches OpenBSD.) return 0 fi # Plumb first; downstream lines may add addresses to it. $IFCONFIG "$iface" plumb || true # If the config file has no non-comment content, just bring it up. if ! grep -Eq '^[[:space:]]*[^#[:space:]]' "$cfgfile" 2>/dev/null; then $IFCONFIG "$iface" up return 0 fi # Walk the file line by line. while IFS= read -r line || [[ -n "$line" ]]; do # Strip comments, then leading and trailing whitespace. # `[[:space:]]##` is zsh extended glob for "one or more whitespace # chars" — covers tab-indented lines and multi-space margins that # an editor might insert. EXTENDED_GLOB is enabled at script top; # the `\#` escape is needed because EXTENDED_GLOB makes bare `#` a # pattern operator. line="${line%%\#*}" line="${line##[[:space:]]##}" line="${line%%[[:space:]]##}" [[ -z "$line" ]] && continue # Split on first whitespace into verb + rest. local verb="${line%%[[:space:]]*}" local rest="" if [[ "$line" == *[[:space:]]* ]]; then rest="${line#*[[:space:]]}" fi # ${=rest} forces word-splitting on $rest — necessary because # ifconfig args like `192.168.122.50/24 up` need to arrive as # separate arguments, not one quoted string. case "$verb" in dhcp) $IFCONFIG "$iface" dhcp start ;; inet) # `inet /` or `inet netmask ` $IFCONFIG "$iface" inet ${=rest} up ;; addif) $IFCONFIG "$iface" addif ${=rest} up ;; inet6) $IFCONFIG "$iface" inet6 ${=rest} up ;; up|down) $IFCONFIG "$iface" "$verb" ;; *) print -u2 "network: ignoring unknown directive in $cfgfile: $line" ;; esac done < "$cfgfile" } # Iterate /etc/hostname. files — the BSD-style marker for "configure # this interface." Iter 7 boot showed /dev/net/ enumeration silently # skipped vioif0 on Hammerhead (the directory either doesn't exist or # doesn't contain DLPI link nodes for virtio NICs); switching to # hostname.* lookup is both more portable and closer to OpenBSD's # original semantics. for cfg in /etc/hostname.*(N); do fname="${cfg:t}" # ${cfg:t} returns "hostname.vioif0"; strip the prefix. iface="${fname#hostname.}" # Defensively skip loopback (we already handled lo0 above). [[ "$iface" == "lo0" ]] && continue configure_interface "$iface" done # --- Static default routes --- # /etc/defaultrouter is one IPv4 address per line. DHCP-acquired routes # don't need this file; only set one when the operator opts in. if [[ -r /etc/defaultrouter ]]; then while IFS= read -r line || [[ -n "$line" ]]; do # Same trim treatment as the hostname. parser. `\#` is # escaped because EXTENDED_GLOB is set script-wide. line="${line%%\#*}" line="${line##[[:space:]]##}" line="${line%%[[:space:]]##}" [[ -z "$line" ]] && continue $ROUTE -n add default "$line" || true done < /etc/defaultrouter fi exit 0 #!/bin/zsh # network/stop.sh — reverse of start.sh (BSD-pivot v2) # # Releases DHCP leases and unplumbs all configured non-loopback interfaces. # Best-effort: each command may fail (e.g., interface already torn down, # dhcpagent already gone) and we continue regardless. ZFS sync happens # in main.reef after this runs. set -u # NOTE: no `set -e` — every step is best-effort during shutdown. IFCONFIG=/sbin/ifconfig # Release any DHCP leases first so the server marks them free. if [[ -d /dev/net ]]; then for dev in /dev/net/*(N); do link="${dev:t}" [[ "$link" == "lo0" ]] && continue # `dhcp release` is idempotent — silently no-op if no lease. $IFCONFIG "$link" dhcp release 2>/dev/null || true done fi # Kill the dhcpagent itself. It manages all leases globally, so one kill # tears down every interface's state at once. pkill -TERM -x dhcpagent 2>/dev/null || true # Unplumb. inet6 first (Hammerhead requires this ordering for clean teardown). if [[ -d /dev/net ]]; then for dev in /dev/net/*(N); do link="${dev:t}" [[ "$link" == "lo0" ]] && continue $IFCONFIG "$link" inet6 unplumb 2>/dev/null || true $IFCONFIG "$link" unplumb 2>/dev/null || true done fi exit 0 # NFSv4 callback daemon — receives delegation recall and other # server-initiated callbacks from NFSv4 servers. Required only on # NFSv4 *clients*. # Replaces /lib/svc/manifest/network/nfs/cbd.xml. [service] name = "nfs-cbd" description = "NFSv4 callback daemon (nfs4cbd)" type = "daemon" [exec] start = "/usr/lib/nfs/nfs4cbd" [dependencies] requires = ["rpcbind"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # NFS client mount oneshot — invokes the upstream nfs-client method # script that mounts NFS filesystems listed in /etc/vfstab and brings # up automounter integrations. Most useful when the four NFS client # daemons (cbd, mapid, statd, lockd) are also enabled. # Replaces /lib/svc/manifest/network/nfs/client.xml. [service] name = "nfs-client" description = "NFS client (mount NFS filesystems from vfstab)" type = "oneshot" [exec] start = "/lib/svc/method/nfs-client start" stop = "/lib/svc/method/nfs-client stop" [dependencies] requires = ["network", "nfs-mapid", "nfs-statd", "nfs-lockd"] [restart] on = "never" # NFS lock manager (RPC program 100021). Implements NLM v3 locking; # both client and server need it. Must wait for statd to register # before lockd can attach to it. # Replaces /lib/svc/manifest/network/nfs/nlockmgr.xml. [service] name = "nfs-lockd" description = "NFS lock manager (lockd)" type = "daemon" [exec] start = "/etc/zyginit/services/nfs-lockd/start.sh" [dependencies] requires = ["rpcbind", "nfs-statd"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] #!/bin/sh # Wait until statd has registered an RPC address. The kernel's NLM # code expects to find statd at attach time; without this wait, # lockd can fail noisily on first call. # # Use absolute paths for every command — first-boot-of-fresh-deploy can # transiently fail bare-command lookups under I/O pressure. See # [[zyginit-service-empty-path]] and the swap/start.sh precedent # (commit 87e9f19f69). PATH= kept as defense-in-depth. PATH=/usr/sbin:/usr/bin:/sbin:/bin export PATH i=0 while ! /usr/bin/rpcinfo -T tcp 127.0.0.1 status >/dev/null 2>&1; do i=$((i + 1)) [ $i -gt 30 ] && { /bin/echo "statd never registered after 30s" >&2 exit 1 } /bin/sleep 1 done exec /usr/lib/nfs/lockd # NFSv4 user/group ID mapping daemon. Maps numeric UID/GID to the # string form (`user@DOMAIN`) used over NFSv4. Required for NFSv4 # client *and* server. # Replaces /lib/svc/manifest/network/nfs/mapid.xml. [service] name = "nfs-mapid" description = "NFSv4 ID mapping daemon (nfsmapid)" type = "daemon" [exec] start = "/usr/lib/nfs/nfsmapid" [dependencies] requires = ["rpcbind"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # NFS server — runs nfsd + mountd. Wraps the upstream nfs-server # method script. Off by default; enable to share filesystems via NFS. # Replaces /lib/svc/manifest/network/nfs/server.xml. [service] name = "nfs-server" description = "NFS server (nfsd + mountd)" type = "daemon" [exec] start = "/lib/svc/method/nfs-server start" stop = "/lib/svc/method/nfs-server stop" [dependencies] requires = ["network", "nfs-mapid", "nfs-statd", "nfs-lockd"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # NFS lock status monitor (RPC program 100024). Tracks remote hosts # holding locks so they can be reclaimed after a client/server reboot. # Required for both NFS client and server lock semantics. # Replaces /lib/svc/manifest/network/nfs/status.xml. [service] name = "nfs-statd" description = "NFS lock status monitor (statd)" type = "daemon" [exec] start = "/usr/lib/nfs/statd" [dependencies] requires = ["rpcbind"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] [service] name = "pfexecd" description = "Profile exec daemon (RBAC privilege execution)" type = "daemon" [exec] start = "/usr/lib/pfexecd" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] [service] name = "powerd" description = "Power management daemon" type = "daemon" [exec] start = "/usr/lib/power/powerd" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # Resource capping daemon. Enforces RSS limits per project/zone using # `rcapadm`/`rcapstat` config. Independent of resource-mgmt; can run # alone or alongside it. # # Replaces /lib/svc/manifest/system/rcap.xml. [service] name = "rcapd" description = "Resource capping daemon" type = "daemon" [exec] start = "/usr/lib/rcap/rcapd" [dependencies] requires = ["filesystem"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # Resource management oneshot: enables the pools subsystem, commits # /etc/pooladm.conf if present, and applies global-zone resource # controls. Folds /lib/svc/manifest/system/pools.xml and # /lib/svc/manifest/system/resource-mgmt.xml into one boot-time step. # # rcapd (resource capping daemon) is its own service — see rcapd.toml. [service] name = "resource-mgmt" description = "Resource pools and global-zone controls" type = "oneshot" [exec] start = "/etc/zyginit/services/resource-mgmt/start.sh" stop = "/etc/zyginit/services/resource-mgmt/stop.sh" [dependencies] requires = ["filesystem"] [restart] on = "never" #!/bin/sh # Enable resource pools, commit config if present, apply global-zone # resource controls. Skip pieces whose binaries aren't installed # (resource pools is optional in zyginit deployments). set -e if [ -x /usr/sbin/pooladm ]; then /usr/sbin/pooladm -e if [ -f /etc/pooladm.conf ]; then /usr/sbin/pooladm -c || { /usr/sbin/pooladm -d /bin/echo "pooladm -c failed; pools subsystem disabled" >&2 exit 1 } fi fi # Apply global-zone resource controls (was the resource-mgmt oneshot). if [ -x /usr/sbin/zoneadm ] && [ -f /etc/zones/global.xml ]; then /usr/sbin/zoneadm -z global apply fi exit 0 #!/bin/sh # Tear down pools subsystem. if [ -x /usr/sbin/pooladm ]; then /usr/sbin/pooladm -x 2>/dev/null /usr/sbin/pooladm -d 2>/dev/null fi exit 0 # root-fs — Ensure root filesystem is mounted read-write # # Tier 0: no dependencies. First oneshot to run. # # On illumos with ZFS root, the kernel mounts the root dataset read-write # at boot via the bootloader; there is no remount step like UFS-era systems. # This service defensively asserts readonly=off on the active root dataset # so any prior bootloader/admin override is corrected before later tiers # write to /. # # Reverses at shutdown: stop.sh sets readonly=on as a defensive measure # before halt/poweroff/reboot so the next boot starts from a clean state. [service] name = "root-fs" description = "Ensure root filesystem is writable (ZFS root)" type = "oneshot" [exec] start = "/etc/zyginit/services/root-fs/start.sh" stop = "/etc/zyginit/services/root-fs/stop.sh" [runlevel] # Essential — required in both single-user and multi-user. mode = "always" [restart] on = "never" #!/bin/zsh # root-fs — ensure / is mounted read-write # # Hammerhead supports both ZFS-rooted and UFS-rooted systems. # # ZFS root: the kernel mounts / read-write at boot via the bootloader. # We defensively assert readonly=off on the active dataset # so an admin override (booted snapshot, recovery) is cleared # before later tiers write to /. # # UFS root: the kernel mounts / read-only first; this oneshot remounts # read-write so /var, /tmp, etc. can be written. This matches # the historical SMF system/filesystem/root method. # # We dispatch on the fstype reported in /etc/mnttab. set -e set -u ROOT_FSTYPE="$(awk '$2 == "/" { print $3 }' /etc/mnttab)" case "$ROOT_FSTYPE" in zfs) ROOT_DS="$(/sbin/zfs list -H -o name / 2>/dev/null || true)" if [[ -z "$ROOT_DS" ]]; then print -u2 "root-fs: could not determine root dataset; skipping readonly" exit 0 fi /sbin/zfs set readonly=off "$ROOT_DS" ;; ufs) /usr/sbin/mount -F ufs -o remount,rw / ;; "") print -u2 "root-fs: could not determine root fstype from /etc/mnttab" exit 1 ;; *) print -u2 "root-fs: unrecognized root fstype '$ROOT_FSTYPE'" exit 1 ;; esac exit 0 #!/bin/zsh # root-fs stop — final sync before halt/poweroff/reboot # # Originally this script set the ZFS root dataset readonly=on as a # defensive "clean state for next boot" measure. That was actively # wrong: it made / read-only BEFORE zyginit's outer shutdown sequence # finished (destroy_socket's unlink of /var/run/zyginit.sock, the # explicit zpool sync + umountall, etc.). Every subsequent write # failed silently, so the next boot saw stale state — most visibly # the un-removed /var/run/zyginit.sock that wedged zygctl post-reboot. # # ZFS is transactionally consistent — it doesn't need a "freeze" # before shutdown. zyginit's main shutdown sequence (`zpool sync`, # umountall -l, sync) plus the kernel's own vfs_syncall inside # uadmin's mdboot path is enough to guarantee txg commit before the # hardware reset. # # UFS root would still need remount,ro for clean fsck. Hammerhead's # BE is ZFS, so this script is effectively a no-op (just a final # `zpool sync` as defense in depth). Add UFS handling here if/when # UFS root is supported. set -u # Defense-in-depth: final zpool sync. zyginit's outer shutdown also # calls `zpool sync` post-shutdown_services; this covers the brief # window between filesystem/stop.sh's `umountall -l` and that call. /usr/sbin/zpool sync 2>/dev/null || true exit 0 [service] name = "rpcbind" description = "RPC portmapper" type = "daemon" [exec] start = "/usr/sbin/rpcbind" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # System Activity Reporter — runs sadc once at boot to seed the day's # data file. Periodic collection happens via cron (/var/spool/cron/ # crontabs/sys, commented out by default). Off by default; enable when # investigating system performance. # Replaces /lib/svc/manifest/system/sar.xml. [service] name = "sar" description = "System activity reporter (sadc seed at boot)" type = "oneshot" [exec] start = "/etc/zyginit/services/sar/start.sh" [dependencies] requires = ["filesystem"] [restart] on = "never" #!/bin/sh # Seed today's sadc data file. Mirrors the original svc-sar behavior # at run-level transition. Idempotent: if today's file already exists, # sadc just appends. /bin/mkdir -p /var/adm/sa exec /usr/lib/sa/sadc /var/adm/sa/sa$(/bin/date +%d) # single-user-shell — Interactive root shell on /dev/console for # single-user (recovery) mode. Activated only when g_runlevel == SINGLE, # either via -s in kernel boot args or `zygctl single` at runtime. # # Phase A: spawns /sbin/sh with no authentication. Physical console # access == root in this mode. This is intentional — the primary use # case is recovering from forgotten root passwords / borked configs, # which can't gate on the very things we're trying to fix. # # Phase B (future): swap start.sh to invoke /sbin/sulogin which # prompts for the root password and falls through to a root shell on # success. Single TOML edit; the architecture stays the same. [service] name = "single-user-shell" description = "Single-user-mode interactive root shell on /dev/console" type = "daemon" [exec] start = "/etc/zyginit/services/single-user-shell/start.sh" [runlevel] mode = "single" [dependencies] # Filesystem needs to be mounted so /var/log etc. are readable for # diagnosis. Everything beyond filesystem (network, ipmgmtd, dlmgmtd, # etc.) is multi-user only and not started in single-user. requires = ["root-fs", "filesystem"] [restart] # When the user types `exit`, supervisor respawns the shell — that's # the expected single-user UX. To leave single-user, run # `zygctl multi` (or `init 3`) which transitions runlevels and stops # this service before starting multi-user services. on = "always" delay = 0 [contract] # The shell forks children freely; don't let stray exits kill the # contract. We rely on `restart=always` to bring the shell back if # the immediate process dies. param = ["inherit", "noorphan"] fatal = ["hwerr"] #!/bin/sh # single-user-shell/start.sh — replace fd 0/1/2 with /dev/console and # exec the root shell. Supervisor sets up fd 1/2 to point at the per- # service log file by default; for an interactive recovery shell we # need the user's actual console terminal. The redirect happens BEFORE # exec so the shell inherits a controlling tty (zyginit's PID-1 holds # /dev/console with O_NOCTTY, leaving it available to claim). exec /dev/console 2>/dev/console exec /sbin/sh [service] name = "sshd" description = "Secure Shell daemon" type = "daemon" [exec] start = "/usr/lib/ssh/sshd -D" [dependencies] requires = ["syslogd", "pfexecd"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # swap — Enable swap devices from /etc/vfstab [service] name = "swap" description = "Enable swap devices" type = "oneshot" [exec] start = "/etc/zyginit/services/swap/start.sh" stop = "/etc/zyginit/services/swap/stop.sh" [runlevel] # Useful in single-user mode for tmpfs backing and emergency repair tools # that may want to spill memory. mode = "always" [dependencies] requires = ["root-fs"] [restart] on = "never" #!/bin/zsh # swap start — enable swap devices listed in /etc/vfstab # # Race: zvol device nodes under /dev/zvol/dsk// are created # asynchronously after `zpool import`. The root pool is imported by the # loader before zyginit runs, but the kernel still has to walk the # config and create minor nodes via /etc/devices machinery. On a fresh # alpha8 deploy (2026-05-29) the swap service ran ~40s before the zvol # symlink existed, causing `swapadd` to fail with "No such file or # directory" and bringing the service up as failed. # # Wait up to 30s for every swap entry's device path to appear before # invoking swapadd. nawk reads /etc/vfstab and emits each fstype=swap # device path. set -e set -u # zyginit spawns services with an empty PATH. /etc/zshenv sets one for # interactive shells, but `#!/bin/zsh` scripts don't always pick it up, # and the PATH= line below was *still* not enough on the 2026-06-03 # clean alpha8 deploy ("command not found: sleep" 8x in zygctl log swap) # — likely because zyginit's spawn env wins after the script's own # assignments somehow. Use absolute paths for every external command so # we never depend on PATH lookups inside zyginit-spawned scripts. PATH=/usr/sbin:/usr/bin:/sbin:/bin export PATH waitfor() { local path=$1 local tries=30 while (( tries-- > 0 )); do [[ -e $path ]] && return 0 /bin/sleep 1 done /bin/echo "swap: device not ready after 30s: $path" >&2 return 1 } # Skip comment lines and the special `swap` tmpfs backing entry # (fstype=swap, special=swap — used for /tmp). /bin/nawk '/^[^#]/ && $4=="swap" && $1 != "swap" { print $1 }' /etc/vfstab | while read dev; do waitfor "$dev" || exit 1 done exec /sbin/swapadd #!/bin/zsh # swap stop — disable all swap devices before halt/reboot set -e set -u # swap -l lists active entries with the device path in column 1 # (skipping the header line). Disable each. if /sbin/swap -l >/dev/null 2>&1; then /sbin/swap -l | awk 'NR>1 {print $1}' | while read dev; do /sbin/swap -d "$dev" || true done fi exit 0 # sysconfig — Misc one-time boot configuration (grab-bag) [service] name = "sysconfig" description = "Misc boot config: core, dump, keymap, scheduler, rctl, tmp cleanup" type = "oneshot" [exec] start = "/etc/zyginit/services/sysconfig/start.sh" [dependencies] requires = ["filesystem"] [restart] on = "never" #!/bin/zsh # sysconfig — misc one-time boot configuration # # Each command is deliberately wrapped in || true for commands where a # specific failure (no device present, no config file, etc.) is acceptable. # A truly mandatory setting would be its own service. # Use absolute paths for every external command — first-boot-of-fresh-deploy # can transiently fail bare-command lookups under I/O pressure (devfsadm + # reconfigure + parallel service start). See [[zyginit-service-empty-path]] # for the investigation that disproved the original "empty PATH" theory. # # The remaining failure mode for this script (sysconfig exit=-1 still # observed on 2026-06-08 fresh-deploy boots, with an empty service log) # is suspected to be inside coreadm/dumpadm/dispadmin/rctladm: they # exec() internal helpers that themselves do PATH-based lookup, and if # any of those misses, the parent hangs and zyginit eventually SIGKILLs # the oneshot (-> exit=-1). Diagnostic markers below pin down which # command was running when zyginit's timeout fired. PATH=/usr/sbin:/usr/bin:/sbin:/bin export PATH set -u # Note: NOT using set -e — individual failures are handled per-command. # --- Diagnostic step markers ------------------------------------------ # zyginit captures stderr to /var/log/zyginit/sysconfig.log. Emit a # timestamped marker before and after each suspect command so the LAST # marker in the log identifies the hang point on a fresh-deploy failure. # Cheap: ~14 lines on a happy-path boot. # Builtin `print` resolves at shell startup (no external lookup); /bin/date # is an absolute external path so it can't lose to a transient PATH race. mark() { print -r -- ">>> $(/bin/date +%T.%N) $*" >&2; } # Core files: enable per-process pattern, apply config from /etc/coreadm.conf mark "coreadm --init" /usr/bin/coreadm --init 2>/dev/null || true mark "coreadm done" # Crash dump: use configured dump device (set once per system, may be a zvol) mark "dumpadm -u" /sbin/dumpadm -u 2>/dev/null || true mark "dumpadm done" # Keyboard map: -i reads layout from /etc/default/kbd (non-interactive). # -s would prompt the operator at the console, blocking boot. if [[ -x /usr/bin/kbd ]]; then mark "kbd -i" /usr/bin/kbd -i 2>/dev/null || true mark "kbd done" fi # Scheduler default dispatch table (TS = time-share, usual default) mark "dispadmin -d TS" /usr/sbin/dispadmin -d TS 2>/dev/null || true mark "dispadmin done" # Resource controls (/etc/project-based rctls) — reload into kernel if [[ -x /usr/sbin/rctladm ]]; then mark "rctladm -u" /usr/sbin/rctladm -u 2>/dev/null || true mark "rctladm done" fi # Clean up /tmp on boot (tmpfs is usually empty after reboot but defend) if [[ -d /tmp ]]; then mark "tmp cleanup" /usr/bin/find /tmp -mindepth 1 -maxdepth 1 -exec /bin/rm -rf {} + 2>/dev/null || true mark "tmp cleanup done" fi # rpcbind needs /var/run/rpc_door (mode 1777) to exist before it starts # (matches the SMF rpc-bind method). Create it here so it's ready by the # time tier 4 brings up rpcbind. mark "rpc_door" /bin/mkdir -p /var/run/rpc_door /bin/chmod 1777 /var/run/rpc_door mark "exit 0" exit 0 [service] name = "syseventd" description = "Sysevent framework daemon" type = "daemon" [exec] start = "/usr/lib/sysevent/syseventd" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # Hammerhead override of services/hammerhead/syslogd.toml shipped by # zyginit. Forces syslogd to start *after* console-login so ttymon # claims /dev/console as ctty before any potential ctty-grabbing # daemon (notably syslogd, which on Hammerhead opens /dev/log via # STREAMS and ends up with TT=console). # # Pairs with zyginit bug 001 (supervisor leaves /dev/console as # inherited fd 0 across setsid). Once that's fixed upstream this # override can be dropped. [service] name = "syslogd" description = "System log daemon" type = "daemon" [exec] start = "/etc/zyginit/services/syslogd/start.sh" [dependencies] requires = ["network", "console-login"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] #!/bin/sh # syslogd — wrapper that ensures /var/adm/messages exists before launch. # # syslogd opens its log targets (per /etc/syslog.conf) at startup; if any # named file doesn't exist, it logs an error and skips that target. On # Hammerhead the default conf points logs at /var/adm/messages, but the # bare BE we boot from has only rotated archives (messages.0, messages.1) # — not messages itself. Pre-create it so syslogd's startup is clean. set -e [ -e /var/adm/messages ] || { /usr/bin/touch /var/adm/messages /bin/chmod 644 /var/adm/messages /bin/chown root:root /var/adm/messages } exec /usr/sbin/syslogd [service] name = "utmpd" description = "utmpx monitor daemon" type = "daemon" [exec] start = "/usr/lib/utmpd" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # Virtual ARP daemon — handles overlay-network MAC/IP lookups for # VXLAN-style virtual networks. Off by default; enable when overlay # networking is configured. Replaces /lib/svc/manifest/network/varpd.xml. [service] name = "varpd" description = "Virtual ARP daemon (overlay networks)" type = "daemon" [exec] start = "/usr/lib/varpd/varpd" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # Text login on virtual terminal 3 (reachable with Ctrl-Alt-F3). # # A copy of console-login bound to /dev/vt/3 instead of /dev/console, giving a # text-console fallback alongside the graphical greeter. ttymon self-claims # /dev/vt/3 as its ctty (opens it as session leader, no O_NOCTTY), so no # zyginit [tty] feature is needed. Requires vtdaemon: it configures the VTs # (VT_CONFIG) and arbitrates the Ctrl-Alt-F# hotkey switch to reach this VT. [service] name = "vt3-login" description = "Text login on VT3 (Ctrl-Alt-F3)" type = "daemon" [exec] start = "/etc/zyginit/services/vt3-login/start.sh" [dependencies] requires = ["utmpd", "vtdaemon"] [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] [restart] on = "failure" delay = 5 max_retries = 3 #!/bin/zsh # Text getty on /dev/vt/3. write_login_utmpx (shared with console-login) writes # the LOGIN_PROCESS utmpx entry that illumos login(1) requires; ttymon updates # but never creates it. Then exec ttymon so it becomes the supervised process. /etc/zyginit/services/console-login/write_login_utmpx v3 vt/3 $$ exec /usr/lib/saf/ttymon \ -g \ -d /dev/vt/3 \ -l console \ -m ldterm,ttcompat \ -T sun-color \ -h \ -p "hammerhead vt3 login: " # Text login on virtual terminal 4 (reachable with Ctrl-Alt-F4). # # A copy of console-login bound to /dev/vt/4 instead of /dev/console, giving a # text-console fallback alongside the graphical greeter. ttymon self-claims # /dev/vt/4 as its ctty (opens it as session leader, no O_NOCTTY), so no # zyginit [tty] feature is needed. Requires vtdaemon: it configures the VTs # (VT_CONFIG) and arbitrates the Ctrl-Alt-F# hotkey switch to reach this VT. [service] name = "vt4-login" description = "Text login on VT4 (Ctrl-Alt-F4)" type = "daemon" [exec] start = "/etc/zyginit/services/vt4-login/start.sh" [dependencies] requires = ["utmpd", "vtdaemon"] [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] [restart] on = "failure" delay = 5 max_retries = 3 #!/bin/zsh # Text getty on /dev/vt/4. write_login_utmpx (shared with console-login) writes # the LOGIN_PROCESS utmpx entry that illumos login(1) requires; ttymon updates # but never creates it. Then exec ttymon so it becomes the supervised process. /etc/zyginit/services/console-login/write_login_utmpx v4 vt/4 $$ exec /usr/lib/saf/ttymon \ -g \ -d /dev/vt/4 \ -l console \ -m ldterm,ttcompat \ -T sun-color \ -h \ -p "hammerhead vt4 login: " # vtdaemon — virtual terminal daemon. # # Configures the kernel VT subsystem (VT_CONFIG / nodecount) and arbitrates VT # switching. REQUIRED for two things on Hammerhead: # 1. X/greeter bring-up: Xorg's xf86OpenConsole does VT_ACTIVATE + VT_WAITACTIVE # on a freshly-allocated VT; VT_WAITACTIVE sleeps forever unless the VT # subsystem is configured, which vtdaemon does. Without vtdaemon, Xorg # started with no controlling terminal (i.e. by zyginit) hangs at # "using VT number 2". (Confirmed 2026-07-24.) # 2. Ctrl-Alt-F# hotkey switching: the kernel DETECTS the chord and door-upcalls # vtdaemon, which issues the VT_ACTIVATE. No vtdaemon => no hotkey switching. # # type=daemon, unmodified: main() (vtdaemon.c:1204) never forks — it closes # fds 0-2, setsid()s, opens /dev/vt/1, and runs the door server in the # FOREGROUND until signalled. NOT oneshot (never exits -> would spin the drain # timeout). NO user=/group=: it calls priv_isfullset() and exits 1 without full # privileges, so it must run as root. [service] name = "vtdaemon" description = "Virtual terminal daemon (VT config + switch arbitration)" type = "daemon" [exec] start = "/etc/zyginit/services/vtdaemon/start.sh" [dependencies] # Logs via syslog (openlog) after closing fds 0-2 — its own log file is empty # by design, so syslogd must be up. requires = ["syslogd"] [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] [restart] on = "failure" delay = 5 max_retries = 3 #!/bin/zsh # vtdaemon start hook. Runs in the foreground (vtdaemon.c main() closes fds 0-2, # setsid()s, opens /dev/vt/1, and runs the door server until signalled — no # fork), so zyginit supervises it directly as a daemon. # # -c 6 sets nodecount=6 (VT_CONFIG, applied only when >= 2). NOTE: nodecount # COUNTS vt0 (the VT manager), so -c N enables usable consoles vt1..vt(N-1). # -c 6 => vt1..vt5: vt1 system console, vt2 X greeter, vt3/vt4/vt5 text gettys. # Runs as root (no priv drop): vtdaemon needs a full privilege set # (priv_isfullset) or exits 1. exec /usr/lib/vtdaemon -c 6 # WPA supplicant for Wi-Fi. Off by default — admin enables per-radio # after configuring credentials. Replaces /lib/svc/manifest/network/wpa.xml. [service] name = "wpa" description = "WPA supplicant for Wi-Fi" type = "daemon" [exec] start = "/usr/lib/inet/wpad" [dependencies] requires = ["network"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"] # Zones boot/halt — at boot, walks /etc/zones/index and boots zones # whose autoboot=true. At shutdown, halts running zones gracefully # (init 0 first, halt as fallback). Off by default; enable when zones # are configured. Replaces /lib/svc/manifest/system/zones.xml. [service] name = "zones" description = "Zone boot/halt orchestration" type = "oneshot" [exec] start = "/etc/zyginit/services/zones/start.sh" stop = "/etc/zyginit/services/zones/stop.sh" [dependencies] requires = ["filesystem", "network"] [restart] on = "never" #!/bin/sh # Boot installed zones whose autoboot property is true; sysboot the # rest. Trimmed-down version of upstream svc-zones (drops SMF FMRI # tracking and waits). set -e [ -x /usr/sbin/zoneadm ] || exit 0 # zoneadm not installed [ -f /etc/zones/index ] || exit 0 # no zone configs /bin/egrep -vs '^#|^global:' /etc/zones/index || exit 0 # only global zone for zone in $(/usr/sbin/zoneadm list -pi | /bin/nawk -F: '$3=="installed"{print $2}'); do if /usr/sbin/zonecfg -z "$zone" info autoboot 2>/dev/null | /bin/grep -q true; then /bin/echo "Booting zone: $zone" /usr/sbin/zoneadm -z "$zone" boot & else /usr/sbin/zoneadm -z "$zone" sysboot & fi done wait exit 0 #!/bin/sh # Halt running non-global zones. Tries init 0 first (graceful, 60s), # then `zoneadm halt` (forced). # # zyginit spawns services with an empty PATH — set one explicitly so # the wait-loop's `sleep 1` actually delays. See swap/start.sh for # the same fix and the 2026-06-01 root-cause notes. PATH=/usr/sbin:/usr/bin:/sbin:/bin export PATH [ -x /usr/sbin/zoneadm ] || exit 0 zonelist=$(/usr/sbin/zoneadm list -p | /bin/nawk -F: '$2!="global"{print $2}') [ -z "$zonelist" ] && exit 0 for z in $zonelist; do /usr/sbin/zlogin "$z" /sbin/init 0 2>/dev/null || true done # Wait up to 60s for zones to settle, then halt the remainder. i=0 while [ $i -lt 60 ]; do remaining=$(/usr/sbin/zoneadm list -p | /bin/nawk -F: '$2!="global"&&$3=="running"{print $2}') [ -z "$remaining" ] && exit 0 i=$((i + 1)) /bin/sleep 1 done for z in $(/usr/sbin/zoneadm list -p | /bin/nawk -F: '$2!="global"&&$3=="running"{print $2}'); do /usr/sbin/zoneadm -z "$z" halt 2>/dev/null || true done exit 0 # Zone statistics daemon — collects per-zone resource utilization for # `zonestat(8)`. Useful with zones; harmless otherwise (just sits idle). # Replaces /lib/svc/manifest/system/zonestat.xml. [service] name = "zonestat" description = "Zone statistics daemon" type = "daemon" [exec] start = "/usr/lib/zones/zonestatd" [dependencies] requires = ["filesystem"] [restart] on = "failure" delay = 5 max_retries = 3 [contract] param = ["inherit", "noorphan"] fatal = ["hwerr"]