Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions .github/workflows/apt-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Reusable APT publication: dispatches package-updated to an APT repository.
#
# channel: unstable — call from main.yml after stage-release.yml, passing its
# prerelease-tag.
# channel: stable — call from release.yml on `release: published`. The job
# skips pre-releases and requires a v<upstream>+<N> tag.
#
# Either way the release must carry assets, so a release staged without
# packages is never dispatched.
#
# The APT repository fetches the .deb assets from this repository's releases.

name: APT publish

on:
workflow_call:
inputs:
channel:
description: 'unstable or stable'
required: true
type: string
apt-repository:
description: 'APT repository to dispatch to (e.g. halos-org/apt.halos.fi)'
required: true
type: string
apt-distro:
description: 'APT distribution (e.g. trixie, any)'
required: true
type: string
apt-component:
description: 'APT component (e.g. main, hatlabs)'
required: true
type: string
prerelease-tag:
description: 'Unstable channel: the pre-release tag from release-version.yml'
required: false
default: ''
type: string
runs-on:
description: 'Runner to use'
required: false
default: 'ubuntu-latest'
type: string
secrets:
APT_REPO_PAT:
description: 'Token allowed to send repository_dispatch to apt-repository'
required: true

permissions:
contents: read

# Run steps with -eo pipefail rather than the implicit bash -e.
defaults:
run:
shell: bash

jobs:
apt-publish:
if: ${{ !(github.event_name == 'release' && github.event.release.prerelease) }}
runs-on: ${{ inputs.runs-on }}
env:
CHANNEL: ${{ inputs.channel }}
PRERELEASE_TAG: ${{ inputs.prerelease-tag }}
steps:
- name: Validate channel
env:
EVENT: ${{ github.event_name }}
run: |
case "$CHANNEL" in
unstable)
if [ -z "$PRERELEASE_TAG" ]; then
echo "::error::The unstable channel needs prerelease-tag"
exit 1
fi
;;
stable)
if [ "$EVENT" != release ]; then
echo "::error::The stable channel is published from a release event, not $EVENT"
exit 1
fi
;;
*)
echo "::error::channel must be unstable or stable, got '$CHANNEL'"
exit 1
;;
esac

- name: Verify release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
# Tag formats written by release-version.yml.
if [ "$CHANNEL" = stable ]; then
TAG=$RELEASE_TAG
PATTERN='^v([0-9][0-9A-Za-z.~-]*)\+([0-9]+)$'
else
TAG=$PRERELEASE_TAG
PATTERN='^v([0-9][0-9A-Za-z.~-]*)\+([0-9]+)_pre$'
fi
if ! [[ $TAG =~ $PATTERN ]]; then
echo "::error::Release tag '$TAG' does not match the $CHANNEL tag format"
exit 1
fi
echo "Debian version: ${BASH_REMATCH[1]}-${BASH_REMATCH[2]}"
ASSET_COUNT=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets | length')
if [ "$ASSET_COUNT" -eq 0 ]; then
echo "::error::Release $TAG has no assets; pass build-deb.yml's artifact output to stage-release.yml"
exit 1
fi
echo "Release $TAG has $ASSET_COUNT asset(s)"

- name: Dispatch to APT repository
uses: peter-evans/repository-dispatch@v3
Comment thread
mairas marked this conversation as resolved.
with:
token: ${{ secrets.APT_REPO_PAT }}
repository: ${{ inputs.apt-repository }}
event-type: package-updated
client-payload: |
{
"repository": "${{ github.repository }}",
"distro": "${{ inputs.apt-distro }}",
"channel": "${{ inputs.channel }}",
"component": "${{ inputs.apt-component }}"
}
157 changes: 157 additions & 0 deletions .github/workflows/build-deb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@
# on every package found in the repository root or build/. A build that
# produces no package fails.
#
# Check mode (PRs): call without release inputs. Nothing is uploaded.
#
# Release mode (main): pass revision and the other release inputs, normally
# from release-version.yml. The workflow then generates debian/changelog
# before building, renames packages with a +<distro>+<component> suffix, and
# uploads them as the artifact named in the `artifact` output.
#
# Requirements:
# - .github/actions/build-deb/action.yml in the caller repository
#
# Optional caller scripts:
# - .github/scripts/generate-changelog.sh --upstream <v> --revision <n>
# - .github/scripts/rename-packages.sh --version <v> --distro <d> --component <c>

name: Build Debian package

Expand All @@ -22,16 +33,129 @@ on:
required: false
default: true
type: boolean
revision:
description: 'Release mode: revision N. Leave empty for check mode.'
required: false
default: ''
type: string
upstream-version:
description: 'Release mode: upstream version'
required: false
default: ''
type: string
package-name:
description: 'Release mode: Debian package name used in the changelog and default rename'
required: false
default: ''
type: string
apt-distro:
description: 'Release mode: APT distribution for the package name suffix (e.g. trixie, any)'
required: false
default: ''
type: string
apt-component:
description: 'Release mode: APT component for the package name suffix (e.g. main, hatlabs)'
required: false
default: ''
type: string
maintainer-name:
description: 'Release mode: maintainer name for debian/changelog'
required: false
default: ''
type: string
maintainer-email:
description: 'Release mode: maintainer email for debian/changelog'
required: false
default: ''
type: string
outputs:
artifact:
description: 'Release mode: name of the uploaded package artifact. Empty in check mode.'
value: ${{ jobs.build-deb.outputs.artifact }}

permissions:
contents: read

env:
ARTIFACT_NAME: deb-packages

# Run steps with -eo pipefail rather than the implicit bash -e.
defaults:
run:
shell: bash

jobs:
build-deb:
runs-on: ${{ inputs.runs-on }}
outputs:
artifact: ${{ steps.upload.outputs.artifact-id && env.ARTIFACT_NAME || '' }}
env:
RELEASE_MODE: ${{ inputs.revision != '' }}
REVISION: ${{ inputs.revision }}
UPSTREAM: ${{ inputs.upstream-version }}
PACKAGE_NAME: ${{ inputs.package-name }}
APT_DISTRO: ${{ inputs.apt-distro }}
APT_COMPONENT: ${{ inputs.apt-component }}
MAINTAINER_NAME: ${{ inputs.maintainer-name }}
MAINTAINER_EMAIL: ${{ inputs.maintainer-email }}
steps:
- name: Validate inputs
run: |
given=() missing=()
for name in UPSTREAM PACKAGE_NAME APT_DISTRO APT_COMPONENT MAINTAINER_NAME MAINTAINER_EMAIL; do
if [ -n "${!name}" ]; then given+=("$name"); else missing+=("$name"); fi
done
if [ "$RELEASE_MODE" = true ] && [ "${#missing[@]}" -gt 0 ]; then
echo "::error::Release mode (revision set) also needs: ${missing[*]}"
exit 1
fi
if [ "$RELEASE_MODE" != true ] && [ "${#given[@]}" -gt 0 ]; then
echo "::error::Release inputs given without revision: ${given[*]}. Pass revision for release mode, or none of them for check mode."
exit 1
fi
echo "Release mode: $RELEASE_MODE"

- name: Checkout code
uses: actions/checkout@v4
with:
# Release mode reads tags for the changelog.
fetch-depth: ${{ inputs.revision == '' && 1 || 0 }}
persist-credentials: false

- name: Generate debian/changelog
if: inputs.revision != ''
run: |
if [ -f .github/scripts/generate-changelog.sh ]; then
echo "Using local generate-changelog.sh"
.github/scripts/generate-changelog.sh --upstream "$UPSTREAM" --revision "$REVISION"
exit 0
fi

# lintian's debian-changelog-line-too-long warning is fatal, so fold
# commit subjects into bullets that fit in 80 columns.
wrap_changes() {
while IFS= read -r subject; do
[ -z "$subject" ] && continue
printf '%s\n' "$subject" \
| fold -s -w 76 \
| sed -e 's/[[:space:]]*$//' -e '1s/^/ * /' -e '1!s/^/ /'
done
}

LAST_TAG=$(git tag --merged HEAD -l "v*" --sort=-version:refname | grep -v "_pre" | head -n1 || true)
if [ -n "$LAST_TAG" ]; then
CHANGES=$(git log "${LAST_TAG}..HEAD" --pretty=tformat:"%s" --no-merges -- | wrap_changes)
else
CHANGES=$(git log -10 --pretty=tformat:"%s" --no-merges | wrap_changes)
fi
[ -z "$CHANGES" ] && CHANGES=" * Build ${REVISION}"

{
printf '%s (%s-%s) unstable; urgency=medium\n\n' "$PACKAGE_NAME" "$UPSTREAM" "$REVISION"
printf '%s\n\n' "$CHANGES"
printf ' -- %s <%s> %s\n' "$MAINTAINER_NAME" "$MAINTAINER_EMAIL" "$(date -R)"
} > debian/changelog
cat debian/changelog

- name: Build .deb package
uses: ./.github/actions/build-deb
Expand Down Expand Up @@ -73,3 +197,36 @@ jobs:
echo "::error::Lintian found issues. To suppress specific tags, create debian/<package>.lintian-overrides"
exit 1
fi

- name: Rename packages with distro and component suffix
if: inputs.revision != ''
run: |
DEBIAN_VERSION="${UPSTREAM}-${REVISION}"
if [ -f .github/scripts/rename-packages.sh ]; then
echo "Using local rename-packages.sh"
.github/scripts/rename-packages.sh --version "$DEBIAN_VERSION" --distro "$APT_DISTRO" --component "$APT_COMPONENT"
exit 0
fi

OLD_NAME="${PACKAGE_NAME}_${DEBIAN_VERSION}_all.deb"
NEW_NAME="${PACKAGE_NAME}_${DEBIAN_VERSION}_all+${APT_DISTRO}+${APT_COMPONENT}.deb"
for dir in . build; do
if [ -f "$dir/$OLD_NAME" ]; then
mv "$dir/$OLD_NAME" "$dir/$NEW_NAME"
echo "Renamed $dir/$OLD_NAME -> $dir/$NEW_NAME"
exit 0
fi
done
echo "::error::Expected package $OLD_NAME not found in the repository root or build/"
exit 1

- name: Upload packages
id: upload
if: inputs.revision != ''
uses: actions/upload-artifact@v4
with:
name: ${{ env.ARTIFACT_NAME }}
path: |
*.deb
build/*.deb
if-no-files-found: error
5 changes: 5 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ on:
permissions:
contents: read

# Run steps with -eo pipefail rather than the implicit bash -e.
defaults:
run:
shell: bash

jobs:
tests:
runs-on: ${{ inputs.runs-on }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
persist-credentials: false

- name: Shellcheck
run: shellcheck scripts/*.sh tests/*.sh
run: shellcheck -x scripts/*.sh tests/*.sh tests/lib/*.sh tests/lib/fake-gh

- name: Run script tests
run: |
Expand Down
Loading
Loading