From 98f40488aa6399b54c7601a933030626693fe274 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 22 Dec 2025 08:40:25 +0000 Subject: [PATCH] feat: Add optional DNS hook mode for certonly Co-authored-by: mathieumoran1 --- python_video_server/README.md | 12 +++++- .../__pycache__/letsencrypt.cpython-312.pyc | Bin 14502 -> 15062 bytes python_video_server/letsencrypt.py | 40 ++++++++++++++---- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/python_video_server/README.md b/python_video_server/README.md index f8311a5..54d0eba 100644 --- a/python_video_server/README.md +++ b/python_video_server/README.md @@ -52,13 +52,23 @@ python3 -m python_video_server.letsencrypt certonly \ --domains example.com,www.example.com ``` -During issuance, you'll be prompted to create a TXT record like: +During issuance, certbot will prompt you to create a TXT record like: - Name: `_acme-challenge.example.com` - Type: `TXT` - Value: (a token certbot prints) After it propagates, press Enter and certbot will continue. +If you run into a situation where the prompt/token only shows up in logs, you can try the hook-based mode: + +```bash +python3 -m python_video_server.letsencrypt certonly \ + --challenge dns \ + --dns-hooks \ + --email you@example.com \ + --domains example.com,www.example.com +``` + Certificates will be written under: - `python_video_server/certs/config/live//fullchain.pem` - `python_video_server/certs/config/live//privkey.pem` diff --git a/python_video_server/__pycache__/letsencrypt.cpython-312.pyc b/python_video_server/__pycache__/letsencrypt.cpython-312.pyc index 7d125dbac28e073c70a0cb4a46c0503154bc900a..709b682ea4f5f9fddd475c29cdeed57670a93398 100644 GIT binary patch delta 1609 zcmaJ>Uu;@c80SDQh0BGq*4jeHxD;r)j!U6SX_vUpCG9el(P-^Dw?>BC+XAB(Jok2M z8C|o)2NpHydX{WWW|nAX7TcEvHH-hEuk%I2R6-g}_O=&OZ69VBbh z538@Ub1gX|)=_y%aW*t7s+6lfY5w!-JjiPV-KKtns4kT>?w{MEml8=esOeas1%O^bO)k)O~BV=dhLclBNK(r z_C~rms-laFP{~N?rJRoE=VQ`BEd2z1)!RkENgRxR=$*A3hp%EY$MC$Y#3Uh>&PXDh zBHoa509eqH^JPOtFGzAUk;$Bw(F5n4>nwIx7_#;o86_Hnb8-blA=xP$AmAtRDVNg_ z$Ht%dlRp9IJ5K<8?3!Nroz`~K1!mW*V$7>8H%!+|4-NJ$8+X@JvK_h_DI6;--rTp< zH+p}u)OX^0##soh=F3cX+3eafcdUkxsch~iR=8w#Z0jkT={FT+G!??zjLK+vxIa*S z)_Nm#EmiKemo1KRkGsseb`4q*rZt+bhPmzb|BG|)joWT7F1bHad#7>vPxL%5z$G3o zNL(9qV+OVmc#QzL4UjZGJcA}YHV{Sgo?#F}cRVbW!1OqJ;CW0l*uqT@c782>$wk$) z(t(NUK>mYA94^7VfxDiJdOz3yLf>ZpMhh|@v{N2|7C3ak>qMV>+coM`C;G!{174(f z9U5Nnp_^VOHGpIM=xeX@IEfByG8;H889Ojzjm< zT=5nDMF1r9p6_*xfBBATWvqb`IySHfPNB~RPBQbDsI(|cSrOA6{>v*>qBWk#B;%qC zCG5@_@F_|%U+tp`6CRI_MJA&sCMI5*tN?#5>%mhto`|K>qBJM6q-{BNN)}lnVC6VW zW)+!LGHezmB?TMjMTx~N%U+Bn6(2h$&c+tf3cDbSG8+%pnzLVKajXbqaV2>{WNTLd zFOZqfDn3pFvt%En)M|xr8r|^+z-jc8KMh1Q76^h_bSdxxFrqI4Ggf$pY?6^hB`VaC z$&hq7Odx(RKo_tX!w;PNsF)b!fV-^gEVuP+nV-2nd8_@lb=`XV!1{r^ru&+b<@r+Q zk&=0|Y+NZ`K6DY-E@5e|Ba&g`nR#5Jz{+A;j4PtRwZZ~wA36taA$jO+u!jB~>cQ9( zp0`LQ*Ue*^=^z>~xd5wH?K1e(nnAycvgc(h?v)d+nWv>TfXso@%W6g-R*>>qX; z@+6oi@D2(OhpSf_lD@`;eJ~uL*Re@nb@?0g^Dx>M0-$f_wAzAkWCR#@FQ6+U?tcKf Ch>2qW delta 1140 zcma)5O-vI(6y9kmZMWN^0xA+LblcKZ7bqaK#P}1v$WIeu6g6n6oh@3IR<}#U+UkWE z2nnDs#;anYi4qgii$^XV3@0Tq4e?+M9*Bw2;Kg|HO`8_HIobW@d*8hI-cH`^$MNSk z9V>P_&*2CePhVU2FFCrLybVJm$C1sNT*w(C_s;6|EQ5^t%;*p(2fNOb!-{yp3=Sy7x%)?CQopi{r|h;ZXew6)f%nwgW%_@A?i)gh*!df%V8uQiKm?AL4D@i>Z(MD77#Vzs?lz-!!x&u|7>67bqo+npV z>sqHBQ)DVIKlsVj^CqzBI`!V-oU+az`7G?tv&WqmcIJhHd7<%>P&IFz3FQTmF=16` z0P1b7m==Eca>u~XidMzo0)r3(iNR?GeGK{;T!K!in~Z=eHJV~5 z9EJzd25&`HxL7ndc_3^sP&XuHvBN`o>cvLl5*vie7~<7Mz8&?NQnc6%Z{;SVk4ms1 zi||n11^Zie*NW(K=pP?ryc%A}4K>ZBn}y!o8BUTzX$h7LQtnwxFCi*?Z@r8-&~~iy zCf3kYc-S^U&VtQ9AY4abtwO(jLqVDP@75FqbM$KRT2x7CD$73ykK6rZ2;Q};Bnn*} zU4+6|$8jRS>yF{tH6f+wVXDL;Yu64ezdnYBqc(LF-$!$S8AnT3`>Jz{S_LevBDsafV6K#&A&fbl|8 z_Axfcfc*wq1%pE03LQMg!U8Moo`bIer+v%Fpv}V0U=R!R1wFP2mMk-vf;+)}Tk$9? lo`erUKmQ0R9*0XUgS`iK1c`TZg47`f+KK&F9F}@Ke*xK20=EDF diff --git a/python_video_server/letsencrypt.py b/python_video_server/letsencrypt.py index 0277f4f..92d0489 100644 --- a/python_video_server/letsencrypt.py +++ b/python_video_server/letsencrypt.py @@ -257,26 +257,42 @@ def cmd_certonly(args: argparse.Namespace) -> int: if challenge == "dns": # DNS-01 works without binding any local ports; suitable for shared hosting / no root. - _, work_dir, _ = _ensure_dirs(base_dir) - auth_hook, cleanup_hook = _ensure_dns_hook_scripts(work_dir) + # + # Default: use certbot's built-in interactive manual prompt (most reliable). + # Optional: --dns-hooks to use our hook scripts (useful for automation). certbot_args = [ "certonly", "--manual", "--preferred-challenges", "dns", "--manual-public-ip-logging-ok", - "--manual-auth-hook", - str(auth_hook), - "--manual-cleanup-hook", - str(cleanup_hook), - *_common_certbot_flags( + ] + + if bool(args.dns_hooks): + _, work_dir, _ = _ensure_dirs(base_dir) + auth_hook, cleanup_hook = _ensure_dns_hook_scripts(work_dir) + certbot_args += [ + "--manual-auth-hook", + str(auth_hook), + "--manual-cleanup-hook", + str(cleanup_hook), + ] + certbot_args += _common_certbot_flags( email=args.email, domains=domains, base_dir=base_dir, production=production, non_interactive=True, - ), - ] + ) + else: + # IMPORTANT: manual DNS prompt requires interactive mode. + certbot_args += _common_certbot_flags( + email=args.email, + domains=domains, + base_dir=base_dir, + production=production, + non_interactive=False, + ) else: certbot_args = [ "certonly", @@ -371,6 +387,12 @@ def _build_parser() -> argparse.ArgumentParser: choices=["http", "dns"], help="ACME challenge type: http (standalone) or dns (manual TXT record). Default: http.", ) + c.add_argument( + "--dns-hooks", + action="store_true", + default=bool(int(os.environ.get("LE_DNS_HOOKS", "0"))), + help="(DNS challenge only) Use hook scripts to print token and wait. Default uses certbot's interactive manual prompt.", + ) c.add_argument("--force-renewal", action="store_true", help="Force issuance even if not close to expiry.") c.add_argument( "--test-cert",