firmware #463
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: | |
TAG_NAME: firmware | |
TERM: linux | |
BR2_DL_DIR: ~/dl | |
TG_TOKEN: ${{secrets.TELEGRAM_TOKEN_BOT_THINGINO}} | |
TG_CHANNEL: -1002083893006_14394 | |
TG_TOPIC: 14394 | |
TG_CHANNEL_SCRATCH: ${{secrets.TELEGRAM_CHANNEL_THINGINO_SCRATCH}} | |
TG_OPTIONS: -s | |
FORCE_UNSAFE_CONFIGURE: 1 | |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
jobs: | |
notify-begin: | |
runs-on: ubuntu-latest | |
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 "America/Los_Angeles" | |
- 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 | |
- name: Setup Notification Channel | |
if: ${{ github.event.inputs.tg_scratch == 'true' }} | |
run: | | |
echo "TG_TOPIC=" >> $GITHUB_ENV | |
- 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 }} | |
fi | |
TG_MSG="Firmware build started:\nJob: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})\n\n" | |
TG_ICON="\xF0\x9F\x9A\xA6 GitHub Actions" | |
TG_HEADER=$(echo -e ${TG_MSG}${TG_ICON}) | |
HTTP=$(curl ${TG_OPTIONS} -H "Content-Type: multipart/form-data" -X POST https://api.telegram.org/bot${TG_TOKEN}/sendMessage -F parse_mode=MarkdownV2 -F message_thread_id=${TG_TOPIC} -F chat_id=${TG_CHANNEL} -F text="${TG_HEADER}" -F disable_web_page_preview=true) | |
echo Telegram response: ${HTTP} | |
generate-matrix: | |
runs-on: ubuntu-latest | |
outputs: | |
matrix: ${{ steps.set-matrix.outputs.matrix }} | |
steps: | |
- name: Set timezone | |
run: | | |
sudo timedatectl set-timezone "America/Los_Angeles" | |
- 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-latest | |
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: Update package manager sources | |
run: | | |
apt-get update | |
- name: Install build dependencies | |
run: | | |
apt-get install -y --no-install-recommends --no-install-suggests build-essential bc cmake cpio curl ca-certificates file git make gawk jq procps rsync tzdata u-boot-tools unzip wget | |
- name: Set timezone | |
run: | | |
ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime | |
echo "America/Los_Angeles" > /etc/timezone | |
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure -f noninteractive tzdata | |
- name: Setup gh workspace in 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 "GIT_HASH=${GIT_HASH}" >> ${GITHUB_ENV} | |
echo "GIT_BRANCH=${GIT_BRANCH}" >> ${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 }}-${{ github.run_id }} | |
restore-keys: | | |
${{ runner.os }}-ccache-${{ matrix.thingino-version }}-${{ env.CURRENT_YEAR }}-week-${{ env.WEEK_NUMBER }} | |
${{ runner.os }}-ccache-${{ matrix.thingino-version }}-${{ env.CURRENT_YEAR }}-week- | |
${{ runner.os }}-ccache-${{ matrix.thingino-version }}- | |
${{ runner.os }}-ccache- | |
${{ runner.os }}-ccache-shared-v1-${{ env.CURRENT_YEAR }}-week-${{ env.WEEK_NUMBER }} | |
- 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 }} | |
- 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 | |
- 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: Locate Built Firmware Artifacts | |
run: | | |
DYNAMIC_PART="${{ matrix.thingino-version }}" | |
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 "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 update firmware artifacts | |
uses: actions/upload-artifact@v4 | |
with: | |
name: thingino-${{ matrix.thingino-version }}-update-firmware | |
path: | | |
${{ env.UPDATE_FW }} | |
${{ env.UPDATE_FW_SHA }} | |
- name: Upload full firmware artifacts | |
uses: actions/upload-artifact@v4 | |
with: | |
name: thingino-${{ matrix.thingino-version }}-full-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 }} | |
- name: Upload full firmware to release | |
uses: softprops/action-gh-release@master | |
with: | |
tag_name: ${{ env.TAG_NAME }} | |
make_latest: false | |
draft: true | |
files: | | |
${{ env.FULL_FW }} | |
${{ env.FULL_FW_SHA }} | |
${{ env.UPDATE_FW_SHA }} | |
- name: Send firmware completion notifications with binaries | |
if: ${{ env.TG_DISABLED == 'false' && (env.FULL_FW) }} | |
run: | | |
if [[ "${{ github.event.inputs.tg_scratch }}" == 'true' ]]; then | |
TG_CHANNEL=${{ env.TG_CHANNEL_SCRATCH }} | |
fi | |
if [ -n "${{ env.FULL_FW }}" ]; then | |
ESCAPED_TAG_NAME=$(echo "${TAG_NAME}" | sed 's/-/\\-/g') | |
TG_MSG="Commit: [${GIT_HASH}](https://github.com/${GITHUB_REPOSITORY}/commit/${GIT_HASH})\nBranch: [${GIT_BRANCH}](https://github.com/${GITHUB_REPOSITORY}/tree/${GIT_BRANCH})\nTag: [${ESCAPED_TAG_NAME}](https://github.com/${GITHUB_REPOSITORY}/releases/tag/${ESCAPED_TAG_NAME})\nBuild Time: ${TIME}\nJob: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})\n\n" | |
TG_HEADER=$(echo -e "${TG_MSG}\xE2\x9C\x85 GitHub Actions") | |
HTTP_FULL=$(curl ${TG_OPTIONS} -H "Content-Type: multipart/form-data" -X POST https://api.telegram.org/bot${TG_TOKEN}/sendDocument -F parse_mode=MarkdownV2 -F message_thread_id=${TG_TOPIC} -F chat_id=${TG_CHANNEL} -F caption="${TG_HEADER}" -F document=@${FULL_FW} -F disable_web_page_preview=true) | |
echo "Telegram response Full Firmware: $HTTP_FULL" | |
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 }} | |
fi | |
TG_ESCAPED_VERSION=$(echo "${{ matrix.thingino-version }}" | sed 's/_/\\_/g') | |
TG_WARN="Error: ${TG_ESCAPED_VERSION}\n" | |
TG_MSG="Commit: [${GIT_HASH}](https://github.com/${GITHUB_REPOSITORY}/commit/${GIT_HASH})\nBranch: [${GIT_BRANCH}](https://github.com/${GITHUB_REPOSITORY}/tree/${GIT_BRANCH})\nTag: [${TAG_NAME}](https://github.com/${GITHUB_REPOSITORY}/releases/tag/${TAG_NAME})\nJob: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})\n\n" | |
TG_ICON="\xE2\x9A\xA0 GitHub Actions" | |
TG_HEADER=$(echo -e ${TG_WARN}${TG_MSG}${TG_ICON}) | |
HTTP=$(curl ${TG_OPTIONS} -H "Content-Type: multipart/form-data" -X POST https://api.telegram.org/bot${TG_TOKEN}/sendMessage -F parse_mode=MarkdownV2 -F message_thread_id=${TG_TOPIC} -F chat_id=${TG_CHANNEL} -F text="${TG_HEADER}" -F disable_web_page_preview=true) | |
echo Telegram response: ${HTTP} | |
notify-completion: | |
needs: [buildroot, notify-begin] | |
runs-on: ubuntu-latest | |
if: always() | |
steps: | |
- name: Set timezone | |
run: | | |
sudo timedatectl set-timezone "America/Los_Angeles" | |
- 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[] | "\(.sha[0:7]) \(.commit.message | gsub("\n"; " "))"') | |
RELEASE_NOTES="### Commits since last 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 | |
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..." | |
gh release edit ${{ env.TAG_NAME }} --latest --draft=false --notes-file "${{ env.RELEASE_NOTES_FILE }}" | |
echo "Release marked as latest" | |
else | |
echo "Release not found, skipping latest release update" | |
fi | |
- name: Send completion summary | |
if: ${{ env.TG_DISABLED == 'false' }} | |
run: | | |
if [[ "${{ github.event.inputs.tg_scratch }}" == 'true' ]]; then | |
TG_CHANNEL=${{ env.TG_CHANNEL_SCRATCH }} | |
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)) | |
TG_MSG="Firmware build completed:\nTotal build time: ${ELAPSED_MIN}m ${ELAPSED_SEC}s\nJob: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})\n\n" | |
TG_ICON="\xF0\x9F\x9A\xA9 GitHub Actions" | |
TG_HEADER=$(echo -e ${TG_MSG}${TG_ICON}) | |
HTTP=$(curl ${TG_OPTIONS} -H "Content-Type: multipart/form-data" -X POST https://api.telegram.org/bot${TG_TOKEN}/sendMessage -F parse_mode=MarkdownV2 -F message_thread_id=${TG_TOPIC} -F chat_id=${TG_CHANNEL} -F text="${TG_HEADER}" -F disable_web_page_preview=true) | |
echo Telegram response: ${HTTP} |