Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
turnkey-openvpn-19.0 (1) turnkey; urgency=low

* Install OpenVPN from Debian repos - v2.6.14.

-- Stefan Davis <nafets.sivad@gmail.com> Fri, 26 Jun 2026 05:05:23 +0000

turnkey-openvpn-18.1 (1) turnkey; urgency=low

* v18.1 rebuild - includes latest Debian & TurnKey packages.
Expand Down
2 changes: 1 addition & 1 deletion conf.d/main
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ OPENSSL_CONF=$EASY_RSA/openssl-easyrsa.cnf
SRC=/usr/local/src

# enable ip forwarding
sed -i "/^#net.ipv4.ip_forward=1/ s/#//" /etc/sysctl.conf
echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.d/40-openvpn.conf

mkdir -p $EASY_RSA
cp -ur /usr/share/easy-rsa/* $EASY_RSA
Expand Down
3 changes: 3 additions & 0 deletions overlay/usr/lib/inithooks/bin/openvpn-server-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,7 @@ verb 4
# the server will be configured with x.x.x.1
# important: must not be used on your network
server $(expand_cidr "$virtual_subnet")

cipher AES-256-GCM
auth SHA512
EOF
86 changes: 41 additions & 45 deletions overlay/usr/lib/inithooks/bin/openvpn.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@
Note: options not specified but required by profile will be asked interactively
"""

import getopt
import os
from os.path import exists
import subprocess
import sys
import getopt
from libinithooks import inithooks_cache
from os.path import exists
from random import randint as r

from libinithooks import is_interactive, warn, info
from libinithooks import info, inithooks_cache, is_interactive, warn
from libinithooks.dialog_wrapper import Dialog
import subprocess


def fatal(e):
print("Error:", e, file=sys.stderr)
Expand All @@ -39,29 +39,29 @@ def fatal(e):
def usage(e=None):
if e:
print("Error:", e, file=sys.stderr)
print("Syntax: %s [options]" % sys.argv[0], file=sys.stderr)
print(f"Syntax: {sys.argv[0]} [options]", file=sys.stderr)
print(__doc__, file=sys.stderr)
sys.exit(1)

def expand_cidr(cidr):
network, bitcount = cidr.split('/')
network, bitcount = cidr.split("/")
# turn /<bitcount> into a 32-long bit array
bits = ('1' * int(bitcount)).ljust(32, '0')
bits = ("1" * int(bitcount)).ljust(32, "0")
# split the bit array into 4 bytes
bytes_list = [
int(bits[0:8], 2),
int(bits[8:16], 2),
int(bits[16:24], 2),
int(bits[24:32], 2),
]

return "{} {}.{}.{}.{}".format(network, *bytes_list)

def main():
try:
opts, args = getopt.gnu_getopt(sys.argv[1:], "h",
['help', 'profile=', 'key-email=', 'public-address=', 'virtual-subnet=',
'private-subnet='])
["help", "profile=", "key-email=", "public-address=", "virtual-subnet=",
"private-subnet="])
except getopt.GetoptError as e:
usage(e)

Expand All @@ -70,50 +70,49 @@ def main():
public_address = ""
virtual_subnet = ""
private_subnet = ""
redirect_client_gateway = ""
for opt, val in opts:
if opt in ('-h', '--help'):
if opt in ("-h", "--help"):
usage()
elif opt == '--profile':
elif opt == "--profile":
profile = val
elif opt == '--key-email':
elif opt == "--key-email":
key_email = val
elif opt == '--public-address':
elif opt == "--public-address":
public_address = val
elif opt == '--virtual-subnet':
elif opt == "--virtual-subnet":
virtual_subnet = val
elif opt == '--private-subnet':
elif opt == "--private-subnet":
private_subnet = val

dialog = Dialog('TurnKey Linux - First boot configuration')
dialog = Dialog("TurnKey Linux - First boot configuration")

tun_exists = exists('/dev/net/tun')
tun_exists = exists("/dev/net/tun")
if not tun_exists:
if is_interactive:
dialog.msgbox('Tun device not created', '''
dialog.msgbox("Tun device not created", """
Failed to create `/dev/net/tun` device on boot, this is expected when running inside a non-privileged container.

If you are running on an unprivileged container, you will need to create this device on the host.''')
If you are running on an unprivileged container, you will need to create this device on the host.""")
else:
warn('Failed to create `/dev/net/tun` device on boot, this is expected when '
+ 'running inside a non-privileged container. If you are '
+ 'running on an unprivileged container, you will need to '
+ 'create this device on the host.')
warn("Failed to create `/dev/net/tun` device on boot, this is expected when "
"running inside a non-privileged container. If you are "
"running on an unprivileged container, you will need to "
"create this device on the host.")
else:
info('/dev/net/tun created successfully')
info("/dev/net/tun created successfully")

if not profile:
profile = dialog.menu(
"OpenVPN Profile",
"Choose a profile for this server.\n\n* Gateway: clients will be configured to route all\n their traffic through the VPN.",
[
('server', 'Accept VPN connections from clients'),
('gateway', 'Accept VPN connections from clients*'),
('client', 'Initiate VPN connections to a server')
("server", "Accept VPN connections from clients"),
("gateway", "Accept VPN connections from clients*"),
("client", "Initiate VPN connections to a server"),
])

if not profile in ('server', 'gateway', 'client'):
fatal('invalid profile: %s' % profile)
if profile not in ("server", "gateway", "client"):
fatal(f"invalid profile: {profile}")

if profile == "client":
return
Expand All @@ -124,15 +123,15 @@ def main():
"Enter email address for the OpenVPN server key.",
"admin@example.com")

inithooks_cache.write('APP_EMAIL', key_email)
inithooks_cache.write("APP_EMAIL", key_email)

if not public_address:
public_address = dialog.get_input(
"OpenVPN Public Address",
"Enter FQDN or IP address of server reachable by clients",
"vpn.example.com")

auto_virtual_subnet = "10.%d.%d.0/24" % (r(2, 254), r(2, 254))
auto_virtual_subnet = f"10.{r(2, 254)}.{r(2, 254)}.0/24"
if not virtual_subnet:
virtual_subnet = dialog.get_input(
"OpenVPN Virtual Subnet",
Expand All @@ -152,22 +151,19 @@ def main():
if private_subnet.upper() == "SKIP":
private_subnet = ""

cmd = os.path.join(os.path.dirname(__file__), 'openvpn-server-init.sh')
cmd = os.path.join(os.path.dirname(__file__), "openvpn-server-init.sh")
subprocess.run([cmd, key_email, public_address, virtual_subnet])

if profile == "gateway":
fh = open("/etc/openvpn/server.conf", "a")
fh.write("# configure clients to route all their traffic through the vpn\n")
fh.write("push \"redirect-gateway def1 bypass-dhcp\"\n\n")
fh.close()
with open("/etc/openvpn/server.conf", "a") as fob:
fob.write("# configure clients to route all their traffic through the vpn\n")
fob.write('push "redirect-gateway def1 bypass-dhcp"\n\n')

if private_subnet:
fh = open("/etc/openvpn/server.conf", "a")
fh.write("# push routes to clients to allow them to reach private subnets\n")
for _private_subnet in private_subnet.split(',') :
fh.write("push \"route %s\"\n" % expand_cidr(_private_subnet))
fh.close()
subprocess.run(['systemctl', 'start', 'openvpn@server'])
with open("/etc/openvpn/server.conf", "a") as fob:
fob.write("# push routes to clients to allow them to reach private subnets\n")
fob.writelines(f'push "route {expand_cidr(_private_subnet)}"\n' for _private_subnet in private_subnet.split(","))
subprocess.run(["systemctl", "start", "openvpn@server"])

if __name__ == "__main__":
main()
Expand Down