No description
Find a file
Till Wegmueller 1d23933487 runners/source: HTTP, OCI, and archive backends
Implement the deferred half of Phase B for runner source fetching:

- HTTP/HTTPS via reqwest, gated behind a `network` feature on
  sysconfig-modules (default-on, off for offline builds).
- OCI registry pulls via oci-client (matches vm-manager's choice).
  Single-layer artifacts work directly; multi-layer artifacts require
  `media-type=` to disambiguate. Layer digest is always verified
  against the manifest, and any user-supplied `digest=` is checked too.
- Archive extraction for tar, tar.gz, tar.zst, zip, and gz. `gz` is
  single-file (rejects `path`); the others require `path`. Path
  traversal (`..`, absolute paths) and in-archive `..` entries are
  rejected. Symlinks/hardlinks at the selected path are refused.
- Both fetch backends use a local current-thread tokio runtime so the
  rest of the crate stays sync, matching the EC2 provider stub.

Drops the `SourceSchemeUnimplemented` and `ArchiveExtractionUnimplemented`
validation errors (and their tests), since validate no longer needs to
gate against unimplemented backends. `NetworkSourceTooEarly` and
`UnverifiedRemoteSource` stay — those are real semantic checks.

Test coverage: HTTP fetch + sha256, redirect, 404, HTTP-served tar.gz
archive extraction, and unit tests for each archive format including
path-traversal rejection. Wiremock stands in for the OCI registry; live
OCI is left to integration testing.

Verified on illumos in the OmniOS r151058 VM (sysconfig-modules
--no-default-features and sysconfig-providers/sysconfig-schema crates;
oci-client transitively pulls deps that OOM the 2GB VM under release).
2026-05-05 23:18:38 +02:00
crates runners/source: HTTP, OCI, and archive backends 2026-05-05 23:18:38 +02:00
docs runners/source: HTTP, OCI, and archive backends 2026-05-05 23:18:38 +02:00
examples Initial sysconfig: typed KDL config tool for illumos cloud images 2026-04-28 14:51:11 +02:00
service-configs/smf Initial sysconfig: typed KDL config tool for illumos cloud images 2026-04-28 14:51:11 +02:00
.gitignore Initial sysconfig: typed KDL config tool for illumos cloud images 2026-04-28 14:51:11 +02:00
Cargo.lock runners/source: HTTP, OCI, and archive backends 2026-05-05 23:18:38 +02:00
Cargo.toml runners/source: HTTP, OCI, and archive backends 2026-05-05 23:18:38 +02:00
LICENSE Initial commit 2026-04-28 12:40:29 +00:00
README.md Initial sysconfig: typed KDL config tool for illumos cloud images 2026-04-28 14:51:11 +02:00

sysconfig

A typed, declarative system configuration tool for illumos cloud images. A simpler, faster, type-safe replacement for cloud-init.

Why sysconfig?

cloud-init is a 60k+ line Python project that boots ~10 SMF services and takes 30+ seconds on a small VM. It has been the de facto standard for cloud provisioning, but on illumos it's a poor fit:

  • Slow: serial Python startup + many SMF services
  • Untyped: YAML with implicit schema, errors only at runtime
  • Linux-centric: many features assume systemd, Debian, NetworkManager
  • Three daemons: cloud-init-local, cloud-init, cloud-config, cloud-final

sysconfig is a single Rust binary, one transient SMF service, KDL config:

cloud-init sysconfig
Daemons 4 SMF services 1 transient
Config format YAML typed KDL
Lines of code ~60,000 (Python) ~3,500 (Rust)
Boot overhead 30+ seconds <1 second
Self-assembly merge order is implicit /etc/sysconfig.d/*.kdl lex order

Quick start

The fastest way to try sysconfig is the prebuilt OmniOS image:

# Pull and run with vmctl (https://code.aopc.cloud/CloudNebulaProject/vm-manager)
vmctl create --name myvm \
    --image-url oci://code.aopc.cloud/cloudnebulaproject/omnios-sysconfig:latest \
    --vcpus 2 --memory 2048 \
    --start

The VM boots, sysconfig detects the NoCloud seed ISO that vmctl created, applies hostname/SSH keys/users from your cloud-init user-data, and exits — no daemons left running.

Default root password (until you provide one): test123.

How it works

boot
  ↓
SMF starts svc:/system/sysconfig:default  (transient)
  ↓
sysconfig provision --source auto
  ├── detect cloud env (NoCloud, EC2, GCP, …)
  ├── fetch metadata (mount CIDATA, HTTP, mdata-get, …)
  ├── write /etc/sysconfig.d/10-cloud.kdl
  └── invoke `sysconfig apply`
        ├── load all /etc/sysconfig.d/*.kdl in lex order
        ├── merge into a typed SysConfig
        ├── validate
        └── apply via modules: hostname → DNS → interfaces → users
  ↓
exit 0  (service goes online, then idle)

The merged config is the source of truth — everything else is just a way to populate it. Drop your own 50-local.kdl next to the cloud fragment and your overrides win.

Configuration

sysconfig {
    hostname "web01"
    timezone "Europe/Zurich"

    nameserver "1.1.1.1"
    nameserver "1.0.0.1"
    search-domain "example.com"

    interface "net0" {
        address name="v4" kind="dhcp4"
    }

    interface "net1" selector="mac:52:54:00:11:22:33" {
        address name="v4" kind="static" "10.0.0.5/24"
        address name="v6" kind="addrconf"
    }

    user "admin" {
        shell "/usr/bin/bash"
        groups "wheel" "staff"
        sudo "unrestricted"
        ssh-key "ssh-ed25519 AAAA…"
    }
}

See docs/configuration.md for the full schema.

Self-assembly via fragments

/etc/sysconfig.d/ is read in lexicographic order. Each fragment is a complete sysconfig { … } block. Later fragments override earlier ones for scalars (hostname, timezone) and append-with-dedup for lists (nameservers, users, interfaces).

Recommended layout:

File Source Purpose
00-defaults.kdl image shipped baseline (DHCP, UTC, sane DNS)
10-cloud.kdl sysconfig written by sysconfig provision
50-local.kdl admin local overrides
90-override.kdl admin last-word overrides

This is the "drop-in" pattern, the same as systemd and cron.d.

Commands

sysconfig apply [--dry-run] [--config-dir /etc/sysconfig.d]
sysconfig provision [--source auto|nocloud|ec2|] [--dry-run]
sysconfig diff [--config-dir /etc/sysconfig.d]
sysconfig validate [PATH...]
  • apply — merge fragments and configure the system
  • provision — detect cloud, fetch metadata, write fragment, then apply
  • diff — show what apply would do (alias for apply --dry-run)
  • validate — check syntax and semantics of fragment files

Status

v0.1.0 — early access. Works end-to-end on OmniOS bloody. Tested with NoCloud (vmctl/cloud-localds). EC2/GCP/Azure providers are scaffolded but not yet wired into release builds.

Module illumos Linux FreeBSD
hostname (untested)
DNS (/etc/resolv.conf) (untested)
interfaces (ipadm) gated
users (useradd/usermod) (untested)
SSH keys (untested)
sudo (/etc/sudoers.d/) (untested)
NoCloud (CIDATA mount) (untested)
cloud-init user-data parser

Building

cargo build --release

Cross-compiling to illumos from Linux requires the illumos C toolchain (for ring/reqwest linkage). The simplest path is to build natively inside an illumos VM. See docs/building.md.

Repo layout

crates/
  sysconfig-schema/     typed config + KDL parsing + fragment merging
  sysconfig-modules/    platform actions (hostname, DNS, ifaces, users)
  sysconfig-providers/  cloud metadata fetchers (NoCloud today)
  sysconfig/            CLI binary
service-configs/smf/    SMF manifest (single transient service)
examples/               example KDL fragments
docs/                   user and developer documentation
tests/                  integration tests

Documentation

License

MPL-2.0. See LICENSE.