name: Build and publish app release on: release: types: [published] workflow_dispatch: inputs: tag: description: Existing GitHub release tag to build and publish required: true type: string publish_appstore: description: Publish the built release to the Nextcloud App Store required: true default: true type: boolean nightly: description: Mark a manually dispatched App Store release as nightly required: true default: false type: boolean permissions: contents: write concurrency: group: appstore-release-${{ github.event.release.tag_name || inputs.tag }} cancel-in-progress: false env: APP_NAME: educai jobs: build-and-publish: name: Build, sign and publish runs-on: ubuntu-latest timeout-minutes: 25 steps: - name: Checkout release automation uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ github.event.repository.default_branch }} path: automation persist-credentials: false - name: Checkout released source uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ github.event.release.tag_name || inputs.tag }} path: source fetch-depth: 1 persist-credentials: false - name: Validate release metadata id: release shell: bash env: EVENT_NAME: ${{ github.event_name }} RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag }} RELEASE_PRERELEASE: ${{ github.event.release.prerelease || inputs.nightly || false }} MANUAL_PUBLISH: ${{ inputs.publish_appstore || false }} run: | set -euo pipefail if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then echo "Unsupported release tag: $RELEASE_TAG" >&2 exit 1 fi version="$(sed -n 's:.*\([^<]*\).*:\1:p' source/appinfo/info.xml)" if [[ "$RELEASE_TAG" != "v$version" ]]; then echo "Release tag $RELEASE_TAG does not match app version $version" >&2 exit 1 fi nightly="false" if [[ "$RELEASE_PRERELEASE" == "true" ]]; then nightly="true" fi publish_appstore="true" if [[ "$EVENT_NAME" == "workflow_dispatch" && "$MANUAL_PUBLISH" != "true" ]]; then publish_appstore="false" fi asset="$APP_NAME-$version.tar.gz" download_url="https://github.com/$GITHUB_REPOSITORY/releases/download/$RELEASE_TAG/$asset" echo "tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT" echo "version=$version" >> "$GITHUB_OUTPUT" echo "asset=$asset" >> "$GITHUB_OUTPUT" echo "artifact=$GITHUB_WORKSPACE/build/artifacts/appstore/$asset" >> "$GITHUB_OUTPUT" echo "download_url=$download_url" >> "$GITHUB_OUTPUT" echo "nightly=$nightly" >> "$GITHUB_OUTPUT" echo "publish_appstore=$publish_appstore" >> "$GITHUB_OUTPUT" - name: Set up Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version-file: source/.nvmrc cache: npm cache-dependency-path: source/package-lock.json - name: Set up PHP and Composer uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 with: php-version: '8.1' tools: composer:v2 coverage: none - name: Build production tarball shell: bash run: | set -euo pipefail bash automation/scripts/build-release.sh \ "$GITHUB_WORKSPACE/source" \ "$GITHUB_WORKSPACE/build/artifacts/appstore" - name: Validate release archive shell: bash env: ARTIFACT: ${{ steps.release.outputs.artifact }} VERSION: ${{ steps.release.outputs.version }} run: | set -euo pipefail test -f "$ARTIFACT" archive_list="$RUNNER_TEMP/$APP_NAME-archive.txt" tar -tzf "$ARTIFACT" > "$archive_list" test "$(cut -d/ -f1 "$archive_list" | sort -u)" = "$APP_NAME" grep -Fx "$APP_NAME/appinfo/info.xml" "$archive_list" >/dev/null grep -Fx "$APP_NAME/vendor/autoload.php" "$archive_list" >/dev/null grep -Fx "$APP_NAME/js/educai-main.mjs" "$archive_list" >/dev/null if grep -Eq "^$APP_NAME/(node_modules|src|tests|vendor-bin|\.git)/" "$archive_list"; then echo "Development files were included in the release archive" >&2 exit 1 fi packaged_version="$(tar -xOf "$ARTIFACT" "$APP_NAME/appinfo/info.xml" | sed -n 's:.*\([^<]*\).*:\1:p')" test "$packaged_version" = "$VERSION" - name: Verify signing key and certificate shell: bash env: ARTIFACT: ${{ steps.release.outputs.artifact }} APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} APP_PUBLIC_CRT: ${{ secrets.APP_PUBLIC_CRT }} run: | set -euo pipefail umask 077 key_file="$RUNNER_TEMP/$APP_NAME.key" cert_file="$RUNNER_TEMP/$APP_NAME.crt" public_key_file="$RUNNER_TEMP/$APP_NAME.pub" signature_file="$RUNNER_TEMP/$APP_NAME.signature" printf '%s\n' "$APP_PRIVATE_KEY" > "$key_file" printf '%s\n' "$APP_PUBLIC_CRT" > "$cert_file" key_fingerprint="$(openssl pkey -in "$key_file" -pubout -outform DER | sha256sum | cut -d' ' -f1)" cert_fingerprint="$(openssl x509 -in "$cert_file" -pubkey -noout | openssl pkey -pubin -outform DER | sha256sum | cut -d' ' -f1)" test "$key_fingerprint" = "$cert_fingerprint" openssl dgst -sha512 -sign "$key_file" -binary "$ARTIFACT" > "$signature_file" openssl x509 -in "$cert_file" -pubkey -noout > "$public_key_file" openssl dgst -sha512 -verify "$public_key_file" -signature "$signature_file" "$ARTIFACT" - name: Attach tarball to GitHub release shell: bash env: GH_TOKEN: ${{ github.token }} ARTIFACT: ${{ steps.release.outputs.artifact }} RELEASE_TAG: ${{ steps.release.outputs.tag }} DOWNLOAD_URL: ${{ steps.release.outputs.download_url }} run: | set -euo pipefail gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null gh release upload "$RELEASE_TAG" "$ARTIFACT" \ --repo "$GITHUB_REPOSITORY" \ --clobber local_hash="$(sha256sum "$ARTIFACT" | cut -d' ' -f1)" public_hash="$(curl --fail --silent --show-error --location "$DOWNLOAD_URL" | sha256sum | cut -d' ' -f1)" test "$local_hash" = "$public_hash" - name: Publish release to Nextcloud App Store if: ${{ steps.release.outputs.publish_appstore == 'true' }} uses: R0Wi/nextcloud-appstore-push-action@669734a3b4fb1ff7a636ae6d5bf2c18fab10a4e0 with: app_name: ${{ env.APP_NAME }} appstore_token: ${{ secrets.APPSTORE_TOKEN }} download_url: ${{ steps.release.outputs.download_url }} app_private_key: ${{ secrets.APP_PRIVATE_KEY }} nightly: ${{ steps.release.outputs.nightly }} - name: Remove signing material if: ${{ always() }} shell: bash run: | rm -f \ "$GITHUB_WORKSPACE/$APP_NAME.key" \ "$RUNNER_TEMP/$APP_NAME.key" \ "$RUNNER_TEMP/$APP_NAME.crt" \ "$RUNNER_TEMP/$APP_NAME.pub" \ "$RUNNER_TEMP/$APP_NAME.signature"