Helpers for administering runit services: as root on a runit system, as a regular user on macOS or Linux, or as PID 1 in a container.
$ curl -fsSL https://raw.githubusercontent.com/rubyists/sv-helper/main/bootstrap/install.sh | bashThat installs the latest release in ~/.local, or in /usr/local as
root. To choose the release, the prefix, or both:
$ curl -fsSL https://raw.githubusercontent.com/rubyists/sv-helper/main/bootstrap/install.sh | VERSION=v4.2.0 bash
$ curl -fsSL https://raw.githubusercontent.com/rubyists/sv-helper/main/bootstrap/install.sh | bash -s -- --prefix /opt/svIt downloads the release’s archive and checks it against that release’s
SHA256SUMS. If packslip is installed, it also checks the release’s
signature against this repository’s release workflow. Then it runs the
install.sh inside the archive, passing on any arguments after --, so
everything below about install.sh applies. It never uses sudo.
Releases before v4.0.0 have nothing it can install.
No package manager at all:
$ curl -fsSLO https://github.com/rubyists/sv-helper/releases/latest/download/sv-helper-linux.tar.gz
$ tar -xzf sv-helper-linux.tar.gz
$ cd sv-helper-*/
$ ./install.shReplace linux with darwin on macOS. Each release also publishes
rsvlog, sv-helper.sh and runsvdir.sh on their own, a SHA256SUMS
covering everything, and a signed packslip bundle. To check what you
downloaded:
$ sha256sum -c SHA256SUMS --ignore-missing
$ packslip verify packslip.sigstore.json \
--identity-prefix 'https://github.com/rubyists/sv-helper/.github/workflows/main.yaml@' \
--issuer https://token.actions.githubusercontent.com \
--artifact sv-helper-linux.tar.gzSee Releasing for how releases are built and verified.
From a checkout, or from that unpacked archive:
$ ./install.sh # ~/.local by default, /usr/local as root
$ ./install.sh --prefix /opt/sv
$ ./install.sh uninstallIt installs sv-helper, rsvlog and runsvdir.sh, plus the command
links below, and the container stages as inert data for
sv-helper install-stages (see Containers). Running it
again over an identical tree is fine. A file it did not write is
reported rather than replaced, and uninstall takes back only its own
files and leaves everything else alone.
PREFIX is where the files will live when they run. DESTDIR is a
staging root used only at install time, for package builds:
$ ./install.sh --destdir "$pkgdir" --prefix /usrmake install and make uninstall drive the same installer, so there is
only one definition of what "installed" means.
sv-enable <service> # Enable a service, so the supervisor starts it
sv-disable <service> # Disable it again
svls [<service>] # Status of one service, or of all of them
sv-find <service> # Where a service's definition is
sv-list # Every service definition available
sv-start <service> # Start a stopped service
sv-stop <service> # Stop a running one
sv-restart <service> # Restart it
sv-helper paths # Every path this invocation would use
sv-helper version # sv-helper's versionAll of them are the same script, sv-helper.sh, dispatching on the name
it was called by. sv-helper paths is the one to reach for when a
service turns up somewhere unexpected.
As a regular user they manage your own services. Root’s services are
root’s: runsv lets only the account that runs it ask about a service, so
pointing SVDIR at a system tree gets you an explanation and the
command to run instead, never a silent sudo:
$ SVDIR=/var/service svls
Listing All Services
Cannot ask runsv about 40 service(s) in /var/service as tj: they belong to root.
Run it as root instead: sudo env SVDIR=/var/service svlsEvery command, rsvlog and runsvdir.sh included, also takes
--version:
$ svls --version
svls (sv-helper) 5.0.0The invoking user decides the scope. A regular user never gets
system-wide state, however writable /var/service happens to be, and
nothing here ever escalates privilege. If a directory is not yours, it
says so rather than reaching for sudo.
| Definitions | Enabled tree | Logs | |
|---|---|---|---|
Linux, root |
/etc/sv |
/var/service, /service or /etc/service |
/var/log |
Linux, user |
${XDG_CONFIG_HOME:-~/.config}/sv-helper/sv |
${XDG_STATE_HOME:-~/.local/state}/sv-helper/service |
${XDG_STATE_HOME:-~/.local/state}/sv-helper/log |
macOS |
$(brew --prefix)/etc/sv |
$(brew --prefix)/var/service |
$(brew --prefix)/var/log |
Paths are resolved from the environment and the invoking UID, never from
where the scripts happen to be installed, so a copy in a checkout and a
copy in /usr/bin behave identically.
Any of it can be overridden, and an override is never second-guessed:
SVDIR=... # the enabled tree to supervise and act on
SV_SOURCE_DIR=... # where to look for service definitions
SV_LOG_BASE=... # where per-service logs go
SV_ROOT=... # for runsvdir.sh: pick $SV_ROOT/service/<tree> by hostname
SV_STAGE_DIR=... # where install-stages takes the runit stages frommacOS runs on Homebrew’s runit (brew install runit), which is what
decides these paths. Its formula patches sv to default to
$(brew --prefix)/var/service, and ships the brew services definition
that supervises it. To keep a tree running across logins:
$ brew services start runitThat logs the supervisor itself to $(brew --prefix)/var/log/runit.log.
Its launch agent runs with a minimal PATH that does not include
Homebrew’s bin, so the helpers find runit’s tools themselves rather
than requiring an interactive shell environment.
runsvdir.sh starts one, and creates the directories it needs if they
are not there yet:
$ runsvdir.shAn explicit SVDIR is always what gets supervised. With SV_ROOT set
instead, it picks $SV_ROOT/service/<tree> by hostname: $HOSTNAME,
then the hostname with one trailing -component removed, then two, each
optionally prefixed with $SV_PREFIX, falling back to generic.
runsvdir.sh --print-svdir prints the tree it would choose, and changes
nothing.
rsvlog is a generic run script for a service’s log directory:
$ mkdir -p ~/.config/sv-helper/sv/myservice/log
$ ln -s /usr/bin/rsvlog ~/.config/sv-helper/sv/myservice/log/runLogs then land in the log base above, under the service’s own name, with
./main and ./current linked where everything expects them.
A conf file beside it changes that:
SV_LOG_SYSLOG=true # log to syslog instead, through logger
SV_LOG_SYSLOG_PRIORITY=... # facility.level, default daemon.info
SV_LOGDIR=subdir # under the log base, or an absolute path
CURRENT_LOG_FILE=name.log # a second name for the live log
USERGROUP=user:group # run the logger as this accountFor example, logging to syslog:
SV_LOG_SYSLOG=true
SV_LOG_SYSLOG_PRIORITY=local7.infoor to a named subdirectory of the log base:
SV_LOGDIR=myservice/service_logs
CURRENT_LOG_FILE=myservice.logUSERGROUP only applies as root. A regular user’s logs are written as
that user. Its old default of rsvlog:adm is still used when that
account exists, and quietly skipped when it does not. Plenty of hosts
and almost every container have no such user, and a log service that
crash-loops on chown logs nothing at all. An account you name
explicitly is a different matter: if it does not exist, that is an error
rather than a silent downgrade to root.
An existing ./main directory is always kept as-is, so an established
log layout is never moved out from under the logs already in it.
etc/runit/{1,2,3} are a complete runit lifecycle for a container,
running as root or as a regular user, with runsvdir.sh as stage 2: the
same script a regular user runs on a host. Installing sv-helper puts them
beside it as data. Putting them live is a separate, explicit step,
because dropping files into /etc/runit changes how the machine boots:
$ sv-helper install-stages
$ sv-helper uninstall-stagesStage 2 records the tree it supervises, and stage 3 and sv-helper use
that record rather than working the tree out again, so all three always
agree on it.
See container/Readme.md for working `Containerfile`s for both, how stopping works, and what to expect when a service fails.
$ git submodule update --init --recursive
$ make testTests run under bats. See test/Readme.adoc.