From 81bd690a9667e208fd23642739c20f9fc86f37cc Mon Sep 17 00:00:00 2001 From: yaroslavmokflmg Date: Mon, 6 Jul 2026 19:40:57 -0400 Subject: [PATCH 1/9] Add default release patch+1 to release workflow --- .github/steps/resolve-version/action.yml | 46 ++++++++++++++++++++++++ .github/workflows/release.yml | 40 +++++++++++++++------ 2 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 .github/steps/resolve-version/action.yml diff --git a/.github/steps/resolve-version/action.yml b/.github/steps/resolve-version/action.yml new file mode 100644 index 0000000000..eedb535bff --- /dev/null +++ b/.github/steps/resolve-version/action.yml @@ -0,0 +1,46 @@ +name: 'Resolve Version' +description: 'Resolves the release version: patch+1 auto bump when empty, provided version used as-is' + +inputs: + version: + description: 'Requested version (x.y.z). Leave empty for auto patch bump' + required: false + default: '' + +outputs: + version: + description: 'Resolved release version' + value: ${{ steps.resolve.outputs.version }} + +runs: + using: "composite" + steps: + - name: Resolve version + id: resolve + shell: bash + env: + INPUT_VERSION: ${{ inputs.version }} + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + latest=$(gh release view --json tagName --jq '.tagName' 2>/dev/null || echo "") + latest=${latest:-0.0.0} + if ! [[ "$latest" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + latest="0.0.0" + fi + IFS='.' read -r cur_major cur_minor cur_patch <<< "$latest" + + if [ -z "$INPUT_VERSION" ]; then + version="${cur_major}.${cur_minor}.$((cur_patch + 1))" + echo "No version provided, auto patch bump: ${latest} -> ${version}" + else + if ! [[ "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Invalid version format: ${INPUT_VERSION} (expected x.y.z)" + exit 1 + fi + version="$INPUT_VERSION" + echo "Using provided version: ${version}" + fi + + echo "version=${version}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec2e73aca1..229dec0af1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,9 +18,10 @@ on: workflow_dispatch: inputs: version: - description: "Version to release (e.g., 1.1.57)" - required: true + description: "Version to release (x.y.z). Leave empty for auto patch bump." + required: false type: string + default: '' env: REGISTRY: "ghcr.io" @@ -32,6 +33,23 @@ env: # ============================================================================= jobs: + resolve: + name: Resolve Version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.resolve.outputs.version }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Resolve version + id: resolve + if: github.event_name == 'workflow_dispatch' + uses: ./.github/steps/resolve-version + with: + version: ${{ inputs.version }} + changes: name: Detect Changes uses: ./.github/workflows/changes.yml @@ -42,11 +60,11 @@ jobs: build: name: "Build meshcentral" - needs: [changes] + needs: [resolve, changes] runs-on: ubuntu-latest if: | (github.event_name == 'push' && needs.changes.outputs.docker == 'true') || - (github.event_name == 'workflow_dispatch' && inputs.version != '') + (github.event_name == 'workflow_dispatch' && needs.resolve.outputs.version != '') steps: - name: Checkout uses: actions/checkout@v4 @@ -87,7 +105,7 @@ jobs: tags: | type=ref,event=tag type=raw,value=latest,enable={{is_default_branch}} - type=raw,value=${{ inputs.version }},enable=${{ inputs.version != '' }} + type=raw,value=${{ needs.resolve.outputs.version }},enable=${{ needs.resolve.outputs.version != '' }} labels: | org.opencontainers.image.vendor=${{ env.ORGANISATION }} org.opencontainers.image.description=MeshCentral - full computer management web site @@ -120,11 +138,11 @@ jobs: build_helm: name: "Build Helm Chart" - needs: [changes] + needs: [resolve, changes] runs-on: ubuntu-latest if: | (github.event_name == 'push' && needs.changes.outputs.helm == 'true') || - (github.event_name == 'workflow_dispatch' && inputs.version != '') + (github.event_name == 'workflow_dispatch' && needs.resolve.outputs.version != '') steps: - name: Checkout uses: actions/checkout@v4 @@ -146,7 +164,7 @@ jobs: release: name: "Create Release" - needs: [build, build_helm] + needs: [resolve, build, build_helm] runs-on: ubuntu-latest if: ${{ !failure() && !cancelled() }} steps: @@ -155,7 +173,7 @@ jobs: - name: Generate release header run: | - VERSION="${{ inputs.version || 'latest' }}" + VERSION="${{ needs.resolve.outputs.version || 'latest' }}" cat > RELEASE_HEADER.md < Date: Tue, 7 Jul 2026 08:41:10 -0400 Subject: [PATCH 2/9] ci(release): package version resolution as reusable workflow --- .github/workflows/release.yml | 38 +++++++++++++---------------------- .github/workflows/version.yml | 31 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/version.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 229dec0af1..7170252085 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,22 +33,12 @@ env: # ============================================================================= jobs: - resolve: + version: name: Resolve Version - runs-on: ubuntu-latest - outputs: - version: ${{ steps.resolve.outputs.version }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Resolve version - id: resolve - if: github.event_name == 'workflow_dispatch' - uses: ./.github/steps/resolve-version - with: - version: ${{ inputs.version }} + if: github.event_name == 'workflow_dispatch' + uses: ./.github/workflows/version.yml + with: + version: ${{ inputs.version }} changes: name: Detect Changes @@ -60,11 +50,11 @@ jobs: build: name: "Build meshcentral" - needs: [resolve, changes] + needs: [version, changes] runs-on: ubuntu-latest if: | (github.event_name == 'push' && needs.changes.outputs.docker == 'true') || - (github.event_name == 'workflow_dispatch' && needs.resolve.outputs.version != '') + (github.event_name == 'workflow_dispatch' && needs.version.outputs.version != '') steps: - name: Checkout uses: actions/checkout@v4 @@ -105,7 +95,7 @@ jobs: tags: | type=ref,event=tag type=raw,value=latest,enable={{is_default_branch}} - type=raw,value=${{ needs.resolve.outputs.version }},enable=${{ needs.resolve.outputs.version != '' }} + type=raw,value=${{ needs.version.outputs.version }},enable=${{ needs.version.outputs.version != '' }} labels: | org.opencontainers.image.vendor=${{ env.ORGANISATION }} org.opencontainers.image.description=MeshCentral - full computer management web site @@ -138,11 +128,11 @@ jobs: build_helm: name: "Build Helm Chart" - needs: [resolve, changes] + needs: [version, changes] runs-on: ubuntu-latest if: | (github.event_name == 'push' && needs.changes.outputs.helm == 'true') || - (github.event_name == 'workflow_dispatch' && needs.resolve.outputs.version != '') + (github.event_name == 'workflow_dispatch' && needs.version.outputs.version != '') steps: - name: Checkout uses: actions/checkout@v4 @@ -164,7 +154,7 @@ jobs: release: name: "Create Release" - needs: [resolve, build, build_helm] + needs: [version, build, build_helm] runs-on: ubuntu-latest if: ${{ !failure() && !cancelled() }} steps: @@ -173,7 +163,7 @@ jobs: - name: Generate release header run: | - VERSION="${{ needs.resolve.outputs.version || 'latest' }}" + VERSION="${{ needs.version.outputs.version || 'latest' }}" cat > RELEASE_HEADER.md < Date: Tue, 7 Jul 2026 08:51:46 -0400 Subject: [PATCH 3/9] ci(release): package version resolution as reusable workflow --- .github/workflows/version.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 38e0474bff..d5980ef955 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -18,14 +18,14 @@ jobs: name: Resolve version runs-on: ubuntu-latest outputs: - version: ${{ steps.resolve.outputs.version }} + version: ${{ steps.version.outputs.version }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Resolve version - id: resolve + id: version uses: ./.github/steps/resolve-version with: version: ${{ inputs.version }} From ba09c8d3747d82b0303debb5c8632fe196a08111 Mon Sep 17 00:00:00 2001 From: yaroslavmokflmg Date: Tue, 7 Jul 2026 11:24:33 -0400 Subject: [PATCH 4/9] ci(release): inline version resolution, drop resolve-version composite action --- .github/steps/resolve-version/action.yml | 46 ------------------------ .github/workflows/version.yml | 25 +++++++++++-- 2 files changed, 22 insertions(+), 49 deletions(-) delete mode 100644 .github/steps/resolve-version/action.yml diff --git a/.github/steps/resolve-version/action.yml b/.github/steps/resolve-version/action.yml deleted file mode 100644 index eedb535bff..0000000000 --- a/.github/steps/resolve-version/action.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: 'Resolve Version' -description: 'Resolves the release version: patch+1 auto bump when empty, provided version used as-is' - -inputs: - version: - description: 'Requested version (x.y.z). Leave empty for auto patch bump' - required: false - default: '' - -outputs: - version: - description: 'Resolved release version' - value: ${{ steps.resolve.outputs.version }} - -runs: - using: "composite" - steps: - - name: Resolve version - id: resolve - shell: bash - env: - INPUT_VERSION: ${{ inputs.version }} - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - - latest=$(gh release view --json tagName --jq '.tagName' 2>/dev/null || echo "") - latest=${latest:-0.0.0} - if ! [[ "$latest" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - latest="0.0.0" - fi - IFS='.' read -r cur_major cur_minor cur_patch <<< "$latest" - - if [ -z "$INPUT_VERSION" ]; then - version="${cur_major}.${cur_minor}.$((cur_patch + 1))" - echo "No version provided, auto patch bump: ${latest} -> ${version}" - else - if ! [[ "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Invalid version format: ${INPUT_VERSION} (expected x.y.z)" - exit 1 - fi - version="$INPUT_VERSION" - echo "Using provided version: ${version}" - fi - - echo "version=${version}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index d5980ef955..8a8739ef22 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -26,6 +26,25 @@ jobs: - name: Resolve version id: version - uses: ./.github/steps/resolve-version - with: - version: ${{ inputs.version }} + env: + INPUT_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + + latest=$(git tag --list | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1) + latest=${latest:-0.0.0} + IFS='.' read -r cur_major cur_minor cur_patch <<< "$latest" + + if [ -z "$INPUT_VERSION" ]; then + version="${cur_major}.${cur_minor}.$((cur_patch + 1))" + echo "No version provided, auto patch bump: ${latest} -> ${version}" + else + if ! [[ "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Invalid version format: ${INPUT_VERSION} (expected x.y.z)" + exit 1 + fi + version="$INPUT_VERSION" + echo "Using provided version: ${version}" + fi + + echo "version=${version}" >> "$GITHUB_OUTPUT" From e3640e5858d2a40f0436af8ac2581402d5a9cdef Mon Sep 17 00:00:00 2001 From: yaroslavmokflmg Date: Tue, 7 Jul 2026 15:35:34 -0400 Subject: [PATCH 5/9] ci(release): tag helm chart with resolved version, not raw input --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7170252085..6f8e33700f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -145,7 +145,7 @@ jobs: with: name: meshcentral repository: ${{ github.repository }}/helm-charts - tag: ${{ inputs.version || '9.9.9' }} + tag: ${{ needs.version.outputs.version || '9.9.9' }} path: ./charts/meshcentral registry: ${{ env.REGISTRY }} registry_username: ${{ github.actor }} From 6e3172cbeaaa5f4a7107f227734843649fb05087 Mon Sep 17 00:00:00 2001 From: yaroslavmokflmg Date: Tue, 7 Jul 2026 15:44:59 -0400 Subject: [PATCH 6/9] ci(release): resolve version from GitHub Releases and validate explicit bumps --- .github/workflows/version.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 8a8739ef22..b57cd4e849 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -20,18 +20,18 @@ jobs: outputs: version: ${{ steps.version.outputs.version }} steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Resolve version id: version env: INPUT_VERSION: ${{ inputs.version }} + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} run: | set -euo pipefail - - latest=$(git tag --list | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1) + # Releases, not git tags: upstream's inherited bare tags and the 9.9.x sentinel outrank the real line under sort -V + latest=$(gh release list --limit 100 --exclude-drafts --exclude-pre-releases \ + --json tagName --jq '.[].tagName' \ + | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | grep -v '^9\.9\.' | sort -V | tail -1 || true) latest=${latest:-0.0.0} IFS='.' read -r cur_major cur_minor cur_patch <<< "$latest" @@ -43,6 +43,13 @@ jobs: echo "Invalid version format: ${INPUT_VERSION} (expected x.y.z)" exit 1 fi + IFS='.' read -r new_major new_minor new_patch <<< "$INPUT_VERSION" + if ! ([ "$new_major" -eq $((cur_major + 1)) ] && [ "$new_minor" -eq 0 ] && [ "$new_patch" -eq 0 ]) && + ! ([ "$new_major" -eq "$cur_major" ] && [ "$new_minor" -eq $((cur_minor + 1)) ] && [ "$new_patch" -eq 0 ]) && + ! ([ "$new_major" -eq "$cur_major" ] && [ "$new_minor" -eq "$cur_minor" ] && [ "$new_patch" -eq $((cur_patch + 1)) ]); then + echo "Invalid version bump: ${latest} -> ${INPUT_VERSION} (expected $((cur_major + 1)).0.0, ${cur_major}.$((cur_minor + 1)).0, or ${cur_major}.${cur_minor}.$((cur_patch + 1)))" + exit 1 + fi version="$INPUT_VERSION" echo "Using provided version: ${version}" fi From 2454079d79d61513c457d4fba6317f2891602643 Mon Sep 17 00:00:00 2001 From: yaroslavmokflmg Date: Tue, 7 Jul 2026 16:19:17 -0400 Subject: [PATCH 7/9] ci(release): keep build jobs running when version is skipped on push --- .github/workflows/release.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f8e33700f..f2414fce06 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,8 +53,11 @@ jobs: needs: [version, changes] runs-on: ubuntu-latest if: | - (github.event_name == 'push' && needs.changes.outputs.docker == 'true') || - (github.event_name == 'workflow_dispatch' && needs.version.outputs.version != '') + !failure() && !cancelled() && + ( + (github.event_name == 'push' && needs.changes.outputs.docker == 'true') || + (github.event_name == 'workflow_dispatch' && needs.version.outputs.version != '') + ) steps: - name: Checkout uses: actions/checkout@v4 @@ -131,8 +134,11 @@ jobs: needs: [version, changes] runs-on: ubuntu-latest if: | - (github.event_name == 'push' && needs.changes.outputs.helm == 'true') || - (github.event_name == 'workflow_dispatch' && needs.version.outputs.version != '') + !failure() && !cancelled() && + ( + (github.event_name == 'push' && needs.changes.outputs.helm == 'true') || + (github.event_name == 'workflow_dispatch' && needs.version.outputs.version != '') + ) steps: - name: Checkout uses: actions/checkout@v4 From 717c140d3e04ebfc2ab1b4bab8a614f1c12cf4be Mon Sep 17 00:00:00 2001 From: yaroslavmokflmg Date: Tue, 7 Jul 2026 17:35:44 -0400 Subject: [PATCH 8/9] ci(release): resolve version by event inside version.yml so latest updates on push --- .github/workflows/release.yml | 24 ++++++++---------------- .github/workflows/version.yml | 8 ++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f2414fce06..640afbdf01 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,6 @@ env: jobs: version: name: Resolve Version - if: github.event_name == 'workflow_dispatch' uses: ./.github/workflows/version.yml with: version: ${{ inputs.version }} @@ -53,11 +52,8 @@ jobs: needs: [version, changes] runs-on: ubuntu-latest if: | - !failure() && !cancelled() && - ( - (github.event_name == 'push' && needs.changes.outputs.docker == 'true') || - (github.event_name == 'workflow_dispatch' && needs.version.outputs.version != '') - ) + (github.event_name == 'push' && needs.changes.outputs.docker == 'true') || + (github.event_name == 'workflow_dispatch' && needs.version.outputs.version != '') steps: - name: Checkout uses: actions/checkout@v4 @@ -134,11 +130,8 @@ jobs: needs: [version, changes] runs-on: ubuntu-latest if: | - !failure() && !cancelled() && - ( - (github.event_name == 'push' && needs.changes.outputs.helm == 'true') || - (github.event_name == 'workflow_dispatch' && needs.version.outputs.version != '') - ) + (github.event_name == 'push' && needs.changes.outputs.helm == 'true') || + (github.event_name == 'workflow_dispatch' && needs.version.outputs.version != '') steps: - name: Checkout uses: actions/checkout@v4 @@ -151,7 +144,7 @@ jobs: with: name: meshcentral repository: ${{ github.repository }}/helm-charts - tag: ${{ needs.version.outputs.version || '9.9.9' }} + tag: ${{ needs.version.outputs.version }} path: ./charts/meshcentral registry: ${{ env.REGISTRY }} registry_username: ${{ github.actor }} @@ -162,14 +155,13 @@ jobs: name: "Create Release" needs: [version, build, build_helm] runs-on: ubuntu-latest - if: ${{ !failure() && !cancelled() }} steps: - name: Checkout uses: actions/checkout@v4 - name: Generate release header run: | - VERSION="${{ needs.version.outputs.version || 'latest' }}" + VERSION="${{ needs.version.outputs.version }}" cat > RELEASE_HEADER.md <> "$GITHUB_OUTPUT" + exit 0 + fi + # Releases, not git tags: upstream's inherited bare tags and the 9.9.x sentinel outrank the real line under sort -V latest=$(gh release list --limit 100 --exclude-drafts --exclude-pre-releases \ --json tagName --jq '.[].tagName' \ From 295966a71203fdfb054b2b1de081e2e156236d72 Mon Sep 17 00:00:00 2001 From: yaroslavmokflmg Date: Tue, 7 Jul 2026 17:40:34 -0400 Subject: [PATCH 9/9] ci(version): split resolve into push and dispatch steps --- .github/workflows/version.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 012d596239..37f882e6b5 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -18,24 +18,25 @@ jobs: name: Resolve version runs-on: ubuntu-latest outputs: - version: ${{ steps.version.outputs.version }} + version: ${{ steps.version_push.outputs.version || steps.version_dispatch.outputs.version }} steps: - - name: Resolve version - id: version + - name: Resolve version (push) + id: version_push + if: github.event_name != 'workflow_dispatch' + run: | + echo "push event: version resolves to latest" + echo "version=latest" >> "$GITHUB_OUTPUT" + + - name: Resolve version (dispatch) + id: version_dispatch + if: github.event_name == 'workflow_dispatch' env: INPUT_VERSION: ${{ inputs.version }} - EVENT_NAME: ${{ github.event_name }} GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} run: | set -euo pipefail - if [ "$EVENT_NAME" != "workflow_dispatch" ]; then - echo "push event: version resolves to latest" - echo "version=latest" >> "$GITHUB_OUTPUT" - exit 0 - fi - # Releases, not git tags: upstream's inherited bare tags and the 9.9.x sentinel outrank the real line under sort -V latest=$(gh release list --limit 100 --exclude-drafts --exclude-pre-releases \ --json tagName --jq '.[].tagName' \