From ae1933a8b7485a367cd334284527fb6ab8923d10 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Sun, 13 Sep 2026 22:59:16 -0400 Subject: [PATCH] amail: certbot fetches the door's certificate, since Caddy can't MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Cloudflare origin cert is a self-issued *.aesthetic.computer wildcard, and Caddy skips ACME for any name a loaded cert already covers — so it would never mint one for inbound.aesthetic.computer. Caddy now serves certbot's HTTP-01 webroot on :80 ahead of the redirect, and the door reads /etc/letsencrypt first. Co-Authored-By: Claude Fable 5.1 --- lith/Caddyfile | 16 +++++++++++++--- lith/mail-inbound.mjs | 21 ++++++++++++++------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/lith/Caddyfile b/lith/Caddyfile index b905bb859e..6676ba13a0 100644 --- a/lith/Caddyfile +++ b/lith/Caddyfile @@ -1246,13 +1246,23 @@ gym.anthonyzollo.com { # --- inbound.aesthetic.computer --- # Not a website. This is the SMTP door for Amail (lith/mail-inbound.mjs, # lith-mail.service on :25) — Google Workspace routes every unknown -# @aesthetic.computer address here. Caddy serves the host only so it holds a -# Let's Encrypt certificate the SMTP server offers for STARTTLS. The DNS -# record is DNS-only (grey cloud): Cloudflare does not proxy port 25. +# @aesthetic.computer address here. The DNS record is DNS-only (grey cloud): +# Cloudflare does not proxy port 25. +# +# Its STARTTLS certificate can't come from Caddy's automation: the Cloudflare +# origin cert loaded above is a self-issued *.aesthetic.computer wildcard, and +# Caddy skips ACME for any name a loaded cert already covers. So certbot +# fetches it by HTTP-01 through the webroot served below (:80), and the door +# reads /etc/letsencrypt/live/inbound.aesthetic.computer/. inbound.aesthetic.computer { respond "Amail inbound — SMTP on :25" 200 } :80 { + # certbot's HTTP-01 challenge files, before the redirect swallows them. + handle /.well-known/acme-challenge/* { + root * /var/lib/amail-acme + file_server + } redir https://{host}{uri} 301 } diff --git a/lith/mail-inbound.mjs b/lith/mail-inbound.mjs index 2547658741..e85b933fe3 100644 --- a/lith/mail-inbound.mjs +++ b/lith/mail-inbound.mjs @@ -103,14 +103,21 @@ export function letterText(parsed) { .trim(); } -// Caddy holds a Let's Encrypt cert for the host (the Caddyfile serves it for -// exactly this reason). Offer STARTTLS with it when it exists. +// The host's Let's Encrypt cert, for STARTTLS. certbot keeps it under +// /etc/letsencrypt (see the Caddyfile for why Caddy can't mint this one); +// Caddy's own store is checked second in case that ever changes. function tlsFor(host) { - const dir = `/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${host}`; - const key = `${dir}/${host}.key`; - const cert = `${dir}/${host}.crt`; - if (existsSync(key) && existsSync(cert)) { - return { key: readFileSync(key), cert: readFileSync(cert) }; + const places = [ + [`/etc/letsencrypt/live/${host}/privkey.pem`, `/etc/letsencrypt/live/${host}/fullchain.pem`], + [ + `/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${host}/${host}.key`, + `/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${host}/${host}.crt`, + ], + ]; + for (const [key, cert] of places) { + if (existsSync(key) && existsSync(cert)) { + return { key: readFileSync(key), cert: readFileSync(cert) }; + } } return null; } -- 2.51.2