#!/usr/bin/env bash set -euo pipefail server_url="${OTO_BOOTSTRAP_SERVER_URL:-}" socket_url="${OTO_BOOTSTRAP_SOCKET_URL:-}" agent_id="${OTO_AGENT_ID:-}" enrollment_token="" release_base_url="${OTO_BOOTSTRAP_RELEASE_BASE_URL:-}" agent_alias="${OTO_AGENT_ALIAS:-}" install_dir="${HOME}/.oto/bin" config_path="${HOME}/.oto/agent/config.yaml" workspace_root="${HOME}/.oto/workspace" log_dir="${HOME}/.oto/agent/log" background="true" usage() { echo "Usage: $0 --token [options]" echo "Options:" echo " --server-url OTO Core HTTP URL (default: served by bootstrap endpoint or OTO_BOOTSTRAP_SERVER_URL)" echo " --socket-url Runner proto-socket URL (default: served by bootstrap endpoint or OTO_BOOTSTRAP_SOCKET_URL)" echo " --release-base-url OTO release asset base URL (default: served by bootstrap endpoint or OTO_BOOTSTRAP_RELEASE_BASE_URL)" echo " --agent-id Agent ID override (default: derived from this device)" echo " --agent-alias Alias override (default: device name)" echo " --install-dir Directory to install OTO agent (default: \$HOME/.oto/bin)" echo " --config-path Path to config file (default: \$HOME/.oto/agent/config.yaml)" echo " --workspace-root Workspace root directory (default: \$HOME/.oto/workspace)" echo " --log-dir Log directory (default: \$HOME/.oto/agent/log)" echo " --no-background Run in the foreground" } yaml_escape() { local value="$1" value="${value//\\/\\\\}" value="${value//\"/\\\"}" value="${value//$'\t'/\\t}" value="${value//$'\r'/\\r}" value="${value//$'\n'/\\n}" printf '%s' "$value" } hash_value() { local value="$1" if command -v sha256sum >/dev/null 2>&1; then printf '%s' "$value" | sha256sum | awk '{print $1}' elif command -v shasum >/dev/null 2>&1; then printf '%s' "$value" | shasum -a 256 | awk '{print $1}' else printf '%s' "$value" | cksum | awk '{print $1}' fi } device_identity_source() { local source="" if [[ "${os_name:-}" = "macos" ]] && command -v ioreg >/dev/null 2>&1; then source="$(ioreg -rd1 -c IOPlatformExpertDevice 2>/dev/null | awk -F'"' '/IOPlatformUUID/ { print $(NF-1); exit }' || true)" fi if [[ -z "$source" && "${os_name:-}" = "linux" ]]; then if [[ -r /etc/machine-id ]]; then source="$(tr -d '[:space:]' < /etc/machine-id)" elif [[ -r /var/lib/dbus/machine-id ]]; then source="$(tr -d '[:space:]' < /var/lib/dbus/machine-id)" fi fi if [[ -z "$source" ]]; then source="$(hostname 2>/dev/null || true)" fi printf '%s' "$source" } default_agent_id() { local source hash source="$(device_identity_source)" hash="$(hash_value "oto:${os_name}:${source}")" printf 'oto-%s' "${hash:0:32}" } default_agent_alias() { local alias="" if [[ "${os_name:-}" = "macos" ]] && command -v scutil >/dev/null 2>&1; then alias="$(scutil --get ComputerName 2>/dev/null || true)" fi if [[ -z "$alias" ]]; then alias="$(hostname -s 2>/dev/null || hostname 2>/dev/null || true)" fi if [[ -z "$alias" ]]; then alias="oto-runner" fi printf '%s' "$alias" } if [[ -z "$server_url" ]]; then server_url='__OTO_BOOTSTRAP_DEFAULT_SERVER_URL__' fi if [[ -z "$socket_url" ]]; then socket_url='__OTO_BOOTSTRAP_DEFAULT_SOCKET_URL__' fi if [[ -z "$release_base_url" ]]; then release_base_url='__OTO_BOOTSTRAP_DEFAULT_RELEASE_BASE_URL__' fi if [[ "$server_url" = "__OTO_BOOTSTRAP_DEFAULT_SERVER_URL__" ]]; then server_url="" fi if [[ "$socket_url" = "__OTO_BOOTSTRAP_DEFAULT_SOCKET_URL__" ]]; then socket_url="" fi if [[ "$release_base_url" = "__OTO_BOOTSTRAP_DEFAULT_RELEASE_BASE_URL__" ]]; then release_base_url="" fi while [[ $# -gt 0 ]]; do case "$1" in --server-url|--edge-url) server_url="$2" shift 2 ;; --socket-url) socket_url="$2" shift 2 ;; --agent-id) agent_id="$2" shift 2 ;; --token) enrollment_token="$2" shift 2 ;; --release-base-url) release_base_url="$2" shift 2 ;; --agent-alias) agent_alias="$2" shift 2 ;; --install-dir) install_dir="$2" shift 2 ;; --config-path) config_path="$2" shift 2 ;; --workspace-root) workspace_root="$2" shift 2 ;; --log-dir) log_dir="$2" shift 2 ;; --no-background) background="false" shift 1 ;; *) echo "Unknown option: $1" >&2 usage exit 1 ;; esac done if [[ -z "$enrollment_token" ]]; then echo "Error: Missing required argument: --token." >&2 usage exit 1 fi if [[ -z "$server_url" ]]; then echo "Error: Missing OTO Core URL. Use --server-url or fetch this script from the OTO Core bootstrap endpoint." >&2 usage exit 1 fi if [[ -z "$release_base_url" ]]; then echo "Error: Missing release base URL. Use --release-base-url or fetch this script from the OTO Core bootstrap endpoint." >&2 usage exit 1 fi case "$release_base_url" in https://*) ;; *) echo "Error: --release-base-url must use https://" >&2 exit 1 ;; esac os=$(uname -s) case "$os" in Linux) os_name="linux" ;; Darwin) os_name="macos" ;; *) echo "Error: Unsupported OS: $os" >&2 exit 1 ;; esac arch=$(uname -m) case "$arch" in x86_64|amd64) arch_name="x64" ;; aarch64|arm64) arch_name="arm64" ;; *) echo "Error: Unsupported architecture: $arch" >&2 exit 1 ;; esac asset_name="oto-${os_name}-${arch_name}.tar.gz" if [[ -z "$agent_id" ]]; then agent_id="$(default_agent_id)" fi if [[ -z "$agent_alias" ]]; then agent_alias="$(default_agent_alias)" fi if [[ -z "$agent_id" ]]; then echo "Error: Unable to derive agent id from this device. Use --agent-id to override." >&2 exit 1 fi download_url="${release_base_url}/${asset_name}" tmp_dir=$(mktemp -d) trap 'rm -rf "$tmp_dir"' EXIT echo "Downloading OTO agent..." curl -fsSL "$download_url" -o "$tmp_dir/$asset_name" echo "Extracting OTO agent..." tar -xzf "$tmp_dir/$asset_name" -C "$tmp_dir" mkdir -p "$install_dir" cp "$tmp_dir/oto" "$install_dir/oto" chmod +x "$install_dir/oto" if [[ "$os_name" = "macos" ]]; then xattr -dr com.apple.quarantine "$install_dir/oto" 2>/dev/null || true if command -v codesign >/dev/null 2>&1; then codesign --force --sign - "$install_dir/oto" >/dev/null 2>&1 || true fi fi echo "Verifying OTO binary..." if ! "$install_dir/oto" --version > /dev/null 2>&1; then echo "Error: OTO binary verification failed." >&2 exit 1 fi config_dir=$(dirname "$config_path") agent_state_dir="${HOME}/.oto/agent" pid_path="${agent_state_dir}/oto-agent.pid" mkdir -p "$config_dir" mkdir -p "$agent_state_dir" mkdir -p "$workspace_root" mkdir -p "$log_dir" cat < "$config_path" agent: id: "$(yaml_escape "$agent_id")" alias: "$(yaml_escape "$agent_alias")" enrollment_token: "$(yaml_escape "$enrollment_token")" server: url: "$(yaml_escape "$server_url")" EOF if [[ -n "$socket_url" ]]; then printf ' socket_url: "%s"\n' "$(yaml_escape "$socket_url")" >> "$config_path" fi cat <> "$config_path" runtime: install_dir: "$(yaml_escape "$install_dir")" workspace_root: "$(yaml_escape "$workspace_root")" log_dir: "$(yaml_escape "$log_dir")" EOF chmod 600 "$config_path" if [ "$background" = "true" ]; then echo "Starting OTO agent in the background..." nohup "$install_dir/oto" agent run --config "$config_path" >> "$log_dir/oto-agent.log" 2>&1 & pid=$! echo "$pid" > "$pid_path" echo "OTO agent started in the background. PID: $pid" echo "Log path: $log_dir/oto-agent.log" else echo "OTO agent is configured to run in the foreground." echo "To run it manually, use the following command:" echo " $install_dir/oto agent run --config $config_path" fi