Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ An atomic Linux host keeps its base system read-only and replaceable. You still

## What you get

- **Seven signed distros.** Debian 13, Ubuntu 26.04 LTS, Fedora 44, CentOS Stream 10, Arch, and openSUSE Tumbleweed and Leap 16.0, rebuilt weekly and verified against the Frostyard publishing workflow before use. [Machine images →](https://frostyard.github.io/nsl/reference/machine-images/)
- **Eight signed distros.** Debian 13, Ubuntu 26.04 LTS, Fedora 44, CentOS Stream 10, Arch, openSUSE Tumbleweed and Leap 16.0, and the Azure Linux 4.0 beta, rebuilt weekly and verified against the Frostyard publishing workflow before use. [Machine images →](https://frostyard.github.io/nsl/reference/machine-images/)
- **Your files and your account.** Your home, removable media and `/mnt` appear at `/mnt/host`, and you have your own username, UID and GID with passwordless `sudo`. [Host files →](https://frostyard.github.io/nsl/guides/host-files/)
- **Ports and windows on the host.** A server in a machine is reachable at the same port on host `127.0.0.1`, and Wayland applications open windows on your desktop. [Ports →](https://frostyard.github.io/nsl/guides/ports/) · [Desktop applications →](https://frostyard.github.io/nsl/guides/desktop/)
- **Your editor.** `nsl ssh-config` gives VS Code or any SSH client a host alias for a machine. [Editors over SSH →](https://frostyard.github.io/nsl/guides/editors/)
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ The binary implements the whole CLI contract: machines in the shared VM or, isol

- [Machines in a shared VM](plans/shared-vm-implementation.md) — implementation of ADR-0016 and ADR-0017; start here for new work.
- [Ubuntu, CentOS Stream and openSUSE Leap machine images](plans/more-machine-images.md) — three more machine images, accepted and published.
- [Azure Linux 4.0 machine image](plans/azure-linux.md) — Microsoft's Fedora-based beta in the rpm family.
- [Shared-VM experiment](plans/shared-vm-experiment.md) — machines as containers in one VM; evidence for ADR-0017.
- [Optional cloud-init provisioning](plans/cloud-init-provisioning.md) — deferred until cloud-init is re-validated in machines.

Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0009-distribution-neutral-guest-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Keep the host CLI and the VM agent distribution-neutral. Define one [machine-ima
Creation applies only per-machine data: account, hostname, time zone, `sudo` rule and nspawn settings.

- Fail clearly when a required capability is missing. Do not infer support from a distro name, `ID_LIKE`, a successful boot or a package install.
- Support a distro release only after its image passes acceptance. Debian 13, Fedora 44, Arch and openSUSE Tumbleweed came first; Ubuntu 26.04 LTS, CentOS Stream 10 and openSUSE Leap 16.0 followed. Track SUSE Linux Enterprise separately, including access and redistribution terms.
- Support a distro release only after its image passes acceptance. Debian 13, Fedora 44, Arch and openSUSE Tumbleweed came first; Ubuntu 26.04 LTS, CentOS Stream 10 and openSUSE Leap 16.0 followed, then Azure Linux 4.0. A release its distro still calls beta may join on the same terms, and the user documentation marks it as a beta. Track SUSE Linux Enterprise separately, including access and redistribution terms.
- Keep guest distro, host distro and CPU architecture independent. Start with x86-64.
- Every published image pins its recipe and integration revisions and records package inputs, provenance and acceptance results.
- Distro MAC policy does not apply inside machines. Document that plainly; do not claim a distro's SELinux or AppArmor behavior.
Expand Down
2 changes: 1 addition & 1 deletion docs/design/image-publication.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A self-hosted runner labeled `nsl-image-builder` supplies the host prerequisites

`scripts/publish-images.py build`:

1. runs `make ci`, then builds the VM image and the seven machine images from pinned recipes;
1. runs `make ci`, then builds the VM image and every machine image from pinned recipes;
2. runs `probe-vm.py` on the VM image;
3. runs `probe-machines.py` with every machine image, an isolated machine and `--gui`, so every declared capability is accepted: `gui` by the GUI check and `nesting` by the Podman check;
4. runs `measure-machines.py` with the Debian, Fedora, Arch and Tumbleweed images, the four that set the budget: four idle machines at or below 1,200 MiB without desktop sessions, as the experiment measured, and an additional machine at p95 ≤ 2 s. The budget comes from the publication runner's measurements ([ADR-0019](../adr/0019-persistent-publication-runner.md)). The acceptance report also records what four desktop sessions add, ungated;
Expand Down
44 changes: 44 additions & 0 deletions docs/plans/azure-linux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Plan: Azure Linux 4.0 machine image

**Status: Phase 1 complete, 2026-10-08. Phase 2 waits for a publication run.**

Add Azure Linux 4.0, Microsoft's Fedora-based cloud distribution, as a machine image in the `rpm` family. It joins the catalogue only after it passes the [machine-image acceptance](../specs/machine-images.md#acceptance), under [ADR-0009](../adr/0009-distribution-neutral-guest-contract.md).

Azure Linux 4.0 is a beta. Microsoft publishes it only under `packages.microsoft.com/azurelinux/4.0/beta/`, with `base`, `microsoft`, `nvidia` and `sdk` repositories; the image's own repository files enable `base` and `microsoft`. Packages and repository metadata are signed with the Mariner release key (`0CD9FED33135CE90`), the key mkosi already trusts for Azure Linux 3.0 through `distribution-gpg-keys`.

## Phase 1 — Profile and acceptance

- **Profile.** `image/machines/profiles/azurelinux/`, distribution `azure` (mkosi's name) and release `4.0`, in the `rpm` family with the Fedora 44 tools tree. Every rpm-family adapter package is in the beta `base` repository.
- mkosi v27's Azure Linux support builds `4.0/prod/REPO/ARCH` URLs, which do not exist for 4.0. `LocalMirror=` reads `4.0/beta/base/x86_64/` for the build and leaves the image's repository files to the distro.
- The pinned recipes (`68263d0`) have no Azure Linux recipe, so the profile names what Fedora's recipe adds that 4.0 packages: `dnf5`, `dbus-broker`, `glibc-langpack-en`, `iproute`, `iputils`, `pam`, `util-linux` and `vim-minimal`. A post-install script links `/etc/resolv.conf` to `/run/systemd/resolve/stub-resolv.conf`, as every recipe does.
- `azurelinux-release-common` requires `azurelinux-repos(4.0)`, which both `azurelinux-repos` and `azurelinux-repos-dev` provide. The dev package points dnf at an internal development feed with `gpgcheck=0`, so the profile names `azurelinux-repos`.
- **Probe.** Azure Linux 4.0 packages no `wayland-utils`, and its enabled repositories have none of galculator, zenity or foot. On Azure Linux, `probe-machines.py` lists the compositor's globals with a Python client of the Wayland protocol, and opens `gtk-lshw` from `lshw-gui`, a GTK 3 window that waits for an answer when run without `pkexec`.
- **Done when:** the image builds, and every check of `probe-machines.py --gui` passes on it beside a Debian peer with VM image r10.

**Result, 2026-10-08: complete.**

| Image | systemd | Size (zstd) | Entry latency, median |
| --- | --- | --- | --- |
| `nsl-machine-azure-4.0-x86-64-r1` | 258.4 | 71 MB | 62 ms |

- **Build.** The image built on the first attempt in a fresh builder, from `azurelinux-release` 4.0-26 and 138 packages, with the signed `azurelinux-repos` and the basic release identity. Its os-release reads `Azure Linux 4.0 (Four Beta)`. It is the smallest image in the catalogue.
- **Acceptance** (`azure-probe-1.json`, VM image r10 `511a0431…` built from this branch, `--gui`): an Azure Linux machine and the published Debian r5 image ran 24 checks, and all passed. On Azure Linux, systemd reached `running` with no failed units, every tally check passed, and the packages check installed Python, jq, Podman 5.8 and `lshw-gui` from the beta repository in 44 s. The Python client found 33 globals, including `wl_compositor` and `xdg_wm_base`, and `gtk-lshw` held a window open until the timeout. Creation took 12 s and the first start 1.4 s.

## Phase 2 — Publication

- Add the selectors `azurelinux:4.0` and `azure-linux:4.0` to the publisher, and list the image as a beta in the user documentation.
- **Done when:** a signed catalogue lists the image, and `nsl create` works from `azurelinux:4.0` on a host with an empty cache.

## Later / ideas

- Move to Microsoft's production repositories when Azure Linux 4.0 leaves beta. The image's `azurelinux-repos` follows the distro; the profile's `LocalMirror=` changes with it, or goes once mkosi builds the right URLs for 4.0.
- An Azure Linux recipe upstream in `nspawn/mkosi-definitions` would replace the profile's package list and post-install script.

## Open questions

- Should the probe list Wayland globals with its Python client for every image, and drop `wayland-utils` from the workload? It stays limited to Azure Linux until another distro lacks `wayland-info`.

## References

- Implements: [machine images](../specs/machine-images.md), [image publication](../design/image-publication.md), [ADR-0009](../adr/0009-distribution-neutral-guest-contract.md).
- Follows: [Ubuntu, CentOS Stream and openSUSE Leap machine images](more-machine-images.md).
1 change: 1 addition & 0 deletions docs/plans/more-machine-images.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Add the three machine images that [ADR-0009](../adr/0009-distribution-neutral-gu

- Further releases of the same families, such as Debian 12, Ubuntu 24.04 LTS, Fedora 43 or CentOS Stream 9, each as a profile that passes acceptance.
- AlmaLinux and Rocky Linux, which the recipes also carry, in the `rpm` family.
- Azure Linux 4.0, which the recipes do not carry: [its own plan](azure-linux.md).
- Track SUSE Linux Enterprise separately, including access and redistribution terms ([ADR-0009](../adr/0009-distribution-neutral-guest-contract.md)).

## Open questions
Expand Down
5 changes: 4 additions & 1 deletion docs/specs/machine-images.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ A machine image is one root filesystem tree, packaged as `rootfs.tar.zst` in a s
| `arch:rolling`, `arch:btw` | `arch` | No `/etc/pacman.d/gnupg` in the image: the recipe's keyring, with its master private key, is deleted after the recipe's own scripts run. `nsl-pacman-keyring.service` runs `pacman-key --init` and `--populate` on first boot. Fedora 44 tools tree. |
| `opensuse:tumbleweed`, `opensuse-tumbleweed:rolling` | `suse` | `shadow`, `timezone`, and `glibc-locale-base` for `C.UTF-8`; openSUSE Tumbleweed tools tree. |
| `opensuse:16.0`, `opensuse-leap:16.0` | `suse` | The suse family's packages; openSUSE Tumbleweed tools tree. |
| `azurelinux:4.0`, `azure-linux:4.0` | `rpm` | A beta: Microsoft publishes Azure Linux 4.0 only under `packages.microsoft.com/azurelinux/4.0/beta`, which the build reads directly, since mkosi expects `prod`. The recipes have no Azure Linux recipe, so the profile adds what Fedora's adds: `dnf5`, `dbus-broker`, `glibc-langpack-en` and base tools, and links `/etc/resolv.conf` to the VM's resolver. It names `azurelinux-repos`: `azurelinux-repos-dev` also satisfies the release package and points dnf at an unsigned development feed. Fedora 44 tools tree. |

Each adapter also names its family's `sudo`, CA certificates, OpenSSH server, DejaVu font, Adwaita cursors and Wayland client libraries.

Other distros and releases join after they pass acceptance ([plan](../plans/more-machine-images.md)).
Other distros and releases join after they pass acceptance ([plan](../plans/more-machine-images.md), [Azure Linux](../plans/azure-linux.md)).

### Machine layer

Expand Down Expand Up @@ -108,6 +109,8 @@ Workload checks:
| Translation | The working directory maps by device and inode, including a bind-mount alias; `/usr/share` is refused. |
| Persistence | Packages, home, an enabled system service and a Podman image survive a VM restart. |

Azure Linux 4.0 packages no `wayland-info`. On it, the probe lists the compositor's globals with a Python client of the Wayland protocol instead.

Checks that an image does not repeat the [hub image tally](../plans/shared-vm-experiment.md#image-source-tally-hubnspawnorg-or-our-own):

| Check | Expected |
Expand Down
10 changes: 5 additions & 5 deletions image/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
nsl builds its images from pinned [nspawn/mkosi-definitions](https://github.com/nspawn/mkosi-definitions) recipes with mkosi v27, inside a disposable Lima builder. There are two kinds ([ADR-0017](../docs/adr/0017-shared-vm-and-machine-images.md)):

- **The nsl VM image** (`vm/`): the Debian trixie disk that every nsl VM boots. It hosts machines as systemd-nspawn containers and runs the nsl agent. Contract: [VM image](../docs/specs/vm-image.md).
- **Machine images** (`machines/`): distro root filesystems that run as machines: Debian 13, Ubuntu 26.04 LTS, Fedora 44, CentOS Stream 10, Arch, openSUSE Tumbleweed and openSUSE Leap 16.0. Contract: [machine images](../docs/specs/machine-images.md).
- **Machine images** (`machines/`): distro root filesystems that run as machines: Debian 13, Ubuntu 26.04 LTS, Fedora 44, CentOS Stream 10, Arch, openSUSE Tumbleweed, openSUSE Leap 16.0 and Azure Linux 4.0, a beta. Contract: [machine images](../docs/specs/machine-images.md).

## Build the VM image

Expand Down Expand Up @@ -43,20 +43,20 @@ The composer copies the agent to `/usr/lib/nsl/nsl-agent` and writes `/usr/lib/n
## Build machine images

```sh
scripts/build-image.sh --role machine --distribution debian # also ubuntu, fedora, centos, arch, opensuse
scripts/build-image.sh --role machine --distribution debian # also ubuntu, fedora, centos, arch, opensuse, azure
scripts/build-image.sh --role machine --distribution opensuse --release 16.0 # Leap; opensuse alone is Tumbleweed
python3 scripts/probe-machines.py --nsl build/nsl --vm-image build/image/share/nsl-vm-trixie-x86-64-r9.raw \
--machine-image build/image/share/nsl-machine-debian-trixie-x86-64-r4.tar.zst \
--evidence build/image/evidence/machines-probe.json [--gui]
```

A machine build uses the recipe's container output without the disk profile, as a zstd tar: `nsl-machine-DISTRIBUTION-RELEASE-x86-64-rN.tar.zst`, with its manifest and the descriptor as built. Debian builds with the builder's own tools. Ubuntu, Fedora, CentOS Stream and Arch use a Fedora 44 tools tree, and Tumbleweed and Leap an openSUSE Tumbleweed one; the builder keeps mkosi's cache in `/var/cache/nsl-mkosi` between builds. Bump the profile's revision in `machines/profiles/NAME/profile.json` when inputs change.
A machine build uses the recipe's container output without the disk profile, as a zstd tar: `nsl-machine-DISTRIBUTION-RELEASE-x86-64-rN.tar.zst`, with its manifest and the descriptor as built. Debian builds with the builder's own tools. Ubuntu, Fedora, CentOS Stream, Arch and Azure Linux use a Fedora 44 tools tree, and Tumbleweed and Leap an openSUSE Tumbleweed one; the builder keeps mkosi's cache in `/var/cache/nsl-mkosi` between builds. Bump the profile's revision in `machines/profiles/NAME/profile.json` when inputs change.

`machines/` composes three layers:

1. `common/`: the machine layer. It holds the `nsl` PAM service, the nesting mount and its preset, the Podman drop-in, `nsl-path`, and `nsl-osc7.sh`, which reports the working directory to VTE terminals. Its finalize script, which runs after the recipes' own scripts, masks every networkd and resolved unit and disables SSH services. It also removes SSH host keys and the random seed, sets the machine ID to `uninitialized`, and fills in the descriptor.
2. `families/FAMILY/`: packages, the tools tree and family fixes, such as Arch's keyring deletion and first-boot `nsl-pacman-keyring.service`. Families are `debian` (Debian and Ubuntu), `rpm` (Fedora and CentOS Stream), `arch` and `suse` (Tumbleweed and Leap).
3. `profiles/NAME/`: the distribution, release and revision, and settings for that distribution alone, such as Ubuntu's tools tree.
2. `families/FAMILY/`: packages, the tools tree and family fixes, such as Arch's keyring deletion and first-boot `nsl-pacman-keyring.service`. Families are `debian` (Debian and Ubuntu), `rpm` (Fedora, CentOS Stream and Azure Linux), `arch` and `suse` (Tumbleweed and Leap).
3. `profiles/NAME/`: the distribution, release and revision, and settings for that distribution alone, such as Ubuntu's tools tree. The recipes have no Azure Linux recipe, so its profile names the packages and the `resolv.conf` link that a recipe would add, and reads Microsoft's beta repository.

Create a machine from a local build with `nsl create NAME --image FILE --digest sha256:HEX`. `probe-machines.py` runs the agent's entry matrix, the hub-image tally checks and the workload checks through the CLI.

Expand Down
20 changes: 20 additions & 0 deletions image/machines/profiles/azurelinux/mkosi.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[Distribution]
# Azure Linux 4.0 publishes only a beta channel, under beta/ rather than the prod/
# that mkosi's repositories assume. The build reads the base repository directly;
# the image's own repository files come from azurelinux-repos.
LocalMirror=https://packages.microsoft.com/azurelinux/4.0/beta/base/x86_64/

[Content]
# The recipes carry no Azure Linux recipe, so the profile names what Fedora's adds.
# azurelinux-repos-dev also provides azurelinux-repos(4.0) and points dnf at an
# unsigned development feed; naming azurelinux-repos keeps the signed one.
Packages=azurelinux-repos
dbus-broker
dnf5
glibc-langpack-en
iproute
iputils
pam
util-linux
vim-minimal
PostInstallationScripts=nsl-azure-postinst.chroot
5 changes: 5 additions & 0 deletions image/machines/profiles/azurelinux/nsl-azure-postinst.chroot
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh
# What the recipes do for every other distribution: machines bind the VM's
# resolver at /run/systemd/resolve.
set -eu
ln -sf ../run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
7 changes: 7 additions & 0 deletions image/machines/profiles/azurelinux/profile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"distribution": "azure",
"release": "4.0",
"architecture": "x86-64",
"family": "rpm",
"revision": 1
}
2 changes: 1 addition & 1 deletion scripts/build-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ while (($#)); do
(($# >= 2)) || { echo "Missing value for $1" >&2; exit 2; }
selection+=("$1" "$2"); shift 2 ;;
--help|-h)
echo 'Usage: scripts/build-image.sh --role vm | --role machine --distribution debian|ubuntu|fedora|centos|arch|opensuse [--release RELEASE]'
echo 'Usage: scripts/build-image.sh --role vm | --role machine --distribution debian|ubuntu|fedora|centos|arch|opensuse|azure [--release RELEASE]'
exit 0 ;;
*) echo "Unknown option: $1" >&2; exit 2 ;;
esac
Expand Down
1 change: 1 addition & 0 deletions scripts/compose-image.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
('ubuntu', 'resolute'): 'ubuntu',
('centos', '10'): 'centos',
('opensuse', '16.0'): 'opensuse-leap',
('azure', '4.0'): 'azurelinux',
}


Expand Down
Loading
Loading