Skip to content

GitLab CI

The GitLab equivalent of the GitHub Actions guide. The job definition is inlined below; copy it into your .gitlab-ci.yml.

CI/CD variables

In Project → Settings → CI/CD → Variables, define (mark them Masked and Protected if appropriate):

  • SIMPLEOTA_BASE_URL
  • SIMPLEOTA_PROJECT_ID
  • SIMPLEOTA_TOKEN

Minimal job

upload-firmware:
  image: curlimages/curl:8.10.1
  rules:
    - if: $CI_COMMIT_TAG =~ /^v/
    - if: $CI_PIPELINE_SOURCE == "web"
  script:
    - |
      MANIFEST_JSON=$(cat <<EOF
      {
        "framework": "arduino",
        "chip_family": "esp32",
        "board_id": "esp32dev",
        "partition_profile": "default_4mb",
        "nvs_schema_version": 1,
        "security_mode": "basic"
      }
      EOF
      )

      curl --fail-with-body \
        -X POST "${SIMPLEOTA_BASE_URL}/api/v1/projects/${SIMPLEOTA_PROJECT_ID}/artifacts/" \
        -H "Authorization: Bearer ${SIMPLEOTA_TOKEN}" \
        -F "manifest=${MANIFEST_JSON}" \
        -F "[email protected]"

Signed uploads

For signed firmware, define one more variable (Masked): SIMPLEOTA_SIGNING_KEY, containing the private key PEM. The curlimages/curl image has no OpenSSL, so use a base image that does:

upload-firmware-signed:
  image: alpine:3.21
  rules:
    - if: $CI_COMMIT_TAG =~ /^v/
  before_script:
    - apk add --no-cache curl openssl
  script:
    - printf '%s' "$SIMPLEOTA_SIGNING_KEY" > private.pem
    - openssl pkeyutl -sign -inkey private.pem -rawin -in firmware.bin | openssl base64 -A > firmware.sig.b64
    - rm -f private.pem
    - |
      MANIFEST_JSON=$(cat <<EOF
      {
        "framework": "arduino",
        "version_label": "${CI_COMMIT_TAG}",
        "chip_family": "esp32",
        "board_id": "esp32dev",
        "partition_profile": "default_4mb",
        "nvs_schema_version": 1,
        "security_mode": "signed",
        "signing_metadata": {
          "key_id": "prod-2026",
          "algorithm": "ed25519",
          "signature": "$(cat firmware.sig.b64)"
        }
      }
      EOF
      )

      curl --fail-with-body \
        -X POST "${SIMPLEOTA_BASE_URL}/api/v1/projects/${SIMPLEOTA_PROJECT_ID}/artifacts/" \
        -H "Authorization: Bearer ${SIMPLEOTA_TOKEN}" \
        -F "manifest=${MANIFEST_JSON}" \
        -F "[email protected]"

Replace prod-2026 with your project's key id. The signature must cover the final, shippable bytes: with ESP32 Secure Boot, sign after espsecure (see Secure Boot notes).

If your build job runs in an earlier stage, mark the binary as an artifact so this stage can pick it up:

build:
  stage: build
  script: ...
  artifacts:
    paths:
      - firmware.bin
    expire_in: 1 week