Refine VPN installer and menu-bar app integration

This commit is contained in:
2026-06-24 23:15:38 +03:00
parent 2acf5dd50b
commit fdb15ae462
6 changed files with 1126 additions and 228 deletions

View File

@@ -11,25 +11,29 @@ OC_CONFIG_DIR="${OPENCONNECT_LITE_CONFIG_DIR:-$HOME/.config/openconnect-lite}"
DNS_CLEANUP="/usr/local/sbin/lemana-vpn-dns-cleanup"
USERNAME="${LEMANA_VPN_USERNAME:-}"
BW_ITEM="${LEMANA_VPN_BW_ITEM:-LM LDAP}"
CREDENTIAL_SOURCE="${LEMANA_VPN_CREDENTIAL_SOURCE:-bitwarden}"
USE_BITWARDEN=1
USE_TOUCHID=1
CREDENTIAL_SOURCE="${LEMANA_VPN_CREDENTIAL_SOURCE:-none}"
AUTH_PROFILE="${LEMANA_VPN_AUTH_PROFILE:-manual}"
USE_BITWARDEN=0
USE_TOUCHID=0
INSTALL_SUDOERS=1
INSTALL_ALIASES=1
INSTALL_APP=1
INSTALL_AUTOSTART=1
CONFIGURE_LOGIN=0
CONFIGURE_KEYCHAIN=0
DRY_RUN=0
FORCE=0
INTERACTIVE=auto
BITWARDEN_FORCED=0
CREDENTIAL_SOURCE_FORCED=0
AUTH_PROFILE_FORCED=0
TOUCHID_FORCED=0
SUDOERS_FORCED=0
SHELL_FORCED=0
APP_FORCED=0
AUTOSTART_FORCED=0
CONFIGURE_KEYCHAIN_FORCED=0
CONFIGURE_LOGIN_FORCED=0
APP_DIR="${LEMANA_VPN_APP_DIR:-$HOME/Applications/LemanaVPN.app}"
LAUNCH_AGENT="$HOME/Library/LaunchAgents/ru.dokops.LemanaVPN.plist"
@@ -75,12 +79,14 @@ Usage:
sh install.sh [options]
Options:
--with-bitwarden Install/use Bitwarden CLI module (default)
--without-bitwarden Do not install/use Bitwarden CLI; use Keychain credentials
--credential-source VALUE Credential source: bitwarden or keychain
--with-touchid Install/use keychain-fingerprint Touch ID helper (default)
--with-bitwarden Install/use Bitwarden CLI module as an optional credential source
--without-bitwarden Do not install/use Bitwarden CLI (default)
--credential-source VALUE Credential source: none, keychain, or bitwarden
--auth-profile VALUE Auth profile: manual, saved-login, saved-totp, or auto
--with-touchid Install/use keychain-fingerprint Touch ID helper with Bitwarden
--without-touchid Do not install/use Touch ID helper
--configure-keychain Prompt for LDAP password and TOTP secret after install
--configure-login Prompt for LDAP username/password after install
--configure-keychain Prompt for LDAP password and permanent 2FA setup code after install
--username VALUE Corporate LDAP username (otherwise asked on first Terminal connection)
--bw-item VALUE Bitwarden item name (default: LM LDAP)
--raw-base-url URL Raw file base URL for curl installs
@@ -92,15 +98,15 @@ Options:
--without-autostart Do not install LaunchAgent
--interactive Ask before installing optional missing modules
--non-interactive Use selected/default modules without prompts
--minimal Same as --without-bitwarden --without-touchid
--minimal Manual-first install without optional credential helpers
--dry-run Print actions without changing files
--force Reinstall files even when present
-h, --help Show this help
Examples:
sh install.sh
sh install.sh --minimal --configure-keychain
sh install.sh --credential-source keychain --configure-keychain
sh install.sh --configure-login
sh install.sh --credential-source keychain --auth-profile saved-totp --configure-keychain
sh install.sh --without-touchid
USAGE
}
@@ -112,29 +118,53 @@ while [ "$#" -gt 0 ]; do
USE_BITWARDEN=1
BITWARDEN_FORCED=1
CREDENTIAL_SOURCE_FORCED=1
if [ "$AUTH_PROFILE_FORCED" -eq 0 ] && [ "$AUTH_PROFILE" = "manual" ]; then
AUTH_PROFILE="saved-totp"
fi
;;
--without-bitwarden)
CREDENTIAL_SOURCE="keychain"
if [ "$CREDENTIAL_SOURCE" = "bitwarden" ]; then
CREDENTIAL_SOURCE="none"
fi
USE_BITWARDEN=0
USE_TOUCHID=0
BITWARDEN_FORCED=1
CREDENTIAL_SOURCE_FORCED=1
;;
--credential-source)
shift
[ "$#" -gt 0 ] || { echo "--credential-source requires bitwarden or keychain" >&2; exit 1; }
[ "$#" -gt 0 ] || { echo "--credential-source requires none, keychain, or bitwarden" >&2; exit 1; }
CREDENTIAL_SOURCE="$1"
CREDENTIAL_SOURCE_FORCED=1
case "$CREDENTIAL_SOURCE" in
none)
USE_BITWARDEN=0
USE_TOUCHID=0
;;
bitwarden)
USE_BITWARDEN=1
if [ "$AUTH_PROFILE_FORCED" -eq 0 ] && [ "$AUTH_PROFILE" = "manual" ]; then
AUTH_PROFILE="saved-totp"
fi
;;
keychain)
USE_BITWARDEN=0
USE_TOUCHID=0
;;
*)
echo "--credential-source requires bitwarden or keychain" >&2
echo "--credential-source requires none, keychain, or bitwarden" >&2
exit 1
;;
esac
;;
--auth-profile)
shift
[ "$#" -gt 0 ] || { echo "--auth-profile requires manual, saved-login, saved-totp, or auto" >&2; exit 1; }
AUTH_PROFILE="$1"
AUTH_PROFILE_FORCED=1
case "$AUTH_PROFILE" in
manual|saved-login|saved-totp|auto) ;;
*)
echo "--auth-profile requires manual, saved-login, saved-totp, or auto" >&2
exit 1
;;
esac
@@ -147,9 +177,31 @@ while [ "$#" -gt 0 ]; do
USE_TOUCHID=0
TOUCHID_FORCED=1
;;
--configure-login)
CONFIGURE_LOGIN=1
CONFIGURE_LOGIN_FORCED=1
CONFIGURE_KEYCHAIN=0
if [ "$AUTH_PROFILE_FORCED" -eq 0 ]; then
AUTH_PROFILE="saved-login"
fi
if [ "$CREDENTIAL_SOURCE_FORCED" -eq 0 ]; then
CREDENTIAL_SOURCE="keychain"
USE_BITWARDEN=0
USE_TOUCHID=0
fi
;;
--configure-keychain)
CONFIGURE_KEYCHAIN=1
CONFIGURE_LOGIN=0
CONFIGURE_KEYCHAIN_FORCED=1
if [ "$AUTH_PROFILE_FORCED" -eq 0 ]; then
AUTH_PROFILE="saved-totp"
fi
if [ "$CREDENTIAL_SOURCE_FORCED" -eq 0 ]; then
CREDENTIAL_SOURCE="keychain"
USE_BITWARDEN=0
USE_TOUCHID=0
fi
;;
--username)
shift
@@ -195,11 +247,13 @@ while [ "$#" -gt 0 ]; do
--interactive) INTERACTIVE=1 ;;
--non-interactive) INTERACTIVE=0 ;;
--minimal)
CREDENTIAL_SOURCE="keychain"
CREDENTIAL_SOURCE="none"
AUTH_PROFILE="manual"
USE_BITWARDEN=0
USE_TOUCHID=0
BITWARDEN_FORCED=1
CREDENTIAL_SOURCE_FORCED=1
AUTH_PROFILE_FORCED=1
TOUCHID_FORCED=1
;;
--dry-run) DRY_RUN=1 ;;
@@ -329,54 +383,121 @@ yes_no() {
done
}
choose_credential_source_interactive() {
log_step "Настраиваю способ хранения credentials"
log_detail "Можно выбрать Bitwarden или бесплатный macOS Keychain flow с ручным вводом LDAP password и TOTP seed."
print_install_intro() {
log_step "Lemana VPN установит приложение для подключения к корпоративному VPN"
log_detail "По умолчанию ничего не сохраняется: при подключении откроется окно входа, а логин, пароль и одноразовый код вводятся вручную."
log_detail "Дополнительные удобства можно включить сейчас или позже."
}
choose_advanced_signin_interactive() {
while :; do
prompt_printf '\n'
prompt_printf '%sКак хранить VPN credentials?%s\n' "$C_BOLD" "$C_RESET"
prompt_printf ' 1) Bitwarden -> macOS Keychain\n'
prompt_printf ' 2) macOS Keychain: ввести LDAP password и TOTP seed сейчас\n'
prompt_printf ' 3) macOS Keychain: настрою вручную позже\n'
prompt_printf '%sАвтоматический вход требует сохранённых данных.%s\n' "$C_BOLD" "$C_RESET"
prompt_printf ' Автоматический вход работает только если на Mac сохранены корпоративный пароль\n'
prompt_printf ' и постоянный код настройки 2FA. Это не текущие 6 цифр из приложения.\n\n'
prompt_printf ' 1) Вернуться к ручному режиму\n'
prompt_printf ' 2) Сохранить пароль и постоянный код 2FA в macOS Keychain\n'
prompt_printf ' 3) Взять пароль и код 2FA из Bitwarden\n'
prompt_printf '%sВыбор [1/2/3, Enter=1]:%s ' "$C_BOLD" "$C_RESET"
prompt_read_answer
case "$answer" in
""|1)
CREDENTIAL_SOURCE=bitwarden
USE_BITWARDEN=1
AUTH_PROFILE=manual
CREDENTIAL_SOURCE=none
USE_BITWARDEN=0
USE_TOUCHID=0
CONFIGURE_LOGIN=0
CONFIGURE_KEYCHAIN=0
;;
2)
AUTH_PROFILE=saved-totp
CREDENTIAL_SOURCE=keychain
USE_BITWARDEN=0
USE_TOUCHID=0
CONFIGURE_LOGIN=0
CONFIGURE_KEYCHAIN=1
;;
3)
CREDENTIAL_SOURCE=keychain
USE_BITWARDEN=0
USE_TOUCHID=0
AUTH_PROFILE=saved-totp
CREDENTIAL_SOURCE=bitwarden
USE_BITWARDEN=1
CONFIGURE_LOGIN=0
CONFIGURE_KEYCHAIN=0
;;
*)
prompt_printf 'Введите 1, 2 или 3.\n'
prompt_printf 'Введите 1, 2 или 3. Если не уверены, нажмите Enter.\n'
continue
;;
esac
break
done
}
choose_auth_profile_interactive() {
log_step "Выбираю режим подключения"
log_detail "Если не уверены, просто нажмите Enter: будет ручной режим без сохранения паролей."
while :; do
prompt_printf '\n'
prompt_printf '%sКак подключаться по умолчанию?%s\n' "$C_BOLD" "$C_RESET"
prompt_printf ' 1) Вводить всё вручную (рекомендуется)\n'
prompt_printf ' Откроется окно входа. Вы сами введёте логин, пароль и одноразовый код.\n\n'
prompt_printf ' 2) Запомнить логин и пароль на этом Mac\n'
prompt_printf ' При входе логин и пароль подставятся сами. Одноразовый код вы введёте вручную.\n\n'
prompt_printf ' 3) Настроить автоматический вход\n'
prompt_printf ' Нужно заранее сохранить пароль и постоянный код настройки 2FA.\n'
prompt_printf '%sВыбор [1/2/3, Enter=1]:%s ' "$C_BOLD" "$C_RESET"
prompt_read_answer
case "$answer" in
""|1)
AUTH_PROFILE=manual
CREDENTIAL_SOURCE=none
USE_BITWARDEN=0
USE_TOUCHID=0
CONFIGURE_LOGIN=0
CONFIGURE_KEYCHAIN=0
if yes_no "Сохранить логин и пароль в macOS Keychain для подстановки при входе?" n; then
AUTH_PROFILE=saved-login
CREDENTIAL_SOURCE=keychain
USE_BITWARDEN=0
USE_TOUCHID=0
CONFIGURE_LOGIN=1
CONFIGURE_KEYCHAIN=0
fi
;;
2)
AUTH_PROFILE=saved-login
CREDENTIAL_SOURCE=keychain
USE_BITWARDEN=0
USE_TOUCHID=0
CONFIGURE_LOGIN=1
CONFIGURE_KEYCHAIN=0
;;
3)
choose_advanced_signin_interactive
;;
*)
prompt_printf 'Введите 1, 2 или 3. Если не уверены, нажмите Enter.\n'
continue
;;
esac
break
done
case "$CREDENTIAL_SOURCE" in
bitwarden)
log_info "Credential source: Bitwarden sync into macOS Keychain"
case "$AUTH_PROFILE" in
manual)
log_info "Режим подключения: ручной ввод логина, пароля и одноразового кода"
;;
keychain)
if [ "$CONFIGURE_KEYCHAIN" -eq 1 ]; then
log_info "Credential source: macOS Keychain, credentials будут запрошены после установки"
else
log_info "Credential source: macOS Keychain, credentials можно настроить позже через vpn --configure-keychain"
fi
saved-login)
log_info "Режим подключения: логин и пароль будут сохранены в macOS Keychain, одноразовый код вводится вручную"
;;
saved-totp)
log_info "Режим подключения: сохранённые данные будут подставляться в форму входа"
;;
auto)
log_info "Режим подключения: автоматический вход включается только через явный auto-запуск"
;;
esac
}
@@ -413,13 +534,31 @@ print_detected_state() {
log " Menu Bar app: $(bool_word test -x "$APP_DIR/Contents/MacOS/LemanaVPN")"
log " LaunchAgent: $(bool_word test -f "$LAUNCH_AGENT")"
log " Keychain password: $(bool_word keychain_has openconnect-lite "$USERNAME")"
log " Keychain TOTP seed: $(bool_word keychain_has openconnect-lite "totp/$USERNAME")"
log " Keychain 2FA setup: $(bool_word keychain_has openconnect-lite "totp/$USERNAME")"
}
choose_modules() {
if interactive_enabled; then
print_install_intro
fi
print_detected_state
case "$AUTH_PROFILE" in
manual|saved-login|saved-totp|auto) ;;
*)
die "Unknown auth profile: $AUTH_PROFILE"
;;
esac
if [ "$CREDENTIAL_SOURCE_FORCED" -eq 0 ] && [ "$CREDENTIAL_SOURCE" = "none" ] && [ "$AUTH_PROFILE" != "manual" ]; then
CREDENTIAL_SOURCE=keychain
fi
case "$CREDENTIAL_SOURCE" in
none)
USE_BITWARDEN=0
USE_TOUCHID=0
;;
bitwarden)
USE_BITWARDEN=1
;;
@@ -438,18 +577,19 @@ choose_modules() {
fi
log_info "Interactive prompts: on"
log_detail "Установщик проведёт по выбору credential source и отсутствующих опциональных модулей; флаги командной строки имеют приоритет."
log_detail "Установщик проведёт по понятному выбору режима входа; флаги командной строки имеют приоритет."
if [ "$CREDENTIAL_SOURCE_FORCED" -eq 0 ]; then
choose_credential_source_interactive
if [ "$AUTH_PROFILE_FORCED" -eq 0 ] && [ "$CREDENTIAL_SOURCE_FORCED" -eq 0 ]; then
choose_auth_profile_interactive
fi
if [ "$CREDENTIAL_SOURCE" = "bitwarden" ] && [ "$BITWARDEN_FORCED" -eq 0 ] && ! command -v bw >/dev/null 2>&1; then
if yes_no "Bitwarden CLI не найден. Поставить модуль Bitwarden?" y; then
if yes_no "Bitwarden не найден на этом Mac. Установить его сейчас?" n; then
CREDENTIAL_SOURCE=bitwarden
USE_BITWARDEN=1
else
CREDENTIAL_SOURCE=keychain
AUTH_PROFILE=manual
CREDENTIAL_SOURCE=none
USE_BITWARDEN=0
USE_TOUCHID=0
fi
@@ -458,7 +598,7 @@ choose_modules() {
if [ "$TOUCHID_FORCED" -eq 0 ]; then
if [ "$USE_BITWARDEN" -eq 1 ]; then
if ! [ -x "$INSTALL_BIN_DIR/keychain-fingerprint" ]; then
if yes_no "Touch ID helper не найден. Собрать и установить?" y; then
if yes_no "Разрешить Touch ID для открытия Bitwarden?" n; then
USE_TOUCHID=1
else
USE_TOUCHID=0
@@ -504,9 +644,17 @@ choose_modules() {
fi
fi
if [ "$CONFIGURE_KEYCHAIN_FORCED" -eq 0 ] && [ "$CREDENTIAL_SOURCE" = "keychain" ] && [ "$CONFIGURE_KEYCHAIN" -eq 0 ]; then
if [ "$CONFIGURE_LOGIN_FORCED" -eq 0 ] && [ "$AUTH_PROFILE" = "saved-login" ] && [ "$CONFIGURE_LOGIN" -eq 0 ]; then
if ! keychain_has openconnect-lite "$USERNAME"; then
if yes_no "Запомнить логин и пароль в macOS Keychain после установки?" y; then
CONFIGURE_LOGIN=1
fi
fi
fi
if [ "$CONFIGURE_KEYCHAIN_FORCED" -eq 0 ] && [ "$AUTH_PROFILE" = "saved-totp" ] && [ "$CREDENTIAL_SOURCE" = "keychain" ] && [ "$CONFIGURE_KEYCHAIN" -eq 0 ]; then
if ! keychain_has openconnect-lite "$USERNAME" || ! keychain_has openconnect-lite "totp/$USERNAME"; then
if yes_no "Bitwarden отключён, а Keychain credentials неполные. Записать LDAP-пароль и TOTP seed после установки?" y; then
if yes_no "Сохранить пароль и постоянный код настройки 2FA в macOS Keychain после установки?" y; then
CONFIGURE_KEYCHAIN=1
fi
fi
@@ -516,7 +664,13 @@ choose_modules() {
script_dir() {
case "$0" in
*/*) cd "$(dirname "$0")" 2>/dev/null && pwd ;;
*) return 1 ;;
*)
if [ -f "$PWD/$0" ]; then
pwd
else
return 1
fi
;;
esac
}
@@ -569,7 +723,7 @@ install_homebrew_packages() {
log_ok "Homebrew package already installed: bitwarden-cli"
else
log_info "Installing Homebrew package: bitwarden-cli"
log_detail "Bitwarden CLI нужен, чтобы брать LDAP-пароль и TOTP seed из item '$BW_ITEM'."
log_detail "Bitwarden CLI нужен, чтобы брать LDAP-пароль и постоянный код настройки 2FA из item '$BW_ITEM'."
run brew install bitwarden-cli
fi
else
@@ -615,7 +769,7 @@ install_cli() {
install_config() {
tmp="$1"
log_step "Записываю конфигурацию"
log_detail "Здесь сохраняются выбранные модули, LDAP username если он задан, и профиль openconnect-lite для Keycloak SSO."
log_detail "Здесь сохраняются выбранный режим входа, LDAP username если он задан, и профиль openconnect-lite для Keycloak SSO."
run mkdir -p "$CONFIG_DIR" "$OC_CONFIG_DIR"
@@ -628,6 +782,7 @@ install_config() {
fi
env_content="LEMANA_VPN_USERNAME=\"$USERNAME\"
LEMANA_VPN_AUTH_PROFILE=\"$AUTH_PROFILE\"
LEMANA_VPN_CREDENTIAL_SOURCE=\"$CREDENTIAL_SOURCE\"
LEMANA_VPN_BW_ITEM=\"$BW_ITEM\"
LEMANA_VPN_USE_BITWARDEN=\"$USE_BITWARDEN\"
@@ -878,7 +1033,7 @@ EOF
maybe_login_bitwarden() {
if [ "$CREDENTIAL_SOURCE" != "bitwarden" ]; then
log_skip "Credential source is keychain; пропускаю Bitwarden login."
log_skip "Bitwarden не выбран; пропускаю Bitwarden login."
return 0
fi
if ! command -v bw >/dev/null 2>&1; then
@@ -899,6 +1054,43 @@ maybe_login_bitwarden() {
fi
}
auth_profile_label() {
case "$AUTH_PROFILE" in
manual) printf 'ручной ввод без сохранения паролей' ;;
saved-login) printf 'сохранить логин и пароль, одноразовый код вводить вручную' ;;
saved-totp) printf 'подставлять сохранённые данные в окно входа' ;;
auto) printf 'автоматический вход только при явном запуске' ;;
*) printf '%s' "$AUTH_PROFILE" ;;
esac
}
print_final_next_steps() {
log ""
log_ok "Готово."
log_info "Open a new shell or run: exec zsh"
case "$AUTH_PROFILE" in
manual)
log_info "Как подключиться: откройте LemanaVPN.app и нажмите \"Подключить\", или выполните: vpn"
log_detail "Откроется окно входа. Введите корпоративный логин, пароль и одноразовый код вручную."
;;
saved-login)
log_info "Как подключиться: Lemana VPN подставит логин и пароль, одноразовый код введите вручную."
log_detail "Команда подключения: vpn"
;;
saved-totp)
log_info "Как подключиться: Lemana VPN подставит сохранённые данные в видимое окно входа."
log_detail "Для полностью автоматического запуска используйте явно: vpn-auto"
;;
auto)
log_info "Автоматический вход включён явно. Если он не сработает, используйте ручной режим: vpn --manual-full"
;;
esac
log_info "Status: vpn --status"
if [ "$INSTALL_APP" -eq 1 ]; then
log_info "App: open '$APP_DIR'"
fi
}
main() {
[ "$(uname -s)" = "Darwin" ] || die "This installer supports macOS only"
@@ -909,7 +1101,12 @@ main() {
log_step "Начинаю установку Lemana VPN"
log_detail "Повторный запуск безопасен: файлы обновляются идемпотентно, существующие credentials не перезаписываются."
log_info "Modules: credential_source=$CREDENTIAL_SOURCE bitwarden=$USE_BITWARDEN touchid=$USE_TOUCHID sudoers=$INSTALL_SUDOERS shell=$INSTALL_ALIASES app=$INSTALL_APP autostart=$INSTALL_AUTOSTART"
if interactive_enabled; then
log_info "Выбран режим: $(auth_profile_label)"
log_detail "Bitwarden и Touch ID будут установлены только если они выбраны явно."
else
log_info "Modules: auth_profile=$AUTH_PROFILE credential_source=$CREDENTIAL_SOURCE bitwarden=$USE_BITWARDEN touchid=$USE_TOUCHID sudoers=$INSTALL_SUDOERS shell=$INSTALL_ALIASES app=$INSTALL_APP autostart=$INSTALL_AUTOSTART"
fi
install_homebrew_packages
install_openconnect_lite
@@ -924,20 +1121,17 @@ main() {
install_shell_aliases "$tmp"
maybe_login_bitwarden
if [ "$CONFIGURE_KEYCHAIN" -eq 1 ]; then
if [ "$CONFIGURE_LOGIN" -eq 1 ]; then
log_step "Записываю логин и пароль в macOS Keychain"
log_detail "Будет запрошен корпоративный LDAP-пароль. Одноразовый код при подключении останется ручным."
run "$INSTALL_BIN_DIR/vpn-lemanapro.sh" --configure-login
elif [ "$CONFIGURE_KEYCHAIN" -eq 1 ]; then
log_step "Записываю credentials в macOS Keychain"
log_detail "Будут запрошены LDAP-пароль и постоянный TOTP seed, не текущий 30-секундный код."
log_detail "Будут запрошены LDAP-пароль и постоянный код настройки 2FA, не текущий 30-секундный код."
run "$INSTALL_BIN_DIR/vpn-lemanapro.sh" --configure-keychain
fi
log ""
log_ok "Done."
log_info "Open a new shell or run: exec zsh"
log_info "Connect: vpn"
log_info "Status: vpn --status"
if [ "$INSTALL_APP" -eq 1 ]; then
log_info "App: open '$APP_DIR'"
fi
print_final_next_steps
}
main "$@"