an init system
  • C++ 72.2%
  • Python 27.8%
Find a file
2026-08-27 23:06:37 +01:00
etc more things 2026-08-27 22:50:44 +01:00
src lockdown on some validation 2026-08-27 23:06:37 +01:00
tools more things 2026-08-27 22:50:44 +01:00
.clang-format more things 2026-08-27 22:50:44 +01:00
.gitignore add stuff 2026-08-27 19:08:08 +01:00
configure.py more things 2026-08-27 22:50:44 +01:00
LICENSE Initial commit 2026-08-28 01:52:19 +09:00
README.md more things 2026-08-27 22:50:44 +01:00

bajia

an init system (PID 1) for embedded / VM targets, written in C++20 and configured with a small declarative language inspired by Android's init .rc format.

Status

  • .rc config parser (services + on-trigger action blocks)
  • Supervisor event loop built on signalfd + epoll
  • service spawn / reap / respawn (per-service restart policy)
  • action commands: start, stop, restart, exec, mkdir, chmod, chown, setenv, write, symlink, mount, log
  • logger with a ring buffer that flushes to the console once available

roadmap:

  • SIGCHLD crash-window limiting (rate-limited restarts; crash-threshold/ crash-window are parsed but not yet enforced by the reaper)
  • dependency ordering between services
  • property triggers (property:<k>=<v>) and setprop/getprop
  • per-service logging to files
  • reboot/poweroff path with ordered unmount
  • readiness/socket activation

building

requires a C++20 compiler and Ninja

python3 configure.py     # generates build/build.ninja
ninja -C build           # produces build/bajia

configure.py also writes a thin Makefile convenience wrapper (make, make clean, make format, --asan, --debug).

running

As a real init, the kernel must launch it as PID 1:

init=/path/to/bajia

Or by hand against a config (useful for development, may not behave like a real boot). bajia normally refuses to start unless it is PID 1; pass --run-as-user to override:

./build/bajia --run-as-user etc/init.rc

If no files are given it looks for /etc/bajia/init.rc.

configuration language

See etc/init.rc for a complete example.

services

service NAME /path/to/exe [args...]
    user = root|other       # uid after the privilege drop (name or number)
    group = GROUP [GROUP...]  # primary gid + supplementary groups (names/numbers)
    oneshot                 # run once and exit, never respawn
    disabled                # not started by the boot sequence
    console                 # bind stdio to /dev/console
    class = NAME            # grouping (default "default")
    respawn = never|on-failure|always   # restart policy (default always)
    crash-threshold = N     # restarts allowed per window
    crash-window = SECS
    seclabel = CONTEXT      # SELinux exec context (--selinux build)
    setenv = K=V            # extra environment (repeatable)
    cwd = /path

actions

on TRIGGER
    start NAME | stop NAME | restart NAME
    exec /cmd args...
    mkdir PATH [mode]
    chmod PATH mode
    chown PATH uid gid
    setenv K V
    write PATH CONTENT
    symlink TARGET LINK
    mount SOURCE TARGET FSTYPE
    log message

boot triggers fire in order: early-init, init, boot. shutdown triggers fire when the system is winding down. property/service-* triggers are on the roadmap.

Services run as root by default; user/group trigger a full privilege drop (supplementary groups, then gid, then uid) before exec.

control

A running init listens on an abstract unix socket (@bajia). The bundled bctl client drives it:

bctl status              # list services + state
bctl start NAME          # start a service
bctl stop NAME           # graceful stop (SIGTERM)
bctl restart NAME        # restart a service
bctl trigger EVENT       # fire an action trigger
bctl reload              # re-parse init.rc and reconcile services
bctl shutdown [poweroff|reboot]

reload (also kill -HUP 1) re-parses the rc files: removed services are stopped, added services registered, and running services whose definition changed are restarted with the new definition. A parse error rejects the reload and keeps the live config.

SELinux (experimental)

SELinux support is opt-in (configure.py --selinux, adds -DBAJIA_SELINUX

  • -lselinux). With it enabled, PID 1 mounts selinuxfs, loads the policy from /etc/selinux/config, calls selinux_restorecon on the core tree, and applies a per-service exec label via the seclabel = CONTEXT service option.

To bring up a policy without hand-writing one, reuse the host's installed policy (Fedora/SELinux hosts have one at /etc/selinux/<type>/) in permissive mode:

python3 tools/run_vm.py --selinux

This bundles the host policy.policy.<vers> and file_contexts into the initramfs, writes SELINUX=permissive, and boots selinux=1 enforcing=0. Watch the serial console for selinux: policy loaded, enforcing=0; a selinux-probe service prints the runtime exec contexts:

probe-ctx=system_u:system_r:init_t:s0 init-ctx=system_u:system_r:kernel_t:s0

The guest kernel must support SELinux and the policy version must match the kernel's (cat /sys/fs/selinux/policyvers). Once permissive is stable, read avc: denied lines from dmesg and iterate toward a minimal custom policy with checkpolicy/audit2allow, then flip to enforcing=1.

Limitations: uses the dynamic libselinux (no static build on Fedora), so the --selinux init is dynamically linked and the loader + libs (libselinux, libpcre2-8, glibc) are bundled into the initramfs.