diff --git a/README.md b/README.md index 9178fc8..617c1a4 100644 --- a/README.md +++ b/README.md @@ -243,14 +243,19 @@ WantedBy=multi-user.target The service step is a **separate program** — `install-service.sh` — so you can also run it on its own (the installer just calls it after OpenCode is installed): ```bash -./install-service.sh # interactive +./install-service.sh # interactive (also asks for a password) ./install-service.sh --port 4096 ./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 +./install-service.sh --yes # non-interactive defaults +./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 --uninstall # stop and remove the service +./install-service.sh --dry-run # show the unit, change nothing ``` +**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. + It refuses to run non-interactively unless you pass `--yes`, and backs up any existing unit before replacing it. - **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. diff --git a/install-service.sh b/install-service.sh index 7dd8063..5b3b77f 100755 --- a/install-service.sh +++ b/install-service.sh @@ -2,24 +2,31 @@ # # OpenAgents Control — Architect Fork · web UI service installer # ============================================================================ -# Creates (or removes) a systemd service that runs the OpenCode web UI as a -# background process: +# Creates (or removes) a systemd service that runs the OpenCode web UI. # # opencode serve --hostname --port # -# Run it on its own, or let install.sh call it after OpenCode is installed. +# Optionally protects it with OpenCode's own HTTP basic auth: the password is +# written to a root-readable env file (mode 600) referenced by the unit via +# EnvironmentFile= — the password never appears in the unit itself. # # Usage: -# ./install-service.sh # interactive -# ./install-service.sh --port 4096 # interactive, prefilled port +# ./install-service.sh # interactive (asks for a password) +# ./install-service.sh --port 4096 # ./install-service.sh --host 0.0.0.0 --port 8080 # ./install-service.sh --yes # non-interactive defaults +# ./install-service.sh --password secret # enable basic auth +# ./install-service.sh --username admin --password secret +# ./install-service.sh --no-password # ensure the server is unsecured # ./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) +# OPENCODE_SERVICE_NAME unit name (default: opencode.service) +# OPENCODE_SERVER_PASSWORD basic-auth password (same as --password) +# OPENCODE_SERVER_USERNAME basic-auth username (default: opencode) +# OPENCODE_ENV_FILE env file path (default: /etc/opencode/opencode.env) +# OAC_FORCE_SYSTEMD 1/0 to force systemd detection (containers/CI) # ============================================================================ set -euo pipefail @@ -54,11 +61,15 @@ have_tty() { } tty_read() { local __var="$1" __val="" - if [ -t 0 ] || [ ! -r /dev/tty ]; then - read -r __val || true - else - read -r __val /dev/null || true + info "Credentials file left in place: ${ENV_FILE} (remove it manually if you like)." exit 0 fi @@ -172,12 +223,45 @@ if [ "$INTERACTIVE" = 1 ] && [ "$DRY_RUN" = 0 ]; then 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 + + if [ "$PASSWORD_SET" = 0 ]; then + printf " Protect the server with a password? [y/N]: "; v=""; tty_read v + case "$v" in + [Yy]*) + printf " Username [%s]: " "$SVC_USERNAME"; v=""; tty_read v; [ -n "$v" ] && SVC_USERNAME="$v" + pw1=""; printf " Password: "; tty_read_secret pw1 + pw2=""; printf " Confirm password: "; tty_read_secret pw2 + if [ -z "$pw1" ]; then + warn "Empty password — the server will be unsecured." + elif [ "$pw1" != "$pw2" ]; then + warn "Passwords do not match — the server will be unsecured." + else + PASSWORD="$pw1"; PASSWORD_SET=1 + fi + ;; + esac + fi fi [ -n "$ENABLE" ] || ENABLE=1 +# --------------------------------------------------------------------------- +# Validate the password (env-file safety) +# --------------------------------------------------------------------------- +if [ "$PASSWORD_SET" = 1 ]; then + case "$PASSWORD" in + *$'\n'*) warn "Password contains a newline — disabling auth."; PASSWORD_SET=0 ;; + esac + case "$SVC_USERNAME" in + *$'\n'*) SVC_USERNAME="opencode" ;; + esac +fi + # --------------------------------------------------------------------------- # Build the unit # --------------------------------------------------------------------------- +ENV_LINE="" +[ "$PASSWORD_SET" = 1 ] && ENV_LINE="EnvironmentFile=-${ENV_FILE}" + UNIT="$(mktemp 2>/dev/null || mktemp -t oac-unit)" cat > "$UNIT" </dev/null; then + die "Could not write ${ENV_FILE}" + fi + $SUDO chmod 600 "$ENV_FILE" + success "Wrote credentials to ${ENV_FILE} (mode 600)" +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)" @@ -252,3 +356,9 @@ if [ "$ENABLE" = 1 ]; then else success "Unit installed — start it with: sudo systemctl start ${SERVICE_NAME}" fi + +if [ "$PASSWORD_SET" = 1 ]; then + info "Auth: HTTP basic — username '${SVC_USERNAME}', password from ${ENV_FILE}" +else + warn "No password set — the server is unsecured. Put it behind your proxy's auth." +fi