Исправь статус модулей VPN-приложения

This commit is contained in:
2026-05-19 13:16:08 +03:00
parent 89e899dfa1
commit 44ff3a2df9
5 changed files with 85 additions and 13 deletions

View File

@@ -97,7 +97,7 @@ curl -fsSL https://example.org/dokril/lemana-vpn/raw/branch/main/install.sh \
```sh
vpn --status
Modules: core=ok, bitwarden=on, touchid=on, dns=on, patches=active, keychain=password:yes/totp_seed:yes
Modules: core=ok, bitwarden=on, touchid=on, dns=on, app=on, autostart=on, patches=active, keychain=password:yes/totp_seed:yes
VPN disconnected
```
@@ -111,6 +111,8 @@ VPN disconnected
| `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 |
@@ -131,12 +133,12 @@ Detected state:
openconnect-lite: yes
Bitwarden CLI: no
Touch ID helper: no
Swift: yes
Menu Bar app: no
LaunchAgent: 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
```
@@ -236,6 +238,8 @@ sh install.sh --without-touchid
Приложение живёт в macOS status bar, запускает `~/bin/vpn-lemanapro.sh --json`, показывает состояние VPN, IP, оставшееся время сессии, health-check тоннеля и строку состояния модулей.
Если в меню видно `modules unavailable: update CLI`, значит запущенное приложение обращается к старому `~/bin/vpn-lemanapro.sh`, который ещё не умеет отдавать модульный статус. Повтори установку через `curl`; установщик обновит CLI и перезапустит уже запущенное `LemanaVPN.app`.
Для сборки нужен Swift 5.9+ из Xcode Command Line Tools:
```sh

View File

@@ -45,21 +45,29 @@ struct ModuleStatus: Decodable {
var backup: Bool
}
struct AppModule: Decodable {
var installed: Bool
var autostart: Bool
}
var core: Core
var bitwarden: ToggleModule
var touchid: ToggleModule
var keychain: Keychain
var dns_cleanup: DnsCleanup
var patches: Patches
var app: AppModule?
var summary: String {
let coreState = core.openconnect && core.openconnect_lite && core.openconnect_lite_config ? "core ok" : "core missing"
let bwState = bitwarden.enabled ? (bitwarden.installed ? "bw on" : "bw missing") : "bw off"
let touchState = touchid.enabled ? (touchid.installed ? "touch on" : "touch missing") : "touch off"
let dnsState = dns_cleanup.installed ? "dns on" : "dns missing"
let appState = app.map { $0.installed ? "app on" : "app missing" } ?? "app unknown"
let autostartState = app.map { $0.autostart ? "autostart on" : "autostart off" } ?? "autostart unknown"
let patchState = patches.active ? "patches active" : "patches pending"
let keychainState = "kc \(keychain.password ? "pass" : "-")/\(keychain.totp_seed ? "totp" : "-")"
return [coreState, bwState, touchState, dnsState, patchState, keychainState].joined(separator: " | ")
return [coreState, bwState, touchState, dnsState, appState, autostartState, patchState, keychainState].joined(separator: " | ")
}
}
@@ -88,7 +96,7 @@ class VPNManager: ObservableObject {
@Published var state: VPNState = .disconnected
@Published var lastError: String?
@Published var tunnelHealthy: Bool = true
@Published var moduleSummary: String = "modules unknown"
@Published var moduleSummary: String = "modules loading..."
private var process: Process?
private var outputPipe: Pipe?
@@ -151,12 +159,34 @@ class VPNManager: ObservableObject {
proc.terminationHandler = { [weak self] _ in
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let self = self, let text = String(data: data, encoding: .utf8) else { return }
guard let self = self else { return }
let text = String(data: data, encoding: .utf8) ?? ""
let lastLine = text.split(separator: "\n").map(String.init).last ?? ""
guard let jsonData = lastLine.data(using: .utf8),
let response = try? JSONDecoder().decode(VPNStatusResponse.self, from: jsonData),
let modules = response.modules else { return }
Task { @MainActor in
guard !lastLine.isEmpty, let jsonData = lastLine.data(using: .utf8) else {
self.moduleSummary = "modules unavailable"
self.log("[modules] status refresh returned no JSON output")
return
}
let response: VPNStatusResponse
do {
response = try JSONDecoder().decode(VPNStatusResponse.self, from: jsonData)
} catch {
self.moduleSummary = "modules unavailable"
let compact = text.replacingOccurrences(of: "\n", with: "\\n")
let preview = compact.count > 500 ? String(compact.prefix(500)) + "..." : compact
self.log("[modules] status decode failed: \(error.localizedDescription); output=\(preview)")
return
}
guard let modules = response.modules else {
self.moduleSummary = "modules unavailable: update CLI"
self.log("[modules] status has no modules field; reinstall CLI with install.sh")
return
}
self.moduleSummary = modules.summary
}
}

View File

@@ -19,6 +19,8 @@ USE_BITWARDEN="${LEMANA_VPN_USE_BITWARDEN:-1}"
USE_TOUCHID="${LEMANA_VPN_USE_TOUCHID:-1}"
CACHE_BW_SESSION="${LEMANA_VPN_CACHE_BW_SESSION:-0}"
DNS_CLEANUP="${LEMANA_VPN_DNS_CLEANUP:-/usr/local/sbin/lemana-vpn-dns-cleanup}"
APP_DIR="${LEMANA_VPN_APP_DIR:-$HOME/Applications/LemanaVPN.app}"
LAUNCH_AGENT="${LEMANA_VPN_LAUNCH_AGENT:-$HOME/Library/LaunchAgents/ru.dokops.LemanaVPN.plist}"
BW_KC_SERVICE="${LEMANA_VPN_BW_KC_SERVICE:-vpn-lemanapro}"
BW_KC_ACCOUNT_SESSION="${LEMANA_VPN_BW_KC_ACCOUNT_SESSION:-bw-session}"
BW_KC_ACCOUNT_MASTER="${LEMANA_VPN_BW_KC_ACCOUNT_MASTER:-bw-master}"
@@ -106,12 +108,15 @@ _keychain_has() {
_module_status_json() {
local openconnect_installed openconnect_lite_installed bitwarden_installed touchid_installed dns_cleanup_installed
local app_installed app_autostart
local config_present oc_config_present patch_backup_present patches_active keychain_password keychain_totp_seed
openconnect_installed="$(_module_bool command -v openconnect)"
openconnect_lite_installed="$(_module_bool test -x "$OC_BIN")"
bitwarden_installed="$(_module_bool command -v bw)"
touchid_installed="$(_module_bool test -x "$KC_FP")"
dns_cleanup_installed="$(_module_bool test -x "$DNS_CLEANUP")"
app_installed="$(_module_bool test -x "$APP_DIR/Contents/MacOS/LemanaVPN")"
app_autostart="$(_module_bool test -f "$LAUNCH_AGENT")"
config_present="$(_module_bool test -f "$CONFIG_FILE")"
oc_config_present="$(_module_bool test -f "$HOME/.config/openconnect-lite/config.toml")"
patch_backup_present="$(_module_bool test -f "$PATCH_BACKUP_DIR/webengine_process.py.before-lemana-vpn")"
@@ -119,7 +124,7 @@ _module_status_json() {
keychain_password="$(_module_bool _keychain_has openconnect-lite "$KC_USERNAME")"
keychain_totp_seed="$(_module_bool _keychain_has openconnect-lite "totp/$KC_USERNAME")"
printf '{"core":{"openconnect":%s,"openconnect_lite":%s,"config":%s,"openconnect_lite_config":%s},"bitwarden":{"enabled":%s,"installed":%s,"item":"%s"},"touchid":{"enabled":%s,"installed":%s},"keychain":{"password":%s,"totp_seed":%s},"dns_cleanup":{"installed":%s},"patches":{"active":%s,"backup":%s}}' \
printf '{"core":{"openconnect":%s,"openconnect_lite":%s,"config":%s,"openconnect_lite_config":%s},"bitwarden":{"enabled":%s,"installed":%s,"item":"%s"},"touchid":{"enabled":%s,"installed":%s},"keychain":{"password":%s,"totp_seed":%s},"dns_cleanup":{"installed":%s},"patches":{"active":%s,"backup":%s},"app":{"installed":%s,"autostart":%s}}' \
"$openconnect_installed" \
"$openconnect_lite_installed" \
"$config_present" \
@@ -133,7 +138,9 @@ _module_status_json() {
"$keychain_totp_seed" \
"$dns_cleanup_installed" \
"$patches_active" \
"$patch_backup_present"
"$patch_backup_present" \
"$app_installed" \
"$app_autostart"
}
_module_human_part() {
@@ -149,6 +156,7 @@ _module_human_part() {
_module_status_human() {
local core bitwarden_installed touchid_installed dns_cleanup_installed patches_active keychain_password keychain_totp_seed
local app_installed app_autostart
if command -v openconnect >/dev/null 2>&1 && [[ -x "$OC_BIN" && -f "$HOME/.config/openconnect-lite/config.toml" ]]; then
core="core=ok"
else
@@ -161,13 +169,17 @@ _module_status_human() {
patches_active="$(_module_bool _patches_active)"
keychain_password="$(_module_bool _keychain_has openconnect-lite "$KC_USERNAME")"
keychain_totp_seed="$(_module_bool _keychain_has openconnect-lite "totp/$KC_USERNAME")"
app_installed="$(_module_bool test -x "$APP_DIR/Contents/MacOS/LemanaVPN")"
app_autostart="$(_module_bool test -f "$LAUNCH_AGENT")"
printf 'Modules: %s, ' "$core"
_module_human_part "bitwarden" "$USE_BITWARDEN" "$bitwarden_installed"
printf ', '
_module_human_part "touchid" "$USE_TOUCHID" "$touchid_installed"
printf ', dns=%s, patches=%s, keychain=password:%s/totp_seed:%s\n' \
printf ', dns=%s, app=%s, autostart=%s, patches=%s, keychain=password:%s/totp_seed:%s\n' \
"$([[ "$dns_cleanup_installed" == "true" ]] && printf on || printf missing)" \
"$([[ "$app_installed" == "true" ]] && printf on || printf missing)" \
"$([[ "$app_autostart" == "true" ]] && printf on || printf off)" \
"$([[ "$patches_active" == "true" ]] && printf active || printf pending)" \
"$([[ "$keychain_password" == "true" ]] && printf yes || printf no)" \
"$([[ "$keychain_totp_seed" == "true" ]] && printf yes || printf no)"

View File

@@ -566,6 +566,22 @@ install_launch_agent() {
fi
}
restart_running_menu_bar_app() {
[ "$INSTALL_APP" -eq 1 ] || return 0
if [ "$DRY_RUN" -eq 1 ]; then
printf '+ restart LemanaVPN.app if running\n'
return 0
fi
if pgrep -x LemanaVPN >/dev/null 2>&1; then
log "Restarting running LemanaVPN.app"
killall LemanaVPN >/dev/null 2>&1 || true
sleep 1
open "$APP_DIR" >/dev/null 2>&1 || true
fi
}
install_shell_aliases() {
[ "$INSTALL_ALIASES" -eq 1 ] || return 0
@@ -636,6 +652,7 @@ main() {
install_touchid_helper "$tmp"
install_menu_bar_app "$tmp"
install_launch_agent "$tmp"
restart_running_menu_bar_app
install_shell_aliases "$tmp"
maybe_login_bitwarden

View File

@@ -19,5 +19,14 @@ printf '%s\n' "$output" | grep -q 'Modules: bitwarden=0 touchid=0 sudoers=1 shel
printf '%s\n' "$output" | grep -q 'sudo install -d -m 755 -o root -g wheel /usr/local/sbin'
printf '%s\n' "$output" | grep -q 'swift build -c release --package-path'
printf '%s\n' "$output" | grep -q 'launchctl load'
printf '%s\n' "$output" | grep -q 'restart LemanaVPN.app if running'
status_json="$(bash "$ROOT/bin/vpn-lemanapro.sh" --status --json)"
printf '%s\n' "$status_json" | grep -q '"modules":'
printf '%s\n' "$status_json" | grep -q '"app":'
status_text="$(bash "$ROOT/bin/vpn-lemanapro.sh" --status)"
printf '%s\n' "$status_text" | grep -q 'app='
printf '%s\n' "$status_text" | grep -q 'autostart='
printf 'smoke ok\n'