NewOpenAgents/install-service.sh
cclohmar bbe6b042a7 feat: standalone install-service.sh; install.sh delegates the service step
- new install-service.sh: creates/removes the OpenCode web UI systemd service as its own process (interactive by default, flags for automation, --uninstall, --dry-run, backs up an existing unit)
- install.sh no longer embeds the service logic; run_service_setup locates the sibling script (or downloads it) and invokes it as a separate process
- README documents running install-service.sh on its own
2026-10-13 07:38:17 +00:00

254 lines
8.6 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
#
# OpenAgents Control — Architect Fork · web UI service installer
# ============================================================================
# Creates (or removes) a systemd service that runs the OpenCode web UI as a
# background process:
#
# opencode serve --hostname <host> --port <port>
#
# Run it on its own, or let install.sh call it after OpenCode is installed.
#
# Usage:
# ./install-service.sh # interactive
# ./install-service.sh --port 4096 # interactive, prefilled port
# ./install-service.sh --host 0.0.0.0 --port 8080
# ./install-service.sh --yes # non-interactive defaults
# ./install-service.sh --uninstall # stop and remove the service
# ./install-service.sh --dry-run # show the unit, change nothing
#
# Environment:
# OPENCODE_SERVICE_NAME unit name (default: opencode.service)
# OAC_FORCE_SYSTEMD 1/0 to force systemd detection (containers/CI)
# ============================================================================
set -euo pipefail
SERVICE_NAME="${OPENCODE_SERVICE_NAME:-opencode.service}"
DEFAULT_HOST="127.0.0.1"
DEFAULT_PORT="4096"
OPENCODE_BIN_DIR="${HOME}/.opencode/bin"
# ---------------------------------------------------------------------------
# Output helpers
# ---------------------------------------------------------------------------
if [ -t 1 ]; then
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; CYAN='\033[0;36m'; MAGENTA='\033[0;35m'
BOLD='\033[1m'; NC='\033[0m'
else
RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; MAGENTA=''; BOLD=''; NC=''
fi
info() { printf "${BLUE}ℹ${NC} %s\n" "$1"; }
success() { printf "${GREEN}✓${NC} %s\n" "$1"; }
warn() { printf "${YELLOW}⚠${NC} %s\n" "$1"; }
error() { printf "${RED}✗${NC} %s\n" "$1" >&2; }
step() { printf "\n${MAGENTA}${BOLD}▶${NC} %s\n\n" "$1"; }
die() { error "$1"; exit 1; }
# Prompt on the controlling terminal — works even under `curl … | bash`.
have_tty() {
[ -t 1 ] || return 1
[ -t 0 ] && return 0
[ -r /dev/tty ]
}
tty_read() {
local __var="$1" __val=""
if [ -t 0 ] || [ ! -r /dev/tty ]; then
read -r __val || true
else
read -r __val </dev/tty || true
fi
printf -v "$__var" '%s' "$__val"
}
have_opencode() {
command -v opencode >/dev/null 2>&1 && return 0
[ -x "${OPENCODE_BIN_DIR}/opencode" ] && return 0
return 1
}
opencode_path() {
if command -v opencode >/dev/null 2>&1; then command -v opencode
else printf '%s' "${OPENCODE_BIN_DIR}/opencode"; fi
}
is_systemd() {
if [ "${OAC_FORCE_SYSTEMD:-}" = "1" ]; then return 0; fi
if [ "${OAC_FORCE_SYSTEMD:-}" = "0" ]; then return 1; fi
command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]
}
# ---------------------------------------------------------------------------
# Arguments
# ---------------------------------------------------------------------------
HOST=""; PORT=""; WORKDIR=""; SVC_USER=""; SVC_GROUP=""
ENABLE="" # 1 | 0 | empty=ask
ASSUME_YES=0
DRY_RUN=0
UNINSTALL=0
usage() {
sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//'
}
while [ $# -gt 0 ]; do
case "$1" in
--host) [ $# -ge 2 ] || die "--host needs a value"; HOST="$2"; shift ;;
--host=*) HOST="${1#*=}" ;;
--port) [ $# -ge 2 ] || die "--port needs a value"; PORT="$2"; shift ;;
--port=*) PORT="${1#*=}" ;;
--dir) [ $# -ge 2 ] || die "--dir needs a value"; WORKDIR="$2"; shift ;;
--dir=*) WORKDIR="${1#*=}" ;;
--user) [ $# -ge 2 ] || die "--user needs a value"; SVC_USER="$2"; shift ;;
--user=*) SVC_USER="${1#*=}" ;;
--enable) ENABLE=1 ;;
--no-enable) ENABLE=0 ;;
-y|--yes) ASSUME_YES=1 ;;
--uninstall) UNINSTALL=1 ;;
--dry-run) DRY_RUN=1 ;;
-h|--help) usage; exit 0 ;;
*) error "Unknown option: $1"; echo; usage; exit 1 ;;
esac
shift
done
# ---------------------------------------------------------------------------
# Privilege
# ---------------------------------------------------------------------------
SUDO=""
[ "$(id -u)" != "0" ] && SUDO="sudo"
UNIT_PATH="/etc/systemd/system/${SERVICE_NAME}"
# ---------------------------------------------------------------------------
# Uninstall
# ---------------------------------------------------------------------------
if [ "$UNINSTALL" = 1 ]; then
if ! is_systemd; then
warn "systemd not detected — nothing to uninstall here."; exit 0
fi
if [ "$DRY_RUN" = 1 ]; then
info "[dry-run] would stop, disable and delete ${UNIT_PATH}"; exit 0
fi
$SUDO systemctl disable --now "$SERVICE_NAME" 2>/dev/null || true
if [ -e "$UNIT_PATH" ]; then
$SUDO rm -f "$UNIT_PATH" && success "Removed ${UNIT_PATH}"
else
info "No unit at ${UNIT_PATH}"
fi
$SUDO systemctl daemon-reload 2>/dev/null || true
exit 0
fi
# ---------------------------------------------------------------------------
# Locate OpenCode
# ---------------------------------------------------------------------------
have_opencode || die "OpenCode is not installed (looked on PATH and ${OPENCODE_BIN_DIR}). Install it first."
OC_BIN="$(opencode_path)"
# ---------------------------------------------------------------------------
# Prompt (interactive only)
# ---------------------------------------------------------------------------
INTERACTIVE=0
if [ "$ASSUME_YES" = 0 ] && have_tty; then INTERACTIVE=1; fi
if [ "$INTERACTIVE" = 1 ] && [ "$DRY_RUN" = 0 ]; then
printf "\n${BOLD}Set up the OpenCode web UI as a systemd service?${NC} [Y/n]: "
ans=""; tty_read ans
case "$ans" in
[Nn]*) info "Cancelled."; exit 0 ;;
esac
fi
[ -n "$HOST" ] || HOST="$DEFAULT_HOST"
[ -n "$PORT" ] || PORT="$DEFAULT_PORT"
[ -n "$WORKDIR" ] || WORKDIR="$(pwd)"
[ -n "$SVC_USER" ] || SVC_USER="$(id -un)"
[ -n "$SVC_GROUP" ] || SVC_GROUP="$(id -gn)"
if [ "$INTERACTIVE" = 1 ] && [ "$DRY_RUN" = 0 ]; then
v=""
printf " Bind address [%s]: " "$HOST"; tty_read v; [ -n "$v" ] && HOST="$v"
printf " Port [%s]: " "$PORT"; tty_read v; [ -n "$v" ] && PORT="$v"
printf " Working directory [%s]: " "$WORKDIR"; tty_read v; [ -n "$v" ] && WORKDIR="$v"
printf " Start now and enable on boot? [Y/n]: "; v=""; tty_read v
case "$v" in [Nn]*) ENABLE=0 ;; *) ENABLE=1 ;; esac
fi
[ -n "$ENABLE" ] || ENABLE=1
# ---------------------------------------------------------------------------
# Build the unit
# ---------------------------------------------------------------------------
UNIT="$(mktemp 2>/dev/null || mktemp -t oac-unit)"
cat > "$UNIT" <<EOF
[Unit]
Description=opencode server (web UI)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=${SVC_USER}
Group=${SVC_GROUP}
WorkingDirectory=${WORKDIR}
ExecStart=${OC_BIN} serve --hostname ${HOST} --port ${PORT}
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
cleanup() { rm -f "$UNIT"; }
trap cleanup EXIT INT TERM
# ---------------------------------------------------------------------------
# Install
# ---------------------------------------------------------------------------
if ! is_systemd; then
warn "systemd not detected — cannot install a service here."
info "Start the web UI manually: ${OC_BIN} serve --hostname ${HOST} --port ${PORT}"
exit 0
fi
echo
info "Unit to install:"
echo "------------------------------------------------------------"
cat "$UNIT"
echo "------------------------------------------------------------"
if [ "$DRY_RUN" = 1 ]; then
warn "Dry run — nothing was written."
exit 0
fi
if [ "$INTERACTIVE" = 1 ]; then
printf "Install to %s? [y/N]: " "$UNIT_PATH"
ans=""; tty_read ans
case "$ans" in
[Yy]*) ;;
*) info "Cancelled."; exit 0 ;;
esac
fi
# Back up an existing unit before replacing it
if [ -e "$UNIT_PATH" ]; then
backup="${UNIT_PATH}.bak.$(date +%Y%m%d-%H%M%S)"
if $SUDO cp "$UNIT_PATH" "$backup" 2>/dev/null; then
info "Backed up the existing unit to ${backup}"
fi
fi
if ! $SUDO cp "$UNIT" "$UNIT_PATH"; then
die "Could not write ${UNIT_PATH} (needs root). Re-run with sudo."
fi
$SUDO systemctl daemon-reload 2>/dev/null || true
if [ "$ENABLE" = 1 ]; then
if $SUDO systemctl enable --now "$SERVICE_NAME" 2>/dev/null; then
success "Web UI service enabled — http://${HOST}:${PORT}"
else
warn "Unit installed; enable it later: sudo systemctl enable --now ${SERVICE_NAME}"
fi
else
success "Unit installed — start it with: sudo systemctl start ${SERVICE_NAME}"
fi