S6-RC-COMPILE(8)

πŸ“¦ version 0.7.0.0, in nixpkgs unstable

πŸ’– s6-rc-compile(8) πŸ’–

quick vibe check bestie: this is a System Manager's Manual page, it's s6-rc-compile(8), and it is literally the compiler for your service database. buckle up, we're going in 🫢


🌸 NAME 🌸

s6-rc-compile β€” service database compiler. that's the whole job. iconic, honestly.


✨ SYNOPSIS ✨

s6-rc-compile [-v verbosity] [-h fdhuser] [-b] compiled source...

πŸ¦‹ DESCRIPTION πŸ¦‹

okay so s6-rc-compile is a service database compiler. it takes a whole series of service definitions written in the source format and compiles them into a directory β€” that's the compiled format. and then? the administrator can go look at that compiled database through s6-rc-db(8), or put it somewhere it'll get registered at boot time as the current compiled database by s6-rc-init(8), or even live update the current service database via s6-rc-update(8). so many options, all of them slay ✨

the way it works: s6-rc-compile analyzes every directory source in its arguments. for every subdirectory service in source, it expects to find a valid service definition in service. then it outputs a compiled version of the service database into compiled. that database contains info for all the services declared in every source argument. all of them. no cap πŸ’…


πŸŽ€ Source format πŸŽ€

s6-rc-compile scans every source directory to find service definition directories in it. it ignores every file that isn't a directory, and it also ignores anything that starts with a dot (sorry hidden files, not today πŸ™ˆ). for every service definition directory it finds, it creates a service with the same name as the directory.

so, the naming rules, and yes there are rules:

  • names cannot be duplicated.
  • names cannot contain a slash or a newline.
  • names can contain spaces and tabs β€” but honestly? using anything other than alphanumerical characters, underscores and dashes is discouraged. the s6-rc programs themselves will handle weird names just fine, no drama, but other tools β€” especially shell scripts β€” may absolutely not. so like, don't do that to yourself 😭
  • names are also forbidden from using the reserved s6rc- and s6-rc- prefixes. periodt.

every service definition directory service is expected to contain the following files:

πŸ’œ For every service

  • type β€” a regular file that contains only the text oneshot, longrun or bundle, plus a terminating newline. this file declares the type of service defined by the directory. it's giving "the whole personality in one file" πŸŽ€
  • flag-essential β€” an optional regular file. the contents are completely irrelevant, only its presence is tested. if this file exists, the service gets marked as essential, which means an s6-rc -d change foo command will not stop the service β€” only an s6-rc -D change foo command will. and if the service is a bundle, the flag gets propagated to all its contents, i.e. all the services it represents get marked as essential too. it spreads bestie, it spreads 🌈
  • flag-recommended β€” an optional regular file. again, contents irrelevant, only presence is tested. if it exists, the service gets marked as recommended. heads up though: this is only important for s6-rc-repo(7) commands, not when compiling the database. so it's lowkey just vibes over here πŸ¦‹

🧚 For bundles

  • contents.d β€” a directory. it must contain files, no matter their type, no matter their contents, named after services. (typically empty regular files are used, that's the move.) all the services named in this directory will be represented by the bundle named service.
  • contents β€” if contents.d does not exist, the bundle contents can be read from a regular file named contents instead. this file must be a list of service names, one per line. whitespace at the beginning of a line is ignored, but trailing whitespace is not (rude, i know). lines starting with a # character are ignored. the file defines the services that will be represented by the bundle named service. ⚠️ note that this format is deprecated.
so yes, you can use bundle names inside a contents.d directory or a contents file. but if s6-rc-compile detects a cycle in bundle definitions, it will complain and exit 1. a whole loop of bundles eating each other? not the vibe. πŸ’€

🌸 For atomic services

  • timeout-up β€” an optional regular file. if it exists, it must contain an integer, which is the max number of milliseconds s6-rc(8) will wait for successful completion of the service start. if starting the service takes longer than this value, s6-rc(8) will declare the transition a failure. if the file doesn't exist, or contains 0, no timeout is defined and s6-rc(8) will wait indefinitely for the service to start. forever. patient king πŸ‘‘
  • timeout-down β€” an optional regular file. if it exists, it must contain an integer, which is the max number of milliseconds s6-rc(8) will wait for successful completion of the service stop. if stopping the service takes longer than this value, s6-rc(8) will declare the transition a failure. if the file doesn't exist, or contains 0, no timeout is defined and s6-rc(8) will wait indefinitely for the service to stop. same energy, other direction πŸ¦‹
  • dependencies.d β€” an optional directory. it must contain files, no matter their type, no matter their contents, named after services. (typically empty regular files are used.) all the services named in this directory will be direct dependencies of service, i.e. the services that must be up in order for service to work properly. your ride-or-dies, basically 🫢
  • dependencies β€” if dependencies.d does not exist: an optional regular file. it must be a list of service names, one per line. whitespace at the beginning of a line is ignored, but trailing whitespace is not. lines starting with a # character are ignored. the file defines the direct dependencies of service. ⚠️ note that this format is deprecated.

okay and this part is important so listen up: it is unnecessary to manually define complete sets of dependencies in the dependencies.d directory or the dependencies file, because s6-rc(8) will properly handle dependency chains. like β€” if A depends on B, no matter the underlying implementation of B, and the current implementation of B depends on C, then you should just put B in A/dependencies.d. when starting the set, s6-rc(8) will start C first, then B, then A. and if the underlying implementation of B changes and stops depending on C? then you just have to modify the dependencies for B, and the definition of A is still correct. that's the whole flex. that's why this design ate πŸ’…

of course β€” if A depends on C anyway, you should add both B and C to A/dependencies.d. don't leave C hanging.

and if s6-rc-compile detects a cycle in dependencies across services, it will complain and exit 1. again: cycles are not the vibe. πŸ’€

⭐ For oneshots

  • up and down β€” two regular files, which must each contain a single Unix command line. the files get interpreted by the execlineb(1) lexer at compile time, and the results are stored into the compiled database in an internal form. up gets run when the service is started, and down gets executed when the service is stopped. up is mandatory, but down is optional β€” if no down file is provided in the source definition directory, then it's treated as the empty script. and if a script is empty, s6-rc(8) will consider that the corresponding transition for this service does nothing and always succeeds. free win πŸŽ€

so up and down are interpreted by execlineb(1), but that does not mean they have to be entirely written in the execline language. the execlineb(1) lexer is only used because it can compile a Unix command line from a text file and store the compiled result, whereas a shell would have to be invoked every time the script is run. there are many ways to write up and down scripts:

  • they can be written as standard execline scripts, without the initial shebang line or positional parameter management.
  • they can be written in any scripting language by invoking the interpreter directly: for instance /bin/sh -c β€œscript”, where script is a shell script.
  • they can also just call a script that will actually be stored somewhere else: for instance, up can contain /etc/init.d/service start while down contains /etc/init.d/service stop, and /etc/init.d/service is a System V-style init script.

and please, bestie, don't think you have to learn all the intricacies of the execline language just because the up and down scripts get lexed by it. you don't. breathe. you're fine 😭🫢

🌈 For longruns

the s6-rc service definition directory for a longrun service is similar to an s6-service-directory(7), but there are a few differences:

  • s6-rc-compile crafts the servicedir itself, based on what it finds in the service definition directory. it does not copy everything directly from the definition directory to the servicedir β€” only two subdirectories get copied verbatim: data and env. so if you want to store service configuration data, to be used by the run script, in the service directory, make sure it's in a data/ or env/ subdirectory. put it anywhere else and it's just gone, sorry πŸ’€
  • definition directories cannot have a log subdirectory β€” or if they do, it will be ignored. from s6-rc-compile's point of view, logged s6 services must actually be defined as two separate s6-rc services, one defined as a producer and one defined as a consumer, making a pipeline of just two services; see below for more information about pipelines. two services, one pipeline, main character energy πŸ‘‘

the following files must or may appear in a longrun definition directory:

  • a mandatory regular file named run, as well as optional files named finish, notification-fd, lock-fd, timeout-kill, timeout-finish, max-death-tally, and down-signal. these files will be copied, or recreated, in the generated s6-service-directory(7): they're meant to be used by the s6-supervise(8) process that will manage the longrun service. that's the whole squad right there 🫢
  • optional directories named instance and instances. these will be copied verbatim into the generated service directory. they're used internally by some s6 tools; using them for your own purposes is not recommended β€” you should ignore them and use the data directory instead. like, respectfully, they're not for you πŸ™ˆ
  • optional directories named data and env. these will be copied verbatim into the generated service directory. yes, these ones. use these πŸ’–
  • an optional file named producer-for. if this file exists, then it must contain the name of another longrun service servicelog; service is then declared as a producer for servicelog. servicelog must also, in its own definition directory, be declared as a consumer for at least service. it takes two to pipeline, bestie ✨
  • an optional file named consumer-for. if this file exists, then it must contain a list of longrun services serviceprod...: service is then declared as a consumer for all the services in serviceprod. each of those services must also, in its own definition directory, be declared as a producer for service. mutual, consensual, compile-time-verified πŸ’…
  • pipeline-name is an optional file πŸŽ€ β€” if it's sitting there and there's a consumer-for file next to it, and there's no producer-for file, then a bundle gets created automatically, named after whatever's inside the pipeline-name file, and it contains every service in the pipeline that ends at service. Peep the pipelining section below for the whole story. And heads up: the pipeline-name file is ignored if service isn't a last consumer. Periodt.

Lowkey important: service directories get relocated and copied at boot time πŸ¦‹ β€” so if your run or finish scripts point at files inside the service directory (probably in the data or env subdirectories!), they better use relative paths, not absolute ones. Fr fr.

Also you literally cannot create a ./down file for s6-supervise(8) in a generated service directory πŸ™ˆ. Even if such a file exists in the definition directory, it gets ignored β€” it will not be replicated into the service directory. This is intentional: s6-rc(8) internally uses /down files in the service directories it manages, to mark longrun services that are down. So don't fight it, bestie πŸ’….

The producer-for, consumer-for and pipeline-name files are what set up automatic longrun pipelining ✨.

🌈 Longrun pipelining 🌈

If you've used supervision suites before, you already know logged services: one service acts as a producer, gets coupled with another service β€” its logger β€” and the supervision system automatically keeps an open pipe between the producer's stdout and the logger's stdin. Iconic behaviour, honestly.

s6-rc(8) comes with an extension of this whole mechanism πŸ”₯. Instead of only letting two longrun services be pipelined, it can set up an indefinite number of longrun services this way. Ate.

  • A producer declares its direct consumer in a producer-for file.
  • Intermediate services declare both their direct producers in their consumer-for file, and their direct consumer in their producer-for file.
  • The last consumer only declares its direct producers in a consumer-for file.
  • The last consumer may also declare a name for the whole pipeline, in its pipeline-name file. If it does, then a bundle is automatically created with that given name, and it contains all the services in the pipeline. πŸŽ€

s6-rc-compile will detect pipelines and set up the service directories so that every producer's stdout is connected to its consumer's stdin β€” and so that the pipes don't break whenever one element in the chain dies πŸ’€.

A service can only be declared as a producer for one other service, but it can be declared as a consumer for several other services: the consumer-for can have several lines. Translation: one service can only send its output to one consumer, but it can read its input from several producers. And if there are several producers feeding a service, they all write to the same Unix pipe that the service reads from 🫢.

Which means… what s6-rc calls "pipelines" are really funnels 😭. You can collapse multiple data streams into a single data stream at every step of your processing chain. The pipeline terminology just stuck around for history reasons: previous versions of s6-rc(8) couldn't handle multiple producers. Iykyk.

s6-rc-compile checks for pipeline consistency πŸ‘‘. It must see a producer-for file in the producers' definition that's consistent with the consumer-for file in the consumer's definition. It will detect and reject cycles as well as collisions. No shortcuts, no chaos.

The pipe linking a set of producers with a consumer is created and stored at run-time in an s6-fdholder-daemon(8) instance managed by an automatically generated longrun service named s6rc-fdholder πŸ¦‹.

🧚 Compiled database usage 🧚

Once it's been built with s6-rc-compile, a compiled database cannot be modified. It β€” meaning the directory containing the n, db and resolve.cdb files, plus the servicedirs subdirectory β€” can be moved around in the filesystem, as long as the insides stay untouched πŸ’œ.

Once a compiled database is live (i.e. it has services running on it, following an invocation of s6-rc-init(8) or s6-rc-update(8)), it must not move anymore, and it must not be deleted ⚠️. The only way to "free" such a compiled database for displacement or deletion is to replace it as the live one via another call to s6-rc-update(8). That's the whole escape hatch, bestie.

And no matter what user compiles the database, only root and the user owning the supervision tree at run-time will be able to operate the compiled database with the s6-rc(8) command πŸ‘‘


✨ OPTIONS ✨

  • -v verbosity β€” be more or less verbose. Default is 1: warning and error messages get printed to stderr. 0 silences warnings. 2 adds a bit more info about what s6-rc-compile is doing. 3 or more is heavy debug output πŸ›.
  • -h fdhuser β€” arrange for the s6-fdholder-daemon(8) program (the one that maintains the pipes for the longrun pipelines) to run as user fdhuser. By default it runs as the user owning the supervision tree, i.e. most likely root. πŸ‘‘
  • -b β€” make s6-rc-oneshot-run(8) invocations wait instead of fail on lock contention. This shouldn't change anything in practice, and you can literally just ignore that option πŸ™ˆ.

πŸ’€ EXIT STATUS πŸ’€

  • 0 β€” Success. Slay. 🌸
  • 1 β€” Error in a source directory.
  • 100 β€” Wrong usage.
  • 111 β€” System call failed.

πŸ“ EXAMPLES πŸ“

The examples/source subdirectory of the s6-rc package contains a set of service definition directories, and it's actually a working, valid set for a Linux system running busybox and the skarnet.org packages 🫢. Of course, only the service definition set has been kept and private information has been removed, so it won't work out-of-the-box without the proper specific files β€” notably configuration in /etc β€” but nevertheless, you can browse the source and understand what it does, and adapt it to your own needs. It will compile as-is with s6-rc-compile, and you can examine the resulting compiled database with s6-rc-db(8). πŸ‘‘


🌈 SEE ALSO 🌈

This man page is ported from the authoritative documentation at: https://skarnet.org/software/s6-rc/s6-rc-compile.html


πŸ‘‘ AUTHORS πŸ‘‘

  • Laurent Bercot
  • Alexis <flexibeast@gmail.com> (man page port)

Okay that's the whole page, bestie, you made it to the end πŸŽ€πŸ’– β€” this one's from the Debian packaging of s6-rc-compile(8), dated October 21, 2025. Go build yourself a database, you absolute icon. βœ¨πŸ¦‹

πŸ“¦ where this page comes from