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.

## 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
}
)
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
SYMBOL INDEX (34 symbols across 4 files)
FILE: rwssl/rwssl.py
function main (line 12) | def main():
FILE: rwssl/tools.py
function parsetpl (line 11) | def parsetpl(tpl, data={}):
function runcmd (line 16) | def runcmd(cmd):
function reloadservice (line 22) | def reloadservice(service):
function restartservice (line 25) | def restartservice(service):
function rmcontent (line 28) | def rmcontent(path):
function getsubstr (line 35) | def getsubstr(s, start, end):
function userexists (line 38) | def userexists(username):
function doconfirm (line 45) | def doconfirm(msg = "Do you really want to perform this irreversible act...
FILE: rwssl/utils.py
class ServerPilot (line 12) | class ServerPilot:
method __init__ (line 13) | def __init__(self, username=False, app=False):
method setuser (line 26) | def setuser(self, username):
method setapp (line 29) | def setapp(self, app):
method setdomains (line 32) | def setdomains(self, domains):
method usrhome (line 43) | def usrhome(self):
method appsdir (line 48) | def appsdir(self):
method appdir (line 51) | def appdir(self):
method appnginxconf (line 56) | def appnginxconf(self):
method isvalidapp (line 61) | def isvalidapp(self):
method appdetails (line 66) | def appdetails(self):
method findapps (line 81) | def findapps(self):
method gettpldata (line 119) | def gettpldata(self):
method createnginxvhost (line 142) | def createnginxvhost(self):
method createnginxsslvhost (line 151) | def createnginxsslvhost(self):
method createnginxsslforcedvhost (line 158) | def createnginxsslforcedvhost(self):
method reloadservices (line 165) | def reloadservices(self):
method search (line 168) | def search(self, value, data):
method getcert (line 177) | def getcert(self):
method apphasssl (line 252) | def apphasssl(self):
method removecert (line 255) | def removecert(self):
method forcessl (line 280) | def forcessl(self):
method unforcessl (line 295) | def unforcessl(self):
FILE: setup.py
class SetupSslRenewCron (line 8) | class SetupSslRenewCron(install):
method run (line 9) | def run(self):
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (36K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 43,
"preview": "custom: ['https://buymeacoffee.com/rehmat']"
},
{
"path": ".gitignore",
"chars": 99,
"preview": ".installed.cfg\nbin\ndevelop-eggs\ndist\ndownloads\neggs\nparts\nrwssl.egg-info\nlib\nlib64\ntest-data\n*.pyc\n"
},
{
"path": "README.md",
"chars": 5457,
"preview": "# [Try a Free Control Panel](https://fastcp.org)\nI like ServerPilot a lot. How it configures the LAMP/LEMP stack and how"
},
{
"path": "requirements.txt",
"chars": 134,
"preview": "decorator==4.4.0\nJinja2==2.10.3\nMarkupSafe==1.1.1\npython-nginx==1.5.3\nsix==1.12.0\ntermcolor==1.1.0\nvalidators==0.14.0\nce"
},
{
"path": "rwssl/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "rwssl/__main__.py",
"chars": 61,
"preview": "from rwssl.rwssl import *\nif __name__ == '__main__':\n\tmain()\n"
},
{
"path": "rwssl/rwssl.py",
"chars": 8494,
"preview": "#! /usr/bin/env python\n# -*- coding: utf-8 -*-\nimport argparse\nfrom .utils import ServerPilot\nfrom termcolor import colo"
},
{
"path": "rwssl/templates/acme.tpl",
"chars": 829,
"preview": "############################################################################### \n# This conf file was auto-generated by "
},
{
"path": "rwssl/templates/nginx-main.tpl",
"chars": 465,
"preview": "############################################################################### \n# This conf file was auto-generated by "
},
{
"path": "rwssl/templates/nginx-ssl.tpl",
"chars": 2050,
"preview": "############################################################################### \n# This conf file was auto-generated by "
},
{
"path": "rwssl/templates/nginx-sslforced.tpl",
"chars": 1612,
"preview": "############################################################################### \n# This conf file was auto-generated by "
},
{
"path": "rwssl/templates/nginx.tpl",
"chars": 1079,
"preview": "############################################################################### \n# This conf file was auto-generated by "
},
{
"path": "rwssl/tools.py",
"chars": 1286,
"preview": "import subprocess\nimport os\nfrom jinja2 import Environment, BaseLoader\nimport pkgutil\nimport shutil\nimport pwd\nimport co"
},
{
"path": "rwssl/utils.py",
"chars": 11514,
"preview": "import os\nfrom .tools import *\nfrom termcolor import colored\nimport nginx\nimport json\nimport validators\nfrom getpass imp"
},
{
"path": "setup.py",
"chars": 1522,
"preview": "from setuptools import setup\nimport os\nimport subprocess\nfrom setuptools.command.install import install\nimport shutil\nim"
}
]
About this extraction
This page contains the full source code of the rehmatworks/serverpilot-letsencrypt GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (33.8 KB), approximately 8.6k tokens, and a symbol index with 34 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.