Skip to content

Commit

Permalink
Merge pull request #47 from kiwix/protonvpn-wireguard-config-downloader
Browse files Browse the repository at this point in the history
Protonvpn wireguard config downloader
  • Loading branch information
elfkuzco authored Sep 2, 2024
2 parents 355dbfa + 45b4e65 commit d562d0f
Show file tree
Hide file tree
Showing 11 changed files with 610 additions and 0 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/protonvpn-wireguard-config-downloader-QA.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: ProtonVPN Wireguard Config Downloader QA

on:
pull_request:
push:
paths:
- 'protonvpn-wireguard-config-downloader/**'
branches:
- main

jobs:

check-qa:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version-file: protonvpn-wireguard-config-downloader/pyproject.toml
architecture: x64

- name: Install dependencies (and project)
working-directory: protonvpn-wireguard-config-downloader
run: |
pip install -U pip
pip install -e .[lint,scripts,test,check]
- name: Check black formatting
working-directory: protonvpn-wireguard-config-downloader
run: inv lint-black

- name: Check ruff
working-directory: protonvpn-wireguard-config-downloader
run: inv lint-ruff

- name: Check pyright
working-directory: protonvpn-wireguard-config-downloader
run: inv check-pyright
16 changes: 16 additions & 0 deletions protonvpn-wireguard-config-downloader/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM python:3.11-slim-bookworm
LABEL org.opencontainers.image.source=https://github.com/kiwix/mirrors-qa

# We need gnupg2 for python-gnupg used in Proton libraries to work properly.
RUN apt-get update && apt-get install -y gnupg2

COPY src /src/src

COPY pyproject.toml README.md /src/

RUN pip install --no-cache-dir /src \
&& rm -rf /src

RUN mkdir /data

CMD ["protonvpn-wireguard-configs", "--help"]
23 changes: 23 additions & 0 deletions protonvpn-wireguard-config-downloader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# ProtonVPN Wireguard Configuration Downloader

A tool to automatically download wireguard configuration files for all available VPN servers from ProtonVPN.

**NOTE**
This script is intended to be used in Linux environments only.

## Environment Variables

- `USERNAME`: username for connnecting to ProtonVPN account
- `PASSWORD`
- `WORKDIR`: location to store configuration files. (default: /data)
- `WIREGUARD_PORT`: Port of the wireguard configuration files (default: 51820).This allows to choose the wireguard port for the configuration files rather than leaving it to the ProtonVPN library which often defaults to the first available port in the session object.

## Usage
- Build the image
```sh
docker build -t protonvpn-wireguard-config-downloader .
```
- Download the configuration files
```sh
docker run --rm -e USERNAME=abcd@efg -e PASSWORD=pa55word -v ./proton:/data protonvpn-wireguard-config-downloader protonvpn-wireguard-configs
```
238 changes: 238 additions & 0 deletions protonvpn-wireguard-config-downloader/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
[build-system]
requires = ["hatchling", "hatch-openzim"]
build-backend = "hatchling.build"

[project]
name = "protonvpn_wireguard_config_downloader"
requires-python = ">=3.11,<3.13"
description = "ProtonVPN Wireguard Configuration Files Downloader"
readme = "README.md"
authors = [
{ name = "Kiwix", email = "[email protected]" },
]
keywords = ["protonvpn", "wireguard"]
dependencies = [
"proton-core @ https://github.com/ProtonVPN/python-proton-core/archive/refs/tags/v0.2.0.zip",
"proton-vpn-logger @ https://github.com/ProtonVPN/python-proton-vpn-logger/archive/refs/tags/v0.2.1.zip",
"proton-vpn-api-core @ https://github.com/ProtonVPN/python-proton-vpn-api-core/archive/refs/tags/v0.32.2.zip",
"distro==1.9.0"
]
license = {text = "GPL-3.0-or-later"}
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
]

dynamic = ["version"]

[project.urls]
Homepage = "https://github.com/kiwix/mirrors-qa/protonvpn-wireguard-config-downloader"

[project.optional-dependencies]
scripts = [
"invoke==2.2.0",
]
lint = [
"black==24.4.2",
"ruff==0.5.1",
]
check = [
"pyright==1.1.370",
]
test = [
"pytest==8.2.2",
"coverage==7.5.4",
]
dev = [
"pre-commit==3.7.1",
"debugpy==1.8.2",
"protonvpn_wireguard_config_downloader[scripts]",
"protonvpn_wireguard_config_downloader[lint]",
"protonvpn_wireguard_config_downloader[test]",
"protonvpn_wireguard_config_downloader[check]",
]

[project.scripts]
protonvpn-wireguard-configs = "protonvpn_wireguard_config_downloader.entrypoint:main"

[tool.hatch.version]
path = "src/protonvpn_wireguard_config_downloader/__about__.py"

[tool.hatch.build]
exclude = [
"/.github",
]

[tool.hatch.metadata]
allow-direct-references = true

[tool.hatch.build.targets.wheel]
packages = ["src/protonvpn_wireguard_config_downloader"]

[tool.hatch.envs.default]
features = ["dev"]

[tool.hatch.envs.test]
features = ["scripts", "test"]


[tool.hatch.envs.test.scripts]
run = "inv test --args '{args}'"
run-cov = "inv test-cov --args '{args}'"
report-cov = "inv report-cov"
coverage = "inv coverage --args '{args}'"
html = "inv coverage --html --args '{args}'"

[tool.hatch.envs.lint]
template = "lint"
skip-install = false
features = ["scripts", "lint"]

[tool.hatch.envs.lint.scripts]
black = "inv lint-black --args '{args}'"
ruff = "inv lint-ruff --args '{args}'"
all = "inv lintall --args '{args}'"
fix-black = "inv fix-black --args '{args}'"
fix-ruff = "inv fix-ruff --args '{args}'"
fixall = "inv fixall --args '{args}'"

[tool.hatch.envs.check]
features = ["scripts", "check"]

[tool.hatch.envs.check.scripts]
pyright = "inv check-pyright --args '{args}'"
all = "inv checkall --args '{args}'"

[tool.black]
line-length = 88
target-version = ['py310']

[tool.ruff]
target-version = "py311"
line-length = 88
src = ["src"]

[tool.ruff.lint]
select = [
"A", # flake8-builtins
# "ANN", # flake8-annotations
"ARG", # flake8-unused-arguments
# "ASYNC", # flake8-async
"B", # flake8-bugbear
# "BLE", # flake8-blind-except
"C4", # flake8-comprehensions
"C90", # mccabe
# "COM", # flake8-commas
# "D", # pydocstyle
# "DJ", # flake8-django
"DTZ", # flake8-datetimez
"E", # pycodestyle (default)
"EM", # flake8-errmsg
# "ERA", # eradicate
# "EXE", # flake8-executable
"F", # Pyflakes (default)
# "FA", # flake8-future-annotations
"FBT", # flake8-boolean-trap
# "FLY", # flynt
# "G", # flake8-logging-format
"I", # isort
"ICN", # flake8-import-conventions
# "INP", # flake8-no-pep420
# "INT", # flake8-gettext
"ISC", # flake8-implicit-str-concat
"N", # pep8-naming
# "NPY", # NumPy-specific rules
# "PD", # pandas-vet
# "PGH", # pygrep-hooks
# "PIE", # flake8-pie
# "PL", # Pylint
"PLC", # Pylint: Convention
"PLE", # Pylint: Error
"PLR", # Pylint: Refactor
"PLW", # Pylint: Warning
# "PT", # flake8-pytest-style
# "PTH", # flake8-use-pathlib
# "PYI", # flake8-pyi
"Q", # flake8-quotes
# "RET", # flake8-return
# "RSE", # flake8-raise
"RUF", # Ruff-specific rules
"S", # flake8-bandit
# "SIM", # flake8-simplify
# "SLF", # flake8-self
"T10", # flake8-debugger
"T20", # flake8-print
# "TCH", # flake8-type-checking
# "TD", # flake8-todos
"TID", # flake8-tidy-imports
# "TRY", # tryceratops
"UP", # pyupgrade
"W", # pycodestyle
"YTT", # flake8-2020
]
ignore = [
# Allow non-abstract empty methods in abstract base classes
"B027",
# Remove flake8-errmsg since we consider they bloat the code and provide limited value
"EM",
# Allow boolean positional values in function calls, like `dict.get(... True)`
"FBT003",
# Ignore checks for possible passwords
"S105", "S106", "S107",
# Ignore warnings on subprocess.run / popen
"S603",
# Ignore complexity
"C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915",
]
unfixable = [
# Don't touch unused imports
"F401",
]

[tool.ruff.lint.isort]
known-first-party = ["protonvpn_wireguard_config_downloader"]

[tool.ruff.lint.flake8-bugbear]
# add exceptions to B008 for fastapi.
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]

[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"

[tool.ruff.lint.per-file-ignores]
# Tests can use magic values, assertions, and relative imports
"tests/**/*" = ["PLR2004", "S101", "TID252"]

[tool.pytest.ini_options]
minversion = "7.3"
testpaths = ["tests"]
pythonpath = [".", "src"]

[tool.coverage.paths]
protonvpn_wireguard_config_downloader = ["src/protonvpn_wireguard_config_downloader"]
tests = ["tests"]

[tool.coverage.run]
source_pkgs = ["protonvpn_wireguard_config_downloader"]
branch = true
parallel = true
omit = [
"src/protonvpn_wireguard_config_downloader/__about__.py",
]

[tool.coverage.report]
exclude_lines = [
"no cov",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]

[tool.pyright]
include = ["src", "tests", "tasks.py"]
exclude = [".env/**", ".venv/**"]
extraPaths = ["src"]
pythonVersion = "3.11"
typeCheckingMode="strict"
disableBytesTypePromotions = true
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "1.0.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import logging

from protonvpn_wireguard_config_downloader.settings import Settings

logger = logging.getLogger("task")

if not logger.hasHandlers():
logger.setLevel(logging.DEBUG if Settings.DEBUG else logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("[%(asctime)s: %(levelname)s] %(message)s"))
logger.addHandler(handler)
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import argparse
import asyncio
import logging
from pathlib import Path

from protonvpn_wireguard_config_downloader import logger
from protonvpn_wireguard_config_downloader.__about__ import __version__
from protonvpn_wireguard_config_downloader.protonvpn import (
login,
logout,
save_vpn_server_wireguard_config,
vpn_servers,
)
from protonvpn_wireguard_config_downloader.settings import Settings


async def download_vpn_wireguard_configs(
username: str, password: str, wireguard_port: int, work_dir: Path
) -> None:
"""Download Wireguard configuration files for all VPN servers."""
session = await login(username, password)
try:
logger.debug("Fetching available VPN servers for client...")
for vpn_server in vpn_servers(session, wireguard_port):
save_vpn_server_wireguard_config(session, vpn_server, work_dir)
finally:
logger.debug("Logging out...")
await logout(session)
logger.info("Successfully logged out client.")


def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"-v", "--verbose", help="Show verbose output", action="store_true"
)
parser.add_argument(
"--version",
help="Show version and exit.",
action="version",
version="%(prog)s " + __version__,
)
args = parser.parse_args()
if args.verbose:
logger.setLevel(logging.DEBUG)

asyncio.run(
download_vpn_wireguard_configs(
Settings.USERNAME,
Settings.PASSWORD,
Settings.WIREGUARD_PORT,
Settings.WORKDIR,
)
)
Loading

0 comments on commit d562d0f

Please sign in to comment.