Skip to content

Architecture

How ActiveVPN is organized under the hood.

In this guide

File layout

ActiveVPN/
├── main.py               # Entry point + CLI (argparse) + rich TUI rendering
├── config.py             # Legacy shim → re-exports activevpn.config
├── pyproject.toml        # Packaging, metadata, console script
├── requirements.txt      # Runtime dependencies
├── activevpn/            # The library (importable as a package)
│   ├── __init__.py       # Public API exports
│   ├── config.py         # Config dataclass, platformdirs paths, load_config()
│   ├── detector.py       # NetworkDetector + typed data model
│   ├── logger.py         # History persistence and export helpers
│   ├── logo.py           # ASCII banner generation (pyfiglet + rich)
│   └── help.py           # Help menu rendering
├── core/                 # Backward-compatible shim (deprecated, use activevpn)
├── tests/                # pytest suite (mocked psutil/requests)
├── logo/                 # Brand logo
└── docs/                 # Documentation

Module responsibilities

Module Responsibility
main.py Parses CLI flags, orchestrates the scan, renders tables/panels with rich, returns exit codes.
activevpn/config.py Defines the typed Config dataclass and the user-facing paths via platformdirs: config at ~/.config/neostore/ActiveVPN/config.toml (Linux), %LOCALAPPDATA%\neostore\ActiveVPN (Windows), ~/Library/Application Support/neostore/ActiveVPN (macOS). load_config() merges defaults + file overrides + inline overrides. Config files are TOML (legacy .activevpn.json still loads via a JSON fallback).
activevpn/detector.py NetworkDetector checks interfaces (check_interfaces), processes (check_processes), public IP with API failover (get_public_ip_info), DNS resolver (check_dns_leak), IPv4/IPv6 (get_ipv4/get_ipv6), computes the verdict (_compute_verdict), and kills VPN processes (kill_vpn_services). Exposes the typed model ScanResult / Verdict / IPInfo / DNSInfo / ProcessInfo, a one-shot scan() function, and a watch() generator with callbacks.
activevpn/logger.py Appends scans to the history JSON (in the data dir), loads history, clears it, and exports to JSON/CSV/TXT. save_log() accepts either a ScanResult or a dict.
activevpn/logo.py Renders the pyfiglet banner inside a rich panel.
activevpn/help.py Renders the help menu and exit-code table.
core/ Deprecated shim that re-exports activevpn.* for backward compatibility.

Scan flow

run()                          # main.py
 ├─ print_banner()             # activevpn/logo.py
 ├─ NetworkDetector(console)   # activevpn/detector.py
 ├─ scan_network()
 │   ├─ check_interfaces()     # psutil.net_if_addrs() vs vpn_interface_patterns
 │   ├─ check_processes()      # psutil.process_iter() vs vpn/tor_process_names
 │   ├─ get_public_ip_info()   # tries ip_api_urls in order until one succeeds
 │   ├─ check_dns_leak()       # edns.ip-api.com
 │   ├─ get_ipv4() / get_ipv6()# api.ipify.org / api6.ipify.org
 │   └─ _compute_verdict()     # weighted score + label
 ├─ save_log()                 # activevpn/logger.py
 └─ display tables/panels      # main.py (rich)

Verdict scoring

_compute_verdict() sums weights from the active Config (defaults in activevpn/config.py):

Signal Weight
VPN interface detected 50
VPN process(es) running 40
Tor process detected 35
IP flagged as hosting 25
IP flagged as proxy 30

The score is capped at 100 and mapped to a label:

Score Label
75–100 VPN DETECTED
40–74 LIKELY VPN/PROXY
20–39 SUSPICIOUS
0–19 CLEAN

History and export

Every scan is appended to scan_history.json in the platform data directory (~/.local/share/neostore/ActiveVPN/ on Linux, %LOCALAPPDATA%\neostore\ActiveVPN\ on Windows). activevpn/logger.py flattens each entry into CSV/TXT/JSON rows for --export and renders the table for --history.

Using as a library

import activevpn

result = activevpn.scan(console=None)          # silent one-shot scan
result.verdict.label                           # -> "CLEAN"
result.to_json()                               # serializable dict/JSON

cfg = activevpn.load_config()                  # merge file overrides
cfg.vpn_process_names.append("my-vpn-daemon")

detector = activevpn.NetworkDetector(console=None, config=cfg)
for r in detector.watch(interval=30, on_change=lambda r: print(r.verdict.label)):
    ...

← Back to Home