UNPKG

vibetunnel

Version:

Terminal sharing server with web interface - supports macOS, Linux, and headless environments

374 lines (360 loc) 44.1 kB
#!/usr/bin/env node "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.installSystemdService = installSystemdService; const node_child_process_1 = require("node:child_process"); const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); // Colors for output const RED = '\x1b[0;31m'; const GREEN = '\x1b[0;32m'; const BLUE = '\x1b[0;34m'; const NC = '\x1b[0m'; // No Color // Configuration const SERVICE_NAME = 'vibetunnel'; const SERVICE_FILE = 'vibetunnel.service'; // Get the current user (regular user only, no sudo/root) function getCurrentUser() { const username = process.env.USER || 'unknown'; const home = process.env.HOME || `/home/${username}`; return { username, home }; } // Print colored output function printInfo(message) { console.log(`${BLUE}[INFO]${NC} ${message}`); } function printSuccess(message) { console.log(`${GREEN}[SUCCESS]${NC} ${message}`); } function printError(message) { console.log(`${RED}[ERROR]${NC} ${message}`); } // Create a stable wrapper script that can find vibetunnel regardless of node version manager function createVibetunnelWrapper() { const { username, home } = getCurrentUser(); const wrapperPath = `${home}/.local/bin/vibetunnel-systemd`; const wrapperContent = `#!/bin/bash # VibeTunnel Systemd Wrapper Script # This script finds and executes vibetunnel for user: ${username} # Function to log messages log_info() { echo "[INFO] $1" >&2 } log_error() { echo "[ERROR] $1" >&2 } # Set up environment for user ${username} export HOME="${home}" export USER="${username}" # Try to find vibetunnel in various ways find_vibetunnel() { # Method 1: Check if vibetunnel is in PATH if command -v vibetunnel >/dev/null 2>&1; then log_info "Found vibetunnel in PATH" vibetunnel "$@" return $? fi # Method 2: Check for nvm installations if [ -d "${home}/.nvm" ]; then log_info "Checking nvm installation for user ${username}" export NVM_DIR="${home}/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" if command -v vibetunnel >/dev/null 2>&1; then log_info "Found vibetunnel via nvm" vibetunnel "$@" return $? fi fi # Method 3: Check for fnm installations if [ -d "${home}/.local/share/fnm" ] && [ -x "${home}/.local/share/fnm/fnm" ]; then log_info "Checking fnm installation for user ${username}" export FNM_DIR="${home}/.local/share/fnm" export PATH="${home}/.local/share/fnm:$PATH" export SHELL="/bin/bash" # Force shell for fnm # Initialize fnm with explicit shell and use the default node version eval "$("${home}/.local/share/fnm/fnm" env --shell bash)" 2>/dev/null || true # Try to use the default node version or current version "${home}/.local/share/fnm/fnm" use default >/dev/null 2>&1 || "${home}/.local/share/fnm/fnm" use current >/dev/null 2>&1 || true if command -v vibetunnel >/dev/null 2>&1; then log_info "Found vibetunnel via fnm" vibetunnel "$@" return $? fi fi # Method 4: Check common global npm locations for npm_bin in "/usr/local/bin/npm" "/usr/bin/npm" "/opt/homebrew/bin/npm"; do if [ -x "$npm_bin" ]; then log_info "Trying npm global with $npm_bin" NPM_PREFIX=$("$npm_bin" config get prefix 2>/dev/null) if [ -n "$NPM_PREFIX" ] && [ -x "$NPM_PREFIX/bin/vibetunnel" ]; then log_info "Found vibetunnel via npm global: $NPM_PREFIX/bin/vibetunnel" "$NPM_PREFIX/bin/vibetunnel" "$@" return $? fi fi done # Method 5: Try to run with node directly using global npm package for node_bin in "/usr/local/bin/node" "/usr/bin/node" "/opt/homebrew/bin/node"; do if [ -x "$node_bin" ]; then for script_path in "/usr/local/lib/node_modules/vibetunnel/dist/cli.js" "/usr/lib/node_modules/vibetunnel/dist/cli.js"; do if [ -f "$script_path" ]; then log_info "Running vibetunnel via node: $node_bin $script_path" "$node_bin" "$script_path" "$@" return $? fi done fi done log_error "Could not find vibetunnel installation for user ${username}" log_error "Please ensure vibetunnel is installed globally: npm install -g vibetunnel" return 1 } # Execute the function with all arguments find_vibetunnel "$@" `; try { // Ensure ~/.local/bin directory exists const localBinDir = `${home}/.local/bin`; if (!(0, node_fs_1.existsSync)(localBinDir)) { (0, node_fs_1.mkdirSync)(localBinDir, { recursive: true }); printInfo(`Created directory: ${localBinDir}`); } // Create the wrapper script (0, node_fs_1.writeFileSync)(wrapperPath, wrapperContent); (0, node_fs_1.chmodSync)(wrapperPath, 0o755); printSuccess(`Created wrapper script at ${wrapperPath}`); return wrapperPath; } catch (error) { printError(`Failed to create wrapper script: ${error}`); process.exit(1); } } // Verify that vibetunnel is accessible and return wrapper path function checkVibetunnelAndCreateWrapper() { // First, verify that vibetunnel is actually installed somewhere try { const vibetunnelPath = (0, node_child_process_1.execSync)('which vibetunnel', { encoding: 'utf8', stdio: 'pipe' }).trim(); printInfo(`Found VibeTunnel at: ${vibetunnelPath}`); } catch (_error) { printError('VibeTunnel is not installed or not accessible. Please install it first:'); console.log(' npm install -g vibetunnel'); process.exit(1); } // Create and return the wrapper script path return createVibetunnelWrapper(); } // Remove wrapper script during uninstall function removeVibetunnelWrapper() { const { home } = getCurrentUser(); const wrapperPath = `${home}/.local/bin/vibetunnel-systemd`; try { if ((0, node_fs_1.existsSync)(wrapperPath)) { (0, node_fs_1.unlinkSync)(wrapperPath); printInfo('Removed wrapper script'); } } catch (_error) { // Ignore errors when removing wrapper } } // No need to create users or directories - using current user // Get the systemd service template function getServiceTemplate(vibetunnelPath) { const { home } = getCurrentUser(); return `[Unit] Description=VibeTunnel - Terminal sharing server with web interface Documentation=https://github.com/amantus-ai/vibetunnel After=network.target Wants=network.target [Service] Type=simple WorkingDirectory=${home} ExecStart=${vibetunnelPath} --port 4020 --bind 0.0.0.0 Restart=always RestartSec=10 StandardOutput=journal StandardError=journal SyslogIdentifier=${SERVICE_NAME} # Environment - preserve user environment for node version managers Environment=NODE_ENV=production Environment=VIBETUNNEL_LOG_LEVEL=info Environment=HOME=%h Environment=USER=%i # Resource limits LimitNOFILE=65536 MemoryHigh=512M MemoryMax=1G [Install] WantedBy=default.target`; } // Install systemd service function installService(vibetunnelPath) { printInfo('Installing user systemd service...'); const { home } = getCurrentUser(); const systemdDir = `${home}/.config/systemd/user`; const serviceContent = getServiceTemplate(vibetunnelPath); const servicePath = (0, node_path_1.join)(systemdDir, SERVICE_FILE); try { // Create user systemd directory if it doesn't exist (0, node_fs_1.mkdirSync)(systemdDir, { recursive: true }); (0, node_fs_1.writeFileSync)(servicePath, serviceContent); (0, node_fs_1.chmodSync)(servicePath, 0o644); // Reload user systemd (0, node_child_process_1.execSync)('systemctl --user daemon-reload', { stdio: 'pipe' }); printSuccess('User systemd service installed'); } catch (error) { printError(`Failed to install service: ${error}`); process.exit(1); } } // Configure service function configureService() { printInfo('Configuring service...'); try { // Enable the user service (0, node_child_process_1.execSync)(`systemctl --user enable ${SERVICE_NAME}`, { stdio: 'pipe' }); printSuccess('User service enabled for automatic startup'); // Enable lingering so service starts on boot even when user not logged in try { const { username } = getCurrentUser(); (0, node_child_process_1.execSync)(`loginctl enable-linger ${username}`, { stdio: 'pipe' }); printSuccess('User lingering enabled - service will start on boot'); } catch (error) { printError(`Failed to enable lingering: ${error}`); printError('Service will only start when user logs in'); } } catch (error) { printError(`Failed to configure service: ${error}`); process.exit(1); } } // Display usage instructions function showUsage() { const { username, home } = getCurrentUser(); printSuccess('VibeTunnel systemd service installation completed!'); console.log(''); console.log('Usage:'); console.log(` systemctl --user start ${SERVICE_NAME} # Start the service`); console.log(` systemctl --user stop ${SERVICE_NAME} # Stop the service`); console.log(` systemctl --user restart ${SERVICE_NAME} # Restart the service`); console.log(` systemctl --user status ${SERVICE_NAME} # Check service status`); console.log(` systemctl --user enable ${SERVICE_NAME} # Enable auto-start (already done)`); console.log(` systemctl --user disable ${SERVICE_NAME} # Disable auto-start`); console.log(''); console.log('Logs:'); console.log(` journalctl --user -u ${SERVICE_NAME} -f # Follow logs in real-time`); console.log(` journalctl --user -u ${SERVICE_NAME} # View all logs`); console.log(''); console.log('Configuration:'); console.log(' Service runs on port 4020 by default'); console.log(' Web interface: http://localhost:4020'); console.log(` Service runs as user: ${username}`); console.log(` Working directory: ${home}`); console.log(` Wrapper script: ${home}/.local/bin/vibetunnel-systemd`); console.log(''); console.log(`To customize the service, edit: ${home}/.config/systemd/user/${SERVICE_FILE}`); console.log(`Then run: systemctl --user daemon-reload && systemctl --user restart ${SERVICE_NAME}`); } // Uninstall function function uninstallService() { printInfo('Uninstalling VibeTunnel user systemd service...'); try { // Stop and disable user service try { (0, node_child_process_1.execSync)(`systemctl --user is-active ${SERVICE_NAME}`, { stdio: 'pipe' }); (0, node_child_process_1.execSync)(`systemctl --user stop ${SERVICE_NAME}`, { stdio: 'pipe' }); printInfo('User service stopped'); } catch (_error) { // Service not running } try { (0, node_child_process_1.execSync)(`systemctl --user is-enabled ${SERVICE_NAME}`, { stdio: 'pipe' }); (0, node_child_process_1.execSync)(`systemctl --user disable ${SERVICE_NAME}`, { stdio: 'pipe' }); printInfo('User service disabled'); } catch (_error) { // Service not enabled } // Remove service file const { home } = getCurrentUser(); const systemdDir = `${home}/.config/systemd/user`; const servicePath = (0, node_path_1.join)(systemdDir, SERVICE_FILE); if ((0, node_fs_1.existsSync)(servicePath)) { (0, node_fs_1.unlinkSync)(servicePath); printInfo('Service file removed'); } // Reload user systemd (0, node_child_process_1.execSync)('systemctl --user daemon-reload', { stdio: 'pipe' }); // Remove wrapper script removeVibetunnelWrapper(); // Optionally disable lingering (ask user) const { username } = getCurrentUser(); printInfo('Note: User lingering is still enabled. To disable:'); console.log(` loginctl disable-linger ${username}`); printSuccess('VibeTunnel user systemd service uninstalled'); } catch (error) { printError(`Failed to uninstall service: ${error}`); process.exit(1); } } // Check service status function checkServiceStatus() { try { const status = (0, node_child_process_1.execSync)(`systemctl --user status ${SERVICE_NAME}`, { encoding: 'utf8' }); console.log(status); } catch (error) { // systemctl status returns non-zero for inactive services, which is normal if (error instanceof Error && 'stdout' in error) { console.log(error.stdout); } else { printError(`Failed to get service status: ${error}`); } } } // Check if running as root and prevent execution function checkNotRoot() { if (process.getuid && process.getuid() === 0) { printError('This installer must NOT be run as root!'); printError('VibeTunnel systemd service should run as a regular user for security.'); printError('Please run this command as a regular user (without sudo).'); process.exit(1); } } // Main installation function function installSystemdService(action = 'install') { // Prevent running as root for security checkNotRoot(); switch (action) { case 'install': { printInfo('Installing VibeTunnel user systemd service...'); const wrapperPath = checkVibetunnelAndCreateWrapper(); installService(wrapperPath); configureService(); showUsage(); break; } case 'uninstall': { uninstallService(); break; } case 'status': checkServiceStatus(); break; default: console.log('Usage: vibetunnel systemd [install|uninstall|status]'); console.log(' install - Install VibeTunnel user systemd service (default)'); console.log(' uninstall - Remove VibeTunnel user systemd service'); console.log(' status - Check service status'); process.exit(1); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3lzdGVtZC1pbnN0YWxsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvc2VydmVyL3NlcnZpY2VzL3N5c3RlbWQtaW5zdGFsbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQXFYQSxzREErQkM7QUFsWkQsMkRBQThDO0FBQzlDLHFDQUFzRjtBQUN0Rix5Q0FBaUM7QUFFakMsb0JBQW9CO0FBQ3BCLE1BQU0sR0FBRyxHQUFHLFlBQVksQ0FBQztBQUN6QixNQUFNLEtBQUssR0FBRyxZQUFZLENBQUM7QUFDM0IsTUFBTSxJQUFJLEdBQUcsWUFBWSxDQUFDO0FBQzFCLE1BQU0sRUFBRSxHQUFHLFNBQVMsQ0FBQyxDQUFDLFdBQVc7QUFFakMsZ0JBQWdCO0FBQ2hCLE1BQU0sWUFBWSxHQUFHLFlBQVksQ0FBQztBQUNsQyxNQUFNLFlBQVksR0FBRyxvQkFBb0IsQ0FBQztBQUUxQyx5REFBeUQ7QUFDekQsU0FBUyxjQUFjO0lBQ3JCLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxJQUFJLFNBQVMsQ0FBQztJQUMvQyxNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksSUFBSSxTQUFTLFFBQVEsRUFBRSxDQUFDO0lBRXJELE9BQU8sRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLENBQUM7QUFDNUIsQ0FBQztBQUVELHVCQUF1QjtBQUN2QixTQUFTLFNBQVMsQ0FBQyxPQUFlO0lBQ2hDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxJQUFJLFNBQVMsRUFBRSxJQUFJLE9BQU8sRUFBRSxDQUFDLENBQUM7QUFDL0MsQ0FBQztBQUVELFNBQVMsWUFBWSxDQUFDLE9BQWU7SUFDbkMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLEtBQUssWUFBWSxFQUFFLElBQUksT0FBTyxFQUFFLENBQUMsQ0FBQztBQUNuRCxDQUFDO0FBRUQsU0FBUyxVQUFVLENBQUMsT0FBZTtJQUNqQyxPQUFP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QUFvQyxDQUFDLENBQUM7WUFDbEQsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNwQixDQUFDO0FBQ0gsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIiMhL3Vzci9iaW4vZW52IG5vZGVcblxuaW1wb3J0IHsgZXhlY1N5bmMgfSBmcm9tICdub2RlOmNoaWxkX3Byb2Nlc3MnO1xuaW1wb3J0IHsgY2htb2RTeW5jLCBleGlzdHNTeW5jLCBta2RpclN5bmMsIHVubGlua1N5bmMsIHdyaXRlRmlsZVN5bmMgfSBmcm9tICdub2RlOmZzJztcbmltcG9ydCB7IGpvaW4gfSBmcm9tICdub2RlOnBhdGgnO1xuXG4vLyBDb2xvcnMgZm9yIG91dHB1dFxuY29uc3QgUkVEID0gJ1xceDFiWzA7MzFtJztcbmNvbnN0IEdSRUVOID0gJ1xceDFiWzA7MzJtJztcbmNvbnN0IEJMVUUgPSAnXFx4MWJbMDszNG0nO1xuY29uc3QgTkMgPSAnXFx4MWJbMG0nOyAvLyBObyBDb2xvclxuXG4vLyBDb25maWd1cmF0aW9uXG5jb25zdCBTRVJWSUNFX05BTUUgPSAndmliZXR1bm5lbCc7XG5jb25zdCBTRVJWSUNFX0ZJTEUgPSAndmliZXR1bm5lbC5zZXJ2aWNlJztcblxuLy8gR2V0IHRoZSBjdXJyZW50IHVzZXIgKHJlZ3VsYXIgdXNlciBvbmx5LCBubyBzdWRvL3Jvb3QpXG5mdW5jdGlvbiBnZXRDdXJyZW50VXNlcigpOiB7IHVzZXJuYW1lOiBzdHJpbmc7IGhvbWU6IHN0cmluZyB9IHtcbiAgY29uc3QgdXNlcm5hbWUgPSBwcm9jZXNzLmVudi5VU0VSIHx8ICd1bmtub3duJztcbiAgY29uc3QgaG9tZSA9IHByb2Nlc3MuZW52LkhPTUUgfHwgYC9ob21lLyR7dXNlcm5hbWV9YDtcblxuICByZXR1cm4geyB1c2VybmFtZSwgaG9tZSB9O1xufVxuXG4vLyBQcmludCBjb2xvcmVkIG91dHB1dFxuZnVuY3Rpb24gcHJpbnRJbmZvKG1lc3NhZ2U6IHN0cmluZyk6IHZvaWQge1xuICBjb25zb2xlLmxvZyhgJHtCTFVFfVtJTkZPXSR7TkN9ICR7bWVzc2FnZX1gKTtcbn1cblxuZnVuY3Rpb24gcHJpbnRTdWNjZXNzKG1lc3NhZ2U6IHN0cmluZyk6IHZvaWQge1xuICBjb25zb2xlLmxvZyhgJHtHUkVFTn1bU1VDQ0VTU10ke05DfSAke21lc3NhZ2V9YCk7XG59XG5cbmZ1bmN0aW9uIHByaW50RXJyb3IobWVzc2FnZTogc3RyaW5nKTogdm9pZCB7XG4gIGNvbnNvbGUubG9nKGAke1JFRH1bRVJST1JdJHtOQ30gJHttZXNzYWdlfWApO1xufVxuXG4vLyBDcmVhdGUgYSBzdGFibGUgd3JhcHBlciBzY3JpcHQgdGhhdCBjYW4gZmluZCB2aWJldHVubmVsIHJlZ2FyZGxlc3Mgb2Ygbm9kZSB2ZXJzaW9uIG1hbmFnZXJcbmZ1bmN0aW9uIGNyZWF0ZVZpYmV0dW5uZWxXcmFwcGVyKCk6IHN0cmluZyB7XG4gIGNvbnN0IHsgdXNlcm5hbWUsIGhvbWUgfSA9IGdldEN1cnJlbnRVc2VyKCk7XG4gIGNvbnN0IHdyYXBwZXJQYXRoID0gYCR7aG9tZX0vLmxvY2FsL2Jpbi92aWJldHVubmVsLXN5c3RlbWRgO1xuICBjb25zdCB3cmFwcGVyQ29udGVudCA9IGAjIS9iaW4vYmFzaFxuIyBWaWJlVHVubmVsIFN5c3RlbWQgV3JhcHBlciBTY3JpcHRcbiMgVGhpcyBzY3JpcHQgZmluZHMgYW5kIGV4ZWN1dGVzIHZpYmV0dW5uZWwgZm9yIHVzZXI6ICR7dXNlcm5hbWV9XG5cbiMgRnVuY3Rpb24gdG8gbG9nIG1lc3NhZ2VzXG5sb2dfaW5mbygpIHtcbiAgICBlY2hvIFwiW0lORk9dICQxXCIgPiYyXG59XG5cbmxvZ19lcnJvcigpIHtcbiAgICBlY2hvIFwiW0VSUk9SXSAkMVwiID4mMlxufVxuXG4jIFNldCB1cCBlbnZpcm9ubWVudCBmb3IgdXNlciAke3VzZXJuYW1lfVxuZXhwb3J0IEhPTUU9XCIke2hvbWV9XCJcbmV4cG9ydCBVU0VSPVwiJHt1c2VybmFtZX1cIlxuXG4jIFRyeSB0byBmaW5kIHZpYmV0dW5uZWwgaW4gdmFyaW91cyB3YXlzXG5maW5kX3ZpYmV0dW5uZWwoKSB7XG4gICAgIyBNZXRob2QgMTogQ2hlY2sgaWYgdmliZXR1bm5lbCBpcyBpbiBQQVRIXG4gICAgaWYgY29tbWFuZCAtdiB2aWJldHVubmVsID4vZGV2L251bGwgMj4mMTsgdGhlblxuICAgICAgICBsb2dfaW5mbyBcIkZvdW5kIHZpYmV0dW5uZWwgaW4gUEFUSFwiXG4gICAgICAgIHZpYmV0dW5uZWwgXCIkQFwiXG4gICAgICAgIHJldHVybiAkP1xuICAgIGZpXG4gICAgXG4gICAgIyBNZXRob2QgMjogQ2hlY2sgZm9yIG52bSBpbnN0YWxsYXRpb25zXG4gICAgaWYgWyAtZCBcIiR7aG9tZX0vLm52bVwiIF07IHRoZW5cbiAgICAgICAgbG9nX2luZm8gXCJDaGVja2luZyBudm0gaW5zdGFsbGF0aW9uIGZvciB1c2VyICR7dXNlcm5hbWV9XCJcbiAgICAgICAgZXhwb3J0IE5WTV9ESVI9XCIke2hvbWV9Ly5udm1cIlxuICAgICAgICBbIC1zIFwiJE5WTV9ESVIvbnZtLnNoXCIgXSAmJiAuIFwiJE5WTV9ESVIvbnZtLnNoXCJcbiAgICAgICAgaWYgY29tbWFuZCAtdiB2aWJldHVubmVsID4vZGV2L251bGwgMj4mMTsgdGhlblxuICAgICAgICAgICAgbG9nX2luZm8gXCJGb3VuZCB2aWJldHVubmVsIHZpYSBudm1cIlxuICAgICAgICAgICAgdmliZXR1bm5lbCBcIiRAXCJcbiAgICAgICAgICAgIHJldHVybiAkP1xuICAgICAgICBmaVxuICAgIGZpXG4gICAgXG4gICAgIyBNZXRob2QgMzogQ2hlY2sgZm9yIGZubSBpbnN0YWxsYXRpb25zICBcbiAgICBpZiBbIC1kIFwiJHtob21lfS8ubG9jYWwvc2hhcmUvZm5tXCIgXSAmJiBbIC14IFwiJHtob21lfS8ubG9jYWwvc2hhcmUvZm5tL2ZubVwiIF07IHRoZW5cbiAgICAgICAgbG9nX2luZm8gXCJDaGVja2luZyBmbm0gaW5zdGFsbGF0aW9uIGZvciB1c2VyICR7dXNlcm5hbWV9XCJcbiAgICAgICAgZXhwb3J0IEZOTV9ESVI9XCIke2hvbWV9Ly5sb2NhbC9zaGFyZS9mbm1cIlxuICAgICAgICBleHBvcnQgUEFUSD1cIiR7aG9tZX0vLmxvY2FsL3NoYXJlL2ZubTokUEFUSFwiXG4gICAgICAgIGV4cG9ydCBTSEVMTD1cIi9iaW4vYmFzaFwiICAjIEZvcmNlIHNoZWxsIGZvciBmbm1cbiAgICAgICAgIyBJbml0aWFsaXplIGZubSB3aXRoIGV4cGxpY2l0IHNoZWxsIGFuZCB1c2UgdGhlIGRlZmF1bHQgbm9kZSB2ZXJzaW9uXG4gICAgICAgIGV2YWwgXCIkKFwiJHtob21lfS8ubG9jYWwvc2hhcmUvZm5tL2ZubVwiIGVudiAtLXNoZWxsIGJhc2gpXCIgMj4vZGV2L251bGwgfHwgdHJ1ZVxuICAgICAgICAjIFRyeSB0byB1c2UgdGhlIGRlZmF1bHQgbm9kZSB2ZXJzaW9uIG9yIGN1cnJlbnQgdmVyc2lvblxuICAgICAgICBcIiR7aG9tZX0vLmxvY2FsL3NoYXJlL2ZubS9mbm1cIiB1c2UgZGVmYXVsdCA+L2Rldi9udWxsIDI+JjEgfHwgXCIke2hvbWV9Ly5sb2NhbC9zaGFyZS9mbm0vZm5tXCIgdXNlIGN1cnJlbnQgPi9kZXYvbnVsbCAyPiYxIHx8IHRydWVcbiAgICAgICAgaWYgY29tbWFuZCAtdiB2aWJldHVubmVsID4vZGV2L251bGwgMj4mMTsgdGhlblxuICAgICAgICAgICAgbG9nX2luZm8gXCJGb3VuZCB2aWJldHVubmVsIHZpYSBmbm1cIlxuICAgICAgICAgICAgdmliZXR1bm5lbCBcIiRAXCJcbiAgICAgICAgICAgIHJldHVybiAkP1xuICAgICAgICBmaVxuICAgIGZpXG4gICAgXG4gICAgIyBNZXRob2QgNDogQ2hlY2sgY29tbW9uIGdsb2JhbCBucG0gbG9jYXRpb25zXG4gICAgZm9yIG5wbV9iaW4gaW4gXCIvdXNyL2xvY2FsL2Jpbi9ucG1cIiBcIi91c3IvYmluL25wbVwiIFwiL29wdC9ob21lYnJldy9iaW4vbnBtXCI7IGRvXG4gICAgICAgIGlmIFsgLXggXCIkbnBtX2JpblwiIF07IHRoZW5cbiAgICAgICAgICAgIGxvZ19pbmZvIFwiVHJ5aW5nIG5wbSBnbG9iYWwgd2l0aCAkbnBtX2JpblwiXG4gICAgICAgICAgICBOUE1fUFJFRklYPSQoXCIkbnBtX2JpblwiIGNvbmZpZyBnZXQgcHJlZml4IDI+L2Rldi9udWxsKVxuICAgICAgICAgICAgaWYgWyAtbiBcIiROUE1fUFJFRklYXCIgXSAmJiBbIC14IFwiJE5QTV9QUkVGSVgvYmluL3ZpYmV0dW5uZWxcIiBdOyB0aGVuXG4gICAgICAgICAgICAgICAgbG9nX2luZm8gXCJGb3VuZCB2aWJldHVubmVsIHZpYSBucG0gZ2xvYmFsOiAkTlBNX1BSRUZJWC9iaW4vdmliZXR1bm5lbFwiXG4gICAgICAgICAgICAgICAgXCIkTlBNX1BSRUZJWC9iaW4vdmliZXR1bm5lbFwiIFwiJEBcIlxuICAgICAgICAgICAgICAgIHJldHVybiAkP1xuICAgICAgICAgICAgZmlcbiAgICAgICAgZmlcbiAgICBkb25lXG4gICAgXG4gICAgIyBNZXRob2QgNTogVHJ5IHRvIHJ1biB3aXRoIG5vZGUgZGlyZWN0bHkgdXNpbmcgZ2xvYmFsIG5wbSBwYWNrYWdlXG4gICAgZm9yIG5vZGVfYmluIGluIFwiL3Vzci9sb2NhbC9iaW4vbm9kZVwiIFwiL3Vzci9iaW4vbm9kZVwiIFwiL29wdC9ob21lYnJldy9iaW4vbm9kZVwiOyBkb1xuICAgICAgICBpZiBbIC14IFwiJG5vZGVfYmluXCIgXTsgdGhlblxuICAgICAgICAgICAgZm9yIHNjcmlwdF9wYXRoIGluIFwiL3Vzci9sb2NhbC9saWIvbm9kZV9tb2R1bGVzL3ZpYmV0dW5uZWwvZGlzdC9jbGkuanNcIiBcIi91c3IvbGliL25vZGVfbW9kdWxlcy92aWJldHVubmVsL2Rpc3QvY2xpLmpzXCI7IGRvXG4gICAgICAgICAgICAgICAgaWYgWyAtZiBcIiRzY3JpcHRfcGF0aFwiIF07IHRoZW5cbiAgICAgICAgICAgICAgICAgICAgbG9nX2luZm8gXCJSdW5uaW5nIHZpYmV0dW5uZWwgdmlhIG5vZGU6ICRub2RlX2JpbiAkc2NyaXB0X3BhdGhcIlxuICAgICAgICAgICAgICAgICAgICBcIiRub2RlX2JpblwiIFwiJHNjcmlwdF9wYXRoXCIgXCIkQFwiXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiAkP1xuICAgICAgICAgICAgICAgIGZpXG4gICAgICAgICAgICBkb25lXG4gICAgICAgIGZpXG4gICAgZG9uZVxuICAgIFxuICAgIGxvZ19lcnJvciBcIkNvdWxkIG5vdCBmaW5kIHZpYmV0dW5uZWwgaW5zdGFsbGF0aW9uIGZvciB1c2VyICR7dXNlcm5hbWV9XCJcbiAgICBsb2dfZXJyb3IgXCJQbGVhc2UgZW5zdXJlIHZpYmV0dW5uZWwgaXMgaW5zdGFsbGVkIGdsb2JhbGx5OiBucG0gaW5zdGFsbCAtZyB2aWJldHVubmVsXCJcbiAgICByZXR1cm4gMVxufVxuXG4jIEV4ZWN1dGUgdGhlIGZ1bmN0aW9uIHdpdGggYWxsIGFyZ3VtZW50c1xuZmluZF92aWJldHVubmVsIFwiJEBcIlxuYDtcblxuICB0cnkge1xuICAgIC8vIEVuc3VyZSB+Ly5sb2NhbC9iaW4gZGlyZWN0b3J5IGV4aXN0c1xuICAgIGNvbnN0IGxvY2FsQmluRGlyID0gYCR7aG9tZX0vLmxvY2FsL2JpbmA7XG4gICAgaWYgKCFleGlzdHNTeW5jKGxvY2FsQmluRGlyKSkge1xuICAgICAgbWtkaXJTeW5jKGxvY2FsQmluRGlyLCB7IHJlY3Vyc2l2ZTogdHJ1ZSB9KTtcbiAgICAgIHByaW50SW5mbyhgQ3JlYXRlZCBkaXJlY3Rvcnk6ICR7bG9jYWxCaW5EaXJ9YCk7XG4gICAgfVxuXG4gICAgLy8gQ3JlYXRlIHRoZSB3cmFwcGVyIHNjcmlwdFxuICAgIHdyaXRlRmlsZVN5bmMod3JhcHBlclBhdGgsIHdyYXBwZXJDb250ZW50KTtcbiAgICBjaG1vZFN5bmMod3JhcHBlclBhdGgsIDBvNzU1KTtcblxuICAgIHByaW50U3VjY2VzcyhgQ3JlYXRlZCB3cmFwcGVyIHNjcmlwdCBhdCAke3dyYXBwZXJQYXRofWApO1xuICAgIHJldHVybiB3cmFwcGVyUGF0aDtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICBwcmludEVycm9yKGBGYWlsZWQgdG8gY3JlYXRlIHdyYXBwZXIgc2NyaXB0OiAke2Vycm9yfWApO1xuICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgfVxufVxuXG4vLyBWZXJpZnkgdGhhdCB2aWJldHVubmVsIGlzIGFjY2Vzc2libGUgYW5kIHJldHVybiB3cmFwcGVyIHBhdGhcbmZ1bmN0aW9uIGNoZWNrVmliZXR1bm5lbEFuZENyZWF0ZVdyYXBwZXIoKTogc3RyaW5nIHtcbiAgLy8gRmlyc3QsIHZlcmlmeSB0aGF0IHZpYmV0dW5uZWwgaXMgYWN0dWFsbHkgaW5zdGFsbGVkIHNvbWV3aGVyZVxuICB0cnkge1xuICAgIGNvbnN0IHZpYmV0dW5uZWxQYXRoID0gZXhlY1N5bmMoJ3doaWNoIHZpYmV0dW5uZWwnLCB7IGVuY29kaW5nOiAndXRmOCcsIHN0ZGlvOiAncGlwZScgfSkudHJpbSgpO1xuICAgIHByaW50SW5mbyhgRm91bmQgVmliZVR1bm5lbCBhdDogJHt2aWJldHVubmVsUGF0aH1gKTtcbiAgfSBjYXRjaCAoX2Vycm9yKSB7XG4gICAgcHJpbnRFcnJvcignVmliZVR1bm5lbCBpcyBub3QgaW5zdGFsbGVkIG9yIG5vdCBhY2Nlc3NpYmxlLiBQbGVhc2UgaW5zdGFsbCBpdCBmaXJzdDonKTtcbiAgICBjb25zb2xlLmxvZygnICBucG0gaW5zdGFsbCAtZyB2aWJldHVubmVsJyk7XG4gICAgcHJvY2Vzcy5leGl0KDEpO1xuICB9XG5cbiAgLy8gQ3JlYXRlIGFuZCByZXR1cm4gdGhlIHdyYXBwZXIgc2NyaXB0IHBhdGhcbiAgcmV0dXJuIGNyZWF0ZVZpYmV0dW5uZWxXcmFwcGVyKCk7XG59XG5cbi8vIFJlbW92ZSB3cmFwcGVyIHNjcmlwdCBkdXJpbmcgdW5pbnN0YWxsXG5mdW5jdGlvbiByZW1vdmVWaWJldHVubmVsV3JhcHBlcigpOiB2b2lkIHtcbiAgY29uc3QgeyBob21lIH0gPSBnZXRDdXJyZW50VXNlcigpO1xuICBjb25zdCB3cmFwcGVyUGF0aCA9IGAke2hvbWV9Ly5sb2NhbC9iaW4vdmliZXR1bm5lbC1zeXN0ZW1kYDtcbiAgdHJ5IHtcbiAgICBpZiAoZXhpc3RzU3luYyh3cmFwcGVyUGF0aCkpIHtcbiAgICAgIHVubGlua1N5bmMod3JhcHBlclBhdGgpO1xuICAgICAgcHJpbnRJbmZvKCdSZW1vdmVkIHdyYXBwZXIgc2NyaXB0Jyk7XG4gICAgfVxuICB9IGNhdGNoIChfZXJyb3IpIHtcbiAgICAvLyBJZ25vcmUgZXJyb3JzIHdoZW4gcmVtb3Zpbmcgd3JhcHBlclxuICB9XG59XG5cbi8vIE5vIG5lZWQgdG8gY3JlYXRlIHVzZXJzIG9yIGRpcmVjdG9yaWVzIC0gdXNpbmcgY3VycmVudCB1c2VyXG5cbi8vIEdldCB0aGUgc3lzdGVtZCBzZXJ2aWNlIHRlbXBsYXRlXG5mdW5jdGlvbiBnZXRTZXJ2aWNlVGVtcGxhdGUodmliZXR1bm5lbFBhdGg6IHN0cmluZyk6IHN0cmluZyB7XG4gIGNvbnN0IHsgaG9tZSB9ID0gZ2V0Q3VycmVudFVzZXIoKTtcblxuICByZXR1cm4gYFtVbml0XVxuRGVzY3JpcHRpb249VmliZVR1bm5lbCAtIFRlcm1pbmFsIHNoYXJpbmcgc2VydmVyIHdpdGggd2ViIGludGVyZmFjZVxuRG9jdW1lbnRhdGlvbj1odHRwczovL2dpdGh1Yi5jb20vYW1hbnR1cy1haS92aWJldHVubmVsXG5BZnRlcj1uZXR3b3JrLnRhcmdldFxuV2FudHM9bmV0d29yay50YXJnZXRcblxuW1NlcnZpY2VdXG5UeXBlPXNpbXBsZVxuV29ya2luZ0RpcmVjdG9yeT0ke2hvbWV9XG5FeGVjU3RhcnQ9JHt2aWJldHVubmVsUGF0aH0gLS1wb3J0IDQwMjAgLS1iaW5kIDAuMC4wLjBcblJlc3RhcnQ9YWx3YXlzXG5SZXN0YXJ0U2VjPTEwXG5TdGFuZGFyZE91dHB1dD1qb3VybmFsXG5TdGFuZGFyZEVycm9yPWpvdXJuYWxcblN5c2xvZ0lkZW50aWZpZXI9JHtTRVJWSUNFX05BTUV9XG5cbiMgRW52aXJvbm1lbnQgLSBwcmVzZXJ2ZSB1c2VyIGVudmlyb25tZW50IGZvciBub2RlIHZlcnNpb24gbWFuYWdlcnNcbkVudmlyb25tZW50PU5PREVfRU5WPXByb2R1Y3Rpb25cbkVudmlyb25tZW50PVZJQkVUVU5ORUxfTE9HX0xFVkVMPWluZm9cbkVudmlyb25tZW50PUhPTUU9JWhcbkVudmlyb25tZW50PVVTRVI9JWlcblxuIyBSZXNvdXJjZSBsaW1pdHNcbkxpbWl0Tk9GSUxFPTY1NTM2XG5NZW1vcnlIaWdoPTUxMk1cbk1lbW9yeU1heD0xR1xuXG5bSW5zdGFsbF1cbldhbnRlZEJ5PWRlZmF1bHQudGFyZ2V0YDtcbn1cblxuLy8gSW5zdGFsbCBzeXN0ZW1kIHNlcnZpY2VcbmZ1bmN0aW9uIGluc3RhbGxTZXJ2aWNlKHZpYmV0dW5uZWxQYXRoOiBzdHJpbmcpOiB2b2lkIHtcbiAgcHJpbnRJbmZvKCdJbnN0YWxsaW5nIHVzZXIgc3lzdGVtZCBzZXJ2aWNlLi4uJyk7XG5cbiAgY29uc3QgeyBob21lIH0gPSBnZXRDdXJyZW50VXNlcigpO1xuICBjb25zdCBzeXN0ZW1kRGlyID0gYCR7aG9tZX0vLmNvbmZpZy9zeXN0ZW1kL3VzZXJgO1xuICBjb25zdCBzZXJ2aWNlQ29udGVudCA9IGdldFNlcnZpY2VUZW1wbGF0ZSh2aWJldHVubmVsUGF0aCk7XG4gIGNvbnN0IHNlcnZpY2VQYXRoID0gam9pbihzeXN0ZW1kRGlyLCBTRVJWSUNFX0ZJTEUpO1xuXG4gIHRyeSB7XG4gICAgLy8gQ3JlYXRlIHVzZXIgc3lzdGVtZCBkaXJlY3RvcnkgaWYgaXQgZG9lc24ndCBleGlzdFxuICAgIG1rZGlyU3luYyhzeXN0ZW1kRGlyLCB7IHJlY3Vyc2l2ZTogdHJ1ZSB9KTtcblxuICAgIHdyaXRlRmlsZVN5bmMoc2VydmljZVBhdGgsIHNlcnZpY2VDb250ZW50KTtcbiAgICBjaG1vZFN5bmMoc2VydmljZVBhdGgsIDBvNjQ0KTtcblxuICAgIC8vIFJlbG9hZCB1c2VyIHN5c3RlbWRcbiAgICBleGVjU3luYygnc3lzdGVtY3RsIC0tdXNlciBkYWVtb24tcmVsb2FkJywgeyBzdGRpbzogJ3BpcGUnIH0pO1xuICAgIHByaW50U3VjY2VzcygnVXNlciBzeXN0ZW1kIHNlcnZpY2UgaW5zdGFsbGVkJyk7XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgcHJpbnRFcnJvcihgRmFpbGVkIHRvIGluc3RhbGwgc2VydmljZTogJHtlcnJvcn1gKTtcbiAgICBwcm9jZXNzLmV4aXQoMSk7XG4gIH1cbn1cblxuLy8gQ29uZmlndXJlIHNlcnZpY2VcbmZ1bmN0aW9uIGNvbmZpZ3VyZVNlcnZpY2UoKTogdm9pZCB7XG4gIHByaW50SW5mbygnQ29uZmlndXJpbmcgc2VydmljZS4uLicpO1xuXG4gIHRyeSB7XG4gICAgLy8gRW5hYmxlIHRoZSB1c2VyIHNlcnZpY2VcbiAgICBleGVjU3luYyhgc3lzdGVtY3RsIC0tdXNlciBlbmFibGUgJHtTRVJWSUNFX05BTUV9YCwgeyBzdGRpbzogJ3BpcGUnIH0pO1xuICAgIHByaW50U3VjY2VzcygnVXNlciBzZXJ2aWNlIGVuYWJsZWQgZm9yIGF1dG9tYXRpYyBzdGFydHVwJyk7XG5cbiAgICAvLyBFbmFibGUgbGluZ2VyaW5nIHNvIHNlcnZpY2Ugc3RhcnRzIG9uIGJvb3QgZXZlbiB3aGVuIHVzZXIgbm90IGxvZ2dlZCBpblxuICAgIHRyeSB7XG4gICAgICBjb25zdCB7IHVzZXJuYW1lIH0gPSBnZXRDdXJyZW50VXNlcigpO1xuICAgICAgZXhlY1N5bmMoYGxvZ2luY3RsIGVuYWJsZS1saW5nZXIgJHt1c2VybmFtZX1gLCB7IHN0ZGlvOiAncGlwZScgfSk7XG4gICAgICBwcmludFN1Y2Nlc3MoJ1VzZXIgbGluZ2VyaW5nIGVuYWJsZWQgLSBzZXJ2aWNlIHdpbGwgc3RhcnQgb24gYm9vdCcpO1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBwcmludEVycm9yKGBGYWlsZWQgdG8gZW5hYmxlIGxpbmdlcmluZzogJHtlcnJvcn1gKTtcbiAgICAgIHByaW50RXJyb3IoJ1NlcnZpY2Ugd2lsbCBvbmx5IHN0YXJ0IHdoZW4gdXNlciBsb2dzIGluJyk7XG4gICAgfVxuICB9IGNhdGNoIChlcnJvcikge1xuICAgIHByaW50RXJyb3IoYEZhaWxlZCB0byBjb25maWd1cmUgc2VydmljZTogJHtlcnJvcn1gKTtcbiAgICBwcm9jZXNzLmV4aXQoMSk7XG4gIH1cbn1cblxuLy8gRGlzcGxheSB1c2FnZSBpbnN0cnVjdGlvbnNcbmZ1bmN0aW9uIHNob3dVc2FnZSgpOiB2b2lkIHtcbiAgY29uc3QgeyB1c2VybmFtZSwgaG9tZSB9ID0gZ2V0Q3VycmVudFVzZXIoKTtcblxuICBwcmludFN1Y2Nlc3MoJ1ZpYmVUdW5uZWwgc3lzdGVtZCBzZXJ2aWNlIGluc3RhbGxhdGlvbiBjb21wbGV0ZWQhJyk7XG4gIGNvbnNvbGUubG9nKCcnKTtcbiAgY29uc29sZS5sb2coJ1VzYWdlOicpO1xuICBjb25zb2xlLmxvZyhgICBzeXN0ZW1jdGwgLS11c2VyIHN0YXJ0ICR7U0VSVklDRV9OQU1FfSAgICAgIyBTdGFydCB0aGUgc2VydmljZWApO1xuICBjb25zb2xlLmxvZyhgICBzeXN0ZW1jdGwgLS11c2VyIHN0b3AgJHtTRVJWSUNFX05BTUV9ICAgICAgIyBTdG9wIHRoZSBzZXJ2aWNlYCk7XG4gIGNvbnNvbGUubG9nKGAgIHN5c3RlbWN0bCAtLXVzZXIgcmVzdGFydCAke1NFUlZJQ0VfTkFNRX0gICAjIFJlc3RhcnQgdGhlIHNlcnZpY2VgKTtcbiAgY29uc29sZS5sb2coYCAgc3lzdGVtY3RsIC0tdXNlciBzdGF0dXMgJHtTRVJWSUNFX05BTUV9ICAgICMgQ2hlY2sgc2VydmljZSBzdGF0dXNgKTtcbiAgY29uc29sZS5sb2coYCAgc3lzdGVtY3RsIC0tdXNlciBlbmFibGUgJHtTRVJWSUNFX05BTUV9ICAgICMgRW5hYmxlIGF1dG8tc3RhcnQgKGFscmVhZHkgZG9uZSlgKTtcbiAgY29uc29sZS5sb2coYCAgc3lzdGVtY3RsIC0tdXNlciBkaXNhYmxlICR7U0VSVklDRV9OQU1FfSAgICMgRGlzYWJsZSBhdXRvLXN0YXJ0YCk7XG4gIGNvbnNvbGUubG9nKCcnKTtcbiAgY29uc29sZS5sb2coJ0xvZ3M6Jyk7XG4gIGNvbnNvbGUubG9nKGAgIGpvdXJuYWxjdGwgLS11c2VyIC11ICR7U0VSVklDRV9OQU1FfSAtZiAgICAjIEZvbGxvdyBsb2dzIGluIHJlYWwtdGltZWApO1xuICBjb25zb2xlLmxvZyhgICBqb3VybmFsY3RsIC0tdXNlciAtdSAke1NFUlZJQ0VfTkFNRX0gICAgICAgIyBWaWV3IGFsbCBsb2dzYCk7XG4gIGNvbnNvbGUubG9nKCcnKTtcbiAgY29uc29sZS5sb2coJ0NvbmZpZ3VyYXRpb246Jyk7XG4gIGNvbnNvbGUubG9nKCcgIFNlcnZpY2UgcnVucyBvbiBwb3J0IDQwMjAgYnkgZGVmYXVsdCcpO1xuICBjb25zb2xlLmxvZygnICBXZWIgaW50ZXJmYWNlOiBodHRwOi8vbG9jYWxob3N0OjQwMjAnKTtcbiAgY29uc29sZS5sb2coYCAgU2VydmljZSBydW5zIGFzIHVzZXI6ICR7dXNlcm5hbWV9YCk7XG4gIGNvbnNvbGUubG9nKGAgIFdvcmtpbmcgZGlyZWN0b3J5OiAke2hvbWV9YCk7XG4gIGNvbnNvbGUubG9nKGAgIFdyYXBwZXIgc2NyaXB0OiAke2hvbWV9Ly5sb2NhbC9iaW4vdmliZXR1bm5lbC1zeXN0ZW1kYCk7XG4gIGNvbnNvbGUubG9nKCcnKTtcbiAgY29uc29sZS5sb2coYFRvIGN1c3RvbWl6ZSB0aGUgc2VydmljZSwgZWRpdDogJHtob21lfS8uY29uZmlnL3N5c3RlbWQvdXNlci8ke1NFUlZJQ0VfRklMRX1gKTtcbiAgY29uc29sZS5sb2coXG4gICAgYFRoZW4gcnVuOiBzeXN0ZW1jdGwgLS11c2VyIGRhZW1vbi1yZWxvYWQgJiYgc3lzdGVtY3RsIC0tdXNlciByZXN0YXJ0ICR7U0VSVklDRV9OQU1FfWBcbiAgKTtcbn1cblxuLy8gVW5pbnN0YWxsIGZ1bmN0aW9uXG5mdW5jdGlvbiB1bmluc3RhbGxTZXJ2aWNlKCk6IHZvaWQge1xuICBwcmludEluZm8oJ1VuaW5zdGFsbGluZyBWaWJlVHVubmVsIHVzZXIgc3lzdGVtZCBzZXJ2aWNlLi4uJyk7XG5cbiAgdHJ5IHtcbiAgICAvLyBTdG9wIGFuZCBkaXNhYmxlIHVzZXIgc2VydmljZVxuICAgIHRyeSB7XG4gICAgICBleGVjU3luYyhgc3lzdGVtY3RsIC0tdXNlciBpcy1hY3RpdmUgJHtTRVJWSUNFX05BTUV9YCwgeyBzdGRpbzogJ3BpcGUnIH0pO1xuICAgICAgZXhlY1N5bmMoYHN5c3RlbWN0bCAtLXVzZXIgc3RvcCAke1NFUlZJQ0VfTkFNRX1gLCB7IHN0ZGlvOiAncGlwZScgfSk7XG4gICAgICBwcmludEluZm8oJ1VzZXIgc2VydmljZSBzdG9wcGVkJyk7XG4gICAgfSBjYXRjaCAoX2Vycm9yKSB7XG4gICAgICAvLyBTZXJ2aWNlIG5vdCBydW5uaW5nXG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgIGV4ZWNTeW5jKGBzeXN0ZW1jdGwgLS11c2VyIGlzLWVuYWJsZWQgJHtTRVJWSUNFX05BTUV9YCwgeyBzdGRpbzogJ3BpcGUnIH0pO1xuICAgICAgZXhlY1N5bmMoYHN5c3RlbWN0bCAtLXVzZXIgZGlzYWJsZSAke1NFUlZJQ0VfTkFNRX1gLCB7IHN0ZGlvOiAncGlwZScgfSk7XG4gICAgICBwcmludEluZm8oJ1VzZXIgc2VydmljZSBkaXNhYmxlZCcpO1xuICAgIH0gY2F0Y2ggKF9lcnJvcikge1xuICAgICAgLy8gU2VydmljZSBub3QgZW5hYmxlZFxuICAgIH1cblxuICAgIC8vIFJlbW92ZSBzZXJ2aWNlIGZpbGVcbiAgICBjb25zdCB7IGhvbWUgfSA9IGdldEN1cnJlbnRVc2VyKCk7XG4gICAgY29uc3Qgc3lzdGVtZERpciA9IGAke2hvbWV9Ly5jb25maWcvc3lzdGVtZC91c2VyYDtcbiAgICBjb25zdCBzZXJ2aWNlUGF0aCA9IGpvaW4oc3lzdGVtZERpciwgU0VSVklDRV9GSUxFKTtcbiAgICBpZiAoZXhpc3RzU3luYyhzZXJ2aWNlUGF0aCkpIHtcbiAgICAgIHVubGlua1N5bmMoc2VydmljZVBhdGgpO1xuICAgICAgcHJpbnRJbmZvKCdTZXJ2aWNlIGZpbGUgcmVtb3ZlZCcpO1xuICAgIH1cblxuICAgIC8vIFJlbG9hZCB1c2VyIHN5c3RlbWRcbiAgICBleGVjU3luYygnc3lzdGVtY3RsIC0tdXNlciBkYWVtb24tcmVsb2FkJywgeyBzdGRpbzogJ3BpcGUnIH0pO1xuXG4gICAgLy8gUmVtb3ZlIHdyYXBwZXIgc2NyaXB0XG4gICAgcmVtb3ZlVmliZXR1bm5lbFdyYXBwZXIoKTtcblxuICAgIC8vIE9wdGlvbmFsbHkgZGlzYWJsZSBsaW5nZXJpbmcgKGFzayB1c2VyKVxuICAgIGNvbnN0IHsgdXNlcm5hbWUgfSA9IGdldEN1cnJlbnRVc2VyKCk7XG4gICAgcHJpbnRJbmZvKCdOb3RlOiBVc2VyIGxpbmdlcmluZyBpcyBzdGlsbCBlbmFibGVkLiBUbyBkaXNhYmxlOicpO1xuICAgIGNvbnNvbGUubG9nKGAgIGxvZ2luY3RsIGRpc2FibGUtbGluZ2VyICR7dXNlcm5hbWV9YCk7XG5cbiAgICBwcmludFN1Y2Nlc3MoJ1ZpYmVUdW5uZWwgdXNlciBzeXN0ZW1kIHNlcnZpY2UgdW5pbnN0YWxsZWQnKTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICBwcmludEVycm9yKGBGYWlsZWQgdG8gdW5pbnN0YWxsIHNlcnZpY2U6ICR7ZXJyb3J9YCk7XG4gICAgcHJvY2Vzcy5leGl0KDEpO1xuICB9XG59XG5cbi8vIENoZWNrIHNlcnZpY2Ugc3RhdHVzXG5mdW5jdGlvbiBjaGVja1NlcnZpY2VTdGF0dXMoKTogdm9pZCB7XG4gIHRyeSB7XG4gICAgY29uc3Qgc3RhdHVzID0gZXhlY1N5bmMoYHN5c3RlbWN0bCAtLXVzZXIgc3RhdHVzICR7U0VSVklDRV9OQU1FfWAsIHsgZW5jb2Rpbmc6ICd1dGY4JyB9KTtcbiAgICBjb25zb2xlLmxvZyhzdGF0dXMpO1xuICB9IGNhdGNoIChlcnJvcikge1xuICAgIC8vIHN5c3RlbWN0bCBzdGF0dXMgcmV0dXJucyBub24temVybyBmb3IgaW5hY3RpdmUgc2VydmljZXMsIHdoaWNoIGlzIG5vcm1hbFxuICAgIGlmIChlcnJvciBpbnN0YW5jZW9mIEVycm9yICYmICdzdGRvdXQnIGluIGVycm9yKSB7XG4gICAgICBjb25zb2xlLmxvZyhlcnJvci5zdGRvdXQpO1xuICAgIH0gZWxzZSB7XG4gICAgICBwcmludEVycm9yKGBGYWlsZWQgdG8gZ2V0IHNlcnZpY2Ugc3RhdHVzOiAke2Vycm9yfWApO1xuICAgIH1cbiAgfVxufVxuXG4vLyBDaGVjayBpZiBydW5uaW5nIGFzIHJvb3QgYW5kIHByZXZlbnQgZXhlY3V0aW9uXG5mdW5jdGlvbiBjaGVja05vdFJvb3QoKTogdm9pZCB7XG4gIGlmIChwcm9jZXNzLmdldHVpZCAmJiBwcm9jZXNzLmdldHVpZCgpID09PSAwKSB7XG4gICAgcHJpbnRFcnJvcignVGhpcyBpbnN0YWxsZXIgbXVzdCBOT1QgYmUgcnVuIGFzIHJvb3QhJyk7XG4gICAgcHJpbnRFcnJvcignVmliZVR1bm5lbCBzeXN0ZW1kIHNlcnZpY2Ugc2hvdWxkIHJ1biBhcyBhIHJlZ3VsYXIgdXNlciBmb3Igc2VjdXJpdHkuJyk7XG4gICAgcHJpbnRFcnJvcignUGxlYXNlIHJ1biB0aGlzIGNvbW1hbmQgYXMgYSByZWd1bGFyIHVzZXIgKHdpdGhvdXQgc3VkbykuJyk7XG4gICAgcHJvY2Vzcy5leGl0KDEpO1xuICB9XG59XG5cbi8vIE1haW4gaW5zdGFsbGF0aW9uIGZ1bmN0aW9uXG5leHBvcnQgZnVuY3Rpb24gaW5zdGFsbFN5c3RlbWRTZXJ2aWNlKGFjdGlvbjogc3RyaW5nID0gJ2luc3RhbGwnKTogdm9pZCB7XG4gIC8vIFByZXZlbnQgcnVubmluZyBhcyByb290IGZvciBzZWN1cml0eVxuICBjaGVja05vdFJvb3QoKTtcblxuICBzd2l0Y2ggKGFjdGlvbikge1xuICAgIGNhc2UgJ2luc3RhbGwnOiB7XG4gICAgICBwcmludEluZm8oJ0luc3RhbGxpbmcgVmliZVR1bm5lbCB1c2VyIHN5c3RlbWQgc2VydmljZS4uLicpO1xuXG4gICAgICBjb25zdCB3cmFwcGVyUGF0aCA9IGNoZWNrVmliZXR1bm5lbEFuZENyZWF0ZVdyYXBwZXIoKTtcbiAgICAgIGluc3RhbGxTZXJ2aWNlKHdyYXBwZXJQYXRoKTtcbiAgICAgIGNvbmZpZ3VyZVNlcnZpY2UoKTtcbiAgICAgIHNob3dVc2FnZSgpO1xuICAgICAgYnJlYWs7XG4gICAgfVxuXG4gICAgY2FzZSAndW5pbnN0YWxsJzoge1xuICAgICAgdW5pbnN0YWxsU2VydmljZSgpO1xuICAgICAgYnJlYWs7XG4gICAgfVxuXG4gICAgY2FzZSAnc3RhdHVzJzpcbiAgICAgIGNoZWNrU2VydmljZVN0YXR1cygpO1xuICAgICAgYnJlYWs7XG5cbiAgICBkZWZhdWx0OlxuICAgICAgY29uc29sZS5sb2coJ1VzYWdlOiB2aWJldHVubmVsIHN5c3RlbWQgW2luc3RhbGx8dW5pbnN0YWxsfHN0YXR1c10nKTtcbiAgICAgIGNvbnNvbGUubG9nKCcgIGluc3RhbGwgICAtIEluc3RhbGwgVmliZVR1bm5lbCB1c2VyIHN5c3RlbWQgc2VydmljZSAoZGVmYXVsdCknKTtcbiAgICAgIGNvbnNvbGUubG9nKCcgIHVuaW5zdGFsbCAtIFJlbW92ZSBWaWJlVHVubmVsIHVzZXIgc3lzdGVtZCBzZXJ2aWNlJyk7XG4gICAgICBjb25zb2xlLmxvZygnICBzdGF0dXMgICAgLSBDaGVjayBzZXJ2aWNlIHN0YXR1cycpO1xuICAgICAgcHJvY2Vzcy5leGl0KDEpO1xuICB9XG59XG4iXX0=