mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-09-21 02:26:15 -04:00
606 lines
28 KiB
YAML
606 lines
28 KiB
YAML
name: CI
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
push:
|
|
branches:
|
|
- dev
|
|
- master
|
|
paths-ignore:
|
|
- "docs/**"
|
|
|
|
# only run the latest commit to avoid cache overwrites
|
|
concurrency:
|
|
group: ${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
PYTHON_VERSION: 3.11
|
|
|
|
jobs:
|
|
amd64_build:
|
|
runs-on: ubuntu-22.04
|
|
name: AMD64 Build
|
|
steps:
|
|
- name: Check out code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up QEMU and Buildx
|
|
id: setup
|
|
uses: ./.github/actions/setup
|
|
with:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
- name: Build and push amd64 standard build
|
|
uses: docker/build-push-action@v7
|
|
with:
|
|
context: .
|
|
file: docker/main/Dockerfile
|
|
push: true
|
|
platforms: linux/amd64
|
|
target: frigate
|
|
tags: ${{ steps.setup.outputs.image-name }}-amd64
|
|
cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64
|
|
cache-to: type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64,mode=max
|
|
smoke_test:
|
|
runs-on: ubuntu-22.04
|
|
name: AMD64 Smoke Test
|
|
needs:
|
|
- amd64_build
|
|
steps:
|
|
- name: Check out code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up QEMU and Buildx
|
|
id: setup
|
|
uses: ./.github/actions/setup
|
|
with:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
- name: Start container
|
|
run: |
|
|
mkdir -p /tmp/frigate-config /tmp/frigate-media
|
|
printf 'mqtt:\n enabled: false\ncameras: {}\n' > /tmp/frigate-config/config.yml
|
|
# simulate a root-era install: root-owned 0600 jwt secret pre-exists
|
|
docker run --rm -v /tmp/frigate-config:/config --entrypoint bash \
|
|
${{ steps.setup.outputs.image-name }}-amd64 \
|
|
-c "python3 -c 'import secrets; open(\"/config/.jwt_secret\",\"w\").write(secrets.token_hex(64))' && chmod 600 /config/.jwt_secret && chown 0:0 /config/.jwt_secret"
|
|
docker run -d --name frigate --shm-size 256m \
|
|
-v /tmp/frigate-config:/config \
|
|
-v /tmp/frigate-media:/media/frigate \
|
|
--mount type=tmpfs,target=/tmp/cache,tmpfs-size=100000000 \
|
|
-p 5000:5000 -p 8971:8971 \
|
|
${{ steps.setup.outputs.image-name }}-amd64
|
|
- name: Wait for API
|
|
run: |
|
|
for i in $(seq 1 60); do
|
|
curl -fs http://127.0.0.1:5000/api/version && exit 0
|
|
sleep 5
|
|
done
|
|
echo "API never came up"; docker logs frigate; exit 1
|
|
- name: Assert security headers and permissions
|
|
run: |
|
|
headers=$(curl -ksI https://127.0.0.1:8971/)
|
|
echo "$headers"
|
|
echo "$headers" | grep -qi "x-content-type-options: nosniff"
|
|
echo "$headers" | grep -qi "referrer-policy: strict-origin-when-cross-origin"
|
|
# server_tokens off: Server header must not include a version.
|
|
# written as an if rather than "! grep", because bash exempts a
|
|
# negated command from set -e and the assertion would never fail
|
|
if echo "$headers" | grep -qiE "^server: nginx/[0-9]"; then
|
|
echo "Server header leaks the nginx version; server_tokens is not off"
|
|
exit 1
|
|
fi
|
|
# Frigate never ships frame-ancestors: HA's Webpage card and iframe
|
|
# panels frame it cross-origin and it would break them silently
|
|
if echo "$headers" | grep -qi "frame-ancestors"; then
|
|
echo "response carries frame-ancestors, which breaks cross-origin iframe embedding"
|
|
exit 1
|
|
fi
|
|
# -t as root would chown the live cache and temp dirs to the `user`
|
|
# directive user; stdout discarded because -t reopens the config's
|
|
# /dev/stdout logs and the docker exec pipe is root-owned
|
|
docker exec frigate /command/s6-setuidgid frigate bash -c '/usr/local/nginx/sbin/nginx -e stderr -t -c /tmp/nginx/conf/nginx.conf >/dev/null'
|
|
docker exec frigate stat -c %a /config/tls/privkey.pem | grep -qx 600
|
|
docker exec frigate stat -c %a /dev/shm/go2rtc.yaml | grep -qx 640
|
|
- name: Assert services run as non-root
|
|
run: |
|
|
ps_out=$(docker exec frigate ps -eo user=,comm=)
|
|
echo "$ps_out"
|
|
assert_nonroot() {
|
|
# the process must exist AND no instance of it may run as root
|
|
echo "$ps_out" | grep -qw "$1" || { echo "$1 is not running"; exit 1; }
|
|
if echo "$ps_out" | grep -w "$1" | grep -q '^root'; then
|
|
echo "$1 is running as root"; exit 1
|
|
fi
|
|
}
|
|
assert_nonroot python3
|
|
assert_nonroot go2rtc
|
|
assert_nonroot nginx
|
|
# root-era jwt secret must have been captured by the sweep and the
|
|
# auth stack must be functional: wrong creds => clean 401, not 500
|
|
docker exec frigate stat -c %u /config/.jwt_secret | grep -qx "$(docker exec frigate id -u frigate)"
|
|
code=$(curl -s -o /dev/null -w '%{http_code}' -X POST http://127.0.0.1:5000/api/login \
|
|
-H 'content-type: application/json' -d '{"user":"admin","password":"definitely-wrong"}')
|
|
[ "$code" = "401" ] || { echo "login endpoint returned $code"; exit 1; }
|
|
# a root nginx -t above would have chowned the runtime dirs to root
|
|
owners=$(docker exec frigate stat -c %U /tmp/nginx /dev/shm/nginx_cache)
|
|
echo "$owners"
|
|
if echo "$owners" | grep -qvx frigate; then
|
|
echo "nginx runtime dirs are not owned by frigate"; exit 1
|
|
fi
|
|
# runtime user can write recordings storage
|
|
docker exec frigate /command/s6-setuidgid frigate touch /media/frigate/.write-probe
|
|
docker exec frigate rm /media/frigate/.write-probe
|
|
# tmpfs mount per the docs: arrives root-owned, holds the ZMQ IPC sockets
|
|
docker exec frigate /command/s6-setuidgid frigate touch /tmp/cache/.write-probe
|
|
docker exec frigate rm /tmp/cache/.write-probe
|
|
# models are baked in as root and archive members can carry root-only modes
|
|
docker exec frigate /command/s6-setuidgid frigate sh -c '
|
|
for f in /cpu_model.tflite /edgetpu_model.tflite /cpu_audio_model.tflite \
|
|
/labelmap.txt /audio-labelmap.txt /openvino-model/*; do
|
|
[ -e "$f" ] || continue
|
|
test -r "$f" || { echo "$f is not readable by the runtime user"; exit 1; }
|
|
done'
|
|
- name: Assert device access grants
|
|
run: |
|
|
# a fake accelerator node created after boot, then the oneshot re-run.
|
|
# /command is on PATH only for s6-supervised services, and the
|
|
# with-contenv shebang resolves its execline helpers through PATH
|
|
docker exec frigate mknod /dev/apex_9 c 120 99
|
|
docker exec frigate sh -c 'export PATH=/command:$PATH; exec /etc/s6-overlay/s6-rc.d/init-devices/run'
|
|
acl=$(docker exec frigate getfacl -p /dev/apex_9)
|
|
echo "$acl"
|
|
echo "$acl" | grep -q "user:frigate:rw-"
|
|
echo "$acl" | grep -q "user:go2rtc:rw-"
|
|
# the usb tree gets recursive grants plus a default ACL that
|
|
# newly created nodes inherit (the Coral re-enumeration path)
|
|
docker exec frigate sh -c 'mkdir -p /dev/bus/usb/001 && mknod /dev/bus/usb/001/002 c 189 1'
|
|
docker exec frigate sh -c 'export PATH=/command:$PATH; exec /etc/s6-overlay/s6-rc.d/init-devices/run'
|
|
docker exec frigate getfacl -p /dev/bus/usb/001 | grep -q "user:frigate:rwx"
|
|
docker exec frigate sh -c 'mknod /dev/bus/usb/001/099 c 189 98 && chmod 664 /dev/bus/usb/001/099'
|
|
inherited=$(docker exec frigate getfacl -p /dev/bus/usb/001/099)
|
|
echo "$inherited"
|
|
echo "$inherited" | grep -q "user:frigate:rw-"
|
|
# getfacl prints granted perms even when the mask clamps them to
|
|
# nothing, with a trailing "#effective:" comment; a clamped ACL must
|
|
# fail this assertion, not sneak past it. The check is scoped to the
|
|
# runtime users because the inherited group:: entry is always clamped
|
|
# on a non-directory, so an unscoped grep could never pass.
|
|
if echo "$inherited" | grep -E "^user:(frigate|go2rtc):" | grep -q "effective"; then
|
|
echo "inherited ACL is mask-clamped and grants no real access"; exit 1
|
|
fi
|
|
# hardware that is absent must stay silent: the literal table entries
|
|
# are not globs, so nullglob does not drop them and only an existence
|
|
# check keeps them from warning on every boot
|
|
out=$(docker exec frigate sh -c 'export PATH=/command:$PATH; exec /etc/s6-overlay/s6-rc.d/init-devices/run')
|
|
echo "$out"
|
|
if echo "$out" | grep -q "WARN"; then
|
|
echo "grant warned about device nodes that do not exist"; exit 1
|
|
fi
|
|
- name: Assert escape hatch restores root
|
|
run: |
|
|
mkdir -p /tmp/frigate-config-root
|
|
printf 'mqtt:\n enabled: false\ncameras: {}\n' > /tmp/frigate-config-root/config.yml
|
|
# pre-seed so the absence check proves the rm -f, not a vacuous pass
|
|
echo "2:1000:1000" > /tmp/frigate-config-root/.permissions_version
|
|
docker run -d --name frigate-root --shm-size 256m \
|
|
-e FRIGATE_RUN_AS_ROOT=true \
|
|
-v /tmp/frigate-config-root:/config \
|
|
${{ steps.setup.outputs.image-name }}-amd64
|
|
up=0
|
|
for i in $(seq 1 60); do
|
|
docker exec frigate-root curl -fs http://127.0.0.1:5000/api/version && up=1 && break
|
|
sleep 5
|
|
done
|
|
if [ "$up" -ne 1 ]; then echo "escape hatch container never healthy"; docker logs frigate-root; exit 1; fi
|
|
ps_out=$(docker exec frigate-root ps -eo user=,comm=)
|
|
echo "$ps_out"
|
|
echo "$ps_out" | grep -w python3 | grep -q '^root'
|
|
echo "$ps_out" | grep -w go2rtc | grep -q '^root'
|
|
echo "$ps_out" | grep -w nginx | grep -q '^root'
|
|
# an if, not ! test: bash exempts negated commands from set -e
|
|
if docker exec frigate-root test -f /config/.permissions_version; then
|
|
echo "escape hatch did not delete the sweep sentinel"; exit 1
|
|
fi
|
|
docker rm -f frigate-root
|
|
- name: Assert granular root services
|
|
run: |
|
|
mkdir -p /tmp/frigate-config-granular /tmp/frigate-media-granular
|
|
printf 'mqtt:\n enabled: false\ncameras: {}\n' > /tmp/frigate-config-granular/config.yml
|
|
docker run -d --name frigate-granular --shm-size 256m \
|
|
-e FRIGATE_ROOT_SERVICES=frigate \
|
|
-v /tmp/frigate-config-granular:/config \
|
|
-v /tmp/frigate-media-granular:/media/frigate \
|
|
${{ steps.setup.outputs.image-name }}-amd64
|
|
up=0
|
|
for i in $(seq 1 60); do
|
|
docker exec frigate-granular curl -fs http://127.0.0.1:5000/api/version && up=1 && break
|
|
sleep 5
|
|
done
|
|
if [ "$up" -ne 1 ]; then echo "granular container never became healthy"; docker logs frigate-granular; exit 1; fi
|
|
ps_out=$(docker exec frigate-granular ps -eo user=,comm=)
|
|
echo "$ps_out"
|
|
# the listed service runs as root
|
|
echo "$ps_out" | grep -w python3 | grep -q '^root'
|
|
# unlisted services still drop; ifs because set -e exempts negated commands
|
|
if echo "$ps_out" | grep -w go2rtc | grep -q '^root'; then
|
|
echo "go2rtc is unexpectedly running as root"; exit 1
|
|
fi
|
|
if echo "$ps_out" | grep -w nginx | grep -q '^root'; then
|
|
echo "nginx is unexpectedly running as root"; exit 1
|
|
fi
|
|
# the sweep still ran and the sentinel records the mode
|
|
docker exec frigate-granular cat /config/.permissions_version | grep -qx "2:1000:1000:frigate"
|
|
# the root frigate process chowns the db it creates (first-boot immediacy)
|
|
docker exec frigate-granular stat -c %u /config/frigate.db | grep -qx 1000
|
|
# plant a root-owned straggler; the per-boot sweep must reclaim it on restart
|
|
docker exec frigate-granular sh -c 'mkdir -p /media/frigate/clips && touch /media/frigate/clips/straggler.webp'
|
|
docker restart frigate-granular
|
|
up=0
|
|
for i in $(seq 1 60); do
|
|
docker exec frigate-granular curl -fs http://127.0.0.1:5000/api/version && up=1 && break
|
|
sleep 5
|
|
done
|
|
if [ "$up" -ne 1 ]; then echo "granular container never came back after restart"; docker logs frigate-granular; exit 1; fi
|
|
docker exec frigate-granular stat -c %u /media/frigate/clips/straggler.webp | grep -qx 1000
|
|
docker rm -f frigate-granular
|
|
- name: Assert unknown root service fails fast
|
|
run: |
|
|
mkdir -p /tmp/frigate-config-badsvc
|
|
printf 'mqtt:\n enabled: false\ncameras: {}\n' > /tmp/frigate-config-badsvc/config.yml
|
|
docker run -d --name frigate-badsvc --shm-size 256m \
|
|
-e FRIGATE_ROOT_SERVICES=frigatee \
|
|
-v /tmp/frigate-config-badsvc:/config \
|
|
${{ steps.setup.outputs.image-name }}-amd64
|
|
found=0
|
|
for i in $(seq 1 12); do
|
|
if docker logs frigate-badsvc 2>&1 | grep -q "unknown service 'frigatee'"; then found=1; break; fi
|
|
sleep 5
|
|
done
|
|
if [ "$found" -ne 1 ]; then
|
|
echo "no fail-fast error for an unknown service name"; docker logs frigate-badsvc; exit 1
|
|
fi
|
|
# the failed oneshot blocks startup through the dependency chain
|
|
if docker exec frigate-badsvc curl -fs http://127.0.0.1:5000/api/version; then
|
|
echo "container came up despite an invalid FRIGATE_ROOT_SERVICES"; exit 1
|
|
fi
|
|
docker rm -f frigate-badsvc
|
|
- name: Assert PUID/PGID remapping
|
|
run: |
|
|
mkdir -p /tmp/frigate-config-puid /tmp/frigate-media-puid
|
|
printf 'mqtt:\n enabled: false\ncameras: {}\n' > /tmp/frigate-config-puid/config.yml
|
|
docker run -d --name frigate-puid --shm-size 256m \
|
|
-e PUID=1500 -e PGID=1500 \
|
|
-v /tmp/frigate-config-puid:/config \
|
|
-v /tmp/frigate-media-puid:/media/frigate \
|
|
${{ steps.setup.outputs.image-name }}-amd64
|
|
up=0
|
|
for i in $(seq 1 60); do
|
|
docker exec frigate-puid curl -fs http://127.0.0.1:5000/api/version && up=1 && break
|
|
sleep 5
|
|
done
|
|
if [ "$up" -ne 1 ]; then echo "PUID container never became healthy"; docker logs frigate-puid; exit 1; fi
|
|
docker exec frigate-puid id -u frigate | grep -qx 1500
|
|
docker exec frigate-puid id -g frigate | grep -qx 1500
|
|
docker exec frigate-puid cat /config/.permissions_version | grep -qx "2:1500:1500"
|
|
# second boot must skip the sweep (sentinel hit). Poll rather than
|
|
# sleep: the string can only come from the second boot (the first
|
|
# had no sentinel), so grepping the full log is unambiguous.
|
|
docker restart frigate-puid
|
|
ok=0
|
|
for i in $(seq 1 30); do
|
|
docker logs frigate-puid 2>&1 | grep -q "already applied" && ok=1 && break
|
|
sleep 2
|
|
done
|
|
if [ "$ok" -ne 1 ]; then echo "sentinel skip never logged"; docker logs frigate-puid; exit 1; fi
|
|
docker rm -f frigate-puid
|
|
- name: Assert read-only rootfs with --user works
|
|
run: |
|
|
mkdir -p /tmp/frigate-config-ro /tmp/frigate-media-ro
|
|
printf 'mqtt:\n enabled: false\ncameras: {}\n' > /tmp/frigate-config-ro/config.yml
|
|
sudo chown -R 1000:1000 /tmp/frigate-config-ro /tmp/frigate-media-ro
|
|
# /run must allow exec: S6_READ_ONLY_ROOT has s6 copy its service
|
|
# scripts there and run them, and --tmpfs defaults to noexec
|
|
docker run -d --name frigate-ro --shm-size 256m \
|
|
--read-only --tmpfs /tmp:rw,size=1g --tmpfs /run:exec,nosuid,nodev,mode=0755 \
|
|
--user 1000:1000 \
|
|
--security-opt no-new-privileges:true \
|
|
-v /tmp/frigate-config-ro:/config \
|
|
-v /tmp/frigate-media-ro:/media/frigate \
|
|
${{ steps.setup.outputs.image-name }}-amd64
|
|
up=0
|
|
for i in $(seq 1 60); do
|
|
docker exec frigate-ro curl -fs http://127.0.0.1:5000/api/version && up=1 && break
|
|
sleep 5
|
|
done
|
|
if [ "$up" -ne 1 ]; then echo "read-only container never healthy"; docker logs frigate-ro; exit 1; fi
|
|
# an if, not "! grep": bash exempts a negated command from set -e and
|
|
# the assertion would never fail
|
|
if docker logs frigate-ro 2>&1 | grep -i "read-only file system"; then
|
|
echo "a service tried to write to the read-only rootfs"; exit 1
|
|
fi
|
|
# the self-signed cert has to land in /config, the only writable path
|
|
docker exec frigate-ro test -f /config/tls/privkey.pem
|
|
# and nginx must serve it, which is what proves the templated cert path
|
|
docker exec frigate-ro curl -ksSI https://127.0.0.1:8971/ >/dev/null
|
|
# logging must work via the s6-log fallback (no logutil-service as non-root)
|
|
docker exec frigate-ro test -s /dev/shm/logs/frigate/current
|
|
# runtime user can write recordings storage
|
|
docker exec frigate-ro touch /media/frigate/.write-probe
|
|
docker exec frigate-ro rm /media/frigate/.write-probe
|
|
docker rm -f frigate-ro
|
|
- name: Assert PUID with read-only fails fast with clear error
|
|
run: |
|
|
docker run -d --name frigate-ro-puid --shm-size 256m \
|
|
--read-only --tmpfs /tmp:rw,size=1g --tmpfs /run:exec,nosuid,nodev,mode=0755 \
|
|
-e PUID=1500 -e PGID=1500 \
|
|
-v /tmp/frigate-config-ro:/config \
|
|
${{ steps.setup.outputs.image-name }}-amd64
|
|
found=0
|
|
for i in $(seq 1 12); do
|
|
if docker logs frigate-ro-puid 2>&1 | grep -q "not compatible with read_only"; then found=1; break; fi
|
|
sleep 5
|
|
done
|
|
if [ "$found" -ne 1 ]; then
|
|
echo "no fail-fast error for PUID with a read-only rootfs"; docker logs frigate-ro-puid; exit 1
|
|
fi
|
|
docker rm -f frigate-ro-puid
|
|
- name: Assert EXTRA_GROUPS with read-only fails fast with clear error
|
|
run: |
|
|
docker run -d --name frigate-ro-groups --shm-size 256m \
|
|
--read-only --tmpfs /tmp:rw,size=1g --tmpfs /run:exec,nosuid,nodev,mode=0755 \
|
|
-e EXTRA_GROUPS=44 \
|
|
-v /tmp/frigate-config-ro:/config \
|
|
-v /tmp/frigate-media-ro:/media/frigate \
|
|
${{ steps.setup.outputs.image-name }}-amd64
|
|
found=0
|
|
for i in $(seq 1 12); do
|
|
if docker logs frigate-ro-groups 2>&1 | grep -q "EXTRA_GROUPS needs a writable /etc"; then found=1; break; fi
|
|
sleep 5
|
|
done
|
|
if [ "$found" -ne 1 ]; then
|
|
echo "no fail-fast error for EXTRA_GROUPS with a read-only rootfs"; docker logs frigate-ro-groups; exit 1
|
|
fi
|
|
docker rm -f frigate-ro-groups
|
|
- name: Assert read-only rootfs in the default mode works
|
|
run: |
|
|
mkdir -p /tmp/frigate-config-rod /tmp/frigate-media-rod
|
|
printf 'mqtt:\n enabled: false\ncameras: {}\n' > /tmp/frigate-config-rod/config.yml
|
|
docker run -d --name frigate-rod --shm-size 256m \
|
|
--read-only --tmpfs /tmp:rw,size=1g --tmpfs /run:exec,nosuid,nodev,mode=0755 \
|
|
--security-opt no-new-privileges:true \
|
|
-v /tmp/frigate-config-rod:/config \
|
|
-v /tmp/frigate-media-rod:/media/frigate \
|
|
${{ steps.setup.outputs.image-name }}-amd64
|
|
up=0
|
|
for i in $(seq 1 60); do
|
|
docker exec frigate-rod curl -fs http://127.0.0.1:5000/api/version && up=1 && break
|
|
sleep 5
|
|
done
|
|
if [ "$up" -ne 1 ]; then echo "read-only default-mode container never healthy"; docker logs frigate-rod; exit 1; fi
|
|
if docker logs frigate-rod 2>&1 | grep -i "read-only file system"; then
|
|
echo "a service tried to write to the read-only rootfs"; exit 1
|
|
fi
|
|
# the point of this mode over docker's user:: the drop still happens
|
|
# and go2rtc still gets its own separate user
|
|
ps_out=$(docker exec frigate-rod ps -eo user=,comm=)
|
|
echo "$ps_out"
|
|
for svc in python3 nginx; do
|
|
if echo "$ps_out" | grep -w "$svc" | grep -q '^root'; then
|
|
echo "$svc is running as root"; exit 1
|
|
fi
|
|
done
|
|
echo "$ps_out" | grep -w go2rtc | grep -q '^go2rtc'
|
|
# the ownership sweep still ran and recorded itself in /config
|
|
docker exec frigate-rod cat /config/.permissions_version | grep -qx "2:1000:1000"
|
|
# setfacl under a read-only rootfs, which nothing else covers:
|
|
# init-devices exits early under --user, so that path is never reached
|
|
docker exec frigate-rod mknod /dev/apex_9 c 120 99
|
|
docker exec frigate-rod sh -c 'export PATH=/command:$PATH; exec /etc/s6-overlay/s6-rc.d/init-devices/run'
|
|
docker exec frigate-rod getfacl -p /dev/apex_9 | grep -q "user:frigate:rw-"
|
|
docker rm -f frigate-rod
|
|
- name: "Assert switching that install to user: still starts"
|
|
run: |
|
|
# the config dir above now holds a go2rtc-owned go2rtc_homekit.yml,
|
|
# which user: keeps readable but not writable (no supplementary groups)
|
|
docker run -d --name frigate-rod-user --shm-size 256m \
|
|
--read-only --tmpfs /tmp:rw,size=1g --tmpfs /run:exec,nosuid,nodev,mode=0755 \
|
|
--user 1000:1000 \
|
|
-v /tmp/frigate-config-rod:/config \
|
|
-v /tmp/frigate-media-rod:/media/frigate \
|
|
${{ steps.setup.outputs.image-name }}-amd64
|
|
up=0
|
|
for i in $(seq 1 60); do
|
|
docker exec frigate-rod-user curl -fs http://127.0.0.1:5000/api/version && up=1 && break
|
|
sleep 5
|
|
done
|
|
if [ "$up" -ne 1 ]; then echo "container did not survive the switch to user:"; docker logs frigate-rod-user; exit 1; fi
|
|
docker logs frigate-rod-user 2>&1 | grep -q "HomeKit pairing changes will not persist"
|
|
docker rm -f frigate-rod-user
|
|
- name: Teardown
|
|
if: always()
|
|
run: docker rm -f frigate || true
|
|
arm64_build:
|
|
runs-on: ubuntu-22.04-arm
|
|
name: ARM Build
|
|
steps:
|
|
- name: Check out code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up QEMU and Buildx
|
|
id: setup
|
|
uses: ./.github/actions/setup
|
|
with:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
- name: Build and push arm64 standard build
|
|
uses: docker/build-push-action@v7
|
|
with:
|
|
context: .
|
|
file: docker/main/Dockerfile
|
|
push: true
|
|
platforms: linux/arm64
|
|
target: frigate
|
|
tags: |
|
|
${{ steps.setup.outputs.image-name }}-standard-arm64
|
|
cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64
|
|
- name: Build and push RPi build
|
|
uses: docker/bake-action@v7
|
|
with:
|
|
source: .
|
|
push: true
|
|
targets: rpi
|
|
files: docker/rpi/rpi.hcl
|
|
set: |
|
|
rpi.tags=${{ steps.setup.outputs.image-name }}-rpi
|
|
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64
|
|
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64,mode=max
|
|
jetson_jp6_build:
|
|
runs-on: ubuntu-22.04-arm
|
|
name: Jetson Jetpack 6
|
|
steps:
|
|
- name: Check out code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up QEMU and Buildx
|
|
id: setup
|
|
uses: ./.github/actions/setup
|
|
with:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
- name: Build and push TensorRT (Jetson, Jetpack 6)
|
|
env:
|
|
ARCH: arm64
|
|
BASE_IMAGE: nvcr.io/nvidia/tensorrt:23.12-py3-igpu
|
|
SLIM_BASE: nvcr.io/nvidia/tensorrt:23.12-py3-igpu
|
|
TRT_BASE: nvcr.io/nvidia/tensorrt:23.12-py3-igpu
|
|
uses: docker/bake-action@v7
|
|
with:
|
|
source: .
|
|
push: true
|
|
targets: tensorrt
|
|
files: docker/tensorrt/trt.hcl
|
|
set: |
|
|
tensorrt.tags=${{ steps.setup.outputs.image-name }}-tensorrt-jp6
|
|
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp6
|
|
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp6,mode=max
|
|
amd64_extra_builds:
|
|
runs-on: ubuntu-22.04
|
|
name: AMD64 Extra Build
|
|
needs:
|
|
- amd64_build
|
|
steps:
|
|
- name: Check out code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up QEMU and Buildx
|
|
id: setup
|
|
uses: ./.github/actions/setup
|
|
with:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
- name: Build and push TensorRT (x86 GPU)
|
|
env:
|
|
COMPUTE_LEVEL: "50 60 70 80 90"
|
|
uses: docker/bake-action@v7
|
|
with:
|
|
source: .
|
|
push: true
|
|
targets: tensorrt
|
|
files: docker/tensorrt/trt.hcl
|
|
set: |
|
|
tensorrt.tags=${{ steps.setup.outputs.image-name }}-tensorrt
|
|
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-tensorrt
|
|
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-tensorrt,mode=max
|
|
- name: AMD/ROCm general build
|
|
env:
|
|
HSA_OVERRIDE: 0
|
|
uses: docker/bake-action@v7
|
|
with:
|
|
source: .
|
|
push: true
|
|
targets: rocm
|
|
files: docker/rocm/rocm.hcl
|
|
set: |
|
|
rocm.tags=${{ steps.setup.outputs.image-name }}-rocm
|
|
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-rocm,mode=max
|
|
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-rocm
|
|
arm64_extra_builds:
|
|
runs-on: ubuntu-22.04-arm
|
|
name: ARM Extra Build
|
|
needs:
|
|
- arm64_build
|
|
steps:
|
|
- name: Check out code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up QEMU and Buildx
|
|
id: setup
|
|
uses: ./.github/actions/setup
|
|
with:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
- name: Build and push Rockchip build
|
|
uses: docker/bake-action@v7
|
|
with:
|
|
source: .
|
|
push: true
|
|
targets: rk
|
|
files: docker/rockchip/rk.hcl
|
|
set: |
|
|
rk.tags=${{ steps.setup.outputs.image-name }}-rk
|
|
*.cache-from=type=gha
|
|
synaptics_build:
|
|
runs-on: ubuntu-22.04-arm
|
|
name: Synaptics Build
|
|
needs:
|
|
- arm64_build
|
|
steps:
|
|
- name: Check out code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up QEMU and Buildx
|
|
id: setup
|
|
uses: ./.github/actions/setup
|
|
with:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
- name: Build and push Synaptics build
|
|
uses: docker/bake-action@v7
|
|
with:
|
|
source: .
|
|
push: true
|
|
targets: synaptics
|
|
files: docker/synaptics/synaptics.hcl
|
|
set: |
|
|
synaptics.tags=${{ steps.setup.outputs.image-name }}-synaptics
|
|
*.cache-from=type=gha
|
|
# The majority of users running arm64 are rpi users, so the rpi
|
|
# build should be the primary arm64 image
|
|
assemble_default_build:
|
|
runs-on: ubuntu-22.04
|
|
name: Assemble and push default build
|
|
needs:
|
|
- amd64_build
|
|
- arm64_build
|
|
steps:
|
|
- id: lowercaseRepo
|
|
uses: ASzc/change-string-case-action@v6
|
|
with:
|
|
string: ${{ github.repository }}
|
|
- name: Log in to the Container registry
|
|
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
- name: Create short sha
|
|
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
|
- uses: int128/docker-manifest-create-action@v2
|
|
with:
|
|
tags: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}
|
|
sources: |
|
|
ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}-amd64
|
|
ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}-rpi
|