Documentation > Professional+ > Air-Gap Deployment
⭐ PRO+

Air-Gap Deployment

Build a single ISO image containing the SysManage server (or agent) plus every transitive operating-system package and Python wheel it needs, then carry that ISO into a network that has no path to the public internet and install with one command.

Overview

Many regulated environments — defense networks, classified labs, operational-technology segments, isolated tenancy zones — cannot pull packages from the public internet at install time. The Pro+ Air-Gap Install Bundle Builder solves this by producing a self-contained multi-OS ISO from an internet-connected SysManage host. You burn or copy the ISO onto removable media, walk it into the air-gapped network, mount it on the target machine, and run one installer script. The installer detects the host operating system and consumes only the bundled packages — it never reaches out to a remote mirror.

The workflow always involves two distinct machines: a build host that has internet access and a SysManage server running with a Pro+ license, and one or more target hosts sitting inside the air-gapped enclave. Pro+ is required only on the build host; the resulting ISO installs unmodified SysManage server or agent packages, both of which run under any tier (Community, Pro, or Pro+) on the target.

The Three Bundle Types

The Air-Gap Install Bundles tab in Settings can produce three distinct bundle ISOs. Choose the ones that match what you intend to run inside the enclave; all three are produced from the same UI tab on the same Pro+-licensed build host.

  • Server bundle (~1 GB) — the SysManage server .deb/.rpm/.apk/.pkg/.msi for every supported platform, plus its full transitive native-package dependency closure (PostgreSQL, nginx, Python build deps) and every Python wheel from requirements.txt. Install this first on the air-gapped server host.
  • Agent bundle (~500 MB) — the SysManage agent package for every supported platform, with its transitive native-package and wheel closures. Install this on every host inside the enclave that should be managed.
  • Pro+ overlay bundle (~50 MB) — the build host's Cython engine .so files, JavaScript plugin shims, license JWT, and cached license public key. Apply this on the air-gapped server host after the server bundle has been installed; it unlocks Pro+ engines locally without any phone-home to license.sysmanage.org. Not needed if you only need Community-tier features on the air-gapped server.

If you want Pro+ functionality on the air-gapped server, the install order is: (1) server bundle on the server host, (2) Pro+ overlay on the same server host, (3) agent bundle on each managed host. The Pro+ overlay's installer expects the SysManage server to already be in place — it copies engine modules into /var/lib/sysmanage/modules/, drops the license public key into /var/lib/sysmanage/license/, writes the license JWT into /etc/sysmanage.yaml, and restarts the service.

Supported Target Platforms

A single bundle contains installers for every platform listed below. The dispatcher script at the root of the ISO routes to the right subdirectory automatically based on the target's /etc/os-release (or uname for BSD/macOS).

  • Ubuntu — 22.04 (jammy), 24.04 (noble), 25.10 (questing), 26.04 (resolute)
  • Debian — 12 (bookworm), 13 (trixie)
  • Fedora — 40, 41
  • RHEL / Rocky / AlmaLinux — 9
  • openSUSE Leap — 15.x
  • Alpine — 3.20+
  • BSD — FreeBSD, NetBSD, OpenBSD (current release)
  • macOS — recent releases via signed .pkg
  • Windows — 10/11 x64 and ARM64 via signed .msi

The build also collects every transitive native-package dependency (e.g. nginx, docker.io, postgresql-client, libpq-dev on Ubuntu) and every transitive Python wheel from the package's requirements.txt, so the air-gapped target needs no upstream apt/dnf/zypper/PyPI access whatsoever.

Prerequisites

On the build host (internet-connected)

  • SysManage server running with a valid Pro+ license. The bundle builder UI tab and API endpoints both return 403 on Community and Pro tiers.
  • Docker must be installed and the SysManage service user (typically sysmanage, or your dev user when running make start) must be a member of the docker group. Each per-platform builder spawns a throwaway distro container.
  • Build host packages: xorriso (ISO mastering), jq (GitHub Releases parsing), curl. On Debian/Ubuntu: sudo apt install -y docker.io xorriso jq curl.
  • At least 4 GB free under /var/lib/sysmanage/airgap-bundles/ (per bundle). The server bundle currently lands around 1 GB; the agent bundle around 500 MB.
  • Outbound access to ppa.launchpadcontent.net, api.github.com, github.com, deb.debian.org, dl.fedoraproject.org, your distro mirror of choice, and PyPI (pypi.org, files.pythonhosted.org). The build pulls upstream packages and wheels through the Docker container's own apt/dnf cache.

On the target host (air-gapped)

  • A supported OS from the platforms list above, fresh enough that its kernel and libc match the bundle. The bundle ships amd64/x86_64 binaries for Linux/BSD and the corresponding native arch for macOS/Windows.
  • sudo available to the operator running the install. The installer needs root to call dpkg -i / dnf install / pkg add / installer -pkg.
  • No internet required. The installer sets PIP_NO_INDEX=1 and PIP_FIND_LINKS so the package's post-install scripts use only the bundled wheels.

Step 1 — Build the Bundle on the Internet-Connected Host

The bundle is built from the SysManage Settings UI on a Pro+-licensed host that has internet access. You do not need to install the air-gap target's OS on the build host — every per-platform fetch runs inside its own Docker container.

  1. Log into the SysManage web UI on the build host with an admin account.
  2. Navigate to Settings → Air-Gap Install Bundles. This tab is only visible when the server has an active Pro+ license.
  3. Confirm the Docker readiness banner reads "Docker is ready". If it doesn't, the banner gives the exact remediation command — usually sudo usermod -aG docker <service-user> followed by a service restart.
  4. Click Build Server Bundle, Build Agent Bundle, or Build Pro+ Bundle. You can queue all three — they will run sequentially. The Server and Agent bundles spin up Docker containers per platform (slow); the Pro+ bundle just copies local files (fast — seconds).
  5. The new bundle row appears with status queued, then building. Builds typically take 15–30 minutes depending on how many platforms are enabled and the speed of the build host's network. The UI auto-polls — no manual refresh needed.
  6. When the status flips to ready, the row's Actions column shows a download icon. The recorded Version column reflects the upstream SysManage release that was baked into this bundle.
  7. Click the download icon. Your browser downloads sysmanage-server-bundle-<version>.iso (or sysmanage-agent-bundle-<version>.iso) through the authenticated API. The file is large (~500 MB to ~1 GB) — keep the tab open until the download completes.

If a build fails, the row's Status chip shows failed and the "Recent build failures" alert at the top of the tab summarizes the first error line. The complete per-build log lives on the server at /var/lib/sysmanage/airgap-bundles/<bundle-uuid>.log — useful for diagnosing PPA build issues, missing GitHub Releases assets, or Docker registry hiccups.

Step 2 — Carry the ISO into the Air-Gapped Network

The ISO is a standard ISO-9660 image with Joliet and Rock Ridge extensions, so any tool that writes ISOs to media will work. Pick whichever method your site's security policy permits:

  • Optical media — burn to DVD or Blu-ray with xorrecord, brasero, or the OS's native burning tool. Best when your enclave's data-diode/airlock policy treats optical media as one-way write.
  • USB stick — copy the .iso file directly onto a USB device formatted with a filesystem the target can read (FAT32 / exFAT / ext4). No need to "burn" — the installer mounts the file with mount -o loop.
  • Cross-domain solution — push the file through your site's approved data diode or unidirectional gateway. The ISO is a single self-contained binary, so it transfers cleanly without referencing external resources.

For repeatable air-gap installs (lab fleets, dev environments inside the same enclave), store the ISO on a small file share inside the air-gapped network and pull it from there on each install — no need to walk a USB stick around every time.

Step 3 — Install on the Air-Gapped Target

Once the ISO is on the target host (mounted from optical media or copied as a file), the install is a single command. The dispatcher script at the root of the ISO detects the host OS and hands off to the correct platform installer.

From an ISO file (most common)

sudo mount -o loop /path/to/sysmanage-server-bundle-2.4.0.3.iso /mnt
sudo /mnt/install.sh
sudo umount /mnt

From optical media

sudo mount /dev/sr0 /mnt
sudo /mnt/install.sh
sudo umount /mnt
sudo eject /dev/sr0

The dispatcher prints a single [install] Detected: id=<os> version=<ver> line and then runs the matching platform installer (for example linux-ubuntu-resolute/install.sh on Ubuntu 26.04). The platform installer in turn invokes dpkg -i, dnf install, zypper install, apk add, pkg add, pkg_add, or installer -pkg depending on the target — always against the bundled artifacts only, never reaching out to the network.

Windows targets

Windows installs are GUI-only. Mount the ISO (right-click → Mount on Windows 10/11), open the windows\ folder, and double-click sysmanage-x64.msi or sysmanage-arm64.msi — whichever matches the target's processor architecture.

Configure /etc/sysmanage.yaml

Once the server install finishes, configure /etc/sysmanage.yaml using the online SysManage configuration builder. Two settings matter specifically for air-gap installs: set api.host: 0.0.0.0 (so the API is reachable from the web UI on another machine — binding to localhost only works for a single-host install) and set role: repository at the top level if this server is the air-gapped half of a collector/repository pair. Then run the database migrations and start the service: cd /opt/sysmanage && sudo -u sysmanage .venv/bin/python -m alembic upgrade head && sudo systemctl enable --now sysmanage.

Step 4 — Apply the Pro+ Overlay (optional)

Skip this step if the air-gapped server only needs Community-tier functionality. To enable Pro+ features (repository mirroring, child-host management, federation, etc.) on the air-gapped server, install the Pro+ overlay bundle on top of the already-installed server. The Pro+ overlay carries the licensed Cython engine modules, the license JWT, and the cached license public key — no network access to license.sysmanage.org is needed at any point.

From the air-gapped server, mount the Pro+ overlay ISO and run its installer:

sudo mount -o loop /path/to/sysmanage-proplus-bundle-<version>.iso /mnt
sudo /mnt/install.sh
sudo umount /mnt

The overlay's install.sh is idempotent — safe to re-run when you ship a refreshed overlay later. It performs five steps:

  1. Verifies the SysManage server is installed (looks for /opt/sysmanage/.venv/bin/python and /etc/sysmanage.yaml).
  2. Copies the bundled Cython engine modules and JavaScript plugin shims to /var/lib/sysmanage/modules/ (owned by sysmanage:sysmanage, mode 0644).
  3. Drops the cached license public key into /var/lib/sysmanage/license/public_key.pem. Without this, the license validator would try to phone-home and fail.
  4. Edits /etc/sysmanage.yaml via the SysManage venv's PyYAML: writes license.key (the JWT carried in the bundle), and disables geo_lookup.enabled + geo_lookup.ipapi_fallback_enabled so no DNS queries leave the enclave for IP-geolocation lookups.
  5. Populates the proplus_module_cache table in the SysManage database with a row per engine .so file (module code, platform, architecture, Python version, file path, SHA-512 hash). The module loader queries this table on startup to decide which engines to load — without these rows it would try to download them from license.sysmanage.org and fail on air-gap.

The installer then runs systemctl restart sysmanage and prints commands to verify the activation. Confirm with curl -ks http://localhost:3000/api/v1/server-info | python3 -m json.tool — you should see license_tier: enterprise (or whatever tier the JWT carries) and role_engine_loaded: true. The navbar role chip in the web UI turns green after a hard refresh.

About the offline grace period: the Pro+ overlay does not blank the license.phone_home_url setting. The license JWT includes an offline_days field (typically 30); the validator runs in offline-grace mode for that many days after install. Plan to rebuild the Pro+ overlay on the internet-connected build host and re-apply it on the air-gapped server before the grace window expires — otherwise Pro+ silently demotes to Community tier on the next service start. This is the intended enforcement mechanism that prevents the overlay ISO from being shared indefinitely.

What's Inside the Bundle

The ISO is laid out by platform, with a per-platform install.sh in each subdirectory. The root install.sh dispatches based on the detected OS. A typical server bundle looks like this:

/                              # ISO root
  install.sh                   # OS-detection dispatcher
  README.txt                   # quick-start instructions
  linux-ubuntu-jammy/
    install.sh                 # per-platform installer
    sysmanage_2.4.0.3+ppa1~jammy1_all.deb
    apt-deps/                  # transitive .deb dependencies
    wheels/                    # transitive Python wheels
  linux-ubuntu-noble/...
  linux-ubuntu-questing/...
  linux-ubuntu-resolute/...
  linux-debian-bookworm/...
  linux-fedora-40/
    sysmanage-2.4.0.3-1.x86_64.rpm
    rpm-deps/                  # transitive .rpm dependencies
    wheels/
  linux-fedora-41/...
  linux-rhel-9/...
  linux-opensuse-leap/...
  linux-alpine-3.20/
    sysmanage-2.4.0.3-alpine320.apk
    apk-deps/
    wheels/
  freebsd/sysmanage.pkg
  netbsd/sysmanage.tgz
  openbsd/sysmanage.tgz
  macos/sysmanage.pkg
  windows/sysmanage-x64.msi
  windows/sysmanage-arm64.msi

Troubleshooting

"Docker isn't ready" banner on the build host

Three distinct failure modes: Docker not installed, daemon not running, or the SysManage service user not in the docker group. The banner names which one applies and shows the exact remediation. After running the command, click Re-check Docker. If you added a group, you must also restart the SysManage service so the running process picks up the new group set — bash does not re-read /etc/group for a running process.

"Installing <package> + 0 deps..." on the air-gapped target

The bundle was built with an old script version that failed to walk transitive dependencies. Re-build the bundle on the build host after pulling the latest SysManage server release; the per-platform build log should report downloaded N apt deps; M were unavailable/virtual with N in the hundreds.

"Unsupported or unrecognised OS" from the dispatcher

The target's /etc/os-release ID/VERSION_ID don't match any of the per-platform subdirectories on the ISO. The dispatcher lists the subdirectories present in the bundle so you can confirm. Either the bundle was built with that platform disabled, or the per-platform builder for that distro failed during the build (check the build log on the build host).

Build fails on Ubuntu with "Unable to locate package sysmanage"

The Launchpad PPA accepted the source upload but the binary build is still pending or failed. The bundle script transparently falls back to fetching the generic .deb from GitHub Releases when the PPA download fails, so this is usually transient — re-run the build once the PPA shows the codename as Published.

Versioning and Re-Builds

Each bundle records the upstream SysManage release tag (e.g. 2.4.0.3) at build time, visible in the Version column of the bundle list. The recorded version comes from api.github.com/repos/bceverly/sysmanage/releases/latest (or sysmanage-agent) at the moment the build started.

To upgrade an air-gapped fleet, build a fresh bundle from the build host (after that host has pulled the new release), carry the new ISO into the enclave, and re-run the installer on each target. The installers use dpkg -i / dnf install / apk add with the same package name, which upgrades in place. Configuration and database state are preserved across upgrades.

Old bundles stay listed in the UI until you delete them — they consume disk on the build host but cost nothing else. Use the trash icon in the bundle row to remove both the row and the on-disk ISO.

Security Notes

  • Bundled packages are the same signed artifacts shipped to public package archives — .deb files from the bceverly Launchpad PPA carry the project GPG signature, .rpm files are signed via the COPR/OBS pipeline, .msi files are SignPath-signed, and .pkg files for macOS are notarized.
  • The ISO itself is not signed. Treat it like any other staged build artifact — verify integrity (SHA-256) between the build host and the air-gapped network through your established change-control process before installing.
  • The bundled installer makes no outbound network connections. Once installed, the SysManage server or agent connects only to the endpoints configured in /etc/sysmanage.yaml (or the agent's config file) — typically a SysManage server inside the same enclave.

Related Documentation

  • Repository Mirroring — once SysManage is installed inside the enclave via this bundle, the Pro+ mirroring engine can serve subsequent OS-package updates from a mirror host on the private network.
  • Child Host Management — agent bundles can be re-used to populate child hosts (LXD/KVM/bhyve guests) created by the parent inside the air-gapped enclave.
  • Server Installation — the standard internet-connected install procedure, for reference on what the air-gap bundle automates.