diff --git a/bin/rpyc_classic.py b/bin/rpyc_classic.py index 8f7d06d6..824a6159 100755 --- a/bin/rpyc_classic.py +++ b/bin/rpyc_classic.py @@ -1,132 +1,6 @@ #!/usr/bin/env python -""" -classic rpyc server (threaded, forking or std) running a SlaveService -usage: - rpyc_classic.py # default settings - rpyc_classic.py -m forking -p 12345 # custom settings - - # ssl-authenticated server (keyfile and certfile are required) - rpyc_classic.py --ssl-keyfile keyfile.pem --ssl-certfile certfile.pem --ssl-cafile cafile.pem -""" -import sys -import os -import rpyc -from plumbum import cli -from rpyc.utils.server import ThreadedServer, ForkingServer, OneShotServer -from rpyc.utils.classic import DEFAULT_SERVER_PORT, DEFAULT_SERVER_SSL_PORT -from rpyc.utils.registry import REGISTRY_PORT -from rpyc.utils.registry import UDPRegistryClient, TCPRegistryClient -from rpyc.utils.authenticators import SSLAuthenticator -from rpyc.lib import setup_logger -from rpyc.core import SlaveService - - -class ClassicServer(cli.Application): - mode = cli.SwitchAttr(["-m", "--mode"], cli.Set("threaded", "forking", "stdio", "oneshot"), - default="threaded", help="The serving mode (threaded, forking, or 'stdio' for " - "inetd, etc.)") - - port = cli.SwitchAttr(["-p", "--port"], cli.Range(0, 65535), default=None, - help="The TCP listener port (" - "default = {DEFAULT_SERVER_PORT!r}, " - "default for SSL = {DEFAULT_SERVER_SSL_PORT!r})", - group="Socket Options") - host = cli.SwitchAttr(["--host"], str, default="", help="The host to bind to. " - "The default is localhost", group="Socket Options") - ipv6 = cli.Flag(["--ipv6"], help="Enable IPv6", group="Socket Options") - - logfile = cli.SwitchAttr("--logfile", str, default=None, help="Specify the log file to use; " - "the default is stderr", group="Logging") - quiet = cli.Flag(["-q", "--quiet"], help="Quiet mode (only errors will be logged)", - group="Logging") - - ssl_keyfile = cli.SwitchAttr("--ssl-keyfile", cli.ExistingFile, - help="The keyfile to use for SSL. Required for SSL", group="SSL", - requires=["--ssl-certfile"]) - ssl_certfile = cli.SwitchAttr("--ssl-certfile", cli.ExistingFile, - help="The certificate file to use for SSL. Required for SSL", group="SSL", - requires=["--ssl-keyfile"]) - ssl_cafile = cli.SwitchAttr("--ssl-cafile", cli.ExistingFile, - help="The certificate authority chain file to use for SSL. " - "Optional; enables client-side authentication", - group="SSL", requires=["--ssl-keyfile"]) - - auto_register = cli.Flag("--register", help="Asks the server to attempt registering with " - "a registry server. By default, the server will not attempt to register", - group="Registry") - registry_type = cli.SwitchAttr("--registry-type", cli.Set("UDP", "TCP"), - default="UDP", help="Specify a UDP or TCP registry", group="Registry") - registry_port = cli.SwitchAttr("--registry-port", cli.Range(0, 65535), default=REGISTRY_PORT, - help="The registry's UDP/TCP port", group="Registry") - registry_host = cli.SwitchAttr("--registry-host", str, default=None, - help="The registry host machine. For UDP, the default is 255.255.255.255; " - "for TCP, a value is required", group="Registry") - - def main(self): - if not self.host: - self.host = "::1" if self.ipv6 else "127.0.0.1" - - if self.registry_type == "UDP": - if self.registry_host is None: - self.registry_host = "255.255.255.255" - self.registrar = UDPRegistryClient(ip=self.registry_host, port=self.registry_port) - else: - if self.registry_host is None: - raise ValueError("With TCP registry, you must specify --registry-host") - self.registrar = TCPRegistryClient(ip=self.registry_host, port=self.registry_port) - - if self.ssl_keyfile: - self.authenticator = SSLAuthenticator(self.ssl_keyfile, self.ssl_certfile, - self.ssl_cafile) - default_port = DEFAULT_SERVER_SSL_PORT - else: - self.authenticator = None - default_port = DEFAULT_SERVER_PORT - if self.port is None: - self.port = default_port - - setup_logger(self.quiet, self.logfile) - - if self.mode == "threaded": - self._serve_mode(ThreadedServer) - elif self.mode == "forking": - self._serve_mode(ForkingServer) - elif self.mode == "oneshot": - self._serve_oneshot() - elif self.mode == "stdio": - self._serve_stdio() - - def _serve_mode(self, factory): - t = factory(SlaveService, hostname=self.host, port=self.port, - reuse_addr=True, ipv6=self.ipv6, authenticator=self.authenticator, - registrar=self.registrar, auto_register=self.auto_register) - t.start() - - def _serve_oneshot(self): - t = OneShotServer(SlaveService, hostname=self.host, port=self.port, - reuse_addr=True, ipv6=self.ipv6, authenticator=self.authenticator, - registrar=self.registrar, auto_register=self.auto_register) - t._listen() - sys.stdout.write("rpyc-oneshot\n") - sys.stdout.write(f"{t.host}\t{t.port}\n") - sys.stdout.flush() - t.start() - - def _serve_stdio(self): - origstdin = sys.stdin - origstdout = sys.stdout - sys.stdin = open(os.devnull, "r") - sys.stdout = open(os.devnull, "w") - sys.stderr = open(os.devnull, "w") - conn = rpyc.classic.connect_pipes(origstdin, origstdout) - try: - try: - conn.serve_all() - except KeyboardInterrupt: - print("User interrupt!") - finally: - conn.close() +from rpyc.cli.rpyc_classic import * if __name__ == "__main__": - ClassicServer.run() + main() diff --git a/bin/rpyc_registry.py b/bin/rpyc_registry.py old mode 100755 new mode 100644 index 410e5220..d8dc6cca --- a/bin/rpyc_registry.py +++ b/bin/rpyc_registry.py @@ -1,45 +1,6 @@ #!/usr/bin/env python -""" -The registry server listens to broadcasts on UDP port 18812, answering to -discovery queries by clients and registering keepalives from all running -servers. In order for clients to use discovery, a registry service must -be running somewhere on their local network. -""" -from plumbum import cli -from rpyc.utils.registry import REGISTRY_PORT, DEFAULT_PRUNING_TIMEOUT -from rpyc.utils.registry import UDPRegistryServer, TCPRegistryServer -from rpyc.lib import setup_logger - - -class RegistryServer(cli.Application): - mode = cli.SwitchAttr(["-m", "--mode"], cli.Set("UDP", "TCP"), default="UDP", - help="Serving mode") - - ipv6 = cli.Flag(["-6", "--ipv6"], help="use ipv6 instead of ipv4") - - port = cli.SwitchAttr(["-p", "--port"], cli.Range(0, 65535), default=REGISTRY_PORT, - help="The UDP/TCP listener port") - - logfile = cli.SwitchAttr(["--logfile"], str, default=None, - help="The log file to use; the default is stderr") - - quiet = cli.SwitchAttr(["-q", "--quiet"], help="Quiet mode (only errors are logged)") - - pruning_timeout = cli.SwitchAttr(["-t", "--timeout"], int, - default=DEFAULT_PRUNING_TIMEOUT, help="Set a custom pruning timeout (in seconds)") - - allow_listing = cli.SwitchAttr(["-l", "--listing"], bool, default=False, help="Enable/disable listing on registry") - - def main(self): - if self.mode.upper() == "UDP": - server = UDPRegistryServer(host='::' if self.ipv6 else '0.0.0.0', port=self.port, - pruning_timeout=self.pruning_timeout, allow_listing=self.allow_listing) - elif self.mode.upper() == "TCP": - server = TCPRegistryServer(port=self.port, pruning_timeout=self.pruning_timeout, - allow_listing=self.allow_listing) - setup_logger(self.quiet, self.logfile) - server.start() +from rpyc.cli.rpyc_registry import * if __name__ == "__main__": - RegistryServer.run() + main() diff --git a/bin/rpycd.py b/bin/rpycd.py deleted file mode 100755 index 8f9d997b..00000000 --- a/bin/rpycd.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python -from __future__ import with_statement -import daemon -from lockfile.pidlockfile import PIDLockFile -import sys -import signal -import os -from rpyc.utils.server import ThreadedServer, ForkingServer -from rpyc.core.service import SlaveService -from rpyc.lib import setup_logger -try: - from configparser import ConfigParser -except ImportError: - from ConfigParser import ConfigParser - -server = None -cur_dir = os.getcwd() -bin_dir = os.path.dirname(__file__) -DEFAULTS = { - "rpycd": { - "mode": "threaded", - "host": "127.0.0.1", - "port": 18812, - "quiet": "True", - "logfile": "rpycd.log" - } -} - - -def start(): - global server - - conf = ConfigParser() - conf.read_dict(DEFAULTS) - conf.read([ - os.path.join(cur_dir, 'rpycd.conf'), - os.path.join(bin_dir, 'rpycd.conf'), # later files trump earlier ones - os.environ.get("RPYC_DAEMON_CONF", "rpycd.conf") - ]) - - mode = conf.get("rpycd", "mode").lower() - if mode == "threaded": - factory = ThreadedServer - elif mode == "forking": - factory = ForkingServer - else: - raise ValueError(f"Invalid mode {mode!r}") - - quiet = conf.getboolean("rpycd", "quiet") - logfile = os.path.join(cur_dir, conf.get("rpycd", "logfile")) - setup_logger(quiet, logfile) - - server = factory(SlaveService, hostname=conf.get("rpycd", "host"), - port=conf.getint("rpycd", "port"), reuse_addr=True) - server.start() - server.serve_all() - - -def reload(*args): - server.close() - start() - - -def stop(*args): - server.close() - sys.exit() - - -def main(): - pid_file = os.path.join(cur_dir, 'rpycd.pid') - with daemon.DaemonContext( - pidfile=PIDLockFile(pid_file), - signal_map={signal.SIGTERM: stop, signal.SIGHUP: reload}): - start() diff --git a/docs/docs/rpyc-release-process.rst b/docs/docs/rpyc-release-process.rst index b52ed999..e901f28b 100644 --- a/docs/docs/rpyc-release-process.rst +++ b/docs/docs/rpyc-release-process.rst @@ -6,7 +6,7 @@ A walkthrough of doing a RPyC Release. 1. Ensure a clean and current build environment (i.e., ``git pull; git status``) 2. Describe commit history within ``CHANGELOG.rst`` (see `Generate Entry`_) 3. Update ``release_date`` in ``rpyc/version.py`` and bump version (`Semantic Versioning`_ and `Versioning using Hatch`_) -4. Verify changes and run ``git add .``, ``git push``, and ``export ver=$(python -c 'import rpyc; print(rpyc.__version__)')``. +4. Verify changes and run ``export ver=$(python -c 'import rpyc; print(rpyc.__version__)')``, ``git add .``, and ``git push``. 5. Create an Annotated tag: ``git tag -a ${ver} -m "Updated CHANGELOG.rst and version for release ${ver}"`` 6. Publish release tag: ``git push origin ${ver}`` 7. Install hatch: ``pyenv exec pip install hatch`` diff --git a/pyproject.toml b/pyproject.toml index 1126e2bb..cd77050d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,13 +46,16 @@ dynamic = [ Homepage = "https://rpyc.readthedocs.org" Source = "https://github.com/tomerfiliba-org/rpyc" +[project.scripts] +rpyc_classic = "rpyc.cli.rpyc_classic:main" +rpyc_registry = "rpyc.cli.rpyc_registry:main" + [tool.hatch.version] path = "rpyc/version.py" [tool.hatch.build.targets.sdist] -only-include = [ - "/rpyc", - "/bin", -] +only-include = ["rpyc"] + + [tool.hatch.build.targets.wheel] -packages = ["rpyc"] +only-include = ["rpyc"] diff --git a/rpyc/__init__.py b/rpyc/__init__.py index bdb19a82..bad2d850 100644 --- a/rpyc/__init__.py +++ b/rpyc/__init__.py @@ -54,6 +54,7 @@ from rpyc.lib import setup_logger, spawn from rpyc.utils.server import OneShotServer, ThreadedServer, ThreadPoolServer, ForkingServer +from rpyc import cli __author__ = "Tomer Filiba (tomerfiliba@gmail.com)" diff --git a/rpyc/cli/__init__.py b/rpyc/cli/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rpyc/cli/rpyc_classic.py b/rpyc/cli/rpyc_classic.py new file mode 100755 index 00000000..64335c61 --- /dev/null +++ b/rpyc/cli/rpyc_classic.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python +""" +classic rpyc server (threaded, forking or std) running a SlaveService +usage: + rpyc_classic.py # default settings + rpyc_classic.py -m forking -p 12345 # custom settings + + # ssl-authenticated server (keyfile and certfile are required) + rpyc_classic.py --ssl-keyfile keyfile.pem --ssl-certfile certfile.pem --ssl-cafile cafile.pem +""" +import sys +import os +import rpyc +from plumbum import cli +from rpyc.utils.server import ThreadedServer, ForkingServer, OneShotServer +from rpyc.utils.classic import DEFAULT_SERVER_PORT, DEFAULT_SERVER_SSL_PORT +from rpyc.utils.registry import REGISTRY_PORT +from rpyc.utils.registry import UDPRegistryClient, TCPRegistryClient +from rpyc.utils.authenticators import SSLAuthenticator +from rpyc.lib import setup_logger +from rpyc.core import SlaveService + + +class ClassicServer(cli.Application): + mode = cli.SwitchAttr(["-m", "--mode"], cli.Set("threaded", "forking", "stdio", "oneshot"), + default="threaded", help="The serving mode (threaded, forking, or 'stdio' for " + "inetd, etc.)") + + port = cli.SwitchAttr(["-p", "--port"], cli.Range(0, 65535), default=None, + help="The TCP listener port (" + "default = {DEFAULT_SERVER_PORT!r}, " + "default for SSL = {DEFAULT_SERVER_SSL_PORT!r})", + group="Socket Options") + host = cli.SwitchAttr(["--host"], str, default="", help="The host to bind to. " + "The default is localhost", group="Socket Options") + ipv6 = cli.Flag(["--ipv6"], help="Enable IPv6", group="Socket Options") + + logfile = cli.SwitchAttr("--logfile", str, default=None, help="Specify the log file to use; " + "the default is stderr", group="Logging") + quiet = cli.Flag(["-q", "--quiet"], help="Quiet mode (only errors will be logged)", + group="Logging") + + ssl_keyfile = cli.SwitchAttr("--ssl-keyfile", cli.ExistingFile, + help="The keyfile to use for SSL. Required for SSL", group="SSL", + requires=["--ssl-certfile"]) + ssl_certfile = cli.SwitchAttr("--ssl-certfile", cli.ExistingFile, + help="The certificate file to use for SSL. Required for SSL", group="SSL", + requires=["--ssl-keyfile"]) + ssl_cafile = cli.SwitchAttr("--ssl-cafile", cli.ExistingFile, + help="The certificate authority chain file to use for SSL. " + "Optional; enables client-side authentication", + group="SSL", requires=["--ssl-keyfile"]) + + auto_register = cli.Flag("--register", help="Asks the server to attempt registering with " + "a registry server. By default, the server will not attempt to register", + group="Registry") + registry_type = cli.SwitchAttr("--registry-type", cli.Set("UDP", "TCP"), + default="UDP", help="Specify a UDP or TCP registry", group="Registry") + registry_port = cli.SwitchAttr("--registry-port", cli.Range(0, 65535), default=REGISTRY_PORT, + help="The registry's UDP/TCP port", group="Registry") + registry_host = cli.SwitchAttr("--registry-host", str, default=None, + help="The registry host machine. For UDP, the default is 255.255.255.255; " + "for TCP, a value is required", group="Registry") + + def main(self): + if not self.host: + self.host = "::1" if self.ipv6 else "127.0.0.1" + + if self.registry_type == "UDP": + if self.registry_host is None: + self.registry_host = "255.255.255.255" + self.registrar = UDPRegistryClient(ip=self.registry_host, port=self.registry_port) + else: + if self.registry_host is None: + raise ValueError("With TCP registry, you must specify --registry-host") + self.registrar = TCPRegistryClient(ip=self.registry_host, port=self.registry_port) + + if self.ssl_keyfile: + self.authenticator = SSLAuthenticator(self.ssl_keyfile, self.ssl_certfile, + self.ssl_cafile) + default_port = DEFAULT_SERVER_SSL_PORT + else: + self.authenticator = None + default_port = DEFAULT_SERVER_PORT + if self.port is None: + self.port = default_port + + setup_logger(self.quiet, self.logfile) + + if self.mode == "threaded": + self._serve_mode(ThreadedServer) + elif self.mode == "forking": + self._serve_mode(ForkingServer) + elif self.mode == "oneshot": + self._serve_oneshot() + elif self.mode == "stdio": + self._serve_stdio() + + def _serve_mode(self, factory): + t = factory(SlaveService, hostname=self.host, port=self.port, + reuse_addr=True, ipv6=self.ipv6, authenticator=self.authenticator, + registrar=self.registrar, auto_register=self.auto_register) + t.start() + + def _serve_oneshot(self): + t = OneShotServer(SlaveService, hostname=self.host, port=self.port, + reuse_addr=True, ipv6=self.ipv6, authenticator=self.authenticator, + registrar=self.registrar, auto_register=self.auto_register) + t._listen() + sys.stdout.write("rpyc-oneshot\n") + sys.stdout.write(f"{t.host}\t{t.port}\n") + sys.stdout.flush() + t.start() + + def _serve_stdio(self): + origstdin = sys.stdin + origstdout = sys.stdout + sys.stdin = open(os.devnull, "r") + sys.stdout = open(os.devnull, "w") + sys.stderr = open(os.devnull, "w") + conn = rpyc.classic.connect_pipes(origstdin, origstdout) + try: + try: + conn.serve_all() + except KeyboardInterrupt: + print("User interrupt!") + finally: + conn.close() + + +def main(): + ClassicServer.run() diff --git a/rpyc/cli/rpyc_registry.py b/rpyc/cli/rpyc_registry.py new file mode 100755 index 00000000..81ff4156 --- /dev/null +++ b/rpyc/cli/rpyc_registry.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +""" +The registry server listens to broadcasts on UDP port 18812, answering to +discovery queries by clients and registering keepalives from all running +servers. In order for clients to use discovery, a registry service must +be running somewhere on their local network. +""" +from plumbum import cli +from rpyc.utils.registry import REGISTRY_PORT, DEFAULT_PRUNING_TIMEOUT +from rpyc.utils.registry import UDPRegistryServer, TCPRegistryServer +from rpyc.lib import setup_logger + + +class RegistryServer(cli.Application): + mode = cli.SwitchAttr(["-m", "--mode"], cli.Set("UDP", "TCP"), default="UDP", + help="Serving mode") + + ipv6 = cli.Flag(["-6", "--ipv6"], help="use ipv6 instead of ipv4") + + port = cli.SwitchAttr(["-p", "--port"], cli.Range(0, 65535), default=REGISTRY_PORT, + help="The UDP/TCP listener port") + + logfile = cli.SwitchAttr(["--logfile"], str, default=None, + help="The log file to use; the default is stderr") + + quiet = cli.SwitchAttr(["-q", "--quiet"], help="Quiet mode (only errors are logged)") + + pruning_timeout = cli.SwitchAttr(["-t", "--timeout"], int, + default=DEFAULT_PRUNING_TIMEOUT, help="Set a custom pruning timeout (in seconds)") + + allow_listing = cli.SwitchAttr(["-l", "--listing"], bool, default=False, help="Enable/disable listing on registry") + + def main(self): + if self.mode.upper() == "UDP": + server = UDPRegistryServer(host='::' if self.ipv6 else '0.0.0.0', port=self.port, + pruning_timeout=self.pruning_timeout, allow_listing=self.allow_listing) + elif self.mode.upper() == "TCP": + server = TCPRegistryServer(port=self.port, pruning_timeout=self.pruning_timeout, + allow_listing=self.allow_listing) + setup_logger(self.quiet, self.logfile) + server.start() + + +def main(): + RegistryServer.run()