<# GridShare Node Agent — Windows Installer (WSL2 + Docker + NVIDIA) Run in an **Administrator** PowerShell: irm https://gridshare.in/dl/install.ps1 | iex What it does: 1. Verifies Windows 10 2004+ / 11 and detects your NVIDIA GPU 2. Installs WSL2 + Ubuntu (reboots ONCE if WSL wasn't enabled — then you re-run the same command and it continues automatically) 3. Runs the GridShare Linux agent inside Ubuntu, which installs Docker + the NVIDIA Container Toolkit, verifies GPU passthrough, and registers your node 4. Adds a logon task so the node auto-starts after every reboot GridShare on Windows = the Linux agent running inside WSL2 with GPU passthrough (the standard, well-supported path for CUDA on Windows). No native Windows runner exists; WSL2 is how the GPU reaches Docker containers. Tested target: Windows 10 22H2 / Windows 11 23H2 with a recent NVIDIA driver. Support: support@gridshare.in #> $ErrorActionPreference = "Stop" # Account-first install: set $env:GRIDSHARE_TOKEN (a gsp_... provision token minted in # the portal) to install with ZERO prompts. Central reads name/UPI/city off the provider # record saved at signup. Priority: TOKEN > interactive prompts. The token is forwarded # into WSL2 and the Linux agent's own token path does the single /nodes/provision call. $GridShareToken = if ($env:GRIDSHARE_TOKEN) { $env:GRIDSHARE_TOKEN.Trim() } else { "" } $env:WSL_UTF8 = "1" # make wsl.exe emit UTF-8; Windows PowerShell otherwise reads UTF-16 + null bytes and string matches silently fail $Distro = "Ubuntu-24.04" # pinned LTS: 24.04 has a Docker apt repo + real docker.service + NVIDIA toolkit. Default "Ubuntu" now pulls 26.04 (no Docker repo). CTO-approved 2026-06-14. $LinuxInstaller = "https://gridshare.in/dl/install.sh" $NodeBin = "/opt/gridshare-node/venv/bin/gridshare-node" $TaskName = "GridShareNode" function Hd { param($m) Write-Host "`n -- $m" -ForegroundColor Cyan } function OK { param($m) Write-Host " [OK] $m" -ForegroundColor Green } function Inf { param($m) Write-Host " $m" -ForegroundColor Gray } function Wn { param($m) Write-Host " [!] $m" -ForegroundColor Yellow } function Die { param($m) Write-Host "`n [X] $m`n" -ForegroundColor Red; exit 1 } Write-Host "" Write-Host " ==============================================================" -ForegroundColor Cyan Write-Host " GridShare - Windows GPU Node Installer (WSL2 + NVIDIA)" -ForegroundColor Cyan Write-Host " ==============================================================" -ForegroundColor Cyan # ── Admin check (do NOT rely on #Requires — `irm | iex` does not enforce it) ── $me = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() if (-not $me.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Die "Please run this in an Administrator PowerShell (right-click PowerShell -> Run as administrator)." } # ── Windows build check ────────────────────────────────────────────────────── Hd "Checking Windows version" $build = [int](Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").CurrentBuildNumber if ($build -lt 19041) { Die "Windows 10 version 2004 (build 19041) or newer is required. Yours: build $build." } OK "Windows build $build" # ── NVIDIA GPU check ───────────────────────────────────────────────────────── Hd "Detecting NVIDIA GPU" $gpu = (Get-CimInstance Win32_VideoController -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "NVIDIA" } | Select-Object -First 1).Name if ($gpu) { OK "GPU: $gpu" } else { Wn "No NVIDIA GPU detected. GridShare on Windows needs an NVIDIA GPU for GPU jobs." $ans = Read-Host " Continue anyway (CPU-only)? [y/N]" if ($ans -notmatch '^[Yy]') { exit 0 } } Inf "Tip: keep your NVIDIA GeForce driver current — that is what exposes the GPU to WSL2." # ── WSL readiness — ROBUST checks (the old single check false-positived) ────── # Field bug (factory-reset Win11, 2026-06-16): the box had only the LEGACY inbox # wsl.exe — no `--version`, rejects modern distro names. `wsl -d $Distro -u root # -e true` returned exit 0 while printing usage to stderr (suppressed by 2>$null) # → we wrongly judged WSL "ready", SKIPPED the whole install, and every later step # failed with usage text. Fix: test the MODERN engine and the distro REGISTRATION # explicitly, never trusting one suppressed exit code. function Test-ModernWsl { # The legacy stub does NOT implement `--version` (prints usage). Modern WSL # (Store / `wsl --install`) prints a real "WSL version:" line. WSL_UTF8=1 (set # at the top) makes this output clean ASCII so the match is reliable. $out = (& wsl.exe --version 2>&1 | Out-String) return ($out -match 'WSL version') } function Test-DistroReady { # Need: modern engine + distro REGISTERED (in `wsl -l -q`) + a command actually # runs inside it. All three, or it's not ready. if (-not (Test-ModernWsl)) { return $false } $listed = (& wsl.exe -l -q 2>&1 | Out-String) if ($listed -notmatch [regex]::Escape($Distro)) { return $false } & wsl.exe -d $Distro -u root -e true 2>$null return ($LASTEXITCODE -eq 0) } Hd "Setting up WSL2 + $Distro (one command, at most one restart, no typing)" # One-shot reboot guard: the GridShareResume task exists only AFTER we've rebooted # once, so we NEVER reboot twice. A resumed run continues without another reboot. $resumed = $false try { if (Get-ScheduledTask -TaskName 'GridShareResume' -ErrorAction SilentlyContinue) { $resumed = $true } } catch {} function Invoke-RebootResume([string]$why) { Hd "One-time restart needed" Wn "$why Windows restarts ONCE, then finishes on its own -- you type nothing, and it will NOT loop." $resume = "[Net.ServicePointManager]::SecurityProtocol='Tls12'; try { irm https://gridshare.in/dl/install.ps1 | iex } finally { Unregister-ScheduledTask -TaskName 'GridShareResume' -Confirm:`$false -ErrorAction SilentlyContinue }" try { $a = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -Command `"$resume`"" $t = New-ScheduledTaskTrigger -AtLogOn $p = New-ScheduledTaskPrincipal -UserId "$env:USERNAME" -RunLevel Highest Register-ScheduledTask -TaskName "GridShareResume" -Action $a -Trigger $t -Principal $p -Force | Out-Null OK "Auto-resume armed (one-shot) -- continues by itself after you log back in." } catch { Wn "Couldn't arm auto-resume; after restarting, just re-run this installer." } $r = Read-Host " Restart now? [Y/n]" if ($r -notmatch '^[Nn]') { Restart-Computer -Force } else { Inf "Restart when ready -- GridShare resumes automatically at login." } exit 0 } if (Test-DistroReady) { OK "WSL2 + $Distro already ready" } else { # ── Phase 1: ensure the MODERN WSL engine + Virtual Machine Platform exist ── # A factory-reset Windows often ships only the legacy inbox stub. `wsl --install # --no-distribution` pulls the real engine; it needs ONE reboot to switch on the # Virtual Machine Platform feature. if (-not (Test-ModernWsl)) { Inf "Installing the modern WSL engine + Virtual Machine Platform ..." & wsl.exe --install --no-distribution 2>&1 | Out-Null & dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart 2>&1 | Out-Null & dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart 2>&1 | Out-Null if (-not $resumed) { Invoke-RebootResume "WSL had to be installed." } # Resumed and still no modern engine → try an update once more. if (-not (Test-ModernWsl)) { & wsl.exe --update 2>&1 | Out-Null; Start-Sleep -Seconds 5 } } # ── Phase 2: install the distro NON-INTERACTIVELY (no UNIX-user prompt) ────── # --no-launch registers Ubuntu WITHOUT the first-run username/password setup — # the agent runs as root, so no Linux user is ever needed. This is the step that # makes the provider experience truly hands-off. if (-not (Test-DistroReady)) { Inf "Installing $Distro (no questions asked) ..." try { & wsl.exe --set-default-version 2 2>&1 | Out-Null } catch {} & wsl.exe --install -d $Distro --no-launch 2>&1 | Out-Null Start-Sleep -Seconds 6 # First `wsl -d` triggers the distro's one-time init; do it as root. & wsl.exe -d $Distro -u root -e true 2>&1 | Out-Null Start-Sleep -Seconds 3 if (-not (Test-DistroReady)) { # Distro registered but the VM platform may still need the reboot to run. if (-not $resumed) { Invoke-RebootResume "WSL + $Distro were just installed." } & wsl.exe --update 2>&1 | Out-Null; Start-Sleep -Seconds 5 if (-not (Test-DistroReady)) { Unregister-ScheduledTask -TaskName 'GridShareResume' -Confirm:$false -ErrorAction SilentlyContinue Die "WSL2 still isn't ready after the restart. Run 'wsl --install -d $Distro' once manually, then re-run this installer (it will NOT loop)." } } } OK "WSL2 + $Distro ready" } # ── Collect provider details (here, where there's a real console) ──────────── # On the token path we ask NOTHING — central already has the provider's saved # name/UPI/city from signup, and the token is the only credential we need. if (-not $GridShareToken) { Hd "Your earnings details (UPI payouts only -- never shared publicly)" $ownerName = "" while (-not $ownerName) { $ownerName = (Read-Host " Your full name (for UPI payouts)").Trim() } $upiId = "" while ($upiId -notmatch '@') { $upiId = (Read-Host " UPI ID (e.g. name@upi, mobile@ybl)").Trim() } $city = (Read-Host " Your city (e.g. Chennai)").Trim(); if (-not $city) { $city = "Chennai" } # No price prompt: GridShare sets the hourly rate by GPU tier (providers don't set it). } else { Hd "Account-first install (provision token detected)" Inf "No questions asked -- your saved name / UPI / city come from your dashboard." } # ── Run the GridShare Linux installer inside WSL2 (non-interactive) ─────────── Hd "Installing GridShare inside WSL2 (Docker + NVIDIA toolkit + agent)" Inf "This installs Docker, the NVIDIA Container Toolkit, verifies GPU passthrough, and registers your node." # Pass settings inline (single-quote-escaped for bash) and run install.sh as root. # We use `curl | bash` under GS_NONINTERACTIVE=1, which makes install.sh skip the # `/dev/tty` grab that previously forced the fragile process-substitution `<(curl)` # form. `--` cleanly separates wsl's own args from the bash command line; newer # wsl.exe builds parse that reliably (the old `-e bash -c "<(curl)"` printed usage # on a fresh Win11 box). Env is inlined into the bash -lc string (no WSLENV). function ShQuote([string]$s) { "'" + ($s -replace "'", "'\''") + "'" } if ($GridShareToken) { # Token path: forward ONLY the provision token. The Linux agent's token branch # calls /nodes/provision once and pulls name/UPI/city from the provider record. $gsEnv = "export GRIDSHARE_TOKEN=$(ShQuote $GridShareToken) GS_NONINTERACTIVE=1;" } else { $gsEnv = "export GS_OWNER_NAME=$(ShQuote $ownerName) GS_UPI_ID=$(ShQuote $upiId) GS_CITY=$(ShQuote $city) GS_NONINTERACTIVE=1;" } & wsl.exe -d $Distro -u root -- bash -lc "$gsEnv curl -fsSL $LinuxInstaller | bash" $rc = $LASTEXITCODE if ($rc -ne 0) { Wn "The in-WSL installer exited with code $rc -- see output above; node may not be fully set up." } # ── Boot persistence: keep the node 24/7 — no open window, survives logout+reboot ── Hd "Setting up durable auto-start (24/7 — no terminal to babysit)" # WSL2 shuts a distro down once nothing holds it, so the task holds it with a # foreground `sleep infinity` (systemd then keeps the agent up). The fix vs the old # logon-only task: run it at SYSTEM STARTUP and WHETHER OR NOT the user is logged on # (S4U), with auto-restart if it ever drops — so closing the window, logging out, or # rebooting never takes the node offline. (The provider must NOT keep a terminal open.) $wslCall = "wsl.exe -d $Distro -u root -e bash -lc 'systemctl start gridshare-node; exec sleep infinity'" try { $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -WindowStyle Hidden -Command `"$wslCall`"" # Boot AND logon triggers: AtStartup gives no-login durability; AtLogOn is the # proven fallback for Windows builds where WSL won't launch in a sessionless S4U context. $triggers = @((New-ScheduledTaskTrigger -AtStartup), (New-ScheduledTaskTrigger -AtLogOn)) # S4U = "run whether the user is logged on or not" (the key change from interactive). $principal = New-ScheduledTaskPrincipal -UserId "$env:USERNAME" -LogonType S4U -RunLevel Highest # Never time out; relaunch if WSL/the hold dies; keep running on battery; no dupes. $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries ` -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 999 ` -RestartInterval (New-TimeSpan -Minutes 1) -MultipleInstances IgnoreNew Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $triggers -Principal $principal -Settings $settings -Force | Out-Null Start-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue # up now, no reboot needed OK "Durable auto-start configured ($TaskName) — runs at boot, no window, survives logout/reboot" } catch { Wn "Could not register the durable auto-start task: $($_.Exception.Message). Start manually: $wslCall" } # ── Verify the node is actually LIVE (connected to the relay) ───────────────── # "Installer finished (rc=0)" != "online": confirm the agent really connected to # GridShare via its local /api/status INSIDE WSL (the agent serves :7842 on WSL's # localhost). Gate the final verdict on the real connection state, never just rc — # so we never tell a provider "running" while a firewall / slow connect left the # node invisible in the marketplace. (Same Done!=online fix as the bash installers.) Hd "Verifying your node is LIVE" $NodeOnline = $false foreach ($i in 1..20) { # poll ~40s try { $st = (& wsl.exe -d $Distro -u root -e bash -lc "curl -s --max-time 3 http://127.0.0.1:7842/api/status" 2>$null | Out-String) if ($st -match '"connected"\s*:\s*true') { $NodeOnline = $true; break } } catch {} Start-Sleep -Seconds 2 } if ($NodeOnline) { Hd "Done" OK "LIVE -- your Windows GPU node is connected and now listed in the marketplace." Inf "Dashboard: in a WSL/Ubuntu terminal, open http://localhost:7842" Inf "Logs: wsl -d $Distro -u root -e journalctl -u gridshare-node -f" Inf "Support: support@gridshare.in" } elseif ($rc -ne 0) { Hd "Not finished" Wn "The install hit an error inside WSL (exit $rc) and the node is not online." Inf "Please send the output above to support@gridshare.in and we'll sort it." } else { Hd "Installed -- but not connected yet" Wn "The agent is set up but hasn't connected to GridShare yet (firewall / blocked outbound HTTPS, or a slow first connect)." Inf "Watch it connect: wsl -d $Distro -u root -e journalctl -u gridshare-node -f (look for 'connected')" Inf "Give it a minute, then confirm at https://gridshare.in/provider -- or send the output above to support@gridshare.in." } Write-Host ""