Skip to content

Commit d1c106a

Browse files
committed
Improve cli logging and experience
1 parent 4819fd2 commit d1c106a

File tree

3 files changed

+57
-23
lines changed

3 files changed

+57
-23
lines changed

src/certapi/challenge_solver/InmemoryChallengeSolver.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ def __init__(self):
1313
def supported_challenge_type(self) -> Literal["http-01"]:
1414
return "http-01"
1515

16+
def supports_domain(self, domain: str) -> bool:
17+
return "*" not in domain
18+
1619
def save_challenge(self, key: str, value: str, domain: str = None):
1720
self.challenges[key] = value
1821

@@ -23,6 +26,9 @@ def delete_challenge(self, key: str, domain: str = None):
2326
if key in self.challenges:
2427
del self.challenges[key]
2528

29+
def cleanup_old_challenges(self):
30+
self.challenges.clear()
31+
2632
def __iter__(self):
2733
return iter(self.challenges)
2834

src/certapi/cli.py

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111

1212
import requests
1313

14-
from certapi import AcmeCertIssuer, CertApiException, CloudflareChallengeSolver, InMemoryChallengeSolver, Key
14+
from certapi import (
15+
AcmeCertIssuer,
16+
CertApiException,
17+
CloudflareChallengeSolver,
18+
FileSystemKeyStore,
19+
InMemoryChallengeSolver,
20+
)
21+
from certapi.crypto import certs_from_pem
1522

1623

1724
def is_root() -> bool:
@@ -95,14 +102,29 @@ def obtain_certificate(domains: List[str], api_key: Optional[str] = None):
95102
print("Starting HTTP challenge server on port 80...")
96103
server, _ = _start_http_challenge_server(challenge_solver, port=80)
97104

98-
cert_issuer = AcmeCertIssuer(Key.generate("ecdsa"), challenge_solver)
105+
keystore_path = "/etc/ssl"
106+
key_store = FileSystemKeyStore(keystore_path)
107+
cert_issuer = AcmeCertIssuer.with_keystore(
108+
key_store,
109+
challenge_solver,
110+
account_key_name="acme_account",
111+
)
99112
cert_issuer.setup()
100113
try:
101-
key, cert = cert_issuer.generate_key_and_cert_for_domains(domains)
102-
print("------ Private Key -----")
103-
print(key.to_pem().decode("utf-8"))
104-
print("------- Certificate ------")
105-
print(cert)
114+
key, cert = cert_issuer.generate_key_and_cert_for_domains(domains, key_type="rsa")
115+
key_name = domains[0]
116+
key_id = key_store.save_key(key, key_name)
117+
key_store.save_cert(key_id, cert, domains, name=key_name)
118+
119+
cert_chain = certs_from_pem(cert.encode("utf-8"))
120+
leaf_cert = cert_chain[0] if cert_chain else None
121+
expiry = leaf_cert.not_valid_after.isoformat() if leaf_cert else "unknown"
122+
123+
key_path = os.path.join(key_store.keys_dir, f"{key_name}.key")
124+
cert_path = os.path.join(key_store.certs_dir, f"{key_name}.crt")
125+
print(f"\n Certificate expires at: {expiry}")
126+
print(f" Key path: {key_path}")
127+
print(f" Cert path: {cert_path}")
106128
except CertApiException as e:
107129
print("An error occurred:")
108130
print(e.json_obj())
@@ -113,60 +135,66 @@ def obtain_certificate(domains: List[str], api_key: Optional[str] = None):
113135

114136

115137
def verify_environment(domains: List[str], api_key: Optional[str] = None) -> None:
116-
print("certapi is installed and CLI is working.")
138+
print("[verify] certapi CLI ready")
117139
if api_key:
118-
print("Cloudflare API key detected. DNS challenge is available.")
140+
print("[verify] Cloudflare API key detected; DNS-01 challenge available")
119141
if domains:
120142
solver = CloudflareChallengeSolver(api_key=api_key)
121143
unsupported = [domain for domain in domains if not solver.supports_domain(domain)]
122144
if unsupported:
123-
print("Warning: Cloudflare account does not appear to manage:")
145+
print("[verify] Warning: Cloudflare account does not appear to manage:")
124146
for domain in unsupported:
125-
print(f"- {domain}")
147+
print(f"[verify] - {domain}")
126148
else:
127-
print("Cloudflare account appears to manage the provided domain(s).")
149+
print("[verify] Cloudflare account appears to manage the provided domain(s)")
128150
else:
129-
print("Cloudflare API key not detected. HTTP challenge will be used.")
151+
print("[verify] Cloudflare API key not detected; HTTP-01 challenge will be used")
130152
if not domains:
131-
print("No domains provided. Skipping HTTP routing check.")
153+
print("[verify] No domains provided; skipping HTTP routing check")
132154
return
133155
if is_root():
134156
pids = find_process_on_port(80)
135157
if pids:
136-
print(f"Warning: port 80 is in use by process(es): {', '.join(pids)}")
158+
print(f"[verify] Warning: port 80 is in use by process(es): {', '.join(pids)}")
137159
else:
138-
print("Port 80 is available.")
160+
print("[verify] Port 80 is available")
139161
else:
140-
print("Warning: not running as root, port 80 binding will fail.")
162+
print("[verify] Warning: not running as root; port 80 binding will fail")
141163
return
142164

143165
_ensure_port_80_available()
144166
challenge_solver = InMemoryChallengeSolver()
145-
print("Starting HTTP challenge server on port 80...")
167+
print("[verify] Starting HTTP challenge server on port 80...")
146168
server, _ = _start_http_challenge_server(challenge_solver, port=80)
169+
ok = 0
170+
failed = 0
147171
try:
148172
for domain in domains:
149173
token = secrets.token_urlsafe(24)
150174
value = secrets.token_urlsafe(32)
151175
challenge_solver.save_challenge(token, value, domain)
152176
url = f"http://{domain}/.well-known/acme-challenge/{token}"
153-
print(f"Verifying HTTP routing for {domain}...")
177+
print(f"[verify] Checking HTTP routing for {domain}...")
154178
try:
155179
response = requests.get(url, allow_redirects=False, timeout=5)
156180
if response.status_code == 200 and response.text.strip() == value:
157-
print(f"OK: {domain} routes to this server.")
181+
ok += 1
182+
print(f"[verify] OK: {domain} routes to this server")
158183
else:
184+
failed += 1
159185
print(
160-
f"Failed: {domain} returned status {response.status_code} "
186+
f"[verify] FAILED: {domain} returned status {response.status_code} "
161187
f"with body '{response.text.strip()}'"
162188
)
163189
except requests.RequestException as exc:
164-
print(f"Failed: {domain} request error: {exc}")
190+
failed += 1
191+
print(f"[verify] FAILED: {domain} request error: {exc}")
165192
finally:
166193
challenge_solver.delete_challenge(token, domain)
167194
finally:
168195
server.shutdown()
169196
server.server_close()
197+
print(f"\n Summary: {ok} OK, {failed} FAILED")
170198

171199

172200
def main():

src/certapi/issuers/AcmeCertIssuer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def __init__(
2525
def with_keystore(
2626
key_store: "KeyStore",
2727
challenge_solver: ChallengeSolver,
28-
account_key_name: str = "acme_account.key",
28+
account_key_name: str = "acme_account",
2929
acme_url: str = None,
3030
) -> "AcmeCertIssuer":
3131
account_key, _ = key_store._get_or_generate_key(account_key_name, "rsa")

0 commit comments

Comments
 (0)