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
128 changes: 128 additions & 0 deletions .github/workflows/podman.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Rootless Podman launcher
on:
pull_request:
paths:
- 'packages/run-spaceapp/**'
- 'scripts/podman-launcher-smoke.mjs'
- 'scripts/tests/podman-smoke-cleanup.test.mjs'
- '.github/workflows/podman.yml'
- 'Dockerfile'
push:
paths:
- 'packages/run-spaceapp/**'
- 'scripts/podman-launcher-smoke.mjs'
- 'scripts/tests/podman-smoke-cleanup.test.mjs'
- '.github/workflows/podman.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: podman-${{ github.ref }}
cancel-in-progress: true
jobs:
small:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '22'
- name: Install rootless engine and Compose provider
shell: bash
run: |
set -euo pipefail
# Install the supported engine/provider independently of the runner's bundled version.
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
brew install podman docker-compose
echo "$(brew --prefix)/bin" >> "$GITHUB_PATH"
echo "PODMAN_COMPOSE_PROVIDER=$(brew --prefix)/bin/docker-compose" >> "$GITHUB_ENV"
sudo apt-get update
sudo apt-get install -y uidmap
# Keep the new engine separate from bundled rootful storage/configuration.
export XDG_CONFIG_HOME="$RUNNER_TEMP/spaceapp-podman-config"
export XDG_DATA_HOME="$RUNNER_TEMP/spaceapp-podman-data"
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
export CONTAINERS_STORAGE_CONF="$XDG_CONFIG_HOME/containers/storage.conf"
export CONTAINERS_CONF="$XDG_CONFIG_HOME/containers/containers.conf"
mkdir -p "$XDG_CONFIG_HOME/containers" "$XDG_DATA_HOME/containers/storage" "$XDG_RUNTIME_DIR/containers"
cat > "$CONTAINERS_STORAGE_CONF" <<EOF
[storage]
driver = "overlay"
runroot = "$XDG_RUNTIME_DIR/containers"
graphroot = "$XDG_DATA_HOME/containers/storage"
EOF
cat > "$CONTAINERS_CONF" <<EOF
[engine]
static_dir = "$XDG_DATA_HOME/containers/storage/libpod"
volume_path = "$XDG_DATA_HOME/containers/storage/volumes"
tmp_dir = "$XDG_RUNTIME_DIR/libpod/tmp"
cgroup_manager = "systemd"
events_logger = "file"
EOF
printf '%s\n' "XDG_CONFIG_HOME=$XDG_CONFIG_HOME" "XDG_DATA_HOME=$XDG_DATA_HOME" \
"XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" "CONTAINERS_STORAGE_CONF=$CONTAINERS_STORAGE_CONF" \
"CONTAINERS_CONF=$CONTAINERS_CONF" >> "$GITHUB_ENV"
# Health checks run in systemd's user manager, outside this shell.
systemctl --user import-environment XDG_CONFIG_HOME XDG_DATA_HOME CONTAINERS_STORAGE_CONF CONTAINERS_CONF
# Grant user namespaces to this engine binary on Ubuntu's restricted-userns hosts.
# This leaves the system restriction and other applications' profiles in place.
if command -v apparmor_parser >/dev/null; then
printf 'abi <abi/4.0>,\ninclude <tunables/global>\nprofile spaceapp-ci-podman /home/linuxbrew/.linuxbrew/Cellar/podman/*/bin/podman flags=(unconfined) {\n userns,\n}\n' | sudo tee /etc/apparmor.d/spaceapp-ci-podman >/dev/null
sudo apparmor_parser -r /etc/apparmor.d/spaceapp-ci-podman
fi
test "$(id -u)" != 0
podman info --format json
# Exercise the same UID mapping before the full application build.
probe=$(mktemp -d "$RUNNER_TEMP/podman-ownership.XXXXXX")
trap 'rm -rf -- "$probe"' EXIT
podman run --rm --network none --userns keep-id:uid=10001,gid=10001 --user 10001:10001 \
-v "$probe:/probe" \
docker.io/library/node:22.23.0-trixie-slim@sha256:cfd8f2a5bc50526aee08e88970979f92722828e7dcc6d8983607fb8bff4bdb82 \
node -e 'if (process.getuid() !== 10001) process.exit(1); require("node:fs").writeFileSync("/probe/owned", "ok")'
test "$(stat -c '%u' "$probe/owned")" = "$(id -u)"
# Require an automatically scheduled health check, not a manual invocation.
health_container=$(podman run -d --network none --health-cmd 'node -e "process.exit(0)"' --health-interval 1s \
docker.io/library/node:22.23.0-trixie-slim@sha256:cfd8f2a5bc50526aee08e88970979f92722828e7dcc6d8983607fb8bff4bdb82 \
node -e 'setInterval(() => {}, 1000)')
trap 'podman rm -f "$health_container"; rm -rf -- "$probe"' EXIT
for attempt in {1..30}; do
health=$(podman inspect --format '{{.State.Health.Status}}' "$health_container")
if [ "$health" = healthy ]; then break; fi
sleep 1
done
if [ "$health" != healthy ]; then
podman inspect --format '{{json .State.Health}}' "$health_container"
systemctl --user list-timers --all --no-pager
exit 1
fi
echo 'PASS automatic Podman health check'
- name: Unit tests and runtime images
shell: bash
run: |
set -euo pipefail
npm test --workspace=run-spaceapp
node --test scripts/tests/podman-smoke-cleanup.test.mjs
version=$(node -p "require('./packages/run-spaceapp/package.json').spaceappRuntimeVersion")
for target in core cli; do
podman build --format docker --layers --build-arg TARGETARCH=amd64 --target "$target" -t "ghcr.io/oll4com/spaceapp-$target:$version" .
done
podman pull pgvector/pgvector:0.8.3-pg17-trixie@sha256:3a3147ccd9a0aee952bb69e1acea1eb3cc7183544185b87a662f872cfa47ae3b
- name: Real small install, readiness, ownership, backup, restore and purge
shell: bash
env:
SPACEAPP_SMOKE_LOCAL_IMAGES: '1'
run: |
set -euo pipefail
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
mkdir -p "$XDG_RUNTIME_DIR/podman"
podman system service --time=0 "unix://$XDG_RUNTIME_DIR/podman/podman.sock" > "$RUNNER_TEMP/spaceapp-podman-api.log" 2>&1 &
service_pid=$!
trap 'kill "$service_pid" 2>/dev/null || true' EXIT
for attempt in {1..30}; do
if curl --fail --silent --unix-socket "$XDG_RUNTIME_DIR/podman/podman.sock" http://localhost/_ping; then break; fi
sleep 1
done
timeout --signal=TERM --kill-after=30s 20m node scripts/podman-launcher-smoke.mjs small
11 changes: 9 additions & 2 deletions 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 on Linux/macOS/Windows, or rootless Podman >=5 with Docker Compose >=5.6.0 on Linux/macOS. Docker installation remains automatic when selected; Podman and its Compose provider must already be installed.

### Run the Installation Command

Expand All @@ -102,10 +102,17 @@ Run the universal command in your terminal:
npx --yes run-spaceapp@latest install
```

For rootless Podman, use `install --engine podman --profile small`. On macOS,
start a rootless Podman machine with at least 4 CPUs, 8192 MiB RAM, and enough
free disk first. The engine is saved for every later command. A working Docker
engine is preferred automatically; Podman-only hosts use Podman without a
Docker install. Existing installations cannot switch engines in place, and
Podman requires isolated access. See [Podman setup and diagnostics](packages/run-spaceapp/README.md#rootless-podman-linux-and-macos).

> **Windows 11 Tip**: Use **Command Prompt** (`cmd.exe`) or the Command Prompt profile in Windows Terminal. If PowerShell has a restricted execution policy, run `npx.cmd --yes run-spaceapp@latest install`.

### What Happens Next (Step-by-Step Onboarding):
1. **System & Docker Verification**: The launcher verifies CPU, RAM, and disk space, and installs/starts Docker if needed.
1. **System & Engine Verification**: The launcher verifies CPU, RAM, disk space and the selected engine; the Docker path installs/starts Docker if needed.
2. **Container Initialization**: Pulls SpaceApp's images by the digest the npm package pins (from run-spaceapp 1.1.7; earlier packages by exact version tag; published with SBOM and provenance attestations that the release workflow verifies) and the third-party images pinned by digest, then starts the container stack.
3. **One-Time Setup Token**: The terminal prints a secure 15-minute setup token and automatically opens `http://127.0.0.1:4911`.
4. **Create Your Operator Account**: Paste the token into the setup screen, enter your email and password, and click Create Owner.
Expand Down
46 changes: 41 additions & 5 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Getting started

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.
in containers. The host needs Node.js only for the small `spaceapp` launcher,
and enough resources for the stack. Docker is the default engine; the launcher
installs and starts it when selected and missing. Rootless Podman is also
supported on Linux and macOS.

## Requirements

Expand Down Expand Up @@ -53,6 +54,41 @@ its code signature, installs it, starts it, and waits until it is ready. Docker
Desktop uses its managed lightweight Linux environment. Apple Silicon uses the
native `linux/arm64` images and Intel Macs use `linux/amd64`.

## Rootless Podman on Linux or macOS

Install Podman >=5 and Docker Compose >=5.6.0, then select the provider with
`PODMAN_COMPOSE_PROVIDER=/path/to/docker-compose` when needed. Use a normal
user with cgroup v2 and delegated CPU/memory controllers. `podman info` must
report rootless mode. On macOS, start a rootless Podman machine first and
allocate at least 4 CPUs and 8192 MiB RAM for `small`; the larger profiles need
their listed CPU/RAM allocation inside the machine too. Ensure both the host
and machine have enough free disk and share your installation/workspace paths.

```bash
npx --yes run-spaceapp@latest install --engine podman --profile small
npx --yes run-spaceapp@latest doctor
```

The engine choice persists for every lifecycle, workspace, provider, backup,
restore, update/rollback and diagnostic command. `--answers` also accepts
`"engine": "podman"`. A working Docker engine wins automatic selection; a
Podman-only installation does not install Docker. Existing configurations
without an engine field use Docker. Changing an existing installation's engine
is refused; use a separate `SPACEAPP_HOME` for a fresh install. The Podman path
requires isolated access and refuses host-root access.

`doctor` shows the engine and Compose versions, rootless/cgroup capabilities,
and actual CPU/RAM/disk capacity. On macOS, it includes machine limits and
resize guidance: stop the machine, run `podman machine set --cpus N --memory
MiB --disk-size GiB <name>`, and start it again. The launcher does not resize it.

With Podman's `keep-id` user namespace, application uid 10001 maps to your
host user. Workspace writes retain your ownership without host `chown` or ACL
changes. On SELinux systems, only launcher-owned binds and explicitly added
workspace paths receive shared `z` labels. Read-only workspaces remain
read-only. The same published application images are used by both engines.
For provider details and commands see the [launcher guide](../packages/run-spaceapp/README.md#rootless-podman-linux-and-macos).

## Windows 11 — Command Prompt

Enable hardware virtualization and install Node.js 20.11 or newer. Open
Expand Down Expand Up @@ -312,8 +348,8 @@ npx --yes run-spaceapp@latest status
npx --yes run-spaceapp@latest logs
```

`doctor` checks Node.js, Docker CLI, Docker Compose, Docker Engine, CPU,
memory, and free disk exactly once each. It distinguishes a missing CLI or
`doctor` checks Node.js, the selected container engine and Compose provider,
CPU, memory, and free disk. It distinguishes a missing CLI or
Compose plugin from an installed engine that is stopped or inaccessible.
The `small` profile should show core, CLI, and PostgreSQL running; `medium`
adds Temporal, and `large` also adds the browser service. If startup is still
Expand Down
51 changes: 48 additions & 3 deletions packages/run-spaceapp/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# run-spaceapp

Cross-platform Docker launcher for SpaceApp.
Cross-platform container launcher for SpaceApp: Docker, or rootless Podman on Linux and macOS.

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

Expand All @@ -17,7 +17,7 @@ npm install -g run-spaceapp

The smallest profile requires Node.js 20.11 or newer, 4 CPUs, 8 GB-class RAM
(7 GiB usable), and 6.5 GiB free for a fresh installation. Keep `@latest` in the command so npm resolves the current release instead
of matching an existing local launcher. If Docker is missing, the universal
of matching an existing local launcher. With the Docker engine selected, if Docker is missing the universal
install command automatically installs it from official Docker sources on
Windows, macOS, Ubuntu, Debian, Fedora, RHEL, and CentOS, or with native
`pacman` packages on Arch-family Linux including CachyOS. It starts Docker and
Expand All @@ -41,7 +41,7 @@ credential-free config so Docker Desktop's graphical credential helper is never
invoked outside an interactive logon.

The launcher selects `small`, `medium`, or `large` automatically using CPU,
RAM and free disk. Docker's CPU and RAM allocation is checked as well.
RAM and free disk. The selected engine's CPU and RAM allocation is checked as well.

| Profile | CPU | Usable RAM | Fresh free space | Services |
| --- | --- | --- | --- | --- |
Expand All @@ -62,6 +62,51 @@ before pausing writers. Existing data is kept in place; the launcher never delet
images, backups or volumes to free space. Separate Docker disk capacity is checked
when measurable and its availability is reported.

### Rootless Podman (Linux and macOS)

Install Podman **5 or newer** and **Docker Compose 5.6.0 or newer** first.
Set `PODMAN_COMPOSE_PROVIDER` to the Docker Compose executable if Podman does
not select it automatically. The alternative `podman-compose` Python provider
is not supported. Run as your normal user; cgroup v2 with delegated CPU and
memory controllers is required. `podman info` must report rootless operation.

```bash
npx --yes run-spaceapp@latest install --engine podman --profile small
```

The engine is saved in `config.json`; `up`, `down`, `status`, `logs`, updates,
rollback, backup/restore, provider commands, `doctor --fix`, and uninstall all
reuse it. Non-interactive installation accepts `"engine": "podman"` in
`--answers`. Without an explicit choice, a working Docker engine takes
precedence; a Podman-only host selects Podman without installing Docker.
Existing configurations without an engine field continue using Docker.
SpaceApp refuses to switch an existing installation's engine because its
volumes belong to that engine. Use a separate `SPACEAPP_HOME` for a fresh
installation; there is no automatic data migration. Podman only supports
`--access isolated`; `--access host-root` is refused before changes.

On macOS, create/start a rootless `podman machine` first. Its resources must
meet the selected profile, in addition to the host requirements above. For
example, `small` needs at least 4 CPUs, 8192 MiB machine RAM, and sufficient
free space inside the machine and on the host. Inspect with
`podman machine inspect <name>`. To increase capacity, stop the machine, run
`podman machine set --cpus 4 --memory 8192 --disk-size 40 <name>`, and start it
again. SpaceApp reports insufficient capacity and never resizes a machine.
Installation and workspace directories must be shared with that machine.

The regular published images are used. Containers initialize volumes as
namespace root and run application processes as uid 10001; `keep-id` maps
that uid to your host user so workspace writes keep your ownership. On
SELinux hosts, launcher-owned secret binds and explicitly added workspaces
receive a shared `z` label; read-only workspaces stay read-only. No whole-home
relabel or host `chown` is performed. `doctor` and `support-bundle` report the
engine/version, Compose provider, rootless state, cgroup delegation and
available CPU/RAM/disk without including the raw engine environment.

`uninstall` removes containers while preserving data; `uninstall --purge`
(or `--purge-data`) additionally removes this installation's volumes after
confirmation.

### Telemetry

Telemetry is off unless you turn it on. The install wizard lists what is sent
Expand Down
2 changes: 1 addition & 1 deletion packages/run-spaceapp/package.json
Original file line number Diff line number Diff line change
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