mirror of
https://github.com/MAKS-IT-COM/maksit-cluster-console.git
synced 2026-09-30 00:38:10 +02:00
446 lines
18 KiB
PowerShell
446 lines
18 KiB
PowerShell
#requires -Version 7.0
|
|
#requires -PSEdition Core
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Linux Flatpak pack plugin for published .NET desktop apps (Community).
|
|
|
|
.DESCRIPTION
|
|
Stages a linux-* DotNetPublish folder into a Flatpak layout (manifest,
|
|
desktop file, metainfo, launch script). Prefers Flathub org.flatpak.Builder
|
|
(current builder) over distro flatpak-builder. On Windows, native builder
|
|
is tried first; if missing, the plugin builds via a pinned WSL distro
|
|
(default Debian) on the Linux filesystem, then copies the bundle back.
|
|
Docker Desktop WSL distros are never used. whenBuilderMissing skip|fail
|
|
applies when neither native nor WSL builder is available.
|
|
#>
|
|
|
|
if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) {
|
|
$srcDir = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
|
$pluginSupportModulePath = Join-Path $srcDir "modules/Engine/PluginSupport.psm1"
|
|
if (Test-Path $pluginSupportModulePath -PathType Leaf) {
|
|
Import-Module $pluginSupportModulePath -Force -Global -ErrorAction Stop
|
|
}
|
|
}
|
|
|
|
function Get-FlatpakWhenBuilderMissing {
|
|
param($PluginSettings)
|
|
|
|
$value = [string](Get-PluginPropertyValue -PluginSettings $PluginSettings -Name 'whenBuilderMissing' -Default 'skip')
|
|
if ($value -notin @('skip', 'fail')) {
|
|
throw "FlatpakPack whenBuilderMissing must be 'skip' or 'fail'."
|
|
}
|
|
|
|
return $value
|
|
}
|
|
|
|
function Test-FlatpakBuilderMissingException {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[System.Management.Automation.ErrorRecord]$ErrorRecord
|
|
)
|
|
|
|
$message = [string]$ErrorRecord.Exception.Message
|
|
if ($message -match '(?i)bash: line \d+') {
|
|
return $false
|
|
}
|
|
|
|
if ($ErrorRecord.Exception -is [System.Management.Automation.CommandNotFoundException]) {
|
|
return $true
|
|
}
|
|
|
|
if ($message -match '(?i)(not recognized|command not found|The term .+ is not recognized|marked unavailable)') {
|
|
return $true
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
function Test-WslFallbackSkippable {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[System.Management.Automation.ErrorRecord]$ErrorRecord
|
|
)
|
|
|
|
if (Test-FlatpakBuilderMissingException -ErrorRecord $ErrorRecord) {
|
|
return $true
|
|
}
|
|
|
|
$message = [string]$ErrorRecord.Exception.Message
|
|
if ($message -match "(?i)WSL distro '.+' is not installed") {
|
|
return $true
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
function Get-WslInstalledDistroNames {
|
|
$output = Invoke-ExternalCommand -Name wsl -ArgumentList @('-l', '-q') -ThrowOnError:$false
|
|
return @(ConvertFrom-WslListOutput -InputObject $output)
|
|
}
|
|
|
|
function Invoke-FlatpakBundleViaWsl {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Distro,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$WindowsStageRoot,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$WindowsBundlePath,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$AppId
|
|
)
|
|
|
|
Assert-WslDistroAllowedForFlatpak -Distro $Distro
|
|
$installed = @(Get-WslInstalledDistroNames)
|
|
if (-not (Test-WslDistroInstalled -Distro $Distro -InstalledNames $installed)) {
|
|
throw "WSL distro '$Distro' is not installed. Install Debian with: wsl --install Debian --no-launch --web-download"
|
|
}
|
|
|
|
$script = ConvertTo-UnixLineEndings -Value (
|
|
New-WslFlatpakBuildScript `
|
|
-WindowsStageRoot $WindowsStageRoot `
|
|
-WindowsBundlePath $WindowsBundlePath `
|
|
-AppId $AppId
|
|
)
|
|
if (-not $script.EndsWith("`n")) {
|
|
$script += "`n"
|
|
}
|
|
|
|
$tempScript = Join-Path ([System.IO.Path]::GetTempPath()) ("maksit-flatpak-" + [guid]::NewGuid().ToString('N') + ".sh")
|
|
try {
|
|
[System.IO.File]::WriteAllBytes($tempScript, [System.Text.UTF8Encoding]::new($false).GetBytes($script))
|
|
$wslScript = ConvertTo-WslMountPath -WindowsPath $tempScript
|
|
Invoke-ExternalCommand -Name wsl -ArgumentList @('-d', $Distro, '--', 'bash', $wslScript) | Out-Null
|
|
}
|
|
finally {
|
|
if (Test-Path -LiteralPath $tempScript -PathType Leaf) {
|
|
Remove-Item -LiteralPath $tempScript -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
}
|
|
|
|
function Invoke-HostFlatpakBuilder {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$RepoDir,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BuildDir,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ManifestPath
|
|
)
|
|
|
|
$hasFlathubBuilder = $false
|
|
try {
|
|
Invoke-ExternalCommand -Name flatpak -ArgumentList @('info', 'org.flatpak.Builder') -ThrowOnError:$false | Out-Null
|
|
$hasFlathubBuilder = ($global:LASTEXITCODE -eq 0)
|
|
}
|
|
catch {
|
|
if (-not (Test-FlatpakBuilderMissingException -ErrorRecord $_)) {
|
|
throw
|
|
}
|
|
}
|
|
|
|
if ($hasFlathubBuilder) {
|
|
$stageRoot = [System.IO.Path]::GetDirectoryName($ManifestPath)
|
|
Invoke-ExternalCommand -Name flatpak -ArgumentList @(
|
|
'run',
|
|
'--filesystem=host',
|
|
'--share=network',
|
|
"--cwd=$stageRoot",
|
|
'org.flatpak.Builder',
|
|
'--disable-rofiles-fuse',
|
|
"--repo=$RepoDir",
|
|
'--force-clean',
|
|
$BuildDir,
|
|
$ManifestPath
|
|
) | Out-Null
|
|
return
|
|
}
|
|
|
|
Invoke-ExternalCommand -Name flatpak-builder -ArgumentList @(
|
|
'--disable-rofiles-fuse',
|
|
"--repo=$RepoDir",
|
|
'--force-clean',
|
|
$BuildDir,
|
|
$ManifestPath
|
|
) | Out-Null
|
|
}
|
|
|
|
function Invoke-Plugin {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
$Settings
|
|
)
|
|
|
|
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
|
|
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Add-ReleaseAssetPaths"
|
|
Import-PluginDependency -ModuleName "ExternalCommandSupport" -RequiredCommand "Invoke-ExternalCommand"
|
|
Import-PluginDependency -ModuleName "DesktopPackSupport" -RequiredCommand "New-WslFlatpakBuildScript"
|
|
|
|
$pluginSettings = $Settings
|
|
$sharedSettings = $Settings.context
|
|
$scriptDir = $sharedSettings.scriptDir
|
|
$version = [string]$sharedSettings.version
|
|
$artifactsDirectory = $sharedSettings.artifactsDirectory
|
|
|
|
$appId = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'appId')
|
|
$appName = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'appName')
|
|
if ([string]::IsNullOrWhiteSpace($appId)) {
|
|
throw "FlatpakPack plugin requires appId (reverse-DNS, e.g. com.maks_it.Wvc210)."
|
|
}
|
|
|
|
Assert-FlatpakAppId -AppId $appId
|
|
|
|
if ([string]::IsNullOrWhiteSpace($appName)) {
|
|
throw "FlatpakPack plugin requires appName."
|
|
}
|
|
|
|
$command = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'command')
|
|
$executableName = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'executableName')
|
|
$runtimeIdentifier = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'runtimeIdentifier' -Default 'linux-x64')
|
|
$publishDirSetting = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'publishDir')
|
|
$iconSetting = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'iconPath')
|
|
$summary = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'summary' -Default $appName)
|
|
$projectLicense = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'projectLicense' -Default 'MIT')
|
|
$categories = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'categories' -Default 'Utility;')
|
|
$runtime = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'runtime' -Default 'org.freedesktop.Platform')
|
|
$runtimeVersion = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'runtimeVersion' -Default '24.08')
|
|
$sdk = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'sdk' -Default 'org.freedesktop.Sdk')
|
|
$whenBuilderMissing = Get-FlatpakWhenBuilderMissing -PluginSettings $pluginSettings
|
|
$wslDistro = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'wslDistro' -Default 'Debian')
|
|
$useWslSetting = Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'useWsl' -Default $null
|
|
$useWsl = if ($null -eq $useWslSetting) { [bool]$IsWindows } else { [bool]$useWslSetting }
|
|
$buildBundle = $true
|
|
if ($pluginSettings.PSObject.Properties['buildBundle'] -and $null -ne $pluginSettings.buildBundle) {
|
|
$buildBundle = [bool]$pluginSettings.buildBundle
|
|
}
|
|
|
|
$attachSourceZip = $false
|
|
if ($pluginSettings.PSObject.Properties['attachSourceZip'] -and $null -ne $pluginSettings.attachSourceZip) {
|
|
$attachSourceZip = [bool]$pluginSettings.attachSourceZip
|
|
}
|
|
|
|
$finishArgs = @(Get-DefaultFlatpakFinishArgs)
|
|
if ($pluginSettings.PSObject.Properties['finishArgs'] -and $null -ne $pluginSettings.finishArgs) {
|
|
$finishArgs = @($pluginSettings.finishArgs | ForEach-Object { [string]$_ } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace([string]$artifactsDirectory)) {
|
|
throw "FlatpakPack plugin requires an artifacts directory in the shared context."
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $artifactsDirectory -PathType Container)) {
|
|
New-Item -ItemType Directory -Path $artifactsDirectory | Out-Null
|
|
}
|
|
|
|
$publishDirectory = Resolve-DesktopPublishDirectory `
|
|
-Context $sharedSettings `
|
|
-RuntimeIdentifier $runtimeIdentifier `
|
|
-PublishDir $publishDirSetting `
|
|
-ScriptDir $scriptDir
|
|
|
|
$executablePath = Resolve-DesktopExecutablePath `
|
|
-PublishDirectory $publishDirectory `
|
|
-ExecutableName $executableName
|
|
$executableFileName = [System.IO.Path]::GetFileName($executablePath)
|
|
if ([string]::IsNullOrWhiteSpace($command)) {
|
|
$command = $executableFileName
|
|
}
|
|
|
|
$moduleName = ($appId.Split('.') | Select-Object -Last 1).ToLowerInvariant()
|
|
$stageRoot = Join-Path $artifactsDirectory "flatpak-$moduleName"
|
|
if (Test-Path -LiteralPath $stageRoot) {
|
|
Remove-Item -LiteralPath $stageRoot -Recurse -Force
|
|
}
|
|
|
|
$filesRoot = Join-Path $stageRoot 'files'
|
|
$libDir = Join-Path $filesRoot "lib\$moduleName"
|
|
$binDir = Join-Path $filesRoot 'bin'
|
|
$shareAppDir = Join-Path $filesRoot 'share\applications'
|
|
$shareMetaDir = Join-Path $filesRoot 'share\metainfo'
|
|
$shareIconDir = Join-Path $filesRoot 'share\icons\hicolor\256x256\apps'
|
|
New-Item -ItemType Directory -Path $libDir, $binDir, $shareAppDir, $shareMetaDir, $shareIconDir -Force | Out-Null
|
|
|
|
Write-Log -Level "STEP" -Message "Staging Flatpak layout from $publishDirectory"
|
|
Copy-Item -Path (Join-Path $publishDirectory '*') -Destination $libDir -Recurse -Force
|
|
|
|
$launchScript = New-FlatpakLaunchScript -ModuleName $moduleName -ExecutableFileName $executableFileName
|
|
$launchPath = Join-Path $binDir $command
|
|
[System.IO.File]::WriteAllText($launchPath, $launchScript.Replace("`r`n", "`n"), [System.Text.UTF8Encoding]::new($false))
|
|
|
|
$desktopPath = Join-Path $shareAppDir "$appId.desktop"
|
|
[System.IO.File]::WriteAllText(
|
|
$desktopPath,
|
|
(New-FlatpakDesktopEntry -AppId $appId -AppName $appName -Command $command -Categories $categories).Replace("`r`n", "`n"),
|
|
[System.Text.UTF8Encoding]::new($false)
|
|
)
|
|
|
|
$metaPath = Join-Path $shareMetaDir "$appId.metainfo.xml"
|
|
[System.IO.File]::WriteAllText(
|
|
$metaPath,
|
|
(New-FlatpakMetainfoXml -AppId $appId -AppName $appName -Summary $summary -ProjectLicense $projectLicense).Replace("`r`n", "`n"),
|
|
[System.Text.UTF8Encoding]::new($false)
|
|
)
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($iconSetting)) {
|
|
$iconPath = if ([System.IO.Path]::IsPathRooted($iconSetting)) {
|
|
$iconSetting
|
|
}
|
|
else {
|
|
[System.IO.Path]::GetFullPath((Join-Path $scriptDir $iconSetting))
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $iconPath -PathType Leaf)) {
|
|
throw "FlatpakPack iconPath not found: $iconPath"
|
|
}
|
|
|
|
if ([System.IO.Path]::GetExtension($iconPath) -ine '.png') {
|
|
Write-Log -Level "WARN" -Message " Flatpak icon should be PNG; skipping copy of $iconPath"
|
|
}
|
|
else {
|
|
Copy-Item -LiteralPath $iconPath -Destination (Join-Path $shareIconDir "$appId.png") -Force
|
|
}
|
|
}
|
|
|
|
$buildCommands = @(
|
|
'mkdir -p /app/lib /app/share',
|
|
"cp -a lib/$moduleName /app/lib/$moduleName",
|
|
'cp -a share /app/share',
|
|
"install -Dm755 bin/$command /app/bin/$command",
|
|
"chmod +x /app/lib/$moduleName/$executableFileName"
|
|
)
|
|
|
|
$manifest = New-FlatpakManifestObject `
|
|
-AppId $appId `
|
|
-Command $command `
|
|
-ModuleName $moduleName `
|
|
-Runtime $runtime `
|
|
-RuntimeVersion $runtimeVersion `
|
|
-Sdk $sdk `
|
|
-FinishArgs $finishArgs `
|
|
-BuildCommands $buildCommands
|
|
|
|
$manifestPath = Join-Path $stageRoot 'manifest.json'
|
|
$manifestJson = $manifest | ConvertTo-Json -Depth 8
|
|
[System.IO.File]::WriteAllText($manifestPath, $manifestJson, [System.Text.UTF8Encoding]::new($false))
|
|
|
|
$bundleNamePattern = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'bundleNamePattern' -Default '{name}-{version}.flatpak')
|
|
$safeName = ($appName -replace '[^A-Za-z0-9._-]', '-').Trim('-')
|
|
$bundleFileName = $bundleNamePattern.Replace('{version}', $version).Replace('{id}', $appId).Replace('{name}', $safeName)
|
|
$bundlePath = Join-Path $artifactsDirectory $bundleFileName
|
|
|
|
$builderBlock = (Get-FlatpakBuilderShellLines) -join "`n"
|
|
$buildScript = @"
|
|
#!/bin/sh
|
|
set -e
|
|
cd "`$(dirname "`$0")"
|
|
$builderBlock
|
|
flatpak build-bundle "`$ROOT/repo" "../$bundleFileName" "$appId"
|
|
"@
|
|
[System.IO.File]::WriteAllText((Join-Path $stageRoot 'build-flatpak.sh'), $buildScript.Replace("`r`n", "`n"), [System.Text.UTF8Encoding]::new($false))
|
|
|
|
$assetPaths = [System.Collections.Generic.List[string]]::new()
|
|
|
|
if ($attachSourceZip) {
|
|
$sourceZipPattern = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'sourceZipNamePattern' -Default '{id}-{version}-flatpak-src.zip')
|
|
$sourceZipName = $sourceZipPattern.Replace('{version}', $version).Replace('{id}', $appId).Replace('{name}', $safeName)
|
|
$sourceZipPath = Join-Path $artifactsDirectory $sourceZipName
|
|
if (Test-Path -LiteralPath $sourceZipPath -PathType Leaf) {
|
|
Remove-Item -LiteralPath $sourceZipPath -Force
|
|
}
|
|
|
|
Compress-Archive -Path $stageRoot -DestinationPath $sourceZipPath -CompressionLevel Optimal -Force
|
|
Write-Log -Level "OK" -Message " Flatpak source archive: $sourceZipPath"
|
|
$assetPaths.Add($sourceZipPath)
|
|
Set-EngineFact -Context $sharedSettings -Namespace 'desktop' -Name 'flatpakSourcePath' -Value $sourceZipPath -Overwrite Replace
|
|
}
|
|
|
|
if ($buildBundle) {
|
|
Write-Log -Level "STEP" -Message "Building Flatpak bundle..."
|
|
$buildDir = Join-Path $stageRoot 'build-dir'
|
|
$repoDir = Join-Path $stageRoot 'repo'
|
|
$built = $false
|
|
try {
|
|
Invoke-HostFlatpakBuilder -RepoDir $repoDir -BuildDir $buildDir -ManifestPath $manifestPath
|
|
|
|
if (Test-Path -LiteralPath $bundlePath -PathType Leaf) {
|
|
Remove-Item -LiteralPath $bundlePath -Force
|
|
}
|
|
|
|
Invoke-ExternalCommand -Name flatpak -ArgumentList @(
|
|
'build-bundle',
|
|
$repoDir,
|
|
$bundlePath,
|
|
$appId
|
|
) | Out-Null
|
|
|
|
if (-not (Test-Path -LiteralPath $bundlePath -PathType Leaf)) {
|
|
throw "flatpak build-bundle completed but bundle was not produced: $bundlePath"
|
|
}
|
|
|
|
$built = $true
|
|
}
|
|
catch {
|
|
if (-not (Test-FlatpakBuilderMissingException -ErrorRecord $_)) {
|
|
throw
|
|
}
|
|
|
|
Write-Log -Level "INFO" -Message " Native Flatpak builder not available (Flathub org.flatpak.Builder or distro flatpak-builder)."
|
|
}
|
|
|
|
if (-not $built -and $useWsl -and $IsWindows) {
|
|
try {
|
|
Write-Log -Level "STEP" -Message "Building Flatpak bundle via WSL distro $wslDistro (Linux filesystem, not Docker Desktop)..."
|
|
if (Test-Path -LiteralPath $bundlePath -PathType Leaf) {
|
|
Remove-Item -LiteralPath $bundlePath -Force
|
|
}
|
|
|
|
Invoke-FlatpakBundleViaWsl `
|
|
-Distro $wslDistro `
|
|
-WindowsStageRoot $stageRoot `
|
|
-WindowsBundlePath $bundlePath `
|
|
-AppId $appId
|
|
|
|
if (-not (Test-Path -LiteralPath $bundlePath -PathType Leaf)) {
|
|
throw "WSL flatpak build-bundle completed but bundle was not produced: $bundlePath"
|
|
}
|
|
|
|
$built = $true
|
|
}
|
|
catch {
|
|
if ($whenBuilderMissing -eq 'skip' -and (Test-WslFallbackSkippable -ErrorRecord $_)) {
|
|
Write-Log -Level "WARN" -Message " WSL Flatpak builder not available ($($_.Exception.Message)); skipped bundle."
|
|
}
|
|
else {
|
|
throw
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($built) {
|
|
Write-Log -Level "OK" -Message " Flatpak bundle ready: $bundlePath"
|
|
$assetPaths.Add($bundlePath)
|
|
Set-EngineFact -Context $sharedSettings -Namespace 'desktop' -Name 'flatpakBundlePath' -Value $bundlePath -Overwrite Replace
|
|
}
|
|
elseif ($whenBuilderMissing -eq 'fail') {
|
|
throw "Flatpak builder is not available (Flathub org.flatpak.Builder, distro flatpak-builder, or WSL distro $wslDistro)."
|
|
}
|
|
elseif (-not $useWsl -or -not $IsWindows) {
|
|
Write-Log -Level "WARN" -Message " Flatpak builder not available; skipped bundle. Install Flathub org.flatpak.Builder (preferred) or distro flatpak-builder."
|
|
}
|
|
}
|
|
|
|
Set-EngineState -Context $sharedSettings -Name 'releaseDir' -Value $artifactsDirectory
|
|
if ($assetPaths.Count -gt 0) {
|
|
Add-ReleaseAssetPaths -Context $sharedSettings -Path @($assetPaths)
|
|
}
|
|
}
|
|
|
|
Export-ModuleMember -Function Invoke-Plugin
|