cont the remaining workflow even if the cert is too new to refresh

This commit is contained in:
LuKe Tidd 2024-09-30 13:24:55 -04:00
parent 68497c4d10
commit e31ec00d3d
Signed by: luke
GPG Key ID: 75D6600BEF4E8E8F
2 changed files with 79 additions and 71 deletions

View File

@ -40,8 +40,8 @@ restart_delay = {
} }
pfx_key_path = { pfx_key_path = {
'plex': '/data/plex/certs/certificate.pfx', 'plex': pathlib.Path('/data/plex/certs/certificate.pfx'),
'jellyfin': '/data/jellyfin/ssl/jellyfin.pfx', 'jellyfin': pathlib.Path('/data/jellyfin/ssl/jellyfin.pfx'),
} }
# Cert owning user if different than the name of the service # Cert owning user if different than the name of the service
@ -163,7 +163,7 @@ def pfx_gen(service):
except KeyError: except KeyError:
sys.exit(f'{service} has no defined private key path.') sys.exit(f'{service} has no defined private key path.')
cmd = ['/usr/bin/openssl', 'pkcs12', '-export', '-out', pkp, cmd = ['/usr/bin/openssl', 'pkcs12', '-export', '-out', str(pkp),
'-inkey', f'/etc/letsencrypt/live/{service}.{domain}/privkey.pem', '-inkey', f'/etc/letsencrypt/live/{service}.{domain}/privkey.pem',
'-in', f'/etc/letsencrypt/live/{service}.{domain}/cert.pem', '-in', f'/etc/letsencrypt/live/{service}.{domain}/cert.pem',
'-certfile', f'/etc/letsencrypt/live/{service}.{domain}/chain.pem'] '-certfile', f'/etc/letsencrypt/live/{service}.{domain}/chain.pem']
@ -234,15 +234,24 @@ def run_cert_bot(fqdn, service, challenge_path, decrypt_pp):
log.info(f'certbot cmd: "{" ".join(cmd)}"') log.info(f'certbot cmd: "{" ".join(cmd)}"')
cb = pexpect.spawnu(' '.join(cmd)) cb = pexpect.spawnu(' '.join(cmd))
cb.logfile = sys.stderr cb.logfile = sys.stderr
do_update = True
matches = [
'Create a file containing just this data:\r\n\r\n([^\r]+)\r',
('You have an existing certificate that has exactly the '
"same domains or certificate name you requested and isn't "
'close to expiry'),
'(U)pdate key type/(K)eep existing key type:',
'no action taken',
pexpect.TIMEOUT,
pexpect.EOF]
while True: while True:
res = cb.expect( res = cb.expect(matches, timeout=20)
['Create a file containing just this data:\r\n\r\n([^\r]+)\r', print(f'\nresult: {matches[res]}, {res}')
('You have an existing certificate that has exactly the ' if res > 3:
"same domains or certificate name you requested and isn't "
'close to expiry'),'(U)pdate key type/(K)eep existing key type:',
pexpect.TIMEOUT, pexpect.EOF], timeout=20)
if res > 2:
sys.exit('Timed out') sys.exit('Timed out')
if res == 3:
do_update = False
break
if res == 2: if res == 2:
cb.sendline('U') cb.sendline('U')
continue continue
@ -251,74 +260,76 @@ def run_cert_bot(fqdn, service, challenge_path, decrypt_pp):
res = cb.expect_exact(['cancel):', pexpect.TIMEOUT, pexpect.EOF]) res = cb.expect_exact(['cancel):', pexpect.TIMEOUT, pexpect.EOF])
if res > 0: if res > 0:
sys.exit('Timed out in setup with existing cert') sys.exit('Timed out in setup with existing cert')
cb.sendline('2') cb.sendline('1')
res = cb.expect( continue
['Create a file containing just this data:\r\n\r\n([^\r]+)\r', # res = cb.expect(
pexpect.TIMEOUT, pexpect.EOF], timeout=20) # ['Create a file containing just this data:\r\n\r\n([^\r]+)\r',
if res > 1: # pexpect.TIMEOUT, pexpect.EOF], timeout=20)
sys.exit('Timed out') # if res > 1:
# sys.exit('Timed out')
if res == 0: if res == 0:
break break
data = cb.match.group(1) if do_update:
log.info(f'secret data: {data}') data = cb.match.group(1)
log.info('the data string and location for the shared secret are known') log.info(f'secret data: {data}')
long_match = ('And make it available on your web server at this URL:' log.info('the data string and location for the shared secret are known')
'\r\n\r\nhttp://%s/.well-known/acme-challenge/([^\r]+)\r') long_match = ('And make it available on your web server at this URL:'
res = cb.expect([long_match % (fqdn,), pexpect.TIMEOUT, pexpect.EOF]) '\r\n\r\nhttp://%s/.well-known/acme-challenge/([^\r]+)\r')
if res > 0: res = cb.expect([long_match % (fqdn,), pexpect.TIMEOUT, pexpect.EOF])
sys.exit('Timed out') if res > 0:
filename = cb.match.group(1) sys.exit('Timed out')
log.info(f'filename of secret: {filename}') filename = cb.match.group(1)
res = cb.expect(['Press Enter to Continue', pexpect.EOF], timeout=0) log.info(f'filename of secret: {filename}')
res = cb.expect(['Press Enter to Continue', pexpect.EOF], timeout=0)
data_file = challenge_path / pathlib.Path(filename) data_file = challenge_path / pathlib.Path(filename)
try: try:
with open(data_file, 'w') as f: with open(data_file, 'w') as f:
f.write(data) f.write(data)
except: except:
sys.exit(f'Failed to write {data_file}') sys.exit(f'Failed to write {data_file}')
log.info('created secret file with secret data') log.info('created secret file with secret data')
data_file.chmod(0o644) data_file.chmod(0o644)
log.info('and chmodded') log.info('and chmodded')
symlink_name = pathlib.Path(f'{service}-le') symlink_name = pathlib.Path(f'{service}-le')
log.info(f'nginx symlink: {symlink_name}') log.info(f'nginx symlink: {symlink_name}')
nx_conf = pathlib.Path('/etc/nginx') nx_conf = pathlib.Path('/etc/nginx')
avail_path = nx_conf / pathlib.Path('sites-available') avail_path = nx_conf / pathlib.Path('sites-available')
enabled_path = nx_conf / pathlib.Path('sites-enabled') enabled_path = nx_conf / pathlib.Path('sites-enabled')
service_available_file = avail_path / symlink_name service_available_file = avail_path / symlink_name
log.info(f'nginx service avail symlink: {service_available_file}') log.info(f'nginx service avail symlink: {service_available_file}')
service_enabled_symlink = enabled_path / symlink_name service_enabled_symlink = enabled_path / symlink_name
log.info(f'nginx service enabled symlink: {service_enabled_symlink}') log.info(f'nginx service enabled symlink: {service_enabled_symlink}')
if not service_enabled_symlink.is_symlink(): if not service_enabled_symlink.is_symlink():
service_enabled_symlink.symlink_to(service_available_file) service_enabled_symlink.symlink_to(service_available_file)
log.info('created symlink to enable service') log.info('created symlink to enable service')
log.info(f'open port 80 to {service}') log.info(f'open port 80 to {service}')
firewall_mod('HTTP_UP', service, decrypt_pp) firewall_mod('HTTP_UP', service, decrypt_pp)
restart('nginx') restart('nginx')
cb.sendline() cb.sendline()
log.info(f'sent <enter> to certbot to continue process') log.info(f'sent <enter> to certbot to continue process')
res = cb.expect([pexpect.EOF]) res = cb.expect([pexpect.EOF])
log.info(f'cerbot completed. final output: {cb.before}') log.info(f'cerbot completed. final output: {cb.before}')
output_text = str(cb.before) output_text = str(cb.before)
if 'failed' in output_text: if 'failed' in output_text:
sys.exit('Something went wrong') sys.exit('Something went wrong')
log.info(f'close port 80 to {service}') log.info(f'close port 80 to {service}')
firewall_mod('HTTP_DOWN', service, decrypt_pp) firewall_mod('HTTP_DOWN', service, decrypt_pp)
service_enabled_symlink.unlink() service_enabled_symlink.unlink()
if service_enabled_symlink.is_symlink(): if service_enabled_symlink.is_symlink():
sys.exit(f'Could not unlink {service_enabled_symlink}') sys.exit(f'Could not unlink {service_enabled_symlink}')
log.info('created symlink to enable service') log.info('created symlink to enable service')
log.info('removed symlink in nginx to disable HTTP') log.info('removed symlink in nginx to disable HTTP')
restart('nginx') restart('nginx')
key_path = pathlib.Path('/etc/letsencrypt') key_path = pathlib.Path('/etc/letsencrypt')
live = key_path / pathlib.Path(f'live/{service}.{domain}') live = key_path / pathlib.Path(f'live/{service}.{domain}')
@ -372,9 +383,6 @@ def main(args):
fqdn = f'{service}.{domain}' fqdn = f'{service}.{domain}'
log.info(f'fqdn: {fqdn}') log.info(f'fqdn: {fqdn}')
# Dry run:
# cmd = ['/usr/bin/certbot', '--dry-run', 'certonly', '--manual', '-d', fqdn]
# Real run:
run_cert_bot(fqdn, service, challenge_path, decrypt_pp) run_cert_bot(fqdn, service, challenge_path, decrypt_pp)
restart(service) restart(service)

View File

@ -3,9 +3,9 @@
declare -a services declare -a services
services+=('git') services+=('git')
services+=('plex') services+=('plex')
# services+=('jellyfin') services+=('jellyfin')
services+=('photoprism') services+=('photoprism')
services+=('nextcloud') # services+=('nextcloud')
services+=('read') services+=('read')
services+=('www') services+=('www')
services+=('chat') services+=('chat')