scripts/package.ps1
Rama main · réplica de lectura del repositorio original.
param(
[string]$OutDir = "release",
[string]$Version = "",
[string]$NodeVersion = "",
[switch]$NodeLocal,
[switch]$WithPVE,
[string]$NodeLocalDir = "",
[switch]$UseGitTag,
[switch]$Release,
# [string[]]$Targets = @("linux/amd64", "windows/amd64"),
[string[]]$Targets = @("linux/amd64"),
[Alias("z")]
[switch]$Zip
)
$ErrorActionPreference = "Stop"
$FrontendDistDir = "build/frontend/dist"
$NodeLocalDefaultDir = "deploy/node"
$NodeRemoteUrl = "https://github.com/Ithildur/Ithiltir-node.git"
$NodeRepoSlug = "Ithildur/Ithiltir-node"
$BuildChannel = "release"
$NodeAssetManifest = [ordered]@{
"node_linux_amd64_sha256" = "linux/node_linux_amd64"
"node_linux_arm64_sha256" = "linux/node_linux_arm64"
"node_macos_arm64_sha256" = "macos/node_macos_arm64"
"node_windows_amd64_sha256" = "windows/node_windows_amd64.exe"
"node_windows_arm64_sha256" = "windows/node_windows_arm64.exe"
"runner_windows_amd64_sha256" = "windows/runner_windows_amd64.exe"
"runner_windows_arm64_sha256" = "windows/runner_windows_arm64.exe"
}
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
function Get-GitTag {
if ($env:GITHUB_REF_TYPE -eq "tag" -and $env:GITHUB_REF_NAME) {
return $env:GITHUB_REF_NAME.Trim()
}
$tags = @(& git -C $repoRoot tag --points-at HEAD 2>$null | Where-Object { $_ })
if ($tags.Count -ne 1) {
if ($tags.Count -gt 0) {
[Console]::Error.WriteLine(($tags -join [Environment]::NewLine))
}
throw "current commit must have exactly one git tag"
}
return $tags[0].Trim()
}
function Invoke-VersionTool {
param(
[Parameter(Mandatory = $true)]
[string[]]$Arguments,
[string[]]$InputLines = @()
)
$go = Resolve-Go
if ($InputLines.Count -gt 0 -or ($Arguments.Count -gt 0 -and $Arguments[0] -eq "latest")) {
$inputText = ""
if ($InputLines.Count -gt 0) {
$inputText = ($InputLines -join [Environment]::NewLine) + [Environment]::NewLine
}
$output = @($inputText | & $go -C $repoRoot run ./cmd/releasever @Arguments)
} else {
$output = @(& $go -C $repoRoot run ./cmd/releasever @Arguments)
}
if ($LASTEXITCODE -ne 0) {
throw ("version tool failed: go run ./cmd/releasever {0}" -f ($Arguments -join " "))
}
return $output
}
function Get-VersionChannel {
param(
[Parameter(Mandatory = $true)]
[string]$Value
)
$output = Invoke-VersionTool -Arguments @("channel", $Value)
if ($output.Count -eq 0) { return "" }
return $output[0].Trim()
}
function Test-SemVer {
param(
[Parameter(Mandatory = $true)]
[string]$Value
)
try {
Invoke-VersionTool -Arguments @("validate", $Value) | Out-Null
return $true
} catch {
return $false
}
}
function Resolve-BuildVersion {
param(
[string]$RawVersion
)
if ($UseGitTag) {
$RawVersion = Get-GitTag
}
if ($Release -and -not $UseGitTag) {
throw "release mode requires -UseGitTag"
}
if ([string]::IsNullOrWhiteSpace($RawVersion)) {
$RawVersion = "0.0.0-dev"
}
$RawVersion = $RawVersion.Trim()
try {
$script:BuildChannel = Get-VersionChannel -Value $RawVersion
} catch {
throw "version must be strict SemVer without a v prefix: $RawVersion"
}
return $RawVersion
}
function Get-LatestRemoteTag {
param(
[Parameter(Mandatory = $true)]
[string]$RemoteUrl,
[Parameter(Mandatory = $true)]
[string]$Channel
)
$refs = @(& git ls-remote --tags --refs --sort="v:refname" $RemoteUrl 2>$null)
if ($LASTEXITCODE -ne 0) {
$refs = @(& git ls-remote --tags --refs $RemoteUrl)
if ($LASTEXITCODE -ne 0) {
throw "failed to fetch tags from $RemoteUrl"
}
}
$tags = @()
foreach ($line in $refs) {
$parts = $line -split "\s+"
if ($parts.Count -lt 2) { continue }
$tags += ($parts[1] -replace "^refs/tags/", "")
}
try {
$latest = Invoke-VersionTool -Arguments @("latest", $Channel) -InputLines $tags
} catch {
throw "no valid Ithiltir-node $Channel tags found on $RemoteUrl"
}
if ($latest.Count -eq 0 -or [string]::IsNullOrWhiteSpace($latest[0])) {
throw "no valid Ithiltir-node $Channel tags found on $RemoteUrl"
}
return $latest[0].Trim()
}
function Resolve-NodeVersion {
param(
[string]$RawVersion,
[string]$LocalDir
)
if ([string]::IsNullOrWhiteSpace($RawVersion)) {
if (-not [string]::IsNullOrWhiteSpace($env:ITHILTIR_NODE_VERSION)) {
$RawVersion = $env:ITHILTIR_NODE_VERSION
}
}
if ([string]::IsNullOrWhiteSpace($RawVersion) -and -not [string]::IsNullOrWhiteSpace($LocalDir)) {
throw "local node packaging requires -NodeVersion or ITHILTIR_NODE_VERSION"
}
if ([string]::IsNullOrWhiteSpace($RawVersion)) {
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
throw "git is required to resolve latest Ithiltir-node release tag"
}
$RawVersion = Get-LatestRemoteTag -RemoteUrl $NodeRemoteUrl -Channel $BuildChannel
}
$RawVersion = $RawVersion.Trim()
if (-not (Test-SemVer -Value $RawVersion)) {
throw "node version must be strict SemVer without a v prefix: $RawVersion"
}
if ($Release) {
$nodeChannel = Get-VersionChannel -Value $RawVersion
if ($BuildChannel -eq "release" -and $nodeChannel -ne "release") {
throw "node version channel ($nodeChannel) must match dash version channel ($BuildChannel)"
}
}
return $RawVersion
}
function Download-File {
param(
[Parameter(Mandatory = $true)]
[string]$Url,
[Parameter(Mandatory = $true)]
[string]$OutFile
)
$outDir = Split-Path -Parent $OutFile
if ($outDir -and -not (Test-Path $outDir)) {
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
}
Invoke-WebRequest -Uri $Url -OutFile $OutFile -UseBasicParsing -TimeoutSec 600
}
function Download-NodeAsset {
param(
[Parameter(Mandatory = $true)]
[string]$Version,
[Parameter(Mandatory = $true)]
[string]$Asset,
[Parameter(Mandatory = $true)]
[string]$OutFile
)
$url = "https://github.com/{0}/releases/download/{1}/{2}" -f $NodeRepoSlug, $Version, $Asset
Write-Host ("downloading node asset: {0}" -f $url)
Download-File -Url $url -OutFile $OutFile
}
function Set-UnixExecutable {
param(
[Parameter(Mandatory = $true)]
[string[]]$Paths
)
$chmod = Get-Command chmod -ErrorAction SilentlyContinue
if (-not $chmod) { return }
foreach ($path in $Paths) {
& $chmod.Source 755 $path
if ($LASTEXITCODE -ne 0) {
throw ("chmod failed: {0}" -f $path)
}
}
}
function Get-FileSHA256 {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
}
function Set-NodeDeployPermissions {
param(
[Parameter(Mandatory = $true)]
[string]$DeployDir
)
Set-UnixExecutable -Paths @(
(Join-Path $DeployDir "linux/node_linux_amd64"),
(Join-Path $DeployDir "linux/node_linux_arm64"),
(Join-Path $DeployDir "macos/node_macos_arm64")
)
}
function Prepare-RemoteNodeDeploy {
param(
[Parameter(Mandatory = $true)]
[string]$Version,
[Parameter(Mandatory = $true)]
[string]$DeployDir
)
if (Test-Path $DeployDir) {
Remove-Item -Recurse -Force $DeployDir
}
New-Item -ItemType Directory -Path (Join-Path $DeployDir "linux") -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $DeployDir "macos") -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $DeployDir "windows") -Force | Out-Null
$linuxAmd64 = Join-Path $DeployDir "linux/node_linux_amd64"
$linuxArm64 = Join-Path $DeployDir "linux/node_linux_arm64"
$macArm64 = Join-Path $DeployDir "macos/node_macos_arm64"
Download-NodeAsset -Version $Version -Asset "Ithiltir-node-linux-amd64" -OutFile $linuxAmd64
Download-NodeAsset -Version $Version -Asset "Ithiltir-node-linux-arm64" -OutFile $linuxArm64
Download-NodeAsset -Version $Version -Asset "Ithiltir-node-macos-arm64" -OutFile $macArm64
Download-NodeAsset -Version $Version -Asset "Ithiltir-node-windows-amd64.exe" -OutFile (Join-Path $DeployDir "windows/node_windows_amd64.exe")
Download-NodeAsset -Version $Version -Asset "Ithiltir-node-windows-arm64.exe" -OutFile (Join-Path $DeployDir "windows/node_windows_arm64.exe")
Download-NodeAsset -Version $Version -Asset "Ithiltir-runner-windows-amd64.exe" -OutFile (Join-Path $DeployDir "windows/runner_windows_amd64.exe")
Download-NodeAsset -Version $Version -Asset "Ithiltir-runner-windows-arm64.exe" -OutFile (Join-Path $DeployDir "windows/runner_windows_arm64.exe")
Set-NodeDeployPermissions -DeployDir $DeployDir
}
function Copy-LocalNodeAsset {
param(
[Parameter(Mandatory = $true)]
[string[]]$Sources,
[Parameter(Mandatory = $true)]
[string]$OutFile
)
foreach ($source in $Sources) {
if (Test-Path $source -PathType Leaf) {
$outDir = Split-Path -Parent $OutFile
if ($outDir -and -not (Test-Path $outDir)) {
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
}
Copy-Item -Force $source $OutFile
return
}
}
throw ("local node asset not found. tried: {0}" -f ($Sources -join ", "))
}
function Prepare-LocalNodeDeploy {
param(
[Parameter(Mandatory = $true)]
[string]$SourceDir,
[Parameter(Mandatory = $true)]
[string]$DeployDir
)
if (-not (Test-Path $SourceDir -PathType Container)) {
throw ("local node deploy directory not found: {0}" -f $SourceDir)
}
if (Test-Path $DeployDir) {
Remove-Item -Recurse -Force $DeployDir
}
New-Item -ItemType Directory -Path (Join-Path $DeployDir "linux") -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $DeployDir "macos") -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $DeployDir "windows") -Force | Out-Null
Copy-LocalNodeAsset -Sources @((Join-Path $SourceDir "linux/node_linux_amd64"), (Join-Path $SourceDir "linux/Ithiltir-node-linux-amd64"), (Join-Path $SourceDir "Ithiltir-node-linux-amd64")) -OutFile (Join-Path $DeployDir "linux/node_linux_amd64")
Copy-LocalNodeAsset -Sources @((Join-Path $SourceDir "linux/node_linux_arm64"), (Join-Path $SourceDir "linux/Ithiltir-node-linux-arm64"), (Join-Path $SourceDir "Ithiltir-node-linux-arm64")) -OutFile (Join-Path $DeployDir "linux/node_linux_arm64")
Copy-LocalNodeAsset -Sources @((Join-Path $SourceDir "macos/node_macos_arm64"), (Join-Path $SourceDir "macos/Ithiltir-node-macos-arm64"), (Join-Path $SourceDir "Ithiltir-node-macos-arm64")) -OutFile (Join-Path $DeployDir "macos/node_macos_arm64")
Copy-LocalNodeAsset -Sources @((Join-Path $SourceDir "windows/node_windows_amd64.exe"), (Join-Path $SourceDir "windows/Ithiltir-node-windows-amd64.exe"), (Join-Path $SourceDir "Ithiltir-node-windows-amd64.exe")) -OutFile (Join-Path $DeployDir "windows/node_windows_amd64.exe")
Copy-LocalNodeAsset -Sources @((Join-Path $SourceDir "windows/node_windows_arm64.exe"), (Join-Path $SourceDir "windows/Ithiltir-node-windows-arm64.exe"), (Join-Path $SourceDir "Ithiltir-node-windows-arm64.exe")) -OutFile (Join-Path $DeployDir "windows/node_windows_arm64.exe")
Copy-LocalNodeAsset -Sources @((Join-Path $SourceDir "windows/runner_windows_amd64.exe"), (Join-Path $SourceDir "windows/Ithiltir-runner-windows-amd64.exe"), (Join-Path $SourceDir "Ithiltir-runner-windows-amd64.exe")) -OutFile (Join-Path $DeployDir "windows/runner_windows_amd64.exe")
Copy-LocalNodeAsset -Sources @((Join-Path $SourceDir "windows/runner_windows_arm64.exe"), (Join-Path $SourceDir "windows/Ithiltir-runner-windows-arm64.exe"), (Join-Path $SourceDir "Ithiltir-runner-windows-arm64.exe")) -OutFile (Join-Path $DeployDir "windows/runner_windows_arm64.exe")
Set-NodeDeployPermissions -DeployDir $DeployDir
}
function Resolve-RepoPath {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if ([System.IO.Path]::IsPathRooted($Path)) {
return [System.IO.Path]::GetFullPath($Path)
}
return [System.IO.Path]::GetFullPath((Join-Path $repoRoot $Path))
}
$outRoot = Resolve-RepoPath $OutDir
New-Item -ItemType Directory -Path $outRoot -Force | Out-Null
if ($NodeLocal -and [string]::IsNullOrWhiteSpace($NodeLocalDir)) {
$NodeLocalDir = $NodeLocalDefaultDir
}
function Resolve-Go {
$go = Get-Command go -ErrorAction SilentlyContinue
if (-not $go) {
throw "go is required to build dash"
}
return $go.Source
}
function Get-DashBinaryPath {
param(
[string]$Os,
[string]$Arch,
[string]$DistRoot
)
$osDir = $Os
if ($Os -eq "darwin") {
$osDir = "macos"
}
$binName = "dash_{0}" -f $Arch
if ($Os -eq "windows") {
$binName = "{0}.exe" -f $binName
}
return Join-Path $DistRoot (Join-Path $osDir $binName)
}
function Build-DashBinary {
param(
[Parameter(Mandatory = $true)]
[string]$Os,
[Parameter(Mandatory = $true)]
[string]$Arch,
[Parameter(Mandatory = $true)]
[string]$DistRoot,
[Parameter(Mandatory = $true)]
[string]$GoCmd,
[Parameter(Mandatory = $true)]
[string]$Ldflags
)
$output = Get-DashBinaryPath -Os $Os -Arch $Arch -DistRoot $DistRoot
$outputDir = Split-Path -Parent $output
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
Write-Host ("building dash for {0}/{1}: {2}" -f $Os, $Arch, $output)
$oldGoos = $env:GOOS
$oldGoarch = $env:GOARCH
$oldCgo = $env:CGO_ENABLED
try {
$env:GOOS = $Os
$env:GOARCH = $Arch
$env:CGO_ENABLED = "0"
& $GoCmd -C $repoRoot build -trimpath -ldflags $Ldflags -o $output ./cmd/dash
if ($LASTEXITCODE -ne 0) {
throw ("go build failed for {0}/{1} (exit {2})" -f $Os, $Arch, $LASTEXITCODE)
}
} finally {
$env:GOOS = $oldGoos
$env:GOARCH = $oldGoarch
$env:CGO_ENABLED = $oldCgo
}
}
$buildVersion = Resolve-BuildVersion -RawVersion $Version
$nodeBuildVersion = Resolve-NodeVersion -RawVersion $NodeVersion -LocalDir $NodeLocalDir
$ldflags = "-s -w -X dash/internal/version.Current=$buildVersion -X dash/internal/version.BundledNode=$nodeBuildVersion"
Write-Host ("dash version: {0}" -f $buildVersion)
Write-Host ("dash channel: {0}" -f $BuildChannel)
Write-Host ("node version: {0}" -f $nodeBuildVersion)
if (-not [string]::IsNullOrWhiteSpace($NodeLocalDir)) {
Write-Host ("node source: {0}" -f (Resolve-RepoPath $NodeLocalDir))
}
$goCmd = Resolve-Go
$frontendScript = Join-Path $repoRoot "scripts/build_frontend.ps1"
$frontendDistPath = Resolve-RepoPath $FrontendDistDir
$targetList = @()
foreach ($rawTarget in $Targets) {
$targetList += $rawTarget.Split(",", [System.StringSplitOptions]::RemoveEmptyEntries) | ForEach-Object { $_.Trim() } | Where-Object { $_ }
}
if ($targetList.Count -eq 0) {
throw "no valid targets provided"
}
foreach ($t in $targetList) {
$parts = $t.Split("/", 2)
if ($parts.Count -ne 2 -or [string]::IsNullOrWhiteSpace($parts[0]) -or [string]::IsNullOrWhiteSpace($parts[1])) {
throw "invalid target: $t (expected os/arch)"
}
}
Push-Location $repoRoot
try {
if (-not (Test-Path $frontendScript)) {
throw ("frontend build script not found: {0}" -f $frontendScript)
}
& $frontendScript -OutDir $FrontendDistDir -Version $buildVersion
if ($LASTEXITCODE -ne 0) {
throw ("frontend build failed (exit {0})" -f $LASTEXITCODE)
}
if (-not (Test-Path $frontendDistPath)) {
throw ("frontend build output not found: {0}" -f $frontendDistPath)
}
$distRoot = Join-Path $repoRoot "build"
$nodeDeployPath = Join-Path $distRoot "node-deploy"
if ([string]::IsNullOrWhiteSpace($NodeLocalDir)) {
Prepare-RemoteNodeDeploy -Version $nodeBuildVersion -DeployDir $nodeDeployPath
} else {
Prepare-LocalNodeDeploy -SourceDir (Resolve-RepoPath $NodeLocalDir) -DeployDir $nodeDeployPath
}
if ($WithPVE) {
$pveManifest = @("format_version=1", "node_version=$nodeBuildVersion")
foreach ($pveArch in @("amd64", "arm64")) {
$pveFile = "pve_cache_linux_$pveArch"
$pveOutput = Join-Path $nodeDeployPath "linux/$pveFile"
if ([string]::IsNullOrWhiteSpace($NodeLocalDir)) {
Download-NodeAsset -Version $nodeBuildVersion -Asset "Ithiltir-pve-cache-linux-$pveArch" -OutFile $pveOutput
} else {
$pveSource = Resolve-RepoPath $NodeLocalDir
Copy-LocalNodeAsset -Sources @((Join-Path $pveSource "linux/$pveFile"), (Join-Path $pveSource "Ithiltir-pve-cache-linux-$pveArch")) -OutFile $pveOutput
}
$pveManifest += "${pveArch}_sha256=$(Get-FileSHA256 -Path $pveOutput)"
Set-UnixExecutable -Paths @($pveOutput)
}
[System.IO.File]::WriteAllText((Join-Path $nodeDeployPath "linux/pve-cache.env"), ($pveManifest -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
}
$nodeAssetSHA256 = @{}
foreach ($field in $NodeAssetManifest.Keys) {
$nodeAssetSHA256[$field] = Get-FileSHA256 -Path (Join-Path $nodeDeployPath $NodeAssetManifest[$field])
}
foreach ($t in $targetList) {
$parts = $t.Split("/", 2)
if ($parts.Count -ne 2) {
throw "invalid target: $t (expected os/arch)"
}
$os = $parts[0]
$arch = $parts[1]
Build-DashBinary -Os $os -Arch $arch -DistRoot $distRoot -GoCmd $goCmd -Ldflags $ldflags
$buildRoot = Join-Path $outRoot "build"
New-Item -ItemType Directory -Path $buildRoot -Force | Out-Null
$pkgRoot = Join-Path $buildRoot "Ithiltir-dash"
if (Test-Path $pkgRoot) {
Remove-Item -Recurse -Force $pkgRoot
}
New-Item -ItemType Directory -Path $pkgRoot | Out-Null
New-Item -ItemType Directory -Path (Join-Path $pkgRoot "bin") | Out-Null
New-Item -ItemType Directory -Path (Join-Path $pkgRoot "configs") | Out-Null
New-Item -ItemType Directory -Path (Join-Path $pkgRoot "logs") | Out-Null
$exeName = "dash"
if ($os -eq "windows") {
$exeName = "dash.exe"
}
$exePath = Join-Path $pkgRoot ("bin/{0}" -f $exeName)
$sourceExe = Get-DashBinaryPath -Os $os -Arch $arch -DistRoot $distRoot
if (-not (Test-Path $sourceExe)) {
throw ("dash build output not found: {0}" -f $sourceExe)
}
Copy-Item -Force $sourceExe $exePath
$releaseMetadata = [string[]]@(
"format_version=1"
"dash_version=$buildVersion"
"node_version=$nodeBuildVersion"
"target_os=$os"
"target_arch=$arch"
)
foreach ($field in $NodeAssetManifest.Keys) {
$releaseMetadata += "{0}={1}" -f $field, $nodeAssetSHA256[$field]
}
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText(
(Join-Path $pkgRoot "release.env"),
(($releaseMetadata -join "`n") + "`n"),
$utf8NoBom
)
Copy-Item -Force (Join-Path $repoRoot "configs/config.example.yaml") (Join-Path $pkgRoot "configs/config.example.yaml")
Copy-Item -Recurse -Force $frontendDistPath (Join-Path $pkgRoot "dist")
Copy-Item -Recurse -Force $nodeDeployPath (Join-Path $pkgRoot "deploy")
Copy-Item -Force (Join-Path $repoRoot "install_dash_linux.sh") $pkgRoot
Copy-Item -Force (Join-Path $repoRoot "update_dash_linux.sh") $pkgRoot
if ($os -eq "linux") {
Set-UnixExecutable -Paths @(
$exePath,
(Join-Path $pkgRoot "install_dash_linux.sh"),
(Join-Path $pkgRoot "update_dash_linux.sh")
)
}
if ($Zip) {
$zipPath = Join-Path $outRoot ("Ithiltir_dash_{0}_{1}.zip" -f $os, $arch)
if (Test-Path $zipPath) {
Remove-Item -Force $zipPath
}
Compress-Archive -Path $pkgRoot -DestinationPath $zipPath -Force
}
}
} finally {
Pop-Location
}
Write-Host ("done. output: {0}" -f $outRoot)