Skip to content

Instantly share code, notes, and snippets.

@squatto
Last active August 15, 2018 13:53
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save squatto/69d81ab4790f21a94828041c9295e8da to your computer and use it in GitHub Desktop.
Save squatto/69d81ab4790f21a94828041c9295e8da to your computer and use it in GitHub Desktop.
Webfaction letsencrypt automatic certificate renewal and installation

Webfaction letsencrypt automatic certificate renewal and installation

This process helps you get a free letsencrypt certificate installed on your Webfaction domain, and then keeps it renewed. You can follow these steps for as many domains as you need.

Based on the instructions here.
Follow all of the instructions, but use this python script instead.

Usage: ./certificate_renewal.py domain.tld certname

When adding the cron entry make sure to add the domain and certname to the command!

For example:

0 2 * * * /usr/local/bin/python $HOME/certificate_renewal.py example.com example.com-letsencrypt 2>> ~/certificate_renewal-example.com.log

#!/usr/local/bin/python
from os import chdir, environ, getcwd, listdir, stat
from sys import exit, argv
from subprocess import Popen, PIPE
from xmlrpclib import ServerProxy, Fault
HIDDEN_ACME_DIR_NAME = '.acme.sh'
if len(argv) < 2:
exit('Usage: %s domain.tld certname' % argv[0])
def data_to_var(filename):
try:
assert (filename in listdir('.') and stat(filename).st_size > 0)
except AssertionError as exc:
exit('The file \"{}\" does not exist inside \"{}\" or is empty. Exception: {}'.format(filename, getcwd(), exc))
else:
with open(filename, 'r') as f:
var_cert = f.read()
return var_cert
if __name__ == '__main__':
# Run the command advised by acme.sh script in order to renew the certificates.
# Each certificate lasts 90 days and the max permitted day to renew a certificate is 60 days from the issue date -
# in other words the earlier we can renew a certificate is 30 days before expiration. This can be changed through
# the --days argument during the --issue step. Type ".acme.sh/acme.sh --help" for more information.
# This script will run as a cron job every day in order for the certs to be renewed when appropriate.
acme_process = Popen(['%s/acme.sh' % HIDDEN_ACME_DIR_NAME, '--renew', '--domain', argv[1]], stdout=PIPE, stderr=PIPE)
out, err = acme_process.communicate()
if err:
exit("An error occurred during the renewal process. Error: {}".format(err))
if 'Cert success.' in out:
hostname, err = Popen(['hostname', '-s'], stdout=PIPE, stderr=PIPE).communicate()
if err:
exit("An error occurred while trying to determine the hostname. Error: {}".format(err))
d = {
'url': 'https://api.webfaction.com/', # Fixed. Not to be changed.
'version': 2, # Fixed. Not to be changed.
's_name': hostname.strip('\n').title(),
'user': 'webfaction username',
'pwd': 'webfaction password', # Your Webfaction password.
'domain': argv[1], # Your domain name where you issued the certificate.
'cert_name': argv[2] # Your certification name (see step #20).
}
# Initially empty values (to be filled later with data from files)
domain_cert, pv_key, intermediate_cert = '', '', ''
# Directory declarations in order to know where to work
valid_cert_dir = '{home}/{acme}/{domain}'.\
format(home=environ.get('HOME'), acme=HIDDEN_ACME_DIR_NAME, domain=d.get('domain'))
# Change directory to the one that matches our domain
chdir(valid_cert_dir)
# Test if current working directory is the valid one
try:
assert getcwd() == valid_cert_dir
except AssertionError:
exit('Current working directory is not {}! Instead is {}.'.format(valid_cert_dir, getcwd()))
# try to connect to Webfaction API
try:
server = ServerProxy(d.get('url'))
session_id, _ = server.login(d.get('user'), d.get('pwd'), d.get('s_name'), d.get('version'))
except Fault as e:
exit("Exception occurred at connection with Webfaction's API. {}".format(e))
else:
# Connection is successful. Proceed...
# read domain certificate and store it as a variable
domain_cert = data_to_var('{}.cer'.format(d.get('domain')))
# read private key certificate and store it as a variable
pv_key = data_to_var('{}.key'.format(d.get('domain')))
# read intermediate certificate and store it as a variable
intermediate_cert = data_to_var('ca.cer')
# Install the renewed certificate to your Web server through the Webfaction's API
if domain_cert and pv_key and intermediate_cert:
# https://docs.webfaction.com/xmlrpc-api/apiref.html#method-update_certificate
# update_certificate(session_id, name, certificate, private_key, intermediates)
server.update_certificate(session_id, d.get('cert_name'), domain_cert, pv_key, intermediate_cert)
else:
exit("Unable to renew cert. acme.sh output:\n\n{}".format(out))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment