From e54995ceea6b86075091b24f7d3a285cf4ab663b Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:01:52 +0200 Subject: [PATCH 01/23] IONOS(ci): add concurrency group to cancel superseded runs Adds a concurrency block that cancels in-progress workflow runs when a new run is queued on the same PR or non-critical branch. Runs on ionos-dev, ionos-stable, and rc/* are keyed by run_id so concurrent pushes to release branches don't cancel each other. Mirrors the strategy used by the sister ncw-server build-artifact workflow (~/develop/easycloud/ncw-server/.github/workflows/build-artifact.yml). --- .github/workflows/hidrive-next-build.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 57f03c543231c..a32f99affbfb6 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -50,6 +50,20 @@ on: type: string default: '' +concurrency: + group: >- + ${{ github.workflow }}-${{ + github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || + ( + ( + contains(fromJson('["refs/heads/ionos-dev","refs/heads/ionos-stable"]'), github.ref) || + startsWith(github.ref, 'refs/heads/rc/') + ) && github.run_id || + github.ref + ) + }} + cancel-in-progress: true + env: TARGET_PACKAGE_NAME: hidrive-next.zip REGISTRY: ghcr.io From e41cb5b121c0bb9264ef400e97eccf13ead17028 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:02:44 +0200 Subject: [PATCH 02/23] IONOS(ci): add prepare-matrix preflight summary for remote trigger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a "Check configuration" step that writes a markdown table to $GITHUB_STEP_SUMMARY summarizing whether the trigger-remote-dev-worflow job will fire — DISABLE_REMOTE_TRIGGER value, event type, branch, and the predicted go/no-go. Purely observational. Makes the most common build-pipeline question ("why didn't the GitLab job get triggered?") visible at the top of the run summary, instead of forcing a dig through skipped-job conditions. --- .github/workflows/hidrive-next-build.yml | 68 ++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index a32f99affbfb6..90ddc2813190f 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -109,6 +109,74 @@ jobs: - name: Install dependencies run: sudo apt-get update && sudo apt-get install -y make jq + - name: Check configuration + run: | + echo "### 🔧 Remote Trigger Configuration" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**DISABLE_REMOTE_TRIGGER value:** \`${{ vars.DISABLE_REMOTE_TRIGGER }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Event type:** \`${{ github.event_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Branch:** \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "🔧 Remote Trigger Configuration" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "DISABLE_REMOTE_TRIGGER = '${{ vars.DISABLE_REMOTE_TRIGGER }}'" + echo "Event type = '${{ github.event_name }}'" + echo "Branch = '${{ github.ref_name }}'" + echo "" + + if [ "${{ vars.DISABLE_REMOTE_TRIGGER }}" == "true" ]; then + echo "âš ī¸ Remote trigger is DISABLED" + echo " The 'trigger-remote-dev-worflow' job will be SKIPPED" + echo "**Status:** âš ī¸ Remote trigger is **DISABLED**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The \`trigger-remote-dev-worflow\` job will be skipped." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "To enable, delete the variable or set it to a value other than 'true' at:" >> $GITHUB_STEP_SUMMARY + echo "https://github.com/${{ github.repository }}/settings/variables/actions" >> $GITHUB_STEP_SUMMARY + else + echo "✅ Remote trigger is ENABLED" + echo " Checking if trigger conditions are met..." + echo "**Status:** ✅ Remote trigger is **ENABLED**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + WILL_TRIGGER=true + echo "**Trigger Conditions Check:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ github.event_name }}" != "push" ]; then + echo "- ❌ Event must be 'push' (current: \`${{ github.event_name }}\`)" >> $GITHUB_STEP_SUMMARY + echo " ❌ Event type is '${{ github.event_name }}' (must be 'push')" + WILL_TRIGGER=false + else + echo "- ✅ Event is 'push'" >> $GITHUB_STEP_SUMMARY + echo " ✅ Event type is 'push'" + fi + + VALID_BRANCH_PATTERN='^(ionos-dev|ionos-stable)$' + if [[ ! "${{ github.ref_name }}" =~ $VALID_BRANCH_PATTERN ]]; then + echo "- ❌ Branch must be 'ionos-dev' or 'ionos-stable' (current: \`${{ github.ref_name }}\`)" >> $GITHUB_STEP_SUMMARY + echo " ❌ Branch is '${{ github.ref_name }}' (must be 'ionos-dev' or 'ionos-stable')" + WILL_TRIGGER=false + else + echo "- ✅ Branch is '\`${{ github.ref_name }}\`'" >> $GITHUB_STEP_SUMMARY + echo " ✅ Branch is '${{ github.ref_name }}'" + fi + + echo "- â„šī¸ All dependent jobs must succeed (checked at job runtime)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "$WILL_TRIGGER" = "true" ]; then + echo "**Expected:** The \`trigger-remote-dev-worflow\` job **WILL RUN** (if all dependent jobs succeed)." >> $GITHUB_STEP_SUMMARY + echo "đŸŽ¯ Expected: trigger-remote-dev-worflow job WILL RUN (if all dependent jobs succeed)" + else + echo "**Expected:** The \`trigger-remote-dev-worflow\` job **WILL BE SKIPPED** due to unmet conditions above." >> $GITHUB_STEP_SUMMARY + echo "â­ī¸ Expected: trigger-remote-dev-worflow job WILL BE SKIPPED" + fi + fi + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + - name: Check JFrog credentials id: jfrog-available if: github.event.inputs.force_rebuild != 'true' From 595725ac1046bc386462e3efbc6f57d68162d5ab Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:03:10 +0200 Subject: [PATCH 03/23] IONOS(ci): add gh cache list visibility step to prepare-matrix Lists all GitHub Actions caches available to the run before detect-app-cache.sh consults them. Makes cache-miss investigations tractable by surfacing the actual cache key list in the run log instead of requiring 'gh cache list' from a developer machine. --- .github/workflows/hidrive-next-build.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 90ddc2813190f..17e012583143d 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -177,6 +177,11 @@ jobs: fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + - name: List caches before restore + run: gh cache list + env: + GH_TOKEN: ${{ github.token }} + - name: Check JFrog credentials id: jfrog-available if: github.event.inputs.force_rebuild != 'true' From 1f1ad588dc69f2f7194754176cbb1d8b20091584 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:03:46 +0200 Subject: [PATCH 04/23] IONOS(ci): add secrets preflight and upload retry to artifactory upload Adds a 'Check prerequisites' step at the top of upload-to-artifactory that fails fast with ::error:: annotations if any of JF_ARTIFACTORY_URL, JF_ARTIFACTORY_USER, or JF_ACCESS_TOKEN are unset, instead of failing deep inside jf rt upload with a less obvious error. Wraps the jf rt upload call in a 3-attempt retry loop with exponential backoff (10s, 20s) to absorb transient Artifactory hiccups that otherwise red-flag the whole pipeline. --- .github/workflows/hidrive-next-build.yml | 64 +++++++++++++++++++++--- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 17e012583143d..28864a7f50127 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -610,6 +610,31 @@ jobs: BUILD_NAME: "hidrive_next-snapshot" steps: + - name: Check prerequisites + run: | + echo "Checking if required secrets are set..." + error_count=0 + + if [ -z "${{ secrets.JF_ARTIFACTORY_URL }}" ]; then + echo "::error::JF_ARTIFACTORY_URL secret is not set" + error_count=$((error_count + 1)) + fi + + if [ -z "${{ secrets.JF_ARTIFACTORY_USER }}" ]; then + echo "::error::JF_ARTIFACTORY_USER secret is not set" + error_count=$((error_count + 1)) + fi + + if [ -z "${{ secrets.JF_ACCESS_TOKEN }}" ]; then + echo "::error::JF_ACCESS_TOKEN secret is not set" + error_count=$((error_count + 1)) + fi + + if [ $error_count -ne 0 ]; then + echo "::error::Required secrets are not set. Aborting." + exit 1 + fi + - name: Download artifact zip uses: actions/download-artifact@v4 with: @@ -649,12 +674,39 @@ jobs: export PATH_TO_LATEST_ARTIFACT="${PATH_TO_DIRECTORY}/${PATH_TO_FILE}" - # Promote current build to the "latest" dev build - jf rt upload "${{ env.TARGET_PACKAGE_NAME }}" \ - --build-name "${{ env.BUILD_NAME }}" \ - --build-number ${{ github.run_number }} \ - --target-props "hdnext.nc_version=${{ needs.hidrive-next-build.outputs.NC_VERSION }};vcs.branch=${{ github.ref }};vcs.revision=${{ github.sha }}" \ - $PATH_TO_LATEST_ARTIFACT + # Upload with retry logic (3 attempts with exponential backoff: 10s, 20s) + MAX_ATTEMPTS=3 + ATTEMPT=1 + UPLOAD_SUCCESS=false + DELAY_SEC=10 + + while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do + echo "Upload attempt $ATTEMPT of $MAX_ATTEMPTS..." + + if jf rt upload "${{ env.TARGET_PACKAGE_NAME }}" \ + --build-name "${{ env.BUILD_NAME }}" \ + --build-number ${{ github.run_number }} \ + --target-props "hdnext.nc_version=${{ needs.hidrive-next-build.outputs.NC_VERSION }};vcs.branch=${{ github.ref }};vcs.revision=${{ github.sha }}" \ + $PATH_TO_LATEST_ARTIFACT; then + UPLOAD_SUCCESS=true + echo "✅ Upload successful on attempt $ATTEMPT" + break + else + echo "âš ī¸ Upload attempt $ATTEMPT failed" + if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then + echo "Waiting $DELAY_SEC seconds before retry..." + sleep $DELAY_SEC + DELAY_SEC=$((DELAY_SEC * 2)) + fi + fi + + ATTEMPT=$((ATTEMPT + 1)) + done + + if [ "$UPLOAD_SUCCESS" != "true" ]; then + echo "❌ Upload failed after $MAX_ATTEMPTS attempts" + exit 1 + fi echo "ARTIFACTORY_LAST_BUILD_PATH=${PATH_TO_LATEST_ARTIFACT}" >> $GITHUB_OUTPUT From fac02861f7044dfbb1aeb850bdb05312059cf1ef Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:04:03 +0200 Subject: [PATCH 05/23] IONOS(ci): verify JFrog upload landed for each app build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a successful jf rt upload in the per-app matrix step, runs jf rt s against the target path to confirm the artifact is searchable. Log-only — a verification miss prints a warning but does not fail the build, since the upload itself already returned success. Surfaces the rare case where JFrog accepts an upload but the artifact is not immediately searchable (typically a routing/indexing issue on the Artifactory side). --- .github/workflows/hidrive-next-build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 28864a7f50127..8122c3deed534 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -371,6 +371,12 @@ jobs: if jf rt upload "$ARCHIVE_NAME" "$JFROG_PATH" \ --target-props "app.name=${APP_NAME};app.sha=${APP_SHA};vcs.branch=${{ github.ref_name }};vcs.revision=${{ github.sha }}"; then echo "✅ Uploaded $APP_NAME to JFrog" + echo "Verifying upload..." + if jf rt s "$JFROG_PATH" 2>/dev/null | grep -q "$JFROG_PATH"; then + echo "✓ Upload verified — artifact is accessible at $JFROG_PATH" + else + echo "⚠ Upload reported success but verification search did not find the artifact" + fi else echo "❌ JFrog upload failed" exit 1 From 6bbebaa189950e538d57b468a13ccc2f6352962d Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:04:31 +0200 Subject: [PATCH 06/23] IONOS(ci): add get-job-data composite action Composite action that fetches the GitHub Actions job HTML URL via the REST API and exposes it as an output. Used by subsequent steps to attach job.html_url to JFrog artifact properties for traceability between an Artifactory artifact and the run/job that produced it. Uses a temp netrc file to keep the GH token off the curl command line, and trap-cleans both the netrc and the curl-error temp file on exit. Returns an empty output (not a failure) when the API call or job lookup fails, so callers can include it with continue-on-error: true. Copied from sister ncw-server (.github/actions/get-job-data/action.yml). --- .github/actions/get-job-data/action.yml | 93 +++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 .github/actions/get-job-data/action.yml diff --git a/.github/actions/get-job-data/action.yml b/.github/actions/get-job-data/action.yml new file mode 100644 index 0000000000000..a0b15c0dd8030 --- /dev/null +++ b/.github/actions/get-job-data/action.yml @@ -0,0 +1,93 @@ +name: 'Get Job Data from GitHub Actions' +description: 'Fetches the GitHub Actions job data from the GitHub API' +inputs: + job-name: + description: 'The name of the job to find' + required: true + github-token: + description: 'GitHub token for API authentication' + required: true + repository: + description: 'Repository in owner/repo format' + required: true + run-id: + description: 'GitHub Actions run ID' + required: true +outputs: + job_html_url: + description: 'The HTML URL of the job' + value: ${{ steps.get_url.outputs.job_html_url }} +runs: + using: 'composite' + steps: + - name: Fetch job URL from GitHub API + id: get_url + shell: bash + run: | + # Fetch the numeric job ID from GitHub API + CURL_ERROR_FILE=$(mktemp) + NETRC_FILE=$(mktemp) + + # Write GitHub API credentials to a temporary netrc file to avoid + # passing the token directly on the curl command line. + printf '%s\n' \ + 'machine api.github.com' \ + ' login x-access-token' \ + " password ${{ inputs.github-token }}" \ + > "$NETRC_FILE" + chmod 600 "$NETRC_FILE" + + # Ensure temporary file cleanup on exit + cleanup() { + if [ -n "$CURL_ERROR_FILE" ] && [ -f "$CURL_ERROR_FILE" ]; then + rm -f "$CURL_ERROR_FILE" + fi + if [ -n "$NETRC_FILE" ] && [ -f "$NETRC_FILE" ]; then + rm -f "$NETRC_FILE" + fi + } + trap cleanup EXIT + + API_RESPONSE=$(curl -sS -w "\n%{http_code}" \ + --netrc-file "$NETRC_FILE" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ inputs.repository }}/actions/runs/${{ inputs.run-id }}/jobs" 2>"$CURL_ERROR_FILE") + + CURL_EXIT_CODE=$? + if [ $CURL_EXIT_CODE -ne 0 ]; then + echo "❌ ERROR: curl request to GitHub API failed with exit code $CURL_EXIT_CODE" + if [ -s "$CURL_ERROR_FILE" ]; then + echo "curl error output:" + cat "$CURL_ERROR_FILE" + fi + echo "job_html_url=" >> "$GITHUB_OUTPUT" + exit 0 + fi + + HTTP_CODE=$(echo "$API_RESPONSE" | tail -n1) + RESPONSE_BODY=$(echo "$API_RESPONSE" | sed '$d') + + if [ "$HTTP_CODE" != "200" ]; then + echo "âš ī¸ WARNING: GitHub API request failed with $HTTP_CODE" + echo "job_html_url=" >> "$GITHUB_OUTPUT" + exit 0 + fi + + EXPECTED_JOB_NAME="${{ inputs.job-name }}" + JOB_URL=$(echo "$RESPONSE_BODY" | jq -r \ + --arg job_name "$EXPECTED_JOB_NAME" \ + '.jobs[] | select(.name == $job_name) | .html_url') + + if [ -z "$JOB_URL" ] || [ "$JOB_URL" = "null" ]; then + echo "âš ī¸ WARNING: Failed to extract job URL from response for job name '$EXPECTED_JOB_NAME'." + echo "Possible causes:" + echo " - The job name does not match exactly (including spaces and case)." + echo " - The job has not started yet at the time this action ran." + echo " - The GitHub API response format was unexpected." + echo "Please verify that the job has started before this action runs and double-check the exact job name in the GitHub Actions UI." + echo "job_html_url=" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "job_html_url=$JOB_URL" >> "$GITHUB_OUTPUT" + echo "Job URL: $JOB_URL" From 5119659afc6ae9fa1bf924059f8b52c877e20d26 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:06:19 +0200 Subject: [PATCH 07/23] IONOS(ci): use JFrog props arrays and embed job.html_url Converts the two jf rt upload --target-props sites (build-apps, upload-to-artifactory) from a single inline semicolon string to a bash array assembled per upload. Adds an invocation of the get-job-data composite action before each upload and conditionally appends job.html_url to the props array. Result: every artifact in JFrog now carries a clickable link back to the exact GitHub Actions job that produced it, simplifying post-incident traceability. Adds a Checkout step to upload-to-artifactory (self-hosted runner) since the local composite action requires the repo on disk. --- .github/workflows/hidrive-next-build.yml | 52 ++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 8122c3deed534..fd74a04e01d4d 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -347,6 +347,16 @@ jobs: echo "cache_key=${EFFECTIVE_VERSION}-app-build-${APP_NAME}-${SHORT_SHA}" >> $GITHUB_OUTPUT echo "archive_name=${APP_NAME}-${SHORT_SHA}.tar.gz" >> $GITHUB_OUTPUT + - name: Get Job data + id: get_job_data + continue-on-error: true + uses: ./.github/actions/get-job-data + with: + job-name: 'build-apps (${{ matrix.app_info.name }}, ${{ matrix.app_info.sha }})' + github-token: ${{ github.token }} + repository: ${{ github.repository }} + run-id: ${{ github.run_id }} + - name: Upload ${{ matrix.app_info.name }} to JFrog if: steps.jfrog-creds.outputs.available == 'true' run: | @@ -368,8 +378,18 @@ jobs: echo "Archive size: $(ls -lh "$ARCHIVE_NAME" | awk '{print $5}')" echo "Uploading to: $JFROG_PATH" - if jf rt upload "$ARCHIVE_NAME" "$JFROG_PATH" \ - --target-props "app.name=${APP_NAME};app.sha=${APP_SHA};vcs.branch=${{ github.ref_name }};vcs.revision=${{ github.sha }}"; then + JFROG_PROPS_LIST=() + JFROG_PROPS_LIST+=("app.name=${APP_NAME}") + JFROG_PROPS_LIST+=("app.sha=${APP_SHA}") + JFROG_PROPS_LIST+=("vcs.branch=${{ github.ref_name }}") + JFROG_PROPS_LIST+=("vcs.revision=${{ github.sha }}") + JOB_URL="${{ steps.get_job_data.outputs.job_html_url }}" + if [ -n "$JOB_URL" ]; then + JFROG_PROPS_LIST+=("job.html_url=${JOB_URL}") + fi + JFROG_PROPS=$(IFS=';'; printf '%s' "${JFROG_PROPS_LIST[*]}") + + if jf rt upload "$ARCHIVE_NAME" "$JFROG_PATH" --target-props "$JFROG_PROPS"; then echo "✅ Uploaded $APP_NAME to JFrog" echo "Verifying upload..." if jf rt s "$JFROG_PATH" 2>/dev/null | grep -q "$JFROG_PATH"; then @@ -641,6 +661,12 @@ jobs: exit 1 fi + # Checkout is required to access the local composite action at ./.github/actions/get-job-data + - name: Checkout repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 + with: + fetch-depth: 1 + - name: Download artifact zip uses: actions/download-artifact@v4 with: @@ -658,6 +684,16 @@ jobs: # Ping the server jf rt ping + - name: Get Job data + id: get_job_data + continue-on-error: true + uses: ./.github/actions/get-job-data + with: + job-name: 'Push to artifactory' + github-token: ${{ github.token }} + repository: ${{ github.repository }} + run-id: ${{ github.run_id }} + - name: Upload build to artifactory id: artifactory_upload run: | @@ -680,6 +716,16 @@ jobs: export PATH_TO_LATEST_ARTIFACT="${PATH_TO_DIRECTORY}/${PATH_TO_FILE}" + JFROG_PROPS_LIST=() + JFROG_PROPS_LIST+=("hdnext.nc_version=${{ needs.hidrive-next-build.outputs.NC_VERSION }}") + JFROG_PROPS_LIST+=("vcs.branch=${{ github.ref }}") + JFROG_PROPS_LIST+=("vcs.revision=${{ github.sha }}") + JOB_URL="${{ steps.get_job_data.outputs.job_html_url }}" + if [ -n "$JOB_URL" ]; then + JFROG_PROPS_LIST+=("job.html_url=${JOB_URL}") + fi + JFROG_PROPS=$(IFS=';'; printf '%s' "${JFROG_PROPS_LIST[*]}") + # Upload with retry logic (3 attempts with exponential backoff: 10s, 20s) MAX_ATTEMPTS=3 ATTEMPT=1 @@ -692,7 +738,7 @@ jobs: if jf rt upload "${{ env.TARGET_PACKAGE_NAME }}" \ --build-name "${{ env.BUILD_NAME }}" \ --build-number ${{ github.run_number }} \ - --target-props "hdnext.nc_version=${{ needs.hidrive-next-build.outputs.NC_VERSION }};vcs.branch=${{ github.ref }};vcs.revision=${{ github.sha }}" \ + --target-props "$JFROG_PROPS" \ $PATH_TO_LATEST_ARTIFACT; then UPLOAD_SUCCESS=true echo "✅ Upload successful on attempt $ATTEMPT" From d1c0780eb80218c03594f0bd2531984b72f2c457 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:06:51 +0200 Subject: [PATCH 08/23] IONOS(ci): bypass npm cache when force_rebuild is set The hidrive-next-build final job's setup-node was previously unconditionally caching npm dependencies, even when the user triggered workflow_dispatch with force_rebuild=true. That meant force_rebuild busted the JFrog and custom-npms caches but silently kept a stale npm cache. Drives the cache flag from the force_rebuild input: empty string when forcing, 'npm' otherwise. Matches the existing per-app composer cache gate (lines 478-485) so force_rebuild now uniformly clears every cache the workflow controls. --- .github/workflows/hidrive-next-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index fd74a04e01d4d..a9bda49cb10c3 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -553,6 +553,7 @@ jobs: uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version-file: "package.json" + cache: ${{ github.event.inputs.force_rebuild != 'true' && 'npm' || '' }} - name: Install Dependencies run: sudo apt-get update && sudo apt-get install -y make zip unzip From 7a4cacb939d6e28fee3c0b245bb70af02a160d1a Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:07:25 +0200 Subject: [PATCH 09/23] IONOS(ci): pin PHP version to 8.3 explicitly Both setup-php invocations (per-app build and final hidrive-next-build) were relying on shivammathur/setup-php's default, which can drift with upstream changes to the action. Pinning to 8.3 makes the runtime explicit and keeps test/build environments aligned with production. Aligns with the sister ncw-server build-artifact workflow. --- .github/workflows/hidrive-next-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index a9bda49cb10c3..3ec413c7e4a43 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -316,6 +316,7 @@ jobs: if: steps.app-config.outputs.has-composer == 'true' uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 #v2.31.1 with: + php-version: '8.3' tools: composer:v2 extensions: gd, zip, curl, xml, xmlrpc, mbstring, sqlite, xdebug, pgsql, intl, imagick, gmp, apcu, bcmath, redis, soap, imap, opcache env: @@ -564,6 +565,7 @@ jobs: - name: Setup PHP with PECL extension uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 #v2.31.1 with: + php-version: '8.3' tools: composer:v2 extensions: gd, zip, curl, xml, xmlrpc, mbstring, sqlite, xdebug, pgsql, intl, imagick, gmp, apcu, bcmath, redis, soap, imap, opcache env: From c44e22400bc1b25f753ad93241a6f850a1c5a51d Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:07:40 +0200 Subject: [PATCH 10/23] IONOS(ci): add per-app build completion summary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each successful matrix job in build-apps now appends a small markdown block to $GITHUB_STEP_SUMMARY listing the app, its SHA, path, and success status. With 7 parallel matrix jobs the run summary becomes a single-glance view of which apps actually built (vs. restored from cache) — useful when investigating cache-hit ratios. Aligns with the sister ncw-server build-artifact workflow. --- .github/workflows/hidrive-next-build.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 3ec413c7e4a43..53efe5d44d6fb 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -337,6 +337,15 @@ jobs: PUPPETEER_SKIP_DOWNLOAD: true run: make -f IONOS/Makefile ${{ steps.app-config.outputs.makefile-target }} + - name: Report build completion + if: success() + run: | + echo "### ✅ Built ${{ matrix.app_info.name }}" >> $GITHUB_STEP_SUMMARY + echo "- **SHA:** \`${{ matrix.app_info.sha }}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Path:** ${{ steps.app-config.outputs.path }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status:** Success" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + - name: Compute app cache key id: app-cache-key run: | From b3ee48b996179906ba16baf5d232e0786f1df099 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:08:46 +0200 Subject: [PATCH 11/23] IONOS(ci): validate appinfo/info.xml after app restore The hidrive-next-build restore step previously only validated that the target directory existed after tar extraction. A corrupt or truncated archive could leave a near-empty directory and still pass this check, causing downstream build failures with confusing errors. Adds an appinfo/info.xml file check after both restore paths (cached apps and newly built apps). The check is conditional on APP_PATH starting with 'apps-' so themes/ submodules, which don't ship appinfo/info.xml, continue to pass with just the directory check. --- .github/workflows/hidrive-next-build.yml | 28 +++++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 53efe5d44d6fb..59c4179c0a0a2 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -512,12 +512,20 @@ jobs: mkdir -p "$(dirname "$APP_PATH")" tar -xzf "$ARCHIVE_NAME" -C "$(dirname "$APP_PATH")" rm -f "$ARCHIVE_NAME" - if [ -d "$APP_PATH" ]; then - echo "✅ Restored $APP_NAME from JFrog" - else + if [ ! -d "$APP_PATH" ]; then echo "❌ Restore validation failed: $APP_PATH not found" exit 1 fi + # appinfo/info.xml lives in standard Nextcloud apps but not in themes/ + case "$APP_PATH" in + apps-*) + if [ ! -f "$APP_PATH/appinfo/info.xml" ]; then + echo "❌ Restore validation failed: $APP_PATH/appinfo/info.xml missing" + exit 1 + fi + ;; + esac + echo "✅ Restored $APP_NAME from JFrog" elif [ "$SOURCE" == "github-cache" ]; then CACHE_KEY=$(echo "$app_json" | jq -r '.cache_key') @@ -548,12 +556,20 @@ jobs: tar -xzf "$ARCHIVE_NAME" -C "$(dirname "$APP_PATH")" rm -f "$ARCHIVE_NAME" - if [ -d "$APP_PATH" ]; then - echo "✅ Restored $APP_NAME from JFrog" - else + if [ ! -d "$APP_PATH" ]; then echo "❌ Restore validation failed: $APP_PATH not found" exit 1 fi + # appinfo/info.xml lives in standard Nextcloud apps but not in themes/ + case "$APP_PATH" in + apps-*) + if [ ! -f "$APP_PATH/appinfo/info.xml" ]; then + echo "❌ Restore validation failed: $APP_PATH/appinfo/info.xml missing" + exit 1 + fi + ;; + esac + echo "✅ Restored $APP_NAME from JFrog" done < <(echo "$APPS_TO_BUILD" | jq -c '.[]') fi From e36a8a49f6f5311cb4ba0333586b040f904c44e5 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:09:41 +0200 Subject: [PATCH 12/23] =?UTF-8?q?IONOS(ci):=20detect-app-cache.sh=20?= =?UTF-8?q?=E2=80=94=20continue=20on=20missing=20submodule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the script would exit 1 on the first app whose submodule directory was missing or not a git repo, killing the whole pipeline. This is overly strict: a partial submodule init (e.g. a network blip during checkout) shouldn't block builds for the other six apps. Now both edge cases queue the app for build with sha="unknown" and log a non-fatal '⊘' marker to the step summary, then continue. The resulting cache_key is non-collidable with normal runs ('-unknown' suffix), so this never poisons the regular per-SHA cache. The build itself proceeds because the matrix step doesn't depend on the SHA being valid — only the cache key does. Aligns with the sister ncw-server detect-app-cache.sh behavior. --- .github/scripts/detect-app-cache.sh | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/scripts/detect-app-cache.sh b/.github/scripts/detect-app-cache.sh index fd739120f36b4..0803d27156a63 100755 --- a/.github/scripts/detect-app-cache.sh +++ b/.github/scripts/detect-app-cache.sh @@ -141,15 +141,29 @@ while IFS= read -r app_json; do if [ -d "$APP_PATH" ]; then CURRENT_SHA=$(git -C "$APP_PATH" rev-parse HEAD 2>/dev/null || echo "") else - echo "ERROR: $APP_NAME - directory not found at '$APP_PATH'. Submodule not initialised?" - echo "| $APP_NAME | N/A | N/A | ❌ Directory not found |" >> "$GITHUB_STEP_SUMMARY" - exit 1 + echo "⊘ $APP_NAME - directory not found at '$APP_PATH' (submodule not initialised?), will build" + echo "| $APP_NAME | N/A | N/A | ⊘ Directory not found — will build |" >> "$GITHUB_STEP_SUMMARY" + UNKNOWN_SUFFIX="unknown" + APPS_TO_BUILD=$(echo "$APPS_TO_BUILD" | jq -c \ + --arg app "$APP_NAME" --arg sha "$UNKNOWN_SUFFIX" \ + --arg archive_name "${APP_NAME}-${UNKNOWN_SUFFIX}.tar.gz" \ + --arg jfrog_path "${ARTIFACTORY_REPOSITORY_SNAPSHOT}/apps/${CACHE_VERSION}/${APP_NAME}/${APP_NAME}-${UNKNOWN_SUFFIX}.tar.gz" \ + '. + [{name: $app, sha: $sha, archive_name: $archive_name, jfrog_path: $jfrog_path}]') + APPS_TO_BUILD_COUNT=$((APPS_TO_BUILD_COUNT + 1)) + continue fi if [ -z "$CURRENT_SHA" ]; then - echo "ERROR: $APP_NAME - '$APP_PATH' exists but is not a git repo (cannot determine SHA)." - echo "| $APP_NAME | N/A | N/A | ❌ Not a git repo |" >> "$GITHUB_STEP_SUMMARY" - exit 1 + echo "⊘ $APP_NAME - '$APP_PATH' exists but is not a git repo, will build" + echo "| $APP_NAME | N/A | N/A | ⊘ Not a git repo — will build |" >> "$GITHUB_STEP_SUMMARY" + UNKNOWN_SUFFIX="unknown" + APPS_TO_BUILD=$(echo "$APPS_TO_BUILD" | jq -c \ + --arg app "$APP_NAME" --arg sha "$UNKNOWN_SUFFIX" \ + --arg archive_name "${APP_NAME}-${UNKNOWN_SUFFIX}.tar.gz" \ + --arg jfrog_path "${ARTIFACTORY_REPOSITORY_SNAPSHOT}/apps/${CACHE_VERSION}/${APP_NAME}/${APP_NAME}-${UNKNOWN_SUFFIX}.tar.gz" \ + '. + [{name: $app, sha: $sha, archive_name: $archive_name, jfrog_path: $jfrog_path}]') + APPS_TO_BUILD_COUNT=$((APPS_TO_BUILD_COUNT + 1)) + continue fi # Add SHA to the map for all apps (regardless of cache status) From f6efceea431a66262ac86d281914f66885446812 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:09:56 +0200 Subject: [PATCH 13/23] IONOS(ci): upload per-app build artifacts for debugging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a parallel actions/upload-artifact step after each app build, making the freshly built tree downloadable from the workflow run page for one day. Complements the JFrog upload (which is the primary delivery channel) by giving in-flight access without an Artifactory login. retention-days: 1 keeps storage costs negligible — these are debugging conveniences, not durable artifacts. --- .github/workflows/hidrive-next-build.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 59c4179c0a0a2..f61295a84f898 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -419,6 +419,15 @@ jobs: path: ${{ steps.app-config.outputs.path }} key: ${{ steps.app-cache-key.outputs.cache_key }} + - name: Upload ${{ matrix.app_info.name }} build artifacts + uses: actions/upload-artifact@v4 + with: + retention-days: 1 + name: app-build-${{ matrix.app_info.name }} + path: | + ${{ steps.app-config.outputs.path }} + !${{ steps.app-config.outputs.path }}/node_modules + - name: Show changes on failure if: failure() run: | From bd09855bb45a992ffaf54d34e63f550d73358d25 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:10:13 +0200 Subject: [PATCH 14/23] =?UTF-8?q?IONOS(ci):=20rename=20trigger-remote-dev-?= =?UTF-8?q?worflow=20=E2=86=92=20trigger-remote-dev-workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the longstanding typo in the job key. Updates the job declaration and all references in step output (preflight summary written from prepare-matrix). Coordination notes: - No needs:/if: references the old name (the job is a leaf in the dependency graph — only the misspelled key itself). - If branch protection on ionos-dev/ionos-stable has a required status check named 'trigger-remote-dev-worflow', that check must be re-added under the new name in Settings → Branches. - No external webhook listens for this job name (verified by repo search; the GitLab trigger is invoked from within the job, not the other direction). --- .github/workflows/hidrive-next-build.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index f61295a84f898..9cb2b0f7aa5c6 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -128,10 +128,10 @@ jobs: if [ "${{ vars.DISABLE_REMOTE_TRIGGER }}" == "true" ]; then echo "âš ī¸ Remote trigger is DISABLED" - echo " The 'trigger-remote-dev-worflow' job will be SKIPPED" + echo " The 'trigger-remote-dev-workflow' job will be SKIPPED" echo "**Status:** âš ī¸ Remote trigger is **DISABLED**" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "The \`trigger-remote-dev-worflow\` job will be skipped." >> $GITHUB_STEP_SUMMARY + echo "The \`trigger-remote-dev-workflow\` job will be skipped." >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "To enable, delete the variable or set it to a value other than 'true' at:" >> $GITHUB_STEP_SUMMARY echo "https://github.com/${{ github.repository }}/settings/variables/actions" >> $GITHUB_STEP_SUMMARY @@ -168,11 +168,11 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY if [ "$WILL_TRIGGER" = "true" ]; then - echo "**Expected:** The \`trigger-remote-dev-worflow\` job **WILL RUN** (if all dependent jobs succeed)." >> $GITHUB_STEP_SUMMARY - echo "đŸŽ¯ Expected: trigger-remote-dev-worflow job WILL RUN (if all dependent jobs succeed)" + echo "**Expected:** The \`trigger-remote-dev-workflow\` job **WILL RUN** (if all dependent jobs succeed)." >> $GITHUB_STEP_SUMMARY + echo "đŸŽ¯ Expected: trigger-remote-dev-workflow job WILL RUN (if all dependent jobs succeed)" else - echo "**Expected:** The \`trigger-remote-dev-worflow\` job **WILL BE SKIPPED** due to unmet conditions above." >> $GITHUB_STEP_SUMMARY - echo "â­ī¸ Expected: trigger-remote-dev-worflow job WILL BE SKIPPED" + echo "**Expected:** The \`trigger-remote-dev-workflow\` job **WILL BE SKIPPED** due to unmet conditions above." >> $GITHUB_STEP_SUMMARY + echo "â­ī¸ Expected: trigger-remote-dev-workflow job WILL BE SKIPPED" fi fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @@ -863,7 +863,7 @@ jobs: exit 1 # make it red to grab attention - trigger-remote-dev-worflow: + trigger-remote-dev-workflow: runs-on: self-hosted name: Trigger remote workflow From f739b4cf0c7b97c4583a83ad55dcc4a0fac195aa Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 13 May 2026 10:50:40 +0200 Subject: [PATCH 15/23] IONOS(ci): bump actions/checkout to v5.0.1 v5 drops Node.js 16 in favor of Node 20 (which is what GitHub-hosted runners ship today) and includes upstream fixes for partial clone behavior. All checkout invocations updated to the same pinned SHA. Aligns with the sister ncw-server build-artifact workflow (which uses @v5). --- .github/workflows/hidrive-next-build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 9cb2b0f7aa5c6..d69a143ede6b5 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -101,7 +101,7 @@ jobs: echo "Effective cache version: $EFFECTIVE_VERSION" - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: submodules: true fetch-depth: 1 @@ -277,7 +277,7 @@ jobs: echo "Building $APP_NAME (SHA: ${{ matrix.app_info.sha }})" - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: submodules: true fetch-depth: 1 @@ -452,7 +452,7 @@ jobs: name: hidrive-next-build steps: - name: Checkout server - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: submodules: true @@ -700,7 +700,7 @@ jobs: # Checkout is required to access the local composite action at ./.github/actions/get-job-data - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 1 From a775ccaf628816643b98b18e4a5be9d36abca3ce Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 13 May 2026 10:50:52 +0200 Subject: [PATCH 16/23] IONOS(ci): bump actions/setup-node to v6.4.0 v6 drops Node 16 support and adds compatibility with Node 24, plus the latest npm cache and proxy fixes. All setup-node invocations updated to the same pinned SHA. Aligns with the sister ncw-server build-artifact workflow (which uses @v6). --- .github/workflows/hidrive-next-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index d69a143ede6b5..f7bdcf899420a 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -306,7 +306,7 @@ jobs: - name: Set up node if: steps.app-config.outputs.has-npm == 'true' - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version-file: "package.json" cache: 'npm' @@ -585,7 +585,7 @@ jobs: echo "✅ All apps restored" - name: Set up node with version from package.json's engines - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version-file: "package.json" cache: ${{ github.event.inputs.force_rebuild != 'true' && 'npm' || '' }} From 0a72a9fd8893daf6aeeefdf275a30c78f03ac6a5 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 13 May 2026 10:51:04 +0200 Subject: [PATCH 17/23] IONOS(ci): bump actions/download-artifact to v5.0.0 Switches from the unpinned @v4 short tag to SHA-pinned v5.0.0, matching the project's policy of full-SHA pinning for security review traceability. v5 brings significantly improved download performance (parallel streaming) for large workflow artifacts. Aligns with the sister ncw-server build-artifact workflow (which uses @v5). --- .github/workflows/hidrive-next-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index f7bdcf899420a..f816948f7e2a1 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -705,7 +705,7 @@ jobs: fetch-depth: 1 - name: Download artifact zip - uses: actions/download-artifact@v4 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: name: hidrive_next_build_artifact @@ -818,7 +818,7 @@ jobs: steps: - name: Download artifact zip - uses: actions/download-artifact@v4 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: name: hidrive_next_build_artifact From 01e79eb4ed8f9745566fa18d0a00edcd50293290 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 13 May 2026 10:51:36 +0200 Subject: [PATCH 18/23] IONOS(ci): bump docker/login-action to v3.7.0 Brings in upstream fixes for credential handling on self-hosted runners. Minor v3.x bump; no API changes. Adds a version comment alongside the SHA for human readability. --- .github/workflows/hidrive-next-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index f816948f7e2a1..9aa4243ee4886 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -823,7 +823,7 @@ jobs: name: hidrive_next_build_artifact - name: Log in to the Container registry - uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} From 03ee4465429348ff5a714fbaa40a7a53a124cca2 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Wed, 13 May 2026 10:51:51 +0200 Subject: [PATCH 19/23] IONOS(ci): bump docker/metadata-action to v5.10.0 Latest v5.x. No API changes; routine bump bringing in OCI manifest label improvements. Adds version comment alongside the SHA. --- .github/workflows/hidrive-next-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 9aa4243ee4886..62fd9ba798104 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -831,7 +831,7 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 with: images: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" From 4e58bb71e09a3fe7a0d550b58a8a91650c99af04 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:13:10 +0200 Subject: [PATCH 20/23] IONOS(ci): build rc/** and */dev/* branches on push Extends the push branch filter to fire the workflow for release-candidate branches ('rc/**') and developer feature branches ('*/dev/*', e.g. 'mk/dev/foo'). Previously these branches only built via the pull_request trigger, which is awkward for developers who want a fast feedback loop on push before opening a PR. Companion commits in this branch route those branches' artifacts to distinct Artifactory paths and extend the remote-trigger conditions to match. Aligns with the sister ncw-server build-artifact workflow. --- .github/workflows/hidrive-next-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 62fd9ba798104..d75800e2ac215 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -32,6 +32,8 @@ on: branches: - ionos-dev - ionos-stable + - 'rc/**' + - '*/dev/*' workflow_dispatch: inputs: force_rebuild: From 9ca579f0874b37e09ad67be9266c777878c6e1d1 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:13:52 +0200 Subject: [PATCH 21/23] IONOS(ci): route rc/* and */dev/* artifacts to nested JFrog paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends upload-to-artifactory's gating condition to allow PR, workflow_dispatch, ionos-dev/ionos-stable, rc/*, and */dev/* runs to land in Artifactory. Adds two new ARTIFACTORY_STAGE_PREFIX rules: - rc/* → uses the literal branch name as prefix (e.g. 'rc/33.0') - */dev/* → uses 'dev-' where is the slug before /dev/ with non-alphanumerics replaced by '-' (e.g. 'mk/dev/foo' → 'dev-mk') This separates feature-branch and release-candidate artifacts from the main dev/stable streams so they don't collide on the "latest" NC_VERSION file name. The path table is documented inline next to the derivation. --- .github/workflows/hidrive-next-build.yml | 25 ++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index d75800e2ac215..248c7a9dbc7f3 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -657,10 +657,13 @@ jobs: upload-to-artifactory: runs-on: self-hosted - # Upload the artifact to the Artifactory repository on PR *OR* on "ionos-dev|ionos-stable" branch push + # Upload the artifact to the Artifactory repository on PR *OR* on + # "ionos-dev|ionos-stable|rc/*|*/dev/*" branch push *OR* on manual workflow_dispatch if: | always() && - (github.event_name == 'pull_request' || github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable') && + (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || + github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable' || + startsWith(github.ref_name, 'rc/') || contains(github.ref_name, '/dev/')) && needs.prepare-matrix.result == 'success' && (needs.build-apps.result == 'success' || needs.build-apps.result == 'skipped') && needs.hidrive-next-build.result == 'success' @@ -736,14 +739,24 @@ jobs: - name: Upload build to artifactory id: artifactory_upload run: | - # PR builds are stored in a separate directory as "dev/pr/hidrive-next-pr-.zip" - # Push to "ionos-dev" branch is stored as "dev/hidrive-next-.zip" + # Artifactory Build Storage Structure: + # | Branch/Event | Stage Prefix | Artifact Path | + # |------------------|---------------------|----------------------------------------------------------------| + # | Pull Request | dev | dev/pr/hidrive-next-pr-.zip | + # | ionos-dev | dev | dev/hidrive-next-.zip | + # | ionos-stable | stable | stable/hidrive-next-.zip | + # | rc/* | | rc//hidrive-next-.zip | + # | */dev/* | dev- | dev-/hidrive-next-.zip | ARTIFACTORY_STAGE_PREFIX="dev" - # set ARTIFACTORY_STAGE_PREFIX=stable on ionos-stable branch - if [ ${{ github.ref_name }} == "ionos-stable" ]; then + if [ "${{ github.ref_name }}" == "ionos-stable" ]; then ARTIFACTORY_STAGE_PREFIX="stable" + elif [[ "${{ github.ref_name }}" =~ ^rc/.*$ ]]; then + ARTIFACTORY_STAGE_PREFIX="${{ github.ref_name }}" + elif [[ "${{ github.ref_name }}" =~ ^.*/dev/.*$ ]]; then + BRANCH_PREFIX=$(echo "${{ github.ref_name }}" | sed 's|/.*||' | sed 's/[^A-Za-z0-9._-]/-/g') + ARTIFACTORY_STAGE_PREFIX="dev-${BRANCH_PREFIX}" fi export PATH_TO_DIRECTORY="${{ env.ARTIFACTORY_REPOSITORY_SNAPSHOT }}/${ARTIFACTORY_STAGE_PREFIX}" From fb0f7f9fe3c9054f1ef5a153f40022cd6d29dbac Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:14:30 +0200 Subject: [PATCH 22/23] IONOS(ci): trigger remote workflow for rc/* and */dev/* branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends three things to handle the newly-buildable rc/* and */dev/* branches: 1. trigger-remote-dev-workflow's gating condition now accepts rc/* and */dev/* refs in addition to ionos-dev/ionos-stable. 2. BUILD_TYPE mapping in the GitLab webhook payload: - rc/* → 'rc' - */dev/* → 'dev-' (matches the Artifactory stage prefix introduced in the companion 'route rc/*â€Ļ' commit so the GitLab side can reason about artifact location consistently). 3. The prepare-matrix preflight-summary branch-validity regex now matches the same four patterns, so the predicted go/no-go in the step summary stays accurate. GitLab pipeline coordination: the new BUILD_TYPE values land in the trigger payload's `variables[BUILD_TYPE]`. Coordinate with the GitLab maintainer to confirm the downstream pipeline handles 'rc' and 'dev-*' explicitly (or accepts them as fallthrough to 'dev'-like behavior). --- .github/workflows/hidrive-next-build.yml | 29 +++++++++++++++--------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 248c7a9dbc7f3..2c6e1ba47039b 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -156,10 +156,10 @@ jobs: echo " ✅ Event type is 'push'" fi - VALID_BRANCH_PATTERN='^(ionos-dev|ionos-stable)$' + VALID_BRANCH_PATTERN='^(ionos-dev|ionos-stable)$|^rc/.*$|^[^/]+/dev/.*$' if [[ ! "${{ github.ref_name }}" =~ $VALID_BRANCH_PATTERN ]]; then - echo "- ❌ Branch must be 'ionos-dev' or 'ionos-stable' (current: \`${{ github.ref_name }}\`)" >> $GITHUB_STEP_SUMMARY - echo " ❌ Branch is '${{ github.ref_name }}' (must be 'ionos-dev' or 'ionos-stable')" + echo "- ❌ Branch must be 'ionos-dev', 'ionos-stable', 'rc/*' or '*/dev/*' (current: \`${{ github.ref_name }}\`)" >> $GITHUB_STEP_SUMMARY + echo " ❌ Branch is '${{ github.ref_name }}' (must be 'ionos-dev', 'ionos-stable', 'rc/*' or '*/dev/*')" WILL_TRIGGER=false else echo "- ✅ Branch is '\`${{ github.ref_name }}\`'" >> $GITHUB_STEP_SUMMARY @@ -883,13 +883,14 @@ jobs: name: Trigger remote workflow needs: [ hidrive-next-build, upload-to-artifactory ] - # Trigger remote build on "ionos-dev|ionos-stable" branch *push* defined in the on:push:branches + # Trigger remote build on "ionos-dev|ionos-stable|rc/*|*/dev/*" branch *push* defined in on:push:branches # Can be disabled via repository variable 'DISABLE_REMOTE_TRIGGER' (set to 'true' to disable) # Configure at: https://github.com/IONOS-Productivity/nc-server/settings/variables/actions if: | always() && github.event_name == 'push' && - (github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable') && + (github.ref_name == 'ionos-dev' || github.ref_name == 'ionos-stable' || + startsWith(github.ref_name, 'rc/') || contains(github.ref_name, '/dev/')) && needs.hidrive-next-build.result == 'success' && needs.upload-to-artifactory.result == 'success' && vars.DISABLE_REMOTE_TRIGGER != 'true' @@ -950,16 +951,22 @@ jobs: set -x # Branch to GitLab Trigger Mapping (see HDNEXT-1373): - # | ref_name | GITLAB_REF | BUILD_TYPE | - # |--------------|------------|------------| - # | ionos-dev | main | dev | - # | ionos-stable | main | stable | + # | ref_name | GITLAB_REF | BUILD_TYPE | + # |--------------|------------|-------------------| + # | ionos-dev | main | dev | + # | ionos-stable | main | stable | + # | rc/* | main | rc | + # | */dev/* | main | dev- | BUILD_TYPE="dev" - # Override build type for stable branch - if [ ${{ github.ref_name }} == "ionos-stable" ]; then + if [ "${{ github.ref_name }}" == "ionos-stable" ]; then BUILD_TYPE="stable" + elif [[ "${{ github.ref_name }}" =~ ^rc/ ]]; then + BUILD_TYPE="rc" + elif [[ "${{ github.ref_name }}" =~ ^.*/dev/.*$ ]]; then + BRANCH_PREFIX=$(echo "${{ github.ref_name }}" | sed 's|/.*||' | sed 's/[^A-Za-z0-9._-]/-/g') + BUILD_TYPE="dev-${BRANCH_PREFIX}" fi # Construct source build URL for traceability From 076a365945c5758100654daf9f34ed0d62c67566 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Tue, 12 May 2026 18:15:00 +0200 Subject: [PATCH 23/23] IONOS(ci): nest JFrog artifact path by NC_VERSION and short SHA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Branch uploads (ionos-dev / ionos-stable / rc/* / */dev/*) now write to '/hidrive-next-//hidrive-next-.zip' instead of overwriting a single '/hidrive-next-.zip' slot. The nested layout preserves every build by sha and lets us debug a regression against the exact artifact a given commit produced. PR uploads are unchanged: 'dev/pr/hidrive-next-pr-.zip' is still the per-PR canonical path, since PRs benefit from "overwrite the latest on the open PR" semantics. âš ī¸ Downstream consumer impact: - trigger-remote-dev-workflow passes ARTIFACTORY_LAST_BUILD_PATH as a literal full path to the GitLab pipeline, so GitLab consumers that re-derive the path from a glob (`dev/hidrive-next-*.zip`) will break. Coordinate with the GitLab pipeline maintainer before merging this commit — or revert if a "latest" symlink is needed. - The Artifactory cleanup policy may need adjustment: per-sha retention will grow the repo over time. The previous flat layout retained one slot per branch. Aligns with the sister ncw-server build-artifact workflow. --- .github/workflows/hidrive-next-build.yml | 26 +++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/hidrive-next-build.yml b/.github/workflows/hidrive-next-build.yml index 2c6e1ba47039b..65be72b289cca 100644 --- a/.github/workflows/hidrive-next-build.yml +++ b/.github/workflows/hidrive-next-build.yml @@ -740,13 +740,13 @@ jobs: id: artifactory_upload run: | # Artifactory Build Storage Structure: - # | Branch/Event | Stage Prefix | Artifact Path | - # |------------------|---------------------|----------------------------------------------------------------| - # | Pull Request | dev | dev/pr/hidrive-next-pr-.zip | - # | ionos-dev | dev | dev/hidrive-next-.zip | - # | ionos-stable | stable | stable/hidrive-next-.zip | - # | rc/* | | rc//hidrive-next-.zip | - # | */dev/* | dev- | dev-/hidrive-next-.zip | + # | Branch/Event | Stage Prefix | Artifact Path | + # |------------------|---------------------|------------------------------------------------------------------------------------| + # | Pull Request | dev | dev/pr/hidrive-next-pr-.zip | + # | ionos-dev | dev | dev/hidrive-next-//hidrive-next-.zip | + # | ionos-stable | stable | stable/hidrive-next-//hidrive-next-.zip | + # | rc/* | | rc//hidrive-next-//hidrive-next-.zip | + # | */dev/* | dev- | dev-/hidrive-next-//hidrive-next-.zip | ARTIFACTORY_STAGE_PREFIX="dev" @@ -760,10 +760,16 @@ jobs: fi export PATH_TO_DIRECTORY="${{ env.ARTIFACTORY_REPOSITORY_SNAPSHOT }}/${ARTIFACTORY_STAGE_PREFIX}" - PATH_TO_FILE="pr/hidrive-next-pr-${{ github.event.pull_request.number }}.zip" - if [ -z "${{ github.event.pull_request.number }}" ]; then - PATH_TO_FILE="hidrive-next-${{ needs.hidrive-next-build.outputs.NC_VERSION }}.zip" + # PR uploads keep the flat layout (one slot per PR number); + # branch uploads nest under // to preserve every build + if [ -n "${{ github.event.pull_request.number }}" ]; then + PATH_TO_FILE="pr/hidrive-next-pr-${{ github.event.pull_request.number }}.zip" + else + SHORT_SHA="${{ github.sha }}" + SHORT_SHA="${SHORT_SHA:0:7}" + NC_VERSION="${{ needs.hidrive-next-build.outputs.NC_VERSION }}" + PATH_TO_FILE="hidrive-next-${NC_VERSION}/${SHORT_SHA}/hidrive-next-${NC_VERSION}.zip" fi export PATH_TO_LATEST_ARTIFACT="${PATH_TO_DIRECTORY}/${PATH_TO_FILE}"