chipsenkbeil / service-manager-rs

Provides adapters to communicate with various operating system service managers like launchd and systemd
Apache License 2.0
188 stars 15 forks source link

Support runit #1

Open chipsenkbeil opened 2 years ago

chipsenkbeil commented 2 years ago

I didn't think this was still used since wikipedia mentions the last stable release was in 2014, but was mentioned that it's used on busybox systems. Would need to see if we could get a test environment for this one.

LoganDark commented 2 years ago

Would need to see if we could get a test environment for this one.

If you don't mind following LFS-jr, the KISS Linux installation guide will take up an afternoon and leave you with a functioning system that uses runit. After that, the service management documentation will tell you how sv works.

I'm not entirely sure how coupled Runit and sv are, or how easy it is to tell when it's in use versus merely being installed on the system (since sv comes with all Busybox installations). I do happen to have a mostly working KISS system but I rarely use it because it doesn't detect my trackpad yet (stupid kernel regressions).

chipsenkbeil commented 2 years ago

It looks like Void Linux uses runit as the official system and it has a docker container, so maybe it could be plugged into a github action.

https://docs.voidlinux.org/config/services/index.html


I'm not entirely sure how coupled Runit and sv are, or how easy it is to tell when it's in use versus merely being installed on the system (since sv comes with all Busybox installations)

The checks today are fairly naive, not actually determining which system is being used, only that they're available. For my purposes, if there are multiple systems installed, I would require the user to specify which they want to use.

LoganDark commented 2 years ago

if there are multiple systems installed, I would require the user to specify which they want to use.

This isn't in control of the developer so I think you should just improve your checks.

Say hello to Bedrock Linux which makes multiple distros run on the same computer at the same time! You might have systemctl installed but be using Runit anyway. Hell, you might have sc.exe installed AND systemctl, because you could be running in WSL. Yes, the exe will be in PATH and it will be natively executable. WSL does this.

paradigm commented 2 years ago

I'm certainly sympathetic to the desire to avoid scope creep and retain the constraint that, if multiple service managers are installed, the user must specify which. I don't intend to add pressure here.

However, should LoganDark's request be heeded, I think it worth noting that most Linux systems with multiple service managers installed (including Bedrock Linux) have one which is privileged as the "main" one for the given session. At least for most inits on most Linux systems,I don't think detecting this and defaulting to it would be terribly difficult. Tedious to implement and test for every supported service manager, maybe, but not difficult.

Typically a Linux system is initialized by a service manager with PID 1. Information about this process can be queried via the /proc/1/ directory. If you're running as root (which I think would be the case for this project most of the time?) you can readlink() /proc/1/exe to get the path to the binary providing the init. If the filename is systemd, the system is running systemd, if it's runit it's runit, etc. If you're not running as root, /proc/1/cmdline is also available, which is a null-separated list of arguments used to launch the init. I'm not sure if it's as reliable as /proc/1/exe, but you can read out its first null-separated field and check if the command is systemd, runit, etc.

The main exception is OpenRC, at least as I'm familiar with it on Gentoo systems. PID 1 is actually a SysV binary (just named init via the /proc/1/ checks) which launches OpenRC as a separate, non-PID-1 process. If there's no interest in supporting SysV, the above described /proc/1 checks could detect init and assume the system is OpenRC. If there's interest in eventually supporting either SysV flavor (e.g. traditional with MXLinux or BSD-style with Slackware) then things get trickier. Scanning /proc/* for an OpenRC process or parsing SysV configuration to see if it is launching OpenRC is doable but less trivial than just checking a file in /proc/1.


With regards to Bedrock Linux's need for this functionality, I think there's a few things to note:

LoganDark commented 2 years ago

I'm certainly sympathetic to the desire to avoid scope creep and retain the constraint that, if multiple service managers are installed, the user must specify which.

My main concern is the question of "who is the user here?". Certainly the developer using this library shouldn't choose which service manager to use, unless they know for sure. So this would be the user running the end application, all the way down, where actually specifying is difficult. How will the library get their input? Check for an environment variable and abort the process if it's not present? What will downstream crates think of that? Maybe shift the responsibility of asking for input onto the developer? That would be mildly inconvenient for everyone.

I think an auto-detection mechanism is legitimately the easiest method that provides the greatest ROI, not just for this library, but for all its downstream consumers (users and developers alike).

chipsenkbeil commented 2 years ago

Thanks @paradigm and @LoganDark for all of the wonderful feedback and insight! I really value this kind of stuff, and it's already highlighted an area (auto detection) that I hadn't considered in depth.

I'm certainly sympathetic to the desire to avoid scope creep and retain the constraint that, if multiple service managers are installed, the user must specify which. I don't intend to add pressure here.

No pressure added! I have no qualms about supporting a more indepth attempt at determining the active manager :) I just lack the knowledge in many of these platforms to implement that myself.

For the moment, this library does the minimum I need for my primary project of distant, but I can revisit this later or more than happy to accept a contribution for this.

Typically a Linux system is initialized by a service manager with PID 1. Information about this process can be queried via the /proc/1/ directory.

Good to know! I'd seen in passing the PID 1 requirement for Linux service managers when I was setting up some of the CI testing, but didn't give it much thought. In the past, I've used the sysinfo crate to retrieve process information, so maybe I can use it to query for more information.

If you're running as root (which I think would be the case for this project most of the time?)

By default, you'll need elevated permissions to be able to write any of the files or execute commands. I've seen this when using my CLI to install services, start them, etc.

If you're using user-level installation and access, then you don't need an escalation. With systemd, this is systemctl --user and launchctl on Mac can run regardless and just detects user-level configurations.

I think it's out of scope to request elevated privileges through the library, but there are crates like deelevate on Windows and sudo on Unix systems.

My main concern is the question of "who is the user here?". Certainly the developer using this library shouldn't choose which service manager to use, unless they know for sure. So this would be the user running the end application, all the way down, where actually specifying is difficult. How will the library get their input? Check for an environment variable and abort the process if it's not present? What will downstream crates think of that? Maybe shift the responsibility of asking for input onto the developer? That would be mildly inconvenient for everyone.

For my usecase, the user I'm referring to is the user of my CLI. So from that perspective, it goes library -> cli -> end user where end user has the option to select an alternative service manager from the default by providing a CLI parameter or config file setting.

I recognize that this isn't something that every Rust developer is going to do, so auto-detection would be a nice to have. Not ruling it out, but I don't have the depth of knowledge on the Linux/Unix side to do this over the span of a couple of days! 😄 Would love help here if you're interested in offer ❤️

LoganDark commented 2 years ago

a CLI parameter or config file setting.

You won't have access to this from a Rust library. You don't do argument or config file parsing. So this won't work. You'd need to get the developer to add integration manually, which is annoying for them :(

paradigm commented 2 years ago

Thanks @paradigm and @LoganDark for all of the wonderful feedback and insight! I really value this kind of stuff, and it's already highlighted an area (auto detection) that I hadn't considered in depth.

Happy to help :)

Typically a Linux system is initialized by a service manager with PID 1. Information about this process can be queried via the /proc/1/ directory.

Good to know! I'd seen in passing the PID 1 requirement for Linux service managers when I was setting up some of the CI testing, but didn't give it much thought. In the past, I've used the sysinfo crate to retrieve process information, so maybe I can use it to query for more information.

While you could certainly use a specialized crate that provides the information if need be, you probably don't need one here if you're familiar with basic filesystem operations like reading files or the difference between a file name and a file path. /proc is intended to be interacted with as though it is a normal directory that just contains normal files. You'll probably figure it out quickly enough should you take the time to experiment with it.

Would love help here if you're interested in offer heart

I'm swamped with other projects and won't have time to actually submit something polished, but hopefully this is enough to get you started:

enum ServiceManager {
    Systemd,
    Runit,
    OpenRC,
}

impl std::str::FromStr for ServiceManager {
    type Err = std::io::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "systemd" => Ok(ServiceManager::Systemd),
            "runit" => Ok(ServiceManager::Runit),
            "init" => Ok(ServiceManager::OpenRC), // or possibly SysV
            _ => todo!(), // unrecognized init error
        }
    }
}

// Requires root
fn init_from_exe() -> ServiceManager {
    // path to init binary, e.g. `/sbin/init`
    let exe_path = std::fs::read_link("/proc/1/exe").unwrap();
    // init binary filename, e.g. `init`
    let exe_filename = exe_path.file_name().unwrap();

    exe_filename.to_str().unwrap().parse().unwrap()
}

// Does not require, maybe less reliable than init_from_exe?
fn init_from_cmd() -> ServiceManager {
    // Full command line with arguments, e.g. `/sbin/init foo bar baz`
    let cmd_line = std::fs::read_to_string("/proc/1/cmd").unwrap();
    // The command itself without args, e.g. `/sbin/init`
    let cmd_path = cmd_line.split('\0').next().unwrap();
    // The command's filename, e.g. `init`
    let cmd_filename = cmd_path.rsplit('/').next().unwrap();

    cmd_filename.parse().unwrap()
}
LoganDark commented 2 years ago

In KISS Linux, init is a symlink to busybox. Assuming you're following symlinks, busybox should also resolve to Runit.

paradigm commented 2 years ago

busybox provides both runit-style runsvdir and SysV-style init. KISS Linux may use busybox's runit functionality, but another distro may use its SysV functionality. A busybox PID 1 binary be like the init PID 1 binary where the init binary name isn't sufficient.

I may need to walk back my initial assertion that reliably auto-detecting the main service manager would be trivial. I think my relatively simple proposal stands for many distros, including most systemd distros and some runit distros, but not all.