name: Nightly releases
on:
schedule:
- cron: "17 2 */2 * *"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: nightly-release
cancel-in-progress: false
env:
NIGHTLY_VERSION: nightly-${{ github.run_id }}-${{ github.run_attempt }}
jobs:
package:
name: Package release
uses: ./.github/workflows/package-release.yml
with:
version: nightly-${{ github.run_id }}-${{ github.run_attempt }}
retention_days: 14
publish:
name: Publish nightly release
needs: package
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
actions: read
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Configure primary repository access
env:
PRIMARY_GIT_SSH_KEY: ${{ secrets.PRIMARY_GIT_SSH_KEY }}
run: |
if [[ -z "$PRIMARY_GIT_SSH_KEY" ]]; then
echo "PRIMARY_GIT_SSH_KEY is not configured" >&2
exit 1
fi
key_file="$RUNNER_TEMP/primary_git_key"
known_hosts_file="$RUNNER_TEMP/primary_git_known_hosts"
install -m 600 /dev/null "$key_file"
printf '%s\n' "$PRIMARY_GIT_SSH_KEY" > "$key_file"
printf '%s\n' 'git.marzeq.foo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPxWUKlEvL387Pa9OF9bFWiDHSI45QXWxHrejac2QpPe' > "$known_hosts_file"
echo "GIT_SSH_COMMAND=ssh -i $key_file -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$known_hosts_file" >> "$GITHUB_ENV"
git remote add primary git@git.marzeq.foo:marzeq/qk.git
- name: Download release archives
uses: actions/download-artifact@v7
with:
pattern: qk-${{ env.NIGHTLY_VERSION }}-*
path: dist
merge-multiple: true
- name: Publish tag through primary mirror
run: |
git tag "$NIGHTLY_VERSION" "$GITHUB_SHA"
git push primary "refs/tags/$NIGHTLY_VERSION"
- name: Wait for mirrored GitHub tag
env:
GH_TOKEN: ${{ github.token }}
run: |
for attempt in {1..12}; do
if gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$NIGHTLY_VERSION" >/dev/null 2>&1; then
exit 0
fi
sleep 5
done
echo "tag $NIGHTLY_VERSION was not mirrored to GitHub within 60 seconds" >&2
exit 1
- name: Publish prerelease
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "$NIGHTLY_VERSION" dist/*.tar.gz \
--repo "$GITHUB_REPOSITORY" \
--title "QK $NIGHTLY_VERSION" \
--notes "Automated build of $GITHUB_SHA from $(date -u +'%Y-%m-%d %H:%M UTC')." \
--prerelease \
--verify-tag
- name: Remove primary tag after publication failure
if: ${{ failure() }}
run: |
if git remote get-url primary >/dev/null 2>&1 &&
git ls-remote --exit-code --tags primary "refs/tags/$NIGHTLY_VERSION" >/dev/null 2>&1; then
git push primary --delete "refs/tags/$NIGHTLY_VERSION"
fi
- name: Keep the latest three nightly releases
env:
GH_TOKEN: ${{ github.token }}
run: |
mapfile -t nightly_releases < <(
gh release list \
--repo "$GITHUB_REPOSITORY" \
--limit 100 \
--json tagName,createdAt,isDraft \
--jq 'map(select(.isDraft == false and (.tagName | startswith("nightly-")))) | sort_by(.createdAt) | reverse | .[].tagName'
)
retained_tags=("${nightly_releases[@]:0:3}")
is_retained() {
local candidate=$1
local retained_tag
for retained_tag in "${retained_tags[@]}"; do
if [[ $candidate == "$retained_tag" ]]; then
return 0
fi
done
return 1
}
delete_github_tag() {
local tag=$1
local error
if ! gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$tag" >/dev/null 2>&1; then
return 0
fi
if error=$(gh api --method DELETE "repos/$GITHUB_REPOSITORY/git/refs/tags/$tag" 2>&1); then
return 0
fi
# Deleting the primary tag is mirrored to GitHub asynchronously.
# Treat a tag that disappeared during the API request as success.
if ! gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$tag" >/dev/null 2>&1; then
return 0
fi
echo "$error" >&2
return 1
}
for tag in "${nightly_releases[@]:3}"; do
gh release delete "$tag" --repo "$GITHUB_REPOSITORY" --yes
if git ls-remote --exit-code --tags primary "refs/tags/$tag" >/dev/null 2>&1; then
git push primary --delete "refs/tags/$tag"
fi
delete_github_tag "$tag"
done
mapfile -t primary_tags < <(
git ls-remote --refs --tags primary 'refs/tags/nightly-*' |
awk '{ sub("^refs/tags/", "", $2); print $2 }'
)
for tag in "${primary_tags[@]}"; do
if ! is_retained "$tag"; then
git push primary --delete "refs/tags/$tag"
fi
done
mapfile -t github_tags < <(
gh api --paginate "repos/$GITHUB_REPOSITORY/git/matching-refs/tags/nightly-" \
--jq '.[] | .ref | sub("^refs/tags/"; "")'
)
for tag in "${github_tags[@]}"; do
if ! is_retained "$tag"; then
delete_github_tag "$tag"
fi
done