From 7f7fdc2d2aa085383db8e4bde74a571465a7f6f7 Mon Sep 17 00:00:00 2001 From: klemek Date: Sun, 12 Apr 2026 23:22:33 +0200 Subject: [PATCH] feat: renew command --- README.md | 18 ++++-------------- main.py | 5 ++++- src/handler.py | 2 +- src/params.py | 29 +++++++++++++---------------- src/server.py | 20 ++++++++++++++++++-- 5 files changed, 40 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 50fe671..c1fd933 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ```txt usage: stapler [-h] [--debug | --no-debug] [-d DATA_DIR] [--certificates | --no-certificates] [--certbot | --no-certbot] [--self-signed-path SELF_SIGNED_PATH] [--certbot-conf CERTBOT_CONF] - [--certbot-www CERTBOT_WWW] + [--certbot-www CERTBOT_WWW] [--host HOST] [-p PORT] [--https | --no-https] [-t TOKEN] [--max-size-bytes MAX_SIZE] [-b BIND] COMMAND ... Static pages as simple as a gzip file @@ -10,6 +10,7 @@ Static pages as simple as a gzip file positional arguments: COMMAND run Run Stapler server + renew Renew certificates options: -h, --help show this help message and exit @@ -26,21 +27,10 @@ options: Certbot config dir (default: /etc/letsencrypt) --certbot-www CERTBOT_WWW Certbot www dir (default: ./data/.certbot) - -(Each option can be supplied with equivalent environment variable.) -``` - -```txt -usage: stapler run [-h] [--host HOST] [-p PORT] [--https | --no-https] -t TOKEN [--max-size-bytes MAX_SIZE] [-b BIND] - -Run Stapler server - -options: - -h, --help show this help message and exit --host HOST server default host (default: localhost:8080) -p, --port PORT server port (default: 8080) --https, --no-https Use https (implies --certificates) (default: true) - -t, --token TOKEN secret token for update requests + -t, --token TOKEN secret token for update requests (default: ) --max-size-bytes MAX_SIZE max size of accepted archives (in bytes) (default: 2000000) -b, --bind BIND server bind address (default: 0.0.0.0) @@ -101,7 +91,7 @@ curl -X DELETE \ - [x] redirect /.well-known/acme-challenge to specific path - [x] certbot/self-signed create/renew in specific dir - [x] better logger -- [ ] renew command +- [x] renew command - [x] https mode w/ multiple hosts - [ ] restart command (on new/deleted host) - [ ] proper doc diff --git a/main.py b/main.py index 1156348..88ac0d4 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,5 @@ +import sys + from src.logs import setup_logs from src.params import parse_parameters from src.server import StaplerServer @@ -7,7 +9,8 @@ def main() -> None: params = parse_parameters() setup_logs(params) server = StaplerServer(params) - server.start() + method = getattr(server, params.command) + sys.exit(method()) if __name__ == "__main__": diff --git a/src/handler.py b/src/handler.py index 87265d1..f36b35e 100644 --- a/src/handler.py +++ b/src/handler.py @@ -181,7 +181,7 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): self.logger.debug(fmt, *args) def __check_update_request(self) -> str | None: - if self.headers["X-Token"] != self.token: + if len(self.token) and self.headers["X-Token"] != self.token: self.send_error(http.HTTPStatus.UNAUTHORIZED, "Invalid token") return None if (sub_path := self.__get_subpath_full(self.path)) is None: diff --git a/src/params.py b/src/params.py index 7791e73..496b000 100644 --- a/src/params.py +++ b/src/params.py @@ -1,6 +1,7 @@ import argparse import dataclasses import os +import typing from . import project @@ -21,7 +22,7 @@ class Parameters: with_certbot: bool with_certificates: bool https: bool - command: str + command: typing.Literal["run", "renew"] debug: bool @classmethod @@ -95,7 +96,6 @@ def parse_parameters() -> Parameters: epilog=__EPILOG, suggest_on_error=True, ) - subparsers = parser.add_subparsers(dest="command", required=True, metavar="COMMAND") parser.add_argument("--debug", action=argparse.BooleanOptionalAction) __add_arg_str( parser, @@ -140,56 +140,53 @@ def parse_parameters() -> Parameters: default="./data/.certbot", help_txt="Certbot www dir", ) - - run_parser = subparsers.add_parser( - "run", - help="Run Stapler server", - description="Run Stapler server", - epilog=__EPILOG, - ) __add_arg_str( - run_parser, + parser, "--host", env_var="HOST", default="localhost:8080", help_txt="server default host", ) __add_arg_int( - run_parser, + parser, "-p", "--port", env_var="PORT", default=8080, help_txt="server port", ) - run_parser.add_argument( + parser.add_argument( "--https", action=argparse.BooleanOptionalAction, help="Use https (implies --certificates) (default: true)", default=True, ) - __add_arg_str_required( - run_parser, + __add_arg_str( + parser, "-t", "--token", env_var="TOKEN", + default="", help_txt="secret token for update requests", ) __add_arg_int( - run_parser, + parser, "--max-size-bytes", env_var="MAX_SIZE", default=2_000_000, help_txt="max size of accepted archives (in bytes)", ) __add_arg_str( - run_parser, + parser, "-b", "--bind", env_var="BIND", default="0.0.0.0", help_txt="server bind address", ) + subparsers = parser.add_subparsers(dest="command", required=True, metavar="COMMAND") + subparsers.add_parser("run", help="Run Stapler server") + subparsers.add_parser("renew", help="Renew certificates") args = parser.parse_args() if args.https: args.with_certificates = True diff --git a/src/server.py b/src/server.py index c31e178..f015b56 100644 --- a/src/server.py +++ b/src/server.py @@ -20,11 +20,16 @@ class StaplerServer: def request_handler(self, *args: typing.Any) -> http.server.BaseHTTPRequestHandler: return handler.RequestHandler(*args, params=self.params, registry=self.registry) + def __get_all_hosts(self) -> list[str]: + return [self.default_host, *self.registry.get_hosts()] + def __startup(self) -> None: self.logger.info("Starting up...") self.registry.load_pages() if self.params.with_certificates: - self.cert_manager.init([self.default_host, *self.registry.get_hosts()]) + self.cert_manager.init(self.__get_all_hosts()) + if not len(self.params.token): + self.logger.warning("No token provided update requests will fail") def __create_https_context(self, server: http.server.HTTPServer) -> bool: https = False @@ -35,7 +40,7 @@ class StaplerServer: server.socket = context.wrap_socket(server.socket, server_side=True) return https - def start(self) -> None: + def run(self) -> int: self.logger.info("Version %s", project.get_version()) self.__startup() server = http.server.ThreadingHTTPServer( @@ -55,3 +60,14 @@ class StaplerServer: ) with contextlib.suppress(KeyboardInterrupt): server.serve_forever() + return 0 + + def renew(self) -> int: + self.logger.info("Starting up...") + if not self.params.with_certificates: + self.logger.warning("Cannot renew without certificates") + return 1 + self.registry.load_pages() + for host in self.__get_all_hosts(): + self.cert_manager.create_or_update(host) + return 0