feat(service): optional OpenCode HTTP basic auth, set at install time
- install-service.sh: interactive password prompt (hidden input); --password/--username/--no-password and OPENCODE_SERVER_PASSWORD/USERNAME env - credentials are written to a root-only env file (mode 600) referenced by the unit via EnvironmentFile=; the password never appears in the unit - unit name/env path overridable for testing (OPENCODE_SERVICE_NAME, OPENCODE_ENV_FILE) - README documents the auth option
This commit is contained in:
parent
bbe6b042a7
commit
381c1008e9
2 changed files with 133 additions and 18 deletions
|
|
@ -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 --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.
|
||||
|
|
|
|||
|
|
@ -2,23 +2,30 @@
|
|||
#
|
||||
# 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 <host> --port <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)
|
||||
# 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)
|
||||
# ============================================================================
|
||||
|
||||
|
|
@ -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/tty || true
|
||||
fi
|
||||
if [ -t 0 ] || [ ! -r /dev/tty ]; then read -r __val || true
|
||||
else read -r __val </dev/tty || true; fi
|
||||
printf -v "$__var" '%s' "$__val"
|
||||
}
|
||||
tty_read_secret() {
|
||||
local __var="$1" __val=""
|
||||
if [ -t 0 ] || [ ! -r /dev/tty ]; then read -rs __val || true
|
||||
else read -rs __val </dev/tty || true; fi
|
||||
printf '\n'
|
||||
printf -v "$__var" '%s' "$__val"
|
||||
}
|
||||
|
||||
|
|
@ -83,12 +94,44 @@ is_systemd() {
|
|||
# ---------------------------------------------------------------------------
|
||||
HOST=""; PORT=""; WORKDIR=""; SVC_USER=""; SVC_GROUP=""
|
||||
ENABLE="" # 1 | 0 | empty=ask
|
||||
SVC_USERNAME="${OPENCODE_SERVER_USERNAME:-opencode}"
|
||||
PASSWORD="${OPENCODE_SERVER_PASSWORD:-}"
|
||||
PASSWORD_SET=0
|
||||
[ -n "$PASSWORD" ] && PASSWORD_SET=1
|
||||
ENV_FILE="${OPENCODE_ENV_FILE:-/etc/opencode/opencode.env}"
|
||||
ASSUME_YES=0
|
||||
DRY_RUN=0
|
||||
UNINSTALL=0
|
||||
|
||||
usage() {
|
||||
sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//'
|
||||
cat <<'EOF'
|
||||
OpenAgents Control — Architect Fork · web UI service installer
|
||||
|
||||
Creates (or removes) a systemd service that runs the OpenCode web UI
|
||||
(opencode serve). Optionally protects it with HTTP basic auth.
|
||||
|
||||
Usage: install-service.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
--host HOST bind address (default: 127.0.0.1)
|
||||
--port PORT port (default: 4096)
|
||||
--dir PATH working directory (default: current dir)
|
||||
--user USER run the service as USER (default: you)
|
||||
--enable start and enable on boot
|
||||
--no-enable install only; do not start
|
||||
--password PW enable basic auth with this password
|
||||
--username USER basic-auth username (default: opencode)
|
||||
--no-password do not set a password (server unsecured)
|
||||
--env-file PATH where to store credentials (default: /etc/opencode/opencode.env)
|
||||
-y, --yes non-interactive (defaults; no prompts)
|
||||
--uninstall stop, disable and remove the service
|
||||
--dry-run show the unit and exit without changing anything
|
||||
-h, --help show this help
|
||||
|
||||
Environment:
|
||||
OPENCODE_SERVICE_NAME, OPENCODE_SERVER_PASSWORD, OPENCODE_SERVER_USERNAME,
|
||||
OPENCODE_ENV_FILE, OAC_FORCE_SYSTEMD
|
||||
EOF
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
|
|
@ -101,6 +144,13 @@ while [ $# -gt 0 ]; do
|
|||
--dir=*) WORKDIR="${1#*=}" ;;
|
||||
--user) [ $# -ge 2 ] || die "--user needs a value"; SVC_USER="$2"; shift ;;
|
||||
--user=*) SVC_USER="${1#*=}" ;;
|
||||
--username) [ $# -ge 2 ] || die "--username needs a value"; SVC_USERNAME="$2"; shift ;;
|
||||
--username=*) SVC_USERNAME="${1#*=}" ;;
|
||||
--password) [ $# -ge 2 ] || die "--password needs a value"; PASSWORD="$2"; PASSWORD_SET=1; shift ;;
|
||||
--password=*) PASSWORD="${1#*=}"; PASSWORD_SET=1 ;;
|
||||
--no-password) PASSWORD=""; PASSWORD_SET=0 ;;
|
||||
--env-file) [ $# -ge 2 ] || die "--env-file needs a value"; ENV_FILE="$2"; shift ;;
|
||||
--env-file=*) ENV_FILE="${1#*=}" ;;
|
||||
--enable) ENABLE=1 ;;
|
||||
--no-enable) ENABLE=0 ;;
|
||||
-y|--yes) ASSUME_YES=1 ;;
|
||||
|
|
@ -136,6 +186,7 @@ if [ "$UNINSTALL" = 1 ]; then
|
|||
info "No unit at ${UNIT_PATH}"
|
||||
fi
|
||||
$SUDO systemctl daemon-reload 2>/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" <<EOF
|
||||
[Unit]
|
||||
|
|
@ -189,6 +273,7 @@ Wants=network-online.target
|
|||
Type=simple
|
||||
User=${SVC_USER}
|
||||
Group=${SVC_GROUP}
|
||||
${ENV_LINE}
|
||||
WorkingDirectory=${WORKDIR}
|
||||
ExecStart=${OC_BIN} serve --hostname ${HOST} --port ${PORT}
|
||||
Restart=on-failure
|
||||
|
|
@ -207,6 +292,7 @@ trap cleanup EXIT INT TERM
|
|||
if ! is_systemd; then
|
||||
warn "systemd not detected — cannot install a service here."
|
||||
info "Start the web UI manually: ${OC_BIN} serve --hostname ${HOST} --port ${PORT}"
|
||||
[ "$PASSWORD_SET" = 1 ] && info "with auth: OPENCODE_SERVER_PASSWORD=*** ${OC_BIN} serve ..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
|
@ -217,6 +303,7 @@ cat "$UNIT"
|
|||
echo "------------------------------------------------------------"
|
||||
|
||||
if [ "$DRY_RUN" = 1 ]; then
|
||||
[ "$PASSWORD_SET" = 1 ] && info "[dry-run] would write credentials to ${ENV_FILE} (mode 600)"
|
||||
warn "Dry run — nothing was written."
|
||||
exit 0
|
||||
fi
|
||||
|
|
@ -230,6 +317,23 @@ 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")"
|
||||
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
|
||||
|
||||
# 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
|
||||
|
|
|
|||
Loading…
Reference in a new issue