fix: harden local installation guide
This commit is contained in:
+122
-6
@@ -89,6 +89,25 @@ 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
|
||||
@@ -206,30 +225,112 @@ curl --fail http://127.0.0.1:8787/health
|
||||
"$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 <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. Application source updates are separate from Pi
|
||||
lifecycle updates:
|
||||
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
|
||||
"$THTCTL" --installation "$INSTALLATION" stop
|
||||
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
|
||||
```
|
||||
|
||||
On native Windows use the PowerShell build launcher and the Git-for-Windows Bash LF check. Review
|
||||
release notes before updating. Pi has its own transactional `pi update` and rollback workflow in
|
||||
[Pi management](pi-management.md); never install a package in the running container.
|
||||
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
|
||||
|
||||
@@ -278,6 +379,21 @@ 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.
|
||||
|
||||
@@ -134,10 +134,13 @@ editing state files.
|
||||
|
||||
## Direct support access
|
||||
|
||||
Advanced support may inspect the bundled executable directly with the installation's exact
|
||||
validated Compose file set, for example `docker compose exec core pi --version`. This is read-only
|
||||
diagnosis, not an update mechanism. Do not run package installers, alter Pi files inside the live
|
||||
container, mount the Docker socket, expose a browser shell, or use a host Pi as a substitute.
|
||||
Raw Compose access is unsupported: there is no public operator command that safely reconstructs
|
||||
the installation's hashed project name, project directory, environment file, optional overrides,
|
||||
and durable current-image selector for ad-hoc Pi execution. Do not approximate those arguments or
|
||||
delete/edit lifecycle state for support.
|
||||
|
||||
Prefer `thothctl pi status`, `pi doctor`, `pi test`, and `pi logs`, because they include the durable
|
||||
image selector and redact declared secret values. Share only their sanitized output.
|
||||
Route direct executable/version checks through `thothctl pi status`, and collect diagnostics with
|
||||
`thothctl pi doctor`, `thothctl pi test`, and `thothctl pi logs`. These commands are
|
||||
installation-aware and redact declared secret values. Share only their sanitized output. Do not
|
||||
run package installers, alter Pi files inside the live container, mount the Docker socket, expose
|
||||
a browser shell, or use a host Pi as a substitute.
|
||||
|
||||
@@ -64,8 +64,17 @@ The safest recovery is to reclone into a new directory. First commit wanted work
|
||||
backup outside both clones. Then clone with conversion disabled, run the verifier, and copy back
|
||||
only reviewed changes.
|
||||
|
||||
If a reviewed working tree must be repaired in place, make a backup or commit all wanted changes
|
||||
before continuing. Then use Git's repository attributes to stage a renormalization:
|
||||
If a reviewed working tree must be repaired in place, Git must first normalize the index, export
|
||||
that exact index to a separate repair directory, verify the exported bytes, and only then copy the
|
||||
verified tracked files over the worktree. `git add --renormalize .` alone does not change existing
|
||||
worktree bytes.
|
||||
|
||||
> **WARNING — destructive worktree rewrite.** Make a backup outside the clone or commit every
|
||||
> wanted tracked change before continuing. The copy step below overwrites tracked worktree bytes
|
||||
> from the staged index export. Stop if the staged diff does not contain exactly the wanted content;
|
||||
> untracked files are neither exported nor repaired.
|
||||
|
||||
From WSL2, Git Bash, macOS, or Linux:
|
||||
|
||||
```sh
|
||||
git status --short
|
||||
@@ -73,8 +82,48 @@ git config --local core.autocrlf false
|
||||
git add --renormalize .
|
||||
git diff --cached --check
|
||||
git diff --cached
|
||||
REPAIR_DIR="$(cd .. && pwd -P)/ThothII-lf-repair"
|
||||
if [[ -e "$REPAIR_DIR" ]]; then
|
||||
echo "Choose a new empty LF repair directory: $REPAIR_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$REPAIR_DIR"
|
||||
REPAIR_PREFIX="$REPAIR_DIR/"
|
||||
git checkout-index --all --force --prefix="$REPAIR_PREFIX"
|
||||
bash scripts/verify-line-endings.sh "$REPAIR_DIR"
|
||||
# WARNING: destructive copy; make a backup or commit wanted changes before this command.
|
||||
git ls-files -z | while IFS= read -r -d '' path; do
|
||||
cp "$REPAIR_DIR/$path" "$path"
|
||||
done
|
||||
bash scripts/verify-line-endings.sh
|
||||
```
|
||||
|
||||
Review every staged change before committing. The procedure intentionally avoids destructive Git
|
||||
resets; replacing the clone is easier to audit and much safer for uncommitted work.
|
||||
Native Windows PowerShell runs the same Git operations and invokes the byte verifier through Git
|
||||
for Windows:
|
||||
|
||||
```powershell
|
||||
git status --short
|
||||
git config --local core.autocrlf false
|
||||
git add --renormalize .
|
||||
git diff --cached --check
|
||||
git diff --cached
|
||||
$RepairDir = Join-Path (Split-Path -Parent (Get-Location).Path) 'ThothII-lf-repair'
|
||||
if (Test-Path $RepairDir) { throw 'Choose a new empty LF repair directory.' }
|
||||
New-Item -ItemType Directory -Path $RepairDir | Out-Null
|
||||
$RepairPrefix = $RepairDir.Replace('\', '/') + '/'
|
||||
git checkout-index --all --force --prefix=$RepairPrefix
|
||||
& "C:\Program Files\Git\bin\bash.exe" scripts/verify-line-endings.sh $RepairDir
|
||||
if ($LASTEXITCODE -ne 0) { throw 'The staged index export does not satisfy the LF policy.' }
|
||||
# WARNING: destructive copy; make a backup or commit wanted changes before this command.
|
||||
git ls-files | ForEach-Object {
|
||||
Copy-Item -LiteralPath (Join-Path $RepairDir $_) -Destination $_ -Force
|
||||
}
|
||||
& "C:\Program Files\Git\bin\bash.exe" scripts/verify-line-endings.sh
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Tracked worktree bytes were not repaired to the LF policy.' }
|
||||
```
|
||||
|
||||
The first verifier proves the exported index bytes before any overwrite; the final verifier
|
||||
examines the repaired worktree bytes and must also exit `0`. Review the staged diff again before
|
||||
committing, then remove the separate repair directory only after inspecting it. The procedure
|
||||
intentionally avoids `git reset --hard`; replacing the clone is easier to audit and much safer for
|
||||
uncommitted work.
|
||||
|
||||
Reference in New Issue
Block a user