240 lines
8.8 KiB
YAML
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/*
|