From fa1693e8c0f3e8abe6f33a5d094e77c2faa1507c Mon Sep 17 00:00:00 2001 From: pinpox Date: Mon, 18 Aug 2025 14:39:08 +0200 Subject: [PATCH] pkgs/remove-moonlight-sunshine-accept: drop Removes this package as the module has already be deprecated and removed --- pkgs/flake-module.nix | 1 - pkgs/moonlight-sunshine-accept/.envrc | 2 - pkgs/moonlight-sunshine-accept/default.nix | 37 ----- .../moonlight_sunshine_accept/__init__.py | 40 ----- .../moonlight_sunshine_accept/__main__.py | 0 .../moonlight_sunshine_accept/errors.py | 2 - .../moonlight/__init__.py | 37 ----- .../moonlight/init_certificates.py | 74 --------- .../moonlight/init_config.py | 15 -- .../moonlight/join.py | 131 --------------- .../moonlight/run.py | 63 -------- .../moonlight/state.py | 149 ------------------ .../moonlight/uri.py | 22 --- .../sunshine/__init__.py | 63 -------- .../moonlight_sunshine_accept/sunshine/api.py | 63 -------- .../sunshine/config.py | 49 ------ .../sunshine/init_certificates.py | 76 --------- .../sunshine/init_state.py | 16 -- .../sunshine/listen.py | 90 ----------- .../sunshine/state.py | 67 -------- pkgs/moonlight-sunshine-accept/pyproject.toml | 9 -- 21 files changed, 1006 deletions(-) delete mode 100644 pkgs/moonlight-sunshine-accept/.envrc delete mode 100644 pkgs/moonlight-sunshine-accept/default.nix delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/__init__.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/__main__.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/errors.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/__init__.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/init_certificates.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/init_config.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/join.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/run.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/state.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/uri.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/__init__.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/api.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/config.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/init_certificates.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/init_state.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/listen.py delete mode 100644 pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/state.py delete mode 100644 pkgs/moonlight-sunshine-accept/pyproject.toml diff --git a/pkgs/flake-module.nix b/pkgs/flake-module.nix index 3ad2b3708..823e675f9 100644 --- a/pkgs/flake-module.nix +++ b/pkgs/flake-module.nix @@ -19,7 +19,6 @@ agit = pkgs.callPackage ./agit { }; tea-create-pr = pkgs.callPackage ./tea-create-pr { }; zerotier-members = pkgs.callPackage ./zerotier-members { }; - moonlight-sunshine-accept = pkgs.callPackage ./moonlight-sunshine-accept { }; merge-after-ci = pkgs.callPackage ./merge-after-ci { inherit (config.packages) tea-create-pr; }; minifakeroot = pkgs.callPackage ./minifakeroot { }; pending-reviews = pkgs.callPackage ./pending-reviews { }; diff --git a/pkgs/moonlight-sunshine-accept/.envrc b/pkgs/moonlight-sunshine-accept/.envrc deleted file mode 100644 index 1799d252a..000000000 --- a/pkgs/moonlight-sunshine-accept/.envrc +++ /dev/null @@ -1,2 +0,0 @@ -# shellcheck shell=bash -use flake .#moonlight-sunshine-accept diff --git a/pkgs/moonlight-sunshine-accept/default.nix b/pkgs/moonlight-sunshine-accept/default.nix deleted file mode 100644 index 95a40af40..000000000 --- a/pkgs/moonlight-sunshine-accept/default.nix +++ /dev/null @@ -1,37 +0,0 @@ -{ - lib, - python3Packages, - makeDesktopItem, - copyDesktopItems, -}: -let - desktop-file = makeDesktopItem { - name = "org.clan.moonlight-sunset-accept"; - exec = "moonlight-sunshine-accept moonlight join %u"; - desktopName = "moonlight-handler"; - startupWMClass = "moonlight-handler"; - mimeTypes = [ "x-scheme-handler/moonlight" ]; - }; -in -python3Packages.buildPythonApplication { - name = "moonlight-sunshine-accept"; - - src = ./.; - - format = "pyproject"; - - propagatedBuildInputs = [ python3Packages.cryptography ]; - nativeBuildInputs = [ - python3Packages.setuptools - copyDesktopItems - ]; - - desktopItems = [ desktop-file ]; - - meta = with lib; { - description = "Moonlight Sunshine Bridge"; - license = licenses.mit; - maintainers = with maintainers; [ a-kenji ]; - mainProgram = "moonlight-sunshine-accept"; - }; -} diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/__init__.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/__init__.py deleted file mode 100644 index e05a45abf..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python - -import argparse - -from . import moonlight, sunshine - - -def main() -> None: - parser = argparse.ArgumentParser( - prog="moonlight-sunshine-accept", - description="Manage moonlight machines", - ) - subparsers = parser.add_subparsers() - - parser_sunshine = subparsers.add_parser( - "sunshine", - aliases=["sun"], - description="Sunshine configuration", - help="Sunshine configuration", - ) - sunshine.register_parser(parser_sunshine) - - parser_moonlight = subparsers.add_parser( - "moonlight", - aliases=["moon"], - description="Moonlight configuration", - help="Moonlight configuration", - ) - moonlight.register_parser(parser_moonlight) - - args = parser.parse_args() - - if not hasattr(args, "func"): - parser.print_help() - else: - args.func(args) - - -if __name__ == "__main__": - main() diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/__main__.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/__main__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/errors.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/errors.py deleted file mode 100644 index 9c69c25f8..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/errors.py +++ /dev/null @@ -1,2 +0,0 @@ -class Error(Exception): - pass diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/__init__.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/__init__.py deleted file mode 100644 index 6524a4d86..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/__init__.py +++ /dev/null @@ -1,37 +0,0 @@ -import argparse - -from .init_certificates import register_initialization_parser -from .init_config import register_config_initialization_parser -from .join import register_join_parser - - -def register_parser(parser: argparse.ArgumentParser) -> None: - subparser = parser.add_subparsers( - title="command", - description="the command to run", - help="the command to run", - required=True, - ) - - initialization_parser = subparser.add_parser( - "init", - aliases=["i"], - description="Initialize the moonlight credentials", - help="Initialize the moonlight credentials", - ) - register_initialization_parser(initialization_parser) - - config_initialization_parser = subparser.add_parser( - "init-config", - description="Initialize the moonlight configuration", - help="Initialize the moonlight configuration", - ) - register_config_initialization_parser(config_initialization_parser) - - join_parser = subparser.add_parser( - "join", - aliases=["j"], - description="Join a sunshine host", - help="Join a sunshine host", - ) - register_join_parser(join_parser) diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/init_certificates.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/init_certificates.py deleted file mode 100644 index c368e9bde..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/init_certificates.py +++ /dev/null @@ -1,74 +0,0 @@ -import argparse -import datetime -from datetime import timedelta -from pathlib import Path - -from cryptography import hazmat, x509 -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.x509.oid import NameOID - - -def generate_private_key() -> rsa.RSAPrivateKey: - private_key = rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=hazmat.backends.default_backend() - ) - return private_key - - -def generate_certificate(private_key: rsa.RSAPrivateKey) -> bytes: - subject = issuer = x509.Name( - [ - x509.NameAttribute(NameOID.COMMON_NAME, "NVIDIA GameStream Client"), - ] - ) - cert_builder = ( - x509.CertificateBuilder() - .subject_name(subject) - .issuer_name(issuer) - .public_key(private_key.public_key()) - .serial_number(x509.random_serial_number()) - .not_valid_before(datetime.datetime.now(tz=datetime.UTC)) - .not_valid_after( - datetime.datetime.now(tz=datetime.UTC) + timedelta(days=365 * 20) - ) - .add_extension( - x509.SubjectAlternativeName([x509.DNSName("localhost")]), - critical=False, - ) - .sign(private_key, hashes.SHA256(), default_backend()) - ) - pem_certificate = cert_builder.public_bytes(serialization.Encoding.PEM) - return pem_certificate - - -def private_key_to_pem(private_key: rsa.RSAPrivateKey) -> bytes: - pem_private_key = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - # format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption(), - ) - return pem_private_key - - -def init_credentials() -> tuple[bytes, bytes]: - private_key = generate_private_key() - certificate = generate_certificate(private_key) - private_key_pem = private_key_to_pem(private_key) - return certificate, private_key_pem - - -def write_credentials(_args: argparse.Namespace) -> None: - pem_certificate, pem_private_key = init_credentials() - credentials_path = Path.cwd() / "credentials" - Path(credentials_path).mkdir(parents=True, exist_ok=True) - - (credentials_path / "cacert.pem").write_bytes(pem_certificate) - (credentials_path / "cakey.pem").write_bytes(pem_private_key) - print("Finished writing moonlight credentials") - - -def register_initialization_parser(parser: argparse.ArgumentParser) -> None: - parser.set_defaults(func=write_credentials) diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/init_config.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/init_config.py deleted file mode 100644 index a2f9e3174..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/init_config.py +++ /dev/null @@ -1,15 +0,0 @@ -import argparse -from pathlib import Path - -from .state import init_state - - -def init_config(args: argparse.Namespace) -> None: - init_state(args.certificate.read_text(), args.key.read_text()) - print("Finished initializing moonlight state.") - - -def register_config_initialization_parser(parser: argparse.ArgumentParser) -> None: - parser.add_argument("--certificate", type=Path) - parser.add_argument("--key", type=Path) - parser.set_defaults(func=init_config) diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/join.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/join.py deleted file mode 100644 index 69f6af46e..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/join.py +++ /dev/null @@ -1,131 +0,0 @@ -import argparse -import base64 -import json -import socket - -from .run import MoonlightPairing -from .state import add_sunshine_host, gen_pin, get_moonlight_certificate -from .uri import parse_moonlight_uri - - -def send_join_request(host: str, port: int, cert: str) -> bool: - max_tries = 3 - response = False - for _tries in range(max_tries): - response = send_join_request_api(host, port) - if response: - return response - return bool(send_join_request_native(host, port, cert)) - - -# This is the preferred join method, but sunshines pin mechanism -# seems to be somewhat brittle in repeated testing, retry then fallback to native -def send_join_request_api(host: str, port: int) -> bool: - moonlight = MoonlightPairing() - # is_paired = moonlight.check(host) - is_paired = False - if is_paired: - print(f"Moonlight is already paired with this host: {host}") - return True - pin = gen_pin() - moonlight.init_pairing(host, pin) - moonlight.wait_until_started() - - with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: - s.connect((host, port)) - json_body = json.dumps({"type": "api", "pin": pin}) - request = ( - f"POST / HTTP/1.1\r\n" - f"Content-Type: application/json\r\n" - f"Content-Length: {len(json_body)}\r\n" - f"Connection: close\r\n\r\n" - f"{json_body}" - ) - try: - s.sendall(request.encode("utf-8")) - response = s.recv(16384).decode("utf-8") - print(response) - body = response.split("\n")[-1] - print(body) - moonlight.terminate() - except Exception as e: - print(f"An error occurred: {e}") - moonlight.terminate() - return False - else: - return True - - -def send_join_request_native(host: str, port: int, cert: str) -> bool: - # This is the hardcoded UUID for the moonlight client - uuid = "123456789ABCD" - with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: - s.connect((host, port)) - encoded_cert = base64.urlsafe_b64encode(cert.encode("utf-8")).decode("utf-8") - json_body_str = json.dumps( - {"type": "native", "uuid": uuid, "cert": encoded_cert} - ) - request = ( - f"POST / HTTP/1.1\r\n" - f"Content-Type: application/json\r\n" - f"Content-Length: {len(json_body_str)}\r\n" - f"Connection: close\r\n\r\n" - f"{json_body_str}" - ) - try: - s.sendall(request.encode("utf-8")) - response = s.recv(16384).decode("utf-8") - print(response) - lines = response.split("\n") - body = "\n".join(lines[2:])[2:] - print(body) - except Exception as e: - print(f"An error occurred: {e}") - else: - return True - # TODO: fix - try: - print(f"response: {response}") - data = json.loads(response) - print(f"Data: {data}") - print(f"Host uuid: {data['uuid']}") - print(f"Host certificate: {data['cert']}") - print("Joining sunshine host") - cert = data["cert"] - cert = base64.urlsafe_b64decode(cert).decode("utf-8") - uuid = data["uuid"] - hostname = data["hostname"] - add_sunshine_host(hostname, host, cert, uuid) - except json.JSONDecodeError as e: - print(f"Failed to decode JSON: {e}") - pos = e.pos - print(f"Failed to decode JSON: unexpected character {response[pos]}") - return False - - -def join(args: argparse.Namespace) -> None: - if args.url: - (host, port) = parse_moonlight_uri(args.url) - if port is None: - port = 48011 - else: - port = args.port - host = args.host - - print(f"Host: {host}, port: {port}") - # TODO: If cert is not provided parse from config - # cert = args.cert - cert = get_moonlight_certificate() - assert port is not None - if send_join_request(host, port, cert): - print(f"Successfully joined sunshine host: {host}") - else: - print(f"Failed to join sunshine host: {host}") - - -def register_join_parser(parser: argparse.ArgumentParser) -> None: - parser.add_argument("url", nargs="?") - parser.add_argument("--port", type=int, default=48011) - parser.add_argument("--host") - parser.add_argument("--cert") - parser.set_defaults(func=join) diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/run.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/run.py deleted file mode 100644 index ee4752ced..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/run.py +++ /dev/null @@ -1,63 +0,0 @@ -import subprocess -import sys -import threading - - -class MoonlightPairing: - def __init__(self) -> None: - self.process: subprocess.Popen | None = None - self.output = "" - self.found = threading.Event() - - def init_pairing(self, host: str, pin: str) -> bool: - # host = f"[{host}]" - args = ["moonlight", "pair", host, "--pin", pin] - print("Trying to pair") - try: - print(f"Running command: {args}") - self.process = subprocess.Popen(args, stdout=subprocess.PIPE) - print("Pairing initiated") - thread = threading.Thread( - target=self.stream_output, - args=('Latest supported GFE server: "99.99.99.99"',), - ) - thread.start() - print("Thread started") - except Exception as e: - print( - "Error occurred while starting the process: ", str(e), file=sys.stderr - ) - return False - else: - return True - - def check(self, host: str) -> bool: - try: - result = subprocess.run( - ["moonlight", "list", "localhost", host], check=True - ) - except subprocess.CalledProcessError: - return False - else: - return result.returncode == 0 - - def terminate(self) -> None: - if self.process: - self.process.terminate() - self.process.wait() - - def stream_output(self, target_string: str) -> None: - assert self.process is not None - assert self.process.stdout is not None - for line in iter(self.process.stdout.readline, b""): - line = line.decode() - self.output += line - if target_string in line: - self.found.set() - break - - def wait_until_started(self, timeout: int = 10) -> None: - if self.found.wait(timeout): - print("Started up.") - else: - print("Starting up took took too long. Terminated the process.") diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/state.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/state.py deleted file mode 100644 index 6d7803805..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/state.py +++ /dev/null @@ -1,149 +0,0 @@ -import contextlib -import random -import string -from configparser import ConfigParser, DuplicateSectionError, NoOptionError -from pathlib import Path - - -def moonlight_config_dir() -> Path: - return Path.home() / ".config" / "Moonlight Game Streaming Project" - - -def moonlight_state_file() -> Path: - return moonlight_config_dir() / "Moonlight.conf" - - -def load_state() -> ConfigParser | None: - try: - with moonlight_state_file().open() as file: - config = ConfigParser() - config.read_file(file) - print(config.sections()) - return config - except FileNotFoundError: - print("Sunshine state file not found.") - return None - - -# prepare the string for the config file -# this is how qt handles byte arrays -def convert_string_to_bytearray(data: str) -> str: - byte_array = '"@ByteArray(' - byte_array += data.replace("\n", "\\n") - byte_array += ')"' - return byte_array - - -def convert_bytearray_to_string(byte_array: str) -> str: - if byte_array.startswith('"@ByteArray(') and byte_array.endswith(')"'): - byte_array = byte_array[12:-2] - return byte_array.replace("\\n", "\n") - return byte_array - - -# this must be created before moonlight is first run -def init_state(certificate: str, key: str) -> None: - print("Initializing moonlight state.") - moonlight_config_dir().mkdir(parents=True, exist_ok=True) - print("Initialized moonlight config directory.") - - print("Writing moonlight state file.") - # write the initial bootstrap config file - with moonlight_state_file().open("w") as file: - config = ConfigParser() - # bytearray objects are not supported by ConfigParser, - # so we need to adjust them ourselves - config.add_section("General") - config.set("General", "certificate", convert_string_to_bytearray(certificate)) - config.set("General", "key", convert_string_to_bytearray(key)) - config.set("General", "latestsupportedversion-v1", "99.99.99.99") - config.add_section("gcmapping") - config.set("gcmapping", "size", "0") - - config.write(file) - - -def write_state(data: ConfigParser) -> None: - with moonlight_state_file().open("w") as file: - data.write(file) - - -def add_sunshine_host_to_parser( - config: ConfigParser, hostname: str, manual_host: str, certificate: str, uuid: str -) -> bool: - with contextlib.suppress(DuplicateSectionError): - config.add_section("hosts") - - # amount of hosts - try: - no_hosts = int(config.get("hosts", "size")) - except NoOptionError: - no_hosts = 0 - - new_host = no_hosts + 1 - - config.set( - "hosts", f"{new_host}\\srvcert", convert_string_to_bytearray(certificate) - ) - config.set("hosts", "size", str(new_host)) - config.set("hosts", f"{new_host}\\uuid", uuid) - config.set("hosts", f"{new_host}\\hostname", hostname) - config.set("hosts", f"{new_host}\\nvidiasv", "false") - config.set("hosts", f"{new_host}\\customname", "false") - config.set("hosts", f"{new_host}\\manualaddress", manual_host) - config.set("hosts", f"{new_host}\\manualport", "47989") - config.set("hosts", f"{new_host}\\remoteport", "0") - config.set("hosts", f"{new_host}\\remoteaddress", "") - config.set("hosts", f"{new_host}\\localaddress", "") - config.set("hosts", f"{new_host}\\localport", "0") - config.set("hosts", f"{new_host}\\ipv6port", "0") - config.set("hosts", f"{new_host}\\ipv6address", "") - config.set( - "hosts", f"{new_host}\\mac", convert_string_to_bytearray("\\xceop\\x8d\\xfc{") - ) - add_app(config, "Desktop", new_host, 1, 881448767) - add_app(config, "Low Res Desktop", new_host, 2, 303580669) - add_app(config, "Steam Big Picture", new_host, 3, 1093255277) - - print(config.items("hosts")) - return True - - -# set default apps for the host for now -# TODO: do this dynamically -def add_app( - config: ConfigParser, name: str, host_id: int, app_id: int, app_no: int -) -> None: - identifier = f"{host_id}\\apps\\{app_id}\\" - config.set("hosts", f"{identifier}appcollector", "false") - config.set("hosts", f"{identifier}directlaunch", "false") - config.set("hosts", f"{identifier}hdr", "false") - config.set("hosts", f"{identifier}hidden", "false") - config.set("hosts", f"{identifier}id", f"{app_no}") - config.set("hosts", f"{identifier}name", f"{name}") - - -def get_moonlight_certificate() -> str: - config = load_state() - if config is None: - msg = "Moonlight state file not found." - raise FileNotFoundError(msg) - certificate = config.get("General", "certificate") - certificate = convert_bytearray_to_string(certificate) - return certificate - - -def gen_pin() -> str: - return "".join(random.choice(string.digits) for _ in range(4)) - - -def add_sunshine_host( - hostname: str, manual_host: str, certificate: str, uuid: str -) -> bool: - config = load_state() - if config is None: - return False - hostname = "test" - add_sunshine_host_to_parser(config, hostname, manual_host, certificate, uuid) - write_state(config) - return True diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/uri.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/uri.py deleted file mode 100644 index 339cb5e37..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/uri.py +++ /dev/null @@ -1,22 +0,0 @@ -from urllib.parse import urlparse - -from moonlight_sunshine_accept.errors import Error - - -def parse_moonlight_uri(uri: str) -> tuple[str, int | None]: - print(uri) - if uri.startswith("moonlight:"): - # Fixes a bug where moonlight:// is not parsed correctly - uri = uri[10:] - uri = "moonlight://" + uri - print(uri) - parsed = urlparse(uri) - if parsed.scheme != "moonlight": - msg = f"Invalid moonlight URI: {uri}" - raise Error(msg) - hostname = parsed.hostname - if hostname is None: - msg = f"Invalid moonlight URI: {uri}" - raise Error(msg) - port = parsed.port - return (hostname, port) diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/__init__.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/__init__.py deleted file mode 100644 index 88872b0a6..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/__init__.py +++ /dev/null @@ -1,63 +0,0 @@ -import argparse - -from .init_certificates import register_initialization_parser -from .init_state import register_state_initialization_parser -from .listen import register_socket_listener - - -def register_parser(parser: argparse.ArgumentParser) -> None: - subparser = parser.add_subparsers( - title="command", - description="the command to run", - help="the command to run", - required=True, - ) - - subparser.add_parser( - "generate", - # title="command", - aliases=["gen"], - description="Generate a shareable link", - help="Generate a shareable link", - ) - # TODO: add a timeout for the link - # generate.add_argument( - # "--timeout", - # default="10", - # ) - # copy = subparsers.add_parser("copy", description="Copy the link to the clipboard") - - initialization_parser = subparser.add_parser( - "init", - aliases=["i"], - description="Initialize the sunshine credentials", - help="Initialize the sunshine credentials", - ) - register_initialization_parser(initialization_parser) - - state_initialization_parser = subparser.add_parser( - "init-state", - description="Initialize the sunshine state file", - help="Initialize the sunshine state file", - ) - register_state_initialization_parser(state_initialization_parser) - - listen_parser = subparser.add_parser( - "listen", - description="Listen for incoming connections", - help="Listen for incoming connections", - ) - register_socket_listener(listen_parser) - - # TODO: Add a machine directly <- useful when using dependent secrets - # sunshine_add = subparser.add_parser( - # "add", - # aliases=["a"], - # description="Add a new moonlight machine to sunshine", - # help="Add a new moonlight machine to sunshine", - # ) - # sunshine_add.add_argument("--url", type=str, help="URL of the moonlight machine") - # sunshine_add.add_argument( - # "--cert", type=str, help="Certificate of the moonlight machine" - # ) - # sunshine_add.add_argument("--uuid", type=str, help="UUID of the moonlight machine") diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/api.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/api.py deleted file mode 100644 index 1df45e1dc..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/api.py +++ /dev/null @@ -1,63 +0,0 @@ -import base64 -import http.client -import json - - -def get_context() -> http.client.ssl.SSLContext: # type: ignore - # context = http.client.ssl.create_default_context() - # # context.load_cert_chain( - # # certfile="/var/lib/sunshine/sunshine.cert", keyfile="/var/lib/sunshine/sunshine.key" - # # ) - # context.load_cert_chain( - # certfile="/home/kenji/.config/sunshine/credentials/cacert.pem", - # keyfile="/home/kenji/.config/sunshine/credentials/cakey.pem", - # ) - return http.client.ssl._create_unverified_context() # type: ignore # noqa: SLF001 - - -def pair(pin: str) -> str: - conn = http.client.HTTPSConnection("localhost", 47990, context=get_context()) - - # TODO: dynamic username and password - user_and_pass = base64.b64encode(b"sunshine:sunshine").decode("ascii") - headers = { - "Content-Type": "application/json", - "Authorization": f"Basic {user_and_pass}", - } - - # Define the parameters - params = json.dumps({"pin": f"{pin}"}) - - # Make the request - conn.request("POST", "/api/pin", params, headers) - - # Get and print the response - res = conn.getresponse() - data = res.read() - - print(data.decode("utf-8")) - return data.decode("utf-8") - - -def restart() -> None: - # Define the connection - conn = http.client.HTTPSConnection( - "localhost", - 47990, - context=http.client.ssl._create_unverified_context(), # type: ignore # noqa: SLF001 - ) - user_and_pass = base64.b64encode(b"sunshine:sunshine").decode("ascii") - headers = { - "Content-Type": "application/json", - "Authorization": f"Basic {user_and_pass}", - } - - # Make the request - conn.request("POST", "/api/restart", {}, headers) - - # Get and print the response - # There wont be a response, because it is restarted - res = conn.getresponse() - data = res.read() - - print(data.decode("utf-8")) diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/config.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/config.py deleted file mode 100644 index 83e79bce2..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/config.py +++ /dev/null @@ -1,49 +0,0 @@ -import configparser -from dataclasses import dataclass -from pathlib import Path - -# address_family = both -# channels = 5 -# pkey = /var/lib/sunshine/sunshine.key -# cert = /var/lib/sunshine/sunshine.cert -# file_state = /var/lib/sunshine/state.json -# credentials_file = /var/lib/sunshine/credentials.json - -PSEUDO_SECTION = "DEFAULT" - - -@dataclass -class Config: - config: configparser.ConfigParser - config_location: Path - _instance = None - - def __new__(cls, config_location: Path | None = None) -> "Config": - if cls._instance is None: - cls._instance = super().__new__(cls) - cls._instance.config = configparser.ConfigParser() - config = config_location or cls._instance.default_sunshine_config_file() - cls._instance.config_location = config - with config.open() as f: - config_string = f"[{PSEUDO_SECTION}]\n" + f.read() - print(config_string) - cls._instance.config.read_string(config_string) - return cls._instance - - def default_sunshine_config_dir(self) -> Path: - return Path.home() / ".config" / "sunshine" - - def default_sunshine_config_file(self) -> Path: - return self.default_sunshine_config_dir() / "sunshine.conf" - - def get_private_key(self) -> str: - return self.config.get(PSEUDO_SECTION, "pkey") - - def get_certificate(self) -> str: - return self.config.get(PSEUDO_SECTION, "cert") - - def get_state_file(self) -> str: - return self.config.get(PSEUDO_SECTION, "file_state") - - def get_credentials_file(self) -> str: - return self.config.get(PSEUDO_SECTION, "credentials_file") diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/init_certificates.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/init_certificates.py deleted file mode 100644 index a422482db..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/init_certificates.py +++ /dev/null @@ -1,76 +0,0 @@ -import argparse -import datetime -import uuid -from pathlib import Path - -from cryptography import hazmat, x509 -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.x509.oid import NameOID - - -def generate_private_key() -> rsa.RSAPrivateKey: - private_key = rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=hazmat.backends.default_backend() - ) - return private_key - - -def generate_certificate(private_key: rsa.RSAPrivateKey) -> bytes: - subject = issuer = x509.Name( - [ - x509.NameAttribute(NameOID.COMMON_NAME, "Sunshine Gamestream Host"), - ] - ) - cert_builder = ( - x509.CertificateBuilder() - .subject_name(subject) - .issuer_name(issuer) - .public_key(private_key.public_key()) - .serial_number(61093384576940497812448570031200738505731293357) - .not_valid_before(datetime.datetime(2024, 2, 27, tzinfo=datetime.UTC)) - .not_valid_after(datetime.datetime(2044, 2, 22, tzinfo=datetime.UTC)) - .add_extension( - x509.SubjectAlternativeName([x509.DNSName("localhost")]), - critical=False, - ) - .sign(private_key, hashes.SHA256(), default_backend()) - ) - pem_certificate = cert_builder.public_bytes(serialization.Encoding.PEM) - return pem_certificate - - -def private_key_to_pem(private_key: rsa.RSAPrivateKey) -> bytes: - pem_private_key = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption(), - ) - return pem_private_key - - -def init_credentials() -> tuple[bytes, bytes]: - private_key = generate_private_key() - certificate = generate_certificate(private_key) - private_key_pem = private_key_to_pem(private_key) - return certificate, private_key_pem - - -def uniqueid() -> str: - return str(uuid.uuid4()).upper() - - -def write_credentials(_args: argparse.Namespace) -> None: - print("Writing sunshine credentials") - pem_certificate, pem_private_key = init_credentials() - credentials_dir = Path("credentials") - credentials_dir.mkdir(parents=True, exist_ok=True) - (credentials_dir / "cacert.pem").write_bytes(pem_certificate) - (credentials_dir / "cakey.pem").write_bytes(pem_private_key) - print("Generating sunshine UUID") - Path("uuid").write_text(uniqueid()) - - -def register_initialization_parser(parser: argparse.ArgumentParser) -> None: - parser.set_defaults(func=write_credentials) diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/init_state.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/init_state.py deleted file mode 100644 index 95f2028b4..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/init_state.py +++ /dev/null @@ -1,16 +0,0 @@ -import argparse - -from .state import init_state - - -def init_state_file(args: argparse.Namespace) -> None: - uuid = args.uuid - state_file = args.state_file - init_state(uuid, state_file) - print("Finished initializing sunshine state file.") - - -def register_state_initialization_parser(parser: argparse.ArgumentParser) -> None: - parser.add_argument("--uuid") - parser.add_argument("--state-file") - parser.set_defaults(func=init_state_file) diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/listen.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/listen.py deleted file mode 100644 index 5fe24adaa..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/listen.py +++ /dev/null @@ -1,90 +0,0 @@ -import argparse -import base64 -import json -import socket -import traceback - -from .api import pair -from .state import default_sunshine_state_file - - -# listen on a specific port for information from the moonlight side -def listen(port: int, cert: str, uuid: str, state_file: str) -> bool: - host = "" - # Create a socket object with dual-stack support - server_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) - # Enable dual-stack support - server_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) - # Bind the socket to the host and port - server_socket.bind((host, port)) - # Listen for incoming connections (accept up to 5) - server_socket.listen(5) - - while True: - # Accept incoming connection - client_socket, addr = server_socket.accept() - - print(f"Connection accepted from {addr}") - - # Receive data from the client - - data = client_socket.recv(16384) - - try: - request = data.decode("utf-8") - raw_body = request.split("\n")[-1] - print(raw_body) - body = json.loads(raw_body) - - pair_type = body.get("type", "") - - if pair_type == "api": - print("Api request") - status = pair(body.get("pin", "")) - status = json.dumps(status) - response = f"HTTP/1.1 200 OK\r\nContent-Type:application/json\r\n\r\\{status}\r\n" - client_socket.sendall(response.encode("utf-8")) - - if pair_type == "native": - # url = unquote(data_str.split()[1]) - # rec_uuid = parse_qs(urlparse(url).query).get("uuid", [""])[0] - # rec_cert = parse_qs(urlparse(url).query).get("cert", [""])[0] - # decoded_cert = base64.urlsafe_b64decode(rec_cert).decode("utf-8") - # print(f"Received URL: {url}") - # print(f"Extracted UUID: {rec_uuid}") - # print(f"Extracted Cert: {decoded_cert}") - encoded_cert = base64.urlsafe_b64encode(cert.encode("utf-8")).decode( - "utf-8" - ) - json_data = {} - json_data["uuid"] = uuid - json_data["cert"] = encoded_cert - json_data["hostname"] = socket.gethostname() - json_body = json.dumps(json_data) - response = f"HTTP/1.1 200 OK\r\nContent-Type:application/json\r\n\r\\{json_body}\r\n" - client_socket.sendall(response.encode("utf-8")) - # add_moonlight_client(decoded_cert, state_file, rec_uuid) - - except UnicodeDecodeError: - print(f"UnicodeDecodeError: Cannot decode byte {data[8]}") - traceback.print_exc() - - client_socket.close() - - -def init_listener(args: argparse.Namespace) -> None: - port = args.port - cert = args.cert - uuid = args.uuid - state = args.state - listen(port, cert, uuid, state) - - -def register_socket_listener(parser: argparse.ArgumentParser) -> None: - parser.add_argument("--port", default=48011, type=int) - parser.add_argument("--cert") - parser.add_argument("--uuid") - parser.add_argument("--state", default=default_sunshine_state_file()) - # TODO: auto accept - # parser.add_argument("--auto-accept") - parser.set_defaults(func=init_listener) diff --git a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/state.py b/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/state.py deleted file mode 100644 index 0f96d755b..000000000 --- a/pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/state.py +++ /dev/null @@ -1,67 +0,0 @@ -import json -from pathlib import Path -from typing import Any - - -def default_sunshine_config_dir() -> Path: - return Path.home() / ".config" / "sunshine" - - -def default_sunshine_state_file() -> Path: - return default_sunshine_config_dir() / "sunshine_state.json" - - -def load_state(sunshine_state_path: Path) -> str | None: - sunshine_state_path = sunshine_state_path or default_sunshine_state_file() - print(f"Loading sunshine state from {sunshine_state_path}") - try: - return json.loads(sunshine_state_path.read_text()) - except FileNotFoundError: - print("Sunshine state file not found.") - return None - - -# this needs to be created before sunshine is first run -def init_state(uuid: str, sunshine_state_path: Path) -> None: - print("Initializing sunshine state.") - - data: dict[str, Any] = {} - data["root"] = {} - data["root"]["uniqueid"] = uuid - data["root"]["devices"] = [] - - # write the initial bootstrap config file - write_state(data, sunshine_state_path) - - -def write_state(data: dict[str, Any], sunshine_state_path: Path) -> None: - sunshine_state_path = sunshine_state_path or default_sunshine_state_file() - with sunshine_state_path.open("w") as file: - json.dump(data, file, indent=4) - - -# this is used by moonlight-qt -def pseudo_uuid() -> str: - return "0123456789ABCDEF" - - -# TODO: finish this function -def add_moonlight_client( - certificate: str, sunshine_state_path: Path, uuid: str -) -> None: - print("Adding moonlight client to sunshine state.") - raw_state = load_state(sunshine_state_path) - if raw_state: - state = json.loads(raw_state) - - if not state["root"]["devices"]: - state["root"]["devices"].append( - {"uniqueid": pseudo_uuid(), "certs": [certificate]} - ) - write_state(state, sunshine_state_path) - if certificate not in state["root"]["devices"][0]["certs"]: - state["root"]["devices"][0]["certs"].append(certificate) - state["root"]["devices"][0]["uniqueid"] = pseudo_uuid() - write_state(state, sunshine_state_path) - else: - print("Moonlight certificate already added.") diff --git a/pkgs/moonlight-sunshine-accept/pyproject.toml b/pkgs/moonlight-sunshine-accept/pyproject.toml deleted file mode 100644 index 43a982145..000000000 --- a/pkgs/moonlight-sunshine-accept/pyproject.toml +++ /dev/null @@ -1,9 +0,0 @@ -[build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" - -[project] -name = "moonlight-sunshine-accept" -description = "Moonlight Sunshine Bridge" -dynamic = ["version"] -scripts = { moonlight-sunshine-accept = "moonlight_sunshine_accept:main" }