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
|
import logging
|
||||||
from secrets import token_hex
|
from secrets import token_hex
|
||||||
import shutil
|
import shutil
|
||||||
|
from tempfile import mkdtemp
|
||||||
|
|
||||||
from aiohttp import BasicAuth, ClientSession, UnixConnector
|
from aiohttp import BasicAuth, ClientSession, UnixConnector
|
||||||
from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError
|
from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError
|
||||||
@@ -62,11 +63,11 @@ from .const import (
|
|||||||
CONF_DEBUG_UI,
|
CONF_DEBUG_UI,
|
||||||
DEBUG_UI_URL_MESSAGE,
|
DEBUG_UI_URL_MESSAGE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
HA_MANAGED_UNIX_SOCKET,
|
|
||||||
HA_MANAGED_URL,
|
HA_MANAGED_URL,
|
||||||
RECOMMENDED_VERSION,
|
RECOMMENDED_VERSION,
|
||||||
)
|
)
|
||||||
from .server import Server
|
from .server import Server
|
||||||
|
from .util import get_go2rtc_unix_socket_path
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -154,10 +155,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
|
|
||||||
auth = BasicAuth(username, password)
|
auth = BasicAuth(username, password)
|
||||||
# HA will manage the binary
|
# HA will manage the binary
|
||||||
|
temp_dir = mkdtemp(prefix="go2rtc-")
|
||||||
# Manually created session (not using the helper) needs to be closed manually
|
# Manually created session (not using the helper) needs to be closed manually
|
||||||
# See on_stop listener below
|
# See on_stop listener below
|
||||||
session = ClientSession(
|
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(
|
server = Server(
|
||||||
hass,
|
hass,
|
||||||
@@ -166,6 +169,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
enable_ui=domain_config.get(CONF_DEBUG_UI, False),
|
enable_ui=domain_config.get(CONF_DEBUG_UI, False),
|
||||||
username=username,
|
username=username,
|
||||||
password=password,
|
password=password,
|
||||||
|
working_dir=temp_dir,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await server.start()
|
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."
|
DEBUG_UI_URL_MESSAGE = "Url and debug_ui cannot be set at the same time."
|
||||||
HA_MANAGED_API_PORT = 11984
|
HA_MANAGED_API_PORT = 11984
|
||||||
HA_MANAGED_URL = f"http://localhost:{HA_MANAGED_API_PORT}/"
|
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)
|
# When changing this version, also update the corresponding SHA hash (_GO2RTC_SHA)
|
||||||
# in script/hassfest/docker.py.
|
# in script/hassfest/docker.py.
|
||||||
RECOMMENDED_VERSION = "1.9.12"
|
RECOMMENDED_VERSION = "1.9.12"
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ from go2rtc_client import Go2RtcRestClient
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
_TERMINATE_TIMEOUT = 5
|
_TERMINATE_TIMEOUT = 5
|
||||||
_SETUP_TIMEOUT = 30
|
_SETUP_TIMEOUT = 30
|
||||||
_SUCCESSFUL_BOOT_MESSAGE = "INF [api] listen addr="
|
_SUCCESSFUL_BOOT_MESSAGE = "INF [api] listen addr="
|
||||||
_LOCALHOST_IP = "127.0.0.1"
|
|
||||||
_LOG_BUFFER_SIZE = 512
|
_LOG_BUFFER_SIZE = 512
|
||||||
_RESPAWN_COOLDOWN = 1
|
_RESPAWN_COOLDOWN = 1
|
||||||
|
|
||||||
@@ -122,7 +122,9 @@ def _format_list_for_yaml(items: tuple[str, ...]) -> str:
|
|||||||
return f"[{formatted_items}]"
|
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."""
|
"""Create temporary config file."""
|
||||||
app_modules: tuple[str, ...] = _APP_MODULES
|
app_modules: tuple[str, ...] = _APP_MODULES
|
||||||
api_paths: tuple[str, ...] = _API_ALLOW_PATHS
|
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
|
# 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
|
# 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(
|
file.write(
|
||||||
_GO2RTC_CONFIG_FORMAT.format(
|
_GO2RTC_CONFIG_FORMAT.format(
|
||||||
listen_config=listen_config,
|
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),
|
app_modules=_format_list_for_yaml(app_modules),
|
||||||
api_allow_paths=_format_list_for_yaml(api_paths),
|
api_allow_paths=_format_list_for_yaml(api_paths),
|
||||||
username=username,
|
username=username,
|
||||||
@@ -165,6 +169,7 @@ class Server:
|
|||||||
enable_ui: bool = False,
|
enable_ui: bool = False,
|
||||||
username: str,
|
username: str,
|
||||||
password: str,
|
password: str,
|
||||||
|
working_dir: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the server."""
|
"""Initialize the server."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
@@ -173,6 +178,7 @@ class Server:
|
|||||||
self._enable_ui = enable_ui
|
self._enable_ui = enable_ui
|
||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
|
self._working_dir = working_dir
|
||||||
self._log_buffer: deque[str] = deque(maxlen=_LOG_BUFFER_SIZE)
|
self._log_buffer: deque[str] = deque(maxlen=_LOG_BUFFER_SIZE)
|
||||||
self._process: asyncio.subprocess.Process | None = None
|
self._process: asyncio.subprocess.Process | None = None
|
||||||
self._startup_complete = asyncio.Event()
|
self._startup_complete = asyncio.Event()
|
||||||
@@ -190,7 +196,11 @@ class Server:
|
|||||||
"""Start the server."""
|
"""Start the server."""
|
||||||
_LOGGER.debug("Starting go2rtc server")
|
_LOGGER.debug("Starting go2rtc server")
|
||||||
config_file = await self._hass.async_add_executor_job(
|
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()
|
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."""
|
"""Go2rtc test configuration."""
|
||||||
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
@@ -228,3 +229,15 @@ async def init_test_integration(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
return test_camera
|
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([
|
_CallList([
|
||||||
_Call(
|
_Call(
|
||||||
tuple(
|
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({
|
dict({
|
||||||
}),
|
}),
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
_CallList([
|
_CallList([
|
||||||
_Call(
|
_Call(
|
||||||
tuple(
|
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({
|
dict({
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
from unittest.mock import ANY, AsyncMock, Mock, patch
|
from unittest.mock import ANY, AsyncMock, Mock, patch
|
||||||
|
|
||||||
@@ -39,9 +40,9 @@ from homeassistant.components.go2rtc.const import (
|
|||||||
CONF_DEBUG_UI,
|
CONF_DEBUG_UI,
|
||||||
DEBUG_UI_URL_MESSAGE,
|
DEBUG_UI_URL_MESSAGE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
HA_MANAGED_UNIX_SOCKET,
|
|
||||||
RECOMMENDED_VERSION,
|
RECOMMENDED_VERSION,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.go2rtc.util import get_go2rtc_unix_socket_path
|
||||||
from homeassistant.components.stream import Orientation
|
from homeassistant.components.stream import Orientation
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
|
||||||
@@ -239,6 +240,7 @@ async def test_setup_go_binary(
|
|||||||
server_stop: Mock,
|
server_stop: Mock,
|
||||||
init_test_integration: MockCamera,
|
init_test_integration: MockCamera,
|
||||||
has_go2rtc_entry: bool,
|
has_go2rtc_entry: bool,
|
||||||
|
server_dir: Path,
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
ui_enabled: bool,
|
ui_enabled: bool,
|
||||||
expected_username: str,
|
expected_username: str,
|
||||||
@@ -255,6 +257,7 @@ async def test_setup_go_binary(
|
|||||||
enable_ui=ui_enabled,
|
enable_ui=ui_enabled,
|
||||||
username=expected_username,
|
username=expected_username,
|
||||||
password=expected_password,
|
password=expected_password,
|
||||||
|
working_dir=str(server_dir),
|
||||||
)
|
)
|
||||||
call_kwargs = server.call_args[1]
|
call_kwargs = server.call_args[1]
|
||||||
assert call_kwargs["username"] == expected_username
|
assert call_kwargs["username"] == expected_username
|
||||||
@@ -1034,7 +1037,7 @@ async def test_stream_orientation_with_generic_camera(
|
|||||||
"rest_client",
|
"rest_client",
|
||||||
"server",
|
"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."""
|
"""Test Unix socket is used for HA-managed go2rtc instances."""
|
||||||
config = {DOMAIN: {}}
|
config = {DOMAIN: {}}
|
||||||
|
|
||||||
@@ -1056,7 +1059,7 @@ async def test_unix_socket_connection(hass: HomeAssistant) -> None:
|
|||||||
assert "connector" in call_kwargs
|
assert "connector" in call_kwargs
|
||||||
connector = call_kwargs["connector"]
|
connector = call_kwargs["connector"]
|
||||||
assert isinstance(connector, UnixConnector)
|
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
|
# Auth should be auto-generated when credentials are not explicitly configured
|
||||||
assert "auth" in call_kwargs
|
assert "auth" in call_kwargs
|
||||||
auth = call_kwargs["auth"]
|
auth = call_kwargs["auth"]
|
||||||
@@ -1120,7 +1123,7 @@ async def test_basic_auth_with_custom_url(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("rest_client")
|
@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."""
|
"""Test BasicAuth session is created when username and password are provided with debug_ui."""
|
||||||
config = {
|
config = {
|
||||||
DOMAIN: {
|
DOMAIN: {
|
||||||
@@ -1158,7 +1161,7 @@ async def test_basic_auth_with_debug_ui(hass: HomeAssistant) -> None:
|
|||||||
assert "connector" in call_kwargs
|
assert "connector" in call_kwargs
|
||||||
connector = call_kwargs["connector"]
|
connector = call_kwargs["connector"]
|
||||||
assert isinstance(connector, UnixConnector)
|
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
|
assert "auth" in call_kwargs
|
||||||
auth = call_kwargs["auth"]
|
auth = call_kwargs["auth"]
|
||||||
assert isinstance(auth, BasicAuth)
|
assert isinstance(auth, BasicAuth)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||||
|
|
||||||
@@ -47,16 +48,22 @@ def server(
|
|||||||
enable_ui: bool,
|
enable_ui: bool,
|
||||||
username: str,
|
username: str,
|
||||||
password: str,
|
password: str,
|
||||||
) -> Server:
|
server_dir: Path,
|
||||||
|
) -> Generator[Server]:
|
||||||
"""Fixture to initialize the Server."""
|
"""Fixture to initialize the Server."""
|
||||||
return Server(
|
with patch(
|
||||||
hass,
|
"homeassistant.components.go2rtc.server.get_go2rtc_unix_socket_path",
|
||||||
binary=TEST_BINARY,
|
return_value="/test/path/go2rtc.sock",
|
||||||
session=mock_session,
|
):
|
||||||
enable_ui=enable_ui,
|
yield Server(
|
||||||
username=username,
|
hass,
|
||||||
password=password,
|
binary=TEST_BINARY,
|
||||||
)
|
session=mock_session,
|
||||||
|
enable_ui=enable_ui,
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
working_dir=str(server_dir),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|||||||
Reference in New Issue
Block a user