af8a7a58a4
PROMPT-TEMPLATE: standard 'For the operator' plain-language section, mandatory for M+ tasks and anything with a STOP. ROADMAP rulings (operator, 2026-07-21): R-25b full-teardown cascade with three acks + typed name (M-sized, spec to follow, no longer blocks R-3); R-11 channel = direct Messenger, doc is the architect's; R-42 option (a), sidecars follow the app; R-17 delete the archive - spike-lite found NO tooling verb targets it, so it is an operator console action; R-4 complete (freemail.hu verified). R-55 + R-41 slice 1 marked shipped; new R-56 (app difficulty classification - the constructive half of the glance ruling). scripts/build-hub.sh v1.23.0: the hub build script was outside any repo. Adopted verbatim + versioned; the build-dir path is now a symlink to it. felhom-testing skill: the ~1/5 recovery-code 'known flake' is retired - it was a real defect the test was correctly detecting.
219 lines
8.3 KiB
Bash
Executable File
219 lines
8.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# felhom-hub — Docker image build script
|
|
# =============================================================================
|
|
# CANONICAL LOCATION: felhom.eu/scripts/build-hub.sh (this file, in git).
|
|
# OPERATIVE LOCATION: /mnt/5_hdd/felhom.eu/build/felhom-hub/build.sh — a SYMLINK to this file, so
|
|
# there is exactly one source of truth and a repo edit is live immediately (the install_skills.py
|
|
# pattern). It lived ONLY in that build dir until v1.23.0, outside any repo and therefore
|
|
# unversioned, unreviewed and unrecoverable — noted as a follow-up in felhom.eu/REPORT.md §6.
|
|
#
|
|
# Copies hub/ source from the felhom.eu git repo, builds the Docker image. Build artifacts stay in
|
|
# the build dir's workspace/ — the git repo stays clean.
|
|
#
|
|
# Usage:
|
|
# ./build.sh # Build for current platform, tag as :dev
|
|
# ./build.sh 0.1.0 # Build with version tag
|
|
# ./build.sh 0.1.0 --push # Build + push to Gitea registry
|
|
# ./build.sh 0.1.0 --multiarch # Build amd64+arm64 + push
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
SCRIPT_VERSION="1.23.0"
|
|
|
|
# --- Configuration (edit these if your paths differ) ---
|
|
REPO_DIR="/mnt/5_hdd/felhom.eu/git/felhom.eu"
|
|
HUB_SRC="${REPO_DIR}/hub"
|
|
REGISTRY="gitea.dooplex.hu/admin"
|
|
IMAGE="${REGISTRY}/felhom-hub"
|
|
|
|
# Build workspace — a temp directory next to this script, NOT in the git repo
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
BUILD_DIR="${SCRIPT_DIR}/workspace"
|
|
|
|
# --- Parse arguments ---
|
|
VERSION="${1:-dev}"
|
|
ACTION="${2:-}" # --push or --multiarch
|
|
|
|
# --- Colors ---
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
|
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|
step() { echo -e "${CYAN}[STEP]${NC} $*"; }
|
|
|
|
# --- Pre-flight checks ---
|
|
if [[ ! -d "${HUB_SRC}" ]]; then
|
|
error "Hub source not found: ${HUB_SRC}"
|
|
error "Clone the repo first: git clone https://gitea.dooplex.hu/admin/felhom.eu.git ${REPO_DIR}"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v docker &>/dev/null; then
|
|
error "Docker not found."
|
|
exit 1
|
|
fi
|
|
|
|
# Get git commit for build metadata
|
|
GIT_COMMIT=$(cd "${REPO_DIR}" && git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
|
|
|
echo ""
|
|
info "╔══════════════════════════════════════╗"
|
|
info "║ felhom-hub build ║"
|
|
info "╚══════════════════════════════════════╝"
|
|
info "Version: ${VERSION} (build-hub.sh v${SCRIPT_VERSION})"
|
|
info "Commit: ${GIT_COMMIT}"
|
|
info "Source: ${HUB_SRC}"
|
|
info "Build: ${BUILD_DIR}"
|
|
info "Image: ${IMAGE}:${VERSION}"
|
|
echo ""
|
|
|
|
# =========================================================================
|
|
# Step 1: Pull latest code
|
|
# =========================================================================
|
|
step "1/3 — Pulling latest code..."
|
|
|
|
cd "${REPO_DIR}"
|
|
git pull --ff-only 2>/dev/null || warn "git pull failed (offline or dirty tree?)"
|
|
|
|
# =========================================================================
|
|
# Step 2: Sync source to build workspace
|
|
# =========================================================================
|
|
step "2/3 — Syncing source to workspace..."
|
|
|
|
mkdir -p "${BUILD_DIR}"
|
|
|
|
rsync -a --delete \
|
|
--exclude '.git' \
|
|
--exclude 'bin/' \
|
|
--exclude '*.exe' \
|
|
"${HUB_SRC}/" "${BUILD_DIR}/"
|
|
|
|
info "Source synced to ${BUILD_DIR}"
|
|
|
|
# Sync app assets from website directory into build workspace
|
|
WEBSITE_ASSETS_DIR="${REPO_DIR}/website/assets"
|
|
if [[ -d "${WEBSITE_ASSETS_DIR}" ]]; then
|
|
mkdir -p "${BUILD_DIR}/assets"
|
|
cp "${WEBSITE_ASSETS_DIR}"/*-logo.svg "${BUILD_DIR}/assets/" 2>/dev/null || true
|
|
cp "${WEBSITE_ASSETS_DIR}"/*-logo.png "${BUILD_DIR}/assets/" 2>/dev/null || true
|
|
cp "${WEBSITE_ASSETS_DIR}"/*-screenshot-*.webp "${BUILD_DIR}/assets/" 2>/dev/null || true
|
|
ASSET_COUNT=$(ls "${BUILD_DIR}/assets/" 2>/dev/null | wc -l)
|
|
info "App assets synced (${ASSET_COUNT} files)"
|
|
else
|
|
warn "Website assets dir not found: ${WEBSITE_ASSETS_DIR}"
|
|
mkdir -p "${BUILD_DIR}/assets"
|
|
fi
|
|
|
|
# Verify structure
|
|
cd "${BUILD_DIR}"
|
|
|
|
for dir in cmd/hub internal/api internal/store internal/web; do
|
|
if [[ ! -d "${dir}" ]]; then
|
|
error "Missing expected directory: ${dir}"
|
|
error "Hub source structure may be incomplete."
|
|
exit 1
|
|
fi
|
|
done
|
|
info "Package structure verified ✓"
|
|
|
|
if [[ ! -f "go.mod" ]]; then
|
|
error "go.mod not found in build workspace!"
|
|
exit 1
|
|
fi
|
|
|
|
# Ensure go.sum exists (Dockerfile COPY will fail without it)
|
|
if command -v go &>/dev/null; then
|
|
info "Running go mod tidy..."
|
|
go mod tidy 2>&1 || warn "go mod tidy had issues (Docker build may still work)"
|
|
elif [[ ! -f "go.sum" ]]; then
|
|
warn "go.sum missing and Go not installed locally — creating empty go.sum"
|
|
warn "(Docker build stage will resolve dependencies via 'go mod download')"
|
|
touch go.sum
|
|
fi
|
|
|
|
# =========================================================================
|
|
# Step 3: Docker build
|
|
# =========================================================================
|
|
step "3/3 — Building Docker image..."
|
|
|
|
BUILD_ARGS=(
|
|
--build-arg "VERSION=${VERSION}"
|
|
--build-arg "BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
)
|
|
|
|
case "${ACTION}" in
|
|
--push)
|
|
info "Building for current platform + pushing..."
|
|
docker build "${BUILD_ARGS[@]}" \
|
|
-t "${IMAGE}:${VERSION}" \
|
|
-t "${IMAGE}:latest" \
|
|
.
|
|
|
|
info "Pushing..."
|
|
docker push "${IMAGE}:${VERSION}"
|
|
docker push "${IMAGE}:latest"
|
|
;;
|
|
|
|
--multiarch)
|
|
info "Building multi-arch (amd64 + arm64) + pushing..."
|
|
|
|
# Ensure buildx builder exists
|
|
if ! docker buildx inspect felhom-builder &>/dev/null; then
|
|
info "Creating buildx builder (one-time setup)..."
|
|
docker buildx create --name felhom-builder --use --bootstrap
|
|
else
|
|
docker buildx use felhom-builder
|
|
fi
|
|
|
|
docker buildx build "${BUILD_ARGS[@]}" \
|
|
--platform linux/amd64,linux/arm64 \
|
|
-t "${IMAGE}:${VERSION}" \
|
|
-t "${IMAGE}:latest" \
|
|
--push \
|
|
.
|
|
;;
|
|
|
|
*)
|
|
info "Building for current platform (local only)..."
|
|
docker build "${BUILD_ARGS[@]}" \
|
|
-t "${IMAGE}:${VERSION}" \
|
|
-t "${IMAGE}:latest" \
|
|
.
|
|
;;
|
|
esac
|
|
|
|
# =========================================================================
|
|
# Summary
|
|
# =========================================================================
|
|
echo ""
|
|
info "╔══════════════════════════════════════╗"
|
|
info "║ Build complete ✓ ║"
|
|
info "╚══════════════════════════════════════╝"
|
|
info "Image: ${IMAGE}:${VERSION}"
|
|
|
|
# Show image size if available locally
|
|
SIZE=$(docker image inspect "${IMAGE}:${VERSION}" --format='{{.Size}}' 2>/dev/null || echo "")
|
|
if [[ -n "${SIZE}" ]]; then
|
|
SIZE_HUMAN=$(numfmt --to=iec "${SIZE}" 2>/dev/null || echo "${SIZE} bytes")
|
|
info "Size: ${SIZE_HUMAN}"
|
|
fi
|
|
|
|
echo ""
|
|
if [[ "${ACTION}" == "" ]]; then
|
|
info "Image is local only. To push: ./build.sh ${VERSION} --push"
|
|
else
|
|
info "Image pushed to registry."
|
|
echo ""
|
|
info "Deploy to k3s — GitOps ONLY. The manifest in git is the source of truth:"
|
|
info " 1. bump the image tag in ${REPO_DIR}/manifests/hub.yaml to ${VERSION}, commit, push"
|
|
info " 2. hard-refresh + sync the ArgoCD app (auto-sync is OFF — the sync is deliberate):"
|
|
info " sudo kubectl -n argocd annotate application felhom argocd.argoproj.io/refresh=hard --overwrite"
|
|
info " sudo kubectl -n argocd patch application felhom --type merge \\"
|
|
info " -p '"'"'{\"operation\":{\"initiatedBy\":{\"username\":\"cc\"},\"sync\":{\"syncStrategy\":{\"apply\":{}}}}}'"'"'"
|
|
info ""
|
|
info " NEVER \`kubectl set image\` or \`kubectl apply\` by hand — ArgoCD reverts it on the next"
|
|
info " sync, so the change looks applied, works for a while, and then silently disappears."
|
|
fi
|
|
echo ""
|