jellyfin completed first complete update
This commit is contained in:
parent
7710b76758
commit
b8d2daf097
25
README.md
25
README.md
@ -1,3 +1,24 @@
|
|||||||
# mycertbot
|
ssl-update
|
||||||
|
automation for cert renewal with local hooks
|
||||||
|
|
||||||
|
given a service:
|
||||||
|
* start letsencrypt's certbot "manually", getting ownership proof data
|
||||||
|
* write proof into nginx's serving path
|
||||||
|
* log into the firewall, allow http for the given service
|
||||||
|
* enable http for the given service
|
||||||
|
* instruct let's encrypt to check the proof
|
||||||
|
* get new keys
|
||||||
|
* disable http for the service
|
||||||
|
* log into firewall, block http for the given service
|
||||||
|
* set permissions and ownership on new keys
|
||||||
|
* perform service specific hooks
|
||||||
|
* jellyfin: generating a pkcs12 key
|
||||||
|
|
||||||
|
All secrets are GPG encrypted and one password prompt allows for script access
|
||||||
|
to all secrets necessary.
|
||||||
|
|
||||||
|
State:
|
||||||
|
* Only jellyfin is tested and working
|
||||||
|
* Can only really test when keys come closer to expiring
|
||||||
|
* code is ugly, could be a nice class or something
|
||||||
|
|
||||||
my process for updating certs
|
|
14
nfo/success_msg
Normal file
14
nfo/success_msg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
Successfully received certificate.
|
||||||
|
Certificate is saved at: /etc/letsencrypt/live/jellyfin.drheck.dev/fullchain.pem
|
||||||
|
Key is saved at: /etc/letsencrypt/live/jellyfin.drheck.dev/privkey.pem
|
||||||
|
This certificate expires on 2022-10-01.
|
||||||
|
These files will be updated when the certificate renews.
|
||||||
|
|
||||||
|
NEXT STEPS:
|
||||||
|
- This certificate will not be renewed automatically. Autorenewal of --manual certificates requires the use of an authentication hook script (--manual-auth-hook) but one was not provided. To renew this certificate, repeat this same certbot command before the certificate's expiry date.
|
||||||
|
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
If you like Certbot, please consider supporting our work by:
|
||||||
|
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
|
||||||
|
* Donating to EFF: https://eff.org/donate-le
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
168
update_cert.py
168
update_cert.py
@ -13,11 +13,29 @@ import getpass
|
|||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import pexpect
|
import pexpect
|
||||||
|
import pwd
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
services = ['git', 'plex', 'jellyfin', 'photoprism']
|
services = ['git', 'plex', 'jellyfin', 'photoprism']
|
||||||
|
|
||||||
|
users = {
|
||||||
|
'jellyfin': 'jellyfin',
|
||||||
|
'git': 'gitea',
|
||||||
|
'plex': 'plex',
|
||||||
|
'photoprism': 'photoprism',
|
||||||
|
}
|
||||||
|
|
||||||
|
service_systemd = {
|
||||||
|
'jellyfin': 'jellyfin',
|
||||||
|
'git': 'gitea',
|
||||||
|
'plex': 'plexmediaserver',
|
||||||
|
'photoprism': 'photoprism',
|
||||||
|
}
|
||||||
|
|
||||||
|
cert_files = ['privkey1.pem', 'fullchain1.pem', 'chain1.pem', 'cert1.pem']
|
||||||
|
|
||||||
|
|
||||||
def rmdir(directory):
|
def rmdir(directory):
|
||||||
directory = pathlib.Path(directory)
|
directory = pathlib.Path(directory)
|
||||||
@ -29,10 +47,38 @@ def rmdir(directory):
|
|||||||
directory.rmdir()
|
directory.rmdir()
|
||||||
|
|
||||||
|
|
||||||
|
def jellyfin():
|
||||||
|
cmd = ['/usr/bin/su', '-l', 'luke', '/usr/bin/pass', 'show', 'ssl/jellyfin']
|
||||||
|
p = subprocess.run(cmd, capture_output=True)
|
||||||
|
export_pw = p.stdout.decode('UTF-8').strip()
|
||||||
|
if not export_pw:
|
||||||
|
sys.exit('Couldnt get ssl export password for jellyfin')
|
||||||
|
cmd = ['/usr/bin/openssl', 'pkcs12', '-export', '-out',
|
||||||
|
'/etc/letsencrypt/live/jellyfin.drheck.dev/jellyfin.pfx',
|
||||||
|
'-inkey', '/etc/letsencrypt/live/jellyfin.drheck.dev/privkey.pem',
|
||||||
|
'-in', '/etc/letsencrypt/live/jellyfin.drheck.dev/cert.pem',
|
||||||
|
'-certfile', '/etc/letsencrypt/live/jellyfin.drheck.dev/chain.pem']
|
||||||
|
p = pexpect.spawnu(' '.join(cmd))
|
||||||
|
res = p.expect(['Enter Export Password:', pexpect.EOF, pexpect.TIMEOUT])
|
||||||
|
if res > 0:
|
||||||
|
sys.exit('Failed to run openssl to generate '
|
||||||
|
f'pkcs12 keys for jellyfin: {p.before}')
|
||||||
|
p.sendline(export_pw)
|
||||||
|
res = p.expect(['Verifying - Enter Export Password:',
|
||||||
|
pexpect.EOF, pexpect.TIMEOUT])
|
||||||
|
if res > 0:
|
||||||
|
sys.exit('Failed to run openssl to generate '
|
||||||
|
f'pkcs12 keys for jellyfin: {p.before}')
|
||||||
|
p.sendline(export_pw)
|
||||||
|
res = p.expect([pexpect.EOF, pexpect.TIMEOUT])
|
||||||
|
if res > 0:
|
||||||
|
sys.exit(f'Failed to run openssl to generate '
|
||||||
|
'pkcs12 keys for jellyfin: {p.before}')
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
|
|
||||||
# Get SSH decryption password
|
# Get SSH decryption password
|
||||||
|
|
||||||
cmd = ['/usr/bin/su', '-l', 'luke', '/usr/bin/pass', 'show', 'ssh/autofirewall']
|
cmd = ['/usr/bin/su', '-l', 'luke', '/usr/bin/pass', 'show', 'ssh/autofirewall']
|
||||||
p = subprocess.run(cmd, capture_output=True)
|
p = subprocess.run(cmd, capture_output=True)
|
||||||
decrypt_pp = p.stdout.decode('UTF-8').strip()
|
decrypt_pp = p.stdout.decode('UTF-8').strip()
|
||||||
@ -40,7 +86,6 @@ def main(args):
|
|||||||
sys.exit('Couldnt get decryption passpharase')
|
sys.exit('Couldnt get decryption passpharase')
|
||||||
|
|
||||||
# Start Certbot, get data to send to service
|
# Start Certbot, get data to send to service
|
||||||
|
|
||||||
challenge_path = pathlib.Path('/usr/share/nginx/html/'
|
challenge_path = pathlib.Path('/usr/share/nginx/html/'
|
||||||
'.well-known/acme-challenge/')
|
'.well-known/acme-challenge/')
|
||||||
if challenge_path.is_dir():
|
if challenge_path.is_dir():
|
||||||
@ -55,22 +100,19 @@ def main(args):
|
|||||||
|
|
||||||
fqdn = f'{service}.drheck.dev'
|
fqdn = f'{service}.drheck.dev'
|
||||||
cmd = ['/usr/bin/certbot', 'certonly', '--manual', '-d', fqdn]
|
cmd = ['/usr/bin/certbot', 'certonly', '--manual', '-d', fqdn]
|
||||||
p = pexpect.spawnu(' '.join(cmd))
|
cb = pexpect.spawnu(' '.join(cmd))
|
||||||
# p.logfile = sys.stderr
|
res = cb.expect(['Create a file containing just this data:\r\n\r\n([^\r]+)\r',
|
||||||
res = p.expect(['Create a file containing just this data:\r\n\r\n([^\r]+)\r',
|
|
||||||
pexpect.TIMEOUT, pexpect.EOF], timeout=20)
|
pexpect.TIMEOUT, pexpect.EOF], timeout=20)
|
||||||
if res > 0:
|
if res > 0:
|
||||||
sys.exit('Timed out')
|
sys.exit('Timed out')
|
||||||
data = p.match.group(1)
|
data = cb.match.group(1)
|
||||||
long_match = ('And make it available on your web server at this URL:'
|
long_match = ('And make it available on your web server at this URL:'
|
||||||
'\r\n\r\nhttp://%s/.well-known/acme-challenge/([^\r]+)\r')
|
'\r\n\r\nhttp://%s/.well-known/acme-challenge/([^\r]+)\r')
|
||||||
res = p.expect([long_match % (fqdn,), pexpect.TIMEOUT, pexpect.EOF])
|
res = cb.expect([long_match % (fqdn,), pexpect.TIMEOUT, pexpect.EOF])
|
||||||
if res > 0:
|
if res > 0:
|
||||||
sys.exit('Timed out')
|
sys.exit('Timed out')
|
||||||
filename = p.match.group(1)
|
filename = cb.match.group(1)
|
||||||
res = p.expect(['Press Enter to Continue', pexpect.EOF], timeout=0)
|
res = cb.expect(['Press Enter to Continue', pexpect.EOF], timeout=0)
|
||||||
|
|
||||||
# Certbot is paused. Got the data
|
|
||||||
|
|
||||||
# put data in acme-challenge file
|
# put data in acme-challenge file
|
||||||
data_file = challenge_path / pathlib.Path(filename)
|
data_file = challenge_path / pathlib.Path(filename)
|
||||||
@ -82,7 +124,6 @@ def main(args):
|
|||||||
data_file.chmod(0o644)
|
data_file.chmod(0o644)
|
||||||
|
|
||||||
# put symlink in nginx enabled sites
|
# put symlink in nginx enabled sites
|
||||||
|
|
||||||
symlink_name = pathlib.Path(f'{service}-le')
|
symlink_name = pathlib.Path(f'{service}-le')
|
||||||
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')
|
||||||
@ -94,8 +135,7 @@ def main(args):
|
|||||||
service_enabled_symlink.symlink_to(service_available_file)
|
service_enabled_symlink.symlink_to(service_available_file)
|
||||||
|
|
||||||
# open port 80 to ${service}.drheck.dev
|
# open port 80 to ${service}.drheck.dev
|
||||||
|
os.environ['state'] = 'HTTP_UP'
|
||||||
os.environ['state'] = 'HTTP_DOWN'
|
|
||||||
os.environ['ssl_service'] = service
|
os.environ['ssl_service'] = service
|
||||||
cmd = ['/usr/bin/ssh', '-i', '/root/.ssh/id_autofirewall', '-o',
|
cmd = ['/usr/bin/ssh', '-i', '/root/.ssh/id_autofirewall', '-o',
|
||||||
'SendEnv=state', '-o', 'SendEnv=ssl_service', '-l', 'luke131',
|
'SendEnv=state', '-o', 'SendEnv=ssl_service', '-l', 'luke131',
|
||||||
@ -103,7 +143,8 @@ def main(args):
|
|||||||
print(f'cmd: {cmd}')
|
print(f'cmd: {cmd}')
|
||||||
p = pexpect.spawnu(' '.join(cmd))
|
p = pexpect.spawnu(' '.join(cmd))
|
||||||
p.logfile = sys.stderr
|
p.logfile = sys.stderr
|
||||||
res = p.expect(["""Enter passphrase for key '/root/.ssh/id_autofirewall':""", pexpect.TIMEOUT, pexpect.EOF])
|
res = p.expect(["Enter passphrase for key '/root/.ssh/id_autofirewall':",
|
||||||
|
pexpect.TIMEOUT, pexpect.EOF])
|
||||||
if res > 0:
|
if res > 0:
|
||||||
sys.exit('Couldnt send decryption key to ssh.')
|
sys.exit('Couldnt send decryption key to ssh.')
|
||||||
p.sendline(decrypt_pp)
|
p.sendline(decrypt_pp)
|
||||||
@ -114,18 +155,103 @@ def main(args):
|
|||||||
print(f'Turned up HTTP for {service}')
|
print(f'Turned up HTTP for {service}')
|
||||||
|
|
||||||
# restart nginx
|
# restart nginx
|
||||||
|
cmd = ['/usr/bin/systemctl', 'restart', 'nginx']
|
||||||
|
p = subprocess.run(cmd, capture_output=True)
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
# get nginx status
|
||||||
|
cmd = ['/usr/bin/systemctl', 'status', 'nginx']
|
||||||
|
p = subprocess.run(cmd, capture_output=True)
|
||||||
|
stdout = p.stdout.decode('UTF-8').split('\n')
|
||||||
|
success = False
|
||||||
|
for line in stdout:
|
||||||
|
if 'Active: active (running)' in line:
|
||||||
|
success = True
|
||||||
|
if not success:
|
||||||
|
sys.exit('nginx did not restart properly')
|
||||||
|
print('nginx restarted properly')
|
||||||
|
|
||||||
# continue with certbot
|
# continue with certbot
|
||||||
# close port 80 to git.drheck.dev
|
cb.sendline()
|
||||||
|
res = cb.expect([pexpect.EOF])
|
||||||
|
print(cb.before)
|
||||||
|
|
||||||
|
# close 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 down HTTP for {service}')
|
||||||
|
|
||||||
# remove symlink in nginx enabled sites
|
# remove symlink in nginx enabled sites
|
||||||
|
service_enabled_symlink.unlink()
|
||||||
|
|
||||||
# restart nginx
|
# restart nginx
|
||||||
|
cmd = ['/usr/bin/systemctl', 'restart', 'nginx']
|
||||||
|
p = subprocess.run(cmd, capture_output=True)
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
# get nginx status
|
||||||
|
cmd = ['/usr/bin/systemctl', 'status', 'nginx']
|
||||||
|
p = subprocess.run(cmd, capture_output=True)
|
||||||
|
stdout = p.stdout.decode('UTF-8').split('\n')
|
||||||
|
success = False
|
||||||
|
for line in stdout:
|
||||||
|
if 'Active: active (running)' in line:
|
||||||
|
success = True
|
||||||
|
if not success:
|
||||||
|
sys.exit('nginx did not restart properly')
|
||||||
|
print('nginx restarted properly')
|
||||||
|
|
||||||
# chmod
|
# chmod
|
||||||
# /etc/letsencrypt/live/git.drheck.dev
|
key_path = pathlib.Path('/etc/letsencrypt')
|
||||||
# /etc/letsencrypt/archive/git.drheck.dev
|
live = key_path / pathlib.Path(f'live/{service}.drheck.dev')
|
||||||
# restart gitea
|
archive = key_path / pathlib.Path(f'archive/{service}.drheck.dev')
|
||||||
# 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()
|
|
||||||
|
|
||||||
|
user = users[service]
|
||||||
|
uid = pwd.getpwnam(user).pw_uid
|
||||||
|
gid = pwd.getpwnam(user).pw_gid
|
||||||
|
|
||||||
|
os.chown(live, uid, gid)
|
||||||
|
os.chown(archive, uid, gid)
|
||||||
|
for cert_file in cert_files:
|
||||||
|
os.chown(archive / pathlib.Path(cert_file), uid, gid)
|
||||||
|
|
||||||
|
# service specific work
|
||||||
|
if service in globals():
|
||||||
|
eval(f'{service}()')
|
||||||
|
print(f'{service} specific work complete.')
|
||||||
|
|
||||||
|
# restart $service
|
||||||
|
systemd = service_systemd[service]
|
||||||
|
cmd = ['/usr/bin/systemctl', 'restart', systemd]
|
||||||
|
p = subprocess.run(cmd)
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
# get $service status
|
||||||
|
cmd = ['/usr/bin/systemctl', 'status', systemd]
|
||||||
|
p = subprocess.run(cmd, capture_output=True)
|
||||||
|
stdout = p.stdout.decode('UTF-8').split('\n')
|
||||||
|
success = False
|
||||||
|
for line in stdout:
|
||||||
|
if 'Active: active (running)' in line:
|
||||||
|
success = True
|
||||||
|
if not success:
|
||||||
|
sys.exit(f'{service} did not restart properly')
|
||||||
|
print(f'{service} restarted properly')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user