Skip to content

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.x or 172.28.x.x range. 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 localhost doesn'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)

  1. Win + Rwf.msc → Enter
  2. Inbound Rules → New Rule
  3. Port → TCP → Specific local ports: 11434
  4. Allow the connection
  5. Apply to Private profile ONLY (not Public)
  6. Name: Atlantis-Ollama-Inbound-Gate → Finish

Trunks status: Rule Atlantis-Ollama-Inbound-Gate confirmed 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.


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=true is 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

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 launch commands 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
on launch automatically.

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 ✅