chore: one-off App Store ownership transfer for launchpad #1
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: App Store ownership transfer | |
| # One-off, delete this file and its branch once the transfer has landed. | |
| # | |
| # The Nextcloud app store has no "transfer to user X" button and no | |
| # co-maintainer concept. Ownership moves the other way around: the CURRENT | |
| # owner unlocks the app (Account > Transfer app ownership), and the NEW owner | |
| # registers it again with the app certificate plus a signature over the app id. | |
| # That registration is the transfer, and it is exactly the POST below. | |
| # | |
| # See https://nextcloudappstore.readthedocs.io/en/latest/developer.html | |
| # #transferring-your-app-to-a-new-owner | |
| # | |
| # The app ends up owned by whichever account owns NEXTCLOUD_APPSTORE_TOKEN. | |
| # The signing key never leaves the runner and the signature is never printed: | |
| # together with the public certificate it is enough for anyone to claim an | |
| # app that is currently unlocked for transfer. | |
| on: | |
| push: | |
| branches: [chore/appstore-ownership-transfer] | |
| jobs: | |
| transfer: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Register launchpad under the App Store token's account | |
| env: | |
| APP_ID: launchpad | |
| SIGNING_KEY: ${{ secrets.NEXTCLOUD_SIGNING_KEY }} | |
| SIGNING_CERT: ${{ secrets.NEXTCLOUD_SIGNING_CERT }} | |
| APPSTORE_TOKEN: ${{ secrets.NEXTCLOUD_APPSTORE_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| # An org secret that is not shared with this repo arrives as an empty | |
| # string, not as an error, so the request would 401 for a reason the | |
| # response body does not explain. Fail here instead. | |
| [ -n "$APPSTORE_TOKEN" ] || { echo "::error::NEXTCLOUD_APPSTORE_TOKEN is empty in this repository's scope"; exit 1; } | |
| [ -n "$SIGNING_KEY" ] || { echo "::error::NEXTCLOUD_SIGNING_KEY is empty in this repository's scope"; exit 1; } | |
| printf '%s\n' "$SIGNING_KEY" > signing-key.key | |
| printf '%s\n' "$SIGNING_CERT" > signing-cert.crt | |
| SIG=$(echo -n "$APP_ID" | openssl dgst -sha512 -sign signing-key.key | openssl base64 -A) | |
| CODE=$(curl -s -o response.json -w '%{http_code}' \ | |
| -X POST "https://apps.nextcloud.com/api/v1/apps" \ | |
| -H "Authorization: Token $APPSTORE_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{\"certificate\": $(jq -Rs . < signing-cert.crt), \"signature\": \"$SIG\"}") | |
| rm -f signing-key.key | |
| echo "HTTP $CODE" | |
| cat response.json || true | |
| echo | |
| case "$CODE" in | |
| 200|201) echo "OK: $APP_ID is now registered to the account that owns NEXTCLOUD_APPSTORE_TOKEN" ;; | |
| 403) echo "::error::403: the app is locked for transfer, or the token's account may not register it"; exit 1 ;; | |
| 401) echo "::error::401: the App Store token was rejected"; exit 1 ;; | |
| *) echo "::error::unexpected HTTP $CODE"; exit 1 ;; | |
| esac |