nixos-core is a multi-call binary implementing core NixOS system utilities in
safe, portable Rust, replacing some of the Perl, Bash and Python scripts that
are generally load-bearing for safety and performance. While some of those
scripts _can_ be replaced through various means (such as the Perlless and
Bashless profiles in Nixpkgs) the available methods usually change how your
system behaves, and may yield general instability or even breakage as is the
case with the /etc overlay system.
This repository provides a multi-call 1 Rust binary that aims to replace some of the fragile and occasionally undesirable Bash, Perl and Python scripts as well as some utilities that were already written in Rust in a fast, portable and easy-to-audit codebase. See the motivation section for why we also try to replace Rust code with more Rust code, or why this repository exists in the first place.
The NixOS module and the nixos-core crate replaces legacy Bash and Perl
scripts that execute on every NixOS system during boot and activation. While the
amount of Bash and Perl has been steadily decreasing in Nixpkgs, non-Systemd
spins of NixOS don't always get the same luxury. Thus, nixos-core aims to be the
one-size-fits-all that you can use on both NixOS and spins of NixOS requiring a
solution to those common problems.
The multi-call design of nixos-core, like BusyBox, reduces binary size and
simplifies deployment so that it can fit on your NixOS or NixOS-like system. The
project is intentionally modular to fit the particular needs of any user through
the NixOS module and
feature flags.
The scope of this program is currently limited to what Nixpkgs permits in terms of replacing the legacy solutions cleanly through the module system. Here is a general overview of what can currently be replaced:
| Command | Original Component | Purpose | Status |
|---|---|---|---|
update-users-groups |
update-users-groups.pl |
Manage /etc/passwd, /etc/group, /etc/shadow |
First-class |
setup-etc |
setup-etc.pl |
Atomically update /etc/static |
First-class |
init-script-builder |
init-script-builder.sh |
Create generic /sbin/init |
First-class |
stage-1-init |
stage-1-init.sh |
Initrd bootstrap | First-class |
stage-2-init |
stage-2-init.sh |
System activation | First-class |
persist |
Impermanence | Project persistent paths into an ephemeral root | First-class |
While the project is rather new, base rewrites are complete and mostly verified
through VM tests and even in real systems. If you're interested in testing
nixos-core on your system, but are skeptical, you may be interested in
enabling it gradually through the NixOS module knobs and test each individual
component. As it stands, nixos-core does not change anything that can "brick"
your system, i.e., you can easily roll back a generation in case something goes
wrong.
To install nixos-core on a flake-based NixOS system, add this repository as an
input, import its NixOS module, and enable it in your host configuration. For
example, in your system's flake.nix:
{
inputs = {
# ...
nixos-core.url = "github:manic-systems/nixos-core";
# ...
};
outputs = { nixpkgs, nixos-core, ... }: {
nixosConfigurations.<my-hostname> = nixpkgs.lib.nixosSystem {
modules = [
# Add nixos-core's NixOS module, and enable `system.nixos-core` to
# replace components.
nixos-core.nixosModules.default
{ system.nixos-core.enable = true; }
];
};
};
}Important
Un-tagged releases of nixos-core should be considered unstable. If you are interested in stable releases and in updating on your own schedule, please consider using a specific tag in your flake input, and update after reviewing the changes yourself.
Once you add the NixOS module for nixos-core, switch your system via nh or
nixos-rebuild and then reboot to use the new boot-stage components. You can
enable individual replacements gradually through system.nixos-core.components
instead of switching them all on at once; the module cannot be enabled
alongside system.nixos-init, /etc overlay, Userborn, or systemd-sysusers.
Take a look at the NixOS module for the exact incompatabilities and override
behaviour.
The nixos-core crate provides a multi-call binary invocable either as a
symlink:
# Invoke the update-users-groups command
$ update-users-groups /nix/store/...-users-groups.jsonOr explicitly as a subcommand:
# Subcommand of update-users-groups
$ nixos-core update-users-groups /nix/store/...-users-groups.jsonThis usage pattern, like the mono-repo design, is a deliberate choice that allows reusing code and shared patterns without the cognitive overhead of cross-referencing separate projects. It also lets us provide a relatively small binary that does everything.
All commands provided by the nixos-core binary are enabled by default, but
also feature-gated through Rust's feature flags. This design lets you disable
commands that you don't want or need at build time and choose which binaries
to install, and what components to replace. Feature flags work both with cargo
(--features) and as package arguments when building with Nix:
[(prev: final: {
nixos-core = prev.nixos-core.override {
withStage1 = false; # e.g., skip initrd bootstrap
}
})];Additional feature flags, also exposed as overridable package attributes,
control the behavior of stage 2. These are bootspec,
systemd-integration, and full-nixos-init-compat, providing complete
compatibility with the pre-existing behavior of Nixpkgs' stage 2. If you are
developing a systemd-less NixOS variant but still want to manage stage 2, you
can disable systemd-integration while retaining bootspec support. Most of
these features are described in detail in the stage2 crate's README.
nixos-core aims to be a safe, independent core utility for NixOS and NixOS
derivatives that build their own tooling from scratch, such as but not limited
to MicrOS and Finix. The main goals of this project is being a fast,
portable and consistent utilities written in clean Rust, from one coherent
codebase, and designed modular enough through feature flags and NixOS module
knobs to fit into any system and derivative project.
It is an out-of-tree module to meet those goals without the behavioral changes
imposed by Nixpkgs alternatives like Userborn, the /etc overlay, or
nixos-init. That is not to say those are fundamentally incompatible with this
project, but they are different. There may be room for collaboration in the
future, however, nixos-core ultimately supersedes all of the Nixpkgs-native Rust
rewrites in a more generic, reusable manner with less dogmatism involved.
We're always open to new contributions. Please keep in mind that as a critical system utility, nixos-core has strict contribution requirements. We expect you to write safe, principled Rust and test your code accordingly through unit and integration tests. If you have a good idea, but can't exactly be sure how to proceed you may open an issue to discuss your changes with us beforehand.
nixos-core is built with the latest stable Rust available in Nixpkgs, which is
1.98.1 at the time of writing. The Minimum Supported Rust Version (MSRV) is
set at 1.95.0, and may change as the language evolves.
- No
unsafecode except for unavoidable syscalls (crypt,geteuid) - Explicit error handling with the
?operator - Dry-run mode available for all destructive operations
Run the same Clippy check as CI across every workspace crate, target, and feature before submitting Rust changes:
# Run the Clippy lint suite
$ cargo clippy --workspace --all-targets --all-features -- -D warningsThis repository provides a few VM test to verify correct behaviour. Those are rather limited at the time but should provide some amount of guidance for you.
# Run all VM tests that are exposed under `checks`
$ nix flake check
# Run a specific VM tests
$ nix build .#checks.x86_64-linux.bootYou can find the available NixOS VM tests in the nix/vm-tests directory. If you're adding new features, you should add a new test component or some subtests that verify that your code works exactly as expected. There is also this neat little command you can use to get a list of checks supported by each system:
# Get a list of checks supported by each system. For now all systems
# support the same group of checks, but it might differ in the future.
$ nix eval .#checks --apply 'x: builtins.mapAttrs (_: builtins.attrNames) x' --jsonPipe it into jq for a nicer result.
This project is derived from Nixpkgs, originally licensed under the MIT License.
Original copyright and MIT license text are preserved in licenses/MIT.txt
located in the repository root. This project and all project code/documentation
are distributed under the Mozilla Public License (MPL) version 2.0. See
LICENSE for more details on the exact conditions. An online copy is
provided here.
Footnotes
-
Multicall in Unix refers to a type of binary that allows multiple commands to be executed through a single executable, depending on the name used to invoke it. See here for a more comprehensive explanation. Historically multicall binaries have also been called "BusyBox-like", which should probably be what you're thinking of when you hear "multi-call" for the purposes of this project. ↩