Files
GitLite/.gitea/workflows/app_builder.yaml
T
Christoph 927e0c3e92
publish / Build and publish Windows installer (release) Failing after 14m48s
publish / Build and publish Ubuntu AppImage (release) Successful in 19m53s
publish / Build and publish AUR packages (release) Failing after 46m10s
feat(workflows): attach Arch package to Gitea release
Add a CI step that finds the built Arch binary and attaches it
to the matching Gitea release using the repository API. The step
validates that the package was created, requires a Gitea token,
and skips upload if the asset already exists to avoid duplicates.

- Export artifact path to the environment and validate its presence
- Query the release to obtain its ID and upload the asset if missing
2026-08-15 19:48:17 +02:00

489 lines
19 KiB
YAML

name: "publish"
on:
release:
types: [published]
jobs:
publish-windows:
name: Build and publish Windows installer
permissions:
contents: write
environment: production
env:
# --- Gitea (unchanged) ---
GITEA_API: https://git.cbsk-tech.de/api/v1
OWNER: Christoph
REPO: GitLite
GITEA_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
GITEA_FALLBACK_TOKEN: ${{ github.token }}
# --- MinIO / S3-compatible upload ---
MINIO_ENDPOINT: ${{ vars.MINIO_ENDPOINT }} # e.g. https://updates.example.com
S3_BUCKET: ${{ vars.S3_BUCKET }} # e.g. updates
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }} # used by aws cli
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }} # used by aws cli
ARTIFACT_BASE_URL: ${{ vars.ARTIFACT_BASE_URL }} # e.g. https://updates.example.com
S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} # used by cicd_tool
S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} # used by cicd_tool
defaults:
run:
working-directory: GitLite
runs-on: windows-latest
steps:
# cicd_tool/main.py expects the repo at <workspace>/GitLite, so check out there.
- uses: actions/checkout@v5
with:
path: GitLite
- name: setup node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: install frontend dependencies
run: npm ci
- name: Update package.json Version
run: npm version ${{ github.ref_name }} --no-git-tag-version --allow-same-version
- name: Update tauri.conf.json Version
env:
RELEASE_VERSION: ${{ github.ref_name }}
run: |
node -e "const fs=require('fs'); const path='src-tauri/tauri.conf.json'; const config=JSON.parse(fs.readFileSync(path,'utf8')); config.version=process.env.RELEASE_VERSION; fs.writeFileSync(path, JSON.stringify(config,null,2)+'\n');"
- name: Commit and Push Changes
shell: powershell
run: |
git config user.name '${{ vars.USERNAME_GIT }}'
git config user.email '${{ vars.EMAIL_GIT }}'
# npm version also bumps the version inside package-lock.json, so stage it
# too -- otherwise it stays as an unstaged change and blocks the rebase.
git add package.json package-lock.json src-tauri/tauri.conf.json
git commit -m 'Update version to ${{ github.ref_name }}'
if ($LASTEXITCODE -ne 0) {
# Version files already match the tag -> nothing to push, succeed quietly.
Write-Host 'No version changes to commit; skipping push.'
exit 0
}
# The release build runs in detached HEAD on the tag. Resolve the branch
# that should receive the version bump.
$branch = '${{ github.event.release.target_commitish }}'
if ([string]::IsNullOrEmpty($branch) -or $branch -match '^[0-9a-f]{40}$') {
$branch = (
git branch -r --contains $env:GITHUB_SHA |
ForEach-Object { $_.Trim() } |
Where-Object { $_ -and ($_ -notmatch '->') } |
ForEach-Object { $_ -replace '^(?:remotes/)?[^/]+/', '' } |
Select-Object -First 1
)
}
if ([string]::IsNullOrEmpty($branch)) {
Write-Host 'Could not determine branch for push.'
git branch -a
exit 1
}
# The tag may be behind the branch tip. Replay the bump commit on top of
# the current remote branch so the push is a clean fast-forward.
git fetch origin $branch
git rebase "origin/$branch"
if ($LASTEXITCODE -ne 0) {
git rebase --abort
Write-Host "Rebase onto origin/$branch failed."
exit 1
}
git push origin "HEAD:refs/heads/$branch"
- name: Build Tauri App
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD}}
NO_STRIP: ""
run: npm run tauri -- build --bundles nsis
# ----------------------
# Upload to MinIO (via mc) and create latest.json
# ----------------------
- name: Upload artifacts to MinIO with cicd_tool
working-directory: GitLite/cicd_tool
env:
PUBLISH_PLATFORM: windows-x86_64
run: |
uv sync
uv run main.py
publish-arch:
name: Build and publish AUR packages
needs: publish-ubuntu
permissions:
contents: write
runs-on: archlinux
environment: production
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
ARTIFACT_BASE_URL: ${{ vars.ARTIFACT_BASE_URL }}
GITEA_API: https://git.cbsk-tech.de/api/v1
OWNER: Christoph
REPO: GitLite
GITEA_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
GITEA_FALLBACK_TOKEN: ${{ github.token }}
defaults:
run:
working-directory: GitLite
steps:
- name: Prepare Arch job container
working-directory: /
run: |
set -euo pipefail
# The runner image may contain only a single geo mirror. Seed several
# currently active HTTPS mirrors and retain the image's list as a
# fallback so one slow CDN cannot fail the complete release.
ARCH_MIRROR_BACKUP="$(mktemp)"
cp /etc/pacman.d/mirrorlist "$ARCH_MIRROR_BACKUP"
printf '%s\n' \
'Server = https://frankfurt.mirror.pkgbuild.com/$repo/os/$arch' \
'Server = https://mirror.osbeck.com/archlinux/$repo/os/$arch' \
'Server = https://mirror.ubrco.de/archlinux/$repo/os/$arch' \
'Server = https://ftp.halifax.rwth-aachen.de/archlinux/$repo/os/$arch' \
'Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch' \
> /etc/pacman.d/mirrorlist
sed -n '/^[[:space:]]*Server[[:space:]]*=/p' "$ARCH_MIRROR_BACKUP" \
>> /etc/pacman.d/mirrorlist
ARCH_PACKAGES=(
base-devel curl git nodejs npm openssh rust
webkit2gtk-4.1 gtk3 hicolor-icon-theme
libappindicator-gtk3 librsvg xdotool
)
for ARCH_INSTALL_ATTEMPT in 1 2 3; do
echo "pacman attempt $ARCH_INSTALL_ATTEMPT of 3"
timeout --signal=TERM 30m \
pacman -Syu --needed --noconfirm --noprogressbar \
"${ARCH_PACKAGES[@]}" \
</dev/null &
ARCH_PACMAN_PID=$!
(
ARCH_WAIT_SECONDS=0
while true; do
sleep 30
((ARCH_WAIT_SECONDS += 30))
echo "pacman is still running (${ARCH_WAIT_SECONDS}s elapsed)"
done
) &
ARCH_HEARTBEAT_PID=$!
ARCH_PACMAN_STATUS=0
wait "$ARCH_PACMAN_PID" || ARCH_PACMAN_STATUS=$?
kill "$ARCH_HEARTBEAT_PID" 2>/dev/null || true
wait "$ARCH_HEARTBEAT_PID" 2>/dev/null || true
if [ "$ARCH_PACMAN_STATUS" -eq 0 ]; then
break
fi
if [ "$ARCH_PACMAN_STATUS" -eq 124 ]; then
echo "pacman attempt timed out after 30 minutes" >&2
fi
if [ "$ARCH_INSTALL_ATTEMPT" -eq 3 ]; then
echo "pacman failed after 3 attempts" >&2
exit 1
fi
sleep "$((ARCH_INSTALL_ATTEMPT * 5))"
done
- uses: actions/checkout@v5
with:
path: GitLite
- name: Set release version
env:
RELEASE_VERSION: ${{ github.ref_name }}
run: |
npm version "$RELEASE_VERSION" --no-git-tag-version --allow-same-version
PACKAGE_VERSION="$(node -p "require('./package.json').version")"
echo "PACKAGE_VERSION=$PACKAGE_VERSION" >> "$GITHUB_ENV"
echo "RELEASE_TAG=$RELEASE_VERSION" >> "$GITHUB_ENV"
- name: Generate and build AUR package
run: |
if ! id -u builder >/dev/null 2>&1; then
useradd --create-home builder
fi
AUR_SOURCE_DIR="$(mktemp -d)"
cp PKGBUILD "$AUR_SOURCE_DIR/PKGBUILD"
SOURCE_ARCHIVE="$AUR_SOURCE_DIR/gitty-desktop-$PACKAGE_VERSION.tar.gz"
curl --fail --location --silent --show-error \
--output "$SOURCE_ARCHIVE" \
"https://git.cbsk-tech.de/Christoph/GitLite/archive/$RELEASE_TAG.tar.gz"
CHECKSUM="$(sha256sum "$SOURCE_ARCHIVE" | cut -d ' ' -f 1)"
sed -i \
-e "s/^pkgver=.*/pkgver=$PACKAGE_VERSION/" \
-e "s/^pkgrel=.*/pkgrel=1/" \
-e "s/^_tag=.*/_tag=$RELEASE_TAG/" \
-e "s/^sha256sums=.*/sha256sums=('$CHECKSUM')/" \
"$AUR_SOURCE_DIR/PKGBUILD"
chown -R builder:builder "$AUR_SOURCE_DIR"
runuser -u builder -- \
bash -lc "cd '$AUR_SOURCE_DIR' && makepkg --cleanbuild --noconfirm && makepkg --printsrcinfo > .SRCINFO"
echo "AUR_SOURCE_DIR=$AUR_SOURCE_DIR" >> "$GITHUB_ENV"
- name: Generate and build binary AUR package
run: |
set -euo pipefail
LATEST_JSON="$(curl --fail --location --silent --show-error \
"$ARTIFACT_BASE_URL/gitty/latest.json")"
export LATEST_JSON
APPIMAGE_URL="$(node -e "const p=JSON.parse(process.env.LATEST_JSON); const a=p.platforms?.['linux-x86_64']?.url; if (!a) process.exit(1); process.stdout.write(a)")"
APPIMAGE_NAME="${APPIMAGE_URL##*/}"
AUR_BIN_DIR="$(mktemp -d)"
cp PKGBUILD-bin "$AUR_BIN_DIR/PKGBUILD"
APPIMAGE_PATH="$AUR_BIN_DIR/$APPIMAGE_NAME"
curl --fail --location --silent --show-error \
--output "$APPIMAGE_PATH" "$APPIMAGE_URL"
APPIMAGE_CHECKSUM="$(sha256sum "$APPIMAGE_PATH" | cut -d ' ' -f 1)"
ICON_PATH="$AUR_BIN_DIR/gitty-desktop.png"
curl --fail --location --silent --show-error \
--output "$ICON_PATH" \
"https://git.cbsk-tech.de/Christoph/GitLite/raw/tag/$RELEASE_TAG/src-tauri/icons/icon.png"
ICON_CHECKSUM="$(sha256sum "$ICON_PATH" | cut -d ' ' -f 1)"
sed -i \
-e "s/^pkgver=.*/pkgver=$PACKAGE_VERSION/" \
-e "s/^pkgrel=.*/pkgrel=1/" \
-e "s|^_appimage=.*|_appimage=\"$APPIMAGE_NAME\"|" \
-e "s|^_artifact_url=.*|_artifact_url=\"$APPIMAGE_URL\"|" \
-e "s/^_tag=.*/_tag=$RELEASE_TAG/" \
-e "/^sha256sums=/,/^[[:space:]]*'SKIP')$/c\\sha256sums=('$APPIMAGE_CHECKSUM'\\n '$ICON_CHECKSUM')" \
"$AUR_BIN_DIR/PKGBUILD"
chown -R builder:builder "$AUR_BIN_DIR"
runuser -u builder -- \
bash -lc "cd '$AUR_BIN_DIR' && makepkg --cleanbuild --noconfirm && makepkg --printsrcinfo > .SRCINFO"
ARCH_BIN_PACKAGE_PATH="$(find "$AUR_BIN_DIR" -maxdepth 1 -type f -name 'gitty-desktop-bin-*.pkg.tar.zst' -print -quit)"
if [ -z "$ARCH_BIN_PACKAGE_PATH" ]; then
echo "The binary Arch package was not created" >&2
exit 1
fi
echo "AUR_BIN_DIR=$AUR_BIN_DIR" >> "$GITHUB_ENV"
echo "ARCH_BIN_PACKAGE_PATH=$ARCH_BIN_PACKAGE_PATH" >> "$GITHUB_ENV"
- name: Attach binary Arch package to Gitea release
run: |
set -euo pipefail
TOKEN="${GITEA_TOKEN:-${GITEA_FALLBACK_TOKEN:-}}"
if [ -z "$TOKEN" ]; then
echo "A Gitea token is required to upload the Arch package" >&2
exit 1
fi
RELEASE_JSON="$(curl --fail --location --silent --show-error \
--header "Authorization: token $TOKEN" \
"$GITEA_API/repos/$OWNER/$REPO/releases/tags/$RELEASE_TAG")"
export RELEASE_JSON
RELEASE_ID="$(node -e "const r=JSON.parse(process.env.RELEASE_JSON); if (!r.id) process.exit(1); process.stdout.write(String(r.id))")"
PACKAGE_NAME="${ARCH_BIN_PACKAGE_PATH##*/}"
export PACKAGE_NAME
if node -e "const r=JSON.parse(process.env.RELEASE_JSON); process.exit((r.assets || []).some(a => a.name === process.env.PACKAGE_NAME) ? 0 : 1)"; then
echo "Release asset $PACKAGE_NAME already exists; skipping upload."
else
curl --fail --location --silent --show-error \
--request POST \
--header "Authorization: token $TOKEN" \
--form "attachment=@$ARCH_BIN_PACKAGE_PATH;type=application/octet-stream" \
"$GITEA_API/repos/$OWNER/$REPO/releases/$RELEASE_ID/assets?name=$PACKAGE_NAME"
echo
echo "Attached $PACKAGE_NAME to Gitea release $RELEASE_TAG."
fi
- name: Publish PKGBUILD to AUR
env:
AUR_GIT_NAME: ${{ vars.USERNAME_GIT }}
AUR_GIT_EMAIL: ${{ vars.EMAIL_GIT }}
run: |
set -euo pipefail
if [ -z "$AUR_SSH_PRIVATE_KEY" ]; then
echo "AUR_SSH_PRIVATE_KEY is required" >&2
exit 1
fi
install -d -m 0700 "$HOME/.ssh"
printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > "$HOME/.ssh/aur"
chmod 0600 "$HOME/.ssh/aur"
AUR_RETRY_DELAYS=(30 60 120 240)
AUR_KNOWN_HOSTS_TEMP="$(mktemp)"
AUR_HOST_KEY_READY=0
for AUR_ATTEMPT in 1 2 3 4 5; do
echo "AUR host-key attempt $AUR_ATTEMPT of 5"
if timeout 30s ssh-keyscan -T 15 -H aur.archlinux.org > "$AUR_KNOWN_HOSTS_TEMP" \
&& [ -s "$AUR_KNOWN_HOSTS_TEMP" ]; then
AUR_HOST_KEY_READY=1
break
fi
if [ "$AUR_ATTEMPT" -lt 5 ]; then
sleep "${AUR_RETRY_DELAYS[$((AUR_ATTEMPT - 1))]}"
fi
done
if [ "$AUR_HOST_KEY_READY" -ne 1 ]; then
echo "Could not retrieve the AUR SSH host key after 5 attempts" >&2
exit 1
fi
install -m 0600 "$AUR_KNOWN_HOSTS_TEMP" "$HOME/.ssh/known_hosts"
export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/aur -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=30 -o ServerAliveInterval=15 -o ServerAliveCountMax=2"
publish_aur_package() {
local package_name="$1"
local package_source_dir="$2"
local checkout_root checkout clone_candidate pushed
checkout_root="$(mktemp -d)"
checkout=""
for AUR_ATTEMPT in 1 2 3 4 5; do
clone_candidate="$checkout_root/attempt-$AUR_ATTEMPT"
echo "$package_name clone attempt $AUR_ATTEMPT of 5"
if timeout 3m git -c init.defaultBranch=master clone \
"ssh://aur@aur.archlinux.org/$package_name.git" "$clone_candidate"; then
checkout="$clone_candidate"
break
fi
if [ "$AUR_ATTEMPT" -lt 5 ]; then
echo "AUR is unavailable; retrying after backoff"
sleep "${AUR_RETRY_DELAYS[$((AUR_ATTEMPT - 1))]}"
fi
done
if [ -z "$checkout" ]; then
echo "Could not clone $package_name after 5 attempts" >&2
return 1
fi
cp "$package_source_dir/PKGBUILD" "$package_source_dir/.SRCINFO" "$checkout/"
git -C "$checkout" config user.name "${AUR_GIT_NAME:-Gitty Release Bot}"
git -C "$checkout" config user.email "${AUR_GIT_EMAIL:-aur@localhost}"
git -C "$checkout" add PKGBUILD .SRCINFO
if git -C "$checkout" diff --cached --quiet; then
echo "$package_name already matches release $PACKAGE_VERSION"
return 0
fi
git -C "$checkout" commit -m "Update to $PACKAGE_VERSION"
pushed=0
for AUR_ATTEMPT in 1 2 3 4 5; do
echo "$package_name push attempt $AUR_ATTEMPT of 5"
if timeout 3m git -C "$checkout" push origin HEAD:master; then
pushed=1
break
fi
if [ "$AUR_ATTEMPT" -lt 5 ]; then
echo "AUR push failed; retrying after backoff"
sleep "${AUR_RETRY_DELAYS[$((AUR_ATTEMPT - 1))]}"
fi
done
if [ "$pushed" -ne 1 ]; then
echo "Could not publish $package_name after 5 attempts" >&2
return 1
fi
}
publish_aur_package gitty-desktop "$AUR_SOURCE_DIR"
publish_aur_package gitty-desktop-bin "$AUR_BIN_DIR"
publish-ubuntu:
name: Build and publish Ubuntu AppImage
permissions:
contents: write
environment: production
env:
GITEA_API: https://git.cbsk-tech.de/api/v1
OWNER: Christoph
REPO: GitLite
GITEA_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
GITEA_FALLBACK_TOKEN: ${{ github.token }}
MINIO_ENDPOINT: ${{ vars.MINIO_ENDPOINT }}
S3_BUCKET: ${{ vars.S3_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }}
ARTIFACT_BASE_URL: ${{ vars.ARTIFACT_BASE_URL }}
S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
defaults:
run:
working-directory: GitLite
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5
with:
path: GitLite
- name: Install Ubuntu dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential curl wget file libssl-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf libxdo-dev libfuse2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install frontend dependencies
run: npm ci
- name: Update package.json version
run: npm version ${{ github.ref_name }} --no-git-tag-version --allow-same-version
- name: Update tauri.conf.json version
env:
RELEASE_VERSION: ${{ github.ref_name }}
run: |
node -e "const fs=require('fs'); const path='src-tauri/tauri.conf.json'; const config=JSON.parse(fs.readFileSync(path,'utf8')); config.version=process.env.RELEASE_VERSION; fs.writeFileSync(path, JSON.stringify(config,null,2)+'\n');"
- name: Build Tauri App
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD}}
NO_STRIP: "1"
run: npm run tauri -- build --bundles appimage
- name: Upload artifacts to MinIO with cicd_tool
working-directory: GitLite/cicd_tool
env:
PUBLISH_PLATFORM: linux-x86_64
run: |
uv sync
uv run main.py