# Tooling — Proxmox host All commands assume PowerShell from the project root. > **Canonical catalog:** [`docs/TOOL-INDEX.md`](../docs/TOOL-INDEX.md) — verified > signatures, env requirements, and read-only/mutating classification for every > script in the repo. This file holds the host-level usage patterns. ## Environment ```powershell . .\.env.local.ps1 ``` Without a private env file, SSH still works from the hardcoded defaults in `scripts/ProxmoxAgent.ps1`. API calls require token env vars. > **Status of `.env.local.ps1` (2026-08-29):** `PROXMOX_API_TOKEN_ID` > (`root@pam!openclaw`, verified 200 against `/version` and > `/cluster/resources`), `PROXMOX_API_TOKEN_SECRET`, `COOLIFY_EMAIL`, > `COOLIFY_PASSWORD` (probable — unverified) and a working `GITHUB_TOKEN` > (extracted from Windows Credential Manager after the old PAT expired) are all > loaded. The Proxmox REST API, the Coolify API and GitHub work. Only > `CLOUDFLARE_API_TOKEN` is still missing — tunnel changes go through the > Cloudflare dashboard. ## Smoke test and inventory ```powershell .\scripts\Test-ProxmoxConnection.ps1 # config + SSH + Docker sample + API auth; exits 1 on FAIL .\scripts\Get-ProxmoxInventory.ps1 # host, LXC, QEMU, Docker in LXC 102 ``` ## SSH wrapper ```powershell .\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct list" .\scripts\Invoke-ProxmoxSsh.ps1 -Command "qm list" .\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker ps -a" ``` ### ⚠️ Nested quotes get mangled — use base64 `Invoke-ProxmoxSshCommand` passes `$Command` as a single argument to `ssh`, and PowerShell 5.1 destroys embedded quotes when calling a native executable. A command with two levels of quoting arrives corrupted (`bash: line 1: -c: command not found`). Encode it instead: ```powershell $remote = @' pct exec 102 -- docker exec -i bash -lc 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -c "SELECT 1"' '@ $b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($remote)) .\scripts\Invoke-ProxmoxSsh.ps1 -Command "echo $b64 | base64 -d | bash 2>&1" ``` The single-quoted here-string `@'...'@` is required so PowerShell does not expand `$POSTGRES_PASSWORD` on the Windows side. Single-level quoting (`docker ps --format '{{.Names}}'`) works through the plain wrapper. ## Safe read-only commands ```powershell .\scripts\Invoke-ProxmoxSsh.ps1 -Command "hostname && pveversion && uname -r && uptime" .\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct list && qm list" .\scripts\Invoke-ProxmoxSsh.ps1 -Command "df -h / && free -h" .\scripts\Invoke-ProxmoxSsh.ps1 -Command "pvesh get /cluster/resources" .\scripts\Invoke-ProxmoxSsh.ps1 -Command "systemctl --failed" .\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'" .\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker stats --no-stream" ``` ## Resolving a container name Coolify names containers `-` plus an optional build suffix, so they are **not guessable and change when a redeploy recreates the container**: ```powershell .\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker ps --format '{{.Names}}' | grep -i " ``` ## Proxmox REST API from a PowerShell session Requires `PROXMOX_API_TOKEN_ID` + `PROXMOX_API_TOKEN_SECRET`; throws without them. ```powershell . .\scripts\ProxmoxAgent.ps1 Invoke-ProxmoxApi -Path "/version" Invoke-ProxmoxApi -Path "/nodes" Invoke-ProxmoxApi -Path "/cluster/resources" ``` ## Cloudflare API — tunnel and DNS Requires `CLOUDFLARE_API_TOKEN`. `GET` is read-only; everything else mutates. `-Raw` returns objects, the default returns a JSON string. ```powershell .\scripts\Invoke-CloudflareApi.ps1 -Path "/user/tokens/verify" ``` The tunnel is **dashboard-managed** — fix routes there or via this API, never by editing files on the host. See [`docs/runbooks/cloudflare-tunnel.md`](../docs/runbooks/cloudflare-tunnel.md). ## Host automation ```powershell # Power-outage auto-start of LXC 102 + tunnel. Audit without touching anything: .\scripts\Install-CoolifyAutostart.ps1 -VerifyOnly ``` Installed on the host: `coolify-autostart.service` (systemd, **enabled**) and the Chatwoot guard at `/root/scripts/chatwoot-enterprise-guard.sh`, scheduled by `/etc/cron.d/chatwoot-enterprise-guard` every 5 minutes. ## Chatwoot enterprise licence ```powershell .\scripts\Get-ChatwootLicenseStatus.ps1 -Deep # read-only diagnosis .\scripts\Apply-ChatwootEnterprisePatch.ps1 -DryRun -ReenableAccountFeatures # preview the repair ``` Full context: [`docs/runbooks/chatwoot-update.md`](../docs/runbooks/chatwoot-update.md). ## Per-app deploy helpers `scripts/apps/` holds app-specific one-off deploy scripts with hardcoded uuids and domains — e.g. `Deploy-SoloLeveling.ps1`, which builds the image on the server to work around a private GHCR without `read:packages`. Read the header before running one; they are **mutating** and tied to a specific resource. ## Commands that require confirmation - `pct start|shutdown|reboot|stop|destroy` - `qm start|shutdown|reboot|stop|destroy` - `docker restart|stop|rm|compose up|compose down` - Package updates, firewall edits, network edits, storage edits - Any Cloudflare write (`POST`/`PUT`/`PATCH`/`DELETE`) - `Install-CoolifyAutostart.ps1` without `-VerifyOnly` - `Apply-ChatwootEnterprisePatch.ps1` without `-DryRun`