From 3217ee7a076ff18e24f15cc29abf36288b6e7cff Mon Sep 17 00:00:00 2001 From: cclohmar Date: Tue, 13 Oct 2026 08:18:39 +0000 Subject: [PATCH] feat(service): v2-native path via 'opencode service' + 'serve --service' - detect OpenCode v2 (serve --help has --service) and configure it with 'opencode service set hostname/port/password/cors'; the unit runs 'opencode serve --service' (what the v2 manager spawns) - v2 stores the password in OpenCode's own service config; v1 keeps the EnvironmentFile + flags - password-based (portable) access for the web app: Add server -> address + password, username 'opencode' - README documents v2 vs v1 --- README.md | 2 ++ install-service.sh | 85 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index cb04ddf..69f950d 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,8 @@ The service step is a **separate program** — `install-service.sh` — so you c It refuses to run non-interactively unless you pass `--yes`, and backs up any existing unit before replacing it. +**OpenCode v2 vs v1.** On **v2**, the installer uses OpenCode's own background-service manager: it runs `opencode service set hostname/port/password/cors` and installs a unit that runs `opencode serve --service` (exactly the command the manager itself spawns). The password is stored in OpenCode's service config, not in our unit. On **v1** it passes `--hostname/--port` and keeps the password in an `EnvironmentFile`. + - **Default bind is `127.0.0.1`** — the server has **no authentication of its own**, so keep it loopback-only and put a reverse proxy (Zoraxy, nginx, …) in front. Choose `0.0.0.0` only if you deliberately want it exposed on your network. - **Non-systemd** hosts: it prints the command to run the web UI manually instead. - **Non-interactive** (e.g. `curl | bash`, CI): the service is **not** set up unless you pass `--service`. diff --git a/install-service.sh b/install-service.sh index 1b0cb2d..2f87820 100755 --- a/install-service.sh +++ b/install-service.sh @@ -201,6 +201,18 @@ fi have_opencode || die "OpenCode is not installed (looked on PATH and ${OPENCODE_BIN_DIR}). Install it first." OC_BIN="$(opencode_path)" +# OpenCode v2 manages its own background server (`opencode service`) and runs it +# as `opencode serve --service`; the server reads host/port/password/cors from the +# service config. v1 has no manager — we pass flags + an env file instead. +OPENCODE_V2=0 +if "$OC_BIN" serve --help 2>&1 | grep -q -- '--service'; then OPENCODE_V2=1; fi + +# Run an opencode command as the service user. +oc_run() { + if [ "$SVC_USER" = "$(id -un)" ]; then "$OC_BIN" "$@" + else $SUDO -u "$SVC_USER" "$OC_BIN" "$@"; fi +} + # --------------------------------------------------------------------------- # Prompt (interactive only) # --------------------------------------------------------------------------- @@ -270,11 +282,17 @@ fi # Build the unit # --------------------------------------------------------------------------- ENV_LINE="" -[ "$PASSWORD_SET" = 1 ] && ENV_LINE="EnvironmentFile=-${ENV_FILE}" - -# Build the optional --cors flags -CORS_ARGS="" -for o in $CORS; do CORS_ARGS="${CORS_ARGS} --cors ${o}"; done +EXEC_ARGS="" +RESTART_POLICY="on-failure" +if [ "$OPENCODE_V2" = 1 ]; then + # v2: host/port/password/cors live in the service config, so run in service mode. + EXEC_ARGS="--service" + RESTART_POLICY="always" # a config change stops+restarts the server; bring it back +else + [ "$PASSWORD_SET" = 1 ] && ENV_LINE="EnvironmentFile=-${ENV_FILE}" + for o in $CORS; do EXEC_ARGS="${EXEC_ARGS} --cors ${o}"; done + EXEC_ARGS="--hostname ${HOST} --port ${PORT}${EXEC_ARGS}" +fi UNIT="$(mktemp 2>/dev/null || mktemp -t oac-unit)" cat > "$UNIT" <')" + else + [ "$PASSWORD_SET" = 1 ] && info "[dry-run] would write credentials to ${ENV_FILE} (mode 600)" + fi warn "Dry run — nothing was written." exit 0 fi @@ -331,21 +353,35 @@ if [ "$INTERACTIVE" = 1 ]; then esac fi -# Write the credentials file (root-readable) before pointing the unit at it. -if [ "$PASSWORD_SET" = 1 ]; then - esc="$(printf '%s' "$PASSWORD" | sed 's/\\/\\\\/g; s/"/\\"/g')" - if ! $SUDO mkdir -p "$(dirname "$ENV_FILE")"; then - die "Could not create $(dirname "$ENV_FILE")" +# Apply the server configuration. +if [ "$OPENCODE_V2" = 1 ]; then + info "Configuring the OpenCode service…" + oc_run service set hostname "$HOST" || die "opencode service set hostname failed" + oc_run service set port "$PORT" || die "opencode service set port failed" + if [ "$PASSWORD_SET" = 1 ]; then + oc_run service set password "$PASSWORD" || die "opencode service set password failed" fi - if ! { printf 'OPENCODE_SERVER_PASSWORD="%s"\n' "$esc" - if [ "$SVC_USERNAME" != "opencode" ]; then - printf 'OPENCODE_SERVER_USERNAME="%s"\n' "$SVC_USERNAME" - fi - } | $SUDO tee "$ENV_FILE" >/dev/null; then - die "Could not write ${ENV_FILE}" + if [ -n "$CORS" ]; then + cors_list="$(printf '%s' "$CORS" | tr ' ' ',')" + oc_run service set cors "$cors_list" || die "opencode service set cors failed" + fi + success "Configured the OpenCode service (host/port/password/cors)" +else + if [ "$PASSWORD_SET" = 1 ]; then + esc="$(printf '%s' "$PASSWORD" | sed 's/\\/\\\\/g; s/"/\\"/g')" + if ! $SUDO mkdir -p "$(dirname "$ENV_FILE")"; then + die "Could not create $(dirname "$ENV_FILE")" + fi + if ! { printf 'OPENCODE_SERVER_PASSWORD="%s"\n' "$esc" + if [ "$SVC_USERNAME" != "opencode" ]; then + printf 'OPENCODE_SERVER_USERNAME="%s"\n' "$SVC_USERNAME" + fi + } | $SUDO tee "$ENV_FILE" >/dev/null; then + die "Could not write ${ENV_FILE}" + fi + $SUDO chmod 600 "$ENV_FILE" + success "Wrote credentials to ${ENV_FILE} (mode 600)" fi - $SUDO chmod 600 "$ENV_FILE" - success "Wrote credentials to ${ENV_FILE} (mode 600)" fi # Back up an existing unit before replacing it @@ -372,7 +408,12 @@ else fi if [ "$PASSWORD_SET" = 1 ]; then - info "Auth: HTTP basic — username '${SVC_USERNAME}', password from ${ENV_FILE}" + if [ "$OPENCODE_V2" = 1 ]; then + info "Auth: HTTP basic — username 'opencode', password in the OpenCode service config" + info "In the web app: Add server → address = your public URL, username blank (opencode), password." + else + info "Auth: HTTP basic — username '${SVC_USERNAME}', password from ${ENV_FILE}" + fi else warn "No password set — the server is unsecured. Put it behind your proxy's auth." fi