From 965e36af1fe29c60a01664745e051fe84cd8bcc0 Mon Sep 17 00:00:00 2001 From: Ryan Prior Date: Wed, 27 May 2020 22:37:14 -0500 Subject: [PATCH 1/4] Adds instructions for installing, uninstalling, updating using Guix --- README.md | 7 +++++++ USAGE.md | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/README.md b/README.md index 8d48092..cf49b74 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,12 @@ For more detailed information on installing, updating and uninstalling, please v For the following Linux distribution(s), install the official `protonvpn-cli` package: +#### Guix + +```sh +guix install protonvpn-cli +``` + #### Fedora ```sh @@ -68,6 +74,7 @@ Depending on your distribution, run the appropriate following command to install |Ubuntu/Linux Mint/Debian and derivatives | `sudo apt install -y openvpn dialog python3-pip python3-setuptools`| |OpenSUSE/SLES | `sudo zypper in -y openvpn dialog python3-pip python3-setuptools` | |Arch Linux/Manjaro | `sudo pacman -S openvpn dialog python-pip python-setuptools` | +|Guix | `guix environment protonvpn-cli` | #### Installing ProtonVPN-CLI diff --git a/USAGE.md b/USAGE.md index d1e8733..6318acb 100644 --- a/USAGE.md +++ b/USAGE.md @@ -8,6 +8,7 @@ This document provides an extensive guide on how to install and use ProtonVPN-CL - [Table of Contents](#table-of-contents) - [Installation & Updating](#installation--updating) - [Installing from distribution repositories](#installing-from-distribution-repositories) + - [Guix](#guix) - [Fedora](#fedora) - [CentOS & RHEL](#centos--rhel) - [Installing from PyPI](#installing-from-pypi) @@ -43,6 +44,12 @@ This document provides an extensive guide on how to install and use ProtonVPN-CL For the following Linux distribution(s), install the official `protonvpn-cli` package: +#### Guix + +```sh +guix install protonvpn-cli +``` + #### Fedora ```sh @@ -85,6 +92,7 @@ Depending on your distribution, run the appropriate following command to install |Ubuntu/Linux Mint/Debian and derivatives | `sudo apt install -y openvpn dialog python3-pip python3-setuptools`| |OpenSUSE/SLES | `sudo zypper in -y openvpn dialog python3-pip python3-setuptools` | |Arch Linux/Manjaro | `sudo pacman -S openvpn dialog python-pip python-setuptools` | +|Guix | `guix environment protonvpn-cli` | #### Installing ProtonVPN-CLI From 494ccd9aa0ec3aca529b582b25a732242c91f542 Mon Sep 17 00:00:00 2001 From: Alexandru Cheltuitor Date: Sat, 4 Jul 2020 11:27:59 +0100 Subject: [PATCH 2/4] Migrated from Docopt to ArgParse (#204) * Ported CLI to argparse, based on OO programming paradigm * Added descriptive comments * Updated usage string * Added logging * Updated exmaples * Removed function based CLI code * Removed docopt dependency * Added usage constant * Added back the PVPN_WAIT environment variable * Addressed Flake8 issues * Examples are now inline with the CLI * Removed unncesessary comment * Cleaned up code and improved readability * Removed dependencies still they are no longer needed * Updated inline command * Allow uppercase protocol with -p * Allign help message * Return missing -p to example Co-authored-by: Alexandru Cheltuitor <31934100+calexandru2018@users.noreply.github.com> --- protonvpn_cli/cli.py | 203 +++++++++++++++++++++++-------------- protonvpn_cli/constants.py | 46 +++++++++ requirements.txt | 1 - setup.py | 1 - 4 files changed, 175 insertions(+), 76 deletions(-) diff --git a/protonvpn_cli/cli.py b/protonvpn_cli/cli.py index 786bc14..c2e37cd 100644 --- a/protonvpn_cli/cli.py +++ b/protonvpn_cli/cli.py @@ -1,48 +1,3 @@ -""" -A CLI for ProtonVPN. - -Usage: - protonvpn init - protonvpn (c | connect) [] [-p ] - protonvpn (c | connect) [-f | --fastest] [-p ] - protonvpn (c | connect) [--cc ] [-p ] - protonvpn (c | connect) [--sc] [-p ] - protonvpn (c | connect) [--p2p] [-p ] - protonvpn (c | connect) [--tor] [-p ] - protonvpn (c | connect) [-r | --random] [-p ] - protonvpn (r | reconnect) - protonvpn (d | disconnect) - protonvpn (s | status) - protonvpn configure - protonvpn refresh - protonvpn examples - protonvpn (-h | --help) - protonvpn (-v | --version) - -Options: - -f, --fastest Select the fastest ProtonVPN server. - -r, --random Select a random ProtonVPN server. - --cc CODE Determine the country for fastest connect. - --sc Connect to the fastest Secure-Core server. - --p2p Connect to the fastest torrent server. - --tor Connect to the fastest Tor server. - -p PROTOCOL Determine the protocol (UDP or TCP). - -h, --help Show this help message. - -v, --version Display version. - -Commands: - init Initialize a ProtonVPN profile. - c, connect Connect to a ProtonVPN server. - r, reconnect Reconnect to the last server. - d, disconnect Disconnect the current session. - s, status Show connection status. - configure Change ProtonVPN-CLI configuration. - refresh Refresh OpenVPN configuration and server data. - examples Print some example commands. - -Arguments: - Servername (CH#4, CH-US-1, HK5-Tor). -""" # Standard Libraries import sys import os @@ -51,8 +6,7 @@ import getpass import shutil import time -# External Libraries -from docopt import docopt +import argparse # protonvpn-cli Functions from . import connection from .logger import logger @@ -63,7 +17,7 @@ ) # Constants from .constants import ( - CONFIG_DIR, CONFIG_FILE, PASSFILE, USER, VERSION, SPLIT_TUNNEL_FILE + CONFIG_DIR, CONFIG_FILE, PASSFILE, USER, VERSION, SPLIT_TUNNEL_FILE, USAGE ) @@ -88,13 +42,62 @@ def cli(): logger.debug("USER: {0}".format(USER)) logger.debug("CONFIG_DIR: {0}".format(CONFIG_DIR)) - args = docopt(__doc__, version="ProtonVPN-CLI v{0}".format(VERSION)) - logger.debug("Arguments\n{0}".format(str(args).replace("\n", ""))) + ProtonVPNCLI() + + +class ProtonVPNCLI(): + server_features_dict = dict( + p2p=4, + sc=1, + tor=2 + ) + + def __init__(self): + parser = argparse.ArgumentParser( + prog="protonvpn", + add_help=False + ) + + parser.add_argument("command", nargs="?") + parser.add_argument("-v", "--version", required=False, action="store_true") + parser.add_argument("-h", "--help", required=False, action="store_true") + + args = parser.parse_args(sys.argv[1:2]) + + logger.debug("Main argument\n{0}".format(args)) + + if args.version: + print("\nProtonVPN CLI v.{}".format(VERSION)) + parser.exit(1) + elif not args.command or not hasattr(self, args.command) or args.help: + print(USAGE) + parser.exit(1) + + getattr(self, args.command)() + + def init(self): + """CLI command that intialiazes ProtonVPN profile""" + parser = argparse.ArgumentParser(description="Initialize ProtonVPN profile", prog="protonvpn init") + parser.add_argument( + "-i", "--inline", nargs=3, required=False, + help="Inline intialize profile. (username password protocol)", metavar="" + ) + + args = parser.parse_args(sys.argv[2:]) + logger.debug("Sub-arguments\n{0}".format(args)) + + if args.inline: + print("Please intialize without '-i/--inline' as it is not fully supported yet.") + sys.exit(1) - # Parse arguments - if args.get("init"): init_cli() - elif args.get("c") or args.get("connect"): + + def c(self): + """Short CLI command for connecting to the VPN""" + self.connect() + + def connect(self): + """Full CLI command for connecting to the VPN""" check_root() check_init() @@ -106,45 +109,97 @@ def cli(): if int(os.environ.get("PVPN_WAIT", 0)) > 0: wait_for_network(int(os.environ["PVPN_WAIT"])) - protocol = args.get("-p") - if protocol is not None and protocol.lower().strip() in ["tcp", "udp"]: + parser = argparse.ArgumentParser(description="Connect to ProtonVPN", prog="protonvpn c") + group = parser.add_mutually_exclusive_group() + group.add_argument("servername", nargs="?", help="Servername (CH#4, CH-US-1, HK5-Tor).", metavar="") + group.add_argument("-f", "--fastest", help="Connect to the fastest ProtonVPN server.", action="store_true") + group.add_argument("-r", "--random", help="Connect to a random ProtonVPN server.", action="store_true") + group.add_argument("--cc", help="Connect to the specified country code (SE, PT, BR, AR).", metavar="") + group.add_argument("--sc", help="Connect to the fastest Secure-Core server.", action="store_true") + group.add_argument("--p2p", help="Connect to the fastest torrent server.", action="store_true") + group.add_argument("--tor", help="Connect to the fastest Tor server.", action="store_true") + parser.add_argument( + "-p", "--protocol", help="Connect via specified protocol.", + choices=["udp", "tcp"], metavar="", type=str.lower + ) + + args = parser.parse_args(sys.argv[2:]) + logger.debug("Sub-arguments:\n{0}".format(args)) + + protocol = args.protocol + if protocol and protocol.lower().strip() in ["tcp", "udp"]: protocol = protocol.lower().strip() - if args.get("--random"): + if args.random: connection.random_c(protocol) - elif args.get("--fastest"): + elif args.fastest: connection.fastest(protocol) - elif args.get(""): - connection.direct(args.get(""), protocol) - elif args.get("--cc") is not None: - connection.country_f(args.get("--cc"), protocol) - # Features: 1: Secure-Core, 2: Tor, 4: P2P - elif args.get("--p2p"): - connection.feature_f(4, protocol) - elif args.get("--sc"): - connection.feature_f(1, protocol) - elif args.get("--tor"): - connection.feature_f(2, protocol) + elif args.servername: + connection.direct(args.servername, protocol) + elif args.cc: + connection.country_f(args.cc, protocol) + elif args.p2p: + connection.feature_f(self.server_features_dict.get("p2p", None), protocol) + elif args.sc: + connection.feature_f(self.server_features_dict.get("sc", None), protocol) + elif args.tor: + connection.feature_f(self.server_features_dict.get("tor", None), protocol) else: connection.dialog() - elif args.get("r") or args.get("reconnect"): + + def r(self): + """Short CLI command to reconnect to the last connected VPN Server""" + self.reconnect() + + def reconnect(self): + """Full CLI command to reconnect to the last connected VPN Server""" check_root() check_init() connection.reconnect() - elif args.get("d") or args.get("disconnect"): + + def d(self): + """Short CLI command to disconnect the VPN if a connection is present""" + self.disconnect() + + def disconnect(self): + """Full CLI command to disconnect the VPN if a connection is present""" check_root() check_init() connection.disconnect() - elif args.get("s") or args.get("status"): + + def s(self): + """Short CLI command to display the current VPN status""" + self.status() + + def status(self): + """Full CLI command to display the current VPN status""" connection.status() - elif args.get("configure"): + + def cf(self): + """Short CLI command to change single configuration values""" + self.configure() + + def configure(self): + """Full CLI command to change single configuration values""" check_root() check_init() configure_cli() - elif args.get("refresh"): + + def rf(self): + """Short CLI command to refresh server list""" + self.refresh() + + def refresh(self): + """Full CLI command to refresh server list""" check_init() pull_server_data(force=True) - elif args.get("examples"): + + def ex(self): + """Short CLI command to display usage examples""" + self.examples() + + def examples(self): + """Full CLI command to display usage examples""" print_examples() diff --git a/protonvpn_cli/constants.py b/protonvpn_cli/constants.py index 224ddb4..d5aff0c 100644 --- a/protonvpn_cli/constants.py +++ b/protonvpn_cli/constants.py @@ -18,3 +18,49 @@ OVPN_FILE = os.path.join(CONFIG_DIR, "connect.ovpn") PASSFILE = os.path.join(CONFIG_DIR, "pvpnpass") VERSION = "2.2.4" + +USAGE = """ +ProtonVPN CLI + +Usage: + protonvpn init + protonvpn (c | connect) [] [-p ] + protonvpn (c | connect) [-f | --fastest] [-p ] + protonvpn (c | connect) [--cc ] [-p ] + protonvpn (c | connect) [--sc] [-p ] + protonvpn (c | connect) [--p2p] [-p ] + protonvpn (c | connect) [--tor] [-p ] + protonvpn (c | connect) [-r | --random] [-p ] + protonvpn (r | reconnect) + protonvpn (d | disconnect) + protonvpn (s | status) + protonvpn (cf | configure) + protonvpn (rf | refresh) + protonvpn (ex | examples) + protonvpn (-h | --help) + protonvpn (-v | --version) + +Options: + -f, --fastest Select the fastest ProtonVPN server. + -r, --random Select a random ProtonVPN server. + --cc CODE Determine the country for fastest connect. + --sc Connect to the fastest Secure-Core server. + --p2p Connect to the fastest torrent server. + --tor Connect to the fastest Tor server. + -p PROTOCOL Determine the protocol (UDP or TCP). + -h, --help Show this help message. + -v, --version Display version. + +Commands: + init Initialize a ProtonVPN profile. + c, connect Connect to a ProtonVPN server. + r, reconnect Reconnect to the last server. + d, disconnect Disconnect the current session. + s, status Show connection status. + cf, configure Change ProtonVPN-CLI configuration. + rf, refresh Refresh OpenVPN configuration and server data. + ex, examples Print some example commands. + +Arguments: + Servername (CH#4, CH-US-1, HK5-Tor). +""" diff --git a/requirements.txt b/requirements.txt index 0ca4cd0..4553af2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ # Call with pip install -r requirements.txt -docopt requests pythondialog jinja2 \ No newline at end of file diff --git a/setup.py b/setup.py index d99700c..c533a9f 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,6 @@ }, install_requires=[ "requests", - "docopt", "pythondialog", "jinja2", ], From d209523b23d1ddb75563c77826611a8ce28d6311 Mon Sep 17 00:00:00 2001 From: mazhead Date: Mon, 9 Nov 2020 07:34:39 +0100 Subject: [PATCH 3/4] OpenVPN custom config Adding custom config option and a example how to run systemd with restart possibility. --- USAGE.md | 20 ++++-- protonvpn_cli/cli.py | 76 +++++++++++++++++++-- protonvpn_cli/constants.py | 1 + protonvpn_cli/templates/openvpn_template.j2 | 9 +++ protonvpn_cli/utils.py | 19 +++++- 5 files changed, 116 insertions(+), 9 deletions(-) diff --git a/USAGE.md b/USAGE.md index 6318acb..897fbc7 100644 --- a/USAGE.md +++ b/USAGE.md @@ -459,15 +459,22 @@ This lets you use ProtonVPN-CLI by simply typing `protonvpn` without sudo or jus Systemd is the current init system of most major Linux distributions. This guide shows you how to use systemd to automatically connect to a ProtonVPN Server when you boot up your system. -1. Find the location of the executable with `sudo which protonvpn` +1. Update the OpenVPN configuration `protonvpn configure` and add the following: + ``` + writepid /var/run/proton.pid + ``` + + Adjust the path to your liking / system specs. + +2. Find the location of the executable with `sudo which protonvpn` ![which-protonvpn](resources/images/usage-which-protonvpn.png) -2. Create the unit file in `/etc/systemd/system` +3. Create the unit file in `/etc/systemd/system` `sudo nano /etc/systemd/system/protonvpn-autoconnect.service` -3. Add the following contents to this file +4. Add the following contents to this file ``` [Unit] @@ -477,9 +484,12 @@ Systemd is the current init system of most major Linux distributions. This guide [Service] Type=forking ExecStart=/usr/local/bin/protonvpn connect -f + ExecStop=/usr/local/bin/protonvpn d Environment=PVPN_WAIT=300 Environment=PVPN_DEBUG=1 Environment=SUDO_USER=user + PIDFile=/var/run/proton.pid + Restart=always [Install] WantedBy=multi-user.target @@ -489,10 +499,12 @@ Systemd is the current init system of most major Linux distributions. This guide `PVPN_WAIT=300` means that ProtonVPN-CLI will check for 300 Seconds if the internet connection is working before timing out. Adjust this value as you prefer. - Also replace the path to the `protonvpn` executable in the `ExecStart=` line with the output of Step 1. + Also replace the path to the `protonvpn` executable in the `ExecStart=` line with the output of Step 2. If you want another connect command than fastest as used in this example, just replace `-f` with what you personally prefer. + Make sure that PIDFile path is same as from step 1. + 4. Reload the systemd configuration `sudo systemctl daemon-reload`. diff --git a/protonvpn_cli/cli.py b/protonvpn_cli/cli.py index c2e37cd..6416817 100644 --- a/protonvpn_cli/cli.py +++ b/protonvpn_cli/cli.py @@ -17,7 +17,7 @@ ) # Constants from .constants import ( - CONFIG_DIR, CONFIG_FILE, PASSFILE, USER, VERSION, SPLIT_TUNNEL_FILE, USAGE + CONFIG_DIR, CONFIG_FILE, PASSFILE, USER, VERSION, SPLIT_TUNNEL_FILE, OVPN_CUSTOM_CONF_FILE, USAGE ) @@ -216,6 +216,7 @@ def init_config_file(): "initialized": "0", "dns_leak_protection": "1", "custom_dns": "None", + "custom_openvpn_conf": "0", "check_update_interval": "3", "api_domain": "https://api.protonvpn.ch", } @@ -223,7 +224,6 @@ def init_config_file(): "last_api_pull": "0", "last_update_check": str(int(time.time())), } - with open(CONFIG_FILE, "w") as f: config.write(f) change_file_owner(CONFIG_FILE) @@ -371,7 +371,8 @@ def configure_cli(): "4) DNS Management\n" "5) Kill Switch\n" "6) Split Tunneling\n" - "7) Purge Configuration\n" + "7) Advanced OpenVPN config\n" + "8) Purge Configuration\n" ) user_choice = input( @@ -397,8 +398,11 @@ def configure_cli(): elif user_choice == "6": set_split_tunnel() break - # Make sure this is always the last option elif user_choice == "7": + set_ovpn_custom_config() + break + # Make sure this is always the last option + elif user_choice == "8": purge_configuration() break elif user_choice == "": @@ -710,3 +714,67 @@ def set_split_tunnel(): print() print("Split tunneling configuration updated.") + +def set_ovpn_custom_config(): + """Add custom OpenVPN config""" + + print() + print( + "For advanced users only.\n" + "This option enables you to ADD additional OpenVPN config options.\n" + ) + print() + + if os.path.isfile(OVPN_CUSTOM_CONF_FILE) and os.stat(OVPN_CUSTOM_CONF_FILE) != 0: + print( + "By editing the current config will be overwritten!\n" + "Current custom OpenVPN configuration:\n" + ) + + with open(OVPN_CUSTOM_CONF_FILE, 'r') as f: + print(f.read()) + + user_choice = input("Enable custom OpenVPN configuration? [y/N]: ") + + if user_choice.strip().lower() == "y": + print() + print( + "Enter custom config below.\n" + "Enter each option on a new line.\n" + "All lines will be added 'as-is' without validation.\n" + "To finish the configuration hit enter.\n" + ) + set_config_value("USER", "custom_openvpn_conf", 1) + print() + + config_lines = [] + + while True: + line = input() + if line: + config_lines.append(line) + else: + break + + config_text = '\n'.join(config_lines) + + with open(OVPN_CUSTOM_CONF_FILE, "a") as f: + f.write(config_text) + + if os.path.isfile(OVPN_CUSTOM_CONF_FILE): + change_file_owner(OVPN_CUSTOM_CONF_FILE) + else: + logger.debug("No ovpn custom config file existing.") + set_config_value("USER", "custom_openvpn_conf", 0) + + else: + set_config_value("USER", "custom_openvpn_conf", 0) + + if os.path.isfile(OVPN_CUSTOM_CONF_FILE): + clear_config = input("Remove custom OpenVPN configuration? [y/N]: ") + + if clear_config.strip().lower() == "y": + os.remove(OVPN_CUSTOM_CONF_FILE) + + print() + print("Custom OpenVPN configuration updated.") diff --git a/protonvpn_cli/constants.py b/protonvpn_cli/constants.py index d5aff0c..9730f58 100644 --- a/protonvpn_cli/constants.py +++ b/protonvpn_cli/constants.py @@ -16,6 +16,7 @@ SERVER_INFO_FILE = os.path.join(CONFIG_DIR, "serverinfo.json") SPLIT_TUNNEL_FILE = os.path.join(CONFIG_DIR, "split_tunnel.txt") OVPN_FILE = os.path.join(CONFIG_DIR, "connect.ovpn") +OVPN_CUSTOM_CONF_FILE = os.path.join(CONFIG_DIR, "ovpn_custom_conf.txt") PASSFILE = os.path.join(CONFIG_DIR, "pvpnpass") VERSION = "2.2.4" diff --git a/protonvpn_cli/templates/openvpn_template.j2 b/protonvpn_cli/templates/openvpn_template.j2 index 8783efa..c973610 100644 --- a/protonvpn_cli/templates/openvpn_template.j2 +++ b/protonvpn_cli/templates/openvpn_template.j2 @@ -69,6 +69,15 @@ route {{ ip_nm_pair.ip }} {{ ip_nm_pair.nm }} net_gateway {%- endfor %} {%- endif %} +{%- if custom_ovpn_conf %} + +# USER CUSTOM CONFIG +{%- for custom_ovpn_conf_line in custom_ovpn_conf_lines %} +{{ custom_ovpn_conf_line }} +{%- endfor %} +# END OF CUSTOM CONFIG +{%- endif %} + -----BEGIN CERTIFICATE----- MIIFozCCA4ugAwIBAgIBATANBgkqhkiG9w0BAQ0FADBAMQswCQYDVQQGEwJDSDEV diff --git a/protonvpn_cli/utils.py b/protonvpn_cli/utils.py index afa797c..622e0dd 100644 --- a/protonvpn_cli/utils.py +++ b/protonvpn_cli/utils.py @@ -17,7 +17,7 @@ # Constants from .constants import ( USER, CONFIG_FILE, SERVER_INFO_FILE, SPLIT_TUNNEL_FILE, - VERSION, OVPN_FILE + OVPN_CUSTOM_CONF_FILE, VERSION, OVPN_FILE ) @@ -293,6 +293,21 @@ def create_openvpn_config(serverlist, protocol, ports): ip_nm_pairs.append({"ip": ip, "nm": netmask}) + # Custom config + try: + if get_config_value("USER", "custom_openvpn_conf") == "1": + custom_ovpn_conf = True + else: + custom_ovpn_conf = False + except KeyError: + custom_ovpn_conf = False + + custom_ovpn_conf_lines = [] + + if custom_ovpn_conf: + with open(OVPN_CUSTOM_CONF_FILE, 'r') as f: + custom_ovpn_conf_lines = f.readlines() + # IPv6 ipv6_disabled = is_ipv6_disabled() @@ -302,6 +317,8 @@ def create_openvpn_config(serverlist, protocol, ports): "openvpn_ports": ports, "split": split, "ip_nm_pairs": ip_nm_pairs, + "custom_ovpn_conf": custom_ovpn_conf, + "custom_ovpn_conf_lines": custom_ovpn_conf_lines, "ipv6_disabled": ipv6_disabled } From 870aabb8841d56fc000429d1c63afd4265b0fe9d Mon Sep 17 00:00:00 2001 From: mazhead Date: Thu, 12 Nov 2020 11:21:54 +0100 Subject: [PATCH 4/4] flake8 fixup Fix linting --- protonvpn_cli/cli.py | 5 +++-- protonvpn_cli/utils.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/protonvpn_cli/cli.py b/protonvpn_cli/cli.py index 6416817..d2dcd33 100644 --- a/protonvpn_cli/cli.py +++ b/protonvpn_cli/cli.py @@ -715,6 +715,7 @@ def set_split_tunnel(): print() print("Split tunneling configuration updated.") + def set_ovpn_custom_config(): """Add custom OpenVPN config""" @@ -733,7 +734,7 @@ def set_ovpn_custom_config(): with open(OVPN_CUSTOM_CONF_FILE, 'r') as f: print(f.read()) - + user_choice = input("Enable custom OpenVPN configuration? [y/N]: ") if user_choice.strip().lower() == "y": @@ -755,7 +756,7 @@ def set_ovpn_custom_config(): config_lines.append(line) else: break - + config_text = '\n'.join(config_lines) with open(OVPN_CUSTOM_CONF_FILE, "a") as f: diff --git a/protonvpn_cli/utils.py b/protonvpn_cli/utils.py index 622e0dd..714adb8 100644 --- a/protonvpn_cli/utils.py +++ b/protonvpn_cli/utils.py @@ -301,7 +301,7 @@ def create_openvpn_config(serverlist, protocol, ports): custom_ovpn_conf = False except KeyError: custom_ovpn_conf = False - + custom_ovpn_conf_lines = [] if custom_ovpn_conf: