Skip to content

feat: guest definitions as data - #48

Merged
NovusEdge merged 25 commits into
mainfrom
feat/guest-definitions
Sep 4, 2026
Merged

NovusEdge merged 25 commits into
mainfrom
feat/guest-definitions

Conversation

@NovusEdge

Copy link
Copy Markdown
Owner

What changed

Guest OS facts move from a Go literal to internal/guest/bundled/<name>.toml, with ~/.stoat/guests/*.toml merged over them per field. internal/tomlx is the one TOML decoder (path in every error, unknown keys, schema bound); vm.toml warns on an unknown key and recipe.toml rejects one.

Recipe scripts now get a rendered prelude over ssh and in the cloud-init seed: stoat_pkg_setup, stoat_pkg_install, stoat_svc_*, STOAT_OS, STOAT_INIT, STOAT_PKGMGR. escalate comes from the guest file instead of a hardcoded sudo. The capability table, the runtime package table and the scaffold text are gone from Go.

stoat guest ls and stoat guest show <name> expose the set, with --json. A non-empty os naming no loaded guest marks the VM broken instead of silently defaulting. provision is a kong alias of apply. xfce installs through the verbs.

Why

Adding a guest OS is a file. Three per-OS tables that could drift apart are now one.

Tests run

  • just check and just test
  • just e2e: NOT RUN. No KVM in the environment this was built in.

OS matrix

alpine, ubuntu, debian, fedora, arch: the bundled files are golden-tested against the deleted Go literal, and each prelude is a golden checked with sh -n. No live boot on any of them.

Docs

  • docs/reference/guest.md, cli.md, json.md, writing-recipes.md

Before merge

xfce's install script was rewritten onto the verbs, so this wants a live boot. Run just e2e on a host with KVM.

Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
…ases apply

Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
@NovusEdge NovusEdge added enhancement New feature needs-live-boot Cannot be verified by agents; needs a real Alpine boot labels Sep 4, 2026
@NovusEdge NovusEdge self-assigned this Sep 4, 2026
@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: 6c609a84-a51a-4869-b4f3-cd91c0852d40

📥 Commits

Reviewing files that changed from the base of the PR and between c7d34b2 and ce75a97.

📒 Files selected for processing (64)
  • docs/SUMMARY.md
  • docs/design/core-api.md
  • docs/design/guest-subsystem.md
  • docs/recipe-spec-v2.md
  • docs/reference/cli.md
  • docs/reference/guest.md
  • docs/reference/json.md
  • docs/writing-recipes.md
  • internal/backend/backend.go
  • internal/backend/cloudinit.go
  • internal/cli/cli.go
  • internal/cli/cli_test.go
  • internal/cli/grammar.go
  • internal/cli/json_test.go
  • internal/cli/kong_test.go
  • internal/cli/run_guest.go
  • internal/cli/run_recipes.go
  • internal/cli/wire/dto.go
  • internal/cli/wire/dto_test.go
  • internal/cloudinit/cloudinit.go
  • internal/cloudinit/cloudinit_test.go
  • internal/cloudinit/scripts.go
  • internal/cloudinit/scripts_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/core/clone.go
  • internal/core/guests.go
  • internal/core/guests_test.go
  • internal/core/vm.go
  • internal/core/vm_test.go
  • internal/guest/bundled/alpine.toml
  • internal/guest/bundled/arch.toml
  • internal/guest/bundled/debian.toml
  • internal/guest/bundled/fedora.toml
  • internal/guest/bundled/ubuntu.toml
  • internal/guest/guest.go
  • internal/guest/guest_test.go
  • internal/guest/load.go
  • internal/guest/load_test.go
  • internal/guest/prelude.go
  • internal/guest/prelude_test.go
  • internal/guest/testdata/prelude/alpine.sh
  • internal/guest/testdata/prelude/arch.sh
  • internal/guest/testdata/prelude/debian.sh
  • internal/guest/testdata/prelude/fedora.sh
  • internal/guest/testdata/prelude/ubuntu.sh
  • internal/recipes/bootstrap.go
  • internal/recipes/bootstrap_test.go
  • internal/recipes/bundled/xfce/install-alpine.sh
  • internal/recipes/bundled/xfce/install-arch.sh
  • internal/recipes/bundled/xfce/install-debian.sh
  • internal/recipes/bundled/xfce/install.sh
  • internal/recipes/bundled/xfce/recipe.toml
  • internal/recipes/manifest.go
  • internal/recipes/manifest_test.go
  • internal/recipes/runtime.go
  • internal/recipes/runtime_test.go
  • internal/recipes/scaffold.go
  • internal/sshx/sharemount.go
  • internal/sshx/sshx.go
  • internal/sshx/sshx_test.go
  • internal/tomlx/tomlx.go
  • internal/tomlx/tomlx_test.go
  • internal/tui/app.go

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@NovusEdge
NovusEdge merged commit 3412c22 into main Sep 4, 2026
7 of 8 checks passed
@NovusEdge NovusEdge mentioned this pull request Sep 4, 2026
2 tasks done
NovusEdge added a commit that referenced this pull request Sep 4, 2026
Signed-off-by: NovusEdge <novusedge0@gmail.com>

#48 branched before the linter landed in #47, so its code never met errcheck. The bundled-guest temp file now reports a failed Close or Remove, and the config test fails on a setup error instead of ignoring it.
NovusEdge added a commit that referenced this pull request Sep 5, 2026
PR #48 merged install-alpine.sh into install.sh and dropped its setup-xorg-base call. Alpine's xfce4 metapackage does not depend on xorg-server or xinit, so tty1 looped on 'startx: not found' and the e2e libinput assert failed.

Signed-off-by: NovusEdge <novusedge0@gmail.com>
NovusEdge added a commit that referenced this pull request Sep 5, 2026
PR #48 merged install-alpine.sh into install.sh and dropped its setup-xorg-base call. Alpine's xfce4 metapackage does not depend on xorg-server or xinit, so tty1 looped on 'startx: not found' and the e2e libinput assert failed.

Signed-off-by: NovusEdge <novusedge0@gmail.com>
NovusEdge added a commit that referenced this pull request Sep 5, 2026
* test(config): pin vm encoder contract

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(recipes): pin schema 3 manifest contract

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(guest): pin command verb preludes

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(config): pin recipe state storage

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(recipes): pin manifest ordering

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(config): pin secrets file contract

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(guest): forward python command args

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* refactor(config): encode vm.toml with go-toml/v2

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(recipes): parse schema 3 params and health

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(guest): add download and useradd verbs

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* fix(config): mark toml encoder dependency direct

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(config): store recipe params and applied state

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(config): store recipe secrets securely

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(recipes): pin v3 boundary validation

Cover explicit schema compatibility, schema-2 v3 blocks, and health timeout presence. Assert SaveSecrets preserves an empty recipe entry in its caller-owned map.

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* fix(recipes): validate schema and health bounds

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(recipes): pin v3 chunk two contract

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(recipes): resolve params and hash them

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(cli): isolate recipe parameter fixture

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(cli): persist recipe parameter edits

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(sshx): deliver recipe params and outputs

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(cloud): deliver recipe params and secrets

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(core): run recipe health checks after apply

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(recipe): close chunk two review boundaries

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(cloudinit): inherit xorriso umask

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* fix(recipe): close chunk two contract gaps

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(recipe): cover contract v3 callers

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(tui): exercise recipe parameter lifecycle

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(cli): add wait healthy mode

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(cli): assert decoded secret redaction

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(cli): expose recipe show contract

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(status): expose redacted recipe state

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(cli): cover apply stream redaction

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(cloudinit): cover namespace collision

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(contract): correct defaults and e2e redaction

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* fix(cli): close apply log redaction gaps

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(tui): add recipe parameter form

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* feat(recipes): ship schema samples and bundled contracts

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(contract): close chunk three review gaps

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(cli): correct source-boundary redaction case

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* fix(contract): close reviewed chunk gaps

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(contract): finish reviewed caller gaps

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* fix(contract): close health and cloudinit review gaps

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(cloudinit): cover multiline Debian prelude

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(core): retain single health timeout detail

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(cloudinit): parse setup command YAML

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* fix(contract): preserve live prelude and health detail

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* test(sshx): model ssh argv joining in the fake

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* fix(sshx): quote the output read-back for ssh

Signed-off-by: NovusEdge <novusedge0@gmail.com>

* fix(recipes): restore the X server on Alpine xfce

PR #48 merged install-alpine.sh into install.sh and dropped its setup-xorg-base call. Alpine's xfce4 metapackage does not depend on xorg-server or xinit, so tty1 looped on 'startx: not found' and the e2e libinput assert failed.

Signed-off-by: NovusEdge <novusedge0@gmail.com>

---------

Signed-off-by: NovusEdge <novusedge0@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature needs-live-boot Cannot be verified by agents; needs a real Alpine boot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant