1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-20 02:48:57 +00:00

Create the go2rtc unix socket inside a temporary folder (#157742)

This commit is contained in:
Robert Resch
2025-12-02 13:35:39 +01:00
committed by GitHub
parent 3bccb4b89c
commit 2c72cd94f2
8 changed files with 73 additions and 25 deletions

View File

@@ -6,6 +6,7 @@ from dataclasses import dataclass
import logging
from secrets import token_hex
import shutil
from tempfile import mkdtemp
from aiohttp import BasicAuth, ClientSession, UnixConnector
from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError
@@ -62,11 +63,11 @@ from .const import (
CONF_DEBUG_UI,
DEBUG_UI_URL_MESSAGE,
DOMAIN,
HA_MANAGED_UNIX_SOCKET,
HA_MANAGED_URL,
RECOMMENDED_VERSION,
)
from .server import Server
from .util import get_go2rtc_unix_socket_path
_LOGGER = logging.getLogger(__name__)
@@ -154,10 +155,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
auth = BasicAuth(username, password)
# HA will manage the binary
temp_dir = mkdtemp(prefix="go2rtc-")
# Manually created session (not using the helper) needs to be closed manually
# See on_stop listener below
session = ClientSession(
connector=UnixConnector(path=HA_MANAGED_UNIX_SOCKET), auth=auth
connector=UnixConnector(path=get_go2rtc_unix_socket_path(temp_dir)),
auth=auth,
)
server = Server(
hass,
@@ -166,6 +169,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
enable_ui=domain_config.get(CONF_DEBUG_UI, False),
username=username,
password=password,
working_dir=temp_dir,
)
try:
await server.start()

View File

@@ -6,7 +6,6 @@ CONF_DEBUG_UI = "debug_ui"
DEBUG_UI_URL_MESSAGE = "Url and debug_ui cannot be set at the same time."
HA_MANAGED_API_PORT = 11984
HA_MANAGED_URL = f"http://localhost:{HA_MANAGED_API_PORT}/"
HA_MANAGED_UNIX_SOCKET = "/run/go2rtc.sock"
# When changing this version, also update the corresponding SHA hash (_GO2RTC_SHA)
# in script/hassfest/docker.py.
RECOMMENDED_VERSION = "1.9.12"

View File

@@ -12,13 +12,13 @@ from go2rtc_client import Go2RtcRestClient
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from .const import HA_MANAGED_API_PORT, HA_MANAGED_UNIX_SOCKET, HA_MANAGED_URL
from .const import HA_MANAGED_API_PORT, HA_MANAGED_URL
from .util import get_go2rtc_unix_socket_path
_LOGGER = logging.getLogger(__name__)
_TERMINATE_TIMEOUT = 5
_SETUP_TIMEOUT = 30
_SUCCESSFUL_BOOT_MESSAGE = "INF [api] listen addr="
_LOCALHOST_IP = "127.0.0.1"
_LOG_BUFFER_SIZE = 512
_RESPAWN_COOLDOWN = 1
@@ -122,7 +122,9 @@ def _format_list_for_yaml(items: tuple[str, ...]) -> str:
return f"[{formatted_items}]"
def _create_temp_file(enable_ui: bool, username: str, password: str) -> str:
def _create_temp_file(
enable_ui: bool, username: str, password: str, working_dir: str
) -> str:
"""Create temporary config file."""
app_modules: tuple[str, ...] = _APP_MODULES
api_paths: tuple[str, ...] = _API_ALLOW_PATHS
@@ -139,11 +141,13 @@ def _create_temp_file(enable_ui: bool, username: str, password: str) -> str:
# Set delete=False to prevent the file from being deleted when the file is closed
# Linux is clearing tmp folder on reboot, so no need to delete it manually
with NamedTemporaryFile(prefix="go2rtc_", suffix=".yaml", delete=False) as file:
with NamedTemporaryFile(
prefix="go2rtc_", suffix=".yaml", dir=working_dir, delete=False
) as file:
file.write(
_GO2RTC_CONFIG_FORMAT.format(
listen_config=listen_config,
unix_socket=HA_MANAGED_UNIX_SOCKET,
unix_socket=get_go2rtc_unix_socket_path(working_dir),
app_modules=_format_list_for_yaml(app_modules),
api_allow_paths=_format_list_for_yaml(api_paths),
username=username,
@@ -165,6 +169,7 @@ class Server:
enable_ui: bool = False,
username: str,
password: str,
working_dir: str,
) -> None:
"""Initialize the server."""
self._hass = hass
@@ -173,6 +178,7 @@ class Server:
self._enable_ui = enable_ui
self._username = username
self._password = password
self._working_dir = working_dir
self._log_buffer: deque[str] = deque(maxlen=_LOG_BUFFER_SIZE)
self._process: asyncio.subprocess.Process | None = None
self._startup_complete = asyncio.Event()
@@ -190,7 +196,11 @@ class Server:
"""Start the server."""
_LOGGER.debug("Starting go2rtc server")
config_file = await self._hass.async_add_executor_job(
_create_temp_file, self._enable_ui, self._username, self._password
_create_temp_file,
self._enable_ui,
self._username,
self._password,
self._working_dir,
)
self._startup_complete.clear()

View File

@@ -0,0 +1,12 @@
"""Go2rtc utility functions."""
from pathlib import Path
_HA_MANAGED_UNIX_SOCKET_FILE = "go2rtc.sock"
def get_go2rtc_unix_socket_path(path: str | Path) -> str:
"""Get the Go2rtc unix socket path."""
if not isinstance(path, Path):
path = Path(path)
return str(path / _HA_MANAGED_UNIX_SOCKET_FILE)

View File

@@ -1,6 +1,7 @@
"""Go2rtc test configuration."""
from collections.abc import Generator
from pathlib import Path
from unittest.mock import AsyncMock, Mock, patch
from awesomeversion import AwesomeVersion
@@ -228,3 +229,15 @@ async def init_test_integration(
await hass.async_block_till_done()
return test_camera
@pytest.fixture
def server_dir(tmp_path: Path) -> Generator[Path]:
"""Fixture to provide a temporary directory for the server."""
server_dir = tmp_path / "go2rtc"
server_dir.mkdir()
with patch(
"homeassistant.components.go2rtc.mkdtemp",
return_value=str(server_dir),
):
yield server_dir

View File

@@ -3,7 +3,7 @@
_CallList([
_Call(
tuple(
b'# This file is managed by Home Assistant\n# Do not edit it manually\n\napp:\n modules: ["api","exec","ffmpeg","http","mjpeg","onvif","rtmp","rtsp","srtp","webrtc","ws"]\n\napi:\n listen: ""\n unix_listen: "/run/go2rtc.sock"\n allow_paths: ["/","/api","/api/frame.jpeg","/api/schemes","/api/streams","/api/webrtc","/api/ws"]\n local_auth: true\n username: d2a0b844f4cdbe773702176c47c9a675eb0c56a0779b8f880cdb3b492ed3b1c1\n password: bc495d266a32e66ba69b9c72546e00101e04fb573f1bd08863fe4ad1aac02949\n\n# ffmpeg needs the exec module\n# Restrict execution to only ffmpeg binary\nexec:\n allow_paths:\n - ffmpeg\n\nrtsp:\n listen: "127.0.0.1:18554"\n\nwebrtc:\n listen: ":18555/tcp"\n ice_servers: []\n',
b'# This file is managed by Home Assistant\n# Do not edit it manually\n\napp:\n modules: ["api","exec","ffmpeg","http","mjpeg","onvif","rtmp","rtsp","srtp","webrtc","ws"]\n\napi:\n listen: ""\n unix_listen: "/test/path/go2rtc.sock"\n allow_paths: ["/","/api","/api/frame.jpeg","/api/schemes","/api/streams","/api/webrtc","/api/ws"]\n local_auth: true\n username: d2a0b844f4cdbe773702176c47c9a675eb0c56a0779b8f880cdb3b492ed3b1c1\n password: bc495d266a32e66ba69b9c72546e00101e04fb573f1bd08863fe4ad1aac02949\n\n# ffmpeg needs the exec module\n# Restrict execution to only ffmpeg binary\nexec:\n allow_paths:\n - ffmpeg\n\nrtsp:\n listen: "127.0.0.1:18554"\n\nwebrtc:\n listen: ":18555/tcp"\n ice_servers: []\n',
),
dict({
}),
@@ -14,7 +14,7 @@
_CallList([
_Call(
tuple(
b'# This file is managed by Home Assistant\n# Do not edit it manually\n\napp:\n modules: ["api","exec","ffmpeg","http","mjpeg","onvif","rtmp","rtsp","srtp","webrtc","ws","debug"]\n\napi:\n listen: ":11984"\n unix_listen: "/run/go2rtc.sock"\n allow_paths: ["/","/api","/api/frame.jpeg","/api/schemes","/api/streams","/api/webrtc","/api/ws","/api/config","/api/log","/api/streams.dot"]\n local_auth: true\n username: user\n password: pass\n\n# ffmpeg needs the exec module\n# Restrict execution to only ffmpeg binary\nexec:\n allow_paths:\n - ffmpeg\n\nrtsp:\n listen: "127.0.0.1:18554"\n\nwebrtc:\n listen: ":18555/tcp"\n ice_servers: []\n',
b'# This file is managed by Home Assistant\n# Do not edit it manually\n\napp:\n modules: ["api","exec","ffmpeg","http","mjpeg","onvif","rtmp","rtsp","srtp","webrtc","ws","debug"]\n\napi:\n listen: ":11984"\n unix_listen: "/test/path/go2rtc.sock"\n allow_paths: ["/","/api","/api/frame.jpeg","/api/schemes","/api/streams","/api/webrtc","/api/ws","/api/config","/api/log","/api/streams.dot"]\n local_auth: true\n username: user\n password: pass\n\n# ffmpeg needs the exec module\n# Restrict execution to only ffmpeg binary\nexec:\n allow_paths:\n - ffmpeg\n\nrtsp:\n listen: "127.0.0.1:18554"\n\nwebrtc:\n listen: ":18555/tcp"\n ice_servers: []\n',
),
dict({
}),

View File

@@ -2,6 +2,7 @@
from collections.abc import Awaitable, Callable
import logging
from pathlib import Path
from typing import NamedTuple
from unittest.mock import ANY, AsyncMock, Mock, patch
@@ -39,9 +40,9 @@ from homeassistant.components.go2rtc.const import (
CONF_DEBUG_UI,
DEBUG_UI_URL_MESSAGE,
DOMAIN,
HA_MANAGED_UNIX_SOCKET,
RECOMMENDED_VERSION,
)
from homeassistant.components.go2rtc.util import get_go2rtc_unix_socket_path
from homeassistant.components.stream import Orientation
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
@@ -239,6 +240,7 @@ async def test_setup_go_binary(
server_stop: Mock,
init_test_integration: MockCamera,
has_go2rtc_entry: bool,
server_dir: Path,
config: ConfigType,
ui_enabled: bool,
expected_username: str,
@@ -255,6 +257,7 @@ async def test_setup_go_binary(
enable_ui=ui_enabled,
username=expected_username,
password=expected_password,
working_dir=str(server_dir),
)
call_kwargs = server.call_args[1]
assert call_kwargs["username"] == expected_username
@@ -1034,7 +1037,7 @@ async def test_stream_orientation_with_generic_camera(
"rest_client",
"server",
)
async def test_unix_socket_connection(hass: HomeAssistant) -> None:
async def test_unix_socket_connection(hass: HomeAssistant, server_dir: Path) -> None:
"""Test Unix socket is used for HA-managed go2rtc instances."""
config = {DOMAIN: {}}
@@ -1056,7 +1059,7 @@ async def test_unix_socket_connection(hass: HomeAssistant) -> None:
assert "connector" in call_kwargs
connector = call_kwargs["connector"]
assert isinstance(connector, UnixConnector)
assert connector.path == HA_MANAGED_UNIX_SOCKET
assert connector.path == get_go2rtc_unix_socket_path(server_dir)
# Auth should be auto-generated when credentials are not explicitly configured
assert "auth" in call_kwargs
auth = call_kwargs["auth"]
@@ -1120,7 +1123,7 @@ async def test_basic_auth_with_custom_url(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("rest_client")
async def test_basic_auth_with_debug_ui(hass: HomeAssistant) -> None:
async def test_basic_auth_with_debug_ui(hass: HomeAssistant, server_dir: Path) -> None:
"""Test BasicAuth session is created when username and password are provided with debug_ui."""
config = {
DOMAIN: {
@@ -1158,7 +1161,7 @@ async def test_basic_auth_with_debug_ui(hass: HomeAssistant) -> None:
assert "connector" in call_kwargs
connector = call_kwargs["connector"]
assert isinstance(connector, UnixConnector)
assert connector.path == HA_MANAGED_UNIX_SOCKET
assert connector.path == get_go2rtc_unix_socket_path(server_dir)
assert "auth" in call_kwargs
auth = call_kwargs["auth"]
assert isinstance(auth, BasicAuth)

View File

@@ -3,6 +3,7 @@
import asyncio
from collections.abc import Generator
import logging
from pathlib import Path
import subprocess
from unittest.mock import AsyncMock, MagicMock, Mock, patch
@@ -47,16 +48,22 @@ def server(
enable_ui: bool,
username: str,
password: str,
) -> Server:
server_dir: Path,
) -> Generator[Server]:
"""Fixture to initialize the Server."""
return Server(
hass,
binary=TEST_BINARY,
session=mock_session,
enable_ui=enable_ui,
username=username,
password=password,
)
with patch(
"homeassistant.components.go2rtc.server.get_go2rtc_unix_socket_path",
return_value="/test/path/go2rtc.sock",
):
yield Server(
hass,
binary=TEST_BINARY,
session=mock_session,
enable_ui=enable_ui,
username=username,
password=password,
working_dir=str(server_dir),
)
@pytest.fixture