# Install ThothII on a local PC or Mac This guide installs one loopback-only ThothII on the same Windows, macOS, or Linux computer that runs Docker. The supported application is one Docker Compose distribution containing exactly `frontend` and `core`; Pi is pinned inside `core`. DWH, vector database, embedding, and LLM remain external configurable services even when they run on this computer. No host Pi, Node.js, Python, Go toolchain, Docker socket in core, or browser shell is required. Commands that contain example paths must be changed to absolute paths on your computer. ## Choose your platform - **macOS:** use Terminal and Docker Desktop. Apple Silicon and Intel are supported by the local image build. - **Windows PowerShell:** use Docker Desktop with its WSL2 engine, Git for Windows, the Windows build launcher, and `thothctl-windows-amd64.exe`. - **Windows WSL2 (recommended):** enable Docker Desktop integration for your Linux distribution, clone under `/home/` rather than `/mnt/c`, and follow the Linux shell commands. - **Linux PC:** use Docker Engine plus the Compose v2 plugin and the Linux `thothctl` binary. Windows users must also read [Windows and WSL2 line endings](windows-line-endings.md) before the first build. ## Prerequisites Install only: 1. Git 2.39 or newer. 2. Docker Desktop on macOS/Windows, or Docker Engine on Linux. 3. Docker Compose v2 (`docker compose`, not legacy `docker-compose`). 4. About 10 GB of free disk for source, images, build cache, and initial volumes. 5. Network access to the workspace Git remote and configured DWH/vector/embedding/LLM endpoints. Verify the tools: ```sh git --version docker version docker compose version docker run --rm hello-world ``` On Linux, add the operator to the Docker group only if local policy permits it; sign out and back in afterward. A local installation needs no inbound firewall rule because ports bind only to `127.0.0.1`. ## Clone and verify LF Use a `git clone` command that disables automatic CRLF conversion for this checkout. macOS and Linux: ```sh git -c core.autocrlf=false clone https://github.example.invalid/your-org/ThothII.git cd ThothII git config --local core.autocrlf false bash scripts/verify-line-endings.sh ``` Windows PowerShell: ```powershell git -c core.autocrlf=false clone https://github.example.invalid/your-org/ThothII.git Set-Location ThothII git config --local core.autocrlf false & "C:\Program Files\Git\bin\bash.exe" scripts/verify-line-endings.sh ``` Windows WSL2: ```sh mkdir -p "$HOME/src" && cd "$HOME/src" git -c core.autocrlf=false clone https://github.example.invalid/your-org/ThothII.git cd ThothII git config --local core.autocrlf false bash scripts/verify-line-endings.sh ``` Stop if the verifier names any path. Do not build from a CRLF checkout. ## Create the local operator files Copy the non-secret template. This untracked `.env` contains addresses and absolute source paths, never secret values: ```sh cp deploy/env/local.env.example deploy/env/local.env mkdir -p /absolute/path/to/thothii-operator/secrets chmod 0700 /absolute/path/to/thothii-operator/secrets ``` Native Windows PowerShell performs the same setup without POSIX utilities. The ACL commands remove inherited access from the new operator directory and grant full control only to the current Windows identity. Stop if either `icacls.exe` command returns a nonzero exit code: ```powershell $OperatorDir = Join-Path $env:USERPROFILE 'thothii-operator' $SecretsDir = Join-Path $OperatorDir 'secrets' $CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name if (Test-Path $OperatorDir) { throw 'Use a new operator directory or review its ACLs manually.' } New-Item -ItemType Directory -Force -Path $OperatorDir, $SecretsDir | Out-Null icacls.exe $OperatorDir /inheritance:r if ($LASTEXITCODE -ne 0) { throw 'Could not remove inherited operator-directory ACLs.' } icacls.exe $OperatorDir /grant:r "${CurrentUser}:(OI)(CI)F" if ($LASTEXITCODE -ne 0) { throw 'Could not grant the current user the operator-directory ACL.' } Copy-Item deploy/env/local.env.example deploy/env/local.env Copy-Item docs/install/examples/thothii-installation.local.yaml ` (Join-Path $OperatorDir 'thothii-installation.yaml') ``` Edit `deploy/env/local.env`. At minimum set the workspace Git remote, `PI_AUTH_FILE`, `THT_SECRETS_FILE`, external service endpoints, and the absolute `THT_WORKSPACE_BINDINGS_ENV_FILE`. Create each secret as a separate regular file under the protected operator directory and set mode `0600`. On Windows use a user-only ACL instead. Do not paste credentials into this guide's commands, `.env`, workspace YAML, Git, URLs, image build arguments, or the installation descriptor. Secret contents are mounted read-only under `/run/secrets` (Pi's auth store has its own protected read-only mount) and must never be committed, embedded, rendered, or logged. Follow [the local workspace-registry guide](local-workspace-registry.md) to create the bindings file, choose exactly one Git SSH/HTTPS override, and generate the connector-secret override. A fresh install requires a valid private workspace repository; the Git-backed registry remains the source of truth. Copy the installation example to an operator-controlled file named exactly `thothii-installation.yaml`, then replace all placeholders with absolute paths: ```sh cp docs/install/examples/thothii-installation.local.yaml \ /absolute/path/to/thothii-operator/thothii-installation.yaml ``` For HTTPS, replace the SSH override in that file with `deploy/compose.git-https.yaml`. Add only reviewed local overrides, including the generated connector-secret file. Paths may contain spaces when correctly represented as YAML strings. Native Windows uses the same four fields. Use single-quoted absolute Windows paths so backslashes remain literal YAML characters: ```yaml profile: local projectDirectory: 'C:\Users\operator\src\ThothII' envFile: 'C:\Users\operator\src\ThothII\deploy\env\local.env' overrides: - 'C:\Users\operator\src\ThothII\deploy\compose.git-ssh.yaml' - 'C:\Users\operator\thothii-operator\connector-secrets.local.yaml' ``` ## Address external services An address is interpreted inside `core`. Therefore container 127.0.0.1 means the container itself, not the Docker host. Keep every DWH, vector, embedding, and LLM address configurable in the local environment/workspace bindings. - **Docker Desktop (macOS and Windows):** use `host.docker.internal`, for example `http://host.docker.internal:11434`. - **Linux:** if a service runs on the host, create an untracked override and include its absolute path in `thothii-installation.yaml`: ```yaml services: core: extra_hosts: - "host.docker.internal:host-gateway" ``` Then use `host.docker.internal` in the endpoint. `extra_hosts: host.docker.internal:host-gateway` is a host routing aid, not a bundled service. Prefer a real DNS name for independently operated services; retain TLS and authentication even when co-located. ## Build ThothII and thothctl From the repository root, macOS/Linux/WSL2 users run: ```sh bash scripts/build-local.sh bash scripts/build-thothctl.sh ``` Native PowerShell users run: ```powershell powershell -ExecutionPolicy Bypass -File scripts/build-local.ps1 & "C:\Program Files\Git\bin\bash.exe" scripts/build-thothctl.sh ``` The second command uses Docker to create native operator binaries under `dist/thothctl`; users do not need to know or install Go. Select `thothctl-darwin-arm64` or `-amd64` on macOS, `thothctl-linux-amd64` or `-arm64` on Linux/WSL2, and `thothctl-windows-amd64.exe` on Windows. Copy the selected file to the protected operator directory and, on macOS/Linux, run `chmod 0755` on it. ## Start and verify Set convenient variables (PowerShell users use `$THTCTL` and `$INSTALLATION` with `& $THTCTL`): Every operator call has the form `thothctl --installation `. ```sh THTCTL=/absolute/path/to/thothii-operator/thothctl INSTALLATION=/absolute/path/to/thothii-operator/thothii-installation.yaml "$THTCTL" --installation "$INSTALLATION" update --check-only "$THTCTL" --installation "$INSTALLATION" start "$THTCTL" --installation "$INSTALLATION" status "$THTCTL" --installation "$INSTALLATION" doctor ``` Native PowerShell uses the same order: ```powershell $THTCTL = 'C:\Users\operator\thothii-operator\thothctl.exe' $INSTALLATION = 'C:\Users\operator\thothii-operator\thothii-installation.yaml' & $THTCTL --installation $INSTALLATION update --check-only & $THTCTL --installation $INSTALLATION start & $THTCTL --installation $INSTALLATION status & $THTCTL --installation $INSTALLATION doctor ``` Wait for both services, then check the same-origin frontend and direct loopback core: ```sh curl --fail http://127.0.0.1:8080/health curl --fail http://127.0.0.1:8787/health "$THTCTL" --installation "$INSTALLATION" pi doctor "$THTCTL" --installation "$INSTALLATION" pi test ``` Native PowerShell must call `curl.exe` explicitly; Windows PowerShell may otherwise resolve `curl` to `Invoke-WebRequest`: ```powershell curl.exe --fail --silent --show-error http://127.0.0.1:8080/health curl.exe --fail --silent --show-error http://127.0.0.1:8787/health & $THTCTL --installation $INSTALLATION pi doctor & $THTCTL --installation $INSTALLATION pi test ``` Open . If a check fails, run `thothctl ... logs` or `pi logs`; these are bounded and sanitize declared secrets. Do not publish either loopback port. ## Update an installation Commit or back up local operator changes first and finish active sessions. A promoted Pi image is selected by the durable, installation-specific `current-image.yaml` after every base/profile file. Therefore rebuilding `thothii-core:local` followed by `update --check-only` does not reconcile a previous `pi update`: the old promoted core would remain selected. Do not delete or edit the selector. The supported source-update path is a transactional `pi update --source build` from a clean pulled checkout whose `docker/core.Dockerfile` pins a different Pi version than the running installation. `thothctl` currently treats the same requested Pi version as a no-op. The explicit comparison below therefore stops instead of silently deploying only part of a revision. If it stops, keep the current installation running and wait for a release with a new Pi pin or a future supported reconciliation command; there is no supported manual same-version selector-removal procedure. macOS, Linux, and WSL2: ```sh git status --short git diff --quiet git diff --cached --quiet git pull --ff-only git config --local core.autocrlf false bash scripts/verify-line-endings.sh SOURCE_REVISION="$(git rev-parse HEAD)" NEXT_PI_VERSION="$(sed -n 's/^ARG PI_VERSION=//p' docker/core.Dockerfile)" RUNNING_PI_VERSION="$("$THTCTL" --installation "$INSTALLATION" pi status)" RUNNING_PI_VERSION="${RUNNING_PI_VERSION#Pi version: }" if [[ -z "$NEXT_PI_VERSION" || "$NEXT_PI_VERSION" == "$RUNNING_PI_VERSION" ]]; then echo "Source update stopped: the pulled revision must pin a new Pi version." >&2 exit 1 fi bash scripts/build-local.sh bash scripts/build-thothctl.sh "$THTCTL" --installation "$INSTALLATION" update --check-only "$THTCTL" --installation "$INSTALLATION" pi update \ --version "$NEXT_PI_VERSION" --source build --yes --drain "$THTCTL" --installation "$INSTALLATION" start curl --fail http://127.0.0.1:8080/health curl --fail http://127.0.0.1:8787/health printf 'Built source revision: %s\n' "$SOURCE_REVISION" "$THTCTL" --installation "$INSTALLATION" status "$THTCTL" --installation "$INSTALLATION" pi status "$THTCTL" --installation "$INSTALLATION" doctor ``` Native Windows PowerShell uses the same fail-closed version comparison and transactional promotion: ```powershell git status --short git diff --quiet if ($LASTEXITCODE -ne 0) { throw 'Commit or back up tracked source changes before update.' } git diff --cached --quiet if ($LASTEXITCODE -ne 0) { throw 'Commit or back up staged source changes before update.' } git pull --ff-only if ($LASTEXITCODE -ne 0) { throw 'The source pull failed.' } git config --local core.autocrlf false & "C:\Program Files\Git\bin\bash.exe" scripts/verify-line-endings.sh if ($LASTEXITCODE -ne 0) { throw 'The pulled checkout contains CRLF files.' } $SourceRevision = git rev-parse HEAD $VersionLine = @(Select-String -Path docker/core.Dockerfile -Pattern '^ARG PI_VERSION=(.+)$') if ($VersionLine.Count -ne 1) { throw 'Expected exactly one pinned default PI_VERSION.' } $NextPiVersion = $VersionLine.Matches[0].Groups[1].Value $RunningPiVersion = (& $THTCTL --installation $INSTALLATION pi status) ` -replace '^Pi version:\s*', '' if ([string]::IsNullOrWhiteSpace($NextPiVersion) -or $NextPiVersion -eq $RunningPiVersion) { throw 'Source update stopped: the pulled revision must pin a new Pi version.' } powershell -ExecutionPolicy Bypass -File scripts/build-local.ps1 if ($LASTEXITCODE -ne 0) { throw 'The local image build failed.' } & "C:\Program Files\Git\bin\bash.exe" scripts/build-thothctl.sh if ($LASTEXITCODE -ne 0) { throw 'The thothctl build failed.' } & $THTCTL --installation $INSTALLATION update --check-only if ($LASTEXITCODE -ne 0) { throw 'The installation render check failed.' } & $THTCTL --installation $INSTALLATION pi update ` --version $NextPiVersion --source build --yes --drain if ($LASTEXITCODE -ne 0) { throw 'The transactional core update failed.' } & $THTCTL --installation $INSTALLATION start if ($LASTEXITCODE -ne 0) { throw 'The installation start failed.' } curl.exe --fail --silent --show-error http://127.0.0.1:8080/health curl.exe --fail --silent --show-error http://127.0.0.1:8787/health Write-Output "Built source revision: $SourceRevision" & $THTCTL --installation $INSTALLATION status & $THTCTL --installation $INSTALLATION pi status & $THTCTL --installation $INSTALLATION doctor ``` The recorded Git revision identifies the clean worktree used for the candidate build. In `thothctl status`, confirm that `core` reports the installation lifecycle candidate image, then require `pi status` to equal the new pin and `doctor` to pass. This is the supported running-image and source-revision evidence; `update --check-only` alone proves only that Compose renders. Review release notes before updating. See [Pi management](pi-management.md) for rollback; never install a package in the running container. ## Back up and restore Back up before source/Pi updates and test restoration periodically. First stop cleanly: ```sh "$THTCTL" --installation "$INSTALLATION" stop docker volume ls --format '{{.Name}}' | grep '^thothii-' ``` Identify the four exact volumes belonging to this installation: `settings`, `pi-state`, `workspace-registry`, and `sessions`. Confirm their Compose project label with `docker volume inspect`. For each exact volume, archive it to a protected backup directory: ```sh BACKUP_DIR=/absolute/path/to/backups/2026-08-05 VOLUME=exact-installation-volume-name mkdir -p "$BACKUP_DIR" docker run --rm -v "$VOLUME:/source:ro" -v "$BACKUP_DIR:/backup" \ alpine:3.22 tar -C /source -czf "/backup/$VOLUME.tgz" . ``` Native PowerShell can run the same read-only archive container: ```powershell $BackupDir = 'C:\Users\operator\thothii-backups\2026-08-05' $Volume = 'exact-installation-volume-name' New-Item -ItemType Directory -Force $BackupDir | Out-Null docker run --rm -v "${Volume}:/source:ro" -v "${BackupDir}:/backup" ` alpine:3.22 tar -C /source -czf "/backup/${Volume}.tgz" . ``` Also back up the installation descriptor, operator environment, generated overrides, and secret files to separate encrypted/protected storage. Never commit them. Record image digests and the Git revision. Do not back up while containers are running. Restore only while stopped and only into a new, verified-empty exact target volume. Test the archive in a disposable installation first: ```sh TARGET_VOLUME=exact-empty-target-volume-name ARCHIVE=/absolute/path/to/backups/2026-08-05/exact-volume-name.tgz docker run --rm -v "$TARGET_VOLUME:/target" alpine:3.22 \ sh -c 'test -z "$(ls -A /target)"' docker run --rm -v "$TARGET_VOLUME:/target" -v "$(dirname "$ARCHIVE"):/backup:ro" \ alpine:3.22 tar -C /target -xzf "/backup/$(basename "$ARCHIVE")" ``` Native PowerShell uses `Split-Path` to produce the read-only archive mount and archive name: ```powershell $TargetVolume = 'exact-empty-target-volume-name' $Archive = 'C:\Users\operator\thothii-backups\2026-08-05\exact-volume-name.tgz' $ArchiveDir = Split-Path -Parent $Archive $ArchiveName = Split-Path -Leaf $Archive docker run --rm -v "${TargetVolume}:/target" alpine:3.22 ` sh -ceu 'test -z "$(ls -A /target)"' if ($LASTEXITCODE -ne 0) { throw 'The restore target volume is not empty.' } docker run --rm -v "${TargetVolume}:/target" -v "${ArchiveDir}:/backup:ro" ` alpine:3.22 tar -C /target -xzf "/backup/${ArchiveName}" if ($LASTEXITCODE -ne 0) { throw 'The volume restore failed.' } ``` Restore all four volumes from the same backup set, restore protected operator files separately, then run `update --check-only`, `start`, `doctor`, registry status/diagnostics, and a known session before normal use. Never merge an archive into a non-empty volume. ## Data-preserving uninstall Run `thothctl stop`, retain the installation descriptor at the same absolute path, and make one verified backup set. In Docker Desktop, remove only this installation's stopped `core` and `frontend` containers and optional local images; leave its four named volumes. On Linux, use the containers' exact Compose project labels to remove only those stopped containers. Do not prune global Docker data. Do **not** run `docker compose down --volumes`: it deletes the application data this procedure is meant to preserve. Keep the operator directory and protected secrets if you intend to reinstall. Using the same descriptor path preserves the `thothctl` project identity and reconnects the same named volumes after rebuilding the source checkout. ## Next: workspaces and Pi Complete [local workspace-registry installation](local-workspace-registry.md), including Git trust, bindings, pull, validation, diagnostics, and registry recovery. Then use [Pi management](pi-management.md) for provider/model configuration, smoke testing, transactional update, and rollback. The Git-backed workspace registry is always the workspace source of truth. Local DWH, vector, embedding, or LLM processes remain independent services and are never added to the mandatory ThothII core.