hardware/windows/pi-web-autostart.md

pi-web autostart (Windows, logon task)

Pi Web is a local browser UI for the pi coding agent. This document describes the autostart setup on this machine (Nuk, user domin), which starts the server in the background at every logon, with no console window.

Set up: 2026-09-17 ยท Host: Nuk ยท User: NUK\domin ยท Windows 11 Pro (build 26200)

A second pi-web runs inside WSL on port 30142 (pi-wsl.dph.am), using the WSL Node/pi install rather than this Windows one. See ../ubuntu-wsl/services/pi-web.md.


1. What happens at logon

  1. Windows Task Scheduler fires the pi-web task 30 seconds after NUK\domin logs on.
  2. The task runs a hidden Windows PowerShell process (no window, no taskbar entry).
  3. That process (autostart.ps1) sets the pi-web environment variables and launches the global pi-web shim bound to 0.0.0.0:30141.
  4. All stdout/stderr goes to a log file; the browser is not opened automatically.

Result: Pi Web is reachable at http://192.168.1.153:30141/ or http://Nuk:30141/ shortly after logging in.


2. Files

All files live in %LOCALAPPDATA%\pi-web, i.e. C:\Users\domin\AppData\Local\pi-web.

File Purpose
autostart.ps1 The launcher. Environment variables live here. Starts pi-web, writes the log, propagates the exit code. Accepts optional -Port, -Hostname, -MaxLogBytes for testing.
install-autostart.ps1 Registers/refreshes the pi-web scheduled task. Idempotent โ€” safe to re-run any time. Does not require admin rights.
logs\pi-web.log Runtime output (appended, UTF-8).
logs\pi-web.log.1 Previous log, kept after rotation.

Related but separate:

File Purpose
C:\Users\domin\Desktop\Start pi-web.cmd Original manual launcher (visible window, pauses on error). Has its own copy of the environment variables.
C:\Program Files\nodejs\pi-web.cmd Global npm shim for @agegr/pi-web. Resolved at each start, so upgrading pi-web does not break autostart.

3. Environment variables

Set inside autostart.ps1. Defaults in use:

Variable Value Meaning
PI_WEB_ALLOWED_HOSTS 192.168.1.153,Nuk Extra hostnames accepted by the server (reverse proxies / direct LAN access).
PI_WEB_NO_OPEN 1 Never open a browser automatically.

Commented-out options available in the file:

Variable Effect
PI_WEB_PASSWORD Require a password for browser logins (HTTP Basic Auth, user pi). Recommended while bound to 0.0.0.0.
PI_WEB_IDLE_TIMEOUT_MS 0 disables the 10-minute idle shutdown.
PI_WEB_SKIP_VERSION_CHECK 1 skips update checks on every boot.
HTTP_PROXY / HTTPS_PROXY / NO_PROXY Honored for server-side model and API requests if set; not configured here.

Windows Firewall

An inbound rule named pi-web (TCP 30141) allows LAN access: Allow, TCP 30141, profile Private, no Program restriction (see Troubleshooting below for why a Program-scoped rule doesn't work here). Recreate it if missing:

New-NetFirewallRule -DisplayName "pi-web (TCP 30141)" `
  -Direction Inbound -Action Allow -Protocol TCP -LocalPort 30141 -Profile Private

Bind address and port are not environment variables here โ€” the wrapper passes them as CLI flags (-H 0.0.0.0 -p 30141). Pi Web recognizes PI_WEB_HOSTNAME/PORT as the equivalent environment form, and the CLI sets PI_WEB_HOSTNAME for its child process.

โš ๏ธ Duplication: PI_WEB_ALLOWED_HOSTS and the bind address also exist in Desktop\Start pi-web.cmd. If the machine's DHCP address changes, update both files (or make the desktop script delegate to autostart.ps1).


4. Scheduled task definition

Task name: pi-web (root folder \, visible in Task Scheduler).

Field Value
Trigger At logon of NUK\domin, delay PT30S
Action C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe
Arguments -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File "C:\Users\domin\AppData\Local\pi-web\autostart.ps1"
Principal NUK\domin, logon type Interactive, run level Limited (no elevation)
Execution time limit PT0S (unlimited โ€” the server runs until logoff)
Multiple instances IgnoreNew (a second run cannot double-start the server)
Start when available Enabled (catches up if the logon trigger was missed)
Power Starts on battery; does not stop when switching to battery
Restart on failure 3 attempts, 1 minute apart

install-autostart.ps1 is the source of truth for these settings โ€” edit it and re-run rather than tweaking the task by hand.


5. Common operations

Run these in a normal (non-elevated) PowerShell window.

# Status of the task
Get-ScheduledTask -TaskName pi-web | Select-Object TaskName, State
Get-ScheduledTaskInfo -TaskName pi-web | Select-Object LastRunTime, LastTaskResult

# Start it now (stop any running pi-web first - see Troubleshooting)
Start-ScheduledTask -TaskName pi-web

# Watch the log
Get-Content "$env:LOCALAPPDATA\pi-web\logs\pi-web.log" -Encoding UTF8 -Wait

# Stop it
Stop-ScheduledTask -TaskName pi-web

# Temporarily disable / re-enable autostart
Disable-ScheduledTask -TaskName pi-web
Enable-ScheduledTask  -TaskName pi-web

# Remove autostart completely
Unregister-ScheduledTask -TaskName pi-web -Confirm:$false

# Re-apply settings (e.g. after changing the delay)
& "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" `
    -NoProfile -ExecutionPolicy Bypass `
    -File "$env:LOCALAPPDATA\pi-web\install-autostart.ps1"

Stopping the task stops the wrapper; the node child processes normally exit with it. If they linger, see below.


6. Verification performed at setup

The wrapper and the real task definition were both tested on a throwaway port (30142) so the live instance on 30141 was never disturbed:

Check Result
Env vars present in the child process PI_WEB_ALLOWED_HOSTS=192.168.1.153,Nuk logged
Server responds HTTP 200 from http://127.0.0.1:30142/
Window hidden MainWindowHandle=0 on the wrapper process
Task plumbing Duplicate task ran the wrapper via Task Scheduler, result 0x41301 (running)
Log encoding Clean UTF-8, no PowerShell error-record noise

The temporary task, its processes, and the test log were all removed afterwards. Test the real thing at the next logon, or manually with Start-ScheduledTask.


7. Troubleshooting

Symptom Cause / fix
Nothing in the log after logon Task disabled or not triggered. Check Get-ScheduledTask -TaskName pi-web. Also LastTaskResult โ€” 0x41301 means still running.
EADDRINUSE / port 30141 already used Another pi-web is already running (e.g. the Desktop .cmd). Stop it, or Get-NetTCPConnection -LocalPort 30141 -State Listen to find the owner.
Browser shows "Invalid host header" The address you used is not in PI_WEB_ALLOWED_HOSTS. Add it to autostart.ps1 and restart the task.
Unreachable from another device Confirm you bound 0.0.0.0 (yes, by default) and that Windows Firewall allows port 30141 inbound. Gotcha (found 2026-09-18): C:\Program Files\nodejs is an nvm-windows symlink (currently โ†’ C:\Users\domin\AppData\Roaming\nvm\v26.9.0). A Windows Firewall rule scoped to -Program "C:\Program Files\nodejs\node.exe" silently never matched real traffic, even though Get-Process reported that exact path. Fix: use a port-only inbound Allow rule with no -Program filter (see rule pi-web (TCP 30141), Private profile, TCP 30141, Program=Any). Symlink path matching for firewall rules is unreliable with nvm-windows โ€” don't scope future rules to a Program path here.
Works via curl/on the Windows machine itself, not from another device's browser Testing http://<LAN-IP>:30141 from the same Windows PC isn't a real cross-device test โ€” Windows shortcuts self-addressed traffic internally, bypassing the normal per-NIC firewall path. Use curl -v http://192.168.1.153:30141 from the other device's terminal for a real test (bypasses browser weirdness โ€” shows a clean connect/HTTP status instead of a vague "site can't be reached").
Works in Safari but not Chrome (Incognito) on a Mac client Seen 2026-09-18: confirmed via curl that networking + Windows Firewall were fine; Safari loaded the page fine, Chrome Incognito still failed. Resolved by fully quitting and reopening Chrome โ€” stale cached state (likely HSTS/connection cache) for that host:port, not a server/firewall issue. If it recurs, try a full Chrome restart first, then chrome://net-internals/#hsts.
Wrong Node version / pi-web not found The wrapper falls back to pi-web on PATH. nvm-windows repoints C:\Program Files\nodejs, so activate the intended version with nvm use <ver> and re-run the task.
Stale node processes after stopping Get-Process node then Stop-Process -Id <pid>. Killing the parent does not always kill children on Windows.
Log grows too large Rotation is automatic at 5 MB (pi-web.log โ†’ pi-web.log.1). Change -MaxLogBytes in autostart.ps1.

Useful one-liner to see what is actually listening:

Get-NetTCPConnection -LocalPort 30141 -State Listen |
    ForEach-Object { Get-CimInstance Win32_Process -Filter "ProcessId=$($_.OwningProcess)" } |
    Select-Object ProcessId, CommandLine

8. Security notes

  • The server binds 0.0.0.0, so any device on the LAN can reach it.
  • No password is configured. Pi Web drives an agent with filesystem access, so anyone on the network can execute commands through it. Set PI_WEB_PASSWORD in autostart.ps1 (a long random value) and restart the task.
  • Password auth does not encrypt traffic โ€” HTTP only. For access beyond a trusted LAN, use HTTPS via a reverse proxy or a VPN.
  • The task runs unelevated (RunLevel: Limited), so pi-web gets normal user rights only.

9. Limitations and design notes

  • Logon trigger, not boot trigger. Pi Web needs the user profile and ~/.pi/agent data, so it starts when domin logs in. Running it with no one logged in would require a "run whether user is logged on or not" task with stored credentials.
  • 30-second delay exists so PATH, Node, and the network are ready. Change with install-autostart.ps1 -DelaySeconds <n>.
  • No auto-open โ€” the browser is deliberately not launched on boot.
  • Single instance โ€” IgnoreNew plus the fixed port prevent duplicates.
  • Updating the package (npm install -g @agegr/pi-web@latest) needs no changes here.