mirror of
https://github.com/Screenly/Anthias.git
synced 2026-08-02 18:57:32 -04:00
The docker-build workflow's matrix still listed the websocket and nginx services afterf421130bdeleted their Dockerfiles, so every push to master since Apr 27 fails the `websocket`/`nginx` matrix jobs. The publish-latest-tag step is gated on the full matrix succeeding, so the floating `latest-<board>` tags have been stuck on a pre-f421130b SHA that's also post-ee12387b — the worst possible window: * `bin/start_viewer.sh` runs `python -m viewer` via `sudo -E -u viewer`.ee12387bmoved Python deps from system site-packages into `/venv`, but sudo strips PATH to its `secure_path` even with `-E`, so `python` resolves to `/usr/bin/python3` (no Anthias deps) and the viewer dies on `import django`. Pin the absolute `/venv/bin/python` so sudo's PATH reset is a no-op. * `lib/diagnostics.get_display_power()` calls `cec.init()` and `tv.is_on()` directly. libcec can block in a C call (TV asleep, HDMI link dropped, CEC bus quiet) that ignores Python signals, so the celery task hits its 30s hard `time_limit` and gets SIGKILL'd every 5 minutes. Run the CEC query in a subprocess with `subprocess.run(..., timeout=10)` so a hung libcec call can be killed cleanly. The secondary `_Code.co_positions` AttributeError in the worker logs is billiard's broken traceback formatter for that timeout exception — it goes away once the hang stops. Drop `websocket` and `nginx` from the `service` matrix and from the `SERVICES=(...)` list in the latest-tag mirror step so the workflow can go green again. Once a build publishes, fresh installs will pull post-f421130b images that have the uvicorn server (which serves statics itself, no nginx required) plus the two fixes above. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
127 lines
2.9 KiB
Python
Executable File
127 lines
2.9 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from datetime import datetime
|
|
|
|
from lib import device_helper
|
|
|
|
from . import utils
|
|
|
|
|
|
_CEC_QUERY_SCRIPT = """
|
|
import sys
|
|
try:
|
|
import cec
|
|
cec.init()
|
|
tv = cec.Device(cec.CECDEVICE_TV)
|
|
except Exception:
|
|
sys.stdout.write('CEC error')
|
|
sys.exit(0)
|
|
try:
|
|
sys.stdout.write('True' if tv.is_on() else 'False')
|
|
except IOError:
|
|
sys.stdout.write('Unknown')
|
|
"""
|
|
|
|
|
|
def get_display_power() -> str | bool:
|
|
"""
|
|
Queries the TV using CEC.
|
|
|
|
The CEC stack can block inside libcec (no HDMI link, TV asleep,
|
|
adapter unresponsive) in a C call that ignores Python signals,
|
|
which would tie up the celery worker until it hits its hard
|
|
time_limit and gets SIGKILL'd. Run the query in a subprocess so
|
|
we can enforce a timeout and recover cleanly.
|
|
"""
|
|
try:
|
|
result = subprocess.run(
|
|
[sys.executable, '-c', _CEC_QUERY_SCRIPT],
|
|
capture_output=True,
|
|
timeout=10,
|
|
)
|
|
except subprocess.TimeoutExpired:
|
|
return 'CEC error'
|
|
|
|
output = result.stdout.decode('utf-8', errors='replace').strip()
|
|
if output == 'True':
|
|
return True
|
|
if output == 'False':
|
|
return False
|
|
return output or 'CEC error'
|
|
|
|
|
|
def get_uptime() -> float:
|
|
with open('/proc/uptime', 'r') as f:
|
|
uptime_seconds = float(f.readline().split()[0])
|
|
|
|
return uptime_seconds
|
|
|
|
|
|
def get_load_avg() -> dict[str, float]:
|
|
"""
|
|
Returns load average rounded to two digits.
|
|
"""
|
|
|
|
load_avg: dict[str, float] = {}
|
|
get_load_avg = os.getloadavg()
|
|
|
|
load_avg['1 min'] = round(get_load_avg[0], 2)
|
|
load_avg['5 min'] = round(get_load_avg[1], 2)
|
|
load_avg['15 min'] = round(get_load_avg[2], 2)
|
|
|
|
return load_avg
|
|
|
|
|
|
def get_git_branch() -> str | None:
|
|
return os.getenv('GIT_BRANCH')
|
|
|
|
|
|
def get_git_short_hash() -> str | None:
|
|
return os.getenv('GIT_SHORT_HASH')
|
|
|
|
|
|
def get_git_hash() -> str | None:
|
|
return os.getenv('GIT_HASH')
|
|
|
|
|
|
def try_connectivity() -> list[str]:
|
|
urls = [
|
|
'http://www.google.com',
|
|
'http://www.bbc.co.uk',
|
|
'https://www.google.com',
|
|
'https://www.bbc.co.uk',
|
|
]
|
|
result = []
|
|
for url in urls:
|
|
if utils.url_fails(url):
|
|
result.append('{}: Error'.format(url))
|
|
else:
|
|
result.append('{}: OK'.format(url))
|
|
return result
|
|
|
|
|
|
def get_utc_isodate() -> str:
|
|
return datetime.isoformat(datetime.utcnow())
|
|
|
|
|
|
def get_debian_version() -> str:
|
|
debian_version = '/etc/debian_version'
|
|
if os.path.isfile(debian_version):
|
|
with open(debian_version, 'r') as f:
|
|
for line in f:
|
|
return str(line).strip()
|
|
return 'Unable to get Debian version.'
|
|
else:
|
|
return 'Unable to get Debian version.'
|
|
|
|
|
|
def get_raspberry_code() -> int | str:
|
|
return device_helper.parse_cpu_info().get('hardware', 'Unknown')
|
|
|
|
|
|
def get_raspberry_model() -> int | str:
|
|
return device_helper.parse_cpu_info().get('model', 'Unknown')
|