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
This commit is contained in:
Claus Lohmar 2026-10-13 08:18:39 +00:00
parent 8cef2acfa5
commit 3217ee7a07
2 changed files with 65 additions and 22 deletions

View file

@ -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`.

View file

@ -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" <<EOF
@ -289,8 +307,8 @@ User=${SVC_USER}
Group=${SVC_GROUP}
${ENV_LINE}
WorkingDirectory=${WORKDIR}
ExecStart=${OC_BIN} serve --hostname ${HOST} --port ${PORT}${CORS_ARGS}
Restart=on-failure
ExecStart=${OC_BIN} serve ${EXEC_ARGS}
Restart=${RESTART_POLICY}
RestartSec=5
[Install]
@ -317,7 +335,11 @@ cat "$UNIT"
echo "------------------------------------------------------------"
if [ "$DRY_RUN" = 1 ]; then
[ "$PASSWORD_SET" = 1 ] && info "[dry-run] would write credentials to ${ENV_FILE} (mode 600)"
if [ "$OPENCODE_V2" = 1 ]; then
info "[dry-run] would run: opencode service set hostname ${HOST}; port ${PORT}$([ "$PASSWORD_SET" = 1 ] && echo '; password <hidden>')"
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