Import verified AzerothAI snapshot from GitHub
Core tests / core (push) Successful in 9m57s
Full verification / application (push) Failing after 5m16s
Full verification / local-brain (push) Failing after 6m15s
Realm simulation / simulated-live-world (push) Failing after 5m32s
WoW integration lab / real-worldserver (push) Canceled after 17m23s
Core tests / core (push) Successful in 9m57s
Full verification / application (push) Failing after 5m16s
Full verification / local-brain (push) Failing after 6m15s
Realm simulation / simulated-live-world (push) Failing after 5m32s
WoW integration lab / real-worldserver (push) Canceled after 17m23s
Source branch: feature/ai-brain-router Source commit: c2a09e0460465201a58aaab56c71424e9a96405e
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
# Temporary Windows PC + Ubuntu VM Lab
|
||||
|
||||
This is the short-lived lab profile for running AzerothAI on a Windows gaming PC until the dedicated mini PC is ready.
|
||||
|
||||
## Layout
|
||||
|
||||
Windows gaming PC:
|
||||
- WoW 3.3.5a client
|
||||
- Ollama
|
||||
- Qwen3.5 4B on the RTX GPU
|
||||
- Ubuntu Server VM containing AzerothCore, Playerbots, ALE, MySQL, AzerothAI, and World Monitor
|
||||
|
||||
The Windows install stays intact. Do not install Proxmox on the gaming PC for this temporary lab.
|
||||
|
||||
## VM settings
|
||||
|
||||
Create an Ubuntu Server VM in VMware with:
|
||||
- 4 vCPUs
|
||||
- 8 GB RAM
|
||||
- 90 GB dynamically allocated disk
|
||||
- NAT networking for the temporary Wi-Fi lab
|
||||
- no desktop environment required
|
||||
|
||||
Ubuntu 24.04 LTS is the reference host, but newer Ubuntu hosts are acceptable because the AzerothCore Docker build itself uses its pinned container base. The bootstrap automatically expands free LVM space and handles the VMware NAT path to Windows-hosted Ollama.
|
||||
|
||||
With NAT, Windows can reach the VM directly on VMnet8. Phone/iPad access is optional and can be added later with bridged networking or a tunnel.
|
||||
|
||||
If free disk space is tight, 70 GB can work for a short test, but 90 GB leaves more breathing room for AzerothCore build output and extracted client data.
|
||||
|
||||
## Step 1 — Windows host
|
||||
|
||||
Open PowerShell as Administrator in the AzerothAI checkout.
|
||||
|
||||
Read-only check:
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File .\deploy\pc-lab\host-setup.ps1
|
||||
|
||||
When ready to set up the model host:
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File .\deploy\pc-lab\host-setup.ps1 -InstallOllama -StartOllama -OpenFirewall
|
||||
|
||||
That checks RAM, free space, CPU virtualization and GPU; installs Ollama through winget if requested; binds Ollama to TCP 11434; pulls qwen3.5:4b; opens TCP 11434 on Windows Private networks only; and prints the Windows LAN IPv4 address.
|
||||
|
||||
Keep that IPv4 address.
|
||||
|
||||
## Step 2 — Create Ubuntu VM
|
||||
|
||||
Install Ubuntu Server normally. For this temporary Wi-Fi setup, use VMware NAT.
|
||||
|
||||
After installation:
|
||||
|
||||
ip addr
|
||||
|
||||
Keep the VM's LAN IPv4 address too.
|
||||
|
||||
## Step 3 — Clone AzerothAI in Ubuntu
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y git
|
||||
git clone https://github.com/Endami-backup/AzerothAI.git
|
||||
cd AzerothAI
|
||||
git checkout feature/ai-brain-router
|
||||
bash deploy/pc-lab/vm-bootstrap.sh
|
||||
|
||||
The bootstrap first tries to discover the Windows VMnet8/Ollama endpoint automatically. If it cannot, enter the VMware NAT host IPv4 printed by host-setup.ps1.
|
||||
|
||||
On the first run the script may install Docker and ask you to log out/back in before rerunning. That is expected.
|
||||
|
||||
The bootstrap verifies the VM can reach Qwen on Windows, calls the real playable-realm bootstrap, builds AzerothCore + Playerbots + ALE, imports the realm databases/client data, starts AzerothAI, points AzerothAI at the Windows Ollama host, and prints the WoW realm and dashboard addresses.
|
||||
|
||||
## Step 4 — Create your WoW GM account
|
||||
|
||||
Inside Ubuntu:
|
||||
|
||||
cd ~/azerothai-pc-lab/azerothcore
|
||||
docker attach ac-worldserver
|
||||
|
||||
At the worldserver console:
|
||||
|
||||
account create YOURNAME YOURPASSWORD
|
||||
account set gmlevel YOURNAME 3 -1
|
||||
|
||||
Detach without killing worldserver with Ctrl-P, then Ctrl-Q.
|
||||
|
||||
Then save those same credentials for AzerothAI's localhost-only SOAP GM channel from the AzerothAI checkout:
|
||||
|
||||
bash deploy/playable/set-gm-credentials.sh YOURNAME YOURPASSWORD
|
||||
|
||||
Restart the AzerothAI runtime if the helper tells you to.
|
||||
|
||||
## Step 5 — Connect WoW from Windows
|
||||
|
||||
Edit:
|
||||
|
||||
<WoW 3.3.5a folder>\Data\enUS\realmlist.wtf
|
||||
|
||||
Use the Ubuntu VM IP:
|
||||
|
||||
set realmlist 192.168.x.x
|
||||
|
||||
Launch WoW and sign in. The realm name is AzerothAI Lab.
|
||||
|
||||
## Step 6 — Open World Monitor
|
||||
|
||||
From Windows, iPhone, or iPad on the same network:
|
||||
|
||||
http://<ubuntu-vm-ip>:8787
|
||||
|
||||
Open World Monitor. It shows Zaku's location/state/home/goal/last decision, active zones, population bands, Hardcore deaths, authoritative ALE events, server/model health, and Population Director activity.
|
||||
|
||||
## Step 7 — Test connectivity from Windows
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File .\deploy\pc-lab\check-lab.ps1 -VmIp 192.168.x.x
|
||||
|
||||
It checks World Monitor on TCP 8787, WoW authserver on 3724, worldserver on 8085, and the AzerothAI health API.
|
||||
|
||||
## What still needs real-server proof
|
||||
|
||||
The simulated ALE/live-world path is green in CI. This VM gives us the physical environment to validate:
|
||||
- real Playerbot character GUID binding
|
||||
- PlayerBotAI execution of AzerothAI intents
|
||||
- all real damage paths for Zaku's 1 HP clamp
|
||||
- actual quest/travel/social behavior
|
||||
- real WoW client login/play
|
||||
|
||||
If the live server differs from the simulator, the worldserver is authoritative and we fix the adapter.
|
||||
|
||||
## Stop the lab
|
||||
|
||||
From Ubuntu:
|
||||
|
||||
export AZEROTHAI_PLAYABLE_ROOT="$HOME/azerothai-pc-lab"
|
||||
bash deploy/playable/stop.sh
|
||||
|
||||
The database and realm files remain on disk.
|
||||
|
||||
## Thursday migration
|
||||
|
||||
Before deleting the VM, create a private migration archive:
|
||||
|
||||
cd ~/AzerothAI
|
||||
export AZEROTHAI_PLAYABLE_ROOT="$HOME/azerothai-pc-lab"
|
||||
bash deploy/pc-lab/export-for-mini-pc.sh
|
||||
|
||||
The archive contains the AzerothCore auth/characters/world databases, AzerothAI local state, generated configuration, and runtime secrets. It is created with owner-only permissions and must be treated as sensitive.
|
||||
|
||||
This VM is disposable, but Zaku is not. On the mini PC we should import this state so Zaku remains the same persistent identity rather than being rebuilt as a new character.
|
||||
|
||||
|
||||
## Remote dashboard over Tailscale
|
||||
|
||||
If the dashboard HTML loads over the raw Tailscale IP but CSS/JS assets do not, use the built-in proxy helper:
|
||||
|
||||
```bash
|
||||
cd ~/AzerothAI
|
||||
git pull
|
||||
bash deploy/pc-lab/tailscale-dashboard.sh
|
||||
```
|
||||
|
||||
It serves built client assets directly with nginx, proxies API/SSR requests back to the AzerothAI runtime on port 8787, and publishes the result privately with Tailscale Serve over HTTPS. Use the `https://...ts.net/` URL it prints instead of raw `http://100.x.x.x:8787`.
|
||||
@@ -0,0 +1,48 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$VmIp,
|
||||
[int]$DashboardPort = 8787,
|
||||
[int]$AuthPort = 3724,
|
||||
[int]$WorldPort = 8085
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
function Test-Port([string]$HostName,[int]$Port,[string]$Label) {
|
||||
$result = Test-NetConnection -ComputerName $HostName -Port $Port -WarningAction SilentlyContinue
|
||||
if ($result.TcpTestSucceeded) {
|
||||
Write-Host "[OK] $Label ${HostName}:$Port" -ForegroundColor Green
|
||||
return $true
|
||||
}
|
||||
Write-Host "[FAIL] $Label ${HostName}:$Port" -ForegroundColor Red
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-Host "AzerothAI PC Lab connectivity check" -ForegroundColor Cyan
|
||||
Write-Host "VM: $VmIp"
|
||||
Write-Host ""
|
||||
|
||||
$ok = $true
|
||||
$ok = (Test-Port $VmIp $DashboardPort "World Monitor") -and $ok
|
||||
$ok = (Test-Port $VmIp $AuthPort "WoW authserver") -and $ok
|
||||
$ok = (Test-Port $VmIp $WorldPort "WoW worldserver") -and $ok
|
||||
|
||||
try {
|
||||
$health = Invoke-RestMethod -Uri "http://${VmIp}:$DashboardPort/api/health" -TimeoutSec 5
|
||||
Write-Host "[OK] AzerothAI health API" -ForegroundColor Green
|
||||
$health | ConvertTo-Json -Depth 5
|
||||
} catch {
|
||||
Write-Host "[FAIL] AzerothAI health API" -ForegroundColor Red
|
||||
$ok = $false
|
||||
}
|
||||
|
||||
if (-not $ok) {
|
||||
Write-Host ""
|
||||
Write-Host "One or more checks failed. Verify the VM is using Bridged networking and the Ubuntu firewall is not blocking these ports." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "All network checks passed." -ForegroundColor Green
|
||||
Write-Host "World Monitor: http://${VmIp}:$DashboardPort"
|
||||
Write-Host "WoW realmlist: set realmlist $VmIp"
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
LAB_ROOT="${AZEROTHAI_PLAYABLE_ROOT:-$HOME/azerothai-pc-lab}"
|
||||
ACORE_ROOT="$LAB_ROOT/azerothcore"
|
||||
RUNTIME_ENV="$LAB_ROOT/runtime.env"
|
||||
OUT_DIR="${1:-$HOME}"
|
||||
STAMP="$(date +%Y%m%d-%H%M%S)"
|
||||
WORK="$LAB_ROOT/export-$STAMP"
|
||||
ARCHIVE="$OUT_DIR/azerothai-pc-lab-$STAMP.tar.gz"
|
||||
|
||||
mkdir -p "$WORK/mysql" "$WORK/azerothai"
|
||||
|
||||
if [[ ! -f "$RUNTIME_ENV" ]]; then
|
||||
echo "Missing $RUNTIME_ENV. This does not look like an initialized PC lab." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$RUNTIME_ENV"
|
||||
: "${DOCKER_DB_ROOT_PASSWORD:?Missing database password in runtime.env}"
|
||||
|
||||
if ! docker inspect ac-database >/dev/null 2>&1; then
|
||||
echo "The AzerothCore database container is not present." >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
echo "==> Dumping AzerothCore databases"
|
||||
for db in acore_auth acore_characters acore_world; do
|
||||
docker exec ac-database mysqldump -uroot -p"$DOCKER_DB_ROOT_PASSWORD" --single-transaction --routines --events --triggers "$db" > "$WORK/mysql/$db.sql"
|
||||
done
|
||||
|
||||
echo "==> Preserving AzerothAI state and runtime configuration"
|
||||
cp "$RUNTIME_ENV" "$WORK/runtime.env"
|
||||
chmod 600 "$WORK/runtime.env"
|
||||
|
||||
if [[ -d "$REPO_ROOT/.wrangler/state" ]]; then
|
||||
tar -C "$REPO_ROOT" -czf "$WORK/azerothai/wrangler-state.tar.gz" .wrangler/state
|
||||
fi
|
||||
|
||||
if [[ -d "$LAB_ROOT/etc" ]]; then
|
||||
tar -C "$LAB_ROOT" -czf "$WORK/azerothcore-etc.tar.gz" etc
|
||||
fi
|
||||
|
||||
cat > "$WORK/MANIFEST.txt" <<EOF
|
||||
AzerothAI temporary PC lab migration export
|
||||
Created: $(date -Is)
|
||||
Source branch: $(git -C "$REPO_ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)
|
||||
Source commit: $(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || echo unknown)
|
||||
|
||||
Contains:
|
||||
- acore_auth SQL
|
||||
- acore_characters SQL
|
||||
- acore_world SQL
|
||||
- AzerothAI local D1/SQLite state, when present
|
||||
- AzerothCore generated configuration, when present
|
||||
- runtime.env including bridge/operator/database secrets
|
||||
|
||||
Treat this archive as sensitive because it contains credentials.
|
||||
EOF
|
||||
|
||||
echo "==> Creating migration archive"
|
||||
tar -C "$WORK" -czf "$ARCHIVE" .
|
||||
chmod 600 "$ARCHIVE"
|
||||
rm -rf "$WORK"
|
||||
|
||||
echo
|
||||
echo "Migration export created:"
|
||||
echo " $ARCHIVE"
|
||||
echo
|
||||
echo "Keep this file private. It contains realm data and runtime credentials."
|
||||
@@ -0,0 +1,174 @@
|
||||
param(
|
||||
[switch]$InstallOllama,
|
||||
[switch]$StartOllama,
|
||||
[switch]$OpenFirewall,
|
||||
[string]$Model = "qwen3.5:4b"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Section([string]$Text) {
|
||||
Write-Host ""
|
||||
Write-Host "==> $Text" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
function Get-LanIPv4 {
|
||||
$candidates = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.IPAddress -notlike "127.*" -and
|
||||
$_.IPAddress -notlike "169.254.*" -and
|
||||
$_.InterfaceAlias -notmatch "vEthernet|VirtualBox|VMware|Loopback|Tailscale"
|
||||
} |
|
||||
Sort-Object InterfaceMetric
|
||||
|
||||
return ($candidates | Select-Object -First 1 -ExpandProperty IPAddress)
|
||||
}
|
||||
|
||||
function Get-VMwareNatInfo {
|
||||
$addr = Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias "*VMware*VMnet8*" -ErrorAction SilentlyContinue |
|
||||
Select-Object -First 1
|
||||
if (-not $addr) { return $null }
|
||||
|
||||
$octets = $addr.IPAddress.Split('.')
|
||||
if ($octets.Count -ne 4) { return $null }
|
||||
|
||||
$subnet = "$($octets[0]).$($octets[1]).$($octets[2]).0/$($addr.PrefixLength)"
|
||||
return [pscustomobject]@{
|
||||
IP = $addr.IPAddress
|
||||
PrefixLength = $addr.PrefixLength
|
||||
Subnet = $subnet
|
||||
}
|
||||
}
|
||||
|
||||
Section "AzerothAI temporary Windows host check"
|
||||
|
||||
$lanIp = Get-LanIPv4
|
||||
$vmnet8 = Get-VMwareNatInfo
|
||||
if (-not $lanIp) {
|
||||
Write-Warning "Could not auto-detect a LAN IPv4 address."
|
||||
} else {
|
||||
Write-Host "Windows LAN IPv4: $lanIp"
|
||||
}
|
||||
if ($vmnet8) {
|
||||
Write-Host "VMware NAT host IPv4: $($vmnet8.IP)"
|
||||
Write-Host "VMware NAT subnet: $($vmnet8.Subnet)"
|
||||
}
|
||||
|
||||
$free = Get-PSDrive -Name C
|
||||
$freeGB = [math]::Round($free.Free / 1GB, 1)
|
||||
Write-Host "Free space on C:: $freeGB GB"
|
||||
if ($freeGB -lt 70) {
|
||||
Write-Warning "Less than 70 GB is free. The temporary VM may be tight once AzerothCore and client data are built."
|
||||
}
|
||||
|
||||
$ram = Get-CimInstance Win32_ComputerSystem
|
||||
$totalGB = [math]::Round($ram.TotalPhysicalMemory / 1GB, 1)
|
||||
Write-Host "Installed RAM: $totalGB GB"
|
||||
if ($totalGB -lt 14) {
|
||||
Write-Warning "This PC has less memory than expected for an 8 GB VM plus Windows."
|
||||
}
|
||||
|
||||
$cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
|
||||
Write-Host "CPU: $($cpu.Name)"
|
||||
Write-Host "Logical processors: $($cpu.NumberOfLogicalProcessors)"
|
||||
|
||||
$gpu = Get-CimInstance Win32_VideoController | Select-Object -ExpandProperty Name
|
||||
Write-Host "GPU(s): $($gpu -join ', ')"
|
||||
|
||||
$virt = (Get-CimInstance Win32_Processor | Select-Object -First 1).VirtualizationFirmwareEnabled
|
||||
Write-Host "Firmware virtualization enabled: $virt"
|
||||
if (-not $virt) {
|
||||
Write-Warning "CPU virtualization appears disabled. Enable SVM/AMD-V in BIOS before creating the VM."
|
||||
}
|
||||
|
||||
$ollamaCommand = Get-Command ollama -ErrorAction SilentlyContinue
|
||||
$ollamaDefault = Join-Path $env:LOCALAPPDATA "Programs\Ollama\ollama.exe"
|
||||
$ollamaExe = if ($ollamaCommand) { $ollamaCommand.Source } elseif (Test-Path $ollamaDefault) { $ollamaDefault } else { $null }
|
||||
|
||||
if ($InstallOllama -and -not $ollamaExe) {
|
||||
Section "Installing Ollama"
|
||||
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
|
||||
throw "winget is not available. Install Ollama manually, then rerun this script."
|
||||
}
|
||||
winget install --id Ollama.Ollama -e --accept-source-agreements --accept-package-agreements
|
||||
if (Test-Path $ollamaDefault) {
|
||||
$ollamaExe = $ollamaDefault
|
||||
} else {
|
||||
$ollamaCommand = Get-Command ollama -ErrorAction SilentlyContinue
|
||||
if ($ollamaCommand) { $ollamaExe = $ollamaCommand.Source }
|
||||
}
|
||||
}
|
||||
|
||||
if ($ollamaExe) {
|
||||
Section "Ollama"
|
||||
Write-Host "Ollama detected: $ollamaExe"
|
||||
if ($StartOllama) {
|
||||
[Environment]::SetEnvironmentVariable("OLLAMA_HOST", "0.0.0.0:11434", "User")
|
||||
$env:OLLAMA_HOST = "0.0.0.0:11434"
|
||||
|
||||
Get-Process -Name "ollama" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 1
|
||||
|
||||
Start-Process -FilePath $ollamaExe -ArgumentList "serve" -WindowStyle Hidden
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
Write-Host "Pulling $Model ..."
|
||||
& $ollamaExe pull $Model
|
||||
|
||||
try {
|
||||
Invoke-RestMethod -Uri "http://127.0.0.1:11434/api/tags" -TimeoutSec 5 | Out-Null
|
||||
Write-Host "Ollama API: OK"
|
||||
} catch {
|
||||
Write-Warning "Ollama did not answer on port 11434 yet."
|
||||
}
|
||||
} else {
|
||||
Write-Host "Use -StartOllama when you want this script to expose Ollama to the Ubuntu VM and pull $Model."
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Ollama is not installed. Re-run with -InstallOllama -StartOllama, or install it manually."
|
||||
}
|
||||
|
||||
if ($OpenFirewall) {
|
||||
Section "Windows firewall"
|
||||
$profiles = Get-NetConnectionProfile -ErrorAction SilentlyContinue
|
||||
$activeProfiles = $profiles | Where-Object { $_.IPv4Connectivity -ne "Disconnected" }
|
||||
foreach ($profile in $activeProfiles) {
|
||||
Write-Host "Network profile: $($profile.InterfaceAlias) -> $($profile.NetworkCategory)"
|
||||
if ($profile.NetworkCategory -eq "Public") {
|
||||
Write-Warning "This active network is Public. The AzerothAI firewall rule is Private-only and will not accept VM traffic until the network is trusted/Private or you deliberately change the rule."
|
||||
}
|
||||
}
|
||||
$ruleName = "AzerothAI Ollama from VM"
|
||||
$existing = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
|
||||
if ($existing) {
|
||||
Remove-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if ($vmnet8) {
|
||||
New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Action Allow -Protocol TCP -LocalPort 11434 -RemoteAddress $vmnet8.Subnet -Profile Any | Out-Null
|
||||
Write-Host "Opened TCP 11434 only to the VMware NAT subnet $($vmnet8.Subnet)."
|
||||
} else {
|
||||
New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Action Allow -Protocol TCP -LocalPort 11434 -Profile Private | Out-Null
|
||||
Write-Host "VMnet8 was not detected, so TCP 11434 was opened on Private networks only."
|
||||
}
|
||||
}
|
||||
|
||||
Section "Temporary VM recommendation"
|
||||
Write-Host "Ubuntu Server 24.04 LTS"
|
||||
Write-Host "vCPU: 4"
|
||||
Write-Host "RAM: 8 GB"
|
||||
Write-Host "Disk: 90 GB dynamically allocated"
|
||||
Write-Host "Network: NAT recommended for this temporary Wi-Fi lab"
|
||||
Write-Host ""
|
||||
Write-Host "After Ubuntu boots, clone AzerothAI and run:"
|
||||
Write-Host " bash deploy/pc-lab/vm-bootstrap.sh"
|
||||
Write-Host ""
|
||||
if ($vmnet8) {
|
||||
Write-Host "The Ubuntu NAT VM should use this Windows Ollama endpoint:"
|
||||
Write-Host " http://$($vmnet8.IP):11434"
|
||||
} elseif ($lanIp) {
|
||||
Write-Host "Fallback Windows Ollama endpoint:"
|
||||
Write-Host " http://${lanIp}:11434"
|
||||
}
|
||||
Write-Host ""
|
||||
Write-Host "Nothing in this script installs Proxmox or modifies your Windows boot configuration."
|
||||
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
LAB_ROOT="${AZEROTHAI_PLAYABLE_ROOT:-$HOME/azerothai-pc-lab}"
|
||||
DEV_PORT="${AZEROTHAI_REMOTE_UI_DEV_PORT:-5173}"
|
||||
PROXY_PORT="${AZEROTHAI_TAILNET_PROXY_PORT:-8788}"
|
||||
APP_PORT="${AZEROTHAI_APP_PORT:-8787}"
|
||||
PID_FILE="$LAB_ROOT/azerothai-ui-dev.pid"
|
||||
LOG_FILE="$LAB_ROOT/azerothai-ui-dev.log"
|
||||
NGINX_SITE="/etc/nginx/sites-available/azerothai-tailnet"
|
||||
NGINX_LINK="/etc/nginx/sites-enabled/azerothai-tailnet"
|
||||
|
||||
need(){ command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2; exit 1; }; }
|
||||
need curl
|
||||
need tailscale
|
||||
need pnpm
|
||||
|
||||
TAILSCALE_IP="$(tailscale ip -4 2>/dev/null | head -n1 || true)"
|
||||
if [[ -z "$TAILSCALE_IP" ]]; then
|
||||
echo "No active Tailscale IPv4 address found." >&2
|
||||
exit 2
|
||||
fi
|
||||
DNS_NAME="$(tailscale status --json 2>/dev/null | python3 -c 'import json,sys; d=json.load(sys.stdin); print((d.get("Self",{}).get("DNSName") or "").rstrip("."))' 2>/dev/null || true)"
|
||||
|
||||
echo "==> Rebuilding live AzerothAI backend with current source"
|
||||
cd "$REPO_ROOT"
|
||||
pnpm build
|
||||
if [[ -f "$LAB_ROOT/azerothai.pid" ]]; then
|
||||
OLD_APP_PID="$(cat "$LAB_ROOT/azerothai.pid" 2>/dev/null || true)"
|
||||
if [[ -n "$OLD_APP_PID" ]]; then kill "$OLD_APP_PID" 2>/dev/null || true; fi
|
||||
fi
|
||||
pkill -f "wrangler.*8787" 2>/dev/null || true
|
||||
pkill -f "pnpm.*start:lan" 2>/dev/null || true
|
||||
sleep 2
|
||||
nohup pnpm start:lan >"$LAB_ROOT/azerothai.log" 2>&1 &
|
||||
echo $! > "$LAB_ROOT/azerothai.pid"
|
||||
|
||||
echo "==> Waiting for live AzerothAI backend"
|
||||
for _ in {1..90}; do
|
||||
if curl -fsS "http://127.0.0.1:$APP_PORT/api/health" >/dev/null 2>&1; then break; fi
|
||||
sleep 1
|
||||
done
|
||||
curl -fsS "http://127.0.0.1:$APP_PORT/api/health" >/dev/null
|
||||
|
||||
echo "==> Restarting autonomous high-level service if this lab is configured"
|
||||
RUNTIME_ENV="$LAB_ROOT/runtime.env"
|
||||
if [[ -f "$RUNTIME_ENV" ]] && grep -q '^AUTONOMY_TOKEN=' "$RUNTIME_ENV"; then
|
||||
bash "$REPO_ROOT/deploy/playable/install-autonomy-service.sh"
|
||||
fi
|
||||
|
||||
echo "==> Ensuring durable ALE database relay is active"
|
||||
if [[ -f "$RUNTIME_ENV" ]] && grep -q '^ALE_BRIDGE_TOKEN=' "$RUNTIME_ENV" && grep -q '^DOCKER_DB_ROOT_PASSWORD=' "$RUNTIME_ENV"; then
|
||||
bash "$REPO_ROOT/deploy/playable/install-ale-relay-service.sh"
|
||||
fi
|
||||
|
||||
echo "==> Starting hydration-safe dashboard UI"
|
||||
mkdir -p "$LAB_ROOT"
|
||||
if [[ -f "$PID_FILE" ]]; then
|
||||
OLD_PID="$(cat "$PID_FILE" 2>/dev/null || true)"
|
||||
if [[ -n "$OLD_PID" ]]; then kill "$OLD_PID" 2>/dev/null || true; fi
|
||||
fi
|
||||
pkill -f "vinext.*$DEV_PORT" 2>/dev/null || true
|
||||
cd "$REPO_ROOT"
|
||||
nohup pnpm dev >"$LOG_FILE" 2>&1 &
|
||||
echo $! > "$PID_FILE"
|
||||
|
||||
for _ in {1..90}; do
|
||||
if curl -fsS "http://127.0.0.1:$DEV_PORT/" >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if ! curl -fsS "http://127.0.0.1:$DEV_PORT/" >/dev/null 2>&1; then
|
||||
echo "Dashboard UI dev server did not start." >&2
|
||||
tail -80 "$LOG_FILE" >&2 || true
|
||||
exit 3
|
||||
fi
|
||||
|
||||
echo "==> Installing nginx if needed"
|
||||
if ! command -v nginx >/dev/null 2>&1; then
|
||||
sudo apt-get update
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nginx
|
||||
fi
|
||||
|
||||
echo "==> Wiring UI to the live AzerothAI API"
|
||||
sudo tee "$NGINX_SITE" >/dev/null <<EOF
|
||||
server {
|
||||
listen 127.0.0.1:$PROXY_PORT;
|
||||
server_name _;
|
||||
|
||||
location ^~ /api/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
proxy_set_header Host \$http_host;
|
||||
proxy_set_header X-Forwarded-Host \$http_host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_pass http://127.0.0.1:$APP_PORT;
|
||||
add_header Cache-Control "no-store" always;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host 127.0.0.1:$DEV_PORT;
|
||||
proxy_set_header X-Forwarded-Host \$http_host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_pass http://127.0.0.1:$DEV_PORT;
|
||||
add_header Cache-Control "no-store" always;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
sudo ln -sfn "$NGINX_SITE" "$NGINX_LINK"
|
||||
sudo nginx -t
|
||||
sudo systemctl enable --now nginx
|
||||
sudo systemctl reload nginx
|
||||
|
||||
echo "==> Verifying dashboard and live API through proxy"
|
||||
VERIFY_HOST="${DNS_NAME:-$TAILSCALE_IP}"
|
||||
curl -fsS -H "Host: $VERIFY_HOST" "http://127.0.0.1:$PROXY_PORT/" >/dev/null
|
||||
curl -fsS -H "Host: $VERIFY_HOST" "http://127.0.0.1:$PROXY_PORT/api/state" >/dev/null
|
||||
ORIGIN="https://$VERIFY_HOST"
|
||||
ACTION_BODY="$(mktemp)"
|
||||
ACTION_CODE="$(curl -sS -o "$ACTION_BODY" -w '%{http_code}' -X POST -H "Host: $VERIFY_HOST" -H "Origin: $ORIGIN" -H "Content-Type: application/json" --data '{}' "http://127.0.0.1:$PROXY_PORT/api/actions" || true)"
|
||||
if [[ "$ACTION_CODE" == "403" ]] && grep -q "Cross-origin action rejected" "$ACTION_BODY"; then
|
||||
echo "Remote action origin check is still rejecting the trusted tailnet host." >&2
|
||||
cat "$ACTION_BODY" >&2
|
||||
rm -f "$ACTION_BODY"
|
||||
exit 47
|
||||
fi
|
||||
rm -f "$ACTION_BODY"
|
||||
echo "Remote action origin check: accepted (HTTP $ACTION_CODE expected for empty test action)"
|
||||
|
||||
echo "==> Publishing privately with Tailscale Serve"
|
||||
sudo tailscale serve --bg "$PROXY_PORT"
|
||||
|
||||
STAMP="$(date +%s)"
|
||||
|
||||
echo
|
||||
echo "============================================================"
|
||||
echo " AzerothAI remote dashboard is ready"
|
||||
echo "============================================================"
|
||||
if [[ -n "$DNS_NAME" ]]; then
|
||||
echo "Open this fresh URL:"
|
||||
echo " https://$DNS_NAME/?v=$STAMP"
|
||||
else
|
||||
echo "Run: tailscale serve status"
|
||||
fi
|
||||
echo
|
||||
echo "UI log: $LOG_FILE"
|
||||
echo "Live API remains on port $APP_PORT."
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
LAB_ROOT="${AZEROTHAI_PLAYABLE_ROOT:-$HOME/azerothai-pc-lab}"
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
echo "==> Rebuilding AzerothAI so server HTML and client bundles match"
|
||||
pnpm build
|
||||
|
||||
echo "==> Restarting AzerothAI runtime"
|
||||
if [[ -f "$LAB_ROOT/azerothai.pid" ]]; then
|
||||
PID="$(cat "$LAB_ROOT/azerothai.pid" 2>/dev/null || true)"
|
||||
if [[ -n "$PID" ]]; then kill "$PID" 2>/dev/null || true; fi
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
nohup pnpm start:lan >"$LAB_ROOT/azerothai.log" 2>&1 &
|
||||
echo $! > "$LAB_ROOT/azerothai.pid"
|
||||
|
||||
echo "==> Waiting for dashboard health"
|
||||
for _ in {1..90}; do
|
||||
if curl -fsS http://127.0.0.1:8787/api/health >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
curl -fsS http://127.0.0.1:8787/api/health >/dev/null
|
||||
|
||||
echo "==> Rebuilding remote Tailscale dashboard proxy"
|
||||
bash "$REPO_ROOT/deploy/pc-lab/tailscale-dashboard.sh"
|
||||
|
||||
DNS_NAME="$(tailscale status --json 2>/dev/null | python3 -c 'import json,sys; d=json.load(sys.stdin); print((d.get("Self",{}).get("DNSName") or "").rstrip("."))' 2>/dev/null || true)"
|
||||
STAMP="$(date +%s)"
|
||||
echo
|
||||
echo "Remote dashboard repair complete."
|
||||
if [[ -n "$DNS_NAME" ]]; then
|
||||
echo "Open this fresh URL:"
|
||||
echo " https://$DNS_NAME/?v=$STAMP"
|
||||
fi
|
||||
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
STATIC_ROOT="$REPO_ROOT/dist/client"
|
||||
PUBLIC_ROOT="/var/www/azerothai"
|
||||
NGINX_SITE="/etc/nginx/sites-available/azerothai-tailnet"
|
||||
NGINX_LINK="/etc/nginx/sites-enabled/azerothai-tailnet"
|
||||
LOCAL_PROXY_PORT="${AZEROTHAI_TAILNET_PROXY_PORT:-8788}"
|
||||
APP_PORT="${AZEROTHAI_APP_PORT:-8787}"
|
||||
|
||||
need(){ command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2; exit 1; }; }
|
||||
|
||||
if [[ ! -d "$STATIC_ROOT" ]]; then
|
||||
echo "Missing built client assets at $STATIC_ROOT" >&2
|
||||
echo "Run: cd $REPO_ROOT && pnpm build" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
need tailscale
|
||||
need curl
|
||||
|
||||
TAILSCALE_IP="$(tailscale ip -4 2>/dev/null | head -n1 || true)"
|
||||
if [[ -z "$TAILSCALE_IP" ]]; then
|
||||
echo "No active Tailscale IPv4 address found. Run: sudo tailscale up --hostname=azerothai-lab" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "==> Checking AzerothAI backend"
|
||||
curl -fsS "http://127.0.0.1:$APP_PORT/api/health" >/dev/null || {
|
||||
echo "AzerothAI is not responding on 127.0.0.1:$APP_PORT" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "==> Installing nginx if needed"
|
||||
if ! command -v nginx >/dev/null 2>&1; then
|
||||
sudo apt-get update
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nginx
|
||||
fi
|
||||
|
||||
echo "==> Publishing built client assets to an nginx-readable directory"
|
||||
sudo rm -rf "$PUBLIC_ROOT"
|
||||
sudo mkdir -p "$PUBLIC_ROOT"
|
||||
sudo cp -a "$STATIC_ROOT"/. "$PUBLIC_ROOT"/
|
||||
sudo find "$PUBLIC_ROOT" -type d -exec chmod 755 {} +
|
||||
sudo find "$PUBLIC_ROOT" -type f -exec chmod 644 {} +
|
||||
|
||||
echo "==> Configuring a static-asset-safe reverse proxy"
|
||||
sudo tee "$NGINX_SITE" >/dev/null <<EOF
|
||||
server {
|
||||
listen 127.0.0.1:$LOCAL_PROXY_PORT;
|
||||
server_name _;
|
||||
|
||||
root $PUBLIC_ROOT;
|
||||
|
||||
location ~* \\.(?:css|js|mjs|svg|png|ico|woff|woff2)$ {
|
||||
try_files \$uri @azerothai;
|
||||
access_log off;
|
||||
add_header Cache-Control "no-store" always;
|
||||
}
|
||||
|
||||
location / {
|
||||
add_header Cache-Control "no-store" always;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$http_host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_pass http://127.0.0.1:$APP_PORT;
|
||||
}
|
||||
|
||||
location @azerothai {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$http_host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_pass http://127.0.0.1:$APP_PORT;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
sudo ln -sfn "$NGINX_SITE" "$NGINX_LINK"
|
||||
sudo nginx -t
|
||||
sudo systemctl enable --now nginx
|
||||
sudo systemctl reload nginx
|
||||
|
||||
echo "==> Verifying proxied page and repairing asset paths if needed"
|
||||
PAGE="$(curl -fsS -H "Host: $TAILSCALE_IP:$LOCAL_PROXY_PORT" "http://127.0.0.1:$LOCAL_PROXY_PORT/")"
|
||||
|
||||
map_asset_path(){
|
||||
local raw="$1" clean target base match srcdir dstdir
|
||||
clean="${raw%%\?*}"
|
||||
[[ "$clean" == /* ]] || return 0
|
||||
target="$PUBLIC_ROOT$clean"
|
||||
[[ -f "$target" ]] && return 0
|
||||
base="$(basename "$clean")"
|
||||
match="$(find "$PUBLIC_ROOT" "$REPO_ROOT/dist" "$REPO_ROOT/.next" -type f -name "$base" -print -quit 2>/dev/null || true)"
|
||||
[[ -n "$match" ]] || return 0
|
||||
dstdir="$(dirname "$target")"
|
||||
sudo mkdir -p "$dstdir"
|
||||
sudo cp -a "$match" "$target"
|
||||
}
|
||||
|
||||
while IFS= read -r asset; do
|
||||
[[ -n "$asset" ]] || continue
|
||||
map_asset_path "$asset"
|
||||
done < <(printf '%s' "$PAGE" | grep -oE '(href|src)="[^"]+\.(css|js|mjs|svg|png|ico|woff|woff2)(\?[^"]*)?"' | cut -d'"' -f2 | sort -u || true)
|
||||
|
||||
CSS_PATH="$(printf '%s' "$PAGE" | grep -oE 'href="[^"]+\.css[^"]*"' | head -n1 | cut -d'"' -f2 || true)"
|
||||
if [[ -n "$CSS_PATH" ]]; then
|
||||
case "$CSS_PATH" in
|
||||
http://*|https://*) CSS_URL="$CSS_PATH" ;;
|
||||
/*) CSS_URL="http://127.0.0.1:$LOCAL_PROXY_PORT$CSS_PATH" ;;
|
||||
*) CSS_URL="http://127.0.0.1:$LOCAL_PROXY_PORT/$CSS_PATH" ;;
|
||||
esac
|
||||
if curl -fsS -H "Host: $TAILSCALE_IP:$LOCAL_PROXY_PORT" "$CSS_URL" >/dev/null; then
|
||||
echo "Stylesheet reachable through proxy: $CSS_PATH"
|
||||
else
|
||||
echo "Stylesheet still returned 404: $CSS_PATH" >&2
|
||||
echo "Built CSS files found:" >&2
|
||||
find "$PUBLIC_ROOT" -type f -name '*.css' -print | head -20 >&2 || true
|
||||
exit 44
|
||||
fi
|
||||
else
|
||||
echo "Warning: no stylesheet link found in HTML." >&2
|
||||
fi
|
||||
|
||||
echo "==> Verifying all JavaScript bundles through proxy"
|
||||
JS_FAILURES=0
|
||||
while IFS= read -r script; do
|
||||
[[ -n "$script" ]] || continue
|
||||
case "$script" in
|
||||
http://*|https://*) JS_URL="$script" ;;
|
||||
/*) JS_URL="http://127.0.0.1:$LOCAL_PROXY_PORT$script" ;;
|
||||
*) JS_URL="http://127.0.0.1:$LOCAL_PROXY_PORT/$script" ;;
|
||||
esac
|
||||
CODE="$(curl -sS -o /dev/null -w '%{http_code}' -H "Host: $TAILSCALE_IP:$LOCAL_PROXY_PORT" "$JS_URL" || true)"
|
||||
echo "$CODE $script"
|
||||
if [[ "$CODE" != "200" ]]; then JS_FAILURES=$((JS_FAILURES+1)); fi
|
||||
done < <(printf '%s' "$PAGE" | grep -oE 'src="[^"]+\\.(js|mjs)(\\?[^"]*)?"' | cut -d'"' -f2 | sort -u || true)
|
||||
|
||||
STATE_CODE="$(curl -sS -o /dev/null -w '%{http_code}' -H "Host: $TAILSCALE_IP:$LOCAL_PROXY_PORT" "http://127.0.0.1:$LOCAL_PROXY_PORT/api/state" || true)"
|
||||
echo "$STATE_CODE /api/state"
|
||||
if [[ "$STATE_CODE" != "200" ]]; then
|
||||
echo "Dashboard API state is not reachable through the proxy." >&2
|
||||
exit 45
|
||||
fi
|
||||
if [[ "$JS_FAILURES" -ne 0 ]]; then
|
||||
echo "$JS_FAILURES JavaScript bundle(s) are still unavailable." >&2
|
||||
exit 46
|
||||
fi
|
||||
|
||||
echo "==> Publishing privately with Tailscale Serve"
|
||||
sudo tailscale serve --bg "$LOCAL_PROXY_PORT"
|
||||
|
||||
DNS_NAME="$(tailscale status --json 2>/dev/null | python3 -c 'import json,sys; d=json.load(sys.stdin); print((d.get("Self",{}).get("DNSName") or "").rstrip("."))' 2>/dev/null || true)"
|
||||
|
||||
echo
|
||||
echo "============================================================"
|
||||
echo " AzerothAI private dashboard proxy is ready"
|
||||
echo "============================================================"
|
||||
if [[ -n "$DNS_NAME" ]]; then
|
||||
echo "Open on any device in your tailnet:"
|
||||
echo " https://$DNS_NAME/"
|
||||
else
|
||||
echo "Run: tailscale serve status"
|
||||
echo "Then open the HTTPS URL it prints."
|
||||
fi
|
||||
echo
|
||||
echo "Direct raw-IP access to :8787 can still be used for diagnostics,"
|
||||
echo "but the Tailscale Serve HTTPS URL is the recommended remote dashboard."
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
|
||||
if [[ "$(id -u)" -eq 0 ]]; then
|
||||
echo "Run this as your normal Ubuntu user, not root." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
discover_windows_host(){
|
||||
local src prefix network candidate
|
||||
src="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}')"
|
||||
prefix="$(ip -4 -o addr show scope global | awk 'NR==1{print $4}' | cut -d/ -f2)"
|
||||
if [[ -n "$src" && -n "$prefix" && "$prefix" -ge 24 ]]; then
|
||||
network="$(echo "$src" | awk -F. '{print $1"."$2"."$3}')"
|
||||
candidate="$network.1"
|
||||
if curl -fsS --connect-timeout 2 "http://$candidate:11434/api/tags" >/dev/null 2>&1; then
|
||||
printf '%s' "$candidate"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
local gw
|
||||
gw="$(ip route | awk '/default/ {print $3; exit}')"
|
||||
if [[ -n "$gw" ]] && curl -fsS --connect-timeout 2 "http://$gw:11434/api/tags" >/dev/null 2>&1; then
|
||||
printf '%s' "$gw"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ -z "${WINDOWS_HOST_IP:-}" ]]; then
|
||||
WINDOWS_HOST_IP="$(discover_windows_host || true)"
|
||||
fi
|
||||
|
||||
if ! sudo -n true 2>/dev/null; then
|
||||
echo "This setup needs sudo for Docker/packages. Ubuntu may ask for your password."
|
||||
fi
|
||||
|
||||
echo "==> Installing Ubuntu prerequisites"
|
||||
sudo apt-get update
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl git jq openssl python3 unzip lvm2
|
||||
|
||||
echo "==> Expanding Ubuntu root LVM to use free virtual-disk space"
|
||||
ROOT_DEV="$(findmnt -n -o SOURCE / || true)"
|
||||
if [[ "$ROOT_DEV" == /dev/mapper/* ]] && command -v lvs >/dev/null 2>&1; then
|
||||
FREE_BYTES="$(sudo vgs --noheadings --units b --nosuffix -o vg_free 2>/dev/null | awk '{sum+=$1} END{printf "%.0f",sum}')"
|
||||
if [[ "${FREE_BYTES:-0}" -gt 1073741824 ]]; then
|
||||
sudo lvextend -l +100%FREE -r "$ROOT_DEV"
|
||||
fi
|
||||
fi
|
||||
df -h /
|
||||
|
||||
if ! command -v node >/dev/null 2>&1 || [[ "$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)" -lt 22 ]]; then
|
||||
echo "==> Installing Node.js 22"
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs
|
||||
fi
|
||||
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo "==> Installing pnpm"
|
||||
sudo corepack enable
|
||||
sudo corepack prepare pnpm@11.25.0 --activate
|
||||
fi
|
||||
|
||||
echo "Node: $(node --version)"
|
||||
echo "pnpm: $(pnpm --version)"
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "==> Installing Docker Engine"
|
||||
curl -fsSL https://get.docker.com | sudo sh
|
||||
sudo usermod -aG docker "$USER"
|
||||
echo
|
||||
echo "Docker was installed and your user was added to the docker group."
|
||||
echo "Log out of Ubuntu and back in, then rerun this script."
|
||||
exit 20
|
||||
fi
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "Docker exists but this user cannot access it yet."
|
||||
echo "Log out of Ubuntu and back in, then rerun this script."
|
||||
exit 21
|
||||
fi
|
||||
|
||||
echo "==> Checking Windows-hosted Ollama"
|
||||
OLLAMA_AVAILABLE=0
|
||||
if [[ -n "${WINDOWS_HOST_IP:-}" ]] && curl -fsS --connect-timeout 4 "http://$WINDOWS_HOST_IP:11434/api/tags" >/tmp/ollama-tags.json; then
|
||||
OLLAMA_AVAILABLE=1
|
||||
echo "Ollama reachable at http://$WINDOWS_HOST_IP:11434"
|
||||
else
|
||||
echo "Windows Ollama is not reachable yet. Continuing with deterministic AzerothAI decisions."
|
||||
echo "The game realm can boot first; Qwen can be connected afterward without rebuilding AzerothCore."
|
||||
fi
|
||||
|
||||
echo "==> Bootstrapping playable AzerothAI realm"
|
||||
export AZEROTHAI_PLAYABLE_ROOT="${AZEROTHAI_PLAYABLE_ROOT:-$HOME/azerothai-pc-lab}"
|
||||
bash "$REPO_ROOT/deploy/playable/bootstrap.sh"
|
||||
|
||||
LAB_ROOT="$AZEROTHAI_PLAYABLE_ROOT"
|
||||
if [[ "$OLLAMA_AVAILABLE" -eq 1 ]]; then
|
||||
echo "==> Pointing AzerothAI at Qwen on the Windows RTX host"
|
||||
DEV_VARS="$REPO_ROOT/.dev.vars"
|
||||
python3 - "$DEV_VARS" "http://$WINDOWS_HOST_IP:11434" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
p=Path(sys.argv[1])
|
||||
value=sys.argv[2]
|
||||
lines=p.read_text().splitlines() if p.exists() else []
|
||||
out=[]
|
||||
found=False
|
||||
for line in lines:
|
||||
if line.startswith("LLM_URL="):
|
||||
out.append("LLM_URL="+value)
|
||||
found=True
|
||||
else:
|
||||
out.append(line)
|
||||
if not found:
|
||||
out.append("LLM_URL="+value)
|
||||
p.write_text("\n".join(out).rstrip()+"\n")
|
||||
PY
|
||||
chmod 600 "$DEV_VARS"
|
||||
|
||||
echo "==> Restarting AzerothAI runtime with host Ollama endpoint"
|
||||
if [[ -f "$LAB_ROOT/azerothai.pid" ]]; then
|
||||
pid="$(cat "$LAB_ROOT/azerothai.pid")"
|
||||
kill "$pid" 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
cd "$REPO_ROOT"
|
||||
nohup pnpm start:lan >"$LAB_ROOT/azerothai.log" 2>&1 &
|
||||
echo $! > "$LAB_ROOT/azerothai.pid"
|
||||
|
||||
for _ in {1..60}; do
|
||||
if curl -fsS http://127.0.0.1:8787/api/health >/tmp/azerothai-health.json 2>/dev/null; then break; fi
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
VM_IP="$(hostname -I | awk '{print $1}')"
|
||||
echo
|
||||
echo "============================================================"
|
||||
echo " TEMPORARY PC LAB READY"
|
||||
echo "============================================================"
|
||||
echo "Ubuntu VM IP : $VM_IP"
|
||||
echo "Windows host : ${WINDOWS_HOST_IP:-not connected yet}"
|
||||
if [[ "$OLLAMA_AVAILABLE" -eq 1 ]]; then
|
||||
echo "Ollama : http://$WINDOWS_HOST_IP:11434"
|
||||
else
|
||||
echo "Ollama : not connected yet (realm still usable)"
|
||||
fi
|
||||
echo
|
||||
echo "Next:"
|
||||
echo " 1. Create your WoW/GM account using the worldserver console."
|
||||
echo " 2. Put the Ubuntu VM IP into Windows WoW realmlist.wtf."
|
||||
echo " 3. Open AzerothAI World Monitor from Windows/iPad:"
|
||||
echo " http://$VM_IP:8787"
|
||||
echo
|
||||
echo "Check everything:"
|
||||
echo " AZEROTHAI_PLAYABLE_ROOT=$AZEROTHAI_PLAYABLE_ROOT bash deploy/playable/status.sh"
|
||||
Reference in New Issue
Block a user