Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Guides and references for running and integrating **microinit**.
| [Operator guide](operator.md) | Linux admins / device operators | Managing services from the shell, writing JSON config, dependencies, boot sequence |
| [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 |
| [Go SDK](sdk/golang.md) | Go integrators | `client` / `config` / `supervise` with examples |

Also see the man pages in the repository:

Expand Down
8 changes: 5 additions & 3 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,14 @@ Operators normally use the companion `shutdown` binary (`shutdown -r now`, …)
"pid": 1234,
"restarts": 0,
"liveness_failures": 0,
"enabled": true
"enabled": true,
"labels": { "created-by": "bigfred" }
}
]
}
```

`pid` may be `null` when not tracked. `liveness_failures` counts how many times `livenessProbe` failed since boot (or since the service was added on reload).
`pid` may be `null` when not tracked. `liveness_failures` counts how many times `livenessProbe` failed since boot (or since the service was added on reload). `labels` is omitted when empty; keys come from the service config / drop-in.

### `status`

Expand All @@ -193,7 +194,8 @@ Operators normally use the companion `shutdown` binary (`shutdown -r now`, …)
"pid": 1234,
"restarts": 0,
"liveness_failures": 0,
"enabled": true
"enabled": true,
"labels": { "created-by": "bigfred" }
}
}
```
Expand Down
37 changes: 37 additions & 0 deletions docs/developer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Developer documentation

Index of docs for people changing or integrating **microinit**.

## Guides

| Document | Contents |
|----------|----------|
| [Architecture](architecture.md) | `init` vs `supervise`, reload, OTel, distribution |
| [Control socket API](api.md) | Unix socket framing and JSON messages |
| [Operator guide](operator.md) | Shell usage, JSON config, boot sequence |
| [Documentation index](README.md) | Operator-oriented entry point |

## SDKs

| Document | Language | Contents |
|----------|----------|----------|
| [Go SDK](sdk/golang.md) | Go | `client`, `config`, `supervise` — embed or control microinit |

## Source layout (Go)

```
go/
go.mod # module github.com/dcc-bigfred/microinit/go
client/ # IPC client
config/ # ServiceDef + drop-ins
supervise/ # EnsureRunning / Shutdown host
README.md
```

Version tags: `go/vX.Y.Z`. See [Go SDK](sdk/golang.md) for import examples.

## Man pages / examples

- `man/man8/microinit.8.mdoc` — CLI
- `man/man5/microinit.json.5.mdoc` — config fields
- [`examples/microinit.json.example`](../examples/microinit.json.example)
11 changes: 7 additions & 4 deletions docs/operator.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ The control socket defaults to `$DATA_DIR/run/microinit.sock` (hub: `/data/run/m

```bash
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
Expand Down Expand Up @@ -91,7 +94,7 @@ Minimal long-running service (foreground binary — preferred so microinit can t
"name": "myapp",
"enabled": true,
"daemon": true,
"restart": true,
"restartPolicy": "onError",
"restartBackoff": 2,
"startWaitSecs": 1,
"shutdownWaitSecs": 5,
Expand All @@ -114,14 +117,14 @@ Or set explicit commands:
"stopCmd": "killall myapp"
```

If `startCmd` is set, it is used instead of `cmd start`. Prefer **`exec` of the real process in the foreground** in the start script so `killall` / crashes are visible to microinit and `restart: true` works.
If `startCmd` is set, it is used instead of `cmd start`. Prefer **`exec` of the real process in the foreground** in the start script so `killall` / crashes are visible to microinit and `restartPolicy` works.

### Important fields (plain language)

| Field | Role |
|-------|------|
| `daemon` | `true` = long-lived; `false` = one-shot job |
| `restart` | Restart after crash (daemons only) |
| `restartPolicy` | `always` / `onError` (default) / `none` — auto-restart (daemons only) |
| `restartBackoff` | Seconds to wait before restarting |
| `startWaitSecs` | After start, wait this long; if the process dies in that window → `failed`. Use `1` (or more) when the start command **stays** as the service process |
| `shutdownWaitSecs` | After stop, wait then `SIGKILL` |
Expand Down Expand Up @@ -241,7 +244,7 @@ Edit `/data/etc/microinit.json` (or a drop-in), save — wait a moment for reloa

**Service dies and stays dead**

Check `restart: true` and that microinit is tracking a real PID (`list` shows a PID). Scripts that background with `start-stop-daemon -b` and exit leave microinit thinking the service is fine with no PID — prefer foreground `exec`.
Check `restartPolicy` and that microinit is tracking a real PID (`list` shows a PID). Scripts that background with `start-stop-daemon -b` and exit leave microinit thinking the service is fine with no PID — prefer foreground `exec`.

---

Expand Down
205 changes: 205 additions & 0 deletions docs/sdk/golang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# Go SDK

Embed or control [microinit](https://github.com/dcc-bigfred/microinit) from Go.

## Module

```
github.com/dcc-bigfred/microinit/go
```

Tag releases as `go/vX.Y.Z` (required because the module path ends with `/go`).

```bash
go get github.com/dcc-bigfred/microinit/go@go/v0.3.0
```

Private repos: `GOPRIVATE=github.com/dcc-bigfred/*`.

Local monorepo:

```go
replace github.com/dcc-bigfred/microinit/go => ../microinit/go
```

## Packages

| Package | Import path | Role |
|---------|-------------|------|
| **client** | `…/go/client` | IPC to a running daemon |
| **config** | `…/go/config` | `ServiceDef`, drop-in read/write, labels helpers |
| **supervise** | `…/go/supervise` | Join or spawn `microinit supervise` inside your process |

Default control socket: `/data/run/microinit.sock` (`client.DefaultSocket`).

## Labels

Service configs may include `labels` (`map[string]string`). Convention for embedders:

```go
svc := config.WithCreatedBy(config.ServiceDef{
Name: "worker", StartCmd: "exec /usr/bin/worker",
}, "my-app")
// writes labels: {"created-by":"my-app"}

// Filter a List() result:
for _, s := range list {
if config.MatchLabels(s.Labels, map[string]string{config.LabelCreatedBy: "my-app"}) {
fmt.Println(s.Name)
}
}
```

CLI: `microinit list -l created-by=my-app` and `microinit list --show-labels`.

## Design: process vs product policy

`supervise.Host` only manages the **daemon process**:

- join an existing socket, or spawn one supervise instance
- `Shutdown` only if **this** Host spawned the process

Stopping services, tracking “owned” drop-ins, refusing system service names, Redis/Alloy templates — that stays in the application (e.g. bigfred).

```mermaid
flowchart LR
app[Your app]
host[supervise.Host]
cfg[config drop-ins]
cli[client IPC]
mi[microinit process]
app --> host
app --> cfg
host --> cli
host -->|spawn or join| mi
cfg -->|JSON files| mi
cli --> mi
```

## Example: IPC only (admin UI)

```go
package main

import (
"fmt"
"log"

"github.com/dcc-bigfred/microinit/go/client"
)

func main() {
c := &client.Client{Socket: client.DefaultSocket}
list, err := c.List()
if err != nil {
log.Fatal(err)
}
for _, s := range list {
fmt.Printf("%s %s\n", s.Name, s.State)
}
if err := c.Control("redis", "restart"); err != nil {
log.Fatal(err)
}
}
```

## Example: embed microinit in your process

```go
package main

import (
"context"
"log"
"os"
"os/signal"

"github.com/dcc-bigfred/microinit/go/config"
"github.com/dcc-bigfred/microinit/go/supervise"
)

func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()

data := "/data" // or your DATA_DIR
h := supervise.New(
data+"/run/microinit.sock",
"microinit",
data+"/etc/microinit.json",
data+"/etc/microinit.d/services",
)

joined, err := h.EnsureRunning(ctx)
if err != nil {
log.Fatal(err)
}
log.Printf("microinit ready (joined=%v)", joined)

// Application policy: write only your drop-ins.
_ = config.WriteDropin(h.DropinDir, "app", "worker", config.WithCreatedBy(config.ServiceDef{
Name: "worker",
Enabled: config.BoolPtr(true),
Daemon: config.BoolPtr(true),
RestartPolicy: config.RestartOnError,
StartCmd: "exec /usr/bin/my-worker",
}, "my-app"))

<-ctx.Done()

// Application policy: stop services you started (optional).
_ = h.Client().Control("worker", "stop")

// SDK: tear down the process only if we spawned it.
if err := h.Shutdown(context.Background()); err != nil {
log.Fatal(err)
}
}
```

## Example: respect system services from base config

```go
system, err := config.BaseConfigServiceNames("/data/etc/microinit.json")
if err != nil {
log.Fatal(err)
}
if _, ok := system["redis"]; ok {
log.Fatal("refusing to overwrite system service redis")
}
err = config.WriteDropin(dropinDir, "infra", "redis", svc)
```

## client API (summary)

| Method | Description |
|--------|-------------|
| `List()` | All services |
| `Status(name)` | One service |
| `Control(name, start\|stop\|restart)` | Lifecycle |
| `Shutdown()` | Halt-mode shutdown (IPC) |
| `FollowLogs` / `ReadResponse` | Log stream |
| `ValidateName` / `FormatLogLine` | Helpers |

## config API (summary)

| Function | Description |
|----------|-------------|
| `WriteDropin` / `RemoveDropin` | Single file under `{dir}/{group}/{name}.json` |
| `SyncGroup` / `ListGroup` | Reconcile a group directory |
| `DropinExists` | Presence check |
| `BaseConfigServiceNames` | Names from main `microinit.json` |
| `WithCreatedBy` / `MatchLabels` | Label helpers (`created-by`) |
| `BoolPtr` / `IntPtr` | Optional JSON helpers |

## supervise API (summary)

| Method | Description |
|--------|-------------|
| `New(socket, bin, configPath, dropinDir)` | Construct host |
| `EnsureRunning(ctx) (joined, err)` | Join or spawn + wait for IPC |
| `Client()` | Bound IPC client |
| `Spawned()` | Whether this host owns the process |
| `Shutdown(ctx)` | Stop process **only if spawned** |

Also see [module README](../../go/README.md) and [Control socket API](../api.md).
6 changes: 3 additions & 3 deletions examples/microinit.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"name": "network",
"enabled": true,
"daemon": false,
"restart": false,
"restartPolicy": "none",
"restartBackoff": 2,
"successExitCodes": [
0
Expand All @@ -34,7 +34,7 @@
"name": "redis",
"enabled": true,
"daemon": true,
"restart": true,
"restartPolicy": "onError",
"restartBackoff": 2,
"successExitCodes": [
0
Expand All @@ -56,7 +56,7 @@
"name": "remote-icmp",
"enabled": true,
"daemon": true,
"restart": true,
"restartPolicy": "onError",
"restartBackoff": 5,
"successExitCodes": [
0
Expand Down
46 changes: 46 additions & 0 deletions go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Go SDK for microinit

Module: `github.com/dcc-bigfred/microinit/go`

| Package | Import | Role |
|---------|--------|------|
| client | `github.com/dcc-bigfred/microinit/go/client` | IPC (list/control/logs) |
| config | `github.com/dcc-bigfred/microinit/go/config` | ServiceDef + drop-ins |
| supervise | `github.com/dcc-bigfred/microinit/go/supervise` | Join/spawn daemon in-process |

Full guide with examples: **[docs/sdk/golang.md](../docs/sdk/golang.md)**. Developer index: **[docs/developer.md](../docs/developer.md)**.

## Install

```bash
go get github.com/dcc-bigfred/microinit/go@go/v0.3.0
```

Tag Go releases as **`go/vX.Y.Z`**. Private: `GOPRIVATE=github.com/dcc-bigfred/*`.

```go
// local monorepo
replace github.com/dcc-bigfred/microinit/go => ../microinit/go
```

## Quick start

```go
import (
"github.com/dcc-bigfred/microinit/go/client"
"github.com/dcc-bigfred/microinit/go/config"
"github.com/dcc-bigfred/microinit/go/supervise"
)

c := &client.Client{Socket: client.DefaultSocket}
list, err := c.List()

svc := config.WithCreatedBy(config.ServiceDef{Name: "worker", StartCmd: "exec worker"}, "my-app")

h := supervise.New(socket, "microinit", configPath, dropinDir)
joined, err := h.EnsureRunning(ctx)
// … app writes drop-ins / stops its services …
err = h.Shutdown(ctx) // no-op if joined
```

Default socket: `/data/run/microinit.sock`.
Loading
Loading