#!/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 --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/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" </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