mirror of
https://github.com/TommyFang2077/dsh-desktop.git
synced 2026-08-17 09:06:36 +08:00
feat: publish Gitea releases via Gitea Actions runner
This commit is contained in:
25
.gitea/workflows/publish.yml
Normal file
25
.gitea/workflows/publish.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
name: publish-gitea
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "GitHub release tag to publish (default: latest)"
|
||||
required: false
|
||||
default: latest
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: [self-hosted, linux, x64]
|
||||
steps:
|
||||
- name: Download publish scripts
|
||||
run: |
|
||||
BASE=https://raw.githubusercontent.com/TommyFang2077/dsh-easy-desktop/main
|
||||
curl -fsSL -o build-gitea-update.py "$BASE/scripts/build-gitea-update.py"
|
||||
curl -fsSL -o publish-gitea-release.sh "$BASE/scripts/publish-gitea-release.sh"
|
||||
curl -fsSL -o publish-gitea-actions.sh "$BASE/scripts/publish-gitea-actions.sh"
|
||||
- name: Publish to Gitea
|
||||
run: bash publish-gitea-actions.sh
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
GITEA_BASE_URL: http://192.168.30.33:3000
|
||||
RELEASE_TAG: ${{ github.event.inputs.tag }}
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
@@ -39,6 +40,27 @@ INSTALLER_KIND = {
|
||||
"linux": "appimage",
|
||||
"windows": "nsis",
|
||||
}
|
||||
# Asset-name patterns for GitHub Release downloads (flat directory): maps a
|
||||
# fileName to its canonical (os, arch, deliverable suffix).
|
||||
FLAT_PATTERNS: list[tuple[re.Pattern, str, str, str]] = [
|
||||
(re.compile(r"^.*_aarch64\.app\.tar\.gz\.sig$"), "darwin", "aarch64", ".app.tar.gz.sig"),
|
||||
(re.compile(r"^.*_aarch64\.app\.tar\.gz$"), "darwin", "aarch64", ".app.tar.gz"),
|
||||
(re.compile(r"^.*_aarch64\.dmg$"), "darwin", "aarch64", ".dmg"),
|
||||
(re.compile(r"^.*_x64\.app\.tar\.gz\.sig$"), "darwin", "x86_64", ".app.tar.gz.sig"),
|
||||
(re.compile(r"^.*_x64\.app\.tar\.gz$"), "darwin", "x86_64", ".app.tar.gz"),
|
||||
(re.compile(r"^.*_x64\.dmg$"), "darwin", "x86_64", ".dmg"),
|
||||
(re.compile(r"^.*_amd64\.deb\.sig$"), "linux", "x86_64", ".deb.sig"),
|
||||
(re.compile(r"^.*_amd64\.deb$"), "linux", "x86_64", ".deb"),
|
||||
(re.compile(r"^.*_amd64\.AppImage\.sig$"), "linux", "x86_64", ".AppImage.sig"),
|
||||
(re.compile(r"^.*_amd64\.AppImage$"), "linux", "x86_64", ".AppImage"),
|
||||
(re.compile(r"^.*?\.x86_64\.rpm\.sig$"), "linux", "x86_64", ".rpm.sig"),
|
||||
(re.compile(r"^.*?\.x86_64\.rpm$"), "linux", "x86_64", ".rpm"),
|
||||
(re.compile(r"^.*_x64-setup\.exe\.sig$"), "windows", "x86_64", ".exe.sig"),
|
||||
(re.compile(r"^.*_x64-setup\.exe$"), "windows", "x86_64", ".exe"),
|
||||
(re.compile(r"^.*_x64_en-US\.msi\.sig$"), "windows", "x86_64", ".msi.sig"),
|
||||
(re.compile(r"^.*_x64_en-US\.msi$"), "windows", "x86_64", ".msi"),
|
||||
(re.compile(r"^.*\.flatpak$"), "linux", "x86_64", ".flatpak"),
|
||||
]
|
||||
|
||||
|
||||
def artifact_target(path: Path, root: Path) -> tuple[str, str] | None:
|
||||
@@ -73,6 +95,28 @@ def stage_artifacts(source: Path, output: Path, version: str) -> dict[tuple[str,
|
||||
return staged
|
||||
|
||||
|
||||
def stage_flat_artifacts(source: Path, output: Path, version: str) -> dict[tuple[str, str, str], Path]:
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
staged: dict[tuple[str, str, str], Path] = {}
|
||||
for path in sorted(source.iterdir()):
|
||||
if not path.is_file():
|
||||
continue
|
||||
match = next(
|
||||
((pattern, os_name, arch, suffix) for pattern, os_name, arch, suffix in FLAT_PATTERNS if pattern.match(path.name)),
|
||||
None,
|
||||
)
|
||||
if match is None:
|
||||
continue
|
||||
_, os_name, arch, suffix = match
|
||||
key = (os_name, arch, suffix)
|
||||
if key in staged:
|
||||
raise ValueError(f"duplicate {os_name}-{arch}{suffix}: {staged[key]} and {path}")
|
||||
destination = output / f"dsh-easy-desktop_{version}_{os_name}_{arch}{suffix}"
|
||||
shutil.copy2(path, destination)
|
||||
staged[key] = destination
|
||||
return staged
|
||||
|
||||
|
||||
def build_manifest(
|
||||
staged: dict[tuple[str, str, str], Path],
|
||||
version: str,
|
||||
@@ -109,7 +153,8 @@ def build_manifest(
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--artifacts", required=True, type=Path)
|
||||
parser.add_argument("--artifacts", type=Path, help="matrix-layout artifact directory")
|
||||
parser.add_argument("--flat-artifacts", type=Path, help="flat GitHub Release asset directory")
|
||||
parser.add_argument("--output", required=True, type=Path)
|
||||
parser.add_argument("--version", required=True)
|
||||
parser.add_argument("--package-base-url", required=True)
|
||||
@@ -117,7 +162,12 @@ def main() -> None:
|
||||
parser.add_argument("--pub-date")
|
||||
args = parser.parse_args()
|
||||
|
||||
staged = stage_artifacts(args.artifacts, args.output, args.version)
|
||||
if (args.artifacts is None) == (args.flat_artifacts is None):
|
||||
raise SystemExit("exactly one of --artifacts or --flat-artifacts is required")
|
||||
if args.artifacts is not None:
|
||||
staged = stage_artifacts(args.artifacts, args.output, args.version)
|
||||
else:
|
||||
staged = stage_flat_artifacts(args.flat_artifacts, args.output, args.version)
|
||||
manifest = build_manifest(
|
||||
staged,
|
||||
args.version,
|
||||
|
||||
50
scripts/publish-gitea-actions.sh
Executable file
50
scripts/publish-gitea-actions.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# Gitea Actions publisher: download a GitHub release's assets and publish
|
||||
# them to the local Gitea instance over the LAN interface (bypassing the
|
||||
# public reverse proxy, which times out on large uploads).
|
||||
set -euo pipefail
|
||||
|
||||
GITHUB_REPO="${GITHUB_REPO:-TommyFang2077/dsh-easy-desktop}"
|
||||
REPO_OWNER="${GITEA_OWNER:-TomHanck4}"
|
||||
REPO_NAME="${GITEA_REPO:-dsh-easy-desktop}"
|
||||
GITEA_BASE_URL="${GITEA_BASE_URL:-http://192.168.30.33:3000}"
|
||||
PACKAGE_BASE_URL="${PACKAGE_BASE_URL:-https://git.fangsiyuan.top/api/packages/TomHanck4/generic/dsh-easy-desktop-updater}"
|
||||
RELEASE_TAG="${RELEASE_TAG:-latest}"
|
||||
: "${GITEA_TOKEN:?GITEA_TOKEN is required}"
|
||||
|
||||
if [[ "$RELEASE_TAG" == "latest" ]]; then
|
||||
RELEASE_TAG=$(curl -fsS "https://api.github.com/repos/$GITHUB_REPO/releases?per_page=1" | jq -r '.[0].tag_name')
|
||||
fi
|
||||
[[ -n "$RELEASE_TAG" && "$RELEASE_TAG" != "null" ]] || {
|
||||
echo "no GitHub release found" >&2
|
||||
exit 1
|
||||
}
|
||||
VERSION="${RELEASE_TAG#v}"
|
||||
echo "target: $RELEASE_TAG ($VERSION)"
|
||||
echo "gitea: $GITEA_BASE_URL"
|
||||
|
||||
if curl -fsS -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_BASE_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/releases/tags/$RELEASE_TAG" >/dev/null 2>&1; then
|
||||
echo "release $RELEASE_TAG already published on Gitea; nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
rm -rf artifacts staged
|
||||
mkdir -p artifacts
|
||||
|
||||
curl -fsS "https://api.github.com/repos/$GITHUB_REPO/releases/tags/$RELEASE_TAG" -o /tmp/release.json
|
||||
jq -r '.assets[].browser_download_url' /tmp/release.json | while read -r url; do
|
||||
curl -fsSL --retry 3 --retry-delay 5 -o "artifacts/$(basename "$url")" "$url" &
|
||||
done
|
||||
wait
|
||||
echo "downloaded $(find artifacts -type f | wc -l) assets"
|
||||
|
||||
python3 build-gitea-update.py \
|
||||
--flat-artifacts artifacts \
|
||||
--output staged \
|
||||
--version "$VERSION" \
|
||||
--pub-date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
--package-base-url "$PACKAGE_BASE_URL"
|
||||
|
||||
bash publish-gitea-release.sh staged
|
||||
echo "published $RELEASE_TAG to Gitea"
|
||||
@@ -68,5 +68,68 @@ class GiteaUpdateManifestTests(unittest.TestCase):
|
||||
self.assertTrue(windows["url"].endswith("/1.2.3/dsh-easy-desktop_1.2.3_windows_x86_64.exe"))
|
||||
|
||||
|
||||
def test_normalizes_flat_github_assets(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
artifacts = root / "artifacts"
|
||||
output = root / "staged"
|
||||
artifacts.mkdir()
|
||||
fixtures = {
|
||||
"DeepSeek.Harness_0.1.1_aarch64.app.tar.gz": "darwin-aarch64",
|
||||
"DeepSeek.Harness_0.1.1_aarch64.app.tar.gz.sig": "darwin-aarch64",
|
||||
"DeepSeek.Harness_0.1.1_aarch64.dmg": "darwin-aarch64",
|
||||
"DeepSeek.Harness_0.1.1_x64.app.tar.gz": "darwin-x86_64",
|
||||
"DeepSeek.Harness_0.1.1_x64.app.tar.gz.sig": "darwin-x86_64",
|
||||
"DeepSeek.Harness_0.1.1_x64.dmg": "darwin-x86_64",
|
||||
"DeepSeek.Harness_0.1.1_amd64.deb": "linux-x86_64",
|
||||
"DeepSeek.Harness_0.1.1_amd64.AppImage": "linux-x86_64",
|
||||
"DeepSeek.Harness_0.1.1_amd64.AppImage.sig": "linux-x86_64",
|
||||
"DeepSeek.Harness-0.1.1-1.x86_64.rpm": "linux-x86_64",
|
||||
"DeepSeek.Harness_0.1.1_x64-setup.exe": "windows-x86_64",
|
||||
"DeepSeek.Harness_0.1.1_x64-setup.exe.sig": "windows-x86_64",
|
||||
"DeepSeek.Harness_0.1.1_x64_en-US.msi": "windows-x86_64",
|
||||
"io.github.tommyfang.DshDesktop.flatpak": "linux-x86_64",
|
||||
}
|
||||
for name, marker in fixtures.items():
|
||||
(artifacts / name).write_bytes(marker.encode())
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
"python3",
|
||||
str(SCRIPT),
|
||||
"--flat-artifacts",
|
||||
str(artifacts),
|
||||
"--output",
|
||||
str(output),
|
||||
"--version",
|
||||
"1.2.3",
|
||||
"--package-base-url",
|
||||
"http://gitea.example/api/packages/u/generic/app",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
manifest = json.loads((output / "latest.json").read_text(encoding="utf-8"))
|
||||
self.assertEqual(
|
||||
set(manifest["platforms"]),
|
||||
{
|
||||
"darwin-aarch64-app",
|
||||
"darwin-aarch64",
|
||||
"darwin-x86_64-app",
|
||||
"darwin-x86_64",
|
||||
"linux-x86_64-appimage",
|
||||
"linux-x86_64",
|
||||
"windows-x86_64-nsis",
|
||||
"windows-x86_64",
|
||||
},
|
||||
)
|
||||
# Each updater target embeds its own .sig content; fixed-name
|
||||
# assets (dmg/rpm/deb/flatpak) are deliverables only.
|
||||
self.assertEqual(
|
||||
manifest["platforms"]["linux-x86_64"]["signature"],
|
||||
"linux-x86_64",
|
||||
)
|
||||
self.assertNotIn("linux-x86_64-app", manifest["platforms"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user