From f3d4d0962f4bf30de6c5f9423ce78b62dc78a442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:31:12 +0200 Subject: [PATCH 1/2] feat: publish Android binaries with jniLibs names Expose libmicroinit.so and libshutdown.so in arm64 artifacts and OCI layers while accepting legacy artifact names during the transition. Co-authored-by: Cursor --- .github/workflows/ci.yml | 10 ++++++++-- scripts/build-android.sh | 16 +++++++++++++++- scripts/publish-oci-microinit-android.sh | 23 ++++++++++++----------- scripts/retag-oci-microinit-android.sh | 23 ++++++++++++++++------- 4 files changed, 51 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb28e63..2bd60a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -235,7 +235,7 @@ jobs: - name: Verify Bionic linkage run: | set -euo pipefail - for f in dist/microinit-android-* dist/shutdown-android-*; do + for f in dist/libmicroinit.so dist/libshutdown.so dist/microinit-android-* dist/shutdown-android-*; do [ -f "$f" ] || continue file "$f" if ! file "$f" | grep -qi 'android\|linker64\|linker'; then @@ -288,7 +288,13 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Publish microinit-android-arm64 - run: ./scripts/publish-oci-microinit-android.sh dist/microinit-android-arm64 dist/shutdown-android-arm64 + run: | + set -euo pipefail + microinit=dist/libmicroinit.so + shutdown=dist/libshutdown.so + [[ -f "${microinit}" ]] || microinit=dist/microinit-android-arm64 + [[ -f "${shutdown}" ]] || shutdown=dist/shutdown-android-arm64 + ./scripts/publish-oci-microinit-android.sh "${microinit}" "${shutdown}" - name: Job summary run: | diff --git a/scripts/build-android.sh b/scripts/build-android.sh index 6007f52..840d811 100755 --- a/scripts/build-android.sh +++ b/scripts/build-android.sh @@ -124,8 +124,22 @@ build_one() { cp -f "target/${rust_target}/release/microinit" "dist/${dist_micro}" cp -f "target/${rust_target}/release/shutdown" "dist/${dist_shut}" chmod 755 "dist/${dist_micro}" "dist/${dist_shut}" + + # Android jniLibs require native libraries to use the lib*.so convention. + # Keep arch-specific release artifacts for GitHub Release compatibility. + if [[ "${rust_target}" == "aarch64-linux-android" ]]; then + cp -f "dist/${dist_micro}" dist/libmicroinit.so + cp -f "dist/${dist_shut}" dist/libshutdown.so + chmod 755 dist/libmicroinit.so dist/libshutdown.so + fi + file "dist/${dist_micro}" "dist/${dist_shut}" || true - echo "wrote dist/${dist_micro} dist/${dist_shut}" + if [[ "${rust_target}" == "aarch64-linux-android" ]]; then + file dist/libmicroinit.so dist/libshutdown.so || true + echo "wrote dist/${dist_micro} dist/${dist_shut} dist/libmicroinit.so dist/libshutdown.so" + else + echo "wrote dist/${dist_micro} dist/${dist_shut}" + fi } for a in "${ARCHES[@]}"; do diff --git a/scripts/publish-oci-microinit-android.sh b/scripts/publish-oci-microinit-android.sh index 45cd0fb..0beb623 100755 --- a/scripts/publish-oci-microinit-android.sh +++ b/scripts/publish-oci-microinit-android.sh @@ -1,12 +1,12 @@ #!/usr/bin/env bash # Publish microinit Android/arm64 OCI artifact to GHCR (ORAS). # Intended for CI on push to main/master only. -# Usage: publish-oci-microinit-android.sh [shutdown-android-arm64] +# Usage: publish-oci-microinit-android.sh [libshutdown.so|shutdown-android-arm64] # # Tags: main, sha-<7> set -euo pipefail -BIN="${1:?usage: $0 [shutdown-android-arm64]}" +BIN="${1:?usage: $0 [libshutdown.so|shutdown-android-arm64]}" SHUTDOWN="${2:-}" IMAGE="${MICROINIT_OCI_ANDROID_IMAGE:-ghcr.io/dcc-bigfred/microinit-android-arm64}" BIN_MEDIA_TYPE="application/vnd.dcc-bigfred.microinit.android.arm64.v1" @@ -29,11 +29,12 @@ tmpdir="$(mktemp -d)" cleanup() { rm -rf "${tmpdir}"; } trap cleanup EXIT -cp -f "${BIN}" "${tmpdir}/microinit-android-arm64" -chmod 755 "${tmpdir}/microinit-android-arm64" +# Publish jniLibs-compatible layer names even when a legacy artifact is passed. +cp -f "${BIN}" "${tmpdir}/libmicroinit.so" +chmod 755 "${tmpdir}/libmicroinit.so" layers=( - "microinit-android-arm64:${BIN_MEDIA_TYPE}" + "libmicroinit.so:${BIN_MEDIA_TYPE}" ) if [[ -n "${SHUTDOWN}" ]]; then @@ -41,9 +42,9 @@ if [[ -n "${SHUTDOWN}" ]]; then echo "error: shutdown binary not found: ${SHUTDOWN}" >&2 exit 1 fi - cp -f "${SHUTDOWN}" "${tmpdir}/shutdown-android-arm64" - chmod 755 "${tmpdir}/shutdown-android-arm64" - layers+=("shutdown-android-arm64:${SHUTDOWN_MEDIA_TYPE}") + cp -f "${SHUTDOWN}" "${tmpdir}/libshutdown.so" + chmod 755 "${tmpdir}/libshutdown.so" + layers+=("libshutdown.so:${SHUTDOWN_MEDIA_TYPE}") fi annotate=( @@ -54,9 +55,9 @@ annotate=( ) echo "Publishing ${IMAGE}:main and :${SHA_TAG}" -echo " microinit: $(wc -c < "${tmpdir}/microinit-android-arm64") bytes" -if [[ -f "${tmpdir}/shutdown-android-arm64" ]]; then - echo " shutdown: $(wc -c < "${tmpdir}/shutdown-android-arm64") bytes" +echo " libmicroinit.so: $(wc -c < "${tmpdir}/libmicroinit.so") bytes" +if [[ -f "${tmpdir}/libshutdown.so" ]]; then + echo " libshutdown.so: $(wc -c < "${tmpdir}/libshutdown.so") bytes" fi ( cd "${tmpdir}" diff --git a/scripts/retag-oci-microinit-android.sh b/scripts/retag-oci-microinit-android.sh index 741bf31..a9a4f21 100755 --- a/scripts/retag-oci-microinit-android.sh +++ b/scripts/retag-oci-microinit-android.sh @@ -33,20 +33,29 @@ find_layer() { return 1 } -BIN_NAME="$(find_layer microinit-android-arm64)" || true +BIN_NAME="$(find_layer libmicroinit.so || find_layer microinit-android-arm64)" || true if [[ -z "${BIN_NAME}" ]]; then - echo "error: expected microinit-android-arm64 in OCI artifact, found:" >&2 + echo "error: expected libmicroinit.so (or legacy microinit-android-arm64) in OCI artifact, found:" >&2 find "${tmpdir}" -type f >&2 exit 1 fi +# Normalize legacy layers to the jniLibs-compatible OCI layer names. +if [[ "${BIN_NAME}" != "libmicroinit.so" ]]; then + cp -f "${tmpdir}/${BIN_NAME}" "${tmpdir}/libmicroinit.so" + chmod 755 "${tmpdir}/libmicroinit.so" +fi push_args=( - "${BIN_NAME}:${BIN_MEDIA_TYPE}" + "libmicroinit.so:${BIN_MEDIA_TYPE}" ) -SHUTDOWN_NAME="$(find_layer shutdown-android-arm64)" || true +SHUTDOWN_NAME="$(find_layer libshutdown.so || find_layer shutdown-android-arm64)" || true if [[ -n "${SHUTDOWN_NAME}" ]]; then - push_args+=("${SHUTDOWN_NAME}:${SHUTDOWN_MEDIA_TYPE}") + if [[ "${SHUTDOWN_NAME}" != "libshutdown.so" ]]; then + cp -f "${tmpdir}/${SHUTDOWN_NAME}" "${tmpdir}/libshutdown.so" + chmod 755 "${tmpdir}/libshutdown.so" + fi + push_args+=("libshutdown.so:${SHUTDOWN_MEDIA_TYPE}") fi annotate=( @@ -57,9 +66,9 @@ annotate=( ) echo "Publishing ${IMAGE}:${RELEASE_TAG} and :latest-release" -echo " microinit: $(wc -c < "${tmpdir}/${BIN_NAME}") bytes" +echo " libmicroinit.so: $(wc -c < "${tmpdir}/libmicroinit.so") bytes" if [[ -n "${SHUTDOWN_NAME}" ]]; then - echo " shutdown: $(wc -c < "${tmpdir}/${SHUTDOWN_NAME}") bytes" + echo " libshutdown.so: $(wc -c < "${tmpdir}/libshutdown.so") bytes" fi ( cd "${tmpdir}" From 37709ff8a0aedc1aeb282e87fed8c344df47f7fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20K=C4=99ska?= <372403+keskad@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:38:17 +0200 Subject: [PATCH 2/2] docs: expand operator guides with lifecycle, config, and supervise examples Add configuration, service-lifecycle, and using-as-supervisord pages for administrators. Slim down operator.md, de-emphasize product-specific branding, and link the new guides from the docs index and README. Co-authored-by: Cursor --- README.md | 7 +- docs/README.md | 5 +- docs/api.md | 3 +- docs/architecture.md | 11 +- docs/configuration.md | 230 +++++++++++++++++++++++++++++++++++ docs/operator.md | 130 ++++++++------------ docs/service-lifecycle.md | 186 ++++++++++++++++++++++++++++ docs/using-as-supervisord.md | 195 +++++++++++++++++++++++++++++ 8 files changed, 680 insertions(+), 87 deletions(-) create mode 100644 docs/configuration.md create mode 100644 docs/service-lifecycle.md create mode 100644 docs/using-as-supervisord.md diff --git a/README.md b/README.md index d9b8a7a..9646842 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ microinit

-Lightweight PID 1 init system and service supervisor designed for BigFred OS. +Lightweight PID 1 init system and service supervisor for embedded Linux and containers. Works in embedded systems based on Linux as well as in containers. Lightning fast and solid-rock reliable. Inspired by supervisord and Kubernetes, handles dependencies, able to self-heal. @@ -131,6 +131,9 @@ MIT See **[docs/README.md](docs/README.md)** for the full index. -- [Operator guide](docs/operator.md) — manage services, write config, dependencies, boot +- [Operator guide](docs/operator.md) — everyday CLI and boot +- [Configuration](docs/configuration.md) — JSON, drop-ins, hot reload +- [Service lifecycle](docs/service-lifecycle.md) — states and dependency behaviour at boot +- [Using as supervisord](docs/using-as-supervisord.md) — PHP-FPM + NGINX example - [Control socket API](docs/api.md) — Unix socket protocol for integrations - [Architecture](docs/architecture.md) — design overview (`init` / `supervise`, reload, OTel) diff --git a/docs/README.md b/docs/README.md index acf14b9..3c8a08a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,10 @@ Guides and references for running and integrating **microinit**. | Document | Audience | Contents | |----------|----------|----------| -| [Operator guide](operator.md) | Linux admins / device operators | Managing services from the shell, writing JSON config, dependencies, boot sequence | +| [Operator guide](operator.md) | Linux admins / device operators | Everyday CLI, boot, quick config overview | +| [Configuration](configuration.md) | Administrators | JSON files, drop-ins in subfolders, hot reload, service fields | +| [Service lifecycle](service-lifecycle.md) | Administrators | States over time; dependency when a service restarts at boot | +| [Using as supervisord](using-as-supervisord.md) | Container / VM admins | `supervise` mode; PHP-FPM + NGINX with drop-ins and hot reload | | [Control socket API](api.md) | Integrators / UI / scripts | Unix socket framing, request/response JSON | | [Architecture](architecture.md) | Developers | Design overview: `init` vs `supervise`, reload, OTel, distribution | | [Developer index](developer.md) | Contributors / embedders | Doc map + Go SDK pointer | diff --git a/docs/api.md b/docs/api.md index 4193a14..f0736d2 100644 --- a/docs/api.md +++ b/docs/api.md @@ -333,6 +333,7 @@ For `logs` with `follow: true`, keep reading frames in a loop (each frame has it ## Further reading -- [Operator guide](operator.md) — day-to-day CLI and config +- [Operator guide](operator.md) — day-to-day CLI +- [Configuration](configuration.md) — JSON files and hot reload - [Architecture](architecture.md) — internals - [Documentation index](README.md) diff --git a/docs/architecture.md b/docs/architecture.md index aa25792..1e8c9eb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -8,7 +8,7 @@ This document explains **why** microinit exists, **how** it is designed, and **w **microinit** is a small program that on a device (or in a container) **starts and supervises services** — networking, databases, applications — according to a list stored in JSON files. It can also act as a classic system init (Linux process number 1). -It is not a full systemd. It aims to be **lightweight, predictable, and easy to embed** in a system image (for example BigFred OS) and in containers. +It is not a full systemd. It aims to be **lightweight, predictable, and easy to embed** in custom Linux system images and in containers. --- @@ -18,7 +18,7 @@ The same supervisor logic runs under two thin wrappers: | | `microinit init` | `microinit supervise` | |---|---|---| -| Typical use | Embedded / hub system (PID 1) | Container, distroless | +| Typical use | Embedded / custom Linux (PID 1) | Container, distroless | | Early-boot (mounts, etc.) | Yes | No | | Console login (getty) | Yes, when PID 1 | No | | Logs on TTYs (tty2 / tty3) | Yes | No (in-memory ring + IPC / optional files) | @@ -175,11 +175,10 @@ With `panic = abort` in release, metrics code must avoid panics — a failure in Two independent Linux artifacts, plus an Android binary bundle: -1. **ORAS (linux)** — static arm64 binaries (`microinit` + `shutdown`) plus early-boot and unmount scripts for the hub system image, e.g. `ghcr.io/dcc-bigfred/microinit-linux-arm64`. +1. **ORAS (linux)** — static arm64 binaries (`microinit` + `shutdown`) plus early-boot and unmount scripts for system images (see project README for registry URLs). 2. **ORAS (android)** — supervise-only Bionic binaries (`microinit` + `shutdown`, built - with `--no-default-features`), e.g. `ghcr.io/dcc-bigfred/microinit-android-arm64`. - GitHub Releases also ship `armv7` and `x86_64`. -3. **Distroless container image** (amd64 + arm64) — `ghcr.io/dcc-bigfred/microinit`, default `ENTRYPOINT /microinit` + `CMD supervise`. + with `--no-default-features`). GitHub Releases also ship `armv7` and `x86_64`. +3. **Distroless container image** (amd64 + arm64) — default `ENTRYPOINT /microinit` + `CMD supervise` (see README for the current image name). Tag lifecycle: `main` / `sha-<7>` on push, `v*` / `latest-release` on release. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..2bfb31a --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,230 @@ +# Configuration + +This page explains **where** microinit reads settings, **how** to describe a service in JSON, and **what happens when you save a file** (hot reload). No Rust required — only a text editor. + +--- + +## Where files live + +By default durable state lives under **`/data`**. Override with **`DATA_DIR`** (absolute path, e.g. `/data`). + +| File or folder | Purpose | +|----------------|---------| +| `$DATA_DIR/etc/microinit.json` | Main file — service list and global options | +| `$DATA_DIR/etc/microinit.services.enabled-override.json` | Written by `microinit enable` / `disable` | +| `$DATA_DIR/etc/microinit.d/services/**/*.json` | **Drop-ins** — extra or overriding services | +| `$DATA_DIR/etc/microinit.json.example` | Example (created if missing) | + +A system image may **seed** `/data/etc/microinit.json` from `/etc/microinit/microinit.json` during early-boot — only if `/data` does not already have its own copy. + +With **`microinit supervise --config /path/microinit.json`**, drop-ins sit next to that file: + +```text +/etc/microinit/microinit.json +/etc/microinit/microinit.d/services/ +/etc/microinit/microinit.services.enabled-override.json +``` + +--- + +## Drop-ins (files in subfolders) + +You do not have to put every service in one big `microinit.json`. Extra JSON under **`microinit.d/services/`** is loaded automatically. + +**Rules:** + +1. Any `**/*.json` in that tree (subfolders are fine). +2. Files sorted **alphabetically** (full path). +3. Same service **`name`** — the **later** file wins. +4. Add a new service or replace fields of an existing one. + +**Example layout:** + +```text +/data/etc/ + microinit.json + microinit.d/ + services/ + base/network.json + web/ + php-fpm.json + nginx.json +``` + +`web/nginx.json` can set `"dependsOn": ["php-fpm"]` while `php-fpm` lives in `web/php-fpm.json`. Folder order matters only when the **same** service name appears twice. + +**Tip:** folder names like `10-base/`, `20-web/` control override order. + +--- + +## Main file (skeleton) + +```json +{ + "version": 1, + "socket": "/run/microinit.sock", + "console": "/dev/tty1", + "logs": { + "tty": "/dev/tty2", + "initTty": "/dev/tty3", + "lines": 300, + "logToFiles": false, + "dir": "/data/logs" + }, + "services": [] +} +``` + +| Field | Meaning | +|-------|---------| +| `version` | Schema version (use `1`) | +| `socket` | Control socket for `microinit list`, UI, scripts | +| `console` | Boot console and getty (init mode) | +| `logs.tty` | Service logs (init mode) | +| `logs.initTty` | microinit’s own messages | +| `logs.logToFiles` | If `true`, also files under `$DATA_DIR/logs/` | +| `openTelemetry` | Optional metrics (see README) | + +Most operators only edit **`services`**. + +--- + +## One service entry + +### Long-running service (daemon) + +```json +{ + "name": "myapp", + "enabled": true, + "daemon": true, + "restart": true, + "restartBackoff": 2, + "startWaitSecs": 1, + "shutdownWaitSecs": 5, + "dependsOn": ["network"], + "cmd": "/etc/init.d/myapp", + "cwd": "/" +} +``` + +With `cmd`, microinit runs `cmd start`, `cmd stop`, `cmd restart`. + +Or explicit commands: + +```json +"startCmd": "/usr/sbin/myapp --config /data/etc/myapp.conf", +"stopCmd": "killall myapp" +``` + +**Important:** start should **`exec`** the program in the **foreground** so microinit can track the process and restart on crash. A script that backgrounds a daemon and exits makes microinit think everything is fine with no PID tracked. + +### One-shot job (at boot) + +```json +{ + "name": "network", + "daemon": false, + "restart": false, + "cmd": "/etc/init.d/network" +} +``` + +Success → `succeeded`. Failure → `failed`. + +### Fields — short reference + +| Field | Role | +|-------|------| +| `name` | Unique name (CLI, `dependsOn`) | +| `enabled` | `false` = do not start at boot | +| `daemon` | `true` = long-lived; `false` = one-shot | +| `restart` | Retry after crash (daemon only) | +| `restartBackoff` | Seconds before each retry | +| `startWaitSecs` | After start, wait; if process dies in window → `failed` | +| `shutdownWaitSecs` | After stop, wait then `SIGKILL` | +| `background` | Parallel start at boot | +| `dependsOn` | These must be `running` or `succeeded` first | +| `livenessProbe` | Optional health check; failure triggers restart | + +### Liveness probe + +Exactly **one** of `cmd`, `httpUrl`, or `tcpAddr`: + +```json +"livenessProbe": { + "httpUrl": "http://127.0.0.1:8080/health", + "httpAcceptedCodes": [200], + "interval": 30, + "timeout": 5 +} +``` + +Defaults: `interval` 60 s, `timeout` 5 s. + +--- + +## Enable / disable without editing JSON + +```bash +microinit disable grafana +microinit enable grafana +``` + +The override file wins over `"enabled"` in JSON. + +--- + +## Hot reload (save file → apply) + +microinit watches JSON via **inotify** (no periodic disk scanning). On save: + +1. Short pause (~300 ms) — one save often emits several events. +2. Load and merge (main + drop-ins + override). +3. **Invalid JSON** → old config kept; warning in logs. +4. **Valid JSON** → diff services: + - new → start (with `dependsOn`) + - removed → stop + - definition changed → restart + - `enabled` toggled → start or stop + +**Reboot is usually not needed.** After saving: + +```bash +microinit list +``` + +### What does **not** hot-reload + +Requires **microinit restart** (on PID 1 hosts: reboot): + +- `socket` path +- `logs.*` (TTYs, `logToFiles`, buffer size) +- `console` + +--- + +## Dependencies + +```json +"dependsOn": ["network", "redis"] +``` + +A service starts when every listed name is **`running`** or **`succeeded`**. Until then: **`waiting_for_dependency`** — it starts **on its own** when ready. + +`microinit stop` while waiting **cancels** the wait — fixing the dependency does **not** start the service without `microinit start`. + +```bash +microinit start --force myapp # debugging only +``` + +Boot example with restarts: [Service lifecycle](service-lifecycle.md). + +--- + +## Further reading + +- [Operator guide](operator.md) — everyday commands +- [Using as supervisord](using-as-supervisord.md) — PHP-FPM + NGINX +- [Service lifecycle](service-lifecycle.md) +- `man/man5/microinit.json.5.mdoc` — full field list diff --git a/docs/operator.md b/docs/operator.md index 200525d..f07be36 100644 --- a/docs/operator.md +++ b/docs/operator.md @@ -1,20 +1,24 @@ # Operator guide -This guide is for people who already know a Linux shell and need to **run and maintain services** under microinit on a device (or in a container). You do not need to know Rust. +For someone who knows basic Linux (SSH, editing files, systemd-style thinking) but **does not need to know** microinit or Rust. Day-to-day work: status, start/stop, boot. + +**More detail:** + +- [Configuration](configuration.md) — JSON files, drop-ins, hot reload +- [Service lifecycle](service-lifecycle.md) — states over time, dependencies at boot +- [Using as supervisord](using-as-supervisord.md) — PHP-FPM + NGINX in a container --- ## What microinit does -microinit is the program that: - -1. Starts a list of services from a JSON file -2. Keeps long-running services alive (optional restart on crash) -3. Lets you start/stop/enable/disable services and read their logs +1. Starts services from JSON config +2. Keeps daemons alive (optional restart after crash) +3. Lets you start/stop/enable/disable and read logs from the shell -On BigFred OS it is usually **PID 1** (`/sbin/init`). In a container you often run `microinit supervise` instead. +On embedded or custom Linux systems it often runs as **PID 1** (`/sbin/init`). In a container: **`microinit supervise`** (supervisord-like). -Default durable data lives under **`/data`**. You can point that elsewhere with the environment variable **`DATA_DIR`** (must be an absolute path). +Settings and logs usually live under **`/data`**. Another root: **`DATA_DIR`** (absolute path only). --- @@ -26,15 +30,14 @@ The control socket defaults to `$DATA_DIR/run/microinit.sock` (hub: `/data/run/m microinit list # name, state, pid, restarts, enabled, live_fail microinit list --show-labels # same + LABELS column microinit list -l created-by=bigfred # filter (AND if -l repeated) -microinit describe redis # deps, events, labels microinit describe redis # deps, reverse deps, graph, recent events microinit start redis -microinit start --force alloy # start even if dependsOn are not ready +microinit start --force alloy # start even if dependsOn are not ready microinit stop redis microinit restart redis microinit enable dropbear microinit disable dropbear -microinit logs # mixed recent lines +microinit logs microinit logs redis --follow microinit logs redis --lines 100 ``` @@ -45,43 +48,29 @@ microinit logs redis --lines 100 | State | Meaning | |-------|---------| -| `running` | Daemon process is up (PID tracked) | -| `succeeded` | One-shot job finished successfully | -| `failed` | Start failed or process exited badly | +| `running` | Daemon is up (PID shown) | +| `succeeded` | One-shot finished OK | +| `failed` | Start or exit error | | `stopped` | Stopped on purpose | -| `disabled` | Not allowed to start (`enabled: false`) | -| `waiting_for_dependency` | Start requested; waiting for `dependsOn` | -| `starting` / `restarting` | Transition in progress | - ---- - -## Configuration files - -### Main file - -**`$DATA_DIR/etc/microinit.json`** (usually `/data/etc/microinit.json`) - -If the file is missing at first boot, microinit can create an empty one and an example. On BigFred OS the image often **seeds** this file from `/etc/microinit/microinit.json` during early-boot (only if `/data` does not already have a copy). - -Editing `/data/etc/microinit.json` is the normal way to change what runs on a given device. +| `disabled` | Not allowed to start | +| `waiting_for_dependency` | Waiting for `dependsOn` | +| `starting` / `restarting` | In progress | -### Enable/disable override +Full example with restarts: [Service lifecycle](service-lifecycle.md). -**`$DATA_DIR/etc/microinit.services.enabled-override.json`** - -Written when you run `microinit enable` / `disable`. It only stores `true`/`false` per service name and wins over the `enabled` field in the main JSON. You rarely edit this by hand. - -### Drop-ins - -**`$DATA_DIR/etc/microinit.d/services/**/*.json`** +--- -Extra or overriding service definitions. Files are merged in **path sort order**; for the same service `name`, a **later** file wins. Useful for site-specific add-ons without rewriting the whole base config. +## Configuration (short) -### Hot-reload +| What | Where | +|------|--------| +| Main list | `$DATA_DIR/etc/microinit.json` | +| Enable/disable override | `$DATA_DIR/etc/microinit.services.enabled-override.json` | +| Extra services | `$DATA_DIR/etc/microinit.d/services/**/*.json` | -Saving any of those JSON files is picked up automatically (inotify). Invalid JSON keeps the previous config and logs a warning. +Edit JSON, **save** — hot reload (no reboot in most cases). Invalid JSON is ignored. -Changes to **socket path**, **log TTYs**, and **log-to-files** options need a **full microinit restart** (reboot on a hub). +Details: [Configuration](configuration.md). --- @@ -175,37 +164,22 @@ Example: Redis needs the network service first. "dependsOn": ["network"] ``` -What happens: - -1. You (or boot) request start of `redis`. -2. If `network` is not yet `running`/`succeeded`, redis goes to **`waiting_for_dependency`**. -3. When `network` becomes ready, redis **starts by itself**. -4. If you **`stop`** redis while it is waiting, that wait is cancelled. Later, when network is up, redis will **not** auto-start until you `start` it again. - -To start anyway (debugging): +If `network` is not `running` or `succeeded`, the service stays in **`waiting_for_dependency`** and starts **on its own** when ready. Manual **`stop`** cancels the wait. ```bash -microinit start --force redis +microinit start --force redis # debugging only ``` -You will see a short message on stdout, for example: - -- `redis: waiting for dependencies (network)` -- `redis: starting with --force (unmet dependencies: network)` -- `redis: starting` - --- -## Boot sequence (init mode) - -Typical hub boot: +## Boot sequence (init mode as PID 1) 1. Kernel starts `/sbin/init` (microinit). -2. **Early-boot** script runs (mount `/data`, seed configs, remount root RO, …). -3. Config is loaded from disk (**after** early-boot). -4. Enabled services start (order respects `dependsOn`; `background: true` services start in parallel). -5. Console shows `[ OK ]` / `[ FAIL ]` style status; getty on the console TTY. -6. Control socket listens; config files are watched for reload. +2. **Early-boot** (mount `/data`, seed config, …). +3. Config loaded from disk. +4. Enabled services start (`dependsOn` order; `background: true` in parallel). +5. Console `[ OK ]` / `[ FAIL ]`; getty. +6. IPC socket; JSON files watched for reload. On shutdown in **`init`** mode (`shutdown -r`, IPC `shutdown`, SIGTERM, …): services stop in reverse dependency order, then the **unmount** script runs (unbind mounts / umount `/data`), then reboot or power-off. @@ -215,32 +189,31 @@ In **`supervise`** mode there is no early-boot, getty, late unmount, or machine | Where | What | |-------|------| -| `/dev/tty2` (default) | Service stdout/stderr | -| `/dev/tty3` (default) | microinit’s own messages | -| `microinit logs …` | Same rings over the socket | -| `$DATA_DIR/logs/` | Optional files if `logs.logToFiles` is true | +| `/dev/tty2` | Service stdout/stderr | +| `/dev/tty3` | microinit messages | +| `microinit logs …` | Same via socket | +| `$DATA_DIR/logs/` | Files when `logs.logToFiles: true` | --- -## Common operator tasks +## Common tasks -**See why something did not start** +**Why did it not start?** ```bash microinit list microinit logs nameofservice --lines 50 -# also check /dev/tty3 ``` -**Temporarily disable a service across reboots** +**Disable across reboots** ```bash microinit disable grafana ``` -**Change config and apply** +**Apply a config change** -Edit `/data/etc/microinit.json` (or a drop-in), save — wait a moment for reload — then `microinit list`. +Edit a file under `/data/etc/`, save, wait a moment, `microinit list`. **Service dies and stays dead** @@ -250,6 +223,9 @@ Check `restartPolicy` and that microinit is tracking a real PID (`list` shows a ## Further reading -- [Control socket API](api.md) — for scripts and UIs that talk to the socket directly -- [Architecture](architecture.md) — design background -- [Documentation index](README.md) +- [Configuration](configuration.md) +- [Service lifecycle](service-lifecycle.md) +- [Using as supervisord](using-as-supervisord.md) +- [Control socket API](api.md) — scripts and UI +- [Architecture](architecture.md) — for developers +- [Documentation index](README.md) diff --git a/docs/service-lifecycle.md b/docs/service-lifecycle.md new file mode 100644 index 0000000..a401887 --- /dev/null +++ b/docs/service-lifecycle.md @@ -0,0 +1,186 @@ +# Service lifecycle + +This page shows **what happens over time** — not just a list of states, but a realistic boot story: one service keeps crashing and restarting, and another waits until the first one is finally healthy. + +--- + +## States you will see + +| State | In plain words | +|-------|----------------| +| `starting` | Start command is running | +| `running` | Daemon is up; microinit tracks a PID | +| `succeeded` | One-shot job finished OK | +| `failed` | Start failed or bad exit | +| `stopped` | Stopped on purpose | +| `disabled` | `enabled: false` | +| `waiting_for_dependency` | Should start, but `dependsOn` is not ready | +| `restarting` | Crashed; waiting `restartBackoff` before retry | +| `stopping` | Stop in progress | + +Quick check: + +```bash +microinit list +``` + +Columns **`restarts`** and **`live_fail`** help spot unstable services. + +--- + +## Example: dependency + restart loop at boot + +Scenario: + +- **`database`** — a daemon that sometimes fails on cold boot (disk, port). It has `restart: true` and `restartBackoff: 3`. +- **`webapp`** — needs the database. It has `dependsOn: ["database"]`. + +Short config: + +```json +{ + "services": [ + { + "name": "database", + "daemon": true, + "restart": true, + "restartBackoff": 3, + "startWaitSecs": 2, + "startCmd": "/usr/sbin/mydatabase --foreground" + }, + { + "name": "webapp", + "daemon": true, + "restart": true, + "restartBackoff": 2, + "startWaitSecs": 1, + "dependsOn": ["database"], + "startCmd": "/usr/sbin/webapp" + } + ] +} +``` + +### Timeline (what the admin sees) + +```text +Boot + │ + ├─ database: starting + │ └─ quick crash → failed, then restarting (restarts=1) + │ + ├─ webapp: waiting_for_dependency (database not running yet) + │ + ├─ … 3 seconds (restartBackoff) … + │ + ├─ database: starting (2nd attempt) + │ └─ crash again → restarting (restarts=2) + │ + ├─ webapp: still waiting_for_dependency + │ + ├─ … 3 seconds … + │ + ├─ database: starting (3rd attempt) + │ └─ stays up → running ✓ + │ + └─ webapp: dependency ready → starting → running ✓ +``` + +After the **second restart** (third start attempt) the database finally runs — then **webapp starts on its own**, without `microinit start webapp`. + +microinit retries `waiting_for_dependency` about every 200 ms in the background. No manual step needed. + +### `microinit list` mid-boot + +After the first database crash: + +```text +database failed - 1 true 0 +webapp waiting_for_dependency - 0 true 0 +``` + +When the database is up: + +```text +database running 1234 2 true 0 +webapp running 1240 0 true 0 +``` + +(`restarts=2` on database = two crashes before the successful run.) + +--- + +## Diagram + +```mermaid +sequenceDiagram + participant Boot + participant DB as database + participant WA as webapp + + Boot->>DB: start (attempt 1) + Boot->>WA: start requested + WA-->>WA: waiting_for_dependency + DB-->>DB: crash → restarting + Note over DB: wait 3s + DB->>DB: start (attempt 2) + DB-->>DB: crash → restarting + Note over DB: wait 3s + DB->>DB: start (attempt 3) + DB-->>DB: running + WA->>WA: deps ready → starting + WA-->>WA: running +``` + +--- + +## One-shot dependency + +If **`network`** is one-shot (`daemon: false`) and exits 0 → state **`succeeded`**. That counts as “ready” for `dependsOn`. + +If the one-shot **fails** (`failed`), dependents keep waiting until you fix and restart the dependency (or use `--force`). + +--- + +## When waiting does **not** resume on its own + +| You did | Result | +|---------|--------| +| Boot / `microinit start webapp` | Waits, then starts | +| `microinit stop webapp` while waiting | Stays **stopped** — fixing database does **not** start webapp | +| `microinit disable webapp` | **disabled** until `enable` | + +--- + +## Crash after a successful start + +If **`webapp`** is already **`running`** and **`database`** crashes: + +- database goes through restarting / running (if `restart: true`); +- **webapp is not stopped automatically** — microinit does not cascade-stop dependents when a dependency dies. + +If webapp must die with the database, that belongs in the app or a `livenessProbe` on webapp. + +--- + +## Debugging + +```bash +microinit list +microinit logs database --lines 80 +microinit logs webapp --lines 80 +# on a full system, also /dev/tty3 +``` + +Force start (debugging only): + +```bash +microinit start --force webapp +``` + +--- + +## Further reading + +- [Configuration](configuration.md) — `dependsOn`, `restart`, hot reload +- [Operator guide](operator.md) diff --git a/docs/using-as-supervisord.md b/docs/using-as-supervisord.md new file mode 100644 index 0000000..56758c1 --- /dev/null +++ b/docs/using-as-supervisord.md @@ -0,0 +1,195 @@ +# Using as supervisord + +**`microinit supervise`** is for **containers** and **servers** without full PID-1 init (no early-boot, no getty). It does what **supervisord** does: keep processes alive, start/stop from the CLI, read logs. + +When **`microinit init`** runs as PID 1 (`/sbin/init`) on a host system. In Docker or on a VM: + +```bash +microinit supervise --config /etc/microinit/microinit.json +``` + +Build locally or pull a published image (see project README). Example: + +```bash +docker run --rm -v "$PWD/config:/etc/microinit" microinit:main +``` + +--- + +## Example: PHP-FPM + NGINX + +Classic stack: **PHP-FPM** on port 9000, **NGINX** serves HTTP and talks to PHP-FPM. NGINX must start **after** PHP-FPM. + +Config split into: + +- a small **main** file (socket, logs) +- **drop-ins** in subfolders (easy to add more services) +- **hot reload** — edit JSON, save, no container restart + +### File layout + +```text +/etc/microinit/ + microinit.json + microinit.d/ + services/ + web/ + php-fpm.json + nginx.json +``` + +### Main file — `microinit.json` + +```json +{ + "version": 1, + "socket": "/run/microinit.sock", + "logs": { + "lines": 500, + "logToFiles": true, + "dir": "/var/log/microinit" + }, + "services": [] +} +``` + +Services live only in drop-ins — the main file stays short. + +### PHP-FPM — `microinit.d/services/web/php-fpm.json` + +```json +{ + "services": [ + { + "name": "php-fpm", + "enabled": true, + "daemon": true, + "restart": true, + "restartBackoff": 2, + "startWaitSecs": 1, + "shutdownWaitSecs": 10, + "dependsOn": [], + "startCmd": "exec /usr/sbin/php-fpm8.2 --nodaemonize --fpm-config /etc/php8/php-fpm.conf", + "stopCmd": "killall php-fpm8.2", + "cwd": "/", + "livenessProbe": { + "tcpAddr": "127.0.0.1:9000", + "interval": 30, + "timeout": 3 + } + } + ] +} +``` + +- **`exec`** and **`--nodaemonize`** — PHP-FPM in the foreground; microinit tracks the PID. +- **`livenessProbe`** — checks port 9000. + +### NGINX — `microinit.d/services/web/nginx.json` + +```json +{ + "services": [ + { + "name": "nginx", + "enabled": true, + "daemon": true, + "restart": true, + "restartBackoff": 2, + "startWaitSecs": 1, + "shutdownWaitSecs": 10, + "dependsOn": ["php-fpm"], + "startCmd": "exec /usr/sbin/nginx -g 'daemon off;'", + "stopCmd": "nginx -s quit", + "cwd": "/", + "livenessProbe": { + "httpUrl": "http://127.0.0.1:8080/health", + "httpAcceptedCodes": [200], + "interval": 30, + "timeout": 5 + } + } + ] +} +``` + +- **`dependsOn: ["php-fpm"]`** — NGINX stays in `waiting_for_dependency` until PHP-FPM is `running`. +- **`daemon off`** — NGINX in the foreground (supervisord-style). + +--- + +## Hot reload in practice + +microinit watches the main file and everything under `microinit.d/services/**/*.json`. + +### Add a service without restarting the container + +e.g. `microinit.d/services/web/extra-app.json`: + +```json +{ + "services": [ + { + "name": "extra-app", + "daemon": true, + "restart": true, + "startWaitSecs": 1, + "dependsOn": ["php-fpm"], + "startCmd": "exec /usr/sbin/my-extra-daemon" + } + ] +} +``` + +Save the file. Within about a second: + +```bash +microinit list +``` + +`extra-app` should start (after `php-fpm` is ready). + +### Change PHP-FPM settings + +Edit `php-fpm.json` — microinit detects a definition change and **restarts** `php-fpm`. NGINX keeps running unless you change its JSON too or run `microinit restart nginx`. + +### Invalid JSON + +Broken JSON keeps the **previous** config. Check: + +```bash +microinit logs --lines 20 +``` + +--- + +## Everyday commands + +```bash +microinit list +microinit restart nginx +microinit stop php-fpm +microinit logs nginx --follow +``` + +Socket defaults to `/run/microinit.sock`; use `--socket` on the daemon and clients if needed. + +--- + +## supervise vs init + +| | `supervise` | `init` (host PID 1) | +|---|-------------|---------------------| +| Early-boot, `/data` | No | Yes | +| Getty | No | Yes | +| Logs on tty2/tty3 | No (ring + files + IPC) | Yes | +| Supervision + hot reload | Yes | Yes | +| `shutdown -r` | Stop services, exit | Stop, umount, reboot | + +--- + +## Further reading + +- [Configuration](configuration.md) — drop-ins, JSON fields +- [Service lifecycle](service-lifecycle.md) — waiting on dependencies +- [Operator guide](operator.md)