#!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ACTIVE_PROJECT_FILE="${ROOT}/projects/active-project.json" usage() { cat <<'EOF' Usage: ./scripts/smart-ide-ssh-tunnel-plan.sh [--project ] [--env ] [--mode ] [--json] Purpose: Print an SSH tunnel command to reach the Smart IDE services running on a remote host. Project/env resolution (first match): - --project / --env - SMART_IDE_PROJECT_ID / SMART_IDE_ENV - projects/active-project.json (local, gitignored) The SSH target is read from: projects//conf.json -> smart_ide.remote_data_access.environments..ssh_host_alias Notes: - This prints a command; it does not daemonize anything. - Bind is limited to 127.0.0.1 on the client. EOF } PROJECT_ID="${SMART_IDE_PROJECT_ID:-}" ENV_NAME="${SMART_IDE_ENV:-}" MODE="minimal" AS_JSON="false" while [[ $# -gt 0 ]]; do case "$1" in -h|--help) usage exit 0 ;; --project) PROJECT_ID="${2:-}" shift 2 ;; --env) ENV_NAME="${2:-}" shift 2 ;; --mode) MODE="${2:-}" shift 2 ;; --json) AS_JSON="true" shift 1 ;; *) echo "Unknown argument: $1" >&2 usage >&2 exit 2 ;; esac done if ! command -v jq >/dev/null 2>&1; then echo "Missing dependency: jq" >&2 exit 1 fi if [[ -z "${PROJECT_ID}" ]] && [[ -f "${ACTIVE_PROJECT_FILE}" ]]; then PROJECT_ID="$(jq -r '.id // empty' "${ACTIVE_PROJECT_FILE}")" fi if [[ -z "${ENV_NAME}" ]] && [[ -f "${ACTIVE_PROJECT_FILE}" ]]; then ENV_NAME="$(jq -r '.default_env // empty' "${ACTIVE_PROJECT_FILE}")" fi if [[ -z "${PROJECT_ID}" ]]; then echo "Missing project id. Provide --project or create projects/active-project.json" >&2 exit 1 fi if [[ -z "${ENV_NAME}" ]]; then ENV_NAME="test" fi case "${ENV_NAME}" in test|pprod|prod) ;; *) echo "Invalid --env: ${ENV_NAME} (expected test|pprod|prod)" >&2 exit 1 ;; esac case "${MODE}" in minimal|all) ;; *) echo "Invalid --mode: ${MODE} (expected minimal|all)" >&2 exit 1 ;; esac CONF_FILE="${ROOT}/projects/${PROJECT_ID}/conf.json" if [[ ! -f "${CONF_FILE}" ]]; then echo "Missing project conf: ${CONF_FILE}" >&2 exit 1 fi SSH_HOST_ALIAS="$( jq -r ".smart_ide.remote_data_access.environments.${ENV_NAME}.ssh_host_alias // empty" "${CONF_FILE}" )" if [[ -z "${SSH_HOST_ALIAS}" ]] || [[ "${SSH_HOST_ALIAS}" == "null" ]]; then echo "Missing ssh_host_alias for ${PROJECT_ID}/${ENV_NAME} in ${CONF_FILE}" >&2 exit 1 fi declare -a forwards=() add_forward() { local port="$1" forwards+=("-L" "127.0.0.1:${port}:127.0.0.1:${port}") } # Minimal: enough for Lapce to talk to orchestrator + agents + tools jobs via tunnel. add_forward 37145 # smart-ide-orchestrator add_forward 37144 # ia-dev-gateway add_forward 37147 # smart-ide-tools-bridge if [[ "${MODE}" == "all" ]]; then add_forward 37149 # smart-ide-global-api (internal; mostly for debugging) add_forward 37148 # smart-ide-sso-gateway (OIDC front) add_forward 37146 # anythingllm-devtools add_forward 37140 # repos-devtools-server add_forward 37143 # agent-regex-search-api add_forward 37141 # langextract-api add_forward 37142 # claw-harness-proxy add_forward 8000 # local-office (API) add_forward 11434 # ollama (host service) add_forward 3001 # anythingllm (host service, default docker) fi declare -a argv=( ssh -N -o ExitOnForwardFailure=yes -o BatchMode=yes -o ServerAliveInterval=10 -o ServerAliveCountMax=6 "${forwards[@]}" "${SSH_HOST_ALIAS}" ) hint="Run in a dedicated terminal; stop with Ctrl+C. Ensure the local ports are free." if [[ "${AS_JSON}" == "true" ]]; then jq -n \ --arg projectId "${PROJECT_ID}" \ --arg env "${ENV_NAME}" \ --arg sshHostAlias "${SSH_HOST_ALIAS}" \ --arg mode "${MODE}" \ --arg hint "${hint}" \ --argjson argv "$(printf '%s\n' "${argv[@]}" | jq -R . | jq -s .)" \ '{ projectId: $projectId, env: $env, sshHostAlias: $sshHostAlias, mode: $mode, argv: $argv, hint: $hint }' exit 0 fi echo "projectId=${PROJECT_ID}" echo "env=${ENV_NAME}" echo "sshHostAlias=${SSH_HOST_ALIAS}" echo "mode=${MODE}" echo printf '%q ' "${argv[@]}" echo echo echo "${hint}"