Files
lemana-vpn/README.md

32 KiB
Raw Blame History

Lemana VPN

CLI-установка корпоративного VPN vpn.lemanapro.ru для macOS.

Модули по умолчанию: Core: включён; Bitwarden: включён; Touch ID: включён; DNS cleanup: включён; Swift Menu Bar app: включён; автозапуск приложения: включён; runtime-патчи: применяются автоматически перед подключением.

Credential sources: bitwarden синхронизирует LDAP-пароль и TOTP seed из Bitwarden в macOS Keychain; keychain хранит LDAP-пароль и постоянный TOTP seed напрямую в macOS Keychain. Оба источника используют один и тот же runtime openconnect-lite для SSO/autofill.

Репозиторий собирает в один воспроизводимый пакет то, что раньше было ручной локальной настройкой:

  • openconnect как VPN-клиент;
  • openconnect-lite для SAML SSO через Keycloak;
  • опциональный Bitwarden CLI для LDAP-пароля и TOTP seed;
  • опциональный Touch ID helper для мастер-пароля Bitwarden;
  • Swift Menu Bar app LemanaVPN.app;
  • безопасный DNS cleanup через root-owned wrapper;
  • алиасы vpn, vpn-auto, vpn-manual, vpn-debug, vpn-fix-dns.

Быстрая установка

curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh

Если установка запущена из терминала, скрипт работает как интерактивный wizard: проверит, что уже стоит, спросит как хранить credentials, предложит нужные модули и проведёт через настройку.

После установки открой новый shell или выполни:

exec zsh
vpn

Интерактивная установка

Обычный путь — запустить installer без флагов и ответить на вопросы:

curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh

Первый важный вопрос — как хранить VPN credentials:

Как хранить VPN credentials?
  1) Bitwarden -> macOS Keychain
  2) macOS Keychain: ввести LDAP password и TOTP seed сейчас
  3) macOS Keychain: настрою вручную позже
Выбор [1/2/3, Enter=1]:

Что означают варианты:

  • 1 — использовать Bitwarden как sync-provider: installer поставит/проверит bw, а при запуске vpn CLI переложит LDAP password и TOTP seed из Bitwarden в macOS Keychain.
  • 2 — бесплатный Keychain-only путь: после установки CLI спросит LDAP password и постоянный TOTP seed или otpauth://...secret=..., затем сохранит их в macOS Keychain.
  • 3 — поставить CLI/app сейчас, а credentials настроить позже командой vpn --configure-keychain.

Дальше installer спросит только про системные модули: Touch ID для Bitwarden, sudoers, aliases, Swift Menu Bar app и автозапуск.

Флаги остаются для CI, повторяемых установок и диагностики. Для обычной установки они не нужны.

Проверить действия без изменений:

curl -fsSL https://git.dokops.ru/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 -- --non-interactive --credential-source keychain --configure-keychain

Принудительно включить интерактивные вопросы:

curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --interactive

Запустить без вопросов, с выбранными флагами и дефолтами:

curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --non-interactive

Если raw URL отличается, переопредели базовый адрес:

curl -fsSL https://example.org/dokril/lemana-vpn/raw/branch/main/install.sh \
  | LEMANA_VPN_RAW_BASE_URL=https://example.org/dokril/lemana-vpn/raw/branch/main sh

Что ставится

Путь Назначение
~/bin/vpn-lemanapro.sh Основной CLI для подключения, статуса и sync секретов
~/bin/uninstall-lemana-vpn.sh Локальный uninstall helper
~/bin/keychain-fingerprint Опциональный Touch ID helper для мастер-пароля Bitwarden
~/Applications/LemanaVPN.app Swift Menu Bar app для подключения из status bar
~/Library/LaunchAgents/ru.dokops.LemanaVPN.plist Автозапуск Menu Bar app при логине
~/.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
/etc/sudoers.d/lemana-vpn-dns NOPASSWD только для DNS cleanup wrapper
~/.zshrc Идемпотентный блок алиасов vpn, vpn-auto, vpn-manual, vpn-debug, vpn-fix-dns

Статус модулей

vpn и vpn --status первой строкой показывают, какие модули включены в конфиге и реально установлены на машине:

vpn --status
Modules: ✅ core=ok, ✅ bitwarden=on, ✅ touchid=on, ✅ dns=on, ✅ app=on, ✅ autostart=on, ✅ patches=active, ✅ keychain=password:yes/totp_seed:yes
VPN disconnected

Emoji в human-выводе помогают быстро отличать норму, отключённый опциональный модуль и проблему:

  • — модуль установлен или состояние готово;
  • ⏭️ — модуль осознанно отключён;
  • ⚠️ — модуль включён, но чего-то не хватает.

Значения:

Поле Значение
core=ok Есть openconnect, openconnect-lite и config
bitwarden=on Модуль включён и bw установлен
bitwarden=off Модуль отключён через --without-bitwarden или LEMANA_VPN_USE_BITWARDEN=0
bitwarden=missing Модуль включён, но bw не найден
touchid=on/off/missing Состояние Touch ID helper
dns=on/missing Наличие DNS cleanup wrapper
app=on/missing Установлен ли ~/Applications/LemanaVPN.app
autostart=on/off Есть ли LaunchAgent для запуска приложения при логине
patches=active/pending Применены ли runtime-патчи openconnect-lite
keychain=password:yes/totp_seed:yes Есть ли LDAP-пароль и TOTP seed в Keychain

JSON-режим тоже отдаёт модульный статус:

vpn --status --json

Интерактивная установка

Перед установкой install.sh печатает текущее состояние:

Detected state:
  openconnect:       yes
  pipx:              yes
  openconnect-lite:  yes
  Bitwarden CLI:     no
  Touch ID helper:   no
  DNS cleanup:       no
  sudoers:           no/no
  shell aliases:     no
  Swift:             yes
  Menu Bar app:      no
  LaunchAgent:       no
  Keychain password: no
  Keychain TOTP seed: no

Если доступен терминал, скрипт ведёт установку вопросами:

  • выбрать credential source: Bitwarden sync, Keychain с вводом credentials сейчас, или Keychain с настройкой позже;
  • поставить ли Bitwarden CLI, если выбран Bitwarden и bw не найден;
  • собрать ли Touch ID helper, если выбран Bitwarden и helper не найден;
  • собрать ли Swift Menu Bar app, если ~/Applications/LemanaVPN.app не найден;
  • включить ли автозапуск Menu Bar app при логине;
  • настроить ли sudoers для openconnect и DNS cleanup;
  • добавить ли алиасы в ~/.zshrc.

Флаги имеют приоритет над вопросами и нужны в основном для CI, диагностики или повторяемых unattended installs. Например, --credential-source keychain --configure-keychain сразу выберет Keychain-only flow, а --no-shell не будет предлагать алиасы.

В неинтерактивной среде скрипт не задаёт вопросов и использует выбранные флаги/дефолты. Для CI или повторяемой установки лучше явно указывать --non-interactive.

Логи установщика и удаления

Установщик и uninstall script печатают пошаговый лог с emoji, цветом в интерактивном терминале и коротким пояснением, зачем нужен каждый шаг. Например, перед сборкой Swift-приложения установщик отдельно пишет, что swift build может занять время и что строки компилятора вида [2/5] Write swift-version... являются нормальным выводом. При удалении отдельно показывается откат runtime-патчей openconnect-lite, удаление sudoers/DNS wrapper, приложения, aliases и config.

Отключить цвет:

NO_COLOR=1 sh install.sh
NO_COLOR=1 sh uninstall.sh

Отключить emoji:

LEMANA_VPN_NO_EMOJI=1 sh install.sh
LEMANA_VPN_NO_EMOJI=1 sh uninstall.sh

Модули

Core

Всегда устанавливается:

  • openconnect через Homebrew;
  • pipx через Homebrew;
  • openconnect-lite через pipx;
  • CLI vpn-lemanapro.sh;
  • openconnect-lite config;
  • DNS cleanup wrapper.

Как работают credential sources

У Lemana VPN есть два разных способа подготовить credentials, но один общий runtime-контракт: перед запуском SSO в macOS Keychain должны лежать LDAP-пароль и постоянный TOTP seed для openconnect-lite.

Keychain entries:

  • service openconnect-lite, account <LDAP username> — корпоративный LDAP/domain пароль;
  • service openconnect-lite, account totp/<LDAP username> — постоянный TOTP seed.

credential_source=bitwarden — это sync-режим. CLI открывает Bitwarden vault, читает item LM LDAP, берёт из него LDAP password и TOTP seed, нормализует otpauth://...secret=... если нужно, затем записывает оба секрета в macOS Keychain. После этого подключение идёт так же, как в остальных режимах: openconnect-lite читает данные из Keychain, генерирует текущий одноразовый TOTP-код из seed и заполняет Keycloak форму.

credential_source=keychain — это бесплатный built-in режим без Bitwarden. Пользователь один раз запускает vpn --configure-keychain или установку с --credential-source keychain --configure-keychain, вводит LDAP password и постоянный TOTP seed. CLI сохраняет их напрямую в macOS Keychain и при следующих подключениях не спрашивает Bitwarden, master password или текущий OTP-код.

Важно: Lemana VPN не хранит и не принимает текущий 6-значный код как постоянную настройку. Такой код живёт около 30 секунд. Для автоматического SSO нужен именно seed: raw Base32 или otpauth://totp/...?...secret=BASE32.

Если запуск идёт из menu-bar app, интерактивного terminal prompt нет. Поэтому при пустом Keychain приложение покажет ошибку, а настройку нужно один раз выполнить в Terminal:

vpn --configure-keychain

Bitwarden

Включён по умолчанию. CLI при каждом запуске vpn пытается получить LDAP-пароль и TOTP seed из записи Bitwarden LM LDAP, затем записывает их в macOS Keychain для openconnect-lite.

TOTP seed — это постоянный секрет 2FA. Сам одноразовый TOTP-код меняется каждые 30 секунд и генерируется openconnect-lite в момент входа.

Если vault заблокирован и Touch ID helper не смог его открыть, CLI спросит Bitwarden master password. Это пароль от хранилища Bitwarden, а не корпоративный LDAP-пароль. Он нужен только чтобы достать LDAP password/TOTP seed из item LM LDAP и переложить их в macOS Keychain.

Выбрать Keychain вместо Bitwarden можно прямо в installer wizard: пункт 2 вводит LDAP password и TOTP seed сразу после установки, пункт 3 оставляет настройку на потом.

То же самое можно задать флагами для неинтерактивной установки:

sh install.sh --non-interactive --credential-source keychain --configure-keychain

В этом режиме credentials нужно положить в Keychain вручную:

vpn-lemanapro.sh --configure-keychain

Если credentials уже лежат в Keychain, подключение без Bitwarden не будет спрашивать пароль заново. CLI явно напишет, что Bitwarden отключён и используются сохранённые LDAP password/TOTP seed из macOS Keychain.

Если Bitwarden нет

Bitwarden не обязателен. Без него установка работает как обычный openconnect-lite profile с секретами в macOS Keychain.

Интерактивная установка:

curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh

В первом вопросе выбери пункт 2:

2) macOS Keychain: ввести LDAP password и TOTP seed сейчас

Это встроенный бесплатный путь: не нужен Bitwarden account, платный Bitwarden TOTP или внешний password manager. Setup prompt спросит корпоративный LDAP-пароль и постоянный TOTP seed. Seed можно вставить как raw Base32 или как otpauth://totp/...?...secret=BASE32 URI.

Что понадобится:

  • LDAP username;
  • LDAP password: корпоративный LDAP/domain пароль, не мастер-пароль Bitwarden;
  • TOTP secret из корпоративной 2FA настройки.

Важно: вводить нужно не текущие 6 цифр из authenticator-приложения, а постоянный secret. Обычно он есть в QR-коде как secret=BASE32... или может быть показан при ручной настройке TOTP.

Не вставляй текущий 6-значный authenticator code в vpn --configure-keychain. Lemana VPN сохраняет в Keychain постоянный TOTP seed, а openconnect-lite по нему генерирует свежие одноразовые коды во время каждого SSO login.

Если запуск идёт из LemanaVPN.app, приложение не может безопасно показать интерактивный terminal prompt для ввода LDAP/TOTP. Если Keychain пустой, приложение покажет ошибку. В этом случае один раз выполни в Terminal:

vpn --configure-keychain

Если secret есть только в QR-коде:

  1. Открой QR-код в приложении/на портале, где настраивалась 2FA.
  2. Найди режим ручной настройки, где показывается secret.
  3. Если доступен только QR, его нужно расшифровать любым локальным QR-сканером и взять параметр secret.
  4. Вставь secret в prompt TOTP secret (BASE32...).

Если TOTP secret получить нельзя, автоматический headless-вход невозможен: openconnect-lite не сможет сам генерировать свежий TOTP-код на каждом входе.

Touch ID

Включён по умолчанию. Установщик собирает keychain-fingerprint из https://github.com/dss99911/keychain-fingerprint.git и кладёт бинарник в ~/bin/keychain-fingerprint.

Важно: этот helper показывает системный Touch ID prompt перед чтением мастер-пароля Bitwarden, но это не аппаратный Keychain ACL. Это удобный локальный гейт поверх записи Keychain.

Отключить:

sh install.sh --without-touchid

Swift Menu Bar app

Включён по умолчанию. Установщик собирает Swift-приложение из исходников в репозитории и кладёт bundle в:

~/Applications/LemanaVPN.app

Приложение живёт в macOS status bar, запускает ~/bin/vpn-lemanapro.sh --json, показывает состояние VPN, IP, оставшееся время сессии, health-check тоннеля и строку состояния модулей.

Строка состояния модулей в меню приложения использует те же маркеры, что CLI: для готового модуля, ⏭️ для отключённого опционального модуля и ⚠️ для проблемы. Иконка строки тоже меняется: checkmark.circle для полностью готового набора и exclamationmark.triangle для неполной установки.

Если в меню видно modules unavailable: update CLI, значит запущенное приложение обращается к старому ~/bin/vpn-lemanapro.sh, который ещё не умеет отдавать модульный статус. Повтори установку через curl; установщик обновит CLI и перезапустит уже запущенное LemanaVPN.app.

Для сборки нужен Swift 5.9+ из Xcode Command Line Tools:

xcode-select --install

Отключить установку приложения:

curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --without-app

Оставить приложение, но отключить автозапуск:

curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --without-autostart

Ручной запуск:

open ~/Applications/LemanaVPN.app

Использование

vpn                 # подключиться
vpn-auto            # автоматический режим: скрытый браузер, auto-fill и submit
vpn-manual          # ручной режим: видимый браузер, auto-fill без submit
vpn --manual        # то же самое без alias
vpn --status        # статус без нового подключения
vpn --status --json # статус в JSON
vpn-debug           # видимый браузер и debug-логи
vpn --manual --debug # ручной режим с debug-логами
vpn-fix-dns         # сбросить корпоративные DNS после аварийного завершения
open ~/Applications/LemanaVPN.app  # открыть Swift-приложение в menu bar

Режимы подключения:

  • auto — режим по умолчанию. Браузер скрытый, LDAP-пароль и TOTP берутся из Bitwarden/Keychain, Keycloak форма заполняется и отправляется автоматически.
  • manual — браузер видимый, LDAP-пароль и TOTP берутся из Bitwarden/Keychain и подставляются в поля, но кнопки входа не нажимаются. Нажимаешь сам после проверки формы.
  • --manual-sso оставлен как совместимый alias для --manual.

Первый запуск с Bitwarden:

  1. CLI проверит bw.
  2. Если vault locked, попросит мастер-пароль.
  3. Если установлен Touch ID helper, предложит сохранить мастер-пароль за Touch ID prompt.
  4. Достанет LM LDAP, запишет LDAP-пароль и TOTP seed в Keychain.
  5. Запустит openconnect-lite и пройдёт Keycloak SSO.

Настройка

Файл ~/.config/lemana-vpn/env:

LEMANA_VPN_USERNAME="60103293"
LEMANA_VPN_CREDENTIAL_SOURCE="bitwarden"
LEMANA_VPN_BW_ITEM="LM LDAP"
LEMANA_VPN_USE_BITWARDEN="1"
LEMANA_VPN_USE_TOUCHID="1"
LEMANA_VPN_DNS_CLEANUP="/usr/local/sbin/lemana-vpn-dns-cleanup"

Для бесплатного Keychain-only источника:

LEMANA_VPN_USERNAME="60103293"
LEMANA_VPN_CREDENTIAL_SOURCE="keychain"
LEMANA_VPN_USE_BITWARDEN="0"
LEMANA_VPN_USE_TOUCHID="0"
LEMANA_VPN_DNS_CLEANUP="/usr/local/sbin/lemana-vpn-dns-cleanup"

Для другого логина:

curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh \
  | sh -s -- --username 12345678

Bitwarden item

Нужна запись:

  • название: LM LDAP;
  • username: корпоративный LDAP логин;
  • password: LDAP пароль;
  • TOTP: otpauth://...secret=BASE32... или raw BASE32 secret.

Это не 6-значный одноразовый код. В Bitwarden должен лежать постоянный TOTP secret, из которого коды генерируются автоматически.

Почему DNS wrapper, а не wildcard sudoers

Старый вариант давал NOPASSWD на networksetup -setdnsservers *. Это слишком широкое право: любой локальный процесс пользователя мог поменять DNS на произвольный сервер.

Новый вариант разрешает sudo только на /usr/local/sbin/lemana-vpn-dns-cleanup. Wrapper сбрасывает DNS только если текущий DNS начинается с 10., то есть похож на корпоративный VPN DNS.

Runtime-патчи openconnect-lite

openconnect-lite работает, но для текущей macOS + Keycloak SSO цепочки ему нужны runtime-патчи. CLI применяет их перед подключением в файле:

~/.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 без событий
legacy auto-fill Сохраняет старую рабочую схему ApplicationWorld, прямой value = ... и простой click() Это ровно тот режим, на котором hidden SSO раньше стабильно проходил Keycloak
URL guard Проверяет location.href через new RegExp(...) перед auto-fill Qt игнорирует @include, без guard auto-fill может кликнуть Cisco ACS и сломать SAML
auth redirect Читает 302 с vpn.lemanapro.ru без автоматического follow-redirect Python requests может падать на TLS reset при открытии / на sslvpna/b, хотя для SAML нужен только конечный host
manual submit gate Позволяет отключить только auto-click через LEMANA_VPN_AUTOFILL_CLICK=0 Ручной режим видит заполненную форму, но сам решает, когда нажать вход
manual SSO disable Позволяет полностью отключить auto-fill через LEMANA_VPN_AUTOFILL_DISABLE=1 Нужен для низкоуровневой диагностики без подстановки полей

Перед первым изменением CLI сохраняет оригинальный файл:

~/.config/lemana-vpn/patch-backups/webengine_process.py.before-lemana-vpn

Откат патчей выполняет uninstall script. Если backup отсутствует, автоматического rollback нет: значит файл был уже патчен старой ручной установкой или openconnect-lite переустановили после backup.

Диагностика

Проверить установку:

command -v vpn-lemanapro.sh
openconnect --version
~/.local/bin/openconnect-lite --help
sudo -n /usr/local/sbin/lemana-vpn-dns-cleanup
vpn --status

Обычный vpn теперь пишет путь к подробному логу openconnect-lite:

~/Library/Logs/LemanaVPN-openconnect-lite.log

Если после Connecting to VPN (lemanapro)... SSO завис или не видно, что происходит, смотри этот файл:

tail -f ~/Library/Logs/LemanaVPN-openconnect-lite.log

В обычном режиме CLI также печатает heartbeat Still waiting for SSO/openconnect-lite... до успешного подключения, чтобы было понятно, что процесс живой. В vpn-debug дополнительно показываются raw-логи и видимый браузер.

Если в логе повторяется один и тот же URL вида employee.auth.lemanapro.ru/realms/employee/login-actions/authenticate, значит hidden-браузер застрял на Keycloak до перехода в Cisco ACS. Сначала обнови и примени runtime-патчи без подключения:

curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh
vpn-lemanapro.sh --patch-only

Если лог падает раньше браузера с SSLEOFError / UNEXPECTED_EOF_WHILE_READING на sslvpna.lemanapro.ru или sslvpnb.lemanapro.ru, это ломается этап определения конечного Cisco headend. Актуальный runtime-патч auth redirect не открывает / на sslvpna/b, а только берёт Location из 302 ответа vpn.lemanapro.ru и продолжает штатный SAML init через POST.

Если SSO ломается после обновления openconnect-lite, запусти:

vpn-debug

Если нужно самому посмотреть форму Keycloak, но оставить подстановку LDAP/TOTP:

vpn --manual

В этом режиме браузер видимый, openconnect-lite заполняет поля из Keychain/Bitwarden, но не нажимает submit. Для полной диагностики без подстановки можно отдельно выставить LEMANA_VPN_AUTOFILL_DISABLE=1.

Если установка падает на строке install: /usr/local/sbin/...: No such file or directory, значит на машине не было /usr/local/sbin. Актуальный install.sh создаёт эту директорию сам; достаточно повторить установку свежей командой curl.

CLI перед подключением патчит openconnect-lite:

  • minimal -> offscreen, чтобы Qt WebEngine не падал на macOS;
  • добавляет input и change events для Keycloak auto-fill, сохраняя старое прямое присваивание value = ...;
  • оставляет auto-fill в старом ApplicationWorld и не добавляет stateful click guards/native setters;
  • добавляет URL guard, чтобы auto-fill не кликал submit на Cisco ACS;
  • добавляет auth redirect patch, чтобы Python не падал на TLS reset при follow-redirect к sslvpna/b;
  • добавляет manual submit gate для видимой ручной диагностики с auto-fill, но без auto-submit.

Удаление

Рекомендуемый способ:

curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/uninstall.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;
  • останавливает уже запущенный процесс LemanaVPN, удаляет ~/Applications/LemanaVPN.app и LaunchAgent автозапуска;
  • удаляет ~/.config/lemana-vpn, если не передан --keep-config.

Опциональные режимы:

uninstall-lemana-vpn.sh --dry-run
uninstall-lemana-vpn.sh --keep-config
uninstall-lemana-vpn.sh --keep-app
uninstall-lemana-vpn.sh --remove-keychain
uninstall-lemana-vpn.sh --remove-touchid-helper
uninstall-lemana-vpn.sh --remove-openconnect-lite

Ручной вариант, если нужен полный контроль:

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
sudo rm -f /etc/sudoers.d/lemana-vpn-openconnect /etc/sudoers.d/lemana-vpn-dns

Из ~/.zshrc удалить блок:

# >>> lemana-vpn
...
# <<< lemana-vpn