329 lines
11 KiB
YAML
329 lines
11 KiB
YAML
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
|
|
- 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 <<EOF
|
|
services:
|
|
app:
|
|
image: ${INVENTAR_APP_IMAGE:-ghcr.io/aiirondev/legendary-octo-garbanzo:${{ steps.meta.outputs.tag }}}
|
|
container_name: inventarsystem-app
|
|
restart: unless-stopped
|
|
ports:
|
|
- "${INVENTAR_HTTP_PORT:-10000}:8000"
|
|
depends_on:
|
|
- mongodb
|
|
- redis
|
|
environment:
|
|
INVENTAR_MONGODB_HOST: mongodb
|
|
INVENTAR_MONGODB_PORT: "27017"
|
|
INVENTAR_MONGODB_DB: Inventarsystem
|
|
INVENTAR_BACKUP_FOLDER: /data/backups
|
|
INVENTAR_LOGS_FOLDER: /data/logs
|
|
expose:
|
|
- "8000"
|
|
volumes:
|
|
- ./config.json:/app/config.json:ro
|
|
- app_uploads:/app/Web/uploads
|
|
- app_thumbnails:/app/Web/thumbnails
|
|
- app_previews:/app/Web/previews
|
|
- app_qrcodes:/app/Web/QRCodes
|
|
- app_backups:/data/backups
|
|
- app_logs:/data/logs
|
|
|
|
mongodb:
|
|
image: mongo:7.0
|
|
container_name: inventarsystem-mongodb
|
|
restart: unless-stopped
|
|
volumes:
|
|
- mongodb_data:/data/db
|
|
healthcheck:
|
|
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 10
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
container_name: inventarsystem-redis
|
|
restart: unless-stopped
|
|
command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
|
|
ports:
|
|
- "6379:6379"
|
|
volumes:
|
|
- redis_data:/data
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
volumes:
|
|
mongodb_data:
|
|
app_uploads:
|
|
app_thumbnails:
|
|
app_previews:
|
|
app_qrcodes:
|
|
app_backups:
|
|
app_logs:
|
|
redis_data:
|
|
EOF
|
|
|
|
# Copy runtime scripts and config if present
|
|
for f in start.sh stop.sh restart.sh backup.sh config.json update.sh; do
|
|
if [ -f "$f" ]; then
|
|
cp "$f" "release-bundle/$(basename "$f")"
|
|
fi
|
|
done
|
|
|
|
# Multitenant scripts & docs (optional)
|
|
for f in docker-compose-multitenant.yml manage-tenant.sh run-tenant-cmd.sh MULTITENANT_DEPLOYMENT.md MULTITENANT_PYTHON_API.md; do
|
|
if [ -f "$f" ]; then
|
|
cp "$f" "release-bundle/$(basename "$f")"
|
|
fi
|
|
done
|
|
|
|
cat > release-bundle/DEVELOPMENT.md <<EOF
|
|
This is a development prerelease bundle for tag: ${{ steps.meta.outputs.tag }}
|
|
|
|
This prerelease is intentionally separate from normal releases and will not be used by default.
|
|
|
|
To install this prerelease on a target host run:
|
|
|
|
./update.sh ${{ steps.meta.outputs.tag }}
|
|
|
|
EOF
|
|
|
|
# Make any shipped scripts executable
|
|
find release-bundle -maxdepth 1 -type f -name '*.sh' -exec chmod +x {} \; || true
|
|
tar -czf inventarsystem-docker-bundle.tar.gz -C release-bundle .
|
|
|
|
- name: Create or update GitHub Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: ${{ steps.meta.outputs.tag }}
|
|
prerelease: ${{ steps.meta.outputs.is_development }}
|
|
files: |
|
|
inventarsystem-docker-bundle.tar.gz
|
|
inventarsystem-image-${{ steps.meta.outputs.tag }}.tar.gz
|
|
inventarsystem-image-dev.tar.gz
|
|
fail_on_unmatched_files: false
|
|
generate_release_notes: true
|