diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 669fa4073..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,273 +0,0 @@ -version: 2.1 - -executors: - docker-executor: - docker: - - image: cimg/base:2025.11 - resource_class: medium - -commands: - setup-buildx: - steps: - - run: - name: Install Docker Buildx - command: | - mkdir -p ~/.docker/cli-plugins - BUILDX_VERSION=v0.12.1 - wget -q "https://github.com/docker/buildx/releases/download/${BUILDX_VERSION}/buildx-${BUILDX_VERSION}.linux-amd64" \ - -O ~/.docker/cli-plugins/docker-buildx - chmod +x ~/.docker/cli-plugins/docker-buildx - docker buildx version - - - run: - name: Create buildx builder - command: | - docker buildx create --name multiarch --driver docker-container --use --bootstrap - docker buildx inspect multiarch - - write-secrets: - description: "Store secrets in secret file on build-time" - steps: - - run: - name: Store secrets - command: | - echo "$MYSQL_ROOT_PASSWORD" > .balena/secrets/secret_mysql_root_password - echo "$MYSQL_USER" > .balena/secrets/secret_mysql_user - echo "$MYSQL_PASSWORD" > .balena/secrets/secret_mysql_password - echo "$MYSQL_DATABASE" > .balena/secrets/secret_mysql_database - echo "$MASTER_PASSWORD" > .balena/secrets/secret_master_password - - set-build-tag: - description: "Set BUILD_TAG environment variable based on branch or tag" - steps: - - run: - name: Set build tag - command: | - if [ -n "${CIRCLE_TAG}" ]; then - TAG="${CIRCLE_TAG}" - else - TAG="${CIRCLE_BRANCH}-${CIRCLE_SHA1:0:7}" - fi - # lowercase - TAG=$(echo "$TAG" | tr '[:upper:]' '[:lower:]') - # replace invalid chars with '-' - echo "export BUILD_TAG=$(echo "$TAG" | sed 's/[^a-z0-9._-]/-/g')" >> $BASH_ENV - source $BASH_ENV - echo "Build tag: ${BUILD_TAG}" - -jobs: - shellcheck: - docker: - - image: koalaman/shellcheck-alpine:stable - steps: - - checkout - - run: - name: Run shellcheck - command: | - find . -type f -name "*.sh" -exec shellcheck --exclude=SC2154,SC1091,SC2034,SC2096,SC2038 {} + - - build-x86_64: - executor: docker-executor - steps: - - checkout - - setup_remote_docker: - version: docker24 - docker_layer_caching: true - - - setup-buildx - - set-build-tag - - write-secrets - - - run: - name: Login to Docker Hub - command: | - echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin - - - run: - name: Build and push x86_64 - command: | - export DOCKER_ORG=${DOCKER_USER} - export BAKE_TAG=${BUILD_TAG} - export BALENA_ARCH=x86_64 - export PLATFORM=linux/amd64 - - echo "Building ${DOCKER_ORG}/*:${BAKE_TAG} for ${PLATFORM}" - ln -sf "${BALENA_ARCH}.env" ".env" - cp -vf "docker-compose.${BALENA_ARCH}" "docker-compose.yml" - docker buildx bake -f docker-bake.hcl \ - --set "*.platform=${PLATFORM}" \ - --push - - - run: - name: Tag images with architecture suffix - command: | - for service in mysqldb php-fpm httpd balena-storage; do - docker buildx imagetools create \ - --tag ${DOCKER_USER}/${service}:${BUILD_TAG}-x86_64 \ - ${DOCKER_USER}/${service}:${BUILD_TAG} - done - - build-aarch64: - executor: docker-executor - steps: - - checkout - - setup_remote_docker: - version: docker24 - docker_layer_caching: true - - - setup-buildx - - set-build-tag - - write-secrets - - - run: - name: Login to Docker Hub - command: | - echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin - - - run: - name: Build and push aarch64 - command: | - export DOCKER_ORG=${DOCKER_USER} - export BAKE_TAG=${BUILD_TAG} - export BALENA_ARCH=aarch64 - export PLATFORM=linux/arm64 - - echo "Building ${DOCKER_ORG}/*:${BAKE_TAG} for ${PLATFORM}" - ln -sf "${BALENA_ARCH}.env" ".env" - cp -vf "docker-compose.${BALENA_ARCH}" "docker-compose.yml" - docker buildx bake -f docker-bake.hcl \ - --set "*.platform=${PLATFORM}" \ - --push - - - run: - name: Tag images with architecture suffix - command: | - for service in mysqldb php-fpm httpd balena-storage; do - docker buildx imagetools create \ - --tag ${DOCKER_USER}/${service}:${BUILD_TAG}-aarch64 \ - ${DOCKER_USER}/${service}:${BUILD_TAG} - done - - build-armhf: - executor: docker-executor - steps: - - checkout - - setup_remote_docker: - version: docker24 - docker_layer_caching: true - - - setup-buildx - - set-build-tag - - write-secrets - - - run: - name: Login to Docker Hub - command: | - echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin - - - run: - name: Build and push armhf - command: | - export DOCKER_ORG=${DOCKER_USER} - export BAKE_TAG=${BUILD_TAG} - export BALENA_ARCH=armhf - export PLATFORM=linux/arm/v7 - - echo "Building ${DOCKER_ORG}/*:${BAKE_TAG} for ${PLATFORM}" - ln -sf "${BALENA_ARCH}.env" ".env" - cp -vf "docker-compose.${BALENA_ARCH}" "docker-compose.yml" - docker buildx bake -f docker-bake.hcl \ - --set "*.platform=${PLATFORM}" \ - --push - - - run: - name: Tag images with architecture suffix - command: | - for service in mysqldb php-fpm httpd balena-storage; do - docker buildx imagetools create \ - --tag ${DOCKER_USER}/${service}:${BUILD_TAG}-armhf \ - ${DOCKER_USER}/${service}:${BUILD_TAG} - done - - create-manifests: - executor: docker-executor - steps: - - checkout - - setup_remote_docker: - version: docker24 - - - set-build-tag - - - run: - name: Login to Docker Hub - command: | - echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin - - - run: - name: Create and push multi-arch manifests - command: | - for service in mysqldb php-fpm httpd balena-storage; do - echo "Creating manifest for ${service}:${BUILD_TAG}" - - # Create multi-arch manifest - docker manifest create ${DOCKER_USER}/${service}:${BUILD_TAG} \ - ${DOCKER_USER}/${service}:${BUILD_TAG}-x86_64 \ - ${DOCKER_USER}/${service}:${BUILD_TAG}-aarch64 \ - ${DOCKER_USER}/${service}:${BUILD_TAG}-armhf - - docker manifest push ${DOCKER_USER}/${service}:${BUILD_TAG} - - # Also create/update latest tag for main branch - if [ "${CIRCLE_BRANCH}" = "main" ] || [ "${CIRCLE_BRANCH}" = "master" ]; then - echo "Updating latest tag for ${service}" - docker manifest create ${DOCKER_USER}/${service}:latest \ - ${DOCKER_USER}/${service}:${BUILD_TAG}-x86_64 \ - ${DOCKER_USER}/${service}:${BUILD_TAG}-aarch64 \ - ${DOCKER_USER}/${service}:${BUILD_TAG}-armhf - - docker manifest push ${DOCKER_USER}/${service}:latest - fi - done - - - run: - name: Verify manifests - command: | - for service in mysqldb php-fpm httpd balena-storage; do - echo "Verifying ${service}:${BUILD_TAG}" - docker manifest inspect ${DOCKER_USER}/${service}:${BUILD_TAG} - done - -workflows: - build-all-branches: - jobs: - - shellcheck - - - build-x86_64: - context: - - Info-b23prodtm - requires: - - shellcheck - filters: - branches: - only: development - - - build-aarch64: - context: - - Info-b23prodtm - requires: - - shellcheck - - - build-armhf: - context: - - Info-b23prodtm - requires: - - shellcheck - - - create-manifests: - requires: - - build-x86_64 - - build-aarch64 - - build-armhf - filters: - branches: - only: development diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index e07b48de9..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Multi-Arch Build - -on: - push: - branches: [ "main", "development" ] - pull_request: -env: - DOCKER_ORG: ${{ secrets.DOCKER_USER }} - MYSQL_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }} - MYSQL_USER: ${{ secrets.MYSQL_USER }} - MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }} - MYSQL_DATABASE: ${{ secrets.MYSQL_DATABASE }} - MASTER_PASSWORD: ${{ secrets.MASTER_PASSWORD }} -jobs: - build-services: - runs-on: ubuntu-latest -# runs-on: self-hosted - environment: AUTH - strategy: - fail-fast: false - matrix: - arch: [ x86_64 ] - # arch: [ x86_64, aarch64, armhf ] - steps: - - uses: actions/checkout@v4 - - name: Enable QEMU - uses: docker/setup-qemu-action@v3 - - name: Enable Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASS }} - - name: Sanitize ref name for Docker tag - id: sanitize - run: | - RAW="${{ github.ref_name }}" - TAG=$(echo "$RAW" | tr '[:upper:]' '[:lower:]') - TAG=$(echo "$TAG" | sed 's/[^a-z0-9._-]/-/g') - echo "tag=$TAG" >> $GITHUB_OUTPUT - - name: Build docker-compose stack - env: - BALENA_ARCH: ${{ matrix.arch }} - BAKE_TAG: ${{ steps.sanitize.outputs.tag }} - run: | - case "${BALENA_ARCH}" in - "armhf") export PLATFORM="linux/arm/v7" ;; - "aarch64") export PLATFORM="linux/arm64" ;; - "x86_64") export PLATFORM="linux/amd64" ;; - *) export PLATFORM="linux/$(uname -m)" ;; - esac - ln -sf "${BALENA_ARCH}.env" ".env" - cp -vf "docker-compose.${BALENA_ARCH}" "docker-compose.yml" - - echo "${{ secrets.MYSQL_ROOT_PASSWORD }}" > .balena/secrets/secret_mysql_root_password - echo "${{ secrets.MYSQL_USER }}" > .balena/secrets/secret_mysql_user - echo "${{ secrets.MYSQL_PASSWORD }}" > .balena/secrets/secret_mysql_password - echo "${{ secrets.MYSQL_DATABASE }}" > .balena/secrets/secret_mysql_database - echo "${{ secrets.MASTER_PASSWORD }}" > .balena/secrets/secret_master_password - - docker buildx bake -f docker-bake.hcl \ - --set "*.platform=${PLATFORM}" \ - --push - - deploy: - runs-on: ubuntu-latest -# runs-on: self-hosted - environment: AUTH - needs: build-services - if: github.event_name != 'pull_request' - steps: - - uses: actions/checkout@v4 - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASS }} - - name: Set up Docker Compose - uses: docker/setup-compose-action@v1 - with: - version: latest - - name: Deploy stack - env: - BAKE_TAG: ${{ github.ref_name }} - run: docker compose up -d diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 000000000..1e007fd47 --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,142 @@ +name: Docker Build Multi-Platform with Manifest Merge + +on: + pull_request: + branches: [ main, development ] + push: + branches: [ main, development ] + tags: + - 'v*' + workflow_dispatch: + +env: + REGISTRY: docker.io + REGISTRY_IMAGE: ${{ secrets.DOCKER_USER }} + +jobs: + build-services: + runs-on: ${{ vars.DEFAULT_RUNNER || 'ubuntu-latest' }} + timeout-minutes: 120 + environment: AUTH + permissions: + contents: read + actions: write + + strategy: + fail-fast: false + matrix: + include: + - balena_arch: armhf + platform: linux/arm/v7 + - balena_arch: aarch64 + platform: linux/arm64 + - balena_arch: x86_64 + platform: linux/amd64 + + steps: + - uses: actions/checkout@v4 + + - name: Sanitize ref name + id: ref + run: | + RAW_REF="${GITHUB_REF_NAME}" + + # Docker-safe normalization: + SAFE_REF=$(echo "$RAW_REF" \ + | tr '/' '-' \ + | tr ':' '-' \ + | tr '[:upper:]' '[:lower:]' \ + | sed 's/[^a-z0-9._-]//g') + + echo "safe_ref=$SAFE_REF" >> $GITHUB_OUTPUT + + SHORT_SHA=${GITHUB_SHA:0:7} + echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT + + - name: Enable QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: ${{ matrix.platform }} + + - name: Enable Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASS }} + + - name: Write buildkit secrets + run: | + mkdir -p .balena/secrets + echo "${{ secrets.MYSQL_ROOT_PASSWORD }}" > .balena/secrets/secret_mysql_root_password + echo "${{ secrets.MYSQL_USER }}" > .balena/secrets/secret_mysql_user + echo "${{ secrets.MYSQL_PASSWORD }}" > .balena/secrets/secret_mysql_password + echo "${{ secrets.MYSQL_DATABASE }}" > .balena/secrets/secret_mysql_database + echo "${{ secrets.MASTER_PASSWORD }}" > .balena/secrets/secret_master_password + + - name: Build service image for ${{ matrix.balena_arch }} + uses: docker/bake-action@v5 + env: + GITHUB_SHA: ${{ steps.ref.outputs.short_sha }} + BAKE_TAG: ${{ steps.ref.outputs.safe_ref }} + with: + files: ./docker-bake.hcl + targets: ${{ matrix.balena_arch }} + push: true + set: | + *.cache-from=type=gha + *.cache-to=type=gha,mode=max + *.args.BALENA_ARCH=${{ matrix.balena_arch }} + *.platform=${{ matrix.platform }} + + deploy: + needs: build-services + runs-on: ${{ vars.DEFAULT_RUNNER || 'ubuntu-latest' }} + timeout-minutes: 120 + environment: AUTH + if: github.event_name != 'pull_request' + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Sanitize ref name + id: ref + run: | + RAW_REF="${GITHUB_REF_NAME}" + + # Docker-safe normalization: + SAFE_REF=$(echo "$RAW_REF" \ + | tr '/' '-' \ + | tr ':' '-' \ + | tr '[:upper:]' '[:lower:]' \ + | sed 's/[^a-z0-9._-]//g') + + echo "safe_ref=$SAFE_REF" >> $GITHUB_OUTPUT + + SHORT_SHA=${GITHUB_SHA:0:7} + echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT + + - name: Enable Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASS }} + + - name: Push multi-platform manifests + env: + GITHUB_SHA: ${{ steps.ref.outputs.short_sha }} + BAKE_TAG: ${{ steps.ref.outputs.safe_ref }} + run: | + if "./manifest-push.sh"; then + echo "✅ All manifests pushed successfully" + else + echo "❌ Manifest push failed" + exit 1 + fi diff --git a/Dockerfile.aarch64 b/Dockerfile.aarch64 index ac2301756..1f0383e0a 100644 --- a/Dockerfile.aarch64 +++ b/Dockerfile.aarch64 @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # BEGIN - Multi-stage Build # Stage 1: Install NodeJS dependencies -FROM node:20-alpine3.16 AS node-build +FROM node:20-alpine AS node-build WORKDIR /usr/local/project @@ -17,7 +17,7 @@ RUN apk add --no-cache nodejs npm \ && yarn cache clean # Stage 2: Build PHP-FPM environment -FROM php:7.4.33-fpm-alpine3.16 +FROM php:8.3-fpm-alpine # Set up working directory and environment ARG HTDOCS diff --git a/Dockerfile.armhf b/Dockerfile.armhf index b877b818a..08f27b8ac 100644 --- a/Dockerfile.armhf +++ b/Dockerfile.armhf @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # BEGIN - Multi-stage Build # Stage 1: Install NodeJS dependencies -FROM node:20-alpine3.16 AS node-build +FROM node:20-alpine AS node-build WORKDIR /usr/local/project @@ -17,7 +17,7 @@ RUN apk add --no-cache nodejs npm \ && yarn cache clean # Stage 2: Build PHP-FPM environment -FROM php:7.4.33-fpm-alpine3.16 +FROM php:8.3-fpm-alpine # Set up working directory and environment ARG HTDOCS diff --git a/Dockerfile.template b/Dockerfile.template index 76c41e0a4..a5b84cae6 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # BEGIN - Multi-stage Build # Stage 1: Install NodeJS dependencies -FROM node:20-alpine3.16 AS node-build +FROM node:20-alpine AS node-build WORKDIR /usr/local/project @@ -17,7 +17,7 @@ RUN apk add --no-cache nodejs npm \ && yarn cache clean # Stage 2: Build PHP-FPM environment -FROM php:7.4.33-fpm-alpine3.16 +FROM php:8.3-fpm-alpine # Set up working directory and environment ARG HTDOCS diff --git a/Dockerfile.x86_64 b/Dockerfile.x86_64 index f39b7b930..4a554ade9 100644 --- a/Dockerfile.x86_64 +++ b/Dockerfile.x86_64 @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # BEGIN - Multi-stage Build # Stage 1: Install NodeJS dependencies -FROM node:20-alpine3.16 AS node-build +FROM node:20-alpine AS node-build WORKDIR /usr/local/project @@ -17,7 +17,7 @@ RUN apk add --no-cache nodejs npm \ && yarn cache clean # Stage 2: Build PHP-FPM environment -FROM php:7.4.33-fpm-alpine3.16 +FROM php:8.3-fpm-alpine # Set up working directory and environment ARG HTDOCS diff --git a/common.env b/common.env index 25f7187d0..90dcdf314 100644 --- a/common.env +++ b/common.env @@ -1,2 +1,2 @@ +BALENA_PROJECTS=( php-fpm httpd mysqldb balena-storage ) BALENA_PROJECTS_FLAGS=( PLATFORM IMG_TAG SECONDARY_HUB SECONDARY_TAG BALENA_ARCH BALENA_MACHINE_NAME PHP_EXTENSIONS ) -BALENA_PROJECTS=( ./deployment/images/httpd ./deployment/images/mysqldb . ./balena-storage ) diff --git a/docker-bake.hcl b/docker-bake.hcl index 0b5aab8c8..bc141ff5a 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,9 +1,24 @@ +# docker-bake.hcl - Multi-platform builds with GHA cache + +variable "REGISTRY" { + default = "docker.io" +} + variable "DOCKER_ORG" { - default = "betothreeprod" + default = "bprtkop" +} + +# Alias for compatibility with manifest-push.sh +variable "REGISTRY_IMAGE" { + default = "${DOCKER_ORG}" } variable "BAKE_TAG" { - default = "latest" + default = "" +} + +variable "GITHUB_SHA" { + default = "" } variable "PLATFORM" { @@ -15,75 +30,245 @@ variable "BALENA_ARCH" { } group "default" { - targets = ["db", "php-fpm", "httpd", "balena-storage"] + targets = ["php-fpm-x86_64", "httpd-x86_64", "db-x86_64", "balena-storage-x86_64"] } -target "db" { - context = "deployment/images/mysqldb" - dockerfile = "Dockerfile.${BALENA_ARCH}" - platforms = ["${PLATFORM}"] - tags = [ - "${DOCKER_ORG}/mysqldb:latest", - "${DOCKER_ORG}/mysqldb:${BAKE_TAG}" - ] - args = { - PUID = "1000" - PGID = "1000" - } - secret = [ - "id=mysql_root_password,src=.balena/secrets/secret_mysql_root_password", - "id=mysql_user,src=.balena/secrets/secret_mysql_user", - "id=mysql_password,src=.balena/secrets/secret_mysql_password", - "id=mysql_database,src=.balena/secrets/secret_mysql_database", - "id=master_password,src=.balena/secrets/secret_master_password", - ] +# Common target with GitHub Actions cache +target "common" { + cache-from = ["type=gha"] + cache-to = ["type=gha,mode=max"] } +# ============================================================================ +# PHP-FPM SERVICE - CakePHP application (main service) +# ============================================================================ target "php-fpm" { + inherits = ["common"] context = "." dockerfile = "Dockerfile.${BALENA_ARCH}" - platforms = ["${PLATFORM}"] - tags = [ - "${DOCKER_ORG}/php-fpm:latest", - "${DOCKER_ORG}/php-fpm:${BAKE_TAG}" - ] args = { - PUID = "1000" - PGID = "1000" + PUID = "1000" + PGID = "1000" MYPHPCMS_DIR = "app/webroot/php-cms" MYPHPCMS_LOG = "app/tmp/logs" - HTDOCS = "/var/www/html" + HTDOCS = "/var/www/html" } + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/php-fpm:latest", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/php-fpm:${replace(BAKE_TAG, "/", "-")}" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/php-fpm:${GITHUB_SHA}" : "" + ] secret = [ "id=mysql_root_password,src=.balena/secrets/secret_mysql_root_password", "id=mysql_user,src=.balena/secrets/secret_mysql_user", "id=mysql_password,src=.balena/secrets/secret_mysql_password", "id=mysql_database,src=.balena/secrets/secret_mysql_database", - "id=master_password,src=.balena/secrets/secret_master_password" + "id=master_password,src=.balena/secrets/secret_master_password", ] } +# ============================================================================ +# HTTPD SERVICE - Apache reverse proxy +# ============================================================================ target "httpd" { + inherits = ["common"] context = "deployment/images/httpd" dockerfile = "Dockerfile.${BALENA_ARCH}" - platforms = ["${PLATFORM}"] - tags = [ - "${DOCKER_ORG}/httpd:latest", - "${DOCKER_ORG}/httpd:${BAKE_TAG}" - ] args = { PUID = "1000" PGID = "1000" HTDOCS = "/var/www/html" } + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/httpd:latest", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/httpd:${replace(BAKE_TAG, "/", "-")}" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/httpd:${GITHUB_SHA}" : "" + ] +} + +# ============================================================================ +# DB SERVICE - MariaDB database +# ============================================================================ +target "db" { + inherits = ["common"] + context = "deployment/images/mysqldb" + dockerfile = "Dockerfile.${BALENA_ARCH}" + args = { + PUID = "1000" + PGID = "1000" + } + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/mysqldb:latest", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/mysqldb:${replace(BAKE_TAG, "/", "-")}" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/mysqldb:${GITHUB_SHA}" : "" + ] + secret = [ + "id=mysql_root_password,src=.balena/secrets/secret_mysql_root_password", + "id=mysql_user,src=.balena/secrets/secret_mysql_user", + "id=mysql_password,src=.balena/secrets/secret_mysql_password", + "id=mysql_database,src=.balena/secrets/secret_mysql_database", + "id=master_password,src=.balena/secrets/secret_master_password", + ] } +# ============================================================================ +# BALENA-STORAGE SERVICE +# ============================================================================ target "balena-storage" { + inherits = ["common"] context = "balena-storage" dockerfile = "Dockerfile.${BALENA_ARCH}" - platforms = ["${PLATFORM}"] - tags = [ - "${DOCKER_ORG}/balena-storage:latest", - "${DOCKER_ORG}/balena-storage:${BAKE_TAG}" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/balena-storage:latest", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/balena-storage:${replace(BAKE_TAG, "/", "-")}" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/balena-storage:${GITHUB_SHA}" : "" + ] +} + +# ============================================================================ +# MATRIX BUILDS - Per-architecture groups +# ============================================================================ + +group "armhf" { + targets = ["php-fpm-armhf", "httpd-armhf", "db-armhf", "balena-storage-armhf"] +} + +target "php-fpm-armhf" { + inherits = ["php-fpm"] + platforms = ["linux/arm/v7"] + dockerfile = "Dockerfile.armhf" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/php-fpm:arm32v7", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/php-fpm:${replace(BAKE_TAG, "/", "-")}-arm32v7" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/php-fpm:${GITHUB_SHA}-arm32v7" : "" + ] +} + +target "httpd-armhf" { + inherits = ["httpd"] + platforms = ["linux/arm/v7"] + dockerfile = "Dockerfile.armhf" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/httpd:arm32v7", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/httpd:${replace(BAKE_TAG, "/", "-")}-arm32v7" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/httpd:${GITHUB_SHA}-arm32v7" : "" + ] +} + +target "db-armhf" { + inherits = ["db"] + platforms = ["linux/arm/v7"] + dockerfile = "Dockerfile.armhf" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/mysqldb:arm32v7", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/mysqldb:${replace(BAKE_TAG, "/", "-")}-arm32v7" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/mysqldb:${GITHUB_SHA}-arm32v7" : "" + ] +} + +target "balena-storage-armhf" { + inherits = ["balena-storage"] + platforms = ["linux/arm/v7"] + dockerfile = "Dockerfile.armhf" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/balena-storage:arm32v7", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/balena-storage:${replace(BAKE_TAG, "/", "-")}-arm32v7" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/balena-storage:${GITHUB_SHA}-arm32v7" : "" + ] +} + +group "aarch64" { + targets = ["php-fpm-aarch64", "httpd-aarch64", "db-aarch64", "balena-storage-aarch64"] +} + +target "php-fpm-aarch64" { + inherits = ["php-fpm"] + platforms = ["linux/arm64"] + dockerfile = "Dockerfile.aarch64" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/php-fpm:arm64v8", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/php-fpm:${replace(BAKE_TAG, "/", "-")}-arm64v8" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/php-fpm:${GITHUB_SHA}-arm64v8" : "" + ] +} + +target "httpd-aarch64" { + inherits = ["httpd"] + platforms = ["linux/arm64"] + dockerfile = "Dockerfile.aarch64" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/httpd:arm64v8", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/httpd:${replace(BAKE_TAG, "/", "-")}-arm64v8" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/httpd:${GITHUB_SHA}-arm64v8" : "" + ] +} + +target "db-aarch64" { + inherits = ["db"] + platforms = ["linux/arm64"] + dockerfile = "Dockerfile.aarch64" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/mysqldb:arm64v8", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/mysqldb:${replace(BAKE_TAG, "/", "-")}-arm64v8" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/mysqldb:${GITHUB_SHA}-arm64v8" : "" + ] +} + +target "balena-storage-aarch64" { + inherits = ["balena-storage"] + platforms = ["linux/arm64"] + dockerfile = "Dockerfile.aarch64" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/balena-storage:arm64v8", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/balena-storage:${replace(BAKE_TAG, "/", "-")}-arm64v8" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/balena-storage:${GITHUB_SHA}-arm64v8" : "" + ] +} + +group "x86_64" { + targets = ["php-fpm-x86_64", "httpd-x86_64", "db-x86_64", "balena-storage-x86_64"] +} + +target "php-fpm-x86_64" { + inherits = ["php-fpm"] + platforms = ["linux/amd64"] + dockerfile = "Dockerfile.x86_64" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/php-fpm:amd64", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/php-fpm:${replace(BAKE_TAG, "/", "-")}-amd64" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/php-fpm:${GITHUB_SHA}-amd64" : "" + ] +} + +target "httpd-x86_64" { + inherits = ["httpd"] + platforms = ["linux/amd64"] + dockerfile = "Dockerfile.x86_64" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/httpd:amd64", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/httpd:${replace(BAKE_TAG, "/", "-")}-amd64" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/httpd:${GITHUB_SHA}-amd64" : "" + ] +} + +target "db-x86_64" { + inherits = ["db"] + platforms = ["linux/amd64"] + dockerfile = "Dockerfile.x86_64" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/mysqldb:amd64", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/mysqldb:${replace(BAKE_TAG, "/", "-")}-amd64" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/mysqldb:${GITHUB_SHA}-amd64" : "" + ] +} + +target "balena-storage-x86_64" { + inherits = ["balena-storage"] + platforms = ["linux/amd64"] + dockerfile = "Dockerfile.x86_64" + tags = [ + "${REGISTRY}/${REGISTRY_IMAGE}/balena-storage:amd64", + BAKE_TAG != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/balena-storage:${replace(BAKE_TAG, "/", "-")}-amd64" : "", + GITHUB_SHA != "" ? "${REGISTRY}/${REGISTRY_IMAGE}/balena-storage:${GITHUB_SHA}-amd64" : "" ] } diff --git a/manifest-push.sh b/manifest-push.sh new file mode 100755 index 000000000..3326c502c --- /dev/null +++ b/manifest-push.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -eu + +TOPDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +# ============================================================================ +# INITIALISATION - Read from docker-bake.hcl (single source of truth) +# ============================================================================ + +# Verify docker-bake.hcl +if [[ ! -f "${TOPDIR}/docker-bake.hcl" ]]; then + echo "❌ Error: docker-bake.hcl not found at ${TOPDIR}/docker-bake.hcl" + exit 1 +fi + +# Parse REGISTRY from docker-bake.hcl +REGISTRY=${REGISTRY:-$(grep -A1 'variable "REGISTRY"' "${TOPDIR}/docker-bake.hcl" | grep 'default' | sed 's/.*default = "\(.*\)".*/\1/')} + +# Parse REGISTRY_IMAGE (DOCKER_ORG) from docker-bake.hcl +REGISTRY_IMAGE=${REGISTRY_IMAGE:-$(grep -A1 'variable "DOCKER_ORG"' "${TOPDIR}/docker-bake.hcl" | grep 'default' | sed 's/.*default = "\(.*\)".*/\1/')} + +# Verify common.env for BALENA_PROJECTS +if [[ ! -f "${TOPDIR}/common.env" ]]; then + echo "❌ Error: common.env not found (required for BALENA_PROJECTS)" + exit 1 +fi + +# shellcheck source=common.env +source "${TOPDIR}/common.env" + +# ============================================================================ +# GIT VARIABLES - with robust fallback logic +# ============================================================================ + +BAKE_TAG="${BAKE_TAG:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown')}" +GITHUB_SHA="${GITHUB_SHA:-$(git rev-parse --short=7 HEAD 2>/dev/null || echo 'unknown')}" + +# ============================================================================ +# VALIDATIONS +# ============================================================================ + +: "${REGISTRY?Error: REGISTRY not defined}" +: "${REGISTRY_IMAGE?Error: REGISTRY_IMAGE not defined}" + +if [[ "${BAKE_TAG}" == "unknown" || "${GITHUB_SHA}" == "unknown" ]]; then + echo "⚠️ Warning: Git info missing (detached repo or git absent)" + echo " BAKE_TAG=${BAKE_TAG}, GITHUB_SHA=${GITHUB_SHA:0:7}" +fi + +# ============================================================================ +# MANIFEST MERGE - Create & push manifests for ALL services +# ============================================================================ + +echo "" +echo "🔄 Creating and pushing multi-platform manifests for ALL services" +echo " REGISTRY=${REGISTRY}" +echo " IMAGE=${REGISTRY_IMAGE}" +echo " BAKE_TAG=${BAKE_TAG}" +echo "" + +for SERVICE in "${BALENA_PROJECTS[@]}"; do + IMAGE_BASE="${REGISTRY}/${REGISTRY_IMAGE}/${SERVICE}" + + echo "📦 Processing: ${SERVICE}" + docker buildx imagetools create -t \ + "${IMAGE_BASE}:${GITHUB_SHA}" \ + "${IMAGE_BASE}:${GITHUB_SHA}-amd64" \ + "${IMAGE_BASE}:${GITHUB_SHA}-arm32v7" \ + "${IMAGE_BASE}:${GITHUB_SHA}-arm64v8" + + # Also push as latest if on main or development branch + if [[ "${BAKE_TAG}" == "main" || "${BAKE_TAG}" == "development" ]]; then + echo " Creating latest manifest..." + docker buildx imagetools create -t \ + "${IMAGE_BASE}:latest" \ + "${IMAGE_BASE}:${GITHUB_SHA}-amd64" \ + "${IMAGE_BASE}:${GITHUB_SHA}-arm32v7" \ + "${IMAGE_BASE}:${GITHUB_SHA}-arm64v8" + fi + + echo " ✅ ${SERVICE} done" + echo "" +done + +echo "✅ All manifests pushed successfully"