#!/bin/sh set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd)" TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT INT TERM export HOME="$TMP_DIR/home" export LEMANA_VPN_BIN_DIR="$HOME/bin" export LEMANA_VPN_CONFIG_DIR="$HOME/.config/lemana-vpn" export OPENCONNECT_LITE_CONFIG_DIR="$HOME/.config/openconnect-lite" mkdir -p "$HOME" output="$(cd "$ROOT" && sh install.sh --dry-run --non-interactive --minimal)" printf '%s\n' "$output" | grep -q 'Detected state:' printf '%s\n' "$output" | grep -q 'Interactive prompts: off' printf '%s\n' "$output" | grep -q 'Modules: bitwarden=0 touchid=0 sudoers=1 shell=1 app=1 autostart=1' printf '%s\n' "$output" | grep -q 'Проверяю Homebrew-зависимости' printf '%s\n' "$output" | grep -q 'Swift build может занять минуту' 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' esc="$(printf '\033')" if printf '%s\n' "$output" | grep -q "$esc"; then echo "non-tty dry-run output contains ANSI color codes" >&2 exit 1 fi 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":' grep -q 'LemanaVPN-openconnect-lite.log' "$ROOT/bin/vpn-lemanapro.sh" grep -q '"event":"waiting"' "$ROOT/bin/vpn-lemanapro.sh" grep -q -- '--patch-only' "$ROOT/bin/vpn-lemanapro.sh" grep -q -- '--auto' "$ROOT/bin/vpn-lemanapro.sh" grep -q -- '--manual' "$ROOT/bin/vpn-lemanapro.sh" grep -q -- '--manual-sso' "$ROOT/bin/vpn-lemanapro.sh" grep -q 'LEMANA_VPN_AUTOFILL_DISABLE' "$ROOT/bin/vpn-lemanapro.sh" grep -q 'LEMANA_VPN_AUTOFILL_CLICK' "$ROOT/bin/vpn-lemanapro.sh" grep -q 'vpn-auto' "$ROOT/install.sh" grep -q 'vpn-manual' "$ROOT/install.sh" grep -q 'connect(mode: .auto)' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift" grep -q 'connect(mode: .manual)' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift" grep -q 'enum VPNLaunchMode' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" fake_webengine="$TMP_DIR/webengine_process.py" fake_authenticator="$TMP_DIR/authenticator.py" cat > "$fake_webengine" <<'PY' import json import sys class Browser: def run(self, display_mode, credentials, url_pattern, rules): argv = sys.argv.copy() if display_mode == "hidden": argv += ["-platform", "minimal"] if credentials: logger.info("Initiating autologin", cred=credentials) for url_pattern, rules in auto_fill_rules.items(): script = QWebEngineScript() script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady) script.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld) script.setSourceCode( f""" // ==UserScript== // @include {url_pattern} // ==/UserScript== function autoFill() {{ {get_selectors(rules, credentials)} setTimeout(autoFill, 1000); }} autoFill(); """ ) self.page().scripts().insert(script) def get_selectors(rules, credentials): statements = [] for rule in rules: selector = json.dumps(rule.selector) if rule.action == "stop": statements.append( f"""var elem = document.querySelector({selector}); if (elem) {{ return; }}""" ) elif rule.fill: value = json.dumps(getattr(credentials, rule.fill, None)) if value: statements.append( f"""var elem = document.querySelector({selector}); if (elem) {{ elem.dispatchEvent(new Event("focus")); elem.value = {value}; elem.dispatchEvent(new Event("blur")); }}""" ) else: logger.warning( "Credential info not available", type=rule.fill, possibilities=dir(credentials), ) elif rule.action == "click": statements.append( f"""var elem = document.querySelector({selector}); if (elem) {{ elem.dispatchEvent(new Event("focus")); elem.click(); }}""" ) return "\n".join(statements) PY cat > "$fake_authenticator" <<'PY' import requests import structlog logger = structlog.get_logger() class Authenticator: def _detect_authentication_target_url(self): # Follow possible redirects in a GET request # Authentication will occur using a POST request on the final URL response = requests.get(self.host.vpn_url) response.raise_for_status() self.host.address = response.url logger.debug("Auth target url", url=self.host.vpn_url) def _start_authentication(self): pass PY LEMANA_VPN_WEBENGINE_PROCESS="$fake_webengine" \ LEMANA_VPN_AUTHENTICATOR="$fake_authenticator" \ LEMANA_VPN_OC_PYTHON=python3 \ LEMANA_VPN_PATCH_BACKUP_DIR="$TMP_DIR/patch-backups" \ bash "$ROOT/bin/vpn-lemanapro.sh" --patch-only >/dev/null grep -q '"offscreen"' "$fake_webengine" grep -q 'LEMANA_VPN_AUTOFILL_DISABLE' "$fake_webengine" grep -q 'new RegExp' "$fake_webengine" grep -q 'script.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld)' "$fake_webengine" grep -q 'new Event("input", {{bubbles: true}})' "$fake_webengine" grep -q 'LEMANA_VPN_AUTOFILL_CLICK' "$fake_webengine" grep -q 'os.environ.get("LEMANA_VPN_AUTOFILL_CLICK", "1") != "0"' "$fake_webengine" if grep -q 'ScriptWorldId.MainWorld' "$fake_webengine"; then echo "patched auto-fill should keep the original ApplicationWorld behavior" >&2 exit 1 fi if grep -q '__lemanaVpnClicked' "$fake_webengine"; then echo "patched auto-fill should stay stateless like the original working setup" >&2 exit 1 fi if grep -q 'valueSetter' "$fake_webengine"; then echo "patched auto-fill should use the original direct value assignment with input/change events" >&2 exit 1 fi grep -q 'from urllib.parse import urljoin' "$fake_authenticator" grep -q 'self.session.get(self.host.vpn_url, allow_redirects=False)' "$fake_authenticator" grep -q 'response.headers.get("Location")' "$fake_authenticator" if grep -q 'requests.get(self.host.vpn_url)' "$fake_authenticator"; then echo "auth target detection must not follow redirects with bare requests.get" >&2 exit 1 fi status_text="$(bash "$ROOT/bin/vpn-lemanapro.sh" --status)" printf '%s\n' "$status_text" | grep -q 'Modules:' printf '%s\n' "$status_text" | grep -q 'core=' printf '%s\n' "$status_text" | grep -q 'app=' printf '%s\n' "$status_text" | grep -q 'autostart=' uninstall_home="$TMP_DIR/uninstall-home" mkdir -p "$uninstall_home" uninstall_output="$( HOME="$uninstall_home" \ LEMANA_VPN_BIN_DIR="$uninstall_home/bin" \ LEMANA_VPN_CONFIG_DIR="$uninstall_home/.config/lemana-vpn" \ OPENCONNECT_LITE_CONFIG_DIR="$uninstall_home/.config/openconnect-lite" \ sh "$ROOT/uninstall.sh" --dry-run --remove-keychain --remove-touchid-helper --remove-openconnect-lite )" printf '%s\n' "$uninstall_output" | grep -q 'Начинаю удаление Lemana VPN' printf '%s\n' "$uninstall_output" | grep -q 'Проверяю runtime-патчи openconnect-lite' printf '%s\n' "$uninstall_output" | grep -q 'Удаляю sudoers и DNS cleanup wrapper' printf '%s\n' "$uninstall_output" | grep -q 'killall LemanaVPN # if running' printf '%s\n' "$uninstall_output" | grep -q 'Удаляю VPN-записи из macOS Keychain' if printf '%s\n' "$uninstall_output" | grep -q "$esc"; then echo "non-tty uninstall dry-run output contains ANSI color codes" >&2 exit 1 fi missing_user="lemana-smoke-missing-$$" set +e manual_output="$( HOME="$HOME" \ LEMANA_VPN_USERNAME="$missing_user" \ LEMANA_VPN_USE_BITWARDEN=0 \ bash "$ROOT/bin/vpn-lemanapro.sh" --json 2>&1 )" manual_code=$? set -e [ "$manual_code" -ne 0 ] printf '%s\n' "$manual_output" | grep -q '"event":"keychain_required"' printf '%s\n' "$manual_output" | grep -q 'vpn --configure-keychain' if printf '%s\n' "$manual_output" | grep -q 'Cleaning up VPN DNS'; then echo "missing manual credentials should fail before VPN cleanup trap is installed" >&2 exit 1 fi fake_pwd="$TMP_DIR/fake-pwd" mkdir -p "$fake_pwd/bin" printf 'stale local cli\n' > "$fake_pwd/bin/vpn-lemanapro.sh" piped_output="$( cd "$fake_pwd" && LEMANA_VPN_RAW_BASE_URL="file://$ROOT" sh -s -- --dry-run --non-interactive --minimal --without-app < "$ROOT/install.sh" )" printf '%s\n' "$piped_output" | grep -q "curl -fsSL file://$ROOT/bin/vpn-lemanapro.sh" if printf '%s\n' "$piped_output" | grep -q "$fake_pwd/bin/vpn-lemanapro.sh"; then echo "piped install used stale PWD/bin/vpn-lemanapro.sh" >&2 exit 1 fi printf 'smoke ok\n'