mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-08-11 00:30:12 -04:00
Local build via Docker git context required `git` in the BuildKit worker,
which QNAP Container Station and Synology DSM don't provide. Both sidecar
images are now published to ghcr.io/maziggy/{orca-slicer-api,bambu-studio-api}
and docker.io/maziggy/{orca-slicer-api,bambu-studio-api}; compose pulls
:latest by default, SIDECAR_TAG=bambuddy-X.Y.Z pins per release.
Wiki, slicer-api README, .env.example, and CHANGELOG aligned. Build-from-source
path kept under "Building from source (advanced)" for forks / dev work.
Both images are linux/amd64 only — OrcaSlicer ARM64 on hold pending upstream
fix; BambuStudio doesn't publish ARM64.
450 lines
16 KiB
Bash
Executable file
450 lines
16 KiB
Bash
Executable file
#!/bin/bash
|
|
# Daily beta build: build Docker image, push to registries, create/update GitHub prerelease
|
|
#
|
|
# Usage:
|
|
# ./docker-publish-daily-beta.sh [--parallel] [--ghcr-only] [--dockerhub-only] [--skip-release]
|
|
#
|
|
# Examples:
|
|
# ./docker-publish-daily-beta.sh # Full daily beta workflow
|
|
# ./docker-publish-daily-beta.sh --parallel # Build both archs simultaneously
|
|
# ./docker-publish-daily-beta.sh --ghcr-only # Only push to GHCR
|
|
# ./docker-publish-daily-beta.sh --dockerhub-only # Only push to Docker Hub
|
|
# ./docker-publish-daily-beta.sh --skip-release # Build+push without GitHub release
|
|
#
|
|
# Reads APP_VERSION from backend/app/core/config.py (must be a beta version like 0.2.2b1).
|
|
# Builds and pushes a multi-arch Docker image tagged as 'daily'. Each push overwrites the
|
|
# previous 'daily' image. A GitHub prerelease is created with a date-stamped tag for history.
|
|
#
|
|
# Users can stay up to date by pulling the 'daily' tag or using Watchtower:
|
|
# docker pull ghcr.io/maziggy/bambuddy:daily
|
|
#
|
|
# Prerequisites:
|
|
# 1. Log in to ghcr.io:
|
|
# echo $GITHUB_TOKEN | docker login ghcr.io -u YOUR_USERNAME --password-stdin
|
|
#
|
|
# 2. Log in to Docker Hub:
|
|
# docker login -u YOUR_USERNAME
|
|
#
|
|
# 3. GitHub CLI (gh) authenticated for creating releases
|
|
#
|
|
# Supported architectures:
|
|
# - linux/amd64 (x86_64, most servers/desktops)
|
|
# - linux/arm64 (Raspberry Pi 4/5, Apple Silicon via emulation)
|
|
|
|
set -e
|
|
|
|
# Configuration
|
|
GHCR_REGISTRY="ghcr.io"
|
|
DOCKERHUB_REGISTRY="docker.io"
|
|
IMAGE_NAME="maziggy/bambuddy"
|
|
GHCR_IMAGE="${GHCR_REGISTRY}/${IMAGE_NAME}"
|
|
DOCKERHUB_IMAGE="${DOCKERHUB_REGISTRY}/${IMAGE_NAME}"
|
|
PLATFORMS="linux/amd64,linux/arm64"
|
|
BUILDER_NAME="bambuddy-builder"
|
|
CONFIG_FILE="backend/app/core/config.py"
|
|
CHANGELOG_FILE="CHANGELOG.md"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Parse arguments
|
|
PARALLEL=false
|
|
PUSH_GHCR=true
|
|
PUSH_DOCKERHUB=true
|
|
SKIP_RELEASE=false
|
|
INCLUDE_SIDECARS=false
|
|
for arg in "$@"; do
|
|
case $arg in
|
|
--parallel)
|
|
PARALLEL=true
|
|
;;
|
|
--ghcr-only)
|
|
PUSH_DOCKERHUB=false
|
|
;;
|
|
--dockerhub-only)
|
|
PUSH_GHCR=false
|
|
;;
|
|
--skip-release)
|
|
SKIP_RELEASE=true
|
|
;;
|
|
--include-sidecars)
|
|
INCLUDE_SIDECARS=true
|
|
;;
|
|
--help|-h)
|
|
echo "Usage: $0 [--parallel] [--ghcr-only] [--dockerhub-only] [--skip-release] [--include-sidecars]"
|
|
echo ""
|
|
echo "Build and publish a daily beta Docker image using the APP_VERSION from config.py."
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --parallel Build both architectures simultaneously"
|
|
echo " --ghcr-only Only push to GitHub Container Registry"
|
|
echo " --dockerhub-only Only push to Docker Hub"
|
|
echo " --skip-release Build+push without creating/updating GitHub release"
|
|
echo " --include-sidecars Also rebuild + push orca-slicer-api and bambu-studio-api"
|
|
echo " images (off by default — slicer rebuilds are expensive)"
|
|
echo " --help, -h Show this help"
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo -e "${RED}Unknown argument: $arg${NC}"
|
|
echo "Run $0 --help for usage"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# ============================================================
|
|
# Step 1: Read and validate APP_VERSION
|
|
# ============================================================
|
|
echo -e "${BLUE}[1/4] Validating APP_VERSION...${NC}"
|
|
|
|
VERSION=$(grep -oP 'APP_VERSION = "\K[^"]+' "$CONFIG_FILE")
|
|
|
|
if [ -z "$VERSION" ]; then
|
|
echo -e "${RED}Error: Could not read APP_VERSION from ${CONFIG_FILE}${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+b[0-9]+$ ]]; then
|
|
echo -e "${RED}Error: APP_VERSION '${VERSION}' is not a beta version (expected X.Y.Zb<N>)${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# Date-stamped tag for GitHub releases only (not used as Docker tag)
|
|
DAILY_DATE=$(date +%Y%m%d)
|
|
DAILY_TAG="${VERSION}-daily.${DAILY_DATE}"
|
|
|
|
echo -e "${GREEN} APP_VERSION: ${VERSION}${NC}"
|
|
echo -e "${GREEN} Docker tag: daily${NC}"
|
|
echo -e "${GREEN} Release tag: v${DAILY_TAG}${NC}"
|
|
|
|
# ============================================================
|
|
# Step 2: Build & push Docker images
|
|
# ============================================================
|
|
echo ""
|
|
|
|
# Get CPU count
|
|
CPU_COUNT=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
|
|
|
|
echo -e "${GREEN}================================================${NC}"
|
|
echo -e "${GREEN} Daily beta build${NC}"
|
|
echo -e "${GREEN} Version: ${VERSION}${NC}"
|
|
echo -e "${GREEN} Docker tag: daily${NC}"
|
|
echo -e "${GREEN} Platforms: ${PLATFORMS}${NC}"
|
|
echo -e "${GREEN} CPU cores: ${CPU_COUNT}${NC}"
|
|
if [ "$PARALLEL" = true ]; then
|
|
echo -e "${GREEN} Mode: PARALLEL (both archs simultaneously)${NC}"
|
|
else
|
|
echo -e "${GREEN} Mode: Sequential (amd64 → arm64)${NC}"
|
|
fi
|
|
echo -e "${GREEN} Registries:${NC}"
|
|
if [ "$PUSH_GHCR" = true ]; then
|
|
echo -e "${GREEN} - ${GHCR_IMAGE}${NC}"
|
|
fi
|
|
if [ "$PUSH_DOCKERHUB" = true ]; then
|
|
echo -e "${GREEN} - ${DOCKERHUB_IMAGE}${NC}"
|
|
fi
|
|
echo -e "${GREEN}================================================${NC}"
|
|
echo ""
|
|
|
|
# Check registry logins
|
|
if [ "$PUSH_GHCR" = true ]; then
|
|
if ! grep -q "ghcr.io" ~/.docker/config.json 2>/dev/null; then
|
|
echo -e "${YELLOW}Warning: You may not be logged in to ghcr.io${NC}"
|
|
echo "Run: echo \$GITHUB_TOKEN | docker login ghcr.io -u YOUR_USERNAME --password-stdin"
|
|
echo ""
|
|
fi
|
|
fi
|
|
|
|
if [ "$PUSH_DOCKERHUB" = true ]; then
|
|
if ! grep -q "index.docker.io\|docker.io" ~/.docker/config.json 2>/dev/null; then
|
|
echo -e "${RED}Error: You are not logged in to Docker Hub${NC}"
|
|
echo "Run: docker login -u YOUR_USERNAME"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Setup buildx builder if not exists
|
|
echo -e "${BLUE}[2/4] Setting up Docker Buildx and building...${NC}"
|
|
if ! docker buildx inspect "$BUILDER_NAME" >/dev/null 2>&1; then
|
|
echo "Creating new buildx builder: $BUILDER_NAME (optimized for ${CPU_COUNT} cores)"
|
|
docker buildx create \
|
|
--name "$BUILDER_NAME" \
|
|
--driver docker-container \
|
|
--driver-opt network=host \
|
|
--driver-opt "env.BUILDKIT_STEP_LOG_MAX_SIZE=10000000" \
|
|
--buildkitd-flags "--allow-insecure-entitlement network.host --oci-worker-gc=false" \
|
|
--config /dev/stdin <<EOF
|
|
[worker.oci]
|
|
max-parallelism = ${CPU_COUNT}
|
|
EOF
|
|
docker buildx inspect --bootstrap "$BUILDER_NAME"
|
|
fi
|
|
docker buildx use "$BUILDER_NAME"
|
|
|
|
# Verify builder supports multi-platform
|
|
if ! docker buildx inspect --bootstrap | grep -q "linux/arm64"; then
|
|
echo -e "${YELLOW}Installing QEMU for cross-platform builds...${NC}"
|
|
docker run --privileged --rm tonistiigi/binfmt --install all
|
|
fi
|
|
|
|
# Beta versions get 'daily' tag (never 'latest')
|
|
echo -e "${YELLOW}Beta version — tagging as 'daily' (not 'latest')${NC}"
|
|
|
|
# Build tags for all target registries
|
|
TAGS=""
|
|
if [ "$PUSH_GHCR" = true ]; then
|
|
TAGS="$TAGS -t ${GHCR_IMAGE}:daily"
|
|
fi
|
|
if [ "$PUSH_DOCKERHUB" = true ]; then
|
|
TAGS="$TAGS -t ${DOCKERHUB_IMAGE}:daily"
|
|
fi
|
|
|
|
# Common build args (no cache to ensure clean builds)
|
|
BUILD_ARGS="--provenance=false --sbom=false --no-cache --pull"
|
|
|
|
if [ "$PARALLEL" = true ]; then
|
|
# Parallel build: Build each architecture separately then combine manifests
|
|
echo -e "${YELLOW}Building amd64 and arm64 in parallel (${CPU_COUNT} cores each, no cache)...${NC}"
|
|
|
|
# Build per-arch staging tags for each target registry
|
|
ARCH_TAGS_AMD64=""
|
|
ARCH_TAGS_ARM64=""
|
|
if [ "$PUSH_GHCR" = true ]; then
|
|
ARCH_TAGS_AMD64="$ARCH_TAGS_AMD64 -t ${GHCR_IMAGE}:daily-amd64"
|
|
ARCH_TAGS_ARM64="$ARCH_TAGS_ARM64 -t ${GHCR_IMAGE}:daily-arm64"
|
|
fi
|
|
if [ "$PUSH_DOCKERHUB" = true ]; then
|
|
ARCH_TAGS_AMD64="$ARCH_TAGS_AMD64 -t ${DOCKERHUB_IMAGE}:daily-amd64"
|
|
ARCH_TAGS_ARM64="$ARCH_TAGS_ARM64 -t ${DOCKERHUB_IMAGE}:daily-arm64"
|
|
fi
|
|
|
|
# Build amd64 in background
|
|
(
|
|
echo -e "${BLUE}[amd64] Starting build...${NC}"
|
|
docker buildx build \
|
|
--platform linux/amd64 \
|
|
${ARCH_TAGS_AMD64} \
|
|
${BUILD_ARGS} \
|
|
--push \
|
|
. 2>&1 | sed 's/^/[amd64] /'
|
|
echo -e "${GREEN}[amd64] Complete!${NC}"
|
|
) &
|
|
PID_AMD64=$!
|
|
|
|
# Build arm64 in background
|
|
(
|
|
echo -e "${BLUE}[arm64] Starting build...${NC}"
|
|
docker buildx build \
|
|
--platform linux/arm64 \
|
|
${ARCH_TAGS_ARM64} \
|
|
${BUILD_ARGS} \
|
|
--push \
|
|
. 2>&1 | sed 's/^/[arm64] /'
|
|
echo -e "${GREEN}[arm64] Complete!${NC}"
|
|
) &
|
|
PID_ARM64=$!
|
|
|
|
# Wait for both builds
|
|
echo "Waiting for parallel builds to complete..."
|
|
wait $PID_AMD64
|
|
wait $PID_ARM64
|
|
|
|
# Create multi-arch manifests per registry (no cross-registry blob copies)
|
|
echo -e "${BLUE}Creating multi-arch manifests...${NC}"
|
|
|
|
if [ "$PUSH_GHCR" = true ]; then
|
|
echo -e "${BLUE} Creating GHCR manifest...${NC}"
|
|
docker buildx imagetools create \
|
|
-t "${GHCR_IMAGE}:daily" \
|
|
"${GHCR_IMAGE}:daily-amd64" \
|
|
"${GHCR_IMAGE}:daily-arm64"
|
|
fi
|
|
if [ "$PUSH_DOCKERHUB" = true ]; then
|
|
echo -e "${BLUE} Creating Docker Hub manifest...${NC}"
|
|
docker buildx imagetools create \
|
|
-t "${DOCKERHUB_IMAGE}:daily" \
|
|
"${DOCKERHUB_IMAGE}:daily-amd64" \
|
|
"${DOCKERHUB_IMAGE}:daily-arm64"
|
|
fi
|
|
else
|
|
# Sequential build (default): Build both platforms in one command
|
|
echo -e "${YELLOW}Building sequentially with ${CPU_COUNT} cores (no cache)...${NC}"
|
|
DOCKER_BUILDKIT=1 docker buildx build \
|
|
--platform "$PLATFORMS" \
|
|
${BUILD_ARGS} \
|
|
$TAGS \
|
|
--push \
|
|
.
|
|
fi
|
|
|
|
# ============================================================
|
|
# Step 3: Create/update GitHub release
|
|
# ============================================================
|
|
if [ "$SKIP_RELEASE" = true ]; then
|
|
echo -e "${YELLOW}[3/4] Skipping GitHub release (--skip-release)${NC}"
|
|
else
|
|
echo -e "${BLUE}[3/4] Creating/updating GitHub release...${NC}"
|
|
|
|
# Extract release notes from CHANGELOG: content between ## [<version>] and the next ## [ heading
|
|
CHANGELOG_NOTES=$(sed -n "/^## \[${VERSION}\]/,/^## \[/{/^## \[/!p}" "$CHANGELOG_FILE" | sed '/^$/d; 1{/^$/d}')
|
|
|
|
# Strip @mentions so GitHub doesn't auto-generate a "Contributors" section
|
|
CHANGELOG_NOTES=$(echo "$CHANGELOG_NOTES" | sed 's/@\([a-zA-Z0-9_-]*\)/\1/g')
|
|
|
|
if [ -z "$CHANGELOG_NOTES" ]; then
|
|
echo -e "${YELLOW} Warning: No changelog notes found for ${VERSION}${NC}"
|
|
CHANGELOG_NOTES="No changelog notes available for this release."
|
|
fi
|
|
|
|
# GitHub caps release body at 125000 chars. Reserve ~2000 for the boilerplate
|
|
# header/pull-commands and truncate the changelog tail with a link to the full file.
|
|
MAX_NOTES_LEN=122000
|
|
if [ ${#CHANGELOG_NOTES} -gt $MAX_NOTES_LEN ]; then
|
|
echo -e "${YELLOW} Changelog section is ${#CHANGELOG_NOTES} chars; truncating to ${MAX_NOTES_LEN} for GitHub release body${NC}"
|
|
CHANGELOG_NOTES="${CHANGELOG_NOTES:0:$MAX_NOTES_LEN}
|
|
|
|
---
|
|
|
|
_Changelog truncated — see the full [CHANGELOG.md](https://github.com/maziggy/bambuddy/blob/main/CHANGELOG.md) for the complete list._"
|
|
fi
|
|
|
|
# Build pull commands for the release body
|
|
PULL_COMMANDS=""
|
|
if [ "$PUSH_GHCR" = true ]; then
|
|
PULL_COMMANDS="docker pull ghcr.io/maziggy/bambuddy:daily"
|
|
fi
|
|
if [ "$PUSH_DOCKERHUB" = true ]; then
|
|
if [ -n "$PULL_COMMANDS" ]; then
|
|
PULL_COMMANDS="${PULL_COMMANDS}
|
|
# or
|
|
docker pull maziggy/bambuddy:daily"
|
|
else
|
|
PULL_COMMANDS="docker pull maziggy/bambuddy:daily"
|
|
fi
|
|
fi
|
|
|
|
# Create the release body
|
|
TODAY=$(date +%Y-%m-%d)
|
|
RELEASE_BODY=$(cat <<EOF
|
|
> [!NOTE]
|
|
> This is a **daily beta build** (${TODAY}). It contains the latest fixes and improvements but may have undiscovered issues.
|
|
>
|
|
> **Docker users:** Update by pulling the new image:
|
|
> \`\`\`
|
|
> ${PULL_COMMANDS}
|
|
> \`\`\`
|
|
>
|
|
> **Tip:** Use [Watchtower](https://containrrr.dev/watchtower/) to automatically update when new daily builds are pushed.
|
|
|
|
---
|
|
|
|
${CHANGELOG_NOTES}
|
|
EOF
|
|
)
|
|
|
|
# Delete ALL old daily releases — only the latest daily build should exist
|
|
echo " Cleaning up old daily releases..."
|
|
OLD_DAILY_RELEASES=$(gh release list --limit 100 --json tagName --jq '.[] | select(.tagName | test("-daily\\.")) | .tagName' 2>/dev/null || true)
|
|
if [ -n "$OLD_DAILY_RELEASES" ]; then
|
|
while IFS= read -r old_tag; do
|
|
echo " Deleting old daily release: ${old_tag}..."
|
|
gh release delete "$old_tag" --yes --cleanup-tag 2>/dev/null || true
|
|
done <<< "$OLD_DAILY_RELEASES"
|
|
fi
|
|
|
|
# Create/move tag to current HEAD and push
|
|
echo " Tagging current HEAD as v${DAILY_TAG}..."
|
|
git tag -f "v${DAILY_TAG}"
|
|
git push origin "v${DAILY_TAG}" --force
|
|
|
|
echo " Creating release v${DAILY_TAG}..."
|
|
NOTES_FILE=$(mktemp)
|
|
trap 'rm -f "$NOTES_FILE"' EXIT
|
|
printf '%s\n' "$RELEASE_BODY" > "$NOTES_FILE"
|
|
gh release create "v${DAILY_TAG}" \
|
|
--title "Daily Beta Build v${DAILY_TAG}" \
|
|
--prerelease \
|
|
--generate-notes=false \
|
|
--notes-file "$NOTES_FILE"
|
|
echo -e "${GREEN} Created GitHub release: v${DAILY_TAG}${NC}"
|
|
fi
|
|
|
|
# ============================================================
|
|
# Step 4: Verify
|
|
# ============================================================
|
|
echo -e "${BLUE}[4/4] Verifying...${NC}"
|
|
|
|
if [ "$PUSH_GHCR" = true ]; then
|
|
echo -e "${BLUE}GHCR manifest:${NC}"
|
|
docker buildx imagetools inspect "${GHCR_IMAGE}:daily"
|
|
fi
|
|
if [ "$PUSH_DOCKERHUB" = true ]; then
|
|
echo -e "${BLUE}Docker Hub manifest:${NC}"
|
|
docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:daily"
|
|
fi
|
|
|
|
if [ "$SKIP_RELEASE" != true ]; then
|
|
echo ""
|
|
echo -e "${BLUE}GitHub release:${NC}"
|
|
gh release view "v${DAILY_TAG}"
|
|
fi
|
|
|
|
# ============================================================
|
|
# Summary
|
|
# ============================================================
|
|
echo ""
|
|
echo -e "${GREEN}================================================${NC}"
|
|
echo -e "${GREEN} Daily beta build complete!${NC}"
|
|
echo -e "${GREEN} Version: ${VERSION}${NC}"
|
|
echo -e "${GREEN}================================================${NC}"
|
|
if [ "$PUSH_GHCR" = true ]; then
|
|
echo " GHCR: ${GHCR_IMAGE}:daily"
|
|
fi
|
|
if [ "$PUSH_DOCKERHUB" = true ]; then
|
|
echo " Docker Hub: ${DOCKERHUB_IMAGE}:daily"
|
|
fi
|
|
if [ "$SKIP_RELEASE" != true ]; then
|
|
echo " Release: https://github.com/${IMAGE_NAME}/releases/tag/v${DAILY_TAG}"
|
|
fi
|
|
echo ""
|
|
echo -e "${BLUE}Supported platforms:${NC}"
|
|
echo " - linux/amd64 (Intel/AMD servers, desktops)"
|
|
echo " - linux/arm64 (Raspberry Pi 4/5, Apple Silicon)"
|
|
echo ""
|
|
echo -e "${GREEN}Users can now run:${NC}"
|
|
if [ "$PUSH_GHCR" = true ]; then
|
|
echo " docker pull ${GHCR_IMAGE}:daily"
|
|
fi
|
|
if [ "$PUSH_DOCKERHUB" = true ]; then
|
|
echo " docker pull ${DOCKERHUB_IMAGE}:daily"
|
|
echo " docker pull ${IMAGE_NAME}:daily # shorthand"
|
|
fi
|
|
|
|
# ============================================================
|
|
# Sidecar images (orca-slicer-api + bambu-studio-api) — opt-in for daily
|
|
# ============================================================
|
|
SIDECAR_SCRIPT="/opt/claude/projects/orca-slicer-api/docker-publish-sidecars.sh"
|
|
if [ "$INCLUDE_SIDECARS" != true ]; then
|
|
echo ""
|
|
echo -e "${YELLOW}Sidecar images not rebuilt (daily default).${NC}"
|
|
echo -e "${YELLOW}Pass --include-sidecars to also publish orca-slicer-api + bambu-studio-api.${NC}"
|
|
elif [ ! -x "$SIDECAR_SCRIPT" ]; then
|
|
echo ""
|
|
echo -e "${YELLOW}Sidecar helper not found at ${SIDECAR_SCRIPT} — skipping sidecar build.${NC}"
|
|
else
|
|
echo ""
|
|
echo -e "${GREEN}================================================${NC}"
|
|
echo -e "${GREEN} Publishing sidecar images (daily channel)${NC}"
|
|
echo -e "${GREEN}================================================${NC}"
|
|
SIDECAR_ARGS="--channel daily --version ${VERSION}"
|
|
[ "$PARALLEL" = true ] && SIDECAR_ARGS="$SIDECAR_ARGS --parallel"
|
|
[ "$PUSH_GHCR" = false ] && SIDECAR_ARGS="$SIDECAR_ARGS --dockerhub-only"
|
|
[ "$PUSH_DOCKERHUB" = false ] && SIDECAR_ARGS="$SIDECAR_ARGS --ghcr-only"
|
|
"$SIDECAR_SCRIPT" $SIDECAR_ARGS
|
|
fi
|