mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-29 02:41:07 +00:00
* update podman setup and docs Signed-off-by: sallyom <somalley@redhat.com> * podman: persist runtime env defaults Co-authored-by: albertxos <kickban3000@gmail.com> Signed-off-by: sallyom <somalley@redhat.com> * podman: harden env and path handling, other setup updates Signed-off-by: sallyom <somalley@redhat.com> * podman: allow symlinked home path components Signed-off-by: sallyom <somalley@redhat.com> * update podman docs Signed-off-by: sallyom <somalley@redhat.com> --------- Signed-off-by: sallyom <somalley@redhat.com> Co-authored-by: albertxos <kickban3000@gmail.com>
575 lines
18 KiB
Bash
Executable File
575 lines
18 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Rootless OpenClaw in Podman: run after one-time setup.
|
|
#
|
|
# One-time setup (from repo root): ./scripts/podman/setup.sh
|
|
# Then:
|
|
# ./scripts/run-openclaw-podman.sh launch # Start gateway
|
|
# ./scripts/run-openclaw-podman.sh launch setup # Onboarding wizard
|
|
#
|
|
# Manage the running container from the host CLI:
|
|
# openclaw --container openclaw dashboard --no-open
|
|
# openclaw --container openclaw channels login
|
|
#
|
|
# Legacy: "setup-host" delegates to the Podman setup script
|
|
|
|
set -euo pipefail
|
|
|
|
PLATFORM_NAME="$(uname -s 2>/dev/null || echo unknown)"
|
|
|
|
resolve_user_home() {
|
|
local user="$1"
|
|
local home=""
|
|
if command -v getent >/dev/null 2>&1; then
|
|
home="$(getent passwd "$user" 2>/dev/null | cut -d: -f6 || true)"
|
|
fi
|
|
if [[ -z "$home" && -f /etc/passwd ]]; then
|
|
home="$(awk -F: -v u="$user" '$1==u {print $6}' /etc/passwd 2>/dev/null || true)"
|
|
fi
|
|
if [[ -z "$home" ]]; then
|
|
home="/home/$user"
|
|
fi
|
|
printf '%s' "$home"
|
|
}
|
|
|
|
fail() {
|
|
echo "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
validate_single_line_value() {
|
|
local label="$1"
|
|
local value="$2"
|
|
if [[ "$value" == *$'\n'* || "$value" == *$'\r'* ]]; then
|
|
fail "Invalid $label: control characters are not allowed."
|
|
fi
|
|
}
|
|
|
|
validate_absolute_path() {
|
|
local label="$1"
|
|
local value="$2"
|
|
validate_single_line_value "$label" "$value"
|
|
[[ "$value" == /* ]] || fail "Invalid $label: expected an absolute path."
|
|
[[ "$value" != *"//"* ]] || fail "Invalid $label: repeated slashes are not allowed."
|
|
[[ "$value" != *"/./"* && "$value" != */. && "$value" != *"/../"* && "$value" != */.. ]] ||
|
|
fail "Invalid $label: dot path segments are not allowed."
|
|
}
|
|
|
|
validate_mount_source_path() {
|
|
local label="$1"
|
|
local value="$2"
|
|
validate_absolute_path "$label" "$value"
|
|
[[ "$value" != *:* ]] || fail "Invalid $label: ':' is not allowed in Podman bind-mount source paths."
|
|
}
|
|
|
|
ensure_safe_existing_regular_file() {
|
|
local label="$1"
|
|
local file="$2"
|
|
validate_absolute_path "$label" "$file"
|
|
[[ -e "$file" ]] || fail "Missing $label: $file"
|
|
[[ ! -L "$file" ]] || fail "Unsafe $label: symlinks are not allowed ($file)"
|
|
[[ -f "$file" ]] || fail "Unsafe $label: expected a regular file ($file)"
|
|
}
|
|
|
|
ensure_safe_existing_dir() {
|
|
local label="$1"
|
|
local dir="$2"
|
|
validate_absolute_path "$label" "$dir"
|
|
[[ -d "$dir" ]] || fail "Missing $label: $dir"
|
|
[[ ! -L "$dir" ]] || fail "Unsafe $label: symlinks are not allowed ($dir)"
|
|
}
|
|
|
|
stat_uid() {
|
|
local path="$1"
|
|
if stat -f '%u' "$path" >/dev/null 2>&1; then
|
|
stat -f '%u' "$path"
|
|
else
|
|
stat -Lc '%u' "$path"
|
|
fi
|
|
}
|
|
|
|
stat_mode() {
|
|
local path="$1"
|
|
if stat -f '%Lp' "$path" >/dev/null 2>&1; then
|
|
stat -f '%Lp' "$path"
|
|
else
|
|
stat -Lc '%a' "$path"
|
|
fi
|
|
}
|
|
|
|
ensure_private_existing_dir_owned_by_user() {
|
|
local label="$1"
|
|
local dir="$2"
|
|
local uid=""
|
|
local mode=""
|
|
ensure_safe_existing_dir "$label" "$dir"
|
|
uid="$(stat_uid "$dir")"
|
|
[[ "$uid" == "$(id -u)" ]] || fail "Unsafe $label: not owned by current user ($dir)"
|
|
mode="$(stat_mode "$dir")"
|
|
(( (8#$mode & 0022) == 0 )) || fail "Unsafe $label: group/other writable ($dir)"
|
|
}
|
|
|
|
ensure_private_existing_regular_file_owned_by_user() {
|
|
local label="$1"
|
|
local file="$2"
|
|
local uid=""
|
|
local mode=""
|
|
ensure_safe_existing_regular_file "$label" "$file"
|
|
uid="$(stat_uid "$file")"
|
|
[[ "$uid" == "$(id -u)" ]] || fail "Unsafe $label: not owned by current user ($file)"
|
|
mode="$(stat_mode "$file")"
|
|
(( (8#$mode & 0077) == 0 )) || fail "Unsafe $label: expected owner-only permissions ($file)"
|
|
}
|
|
|
|
ensure_safe_write_file_path() {
|
|
local label="$1"
|
|
local file="$2"
|
|
local dir
|
|
validate_absolute_path "$label" "$file"
|
|
if [[ -e "$file" ]]; then
|
|
[[ ! -L "$file" ]] || fail "Unsafe $label: symlinks are not allowed ($file)"
|
|
[[ -f "$file" ]] || fail "Unsafe $label: expected a regular file ($file)"
|
|
fi
|
|
dir="$(dirname "$file")"
|
|
ensure_safe_existing_dir "${label} parent directory" "$dir"
|
|
}
|
|
|
|
write_file_atomically() {
|
|
local file="$1"
|
|
local mode="$2"
|
|
local dir=""
|
|
local tmp=""
|
|
ensure_safe_write_file_path "output file" "$file"
|
|
dir="$(dirname "$file")"
|
|
tmp="$(mktemp "$dir/.tmp.XXXXXX")"
|
|
cat >"$tmp"
|
|
chmod "$mode" "$tmp"
|
|
mv -f "$tmp" "$file"
|
|
}
|
|
|
|
load_podman_env_file() {
|
|
local file="$1"
|
|
local line=""
|
|
local key=""
|
|
local value=""
|
|
local trimmed=""
|
|
local dir=""
|
|
ensure_private_existing_regular_file_owned_by_user "Podman env file" "$file"
|
|
dir="$(dirname "$file")"
|
|
ensure_private_existing_dir_owned_by_user "Podman env directory" "$dir"
|
|
exec 9<"$file" || fail "Unable to open Podman env file: $file"
|
|
while IFS= read -r line <&9 || [[ -n "$line" ]]; do
|
|
trimmed="${line#"${line%%[![:space:]]*}"}"
|
|
[[ -z "$trimmed" || "${trimmed:0:1}" == "#" ]] && continue
|
|
[[ "$line" == *"="* ]] || continue
|
|
key="${line%%=*}"
|
|
value="${line#*=}"
|
|
key="${key#"${key%%[![:space:]]*}"}"
|
|
key="${key%"${key##*[![:space:]]}"}"
|
|
[[ "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
|
|
case "$key" in
|
|
OPENCLAW_GATEWAY_TOKEN|OPENCLAW_PODMAN_CONTAINER|OPENCLAW_PODMAN_IMAGE|OPENCLAW_IMAGE|OPENCLAW_PODMAN_PULL|OPENCLAW_PODMAN_GATEWAY_HOST_PORT|OPENCLAW_GATEWAY_PORT|OPENCLAW_PODMAN_BRIDGE_HOST_PORT|OPENCLAW_BRIDGE_PORT|OPENCLAW_GATEWAY_BIND|OPENCLAW_PODMAN_USERNS|OPENCLAW_BIND_MOUNT_OPTIONS|OPENCLAW_PODMAN_PUBLISH_HOST)
|
|
;;
|
|
*)
|
|
continue
|
|
;;
|
|
esac
|
|
if [[ "$value" =~ ^\".*\"$ || "$value" =~ ^\'.*\'$ ]]; then
|
|
value="${value:1:${#value}-2}"
|
|
fi
|
|
printf -v "$key" '%s' "$value"
|
|
export "$key"
|
|
done
|
|
exec 9<&-
|
|
}
|
|
|
|
validate_port() {
|
|
local label="$1"
|
|
local value="$2"
|
|
local numeric=""
|
|
[[ "$value" =~ ^[0-9]{1,5}$ ]] || fail "Invalid $label: must be numeric."
|
|
numeric=$((10#$value))
|
|
(( numeric >= 1 && numeric <= 65535 )) || fail "Invalid $label: out of range."
|
|
}
|
|
|
|
EFFECTIVE_USER="$(id -un)"
|
|
EFFECTIVE_HOME="${HOME:-}"
|
|
if [[ -z "$EFFECTIVE_HOME" ]]; then
|
|
EFFECTIVE_HOME="$(resolve_user_home "$EFFECTIVE_USER")"
|
|
fi
|
|
if [[ "$(id -u)" -eq 0 ]]; then
|
|
fail "Run run-openclaw-podman.sh as your normal user so Podman stays rootless."
|
|
fi
|
|
|
|
# Legacy: setup-host -> run the Podman setup script
|
|
if [[ "${1:-}" == "setup-host" ]]; then
|
|
shift
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
SETUP_PODMAN="$REPO_ROOT/scripts/podman/setup.sh"
|
|
if [[ -f "$SETUP_PODMAN" ]]; then
|
|
exec "$SETUP_PODMAN" "$@"
|
|
fi
|
|
SETUP_PODMAN="$REPO_ROOT/setup-podman.sh"
|
|
if [[ -f "$SETUP_PODMAN" ]]; then
|
|
exec "$SETUP_PODMAN" "$@"
|
|
fi
|
|
echo "Podman setup script not found. Run from repo root: ./scripts/podman/setup.sh" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "${1:-}" == "launch" ]]; then
|
|
shift
|
|
fi
|
|
|
|
if [[ -z "${EFFECTIVE_HOME:-}" ]]; then
|
|
EFFECTIVE_HOME="/tmp"
|
|
fi
|
|
validate_absolute_path "effective home" "$EFFECTIVE_HOME"
|
|
|
|
CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$EFFECTIVE_HOME/.openclaw}"
|
|
ENV_FILE="${OPENCLAW_PODMAN_ENV:-$CONFIG_DIR/.env}"
|
|
# Bootstrap `.env` may set runtime/container options, but it must not
|
|
# relocate the config/workspace/env paths mid-run. Those path overrides are
|
|
# only honored from the parent process environment before bootstrap.
|
|
if [[ -f "$ENV_FILE" ]]; then
|
|
load_podman_env_file "$ENV_FILE"
|
|
fi
|
|
|
|
CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$EFFECTIVE_HOME/.openclaw}"
|
|
ENV_FILE="${OPENCLAW_PODMAN_ENV:-$CONFIG_DIR/.env}"
|
|
WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-$CONFIG_DIR/workspace}"
|
|
CONTAINER_NAME="${OPENCLAW_PODMAN_CONTAINER:-openclaw}"
|
|
OPENCLAW_IMAGE="${OPENCLAW_PODMAN_IMAGE:-${OPENCLAW_IMAGE:-openclaw:local}}"
|
|
PODMAN_PULL="${OPENCLAW_PODMAN_PULL:-never}"
|
|
HOST_GATEWAY_PORT="${OPENCLAW_PODMAN_GATEWAY_HOST_PORT:-${OPENCLAW_GATEWAY_PORT:-18789}}"
|
|
HOST_BRIDGE_PORT="${OPENCLAW_PODMAN_BRIDGE_HOST_PORT:-${OPENCLAW_BRIDGE_PORT:-18790}}"
|
|
PUBLISH_HOST="${OPENCLAW_PODMAN_PUBLISH_HOST:-127.0.0.1}"
|
|
validate_mount_source_path "config directory" "$CONFIG_DIR"
|
|
validate_mount_source_path "workspace directory" "$WORKSPACE_DIR"
|
|
validate_absolute_path "env file path" "$ENV_FILE"
|
|
validate_single_line_value "container name" "$CONTAINER_NAME"
|
|
validate_single_line_value "image name" "$OPENCLAW_IMAGE"
|
|
validate_single_line_value "publish host" "$PUBLISH_HOST"
|
|
validate_port "gateway host port" "$HOST_GATEWAY_PORT"
|
|
validate_port "bridge host port" "$HOST_BRIDGE_PORT"
|
|
|
|
cd "$EFFECTIVE_HOME" 2>/dev/null || cd /tmp 2>/dev/null || true
|
|
|
|
RUN_SETUP=false
|
|
if [[ "${1:-}" == "setup" || "${1:-}" == "onboard" ]]; then
|
|
RUN_SETUP=true
|
|
shift
|
|
fi
|
|
|
|
mkdir -p "$CONFIG_DIR" "$WORKSPACE_DIR"
|
|
mkdir -p "$CONFIG_DIR/canvas" "$CONFIG_DIR/cron"
|
|
chmod 700 "$CONFIG_DIR" "$WORKSPACE_DIR"
|
|
ensure_private_existing_dir_owned_by_user "config directory" "$CONFIG_DIR"
|
|
ensure_private_existing_dir_owned_by_user "workspace directory" "$WORKSPACE_DIR"
|
|
|
|
# For published container ports, the gateway must listen on the container
|
|
# interface. Keep host access local-only by default via 127.0.0.1 publish.
|
|
GATEWAY_BIND="${OPENCLAW_GATEWAY_BIND:-lan}"
|
|
|
|
upsert_env_var() {
|
|
local file="$1"
|
|
local key="$2"
|
|
local value="$3"
|
|
local tmp
|
|
local dir
|
|
ensure_safe_write_file_path "env file" "$file"
|
|
dir="$(dirname "$file")"
|
|
tmp="$(mktemp "$dir/.env.tmp.XXXXXX")"
|
|
if [[ -f "$file" ]]; then
|
|
awk -v k="$key" -v v="$value" '
|
|
BEGIN { found = 0 }
|
|
$0 ~ ("^" k "=") { print k "=" v; found = 1; next }
|
|
{ print }
|
|
END { if (!found) print k "=" v }
|
|
' "$file" >"$tmp"
|
|
else
|
|
printf '%s=%s\n' "$key" "$value" >"$tmp"
|
|
fi
|
|
mv "$tmp" "$file"
|
|
chmod 600 "$file" 2>/dev/null || true
|
|
}
|
|
|
|
generate_token_hex_32() {
|
|
if command -v openssl >/dev/null 2>&1; then
|
|
openssl rand -hex 32
|
|
return 0
|
|
fi
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
python3 - <<'PY'
|
|
import secrets
|
|
print(secrets.token_hex(32))
|
|
PY
|
|
return 0
|
|
fi
|
|
if command -v od >/dev/null 2>&1; then
|
|
od -An -N32 -tx1 /dev/urandom | tr -d " \n"
|
|
return 0
|
|
fi
|
|
echo "Missing dependency: need openssl or python3 (or od) to generate OPENCLAW_GATEWAY_TOKEN." >&2
|
|
exit 1
|
|
}
|
|
|
|
create_token_env_file() {
|
|
local file="$1"
|
|
local token="$2"
|
|
local dir=""
|
|
local tmp=""
|
|
dir="$(dirname "$file")"
|
|
ensure_private_existing_dir_owned_by_user "token env directory" "$dir"
|
|
tmp="$(mktemp "$dir/.token.env.XXXXXX")"
|
|
chmod 600 "$tmp"
|
|
printf 'OPENCLAW_GATEWAY_TOKEN=%s\n' "$token" >"$tmp"
|
|
printf '%s' "$tmp"
|
|
}
|
|
|
|
sync_local_control_ui_origins_via_cli() {
|
|
local file="$1"
|
|
local port="$2"
|
|
local config_dir=""
|
|
local allowed_json=""
|
|
local merged_json=""
|
|
config_dir="$(dirname "$file")"
|
|
if ! command -v openclaw >/dev/null 2>&1; then
|
|
echo "Warning: openclaw not found; unable to sync gateway.controlUi.allowedOrigins in $file." >&2
|
|
return 0
|
|
fi
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
OPENCLAW_CONTAINER="" OPENCLAW_CONFIG_DIR="$config_dir" \
|
|
openclaw config set gateway.controlUi.allowedOrigins \
|
|
"[\"http://127.0.0.1:${port}\",\"http://localhost:${port}\"]" \
|
|
--strict-json >/dev/null
|
|
return 0
|
|
fi
|
|
allowed_json="$(
|
|
OPENCLAW_CONTAINER="" OPENCLAW_CONFIG_DIR="$config_dir" \
|
|
openclaw config get gateway.controlUi.allowedOrigins --json 2>/dev/null || true
|
|
)"
|
|
merged_json="$(python3 - "$port" "$allowed_json" <<'PY'
|
|
import json
|
|
import sys
|
|
|
|
port = sys.argv[1]
|
|
raw = sys.argv[2] if len(sys.argv) > 2 else ""
|
|
desired = [
|
|
f"http://127.0.0.1:{port}",
|
|
f"http://localhost:{port}",
|
|
]
|
|
allowed = []
|
|
if raw:
|
|
try:
|
|
parsed = json.loads(raw)
|
|
if isinstance(parsed, list):
|
|
allowed = parsed
|
|
except json.JSONDecodeError:
|
|
allowed = []
|
|
cleaned = []
|
|
seen = set()
|
|
for origin in allowed + desired:
|
|
if not isinstance(origin, str):
|
|
continue
|
|
normalized = origin.strip()
|
|
if not normalized or normalized in seen:
|
|
continue
|
|
cleaned.append(normalized)
|
|
seen.add(normalized)
|
|
print(json.dumps(cleaned))
|
|
PY
|
|
)"
|
|
OPENCLAW_CONTAINER="" OPENCLAW_CONFIG_DIR="$config_dir" \
|
|
openclaw config set gateway.controlUi.allowedOrigins "$merged_json" --strict-json >/dev/null
|
|
}
|
|
|
|
sync_local_control_ui_origins() {
|
|
local file="$1"
|
|
local port="$2"
|
|
local dir=""
|
|
local tmp=""
|
|
ensure_safe_write_file_path "config file" "$file"
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
echo "Warning: python3 not found; unable to sync gateway.controlUi.allowedOrigins in $file." >&2
|
|
return 0
|
|
fi
|
|
dir="$(dirname "$file")"
|
|
ensure_private_existing_dir_owned_by_user "config file directory" "$dir"
|
|
tmp="$(mktemp "$dir/.config.tmp.XXXXXX")"
|
|
if ! python3 - "$file" "$port" "$tmp" <<'PY'
|
|
import json
|
|
import sys
|
|
|
|
path = sys.argv[1]
|
|
port = sys.argv[2]
|
|
tmp = sys.argv[3]
|
|
try:
|
|
with open(path, "r", encoding="utf-8") as fh:
|
|
data = json.load(fh)
|
|
except json.JSONDecodeError as exc:
|
|
print(
|
|
f"Warning: unable to sync gateway.controlUi.allowedOrigins in {path}: existing config is not strict JSON ({exc}). Leaving file unchanged.",
|
|
file=sys.stderr,
|
|
)
|
|
raise SystemExit(1)
|
|
if not isinstance(data, dict):
|
|
raise SystemExit(f"{path}: expected top-level object")
|
|
gateway = data.setdefault("gateway", {})
|
|
if not isinstance(gateway, dict):
|
|
raise SystemExit(f"{path}: expected gateway object")
|
|
gateway.setdefault("mode", "local")
|
|
control_ui = gateway.setdefault("controlUi", {})
|
|
if not isinstance(control_ui, dict):
|
|
raise SystemExit(f"{path}: expected gateway.controlUi object")
|
|
allowed = control_ui.get("allowedOrigins")
|
|
desired = [
|
|
f"http://127.0.0.1:{port}",
|
|
f"http://localhost:{port}",
|
|
]
|
|
if not isinstance(allowed, list):
|
|
allowed = []
|
|
cleaned = []
|
|
seen = set()
|
|
for origin in allowed:
|
|
if not isinstance(origin, str):
|
|
continue
|
|
normalized = origin.strip()
|
|
if not normalized or normalized in seen:
|
|
continue
|
|
cleaned.append(normalized)
|
|
seen.add(normalized)
|
|
for origin in desired:
|
|
if origin not in seen:
|
|
cleaned.append(origin)
|
|
seen.add(origin)
|
|
control_ui["allowedOrigins"] = cleaned
|
|
with open(tmp, "w", encoding="utf-8") as fh:
|
|
json.dump(data, fh, indent=2)
|
|
fh.write("\n")
|
|
PY
|
|
then
|
|
rm -f "$tmp"
|
|
sync_local_control_ui_origins_via_cli "$file" "$port"
|
|
return 0
|
|
fi
|
|
[[ -s "$tmp" ]] || {
|
|
rm -f "$tmp"
|
|
return 0
|
|
}
|
|
chmod 600 "$tmp" 2>/dev/null || true
|
|
mv -f "$tmp" "$file"
|
|
}
|
|
|
|
TOKEN_ENV_FILE=""
|
|
cleanup_token_env_file() {
|
|
if [[ -n "$TOKEN_ENV_FILE" && -f "$TOKEN_ENV_FILE" ]]; then
|
|
rm -f "$TOKEN_ENV_FILE"
|
|
fi
|
|
}
|
|
trap cleanup_token_env_file EXIT
|
|
|
|
if [[ -z "${OPENCLAW_GATEWAY_TOKEN:-}" ]]; then
|
|
export OPENCLAW_GATEWAY_TOKEN="$(generate_token_hex_32)"
|
|
mkdir -p "$(dirname "$ENV_FILE")"
|
|
ensure_safe_existing_dir "env file directory" "$(dirname "$ENV_FILE")"
|
|
upsert_env_var "$ENV_FILE" "OPENCLAW_GATEWAY_TOKEN" "$OPENCLAW_GATEWAY_TOKEN"
|
|
echo "Generated OPENCLAW_GATEWAY_TOKEN and wrote it to $ENV_FILE." >&2
|
|
fi
|
|
|
|
CONFIG_JSON="$CONFIG_DIR/openclaw.json"
|
|
if [[ ! -f "$CONFIG_JSON" ]]; then
|
|
(
|
|
umask 077
|
|
write_file_atomically "$CONFIG_JSON" 600 <<'JSON'
|
|
{ "gateway": { "mode": "local" } }
|
|
JSON
|
|
)
|
|
echo "Created $CONFIG_JSON (minimal gateway.mode=local)." >&2
|
|
fi
|
|
sync_local_control_ui_origins "$CONFIG_JSON" "$HOST_GATEWAY_PORT"
|
|
|
|
PODMAN_USERNS="${OPENCLAW_PODMAN_USERNS:-keep-id}"
|
|
USERNS_ARGS=()
|
|
RUN_USER_ARGS=()
|
|
case "$PODMAN_USERNS" in
|
|
""|auto) ;;
|
|
keep-id) USERNS_ARGS=(--userns=keep-id) ;;
|
|
host) USERNS_ARGS=(--userns=host) ;;
|
|
*)
|
|
echo "Unsupported OPENCLAW_PODMAN_USERNS=$PODMAN_USERNS (expected: keep-id, auto, host)." >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
RUN_UID="$(id -u)"
|
|
RUN_GID="$(id -g)"
|
|
if [[ "$PODMAN_USERNS" == "keep-id" ]]; then
|
|
RUN_USER_ARGS=(--user "${RUN_UID}:${RUN_GID}")
|
|
echo "Starting container as uid=${RUN_UID} gid=${RUN_GID} (must match owner of $CONFIG_DIR)" >&2
|
|
else
|
|
echo "Starting container without --user (OPENCLAW_PODMAN_USERNS=$PODMAN_USERNS), mounts may require ownership fixes." >&2
|
|
fi
|
|
|
|
SELINUX_MOUNT_OPTS=""
|
|
if [[ -z "${OPENCLAW_BIND_MOUNT_OPTIONS:-}" ]]; then
|
|
if [[ "$(uname -s 2>/dev/null)" == "Linux" ]] && command -v getenforce >/dev/null 2>&1; then
|
|
_selinux_mode="$(getenforce 2>/dev/null || true)"
|
|
if [[ "$_selinux_mode" == "Enforcing" || "$_selinux_mode" == "Permissive" ]]; then
|
|
SELINUX_MOUNT_OPTS=",Z"
|
|
fi
|
|
fi
|
|
else
|
|
SELINUX_MOUNT_OPTS="${OPENCLAW_BIND_MOUNT_OPTIONS#:}"
|
|
[[ -n "$SELINUX_MOUNT_OPTS" ]] && SELINUX_MOUNT_OPTS=",$SELINUX_MOUNT_OPTS"
|
|
fi
|
|
|
|
if [[ "$RUN_SETUP" == true ]]; then
|
|
TOKEN_ENV_FILE="$(create_token_env_file "$ENV_FILE" "$OPENCLAW_GATEWAY_TOKEN")"
|
|
podman run --pull="$PODMAN_PULL" --rm -it \
|
|
--init \
|
|
"${USERNS_ARGS[@]}" "${RUN_USER_ARGS[@]}" \
|
|
-e HOME=/home/node -e TERM=xterm-256color -e BROWSER=echo \
|
|
-e NPM_CONFIG_CACHE=/home/node/.openclaw/.npm \
|
|
-e OPENCLAW_NO_RESPAWN=1 \
|
|
--env-file "$TOKEN_ENV_FILE" \
|
|
-v "$CONFIG_DIR:/home/node/.openclaw:rw${SELINUX_MOUNT_OPTS}" \
|
|
-v "$WORKSPACE_DIR:/home/node/.openclaw/workspace:rw${SELINUX_MOUNT_OPTS}" \
|
|
"$OPENCLAW_IMAGE" \
|
|
node dist/index.js onboard "$@"
|
|
exit 0
|
|
fi
|
|
|
|
TOKEN_ENV_FILE="$(create_token_env_file "$ENV_FILE" "$OPENCLAW_GATEWAY_TOKEN")"
|
|
podman run --pull="$PODMAN_PULL" -d --replace \
|
|
--name "$CONTAINER_NAME" \
|
|
--init \
|
|
"${USERNS_ARGS[@]}" "${RUN_USER_ARGS[@]}" \
|
|
-e HOME=/home/node -e TERM=xterm-256color \
|
|
-e NPM_CONFIG_CACHE=/home/node/.openclaw/.npm \
|
|
-e OPENCLAW_NO_RESPAWN=1 \
|
|
--env-file "$TOKEN_ENV_FILE" \
|
|
-v "$CONFIG_DIR:/home/node/.openclaw:rw${SELINUX_MOUNT_OPTS}" \
|
|
-v "$WORKSPACE_DIR:/home/node/.openclaw/workspace:rw${SELINUX_MOUNT_OPTS}" \
|
|
-p "${PUBLISH_HOST}:${HOST_GATEWAY_PORT}:18789" \
|
|
-p "${PUBLISH_HOST}:${HOST_BRIDGE_PORT}:18790" \
|
|
"$OPENCLAW_IMAGE" \
|
|
node dist/index.js gateway --bind "$GATEWAY_BIND" --port 18789
|
|
|
|
echo "Container $CONTAINER_NAME started. Dashboard: http://127.0.0.1:${HOST_GATEWAY_PORT}/"
|
|
echo "Host CLI: openclaw --container $CONTAINER_NAME dashboard --no-open"
|
|
echo "Logs: podman logs -f $CONTAINER_NAME"
|
|
if [[ "$PLATFORM_NAME" == "Darwin" ]]; then
|
|
echo "macOS Podman note: if Control UI login hits device-auth errors, prefer the SSH-tunnel or Tailscale paths in docs/install/podman.md."
|
|
echo "Local-safe workaround:"
|
|
echo " OPENCLAW_CONTAINER=$CONTAINER_NAME openclaw dashboard --no-open"
|
|
echo " One-time setup:"
|
|
echo " OPENCLAW_CONTAINER=$CONTAINER_NAME openclaw config set gateway.controlUi.allowedOrigins '[\"http://127.0.0.1:18789\",\"http://localhost:18789\",\"http://127.0.0.1:28889\",\"http://localhost:28889\"]' --strict-json"
|
|
echo " podman restart $CONTAINER_NAME"
|
|
echo " ssh -N -i ~/.local/share/containers/podman/machine/machine -p <podman-vm-ssh-port> -L 28889:127.0.0.1:18789 core@127.0.0.1"
|
|
echo " Then open http://127.0.0.1:28889/"
|
|
echo " Note: find <podman-vm-ssh-port> with: podman system connection list"
|
|
fi
|
|
if [[ "$PLATFORM_NAME" == "Linux" ]]; then
|
|
echo "For auto-start/restarts, use: ./scripts/podman/setup.sh --quadlet (Quadlet + systemd user service)."
|
|
fi
|