248 lines
5.3 KiB
Bash
Executable File
248 lines
5.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
XWORKMATE_CODEX_ACP_DEFAULT_HOST="${XWORKMATE_CODEX_ACP_DEFAULT_HOST:-127.0.0.1}"
|
|
XWORKMATE_CODEX_ACP_DEFAULT_PORT="${XWORKMATE_CODEX_ACP_DEFAULT_PORT:-9001}"
|
|
XWORKMATE_CODEX_ACP_PORT_SCAN_LIMIT="${XWORKMATE_CODEX_ACP_PORT_SCAN_LIMIT:-100}"
|
|
XWORKMATE_CODEX_ACP_DRY_RUN="${XWORKMATE_CODEX_ACP_DRY_RUN:-0}"
|
|
|
|
common_die() {
|
|
echo "Error: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
common_info() {
|
|
echo "$*"
|
|
}
|
|
|
|
common_warn() {
|
|
echo "Warning: $*" >&2
|
|
}
|
|
|
|
common_print_cmd() {
|
|
printf '+'
|
|
printf ' %q' "$@"
|
|
printf '\n'
|
|
}
|
|
|
|
common_run_service_cmd() {
|
|
if [[ "$XWORKMATE_CODEX_ACP_DRY_RUN" == "1" ]]; then
|
|
common_print_cmd "$@"
|
|
return 0
|
|
fi
|
|
"$@"
|
|
}
|
|
|
|
common_run_service_cmd_allow_fail() {
|
|
if [[ "$XWORKMATE_CODEX_ACP_DRY_RUN" == "1" ]]; then
|
|
common_print_cmd "$@"
|
|
return 0
|
|
fi
|
|
"$@" || true
|
|
}
|
|
|
|
common_validate_port() {
|
|
local port="$1"
|
|
if [[ ! "$port" =~ ^[0-9]+$ ]]; then
|
|
common_die "Invalid port: $port"
|
|
fi
|
|
if (( port < 1 || port > 65535 )); then
|
|
common_die "Port out of range: $port"
|
|
fi
|
|
}
|
|
|
|
common_resolve_codex_bin() {
|
|
local candidate="${CODEX_BIN:-${XWORKMATE_CODEX_ACP_CODEX_BIN:-}}"
|
|
if [[ -z "$candidate" ]]; then
|
|
candidate="$(command -v codex || true)"
|
|
fi
|
|
if [[ -z "$candidate" ]]; then
|
|
common_die "Unable to find codex in PATH. Set CODEX_BIN=/absolute/path/to/codex."
|
|
fi
|
|
if [[ ! -x "$candidate" ]]; then
|
|
common_die "codex binary is not executable: $candidate"
|
|
fi
|
|
printf '%s\n' "$candidate"
|
|
}
|
|
|
|
common_listen_url() {
|
|
local port="$1"
|
|
printf 'ws://%s:%s\n' "$XWORKMATE_CODEX_ACP_DEFAULT_HOST" "$port"
|
|
}
|
|
|
|
common_load_config() {
|
|
if [[ -f "$CONFIG_FILE" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "$CONFIG_FILE"
|
|
fi
|
|
}
|
|
|
|
common_ensure_dirs() {
|
|
mkdir -p "$CONFIG_DIR" "$RUNTIME_DIR"
|
|
if [[ -n "${LOG_DIR:-}" ]]; then
|
|
mkdir -p "$LOG_DIR"
|
|
fi
|
|
}
|
|
|
|
common_write_config() {
|
|
local codex_bin="$1"
|
|
local port="$2"
|
|
local listen_url
|
|
listen_url="$(common_listen_url "$port")"
|
|
|
|
common_ensure_dirs
|
|
|
|
{
|
|
printf 'CODEX_BIN=%q\n' "$codex_bin"
|
|
printf 'CODEX_ACP_HOST=%q\n' "$XWORKMATE_CODEX_ACP_DEFAULT_HOST"
|
|
printf 'CODEX_ACP_PORT=%q\n' "$port"
|
|
printf 'CODEX_ACP_LISTEN_URL=%q\n' "$listen_url"
|
|
} > "$CONFIG_FILE"
|
|
}
|
|
|
|
common_write_launcher() {
|
|
local codex_bin="$1"
|
|
local listen_url="$2"
|
|
|
|
common_ensure_dirs
|
|
|
|
{
|
|
printf '#!/usr/bin/env bash\n'
|
|
printf 'set -euo pipefail\n'
|
|
printf 'exec '
|
|
printf '%q ' "$codex_bin" "app-server" "--listen" "$listen_url"
|
|
printf '\n'
|
|
} > "$LAUNCHER_FILE"
|
|
|
|
chmod +x "$LAUNCHER_FILE"
|
|
}
|
|
|
|
common_port_is_in_use() {
|
|
local port="$1"
|
|
|
|
if command -v lsof >/dev/null 2>&1; then
|
|
lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1
|
|
return $?
|
|
fi
|
|
|
|
if command -v ss >/dev/null 2>&1; then
|
|
ss -H -ltn 2>/dev/null | awk -v needle=":$port" '
|
|
$4 ~ needle "$" { found = 1 }
|
|
END { exit found ? 0 : 1 }
|
|
'
|
|
return $?
|
|
fi
|
|
|
|
if command -v netstat >/dev/null 2>&1; then
|
|
netstat -an 2>/dev/null | awk -v needle=":$port" '
|
|
$1 ~ /^tcp/ && $4 ~ needle "$" && $NF ~ /LISTEN|LISTENING/ { found = 1 }
|
|
END { exit found ? 0 : 1 }
|
|
'
|
|
return $?
|
|
fi
|
|
|
|
if command -v nc >/dev/null 2>&1; then
|
|
nc -z "$XWORKMATE_CODEX_ACP_DEFAULT_HOST" "$port" >/dev/null 2>&1
|
|
return $?
|
|
fi
|
|
|
|
common_die "Unable to detect port availability. Install one of lsof, ss, netstat, or nc."
|
|
}
|
|
|
|
common_find_available_port() {
|
|
local preferred="$1"
|
|
local port
|
|
local max_port=$((preferred + XWORKMATE_CODEX_ACP_PORT_SCAN_LIMIT))
|
|
|
|
if (( max_port > 65535 )); then
|
|
max_port=65535
|
|
fi
|
|
|
|
for ((port = preferred; port <= max_port; port++)); do
|
|
if ! common_port_is_in_use "$port"; then
|
|
printf '%s\n' "$port"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
common_die "No free port found in range ${preferred}-${max_port}."
|
|
}
|
|
|
|
common_select_port() {
|
|
local preferred="$1"
|
|
local current_port="${2:-}"
|
|
local preserve_current_busy="${3:-0}"
|
|
|
|
common_validate_port "$preferred"
|
|
|
|
if [[ "$preserve_current_busy" == "1" && -n "$current_port" && "$preferred" == "$current_port" ]]; then
|
|
printf '%s\n' "$preferred"
|
|
return 0
|
|
fi
|
|
|
|
if common_port_is_in_use "$preferred"; then
|
|
common_find_available_port "$preferred"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s\n' "$preferred"
|
|
}
|
|
|
|
common_endpoint_from_config() {
|
|
common_load_config
|
|
local port="${CODEX_ACP_PORT:-$XWORKMATE_CODEX_ACP_DEFAULT_PORT}"
|
|
common_validate_port "$port"
|
|
common_listen_url "$port"
|
|
}
|
|
|
|
common_cleanup_dir_if_empty() {
|
|
local dir="$1"
|
|
if [[ -d "$dir" ]]; then
|
|
rmdir "$dir" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
common_parse_args() {
|
|
ACTION="${1:-help}"
|
|
shift || true
|
|
|
|
PORT_OVERRIDE=""
|
|
LOG_LINES="50"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--port)
|
|
shift
|
|
[[ $# -gt 0 ]] || common_die "--port requires a value"
|
|
PORT_OVERRIDE="$1"
|
|
shift
|
|
;;
|
|
--lines)
|
|
shift
|
|
[[ $# -gt 0 ]] || common_die "--lines requires a value"
|
|
LOG_LINES="$1"
|
|
shift
|
|
;;
|
|
-h|--help|help)
|
|
ACTION="help"
|
|
shift
|
|
;;
|
|
*)
|
|
if [[ "$ACTION" == "set-port" && -z "$PORT_OVERRIDE" ]]; then
|
|
PORT_OVERRIDE="$1"
|
|
shift
|
|
continue
|
|
fi
|
|
common_die "Unknown argument: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -n "$PORT_OVERRIDE" ]]; then
|
|
common_validate_port "$PORT_OVERRIDE"
|
|
fi
|
|
if [[ ! "$LOG_LINES" =~ ^[0-9]+$ ]]; then
|
|
common_die "Invalid --lines value: $LOG_LINES"
|
|
fi
|
|
}
|