diff --git a/cli/internal/deploy/deployfiles/embedded/docker_compose/README.md b/cli/internal/deploy/deployfiles/embedded/docker_compose/README.md index 5b66da3670..741cec5d10 100644 --- a/cli/internal/deploy/deployfiles/embedded/docker_compose/README.md +++ b/cli/internal/deploy/deployfiles/embedded/docker_compose/README.md @@ -16,49 +16,51 @@ This README focuses on the easiest guided deployment which is via install.sh. curl -fsSL https://raw.githubusercontent.com/onyx-dot-app/onyx/main/deployment/docker_compose/install.sh > install.sh && chmod +x install.sh && ./install.sh ``` +The script installs the Onyx CLI (`onyx-cli`) and hands over to `onyx-cli deploy install`, which is +where the guided installation lives. Any flags you pass are forwarded to it. If you already have the +CLI (`pip install onyx-cli`), skip the script and run `onyx-cli deploy install` directly. + This provides a guided installation of Onyx via Docker Compose. It will deploy the latest version of Onyx and set up the volumes to ensure data is persisted across deployments or upgrades. -The script will create an onyx_data directory, all necessary files for the deployment will be stored in -there. Note that no application critical data is stored in that directory so even if you delete it, the -data needed to restore the app will not be destroyed. +The deployment files are stored in `~/.config/onyx` (an existing `onyx_data` directory from an older +install is detected and kept in place; `--dir` targets another location). Note that no application +critical data is stored in that directory so even if you delete it, the data needed to restore the app +will not be destroyed. The data about chats, users, etc. are instead stored as named Docker Volumes. This is managed by Docker and where it is stored will depend on your Docker setup. You can always delete these as well by running -the install.sh script with --delete-data. +`onyx-cli deploy uninstall`. -To shut down the deployment without deleting, use install.sh --shutdown. +To shut down the deployment without deleting, use `onyx-cli deploy stop`. -### Onyx CLI (alternative) +### Managing the deployment -The same guided install also ships inside the Onyx CLI and will eventually -replace install.sh once it stabilizes: +Beyond installing, the CLI covers the rest of the lifecycle: -``` -pip install onyx-cli && onyx-cli deploy install -``` - -It understands existing install.sh deployments (an `onyx_data` directory is -detected and managed in place; new installs default to `~/.config/onyx`) and -adds lifecycle commands: `onyx-cli deploy status` (versions, containers, -health), `deploy stop`, `deploy upgrade [--tag vX.Y.Z]` (rewrites only -IMAGE_TAG, preserves your .env edits, backs up hand-edited files), and -`deploy uninstall`. +| Command | What it does | +| --- | --- | +| `onyx-cli deploy status` | Installed version, containers, and health (`--json` for scripts) | +| `onyx-cli deploy logs [service...]` | Logs of the deployment's containers | +| `onyx-cli deploy stop` | Stop the containers, keep the data | +| `onyx-cli deploy upgrade [--tag vX.Y.Z]` | Upgrade in place (see below) | +| `onyx-cli deploy uninstall` | Remove the containers, volumes, and deployment directory | ### Upgrading the deployment -Onyx maintains backwards compatibility across all minor versions following SemVer. If following the install.sh script (or through Docker Compose), you can -upgrade it by first bringing down the containers. To do this, use `install.sh --shutdown` -(or `docker compose down` from the directory with the docker-compose.yml file). +Onyx maintains backwards compatibility across all minor versions following SemVer, so upgrading is +`onyx-cli deploy upgrade` (add `--tag vX.Y.Z` to pin a version). It rewrites only IMAGE_TAG, preserves +your .env edits, and backs up hand-edited files. -After the containers are stopped, you can safely upgrade by either re-running the `install.sh` script (if you left the values as default which is latest, -then it will automatically update to latest each time the script is run). If you are more comfortable running docker compose commands, you can also run -commands directly from the directory with the docker-compose.yml file. First verify the version you want in the environment file (see below), -(if using `latest` tag, be sure to run `docker compose pull`) and run `docker compose up` to restart the services on the latest version +If you are more comfortable running docker compose commands, you can also run commands directly from +the directory with the docker-compose.yml file. First bring the containers down (`docker compose down`), +verify the version you want in the environment file (see below), (if using `latest` tag, be sure to run +`docker compose pull`) and run `docker compose up` to restart the services on the latest version ### Environment variables -The Docker Compose files try to look for a .env file in the same directory. The `install.sh` script sets it up from a file called env.template which is -downloaded during the initial setup. Feel free to edit the .env file to customize your deployment. The most important / common changed values are -located near the top of the file. +The Docker Compose files try to look for a .env file in the same directory. The installer sets it up +from a file called env.template. Feel free to edit the .env file to customize your deployment. The most +important / common changed values are located near the top of the file. Later `onyx-cli deploy` runs +keep your edits. IMAGE_TAG is the version of Onyx to run. It is recommended to leave it as latest to get all updates with each redeployment. @@ -67,26 +69,3 @@ Every image publishes a `-dev` twin for each of its tags (e.g. `latest-dev`, `v1 its `-dev` twin adds interactive debugging tools (vim, nano, curl, ps, psql) that the default image leaves out to stay minimal. The web-server, model-server, and sandbox `-dev` tags are identical to their plain counterparts and exist so that one version string covers every image. - -## Maintaining the compose files (contributors) - -`docker-compose.yml`, `docker-compose.prod.yml` and `docker-compose.prod-no-letsencrypt.yml` are -generated from the single source of truth `docker-compose.template.yml` — do not hand-edit them. -To change any of the three, edit the template (per-variant differences are expressed with `#!for` / -`#!only` / `#!value` directives, documented in `ods generate-compose --help`) and regenerate: - -``` -ods generate-compose --write -``` - -The generator lives in `tools/ods` (`internal/composegen`) and ships with the `onyx-devtools` -package. The `docker-compose-sync` pre-commit hook runs it automatically for commits touching the -template or the generated files, so a stray edit to a generated file gets reverted on the next -commit. - -onyx-cli embeds copies of the guided-install deployment files (the generated -`docker-compose.yml` and `docker-compose.prod.yml`, the lite/craft overlays, the env templates, -the nginx config, and this README) under `cli/internal/deploy/deployfiles/embedded/`. The same `ods generate-compose --write` run -refreshes them after rendering the variants, and a drift test in the cli module (`go test ./...`) -gates staleness. If you change any of those files, re-run the generator and commit the refreshed -embedded copies. diff --git a/deployment/README.md b/deployment/README.md index 5c5bcdd948..9192c2b925 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -1,2 +1,30 @@ Documentation for how to deploy Onyx can be found in our official docs: https://docs.onyx.app/deployment/overview + +## Maintaining the compose files (contributors) + +`docker_compose/docker-compose.yml`, `docker-compose.prod.yml` and +`docker-compose.prod-no-letsencrypt.yml` are generated from the single source of truth +`docker_compose/docker-compose.template.yml` — do not hand-edit them. To change any of the three, +edit the template (per-variant differences are expressed with `#!for` / `#!only` / `#!value` +directives, documented in `ods generate-compose --help`) and regenerate: + +``` +ods generate-compose --write +``` + +The generator lives in `tools/ods` (`internal/composegen`) and ships with the `onyx-devtools` +package. The `docker-compose-sync` pre-commit hook runs it automatically for commits touching the +template or the generated files, so a stray edit to a generated file gets reverted on the next +commit. + +onyx-cli embeds copies of the guided-install deployment files (the generated +`docker-compose.yml` and `docker-compose.prod.yml`, the lite/craft overlays, the env templates, +the nginx config, and `docker_compose/README.md`) under +`cli/internal/deploy/deployfiles/embedded/`. The same `ods generate-compose --write` run refreshes +them after rendering the variants, and a drift test in the cli module (`go test ./...`) gates +staleness. If you change any of those files, re-run the generator and commit the refreshed embedded +copies. + +Note that `docker_compose/README.md` ships inside every self-hosted install, so keep it +user-facing — contributor notes like this one belong here instead. diff --git a/deployment/docker_compose/README.md b/deployment/docker_compose/README.md index 5b66da3670..741cec5d10 100644 --- a/deployment/docker_compose/README.md +++ b/deployment/docker_compose/README.md @@ -16,49 +16,51 @@ This README focuses on the easiest guided deployment which is via install.sh. curl -fsSL https://raw.githubusercontent.com/onyx-dot-app/onyx/main/deployment/docker_compose/install.sh > install.sh && chmod +x install.sh && ./install.sh ``` +The script installs the Onyx CLI (`onyx-cli`) and hands over to `onyx-cli deploy install`, which is +where the guided installation lives. Any flags you pass are forwarded to it. If you already have the +CLI (`pip install onyx-cli`), skip the script and run `onyx-cli deploy install` directly. + This provides a guided installation of Onyx via Docker Compose. It will deploy the latest version of Onyx and set up the volumes to ensure data is persisted across deployments or upgrades. -The script will create an onyx_data directory, all necessary files for the deployment will be stored in -there. Note that no application critical data is stored in that directory so even if you delete it, the -data needed to restore the app will not be destroyed. +The deployment files are stored in `~/.config/onyx` (an existing `onyx_data` directory from an older +install is detected and kept in place; `--dir` targets another location). Note that no application +critical data is stored in that directory so even if you delete it, the data needed to restore the app +will not be destroyed. The data about chats, users, etc. are instead stored as named Docker Volumes. This is managed by Docker and where it is stored will depend on your Docker setup. You can always delete these as well by running -the install.sh script with --delete-data. +`onyx-cli deploy uninstall`. -To shut down the deployment without deleting, use install.sh --shutdown. +To shut down the deployment without deleting, use `onyx-cli deploy stop`. -### Onyx CLI (alternative) +### Managing the deployment -The same guided install also ships inside the Onyx CLI and will eventually -replace install.sh once it stabilizes: +Beyond installing, the CLI covers the rest of the lifecycle: -``` -pip install onyx-cli && onyx-cli deploy install -``` - -It understands existing install.sh deployments (an `onyx_data` directory is -detected and managed in place; new installs default to `~/.config/onyx`) and -adds lifecycle commands: `onyx-cli deploy status` (versions, containers, -health), `deploy stop`, `deploy upgrade [--tag vX.Y.Z]` (rewrites only -IMAGE_TAG, preserves your .env edits, backs up hand-edited files), and -`deploy uninstall`. +| Command | What it does | +| --- | --- | +| `onyx-cli deploy status` | Installed version, containers, and health (`--json` for scripts) | +| `onyx-cli deploy logs [service...]` | Logs of the deployment's containers | +| `onyx-cli deploy stop` | Stop the containers, keep the data | +| `onyx-cli deploy upgrade [--tag vX.Y.Z]` | Upgrade in place (see below) | +| `onyx-cli deploy uninstall` | Remove the containers, volumes, and deployment directory | ### Upgrading the deployment -Onyx maintains backwards compatibility across all minor versions following SemVer. If following the install.sh script (or through Docker Compose), you can -upgrade it by first bringing down the containers. To do this, use `install.sh --shutdown` -(or `docker compose down` from the directory with the docker-compose.yml file). +Onyx maintains backwards compatibility across all minor versions following SemVer, so upgrading is +`onyx-cli deploy upgrade` (add `--tag vX.Y.Z` to pin a version). It rewrites only IMAGE_TAG, preserves +your .env edits, and backs up hand-edited files. -After the containers are stopped, you can safely upgrade by either re-running the `install.sh` script (if you left the values as default which is latest, -then it will automatically update to latest each time the script is run). If you are more comfortable running docker compose commands, you can also run -commands directly from the directory with the docker-compose.yml file. First verify the version you want in the environment file (see below), -(if using `latest` tag, be sure to run `docker compose pull`) and run `docker compose up` to restart the services on the latest version +If you are more comfortable running docker compose commands, you can also run commands directly from +the directory with the docker-compose.yml file. First bring the containers down (`docker compose down`), +verify the version you want in the environment file (see below), (if using `latest` tag, be sure to run +`docker compose pull`) and run `docker compose up` to restart the services on the latest version ### Environment variables -The Docker Compose files try to look for a .env file in the same directory. The `install.sh` script sets it up from a file called env.template which is -downloaded during the initial setup. Feel free to edit the .env file to customize your deployment. The most important / common changed values are -located near the top of the file. +The Docker Compose files try to look for a .env file in the same directory. The installer sets it up +from a file called env.template. Feel free to edit the .env file to customize your deployment. The most +important / common changed values are located near the top of the file. Later `onyx-cli deploy` runs +keep your edits. IMAGE_TAG is the version of Onyx to run. It is recommended to leave it as latest to get all updates with each redeployment. @@ -67,26 +69,3 @@ Every image publishes a `-dev` twin for each of its tags (e.g. `latest-dev`, `v1 its `-dev` twin adds interactive debugging tools (vim, nano, curl, ps, psql) that the default image leaves out to stay minimal. The web-server, model-server, and sandbox `-dev` tags are identical to their plain counterparts and exist so that one version string covers every image. - -## Maintaining the compose files (contributors) - -`docker-compose.yml`, `docker-compose.prod.yml` and `docker-compose.prod-no-letsencrypt.yml` are -generated from the single source of truth `docker-compose.template.yml` — do not hand-edit them. -To change any of the three, edit the template (per-variant differences are expressed with `#!for` / -`#!only` / `#!value` directives, documented in `ods generate-compose --help`) and regenerate: - -``` -ods generate-compose --write -``` - -The generator lives in `tools/ods` (`internal/composegen`) and ships with the `onyx-devtools` -package. The `docker-compose-sync` pre-commit hook runs it automatically for commits touching the -template or the generated files, so a stray edit to a generated file gets reverted on the next -commit. - -onyx-cli embeds copies of the guided-install deployment files (the generated -`docker-compose.yml` and `docker-compose.prod.yml`, the lite/craft overlays, the env templates, -the nginx config, and this README) under `cli/internal/deploy/deployfiles/embedded/`. The same `ods generate-compose --write` run -refreshes them after rendering the variants, and a drift test in the cli module (`go test ./...`) -gates staleness. If you change any of those files, re-run the generator and commit the refreshed -embedded copies. diff --git a/deployment/docker_compose/install.sh b/deployment/docker_compose/install.sh index a1020a4a87..4f0ddf1def 100755 --- a/deployment/docker_compose/install.sh +++ b/deployment/docker_compose/install.sh @@ -1,160 +1,110 @@ #!/bin/bash +# Onyx installer — a bootstrap for `onyx-cli deploy install`. +# +# The guided install itself lives in the Onyx CLI (Go). This script only +# installs that CLI and hands over; every argument is forwarded untouched. +# +# curl -fsSL https://raw.githubusercontent.com/onyx-dot-app/onyx/main/deployment/docker_compose/install.sh | bash +# curl -fsSL https://raw.githubusercontent.com/onyx-dot-app/onyx/main/deployment/docker_compose/install.sh | bash -s -- --lite --no-prompt +# +# Environment: +# ONYX_CLI_VERSION onyx-cli release to install (e.g. v1.3.1). Defaults to +# the newest published release. +# ONYX_CLI_BIN_DIR where the binary is installed. Defaults to +# /usr/local/bin when running as root, else ~/.local/bin. +# # This script must remain compatible with bash 3.2 — macOS still ships # 3.2.57 by default and the curl-pipe installer is invoked with /bin/bash. -# Avoid bash 4+ features (associative arrays, ${var,,}, etc.). Notably, -# do not re-enable `set -u`: on bash 3.2, `"${arr[@]}"` errors as -# "unbound variable" when arr is an explicitly-declared empty array. +# Avoid bash 4+ features (associative arrays, ${var,,}, etc.). set -eo pipefail -# Expected resource requirements (overridden below if --lite) -EXPECTED_DOCKER_RAM_GB=10 -EXPECTED_DISK_GB=32 +REPO="onyx-dot-app/onyx" +RELEASES_URL="https://github.com/${REPO}/releases" +# Rolling release that always carries the newest CLI under version-less asset +# names. The repo-global releases/latest alias can't be used: it resolves to +# the Onyx app releases, not the CLI ones. +LATEST_TAG="cli-latest" -# Parse command line arguments -SHUTDOWN_MODE=false -DELETE_DATA_MODE=false -INCLUDE_CRAFT=false # Disabled by default, use --include-craft to enable -LITE_MODE=false # Disabled by default, use --lite to enable -USE_LOCAL_FILES=false # Disabled by default, use --local to skip downloading config files -NO_PROMPT=false -DRY_RUN=false -VERBOSE=false -NO_WAIT=false # When false (default), pass --wait to `docker compose up` -WAIT_TIMEOUT_SECONDS=600 +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BOLD='\033[1m' +NC='\033[0m' # No Color -while [[ $# -gt 0 ]]; do - case $1 in - --shutdown) - SHUTDOWN_MODE=true - shift - ;; - --delete-data) - DELETE_DATA_MODE=true - shift - ;; - --include-craft) - INCLUDE_CRAFT=true - shift - ;; - --lite) - LITE_MODE=true - shift - ;; - --local) - USE_LOCAL_FILES=true - shift - ;; - --no-prompt) - NO_PROMPT=true - shift - ;; - --dry-run) - DRY_RUN=true - shift - ;; - --verbose) - VERBOSE=true - shift - ;; - --no-wait) - NO_WAIT=true - shift - ;; +print_success() { + echo -e "${GREEN}✓${NC} $1" +} + +print_error() { + echo -e "${RED}✗${NC} $1" >&2 +} + +print_info() { + echo -e "${YELLOW}ℹ${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}⚠${NC} $1" +} + +show_help() { + echo "Onyx Installation Script" + echo "" + echo "Installs the Onyx CLI and runs its guided deployment:" + echo " onyx-cli deploy install [OPTIONS]" + echo "" + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Common options (forwarded to the CLI):" + echo " --lite Deploy Onyx Lite (no OpenSearch, Redis, or model servers)" + echo " --include-craft Enable Onyx Craft (AI-powered web app building)" + echo " --tag Image tag to deploy (default: the latest Onyx release)" + echo " --local Use existing config files instead of downloading them" + echo " --no-prompt Run non-interactively with defaults (for CI/automation)" + echo " --dry-run Show what would be done without making changes" + echo " --verbose Show detailed output for debugging" + echo " --no-wait Return as soon as containers are started" + echo " --dir Deployment directory (default: ~/.config/onyx, or an" + echo " existing ./onyx_data)" + echo " --help, -h Show this help message" + echo "" + echo "Run 'onyx-cli deploy install --help' for the full list." + echo "" + echo "Managing an existing deployment (these replace the retired" + echo "--shutdown / --delete-data flags):" + echo " onyx-cli deploy status Versions, containers, and health" + echo " onyx-cli deploy logs Logs of the deployment's containers" + echo " onyx-cli deploy stop Stop the containers, keep the data" + echo " onyx-cli deploy upgrade Upgrade to a newer version" + echo " onyx-cli deploy uninstall Remove the deployment and all its data" + echo "" + echo "Environment:" + echo " ONYX_CLI_VERSION onyx-cli release to install (default: newest)" + echo " ONYX_CLI_BIN_DIR install location (default: ~/.local/bin, or" + echo " /usr/local/bin as root)" +} + +for arg in "$@"; do + case "$arg" in --help|-h) - echo "Onyx Installation Script" - echo "" - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Options:" - echo " --include-craft Enable Onyx Craft (AI-powered web app building)" - echo " --lite Deploy Onyx Lite (no Vespa, Redis, or model servers)" - echo " --local Use existing config files instead of downloading from GitHub" - echo " --shutdown Stop (pause) Onyx containers" - echo " --delete-data Remove all Onyx data (containers, volumes, and files)" - echo " --no-prompt Run non-interactively with defaults (for CI/automation)" - echo " --dry-run Show what would be done without making changes" - echo " --verbose Show detailed output for debugging" - echo " --no-wait Skip 'docker compose up --wait'; return as soon as" - echo " containers are started (they may still be initializing)" - echo " --help, -h Show this help message" - echo "" - echo "Examples:" - echo " $0 # Install Onyx" - echo " $0 --lite # Install Onyx Lite (minimal deployment)" - echo " $0 --include-craft # Install Onyx with Craft enabled" - echo " $0 --shutdown # Pause Onyx services" - echo " $0 --delete-data # Completely remove Onyx and all data" - echo " $0 --local # Re-run using existing config files on disk" - echo " $0 --no-prompt # Non-interactive install with defaults" + show_help exit 0 ;; - *) - echo "Unknown option: $1" - echo "Use --help for usage information" - exit 1 - ;; esac done -if [[ "$VERBOSE" = true ]]; then - set -x -fi - -if [[ "$LITE_MODE" = true ]] && [[ "$INCLUDE_CRAFT" = true ]]; then - echo "ERROR: --lite and --include-craft cannot be used together." - echo "Craft requires services (Vespa, Redis, background workers) that lite mode disables." - exit 1 -fi - -# When --lite is passed as a flag, lower resource thresholds early (before the -# resource check). When lite is chosen interactively, the thresholds are adjusted -# after the resource check has already passed with the standard thresholds — -# which is the safer direction. -if [[ "$LITE_MODE" = true ]]; then - EXPECTED_DOCKER_RAM_GB=4 - EXPECTED_DISK_GB=16 -fi - -INSTALL_ROOT="${INSTALL_PREFIX:-onyx_data}" - -LITE_COMPOSE_FILE="docker-compose.onyx-lite.yml" -CRAFT_COMPOSE_FILE="docker-compose.craft.yml" - -# Populate COMPOSE_FILE_ARGS with the -f flags for docker compose. -# Pass "true" as $1 to auto-detect previously-downloaded overlays (used by -# shutdown/delete-data so users don't need to remember --lite/--include-craft). -COMPOSE_FILE_ARGS=() -build_compose_file_args() { - local auto_detect="${1:-false}" - COMPOSE_FILE_ARGS=(-f docker-compose.yml) - if [[ "$LITE_MODE" = true ]] || { [[ "$auto_detect" = true ]] && [[ -f "${INSTALL_ROOT}/deployment/${LITE_COMPOSE_FILE}" ]]; }; then - COMPOSE_FILE_ARGS+=(-f "${LITE_COMPOSE_FILE}") - fi - # Craft Docker sandbox backend is opt-in (--include-craft) so the host - # Docker socket isn't bind-mounted on default deployments. Layer the - # craft overlay on top when explicitly enabled, or auto-detect a - # previously-downloaded overlay on shutdown/delete-data. - if [[ "$INCLUDE_CRAFT" = true ]] || { [[ "$auto_detect" = true ]] && [[ -f "${INSTALL_ROOT}/deployment/${CRAFT_COMPOSE_FILE}" ]]; }; then - COMPOSE_FILE_ARGS+=(-f "${CRAFT_COMPOSE_FILE}") - fi -} - # --- Downloader detection (curl with wget fallback) --- DOWNLOADER="" -detect_downloader() { - if command -v curl &> /dev/null; then - DOWNLOADER="curl" - return 0 - fi - if command -v wget &> /dev/null; then - DOWNLOADER="wget" - return 0 - fi - echo "ERROR: Neither curl nor wget found. Please install one and retry." +if command -v curl &> /dev/null; then + DOWNLOADER="curl" +elif command -v wget &> /dev/null; then + DOWNLOADER="wget" +else + print_error "Neither curl nor wget found. Please install one and retry." exit 1 -} -detect_downloader +fi download_file() { local url="$1" @@ -166,1223 +116,183 @@ download_file() { fi } -# Fetches the most recent published release tag from the Onyx GitHub repo. -# Used to default the installer to a pinned, tested release (instead of the -# rolling "edge" tag) and to pull compose/nginx files that match it. -LATEST_RELEASE_TAG="" -fetch_latest_release_tag() { - local api_url="https://api.github.com/repos/onyx-dot-app/onyx/releases/latest" - local response="" - if [[ "$DOWNLOADER" == "curl" ]]; then - response=$(curl -fsSL --retry 2 --retry-delay 1 \ - --connect-timeout 5 --max-time 10 \ - -H "Accept: application/vnd.github+json" \ - "$api_url" 2>/dev/null) || return 1 +# --- Platform detection --- +OS="$(uname -s)" +ARCH="$(uname -m)" + +case "$OS" in + Linux) + OS="linux" + ;; + Darwin) + OS="darwin" + ;; + MINGW*|MSYS*|CYGWIN*|Windows_NT) + print_error "This script does not support Windows shells." + echo " Use the PowerShell installer instead:" >&2 + echo " irm https://raw.githubusercontent.com/${REPO}/main/deployment/docker_compose/install.ps1 | iex" >&2 + exit 1 + ;; + *) + print_error "Unsupported operating system: ${OS}" + echo " Onyx supports Linux and macOS. See https://docs.onyx.app/deployment/overview" >&2 + exit 1 + ;; +esac + +case "$ARCH" in + x86_64|amd64) + ARCH="amd64" + ;; + aarch64|arm64) + ARCH="arm64" + ;; + *) + print_error "Unsupported architecture: ${ARCH}" + echo " Prebuilt onyx-cli binaries exist for amd64 and arm64 only." >&2 + echo " Install from source or PyPI instead: pip install onyx-cli" >&2 + exit 1 + ;; +esac + +# --- Resolve the release to download --- +# Pinned versions live under their own cli/vX.Y.Z tag with versioned asset +# names; the default rolling release drops the version from both. +if [[ -n "$ONYX_CLI_VERSION" ]]; then + CLI_VERSION="${ONYX_CLI_VERSION#v}" + ARCHIVE_NAME="onyx-cli_${CLI_VERSION}_${OS}_${ARCH}.tar.gz" + CHECKSUMS_NAME="onyx-cli_${CLI_VERSION}_checksums.txt" + DOWNLOAD_BASE="${RELEASES_URL}/download/cli/v${CLI_VERSION}" + VERSION_LABEL="v${CLI_VERSION}" +else + ARCHIVE_NAME="onyx-cli_${OS}_${ARCH}.tar.gz" + CHECKSUMS_NAME="onyx-cli_checksums.txt" + DOWNLOAD_BASE="${RELEASES_URL}/download/${LATEST_TAG}" + VERSION_LABEL="latest" +fi + +# --- Resolve where the binary goes --- +if [[ -n "$ONYX_CLI_BIN_DIR" ]]; then + BIN_DIR="$ONYX_CLI_BIN_DIR" +elif [[ "$(id -u)" -eq 0 ]]; then + BIN_DIR="/usr/local/bin" +else + BIN_DIR="${HOME}/.local/bin" +fi +BIN_PATH="${BIN_DIR}/onyx-cli" + +TMP_DIR="" +cleanup() { + if [[ -n "$TMP_DIR" ]]; then + rm -rf "$TMP_DIR" + TMP_DIR="" + fi +} +trap cleanup EXIT + +TMP_DIR="$(mktemp -d)" + +print_info "Installing onyx-cli (${VERSION_LABEL}, ${OS}/${ARCH})..." + +if ! download_file "${DOWNLOAD_BASE}/${ARCHIVE_NAME}" "${TMP_DIR}/${ARCHIVE_NAME}"; then + print_error "Failed to download ${ARCHIVE_NAME}" + if [[ -n "$ONYX_CLI_VERSION" ]]; then + echo " Check that ${VERSION_LABEL} exists: ${RELEASES_URL}?q=cli%2Fv" >&2 else - response=$(wget -q --tries=2 --timeout=10 \ - --header="Accept: application/vnd.github+json" \ - -O- "$api_url" 2>/dev/null) || return 1 + echo " Please ensure you have internet connection and try again." >&2 fi - local tag - tag=$(echo "$response" \ - | grep -oE '"tag_name"[[:space:]]*:[[:space:]]*"[^"]+"' \ - | head -1 \ - | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') - if [[ -z "$tag" ]]; then - return 1 - fi - LATEST_RELEASE_TAG="$tag" - return 0 -} + exit 1 +fi -# Craft sandbox backend for an image tag. The docker backend exists only in -# v4.0.6+; older pinned tags get "kubernetes" (rolling tags track newest). -sandbox_backend_for_tag() { - local ver="${1#v}"; ver="${ver%%-*}" - printf '%s' "$ver" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$' || { echo docker; return; } - local major="${ver%%.*}" rest="${ver#*.}" - local minor="${rest%%.*}" patch="${rest#*.}" - if [ "$major" -gt 4 ] || - { [ "$major" -eq 4 ] && [ "$minor" -gt 0 ]; } || - { [ "$major" -eq 4 ] && [ "$minor" -eq 0 ] && [ "$patch" -ge 6 ]; }; then - echo docker - else - echo kubernetes - fi -} +# --- Verify the download --- +# The checksums file covers every asset in the release; the archive's own line +# is the one that matters. Every failure here is fatal: the archive is about to +# be executed, so an unverified one is never installed. +verify_checksum() { + local archive="${TMP_DIR}/${ARCHIVE_NAME}" + local sums="${TMP_DIR}/${CHECKSUMS_NAME}" + local expected actual -# Write SANDBOX_BACKEND to .env for image tag $1; sets SANDBOX_BACKEND_VALUE. -set_sandbox_backend_for_tag() { - SANDBOX_BACKEND_VALUE="$(sandbox_backend_for_tag "$1")" - if grep -q "^#* *SANDBOX_BACKEND=" "$ENV_FILE"; then - sed -i.bak "s/^#* *SANDBOX_BACKEND=.*/SANDBOX_BACKEND=$SANDBOX_BACKEND_VALUE/" "$ENV_FILE" - else - echo "SANDBOX_BACKEND=$SANDBOX_BACKEND_VALUE" >> "$ENV_FILE" - fi -} - -# --- Docker Compose detection --- -# Sets COMPOSE_CMD to "docker compose" (plugin) or "docker-compose" (standalone). -# Returns 0 if either is available, 1 otherwise. Safe to re-run after installing -# compose mid-script. -COMPOSE_CMD="" -detect_compose_cmd() { - if docker compose version &> /dev/null; then - COMPOSE_CMD="docker compose" - return 0 - fi - if command -v docker-compose &> /dev/null; then - COMPOSE_CMD="docker-compose" - return 0 - fi - COMPOSE_CMD="" - return 1 -} -detect_compose_cmd || true - -# --- Docker daemon access detection --- -# On Linux, a non-root user can only talk to /var/run/docker.sock when they're -# in the "docker" group. Group membership doesn't apply to the current shell, -# so when we add the user to the group mid-script we instead prefix subsequent -# docker commands with sudo to finish this run. Future runs (after the user -# logs out and back in) won't need it. -# -# Trailing ``env`` anchors the splat with a literal command so bash 3.2's -# single-pass parser doesn't misclassify the inline ``VAR=val`` prefixes, and -# so sudo's ``env_reset`` can't strip them (they're positional args to ``env``). -DOCKER_SUDO=(env) -refresh_docker_sudo() { - DOCKER_SUDO=(env) - if ! command -v docker &> /dev/null; then - return - fi - if [[ "$(id -u)" -eq 0 ]]; then - return - fi - if docker info &> /dev/null; then - return - fi - if [[ "$OSTYPE" == "linux-gnu"* ]] || [[ -n "${WSL_DISTRO_NAME:-}" ]]; then - DOCKER_SUDO=(sudo env) - fi -} -refresh_docker_sudo - -# 0 iff DOCKER_SUDO runs commands under sudo. Wrapped so callers don't -# open-code the [0]-element check (the ``env``-anchor workaround above -# precludes the cleaner `${#DOCKER_SUDO[@]} > 0` form). -is_using_sudo() { - [[ "${DOCKER_SUDO[0]}" == "sudo" ]] -} - -# Ensures a required file is present. With --local, verifies the file exists on -# disk. Otherwise, downloads it from the given URL. Returns 0 on success, 1 on -# failure (caller should handle the exit). -ensure_file() { - local path="$1" - local url="$2" - local desc="$3" - - if [[ "$USE_LOCAL_FILES" = true ]]; then - if [[ -f "$path" ]]; then - print_success "Using existing ${desc}" - return 0 - fi - print_error "Required file missing: ${desc} (${path})" + if ! download_file "${DOWNLOAD_BASE}/${CHECKSUMS_NAME}" "$sums"; then + print_error "Failed to download ${CHECKSUMS_NAME}" + echo " The downloaded binary cannot be verified, so it will not be installed." >&2 + echo " Check your internet connection and try again." >&2 return 1 fi - print_info "Downloading ${desc}..." - if download_file "$url" "$path" 2>/dev/null; then - print_success "${desc} downloaded" - return 0 + expected="$(awk -v name="$ARCHIVE_NAME" '$2 == name { print $1; exit }' "$sums")" + if [[ -z "$expected" ]]; then + print_error "No checksum listed for ${ARCHIVE_NAME} in ${CHECKSUMS_NAME}" + echo " The downloaded binary cannot be verified, so it will not be installed." >&2 + echo " Please report this at ${RELEASES_URL%/releases}/issues" >&2 + return 1 fi - print_error "Failed to download ${desc}" - print_info "Please ensure you have internet connection and try again" - return 1 -} -# --- Interactive prompt helpers --- -is_interactive() { - [[ "$NO_PROMPT" = false ]] && [[ -r /dev/tty ]] && [[ -w /dev/tty ]] -} - -read_prompt_line() { - local prompt_text="$1" - if ! is_interactive; then - REPLY="" - return + if command -v sha256sum &> /dev/null; then + actual="$(sha256sum "$archive" | awk '{print $1}')" + elif command -v shasum &> /dev/null; then + actual="$(shasum -a 256 "$archive" | awk '{print $1}')" + elif command -v openssl &> /dev/null; then + actual="$(openssl dgst -sha256 "$archive" | awk '{print $NF}')" + else + print_error "No sha256 tool found (sha256sum, shasum, or openssl)" + echo " The downloaded binary cannot be verified, so it will not be installed." >&2 + echo " Install one of those tools and try again." >&2 + return 1 fi - [[ -n "$prompt_text" ]] && printf "%s" "$prompt_text" > /dev/tty - IFS= read -r REPLY < /dev/tty || REPLY="" -} -read_prompt_char() { - local prompt_text="$1" - if ! is_interactive; then - REPLY="" - return - fi - [[ -n "$prompt_text" ]] && printf "%s" "$prompt_text" > /dev/tty - IFS= read -r -n 1 REPLY < /dev/tty || REPLY="" - printf "\n" > /dev/tty -} - -prompt_or_default() { - local prompt_text="$1" - local default_value="$2" - read_prompt_line "$prompt_text" - [[ -z "$REPLY" ]] && REPLY="$default_value" - return 0 -} - -prompt_yn_or_default() { - local prompt_text="$1" - local default_value="$2" - read_prompt_char "$prompt_text" - [[ -z "$REPLY" ]] && REPLY="$default_value" - return 0 -} - -confirm_action() { - local description="$1" - prompt_yn_or_default "Install ${description}? (Y/n) [default: Y] " "Y" - if [[ "$REPLY" =~ ^[Nn] ]]; then - print_warning "Skipping: ${description}" + if [[ "$actual" != "$expected" ]]; then + print_error "Checksum mismatch for ${ARCHIVE_NAME}" + echo " expected: ${expected}" >&2 + echo " actual: ${actual}" >&2 return 1 fi return 0 } -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -BOLD='\033[1m' -NC='\033[0m' # No Color +verify_checksum || exit 1 -# Step counter variables -CURRENT_STEP=0 -TOTAL_STEPS=9 - -# Print colored output -print_success() { - echo -e "${GREEN}✓${NC} $1" -} - -print_error() { - echo -e "${RED}✗${NC} $1" -} - -print_info() { - echo -e "${YELLOW}ℹ${NC} $1" -} - -print_step() { - CURRENT_STEP=$((CURRENT_STEP + 1)) - echo "" - echo -e "${BLUE}${BOLD}=== $1 - Step ${CURRENT_STEP}/${TOTAL_STEPS} ===${NC}" - echo "" -} - -print_warning() { - echo -e "${YELLOW}⚠${NC} $1" -} - -# Handle shutdown mode -if [ "$SHUTDOWN_MODE" = true ]; then - echo "" - echo -e "${BLUE}${BOLD}=== Shutting down Onyx ===${NC}" - echo "" - - if [ -d "${INSTALL_ROOT}/deployment" ]; then - print_info "Stopping Onyx containers..." - - # Check if docker-compose.yml exists - if [ -f "${INSTALL_ROOT}/deployment/docker-compose.yml" ]; then - if [[ -z "$COMPOSE_CMD" ]]; then - print_error "Docker Compose not found. Cannot stop containers." - exit 1 - fi - - # Stop containers (without removing them) - build_compose_file_args true - # shellcheck disable=SC2086 # COMPOSE_CMD is "docker compose" and needs word-splitting - if (cd "${INSTALL_ROOT}/deployment" && ${DOCKER_SUDO[@]+"${DOCKER_SUDO[@]}"} HOST_PORT="${HOST_PORT:-3000}" IMAGE_TAG="${IMAGE_TAG:-edge}" $COMPOSE_CMD "${COMPOSE_FILE_ARGS[@]}" stop); then - print_success "Onyx containers stopped (paused)" - else - print_error "Failed to stop containers" - exit 1 - fi - else - print_warning "docker-compose.yml not found in ${INSTALL_ROOT}/deployment" - fi - else - print_warning "Onyx data directory not found. Nothing to shutdown." - fi - - echo "" - print_success "Onyx shutdown complete!" - exit 0 -fi - -# Handle delete data mode -if [ "$DELETE_DATA_MODE" = true ]; then - echo "" - echo -e "${RED}${BOLD}=== WARNING: This will permanently delete all Onyx data ===${NC}" - echo "" - print_warning "This action will remove:" - echo " • All Onyx containers and volumes" - echo " • All downloaded files and configurations" - echo " • All user data and documents" - echo "" - if is_interactive; then - prompt_or_default "Are you sure you want to continue? Type 'DELETE' to confirm: " "" - echo "" > /dev/tty - if [ "$REPLY" != "DELETE" ]; then - print_info "Operation cancelled." - exit 0 - fi - else - print_error "Cannot confirm destructive operation in non-interactive mode." - print_info "Run interactively or remove the ${INSTALL_ROOT} directory manually." - exit 1 - fi - - print_info "Removing Onyx containers and volumes..." - - if [ -d "${INSTALL_ROOT}/deployment" ]; then - # Check if docker-compose.yml exists - if [ -f "${INSTALL_ROOT}/deployment/docker-compose.yml" ]; then - if [[ -z "$COMPOSE_CMD" ]]; then - print_error "Docker Compose not found. Cannot remove containers." - exit 1 - fi - - # Stop and remove containers with volumes - build_compose_file_args true - # shellcheck disable=SC2086 # COMPOSE_CMD is "docker compose" and needs word-splitting - if (cd "${INSTALL_ROOT}/deployment" && ${DOCKER_SUDO[@]+"${DOCKER_SUDO[@]}"} HOST_PORT="${HOST_PORT:-3000}" IMAGE_TAG="${IMAGE_TAG:-edge}" $COMPOSE_CMD "${COMPOSE_FILE_ARGS[@]}" down -v); then - print_success "Onyx containers and volumes removed" - else - print_error "Failed to remove containers and volumes" - fi - fi - fi - - print_info "Removing data directories..." - if [ -d "${INSTALL_ROOT}" ]; then - rm -rf "${INSTALL_ROOT}" - print_success "Data directories removed" - else - print_warning "No ${INSTALL_ROOT} directory found" - fi - - echo "" - print_success "All Onyx data has been permanently deleted!" - exit 0 -fi - -# --- Auto-install Docker (Linux only) --- -# Runs before the banner so a group-based re-exec doesn't repeat it. -install_docker_linux() { - local distro_id="" - if [[ -f /etc/os-release ]]; then - # shellcheck source=/dev/null - distro_id="$(. /etc/os-release && echo "${ID:-}")" - fi - - case "$distro_id" in - amzn) - print_info "Detected Amazon Linux — installing Docker via package manager..." - if command -v dnf &> /dev/null; then - sudo dnf install -y docker - else - sudo yum install -y docker - fi - ;; - *) - print_info "Installing Docker via get.docker.com..." - download_file "https://get.docker.com" /tmp/get-docker.sh - sudo sh /tmp/get-docker.sh - rm -f /tmp/get-docker.sh - ;; - esac - - sudo systemctl start docker 2>/dev/null || sudo service docker start 2>/dev/null || true - sudo systemctl enable docker 2>/dev/null || true -} - -# Detect OS (including WSL) -IS_WSL=false -if [[ -n "${WSL_DISTRO_NAME:-}" ]] || grep -qi microsoft /proc/version 2>/dev/null; then - IS_WSL=true -fi - -# Resolve the git ref to pull deployment files from, and the default image tag -# to suggest at the version prompt. Both default to the most recent GitHub -# release so users land on a pinned, tested version. If the API is unreachable, -# fall back to main / edge (the pre-existing defaults). -RELEASE_REF="main" -DEFAULT_IMAGE_TAG="edge" -RELEASE_LOOKUP_FAILED=false -if [[ "$USE_LOCAL_FILES" = false ]]; then - if fetch_latest_release_tag; then - RELEASE_REF="$LATEST_RELEASE_TAG" - DEFAULT_IMAGE_TAG="$LATEST_RELEASE_TAG" - else - RELEASE_LOOKUP_FAILED=true - fi -fi - -# Dry-run: show plan and exit -if [[ "$DRY_RUN" = true ]]; then - print_info "Dry run mode — showing what would happen:" - echo " • Install root: ${INSTALL_ROOT}" - echo " • Lite mode: ${LITE_MODE}" - echo " • Include Craft: ${INCLUDE_CRAFT}" - echo " • OS type: ${OSTYPE:-unknown} (WSL: ${IS_WSL})" - echo " • Downloader: ${DOWNLOADER}" - echo " • Default image tag: ${DEFAULT_IMAGE_TAG} (config ref: ${RELEASE_REF})" - echo "" - print_success "Dry run complete (no changes made)" - exit 0 -fi - -if ! command -v docker &> /dev/null; then - if [[ "$OSTYPE" == "linux-gnu"* ]] || [[ -n "${WSL_DISTRO_NAME:-}" ]]; then - print_info "Docker is required but not installed." - if ! confirm_action "Docker Engine"; then - print_error "Docker is required to run Onyx." - exit 1 - fi - install_docker_linux - if ! command -v docker &> /dev/null; then - print_error "Docker installation failed." - echo " Visit: https://docs.docker.com/get-docker/" - exit 1 - fi - print_success "Docker installed successfully" - # Compose plugin may ship with Docker — re-detect. - detect_compose_cmd || true - fi -fi - -# --- Auto-install Docker Compose plugin (Linux only) --- -if command -v docker &> /dev/null \ - && [[ -z "$COMPOSE_CMD" ]] \ - && { [[ "$OSTYPE" == "linux-gnu"* ]] || [[ -n "${WSL_DISTRO_NAME:-}" ]]; }; then - - print_info "Docker Compose is required but not installed." - if ! confirm_action "Docker Compose plugin"; then - print_error "Docker Compose is required to run Onyx." - exit 1 - fi - COMPOSE_ARCH="$(uname -m)" - COMPOSE_URL="https://github.com/docker/compose/releases/latest/download/docker-compose-linux-${COMPOSE_ARCH}" - COMPOSE_DIR="/usr/local/lib/docker/cli-plugins" - COMPOSE_TMP="$(mktemp)" - sudo mkdir -p "$COMPOSE_DIR" - if download_file "$COMPOSE_URL" "$COMPOSE_TMP"; then - sudo mv "$COMPOSE_TMP" "$COMPOSE_DIR/docker-compose" - sudo chmod +x "$COMPOSE_DIR/docker-compose" - if detect_compose_cmd && [[ "$COMPOSE_CMD" == "docker compose" ]]; then - print_success "Docker Compose plugin installed" - else - print_error "Docker Compose plugin installed but not detected." - echo " Visit: https://docs.docker.com/compose/install/" - exit 1 - fi - else - rm -f "$COMPOSE_TMP" - print_error "Failed to download Docker Compose plugin." - echo " Visit: https://docs.docker.com/compose/install/" - exit 1 - fi -fi - -# On Linux, ensure docker commands can talk to the daemon. If `docker info` -# fails, fall back to sudo for the rest of this run. Add the user to the -# "docker" group only when they aren't already a member — `docker info` also -# fails when the daemon is stopped, and we don't want to silently mutate -# group membership in that case. The downstream daemon check will exit with -# a clearer message if the daemon really is down. -refresh_docker_sudo -if is_using_sudo; then - if ! id -nG "$USER" | grep -qw docker; then - if ! getent group docker &> /dev/null; then - sudo groupadd docker - fi - print_info "Adding $USER to the docker group (effective on next login)..." - sudo usermod -aG docker "$USER" - fi - print_info "Using sudo for docker commands in this run." -fi - -# ASCII Art Banner -echo "" -echo -e "${BLUE}${BOLD}" -echo " ____ " -echo " / __ \ " -echo "| | | |_ __ _ ___ __ " -echo "| | | | '_ \| | | \ \/ / " -echo "| |__| | | | | |_| |> < " -echo " \____/|_| |_|\__, /_/\_\ " -echo " __/ | " -echo " |___/ " -echo -e "${NC}" -echo "Welcome to Onyx Installation Script" -echo "====================================" -echo "" - -if [[ "$RELEASE_LOOKUP_FAILED" = true ]]; then - print_warning "Could not determine latest Onyx release — falling back to main / edge" -fi - -# User acknowledgment section -echo -e "${YELLOW}${BOLD}This script will:${NC}" -echo "1. Download deployment files for Onyx into a new '${INSTALL_ROOT}' directory" -echo "2. Check your system resources (Docker, memory, disk space)" -echo "3. Guide you through deployment options (version, authentication)" -echo "" - -if is_interactive; then - echo -e "${YELLOW}${BOLD}Please acknowledge and press Enter to continue...${NC}" - read_prompt_line "" - echo "" -else - echo -e "${YELLOW}${BOLD}Running in non-interactive mode - proceeding automatically...${NC}" - echo "" -fi - -# GitHub repo base URL — pinned to the resolved release ref (latest release tag -# if reachable, otherwise main). -GITHUB_RAW_URL="https://raw.githubusercontent.com/onyx-dot-app/onyx/${RELEASE_REF}/deployment/docker_compose" - -# Check system requirements -print_step "Verifying Docker installation" - -# Check Docker -if ! command -v docker &> /dev/null; then - print_error "Docker is not installed. Please install Docker first." - echo "Visit: https://docs.docker.com/get-docker/" - exit 1 -fi -DOCKER_VERSION=$(docker --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) -print_success "Docker $DOCKER_VERSION is installed" - -# Check Docker Compose -if [[ -z "$COMPOSE_CMD" ]]; then - print_error "Docker Compose is not installed. Please install Docker Compose first." - echo "Visit: https://docs.docker.com/compose/install/" +if ! tar -xzf "${TMP_DIR}/${ARCHIVE_NAME}" -C "$TMP_DIR" onyx-cli; then + print_error "Failed to extract ${ARCHIVE_NAME}" exit 1 fi -if [[ "$COMPOSE_CMD" == "docker compose" ]]; then - COMPOSE_VERSION=$($COMPOSE_CMD version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) - COMPOSE_TYPE="plugin" -else - COMPOSE_VERSION=$($COMPOSE_CMD --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) - COMPOSE_TYPE="standalone" -fi -if [ -z "$COMPOSE_VERSION" ]; then - # Handle non-standard versions like "dev" - assume recent enough - COMPOSE_VERSION="dev" - print_success "Docker Compose (dev build) is installed (${COMPOSE_TYPE})" -else - print_success "Docker Compose $COMPOSE_VERSION is installed (${COMPOSE_TYPE})" -fi - -# Returns 0 if $1 <= $2, 1 if $1 > $2 -# Handles missing or non-numeric parts gracefully (treats them as 0) -version_compare() { - local version1="${1:-0.0.0}" - local version2="${2:-0.0.0}" - - local v1_major v1_minor v1_patch v2_major v2_minor v2_patch - v1_major=$(echo "$version1" | cut -d. -f1) - v1_minor=$(echo "$version1" | cut -d. -f2) - v1_patch=$(echo "$version1" | cut -d. -f3) - v2_major=$(echo "$version2" | cut -d. -f1) - v2_minor=$(echo "$version2" | cut -d. -f2) - v2_patch=$(echo "$version2" | cut -d. -f3) - - # Default non-numeric or empty parts to 0 - [[ "$v1_major" =~ ^[0-9]+$ ]] || v1_major=0 - [[ "$v1_minor" =~ ^[0-9]+$ ]] || v1_minor=0 - [[ "$v1_patch" =~ ^[0-9]+$ ]] || v1_patch=0 - [[ "$v2_major" =~ ^[0-9]+$ ]] || v2_major=0 - [[ "$v2_minor" =~ ^[0-9]+$ ]] || v2_minor=0 - [[ "$v2_patch" =~ ^[0-9]+$ ]] || v2_patch=0 - - if [ "$v1_major" -lt "$v2_major" ]; then return 0 - elif [ "$v1_major" -gt "$v2_major" ]; then return 1; fi - - if [ "$v1_minor" -lt "$v2_minor" ]; then return 0 - elif [ "$v1_minor" -gt "$v2_minor" ]; then return 1; fi - - [ "$v1_patch" -le "$v2_patch" ] -} - -# Check Docker daemon -if ! ${DOCKER_SUDO[@]+"${DOCKER_SUDO[@]}"} docker info &> /dev/null; then - if [[ "$OSTYPE" == "darwin"* ]]; then - print_info "Docker daemon is not running. Starting Docker Desktop..." - open -a Docker - # Wait up to 120 seconds for Docker to be ready - DOCKER_WAIT=0 - DOCKER_MAX_WAIT=120 - while ! docker info &> /dev/null; do - if [ $DOCKER_WAIT -ge $DOCKER_MAX_WAIT ]; then - print_error "Docker Desktop did not start within ${DOCKER_MAX_WAIT} seconds." - print_info "Please start Docker Desktop manually and re-run this script." - exit 1 - fi - printf "\r\033[KWaiting for Docker Desktop to start... (%ds)" "$DOCKER_WAIT" - sleep 2 - DOCKER_WAIT=$((DOCKER_WAIT + 2)) - done - echo "" - print_success "Docker Desktop is now running" - else - print_error "Docker daemon is not running. Please start Docker." - exit 1 - fi -else - print_success "Docker daemon is running" -fi - -# Check Docker resources -print_step "Verifying Docker resources" - -# Try to get memory allocation (method varies by platform) -if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS - Docker Desktop - if command -v jq &> /dev/null && [ -f ~/Library/Group\ Containers/group.com.docker/settings.json ]; then - MEMORY_MB=$(cat ~/Library/Group\ Containers/group.com.docker/settings.json 2>/dev/null | jq '.memoryMiB // 0' 2>/dev/null || echo "0") - else - # Try to get from docker system info - MEMORY_BYTES=$(docker system info 2>/dev/null | grep -i "total memory" | grep -oE '[0-9]+\.[0-9]+' | head -1) - if [ -n "$MEMORY_BYTES" ]; then - # Convert from GiB to MB (multiply by 1024) - MEMORY_MB=$(echo "$MEMORY_BYTES * 1024" | bc 2>/dev/null | cut -d. -f1) - if [ -z "$MEMORY_MB" ]; then - MEMORY_MB="0" - fi - else - MEMORY_MB="0" - fi - fi -else - # Linux - Native Docker - MEMORY_KB=$(grep MemTotal /proc/meminfo | grep -oE '[0-9]+' || echo "0") - MEMORY_MB=$((MEMORY_KB / 1024)) -fi - -# Convert to GB for display -if [ "$MEMORY_MB" -gt 0 ]; then - MEMORY_GB=$(awk "BEGIN {printf \"%.1f\", $MEMORY_MB / 1024}") - if [ "$(awk "BEGIN {print ($MEMORY_MB >= 1024)}")" = "1" ]; then - MEMORY_DISPLAY="~${MEMORY_GB}GB" - else - MEMORY_DISPLAY="${MEMORY_MB}MB" - fi - if [[ "$OSTYPE" == "darwin"* ]]; then - print_info "Docker memory allocation: ${MEMORY_DISPLAY}" - else - print_info "System memory: ${MEMORY_DISPLAY} (Docker uses host memory directly)" - fi -else - print_warning "Could not determine memory allocation" - MEMORY_DISPLAY="unknown" - MEMORY_MB=0 -fi - -# Check disk space (different commands for macOS vs Linux) -if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS uses -g for GB - DISK_AVAILABLE=$(df -g . | awk 'NR==2 {print $4}') -else - # Linux uses -BG for GB - DISK_AVAILABLE=$(df -BG . | awk 'NR==2 {print $4}' | sed 's/G//') -fi -print_info "Available disk space: ${DISK_AVAILABLE}GB" - -# Resource requirements check -RESOURCE_WARNING=false -EXPECTED_RAM_MB=$((EXPECTED_DOCKER_RAM_GB * 1024)) - -if [ "$MEMORY_MB" -gt 0 ] && [ "$MEMORY_MB" -lt "$EXPECTED_RAM_MB" ]; then - print_warning "Less than ${EXPECTED_DOCKER_RAM_GB}GB RAM available (found: ${MEMORY_DISPLAY})" - RESOURCE_WARNING=true -fi - -if [ "$DISK_AVAILABLE" -lt "$EXPECTED_DISK_GB" ]; then - print_warning "Less than ${EXPECTED_DISK_GB}GB disk space available (found: ${DISK_AVAILABLE}GB)" - RESOURCE_WARNING=true -fi - -if [ "$RESOURCE_WARNING" = true ]; then - echo "" - print_warning "Onyx recommends at least ${EXPECTED_DOCKER_RAM_GB}GB RAM and ${EXPECTED_DISK_GB}GB disk space for optimal performance in standard mode." - print_warning "Lite mode requires less resources (1-4GB RAM, 8-16GB disk depending on usage), but does not include a vector database." - echo "" - prompt_yn_or_default "Do you want to continue anyway? (Y/n): " "y" - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - print_info "Installation cancelled. Please allocate more resources and try again." - exit 1 - fi - print_info "Proceeding with installation despite resource limitations..." -fi - -# Create directory structure -print_step "Creating directory structure" -if [ -d "${INSTALL_ROOT}" ]; then - print_info "Directory structure already exists" - print_success "Using existing ${INSTALL_ROOT} directory" -fi -mkdir -p "${INSTALL_ROOT}/deployment" -mkdir -p "${INSTALL_ROOT}/data/nginx/local" -print_success "Directory structure created" - -# Ensure all required configuration files are present -NGINX_BASE_URL="https://raw.githubusercontent.com/onyx-dot-app/onyx/${RELEASE_REF}/deployment/data/nginx" - -if [[ "$USE_LOCAL_FILES" = true ]]; then - print_step "Verifying existing configuration files" -else - print_step "Downloading Onyx configuration files" - print_info "This step downloads all necessary configuration files from GitHub..." -fi - -ensure_file "${INSTALL_ROOT}/deployment/docker-compose.yml" \ - "${GITHUB_RAW_URL}/docker-compose.yml" "docker-compose.yml" || exit 1 - -# Check Docker Compose version compatibility after obtaining docker-compose.yml -if [ "$COMPOSE_VERSION" != "dev" ] && version_compare "$COMPOSE_VERSION" "2.24.0"; then - print_warning "Docker Compose version $COMPOSE_VERSION is older than 2.24.0" - echo "" - print_warning "The docker-compose.yml file uses the newer env_file format that requires Docker Compose 2.24.0 or later." - echo "" - print_info "To use this configuration with your current Docker Compose version, you have two options:" - echo "" - echo "1. Upgrade Docker Compose to version 2.24.0 or later (recommended)" - echo " Visit: https://docs.docker.com/compose/install/" - echo "" - echo "2. Manually replace all env_file sections in docker-compose.yml" - echo " Change from:" - echo " env_file:" - echo " - path: .env" - echo " required: false" - echo " To:" - echo " env_file: .env" - echo "" - print_warning "The installation will continue, but may fail if Docker Compose cannot parse the file." - echo "" - prompt_yn_or_default "Do you want to continue anyway? (Y/n): " "y" - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - print_info "Installation cancelled. Please upgrade Docker Compose or manually edit the docker-compose.yml file." - exit 1 - fi - print_info "Proceeding with installation despite Docker Compose version compatibility issues..." -fi - -# Ask for deployment mode (standard vs lite) unless already set via --lite flag -if [[ "$LITE_MODE" = false ]]; then - print_info "Which deployment mode would you like?" - echo "" - echo " 1) Lite - Minimal deployment (no OpenSearch, Redis, or model servers)" - echo " LLM chat, tools, file uploads, and Projects still work" - echo " 2) Standard - Full deployment with search, connectors, and RAG" - echo "" - prompt_or_default "Choose a mode (1 or 2) [default: 1]: " "1" - echo "" - - case "$REPLY" in - 2) - print_info "Selected: Standard mode" - ;; - *) - LITE_MODE=true - print_info "Selected: Lite mode" - ;; - esac -else - print_info "Deployment mode: Lite (set via --lite flag)" -fi - -if [[ "$LITE_MODE" = true ]] && [[ "$INCLUDE_CRAFT" = true ]]; then - print_error "--include-craft cannot be used with Lite mode." - print_info "Craft requires services (Vespa, Redis, background workers) that lite mode disables." +if ! mkdir -p "$BIN_DIR" 2>/dev/null || [[ ! -w "$BIN_DIR" ]]; then + print_error "Cannot write to ${BIN_DIR}" + echo " Re-run with sudo, or set ONYX_CLI_BIN_DIR to a writable directory." >&2 exit 1 fi -if [[ "$LITE_MODE" = true ]]; then - EXPECTED_DOCKER_RAM_GB=4 - EXPECTED_DISK_GB=16 -fi +chmod +x "${TMP_DIR}/onyx-cli" +mv -f "${TMP_DIR}/onyx-cli" "$BIN_PATH" +print_success "onyx-cli installed to ${BIN_PATH}" -# Handle lite overlay file based on selected mode -if [[ "$LITE_MODE" = true ]]; then - ensure_file "${INSTALL_ROOT}/deployment/${LITE_COMPOSE_FILE}" \ - "${GITHUB_RAW_URL}/${LITE_COMPOSE_FILE}" "${LITE_COMPOSE_FILE}" || exit 1 -elif [[ -f "${INSTALL_ROOT}/deployment/${LITE_COMPOSE_FILE}" ]]; then - rm -f "${INSTALL_ROOT}/deployment/${LITE_COMPOSE_FILE}" - print_info "Removed previous lite overlay (switching to standard mode)" -fi - -# Handle craft overlay file based on selected mode. Keeps the Docker socket -# bind-mount out of the default file so non-Craft deployments never inherit -# it; only `--include-craft` (or an existing overlay from a prior install) -# adds it back. -if [[ "$INCLUDE_CRAFT" = true ]]; then - ensure_file "${INSTALL_ROOT}/deployment/${CRAFT_COMPOSE_FILE}" \ - "${GITHUB_RAW_URL}/${CRAFT_COMPOSE_FILE}" "${CRAFT_COMPOSE_FILE}" || exit 1 -elif [[ -f "${INSTALL_ROOT}/deployment/${CRAFT_COMPOSE_FILE}" ]]; then - rm -f "${INSTALL_ROOT}/deployment/${CRAFT_COMPOSE_FILE}" - print_info "Removed previous craft overlay (Craft disabled this run)" -fi - -ensure_file "${INSTALL_ROOT}/deployment/env.template" \ - "${GITHUB_RAW_URL}/env.template" "env.template" || exit 1 - -ensure_file "${INSTALL_ROOT}/data/nginx/app.conf.template" \ - "$NGINX_BASE_URL/app.conf.template" "nginx/app.conf.template" || exit 1 - -ensure_file "${INSTALL_ROOT}/data/nginx/run-nginx.sh" \ - "$NGINX_BASE_URL/run-nginx.sh" "nginx/run-nginx.sh" || exit 1 -chmod +x "${INSTALL_ROOT}/data/nginx/run-nginx.sh" - -ensure_file "${INSTALL_ROOT}/README.md" \ - "${GITHUB_RAW_URL}/README.md" "README.md" || exit 1 - -touch "${INSTALL_ROOT}/data/nginx/local/.gitkeep" -print_success "All configuration files ready" - -# Set up deployment configuration -print_step "Setting up deployment configs" -ENV_FILE="${INSTALL_ROOT}/deployment/.env" -ENV_TEMPLATE="${INSTALL_ROOT}/deployment/env.template" -# Check if services are already running -if [ -d "${INSTALL_ROOT}/deployment" ] && [ -f "${INSTALL_ROOT}/deployment/docker-compose.yml" ]; then - build_compose_file_args true - # shellcheck disable=SC2086 # COMPOSE_CMD is "docker compose" and needs word-splitting - RUNNING_CONTAINERS=$(cd "${INSTALL_ROOT}/deployment" && ${DOCKER_SUDO[@]+"${DOCKER_SUDO[@]}"} $COMPOSE_CMD "${COMPOSE_FILE_ARGS[@]}" ps -q 2>/dev/null | wc -l) - if [ "$RUNNING_CONTAINERS" -gt 0 ]; then - print_error "Onyx services are currently running!" - echo "" - print_info "To make configuration changes, you must first shut down the services." - echo "" - print_info "Please run the following command to shut down Onyx:" - echo -e " ${BOLD}./install.sh --shutdown${NC}" - echo "" - print_info "Then run this script again to make your changes." - exit 1 - fi -fi - -if [ -f "$ENV_FILE" ]; then - print_info "Existing .env file found. What would you like to do?" - echo "" - echo "• Press Enter to restart with current configuration" - echo "• Type 'update' to update to a newer version" - echo "" - prompt_or_default "Choose an option [default: restart]: " "" - echo "" - - if [ "$REPLY" = "update" ]; then - # Craft uses the regular backend image (ENABLE_CRAFT is a runtime flag), - # so the tag is prompted for normally even with --include-craft. - print_info "Update selected. Which tag would you like to deploy?" - echo "" - echo "• Press Enter for ${DEFAULT_IMAGE_TAG} (recommended)" - echo "• Type a specific tag (e.g., v0.1.0)" - echo "" - prompt_or_default "Enter tag [default: ${DEFAULT_IMAGE_TAG}]: " "${DEFAULT_IMAGE_TAG}" - VERSION="$REPLY" - echo "" - - if [ "$VERSION" = "edge" ]; then - print_info "Selected: edge (latest nightly)" - else - print_info "Selected: $VERSION" +case ":${PATH}:" in + *":${BIN_DIR}:"*) + # An onyx-cli earlier in PATH (e.g. a pip install) would shadow the one + # just installed, leaving the follow-up `onyx-cli deploy` commands on a + # different version than the install ran with. + SHADOWED_BY="$(command -v onyx-cli 2>/dev/null || true)" + if [[ -n "$SHADOWED_BY" ]] && [[ "$SHADOWED_BY" != "$BIN_PATH" ]]; then + print_warning "Another onyx-cli takes precedence in PATH: ${SHADOWED_BY}" fi + ;; + *) + print_warning "${BIN_DIR} is not in your PATH — add it to run onyx-cli directly:" + echo -e " ${BOLD}export PATH=\"${BIN_DIR}:\$PATH\"${NC}" + ;; +esac - # Update .env file with new version - print_info "Updating configuration for version $VERSION..." - if grep -q "^IMAGE_TAG=" "$ENV_FILE"; then - # Update existing IMAGE_TAG line - sed -i.bak "s/^IMAGE_TAG=.*/IMAGE_TAG=$VERSION/" "$ENV_FILE" - else - # Add IMAGE_TAG line if it doesn't exist - echo "IMAGE_TAG=$VERSION" >> "$ENV_FILE" - fi - print_success "Updated IMAGE_TAG to $VERSION in .env file" - print_success "Configuration updated for upgrade" - else - print_info "Keeping existing configuration..." - print_success "Will restart with current settings" - fi +# The temp dir is gone by the time exec replaces this process, so drop it now: +# an EXIT trap never fires across exec. +cleanup - # Honor --include-craft on existing installs regardless of update/restart: - # enable Craft at runtime and align SANDBOX_BACKEND with the effective tag - # (the one just chosen, or the pinned IMAGE_TAG already in .env on a restart). - if [ "$INCLUDE_CRAFT" = true ]; then - if grep -q "^#* *ENABLE_CRAFT=" "$ENV_FILE"; then - sed -i.bak 's/^#* *ENABLE_CRAFT=.*/ENABLE_CRAFT=true/' "$ENV_FILE" - else - echo "ENABLE_CRAFT=true" >> "$ENV_FILE" - fi - EFFECTIVE_TAG="${VERSION:-$(grep -E '^IMAGE_TAG=' "$ENV_FILE" | head -1 | cut -d= -f2-)}" - set_sandbox_backend_for_tag "$EFFECTIVE_TAG" - print_success "Onyx Craft enabled (ENABLE_CRAFT=true, SANDBOX_BACKEND=$SANDBOX_BACKEND_VALUE, image tag: ${EFFECTIVE_TAG})" - fi - - # Ensure COMPOSE_PROFILES is cleared when running in lite mode on an - # existing .env (the template ships with s3-filestore enabled). - if [[ "$LITE_MODE" = true ]] && grep -q "^COMPOSE_PROFILES=.*s3-filestore" "$ENV_FILE" 2>/dev/null; then - sed -i.bak 's/^COMPOSE_PROFILES=.*/COMPOSE_PROFILES=/' "$ENV_FILE" 2>/dev/null || true - print_success "Cleared COMPOSE_PROFILES for lite mode" - fi -else - print_info "No existing .env file found. Setting up new deployment..." - echo "" - - # Craft uses the regular backend image (ENABLE_CRAFT is a runtime flag), so - # the tag is prompted for normally even with --include-craft. - print_info "Which tag would you like to deploy?" - echo "" - echo "• Press Enter for ${DEFAULT_IMAGE_TAG} (recommended)" - echo "• Type a specific tag (e.g., v0.1.0)" - echo "" - prompt_or_default "Enter tag [default: ${DEFAULT_IMAGE_TAG}]: " "${DEFAULT_IMAGE_TAG}" - VERSION="$REPLY" - echo "" - - if [ "$VERSION" = "edge" ]; then - print_info "Selected: edge (latest nightly)" - else - print_info "Selected: $VERSION" - fi - - # Create .env file from template - print_info "Creating .env file with your selections..." - cp "$ENV_TEMPLATE" "$ENV_FILE" - - # Update IMAGE_TAG with selected version - print_info "Setting IMAGE_TAG to $VERSION..." - sed -i.bak "s/^IMAGE_TAG=.*/IMAGE_TAG=$VERSION/" "$ENV_FILE" - print_success "IMAGE_TAG set to $VERSION" - - # In lite mode, MinIO never starts (COMPOSE_PROFILES cleared) and the - # onyx-lite overlay forces FILE_STORE_BACKEND=postgres at runtime; set it in - # .env too so the file isn't misleadingly left on s3 (the MinIO/S3 creds - # generated below are then unused, but stay ready if s3-filestore is enabled). - if [[ "$LITE_MODE" = true ]]; then - sed -i.bak 's/^COMPOSE_PROFILES=.*/COMPOSE_PROFILES=/' "$ENV_FILE" 2>/dev/null || true - sed -i.bak 's/^FILE_STORE_BACKEND=.*/FILE_STORE_BACKEND=postgres/' "$ENV_FILE" 2>/dev/null || true - print_success "Cleared COMPOSE_PROFILES and set FILE_STORE_BACKEND=postgres for lite mode" - fi - - # Check if openssl is available - if ! command -v openssl &> /dev/null; then - print_error "openssl is required to generate secure secrets but was not found." - exit 1 - fi - - # Generate a secure USER_AUTH_SECRET - USER_AUTH_SECRET=$(openssl rand -hex 32) - sed -i.bak "s/^USER_AUTH_SECRET=.*/USER_AUTH_SECRET=\"$USER_AUTH_SECRET\"/" "$ENV_FILE" 2>/dev/null || true - - # Random MinIO/S3 credentials instead of the minioadmin default. The app uses - # MinIO's root creds, so the same pair goes to both the server and app vars. - MINIO_ACCESS_KEY=$(openssl rand -hex 16) - MINIO_SECRET_KEY=$(openssl rand -hex 32) - sed -i.bak "s/^MINIO_ROOT_USER=.*/MINIO_ROOT_USER=$MINIO_ACCESS_KEY/" "$ENV_FILE" 2>/dev/null || true - sed -i.bak "s/^MINIO_ROOT_PASSWORD=.*/MINIO_ROOT_PASSWORD=$MINIO_SECRET_KEY/" "$ENV_FILE" 2>/dev/null || true - sed -i.bak "s/^S3_AWS_ACCESS_KEY_ID=.*/S3_AWS_ACCESS_KEY_ID=$MINIO_ACCESS_KEY/" "$ENV_FILE" 2>/dev/null || true - sed -i.bak "s/^S3_AWS_SECRET_ACCESS_KEY=.*/S3_AWS_SECRET_ACCESS_KEY=$MINIO_SECRET_KEY/" "$ENV_FILE" 2>/dev/null || true - - # Configure Craft based on --include-craft. - # By default, env.template has Craft commented out (disabled). - if [ "$INCLUDE_CRAFT" = true ]; then - # Set ENABLE_CRAFT=true for runtime configuration (handles commented and uncommented lines) - sed -i.bak 's/^#* *ENABLE_CRAFT=.*/ENABLE_CRAFT=true/' "$ENV_FILE" 2>/dev/null || true - # docker backend exists only in v4.0.6+; older tags get kubernetes. - set_sandbox_backend_for_tag "$VERSION" - print_success "Onyx Craft enabled (ENABLE_CRAFT=true, SANDBOX_BACKEND=$SANDBOX_BACKEND_VALUE)" - - if [ "$SANDBOX_BACKEND_VALUE" = "docker" ]; then - echo "" - print_warning "Craft + docker backend: api_server and background bind-mount" - print_warning "/var/run/docker.sock (RW = root on host on compromise);" - print_warning "sandbox-proxy bind-mounts it RO (still exposes container env," - print_warning "labels, and the events stream). Only enable on hosts you fully" - print_warning "control. On EC2, require IMDSv2 (HttpTokens=required) so" - print_warning "sandboxes cannot pull IAM credentials from instance metadata." - echo "" - else - print_info "Image tag ${VERSION} predates the docker sandbox backend (v4.0.6+); using SANDBOX_BACKEND=${SANDBOX_BACKEND_VALUE}." - fi - else - print_info "Onyx Craft disabled (use --include-craft to enable)" - fi - - print_success ".env file created with your preferences" - echo "" - print_info "IMPORTANT: The .env file has been configured with your selections." - print_info "You can customize it later for:" - echo " • AI model configuration" - echo " • Domain settings (for production)" - echo " • Onyx Craft (set ENABLE_CRAFT=true)" - echo "" +# `curl ... | bash` leaves stdin on the pipe, and the CLI only prompts when +# stdin is a terminal. Reconnect it to the controlling terminal (when there is +# one) so a piped install is still interactive, the way this script's previous +# implementation read its own prompts from /dev/tty. +if [[ ! -t 0 ]] && (: < /dev/tty) 2> /dev/null; then + exec "$BIN_PATH" deploy install "$@" < /dev/tty fi -# Pre-create the sandbox bridge — compose overlay references it as external. -if [ "$INCLUDE_CRAFT" = true ]; then - SANDBOX_NET="${SANDBOX_DOCKER_NETWORK:-onyx_craft_sandbox}" - if ! ${DOCKER_SUDO[@]+"${DOCKER_SUDO[@]}"} docker network inspect "$SANDBOX_NET" >/dev/null 2>&1; then - if ${DOCKER_SUDO[@]+"${DOCKER_SUDO[@]}"} docker network create "$SANDBOX_NET" >/dev/null 2>&1; then - print_success "Created sandbox bridge network: $SANDBOX_NET" - else - print_warning "Could not create sandbox network $SANDBOX_NET — create it manually:" - echo " docker network create $SANDBOX_NET" - fi - fi - - # Same for the CA volume. Name pinned to match - # configs.SANDBOX_PROXY_CA_VOLUME_NAME. - SANDBOX_PROXY_CA_VOL="sandbox_proxy_ca" - if ! ${DOCKER_SUDO[@]+"${DOCKER_SUDO[@]}"} docker volume inspect "$SANDBOX_PROXY_CA_VOL" >/dev/null 2>&1; then - if ${DOCKER_SUDO[@]+"${DOCKER_SUDO[@]}"} docker volume create "$SANDBOX_PROXY_CA_VOL" >/dev/null 2>&1; then - print_success "Created sandbox proxy CA volume: $SANDBOX_PROXY_CA_VOL" - else - print_warning "Could not create sandbox proxy CA volume $SANDBOX_PROXY_CA_VOL — create it manually:" - echo " docker volume create $SANDBOX_PROXY_CA_VOL" - fi - fi -fi - -# Function to check if a port is available -is_port_available() { - local port=$1 - - # Try netcat first if available - if command -v nc &> /dev/null; then - # Try to connect to the port, if it fails, the port is available - if nc -z localhost "$port" 2>/dev/null; then - return 1 - fi - return 0 - # Fallback using curl/telnet approach - elif command -v curl &> /dev/null; then - # Try to connect with curl, if it fails, the port might be available - if curl -s --max-time 1 --connect-timeout 1 "http://localhost:$port" >/dev/null 2>&1; then - return 1 - fi - return 0 - # Final fallback using lsof if available - elif command -v lsof &> /dev/null; then - # Check if any process is listening on the port - if lsof -i ":$port" >/dev/null 2>&1; then - return 1 - fi - return 0 - else - # No port checking tools available, assume port is available - print_warning "No port checking tools available (nc, curl, lsof). Assuming port $port is available." - return 0 - fi -} - -# Function to find the first available port starting from a given port -find_available_port() { - local start_port=${1:-3000} - local port=$start_port - - while [ "$port" -le 65535 ]; do - if is_port_available "$port"; then - echo "$port" - return 0 - fi - port=$((port + 1)) - done - - # If no port found, return the original port as fallback - echo "$start_port" - return 1 -} - -# Check for port checking tools availability -PORT_CHECK_AVAILABLE=false -if command -v nc &> /dev/null || command -v curl &> /dev/null || command -v lsof &> /dev/null; then - PORT_CHECK_AVAILABLE=true -fi - -if [ "$PORT_CHECK_AVAILABLE" = false ]; then - print_warning "No port checking tools found (nc, curl, lsof). Port detection may not work properly." - print_info "Consider installing one of these tools for reliable automatic port detection." -fi - -# Find available port for nginx -print_step "Checking for available ports" -AVAILABLE_PORT=$(find_available_port 3000) - -if [ "$AVAILABLE_PORT" != "3000" ]; then - print_info "Port 3000 is in use, found available port: $AVAILABLE_PORT" -else - print_info "Port 3000 is available" -fi - -# Export HOST_PORT for docker-compose -export HOST_PORT=$AVAILABLE_PORT -print_success "Using port $AVAILABLE_PORT for nginx" - -# Determine if we're using a floating tag (edge, latest) that should force pull. -# Read IMAGE_TAG from .env file and remove any quotes or whitespace. -CURRENT_IMAGE_TAG=$(grep "^IMAGE_TAG=" "$ENV_FILE" | head -1 | cut -d'=' -f2 | tr -d ' "'"'"'') - -if [ "$CURRENT_IMAGE_TAG" = "edge" ] || [ "$CURRENT_IMAGE_TAG" = "latest" ]; then - USE_LATEST=true - # Floating tags track main, so configs should come from main. - CONFIG_REF="main" - print_info "Using '$CURRENT_IMAGE_TAG' tag - will force pull and recreate containers" -else - USE_LATEST=false - # Pinned release tags use their own ref so configs match the images. - CONFIG_REF="$CURRENT_IMAGE_TAG" -fi - -# The initial download pulled configs from RELEASE_REF (latest release, or main -# on fallback). If the chosen image tag wants configs from a different ref, -# re-fetch them so the compose file matches the images being pulled. -if [[ "$CONFIG_REF" != "$RELEASE_REF" ]] && [[ "$USE_LOCAL_FILES" = false ]]; then - CONFIG_BASE="https://raw.githubusercontent.com/onyx-dot-app/onyx/${CONFIG_REF}/deployment" - print_info "Fetching config files matching ${CONFIG_REF}..." - if download_file "${CONFIG_BASE}/docker_compose/docker-compose.yml" "${INSTALL_ROOT}/deployment/docker-compose.yml" 2>/dev/null; then - download_file "${CONFIG_BASE}/data/nginx/app.conf.template" "${INSTALL_ROOT}/data/nginx/app.conf.template" 2>/dev/null || true - download_file "${CONFIG_BASE}/data/nginx/run-nginx.sh" "${INSTALL_ROOT}/data/nginx/run-nginx.sh" 2>/dev/null || true - chmod +x "${INSTALL_ROOT}/data/nginx/run-nginx.sh" - if [[ "$LITE_MODE" = true ]]; then - download_file "${CONFIG_BASE}/docker_compose/${LITE_COMPOSE_FILE}" \ - "${INSTALL_ROOT}/deployment/${LITE_COMPOSE_FILE}" 2>/dev/null || true - fi - if [[ "$INCLUDE_CRAFT" = true ]]; then - download_file "${CONFIG_BASE}/docker_compose/${CRAFT_COMPOSE_FILE}" \ - "${INSTALL_ROOT}/deployment/${CRAFT_COMPOSE_FILE}" 2>/dev/null || true - fi - print_success "Config files updated to match ${CONFIG_REF}" - else - print_warning "Ref ${CONFIG_REF} not found on GitHub — keeping configs from ${RELEASE_REF}" - fi -fi - -# Pull Docker images with reduced output -print_step "Pulling Docker images" -print_info "This may take several minutes depending on your internet connection..." -echo "" -print_info "Downloading Docker images (this may take a while)..." -# Pass IMAGE_TAG inline so the value the user picked at the prompt wins over -# any `export IMAGE_TAG=...` inherited from the caller's shell. Compose's -# substitution prefers shell env over .env, so without this an exported -# IMAGE_TAG in the user's shellrc would silently swap the images being pulled. -build_compose_file_args -# shellcheck disable=SC2086 # COMPOSE_CMD is "docker compose" and needs word-splitting -if (cd "${INSTALL_ROOT}/deployment" && ${DOCKER_SUDO[@]+"${DOCKER_SUDO[@]}"} HOST_PORT="$HOST_PORT" IMAGE_TAG="$CURRENT_IMAGE_TAG" $COMPOSE_CMD "${COMPOSE_FILE_ARGS[@]}" pull --quiet); then - print_success "Docker images downloaded successfully" -else - print_error "Failed to download Docker images" - exit 1 -fi - -# Build the up flags. With --wait (default), Compose waits until every -# container with a healthcheck reports healthy before returning, and names any -# service that stays unhealthy past the timeout. -UP_WAIT_ARGS=() -if [[ "$NO_WAIT" = false ]]; then - UP_WAIT_ARGS=(--wait --wait-timeout "${WAIT_TIMEOUT_SECONDS}") -fi - -# Start services -print_step "Starting Onyx services" -print_info "Launching containers..." -if [[ "$NO_WAIT" = false ]]; then - print_info "Waiting up to ${WAIT_TIMEOUT_SECONDS}s for all services to become healthy..." -fi -echo "" -UP_EXIT=0 -if [ "$USE_LATEST" = true ]; then - print_info "Force pulling latest images and recreating containers..." - # shellcheck disable=SC2086 # COMPOSE_CMD is "docker compose" and needs word-splitting - (cd "${INSTALL_ROOT}/deployment" && ${DOCKER_SUDO[@]+"${DOCKER_SUDO[@]}"} HOST_PORT="$HOST_PORT" IMAGE_TAG="$CURRENT_IMAGE_TAG" $COMPOSE_CMD "${COMPOSE_FILE_ARGS[@]}" up -d --pull always --force-recreate "${UP_WAIT_ARGS[@]}") || UP_EXIT=$? -else - # shellcheck disable=SC2086 # COMPOSE_CMD is "docker compose" and needs word-splitting - (cd "${INSTALL_ROOT}/deployment" && ${DOCKER_SUDO[@]+"${DOCKER_SUDO[@]}"} HOST_PORT="$HOST_PORT" IMAGE_TAG="$CURRENT_IMAGE_TAG" $COMPOSE_CMD "${COMPOSE_FILE_ARGS[@]}" up -d "${UP_WAIT_ARGS[@]}") || UP_EXIT=$? -fi -if [ $UP_EXIT -ne 0 ]; then - print_error "Failed to start Onyx services" - echo "" - print_info "Current container status:" - # shellcheck disable=SC2086 # COMPOSE_CMD is "docker compose" and needs word-splitting - (cd "${INSTALL_ROOT}/deployment" && ${DOCKER_SUDO[@]+"${DOCKER_SUDO[@]}"} HOST_PORT="$HOST_PORT" IMAGE_TAG="$CURRENT_IMAGE_TAG" $COMPOSE_CMD "${COMPOSE_FILE_ARGS[@]}" ps) - echo "" - print_info "Check the logs of any unhealthy service:" - echo " (cd \"${INSTALL_ROOT}/deployment\" && $(is_using_sudo && echo "sudo ")$COMPOSE_CMD ${COMPOSE_FILE_ARGS[*]} logs )" - echo "" - print_info "If the issue persists, please contact: founders@onyx.app" - exit 1 -fi - -# Success message -print_step "Installation Complete!" -echo "" -if [[ "$NO_WAIT" = false ]]; then - echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${GREEN}${BOLD} 🎉 Onyx service is ready! 🎉${NC}" - echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -else - echo -e "${YELLOW}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${YELLOW}${BOLD} ⚠️ Onyx containers started ⚠️${NC}" - echo -e "${YELLOW}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - print_info "Services may still be initializing. Check status with:" - echo " (cd \"${INSTALL_ROOT}/deployment\" && $(is_using_sudo && echo "sudo ")$COMPOSE_CMD ${COMPOSE_FILE_ARGS[*]} ps)" -fi -echo "" -print_info "Access Onyx at:" -echo -e " ${BOLD}http://localhost:${HOST_PORT}${NC}" -echo "" -print_info "If authentication is enabled, you can create your admin account here:" -echo " • Visit http://localhost:${HOST_PORT}/auth/signup to create your admin account" -echo " • The first user created will automatically have admin privileges" -echo "" -if [[ "$LITE_MODE" = true ]]; then - echo "" - print_info "Running in Lite mode — the following services are NOT started:" - echo " • Vespa (vector database)" - echo " • Redis (cache)" - echo " • Model servers (embedding/inference)" - echo " • Background workers (Celery)" - echo "" - print_info "Connectors and RAG search are disabled. LLM chat, tools, user file" - print_info "uploads, Projects, Agent knowledge, and code interpreter still work." -fi -echo "" -print_info "Refer to the README in the ${INSTALL_ROOT} directory for more information." -echo "" -print_info "For help or issues, contact: founders@onyx.app" -echo "" - -# --- GitHub star prompt (inspired by oh-my-codex) --- -# Only prompt in interactive mode and only if gh CLI is available. -# Uses the GitHub API directly (PUT /user/starred) like oh-my-codex. -if is_interactive && command -v gh &>/dev/null; then - prompt_yn_or_default "Enjoying Onyx? Star the repo on GitHub? [Y/n] " "Y" - if [[ ! "$REPLY" =~ ^[Nn] ]]; then - if GH_PAGER='' gh api -X PUT /user/starred/onyx-dot-app/onyx < /dev/null >/dev/null 2>&1; then - print_success "Thanks for the star!" - else - print_info "Star us at: https://github.com/onyx-dot-app/onyx" - fi - fi -fi +exec "$BIN_PATH" deploy install "$@" diff --git a/docs/craft/docker/docker-compose-overview.md b/docs/craft/docker/docker-compose-overview.md index 11c077d95f..ee7a28293e 100644 --- a/docs/craft/docker/docker-compose-overview.md +++ b/docs/craft/docker/docker-compose-overview.md @@ -24,7 +24,7 @@ cp "$WT"/deployment/data/nginx/app.conf.template ~/onyx_data/data/ cp "$WT"/deployment/data/nginx/run-nginx.sh ~/onyx_data/data/nginx/ # 2. Run installer in --local mode with craft. -bash "$WT"/deployment/docker_compose/install.sh --local --include-craft +bash "$WT"/deployment/docker_compose/install.sh --local --include-craft --dir ~/onyx_data # 3. Fix the .env (existing-env install path skips these; see "Required env vars" below). cat >> ~/onyx_data/deployment/.env <<'ENV' @@ -101,13 +101,14 @@ cp "$WT"/deployment/data/nginx/run-nginx.sh ~/onyx_data/data/ ### 2. Run the installer ```bash -bash "$WT"/deployment/docker_compose/install.sh --local --include-craft +bash "$WT"/deployment/docker_compose/install.sh --local --include-craft --dir ~/onyx_data ``` `--local` skips downloads and uses the pre-staged files. `--include-craft` -opts into the Docker sandbox backend. +opts into the Docker sandbox backend. `--dir` points at the staged directory — +without it the installer defaults to `~/.config/onyx`. -The installer is **interactive** — it reads prompts directly from `/dev/tty`, +The installer is **interactive** — it prompts only when stdin is a terminal, so piping `2\n\n` as stdin does not work. Either run it from a terminal or adapt the prompts (Standard mode = `2`, keep existing env = blank). @@ -591,9 +592,9 @@ Next Craft prompt re-provisions with the current code's env injection. cd ~/onyx_data/deployment docker compose -f docker-compose.yml -f docker-compose.craft.yml down -# Or use the installer: -bash /path/to/install.sh --shutdown # stop containers, keep volumes -bash /path/to/install.sh --delete-data # stop AND wipe all data +# Or use the CLI the installer hands over to: +onyx-cli deploy stop # stop containers, keep volumes +onyx-cli deploy uninstall # stop AND wipe all data # Kill orphan sandbox containers: docker ps --filter "name=sandbox-" -q | xargs -r docker rm -f