The official OpenAI Codex CLI on Allwinner H3 / armv7l (Smart Pi One, Yumi SmartPad) — hardware the official installer rejects, because OpenAI ships codex for x86_64 and aarch64 only.
Two ways to get it running on the board, and the installer picks for you:
- native — this project cross-compiles the Apache-2.0 source for armv7 and
publishes the binary. No emulator.
codex --versionin 0.084 s on the pad. - emulated — the official aarch64 binary under 64-on-32 qemu, available the day upstream ships. Same command, 2.40 s.
Sign in with a ChatGPT Plus/Pro/Business account (device code, no browser on the board) or an API key. One command installs everything, and the same command is the updater.
curl -fsSL https://raw.githubusercontent.com/Yumi-Lab/codex-cli-smartpi/main/install.sh | bashThen sign in — the board never needs a browser:
codex login --device-auth # open the URL on any machine, type the code
printenv OPENAI_API_KEY | codex login --with-api-key # or use an API keyCODEX_VERSION=x.y.z pins a version, CODEX_FORCE=1 reinstalls anyway.
| Command | Purpose |
|---|---|
codex |
Interactive TUI |
codex exec "task" |
One-shot, non-interactive (scripts, the gateway, cron) |
codex exec --dangerously-bypass-approvals-and-sandbox "task" |
Unattended: no prompt, no sandbox. The only flavour that runs without a human here — do not use --full-auto, it asks for a sandbox this kernel cannot give (see below). Run it in a directory you control. |
codex-check-update |
OTA probe: {"cli","installed","engine","latest","update_available"} |
codex-slot status [--json] |
How many agents are running, how many are queued, how much memory is left |
codex-bin … |
The wrapper without the dispatcher's co-tenancy warning |
Environment knobs, no reinstall needed:
| Variable | Effect |
|---|---|
CODEX_ENGINE=native|emulated|auto |
Which payload to install (default auto), and which one to run when both are present |
CODEX_CPUS=0,1 |
Thermal throttle — default is all 4 cores, both engines |
CODEX_QEMU=7.2 |
Fall back to the vendored qemu 7.2 engine (emulated only) |
CODEX_TB_SIZE=256 |
Translation-cache size in MiB (default 128, fork engine only) |
CODEX_KEEP_EMULATION=1 |
Install the emulated payload next to the native one |
CODEX_NATIVE_TARBALL=<path|url> |
Install a native build from a snapshot release, or one you built yourself |
CODEX_MAX_PARALLEL=10 |
Hard ceiling on concurrent agents — extra ones queue (0 disables the limiter) |
CODEX_MIN_FREE_MB=180 |
Memory floor: no new agent starts below it, whatever the ceiling says |
CODEX_SLOT_TIMEOUT=0 |
Give up after n seconds of queuing instead of waiting forever (exit 75) |
codex update or the upstream installer on this board — both
would drop a 64-bit binary outside the wrapper. Re-run install.sh.
install.sh asks the releases of this repo whether a native armv7 build exists
for the version it resolved. If it does, it installs that and downloads no
emulator at all. Otherwise it falls back to emulation. codex-check-update
reports which one is in use.
Whether a codex version can be built for armv7 comes down to one thing: whether
the CLI links V8, which has no armv7 build. At the current release it does,
so the board runs emulated today — and upstream has already moved v8 out of the
CLI's dependency graph on main, so the next release will be built and published
automatically. The whole story: docs/NATIVE-BUILD.md.
Meanwhile the newest native binary is published as a snapshot prerelease
built from main. Install it explicitly — one minute end to end on the board:
curl -fsSL https://raw.githubusercontent.com/Yumi-Lab/codex-cli-smartpi/main/install.sh | \
CODEX_NATIVE_TARBALL=<url of the snapshot .tar.gz> bashThe URL is on the releases page
and the checksum beside it is verified during the install. A snapshot reports
version 0.0.0 (upstream stamps versions only when it tags) and install.sh
never picks one up on its own.
- The official binary is static Rust (musl, aarch64), 269 MB — the shape that emulates well in user mode.
- QEMU dropped "64-bit guest on a 32-bit host" in version 10, so two engines are
installed: the Yumi qemu fork 9.2.4
(default — restores correct single-copy-atomic 64-bit accesses on Cortex-A7,
which a tokio-based Rust app needs, plus a sizeable translation cache) and
qemu-aarch64-static 7.2 from Debian bookworm (vendored in
vendor/, reachable withCODEX_QEMU=7.2). - A wrapper runs it at low priority on all 4 cores. Watch thermals on long
agentic runs: this SoC has reached ~102 °C under a sustained 4-core emulated
load and frozen —
CODEX_CPUS=0,1throttles it.earlyoomis installed as the memory safety net, and the rule on 1 GB is one heavy CLI at a time. - Optional, root only: an
aarch64binfmt_misc handler pointing at the fork engine, so any aarch64 helper codex re-execs is loaded instead of failing withENOEXEC(echo -1 > /proc/sys/fs/binfmt_misc/qemu-aarch64-yumiremoves it).
A batch — the gateway, a cron, a loop — could otherwise start ten agents at once,
and on 1 GB with SD-card swap memory exhaustion freezes the board before the OOM
killer reacts. Every launch therefore goes through codex-slot: a hard ceiling
of 10 concurrent agents (extra ones wait, they do not fail) plus a memory
floor — no new agent starts while MemAvailable is under 180 MB. The count
alone would be theatre; the floor is what makes it honest on a board that may
only fit a handful of real turns.
There is no daemon: slots are flock locks held by the codex process itself.
Nothing to start, nothing to crash, nothing to leak — a lock dies with its
process, and a limiter that cannot write its slot directory steps aside rather
than breaking the CLI.
Measured on the board, 12 agents launched simultaneously: peak exactly 10
running with 2 queued, MemAvailable never below 662 MB, zero swap,
44 °C, queue drained by itself. With the floor raised above what is free, jobs
queue and then exit 75 (try again later) instead of taking the board down.
~/.codex/config.toml is created, only if you have none, with:
sandbox_mode = "danger-full-access"
approval_policy = "untrusted"This is a real trade-off, not a shortcut. Codex's Linux sandbox cannot work
on this board: the seccomp filter it builds exists for x86_64 and aarch64 only
(upstream's landlock.rs
ends in unimplemented!() for anything else), and bwrap and the re-exec'd
codex-linux-sandbox are 64-bit binaries. A native armv7 build hits the same
wall. Approvals are therefore the safety net — codex asks before running a
command. Never leave both the sandbox off and approvals off on a board exposed
to the network.
- Check:
codex-check-updateprints one JSON line — the probe the Yumi AI Gateway console polls for its update badge.--latest/--installed/--engineprint a single value. - Update: re-run
install.sh— that IS the updater. It exits fast when already newest, verifies the published sha256, and keeps the previous binary until the new one is in place. - Automatic upstream tracking: a daily GitHub Actions cron compares upstream against the releases here and builds + publishes the armv7 binary when a new version lands. Nothing to bump anywhere; a version that cannot be built for armv7 is reported as a skip, not a failure.
- Privileges: root/sudo for the first install only. Updates run as any user
that owns
/opt/codex, so the gateway service user updates without sudo.
armv7l board, Debian/Armbian bookworm or newer, ≥ 1 GB RAM, curl and tar.
Disk: ≈ 300 MB for the native payload (211 MB binary, no emulator),
≥ 700 MB for the emulated one (269 MB binary + 105 MB download + the previous
version during an update) — the installer checks before touching anything. The
native build is dynamically linked and needs libssl.so.3, libcrypto.so.3,
libz and libgcc_s, present by default on Debian bookworm/trixie and DietPi.
Validated on a real Smart Pi One (DietPi / Debian 13 trixie, 2026-08-04):
| native armv7 | emulated aarch64 | |
|---|---|---|
codex --version |
0.084 s | 2.40 s |
codex exec, no credentials |
reaches api.openai.com over TLS + WebSockets, expected 401 |
same |
codex login --device-auth |
prints URL + one-time code | same |
| On disk | 211 MB | 269 MB + 35 MB emulator |
| Temperature | 41-46 °C | 42-46 °C |
Also green: the cross-build pipeline (~47 min on a runner, 85 MB tarball), the daily watcher, and CI on every push — unit tests, a staged install on x86_64, and a full install plus emulated smoke test inside a real armv7l userland.
Not yet exercised on the board: the TUI over a long session, an authenticated
agent turn, thermals under sustained load. test/pad-smoke.sh user@pad collects
the measurable part in one pass.
Full reasoning, measurements and dead ends: docs/METHODOLOGY.md.
No board needed — .github/workflows/ci.yml runs all
of this on every push:
test/unit.sh # offline: metadata parsing, OTA probe, no hardcoded paths
test/install-armv7-docker.sh # full install + emulated smoke in a real armv7l userland
CODEX_DRY_RUN=1 bash install.sh # resolve everything, write nothing
CODEX_OPT=/tmp/opt CODEX_BINDIR=/tmp/bin … # relocate the install, no root required
CODEX_ALLOW_ANY_ARCH=1 CODEX_SMOKE=0 … # stage the payload on a non-armv7l host, skip the run(The installer refuses a 64-bit host without CODEX_ALLOW_ANY_ARCH — that guard
has its own CI job.)
The native binary is built by build/cross-armv7.sh,
reproducible anywhere with Docker:
docker run --rm -v "$PWD:/repo" -w /repo rust:bookworm bash build/cross-armv7.sh- claude-code-smartpi — official Anthropic Claude Code, native.
- grok-cli-smartpi — official xAI Grok CLI, same 64-on-32 emulation path.
- kimi-cli-smartpi / kimi-code-smartpi — Moonshot CLIs, native.
- vibe-cli-smartpi — official Mistral Vibe CLI, native.
- qemu-64on32-smartpi — the qemu fork the emulated CLIs run on.
- Scripts in this repo: MIT (Yumi Lab)
vendor/qemu-aarch64-static: GPL-2.0, extracted as-is from the Debian bookworm package (vendor/README.md)- Codex itself is Apache-2.0 (openai/codex). The official binary is downloaded from OpenAI at install time, not redistributed here; the native binary is built from that same public source.