firmware #455
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: Setup Notification Environment | |
run: | | |
echo "TG_DISABLED=${{ github.event.inputs.tg_disabled || 'false' }}" >> $GITHUB_ENV | |
echo "tg_disabled=${{ github.event.inputs.tg_disabled || 'false' }}" >> $GITHUB_OUTPUT | |
- 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 | |
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: Setup Environment Variables | |
id: date | |
run: | | |
echo "WEEK_NUMBER=$(date +%U)" >> $GITHUB_ENV | |
echo "CURRENT_YEAR=$(date +%Y)" >> $GITHUB_ENV | |
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} | |
echo "TG_DISABLED=${{ github.event.inputs.tg_disabled || 'false' }}" >> $GITHUB_ENV | |
echo "TAG_NAME=$TAG_NAME-$(date +'%Y-%m-%d')" >> $GITHUB_ENV | |
- 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: Checkout source | |
uses: actions/checkout@v4 | |
with: | |
submodules: 'true' | |
ref: "master" | |
fetch-depth: "1" | |
- 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 workflow dummy debug firmware | |
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 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 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: Find built firmware images | |
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) | |
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_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 artifact | |
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 artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: thingino-${{ matrix.thingino-version }}-full-firmware | |
path: | | |
${{ env.FULL_FW }} | |
${{ 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})\nTime: ${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: Setup Environment | |
run: | | |
echo "TG_DISABLED=${{ github.event.inputs.tg_disabled || 'false' }}" >> $GITHUB_ENV | |
echo "TAG_NAME=$TAG_NAME-$(date +'%Y-%m-%d')" >> $GITHUB_ENV | |
- name: Checkout 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...HEAD \ | |
-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 elapsed 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 chat_id=${TG_CHANNEL} -F text="${TG_HEADER}" -F disable_web_page_preview=true) | |
echo Telegram response: ${HTTP} |