Skip to content

Commit 0927a8f

Browse files
add checksums and installation security
1 parent 394d274 commit 0927a8f

File tree

6 files changed

+852
-55
lines changed

6 files changed

+852
-55
lines changed

install_pieces_cli.ps1

Lines changed: 228 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ function Setup-PowerShellPath {
169169

170170
# Windows-specific PATH setup
171171
$currentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
172-
172+
173173
# Check PATH length limit
174174
$maxPathLength = 2048
175175
if ($currentPath -and ($currentPath.Length + $InstallDir.Length + 1) -ge $maxPathLength) {
@@ -226,6 +226,196 @@ function Setup-PowerShellPath {
226226
return $true
227227
}
228228

229+
# Verify SHA256 checksum of a file
230+
function Test-FileChecksum {
231+
param(
232+
[string]$FilePath,
233+
[string]$ExpectedChecksum
234+
)
235+
236+
if (!(Test-Path $FilePath)) {
237+
Write-Error "File not found: $FilePath"
238+
return $false
239+
}
240+
241+
try {
242+
$actualChecksum = (Get-FileHash -Path $FilePath -Algorithm SHA256).Hash.ToLower()
243+
$expectedLower = $ExpectedChecksum.ToLower()
244+
245+
if ($actualChecksum -eq $expectedLower) {
246+
return $true
247+
} else {
248+
Write-Error "Checksum verification failed for $FilePath"
249+
Write-Error "Expected: $expectedLower"
250+
Write-Error "Actual: $actualChecksum"
251+
return $false
252+
}
253+
}
254+
catch {
255+
Write-Error "Failed to calculate checksum for $FilePath : $_"
256+
return $false
257+
}
258+
}
259+
260+
# Download a file with Invoke-WebRequest and verify its checksum
261+
function Invoke-SecureDownload {
262+
param(
263+
[string]$Url,
264+
[string]$OutputPath,
265+
[string]$ExpectedChecksum
266+
)
267+
268+
Write-Info "Downloading $(Split-Path $OutputPath -Leaf)..."
269+
270+
try {
271+
# Download with Invoke-WebRequest
272+
Invoke-WebRequest -Uri $Url -OutFile $OutputPath -UseBasicParsing
273+
274+
# Verify checksum
275+
if (Test-FileChecksum -FilePath $OutputPath -ExpectedChecksum $ExpectedChecksum) {
276+
Write-Success "Downloaded and verified $(Split-Path $OutputPath -Leaf)"
277+
return $true
278+
} else {
279+
Remove-Item -Path $OutputPath -Force -ErrorAction SilentlyContinue
280+
return $false
281+
}
282+
}
283+
catch {
284+
Write-Error "Failed to download $Url : $_"
285+
Remove-Item -Path $OutputPath -Force -ErrorAction SilentlyContinue
286+
return $false
287+
}
288+
}
289+
290+
# Get dependency information
291+
function Get-Dependencies {
292+
return @"
293+
https://storage.googleapis.com/app-releases-production/pieces_cli/release/pieces_cli-1.16.5.tar.gz 3ff92f965dbfe0ffeed9f5460b148e84708e60b39626cd03f3c36a817e78f2ab
294+
https://files.pythonhosted.org/packages/e3/52/6ad8f63ec8da1bf40f96996d25d5b650fdd38f5975f8c813732c47388f18/aenum-3.1.16-py3-none-any.whl 9035092855a98e41b66e3d0998bd7b96280e85ceb3a04cc035636138a1943eaf
295+
https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89
296+
https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz 3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6
297+
https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz 75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b
298+
https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz 27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202
299+
https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz 4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1
300+
https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz 6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8
301+
https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz 75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc
302+
https://files.pythonhosted.org/packages/6e/fa/66bd985dd0b7c109a3bcb89272ee0bfb7e2b4d06309ad7b38ff866734b2a/httpx_sse-0.4.1.tar.gz 8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e
303+
https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz 12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9
304+
https://files.pythonhosted.org/packages/d5/00/a297a868e9d0784450faa7365c2172a7d6110c763e30ba861867c32ae6a9/jsonschema-4.25.0.tar.gz e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f
305+
https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz 630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608
306+
https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3
307+
https://files.pythonhosted.org/packages/3a/f5/9506eb5578d5bbe9819ee8ba3198d0ad0e2fbe3bab8b257e4131ceb7dfb6/mcp-1.11.0.tar.gz 49a213df56bb9472ff83b3132a4825f5c8f5b120a90246f08b0dac6bedac44c8
308+
https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
309+
https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz 3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc
310+
https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz 931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed
311+
https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db
312+
https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz 7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc
313+
https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz 06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee
314+
https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz 636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887
315+
https://files.pythonhosted.org/packages/30/23/2f0a3efc4d6a32f3b63cdff36cd398d9701d26cda58e3ab97ac79fb5e60d/pyperclip-1.9.0.tar.gz b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310
316+
https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz 37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3
317+
https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab
318+
https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz 8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13
319+
https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e
320+
https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa
321+
https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz 439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098
322+
https://files.pythonhosted.org/packages/1e/d9/991a0dee12d9fc53ed027e26a26a64b151d77252ac477e22666b9688bc16/rpds_py-0.27.0.tar.gz 8b23cf252f180cda89220b378d917180f29d313cd6a07b2431c0d3b776aae86f
323+
https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
324+
https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
325+
https://files.pythonhosted.org/packages/42/6f/22ed6e33f8a9e76ca0a412405f31abb844b779d52c5f96660766edcd737c/sse_starlette-3.0.2.tar.gz ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a
326+
https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz 6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8
327+
https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz 38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36
328+
https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz 6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28
329+
https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz 3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760
330+
https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01
331+
https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz 72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5
332+
https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz 3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da
333+
"@
334+
}
335+
336+
# Download and verify all dependencies
337+
function Invoke-DownloadDependencies {
338+
param([string]$DownloadDir)
339+
340+
Write-Info "Creating download directory: $DownloadDir"
341+
New-Item -Path $DownloadDir -ItemType Directory -Force | Out-Null
342+
343+
# Parse and download each dependency
344+
$dependencies = Get-Dependencies
345+
$lines = $dependencies -split "`n" | Where-Object { $_.Trim() -ne "" }
346+
347+
foreach ($line in $lines) {
348+
$parts = $line.Trim() -split " "
349+
if ($parts.Length -ge 2) {
350+
$url = $parts[0]
351+
$checksum = $parts[1]
352+
353+
$filename = Split-Path $url -Leaf
354+
$outputPath = Join-Path $DownloadDir $filename
355+
356+
# Skip if already downloaded and verified
357+
if ((Test-Path $outputPath) -and (Test-FileChecksum -FilePath $outputPath -ExpectedChecksum $checksum)) {
358+
Write-Info "Already have verified $filename"
359+
continue
360+
}
361+
362+
# Download and verify
363+
if (!(Invoke-SecureDownload -Url $url -OutputPath $outputPath -ExpectedChecksum $checksum)) {
364+
Write-Error "Failed to download and verify $filename"
365+
return $false
366+
}
367+
}
368+
}
369+
370+
Write-Success "All dependencies downloaded and verified!"
371+
return $true
372+
}
373+
374+
# Install packages offline using pip with no-deps and local files
375+
function Install-PackagesOffline {
376+
param(
377+
[string]$DownloadDir,
378+
[string]$PipPath
379+
)
380+
381+
Write-Info "Installing packages from verified downloads..."
382+
383+
# Parse and install each dependency
384+
$dependencies = Get-Dependencies
385+
$lines = $dependencies -split "`n" | Where-Object { $_.Trim() -ne "" }
386+
387+
foreach ($line in $lines) {
388+
$parts = $line.Trim() -split " "
389+
if ($parts.Length -ge 2) {
390+
$url = $parts[0]
391+
$filename = Split-Path $url -Leaf
392+
$packagePath = Join-Path $DownloadDir $filename
393+
394+
if (!(Test-Path $packagePath)) {
395+
Write-Error "Package file not found: $packagePath"
396+
return $false
397+
}
398+
399+
Write-Info "Installing $filename..."
400+
401+
# Install with no dependencies flag to prevent pip from accessing PyPI
402+
try {
403+
& $PipPath install $packagePath --no-deps --force-reinstall --quiet
404+
if ($LASTEXITCODE -ne 0) {
405+
throw "pip install failed with exit code $LASTEXITCODE"
406+
}
407+
}
408+
catch {
409+
Write-Error "Failed to install $filename : $_"
410+
return $false
411+
}
412+
}
413+
}
414+
415+
Write-Success "All packages installed successfully!"
416+
return $true
417+
}
418+
229419
# Check if running as admin/root
230420
function Test-Administrator {
231421
if (Test-Windows) {
@@ -246,7 +436,22 @@ function Test-Administrator {
246436
function Install-PiecesCLI {
247437
Write-Info "Starting Pieces CLI installation..."
248438

249-
# Step 1: Check if running as Administrator/root
439+
# Step 1: Check system requirements
440+
Write-Info "Checking system requirements..."
441+
442+
# PowerShell should have Invoke-WebRequest and Get-FileHash built-in
443+
# These are required for secure downloads and checksum verification
444+
try {
445+
Get-Command Invoke-WebRequest -ErrorAction Stop | Out-Null
446+
Get-Command Get-FileHash -ErrorAction Stop | Out-Null
447+
}
448+
catch {
449+
Write-Error "Required PowerShell cmdlets not available (Invoke-WebRequest, Get-FileHash)"
450+
Write-Error "Please ensure you're running PowerShell 3.0 or later"
451+
return
452+
}
453+
454+
# Step 2: Check if running as Administrator/root
250455
if (Test-Administrator) {
251456
Write-Warning "You appear to be running this script as Administrator/root."
252457
Write-Warning "This may cause the installation to be inaccessible to non-admin users."
@@ -257,7 +462,7 @@ function Install-PiecesCLI {
257462
}
258463
}
259464

260-
# Step 2: Find Python executable
465+
# Step 3: Find Python executable
261466
Write-Info "Locating Python executable..."
262467
$pythonCmd = Find-Python
263468

@@ -274,7 +479,7 @@ function Install-PiecesCLI {
274479
$pythonVersion = & $pythonCmd.Split(' ') --version 2>&1
275480
Write-Success "Found Python: $pythonCmd ($pythonVersion)"
276481

277-
# Step 3: Set installation directory
482+
# Step 4: Set installation directory
278483
$homeDir = Get-HomeDirectory
279484
$installDir = Join-Path $homeDir ".pieces-cli"
280485
$venvDir = Join-Path $installDir "venv"
@@ -286,7 +491,7 @@ function Install-PiecesCLI {
286491
New-Item -Path $installDir -ItemType Directory | Out-Null
287492
}
288493

289-
# Step 4: Create virtual environment
494+
# Step 5: Create virtual environment
290495
Write-Info "Creating virtual environment..."
291496
if (Test-Path $venvDir) {
292497
Write-Warning "Virtual environment already exists. Removing old environment..."
@@ -313,9 +518,6 @@ function Install-PiecesCLI {
313518

314519
Write-Success "Virtual environment created successfully."
315520

316-
# Step 5: Install pieces-cli
317-
Write-Info "Installing Pieces CLI..."
318-
319521
# Use venv's pip - different paths for Windows vs Unix
320522
if (Test-Windows) {
321523
$venvPip = Join-Path $venvDir "Scripts\pip.exe"
@@ -330,32 +532,29 @@ function Install-PiecesCLI {
330532
return
331533
}
332534

333-
# Upgrade pip first
334-
Write-Info "Upgrading pip..."
335-
try {
336-
& $venvPip install --upgrade pip --quiet
337-
if ($LASTEXITCODE -ne 0) { throw "Pip upgrade failed" }
338-
}
339-
catch {
340-
Write-Warning "Failed to upgrade pip, continuing with existing version..."
341-
}
535+
# Step 6a: Download all dependencies securely
536+
$downloadDir = Join-Path $installDir "downloads"
537+
Write-Info "Downloading and verifying all dependencies"
342538

343-
# Install pieces-cli
344-
Write-Info "Installing pieces-cli package..."
345-
try {
346-
& $venvPip install pieces-cli --quiet
347-
if ($LASTEXITCODE -ne 0) { throw "pieces-cli installation failed" }
348-
}
349-
catch {
350-
Write-Error "Failed to install pieces-cli: $_"
539+
if (!(Invoke-DownloadDependencies -DownloadDir $downloadDir)) {
540+
Write-Error "Failed to download dependencies."
351541
Write-Error "Please check your internet connection and try again."
352-
Write-Error "If the problem persists, check if pypi.org is accessible."
542+
return
543+
}
544+
545+
if (!(Install-PackagesOffline -DownloadDir $downloadDir -PipPath $venvPip)) {
546+
Write-Error "Failed to install packages offline."
547+
Write-Error "Installation may be corrupted, please try again."
353548
return
354549
}
355550

356551
Write-Success "Pieces CLI installed successfully!"
357552

358-
# Step 6: Create wrapper script
553+
# Clean up downloads after successful installation
554+
Write-Info "Cleaning up download cache..."
555+
Remove-Item -Path $downloadDir -Recurse -Force -ErrorAction SilentlyContinue
556+
557+
# Step 7: Create wrapper script
359558
Write-Info "Creating wrapper script..."
360559

361560
if (Test-Windows) {
@@ -442,7 +641,7 @@ exec "`$PIECES_EXECUTABLE" "`$@"
442641

443642
Write-Success "Wrapper script created at: $wrapperScript"
444643

445-
# Step 7: Configure PowerShell
644+
# Step 8: Configure PowerShell
446645
Write-Info "Configuring PowerShell integration..."
447646

448647
if (Test-Command "pwsh") {
@@ -478,7 +677,7 @@ exec "`$PIECES_EXECUTABLE" "`$@"
478677
Write-Host ""
479678
}
480679

481-
# Step 8: Final instructions
680+
# Step 9: Final instructions
482681
Write-Host ""
483682
Write-Success "Installation completed successfully!"
484683
Write-Host ""

0 commit comments

Comments
 (0)