#!/bin/bash # Push LMS images to GitHub Container Registry (GHCR) # Auto-bumps the patch version in VERSION file on every successful push. # # Usage: # ./push-ghcr.sh # auto-bump patch: 1.2.3 → 1.2.4 # ./push-ghcr.sh minor # bump minor: 1.2.3 → 1.3.0 # ./push-ghcr.sh major # bump major: 1.2.3 → 2.0.0 # ./push-ghcr.sh 2.5.1 # use exact version (no auto-bump) # ./push-ghcr.sh --no-cache # force full Docker rebuild # # Reads GITHUB_USER and GITHUB_TOKEN from .env # Version is stored in ./VERSION set -euo pipefail cd "$(dirname "$0")" # ── Colour helpers ──────────────────────────────────────────────────────────── GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m' ok() { echo -e "${GREEN}✔${RESET} $*"; } info() { echo -e "${CYAN}▶${RESET} $*"; } warn() { echo -e "${YELLOW}⚠${RESET} $*"; } fail() { echo -e "\033[0;31m✘${RESET} $*" >&2; exit 1; } step() { echo -e "\n${BOLD}${CYAN}══ $* ══${RESET}"; } # ── Parse arguments ─────────────────────────────────────────────────────────── BUMP_TYPE="patch" EXPLICIT_TAG="" NO_CACHE="" for arg in "$@"; do case $arg in major|minor|patch) BUMP_TYPE=$arg ;; --no-cache) NO_CACHE="--no-cache" ;; [0-9]*.[0-9]*.[0-9]*) EXPLICIT_TAG=$arg ;; # exact semver passed *) fail "Unknown argument: $arg" ;; esac done # ── Load .env ───────────────────────────────────────────────────────────────── [ -f .env ] || fail ".env not found. Copy .env.example → .env first." # shellcheck disable=SC2046 export $(grep -v '^#' .env | grep -E 'GITHUB_USER|GITHUB_TOKEN' | xargs) GITHUB_USER="${GITHUB_USER:?GITHUB_USER not set in .env}" GITHUB_TOKEN="${GITHUB_TOKEN:?GITHUB_TOKEN not set in .env}" # ── Read current version ────────────────────────────────────────────────────── VERSION_FILE="VERSION" [ -f "$VERSION_FILE" ] || echo "1.0.0" > "$VERSION_FILE" CURRENT=$(cat "$VERSION_FILE" | tr -d '[:space:]') if ! [[ $CURRENT =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then fail "VERSION file contains invalid semver: '$CURRENT'. Expected format: X.Y.Z" fi IFS='.' read -r V_MAJOR V_MINOR V_PATCH <<< "$CURRENT" # ── Compute new version ─────────────────────────────────────────────────────── if [ -n "$EXPLICIT_TAG" ]; then NEW_VERSION="$EXPLICIT_TAG" info "Using explicit version: ${BOLD}$NEW_VERSION${RESET}" else case $BUMP_TYPE in major) NEW_VERSION="$((V_MAJOR+1)).0.0" ;; minor) NEW_VERSION="${V_MAJOR}.$((V_MINOR+1)).0" ;; patch) NEW_VERSION="${V_MAJOR}.${V_MINOR}.$((V_PATCH+1))" ;; esac info "Bumping ${BUMP_TYPE}: ${BOLD}${CURRENT}${RESET} → ${BOLD}${NEW_VERSION}${RESET}" fi # ── Image names ─────────────────────────────────────────────────────────────── REGISTRY="ghcr.io/${GITHUB_USER}" BACKEND_VERSIONED="${REGISTRY}/lms-backend:${NEW_VERSION}" FRONTEND_VERSIONED="${REGISTRY}/lms-frontend:${NEW_VERSION}" BACKEND_LATEST="${REGISTRY}/lms-backend:latest" FRONTEND_LATEST="${REGISTRY}/lms-frontend:latest" step "LMS → GHCR Push" echo -e " Version : ${BOLD}${NEW_VERSION}${RESET}" echo -e " Backend : ${BACKEND_VERSIONED}" echo -e " Frontend : ${FRONTEND_VERSIONED}" # ── 1. Login to GHCR ───────────────────────────────────────────────────────── step "Login to ghcr.io" echo "$GITHUB_TOKEN" | docker login ghcr.io -u "$GITHUB_USER" --password-stdin ok "Logged in" # ── 2. Build images ─────────────────────────────────────────────────────────── step "Building images" [ -n "$NO_CACHE" ] && warn "Building without layer cache" # shellcheck disable=SC2086 docker compose build $NO_CACHE ok "Build complete" # ── 3. Tag versioned + latest ───────────────────────────────────────────────── step "Tagging images" docker tag lms-backend:latest "$BACKEND_VERSIONED" docker tag lms-frontend:latest "$FRONTEND_VERSIONED" docker tag lms-backend:latest "$BACKEND_LATEST" docker tag lms-frontend:latest "$FRONTEND_LATEST" ok "Tagged ${NEW_VERSION} + latest" # ── 4. Push versioned + latest ──────────────────────────────────────────────── step "Pushing to GHCR" docker push "$BACKEND_VERSIONED" docker push "$FRONTEND_VERSIONED" docker push "$BACKEND_LATEST" docker push "$FRONTEND_LATEST" ok "Push complete" # ── 5. Save new version (only after successful push) ───────────────────────── if [ -z "$EXPLICIT_TAG" ]; then echo "$NEW_VERSION" > "$VERSION_FILE" # Commit VERSION bump if inside a git repo if git rev-parse --git-dir &>/dev/null; then git add "$VERSION_FILE" git commit -m "chore: bump version ${CURRENT} → ${NEW_VERSION}" --no-verify 2>/dev/null \ && ok "Committed VERSION bump to git" \ || warn "VERSION updated locally but git commit skipped (nothing to commit or no git user configured)" else ok "VERSION file updated to ${NEW_VERSION}" fi fi # ── Summary ─────────────────────────────────────────────────────────────────── echo "" echo -e "${BOLD}${GREEN}══════════════════════════════════════════════════════${RESET}" echo -e "${BOLD}${GREEN} ✅ Images pushed successfully — v${NEW_VERSION}${RESET}" echo -e "${BOLD}${GREEN}══════════════════════════════════════════════════════${RESET}" echo -e " ${BOLD}Versioned tags:${RESET}" echo " $BACKEND_VERSIONED" echo " $FRONTEND_VERSIONED" echo -e " ${BOLD}Latest tags also updated:${RESET}" echo " $BACKEND_LATEST" echo " $FRONTEND_LATEST" echo "" echo -e " ${BOLD}📋 Add to .env on target machine:${RESET}" echo " BACKEND_IMAGE=${BACKEND_VERSIONED}" echo " FRONTEND_IMAGE=${FRONTEND_VERSIONED}" echo "" echo -e " ${BOLD}🚀 Deploy on target machine:${RESET}" echo " docker compose pull && docker compose up -d"