changeset 2057:634a44d10c96 acme-tiny

https.luan cleanup and add test
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 12 Nov 2025 23:18:04 -0700
parents 75cd3c7bda02
children 1b0c38e26c94
files host/test/test_https.luan src/luan/host/https.luan
diffstat 2 files changed, 66 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/host/test/test_https.luan	Wed Nov 12 23:18:04 2025 -0700
@@ -0,0 +1,21 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local do_file = Luan.do_file or error()
+local Io = require "luan:Io.luan"
+local uri = Io.uri or error()
+local Hosted = require "luan:host/Hosted.luan"
+local Logging = require "luan:logging/Logging.luan"
+local logger = Logging.logger "test_https"
+
+
+do_file "classpath:luan/host/https.luan"
+
+local is_https = true
+local domain = "https.s3.luan.software"
+local site_dir = uri("file:local")
+local luanhost_dir = uri("file:..")
+local dry_run = true
+
+site_dir.mkdir()
+
+Hosted.do_set_https(is_https,domain,site_dir,luanhost_dir,dry_run)
--- a/src/luan/host/https.luan	Tue Nov 11 22:07:12 2025 -0800
+++ b/src/luan/host/https.luan	Wed Nov 12 23:18:04 2025 -0700
@@ -9,6 +9,8 @@
 local uri = Io.uri or error()
 local String = require "luan:String.luan"
 local starts_with = String.starts_with or error()
+local Thread = require "luan:Thread.luan"
+local try_synchronized = Thread.try_synchronized or error()
 local Http = require "luan:http/Http.luan"
 local Hosted = require "luan:host/Hosted.luan"
 local Logging = require "luan:logging/Logging.luan"
@@ -16,33 +18,16 @@
 
 logger.info("Hello test")
 
-function Hosted.set_https(is_https)
-	if Http.did_init() then
-		logger.error(new_error("set_https called outside of init.luan"))
-		return
-	end
-	local domain = Http.domain
-	local site_dir = uri("site:").parent()
+local function do_set_https(is_https,domain,site_dir,luanhost_dir,dry_run)
 	local nginx_file = site_dir.child("nginx.ssl.conf")
 	local key_file = site_dir.child(domain..".key")
-	local key_file_str = key_file.canonical().to_string()
 	local csr_file = site_dir.child(domain..".csr")
-	local csr_file_str = csr_file.canonical().to_string()
 	local local_cer_file = site_dir.child("fullchain.cer")
-	local local_cer_file_str = local_cer_file.canonical().to_string()
 	local local_ca_file = site_dir.child("ca.cer")
 	-- luan/host
-	local luanhost_dir = uri("file:.").canonical().to_string()
+	local luanhost_file = "file:"..luanhost_dir.to_string().."/"
+	local luanhost_dir_str = luanhost_dir.canonical().to_string()
 	local changed = false
-	-- use for testing, so as to not hit rate limits
-	-- on the real letsencrypt servers
-	local dry_run = false
-	local dry_run_dir_url = "https://acme-staging-v02.api.letsencrypt.org/directory"
-
-	-- declare these so they are visible in the catch and finally blocks
-	local guard_file = "/tmp/acme_setup_locks/"..domain..".lock"
-	local guard_uri = uri("file:"..guard_file)
-	local temp_dir_string = "/tmp/acme_setup/"..domain
 
 	if is_https then -- https
 		if not key_file.exists() then
@@ -60,37 +45,21 @@
 			else
 				-- set up a temporary barebones nginx conf
 				-- to serve acme challenges on the domain
+				local temp_dir = uri("file:/tmp/acme_setup/"..domain)
 				try
-
-					-- recursion guard, must have this to prevent
-					-- the http request from invoking this code
-					-- and causing an infinite recursion.
-					local cmd = "mkdir -p /tmp/acme_setup_locks/"
-					local s = uri("bash:"..cmd).read_text()
-					if guard_uri.exists() then
-						logger.info("set_https already running for "..domain..", skipping")
-						return
-					end
-
 					-- Clean out old temp files
-					local cmd = "rm -rf "..temp_dir_string
-					local s = uri("bash:"..cmd).read_text()
+					temp_dir.delete()
 
 					-- create all needed dirs at once by using
 					-- mkdir -p on the deepest nested dir (acme-challenge)
-					local webroot = temp_dir_string.."/webroot"
+					local webroot = temp_dir.to_string().."/webroot"
 					local acme_challenges = webroot.."/.well-known/acme-challenge"
-					local cmd = "mkdir -p "..acme_challenges
-					local s = uri("bash:"..cmd).read_text()
-
-					guard_uri.write("this is a recursion guard, see https.luan")
-
+					uri("file:"..acme_challenges).mkdir()
 
 					-- Create the nginx config from the template
-					local temp_dir = uri("file:"..temp_dir_string)
 					-- The *output* file, where the generated config is stored
 					local acme_nginx_file = temp_dir.child("nginx.acme_setup.conf")
-					local conf = load_file "file:startup/nginx/nginx.acme_setup.conf.luan"
+					local conf = load_file(luanhost_file.."startup/nginx/nginx.acme_setup.conf.luan")
 					local acme_nginx = ` conf(webroot,domain) `
 					acme_nginx_file.write(acme_nginx)
 
@@ -104,32 +73,36 @@
 					-- glob include confs in /tmp/acme_setup/*/nginx.acme_setup.conf
 					-- so we just need to reload it so it can find the one we just made
 					local cmd = [[
-						sudo $(which nginx) -t -c "]]..luanhost_dir..[[/local/nginx.conf" && \ 
-            sudo $(which nginx) -s reload -c "]]..luanhost_dir..[[/local/nginx.conf";
+						sudo $(which nginx) -t -c "]]..luanhost_dir_str..[[/local/nginx.conf" && \ 
+						sudo $(which nginx) -s reload -c "]]..luanhost_dir_str..[[/local/nginx.conf";
 					]]
 					local s = uri("bash:"..cmd).read_text()
 					logger.info("reload_nginx "..s)
 
 					-- We've set up nginx to serve from our temp root, now we need to
 					-- create a *domain key*, which we then use to sign our cert.
+					local key_file_str = key_file.canonical().to_string()
 					local cmd = "openssl genrsa 4096 > "..key_file_str
 					local s = uri("bash:"..cmd).read_text()
 					logger.info("create domain key\n"..s)
 
 					-- create the cert, signed with the key we just made
+					local csr_file_str = csr_file.canonical().to_string()
 					local cmd = 'openssl req -new -sha256 -key '..key_file_str..' -subj "/CN='..domain..'" > '..csr_file_str
 					local s = uri("bash:"..cmd).read_text()
 					logger.info("create cert\n"..s)
 
 					-- Finally, get our cert signed by letsencrypt.
 					local cmd = [[
-						./acme_tiny --account-key ./local/tiny_account.key \
+						]]..luanhost_dir_str..[[/acme_tiny --account-key ./local/tiny_account.key \
 						--csr ]]..csr_file_str..[[ \
 						--acme-dir ]]..acme_challenges..[[ \
 					]]
-					if dry_run == true then
+					if dry_run then
+						local dry_run_dir_url = "https://acme-staging-v02.api.letsencrypt.org/directory"
 						cmd = cmd.." --directory-url "..dry_run_dir_url
 					end
+					local local_cer_file_str = local_cer_file.canonical().to_string()
 					cmd = cmd.."> "..local_cer_file_str
 
 					local s = uri("bash:"..cmd).read_text()
@@ -141,11 +114,7 @@
 				catch e
 					logger.error("Error setting up ACME: "..e)
 				finally
-					if guard_uri and guard_uri.exists() then
-						guard_uri.delete()
-					end
-					local cmd = "rm -rf "..temp_dir_string
-					local s = uri("bash:"..cmd).read_text()
+					temp_dir.delete()
 				end_try
 
 			end
@@ -158,8 +127,8 @@
 				changed = true
 				-- the nginx config only requires 2 files:
 				-- fullchain.cer and DOMAIN.key
-				local conf = load_file "file:startup/nginx/nginx.ssl.conf.luan"
-				local nginx = ` conf(luanhost_dir,domain) `
+				local conf = load_file(luanhost_file.."startup/nginx/nginx.ssl.conf.luan")
+				local nginx = ` conf(luanhost_dir_str,domain) `
 				nginx_file.write(nginx)
 			end
 		end
@@ -179,12 +148,34 @@
 	end
 	if changed then
 		local cmd = [[
-sudo $(which nginx) -t -c "]]..luanhost_dir..[[/local/nginx.conf" && \
-sudo $(which nginx) -s reload -c "]]..luanhost_dir..[[/local/nginx.conf";
+sudo $(which nginx) -t -c "]]..luanhost_dir_str..[[/local/nginx.conf" && \
+sudo $(which nginx) -s reload -c "]]..luanhost_dir_str..[[/local/nginx.conf";
 ]]
 		local s = uri("bash:"..cmd).read_text()
 		logger.info("reload_nginx "..s)
 	end
 	--logger.info "done"
 end
+Hosted.do_set_https = do_set_https  -- for testing
+
+function Hosted.set_https(is_https)
+	if Http.did_init() then
+		logger.error(new_error("set_https called outside of init.luan"))
+		return
+	end
+	local domain = Http.domain
+	local site_dir = uri("site:").parent()
+	local luanhost_dir = uri("file:.")
+
+	-- use for testing, so as to not hit rate limits
+	-- on the real letsencrypt servers
+	local dry_run = false
+
+	if not try_synchronized( function()
+		do_set_https(is_https,domain,site_dir,luanhost_dir,dry_run)
+	end, domain..".lock", 0 )() then
+		logger.info("set_https already running for "..domain..", skipping")
+	end
+end
+
 Hosted.set_https = Boot.no_security(Hosted.set_https)