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
152 changes: 152 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Every guarantee in ROADMAP.md was, until this file existed, a claim about
# something someone had run once on their own machine. This runs them.
#
# Split by cost. `check` is seconds and gates everything; the boot suites are
# minutes each and run in parallel with one another. aarch64 has no hardware
# acceleration on an x86_64 runner, so its boot goes through QEMU's JIT and is
# the slowest thing here by a wide margin.
name: CI

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

# A second push supersedes the first. Nothing here is worth finishing for a
# commit that is already history.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always
# A failed build should not also cost a stack trace nobody reads.
RUST_BACKTRACE: 1

jobs:
check:
name: fmt, clippy, host tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Add the musl targets
run: |
rustup target add x86_64-unknown-linux-musl
rustup target add aarch64-unknown-linux-musl

- uses: Swatinem/rust-cache@v2

- run: cargo fmt --all --check

# Both targets. The oxinit crate refuses to compile off Linux, and a
# lint that only ever ran against one architecture is a lint that has
# not run against the other.
- run: cargo clippy --target x86_64-unknown-linux-musl --workspace --all-targets -- -D warnings
- run: cargo clippy --target aarch64-unknown-linux-musl --workspace --all-targets -- -D warnings

# The library crates have no OS dependency and run on the host. The
# parser, the graph, the restart policy, the log format and the calendar
# arithmetic are where the logic errors are.
- name: Host tests
run: cargo test -p oxinit-unit -p oxinit-graph -p oxinit-service -p oxinit-cgroup -p oxinit-user -p oxinit-ipc -p oxinit-log -p oxinit-timer

boot:
name: boot (${{ matrix.arch }})
runs-on: ubuntu-latest
needs: check
strategy:
fail-fast: false
matrix:
arch: [x86_64, aarch64]
steps:
- uses: actions/checkout@v4

- run: rustup target add ${{ matrix.arch }}-unknown-linux-musl

- uses: Swatinem/rust-cache@v2

- name: QEMU and cpio
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends qemu-system-x86 qemu-system-arm cpio

# Keyed on the Alpine release the fetch is pinned to, so a bump
# invalidates it and nothing else does.
- name: Cache the boot artifacts
uses: actions/cache@v4
with:
path: |
target/vmlinuz-${{ matrix.arch }}
target/busybox-${{ matrix.arch }}
key: alpine-v3.22-${{ matrix.arch }}

- run: cargo xtask fetch --arch ${{ matrix.arch }}

- run: cargo xtask test-boot --arch ${{ matrix.arch }}

# The log is the whole evidence, and a failure that only says which
# needle was missing is a failure you have to reproduce locally to read.
- name: The serial log
if: always()
uses: actions/upload-artifact@v4
with:
name: serial-${{ matrix.arch }}
path: target/test-boot-${{ matrix.arch }}.log
if-no-files-found: ignore

container:
name: container
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v4
- run: rustup target add x86_64-unknown-linux-musl
- uses: Swatinem/rust-cache@v2

- run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cpio

- name: Cache the boot artifacts
uses: actions/cache@v4
with:
path: target/busybox-x86_64
key: alpine-v3.22-x86_64-shell

- run: cargo xtask fetch --arch x86_64

# Docker is already on the runner, which is the only thing this needs
# beyond the build.
- run: cargo xtask container

distro:
name: a real userspace
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v4
- run: rustup target add x86_64-unknown-linux-musl
- uses: Swatinem/rust-cache@v2

- name: QEMU and cpio
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends qemu-system-x86 cpio

- name: Cache the boot artifacts
uses: actions/cache@v4
with:
path: target/vmlinuz-x86_64
key: alpine-v3.22-x86_64-kernel

- run: cargo xtask fetch --arch x86_64

- run: cargo xtask test-distro

- name: The serial log
if: always()
uses: actions/upload-artifact@v4
with:
name: serial-distro
path: target/test-distro-x86_64.log
if-no-files-found: ignore
17 changes: 11 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ MSRV is stable minus two releases. No nightly features are used.
**Tools.** `qemu-system-x86_64`, `qemu-system-aarch64` and `cpio`. Docker or
podman as well, if you are running `cargo xtask container`.

**Boot artifacts.** A kernel and a static busybox per architecture, under
`target/`, where `xtask` looks for them by name — `vmlinuz-x86_64`,
`vmlinuz-aarch64`, `busybox-x86_64`, `busybox-aarch64`. Alpine publishes both:
a kernel under `releases/<arch>/netboot/vmlinuz-lts` and a `busybox-static`
package under `main/<arch>/`. `--kernel` and `--shell` override, and so do
`$OXINIT_KERNEL_<ARCH>` and `$OXINIT_SHELL_<ARCH>`.
**Boot artifacts.** One command per architecture:

```bash
cargo xtask fetch --arch x86_64
cargo xtask fetch --arch aarch64
```

That downloads a kernel and a statically linked busybox from Alpine into
`target/`, where `xtask` looks for them by name. `--kernel` and `--shell`
override, and so do `$OXINIT_KERNEL_<ARCH>` and `$OXINIT_SHELL_<ARCH>`, if you
would rather use a kernel you already have.

```bash
# Debian/Ubuntu
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ A service manager and PID 1 for Linux, written in Rust.

[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)](#license)
[![Status](https://img.shields.io/badge/status-pre--alpha-orange)](ROADMAP.md)
[![CI](https://github.com/youhide/oxinit/actions/workflows/ci.yml/badge.svg)](https://github.com/youhide/oxinit/actions/workflows/ci.yml)

**Pre-alpha. Nothing is stable.** See [ROADMAP.md](ROADMAP.md) for what works.

Expand Down Expand Up @@ -202,7 +203,7 @@ getting it wrong strands the machine.

## Current state

Milestones 0 through 11 are done. oxinit boots under QEMU and runs as a
Milestones 0 through 12 are done. oxinit boots under QEMU and runs as a
container's PID 1.

| | |
Expand All @@ -219,12 +220,13 @@ container's PID 1.
| **M9** | aarch64 as a tested target, not an assumed one. Both boot the same suite. |
| **M10** | A real distribution userspace, with dynamically linked services. |
| **M11** | `after`, `requires` and `conflicts` enforced; `start-sec` bounds activation. |
| **M12** | CI: every suite above, on every push. |

One `epoll` loop multiplexes the signalfd, the timerfd, the notify socket, the
control socket, every socket unit's listening descriptor and every service
cgroup's `cgroup.events`. One thread. No async runtime.

Nothing is scheduled after M11. [ROADMAP.md](ROADMAP.md) has the breakdown,
Nothing is scheduled after M12. [ROADMAP.md](ROADMAP.md) has the breakdown,
including what each milestone was verified against and what was deferred out
of it.

Expand All @@ -250,11 +252,15 @@ Edit to boot takes a few seconds. `Ctrl-A X` exits QEMU. Setup details are in
Two suites assert on that boot rather than asking you to watch it:

```bash
cargo xtask fetch --arch x86_64 # a kernel and a busybox, into target/
cargo xtask test-boot --arch all # boots x86_64 and aarch64, matches the log
cargo xtask container # runs it in Docker, checks the exit code
cargo xtask test-distro # boots a real Alpine userspace
```

All four run in CI on every push, along with `fmt`, `clippy` against both musl
targets, and the host tests.

The library crates test on any host, with no VM and no kernel:

```bash
Expand Down
44 changes: 43 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,49 @@ Verified under QEMU, both architectures, and against a real Alpine userspace:
29 checks on each architecture, 31 against the distribution image, 146 host
tests.

## Beyond M11
## M12 — Continuous verification

**Done.**

Every milestone above ends with a paragraph beginning "Verified". Until this
one, each of those was a claim about something someone had run once, on their
own machine, and nothing re-ran it afterwards.

- [x] `cargo xtask fetch`: download the kernel and the static busybox an
architecture needs, so the setup is a command rather than a paragraph
about where Alpine keeps things.
- [x] A workflow that runs `fmt`, `clippy` against both musl targets, the host
tests, both boot suites, the container suite, and the distribution
suite.
- [x] Serial logs kept when a boot fails.

Split by cost. The lint and test job is seconds and gates the rest; the four
boot suites run in parallel with one another. aarch64 has no hardware
acceleration on an x86_64 runner, so its boot goes through QEMU's JIT and is
the slowest thing in the set by a wide margin — which is an argument for
running it, not against: it is the configuration least likely to be exercised
by hand.

A boot suite that fails tells you which line was missing from a log you do not
have. So the log is uploaded, and a failure is readable without reproducing
it.

Verified by the thing itself, which is the only way this milestone can be:

| Job | Result | Time |
|-------------------------|---------|-------|
| fmt, clippy, host tests | success | 0m44s |
| boot (x86_64) | success | 1m33s |
| boot (aarch64) | success | 1m42s |
| container | success | 1m18s |
| a real userspace | success | 1m31s |

The whole set is under two minutes wall-clock after the gate, which was the
surprise: an emulated ARM64 boot on an x86_64 runner costs nine seconds more
than a native one. The 300-second budget picked in M9 turns out to be an order
of magnitude of headroom rather than a tight fit.

## Beyond M12

Not planned, not designed, listed only so the questions have an answer:

Expand Down
Loading
Loading