feat(install): interactive web UI service setup; README for the public repo
- install.sh: at the end of an interactive install, offer to set up the OpenCode web UI as a systemd service. Prompts for bind address (default 127.0.0.1), port (4096), working dir and enable-on-boot, shows the unit, then asks to install. --service/--no-service for scripting; off by default non-interactively. Non-systemd hosts get the manual command. - fix: perform_install returned non-zero under set -e, aborting the post-install and service steps - README: repository is now public (quick curl|bash install, no token); document the web UI service and its security default
This commit is contained in:
parent
2e1f8ca525
commit
b25b2fa7c8
2 changed files with 172 additions and 11 deletions
57
README.md
57
README.md
|
|
@ -134,11 +134,17 @@ env.example template for the optional .env (Telegram/Gemini integrations)
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
This repository is **private**, so the installer needs access to it. The simplest path is to clone it once and run the installer from the clone — your existing git credentials are used, no token to paste.
|
The repository is **public** — no token required.
|
||||||
|
|
||||||
The installer is **all-in-one**: if OpenCode isn't already installed, it bootstraps the runtime first (from the official installer at [`opencode.ai/v2/install`](https://opencode.ai/v2/install)), then installs the fork.
|
The installer is **all-in-one**: if OpenCode isn't already installed, it bootstraps the runtime first (from the official installer at [`opencode.ai/v2/install`](https://opencode.ai/v2/install)), then installs the fork — and finally offers to set up the **web UI service**.
|
||||||
|
|
||||||
### Recommended — clone, then install
|
### Quick install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.lohmar.co.uk/cclohmar/NewOpenAgents/raw/branch/main/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Or clone, then install (recommended for updates)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.lohmar.co.uk/cclohmar/NewOpenAgents.git ~/.oac
|
git clone https://git.lohmar.co.uk/cclohmar/NewOpenAgents.git ~/.oac
|
||||||
|
|
@ -148,13 +154,7 @@ git clone https://git.lohmar.co.uk/cclohmar/NewOpenAgents.git ~/.oac
|
||||||
~/.oac/install.sh --local # → ./.opencode (this project)
|
~/.oac/install.sh --local # → ./.opencode (this project)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bootstrap without a clone (private → pass a token)
|
**Interactively, the installer walks you through everything** — where to install, the OpenCode runtime if it's missing, and finally the optional web UI service. You answer prompts; you rarely need a flag. Flags exist for scripting and CI.
|
||||||
|
|
||||||
```bash
|
|
||||||
export OAC_TOKEN=<a Forgejo token with repo:read>
|
|
||||||
curl -fsSL "https://git.lohmar.co.uk/cclohmar/NewOpenAgents/raw/branch/main/install.sh?token=$OAC_TOKEN" \
|
|
||||||
| bash -s -- --global --yes --token "$OAC_TOKEN"
|
|
||||||
```
|
|
||||||
|
|
||||||
The installer offers **Local** (`.opencode/` in the current project), **Global** (`~/.config/opencode`) or a **Custom** path, and — when files already exist — lets you **skip**, **overwrite** or **back up & overwrite**. A `--source DIR` flag installs from a local copy; `--dry-run` shows the plan without changing anything.
|
The installer offers **Local** (`.opencode/` in the current project), **Global** (`~/.config/opencode`) or a **Custom** path, and — when files already exist — lets you **skip**, **overwrite** or **back up & overwrite**. A `--source DIR` flag installs from a local copy; `--dry-run` shows the plan without changing anything.
|
||||||
|
|
||||||
|
|
@ -167,10 +167,13 @@ The installer offers **Local** (`.opencode/` in the current project), **Global**
|
||||||
| `--install-dir PATH` | install into an exact path |
|
| `--install-dir PATH` | install into an exact path |
|
||||||
| `--source DIR` | install from a local clone instead of downloading |
|
| `--source DIR` | install from a local clone instead of downloading |
|
||||||
| `--branch REF` | branch/tag to install from (default `main`) |
|
| `--branch REF` | branch/tag to install from (default `main`) |
|
||||||
|
| `--token TOKEN` | Forgejo token — only for a private mirror/fork (not needed here) |
|
||||||
| `--with-opencode` | install/update OpenCode even if it is already present |
|
| `--with-opencode` | install/update OpenCode even if it is already present |
|
||||||
| `--no-opencode` | skip the OpenCode runtime install |
|
| `--no-opencode` | skip the OpenCode runtime install |
|
||||||
| `--opencode-version V` | install a specific OpenCode version |
|
| `--opencode-version V` | install a specific OpenCode version |
|
||||||
| `--opencode-no-path` | don't let the OpenCode installer edit your shell PATH |
|
| `--opencode-no-path` | don't let the OpenCode installer edit your shell PATH |
|
||||||
|
| `--service` | set up the web UI service non-interactively (with defaults) |
|
||||||
|
| `--no-service` | never offer the web UI service |
|
||||||
| `--yes` / `-y` | non-interactive; skip existing files |
|
| `--yes` / `-y` | non-interactive; skip existing files |
|
||||||
| `--force` | non-interactive; overwrite existing files |
|
| `--force` | non-interactive; overwrite existing files |
|
||||||
| `--dry-run` | show what would happen, change nothing |
|
| `--dry-run` | show what would happen, change nothing |
|
||||||
|
|
@ -198,6 +201,40 @@ Then start OpenCode and pick your agent:
|
||||||
opencode # choose OpenArchitect for the controlling tier, OpenAgent to build
|
opencode # choose OpenArchitect for the controlling tier, OpenAgent to build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Web UI service — optional, offered at the end of the install
|
||||||
|
|
||||||
|
At the end of an interactive install the installer asks whether to set up the OpenCode **web UI** as a systemd service (Linux):
|
||||||
|
|
||||||
|
```
|
||||||
|
Set up the OpenCode web UI as a systemd service? [y/N]
|
||||||
|
```
|
||||||
|
|
||||||
|
Answer **y** and it prompts for the bind address, port, working directory and whether to enable it on boot, then shows you the unit and asks for a final confirmation:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=opencode server (web UI)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=<you>
|
||||||
|
Group=<you>
|
||||||
|
WorkingDirectory=<your project>
|
||||||
|
ExecStart=<opencode> serve --hostname 127.0.0.1 --port 4096
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
- **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`.
|
||||||
|
- Run it by hand anytime: `opencode serve --hostname 127.0.0.1 --port 4096` (or `opencode web` to also open a browser).
|
||||||
|
|
||||||
## The context system — your patterns, loaded first
|
## The context system — your patterns, loaded first
|
||||||
|
|
||||||
*(Inherited from upstream OAC — the secret weapon stays.)*
|
*(Inherited from upstream OAC — the secret weapon stays.)*
|
||||||
|
|
|
||||||
126
install.sh
126
install.sh
|
|
@ -51,6 +51,7 @@ PAYLOAD_FILES="env.example package.json"
|
||||||
# OpenCode runtime — the official installer places the binary in $HOME/.opencode/bin
|
# OpenCode runtime — the official installer places the binary in $HOME/.opencode/bin
|
||||||
OPENCODE_INSTALLER_URL="${OPENCODE_INSTALLER_URL:-https://opencode.ai/v2/install}"
|
OPENCODE_INSTALLER_URL="${OPENCODE_INSTALLER_URL:-https://opencode.ai/v2/install}"
|
||||||
OPENCODE_BIN_DIR="${HOME}/.opencode/bin"
|
OPENCODE_BIN_DIR="${HOME}/.opencode/bin"
|
||||||
|
SERVICE_UNIT_NAME="opencode.service"
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Colours (only when writing to a terminal)
|
# Colours (only when writing to a terminal)
|
||||||
|
|
@ -111,6 +112,10 @@ Runtime:
|
||||||
--opencode-version V Install a specific OpenCode version
|
--opencode-version V Install a specific OpenCode version
|
||||||
--opencode-no-path Don't let the OpenCode installer edit your shell PATH
|
--opencode-no-path Don't let the OpenCode installer edit your shell PATH
|
||||||
|
|
||||||
|
Service (offered interactively at the end):
|
||||||
|
--service Set up the web UI service non-interactively (defaults)
|
||||||
|
--no-service Never offer the web UI service
|
||||||
|
|
||||||
Behaviour:
|
Behaviour:
|
||||||
-y, --yes Non-interactive; skip files that already exist
|
-y, --yes Non-interactive; skip files that already exist
|
||||||
--force Non-interactive; overwrite existing files
|
--force Non-interactive; overwrite existing files
|
||||||
|
|
@ -141,6 +146,7 @@ SOURCE=""
|
||||||
OPENCODE_MODE="auto" # auto | yes | no
|
OPENCODE_MODE="auto" # auto | yes | no
|
||||||
OPENCODE_VERSION=""
|
OPENCODE_VERSION=""
|
||||||
OPENCODE_NO_PATH=0
|
OPENCODE_NO_PATH=0
|
||||||
|
SERVICE_MODE="auto" # auto | yes | no
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
|
@ -162,6 +168,8 @@ while [ $# -gt 0 ]; do
|
||||||
--opencode-version) [ $# -ge 2 ] || die "--opencode-version needs a version"; OPENCODE_VERSION="$2"; shift ;;
|
--opencode-version) [ $# -ge 2 ] || die "--opencode-version needs a version"; OPENCODE_VERSION="$2"; shift ;;
|
||||||
--opencode-version=*) OPENCODE_VERSION="${1#*=}" ;;
|
--opencode-version=*) OPENCODE_VERSION="${1#*=}" ;;
|
||||||
--opencode-no-path) OPENCODE_NO_PATH=1 ;;
|
--opencode-no-path) OPENCODE_NO_PATH=1 ;;
|
||||||
|
--service) SERVICE_MODE="yes" ;;
|
||||||
|
--no-service) SERVICE_MODE="no" ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) error "Unknown option: $1"; echo; usage; exit 1 ;;
|
*) error "Unknown option: $1"; echo; usage; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
@ -261,6 +269,117 @@ ensure_opencode() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Web UI service (systemd) — offered interactively
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
is_systemd() {
|
||||||
|
if [ "${OAC_FORCE_SYSTEMD:-}" = "1" ]; then return 0; fi # override for containers/CI
|
||||||
|
if [ "${OAC_FORCE_SYSTEMD:-}" = "0" ]; then return 1; fi
|
||||||
|
command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_service() {
|
||||||
|
[ "$SERVICE_MODE" = "no" ] && { info "Skipping the web UI service (--no-service)"; return 0; }
|
||||||
|
|
||||||
|
have_opencode || { info "OpenCode is not installed — skipping the web UI service."; return 0; }
|
||||||
|
|
||||||
|
local interactive=0
|
||||||
|
if [ "$SERVICE_MODE" = "auto" ]; then
|
||||||
|
if [ -t 0 ] && [ -t 1 ]; then
|
||||||
|
interactive=1
|
||||||
|
else
|
||||||
|
return 0 # non-interactive: only set up the service when explicitly asked
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$interactive" = 1 ]; then
|
||||||
|
printf "\n${BOLD}Set up the OpenCode web UI as a systemd service?${NC} [y/N]: "
|
||||||
|
ans=""; read -r ans || true
|
||||||
|
case "$ans" in [Yy]*) ;; *) info "Skipping the web UI service."; return 0 ;; esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_systemd; then
|
||||||
|
warn "systemd not detected — cannot install a service here."
|
||||||
|
info "Start the web UI manually: $(opencode_path) serve --hostname 127.0.0.1 --port 4096"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local host port enable dir u g oc_bin v
|
||||||
|
oc_bin="$(opencode_path)"
|
||||||
|
host="127.0.0.1"
|
||||||
|
port="4096"
|
||||||
|
enable=1
|
||||||
|
dir="$(pwd)"
|
||||||
|
u="$(id -un)"
|
||||||
|
g="$(id -gn)"
|
||||||
|
|
||||||
|
if [ "$interactive" = 1 ]; then
|
||||||
|
printf " Bind address [%s]: " "$host"; v=""; read -r v || true; [ -n "$v" ] && host="$v"
|
||||||
|
printf " Port [%s]: " "$port"; v=""; read -r v || true; [ -n "$v" ] && port="$v"
|
||||||
|
printf " Working directory [%s]: " "$dir"; v=""; read -r v || true; [ -n "$v" ] && dir="$v"
|
||||||
|
printf " Start now and enable on boot? [Y/n]: "; v=""; read -r v || true
|
||||||
|
case "$v" in [Nn]*) enable=0 ;; esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
local unit
|
||||||
|
unit="$(mktemp 2>/dev/null || mktemp -t oac-unit)"
|
||||||
|
cat > "$unit" <<UNIT
|
||||||
|
[Unit]
|
||||||
|
Description=opencode server (web UI)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=${u}
|
||||||
|
Group=${g}
|
||||||
|
WorkingDirectory=${dir}
|
||||||
|
ExecStart=${oc_bin} serve --hostname ${host} --port ${port}
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
UNIT
|
||||||
|
|
||||||
|
local sudo=""
|
||||||
|
[ "$(id -u)" != "0" ] && sudo="sudo"
|
||||||
|
|
||||||
|
if [ "$interactive" = 1 ]; then
|
||||||
|
echo
|
||||||
|
info "Unit to install:"
|
||||||
|
echo "------------------------------------------------------------"
|
||||||
|
cat "$unit"
|
||||||
|
echo "------------------------------------------------------------"
|
||||||
|
printf "Install to /etc/systemd/system/%s? [y/N]: " "$SERVICE_UNIT_NAME"
|
||||||
|
ans=""; read -r ans || true
|
||||||
|
case "$ans" in
|
||||||
|
[Yy]*) ;;
|
||||||
|
*) rm -f "$unit"; info "Skipping the web UI service."; return 0 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! $sudo cp "$unit" "/etc/systemd/system/${SERVICE_UNIT_NAME}" 2>/dev/null; then
|
||||||
|
rm -f "$unit"
|
||||||
|
warn "Could not write /etc/systemd/system/${SERVICE_UNIT_NAME} (needs root)."
|
||||||
|
info "Re-run with sudo, or copy the unit and run: sudo systemctl enable --now ${SERVICE_UNIT_NAME}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
rm -f "$unit"
|
||||||
|
$sudo systemctl daemon-reload 2>/dev/null || true
|
||||||
|
|
||||||
|
if [ "$enable" = 1 ]; then
|
||||||
|
if $sudo systemctl enable --now "$SERVICE_UNIT_NAME" 2>/dev/null; then
|
||||||
|
success "Web UI service enabled — http://${host}:${port}"
|
||||||
|
else
|
||||||
|
warn "Unit installed; enable it later: sudo systemctl enable --now ${SERVICE_UNIT_NAME}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
success "Unit installed — start it with: sudo systemctl start ${SERVICE_UNIT_NAME}"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Resolve the source payload
|
# Resolve the source payload
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
@ -472,6 +591,7 @@ perform_install() {
|
||||||
[ "$skipped" -gt 0 ] && echo -e " Skipped: ${CYAN}${skipped}${NC}"
|
[ "$skipped" -gt 0 ] && echo -e " Skipped: ${CYAN}${skipped}${NC}"
|
||||||
[ -n "$BACKUP_DIR" ] && echo -e " Backup: ${CYAN}${BACKUP_DIR}${NC}"
|
[ -n "$BACKUP_DIR" ] && echo -e " Backup: ${CYAN}${BACKUP_DIR}${NC}"
|
||||||
fi
|
fi
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
show_post_install() {
|
show_post_install() {
|
||||||
|
|
@ -505,7 +625,11 @@ main() {
|
||||||
[ "$DRY_RUN" = 1 ] && warn "Dry run — no changes will be written"
|
[ "$DRY_RUN" = 1 ] && warn "Dry run — no changes will be written"
|
||||||
|
|
||||||
perform_install
|
perform_install
|
||||||
[ "$DRY_RUN" != 1 ] && show_post_install
|
|
||||||
|
if [ "$DRY_RUN" != 1 ]; then
|
||||||
|
show_post_install
|
||||||
|
setup_service
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main
|
main
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue