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:
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
12
homeassistant/components/go2rtc/util.py
Normal file
12
homeassistant/components/go2rtc/util.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
}),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user