Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/update-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: update-images
on:
workflow_dispatch:
schedule:
- cron: "0 5 * * 1" # runs weekly every Monday at 05:00 UTC

jobs:
bump-images:
runs-on: "ubuntu-latest"
steps:
- name: "Checkout the repo"
uses: "actions/checkout@v6"
- name: "Install the Nix package manager"
uses: "cachix/install-nix-action@master"
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: "Refresh container test cloud image pins"
run: tools/update-images.py
- name: "Create Pull Request"
uses: "peter-evans/create-pull-request@v8"
with:
branch: "auto_update_images"
title: "Bump container test cloud image pins"
body: |
Automatically refreshed the cloud image URLs and sha256 hashes in
`lib/container-test-driver/images.json` by running
`tools/update-images.py`.
commit-message: "chore(container-tests): bump cloud image pins"
60 changes: 49 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,59 @@ Before creating a new issue, please [search existing issues](https://github.com/

## Adding New Distributions

System Manager officially supports Ubuntu and NixOS. To add support for another distribution:
System Manager officially supports Ubuntu, Debian, and NixOS.
Promoting a new distribution to officially-supported status means it is exercised by CI on every PR and a regression in it blocks the build.

1. Initialize a new flake with distribution checks disabled:
```sh
nix run 'github:numtide/system-manager' -- init --flake --allow-any-distro
```
### Trying a distribution informally

2. Switch to the new configuration:
```sh
nix run 'github:numtide/system-manager' -- switch --flake '.'
```
If you just want to run System Manager on an untested distribution without contributing it back, initialize a flake and disable the OS check by setting `system-manager.allowAnyDistro = true` in your configuration module:

```nix
{
config.system-manager.allowAnyDistro = true;
}
```

Then iterate with `nix run 'github:numtide/system-manager' -- switch --flake '.'` and debug any errors using the FAQ, GitHub issues, or a discussion.
Once the distribution is stable for your use case, consider upstreaming it via the steps below.

### Adding official support

Adding a distribution touches four areas: the OS allow-list, the container test driver, the VM test driver, and the documentation.

**1. Add the distribution ID to the OS allow-list.**
Edit `nix/modules/default.nix` and append the `/etc/os-release` `ID` value to the `supportedIds` list inside the `osVersion` pre-activation assertion.
The check is bypassed when users set `system-manager.allowAnyDistro = true`, but the allow-list is what controls the default.

**2. Add a container test entry.**
Container tests live under `testFlake/container-tests/` and are parameterized over every distribution declared in `lib/container-test-driver/distros.nix`.
Adding a new entry there causes all existing tests to automatically generate a `container-<distro>-*` variant via `forEachDistro`.

The entry must supply `systems`, a `rootfs` derivation built by `lib.container-test-driver.make-rootfs.buildRootfs`, and a `maskableService` (a systemd unit that test scripts may mask, typically `unattended-upgrades.service` or equivalent).

`buildRootfs` accepts three upstream image formats via `cloudImgFormat`, and the right choice depends on what the distribution publishes:

- `"tar"` (default) consumes a flat rootfs tarball such as Ubuntu's `*-server-cloudimg-amd64-root.tar.xz`. This is the simplest path, has no architecture restrictions, and should be preferred whenever the distribution ships a rootfs tarball.
- `"disk-tarball"` consumes a `.tar.xz` that wraps a raw disk image, such as Debian's `*-genericcloud-*.tar.xz`. It unpacks the outer tarball, locates the root partition with `sfdisk -J` + `jq`, extracts it with `dd`, and dumps the ext4 filesystem into `$out` via `debugfs -R "rdump / $out"`. All required tools (`util-linux`, `e2fsprogs`, `jq`) are cross-architecture in nixpkgs, so this works on both `x86_64-linux` and `aarch64-linux`. `excludePatterns` are applied as a post-extraction prune pass rather than as tar `--exclude` flags. Note: this currently assumes the root filesystem is ext4; a btrfs-backed rootfs (such as Fedora Workstation) would need a `btrfs restore`-based variant added alongside.
- `"qcow2"` extracts the rootfs from a qcow2 cloud disk image using `guestfish tar-out`. It pulls in `libguestfs-with-appliance`, whose `libguestfs-appliance` subpackage is marked `meta.platforms = [ "i686-linux" "x86_64-linux" ]` in nixpkgs, so entries using this format must restrict `systems` to `x86_64-linux`. Use only as a last resort, when the distribution publishes neither a rootfs tarball nor a disk-in-tarball variant.

Pin a specific dated build directory upstream rather than `latest/` and obtain the SHA256 with `nix-prefetch-url`. URL and hash go in `lib/container-test-driver/images.json`; `distros.nix` reads them automatically.

Reuse the existing `excludePatterns` (which strip container-incompatible systemd units) and `extraDirs` (per-package-manager directories like `var/lib/apt/lists/partial`) as a starting point and trim or extend them based on the first build.

**3. Add a VM test entry.**
VM tests live under `testFlake/vm-tests/` and iterate over distributions exposed by `nix-vm-test`.
Edit the `distros` attrset in `testFlake/vm-tests/default.nix` to add a key matching the `nix-vm-test` distribution name (`ubuntu`, `debian`, `fedora`, `rocky`).
Each entry takes a `filter` predicate that selects which versions to exercise — use it to skip versions you do not want in the matrix.
If `nix-vm-test` does not yet support the distribution, support must be added there first.

3. Debug any errors that occur. Refer to the FAQ, GitHub issues, or open a discussion for help.
**4. Run the test matrix and triage failures.**
Build the new check attributes via `nix build .#checks.x86_64-linux.container-<distro>-*` and `vm-<distro>-*-*` and triage any failures.
Prefer fixing tests to be distribution-agnostic over skipping them.

4. Once stable, the distribution can be added to the `supportedIds` list in the [system-manager module](./nix/modules/default.nix).
**5. Update documentation.**
The user-facing platform statement lives in `docs/site/reference/supported-platforms.md`, with secondary mentions in `README.md`, `docs/site/how-to/install.md`, `docs/site/tutorials/getting-started.md`, and `docs/site/how-to/test-configuration.md`.
Mention the new distribution alongside the existing supported ones.

## Creating an Ad-Hoc Release

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ You can find the [full documentation here](https://system-manager.net/main/).

## Quick Example to Get Started

We will assume you're using a non-NixOS distribution (such as Ubuntu) and you have Nix already installed, with flakes enabled.
We will assume you're using a non-NixOS distribution (such as Ubuntu, Debian, or Fedora) and you have Nix already installed, with flakes enabled.

System Manager has an "init" subcommand that can build a set of starting files for you. By default, it places the files in `~/.config/system-manager`. You can run this init subcommand by typing:

Expand Down Expand Up @@ -214,7 +214,7 @@ Then re-run System Manager and your changes will take effect; now you should hav

## Supported Systems

System Manager is currently only supported on NixOS and Ubuntu. However, it can be used on other distributions by enabling the following:
System Manager is currently supported on NixOS, Ubuntu, Debian, and Fedora. However, it can be used on other distributions by enabling the following:

```nix
{
Expand Down
23 changes: 17 additions & 6 deletions crates/system-manager-engine/src/activate/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,32 @@ pub fn lock_managed_users() -> Result<()> {
}

/// Resolves a base shell path (after prefix stripping) to an existing FHS location.
///
/// For nologin, always prefer `/usr/sbin/nologin` regardless of whether the
/// original base path also happens to resolve: on merged-usr distributions
/// (e.g. fedora) `/bin` is a symlink to `/usr/bin`, so a base of
/// `/bin/nologin` would `exists()`-check true via `/usr/bin/nologin` and we
/// would restore the wrong path, leaving `/etc/passwd` with `/bin/nologin`
/// instead of the `/usr/sbin/nologin` every distro ships.
fn resolve_shell(base: &str) -> Result<&str> {
if Path::new(base).exists() {
return Ok(base);
}

match base {
"/bin/nologin" | "/sbin/nologin" => {
for fallback in ["/usr/sbin/nologin", "/usr/bin/nologin", "/bin/nologin"] {
"/bin/nologin" | "/sbin/nologin" | "/usr/sbin/nologin" | "/usr/bin/nologin" => {
for fallback in [
"/usr/sbin/nologin",
"/usr/bin/nologin",
"/sbin/nologin",
"/bin/nologin",
] {
if Path::new(fallback).exists() {
return Ok(fallback);
}
}
anyhow::bail!("No valid nologin shell found for base path '{}'", base);
}
_ => {
if Path::new(base).exists() {
return Ok(base);
}
for fallback in ["/bin/sh", "/usr/bin/sh"] {
if Path::new(fallback).exists() {
return Ok(fallback);
Expand Down
2 changes: 1 addition & 1 deletion docs/site/how-to/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

To use System Manager, you need:

* **A Linux machine.** We've tested System Manager with Ubuntu both as standalone and under Windows Subsystem for Linux (WSL).
* **A Linux machine.** We've tested System Manager with Ubuntu (both as standalone and under Windows Subsystem for Linux) Debian 13 (trixie), and Fedora 43.
* **At least 12GB Disk Space.** However, we recommend at least 16GB, as you will be very tight for space with under 16GB. (This is primarily due to Nix; if you're using System Manager to configure, for example, small servers on the Cloud, 8GB simply won't be enough.)
* **Nix installed system-wide** with flakes enabled. (System Manager doesn't work with a per-user installation of Nix)

Expand Down
4 changes: 2 additions & 2 deletions docs/site/how-to/test-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ Test "Verify application files" (0.3s)
```python
start_all()

# Wait for Ubuntu systemd to be ready
# Wait for the distro's systemd to be ready
machine.wait_for_unit("multi-user.target")

# Activate system-manager configuration (displays full activation output)
Expand Down Expand Up @@ -196,7 +196,7 @@ with subtest("Verify service is responding"):

The test framework:

1. Builds an Ubuntu 24.04 container image with the nix-installer binary included
1. Builds a container image (Ubuntu 22.04, Ubuntu 24.04, Debian 13, or Fedora 43) with the nix-installer binary included
2. Starts the container using systemd-nspawn within the Nix build sandbox
3. Installs Nix via nix-installer at container startup (multi-user mode with daemon)
4. Copies your system-manager profile closure into the container via `nix copy`
Expand Down
8 changes: 4 additions & 4 deletions docs/site/reference/supported-platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ System Manager runs on Linux systems that use systemd for service management.
| Ubuntu 22.04+ | Tested | Primary development platform |
| Ubuntu on WSL2 | Tested | Windows Subsystem for Linux |
| NixOS | Tested | Works alongside existing NixOS configuration |
| Debian | Community | Should work; similar to Ubuntu |
| Fedora | Community | Should work; uses systemd |
| Debian 13 (trixie) | Tested | Container and VM tests |
| Fedora 43 | Tested | Container and VM tests without SELinux support |
| Arch Linux | Community | Should work; uses systemd |

## Requirements
Expand All @@ -29,7 +29,7 @@ System Manager runs on Linux systems that use systemd for service management.
## Platform detection

System Manager checks the platform at activation time using a pre-activation assertion that reads `/etc/os-release`.
By default, it only allows activation on Ubuntu and NixOS.
By default, it only allows activation on Ubuntu, Debian, Fedora, and NixOS.

### Enabling other distributions

Expand All @@ -44,7 +44,7 @@ To allow System Manager to run on untested distributions, set the `system-manage
```

This disables the OS check entirely.
There is no option to selectively allow specific distributions; the check is either on (default, allowing only Ubuntu and NixOS) or off.
There is no option to selectively allow specific distributions; the check is either on (default, allowing only Ubuntu, Debian, Fedora, and NixOS) or off.

## Limitations

Expand Down
2 changes: 1 addition & 1 deletion docs/site/tutorials/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

If you've heard of NixOS, you've probably heard that it lets you define your entire system in configuration files and then reproduce that system anywhere with a single command. System Manager brings that same declarative model to other Linux distributions*, with no reinstalling, no switching operating systems, and no special prerequisites beyond having Nix installed.

*Presently, System Manager is only tested on Ubuntu, and is limited to only Linux distributions based on systemd.
*Presently, System Manager is tested on Ubuntu, Debian, and Fedora, and is limited to only Linux distributions based on systemd.

# Initializing Your System

Expand Down
94 changes: 60 additions & 34 deletions lib/container-test-driver/distros.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
}:
let
makeRootfs = import ./make-rootfs.nix { inherit pkgs system; };
images = builtins.fromJSON (builtins.readFile ./images.json);

fetchCloudImg =
distroName:
let
entry = images.${distroName}.${system} or (throw "Unsupported system for ${distroName}: ${system}");
in
builtins.fetchurl {
inherit (entry) url sha256;
};

ubuntuExcludePatterns = [
"etc/systemd/system/network-online.target.wants/*"
Expand All @@ -23,51 +33,67 @@ let
in
{
ubuntu-22_04 = {
systems = [
"x86_64-linux"
"aarch64-linux"
];
systems = builtins.attrNames images.ubuntu-22_04;
rootfs = makeRootfs.buildRootfs {
name = "ubuntu-22_04";
cloudImg =
if system == "x86_64-linux" then
builtins.fetchurl {
url = "https://cloud-images.ubuntu.com/releases/jammy/release-20260227/ubuntu-22.04-server-cloudimg-amd64-root.tar.xz";
sha256 = "05gw1sspv9d4m5yazc8105yc2vr3y9xkwnwilnzn774w9nivwib3";
}
else if system == "aarch64-linux" then
builtins.fetchurl {
url = "https://cloud-images.ubuntu.com/releases/jammy/release-20260227/ubuntu-22.04-server-cloudimg-arm64-root.tar.xz";
sha256 = "1aya4ainn5289bhczbx97dxv7ck8ng3kmz8yiicz8ynvyfg6mvrq";
}
else
throw "Unsupported system: ${system}";
cloudImg = fetchCloudImg "ubuntu-22_04";
excludePatterns = ubuntuExcludePatterns;
extraDirs = [ "var/lib/apt/lists/partial" ];
};
maskableService = "unattended-upgrades.service";
};

ubuntu-24_04 = {
systems = [
"x86_64-linux"
"aarch64-linux"
];
systems = builtins.attrNames images.ubuntu-24_04;
rootfs = makeRootfs.buildRootfs {
name = "ubuntu-24_04";
cloudImg =
if system == "x86_64-linux" then
builtins.fetchurl {
url = "https://cloud-images.ubuntu.com/releases/noble/release-20251026/ubuntu-24.04-server-cloudimg-amd64-root.tar.xz";
sha256 = "0y3d55f5qy7bxm3mfmnxzpmwp88d7iiszc57z5b9npc6xgwi28np";
}
else if system == "aarch64-linux" then
builtins.fetchurl {
url = "https://cloud-images.ubuntu.com/releases/noble/release-20251026/ubuntu-24.04-server-cloudimg-arm64-root.tar.xz";
sha256 = "1l4l0llfffspzgnmwhax0fcnjn8ih8n4azhfaghng2hh1xvr4a17";
}
else
throw "Unsupported system: ${system}";
cloudImg = fetchCloudImg "ubuntu-24_04";
excludePatterns = ubuntuExcludePatterns;
extraDirs = [ "var/lib/apt/lists/partial" ];
};
maskableService = "unattended-upgrades.service";
};

fedora-43 = {
# x86_64-linux only: the qcow2 extraction path uses libguestfs-with-appliance,
# whose appliance subpackage is not available on aarch64 in nixpkgs.
systems = [ "x86_64-linux" ];
rootfs = makeRootfs.buildRootfs {
name = "fedora-43";
cloudImgFormat = "qcow2";
cloudImg = builtins.fetchurl {
url = "https://dl.fedoraproject.org/pub/fedora/linux/releases/43/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-43-1.6.x86_64.qcow2";
sha256 = "0bxbr2kf6ija4rg36mnspg5qbk0767bn44133zfdilkwm7478rc4";
};
excludePatterns = ubuntuExcludePatterns ++ [
# systemd-firstboot blocks boot waiting for interactive configuration
"usr/lib/systemd/system/systemd-firstboot.service"
# auditd/audit-rules fail inside nspawn (no audit netlink)
"usr/lib/systemd/system/auditd.service"
"usr/lib/systemd/system/audit-rules.service"
# NetworkManager-wait-online blocks multi-user.target inside nspawn
"usr/lib/systemd/system/NetworkManager-wait-online.service"
];
extraDirs = [
"var/cache/dnf"
"var/lib/dnf"
];
# Pre-seed firstboot markers so systemd considers the container already configured.
extraSetup = ''
: > $out/etc/machine-id
echo 'LANG=C.UTF-8' > $out/etc/locale.conf
echo 'KEYMAP=us' > $out/etc/vconsole.conf
'';
};
maskableService = "dnf-automatic.timer";
};

debian-13 = {
systems = builtins.attrNames images.debian-13;
rootfs = makeRootfs.buildRootfs {
name = "debian-13";
cloudImgFormat = "disk-tarball";
cloudImg = fetchCloudImg "debian-13";
excludePatterns = ubuntuExcludePatterns;
extraDirs = [ "var/lib/apt/lists/partial" ];
};
Expand Down
32 changes: 32 additions & 0 deletions lib/container-test-driver/images.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"debian-13": {
"aarch64-linux": {
"sha256": "02jhbgc97kapbdnk8ny6ql12g9xvi34jpirp5kxzgcz1b5wabwg4",
"url": "https://cloud.debian.org/images/cloud/trixie/20260413-2447/debian-13-genericcloud-arm64-20260413-2447.tar.xz"
},
"x86_64-linux": {
"sha256": "049a06pb4bqpmy6qp3jgzblrwim9i492153mcglm53jngh48r5p5",
"url": "https://cloud.debian.org/images/cloud/trixie/20260413-2447/debian-13-genericcloud-amd64-20260413-2447.tar.xz"
}
},
"ubuntu-22_04": {
"aarch64-linux": {
"sha256": "0fg2jv8wi5cqlw0sl07xi3jmyyk1056pzybfqgpxs3nx7crhajlf",
"url": "https://cloud-images.ubuntu.com/releases/jammy/release-20260320/ubuntu-22.04-server-cloudimg-arm64-root.tar.xz"
},
"x86_64-linux": {
"sha256": "1dafwd805bh4c243g7p16jixb52amh09p23ibx1lyj4k50d5yrad",
"url": "https://cloud-images.ubuntu.com/releases/jammy/release-20260320/ubuntu-22.04-server-cloudimg-amd64-root.tar.xz"
}
},
"ubuntu-24_04": {
"aarch64-linux": {
"sha256": "07vjiwx0smm6hyahi8w9y54zyxbpczfaqh75ihvi4msmmhbwb0m6",
"url": "https://cloud-images.ubuntu.com/releases/noble/release-20260321/ubuntu-24.04-server-cloudimg-arm64-root.tar.xz"
},
"x86_64-linux": {
"sha256": "0xyp9pin8m6ys0gxk6spq83dgdfl3malbvps7v78hrp7sjg3rp0k",
"url": "https://cloud-images.ubuntu.com/releases/noble/release-20260321/ubuntu-24.04-server-cloudimg-amd64-root.tar.xz"
}
}
}
Loading