Repository: rehmatworks/serverpilot-letsencrypt Branch: master Commit: 6c7c8aa9fc60 Files: 15 Total size: 33.8 KB Directory structure: gitextract_5wsgpi4j/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── README.md ├── requirements.txt ├── rwssl/ │ ├── __init__.py │ ├── __main__.py │ ├── rwssl.py │ ├── templates/ │ │ ├── acme.tpl │ │ ├── nginx-main.tpl │ │ ├── nginx-ssl.tpl │ │ ├── nginx-sslforced.tpl │ │ └── nginx.tpl │ ├── tools.py │ └── utils.py └── setup.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ custom: ['https://buymeacoffee.com/rehmat'] ================================================ FILE: .gitignore ================================================ .installed.cfg bin develop-eggs dist downloads eggs parts rwssl.egg-info lib lib64 test-data *.pyc ================================================ FILE: README.md ================================================ # [Try a Free Control Panel](https://fastcp.org) I like ServerPilot a lot. How it configures the LAMP/LEMP stack and how it makes use of ACLs to give the secure access to individual users is really great. But I don't like a few things about it: 1. It is entirely paid 2. You need to give your server access away To overcome these issues, I have come up with a free control panel. I have followed the best practices from ServerPilot in configuring NGINX, Apache, MySQL, and PHP as well as I have used a similary directory structure and I have made a similar use of ACLs. But it is entirely self-hosted. You can use my developed control panel to deploy and manage multiple PHP websites on a single server. It is entirely open source and free!! * [Try FastCP now](https://fastcp.org) * [View on GitHub](https://github.com/rehmatworks/fastcp) ** If you like it, consider giving it a star. I'll try to update this ServerPilot Let's encrypt automation script too.** ## ServerPilot Let's Encrypt (`rwssl`) v2.x This Python utility allows you to automate the installation/uninstallation of SSL certificates from Let's Encrypt on ServerPilot servers. Both free servers (from old grand-fathered plan) and servers on premium plans are supported. ![serverpilot-letsencrypt](https://raw.githubusercontent.com/rehmatworks/serverpilot-letsencrypt/master/rwssl.png "ServerPilto Let's Encrypt") ## Getting Started First of all, sign in as root user (or with sudo privileges). Now remove the very old script if you are still using it: ```bash rm /usr/local/bin/rwssl ``` And then install some needed packages: ```bash apt-get update && \ apt-get -y install python3-pip build-essential libssl-dev libffi-dev python3-dev ``` Uninstall older version if exists: ```bash pip3 uninstall -y rwssl ``` And then install the latest version from PyPi: ```bash pip3 install rwssl==2.0.4 ``` Verify that the installation worked. This should bring up the help menu for **rwssl**: ```bash rwssl -h ``` The alternate way to install **rwssl** is by cloning the repository: ```bash cd ~/ && \ git clone https://github.com/rehmatworks/serverpilot-letsencrypt && \ cd serverpilot-letsencrypt && \ pip3 install -r requirements.txt && \ python3 setup.py install ``` Only Python 3.5 and up supported so you shoul install & use rwssl package using pip3 and Python 3.x. ## Available Commands with Examples: Once **rwssl** is installed, a command `rwssl` will become available in your terminal. You will have access to the following sub-commands in order to manage your server. **Update:** Please note that old commands aren't available anymore due to a recent major upgrade. Please check below table for new commands that come with rwssl. You can get help by typing `rwssl -h` as well. | Command | Details | Examples | | ------- | --- | -- | | getcert | Get letsencrypt cert for an app. | `rwssl getcert --app foo` | | getcerts | Get letsencrypt certs for all apps. | `rwssl getcerts` for all users apps or `rwssl getcerts --user john` for john's apps | | removecert | Uninstall SSL cert from an app. | `rwssl removecert --app foo` | | removecerts | Uninstall SSL certs for all apps. | `rwssl removecerts` for all users apps or `rwssl removecerts --user john` for john's apps | | forcessl | Force SSL certificate for an app. | `rwssl forcessl --app foo` | | unforcessl | Unforce SSL certificate for an app. | `rwssl unforcessl --app foo` | | forceall | Force HTTPs for all apps. | `rwssl forceall` for all users apps or `rwssl forceall --user john` for john's apps | | unforceall | Unforce HTTPs for all apps. | `rwssl unforceall` for all users apps or `rwssl unforceall --user john` for john's apps | You can use `rwssl -h` command to get to the help page on above commands. ## Uninstall To uninstall rwssl completely, run: ```bash pip3 uninstall rwssl ``` As a CRON job is added for SSL renewals by rwssl, you can remove the CRON file by running: ```bash rm /etc/cron.weekly/rwssl-sslrenewals ``` Moreoever, a conf file `acme.conf` is created in conf directory of each app in vhosts.d. You should delete them as well. For example, if your app name is example, you should delete the conf file `/etc/nginx-sp/vhosts.d/example.d/acme.conf`. Repeat this step for each app where you used rwssl to get the SSL certificates. That's all! ## Changelog ## [2.0.4] - 2020-07-19 A minor upgrade that addresses renewal CRON issue and DNS-related bug. ### Fixes - Fixed invalid DNS bug - Fixed renewal cron file generation ## [2.0.0] - 2020-04-18 A major upgrade that addresses all reported bugs including SSL renewals. ### Changes - Custom path is used to store SSL certificates - Certificate is named after app name (Addresses missing cert path issue) - Improved vhost file parsing to get app details - Dropped support for Python 2.x (Only Python 3.x is supported) - Using Let's Encrypt staging server (via dry-run) for domain validation (To address quota issues) ### Added - Using Jinja template engine to generate virtual host files from templates - Using a custom ACME verification location (To avoid initial verifications & renewal issues) - Using latest certbot Python package - ACME v2 protocol support - Added validation after vhost files are written (and changes are reverted if errors encountered) ## Bugs & Suggestions For security-related issues, please email me at **contact@rehmat.works** and for common bug reports / feature requests, use the issues section. ================================================ FILE: requirements.txt ================================================ decorator==4.4.0 Jinja2==2.10.3 MarkupSafe==1.1.1 python-nginx==1.5.3 six==1.12.0 termcolor==1.1.0 validators==0.14.0 certbot==0.39.0 ================================================ FILE: rwssl/__init__.py ================================================ ================================================ FILE: rwssl/__main__.py ================================================ from rwssl.rwssl import * if __name__ == '__main__': main() ================================================ FILE: rwssl/rwssl.py ================================================ #! /usr/bin/env python # -*- coding: utf-8 -*- import argparse from .utils import ServerPilot from termcolor import colored import sys import validators from .tools import * import shutil import subprocess def main(): sp = ServerPilot() # Setup cron cronpath = '/etc/cron.weekly/rwssl-sslrenewals' if not os.path.exists(cronpath): croncmd = '%s renew --non-interactive --config-dir /etc/nginx-sp/le-ssls --post-hook "service nginx-sp reload"\n' % shutil.which('certbot') with open(cronpath, 'w') as cronfile: cronfile.writelines(['#!/bin/sh\n', croncmd]) maxexeccmd = "chmod +x {}".format(cronpath) FNULL = open(os.devnull, 'w') subprocess.check_call([maxexeccmd], shell=True, stdout=FNULL, stderr=subprocess.STDOUT) ap = argparse.ArgumentParser(description='A powerful tool to manage SSLs on servers provisioned using ServerPilot.io.') subparsers = ap.add_subparsers(dest="action") # SSL ssl = subparsers.add_parser('getcert', help='Get letsencrypt cert for an app.') ssl.add_argument('--app', dest='app', help='App name for which you want to get an SSL cert.', required=True) sslall = subparsers.add_parser('getcerts', help='Get letsencrypt certs for all apps.') sslall.add_argument('--user', dest='user', help='SSH user to activate SSL for their owned apps. If not provided, SSL will be activated for all apps.', required=False) ussl = subparsers.add_parser('removecert', help='Uninstall SSL cert from an app.') ussl.add_argument('--app', dest='app', help='App name from which you want to uninstall the SSL cert.', required=True) usslall = subparsers.add_parser('removecerts', help='Uninstall SSL certs for all apps.') usslall.add_argument('--user', dest='user', help='SSH user to remove SSLs for their owned apps. If not provided, SSL will be uninstalled from all apps.', required=False) forcessl = subparsers.add_parser('forcessl', help='Force SSL certificate for an app.') forcessl.add_argument('--app', dest='app', help='App name for which you want to force the HTTPS scheme.', required=True) unforcessl = subparsers.add_parser('unforcessl', help='Unforce SSL certificate for an app.') unforcessl.add_argument('--app', dest='app', help='App name for which you want to unforce the HTTPS scheme.', required=True) forceall = subparsers.add_parser('forceall', help='Force HTTPs for all apps.') forceall.add_argument('--user', dest='user', help='SSH user to force HTTPs for their owned apps. If not provided, SSL will be forced for all apps.', required=False) unforceall = subparsers.add_parser('unforceall', help='Unforce HTTPs for all apps.') unforceall.add_argument('--user', dest='user', help='SSH user to unforce HTTPs for their owned apps. If not provided, SSL will be unforced for all apps.', required=False) args = ap.parse_args() if len(sys.argv) <= 1: ap.print_help() sys.exit(0) if args.action == 'getcert': if doconfirm('Do you really want to obtain an SSL certificate for the app {}?'.format(args.app)): sp.setapp(args.app) try: print(colored('Activating SSL for app {}...'.format(args.app), 'blue')) sp.getcert() except Exception as e: print(colored(str(e), 'yellow')) if args.action == 'getcerts': if args.user: sp.setuser(args.user) confirmmsg = 'Do you really want to activate SSL for all apps owned by {}?'.format(args.user) else: confirmmsg = 'Do you really want to activate SSL for all apps existing on this server?' if doconfirm(confirmmsg): try: apps = sp.findapps() if len(apps) > 0: for app in apps: print(colored('Activating SSL for app {}...'.format(app[1]), 'blue')) sp.app = app[1] sp.getcert() else: raise Exception('No apps found!') except Exception as e: print(colored(str(e), 'yellow')) if args.action == 'removecert': if doconfirm('Do you really want to uninstall SSL certificate for the app {}?'.format(args.app)): sp.setapp(args.app) try: print(colored('Uninstalling SSL from app {}...'.format(args.app), 'blue')) sp.removecert() print(colored('SSL has been uninstalled from the app {}.'.format(args.app), 'green')) except Exception as e: print(colored(str(e), 'yellow')) if args.action == 'removecerts': if args.user: sp.setuser(args.user) confirmmsg = 'Do you really want to uninstall SSL for all apps owned by {}?'.format(args.user) else: confirmmsg = 'Do you really want to uninstall SSL for all apps existing on this server?' if doconfirm(confirmmsg): try: apps = sp.findapps() if len(apps) > 0: for app in apps: print(colored('Removing SSL certificate from app {}...'.format(app[1]), 'blue')) sp.app = app[1] sp.removecert() print(colored('SSL has been uninstalled from app {}.'.format(app[1]), 'green')) else: raise Exception('No apps found!') except Exception as e: print(colored(str(e), 'yellow')) if args.action == 'forcessl': if doconfirm('Do you really want to force HTTPs for the app {}?'.format(args.app)): sp.setapp(args.app) try: sp.forcessl() print(colored('HTTPs has been forced for the app {}.'.format(args.app), 'green')) except Exception as e: print(colored(str(e), 'yellow')) if args.action == 'forceall': if args.user: sp.setuser(args.user) confirmmsg = 'Do you really want to force SSL for all apps owned by {}?'.format(args.user) else: confirmmsg = 'Do you really want to force SSL for all apps existing on this server?' if doconfirm(confirmmsg): try: apps = sp.findapps() if len(apps) > 0: for app in apps: print(colored('Forcing SSL certificate for app {}...'.format(app[1]), 'blue')) try: sp.app = app[1] sp.forcessl() print(colored('SSL has been forced for app {}.'.format(app[1]), 'green')) except Exception as e: print(colored(str(e), 'yellow')) else: raise Exception('No apps found!') except Exception as e: print(colored(str(e), 'yellow')) if args.action == 'unforcessl': if doconfirm('Do you really want to unforce HTTPs for the app {}?'.format(args.app)): try: sp.setapp(args.app) sp.unforcessl() print(colored('HTTPs has been unforced for the app {}.'.format(args.app), 'green')) except Exception as e: print(colored(str(e), 'yellow')) if args.action == 'unforceall': if args.user: sp.setuser(args.user) confirmmsg = 'Do you really want to unforce SSL for all apps owned by {}?'.format(args.user) else: confirmmsg = 'Do you really want to unforce SSL for all apps existing on this server?' if doconfirm(confirmmsg): try: apps = sp.findapps() if len(apps) > 0: for app in apps: print(colored('Unforcing SSL certificate for app {}...'.format(app[1]), 'blue')) try: sp.app = app[1] sp.unforcessl() print(colored('SSL has been unforced for app {}.'.format(app[1]), 'green')) except Exception as e: print(colored(str(e), 'yellow')) else: raise Exception('No apps found!') except Exception as e: print(colored(str(e), 'yellow')) ================================================ FILE: rwssl/templates/acme.tpl ================================================ ############################################################################### # This conf file was auto-generated by rwssl script # Author: Rehmat Alam # GitHub Repo: (https://github.com/rehmatworks/serverpilot-letsencrypt/) # Twitter: https://twitter.com/rehmatworks # Email: contact@rehmat.works # # This file rewrites ACME root in order to prevent issues during ACME challenge # verification & later in renewal process. HTACCESS or NGINX rules may interfere # with ACME requests and may result in renewal failure. This conf file prevents # that from happening. ############################################################################### location ^~ /.well-known/acme-challenge/ { default_type "text/plain"; rewrite /.well-known/acme-challenge/(.*) /$1 break; root /var/.rwssl/.well-known/acme-challenge/; } ================================================ FILE: rwssl/templates/nginx-main.tpl ================================================ ############################################################################### # This conf file was auto-generated by rwssl script # Author: Rehmat Alam # GitHub Repo: (https://github.com/rehmatworks/serverpilot-letsencrypt/) # Twitter: https://twitter.com/rehmatworks # Email: contact@rehmat.works ############################################################################### location / { proxy_pass $backend_protocol://$backend_host:$backend_port; } ================================================ FILE: rwssl/templates/nginx-ssl.tpl ================================================ ############################################################################### # This conf file was auto-generated by rwssl script # Author: Rehmat Alam # GitHub Repo: (https://github.com/rehmatworks/serverpilot-letsencrypt/) # Twitter: https://twitter.com/rehmatworks # Email: contact@rehmat.works ############################################################################### server { listen 80; listen [::]:80; server_name {{ servername }}{% if serveralias %} {{ serveralias }}{% endif %}; root /srv/users/{{ username }}/apps/{{ appname }}/public; access_log /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.access.log main; error_log /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.error.log; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.nonssl_conf; include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.conf; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name {{ servername }}{% if serveralias %} {{ serveralias }}{% endif %}; ssl_certificate_key {{ sslpath }}/live/{{appname}}/privkey.pem; ssl_certificate {{ sslpath }}/live/{{appname}}/fullchain.pem; root /srv/users/{{ username }}/apps/{{ appname }}/public; access_log /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.access_ssl.log main; error_log /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.error_ssl.log; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-SSL on; proxy_set_header X-Forwarded-Proto $scheme; include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.ssl_conf; include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.conf; } ================================================ FILE: rwssl/templates/nginx-sslforced.tpl ================================================ ############################################################################### # This conf file was auto-generated by rwssl script # Author: Rehmat Alam # GitHub Repo: (https://github.com/rehmatworks/serverpilot-letsencrypt/) # Twitter: https://twitter.com/rehmatworks # Email: contact@rehmat.works ############################################################################### server { listen 80; listen [::]:80; server_name {{ servername }}{% if serveralias %} {{ serveralias }}{% endif %}; location /.well-known { try_files $uri $uri/ =404; } return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name {{ servername }}{% if serveralias %} {{ serveralias }}{% endif %}; ssl_certificate_key {{ sslpath }}/live/{{appname}}/privkey.pem; ssl_certificate {{ sslpath }}/live/{{appname}}/fullchain.pem; root /srv/users/{{ username }}/apps/{{ appname }}/public; access_log /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.access_ssl.log main; error_log /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.error_ssl.log; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-SSL on; proxy_set_header X-Forwarded-Proto $scheme; include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.ssl_conf; include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.conf; } ================================================ FILE: rwssl/templates/nginx.tpl ================================================ ############################################################################### # This conf file was auto-generated by rwssl script # Author: Rehmat Alam # GitHub Repo: (https://github.com/rehmatworks/serverpilot-letsencrypt/) # Twitter: https://twitter.com/rehmatworks # Email: contact@rehmat.works ############################################################################### server { listen 80; listen [::]:80; server_name {{ servername }}{% if serveralias %} {{ serveralias }}{% endif %}; root /srv/users/{{ username }}/apps/{{ appname }}/public; access_log /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.access.log main; error_log /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.error.log; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.nonssl_conf; include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.conf; } ================================================ FILE: rwssl/tools.py ================================================ import subprocess import os from jinja2 import Environment, BaseLoader import pkgutil import shutil import pwd import configparser import warnings def parsetpl(tpl, data={}): tplstr = str(pkgutil.get_data('rwssl', 'templates/{}'.format(tpl)).decode('utf-8')) tpl = Environment(loader=BaseLoader).from_string(tplstr) return tpl.render(**data) def runcmd(cmd): FNULL = open(os.devnull, 'w') if not "sudo" in cmd: cmd = "sudo {}".format(cmd) subprocess.check_call([cmd], shell=True, stdout=FNULL, stderr=subprocess.STDOUT) def reloadservice(service): runcmd('service {} reload'.format(service)) def restartservice(service): runcmd('service {} restart'.format(service)) def rmcontent(path): if os.path.exists(path): if os.path.isdir(path): shutil.rmtree(path) else: os.unlink(path) def getsubstr(s, start, end): return (s.split(start))[1].split(end)[0] def userexists(username): try: pwd.getpwnam(username) return True except KeyError: return False def doconfirm(msg = "Do you really want to perform this irreversible action"): answer = "" while answer not in ["y", "n"]: answer = input("{} [Y/N] ".format(msg)).lower() return answer == "y" ================================================ FILE: rwssl/utils.py ================================================ import os from .tools import * from termcolor import colored import nginx import json import validators from getpass import getpass import socket import re class ServerPilot: def __init__(self, username=False, app=False): self.mainroot = '/' self.usrdataroot = os.path.join(self.mainroot, 'srv', 'users') self.nginxroot = os.path.join(self.mainroot, 'etc', 'nginx-sp') self.sslroot = os.path.join(self.nginxroot, 'le-ssls') self.vhostdir = 'vhosts.d' self.acmeroot = '/var/.rwssl/' self.acmeconf = 'acme.conf' self.username = username self.app = app self.domains = [] self.acmetpl = 'acme.tpl' def setuser(self, username): self.username = username def setapp(self, app): self.app = app def setdomains(self, domains): doms = domains.split(',') for dom in doms: if validators.domain(dom) is True: self.domains.append(dom) else: raise Exception('{} is not a valid domain.'.format(dom)) if len(self.domains) == 0: raise Exception( 'You need to provide at least one valid domain name.') def usrhome(self): if not self.username: raise Exception('SSH username has not been provided.') return os.path.join(self.usrdataroot, self.username) def appsdir(self): return os.path.join(self.usrhome(), 'apps') def appdir(self): if not self.app: raise Exception('App name has not been provided.') return os.path.join(self.appsdir(), self.app) def appnginxconf(self): if not self.app: raise Exception('App name has not been provided.') return os.path.join(self.nginxroot, self.vhostdir, '{}.conf'.format(self.app)) def isvalidapp(self): if self.appdetails(): return True return False def appdetails(self): conff = os.path.join(self.nginxroot, self.vhostdir, '{}.conf'.format(self.app)) if not os.path.exists(conff): raise Exception('Looks like you provided a wrong app name.') c = nginx.loadf(conff) if len(c.filter('Server')) == 2: s = c.filter('Server')[1] else: s = c.filter('Server')[0] return { 'domains': list(filter(None, re.sub('\s+', ' ', s.filter('Key', 'server_name')[0].as_dict.get('server_name')).split(' '))), 'user': list(filter(None, re.sub('\s+', ' ', s.filter('Key', 'root')[0].as_dict.get('root')).split('/')))[2] } def findapps(self): appsdata = [] i = 0 if not self.username: os.chdir(self.usrdataroot) users = list(filter(os.path.isdir, os.listdir(os.curdir))) if len(users): for user in users: self.username = user appsdir = self.appsdir() if os.path.exists(appsdir): os.chdir(appsdir) apps = list( filter(os.path.isdir, os.listdir(os.curdir))) for app in apps: self.app = app if self.isvalidapp(): i += 1 info = self.appdetails() appsdata.append([i, self.app, info.get( 'user'), ','.join(info.get('domains'))]) else: appsdir = self.appsdir() if not os.path.exists(appsdir): raise Exception( 'Looks like you have provided an invalid SSH user.') else: os.chdir(appsdir) apps = list(filter(os.path.isdir, os.listdir(os.curdir))) for app in apps: self.app = app if self.isvalidapp(): i += 1 info = self.appdetails() appsdata.append([i, self.app, info.get( 'user'), ','.join(info.get('domains'))]) return appsdata def gettpldata(self): if len(self.domains) > 1: serveralias = '' else: serveralias = False servername = '' di = 0 for dom in self.domains: di += 1 if di == 1: servername = dom else: if di > 2: serveralias += ' ' serveralias += dom return { 'appname': self.app, 'username': self.username, 'servername': servername, 'serveralias': serveralias } def createnginxvhost(self): data = self.gettpldata() nginxmaindata = parsetpl('nginx-main.tpl') with open(os.path.join(self.nginxroot, self.vhostdir, '{}.d'.format(self.app), 'main.conf'), 'w') as nginxmain: nginxmain.write(nginxmaindata) nginxtpldata = parsetpl('nginx.tpl', data=data) with open(self.appnginxconf(), 'w') as nginxconf: nginxconf.write(nginxtpldata) def createnginxsslvhost(self): data = self.gettpldata() data.update({'sslpath': self.sslroot}) nginxtpldata = parsetpl('nginx-ssl.tpl', data=data) with open(self.appnginxconf(), 'w') as nginxconf: nginxconf.write(nginxtpldata) def createnginxsslforcedvhost(self): data = self.gettpldata() data.update({'sslpath': self.sslroot}) nginxtpldata = parsetpl('nginx-sslforced.tpl', data=data) with open(self.appnginxconf(), 'w') as nginxconf: nginxconf.write(nginxtpldata) def reloadservices(self): restartservice('nginx-sp') def search(self, value, data): for conf in data: blocks = conf.get('server') for block in blocks: found = block.get(value) if found: return found return None def getcert(self): if not self.isvalidapp(): raise Exception('A valid app name is not provided.') if not os.path.exists(self.acmeroot): os.makedirs(self.acmeroot) # Remove old SSL force rule if exists oldnonsslconf = os.path.join(self.nginxroot, self.vhostdir, '{}.d'.format(self.app), 'rwssl.nonssl_conf') if os.path.exists(oldnonsslconf): os.unlink(oldnonsslconf) reloadservice('nginx-sp') letpl = os.path.join(self.nginxroot, self.vhostdir, '{}.d'.format(self.app), self.acmeconf) if not os.path.exists(letpl): tpldata = parsetpl(self.acmetpl, data={'acmeroot': self.acmeroot}) with open(letpl, 'w') as letplf: letplf.write(tpldata) reloadservice('nginx-sp') with open(self.appnginxconf()) as nginxconf: nginxconfbackup = nginxconf.read() details = self.appdetails() self.setuser(details.get('user')) self.domains = details.get('domains') validdoms = [] try: for domain in details.get('domains'): cmd = "certbot certonly --non-interactive --dry-run --webroot -w {} --register-unsafely-without-email --agree-tos -d {}".format( self.acmeroot, domain) try: runcmd(cmd) validdoms.append(domain) except: ip = socket.gethostbyname(domain) if validators.ipv4(ip) or validators.ipv6(ip): errmsg = 'A possible DNS issue found. {} may be pointing to a wrong IP ({})'.format(domain, ip) else: errmsg = 'SSL is not available for {} yet.'.format( domain) print(colored(errmsg, 'yellow')) except Exception as e: pass if len(validdoms) > 0: domainsstr = '' webroot = self.acmeroot for vd in validdoms: domainsstr += ' -d {}'.format(vd) cmd = "certbot certonly --non-interactive --agree-tos --register-unsafely-without-email --webroot -w {} --cert-name {} --config-dir {}{}".format( webroot, self.app, self.sslroot, domainsstr) runcmd(cmd) self.createnginxsslvhost() try: # For backward compatibility, clean old SSL-vhost if exists oldsslconf = os.path.join( self.nginxroot, self.vhostdir, '{}-ssl.conf'.format(self.app)) if os.path.exists(oldsslconf): os.unlink(oldsslconf) reloadservice('nginx-sp') print(colored('SSL activated for app {} (Domains Secured: {})'.format( self.app, ' '.join(validdoms)), 'green')) except: try: restartservice('nginx-sp') except: with open(self.appnginxconf(), 'w') as restoreconf: restoreconf.write(nginxconfbackup) restartservice('nginx-sp') raise Exception('SSL activation failed!') else: raise Exception('SSL not available for this app yet.') def apphasssl(self): return os.path.exists(os.path.join(self.sslroot, 'live', self.app, 'fullchain.pem')) def removecert(self): if not self.isvalidapp(): raise Exception('A valid app name should be provided.') if not self.apphasssl(): raise Exception( 'The app {} does not have an active SSL certificate.'.format(self.app)) details = self.appdetails() self.domains = details.get('domains') self.setuser(details.get('user')) cmd = "certbot --non-interactive revoke --config-dir {} --cert-name {}".format( self.sslroot, self.app) try: runcmd(cmd) self.createnginxvhost() try: reloadservice('nginx-sp') except: restartservice('nginx-sp') except Exception as e: raise Exception( "SSL certificate cannot be removed: {}".format(str(e))) def forcessl(self): if not self.isvalidapp(): raise Exception('A valid app name should be provided.') if not self.apphasssl(): raise Exception( 'The app {} does not have an active SSL certificate.'.format(self.app)) details = self.appdetails() self.setuser(details.get('user')) self.domains = details.get('domains') self.createnginxsslforcedvhost() try: reloadservice('nginx-sp') except: restartservice('nginx-sp') def unforcessl(self): if not self.isvalidapp(): raise Exception('A valid app name should be provided.') details = self.appdetails() self.setuser(details.get('user')) self.domains = details.get('domains') if self.apphasssl(): self.createnginxsslvhost() else: self.createnginxvhost() try: reloadservice('nginx-sp') except: restartservice('nginx-sp') ================================================ FILE: setup.py ================================================ from setuptools import setup import os import subprocess from setuptools.command.install import install import shutil import sys class SetupSslRenewCron(install): def run(self): crondir = '/etc/cron.weekly' cronfile = os.path.join(crondir, 'rwssl-sslrenewals') if not os.path.exists(crondir): os.makedirs(crondir) try: cmd = '%s renew --non-interactive --config-dir /etc/nginx-sp/le-ssls --post-hook "service nginx-sp reload"\n' % shutil.which('certbot') except: # which() on shutil module is not available under Python 3.x sys.exit('Looks like you are running an older version of Python. Only Python 3.x is supported.') with open(cronfile, 'w') as cf: cf.writelines(['#!/bin/sh\n', cmd]) maxexeccmd = "chmod +x {}".format(cronfile) FNULL = open(os.devnull, 'w') subprocess.check_call([maxexeccmd], shell=True, stdout=FNULL, stderr=subprocess.STDOUT) install.run(self) setup(name='rwssl', version='2.0.4', description='A Python package to manage Let\'s Encrypt SSL on ServerPilot provisioned servers.', author='Rehmat Alam', author_email='contact@rehmat.works', url='https://github.com/rehmatworks/serverpilot-letsencrypt/', license='MIT', python_requires='>3.5.2', entry_points={ 'console_scripts': [ 'rwssl = rwssl.rwssl:main' ], }, packages=[ 'rwssl' ], install_requires=[ 'python-nginx', 'validators', 'termcolor', 'tabulate', 'Jinja2', 'certbot' ], package_data={'rwssl': ['templates/*.tpl']}, cmdclass={ 'install': SetupSslRenewCron } )