Download or Build¶
Download AryaOS¶
Official release images are published as GitHub Releases on snstac/aryaos. Each successful automated image build on main creates an annotated tag (v<UTC-datetime>-<short-sha>) and attaches the compressed image (.img.xz) to that release.
You can also fetch recent images from GitHub Actions:
- Open the repository Actions tab and select Pi-gen image.
- Open a completed run (push to
mainor workflow_dispatch). - Download the workflow artifact (this workflow uses 30-day artifact retention; artifacts accrue toward Actions storage quotas).
For flash instructions, see Installing AryaOS.
Manual build checklist (local Raspberry Pi image)¶
Use this checklist when building an arm64 Raspberry Pi OS–based image on your own machine. Automation-focused notes live in AGENTS.md at the repo root.
Prerequisites¶
- Git and plenty of disk (pi-gen
work/+ deploy output can reach hundreds of gigabytes). - Docker if you use
make build-docker(recommended for unattended builds). - Passwordless or ready sudo if you use
make build/./build.sh. - Repo layout:
Makefile,config(native pi-gen),config.docker(Docker pi-gen bind-mount at/aryaos),shared_files/,stages/.
Steps¶
- Clone this repository.
make pi-gen— clones upstream pi-genarm64into./pi-gen/(gitignored).- Choose one build path:
- Docker (preferred):
make build-docker— runs./pi-gen/build-docker.sh -c <repo>/config.docker, repo mounted read-only at/aryaos. Outputs land under.aryaos-pigen-deploy/at the repo root (and may also appear underpi-gen/deploy/). - Native:
make build— runssudo ./build.sh, which invokes pi-gen withconfig(expects./pi-gen). - After a successful base exists, use
make skip/make unskipto skip or restore pi-genstage0–stage2while iterating on AryaOS-only stages (see Makefile). - Optional apt cache for Docker builds:
make apt-cacher-up, thenARYAOS_APT_CACHE=1 make build-docker(see Makefile targetsapt-cacher-*; compose filedocker-compose.apt-cacher.ymlat repo root). - Optional logged Docker build:
./scripts/agent-build-docker.shmirrors output tobuild-YYYYMMDD-HHMMSS.log. - Lab vs release builds: by default images carry no lab access and expire the default
pipassword at first login. For a lab image (aryaos-dev-lab SSH key + passwordless sudo forpi, no password expiry) build withARYAOS_LAB_ACCESS=1 make build-docker(orsudo ARYAOS_LAB_ACCESS=1 ./build.shnative). Seeshared_files/aryaos/ssh/README.md. - Verify a built image:
sudo scripts/verify-image.sh [--lab] <image>.img|.img.xz|.ziploop-mounts the image and asserts key files, packages, units — and that release images ship no lab access. CI runs this on everymainbuild before publishing a release. - Build the AryaOS overlay package only:
make package-overlaywritesdeploy/debs/aryaos-overlay_<version>_all.deb, containing the portal, Cockpit proxy integration, TAK data package/enrollment helpers, first-boot identity scripts, gpsd defaults, dispatcher hooks, and related systemd/lighttpd configuration. - Lightweight validation without an image:
make ansible-syntax(requires Ansible /ansible-galaxyper Makefile).
Cleanup / retry¶
make build-docker-cleanremoves.aryaos-pigen-work/,.aryaos-pigen-deploy/, and stray pi-gen Docker containers.make clean/make distcleanwiden the wipe (see Makefile).
CI vs local¶
Pull requests run Ansible syntax checks and path sanity checks only; full images build on ubuntu-24.04-arm when merging to main or via workflow_dispatch. See .github/workflows/pi-gen.yml.
Build AryaOS (Raspberry Pi image)¶
The build environment for AryaOS is based on pi-gen. The build produces a Raspberry Pi OS image, compatible with Raspberry Pi Imager.
Any SD card larger than 16 GB should suffice to get started; 32 GB is recommended.
The AryaOS build procedure is inspired by @deltazero's kiosk.pi.
Configuration lives in config at the repo root (not inside the pi-gen/ clone). Key settings include STAGE_LIST, RELEASE, and SHARED_FILES.
Image build steps (detail)¶
To build initially:
- Clone this repository.
- Run
make pi-gento clone the upstreamarm64branch of pi-gen into./pi-gen/(ignored by git). - Run
make buildto create an image (usessudoon the host for pi-gen).
Docker alternative (no host sudo for debootstrap): make build-docker runs ./pi-gen/build-docker.sh with the repo mounted at /aryaos and config.docker. It bind-mounts .aryaos-pigen-work and .aryaos-pigen-deploy at the repo root (writable even if pi-gen/ is root-owned) so retries reuse finished stages: docker rm -f pigen_work then make build-docker again. NUM_CORES defaults to nproc for faster package installs. make build-docker-clean deletes those caches (large) plus stray containers. Images end up in .aryaos-pigen-deploy/; the script also unpacks into pi-gen/deploy/.
To iterate on AryaOS-only stages without rebuilding the base OS:
- Sync the repo.
- Run
make skipto addSKIPmarkers forstage0–stage2. - Run
make buildagain.
The make build step invokes sudo and may prompt for a password.
To refresh the embedded pi-gen clone after make pi-gen, run cd pi-gen && git pull origin arm64 so your toolchain stays aligned with upstream pi-gen.
CI, artifacts, and GitHub Releases¶
The workflow .github/workflows/pi-gen.yml:
- Pull requests — Runs Ansible collection install,
ansible-playbook --syntax-check, checks that key paths (config,manifests/aryaos-sensor-packages.yml, custom stages) exist, validates that exactly one stage carriesEXPORT_IMAGEand is last in everySTAGE_LIST, and HEAD-checks pinned download URLs (catches upstream release renames before they break a 6-hourmainbuild). No image is built (GitHub-hostedubuntu-latest). - Push to
mainorworkflow_dispatch— Builds a dev image by default (ARYAOS_LAB_ACCESS=1: aryaos-dev-lab SSH key + passwordless sudo forpi, no first-boot password expiry) taggedv<ts>-<sha>-devand published as a prerelease — grab it from Releases, burn, and test. For a release image (no lab access, password expiry enforced) run the workflow manually with thereleaseinput checked; it tags without the-devsuffix as a normal release.verify-image.shchecks whichever flavor was built (--labfor dev). Builds the full pi-gen image on GitHub’s hostedubuntu-24.04-armrunner (native aarch64) via usimd/pi-gen-action (compression: xz). No QEMU/binfmt_miscemulation step — debootstrap/chroot run natively. Beforepi-gen-action, the workflow frees disk (dotnet, Android NDK, hosted toolcache, etc.) because default runner images are tight on space. After a successful build, the workflow uploads the image as a workflow artifact (30-day retention), then runsscripts/verify-image.sh(loop-mounts the image; asserts key files/packages/units and that no lab access ships) — a verification failure keeps the artifact but blocks the release. On success it creates an annotated tagv<UTC-datetime>-<12-char-sha>, pushes it, and publishes a GitHub Release with the image attached (generate_release_notes). Builds for the same repo are serialized (concurrency, no cancel-in-progress).
Hosted arm64 notes
- Public repos can use
ubuntu-24.04-armat no extra charge for standard Actions minutes (subject to GitHub policies/limits). usimd/pi-gen-actionmounts stage directories into the pi-gen Docker container, but not the whole repo. This workflow passesdocker-optssoshared_files/,manifests/, and the checkoutpi-gen-src/appear at the same${{ github.workspace }}paths inside the container (needed for../../../shared_files,REPO_ROOT/manifests, andstage-patchedits underpi-gen-src).- If disk pressure persists on hosted runners, consider enabling
increase-runner-disk-sizeonpi-gen-actionor trimming stages; pi-gen work/ trees are large.
Manual v* tags (optional)¶
The workflow no longer triggers on push of version tags (that avoided rebuilding when the workflow itself pushes a release tag). Use workflow_dispatch in the Actions UI to rebuild from main, or adjust the workflow on: section if you want tag-triggered builds again.
Hosting limits¶
- GitHub Releases: each asset must be under 2 GiB. If your
.img.xzis larger, use a stronger xz level in the workflow (trades CPU/time), split the file (split -b 1900M your.img.xz chunk_) and upload the parts as separate assets with reassembly instructions, or store the blob in S3 (or similar) and link it from the release notes. - Actions artifacts: convenient for testing builds from
mainor manual runs; they count against Actions storage quotas and expire (this workflow uses 30 days).
Faster local builds (beefy machine)¶
- Prefer upstream
pi-gen/build-docker.shwith bind mounts or named volumes forwork/anddeploy/so repeated runs reuse downloads where pi-gen allows. - Apt cache (Docker builds): run
make apt-cacher-uponce (startsapt-cacher-ngviadocker-compose.apt-cacher.ymlat the repo root; cache lives in the Docker volumearyaos_apt_cacher_cache). Then build withARYAOS_APT_CACHE=1 make build-docker. The Makefile passesAPT_PROXYinto the pi-gen container (Acquire::http::Proxy, same as upstream pi-gen) usinghost.docker.internal+ Docker’shost-gateway. Check the cache withmake apt-cacher-ping; tail logs withmake apt-cacher-logs. Stop withmake apt-cacher-down(volume is kept until you remove it withdocker volume rm). - Native
make build: setAPT_PROXYinconfig(commented example) to your proxy URL, e.g.http://127.0.0.1:3142whenapt-cacher-ngpublishes that port on the host. - Export
NUM_CORESto match your CPU so pi-gen can parallelize package operations. - Use
make skip/make unskipwhen iterating only on AryaOS stages after a full base image exists.
Off-GitHub build (e.g. AWS on a budget)¶
When Actions hit timeouts, disk limits, or the 2 GiB release cap:
- Launch Ubuntu 22.04/24.04 on EC2 Spot (e.g. compute-optimized 4xlarge class) with a large gp3 volume (256–512 GB) for Docker and pi-gen work dirs.
- Install Docker, clone this repo,
make pi-gen, thensudo ./build.shor runpi-gen’s Docker build script from thepi-gentree. - Copy the artifact from
pi-gen/deploy/to S3 (aws s3 cp ...; add a lifecycle rule to expire old objects). Optionally front it with CloudFront if download traffic grows. - For GitHub distribution, either upload a file smaller than 2 GiB with
gh release uploadusing a token on the instance, or publish an S3 HTTPS URL /latest.jsonpointer in the release notes.
Deploy with Ansible (existing host)¶
The same stages/stage-* trees are usable as Ansible roles. Root ansible.cfg sets roles_path to ./stages.
Install collections once:
ansible-galaxy collection install -r requirements.yml
Example: full stack on an inventory host (default aryaos_profile: rpi in vars.yml):
ansible-playbook -i inventory.yml site.yml
Example: sensor stack on a generic Debian/Ubuntu host (e.g. Dragon-style image) without Pi appliance extras (portal, comitup, etc.):
ansible-playbook -i inventory.yml -e 'aryaos_profile=generic' site.yml
Hardware-specific roles (ADS-B, AIS) are tagged hardware; skip them if the host has no relevant SDR:
ansible-playbook -i inventory.yml -e 'aryaos_profile=generic' site.yml --skip-tags hardware
The PyTAK sensor stack installs from the signed snstac apt repository (built by snstac/packages from each product's latest GitHub Release). The package list is defined once in manifests/aryaos-sensor-packages.yml for both image builds and Ansible; the repo's trust anchor (snstac.gpg + snstac.sources) is vendored at shared_files/aryaos/snstac-packages/. dhbridge installs from the same repo (public since v0.3.2); shared_files/dhbridge/ carries only its config payload.
AryaOS-owned appliance files are also buildable as a local Debian package with make package-overlay. During pi-gen, stage-aryaos/00-install builds that package into the target rootfs and installs it with apt so the running image records the overlay as aryaos-overlay in dpkg. The legacy direct-copy path remains in the stage for now as a fallback while the overlay package is being hardened.
Check playbook syntax¶
make ansible-syntax
Developer loops (faster iteration without an SD card)¶
When you need something close to AryaOS (e.g. Cockpit + cockpit-adsbcot + adsbcot) but do not want to wait for a full pi-gen image, flash, and reboot cycle:
Incremental image builds (reminder)¶
Reuse .aryaos-pigen-work / .aryaos-pigen-deploy, make skip after the base exists, ARYAOS_APT_CACHE=1, and NUM_CORES — see AGENTS.md and the Manual build checklist above. Avoid make build-docker-clean unless you need a cold rebuild.
Ansible against a running Debian/arm64 host¶
site.yml drives the same stages/stage-* trees as roles. Point Ansible at a cloud arm64 VM, QEMU/UTM guest, or spare Pi already running Debian-tier OS, then install only what you need via tags:
ansible-galaxy collection install -r requirements.yml
ansible-playbook -i inventory.yml site.yml \
--limit dev_arm64_example \
-e 'aryaos_profile=generic' \
--tags base,pytak,adsbcot \
--skip-tags hardware
- Add
aryaos(or other tags fromsite.yml) when you need portal/lighttpd/Cockpit proxy behaviour (e.g.UrlRoot=/admin). stage-adsbcotmay assume Pi-oriented.debartifacts (e.g. vendored arm64 readsb) or hardware; an amd64 laptop often needs different packages or vars — prefer arm64 for parity with shipped images.- readsb SDR backends: pi-gen rebuilds readsb with RTL-SDR, SoapySDR (Airspy), and HackRF via
shared_files/adsbcot/readsb-install.sh;04-adsbcot/01-run-chroot.shfails the stage ifreadsb --helplacks any of those. See config.md.
See commented dev_arm64 stubs in inventory.yml.
AirTAK-style readsb + adsbcot on DragonOS (dragonball)¶
For a generic amd64 host (e.g. Ubuntu DragonOS) with SSH as gba and key ~/.ssh/id_ed25519_nopass, use the thin playbook playbooks/readsb-adsbcot-generic.yml (pytak sensor .debs, Charontak CoT hub, + stage-adsbcot readsb build). Host vars: host_vars/dragonball.yml.
ansible-galaxy collection install -r requirements.yml
ansible -i inventory.yml dragonball -m ping -e ansible_become=false
# gba must have sudo (passwordless, or use -K / playbooks/dragonball-secret.yml)
ansible-playbook -i inventory.yml playbooks/readsb-adsbcot-generic.yml \
--limit dragonball \
-e 'adsb_sdr_sn=YOUR_RTL_SERIAL'
dragonball defaults in host_vars/dragonball.yml use an Airspy (1d50:60a1) via readsb_device_type: soapysdr and readsb_soapy_device: driver=airspy. RTL dongles: set readsb_device_type: rtlsdr and adsb_sdr_sn from rtl_test (must differ from uat_sdr_sn).
If sudo prompts for a password, copy playbooks/dragonball-secret.yml.example to gitignored playbooks/dragonball-secret.yml and pass -e @playbooks/dragonball-secret.yml, or run --ask-become-pass.
Without sudo (e.g. gba in the docker group only): rsync the repo to the host, then run scripts/dragonball-deploy-readsb-adsbcot.sh on the machine. It builds readsb with SoapySDR/Airspy in Docker, installs units/config on the host rootfs, and enables services via nsenter (no password).
On a running host: scripts/readsb-use-airspy.sh or scripts/readsb-use-rtl-serial.sh. Probe Airspy with SoapySDRUtil --probe="driver=airspy".
After deploy: systemctl status charontak readsb adsbcot, confirm /run/adsb/aircraft.json existss.
Loop-mount / nspawn / chroot (advanced)¶
You can loop-mount a built .img, then systemd-nspawn or chroot into the rootfs for package inspection or quick commands. This is not a full firmware/kernel/network test and may behave differently than hardware.
QEMU system arm64¶
Boot a generic Debian arm64 cloud image or your produced image under QEMU / UTM for a closer integration test without writing an SD card. Expect lower performance than bare metal.
cockpit-adsbcot UI-only iteration¶
Upstream cockpit-adsbcot documents make devel-install, which symlinks the built bundle into ~/.local/share/cockpit/ on a machine that already runs Cockpit — fastest for frontend changes. It does not reproduce AryaOS lighttpd termination or UrlRoot=/admin by itself; validate that stack via Ansible or a real image.