feat(scripts): add codex acp native service helpers
This commit is contained in:
parent
7bdba33bea
commit
24c038d732
247
scripts/codex-acp-service-common.sh
Executable file
247
scripts/codex-acp-service-common.sh
Executable file
@ -0,0 +1,247 @@
|
||||
#!/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
|
||||
}
|
||||
238
scripts/codex-acp-service-linux.sh
Executable file
238
scripts/codex-acp-service-linux.sh
Executable file
@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=./codex-acp-service-common.sh
|
||||
source "$SCRIPT_DIR/codex-acp-service-common.sh"
|
||||
|
||||
SERVICE_NAME="${XWORKMATE_CODEX_ACP_SERVICE_NAME:-xworkmate-codex-acp.service}"
|
||||
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
|
||||
SYSTEMD_USER_DIR="${XWORKMATE_CODEX_ACP_SYSTEMD_USER_DIR:-$XDG_CONFIG_HOME/systemd/user}"
|
||||
CONFIG_DIR="${XWORKMATE_CODEX_ACP_CONFIG_DIR:-$XDG_CONFIG_HOME/xworkmate/codex-acp}"
|
||||
RUNTIME_DIR="${XWORKMATE_CODEX_ACP_RUNTIME_DIR:-$XDG_STATE_HOME/xworkmate/codex-acp}"
|
||||
LOG_DIR=""
|
||||
CONFIG_FILE="$CONFIG_DIR/config.env"
|
||||
LAUNCHER_FILE="$RUNTIME_DIR/run-codex-acp.sh"
|
||||
SERVICE_FILE="$SYSTEMD_USER_DIR/$SERVICE_NAME"
|
||||
|
||||
linux_has_systemctl() {
|
||||
command -v systemctl >/dev/null 2>&1
|
||||
}
|
||||
|
||||
linux_require_systemctl() {
|
||||
if [[ "$XWORKMATE_CODEX_ACP_DRY_RUN" == "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
linux_has_systemctl || common_die "systemctl is required for Linux native service control."
|
||||
}
|
||||
|
||||
linux_usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") <command> [--port PORT] [--lines N]
|
||||
|
||||
Commands:
|
||||
install Write user unit + enable/start Codex ACP service
|
||||
start Start service using configured or auto-selected port
|
||||
stop Stop the user service
|
||||
restart Restart service, re-picking a free port if needed
|
||||
status Show service state and endpoint
|
||||
endpoint Print configured websocket endpoint
|
||||
logs Show recent journal lines
|
||||
set-port PORT Reconfigure the service port, then restart if active
|
||||
uninstall Stop service and remove systemd user unit + generated files
|
||||
help Show this message
|
||||
|
||||
Defaults:
|
||||
host $XWORKMATE_CODEX_ACP_DEFAULT_HOST
|
||||
default port $XWORKMATE_CODEX_ACP_DEFAULT_PORT
|
||||
|
||||
Environment:
|
||||
CODEX_BIN=/absolute/path/to/codex
|
||||
XWORKMATE_CODEX_ACP_DRY_RUN=1 Generate files without calling systemctl
|
||||
EOF
|
||||
}
|
||||
|
||||
linux_is_active() {
|
||||
if ! linux_has_systemctl; then
|
||||
return 1
|
||||
fi
|
||||
systemctl --user is-active --quiet "$SERVICE_NAME"
|
||||
}
|
||||
|
||||
linux_state() {
|
||||
if ! linux_has_systemctl; then
|
||||
printf 'unavailable (systemctl missing)\n'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if systemctl --user is-enabled "$SERVICE_NAME" >/dev/null 2>&1; then
|
||||
if linux_is_active; then
|
||||
printf 'active\n'
|
||||
return 0
|
||||
fi
|
||||
printf 'enabled\n'
|
||||
return 0
|
||||
fi
|
||||
if linux_is_active; then
|
||||
printf 'active\n'
|
||||
return 0
|
||||
fi
|
||||
printf 'inactive\n'
|
||||
}
|
||||
|
||||
linux_write_service_file() {
|
||||
common_ensure_dirs
|
||||
mkdir -p "$SYSTEMD_USER_DIR"
|
||||
|
||||
cat > "$SERVICE_FILE" <<EOF
|
||||
[Unit]
|
||||
Description=XWorkmate Codex ACP app-server
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=$LAUNCHER_FILE
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
WorkingDirectory=$HOME
|
||||
NoNewPrivileges=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
EOF
|
||||
}
|
||||
|
||||
linux_prepare_service() {
|
||||
common_load_config
|
||||
|
||||
local configured_port="${CODEX_ACP_PORT:-$XWORKMATE_CODEX_ACP_DEFAULT_PORT}"
|
||||
local requested_port="${PORT_OVERRIDE:-$configured_port}"
|
||||
local preserve_busy="0"
|
||||
if linux_is_active && [[ "$requested_port" == "$configured_port" ]]; then
|
||||
preserve_busy="1"
|
||||
fi
|
||||
|
||||
local codex_bin="${CODEX_BIN:-}"
|
||||
if [[ -z "$codex_bin" || ! -x "$codex_bin" ]]; then
|
||||
codex_bin="$(common_resolve_codex_bin)"
|
||||
fi
|
||||
|
||||
local selected_port
|
||||
selected_port="$(common_select_port "$requested_port" "$configured_port" "$preserve_busy")"
|
||||
if [[ "$selected_port" != "$requested_port" ]]; then
|
||||
common_warn "Port $requested_port is busy; using $selected_port instead."
|
||||
fi
|
||||
|
||||
local listen_url
|
||||
listen_url="$(common_listen_url "$selected_port")"
|
||||
|
||||
common_write_config "$codex_bin" "$selected_port"
|
||||
common_write_launcher "$codex_bin" "$listen_url"
|
||||
linux_write_service_file
|
||||
}
|
||||
|
||||
linux_reload_units() {
|
||||
linux_require_systemctl
|
||||
common_run_service_cmd systemctl --user daemon-reload
|
||||
}
|
||||
|
||||
linux_activate_service() {
|
||||
linux_reload_units
|
||||
common_run_service_cmd systemctl --user enable --now "$SERVICE_NAME"
|
||||
}
|
||||
|
||||
linux_stop_service() {
|
||||
linux_require_systemctl
|
||||
if ! linux_is_active; then
|
||||
common_info "Service is not active."
|
||||
return 0
|
||||
fi
|
||||
common_run_service_cmd systemctl --user stop "$SERVICE_NAME"
|
||||
}
|
||||
|
||||
linux_status() {
|
||||
common_info "platform: Linux"
|
||||
common_info "service: $SERVICE_NAME"
|
||||
common_info "state: $(linux_state)"
|
||||
common_info "endpoint: $(common_endpoint_from_config)"
|
||||
common_info "unit: $SERVICE_FILE"
|
||||
common_info "config: $CONFIG_FILE"
|
||||
}
|
||||
|
||||
linux_logs() {
|
||||
linux_require_systemctl
|
||||
common_run_service_cmd journalctl --user -u "$SERVICE_NAME" -n "$LOG_LINES" --no-pager
|
||||
}
|
||||
|
||||
linux_uninstall() {
|
||||
linux_require_systemctl
|
||||
common_run_service_cmd_allow_fail systemctl --user disable --now "$SERVICE_NAME"
|
||||
rm -f "$SERVICE_FILE" "$LAUNCHER_FILE" "$CONFIG_FILE"
|
||||
linux_reload_units
|
||||
common_cleanup_dir_if_empty "$RUNTIME_DIR"
|
||||
common_cleanup_dir_if_empty "$CONFIG_DIR"
|
||||
common_info "Removed user unit: $SERVICE_NAME"
|
||||
}
|
||||
|
||||
common_parse_args "$@"
|
||||
|
||||
case "$ACTION" in
|
||||
install)
|
||||
linux_prepare_service
|
||||
linux_activate_service
|
||||
common_info "Codex ACP service ready at $(common_endpoint_from_config)"
|
||||
;;
|
||||
start)
|
||||
if linux_is_active && [[ -z "$PORT_OVERRIDE" ]]; then
|
||||
common_info "Service already active at $(common_endpoint_from_config)"
|
||||
exit 0
|
||||
fi
|
||||
linux_prepare_service
|
||||
linux_activate_service
|
||||
common_info "Codex ACP service started at $(common_endpoint_from_config)"
|
||||
;;
|
||||
stop)
|
||||
linux_stop_service
|
||||
;;
|
||||
restart)
|
||||
linux_prepare_service
|
||||
linux_reload_units
|
||||
if linux_is_active; then
|
||||
common_run_service_cmd systemctl --user restart "$SERVICE_NAME"
|
||||
else
|
||||
common_run_service_cmd systemctl --user start "$SERVICE_NAME"
|
||||
fi
|
||||
common_info "Codex ACP service restarted at $(common_endpoint_from_config)"
|
||||
;;
|
||||
status)
|
||||
linux_status
|
||||
;;
|
||||
endpoint)
|
||||
common_endpoint_from_config
|
||||
;;
|
||||
logs)
|
||||
linux_logs
|
||||
;;
|
||||
set-port)
|
||||
[[ -n "$PORT_OVERRIDE" ]] || common_die "set-port requires a port value"
|
||||
linux_prepare_service
|
||||
linux_reload_units
|
||||
if linux_is_active; then
|
||||
common_run_service_cmd systemctl --user restart "$SERVICE_NAME"
|
||||
common_info "Codex ACP service moved to $(common_endpoint_from_config)"
|
||||
else
|
||||
common_info "Configured Codex ACP service for $(common_endpoint_from_config)"
|
||||
fi
|
||||
;;
|
||||
uninstall)
|
||||
linux_uninstall
|
||||
;;
|
||||
help)
|
||||
linux_usage
|
||||
;;
|
||||
*)
|
||||
common_die "Unknown command: $ACTION"
|
||||
;;
|
||||
esac
|
||||
233
scripts/codex-acp-service-macos.sh
Executable file
233
scripts/codex-acp-service-macos.sh
Executable file
@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=./codex-acp-service-common.sh
|
||||
source "$SCRIPT_DIR/codex-acp-service-common.sh"
|
||||
|
||||
macos_launchctl_domain() {
|
||||
if launchctl print "gui/$UID" >/dev/null 2>&1; then
|
||||
printf 'gui/%s\n' "$UID"
|
||||
return 0
|
||||
fi
|
||||
printf 'user/%s\n' "$UID"
|
||||
}
|
||||
|
||||
SERVICE_LABEL="${XWORKMATE_CODEX_ACP_SERVICE_LABEL:-plus.svc.xworkmate.codex-acp}"
|
||||
CONFIG_DIR="${XWORKMATE_CODEX_ACP_CONFIG_DIR:-$HOME/Library/Application Support/XWorkmate/codex-acp}"
|
||||
RUNTIME_DIR="${XWORKMATE_CODEX_ACP_RUNTIME_DIR:-$CONFIG_DIR/runtime}"
|
||||
LOG_DIR="${XWORKMATE_CODEX_ACP_LOG_DIR:-$HOME/Library/Logs/XWorkmate/codex-acp}"
|
||||
CONFIG_FILE="$CONFIG_DIR/config.env"
|
||||
LAUNCHER_FILE="$RUNTIME_DIR/run-codex-acp.sh"
|
||||
PLIST_DIR="${XWORKMATE_CODEX_ACP_LAUNCHAGENTS_DIR:-$HOME/Library/LaunchAgents}"
|
||||
SERVICE_FILE="$PLIST_DIR/$SERVICE_LABEL.plist"
|
||||
LAUNCHCTL_DOMAIN="${XWORKMATE_CODEX_ACP_LAUNCHCTL_DOMAIN:-$(macos_launchctl_domain)}"
|
||||
|
||||
macos_usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") <command> [--port PORT] [--lines N]
|
||||
|
||||
Commands:
|
||||
install Write LaunchAgent + start Codex ACP service
|
||||
start Start service using configured or auto-selected port
|
||||
stop Stop the LaunchAgent
|
||||
restart Restart service, re-picking a free port if needed
|
||||
status Show service state and endpoint
|
||||
endpoint Print configured websocket endpoint
|
||||
logs Tail saved stdout/stderr logs
|
||||
set-port PORT Reconfigure the service port, then restart if loaded
|
||||
uninstall Stop service and remove LaunchAgent + generated files
|
||||
help Show this message
|
||||
|
||||
Defaults:
|
||||
host $XWORKMATE_CODEX_ACP_DEFAULT_HOST
|
||||
default port $XWORKMATE_CODEX_ACP_DEFAULT_PORT
|
||||
launchctl $LAUNCHCTL_DOMAIN
|
||||
|
||||
Environment:
|
||||
CODEX_BIN=/absolute/path/to/codex
|
||||
XWORKMATE_CODEX_ACP_DRY_RUN=1 Generate files without calling launchctl
|
||||
EOF
|
||||
}
|
||||
|
||||
macos_is_loaded() {
|
||||
launchctl print "$LAUNCHCTL_DOMAIN/$SERVICE_LABEL" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
macos_state() {
|
||||
if ! macos_is_loaded; then
|
||||
printf 'unloaded\n'
|
||||
return 0
|
||||
fi
|
||||
|
||||
local state
|
||||
state="$(launchctl print "$LAUNCHCTL_DOMAIN/$SERVICE_LABEL" 2>/dev/null | awk -F'= ' '/state =/ { print $2; exit }')"
|
||||
if [[ -n "$state" ]]; then
|
||||
printf '%s\n' "$state"
|
||||
return 0
|
||||
fi
|
||||
printf 'loaded\n'
|
||||
}
|
||||
|
||||
macos_write_service_file() {
|
||||
common_ensure_dirs
|
||||
mkdir -p "$PLIST_DIR"
|
||||
|
||||
cat > "$SERVICE_FILE" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>$SERVICE_LABEL</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>$LAUNCHER_FILE</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>$HOME</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>$LOG_DIR/stdout.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>$LOG_DIR/stderr.log</string>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>$PATH</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
}
|
||||
|
||||
macos_prepare_service() {
|
||||
common_load_config
|
||||
|
||||
local configured_port="${CODEX_ACP_PORT:-$XWORKMATE_CODEX_ACP_DEFAULT_PORT}"
|
||||
local requested_port="${PORT_OVERRIDE:-$configured_port}"
|
||||
local preserve_busy="0"
|
||||
if macos_is_loaded && [[ "$requested_port" == "$configured_port" ]]; then
|
||||
preserve_busy="1"
|
||||
fi
|
||||
|
||||
local codex_bin="${CODEX_BIN:-}"
|
||||
if [[ -z "$codex_bin" || ! -x "$codex_bin" ]]; then
|
||||
codex_bin="$(common_resolve_codex_bin)"
|
||||
fi
|
||||
|
||||
local selected_port
|
||||
selected_port="$(common_select_port "$requested_port" "$configured_port" "$preserve_busy")"
|
||||
if [[ "$selected_port" != "$requested_port" ]]; then
|
||||
common_warn "Port $requested_port is busy; using $selected_port instead."
|
||||
fi
|
||||
|
||||
local listen_url
|
||||
listen_url="$(common_listen_url "$selected_port")"
|
||||
|
||||
common_write_config "$codex_bin" "$selected_port"
|
||||
common_write_launcher "$codex_bin" "$listen_url"
|
||||
macos_write_service_file
|
||||
}
|
||||
|
||||
macos_activate_service() {
|
||||
common_run_service_cmd_allow_fail launchctl bootout "$LAUNCHCTL_DOMAIN" "$SERVICE_FILE"
|
||||
common_run_service_cmd launchctl bootstrap "$LAUNCHCTL_DOMAIN" "$SERVICE_FILE"
|
||||
common_run_service_cmd launchctl kickstart -k "$LAUNCHCTL_DOMAIN/$SERVICE_LABEL"
|
||||
}
|
||||
|
||||
macos_stop_service() {
|
||||
if ! macos_is_loaded; then
|
||||
common_info "Service is not loaded."
|
||||
return 0
|
||||
fi
|
||||
common_run_service_cmd launchctl bootout "$LAUNCHCTL_DOMAIN" "$SERVICE_FILE"
|
||||
}
|
||||
|
||||
macos_status() {
|
||||
common_info "platform: macOS"
|
||||
common_info "label: $SERVICE_LABEL"
|
||||
common_info "state: $(macos_state)"
|
||||
common_info "endpoint: $(common_endpoint_from_config)"
|
||||
common_info "launch agent: $SERVICE_FILE"
|
||||
common_info "config: $CONFIG_FILE"
|
||||
common_info "logs:"
|
||||
common_info " $LOG_DIR/stdout.log"
|
||||
common_info " $LOG_DIR/stderr.log"
|
||||
}
|
||||
|
||||
macos_logs() {
|
||||
mkdir -p "$LOG_DIR"
|
||||
touch "$LOG_DIR/stdout.log" "$LOG_DIR/stderr.log"
|
||||
common_info "==> $LOG_DIR/stdout.log <=="
|
||||
tail -n "$LOG_LINES" "$LOG_DIR/stdout.log"
|
||||
common_info
|
||||
common_info "==> $LOG_DIR/stderr.log <=="
|
||||
tail -n "$LOG_LINES" "$LOG_DIR/stderr.log"
|
||||
}
|
||||
|
||||
macos_uninstall() {
|
||||
macos_stop_service
|
||||
rm -f "$SERVICE_FILE" "$LAUNCHER_FILE" "$CONFIG_FILE"
|
||||
common_cleanup_dir_if_empty "$RUNTIME_DIR"
|
||||
common_cleanup_dir_if_empty "$CONFIG_DIR"
|
||||
common_info "Removed LaunchAgent: $SERVICE_LABEL"
|
||||
}
|
||||
|
||||
common_parse_args "$@"
|
||||
|
||||
case "$ACTION" in
|
||||
install)
|
||||
macos_prepare_service
|
||||
macos_activate_service
|
||||
common_info "Codex ACP service ready at $(common_endpoint_from_config)"
|
||||
;;
|
||||
start)
|
||||
if macos_is_loaded && [[ -z "$PORT_OVERRIDE" ]]; then
|
||||
common_info "Service already loaded at $(common_endpoint_from_config)"
|
||||
exit 0
|
||||
fi
|
||||
macos_prepare_service
|
||||
macos_activate_service
|
||||
common_info "Codex ACP service started at $(common_endpoint_from_config)"
|
||||
;;
|
||||
stop)
|
||||
macos_stop_service
|
||||
;;
|
||||
restart)
|
||||
macos_prepare_service
|
||||
macos_activate_service
|
||||
common_info "Codex ACP service restarted at $(common_endpoint_from_config)"
|
||||
;;
|
||||
status)
|
||||
macos_status
|
||||
;;
|
||||
endpoint)
|
||||
common_endpoint_from_config
|
||||
;;
|
||||
logs)
|
||||
macos_logs
|
||||
;;
|
||||
set-port)
|
||||
[[ -n "$PORT_OVERRIDE" ]] || common_die "set-port requires a port value"
|
||||
macos_prepare_service
|
||||
if macos_is_loaded; then
|
||||
macos_activate_service
|
||||
common_info "Codex ACP service moved to $(common_endpoint_from_config)"
|
||||
else
|
||||
common_info "Configured Codex ACP service for $(common_endpoint_from_config)"
|
||||
fi
|
||||
;;
|
||||
uninstall)
|
||||
macos_uninstall
|
||||
;;
|
||||
help)
|
||||
macos_usage
|
||||
;;
|
||||
*)
|
||||
common_die "Unknown command: $ACTION"
|
||||
;;
|
||||
esac
|
||||
17
scripts/codex-acp-service.sh
Executable file
17
scripts/codex-acp-service.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
exec "$SCRIPT_DIR/codex-acp-service-macos.sh" "$@"
|
||||
;;
|
||||
Linux)
|
||||
exec "$SCRIPT_DIR/codex-acp-service-linux.sh" "$@"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported platform: $(uname -s)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Loading…
Reference in New Issue
Block a user