Ctrl K
ComunidadGuías…

memos-server-updater.ps1

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Latest: Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; & ([ScriptBlock]::Create((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/memospot/memospot/main/memos-server-updater.ps1')))
# Specific tag: Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; & ([ScriptBlock]::Create((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/memospot/memospot/main/memos-server-updater.ps1'))) "v0.25.2"

function main {
  param([string]$Version="latest")

function Stop-MemospotProcesses {
  $processNames = @("memos", "memospot")
  foreach ($name in $processNames) {
    $running = @(Get-Process -Name $name -ErrorAction SilentlyContinue)
    if ($running.Count -gt 0) {
      Write-Host "Stopping running process: $name" -f Cyan
      foreach ($proc in $running) {
        try {
          Stop-Process -Id $proc.Id -Force -ErrorAction Stop
        }
        catch {
          Write-Host "Failed to terminate process $name (PID $($proc.Id)): $($_.Exception.Message)" -f Red
          Exit 1
        }
      }

      Start-Sleep -Seconds 1
      if (@(Get-Process -Name $name -ErrorAction SilentlyContinue).Count -gt 0) {
        Write-Host "Process still running after termination attempt: $name" -f Red
        Exit 1
      }
    }
  }
}


Write-Host @"
         __  __ _____ __  __  ___  ____  ____   ___ _____
        |  \/  | ____|  \/  |/ _ \/ ___||  _ \ / _ \_   _|
        | |\/| |  _| | |\/| | | | \___ \| |_) | | | || |
        | |  | | |___| |  | | |_| |___) |  __/| |_| || |
        |_|  |_|_____|_|  |_|\___/|____/|_|    \___/ |_|
                                                 _       _
 ___  ___ _ ____   _____ _ __    _   _ _ __   __| | __ _| |_ ___ _ __
/ __|/ _ \ '__\ \ / / _ \ '__|  | | | | '_ \ / _` |/ _` | __/ _ \ '__|
\__ \  __/ |   \ V /  __/ |     | |_| | |_) | (_| | (_| | ||  __/ |
|___/\___|_|    \_/ \___|_|      \__,_| .__/ \__,_|\__,_|\__\___|_|
                                      |_|
"@ -f DarkCyan

$GitHubRepo = "memospot/memos-builds"

$DataPath = [IO.Path]::Combine($Env:LocalAppData, "memospot")

$MemospotPath = {
  $searchPaths = @(
    [IO.Path]::Combine($Env:ProgramFiles, "Memospot"),
    $DataPath
  )
  foreach ($dir in $searchPaths) {
    $memosBin = [IO.Path]::Combine($dir, "memos.exe")
    if ([IO.File]::Exists($memosBin)) {
      return (Resolve-Path $dir).Path
    }
  }
}.Invoke()

if ([String]::IsNullOrEmpty($MemospotPath)) {
  Write-Host "Memospot not found. Execution halted." -f Red
  Exit 1
}
else {
  Write-Host "Memospot found at: ``$MemospotPath``" -f Green
}

if ($MemospotPath.StartsWith($Env:ProgramFiles)) {
  $LastDebugPreference = $DebugPreference
  $DebugPreference = 'SilentlyContinue'
  if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
    Write-Host @"

    Memospot was installed system-wide with the MSI installer.

    Administrator access is required to update files on the Program Files folder.

    Please launch Powershell or Windows Terminal as Administrator and run this script again.

"@ -f Yellow
    Write-Host "`n-> Press any key to exit <-`n" -f Cyan
    $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
    Exit 1
  }
  $DebugPreference = $LastDebugPreference
  Write-Host "This script is running with administrator access." -f Green
}

if ($PSVersionTable.PSVersion.Major -lt 6) {
  $global:IsWindows = ([Environment]::OSVersion.Platform -eq "Win32NT")
}
if (-not $IsWindows) {
  Write-Host "This script only supports Windows." -f Red
  Exit 1
}

$HostArch = {
  # Prefer OS architecture from .NET runtime since PowerShell process
  # architecture can differ from the native OS architecture on Windows ARM64.
  try {
    $osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLower()
    switch ($osArch) {
      "x64" { return "x86_64" }
      "arm64" { return "arm64" }
    }
  }
  catch {
    # fallback below
  }

  $archMap = @{
    "x86_64" = @("amd64", "x64", "i386_64", "x86_64")
    "arm64" = @("arm64", "aarch64")
  }

  $platform = {
    if ($IsWindows) {
      if (![String]::IsNullOrEmpty($Env:PROCESSOR_ARCHITEW6432)) {
        return $Env:PROCESSOR_ARCHITEW6432
      }
      return $Env:PROCESSOR_ARCHITECTURE
    }
    return $(uname -p)
  }.Invoke().ToLower()

  foreach ($k in $archMap.Keys) {
    foreach ($v in $archMap[$k]) {
      if ($platform.Equals($v)) {
        return $k
      }
    }
  }
  return $platform
}.Invoke()

if ([String]::IsNullOrEmpty($HostArch)) {
  Write-Host "Unsupported CPU architecture" -f Red
  Exit 1
}

$HostOS = {
  $osMap = @{
    "linux" = "mingw64_nt", "mingw32_nt"
  }

  $platform = {
    if ($IsWindows) {
      return "windows"
    }
    return $(uname -s)
  }.Invoke().ToLower()

  foreach ($k in $osMap.Keys) {
    foreach ($v in $osMap[$k]) {
      if ($platform.StartsWith($v)) {
        return $k
      }
    }
  }
  return $platform.ToLower()
}.Invoke()

if ([String]::IsNullOrEmpty($HostOS)) {
  Write-Host "Unsupported OS: $([Environment]::OSVersion.Platform)" -f Red
  Exit 1
}

function Get-WindowsX64BuildTier {
  $avx2Supported = $false
  $popcntSupported = $false

  try {
    if (-not ("CpuFeatureProbe" -as [type])) {
      Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;

public static class CpuFeatureProbe {
  [DllImport("kernel32.dll")]
  public static extern bool IsProcessorFeaturePresent(uint processorFeature);
}
"@ -ErrorAction SilentlyContinue | Out-Null
    }
    $PF_AVX2_INSTRUCTIONS_AVAILABLE = 40
    $avx2Supported = [CpuFeatureProbe]::IsProcessorFeaturePresent($PF_AVX2_INSTRUCTIONS_AVAILABLE)
  }
  catch {
    $avx2Supported = $false
  }

  try {
    $popcntType = [Type]::GetType("System.Runtime.Intrinsics.X86.Popcnt, System.Runtime.Intrinsics")
    if ($null -ne $popcntType) {
      $isSupported = $popcntType.GetProperty("IsSupported")
      if ($null -ne $isSupported) {
        $popcntSupported = [bool]$isSupported.GetValue($null, $null)
      }
    }
  }
  catch {
    $popcntSupported = $false
  }

  if ($avx2Supported) {
    return "x86_64_v3"
  }
  if ($popcntSupported) {
    return "x86_64_v2"
  }
  return "x86_64_v1"
}

$BackupsPath = [IO.Path]::Combine($DataPath, "server-updater-backups")
if (-not [IO.Directory]::Exists($BackupsPath)) {
  Write-Host "Creating backups directory: $BackupsPath" -f Cyan
  New-Item -Path $BackupsPath -ItemType Directory -Force -ErrorAction Stop
  if ($null -eq $?) {
    Write-Host "Failed to create directory. Make sure you have write permissions to: $DataPath" -f Red
    Exit 1
  }
}

##
$ProgressPreference = 'SilentlyContinue'
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072;
  $ReleaseUrl = if ($Version -eq "latest" -or [String]::IsNullOrEmpty($Version)) {
    "https://api.github.com/repos/$GitHubRepo/releases/latest"
  } else {
    "https://api.github.com/repos/$GitHubRepo/releases/tags/$Version"
  }

  $releaseData = Invoke-WebRequest -Uri $ReleaseUrl -UseBasicParsing -ErrorAction Stop
  if ($null -eq $?) {
    Write-Host "Failed to identify release for tag: $Version" -f Red
    Exit 1
  }

  $latest = ($releaseData | ConvertFrom-Json)
  if ($latest -is [array]) { $latest = $latest[0] }

  $tagName = $latest.tag_name
  $latestAssets = $latest.assets
$sha256Sums = $latestAssets | Where-Object { $_.name.ToLower().EndsWith("sha256sums.txt") } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url
$assetNameCandidates = @(
  "$($HostOS)-$($HostArch).zip",
  "$($HostOS)-$($HostArch)_v1.zip"
)
if ($HostArch -eq "arm64") {
  $assetNameCandidates += @(
    "$($HostOS)-aarch64.zip",
    "$($HostOS)-aarch64_v1.zip"
  )
}
if ($HostArch -eq "x86_64") {
  $x64BuildTier = Get-WindowsX64BuildTier
  Write-Host "Detected x86_64 CPU tier: $x64BuildTier" -f Cyan

  switch ($x64BuildTier) {
    "x86_64_v3" {
      $assetNameCandidates = @(
        "$($HostOS)-x86_64_v3.zip",
        "$($HostOS)-amd64_v3.zip",
        "$($HostOS)-x64_v3.zip",
        "$($HostOS)-x86_64_v2.zip",
        "$($HostOS)-amd64_v2.zip",
        "$($HostOS)-x64_v2.zip",
        "$($HostOS)-x86_64_v1.zip",
        "$($HostOS)-amd64_v1.zip",
        "$($HostOS)-x64_v1.zip"
      ) + $assetNameCandidates
    }
    "x86_64_v2" {
      $assetNameCandidates = @(
        "$($HostOS)-x86_64_v2.zip",
        "$($HostOS)-amd64_v2.zip",
        "$($HostOS)-x64_v2.zip",
        "$($HostOS)-x86_64_v1.zip",
        "$($HostOS)-amd64_v1.zip",
        "$($HostOS)-x64_v1.zip"
      ) + $assetNameCandidates
    }
    default {
      $assetNameCandidates = @(
        "$($HostOS)-x86_64_v1.zip",
        "$($HostOS)-amd64_v1.zip",
        "$($HostOS)-x64_v1.zip"
      ) + $assetNameCandidates
    }
  }

  $assetNameCandidates += @(
    "$($HostOS)-amd64.zip",
    "$($HostOS)-amd64_v1.zip",
    "$($HostOS)-x64.zip",
    "$($HostOS)-x64_v1.zip"
  )
}
$assetNameCandidates = $assetNameCandidates | ForEach-Object { $_.ToLower() } | Select-Object -Unique

$matchedAsset = $null
foreach ($suffix in $assetNameCandidates) {
  $candidate = $latestAssets | Where-Object { $_.name.ToLower().EndsWith($suffix) } | Select-Object -First 1
  if ($null -ne $candidate) {
    $matchedAsset = $candidate
    break
  }
}
$downloadUrl = $matchedAsset.browser_download_url
if ([String]::IsNullOrEmpty($downloadUrl)) {
  Write-Host "Unable to find a valid release URL!" -f Magenta
  Write-Host "Unable to match OS: $HostOS, CPU: $HostArch" -f Magenta
  Write-Host "Tried assets suffixes: $($assetNameCandidates -join ', ')" -f Magenta
  Write-Host "Please open an issue at: https://github.com/$GitHubRepo/issues" -f Magenta
  Write-Host "Remember to provide your OS version, CPU details and Powershell version." -f Magenta
  Exit 1
}

Write-Host "Latest Memos release found: $tagName" -f Green
Write-Host "`nRelease URL: $downloadUrl"
Write-Host "Release SHA256SUMS: $($sha256Sums)"

Write-Host "`nNote that this script will not check whether you actually need to update the Memos server.
Version $tagName will be downloaded and installed regardless." -f Yellow

Write-Host "`n-> Press any key to continue <-`n" -f Cyan
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

Stop-MemospotProcesses

$tempRoot = [IO.Path]::Combine([IO.Path]::GetTempPath(), "memospot-updater-$([Guid]::NewGuid().ToString('N'))")
New-Item -Path $tempRoot -ItemType Directory -Force -ErrorAction Stop | Out-Null
$ZippedRelease = [IO.Path]::Combine($tempRoot, $matchedAsset.name)
Remove-Item -Path $ZippedRelease -Force -ErrorAction SilentlyContinue

Write-Host "Downloading release ``$tagName``... This can take a while." -f Green

Invoke-WebRequest -OutFile $ZippedRelease -Uri $downloadUrl -UseBasicParsing -ErrorAction Stop
if ($null -eq $?) {
  Write-Host "Failed to download file at: $downloadUrl" -f Red
  Exit 1
}

if (![IO.File]::Exists($ZippedRelease)) {
  Write-Host "Failed to download file to: $ZippedRelease" -f Red
  Exit 1
}

if ($IsWindows) {
  Write-Host "Unblocking file: $ZippedRelease"
  Unblock-File -Path $ZippedRelease -ErrorAction SilentlyContinue
}

if (-not [IO.File]::Exists($ZippedRelease)) {
  Write-Host "Unable to find downloaded file: $ZippedRelease" -f Red
  Write-Host @"
  Make sure your antivirus is not blocking the download.

  If you are using Windows Defender and it shows a threat warning, rapidly click on it, expand the most recent threat and click on "Allow on device".

  You have little time to do this before Defender takes action on its own and makes your life harder.

  After allowing the file, try to run this script again.
"@ -f Yellow
  Exit 1
}

if ([String]::IsNullOrEmpty($sha256Sums)) {
  Write-Host "Unable to find SHA256SUMS!" -f Red
  Exit 1
}

$hashes = Invoke-WebRequest -Uri $sha256Sums -UseBasicParsing -ErrorAction Stop
if ($null -eq $?) {
  Write-Host "Failed to download SHA256SUMS file at: $sha256Sums" -f Red
  Exit 1
}

$hashes = [System.Text.Encoding]::UTF8.GetString($hashes.Content)

$hash = $null
foreach ($line in $hashes.Split("`n")) {
  if ([String]::IsNullOrEmpty($line)) {
    continue
  }
  $parts = $line.Split("  ")
  if ($parts[-1].ToLower().Equals($matchedAsset.name.ToLower())) {
    $hash = $parts[0]
    break
  }
}

if ([String]::IsNullOrEmpty($hash)) {
  Write-Host "Unable to find hash for file: $ZippedRelease" -f Red
  Exit 1
}
Write-Host "Expected hash: $hash"

# calculate sha256 hash for $ZippedRelease
$hasher = [Security.Cryptography.SHA256]::Create()
$downloadedZipHash = [System.BitConverter]::ToString($hasher.ComputeHash([IO.File]::ReadAllBytes($ZippedRelease)))
$downloadedZipHash = $downloadedZipHash.Replace("-", "").ToLower()

if ($hash.ToLower().Equals($downloadedZipHash)) {
  Write-Host "Hashes match!" -f Green
}
else {
  Write-Host "Hashes do not match!" -f Red
  Write-Host "Expected: $hash" -f Red
  Write-Host "Actual:   $downloadedZipHash" -f Red
  Write-Host "Try to run this script again." -f Yellow
  Exit 1
}

$dateString = $(Get-Date -Format "yyyyMMdd_HHmmss")

$databasePath = [IO.Path]::Combine($DataPath, "memos_prod.db")
$databasePathWAL = [IO.Path]::Combine($DataPath, "memos_prod.db-wal")
$databasePathSHM = [IO.Path]::Combine($DataPath, "memos_prod.db-shm")
if ([IO.File]::Exists($databasePath)) {
  $databaseBackup = [IO.Path]::Combine($BackupsPath, "memos_db_${dateString}_before_${tagName}.zip")
  Write-Host "Backing up current database"

  $fileList = [System.Collections.ArrayList]@($databasePath)
  if ([IO.File]::Exists($databasePathWAL)) {
    $fileList += $databasePathWAL
  }
  if ([IO.File]::Exists($databasePathSHM)) {
    $fileList += $databasePathSHM
  }

  Compress-Archive -Path $fileList -DestinationPath "$databaseBackup" -Force -ErrorAction Stop
  if (-not [IO.File]::Exists($databaseBackup)) {
    Write-Host "Failed to backup database files. Make sure Memos is stopped and that you have write permissions to: $databaseBackup" -f Red
    Exit 1
  }
}

$distPath = [IO.Path]::Combine($MemospotPath, "dist");
$memosBin = [IO.Path]::Combine($MemospotPath, "memos.exe")
$memosBinBackup = [IO.Path]::Combine($BackupsPath, "memos_binary_${dateString}_before_${tagName}.zip");
if ([IO.File]::Exists($memosBin)) {
  Write-Host "Backing up current memos.exe"
  if ([IO.Directory]::Exists($distPath)) {
    $fileList = [System.Collections.ArrayList]@($memosBin, $distPath)
  }
  else {
    $fileList = [System.Collections.ArrayList]@($memosBin)
  }

  Compress-Archive -Path $fileList -DestinationPath "$memosBinBackup" -Force -ErrorAction Stop
  if (-not [IO.File]::Exists($memosBinBackup)) {
    Write-Host "Failed to backup file. Make sure Memos is stopped and that you have write permissions to: $BackupsPath" -f Red
    Exit 1
  }
}

$stagingPath = [IO.Path]::Combine($tempRoot, "staging")
Remove-Item -Path $stagingPath -Recurse -Force -ErrorAction SilentlyContinue

Write-Host "Extracting Memos package to staging folder: $stagingPath"
Expand-Archive -Path $ZippedRelease -DestinationPath $stagingPath -Force -ErrorAction Stop

$stagedMemosBin = [IO.Path]::Combine($stagingPath, "memos.exe")
if (-not [IO.File]::Exists($stagedMemosBin)) {
  Write-Host "Extracted package does not contain memos.exe" -f Red
  Exit 1
}

$stagedDistPath = [IO.Path]::Combine($stagingPath, "dist")
$newMemosBin = [IO.Path]::Combine($MemospotPath, "memos.exe.new")
Copy-Item -Path $stagedMemosBin -Destination $newMemosBin -Force -ErrorAction Stop
Move-Item -Path $newMemosBin -Destination $memosBin -Force -ErrorAction Stop

if ([IO.Directory]::Exists($stagedDistPath)) {
  $distTempPath = [IO.Path]::Combine($MemospotPath, "dist.new")
  Remove-Item -Path $distTempPath -Recurse -Force -ErrorAction SilentlyContinue
  Copy-Item -Path $stagedDistPath -Destination $distTempPath -Recurse -Force -ErrorAction Stop
  Remove-Item -Path $distPath -Recurse -Force -ErrorAction SilentlyContinue
  Move-Item -Path $distTempPath -Destination $distPath -Force -ErrorAction Stop
}

Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue

# If MSI installer was used, copy dist folder to LocalAppData
# This should increase compatibility with multiple versions of Memospot
if ($MemospotPath.StartsWith($Env:ProgramFiles)) {
  $distPathLocal = [IO.Path]::Combine($DataPath, "dist");
  if ([IO.Directory]::Exists($distPathLocal)) {
    Write-Host "Removing old dist folder: $distPathLocal" -f Cyan
    Remove-Item -Path $distPathLocal -Recurse -Force -ErrorAction SilentlyContinue
  }
  if ([IO.Directory]::Exists($distPath)) {
    Write-Host "Copying dist folder to: $distPathLocal" -f Cyan
    Copy-Item -Path $distPath -Destination $distPathLocal -Recurse -Force -ErrorAction Stop
  }
}

$readmeFile = [IO.Path]::Combine($MemospotPath, "README.md")
Remove-Item -Path $readmeFile -Force -ErrorAction SilentlyContinue

$licenseFile = [IO.Path]::Combine($MemospotPath, "LICENSE")
Remove-Item -Path $licenseFile -Force -ErrorAction SilentlyContinue

Write-Host "Unblocking file: $memosBin" -f Cyan
Unblock-File -Path $memosBin -ErrorAction SilentlyContinue

Write-Host "`nMemos server successfully updated to $tagName" -f Green

  Write-Host "`n-> Press any key to exit <-`n" -f Cyan
  $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}

if ($args.Count -gt 0) {
  main -Version $args[0]
}
else {
  main
}