feat(service): --cors support + warn when username != opencode

- --cors ORIGIN (repeatable) and an interactive CORS prompt; adds --cors to ExecStart
- warn when a password is set with a username other than 'opencode' (the web UI always authenticates as opencode)
- README: --cors + the 'don't stack two basic-auth layers' note
This commit is contained in:
Claus Lohmar 2026-10-13 08:12:11 +00:00
parent b6cbcdc080
commit 8cef2acfa5
2 changed files with 21 additions and 1 deletions

View file

@ -250,6 +250,7 @@ The service step is a **separate program** — `install-service.sh` — so you c
./install-service.sh --password secret # enable OpenCode's HTTP basic auth
./install-service.sh --username admin --password secret
./install-service.sh --no-password # leave the server unsecured
./install-service.sh --cors https://ui.example.com # allow a cross-origin web UI
./install-service.sh --uninstall # stop and remove the service
./install-service.sh --dry-run # show the unit, change nothing
```
@ -257,6 +258,10 @@ The service step is a **separate program** — `install-service.sh` — so you c
**Auth (optional).** OpenCode's server has its own HTTP basic auth — enabled purely by setting `OPENCODE_SERVER_PASSWORD` (username defaults to `opencode`, or set `OPENCODE_SERVER_USERNAME`). The installer offers this interactively, or accept it non-interactively with `--password`. The secret is **never written into the unit**: it goes into a root-only env file (`/etc/opencode/opencode.env`, mode `600`) that the unit pulls in via `EnvironmentFile=`. Use this **or** your proxy's basic auth — they don't conflict.
> **The web app is a client.** On first use it shows an **"Add server"** dialog asking for the **server address** and the **password** — the username is *not* asked because it defaults to `opencode`. Enter your server URL and the password you set here. It's remembered after that. Keep the username as `opencode` (a custom one would not be sent by the browser).
>
> **Don't stack two basic-auth layers.** A browser can send only one `Authorization` header. If your reverse proxy already does basic auth (Zoraxy, nginx…), **leave OpenCode's password unset** and leave the dialog's password **blank** — otherwise the client's OpenCode credentials overwrite the browser's proxy credentials and the proxy rejects them. Choose one layer, not both.
>
> If the web UI is served from a **different origin** than the server, start the server with the matching `--cors <origin>`.
It refuses to run non-interactively unless you pass `--yes`, and backs up any existing unit before replacing it.

View file

@ -94,6 +94,7 @@ is_systemd() {
# ---------------------------------------------------------------------------
HOST=""; PORT=""; WORKDIR=""; SVC_USER=""; SVC_GROUP=""
ENABLE="" # 1 | 0 | empty=ask
CORS="" # space-separated extra allowed origins
SVC_USERNAME="${OPENCODE_SERVER_USERNAME:-opencode}"
PASSWORD="${OPENCODE_SERVER_PASSWORD:-}"
PASSWORD_SET=0
@ -119,6 +120,8 @@ Options:
--user USER run the service as USER (default: you)
--enable start and enable on boot
--no-enable install only; do not start
--cors ORIGIN allow this origin (repeatable); use if the web UI is
served from a different origin than the server
--password PW enable basic auth with this password
--username USER basic-auth username (default: opencode)
--no-password do not set a password (server unsecured)
@ -153,6 +156,8 @@ while [ $# -gt 0 ]; do
--env-file=*) ENV_FILE="${1#*=}" ;;
--enable) ENABLE=1 ;;
--no-enable) ENABLE=0 ;;
--cors) [ $# -ge 2 ] || die "--cors needs an origin"; CORS="${CORS} $2"; shift ;;
--cors=*) CORS="${CORS} ${1#*=}" ;;
-y|--yes) ASSUME_YES=1 ;;
--uninstall) UNINSTALL=1 ;;
--dry-run) DRY_RUN=1 ;;
@ -224,6 +229,9 @@ if [ "$INTERACTIVE" = 1 ] && [ "$DRY_RUN" = 0 ]; then
printf " Start now and enable on boot? [Y/n]: "; v=""; tty_read v
case "$v" in [Nn]*) ENABLE=0 ;; *) ENABLE=1 ;; esac
printf " Extra allowed origins (CORS), space-separated [none]: "; v=""; tty_read v
[ -n "$v" ] && CORS="${CORS} $v"
if [ "$PASSWORD_SET" = 0 ]; then
printf " Protect the server with a password? [y/N]: "; v=""; tty_read v
case "$v" in
@ -253,6 +261,9 @@ if [ "$PASSWORD_SET" = 1 ]; then
case "$SVC_USERNAME" in
*$'\n'*) SVC_USERNAME="opencode" ;;
esac
if [ "$SVC_USERNAME" != "opencode" ]; then
warn "Username '${SVC_USERNAME}' set — the OpenCode web UI always authenticates as 'opencode', so the browser login will fail unless you keep username 'opencode'."
fi
fi
# ---------------------------------------------------------------------------
@ -261,6 +272,10 @@ fi
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
UNIT="$(mktemp 2>/dev/null || mktemp -t oac-unit)"
cat > "$UNIT" <<EOF
[Unit]
@ -274,7 +289,7 @@ User=${SVC_USER}
Group=${SVC_GROUP}
${ENV_LINE}
WorkingDirectory=${WORKDIR}
ExecStart=${OC_BIN} serve --hostname ${HOST} --port ${PORT}
ExecStart=${OC_BIN} serve --hostname ${HOST} --port ${PORT}${CORS_ARGS}
Restart=on-failure
RestartSec=5