- C++ 72.2%
- Python 27.8%
| etc | ||
| src | ||
| tools | ||
| .clang-format | ||
| .gitignore | ||
| configure.py | ||
| LICENSE | ||
| README.md | ||
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
.rcconfig 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:
SIGCHLDcrash-window limiting (rate-limited restarts;crash-threshold/crash-windoware parsed but not yet enforced by the reaper)- dependency ordering between services
- property triggers (
property:<k>=<v>) andsetprop/getprop - per-service logging to files
reboot/poweroffpath 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, callsselinux_restoreconon the core tree, and applies a per-service exec label via theseclabel = CONTEXTservice 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.