mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 12:18:51 +00:00
Visitors hold admin sessions (the editor demo needs is_admin), and some of them restarted HA from the UI — exit code 100 between hourly resets looked like stand instability. demo_guard re-registers restart/stop as no-op services after startup; the hourly docker-level reset is untouched. stand-reset-timer.js logs a per-minute countdown to the :00 reset in the browser console (warn for the last 5 minutes); stand-dev-info.js is the one-shot dev-stand note. Also on the stand host: mem_limit 1536m per container in compose.yml as an OOM safety net.
47 lines
1.9 KiB
Python
47 lines
1.9 KiB
Python
"""Stand-only guard: keep visitor-admins from killing the public demo.
|
|
|
|
Visitors log in as `demo`, and that user HAS to be an administrator — the
|
|
House Plan card gates its editor on `user.is_admin`, and demonstrating the
|
|
editor is the whole point of the stand. But an administrator can also call
|
|
`homeassistant.restart` / `homeassistant.stop`, and visitors do exactly that
|
|
(seen live 2026-07-31: HA exited with the restart code 100 minutes after a
|
|
scheduled reset, right as a visitor session from an external IP was active —
|
|
that is what looked like «стенд перезагружается чаще, чем раз в час»).
|
|
|
|
Once startup finishes this component re-registers both services as no-ops:
|
|
the UI buttons stay, pressing them only leaves a WARNING in the log. The
|
|
hourly stand reset is NOT affected — hp-reset.timer restarts the docker
|
|
container from the outside.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
|
|
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
|
from homeassistant.helpers.typing import ConfigType
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
DOMAIN = "demo_guard"
|
|
BLOCKED = ("restart", "stop")
|
|
|
|
|
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|
async def _blocked(call: ServiceCall) -> None:
|
|
_LOGGER.warning(
|
|
"демо-стенд: homeassistant.%s отключён для посетителей "
|
|
"(стенд и так сбрасывается сам, ежечасно в :00)",
|
|
call.service,
|
|
)
|
|
|
|
@callback
|
|
def _neuter(_event) -> None:
|
|
# After EVENT_HOMEASSISTANT_STARTED every core service is in place;
|
|
# registering the same domain/name again replaces the handler.
|
|
for service in BLOCKED:
|
|
hass.services.async_register("homeassistant", service, _blocked)
|
|
|
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _neuter)
|
|
return True
|