This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
OpenIPC builder is a thin Buildroot overlay layer for building OpenIPC IP-camera
firmware for specific, named consumer devices. It contains only the per-device deltas
(a defconfig, a first-boot customizer, a rootfs exclude list, occasional sensor/board
files). Everything common — the actual Buildroot tree, packages, kernels, toolchains — lives
in OpenIPC/firmware, which builder.sh clones fresh
on every run. This repo is never built in isolation; it is layered on top of a firmware
checkout.
./builder.sh # interactive whiptail menu of all devices
./builder.sh <device> # build one device non-interactively<device> is the directory name under devices/ (minus the _defconfig suffix on its
config), e.g. hi3518ev200_lite_switcam-hs303. It is passed straight through as
make BOARD=<device>. There is no separate test/lint suite — "passing" means the firmware
image builds and (ideally) boots on hardware.
What builder.sh <device> does, in order:
git pull(self-update the builder repo).rm -rf openipcthen cloneOpenIPC/firmware— HEAD by default, or the ref in$OPENIPC_FW_REVif set (used for cross-repo bisects; see build-one.yml).copy_extra_packages— copypackage/*intoopenipc/general/package/and append asource "...Config.in"line for each into the external tree'sConfig.in.- Copy
devices/<device>/*over the firmware tree (defconfig, overlay, excludes, board). make BOARD=<device>then best-effortmake BOARD=<device> size-report.copy_to_archive→archive/<device>/<timestamp>/. Forhi3518ev200_liteit also runsautoup_rootfsto wrap the images asautoupdate-*.imgviamkimage.
openipc/, archive/, cache/, output/ are all gitignored build artifacts.
repack.sh [uboot] [firmware] [ssid] [pass]— does not build. Downloads a prebuilt release.tgz+ u-boot from GitHub releases, optionally bakes in WiFi creds viafw_setenv, anddds a flashable NOR image. Needssquashfs-tools.package.sh [pkg]— force a full rebuild of one Buildroot package inside an existingopenipc/tree (dirclean+rebuild; defaults tobusybox).
Device directory names encode <soc>_<flavor>_<vendor>-<model>[-<version>]:
- soc — OpenIPC SoC name:
hi3518ev200,ssc337de,t31,gk7205v200, … - flavor — firmware track:
lite(default, the vast majority),ultimate,fpv,rubyfpv,apfpv. Preferlitefor new devices unless flash size forces otherwise.
The minimal required files for a registered device (per README "Requirements"):
devices/<device>/br-ext-chip-<vendor>/configs/<device>_defconfig
devices/<device>/general/overlay/usr/share/openipc/customizer.sh
devices/<device>/general/scripts/excludes/<soc>_<flavor>.list
br-ext-chip-<vendor>/— the chip-vendor folder mirrors firmware'sBR2_EXTERNALlayout. One of:br-ext-chip-hisilicon,br-ext-chip-sigmastar,br-ext-chip-goke,br-ext-chip-ingenic(Ingenic = the T-series SoCs). The defconfig inside selects toolchain/kernel/SoC drivers and majestic/webui packages; it references$(OPENIPC_*)and$(EXTERNAL_VENDOR)macros resolved by the firmware Makefile, not by this repo.customizer.sh— runs on first boot. Applies device-specific runtime config:fw_setenv(upgrade URL,wlandev,ptzprofile, WiFi) andcli -s .<path> <value>to seed majestic's config (sensor ini, IR-cut/backlight GPIO pins, codec, fps).<soc>_<flavor>.list— paths to delete from the rootfs (unused sensor.so/.ini, unused WiFi.ko, etc.) so the image fits NOR flash. This is the main lever for the "doesn't fit in 8M NOR" problem.
Optional per-device files: extra general/overlay/... payload (a sensor libsns_*.so, a
sensor .ini, a patched load_hisilicon), or a custom kernel config at
br-ext-chip-<vendor>/board/<family>/<soc>.generic.config.
Generic, non-device-specific defconfigs (*_fpv, *_venc, *_lte, *_mini) plus their
shared kernel configs and exclude lists. These are the matrix entries with a single
underscore (hi3516ev200_fpv, hi3518ev200_mini). The CI artifact-naming logic keys off
this: COMMON = (underscore count) - 1; when COMMON == 1 the firmware's canonically-named
image is uploaded as-is, otherwise the compound <soc>_<flavor>_<vendor>-<model> image is
renamed to <device>-nor.tgz / -nand.tgz to avoid release-asset collisions.
The fastest correct path is to clone the closest existing device — same SoC and same flavor — and edit the deltas. Identify the target's SoC, image sensor, WiFi chip, and flash size first (the README device table lists all of these for existing boards).
- Copy a sibling.
cp -r devices/<soc>_<flavor>_<other> devices/<soc>_<flavor>_<vendor>-<model>. The new directory name is theBOARD/<device>token; keep it<soc>_<flavor>_<vendor>-<model>[-<version>], lowercase, hyphen-separated vendor/model. - Rename + edit the defconfig. It lives at
br-ext-chip-<vendor>/configs/<device>_defconfigand its filename must match the new directory name exactly (this is whatmake BOARD=looks up). Inside, the lines you typically change:- WiFi driver — the
BR2_PACKAGE_<chip>_OPENIPC=yline (e.g.RTL8188FU,RTL8189FS,ATBM…). Pick the one matching the device's WiFi module. BR2_OPENIPC_FLASH_SIZE="8"/"16"to match the NOR/NAND size.- Leave
BR2_OPENIPC_SOC_*,BR2_OPENIPC_VARIANT, the toolchain/kernel block, and the*_OSDRV_*sensor-driver package alone unless the SoC/flavor actually differs.
- WiFi driver — the
- Edit
general/overlay/usr/share/openipc/customizer.sh(runs on first boot):fw_setenv upgrade '…/releases/download/latest/<device>-nor.tgz'— the filename must equal<device>-nor.tgz, because that is exactly what CI renames the artifact to (see theCOMMONlogic above). Getting this wrong breaks self-update.fw_setenv wlandev <driver-profile>; optionalfw_setenv ptz <profile>.cli -s .<path> <value>to seed majestic — IR-cut / backlight / light-sensor GPIO pins, codec, fps. Only set.isp.sensorConfig /etc/sensors/<sensor>.iniwhen the sensor needs a non-default config (most boards don't — it's the exception, e.g. MIPI sensors).
- Trim the exclude list. Keep the filename
<soc>_<flavor>.list(it is named after soc+flavor, not the full device name — do not rename it to the device). Remove from the list the sensor.so/.iniand WiFi.kothat this board actually uses, so they survive into the rootfs; everything else listed gets stripped to fit flash. Sibling devices on the same<soc>_<flavor>often share an identical list — diff against them. - Optional payload. Add files under
general/overlay/…only when required (alibsns_<sensor>.sonot provided by the SoC osdrv, a patchedload_hisilicon, a sensor.ini), or a custom kernel config atbr-ext-chip-<vendor>/board/<family>/<soc>.generic.config. Keep the file count minimal — anything reusable belongs upstream inOpenIPC/firmware. - Register for nightly CI. Add
- <device>to the matrix in.github/workflows/master.ymlunder the matching group (SoC/APFPV/FPV/Ruby/etc.). This matrix is the only build registry — a device not listed here is never built or released. - Document it. Add a row to the device table in
README.md(and the clones table if it's a rebrand of an existing board). - Build & verify locally.
./builder.sh <device>, then checkarchive/<device>/<timestamp>/for the image and confirm it fits the flash size. Usepackage.sh <pkg>to iterate on a single package without a full re-clone.
Standard Buildroot packages (Config.in + <name>.mk + src/ and/or files/) that aren't
(yet) upstream in firmware. copy_extra_packages in builder.sh injects them at build time.
Examples: kc110-board-support (a device-specific C PTZ/IR binary + init scripts, installed
via SITE_METHOD = local), demo-openipc. Read the package Config.in help text — e.g.
kc110-board-support documents the device's exact GPIO/pinmux map.
- master.yml ("Build") — nightly cron (03:00 UTC) + manual dispatch. Builds the full
device matrix; it always rebuilds (the input that actually changes is firmware HEAD /
toolchain / kernel, all outside this repo, so there is no skip-gate). Caches ccache and
Buildroot's
BR2_DL_DIRat/tmp/builder-dl(outsideopenipc/becausebuilder.shrm -rfs that tree each run). Uploads each image to three release tags: datednightly-YYYYMMDD-<sha>, rollingnightly, and legacylatest; pushes the NOR build to Telegram. - build-one.yml — dispatch one platform at a chosen builder
commitand/or firmwarefirmware_ref. Built forgit bisect runacross either repo; publishes tonightly-bisect-*. - manifest.yml — after Build finishes (success or partial failure), regenerates
manifest.json+manifest.flaton thegh-pagesbranch via.github/scripts/enrich_manifest.py. - cleanup.yml — weekly prune of dated
nightly-*releases beyond the newest 90.
The monthly builder-dl cache can freeze packages pinned to a moving ref (VERSION = HEAD /
branch, or the majestic-webui dist asset) because they download under a constant
<pkg>-<ref>.tar.gz filename. Both build workflows have a "Refresh moving-ref package
downloads" step that deletes *-HEAD/master/main/dist.tar.gz from the cache so Buildroot
re-fetches them each run. If you add a package pinned to a branch, make sure its tarball name
matches that glob or it will go stale silently.
Device-specific build/runtime gotchas, recovery procedures, and hardware quirks are recorded
in the kaeru builder initiative — query it before debugging a board (e.g. the
"overlay wipe removes ssh authorized_keys", "busybox ip has no br flag", and
generic→builder migration notes already there). Put new technical findings in kaeru, not here.