282 lines
9.1 KiB
Bash
282 lines
9.1 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ============================================================================
|
|
# build-and-push.sh
|
|
# Location: repo root (e.g. /docker/develop/backup-monitoring)
|
|
#
|
|
# Purpose:
|
|
# - Automatic version bump:
|
|
# 1 = patch, 2 = minor, 3 = major, t = test
|
|
# - Test builds: only update :dev (no commit/tag)
|
|
# - Release builds: update version.txt, commit, tag, push (to the current branch)
|
|
# - Build & push Docker images for each service under ./compose/*
|
|
# - Preflight checks: Docker daemon up, logged in to registry, valid names/tags
|
|
# - Summary: show all images + tags built and pushed
|
|
# - Branch visibility:
|
|
# - Shows currently checked out branch (authoritative)
|
|
# - Reads .last-branch for info (if present) when BRANCH is not set
|
|
# - Writes the current branch back to .last-branch at the end
|
|
#
|
|
# Usage:
|
|
# BRANCH=<branch> ./build-and-push.sh [bump] # BRANCH is optional; informative only
|
|
# ./build-and-push.sh [bump]
|
|
# If [bump] is omitted, you will be prompted (default = t).
|
|
#
|
|
# Tagging rules:
|
|
# - Release build (1/2/3): push :<version>, :dev, :latest
|
|
# - Test build (t): push only :dev (no :latest, no version tag)
|
|
# ============================================================================
|
|
|
|
DOCKER_REGISTRY="gitea.oskamp.info"
|
|
DOCKER_NAMESPACE="ivooskamp"
|
|
|
|
VERSION_FILE="version.txt"
|
|
START_VERSION="v0.1.0"
|
|
COMPOSE_DIR="containers"
|
|
LAST_BRANCH_FILE=".last-branch" # stored in repo root
|
|
|
|
# --- Input: prompt if missing ------------------------------------------------
|
|
BUMP="${1:-}"
|
|
if [[ -z "${BUMP}" ]]; then
|
|
echo "Select bump type: [1] patch, [2] minor, [3] major, [t] test (default: t)"
|
|
read -r BUMP
|
|
BUMP="${BUMP:-t}"
|
|
fi
|
|
|
|
if [[ "$BUMP" != "1" && "$BUMP" != "2" && "$BUMP" != "3" && "$BUMP" != "t" ]]; then
|
|
echo "[ERROR] Unknown bump type '$BUMP' (use 1, 2, 3, or t)."
|
|
exit 1
|
|
fi
|
|
|
|
# --- Helpers -----------------------------------------------------------------
|
|
read_version() {
|
|
if [[ -f "$VERSION_FILE" ]]; then
|
|
tr -d ' \t\n\r' < "$VERSION_FILE"
|
|
else
|
|
echo "$START_VERSION"
|
|
fi
|
|
}
|
|
|
|
write_version() {
|
|
echo "$1" > "$VERSION_FILE"
|
|
}
|
|
|
|
bump_version() {
|
|
local cur="$1"
|
|
local kind="$2"
|
|
local core="${cur#v}"
|
|
IFS='.' read -r MA MI PA <<< "$core"
|
|
case "$kind" in
|
|
1) PA=$((PA + 1));;
|
|
2) MI=$((MI + 1)); PA=0;;
|
|
3) MA=$((MA + 1)); MI=0; PA=0;;
|
|
*) echo "[ERROR] Unknown bump kind"; exit 1;;
|
|
esac
|
|
echo "v${MA}.${MI}.${PA}"
|
|
}
|
|
|
|
check_docker_ready() {
|
|
if ! docker info >/dev/null 2>&1; then
|
|
echo "[ERROR] Docker daemon not reachable. Is Docker running and do you have permission to use it?"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
ensure_registry_login() {
|
|
local cfg="${HOME}/.docker/config.json"
|
|
if [[ ! -f "$cfg" ]]; then
|
|
echo "[ERROR] Docker config not found at $cfg. Please login: docker login ${DOCKER_REGISTRY}"
|
|
exit 1
|
|
fi
|
|
if ! grep -q "\"${DOCKER_REGISTRY}\"" "$cfg"; then
|
|
echo "[ERROR] No registry auth found for ${DOCKER_REGISTRY}. Please run: docker login ${DOCKER_REGISTRY}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
validate_repo_component() {
|
|
local comp="$1"
|
|
if [[ ! "$comp" =~ ^[a-z0-9]+([._-][a-z0-9]+)*$ ]]; then
|
|
echo "[ERROR] Invalid repository component '$comp'."
|
|
echo " Must match: ^[a-z0-9]+([._-][a-z0-9]+)*$ (lowercase, digits, ., _, - as separators)."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
validate_tag() {
|
|
local tag="$1"
|
|
local len="${#tag}"
|
|
if (( len < 1 || len > 128 )); then
|
|
echo "[ERROR] Invalid tag length ($len). Must be between 1 and 128 characters."
|
|
return 1
|
|
fi
|
|
if [[ ! "$tag" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]*$ ]]; then
|
|
echo "[ERROR] Invalid tag '$tag'. Allowed: [A-Za-z0-9_.-], must start with alphanumeric or underscore."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# --- Preflight ---------------------------------------------------------------
|
|
if [[ ! -d ".git" ]]; then
|
|
echo "[ERROR] Not a git repository (.git missing)."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -d "$COMPOSE_DIR" ]]; then
|
|
echo "[ERROR] '$COMPOSE_DIR' directory missing. Expected ./compose/<service>/ with a Dockerfile."
|
|
exit 1
|
|
fi
|
|
|
|
check_docker_ready
|
|
ensure_registry_login
|
|
validate_repo_component "$DOCKER_NAMESPACE"
|
|
|
|
# Detect currently checked out branch (authoritative for this script)
|
|
DETECTED_BRANCH="$(git branch --show-current 2>/dev/null || true)"
|
|
if [[ -z "$DETECTED_BRANCH" ]]; then
|
|
DETECTED_BRANCH="$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)"
|
|
fi
|
|
if [[ -z "$DETECTED_BRANCH" ]]; then
|
|
# Try to derive from upstream
|
|
UPSTREAM_REF_DERIVED="$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || true)"
|
|
if [[ -n "$UPSTREAM_REF_DERIVED" ]]; then
|
|
DETECTED_BRANCH="${UPSTREAM_REF_DERIVED#origin/}"
|
|
fi
|
|
fi
|
|
if [[ -z "$DETECTED_BRANCH" ]]; then
|
|
DETECTED_BRANCH="main"
|
|
fi
|
|
|
|
# Optional signals: BRANCH env and .last-branch (informational only)
|
|
ENV_BRANCH="${BRANCH:-}"
|
|
LAST_BRANCH_FILE_PATH="$(pwd)/$LAST_BRANCH_FILE"
|
|
LAST_BRANCH_VALUE=""
|
|
if [[ -z "$ENV_BRANCH" && -f "$LAST_BRANCH_FILE_PATH" ]]; then
|
|
LAST_BRANCH_VALUE="$(tr -d ' \t\n\r' < "$LAST_BRANCH_FILE_PATH")"
|
|
fi
|
|
|
|
UPSTREAM_REF="$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || echo "origin/$DETECTED_BRANCH")"
|
|
HEAD_SHA="$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")"
|
|
|
|
echo "[INFO] Repo: $(pwd)"
|
|
echo "[INFO] Current branch: $DETECTED_BRANCH"
|
|
echo "[INFO] Upstream: $UPSTREAM_REF"
|
|
echo "[INFO] HEAD (sha): $HEAD_SHA"
|
|
|
|
if [[ -n "$ENV_BRANCH" && "$ENV_BRANCH" != "$DETECTED_BRANCH" ]]; then
|
|
echo "[WARNING] BRANCH='$ENV_BRANCH' differs from checked out branch '$DETECTED_BRANCH'."
|
|
echo "[WARNING] This script does not switch branches; continuing on '$DETECTED_BRANCH'."
|
|
fi
|
|
|
|
if [[ -n "$LAST_BRANCH_VALUE" && "$LAST_BRANCH_VALUE" != "$DETECTED_BRANCH" && -z "$ENV_BRANCH" ]]; then
|
|
echo "[INFO] .last-branch suggests '$LAST_BRANCH_VALUE', but current checkout is '$DETECTED_BRANCH'."
|
|
echo "[INFO] If you intended to build '$LAST_BRANCH_VALUE', switch branches first (use update-and-build.sh)."
|
|
fi
|
|
|
|
# --- Versioning --------------------------------------------------------------
|
|
CURRENT_VERSION="$(read_version)"
|
|
NEW_VERSION="$CURRENT_VERSION"
|
|
DO_TAG_AND_BUMP=true
|
|
|
|
if [[ "$BUMP" == "t" ]]; then
|
|
echo "[INFO] Test build: keeping version $CURRENT_VERSION; will only update :dev."
|
|
DO_TAG_AND_BUMP=false
|
|
else
|
|
NEW_VERSION="$(bump_version "$CURRENT_VERSION" "$BUMP")"
|
|
echo "[INFO] New version: $NEW_VERSION"
|
|
fi
|
|
|
|
if $DO_TAG_AND_BUMP; then
|
|
validate_tag "$NEW_VERSION"
|
|
validate_tag "latest"
|
|
fi
|
|
validate_tag "dev"
|
|
|
|
# --- Version update + VCS ops (release builds only) --------------------------
|
|
if $DO_TAG_AND_BUMP; then
|
|
echo "[INFO] Writing $NEW_VERSION to $VERSION_FILE"
|
|
write_version "$NEW_VERSION"
|
|
|
|
echo "[INFO] Git add + commit (branch: $DETECTED_BRANCH)"
|
|
git add "$VERSION_FILE"
|
|
git commit -m "Release $NEW_VERSION on branch $DETECTED_BRANCH (bump type $BUMP)"
|
|
|
|
echo "[INFO] Git tag $NEW_VERSION"
|
|
git tag -a "$NEW_VERSION" -m "Release $NEW_VERSION"
|
|
|
|
echo "[INFO] Git push + tags"
|
|
git push origin "$DETECTED_BRANCH"
|
|
git push --tags
|
|
else
|
|
echo "[INFO] Skipping commit/tagging (test build)."
|
|
fi
|
|
|
|
# --- Build & push per service ------------------------------------------------
|
|
shopt -s nullglob
|
|
services=( "$COMPOSE_DIR"/* )
|
|
if [[ ${#services[@]} -eq 0 ]]; then
|
|
echo "[ERROR] No services found under $COMPOSE_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
BUILT_IMAGES=()
|
|
|
|
for svc_path in "${services[@]}"; do
|
|
[[ -d "$svc_path" ]] || continue
|
|
svc="$(basename "$svc_path")"
|
|
dockerfile="$svc_path/Dockerfile"
|
|
|
|
validate_repo_component "$svc"
|
|
|
|
if [[ ! -f "$dockerfile" ]]; then
|
|
echo "[WARNING] Skipping '${svc}': Dockerfile not found in ${svc_path}"
|
|
continue
|
|
fi
|
|
|
|
IMAGE_BASE="${DOCKER_REGISTRY}/${DOCKER_NAMESPACE}/${svc}"
|
|
|
|
if $DO_TAG_AND_BUMP; then
|
|
echo "============================================================"
|
|
echo "[INFO] Building ${svc} -> tags: ${NEW_VERSION}, dev, latest"
|
|
echo "============================================================"
|
|
docker build \
|
|
-t "${IMAGE_BASE}:${NEW_VERSION}" \
|
|
-t "${IMAGE_BASE}:dev" \
|
|
-t "${IMAGE_BASE}:latest" \
|
|
"$svc_path"
|
|
|
|
docker push "${IMAGE_BASE}:${NEW_VERSION}"
|
|
docker push "${IMAGE_BASE}:dev"
|
|
docker push "${IMAGE_BASE}:latest"
|
|
|
|
BUILT_IMAGES+=("${IMAGE_BASE}:${NEW_VERSION}" "${IMAGE_BASE}:dev" "${IMAGE_BASE}:latest")
|
|
else
|
|
echo "============================================================"
|
|
echo "[INFO] Test build ${svc} -> tag: dev"
|
|
echo "============================================================"
|
|
docker build -t "${IMAGE_BASE}:dev" "$svc_path"
|
|
docker push "${IMAGE_BASE}:dev"
|
|
BUILT_IMAGES+=("${IMAGE_BASE}:dev")
|
|
fi
|
|
done
|
|
|
|
# --- Persist current branch to .last-branch ----------------------------------
|
|
# (This helps script 1 to preselect next time, and is informative if you run script 2 standalone)
|
|
echo "$DETECTED_BRANCH" > "$LAST_BRANCH_FILE_PATH"
|
|
|
|
# --- Summary -----------------------------------------------------------------
|
|
echo ""
|
|
echo "============================================================"
|
|
echo "[SUMMARY] Build & push complete (branch: $DETECTED_BRANCH)"
|
|
if $DO_TAG_AND_BUMP; then
|
|
echo "[INFO] Release version: $NEW_VERSION"
|
|
else
|
|
echo "[INFO] Test build (no version bump)"
|
|
fi
|
|
echo "[INFO] Images pushed:"
|
|
for img in "${BUILT_IMAGES[@]}"; do
|
|
echo " - $img"
|
|
done
|
|
echo "============================================================"
|