fix(podman): stop assuming /tmp is disk-backed (#38296)

* Podman: avoid hardcoding /tmp for image staging

* Docs: clarify container storage paths

* Podman: secure staged image import

* Podman: clarify streamed image handoff
This commit is contained in:
Vincent Koc
2026-03-06 19:55:26 -05:00
committed by GitHub
parent 74959fc1fd
commit 2d52c88dad
3 changed files with 67 additions and 5 deletions

View File

@@ -535,6 +535,12 @@ docker compose run --rm openclaw-cli devices list --url ws://127.0.0.1:18789
- Dockerfile CMD uses `--allow-unconfigured`; mounted config with `gateway.mode` not `local` will still start. Override CMD to enforce the guard.
- The gateway container is the source of truth for sessions (`~/.openclaw/agents/<agentId>/sessions/`).
### Storage model
- **Persistent host data:** Docker Compose bind-mounts `OPENCLAW_CONFIG_DIR` to `/home/node/.openclaw` and `OPENCLAW_WORKSPACE_DIR` to `/home/node/.openclaw/workspace`, so those paths survive container replacement.
- **Ephemeral sandbox tmpfs:** when `agents.defaults.sandbox` is enabled, the sandbox containers use `tmpfs` for `/tmp`, `/var/tmp`, and `/run`. Those mounts are separate from the top-level Compose stack and disappear with the sandbox container.
- **Disk growth hotspots:** watch `media/`, `agents/<agentId>/sessions/sessions.json`, transcript JSONL files, `cron/runs/*.jsonl`, and rolling file logs under `/tmp/openclaw/` (or your configured `logging.file`). If you also run the macOS app outside Docker, its service logs are separate again: `~/.openclaw/logs/gateway.log`, `~/.openclaw/logs/gateway.err.log`, and `/tmp/openclaw/openclaw-gateway.log`.
## Agent Sandbox (host gateway + Docker tools)
Deep dive: [Sandboxing](/gateway/sandboxing)

View File

@@ -93,6 +93,14 @@ To add quadlet **after** an initial setup that did not use it, re-run: `./setup-
- **Gateway bind:** By default, `run-openclaw-podman.sh` starts the gateway with `--bind loopback` for safe local access. To expose on LAN, set `OPENCLAW_GATEWAY_BIND=lan` and configure `gateway.controlUi.allowedOrigins` (or explicitly enable host-header fallback) in `openclaw.json`.
- **Paths:** Host config and workspace default to `~openclaw/.openclaw` and `~openclaw/.openclaw/workspace`. Override the host paths used by the launch script with `OPENCLAW_CONFIG_DIR` and `OPENCLAW_WORKSPACE_DIR`.
## Storage model
- **Persistent host data:** `OPENCLAW_CONFIG_DIR` and `OPENCLAW_WORKSPACE_DIR` are bind-mounted into the container and retain state on the host.
- **Ephemeral sandbox tmpfs:** if you enable `agents.defaults.sandbox`, the tool sandbox containers mount `tmpfs` at `/tmp`, `/var/tmp`, and `/run`. Those paths are memory-backed and disappear with the sandbox container; the top-level Podman container setup does not add its own tmpfs mounts.
- **Disk growth hotspots:** the main paths to watch are `media/`, `agents/<agentId>/sessions/sessions.json`, transcript JSONL files, `cron/runs/*.jsonl`, and rolling file logs under `/tmp/openclaw/` (or your configured `logging.file`).
`setup-podman.sh` now stages the image tar in a private temp directory and prints the chosen base dir during setup. For non-root runs it accepts `TMPDIR` only when that base is safe to use; otherwise it falls back to `/var/tmp`, then `/tmp`. The saved tar stays owner-only and is streamed into the target users `podman load`, so private caller temp dirs do not block setup.
## Useful commands
- **Logs:** With quadlet: `sudo journalctl --machine openclaw@ --user -u openclaw.service -f`. With script: `sudo -u openclaw podman logs -f openclaw`

View File

@@ -27,6 +27,48 @@ require_cmd() {
fi
}
is_writable_dir() {
local dir="$1"
[[ -n "$dir" && -d "$dir" && ! -L "$dir" && -w "$dir" && -x "$dir" ]]
}
is_safe_tmp_base() {
local dir="$1"
local mode=""
local owner=""
is_writable_dir "$dir" || return 1
mode="$(stat -Lc '%a' "$dir" 2>/dev/null || true)"
if [[ -n "$mode" ]]; then
local perm=$((8#$mode))
if (( (perm & 0022) != 0 && (perm & 01000) == 0 )); then
return 1
fi
fi
if is_root; then
owner="$(stat -Lc '%u' "$dir" 2>/dev/null || true)"
if [[ -n "$owner" && "$owner" != "0" ]]; then
return 1
fi
fi
return 0
}
resolve_image_tmp_dir() {
if ! is_root && is_safe_tmp_base "${TMPDIR:-}"; then
printf '%s' "$TMPDIR"
return 0
fi
if is_safe_tmp_base "/var/tmp"; then
printf '%s' "/var/tmp"
return 0
fi
if is_safe_tmp_base "/tmp"; then
printf '%s' "/tmp"
return 0
fi
printf '%s' "/tmp"
}
is_root() { [[ "$(id -u)" -eq 0 ]]; }
run_root() {
@@ -215,12 +257,18 @@ BUILD_ARGS=()
podman build ${BUILD_ARGS[@]+"${BUILD_ARGS[@]}"} -t openclaw:local -f "$REPO_PATH/Dockerfile" "$REPO_PATH"
echo "Loading image into $OPENCLAW_USER's Podman store..."
TMP_IMAGE="$(mktemp -p /tmp openclaw-image.XXXXXX.tar)"
trap 'rm -f "$TMP_IMAGE"' EXIT
TMP_IMAGE_DIR="$(resolve_image_tmp_dir)"
echo "Using temporary image dir: $TMP_IMAGE_DIR"
TMP_STAGE_DIR="$(mktemp -d -p "$TMP_IMAGE_DIR" openclaw-image.XXXXXX)"
TMP_IMAGE="$TMP_STAGE_DIR/image.tar"
chmod 700 "$TMP_STAGE_DIR"
trap 'rm -rf "$TMP_STAGE_DIR"' EXIT
podman save openclaw:local -o "$TMP_IMAGE"
chmod 644 "$TMP_IMAGE"
(cd /tmp && run_as_user "$OPENCLAW_USER" env HOME="$OPENCLAW_HOME" podman load -i "$TMP_IMAGE")
rm -f "$TMP_IMAGE"
chmod 600 "$TMP_IMAGE"
# Stream the image into the target user's podman load so private temp directories
# do not need to be traversable by $OPENCLAW_USER.
cat "$TMP_IMAGE" | run_as_user "$OPENCLAW_USER" env HOME="$OPENCLAW_HOME" podman load
rm -rf "$TMP_STAGE_DIR"
trap - EXIT
echo "Copying launch script to $LAUNCH_SCRIPT_DST..."