-
Notifications
You must be signed in to change notification settings - Fork 642
Expand file tree
/
Copy pathcertbot-renew.py
More file actions
158 lines (139 loc) · 5.46 KB
/
Copy pathcertbot-renew.py
File metadata and controls
158 lines (139 loc) · 5.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env python3
from os import getenv, sep
from os.path import join
from subprocess import DEVNULL, PIPE, Popen, TimeoutExpired, run
from sys import exit as sys_exit, path as sys_path
from time import monotonic
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 getLogger # type: ignore
from jobs import Job # type: ignore
from letsencrypt_utils import (
CERTBOT_BIN,
DEPS_PATH,
LETSENCRYPT_DATA_PATH as DATA_PATH,
LETSENCRYPT_LOGS_DIR as LOGS_DIR,
LETSENCRYPT_WORK_DIR as WORK_DIR,
ZEROSSL_BOT_SCRIPT,
build_certbot_env,
is_zerossl_used_in_env,
prepare_logs_dir,
resolve_certbot_entrypoint,
)
LOGGER = getLogger("LETS-ENCRYPT.RENEW")
LOGGER_CERTBOT = getLogger("LETS-ENCRYPT.RENEW.CERTBOT")
CERTBOT_TIMEOUT = 600 # seconds
CERTBOT_TIMEOUT = 900 # 15 minutes max for a single certbot invocation
status = 0
try:
# Check if we're using let's encrypt
use_letsencrypt = False
if getenv("MULTISITE", "no") == "no":
use_letsencrypt = getenv("AUTO_LETS_ENCRYPT", "no") == "yes"
else:
for first_server in getenv("SERVER_NAME", "www.example.com").split():
if first_server and getenv(f"{first_server}_AUTO_LETS_ENCRYPT", "no") == "yes":
use_letsencrypt = True
break
if not use_letsencrypt:
LOGGER.info("Let's Encrypt is not activated, skipping renew...")
sys_exit(0)
prepare_logs_dir(LOGS_DIR, LOGGER)
JOB = Job(LOGGER, __file__)
cmd_env = build_certbot_env(JOB, DEPS_PATH)
acme_server = "zerossl" if is_zerossl_used_in_env() else "letsencrypt"
certbot_entrypoint = resolve_certbot_entrypoint(
acme_server,
CERTBOT_BIN,
ZEROSSL_BOT_SCRIPT,
LOGGER,
cmd_env=cmd_env,
fallback_to_certbot=True,
)
certbot_bin = certbot_entrypoint[0]
if acme_server == "zerossl" and certbot_bin != CERTBOT_BIN:
LOGGER.info("Using zerossl-bot wrapper for certificate renewal.")
process = Popen(
[
certbot_bin,
"renew",
"-n",
"--no-random-sleep-on-renew",
"--config-dir",
DATA_PATH.as_posix(),
"--work-dir",
WORK_DIR,
"--logs-dir",
LOGS_DIR,
]
+ (["-v"] if getenv("CUSTOM_LOG_LEVEL", getenv("LOG_LEVEL", "INFO")).upper() == "DEBUG" else []),
stdin=DEVNULL,
stdout=PIPE,
stderr=PIPE,
universal_newlines=True,
env=cmd_env,
)
try:
stdout, stderr = process.communicate(timeout=CERTBOT_TIMEOUT)
except TimeoutExpired:
LOGGER.error(f"certbot renew timed out after {CERTBOT_TIMEOUT}s, killing process.")
process.kill()
stdout, stderr = process.communicate()
status = 2
if stdout:
for line in stdout.splitlines():
line_str = line.strip()
if line_str:
LOGGER_CERTBOT.info(line_str)
if "(success)" in line_str or "Congratulations" in line_str:
status = 1
if stderr:
for line in stderr.splitlines():
LOGGER_CERTBOT.info(line.strip())
deadline = monotonic() + CERTBOT_TIMEOUT
while process.poll() is None:
if monotonic() > deadline:
LOGGER.error(f"certbot renew timed out after {CERTBOT_TIMEOUT}s, killing process.")
process.kill()
process.wait()
status = 2
break
if process.stderr:
for line in process.stderr:
LOGGER_CERTBOT.info(line.strip())
if process.returncode and process.returncode != 0:
status = 2
LOGGER.error("Certificates renewal failed")
# Save Let's Encrypt data to db cache (full directory)
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 data to db cache : {err}")
else:
LOGGER.info("Successfully saved Let's Encrypt data to db cache")
# Trigger OCSP refresh after successful renewal (AFTER database save)
# OCSP job will compare new certs with cached ones and process differential updates
if status == 1 and getenv("SSL_USE_OCSP_STAPLING", "yes").lower() == "yes":
LOGGER.info("🔄 OCSP triggering refresh for renewed certificates")
try:
import sys
ocsp_script = join(sep, "usr", "share", "bunkerweb", "core", "ssl", "jobs", "ocsp-refresh.py")
result = run([sys.executable, ocsp_script, "--force"], stdin=DEVNULL, capture_output=True, text=True, timeout=300)
if result.returncode == 0:
LOGGER.info("✓ OCSP refresh completed successfully after renewal")
else:
LOGGER.warning(f"⚠️ OCSP refresh returned exit code {result.returncode}")
if result.stderr:
for line in result.stderr.strip().splitlines():
LOGGER.debug(f"OCSP: {line}")
except Exception as e:
LOGGER.warning(f"⚠️ OCSP post-renewal refresh failed (non-fatal): {e}")
except SystemExit as e:
status = e.code
except BaseException as e:
status = 2
LOGGER.debug(format_exc())
LOGGER.error(f"Exception while running certbot-renew.py :\n{e}")
sys_exit(status)