name: https://github.com/AIIrondev/legendary-octo-garbanzo on: push: tags: - "v*" workflow_dispatch: inputs: bump: description: "Version bump type (major stays fixed from latest release)" required: false default: "patch" type: choice options: - patch - minor - major - development push_dev: description: "If true, push the :dev image to GHCR for development releases" required: false default: "false" type: choice options: - "true" - "false" permissions: contents: write packages: write env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" jobs: release-docker: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set metadata id: meta env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} EVENT_NAME: ${{ github.event_name }} REF_NAME: ${{ github.ref_name }} BUMP_TYPE: ${{ github.event.inputs.bump || 'patch' }} PUSH_DEV: ${{ github.event.inputs.push_dev || 'false' }} run: | if [ "$EVENT_NAME" = "push" ] && [ -n "$REF_NAME" ]; then TAG="$REF_NAME" else TAG="$(python3 - <<'PY' import json import os import re import urllib.request repo = os.environ["REPO"] token = os.environ.get("GH_TOKEN", "") bump = os.environ.get("BUMP_TYPE", "patch").strip().lower() req = urllib.request.Request( f"https://api.github.com/repos/{repo}/releases/latest", headers={ "Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json", "User-Agent": "legendary-octo-garbanzo-release-workflow", }, ) latest_tag = "v3.0.0" try: with urllib.request.urlopen(req, timeout=20) as resp: data = json.loads(resp.read().decode("utf-8")) latest_tag = (data.get("tag_name") or latest_tag).strip() except Exception: pass m = re.match(r"^v(\d+)\.(\d+)\.(\d+)$", latest_tag) if not m: major, minor, patch = 3, 0, 0 else: major, minor, patch = map(int, m.groups()) # Bump strategy: major / minor / patch if bump == "major": major += 1 minor = 0 patch = 0 elif bump == "minor": minor += 1 patch = 0 else: patch += 1 if bump == "development": # use dev suffix for development releases print(f"v{major}.{minor}.{patch}-dev") else: print(f"v{major}.{minor}.{patch}") PY )" fi if ! echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+(-dev(\.[0-9]+)?)?$'; then echo "Error: tag '$TAG' is not valid semver (vX.Y.Z or vX.Y.Z-dev)" exit 1 fi git fetch --tags --force LATEST_TAG="$(git tag -l 'v*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n1)" if [ -z "$LATEST_TAG" ]; then LATEST_TAG="v3.0.0" fi TAG_MAJOR="${TAG#v}" TAG_MAJOR="${TAG_MAJOR%%.*}" LATEST_MAJOR="$(echo "$LATEST_TAG" | grep -Eo '^v[0-9]+' | tr -d 'v')" if [ -z "$LATEST_MAJOR" ]; then LATEST_MAJOR="3" fi # If not explicitly bumping major, disallow changing major version if [ "${BUMP_TYPE:-}" != "major" ] && [ "$TAG_MAJOR" != "$LATEST_MAJOR" ]; then echo "Error: major version must stay v$LATEST_MAJOR.x.x (got $TAG)" exit 1 fi # Ensure tag uniqueness: if tag exists append numeric suffix if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then i=1 base="$TAG" while git rev-parse -q --verify "refs/tags/${base}.${i}" >/dev/null; do i="$((i + 1))" done TAG="${base}.${i}" fi IMAGE="ghcr.io/aiirondev/legendary-octo-garbanzo:${TAG}" echo "tag=$TAG" >> "$GITHUB_OUTPUT" echo "image=$IMAGE" >> "$GITHUB_OUTPUT" if [ "${BUMP_TYPE:-}" = "development" ]; then echo "is_development=true" >> "$GITHUB_OUTPUT" else echo "is_development=false" >> "$GITHUB_OUTPUT" fi if [ "${BUMP_TYPE:-}" = "development" ] && [ "${PUSH_DEV:-}" = "true" ]; then echo "push_dev=true" >> "$GITHUB_OUTPUT" else echo "push_dev=false" >> "$GITHUB_OUTPUT" fi - name: Create and push tag for manual releases if: github.event_name == 'workflow_dispatch' run: | TAG="${{ steps.meta.outputs.tag }}" git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git tag "$TAG" git push origin "$TAG" - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push release image if: steps.meta.outputs.is_development != 'true' uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile push: true tags: | ${{ steps.meta.outputs.image }} ghcr.io/aiirondev/legendary-octo-garbanzo:latest - name: Build and push development image (:dev) if: steps.meta.outputs.is_development == 'true' && steps.meta.outputs.push_dev == 'true' uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile push: true tags: | ghcr.io/aiirondev/legendary-octo-garbanzo:dev - name: Build local image tar for offline deploy run: | set -euo pipefail IMG="${{ steps.meta.outputs.image }}" TAG="${{ steps.meta.outputs.tag }}" if docker image inspect "$IMG" >/dev/null 2>&1; then echo "Using local image $IMG" docker save "$IMG" | gzip > "inventarsystem-image-${TAG}.tar.gz" exit 0 fi echo "Local image $IMG not found, trying to pull" if docker pull "$IMG" >/dev/null 2>&1; then docker save "$IMG" | gzip > "inventarsystem-image-${TAG}.tar.gz" exit 0 fi echo "Pull failed, attempting local docker build as fallback" docker build -t "$IMG" . docker save "$IMG" | gzip > "inventarsystem-image-${TAG}.tar.gz" # development tar omitted: dev releases will be versioned (vX.Y.Z-dev) and handled by update.sh using the tag - name: Create release-only docker bundle run: | mkdir -p release-bundle cat > release-bundle/docker-compose.yml < release-bundle/DEVELOPMENT.md <