add watchdogs
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
name: windows-exe-build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [window, pyinstaller]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python venv
|
||||
shell: powershell
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
python -m venv .venv
|
||||
.\.venv\Scripts\python -m pip install --upgrade pip
|
||||
|
||||
if (Test-Path requirements.txt) {
|
||||
.\.venv\Scripts\pip install -r requirements.txt
|
||||
}
|
||||
.\.venv\Scripts\pip install pyinstaller
|
||||
|
||||
- name: Build (onedir) with optional --add-data
|
||||
shell: powershell
|
||||
env:
|
||||
APP: ${{ vars.APP_NAME }}
|
||||
ENTRY: ${{ vars.ENTRY_FILE }}
|
||||
EXTRA: ${{ vars.PYINSTALLER_ARGS }}
|
||||
ADD_DATA: ${{ vars.ADD_DATA }} # 예: "config.ini -> .\n dashboard/templates/dashboard -> templates/dashboard"
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($env:APP)) { throw "Missing var: APP_NAME" }
|
||||
if ([string]::IsNullOrWhiteSpace($env:ENTRY)) { throw "Missing var: ENTRY_FILE" }
|
||||
|
||||
# -------------------------
|
||||
# 1) EXTRA (단순 split)
|
||||
# -------------------------
|
||||
$extraArgs = @()
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($env:EXTRA)) {
|
||||
$extraArgs = $env:EXTRA -split "\s+"
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# 2) ADD_DATA: "src -> dest" 를 "--add-data src;dest" 로 변환 (Windows는 ;)
|
||||
# - 비어있으면 아무 것도 추가 안 함
|
||||
# - 여러 줄/쉼표 구분 모두 허용
|
||||
# -------------------------
|
||||
$addArgs = @()
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($env:ADD_DATA)) {
|
||||
|
||||
# 줄바꿈/쉼표로 분리 (사용자가 어느 방식으로 넣어도 되게)
|
||||
$rules = $env:ADD_DATA `
|
||||
-split "(\r?\n|,)" `
|
||||
| ForEach-Object { $_.Trim() } `
|
||||
| Where-Object { $_ -and $_ -ne "-" }
|
||||
|
||||
foreach ($r in $rules) {
|
||||
# 지원 포맷: "src -> dest" 또는 "src=dest" 또는 "src|dest"
|
||||
$src = $null
|
||||
$dst = $null
|
||||
|
||||
if ($r -match "^\s*(.+?)\s*->\s*(.+?)\s*$") {
|
||||
$src = $Matches[1].Trim()
|
||||
$dst = $Matches[2].Trim()
|
||||
}
|
||||
elseif ($r -match "^\s*(.+?)\s*[=|]\s*(.+?)\s*$") {
|
||||
$src = $Matches[1].Trim()
|
||||
$dst = $Matches[2].Trim()
|
||||
}
|
||||
else {
|
||||
throw "ADD_DATA rule format invalid: '$r' (use 'src -> dest')"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($src) -or [string]::IsNullOrWhiteSpace($dst)) {
|
||||
throw "ADD_DATA rule has empty src/dest: '$r'"
|
||||
}
|
||||
|
||||
# PyInstaller Windows delimiter is ';'
|
||||
$pair = "$src;$dst"
|
||||
|
||||
# 인자 추가: --add-data "src;dest"
|
||||
$addArgs += @("--add-data", $pair)
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "APP : $env:APP"
|
||||
Write-Host "ENTRY : $env:ENTRY"
|
||||
Write-Host "EXTRA : $env:EXTRA"
|
||||
Write-Host "ADD_DATA : $env:ADD_DATA"
|
||||
Write-Host "AddArgs : $($addArgs -join ' ')"
|
||||
Write-Host "ExtraArgs: $($extraArgs -join ' ')"
|
||||
|
||||
.\.venv\Scripts\pyinstaller `
|
||||
--name "$env:APP" `
|
||||
@addArgs `
|
||||
@extraArgs `
|
||||
"$env:ENTRY"
|
||||
|
||||
- name: Package zip (onedir dist -> out)
|
||||
shell: powershell
|
||||
env:
|
||||
APP: ${{ vars.APP_NAME }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($env:APP)) { throw "Missing var: APP_NAME" }
|
||||
|
||||
$distDir = Join-Path $PWD "dist\$($env:APP)"
|
||||
if (!(Test-Path $distDir)) { throw "Dist folder not found: $distDir" }
|
||||
|
||||
New-Item -ItemType Directory -Force -Path "out" | Out-Null
|
||||
|
||||
$zipName = "$($env:APP)-$($env:GITHUB_REF_NAME).zip"
|
||||
$zipPath = Join-Path $PWD "out\$zipName"
|
||||
|
||||
if (Test-Path $zipPath) { Remove-Item -Force $zipPath }
|
||||
|
||||
Compress-Archive -Path "$distDir\*" -DestinationPath $zipPath -Force
|
||||
|
||||
if (!(Test-Path $zipPath)) { throw "Zip not created: $zipPath" }
|
||||
|
||||
Write-Host "Created: $zipPath"
|
||||
$zipName | Out-File -FilePath zip_name.txt -Encoding ascii
|
||||
|
||||
# -------------------------
|
||||
# Gitea Release 생성/업로드
|
||||
# -------------------------
|
||||
- name: Create Gitea release (if not exists)
|
||||
shell: powershell
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
REPO: ${{ gitea.repository }}
|
||||
TAG: ${{ gitea.ref_name }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($env:TOKEN)) { throw "Missing secret: GITEA_TOKEN" }
|
||||
if ([string]::IsNullOrWhiteSpace($env:REPO)) { throw "Missing context: gitea.repository" }
|
||||
if ([string]::IsNullOrWhiteSpace($env:TAG)) { throw "Missing tag name" }
|
||||
|
||||
$remote = (git config --get remote.origin.url).Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($remote)) { throw "Cannot determine remote.origin.url" }
|
||||
|
||||
$baseUrl = $null
|
||||
|
||||
if ($remote -match '^(https?)://') {
|
||||
$u = [uri]$remote
|
||||
$path = $u.AbsolutePath -replace '\.git$',''
|
||||
$basePath = ($path -replace '/[^/]+/[^/]+$','')
|
||||
if ([string]::IsNullOrWhiteSpace($basePath) -or $basePath -eq '/') {
|
||||
$baseUrl = "{0}://{1}" -f $u.Scheme, $u.Authority
|
||||
} else {
|
||||
$baseUrl = "{0}://{1}{2}" -f $u.Scheme, $u.Authority, $basePath.TrimEnd('/')
|
||||
}
|
||||
}
|
||||
elseif ($remote -match '^(?<user>[^@]+)@(?<host>[^:]+):(?<path>.+)$') {
|
||||
$host = $Matches.host
|
||||
$path = ($Matches.path -replace '\.git$','') -replace '\\','/'
|
||||
$basePath = ($path -replace '/[^/]+/[^/]+$','')
|
||||
$basePath = $basePath.Trim('/')
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($basePath)) {
|
||||
$baseUrl = "https://$host"
|
||||
} else {
|
||||
$baseUrl = "https://$host/$basePath"
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw "Unsupported remote URL format: $remote"
|
||||
}
|
||||
|
||||
Write-Host "Derived Gitea baseUrl: $baseUrl"
|
||||
|
||||
$apiBase = "$($baseUrl.TrimEnd('/'))/api/v1"
|
||||
$headers = @{ Authorization = "token $env:TOKEN" }
|
||||
|
||||
$rel = $null
|
||||
try {
|
||||
$rel = Invoke-RestMethod -Method Get -Headers $headers -Uri "$apiBase/repos/$env:REPO/releases/tags/$env:TAG"
|
||||
} catch {
|
||||
$rel = $null
|
||||
}
|
||||
|
||||
if (-not $rel) {
|
||||
$body = @{
|
||||
tag_name = $env:TAG
|
||||
name = $env:TAG
|
||||
body = "Automated build for $($env:TAG)"
|
||||
draft = $false
|
||||
prerelease = $false
|
||||
} | ConvertTo-Json -Depth 5
|
||||
|
||||
$rel = Invoke-RestMethod -Method Post -Headers $headers -ContentType "application/json" -Body $body -Uri "$apiBase/repos/$env:REPO/releases"
|
||||
}
|
||||
|
||||
if (-not $rel.id) { throw "Failed to get/create release id" }
|
||||
($rel.id.ToString().Trim()) | Out-File -FilePath release_id.txt -Encoding ascii
|
||||
Write-Host "Release id: $($rel.id)"
|
||||
|
||||
- name: Upload release asset (zip) - replace if exists
|
||||
shell: powershell
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
REPO: ${{ gitea.repository }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($env:TOKEN)) { throw "Missing secret: GITEA_TOKEN" }
|
||||
if ([string]::IsNullOrWhiteSpace($env:REPO)) { throw "Missing context: gitea.repository" }
|
||||
|
||||
if (!(Test-Path "release_id.txt")) { throw "release_id.txt not found (Create release step failed?)" }
|
||||
if (!(Test-Path "zip_name.txt")) { throw "zip_name.txt not found (Zip step failed?)" }
|
||||
|
||||
$releaseId = (Get-Content release_id.txt | Out-String).Trim()
|
||||
$zipName = (Get-Content zip_name.txt | Out-String).Trim()
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($releaseId)) { throw "releaseId is empty after reading release_id.txt" }
|
||||
if ([string]::IsNullOrWhiteSpace($zipName)) { throw "zipName is empty after reading zip_name.txt" }
|
||||
|
||||
if (!(Test-Path "out\$zipName")) { throw "Zip file not found: out\$zipName" }
|
||||
|
||||
$remote = (git config --get remote.origin.url).Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($remote)) { throw "Cannot determine remote.origin.url" }
|
||||
|
||||
$baseUrl = $null
|
||||
|
||||
if ($remote -match '^(https?)://') {
|
||||
$u = [uri]$remote
|
||||
$path = $u.AbsolutePath -replace '\.git$',''
|
||||
$basePath = ($path -replace '/[^/]+/[^/]+$','')
|
||||
if ([string]::IsNullOrWhiteSpace($basePath) -or $basePath -eq '/') {
|
||||
$baseUrl = "{0}://{1}" -f $u.Scheme, $u.Authority
|
||||
} else {
|
||||
$baseUrl = "{0}://{1}{2}" -f $u.Scheme, $u.Authority, $basePath.TrimEnd('/')
|
||||
}
|
||||
}
|
||||
elseif ($remote -match '^(?<user>[^@]+)@(?<host>[^:]+):(?<path>.+)$') {
|
||||
$host = $Matches.host
|
||||
$path = ($Matches.path -replace '\.git$','') -replace '\\','/'
|
||||
$basePath = ($path -replace '/[^/]+/[^/]+$','')
|
||||
$basePath = $basePath.Trim('/')
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($basePath)) {
|
||||
$baseUrl = "https://$host"
|
||||
} else {
|
||||
$baseUrl = "https://$host/$basePath"
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw "Unsupported remote URL format: $remote"
|
||||
}
|
||||
|
||||
Write-Host "Derived Gitea baseUrl: $baseUrl"
|
||||
|
||||
$apiBase = "$($baseUrl.TrimEnd('/'))/api/v1"
|
||||
$headers = @{ Authorization = "token $env:TOKEN" }
|
||||
|
||||
$assets = Invoke-RestMethod -Method Get -Headers $headers -Uri "$apiBase/repos/$env:REPO/releases/$releaseId/assets"
|
||||
foreach ($a in $assets) {
|
||||
if ($a.name -eq $zipName) {
|
||||
Write-Host "Deleting existing asset: $zipName (id=$($a.id))"
|
||||
Invoke-RestMethod -Method Delete -Headers $headers -Uri "$apiBase/repos/$env:REPO/releases/$releaseId/assets/$($a.id)"
|
||||
}
|
||||
}
|
||||
|
||||
$escaped = [uri]::EscapeDataString($zipName)
|
||||
$uploadUrl = "{0}/repos/{1}/releases/{2}/assets?name={3}" -f `
|
||||
$apiBase.TrimEnd('/'), $env:REPO, $releaseId, $escaped
|
||||
|
||||
Write-Host ("Uploading to: [{0}]" -f $uploadUrl)
|
||||
|
||||
& curl.exe -L --fail -X POST `
|
||||
-H "Authorization: token $env:TOKEN" `
|
||||
-H "Content-Type: application/octet-stream" `
|
||||
--data-binary "@out/$zipName" `
|
||||
"$uploadUrl"
|
||||
|
||||
if ($LASTEXITCODE -ne 0) { throw "curl upload failed: $zipName (exit=$LASTEXITCODE)" }
|
||||
|
||||
# -------------------------
|
||||
# Nextcloud(WebDAV) 업로드: 버전 폴더 + latest
|
||||
# -------------------------
|
||||
- name: Upload zip to Nextcloud Releases (WebDAV) - version folder + latest
|
||||
shell: powershell
|
||||
env:
|
||||
NC_USER: ${{ secrets.NEXTCLOUD_USER }}
|
||||
NC_PASS: ${{ secrets.NEXTCLOUD_PASS }}
|
||||
NC_BASE: ${{ secrets.NEXTCLOUD_BASE }} # .../Releases
|
||||
APP: ${{ vars.APP_NAME }} # pop_dis
|
||||
TAG: ${{ gitea.ref_name }} # v2.5.10
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($env:NC_USER)) { throw "Missing secret: NEXTCLOUD_USER" }
|
||||
if ([string]::IsNullOrWhiteSpace($env:NC_PASS)) { throw "Missing secret: NEXTCLOUD_PASS" }
|
||||
if ([string]::IsNullOrWhiteSpace($env:NC_BASE)) { throw "Missing secret: NEXTCLOUD_BASE" }
|
||||
if ([string]::IsNullOrWhiteSpace($env:APP)) { throw "Missing var: APP_NAME" }
|
||||
if ([string]::IsNullOrWhiteSpace($env:TAG)) { throw "Missing tag name" }
|
||||
|
||||
if (!(Test-Path "zip_name.txt")) { throw "zip_name.txt not found (Zip step failed?)" }
|
||||
$zipName = (Get-Content zip_name.txt | Out-String).Trim()
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($zipName)) { throw "zipName is empty after reading zip_name.txt" }
|
||||
if (!(Test-Path "out\$zipName")) { throw "Zip file not found: out\$zipName" }
|
||||
|
||||
$base = $env:NC_BASE.TrimEnd('/')
|
||||
|
||||
$appPath = $env:APP.Trim('/')
|
||||
$tagPath = $env:TAG.Trim('/')
|
||||
$verDirUrl = "$base/$appPath/$tagPath"
|
||||
$latestDirUrl = "$base/$appPath/latest"
|
||||
|
||||
$latestName = "$($env:APP).zip"
|
||||
$verFileUrl = "$verDirUrl/$zipName"
|
||||
$latestUrl = "$latestDirUrl/$latestName"
|
||||
|
||||
Write-Host "Nextcloud base : $base"
|
||||
Write-Host "Version dir URL : $verDirUrl"
|
||||
Write-Host "Latest dir URL : $latestDirUrl"
|
||||
Write-Host "Version file URL : $verFileUrl"
|
||||
Write-Host "Latest file URL : $latestUrl"
|
||||
|
||||
function MkcolIgnoreFail([string]$url) {
|
||||
& curl.exe -L -sS -o NUL -w "%{http_code}" -X MKCOL `
|
||||
-u "$($env:NC_USER):$($env:NC_PASS)" `
|
||||
"$url" | ForEach-Object {
|
||||
Write-Host "MKCOL $url -> HTTP $_"
|
||||
}
|
||||
}
|
||||
|
||||
MkcolIgnoreFail "$base/$appPath"
|
||||
MkcolIgnoreFail "$base/$appPath/$tagPath"
|
||||
MkcolIgnoreFail "$base/$appPath/latest"
|
||||
|
||||
Write-Host "Uploading versioned zip -> $verFileUrl"
|
||||
& curl.exe -L --fail -X PUT `
|
||||
-u "$($env:NC_USER):$($env:NC_PASS)" `
|
||||
--data-binary "@out/$zipName" `
|
||||
"$verFileUrl"
|
||||
if ($LASTEXITCODE -ne 0) { throw "Nextcloud upload failed (versioned) (exit=$LASTEXITCODE)" }
|
||||
|
||||
Write-Host "Uploading latest zip -> $latestUrl"
|
||||
& curl.exe -L --fail -X PUT `
|
||||
-u "$($env:NC_USER):$($env:NC_PASS)" `
|
||||
--data-binary "@out/$zipName" `
|
||||
"$latestUrl"
|
||||
if ($LASTEXITCODE -ne 0) { throw "Nextcloud upload failed (latest) (exit=$LASTEXITCODE)" }
|
||||
Reference in New Issue
Block a user