133 lines
4.1 KiB
Python
Executable File
133 lines
4.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Automation for cert renewal.
|
|
|
|
assumptions:
|
|
* router is danknasty
|
|
* generate key in /root/.ssh/id_autofirewall on server
|
|
* firewall has config from `authorized_keys` file
|
|
* firewall sshd config contains: `AcceptEnv ssl_service state`
|
|
* firewall has `ssl-update.sh` copied to /usr/local/bin and chmod +x
|
|
"""
|
|
|
|
import getpass
|
|
import os
|
|
import pathlib
|
|
import pexpect
|
|
import subprocess
|
|
import sys
|
|
|
|
services = ['git', 'plex', 'jellyfin', 'photoprism']
|
|
|
|
|
|
def rmdir(directory):
|
|
directory = pathlib.Path(directory)
|
|
for item in directory.iterdir():
|
|
if item.is_dir():
|
|
rmdir(item)
|
|
else:
|
|
item.unlink()
|
|
directory.rmdir()
|
|
|
|
|
|
def main(args):
|
|
|
|
# Get SSH decryption password
|
|
|
|
cmd = ['/usr/bin/su', '-l', 'luke', '/usr/bin/pass', 'show', 'ssh/autofirewall']
|
|
p = subprocess.run(cmd, capture_output=True)
|
|
decrypt_pp = p.stdout.decode('UTF-8').strip()
|
|
if not decrypt_pp:
|
|
sys.exit('Couldnt get decryption passpharase')
|
|
|
|
# Start Certbot, get data to send to service
|
|
|
|
challenge_path = pathlib.Path('/usr/share/nginx/html/'
|
|
'.well-known/acme-challenge/')
|
|
if challenge_path.is_dir():
|
|
rmdir(challenge_path)
|
|
challenge_path.mkdir()
|
|
challenge_path.chmod(0o755)
|
|
if len(args) != 1:
|
|
sys.exit(f'Give a service to renew: {", ".join(services)} ')
|
|
service = args[0]
|
|
if service not in services:
|
|
sys.exit(f'Give a service to renew: {", ".join(services)} ')
|
|
|
|
fqdn = f'{service}.drheck.dev'
|
|
cmd = ['/usr/bin/certbot', 'certonly', '--manual', '-d', fqdn]
|
|
p = pexpect.spawnu(' '.join(cmd))
|
|
# p.logfile = sys.stderr
|
|
res = p.expect(['Create a file containing just this data:\r\n\r\n([^\r]+)\r',
|
|
pexpect.TIMEOUT, pexpect.EOF], timeout=20)
|
|
if res > 0:
|
|
sys.exit('Timed out')
|
|
data = p.match.group(1)
|
|
long_match = ('And make it available on your web server at this URL:'
|
|
'\r\n\r\nhttp://%s/.well-known/acme-challenge/([^\r]+)\r')
|
|
res = p.expect([long_match % (fqdn,), pexpect.TIMEOUT, pexpect.EOF])
|
|
if res > 0:
|
|
sys.exit('Timed out')
|
|
filename = p.match.group(1)
|
|
res = p.expect(['Press Enter to Continue', pexpect.EOF], timeout=0)
|
|
|
|
# Certbot is paused. Got the data
|
|
|
|
# put data in acme-challenge file
|
|
data_file = challenge_path / pathlib.Path(filename)
|
|
try:
|
|
with open(data_file, 'w') as f:
|
|
f.write(data)
|
|
except:
|
|
sys.exit(f'Failed to write {data_file}')
|
|
data_file.chmod(0o644)
|
|
|
|
# put symlink in nginx enabled sites
|
|
|
|
symlink_name = pathlib.Path(f'{service}-le')
|
|
nx_conf = pathlib.Path('/etc/nginx')
|
|
avail_path = nx_conf / pathlib.Path('sites-available')
|
|
enabled_path = nx_conf / pathlib.Path('sites-enabled')
|
|
|
|
service_available_file = avail_path / symlink_name
|
|
service_enabled_symlink = enabled_path / symlink_name
|
|
if not service_enabled_symlink.is_symlink():
|
|
service_enabled_symlink.symlink_to(service_available_file)
|
|
|
|
# open port 80 to ${service}.drheck.dev
|
|
|
|
os.environ['state'] = 'HTTP_DOWN'
|
|
os.environ['ssl_service'] = service
|
|
cmd = ['/usr/bin/ssh', '-i', '/root/.ssh/id_autofirewall', '-o',
|
|
'SendEnv=state', '-o', 'SendEnv=ssl_service', '-l', 'luke131',
|
|
'danknasty', 'doas', '-n', '/usr/local/bin/ssl-update.sh']
|
|
print(f'cmd: {cmd}')
|
|
p = pexpect.spawnu(' '.join(cmd))
|
|
p.logfile = sys.stderr
|
|
res = p.expect(["""Enter passphrase for key '/root/.ssh/id_autofirewall':""", pexpect.TIMEOUT, pexpect.EOF])
|
|
if res > 0:
|
|
sys.exit('Couldnt send decryption key to ssh.')
|
|
p.sendline(decrypt_pp)
|
|
res = p.expect(['success', pexpect.TIMEOUT, pexpect.EOF])
|
|
if res > 0:
|
|
sys.exit(f'Failed. error: {p.before}')
|
|
|
|
print(f'Turned up HTTP for {service}')
|
|
|
|
# restart nginx
|
|
# continue with certbot
|
|
# close port 80 to git.drheck.dev
|
|
# remove symlink in nginx enabled sites
|
|
# restart nginx
|
|
# chmod
|
|
# /etc/letsencrypt/live/git.drheck.dev
|
|
# /etc/letsencrypt/archive/git.drheck.dev
|
|
# restart gitea
|
|
# p.expect(r'up\s+(.*?),\s+([0-9]+) users?,\s+load averages?: ([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9])')
|
|
# duration, users, av1, av5, av15 = p.match.groups()
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main(sys.argv[1:])
|