mirror of
https://github.com/angristan/openvpn-install.git
synced 2025-12-16 17:07:02 +01:00
feat: add CLI interface with subcommands (#1398)
Replace environment variable-based configuration with a proper CLI interface using subcommands and flags. ### Commands ``` openvpn-install <command> [options] Commands: install Install and configure OpenVPN server uninstall Remove OpenVPN server client Manage client certificates (add/list/revoke/renew) server Server management (status/renew) interactive Launch interactive menu ``` ### Highlights - **No args → help**: Running without arguments shows help instead of interactive menu - **JSON output**: `client list` and `server status` support `--format json` - **25+ install flags**: Network, DNS, security, and client options - **Interactive mode preserved**: `install --interactive` or `interactive` command ### Breaking Changes Environment variables (`AUTO_INSTALL`, `MENU_OPTION`, `CLIENT`, etc.) are no longer supported. Use CLI flags instead. ```bash # Before MENU_OPTION=1 CLIENT=foo PASS=1 ./openvpn-install.sh # After ./openvpn-install.sh client add foo ``` Closes https://github.com/angristan/openvpn-install/issues/1202
This commit is contained in:
2
.github/workflows/do-test.yml
vendored
2
.github/workflows/do-test.yml
vendored
@@ -97,7 +97,7 @@ jobs:
|
||||
host: ${{ steps.server_ip.outputs.value }}
|
||||
username: root
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
script: 'set -x && AUTO_INSTALL=y bash -x ~/openvpn-install/openvpn-install.sh && ps aux | grep openvpn | grep -v grep > /dev/null 2>&1 && echo "Success: OpenVPN is running" && exit 0 || echo "Failure: OpenVPN is not running" && exit 1'
|
||||
script: 'set -x && bash -x ~/openvpn-install/openvpn-install.sh install && ps aux | grep openvpn | grep -v grep > /dev/null 2>&1 && echo "Success: OpenVPN is running" && exit 0 || echo "Failure: OpenVPN is not running" && exit 1'
|
||||
|
||||
- name: Delete server
|
||||
run: doctl compute droplet delete -f "openvpn-action-${GITHUB_RUN_ID}-${GITHUB_RUN_NUMBER}-${{ matrix.os-image }}"
|
||||
|
||||
10
.github/workflows/docker-test.yml
vendored
10
.github/workflows/docker-test.yml
vendored
@@ -73,7 +73,7 @@ jobs:
|
||||
# Default TLS settings (tls-crypt-v2)
|
||||
tls:
|
||||
- name: tls-crypt-v2
|
||||
sig: "1"
|
||||
sig: crypt-v2
|
||||
key_file: tls-crypt-v2.key
|
||||
# Additional TLS types tested on Ubuntu 24.04 only
|
||||
include:
|
||||
@@ -82,14 +82,14 @@ jobs:
|
||||
image: ubuntu:24.04
|
||||
tls:
|
||||
name: tls-crypt
|
||||
sig: "2"
|
||||
sig: crypt
|
||||
key_file: tls-crypt.key
|
||||
- os:
|
||||
name: ubuntu-24.04-tls-auth
|
||||
image: ubuntu:24.04
|
||||
tls:
|
||||
name: tls-auth
|
||||
sig: "3"
|
||||
sig: auth
|
||||
key_file: tls-auth.key
|
||||
# Test firewalld support on Fedora
|
||||
- os:
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
enable_firewalld: true
|
||||
tls:
|
||||
name: tls-crypt-v2
|
||||
sig: "1"
|
||||
sig: crypt-v2
|
||||
key_file: tls-crypt-v2.key
|
||||
# Test nftables support on Debian
|
||||
- os:
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
enable_nftables: true
|
||||
tls:
|
||||
name: tls-crypt-v2
|
||||
sig: "1"
|
||||
sig: crypt-v2
|
||||
key_file: tls-crypt-v2.key
|
||||
|
||||
name: ${{ matrix.os.name }}
|
||||
|
||||
52
FAQ.md
52
FAQ.md
@@ -10,7 +10,18 @@ You can, of course, it's even recommended, update the `openvpn` package with you
|
||||
|
||||
**Q:** How do I renew certificates before they expire?
|
||||
|
||||
**A:** Run the script again and select "Renew certificates" from the menu. You can renew either client certificates or the server certificate. The script will show you the current expiration date for each certificate and let you choose a new validity period (default: 3650 days / 10 years).
|
||||
**A:** Use the CLI commands to renew certificates:
|
||||
|
||||
```bash
|
||||
# Renew a client certificate
|
||||
./openvpn-install.sh client renew alice
|
||||
|
||||
# Renew with custom validity period (365 days)
|
||||
./openvpn-install.sh client renew alice --cert-days 365
|
||||
|
||||
# Renew the server certificate
|
||||
./openvpn-install.sh server renew
|
||||
```
|
||||
|
||||
For client renewals, a new `.ovpn` file will be generated that you need to distribute to the client. For server renewals, the OpenVPN service will need to be restarted (the script will prompt you).
|
||||
|
||||
@@ -61,20 +72,6 @@ down /usr/share/openvpn/contrib/pull-resolv-conf/client.down
|
||||
|
||||
---
|
||||
|
||||
**Q:** Can I use an OpenVPN 2.3 client?
|
||||
|
||||
**A:** Yes. I really recommend using an up-to-date client, but if you really need it, choose the following options:
|
||||
|
||||
- No compression or LZ0
|
||||
- RSA certificate
|
||||
- DH Key
|
||||
- AES CBC
|
||||
- tls-auth
|
||||
|
||||
If your client is <2.3.3, remove `tls-version-min 1.2` from your `/etc/openvpn/server/server.conf` and `.ovpn` files.
|
||||
|
||||
---
|
||||
|
||||
**Q:** IPv6 is not working on my Hetzner VM
|
||||
|
||||
**A:** This an issue on their side. See <https://angristan.xyz/fix-ipv6-hetzner-cloud/>
|
||||
@@ -109,10 +106,6 @@ Sysctl options are at `/etc/sysctl.d/99-openvpn.conf`
|
||||
|
||||
type `yes` when asked to customize encryption settings and choose `tls-auth`
|
||||
|
||||
- `Options error: Unrecognized option or missing parameter(s) in config.ovpn:36: tls-version-min (2.3.2)` :
|
||||
|
||||
see question "Can I use an OpenVPN 2.3 client?"
|
||||
|
||||
---
|
||||
|
||||
**Q:** How can I access computers the OpenVPN server's remote LAN?
|
||||
@@ -125,22 +118,31 @@ Sysctl options are at `/etc/sysctl.d/99-openvpn.conf`
|
||||
|
||||
**A:** Here is a sample Bash script to achieve this:
|
||||
|
||||
```sh
|
||||
```bash
|
||||
#!/bin/bash
|
||||
userlist=(user1 user2 user3)
|
||||
|
||||
for i in ${userlist[@]};do
|
||||
MENU_OPTION=1 CLIENT=$i PASS=1 ./openvpn-install.sh
|
||||
for user in "${userlist[@]}"; do
|
||||
./openvpn-install.sh client add "$user"
|
||||
done
|
||||
```
|
||||
|
||||
From a list in a text file:
|
||||
|
||||
```sh
|
||||
while read USER
|
||||
do MENU_OPTION="1" CLIENT="$USER" PASS="1" ./openvpn-install.sh
|
||||
```bash
|
||||
#!/bin/bash
|
||||
while read -r user; do
|
||||
./openvpn-install.sh client add "$user"
|
||||
done < users.txt
|
||||
```
|
||||
|
||||
To add password-protected clients:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
./openvpn-install.sh client add alice --password "secretpass123"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Q:** How do I change the default `.ovpn` file created for future clients?
|
||||
|
||||
381
README.md
381
README.md
@@ -1,12 +1,10 @@
|
||||
# openvpn-install
|
||||
|
||||

|
||||

|
||||
[](https://saythanks.io/to/angristan)
|
||||
|
||||
OpenVPN installer for Debian, Ubuntu, Fedora, openSUSE, CentOS, Amazon Linux, Arch Linux, Oracle Linux, Rocky Linux and AlmaLinux.
|
||||
|
||||
This script will let you setup your own secure VPN server in just a few seconds.
|
||||
This script will let you setup and manage your own secure VPN server in just a few seconds.
|
||||
|
||||
## What is this?
|
||||
|
||||
@@ -37,129 +35,18 @@ That said, OpenVPN still makes sense when you need:
|
||||
- **Password-protected private keys**: WireGuard configs store the private key in plain text
|
||||
- **Legacy compatibility**: clients exist for pretty much every platform, including older systems
|
||||
|
||||
## Usage
|
||||
|
||||
First, on your server, get the script and make it executable:
|
||||
|
||||
```bash
|
||||
curl -O https://raw.githubusercontent.com/angristan/openvpn-install/master/openvpn-install.sh
|
||||
chmod +x openvpn-install.sh
|
||||
```
|
||||
|
||||
Then run it:
|
||||
|
||||
```sh
|
||||
./openvpn-install.sh
|
||||
```
|
||||
|
||||
You need to run the script as root and have the TUN module enabled.
|
||||
|
||||
The first time you run it, you'll have to follow the assistant and answer a few questions to setup your VPN server.
|
||||
|
||||
When OpenVPN is installed, you can run the script again, and you will get the choice to:
|
||||
|
||||
- Add a client
|
||||
- List client certificates
|
||||
- Revoke a client
|
||||
- Renew certificates (client or server)
|
||||
- Uninstall OpenVPN
|
||||
- List connected clients (shows real-time connection status)
|
||||
|
||||
In your home directory, you will have `.ovpn` files. These are the client configuration files. Download them from your server and connect using your favorite OpenVPN client.
|
||||
|
||||
If you have any question, head to the [FAQ](#faq) first. And if you need help, you can open a [discussion](https://github.com/angristan/openvpn-install/discussions). Please search existing issues and dicussions first.
|
||||
|
||||
### Headless install
|
||||
|
||||
It's also possible to run the script headless, e.g. without waiting for user input, in an automated manner.
|
||||
|
||||
Example usage:
|
||||
|
||||
```bash
|
||||
AUTO_INSTALL=y ./openvpn-install.sh
|
||||
|
||||
# or
|
||||
|
||||
export AUTO_INSTALL=y
|
||||
./openvpn-install.sh
|
||||
```
|
||||
|
||||
A default set of variables will then be set, by passing the need for user input.
|
||||
|
||||
If you want to customise your installation, you can export them or specify them on the same line, as shown above.
|
||||
|
||||
- `APPROVE_INSTALL=y`
|
||||
- `APPROVE_IP=y`
|
||||
- `IPV6_SUPPORT=n`
|
||||
- `VPN_SUBNET=10.8.0.0` (VPN subnet, must be a valid RFC1918 /24 network like `10.8.0.0`, `10.9.0.0`, `172.16.0.0`, or `192.168.1.0`)
|
||||
- `PORT_CHOICE=1`
|
||||
- `PROTOCOL_CHOICE=1`
|
||||
- `DNS=1`
|
||||
- `COMPRESSION_ENABLED=n`
|
||||
- `CUSTOMIZE_ENC=n`
|
||||
- `CLIENT=clientname`
|
||||
- `PASS=1` (set to `2` for password-protected clients, requires `PASSPHRASE`)
|
||||
- `MULTI_CLIENT=n`
|
||||
- `CLIENT_CERT_DURATION_DAYS=3650`
|
||||
- `SERVER_CERT_DURATION_DAYS=3650`
|
||||
- `NEW_CLIENT=y` (set to `n` to skip client creation after installation)
|
||||
- `CLIENT_FILEPATH=/custom/path/client.ovpn` (optional, overrides default output path)
|
||||
|
||||
The `.ovpn` file is saved to `CLIENT_FILEPATH` if defined, otherwise: the client's home directory if it exists (`/home/$CLIENT`), otherwise `SUDO_USER`'s home, otherwise `/root`. When the client name matches a system user, the script automatically sets proper ownership and permissions on the file.
|
||||
|
||||
If the server is behind NAT, you can specify its endpoint with the `ENDPOINT` variable. If the endpoint is the public IP address which it is behind, you can use `ENDPOINT=$(curl -4 ifconfig.co)` (the script will default to this). The endpoint can be an IPv4 or a domain.
|
||||
|
||||
Other variables can be set depending on your choice (encryption, compression). You can search for them in the `installQuestions()` function of the script.
|
||||
|
||||
The headless install is more-or-less idempotent, in that it has been made safe to run multiple times with the same parameters, e.g. by a state provisioner like Ansible/Terraform/Salt/Chef/Puppet. It will only install and regenerate the Easy-RSA PKI if it doesn't already exist, and it will only install OpenVPN and other upstream dependencies if OpenVPN isn't already installed. It will recreate all local config and re-generate the client file on each headless run.
|
||||
|
||||
### Headless User Addition
|
||||
|
||||
It's also possible to automate the addition of a new user. Here, the key is to provide the (string) value of the `MENU_OPTION` variable along with the remaining mandatory variables before invoking the script.
|
||||
|
||||
The following Bash script adds a new user `foo` to an existing OpenVPN configuration
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
export MENU_OPTION="1"
|
||||
export CLIENT="foo"
|
||||
export PASS="1" # set to "2" for a password-protected client, and set PASSPHRASE
|
||||
export CLIENT_FILEPATH="" # optional, custom path for .ovpn file
|
||||
./openvpn-install.sh
|
||||
```
|
||||
|
||||
### Headless User Revocation
|
||||
|
||||
It's also possible to automate the revocation of an existing user. The key is to provide the `MENU_OPTION` variable set to `3` along with either `CLIENT` (client name) or `CLIENTNUMBER` (1-based index from the client list).
|
||||
|
||||
The following Bash script revokes the existing user `foo`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
export MENU_OPTION="3"
|
||||
export CLIENT="foo"
|
||||
./openvpn-install.sh
|
||||
```
|
||||
|
||||
Alternatively, you can use the client number:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
export MENU_OPTION="3"
|
||||
export CLIENTNUMBER="1" # Revokes the first client in the list
|
||||
./openvpn-install.sh
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Installs and configures a ready-to-use OpenVPN server
|
||||
- CLI interface for automation and scripting (non-interactive mode with JSON output)
|
||||
- Certificate renewal for both client and server certificates
|
||||
- List and monitor connected clients
|
||||
- Uses [official OpenVPN repositories](https://community.openvpn.net/openvpn/wiki/OpenvpnSoftwareRepos) when possible for the latest stable releases
|
||||
- Firewall rules and forwarding managed seamlessly (native firewalld and nftables support, iptables fallback)
|
||||
- Configurable VPN subnet (default: `10.8.0.0/24`)
|
||||
- If needed, the script can cleanly remove OpenVPN, including configuration and firewall rules
|
||||
- Customisable encryption settings, enhanced default settings (see [Security and Encryption](#security-and-encryption) below)
|
||||
- OpenVPN 2.4 features, mainly encryption improvements (see [Security and Encryption](#security-and-encryption) below)
|
||||
- Uses latest OpenVPN features when available (see [Security and Encryption](#security-and-encryption) below)
|
||||
- Variety of DNS resolvers to be pushed to the clients
|
||||
- Choice to use a self-hosted resolver with Unbound (supports already existing Unbound installations)
|
||||
- Choice between TCP and UDP
|
||||
@@ -196,6 +83,258 @@ To be noted:
|
||||
- It's only tested on `amd64` architecture.
|
||||
- The script requires `systemd`.
|
||||
|
||||
## Usage
|
||||
|
||||
First, download the script on your server and make it executable:
|
||||
|
||||
```bash
|
||||
curl -O https://raw.githubusercontent.com/angristan/openvpn-install/master/openvpn-install.sh
|
||||
chmod +x openvpn-install.sh
|
||||
```
|
||||
|
||||
You need to run the script as root and have the TUN module enabled.
|
||||
|
||||
### Interactive Mode
|
||||
|
||||
The easiest way to get started is the interactive menu:
|
||||
|
||||
```bash
|
||||
./openvpn-install.sh interactive
|
||||
```
|
||||
|
||||
This will guide you through installation and client management.
|
||||
|
||||
In your home directory, you will have `.ovpn` files. These are the client configuration files. Download them from your server (using `scp` for example) and connect using your favorite OpenVPN client.
|
||||
|
||||
If you have any question, head to the [FAQ](#faq) first. And if you need help, you can open a [discussion](https://github.com/angristan/openvpn-install/discussions). Please search existing issues and discussions first.
|
||||
|
||||
### CLI Mode
|
||||
|
||||
> [!WARNING]
|
||||
> API compatibility is not guaranteed. Breaking changes may occur between versions. If you use this script programmatically (e.g., in automation or CI/CD), pin to a specific commit rather than using the master branch.
|
||||
|
||||
For automation and scripting, use the CLI interface:
|
||||
|
||||
```bash
|
||||
# Install with defaults
|
||||
./openvpn-install.sh install
|
||||
|
||||
# Add a client
|
||||
./openvpn-install.sh client add alice
|
||||
|
||||
# List clients
|
||||
./openvpn-install.sh client list
|
||||
|
||||
# Revoke a client
|
||||
./openvpn-install.sh client revoke alice
|
||||
```
|
||||
|
||||
#### Commands
|
||||
|
||||
```text
|
||||
openvpn-install <command> [options]
|
||||
|
||||
Commands:
|
||||
install Install and configure OpenVPN server
|
||||
uninstall Remove OpenVPN server
|
||||
client Manage client certificates
|
||||
server Server management
|
||||
interactive Launch interactive menu
|
||||
|
||||
Global Options:
|
||||
--verbose Show detailed output
|
||||
--log <path> Log file path (default: openvpn-install.log)
|
||||
--no-log Disable file logging
|
||||
--no-color Disable colored output
|
||||
-h, --help Show help
|
||||
```
|
||||
|
||||
Run `./openvpn-install.sh <command> --help` for command-specific options.
|
||||
|
||||
#### Client Management
|
||||
|
||||
```bash
|
||||
# Add a new client
|
||||
./openvpn-install.sh client add alice
|
||||
|
||||
# Add a password-protected client
|
||||
./openvpn-install.sh client add bob --password
|
||||
|
||||
# Revoke a client
|
||||
./openvpn-install.sh client revoke alice
|
||||
|
||||
# Renew a client certificate
|
||||
./openvpn-install.sh client renew bob --cert-days 365
|
||||
```
|
||||
|
||||
List all clients:
|
||||
|
||||
```text
|
||||
$ ./openvpn-install.sh client list
|
||||
══ Client Certificates ══
|
||||
[INFO] Found 3 client certificate(s)
|
||||
|
||||
Name Status Expiry Remaining
|
||||
---- ------ ------ ---------
|
||||
alice Valid 2035-01-15 3650 days
|
||||
bob Valid 2035-01-15 3650 days
|
||||
charlie Revoked 2035-01-15 unknown
|
||||
```
|
||||
|
||||
JSON output for scripting:
|
||||
|
||||
```text
|
||||
$ ./openvpn-install.sh client list --format json | jq
|
||||
{
|
||||
"clients": [
|
||||
{
|
||||
"name": "alice",
|
||||
"status": "valid",
|
||||
"expiry": "2035-01-15",
|
||||
"days_remaining": 3650
|
||||
},
|
||||
{
|
||||
"name": "bob",
|
||||
"status": "valid",
|
||||
"expiry": "2035-01-15",
|
||||
"days_remaining": 3650
|
||||
},
|
||||
{
|
||||
"name": "charlie",
|
||||
"status": "revoked",
|
||||
"expiry": "2035-01-15",
|
||||
"days_remaining": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Server Management
|
||||
|
||||
```bash
|
||||
# Renew server certificate
|
||||
./openvpn-install.sh server renew
|
||||
|
||||
# Uninstall OpenVPN
|
||||
./openvpn-install.sh uninstall
|
||||
```
|
||||
|
||||
Show connected clients (data refreshes every 60 seconds):
|
||||
|
||||
```text
|
||||
$ ./openvpn-install.sh server status
|
||||
══ Connected Clients ══
|
||||
[INFO] Found 2 connected client(s)
|
||||
|
||||
Name Real Address VPN IP Connected Since Transfer
|
||||
---- ------------ ------ --------------- --------
|
||||
alice 203.0.113.45:52341 10.8.0.2 2025-01-15 14:32 ↓1.2M ↑500K
|
||||
bob 198.51.100.22:41892 10.8.0.3 2025-01-15 09:15 ↓800K ↑200K
|
||||
|
||||
[INFO] Note: Data refreshes every 60 seconds.
|
||||
```
|
||||
|
||||
#### Install Options
|
||||
|
||||
The `install` command supports many options for customization:
|
||||
|
||||
```bash
|
||||
# Custom port and protocol
|
||||
./openvpn-install.sh install --port 443 --protocol tcp
|
||||
|
||||
# Custom DNS provider
|
||||
./openvpn-install.sh install --dns quad9
|
||||
|
||||
# Custom encryption settings
|
||||
./openvpn-install.sh install --cipher AES-256-GCM --cert-type rsa --rsa-bits 4096
|
||||
|
||||
# Custom VPN subnet
|
||||
./openvpn-install.sh install --subnet 10.9.0.0
|
||||
|
||||
# Skip initial client creation
|
||||
./openvpn-install.sh install --no-client
|
||||
|
||||
# Full example with multiple options
|
||||
./openvpn-install.sh install \
|
||||
--port 443 \
|
||||
--protocol tcp \
|
||||
--dns cloudflare \
|
||||
--cipher AES-256-GCM \
|
||||
--client mydevice \
|
||||
--client-cert-days 365
|
||||
```
|
||||
|
||||
**Network Options:**
|
||||
|
||||
- `--endpoint <host>` - Public IP or hostname for clients (default: auto-detected)
|
||||
- `--ip <addr>` - Server listening IP (default: auto-detected)
|
||||
- `--ipv6` - Enable IPv6 support (default: disabled)
|
||||
- `--subnet <x.x.x.0>` - VPN subnet (default: `10.8.0.0`)
|
||||
- `--port <num>` - OpenVPN port (default: `1194`)
|
||||
- `--port-random` - Use random port (49152-65535)
|
||||
- `--protocol <udp|tcp>` - Protocol (default: `udp`)
|
||||
|
||||
**DNS Options:**
|
||||
|
||||
- `--dns <provider>` - DNS provider (default: `cloudflare`). Options: `system`, `unbound`, `cloudflare`, `quad9`, `quad9-uncensored`, `fdn`, `dnswatch`, `opendns`, `google`, `yandex`, `adguard`, `nextdns`, `custom`
|
||||
- `--dns-primary <ip>` - Custom primary DNS (requires `--dns custom`)
|
||||
- `--dns-secondary <ip>` - Custom secondary DNS (requires `--dns custom`)
|
||||
|
||||
**Security Options:**
|
||||
|
||||
- `--cipher <cipher>` - Data cipher (default: `AES-128-GCM`). Options: `AES-128-GCM`, `AES-192-GCM`, `AES-256-GCM`, `AES-128-CBC`, `AES-192-CBC`, `AES-256-CBC`, `CHACHA20-POLY1305`
|
||||
- `--cert-type <ecdsa|rsa>` - Certificate type (default: `ecdsa`)
|
||||
- `--cert-curve <curve>` - ECDSA curve (default: `prime256v1`). Options: `prime256v1`, `secp384r1`, `secp521r1`
|
||||
- `--rsa-bits <2048|3072|4096>` - RSA key size (default: `2048`)
|
||||
- `--hmac <alg>` - HMAC algorithm (default: `SHA256`). Options: `SHA256`, `SHA384`, `SHA512`
|
||||
- `--tls-sig <mode>` - TLS mode (default: `crypt-v2`). Options: `crypt-v2`, `crypt`, `auth`
|
||||
- `--dh-type <ecdh|dh>` - DH key exchange type (default: `ecdh`)
|
||||
- `--dh-curve <curve>` - ECDH curve (default: `prime256v1`). Options: `prime256v1`, `secp384r1`, `secp521r1`
|
||||
- `--dh-bits <2048|3072|4096>` - DH key size when using `--dh-type dh` (default: `2048`)
|
||||
- `--server-cert-days <n>` - Server cert validity in days (default: `3650`)
|
||||
|
||||
**Client Options:**
|
||||
|
||||
- `--client <name>` - Initial client name (default: `client`)
|
||||
- `--client-password [pass]` - Password-protect client key (default: no password)
|
||||
- `--client-cert-days <n>` - Client cert validity in days (default: `3650`)
|
||||
- `--no-client` - Skip initial client creation
|
||||
|
||||
**Other Options:**
|
||||
|
||||
- `--compression <alg>` - Compression (default: `none`). Options: `none`, `lz4-v2`, `lz4`, `lzo`
|
||||
- `--multi-client` - Allow same cert on multiple devices (default: disabled)
|
||||
|
||||
#### Automation Examples
|
||||
|
||||
**Batch client creation:**
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
for user in alice bob charlie; do
|
||||
./openvpn-install.sh client add "$user"
|
||||
done
|
||||
```
|
||||
|
||||
**Create clients from a file:**
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
while read -r user; do
|
||||
./openvpn-install.sh client add "$user"
|
||||
done < users.txt
|
||||
```
|
||||
|
||||
**JSON output for scripting:**
|
||||
|
||||
```bash
|
||||
# Get client list as JSON
|
||||
./openvpn-install.sh client list --format json | jq '.clients[] | select(.status == "valid")'
|
||||
|
||||
# Get connected clients as JSON
|
||||
./openvpn-install.sh server status --format json
|
||||
```
|
||||
|
||||
## Fork
|
||||
|
||||
This script is based on the great work of [Nyr and its contributors](https://github.com/Nyr/openvpn-install).
|
||||
@@ -236,7 +375,7 @@ More Q&A in [FAQ.md](FAQ.md).
|
||||
|
||||
**Q:** Is there an OpenVPN documentation?
|
||||
|
||||
**A:** Yes, please head to the [OpenVPN Manual](https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage), which references all the options.
|
||||
**A:** Yes, please head to the [OpenVPN Manual](https://openvpn.net/community-docs/community-articles/openvpn-2-6-manual.html), which references all the options.
|
||||
|
||||
---
|
||||
|
||||
@@ -244,14 +383,10 @@ More Q&A in [FAQ.md](FAQ.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
## Discuss changes
|
||||
### Discuss changes
|
||||
|
||||
Please open an issue before submitting a PR if you want to discuss a change, especially if it's a big one.
|
||||
|
||||
### Code formatting
|
||||
|
||||
We use [shellcheck](https://github.com/koalaman/shellcheck) and [shfmt](https://github.com/mvdan/sh) to enforce Bash styling guidelines and good practices. They are executed for each commit / PR with GitHub Actions, so you can check the [lint workflow configuration](https://github.com/angristan/openvpn-install/blob/master/.github/workflows/lint.yml).
|
||||
|
||||
## Security and Encryption
|
||||
|
||||
> [!NOTE]
|
||||
|
||||
1264
openvpn-install.sh
1264
openvpn-install.sh
File diff suppressed because it is too large
Load Diff
@@ -17,29 +17,29 @@ ENV ENABLE_NFTABLES=${ENABLE_NFTABLES}
|
||||
# dnsutils/bind-utils provides dig for DNS testing with Unbound
|
||||
RUN if command -v apt-get >/dev/null; then \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
iproute2 iptables curl procps systemd systemd-sysv dnsutils \
|
||||
iproute2 iptables curl procps systemd systemd-sysv dnsutils jq \
|
||||
&& if [ "$ENABLE_NFTABLES" = "y" ]; then apt-get install -y --no-install-recommends nftables; fi \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
elif command -v dnf >/dev/null; then \
|
||||
dnf install -y --allowerasing \
|
||||
iproute iptables curl procps-ng systemd tar gzip bind-utils \
|
||||
iproute iptables curl procps-ng systemd tar gzip bind-utils jq \
|
||||
&& if [ "$ENABLE_FIREWALLD" = "y" ]; then dnf install -y firewalld; fi \
|
||||
&& if [ "$ENABLE_NFTABLES" = "y" ]; then dnf install -y nftables; fi \
|
||||
&& dnf clean all; \
|
||||
elif command -v yum >/dev/null; then \
|
||||
yum install -y \
|
||||
iproute iptables curl procps-ng systemd tar gzip bind-utils \
|
||||
iproute iptables curl procps-ng systemd tar gzip bind-utils jq \
|
||||
&& if [ "$ENABLE_FIREWALLD" = "y" ]; then yum install -y firewalld; fi \
|
||||
&& if [ "$ENABLE_NFTABLES" = "y" ]; then yum install -y nftables; fi \
|
||||
&& yum clean all; \
|
||||
elif command -v pacman >/dev/null; then \
|
||||
pacman -Syu --noconfirm \
|
||||
iproute2 iptables curl procps-ng bind \
|
||||
iproute2 iptables curl procps-ng bind jq \
|
||||
&& if [ "$ENABLE_NFTABLES" = "y" ]; then pacman -S --noconfirm nftables; fi \
|
||||
&& pacman -Scc --noconfirm; \
|
||||
elif command -v zypper >/dev/null; then \
|
||||
zypper install -y \
|
||||
iproute2 iptables curl procps systemd tar gzip bind-utils gawk \
|
||||
iproute2 iptables curl procps systemd tar gzip bind-utils gawk jq \
|
||||
&& if [ "$ENABLE_NFTABLES" = "y" ]; then zypper install -y nftables; fi \
|
||||
&& zypper clean -a; \
|
||||
fi
|
||||
|
||||
@@ -12,54 +12,40 @@ fi
|
||||
|
||||
echo "TUN device ready"
|
||||
|
||||
# Set up environment for auto-install
|
||||
export AUTO_INSTALL=y
|
||||
# Configuration for install
|
||||
export FORCE_COLOR=1
|
||||
export APPROVE_INSTALL=y
|
||||
export APPROVE_IP=y
|
||||
export IPV6_SUPPORT=n
|
||||
export VPN_SUBNET=10.9.0.0 # Custom subnet to test configurability
|
||||
export PORT_CHOICE=1
|
||||
export PROTOCOL_CHOICE=1
|
||||
export DNS=2 # Self-hosted Unbound DNS resolver
|
||||
export COMPRESSION_ENABLED=n
|
||||
export CLIENT=testclient
|
||||
export PASS=1
|
||||
export ENDPOINT=openvpn-server
|
||||
VPN_SUBNET=10.9.0.0 # Custom subnet to test configurability
|
||||
|
||||
# Calculate VPN gateway from subnet (first usable IP)
|
||||
VPN_GATEWAY="${VPN_SUBNET%.*}.1"
|
||||
export VPN_GATEWAY
|
||||
|
||||
# TLS key type configuration (default: tls-crypt-v2)
|
||||
# TLS_SIG: 1=tls-crypt-v2, 2=tls-crypt, 3=tls-auth
|
||||
# TLS_SIG: crypt-v2, crypt, auth
|
||||
# TLS_KEY_FILE: the expected key file name for verification
|
||||
TLS_SIG="${TLS_SIG:-1}"
|
||||
TLS_SIG="${TLS_SIG:-crypt-v2}"
|
||||
TLS_KEY_FILE="${TLS_KEY_FILE:-tls-crypt-v2.key}"
|
||||
export TLS_SIG
|
||||
|
||||
# If using non-default TLS settings, enable encryption customization
|
||||
if [ "$TLS_SIG" != "1" ]; then
|
||||
export CUSTOMIZE_ENC=y
|
||||
# Set other encryption defaults when customizing
|
||||
export CIPHER_CHOICE=1 # AES-128-GCM
|
||||
export CERT_TYPE=1 # ECDSA
|
||||
export CERT_CURVE_CHOICE=1 # prime256v1
|
||||
export CC_CIPHER_CHOICE=1 # ECDHE-ECDSA-AES-128-GCM-SHA256
|
||||
export DH_TYPE=1 # ECDH
|
||||
export DH_CURVE_CHOICE=1 # prime256v1
|
||||
export HMAC_ALG_CHOICE=1 # SHA-256
|
||||
# Build install command with CLI flags (using array for proper quoting)
|
||||
INSTALL_CMD=(/opt/openvpn-install.sh install)
|
||||
INSTALL_CMD+=(--endpoint openvpn-server)
|
||||
INSTALL_CMD+=(--dns unbound)
|
||||
INSTALL_CMD+=(--subnet "$VPN_SUBNET")
|
||||
INSTALL_CMD+=(--client testclient)
|
||||
|
||||
# Add TLS signature mode if non-default
|
||||
if [ "$TLS_SIG" != "crypt-v2" ]; then
|
||||
INSTALL_CMD+=(--tls-sig "$TLS_SIG")
|
||||
echo "Testing TLS key type: $TLS_SIG (key file: $TLS_KEY_FILE)"
|
||||
else
|
||||
export CUSTOMIZE_ENC=n
|
||||
fi
|
||||
|
||||
echo "Running OpenVPN install script..."
|
||||
echo "Command: ${INSTALL_CMD[*]}"
|
||||
# Run in subshell because the script calls 'exit 0' after generating client config
|
||||
# Capture output to validate logging format, while still displaying it
|
||||
# Use || true to prevent set -e from exiting on failure, then check exit code
|
||||
INSTALL_OUTPUT="/tmp/install-output.log"
|
||||
(bash /opt/openvpn-install.sh) 2>&1 | tee "$INSTALL_OUTPUT"
|
||||
("${INSTALL_CMD[@]}") 2>&1 | tee "$INSTALL_OUTPUT"
|
||||
INSTALL_EXIT_CODE=${PIPESTATUS[0]}
|
||||
|
||||
echo "=== Installation complete (exit code: $INSTALL_EXIT_CODE) ==="
|
||||
@@ -196,7 +182,7 @@ echo "Original client certificate serial: $ORIG_CERT_SERIAL"
|
||||
# Test client certificate renewal using the script
|
||||
echo "Testing client certificate renewal..."
|
||||
RENEW_OUTPUT="/tmp/renew-client-output.log"
|
||||
(MENU_OPTION=4 RENEW_OPTION=1 CLIENTNUMBER=1 CLIENT_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$RENEW_OUTPUT" || true
|
||||
(bash /opt/openvpn-install.sh client renew testclient --cert-days 3650) 2>&1 | tee "$RENEW_OUTPUT" || true
|
||||
|
||||
# Verify renewal succeeded
|
||||
if grep -q "Certificate for client testclient renewed" "$RENEW_OUTPUT"; then
|
||||
@@ -276,7 +262,7 @@ echo "Original server certificate serial: $ORIG_SERVER_SERIAL"
|
||||
# Test server certificate renewal
|
||||
echo "Testing server certificate renewal..."
|
||||
RENEW_SERVER_OUTPUT="/tmp/renew-server-output.log"
|
||||
(MENU_OPTION=4 RENEW_OPTION=2 CONTINUE=y SERVER_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$RENEW_SERVER_OUTPUT" || true
|
||||
(bash /opt/openvpn-install.sh server renew --cert-days 3650 --force) 2>&1 | tee "$RENEW_SERVER_OUTPUT" || true
|
||||
|
||||
# Verify renewal succeeded
|
||||
if grep -q "Server certificate renewed successfully" "$RENEW_SERVER_OUTPUT"; then
|
||||
@@ -545,7 +531,7 @@ echo "=== Testing Certificate Revocation ==="
|
||||
REVOKE_CLIENT="revoketest"
|
||||
echo "Creating client '$REVOKE_CLIENT' for revocation testing..."
|
||||
REVOKE_CREATE_OUTPUT="/tmp/revoke-create-output.log"
|
||||
(MENU_OPTION=1 CLIENT=$REVOKE_CLIENT PASS=1 CLIENT_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$REVOKE_CREATE_OUTPUT" || true
|
||||
(bash /opt/openvpn-install.sh client add "$REVOKE_CLIENT" --cert-days 3650) 2>&1 | tee "$REVOKE_CREATE_OUTPUT" || true
|
||||
|
||||
if [ -f "/root/$REVOKE_CLIENT.ovpn" ]; then
|
||||
echo "PASS: Client '$REVOKE_CLIENT' created successfully"
|
||||
@@ -579,6 +565,42 @@ if [ ! -f /shared/revoke-client-connected ]; then
|
||||
fi
|
||||
echo "PASS: Client connected with '$REVOKE_CLIENT' certificate"
|
||||
|
||||
# =====================================================
|
||||
# Test server status command
|
||||
# =====================================================
|
||||
echo ""
|
||||
echo "=== Testing Server Status ==="
|
||||
|
||||
# Note: OpenVPN status file updates periodically (default: 1 min)
|
||||
# so we just verify the command works, not that a specific client is visible
|
||||
|
||||
# Test table output
|
||||
STATUS_OUTPUT="/tmp/server-status-output.log"
|
||||
(bash /opt/openvpn-install.sh server status) 2>&1 | tee "$STATUS_OUTPUT" || true
|
||||
|
||||
if grep -q "Connected Clients" "$STATUS_OUTPUT"; then
|
||||
echo "PASS: Server status shows header"
|
||||
else
|
||||
echo "FAIL: Server status missing header"
|
||||
cat "$STATUS_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test JSON output
|
||||
STATUS_JSON_OUTPUT="/tmp/server-status-json-output.log"
|
||||
(bash /opt/openvpn-install.sh server status --format json) 2>&1 | tee "$STATUS_JSON_OUTPUT" || true
|
||||
|
||||
# Validate JSON structure (clients array exists, even if empty)
|
||||
if jq -e '.clients' "$STATUS_JSON_OUTPUT" >/dev/null 2>&1; then
|
||||
echo "PASS: Server status JSON is valid"
|
||||
else
|
||||
echo "FAIL: Server status JSON is invalid"
|
||||
cat "$STATUS_JSON_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Server Status Tests PASSED ==="
|
||||
|
||||
# Signal client to disconnect before revocation
|
||||
touch /shared/revoke-client-disconnect
|
||||
|
||||
@@ -597,11 +619,10 @@ if [ ! -f /shared/revoke-client-disconnected ]; then
|
||||
fi
|
||||
echo "Client disconnected"
|
||||
|
||||
# Now revoke the certificate using the new CLIENT name feature
|
||||
# Now revoke the certificate
|
||||
echo "Revoking certificate for '$REVOKE_CLIENT'..."
|
||||
REVOKE_OUTPUT="/tmp/revoke-output.log"
|
||||
# MENU_OPTION=3 is revoke, CLIENT specifies the client name directly
|
||||
(MENU_OPTION=3 CLIENT=$REVOKE_CLIENT bash /opt/openvpn-install.sh) 2>&1 | tee "$REVOKE_OUTPUT" || true
|
||||
(bash /opt/openvpn-install.sh client revoke "$REVOKE_CLIENT" --force) 2>&1 | tee "$REVOKE_OUTPUT" || true
|
||||
|
||||
if grep -q "Certificate for client $REVOKE_CLIENT revoked" "$REVOKE_OUTPUT"; then
|
||||
echo "PASS: Certificate for '$REVOKE_CLIENT' revoked successfully"
|
||||
@@ -652,7 +673,7 @@ echo "=== Testing List Client Certificates ==="
|
||||
# - testclient (Revoked) - the old certificate revoked during renewal
|
||||
# - revoketest (Revoked) - the revoked certificate
|
||||
LIST_OUTPUT="/tmp/list-clients-output.log"
|
||||
(MENU_OPTION=2 bash /opt/openvpn-install.sh) 2>&1 | tee "$LIST_OUTPUT" || true
|
||||
(bash /opt/openvpn-install.sh client list) 2>&1 | tee "$LIST_OUTPUT" || true
|
||||
|
||||
# Verify list output contains expected clients
|
||||
if grep -q "testclient" "$LIST_OUTPUT" && grep -q "Valid" "$LIST_OUTPUT"; then
|
||||
@@ -680,6 +701,48 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test JSON output
|
||||
echo "Testing client list JSON output..."
|
||||
LIST_JSON_OUTPUT="/tmp/list-clients-json-output.log"
|
||||
(bash /opt/openvpn-install.sh client list --format json) 2>&1 | tee "$LIST_JSON_OUTPUT" || true
|
||||
|
||||
# Validate JSON structure
|
||||
if jq -e '.clients' "$LIST_JSON_OUTPUT" >/dev/null 2>&1; then
|
||||
echo "PASS: Client list JSON is valid"
|
||||
else
|
||||
echo "FAIL: Client list JSON is invalid"
|
||||
cat "$LIST_JSON_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify client count in JSON
|
||||
JSON_CLIENT_COUNT=$(jq '.clients | length' "$LIST_JSON_OUTPUT")
|
||||
if [ "$JSON_CLIENT_COUNT" -eq 3 ]; then
|
||||
echo "PASS: Client list JSON has correct count ($JSON_CLIENT_COUNT)"
|
||||
else
|
||||
echo "FAIL: Client list JSON has wrong count: $JSON_CLIENT_COUNT (expected 3)"
|
||||
cat "$LIST_JSON_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify valid client in JSON
|
||||
if jq -e '.clients[] | select(.name == "testclient" and .status == "valid")' "$LIST_JSON_OUTPUT" >/dev/null 2>&1; then
|
||||
echo "PASS: Client list JSON shows testclient as valid"
|
||||
else
|
||||
echo "FAIL: Client list JSON does not show testclient correctly"
|
||||
cat "$LIST_JSON_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify revoked client in JSON
|
||||
if jq -e ".clients[] | select(.name == \"$REVOKE_CLIENT\" and .status == \"revoked\")" "$LIST_JSON_OUTPUT" >/dev/null 2>&1; then
|
||||
echo "PASS: Client list JSON shows $REVOKE_CLIENT as revoked"
|
||||
else
|
||||
echo "FAIL: Client list JSON does not show $REVOKE_CLIENT correctly"
|
||||
cat "$LIST_JSON_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== List Client Certificates Tests PASSED ==="
|
||||
|
||||
# =====================================================
|
||||
@@ -691,7 +754,7 @@ echo "=== Testing Reuse of Revoked Client Name ==="
|
||||
# Create a new certificate with the same name as the revoked one
|
||||
echo "Creating new client with same name '$REVOKE_CLIENT'..."
|
||||
RECREATE_OUTPUT="/tmp/recreate-output.log"
|
||||
(MENU_OPTION=1 CLIENT=$REVOKE_CLIENT PASS=1 CLIENT_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$RECREATE_OUTPUT" || true
|
||||
(bash /opt/openvpn-install.sh client add "$REVOKE_CLIENT" --cert-days 3650) 2>&1 | tee "$RECREATE_OUTPUT" || true
|
||||
|
||||
if [ -f "/root/$REVOKE_CLIENT.ovpn" ]; then
|
||||
echo "PASS: New client '$REVOKE_CLIENT' created successfully (reusing revoked name)"
|
||||
@@ -758,7 +821,7 @@ PASSPHRASE_CLIENT="passphrasetest"
|
||||
TEST_PASSPHRASE="TestP@ssw0rd#123"
|
||||
echo "Creating client '$PASSPHRASE_CLIENT' with passphrase in headless mode..."
|
||||
PASSPHRASE_OUTPUT="/tmp/passphrase-output.log"
|
||||
(MENU_OPTION=1 CLIENT=$PASSPHRASE_CLIENT PASS=2 PASSPHRASE="$TEST_PASSPHRASE" CLIENT_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$PASSPHRASE_OUTPUT" || true
|
||||
(bash /opt/openvpn-install.sh client add "$PASSPHRASE_CLIENT" --password "$TEST_PASSPHRASE" --cert-days 3650) 2>&1 | tee "$PASSPHRASE_OUTPUT" || true
|
||||
|
||||
# Verify client was created
|
||||
if [ -f "/root/$PASSPHRASE_CLIENT.ovpn" ]; then
|
||||
|
||||
Reference in New Issue
Block a user