__main__
Process entry point for the Selkies streaming server.
Builds the centralized stream server, registers the WebRTC and WebSockets
services, switches to the configured mode, and runs the asyncio loop until a
signal or fatal error unwinds it. Signal handling routes SIGTERM/SIGHUP
through main-task cancellation so a service-manager stop tears down the same
way Ctrl-C does. selkies --version prints the package version and exits.
attributelogger= logging.getLogger(__name__)funcwait_for_app_ready(ready_file, app_wait_ready=False) -> NoneWait for the streaming app's ready signal.
Returns immediately unless app_wait_ready is set, else polls until a
sidecar creates ready_file.
Source Code
async def wait_for_app_ready(ready_file: str, app_wait_ready: bool = False) -> None:
"""Wait for the streaming app's ready signal.
Returns immediately unless `app_wait_ready` is set, else polls until a
sidecar creates `ready_file`.
"""
if app_wait_ready:
logger.info(f"Waiting for streaming app ready file: {ready_file}")
while app_wait_ready and not os.path.exists(ready_file):
await asyncio.sleep(0.2)paramready_filestrparamapp_wait_readybool= FalseReturns
Nonefunc_install_shutdown_signal_handlers() -> NoneMake a service-manager stop (systemd, docker stop, kill) unwind the same
way Ctrl-C does: cancelling the main task raises CancelledError through the
server loop, so the streaming service is stopped, the unix socket is removed and
the disconnect hooks run. Without this SIGTERM is fatal by default, and as
container PID 1 it is ignored outright until SIGKILL.
The first signal wins: later ones are absorbed while the teardown runs, since cancelling the main task again would raise CancelledError at an await inside the cleanup path and leave the rest of it (listener shutdown, unix-socket removal) undone. The handlers stay installed so an impatient orchestrator's repeat SIGTERM cannot fall through to the default fatal disposition either.
Source Code
def _install_shutdown_signal_handlers() -> None:
"""Make a service-manager stop (systemd, `docker stop`, `kill`) unwind the same
way Ctrl-C does: cancelling the main task raises CancelledError through the
server loop, so the streaming service is stopped, the unix socket is removed and
the disconnect hooks run. Without this SIGTERM is fatal by default, and as
container PID 1 it is ignored outright until SIGKILL.
The first signal wins: later ones are absorbed while the teardown runs, since
cancelling the main task again would raise CancelledError at an await inside
the cleanup path and leave the rest of it (listener shutdown, unix-socket
removal) undone. The handlers stay installed so an impatient orchestrator's
repeat SIGTERM cannot fall through to the default fatal disposition either.
"""
loop = asyncio.get_running_loop()
main_task = asyncio.current_task()
if main_task is None:
return
shutting_down = False
def _request_shutdown(signal_name: str) -> None:
nonlocal shutting_down
if shutting_down:
logger.info("Ignoring %s: shutdown already in progress", signal_name)
return
shutting_down = True
logger.info("Received %s, shutting down", signal_name)
main_task.cancel()
for signal_name in ("SIGTERM", "SIGHUP"):
sig = getattr(signal, signal_name, None)
if sig is None:
continue
try:
loop.add_signal_handler(sig, _request_shutdown, signal_name)
except (NotImplementedError, RuntimeError, ValueError):
logger.debug("Cannot install a %s handler on this platform", signal_name)Returns
Nonefuncrun() -> NoneBuild the stream server, register its services, and run until cancelled.
Publishes the resolved gamepad and webcam socket directories to the
environment first, so the LD_PRELOAD interposers in app processes (which
read SELKIES_JS_SOCKET_PATH and SELKIES_WEBCAM_SOCKET_PATH) use the
same directories selkies does however the settings were supplied. The
virtual webcam outlives mode switches (applications hold /dev/videoN
open across them), so it is released only when the server exits.
Source Code
async def run() -> None:
"""Build the stream server, register its services, and run until cancelled.
Publishes the resolved gamepad and webcam socket directories to the
environment first, so the LD_PRELOAD interposers in app processes (which
read `SELKIES_JS_SOCKET_PATH` and `SELKIES_WEBCAM_SOCKET_PATH`) use the
same directories selkies does however the settings were supplied. The
virtual webcam outlives mode switches (applications hold `/dev/videoN`
open across them), so it is released only when the server exits.
"""
_install_shutdown_signal_handlers()
os.environ["SELKIES_JS_SOCKET_PATH"] = settings.js_socket_path
os.environ["SELKIES_WEBCAM_SOCKET_PATH"] = settings.webcam_socket_path
if settings.computer_use_bind:
try:
from pixelflux import start_computer_use
start_computer_use(settings.computer_use_bind)
except Exception as e:
logger.warning(f"Computer-Use server not started: {e}")
await wait_for_app_ready(settings.app_ready_file, settings.app_wait_ready[0])
server = CentralizedStreamServer(settings)
server.register_service("webrtc", WebRTCService(server))
server.register_service("websockets", DataStreamingServer(server))
logger.info(f"Initiating server with {settings.mode} mode")
await server.switch_to_mode(settings.mode)
try:
await server.run()
finally:
await stop_shared_webcam()Returns
Nonefuncmain() -> NoneEntry point for command-line execution.
Runs under uvloop when installed, else the stock loop: uvloop makes the
whole loop (timers, callbacks, socket I/O) markedly faster, which lifts
the pure-Python WebRTC SCTP data-channel throughput and keeps large
transfers from stalling input. uvloop.run owns how its loop is
installed per interpreter, so no event-loop policy API (removed in
Python 3.16) is touched here.
Source Code
def main() -> None:
"""Entry point for command-line execution.
Runs under uvloop when installed, else the stock loop: uvloop makes the
whole loop (timers, callbacks, socket I/O) markedly faster, which lifts
the pure-Python WebRTC SCTP data-channel throughput and keeps large
transfers from stalling input. `uvloop.run` owns how its loop is
installed per interpreter, so no event-loop policy API (removed in
Python 3.16) is touched here.
"""
try:
import uvloop
runner = uvloop.run
except ImportError:
runner = asyncio.run
try:
runner(run())
except KeyboardInterrupt:
logger.info("Server stopped by user")
except asyncio.CancelledError:
logger.info("Server stopped by signal")
except Exception as e:
logger.error(f"Error in main: {e}", exc_info=True)
sys.exit(1)Returns
None