#!/usr/bin/env sh set -eu INSTALL_DIR="${PLATFORM_AGENT_DIR:-/opt/platform-agent}" SERVICE_NAME="${PLATFORM_AGENT_SERVICE:-platform-agent}" CONFIG_PATH="${AGENT_CONFIG:-${INSTALL_DIR}/agent.config.json}" SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" log() { printf '%s\n' "$*" } need_cmd() { command -v "$1" >/dev/null 2>&1 } as_root() { if [ "$(id -u)" -eq 0 ]; then "$@" elif need_cmd sudo; then sudo "$@" else log "Root privileges are required: $*" exit 1 fi } prompt_value() { var_name="$1" label="$2" current_value="$3" secret="${4:-false}" if [ "$secret" = "true" ]; then printf '%s [hidden, Enter keeps current]: ' "$label" stty -echo 2>/dev/null || true IFS= read -r answer || answer="" stty echo 2>/dev/null || true printf '\n' else printf '%s [%s]: ' "$label" "$current_value" IFS= read -r answer || answer="" fi if [ -n "$answer" ]; then eval "$var_name=\"\$answer\"" fi } prompt_yes_no() { label="$1" default="${2:-no}" suffix="[y/N]" [ "$default" = "yes" ] && suffix="[Y/n]" printf '%s %s ' "$label" "$suffix" IFS= read -r answer || answer="" answer="$(printf '%s' "$answer" | tr '[:upper:]' '[:lower:]')" if [ -z "$answer" ]; then [ "$default" = "yes" ] return fi [ "$answer" = "y" ] || [ "$answer" = "yes" ] } json_get() { key_path="$1" fallback="${2:-}" python3 - "$CONFIG_PATH" "$key_path" "$fallback" <<'PY' import json import sys path, key_path, fallback = sys.argv[1], sys.argv[2], sys.argv[3] try: with open(path, "r", encoding="utf-8") as fp: value = json.load(fp) except Exception: print(fallback) raise SystemExit for key in key_path.split("."): if not isinstance(value, dict): value = None break value = value.get(key) if value is None: print(fallback) elif isinstance(value, bool): print("true" if value else "false") else: print(value) PY } service_user() { if [ -f "$SERVICE_FILE" ]; then user_line="$(sed -n 's/^User=//p' "$SERVICE_FILE" | head -n 1)" [ -n "$user_line" ] && { printf '%s' "$user_line" return } fi id -un } expand_path_for_user() { path="$1" run_user="$2" case "$path" in "~") if [ "$run_user" = "root" ]; then printf '/root'; else printf '/home/%s' "$run_user"; fi ;; "~/"*) if [ "$run_user" = "root" ]; then printf '/root/%s' "${path#~/}"; else printf '/home/%s/%s' "$run_user" "${path#~/}"; fi ;; *) printf '%s' "$path" ;; esac } write_config() { tmp_file="$(mktemp)" WORKSPACE_ROOT="$WORKSPACE_ROOT" \ ROBOT_STACK="$ROBOT_STACK" \ ROS_DOMAIN_ID="$ROS_DOMAIN_ID" \ CAMERA_ENABLED="$CAMERA_ENABLED" \ CAMERA_VIDEO_DEVICE="$CAMERA_VIDEO_DEVICE" \ CAMERA_SIGNALING_URL="$CAMERA_SIGNALING_URL" \ KASMVNC_GEOMETRY="$KASMVNC_GEOMETRY" \ AGENT_GATEWAY_TOKEN="$AGENT_GATEWAY_TOKEN" \ python3 - "$CONFIG_PATH" "$tmp_file" <<'PY' import json import os import sys source, target = sys.argv[1], sys.argv[2] with open(source, "r", encoding="utf-8") as fp: cfg = json.load(fp) cfg.setdefault("workspace", {}) cfg.setdefault("ros", {}) cfg.setdefault("camera", {}) cfg.setdefault("desktop", {}) cfg.setdefault("security", {}) cfg["workspace"]["root"] = os.environ["WORKSPACE_ROOT"] cfg["ros"]["stack"] = "nav2" if os.environ["ROBOT_STACK"].lower() == "nav2" else "autoware" cfg["ros"]["domainId"] = os.environ["ROS_DOMAIN_ID"] if os.environ["ROS_DOMAIN_ID"].isdigit() else "0" cfg["camera"]["enabled"] = os.environ["CAMERA_ENABLED"].lower() in ("true", "1", "yes", "y", "on") cfg["camera"]["videoDevice"] = os.environ["CAMERA_VIDEO_DEVICE"] cfg["camera"]["signalingUrl"] = os.environ["CAMERA_SIGNALING_URL"] cfg["desktop"]["geometry"] = os.environ["KASMVNC_GEOMETRY"] if os.environ["AGENT_GATEWAY_TOKEN"]: cfg["security"]["gatewayToken"] = os.environ["AGENT_GATEWAY_TOKEN"] with open(target, "w", encoding="utf-8") as fp: json.dump(cfg, fp, ensure_ascii=False, indent=2) fp.write("\n") PY as_root cp "$tmp_file" "$CONFIG_PATH" rm -f "$tmp_file" } restart_service() { if need_cmd systemctl; then as_root systemctl restart "$SERVICE_NAME" elif need_cmd service; then as_root service "$SERVICE_NAME" restart else log "No service manager found. Restart the agent manually." fi } if ! need_cmd python3; then log "python3 is required to edit ${CONFIG_PATH}." exit 1 fi if [ ! -f "$CONFIG_PATH" ]; then log "Config not found: ${CONFIG_PATH}" log "Set AGENT_CONFIG=/path/to/agent.config.json or install the agent first." exit 1 fi RUN_USER="$(service_user)" WORKSPACE_ROOT="$(json_get workspace.root '~/autonomy-studio/workspaces')" ROBOT_STACK="$(json_get ros.stack autoware)" ROS_DOMAIN_ID="$(json_get ros.domainId 0)" CAMERA_ENABLED="$(json_get camera.enabled false)" CAMERA_VIDEO_DEVICE="$(json_get camera.videoDevice /dev/video0)" CAMERA_SIGNALING_URL="$(json_get camera.signalingUrl ws://127.0.0.1:3000)" KASMVNC_GEOMETRY="$(json_get desktop.geometry 1600x900)" AGENT_GATEWAY_TOKEN="$(json_get security.gatewayToken '')" while :; do clear log "Autonomy Studio Platform Agent Settings" log "=======================================" log "Config : ${CONFIG_PATH}" log "Service : ${SERVICE_NAME}" log "" log "1. Workspace root ${WORKSPACE_ROOT}" log "2. ROS stack ${ROBOT_STACK}" log "3. ROS_DOMAIN_ID ${ROS_DOMAIN_ID}" log "4. Camera enabled ${CAMERA_ENABLED}" log "5. Camera device ${CAMERA_VIDEO_DEVICE}" log "6. Camera signaling ${CAMERA_SIGNALING_URL}" log "7. KasmVNC resolution ${KASMVNC_GEOMETRY}" log "8. Gateway token $(if [ -n "$AGENT_GATEWAY_TOKEN" ]; then printf 'configured'; else printf 'empty'; fi)" log "" log "s. Save and restart" log "q. Quit" log "" printf 'Select: ' IFS= read -r choice || choice="" case "$choice" in 1) prompt_value WORKSPACE_ROOT "Workspace root" "$WORKSPACE_ROOT" ;; 2) prompt_value ROBOT_STACK "ROS stack (autoware/nav2)" "$ROBOT_STACK" case "$ROBOT_STACK" in nav2|NAV2) ROBOT_STACK="nav2" ;; *) ROBOT_STACK="autoware" ;; esac ;; 3) prompt_value ROS_DOMAIN_ID "ROS_DOMAIN_ID" "$ROS_DOMAIN_ID" ROS_DOMAIN_ID="$(printf '%s' "$ROS_DOMAIN_ID" | tr -cd '0-9')" [ -n "$ROS_DOMAIN_ID" ] || ROS_DOMAIN_ID="0" ;; 4) prompt_value CAMERA_ENABLED "Camera enabled true/false" "$CAMERA_ENABLED" ;; 5) prompt_value CAMERA_VIDEO_DEVICE "Camera device" "$CAMERA_VIDEO_DEVICE" ;; 6) prompt_value CAMERA_SIGNALING_URL "Camera signaling URL" "$CAMERA_SIGNALING_URL" ;; 7) prompt_value KASMVNC_GEOMETRY "KasmVNC resolution" "$KASMVNC_GEOMETRY" ;; 8) prompt_value AGENT_GATEWAY_TOKEN "Gateway token" "$AGENT_GATEWAY_TOKEN" true ;; s|S) write_config workspace_path="$(expand_path_for_user "$WORKSPACE_ROOT" "$RUN_USER")" if prompt_yes_no "Create workspace directory ${workspace_path} if missing?" "yes"; then as_root mkdir -p "$workspace_path" as_root chown -R "$RUN_USER":"$RUN_USER" "$workspace_path" || true fi restart_service log "" log "Settings saved and service restarted." exit 0 ;; q|Q) log "Canceled." exit 0 ;; esac done