Compare commits
4 Commits
7addbfdc27
...
da015ea091
| Author | SHA1 | Date |
|---|---|---|
|
|
da015ea091 | 4 days ago |
|
|
825d861f82 | 4 days ago |
|
|
ee66d557de | 4 days ago |
|
|
6096e5d0ab | 4 days ago |
16 changed files with 397 additions and 9 deletions
@ -0,0 +1,48 @@ |
|||
[Unit] |
|||
Description=Asterisk - HTTP server for the directory of the phones |
|||
Documentation=https://busybox.net/downloads/BusyBox.html |
|||
|
|||
Wants=asterisk-build.service |
|||
After=network.target asterisk-build.service var-lib-virtiofs-data.mount |
|||
Requires=var-lib-virtiofs-data.mount |
|||
|
|||
# Static files only, operator-provided on the precious-data mount, like the |
|||
# TFTP root and for the same reasons. A separate directory from the TFTP root: |
|||
# the configuration files of the phones hold their SIP passwords, and HTTP, |
|||
# unlike TFTP, is easily reached from another network. |
|||
|
|||
# Start/stop this unit when the target is started/stopped |
|||
PartOf=asterisk.target |
|||
|
|||
[Container] |
|||
ContainerName=asterisk-http |
|||
Image=localhost/asterisk:latest |
|||
AutoUpdate=local |
|||
|
|||
User=10036 |
|||
Group=10000 |
|||
|
|||
# HTTP listens on 80/tcp, a privileged port. |
|||
AddCapability=CAP_NET_BIND_SERVICE |
|||
|
|||
# Host networking, as the two other containers: the phones reach the same |
|||
# address for SIP, TFTP and HTTP. |
|||
Network=host |
|||
|
|||
# busybox httpd: -f stays in the foreground, -v logs each request. It reads |
|||
# /etc/httpd.conf of the image for the MIME type of XML files, and lists no |
|||
# directory. |
|||
Entrypoint=/usr/sbin/httpd |
|||
Exec=-f -v -p 80 -h /var/lib/virtiofs/data/asterisk/http |
|||
|
|||
# Read-only: the phones fetch, they never upload. |
|||
Volume=/var/lib/virtiofs/data/asterisk/http:/var/lib/virtiofs/data/asterisk/http:ro,z |
|||
|
|||
[Service] |
|||
Restart=always |
|||
RestartSec=10 |
|||
TimeoutStartSec=300 |
|||
TimeoutStopSec=30 |
|||
|
|||
[Install] |
|||
WantedBy=asterisk.target |
|||
@ -1,3 +1,7 @@ |
|||
; Default transfer and pickup settings. The phones transfer with SIP REFER, |
|||
; handled by res_pjsip_refer, not with DTMF feature codes. |
|||
[general] |
|||
; Dialled by a phone, picks up a call ringing on a phone of its pickup groups |
|||
; (named_pickup_group and named_call_group of pjsip.conf). Handled by chan_pjsip |
|||
; before the dialplan. *8 is the default, written here to be found. |
|||
pickupexten = *8 |
|||
|
|||
@ -0,0 +1,10 @@ |
|||
##
|
|||
## Makefile for Frigate quadlet
|
|||
##
|
|||
|
|||
# Frigate runs as root inside its container (s6-overlay, nginx, go2rtc), so its
|
|||
# files belong to root on the host. PROJECT_UID and PROJECT_GID keep their
|
|||
# default, 0.
|
|||
|
|||
# Include common Makefile
|
|||
include ../../scripts/common.mk |
|||
@ -0,0 +1,100 @@ |
|||
# Podman Quadlet: Frigate |
|||
|
|||
## Overview |
|||
|
|||
[Frigate](https://frigate.video/) is a network video recorder with object detection for IP cameras. |
|||
This cookbook runs it as a Podman Quadlet. |
|||
|
|||
This cookbook: |
|||
|
|||
- Runs the upstream image `ghcr.io/blakeblackshear/frigate`, pinned to a full version (`0.18.2`). |
|||
The manifest is multi-architecture (amd64 + arm64). |
|||
- Runs Frigate as root inside its container, as the image requires (s6-overlay). |
|||
- Stores the configuration, the database and the recordings as **precious data** on the virtiofs |
|||
mount `/var/lib/virtiofs/data/frigate`, so that they inherit the hypervisor's ZFS snapshots, |
|||
quota and backups. |
|||
- Seeds the configuration file on first boot, then never touches it again: Frigate owns it, and its |
|||
web interface writes the cameras, the masks and the zones back to it. |
|||
- Binds the unauthenticated listeners of Frigate and go2rtc to the loopback. |
|||
- Detects objects with OpenVINO on the CPU, the default of a new Frigate 0.18 configuration. |
|||
- Uses host networking. |
|||
|
|||
It does not provide MQTT, a reverse proxy, nor any camera definition. |
|||
|
|||
## Prerequisites |
|||
|
|||
- The `base` cookbook (implicit dependency), for the virtiofs mount. |
|||
- A virtiofs mount on `/var/lib/virtiofs/data`, sized for the recordings. |
|||
- Network access at the first boot, to pull the image (about 2 GB on arm64). |
|||
- CPU and memory in proportion to the cameras. Frigate's own figures are for hardware detectors; |
|||
on a CPU, measure the inference time it reports under *System metrics*. |
|||
|
|||
## Files |
|||
|
|||
| Path | Content | |
|||
|------|---------| |
|||
| `/etc/quadlets/frigate/config.yml` | Seed of the configuration file. Shipped by the cookbook; an operator may replace it before the first boot. | |
|||
| `/etc/quadlets/frigate/init.sh` | Copies the seed when Frigate has no configuration file yet. | |
|||
| `/var/lib/virtiofs/data/frigate/config/` | Mounted on `/config`: `config.yml`, the SQLite database, the model cache, the backup of the configuration made before each migration. | |
|||
| `/var/lib/virtiofs/data/frigate/media/` | Mounted on `/media/frigate`: recordings, clips, snapshots, exports. | |
|||
|
|||
`frigate.service` only starts once `/var/lib/virtiofs/data/frigate/config/config.yml` exists. On a |
|||
fresh system, `frigate-init.service` creates it from the seed. Once it exists, the seed is never |
|||
read again: changing `/etc/quadlets/frigate/config.yml` has no effect on a running installation. |
|||
|
|||
## The seed |
|||
|
|||
The seed is the default configuration of Frigate 0.18, plus three restrictions: |
|||
|
|||
- `networking.listen.internal: 127.0.0.1:5000`. Port 5000 serves the UI and the API without |
|||
authentication. The health check and nginx reach it over the loopback; nothing else needs it. |
|||
- `go2rtc.api.listen: 127.0.0.1:1984`. The go2rtc API is not authenticated, and it accepts new |
|||
stream sources, `exec:` included. It listens on every address by default. |
|||
- `go2rtc.rtsp.listen: 127.0.0.1:8554`. The RTSP restream is not authenticated either. |
|||
|
|||
MQTT is disabled. `cameras` is empty: cameras are added from the web interface, or by editing |
|||
`config.yml` on the data mount. |
|||
|
|||
## Ports |
|||
|
|||
| Port | Bound to | Use | |
|||
|------|----------|-----| |
|||
| `8971/tcp` | all | Web interface and API, authenticated, over TLS with a self-signed certificate | |
|||
| `8555/tcp+udp` | all | WebRTC, for live views in a browser | |
|||
| `5000/tcp` | loopback | Web interface and API, **unauthenticated** | |
|||
| `1984/tcp` | loopback | go2rtc API, **unauthenticated** | |
|||
| `8554/tcp` | loopback | RTSP restream, unauthenticated | |
|||
|
|||
## First login |
|||
|
|||
Frigate creates an `admin` user at its first start and prints its password in the journal: |
|||
|
|||
```sh |
|||
journalctl -u frigate.service | grep -A1 -i password |
|||
``` |
|||
|
|||
Change it under *Settings > Users*. To print a new one, set `auth.reset_admin_password: true` in |
|||
`config.yml` and restart Frigate. |
|||
|
|||
## Upgrades |
|||
|
|||
The tag is pinned in `frigate.image` and `frigate.container`, and both must change together. A new |
|||
minor version migrates `config.yml` in place at its first start, after copying it to |
|||
`backup_config.yaml`. There is no migration back: going back to an older version means restoring |
|||
the data mount from a snapshot taken before the upgrade. |
|||
|
|||
## Usage |
|||
|
|||
```sh |
|||
sudo systemctl start frigate.target |
|||
sudo systemctl status frigate.service |
|||
sudo podman logs -f frigate |
|||
curl -k https://localhost:8971/ |
|||
``` |
|||
|
|||
## Shared memory |
|||
|
|||
A camera detecting at 720p takes about 35 MB of shared memory: 1280 × 720 × 1.5 bytes for each of |
|||
the 25 frames Frigate keeps. Logs and nginx cache take up to 50 MB more. `ShmSize=512m` thus covers |
|||
eight such cameras and two added while Frigate runs. A shared memory too small shows as `Bus error` |
|||
in the journal. The calculator in the upstream installation guide gives the size for other setups. |
|||
@ -0,0 +1,42 @@ |
|||
# Seed of /var/lib/virtiofs/data/frigate/config/config.yml, copied once by |
|||
# frigate-init.service. Frigate owns the file afterwards: cameras, masks and |
|||
# zones are added from its web interface. An operator who needs other defaults |
|||
# provides /etc/quadlets/frigate/config.yml before the first boot. |
|||
|
|||
mqtt: |
|||
enabled: false |
|||
|
|||
# Port 5000 serves the UI and the API WITHOUT authentication. It is meant for |
|||
# the container itself (health check, nginx) and goes on the loopback. The |
|||
# authenticated UI stays on 8971, over TLS. |
|||
networking: |
|||
listen: |
|||
internal: 127.0.0.1:5000 |
|||
|
|||
# go2rtc listens on every address by default. Its API on 1984 is not |
|||
# authenticated and accepts new stream sources, "exec:" included; the RTSP |
|||
# restream on 8554 is not authenticated either. Frigate reaches both over the |
|||
# loopback. WebRTC (8555) stays open: the browsers connect to it directly. |
|||
go2rtc: |
|||
api: |
|||
listen: 127.0.0.1:1984 |
|||
rtsp: |
|||
listen: 127.0.0.1:8554 |
|||
|
|||
# The default of a new Frigate 0.18 configuration: OpenVINO on the CPU, with the |
|||
# SSDLite MobileNet v2 model shipped in the image (amd64 and arm64). |
|||
detectors: |
|||
ov: |
|||
type: openvino |
|||
device: CPU |
|||
model: |
|||
width: 300 |
|||
height: 300 |
|||
input_tensor: nhwc |
|||
input_pixel_format: bgr |
|||
path: /openvino-model/ssdlite_mobilenet_v2.xml |
|||
labelmap_path: /openvino-model/coco_91cl_bkgr.txt |
|||
|
|||
cameras: {} |
|||
|
|||
version: 0.18-0 |
|||
@ -0,0 +1,20 @@ |
|||
#!/bin/sh |
|||
# |
|||
# Seed the Frigate configuration file on first boot. |
|||
# |
|||
# Frigate owns config.yml: the web interface writes the cameras, the masks and |
|||
# the zones back to it, and a new minor version migrates it in place. So this |
|||
# script only ever creates it when it is absent, and never touches it again. |
|||
# |
|||
set -eu |
|||
|
|||
SRC=/etc/quadlets/frigate/config.yml |
|||
DST=/var/lib/virtiofs/data/frigate/config/config.yml |
|||
|
|||
if [ -e "$DST" ]; then |
|||
echo "Keeping existing $DST (Frigate owns it)" |
|||
exit 0 |
|||
fi |
|||
|
|||
echo "Seeding $DST from $SRC" |
|||
install -m 0600 -o root -g root "$SRC" "$DST" |
|||
@ -0,0 +1,24 @@ |
|||
[Unit] |
|||
Description=Frigate - Seed the configuration file on first boot |
|||
Documentation=https://docs.frigate.video/configuration/ |
|||
After=var-lib-virtiofs-data.mount systemd-tmpfiles-setup.service |
|||
Requires=var-lib-virtiofs-data.mount |
|||
|
|||
# Only run if there is a seed to copy |
|||
ConditionPathExists=/etc/quadlets/frigate/config.yml |
|||
|
|||
# Start/stop this unit when the target is started/stopped, before the service |
|||
PartOf=frigate.target |
|||
Before=frigate.service |
|||
|
|||
[Service] |
|||
Type=oneshot |
|||
RemainAfterExit=yes |
|||
|
|||
# Copy the seed on first boot only (see init.sh). Frigate owns config.yml |
|||
# afterwards: its web interface writes every change back to it, and it migrates |
|||
# the file in place on an upgrade. |
|||
ExecStart=/etc/quadlets/frigate/init.sh |
|||
|
|||
[Install] |
|||
WantedBy=frigate.target |
|||
@ -0,0 +1,80 @@ |
|||
[Unit] |
|||
Description=Frigate - Network video recorder with object detection |
|||
Documentation=https://docs.frigate.video/ |
|||
After=network.target var-lib-virtiofs-data.mount frigate-init.service |
|||
Requires=var-lib-virtiofs-data.mount frigate-init.service |
|||
|
|||
# The pull runs in its own unit, and it is Wants=, NOT Requires=. A pull needs |
|||
# the registry; starting this container does not, as long as the image is in |
|||
# local storage. With Requires=, a boot without network fails the pull, the |
|||
# dependency fails, and a failed dependency is never retried: Restart= only |
|||
# applies to a unit that started at least once. |
|||
# |
|||
# After= still orders the pull before this container, so nothing is lost on a |
|||
# boot where the network is there. |
|||
Wants=frigate-image.service |
|||
After=frigate-image.service |
|||
|
|||
# Frigate starts on the configuration file seeded by frigate-init.service, or on |
|||
# the one it already owns. Without it, Frigate would write a default one of its |
|||
# own, with none of the settings the README lists. |
|||
ConditionPathExists=/var/lib/virtiofs/data/frigate/config/config.yml |
|||
|
|||
# Start/stop this unit when the target is started/stopped |
|||
PartOf=frigate.target |
|||
|
|||
[Container] |
|||
ContainerName=frigate |
|||
# The image REFERENCE, and not the name of the frigate.image unit: naming the |
|||
# unit makes quadlet generate a Requires= on it, which is the fault described in |
|||
# [Unit] above. Podman resolves the reference at start with --pull=missing. |
|||
# |
|||
# THIS REFERENCE IS ALSO IN frigate.image, and the two must be changed together. |
|||
Image=ghcr.io/blakeblackshear/frigate:0.18.2 |
|||
|
|||
# Root inside the container: the image starts s6-overlay, which runs nginx, |
|||
# go2rtc and Frigate as root. Frigate documents no other way to run. |
|||
|
|||
# Host networking: the cameras push nothing, Frigate pulls their RTSP streams, |
|||
# and WebRTC needs the real addresses of the browsers. The listening sockets are |
|||
# narrowed by the configuration file instead -- see the README. |
|||
Network=host |
|||
|
|||
# Decoded frames are passed between processes in /dev/shm: about 35 MB per |
|||
# camera detecting at 720p, plus up to 50 MB of logs and nginx cache. 512 MB |
|||
# covers eight cameras and two more. A shm too small shows as "Bus error" in the |
|||
# journal. |
|||
ShmSize=512m |
|||
|
|||
# Recording segments are written here first, then moved to /media/frigate. |
|||
Tmpfs=/tmp/cache:rw,size=1g |
|||
|
|||
# Precious data: configuration, database and model cache on one side, |
|||
# recordings on the other. Both on the virtiofs mount, where the host's ZFS |
|||
# snapshots and quota apply. |
|||
Volume=/var/lib/virtiofs/data/frigate/config:/config:z |
|||
Volume=/var/lib/virtiofs/data/frigate/media:/media/frigate:z |
|||
|
|||
# No /etc/localtime: Fedora CoreOS has none (it runs in UTC), and podman fails |
|||
# the start on a missing mount source. Frigate files its recordings by UTC date |
|||
# and its web interface shows the time zone of the browser. |
|||
|
|||
# Health check: the request of the image's own HEALTHCHECK, on the internal |
|||
# port, which the configuration file binds to the loopback. The image also skips |
|||
# the check during an orderly shutdown; that part needs a shell, and the state |
|||
# of a container being stopped does not matter. |
|||
HealthCmd=curl --fail --silent --show-error http://127.0.0.1:5000/api/version |
|||
HealthInterval=30s |
|||
HealthTimeout=10s |
|||
# The first start converts the detection model, which takes minutes on a CPU. |
|||
HealthStartPeriod=300s |
|||
HealthRetries=5 |
|||
|
|||
[Service] |
|||
Restart=always |
|||
RestartSec=10 |
|||
TimeoutStartSec=900 |
|||
TimeoutStopSec=120 |
|||
|
|||
[Install] |
|||
WantedBy=frigate.target |
|||
@ -0,0 +1,27 @@ |
|||
[Unit] |
|||
Description=podman pull ghcr.io/blakeblackshear/frigate |
|||
Documentation=https://docs.frigate.video/ |
|||
|
|||
# Keep trying. A machine whose network arrives after it does fails this pull |
|||
# once, early in the boot, and a oneshot that failed once never runs again. |
|||
# StartLimitIntervalSec=0 makes the retry unlimited: the default start limit |
|||
# (5 attempts in 10 s) would turn the unit "failed" for good within a minute. |
|||
StartLimitIntervalSec=0 |
|||
|
|||
# Stopping the service target stops the pull with it, so `systemctl stop |
|||
# frigate.target` leaves nothing retrying behind. |
|||
PartOf=frigate.target |
|||
|
|||
[Image] |
|||
# Multi-architecture manifest (amd64 + arm64, verified 2026-11-01 on 0.18.2). |
|||
# The tag is a full version: Frigate migrates its configuration file in place |
|||
# when a new minor version starts on it, and keeps no way back. An upgrade is |
|||
# therefore something an operator decides, not something auto-update does. |
|||
# |
|||
# THIS REFERENCE IS ALSO IN frigate.container, on purpose, and the two must be |
|||
# changed together. The reason is in the container file. |
|||
Image=ghcr.io/blakeblackshear/frigate:0.18.2 |
|||
|
|||
[Service] |
|||
Restart=on-failure |
|||
RestartSec=60 |
|||
@ -0,0 +1,11 @@ |
|||
[Unit] |
|||
Description=Frigate Service Target |
|||
Documentation=man:systemd.target(5) |
|||
Requires=frigate-init.service frigate.service |
|||
After=frigate-init.service frigate.service |
|||
|
|||
# Allow isolation - can stop/start this target independently |
|||
AllowIsolate=yes |
|||
|
|||
[Install] |
|||
WantedBy=multi-user.target |
|||
@ -0,0 +1,9 @@ |
|||
# Precious data, on the virtiofs mount so that the host's ZFS snapshots and |
|||
# backups pick it up: |
|||
# - config: config.yml, the SQLite database (events, users), the model cache; |
|||
# - media: the recordings, the snapshots, the clips and the exports. |
|||
# The "$" marks them for removal on `systemd-tmpfiles --purge` (i.e. `make |
|||
# uninstall`). |
|||
d$ /var/lib/virtiofs/data/frigate 0750 root root - |
|||
d$ /var/lib/virtiofs/data/frigate/config 0750 root root - |
|||
d$ /var/lib/virtiofs/data/frigate/media 0750 root root - |
|||
Loading…
Reference in new issue