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
8 changes: 4 additions & 4 deletions .claude/rules/adding-options.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
---
paths:
- "options.go"
- "propolis.go"
- "microvm.go"
- "runner/config.go"
- "runner/cmd/propolis-runner/main.go"
- "runner/cmd/go-microvm-runner/main.go"
---

# Adding a New Option

1. Add the field to the `config` struct in `options.go`
2. Set the default in `defaultConfig()` if needed
3. Create a `With*` constructor following the existing pattern in `options.go`
4. Use the field in `propolis.go` (in `Run()`) where appropriate
5. If the option affects the runner, add the field to BOTH `runner.Config` in `runner/config.go` AND the runner's duplicate `Config` struct in `runner/cmd/propolis-runner/main.go` with the same JSON tag
4. Use the field in `microvm.go` (in `Run()`) where appropriate
5. If the option affects the runner, add the field to BOTH `runner.Config` in `runner/config.go` AND the runner's duplicate `Config` struct in `runner/cmd/go-microvm-runner/main.go` with the same JSON tag
4 changes: 2 additions & 2 deletions .claude/rules/preflight-and-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ paths:
- Create a function returning `preflight.Check` with Name, Description, Run, and Required fields
- See existing checks in `preflight/kvm_linux.go` and `preflight/ports.go` for patterns
- Platform-specific checks go in build-tagged files (`//go:build linux` or `//go:build darwin`)
- Register via `propolis.WithPreflightChecks()` or add to `registerPlatformChecks()` for defaults
- Register via `microvm.WithPreflightChecks()` or add to `registerPlatformChecks()` for defaults

## Adding a Network Provider
- Implement the `net.Provider` interface (Start, SocketPath, Stop)
Expand All @@ -22,4 +22,4 @@ paths:
- Type: `func(rootfsPath string, cfg *image.OCIConfig) error`
- Run before `.krun_config.json` is written and before VM boot
- Multiple hooks run in registration order; any error aborts the pipeline
- Register via `propolis.WithRootFSHook()`
- Register via `microvm.WithRootFSHook()`
2 changes: 1 addition & 1 deletion .github/workflows/builder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ permissions:

env:
REGISTRY: ghcr.io
IMAGE_NAME: stacklok/propolis-builder
IMAGE_NAME: stacklok/go-microvm-builder

jobs:
build:
Expand Down
54 changes: 27 additions & 27 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ jobs:
- name: Upload runtime artifact
uses: actions/upload-artifact@v7
with:
name: propolis-runtime-linux-${{ matrix.arch }}
path: dist/propolis-runtime-linux-${{ matrix.arch }}.tar.gz
name: go-microvm-runtime-linux-${{ matrix.arch }}
path: dist/go-microvm-runtime-linux-${{ matrix.arch }}.tar.gz

- name: Upload firmware artifact
uses: actions/upload-artifact@v7
with:
name: propolis-firmware-linux-${{ matrix.arch }}
path: dist/propolis-firmware-linux-${{ matrix.arch }}.tar.gz
name: go-microvm-firmware-linux-${{ matrix.arch }}
path: dist/go-microvm-firmware-linux-${{ matrix.arch }}.tar.gz

build-artifacts-darwin:
name: Build macOS (${{ matrix.arch }})
Expand Down Expand Up @@ -101,14 +101,14 @@ jobs:
- name: Upload runtime artifact
uses: actions/upload-artifact@v7
with:
name: propolis-runtime-darwin-${{ matrix.arch }}
path: dist/propolis-runtime-darwin-${{ matrix.arch }}.tar.gz
name: go-microvm-runtime-darwin-${{ matrix.arch }}
path: dist/go-microvm-runtime-darwin-${{ matrix.arch }}.tar.gz

- name: Upload firmware artifact
uses: actions/upload-artifact@v7
with:
name: propolis-firmware-darwin-${{ matrix.arch }}
path: dist/propolis-firmware-darwin-${{ matrix.arch }}.tar.gz
name: go-microvm-firmware-darwin-${{ matrix.arch }}
path: dist/go-microvm-firmware-darwin-${{ matrix.arch }}.tar.gz

create-release:
name: Create Release
Expand All @@ -126,18 +126,18 @@ jobs:

- name: Generate checksums
run: |
sha256sum propolis-*.tar.gz > sha256sums.txt
sha256sum go-microvm-*.tar.gz > sha256sums.txt

- name: Create or update GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if gh release view "${{ github.ref_name }}" >/dev/null 2>&1; then
gh release upload "${{ github.ref_name }}" --clobber \
propolis-*.tar.gz sha256sums.txt
go-microvm-*.tar.gz sha256sums.txt
else
gh release create "${{ github.ref_name }}" --generate-notes \
propolis-*.tar.gz sha256sums.txt
go-microvm-*.tar.gz sha256sums.txt
fi

push-oci:
Expand All @@ -153,12 +153,12 @@ jobs:
- name: Download runtime artifact
uses: actions/download-artifact@v8
with:
name: propolis-runtime-linux-${{ matrix.arch }}
name: go-microvm-runtime-linux-${{ matrix.arch }}

- name: Download firmware artifact
uses: actions/download-artifact@v8
with:
name: propolis-firmware-linux-${{ matrix.arch }}
name: go-microvm-firmware-linux-${{ matrix.arch }}

- name: Install oras
uses: oras-project/setup-oras@v1
Expand All @@ -169,15 +169,15 @@ jobs:

- name: Push runtime OCI artifact
run: |
oras push ghcr.io/stacklok/propolis/runtime:${{ github.ref_name }}-linux-${{ matrix.arch }} \
--artifact-type application/vnd.stacklok.propolis.runtime \
propolis-runtime-linux-${{ matrix.arch }}.tar.gz:application/gzip
oras push ghcr.io/stacklok/go-microvm/runtime:${{ github.ref_name }}-linux-${{ matrix.arch }} \
--artifact-type application/vnd.stacklok.go-microvm.runtime \
go-microvm-runtime-linux-${{ matrix.arch }}.tar.gz:application/gzip

- name: Push firmware OCI artifact
run: |
oras push ghcr.io/stacklok/propolis/firmware:${{ github.ref_name }}-linux-${{ matrix.arch }} \
--artifact-type application/vnd.stacklok.propolis.firmware \
propolis-firmware-linux-${{ matrix.arch }}.tar.gz:application/gzip
oras push ghcr.io/stacklok/go-microvm/firmware:${{ github.ref_name }}-linux-${{ matrix.arch }} \
--artifact-type application/vnd.stacklok.go-microvm.firmware \
go-microvm-firmware-linux-${{ matrix.arch }}.tar.gz:application/gzip

push-oci-darwin:
name: Push OCI macOS (${{ matrix.arch }})
Expand All @@ -191,12 +191,12 @@ jobs:
- name: Download runtime artifact
uses: actions/download-artifact@v8
with:
name: propolis-runtime-darwin-${{ matrix.arch }}
name: go-microvm-runtime-darwin-${{ matrix.arch }}

- name: Download firmware artifact
uses: actions/download-artifact@v8
with:
name: propolis-firmware-darwin-${{ matrix.arch }}
name: go-microvm-firmware-darwin-${{ matrix.arch }}

- name: Install oras
uses: oras-project/setup-oras@v1
Expand All @@ -207,12 +207,12 @@ jobs:

- name: Push runtime OCI artifact
run: |
oras push ghcr.io/stacklok/propolis/runtime:${{ github.ref_name }}-darwin-${{ matrix.arch }} \
--artifact-type application/vnd.stacklok.propolis.runtime \
propolis-runtime-darwin-${{ matrix.arch }}.tar.gz:application/gzip
oras push ghcr.io/stacklok/go-microvm/runtime:${{ github.ref_name }}-darwin-${{ matrix.arch }} \
--artifact-type application/vnd.stacklok.go-microvm.runtime \
go-microvm-runtime-darwin-${{ matrix.arch }}.tar.gz:application/gzip

- name: Push firmware OCI artifact
run: |
oras push ghcr.io/stacklok/propolis/firmware:${{ github.ref_name }}-darwin-${{ matrix.arch }} \
--artifact-type application/vnd.stacklok.propolis.firmware \
propolis-firmware-darwin-${{ matrix.arch }}.tar.gz:application/gzip
oras push ghcr.io/stacklok/go-microvm/firmware:${{ github.ref_name }}-darwin-${{ matrix.arch }} \
--artifact-type application/vnd.stacklok.go-microvm.firmware \
go-microvm-firmware-darwin-${{ matrix.arch }}.tar.gz:application/gzip
18 changes: 9 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# propolis
# go-microvm

Go library + runner binary for running OCI container images as microVMs via libkrun.
Two-process model: pure-Go library spawns a CGO runner subprocess. Module: `github.com/stacklok/propolis`.
Two-process model: pure-Go library spawns a CGO runner subprocess. Module: `github.com/stacklok/go-microvm`.

## Commands

Expand Down Expand Up @@ -31,23 +31,23 @@ macOS dev setup: `brew tap slp/krun && brew install libkrun libkrunfw` (see `doc

## Architecture

Entry point: `propolis.go:Run()` orchestrates the full pipeline (preflight, pull, hooks, config, net, spawn, post-boot). Config via functional options in `options.go`. Returns a `*VM` handle (`vm.go`).
Entry point: `microvm.go:Run()` orchestrates the full pipeline (preflight, pull, hooks, config, net, spawn, post-boot). Config via functional options in `options.go`. Returns a `*VM` handle (`vm.go`).

**CGO boundary**: Only `krun/` and `runner/cmd/propolis-runner/` use CGO. Everything else is pure Go. The runner binary is sacrificial -- `krun_start_enter()` never returns, so it runs in a detached subprocess.
**CGO boundary**: Only `krun/` and `runner/cmd/go-microvm-runner/` use CGO. Everything else is pure Go. The runner binary is sacrificial -- `krun_start_enter()` never returns, so it runs in a detached subprocess.

**Key subsystems**: `hypervisor/` (Backend abstraction + libkrun impl), `image/` (OCI pull + cache), `runner/` (subprocess spawning), `net/` (Provider interface + firewall + hosted mode + egress policy + topology constants), `guest/` (guest-side boot orchestration, hardening, SSH server), `hooks/` (RootFS hook factories for key injection, file injection), `extract/` (binary bundle caching), `preflight/` (platform checks via build tags), `ssh/` (keygen + client), `state/` (flock-based JSON persistence), `internal/` (pathutil, procutil).

## Things That Will Bite You

- **CGO boundary is strict**: Only `krun/` and `runner/cmd/propolis-runner/` use CGO. Every other package MUST stay `CGO_ENABLED=0`. Never import `krun` from a non-CGO package.
- **Runner config is duplicated**: `runner.Config` in `runner/config.go` and a duplicate `Config` struct in `runner/cmd/propolis-runner/main.go`. When adding a field, update BOTH structs with the same JSON tag, then handle it in `runVM()`.
- **CGO boundary is strict**: Only `krun/` and `runner/cmd/go-microvm-runner/` use CGO. Every other package MUST stay `CGO_ENABLED=0`. Never import `krun` from a non-CGO package.
- **Runner config is duplicated**: `runner.Config` in `runner/config.go` and a duplicate `Config` struct in `runner/cmd/go-microvm-runner/main.go`. When adding a field, update BOTH structs with the same JSON tag, then handle it in `runVM()`.
- **`krun_start_enter()` never returns**: It calls `exit()` when the guest shuts down. That's why we need the two-process model -- the runner process is sacrificial.
- **Platform build tags**: Preflight checks, resource checks, and some net code use `//go:build linux` or `//go:build darwin`. Each platform goes in a separate file. macOS preflight checks verify `kern.hv_support` sysctl and use `hw.memsize`/`syscall.Statfs` for resources.
- **Entitlements required on macOS**: `assets/entitlements.plist` has three entitlements: `com.apple.security.hypervisor`, `com.apple.security.cs.disable-library-validation`, and `com.apple.security.cs.allow-dyld-environment-variables` (needed because the hypervisor entitlement activates hardened runtime, which strips DYLD_* vars). The `task build-dev-darwin` command signs automatically.
- **CGO Homebrew paths**: `krun/context.go` CGO directives include `-L/opt/homebrew/lib` and `-L/usr/local/lib` for macOS. The linker ignores nonexistent paths.
- **Tests excluding CGO packages**: When CGO isn't available, exclude krun: `CGO_ENABLED=0 go test $(go list ./... | grep -v krun | grep -v propolis-runner)`
- **Tests excluding CGO packages**: When CGO isn't available, exclude krun: `CGO_ENABLED=0 go test $(go list ./... | grep -v krun | grep -v go-microvm-runner)`
- **Functional options pattern**: All public config uses `With*` constructors applying to unexported `config` struct via `optionFunc`. Follow the existing pattern in `options.go` exactly.
- **Backend abstraction**: `WithRunnerPath`, `WithLibDir`, and `WithSpawner` are NOT on the top-level `propolis` package. They live in `hypervisor/libkrun` as backend-specific options. Use `propolis.WithBackend(libkrun.NewBackend(libkrun.WithRunnerPath(...)))`. Similarly, `VM.PID()` is gone; use `VM.ID()` (returns string).
- **Backend abstraction**: `WithRunnerPath`, `WithLibDir`, and `WithSpawner` are NOT on the top-level `microvm` package. They live in `hypervisor/libkrun` as backend-specific options. Use `microvm.WithBackend(libkrun.NewBackend(libkrun.WithRunnerPath(...)))`. Similarly, `VM.PID()` is gone; use `VM.ID()` (returns string).

## Conventions

Expand All @@ -72,7 +72,7 @@ task test # Full test suite with race detector

After modifying CGO-free packages only:
```bash
CGO_ENABLED=0 go vet $(go list ./... | grep -v krun | grep -v propolis-runner)
CGO_ENABLED=0 go vet $(go list ./... | grep -v krun | grep -v go-microvm-runner)
```

When tests fail, fix the implementation, not the tests.
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Contributing to propolis
# Contributing to go-microvm

## Prerequisites

Expand Down Expand Up @@ -60,7 +60,7 @@ Run `task --list` for the full list. Key tasks for development:
- **Error wrapping**: `fmt.Errorf("context: %w", err)`
- **Table-driven tests** with testify
- **Functional options**: follow the `With*` pattern in `options.go`
- **CGO boundary**: only `krun/` and `runner/cmd/propolis-runner/` use CGO.
- **CGO boundary**: only `krun/` and `runner/cmd/go-microvm-runner/` use CGO.
Never import `krun` from other packages.

## Commit Guidelines
Expand Down
Loading
Loading