erplibre/script/install/install_vpn.sh

223 lines
7.7 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# © 2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
#
# Paquets client VPN, par pilote de script/vpn/drivers/.
#
# sudo bash script/install/install_vpn.sh l2tp_ipsec
#
# Un groupe de paquets par pilote : le nom passé en argument est le `name` du
# pilote, et non un nom de paquet. C'est le CLI (script/vpn/vpn.py install)
# qui appelle ce script, et il ne connaît que les noms de pilotes.
#
# Ce script INSTALLE et ne configure rien : la configuration est rendue au
# montage du tunnel, dans un tmpfs, par le pilote. Il désactive tout de même
# le démarrage automatique des services — un strongSwan ou un xl2tpd lancé au
# boot tiendrait UDP 500/1701 et empêcherait l'instance dédiée au profil de
# s'attacher.
set -euo pipefail
log() { echo "[VPN] $*"; }
die() { echo "[VPN] ERREUR: $*" >&2; exit 1; }
usage() {
cat <<'USAGE'
Usage : sudo bash script/install/install_vpn.sh <pilote>
Pilotes connus :
l2tp_ipsec strongSwan + xl2tpd + pppd (L2TP/IPsec à clé pré-partagée)
wireguard wireguard-tools
openvpn openvpn
openconnect openconnect + vpnc-scripts
sshuttle sshuttle (sur RHEL/Rocky/Alma : dépôt EPEL requis)
Sans argument : tous les pilotes.
USAGE
}
check_root() {
[ "$(id -u)" -eq 0 ] || die "à lancer en root : sudo bash $0 $*"
}
detect_os() {
[ -f /etc/os-release ] || die "OS indéterminable (pas de /etc/os-release)"
# shellcheck disable=SC1091
. /etc/os-release
OS="${ID}"
OS_LIKE="${ID_LIKE:-}"
log "OS détecté : ${OS}"
}
family() {
case "$OS" in
ubuntu|debian|linuxmint|pop|elementary|raspbian) echo debian; return ;;
arch|manjaro|endeavouros|artix|garuda) echo arch; return ;;
fedora|rhel|centos|almalinux|rocky) echo rhel; return ;;
opensuse*|sles|sled) echo suse; return ;;
esac
case "$OS_LIKE" in
*debian*|*ubuntu*) echo debian; return ;;
*arch*) echo arch; return ;;
*rhel*|*fedora*) echo rhel; return ;;
*suse*) echo suse; return ;;
esac
die "famille de distribution inconnue : ${OS} (ID_LIKE=${OS_LIKE})"
}
# Les paquets, par pilote puis par famille. `strongswan-starter` fournit la
# commande `ipsec` et le démon starter, que le pilote L2TP utilise ; les
# paquets `charon-systemd`/`swanctl` seuls ne la fournissent PAS.
packages_for() {
local driver="$1" fam="$2"
case "${driver}:${fam}" in
# libstrongswan-standard-plugins apporte le greffon openssl, et
# avec lui 3DES. Sans ce paquet, charon ANNONCE 3DES, le
# concentrateur le choisit — c'est souvent le seul qu'il connaisse —
# et la négociation meurt sur « ENCRYPTION_ALGORITHM 3DES_CBC not
# supported! ». Mesuré sur Ubuntu 24.04 : greffons chargés sans lui,
# « aes md5 rc2 sha1 », donc pas de 3DES.
l2tp_ipsec:debian)
echo "strongswan strongswan-starter libstrongswan-standard-plugins libcharon-extra-plugins xl2tpd ppp" ;;
l2tp_ipsec:arch) echo "strongswan xl2tpd ppp" ;;
l2tp_ipsec:rhel) echo "strongswan xl2tpd ppp" ;;
l2tp_ipsec:suse) echo "strongswan xl2tpd ppp" ;;
# wireguard-tools fournit wg ET wg-quick. Le module noyau est dans
# Linux depuis 5.6 : rien à compiler sur les distributions visées.
wireguard:*) echo "wireguard-tools" ;;
openvpn:*) echo "openvpn" ;;
# vpnc-scripts porte le script que openconnect appelle pour poser
# les routes et le DNS. Sans lui, la session s'ouvre et la machine
# ne voit rien passer.
openconnect:debian) echo "openconnect vpnc-scripts" ;;
openconnect:*) echo "openconnect" ;;
sshuttle:*) echo "sshuttle" ;;
*) die "pilote inconnu : ${driver}. Voir --help." ;;
esac
}
install_packages() {
local fam="$1"; shift
log "Installation : $*"
case "$fam" in
debian)
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
# shellcheck disable=SC2086
apt-get install -y --no-install-recommends $*
;;
arch)
# shellcheck disable=SC2086
pacman -Sy --needed --noconfirm $*
;;
rhel)
# shellcheck disable=SC2086
{ command -v dnf >/dev/null && dnf install -y $*; } \
|| yum install -y $*
;;
suse)
# shellcheck disable=SC2086
zypper --non-interactive install $*
;;
esac
}
disable_autostart() {
# Un service lancé au boot tient le port et fait échouer l'instance
# dédiée au profil. On les arrête et on les désactive : le pilote
# démarre ce dont il a besoin, quand il en a besoin.
command -v systemctl >/dev/null || return 0
for unit in xl2tpd strongswan-starter strongswan ipsec; do
if systemctl list-unit-files "${unit}.service" >/dev/null 2>&1 \
&& systemctl is-enabled "${unit}.service" >/dev/null 2>&1; then
log "désactivation de ${unit}.service (le pilote le pilote)"
systemctl disable --now "${unit}.service" >/dev/null 2>&1 || true
fi
done
}
# Les binaires que chaque pilote exige, en miroir de `binaries` dans
# script/vpn/drivers/. Un paquet installé sans son binaire (nom changé,
# dépôt incomplet) doit être vu ICI, pas au premier montage.
binaries_for() {
case "$1" in
l2tp_ipsec) echo "ipsec xl2tpd pppd ip" ;;
wireguard) echo "wg wg-quick ip" ;;
openvpn) echo "openvpn ip" ;;
openconnect) echo "openconnect ip" ;;
sshuttle) echo "sshuttle ssh" ;;
esac
}
# Le script que openconnect appelle pour poser routes et DNS. Il vient d'un
# paquet à part, dont le nom change de famille en famille, et il s'installe à
# des endroits différents : c'est un FICHIER qu'on cherche, pas un binaire du
# PATH, donc `binaries_for` ne peut pas le voir.
#
# Sans lui, la session s'ouvre, openconnect se lance, et l'interface tun
# n'apparaît jamais : le montage échoue trois étages plus haut, sur un
# symptôme qui n'accuse pas le paquet manquant.
VPNC_SCRIPT_PATHS="
/etc/vpnc/vpnc-script
/usr/share/vpnc-scripts/vpnc-script
/usr/libexec/openconnect/vpnc-script
/usr/lib/openconnect/vpnc-script
"
verify_vpnc_script() {
local fam="$1" path package
for path in ${VPNC_SCRIPT_PATHS}; do
if [ -x "$path" ]; then
log "vpnc-script : ${path}"
return 0
fi
done
case "$fam" in
rhel|suse) package="vpnc-script" ;;
*) package="vpnc-scripts" ;;
esac
die "vpnc-script introuvable — installer le paquet ${package}"
}
verify() {
local driver="$1" fam="$2" missing=""
for b in $(binaries_for "$driver"); do
command -v "$b" >/dev/null || missing="${missing} ${b}"
done
if [ -n "$missing" ]; then
die "toujours absents après installation :${missing}"
fi
if [ "$driver" = "openconnect" ]; then
verify_vpnc_script "$fam"
fi
log "vérifié : tout est en place pour ${driver}"
}
ALL_DRIVERS="l2tp_ipsec wireguard openvpn openconnect sshuttle"
main() {
case "${1:-}" in
-h|--help) usage; exit 0 ;;
esac
check_root "$@"
detect_os
local fam drivers
fam="$(family)"
# Sans argument : tout. C'est ce que « [8] Installer les paquets
# client » demande quand on ne choisit pas de technologie.
drivers="${*:-${ALL_DRIVERS}}"
for driver in ${drivers}; do
log "── ${driver} ──"
install_packages "$fam" "$(packages_for "$driver" "$fam")"
verify "$driver" "$fam"
done
disable_autostart
log "Terminé. Monter un tunnel : ./script/vpn/vpn.py up --profile <nom>"
}
main "$@"