HT-015: WSL↔Windows Bridge
Atlantis ITS — May 2026
Version: 1.1 Status: Active Owner: Shane Hardin Squad Credit: Ozzy (compiled from V20-V23 memory + Trunks production config) Updated: Ozzy — June 10, 2026 (v1.1 — WSL-PM2-Boot fix, INC-005, V26 port map, Tailscale cross-ref) Local Path: C:\AtlantisITS\knowledge Forgejo Path: atlantis-docs/how-to/HT-015-WSL-Bridge.md
Purpose
Document the cross-ecosystem networking architecture between WSL2 (Ubuntu 24.04) and Windows 11 on Trunks — including how services on each side call each other, how to discover the WSL IP, how to configure port forwarding, and how to manage WSL2 memory allocation.
This is the foundation that lets Nova (WSL2) call the Hardware API (Windows), and lets Windows tools reach Ollama, Forgejo, n8n, and other WSL2 services.
Architecture Overview
Trunks runs services across two OS layers simultaneously:
Windows 11 (Host)
├── Nova Hardware API — :5002 (NSSM Windows service) ← INC-004: verify after rebuild
├── Ollama Windows — :11434 (GUI app routing)
├── Codex App — Windows host
└── WSL2 (Ubuntu 24.04) ──────────────────────────────
├── atlantis-ops — :3000
├── atlantis-job-engine — :3005
├── Forgejo — :3010
├── LiteLLM — :4000 (Docker container)
├── AA-NOC frontend — :5200
├── AA-NOC backend — :5201
├── Repo Radar — :5300
├── Tailscale Radar — :5400
├── n8n — :5678
├── Nova Router — :7100
├── nova_moltbook_sandbox — :7202 (Docker)
├── NTFY — :8080
└── Ollama WSL2 — :11434 (Nova brain)
Cross-layer communication: - WSL2 → Windows: via Windows host IP (172.26.x.x:5002) - Windows → WSL2: via localhost (localhost:3000, localhost:5678, localhost:11434, etc.)
Both layers are redundant — if one fails, the other maintains critical services.
Section 1 — WSL2 Memory Configuration
The Problem
WSL2 allocates 50% of Windows RAM by default. On Trunks (32GB), that's 16GB — causing Windows slowdowns when both layers are active under load.
The Fix — .wslconfig
Create or edit C:\Users\Shane\.wslconfig:
[wsl2]
systemd=true
memory=6GB
processors=4
swap=2GB
localhostForwarding=true
gpuSupport=true
Apply the change:
# In Windows PowerShell — restart WSL2
wsl --shutdown
# Then reopen your WSL2 terminal
Verify:
# In WSL2
free -h
# Should show ~6GB total RAM + ~2GB swap
Current Trunks config (May 2026): 6GB RAM, 2GB swap, 4 processors, systemd=true, localhostForwarding=true, gpuSupport=true
Why These Values
- 6GB RAM: Headroom for PM2 services + Ollama inference simultaneously (up from 4GB)
- 2GB swap: Safety net for memory spikes on NVMe — negligible cost
- 4 processors: Balances WSL2 workload without starving Windows
- systemd=true: Enables proper service management in WSL2
- localhostForwarding=true: Windows→WSL2 via localhost without port forwarding rules
- gpuSupport=true: RTX 3060 accessible from WSL2 for Ollama GPU inference
Section 2 — Discovering the WSL2 IP and Windows Host IP
WSL2 IP (from inside WSL2)
# Method 1 — hostname
hostname -I | awk '{print $1}'
# Method 2 — ip command
ip addr show eth0 | grep 'inet ' | awk '{print $2}' | cut -d/ -f1
# Method 3 — check resolv.conf for nameserver (= Windows host IP)
cat /etc/resolv.conf | grep nameserver
WSL2 IP is typically in the
172.26.x.xor172.28.x.xrange. It changes on WSL2 restart — do not hardcode it in scripts.
Windows Host IP (from inside WSL2)
# The nameserver in resolv.conf IS the Windows host IP
cat /etc/resolv.conf | grep nameserver | awk '{print $2}'
# Example output: 172.26.96.1
# Store it dynamically in scripts
WINDOWS_HOST=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}')
echo $WINDOWS_HOST
From Windows — Reaching WSL2 Services
Windows can reach WSL2 services via localhost directly:
# Test WSL2 Ollama from Windows PowerShell
curl http://localhost:11434/api/tags
# Test WSL2 Forgejo from Windows PowerShell
curl http://localhost:3010
# Test WSL2 n8n from Windows PowerShell
curl http://localhost:5678
WSL2 automatically mirrors WSL2 ports to Windows localhost in most cases. If
localhostdoesn't work, use the WSL2 IP directly.
Section 3 — Nova Hardware API Bridge
The Hardware API is a C# service running on Windows (not WSL2) that exposes CPU/GPU temps, RAM usage, and disk metrics via HTTP.
Service Details
| Property | Value |
|---|---|
| Language | C# (LibreHardwareMonitor) |
| Host | Windows — C:\home\shane\tools\nssm-2.24\win64\ |
| Port | :5002 |
| Managed by | NSSM (Windows service) |
| Endpoint | http://localhost:5002 (from Windows) |
| WSL2 call path | http://172.26.x.x:5002 (dynamic Windows host IP) |
Calling the Hardware API from WSL2
# Discover Windows host IP
WINDOWS_HOST=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}')
# Call Hardware API
curl http://$WINDOWS_HOST:5002/hardware
# Example response
# {"cpu_temp": 0, "gpu_temp": 45, "ram_used_gb": 12.4, ...}
Known issue: CPU temp sensor reads 0°C — AMD Tctl/Tdie mapping issue with LibreHardwareMonitor on Ryzen 7 5800X. GPU temp and RAM readings are accurate.
Checking the Hardware API Service
# In Windows PowerShell — check service status
Get-Service | Where-Object {$_.DisplayName -like "*hardware*" -or $_.DisplayName -like "*nova*"}
# Or check via NSSM
C:\home\shane\tools\nssm-2.24\win64\nssm.exe status "Nova Hardware API"
Section 4 — Windows Firewall Rules for WSL2 Services
Some Windows-hosted apps (like Codex App, Ollama Windows GUI) need to reach WSL2 services through the firewall.
Check Existing Rules
# Check for Ollama inbound rule
netsh advfirewall firewall show rule name="Atlantis-Ollama-Inbound-Gate"
Create Inbound Rule for Ollama (if missing)
Win + R→wf.msc→ Enter- Inbound Rules → New Rule
- Port → TCP → Specific local ports:
11434 - Allow the connection
- Apply to Private profile ONLY (not Public)
- Name:
Atlantis-Ollama-Inbound-Gate→ Finish
Trunks status: Rule
Atlantis-Ollama-Inbound-Gateconfirmed created May 19, 2026.
General Pattern for Other Ports
# Add rule via PowerShell (run as Administrator)
New-NetFirewallRule `
-DisplayName "Atlantis-WSL2-[ServiceName]" `
-Direction Inbound `
-Protocol TCP `
-LocalPort [PORT] `
-Action Allow `
-Profile Private
Section 5 — Port Forwarding (Advanced)
In some WSL2 network configurations (especially NAT mode), Windows can't reach WSL2 services via localhost. Use netsh port forwarding as a fallback.
Check WSL2 Network Mode
# In PowerShell — check wslconfig
Get-Content C:\Users\Shane\.wslconfig
If networkingMode=mirrored is set → localhost works automatically, skip this section.
Set Up Port Forwarding (NAT mode only)
# Run as Administrator
# Replace [WSL_IP] with the current WSL2 IP
# Replace [PORT] with the service port
$wslIp = (wsl hostname -I).Trim().Split(" ")[0]
# Forward Ollama
netsh interface portproxy add v4tov4 `
listenport=11434 listenaddress=0.0.0.0 `
connectport=11434 connectaddress=$wslIp
# Forward Forgejo
netsh interface portproxy add v4tov4 `
listenport=3010 listenaddress=0.0.0.0 `
connectport=3010 connectaddress=$wslIp
# Verify rules
netsh interface portproxy show all
Remove Port Forwarding Rules
netsh interface portproxy delete v4tov4 listenport=11434 listenaddress=0.0.0.0
netsh interface portproxy delete v4tov4 listenport=3010 listenaddress=0.0.0.0
Note: Port forwarding rules do not survive WSL2 restart automatically — the WSL2 IP changes. Use a startup script or mirrored networking mode to avoid this.
Section 6 — Mirrored Networking Mode (Recommended)
WSL2 mirrored networking mode makes WSL2 share Windows' network interface — all WSL2 ports are automatically accessible via localhost on Windows without port forwarding rules.
⚠️ Not recommended for Trunks yet.
localhostForwarding=trueis already working and proven. Mirrored mode can conflict with Docker Desktop networking and GPU passthrough (gpuSupport=true). Evaluate in a quiet sprint — suggested: same weekend as Hetzner VPS deploy.
Enable Mirrored Mode (Future Upgrade Path)
Edit C:\Users\Shane\.wslconfig:
[wsl2]
memory=6GB
swap=2GB
processors=4
networkingMode=mirrored
# Apply
wsl --shutdown
# Reopen WSL2 terminal
Benefits: - localhost always works for Windows → WSL2 - No port forwarding rules needed - No IP discovery needed - Survives WSL2 restarts
Caveat: Some older Docker networking configs may need adjustment in mirrored mode. Test before enabling on production Trunks.
Section 7 — WSL2 Service Health Check Script
Quick health check for all WSL2 services from Windows PowerShell:
# Save as C:\AtlantisITS\scripts\wsl-health-check.ps1
$services = @(
@{name="atlantis-ops"; port=3000},
@{name="atlantis-job-engine"; port=3005},
@{name="Forgejo"; port=3010},
@{name="Ollama"; port=11434},
@{name="Nova Router"; port=7100},
@{name="Nova Hardware API"; port=5002}
)
foreach ($svc in $services) {
try {
$response = Invoke-WebRequest -Uri "http://localhost:$($svc.port)" -TimeoutSec 2 -UseBasicParsing -ErrorAction Stop
Write-Host "✅ $($svc.name) :$($svc.port) — OK ($($response.StatusCode))" -ForegroundColor Green
} catch {
Write-Host "❌ $($svc.name) :$($svc.port) — UNREACHABLE" -ForegroundColor Red
}
}
Section 8 — Common Issues & Fixes
| Issue | Cause | Fix |
|---|---|---|
| WSL2 services unreachable from Windows | NAT mode, no port forwarding | Enable mirrored mode or add netsh portproxy rules |
| Windows host IP keeps changing | WSL2 restart assigns new IP | Use cat /etc/resolv.conf dynamically in scripts |
| Hardware API returns 0°C for CPU | AMD Tctl/Tdie mapping issue | Known — GPU and RAM readings accurate |
| WSL2 eating all Windows RAM | Default 50% allocation | Set memory=4GB in .wslconfig |
| Port conflict on :11434 | Both WSL2 + Windows Ollama running | Normal — Windows takes priority for GUI apps |
netsh not found in WSL2 | netsh is Windows-only | Run from Windows PowerShell, not WSL terminal |
| Hardware API service not running | NSSM service stopped | nssm.exe start "Nova Hardware API" in PowerShell |
| Codex App can't reach Ollama | Firewall rule missing | Create Atlantis-Ollama-Inbound-Gate rule — see Section 4 |
| cloudflared offline after reboot | WSL2 boot race condition | WSL-PM2-Boot Task Scheduler (Section 8b) |
| All PM2 services offline after reboot | Same boot race condition | wsl pm2 resurrect then create scheduler job |
Section 8b — WSL2 PM2 Boot Sequencing Fix (INC-005)
The Problem
On Trunks reboot, PM2 starts before WSL2 is fully initialized. cloudflared and other WSL2 services fail silently — PM2 reports them online but they are not actually running. Requires manual wsl pm2 resurrect to recover.
Discovered: TN trip May 26 – June 8, 2026 (INC-005). CF tunnel was DOWN the entire 10-day trip because cloudflared crashed and the boot sequence never auto-recovered it.
Permanent Fix — Windows Task Scheduler (Jimmy's Fix)
Task Scheduler → Create Basic Task:
Name: WSL-PM2-Boot
Trigger: At logon
Action: Start a program
Program: wsl
Arguments: pm2 resurrect
Via elevated PowerShell:
$action = New-ScheduledTaskAction `
-Execute "wsl.exe" `
-Argument "pm2 resurrect"
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal `
-UserId $env:USERNAME -RunLevel Highest
Register-ScheduledTask `
-TaskName "WSL-PM2-Boot" `
-Action $action `
-Trigger $trigger `
-Principal $principal
Verify:
# Reboot Trunks → after login:
wsl pm2 status
# cloudflared should show: online automatically ✅
Manual workaround (before Task Scheduler job exists):
wsl pm2 resurrect
Credit: Jimmy (Gemini) — June 8, 2026
⚠️ Pre-travel rule: Disable Windows Update before any trip.
Settings → Windows Update → Pause updates (35 days)INC-005 root cause: cloudflared crash + Windows Update reboot during TN trip while no one was home.
Quick Reference
# WSL2 — find Windows host IP
cat /etc/resolv.conf | grep nameserver | awk '{print $2}'
# WSL2 — call Hardware API
curl http://$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}'):5002/hardware
# WSL2 — check memory allocation
free -h
# Windows PowerShell — resurrect PM2 after reboot (manual)
wsl pm2 resurrect
# Windows PowerShell — test WSL2 services
curl http://localhost:11434/api/tags
curl http://localhost:3010
curl http://localhost:3000
# Windows PowerShell — check Ollama firewall rule
netsh advfirewall firewall show rule name="Atlantis-Ollama-Inbound-Gate"
# Windows PowerShell — restart WSL2
wsl --shutdown
# Windows PowerShell — check WSL2 status
wsl --status
wsl --list --verbose
Current Trunks WSL Bridge Config (May 2026)
| Setting | Value |
|---|---|
.wslconfig memory | 6GB |
.wslconfig swap | 2GB |
.wslconfig processors | 4 |
.wslconfig systemd | true |
.wslconfig localhostForwarding | true |
.wslconfig gpuSupport | true |
| Networking mode | NAT + localhostForwarding (mirrored mode = future upgrade) |
| Hardware API port | :5002 (Windows) |
| Firewall rule | Atlantis-Ollama-Inbound-Gate (TCP :11434, Private) |
| WSL2 → Windows calls | http://172.26.x.x:5002 (dynamic) |
| Windows → WSL2 calls | http://localhost:[port] |
References
- ATLANTIS_AI_MEMORY.md V23/V24
- HT-013 v1.1: PM2 Process Manager
- HT-014: Windows Service Management
- HT-018 v1.1: Ollama Codex App Bridge (firewall rule context)
- Nova Hardware API:
C:\home\shane\tools\nssm-2.24\win64\ - WSL2 docs: learn.microsoft.com/en-us/windows/wsl/networking
- LibreHardwareMonitor: github.com/LibreHardwareMonitor/LibreHardwareMonitor
Related Documents
| Doc | Relationship |
|---|---|
| HT-016 v1.1 — Nginx WSL2 Reverse Proxy | Builds on HT-015 — uses the WSL2 services this doc maps |
| HT-018 v1.1 — Ollama 0.24.0 + Codex App Bridge Validation | Builds on HT-015 — assumes WSL bridge is working. Go here for Codex App install, upgrade process, model selection, VRAM table, and SOP-008 gates. |
| HT-022 — Tailscale + RustDesk Private Access | Remote access layer that complements WSL bridge — Tailscale makes Trunks reachable remotely; WSL bridge makes services reachable internally |
| HT-013 v1.1 — PM2 Process Manager | WSL2 service management — how PM2 keeps services alive |
| HT-014 — Windows Service Management | Windows-side service management — how NSSM manages Nova Hardware API |
HT-015 v1.1 updated by Ozzy — June 10, 2026 v1.0 compiled by Ozzy — May 21, 2026 v1.1 additions: WSL-PM2-Boot fix (Jimmy), INC-005 context, V26 port map, Tailscale cross-ref Store at: C:\AtlantisITS\knowledge\HT-015-WSL-Bridge.md Push to Forgejo: atlantis-docs/how-to/HT-015-WSL-Bridge.md
Section 9 — Dual Ollama Install & GUI App Routing
Why Two Ollama Installs Exist on Trunks
| Install | Location | Purpose | Starts via |
|---|---|---|---|
| WSL2 Ollama | /usr/local/bin/ollama | Nova brain, squad services, terminal | systemd (auto) |
| Windows Ollama | %LOCALAPPDATA%\Programs\Ollama | GUI apps — Codex App, Cowork, Claude Desktop | Windows tray (auto) |
Both bind to :11434. Windows Ollama owns the tray icon and GUI routing. They coexist — the Atlantis-Ollama-Inbound-Gate firewall rule enables cross-layer communication.
Current GUI App Surface Map
| Command | Surface | Status |
|---|---|---|
ollama launch codex-app --model qwen2.5-coder:7b-instruct-q4_K_M | Codex App | ✅ Validated May 19 |
ollama launch claude | Claude Desktop / Cowork | 🟡 Future |
ollama launch opencode | OpenCode IDE | 🟡 Future |
All
ollama launchcommands must run from Windows PowerShell, not WSL2 terminal.
⚠️ Known Issue: Codex App Loses Ollama After Reboot
Symptom: After rebooting Trunks, Codex App can't find Ollama. Running ollama in PowerShell manually fixes it.
Root cause — boot race condition: 1. Windows boots → Ollama tray app starts 2. Codex App may launch before Ollama daemon fully initializes 3. OR Windows Ollama starts but hasn't loaded the model into context yet 4. Running any ollama command in PowerShell forces full daemon initialization
Fix 1 — Re-run the bridge command after every reboot (current workaround):
# Run in PowerShell after reboot before opening Codex App
ollama launch codex-app --model qwen2.5-coder:7b-instruct-q4_K_M
Fix 2 — Add a startup script to Windows Task Scheduler:
Create C:\AtlantisITS\scripts\start-ollama-bridge.ps1:
# Wait for Ollama daemon to fully initialize
Start-Sleep -Seconds 10
# Pre-warm the model so it's ready for Codex App
$env:OLLAMA_HOST = "http://localhost:11434"
& ollama run qwen2.5-coder:7b-instruct-q4_K_M "ping" 2>$null
Write-Host "Ollama bridge ready for Codex App"
Add to Task Scheduler: 1. Win → search Task Scheduler → Open 2. Create Basic Task → Name: Atlantis Ollama Bridge Warmup 3. Trigger: When I log on 4. Action: Start a program 5. Program: powershell.exe 6. Arguments: -WindowStyle Hidden -ExecutionPolicy Bypass -File "C:\AtlantisITS\scripts\start-ollama-bridge.ps1" 7. Finish
Fix 3 — Windows Ollama startup delay (simplest):
In Windows Ollama settings (tray icon → Settings): - Enable Start Ollama on login if not already enabled - If there's a startup delay option — set to 15-20 seconds to let Windows fully boot first
Fix 4 — Re-run bridge command from Codex App startup hook (if supported):
Check if Codex App has a startup command setting — point it to run:
ollama launch codex-app --model qwen2.5-coder:7b-instruct-q4_K_M
Recommended Approach (Trunks)
Permanent habit going forward: Before extracting ANY zip downloaded from internet or AI tools:
1.) Right-click zip → Properties
2.) Check Unblock at the bottom → Apply
3.) THEN extract
That one checkbox prevents this entire cascade every time.
Until Task Scheduler fix is tested — keep Fix 1 as the habit:
# Morning startup sequence (30 seconds)
ollama list # Confirm models loaded
ollama launch codex-app --model qwen2.5-coder:7b-instruct-q4_K_M
# Codex App now shows Ollama tag ✅