#requires -Version 5.1 <# .SYNOPSIS Stellt bestehende Scoop-Installationen von elexis/streichtech auf medelexis um. .DESCRIPTION Ohne -Apply nur ein Probelauf. Keine Sicherungen, Neuinstallationen oder Git-Pushes. Bearbeitet alle Versionsverzeichnisse der benutzerspezifischen Scoop-Installation. Ein passender bestehender Bucket wird umbenannt. Danach werden elexis und streichtech entfernt. Andere Buckets wie main und extras bleiben bestehen. Alte Paketnamen mit Unterstrich werden auf passende Manifestnamen mit Bindestrich umgestellt. .EXAMPLE .\scripts\update-bucket.ps1 .EXAMPLE .\scripts\update-bucket.ps1 -Apply #> [CmdletBinding()] param( [switch] $Apply, [string] $ScoopRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' }) ) $ErrorActionPreference = 'Stop' $repository = 'https://gitea.medelexis.ch/public/scoop' $targetName = 'medelexis' $target = Join-Path $ScoopRoot "buckets\$targetName" $apps = Join-Path $ScoopRoot 'apps' if (-not (Test-Path -LiteralPath $apps -PathType Container)) { throw "Kein Scoop-Apps-Verzeichnis gefunden: $apps" } function Test-MedelexisRepository([string] $Path) { if (-not (Test-Path -LiteralPath $Path -PathType Container)) { return $false } $remote = & git -C $Path remote get-url origin 2>$null if ($LASTEXITCODE -ne 0) { return $false } try { $uri = [Uri] ([string] $remote).Trim() } catch { return $false } return ($uri.Scheme -eq 'https' -and $uri.Host -eq 'gitea.medelexis.ch' -and $uri.AbsolutePath.TrimEnd('/').Replace('.git', '') -eq '/public/scoop') } function Get-PackageRenames([string] $BucketPath) { if (-not $BucketPath -or -not (Test-Path -LiteralPath $BucketPath)) { return } foreach ($app in Get-ChildItem -LiteralPath $apps -Directory) { if (-not $app.Name.Contains('_')) { continue } $newName = $app.Name.Replace('_', '-') $newManifest = Join-Path $BucketPath "bucket\$newName.json" if (-not (Test-Path -LiteralPath $newManifest)) { $newManifest = Join-Path $BucketPath "$newName.json" } if (-not (Test-Path -LiteralPath $newManifest)) { continue } $current = Join-Path $app.FullName 'current' $installFile = Join-Path $current 'install.json' if (-not (Test-Path -LiteralPath $installFile)) { continue } $install = [IO.File]::ReadAllText($installFile) | ConvertFrom-Json if ($install.bucket -notin @('elexis', 'streichtech', 'medelexis')) { continue } $destination = Join-Path $apps $newName if (Test-Path -LiteralPath $destination) { throw "'$($app.Name)' und '$newName' sind beide installiert. Keine automatische Zusammenführung." } $link = Get-Item -LiteralPath $current -Force if ($link.LinkType -ne 'Junction') { throw "Für '$($app.Name)' wird eine Scoop-current-Junction erwartet. Keine automatische Umbenennung." } $versionPath = [IO.Path]::GetFullPath([string] @($link.Target)[0]) if ((Split-Path $versionPath -Parent) -ne $app.FullName) { throw "Die current-Verknüpfung von '$($app.Name)' zeigt ausserhalb des Paketordners." } $persistSource = Join-Path $ScoopRoot "persist\$($app.Name)" $persistTarget = Join-Path $ScoopRoot "persist\$newName" if ((Test-Path -LiteralPath $persistSource) -and (Test-Path -LiteralPath $persistTarget)) { throw "Beide Persistenzordner für '$($app.Name)' und '$newName' existieren. Keine automatische Zusammenführung." } Write-Host "Paket umbenennen: $($app.Name) -> $newName" [pscustomobject]@{ OldName = $app.Name; NewName = $newName; Source = $app.FullName Destination = $destination; Version = (Split-Path $versionPath -Leaf) PersistSource = $persistSource; PersistTarget = $persistTarget } } } $renameSource = $null if (Test-Path -LiteralPath $target) { if (-not (Test-MedelexisRepository $target)) { throw 'Der vorhandene Bucket medelexis zeigt auf ein anderes Repository. Keine Änderung.' } Write-Host 'Bucket medelexis ist bereits korrekt eingerichtet.' } else { foreach ($oldName in @('elexis', 'streichtech')) { $candidate = Join-Path $ScoopRoot "buckets\$oldName" if (Test-MedelexisRepository $candidate) { $renameSource = $candidate Write-Host "Bucket umbenennen: $oldName -> $targetName" break } } if (-not $renameSource) { Write-Host "Bucket hinzufügen: $targetName -> $repository" } } $changes = @( foreach ($app in Get-ChildItem -LiteralPath $apps -Directory) { foreach ($version in Get-ChildItem -LiteralPath $app.FullName -Directory) { # current und andere Verknüpfungen nicht nochmals bearbeiten. if ($version.Attributes -band [IO.FileAttributes]::ReparsePoint) { continue } $file = Join-Path $version.FullName 'install.json' if (-not (Test-Path -LiteralPath $file -PathType Leaf)) { continue } $original = [IO.File]::ReadAllText($file) $data = $original | ConvertFrom-Json if ($data.bucket -notin @('elexis', 'streichtech')) { continue } $pattern = '(?i)("bucket"\s*:\s*)"(?:elexis|streichtech)"' $updated = [regex]::Replace($original, $pattern, '${1}"medelexis"') if (($updated | ConvertFrom-Json).bucket -ne $targetName) { throw "Bucket-Zuordnung konnte nicht angepasst werden: $file" } Write-Host "$($app.Name) / $($version.Name): $($data.bucket) -> $targetName" [pscustomobject]@{ File = $file; Original = $original; Updated = $updated; App = $app.Name } } } ) $manifestRoot = $(if (Test-Path -LiteralPath $target) { $target } else { $renameSource }) $renames = @(Get-PackageRenames $manifestRoot) if (-not $Apply) { foreach ($oldName in @('elexis', 'streichtech')) { if (Test-Path -LiteralPath (Join-Path $ScoopRoot "buckets\$oldName")) { Write-Host "Alter Bucket wird nach der Umstellung entfernt oder umbenannt: $oldName" } } Write-Host "Probelauf: $($changes.Count) install.json-Dateien würden geändert." Write-Host "Probelauf: $($renames.Count) Paketnamen würden umgestellt." Write-Host 'Mit -Apply anwenden. Es werden keine Sicherungen erstellt.' return } if (-not (Test-Path -LiteralPath $target)) { if ($renameSource) { $bucketRoot = (Resolve-Path -LiteralPath (Join-Path $ScoopRoot 'buckets')).Path $sourcePath = (Resolve-Path -LiteralPath $renameSource).Path $targetPath = [IO.Path]::GetFullPath($target) if ((Split-Path $sourcePath -Parent) -ne $bucketRoot -or (Split-Path $targetPath -Parent) -ne $bucketRoot) { throw 'Bucket-Pfade liegen ausserhalb des erwarteten Verzeichnisses. Keine Umbenennung.' } [IO.Directory]::Move($sourcePath, $targetPath) } else { # scoop verwendet dieselbe Root-Variable für benutzerdefinierte Installationen. if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne [IO.Path]::GetFullPath($(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })).TrimEnd('\')) { throw 'ScoopRoot weicht von der aktiven Scoop-Installation ab; Bucket bitte dort zuerst einrichten.' } & scoop bucket add $targetName $repository if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $target)) { throw 'Bucket konnte nicht hinzugefügt werden. Installationsmetadaten bleiben unverändert.' } } } # Falls der Ziel-Bucket erst hinzugefügt wurde, stehen seine Manifestnamen jetzt bereit. if (-not $manifestRoot) { $renames = @(Get-PackageRenames $target) } # Vor dem ersten Schreiben prüfen, ob während des Probelaufs Dateien geändert wurden. foreach ($change in $changes) { if ([IO.File]::ReadAllText($change.File) -cne $change.Original) { throw 'Installationsmetadaten wurden zwischenzeitlich geändert. Skript bitte erneut starten.' } } foreach ($change in $changes) { [IO.File]::WriteAllText($change.File, $change.Updated, [Text.UTF8Encoding]::new($false)) } foreach ($rename in $renames) { $activeRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' }) if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne [IO.Path]::GetFullPath($activeRoot).TrimEnd('\')) { throw 'Für Paketumbenennungen muss ScoopRoot der aktiven Scoop-Installation entsprechen.' } $appsRoot = (Resolve-Path -LiteralPath $apps).Path if ((Split-Path $rename.Source -Parent) -ne $appsRoot -or (Split-Path $rename.Destination -Parent) -ne $appsRoot) { throw 'Paketpfade liegen ausserhalb des Apps-Verzeichnisses.' } if (Test-Path -LiteralPath $rename.PersistSource) { $persistRoot = (Resolve-Path -LiteralPath (Join-Path $ScoopRoot 'persist')).Path if ((Split-Path $rename.PersistSource -Parent) -ne $persistRoot -or (Split-Path $rename.PersistTarget -Parent) -ne $persistRoot) { throw 'Persistenzpfade liegen ausserhalb des Persist-Verzeichnisses.' } [IO.Directory]::Move($rename.PersistSource, $rename.PersistTarget) } $oldCurrent = Join-Path $rename.Source 'current' & attrib -R /L $oldCurrent if ($LASTEXITCODE -ne 0) { throw 'current-Verknüpfung konnte nicht freigegeben werden.' } # Nur die Junction entfernen, ohne rekursiv ihren Inhalt zu löschen. [IO.Directory]::Delete($oldCurrent) [IO.Directory]::Move($rename.Source, $rename.Destination) $newCurrent = Join-Path $rename.Destination 'current' $newVersion = Join-Path $rename.Destination $rename.Version New-Item -ItemType Junction -Path $newCurrent -Target $newVersion | Out-Null Write-Host "Startverknüpfungen für $($rename.NewName) erneuern ..." & scoop reset "$($rename.NewName)@$($rename.Version)" if ($LASTEXITCODE -ne 0) { throw "Scoop reset für '$($rename.NewName)' fehlgeschlagen." } } foreach ($oldName in @('elexis', 'streichtech')) { $oldPath = Join-Path $ScoopRoot "buckets\$oldName" if (-not (Test-Path -LiteralPath $oldPath)) { continue } $activeRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' }) if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne [IO.Path]::GetFullPath($activeRoot).TrimEnd('\')) { throw 'Zum Entfernen alter Buckets muss ScoopRoot der aktiven Scoop-Installation entsprechen.' } Write-Host "Alten Bucket entfernen: $oldName" & scoop bucket rm $oldName if ($LASTEXITCODE -ne 0 -or (Test-Path -LiteralPath $oldPath)) { throw "Alter Bucket '$oldName' konnte nicht entfernt werden. Die Umstellung kann erneut ausgeführt werden." } } foreach ($name in @((Get-ChildItem -LiteralPath $apps -Directory).Name)) { $installFile = Join-Path $apps "$name\current\install.json" if (-not (Test-Path -LiteralPath $installFile)) { continue } if (([IO.File]::ReadAllText($installFile) | ConvertFrom-Json).bucket -ne $targetName) { continue } if (-not (Test-Path -LiteralPath (Join-Path $target "bucket\$name.json")) -and -not (Test-Path -LiteralPath (Join-Path $target "$name.json"))) { Write-Warning "Für '$name' fehlt weiterhin ein gleichnamiges Manifest." } } Write-Host "Fertig: $($changes.Count) install.json-Dateien auf medelexis umgestellt." Write-Host "Paketnamen umgestellt: $($renames.Count)." Write-Host 'Von elexis/streichtech/medelexis bleibt nur medelexis bestehen. Prüfung: scoop status --local'