diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index ed0a264bc..dfd9db923 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -74,6 +74,9 @@ jobs: bash tests/contracts/test-installer-contracts.sh bash tests/contracts/test-preflight-fixtures.sh + - name: Linux install preflight tests + run: bash tests/test-linux-install-preflight.sh + - name: Installer Simulation Harness run: | bash scripts/simulate-installers.sh diff --git a/dream-server/Makefile b/dream-server/Makefile index 1179b714b..713a32e5c 100644 --- a/dream-server/Makefile +++ b/dream-server/Makefile @@ -24,6 +24,9 @@ test: ## Run unit and contract tests @bash tests/contracts/test-installer-contracts.sh @bash tests/contracts/test-preflight-fixtures.sh @echo "" + @echo "=== Linux install preflight ===" + @bash tests/test-linux-install-preflight.sh + @echo "" @echo "=== AMD/Lemonade contracts ===" @bash tests/contracts/test-amd-lemonade-contracts.sh diff --git a/dream-server/docs/EXTENSION-PR-BRANCHING.md b/dream-server/docs/EXTENSION-PR-BRANCHING.md new file mode 100644 index 000000000..e4a549ba5 --- /dev/null +++ b/dream-server/docs/EXTENSION-PR-BRANCHING.md @@ -0,0 +1,23 @@ +# Extension changes: branch targets (main vs resources/dev) + +Dream Server splits **core runtime** (installer, `dream-server/` compose, CLI, shipped extensions under `dream-server/extensions/`) from the larger **extensions library** under `resources/dev/extensions-library/` (catalog of optional services, workflows, and templates). + +Use this guide when coordinating PRs that touch extensions or integrations. + +## Quick rules + +| Change | Target branch | Notes | +|--------|---------------|--------| +| Installer, `dream-cli`, compose base files, dashboard, dashboard-api, **shipped** `dream-server/extensions/services/*` used by default installs | **main** (via normal PR flow) | Follow [EXTENSIONS.md](EXTENSIONS.md) for manifest/schema. | +| Catalog-only updates: new or updated entries under **`resources/dev/extensions-library/`** (extra services, workflows, templates) | Often **`resources/dev`** or team policy for catalog branches | Does not change core install until wired by installer/catalog pipeline; coordinate with maintainers. | +| Docs-only (troubleshooting, field reports) | **main** | Unless your team batches docs on a doc branch. | +| **Both** core behavior and catalog | Split PRs or one PR with explicit maintainer agreement | Easier review when core and catalog are separate. | + +## Linux / Windows parity + +Platform-specific installer scripts (`installers/`, `dream-server/installers/windows/`) usually land on **main** with tests. Cross-platform doc additions (e.g. [LINUX-TROUBLESHOOTING-GUIDE.md](LINUX-TROUBLESHOOTING-GUIDE.md)) should stay aligned with the same check IDs and behavior as the scripts they reference. + +## Questions? + +- Default extensions and schema: [EXTENSIONS.md](EXTENSIONS.md) +- Installer layout: [INSTALLER-ARCHITECTURE.md](INSTALLER-ARCHITECTURE.md) diff --git a/dream-server/docs/FIELD-INSTALL-REPORT-LINUX.md b/dream-server/docs/FIELD-INSTALL-REPORT-LINUX.md new file mode 100644 index 000000000..8da2d8f1e --- /dev/null +++ b/dream-server/docs/FIELD-INSTALL-REPORT-LINUX.md @@ -0,0 +1,73 @@ +# Field install report (Linux) + +Use this template when reporting Linux install problems so maintainers can reproduce and classify issues quickly. **Do not paste secrets** (API keys, passwords, full `.env`). + +## Environment + +| Field | Your value | +|-------|------------| +| Dream Server version / git SHA | | +| Install command used | e.g. `./install.sh`, `./install.sh --dry-run` | +| Distribution | Paste **PRETTY_NAME** and **VERSION_ID** from `/etc/os-release` | +| Kernel | Output of `uname -a` | +| Architecture | e.g. `x86_64`, `aarch64` | +| Install type | Bare metal / VM / cloud / WSL2 (if applicable) | + +## Hardware + +| Field | Your value | +|-------|------------| +| GPU | `lspci` line or “CPU only” | +| NVIDIA | Output of `nvidia-smi` (if any) or “N/A” | +| RAM | Approximate GB | + +## Docker + +| Field | Your value | +|-------|------------| +| Docker version | `docker --version` | +| Compose | `docker compose version` or `docker-compose version` | +| Docker info | Does `docker info` work without sudo? (yes/no) | +| User in `docker` group? | (yes/no / unknown) | + +## Structured preflight (required) + +From the `dream-server` directory, run: + +```bash +./scripts/linux-install-preflight.sh --json +``` + +Paste the **JSON output** (redact paths if needed). If the command fails, paste the **human** output: + +```bash +./scripts/linux-install-preflight.sh +``` + +## Service preflight (after install) + +If services are installed, also run: + +```bash +./dream-preflight.sh +``` + +Paste the last 40 lines of output (or attach the log path printed at the end). + +## Logs to attach (pick what applies) + +- Installer log if referenced by the installer. +- `docker compose` error from the failing command (not the entire daemon log unless asked). +- For GPU issues: output of `docker info | grep -i nvidia` and relevant `nvidia-smi`. + +## Privacy checklist + +- [ ] Removed API keys and passwords from pasted content. +- [ ] Redacted internal hostnames if required by your employer. +- [ ] Confirmed no private URLs or tokens in compose overrides. + +## Related docs + +- [LINUX-TROUBLESHOOTING-GUIDE.md](LINUX-TROUBLESHOOTING-GUIDE.md) — maps check IDs to fixes. +- [INSTALL-TROUBLESHOOTING.md](INSTALL-TROUBLESHOOTING.md) +- [SUPPORT-MATRIX.md](SUPPORT-MATRIX.md) diff --git a/dream-server/docs/INSTALL-TROUBLESHOOTING.md b/dream-server/docs/INSTALL-TROUBLESHOOTING.md index 8857b3983..60fdff047 100644 --- a/dream-server/docs/INSTALL-TROUBLESHOOTING.md +++ b/dream-server/docs/INSTALL-TROUBLESHOOTING.md @@ -2,6 +2,8 @@ This guide provides solutions for common issues encountered during the installation of Dream Server. +For **Linux**, run `./scripts/linux-install-preflight.sh` (or `./dream-preflight.sh --install-env`) for a structured report with stable check IDs; see [LINUX-TROUBLESHOOTING-GUIDE.md](LINUX-TROUBLESHOOTING-GUIDE.md) for ID-by-ID fixes. + ## Docker Issues ### Problem: Docker Not Installed diff --git a/dream-server/docs/LINUX-PORTABILITY.md b/dream-server/docs/LINUX-PORTABILITY.md index 808327a2c..03082a234 100644 --- a/dream-server/docs/LINUX-PORTABILITY.md +++ b/dream-server/docs/LINUX-PORTABILITY.md @@ -16,6 +16,9 @@ Services are declared under `extensions/services//manifest.yaml` for dashb 1. Docker and Compose v2 work; your user can run containers (e.g. member of the `docker` group). 2. `./install.sh --dry-run` finishes without errors. -3. After a real install, run `./dream-preflight.sh` and `scripts/dream-doctor.sh` if you use them. +3. Before or after install, run **`./scripts/linux-install-preflight.sh`** (or `./dream-preflight.sh --install-env`) for a structured environment report with stable check IDs and optional `--json` output. +4. After a real install, run `./dream-preflight.sh` (service health) and `scripts/dream-doctor.sh` if you use them. -More context: [SUPPORT-MATRIX.md](SUPPORT-MATRIX.md), [TROUBLESHOOTING.md](TROUBLESHOOTING.md). +More context: [LINUX-TROUBLESHOOTING-GUIDE.md](LINUX-TROUBLESHOOTING-GUIDE.md) (ID-indexed fixes), [FIELD-INSTALL-REPORT-LINUX.md](FIELD-INSTALL-REPORT-LINUX.md) (bug report template), [SUPPORT-MATRIX.md](SUPPORT-MATRIX.md), [TROUBLESHOOTING.md](TROUBLESHOOTING.md). + +Extension PRs that touch catalog vs core: [EXTENSION-PR-BRANCHING.md](EXTENSION-PR-BRANCHING.md). diff --git a/dream-server/docs/LINUX-TROUBLESHOOTING-GUIDE.md b/dream-server/docs/LINUX-TROUBLESHOOTING-GUIDE.md new file mode 100644 index 000000000..1b2ae671d --- /dev/null +++ b/dream-server/docs/LINUX-TROUBLESHOOTING-GUIDE.md @@ -0,0 +1,243 @@ +# Linux troubleshooting guide + +Structured reference for **Linux install and runtime** issues. When you run `./scripts/linux-install-preflight.sh` or `./dream-preflight.sh --install-env`, each line uses a **check ID**. Use this document to jump from an ID to likely causes and fixes. + +**Related:** [INSTALL-TROUBLESHOOTING.md](INSTALL-TROUBLESHOOTING.md), [TROUBLESHOOTING.md](TROUBLESHOOTING.md), [LINUX-PORTABILITY.md](LINUX-PORTABILITY.md), [FIELD-INSTALL-REPORT-LINUX.md](FIELD-INSTALL-REPORT-LINUX.md). + +--- + +## Using the install preflight + +From the `dream-server` directory: + +```bash +./scripts/linux-install-preflight.sh +./scripts/linux-install-preflight.sh --json +./scripts/linux-install-preflight.sh --json-file /tmp/preflight.json +./dream-preflight.sh --install-env --json # same as linux-install-preflight.sh +``` + +- **`--strict`** — exit with failure if any check is **warn** or **fail** (useful in automation). +- **`--dream-root PATH`** — directory used for the disk-space probe (default: dream-server root). +- **`--min-disk-gb N`** — minimum free space in GB to treat as OK (default: 15). + +JSON output includes `schema_version`, `kind: linux-install-preflight`, `distro`, `kernel`, `checks[]`, and `summary`. + +--- + +## Check IDs (alphabetical) + +### CGROUP_V2 + +**Symptoms:** Warning that cgroup v2 was not detected. + +**Typical causes:** Older kernels, unusual container hosts, or mis-mounted `/sys/fs/cgroup`. + +**Fixes:** + +- On normal desktop/server distros from the last several years, cgroup v2 is standard; if Docker works, you can ignore this warning. +- If Docker fails with cgroup-related errors, ensure you are not mixing rootless Docker with a broken delegated cgroup setup. See your distro’s Docker documentation. + +--- + +### COMPOSE_CLI + +**Symptoms:** **Fail** — neither `docker compose` nor `docker-compose` works. + +**Typical causes:** Docker Engine installed without the Compose v2 plugin; very old Docker packages; PATH issues. + +**Fixes:** + +- Install **Docker Compose v2** (plugin): often package `docker-compose-plugin` (Debian/Ubuntu) or equivalent. +- Legacy: install standalone `docker-compose` v1 if you must (less ideal). + +Verify: + +```bash +docker compose version +# or +docker-compose version +``` + +--- + +### COMPOSE_FILES + +**Symptoms:** Warning — `docker-compose.base.yml` / `docker-compose.yml` missing under the dream-server tree. + +**Typical causes:** Running the script from the wrong directory; incomplete checkout. + +**Fixes:** + +- `cd` into the extracted **dream-server** directory that contains the compose files from the release or git clone. +- Re-download or re-clone the repository if files are missing. + +--- + +### CURL_INSTALLED + +**Symptoms:** **Warn** — `curl` not in PATH. + +**Typical causes:** Minimal container or netinst image without `curl`. + +**Fixes:** + +```bash +# Debian/Ubuntu +sudo apt update && sudo apt install -y curl + +# Fedora +sudo dnf install -y curl + +# Arch +sudo pacman -S curl +``` + +The installer and many health checks expect `curl`. + +--- + +### DISK_SPACE + +**Symptoms:** **Warn** — low free space on `--dream-root` (or default dream-server root). + +**Typical causes:** Small root partition; large existing Docker data; wrong path passed to `--dream-root`. + +**Fixes:** + +- Free space or move the Dream Server data directory to a larger volume (see installer docs for `data/` layout). +- Point `--dream-root` at the filesystem you intend to use for the install. + +--- + +### DISTRO_INFO + +**Symptoms:** **Fail** — `/etc/os-release` missing. + +**Typical causes:** Non-Linux environment; severely broken chroot. + +**Fixes:** + +- Run on a supported Linux distribution. For Windows, use the Windows installer + WSL2; for macOS, use the macOS path. + +--- + +### DOCKER_DAEMON + +**Symptoms:** **Fail** — `docker info` does not succeed. + +**Typical causes:** Docker service stopped; user lacks permission to the Docker socket; rootless Docker socket not in environment. + +**Fixes:** + +```bash +# systemd +sudo systemctl start docker +sudo systemctl enable docker + +# Permission denied on /var/run/docker.sock +sudo usermod -aG docker "$USER" +# then log out and back in (or newgrp docker) +``` + +Confirm: + +```bash +docker info +docker run --rm hello-world +``` + +--- + +### DOCKER_INSTALLED + +**Symptoms:** **Fail** — `docker` command not found. + +**Typical causes:** Docker Engine never installed; PATH not including Docker binaries. + +**Fixes:** + +- Install [Docker Engine](https://docs.docker.com/engine/install/) for your distro. +- Ensure `/usr/bin` (or wherever `docker` lives) is on your `PATH`. + +--- + +### KERNEL_INFO + +**Symptoms:** Always **pass** — prints `uname -r` (informational). + +**Note:** Use this value when comparing against [SUPPORT-MATRIX.md](SUPPORT-MATRIX.md) or when filing bugs. + +--- + +### JQ_INSTALLED + +**Symptoms:** **Warn** — `jq` not installed. + +**Typical causes:** Minimal system; skipped optional packages. + +**Fixes:** + +- The Dream Server installer often installs `jq` automatically when possible. You can install manually: + +```bash +sudo apt install -y jq # Debian/Ubuntu +sudo dnf install -y jq # Fedora +``` + +--- + +### KERNEL_INFO + +**Symptoms:** Always **pass** with kernel version (informational). + +**Note:** Very old kernels may be incompatible with modern Docker or NVIDIA drivers; if you hit exotic bugs, compare with [SUPPORT-MATRIX.md](SUPPORT-MATRIX.md). + +--- + +### NVIDIA_CONTAINER_RUNTIME + +**Symptoms:** **Warn** — `nvidia-smi` works but Docker does not show an NVIDIA runtime. + +**Typical causes:** NVIDIA drivers installed but [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) not configured for Docker. + +**Fixes:** + +- Install and configure `nvidia-container-toolkit`, then restart Docker: + +```bash +sudo nvidia-ctk runtime configure --runtime=docker +sudo systemctl restart docker +``` + +- Verify: `docker info | grep -i nvidia` + +If you are intentionally **CPU-only**, you can ignore this after setting `GPU_BACKEND=cpu` in your capability/install profile. + +--- + +## Common cross-cutting issues + +### Firewall blocking local ports + +If the dashboard or WebUI is unreachable from another machine, check `ufw` / `firewalld` / `iptables` for the ports in `config/ports.json` (or your `.env` overrides). + +### SELinux / AppArmor + +Rare compose failures can be policy-related. Check audit logs; temporarily testing with permissive profiles is diagnostic only — prefer documented volume labels and permissions. + +### Docker BuildKit / proxy + +Corporate proxies may require `HTTP_PROXY` / `HTTPS_PROXY` in Docker’s systemd drop-in or `~/.docker/config.json` for image pulls. + +--- + +## Getting help + +When opening an issue, attach a **field install report** (see [FIELD-INSTALL-REPORT-LINUX.md](FIELD-INSTALL-REPORT-LINUX.md)) and the JSON from: + +```bash +./scripts/linux-install-preflight.sh --json +``` + +Redact secrets, internal hostnames, and paths you do not want to share. diff --git a/dream-server/dream-preflight.sh b/dream-server/dream-preflight.sh index 67fe85e08..af0aeef9c 100755 --- a/dream-server/dream-preflight.sh +++ b/dream-server/dream-preflight.sh @@ -3,11 +3,19 @@ # Validates all services start correctly before user interaction # Backend-aware: detects AMD vs NVIDIA (both use llama-server) # Usage: ./dream-preflight.sh +# ./dream-preflight.sh --install-env # Linux install environment report (JSON: see scripts/linux-install-preflight.sh --help) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DREAM_DIR="$SCRIPT_DIR" + +case "${1:-}" in + --install-env|--env-report) + shift + exec "$SCRIPT_DIR/scripts/linux-install-preflight.sh" "$@" + ;; +esac LOG_FILE="$DREAM_DIR/preflight-$(date +%Y%m%d-%H%M%S).log" # Safe .env loading (no eval; use lib/safe-env.sh) diff --git a/dream-server/scripts/extension-runtime-check.sh b/dream-server/scripts/extension-runtime-check.sh index b3fd7747b..b2ce0bf13 100644 --- a/dream-server/scripts/extension-runtime-check.sh +++ b/dream-server/scripts/extension-runtime-check.sh @@ -53,12 +53,12 @@ if [[ ${#SERVICE_IDS[@]} -eq 0 ]]; then fi if ! command -v docker >/dev/null 2>&1; then - info "docker not in PATH — skipping extension runtime check" + info "Extension runtime check — docker not in PATH (skipping)" exit 0 fi if ! docker info >/dev/null 2>&1; then - info "Docker daemon not reachable — skipping extension runtime check" + info "Extension runtime check — Docker daemon not reachable (skipping)" exit 0 fi diff --git a/dream-server/scripts/linux-install-preflight.sh b/dream-server/scripts/linux-install-preflight.sh new file mode 100755 index 000000000..27ce14e49 --- /dev/null +++ b/dream-server/scripts/linux-install-preflight.sh @@ -0,0 +1,347 @@ +#!/usr/bin/env bash +# Linux install environment preflight — structured checks with stable IDs and JSON output. +# Use before or during install when services are not yet up (unlike ./dream-preflight.sh). +# +# Usage: +# ./scripts/linux-install-preflight.sh # human-readable report +# ./scripts/linux-install-preflight.sh --json # JSON on stdout +# ./scripts/linux-install-preflight.sh --json-file /tmp/report.json +# ./scripts/linux-install-preflight.sh --strict # exit 1 if any warn or fail +# +# Also reachable as: ./dream-preflight.sh --install-env [same args] + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +OUTPUT_MODE="human" +JSON_FILE="" +STRICT=false +DREAM_ROOT="${DREAM_ROOT:-$ROOT_DIR}" +MIN_DISK_GB_FREE="${MIN_DISK_GB_FREE:-15}" + +while [[ $# -gt 0 ]]; do + case "$1" in + --json) + OUTPUT_MODE="json" + shift + ;; + --json-file) + JSON_FILE="${2:-}" + OUTPUT_MODE="json" + shift 2 + ;; + --strict) + STRICT=true + shift + ;; + --dream-root) + DREAM_ROOT="${2:-}" + shift 2 + ;; + --min-disk-gb) + MIN_DISK_GB_FREE="${2:-}" + shift 2 + ;; + -h|--help) + sed -n '1,20p' "$0" | tail -n +2 + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + exit 2 + ;; + esac +done + +CHECKS_JSONL="$(mktemp)" +trap 'rm -f "$CHECKS_JSONL"' EXIT + +append_check() { + # id, status, message, remediation — safe for special characters via env + export LP_ID="$1" LP_STATUS="$2" LP_MSG="$3" LP_FIX="${4:-}" + python3 -c ' +import json, os +print(json.dumps({ + "id": os.environ["LP_ID"], + "status": os.environ["LP_STATUS"], + "message": os.environ["LP_MSG"], + "remediation": os.environ.get("LP_FIX", ""), +})) +' >>"$CHECKS_JSONL" +} + +# --- Distro fingerprint (from /etc/os-release) --- +DISTRO_ID="" +DISTRO_VERSION_ID="" +DISTRO_PRETTY="" +DISTRO_LIKE="" +if [[ -f /etc/os-release ]]; then + # shellcheck source=/dev/null + source /etc/os-release + DISTRO_ID="${ID:-}" + DISTRO_VERSION_ID="${VERSION_ID:-}" + DISTRO_PRETTY="${PRETTY_NAME:-}" + DISTRO_LIKE="${ID_LIKE:-}" + append_check "DISTRO_INFO" "pass" \ + "Linux distro: ${PRETTY_NAME:-unknown} (ID=${ID:-?}, VERSION_ID=${VERSION_ID:-?})" \ + "" +else + append_check "DISTRO_INFO" "fail" \ + "/etc/os-release not found — installer expects a Linux environment" \ + "Run on a supported Linux distribution or use the platform-specific installer." +fi + +KERNEL="$(uname -r 2>/dev/null || echo unknown)" +append_check "KERNEL_INFO" "pass" "Kernel: $KERNEL" "" + +# --- curl (used by service preflight and many scripts) --- +if command -v curl >/dev/null 2>&1; then + append_check "CURL_INSTALLED" "pass" "curl is available" "" +else + append_check "CURL_INSTALLED" "warn" \ + "curl not found — installer and health checks expect it" \ + "Install curl (e.g. apt install curl / dnf install curl) and re-run." +fi + +# --- Docker CLI --- +if ! command -v docker >/dev/null 2>&1; then + append_check "DOCKER_INSTALLED" "fail" \ + "Docker CLI not found in PATH" \ + "Install Docker Engine and ensure your user can run docker (see LINUX-TROUBLESHOOTING-GUIDE.md#docker_installed)." +else + DV="$(docker --version 2>/dev/null | head -1 || true)" + append_check "DOCKER_INSTALLED" "pass" "Docker CLI: ${DV:-present}" "" +fi + +# --- Docker daemon --- +DOCKER_INFO_OK=false +if command -v docker >/dev/null 2>&1; then + if docker info >/dev/null 2>&1; then + DOCKER_INFO_OK=true + append_check "DOCKER_DAEMON" "pass" "Docker daemon is reachable" "" + else + append_check "DOCKER_DAEMON" "fail" \ + "Docker daemon not running or not accessible" \ + "Start the service (e.g. sudo systemctl start docker) or log in to Docker Desktop; add your user to the docker group if permission denied (see LINUX-TROUBLESHOOTING-GUIDE.md#docker_daemon)." + fi +else + append_check "DOCKER_DAEMON" "fail" "Skipped — Docker CLI missing" "" +fi + +# --- Docker Compose v2 / v1 --- +if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then + CV="$(docker compose version 2>/dev/null | head -1 || true)" + append_check "COMPOSE_CLI" "pass" "Compose: $CV" "" +elif command -v docker-compose >/dev/null 2>&1; then + CV="$(docker-compose version --short 2>/dev/null || docker-compose version 2>/dev/null | head -1)" + append_check "COMPOSE_CLI" "pass" "docker-compose (legacy): $CV" "" +else + append_check "COMPOSE_CLI" "fail" \ + "Neither 'docker compose' nor 'docker-compose' is available" \ + "Install Docker Compose v2 plugin or docker-compose; see LINUX-TROUBLESHOOTING-GUIDE.md#compose_cli." +fi + +# --- NVIDIA Docker runtime (only if nvidia-smi works) --- +if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi --query-gpu=name --format=csv,noheader >/dev/null 2>&1; then + if [[ "$DOCKER_INFO_OK" == true ]]; then + if docker info 2>/dev/null | grep -qi nvidia; then + append_check "NVIDIA_CONTAINER_RUNTIME" "pass" "NVIDIA Container Toolkit / runtime visible to Docker" "" + else + append_check "NVIDIA_CONTAINER_RUNTIME" "warn" \ + "GPU visible to nvidia-smi but Docker does not report NVIDIA runtime" \ + "Install/configure nvidia-container-toolkit so GPU containers work (see LINUX-TROUBLESHOOTING-GUIDE.md#nvidia_container_runtime)." + fi + else + append_check "NVIDIA_CONTAINER_RUNTIME" "warn" \ + "nvidia-smi works but Docker daemon check failed — could not verify NVIDIA runtime" \ + "Fix Docker daemon access first, then install nvidia-container-toolkit if needed." + fi +else + append_check "NVIDIA_CONTAINER_RUNTIME" "pass" "No NVIDIA GPU detected via nvidia-smi — check skipped" "" +fi + +# --- Free disk space for Dream Server root --- +if [[ -d "$DREAM_ROOT" ]]; then + # POSIX-friendly: df -P, parse available KB + if DFOUT="$(df -Pk "$DREAM_ROOT" 2>/dev/null | tail -1)"; then + AVAIL_KB="$(echo "$DFOUT" | awk '{print $4}')" + if [[ "$AVAIL_KB" =~ ^[0-9]+$ ]]; then + AVAIL_GB=$((AVAIL_KB / 1048576)) + if [[ "$AVAIL_GB" -ge "$MIN_DISK_GB_FREE" ]]; then + append_check "DISK_SPACE" "pass" \ + "Free space on $DREAM_ROOT: ~${AVAIL_GB}GB (min ${MIN_DISK_GB_FREE}GB)" "" + else + append_check "DISK_SPACE" "warn" \ + "Low free space on $DREAM_ROOT: ~${AVAIL_GB}GB (recommended ≥${MIN_DISK_GB_FREE}GB free)" \ + "Free disk space or set DREAM_ROOT to a volume with more room; see LINUX-TROUBLESHOOTING-GUIDE.md#disk_space." + fi + else + append_check "DISK_SPACE" "warn" "Could not parse free space for $DREAM_ROOT" "" + fi + else + append_check "DISK_SPACE" "warn" "df failed for $DREAM_ROOT" "" + fi +else + append_check "DISK_SPACE" "warn" "DREAM_ROOT does not exist yet: $DREAM_ROOT" \ + "Create the directory or run from the extracted dream-server tree." +fi + +# --- cgroup v2 (optional signal) --- +if [[ -f /sys/fs/cgroup/cgroup.controllers ]]; then + append_check "CGROUP_V2" "pass" "cgroup v2 detected (/sys/fs/cgroup/cgroup.controllers present)" "" +else + append_check "CGROUP_V2" "warn" \ + "cgroup v2 not detected — some Docker/rootless setups may differ" \ + "Usually fine on modern distros; if Docker fails oddly, see LINUX-TROUBLESHOOTING-GUIDE.md#cgroups." +fi + +# --- jq (installer often installs it; nice to have) --- +if command -v jq >/dev/null 2>&1; then + append_check "JQ_INSTALLED" "pass" "jq available for JSON tooling" "" +else + append_check "JQ_INSTALLED" "warn" \ + "jq not found — installer may install it; some scripts expect it" \ + "Install jq for smoother tooling (see INSTALL-TROUBLESHOOTING.md)." +fi + +# --- Compose files present (expected when run from repo tree) --- +if [[ -f "$ROOT_DIR/docker-compose.base.yml" ]] || [[ -f "$ROOT_DIR/docker-compose.yml" ]]; then + append_check "COMPOSE_FILES" "pass" "Compose files present under dream-server root" "" +else + append_check "COMPOSE_FILES" "warn" \ + "No docker-compose.base.yml or docker-compose.yml in $ROOT_DIR" \ + "Run this script from the extracted Dream Server source tree (dream-server/)." +fi + +emit_report() { + python3 - "$CHECKS_JSONL" "$ROOT_DIR" "$KERNEL" "$DISTRO_ID" "$DISTRO_VERSION_ID" "$DISTRO_PRETTY" "$DISTRO_LIKE" "$DREAM_ROOT" "$MIN_DISK_GB_FREE" <<'PY' +import json +import sys +from datetime import datetime, timezone + +checks_path = sys.argv[1] +root_dir = sys.argv[2] +kernel = sys.argv[3] +distro_id = sys.argv[4] +distro_vid = sys.argv[5] +distro_pretty = sys.argv[6] +distro_like = sys.argv[7] +dream_root = sys.argv[8] +min_disk = sys.argv[9] + +checks = [] +with open(checks_path, encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line: + continue + checks.append(json.loads(line)) + +fail_n = sum(1 for c in checks if c["status"] == "fail") +warn_n = sum(1 for c in checks if c["status"] == "warn") +pass_n = sum(1 for c in checks if c["status"] == "pass") + +exit_ok = fail_n == 0 +report = { + "schema_version": "1", + "kind": "linux-install-preflight", + "generated_at": datetime.now(timezone.utc).isoformat(), + "distro": { + "id": distro_id, + "version_id": distro_vid, + "pretty_name": distro_pretty, + "id_like": distro_like, + }, + "kernel": kernel, + "dream_root": dream_root, + "min_disk_gb_free": min_disk, + "checks": checks, + "summary": { + "pass": pass_n, + "warn": warn_n, + "fail": fail_n, + "exit_ok": exit_ok, + }, +} +print(json.dumps(report, indent=2)) +PY +} + +REPORT_JSON="$(emit_report)" + +EXIT_CODE=0 +echo "$REPORT_JSON" | python3 -c ' +import json,sys +r=json.load(sys.stdin) +s=r["summary"] +sys.exit(0 if s["exit_ok"] else 1) +' || EXIT_CODE=1 + +if [[ "$STRICT" == true ]]; then + echo "$REPORT_JSON" | python3 -c ' +import json,sys +r=json.load(sys.stdin) +s=r["summary"] +sys.exit(0 if s["fail"]==0 and s["warn"]==0 else 1) +' || EXIT_CODE=1 +fi + +if [[ "$OUTPUT_MODE" == "json" ]]; then + echo "$REPORT_JSON" + if [[ -n "$JSON_FILE" ]]; then + printf '%s\n' "$REPORT_JSON" >"$JSON_FILE" + fi + exit "$EXIT_CODE" +fi + +# --- Human output --- +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +echo -e "${BOLD}${CYAN}Linux install preflight${NC} (structured checks)" +echo "Dream root: $DREAM_ROOT" +echo "" + +while IFS= read -r line; do + [[ -z "$line" ]] && continue + mapfile -t _trip < <(python3 -c 'import json,sys; o=json.loads(sys.argv[1]); print(o["id"]); print(o["status"]); print(o["message"])' "$line") + id="${_trip[0]}" + st="${_trip[1]}" + msg="${_trip[2]}" + case "$st" in + pass) sig="${GREEN}PASS${NC}" ;; + warn) sig="${YELLOW}WARN${NC}" ;; + fail) sig="${RED}FAIL${NC}" ;; + *) sig="$st" ;; + esac + echo -e "[$sig] ${BOLD}${id}${NC}: $msg" +done <"$CHECKS_JSONL" + +echo "" +echo -e "${BOLD}Summary${NC}" +echo "$REPORT_JSON" | python3 -c ' +import json,sys +r=json.load(sys.stdin) +s=r["summary"] +print(" pass:", s["pass"], " warn:", s["warn"], " fail:", s["fail"]) +print(" exit_ok:", "true" if s["exit_ok"] else "false") +' + +if [[ "$EXIT_CODE" -eq 0 ]]; then + echo -e "${GREEN}Preflight OK (no failures).${NC}" +else + echo -e "${RED}Preflight failed — see FAIL checks and LINUX-TROUBLESHOOTING-GUIDE.md${NC}" +fi + +if [[ -n "$JSON_FILE" ]]; then + printf '%s\n' "$REPORT_JSON" >"$JSON_FILE" + echo "JSON written to: $JSON_FILE" +fi + +exit "$EXIT_CODE" diff --git a/dream-server/tests/contracts/test-port-contracts.sh b/dream-server/tests/contracts/test-port-contracts.sh index 990882b43..e8910722f 100755 --- a/dream-server/tests/contracts/test-port-contracts.sh +++ b/dream-server/tests/contracts/test-port-contracts.sh @@ -5,6 +5,8 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$ROOT_DIR" python3 - "$ROOT_DIR" <<'PY' +from __future__ import annotations + import json import re import sys diff --git a/dream-server/tests/contracts/test-preflight-fixtures.sh b/dream-server/tests/contracts/test-preflight-fixtures.sh index 8c85e01d4..a8dcda1cd 100755 --- a/dream-server/tests/contracts/test-preflight-fixtures.sh +++ b/dream-server/tests/contracts/test-preflight-fixtures.sh @@ -4,11 +4,14 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$ROOT_DIR" -require_jq() { - command -v jq >/dev/null 2>&1 || { - echo "[FAIL] jq is required" - exit 1 - } +# Prefer jq; fall back to Python so minimal dev images can run contracts. +json_summary_blockers() { + local f="$1" + if command -v jq >/dev/null 2>&1; then + jq -r '.summary.blockers' "$f" + else + python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["summary"]["blockers"])' "$f" + fi } assert_eq() { @@ -21,8 +24,6 @@ assert_eq() { fi } -require_jq - tmpdir="$(mktemp -d)" trap 'rm -rf "$tmpdir"' EXIT @@ -39,7 +40,7 @@ scripts/preflight-engine.sh \ --compose-overlays docker-compose.base.yml,docker-compose.nvidia.yml \ --script-dir "$ROOT_DIR" \ --env >/dev/null -blockers="$(jq -r '.summary.blockers' "$tmpdir/linux-nvidia-good.json")" +blockers="$(json_summary_blockers "$tmpdir/linux-nvidia-good.json")" assert_eq "$blockers" "0" "linux-nvidia-good blockers" echo "[contract] preflight fixture: windows-mvp-good" @@ -55,7 +56,7 @@ scripts/preflight-engine.sh \ --compose-overlays docker-compose.base.yml,docker-compose.nvidia.yml \ --script-dir "$ROOT_DIR" \ --env >/dev/null -blockers="$(jq -r '.summary.blockers' "$tmpdir/windows-mvp-good.json")" +blockers="$(json_summary_blockers "$tmpdir/windows-mvp-good.json")" assert_eq "$blockers" "0" "windows-mvp-good blockers" echo "[contract] preflight fixture: macos-mvp-good" @@ -71,7 +72,7 @@ scripts/preflight-engine.sh \ --compose-overlays docker-compose.base.yml,docker-compose.amd.yml \ --script-dir "$ROOT_DIR" \ --env >/dev/null -blockers="$(jq -r '.summary.blockers' "$tmpdir/macos-mvp-good.json")" +blockers="$(json_summary_blockers "$tmpdir/macos-mvp-good.json")" assert_eq "$blockers" "0" "macos-mvp-good blockers" echo "[contract] preflight fixture: disk-blocker" @@ -87,7 +88,7 @@ scripts/preflight-engine.sh \ --compose-overlays docker-compose.base.yml,docker-compose.nvidia.yml \ --script-dir "$ROOT_DIR" \ --env >/dev/null -blockers="$(jq -r '.summary.blockers' "$tmpdir/disk-blocker.json")" +blockers="$(json_summary_blockers "$tmpdir/disk-blocker.json")" if [[ "$blockers" -lt 1 ]]; then echo "[FAIL] disk-blocker expected >=1 blocker, got $blockers" exit 1 diff --git a/dream-server/tests/test-linux-install-preflight.sh b/dream-server/tests/test-linux-install-preflight.sh new file mode 100755 index 000000000..589801a1d --- /dev/null +++ b/dream-server/tests/test-linux-install-preflight.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# Tests for scripts/linux-install-preflight.sh (static + JSON contract; no Docker required for schema). +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +LP="$ROOT_DIR/scripts/linux-install-preflight.sh" +ROOT_PREFLIGHT="$ROOT_DIR/dream-preflight.sh" + +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' + +PASSED=0 +FAILED=0 +pass() { printf " ${GREEN}✓ PASS${NC} %s\n" "$1"; PASSED=$((PASSED + 1)); } +fail() { printf " ${RED}✗ FAIL${NC} %s\n" "$1"; FAILED=$((FAILED + 1)); } + +echo "" +echo "╔══════════════════════════════════════════════════════════╗" +echo "║ linux-install-preflight.sh tests ║" +echo "╚══════════════════════════════════════════════════════════╝" +echo "" + +if [[ ! -f "$LP" ]]; then + fail "linux-install-preflight.sh missing at $LP" + echo "Result: $PASSED passed, $FAILED failed" + exit 1 +fi +pass "linux-install-preflight.sh exists" + +if bash -n "$LP" 2>/dev/null; then + pass "bash -n syntax check passes" +else + fail "bash -n syntax check failed" +fi + +if grep -q 'set -euo pipefail' "$LP"; then + pass "set -euo pipefail present" +else + fail "set -euo pipefail missing" +fi + +if grep -q 'schema_version' "$LP" && grep -q 'linux-install-preflight' "$LP"; then + pass "JSON report kind/schema referenced in script" +else + fail "Missing schema_version or kind in emitter" +fi + +# JSON contract: required top-level keys +JSON_OUT="$(mktemp)" +trap 'rm -f "$JSON_OUT"' EXIT +if "$LP" --json >"$JSON_OUT" 2>/dev/null || true; then + : +fi +if command -v python3 >/dev/null 2>&1; then + if python3 - </dev/null; then + pass "dream-preflight.sh still passes bash -n" +else + fail "dream-preflight.sh bash -n failed after edit" +fi + +echo "" +echo "Result: $PASSED passed, $FAILED failed" +echo "" +[[ $FAILED -eq 0 ]] diff --git a/dream-server/tests/test-validate-env.sh b/dream-server/tests/test-validate-env.sh index a009e47c7..eb85d9b87 100755 --- a/dream-server/tests/test-validate-env.sh +++ b/dream-server/tests/test-validate-env.sh @@ -100,6 +100,7 @@ fi # 5. .env missing one required key → exit 2 cat > "$TMP_DIR/missing.env" <<'EOF' WEBUI_SECRET=test-secret +SEARXNG_SECRET=searxsecret N8N_USER=admin N8N_PASS=testpass LITELLM_KEY=testkey