init work on letsencrypt_dns core plugin

This commit is contained in:
Florian 2024-11-11 16:01:38 +01:00
parent c93759541b
commit 7e8f1ef25d
No known key found for this signature in database
GPG key ID: 52072123690D7318
10 changed files with 2174 additions and 414 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,380 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from datetime import datetime, timedelta
from itertools import chain
from json import dumps
from os import environ, getenv, sep
from os.path import join
from pathlib import Path
from shutil import rmtree
from subprocess import DEVNULL, STDOUT, Popen
from sys import exit as sys_exit, path as sys_path
from typing import Dict, List, Literal, Type, Union
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("db",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
from common_utils import bytes_hash # type: ignore
from jobs import Job # type: ignore
from logger import setup_logger # type: ignore
LOGGER = setup_logger("LETS-ENCRYPT-DNS.new", getenv("LOG_LEVEL", "INFO"))
LIB_PATH = Path(sep, "var", "lib", "bunkerweb", "letsencrypt_dns")
deps_path = LIB_PATH.joinpath("python").as_posix()
if deps_path not in sys_path:
sys_path.append(deps_path)
CERTBOT_BIN = LIB_PATH.joinpath("python", "bin", "certbot")
LOGGER_CERTBOT = setup_logger("LETS-ENCRYPT-DNS.new.certbot", getenv("LOG_LEVEL", "INFO"))
status = 0
PLUGIN_PATH = Path(sep, "usr", "share", "bunkerweb", "core", "letsencrypt_dns")
JOBS_PATH = PLUGIN_PATH.joinpath("jobs")
CACHE_PATH = Path(sep, "var", "cache", "bunkerweb", "letsencrypt_dns")
DATA_PATH = CACHE_PATH.joinpath("etc")
WORK_DIR = join(sep, "var", "lib", "bunkerweb", "letsencrypt_dns")
LOGS_DIR = join(sep, "var", "log", "bunkerweb", "letsencrypt_dns")
class WildcardGenerator:
def __init__(self):
self.__domain_groups = {}
self.__wildcards = {}
def __generate_wildcards(self, staging: bool = False):
self.__wildcards.clear()
_type = "staging" if staging else "prod"
# * Loop through all the domains and generate wildcards
for group, types in self.__domain_groups.items():
if group not in self.__wildcards:
self.__wildcards[group] = {"staging": set(), "prod": set(), "email": types["email"]}
for domain in types[_type]:
parts = domain.split(".")
# ? Only take subdomains into account for wildcards generation
if len(parts) > 2:
suffix = ".".join(parts[1:])
# ? If the suffix is not already in the wildcards, add it
if suffix not in self.__wildcards[group][_type]:
self.__wildcards[group][_type].add(f"*.{suffix}")
self.__wildcards[group][_type].add(suffix)
continue
# ? Add the raw domain to the wildcards
self.__wildcards[group][_type].add(domain)
def extend(self, group: str, domains: List[str], email: str, staging: bool = False):
if group not in self.__domain_groups:
self.__domain_groups[group] = {"staging": set(), "prod": set(), "email": email}
for domain in domains:
if domain := domain.strip():
self.__domain_groups[group]["staging" if staging else "prod"].add(domain)
self.__generate_wildcards(staging)
def get_wildcards(self) -> Dict[str, Dict[Literal["staging", "prod", "email"], str]]:
ret_data = {}
for group, data in self.__wildcards.items():
ret_data[group] = {"email": data["email"]}
for _type, content in data.items():
if _type in ("staging", "prod"):
# ? Sort domains while favoring wildcards first
ret_data[group][_type] = ",".join(sorted(content, key=lambda x: x[0] != "*"))
return ret_data
def certbot_new(provider: str, credentials_path: Union[str, Path], domains: str, email: str, propagation: str = "default", staging: bool = False) -> int:
if isinstance(credentials_path, str):
credentials_path = Path(credentials_path)
# * Building the certbot command
command = [
CERTBOT_BIN,
"certonly",
"--config-dir",
DATA_PATH.as_posix(),
"--work-dir",
WORK_DIR,
"--logs-dir",
LOGS_DIR,
"--preferred-challenges=dns",
"-n",
"-d",
domains,
"--email",
email,
"--agree-tos",
"--expand",
]
# * Adding the propagation time to the command
if propagation != "default":
if not propagation.isdigit():
LOGGER.warning(f"Invalid propagation time : {propagation}, using provider's default...")
else:
command.extend([f"--dns-{provider}-propagation-seconds", propagation])
env = environ | {"PYTHONPATH": deps_path}
# * Adding the credentials to the command
if provider == "route53":
# ? Route53 credentials are different from the others, we need to add them to the environment
with credentials_path.open("r") as file:
for line in file:
key, value = line.strip().split("=", 1)
env[key] = value
else:
command.extend([f"--dns-{provider}-credentials", credentials_path.as_posix()])
# * Adding plugin argument
if provider == "scaleway":
# ? Scaleway plugin uses a different argument
command.extend(["--authenticator", "dns-scaleway"])
else:
command.append(f"--dns-{provider}")
if staging:
command.append("--staging")
current_date = datetime.now()
process = Popen(command, stdin=DEVNULL, stderr=STDOUT, universal_newlines=True, env=env)
while process.poll() is None:
if datetime.now() - current_date > timedelta(seconds=5):
LOGGER.info("⏳ Still generating certificate(s)...")
current_date = datetime.now()
return process.returncode
IS_MULTISITE = getenv("MULTISITE", "no") == "yes"
try:
servers = getenv("SERVER_NAME", "").lower() or []
if isinstance(servers, str):
servers = servers.split(" ")
if not servers:
LOGGER.error("There are no server names, skipping generation...")
sys_exit(0)
use_letsencrypt_dns = False
if not IS_MULTISITE:
servers = [servers[0]]
use_letsencrypt_dns = getenv("AUTO_LETS_ENCRYPT_DNS", "no") == "yes"
else:
for first_server in servers:
if first_server and getenv(f"{first_server}_AUTO_LETS_ENCRYPT_DNS", "no") == "yes":
use_letsencrypt_dns = True
break
if not use_letsencrypt_dns:
LOGGER.info("Let's Encrypt DNS is not activated, skipping generation...")
sys_exit(0)
elif not CERTBOT_BIN.is_file():
LOGGER.error("Additional dependencies not installed, skipping certificate(s) generation...")
sys_exit(2)
from pydantic import ValidationError
from models import (
CloudflareProvider,
DigitalOceanProvider,
GoogleProvider,
LinodeProvider,
OvhProvider,
Rfc2136Provider,
Route53Provider,
ScalewayProvider,
)
PROVIDER_CLASSES: Dict[
str,
Union[
Type[CloudflareProvider],
Type[DigitalOceanProvider],
Type[GoogleProvider],
Type[LinodeProvider],
Type[OvhProvider],
Type[Rfc2136Provider],
Type[Route53Provider],
Type[ScalewayProvider],
],
] = {
"cloudflare": CloudflareProvider,
"digitalocean": DigitalOceanProvider,
"google": GoogleProvider,
"linode": LinodeProvider,
"ovh": OvhProvider,
"rfc2136": Rfc2136Provider,
"route53": Route53Provider,
"scaleway": ScalewayProvider,
}
JOB = Job(LOGGER)
# ? Restore data from db cache of dns-certbot-renew job
JOB.restore_cache(job_name="dns-certbot-renew")
WILDCARD_GENERATOR = WildcardGenerator()
credential_paths = set()
generated_domains = set()
for first_server in servers:
if getenv(f"{first_server}_AUTO_LETS_ENCRYPT_DNS", getenv("AUTO_LETS_ENCRYPT_DNS", "no")) == "no":
LOGGER.info(f"Skipping certificate(s) generation for {first_server} because it is not enabled")
continue
elif getenv(f"{first_server}_AUTO_LETS_ENCRYPT", getenv("AUTO_LETS_ENCRYPT", "no")) == "yes":
LOGGER.warning(f"Skipping certificate(s) generation for {first_server} because it is using regular Let's Encrypt")
continue
# * Getting all the necessary data
data = {
"domains": getenv(f"{first_server}_SERVER_NAME", getenv("SERVER_NAME", "")).lower() or first_server,
"email": getenv(f"{first_server}_LETS_ENCRYPT_DNS_EMAIL", getenv("LETS_ENCRYPT_DNS_EMAIL", "")) or f"contact@{first_server}",
"staging": getenv(f"{first_server}_USE_LETS_ENCRYPT_DNS_STAGING", getenv("USE_LETS_ENCRYPT_DNS_STAGING", "no")) == "yes",
"provider": getenv(f"{first_server}_LETS_ENCRYPT_DNS_PROVIDER", getenv("LETS_ENCRYPT_DNS_PROVIDER", "")),
"use_wildcard": getenv(f"{first_server}_USE_LETS_ENCRYPT_DNS_WILDCARD", getenv("USE_LETS_ENCRYPT_DNS_WILDCARD", "no")) == "yes",
"propagation": getenv(f"{first_server}_LETS_ENCRYPT_DNS_PROPAGATION", getenv("LETS_ENCRYPT_DNS_PROPAGATION", "default")),
"credential_items": {},
}
for env_key, env_value in environ.items():
if env_value and env_key.startswith(f"{first_server}_LETS_ENCRYPT_DNS_CREDENTIAL_ITEM" if IS_MULTISITE else "LETS_ENCRYPT_DNS_CREDENTIAL_ITEM"):
key, value = env_value.split(" ", 1)
data["credential_items"][key.lower()] = value
LOGGER.debug(f"Data for service {first_server} : {dumps(data)}")
# * Checking if the data is valid
if not data["provider"]:
LOGGER.warning(
f"No provider found for service {first_server} (available providers : {', '.join(PROVIDER_CLASSES.keys())}), skipping certificate(s) generation..." # noqa: E501
)
continue
elif not data["credential_items"]:
LOGGER.warning(f"No credentials items found for service {first_server} (you should have at least one), skipping certificate(s) generation...")
continue
# * Validating the credentials
try:
provider = PROVIDER_CLASSES[data["provider"]](**data["credential_items"])
except ValidationError as ve:
LOGGER.error(f"Error while validating credentials for service {first_server} :\n{ve}")
continue
content = provider.get_formatted_credentials()
# * Adding the domains to Wildcard Generator if necessary
file_path = (first_server, f"credentials.{provider.get_file_type()}")
if data["use_wildcard"]:
group = f"{data['provider']}_{bytes_hash(content, algorithm='sha1')}"
LOGGER.info(
f"Service {first_server} is using wildcard, the propagation time will be the provider's default "
+ "and the email will be the same as the first domain that created the group..."
)
WILDCARD_GENERATOR.extend(group, data["domains"].strip().split(" "), data["email"], data["staging"])
file_path = (f"{group}.{provider.get_file_type()}",)
# * Generating the credentials file
credentials_path = CACHE_PATH.joinpath(*file_path)
if not credentials_path.is_file():
cached, err = JOB.cache_file(
credentials_path.name, content, job_name="dns-certbot-renew", service_id=first_server if not data["use_wildcard"] else ""
)
if not cached:
LOGGER.error(f"Error while saving service {first_server}'s credentials file in cache : {err}")
continue
LOGGER.info(f"Successfully saved service {first_server}'s credentials file in cache")
elif data["use_wildcard"]:
LOGGER.info(f"Service {first_server}'s wildcard credentials file has already been generated")
else:
old_content = credentials_path.read_bytes()
if old_content != content:
LOGGER.warning(f"Service {first_server}'s credentials file is outdated, updating it...")
cached, err = JOB.cache_file(credentials_path.name, content, job_name="dns-certbot-renew", service_id=first_server)
if not cached:
LOGGER.error(f"Error while updating service {first_server}'s credentials file in cache : {err}")
continue
LOGGER.info(f"Successfully updated service {first_server}'s credentials file in cache")
else:
LOGGER.info(f"Service {first_server}'s credentials file is up to date")
credential_paths.add(credentials_path)
credentials_path.chmod(0o600) # ? Setting the permissions to 600 (this is important to avoid warnings from certbot)
if data["use_wildcard"]:
continue
domains = data["domains"].replace(" ", ",")
LOGGER.info(f"Asking certificates for domain(s) : {domains} (email = {data['email']}) {'using staging ' if data['staging'] else ''}...")
if certbot_new(data["provider"], credentials_path, domains, data["email"], data["propagation"], data["staging"]) != 0:
status = 2
LOGGER.error(f"Certificate generation failed for domain(s) {data['domains']} ...")
else:
status = 1 if status == 0 else status
LOGGER.info(f"Certificate generation succeeded for domain(s) : {data['domains']}")
generated_domains.update(data["domains"].split(","))
# * Generating the wildcards if necessary
wildcards = WILDCARD_GENERATOR.get_wildcards()
if wildcards:
for group, data in wildcards.items():
if not data:
continue
# * Generating the certificate from the generated credentials
provider = group.split("_", 1)[0]
email = data.pop("email")
credentials_file = CACHE_PATH.joinpath(f"{group}.{PROVIDER_CLASSES[provider].get_file_type()}")
for key, domains in data.items():
if not domains:
continue
staging = key == "staging"
LOGGER.info(f"Asking wildcard certificates for domain(s) : {domains} (email = {email}) {'using staging ' if staging else ''}...")
if certbot_new(provider, credentials_file, domains, email, staging=staging) != 0:
status = 2
LOGGER.error(f"Certificate generation failed for domain(s) {domains} ...")
else:
status = 1 if status == 0 else status
LOGGER.info(f"Certificate generation succeeded for domain(s) : {domains}")
generated_domains.update(domains.split(","))
else:
LOGGER.info("No wildcard domains found, skipping wildcard certificate(s) generation...")
# * Clearing all missing credentials files
for file in CACHE_PATH.glob("**/*"):
if "etc" in file.parts or not file.is_file() or file.suffix not in (".ini", ".env", ".json"):
continue
# ? If the file is not in the wildcard groups, remove it
if file not in credential_paths:
LOGGER.debug(f"Removing old credentials file {file}")
JOB.del_cache(file.name, job_name="dns-certbot-renew", service_id=file.parent.name if file.parent.name != "letsencrypt_dns" else "")
# * Clearing all no longer needed certificates
if getenv("LETS_ENCRYPT_DNS_CLEAR_OLD_CERTS", "no") == "yes":
LOGGER.info("Clear old certificates is activated, removing old / no longer used certificates...")
for elem in chain(DATA_PATH.glob("archive/*"), DATA_PATH.glob("live/*"), DATA_PATH.glob("renewal/*")):
if elem.name.replace(".conf", "") not in generated_domains and elem.name != "README":
LOGGER.warning(f"Removing old certificate {elem}")
if elem.is_dir():
rmtree(elem, ignore_errors=True)
else:
elem.unlink(missing_ok=True)
# * Save data to db cache
if DATA_PATH.is_dir() and list(DATA_PATH.iterdir()):
cached, err = JOB.cache_dir(DATA_PATH, job_name="dns-certbot-renew")
if not cached:
LOGGER.error(f"Error while saving data to db cache : {err}")
else:
LOGGER.info("Successfully saved data to db cache")
except SystemExit as e:
status = e.code
except:
status = 1
LOGGER.exception("Exception while running certbot-new.py")
sys_exit(status)

View file

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from os import environ, getenv, sep
from os.path import join
from pathlib import Path
from subprocess import DEVNULL, PIPE, Popen
from sys import exit as sys_exit, path as sys_path
from traceback import format_exc
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("db",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
from logger import setup_logger # type: ignore
from jobs import Job # type: ignore
LOGGER = setup_logger("LETS-ENCRYPT-DNS.renew", getenv("LOG_LEVEL", "INFO"))
LOGGER_CERTBOT = setup_logger("LETS-ENCRYPT-DNS.renew.certbot", getenv("LOG_LEVEL", "INFO"))
status = 0
LIB_PATH = Path(sep, "var", "lib", "bunkerweb", "letsencrypt_dns")
PLUGIN_PATH = Path(sep, "usr", "share", "bunkerweb", "core", "letsencrypt_dns")
JOBS_PATH = PLUGIN_PATH.joinpath("jobs")
DATA_PATH = Path(sep, "var", "cache", "bunkerweb", "letsencrypt_dns", "etc")
WORK_DIR = join(sep, "var", "lib", "bunkerweb", "letsencrypt_dns")
LOGS_DIR = join(sep, "var", "log", "bunkerweb", "letsencrypt_dns")
deps_path = LIB_PATH.joinpath("python")
CERTBOT_BIN = deps_path.joinpath("bin", "certbot")
try:
# Check if we're using let's encrypt
use_letsencrypt_dns = False
if not getenv("MULTISITE", "no") == "yes":
use_letsencrypt_dns = getenv("AUTO_LETS_ENCRYPT_DNS", "no") == "yes"
else:
for first_server in getenv("SERVER_NAME", "").split(" "):
if first_server and getenv(f"{first_server}_AUTO_LETS_ENCRYPT_DNS", "no") == "yes":
use_letsencrypt_dns = True
break
if not use_letsencrypt_dns:
LOGGER.info("Let's Encrypt DNS is not activated, skipping generation...")
sys_exit(0)
elif not CERTBOT_BIN.is_file():
LOGGER.error("Additional dependencies not installed, skipping certificate(s) generation...")
sys_exit(2)
JOB = Job(LOGGER)
process = Popen(
[
CERTBOT_BIN,
"renew",
"--no-random-sleep-on-renew",
"--config-dir",
DATA_PATH.as_posix(),
"--work-dir",
WORK_DIR,
"--logs-dir",
LOGS_DIR,
],
stdin=DEVNULL,
stderr=PIPE,
universal_newlines=True,
env=environ | {"PYTHONPATH": deps_path.as_posix()},
)
while process.poll() is None:
if process.stderr:
for line in process.stderr:
LOGGER_CERTBOT.info(line.strip())
if process.returncode != 0:
status = 2
LOGGER.error("Certificates renewal failed")
# Save Let's Encrypt DNS data to db cache
if DATA_PATH.is_dir() and list(DATA_PATH.iterdir()):
cached, err = JOB.cache_dir(DATA_PATH)
if not cached:
LOGGER.error(f"Error while saving Let's Encrypt DNS data to db cache : {err}")
else:
LOGGER.info("Successfully saved Let's Encrypt DNS data to db cache")
except SystemExit as e:
status = e.code
except:
status = 2
LOGGER.error(f"Exception while running certbot-renew.py :\n{format_exc()}")
sys_exit(status)

View file

@ -0,0 +1,167 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from datetime import datetime, timedelta
from os import environ, getenv, sep
from os.path import join
from pathlib import Path
from shutil import rmtree
from subprocess import STDOUT, Popen, PIPE, run
from sys import exit as sys_exit, path as sys_path, version_info
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("db",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
from Database import Database # type: ignore
from logger import setup_logger # type: ignore
LOGGER = setup_logger("LETS-ENCRYPT-DNS.install-deps", getenv("LOG_LEVEL", "INFO"))
status = 0
PLUGIN_PATH = Path(sep, "usr", "share", "bunkerweb", "core", "letsencrypt_dns")
LIB_PATH = Path(sep, "var", "lib", "bunkerweb", "letsencrypt_dns")
PIP_PATH = LIB_PATH.joinpath("pip")
PYTHON_DEPS_PATH = LIB_PATH.joinpath("python")
try:
# * Check if we're using let's encrypt DNS
all_domains = getenv("SERVER_NAME", "")
if not all_domains:
LOGGER.warning("There are no server names, skipping additional dependencies installation...")
sys_exit(0)
use_letsencrypt_dns = False
is_multisite = getenv("MULTISITE", "no") == "yes"
server_names = all_domains.split(" ")
if not is_multisite:
use_letsencrypt_dns = getenv("AUTO_LETS_ENCRYPT_DNS", "no") == "yes"
else:
for first_server in server_names:
if first_server and getenv(f"{first_server}_AUTO_LETS_ENCRYPT_DNS", getenv("AUTO_LETS_ENCRYPT_DNS", "no")) == "yes":
use_letsencrypt_dns = True
break
if not use_letsencrypt_dns:
LOGGER.info("Let's Encrypt DNS is not activated, skipping additional dependencies installation...")
sys_exit(0)
if PYTHON_DEPS_PATH.is_dir() and list(PYTHON_DEPS_PATH.iterdir()):
LOGGER.info("Additional dependencies already installed, checking for updates...")
deps = {}
with PLUGIN_PATH.joinpath("requirements.in").open("r") as f:
for line in f:
if (line := line.strip()) and not line.startswith("#"):
package, version = line.split("==")
deps[package] = version
all_deps_up_to_date = True
process = Popen(
["python3", "-m", "pip", "freeze", "--local"],
stdout=PIPE,
stderr=STDOUT,
universal_newlines=True,
env=environ | {"PYTHONPATH": PYTHON_DEPS_PATH.as_posix()},
)
while process.poll() is None:
if process.stdout is not None:
for line in process.stdout:
if (line := line.strip()) and not line.startswith("#"):
split = line.split("==")
if len(split) != 2:
continue
package, version = split
if package in deps and deps[package] != version:
LOGGER.info(f"⚠️ {package} is outdated: {version} -> {deps[package]}")
all_deps_up_to_date = False
if process.returncode != 0:
LOGGER.error("❌ Error while checking additional python dependencies, updating just in case...")
elif all_deps_up_to_date:
LOGGER.info("✅ All additional dependencies are up to date")
sys_exit(0)
else:
LOGGER.warning("Some additional dependencies are outdated, updating...")
rmtree(PYTHON_DEPS_PATH, ignore_errors=True)
else:
LOGGER.info("Deps path not found, installing additional dependencies...")
PYTHON_DEPS_PATH.mkdir(parents=True, exist_ok=True)
pip_cmd = ["python3", "-m", "pip"]
cmd_env = environ | {"PYTHONPATH": PYTHON_DEPS_PATH.as_posix()}
if PIP_PATH.joinpath("usr", "local", "bin").is_dir() and PIP_PATH.joinpath("usr", "local", "lib").is_dir():
pip_cmd = [PIP_PATH.joinpath("usr", "local", "bin", "pip3").as_posix()]
cmd_env["PYTHONPATH"] += ":" + PIP_PATH.joinpath("usr", "local", "lib", f"python{version_info.major}.{version_info.minor}", "site-packages").as_posix()
else:
process = run(pip_cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True)
if process.returncode != 0:
LOGGER.warning("Pip is not installed, installing pip locally...")
process = Popen(
["python3", "-m", "ensurepip", "--root", PIP_PATH.as_posix()],
stdout=PIPE,
stderr=STDOUT,
universal_newlines=True,
)
while process.poll() is None:
if process.stdout is not None:
for line in process.stdout:
LOGGER.debug(line.strip())
if process.returncode != 0:
LOGGER.error("❌ Error while ensuring pip is up to date")
sys_exit(1)
LOGGER.info("✅ Pip installed successfully")
pip_cmd = [PIP_PATH.joinpath("usr", "local", "bin", "pip3").as_posix()]
cmd_env["PYTHONPATH"] += (
":" + PIP_PATH.joinpath("usr", "local", "lib", f"python{version_info.major}.{version_info.minor}", "site-packages").as_posix()
)
LOGGER.info("Installing additional python dependencies...")
current_date = datetime.now()
process = Popen(
pip_cmd
+ [
"install",
"--no-cache-dir",
"--require-hashes",
"--ignore-installed",
"--target",
PYTHON_DEPS_PATH.as_posix(),
"-r",
PLUGIN_PATH.joinpath("requirements.txt").as_posix(),
],
stdout=PIPE,
stderr=STDOUT,
universal_newlines=True,
env=cmd_env,
)
while process.poll() is None:
if process.stdout is not None:
for line in process.stdout:
if datetime.now() - current_date > timedelta(seconds=5):
LOGGER.info("⏳ Still installing additional python dependencies...")
current_date = datetime.now()
LOGGER.debug(line.strip())
if process.returncode != 0:
LOGGER.error("❌ Error while installing additional python dependencies")
sys_exit(1)
LOGGER.info("✅ Additional dependencies installed successfully")
except SystemExit as e:
status = e.code
except:
status = 1
LOGGER.exception("Exception while running install-dependencies.py")
sys_exit(status)

View file

@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
from os.path import sep
from pathlib import Path
from sys import path as sys_path
from typing import Literal, Optional
LIB_PATH = Path(sep, "var", "lib", "bunkerweb", "letsencrypt_dns")
PYTHON_PATH = LIB_PATH.joinpath("python")
if PYTHON_PATH.as_posix() not in sys_path:
sys_path.append(PYTHON_PATH.as_posix())
from pydantic import BaseModel, ConfigDict
class Provider(BaseModel):
"""Base class for DNS providers."""
# ? Allow extra fields in the model in case there are additional fields that are not defined in the model.
model_config = ConfigDict(extra="allow")
def get_formatted_credentials(self) -> bytes:
"""Return the formatted credentials to be written to a file."""
return "\n".join(f"{key} = {value}" for key, value in self.model_dump(exclude={"file_type"}).items()).encode("utf-8")
@staticmethod
def get_file_type() -> Literal["ini"]:
"""Return the file type that the credentials should be written to."""
return "ini"
class CloudflareProvider(Provider):
dns_cloudflare_api_token: str
class DigitalOceanProvider(Provider):
dns_digitalocean_token: str
class GoogleProvider(Provider):
type: str = "service_account"
project_id: str
private_key_id: str
private_key: str
client_email: str
client_id: str
auth_uri: str = "https://accounts.google.com/o/oauth2/auth"
token_uri: str = "https://accounts.google.com/o/oauth2/token"
auth_provider_x509_cert_url: str = "https://www.googleapis.com/oauth2/v1/certs"
client_x509_cert_url: str
def get_formatted_credentials(self) -> bytes:
"""Return the formatted credentials to be written to a file."""
return self.model_dump_json(indent=2, exclude={"file_type"}).encode("utf-8")
@staticmethod
def get_file_type() -> Literal["json"]:
"""Return the file type that the credentials should be written to."""
return "json"
class LinodeProvider(Provider):
dns_linode_key: str
dns_linode_version: str = "4"
class OvhProvider(Provider):
dns_ovh_endpoint: str = "ovh-eu"
dns_ovh_application_key: str
dns_ovh_application_secret: str
dns_ovh_consumer_key: str
class Rfc2136Provider(Provider):
dns_rfc2136_server: str
dns_rfc2136_port: Optional[str] = None
dns_rfc2136_name: str
dns_rfc2136_secret: str
dns_rfc2136_algorithm: str = "HMAC-MD5"
dns_rfc2136_sign_query: str = "false"
def get_formatted_credentials(self) -> bytes:
"""Return the formatted credentials to be written to a file."""
# ? Return the formatted credentials as a string. The default values are excluded as they are not required.
return "\n".join(f"{key} = {value}" for key, value in self.model_dump(exclude={"file_type"}, exclude_defaults=True).items()).encode("utf-8")
class Route53Provider(Provider):
aws_access_key_id: str
aws_secret_access_key: str
def get_formatted_credentials(self) -> bytes:
"""Return the formatted credentials to be written to a file."""
# ? Return the formatted credentials as a string. The keys are converted to uppercase and the values are represented as a string.
return "\n".join(f"{key.upper()}={value!r}" for key, value in self.model_dump(exclude={"file_type"}).items()).encode("utf-8")
@staticmethod
def get_file_type() -> Literal["env"]:
"""Return the file type that the credentials should be written to."""
return "env"
class ScalewayProvider(Provider):
dns_scaleway_application_token: str
__ALL__ = (
"CloudflareProvider",
"DigitalOceanProvider",
"GoogleProvider",
"LinodeProvider",
"OvhProvider",
"Rfc2136Provider",
"Route53Provider",
"ScalewayProvider",
)

View file

@ -0,0 +1,194 @@
local class = require("middleclass")
local plugin = require("bunkerweb.plugin")
local ssl = require("ngx.ssl")
local utils = require("bunkerweb.utils")
local letsencrypt_dns = class("letsencrypt_dns", plugin)
-- luacheck: globals ngx
local ngx = ngx
local ERR = ngx.ERR
local parse_pem_cert = ssl.parse_pem_cert
local parse_pem_priv_key = ssl.parse_pem_priv_key
local ssl_server_name = ssl.server_name
local get_variable = utils.get_variable
local get_multiple_variables = utils.get_multiple_variables
local has_variable = utils.has_variable
local has_not_variable = utils.has_not_variable
local read_files = utils.read_files
function letsencrypt_dns:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "letsencrypt_dns", ctx)
end
function letsencrypt_dns:set()
local https_configured = self.variables["AUTO_LETS_ENCRYPT_DNS"]
if https_configured == "yes" then
self.ctx.bw.https_configured = "yes"
end
return self:ret(true, "set https_configured to " .. https_configured)
end
function letsencrypt_dns:init()
local ret_ok, ret_err = true, "success"
if has_variable("AUTO_LETS_ENCRYPT_DNS", "yes") and has_not_variable("LETS_ENCRYPT_DNS_PROVIDER", "") then
local multisite, err = get_variable("MULTISITE", false)
if not multisite then
return self:ret(false, "can't get MULTISITE variable : " .. err)
end
if multisite == "yes" then
local vars
vars, err = get_multiple_variables({
"AUTO_LETS_ENCRYPT_DNS",
"LETS_ENCRYPT_DNS_PROVIDER",
"USE_LETS_ENCRYPT_DNS_WILDCARD",
"SERVER_NAME",
})
if not vars then
return self:ret(false, "can't get required variables : " .. err)
end
local credential_items
credential_items, err = get_multiple_variables({ "LETS_ENCRYPT_DNS_CREDENTIAL_ITEM" })
if not credential_items then
return self:ret(false, "can't get credential items : " .. err)
end
for server_name, multisite_vars in pairs(vars) do
if
multisite_vars["AUTO_LETS_ENCRYPT_DNS"] == "yes"
and multisite_vars["LETS_ENCRYPT_DNS_PROVIDER"] ~= ""
and credential_items[server_name]
and server_name ~= "global"
then
local data
if multisite_vars["USE_LETS_ENCRYPT_DNS_WILDCARD"] == "yes" then
local parts = {}
for part in server_name:gmatch("[^.]+") do
table.insert(parts, part)
end
server_name = table.concat(parts, ".", 2)
data = self.datastore:get("plugin_letsencrypt_dns_" .. server_name, true)
end
if not data then
-- Load certificate
local check
check, data = read_files({
"/var/cache/bunkerweb/letsencrypt_dns/etc/live/" .. server_name .. "/fullchain.pem",
"/var/cache/bunkerweb/letsencrypt_dns/etc/live/" .. server_name .. "/privkey.pem",
})
if not check then
self.logger:log(ERR, "error while reading files : " .. data)
ret_ok = false
ret_err = "error reading files"
else
if multisite_vars["USE_LETS_ENCRYPT_DNS_WILDCARD"] == "yes" then
check, err = self:load_data(data, server_name)
else
check, err = self:load_data(data, multisite_vars["SERVER_NAME"])
end
if not check then
self.logger:log(ERR, "error while loading data : " .. err)
ret_ok = false
ret_err = "error loading data"
end
end
end
end
end
else
local server_name
server_name, err = get_variable("SERVER_NAME", false)
if not server_name then
return self:ret(false, "can't get SERVER_NAME variable : " .. err)
end
local use_wildcard
use_wildcard, err = get_variable("USE_LETS_ENCRYPT_DNS_WILDCARD", false)
if not use_wildcard then
return self:ret(false, "can't get USE_LETS_ENCRYPT_DNS_WILDCARD variable : " .. err)
end
server_name = server_name:match("%S+")
if use_wildcard == "yes" then
local parts = {}
for part in server_name:gmatch("[^.]+") do
table.insert(parts, part)
end
server_name = table.concat(parts, ".", 2)
end
local check, data = read_files({
"/var/cache/bunkerweb/letsencrypt_dns/etc/live/" .. server_name .. "/fullchain.pem",
"/var/cache/bunkerweb/letsencrypt_dns/etc/live/" .. server_name .. "/privkey.pem",
})
if not check then
self.logger:log(ERR, "error while reading files : " .. data)
ret_ok = false
ret_err = "error reading files"
else
check, err = self:load_data(data, server_name)
if not check then
self.logger:log(ERR, "error while loading data : " .. err)
ret_ok = false
ret_err = "error loading data"
end
end
end
else
ret_err = "let's encrypt dns is not used"
end
return self:ret(ret_ok, ret_err)
end
function letsencrypt_dns:ssl_certificate()
local server_name, err = ssl_server_name()
if not server_name then
return self:ret(false, "can't get server_name : " .. err)
end
local use_wildcard
use_wildcard, err = get_variable("USE_LETS_ENCRYPT_DNS_WILDCARD", false)
if not use_wildcard then
return self:ret(false, "can't get USE_LETS_ENCRYPT_DNS_WILDCARD variable : " .. err)
end
if use_wildcard == "yes" then
local parts = {}
for part in server_name:gmatch("[^.]+") do
table.insert(parts, part)
end
server_name = table.concat(parts, ".", 2)
end
local data
data, err = self.datastore:get("plugin_letsencrypt_dns_" .. server_name, true)
if not data and err ~= "not found" then
return self:ret(
false,
"error while getting plugin_letsencrypt_dns_" .. server_name .. " from datastore : " .. err
)
elseif data then
return self:ret(true, "certificate/key data found", data)
end
return self:ret(true, "let's encrypt dns is not used")
end
function letsencrypt_dns:load_data(data, server_name)
-- Load certificate
local cert_chain, err = parse_pem_cert(data[1])
if not cert_chain then
return false, "error while parsing pem cert : " .. err
end
-- Load key
local priv_key
priv_key, err = parse_pem_priv_key(data[2])
if not priv_key then
return false, "error while parsing pem priv key : " .. err
end
-- Cache data
for key in server_name:gmatch("%S+") do
local cache_key = "plugin_letsencrypt_dns_" .. key
local ok
ok, err = self.datastore:set(cache_key, { cert_chain, priv_key }, nil, true)
if not ok then
return false, "error while setting data into datastore : " .. err
end
end
return true
end
return letsencrypt_dns

View file

@ -0,0 +1,113 @@
{
"id": "letsencrypt_dns",
"name": "Let's Encrypt DNS",
"description": "Automatic creation, renewal and configuration of Let's Encrypt certificates using DNS challenges.",
"version": "0.7",
"stream": "yes",
"settings": {
"AUTO_LETS_ENCRYPT_DNS": {
"context": "multisite",
"default": "no",
"help": "Activate automatic Let's Encrypt DNS.",
"id": "auto-lets-encrypt-dns",
"label": "Automatic Let's Encrypt Dns",
"regex": "^(yes|no)$",
"type": "check"
},
"LETS_ENCRYPT_DNS_EMAIL": {
"context": "multisite",
"default": "",
"help": "The email address to use for Let's Encrypt notifications.",
"id": "lets-encrypt-dns-email",
"label": "Email Address for Notifications",
"regex": "^([^@ \\t\\r\\n]+@[^@ \\t\\r\\n]+\\.[^@ \\t\\r\\n]+)?$",
"type": "text"
},
"USE_LETS_ENCRYPT_DNS_STAGING": {
"context": "multisite",
"default": "no",
"help": "Use the Let's Encrypt staging environment.",
"id": "use-lets-encrypt-dns-staging",
"label": "Use Let's Encrypt DNS Staging",
"regex": "^(yes|no)$",
"type": "check"
},
"LETS_ENCRYPT_DNS_PROVIDER": {
"context": "multisite",
"default": "",
"help": "The DNS provider to use for DNS challenges.",
"id": "auto-lets-encrypt-dns-provider",
"label": "DNS Provider",
"regex": "^(cloudflare|digitalocean|google|linode|ovh|rfc2136|route53|scaleway)?$",
"type": "select",
"select": [
"",
"cloudflare",
"digitalocean",
"google",
"linode",
"ovh",
"rfc2136",
"route53",
"scaleway"
]
},
"USE_LETS_ENCRYPT_DNS_WILDCARD": {
"context": "multisite",
"default": "yes",
"help": "Create wildcard certificates for all domains using DNS challenges.",
"id": "use-lets-encrypt-dns-wildcard",
"label": "Wildcard Certificates",
"regex": "^(yes|no)$",
"type": "check"
},
"LETS_ENCRYPT_DNS_PROPAGATION": {
"context": "multisite",
"default": "default",
"help": "The time to wait for DNS propagation in seconds.",
"id": "lets-encrypt-dns-propagation",
"label": "DNS Propagation",
"regex": "^(default|\\d+)$",
"type": "text"
},
"LETS_ENCRYPT_DNS_CREDENTIAL_ITEM": {
"context": "multisite",
"default": "",
"help": "Configuration item that will be added to the credentials.ini file for the DNS provider (e.g. 'cloudflare_api_token 123456').",
"id": "lets-encrypt-dns-credential-item",
"label": "Credential Item",
"regex": "^(\\w+ .+)?$",
"type": "password",
"multiple": "lets-encrypt-dns-credential-item"
},
"LETS_ENCRYPT_DNS_CLEAR_OLD_CERTS": {
"context": "global",
"default": "no",
"help": "Clear old certificates when renewing.",
"id": "lets-encrypt-dns-clear-old-certs",
"label": "Clear old certificates when they are no longer needed",
"regex": "^(yes|no)$",
"type": "check"
}
},
"jobs": [
{
"name": "install-lets-encrypt-dns-dependencies",
"file": "install-lets-encrypt-dns-dependencies.py",
"every": "once",
"reload": false
},
{
"name": "dns-certbot-new",
"file": "dns-certbot-new.py",
"every": "once",
"reload": false
},
{
"name": "dns-certbot-renew",
"file": "dns-certbot-renew.py",
"every": "day",
"reload": false
}
]
}

View file

@ -0,0 +1,9 @@
certbot-dns-cloudflare==2.11.0
certbot-dns-digitalocean==2.11.0
certbot-dns-google==2.11.0
certbot-dns-linode==2.11.0
certbot-dns-ovh==2.11.0
certbot-dns-rfc2136==2.11.0
certbot-dns-route53==2.11.0
certbot-dns-scaleway==0.0.7
pydantic==2.9.2

View file

@ -0,0 +1,686 @@
#
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
#
# pip-compile --allow-unsafe --generate-hashes --strip-extras requirements.in
#
acme==2.11.0 \
--hash=sha256:23213ac3074a78862b219e0a30e141fd53238a8bdcf0668bd4dea59b28873fb8 \
--hash=sha256:f4950015cf52ff0de12f37fc28034c7710aca63f64f1696253d2f6cb9f22645e
# via
# certbot
# certbot-dns-cloudflare
# certbot-dns-digitalocean
# certbot-dns-google
# certbot-dns-linode
# certbot-dns-ovh
# certbot-dns-rfc2136
# certbot-dns-route53
# certbot-dns-scaleway
annotated-types==0.7.0 \
--hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \
--hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89
# via pydantic
attrs==24.2.0 \
--hash=sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346 \
--hash=sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2
# via jsonlines
beautifulsoup4==4.12.3 \
--hash=sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051 \
--hash=sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed
# via dns-lexicon
boto3==1.35.24 \
--hash=sha256:97fcc1a14cbc759e4ba9535ced703a99fcf652c9c4b8dfcd06f292c80551684b \
--hash=sha256:be7807f30f26d6c0057e45cfd09dad5968e664488bf4f9138d0bb7a0f6d8ed40
# via certbot-dns-route53
botocore==1.35.24 \
--hash=sha256:1e59b0f14f4890c4f70bd6a58a634b9464bed1c4c6171f87c8795d974ade614b \
--hash=sha256:eb9ccc068255cc3d24c36693fda6aec7786db05ae6c2b13bcba66dce6a13e2e3
# via
# boto3
# s3transfer
cachetools==5.5.0 \
--hash=sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292 \
--hash=sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a
# via google-auth
certbot==2.11.0 \
--hash=sha256:257ae1cb0a534373ca50dd807c9ae96f27660e41379c45afb9b50cab0e6a7a97 \
--hash=sha256:dc4e0a48bcb09448d60362170ca1047cc9a81966da0dd35135f2561f0ea7d5b1
# via
# certbot-dns-cloudflare
# certbot-dns-digitalocean
# certbot-dns-google
# certbot-dns-linode
# certbot-dns-ovh
# certbot-dns-rfc2136
# certbot-dns-route53
# certbot-dns-scaleway
certbot-dns-cloudflare==2.11.0 \
--hash=sha256:2a3e06a692add6aacdc2dfe7fada695483f5fbf4fed073eabd36f3d7745f0c7a \
--hash=sha256:42788044840328de1fe85ea32df1254823f1452e0479a60445fd364f8234a4a9
# via -r requirements.in
certbot-dns-digitalocean==2.11.0 \
--hash=sha256:30e9543baef204e110dacb1e6adf9b1fd04777a14a204bd3cdbfb100c6f6e32a \
--hash=sha256:d5166fc7eb3b3e8a8de4b43e7485d60eda4225db1d525b6f096949d1487e1c6c
# via -r requirements.in
certbot-dns-google==2.11.0 \
--hash=sha256:6af70452913e472f74788e76375ff00a6c427e59c896d7b982732ba33a09a199 \
--hash=sha256:de5fd15b4b60e652ea41e556fccf09376fdc7f881ec7544cb1e25176b2a1a5bf
# via -r requirements.in
certbot-dns-linode==2.11.0 \
--hash=sha256:4727015830ff048e925d2acee26e9bd727e03cb3ceb29d228d45c6602b6f964f \
--hash=sha256:60848af4c336928f0b069d350accae5abd5896118c36920247972b6759aa1ba6
# via -r requirements.in
certbot-dns-ovh==2.11.0 \
--hash=sha256:1a9ccd1d987c0448dd9050a3ac43558569a6d887f2fcde148f2699c4dc624a26 \
--hash=sha256:6be4feb03782bf2dc876319df0a54ee567241d777132546785b1f7c072e6a2df
# via -r requirements.in
certbot-dns-rfc2136==2.11.0 \
--hash=sha256:413a80c09e3a00162d9f7833cb2f5ed3690ae0833e09be84c795b7ee5a357c4b \
--hash=sha256:aaf9f6b387359734b4138f179f96f480889d9a4e2e44fae60c9ebe4d8715f567
# via -r requirements.in
certbot-dns-route53==2.11.0 \
--hash=sha256:2492bd62fbe514a259d4a0b3455d576b267b9a82f26f396af136d9ec2a9c0ba8 \
--hash=sha256:5fd11e3546175574ccc51aaeccb19860e00c633f9bbeb0c4d033bac5553b15bd
# via -r requirements.in
certbot-dns-scaleway==0.0.7 \
--hash=sha256:999dda5b8689277facb77e1757b8a6b207baeecc0ded0c27aea2c51331affc92 \
--hash=sha256:bc0833ed71a5cd314a93f8d02144c54e17066ee1e2b950ed868b14b2211f5e9e
# via -r requirements.in
certifi==2024.8.30 \
--hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8 \
--hash=sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9
# via requests
cffi==1.17.1 \
--hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \
--hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \
--hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \
--hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \
--hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \
--hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \
--hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \
--hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \
--hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \
--hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \
--hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \
--hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \
--hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \
--hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \
--hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \
--hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \
--hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \
--hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \
--hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \
--hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \
--hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \
--hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \
--hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \
--hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \
--hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \
--hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \
--hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \
--hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \
--hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \
--hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \
--hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \
--hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \
--hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \
--hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \
--hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \
--hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \
--hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \
--hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \
--hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \
--hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \
--hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \
--hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \
--hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \
--hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \
--hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \
--hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \
--hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \
--hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \
--hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \
--hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \
--hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \
--hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \
--hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \
--hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \
--hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \
--hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \
--hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \
--hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \
--hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \
--hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \
--hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \
--hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \
--hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \
--hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \
--hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \
--hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \
--hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b
# via cryptography
charset-normalizer==3.3.2 \
--hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \
--hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \
--hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \
--hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \
--hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \
--hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \
--hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \
--hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \
--hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \
--hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \
--hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \
--hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \
--hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \
--hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \
--hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \
--hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \
--hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \
--hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \
--hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \
--hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \
--hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \
--hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \
--hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \
--hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \
--hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \
--hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \
--hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \
--hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \
--hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \
--hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \
--hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \
--hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \
--hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \
--hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \
--hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \
--hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \
--hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \
--hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \
--hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \
--hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \
--hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \
--hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \
--hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \
--hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \
--hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \
--hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \
--hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \
--hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \
--hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \
--hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \
--hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \
--hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \
--hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \
--hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \
--hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \
--hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \
--hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \
--hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \
--hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \
--hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \
--hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \
--hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \
--hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \
--hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \
--hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \
--hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \
--hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \
--hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \
--hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \
--hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \
--hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \
--hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \
--hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \
--hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \
--hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \
--hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \
--hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \
--hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \
--hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \
--hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \
--hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \
--hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \
--hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \
--hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \
--hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \
--hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \
--hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \
--hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \
--hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \
--hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561
# via requests
cloudflare==2.19.4 \
--hash=sha256:3b6000a01a237c23bccfdf6d20256ea5111ec74a826ae9e74f9f0e5bb5b2383f
# via certbot-dns-cloudflare
configargparse==1.7 \
--hash=sha256:d249da6591465c6c26df64a9f73d2536e743be2f244eb3ebe61114af2f94f86b \
--hash=sha256:e7067471884de5478c58a511e529f0f9bd1c66bfef1dea90935438d6c23306d1
# via certbot
configobj==5.0.9 \
--hash=sha256:03c881bbf23aa07bccf1b837005975993c4ab4427ba57f959afdd9d1a2386848
# via certbot
cryptography==43.0.1 \
--hash=sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494 \
--hash=sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806 \
--hash=sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d \
--hash=sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062 \
--hash=sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2 \
--hash=sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4 \
--hash=sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1 \
--hash=sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85 \
--hash=sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84 \
--hash=sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042 \
--hash=sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d \
--hash=sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962 \
--hash=sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2 \
--hash=sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa \
--hash=sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d \
--hash=sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365 \
--hash=sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96 \
--hash=sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47 \
--hash=sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d \
--hash=sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d \
--hash=sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c \
--hash=sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb \
--hash=sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277 \
--hash=sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172 \
--hash=sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034 \
--hash=sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a \
--hash=sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289
# via
# acme
# certbot
# dns-lexicon
# josepy
# pyopenssl
distro==1.9.0 \
--hash=sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed \
--hash=sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2
# via certbot
dns-lexicon==3.18.0 \
--hash=sha256:aabe320093b4f9a7f7e0e430551ae49c38c9cf99b45ef7e28da238c50106b1a0 \
--hash=sha256:c2b1005a6621a2ec648131d96ec61304b90b98842af9ff62b1840ddf9d0e2c26
# via
# certbot-dns-linode
# certbot-dns-ovh
dnspython==2.6.1 \
--hash=sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50 \
--hash=sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc
# via
# certbot-dns-rfc2136
# dns-lexicon
filelock==3.16.1 \
--hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \
--hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435
# via tldextract
google-api-core==2.20.0 \
--hash=sha256:ef0591ef03c30bb83f79b3d0575c3f31219001fc9c5cf37024d08310aeffed8a \
--hash=sha256:f74dff1889ba291a4b76c5079df0711810e2d9da81abfdc99957bc961c1eb28f
# via google-api-python-client
google-api-python-client==2.146.0 \
--hash=sha256:41f671be10fa077ee5143ee9f0903c14006d39dc644564f4e044ae96b380bf68 \
--hash=sha256:b1e62c9889c5ef6022f11d30d7ef23dc55100300f0e8aaf8aa09e8e92540acad
# via certbot-dns-google
google-auth==2.35.0 \
--hash=sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f \
--hash=sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a
# via
# certbot-dns-google
# google-api-core
# google-api-python-client
# google-auth-httplib2
google-auth-httplib2==0.2.0 \
--hash=sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05 \
--hash=sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d
# via google-api-python-client
googleapis-common-protos==1.65.0 \
--hash=sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63 \
--hash=sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0
# via google-api-core
httplib2==0.22.0 \
--hash=sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc \
--hash=sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81
# via
# google-api-python-client
# google-auth-httplib2
idna==3.10 \
--hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
--hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
# via
# requests
# tldextract
importlib-metadata==8.5.0 \
--hash=sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b \
--hash=sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7
# via
# certbot
# dns-lexicon
jmespath==1.0.1 \
--hash=sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 \
--hash=sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe
# via
# boto3
# botocore
josepy==1.14.0 \
--hash=sha256:308b3bf9ce825ad4d4bba76372cf19b5dc1c2ce96a9d298f9642975e64bd13dd \
--hash=sha256:d2b36a30f316269f3242f4c2e45e15890784178af5ec54fa3e49cf9234ee22e0
# via
# acme
# certbot
jsonlines==4.0.0 \
--hash=sha256:0c6d2c09117550c089995247f605ae4cf77dd1533041d366351f6f298822ea74 \
--hash=sha256:185b334ff2ca5a91362993f42e83588a360cf95ce4b71a73548502bda52a7c55
# via cloudflare
jsonpickle==3.3.0 \
--hash=sha256:287c12143f35571ab00e224fa323aa4b090d5a7f086f5f494d7ee9c7eb1a380a \
--hash=sha256:ab467e601e5b1a1cd76f1819d014795165da071744ef30bf3786e9bc549de25a
# via python-digitalocean
mock==5.1.0 \
--hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \
--hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d
# via certbot-dns-scaleway
parsedatetime==2.6 \
--hash=sha256:4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455 \
--hash=sha256:cb96edd7016872f58479e35879294258c71437195760746faffedb692aef000b
# via certbot
proto-plus==1.24.0 \
--hash=sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445 \
--hash=sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12
# via google-api-core
protobuf==5.28.2 \
--hash=sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132 \
--hash=sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f \
--hash=sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece \
--hash=sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0 \
--hash=sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f \
--hash=sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0 \
--hash=sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276 \
--hash=sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7 \
--hash=sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3 \
--hash=sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36 \
--hash=sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d
# via
# google-api-core
# googleapis-common-protos
# proto-plus
pyasn1==0.6.1 \
--hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \
--hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034
# via
# pyasn1-modules
# rsa
pyasn1-modules==0.4.1 \
--hash=sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd \
--hash=sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c
# via google-auth
pycparser==2.22 \
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
--hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
# via cffi
pydantic==2.9.2 \
--hash=sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f \
--hash=sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12
# via -r requirements.in
pydantic-core==2.23.4 \
--hash=sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36 \
--hash=sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05 \
--hash=sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071 \
--hash=sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327 \
--hash=sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c \
--hash=sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36 \
--hash=sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29 \
--hash=sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744 \
--hash=sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d \
--hash=sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec \
--hash=sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e \
--hash=sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e \
--hash=sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577 \
--hash=sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232 \
--hash=sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863 \
--hash=sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6 \
--hash=sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368 \
--hash=sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480 \
--hash=sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2 \
--hash=sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2 \
--hash=sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6 \
--hash=sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769 \
--hash=sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d \
--hash=sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2 \
--hash=sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84 \
--hash=sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166 \
--hash=sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271 \
--hash=sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5 \
--hash=sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb \
--hash=sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13 \
--hash=sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323 \
--hash=sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556 \
--hash=sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665 \
--hash=sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef \
--hash=sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb \
--hash=sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119 \
--hash=sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126 \
--hash=sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510 \
--hash=sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b \
--hash=sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87 \
--hash=sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f \
--hash=sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc \
--hash=sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8 \
--hash=sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21 \
--hash=sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f \
--hash=sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6 \
--hash=sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658 \
--hash=sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b \
--hash=sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3 \
--hash=sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb \
--hash=sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59 \
--hash=sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24 \
--hash=sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9 \
--hash=sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3 \
--hash=sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd \
--hash=sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753 \
--hash=sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55 \
--hash=sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad \
--hash=sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a \
--hash=sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605 \
--hash=sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e \
--hash=sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b \
--hash=sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433 \
--hash=sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8 \
--hash=sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07 \
--hash=sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728 \
--hash=sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0 \
--hash=sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327 \
--hash=sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555 \
--hash=sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64 \
--hash=sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6 \
--hash=sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea \
--hash=sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b \
--hash=sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df \
--hash=sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e \
--hash=sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd \
--hash=sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068 \
--hash=sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3 \
--hash=sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040 \
--hash=sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12 \
--hash=sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916 \
--hash=sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f \
--hash=sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f \
--hash=sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801 \
--hash=sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231 \
--hash=sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5 \
--hash=sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8 \
--hash=sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee \
--hash=sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607
# via pydantic
pyopenssl==24.2.1 \
--hash=sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95 \
--hash=sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d
# via
# acme
# josepy
pyotp==2.9.0 \
--hash=sha256:346b6642e0dbdde3b4ff5a930b664ca82abfa116356ed48cc42c7d6590d36f63 \
--hash=sha256:81c2e5865b8ac55e825b0358e496e1d9387c811e85bb40e71a3b29b288963612
# via dns-lexicon
pyparsing==3.1.4 \
--hash=sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c \
--hash=sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032
# via httplib2
pyrfc3339==1.1 \
--hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \
--hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a
# via
# acme
# certbot
python-dateutil==2.9.0.post0 \
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
# via botocore
python-digitalocean==1.17.0 \
--hash=sha256:0032168e022e85fca314eb3f8dfaabf82087f2ed40839eb28f1eeeeca5afb1fa \
--hash=sha256:107854fde1aafa21774e8053cf253b04173613c94531f75d5a039ad770562b24
# via certbot-dns-digitalocean
pytz==2024.2 \
--hash=sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a \
--hash=sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725
# via
# acme
# certbot
# pyrfc3339
pyyaml==6.0.2 \
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
--hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
--hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
--hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
--hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
--hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
--hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
--hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \
--hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
--hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
--hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \
--hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
--hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \
--hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
--hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \
--hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
--hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
--hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
--hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \
--hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \
--hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
--hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
--hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
--hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
--hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
--hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
--hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \
--hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
--hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
--hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
--hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \
--hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
--hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
--hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
--hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \
--hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \
--hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
--hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
--hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
--hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
--hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
--hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \
--hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \
--hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \
--hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
--hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \
--hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
--hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
# via
# cloudflare
# dns-lexicon
requests==2.32.3 \
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
# via
# acme
# certbot-dns-scaleway
# cloudflare
# dns-lexicon
# google-api-core
# python-digitalocean
# requests-file
# requests-mock
# tldextract
requests-file==2.1.0 \
--hash=sha256:0f549a3f3b0699415ac04d167e9cb39bccfb730cb832b4d20be3d9867356e658 \
--hash=sha256:cf270de5a4c5874e84599fc5778303d496c10ae5e870bfa378818f35d21bda5c
# via tldextract
requests-mock==1.12.1 \
--hash=sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563 \
--hash=sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401
# via certbot-dns-scaleway
rsa==4.9 \
--hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \
--hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21
# via google-auth
s3transfer==0.10.2 \
--hash=sha256:0711534e9356d3cc692fdde846b4a1e4b0cb6519971860796e6bc4c7aea00ef6 \
--hash=sha256:eca1c20de70a39daee580aef4986996620f365c4e0fda6a86100231d62f1bf69
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
setuptools==75.1.0 \
--hash=sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2 \
--hash=sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538
# via boto3
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via python-dateutil
soupsieve==2.6 \
--hash=sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb \
--hash=sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9
# via beautifulsoup4
tldextract==5.1.2 \
--hash=sha256:4dfc4c277b6b97fa053899fcdb892d2dc27295851ab5fac4e07797b6a21b2e46 \
--hash=sha256:c9e17f756f05afb5abac04fe8f766e7e70f9fe387adb1859f0f52408ee060200
# via dns-lexicon
typing-extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
# via
# pydantic
# pydantic-core
uritemplate==4.1.1 \
--hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \
--hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e
# via google-api-python-client
urllib3==1.26.20 \
--hash=sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e \
--hash=sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32
# via
# botocore
# requests
zipp==3.20.2 \
--hash=sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350 \
--hash=sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29
# via
# acme
# certbot
# certbot-dns-cloudflare
# certbot-dns-digitalocean
# certbot-dns-google
# certbot-dns-linode
# certbot-dns-ovh
# certbot-dns-rfc2136
# certbot-dns-route53
# certbot-dns-scaleway

View file

@ -14,6 +14,7 @@
"dnsbl",
"customcert",
"letsencrypt",
"letsencrypt_dns",
"selfsigned"
],
"set": [
@ -21,10 +22,11 @@
"whitelist",
"letsencrypt",
"customcert",
"letsencrypt_dns",
"selfsigned",
"ui"
],
"ssl_certificate": ["customcert", "letsencrypt", "selfsigned"],
"ssl_certificate": ["customcert", "letsencrypt_dns", "letsencrypt", "selfsigned"],
"access": [
"whitelist",
"letsencrypt",