firmware #539
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: firmware | |
on: | |
schedule: | |
# 01:30 PST / Nightly | |
- cron: '30 8 * * *' | |
workflow_dispatch: | |
inputs: | |
tg_disabled: | |
type: boolean | |
description: 'Disable Telegram notifications' | |
required: false | |
default: false | |
tg_scratch: | |
type: boolean | |
description: 'Use TG scratch channel' | |
required: false | |
default: false | |
graph_enabled: | |
type: boolean | |
description: 'Enable build time graph' | |
required: false | |
default: false | |
teacup_only: | |
type: boolean | |
description: 'Only build Tea Cup profile' | |
required: false | |
default: false | |
debug_enabled: | |
type: boolean | |
description: 'Debug: Generate dummy image files' | |
required: false | |
default: false | |
env: | |
BR2_DL_DIR: ~/dl | |
FORCE_UNSAFE_CONFIGURE: 1 | |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
TAG_NAME: firmware | |
TERM: linux | |
TG_TOKEN: ${{secrets.TELEGRAM_TOKEN_BOT_THINGINO}} | |
TG_CHANNEL: ${{secrets.TELEGRAM_CHANNEL_THINGINO_MULTI}} | |
TG_TOPIC: ${{secrets.TELEGRAM_CHANNEL_THINGINO_MULTI_TOPIC_FIRMWARE}} | |
TG_CHANNEL_SCRATCH: ${{secrets.TELEGRAM_CHANNEL_THINGINO_SCRATCH}} | |
TZ: America/Los_Angeles | |
jobs: | |
notify-begin: | |
runs-on: ubuntu-24.04 | |
outputs: | |
start_time: ${{ steps.set_output.outputs.time }} | |
tg_disabled: ${{ steps.set_env.outputs.tg_disabled }} | |
steps: | |
- name: Set timezone | |
run: | | |
sudo timedatectl set-timezone ${{ env.TZ }} | |
- name: Save workflow start time to ENV | |
id: set_output | |
run: echo "time=$(date +%s)" >> $GITHUB_OUTPUT | |
- name: Configure Environment Variables | |
run: | | |
echo "TG_DISABLED=${{ github.event.inputs.tg_disabled || 'false' }}" >> $GITHUB_ENV | |
echo "tg_disabled=${{ github.event.inputs.tg_disabled || 'false' }}" >> $GITHUB_OUTPUT | |
TAG_NAME="$TAG_NAME-$(date +'%Y-%m-%d')" | |
echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
with: | |
ref: "master" | |
fetch-depth: "1" | |
- name: Create release tag | |
continue-on-error: true | |
run: | | |
TAG_NAME="${{ env.TAG_NAME }}" | |
if ! gh api repos/${{ github.repository }}/git/ref/tags/$TAG_NAME &>/dev/null; then | |
git tag "$TAG_NAME" | |
git push origin "$TAG_NAME" | |
else | |
echo "Tag $TAG_NAME already exists." | |
fi | |
- name: Create release | |
continue-on-error: true | |
run: | | |
TAG_NAME="${{ env.TAG_NAME }}" | |
if ! gh release view "$TAG_NAME" --repo "${{ github.repository }}" &>/dev/null; then | |
echo "Release $TAG_NAME does not exist. Creating pre-release." | |
gh release create "$TAG_NAME" --repo "${{ github.repository }}" --title "$TAG_NAME" --notes "pre-release: \`\`\`$TAG_NAME\`\`\` is currently being built, please wait..." --prerelease | |
else | |
echo "Release $TAG_NAME already exists." | |
fi | |
- name: Send build start notifcation via Telegram | |
if: env.TG_DISABLED == 'false' | |
run: | | |
if [[ "${{ github.event.inputs.tg_scratch }}" == 'true' ]]; then | |
TG_CHANNEL=${{ env.TG_CHANNEL_SCRATCH }} | |
export TG_TOPIC="" | |
fi | |
.github/scripts/tg-notify.sh -s $TG_TOKEN $TG_CHANNEL $TG_TOPIC start $TAG_NAME ${{ github.run_id }} ${{ github.repository }} | |
generate-matrix: | |
runs-on: ubuntu-24.04 | |
outputs: | |
matrix: ${{ steps.set-matrix.outputs.matrix }} | |
steps: | |
- name: Set timezone | |
run: | | |
sudo timedatectl set-timezone ${{ env.TZ }} | |
- name: Configure GH workspace | |
run: git config --global --add safe.directory "$GITHUB_WORKSPACE" | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
with: | |
ref: "master" | |
fetch-depth: "1" | |
- name: Generate device matrix | |
id: set-matrix | |
run: | | |
if [[ "${{ github.event.inputs.teacup_only }}" == 'true' ]]; then | |
CONFIGS=$(find configs/cameras/ -type f | sort | awk -F '/' '{print $(NF)}' | awk NF | grep 'teacup') | |
else | |
CONFIGS=$(find configs/cameras/ -type f | sort | awk -F '/' '{print $(NF)}' | awk NF) | |
fi | |
JSON_MATRIX="{\"thingino-version\": [" | |
for CONFIG in $CONFIGS; do | |
JSON_MATRIX+="\"${CONFIG}\"," | |
done | |
JSON_MATRIX="${JSON_MATRIX%,}]}" | |
echo "Matrix: $JSON_MATRIX" | |
echo "matrix=$JSON_MATRIX" >> $GITHUB_OUTPUT | |
buildroot: | |
name: ${{ matrix.thingino-version }} | |
needs: [generate-matrix, notify-begin] | |
runs-on: ubuntu-24.04 | |
outputs: | |
# Feed the hash of the commit we are building into the notify-completion release-notes step | |
GIT_HASH: ${{ steps.env.outputs.git_hash }} | |
TAG_NAME: ${{ steps.env.outputs.tag_name }} | |
defaults: | |
run: | |
shell: bash | |
container: | |
image: debian:bookworm | |
strategy: | |
fail-fast: false | |
matrix: ${{fromJson(needs.generate-matrix.outputs.matrix)}} | |
steps: | |
- name: Install GitHub CLI to container | |
run: | | |
apt-get update | |
apt-get install -y curl | |
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg | |
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null | |
- name: Update package manager sources | |
run: | | |
apt-get update | |
- name: Install build dependencies | |
run: | | |
apt-get install -y --no-install-recommends --no-install-suggests 7zip build-essential bc cmake cpio curl ca-certificates file git gh make gawk jq procps rsync tzdata u-boot-tools unzip wget | |
- name: Set timezone | |
run: | | |
ln -sf /usr/share/zoneinfo/${{ env.TZ }} /etc/localtime | |
echo ${{ env.TZ }} > /etc/timezone | |
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure -f noninteractive tzdata | |
- name: Setup gh workspace to container | |
run: git config --global --add safe.directory "$GITHUB_WORKSPACE" | |
- name: Checkout repository source | |
uses: actions/checkout@v4 | |
with: | |
submodules: 'true' | |
ref: "master" | |
fetch-depth: "1" | |
- name: Configure environment variables | |
id: env | |
run: | | |
echo "WEEK_NUMBER=$(date +%U)" >> $GITHUB_ENV | |
echo "CURRENT_YEAR=$(date +%Y)" >> $GITHUB_ENV | |
echo "GIT_HASH=$(git rev-parse --short ${GITHUB_SHA})" >> $GITHUB_ENV | |
echo "GIT_BRANCH=${GITHUB_REF_NAME}" >> $GITHUB_ENV | |
echo "TG_DISABLED=${{ github.event.inputs.tg_disabled || 'false' }}" >> $GITHUB_ENV | |
TAG_NAME="$TAG_NAME-$(date +'%Y-%m-%d')" | |
echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV | |
echo "GIT_HASH=$(git rev-parse --short ${GITHUB_SHA})" >> $GITHUB_OUTPUT | |
echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_OUTPUT | |
- name: Setup cache directories | |
run: | | |
mkdir -p ~/.ccache | |
mkdir -p ~/dl | |
- name: Restore build cache | |
uses: actions/cache@v4 | |
if: always() | |
with: | |
path: ~/.ccache | |
key: ${{ runner.os }}-ccache-${{ matrix.thingino-version }}-${{ env.CURRENT_YEAR }}-week-${{ env.WEEK_NUMBER }} | |
restore-keys: | | |
${{ runner.os }}-ccache-${{ matrix.thingino-version }}-${{ env.CURRENT_YEAR }}- | |
${{ runner.os }}-ccache-${{ matrix.thingino-version }}- | |
${{ runner.os }}-ccache-shared-v1-${{ env.CURRENT_YEAR }}-week-${{ env.WEEK_NUMBER }} | |
${{ runner.os }}-ccache-shared-v1-${{ env.CURRENT_YEAR }}- | |
${{ runner.os }}-ccache-shared-v1- | |
- name: Restore Buildroot DL cache | |
uses: actions/cache/restore@v4 | |
if: always() | |
with: | |
path: ~/dl | |
key: ${{ runner.os }}-dl-shared-v1-${{ env.CURRENT_YEAR }}-week-${{ env.WEEK_NUMBER }} | |
restore-keys: | | |
${{ runner.os }}-dl-shared-v1-${{ env.CURRENT_YEAR }}- | |
${{ runner.os }}-dl-shared-v1- | |
- name: Build firmware | |
if: ${{ github.event.inputs.debug_enabled != 'true' }} | |
run: | | |
BOARD=${{ matrix.thingino-version }} make fast | |
TIME=$(date -d @${SECONDS} +%M:%S) | |
echo "TIME=${TIME}" >> ${GITHUB_ENV} | |
- name: Generate debug dummy firmware for workflow testing | |
if: ${{ github.event.inputs.debug_enabled == 'true' }} | |
run: | | |
DYNAMIC_PART="${{ matrix.thingino-version }}" | |
mkdir -p ${HOME}/output/${DYNAMIC_PART}/images/ | |
echo "debug" > ${HOME}/output/${DYNAMIC_PART}/images/thingino-${DYNAMIC_PART}.bin | |
echo "debug uboot" > ${HOME}/output/${DYNAMIC_PART}/images/u-boot-lzo-with-spl.bin | |
echo "debug kernel" > ${HOME}/output/${DYNAMIC_PART}/images/uImage | |
echo "debug rootfs" > ${HOME}/output/${DYNAMIC_PART}/images/rootfs.squashfs | |
echo "debug rootfs" > ${HOME}/output/${DYNAMIC_PART}/images/rootfs.tar | |
echo "debug sha" > ${HOME}/output/${DYNAMIC_PART}/images/thingino-${DYNAMIC_PART}.bin.sha256sum | |
echo "debug" > ${HOME}/output/${DYNAMIC_PART}/images/thingino-${DYNAMIC_PART}-update.bin | |
echo "debug sha" > ${HOME}/output/${DYNAMIC_PART}/images/thingino-${DYNAMIC_PART}-update.bin.sha256sum | |
echo "TIME=7:77" >> ${GITHUB_ENV} | |
- name: Generate build time graphs | |
if: ${{ github.event.inputs.graph_enabled == 'true' }} | |
run: | | |
apt-get install -y --no-install-recommends --no-install-suggests python3-numpy python3-matplotlib | |
BOARD=${{ matrix.thingino-version }} make br-graph-build | |
- name: Validate compiled firmware artifacts | |
run: | | |
DYNAMIC_PART="${{ matrix.thingino-version }}" | |
FW_PATH=${HOME}/output/${DYNAMIC_PART}/images/ | |
FULL_FW=$(find ${HOME}/output/${DYNAMIC_PART}*/images/ -name "thingino-${DYNAMIC_PART}.bin" ! -name "*update.bin" | head -n 1) | |
FULL_FW_UBOOT=$(find ${HOME}/output/${DYNAMIC_PART}*/images/ -name "u-boot-lzo-with-spl.bin" | head -n 1) | |
FULL_FW_KERNEL=$(find ${HOME}/output/${DYNAMIC_PART}*/images/ -name "uImage" | head -n 1) | |
FULL_FW_ROOTFS=$(find ${HOME}/output/${DYNAMIC_PART}*/images/ -name "rootfs.squashfs" | head -n 1) | |
FULL_FW_ROOTFS_TAR=$(find ${HOME}/output/${DYNAMIC_PART}*/images/ -name "rootfs.tar" | head -n 1) | |
UPDATE_FW=$(find ${HOME}/output/${DYNAMIC_PART}*/images/ -name "thingino-${DYNAMIC_PART}-update.bin" | head -n 1) | |
echo "FULL_FW: $FULL_FW" | |
if [[ -n "$FULL_FW" ]]; then | |
echo "FW_PATH=${FW_PATH}" >> ${GITHUB_ENV} | |
echo "FULL_FW=${FULL_FW}" >> ${GITHUB_ENV} | |
echo "FULL_FW_UBOOT=${FULL_FW_UBOOT}" >> ${GITHUB_ENV} | |
echo "FULL_FW_KERNEL=${FULL_FW_KERNEL}" >> ${GITHUB_ENV} | |
echo "FULL_FW_ROOTFS=${FULL_FW_ROOTFS}" >> ${GITHUB_ENV} | |
echo "FULL_FW_ROOTFS_TAR=${FULL_FW_ROOTFS_TAR}" >> ${GITHUB_ENV} | |
echo "FULL_FW_SHA=${FULL_FW}.sha256sum" >> ${GITHUB_ENV} | |
echo "UPDATE_FW=${UPDATE_FW}" >> ${GITHUB_ENV} | |
echo "UPDATE_FW_SHA=${UPDATE_FW}.sha256sum" >> ${GITHUB_ENV} | |
else | |
echo "Matching .bin files not found." | |
exit 1 | |
fi | |
- name: Upload duration graph artifact | |
if: ${{ github.event.inputs.graph_enabled == 'true' }} | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ matrix.thingino-version }}-build.hist-duration | |
path: | | |
~/output/${{ matrix.thingino-version }}/graphs/build.hist-duration.pdf | |
- name: Upload compiled firmware artifacts | |
uses: actions/upload-artifact@v4 | |
with: | |
name: thingino-${{ matrix.thingino-version }}-firmware | |
path: | | |
${{ env.FULL_FW }} | |
${{ env.FULL_FW_UBOOT }} | |
${{ env.FULL_FW_KERNEL }} | |
${{ env.FULL_FW_ROOTFS }} | |
${{ env.FULL_FW_ROOTFS_TAR }} | |
${{ env.FULL_FW_SHA }} | |
${{ env.UPDATE_FW }} | |
${{ env.UPDATE_FW_SHA }} | |
- name: Post compiled firmware artifacts to release | |
uses: softprops/[email protected] | |
with: | |
tag_name: ${{ env.TAG_NAME }} | |
make_latest: false | |
prerelease: true | |
files: | | |
${{ env.FULL_FW }} | |
${{ env.FULL_FW_SHA }} | |
${{ env.UPDATE_FW_SHA }} | |
- name: Package binaries for notification | |
run: | | |
7zz a -t7z -mx=9 -mmt=on /tmp/thingino-${{ matrix.thingino-version }}.7z ${{ env.FULL_FW }} ${{ env.FULL_FW_SHA }} | |
- name: Send firmware completion notifications with attachment | |
if: ${{ env.TG_DISABLED == 'false' && (env.FULL_FW) }} | |
run: | | |
if [[ "${{ github.event.inputs.tg_scratch }}" == 'true' ]]; then | |
TG_CHANNEL=${{ env.TG_CHANNEL_SCRATCH }} | |
export TG_TOPIC="" | |
fi | |
if [ -n "${{ env.FULL_FW }}" ]; then | |
.github/scripts/tg-notify.sh -s $TG_TOKEN $TG_CHANNEL $TG_TOPIC completed $TAG_NAME ${{ github.run_id }} ${{ github.repository }} ${GIT_HASH} ${GIT_BRANCH} ${TAG_NAME} ${TIME} ${{ matrix.thingino-version }} /tmp/thingino-${{ matrix.thingino-version }}.7z | |
fi | |
- name: Send error notification | |
if: ${{ env.TG_DISABLED == 'false' && failure() }} | |
run: | | |
if [[ "${{ github.event.inputs.tg_scratch }}" == 'true' ]]; then | |
TG_CHANNEL=${{ env.TG_CHANNEL_SCRATCH }} | |
export TG_TOPIC="" | |
fi | |
.github/scripts/tg-notify.sh -s $TG_TOKEN $TG_CHANNEL $TG_TOPIC error $TAG_NAME "Github CI build failed!" ${{ github.run_id }} ${{ matrix.thingino-version }} ${{ github.repository }} | |
notify-completion: | |
needs: [buildroot, notify-begin] | |
runs-on: ubuntu-24.04 | |
if: always() | |
steps: | |
- name: Set timezone | |
run: | | |
sudo timedatectl set-timezone ${{ env.TZ }} | |
- name: Configure Environment | |
run: | | |
echo "TG_DISABLED=${{ github.event.inputs.tg_disabled || 'false' }}" >> $GITHUB_ENV | |
echo "GIT_HASH=${{ needs.buildroot.outputs.git_hash }}" >> $GITHUB_ENV | |
echo "TAG_NAME=${{ needs.buildroot.outputs.tag_name }}" >> $GITHUB_ENV | |
- name: Checkout repository source | |
uses: actions/checkout@v4 | |
with: | |
submodules: 'false' | |
ref: "master" | |
- name: Generate release notes | |
id: release_notes | |
run: | | |
LAST_RELEASE_TAG=$(gh release list --limit 2 --json tagName -q '.[1].tagName') | |
if [ -z "$LAST_RELEASE_TAG" ]; then | |
echo "No previous release found. Skipping release notes generation." | |
echo "RELEASE_NOTES_FILE=$(pwd)/release_notes.md" >> $GITHUB_ENV | |
exit 0 | |
else | |
echo "Latest release so far is $LAST_RELEASE_TAG" | |
fi | |
COMMITS=$(gh api \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
/repos/${{ github.repository }}/compare/$LAST_RELEASE_TAG...$GIT_HASH \ | |
-q '.commits | |
| sort_by(.commit.author.date) | |
| reverse | |
| .[] | |
| select( | |
.commit.message != "Merge remote-tracking branch '\''origin/master'\''" | |
and (.commit.message | contains("workflow") | not) | |
and (.commit.message | contains("Merge pull request #") | not) | |
) | |
| "\(.sha[0:7]) \(.commit.message | gsub("\n"; " "))"') | |
if [ -z "$COMMITS" ]; then | |
echo "### No changes, nightly rebuild" > release_notes.md | |
else | |
RELEASE_NOTES="### Changes in this release:\n" | |
while IFS= read -r commit; do | |
if [ -n "$commit" ]; then | |
RELEASE_NOTES="${RELEASE_NOTES}- ${commit}\n" | |
fi | |
done <<< "$COMMITS" | |
echo -e "$RELEASE_NOTES" > release_notes.md | |
fi | |
echo "RELEASE_NOTES_FILE=$(pwd)/release_notes.md" >> $GITHUB_ENV | |
- name: Get release ID and Mark as Latest | |
run: | | |
echo "Environment variables:" | |
echo "TAG_NAME: ${{ env.TAG_NAME }}" | |
echo "Checking GH authentication status..." | |
gh auth status | |
echo "Attempting to fetch release URL for tag ${TAG_NAME}..." | |
RELEASE_URL=$(gh release view ${{ env.TAG_NAME }} --json url -q ".url") | |
echo "RELEASE_URL: $RELEASE_URL" | |
if [[ -n "$RELEASE_URL" ]]; then | |
echo "Release URL found, attempting to mark as latest..." | |
if [[ -f "${{ env.RELEASE_NOTES_FILE }}" ]]; then | |
echo "Release notes file exists, including --notes-file..." | |
gh release edit ${{ env.TAG_NAME }} --latest --prerelease=false --notes-file "${{ env.RELEASE_NOTES_FILE }}" | |
else | |
echo "Release notes file does not exist, skipping --notes-file..." | |
gh release edit ${{ env.TAG_NAME }} --latest --prerelease=false --notes "" | |
fi | |
echo "Release marked as latest" | |
else | |
echo "Release not found, skipping latest release update" | |
fi | |
- name: Send notify completion summary | |
if: ${{ env.TG_DISABLED == 'false' }} | |
run: | | |
if [[ "${{ github.event.inputs.tg_scratch }}" == 'true' ]]; then | |
TG_CHANNEL=${{ env.TG_CHANNEL_SCRATCH }} | |
export TG_TOPIC="" | |
fi | |
START_TIME=${{ needs.notify-begin.outputs.start_time }} | |
END_TIME=$(date -u +%s) | |
ELAPSED=$((END_TIME - START_TIME)) | |
ELAPSED_MIN=$((ELAPSED / 60)) | |
ELAPSED_SEC=$((ELAPSED % 60)) | |
.github/scripts/tg-notify.sh -s $TG_TOKEN $TG_CHANNEL $TG_TOPIC finish ${{ github.workflow }} "${ELAPSED_MIN}m ${ELAPSED_SEC}s" ${{ github.run_id }} ${{ github.repository }} |