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
10 changes: 9 additions & 1 deletion docs/deployment/gateway-lifecycle-authority.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,15 @@ Choose the mode that matches the component responsible for process lifecycle ope
| `externally-supervised` | A declared platform supervisor | Validates and attaches to the running gateway without managing its process. |

When nothing is declared, NemoClaw keeps its existing managed behavior.
It uses the packaged gateway user service when that service is installed and otherwise manages a standalone gateway.
On Linux, the installer selects a packaged gateway user service only after the systemd user manager reports the effective unit and executable identity.
The installer verifies that identity and the gateway version before NemoClaw uses the service.
It does not parse a static package unit as the effective service identity.

If the systemd user manager reports a recognized unavailable condition, the installer can keep the existing standalone gateway lifecycle.
This fallback requires no `.wants`, `.requires`, or `.upholds` activation link for either gateway service in the standard systemd user unit paths.
The installer stops when `SYSTEMD_UNIT_PATH` overrides those paths because it cannot verify the activation state.
An activation path stops installation until you restore the user manager or resolve the competing service through its owner.
Other service query errors, malformed metadata, untrusted paths, executable failures, and version mismatches stop installation.

## Declare the mode

Expand Down
23 changes: 14 additions & 9 deletions docs/reference/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,27 @@ Before adoption, NemoClaw asks systemd for the unit's effective `ExecStart`.
When systemd returns the service identity, NemoClaw requires trusted package paths for both the unit and its effective binary.
It then checks the effective binary's version against the blueprint range (`min_openshell_version` through `max_openshell_version`).
This check prevents a supported user-local OpenShell install from hiding an out-of-range binary selected by the package unit's absolute `ExecStart`.
NemoClaw declines a known version below the minimum or a known stable version above the maximum.
An above-maximum development build remains eligible when `NEMOCLAW_OPENSHELL_CHANNEL=dev`, and an undetermined version also remains eligible.
When the systemd identity query fails, the package unit also remains eligible for later startup inspection.
NemoClaw declines the package unit when systemd positively identifies an untrusted unit or effective binary.
For a version rejection, NemoClaw reports the version once.
After either rejection, it selects an existing marked NemoClaw user service when available.
Otherwise, onboarding continues with the standalone gateway fallback.
NemoClaw rejects a known version below the minimum or a known stable version above the maximum.
An above-maximum development build remains eligible when `NEMOCLAW_OPENSHELL_CHANNEL=dev`.
An undetermined version or an unsupported version stops package-unit adoption.
When the systemd identity query reports a recognized unavailable user manager, NemoClaw checks the standard user unit paths before standalone fallback.
It checks `.wants`, `.requires`, and `.upholds` activation links for both gateway service names.
An activation path blocks fallback because its service can later claim port `8080`.
An active `SYSTEMD_UNIT_PATH` override also blocks fallback because NemoClaw cannot verify its activation state.
Other identity-query failures stop package-unit adoption.
NemoClaw stops when systemd identifies an untrusted unit or effective binary.
For a version rejection, NemoClaw reports the version once and blocks another gateway lifecycle.
Identity and path-trust failures also block another gateway lifecycle.
After `systemctl --user daemon-reload` and systemd identity validation, startup checks the effective binary's version again before any service-state mutation.
If a known version is now unsupported, startup does not stop, enable, or restart the package service.
Onboarding continues with the standalone fallback.
Onboarding stops instead of starting a standalone gateway.
For tarball installs, the NemoClaw installer stages a marked user-level `nemoclaw-openshell-gateway.service` based on the upstream unit.
Onboarding validates and reuses a healthy selected service.
It enables or restarts the service when startup or verified runtime drift requires it, then checks gateway health.
The marked service generates the local OpenShell mTLS bundle and reads `$XDG_CONFIG_HOME/openshell/gateway.env`, or `~/.config/openshell/gateway.env` when `XDG_CONFIG_HOME` is not absolute.
NemoClaw preserves unrelated environment entries, accepts `DOCKER_HOST` only for an absolute local `unix://` socket, and refuses foreign or symlinked managed files.
If the selected systemd user service fails inspection, startup, or its health check, NemoClaw prints its `journalctl` log command and attempts the standalone fallback.
For ordinary startup or health failures, NemoClaw prints the `journalctl` log command and attempts the standalone fallback.
Unknown inspection, trust, and version failures stop onboarding.

On Apple Silicon macOS, Homebrew makes the official OpenShell formula authoritative.
The installer stages the formula and onboarding starts its `openshell` service.
Expand Down
33 changes: 33 additions & 0 deletions docs/reference/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,39 @@ curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash

The next installer run must continue past the OpenShell installation step without reporting a version mismatch.

### Installer Reports That the Systemd User Manager Is Unavailable

On Linux, an OpenShell package can install `/usr/lib/systemd/user/openshell-gateway.service` on a host without a reachable systemd user manager.
The service query can then return this diagnostic:

```text
Failed to connect to bus: No medium found
```

The installer accepts only recognized user-manager-unavailable diagnostics for the standalone gateway fallback.
It checks `.wants`, `.requires`, and `.upholds` links in the standard systemd user unit paths.
The installer keeps the standalone lifecycle only when neither gateway service has an activation path that can later claim port `8080`.
The installer also stops when `SYSTEMD_UNIT_PATH` overrides the standard paths.
The installer does not parse, modify, or remove a package or foreign unit to make this decision.

If an activation path exists, the installer stops because the service can start later and compete for port `8080`.
Restore the systemd user manager, then inspect both possible services:

```bash
systemctl --user status openshell-gateway.service
systemctl --user is-enabled openshell-gateway.service
systemctl --user status nemoclaw-openshell-gateway.service
systemctl --user is-enabled nemoclaw-openshell-gateway.service
```

Resolve the competing service through its package or platform owner.
Do not delete an activation link or edit a unit file by hand.
Rerun the installer only after the owner confirms that no enabled user service can claim port `8080`.

Unknown service query errors remain fatal.
The installer also stops for malformed effective metadata, an untrusted unit or executable path, an executable failure, or a gateway version mismatch.
Follow the reported condition instead of forcing the standalone fallback.

<AgentOnly variant="openclaw">

### Sandbox build fails during OpenClaw plugin install
Expand Down
239 changes: 231 additions & 8 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1320,6 +1320,8 @@ prefer_user_local_openshell() {
NEMOCLAW_GATEWAY_SERVICE_MARKER="NEMOCLAW_MANAGED_OPENSHELL_GATEWAY=1"
NEMOCLAW_GATEWAY_SERVICE_MARKER_LINE="# ${NEMOCLAW_GATEWAY_SERVICE_MARKER}"
NEMOCLAW_GATEWAY_SERVICE_NAME="nemoclaw-openshell-gateway"
UPSTREAM_OPENSHELL_GATEWAY_SERVICE_BIN=""
UPSTREAM_OPENSHELL_GATEWAY_SERVICE_ERROR=""

upstream_openshell_gateway_user_service_installed() {
[[ "$(uname -s)" == "Linux" ]] || return 1
Expand All @@ -1342,17 +1344,135 @@ resolve_openshell_gateway_bin_for_user_service() {
done < <(
printf '%s\n' "$exec_start" \
| grep -oE 'path=[^ ;}]+' \
| sed 's/^path=//' \
| sort -u
| sed 's/^path=//'
)
[[ "${#gateway_bins[@]}" -eq 1 ]] || return 1
gateway_bin="${gateway_bins[0]}"
[[ "$gateway_bin" == /*/openshell-gateway && -x "$gateway_bin" ]] || return 1
printf '%s\n' "$gateway_bin"
}

systemd_user_manager_unavailable_diagnostic() {
local diagnostic="${1:-}" line recognized=0
while IFS= read -r line; do
line="${line%$'\r'}"
case "$line" in
"") ;;
"Failed to connect to bus: No medium found" | \
"Failed to connect to bus: Host is down" | \
"Failed to connect to bus: No such file or directory" | \
"System has not been booted with systemd as init system (PID 1). Can't operate." | \
"XDG_RUNTIME_DIR is not set in the environment." | \
"Failed to connect to bus: \$DBUS_SESSION_BUS_ADDRESS and \$XDG_RUNTIME_DIR not defined (consider using --machine=<user>@.host --user to connect to bus of other user)")
recognized=1
;;
*) return 1 ;;
esac
done <<<"$diagnostic"
[[ "$recognized" -eq 1 ]]
}

trusted_upstream_openshell_gateway_unit_for_service() {
case "${1:-}" in
/usr/local/lib/systemd/user/openshell-gateway.service | \
/usr/lib/systemd/user/openshell-gateway.service | \
/lib/systemd/user/openshell-gateway.service)
return 0
;;
*)
return 1
;;
esac
}

trusted_upstream_openshell_gateway_bin_for_service() {
case "${1:-}" in
/usr/local/bin/openshell-gateway | /usr/bin/openshell-gateway)
return 0
;;
*)
return 1
;;
esac
}

inspect_upstream_openshell_gateway_user_service() {
local service_output service_status line fragment_path="" exec_start="" gateway_bin
local fragment_count=0 exec_start_count=0
local -a gateway_bins=()
UPSTREAM_OPENSHELL_GATEWAY_SERVICE_BIN=""
UPSTREAM_OPENSHELL_GATEWAY_SERVICE_ERROR=""

if service_output="$(LC_ALL=C systemctl --user show openshell-gateway.service \
--property=FragmentPath --property=ExecStart 2>&1)"; then
:
else
service_status=$?
UPSTREAM_OPENSHELL_GATEWAY_SERVICE_ERROR="systemctl --user show openshell-gateway.service failed: ${service_output:-exit ${service_status}}"
if systemd_user_manager_unavailable_diagnostic "$service_output"; then
return 2
fi
return 1
fi

while IFS= read -r line; do
line="${line%$'\r'}"
case "$line" in
FragmentPath=*)
fragment_path="${line#FragmentPath=}"
fragment_count=$((fragment_count + 1))
;;
ExecStart=*)
exec_start="${line#ExecStart=}"
exec_start_count=$((exec_start_count + 1))
;;
"") ;;
*)
UPSTREAM_OPENSHELL_GATEWAY_SERVICE_ERROR="The effective upstream OpenShell gateway service returned unexpected metadata."
return 1
;;
esac
done <<<"$service_output"

if [[ "$fragment_count" -ne 1 || "$exec_start_count" -ne 1 ]]; then
UPSTREAM_OPENSHELL_GATEWAY_SERVICE_ERROR="The effective upstream OpenShell gateway service did not return one FragmentPath and one ExecStart value."
return 1
fi
if ! trusted_upstream_openshell_gateway_unit_for_service "$fragment_path"; then
UPSTREAM_OPENSHELL_GATEWAY_SERVICE_ERROR="The effective upstream OpenShell gateway unit path is not trusted: ${fragment_path:-<empty>}"
return 1
fi

while IFS= read -r gateway_bin; do
gateway_bins+=("$gateway_bin")
done < <(
printf '%s\n' "$exec_start" \
| grep -oE 'path=[^ ;}]+' \
| sed 's/^path=//'
)
if [[ "${#gateway_bins[@]}" -ne 1 ]]; then
UPSTREAM_OPENSHELL_GATEWAY_SERVICE_ERROR="The effective upstream OpenShell gateway service did not return one executable path."
return 1
fi
gateway_bin="${gateway_bins[0]}"
if ! trusted_upstream_openshell_gateway_bin_for_service "$gateway_bin"; then
UPSTREAM_OPENSHELL_GATEWAY_SERVICE_ERROR="The effective upstream OpenShell gateway executable path is not trusted: $gateway_bin"
return 1
fi
if [[ ! -x "$gateway_bin" ]]; then
UPSTREAM_OPENSHELL_GATEWAY_SERVICE_ERROR="The effective upstream OpenShell gateway executable is unavailable: $gateway_bin"
return 1
fi

UPSTREAM_OPENSHELL_GATEWAY_SERVICE_BIN="$gateway_bin"
}

resolve_upstream_openshell_gateway_bin_for_service() {
resolve_openshell_gateway_bin_for_user_service openshell-gateway.service
if inspect_upstream_openshell_gateway_user_service; then
printf '%s\n' "$UPSTREAM_OPENSHELL_GATEWAY_SERVICE_BIN"
else
return $?
fi
}

openshell_binary_version() {
Expand All @@ -1363,14 +1483,21 @@ openshell_binary_version() {
}

require_compatible_upstream_openshell_gateway_service() {
local nemoclaw_gateway_bin upstream_gateway_bin nemoclaw_version upstream_version
local nemoclaw_gateway_bin upstream_gateway_bin nemoclaw_version upstream_version inspect_status
if inspect_upstream_openshell_gateway_user_service; then
upstream_gateway_bin="$UPSTREAM_OPENSHELL_GATEWAY_SERVICE_BIN"
else
inspect_status=$?
if [[ "$inspect_status" -eq 2 ]]; then
return 2
fi
error "Could not inspect the effective upstream OpenShell gateway user service. ${UPSTREAM_OPENSHELL_GATEWAY_SERVICE_ERROR} Repair that OpenShell installation, then rerun the installer."
fi
nemoclaw_gateway_bin="$(resolve_openshell_gateway_bin_for_service)" \
|| error "Could not locate the NemoClaw OpenShell gateway binary before checking the existing upstream service."
if ! trusted_openshell_gateway_bin_for_service "$nemoclaw_gateway_bin"; then
error "OpenShell gateway user service binary path is not a trusted install path: $nemoclaw_gateway_bin"
fi
upstream_gateway_bin="$(resolve_upstream_openshell_gateway_bin_for_service)" \
|| error "Could not locate the gateway binary used by the existing upstream OpenShell user service. Remove or repair that OpenShell installation, then rerun the installer."
nemoclaw_version="$(openshell_binary_version "$nemoclaw_gateway_bin")" \
|| error "Could not determine the NemoClaw OpenShell gateway version at $nemoclaw_gateway_bin."
upstream_version="$(openshell_binary_version "$upstream_gateway_bin")" \
Expand Down Expand Up @@ -1431,6 +1558,85 @@ openshell_user_config_home() {
fi
}

enabled_openshell_gateway_user_service_activation_path() {
local user_config_home user_data_home runtime_dir unit_root activation_dir service_name activation_path
local config_dirs data_dirs directory
local -a unit_roots=()
if [[ -n "${SYSTEMD_UNIT_PATH:-}" ]]; then
printf 'SYSTEMD_UNIT_PATH=%q\n' "$SYSTEMD_UNIT_PATH"
return 2
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
user_config_home="$(openshell_user_config_home)"
user_data_home="${XDG_DATA_HOME:-${HOME}/.local/share}"
if [[ "$user_data_home" != /* ]]; then
printf '%s\n' "$user_data_home"
return 2
fi
unit_roots+=(
"${user_config_home}/systemd/user"
"${user_config_home}/systemd/user.control"
"${user_data_home%/}/systemd/user"
"/etc/systemd/user"
"/run/systemd/user"
"/usr/local/lib/systemd/user"
"/usr/lib/systemd/user"
"/lib/systemd/user"
)
config_dirs="${XDG_CONFIG_DIRS:-/etc/xdg}"
data_dirs="${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
local IFS=:
for directory in $config_dirs $data_dirs; do
[[ -n "$directory" ]] || continue
if [[ "$directory" != /* ]]; then
printf '%s\n' "$directory"
return 2
fi
unit_roots+=("${directory%/}/systemd/user")
done
runtime_dir="${XDG_RUNTIME_DIR:-}"
if [[ "$runtime_dir" != /* && "${UID:-}" =~ ^[0-9]+$ ]]; then
runtime_dir="/run/user/${UID}"
fi
if [[ "$runtime_dir" == /* ]]; then
unit_roots+=(
"${runtime_dir%/}/systemd/user.control"
"${runtime_dir%/}/systemd/transient"
"${runtime_dir%/}/systemd/generator.early"
"${runtime_dir%/}/systemd/user"
"${runtime_dir%/}/systemd/generator"
"${runtime_dir%/}/systemd/generator.late"
)
fi

for unit_root in "${unit_roots[@]}"; do
if [[ -e "$unit_root" || -L "$unit_root" ]]; then
if [[ ! -d "$unit_root" || ! -r "$unit_root" || ! -x "$unit_root" ]]; then
printf '%s\n' "$unit_root"
return 2
fi
fi
for activation_dir in "$unit_root"/*.wants "$unit_root"/*.requires "$unit_root"/*.upholds; do
if [[ -L "$activation_dir" && ! -d "$activation_dir" ]]; then
printf '%s\n' "$activation_dir"
return 2
fi
[[ -d "$activation_dir" ]] || continue
if [[ ! -r "$activation_dir" || ! -x "$activation_dir" ]]; then
printf '%s\n' "$activation_dir"
return 2
fi
for service_name in openshell-gateway "${NEMOCLAW_GATEWAY_SERVICE_NAME}"; do
activation_path="${activation_dir}/${service_name}.service"
if [[ -e "$activation_path" || -L "$activation_path" ]]; then
printf '%s\n' "$activation_path"
return 0
fi
done
done
done
return 1
}

install_nemoclaw_openshell_gateway_user_service() {
[[ "$(uname -s)" == "Linux" ]] || return 0
[[ "$(resolve_nemoclaw_gateway_port)" -eq 8080 ]] || return 0
Expand All @@ -1448,8 +1654,25 @@ install_nemoclaw_openshell_gateway_user_service() {
if [[ -f "$service_path" ]] && ! is_nemoclaw_openshell_gateway_user_service "$service_path"; then
error "Refusing to replace non-NemoClaw OpenShell gateway user service: $service_path"
fi
require_compatible_upstream_openshell_gateway_service
info "OpenShell upstream gateway user service is staged; onboarding will select and start it."
local compatibility_status activation_path activation_status
if require_compatible_upstream_openshell_gateway_service; then
info "OpenShell upstream gateway user service is staged; onboarding will select and start it."
return 0
else
compatibility_status=$?
fi
if [[ "$compatibility_status" -ne 2 ]]; then
error "Could not determine whether the effective upstream OpenShell gateway user service is compatible."
fi
if activation_path="$(enabled_openshell_gateway_user_service_activation_path)"; then
error "The systemd user manager is unavailable, but $activation_path can activate a gateway user service that can later claim port 8080. Restore the systemd user manager and inspect or disable that service before rerunning NemoClaw. The installer did not change the unit or activation path."
else
activation_status=$?
if [[ "$activation_status" -eq 2 ]]; then
error "The systemd user manager is unavailable, and the installer could not inspect OpenShell gateway activation configuration at $activation_path. Restore the default unit search path or access to that location, then rerun NemoClaw."
fi
fi
warn "The systemd user manager is unavailable. No enabled OpenShell gateway user service activation path was found, so onboarding will keep the existing standalone gateway on port 8080."
return 0
fi

Expand Down
Loading
Loading