Ctrl K
ComunidadGuías…

run.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/bash
#
# Docker script to configure and start an OpenVPN server
#
# DO NOT RUN THIS SCRIPT ON YOUR PC OR MAC! THIS IS ONLY MEANT TO BE RUN
# IN A CONTAINER!
#
# This file is part of OpenVPN Docker image, available at:
# https://github.com/hwdsl2/docker-openvpn
#
# Copyright (C) 2026 Lin Song <linsongui@gmail.com>
# Copyright (C) 2013 Nyr
#
# Based on the work of Nyr and contributors at:
# https://github.com/Nyr/openvpn-install
#
# This work is licensed under the MIT License
# See: https://opensource.org/licenses/MIT

export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

exiterr()  { echo "Error: $1" >&2; exit 1; }
nospaces() { printf '%s' "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'; }
noquotes() { printf '%s' "$1" | sed -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'$/\1/"; }
easyrsa_run() { echo "+ easyrsa $*" >&2; easyrsa "$@" >/dev/null 2>&1; }

check_ip() {
  IP_REGEX='^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'
  printf '%s' "$1" | tr -d '\n' | grep -Eq "$IP_REGEX"
}

get_public_ip() {
  local ip_addr ip_url
  for ip_url in https://ipv4.icanhazip.com https://api.ipify.org; do
    ip_addr=$(wget -t 2 -T 10 -4 --max-redirect=0 -qO- "$ip_url" 2>/dev/null) || continue
    ip_addr=${ip_addr%$'\r'}
    [[ "$ip_addr" != *$'\n'* && "$ip_addr" != *$'\r'* ]] || continue
    if check_ip "$ip_addr"; then
      public_ip="$ip_addr"
      return 0
    fi
  done
  return 1
}

check_ip6() {
  IP6_REGEX='^[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{0,4}){1,7}$'
  printf '%s' "$1" | tr -d '\n' | grep -Eq "$IP6_REGEX"
}

check_dns_name() {
  FQDN_REGEX='^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
  printf '%s' "$1" | tr -d '\n' | grep -Eq "$FQDN_REGEX"
}

usage_arch() {
  local arch
  arch=$(uname -m 2>/dev/null || printf 'unknown')
  case "$arch" in
    x86_64|amd64) printf 'amd64' ;;
    aarch64|arm64) printf 'arm64' ;;
    armv7l|armv7|armhf) printf 'armv7' ;;
    *) printf 'other' ;;
  esac
}

write_usage_state() {
  local state_file version tmp_file
  state_file=$1
  version=$2
  mkdir -p "$USAGE_STATE_DIR" || return 1
  tmp_file=$(mktemp "$USAGE_STATE_DIR/.usage.XXXXXX") || return 1
  printf '%s\n' "$version" > "$tmp_file" || return 1
  chmod 0644 "$tmp_file" 2>/dev/null || true
  mv "$tmp_file" "$state_file"
}

fetch_usage_asset() {
  local asset base_url
  asset=$1
  base_url=${VPN_USAGE_BASE_URL:-https://github.com/hwdsl2/vpn-extras/releases/download/v1.1.0}
  base_url=${base_url%/}
  command -v wget >/dev/null 2>&1 || return 1
  wget -q -T 5 -O /dev/null "$base_url/$asset" >/dev/null 2>&1
}

read_state_value() {
  [ -r "$1" ] || return 0
  tr -d '[:space:]' < "$1"
}

report_usage_counts() {
  local current_version arch state_file last_version action

  [ "${VPN_DISABLE_USAGE_COUNTS:-0}" != "1" ] || return 0
  $data_mounted || return 0

  current_version="${IMAGE_FLAVOR:-unknown}-${IMAGE_VER:-unknown}"
  arch=$(usage_arch)
  state_file="$USAGE_STATE_DIR/openvpn.version"
  last_version=$(read_state_value "$state_file")
  action=

  if [ -z "$last_version" ]; then
    if $data_existing; then
      action=upgrade
    else
      action=deploy
    fi
  elif [ "$last_version" != "$current_version" ]; then
    action=upgrade
  fi

  if [ -n "$action" ]; then
    if fetch_usage_asset "vpn-v1-openvpn-$action-$arch"; then
      write_usage_state "$state_file" "$current_version" || true
    fi
  fi
}

# Source bind-mounted env file if present (takes precedence over --env-file)
if [ -f /vpn.env ]; then
  # shellcheck disable=SC1091
  . /vpn.env
fi

if [ ! -f "/.dockerenv" ] && [ ! -f "/run/.containerenv" ] \
  && [ -z "$KUBERNETES_SERVICE_HOST" ] \
  && ! head -n 1 /proc/1/sched 2>/dev/null | grep -q '^run\.sh '; then
  exiterr "This script ONLY runs in a container (e.g. Docker, Podman)."
fi

if [ ! -e /dev/net/tun ] || ! ( exec 7<>/dev/net/tun ) 2>/dev/null; then
  exiterr "The TUN device is not available. Add '--device /dev/net/tun' to your docker run command."
fi

NET_IFACE=$(route 2>/dev/null | grep -m 1 '^default' | grep -o '[^ ]*$')
[ -z "$NET_IFACE" ] && NET_IFACE=$(ip -4 route list 0/0 2>/dev/null | grep -m 1 -o 'dev [^ ]*' | awk '{print $2}')
[ -z "$NET_IFACE" ] && NET_IFACE=eth0

# Read and sanitize environment variables
VPN_DNS_NAME=$(nospaces "$VPN_DNS_NAME")
VPN_DNS_NAME=$(noquotes "$VPN_DNS_NAME")
VPN_PUBLIC_IP=$(nospaces "$VPN_PUBLIC_IP")
VPN_PUBLIC_IP=$(noquotes "$VPN_PUBLIC_IP")
VPN_PROTO=$(nospaces "$VPN_PROTO")
VPN_PROTO=$(noquotes "$VPN_PROTO")
VPN_PORT=$(nospaces "$VPN_PORT")
VPN_PORT=$(noquotes "$VPN_PORT")
VPN_CLIENT_NAME=$(nospaces "$VPN_CLIENT_NAME")
VPN_CLIENT_NAME=$(noquotes "$VPN_CLIENT_NAME")
VPN_DNS_SRV1=$(nospaces "$VPN_DNS_SRV1")
VPN_DNS_SRV1=$(noquotes "$VPN_DNS_SRV1")
VPN_DNS_SRV2=$(nospaces "$VPN_DNS_SRV2")
VPN_DNS_SRV2=$(noquotes "$VPN_DNS_SRV2")
if [ -n "$VPN_PUBLIC_IP6" ]; then
  VPN_PUBLIC_IP6=$(nospaces "$VPN_PUBLIC_IP6")
  VPN_PUBLIC_IP6=$(noquotes "$VPN_PUBLIC_IP6")
fi

# Apply defaults
[ -z "$VPN_PROTO" ]       && VPN_PROTO=udp
[ -z "$VPN_PORT" ]        && VPN_PORT=1194
[ -z "$VPN_CLIENT_NAME" ] && VPN_CLIENT_NAME=client
[ -z "$VPN_DNS_SRV1" ]    && VPN_DNS_SRV1=8.8.8.8
[ -z "$VPN_DNS_SRV2" ]    && VPN_DNS_SRV2=8.8.4.4

# Validate protocol
case "$VPN_PROTO" in
  tcp|udp) ;;
  *) exiterr "VPN_PROTO must be 'tcp' or 'udp'. Got: '$VPN_PROTO'." ;;
esac

# Validate port
if ! printf '%s' "$VPN_PORT" | grep -Eq '^[0-9]+$' \
  || [ "$VPN_PORT" -lt 1 ] || [ "$VPN_PORT" -gt 65535 ]; then
  exiterr "VPN_PORT must be an integer between 1 and 65535."
fi

# Sanitize and validate client name
VPN_CLIENT_NAME=$(printf '%s' "$VPN_CLIENT_NAME" | \
  sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g')
if [ -z "$VPN_CLIENT_NAME" ]; then
  exiterr "VPN_CLIENT_NAME is invalid. Use one word only, no special characters except '-' and '_'."
fi

# Validate DNS servers
if [ -n "$VPN_DNS_SRV1" ] && ! check_ip "$VPN_DNS_SRV1"; then
  exiterr "VPN_DNS_SRV1 '$VPN_DNS_SRV1' is not a valid IPv4 address."
fi
if [ -n "$VPN_DNS_SRV2" ] && ! check_ip "$VPN_DNS_SRV2"; then
  exiterr "VPN_DNS_SRV2 '$VPN_DNS_SRV2' is not a valid IPv4 address."
fi

# Determine server address for client configurations
if [ -n "$VPN_DNS_NAME" ]; then
  if ! check_dns_name "$VPN_DNS_NAME"; then
    exiterr "VPN_DNS_NAME '$VPN_DNS_NAME' is not a valid fully qualified domain name (FQDN)."
  fi
  server_addr="$VPN_DNS_NAME"
elif [ -n "$VPN_PUBLIC_IP" ]; then
  if ! check_ip "$VPN_PUBLIC_IP"; then
    exiterr "VPN_PUBLIC_IP '$VPN_PUBLIC_IP' is not a valid IPv4 address."
  fi
  server_addr="$VPN_PUBLIC_IP"
else
  echo
  echo "Trying to auto-detect public IP of this server..."
  if ! get_public_ip; then
    exiterr "Cannot detect this server's public IP. Define it in your 'env' file as 'VPN_PUBLIC_IP'."
  fi
  server_addr="$public_ip"
fi

# Detect IPv6
ip6=""
if [ -n "$VPN_PUBLIC_IP6" ]; then
  ip6="$VPN_PUBLIC_IP6"
  check_ip6 "$ip6" || { echo "Warning: Invalid IPv6 address in 'VPN_PUBLIC_IP6'. Detecting IPv6..." >&2; ip6=""; }
fi
if [ -z "$ip6" ]; then
  ip6=$(ip -6 addr 2>/dev/null | awk '/inet6 [23]/ {print $2}' | cut -d'/' -f1 | head -n1)
  check_ip6 "$ip6" || ip6=""
  if [ -z "$ip6" ] && ip -6 addr 2>/dev/null | grep 'inet6' | grep -qv 'inet6 \(::1\|fe80\)'; then
    ip6=$(wget -t 2 -T 10 -qO- https://ipv6.icanhazip.com 2>/dev/null)
    check_ip6 "$ip6" || ip6=""
  fi
fi

OVPN_CONF="/etc/openvpn/server/server.conf"
USAGE_STATE_DIR="/etc/openvpn/.openvpn-usage"
data_mounted=false
data_existing=false
if grep -q " /etc/openvpn " /proc/mounts 2>/dev/null; then
  data_mounted=true
fi
if $data_mounted && [ -s "$OVPN_CONF" ]; then
  data_existing=true
fi

mkdir -p /etc/openvpn/server/easy-rsa
mkdir -p /etc/openvpn/clients

echo
echo "OpenVPN Docker - https://github.com/hwdsl2/docker-openvpn"

if ! grep -q " /etc/openvpn " /proc/mounts; then
  echo
  echo "Note: /etc/openvpn is not mounted. Server data (keys, certificates"
  echo "      and client configs) will be lost on container removal."
  echo "      Mount a Docker volume at /etc/openvpn to persist data."
fi

if [ ! -f "$OVPN_CONF" ]; then
  echo
  echo "Starting OpenVPN setup..."
  echo "Server address: $server_addr"
  echo "Protocol: $VPN_PROTO, Port: $VPN_PORT"
  echo "First client: $VPN_CLIENT_NAME"
  echo "DNS servers: $VPN_DNS_SRV1, $VPN_DNS_SRV2"
  if [ -n "$ip6" ]; then
    echo "IPv6: enabled"
  fi
  echo

  echo "Initializing PKI..."
  export EASYRSA_PKI="/etc/openvpn/server/easy-rsa/pki"
  easyrsa_run --batch init-pki
  easyrsa_run --batch build-ca nopass
  easyrsa_run --batch --days=3650 build-server-full server nopass
  easyrsa_run --batch --days=3650 build-client-full "$VPN_CLIENT_NAME" nopass
  easyrsa_run --batch --days=3650 gen-crl

  # Copy certs to server directory
  cp /etc/openvpn/server/easy-rsa/pki/ca.crt \
     /etc/openvpn/server/easy-rsa/pki/private/ca.key \
     /etc/openvpn/server/easy-rsa/pki/issued/server.crt \
     /etc/openvpn/server/easy-rsa/pki/private/server.key \
     /etc/openvpn/server/easy-rsa/pki/crl.pem \
     /etc/openvpn/server/
  # CRL is read with each client connection (OpenVPN drops to nobody)
  chown nobody:nobody /etc/openvpn/server/crl.pem
  # Without +x on the directory, OpenVPN can't run stat() on the CRL file
  chmod o+x /etc/openvpn/server/

  # Generate TLS crypt key
  openvpn --genkey secret /etc/openvpn/server/tc.key

  # Use pre-defined ffdhe2048 DH parameters (RFC 7919)
  cat > /etc/openvpn/server/dh.pem <<'DHEOF'
-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
-----END DH PARAMETERS-----
DHEOF

  # Generate server configuration
  cat > /etc/openvpn/server/server.conf <<EOF
port $VPN_PORT
proto $VPN_PROTO
dev tun
ca ca.crt
cert server.crt
key server.key
dh dh.pem
auth SHA256
tls-crypt tc.key
topology subnet
server 10.8.0.0 255.255.255.0
EOF
  if [ -n "$ip6" ]; then
    cat >> /etc/openvpn/server/server.conf <<EOF
server-ipv6 fddd:1194:1194:1194::/64
push "redirect-gateway def1 ipv6 bypass-dhcp"
EOF
  else
    cat >> /etc/openvpn/server/server.conf <<'EOF'
push "redirect-gateway def1 bypass-dhcp"
push "block-ipv6"
push "ifconfig-ipv6 fddd:1194:1194:1194::2/64 fddd:1194:1194:1194::1"
EOF
  fi
  cat >> /etc/openvpn/server/server.conf <<EOF
push "dhcp-option DNS $VPN_DNS_SRV1"
push "dhcp-option DNS $VPN_DNS_SRV2"
push "block-outside-dns"
ifconfig-pool-persist ipp.txt
keepalive 10 120
cipher AES-128-GCM
user nobody
group nobody
persist-key
persist-tun
verb 3
crl-verify crl.pem
EOF
  if [ "$VPN_PROTO" = "udp" ]; then
    echo "explicit-exit-notify" >> /etc/openvpn/server/server.conf
  fi

  # Generate client config template
  cat > /etc/openvpn/server/client-common.txt <<EOF
client
dev tun
proto $VPN_PROTO
remote $server_addr $VPN_PORT
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
auth SHA256
cipher AES-128-GCM
ignore-unknown-option block-outside-dns block-ipv6
verb 3
EOF

  # Generate first client .ovpn file
  {
    cat /etc/openvpn/server/client-common.txt
    echo "<ca>"
    cat /etc/openvpn/server/easy-rsa/pki/ca.crt
    echo "</ca>"
    echo "<cert>"
    sed -ne '/BEGIN CERTIFICATE/,$ p' \
      /etc/openvpn/server/easy-rsa/pki/issued/"$VPN_CLIENT_NAME".crt
    echo "</cert>"
    echo "<key>"
    cat /etc/openvpn/server/easy-rsa/pki/private/"$VPN_CLIENT_NAME".key
    echo "</key>"
    echo "<tls-crypt>"
    sed -ne '/BEGIN OpenVPN Static key/,$ p' /etc/openvpn/server/tc.key
    echo "</tls-crypt>"
  } > /etc/openvpn/clients/"$VPN_CLIENT_NAME".ovpn
  chmod 600 /etc/openvpn/clients/"$VPN_CLIENT_NAME".ovpn

  echo
  echo "Setup complete."
  echo "Client configuration: /etc/openvpn/clients/$VPN_CLIENT_NAME.ovpn"
  echo "Use 'docker cp <container>:/etc/openvpn/clients/$VPN_CLIENT_NAME.ovpn .' to download it."
  echo "Use 'docker exec <container> ovpn_manage --addclient <name>' to add more clients."
  echo
else
  echo
  echo "Found existing OpenVPN configuration, starting server..."
  echo
  # Refresh CRL symlink ownership in case volume was remounted
  chown nobody:nobody /etc/openvpn/server/crl.pem 2>/dev/null || true
  chmod o+x /etc/openvpn/server/ 2>/dev/null || true
fi

# Update sysctl settings
syt='/sbin/sysctl -e -q -w'
$syt net.ipv4.ip_forward=1 2>/dev/null
$syt net.ipv4.conf.all.accept_redirects=0 2>/dev/null
$syt net.ipv4.conf.all.send_redirects=0 2>/dev/null
$syt net.ipv4.conf.all.rp_filter=0 2>/dev/null
$syt net.ipv4.conf.default.accept_redirects=0 2>/dev/null
$syt net.ipv4.conf.default.send_redirects=0 2>/dev/null
$syt net.ipv4.conf.default.rp_filter=0 2>/dev/null
$syt "net.ipv4.conf.$NET_IFACE.send_redirects=0" 2>/dev/null
$syt "net.ipv4.conf.$NET_IFACE.rp_filter=0" 2>/dev/null
if [ -n "$ip6" ]; then
  $syt net.ipv6.conf.all.forwarding=1 2>/dev/null
fi

# Set up iptables rules
modprobe -q ip_tables 2>/dev/null
if ! iptables -t nat -C POSTROUTING -s 10.8.0.0/24 -o "$NET_IFACE" -j MASQUERADE 2>/dev/null; then
  iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o "$NET_IFACE" -j MASQUERADE
  iptables -I INPUT -p "$VPN_PROTO" --dport "$VPN_PORT" -j ACCEPT
  iptables -I FORWARD -s 10.8.0.0/24 -j ACCEPT
  iptables -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
fi

if [ -n "$ip6" ]; then
  modprobe -q ip6_tables 2>/dev/null
  if ip6tables -t nat -L >/dev/null 2>&1; then
    if grep -qs "server-ipv6" "$OVPN_CONF" && \
       ! ip6tables -t nat -C POSTROUTING -s fddd:1194:1194:1194::/64 \
           -o "$NET_IFACE" -j MASQUERADE 2>/dev/null; then
      ip6tables -t nat -A POSTROUTING -s fddd:1194:1194:1194::/64 -o "$NET_IFACE" -j MASQUERADE
      ip6tables -I FORWARD -s fddd:1194:1194:1194::/64 -j ACCEPT
      ip6tables -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
    fi
  fi
fi

echo "Starting OpenVPN server..."
echo
report_usage_counts
exec openvpn --cd /etc/openvpn/server --config server.conf