-
-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathinstall.ps1
More file actions
257 lines (223 loc) · 9.22 KB
/
Copy pathinstall.ps1
File metadata and controls
257 lines (223 loc) · 9.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# pi-web installer for Windows — downloads the binary and sets up auto-start.
#
# Standalone (no pi required):
# irm https://raw.githubusercontent.com/ygncode/pi-web/main/install.ps1 | iex
#
# Via pi package (also registers /web, /remote commands):
# pi install npm:@ygncode/pi-web@beta
#
# Updates are handled by re-running the same command.
#
# Auto-start model (the Windows counterpart of install.sh's launchd/systemd
# setup, kept admin-free): a HKCU Run-key entry launches pi-web-start.vbs at
# login, which runs pi-web-start.ps1 without a console window; the .ps1 loads
# ~/.config/pi-web/env (PI_WEB_TOKEN, PATH, ...) and starts the binary hidden.
# Requires the pi CLI plus a bash for pi's shell tool (Git Bash is enough —
# see pi's docs/windows.md).
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$Repo = 'ygncode/pi-web'
if ($env:PI_WEB_INSTALL_DIR) {
$InstallDir = $env:PI_WEB_INSTALL_DIR
} else {
# No sudo-writable /usr/local/bin equivalent on Windows; the pi agent bin
# dir works for both npm-lifecycle and standalone installs without elevation.
$InstallDir = Join-Path $HOME '.pi\agent\bin'
}
$Binary = Join-Path $InstallDir 'pi-web.exe'
$VersionFile = Join-Path $HOME '.pi\agent\pi-web-version'
$ConfigDir = Join-Path $HOME '.config\pi-web'
$EnvFile = Join-Path $ConfigDir 'env'
$LauncherPs1 = Join-Path $ConfigDir 'pi-web-start.ps1'
$LauncherVbs = Join-Path $ConfigDir 'pi-web-start.vbs'
$RunKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run'
function Info($msg) { Write-Host "-> $msg" }
function Warn($msg) { Write-Host "! $msg" -ForegroundColor Yellow }
function Get-Arch {
$arch = "$env:PROCESSOR_ARCHITECTURE"
# An x64 PowerShell on ARM64 reports AMD64; prefer the OS architecture.
try { $arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() } catch {}
switch -Regex ($arch) {
'^(ARM64|Arm64)$' { return 'arm64' }
'^(AMD64|X64|x64)$' { return 'amd64' }
default { throw "Unsupported architecture: $arch" }
}
}
function Get-PackageTag {
# When running as an npm lifecycle script, install the binary that matches
# the npm package version so pinned installs stay pinned (see install.sh).
if ($env:npm_package_name -eq '@ygncode/pi-web' -and $env:npm_package_version) {
return 'v' + $env:npm_package_version.TrimStart('v')
}
return $null
}
function Get-LatestTag {
try {
$rel = Invoke-RestMethod "https://api.github.com/repos/$Repo/releases/latest"
if ($rel.tag_name) { return $rel.tag_name }
} catch {}
# /latest ignores prereleases. Fall back to the newest release of any type
# (GitHub orders by creation date; install.sh sorts by semver instead).
$rels = @(Invoke-RestMethod "https://api.github.com/repos/${Repo}/releases?per_page=1")
if ($rels.Count -gt 0 -and $rels[0].tag_name) { return $rels[0].tag_name }
throw "Could not determine latest release tag from $Repo."
}
function Get-InstalledVersion {
if (Test-Path $Binary) {
try {
$v = & $Binary -version 2>$null
if ($v) { return "$v".Trim() }
} catch {}
}
# Binary missing or not runnable (e.g. partial install); fall back to the
# version file.
if (Test-Path $VersionFile) { return (Get-Content $VersionFile -First 1) }
return $null
}
function Get-Binary($arch, $tag) {
$asset = "pi-web-windows-$arch.exe"
$url = "https://github.com/$Repo/releases/download/$tag/$asset"
Info "Downloading pi-web $tag (windows-$arch)..."
Info " $url"
$tmpDir = Join-Path ([IO.Path]::GetTempPath()) ('pi-web-' + [IO.Path]::GetRandomFileName())
New-Item -ItemType Directory -Path $tmpDir | Out-Null
$dest = Join-Path $tmpDir 'pi-web.exe'
Invoke-WebRequest -Uri $url -OutFile $dest -UseBasicParsing
return $dest
}
function Stop-PiWeb {
# Stop the running instance before swapping the binary. Skipped for in-place
# self-updates: pi-web spawned this script (via `pi install`) and restarts
# itself afterward (see internal/app/update.go).
Get-Process -Name 'pi-web' -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 1
}
function Install-Binary($src, $tag) {
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
# A running executable cannot be overwritten on Windows, but it can be
# renamed: move the old binary aside, move the new one into place, then try
# to delete the leftover (harmlessly fails while the old process still runs;
# the next install removes it).
$old = "$Binary.old"
Remove-Item $old -Force -ErrorAction SilentlyContinue
if (Test-Path $Binary) { Move-Item $Binary $old -Force }
Move-Item $src $Binary -Force
Remove-Item $old -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path (Split-Path $VersionFile) | Out-Null
Set-Content -Path $VersionFile -Value $tag
Info "pi-web $tag installed to $Binary"
}
function Install-Ctl {
if (-not $PSScriptRoot) { return }
$src = Join-Path $PSScriptRoot '.pi\skills\common\pi_web.py'
if (-not (Test-Path $src)) { return }
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
$py = Join-Path $InstallDir 'pi-web-ctl.py'
Copy-Item $src $py -Force
$cmdPath = Join-Path $InstallDir 'pi-web-ctl.cmd'
@"
@echo off
setlocal
where python >nul 2>nul && (
python "%~dp0pi-web-ctl.py" %*
exit /b %ERRORLEVEL%
)
where py >nul 2>nul && (
py -3 "%~dp0pi-web-ctl.py" %*
exit /b %ERRORLEVEL%
)
echo pi-web-ctl requires Python 3 >&2
exit /b 1
"@ | Set-Content -Path $cmdPath -Encoding ASCII
Info "pi-web-ctl installed to $cmdPath"
}
function Set-EnvFileVar($file, $key, $value) {
$lines = @()
if (Test-Path $file) { $lines = @(Get-Content $file) }
$found = $false
$lines = @($lines | ForEach-Object {
if ($_ -match ('^' + [regex]::Escape($key) + '=')) { $found = $true; "$key=$value" } else { $_ }
})
if (-not $found) { $lines += "$key=$value" }
Set-Content -Path $file -Value $lines
}
function Initialize-EnvFile {
New-Item -ItemType Directory -Force -Path $ConfigDir | Out-Null
if (-not (Test-Path $EnvFile)) { New-Item -ItemType File -Path $EnvFile | Out-Null }
$hasToken = Select-String -Path $EnvFile -Pattern '^PI_WEB_TOKEN=' -Quiet
if (-not $env:PI_WEB_TOKEN -and -not $hasToken) {
$bytes = New-Object byte[] 16
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
$token = -join ($bytes | ForEach-Object { $_.ToString('x2') })
Set-EnvFileVar $EnvFile 'PI_WEB_TOKEN' $token
Info "Generated PI_WEB_TOKEN in $EnvFile"
Warn "Use this token when opening pi-web from another device: $token"
}
# Persist PI_CODING_AGENT_DIR so auto-started pi-web finds the right sessions.
if ($env:PI_CODING_AGENT_DIR) { Set-EnvFileVar $EnvFile 'PI_CODING_AGENT_DIR' $env:PI_CODING_AGENT_DIR }
# The Run-key launcher starts with the login default environment. Preserve
# the install-time PATH so pi-web can find `pi` for browser chat.
Set-EnvFileVar $EnvFile 'PATH' $env:Path
}
function Initialize-Autostart {
New-Item -ItemType Directory -Force -Path $ConfigDir | Out-Null
$ps1 = @"
# Generated by pi-web install.ps1 — starts pi-web hidden with the environment
# from the env file (PI_WEB_TOKEN, PATH, ...). Regenerated on every install.
`$envFile = '$EnvFile'
if (Test-Path `$envFile) {
foreach (`$line in Get-Content `$envFile) {
if (`$line -match '^\s*#' -or `$line -notmatch '=') { continue }
`$name, `$value = `$line -split '=', 2
Set-Item -Path ('Env:' + `$name) -Value `$value
}
}
Start-Process -FilePath '$Binary' -WindowStyle Hidden
"@
Set-Content -Path $LauncherPs1 -Value $ps1
# wscript runs the PowerShell launcher with window style 0 so login does not
# flash a console window.
$vbs = 'CreateObject("WScript.Shell").Run "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File ""' + $LauncherPs1 + '""", 0, False'
Set-Content -Path $LauncherVbs -Value $vbs
Set-ItemProperty -Path $RunKey -Name 'pi-web' -Value ('wscript.exe "' + $LauncherVbs + '"')
Info 'Windows auto-start configured (Run key + hidden launcher)'
}
function Start-PiWeb {
Start-Process -FilePath 'wscript.exe' -ArgumentList ('"' + $LauncherVbs + '"')
}
function Main {
Write-Host ''
Info 'pi-web installer (Windows)'
Write-Host ''
$arch = Get-Arch
$tag = Get-PackageTag
if ($tag) { Info "Using pi-web package version $tag." } else { $tag = Get-LatestTag }
$installed = Get-InstalledVersion
if ((Test-Path $Binary) -and $installed -eq $tag) {
Install-Ctl
Info "Already up-to-date ($tag)."
Write-Host ''
return
}
if ($installed) { Info "Update available: $installed -> $tag" }
$tmpBinary = Get-Binary $arch $tag
$inplace = [bool]$env:PI_WEB_INPLACE_UPDATE
if ((Test-Path $Binary) -and -not $inplace) { Stop-PiWeb }
Install-Binary $tmpBinary $tag
Install-Ctl
# In-place self-update: pi-web triggered this and restarts itself afterward.
# Skip env/auto-start setup so we don't kill the npm process running this
# script or clobber the launcher's PATH.
if ($inplace) {
Info "Binary updated to $tag; pi-web will restart to apply it."
Write-Host ''
return
}
Initialize-EnvFile
Initialize-Autostart
Info 'pi-web will listen on localhost; if Tailscale is running, it will publish HTTPS with Tailscale Serve.'
Start-PiWeb
Info "Done! pi-web $tag is ready."
Write-Host ''
}
Main