cluster

Infrastructure files for Nordgedanken and Midnightthoughts.
git clone git://archive.git.mtrnord.blog/MTRNord/cluster.git
Log | Files | Refs | README

gitops_multiarch_builds.groovy (16257B)


      1 // Seed job: GitOps multi-arch build pipeline
      2 // Auto-creates the image build pipeline job from this definition
      3 
      4 pipelineJob('gitops-multiarch-builds') {
      5   description('Multi-arch container image builds for GitOps infrastructure')
      6 
      7   triggers {
      8     githubPush()
      9     cron('H 3 * * *')
     10   }
     11 
     12   parameters {
     13     choiceParam('BUILD_IMAGE', ['all', 'matrix-backup', 'continuwuity', 'blog', 'bookwyrm'], 'Which image(s) to build')
     14   }
     15 
     16   definition {
     17     cps {
     18       script('''
     19         pipeline {
     20           agent {
     21             kubernetes {
     22               defaultContainer 'docker'
     23               yaml """
     24                 apiVersion: v1
     25                 kind: Pod
     26                 metadata:
     27                   labels:
     28                     jenkins: agent
     29                     job: gitops-multiarch-builds
     30                 spec:
     31                   serviceAccountName: jenkins-operator-jenkins
     32                   containers:
     33                   - name: jnlp
     34                     image: jenkins/inbound-agent:3248.v65ecb_254c298-6
     35                     env:
     36                     - name: JENKINS_TUNNEL
     37                       value: jenkins-operator-slave-jenkins.jenkins.svc.cluster.local:50000
     38                     - name: JENKINS_URL
     39                       value: http://jenkins-operator-http-jenkins.jenkins.svc.cluster.local:8080/
     40                     resources:
     41                       requests:
     42                         cpu: 100m
     43                         memory: 256Mi
     44                       limits:
     45                         memory: 512Mi
     46                     volumeMounts:
     47                     - name: workspace
     48                       mountPath: /home/jenkins/agent
     49                   - name: docker
     50                     image: docker:dind
     51                     securityContext:
     52                       privileged: true
     53                     env:
     54                     - name: DOCKER_TLS_CERTDIR
     55                       value: ""
     56                     resources:
     57                       requests:
     58                         cpu: 2000m
     59                         memory: 6Gi
     60                       limits:
     61                         cpu: 4000m
     62                         memory: 12Gi
     63                     volumeMounts:
     64                     - name: workspace
     65                       mountPath: /home/jenkins/agent
     66                     - name: registry-secret
     67                       mountPath: /var/run/secrets/docker.io/config.json
     68                       subPath: dockerconfig.json
     69                       readOnly: true
     70                     - name: cosign-secret
     71                       mountPath: /var/run/secrets/cosign/key
     72                       subPath: cosign.key
     73                       readOnly: true
     74                     - name: cosign-secret
     75                       mountPath: /var/run/secrets/cosign/password
     76                       subPath: cosign.password
     77                       readOnly: true
     78                   volumes:
     79                   - name: workspace
     80                     emptyDir: {}
     81                   - name: registry-secret
     82                     secret:
     83                       secretName: registry-credentials
     84                       defaultMode: 256
     85                   - name: cosign-secret
     86                     secret:
     87                       secretName: cosign-signing-key
     88                       defaultMode: 256
     89                   restartPolicy: Never
     90               """
     91             }
     92           }
     93 
     94           options {
     95             timestamps()
     96             timeout(time: 4, unit: 'HOURS')
     97             buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '5'))
     98           }
     99 
    100           triggers {
    101             githubPush()
    102             // pollSCM as fallback in case the GitHub webhook is not reachable
    103             pollSCM('H/15 * * * *')
    104             cron('H 3 * * *')
    105           }
    106 
    107           environment {
    108             REGISTRY = 'registry.midnightthoughts.space'
    109             // docker CLI reads credentials from $DOCKER_CONFIG/config.json
    110             DOCKER_CONFIG = '/var/run/secrets/docker.io'
    111           }
    112 
    113           stages {
    114             stage('Prepare Build Environment') {
    115               steps {
    116                 sh """
    117                   apk add --no-cache git jq wget
    118 
    119                   # Allow git to operate on the workspace regardless of owner uid
    120                   git config --global --add safe.directory '*'
    121 
    122                   # Download cosign (same version as GitHub Actions)
    123                   wget -qO /usr/local/bin/cosign \\
    124                     https://github.com/sigstore/cosign/releases/download/v3.0.6/cosign-linux-amd64
    125                   chmod +x /usr/local/bin/cosign
    126 
    127                   # dockerd inside docker:dind starts asynchronously; wait for the socket
    128                   for i in \\$(seq 1 30); do
    129                     if [ -S /var/run/docker.sock ]; then
    130                       break
    131                     fi
    132                     echo "Waiting for docker daemon... (\\$i/30)"
    133                     sleep 2
    134                   done
    135                   if [ ! -S /var/run/docker.sock ]; then
    136                     echo "ERROR: docker daemon did not start within 60 seconds"
    137                     exit 1
    138                   fi
    139 
    140                   # Register QEMU binfmt handlers so the kernel can run arm64 binaries
    141                   # on this amd64 node. The F-flag keeps the interpreter fd open kernel-side
    142                   # so it persists even after the container exits.
    143                   docker run --rm --privileged --platform linux/amd64 \\
    144                     tonistiigi/binfmt:qemu-v8.1.5 --install all
    145 
    146                   # Create docker-container buildx builder.
    147                   # moby/buildkit:buildx-stable-1 already ships buildkit-qemu-aarch64 so
    148                   # no custom image is needed — the per-arch digest bug in Dockerfiles was
    149                   # the root cause of the earlier "Invalid ELF image" failures, not QEMU.
    150                   docker buildx create --name multiarch-builder --driver docker-container \\
    151                     --platform linux/amd64,linux/arm64 2>/dev/null || true
    152                   docker buildx use multiarch-builder
    153                   docker buildx inspect --bootstrap
    154                 """
    155               }
    156             }
    157 
    158             stage('Clone Repository') {
    159               steps {
    160                 // checkout scm only works in Multibranch/Pipeline-from-SCM jobs;
    161                 // this is an inline CPS pipeline so we clone explicitly.
    162                 git url: 'https://github.com/MTRNord/cluster.git', branch: 'main'
    163               }
    164             }
    165 
    166             stage('Build Images') {
    167               parallel {
    168                 stage('matrix-backup') {
    169                   when {
    170                     expression { params.BUILD_IMAGE == 'matrix-backup' || params.BUILD_IMAGE == 'all' }
    171                   }
    172                   steps {
    173                     script { build_matrix_backup() }
    174                   }
    175                 }
    176                 stage('continuwuity') {
    177                   when {
    178                     expression { params.BUILD_IMAGE == 'continuwuity' || params.BUILD_IMAGE == 'all' }
    179                   }
    180                   steps {
    181                     script { build_continuwuity() }
    182                   }
    183                 }
    184                 stage('blog') {
    185                   when {
    186                     expression { params.BUILD_IMAGE == 'blog' || params.BUILD_IMAGE == 'all' }
    187                   }
    188                   steps {
    189                     script { build_blog() }
    190                   }
    191                 }
    192                 stage('bookwyrm') {
    193                   when {
    194                     expression { params.BUILD_IMAGE == 'bookwyrm' || params.BUILD_IMAGE == 'all' }
    195                   }
    196                   steps {
    197                     script { build_bookwyrm() }
    198                   }
    199                 }
    200               }
    201             }
    202           }
    203 
    204           post {
    205             success { echo "Build pipeline completed successfully!" }
    206             failure  { echo "Build pipeline failed!" }
    207           }
    208         }
    209 
    210         // ---------------------------------------------------------------------------
    211         // matrix-backup — Dockerfile: apps/talos_cluster/matrix-backup/backup-tool/
    212         // ---------------------------------------------------------------------------
    213         def build_matrix_backup() {
    214           sh \'\'\'
    215             set -eu
    216             echo "=== Building matrix-backup ==="
    217 
    218             TAG_TS="$(date -u +%Y%m%d-%H%M%S)"
    219             TAG_SHA="sha-$(git rev-parse --short HEAD)"
    220             IMAGE="registry.midnightthoughts.space/mtrnord/cluster/matrix-backup"
    221 
    222             docker buildx build \\
    223               --platform linux/amd64,linux/arm64 \\
    224               --tag "${IMAGE}:${TAG_TS}" \\
    225               --tag "${IMAGE}:main" \\
    226               --tag "${IMAGE}:${TAG_SHA}" \\
    227               --metadata-file /tmp/matrix-backup-meta.json \\
    228               --push \\
    229               -f apps/talos_cluster/matrix-backup/backup-tool/Dockerfile \\
    230               apps/talos_cluster/matrix-backup/backup-tool/
    231 
    232             DIGEST="$(jq -r \'."containerimage.digest"\' /tmp/matrix-backup-meta.json)"
    233             echo "Signing ${IMAGE}@${DIGEST}"
    234             { set +x; } 2>/dev/null
    235             COSIGN_PRIVATE_KEY="$(cat /var/run/secrets/cosign/key)"
    236             COSIGN_PASSWORD="$(cat /var/run/secrets/cosign/password)"
    237             COSIGN_EXPERIMENTAL=1 COSIGN_OCI_EXPERIMENTAL=1 \\
    238             COSIGN_PRIVATE_KEY="$COSIGN_PRIVATE_KEY" COSIGN_PASSWORD="$COSIGN_PASSWORD" \\
    239             cosign sign --yes --key env://COSIGN_PRIVATE_KEY \\
    240               --new-bundle-format=false \\
    241               --use-signing-config=false \\
    242               --registry-referrers-mode=oci-1-1 \\
    243               "${IMAGE}@${DIGEST}"
    244             { set -x; } 2>/dev/null
    245           \'\'\'
    246         }
    247 
    248         // ---------------------------------------------------------------------------
    249         // continuwuity — Dockerfile: apps/talos_cluster/continuwuity/
    250         // ---------------------------------------------------------------------------
    251         def build_continuwuity() {
    252           sh \'\'\'
    253             set -eu
    254             echo "=== Building continuwuity ==="
    255 
    256             TAG_TS="$(date -u +%Y%m%d-%H%M%S)"
    257             TAG_SHA="sha-$(git rev-parse --short HEAD)"
    258             IMAGE="registry.midnightthoughts.space/mtrnord/cluster/continuwuity"
    259 
    260             docker buildx build \\
    261               --platform linux/amd64,linux/arm64 \\
    262               --tag "${IMAGE}:main" \\
    263               --tag "${IMAGE}:${TAG_TS}" \\
    264               --tag "${IMAGE}:${TAG_SHA}" \\
    265               --metadata-file /tmp/continuwuity-meta.json \\
    266               --push \\
    267               -f apps/talos_cluster/continuwuity/Dockerfile \\
    268               apps/talos_cluster/continuwuity/
    269 
    270             DIGEST="$(jq -r \'."containerimage.digest"\' /tmp/continuwuity-meta.json)"
    271             echo "Signing ${IMAGE}@${DIGEST}"
    272             { set +x; } 2>/dev/null
    273             COSIGN_PRIVATE_KEY="$(cat /var/run/secrets/cosign/key)"
    274             COSIGN_PASSWORD="$(cat /var/run/secrets/cosign/password)"
    275             COSIGN_EXPERIMENTAL=1 COSIGN_OCI_EXPERIMENTAL=1 \\
    276             COSIGN_PRIVATE_KEY="$COSIGN_PRIVATE_KEY" COSIGN_PASSWORD="$COSIGN_PASSWORD" \\
    277             cosign sign --yes --key env://COSIGN_PRIVATE_KEY \\
    278               --new-bundle-format=false \\
    279               --use-signing-config=false \\
    280               --registry-referrers-mode=oci-1-1 \\
    281               "${IMAGE}@${DIGEST}"
    282             { set -x; } 2>/dev/null
    283           \'\'\'
    284         }
    285 
    286         // ---------------------------------------------------------------------------
    287         // blog — Dockerfile: apps/talos_cluster/blog/docker/
    288         // ---------------------------------------------------------------------------
    289         def build_blog() {
    290           sh \'\'\'
    291             set -eu
    292             echo "=== Building blog ==="
    293 
    294             TAG_TS="$(date -u +%Y%m%d-%H%M%S)"
    295             TAG_SHA="sha-$(git rev-parse --short HEAD)"
    296             IMAGE="registry.midnightthoughts.space/mtrnord/blog"
    297 
    298             docker buildx build \\
    299               --platform linux/amd64,linux/arm64 \\
    300               --tag "${IMAGE}:latest" \\
    301               --tag "${IMAGE}:${TAG_TS}" \\
    302               --tag "${IMAGE}:${TAG_SHA}" \\
    303               --metadata-file /tmp/blog-meta.json \\
    304               --push \\
    305               -f apps/talos_cluster/blog/docker/Dockerfile \\
    306               apps/talos_cluster/blog/docker/
    307 
    308             DIGEST="$(jq -r \'."containerimage.digest"\' /tmp/blog-meta.json)"
    309             echo "Signing ${IMAGE}@${DIGEST}"
    310             { set +x; } 2>/dev/null
    311             COSIGN_PRIVATE_KEY="$(cat /var/run/secrets/cosign/key)"
    312             COSIGN_PASSWORD="$(cat /var/run/secrets/cosign/password)"
    313             COSIGN_EXPERIMENTAL=1 COSIGN_OCI_EXPERIMENTAL=1 \\
    314             COSIGN_PRIVATE_KEY="$COSIGN_PRIVATE_KEY" COSIGN_PASSWORD="$COSIGN_PASSWORD" \\
    315             cosign sign --yes --key env://COSIGN_PRIVATE_KEY \\
    316               --new-bundle-format=false \\
    317               --use-signing-config=false \\
    318               --registry-referrers-mode=oci-1-1 \\
    319               "${IMAGE}@${DIGEST}"
    320             { set -x; } 2>/dev/null
    321           \'\'\'
    322         }
    323 
    324         // ---------------------------------------------------------------------------
    325         // bookwyrm — clones upstream source, applies our patch, skips if tag exists
    326         // Mirrors the logic in .github/workflows/build-bookwyrm.yaml
    327         // ---------------------------------------------------------------------------
    328         def build_bookwyrm() {
    329           sh \'\'\'
    330             set -eu
    331             echo "=== Building bookwyrm ==="
    332 
    333             # Determine latest upstream release
    334             VERSION="$(wget -qO - \'https://api.github.com/repos/bookwyrm-social/bookwyrm/releases/latest\' \\
    335               | jq -r \'.tag_name\')"
    336             if [ -z "$VERSION" ]; then
    337               echo "ERROR: could not determine upstream bookwyrm version"
    338               exit 1
    339             fi
    340             echo "Upstream version: ${VERSION}"
    341 
    342             IMAGE="registry.midnightthoughts.space/mtrnord/bookwyrm"
    343 
    344             # Skip if this tag already exists in the registry (same logic as GH Actions)
    345             TAGS_JSON="$(wget -qO - \'https://registry.midnightthoughts.space/v2/mtrnord/bookwyrm/tags/list\' 2>/dev/null || echo \'{}\')"
    346             if echo "$TAGS_JSON" | jq -e --arg v "$VERSION" \'(.tags // []) | contains([$v])\' > /dev/null 2>&1; then
    347               echo "Tag ${VERSION} already exists in registry — skipping build"
    348               exit 0
    349             fi
    350             echo "Tag ${VERSION} not found in registry — building"
    351 
    352             # Clone bookwyrm source at the release tag
    353             rm -rf /tmp/bookwyrm
    354             git clone --depth=1 --branch "${VERSION}" \\
    355               https://github.com/bookwyrm-social/bookwyrm.git /tmp/bookwyrm
    356 
    357             # Apply our Dockerfile patch (tolerates if it doesn\'t apply cleanly)
    358             PATCH="$(pwd)/apps/talos_cluster/bookwyrm/dockerfile.patch"
    359             cd /tmp/bookwyrm
    360             if git apply --check "$PATCH" 2>/dev/null; then
    361               git apply "$PATCH"
    362               echo "Dockerfile patch applied"
    363             else
    364               echo "Patch does not apply cleanly — proceeding without it"
    365             fi
    366 
    367             docker buildx build \\
    368               --platform linux/amd64,linux/arm64 \\
    369               --tag "${IMAGE}:${VERSION}" \\
    370               --tag "${IMAGE}:latest" \\
    371               --metadata-file /tmp/bookwyrm-meta.json \\
    372               --push \\
    373               .
    374 
    375             DIGEST="$(jq -r \'."containerimage.digest"\' /tmp/bookwyrm-meta.json)"
    376             echo "Signing ${IMAGE}@${DIGEST}"
    377             { set +x; } 2>/dev/null
    378             COSIGN_PRIVATE_KEY="$(cat /var/run/secrets/cosign/key)"
    379             COSIGN_PASSWORD="$(cat /var/run/secrets/cosign/password)"
    380             COSIGN_EXPERIMENTAL=1 COSIGN_OCI_EXPERIMENTAL=1 \\
    381             COSIGN_PRIVATE_KEY="$COSIGN_PRIVATE_KEY" COSIGN_PASSWORD="$COSIGN_PASSWORD" \\
    382             cosign sign --yes --key env://COSIGN_PRIVATE_KEY \\
    383               --new-bundle-format=false \\
    384               --use-signing-config=false \\
    385               --registry-referrers-mode=oci-1-1 \\
    386               "${IMAGE}@${DIGEST}"
    387             { set -x; } 2>/dev/null
    388           \'\'\'
    389         }
    390       '''.stripIndent())
    391       sandbox(true)
    392     }
    393   }
    394 }