The preflight gate skipped scheduled runs whenever this repo's HEAD matched the SHA recorded in the previous nightly release body. That key ignores every upstream input the build actually depends on: OpenIPC/firmware HEAD, the toolchain release, the kernel tarball, the vendor osdrv packages, and individual buildroot package upstreams. builder.sh re-clones OpenIPC/firmware fresh on every run, so those inputs do move between nightlies even when this repo is quiet. The concrete failure mode this caused: on 2026-05-26 OpenIPC/firmware bumped its hisilicon-opensdk pin to b958db4, which contains the V4 mipi_rx vsync IRQ fix (openhisilicon PR #195, closes #194 — torn frames / 'Timeout from venc channel 0' on hi3516ev200, gk7205v200, gk7205v210, gk7205v300, hi3516ev300 hardware). The next builder nightly (2026-05-27 07:00 UTC) ran with this repo unchanged since 2026-05-25, so preflight skipped the entire Firmware matrix. Field cameras (verified: gk7205v210_lite_vixand-ivg-g4h at IP redacted, running BUILD_ID nightly-20260526-ee40da8, srcversion 8EF923DF907C1F7F3C6898E — the pre-fix open_mipi_rx fingerprint from PR #195's verification table) kept pulling the stale 'latest' release asset and kept showing the regression after sysupgrade. A cross-input dedupe key would be possible (firmware HEAD + toolchain URL hash + ...) but enumerates every upstream this build pulls. Simpler and correct: scheduled nightlies always build. workflow_dispatch is unaffected — that path was already unconditional. Co-authored-by: Dmitry Ilyin <zekccsripfwrocp-15689@yn.furvionx.com>
328 lines
12 KiB
YAML
328 lines
12 KiB
YAML
name: Build
|
|
on:
|
|
schedule:
|
|
- cron: '0 3 * * *'
|
|
workflow_dispatch:
|
|
inputs:
|
|
platform:
|
|
description: Platform
|
|
required: false
|
|
|
|
jobs:
|
|
preflight:
|
|
name: Preflight
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
should_build: ${{ steps.gate.outputs.should_build }}
|
|
head_sha: ${{ steps.gate.outputs.head_sha }}
|
|
short_sha: ${{ steps.gate.outputs.short_sha }}
|
|
build_id: ${{ steps.gate.outputs.build_id }}
|
|
built_at: ${{ steps.gate.outputs.built_at }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- id: gate
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
HEAD=$(git rev-parse HEAD)
|
|
SHORT=$(git rev-parse --short HEAD)
|
|
BUILD_ID="nightly-$(date -u +%Y%m%d)-${SHORT}"
|
|
BUILT_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
# Nightly rebuilds unconditionally on schedule. builder.sh re-clones
|
|
# OpenIPC/firmware at HEAD on every run, so the relevant inputs are
|
|
# mostly outside this repo (firmware HEAD, toolchain release, kernel
|
|
# tarball, vendor osdrv, individual package upstreams). The previous
|
|
# gate skipped when only this repo's HEAD matched the last published
|
|
# nightly, which made upstream fixes (e.g. an openhisilicon mipi_rx
|
|
# regression fix that lands in firmware) invisible to users until
|
|
# something here happened to change. Always build on schedule.
|
|
echo "Build: $BUILD_ID (event=${{ github.event_name }})"
|
|
echo "should_build=true" >> "$GITHUB_OUTPUT"
|
|
echo "head_sha=$HEAD" >> "$GITHUB_OUTPUT"
|
|
echo "short_sha=$SHORT" >> "$GITHUB_OUTPUT"
|
|
echo "build_id=$BUILD_ID" >> "$GITHUB_OUTPUT"
|
|
echo "built_at=$BUILT_AT" >> "$GITHUB_OUTPUT"
|
|
|
|
buildroot:
|
|
name: Firmware
|
|
needs: preflight
|
|
if: needs.preflight.outputs.should_build == 'true'
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
BUILD_ID: ${{ needs.preflight.outputs.build_id }}
|
|
BUILD_SHA: ${{ needs.preflight.outputs.head_sha }}
|
|
BUILD_PLATFORM: ${{ matrix.platform }}
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
platform:
|
|
- gk7202v300_lite_cootli_camv0103
|
|
- gk7202v300_lite_ipg-g3-wr
|
|
- gk7202v300_lite_xg521
|
|
- gk7205v200_lite_tiandy-tc-c321n
|
|
- gk7205v200_lite_vixand-ipc-1
|
|
- gk7205v200_lite_vixand-iph-5-4g
|
|
- gk7205v210_lite_tiandy-tc-c32qn
|
|
- gk7205v210_lite_vixand-ivg-g3s
|
|
- gk7205v210_lite_vixand-ivg-g4f-a
|
|
- gk7205v210_lite_vixand-ivg-g4f-a-w
|
|
- gk7205v210_lite_vixand-ivg-g4h
|
|
- gk7205v300_lite_vixand-ivg-g6s
|
|
- gk7205v300_lite_vixand-ivg-g6s-w
|
|
- hi3516cv200_lite_trassir-tr-d4121ir1-v2
|
|
- hi3516ev300_ultimate_rostelecom-ipc8232swc-we
|
|
- hi3516ev300_ultimate_rvi-1ncmw2028
|
|
- hi3518ev200_lite_smartwares-cip-37210
|
|
- hi3518ev200_lite_switcam-hs303
|
|
- hi3518ev200_lite_switcam-hs303-v2
|
|
- hi3518ev200_lite_vstarcam-c8892wip
|
|
- hi3518ev200_lite_qtech-qvc-ipc-136w
|
|
- hi3518ev300_lite_bathhouse
|
|
- hi3518ev300_lite_xiaomi-mjsxj02hl
|
|
- hi3518ev200_ultimate_lenovo-snowman-1080p
|
|
- ssc30kd_lite_cmcc-ds-ytj5301
|
|
- ssc325_lite_imilab-ec3-cmsxj25a
|
|
- ssc325_lite_imou-c22cp
|
|
- ssc325_lite_tp-link-tapo-c310-v1
|
|
- ssc325_lite_trassir-tr-w2c1-v1
|
|
- ssc325de_lite_imou-c22ep-s2
|
|
- ssc333_lite_meari-speed-6s
|
|
- ssc333_lite_tp-link-tapo-c110-v2
|
|
- ssc333_lite_tp-link-tapo-c110-v26
|
|
- ssc333_lite_babysense-see-hd-ip206
|
|
- ssc333_lite_vstarcam-c43s_b
|
|
- ssc335_lite_tp-link-tapo-c110-v1
|
|
- ssc335_lite_tp-link-tapo-c310-v220
|
|
- ssc335_lite_trassir-tr-w2c1-v2
|
|
- ssc335de_lite_dahua-hfw1230sp-v2
|
|
- ssc335de_lite_imou-c22e-s2-v2
|
|
- ssc335de_lite_uniview-c1l-2wn-g
|
|
- ssc337_lite_h3c-tc2101
|
|
- ssc337_lite_tiandy-tc-c321n-v2
|
|
- ssc337_lite_tp-link-tapo-c110-v1
|
|
- ssc337de_ultimate_foscam-x5
|
|
- ssc338q_fpv_caddx-fly
|
|
- ssc338q_fpv_emax-wyvern-link
|
|
- ssc338q_fpv_openipc-mario-aio
|
|
- ssc338q_fpv_openipc-thinker-aio
|
|
- ssc338q_fpv_openipc-urllc-aio
|
|
- ssc338q_fpv_runcam-wifilink
|
|
- ssc377qe_fpv_ccdcam-im50q01-tipoman
|
|
- t10_lite_hb-wifi-z6
|
|
- t10_lite_jvs-ingt10-gqs60ep
|
|
- t20_ultimate_azarton-c1
|
|
- t20_lite_ec37-t11
|
|
- t31_lite_vstarcam-cs55
|
|
- t21_lite_chinamobile-hdc-51-a5-v12
|
|
- t21_lite_smartwares-cip-37210at
|
|
- t21_lite_xyx-06s
|
|
- t21_lite_wansview-q5-1080p
|
|
- t23_lite_jooan-a6m-u
|
|
- t23_lite_jooan-q3r-u
|
|
- t23_lite_lsc-3215672
|
|
- t31_lite_aceline-aip-o4
|
|
- t31_lite_wansview-q5-2k
|
|
- t31_lite_aoni-ep01j05
|
|
- t31_lite_chinamobile-hdc-51-a6-v11
|
|
- t31_lite_cmcc-hdc-51-a6-v10
|
|
- t31_lite_chinatelecom-y4h-50
|
|
- t31_lite_wyze-v3b
|
|
- t31_lite_xiaomi-mjsxj03hl
|
|
- t31_lite_xiaomi-mjsxj03hl-jxq03
|
|
- t31_lite_tuya-gv7630-t31-ptz
|
|
- t31_ultimate_azarton-c1-t31x
|
|
- t31_ultimate_gcraftsman-gca50
|
|
- t31_lite_zte-k540
|
|
- t40_lite_movols-mo-805p
|
|
|
|
# APFPV
|
|
- ssc30kq_apfpv_greg-generic-bu-eu
|
|
- ssc30kq_apfpv_greg-generic-cu-eu
|
|
- ssc338q_apfpv
|
|
- ssc338q_apfpv_greg-generic-bu-eu
|
|
- ssc338q_apfpv_greg-generic-cu-eu
|
|
- ssc378qe_apfpv
|
|
|
|
# FPV
|
|
- hi3516ev200_fpv
|
|
- hi3516ev300_fpv
|
|
- hi3536dv100_fpv
|
|
- gk7205v200_fpv
|
|
- gk7205v210_fpv
|
|
- gk7205v300_fpv
|
|
- ssc30kq_fpv
|
|
- ssc338q_fpv
|
|
- ssc378qe_fpv
|
|
|
|
# Ruby
|
|
- ssc30kq_rubyfpv_generic
|
|
- ssc338q_rubyfpv_generic
|
|
- ssc338q_rubyfpv_thinker_internal_wifi
|
|
|
|
# LTE
|
|
- hi3516ev200_lte
|
|
- hi3516ev300_lte
|
|
- gk7205v200_lte
|
|
- gk7205v300_lte
|
|
|
|
# Venc
|
|
- gk7205v200_venc
|
|
- gk7205v210_venc
|
|
- gk7205v300_venc
|
|
|
|
# Mini
|
|
- hi3516cv300_mini
|
|
- hi3518ev200_mini
|
|
|
|
steps:
|
|
- name: Checkout source
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Prepare firmware
|
|
run: |
|
|
echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf
|
|
echo CACHE_DATE=$(date +%m) >> ${GITHUB_ENV}
|
|
INPUT=${{inputs.platform}}
|
|
MATRIX=$(echo ${{matrix.platform}} | cut -d_ -f1)
|
|
if [ -z ${INPUT} ] || [ ${INPUT} = ${MATRIX} ]; then
|
|
echo RUN=${MATRIX} >> ${GITHUB_ENV}
|
|
fi
|
|
|
|
- name: Setup ccache
|
|
if: env.RUN
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: /tmp/ccache
|
|
key: ${{matrix.platform}}-${{env.CACHE_DATE}}
|
|
|
|
# builder.sh does `rm -rf openipc` at the top of every run to force a
|
|
# clean re-clone of OpenIPC/firmware, which also nukes the default
|
|
# `openipc/output/dl/` directory Buildroot uses for downloaded source
|
|
# tarballs. Without a cache, every cron run re-downloads ~1-3 GB of
|
|
# tarballs from upstream mirrors — and any one of them randomly
|
|
# failing (e.g. cgit `snapshot/` URLs that regenerate tarballs per
|
|
# request with shifting sha256, like wireguard-tools on
|
|
# git.zx2c4.com) takes down the matrix entry for that board. See
|
|
# kaeru wireguard-zx2c4-tarball-regeneration-hash-drift for the
|
|
# specific incident this caching addresses.
|
|
#
|
|
# Cache to /tmp/builder-dl (outside the openipc/ tree so builder.sh's
|
|
# rm -rf doesn't touch it) and point Buildroot at it via BR2_DL_DIR
|
|
# env var (honored per Buildroot manual section "Location of source
|
|
# tarballs"). Mirrors OpenIPC/firmware/.github/workflows/build.yml's
|
|
# output/dl cache, adapted for builder's clean-clone behavior.
|
|
- name: Setup dl cache
|
|
if: env.RUN
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: /tmp/builder-dl
|
|
key: builder-dl-${{env.CACHE_DATE}}
|
|
restore-keys: builder-dl-
|
|
|
|
- name: Build firmware
|
|
if: env.RUN
|
|
run: |
|
|
export GIT_HASH=$(git rev-parse --short ${GITHUB_SHA})
|
|
export GIT_BRANCH=${GITHUB_REF_NAME}
|
|
echo GIT_HASH=${GIT_HASH} >> ${GITHUB_ENV}
|
|
echo GIT_BRANCH=${GIT_BRANCH} >> ${GITHUB_ENV}
|
|
|
|
mkdir -p /tmp/ccache
|
|
ln -s /tmp/ccache ${HOME}/.ccache
|
|
|
|
mkdir -p /tmp/builder-dl
|
|
export BR2_DL_DIR=/tmp/builder-dl
|
|
|
|
NAME=${{matrix.platform}}
|
|
# Retry budget for transient upstream toolchain/CDN flakes
|
|
# (matches OpenIPC/firmware/.github/workflows/build.yml).
|
|
backoffs="30 60 120 300 600 1200"
|
|
attempt=1
|
|
for sleep_for in $backoffs ""; do
|
|
bash builder.sh ${NAME} && break
|
|
if [ -z "$sleep_for" ]; then
|
|
echo "::error::build failed after ${attempt} attempts"
|
|
exit 1
|
|
fi
|
|
echo "::warning::attempt ${attempt} failed, retrying after ${sleep_for}s"
|
|
sleep "$sleep_for"
|
|
attempt=$((attempt + 1))
|
|
done
|
|
cd openipc
|
|
|
|
TIME=$(date -d @${SECONDS} +%M:%S)
|
|
echo TIME=${TIME} >> ${GITHUB_ENV}
|
|
COMMON=$(echo ${NAME} | awk -F_ '{print NF-1}')
|
|
|
|
NORFW=$(find output/images -name openipc*nor*)
|
|
if [ ! -z ${NORFW} ]; then
|
|
if [ ${COMMON} -eq 1 ]; then
|
|
echo NORFW=openipc/${NORFW} >> ${GITHUB_ENV}
|
|
else
|
|
mv ${NORFW} ../${NAME}-nor.tgz
|
|
echo NORFW=${NAME}-nor.tgz >> ${GITHUB_ENV}
|
|
fi
|
|
fi
|
|
|
|
NANDFW=$(find output/images -name openipc*nand*)
|
|
if [ ! -z ${NANDFW} ]; then
|
|
if [ ${COMMON} -eq 1 ]; then
|
|
echo NANDFW=openipc/${NANDFW} >> ${GITHUB_ENV}
|
|
else
|
|
mv ${NANDFW} ../${NAME}-nand.tgz
|
|
echo NANDFW=${NAME}-nand.tgz >> ${GITHUB_ENV}
|
|
fi
|
|
fi
|
|
|
|
if [ -z ${NORFW} ] && [ -z ${NANDFW} ]; then
|
|
exit 1
|
|
fi
|
|
|
|
- name: Upload firmware (dated)
|
|
if: env.NORFW || env.NANDFW
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: ${{ needs.preflight.outputs.build_id }}
|
|
prerelease: true
|
|
body: |
|
|
sha=${{ needs.preflight.outputs.head_sha }}
|
|
short=${{ needs.preflight.outputs.short_sha }}
|
|
built_at=${{ needs.preflight.outputs.built_at }}
|
|
files: |
|
|
${{env.NORFW}}
|
|
${{env.NANDFW}}
|
|
|
|
- name: Upload firmware (rolling nightly)
|
|
if: env.NORFW || env.NANDFW
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: nightly
|
|
body: |
|
|
sha=${{ needs.preflight.outputs.head_sha }}
|
|
short=${{ needs.preflight.outputs.short_sha }}
|
|
built_at=${{ needs.preflight.outputs.built_at }}
|
|
files: |
|
|
${{env.NORFW}}
|
|
${{env.NANDFW}}
|
|
|
|
- name: Upload firmware (latest — legacy alias)
|
|
if: env.NORFW || env.NANDFW
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: latest
|
|
files: |
|
|
${{env.NORFW}}
|
|
${{env.NANDFW}}
|
|
|
|
- name: Send binary
|
|
if: env.NORFW
|
|
run: |
|
|
TG_MSG="Build: ${BUILD_ID}\nCommit: ${GIT_HASH}\nBranch: ${GIT_BRANCH}\nTime: ${TIME}\n\n"
|
|
TG_ICON="\xE2\x9C\x85 GitHub Actions"
|
|
TG_HEADER=$(echo -e ${TG_MSG}${TG_ICON})
|
|
TG_TOKEN=${{secrets.TELEGRAM_TOKEN_BOT_OPENIPC}}
|
|
TG_CHANNEL=${{secrets.TELEGRAM_CHANNEL_OPENIPC_DEV}}
|
|
HTTP=$(curl -s -o /dev/null -w %{http_code} https://api.telegram.org/bot${TG_TOKEN}/sendDocument -F chat_id=${TG_CHANNEL} -F caption="${TG_HEADER}" -F document=@${NORFW})
|
|
echo Telegram response: ${HTTP}
|