# Instalador de Liathos para Windows. # # irm liathos.com/install.ps1 | iex # # Qué hace, en este orden: # 1. Te pide tu código de acceso y baja Liathos (sin código válido no baja nada). # 2. Baja Node 22, el programa en que corre Liathos, a su propia carpeta. # 3. Instala lo que Liathos necesita para funcionar en esta PC. # 4. Crea los accesos directos y lo abre. # # Todo queda en %LOCALAPPDATA%\Liathos, sin permisos de administrador y sin tocar # nada más de la PC. Tus datos (conversaciones, llaves) quedan aparte, en # %USERPROFILE%\.liathos, y desinstalar no los borra. # # Se puede volver a correr: actualiza Liathos y conserva todo lo tuyo. function Install-Liathos { $ErrorActionPreference = 'Stop' # Sin la barra de progreso, Windows PowerShell 5.1 baja archivos 10 veces más lento. $ProgressPreference = 'SilentlyContinue' $site = 'https://liathos.com' # Para probar contra otra dirección antes de que el dominio esté listo: # $env:LIATHOS_SITE = 'https://liathos.netlify.app' # Sólo https: el código de acceso viaja en cada pedido. if ($env:LIATHOS_SITE) { if ($env:LIATHOS_SITE -notmatch '^https://[A-Za-z0-9.-]+$') { throw 'LIATHOS_SITE tiene que ser una dirección https, sin barra al final.' } $site = $env:LIATHOS_SITE } $root = Join-Path $env:LOCALAPPDATA 'Liathos' $logs = Join-Path $root 'logs' $work = Join-Path $root 'tmp' function Say([string]$text) { Write-Host $text } function Step([string]$text) { Write-Host ''; Write-Host $text -ForegroundColor Cyan } function Stop-Here([string]$text) { Write-Host ''; Write-Host $text -ForegroundColor Red; throw 'liathos-install-stopped' } # Lo que el sitio contestó ante un error, que dice en español qué pasó. Windows # PowerShell 5.1 lo deja en la respuesta; PowerShell 7, en los detalles del error. function Get-ErrorText($err) { try { if ($err.ErrorDetails -and $err.ErrorDetails.Message) { $body = $err.ErrorDetails.Message | ConvertFrom-Json if ($body.error) { return [string]$body.error } } } catch { } try { $stream = $err.Exception.Response.GetResponseStream() $body = (New-Object System.IO.StreamReader($stream)).ReadToEnd() | ConvertFrom-Json if ($body.error) { return [string]$body.error } } catch { } return $null } function Test-Hash([string]$file, [string]$expected, [string]$what) { $actual = (Get-FileHash -Algorithm SHA256 -Path $file).Hash if ($actual -ne $expected.ToUpperInvariant()) { Stop-Here "Lo que se bajó ($what) llegó incompleto o alterado, así que no lo instalo. Probá de nuevo; si se repite, puede ser la conexión o un programa de seguridad." } } function Expand-Zip([string]$zip, [string]$to) { Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory($zip, $to) } # Borrar miles de archivos con Remove-Item tarda minutos; cmd lo hace en segundos. function Remove-Tree([string]$path) { if (Test-Path $path) { & cmd.exe /c "rd /s /q `"$path`"" | Out-Null } } function New-Shortcut([string]$path, [string]$arguments, [string]$description, [string]$icon) { $shell = New-Object -ComObject WScript.Shell $link = $shell.CreateShortcut($path) $link.TargetPath = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe' $link.Arguments = $arguments $link.WorkingDirectory = $root $link.Description = $description $link.WindowStyle = 7 if (Test-Path $icon) { $link.IconLocation = "$icon,0" } $link.Save() } # ── 0. Que esta PC pueda ────────────────────────────────────────────── Say '' Say 'Liathos · instalación para Windows' if ($env:OS -ne 'Windows_NT') { Stop-Here 'Este instalador es para Windows.' } if (-not [Environment]::Is64BitOperatingSystem) { Stop-Here 'Liathos necesita una versión de Windows de 64 bits.' } if ($PSVersionTable.PSVersion.Major -lt 5) { Stop-Here 'Liathos necesita Windows 10 o más nuevo.' } # Windows PowerShell 5.1 no habla TLS 1.2 por defecto, y los dos sitios de abajo lo exigen. [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 $drive = New-Object System.IO.DriveInfo ([System.IO.Path]::GetPathRoot($root)) if ($drive.AvailableFreeSpace -lt 3GB) { Stop-Here ("Falta lugar en el disco {0}: Liathos necesita unos 3 GB libres y hay {1:N1} GB." -f $drive.Name, ($drive.AvailableFreeSpace / 1GB)) } New-Item -ItemType Directory -Force -Path $root, $logs | Out-Null Remove-Tree $work New-Item -ItemType Directory -Force -Path $work | Out-Null # ── 1. El código y Liathos ──────────────────────────────────────────── Step '1/4 · Tu código de acceso' $code = $env:LIATHOS_CODE if (-not $code) { $secure = Read-Host 'Código de acceso (se escribe sin que se vea)' -AsSecureString $pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure) try { $code = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($pointer) } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($pointer) } } $code = ([string]$code).Trim() if (-not $code) { Stop-Here 'Hace falta el código de acceso. Pedíselo a quien te dio Liathos.' } Say 'Bajando Liathos…' $release = Join-Path $work 'liathos.zip' try { $response = Invoke-WebRequest -Uri "$site/api/download" -Method Post -UseBasicParsing -TimeoutSec 600 ` -ContentType 'application/json' -Body (@{ code = $code } | ConvertTo-Json) -OutFile $release -PassThru } catch { $said = Get-ErrorText $_ if ($said) { Stop-Here $said } Stop-Here "No pude conectar con liathos.com. Revisá que tengas internet y probá de nuevo. ($($_.Exception.Message))" } $code = $null $sum = $response.Headers['X-Liathos-Sha256'] if ($sum) { Test-Hash $release ([string]$sum) 'Liathos' } if ((Get-Item $release).Length -lt 1MB) { Stop-Here 'Lo que bajó Liathos es demasiado chico para ser el programa. Probá de nuevo en un rato.' } # ── 2. Node 22 ──────────────────────────────────────────────────────── Step '2/4 · El programa en que corre Liathos' $nodeDir = Join-Path $root 'node' $nodeExe = Join-Path $nodeDir 'node.exe' $haveNode = $false if (Test-Path $nodeExe) { try { $haveNode = ((& $nodeExe --version) -like 'v22.*') } catch { $haveNode = $false } } if ($haveNode) { Say 'Ya está.' } else { Say 'Bajando Node 22 (nodejs.org)…' $base = 'https://nodejs.org/dist/latest-v22.x' try { $sums = (Invoke-WebRequest -Uri "$base/SHASUMS256.txt" -UseBasicParsing -TimeoutSec 60).Content } catch { Stop-Here "No pude conectar con nodejs.org. Revisá tu conexión y probá de nuevo. ($($_.Exception.Message))" } $match = [regex]::Match([string]$sums, '(?m)^([0-9a-f]{64})\s+(node-v22\.\d+\.\d+-win-x64\.zip)\s*$') if (-not $match.Success) { Stop-Here 'No encontré la versión de Node que necesito en nodejs.org. Probá de nuevo más tarde.' } $nodeZip = Join-Path $work $match.Groups[2].Value Invoke-WebRequest -Uri "$base/$($match.Groups[2].Value)" -UseBasicParsing -TimeoutSec 600 -OutFile $nodeZip Test-Hash $nodeZip $match.Groups[1].Value 'Node' $nodeTmp = Join-Path $work 'node-extract' Expand-Zip $nodeZip $nodeTmp Remove-Tree $nodeDir Move-Item -Path (Get-ChildItem -Path $nodeTmp -Directory | Select-Object -First 1).FullName -Destination $nodeDir } # ── 3. Liathos y sus piezas ─────────────────────────────────────────── Step '3/4 · Instalando Liathos' $launcher = Join-Path $root 'app\windows\liathos.ps1' if (Test-Path $launcher) { Say 'Cerrando la versión que estaba abierta…' & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $launcher stop | Out-Null } $new = Join-Path $root 'app.new' $old = Join-Path $root 'app.old' $app = Join-Path $root 'app' Remove-Tree $new Remove-Tree $old Expand-Zip $release $new # Si las piezas no cambiaron respecto de la versión instalada, se reusan: actualizar tarda segundos. $reused = $false $oldLock = Join-Path $app 'package-lock.json' $newLock = Join-Path $new 'package-lock.json' if ((Test-Path (Join-Path $app 'node_modules')) -and (Test-Path $oldLock) -and ` ((Get-FileHash $oldLock).Hash -eq (Get-FileHash $newLock).Hash)) { Move-Item -Path (Join-Path $app 'node_modules') -Destination (Join-Path $new 'node_modules') $reused = $true Say 'Las piezas no cambiaron: las reuso.' } if (-not $reused) { Say 'Instalando las piezas de Liathos. La primera vez tarda entre 3 y 10 minutos: no cierres esta ventana.' $env:PATH = "$nodeDir;$env:PATH" $npmCli = Join-Path $nodeDir 'node_modules\npm\bin\npm-cli.js' $npmLog = Join-Path $logs 'instalacion.log' $npm = Start-Process -FilePath $nodeExe -WorkingDirectory $new -WindowStyle Hidden -PassThru ` -ArgumentList @("`"$npmCli`"", 'ci', '--omit=dev', '--no-audit', '--no-fund', '--no-update-notifier') ` -RedirectStandardOutput $npmLog -RedirectStandardError "$npmLog.err" $started = Get-Date while (-not $npm.HasExited) { Start-Sleep -Seconds 15 if (-not $npm.HasExited) { Say (" … {0} min" -f [math]::Floor(((Get-Date) - $started).TotalMinutes)) } } $npm.WaitForExit() if ($npm.ExitCode -ne 0) { Remove-Tree $new Stop-Here "No se pudieron instalar las piezas de Liathos. Lo que dijo el instalador está en:`n$npmLog.err`nSi no se entiende, mandáselo a quien te dio Liathos." } } if (Test-Path $app) { Move-Item -Path $app -Destination $old } Move-Item -Path $new -Destination $app Remove-Tree $old # ── 4. Accesos directos y arranque ──────────────────────────────────── Step '4/4 · Accesos directos' $launcher = Join-Path $app 'windows\liathos.ps1' $icon = Join-Path $app 'windows\liathos.ico' $common = '-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "{0}" {1}' $menu = Join-Path ([Environment]::GetFolderPath('Programs')) 'Liathos' New-Item -ItemType Directory -Force -Path $menu | Out-Null $desktop = [Environment]::GetFolderPath('Desktop') New-Shortcut (Join-Path $desktop 'Liathos.lnk') ($common -f $launcher, 'start') 'Abrir Liathos' $icon New-Shortcut (Join-Path $menu 'Liathos.lnk') ($common -f $launcher, 'start') 'Abrir Liathos' $icon New-Shortcut (Join-Path $menu 'Cerrar Liathos.lnk') ($common -f $launcher, 'stop') 'Cerrar Liathos' $icon Say 'Listo: «Liathos» en el escritorio y en el menú Inicio.' $startup = Join-Path ([Environment]::GetFolderPath('Startup')) 'Liathos.lnk' $auto = Read-Host '¿Querés que Liathos arranque solo cuando prendés la PC? (s/N)' if ($auto -match '^(s|si|sí|y|yes)$') { New-Shortcut $startup ($common -f $launcher, 'start -NoBrowser') 'Liathos arranca con Windows' $icon Say 'Va a arrancar solo (sin abrir el navegador). Para sacarlo, borrá «Liathos» de la carpeta de inicio de Windows.' } Remove-Tree $work Say '' Say 'Abriendo Liathos… la primera vez tarda un poco más.' & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $launcher start Write-Host '' Write-Host 'Liathos quedó instalado.' -ForegroundColor Green Say 'Para abrirlo otro día: el acceso directo «Liathos». Si cerrás la ventana del navegador, sigue andando; para apagarlo, «Cerrar Liathos» en el menú Inicio.' Say "Tus conversaciones y llaves están en $([Environment]::GetFolderPath('UserProfile'))\.liathos." Say "Para desinstalar: borrá la carpeta $root y los accesos directos. Tus datos no se tocan." } try { Install-Liathos } catch { if ($_.Exception.Message -ne 'liathos-install-stopped') { Write-Host '' Write-Host "Algo salió mal: $($_.Exception.Message)" -ForegroundColor Red Write-Host 'Podés volver a correr el instalador. Si se repite, mandale este mensaje a quien te dio Liathos.' } }