mirror of
https://github.com/Screenly/Anthias.git
synced 2026-06-10 09:08:09 -04:00
Boards with < 1.5 GiB MemTotal (Pi 2/Pi 3 1GB, Pi 4 1GB, Rock Pi 4 1GB, generic-arm64 1GB SKUs) OOM-cycled when QtMultimedia loaded a 4K HEVC asset alongside the two QtWebEngine renderers introduced by #2905. On-device repro on a 1 GB Rock Pi 4 confirmed `global_oom` on the container's bash process followed by a restart loop, plus the kernel keeping sshd in banner-exchange until power-cycle. This patch puts the device into a graceful degraded mode before the OOM cascade fires. * bin/upgrade_containers.sh — exports ANTHIAS_LOW_RAM=1 when TOTAL_MEMORY_KB < 1572864 (1.5 GiB), 0 otherwise. Threshold cleanly splits the 1 GB SKUs from the 2 GB+ SKUs in the supported fleet. * docker-compose.yml.tmpl — forwards ANTHIAS_LOW_RAM to the viewer container. * anthias_host_agent — publishes host:total_mem_kb to Redis alongside the existing host:board_subtype so server-side gates can read MemTotal without re-opening /proc/meminfo themselves. * anthias_common.board — adds LOW_RAM_THRESHOLD_KB, get_total_mem_kb(), is_low_ram_device() helpers. * anthias_webview (view.cpp) — when ANTHIAS_LOW_RAM=1, aliases webView2 onto webView1 so the rest of the dual-buffer logic still runs but never spawns a second Chromium renderer (~100 MB physical RAM saved per device). UX: page swap is in-place with a brief blank during load, no preloaded crossfade. * anthias_server.processing — extends the codec gate with a low-RAM 1080p resolution cap. Above-1080p uploads on low-RAM boards are rejected at upload with the existing recipe machinery, extended to inject `-vf scale=1920:1080:force_original_aspect_ratio=decrease` so the operator's re-encode lands within the envelope. If an upload fails BOTH codec and resolution gates, the codec message wins but the recipe folds in the downscale so a single re-encode satisfies both. * Diagnostics page — Memory card surfaces a "Low-RAM mode" badge with the threshold MiB so operators can see why the device degraded. /api/v2/info's `memory` field gains a `low_ram: bool` for API clients. * docs/board-enablement.md — rewrote the stale `--hwdec=drm-copy` / per-codec dispatch text (removed in #2905); documented the known rkvdec mainline limitation on Armbian 6.18 (HEVC stateless engages via `-hwaccel drm` but produces decode errors; H.264 has no v4l2_request binding in +rpt1 7.1.3) and the new low-RAM mode. Tests cover the four matrix cells (low/high-RAM × in/over-cap), the recipe shape with and without `cap_to_1080p`, the cap defence against unknown / zero dimensions, host_agent's MemTotal parser, and the API endpoint's new low_ram field. Out of scope (separate work): HEVC HW-decode on arm64 — depends on an upstream rkvdec driver fix landing in Debian-shipped Armbian kernels; Anthias does not maintain its own kernel/distro. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
166 lines
5.9 KiB
Cheetah
166 lines
5.9 KiB
Cheetah
# vim: ft=yaml.docker-compose
|
|
|
|
services:
|
|
anthias-server:
|
|
image: ghcr.io/screenly/anthias-server:${DOCKER_TAG}-${DEVICE_TYPE}
|
|
build:
|
|
context: .
|
|
dockerfile: docker/Dockerfile.server
|
|
ports:
|
|
- 80:8080
|
|
environment:
|
|
- MAC_ADDRESS=${MAC_ADDRESS}
|
|
- HOST_USER=${USER}
|
|
- HOME=/data
|
|
- LISTEN=0.0.0.0
|
|
- CELERY_BROKER_URL=redis://redis:6379/0
|
|
- CELERY_RESULT_BACKEND=redis://redis:6379/0
|
|
depends_on:
|
|
- redis
|
|
devices:
|
|
- "/dev/vchiq:/dev/vchiq"
|
|
restart: always
|
|
volumes:
|
|
- resin-data:/data
|
|
- /home/${USER}/.anthias:/data/.anthias
|
|
- /home/${USER}/anthias_assets:/data/anthias_assets
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
labels:
|
|
io.balena.features.supervisor-api: '1'
|
|
logging:
|
|
driver: journald
|
|
options:
|
|
tag: anthias-server
|
|
|
|
anthias-viewer:
|
|
image: ghcr.io/screenly/anthias-viewer:${DOCKER_TAG}-${DEVICE_TYPE}
|
|
build:
|
|
context: .
|
|
dockerfile: docker/Dockerfile.viewer
|
|
mem_limit: ${VIEWER_MEMORY_LIMIT_KB}k
|
|
depends_on:
|
|
- anthias-server
|
|
environment:
|
|
- HOME=/data
|
|
- PORT=8080
|
|
- NOREFRESH=1
|
|
- LISTEN=anthias-server
|
|
# Forward the host's system locale into the viewer container so
|
|
# AnthiasWebview can send a matching Accept-Language header
|
|
# (issue 480 — multi-language URL assets used to render in
|
|
# English regardless of the Pi's locale). upgrade_containers.sh
|
|
# sources /etc/default/locale before running envsubst, so these
|
|
# substitute to whatever `update-locale` wrote. When the host has
|
|
# no locale configured these substitute to empty strings; the
|
|
# viewer's start_viewer.sh treats an empty value as "unset" so
|
|
# downstream consumers see the container image's defaults rather
|
|
# than an empty LANG.
|
|
- LANG=${LANG}
|
|
- LANGUAGE=${LANGUAGE}
|
|
- LC_ALL=${LC_ALL}
|
|
# ``1`` on boards with < 1.5 GiB MemTotal. AnthiasViewer reads
|
|
# this to fall into single-QWebEngineView mode (no preloaded
|
|
# crossfade — the inactive renderer cost ~100 MB physical RAM
|
|
# per box, untenable on 1 GB SBCs). Derived from /proc/meminfo
|
|
# in bin/upgrade_containers.sh so the server side
|
|
# (host_agent → ``host:total_mem_kb`` in Redis) and the viewer
|
|
# side both observe the same total.
|
|
- ANTHIAS_LOW_RAM=${ANTHIAS_LOW_RAM}
|
|
privileged: true
|
|
restart: always
|
|
# cage (the wlroots kiosk compositor used on Pi 5 / x86 /
|
|
# arm64 viewers) doesn't always release the DRM master on
|
|
# SIGTERM — its event loop blocks on a libinput shutdown
|
|
# path that hangs on certain kernels, and the kernel's GPU
|
|
# driver leaves dangling references that prevent the next
|
|
# container start from acquiring DRM master. ``stop_grace_period``
|
|
# caps how long ``docker compose down/restart`` waits before
|
|
# SIGKILLing the container; 3s is short enough that an
|
|
# upgrade rolls in a reasonable time even if cage hangs.
|
|
# ``stop_signal: SIGKILL`` skips the SIGTERM-then-wait dance
|
|
# entirely on intentional stops — same effect as a kernel
|
|
# OOM killer landing on the viewer, which the next ``up``
|
|
# cleans up from automatically.
|
|
stop_grace_period: 3s
|
|
stop_signal: SIGKILL
|
|
shm_size: ${SHM_SIZE_KB}kb
|
|
volumes:
|
|
- resin-data:/data
|
|
- /home/${USER}/.asoundrc:/data/.asoundrc
|
|
- /home/${USER}/.anthias:/data/.anthias
|
|
- /home/${USER}/anthias_assets:/data/anthias_assets
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
labels:
|
|
io.balena.features.supervisor-api: '1'
|
|
logging:
|
|
driver: journald
|
|
options:
|
|
tag: anthias-viewer
|
|
|
|
anthias-celery:
|
|
# Runs on the same image as anthias-server with a CMD override.
|
|
# Shipping one image instead of two is the point — server and celery
|
|
# share their entire root filesystem (base apt + venv + app source),
|
|
# and a separate celery image was duplicating ~825 MB extracted of
|
|
# identical content per device. See refactor: drop celery image.
|
|
image: ghcr.io/screenly/anthias-server:${DOCKER_TAG}-${DEVICE_TYPE}
|
|
# ``mem_limit`` keeps a runaway celery task (e.g. a HEIC →
|
|
# WebP convert on a decompression-bomb fixture) from eating
|
|
# the box's RAM and starving the viewer. 60% of host (computed
|
|
# in bin/upgrade_containers.sh) leaves comfortable headroom on
|
|
# every supported SBC.
|
|
mem_limit: ${CELERY_MEMORY_LIMIT_KB}k
|
|
# ``nice -n 19 ionice -c 3`` keeps any subprocess celery
|
|
# spawns (ffprobe, Pillow's libheif binding) at idle priority
|
|
# so the on-device viewer always wins CPU + IO contention. No
|
|
# current task is CPU-bound enough that this matters in
|
|
# practice, but the wrappers are cheap insurance against a
|
|
# pathological input.
|
|
command: >
|
|
nice -n 19 ionice -c 3
|
|
celery -A anthias_server.celery_tasks.celery worker -B -n worker@anthias
|
|
--loglevel=info --scheduler celery.beat.Scheduler
|
|
depends_on:
|
|
- anthias-server
|
|
- redis
|
|
environment:
|
|
- HOME=/data
|
|
- CELERY_BROKER_URL=redis://redis:6379/0
|
|
- CELERY_RESULT_BACKEND=redis://redis:6379/0
|
|
devices:
|
|
- "/dev/vchiq:/dev/vchiq"
|
|
restart: always
|
|
volumes:
|
|
- resin-data:/data
|
|
- /home/${USER}/.anthias:/data/.anthias
|
|
- /home/${USER}/anthias_assets:/data/anthias_assets
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
labels:
|
|
io.balena.features.supervisor-api: '1'
|
|
logging:
|
|
driver: journald
|
|
options:
|
|
tag: anthias-celery
|
|
|
|
redis:
|
|
image: ghcr.io/screenly/anthias-redis:${DOCKER_TAG}-${DEVICE_TYPE}
|
|
build:
|
|
context: .
|
|
dockerfile: docker/Dockerfile.redis
|
|
ports:
|
|
- 127.0.0.1:6379:6379
|
|
restart: always
|
|
volumes:
|
|
- redis-data:/var/lib/redis
|
|
logging:
|
|
driver: journald
|
|
options:
|
|
tag: anthias-redis
|
|
|
|
volumes:
|
|
resin-data:
|
|
redis-data:
|