Skip to content
Closed
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
148 changes: 148 additions & 0 deletions .github/workflows/platform-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,151 @@ jobs:
- run: npm run check -w run-spaceapp
- run: npm run pack:dry-run -w run-spaceapp
- run: node packages/run-spaceapp/bin/spaceapp.mjs --version

rootless-podman:
name: Rootless Podman / ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 60
env:
ELECTRON_SKIP_BINARY_DOWNLOAD: "1"
SPACEAPP_HOME: /tmp/spaceapp-podman-ci
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22.23.0"
cache: npm
# ubuntu-latest (24.04) currently ships Podman 4.9.3; Homebrew's Linux
# bottle supplies Podman 5+ without replacing the runner's apt packages.
- name: Install supported Podman and dependencies
run: |
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
brew install podman
echo "$(brew --prefix)/bin" >> "$GITHUB_PATH"
mkdir -p "$HOME/.local/bin"
curl --fail --location --silent --show-error \
https://github.com/docker/compose/releases/download/v2.38.2/docker-compose-linux-x86_64 \
--output "$HOME/.local/bin/docker-compose"
chmod 0755 "$HOME/.local/bin/docker-compose"
# Podman 6 needs netavark/aardvark 2; the runner's apt copies are 1.x.
for helper in netavark aardvark-dns; do
curl --fail --location --silent --show-error \
"https://github.com/containers/$helper/releases/download/v2.1.0/$helper.gz" \
| gzip -dc > "$HOME/.local/bin/$helper"
chmod 0755 "$HOME/.local/bin/$helper"
done
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
# Ubuntu's /usr/bin/pasta is paired with Podman 4.9 and lacks the
# --map-guest-addr option used by the newer Homebrew Podman.
test -x "$(brew --prefix)/bin/pasta"
test -x "$(brew --prefix)/bin/crun"
test -x "$(brew --prefix)/bin/conmon"
mkdir -p "$HOME/.config/containers"
printf '[engine]\nhelper_binaries_dir = ["%s/.local/bin", "%s/bin", "/usr/libexec/podman", "/usr/lib/podman", "/usr/bin"]\nconmon_path = ["%s/bin/conmon"]\nruntime = "crun"\n[engine.runtimes]\ncrun = ["%s/bin/crun"]\n' "$HOME" "$(brew --prefix)" "$(brew --prefix)" "$(brew --prefix)" > "$HOME/.config/containers/containers.conf"
npm ci
- name: Start rootless Podman API socket
run: |
mkdir -p "$HOME/.config/systemd/user/podman.service.d"
printf '[Service]\nExecStart=\nExecStart=%s system service\n' "$(command -v podman)" > "$HOME/.config/systemd/user/podman.service.d/override.conf"
systemctl --user daemon-reload
systemctl --user enable --now podman.socket
podman --version
netavark --version
aardvark-dns --version
docker-compose version
podman info --format json | jq '{version: .version.Version, rootless: .host.security.rootless, cgroupVersion: .host.cgroupVersion, socket: .host.remoteSocket.path}'
podman info --format json | jq '{ociRuntime: .host.ociRuntime.path, conmon: .host.conmon.path}'
- name: Exercise rootless Podman lifecycle and purge
run: |
set -euo pipefail
wait_ready() {
for _ in $(seq 1 120); do
if curl --fail --silent http://127.0.0.1:4911/readyz | grep -q '"ok":true'; then return 0; fi
sleep 5
done
echo 'SpaceApp did not become ready on 127.0.0.1:4911' >&2
return 1
}
node packages/run-spaceapp/bin/spaceapp.mjs install --engine podman --profile small --non-interactive --answers '{"confirm":true}' --no-open
wait_ready
readyz=$(curl --fail --silent http://127.0.0.1:4911/readyz)
printf '/readyz: %s\n' "$readyz"
printf '%s' "$readyz" | grep -q '"ok":true'
echo 'Checking status, logs, and doctor'
node packages/run-spaceapp/bin/spaceapp.mjs status
node packages/run-spaceapp/bin/spaceapp.mjs logs > /tmp/spaceapp-podman-logs.txt
test -s /tmp/spaceapp-podman-logs.txt
node packages/run-spaceapp/bin/spaceapp.mjs doctor
echo 'Stopping and restarting the stack'
node packages/run-spaceapp/bin/spaceapp.mjs down
node packages/run-spaceapp/bin/spaceapp.mjs up
wait_ready
echo 'Repairing the stack with doctor --fix'
node packages/run-spaceapp/bin/spaceapp.mjs doctor --fix --non-interactive --answers '{"confirm":true}'
wait_ready
echo 'Checking bundled OpenCode through provider install and installing an optional provider'
project=$(node --input-type=module -e 'import { composeProjectName } from "./packages/run-spaceapp/src/index.mjs"; console.log(composeProjectName(process.env.SPACEAPP_HOME))')
node packages/run-spaceapp/bin/spaceapp.mjs provider install opencode
node packages/run-spaceapp/bin/spaceapp.mjs provider install codex
mkdir -p /tmp/spaceapp-podman-rw /tmp/spaceapp-podman-ro
node packages/run-spaceapp/bin/spaceapp.mjs workspace add /tmp/spaceapp-podman-rw
node packages/run-spaceapp/bin/spaceapp.mjs workspace add /tmp/spaceapp-podman-ro --read-only
rw_target=$(jq -r '.workspaces[] | select(.hostPath == "/tmp/spaceapp-podman-rw") | .containerPath' "$SPACEAPP_HOME/config.json")
ro_target=$(jq -r '.workspaces[] | select(.hostPath == "/tmp/spaceapp-podman-ro") | .containerPath' "$SPACEAPP_HOME/config.json")
test "$rw_target" != null && test "$ro_target" != null
podman exec --user 10001:10001 "${project}-spaceapp-cli-1" sh -ec 'printf podman-owner-check > "$1/owner-check.txt"' sh "$rw_target"
test "$(cat /tmp/spaceapp-podman-rw/owner-check.txt)" = podman-owner-check
test "$(stat -c %u /tmp/spaceapp-podman-rw/owner-check.txt)" = "$(id -u)"
if podman exec --user 10001:10001 "${project}-spaceapp-cli-1" sh -ec 'touch "$1/must-not-write"' sh "$ro_target"; then
echo 'Read-only workspace unexpectedly allowed a write' >&2
exit 1
fi
test ! -e /tmp/spaceapp-podman-ro/must-not-write
echo 'Backing up and restoring the stack'
node packages/run-spaceapp/bin/spaceapp.mjs backup
printf 'RESTORE\n' | node packages/run-spaceapp/bin/spaceapp.mjs restore
wait_ready
echo 'Exercising the same-version update repair path'
node packages/run-spaceapp/bin/spaceapp.mjs update --non-interactive --answers '{"confirm":true,"action":"repair"}'
wait_ready
node packages/run-spaceapp/bin/spaceapp.mjs status
node packages/run-spaceapp/bin/spaceapp.mjs uninstall --purge-data --non-interactive --answers '{"purgeConfirm":"DELETE"}'
test -z "$(podman ps -a --filter "label=com.docker.compose.project=$project" -q)"
test -z "$(podman volume ls --format '{{.Name}}' | grep "^${project}_" || true)"
echo 'Testing the medium workflows profile'
export SPACEAPP_HOME=/tmp/spaceapp-podman-medium-ci
node packages/run-spaceapp/bin/spaceapp.mjs install --engine podman --profile medium --non-interactive --answers '{"confirm":true}' --no-open
wait_ready
project=$(node --input-type=module -e 'import { composeProjectName } from "./packages/run-spaceapp/src/index.mjs"; console.log(composeProjectName(process.env.SPACEAPP_HOME))')
test "$(podman inspect "${project}-temporal-1" --format '{{.State.Running}}')" = true
node packages/run-spaceapp/bin/spaceapp.mjs status
node packages/run-spaceapp/bin/spaceapp.mjs doctor
node packages/run-spaceapp/bin/spaceapp.mjs down
node packages/run-spaceapp/bin/spaceapp.mjs up
wait_ready
node packages/run-spaceapp/bin/spaceapp.mjs doctor --fix --non-interactive --answers '{"confirm":true}'
wait_ready
node packages/run-spaceapp/bin/spaceapp.mjs backup
printf 'RESTORE\n' | node packages/run-spaceapp/bin/spaceapp.mjs restore
wait_ready
node packages/run-spaceapp/bin/spaceapp.mjs uninstall --purge-data --non-interactive --answers '{"purgeConfirm":"DELETE"}'
test -z "$(podman ps -a --filter "label=com.docker.compose.project=$project" -q)"
test -z "$(podman volume ls --format '{{.Name}}' | grep "^${project}_" || true)"
echo 'Testing a real 1.1.5 to 1.1.6 update and rollback'
export SPACEAPP_HOME=/tmp/spaceapp-podman-upgrade-ci
node --input-type=module -e 'import { run } from "./packages/run-spaceapp/src/cli.mjs"; process.exitCode = await run(["install", "--engine", "podman", "--profile", "small", "--non-interactive", "--answers", "{\"confirm\":true}", "--no-open"], { runtimeVersion: "1.1.5" });'
wait_ready
test "$(jq -r .version "$SPACEAPP_HOME/config.json")" = 1.1.5
node packages/run-spaceapp/bin/spaceapp.mjs update --non-interactive --answers '{"confirm":true}'
wait_ready
test "$(jq -r .version "$SPACEAPP_HOME/config.json")" = 1.1.6
test "$(jq -r .previousVersion "$SPACEAPP_HOME/config.json")" = 1.1.5
node packages/run-spaceapp/bin/spaceapp.mjs rollback --non-interactive --answers '{"confirm":true}'
wait_ready
test "$(jq -r .version "$SPACEAPP_HOME/config.json")" = 1.1.5
project=$(node --input-type=module -e 'import { composeProjectName } from "./packages/run-spaceapp/src/index.mjs"; console.log(composeProjectName(process.env.SPACEAPP_HOME))')
node packages/run-spaceapp/bin/spaceapp.mjs uninstall --purge-data --non-interactive --answers '{"purgeConfirm":"DELETE"}'
test -z "$(podman ps -a --filter "label=com.docker.compose.project=$project" -q)"
test -z "$(podman volume ls --format '{{.Name}}' | grep "^${project}_" || true)"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Coordinate multi-step tasks conversationally through the Room Agent or speak nat
- **Hardware**:
- Minimum (`small` profile): 4 CPU cores, 8 GB-class RAM (at least 7 GiB reported by the operating system), 6.5 GiB free disk.
- `large` profile (adds the managed browser): 8 CPU cores, 15 GiB RAM, 11 GiB free disk.
- **Docker**: If Docker is not installed, the installer automatically installs official Docker Desktop (Windows/macOS) or Docker Engine (Linux).
- **Container engine**: Docker is the default and is installed automatically if missing. Rootless Podman 5+ on Linux or Podman machine on macOS is also supported with docker-compose 2.20+, an active Podman API socket, and cgroup v2 CPU/memory delegation on Linux. Use `install --engine podman`; Podman is not installed automatically. Engine switching and Podman host-root access are unsupported; Windows Podman is out of scope. SELinux workspaces use shared relabeling, and macOS machine resources are checked against the profile.

### Run the Installation Command

Expand Down
18 changes: 17 additions & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
SpaceApp packages its application services and redistributable AI coding CLIs
in Docker. The host needs Node.js only for the small `spaceapp` launcher,
and enough resources for the stack. The launcher installs and starts Docker
automatically when it is missing.
automatically when it is missing, or uses rootless Podman when selected.

## Requirements

Expand All @@ -23,6 +23,15 @@ The `large` profile, which adds managed browser sessions, requires:

## Linux

To use rootless Podman instead of Docker, install Podman 5+ and docker-compose
2.20+ with your distribution's package manager, then run
`systemctl --user enable --now podman.socket` and
`npx --yes run-spaceapp@latest install --engine podman --profile small`.
The host must use cgroup v2 and delegate the `cpu` and `memory` controllers to
the user service. Podman is not installed automatically. On SELinux hosts,
workspace mounts are shared `z` relabeled; add a project directory rather than
`$HOME` or a system directory. Podman cannot use `--access host-root`.

Install Node.js 20.11 or newer, then run as a normal non-root user:

```bash
Expand All @@ -41,6 +50,13 @@ automatically, and continues in the same command.

## macOS

For Podman machine, install Podman 5+ and docker-compose 2.20+, start the
machine, then use `install --engine podman --profile small`. The launcher checks
machine CPUs, memory, and disk and prints a resize command when needed. Docker
remains the default when both engines are available. A saved installation
cannot switch engines; use `backup`, uninstall, and restore on a new install.
Podman on Windows is not supported.

Install Node.js 20.11 or newer, then run in Terminal:

```bash
Expand Down
33 changes: 32 additions & 1 deletion packages/run-spaceapp/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
# run-spaceapp

Cross-platform Docker launcher for SpaceApp.
Cross-platform Docker launcher for SpaceApp, with rootless Podman support on Linux and macOS.

### Rootless Podman

Use `install --engine podman --profile small` on Linux or macOS. Later commands
reuse the engine recorded in `config.json`. Docker remains the default when it is
available; when only Podman works, interactive install offers it and
`--non-interactive` selects it. `--answers '{"engine":"podman", "confirm":true}'`
also selects it. An existing installation cannot switch engines because Docker
volumes are not visible to Podman: make a portable `backup`, uninstall, and
restore into a new installation on the chosen engine.

Podman 5 or newer and **docker-compose 2.20 or newer** are required. The launcher
uses `podman compose` with `PODMAN_COMPOSE_PROVIDER=docker-compose`. Compose 2.20
is needed for the template's optional `depends_on.required` condition. Other
template features include profiles, file secrets, health dependencies, `init`,
CPU/memory limits, shared socket volumes, `up --wait`, and `exec`/`run --user`.
On Linux, use rootless Podman with cgroup v2, delegated `cpu` and `memory`
controllers, and start the API socket with
`systemctl --user enable --now podman.socket`. The launcher checks these before
pulling images and does not install Podman. Install Podman and docker-compose
with your distribution's package manager (DNF on Fedora/RHEL, Zypper on
openSUSE, APT on Debian/Ubuntu). `--access host-root` is unsupported with
Podman. Windows Podman is outside the supported scope.

On SELinux hosts, Podman workspace binds use a shared `z` relabel and a
`keep-id:uid=10001,gid=10001` user namespace so files created by agents retain
the invoking user's ownership. Relabeling `$HOME` itself or system paths is
refused; register a dedicated project directory. On macOS, start a Podman
machine first. The launcher checks its CPU, memory, and disk size against the
selected profile and prints an exact `podman machine stop && podman machine set
... && podman machine start` command when it is too small.

Run the same command in Linux, macOS, or Windows 11 Command Prompt:

Expand Down
6 changes: 3 additions & 3 deletions packages/run-spaceapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"version": "1.1.8",
"spaceappRuntimeVersion": "1.1.8",
"spaceappHostRootRuntimeCompatible": true,
"description": "Cross-platform Docker launcher for the SpaceApp self-hosted agent workspace",
"license": "MIT",
"description": "Docker and rootless Podman launcher for the SpaceApp self-hosted agent workspace",
"license": "Apache-2.0",
"type": "module",
"bin": {
"spaceapp": "bin/spaceapp.mjs",
Expand Down Expand Up @@ -36,7 +36,7 @@
],
"scripts": {
"test": "node --test tests/*.test.mjs",
"check": "node --check bin/spaceapp.mjs && node --check src/package-info.mjs && node --check src/index.mjs && node --check src/cli.mjs && node --check src/storage.mjs && node --check src/telemetry.mjs && node --check src/prerequisites.mjs",
"check": "node --check bin/spaceapp.mjs && node --check src/package-info.mjs && node --check src/index.mjs && node --check src/cli.mjs && node --check src/engine.mjs && node --check src/storage.mjs && node --check src/telemetry.mjs && node --check src/prerequisites.mjs",
"pack:dry-run": "npm pack --dry-run"
},
"repository": {
Expand Down
Loading