Host lifecycle: prepare, install, init, run
This document maps every entry point that touches a host to the responsibilities it owns. The goal is one concern, one owner — no duplication, no hidden state changes.
The three layers
Section titled “The three layers”There are three distinct concerns when bringing SHC up on a host:
- “Make this host capable of running SHC” — system packages, Docker installed, runtime libs.
- “Make SHC available as a binary on this system” — file placement, user/group, service unit.
- “Make this host into an SHC node” — Docker daemon checks, Swarm init, certs, cluster bootstrap, system apps.
These are owned by different entry points depending on whether SHC is installed via package manager (deb/rpm/apk/brew) or run from source (make dev, make install).
| Concern | Owned by (deb/rpm/apk) | Owned by (brew) | Owned by (source) |
|---|---|---|---|
| Make host capable | Depends: / Requires: / depends= | depends_on | make prepare |
| Make SHC available | Package payload | Brew Cellar | make install |
| Make host an SHC node | shc init + daemon ensure_node_ready | (same) | (same) |
The cluster bootstrap (shc init, daemon startup post-init) is always SHC’s own code, never the package manager’s. Packages stay minimal: file placement, user creation, service unit registration. They never mutate Docker config or initialize a Swarm.
Detection signal: DESTDIR
Section titled “Detection signal: DESTDIR”make install supports DESTDIR for callers that want to stage files into a directory before packaging (e.g. downstream distro maintainers building from source). The ifdef DESTDIR gate splits the two paths:
DESTDIRset → staging mode: place files into$DESTDIR/..., don’t install runtime deps, don’t enable services.DESTDIRunset → direct install: also pullpreparedeps and start the systemd unit.
Goreleaser packaging (.deb/.rpm/.apk) does not call make install. It builds binaries directly and stages files into the package via nfpms.contents; scriptlets under scripts/ run on the end-user’s machine after install.
What make prepare is, what it isn’t
Section titled “What make prepare is, what it isn’t”make prepare installs runtime system packages (Docker, system libs, the asdf toolchain) — what’s needed to build and run SHC. Currently Debian/Ubuntu only (apt install docker-ce ...). Idempotent.
It does not:
- Initialize Swarm (that’s
shc init—make devauto-runs it on first invocation) - Mutate
/etc/docker/daemon.json(read-only validation happens inensure_node_ready) - Create cluster certs, bootstrap files, or system apps
shc init is the only thing that initializes the cluster (Swarm, certs, node_bootstrap.json). make dev auto-runs shc init --yes on first invocation when no bootstrap.json exists, so contributors run make prepare && make dev and never need a separate “dev-cluster bootstrap” step.
Lifecycle moments
Section titled “Lifecycle moments”| Phase | When | Authority to mutate host? | Calls ensure_node_ready? |
|---|---|---|---|
| Debian/RHEL/Alpine package install | Operator runs apt install shc (or dnf / apk add) | No — depends pull runtime packages, payload places files, postinst creates user + reloads systemd | No |
make prepare | Contributor / source operator preparing runtime | Yes — installs system packages (Docker, libs, goreleaser) | No |
make install (direct) | Source-built prod install | Yes (pulls prepare deps) | No |
make install DESTDIR=... | Manual staging into a tree | No (DESTDIR set) | No |
make package | Build .deb/.rpm/.apk via goreleaser | No (snapshot mode, build only) | No |
shc init | First-time cluster bootstrap on a host | Yes — runs ensure_node_ready, then generates certs, bootstrap.json, Swarm, system apps | Yes |
| daemon startup (post-init) | systemd / make dev after shc init | Yes — re-verifies state idempotently | Yes |
| daemon startup (pre-init) | First-ever daemon boot, no bootstrap.json | No — limited mode, only serves /api/init/* | No |
make dev | Contributor running daemon from source | Yes — auto-runs shc init --yes if no bootstrap, then starts daemon | Yes (via shc init and daemon startup) |
| Test environment | bats / Go test fixture spins ephemeral SHC | Yes (sandboxed under SHC_BASE_DIR) | Yes |
What ensure_node_ready does
Section titled “What ensure_node_ready does”Defined in modules/cluster/ (ce module; the join/enroll overlays live in this repo’s modules/cluster/). Idempotent. Called from shc init, shc join, daemon post-init startup.
| Step | Linux |
|---|---|
Verify Docker daemon is reachable (docker info) | ✓ |
Validate /etc/docker/daemon.json (no iptables: false) | ✓ |
Apply sysctl tuning (/etc/sysctl.d/99-shc.conf) | ✓ (root only, skipped in dev) |
It does not initialize Swarm, generate certs, or write node_bootstrap.json — those are caller responsibilities (shc init does them after ensure_node_ready returns).
Bind / callback addresses
Section titled “Bind / callback addresses”SHC’s HTTP listener and the callback hostname containers use to reach it can differ. See modules/cluster/network.go (ce module).
get_bind_address(): persisted bind_ip (post-init) → docker bridge gateway → 0.0.0.0. get_callback_host(): persisted bind_ip → docker bridge gateway → first private IP. On Linux these collapse to one value — containers on local bridges reach the host through inter-bridge routing via docker0’s gateway.
Path layout
Section titled “Path layout”See core/paths/paths.go (ce module). Three modes: dev (project-relative), root (system FHS), non-root (XDG home).
| Concern | Linux root | Dev mode |
|---|---|---|
| Config | /etc/shc | <project>/.shc/config |
| State (database, vault, deployments) | /var/lib/shc | <project>/.shc/state |
| Cache | /var/cache/shc | <project>/.shc/cache |
| Logs | /var/log/shc | <project>/.shc/logs |
| Runtime / socket / pid | /run/shc | <project>/.shc/run |
| Service unit | systemd /usr/lib/systemd/system/shc.service | (foreground process) |
Dev domains: nip.io, never /etc/hosts
Section titled “Dev domains: nip.io, never /etc/hosts”SHC never edits /etc/hosts. Dev/test domains use nip.io (or sslip.io) wildcard DNS:
keycloak.10.240.0.4.nip.io → 10.240.0.4app.127.0.0.1.nip.io → 127.0.0.1This works without sudo, without DNS-cache flushes, and on every platform. Operators with real DNS supply --host example.com to override.
Packaging: make package
Section titled “Packaging: make package”make package runs goreleaser release --snapshot --clean, which builds the pure-Go binaries (CGO_ENABLED=0) and bundles the external rqlited raft-engine binary, then produces .deb, .rpm, .apk, and source tarballs under dist/. Tag pushes trigger the same flow in CI without --snapshot for real releases.
Multi-platform support matrix
Section titled “Multi-platform support matrix”| Platform | Package | Init system | Status |
|---|---|---|---|
| Debian/Ubuntu | .deb | systemd | First-class |
| RHEL family (Fedora, Rocky, RHEL, CentOS Stream) | .rpm | systemd | First-class |
| Alpine | .apk | OpenRC | First-class |
| macOS | (source via make install) | — | Best-effort |
| Windows | (none) | (none) | Out of scope — WSL2 if requested |