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
13
README.md
13
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):
|
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
|
```bash
|
||||||
./install-service.sh # interactive
|
./install-service.sh # interactive (also asks for a password)
|
||||||
./install-service.sh --port 4096
|
./install-service.sh --port 4096
|
||||||
./install-service.sh --host 0.0.0.0 --port 8080
|
./install-service.sh --host 0.0.0.0 --port 8080
|
||||||
./install-service.sh --yes # non-interactive defaults
|
./install-service.sh --yes # non-interactive defaults
|
||||||
./install-service.sh --uninstall # stop and remove the service
|
./install-service.sh --password secret # enable OpenCode's HTTP basic auth
|
||||||
./install-service.sh --dry-run # show the unit, change nothing
|
./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.
|
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.
|
- **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,24 +2,31 @@
|
||||||
#
|
#
|
||||||
# OpenAgents Control — Architect Fork · web UI service installer
|
# OpenAgents Control — Architect Fork · web UI service installer
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Creates (or removes) a systemd service that runs the OpenCode web UI as a
|
# Creates (or removes) a systemd service that runs the OpenCode web UI.
|
||||||
# background process:
|
|
||||||
#
|
#
|
||||||
# opencode serve --hostname <host> --port <port>
|
# 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:
|
# Usage:
|
||||||
# ./install-service.sh # interactive
|
# ./install-service.sh # interactive (asks for a password)
|
||||||
# ./install-service.sh --port 4096 # interactive, prefilled port
|
# ./install-service.sh --port 4096
|
||||||
# ./install-service.sh --host 0.0.0.0 --port 8080
|
# ./install-service.sh --host 0.0.0.0 --port 8080
|
||||||
# ./install-service.sh --yes # non-interactive defaults
|
# ./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 --uninstall # stop and remove the service
|
||||||
# ./install-service.sh --dry-run # show the unit, change nothing
|
# ./install-service.sh --dry-run # show the unit, change nothing
|
||||||
#
|
#
|
||||||
# Environment:
|
# Environment:
|
||||||
# OPENCODE_SERVICE_NAME unit name (default: opencode.service)
|
# OPENCODE_SERVICE_NAME unit name (default: opencode.service)
|
||||||
# OAC_FORCE_SYSTEMD 1/0 to force systemd detection (containers/CI)
|
# 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
|
set -euo pipefail
|
||||||
|
|
@ -54,11 +61,15 @@ have_tty() {
|
||||||
}
|
}
|
||||||
tty_read() {
|
tty_read() {
|
||||||
local __var="$1" __val=""
|
local __var="$1" __val=""
|
||||||
if [ -t 0 ] || [ ! -r /dev/tty ]; then
|
if [ -t 0 ] || [ ! -r /dev/tty ]; then read -r __val || true
|
||||||
read -r __val || true
|
else read -r __val </dev/tty || true; fi
|
||||||
else
|
printf -v "$__var" '%s' "$__val"
|
||||||
read -r __val </dev/tty || true
|
}
|
||||||
fi
|
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"
|
printf -v "$__var" '%s' "$__val"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,13 +93,45 @@ is_systemd() {
|
||||||
# Arguments
|
# Arguments
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
HOST=""; PORT=""; WORKDIR=""; SVC_USER=""; SVC_GROUP=""
|
HOST=""; PORT=""; WORKDIR=""; SVC_USER=""; SVC_GROUP=""
|
||||||
ENABLE="" # 1 | 0 | empty=ask
|
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
|
ASSUME_YES=0
|
||||||
DRY_RUN=0
|
DRY_RUN=0
|
||||||
UNINSTALL=0
|
UNINSTALL=0
|
||||||
|
|
||||||
usage() {
|
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
|
while [ $# -gt 0 ]; do
|
||||||
|
|
@ -101,6 +144,13 @@ while [ $# -gt 0 ]; do
|
||||||
--dir=*) WORKDIR="${1#*=}" ;;
|
--dir=*) WORKDIR="${1#*=}" ;;
|
||||||
--user) [ $# -ge 2 ] || die "--user needs a value"; SVC_USER="$2"; shift ;;
|
--user) [ $# -ge 2 ] || die "--user needs a value"; SVC_USER="$2"; shift ;;
|
||||||
--user=*) SVC_USER="${1#*=}" ;;
|
--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 ;;
|
--enable) ENABLE=1 ;;
|
||||||
--no-enable) ENABLE=0 ;;
|
--no-enable) ENABLE=0 ;;
|
||||||
-y|--yes) ASSUME_YES=1 ;;
|
-y|--yes) ASSUME_YES=1 ;;
|
||||||
|
|
@ -136,6 +186,7 @@ if [ "$UNINSTALL" = 1 ]; then
|
||||||
info "No unit at ${UNIT_PATH}"
|
info "No unit at ${UNIT_PATH}"
|
||||||
fi
|
fi
|
||||||
$SUDO systemctl daemon-reload 2>/dev/null || true
|
$SUDO systemctl daemon-reload 2>/dev/null || true
|
||||||
|
info "Credentials file left in place: ${ENV_FILE} (remove it manually if you like)."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
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 " Working directory [%s]: " "$WORKDIR"; tty_read v; [ -n "$v" ] && WORKDIR="$v"
|
||||||
printf " Start now and enable on boot? [Y/n]: "; v=""; tty_read v
|
printf " Start now and enable on boot? [Y/n]: "; v=""; tty_read v
|
||||||
case "$v" in [Nn]*) ENABLE=0 ;; *) ENABLE=1 ;; esac
|
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
|
fi
|
||||||
[ -n "$ENABLE" ] || ENABLE=1
|
[ -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
|
# Build the unit
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
ENV_LINE=""
|
||||||
|
[ "$PASSWORD_SET" = 1 ] && ENV_LINE="EnvironmentFile=-${ENV_FILE}"
|
||||||
|
|
||||||
UNIT="$(mktemp 2>/dev/null || mktemp -t oac-unit)"
|
UNIT="$(mktemp 2>/dev/null || mktemp -t oac-unit)"
|
||||||
cat > "$UNIT" <<EOF
|
cat > "$UNIT" <<EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
|
|
@ -189,6 +273,7 @@ Wants=network-online.target
|
||||||
Type=simple
|
Type=simple
|
||||||
User=${SVC_USER}
|
User=${SVC_USER}
|
||||||
Group=${SVC_GROUP}
|
Group=${SVC_GROUP}
|
||||||
|
${ENV_LINE}
|
||||||
WorkingDirectory=${WORKDIR}
|
WorkingDirectory=${WORKDIR}
|
||||||
ExecStart=${OC_BIN} serve --hostname ${HOST} --port ${PORT}
|
ExecStart=${OC_BIN} serve --hostname ${HOST} --port ${PORT}
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
@ -207,6 +292,7 @@ trap cleanup EXIT INT TERM
|
||||||
if ! is_systemd; then
|
if ! is_systemd; then
|
||||||
warn "systemd not detected — cannot install a service here."
|
warn "systemd not detected — cannot install a service here."
|
||||||
info "Start the web UI manually: ${OC_BIN} serve --hostname ${HOST} --port ${PORT}"
|
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
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -217,6 +303,7 @@ cat "$UNIT"
|
||||||
echo "------------------------------------------------------------"
|
echo "------------------------------------------------------------"
|
||||||
|
|
||||||
if [ "$DRY_RUN" = 1 ]; then
|
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."
|
warn "Dry run — nothing was written."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
@ -230,6 +317,23 @@ if [ "$INTERACTIVE" = 1 ]; then
|
||||||
esac
|
esac
|
||||||
fi
|
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
|
# Back up an existing unit before replacing it
|
||||||
if [ -e "$UNIT_PATH" ]; then
|
if [ -e "$UNIT_PATH" ]; then
|
||||||
backup="${UNIT_PATH}.bak.$(date +%Y%m%d-%H%M%S)"
|
backup="${UNIT_PATH}.bak.$(date +%Y%m%d-%H%M%S)"
|
||||||
|
|
@ -252,3 +356,9 @@ if [ "$ENABLE" = 1 ]; then
|
||||||
else
|
else
|
||||||
success "Unit installed — start it with: sudo systemctl start ${SERVICE_NAME}"
|
success "Unit installed — start it with: sudo systemctl start ${SERVICE_NAME}"
|
||||||
fi
|
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