diff --git a/Dockerfile b/Dockerfile index f91f945..d66aad6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,13 +2,19 @@ FROM python:3.14-alpine WORKDIR /app -VOLUME [ "/data" ] +VOLUME [ "/data", "/etc/letsencrypt" ] -ENV HOST=localhost ENV PORT=8080 -ENV BIND=0.0.0.0 +ENV HOST=localhost ENV DATA_DIR=/data ENV MAX_SIZE=2000000 +ENV BIND=0.0.0.0 +ENV CERTBOT_CONF=/etc/letsencrypt +ENV CERTBOT_WWW=/data/.certbot + +RUN apk add --no-cache \ + openssl \ + certbot RUN PIP_ROOT_USER_ACTION=ignore python3 -m pip install uv diff --git a/Makefile b/Makefile index a24965a..44c9272 100644 --- a/Makefile +++ b/Makefile @@ -72,5 +72,8 @@ format: ruff-fix ruff-format ## format project .PHONY: lint lint: ruff ruff-format-check ty ## lint project +.PHONY: build +build: docker-build ## build project + .PHONY: start -start: docker-run ## start server in localhost +start: build docker-run ## start server in localhost diff --git a/README.md b/README.md index 8692522..83c7f71 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,26 @@ # Stapler ```txt -usage: stapler [-h] [-p PORT] [--host HOST] [-d DATA_DIR] -t TOKEN [--max-size-bytes MAX_SIZE_BYTES] [-b BIND] +usage: stapler [-h] [-p PORT] [--host HOST] [-d DATA_DIR] -t TOKEN [--max-size-bytes MAX_SIZE] [-b BIND] [--certbot-conf CERTBOT_CONF] [--certbot-www CERTBOT_WWW] Static pages as simple as a gzip file options: -h, --help show this help message and exit - -p, --port PORT server port (default: 8080) (env var: PORT) - --host HOST server default host (default: localhost) (env var: HOST) + -p, --port PORT server port (default: 8080) + --host HOST server default host (default: localhost) -d, --data-dir DATA_DIR - directory where files are/will be stored (default: ./data) (env var: DATA_DIR) - -t, --token TOKEN secret token for update requests (env var: TOKEN) - --max-size-bytes MAX_SIZE_BYTES - max size of accepted archives (in bytes) (default: 2000000 -> 2MB) (env var: MAX_SIZE) - -b, --bind BIND server bind address (default: 0.0.0.0) (env var: BIND) + directory where pages are/will be stored (default: ./data) + -t, --token TOKEN secret token for update requests + --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) + --certbot-conf CERTBOT_CONF + 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.) ``` ## Endpoints @@ -66,7 +72,7 @@ curl -X DELETE \ - [x] .host in /data/xxx can be translated as host in GET / - [x] header to setup .host file instead of in archive - [x] ignore .gitignore/.host etc at root -- [ ] cerbot install in container + path env/arg +- [x] cerbot install in container + path env/arg - [ ] redirect /.well-known/acme-challenge to specific path - [ ] certbot/self-signed create/renew in specific dir - [ ] renew command diff --git a/pyproject.toml b/pyproject.toml index 0c3c716..c5ace5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,6 @@ description = "Static pages as simple as a gzip file" readme = "README.md" requires-python = ">=3.14" dependencies = [ - "importlib>=1.0.4", "toml>=0.10.2", ] @@ -17,4 +16,4 @@ dev = [ [tool.ruff.lint] select = ["E", "F", "C", "W"] -ignore = [] \ No newline at end of file +ignore = ["E501"] diff --git a/src/params.py b/src/params.py index 289a90e..2361388 100644 --- a/src/params.py +++ b/src/params.py @@ -14,6 +14,8 @@ class Parameters: bind: str token: str max_size_bytes: int + certbot_conf: str + certbot_www: str @classmethod def from_namespace(cls, args: argparse.Namespace) -> "Parameters": @@ -33,48 +35,100 @@ def __get_env_int(var: str, default: int) -> int: return default +def __add_arg_str( + parser: argparse.ArgumentParser, *flags: str, env_var: str, default: str, help: str +): + parser.add_argument( + *flags, + metavar=env_var, + default=__get_env_str(env_var, default), + help=f"{help} (default: {default})", + ) + + +def __add_arg_int( + parser: argparse.ArgumentParser, *flags: str, env_var: str, default: int, help: str +): + parser.add_argument( + *flags, + type=int, + metavar=env_var, + default=__get_env_int(env_var, default), + help=f"{help} (default: {default})", + ) + + +def __add_arg_str_required( + parser: argparse.ArgumentParser, *flags: str, env_var: str, help: str +): + parser.add_argument( + *flags, + metavar=env_var, + required=os.getenv(env_var) is None, + default=os.getenv(env_var), + help=f"{help}", + ) + + def parse_parameters() -> Parameters: parser = argparse.ArgumentParser( - project.get_name(), description=project.get_description() + project.get_name(), + description=project.get_description(), + epilog="(Each option can be supplied with equivalent environment variable.)", ) - parser.add_argument( - "-p", - "--port", - type=int, - default=__get_env_int("PORT", 8080), - help="server port (default: 8080) (env var: PORT)", + __add_arg_int( + parser, "-p", "--port", env_var="PORT", default=8080, help="server port" ) - parser.add_argument( + __add_arg_str( + parser, "--host", - default=__get_env_str("HOST", "localhost"), - help="server default host (default: localhost) (env var: HOST)", + env_var="HOST", + default="localhost", + help="server default host", ) - parser.add_argument( + __add_arg_str( + parser, "-d", "--data-dir", - default=__get_env_str("DATA_DIR", os.path.join(os.getcwd(), "data")), - help="directory where files are/will be stored" - " (default: ./data) (env var: DATA_DIR)", + env_var="DATA_DIR", + default=os.path.join(".", "data"), + help="directory where pages are/will be stored", ) - parser.add_argument( + __add_arg_str_required( + parser, "-t", "--token", - required=os.getenv("TOKEN") is None, - default=os.getenv("TOKEN"), - help="secret token for update requests (env var: TOKEN)", + env_var="TOKEN", + help="secret token for update requests", ) - parser.add_argument( + __add_arg_int( + parser, "--max-size-bytes", - type=int, - default=__get_env_int("MAX_SIZE", 2000000), - help="max size of accepted archives (in bytes)" - " (default: 2000000 -> 2MB) (env var: MAX_SIZE)", + env_var="MAX_SIZE", + default=2_000_000, + help="max size of accepted archives (in bytes)", ) - parser.add_argument( + __add_arg_str( + parser, "-b", "--bind", - default=__get_env_str("BIND", "0.0.0.0"), - help="server bind address (default: 0.0.0.0) (env var: BIND)", + env_var="BIND", + default="0.0.0.0", + help="server bind address", + ) + __add_arg_str( + parser, + "--certbot-conf", + env_var="CERTBOT_CONF", + default="/etc/letsencrypt", + help="Certbot config dir", + ) + __add_arg_str( + parser, + "--certbot-www", + env_var="CERTBOT_WWW", + default=os.path.join(".", "data", ".certbot"), + help="Certbot www dir", ) args = parser.parse_args() return Parameters.from_namespace(args) diff --git a/uv.lock b/uv.lock index a9d7edd..32694bf 100644 --- a/uv.lock +++ b/uv.lock @@ -2,12 +2,6 @@ version = 1 revision = 3 requires-python = ">=3.14" -[[package]] -name = "importlib" -version = "1.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/77/3781f65cafe55480b56914def99022a5d2965a4bb269655c89ef2f1de3cd/importlib-1.0.4.zip", hash = "sha256:b6ee7066fea66e35f8d0acee24d98006de1a0a8a94a8ce6efe73a9a23c8d9826", size = 7074, upload-time = "2016-08-31T19:40:00.728Z" } - [[package]] name = "ruff" version = "0.15.10" @@ -38,7 +32,6 @@ name = "stapler" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "importlib" }, { name = "toml" }, ] @@ -49,10 +42,7 @@ dev = [ ] [package.metadata] -requires-dist = [ - { name = "importlib", specifier = ">=1.0.4" }, - { name = "toml", specifier = ">=0.10.2" }, -] +requires-dist = [{ name = "toml", specifier = ">=0.10.2" }] [package.metadata.requires-dev] dev = [