diff options
Diffstat (limited to 'kvmd')
-rw-r--r-- | kvmd/apps/__init__.py | 19 | ||||
-rw-r--r-- | kvmd/apps/pst/__init__.py | 46 | ||||
-rw-r--r-- | kvmd/apps/pst/__main__.py | 24 | ||||
-rw-r--r-- | kvmd/apps/pst/server.py | 126 |
4 files changed, 215 insertions, 0 deletions
diff --git a/kvmd/apps/__init__.py b/kvmd/apps/__init__.py index a13bdeb0..89907437 100644 --- a/kvmd/apps/__init__.py +++ b/kvmd/apps/__init__.py @@ -485,6 +485,25 @@ def _get_config_scheme() -> Dict: }, }, + "pst": { + "server": { + "unix": Option("/run/kvmd/pst.sock", type=valid_abs_path, unpack_as="unix_path"), + "unix_rm": Option(True, type=valid_bool), + "unix_mode": Option(0o660, type=valid_unix_mode), + "heartbeat": Option(15.0, type=valid_float_f01), + "access_log_format": Option("[%P / %{X-Real-IP}i] '%r' => %s; size=%b ---" + " referer='%{Referer}i'; user_agent='%{User-Agent}i'"), + }, + + "storage": Option("/var/lib/kvmd/pst", type=valid_abs_dir, unpack_as="storage_path"), + "ro_retries_delay": Option(10.0, type=valid_float_f01), + + "remount_cmd": Option([ + "/usr/bin/sudo", "--non-interactive", + "/usr/bin/kvmd-helper-pst-remount", "{mode}", + ], type=valid_command), + }, + "otg": { "vendor_id": Option(0x1D6B, type=valid_otg_id), # Linux Foundation "product_id": Option(0x0104, type=valid_otg_id), # Multifunction Composite Gadget diff --git a/kvmd/apps/pst/__init__.py b/kvmd/apps/pst/__init__.py new file mode 100644 index 00000000..2bb29a08 --- /dev/null +++ b/kvmd/apps/pst/__init__.py @@ -0,0 +1,46 @@ +# ========================================================================== # +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2022 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +# ========================================================================== # + + +from typing import List +from typing import Optional + +from ...logging import get_logger + +from .. import init + +from .server import PstServer + + +# ===== +def main(argv: Optional[List[str]]=None) -> None: + config = init( + prog="kvmd-pst", + description="The KVMD persistent storage manager", + argv=argv, + check_run=True, + )[2] + + PstServer( + **config.pst._unpack(ignore="server"), + ).run(**config.pst.server._unpack()) + + get_logger(0).info("Bye-bye") diff --git a/kvmd/apps/pst/__main__.py b/kvmd/apps/pst/__main__.py new file mode 100644 index 00000000..3849d1b9 --- /dev/null +++ b/kvmd/apps/pst/__main__.py @@ -0,0 +1,24 @@ +# ========================================================================== # +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2022 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +# ========================================================================== # + + +from . import main +main() diff --git a/kvmd/apps/pst/server.py b/kvmd/apps/pst/server.py new file mode 100644 index 00000000..84a62bda --- /dev/null +++ b/kvmd/apps/pst/server.py @@ -0,0 +1,126 @@ +# ========================================================================== # +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2022 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +# ========================================================================== # + + +import os +import asyncio + +from typing import List +from typing import Dict + +from aiohttp.web import Request +from aiohttp.web import WebSocketResponse + +from ...logging import get_logger + +from ... import aiotools +from ... import aiohelpers + +from ...htserver import exposed_http +from ...htserver import exposed_ws +from ...htserver import WsSession +from ...htserver import HttpServer + + +# ===== +class PstServer(HttpServer): # pylint: disable=too-many-arguments,too-many-instance-attributes + def __init__( # pylint: disable=too-many-arguments,too-many-locals + self, + storage_path: str, + ro_retries_delay: float, + remount_cmd: List[str], + ) -> None: + + super().__init__() + + self.__data_path = os.path.join(storage_path, "data") + self.__ro_retries_delay = ro_retries_delay + self.__remount_cmd = remount_cmd + + self.__notifier = aiotools.AioNotifier() + + # ===== WEBSOCKET + + @exposed_http("GET", "/ws") + async def __ws_handler(self, request: Request) -> WebSocketResponse: + async with self._ws_session(request) as ws: + await ws.send_event("loop", {}) + return (await self._ws_loop(ws)) + + @exposed_ws("ping") + async def __ws_ping_handler(self, ws: WsSession, _: Dict) -> None: + await ws.send_event("pong", {}) + + # ===== SYSTEM STUFF + + async def _init_app(self) -> None: + if (await self.__remount_storage(True)): + await self.__remount_storage(False) + aiotools.create_deadly_task("Controller", self.__controller()) + self._add_exposed(self) + + async def _on_shutdown(self) -> None: + logger = get_logger(0) + logger.info("Stopping system tasks ...") + await aiotools.stop_all_deadly_tasks() + logger.info("Disconnecting clients ...") + await self.__broadcast_storage_state(False) + await self._close_all_wss() + logger.info("On-Shutdown complete") + + async def _on_cleanup(self) -> None: + logger = get_logger(0) + await self.__remount_storage(False) + logger.info("On-Cleanup complete") + + async def _on_ws_opened(self) -> None: + await self.__notifier.notify() + + async def _on_ws_closed(self) -> None: + await self.__notifier.notify() + + # ===== SYSTEM TASKS + + async def __controller(self) -> None: + prev = False + while True: + cur = self.__has_clients() + if not prev and cur: + await self.__broadcast_storage_state(await self.__remount_storage(True)) + elif prev and not cur: + while not (await self.__remount_storage(False)): + if self.__has_clients(): + continue + await asyncio.sleep(self.__ro_retries_delay) + prev = cur + await self.__notifier.wait() + + def __has_clients(self) -> bool: + return bool(self._get_wss()) + + async def __broadcast_storage_state(self, write_allowed: bool) -> None: + await self._broadcast_ws_event("storage_state", { + "data": {"path": self.__data_path}, + "write_allowed": write_allowed, + }) + + async def __remount_storage(self, rw: bool) -> bool: + return (await aiohelpers.remount("PST", self.__remount_cmd, rw)) |