├── logs └── empty ├── openvpn_admin ├── __init__.py ├── wsgi.py ├── urls.py └── settings.py ├── openvpn_control ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── openvpn.py ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── xplugins │ ├── __init__.py │ └── jshead.py ├── templatetags │ ├── __init__.py │ └── environ_tags.py ├── __init__.py ├── templates │ └── openvpn │ │ ├── js_head_base.html │ │ └── w_buttom.html ├── apps.py ├── urls.py ├── models.py ├── views.py ├── adminx.py ├── static │ └── openvpn │ │ └── config.js └── forms.py ├── images └── vpn.PNG ├── requirements.txt ├── manage.py ├── LICENSE ├── README.md ├── .gitignore └── supervisord.tmpl.conf /logs/empty: -------------------------------------------------------------------------------- 1 | # required dir -------------------------------------------------------------------------------- /openvpn_admin/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /openvpn_control/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /openvpn_control/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /openvpn_control/xplugins/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /openvpn_control/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /openvpn_control/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/vpn.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexsilva/openvpn-admin/HEAD/images/vpn.PNG -------------------------------------------------------------------------------- /openvpn_control/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'openvpn_control.apps.OpenVpnControlConfig' -------------------------------------------------------------------------------- /openvpn_control/templates/openvpn/js_head_base.html: -------------------------------------------------------------------------------- 1 | {% extends js_head_base %} 2 | {% block extrahead %} 3 | {{ block.super }} 4 | {{ js_head_media }} 5 | {% endblock %} -------------------------------------------------------------------------------- /openvpn_control/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class OpenVpnControlConfig(AppConfig): 5 | name = 'openvpn_control' 6 | verbose_name = "VPN configuration" 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | git+https://github.com/alexsilva/django-xadmin.git@master 2 | git+https://github.com/kennethreitz/its.py.git@master 3 | git+https://github.com/madmouser1/ipgetter.git@master 4 | django-supervisor 5 | django-js-reverse 6 | # supervisor 7 | django-environ 8 | django>=1.9.+ 9 | sh 10 | -------------------------------------------------------------------------------- /openvpn_control/templatetags/environ_tags.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | from django import template 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.simple_tag 9 | def get_setting(name, default=None): 10 | """Returns the value of django settings given as name""" 11 | return settings.ENV.str(name, default=default) 12 | 13 | -------------------------------------------------------------------------------- /openvpn_control/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | 4 | from .views import VPNActivateView, VPNStatusView 5 | 6 | 7 | urlpatterns = [ 8 | url("activate/(?P\d+)", VPNActivateView.as_view(), 9 | name='vpn-control-activate'), 10 | 11 | url("status/(?P\d+)", VPNStatusView.as_view(), 12 | name='vpn-control-status') 13 | ] -------------------------------------------------------------------------------- /openvpn_admin/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for openvpn_admin project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "openvpn_admin.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /openvpn_control/templates/openvpn/w_buttom.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 | -------------------------------------------------------------------------------- /openvpn_control/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Vpn(models.Model): 5 | username = models.CharField("username", max_length=255) 6 | password = models.CharField("password", max_length=32) 7 | 8 | def __unicode__(self): 9 | return self.username 10 | 11 | 12 | class Ovpn(models.Model): 13 | file = models.FileField("File (.ovpn)", help_text="configuration file") 14 | 15 | country = models.CharField("Country", max_length=8) 16 | protocol = models.CharField("Protocol", max_length=8) 17 | port = models.IntegerField("Port") 18 | 19 | vpn = models.ForeignKey(Vpn, verbose_name="VPN") 20 | 21 | activated = models.BooleanField('Activated', default=False) 22 | 23 | def __unicode__(self): 24 | return u"{0.file} / {0.vpn}".format(self) 25 | -------------------------------------------------------------------------------- /openvpn_control/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse, JsonResponse 2 | from django.views.generic import View 3 | from django.core.management import call_command 4 | from .models import Ovpn 5 | import ipgetter 6 | 7 | 8 | class VPNActivateView(View): 9 | def get(self, request, pk): 10 | Ovpn.objects.exclude(pk=pk).update(activated=False) 11 | Ovpn.objects.filter(pk=pk).update(activated=True) 12 | # restart with new configurations (root) 13 | call_command("supervisor", **{'ctl-command': ('restart', 'openvpn')}) 14 | return JsonResponse({ 15 | 'status': "success" 16 | }) 17 | 18 | 19 | class VPNStatusView(View): 20 | def get(self, request, pk): 21 | """Returns data about the currently configured vpn.""" 22 | return JsonResponse({ 23 | 'current_ip': ipgetter.myip() 24 | }) 25 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "openvpn_admin.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /openvpn_admin/urls.py: -------------------------------------------------------------------------------- 1 | """openvpn_admin URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url, include 17 | import xadmin 18 | import openvpn_control.urls 19 | import django_js_reverse.views 20 | 21 | urlpatterns = [ 22 | url(r'^xadmin/', xadmin.site.urls), 23 | url(r'^vpn/control/', include(openvpn_control.urls)), 24 | url(r'^jsreverse/$', django_js_reverse.views.urls_js, name='js_reverse'), 25 | ] 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alex Sandro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openvpn-admin 2 | Web interface written in python/django to manage vpn connections. 3 | 4 | 5 | # INSTALL 6 | 7 | ## Not maintained [send pull requests for improvements] 8 | 9 | ``` git clone https://github.com/alexsilva/openvpn-admin.git ``` 10 | 11 | ``` cd openvpn-admin ``` 12 | 13 | ``` sudo apt-get install supervisor ``` 14 | 15 | ``` sudo python -m pip install -r requirements.txt ``` 16 | 17 | ``` python manage.py makemigrations ``` 18 | 19 | ``` python manage.py migrate ``` 20 | 21 | ``` python manage.py createsuperuser (after enter: admin | admin12345)``` 22 | 23 | # EXPORT SUPERVISOR CONFIG `as root` 24 | 25 | ``` python manage.py supervisor getconfig > /etc/supervisor/supervisord.conf ``` 26 | 27 | # SUPERVISOR INITIALIZATION `Supervisor must start as root user` 28 | 29 | ``` sudo service supervisor start ``` 30 | 31 | 32 | # DEFAULTS (django-environ) 33 | 34 | ``` /etc/supervisor/openvpn-admin/settings.env (Location of the environment configuration script) ``` 35 | 36 | --- 37 | 38 | ``` SUPERVISOR_HTTP_SERVER_PORT == 9105 (Supervisor rpc port) ``` 39 | 40 | --- 41 | 42 | ``` DJANGO_RUNSERVER_PORT == 8105 (Port of the local django admin server) ``` 43 | 44 | --- 45 | 46 | ![Admin](https://github.com/alexsilva/openvpn-admin/raw/master/images/vpn.PNG) 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | .idea 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # IPython Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # dotenv 80 | .env 81 | 82 | # virtualenv 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | -------------------------------------------------------------------------------- /openvpn_control/xplugins/jshead.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from django.forms.widgets import Media 3 | from xadmin.views import BaseAdminPlugin, ListAdminView 4 | 5 | 6 | class JSHeadPlugin(BaseAdminPlugin): 7 | """Move o arquivo js para o head do templates 8 | Necessário em casos que o js precisa ser carregado antes 9 | """ 10 | render_js_on_head = [] 11 | 12 | def __init__(self, admin_view): 13 | super(JSHeadPlugin, self).__init__(admin_view) 14 | self._template = self.admin_view.base_template 15 | self._media = Media() 16 | 17 | def _init(self): 18 | """Configurações do plugin depois que foi marcado como ativo""" 19 | self.admin_view.base_template = 'openvpn/js_head_base.html' 20 | 21 | def init_request(self, *args, **kwargs): 22 | if len(self.render_js_on_head) > 0: 23 | self._init() 24 | return True 25 | return False 26 | 27 | def get_context(self, context): 28 | context['js_head_base'] = self._template 29 | context['js_head_media'] = self._media 30 | return context 31 | 32 | def get_media(self, media): 33 | media_js = media._js 34 | to_remove = [] 35 | for js in media._js: 36 | if js in self.render_js_on_head: 37 | self._media.add_js((js,)) 38 | to_remove.append(js) 39 | for js in to_remove: 40 | try: 41 | media_js.remove(js) 42 | except ValueError: 43 | pass 44 | return media 45 | 46 | 47 | def register(site): 48 | site.register_plugin(JSHeadPlugin, ListAdminView) 49 | -------------------------------------------------------------------------------- /openvpn_control/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2017-05-01 23:34 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Ovpn', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('file', models.FileField(help_text=b'configuration file', upload_to=b'', verbose_name=b'File (.ovpn)')), 22 | ('country', models.CharField(max_length=8, verbose_name=b'Country')), 23 | ('protocol', models.CharField(max_length=8, verbose_name=b'Protocol')), 24 | ('port', models.IntegerField(verbose_name=b'Port')), 25 | ('activated', models.BooleanField(default=False, verbose_name=b'Activated')), 26 | ], 27 | ), 28 | migrations.CreateModel( 29 | name='Vpn', 30 | fields=[ 31 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 32 | ('username', models.CharField(max_length=255, verbose_name=b'username')), 33 | ('password', models.CharField(max_length=32, verbose_name=b'password')), 34 | ], 35 | ), 36 | migrations.AddField( 37 | model_name='ovpn', 38 | name='vpn', 39 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openvpn_control.Vpn', verbose_name=b'VPN'), 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /openvpn_control/adminx.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.template.loader import render_to_string 3 | from django.core.urlresolvers import reverse, reverse_lazy 4 | from xadmin import site 5 | from xadmin.views import filter_hook 6 | 7 | from .xplugins import jshead 8 | 9 | from .models import Vpn, Ovpn 10 | from .forms import VPNForm 11 | 12 | 13 | class VPNAdmin(object): 14 | form = VPNForm 15 | # fields = ( 16 | # "username", 17 | # "password", 18 | # "import_zip" 19 | # ) 20 | 21 | 22 | class OVPNAdmin(object): 23 | refresh_times = range(15, 61, 15) 24 | render_js_on_head = [ 25 | reverse_lazy('js_reverse'), 26 | settings.STATIC_URL + "openvpn/config.js" 27 | ] 28 | search_fields = ( 29 | "country", 30 | 'protocol', 31 | 'port' 32 | ) 33 | 34 | list_filter = ( 35 | "country", 36 | 'protocol', 37 | 'port' 38 | ) 39 | 40 | list_display = ( 41 | 'file', 42 | "country", 43 | 'protocol', 44 | 'port', 45 | 'vpn_activate' 46 | ) 47 | 48 | def vpn_activate(self, instance): 49 | """ativate vpn""" 50 | return render_to_string("openvpn/w_buttom.html", context={ 51 | 'instance': instance 52 | }) 53 | 54 | vpn_activate.short_description = """VPN""" 56 | vpn_activate.allow_tags = True 57 | vpn_activate.is_column = False 58 | 59 | @filter_hook 60 | def get_media(self): 61 | media = super(OVPNAdmin, self).get_media() 62 | media.add_js(self.render_js_on_head) 63 | return media 64 | 65 | 66 | site.register(Vpn, VPNAdmin) 67 | site.register(Ovpn, OVPNAdmin) 68 | 69 | # plugins 70 | 71 | jshead.register(site) 72 | -------------------------------------------------------------------------------- /openvpn_control/management/commands/openvpn.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | 5 | import sh 6 | from django.conf import settings 7 | from django.core.management.base import BaseCommand 8 | 9 | from openvpn_control.models import Ovpn 10 | 11 | 12 | class Command(BaseCommand): 13 | help = 'openvpn command control' 14 | vpn_param = "auth-user-pass" 15 | 16 | def add_arguments(self, parser): 17 | # parser.add_argument('--pk', type=int) 18 | pass 19 | 20 | def _kill_old_process(self): 21 | """kill old openvpn process""" 22 | try: 23 | sh.killall("openvpn") 24 | except: 25 | pass 26 | 27 | def handle(self, *args, **options): 28 | """""" 29 | ovpn = Ovpn.objects.filter(activated=True) 30 | if ovpn.exists(): 31 | self._kill_old_process() 32 | ovpn = ovpn[0] 33 | print >> sys.stdout, "Config: {0.path}".format(ovpn.file) 34 | auth_filepath = os.path.join(settings.BASE_DIR, "vpn{0.vpn.pk}.auth.txt".format(ovpn)) 35 | with open(auth_filepath, "w") as auth: 36 | auth.write(ovpn.vpn.username + '\n') 37 | auth.write(ovpn.vpn.password + '\n') 38 | # get file content 39 | with open(ovpn.file.path, "r") as vpn: 40 | vpn_file_content = vpn.readlines() 41 | # change file 42 | for index, line in enumerate(vpn_file_content): 43 | if re.match(self.vpn_param + '.*', line): 44 | vpn_file_content[index] = "{0.vpn_param} {1:s}\n".format(self, auth_filepath) 45 | break 46 | # write new data 47 | with open(ovpn.file.path, "w") as vpn: 48 | vpn.write(''.join(vpn_file_content)) 49 | # vpn activate 50 | sh.openvpn(ovpn.file.path, _out=sys.stdout) 51 | -------------------------------------------------------------------------------- /openvpn_control/static/openvpn/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by alex on 01/05/2017. 3 | */ 4 | 5 | vpn = { 6 | objects: [], 7 | activate_fn: function () { 8 | }, 9 | update_ip_info: function (pk) { 10 | $.ajax(Urls.vpn_control_status(pk), { 11 | success: function (data) { 12 | $("#vpn-ip").html(data.current_ip) 13 | }, 14 | error: function (data) { 15 | console.log(data) 16 | } 17 | }); 18 | }, 19 | button: function (pk) { 20 | var button = $("#vpn-activate-" + pk); 21 | button.click(function (event) { 22 | event.preventDefault(); 23 | var $this = $(this); 24 | $this.button('loading'); 25 | $.ajax(Urls.vpn_control_activate(pk), { 26 | success: function (data) { 27 | console.log(data); 28 | $(".btn-vpn").removeClass("btn-success") 29 | .removeClass("disabled") 30 | .addClass("btn-default") 31 | .text("activate"); 32 | $this.removeClass("btn-default") 33 | .addClass("btn-success") 34 | .addClass("disabled"); 35 | $this.text(data.status); 36 | vpn.update_ip_info(pk); 37 | }, 38 | error: function (data) { 39 | $this.button('reset'); 40 | console.log(data) 41 | } 42 | }); 43 | }); 44 | }, 45 | exec: function () { 46 | var arrayLength = this.objects.length; 47 | for (var i = 0; i < arrayLength; i++) { 48 | this.button(this.objects[i]) 49 | } 50 | vpn.activate_fn(); 51 | } 52 | }; 53 | 54 | 55 | $(document).ready(function () { 56 | vpn.exec(); 57 | }); -------------------------------------------------------------------------------- /openvpn_control/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | import zipfile 3 | import io 4 | import re 5 | 6 | from .models import Ovpn 7 | 8 | 9 | class VPNForm(forms.ModelForm): 10 | 11 | pattern_details = re.compile("(?P.+?)\d*\.nordvpn\.com\.(?P\w+?)(?P\d+)\.ovpn") 12 | 13 | import_zip = forms.FileField( 14 | label="Importe from zip", 15 | help_text="import .ovpn files from zip", 16 | required=False 17 | ) 18 | 19 | def clean_import_zip(self): 20 | import_zip = self.cleaned_data.get('import_zip') 21 | 22 | if import_zip: 23 | if not zipfile.is_zipfile(import_zip.file): 24 | raise forms.ValidationError("Enter a zip file.") 25 | return import_zip 26 | 27 | def do_import(self, instance): 28 | """Import zip files""" 29 | import_zip = self.cleaned_data.get('import_zip') 30 | 31 | if import_zip and instance.pk is not None: 32 | zipf = zipfile.ZipFile(import_zip.file) 33 | 34 | for filename in zipf.namelist(): 35 | stream = zipf.read(filename) 36 | 37 | match = self.pattern_details.match(filename) 38 | if match: 39 | dgroup = match.groupdict() 40 | ovpn = Ovpn(vpn=instance, 41 | country=dgroup['country'], 42 | protocol=dgroup['protocol'], 43 | port=int(dgroup['port'])) 44 | _stream = io.BytesIO(stream) 45 | _stream.size = len(stream) 46 | ovpn.file.save(filename, _stream) 47 | ovpn.save() 48 | else: 49 | print '*' * 25 50 | print filename 51 | 52 | def save(self, commit=True): 53 | instance = super(VPNForm, self).save(commit=commit) 54 | self.do_import(instance) 55 | return instance 56 | -------------------------------------------------------------------------------- /openvpn_admin/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for openvpn_admin project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.9. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.9/ref/settings/ 11 | """ 12 | 13 | import os 14 | import environ 15 | import its 16 | ENV = environ.Env(DEBUG=(bool, False)) 17 | 18 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 19 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 20 | 21 | env_dir = '/etc/supervisor' if its.linux else os.path.expanduser('~') 22 | environ.Env.read_env(os.path.join(env_dir, 'openvpn-admin', 'settings.env')) 23 | 24 | # Quick-start development settings - unsuitable for production 25 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ 26 | 27 | # SECURITY WARNING: keep the secret key used in production secret! 28 | SECRET_KEY = '*rba&tugphtbek%je&fq*1i54h_z3t-du$6x#h29!8$(r$146n' 29 | 30 | # SECURITY WARNING: don't run with debug turned on in production! 31 | DEBUG = True 32 | 33 | ALLOWED_HOSTS = ["*"] 34 | 35 | SUPERVISOR_CONFIG_FILE = os.path.join(BASE_DIR, "supervisord.tmpl.conf") 36 | 37 | MEDIA_ROOT = os.path.join(BASE_DIR, "vpn-files") 38 | MEDIA_URL = "/media/" 39 | 40 | STATIC_ROOT = os.path.join(BASE_DIR, 'vpn-statics') 41 | STATIC_URL = "/static/" 42 | 43 | XADMIN_EXCLUDE_PLUGINS = ["bookmark", "export"] 44 | 45 | XADMIN_TITLE = u"OpenVPN" 46 | XADMIN_FOOTER_TITLE = XADMIN_TITLE 47 | 48 | # Application definition 49 | INSTALLED_APPS = [ 50 | 'django.contrib.auth', 51 | 'django.contrib.contenttypes', 52 | 'django.contrib.sessions', 53 | 'django.contrib.messages', 54 | 'django.contrib.staticfiles', 55 | 'xadmin', 56 | 'crispy_forms', 57 | 'djsupervisor', 58 | 'openvpn_control', 59 | 'django_js_reverse' 60 | ] 61 | 62 | MIDDLEWARE_CLASSES = [ 63 | 'django.middleware.security.SecurityMiddleware', 64 | 'django.contrib.sessions.middleware.SessionMiddleware', 65 | 'django.middleware.common.CommonMiddleware', 66 | 'django.middleware.csrf.CsrfViewMiddleware', 67 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 68 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 69 | 'django.contrib.messages.middleware.MessageMiddleware', 70 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 71 | ] 72 | 73 | ROOT_URLCONF = 'openvpn_admin.urls' 74 | 75 | TEMPLATES = [ 76 | { 77 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 78 | 'DIRS': [os.path.join(BASE_DIR, 'templates')] 79 | , 80 | 'APP_DIRS': True, 81 | 'OPTIONS': { 82 | 'context_processors': [ 83 | 'django.template.context_processors.debug', 84 | 'django.template.context_processors.request', 85 | 'django.contrib.auth.context_processors.auth', 86 | 'django.contrib.messages.context_processors.messages', 87 | ], 88 | }, 89 | }, 90 | ] 91 | 92 | WSGI_APPLICATION = 'openvpn_admin.wsgi.application' 93 | 94 | 95 | # Database 96 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 97 | 98 | DATABASES = { 99 | 'default': { 100 | 'ENGINE': 'django.db.backends.sqlite3', 101 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 102 | } 103 | } 104 | 105 | 106 | # Password validation 107 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators 108 | 109 | AUTH_PASSWORD_VALIDATORS = [ 110 | { 111 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 112 | }, 113 | { 114 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 115 | }, 116 | { 117 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 118 | }, 119 | { 120 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 121 | }, 122 | ] 123 | 124 | 125 | # Internationalization 126 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 127 | 128 | LANGUAGE_CODE = 'en-us' 129 | 130 | TIME_ZONE = 'UTC' 131 | 132 | USE_I18N = True 133 | 134 | USE_L10N = True 135 | 136 | USE_TZ = True 137 | 138 | 139 | # Static files (CSS, JavaScript, Images) 140 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 141 | 142 | STATIC_URL = '/static/' 143 | -------------------------------------------------------------------------------- /supervisord.tmpl.conf: -------------------------------------------------------------------------------- 1 | {% load environ_tags %} 2 | # https://github.com/Supervisor/initscripts 3 | ; Sample supervisor config file. 4 | ; 5 | ; For more information on the config file, please see: 6 | ; http://supervisord.org/configuration.html 7 | ; 8 | ; Notes: 9 | ; - Shell expansion ("~" or "$HOME") is not supported. Environment 10 | ; variables can be expanded using this syntax: "%(ENV_HOME)s". 11 | ; - Quotes around values are not supported, except in the case of 12 | ; the environment= options as shown below. 13 | ; - Comments must have a leading space: "a=b ;comment" not "a=b;comment". 14 | ; - Command will be truncated if it looks like a config file comment, e.g. 15 | ; "command=bash -c 'foo ; bar'" will truncate to "command=bash -c 'foo ". 16 | 17 | ;[unix_http_server] 18 | ;file=/tmp/supervisor.sock ; the path to the socket file 19 | ;chmod=0700 ; socket file mode (default 0700) 20 | ; socket file uid:gid owner 21 | ;chown={% get_setting "SUPERVISOR_UNIX_SOCKET_FILE_USER" "nobody" %}:{% get_setting "SUPERVISOR_UNIX_SOCKET_FILE_GROUP" "nogroup" %} 22 | ;username=user ; default is no username (open server) 23 | ;password=123 ; default is no password (open server) 24 | 25 | [inet_http_server] ; inet (TCP) server disabled by default 26 | ; ip_address:port specifier, *:port for all iface 27 | port=127.0.0.1:{% get_setting "SUPERVISOR_HTTP_SERVER_PORT" "9105" %} 28 | ;username=user ; default is no username (open server) 29 | ;password=123 ; default is no password (open server) 30 | 31 | [supervisord] 32 | logfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log 33 | logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB 34 | logfile_backups=10 ; # of main logfile backups; 0 means none, default 10 35 | loglevel=info ; log level; default info; others: debug,warn,trace 36 | pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid 37 | nodaemon=false ; start in foreground if true; default false 38 | minfds=1024 ; min. avail startup file descriptors; default 1024 39 | minprocs=200 ; min. avail process descriptors;default 200 40 | ;umask=022 ; process file creation umask; default 022 41 | ;user=chrism ; default is current user, required if root 42 | ;identifier=supervisor ; supervisord identifier, default is 'supervisor' 43 | ;directory=/tmp ; default is not to cd during start 44 | ;nocleanup=true ; don't clean up tempfiles at start; default false 45 | ;childlogdir=/tmp ; 'AUTO' child log dir, default $TEMP 46 | ;environment=KEY="value" ; key value pairs to add to environment 47 | ;strip_ansi=false ; strip ansi escape codes in logs; def. false 48 | 49 | ; The rpcinterface:supervisor section must remain in the config file for 50 | ; RPC (supervisorctl/web interface) to work. Additional interfaces may be 51 | ; added by defining them in separate [rpcinterface:x] sections. 52 | 53 | [rpcinterface:supervisor] 54 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 55 | 56 | ; The supervisorctl section configures how supervisorctl will connect to 57 | ; supervisord. configure it match the settings in either the unix_http_server 58 | ; or inet_http_server section. 59 | 60 | [supervisorctl] 61 | ;serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket 62 | ; use an http:// url to specify an inet socket 63 | serverurl=http://127.0.0.1:{% get_setting "SUPERVISOR_HTTP_SERVER_PORT" "9105" %} 64 | ;username=chris ; should be same as in [*_http_server] if set 65 | ;password=123 ; should be same as in [*_http_server] if set 66 | ;prompt=mysupervisor ; cmd line prompt (default "supervisor") 67 | ;history_file=~/.sc_history ; use readline history if available 68 | 69 | [program:openvpn] 70 | command={{ PYTHON }} {{ PROJECT_DIR }}/manage.py openvpn 71 | redirect_stderr=true 72 | stdout_logfile={{ PROJECT_DIR }}/logs/openvpn.log 73 | stdout_logfile_backups=3 74 | stopasgroup=true 75 | 76 | [program:djangowebserver] 77 | command={{ PYTHON }} {{ PROJECT_DIR }}/manage.py runserver 0.0.0.0:{% get_setting "DJANGO_RUNSERVER_PORT" "8105" %} 78 | redirect_stderr=true 79 | stdout_logfile={{ PROJECT_DIR }}/logs/djangowebserver.log 80 | stdout_logfile_backups=3 81 | stopasgroup=true 82 | 83 | ; The sample program section below shows all possible program subsection values. 84 | ; Create one or more 'real' program: sections to be able to control them under 85 | ; supervisor. 86 | 87 | ;[program:theprogramname] 88 | ;command=/bin/cat ; the program (relative uses PATH, can take args) 89 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s) 90 | ;numprocs=1 ; number of processes copies to start (def 1) 91 | ;directory=/tmp ; directory to cwd to before exec (def no cwd) 92 | ;umask=022 ; umask for process (default None) 93 | ;priority=999 ; the relative start priority (default 999) 94 | ;autostart=true ; start at supervisord start (default: true) 95 | ;startsecs=1 ; # of secs prog must stay up to be running (def. 1) 96 | ;startretries=3 ; max # of serial start failures when starting (default 3) 97 | ;autorestart=unexpected ; when to restart if exited after running (def: unexpected) 98 | ;exitcodes=0,2 ; 'expected' exit codes used with autorestart (default 0,2) 99 | ;stopsignal=QUIT ; signal used to kill process (default TERM) 100 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) 101 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false) 102 | ;killasgroup=false ; SIGKILL the UNIX process group (def false) 103 | ;user=chrism ; setuid to this UNIX account to run the program 104 | ;redirect_stderr=true ; redirect proc stderr to stdout (default false) 105 | ;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO 106 | ;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 107 | ;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10) 108 | ;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 109 | ;stdout_events_enabled=false ; emit events on stdout writes (default false) 110 | ;stdout_syslog=false ; send stdout to syslog with process name (default false) 111 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO 112 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 113 | ;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10) 114 | ;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 115 | ;stderr_events_enabled=false ; emit events on stderr writes (default false) 116 | ;stderr_syslog=false ; send stderr to syslog with process name (default false) 117 | ;environment=A="1",B="2" ; process environment additions (def no adds) 118 | ;serverurl=AUTO ; override serverurl computation (childutils) 119 | 120 | ; The sample eventlistener section below shows all possible eventlistener 121 | ; subsection values. Create one or more 'real' eventlistener: sections to be 122 | ; able to handle event notifications sent by supervisord. 123 | 124 | ;[eventlistener:theeventlistenername] 125 | ;command=/bin/eventlistener ; the program (relative uses PATH, can take args) 126 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s) 127 | ;numprocs=1 ; number of processes copies to start (def 1) 128 | ;events=EVENT ; event notif. types to subscribe to (req'd) 129 | ;buffer_size=10 ; event buffer queue size (default 10) 130 | ;directory=/tmp ; directory to cwd to before exec (def no cwd) 131 | ;umask=022 ; umask for process (default None) 132 | ;priority=-1 ; the relative start priority (default -1) 133 | ;autostart=true ; start at supervisord start (default: true) 134 | ;startsecs=1 ; # of secs prog must stay up to be running (def. 1) 135 | ;startretries=3 ; max # of serial start failures when starting (default 3) 136 | ;autorestart=unexpected ; autorestart if exited after running (def: unexpected) 137 | ;exitcodes=0,2 ; 'expected' exit codes used with autorestart (default 0,2) 138 | ;stopsignal=QUIT ; signal used to kill process (default TERM) 139 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) 140 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false) 141 | ;killasgroup=false ; SIGKILL the UNIX process group (def false) 142 | ;user=chrism ; setuid to this UNIX account to run the program 143 | ;redirect_stderr=false ; redirect_stderr=true is not allowed for eventlisteners 144 | ;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO 145 | ;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 146 | ;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10) 147 | ;stdout_events_enabled=false ; emit events on stdout writes (default false) 148 | ;stdout_syslog=false ; send stdout to syslog with process name (default false) 149 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO 150 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 151 | ;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10) 152 | ;stderr_events_enabled=false ; emit events on stderr writes (default false) 153 | ;stderr_syslog=false ; send stderr to syslog with process name (default false) 154 | ;environment=A="1",B="2" ; process environment additions 155 | ;serverurl=AUTO ; override serverurl computation (childutils) 156 | 157 | ; The sample group section below shows all possible group values. Create one 158 | ; or more 'real' group: sections to create "heterogeneous" process groups. 159 | 160 | ;[group:thegroupname] 161 | ;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions 162 | ;priority=999 ; the relative start priority (default 999) 163 | 164 | ; The [include] section can just contain the "files" setting. This 165 | ; setting can list multiple files (separated by whitespace or 166 | ; newlines). It can also contain wildcards. The filenames are 167 | ; interpreted as relative to this file. Included files *cannot* 168 | ; include files themselves. 169 | 170 | ;[include] 171 | ;files = relative/directory/*.ini --------------------------------------------------------------------------------