diff --git a/README.md b/README.md index e543f11..2b6afe8 100644 --- a/README.md +++ b/README.md @@ -134,11 +134,17 @@ env.example template for the optional .env (Telegram/Gemini integrations) ## 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 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) ``` -### Bootstrap without a clone (private → pass a token) - -```bash -export OAC_TOKEN= -curl -fsSL "https://git.lohmar.co.uk/cclohmar/NewOpenAgents/raw/branch/main/install.sh?token=$OAC_TOKEN" \ - | bash -s -- --global --yes --token "$OAC_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. 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 | | `--source DIR` | install from a local clone instead of downloading | | `--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 | | `--no-opencode` | skip the OpenCode runtime install | | `--opencode-version V` | install a specific OpenCode version | | `--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 | | `--force` | non-interactive; overwrite existing files | | `--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 ``` +## 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= +Group= +WorkingDirectory= +ExecStart= 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 *(Inherited from upstream OAC — the secret weapon stays.)* diff --git a/install.sh b/install.sh index 6cba254..6cf1d2a 100755 --- a/install.sh +++ b/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_INSTALLER_URL="${OPENCODE_INSTALLER_URL:-https://opencode.ai/v2/install}" OPENCODE_BIN_DIR="${HOME}/.opencode/bin" +SERVICE_UNIT_NAME="opencode.service" # --------------------------------------------------------------------------- # Colours (only when writing to a terminal) @@ -111,6 +112,10 @@ Runtime: --opencode-version V Install a specific OpenCode version --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: -y, --yes Non-interactive; skip files that already exist --force Non-interactive; overwrite existing files @@ -141,6 +146,7 @@ SOURCE="" OPENCODE_MODE="auto" # auto | yes | no OPENCODE_VERSION="" OPENCODE_NO_PATH=0 +SERVICE_MODE="auto" # auto | yes | no while [ $# -gt 0 ]; do 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=*) OPENCODE_VERSION="${1#*=}" ;; --opencode-no-path) OPENCODE_NO_PATH=1 ;; + --service) SERVICE_MODE="yes" ;; + --no-service) SERVICE_MODE="no" ;; -h|--help) usage; exit 0 ;; *) error "Unknown option: $1"; echo; usage; exit 1 ;; esac @@ -261,6 +269,117 @@ ensure_opencode() { 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" </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 # --------------------------------------------------------------------------- @@ -472,6 +591,7 @@ perform_install() { [ "$skipped" -gt 0 ] && echo -e " Skipped: ${CYAN}${skipped}${NC}" [ -n "$BACKUP_DIR" ] && echo -e " Backup: ${CYAN}${BACKUP_DIR}${NC}" fi + return 0 } show_post_install() { @@ -505,7 +625,11 @@ main() { [ "$DRY_RUN" = 1 ] && warn "Dry run — no changes will be written" perform_install - [ "$DRY_RUN" != 1 ] && show_post_install + + if [ "$DRY_RUN" != 1 ]; then + show_post_install + setup_service + fi } main