A simple shell-based VPN profile manager driven by a YAML config.
- Multiple profiles in
~/.config/vpn_config.yaml - Commands:
start|up,stop|down,status,backup,restore,backup-stats,set-backup,self-update - Self-update with release notes (prefers GitHub CLI, falls back to curl/wget)
- Cleanup before
start:-k/--killto remove all oldtun*interfaces, routes, DNS caches, and stray openvpn processes - Debug mode:
-dor-vto stream logs without tearing down the tunnel on Ctrl-C - Profile selection:
-p <profile> - Encrypted backup:
backupwith-o/--output <file>(defaultvpn-backup.tar.gz.gpg) - Encrypted restore:
restorewith-i/--input <file>(defaultvpn-backup.tar.gz.gpg) - Bash tab-completion for flags, actions, and profiles
- Works with either Go-yq or Python-yq
Download the latest release from GitHub Releases:
# Download and install latest release
curl -L https://github.com/cdds-ab/vpnctl/releases/latest/download/install.sh | bashOr manually:
wget https://github.com/cdds-ab/vpnctl/releases/latest/download/vpnctl
wget https://github.com/cdds-ab/vpnctl/releases/latest/download/install.sh
chmod +x install.sh
sudo ./install.shFor the best self-update experience with release notes:
# Install GitHub CLI (optional but recommended)
# Ubuntu/Debian:
sudo apt install gh
# macOS:
brew install gh
# Or see: https://cli.github.com/git clone https://github.com/cdds-ab/vpnctl.git
cd vpnctl
./scripts/install.shvpnctl [--version] [-d|-v] [-k] [-p <profile>] [-o <file>] [-i <file>] <start|up|stop|down|status|backup|restore|backup-stats|set-backup <path>|self-update>--version
Show version information and exit.-k/--kill
Before bringing up your chosen profile, clean out all oldtun*interfaces, their routes, DNS caches, and any leftover openvpn processes.-d/--debugor-v/--verbose
Stream the log from the very beginning without tearing down the tunnel on Ctrl-C.-p <profile>
Select which profile from your YAML to use (defaults to the first one).-o <file>/--output <file>(forbackup)
Write the encrypted backup archive to<file>. If omitted, defaults tovpn-backup.tar.gz.gpg.-i <file>/--input <file>(forrestore)
Read the encrypted backup archive from<file>. If omitted, defaults tovpn-backup.tar.gz.gpg.
Note! Currently the backup and recovery depends on having the following configurational setup:
${HOME}/.config/vpn_config.yaml: location of vpn_config.yaml${HOME}/.vpn/: location of the specific vpn configs of your customers
# Show version:
vpnctl --version
# Start the default profile:
vpnctl start
# Kill old VPN bits then start:
vpnctl -k start
# Start and immediately stream all logs:
vpnctl -d start
# Create encrypted backup to a specific path:
vpnctl backup -o ~/Backups/vpn-$(date +%F).tar.gz.gpg
# Restore from encrypted backup:
vpnctl restore -i ~/Backups/vpn-2025-07-05.tar.gz.gpg
# Stop a specific profile:
vpnctl -p customer1 stop
# Check status:
vpnctl status
# Set backup location in config:
vpnctl set-backup ~/Backups/my-vpn-backup.tar.gz.gpg
# View backup statistics:
vpnctl backup-stats
# Update to latest version (shows release notes with gh CLI):
vpnctl self-updatevpnctl includes comprehensive tests using BATS (Bash Automated Testing System):
# Install BATS
sudo apt install bats # Ubuntu/Debian
sudo dnf install bats # Fedora/RHEL
# Run all tests
./tests/run_tests.sh
# Run with coverage analysis
./tests/coverage.sh run
# Generate HTML coverage report
./tests/coverage.sh html
# Run specific test files
bats tests/test_vpnctl.batsInstall automated quality checks that run before each commit:
# One-time installation
./scripts/install-hooks.shWhat the pre-commit hook does:
- ✅ Tests: Runs all BATS tests (must pass to commit)
- ✅ Linting: Shellcheck validation (no warnings allowed)
- ✅ Functionality: Basic vpnctl functionality test
- ✅ GitHub Integration: Validates repo access and commit format (if
ghCLI available) - ✅ Security: Scans for potential secrets in staged files
Benefits:
- Prevents broken commits from entering the repository
- Ensures consistent code quality across all contributors
- Early detection of issues before CI/CD pipeline
- Automatic validation of conventional commit messages
Usage:
# After installation, hooks run automatically on every commit
git commit -m "feat: add new feature" # Hooks execute automatically
# To temporarily bypass hooks (NOT recommended)
git commit --no-verify -m "emergency fix"
# To uninstall hooks
rm .git/hooks/pre-commitThe test suite covers:
- ✅ Core backup file resolution logic
- ✅ Configuration file handling (YAML parsing)
- ✅ Both Go-yq and Python-yq compatibility
- ✅ set-backup command functionality
- ✅ Bash completion system
- ✅ Argument parsing and validation
- ✅ Path expansion and normalization
Coverage reports are generated in coverage/ directory with HTML visualization.
For the best development experience:
-
Install dependencies:
sudo apt install bats shellcheck gh # Ubuntu/Debian -
Install pre-commit hooks:
./scripts/install-hooks.sh
-
Make changes and commit:
# Hooks automatically run tests, linting, and security checks git commit -m "feat: your changes"
- Core VPN Management - start, stop, status, backup, restore operations
- Backup Analytics -
backup-statscommand for encrypted backup analysis - Configurable Backup -
set-backupcommand with YAML config and tab completion - Self-Update System - Automatic update checking and
self-updatecommand - Multi-yq Support - Compatible with both Go-yq (mikefarah) and Python-yq (kislyuk)
- Kill Switch -
-kflag for cleanup of existing tunnel interfaces
- Regression Tests - 21 BATS test cases with matrix testing
- Coverage Analysis - Function coverage validation and CI integration
- CI/CD Pipeline - Automated testing, linting, security scanning
- Multi-environment - Tests with both Go-yq and Python-yq variants
- Semantic Versioning - Automated releases via conventional commits
- GitHub Actions - Complete CI/CD with tests → lint → release pipeline
- Release Assets - Auto-generated with scripts, docs, and checksums
- Conventional Commits - Developer guidelines with commit templates
- Version: v1.0.1 (stable)
- Test Suite: 21 test cases covering core functionality
- Functions: Comprehensive coverage of backup, config, and VPN operations
- CI/CD: Fully automated
- Release Process: Zero-touch via conventional commits
This project uses automated releases with Semantic Versioning:
- feat: New features → MINOR version bump
- fix: Bug fixes → PATCH version bump
- BREAKING CHANGE: Breaking changes → MAJOR version bump
All releases are automatically created via GitHub Actions and include:
- Pre-compiled binaries and scripts
- Automated changelogs
- Release assets with checksums
See CONTRIBUTING.md for commit message guidelines.
cd vpnctl
./scripts/uninstall.shvpnctl expects your configuration residing in ~/.config/vpn_config.yaml:
# Sample VPN config for vpnctl
# Copy to ~/.config/vpn_config.yaml and adjust paths.
backup:
default_file: "$HOME/vpn-backup.tar.gz.gpg"
vpn:
default:
config: "$HOME/.vpn/default.ovpn"
other:
config: "~/vpn/other.ovpn"The backup.default_file setting allows you to specify a default location for backup operations:
- Without config: Uses
vpn-backup.tar.gz.gpgin current directory - With config: Uses the configured path (supports
~and$HOMEexpansion) - Command line override:
-oand-iflags always take precedence
Examples:
backup:
default_file: "~/Backups/vpn-backup.tar.gz.gpg" # Home directory
default_file: "/var/backups/vpn-backup.tar.gz.gpg" # Absolute path
default_file: "$HOME/Documents/vpn-backup.tar.gz.gpg" # Variable expansionUse the set-backup command to easily configure your backup location with tab completion:
# Set backup path (supports tab completion for file paths)
vpnctl set-backup ~/Backups/vpn-backup.tar.gz.gpg
# The command will automatically update your ~/.config/vpn_config.yaml
# All backup operations will now use this location by defaultI personally set it up like this:
backup:
default_file: "$HOME/Backups/vpn-backup.tar.gz.gpg"
vpn:
customer1:
config: "$HOME/.vpn/customer1/config.ovpn"
customer2:
config: "$HOME/.vpn/customer2/config.ovpn"Within each customer's config directory I then place the necessary configuration for OpenVPN. Example for customer2:
user@host:~/.vpn/customer2$ tree
.
├── auth.txt
├── config.ovpn
├── customer2-ca.pem
├── customer2-cert.key
└── customer2-cert.pemNot directly related to vpnctl, but important to your configuration is that you follow up on the paths within config.ovpn, for example:
ca /home/user/.vpn/customer2/customer2-ca.pem
cert /home/user/.vpn/customer2/customer2-cert.pem
key /home/user/.vpn/customer2/customer2-cert.key
auth-user-pass /home/user/.vpn/customer2/auth.txt