Certbot host script for Nginx running in a Docker container
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

125 lines
3.8 KiB

#!/usr/bin/python3
# Dependencies:
# pip3 install crossplane
# https://github.com/nginxinc/crossplane
import sys
import crossplane
import pprint
import shlex
from subprocess import run
import certbot_config as config
# Resolve 'include' directive (non-recursive)
def resolve_include(response, directive):
result = []
for index in directive['includes']:
# Join parsed included file to result list
result.extend(response['config'][index]['parsed'])
return result
# Resolve all 'include' directives (recursive)
def resolve_includes(response, directives=None):
# Assume resolve of first file in response if not specified
if directives is None:
directives = response['config'][0]['parsed']
# Rebuild directive tree with includes in-place
result = []
for directive in directives:
if 'includes' in directive:
# Resolve include and recursively resolve includes in included file
result.extend(resolve_includes(response, resolve_include(response, directive)))
continue
if 'block' in directive:
# Recursively traverse block statement
directive['block'] = resolve_includes(response, directive['block'])
result.append(directive)
return result
# Search in directives using matching function
def search_directives(directives, fn, recursive=False):
result = []
for directive in directives:
if fn(directive):
# Append directive matched by search function to result list
result.append(directive)
elif recursive and 'block' in directive:
# Recursively search block statement
result.extend(search_directives(directive['block'], fn, True))
return result
# Recursive search in directives, steered by matching functions
def resolve_path(directives, fns):
# Pick the first search function in the list and perform a flat search
results = search_directives(directives, fns[0])
if len(results) > 0 and len(fns) > 1:
# A match for the first path element was found, recurse subtree to find next match
return resolve_path(results[0]['block'], fns[1:])
else:
# No further matches were found
return results
# Get all HTTP server directives
def get_servers(directives):
return resolve_path(directives, [
lambda d: ('directive', 'http') in d.items(),
lambda d: ('directive', 'server') in d.items()])
# Generate { server_name: root } dictionary of virtual hosts
def get_vhosts(directives):
servers = get_servers(directives)
vhosts = []
for server in servers:
# Retrieve server name directive
server_name = search_directives(server['block'], lambda d: ('directive', 'server_name') in d.items())
if len(server_name) == 0:
continue
for name in server_name[0]['args']:
# Apply server alias to vhosts list
vhosts.append(name)
return vhosts
# Parse nginx.conf file with Crossplane
response = crossplane.parse(config.nginx_config_file)
if response['errors']:
pprint.pprint(response['errors'])
sys.exit(1)
# Resolve all includes (combine into to one big config tree)
directives = resolve_includes(response)
# Retrieve all virtual hostnames
vhosts = [ name for name in get_vhosts(directives) if not name in config.ignored_domains ]
if config.primary_domain in vhosts:
# Ensure the primary hostname comes first
vhosts.remove(config.primary_domain)
vhosts.insert(0, config.primary_domain)
else:
print('Primary domain is missing from configuration')
sys.exit(1)
print()
print('Found virtual hosts:')
for name in vhosts:
print(' {}'.format(name))
print()
# Build certbot command line
cmd = config.certbot_command
for name in vhosts:
cmd.extend(['-d', name])
print('Running', ' '.join(cmd))
print()
run(cmd)