Update libs from CodeOnTheGo #49
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: Update libs from CodeOnTheGo | |
| on: | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| env: | |
| PLUGINS_REMOTE_PATH: public_html/flags/plugins | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| steps: | |
| - name: Checkout plugin-examples | |
| uses: actions/checkout@v4 | |
| with: | |
| # PAT from a repo admin so the libs/ push bypasses the main ruleset. | |
| # A fine-grained PAT must use the organization as its resource owner, | |
| # not a personal account, or every push returns 403. The next step | |
| # checks this. GITHUB_TOKEN is only a fallback to keep checkout | |
| # working; it cannot bypass the ruleset, so the check rejects it. | |
| token: ${{ secrets.ADMIN_PERSONAL_ACCESS_TOKEN || github.token }} | |
| # The build below takes ~30 minutes. Without this guard, a token that | |
| # cannot write to this repo throws all of that away at "Commit updated | |
| # jars". Check the token first so the run fails in seconds instead. | |
| - name: Verify the push token | |
| env: | |
| ADMIN_PAT: ${{ secrets.ADMIN_PERSONAL_ACCESS_TOKEN }} | |
| GH_TOKEN: ${{ secrets.ADMIN_PERSONAL_ACCESS_TOKEN || github.token }} | |
| run: | | |
| org="${GITHUB_REPOSITORY%%/*}" | |
| if [ -z "$ADMIN_PAT" ]; then | |
| echo "::error::ADMIN_PERSONAL_ACCESS_TOKEN is not set. GITHUB_TOKEN cannot bypass the ${org} main ruleset, so the push would fail." | |
| exit 1 | |
| fi | |
| if ! repo=$(gh api "repos/${GITHUB_REPOSITORY}" 2>/dev/null); then | |
| echo "::error::The token cannot see ${GITHUB_REPOSITORY}. A fine-grained PAT must use resource owner '${org}' (the organization). A token owned by a personal account cannot reach ${org} repositories." | |
| exit 1 | |
| fi | |
| who=$(gh api user --jq .login 2>/dev/null || echo '(unknown)') | |
| push=$(printf '%s' "$repo" | jq -r '.permissions.push // false') | |
| echo "Token identity: ${who}. Push permission: ${push}." | |
| if [ "$push" != "true" ]; then | |
| echo "::error::The token authenticates as '${who}' but cannot write to ${GITHUB_REPOSITORY}. Grant it 'Contents: Read and write' with resource owner '${org}'." | |
| exit 1 | |
| fi | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Set up Gradle | |
| uses: gradle/actions/setup-gradle@v3 | |
| with: | |
| cache-disabled: true | |
| add-job-summary: 'never' | |
| - name: Compute release tag | |
| id: tag | |
| env: | |
| RUN_NUMBER: ${{ github.run_number }} | |
| run: echo "name=build-$(date -u +%Y-%m-%d)-${RUN_NUMBER}" >> "$GITHUB_OUTPUT" | |
| - name: Run update-libs script | |
| run: ./scripts/update-libs.sh | |
| - name: Commit updated jars | |
| id: commit | |
| run: | | |
| sha=$(git -C .cache/CodeOnTheGo rev-parse --short HEAD) | |
| git config user.name "ADFA" | |
| git config user.email "dev-team@appdevforall.org" | |
| git add libs/ | |
| if git diff --cached --quiet; then | |
| echo "No changes to libs/ — nothing to commit." | |
| else | |
| git commit -m "chore: update libs from CodeOnTheGo@${sha}" | |
| git push | |
| fi | |
| echo "codeonthego_sha=${sha}" >> "$GITHUB_OUTPUT" | |
| echo "head_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" | |
| - name: Stage .cgp files with website filenames | |
| run: | | |
| mkdir -p deploy-staging | |
| # Discover every plugin module the same way scripts/update-libs.sh does | |
| # (a sibling dir whose build.gradle.kts applies the plugin-builder Gradle | |
| # plugin) so new plugins deploy to the website automatically. The website | |
| # filename is the lowercased module name with any trailing "-plugin" | |
| # stripped; the case arms hold the legacy filenames the website already | |
| # links that don't follow that rule — never change an existing name. | |
| # Keep in sync with SKIP_PLUGINS in scripts/update-libs.sh. | |
| SKIP_PLUGINS=(pebble-custom-function-template-installer) | |
| staged=0 | |
| for build_file in */build.gradle.kts; do | |
| [ -f "$build_file" ] || continue | |
| grep -qF "com.itsaky.androidide.plugins.build" "$build_file" || continue | |
| module="$(dirname "$build_file")" | |
| skip=0 | |
| for s in "${SKIP_PLUGINS[@]}"; do | |
| [ "$s" = "$module" ] && skip=1 && break | |
| done | |
| if [ "$skip" -eq 1 ]; then | |
| echo "Skipping $module (in SKIP_PLUGINS)" | |
| continue | |
| fi | |
| case "$module" in | |
| apk-viewer) name="apk-analyzer" ;; | |
| markdown-preview) name="markdown-previewer" ;; | |
| *) name="$(echo "${module%-plugin}" | tr '[:upper:]' '[:lower:]')" ;; | |
| esac | |
| src="$(ls "${module}/build/plugin/"*.cgp 2>/dev/null | grep -v -- '-debug\.cgp$' | head -n1)" | |
| if [ -z "$src" ]; then | |
| echo "ERROR: no release .cgp found under ${module}/build/plugin/ (did assemblePlugin run?)" | |
| exit 1 | |
| fi | |
| cp "$src" "deploy-staging/${name}.cgp" | |
| echo "Staged $src -> deploy-staging/${name}.cgp" | |
| staged=$((staged + 1)) | |
| done | |
| if [ "$staged" -eq 0 ]; then | |
| echo "ERROR: no plugin modules discovered under */build.gradle.kts" | |
| exit 1 | |
| fi | |
| ls -la deploy-staging/ | |
| - name: Upload .cgp artifacts for deploy job | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: plugins-cgp | |
| path: deploy-staging/*.cgp | |
| retention-days: 7 | |
| if-no-files-found: error | |
| - name: Publish release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.tag.outputs.name }} | |
| target_commitish: ${{ steps.commit.outputs.head_sha }} | |
| body: Built against CodeOnTheGo@${{ steps.commit.outputs.codeonthego_sha }}. | |
| fail_on_unmatched_files: true | |
| files: '*/build/plugin/*.cgp' | |
| deploy: | |
| needs: release | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Download .cgp artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: plugins-cgp | |
| path: deploy-staging | |
| - name: List staged files | |
| run: ls -la deploy-staging/ | |
| - name: Set up SSH key | |
| env: | |
| GREENGEEKS_HOST: ${{ vars.GREENGEEKS_SSH_HOST }} | |
| GREENGEEKS_KEY: ${{ secrets.GREENGEEKS_SSH_PRIVATE_KEY }} | |
| GREENGEEKS_USER: ${{ vars.GREENGEEKS_SSH_USER }} | |
| run: | | |
| mkdir -p ~/.ssh | |
| if [ -z "$GREENGEEKS_HOST" ]; then | |
| echo "Error: GREENGEEKS_SSH_HOST variable is not set" | |
| exit 1 | |
| fi | |
| echo "$GREENGEEKS_KEY" > ~/.ssh/id_rsa | |
| sed -i '$ { /^$/ d; }' ~/.ssh/id_rsa | |
| chmod 600 ~/.ssh/id_rsa | |
| if ! grep -q "BEGIN.*PRIVATE KEY" ~/.ssh/id_rsa; then | |
| echo "Error: SSH key does not appear to be in correct format" | |
| exit 1 | |
| fi | |
| cat > ~/.ssh/config <<EOF | |
| Host * | |
| IdentitiesOnly yes | |
| PreferredAuthentications publickey | |
| StrictHostKeyChecking no | |
| UserKnownHostsFile ~/.ssh/known_hosts | |
| PubkeyAuthentication yes | |
| PasswordAuthentication no | |
| Host $GREENGEEKS_HOST | |
| User $GREENGEEKS_USER | |
| IdentityFile ~/.ssh/id_rsa | |
| ControlMaster auto | |
| ControlPath ~/.ssh/cm-%r@%h:%p | |
| ControlPersist 10m | |
| ConnectTimeout 60 | |
| ServerAliveInterval 60 | |
| ServerAliveCountMax 10 | |
| EOF | |
| chmod 600 ~/.ssh/config | |
| unset SSH_AUTH_SOCK | |
| unset SSH_AGENT_PID | |
| ssh-keyscan -H "$GREENGEEKS_HOST" >> ~/.ssh/known_hosts 2>/dev/null | |
| - name: Upload .cgp files via scp | |
| env: | |
| REMOTE: ${{ vars.GREENGEEKS_SSH_USER }}@${{ vars.GREENGEEKS_SSH_HOST }} | |
| run: | | |
| for f in deploy-staging/*.cgp; do | |
| echo "Uploading $f -> $REMOTE:$PLUGINS_REMOTE_PATH/$(basename "$f")" | |
| scp -o StrictHostKeyChecking=no "$f" "$REMOTE:$PLUGINS_REMOTE_PATH/" | |
| done | |
| - name: Verify remote MD5 checksums | |
| env: | |
| REMOTE: ${{ vars.GREENGEEKS_SSH_USER }}@${{ vars.GREENGEEKS_SSH_HOST }} | |
| run: | | |
| names=$(cd deploy-staging && ls *.cgp | tr '\n' ' ') | |
| remote_sums=$(ssh "$REMOTE" "cd $PLUGINS_REMOTE_PATH && md5sum $names") | |
| failures=0 | |
| while IFS= read -r line; do | |
| remote_md5=$(echo "$line" | awk '{print $1}') | |
| name=$(echo "$line" | awk '{print $2}') | |
| local_md5=$(md5sum "deploy-staging/$name" | cut -d ' ' -f1) | |
| if [ "$local_md5" = "$remote_md5" ]; then | |
| echo "OK $name ($local_md5)" | |
| else | |
| echo "FAIL $name local=$local_md5 remote=$remote_md5" | |
| failures=$((failures+1)) | |
| fi | |
| done <<< "$remote_sums" | |
| if [ $failures -gt 0 ]; then | |
| echo "ERROR: $failures file(s) failed MD5 verification" | |
| exit 1 | |
| fi | |
| - name: Cleanup SSH key | |
| if: always() | |
| run: | | |
| rm -f ~/.ssh/id_rsa ~/.ssh/known_hosts || true |