The ODS installer is modular — 19 installer library modules, the shared service registry, and 13 ordered phases. This guide is the map for understanding, changing, and reviewing install behavior without missing a parallel Linux/macOS/Windows or upgrade-time writer.
For the per-phase ownership and validation contract, see INSTALLER_PHASE_CONTRACTS.md.
installers/
lib/ # Pure libraries — define functions, no side effects
constants.sh # Colors, paths, VERSION, timezone detection
logging.sh # log(), success(), warn(), error(), install_elapsed()
ui.sh # CRT theme: typing effects, spinners, boot splash, lore
sudo.sh # privilege escalation helpers
detection.sh # GPU detection, capability profiles, backend contracts, secure boot fix
host-arch.sh # host architecture detection and normalization
tier-map.sh # resolve_tier_config() — tier → model/GGUF/context
docker-images.sh # image pull/build planning
compose-select.sh # resolve_compose_config() — compose overlay files + flags
compose-failure-report.sh # useful diagnostics when compose fails
readiness-summary.sh # post-start readiness output
packaging.sh # package-manager abstraction helpers
python-runtime.sh # python/python3 discovery and bootstrap
progress.sh # phase progress reporting
amd-topo.sh # AMD topology helpers
background-tasks.sh # async task helpers
bootstrap-model.sh # bootstrap model selection helpers
nvidia-topo.sh # NVIDIA topology helpers
path-utils.sh # portable path helpers
phases/ # Sequential install steps — execute on source
01-preflight.sh # Root/OS/tools checks, existing installation check
02-detection.sh # Hardware detection → tier assignment → compose config
02b-external-services.sh # Validate/offer host Ollama or LM Studio reuse
03-features.sh # Interactive feature selection menu
04-requirements.sh # RAM, disk, GPU, and port availability checks
05-docker.sh # Install Docker, Docker Compose, NVIDIA Container Toolkit
06-directories.sh # Create dirs, copy source, generate .env, configure services
07-devtools.sh # Install Claude Code, Codex CLI, OpenCode
08-images.sh # Build image pull list and download all Docker images
09-offline.sh # Configure M1 offline/air-gapped operation
10-amd-tuning.sh # AMD APU sysctl, modprobe, GRUB, and tuned setup
11-services.sh # Download GGUF model, generate models.ini, launch stack
12-health.sh # Verify services responding, configure Perplexica, pre-download STT
13-summary.sh # URLs, desktop shortcut, sidebar pin, summary JSON
install-core.sh # Orchestrator: trap → source libs → parse args → source phases
lib/service-registry.sh # Shared service manifest/port registry loaded by installer + CLI
Libraries are safe to source. Every file in lib/ defines functions only — no
side effects. Sourcing them loads function definitions and constants into the shell
without executing anything. They must be sourced in order because later libraries
depend on earlier ones (e.g., logging.sh uses color codes from constants.sh).
Phases execute immediately when sourced. Each file in phases/ is a
self-contained install step that runs its logic the moment source evaluates it.
Phases rely on the functions defined by lib/ and on global variables set by
earlier phases (e.g., phase 04 checks the GPU tier assigned by phase 02).
The orchestrator is thin. install-core.sh sets up interrupt traps, sources
the library modules and lib/service-registry.sh, parses CLI arguments, then
sources the 13 phases in order. All files share one global bash namespace —
everything is sourced, not exec'd.
Every module uses a standardized header:
#!/bin/bash
# ============================================================================
# ODS Installer — <Module Name>
# ============================================================================
# Part of: installers/lib/ (or installers/phases/)
# Purpose: <one-line description>
#
# Expects: <comma-separated list of globals/functions this file reads>
# Provides: <comma-separated list of globals/functions this file defines>
#
# Modder notes:
# <when and why you'd edit this file>
# ============================================================================| Field | Meaning |
|---|---|
| Purpose | What this file does in one line |
| Expects | Globals and functions that must already exist when this file is sourced |
| Provides | Globals and functions this file creates for later files to use |
| Modder notes | Plain-English hint for customizers |
If you add a new file, copy this template. The Expects / Provides chain is
how you trace data flow without reading every line.
Common customizations and exactly where to make them:
| Recipe | What to edit | How |
|---|---|---|
| Add a hardware tier | lib/tier-map.sh + lib/detection.sh |
Add a case in resolve_tier_config() (tier-map.sh) and a detection path in detection.sh. Also update lib/compose-select.sh if a new compose overlay is needed, and add the tier to QUICKSTART.md and README.md hardware tables. |
| Swap CRT theme colors | lib/constants.sh |
Change the ANSI escape code variables (GRN, AMB, RED, etc.) near the top |
| Change lore messages | lib/ui.sh |
Edit the LORE_MESSAGES[] array — add, remove, or reword entries |
| Change boot splash | lib/ui.sh |
Edit the show_stranger_boot() function — it renders the CRT startup sequence |
| Skip a phase | install-core.sh |
Comment out or remove the source line for that phase (e.g., remove phase 07 to skip dev tools) |
| Add a new phase | installers/phases/ |
Create a numbered .sh file with the standard header, then add a source line in install-core.sh in the right order |
| Swap inference backend | lib/compose-select.sh |
Change the compose overlay logic in resolve_compose_config() to point at different compose files |
| Change model downloads | phases/11-services.sh |
Edit the GGUF download logic or add new model files |
| Add a service health check | phases/12-health.sh |
Add a new check_service() call for your service |
| Change minimum requirements | phases/04-requirements.sh |
Adjust RAM/disk/VRAM thresholds per tier |
When a bug involves generated config, check every writer before calling the fix done. This is the most common way install-time surprises survive a patch.
| Config surface | Linux writer | macOS writer | Windows writer | Upgrade/runtime writer |
|---|---|---|---|---|
.env and core ports/secrets |
phases/06-directories.sh |
installers/macos/lib/env-generator.sh |
installers/windows/lib/env-generator.ps1 |
ods config, ods update, installer re-runs |
| OpenCode config | phases/07-devtools.sh |
installers/macos/install-macos.sh |
installers/windows/lib/opencode-config.ps1 |
scripts/update-windows-opencode-config.ps1, scripts/bootstrap-upgrade.sh |
| LiteLLM Lemonade config | phases/06-directories.sh |
n/a | n/a | scripts/bootstrap-upgrade.sh, bin/ods-host-agent.py |
| Perplexica config | phases/12-health.sh, phases/13-summary.sh |
installers/macos/lib/env-generator.sh, installers/macos/install-macos.sh |
installers/windows/lib/env-generator.ps1, installers/windows/install-windows.ps1 |
scripts/bootstrap-upgrade.sh, scripts/repair/repair-perplexica.sh |
| Hermes config | phases/11-services.sh, scripts/patch-hermes-config.py |
installers/macos/install-macos.sh |
installers/windows/phases/06-directories.ps1 |
scripts/bootstrap-upgrade.sh, bin/ods-host-agent.py |
| External text/chat route | phases/02b-external-services.sh, phases/06-directories.sh |
Not installer-managed | Not installer-managed | Linux installer re-runs |
Recent examples: OpenCode on Linux Lemonade mode must use LITELLM_KEY because
LiteLLM enforces auth, while direct llama-server paths keep no-key; Lemonade
lemonade.yaml must preserve extra_body.chat_template_kwargs.enable_thinking: false in install, bootstrap upgrade, and host-agent model activation paths;
Perplexica's persisted defaultChatModel must be refreshed after bootstrap
hot-swap.
Linux external-LLM reuse is an explicit topology choice, not ambient discovery
state. Interactive installs may offer a matching Ollama or LM Studio model;
non-interactive installs adopt one only with --reuse-external-llm or an
explicit URL/provider/model tuple. Phase 02b validates the host endpoint, phase
06 writes both host-facing and container-facing routes, phase 11 omits managed
llama-server/model-router and duplicate downloads, and phase 12 proves the
configured model through host and container completion requests. A rerun with
--no-external-llm clears those routes and restores managed inference.
What's shared vs platform-specific across the installer:
| Layer | Shared | Platform-specific |
|---|---|---|
| Colors, version, paths | lib/constants.sh |
— |
| Logging | lib/logging.sh |
— |
| CRT UI / spinners | lib/ui.sh |
— |
| GPU detection | lib/detection.sh, topology helpers |
Backend contract JSONs (config/backends/) |
| Tier → model mapping | lib/tier-map.sh |
— |
| Compose selection | lib/compose-select.sh |
Per-backend compose overlays |
| Package management | lib/packaging.sh, lib/python-runtime.sh |
apt/dnf/pacman/zypper/brew/PowerShell equivalents |
| Pre-flight checks | phases/01-preflight.sh |
— |
| Docker setup | phases/05-docker.sh |
NVIDIA Container Toolkit vs ROCm |
| AMD system tuning | — | phases/10-amd-tuning.sh (AMD only) |
| Health checks | phases/12-health.sh |
Port/service differences per backend |
for f in installers/lib/*.sh installers/phases/*.sh install-core.sh; do
bash -n "$f"
doneIf any file has a syntax error, bash -n will print the file name and line number.
bash install-core.sh --dry-run --non-interactive --skip-docker --forceThis walks through every phase, printing what would happen without making changes.
bash tests/smoke/linux-nvidia.sh
bash tests/smoke/linux-amd.sh
bash tests/smoke/wsl-logic.sh
bash tests/smoke/macos-dispatch.shbash scripts/simulate-installers.sh
bash tests/integration-test.sh- CONTRIBUTING.md — Contributor validation checklist
- EXTENSIONS.md — Adding Docker services (not installer mods)
- BACKEND-CONTRACT.md — Backend runtime contract format
- INSTALLER_PHASE_CONTRACTS.md - Phase ownership, idempotency, and validation expectations