Mercurial Hosting > luan
view src/luan/host/https.luan @ 2045:265dc9af6a49 acme-tiny
fix guard_uri initiation
| author | Violet7 |
|---|---|
| date | Sun, 09 Nov 2025 02:31:57 -0800 |
| parents | d8550e64d613 |
| children | e0896f65c847 |
line wrap: on
line source
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 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" 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 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 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) if is_https then -- https if not key_file.exists() then local is_local = ip(domain) == "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 cmd = [[ ./local_https.sh "]]..domain..[[" ]] local s = uri("bash:"..cmd).read_text() logger.info("issue local certificate") else -- set up a temporary barebones nginx conf -- to serve acme challenges on the domain try local temp_dir_string = "/tmp/acme_setup/"..domain -- 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() -- create all needed dirs at once by using -- mkdir -p on the deepest nested dir (acme-challenge) local webroot = temp_dir_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") -- 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 acme_nginx = ` conf(webroot,domain) ` acme_nginx_file.write(acme_nginx) -- Create an index.html to search for in the logs -- to verify everything is working local index_file = webroot.."/index.html" local cmd = "echo 'hi, testing' > "..index_file local s = uri("bash:"..cmd).read_text() -- The config in ./local/nginx.conf has a directive to -- 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; ]] 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 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 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 = [[ python3 acme_tiny.py --account-key ./local/tiny_account.key \ --csr ]]..csr_file_str..[[ \ --acme-dir ]]..acme_challenges..[[ \ ]] if dry_run == true then cmd = cmd.." --directory-url "..dry_run_dir_url end cmd = cmd.."> "..local_cer_file_str local s = uri("bash:"..cmd).read_text() logger.info("get cert signed by letsencrypt\n"..s) -- The above http requests made by acme_tiny are the only thing -- that could cause a recursion so it is safe to delete the guard here. catch e logger.error("Error setting up ACME: "..e.to_string()) 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() 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. end if key_file.exists() and local_cer_file.exists() then 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) ` 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_ca_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..[[/local/nginx.conf" && sudo $(which nginx) -s reload; ]] local s = uri("bash:"..cmd).read_text() logger.info("reload_nginx "..s) end --logger.info "done" end Hosted.set_https = Boot.no_security(Hosted.set_https)
