MAQPNADocs

Air-gapped install

Build a signed offline bundle on a connected host, carry it across, verify it, mirror the images into a private registry and install MAQPNA with no internet access.

MAQPNA makes no outbound calls by default: no phone-home, no usage analytics, no update checks and no licence server. For a disconnected cluster you build an offline bundle on a connected host, carry it across, verify it, push its images to your private registry and install the chart from the bundle.

flowchart LR
  subgraph Connected host
    A[maqpna airgap bundle] --> B[maqpna-airgap-TAG.tar.gz<br/>images, chart, SBOMs,<br/>SHA256SUMS + signature]
  end
  B -->|removable media| C
  subgraph Air-gapped site
    C[maqpna airgap verify<br/>--require-signature] --> D[maqpna airgap push<br/>--registry REG]
    D --> E[maqpna install --chart ./chart/...<br/>airgap.imageRegistry=REG]
    E --> F[maqpna smoke / doctor]
  end

Goal#

An installation whose every image comes from your in-country registry, installed from a bundle whose checksums and signature you verified offline.

Prerequisites#

Where You need
Connected build host A MAQPNA source checkout (the bundle script lives in hack/), docker, and optionally helm (to package the chart), syft (SBOMs) and cosign (signature). Access to the MAQPNA images.
Air-gapped site maqpna and maqpna-install (copy the binaries across too), cosign for signature checks, one of skopeo, crane or docker for the push, kubectl and helm if you use hack/airgap-install.sh. A private registry, for example registry.eu.internal:5000/maqpna. The agent-sandbox controller is in the bundle.

What a bundle contains#

Path Content
images/ docker-archive tarballs of the 11 MAQPNA images (maqpna-operator, maqpna-gateway, maqpna-identity, maqpna-attest, maqpna-attest-agent, maqpna-exec, maqpna-egress-relay, maqpna-cli, mcp-echo, maqpna-browser, maqpna-code-interpreter) and the upstream agent-sandbox controller images
images.txt <source-ref> <archive> <repo:tag> per image
chart/ The packaged Helm chart and values-sovereign-eu.yaml (the chart sources when helm was not installed)
manifests/ The upstream agent-sandbox release manifest (sandbox-with-extensions.yaml)
sbom/ SPDX SBOMs per image, when syft is installed
hack/airgap-install.sh The install script
SHA256SUMS Checksums of every file above
SHA256SUMS.sig or SHA256SUMS.cosign.bundle A key-based or keyless cosign signature of SHA256SUMS

Steps#

1. Build the bundle (connected host)#

cd maqpna        # a checkout of the MAQPNA source (https://maqpna.com/github)
maqpna airgap bundle --registry "$REGISTRY" --tag 0.1.1 --pull --key cosign.key --out bundle/

REGISTRY is the release registry listed on maqpna.com/download.

maqpna airgap bundle runs hack/airgap-bundle.sh from the checkout (--repo DIR, default the current directory or a parent). The flags map to the script's environment variables:

Flag Env Default
--registry REGISTRY ghcr.io/maqpna
--tag TAG dev
--agent-sandbox-version AGENT_SANDBOX_VERSION v1.0.4
--out OUT_DIR dist
--key REF COSIGN_KEY none: a key-based SHA256SUMS.sig (file, awskms://, pkcs11:)
--keyless COSIGN_KEYLESS=1 off: a keyless SHA256SUMS.cosign.bundle
--pull PULL=1 off: use local images and pull only missing ones

The script writes maqpna-airgap-<TAG>.tar.gz to the output directory. Without --key or --keyless the bundle is not signed; sign it, so the site can check where it came from.

2. Verify the bundle (air-gapped site)#

Extract it and verify the checksums, the signature and that all 11 images are present:

tar xzf maqpna-airgap-0.1.1.tar.gz && cd maqpna-airgap-0.1.1
maqpna airgap verify . --require-signature --key cosign.pub

For a keyless bundle, pass the identity that signed it (the person or CI workflow on the build host) instead of a key, and a Sigstore trusted_root.json for offline verification:

maqpna airgap verify . --require-signature \
  --certificate-identity release-builder@example.eu \
  --certificate-oidc-issuer https://accounts.google.com \
  --trusted-root trusted_root.json

Without --certificate-identity or --certificate-identity-regexp, the default identity is the MAQPNA release workflows.

Real output of a key-signed test bundle without images (--no-image-check skips the image list):

$ maqpna airgap verify bundle --require-signature --key cosign.pub --no-image-check
checksums: 54 ok, 0 failed
signature: cosign verify-blob OK (key)
bundle OK

A changed file fails, with exit status 3:

$ maqpna airgap verify bundle --no-image-check
FAIL  chart/maqpna/values.yaml: checksum mismatch
checksums: 53 ok, 1 failed
signature: neither SHA256SUMS.sig nor SHA256SUMS.cosign.bundle present - NOT VERIFIED

Without --no-image-check, a bundle that lacks images fails too:

images: 0 of 11 MAQPNA images, 0 upstream (tag )
FAIL  images missing from the bundle: maqpna-operator, maqpna-gateway, maqpna-identity, maqpna-attest, maqpna-attest-agent, maqpna-exec, maqpna-egress-relay, maqpna-cli, mcp-echo, maqpna-browser, maqpna-code-interpreter
FAIL  no upstream agent-sandbox images in the bundle

The standalone offline binary maqpna-sovereign verify-bundle [-key cosign.pub] [-require-signature] DIR runs the same checksum and signature checks (not the image list) for CI pipelines that carry only that binary. It exits 1 on a failure.

3. Push the images to your registry#

maqpna airgap push . --registry registry.eu.internal:5000/maqpna --dry-run   # print the copies
maqpna airgap push . --registry registry.eu.internal:5000/maqpna

push copies every image in images.txt with skopeo, crane or docker, whichever it finds.

4. Install from the bundle#

Install the agent-sandbox controller from the bundle's manifest (its images now come from your registry; edit the image references if your nodes cannot pull from the upstream names), then install the chart from the bundle:

kubectl apply -f manifests/agent-sandbox-v1.0.4.yaml
maqpna install --chart ./chart/maqpna-0.1.1.tgz --profile sovereign-eu -f site-values.yaml \
  --set airgap.imageRegistry=registry.eu.internal:5000/maqpna --wait

airgap.enabled: true (set by the sovereign-eu profile) makes the chart refuse to render public registry references for MAQPNA images; airgap.imageRegistry replaces image.registry. Also set sovereignty.allowedRegistries to your mirror, so sandboxes may only run images from it.

Alternatively, the bundle's script does the same with Helm and kubectl:

REGISTRY=registry.eu.internal:5000/maqpna SKIP_PUSH=1 EXTRA_VALUES=site-values.yaml \
  COSIGN_PUB=cosign.pub hack/airgap-install.sh
Variable Meaning
REGISTRY Required. Private registry and prefix.
NAMESPACE, RELEASE Default maqpna-system, maqpna.
VALUES Default chart/values-sovereign-eu.yaml.
EXTRA_VALUES Your site overrides (recommended).
COSIGN_PUB Public key to verify SHA256SUMS.sig (recommended).
SKIP_PUSH=1 Images already mirrored; only install.
SKIP_DEPS=1 Do not apply the agent-sandbox manifest.
DRY_RUN=1 Print what would be done.

5. Verify the installation#

maqpna smoke --gateway "$GW"
maqpna doctor --strict

Single-node edge sites (k3s)#

Import the images into k3s's containerd instead of pushing to a registry, then install from the bundle's chart:

for f in images/*.tar; do sudo k3s ctr images import "$f"; done
maqpna install --chart ./chart/maqpna-0.1.1.tgz --profile dev --wait

Upgrade an air-gapped installation#

Build, carry and verify the new bundle, push its images, then upgrade from its chart:

maqpna upgrade check --chart ./chart/maqpna-0.2.0.tgz
maqpna upgrade --chart ./chart/maqpna-0.2.0.tgz --dry-run
maqpna upgrade --chart ./chart/maqpna-0.2.0.tgz --wait --atomic

See Upgrade and rollback.

Troubleshooting#

Symptom Cause Fix
signature: neither SHA256SUMS.sig nor SHA256SUMS.cosign.bundle present - NOT VERIFIED The bundle was built without --key or --keyless Rebuild with a signature, or accept an unsigned bundle by omitting --require-signature (not recommended).
bundle --key stops with must specify --bundle with --new-bundle-format cosign 3.x refuses a key-based sign-blob --output-signature without the legacy-format flags, and the script stops on the error Use --keyless, or cosign 2.x on the build host.
FAIL images missing from the bundle Images were not available locally and --pull was not set, or --tag does not match the published tag Rebuild with --pull and --tag X.Y.Z.
Pods in ImagePullBackOff airgap.imageRegistry does not match where you pushed Use the same value as push --registry.
preflight: image-registry-allowed fails The mirror is not in sovereignty.allowedRegistries Add the registry prefix.

Next steps#