18 KiB
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/<user>rather than/mnt/c, and follow the Linux shell commands. - Linux PC: use Docker Engine plus the Compose v2 plugin and the Linux
thothctlbinary.
Windows users must also read Windows and WSL2 line endings before the first build.
Prerequisites
Install only:
- Git 2.39 or newer.
- Docker Desktop on macOS/Windows, or Docker Engine on Linux.
- Docker Compose v2 (
docker compose, not legacydocker-compose). - About 10 GB of free disk for source, images, build cache, and initial volumes.
- Network access to the workspace Git remote and configured DWH/vector/embedding/LLM endpoints.
Verify the tools:
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:
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:
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:
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:
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:
$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 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:
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:
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 examplehttp://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:
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:
bash scripts/build-local.sh
bash scripts/build-thothctl.sh
Native PowerShell users run:
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 <absolute-descriptor> <command>.
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:
$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:
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:
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 http://127.0.0.1:8080. 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:
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:
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 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:
"$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:
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:
$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:
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:
$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, including Git trust, bindings, pull, validation, diagnostics, and registry recovery. Then use Pi management 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.