opsdash-app/.github/workflows/release-appstore.yml
2026-03-07 12:47:20 +07:00

240 lines
8.8 KiB
YAML

name: Build And Publish Appstore Package
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'App version (x.y.z). Optional; falls back to tag/ref.'
required: false
type: string
release_tag:
description: 'Optional existing release tag to attach the tarball to.'
required: false
type: string
push_to_appstore:
description: 'Push to Nextcloud App Store (requires release_tag on manual runs).'
required: false
default: false
type: boolean
permissions:
contents: write
concurrency:
group: release-${{ github.workflow }}-${{ github.event.release.tag_name || github.ref }}
cancel-in-progress: false
env:
APP_NAME: opsdash
NEXTCLOUD_RELEASE_BRANCH: stable32
jobs:
build_and_publish:
environment: release
runs-on: ubuntu-latest
steps:
- name: Checkout app sources
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Checkout Nextcloud server
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
with:
repository: nextcloud/server
ref: ${{ env.NEXTCLOUD_RELEASE_BRANCH }}
submodules: true
path: server
- name: Set up Node.js
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8
with:
node-version: 20
cache: npm
cache-dependency-path: opsdash/package-lock.json
- name: Set up PHP
uses: shivammathur/setup-php@afefcaf556d98dc7896cca380e181decb609ca44
with:
php-version: '8.3'
extensions: mbstring, intl, gd, zip
coverage: none
- name: Cache Composer downloads
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: ~/.composer/cache/files
key: ${{ runner.os }}-composer-release-${{ hashFiles('opsdash/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-release-
${{ runner.os }}-composer-
- name: Resolve release version
id: version
shell: bash
run: |
set -euo pipefail
version="${{ github.event.inputs.version || '' }}"
if [ -z "$version" ]; then
if [ "${{ github.event_name }}" = "release" ]; then
version="${{ github.event.release.tag_name }}"
else
version="${GITHUB_REF_NAME}"
fi
fi
version="${version#v}"
if [ -z "$version" ]; then
echo "Unable to resolve release version" >&2
exit 1
fi
asset_name="${{ env.APP_NAME }}-${version}.tar.gz"
should_push="${{ github.event_name == 'release' || github.event.inputs.push_to_appstore == 'true' }}"
target_tag="${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.release_tag }}"
if [ -n "${target_tag}" ]; then
normalized_target_tag="${target_tag#v}"
if [ "${normalized_target_tag}" != "${version}" ]; then
echo "Version '${version}' does not match release tag '${target_tag}'." >&2
exit 1
fi
fi
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "asset_name=${asset_name}" >> "$GITHUB_OUTPUT"
echo "should_push=${should_push}" >> "$GITHUB_OUTPUT"
echo "target_tag=${target_tag}" >> "$GITHUB_OUTPUT"
- name: Verify required release secrets
shell: bash
env:
APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}
APP_PUBLIC_CRT: ${{ secrets.APP_PUBLIC_CRT }}
APPSTORE_TOKEN: ${{ secrets.APPSTORE_TOKEN }}
SHOULD_PUSH_APPSTORE: ${{ steps.version.outputs.should_push }}
TARGET_TAG: ${{ steps.version.outputs.target_tag }}
run: |
set -euo pipefail
missing=()
for secret in APP_PRIVATE_KEY APP_PUBLIC_CRT; do
if [ -z "${!secret:-}" ]; then
missing+=("$secret")
fi
done
if [ "${SHOULD_PUSH_APPSTORE}" = "true" ] && [ -z "${APPSTORE_TOKEN:-}" ]; then
missing+=("APPSTORE_TOKEN")
fi
if [ "${SHOULD_PUSH_APPSTORE}" = "true" ] && [ -z "${TARGET_TAG:-}" ]; then
echo "push_to_appstore=true requires release_tag on workflow_dispatch." >&2
exit 1
fi
if [ "${#missing[@]}" -gt 0 ]; then
printf 'Missing required secret(s): %s\n' "${missing[*]}" >&2
exit 1
fi
if [ "${SHOULD_PUSH_APPSTORE}" = "true" ] && printf '%s' "${APPSTORE_TOKEN}" | grep -q '[[:space:]]'; then
echo "APPSTORE_TOKEN contains whitespace/newlines. Re-save it as a single-line token." >&2
exit 1
fi
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
printf '%s' "${APP_PRIVATE_KEY}" > "$tmpdir/app.key"
printf '%s' "${APP_PUBLIC_CRT}" > "$tmpdir/app.crt"
if ! openssl pkey -in "$tmpdir/app.key" -noout >/dev/null 2>&1; then
echo "APP_PRIVATE_KEY is not a valid PEM private key." >&2
exit 1
fi
if ! openssl x509 -in "$tmpdir/app.crt" -noout >/dev/null 2>&1; then
echo "APP_PUBLIC_CRT is not a valid PEM certificate." >&2
exit 1
fi
- name: Sync release version sources
run: bash tools/release/bump_version.sh "${{ steps.version.outputs.version }}"
- name: Build appstore tarball
run: make appstore VERSION=${{ steps.version.outputs.version }}
- name: Export signing materials from environment secrets
shell: bash
env:
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}
app_public_crt: ${{ secrets.APP_PUBLIC_CRT }}
run: |
set -euo pipefail
cert_dir="$HOME/.nextcloud/certificates"
mkdir -p "$cert_dir"
php ./tools/ci/file_from_env.php "app_private_key" "$cert_dir/${{ env.APP_NAME }}.key"
php ./tools/ci/file_from_env.php "app_public_crt" "$cert_dir/${{ env.APP_NAME }}.crt"
- name: Sign staged app and rebuild tarball
shell: bash
run: |
set -euo pipefail
cert_dir="$HOME/.nextcloud/certificates"
php server/occ integrity:sign-app \
--privateKey="$cert_dir/${{ env.APP_NAME }}.key" \
--certificate="$cert_dir/${{ env.APP_NAME }}.crt" \
--path="$GITHUB_WORKSPACE/build/${{ env.APP_NAME }}"
test -f "build/${{ env.APP_NAME }}/appinfo/signature.json"
rm -f "build/dist/${{ steps.version.outputs.asset_name }}"
tar -czf "build/dist/${{ steps.version.outputs.asset_name }}" -C build "${{ env.APP_NAME }}"
- name: Upload package as workflow artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
with:
name: appstore-package-${{ steps.version.outputs.version }}
path: build/dist/${{ steps.version.outputs.asset_name }}
if-no-files-found: error
- name: Upload tarball to GitHub release
id: attach_to_release
if: ${{ github.event_name == 'release' || github.event.inputs.release_tag != '' }}
uses: svenstaro/upload-release-action@483c1e56f95e88835747b1c7c60581215016cbf2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/dist/${{ steps.version.outputs.asset_name }}
asset_name: ${{ steps.version.outputs.asset_name }}
tag: ${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.release_tag }}
overwrite: true
- name: Normalize App Store token
if: ${{ steps.version.outputs.should_push == 'true' }}
id: appstore_token
shell: bash
env:
APPSTORE_TOKEN: ${{ secrets.APPSTORE_TOKEN }}
run: |
set -euo pipefail
token="$(printf '%s' "${APPSTORE_TOKEN}" | tr -d '[:space:]')"
if [ -z "$token" ]; then
echo "APPSTORE_TOKEN resolved to an empty value after normalization." >&2
exit 1
fi
echo "::add-mask::$token"
echo "value=$token" >> "$GITHUB_OUTPUT"
- name: Push to Nextcloud App Store
if: ${{ steps.version.outputs.should_push == 'true' }}
uses: R0Wi/nextcloud-appstore-push-action@a011fe619bcf6e77ddebc96f9908e1af4071b9c1
with:
app_name: ${{ env.APP_NAME }}
appstore_token: ${{ steps.appstore_token.outputs.value }}
download_url: ${{ steps.attach_to_release.outputs.browser_download_url }}
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}
nightly: ${{ github.event_name == 'release' && github.event.release.prerelease || false }}
- name: Delete crt and key from local storage
if: always()
run: rm -f ~/.nextcloud/certificates/*