Mercurial Hosting > luan
changeset 2093:c0847b30833f ssltesting
mv https.luan Https.luan
| author | Franklin Schmidt <fschmidt@gmail.com> |
|---|---|
| date | Sat, 13 Dec 2025 20:25:56 -0700 |
| parents | 429827024f4e |
| children | 3c40cb15d468 |
| files | host/test/test_https.luan src/luan/host/Https.luan src/luan/host/https.luan src/luan/host/init.luan |
| diffstat | 4 files changed, 193 insertions(+), 192 deletions(-) [+] |
line wrap: on
line diff
--- a/host/test/test_https.luan Fri Dec 12 18:53:15 2025 -0800 +++ b/host/test/test_https.luan Sat Dec 13 20:25:56 2025 -0700 @@ -3,19 +3,18 @@ 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 Https = require "classpath:luan/host/Https.luan" local is_https = true -local domain = "https.s3.luan.software" +local domain = "https.me.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) +Https.do_set_https(is_https,domain,site_dir,luanhost_dir,dry_run)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/host/Https.luan Sat Dec 13 20:25:56 2025 -0700 @@ -0,0 +1,187 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local new_error = Luan.new_error or error() +local load_file = Luan.load_file or error() +local ipairs = Luan.ipairs or error() +local Io = require "luan:Io.luan" +local ip = Io.ip or error() +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 Logging = require "luan:logging/Logging.luan" +local logger = Logging.logger "https" + + +local Https = {} + +local my_ips = Io.my_ips() + +local function do_set_https(is_https,domain,site_dir,luanhost_dir,dry_run) + local nginx_file = site_dir.child("nginx.ssl.conf") + + -- TODO: implement this later + local ssl_files_dir = site_dir--.child("ssl/") + -- ssl_files_dir.mkdir() + + local key_file = ssl_files_dir.child(domain..".key") + local csr_file = ssl_files_dir.child(domain..".csr") + local tmp_cert_out = ssl_files_dir.child(domain..".crt.tmp") + local local_cer_file = ssl_files_dir.child("fullchain.cer") + -- luan/host + local luanhost_file = "file:"..luanhost_dir.to_string().."/" + local luanhost_dir_str = luanhost_dir.canonical().to_string() + local changed = false + + if is_https then -- https + if not key_file.exists() \ + or not local_cer_file.exists() or local_cer_file.length()==0 \ + or not nginx_file.exists() \ + then + local domain_ip = ip(domain) + local is_local = domain_ip == "127.0.0.1" + logger.info("is_local "..is_local) + + -- Use openssl directly to make a self-signed cert, + -- no external cert authority involved + if is_local then + local ssl_files_dir_str = ssl_files_dir.canonical().to_string().."/"; + local cmd = [[ + openssl req -x509 -newkey rsa:2048 -nodes \ + -keyout ]]..ssl_files_dir_str..domain..[[.key \ + -out ]]..ssl_files_dir_str..[[fullchain.cer -days 365 \ + -subj "/CN=]]..domain..[[" \ + -addext "subjectAltName=DNS:]]..domain..[[,IP:127.0.0.1" + ]] + logger.info("local ssl commandline:\n"..cmd) + local s = uri("bash:"..cmd).read_text() + logger.info("issue local certificate") + else + if my_ips[domain_ip] ~= true then + logger.error("the domain "..domain.." doesn't map to this machine") + return + end + try + -- CHANGEME + dry_run = true + + -- make the challenge dir. note that this is + -- directly under sites/DOMAIN, and *not* under + -- sites/DOMAIN/site. + local acme_challenges = site_dir.child("acme-challenge/") + acme_challenges.mkdir() + + -- Create a domain key to sign the certificate signing request (csr). + 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 csr. + 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 csr\n"..s) + + -- Finally, get our cert from letsencrypt. + local cmd = luanhost_dir_str..[[/acme_tiny --account-key ]]..luanhost_dir_str..[[/local/tiny_account.key \ + --csr ]]..csr_file_str..[[ \ + --acme-dir ]]..acme_challenges.canonical().to_string()..[[ \ + ]] + + -- TODO: this often doesn't work and I don't know if it's + -- because of this code or because of letsencrypt. + -- fix if broken. + 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 + cmd = cmd.." > "..tmp_cert_out.canonical().to_string() + logger.info("acme-tiny commandline:\n"..cmd) + + local s = uri("bash:"..cmd).read_text() + logger.info("get cert signed by letsencrypt\n"..s) + + -- Empty stdout from acme-tiny is a failure. + if tmp_cert_out.length() == 0 then + -- TODO: this should fail non-gracefully, + -- all failures here are almost certainly bugs. + logger.error("FAILED getting cert from letsencrypt.\nSee previous output.\nNot writing to fullchain.cer") + else + -- Success! Move the temp output to the real fullchain. + local tmp_out_str = tmp_cert_out.canonical().to_string() + local local_cer_file_str = local_cer_file.canonical().to_string() + + local cmd = "mv "..tmp_out_str.." "..local_cer_file_str + local s = uri("bash:"..cmd).read_text() + logger.info("move temp output to fullchain.cer\n"..s) + end + + catch e + logger.error("Error setting up ACME: "..e) + end_try + + end + -- We now have our certificate! + -- Now we just need to generate the nginx config + -- that uses it, place it in luan/host/sites/*/nginx.ssl.conf + -- and tell luan-host to reload nginx. + + if key_file.exists() and local_cer_file.exists() and local_cer_file.length() > 0 then + changed = true + -- the nginx config only requires 2 files: + -- fullchain.cer and DOMAIN.key + logger.info("writing nginx conf to "..nginx_file.canonical().to_string()) + 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 + else -- http + if key_file.exists() or nginx_file.exists() then + changed = true + nginx_file.delete() + local_cer_file.delete() + local ptn = domain.."." + for _, file in ipairs(site_dir.children()) do + if starts_with(file.name(),ptn) then + file.delete() + end + end + end + end + if changed then + local cmd = [[ +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 +Https.do_set_https = do_set_https -- for testing + +function Https.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 + +return Https
--- a/src/luan/host/https.luan Fri Dec 12 18:53:15 2025 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,187 +0,0 @@ -local Luan = require "luan:Luan.luan" -local error = Luan.error -local new_error = Luan.new_error or error() -local load_file = Luan.load_file or error() -local ipairs = Luan.ipairs or error() -local Boot = require "luan:Boot.luan" -local Io = require "luan:Io.luan" -local ip = Io.ip or error() -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" -local logger = Logging.logger "https" - - -local my_ips = Io.my_ips() - -local function do_set_https(is_https,domain,site_dir,luanhost_dir,dry_run) - local nginx_file = site_dir.child("nginx.ssl.conf") - - -- TODO: implement this later - local ssl_files_dir = site_dir--.child("ssl/") - -- ssl_files_dir.mkdir() - - local key_file = ssl_files_dir.child(domain..".key") - local csr_file = ssl_files_dir.child(domain..".csr") - local tmp_cert_out = ssl_files_dir.child(domain..".crt.tmp") - local local_cer_file = ssl_files_dir.child("fullchain.cer") - -- luan/host - local luanhost_file = "file:"..luanhost_dir.to_string().."/" - local luanhost_dir_str = luanhost_dir.canonical().to_string() - local changed = false - - if is_https then -- https - if not key_file.exists() \ - or not local_cer_file.exists() or local_cer_file.length()==0 \ - or not nginx_file.exists() \ - then - local domain_ip = ip(domain) - local is_local = domain_ip == "127.0.0.1" - logger.info("is_local "..is_local) - - -- Use openssl directly to make a self-signed cert, - -- no external cert authority involved - if is_local then - local ssl_files_dir_str = ssl_files_dir.canonical().to_string().."/"; - local cmd = [[ - openssl req -x509 -newkey rsa:2048 -nodes \ - -keyout ]]..ssl_files_dir_str..domain..[[.key \ - -out ]]..ssl_files_dir_str..[[fullchain.cer -days 365 \ - -subj "/CN=]]..domain..[[" \ - -addext "subjectAltName=DNS:]]..domain..[[,IP:127.0.0.1" - ]] - logger.info("local ssl commandline:\n"..cmd) - local s = uri("bash:"..cmd).read_text() - logger.info("issue local certificate") - else - if my_ips[domain_ip] ~= true then - logger.error("the domain "..domain.." doesn't map to this machine") - return - end - try - -- CHANGEME - dry_run = true - - -- make the challenge dir. note that this is - -- directly under sites/DOMAIN, and *not* under - -- sites/DOMAIN/site. - local acme_challenges = site_dir.child("acme-challenge/") - acme_challenges.mkdir() - - -- Create a domain key to sign the certificate signing request (csr). - 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 csr. - 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 csr\n"..s) - - -- Finally, get our cert from letsencrypt. - local cmd = luanhost_dir_str..[[/acme_tiny --account-key ]]..luanhost_dir_str..[[/local/tiny_account.key \ - --csr ]]..csr_file_str..[[ \ - --acme-dir ]]..acme_challenges.canonical().to_string()..[[ \ - ]] - - -- TODO: this often doesn't work and I don't know if it's - -- because of this code or because of letsencrypt. - -- fix if broken. - 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 - cmd = cmd.." > "..tmp_cert_out.canonical().to_string() - logger.info("acme-tiny commandline:\n"..cmd) - - local s = uri("bash:"..cmd).read_text() - logger.info("get cert signed by letsencrypt\n"..s) - - -- Empty stdout from acme-tiny is a failure. - if tmp_cert_out.length() == 0 then - -- TODO: this should fail non-gracefully, - -- all failures here are almost certainly bugs. - logger.error("FAILED getting cert from letsencrypt.\nSee previous output.\nNot writing to fullchain.cer") - else - -- Success! Move the temp output to the real fullchain. - local tmp_out_str = tmp_cert_out.canonical().to_string() - local local_cer_file_str = local_cer_file.canonical().to_string() - - local cmd = "mv "..tmp_out_str.." "..local_cer_file_str - local s = uri("bash:"..cmd).read_text() - logger.info("move temp output to fullchain.cer\n"..s) - end - - catch e - logger.error("Error setting up ACME: "..e) - end_try - - end - -- We now have our certificate! - -- Now we just need to generate the nginx config - -- that uses it, place it in luan/host/sites/*/nginx.ssl.conf - -- and tell luan-host to reload nginx. - - if key_file.exists() and local_cer_file.exists() and local_cer_file.length() > 0 then - changed = true - -- the nginx config only requires 2 files: - -- fullchain.cer and DOMAIN.key - logger.info("writing nginx conf to "..nginx_file.canonical().to_string()) - 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 - else -- http - if key_file.exists() or nginx_file.exists() then - changed = true - nginx_file.delete() - local_cer_file.delete() - local ptn = domain.."." - for _, file in ipairs(site_dir.children()) do - if starts_with(file.name(),ptn) then - file.delete() - end - end - end - end - if changed then - local cmd = [[ -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)
--- a/src/luan/host/init.luan Fri Dec 12 18:53:15 2025 -0800 +++ b/src/luan/host/init.luan Sat Dec 13 20:25:56 2025 -0700 @@ -4,6 +4,7 @@ local Package = require "luan:Package.luan" local Number = require "luan:Number.luan" local long = Number.long or error() +local Boot = require "luan:Boot.luan" local dir, domain = ... @@ -99,7 +100,8 @@ end -do_file "classpath:luan/host/https.luan" +local Https = require "classpath:luan/host/Https.luan" +Hosted.set_https = Boot.no_security(Https.set_https) local LuanJava = require "java:luan.Luan"
