From b4e3da99fa6fa582274a187d4a5fc7143fef5bed Mon Sep 17 00:00:00 2001
From: Timothée Ravier <tim@siosm.fr>
Date: Apr 24 2025 17:04:44 +0000
Subject: [PATCH 1/6] justfile: Cleanup upload recipe


---

diff --git a/justfile b/justfile
index 3a4fdad..913cc6e 100644
--- a/justfile
+++ b/justfile
@@ -346,11 +346,11 @@ upload-container variant=default_variant:
     fi
 
     if [[ -z ${CI_REGISTRY_USER+x} ]] || [[ -z ${CI_REGISTRY_PASSWORD+x} ]]; then
-        echo "Skipping artifact archiving: Not in CI"
+        echo "Skipping container upload: Not in CI"
         exit 0
     fi
     if [[ "${CI}" != "true" ]]; then
-        echo "Skipping artifact archiving: Not in CI"
+        echo "Skipping container upload: Not in CI"
         exit 0
     fi
 
@@ -369,23 +369,15 @@ upload-container variant=default_variant:
         echo "${buildid}" > .buildid
     fi
 
-    git_commit=""
-    if [[ -n "${CI_COMMIT_SHORT_SHA}" ]]; then
-        git_commit="${CI_COMMIT_SHORT_SHA}"
-    else
-        git_commit="$(git rev-parse --short HEAD)"
-    fi
-
     # Login to the registry
     skopeo login --username "${CI_REGISTRY_USER}" --password "${CI_REGISTRY_PASSWORD}" quay.io
 
-    # Copy to the new names
     image="quay.io/fedora-ostree-desktops/${variant}"
 
-    # Use '--dest-compress-format zstd:chunked' only once 41 is released
+    # Support for the zstd:chunked format is not ready yet
     SKOPEO_ARGS="--retry-times 3 --dest-compress-format gzip"
 
-    # Copy fully versioned tag (major version, build date/id, git commit)
+    # Push fully versioned tag (major version, build date/id)
     skopeo copy ${SKOPEO_ARGS} \
         "oci-archive:${variant}.ociarchive" \
         "docker://${image}:${version}.${buildid}"

From 83887af4eab559c8b9517b9dc5f8eb3f78fb47b7 Mon Sep 17 00:00:00 2001
From: Timothée Ravier <tim@siosm.fr>
Date: Apr 24 2025 17:04:44 +0000
Subject: [PATCH 2/6] justfile: Support uploading with an arch suffix


---

diff --git a/justfile b/justfile
index 913cc6e..8ebf4ac 100644
--- a/justfile
+++ b/justfile
@@ -4,6 +4,7 @@
 
 # Set a default for some recipes
 default_variant := "silverblue"
+default_arch := "default"
 # Current default in Pungi
 force_nocache := "true"
 
@@ -332,11 +333,12 @@ lorax variant=default_variant:
         ${pwd}/iso/linux
 
 # Upload the containers to a registry (Quay.io)
-upload-container variant=default_variant:
+upload-container variant=default_variant arch=default_arch:
     #!/bin/bash
     set -euxo pipefail
 
     variant={{variant}}
+    arch={{arch}}
 
     declare -A pretty_names={{pretty_names}}
     variant_pretty=${pretty_names[$variant]-}
@@ -374,22 +376,28 @@ upload-container variant=default_variant:
 
     image="quay.io/fedora-ostree-desktops/${variant}"
 
+    # Only append arch suffix if requested
+    suffix=""
+    if [[ ${arch} != "default" ]]; then
+        suffix="-${arch}"
+    fi
+
     # Support for the zstd:chunked format is not ready yet
     SKOPEO_ARGS="--retry-times 3 --dest-compress-format gzip"
 
     # Push fully versioned tag (major version, build date/id)
     skopeo copy ${SKOPEO_ARGS} \
         "oci-archive:${variant}.ociarchive" \
-        "docker://${image}:${version}.${buildid}"
+        "docker://${image}:${version}.${buildid}${suffix}"
 
     # Update "un-versioned" tag (only major version)
     skopeo copy ${SKOPEO_ARGS} \
-        "docker://${image}:${version}.${buildid}" \
-        "docker://${image}:${version}"
+        "docker://${image}:${version}.${buildid}${suffix}" \
+        "docker://${image}:${version}${suffix}"
 
     if [[ "${variant}" == "kinoite-nightly" ]]; then
         # Update latest tag for kinoite-nightly only
         skopeo copy ${SKOPEO_ARGS} \
             "docker://${image}:${version}.${buildid}" \
-            "docker://${image}:latest"
+            "docker://${image}:latest${suffix}"
     fi

From 87baebcd4966f319ee9aea3a49894502b0c21365 Mon Sep 17 00:00:00 2001
From: Timothée Ravier <tim@siosm.fr>
Date: Apr 24 2025 17:04:44 +0000
Subject: [PATCH 3/6] justfile: Add support for generating multi-arch manifests


---

diff --git a/justfile b/justfile
index 8ebf4ac..ee891fe 100644
--- a/justfile
+++ b/justfile
@@ -401,3 +401,48 @@ upload-container variant=default_variant arch=default_arch:
             "docker://${image}:${version}.${buildid}" \
             "docker://${image}:latest${suffix}"
     fi
+
+# Create a multi-arch manifest for a given variant and push it to a registry
+multi-arch-manifest variant=default_variant:
+    #!/bin/bash
+    set -euxo pipefail
+
+    variant={{variant}}
+
+    declare -A pretty_names={{pretty_names}}
+    variant_pretty=${pretty_names[$variant]-}
+    if [[ -z $variant_pretty ]]; then
+        echo "Unknown variant"
+        exit 1
+    fi
+
+    if [[ -z ${CI_REGISTRY_USER+x} ]] || [[ -z ${CI_REGISTRY_PASSWORD+x} ]]; then
+        echo "Skipping multi-arch-manifest: Not in CI"
+        exit 0
+    fi
+    if [[ "${CI}" != "true" ]]; then
+        echo "Skipping multi-arch-manifest: Not in CI"
+        exit 0
+    fi
+
+    version=""
+    if [[ "$(git rev-parse --abbrev-ref HEAD)" == "main" ]] || [[ -f "fedora-rawhide.repo" ]]; then
+        version="rawhide"
+    else
+        version="$(rpm-ostree compose tree --print-only --repo=repo ${variant}.yaml | jq -r '."mutate-os-release"')"
+    fi
+
+    # Login to the registry
+    skopeo login --username "${CI_REGISTRY_USER}" --password "${CI_REGISTRY_PASSWORD}" quay.io
+
+    image="quay.io/fedora-ostree-desktops/${variant}"
+
+    # Create manifest
+    buildah manifest create "${image}:${version}" \
+            "${image}:${version}-x86_64" \
+            "${image}:${version}-aarch64"
+
+    # Push to registry
+    buildah manifest push \
+        "${image}:${version}" \
+        "docker://${image}:${version}"

From bcba695e625a2cdabf292707943dd906c8c8d971 Mon Sep 17 00:00:00 2001
From: Timothée Ravier <tim@siosm.fr>
Date: Apr 24 2025 17:04:44 +0000
Subject: [PATCH 4/6] justfile: Add support for signing with cosign


---

diff --git a/justfile b/justfile
index ee891fe..a5f3b47 100644
--- a/justfile
+++ b/justfile
@@ -446,3 +446,51 @@ multi-arch-manifest variant=default_variant:
     buildah manifest push \
         "${image}:${version}" \
         "docker://${image}:${version}"
+
+# Sign containers using cosign (sigstore)
+sign variant=default_variant:
+    #!/bin/bash
+    set -euxo pipefail
+
+    variant={{variant}}
+
+    declare -A pretty_names={{pretty_names}}
+    variant_pretty=${pretty_names[$variant]-}
+    if [[ -z $variant_pretty ]]; then
+        echo "Unknown variant"
+        exit 1
+    fi
+
+    if [[ -z ${CI_REGISTRY_USER+x} ]] || [[ -z ${CI_REGISTRY_PASSWORD+x} ]]; then
+        echo "Skipping artifact archiving: Not in CI"
+        exit 0
+    fi
+    if [[ "${CI}" != "true" ]]; then
+        echo "Skipping artifact archiving: Not in CI"
+        exit 0
+    fi
+
+    version=""
+    if [[ "$(git rev-parse --abbrev-ref HEAD)" == "main" ]] || [[ -f "fedora-rawhide.repo" ]]; then
+        version="rawhide"
+    else
+        version="$(rpm-ostree compose tree --print-only --repo=repo ${variant}.yaml | jq -r '."mutate-os-release"')"
+    fi
+
+    # Login to the registry
+    skopeo login \
+        --username "${CI_REGISTRY_USER}" \
+        --password "${CI_REGISTRY_PASSWORD}" \
+        --authfile="${HOME}/.docker/config.json" \
+        quay.io
+
+    # Decode private key
+    printenv "COSIGN_PRIVATE_KEY" > private.key.b64
+    base64 --decode private.key.b64 > private.key
+
+    # Sign images recursively
+    image="quay.io/fedora-ostree-desktops/${variant}"
+    cosign sign -y --key private.key --recursive=true ${image}:${version}
+
+    # Cleanup private key
+    rm private.key.b64 private.key

From 51802f9e202fc6f22fb07a3410bc325ed2309f45 Mon Sep 17 00:00:00 2001
From: Timothée Ravier <tim@siosm.fr>
Date: Apr 24 2025 17:07:01 +0000
Subject: [PATCH 5/6] gitlab-ci: Import script to generate config


---

diff --git a/generate-gitlab-ci.sh b/generate-gitlab-ci.sh
new file mode 100755
index 0000000..1e99eb7
--- /dev/null
+++ b/generate-gitlab-ci.sh
@@ -0,0 +1,100 @@
+#!/bin/bash
+# SPDX-License-Identifier: MIT
+
+set -euo pipefail
+# set -x
+
+variants=(
+    'silverblue'
+    'kinoite'
+    'kinoite-mobile'
+    'sway-atomic'
+    'xfce-atomic'
+    'lxqt-atomic'
+    'budgie-atomic'
+    'base-atomic'
+    'cosmic-atomic'
+)
+
+branch="$(git rev-parse --abbrev-ref HEAD)"
+release=""
+if [[ "${branch}" == "main" ]] || [[ -f "fedora-rawhide.repo" ]]; then
+    release="rawhide"
+else
+    release="$(rpm-ostree compose tree --print-only --repo=repo silverblue.yaml | jq -r '."mutate-os-release"')"
+fi
+
+{
+cat <<EOF
+# SPDX-License-Identifier: MIT
+
+# Only used in https://gitlab.com/fedora/ostree/ci-test
+# For tests running in the Fedora infrastructure, see .zuul.yaml and
+# https://fedoraproject.org/wiki/Zuul-based-ci
+
+# See: https://gitlab.com/fedora/ostree/buildroot
+image: quay.io/fedora-ostree-desktops/buildroot:$release
+
+# As those are not official images, we build all available variants.
+# We build the images for merge requests, we push and sign them for commits
+# pushed to release branches and scheduled pipelines.
+
+stages:
+  - build
+  - merge
+EOF
+
+for variant in "${variants[@]}"; do
+cat <<EOF
+
+mr-$variant-x86_64:
+  stage: build
+  script:
+    - just compose-image $variant
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: \$CI_PIPELINE_SOURCE == "merge_request_event"
+
+mr-$variant-aarch64:
+  stage: build
+  script:
+    - just compose-image $variant
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: \$CI_PIPELINE_SOURCE == "merge_request_event"
+
+build-$variant-x86_64:
+  stage: build
+  script:
+    - just compose-image $variant
+    - just upload-container $variant x86_64
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: \$CI_COMMIT_BRANCH == "$branch" && (\$CI_PIPELINE_SOURCE == "push" || \$CI_PIPELINE_SOURCE == "schedule")
+
+build-$variant-aarch64:
+  stage: build
+  script:
+    - just compose-image $variant
+    - just upload-container $variant aarch64
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: \$CI_COMMIT_BRANCH == "$branch" && (\$CI_PIPELINE_SOURCE == "push" || \$CI_PIPELINE_SOURCE == "schedule")
+
+merge-$variant:
+  stage: merge
+  script:
+    - just multi-arch-manifest $variant
+    - just sign $variant
+  needs: ["build-$variant-x86_64", "build-$variant-aarch64"]
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: \$CI_COMMIT_BRANCH == "$branch" && (\$CI_PIPELINE_SOURCE == "push" || \$CI_PIPELINE_SOURCE == "schedule")
+EOF
+done
+} > .gitlab-ci.yml

From 20933a134e1d05dc673601a69a892cb302393345 Mon Sep 17 00:00:00 2001
From: Timothée Ravier <tim@siosm.fr>
Date: Apr 24 2025 17:08:11 +0000
Subject: [PATCH 6/6] gitlab-ci: Re-generate using script


---

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 55e6a73..f02055f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,41 +5,453 @@
 # https://fedoraproject.org/wiki/Zuul-based-ci
 
 # See: https://gitlab.com/fedora/ostree/buildroot
-image: quay.io/fedora-ostree-desktops/buildroot
+image: quay.io/fedora-ostree-desktops/buildroot:rawhide
+
+# As those are not official images, we build all available variants.
+# We build the images for merge requests, we push and sign them for commits
+# pushed to release branches and scheduled pipelines.
 
 stages:
   - build
+  - merge
+
+mr-silverblue-x86_64:
+  stage: build
+  script:
+    - just compose-image silverblue
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+mr-silverblue-aarch64:
+  stage: build
+  script:
+    - just compose-image silverblue
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+build-silverblue-x86_64:
+  stage: build
+  script:
+    - just compose-image silverblue
+    - just upload-container silverblue x86_64
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+build-silverblue-aarch64:
+  stage: build
+  script:
+    - just compose-image silverblue
+    - just upload-container silverblue aarch64
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+merge-silverblue:
+  stage: merge
+  script:
+    - just multi-arch-manifest silverblue
+    - just sign silverblue
+  needs: ["build-silverblue-x86_64", "build-silverblue-aarch64"]
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+mr-kinoite-x86_64:
+  stage: build
+  script:
+    - just compose-image kinoite
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+mr-kinoite-aarch64:
+  stage: build
+  script:
+    - just compose-image kinoite
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+build-kinoite-x86_64:
+  stage: build
+  script:
+    - just compose-image kinoite
+    - just upload-container kinoite x86_64
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+build-kinoite-aarch64:
+  stage: build
+  script:
+    - just compose-image kinoite
+    - just upload-container kinoite aarch64
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+merge-kinoite:
+  stage: merge
+  script:
+    - just multi-arch-manifest kinoite
+    - just sign kinoite
+  needs: ["build-kinoite-x86_64", "build-kinoite-aarch64"]
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+mr-kinoite-mobile-x86_64:
+  stage: build
+  script:
+    - just compose-image kinoite-mobile
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
 
-# As those are not official images, we build all available variants
-.parallel:
-  parallel:
-    matrix:
-    - VARIANT:
-      - silverblue
-      - kinoite
-      - kinoite-mobile
-      - sway-atomic
-      - xfce-atomic
-      - lxqt-atomic
-      - budgie-atomic
-      - base-atomic
-      - cosmic-atomic
+mr-kinoite-mobile-aarch64:
+  stage: build
+  script:
+    - just compose-image kinoite-mobile
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
 
-# Only build the images for merge requests
-buildmr:
+build-kinoite-mobile-x86_64:
   stage: build
   script:
-    - just compose-image $VARIANT
-  parallel: !reference [.parallel, parallel]
+    - just compose-image kinoite-mobile
+    - just upload-container kinoite-mobile x86_64
+  tags:
+    - saas-linux-small-amd64
   rules:
-  - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
 
-# Build and upload the images for commits pushed to the branch & scheduled pipelines
-build:
+build-kinoite-mobile-aarch64:
   stage: build
   script:
-    - just compose-image $VARIANT
-    - just upload-container $VARIANT
-  parallel: !reference [.parallel, parallel]
+    - just compose-image kinoite-mobile
+    - just upload-container kinoite-mobile aarch64
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+merge-kinoite-mobile:
+  stage: merge
+  script:
+    - just multi-arch-manifest kinoite-mobile
+    - just sign kinoite-mobile
+  needs: ["build-kinoite-mobile-x86_64", "build-kinoite-mobile-aarch64"]
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+mr-sway-atomic-x86_64:
+  stage: build
+  script:
+    - just compose-image sway-atomic
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+mr-sway-atomic-aarch64:
+  stage: build
+  script:
+    - just compose-image sway-atomic
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+build-sway-atomic-x86_64:
+  stage: build
+  script:
+    - just compose-image sway-atomic
+    - just upload-container sway-atomic x86_64
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+build-sway-atomic-aarch64:
+  stage: build
+  script:
+    - just compose-image sway-atomic
+    - just upload-container sway-atomic aarch64
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+merge-sway-atomic:
+  stage: merge
+  script:
+    - just multi-arch-manifest sway-atomic
+    - just sign sway-atomic
+  needs: ["build-sway-atomic-x86_64", "build-sway-atomic-aarch64"]
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+mr-xfce-atomic-x86_64:
+  stage: build
+  script:
+    - just compose-image xfce-atomic
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+mr-xfce-atomic-aarch64:
+  stage: build
+  script:
+    - just compose-image xfce-atomic
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+build-xfce-atomic-x86_64:
+  stage: build
+  script:
+    - just compose-image xfce-atomic
+    - just upload-container xfce-atomic x86_64
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+build-xfce-atomic-aarch64:
+  stage: build
+  script:
+    - just compose-image xfce-atomic
+    - just upload-container xfce-atomic aarch64
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+merge-xfce-atomic:
+  stage: merge
+  script:
+    - just multi-arch-manifest xfce-atomic
+    - just sign xfce-atomic
+  needs: ["build-xfce-atomic-x86_64", "build-xfce-atomic-aarch64"]
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+mr-lxqt-atomic-x86_64:
+  stage: build
+  script:
+    - just compose-image lxqt-atomic
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+mr-lxqt-atomic-aarch64:
+  stage: build
+  script:
+    - just compose-image lxqt-atomic
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+build-lxqt-atomic-x86_64:
+  stage: build
+  script:
+    - just compose-image lxqt-atomic
+    - just upload-container lxqt-atomic x86_64
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+build-lxqt-atomic-aarch64:
+  stage: build
+  script:
+    - just compose-image lxqt-atomic
+    - just upload-container lxqt-atomic aarch64
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+merge-lxqt-atomic:
+  stage: merge
+  script:
+    - just multi-arch-manifest lxqt-atomic
+    - just sign lxqt-atomic
+  needs: ["build-lxqt-atomic-x86_64", "build-lxqt-atomic-aarch64"]
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+mr-budgie-atomic-x86_64:
+  stage: build
+  script:
+    - just compose-image budgie-atomic
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+mr-budgie-atomic-aarch64:
+  stage: build
+  script:
+    - just compose-image budgie-atomic
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+build-budgie-atomic-x86_64:
+  stage: build
+  script:
+    - just compose-image budgie-atomic
+    - just upload-container budgie-atomic x86_64
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+build-budgie-atomic-aarch64:
+  stage: build
+  script:
+    - just compose-image budgie-atomic
+    - just upload-container budgie-atomic aarch64
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+merge-budgie-atomic:
+  stage: merge
+  script:
+    - just multi-arch-manifest budgie-atomic
+    - just sign budgie-atomic
+  needs: ["build-budgie-atomic-x86_64", "build-budgie-atomic-aarch64"]
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+mr-base-atomic-x86_64:
+  stage: build
+  script:
+    - just compose-image base-atomic
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+mr-base-atomic-aarch64:
+  stage: build
+  script:
+    - just compose-image base-atomic
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+build-base-atomic-x86_64:
+  stage: build
+  script:
+    - just compose-image base-atomic
+    - just upload-container base-atomic x86_64
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+build-base-atomic-aarch64:
+  stage: build
+  script:
+    - just compose-image base-atomic
+    - just upload-container base-atomic aarch64
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+merge-base-atomic:
+  stage: merge
+  script:
+    - just multi-arch-manifest base-atomic
+    - just sign base-atomic
+  needs: ["build-base-atomic-x86_64", "build-base-atomic-aarch64"]
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+mr-cosmic-atomic-x86_64:
+  stage: build
+  script:
+    - just compose-image cosmic-atomic
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+mr-cosmic-atomic-aarch64:
+  stage: build
+  script:
+    - just compose-image cosmic-atomic
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+build-cosmic-atomic-x86_64:
+  stage: build
+  script:
+    - just compose-image cosmic-atomic
+    - just upload-container cosmic-atomic x86_64
+  tags:
+    - saas-linux-small-amd64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+build-cosmic-atomic-aarch64:
+  stage: build
+  script:
+    - just compose-image cosmic-atomic
+    - just upload-container cosmic-atomic aarch64
+  tags:
+    - saas-linux-small-arm64
+  rules:
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+
+merge-cosmic-atomic:
+  stage: merge
+  script:
+    - just multi-arch-manifest cosmic-atomic
+    - just sign cosmic-atomic
+  needs: ["build-cosmic-atomic-x86_64", "build-cosmic-atomic-aarch64"]
+  tags:
+    - saas-linux-small-amd64
   rules:
-  - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")
+    - if: $CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule")

