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.16 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; if there is none, it falls back to emulation; if GitHub does
not answer, it stops instead of guessing. codex-check-update reports which
engine is in use.
Since 0.147.0 the native build follows upstream: once a day a job builds the newest codex release for armv7 and publishes it here. Emulation covers the hours between an upstream release and its build, and any version that cannot be built for armv7 — up to 0.146.0 the CLI linked V8, which has no armv7 build. The whole story: docs/NATIVE-BUILD.md.
A build of upstream main can also be published as a snapshot prerelease.
It reports version 0.0.0 (upstream stamps versions only when it tags) and
install.sh never picks one up on its own — install it explicitly, the checksum
beside it is verified:
curl -fsSL https://raw.githubusercontent.com/Yumi-Lab/codex-cli-smartpi/main/install.sh | \
CODEX_NATIVE_TARBALL=<url of the snapshot .tar.gz> bash- 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"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 seccomp.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.
What still asks before running a command. Since codex 0.149 there is no
global "ask every time" setting any more (approval_policy = "untrusted", which
this installer used to write, now stops codex from starting — re-running
install.sh removes that line). With the sandbox off, codex runs commands
without asking except those it flags as dangerous. To be asked before every
command, mark the folder untrusted in the config (codex's own trust prompt only
offers "trust" or "quit"):
[projects."/home/pi/work"]
trust_level = "untrusted"On a board exposed to the network, run codex only in folders marked that way.
- 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. A network failure changes nothing: no fallback version, no unverified binary, no silent switch to the emulator — it stops and says so (GitHub is filtered in bursts from mainland China). - 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: ≈ 350 MB for the native payload (240 MB binary + 100 MB download),
≥ 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
and libgcc_s, present by default on Debian bookworm/trixie and DietPi.
Validated on a real Smart Pi One (DietPi / Debian 13 trixie):
| native armv7 — 0.162.1, 2026-10-12 | emulated aarch64 — 0.146.0, 2026-08-04 | |
|---|---|---|
| Update from the published release | 1346 s on a ~75 KB/s home line, checksum verified | — |
codex --version |
0.16 s | 2.40 s |
codex exec, no credentials |
reaches wss://api.openai.com, expected 401 |
same |
codex login --device-auth |
prints URL + one-time code | same |
| On disk | 240 MB | 269 MB + 35 MB emulator |
| Temperature | 36-37 °C | 42-46 °C |
Also green: the cross-build pipeline (~54 min on a runner, 100 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. From mainland China the install
does not complete today: measured from Hangzhou on 2026-10-12, github.com and
raw.githubusercontent.com dropped requests for minutes at a time and the release
CDN delivered 1.2 KB/s — the installer stops with an error instead of hanging. 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.