[
  {
    "path": ".github/FUNDING.yml",
    "content": "custom: ['https://buymeacoffee.com/rehmat']"
  },
  {
    "path": ".gitignore",
    "content": ".installed.cfg\nbin\ndevelop-eggs\ndist\ndownloads\neggs\nparts\nrwssl.egg-info\nlib\nlib64\ntest-data\n*.pyc\n"
  },
  {
    "path": "README.md",
    "content": "# [Try a Free Control Panel](https://fastcp.org)\nI 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:\n\n1. It is entirely paid\n2. You need to give your server access away\n\nTo 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!!\n\n* [Try FastCP now](https://fastcp.org)\n* [View on GitHub](https://github.com/rehmatworks/fastcp)\n\n** If you like it, consider giving it a star. I'll try to update this ServerPilot Let's encrypt automation script too.**\n\n## ServerPilot Let's Encrypt (`rwssl`) v2.x\nThis 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.\n\n![serverpilot-letsencrypt](https://raw.githubusercontent.com/rehmatworks/serverpilot-letsencrypt/master/rwssl.png \"ServerPilto Let's Encrypt\")\n\n## Getting Started\n\nFirst of all, sign in as root user (or with sudo privileges). Now remove the very old script if you are still using it:\n\n```bash\nrm /usr/local/bin/rwssl\n```\n\nAnd then install some needed packages:\n\n```bash\napt-get update  && \\\napt-get -y install python3-pip build-essential libssl-dev libffi-dev python3-dev\n```\n\nUninstall older version if exists:\n```bash\npip3 uninstall -y rwssl\n```\n\nAnd then install the latest version from PyPi:\n\n```bash\npip3 install rwssl==2.0.4\n```\n\nVerify that the installation worked. This should bring up the help menu for **rwssl**:\n\n```bash\nrwssl -h\n```\n\nThe alternate way to install **rwssl** is by cloning the repository:\n\n```bash\ncd ~/ && \\\ngit clone https://github.com/rehmatworks/serverpilot-letsencrypt  && \\\ncd serverpilot-letsencrypt && \\\npip3 install -r requirements.txt && \\\npython3 setup.py install\n```\n\nOnly Python 3.5 and up supported so you shoul install & use rwssl package using pip3 and Python 3.x.\n\n## Available Commands with Examples:\n\nOnce **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.\n\n**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.\n\n| Command | Details | Examples |\n| ------- | --- | -- |\n| getcert | Get letsencrypt cert for an app. | `rwssl getcert --app foo` |\n| getcerts | Get letsencrypt certs for all apps. | `rwssl getcerts` for all users apps or `rwssl getcerts --user john` for john's apps |\n| removecert | Uninstall SSL cert from an app. | `rwssl removecert --app foo` |\n| removecerts | Uninstall SSL certs for all apps. | `rwssl removecerts` for all users apps or `rwssl removecerts --user john` for john's apps |\n| forcessl | Force SSL certificate for an app. | `rwssl forcessl --app foo` |\n| unforcessl | Unforce SSL certificate for an app. | `rwssl unforcessl --app foo` |\n| forceall | Force HTTPs for all apps. | `rwssl forceall` for all users apps or `rwssl forceall --user john` for john's apps |\n| unforceall | Unforce HTTPs for all apps. | `rwssl unforceall` for all users apps or `rwssl unforceall --user john` for john's apps |\n\nYou can use `rwssl -h` command to get to the help page on above commands.\n\n## Uninstall\nTo uninstall rwssl completely, run:\n```bash\npip3 uninstall rwssl\n```\n\nAs a CRON job is added for SSL renewals by rwssl, you can remove the CRON file by running:\n\n```bash\nrm /etc/cron.weekly/rwssl-sslrenewals\n```\n\nMoreoever, 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.\n\nThat's all!\n\n## Changelog\n## [2.0.4] - 2020-07-19\nA minor upgrade that addresses renewal CRON issue and DNS-related bug.\n\n### Fixes\n- Fixed invalid DNS bug\n- Fixed renewal cron file generation\n\n## [2.0.0] - 2020-04-18\nA major upgrade that addresses all reported bugs including SSL renewals.\n\n### Changes\n- Custom path is used to store SSL certificates\n- Certificate is named after app name (Addresses missing cert path issue)\n- Improved vhost file parsing to get app details\n- Dropped support for Python 2.x (Only Python 3.x is supported)\n- Using Let's Encrypt staging server (via dry-run) for domain validation (To address quota issues)\n\n### Added\n- Using Jinja template engine to generate virtual host files from templates\n- Using a custom ACME verification location (To avoid initial verifications & renewal issues)\n- Using latest certbot Python package\n- ACME v2 protocol support\n- Added validation after vhost files are written (and changes are reverted if errors encountered)\n\n## Bugs & Suggestions\nFor security-related issues, please email me at **contact@rehmat.works** and for common bug reports / feature requests, use the issues section.\n"
  },
  {
    "path": "requirements.txt",
    "content": "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\ncertbot==0.39.0\n"
  },
  {
    "path": "rwssl/__init__.py",
    "content": ""
  },
  {
    "path": "rwssl/__main__.py",
    "content": "from rwssl.rwssl import *\nif __name__ == '__main__':\n\tmain()\n"
  },
  {
    "path": "rwssl/rwssl.py",
    "content": "#! /usr/bin/env python\n# -*- coding: utf-8 -*-\nimport argparse\nfrom .utils import ServerPilot\nfrom termcolor import colored\nimport sys\nimport validators\nfrom .tools import *\nimport shutil\nimport subprocess\n\ndef main():\n\n    sp = ServerPilot()\n\n    # Setup cron\n    cronpath = '/etc/cron.weekly/rwssl-sslrenewals'\n    if not os.path.exists(cronpath):\n        croncmd = '%s renew --non-interactive --config-dir /etc/nginx-sp/le-ssls --post-hook \"service nginx-sp reload\"\\n' % shutil.which('certbot')\n        with open(cronpath, 'w') as cronfile:\n            cronfile.writelines(['#!/bin/sh\\n', croncmd])\n        maxexeccmd = \"chmod +x {}\".format(cronpath)\n        FNULL = open(os.devnull, 'w')\n        subprocess.check_call([maxexeccmd], shell=True, stdout=FNULL, stderr=subprocess.STDOUT)\n\n    ap = argparse.ArgumentParser(description='A powerful tool to manage SSLs on servers provisioned using ServerPilot.io.')\n    subparsers = ap.add_subparsers(dest=\"action\")\n\n    # SSL\n    ssl = subparsers.add_parser('getcert', help='Get letsencrypt cert for an app.')\n    ssl.add_argument('--app', dest='app', help='App name for which you want to get an SSL cert.', required=True)\n\n    sslall = subparsers.add_parser('getcerts', help='Get letsencrypt certs for all apps.')\n    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)\n\n    ussl = subparsers.add_parser('removecert', help='Uninstall SSL cert from an app.')\n    ussl.add_argument('--app', dest='app', help='App name from which you want to uninstall the SSL cert.', required=True)\n\n    usslall = subparsers.add_parser('removecerts', help='Uninstall SSL certs for all apps.')\n    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)\n\n    forcessl = subparsers.add_parser('forcessl', help='Force SSL certificate for an app.')\n    forcessl.add_argument('--app', dest='app', help='App name for which you want to force the HTTPS scheme.', required=True)\n\n    unforcessl = subparsers.add_parser('unforcessl', help='Unforce SSL certificate for an app.')\n    unforcessl.add_argument('--app', dest='app', help='App name for which you want to unforce the HTTPS scheme.', required=True)\n\n    forceall = subparsers.add_parser('forceall', help='Force HTTPs for all apps.')\n    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)\n\n    unforceall = subparsers.add_parser('unforceall', help='Unforce HTTPs for all apps.')\n    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)\n\n    args = ap.parse_args()\n\n    if len(sys.argv) <= 1:\n        ap.print_help()\n        sys.exit(0)\n\n    if args.action == 'getcert':\n        if doconfirm('Do you really want to obtain an SSL certificate for the app {}?'.format(args.app)):\n            sp.setapp(args.app)\n            try:\n                print(colored('Activating SSL for app {}...'.format(args.app), 'blue'))\n                sp.getcert()\n            except Exception as e:\n                print(colored(str(e), 'yellow'))\n\n    if args.action == 'getcerts':\n        if args.user:\n            sp.setuser(args.user)\n            confirmmsg = 'Do you really want to activate SSL for all apps owned by {}?'.format(args.user)\n        else:\n            confirmmsg = 'Do you really want to activate SSL for all apps existing on this server?'\n        if doconfirm(confirmmsg):\n            try:\n                apps = sp.findapps()\n                if len(apps) > 0:\n                    for app in apps:\n                        print(colored('Activating SSL for app {}...'.format(app[1]), 'blue'))\n                        sp.app = app[1]\n                        sp.getcert()\n                else:\n                    raise Exception('No apps found!')\n            except Exception as e:\n                print(colored(str(e), 'yellow'))\n\n    if args.action == 'removecert':\n        if doconfirm('Do you really want to uninstall SSL certificate for the app {}?'.format(args.app)):\n            sp.setapp(args.app)\n            try:\n                print(colored('Uninstalling SSL from app {}...'.format(args.app), 'blue'))\n                sp.removecert()\n                print(colored('SSL has been uninstalled from the app {}.'.format(args.app), 'green'))\n            except Exception as e:\n                print(colored(str(e), 'yellow'))\n\n    if args.action == 'removecerts':\n        if args.user:\n            sp.setuser(args.user)\n            confirmmsg = 'Do you really want to uninstall SSL for all apps owned by {}?'.format(args.user)\n        else:\n            confirmmsg = 'Do you really want to uninstall SSL for all apps existing on this server?'\n        if doconfirm(confirmmsg):\n            try:\n                apps = sp.findapps()\n                if len(apps) > 0:\n                    for app in apps:\n                        print(colored('Removing SSL certificate from app {}...'.format(app[1]), 'blue'))\n                        sp.app = app[1]\n                        sp.removecert()\n                        print(colored('SSL has been uninstalled from app {}.'.format(app[1]), 'green'))\n                else:\n                    raise Exception('No apps found!')\n            except Exception as e:\n                print(colored(str(e), 'yellow'))\n\n    if args.action == 'forcessl':\n        if doconfirm('Do you really want to force HTTPs for the app {}?'.format(args.app)):\n            sp.setapp(args.app)\n            try:\n                sp.forcessl()\n                print(colored('HTTPs has been forced for the app {}.'.format(args.app), 'green'))\n            except Exception as e:\n                print(colored(str(e), 'yellow'))\n\n    if args.action == 'forceall':\n        if args.user:\n            sp.setuser(args.user)\n            confirmmsg = 'Do you really want to force SSL for all apps owned by {}?'.format(args.user)\n        else:\n            confirmmsg = 'Do you really want to force SSL for all apps existing on this server?'\n        if doconfirm(confirmmsg):\n            try:\n                apps = sp.findapps()\n                if len(apps) > 0:\n                    for app in apps:\n                        print(colored('Forcing SSL certificate for app {}...'.format(app[1]), 'blue'))\n                        try:\n                            sp.app = app[1]\n                            sp.forcessl()\n                            print(colored('SSL has been forced for app {}.'.format(app[1]), 'green'))\n                        except Exception as e:\n                            print(colored(str(e), 'yellow'))\n                else:\n                    raise Exception('No apps found!')\n            except Exception as e:\n                print(colored(str(e), 'yellow'))\n\n    if args.action == 'unforcessl':\n        if doconfirm('Do you really want to unforce HTTPs for the app {}?'.format(args.app)):\n            try:\n                sp.setapp(args.app)\n                sp.unforcessl()\n                print(colored('HTTPs has been unforced for the app {}.'.format(args.app), 'green'))\n            except Exception as e:\n                print(colored(str(e), 'yellow'))\n\n    if args.action == 'unforceall':\n        if args.user:\n            sp.setuser(args.user)\n            confirmmsg = 'Do you really want to unforce SSL for all apps owned by {}?'.format(args.user)\n        else:\n            confirmmsg = 'Do you really want to unforce SSL for all apps existing on this server?'\n        if doconfirm(confirmmsg):\n            try:\n                apps = sp.findapps()\n                if len(apps) > 0:\n                    for app in apps:\n                        print(colored('Unforcing SSL certificate for app {}...'.format(app[1]), 'blue'))\n                        try:\n                            sp.app = app[1]\n                            sp.unforcessl()\n                            print(colored('SSL has been unforced for app {}.'.format(app[1]), 'green'))\n                        except Exception as e:\n                            print(colored(str(e), 'yellow'))\n                else:\n                    raise Exception('No apps found!')\n            except Exception as e:\n                print(colored(str(e), 'yellow'))\n"
  },
  {
    "path": "rwssl/templates/acme.tpl",
    "content": "############################################################################### \n# This conf file was auto-generated by rwssl script\n# Author: Rehmat Alam\n# GitHub Repo: (https://github.com/rehmatworks/serverpilot-letsencrypt/)\n# Twitter: https://twitter.com/rehmatworks\n# Email: contact@rehmat.works\n#\n# This file rewrites ACME root in order to prevent issues during ACME challenge\n# verification & later in renewal process. HTACCESS or NGINX rules may interfere\n# with ACME requests and may result in renewal failure. This conf file prevents\n# that from happening.\n###############################################################################\n\nlocation ^~ /.well-known/acme-challenge/ {\n    default_type \"text/plain\";\n    rewrite /.well-known/acme-challenge/(.*) /$1 break;\n    root /var/.rwssl/.well-known/acme-challenge/;\n}"
  },
  {
    "path": "rwssl/templates/nginx-main.tpl",
    "content": "############################################################################### \n# This conf file was auto-generated by rwssl script\n# Author: Rehmat Alam\n# GitHub Repo: (https://github.com/rehmatworks/serverpilot-letsencrypt/)\n# Twitter: https://twitter.com/rehmatworks\n# Email: contact@rehmat.works\n###############################################################################\n\nlocation / {\n    proxy_pass      $backend_protocol://$backend_host:$backend_port;\n}"
  },
  {
    "path": "rwssl/templates/nginx-ssl.tpl",
    "content": "############################################################################### \n# This conf file was auto-generated by rwssl script\n# Author: Rehmat Alam\n# GitHub Repo: (https://github.com/rehmatworks/serverpilot-letsencrypt/)\n# Twitter: https://twitter.com/rehmatworks\n# Email: contact@rehmat.works\n###############################################################################\n\nserver {\n    listen       80;\n    listen       [::]:80;\n    server_name {{ servername }}{% if serveralias %} {{ serveralias }}{% endif %};\n\n    root   /srv/users/{{ username }}/apps/{{ appname }}/public;\n\n    access_log  /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.access.log  main;\n    error_log  /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.error.log;\n\n    proxy_set_header    Host              $host;\n    proxy_set_header    X-Real-IP         $remote_addr;\n    proxy_set_header    X-Forwarded-For   $proxy_add_x_forwarded_for;\n\n    include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.nonssl_conf;\n    include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.conf;\n}\n\nserver {\n    listen       443 ssl http2;\n    listen       [::]:443 ssl http2;\n    server_name {{ servername }}{% if serveralias %} {{ serveralias }}{% endif %};\n\n    ssl_certificate_key      {{ sslpath }}/live/{{appname}}/privkey.pem;\n    ssl_certificate          {{ sslpath }}/live/{{appname}}/fullchain.pem;\n\n    root   /srv/users/{{ username }}/apps/{{ appname }}/public;\n\n    access_log  /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.access_ssl.log  main;\n    error_log  /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.error_ssl.log;\n\n    proxy_set_header    Host              $host;\n    proxy_set_header    X-Real-IP         $remote_addr;\n    proxy_set_header    X-Forwarded-For   $proxy_add_x_forwarded_for;\n    proxy_set_header    X-Forwarded-SSL   on;\n    proxy_set_header    X-Forwarded-Proto $scheme;\n\n    include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.ssl_conf;\n    include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.conf;\n}\n"
  },
  {
    "path": "rwssl/templates/nginx-sslforced.tpl",
    "content": "############################################################################### \n# This conf file was auto-generated by rwssl script\n# Author: Rehmat Alam\n# GitHub Repo: (https://github.com/rehmatworks/serverpilot-letsencrypt/)\n# Twitter: https://twitter.com/rehmatworks\n# Email: contact@rehmat.works\n###############################################################################\n\nserver {\n    listen       80;\n    listen       [::]:80;\n    server_name {{ servername }}{% if serveralias %} {{ serveralias }}{% endif %};\n    location /.well-known {\n        try_files $uri $uri/ =404;\n    }\n    return 301 https://$server_name$request_uri;\n}\n\nserver {\n    listen       443 ssl http2;\n    listen       [::]:443 ssl http2;\n    server_name {{ servername }}{% if serveralias %} {{ serveralias }}{% endif %};\n\n    ssl_certificate_key      {{ sslpath }}/live/{{appname}}/privkey.pem;\n    ssl_certificate          {{ sslpath }}/live/{{appname}}/fullchain.pem;\n\n    root   /srv/users/{{ username }}/apps/{{ appname }}/public;\n\n    access_log  /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.access_ssl.log  main;\n    error_log  /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.error_ssl.log;\n\n    proxy_set_header    Host              $host;\n    proxy_set_header    X-Real-IP         $remote_addr;\n    proxy_set_header    X-Forwarded-For   $proxy_add_x_forwarded_for;\n    proxy_set_header    X-Forwarded-SSL   on;\n    proxy_set_header    X-Forwarded-Proto $scheme;\n\n    include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.ssl_conf;\n    include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.conf;\n}\n"
  },
  {
    "path": "rwssl/templates/nginx.tpl",
    "content": "############################################################################### \n# This conf file was auto-generated by rwssl script\n# Author: Rehmat Alam\n# GitHub Repo: (https://github.com/rehmatworks/serverpilot-letsencrypt/)\n# Twitter: https://twitter.com/rehmatworks\n# Email: contact@rehmat.works\n###############################################################################\n\nserver {\n    listen       80;\n    listen       [::]:80;\n    server_name {{ servername }}{% if serveralias %} {{ serveralias }}{% endif %};\n\n    root   /srv/users/{{ username }}/apps/{{ appname }}/public;\n\n    access_log  /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.access.log  main;\n    error_log  /srv/users/{{ username }}/log/{{ appname }}/{{ appname }}_nginx.error.log;\n\n    proxy_set_header    Host              $host;\n    proxy_set_header    X-Real-IP         $remote_addr;\n    proxy_set_header    X-Forwarded-For   $proxy_add_x_forwarded_for;\n\n    include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.nonssl_conf;\n    include /etc/nginx-sp/vhosts.d/{{ appname }}.d/*.conf;\n}\n"
  },
  {
    "path": "rwssl/tools.py",
    "content": "import subprocess\nimport os\nfrom jinja2 import Environment, BaseLoader\nimport pkgutil\nimport shutil\nimport pwd\nimport configparser\nimport warnings\n\n\ndef parsetpl(tpl, data={}):\n    tplstr = str(pkgutil.get_data('rwssl', 'templates/{}'.format(tpl)).decode('utf-8'))\n    tpl = Environment(loader=BaseLoader).from_string(tplstr)\n    return tpl.render(**data)\n\ndef runcmd(cmd):\n    FNULL = open(os.devnull, 'w')\n    if not \"sudo\" in cmd:\n        cmd = \"sudo {}\".format(cmd)\n    subprocess.check_call([cmd], shell=True, stdout=FNULL, stderr=subprocess.STDOUT)\n\ndef reloadservice(service):\n    runcmd('service {} reload'.format(service))\n\ndef restartservice(service):\n    runcmd('service {} restart'.format(service))\n\ndef rmcontent(path):\n    if os.path.exists(path):\n        if os.path.isdir(path):\n            shutil.rmtree(path)\n        else:\n            os.unlink(path)\n\ndef getsubstr(s, start, end):\n    return (s.split(start))[1].split(end)[0]\n\ndef userexists(username):\n    try:\n        pwd.getpwnam(username)\n        return True\n    except KeyError:\n        return False\n\ndef doconfirm(msg = \"Do you really want to perform this irreversible action\"):\n    answer = \"\"\n    while answer not in [\"y\", \"n\"]:\n        answer = input(\"{} [Y/N] \".format(msg)).lower()\n    return answer == \"y\"\n"
  },
  {
    "path": "rwssl/utils.py",
    "content": "import os\nfrom .tools import *\nfrom termcolor import colored\nimport nginx\nimport json\nimport validators\nfrom getpass import getpass\nimport socket\nimport re\n\n\nclass ServerPilot:\n    def __init__(self, username=False, app=False):\n        self.mainroot = '/'\n        self.usrdataroot = os.path.join(self.mainroot, 'srv', 'users')\n        self.nginxroot = os.path.join(self.mainroot, 'etc', 'nginx-sp')\n        self.sslroot = os.path.join(self.nginxroot, 'le-ssls')\n        self.vhostdir = 'vhosts.d'\n        self.acmeroot = '/var/.rwssl/'\n        self.acmeconf = 'acme.conf'\n        self.username = username\n        self.app = app\n        self.domains = []\n        self.acmetpl = 'acme.tpl'\n\n    def setuser(self, username):\n        self.username = username\n\n    def setapp(self, app):\n        self.app = app\n\n    def setdomains(self, domains):\n        doms = domains.split(',')\n        for dom in doms:\n            if validators.domain(dom) is True:\n                self.domains.append(dom)\n            else:\n                raise Exception('{} is not a valid domain.'.format(dom))\n        if len(self.domains) == 0:\n            raise Exception(\n                'You need to provide at least one valid domain name.')\n\n    def usrhome(self):\n        if not self.username:\n            raise Exception('SSH username has not been provided.')\n        return os.path.join(self.usrdataroot, self.username)\n\n    def appsdir(self):\n        return os.path.join(self.usrhome(), 'apps')\n\n    def appdir(self):\n        if not self.app:\n            raise Exception('App name has not been provided.')\n        return os.path.join(self.appsdir(), self.app)\n\n    def appnginxconf(self):\n        if not self.app:\n            raise Exception('App name has not been provided.')\n        return os.path.join(self.nginxroot, self.vhostdir, '{}.conf'.format(self.app))\n\n    def isvalidapp(self):\n        if self.appdetails():\n            return True\n        return False\n\n    def appdetails(self):\n        conff = os.path.join(self.nginxroot, self.vhostdir,\n                             '{}.conf'.format(self.app))\n        if not os.path.exists(conff):\n            raise Exception('Looks like you  provided a wrong app name.')\n        c = nginx.loadf(conff)\n        if len(c.filter('Server')) == 2:\n            s = c.filter('Server')[1]\n        else:\n            s = c.filter('Server')[0]\n        return {\n            'domains': list(filter(None, re.sub('\\s+', ' ', s.filter('Key', 'server_name')[0].as_dict.get('server_name')).split(' '))),\n            'user': list(filter(None, re.sub('\\s+', ' ', s.filter('Key', 'root')[0].as_dict.get('root')).split('/')))[2]\n        }\n\n    def findapps(self):\n        appsdata = []\n        i = 0\n        if not self.username:\n            os.chdir(self.usrdataroot)\n            users = list(filter(os.path.isdir, os.listdir(os.curdir)))\n            if len(users):\n                for user in users:\n                    self.username = user\n                    appsdir = self.appsdir()\n                    if os.path.exists(appsdir):\n                        os.chdir(appsdir)\n                        apps = list(\n                            filter(os.path.isdir, os.listdir(os.curdir)))\n                        for app in apps:\n                            self.app = app\n                            if self.isvalidapp():\n                                i += 1\n                                info = self.appdetails()\n                                appsdata.append([i, self.app, info.get(\n                                    'user'), ','.join(info.get('domains'))])\n        else:\n            appsdir = self.appsdir()\n            if not os.path.exists(appsdir):\n                raise Exception(\n                    'Looks like you have provided an invalid SSH user.')\n            else:\n                os.chdir(appsdir)\n                apps = list(filter(os.path.isdir, os.listdir(os.curdir)))\n                for app in apps:\n                    self.app = app\n                    if self.isvalidapp():\n                        i += 1\n                        info = self.appdetails()\n                        appsdata.append([i, self.app, info.get(\n                            'user'), ','.join(info.get('domains'))])\n        return appsdata\n\n    def gettpldata(self):\n        if len(self.domains) > 1:\n            serveralias = ''\n        else:\n            serveralias = False\n        servername = ''\n        di = 0\n        for dom in self.domains:\n            di += 1\n            if di == 1:\n                servername = dom\n            else:\n                if di > 2:\n                    serveralias += ' '\n                serveralias += dom\n        return {\n            'appname': self.app,\n            'username': self.username,\n            'servername': servername,\n            'serveralias': serveralias\n        }\n\n\n    def createnginxvhost(self):\n        data = self.gettpldata()\n        nginxmaindata = parsetpl('nginx-main.tpl')\n        with open(os.path.join(self.nginxroot, self.vhostdir, '{}.d'.format(self.app), 'main.conf'), 'w') as nginxmain:\n            nginxmain.write(nginxmaindata)\n        nginxtpldata = parsetpl('nginx.tpl', data=data)\n        with open(self.appnginxconf(), 'w') as nginxconf:\n            nginxconf.write(nginxtpldata)\n\n    def createnginxsslvhost(self):\n        data = self.gettpldata()\n        data.update({'sslpath': self.sslroot})\n        nginxtpldata = parsetpl('nginx-ssl.tpl', data=data)\n        with open(self.appnginxconf(), 'w') as nginxconf:\n            nginxconf.write(nginxtpldata)\n\n    def createnginxsslforcedvhost(self):\n        data = self.gettpldata()\n        data.update({'sslpath': self.sslroot})\n        nginxtpldata = parsetpl('nginx-sslforced.tpl', data=data)\n        with open(self.appnginxconf(), 'w') as nginxconf:\n            nginxconf.write(nginxtpldata)\n\n    def reloadservices(self):\n        restartservice('nginx-sp')\n\n    def search(self, value, data):\n        for conf in data:\n            blocks = conf.get('server')\n            for block in blocks:\n                found = block.get(value)\n                if found:\n                    return found\n        return None\n\n    def getcert(self):\n        if not self.isvalidapp():\n            raise Exception('A valid app name is not provided.')\n\n        if not os.path.exists(self.acmeroot):\n            os.makedirs(self.acmeroot)\n        \n        # Remove old SSL force rule if exists\n        oldnonsslconf = os.path.join(self.nginxroot, self.vhostdir, '{}.d'.format(self.app), 'rwssl.nonssl_conf')\n        if os.path.exists(oldnonsslconf):\n            os.unlink(oldnonsslconf)\n            reloadservice('nginx-sp')\n\n        letpl = os.path.join(self.nginxroot, self.vhostdir,\n                             '{}.d'.format(self.app), self.acmeconf)\n        if not os.path.exists(letpl):\n            tpldata = parsetpl(self.acmetpl, data={'acmeroot': self.acmeroot})\n            with open(letpl, 'w') as letplf:\n                letplf.write(tpldata)\n                reloadservice('nginx-sp')\n\n        with open(self.appnginxconf()) as nginxconf:\n            nginxconfbackup = nginxconf.read()\n        details = self.appdetails()\n        self.setuser(details.get('user'))\n        self.domains = details.get('domains')\n        validdoms = []\n\n        try:\n            for domain in details.get('domains'):\n                cmd = \"certbot certonly --non-interactive --dry-run --webroot -w {} --register-unsafely-without-email --agree-tos -d {}\".format(\n                    self.acmeroot, domain)\n                try:\n                    runcmd(cmd)\n                    validdoms.append(domain)\n                except:\n                    ip = socket.gethostbyname(domain)\n                    if validators.ipv4(ip) or validators.ipv6(ip):\n                        errmsg = 'A possible DNS issue found. {} may be pointing to a wrong IP ({})'.format(domain, ip)\n                    else:\n                        errmsg = 'SSL is not available for {} yet.'.format(\n                            domain)\n                    print(colored(errmsg, 'yellow'))\n        except Exception as e:\n            pass\n\n        if len(validdoms) > 0:\n            domainsstr = ''\n            webroot = self.acmeroot\n            for vd in validdoms:\n                domainsstr += ' -d {}'.format(vd)\n            cmd = \"certbot certonly --non-interactive --agree-tos --register-unsafely-without-email --webroot -w {} --cert-name {} --config-dir {}{}\".format(\n                webroot, self.app, self.sslroot, domainsstr)\n            runcmd(cmd)\n            self.createnginxsslvhost()\n            try:\n                # For backward compatibility, clean old SSL-vhost if exists\n                oldsslconf = os.path.join(\n                    self.nginxroot, self.vhostdir, '{}-ssl.conf'.format(self.app))\n                if os.path.exists(oldsslconf):\n                    os.unlink(oldsslconf)\n                reloadservice('nginx-sp')\n                print(colored('SSL activated for app {} (Domains Secured: {})'.format(\n                    self.app, ' '.join(validdoms)), 'green'))\n            except:\n                try:\n                    restartservice('nginx-sp')\n                except:\n                    with open(self.appnginxconf(), 'w') as restoreconf:\n                        restoreconf.write(nginxconfbackup)\n                    restartservice('nginx-sp')\n                    raise Exception('SSL activation failed!')\n        else:\n            raise Exception('SSL not available for this app yet.')\n\n    def apphasssl(self):\n        return os.path.exists(os.path.join(self.sslroot, 'live', self.app, 'fullchain.pem'))\n\n    def removecert(self):\n        if not self.isvalidapp():\n            raise Exception('A valid app name should be provided.')\n\n        if not self.apphasssl():\n            raise Exception(\n                'The app {} does not have an active SSL certificate.'.format(self.app))\n\n        details = self.appdetails()\n        self.domains = details.get('domains')\n        self.setuser(details.get('user'))\n        cmd = \"certbot --non-interactive revoke --config-dir {} --cert-name {}\".format(\n            self.sslroot, self.app)\n        try:\n            runcmd(cmd)\n            self.createnginxvhost()\n            try:\n                reloadservice('nginx-sp')\n            except:\n                restartservice('nginx-sp')\n\n        except Exception as e:\n            raise Exception(\n                \"SSL certificate cannot be removed: {}\".format(str(e)))\n\n    def forcessl(self):\n        if not self.isvalidapp():\n            raise Exception('A valid app name should be provided.')\n        if not self.apphasssl():\n            raise Exception(\n                'The app {} does not have an active SSL certificate.'.format(self.app))\n        details = self.appdetails()\n        self.setuser(details.get('user'))\n        self.domains = details.get('domains')\n        self.createnginxsslforcedvhost()\n        try:\n            reloadservice('nginx-sp')\n        except:\n            restartservice('nginx-sp')\n\n    def unforcessl(self):\n        if not self.isvalidapp():\n            raise Exception('A valid app name should be provided.')\n        details = self.appdetails()\n        self.setuser(details.get('user'))\n        self.domains = details.get('domains')\n        if self.apphasssl():\n            self.createnginxsslvhost()\n        else:\n            self.createnginxvhost()\n        try:\n            reloadservice('nginx-sp')\n        except:\n            restartservice('nginx-sp')\n"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup\nimport os\nimport subprocess\nfrom setuptools.command.install import install\nimport shutil\nimport sys\n\nclass SetupSslRenewCron(install):\n\tdef run(self):\n\t\tcrondir = '/etc/cron.weekly'\n\t\tcronfile = os.path.join(crondir, 'rwssl-sslrenewals')\n\t\tif not os.path.exists(crondir):\n\t\t\tos.makedirs(crondir)\n\t\ttry:\n\t\t\tcmd = '%s renew --non-interactive --config-dir /etc/nginx-sp/le-ssls --post-hook \"service nginx-sp reload\"\\n' % shutil.which('certbot')\n\t\texcept:\n\t\t\t# which() on shutil module is not available under Python 3.x\n\t\t\tsys.exit('Looks like you are running an older version of Python. Only Python 3.x is supported.')\n\n\t\twith open(cronfile, 'w') as cf:\n\t\t\tcf.writelines(['#!/bin/sh\\n', cmd])\n\t\tmaxexeccmd = \"chmod +x {}\".format(cronfile)\n\t\tFNULL = open(os.devnull, 'w')\n\t\tsubprocess.check_call([maxexeccmd], shell=True, stdout=FNULL, stderr=subprocess.STDOUT)\n\t\tinstall.run(self)\n\nsetup(name='rwssl',\n\tversion='2.0.4',\n\tdescription='A Python package to manage Let\\'s Encrypt SSL on ServerPilot provisioned servers.',\n\tauthor='Rehmat Alam',\n\tauthor_email='contact@rehmat.works',\n\turl='https://github.com/rehmatworks/serverpilot-letsencrypt/',\n\tlicense='MIT',\n\tpython_requires='>3.5.2',\n\tentry_points={\n\t\t'console_scripts': [\n\t\t\t'rwssl = rwssl.rwssl:main'\n\t\t\t],\n\t},\n\tpackages=[\n\t\t'rwssl'\n\t],\n\tinstall_requires=[\n\t\t'python-nginx',\n\t\t'validators',\n\t\t'termcolor',\n\t\t'tabulate',\n\t\t'Jinja2',\n\t\t'certbot'\n\t],\n\tpackage_data={'rwssl': ['templates/*.tpl']},\n\tcmdclass={\n\t\t'install': SetupSslRenewCron\n\t}\n)\n"
  }
]