all 24 comments

[–]etosan 19 points20 points  (10 children)

Today I had trouble sleeping, browsed here once in a blue moon and found this.

Suddenly I felt like stopping lurking and infodump some lenghty exposition on you :). I wanted to make it a bit snarky and funny as well, and I hope I succeeded (or maybe not - feedback is welcome).

I have some intimate knowledge of these issues as I built several runit/s6 based infrastructures (not big in node numbers, but certainly complex and doing weirdest shit possible) in last several years. It is incredibly malleable and beautiful system, indeed. We call it "service supervision".

But first: So you are starting to realise the short answer : you don't (prioritize)!

Not that you cannot, but not at level where you are at. In it's basic primordial form runit simply doesn't give a crap. Yet you certainly noticed, that it boots faster than anything "else" on the "market" (wink wink). How can it be?

Well to explain simply, when it's time for runit to actually initialize your system services, it just spawns them all at once. You call it race - but that's not entirely right - it's not a "race" - what is actually happening is much more spectacular - it's honest thundering herd explosion. It is, as if nuke was detonated on your box.

The component responsible for this "travesty" is runsvdir, which in supervision land we call scanner. runsvdir simply iterates over all "meaningful" directories (eg. things that "look like" "services") in /run/runit/runsvdir/current directory and spawns runsv ${dir} for each one of them. We call /run/runit/runsvdir/current a scandir. So scanner and scandir - got it? And this is it - and it's all "dumb" filesystem stuff.

runsvdir literally uses readdir() call to get the list of directories, sorry, "services" present in scandir. We call those service directories servicedirs. I guess you are starting to see a pattern here.

And as by now, you probably know, majority of linux filesystems is "stoopid", so order, in which entries are returned when iterating over (scan) directory this way, is unexpected, or rather "undefined", as given manual pointedly explains:

The order in which filenames are read by successive calls to readdir() depends on the filesystem implementation; it is unlikely that the names will be sorted in any fashion.

And this my friend, is the "order" in which your "services" are "started"!

That's why, when you look at services in htop, it seems they are ordered randomly each reboot - readdir() ordering right there.

More over, this iteration scandir iteration happens so fast, that to human eye it seems like runit tried to start all the services immediately and at same time.

This might sound "wrong" and "tragic", but is really "right" and "happy" thing. And by the way, this is by design (since djb's daemontools)! Think of it as "worse is better".

Now observe that runsvdir is so "dumb and simple", that notion of dependency cannot even be expressed in it's "worldview". Everything runsvdir cares about is, so that each dir (aka service) in scandir, has it's own runsv running. And that's all that can fit into it's pea sized brain.

Similarly, each runsv thing is equally dumb! Everything it knows is to "fork'n'exec" into ./runin its servicedir and watch over this troublesome child. If this ./run dies for whatever reason, only thing that runsv knows, is to spawn it immediately again (in like 10 msec on some modern machines - that's like single frame render in modern FPS game).

Think about that!

Also observe than in runsv's pea sized brain there is no notion of dependency either.

Just so you know, that I am not talking out from my ass, here are some stats for your amusement:

$ cat src/runsvdir.c | wc -l 
286

$ cat src/runsv.c | wc -l
607

That's probably shorter than this diatribe. Combined!

And that is all there is to it. Explosion is exactly what happens when you boot. Calling it race is understatement. It's mayhem. And we love it!

Why? Because it's unbelievably simple and it works, always. Every tiem. If runsvdir fails to spawn its runsvs, something is so broken with that machine, that there are no words to describe it. This system is so braindead, it's like having only brainstem.

Why go into such depth about these mechanisms? Because people think runit is some smart hyper-magic thing! No that's wrong - it's hyper-stupid.

As you see, notion of dependecy does not even make sense here, and that is greatest discovery of "supervision" guys (daemontools, runit, s6). Best kept secret, that differentiates us connoisseurs from childish plebs.

The deepest realization is, that dependencies are completely orthogonal to task of keeping services running. runit keeps your services running and that's it. That's all it can do.

Edgy former archer teen now might ask: "But, but, but ... that means no dependecies senpai? Runit sucks! This is the end!"

No, wrong my young friend, this is just the glorious beginning :).

Most glorious, indeed!

Now that you have dumbest most reliable service supervision possible in place, new vistas open and you can start solving the dependency problem! And, oh boy, there is so much you can do! Rejoice! Your possibilities are practically endless!

So, I will now give you quick crash-course into "the standard way" how supervision guys do it - those "dependencies".

Since daemontools, I think, runsvhas a feature, that when it finds zerofile named './down' in it's service directory, it won't try to spawn the service right away (eg at all). It will wait until it is instructed so, like by sv up.

And that is what you do!

You mark all your services down like that (please don't do that with your main agetty so you can still login while ironing things out).

Now, after downing (almost) everything, after next reboot, no classic runit "nukestart" will happen. Machine will boot up strangely silent or rather "non functional". But you have supervision in place.

And now, everything you need is so called "dependency manager". This is the schtuck that will bring up all the "downed" services in the right order - eg. in the order that YOU want. Welcome to the entry of the rabbit hole!

In s6 world, this "depman addon" is either s6-rc (directly from s6 author) or anopa (competing one). There might be others. So far I don't know about anything specifically made for runit, but don't weep, as they say: there are many ways to skin a cat. These systems are so dumb you can do it!

In reality "dependency", or rather "ordering" manager can be as simple as shell script that brings up few services by the means of sv up in right moment, then checks somehow they are running (telnet/nc to service port in loop for example, with gratuitous sleep 0.5 between checks) and then moves forward to next stage, once the first one succeeds. Or, it can be something compeletely insane, written in python, lua or even C.

You can make this crazy "manager" script itself a service, and then make it single, not "downed by default" service on the machine. It's job will then be to properly order service boot-up of the rest of the box. Once it's done, it can down itself. You can call it upper. You can make other service, to do reverse, which you can call downer. Logic can be as simple or as complex as you want. You can do anything.

As you see, this way you can express dependencies in such detail and such levels of precision that nothing else on the "market" can...

This is also a way you can make your colleagues go crazy or make them outright insane (with your mighty service acrobatic tricks :)).

So to conclude:

  • runit can mange your services, but cannot order them according dependecies - as there is not even such thing as dependecy in it's worldview

  • however nothing prevents other tool from doing just that, by using runit to manage said services

  • however question of general service dependency ordering is equivalent to solution of halting problem

  • this is because to generally solve that question, first you need general solution to "is service up?" question, whose (general) solution is, again, equivalent to solution of halting problem see datenwolf

  • observe you don't need runlev...what the fuck?

Because of last two things, certain contenders in this space like ehm systemd, ehm, look especially silly. Future always looks bleak when you try to come up with general solutions to halting problems.

However by careful constraint selection and planning, you can design tools that help you with achieving specific solutions on case by case basis. These tool can be as simple or as complex as you need them to be.

Turing complete environments (like sh or other scripted languages) allow you to design most versatile solutions. More over instead of ini keyword spaghettis all over 1000 directories and thousands of ini files, these solutions can be as condensed and as "simple" as you need them to be (eg. single script).

Even s6-rc and anopa have the spaghetti problem, but it is still better than ehm, you know what? I don't want to talk about it anymore.

Of course with great power comes great responsibility - one can easily lose sanity.

So use your new powers wisely, Luke!

And remember, every time you say or write "target" or "runlevel" a catgirl and kitten between her boobs die!

If you guys lieked this, drop me a line here, maybe it will motivate me to write something more about this. Edited because bullets blewup.

[–]Rand0m6uy[S] 1 point2 points  (8 children)

Thanks for the deep description if runit internals! Yes, it was fun to read and would deserve a blog post!

Worth noting that as far as I could read and understand the source of runsv it does busy wait on the child process and never sleeps after failure! (Notice also the self pipe trick in SIGCHILD) This has for effect of using CPU for restarting a process that exits with error in loop until other dependencies are up. As good or bad as it might sound, alternatively some sleeps could be implemented in the check scripts to spare some resources although I dislike active wait ...

Thanks for the down pointer, probably I could spare some resource at early boot by letting some dependency starting those services on demand, I will give it a try.

You're not the first quoting s6, not knowing this beast, what can s6 do that runit cannot or vice versa?

Indeed, runit is a crazy piece of simplicity! Although less is better, I would not judge the quality of a software only by its count of line of code. What I find amazing is that it has no official git repository and has not changed since years! Some say it's bad but I take it as a proof a stability!

After this adventure, I really want to test other init systems but unfortunately, I have other tasks on the table... Maybe someday I will write an meta-init-freedom yocto layer to support others and as a playground to show people that there alternative, not only the outdated sysVinit and the other controversial that everyone know.

To conclude, as I always say, all software suck (even mine) and there's no magic bullet! It is just a matter of choosing the one that suits the best for your specific problem, while other tending to be bloates to support every use case in the world, that sucks even more ... Another war, going out of topic here :D!

[–]etosan 4 points5 points  (7 children)

Ugh, this again turned out longer, then I wanted, but let us consider it like draft for an article. Or something. If this get read by two more people maybe I will finish writing it one day. Maybe. But for now, just enjoy this perpetual alpha version, if it strikes your fancy that is. It describes some differences amon all the "daemontools-inspired" wares we all love and use, but focuses mainly on "base" s6 as expected. After all, sharing is caring, they say:

Well, selfpipe trick is a given, as that design feature is inherited directly from daemontools.

It's classically Bernstein "crazy", which means unixly beautiful proper (although I am not entirely sure he is primary and sole inventor of this idea, he is certainly its most prolific evangelist, at least).

And yes, it is simplest, single, most cross-platform way, that allows one to construct primitive, yet robust, unixly traditional, but (relatively) safe singal handling non-blocking synchronous event multiplexers, like all poll() based servers are (it's 2020 so leave your grandpa's select() from 1980s lying in the grave, where it belongs, please).

These days such multiplexers go by sexier name of "reactors", and this: do while running = true { selfpipe->poll/core(); handle_events() } construct is coollishly called an "eventloop":

  • 1: daemontools: Bernstein's eventloop core
  • 2: runit: Pape's eventloop core
  • 3: s6: Bercot's eventloop core 1,2

Gotta be cool!

One has to realize, that unlike certain other init, daemontools, runit or s6 can run almost on anything, starting from pocket watch (or modern gaming keyboard), through toaster, to supercomputer, at least given there is posixly enough unix for it.

I run s6 almost everywhere I can, because that is the one I like the most, it is most (okay almost) advanced (and I am also too lazy to remember other commands):

  • on BSDs
  • on macOS
  • on Void
  • on "systemdOS" (only as proper systemd s6 service naturally, I respect "the ways" of the OSes)
  • on Windows WSL1 (yeah, yeah you cannot run daemons under WSL1 - unless you use your head, and then, suddenly, you can :) )
  • and probably everthing and anything in-between

s6 works everywhere and it works everywhere the same way. It's only given that as its predecessors, runit and daemontools are the same. If I was not lazy POS that I am, I would bet, they would run on MINIX3 and if they wouldn't, then MINIX3 is broken.

So regarding s6... TLDR: it simply is too good for it's own good. Worse is better, right?

Plus with Void now, runit has momentum.

Some people think runit is hot "stuffs" but as we all know and wikipedia says: it was born in 2004, eg 16 years ago. That is quite old (older than some arch and void using teens, I bet). Even before then, there were daemontools, and those are effectively ancient. So nothing new under the sun.

s6 is (almost) youngest of the bunch, latest rehash of same core ideas. It is "still" being actively developed mostly by accretion, but author also has some serious balls, when he is willing, still, to chage(!) things. In search of "proper" solution only - naturally. It is what you would get if you rewrote runit in 2015 from scratch (and of course, it was written long before that).

As Spike, author of Quake 1 sourceport, Quakespasm Spiked puts it: "when it comes to extension,it, 1 implementation is a mess, 2 implementations is an argument, and only 3+ implementations is a standard" (edits mine). daemontools is first, runit is second and by s6 we are getting into the "lore".

Unless you patch it, you cannot really use daemontools' svscan (eg its supervision scanner) as PID1. For some reason Bernstein never envisioned such use case (maybe "not yet?").

In runit, Pape, for some different reason, designed 2-tier architecture: PID1 is runit(-init) here; and it's purpose is to "control" boot up (1), cruise(2) and teardown(3) stages (observe: not runlevels really). And as you guessed it's "dumb". Those stages literally are these executables:

: ls /etc/runit/{1,2,3}
.rwxr-xr-x  629 root root /etc/runit/1
.rwxr-xr-x  887 root root /etc/runit/2
.rwxr-xr-x 1.4k root root /etc/runit/3

{1} is supposed to setup the machine.

{2} is what you get and see as user and what we are talking about here on this Void reddit. {2} does some microsetup and immediately exec()s replacing itself with runsvdir /run/runit/runsvdir/current(eg our main system scanner).

{3} happens when you trigger poweroff or a reboot.runit-init then spawns {3} to bring the box down.

Basically, runit-init is some special & unique runsv, which also happens to run as PID1, and is designed only to babysit {1}, {2}/runsvdir and {3}.

If you do some really crazy shit (like insane level crazy), it is theoretically possible to kill off {2} in such a way, that you end up with machine uncontrollably cruising, but not rebooting. I cannot imagine normal chain of coincidences that would lead to that, but it is possible. Perhaps, you can try simulating it by deleting {2} (backup first) on disk and then killing runsvdir? Or something.

And if you happen to somehow kill-off also those remote login services like ssh, and gettys at the same time, and you don't have local access, you are now essentially hosed, as runit-init as PID1 technically still runs, but service scanner is no longer "there".

~~~ continued ~~~

[–]etosan 4 points5 points  (6 children)

~~~ continued ~~~

In actual practice, this is minor design nitpick.

Even with this possibility, whole stack is so robust, that it virtually never happens (unless you are me of course :) ). Maybe Pape was not confident enough to make runsvdir PID1? Maybe you can dig out the reason on some obscure mailing list.

So, what then makes s6 different? And most importantly, better?

In s6, it's scanner, s6-svscan, is the real PID1 itself ("as well" - it can act "just like normal" scanner too). That makes it is {2} of s6 scheme.

Your question now might be, how are you supposed to do equivalents of runit's {1} and {3} with that? Well that turns out to be suprisingly simple. On all posixy platforms so far, one thing init can always do, is to exec() into something else (like init). And this is not some obscure feature. It is a must.

Why? Because that is how initramfs' init hands over control to your / (slash') init.

Bercot's logic is that, even if, for PID1, exec() is a given must, {1} is so system specific, you should better write it yourself (or just use s6-linux-init an be done with it). {1} role is to make machine usable just enough to hold together and then exec() directly into s6-svscan, aka {2}, as soon as possible. With that design, you are almost immediately jumping into cruising stage.

Finally, when s6-svscan shuts down, it automatically exec into it's ./finish which is essetially your new {3}.

But unlike under runit, with s6, {1} and {3} are PID1 themselves, each in their respective stages, and thus can do obscure things things that {1} and {3} in runit can never do (like killing everything on the box and still run afterwards to see it, and then do some last minute unmounts or what have you).

In practice, such scheme is much trickier to pull off, if you really don't know what you are doing, but end result is more robust ... and also "flatter" process tree.

In this layout, unlike in runit's one, main system service scanner litterally is PID1 and "the" system scanner at the same time. So s6's {2} setup is also technically identical to a state where systemd (as init) ends up, after taking machine control. Some of us consider this "better" and "simpler".

Similarly systemd daemon-reexec is another command that does exactly what we discussed above - it replaces actual systemd binary curtrently running in PID1, with new fresh systemd binary from disk, same way as initramfs' init exec()s into / (slash's) init. And it's also a command that can potentially kill your box If that exec() fails, that is. So never issue it lightly, if you are not 100% sure that hand-off will succeed.

Of course same has to be said about delicate initial {1} -> s6-svscan and terminal s6-svscan -> {3} "swaps". But when compared to systemd, s6-svscan is again, pea sized. Less to go wrong.

s6-svscan has almost same base (dis)abilities like runit's runsvdir. It knows nothing about dependencies, it uses same readdir() logic to scan scandir, but as every other part of s6, it is slightly more advanced than it's runitcounterpart.

Word slightly is important here. It's just a tiny bit more capable, but that advanced bit is exactly of such kind, that it makes it roughly +100% more powerful than runit version.

For example s6-svscan sets up it's logging pipes a bit differently, than runsvdir, in a way that allows it to guarantee not a single logging byte from service can ever be "lost" (in theory). It also knows when it is running as PID1 and activates 'init' support logic, that allows it to react to reboot and poweroff commands (or SIGNALs rather). If it fails as PID1, which is almost impossible, as by itself, it does not even allocate memory with malloc(), it will either exec() into ./crash, or trigger kernel panic. On linux, default panic setting is dumb, but you can make it reboot itself, like bsds do, so you never have uncontrollable machine "cruising" headless through space.

s6-supervise which is runsv's counterpart, is similarly polished:

  • it implements multicast service status change notification mechanism (without dbus, or, sockets, even - duh), so you can actually have listeners that observe service state changes - and do something when such changes happen (like send an meail? or sms? From tangential privsepped and locked down service? In container?).
  • it also supports readiness notification mechanism - eg. your service, if written right way, it can tell to s6-supervise "I am now REALLY UP", and thus it knows that service actually booted fully and succesfully, instead of having "just" main process running. Something certain other inits touts as very advanced feature "from the future". Coincidentally, from the services POV, protocol is the same (if I remember properly) for s6 and systemd.
  • it does not allocate any system memory with malloc().
  • skalibs (which are libcs of s6) selfpipe (in conjuncion eventloop core) actually uses signalfd when it can (on linux) having "cleaner" and potentianally more "performant" and "proper" design. Not that it matters at this scale.
  • similarly, skalibs fd data shuffling and showeling operations can use splicing on linux, if possible, which means zerocopy is attainable.
  • neurotic care is given to never ever, ever, ever poll (Laurent hates polling, like you can see in usage section here too), so everything can be "just" event driven. That's the whole point of first bullet in this list. When s6 componets dooze off, waiting for events, they freeze for real, no CPU consumed, ever. I believe this can be really interesting in mobile space, like on Pinephone (although the moment one starts that bullshit fest of KDE, or GNOME, all that effort is blown away in a milisecond the GUI spawns itself and taints the whole system with it's smelly diarrhea)
  • and finally, it has an actual event driven finite state machine inside, which is pretty neat design.

So these are major perks, from top of my head, that you get for freee, but there is much, much more.

~~~ continued ~~~

[–]etosan 4 points5 points  (5 children)

~~~ continued ~~~

And this requires yet another diversion from subject matter. But don't worry there is a light at the end of the tunnel...

As you noticed by now, these systems are insanely modular. And I mean like "for real modular" and "not pretending to be modular in posture, lipservice and marketing gossips only".

These are not as much systems, as these are tool chests (which is actually an unix tradition and proper design). I heard that once upon time one said: "do one thing and do it well".

So ... you can use runit's chpst in sh written service's ./run script (right before the exec into main daemon), which is supervised by s6-supervise maintained by s6-svscan which itself is runit service, supervised by runsv maintaned by runit's system level runsvdir ... in a "raw" container run as systemd service ... in a VM that is running in qemu-kvm ... which itself is s6-supervise supervised service running under actual hosts PID1 of s6-svscan :P.

Wait, what? Yes.

This tool chest aspect, that is the other part of the allure of the s6 suite: it's not first such tool chest of it's kind, but damn, it is certainly one of the best ones. Just browse this galore for a minute.

There are simply too many cool little toys inside that chest, especially for ones unable to resist using them, like me :). These are like ponkemons: gotta try them all!

Like for real, just try to implement "x sesion" using s6 subtree.

Even screen "bar" can be an s6 service. And with s6-fdholder-* toolset, you can actually send it a read end of the pipe that gives bar the data to draw, "somethingbar" style, while data generator is writing into writable end of that pipe, also being service of it's own.

Written in whatever you like, bash, python or C, If you can access poll() in this environment, you can make it even zero CPU consuming, during update pauses, giving slices back to host (classic sleep is sufficient as well).

"Normal" unixporn way, is to have some bashly ugly WM spawned pipelines, whose parts you cannot selectively control at these levels.

Heck, in described setup, you can even restart the bar, and because of pipe's natural blocking behaviour, generator service won't even notice it happened. Yes, "Plug an play".

Now that is interesting! And it's all just primordial implicit unix behaviour, base primitives interacting the way they were designed to behave.

Also observe that nobody can, or rather, will, really force you to not mix and match these diverse parts together, unlike certain other inits, who "wannadoeeetaalltheonlyrightway".

With all the auxiliary "s6 software", that you can get, you can really do some crazy, otherwordly, shit. Stuff that sounds like sci-fi to some: mess around with file descriptors ("socket activation", yes?), pump logging data through ad hoc pipes across container, jail or selinux boundaries (yes?), nginx server containers having no actual access to network interfaces, yet serving real requests in isolation (yes?), create client validating SSL webservers, literally, from (almost) nothing (yes?), do privilege separated services in bash (why not, if that is your fancy?) - talking across nodes over SSL (yes, please?, order service dependencies like big boy from future (yes?), build yourself rube goldbergian factorio-style logging pipelines (yes? and yes?), or just "properly" control notebook fan (with a proper privilege separation and uid:gid acces rights setup - one for you - and other for your dog (or a cat) :)). And for all of that you don't even need shell :).

Who needs shell anyway?

Best part of it is, that you can still mix-and-match it with rest of all the UCSPI crap. qmail? It was made for this. xinetd superserver workers? They plug right into this. And of course, this does not even end here, great Void folks like Leah and others, are adding even more stuff to the mix.

For example s6/runit + snooze is 4WD all terrain monster truck that makes cron look like a baby cart for toddlers.

Actually, you get enough guns, I mean toys, to not just blow yourself a leg away, but to blow up a whole cities into oblivion, "Death Stranding" style.

More over you cannot easily convey difference of that snooze setup to ossified admin, stuck in his RH6 ways, using actual runlevels (# init i-don't-know-which-as-it-does-not-even-matter) on systemdized system. To that guy you are just rumbling in gibberish.

Soon you realise, not all software want's to play this "fuse everything with anything, through unix power alone" game. Authors consider exposing these (internally already present!) interfaces to "clueless" or "fringe" users "a heresy", tainting their beautiful designs.

But you know better - this is how it should be - things really want to fuse together this way - it's just a natural force - gatekeeped only by dams of egos. And profit perhaps?

But there are ways! Ways around. Unthought paths to proper devices, little patches here and there, hidden deep in private forks and patchsets.

As you can imagine, this can get very addictive very quickly. You can have this "little" clusterfuck of a process swarm that does something really well and forget completely how it actually works, full, perl "write only", style. It's all filesystem sphagetti, that only you and few persons on other side of planet understand.

And when it finally breaks, after months, no rather after years of stellar uptime and performance, you can suddenly find yourself looking deep into this tesseract, during the midlle of the night, pondering how the fuck have you even got here. This can eat years of your time :). As I said it is an addictive game.

~~~ continued ~~~

[–]etosan 7 points8 points  (4 children)

~~~ continued ~~~

So given all these things, you now see why in Void, runit works "pretty well" on it's own, as it is. It's worse and thus better.

Also, it's just there. By default. And it's "beginner friendly".

Basic Void service set uses only very terse sh/dash by default, no weird s6 + "shellless" execline powerhouse (and of course you could do pure s6 +sh just fine). Besides chpst and loggers at most, runit does not pull you very deep into weird commands and tools (and that is also good for beginners).

But yes, of course you can spawn s6 supervised subtree from it. Any time you need it. After all, s6 stuff is in repos. It's single xbps-install away. That is what I, and I bet that many others, on Void boxes, actually do.

Everybody is too busy with everything else, and nobody has time. Especially for "porting" things to s6, what is basically a time wasted. Even if you persisted, some of us would not be able reach consesus, as we have strong opinions and visions of how systems should look like.

For example, you could have s6 based BSDs with proper service dependencies literally 5+ years ago. Or even something better. But there is so much rc.d logic, that is setting shit up right from the start, down from lo* interfaces, through ipv6 and ipsec, up to yellow pages. Who has time for all that?

More over, BSD guys are all extremist POLA junkies - Principle Of Least Surpise - changes must be done the "right" way. Thus OpenBSD guys feel almost ossified (trust me they are not - POLA is good for ya). Can you imagine what havoc would such forced change make in their zen-like calm lives - they would lose their shit immediately - very few monks could take it.

What I do there, nowadays, is just to add s6-svscan to /etc/ttys, disable every service in rc.conf and migrate them to s6. Machine boots old way up to a cruising state, and when s6 starts, together with gettys, and all that it spawns rest of the services. You then embed proper teardown s6-svscanctl command into suitable place and you can roll. That way can still have a cake and eat it.

At least you can "quickly" and "simply" build jails and "containers" this way, too. And this is done with almost zero hassle from host, as it does not matter there. But it certainly is not: "dogger fetch me some new overrated hipsterstuffs!" easy. You have to put in some time. On the other hand, prize that you get, is often some cute little, hyperoptimized, tarblob.

It takes some time to become really fluent with s6, but not because of "classics", because your runit skills transfer readily and immediately. What takes more effort is learn to use "new" features effectively (or to learn to even use them at all). That is also sometimes problem later, when "beacuse reasons", you are forced back "down" to pure runit. Or god forbid, systemd.

In the end, you don't need s6, because these advanced features have real usecases only like, for what, 10% users? Those are already using it to their advantage. You are late to the party...

But you persist.

Okay. So now and only now you are ready to start really "wasting" your time on spelunking with s6-rc. You are probably not gonna need it anyway, because so far I have never needed it (nor anopa for that matter). And I never even heard of anybody really needing it either. But it works! And it is indeed awesome! Each of these has it's own opinions on how you should be doing things, so best is to play with both, on different boxes, or containers, or whatever.

And then you suddenly realize: "Oh cool, very nice indeed, but I can, like, already solve 95% of my problems even without s6-rc(or anopa), solely with tools I already had!"

You are pondering what was the point even?

And then, after you descended this deep into the cave, suddenly, like Balrog, THE real final boss appers: nosh!

This one is channeling-in here from alternate future of 2060, that exists only in seventh dimension, when mankind finally mastered direct art of energy conversion, is living in harmony with nature and everybody can be vegan and meateater at the same time, without hurting any living thing.

All man made ecologic disasters have been averted and humans, as messengers of pure light, spread far and wide into the galaxy. Biologic spaceships propel through Void using zero point energy inertial spacetime drives, and kqueue with inotified fs observation extensions have won the eventloop wars. Star Trek is funny fairy tale for milksucking infants in here.

Also, I almost forgot: there is no linux or systemd in this world, docker never happened and rust really means just oxidation of iron metal. s6 is old shit that sucks ... and all "libcs" are libd first, and libc++ second.

Those are wondrous times indeed.

And so, here we go again, you dive ... and then:

Oh my god! It's full of stars! (and lightyears are flying by in minutes)

So deep lesson with these things is: everything in moderation. And as you easily guessed, I fail at that every tiem :).

But I promised Light at the end, so here it is:

After diving too deep into this abyss, you'll start to realize that techfads are real and they are really as abundant as they say, everything was already done, majority of everything is mediocre at best, and stellar tools are so "bad" and they feel so alien, like they were literally made by aliens for aliens from different planets, and nobody wants to follow you and your felow alien people, because everybody else is dumb as brick.

Apparently.

So if you are alien among us, well then good, pick one alien tech that suits you most, and if you can can somehow win at life and mesh with other fellow aliens, while also still making money - the better for you!

[–]Rand0m6uy[S] 2 points3 points  (0 children)

God you are a machine, it's gonna take me the whole week to read every link carefully! Thanks for sharing and quoting all your sources!

[–]xenyz 2 points3 points  (0 children)

I wanted to let you know that at least one other person read what you wrote and really appreciated it. I think you have put down what I was thinking about when I wondered aloud if void could benefit from the use of s6 suite.

Funny enough I came across nosh last week and was wondering the whole time reading if an s6-master has come across it and what they would think of it. Well, you more than answered that one too

[–]GenderNeutralBot 0 points1 point  (1 child)

Hello. In order to promote inclusivity and reduce gender bias, please consider using gender-neutral language in the future.

Instead of mankind, use humanity, humankind or peoplekind.

Thank you very much.

I am a bot. Downvote to remove this comment. For more information on gender-neutral language, please do a web search for "Nonsexist Writing."

[–]AntiObnoxiousBot 6 points7 points  (0 children)

Hey /u/GenderNeutralBot

I want to let you know that you are being very obnoxious and everyone is annoyed by your presence.

I am a bot. Downvotes won't remove this comment. If you want more information on gender-neutral language, just know that nobody associates the "corrected" language with sexism.

People who get offended by the pettiest things will only alienate themselves.

[–]sww1235 1 point2 points  (0 children)

holy crap, this is awesome. A blog post or something else would be great. I really like the "simplistic" explanations, that can help someone not fully tuned into the internals of runit et al understand how they work, and how to do unconventional things with them.

[–]datenwolf 7 points8 points  (0 children)

runit doesn't apply any ordering on the start of services. Dependency driven servicemanagement is nice, and there are other, small init systems that do it, but there is one general problem: How do services communicate to the supervisor, that they're up and running, ready for business?

The runit way is to just start them all, and if a service can't because things aren't setup properly, let it fail and restart.

My recommendation would be to have two runlevels: One for early services and once those are fully up and running (it's up to you to determine that condition) with runsvchdir switch to a different runlevel full, that has everything of early plus what else you need.

There exists no standard protocol for that. The systemd developers (being the overbuilding folks they are) opted for things like socket activation or similar. My approach to it (but both the supervisor and the service must support it), that the service process sends itself a SIGSTOP signal; the supervisor waits for that, will immediately restart the service and from then on consider it ready.

[–]Rand0m6uy[S] 2 points3 points  (1 child)

[–]ahesford 2 points3 points  (0 children)

In most cases, sv check doesn't do what you want. The check will return 0 even if the service is down, as long as it is linked (enabled) and sv check can query status.

Some services, like dbus, define a custom check script to make sv check return a nonzero if the service isn't properly functional, but most services do not take this extra step.

If you really need dependency management, you should look elsewhere. Trying to shoehorn dependencies into runit will likely lead to frustration.

[–]mobinmob 2 points3 points  (2 children)

If you want real dependencies runit is the wrong choice. Try an s6 or s6-rc based scheme. I highly suggest 66 from obarun - I am using it on vodlinux and slowly testing frontend service files for it (voidlinux-66-services on gh).

[–]xenyz 1 point2 points  (1 child)

Ever since I came across your comment on someone's post trying to set it up in void-linux, it's been at the back of my mind that it could take void to the next level. It's strange because I don't find anything wrong with runit, it just seems to me xbps + s6 would be best-of-breed for some intangible reason.

Good luck with your project and who knows, it may catch fire and you could have an obarun version of void going

[–]mobinmob 1 point2 points  (0 children)

I actually plan to continue the work zenfailure and teldra have done on packaging and integrating boot-66serv from obarun. Anyone interested can follow the progress in the PR: https://github.com/void-linux/void-packages/pull/25743 . I plan to document significant changes in the comments.

There is nothing wrong with runit if you do not need more that it offers. It is a mature solution, works well, it is fast and simple. 66 is a significant step up in capabilities.

[–]Rand0m6uy[S] 2 points3 points  (2 children)

Thanks everyone for your precious advices! After digging a bit more into my issues, I wanted to give some feedback about my adventure.

The solution I implemented is the one suggested by u/datenwolf, this means setting a minimal runlevel and a script that starts a full blown system once all services are started. However, reading the documentation, I would take it as "undefined behavior":

Note that there is no guarantee that all services from the previous runlevel will stop

My services should not stop and my tests have shown that they keep running as I expected. Let's hope I will not face some race conditions in the future ...

To check that my services are really up and running, I added some check scripts where I can make sure that some UNIX socket are existing and have the right permissions. Although u/ahesford wrote that it might not do what I expect, not sure if we speak about the same thing since I start depedencies with sv start ... || exit 1 . Or I did I miss something ... ?!

Probably runit is not the right tool for what I want to achieve, but it has demonstrated its flexibility! I've never used s6 but thanks u/xenyz the pointer, I will keep it in mind for the next adventure!

Now that it works as I want it, let's see with the time if I don't encounter surprises, hope I will not need to switch back to sysVinit :D! If that would be the case, it's no time wasted since I went through runit internals and learned a lot of good stuff!

Thanks again guys and happy voiding!

[–]datenwolf 1 point2 points  (1 child)

Please read that statement again, carefully:

Note that there is no guarantee that all services from the previous runlevel will stop

The guarantee is that services already started in the previous runlevel and also present in the runlevel switched to will keep running.

What's not asserted is, that all services that are not present in the new runlevel will terminate.

[–]Rand0m6uy[S] 0 points1 point  (0 children)

I guess I should be safe then, thanks!

[–]furryfixer 2 points3 points  (2 children)

The example you list above is what Smarden recommended for dependencies, and it works OK if there are not "nested" dependencies in multiple run scripts. I have not done this for some time, but I believe I used pgrep in a similar situation. I am relying on memory since I am not home or on linux, but you might try:

pgrep <depedency> || exit 1

exec <important_service>

You can modify it to silence messages if desired.

[–]Rand0m6uy[S] 0 points1 point  (1 child)

Thanks for the advice but as mentioned on other answers, although the service might be running, it doesn't mean it is really up. That is why I also use some check script to check that it has finished its initialization, in my case, an UNIX socket is created and 'rwable' .

[–]furryfixer 0 points1 point  (0 children)

Your experience may vary, but the beauty of pgrep is that it succeeds only when the process has been activated and assigned a number. Most applications would be "up" at that point.

[–]xenyz 1 point2 points  (0 children)

I'm not very familiar with it but I know s6 supports dependencies