- Rust 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
useradd/usermod -G treats a missing group as a hard error and aborts the whole user creation. Cloud-config carries cross-distro group names (Linux sudo/docker) that need not exist on illumos, so a job user with groups: [sudo, docker] was never created and the orchestrator could not SSH in. Filter -G to the groups present in /etc/group and warn about the rest; sudo access comes from the separate sudoers.d drop-in anyway. |
||
| crates | ||
| docs | ||
| examples | ||
| service-configs/smf | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
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 systemprovision— detect cloud, fetch metadata, write fragment, then applydiff— show whatapplywould do (alias forapply --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
- Architecture — design and rationale
- Configuration reference — KDL schema
- Provisioning — cloud data sources
- Building — native + cross-compile
- Image building — using sysconfig in OS images
- Migration from cloud-init — what maps to what
- Roadmap
License
MPL-2.0. See LICENSE.