diff --git a/README.md b/README.md index 19d36fe..ee03aa2 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ CLI-установка корпоративного VPN `vpn.lemanapro.ru` дл ## Быстрая установка ```sh -curl -fsSL http://192.168.50.109/dokril/lemana-vpn/raw/branch/main/install.sh | sh +curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh ``` После установки открой новый shell или выполни: @@ -29,25 +29,25 @@ vpn Полная установка, режим по умолчанию: ```sh -curl -fsSL http://192.168.50.109/dokril/lemana-vpn/raw/branch/main/install.sh | sh +curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh ``` Без Touch ID, но с Bitwarden: ```sh -curl -fsSL http://192.168.50.109/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --without-touchid +curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --without-touchid ``` Минимальная установка без Bitwarden и Touch ID. Пароль LDAP и TOTP будут один раз записаны в macOS Keychain вручную: ```sh -curl -fsSL http://192.168.50.109/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --minimal --configure-keychain +curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --minimal --configure-keychain ``` Проверить действия без изменений: ```sh -curl -fsSL http://192.168.50.109/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --dry-run +curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --dry-run ``` Если raw URL отличается, переопредели базовый адрес: @@ -62,8 +62,10 @@ curl -fsSL https://example.org/dokril/lemana-vpn/raw/branch/main/install.sh \ | Путь | Назначение | | --- | --- | | `~/bin/vpn-lemanapro.sh` | Основной CLI для подключения, статуса и sync секретов | +| `~/bin/uninstall-lemana-vpn.sh` | Локальный uninstall helper | | `~/bin/keychain-fingerprint` | Опциональный Touch ID helper для мастер-пароля Bitwarden | | `~/.config/lemana-vpn/env` | Локальная конфигурация модулей | +| `~/.config/lemana-vpn/patch-backups/` | Backup исходника `openconnect-lite` перед runtime-патчами | | `~/.config/openconnect-lite/config.toml` | Профиль SSO и auto-fill правила Keycloak | | `/usr/local/sbin/lemana-vpn-dns-cleanup` | Root-owned wrapper для сброса только корпоративных DNS | | `/etc/sudoers.d/lemana-vpn-openconnect` | `NOPASSWD` только для `openconnect` | @@ -144,7 +146,7 @@ LEMANA_VPN_DNS_CLEANUP="/usr/local/sbin/lemana-vpn-dns-cleanup" Для другого логина: ```sh -curl -fsSL http://192.168.50.109/dokril/lemana-vpn/raw/branch/main/install.sh \ +curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh \ | sh -s -- --username 12345678 ``` @@ -163,6 +165,30 @@ curl -fsSL http://192.168.50.109/dokril/lemana-vpn/raw/branch/main/install.sh \ Новый вариант разрешает sudo только на `/usr/local/sbin/lemana-vpn-dns-cleanup`. Wrapper сбрасывает DNS только если текущий DNS начинается с `10.`, то есть похож на корпоративный VPN DNS. +## Runtime-патчи openconnect-lite + +`openconnect-lite` работает, но для текущей macOS + Keycloak SSO цепочки ему нужны три runtime-патча. CLI применяет их перед подключением в файле: + +```sh +~/.local/pipx/venvs/openconnect-lite/lib/python*/site-packages/openconnect_lite/browser/webengine_process.py +``` + +Патчи: + +| Патч | Что меняет | Зачем | +| --- | --- | --- | +| `minimal -> offscreen` | Меняет Qt platform mode для скрытого браузера | `minimal` падает с Qt WebEngine на macOS | +| `input/change events` | После `value = ...` отправляет DOM events | Keycloak не реагирует на прямую запись value | +| `URL guard` | Проверяет `location.href` через `new RegExp(...)` перед auto-fill | Qt игнорирует `@include`, без guard auto-fill может кликнуть Cisco ACS и сломать SAML | + +Перед первым изменением CLI сохраняет оригинальный файл: + +```sh +~/.config/lemana-vpn/patch-backups/webengine_process.py.before-lemana-vpn +``` + +Откат патчей выполняет uninstall script. Если backup отсутствует, автоматического rollback нет: значит файл был уже патчен старой ручной установкой или `openconnect-lite` переустановили после backup. + ## Диагностика Проверить установку: @@ -189,8 +215,41 @@ CLI перед подключением патчит `openconnect-lite`: ## Удаление +Рекомендуемый способ: + ```sh -rm -f ~/bin/vpn-lemanapro.sh ~/bin/keychain-fingerprint +curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/uninstall.sh | sh +``` + +Или локально: + +```sh +uninstall-lemana-vpn.sh +``` + +Что делает uninstall: + +- восстанавливает `openconnect-lite` из backup, если backup есть; +- удаляет `vpn-lemanapro.sh` и `uninstall-lemana-vpn.sh`; +- удаляет sudoers rules и DNS cleanup wrapper; +- удаляет блок `lemana-vpn` из `~/.zshrc`; +- удаляет `~/.config/openconnect-lite/config.toml`; +- удаляет `~/.config/lemana-vpn`, если не передан `--keep-config`. + +Опциональные режимы: + +```sh +uninstall-lemana-vpn.sh --dry-run +uninstall-lemana-vpn.sh --keep-config +uninstall-lemana-vpn.sh --remove-keychain +uninstall-lemana-vpn.sh --remove-touchid-helper +uninstall-lemana-vpn.sh --remove-openconnect-lite +``` + +Ручной вариант, если нужен полный контроль: + +```sh +rm -f ~/bin/vpn-lemanapro.sh ~/bin/uninstall-lemana-vpn.sh rm -rf ~/.config/lemana-vpn rm -f ~/.config/openconnect-lite/config.toml sudo rm -f /usr/local/sbin/lemana-vpn-dns-cleanup @@ -204,4 +263,3 @@ sudo rm -f /etc/sudoers.d/lemana-vpn-openconnect /etc/sudoers.d/lemana-vpn-dns ... # <<< lemana-vpn ``` - diff --git a/bin/vpn-lemanapro.sh b/bin/vpn-lemanapro.sh index 59a34b2..def47c8 100755 --- a/bin/vpn-lemanapro.sh +++ b/bin/vpn-lemanapro.sh @@ -24,6 +24,7 @@ BW_KC_ACCOUNT_SESSION="${LEMANA_VPN_BW_KC_ACCOUNT_SESSION:-bw-session}" BW_KC_ACCOUNT_MASTER="${LEMANA_VPN_BW_KC_ACCOUNT_MASTER:-bw-master}" STATUS_DIR="${LEMANA_VPN_STATUS_DIR:-$HOME/.local/state/vpn-lemanapro}" STATUS_FILE="$STATUS_DIR/status.json" +PATCH_BACKUP_DIR="${LEMANA_VPN_PATCH_BACKUP_DIR:-$CONFIG_DIR/patch-backups}" DEBUG=false JSON_MODE=false @@ -148,12 +149,15 @@ _patch_oc() { return 1 fi - "$OC_PYTHON" - "$wep" <<'PY' + "$OC_PYTHON" - "$wep" "$PATCH_BACKUP_DIR" <<'PY' from pathlib import Path import sys path = Path(sys.argv[1]) +backup_dir = Path(sys.argv[2]) +backup_file = backup_dir / "webengine_process.py.before-lemana-vpn" src = path.read_text() +before = src original = src messages = [] @@ -211,7 +215,10 @@ autoFill(); src = src.replace(old_block, new_block) messages.append("URL guard") -if src != path.read_text(): +if src != before: + backup_dir.mkdir(parents=True, exist_ok=True) + if not backup_file.exists(): + backup_file.write_text(before) path.write_text(src) for message in messages: print(f"Patch applied: {message}") @@ -456,4 +463,3 @@ while true; do sleep 5 _emit '{"event":"connecting"}' "Reconnecting..." done - diff --git a/install.sh b/install.sh index 5b651f4..aef5889 100755 --- a/install.sh +++ b/install.sh @@ -2,7 +2,7 @@ set -eu APP_NAME="lemana-vpn" -DEFAULT_RAW_BASE_URL="http://192.168.50.109/dokril/lemana-vpn/raw/branch/main" +DEFAULT_RAW_BASE_URL="https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main" RAW_BASE_URL="${LEMANA_VPN_RAW_BASE_URL:-$DEFAULT_RAW_BASE_URL}" INSTALL_BIN_DIR="${LEMANA_VPN_BIN_DIR:-$HOME/bin}" @@ -194,6 +194,9 @@ install_cli() { download_file "bin/vpn-lemanapro.sh" "$tmp/vpn-lemanapro.sh" run install -m 755 "$tmp/vpn-lemanapro.sh" "$INSTALL_BIN_DIR/vpn-lemanapro.sh" + + download_file "uninstall.sh" "$tmp/uninstall.sh" + run install -m 755 "$tmp/uninstall.sh" "$INSTALL_BIN_DIR/uninstall-lemana-vpn.sh" } install_config() { @@ -343,4 +346,3 @@ main() { } main "$@" - diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 0000000..5fbb18f --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,168 @@ +#!/bin/sh +set -eu + +INSTALL_BIN_DIR="${LEMANA_VPN_BIN_DIR:-$HOME/bin}" +CONFIG_DIR="${LEMANA_VPN_CONFIG_DIR:-$HOME/.config/lemana-vpn}" +OC_CONFIG_DIR="${OPENCONNECT_LITE_CONFIG_DIR:-$HOME/.config/openconnect-lite}" +OC_VENV="${LEMANA_VPN_OC_VENV:-$HOME/.local/pipx/venvs/openconnect-lite}" +DNS_CLEANUP="${LEMANA_VPN_DNS_CLEANUP:-/usr/local/sbin/lemana-vpn-dns-cleanup}" +USERNAME="${LEMANA_VPN_USERNAME:-60103293}" +DRY_RUN=0 +KEEP_CONFIG=0 +REMOVE_KEYCHAIN=0 +REMOVE_TOUCHID_HELPER=0 +REMOVE_OPENCONNECT_LITE=0 + +usage() { + cat <<'USAGE' +Usage: + sh uninstall.sh [options] + +Options: + --keep-config Keep ~/.config/lemana-vpn + --remove-keychain Remove VPN-related Keychain entries + --remove-touchid-helper Remove ~/bin/keychain-fingerprint + --remove-openconnect-lite Remove pipx openconnect-lite after patch rollback + --dry-run Print actions without changing files + -h, --help Show this help + +Default uninstall restores openconnect-lite patch backup, removes Lemana VPN +scripts/config/sudoers/zsh aliases, and keeps shared package dependencies. +USAGE +} + +while [ "$#" -gt 0 ]; do + case "$1" in + --keep-config) KEEP_CONFIG=1 ;; + --remove-keychain) REMOVE_KEYCHAIN=1 ;; + --remove-touchid-helper) REMOVE_TOUCHID_HELPER=1 ;; + --remove-openconnect-lite) REMOVE_OPENCONNECT_LITE=1 ;; + --dry-run) DRY_RUN=1 ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + esac + shift +done + +log() { + printf '%s\n' "$*" +} + +run() { + if [ "$DRY_RUN" -eq 1 ]; then + printf '+' + for arg in "$@"; do + printf ' %s' "$arg" + done + printf '\n' + return 0 + fi + "$@" +} + +find_webengine_process() { + if [ -n "${LEMANA_VPN_WEBENGINE_PROCESS:-}" ]; then + printf '%s\n' "$LEMANA_VPN_WEBENGINE_PROCESS" + return 0 + fi + find "$OC_VENV/lib" -path '*/site-packages/openconnect_lite/browser/webengine_process.py' -print -quit 2>/dev/null || true +} + +restore_openconnect_lite_patch() { + backup="$CONFIG_DIR/patch-backups/webengine_process.py.before-lemana-vpn" + wep="$(find_webengine_process)" + + if [ ! -f "$backup" ]; then + log "No openconnect-lite patch backup found; patch rollback skipped." + return 0 + fi + + if [ -z "$wep" ] || [ ! -f "$wep" ]; then + log "openconnect-lite source not found; patch rollback skipped." + return 0 + fi + + log "Restoring openconnect-lite source from patch backup" + run cp "$backup" "$wep" +} + +remove_zshrc_block() { + zshrc="$HOME/.zshrc" + [ -f "$zshrc" ] || return 0 + + tmp="$(mktemp)" + if [ "$DRY_RUN" -eq 1 ]; then + printf '+ update %s aliases\n' "$zshrc" + rm -f "$tmp" + return 0 + fi + + awk ' + /^# >>> lemana-vpn$/ { skip=1; next } + /^# <<< lemana-vpn$/ { skip=0; next } + skip != 1 { print } + ' "$zshrc" > "$tmp" + mv "$tmp" "$zshrc" +} + +remove_keychain_entries() { + [ "$REMOVE_KEYCHAIN" -eq 1 ] || return 0 + + log "Removing VPN-related Keychain entries" + run security delete-generic-password -s openconnect-lite -a "$USERNAME" >/dev/null 2>&1 || true + run security delete-generic-password -s openconnect-lite -a "totp/$USERNAME" >/dev/null 2>&1 || true + run security delete-generic-password -s vpn-lemanapro -a bw-session >/dev/null 2>&1 || true + run security delete-generic-password -s vpn-lemanapro -a bw-master >/dev/null 2>&1 || true +} + +main() { + [ "$(uname -s)" = "Darwin" ] || { + echo "This uninstaller supports macOS only" >&2 + exit 1 + } + + restore_openconnect_lite_patch + + log "Removing installed scripts" + run rm -f "$INSTALL_BIN_DIR/vpn-lemanapro.sh" + run rm -f "$INSTALL_BIN_DIR/uninstall-lemana-vpn.sh" + if [ "$REMOVE_TOUCHID_HELPER" -eq 1 ]; then + run rm -f "$INSTALL_BIN_DIR/keychain-fingerprint" + fi + + log "Removing sudoers and DNS cleanup wrapper" + run sudo rm -f /etc/sudoers.d/lemana-vpn-openconnect /etc/sudoers.d/lemana-vpn-dns + run sudo rm -f "$DNS_CLEANUP" + + log "Removing shell aliases" + remove_zshrc_block + + log "Removing openconnect-lite config" + run rm -f "$OC_CONFIG_DIR/config.toml" + + if [ "$KEEP_CONFIG" -eq 0 ]; then + log "Removing Lemana VPN config" + run rm -rf "$CONFIG_DIR" + fi + + remove_keychain_entries + + if [ "$REMOVE_OPENCONNECT_LITE" -eq 1 ]; then + if command -v pipx >/dev/null 2>&1; then + log "Removing openconnect-lite from pipx" + run pipx uninstall openconnect-lite + fi + fi + + log "Done." +} + +main "$@" +