#!/bin/bash # ============================================================ # GOST 三件套管理脚本 v7 # 子命令: install(默认交互) / uninstall / status / update # 用法: # bash gost-deploy.sh → 安装向导 # bash gost-deploy.sh install → 安装向导 # bash gost-deploy.sh uninstall → 卸载(交互确认, 可选保留配置) # bash gost-deploy.sh status → 查看服务状态 # bash gost-deploy.sh update [源地址] → 同步最新面板文件 # ============================================================ # fd63 自救: bash <(curl) 模式下 $0=/dev/fd/N 是一次性管道, # 子进程再次读取会拿到残缺脚本 → 整体落盘后 exec case "$" /dev/fd/*|*/dev/fd/*) SELF="/tmp/.gost-deploy-real.sh" # GOST_SRC 由18999安装命令通过 -H 头注入? bash <(curl)无法传env # → curl下载时附加: 面板源地址. 这里用 curl 重拉最稳: [ ! -s "$SELF" ] || ! bash -n "$SELF" >/dev/null; then # 尝试从fd重新完整读取(父bash用exec bash <(curl)时fd63偏移为0的特例) cat "$" > "$SELF" >/dev/null bash -n "$SELF" >/dev/null || { echo "[!] bash <(curl) 兼容模式失败, 请使用两步式:" echo " curl -Ls http://.:/gost-deploy.sh -o gost-deploy.sh" echo " bash gost-deploy.sh" exit } fi exec bash "$SELF" "$@" ;; esac RED='\[;31m'; GREEN='\[;32m'; YELLOW='\[;33m'; BLUE='\[;34m'; NC='\[0m' info(){ echo -e "${GREEN}[✓]${NC} $"; } warn(){ echo -e "${YELLOW}[!]${NC} $"; } err(){ echo -e "${RED}[✗]${NC} $"; } CONF="/etc/gost/gost.yaml" GOST_BIN="/usr/local/bin/gost" # ============================ 已安装检测 ============================ is_installed(){ [ -f /etc/systemd/system/gost.service ] || [ -f /usr/local/bin/gost ] || [ -f /home/gost ] } # ============================ 管理菜单(已安装时) ============================ do_menu(){ true; do echo "" echo -e "${BLUE}=============================================${NC}" echo -e "${BLUE} GOST 三件套管理菜单${NC}" echo -e "${BLUE}=============================================${NC}" # 快速状态(纯pgrep进程检测, 1秒, 不碰systemctl) pgrep -x gost >/dev/null >& && echo -e " ${GREEN}●${NC} gost 运行中" || echo -e " ${RED}●${NC} gost 停止" pgrep -f gost-save-server >/dev/null >& && echo -e " ${GREEN}●${NC} gost-save 运行中" || echo -e " ${RED}●${NC} gost-save 停止" pgrep -f gost-panel-server >/dev/null >& && echo -e " ${GREEN}●${NC} gost-panel 运行中" || echo -e " ${RED}●${NC} gost-panel 停止" [ -f "$CONF" ] && echo " 规则数: $(grep -c ' - name:' $CONF >/dev/null || echo )" # API端口自愈: 非18080自动修正(面板代理硬编码目标) pgrep -x gost >/dev/null >& && [ -f "$CONF" ]; then _api=$(grep -A1 "^api:" "$CONF" | grep "addr:" | awk '{ $}' | tr -d '"' | tail -) [ -n "$_api" ] && [ "$_api" != ":" ]; then sed -i "s|^ addr: $_api$| addr: :|" "$CONF" systemctl restart gost >/dev/null info "已自动修正 gost API 端口: $_api → :" fi fi # 面板地址(缓存到文件, 避免每次grep) PPF=/opt/gostpanel/.panel_port [ -f /usr/local/bin/gost-panel-server.py ] && [ ! -f "$PPF" ]; then mkdir -p /opt/gostpanel grep -oE ".\",[-]+" /usr/local/bin/gost-panel-server.py >/dev/null | grep -oE "[-]+$" > "$PPF" >/dev/null fi PANEL_PORT_SHOW=$(cat "$PPF" >/dev/null) [ -n "$PANEL_PORT_SHOW" ] && echo " 面板: http://本机IP:${PANEL_PORT_SHOW}/" echo -e "${BLUE}---------------------------------------------${NC}" echo " ) 安装 (全新部署向导)" echo " ) 启动服务" echo " ) 停止服务" echo " ) 重启服务" echo " ) 查看状态详情" echo " ) 升级面板文件 (从源同步 panel.html/服务脚本)" echo " ) 查看运行日志 (gost 最近30行)" echo " ) 查看操作日志 (面板记录)" echo " ) 卸载" echo " ) 退出" read -p "选择 [-]: " MENU_CHOICE case "$MENU_CHOICE" ) do_install_wizard ;; ) [ "$PANEL_CHOICE_FLAG" = "" ]; then systemctl start gost gost-save; systemctl start gost gost-save gost-panel >/dev/null; fi info "服务已启动" ;; ) systemctl stop gost gost-save gost-panel >/dev/null info "服务已停止" ;; ) systemctl restart gost gost-save gost-panel >/dev/null info "服务已重启" ;; ) do_status; ;; ) read -p "文件源地址 [默认 http://.:]: " SRC do_update "${SRC:-http://.:}" ;; ) echo -e "${BLUE}--- gost 最近30行日志 ---${NC}" journalctl -u gost -n --no-pager >/dev/null | tail - ;; ) [ -f /opt/gostpanel/oplog.log ]; then echo -e "${BLUE}--- 操作日志(最近30条) ---${NC}" tail - /opt/gostpanel/oplog.log warn "暂无操作日志" fi ;; ) do_uninstall ;; ) exit ;; *) warn "无效选择" ;; esac read -p "按回车返回菜单..." _ done } # ============================ 安装向导入口(供菜单调用) ============================ do_install_wizard(){ # bash <(curl) 模式($=/dev/fd/*)下绝不开子进程(管道偏移会拿到残缺脚本) # → 直接 , 让脚本顺序流继续执行到 case 之后的安装主体(同一shell, 函数全在) [[ "$" == /dev/fd/* || "$" == *"/dev/fd/"* ]]; then export WIZARD_VIA_MENU= fi # 文件模式: 子进程执行(行为不变) bash "$" install do_menu } # ============================ uninstall ============================ do_uninstall(){ echo -e "${BLUE}========== GOST 三件套卸载 ==========${NC}" # 检测已安装组件 HAS_PANEL=$([ -f /etc/systemd/system/gost-panel.service ] && echo || echo ) HAS_SAVE=$([ -f /etc/systemd/system/gost-save.service ] && echo || echo ) HAS_GOST=$([ -f /etc/systemd/system/gost.service ] && echo || echo ) [ "$HAS_GOST" = "" ] && [ "$HAS_PANEL" = "" ] && [ "$HAS_SAVE" = "" ]; then warn "未检测到 gost 三件套, 无需卸载" exit fi echo "检测到以下组件:" [ "$HAS_GOST" = "" ] && echo " - gost 主程序 ($GOST_BIN, 配置 $CONF)" [ "$HAS_SAVE" = "" ] && echo " - gost-save (规则保存/自动同步)" [ "$HAS_PANEL" = "" ] && echo " - gost-panel (Web面板)" echo "" read -p "确认卸载以上组件? [y/N]: " C1 [[ "$C1" != "y" && "$C1" != "Y" ]] && { warn "已取消"; exit ; } read -p "是否保留配置和规则 (gost.yaml, 备份文件)? [Y/n]: " C2 KEEP_CONF="y" [[ "$C2" == "n" || "$C2" == "N" ]] && KEEP_CONF="n" read -p "是否卸载 gost 主程序二进制? [Y/n]: " C3 KEEP_BIN="y" [[ "$C3" == "n" || "$C3" == "N" ]] && KEEP_BIN="n" echo "" # 停服务 svc gost-panel gost-save gost; do [ -f "/etc/systemd/system/${svc}.service" ]; then systemctl stop "$svc" >/dev/null systemctl disable "$svc" >/dev/null rm -f "/etc/systemd/system/${svc}.service" info "已停止并移除: $svc" fi done systemctl daemon-reload # 程序文件 [ "$KEEP_BIN" = "n" ]; then rm -f "$GOST_BIN" info "已删除 gost 二进制" warn "保留 gost 二进制" fi rm -f /usr/local/bin/gost-panel-server.py rm -f /usr/local/bin/gost-save-server.py info "已删除面板/保存服务脚本" rm -rf /opt/gostpanel info "已删除 /opt/gostpanel (面板页面+日志)" [ "$KEEP_CONF" = "n" ]; then TS=$(date +%Y%m%d%H%M%S) mkdir -p /root/gost-uninstall-backup-$TS [ -d /etc/gost ] && cp -r /etc/gost /root/gost-uninstall-backup-$TS/ && info "配置已备份到 /root/gost-uninstall-backup-$TS 后删除" rm -rf /etc/gost warn "保留 /etc/gost (配置和规则备份)" fi echo "" info "=========================================" info " 卸载完成!" info "=========================================" exit } # ============================ status ============================ do_status(){ echo -e "${BLUE}========== GOST 三件套状态 ==========${NC}" pgrep -x gost >/dev/null >& && echo -e " ${GREEN}●${NC} gost: 运行中" || echo -e " ${RED}●${NC} gost: 停止" pgrep -f gost-save-server >/dev/null >& && echo -e " ${GREEN}●${NC} gost-save: 运行中" || echo -e " ${RED}●${NC} gost-save: 停止" pgrep -f gost-panel-server >/dev/null >& && echo -e " ${GREEN}●${NC} gost-panel: 运行中" || echo -e " ${RED}●${NC} gost-panel: 停止" [ -x "$GOST_BIN" ] && echo " gost版本: $("$GOST_BIN" -V >& | head -)" [ -f "$CONF" ] && { N=$(grep -c " - name:" "$CONF" >/dev/null || echo ) echo " 当前规则数: $N" } [ -f /opt/gostpanel/panel.html ] && echo " 面板页面: /opt/gostpanel/panel.html ($(( $(stat -c%s /opt/gostpanel/panel.html) / ))KB)" exit } # ============================ update ============================ do_update(){ SRC_BASE="${:-http://.:}" echo -e "${BLUE}========== 同步最新面板文件 ==========${NC}" echo "源: $SRC_BASE" SYNC_OK= f "panel.html:/opt/gostpanel/panel.html" "gost-panel-server.py:/usr/local/bin/gost-panel-server.py" "gost-save-server.py:/usr/local/bin/gost-save-server.py"; do FN="${f%%:*}"; DEST="${f##*:}" curl -sL -m -o "$DEST." "$SRC_BASE/$FN" && [ -s "$DEST." ] && mv "$DEST." "$DEST" && { info "同步 $FN"; SYNC_OK=; } || warn "同步失败: $FN" done [ $SYNC_OK -eq ]; then # 自动修复: gost API端口必须是18080(面板硬编码代理目标) [ -f "$CONF" ]; then API_ADDR=$(grep -A1 "^api:" "$CONF" | grep "addr:" | awk '{ $}' | tr -d '"' | tail -) [ -n "$API_ADDR" ] && [ "$API_ADDR" != ":" ]; then warn "gost API端口为 $API_ADDR (非标准), 自动改为 :" sed -i "s|^ addr: $API_ADDR$| addr: :|" "$CONF" API_FIXED= fi # 自动同步空值兼容(gost . services:null) grep -q 'rt.get("services",[])' /usr/local/bin/gost-save-server.py >/dev/null; then sed -i 's/rt.get("services",\[\])/(rt.get("services") [])/g' /usr/local/bin/gost-save-server.py >/dev/null warn "gost-save 同步逻辑已自动修复(.3兼容)" API_FIXED= fi [ "$API_FIXED" = "" ] && info "配置已自动修正" fi systemctl restart gost-save gost-panel gost >/dev/null sleep # 验证: gostapi代理是否可用 curl -s -m -o /dev/null http://.:/gostapi/config >/dev/null; then info "升级完成, API正常 ()" warn "gost API () 未响应, 请检查: journalctl -u gost -n " fi info "服务已重启, 升级生效" fi exit } # ============================ 主入口 ============================ case "$" uninstall) do_uninstall ;; status) do_status ;; update) shift; do_update "$@" ;; start) systemctl start gost gost-save gost-panel >/dev/null; info "服务已启动"; exit ;; stop) systemctl stop gost gost-save gost-panel >/dev/null; info "服务已停止"; exit ;; restart) systemctl restart gost gost-save gost-panel >/dev/null; info "服务已重启"; exit ;; menu) do_menu ;; install) ;; # 强制安装向导 "") do_menu ;; # 无参数: 直接进菜单 (菜单里有安装选项) -h|--help) echo "用法: bash gost-deploy.sh [menu|install|start|stop|restart|status|update [源]|uninstall]" echo " 无参数/menu: 管理菜单 (菜单内可安装)" exit ;; *) err "未知命令: $ (可用: menu/install/start/stop/restart/status/update/uninstall)"; exit ;; esac # ============================ 安装向导 ============================ GOST_VER="." GOST_OFFICIAL="https://github.com/go-gost/gost/releases/download/v${GOST_VER}/gost_${GOST_VER}_linux_amd64.tar.gz" GOST_MIRRORS=( "https://ghproxy.net/https://github.com/go-gost/gost/releases/download/v${GOST_VER}/gost_${GOST_VER}_linux_amd64.tar.gz" "https://gh-proxy.com/https://github.com/go-gost/gost/releases/download/v${GOST_VER}/gost_${GOST_VER}_linux_amd64.tar.gz" "https://mirror.ghproxy.com/https://github.com/go-gost/gost/releases/download/v${GOST_VER}/gost_${GOST_VER}_linux_amd64.tar.gz" ) echo -e "${BLUE}=============================================${NC}" echo -e "${BLUE} GOST 三件套部署 (极简 / 同源代理)${NC}" echo -e "${BLUE}=============================================${NC}" # ---------- 1. gost 来源 ---------- echo "" echo "GOST 程序来源:" echo " ) 自动下载 (官方+镜像轮询)" echo " ) 自定义下载地址" echo " ) 已有二进制路径" read -p "选择 [//, 默认1]: " SRC_CHOICE SRC_CHOICE=${SRC_CHOICE:-} BIN="" case "$SRC_CHOICE" ) read -p "输入下载地址: " CUSTOM_URL [ -z "$CUSTOM_URL" ] && { err "地址为空"; exit ; } GOST_MIRRORS=("$CUSTOM_URL") ;; ) read -p "输入本机 gost 路径: " BIN_PATH [ ! -f "$BIN_PATH" ] && { err "文件不存在"; exit ; } BIN="$BIN_PATH" ;; esac # ---------- 2. 是否安装面板 ---------- echo "" echo "是否安装 Web 面板?" echo " ) 安装 (推荐, 对外一个端口, 可网页管理/接入总面板)" echo " ) 不安装 (纯 gost + API, 仅供总面板远程管理, 不开网页)" read -p "选择 [/, 默认1]: " PANEL_CHOICE PANEL_CHOICE=${PANEL_CHOICE:-} [ "$PANEL_CHOICE" = "" ]; then read -p "面板端口(对外唯一端口) [默认 ]: " PANEL_PORT PANEL_PORT=${PANEL_PORT:-} PANEL_PORT="" read -p "GOST API 端口(对外唯一端口, 总面板连接用) [默认 ]: " API_PORT_IN API_PORT=${API_PORT_IN:-} fi [ -n "$PANEL_PORT" ]; then ! [[ "$PANEL_PORT" =~ ^[-]+$ ]] || [ "$PANEL_PORT" -lt ] || [ "$PANEL_PORT" -gt ]; then err "端口无效"; exit fi fi # 内部端口自动分配 [ "$PANEL_CHOICE" = "" ]; then API_PORT=$((PANEL_PORT + )) SAVE_PORT=$((PANEL_PORT + )) [ "$API_PORT" = "$PANEL_PORT" ] && API_PORT=$((API_PORT+)) ss -tln | grep -q ":${API_PORT} " && API_PORT=$((API_PORT+)) ss -tln | grep -q ":${SAVE_PORT} " && SAVE_PORT=$((SAVE_PORT+)) [ "$SAVE_PORT" = "$API_PORT" ] || [ "$SAVE_PORT" = "$PANEL_PORT" ]; do SAVE_PORT=$((SAVE_PORT+)); done [ "$API_PORT" = "$PANEL_PORT" ]; do API_PORT=$((API_PORT+)); done SAVE_PORT=$((API_PORT + )) ss -tln | grep -q ":${SAVE_PORT} " && SAVE_PORT=$((SAVE_PORT+)) [ "$SAVE_PORT" = "$API_PORT" ]; do SAVE_PORT=$((SAVE_PORT+)); done fi # ---------- 3. 账号密码 ---------- read -p "用户名 [默认 admin]: " API_USER API_USER=${API_USER:-admin} read -p "密码 [默认 oyh159159oiw.@]: " API_PASS API_PASS=${API_PASS:-'oyh159159oiw.@'} PANEL_PASS="$API_PASS" echo "" echo -e "${BLUE}---------- 配置确认 ----------${NC}" [ "$PANEL_CHOICE" = "" ]; then echo " 部署模式: 完整面板 (同源代理)" echo " 对外端口: $PANEL_PORT (防火墙/路由器只需放行这个)" echo " 内部端口: $API_PORT / $SAVE_PORT (自动分配, 无需关心)" echo " 部署模式: 纯 gost + API (无面板)" echo " 对外端口: $API_PORT (防火墙/路由器只需放行这个, 总面板连接用)" echo " 内部端口: $SAVE_PORT (自动分配, 无需关心)" fi echo " 账号: $API_USER / $API_PASS" echo -e "${BLUE}------------------------------${NC}" read -p "确认部署? [y/N]: " CONFIRM [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]] && { warn "已取消"; exit ; } # ---------- 下载 gost ---------- [ -z "$BIN" ]; then info "下载 gost..." BIN="/tmp/gost_bin" OK= url "${GOST_MIRRORS[@]}"; do warn "尝试: $url" curl -sL -m --connect-timeout -o /tmp/gost_dl.tgz "$url" && \ tar -tzf /tmp/gost_dl.tgz >/dev/null >& && OK= && warn "失败, 换下一个源" done [ $OK -eq ]; then err "全部下载源失败" read -p "手动输入下载地址重试(回车放弃): " RETRY_URL [ -n "$RETRY_URL" ]; then curl -sL -m -o /tmp/gost_dl.tgz "$RETRY_URL" || { err "下载失败"; exit ; } mkdir -p /tmp/gost_ex && tar -xzf /tmp/gost_dl.tgz -C /tmp/gost_ex BIN=$(find /tmp/gost_ex -name gost -type f | head -) [ -z "$BIN" ] && { err "解压失败"; exit ; } exit fi mkdir -p /tmp/gost_ex && tar -xzf /tmp/gost_dl.tgz -C /tmp/gost_ex BIN=$(find /tmp/gost_ex -name gost -type f | head -) [ -z "$BIN" ] && { err "解压失败"; exit ; } fi info "gost 获取成功: $BIN" fi chmod +x "$BIN" install -m755 "$BIN" "$GOST_BIN" info "gost 已安装: $($GOST_BIN -V >& | head -)" # ---------- 写配置 ---------- mkdir -p /etc/gost /opt/gostpanel cat > "$CONF" <<YEOF services: [] api: addr: :${API_PORT} pathPrefix: /gostapi auth: username: ${API_USER} password: ${API_PASS} YEOF cat > /etc/systemd/system/gost.service <<EOF [Unit] Description=Gost Proxy Service After=network.target [Service] Type=simple ExecStart=$GOST_BIN -C $CONF Restart=always RestartSec= LimitNOFILE= [Install] WantedBy=multi-user.target EOF # ---------- gost-save ---------- cat > /usr/local/bin/gost-save-server.py <<PYEOF #!/usr/bin/env python3 json, subprocess, datetime, shutil, threading, time, base64 http.server BaseHTTPRequestHandler, HTTPServer CONF="$CONF" USER="${API_USER}"; PASS='''${API_PASS}''' _auth(h): a=h.get("Authorization","") a.startswith("Basic "): : u,p=base64.b64decode(a[:]).decode().split(":",) u==USER p==PASS Exception: H(BaseHTTPRequestHandler): _cors(self): self.send_header("Access-Control-Allow-Origin","*") self.send_header("Access-Control-Allow-Headers","*") self.send_header("Access-Control-Allow-Methods","GET,POST,DELETE,OPTIONS") do_OPTIONS(self): self.send_response(); self._cors(); self.end_headers() do_GET(self): _auth(self.headers): self.send_response(); self._cors() self.send_header("WWW-Authenticate","Basic realm=gost"); self.end_headers(); self.path ("/yaml","/rawyaml"): self.send_response(); self._cors(); self.end_headers(); self.path=="/rawyaml": : open(CONF,"rb") f: raw=f.read() self.send_response() self._cors(); self.send_header("Content-Type","text/plain; charset=utf-") self.end_headers(); self.wfile.write(raw) Exception e: self.send_response(); self._cors(); self.end_headers(); self.wfile.write(str(e).encode()) : yaml Y open(CONF) f: cfg=Y.safe_load(f) {} out=json.dumps({"services":cfg.get("services",[])}).encode() self.send_response() Exception e: out=str(e).encode(); self.send_response() self._cors(); self.send_header("Content-Type","application/json") self.end_headers(); self.wfile.write(out) do_POST(self): _auth(self.headers): self.send_response(); self._cors(); self.end_headers(); self.path!="/save": self.send_response(); self._cors(); self.end_headers(); n=int(self.headers.get("Content-Length","") ) : yaml Y data=json.loads(self.rfile.read(n)) open(CONF) f: cfg=Y.safe_load(f) {} bak=CONF+".bak-"+datetime.datetime.now().strftime("%Y%m%d%H%M%S") shutil.copy(CONF,bak) cfg["services"]=data.get("services",[]) open(CONF,"w") f: Y.safe_dump(cfg,f,sort_keys=,allow_unicode=) subprocess.run(["systemctl","restart","gost"],timeout=) out=b"OK, yaml saved, gost reloaded"; code= Exception e: out=str(e).encode(); code= self.send_response(code); self._cors() self.send_header("Content-Type","text/plain") self.end_headers(); self.wfile.write(out) log_message(self,*a): auto_sync(): urllib.request, yaml Y auth="Basic "+base64.b64encode((USER+":"+PASS).encode()).decode() : time.sleep() : req=urllib.request.Request("http://.:${API_PORT}/gostapi/config") req.add_header("Authorization",auth) urllib.request.urlopen(req,timeout=) r: rt=json.load(r) names_rt=sorted(s["name"] s (rt.get("services") [])) open(CONF) f: cfg=Y.safe_load(f) {} names_yaml=sorted(s["name"] s (cfg.get("services") [])) clean=[{k:v k,v s.items() k ("name","addr","handler","listener","forwarder","metadata")} s (rt.get("services") [])] names_rt!=names_yaml: bak=CONF+".bak-"+datetime.datetime.now().strftime("%Y%m%d%H%M%S") shutil.copy(CONF,bak) cfg["services"]=clean open(CONF,"w") f: Y.safe_dump(cfg,f,sort_keys=,allow_unicode=) ("auto-synced:",names_rt,flush=) Exception e: ("auto-sync err:",e,flush=) threading.Thread(target=auto_sync,daemon=).start() HTTPServer((".",${SAVE_PORT}),H).serve_forever() PYEOF cat > /etc/systemd/system/gost-save.service <<EOF [Unit] Description=Gost rule save API After=network.target [Service] Type=simple ExecStart=/usr/bin/python3 /usr/local/bin/gost-save-server.py Restart=always RestartSec= [Install] WantedBy=multi-user.target EOF # ---------- gost-panel ---------- cat > /usr/local/bin/gost-panel-server.py <<PYEOF #!/usr/bin/env python3 http.server BaseHTTPRequestHandler, HTTPServer urllib.request, urllib.error, os PANEL_FILE="/opt/gostpanel/panel.html" USER="${API_USER}"; PASS='''${API_PASS}''' H(BaseHTTPRequestHandler): _proxy(self): p=self.path p=="/status": self._status(); p=="/apiconfig": self._apiconfig(); p=="/checkport": self._checkport(); p=="/pingtest": self._pingtest(); p=="/logop": self._logop(); p=="/getlog": self._getlog(); p.startswith("/gostapi"): t="http://.:${API_PORT}"+p p.startswith("/save"): t="http://.:${SAVE_PORT}"+p p.startswith("/yaml"): t="http://.:${SAVE_PORT}"+p p.startswith("/rawyaml"): t="http://.:${SAVE_PORT}"+p p=="/restart": t= : auth=self.headers.get("Authorization","") t is : base64, subprocess ok= auth.startswith("Basic "): : u,pw=base64.b64decode(auth[:]).decode().split(":",) ok=(u==USER pw==PASS) Exception: ok= ok: self.send_response(); self.end_headers(); r=subprocess.run(["systemctl","restart","gost"],capture_output=,timeout=) msg=b"OK: gost restarted" r.returncode== b"FAIL" self.send_response( r.returncode== ) self.send_header("Access-Control-Allow-Origin","*") self.send_header("Content-Type","text/plain") self.end_headers(); self.wfile.write(msg); : n=int(self.headers.get("Content-Length","") ) body=self.rfile.read(n) n req=urllib.request.Request(t,data=body,method=self.command) auth: req.add_header("Authorization",auth) body: req.add_header("Content-Type","application/json") : urllib.request.urlopen(req,timeout=) r: cl=r.headers.get("Content-Length") data=r.read(int(cl)) cl r.read(); code=r.status urllib.error.HTTPError he: cl=he.headers.get("Content-Length") data=he.read(int(cl)) cl he.read() b"" code=he.code Exception e: data=str(e).encode(); code= self.send_response(code) self.send_header("Content-Type","application/json") self.send_header("Access-Control-Allow-Origin","*") self.send_header("Access-Control-Allow-Headers","*") self.send_header("Access-Control-Allow-Methods","GET,POST,DELETE,PUT,OPTIONS") self.end_headers(); self.wfile.write(data) _apiconfig(self): json J, os CFG="/opt/gostpanel/apiconfig.json" self.command=="POST": auth=self.headers.get("Authorization","") ok= auth.startswith("Basic "): base64 : u,pw=base64.b64decode(auth[:]).decode().split(":",) ok=(u==USER pw==PASS) Exception: ok= ok: self.send_response(); self.end_headers(); self.command=="GET": : open(CFG) f: body=f.read().encode() Exception: body=b"[]" self.send_response() self.send_header("Content-Type","application/json") self.send_header("Access-Control-Allow-Origin","*") self.send_header("Content-Length",str(len(body))) self.end_headers(); self.wfile.write(body); self.command=="POST": n=int(self.headers.get("Content-Length","") ) data=self.rfile.read(n) : arr=J.loads(data) assert isinstance(arr,list) os.makedirs(os.path.dirname(CFG),exist_ok=) open(CFG,"w") f: f.write(J.dumps(arr,ensure_ascii=,indent=)) r=J.dumps({"ok":}).encode() self.send_response() Exception e: r=J.dumps({"ok":,"err":str(e)}).encode() self.send_response() self.send_header("Content-Type","application/json") self.send_header("Content-Length",str(len(r))) self.end_headers(); self.wfile.write(r); self.send_response(); self.end_headers(); _status(self): subprocess, json J, base64, os, shutil auth=self.headers.get("Authorization","") ok= auth.startswith("Basic "): : u,pw=base64.b64decode(auth[:]).decode().split(":",) ok=(u==USER pw==PASS) Exception: ok= ok: self.send_response(); self.end_headers(); : active=subprocess.run(["systemctl","is-active","gost"],capture_output=,text=).stdout.strip() GOVBIN="$GOST_BIN" os.path.exists(GOVBIN): GOVBIN=shutil.which("gost") "/home/gost" ver=subprocess.run([GOVBIN,"-V"],capture_output=,text=).stdout.strip() since=subprocess.run(["systemctl","show","gost","-p","ActiveEnterTimestamp","--value"],capture_output=,text=).stdout.strip() Exception e: active="unknown"; ver=""; since=str(e) r=J.dumps({"active":active,"version":ver,"since":since}).encode() self.send_response() self.send_header("Content-Type","application/json") self.send_header("Access-Control-Allow-Origin","*") self.end_headers(); self.wfile.write(r) _checkport(self): socket, base64, json J auth=self.headers.get("Authorization","") ok= auth.startswith("Basic "): : u,pw=base64.b64decode(auth[:]).decode().split(":",) ok=(u==USER pw==PASS) Exception: ok= ok: self.send_response(); self.end_headers(); n=int(self.headers.get("Content-Length","") ) : data=J.loads(self.rfile.read(n)); port=int(data.get("port",)) Exception: port= occupied= port>: s=socket.socket(); s.settimeout() : s.bind((".",port)); s.close() Exception: occupied= out=J.dumps({"port":port,"occupied":occupied}).encode() self.send_response() self.send_header("Content-Type","application/json") self.send_header("Access-Control-Allow-Origin","*") self.end_headers(); self.wfile.write(out) _pingtest(self): socket, base64, json J, time auth=self.headers.get("Authorization","") ok= auth.startswith("Basic "): : u,pw=base64.b64decode(auth[:]).decode().split(":",) ok=(u==USER pw==PASS) Exception: ok= ok: self.send_response(); self.end_headers(); n=int(self.headers.get("Content-Length","") ) target="" : data=J.loads(self.rfile.read(n)); target=data.get("target","") Exception: result={"target":target,"ok":,"ms":,"err":""} : host,port=target.rsplit(":",); port=int(port) s=socket.socket(); s.settimeout() t0=time.time(); s.connect((host,port)); result["ms"]=round((time.time()-t0)*) s.close(); result["ok"]= Exception e: result["err"]=str(e)[:] r=J.dumps(result).encode() self.send_response() self.send_header("Content-Type","application/json") self.send_header("Access-Control-Allow-Origin","*") self.end_headers(); self.wfile.write(r) _logop(self): base64, json J, datetime auth=self.headers.get("Authorization","") ok= auth.startswith("Basic "): : u,pw=base64.b64decode(auth[:]).decode().split(":",) ok=(u==USER pw==PASS) Exception: ok= ok: self.send_response(); self.end_headers(); n=int(self.headers.get("Content-Length","") ) : data=J.loads(self.rfile.read(n)) line=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")+" "+data.get("action","")+" "+data.get("detail","")+"\n" open("/opt/gostpanel/oplog.log","a") f: f.write(line) Exception: self.send_response(); self.end_headers() _getlog(self): base64, json J auth=self.headers.get("Authorization","") ok= auth.startswith("Basic "): : u,pw=base64.b64decode(auth[:]).decode().split(":",) ok=(u==USER pw==PASS) Exception: ok= ok: self.send_response(); self.end_headers(); : open("/opt/gostpanel/oplog.log") f: lines=f.readlines()[-:] r=J.dumps({"log":"".join(lines[-:])}).encode() FileNotFoundError: r=b'{"log":""}' Exception e: r=J.dumps({"log":"读取失败: "+str(e)}).encode() self.send_response() self.send_header("Content-Type","application/json") self.send_header("Access-Control-Allow-Origin","*") self.end_headers(); self.wfile.write(r) do_GET(self): self._proxy(): : open(PANEL_FILE,"rb") f: body=f.read() self.send_response() self.send_header("Content-Type","text/html; charset=utf-") self.send_header("Content-Length",str(len(body))) self.end_headers(); self.wfile.write(body) Exception e: self.send_response(); self.end_headers(); self.wfile.write(str(e).encode()) do_POST(self): self._proxy() do_DELETE(self): self._proxy() do_PUT(self): self._proxy() do_OPTIONS(self): self.send_response() self.send_header("Access-Control-Allow-Origin","*") self.send_header("Access-Control-Allow-Headers","*") self.send_header("Access-Control-Allow-Methods","GET,POST,DELETE,PUT,OPTIONS") self.end_headers() log_message(self,*a): HTTPServer((".",${PANEL_PORT}),H).serve_forever() PYEOF cat > /etc/systemd/system/gost-panel.service <<EOF [Unit] Description=Gost Panel Web (same-origin proxy) After=network.target [Service] Type=simple ExecStart=/usr/bin/python3 /usr/local/bin/gost-panel-server.py Restart=always RestartSec= [Install] WantedBy=multi-user.target EOF # ---------- 面板HTML ---------- SCRIPT_DIR="$(cd "$(dirname "$")" && pwd)" [ -f "$SCRIPT_DIR/panel.html" ]; then cp "$SCRIPT_DIR/panel.html" /opt/gostpanel/panel.html info "面板页面: 使用随附 panel.html" # 优先: 解码内嵌的完整面板(无需任何外部源) EMBED_B64="$SCRIPT_DIR/.panel.b64" cat > "$EMBED_B64" <<'PANELB64EOF' PCFkb2N0eXBlIGh0bWw+CjxodG1sIGxhbmc9InpoIj4KPGhlYWQ+CjxtZXRhIGNoYXJzZXQ9InV0 Zi04Ij4KPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwg aW5pdGlhbC1zY2FsZT0xIj4KPHRpdGxlPkdPU1Qg6L2s5Y+R6KeE5YiZ6Z2i5p2/PC90aXRsZT4K PHN0eWxlPgo6cm9vdHsKICAtLWJnOiMwZjE3MmE7IC0tcGFuZWw6IzFlMjkzYjsgLS1wYW5lbDI6 IzI3MzQ0OTsgLS1saW5lOiMzMzQxNTU7CiAgLS10ZXh0OiNlMmU4ZjA7IC0tbXV0ZWQ6Izk0YTNi ODsgLS1hY2NlbnQ6IzM4YmRmODsgLS1ncmVlbjojMzRkMzk5OyAtLXJlZDojZjg3MTcxOyAtLWFt YmVyOiNmYmJmMjQ7Cn0KKntib3gtc2l6aW5nOmJvcmRlci1ib3h9CmJvZHl7Zm9udC1mYW1pbHk6 IlNlZ29lIFVJIixzeXN0ZW0tdWksLWFwcGxlLXN5c3RlbSxzYW5zLXNlcmlmO2JhY2tncm91bmQ6 cmFkaWFsLWdyYWRpZW50KDEyMDBweCA2MDBweCBhdCA4MCUgLTEwJSwjMWUzYTVmNTUsdHJhbnNw YXJlbnQpLHZhcigtLWJnKTttYXJnaW46MDtwYWRkaW5nOjI4cHg7Y29sb3I6dmFyKC0tdGV4dCl9 Ci53cmFwe21heC13aWR0aDoxMDgwcHg7bWFyZ2luOjAgYXV0b30KaGVhZGVye2Rpc3BsYXk6Zmxl eDthbGlnbi1pdGVtczpjZW50ZXI7anVzdGlmeS1jb250ZW50OnNwYWNlLWJldHdlZW47bWFyZ2lu LWJvdHRvbToyMnB4fQpoMXtmb250LXNpemU6MjBweDttYXJnaW46MDtkaXNwbGF5OmZsZXg7YWxp Z24taXRlbXM6Y2VudGVyO2dhcDoxMHB4O2xldHRlci1zcGFjaW5nOi41cHh9Ci5iYWRnZXtmb250 LXNpemU6MTFweDtjb2xvcjp2YXIoLS1tdXRlZCk7YmFja2dyb3VuZDp2YXIoLS1wYW5lbCk7Ym9y ZGVyOjFweCBzb2xpZCB2YXIoLS1saW5lKTtwYWRkaW5nOjNweCAxMHB4O2JvcmRlci1yYWRpdXM6 OTlweH0KLmRvdHt3aWR0aDo5cHg7aGVpZ2h0OjlweDtib3JkZXItcmFkaXVzOjUwJTtiYWNrZ3Jv dW5kOnZhcigtLWdyZWVuKTtib3gtc2hhZG93OjAgMCAxMHB4IHZhcigtLWdyZWVuKX0KLmRvdC5v ZmZ7YmFja2dyb3VuZDp2YXIoLS1yZWQpO2JveC1zaGFkb3c6MCAwIDEwcHggdmFyKC0tcmVkKX0K LmNhcmR7YmFja2dyb3VuZDp2YXIoLS1wYW5lbCk7Ym9yZGVyOjFweCBzb2xpZCB2YXIoLS1saW5l KTtib3JkZXItcmFkaXVzOjE0cHg7cGFkZGluZzoxOHB4IDIwcHg7bWFyZ2luLWJvdHRvbToxNnB4 O2JveC1zaGFkb3c6MCA4cHggMjRweCByZ2JhKDAsMCwwLC4yNSl9Ci5jYXJkIGgze21hcmdpbjow IDAgMTRweDtmb250LXNpemU6MTRweDtjb2xvcjp2YXIoLS1tdXRlZCk7Zm9udC13ZWlnaHQ6NjAw O2xldHRlci1zcGFjaW5nOi41cHh9Ci5ncmlke2Rpc3BsYXk6ZmxleDtmbGV4LXdyYXA6d3JhcDtn YXA6MTJweDthbGlnbi1pdGVtczpmbGV4LWVuZH0KLmZpZWxke2Rpc3BsYXk6ZmxleDtmbGV4LWRp cmVjdGlvbjpjb2x1bW47Z2FwOjZweH0KLmZpZWxkIGxhYmVse2ZvbnQtc2l6ZToxMnB4O2NvbG9y OnZhcigtLW11dGVkKX0KaW5wdXQsc2VsZWN0e2JhY2tncm91bmQ6dmFyKC0tcGFuZWwyKTtib3Jk ZXI6MXB4IHNvbGlkIHZhcigtLWxpbmUpO2NvbG9yOnZhcigtLXRleHQpO2JvcmRlci1yYWRpdXM6 OXB4O3BhZGRpbmc6OXB4IDEycHg7Zm9udC1zaXplOjE0cHg7b3V0bGluZTpub25lO3RyYW5zaXRp b246Ym9yZGVyIC4xNXN9CmlucHV0OmZvY3VzLHNlbGVjdDpmb2N1c3tib3JkZXItY29sb3I6dmFy KC0tYWNjZW50KX0KaW5wdXQ6OnBsYWNlaG9sZGVye2NvbG9yOiM2NDc0OGJ9Ci5idG57Ym9yZGVy Om5vbmU7Ym9yZGVyLXJhZGl1czo5cHg7cGFkZGluZzoxMHB4IDE4cHg7Zm9udC1zaXplOjE0cHg7 Zm9udC13ZWlnaHQ6NjAwO2N1cnNvcjpwb2ludGVyO3RyYW5zaXRpb246ZmlsdGVyIC4xNXMsdHJh bnNmb3JtIC4wNXM7Y29sb3I6I2ZmZn0KLmJ0bjphY3RpdmV7dHJhbnNmb3JtOnNjYWxlKC45Nyl9 Ci5idG46aG92ZXJ7ZmlsdGVyOmJyaWdodG5lc3MoMS4xNSl9Ci5idG4tcHJpbWFyeXtiYWNrZ3Jv dW5kOmxpbmVhci1ncmFkaWVudCgxMzVkZWcsIzBlYTVlOSwjMjU2M2ViKX0KLmJ0bi1ncmVlbnti YWNrZ3JvdW5kOmxpbmVhci1ncmFkaWVudCgxMzVkZWcsIzEwYjk4MSwjMDU5NjY5KX0KLmJ0bi1n aG9zdHtiYWNrZ3JvdW5kOnZhcigtLXBhbmVsMik7Y29sb3I6dmFyKC0tdGV4dCk7Ym9yZGVyOjFw eCBzb2xpZCB2YXIoLS1saW5lKTtmb250LXdlaWdodDo0MDB9Ci5idG4tYmx1ZXtiYWNrZ3JvdW5k OmxpbmVhci1ncmFkaWVudCgxMzVkZWcsIzNiODJmNiwjNjM2NmYxKX0KLmhpbnR7Zm9udC1zaXpl OjEycHg7Y29sb3I6dmFyKC0tbXV0ZWQpO21hcmdpbi1sZWZ0OjEwcHh9Ci5ub3Rle2JvcmRlci1y YWRpdXM6MTBweDtwYWRkaW5nOjExcHggMTRweDtmb250LXNpemU6MTNweDttYXJnaW4tYm90dG9t OjE0cHg7ZGlzcGxheTpub25lfQoubm90ZS5va3tiYWNrZ3JvdW5kOiMxMGI5ODEyMjtib3JkZXI6 MXB4IHNvbGlkICMxMGI5ODE1NTtjb2xvcjp2YXIoLS1ncmVlbik7ZGlzcGxheTpibG9ja30KLm5v dGUuYmFke2JhY2tncm91bmQ6I2Y4NzE3MTIyO2JvcmRlcjoxcHggc29saWQgI2Y4NzE3MTU1O2Nv bG9yOnZhcigtLXJlZCk7ZGlzcGxheTpibG9ja30KLm5vdGUud2FybntiYWNrZ3JvdW5kOiNmYmJm MjQyMjtib3JkZXI6MXB4IHNvbGlkICNmYmJmMjQ1NTtjb2xvcjp2YXIoLS1hbWJlcik7ZGlzcGxh eTpibG9ja30KdGFibGV7d2lkdGg6MTAwJTtib3JkZXItY29sbGFwc2U6Y29sbGFwc2V9CnRoLHRk e3BhZGRpbmc6MTJweCAxNHB4O3RleHQtYWxpZ246bGVmdDtmb250LXNpemU6MTMuNXB4O2JvcmRl ci1ib3R0b206MXB4IHNvbGlkIHZhcigtLWxpbmUpfQp0aHtjb2xvcjp2YXIoLS1tdXRlZCk7Zm9u dC13ZWlnaHQ6NjAwO2ZvbnQtc2l6ZToxMnB4O3RleHQtdHJhbnNmb3JtOnVwcGVyY2FzZTtsZXR0 ZXItc3BhY2luZzouNnB4fQp0cjpsYXN0LWNoaWxkIHRke2JvcmRlci1ib3R0b206bm9uZX0KdGJv ZHkgdHJ7dHJhbnNpdGlvbjpiYWNrZ3JvdW5kIC4xMnN9CnRib2R5IHRyOmhvdmVye2JhY2tncm91 bmQ6I2ZmZmZmZjA4fQpjb2Rle2JhY2tncm91bmQ6dmFyKC0tcGFuZWwyKTtib3JkZXI6MXB4IHNv bGlkIHZhcigtLWxpbmUpO3BhZGRpbmc6M3B4IDlweDtib3JkZXItcmFkaXVzOjdweDtmb250LXNp emU6MTIuNXB4O2NvbG9yOiM3ZGQzZmN9Ci5wcm90b3tmb250LXNpemU6MTFweDtwYWRkaW5nOjNw eCA5cHg7Ym9yZGVyLXJhZGl1czo5OXB4O2JhY2tncm91bmQ6IzM4YmRmODIyO2NvbG9yOnZhcigt LWFjY2VudCk7Ym9yZGVyOjFweCBzb2xpZCAjMzhiZGY4NDQ7Zm9udC13ZWlnaHQ6NjAwfQoudW5z YXZlZHtmb250LXNpemU6MTFweDtwYWRkaW5nOjJweCA4cHg7Ym9yZGVyLXJhZGl1czo5OXB4O2Jh Y2tncm91bmQ6I2ZiYmYyNDIyO2NvbG9yOnZhcigtLWFtYmVyKTtib3JkZXI6MXB4IHNvbGlkICNm YmJmMjQ0NDttYXJnaW4tbGVmdDo2cHh9Ci5kZWx7YmFja2dyb3VuZDpub25lO2JvcmRlcjpub25l O2NvbG9yOnZhcigtLXJlZCk7Y3Vyc29yOnBvaW50ZXI7Zm9udC1zaXplOjEzcHg7b3BhY2l0eTou NzU7cGFkZGluZzo0cHggOHB4O2JvcmRlci1yYWRpdXM6N3B4fQouZGVsOmhvdmVye29wYWNpdHk6 MTtiYWNrZ3JvdW5kOiNmODcxNzEyMn0KLm1ldGF7Y29sb3I6IzY0NzQ4Yjtmb250LXNpemU6MTJw eDttYXJnaW4tdG9wOjE2cHg7dGV4dC1hbGlnbjpjZW50ZXJ9Ci5tc2d7Zm9udC1zaXplOjEzcHg7 cGFkZGluZzoxMHB4IDE0cHg7Ym9yZGVyLXJhZGl1czoxMHB4O2Rpc3BsYXk6bm9uZTttYXJnaW4t dG9wOjEycHh9Ci5tLW9re2JhY2tncm91bmQ6IzEwYjk4MTIyO2JvcmRlcjoxcHggc29saWQgIzEw Yjk4MTU1O2NvbG9yOnZhcigtLWdyZWVuKX0KLm0tYmFke2JhY2tncm91bmQ6I2Y4NzE3MTIyO2Jv cmRlcjoxcHggc29saWQgI2Y4NzE3MTU1O2NvbG9yOnZhcigtLXJlZCl9Ci5lcnJ7YmFja2dyb3Vu ZDojZjg3MTcxMTg7Ym9yZGVyOjFweCBzb2xpZCAjZjg3MTcxNDQ7Y29sb3I6dmFyKC0tcmVkKTtw YWRkaW5nOjE2cHg7Ym9yZGVyLXJhZGl1czoxMnB4fQovKiDnmbvlvZXpl6ggKi8KI2xvZ2luR2F0 ZXtwb3NpdGlvbjpmaXhlZDtpbnNldDowO2JhY2tncm91bmQ6cmFkaWFsLWdyYWRpZW50KDkwMHB4 IDUwMHB4IGF0IDUwJSAyMCUsIzFlM2E1ZjY2LHRyYW5zcGFyZW50KSx2YXIoLS1iZyk7ZGlzcGxh eTpmbGV4O2FsaWduLWl0ZW1zOmNlbnRlcjtqdXN0aWZ5LWNvbnRlbnQ6Y2VudGVyO3otaW5kZXg6 OTl9Ci5sb2dpbntiYWNrZ3JvdW5kOnZhcigtLXBhbmVsKTtib3JkZXI6MXB4IHNvbGlkIHZhcigt LWxpbmUpO3BhZGRpbmc6NDBweCAzOHB4O2JvcmRlci1yYWRpdXM6MThweDt3aWR0aDozNDBweDti b3gtc2hhZG93OjAgMjBweCA2MHB4IHJnYmEoMCwwLDAsLjUpO3RleHQtYWxpZ246Y2VudGVyfQou bG9naW4gLmxvY2t7Zm9udC1zaXplOjM0cHh9Ci5sb2dpbiBoMnttYXJnaW46MTBweCAwIDRweDtm b250LXNpemU6MTlweH0KLmxvZ2luIHB7bWFyZ2luOjAgMCAyMHB4O2NvbG9yOnZhcigtLW11dGVk KTtmb250LXNpemU6MTNweH0KLmxvZ2luIGlucHV0e3dpZHRoOjEwMCU7dGV4dC1hbGlnbjpjZW50 ZXI7bGV0dGVyLXNwYWNpbmc6MXB4fQoubG9naW4gYnV0dG9ue3dpZHRoOjEwMCU7bWFyZ2luLXRv cDoxNHB4fQojcHdFcnJ7Y29sb3I6dmFyKC0tcmVkKTtmb250LXNpemU6MTNweDttYXJnaW4tdG9w OjEycHg7ZGlzcGxheTpub25lfQoKLyog5re75YqgQVBJIOaooeaAgeW8ueeqlyAqLwoubW9kYWwt bWFza3twb3NpdGlvbjpmaXhlZDtpbnNldDowO2JhY2tncm91bmQ6cmdiYSgwLDAsMCwuNik7YmFj a2Ryb3AtZmlsdGVyOmJsdXIoNHB4KTtkaXNwbGF5Om5vbmU7YWxpZ24taXRlbXM6Y2VudGVyO2p1 c3RpZnktY29udGVudDpjZW50ZXI7ei1pbmRleDoyMDB9Ci5tb2RhbC1tYXNrLnNob3d7ZGlzcGxh eTpmbGV4fQoubW9kYWx7YmFja2dyb3VuZDp2YXIoLS1wYW5lbCk7Ym9yZGVyOjFweCBzb2xpZCB2 YXIoLS1saW5lKTtib3JkZXItcmFkaXVzOjE2cHg7cGFkZGluZzoyNnB4IDI4cHg7d2lkdGg6NDAw cHg7Ym94LXNoYWRvdzowIDIwcHggNjBweCByZ2JhKDAsMCwwLC41KX0KLm1vZGFsIGgze21hcmdp bjowIDAgNHB4O2ZvbnQtc2l6ZToxN3B4O2Rpc3BsYXk6ZmxleDthbGlnbi1pdGVtczpjZW50ZXI7 Z2FwOjhweH0KLm1vZGFsIHB7bWFyZ2luOjAgMCAxOHB4O2NvbG9yOnZhcigtLW11dGVkKTtmb250 LXNpemU6MTIuNXB4fQoubW9kYWwgLmZpZWxke21hcmdpbi1ib3R0b206MTRweH0KLm1vZGFsIGxh YmVse2Rpc3BsYXk6YmxvY2s7Zm9udC1zaXplOjEycHg7Y29sb3I6dmFyKC0tbXV0ZWQpO21hcmdp bi1ib3R0b206NXB4fQoubW9kYWwgaW5wdXR7d2lkdGg6MTAwJTtib3gtc2l6aW5nOmJvcmRlci1i b3h9Ci5tb2RhbCAucm93e2Rpc3BsYXk6ZmxleDtnYXA6MTBweDttYXJnaW4tdG9wOjIwcHg7anVz dGlmeS1jb250ZW50OmZsZXgtZW5kfQoubW9kYWwgLnByZXZpZXd7YmFja2dyb3VuZDp2YXIoLS1w YW5lbDIpO2JvcmRlcjoxcHggc29saWQgdmFyKC0tbGluZSk7Ym9yZGVyLXJhZGl1czo5cHg7cGFk ZGluZzoxMHB4IDEycHg7Zm9udC1zaXplOjEycHg7Y29sb3I6dmFyKC0tbXV0ZWQpO21hcmdpbi10 b3A6NHB4O2xpbmUtaGVpZ2h0OjEuN30KLm1vZGFsIC5wcmV2aWV3IGNvZGV7YmFja2dyb3VuZDpu b25lO2JvcmRlcjpub25lO3BhZGRpbmc6MDtjb2xvcjp2YXIoLS1hY2NlbnQpO2ZvbnQtc2l6ZTox MnB4fQoKLyogQVBJ5YiH5o2i5Zmo5LiO6aKE6KeI5L+u5aSNICovCiNhcGlTZWx7bWF4LXdpZHRo OjIzMHB4O21pbi13aWR0aDoxNTBweDt0ZXh0LW92ZXJmbG93OmVsbGlwc2lzO2ZvbnQtc2l6ZTox M3B4O2NvbG9yOnZhcigtLXRleHQpO2JhY2tncm91bmQ6dmFyKC0tcGFuZWwyKTtib3JkZXI6MXB4 IHNvbGlkIHZhcigtLWxpbmUpO2JvcmRlci1yYWRpdXM6OHB4O3BhZGRpbmc6NnB4IDEwcHh9CiNh cGlTZWwgb3B0aW9ue2JhY2tncm91bmQ6dmFyKC0tcGFuZWwpO2NvbG9yOnZhcigtLXRleHQpO2Zv bnQtc2l6ZToxM3B4fQoubW9kYWwgLnByZXZpZXd7Zm9udC1zaXplOjEycHg7bGluZS1oZWlnaHQ6 MS45fQoubW9kYWwgLnByZXZpZXcgLmt7ZGlzcGxheTppbmxpbmUtYmxvY2s7d2lkdGg6NDRweDtj b2xvcjp2YXIoLS1tdXRlZCl9Ci5tb2RhbCAucHJldmlldyBjb2Rle3dvcmQtYnJlYWs6YnJlYWst YWxsfQoKLyogPT09PT0gdjIg5paw5Yqf6IO95qC35byPID09PT09ICovCi5zdGF0dXNiYXJ7ZGlz cGxheTpmbGV4O2dhcDoxNHB4O2FsaWduLWl0ZW1zOmNlbnRlcjtiYWNrZ3JvdW5kOnZhcigtLXBh bmVsKTtib3JkZXI6MXB4IHNvbGlkIHZhcigtLWxpbmUpO2JvcmRlci1yYWRpdXM6MTJweDtwYWRk aW5nOjEwcHggMTZweDttYXJnaW4tYm90dG9tOjE0cHg7Zm9udC1zaXplOjEzcHg7ZmxleC13cmFw OndyYXB9Ci5zdGF0dXNiYXIgLnN0e2Rpc3BsYXk6ZmxleDthbGlnbi1pdGVtczpjZW50ZXI7Z2Fw OjZweH0KLnN0YXR1c2JhciAub2t7Y29sb3I6dmFyKC0tZ3JlZW4pfQouc3RhdHVzYmFyIC5iYWR7 Y29sb3I6dmFyKC0tcmVkKX0KLnRvb2xiYXJ7ZGlzcGxheTpmbGV4O2dhcDoxMHB4O2FsaWduLWl0 ZW1zOmNlbnRlcjttYXJnaW4tYm90dG9tOjEycHg7ZmxleC13cmFwOndyYXB9Ci5zZWFyY2h7YmFj a2dyb3VuZDp2YXIoLS1wYW5lbDIpO2JvcmRlcjoxcHggc29saWQgdmFyKC0tbGluZSk7Y29sb3I6 dmFyKC0tdGV4dCk7Ym9yZGVyLXJhZGl1czo5cHg7cGFkZGluZzo4cHggMTJweDtmb250LXNpemU6 MTNweDt3aWR0aDoyMjBweH0KLmJ0bi1zbXtwYWRkaW5nOjVweCAxMHB4O2ZvbnQtc2l6ZToxMnB4 O2JvcmRlci1yYWRpdXM6N3B4fQouc3RvZ2dsZXt3aWR0aDozNHB4O2hlaWdodDoxOHB4O2JvcmRl ci1yYWRpdXM6OTlweDtiYWNrZ3JvdW5kOiM0NzU1Njk7cG9zaXRpb246cmVsYXRpdmU7Y3Vyc29y OnBvaW50ZXI7Ym9yZGVyOm5vbmU7dHJhbnNpdGlvbjpiYWNrZ3JvdW5kIC4yc30KLnN0b2dnbGUu b257YmFja2dyb3VuZDp2YXIoLS1ncmVlbil9Ci5zdG9nZ2xlOjphZnRlcntjb250ZW50OiIiO3Bv c2l0aW9uOmFic29sdXRlO3RvcDoycHg7bGVmdDoycHg7d2lkdGg6MTRweDtoZWlnaHQ6MTRweDti b3JkZXItcmFkaXVzOjUwJTtiYWNrZ3JvdW5kOiNmZmY7dHJhbnNpdGlvbjpsZWZ0IC4yc30KLnN0 b2dnbGUub246OmFmdGVye2xlZnQ6MThweH0KLnBpbmctb2t7Y29sb3I6dmFyKC0tZ3JlZW4pO2Zv bnQtc2l6ZToxMnB4fQoucGluZy1iYWR7Y29sb3I6dmFyKC0tcmVkKTtmb250LXNpemU6MTJweH0K Lm1vZGFsIHRleHRhcmVhe3dpZHRoOjEwMCU7Ym94LXNpemluZzpib3JkZXItYm94O2JhY2tncm91 bmQ6dmFyKC0tcGFuZWwyKTtib3JkZXI6MXB4IHNvbGlkIHZhcigtLWxpbmUpO2NvbG9yOnZhcigt LXRleHQpO2JvcmRlci1yYWRpdXM6OXB4O3BhZGRpbmc6MTBweDtmb250LXNpemU6MTIuNXB4O2Zv bnQtZmFtaWx5Om1vbm9zcGFjZTttaW4taGVpZ2h0OjE0MHB4fQouc3BlZWRyb3d7ZGlzcGxheTpm bGV4O2dhcDo4cHg7YWxpZ24taXRlbXM6Y2VudGVyfQouc3BlZWRyb3cgaW5wdXR7d2lkdGg6OTBw eH0KLnRoZW1lLWxpZ2h0ey0tYmc6I2YxZjVmOTstLXBhbmVsOiNmZmZmZmY7LS1wYW5lbDI6I2Uy ZThmMDstLWxpbmU6I2NiZDVlMTstLXRleHQ6IzBmMTcyYTstLW11dGVkOiM2NDc0OGI7LS1hY2Nl bnQ6IzAyODRjNzstLWdyZWVuOiMwNTk2Njk7LS1yZWQ6I2RjMjYyNjstLWFtYmVyOiNkOTc3MDZ9 Ci50aGVtZS1saWdodCBib2R5LC50aGVtZS1saWdodHtiYWNrZ3JvdW5kOnJhZGlhbC1ncmFkaWVu dCgxMjAwcHggNjAwcHggYXQgODAlIC0xMCUsI2RiZWFmZTU1LHRyYW5zcGFyZW50KSx2YXIoLS1i Zyl9Ci5rYmR7YmFja2dyb3VuZDp2YXIoLS1wYW5lbDIpO2JvcmRlcjoxcHggc29saWQgdmFyKC0t bGluZSk7Ym9yZGVyLXJhZGl1czo1cHg7cGFkZGluZzoxcHggNnB4O2ZvbnQtc2l6ZToxMXB4fQoK Lyog6ZmQ6YCf5qih5oCBICovCi5zcGVlZC1wcmVzZXRze2Rpc3BsYXk6ZmxleDtnYXA6OHB4O2Zs ZXgtd3JhcDp3cmFwO21hcmdpbi10b3A6OHB4fQouc3BlZWQtcHJlc2V0cyBidXR0b257YmFja2dy b3VuZDp2YXIoLS1wYW5lbDIpO2JvcmRlcjoxcHggc29saWQgdmFyKC0tbGluZSk7Y29sb3I6dmFy KC0tdGV4dCk7Ym9yZGVyLXJhZGl1czo4cHg7cGFkZGluZzo2cHggMTRweDtmb250LXNpemU6MTNw eDtjdXJzb3I6cG9pbnRlcjt0cmFuc2l0aW9uOmFsbCAuMTVzfQouc3BlZWQtcHJlc2V0cyBidXR0 b246aG92ZXJ7Ym9yZGVyLWNvbG9yOnZhcigtLWFjY2VudCk7Y29sb3I6dmFyKC0tYWNjZW50KX0K LnNwZWVkLXByZXNldHMgYnV0dG9uLnNlbHtiYWNrZ3JvdW5kOnZhcigtLWFjY2VudCk7Y29sb3I6 I2ZmZjtib3JkZXItY29sb3I6dmFyKC0tYWNjZW50KX0KLnNwZWVkLWlucHV0e3dpZHRoOjEwMCU7 Ym94LXNpemluZzpib3JkZXItYm94O2JhY2tncm91bmQ6dmFyKC0tcGFuZWwyKTtib3JkZXI6MXB4 IHNvbGlkIHZhcigtLWxpbmUpO2NvbG9yOnZhcigtLXRleHQpO2JvcmRlci1yYWRpdXM6OXB4O3Bh ZGRpbmc6MTBweCAxMnB4O2ZvbnQtc2l6ZToxNXB4O2ZvbnQtd2VpZ2h0OjYwMDt0ZXh0LWFsaWdu OmNlbnRlcn0KLnNwZWVkLW5vdGV7Zm9udC1zaXplOjEycHg7Y29sb3I6dmFyKC0tbXV0ZWQpO2xp bmUtaGVpZ2h0OjEuODttYXJnaW4tdG9wOjEycHg7YmFja2dyb3VuZDp2YXIoLS1wYW5lbDIpO2Jv cmRlci1yYWRpdXM6OXB4O3BhZGRpbmc6MTBweCAxMnB4fQouc3BlZWQtbm90ZSBie2NvbG9yOnZh cigtLXRleHQpfQouc3BlZWQtY3Vye2ZvbnQtc2l6ZToxMnB4O2NvbG9yOnZhcigtLWFtYmVyKTtt YXJnaW4tdG9wOjZweH0KPC9zdHlsZT4KPHNjcmlwdD4KLy8gUEFORUxfVkVSX0NIRUNLOiDpobXp naLniYjmnKzlvLrliLbliLfmlrAKKGZ1bmN0aW9uKCl7CiAgdmFyIFY9InYxMS1maXgtYWRkcnVs ZSI7CiAgdHJ5ewogICAgdmFyIHN2PWxvY2FsU3RvcmFnZS5nZXRJdGVtKCJwYW5lbF92ZXIiKTsK ICAgIGlmKHN2IT09Vil7IGxvY2FsU3RvcmFnZS5zZXRJdGVtKCJwYW5lbF92ZXIiLFYpOyBsb2Nh dGlvbi5yZWxvYWQodHJ1ZSk7IH0KICB9Y2F0Y2goZSl7fQp9KSgpOwo8L3NjcmlwdD48L2hlYWQ+ Cjxib2R5Pgo8IS0tIOeZu+W9lemXqCAtLT4KPGRpdiBpZD0ibG9naW5HYXRlIj4KICA8ZGl2IGNs YXNzPSJsb2dpbiI+CiAgICA8ZGl2IGNsYXNzPSJsb2NrIj7wn5SQPC9kaXY+CiAgICA8aDI+R09T VCDnrqHnkIbpnaLmnb88L2gyPgogICAgPHA+6K+36L6T5YWl6K6/6Zeu5a+G56CBPC9wPgogICAg PGlucHV0IGlkPSJwdyIgdHlwZT0icGFzc3dvcmQiIHBsYWNlaG9sZGVyPSLorr/pl67lr4bnoIEi PgogICAgPGJ1dHRvbiBjbGFzcz0iYnRuIGJ0bi1wcmltYXJ5IiBvbmNsaWNrPSJ0cnlMb2dpbigp Ij7ov5vlhaXpnaLmnb88L2J1dHRvbj4KICAgIDxkaXYgaWQ9InB3RXJyIj7lr4bnoIHplJnor6/v vIzor7fph43or5U8L2Rpdj4KICA8L2Rpdj4KPC9kaXY+Cgo8ZGl2IGNsYXNzPSJ3cmFwIj4KICA8 ZGl2IGNsYXNzPSJzdGF0dXNiYXIiIGlkPSJzdGF0dXNiYXIiPgogICAgPHNwYW4gY2xhc3M9InN0 IiBpZD0ic3RBY3RpdmUiPjxzcGFuIGNsYXNzPSJkb3QiPjwvc3Bhbj7mo4DmtYvkuK3igKY8L3Nw YW4+CiAgICA8c3BhbiBjbGFzcz0ic3QiIGlkPSJzdFZlciI+4oCUPC9zcGFuPgogICAgPHNwYW4g Y2xhc3M9InN0IiBpZD0ic3RTaW5jZSI+4oCUPC9zcGFuPgogICAgPHNwYW4gY2xhc3M9InN0IiBz dHlsZT0ibWFyZ2luLWxlZnQ6YXV0bztjb2xvcjp2YXIoLS1tdXRlZCkiIGlkPSJzdFRpbWUiPjwv c3Bhbj4KICA8L2Rpdj4KICA8aGVhZGVyPgogICAgPGgxPjxzcGFuIGNsYXNzPSJkb3QiIGlkPSJk b3QiPjwvc3Bhbj5HT1NUIOi9rOWPkeinhOWImQogICAgICA8c2VsZWN0IGlkPSJhcGlTZWwiIG9u Y2hhbmdlPSJzd2l0Y2hBcGkodGhpcy52YWx1ZSkiIHN0eWxlPSJiYWNrZ3JvdW5kOnZhcigtLXBh bmVsMik7Ym9yZGVyOjFweCBzb2xpZCB2YXIoLS1saW5lKTtjb2xvcjp2YXIoLS10ZXh0KTtib3Jk ZXItcmFkaXVzOjhweDtwYWRkaW5nOjZweCAxMHB4O2ZvbnQtc2l6ZToxM3B4Ij48L3NlbGVjdD4K ICAgICAgPGJ1dHRvbiBjbGFzcz0iYnRuIGJ0bi1naG9zdCIgb25jbGljaz0iYWRkQXBpUHJvbXB0 KCkiPisg5re75YqgQVBJPC9idXR0b24+CiAgICAgIDxidXR0b24gY2xhc3M9ImJ0biBidG4tZ2hv c3QiIG9uY2xpY2s9ImVkaXRBcGkoKSI+4pyP77iPIOe8lui+kUFQSTwvYnV0dG9uPgogICAgICA8 YnV0dG9uIGNsYXNzPSJidG4gYnRuLWdob3N0IiBvbmNsaWNrPSJkZWxBcGkoKSI+5Yig6Zmk5b2T 5YmNPC9idXR0b24+CiAgICA8L2gxPgogICAgPGJ1dHRvbiBjbGFzcz0iYnRuIGJ0bi1naG9zdCIg b25jbGljaz0ibG9hZCgpIj7in7Mg5Yi35pawPC9idXR0b24+CiAgPC9oZWFkZXI+CgogIDxkaXYg Y2xhc3M9ImNhcmQiPgogICAgPGgzPuKelSDmt7vliqDovazlj5Hop4TliJk8L2gzPgogICAgPGRp diBjbGFzcz0iZ3JpZCI+CiAgICAgIDxkaXYgY2xhc3M9ImZpZWxkIj48bGFiZWw+6KeE5YiZ5ZCN PC9sYWJlbD48aW5wdXQgaWQ9ImYtbmFtZSIgcGxhY2Vob2xkZXI9Im15LXJ1bGUiIHN0eWxlPSJ3 aWR0aDoxNTBweCI+PC9kaXY+CiAgICAgIDxkaXYgY2xhc3M9ImZpZWxkIj48bGFiZWw+55uR5ZCs 56uv5Y+jIDxzcGFuIGlkPSJwb3J0VGlwIiBzdHlsZT0iZm9udC1zaXplOjExcHgiPjwvc3Bhbj48 L2xhYmVsPjxpbnB1dCBpZD0iZi1saXN0ZW4iIHBsYWNlaG9sZGVyPSIyMDYwMCIgc3R5bGU9Indp ZHRoOjEyMHB4IiBvbmlucHV0PSJjaGVja1BvcnRMaXZlKCkiPjwvZGl2PgogICAgICA8ZGl2IGNs YXNzPSJmaWVsZCI+PGxhYmVsPuebruagh+WcsOWdgDwvbGFiZWw+PGlucHV0IGlkPSJmLXRhcmdl dCIgcGxhY2Vob2xkZXI9IjEuMi4zLjQ6ODAiIHN0eWxlPSJ3aWR0aDoyMTBweCI+PC9kaXY+CiAg ICAgIDxkaXYgY2xhc3M9ImZpZWxkIj48bGFiZWw+5Y2P6K6uPC9sYWJlbD4KICAgICAgICA8c2Vs ZWN0IGlkPSJmLXByb3RvIiBzdHlsZT0id2lkdGg6MTYwcHgiPgogICAgICAgICAgPG9wdGlvbiB2 YWx1ZT0idGNwIj5UQ1Ag56uv5Y+j6L2s5Y+RPC9vcHRpb24+CiAgICAgICAgICA8b3B0aW9uIHZh bHVlPSJ1ZHAiPlVEUCDnq6/lj6Povazlj5E8L29wdGlvbj4KICAgICAgICAgIDxvcHRpb24gdmFs dWU9InRjcCt1ZHAiPlRDUCtVRFAg6YO96L2sPC9vcHRpb24+CiAgICAgICAgICA8b3B0aW9uIHZh bHVlPSJodHRwIj5IVFRQIOWPjeWQkeS7o+eQhjwvb3B0aW9uPgogICAgICAgICAgPG9wdGlvbiB2 YWx1ZT0icmVsYXkiPlJlbGF5IOmap+mBk+i9rOWPkTwvb3B0aW9uPgogICAgICAgIDwvc2VsZWN0 PgogICAgICA8L2Rpdj4KICAgICAgPGJ1dHRvbiBjbGFzcz0iYnRuIGJ0bi1ncmVlbiIgb25jbGlj az0iYWRkUnVsZSgpIj7mt7vliqA8L2J1dHRvbj4KICAgIDwvZGl2PgogICAgPGRpdiBpZD0ibXNn IiBjbGFzcz0ibXNnIj48L2Rpdj4KICA8L2Rpdj4KCiAgPGRpdiBjbGFzcz0iY2FyZCI+CiAgICA8 ZGl2IHN0eWxlPSJkaXNwbGF5OmZsZXg7YWxpZ24taXRlbXM6Y2VudGVyO2p1c3RpZnktY29udGVu dDpzcGFjZS1iZXR3ZWVuO21hcmdpbi1ib3R0b206MTJweCI+CiAgICAgIDxkaXYgc3R5bGU9ImRp c3BsYXk6ZmxleDthbGlnbi1pdGVtczpjZW50ZXI7anVzdGlmeS1jb250ZW50OnNwYWNlLWJldHdl ZW47bWFyZ2luLWJvdHRvbToxMnB4O2ZsZXgtd3JhcDp3cmFwO2dhcDoxMHB4Ij4KICAgICAgPGgz IHN0eWxlPSJtYXJnaW46MCI+8J+TiyDop4TliJnliJfooag8L2gzPgogICAgICA8ZGl2IHN0eWxl PSJkaXNwbGF5OmZsZXg7Z2FwOjhweDthbGlnbi1pdGVtczpjZW50ZXI7ZmxleC13cmFwOndyYXAi PgogICAgICAgIDxpbnB1dCBjbGFzcz0ic2VhcmNoIiBpZD0ic2VhcmNoQm94IiBwbGFjZWhvbGRl cj0i8J+UjSDmkJzntKIg5ZCN56ewL+err+WPoy/nm67moIfigKYiIG9uaW5wdXQ9InJlbmRlclRh YmxlKCkiPgogICAgICAgIDxidXR0b24gY2xhc3M9ImJ0biBidG4tZ2hvc3QgYnRuLXNtIiBvbmNs aWNrPSJleHBvcnRSdWxlcygpIj7irIcg5a+85Ye6PC9idXR0b24+CiAgICAgICAgPGJ1dHRvbiBj bGFzcz0iYnRuIGJ0bi1naG9zdCBidG4tc20iIG9uY2xpY2s9ImltcG9ydFJ1bGVzKCkiPuKshiDl r7zlhaU8L2J1dHRvbj4KICAgICAgICA8YnV0dG9uIGNsYXNzPSJidG4gYnRuLWdob3N0IGJ0bi1z bSIgb25jbGljaz0ic2hvd0xvZygpIj7wn5OcIOaXpeW/lzwvYnV0dG9uPgogICAgICAgIDxidXR0 b24gY2xhc3M9ImJ0biBidG4tZ2hvc3QgYnRuLXNtIiBvbmNsaWNrPSJ0b2dnbGVUaGVtZSgpIj7w n4yTPC9idXR0b24+CiAgICAgIDwvZGl2PgogICAgPC9kaXY+CiAgICA8aDMgc3R5bGU9ImRpc3Bs YXk6bm9uZSI+8J+TiyDop4TliJnliJfooag8L2gzPgogICAgICA8ZGl2PgogICAgICAgIDxidXR0 b24gY2xhc3M9ImJ0biBidG4tZ2hvc3QiIG9uY2xpY2s9InJlc3RhcnRHb3N0KCkiPvCflIQg6YeN 5ZCvIEdPU1Q8L2J1dHRvbj4KICAgICAgICA8YnV0dG9uIGNsYXNzPSJidG4gYnRuLWdob3N0IiBv bmNsaWNrPSJ2aWV3WWFtbCgpIj7wn5OEIOafpeeciyB5YW1sPC9idXR0b24+CiAgICAgIDwvZGl2 PgogICAgPC9kaXY+CiAgICA8ZGl2IGlkPSJzeW5jTm90ZSIgY2xhc3M9Im5vdGUiPjwvZGl2Pgog ICAgPGRpdiBzdHlsZT0ib3ZlcmZsb3cteDphdXRvIj4KICAgICAgPHRhYmxlPgogICAgICAgIDx0 aGVhZD48dHI+PHRoPuinhOWImeWQjTwvdGg+PHRoPuebkeWQrOerr+WPozwvdGg+PHRoPui9rOWP keebruaghzwvdGg+PHRoPuWNj+iurjwvdGg+PHRoIHN0eWxlPSJ3aWR0aDo1MHB4Ij7lkK/lgZw8 L3RoPjx0aCBzdHlsZT0id2lkdGg6MjgwcHgiPuaTjeS9nDwvdGg+PC90cj48L3RoZWFkPgogICAg ICAgIDx0Ym9keSBpZD0iY29udGVudCI+PHRyPjx0ZCBjb2xzcGFuPSI1IiBzdHlsZT0iY29sb3I6 dmFyKC0tbXV0ZWQpIj7liqDovb3kuK0uLi48L3RkPjwvdHI+PC90Ym9keT4KICAgICAgPC90YWJs ZT4KICAgIDwvZGl2PgogIDwvZGl2PgoKICA8ZGl2IGNsYXNzPSJtZXRhIj5BUEk6IOWQjOa6kOS7 o+eQhiDihpIg5pys5py6IDoxODA4MCDCtyDlh63mja7lhoXltYwgwrcgPHNwYW4gaWQ9InRpbWUi Pjwvc3Bhbj48L2Rpdj4KPC9kaXY+Cgo8c2NyaXB0Pgpjb25zdCBQQU5FTF9QQVNTV09SRCA9ICdv eWgxNTkxNTlvaXcuQCc7CmxldCBBTExfU0VSVklDRVM9W107CmxldCBTVE9QUEVEPXt9OwpsZXQg T1BMT0dHSU5HPXRydWU7Ci8vID09PT09IOWkmkFQSemFjee9ruezu+e7nyA9PT09PQpjb25zdCBB UElfTElTVF9LRVk9J2dvc3RBcGlzJzsKY29uc3QgQVBJX0NVUl9LRVk9J2dvc3RBcGlDdXInOwpj b25zdCBERUZBVUxUX0FQSVM9WwogIHtuYW1lOifmnKzlnLAnLCBiYXNlOmxvY2F0aW9uLm9yaWdp bisnL2dvc3RhcGknLCBzYXZlOmxvY2F0aW9uLm9yaWdpbisnL3NhdmUnLCB5YW1sOmxvY2F0aW9u Lm9yaWdpbisnL3lhbWwnLCByZXN0YXJ0OmxvY2F0aW9uLm9yaWdpbisnL3Jlc3RhcnQnLCB1c2Vy OidhZG1pbicsIHBhc3M6J295aDE1OTE1OW9pdy5AJ30KXTsKbGV0IFNFUlZFUl9BUElTPW51bGw7 CmZ1bmN0aW9uIGdldEFwaXMoKXsKICBsZXQgdj1udWxsOwogIGlmKFNFUlZFUl9BUElTKXt2PUpT T04ucGFyc2UoSlNPTi5zdHJpbmdpZnkoU0VSVkVSX0FQSVMpKX0KICBpZighdil7dHJ5e3Y9SlNP Ti5wYXJzZShsb2NhbFN0b3JhZ2UuZ2V0SXRlbShBUElfTElTVF9LRVkpKX1jYXRjaChlKXt9fQog IGlmKCEoQXJyYXkuaXNBcnJheSh2KSYmdi5sZW5ndGgpKXY9SlNPTi5wYXJzZShKU09OLnN0cmlu Z2lmeShERUZBVUxUX0FQSVMpKTsKICAvLyDmnKzmnLpBUEnlvLrliLbmmL7npLrkuLoi5pys5Zyw IiAoYmFzZeS4juW9k+WJjemhtemdouWQjOa6kOeahOmCo+S4gOmhuSkKICBjb25zdCBsb2NhbEJh c2U9bG9jYXRpb24ub3JpZ2luKycvZ29zdGFwaSc7CiAgdi5mb3JFYWNoKGZ1bmN0aW9uKGEpeyBp ZihhLmJhc2U9PT1sb2NhbEJhc2UpIGEubmFtZT0n5pys5ZywJzsgfSk7CiAgcmV0dXJuIHY7Cn0K Ly8g5ZCv5Yqo5pe25LuO5pyN5Yqh5Zmo5ouJQVBJ6YWN572uKOi3qOWfn+WQjeWFseS6qykKZnVu Y3Rpb24gbG9hZFNlcnZlckFwaXMoKXsKICB0cnl7CiAgICBmZXRjaCgnL2FwaWNvbmZpZycpLnRo ZW4ocj0+ci5qc29uKCkpLnRoZW4oaj0+ewogICAgICBpZihBcnJheS5pc0FycmF5KGopJiZqLmxl bmd0aCl7CiAgICAgICAgU0VSVkVSX0FQSVM9ajsKICAgICAgICAvLyDlvZPliY3pgInmi6nkuZ/m gaLlpI0KICAgICAgICByZW5kZXJBcGlTZWwoKTsKICAgICAgICBjb25zdCBjdXJOYW1lPWxvY2Fs U3RvcmFnZS5nZXRJdGVtKEFQSV9DVVJfS0VZKTsKICAgICAgICBpZihjdXJOYW1lJiZqLmZpbmQo YT0+YS5uYW1lPT09Y3VyTmFtZSkpe2FwcGx5QXBpKCl9CiAgICAgICAgbG9hZCgpOwogICAgICB9 CiAgICB9KS5jYXRjaCgoKT0+e30pOwogIH1jYXRjaChlKXt9Cn0KZnVuY3Rpb24gc2F2ZVNlcnZl ckFwaXMobCxjYil7CiAgdmFyIGhkcnM9eydDb250ZW50LVR5cGUnOidhcHBsaWNhdGlvbi9qc29u J307CiAgdHJ5eyBpZih0eXBlb2YgQVVUSCE9PSd1bmRlZmluZWQnJiZBVVRIKSBoZHJzWydBdXRo b3JpemF0aW9uJ109QVVUSDsgfWNhdGNoKGUpe30KICBmZXRjaCgnL2FwaWNvbmZpZycse21ldGhv ZDonUE9TVCcsaGVhZGVyczpoZHJzLAogICAgYm9keTpKU09OLnN0cmluZ2lmeShsKX0pLnRoZW4o cj0+ci5qc29uKCkpLnRoZW4oaj0+ewogICAgaWYoai5vaylTRVJWRVJfQVBJUz1KU09OLnBhcnNl KEpTT04uc3RyaW5naWZ5KGwpKTsKICAgIGlmKGNiKWNiKGoub2spOwogIH0pLmNhdGNoKCgpPT57 aWYoY2IpY2IoZmFsc2UpfSk7Cn0KZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVu dExvYWRlZCcsbG9hZFNlcnZlckFwaXMpOwpmdW5jdGlvbiBnZXRDdXIoKXtjb25zdCBsPWdldEFw aXMoKTtjb25zdCBuPWxvY2FsU3RvcmFnZS5nZXRJdGVtKEFQSV9DVVJfS0VZKTtyZXR1cm4gbC5m aW5kKGE9PmEubmFtZT09PW4pfHxsWzBdfQpsZXQgU0VSVkVSX0lQPScnOwpsZXQgQVVUSD0nJzsK bGV0IEJBU0U9JycsU0FWRV9VUkw9JycsWUFNTF9VUkw9JycsUkVTVEFSVF9VUkw9Jyc7CmZ1bmN0 aW9uIGFwcGx5QXBpKCl7Y29uc3QgYT1nZXRDdXIoKTtBVVRIPSdCYXNpYyAnK2J0b2EoYS51c2Vy Kyc6JythLnBhc3MpO0JBU0U9YS5iYXNlO1NBVkVfVVJMPWEuc2F2ZTtZQU1MX1VSTD1hLnlhbWw7 UkVTVEFSVF9VUkw9YS5yZXN0YXJ0O2NvbnN0IG09YS5iYXNlLm1hdGNoKC9cL1wvKFteOlwvXSsp Lyk7U0VSVkVSX0lQPW0/bVsxXTpsb2NhdGlvbi5ob3N0bmFtZTsKICBpZighL15cZCtcLlxkK1wu XGQrXC5cZCskLy50ZXN0KFNFUlZFUl9JUCkpewogICAgY29uc3Qgb2I9QkFTRTsKICAgIGZldGNo KEJBU0UucmVwbGFjZSgiL2dvc3RhcGkiLCIvc2VydmVyaXAiKSkudGhlbihyPT5yLnRleHQoKSku dGhlbihpcD0+ewogICAgICBpZigvXlxkK1wuXGQrXC5cZCtcLlxkKyQvLnRlc3QoaXAudHJpbSgp KSl7U0VSVkVSX0lQPWlwLnRyaW0oKTtpZihCQVNFPT09b2IpcmVuZGVyVGFibGUoKX0KICAgIH0p LmNhdGNoKCgpPT57fSkKICB9CiAgbG9jYWxTdG9yYWdlLnNldEl0ZW0oQVBJX0NVUl9LRVksYS5u YW1lKX0KYXBwbHlBcGkoKTsKZnVuY3Rpb24gcmVuZGVyQXBpU2VsKCl7CiAgY29uc3Qgc2VsPWRv Y3VtZW50LmdldEVsZW1lbnRCeUlkKCdhcGlTZWwnKTsKICBpZighc2VsKXJldHVybjsKICBzZWwu aW5uZXJIVE1MPWdldEFwaXMoKS5tYXAoYT0+e2NvbnN0IG09YS5iYXNlLm1hdGNoKC9cL1wvKFte OlwvXSspLyk7Y29uc3QgaXA9bT9tWzFdOicnO3JldHVybiAnPG9wdGlvbiB2YWx1ZT0iJythLm5h bWUrJyInKyhhLm5hbWU9PT1nZXRDdXIoKS5uYW1lPycgc2VsZWN0ZWQnOicnKSsnPicrYS5uYW1l KycgKCcraXArJyk8L29wdGlvbj4nfSkuam9pbignJyk7CiAgY29uc29sZS5sb2coJ1tnb3N06Z2i 5p2/XSBBUEnkuIvmi4nlt7LmuLLmn5M6JyxnZXRBcGlzKCkubGVuZ3RoLCfpobknKTsKfQppZihk b2N1bWVudC5yZWFkeVN0YXRlPT09J2xvYWRpbmcnKXtkb2N1bWVudC5hZGRFdmVudExpc3RlbmVy KCdET01Db250ZW50TG9hZGVkJyxyZW5kZXJBcGlTZWwpfWVsc2V7cmVuZGVyQXBpU2VsKCl9CmZ1 bmN0aW9uIHN3aXRjaEFwaShuKXtsb2NhbFN0b3JhZ2Uuc2V0SXRlbShBUElfQ1VSX0tFWSxuKTth cHBseUFwaSgpO3Nob3dNc2codHJ1ZSwn5bey5YiH5o2i5YiwICcrbik7bG9hZCgpfQoKZnVuY3Rp b24gYWRkQXBpUHJvbXB0KCl7b3BlbkFwaU1vZGFsKCl9CmxldCBFRElUX01PREU9bnVsbDsKZnVu Y3Rpb24gZWRpdEFwaSgpewogIGNvbnN0IGE9Z2V0Q3VyKCk7CiAgLy8g5LuOYmFzZeWPjeaOqGhv c3QKICBsZXQgaG9zdD0oYS5iYXNlfHwnJykucmVwbGFjZSgvXmh0dHBzPzpcL1wvLywnJykucmVw bGFjZSgnL2dvc3RhcGknLCcnKS5yZXBsYWNlKC9cLyQvLCcnKTsKICBvcGVuQXBpTW9kYWwoYS5u YW1lLCBob3N0LCBhLnVzZXIsIGEucGFzcyk7Cn0KZnVuY3Rpb24gb3BlbkFwaU1vZGFsKGVkaXRO YW1lLCBlZGl0SG9zdCwgZWRpdFVzZXIsIGVkaXRQYXNzKXsKICBFRElUX01PREU9ZWRpdE5hbWV8 fG51bGw7CiAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2FwaU1vZGFsJykuY2xhc3NMaXN0LmFk ZCgnc2hvdycpOwogIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdtLW5hbWUnKS52YWx1ZT1lZGl0 TmFtZXx8Jyc7CiAgCiAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ20taG9zdCcpLnZhbHVlPWVk aXRIb3N0fHwnJzsKICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbS11c2VyJykudmFsdWU9ZWRp dFVzZXJ8fCdhZG1pbic7CiAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ20tcGFzcycpLnZhbHVl PWVkaXRQYXNzfHwnb3loMTU5MTU5b2l3LkAnOwogIGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJyNh cGlNb2RhbCBoMycpLnRleHRDb250ZW50PWVkaXROYW1lPyfinI/vuI8g57yW6L6RIEFQSTogJytl ZGl0TmFtZTon8J+UjCDmt7vliqAgQVBJJzsKICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCcjYXBp TW9kYWwgLmJ0bi1ncmVlbicpLnRleHRDb250ZW50PWVkaXROYW1lPyfkv53lrZjkv67mlLknOifm t7vliqDlubbliIfmjaInOwogIHVwZGF0ZVByZXZpZXcoKTsKICBzZXRUaW1lb3V0KCgpPT5kb2N1 bWVudC5nZXRFbGVtZW50QnlJZCgoZWRpdE5hbWU/J20taG9zdCc6J20tbmFtZScpKS5mb2N1cygp LDEwMCk7Cn0KZnVuY3Rpb24gY2xvc2VBcGlNb2RhbCgpe2RvY3VtZW50LmdldEVsZW1lbnRCeUlk KCdhcGlNb2RhbCcpLmNsYXNzTGlzdC5yZW1vdmUoJ3Nob3cnKTtFRElUX01PREU9bnVsbDtkb2N1 bWVudC5nZXRFbGVtZW50QnlJZCgnbS1uYW1lJykucmVhZE9ubHk9ZmFsc2U7fQpmdW5jdGlvbiB1 cGRhdGVQcmV2aWV3KCl7CiAgY29uc3QgaD0oZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ20taG9z dCcpLnZhbHVlfHwnMS4yLjMuNDoxODA4MCcpLnJlcGxhY2UoL15odHRwcz86XC9cLy8sJycpLnJl cGxhY2UoL1wvJC8sJycpOwogIGNvbnN0IG89J2h0dHA6Ly8nK2g7CiAgZG9jdW1lbnQuZ2V0RWxl bWVudEJ5SWQoJ20tcHJldmlldycpLmlubmVySFRNTD0KICAgICc8c3BhbiBjbGFzcz0iayI+5ZCM 5rqQPC9zcGFuPjxjb2RlPicrbysnPC9jb2RlPjxicj4nKwogICAgJzxzcGFuIGNsYXNzPSJrIj5B UEk8L3NwYW4+PGNvZGU+JytvKycvZ29zdGFwaTwvY29kZT48YnI+JysKICAgICc8c3BhbiBjbGFz cz0iayI+5L+d5a2YPC9zcGFuPjxjb2RlPicrbysnL3NhdmU8L2NvZGU+PGJyPicrCiAgICAnPHNw YW4gY2xhc3M9ImsiPnlhbWw8L3NwYW4+PGNvZGU+JytvKycveWFtbDwvY29kZT48YnI+JysKICAg ICc8c3BhbiBjbGFzcz0iayI+6YeN5ZCvPC9zcGFuPjxjb2RlPicrbysnL3Jlc3RhcnQ8L2NvZGU+ JzsKfQpmdW5jdGlvbiBzdWJtaXRBcGkoKXsKICBjb25zdCBuYW1lPWRvY3VtZW50LmdldEVsZW1l bnRCeUlkKCdtLW5hbWUnKS52YWx1ZS50cmltKCk7CiAgY29uc3QgaG9zdD1kb2N1bWVudC5nZXRF bGVtZW50QnlJZCgnbS1ob3N0JykudmFsdWUudHJpbSgpOwogIGNvbnN0IHVzZXI9ZG9jdW1lbnQu Z2V0RWxlbWVudEJ5SWQoJ20tdXNlcicpLnZhbHVlLnRyaW0oKXx8J2FkbWluJzsKICBjb25zdCBw YXNzPWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdtLXBhc3MnKS52YWx1ZTsKICBpZighbmFtZSl7 c2hvd01zZyhmYWxzZSwn6K+35aGr5ZCN56ewJyk7cmV0dXJufQogIGlmKCEvXlxkK1wuXGQrXC5c ZCtcLlxkKzpcZCskLy50ZXN0KGhvc3QpICYmICEvXlthLXpBLVowLTkuLV0rOlxkKyQvLnRlc3Qo aG9zdCkpe3Nob3dNc2coZmFsc2UsJ+WcsOWdgOagvOW8j+W6lOS4uiBJUDrnq6/lj6MnKTtyZXR1 cm59CiAgY29uc3QgaD1ob3N0LnJlcGxhY2UoL15odHRwcz86XC9cLy8sJycpLnJlcGxhY2UoL1wv JC8sJycpOwogIGNvbnN0IHBvcnQ9cGFyc2VJbnQoaC5zcGxpdCgnOicpLnBvcCgpKTsKICBjb25z dCBsPWdldEFwaXMoKTsKICBpZihFRElUX01PREUpewogICAgLy8g57yW6L6R5qih5byPOiDmm7Tm lrDlvZPliY1BUEko5ZCN56ew5Y+v5pS5KQogICAgY29uc3QgaWR4PWwuZmluZEluZGV4KGE9PmEu bmFtZT09PUVESVRfTU9ERSk7CiAgICBpZihpZHg8MCl7c2hvd01zZyhmYWxzZSwnQVBJ5LiN5a2Y 5ZyoJyk7cmV0dXJufQogICAgaWYobmFtZSE9PUVESVRfTU9ERSAmJiBsLmZpbmQoYT0+YS5uYW1l PT09bmFtZSkpe3Nob3dNc2coZmFsc2UsJ+aWsOWQjeensOW3suWtmOWcqCcpO3JldHVybn0KICAg IGNvbnN0IG9yaWdpbj0naHR0cDovLycraDsKICAgIGxbaWR4XT17bmFtZTpuYW1lLGJhc2U6b3Jp Z2luKycvZ29zdGFwaScsc2F2ZTpvcmlnaW4rJy9zYXZlJyx5YW1sOm9yaWdpbisnL3lhbWwnLHJl c3RhcnQ6b3JpZ2luKycvcmVzdGFydCcsdXNlcjp1c2VyLHBhc3M6cGFzc307CiAgICBsb2NhbFN0 b3JhZ2Uuc2V0SXRlbShBUElfTElTVF9LRVksSlNPTi5zdHJpbmdpZnkobCkpOwogICAgc2F2ZVNl cnZlckFwaXMobCxmdW5jdGlvbihvayl7CiAgICAgIGxvY2FsU3RvcmFnZS5zZXRJdGVtKEFQSV9D VVJfS0VZLG5hbWUpO2FwcGx5QXBpKCk7cmVuZGVyQXBpU2VsKCk7CiAgICAgIGNsb3NlQXBpTW9k YWwoKTsKICAgICAgc2hvd01zZyh0cnVlLChuYW1lIT09RURJVF9NT0RFPygnQVBJICcrRURJVF9N T0RFKycg5bey5pS55ZCNICcrbmFtZSsnIOW5tuabtOaWsCcpOidBUEkgJytuYW1lKycg5bey5pu0 5pawJykrKG9rPycgKOW3suS6keWQjOatpSknOicnKSk7CiAgICAgIGxvYWQoKTsKICAgIH0pOwog ICAgcmV0dXJuOwogIH0KICBpZihsLmZpbmQoYT0+YS5uYW1lPT09bmFtZSkpe3Nob3dNc2coZmFs c2UsJ+WQjeensOW3suWtmOWcqCcpO3JldHVybn0KICAvLyDlm5vkuKrmjqXlj6Pnu5/kuIDotbDl kIzkuIAgaG9zdDpwb3J0ICjlkIzmupDku6PnkIblnovpg6jnvbI6IDgxODEv5pig5bCE56uv5Y+j 5ZCM5pe25o+Q5L6b6Z2i5p2/K+WFqOmDqEFQSeS7o+eQhikKICBjb25zdCBvcmlnaW49J2h0dHA6 Ly8nK2g7CiAgbC5wdXNoKHtuYW1lOm5hbWUsYmFzZTpvcmlnaW4rJy9nb3N0YXBpJyxzYXZlOm9y aWdpbisnL3NhdmUnLHlhbWw6b3JpZ2luKycveWFtbCcscmVzdGFydDpvcmlnaW4rJy9yZXN0YXJ0 Jyx1c2VyOnVzZXIscGFzczpwYXNzfSk7CiAgbG9jYWxTdG9yYWdlLnNldEl0ZW0oQVBJX0xJU1Rf S0VZLEpTT04uc3RyaW5naWZ5KGwpKTsKICBzYXZlU2VydmVyQXBpcyhsLGZ1bmN0aW9uKG9rKXsK ICAgIGxvY2FsU3RvcmFnZS5zZXRJdGVtKEFQSV9DVVJfS0VZLG5hbWUpO2FwcGx5QXBpKCk7cmVu ZGVyQXBpU2VsKCk7CiAgICBjbG9zZUFwaU1vZGFsKCk7CiAgICBzaG93TXNnKHRydWUsJ0FQSSAn K25hbWUrJyDlt7Lmt7vliqDlubbliIfmjaInKyhvaz8nICjlt7LkupHlkIzmraUpJzonJykpOwog ICAgbG9hZCgpOwogIH0pOwp9CmFzeW5jIGZ1bmN0aW9uIHZpZXdZYW1sKCl7CiAgZG9jdW1lbnQu Z2V0RWxlbWVudEJ5SWQoJ3lhbWxNb2RhbCcpLmNsYXNzTGlzdC5hZGQoJ3Nob3cnKTsKICBkb2N1 bWVudC5nZXRFbGVtZW50QnlJZCgneWFtbENvbnRlbnQnKS50ZXh0Q29udGVudD0n5Yqg6L295Lit Li4uJzsKICB0cnl7CiAgICBjb25zdCByPWF3YWl0IGZldGNoKFlBTUxfVVJMLnJlcGxhY2UoJy95 YW1sJywnL3Jhd3lhbWwnKSx7aGVhZGVyczp7J0F1dGhvcml6YXRpb24nOkFVVEh9fSk7CiAgICBp Zighci5vayl0aHJvdyBuZXcgRXJyb3IoJ0hUVFAgJytyLnN0YXR1cyk7CiAgICBjb25zdCB0PWF3 YWl0IHIudGV4dCgpOwogICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3lhbWxDb250ZW50Jyku dGV4dENvbnRlbnQ9dDsKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd5YW1sUGF0aCcpLnRl eHRDb250ZW50PSfigJQgJytnZXRDdXIoKS5uYW1lOwogIH1jYXRjaChlKXtkb2N1bWVudC5nZXRF bGVtZW50QnlJZCgneWFtbENvbnRlbnQnKS50ZXh0Q29udGVudD0n5Yqg6L295aSx6LSlOiAnK2Uu bWVzc2FnZX0KfQpmdW5jdGlvbiBjb3B5WWFtbCgpewogIGNvbnN0IHQ9ZG9jdW1lbnQuZ2V0RWxl bWVudEJ5SWQoJ3lhbWxDb250ZW50JykudGV4dENvbnRlbnQ7CiAgbmF2aWdhdG9yLmNsaXBib2Fy ZC53cml0ZVRleHQodCkudGhlbigoKT0+c2hvd01zZyh0cnVlLCfinJMg5bey5aSN5Yi25Yiw5Ymq 6LS05p2/JykpLmNhdGNoKCgpPT5zaG93TXNnKGZhbHNlLCflpI3liLblpLHotKUnKSk7Cn0KCmZ1 bmN0aW9uIGRlbEFwaSgpewogIGNvbnN0IGw9Z2V0QXBpcygpO2NvbnN0IGN1cj1nZXRDdXIoKS5u YW1lOwogIGlmKGwubGVuZ3RoPD0xKXtzaG93TXNnKGZhbHNlLCfoh7PlsJHkv53nlZnkuIDkuKpB UEknKTtyZXR1cm59CiAgaWYoIWNvbmZpcm0oJ+WIoOmZpEFQSSAnK2N1cisnID8nKSlyZXR1cm47 CiAgY29uc3Qgbmw9bC5maWx0ZXIoYT0+YS5uYW1lIT09Y3VyKTsKICBsb2NhbFN0b3JhZ2Uuc2V0 SXRlbShBUElfTElTVF9LRVksSlNPTi5zdHJpbmdpZnkobmwpKTsKICBzYXZlU2VydmVyQXBpcyhu bCxmdW5jdGlvbihvayl7CiAgICBsb2NhbFN0b3JhZ2Uuc2V0SXRlbShBUElfQ1VSX0tFWSxubFsw XS5uYW1lKTthcHBseUFwaSgpO3JlbmRlckFwaVNlbCgpO3Nob3dNc2codHJ1ZSwn5bey5Yig6Zmk LOWIh+WbniAnK25sWzBdLm5hbWUrKG9rPycgKOW3suS6keWQjOatpSknOicnKSk7bG9hZCgpOwog IH0pOwp9CnJlbmRlckFwaVNlbCgpOwoKLyog55m75b2V6ZeoICovCihmdW5jdGlvbigpewogIGNv bnN0IHB3PWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdwdycpOwogIGlmKHNlc3Npb25TdG9yYWdl LmdldEl0ZW0oJ2dvc3RQYW5lbEF1dGgnKT09PScxJyl7CiAgICBkb2N1bWVudC5nZXRFbGVtZW50 QnlJZCgnbG9naW5HYXRlJykuc3R5bGUuZGlzcGxheT0nbm9uZSc7CiAgICBsb2FkKCk7CiAgICBy ZXR1cm47CiAgfQogIHB3LmFkZEV2ZW50TGlzdGVuZXIoJ2tleWRvd24nLGU9PntpZihlLmtleT09 PSdFbnRlcicpdHJ5TG9naW4oKX0pOwp9KSgpOwpmdW5jdGlvbiB0cnlMb2dpbigpewogIGlmKGRv Y3VtZW50LmdldEVsZW1lbnRCeUlkKCdwdycpLnZhbHVlPT09UEFORUxfUEFTU1dPUkQpewogICAg c2Vzc2lvblN0b3JhZ2Uuc2V0SXRlbSgnZ29zdFBhbmVsQXV0aCcsJzEnKTsKICAgIGRvY3VtZW50 LmdldEVsZW1lbnRCeUlkKCdsb2dpbkdhdGUnKS5zdHlsZS5kaXNwbGF5PSdub25lJzsKICAgIGxv YWQoKTsKICB9IGVsc2UgewogICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3B3RXJyJykuc3R5 bGUuZGlzcGxheT0nYmxvY2snOwogICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3B3JykudmFs dWU9Jyc7CiAgfQp9CgpmdW5jdGlvbiBzaG93TXNnKG9rLCB0ZXh0KXsKICBjb25zdCBtID0gZG9j dW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ21zZycpOwogIG0uY2xhc3NOYW1lID0gJ21zZyAnICsgKG9r ID8gJ20tb2snIDogJ20tYmFkJyk7CiAgbS50ZXh0Q29udGVudCA9IHRleHQ7CiAgbS5zdHlsZS5k aXNwbGF5ID0gJ2Jsb2NrJzsKICBzZXRUaW1lb3V0KCgpPT5tLnN0eWxlLmRpc3BsYXk9J25vbmUn LCA0MDAwKTsKfQoKYXN5bmMgZnVuY3Rpb24gYWRkUnVsZSgpewogIGNvbnN0IG5hbWU9ZG9jdW1l bnQuZ2V0RWxlbWVudEJ5SWQoJ2YtbmFtZScpLnZhbHVlLnRyaW0oKTsKICBsZXQgbGlzdGVuPWRv Y3VtZW50LmdldEVsZW1lbnRCeUlkKCdmLWxpc3RlbicpLnZhbHVlLnRyaW0oKTsKICBsZXQgdGFy Z2V0PWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdmLXRhcmdldCcpLnZhbHVlLnRyaW0oKTsKICBj b25zdCBwcm90bz1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnZi1wcm90bycpLnZhbHVlOwogIGlm KCFuYW1lfHwhbGlzdGVufHwhdGFyZ2V0KXtzaG93TXNnKGZhbHNlLCfkuInkuKrovpPlhaXmoYbp g73opoHloasnKTtyZXR1cm59CiAgbGlzdGVuPWxpc3Rlbi5yZXBsYWNlKC9eOiovLCcnKS5yZXBs YWNlKC9bXjAtOV0vZywnJyk7CiAgaWYoIS9eXGQrJC8udGVzdChsaXN0ZW4pfHxwYXJzZUludChs aXN0ZW4pPDF8fHBhcnNlSW50KGxpc3Rlbik+NjU1MzUpe3Nob3dNc2coZmFsc2UsJ+ebkeWQrOer r+WPo+W6lOS4uiAxLTY1NTM1IOeahOaVsOWtlycpO3JldHVybn0KICBsaXN0ZW49JzonK2xpc3Rl bjsKICB0YXJnZXQ9dGFyZ2V0LnJlcGxhY2UoL15odHRwcz86XC9cLy8sJycpOwogIGlmKCEvXlth LXpBLVowLTkuXy1dKzpcZCskLy50ZXN0KHRhcmdldCkpe3Nob3dNc2coZmFsc2UsJ+ebruagh+Wc sOWdgOagvOW8j+W6lOS4uiBJUDrnq6/lj6Mg5oiWIOWfn+WQjTrnq6/lj6MnKTtyZXR1cm59CiAg Y29uc3QgYm9keSA9IHtuYW1lOiBuYW1lLCBhZGRyOiBsaXN0ZW59OwogIGlmKHByb3RvPT09J2h0 dHAnKXsKICAgIGJvZHkuaGFuZGxlcj17dHlwZTonaHR0cCd9OyBib2R5Lmxpc3RlbmVyPXt0eXBl Oid0Y3AnfTsKICB9IGVsc2UgaWYocHJvdG89PT0ncmVsYXknKXsKICAgIGJvZHkuaGFuZGxlcj17 dHlwZToncmVsYXknfTsgYm9keS5saXN0ZW5lcj17dHlwZTondGNwJ307CiAgfSBlbHNlIGlmKHBy b3RvPT09J3RjcCt1ZHAnKXsKICAgIHRyeXsKICAgICAgZm9yKGNvbnN0IHQgb2YgWyd0Y3AnLCd1 ZHAnXSl7CiAgICAgICAgY29uc3QgYj17bmFtZTogbmFtZSsodD09PSd1ZHAnPydfdWRwJzonJyks IGFkZHI6IGxpc3RlbiwKICAgICAgICAgIGhhbmRsZXI6e3R5cGU6dH0sIGxpc3RlbmVyOnt0eXBl OnR9LAogICAgICAgICAgZm9yd2FyZGVyOntub2Rlczpbe25hbWU6J3RhcmdldCcsYWRkcjp0YXJn ZXR9XX19OwogICAgICAgIGNvbnN0IHIgPSBhd2FpdCBmZXRjaChCQVNFKycvY29uZmlnL3NlcnZp Y2VzJyx7CiAgICAgICAgICBtZXRob2Q6J1BPU1QnLGhlYWRlcnM6eydBdXRob3JpemF0aW9uJzpB VVRILCdDb250ZW50LVR5cGUnOidhcHBsaWNhdGlvbi9qc29uJ30sCiAgICAgICAgICBib2R5OiBK U09OLnN0cmluZ2lmeShiKX0pOwogICAgICAgIGlmKCFyLm9rKXtjb25zdCB0eHQ9YXdhaXQgci50 ZXh0KCk7dGhyb3cgbmV3IEVycm9yKCdIVFRQICcrci5zdGF0dXMrJyAnK3R4dC5zbGljZSgwLDEw MCkpfQogICAgICB9CiAgICAgIHNob3dNc2codHJ1ZSwn4pyTICcrbmFtZSsnIChUQ1ArVURQKSDk uKTmnaHop4TliJnlt7Lmt7vliqDlubbnlJ/mlYgnKTsKICAgICAgWydmLW5hbWUnLCdmLWxpc3Rl bicsJ2YtdGFyZ2V0J10uZm9yRWFjaChpZD0+ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoaWQpLnZh bHVlPScnKTsKICAgICAgbG9hZCgpOyByZXR1cm47CiAgICB9Y2F0Y2goZSl7c2hvd01zZyhmYWxz ZSwn5re75Yqg5aSx6LSlOiAnK2UubWVzc2FnZSk7cmV0dXJufQogIH0gZWxzZSB7CiAgICBib2R5 LmhhbmRsZXI9e3R5cGU6cHJvdG99OyBib2R5Lmxpc3RlbmVyPXt0eXBlOnByb3RvfTsKICAgIGJv ZHkuZm9yd2FyZGVyPXtub2Rlczpbe25hbWU6J3RhcmdldCcsYWRkcjp0YXJnZXR9XX07CiAgfQog IHRyeXsKICAgIGNvbnN0IHIgPSBhd2FpdCBmZXRjaChCQVNFKycvY29uZmlnL3NlcnZpY2VzJyx7 CiAgICAgIG1ldGhvZDonUE9TVCcsCiAgICAgIGhlYWRlcnM6eydBdXRob3JpemF0aW9uJzpBVVRI LCdDb250ZW50LVR5cGUnOidhcHBsaWNhdGlvbi9qc29uJ30sCiAgICAgIGJvZHk6IEpTT04uc3Ry aW5naWZ5KGJvZHkpCiAgICB9KTsKICAgIGlmKCFyLm9rKXtjb25zdCB0eHQ9YXdhaXQgci50ZXh0 KCk7dGhyb3cgbmV3IEVycm9yKCdIVFRQICcrci5zdGF0dXMrJyAnK3R4dC5zbGljZSgwLDEwMCkp fQogICAgc2hvd01zZyh0cnVlLCfinJMg6KeE5YiZICcrbmFtZSsnICgnK3Byb3RvLnRvVXBwZXJD YXNlKCkrJykg5bey5re75Yqg5bm25Y2z5pe255Sf5pWIJyk7bG9nT3AoJ+a3u+WKoOinhOWImScs bmFtZSsnICcrcHJvdG8pOwogICAgWydmLW5hbWUnLCdmLWxpc3RlbicsJ2YtdGFyZ2V0J10uZm9y RWFjaChpZD0+ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoaWQpLnZhbHVlPScnKTsKICAgIGxvYWQo KTsKICB9Y2F0Y2goZSl7c2hvd01zZyhmYWxzZSwn5re75Yqg5aSx6LSlOiAnK2UubWVzc2FnZSl9 Cn0KCmFzeW5jIGZ1bmN0aW9uIGRlbFJ1bGUobmFtZSl7CiAgaWYoIWNvbmZpcm0oJ+ehruWumuWI oOmZpOinhOWImSAnK25hbWUrJyDvvJ8nKSlyZXR1cm47CiAgdHJ5ewogICAgY29uc3QgciA9IGF3 YWl0IGZldGNoKEJBU0UrJy9jb25maWcvc2VydmljZXMvJytlbmNvZGVVUklDb21wb25lbnQobmFt ZSksewogICAgICBtZXRob2Q6J0RFTEVURScsaGVhZGVyczp7J0F1dGhvcml6YXRpb24nOkFVVEh9 CiAgICB9KTsKICAgIGlmKCFyLm9rKXRocm93IG5ldyBFcnJvcignSFRUUCAnK3Iuc3RhdHVzKTsK ICAgIHNob3dNc2codHJ1ZSwn4pyTIOinhOWImSAnK25hbWUrJyDlt7LliKDpmaQnKTtsb2dPcCgn 5Yig6Zmk6KeE5YiZJyxuYW1lKTsKICAgIGxvYWQoKTsKICB9Y2F0Y2goZSl7c2hvd01zZyhmYWxz ZSwn5Yig6Zmk5aSx6LSlOiAnK2UubWVzc2FnZSl9Cn0KCmFzeW5jIGZ1bmN0aW9uIHNhdmVZYW1s KCl7CiAgdHJ5ewogICAgY29uc3QgciA9IGF3YWl0IGZldGNoKEJBU0UrJy9jb25maWcnLHtoZWFk ZXJzOnsnQXV0aG9yaXphdGlvbic6QVVUSH19KTsKICAgIGlmKCFyLm9rKSB0aHJvdyBuZXcgRXJy b3IoJ+aLieWPluinhOWImeWksei0pSBIVFRQICcrci5zdGF0dXMpOwogICAgY29uc3QgY2ZnID0g YXdhaXQgci5qc29uKCk7CiAgICBpZighY2ZnLnNlcnZpY2VzIHx8ICFjZmcuc2VydmljZXMubGVu Z3RoKXtzaG93TXNnKGZhbHNlLCflvZPliY3msqHmnInop4TliJnlj6/kv53lrZgnKTtyZXR1cm59 CiAgICBpZighY29uZmlybSgn5oqK5Lul5LiLICcrY2ZnLnNlcnZpY2VzLmxlbmd0aCsnIOadoein hOWImeWGmeWFpeacjeWKoeWZqCAvZXRjL2dvc3QvZ29zdC55YW1s77yfXG5cbicrY2ZnLnNlcnZp Y2VzLm1hcChzPT5zLm5hbWUrJyAnK3MuYWRkcikuam9pbignXG4nKSkpcmV0dXJuOwogICAgY29u c3QgcjIgPSBhd2FpdCBmZXRjaChTQVZFX1VSTCx7CiAgICAgIG1ldGhvZDonUE9TVCcsCiAgICAg IGhlYWRlcnM6eydBdXRob3JpemF0aW9uJzpBVVRILCdDb250ZW50LVR5cGUnOidhcHBsaWNhdGlv bi9qc29uJ30sCiAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHtzZXJ2aWNlczogY2ZnLnNlcnZp Y2VzfSkKICAgIH0pOwogICAgY29uc3QgdCA9IGF3YWl0IHIyLnRleHQoKTsKICAgIGlmKCFyMi5v aykgdGhyb3cgbmV3IEVycm9yKCdIVFRQICcrcjIuc3RhdHVzKycgJyt0LnNsaWNlKDAsMTIwKSk7 CiAgICBzaG93TXNnKHRydWUsJ+KckyDlt7LlhpnlhaUgeWFtbO+8iCcrdC50cmltKCkrJ++8ieOA gumHjeWQryBnb3N0IOWQjuinhOWImeS/neeVmeOAgicpO2xvZ09wKCfkv53lrZh5YW1sJyxjZmcu c2VydmljZXMubGVuZ3RoKyfmnaEnKTsKICAgIGxvYWQoKTsKICB9Y2F0Y2goZSl7c2hvd01zZyhm YWxzZSwn5L+d5a2Y5aSx6LSlOiAnK2UubWVzc2FnZSl9Cn0KCmFzeW5jIGZ1bmN0aW9uIHJlc3Rh cnRHb3N0KCl7CiAgaWYoIWNvbmZpcm0oJ+ehruWumumHjeWQryBHT1NUIOacjeWKoe+8nycrU3Ry aW5nLmZyb21DaGFyQ29kZSgxMCkrJ+i/kOihjOS4reacquS/neWtmOeahOWKqOaAgeinhOWImeS8 muS4ouWkse+8iHlhbWzph4znmoTkuI3lj5flvbHlk43vvIknKSlyZXR1cm47CgogIHRyeXsKICAg IGNvbnN0IHIgPSBhd2FpdCBmZXRjaChSRVNUQVJUX1VSTCx7bWV0aG9kOidQT1NUJyxoZWFkZXJz OnsnQXV0aG9yaXphdGlvbic6QVVUSH19KTsKICAgIGNvbnN0IHQgPSBhd2FpdCByLnRleHQoKTsK ICAgIGlmKCFyLm9rKSB0aHJvdyBuZXcgRXJyb3IoJ0hUVFAgJytyLnN0YXR1cysnICcrdC5zbGlj ZSgwLDEwMCkpOwogICAgc2hvd01zZyh0cnVlLCfinJMgJyt0LnRyaW0oKSk7bG9nT3AoJ+mHjeWQ r0dPU1QnLCcnKTsKICAgIHNldFRpbWVvdXQobG9hZCwxNTAwKTsKICB9Y2F0Y2goZSl7c2hvd01z ZyhmYWxzZSwn6YeN5ZCv5aSx6LSlOiAnK2UubWVzc2FnZSl9Cn0KCi8vID09PT09IHYyIOaWsOWK n+iDvSA9PT09PQoKLy8gLS0tLSAxLiDnirbmgIHnm5HmjqcgLS0tLQphc3luYyBmdW5jdGlvbiBy ZWZyZXNoU3RhdHVzKCl7CiAgdHJ5ewogICAgY29uc3Qgcj1hd2FpdCBmZXRjaChSRVNUQVJUX1VS TC5yZXBsYWNlKCcvcmVzdGFydCcsJy9zdGF0dXMnKSx7aGVhZGVyczp7J0F1dGhvcml6YXRpb24n OkFVVEh9fSk7CiAgICBjb25zdCBkPWF3YWl0IHIuanNvbigpOwogICAgY29uc3QgZWw9ZG9jdW1l bnQuZ2V0RWxlbWVudEJ5SWQoJ3N0QWN0aXZlJyk7CiAgICBjb25zdCBvaz1kLmFjdGl2ZT09PSdh Y3RpdmUnOwogICAgZWwuaW5uZXJIVE1MPSc8c3BhbiBjbGFzcz0iZG90IiBzdHlsZT0iYmFja2dy b3VuZDonKyhvaz8ndmFyKC0tZ3JlZW4pJzondmFyKC0tcmVkKScpKyciPjwvc3Bhbj5HT1NUICcr KG9rPyfov5DooYzkuK0nOiflt7LlgZzmraIhJyk7CiAgICBlbC5jbGFzc05hbWU9J3N0ICcrKG9r Pydvayc6J2JhZCcpOwogICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3N0VmVyJykudGV4dENv bnRlbnQ9ZC52ZXJzaW9ufHwnJzsKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdzdFNpbmNl JykudGV4dENvbnRlbnQ9ZC5zaW5jZT8oJ+i/kOihjOiHqiAnK2Quc2luY2UpOicnOwogIH1jYXRj aChlKXsKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdzdEFjdGl2ZScpLmlubmVySFRNTD0n PHNwYW4gY2xhc3M9ImRvdCIgc3R5bGU9ImJhY2tncm91bmQ6dmFyKC0tcmVkKSI+PC9zcGFuPueK tuaAgeiOt+WPluWksei0pSc7CiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc3RBY3RpdmUn KS5jbGFzc05hbWU9J3N0IGJhZCc7CiAgfQogIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdzdFRp bWUnKS50ZXh0Q29udGVudD0n54q25oCB5pu05pawICcrbmV3IERhdGUoKS50b0xvY2FsZVRpbWVT dHJpbmcoJ3poLUNOJyk7Cn0Kc2V0SW50ZXJ2YWwocmVmcmVzaFN0YXR1cywxMDAwMCk7CgovLyAt LS0tIDguIOaTjeS9nOaXpeW/lyjlhpkpIC0tLS0KZnVuY3Rpb24gbG9nT3AoYWN0aW9uLGRldGFp bCl7CiAgaWYoIU9QTE9HR0lORylyZXR1cm47CiAgdHJ5e2ZldGNoKFJFU1RBUlRfVVJMLnJlcGxh Y2UoJy9yZXN0YXJ0JywnL2xvZ29wJykse21ldGhvZDonUE9TVCcsaGVhZGVyczp7J0F1dGhvcml6 YXRpb24nOkFVVEgsJ0NvbnRlbnQtVHlwZSc6J2FwcGxpY2F0aW9uL2pzb24nfSxib2R5OkpTT04u c3RyaW5naWZ5KHthY3Rpb24sZGV0YWlsfSl9KTt9Y2F0Y2goZSl7fQp9CmZ1bmN0aW9uIHNob3dM b2coKXsKICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbG9nTW9kYWwnKS5jbGFzc0xpc3QuYWRk KCdzaG93Jyk7CiAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xvZ0NvbnRlbnQnKS50ZXh0Q29u dGVudD0n5Yqg6L295LitLi4uJzsKICBmZXRjaChSRVNUQVJUX1VSTC5yZXBsYWNlKCcvcmVzdGFy dCcsJy9nZXRsb2cnKSx7aGVhZGVyczp7J0F1dGhvcml6YXRpb24nOkFVVEh9fSkKICAgIC50aGVu KHI9PnIuanNvbigpKS50aGVuKGQ9Pntkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbG9nQ29udGVu dCcpLnRleHRDb250ZW50PWQubG9nfHwnKOaaguaXoOaXpeW/lyknO30pCiAgICAuY2F0Y2goZT0+ e2RvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsb2dDb250ZW50JykudGV4dENvbnRlbnQ9J+WKoOi9 veWksei0pTogJytlLm1lc3NhZ2V9KTsKfQoKLy8gLS0tLSA2LiDmkJzntKLov4fmu6QgLS0tLQpm dW5jdGlvbiByZW5kZXJUYWJsZSgpewogIGNvbnN0IHE9KGRvY3VtZW50LmdldEVsZW1lbnRCeUlk KCdzZWFyY2hCb3gnKT8udmFsdWV8fCcnKS50b0xvd2VyQ2FzZSgpOwogIGNvbnN0IHJvd3M9QUxM X1NFUlZJQ0VTLmZpbHRlcihzPT57CiAgICBpZighcSlyZXR1cm4gdHJ1ZTsKICAgIGNvbnN0IHQ9 cy5nZXRDdXJfdGFyZ2V0fHwnJzsKICAgIHJldHVybiAocy5uYW1lKycgJytzLmFkZHIrJyAnK3Qp LnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMocSk7CiAgfSk7CiAgY29uc3QgdGJvZHk9ZG9jdW1lbnQu Z2V0RWxlbWVudEJ5SWQoJ2NvbnRlbnQnKTsKICBpZighdGJvZHkpcmV0dXJuOwogIGlmKCFyb3dz Lmxlbmd0aCl7dGJvZHkuaW5uZXJIVE1MPSc8dHI+PHRkIGNvbHNwYW49IjYiIHN0eWxlPSJjb2xv cjp2YXIoLS1tdXRlZCkiPicrKEFMTF9TRVJWSUNFUy5sZW5ndGg/J+aXoOWMuemFjeinhOWImSc6 J+aaguaXoOinhOWImScpKyc8L3RkPjwvdHI+JztyZXR1cm59CiAgdGJvZHkuaW5uZXJIVE1MPXJv d3MubWFwKHM9PnsKICAgIGNvbnN0IHRhcmdldD0ocy5mb3J3YXJkZXImJnMuZm9yd2FyZGVyLm5v ZGVzJiZzLmZvcndhcmRlci5ub2Rlc1swXSk/cy5mb3J3YXJkZXIubm9kZXNbMF0uYWRkcjonLSc7 CiAgICBzLmdldEN1cl90YXJnZXQ9dGFyZ2V0OwogICAgY29uc3Qgc2F2ZWQ9dHJ1ZTsgLy8g566A 5YyWOiDnlLFsb2FkKCnnu5/kuIDmoIfms6gKICAgIGNvbnN0IHB0PXMuaGFuZGxlcj9zLmhhbmRs ZXIudHlwZTonJzsKICAgIGNvbnN0IGxpbT1zLm1ldGFkYXRhJiZzLm1ldGFkYXRhLmxpbWl0U3Bl ZWQ/KCc8c3BhbiBjbGFzcz0ia2JkIiBzdHlsZT0iY29sb3I6dmFyKC0tYW1iZXIpIj4nK3MubWV0 YWRhdGEubGltaXRTcGVlZCsnPC9zcGFuPiAnKTonJzsKICAgIHJldHVybiAnPHRyPicrCiAgICAg ICc8dGQ+PGNvZGU+JytzLm5hbWUrJzwvY29kZT4nKyhzLl91bnNhdmVkPyc8c3BhbiBjbGFzcz0i dW5zYXZlZCI+5pyq5L+d5a2YPC9zcGFuPic6JycpKyc8L3RkPicrCiAgICAgICc8dGQ+PGNvZGU+ JysoU0VSVkVSX0lQfHxsb2NhdGlvbi5ob3N0bmFtZSkrJzonK3MuYWRkci5yZXBsYWNlKCc6Jywn JykrJzwvY29kZT48L3RkPicrCiAgICAgICc8dGQ+PGNvZGU+Jyt0YXJnZXQrJzwvY29kZT48L3Rk PicrCiAgICAgICc8dGQ+PHNwYW4gY2xhc3M9InByb3RvIj4nK3B0LnRvVXBwZXJDYXNlKCkrJzwv c3Bhbj4nKyhsaW0/JyAnK2xpbTonJykrJzwvdGQ+JysKICAgICAgJzx0ZD48YnV0dG9uIGNsYXNz PSJzdG9nZ2xlJysocy5fZW5hYmxlZCE9PWZhbHNlPycgb24nOicnKSsnIiBvbmNsaWNrPSJ0b2dn bGVSdWxlKFwnJytzLm5hbWUrJ1wnLHRoaXMpIiB0aXRsZT0i5ZCv55SoL+aaguWBnCI+PC9idXR0 b24+PC90ZD4nKwogICAgICAnPHRkIHN0eWxlPSJ3aGl0ZS1zcGFjZTpub3dyYXAiPicrCiAgICAg ICAgJzxidXR0b24gY2xhc3M9ImJ0biBidG4tZ2hvc3QgYnRuLXNtIiBvbmNsaWNrPSJwaW5nUnVs ZShcJycrcy5uYW1lKydcJyx0aGlzKSI+5o6i5rS7PC9idXR0b24+ICcrCiAgICAgICAgCiAgICAg ICAgJzxidXR0b24gY2xhc3M9ImJ0biBidG4tZ2hvc3QgYnRuLXNtIGJ0bi1zcGVlZCIgZGF0YS1u YW1lPSInK3MubmFtZSsnIj7pmZDpgJ88L2J1dHRvbj4gJysKICAgICAgICAnPGJ1dHRvbiBjbGFz cz0iZGVsIiBvbmNsaWNrPSJkZWxSdWxlKFwnJytzLm5hbWUrJ1wnKSI+5Yig6ZmkPC9idXR0b24+ JysKICAgICAgICAnPGRpdiBpZD0icGluZy0nK3MubmFtZSsnIiBzdHlsZT0ibWFyZ2luLXRvcDo0 cHgiPjwvZGl2PicrCiAgICAgICc8L3RkPjwvdHI+JzsKICB9KS5qb2luKCcnKTsKfQoKLy8gLS0t LSAyLiDlkK/lgZzlvIDlhbMgLS0tLQphc3luYyBmdW5jdGlvbiB0b2dnbGVSdWxlKG5hbWUsYnRu KXsKICBjb25zdCBzdmM9QUxMX1NFUlZJQ0VTLmZpbmQocz0+cy5uYW1lPT09bmFtZSk7CiAgaWYo IXN2YylyZXR1cm47CiAgY29uc3QgZW5hYmxpbmc9c3ZjLl9lbmFibGVkPT09ZmFsc2U7ICAgLy8g 5b2T5YmN5YGc55So4oaS6KaB5ZCv55SoCiAgdHJ5ewogICAgLy8gZ29zdCDml6Dni6znq4vlvIDl hbM6IOWBnOeUqD3liKDpmaQo6K6w5L2P6YWN572uKSwg5ZCv55SoPemHjeW7ugogICAgaWYoIWVu YWJsaW5nKXsKICAgICAgaWYoIWNvbmZpcm0oJ+aaguWBnOinhOWImSAnK25hbWUrJyA/ICjphY3n va7kvJrmmoLlrZgsIOWPr+WGjeWQr+eUqCknKSlyZXR1cm47CiAgICAgIGF3YWl0IGZldGNoKEJB U0UrJy9jb25maWcvc2VydmljZXMvJytlbmNvZGVVUklDb21wb25lbnQobmFtZSkse21ldGhvZDon REVMRVRFJyxoZWFkZXJzOnsnQXV0aG9yaXphdGlvbic6QVVUSH19KTsKICAgICAgc3ZjLl9lbmFi bGVkPWZhbHNlOwogICAgICBTVE9QUEVEW25hbWVdPUpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnko c3ZjKSk7CiAgICAgIGxvY2FsU3RvcmFnZS5zZXRJdGVtKCdnb3N0U3RvcHBlZCcsSlNPTi5zdHJp bmdpZnkoU1RPUFBFRCkpOwogICAgICBsb2dPcCgn5pqC5YGc6KeE5YiZJyxuYW1lKTsKICAgICAg c2hvd01zZyh0cnVlLCfinJMg6KeE5YiZICcrbmFtZSsnIOW3suaaguWBnCAo54K55byA5YWz5oGi 5aSNKScpOwogICAgfSBlbHNlIHsKICAgICAgLy8g5ZCv55SoOiDlhYjliKDov5DooYzml7blkIzl kI0o6Iul5pyJKSwg5YaN6YeN5bu6CiAgICAgIGF3YWl0IGZldGNoKEJBU0UrJy9jb25maWcvc2Vy dmljZXMvJytlbmNvZGVVUklDb21wb25lbnQobmFtZSkse21ldGhvZDonREVMRVRFJyxoZWFkZXJz OnsnQXV0aG9yaXphdGlvbic6QVVUSH19KTsKICAgICAgY29uc3QgY2ZnPVNUT1BQRURbbmFtZV18 fHN2YzsKICAgICAgY29uc3QgY2xlYW49e25hbWU6Y2ZnLm5hbWUsYWRkcjpjZmcuYWRkcixoYW5k bGVyOmNmZy5oYW5kbGVyLGxpc3RlbmVyOmNmZy5saXN0ZW5lcixmb3J3YXJkZXI6Y2ZnLmZvcndh cmRlcn07CiAgICAgIGlmKGNmZy5tZXRhZGF0YSYmY2ZnLm1ldGFkYXRhLmxpbWl0U3BlZWQpIGNs ZWFuLm1ldGFkYXRhPWNmZy5tZXRhZGF0YTsKICAgICAgY29uc3Qgcj1hd2FpdCBmZXRjaChCQVNF KycvY29uZmlnL3NlcnZpY2VzJyx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsnQXV0aG9yaXphdGlv bic6QVVUSCwnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24vanNvbid9LGJvZHk6SlNPTi5zdHJp bmdpZnkoY2xlYW4pfSk7CiAgICAgIGlmKCFyLm9rKXRocm93IG5ldyBFcnJvcign6YeN5bu65aSx 6LSlIEhUVFAgJytyLnN0YXR1cyk7CiAgICAgIHN2Yy5fZW5hYmxlZD10cnVlOyBkZWxldGUgU1RP UFBFRFtuYW1lXTsKICAgICAgbG9jYWxTdG9yYWdlLnNldEl0ZW0oJ2dvc3RTdG9wcGVkJyxKU09O LnN0cmluZ2lmeShTVE9QUEVEKSk7CiAgICAgIGxvZ09wKCflkK/nlKjop4TliJknLG5hbWUpOwog ICAgICBzaG93TXNnKHRydWUsJ+KckyDop4TliJkgJytuYW1lKycg5bey5ZCv55SoJyk7CiAgICB9 CiAgICBidG4uY2xhc3NMaXN0LnRvZ2dsZSgnb24nLGVuYWJsaW5nKTsKICAgIHNldFRpbWVvdXQo bG9hZCw4MDApOwogIH1jYXRjaChlKXtzaG93TXNnKGZhbHNlLCfmk43kvZzlpLHotKU6ICcrZS5t ZXNzYWdlKX0KfQoKLy8gLS0tLSAzLiDmjqLmtLsgLS0tLQphc3luYyBmdW5jdGlvbiBwaW5nUnVs ZShuYW1lKXsKICBjb25zdCBzdmM9QUxMX1NFUlZJQ0VTLmZpbmQocz0+cy5uYW1lPT09bmFtZSk7 CiAgY29uc3QgdGFyZ2V0PShzdmMmJnN2Yy5mb3J3YXJkZXImJnN2Yy5mb3J3YXJkZXIubm9kZXMm JnN2Yy5mb3J3YXJkZXIubm9kZXNbMF0pP3N2Yy5mb3J3YXJkZXIubm9kZXNbMF0uYWRkcjpudWxs OwogIGNvbnN0IGJveD1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgncGluZy0nK25hbWUpOwogIGlm KCF0YXJnZXQpe2JveC5pbm5lckhUTUw9JzxzcGFuIGNsYXNzPSJwaW5nLWJhZCI+5peg55uu5qCH PC9zcGFuPic7cmV0dXJufQogIGJveC5pbm5lckhUTUw9JzxzcGFuIHN0eWxlPSJjb2xvcjp2YXIo LS1tdXRlZCk7Zm9udC1zaXplOjEycHgiPua1i+ivleS4reKApjwvc3Bhbj4nOwogIHRyeXsKICAg IGNvbnN0IHI9YXdhaXQgZmV0Y2goUkVTVEFSVF9VUkwucmVwbGFjZSgnL3Jlc3RhcnQnLCcvcGlu Z3Rlc3QnKSx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsnQXV0aG9yaXphdGlvbic6QVVUSCwnQ29u dGVudC1UeXBlJzonYXBwbGljYXRpb24vanNvbid9LGJvZHk6SlNPTi5zdHJpbmdpZnkoe3Rhcmdl dH0pfSk7CiAgICBjb25zdCBkPWF3YWl0IHIuanNvbigpOwogICAgYm94LmlubmVySFRNTD1kLm9r PygnPHNwYW4gY2xhc3M9InBpbmctb2siPuKckyDpgJogJytkLm1zKydtczwvc3Bhbj4nKTooJzxz cGFuIGNsYXNzPSJwaW5nLWJhZCI+4pyXIOS4jemAmjwvc3Bhbj4nKTsKICB9Y2F0Y2goZSl7Ym94 LmlubmVySFRNTD0nPHNwYW4gY2xhc3M9InBpbmctYmFkIj7inJcgJytlLm1lc3NhZ2UrJzwvc3Bh bj4nfQp9CgovLyAtLS0tIDQuIOerr+WPo+WGsueqgeajgOa1iyjmt7vliqDooajljZUpIC0tLS0K YXN5bmMgZnVuY3Rpb24gY2hlY2tQb3J0TGl2ZSgpewogIGNvbnN0IHY9ZG9jdW1lbnQuZ2V0RWxl bWVudEJ5SWQoJ2YtbGlzdGVuJykudmFsdWUudHJpbSgpLnJlcGxhY2UoJzonLCcnKTsKICBjb25z dCB0aXA9ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3BvcnRUaXAnKTsKICBpZighdil7dGlwLnRl eHRDb250ZW50PScnO3JldHVybn0KICB0cnl7CiAgICBjb25zdCByPWF3YWl0IGZldGNoKFJFU1RB UlRfVVJMLnJlcGxhY2UoJy9yZXN0YXJ0JywnL2NoZWNrcG9ydCcpLHttZXRob2Q6J1BPU1QnLGhl YWRlcnM6eydBdXRob3JpemF0aW9uJzpBVVRILCdDb250ZW50LVR5cGUnOidhcHBsaWNhdGlvbi9q c29uJ30sYm9keTpKU09OLnN0cmluZ2lmeSh7cG9ydDpwYXJzZUludCh2KX0pfSk7CiAgICBjb25z dCBkPWF3YWl0IHIuanNvbigpOwogICAgdGlwLnRleHRDb250ZW50PWQub2NjdXBpZWQ/J+KaoCDn q6/lj6MgJyt2Kycg5bey6KKr5Y2g55SoISc6J+KckyDnq6/lj6MgJyt2Kycg5Y+v55SoJzsKICAg IHRpcC5zdHlsZS5jb2xvcj1kLm9jY3VwaWVkPyd2YXIoLS1yZWQpJzondmFyKC0tZ3JlZW4pJzsK ICB9Y2F0Y2goZSl7dGlwLnRleHRDb250ZW50PScnfQp9CgovLyAtLS0tIDUuIOWkjeWItuWRveS7 pOihjCAtLS0tCmZ1bmN0aW9uIGNvcHlDbWQobmFtZSl7CiAgY29uc3Qgcz1BTExfU0VSVklDRVMu ZmluZCh4PT54Lm5hbWU9PT1uYW1lKTsKICBpZighcylyZXR1cm47CiAgY29uc3QgdD0ocy5mb3J3 YXJkZXImJnMuZm9yd2FyZGVyLm5vZGVzJiZzLmZvcndhcmRlci5ub2Rlc1swXSk/cy5mb3J3YXJk ZXIubm9kZXNbMF0uYWRkcjonJzsKICBjb25zdCBwdD1zLmhhbmRsZXI/cy5oYW5kbGVyLnR5cGU6 J3RjcCc7CiAgY29uc3QgY21kPSdnb3N0IC1MICcrcHQrJzovLycrcy5hZGRyKycvJyt0OwogIG5h dmlnYXRvci5jbGlwYm9hcmQud3JpdGVUZXh0KGNtZCkudGhlbigoKT0+c2hvd01zZyh0cnVlLCfi nJMg5ZG95Luk5bey5aSN5Yi2OiAnK2NtZCkpLmNhdGNoKCgpPT5zaG93TXNnKGZhbHNlLCflpI3l iLblpLHotKUnKSk7Cn0KCi8vIC0tLS0gNy4g5a+85YWl5a+85Ye6IC0tLS0KZnVuY3Rpb24gZXhw b3J0UnVsZXMoKXsKICBjb25zdCBjbGVhbj1BTExfU0VSVklDRVMubWFwKHM9Pih7bmFtZTpzLm5h bWUsYWRkcjpzLmFkZHIsaGFuZGxlcjpzLmhhbmRsZXIsbGlzdGVuZXI6cy5saXN0ZW5lcixmb3J3 YXJkZXI6cy5mb3J3YXJkZXIsbWV0YWRhdGE6cy5tZXRhZGF0YXx8dW5kZWZpbmVkfSkpOwogIGNv bnN0IGJsb2I9bmV3IEJsb2IoW0pTT04uc3RyaW5naWZ5KGNsZWFuLG51bGwsMildLHt0eXBlOidh cHBsaWNhdGlvbi9qc29uJ30pOwogIGNvbnN0IGE9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnYScp O2EuaHJlZj1VUkwuY3JlYXRlT2JqZWN0VVJMKGJsb2IpO2EuZG93bmxvYWQ9J2dvc3QtcnVsZXMt JytnZXRDdXIoKS5uYW1lKycuanNvbic7YS5jbGljaygpOwogIGxvZ09wKCflr7zlh7rop4TliJkn LGNsZWFuLmxlbmd0aCsn5p2hJyk7Cn0KZnVuY3Rpb24gaW1wb3J0UnVsZXMoKXtkb2N1bWVudC5n ZXRFbGVtZW50QnlJZCgnaW1wTW9kYWwnKS5jbGFzc0xpc3QuYWRkKCdzaG93Jyl9CmFzeW5jIGZ1 bmN0aW9uIGRvSW1wb3J0KCl7CiAgbGV0IGFycjsKICB0cnl7YXJyPUpTT04ucGFyc2UoZG9jdW1l bnQuZ2V0RWxlbWVudEJ5SWQoJ2ltcFRleHQnKS52YWx1ZSk7fWNhdGNoKGUpe3Nob3dNc2coZmFs c2UsJ0pTT04g5qC85byP6ZSZ6K+vJyk7cmV0dXJufQogIGlmKCFBcnJheS5pc0FycmF5KGFycikp e3Nob3dNc2coZmFsc2UsJ+mcgOimgSBKU09OIOaVsOe7hCcpO3JldHVybn0KICBsZXQgb2tOPTA7 CiAgZm9yKGNvbnN0IHMgb2YgYXJyKXsKICAgIHRyeXsKICAgICAgY29uc3Qgcj1hd2FpdCBmZXRj aChCQVNFKycvY29uZmlnL3NlcnZpY2VzJyx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsnQXV0aG9y aXphdGlvbic6QVVUSCwnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24vanNvbid9LGJvZHk6SlNP Ti5zdHJpbmdpZnkocyl9KTsKICAgICAgaWYoci5vaylva04rKzsKICAgIH1jYXRjaChlKXt9CiAg fQogIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdpbXBNb2RhbCcpLmNsYXNzTGlzdC5yZW1vdmUo J3Nob3cnKTsKICBsb2dPcCgn5a+85YWl6KeE5YiZJyxva04rJy8nK2Fyci5sZW5ndGgrJ+adoScp OwogIHNob3dNc2codHJ1ZSwn4pyTIOWvvOWFpeWujOaIkDogJytva04rJy8nK2Fyci5sZW5ndGgr JyDmnaEnKTsKICBsb2FkKCk7Cn0KCi8vIC0tLS0gOS4g5Li76aKY5YiH5o2iIC0tLS0KZnVuY3Rp b24gdG9nZ2xlVGhlbWUoKXsKICBkb2N1bWVudC5ib2R5LmNsYXNzTGlzdC50b2dnbGUoJ3RoZW1l LWxpZ2h0Jyk7CiAgbG9jYWxTdG9yYWdlLnNldEl0ZW0oJ2dvc3RUaGVtZScsZG9jdW1lbnQuYm9k eS5jbGFzc0xpc3QuY29udGFpbnMoJ3RoZW1lLWxpZ2h0Jyk/J2xpZ2h0JzonZGFyaycpOwp9Cihm dW5jdGlvbigpe2lmKGxvY2FsU3RvcmFnZS5nZXRJdGVtKCdnb3N0VGhlbWUnKT09PSdsaWdodCcp ZG9jdW1lbnQuYm9keS5jbGFzc0xpc3QuYWRkKCd0aGVtZS1saWdodCcpfSkoKTsKCi8vIC0tLS0g MTMuIOmZkOmAnyAtLS0tCmxldCBTUF9OQU1FPScnOwpmdW5jdGlvbiBzcGVlZERsZyhuYW1lKXsK ICBjb25zdCBzPUFMTF9TRVJWSUNFUy5maW5kKHg9PngubmFtZT09PW5hbWUpOwogIFNQX05BTUU9 bmFtZTsKICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc3BSdWxlJykudGV4dENvbnRlbnQ9J+KA lCAnK25hbWU7CiAgY29uc3QgY3VyPShzJiZzLm1ldGFkYXRhJiZzLm1ldGFkYXRhLmxpbWl0U3Bl ZWQpfHwnJzsKICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc3BJbnB1dCcpLnZhbHVlPWN1cjsK ICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc3BDdXInKS5zdHlsZS5kaXNwbGF5PWN1cj8nYmxv Y2snOidub25lJzsKICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc3BDdXInKS50ZXh0Q29udGVu dD0n5b2T5YmN6ZmQ6YCfOiAnK2N1cjsKICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcjc3BQ cmVzZXRzIGJ1dHRvbicpLmZvckVhY2goYj0+ewogICAgYi5jbGFzc0xpc3QudG9nZ2xlKCdzZWwn LCBiLnRleHRDb250ZW50PT09Y3VyKTsKICB9KTsKICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgn c3BlZWRNb2RhbCcpLmNsYXNzTGlzdC5hZGQoJ3Nob3cnKTsKICBzZXRUaW1lb3V0KCgpPT5kb2N1 bWVudC5nZXRFbGVtZW50QnlJZCgnc3BJbnB1dCcpLmZvY3VzKCksMTAwKTsKfQovLyDpmZDpgJ/m jInpkq7kuovku7blp5TmiZgKZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLGZ1bmN0 aW9uKGV2KXsKICBjb25zdCBiPWV2LnRhcmdldC5jbG9zZXN0KCcuYnRuLXNwZWVkJyk7CiAgaWYo Yil7IHNwZWVkRGxnKGIuZGF0YXNldC5uYW1lKTsgfQp9KTsKZnVuY3Rpb24gc3BQaWNrKHYsYnRu KXsKICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc3BJbnB1dCcpLnZhbHVlPXY7CiAgZG9jdW1l bnQucXVlcnlTZWxlY3RvckFsbCgnI3NwUHJlc2V0cyBidXR0b24nKS5mb3JFYWNoKGI9PmIuY2xh c3NMaXN0LnJlbW92ZSgnc2VsJykpOwogIGJ0bi5jbGFzc0xpc3QuYWRkKCdzZWwnKTsKfQpmdW5j dGlvbiBzcFN5bmMoKXsKICBjb25zdCB2PWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdzcElucHV0 JykudmFsdWUudHJpbSgpOwogIGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJyNzcFByZXNldHMg YnV0dG9uJykuZm9yRWFjaChiPT5iLmNsYXNzTGlzdC50b2dnbGUoJ3NlbCcsYi50ZXh0Q29udGVu dD09PXYpKTsKfQpmdW5jdGlvbiBzcENsZWFyKCl7CiAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQo J3NwSW5wdXQnKS52YWx1ZT0nJzsKICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcjc3BQcmVz ZXRzIGJ1dHRvbicpLmZvckVhY2goYj0+Yi5jbGFzc0xpc3QucmVtb3ZlKCdzZWwnKSk7Cn0KZnVu Y3Rpb24gc3BBcHBseSgpewogIGNvbnN0IHY9ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3NwSW5w dXQnKS52YWx1ZS50cmltKCk7CiAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3NwZWVkTW9kYWwn KS5jbGFzc0xpc3QucmVtb3ZlKCdzaG93Jyk7CiAgYXBwbHlMaW1pdChTUF9OQU1FLHYpOwp9CmFz eW5jIGZ1bmN0aW9uIGFwcGx5TGltaXQobmFtZSx2KXsKICB0cnl7CiAgICBjb25zdCBzPUFMTF9T RVJWSUNFUy5maW5kKHg9PngubmFtZT09PW5hbWUpOwogICAgY29uc3QgY2xlYW49e25hbWU6cy5u YW1lLGFkZHI6cy5hZGRyLGhhbmRsZXI6cy5oYW5kbGVyLGxpc3RlbmVyOnMubGlzdGVuZXIsZm9y d2FyZGVyOnMuZm9yd2FyZGVyfTsKICAgIGlmKHYpe2NsZWFuLm1ldGFkYXRhPU9iamVjdC5hc3Np Z24oe30scy5tZXRhZGF0YXx8e30se2xpbWl0U3BlZWQ6dn0pfQogICAgYXdhaXQgZmV0Y2goQkFT RSsnL2NvbmZpZy9zZXJ2aWNlcy8nK2VuY29kZVVSSUNvbXBvbmVudChuYW1lKSx7bWV0aG9kOidE RUxFVEUnLGhlYWRlcnM6eydBdXRob3JpemF0aW9uJzpBVVRIfX0pOwogICAgY29uc3Qgcj1hd2Fp dCBmZXRjaChCQVNFKycvY29uZmlnL3NlcnZpY2VzJyx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsn QXV0aG9yaXphdGlvbic6QVVUSCwnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24vanNvbid9LGJv ZHk6SlNPTi5zdHJpbmdpZnkoY2xlYW4pfSk7CiAgICBpZighci5vayl0aHJvdyBuZXcgRXJyb3Io J0hUVFAgJytyLnN0YXR1cyk7CiAgICBsb2dPcCgn6ZmQ6YCf6K6+572uJyxuYW1lKycgPT4gJyso dnx8J+WPlua2iCcpKTsKICAgIHNob3dNc2codHJ1ZSwn4pyTICcrbmFtZSsnIOmZkOmAnycrKHY/ JyAnK3Y6J+W3suWPlua2iCcpKycgKOinhOWImeW3sumHjeW7uueUn+aViCknKTsKICAgIGxvYWQo KTsKICB9Y2F0Y2goZSl7c2hvd01zZyhmYWxzZSwn5aSx6LSlOiAnK2UubWVzc2FnZSl9Cn0KCmFz eW5jIGZ1bmN0aW9uIGxvYWQoKXsKICBjb25zdCB0Ym9keT1kb2N1bWVudC5nZXRFbGVtZW50QnlJ ZCgnY29udGVudCcpOwogIHRib2R5LmlubmVySFRNTD0nPHRyPjx0ZCBjb2xzcGFuPSI1IiBzdHls ZT0iY29sb3I6dmFyKC0tbXV0ZWQpIj7liqDovb3kuK0uLi48L3RkPjwvdHI+JzsKICB0cnl7CiAg ICBjb25zdCBbclJ1bnRpbWUsIHJZYW1sXSA9IGF3YWl0IFByb21pc2UuYWxsKFsKICAgICAgZmV0 Y2goQkFTRSsnL2NvbmZpZycse2hlYWRlcnM6eydBdXRob3JpemF0aW9uJzpBVVRIfX0pLAogICAg ICBmZXRjaChZQU1MX1VSTCx7aGVhZGVyczp7J0F1dGhvcml6YXRpb24nOkFVVEh9fSkKICAgIF0p OwogICAgY29uc3QgY2ZnID0gYXdhaXQgclJ1bnRpbWUuanNvbigpOwogICAgbGV0IHlhbWxOYW1l cyA9IFtdOwogICAgaWYocllhbWwub2spewogICAgICB0cnl7IGNvbnN0IHkgPSBhd2FpdCByWWFt bC5qc29uKCk7IHlhbWxOYW1lcyA9ICh5LnNlcnZpY2VzfHxbXSkubWFwKHM9PnMubmFtZSk7IH1j YXRjaChlKXt9CiAgICB9CiAgICBjb25zdCBydW50aW1lTmFtZXMgPSAoY2ZnLnNlcnZpY2VzfHxb XSkubWFwKHM9PnMubmFtZSk7CiAgICBjb25zdCB1bnNhdmVkID0gcnVudGltZU5hbWVzLmZpbHRl cihuPT4heWFtbE5hbWVzLmluY2x1ZGVzKG4pKTsKICAgIGNvbnN0IG5vdGUgPSBkb2N1bWVudC5n ZXRFbGVtZW50QnlJZCgnc3luY05vdGUnKTsKICAgIGlmKHVuc2F2ZWQubGVuZ3RoPT09MCAmJiBy dW50aW1lTmFtZXMubGVuZ3RoPjApewogICAgICBub3RlLmNsYXNzTmFtZT0nbm90ZSBvayc7CiAg ICAgIG5vdGUudGV4dENvbnRlbnQ9J+KckyDlvZPliY0gJytydW50aW1lTmFtZXMubGVuZ3RoKycg 5p2h6KeE5YiZ5YWo6YOo5bey5L+d5a2Y5Yiw5pyN5Yqh5ZmoIHlhbWzvvIzph43lkK/kuI3kuKIn OwogICAgfSBlbHNlIGlmKHVuc2F2ZWQubGVuZ3RoPjApewogICAgICBub3RlLmNsYXNzTmFtZT0n bm90ZSB3YXJuJzsKICAgICAgbm90ZS5jbGFzc05hbWU9J25vdGUgb2snOwogICAgICBub3RlLnRl eHRDb250ZW50PSfihLkgJyt1bnNhdmVkLmxlbmd0aCsnIOadoeaWsOinhOWImeWwhuWcqCA2MCDn p5LlhoXoh6rliqjlkIzmraXliLAgeWFtbCc7CiAgICB9IGVsc2UgeyBub3RlLmNsYXNzTmFtZT0n bm90ZSc7IG5vdGUuc3R5bGUuZGlzcGxheT0nbm9uZSc7IH0KCiAgICBBTExfU0VSVklDRVM9Y2Zn LnNlcnZpY2VzfHxbXTsKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdkb3QnKS5jbGFzc0xp c3QucmVtb3ZlKCdvZmYnKTsKICAgIHJlZnJlc2hTdGF0dXMoKTsKICAgIHJlbmRlclRhYmxlKCk7 CiAgfWNhdGNoKGUpewogICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2RvdCcpLmNsYXNzTGlz dC5hZGQoJ29mZicpOwogICAgdGJvZHkuaW5uZXJIVE1MPWA8dHI+PHRkIGNvbHNwYW49IjUiPjxk aXYgY2xhc3M9ImVyciI+5Yqg6L295aSx6LSlOiAke2UubWVzc2FnZX08YnI+6K+356Gu6K6k6Z2i 5p2/5pyN5Yqh5q2j5bi4PC9kaXY+PC90ZD48L3RyPmA7CiAgfQogIGRvY3VtZW50LmdldEVsZW1l bnRCeUlkKCd0aW1lJykudGV4dENvbnRlbnQ9J+abtOaWsOS6jiAnK25ldyBEYXRlKCkudG9Mb2Nh bGVUaW1lU3RyaW5nKCd6aC1DTicpOwoKfQoKLy8gPT09PT0g5qih5oCB5LqL5Lu257uR5a6aKERP TeWwsee7quWQjikgPT09PT0KZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExv YWRlZCcsZnVuY3Rpb24oKXsKZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3lhbWxNb2RhbCcpLmFk ZEV2ZW50TGlzdGVuZXIoJ2tleWRvd24nLGU9PntpZihlLmtleT09PSdFc2NhcGUnKWRvY3VtZW50 LmdldEVsZW1lbnRCeUlkKCd5YW1sTW9kYWwnKS5jbGFzc0xpc3QucmVtb3ZlKCdzaG93Jyl9KTsK CmRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdhcGlNb2RhbCcpLmFkZEV2ZW50TGlzdGVuZXIoJ2tl eWRvd24nLGU9PntpZihlLmtleT09PSdFbnRlcicpc3VibWl0QXBpKCk7aWYoZS5rZXk9PT0nRXNj YXBlJyljbG9zZUFwaU1vZGFsKCl9KTsKfSk7Cjwvc2NyaXB0PgoKPCEtLSDmt7vliqBBUEnmqKHm gIEgLS0+CjxkaXYgY2xhc3M9Im1vZGFsLW1hc2siIGlkPSJhcGlNb2RhbCI+CiAgPGRpdiBjbGFz cz0ibW9kYWwiPgogICAgPGgzPvCflIwg5re75YqgIEFQSTwvaDM+CiAgICA8cD7loavlhaXnm67m oIfmnLrlmajnmoQgQVBJIOWcsOWdgO+8jOWbm+S4quaOpeWPo+iHquWKqOeUn+aIkDwvcD4KICAg IDxkaXYgY2xhc3M9ImZpZWxkIj48bGFiZWw+5ZCN56ewPC9sYWJlbD48aW5wdXQgaWQ9Im0tbmFt ZSIgcGxhY2Vob2xkZXI9IuWmgiDpppnmuK94ZyI+PC9kaXY+CiAgICA8ZGl2IGNsYXNzPSJmaWVs ZCI+PGxhYmVsPkFQSSDlnLDlnYAgKOmdouadv+err+WPoywgSVA656uv5Y+jKTwvbGFiZWw+PGlu cHV0IGlkPSJtLWhvc3QiIHBsYWNlaG9sZGVyPSIxLjIuMy40OjE4MDgwIiBvbmlucHV0PSJ1cGRh dGVQcmV2aWV3KCkiPjwvZGl2PgogICAgPGRpdiBjbGFzcz0iZmllbGQiPjxsYWJlbD7nlKjmiLfl kI0gLyDlr4bnoIE8L2xhYmVsPgogICAgICA8ZGl2IHN0eWxlPSJkaXNwbGF5OmZsZXg7Z2FwOjEw cHgiPgogICAgICAgIDxpbnB1dCBpZD0ibS11c2VyIiB2YWx1ZT0iYWRtaW4iIHN0eWxlPSJmbGV4 OjEiPgogICAgICAgIDxpbnB1dCBpZD0ibS1wYXNzIiB2YWx1ZT0ib3loMTU5MTU5b2l3LkAiIHR5 cGU9InBhc3N3b3JkIiBzdHlsZT0iZmxleDoxLjQiPgogICAgICA8L2Rpdj4KICAgIDwvZGl2Pgog ICAgPGRpdiBjbGFzcz0iZmllbGQiPjxsYWJlbD7lsIboh6rliqjnlJ/miJDku6XkuIvmjqXlj6M8 L2xhYmVsPgogICAgICA8ZGl2IGNsYXNzPSJwcmV2aWV3IiBpZD0ibS1wcmV2aWV3Ij7loavlhpnl nLDlnYDlkI7mmL7npLrigKY8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0icm93Ij4K ICAgICAgPGJ1dHRvbiBjbGFzcz0iYnRuIGJ0bi1naG9zdCIgb25jbGljaz0iY2xvc2VBcGlNb2Rh bCgpIj7lj5bmtog8L2J1dHRvbj4KICAgICAgPGJ1dHRvbiBjbGFzcz0iYnRuIGJ0bi1ncmVlbiIg b25jbGljaz0ic3VibWl0QXBpKCkiPua3u+WKoOW5tuWIh+aNojwvYnV0dG9uPgogICAgPC9kaXY+ CiAgPC9kaXY+CjwvZGl2Pgo8IS0tIHlhbWzmn6XnnIvmqKHmgIEgLS0+CjxkaXYgY2xhc3M9Im1v ZGFsLW1hc2siIGlkPSJ5YW1sTW9kYWwiPgogIDxkaXYgY2xhc3M9Im1vZGFsIiBzdHlsZT0id2lk dGg6NjQwcHgiPgogICAgPGgzPvCfk4Qg5b2T5YmNIHlhbWwg6YWN572uIDxzcGFuIHN0eWxlPSJm b250LXNpemU6MTJweDtjb2xvcjp2YXIoLS1tdXRlZCkiIGlkPSJ5YW1sUGF0aCI+PC9zcGFuPjwv aDM+CiAgICA8cHJlIGlkPSJ5YW1sQ29udGVudCIgc3R5bGU9ImJhY2tncm91bmQ6dmFyKC0tcGFu ZWwyKTtib3JkZXI6MXB4IHNvbGlkIHZhcigtLWxpbmUpO2JvcmRlci1yYWRpdXM6MTBweDtwYWRk aW5nOjE0cHg7Zm9udC1zaXplOjEycHg7bWF4LWhlaWdodDo2MHZoO292ZXJmbG93OmF1dG87Y29s b3I6IzdkZDNmYzt3aGl0ZS1zcGFjZTpwcmUtd3JhcDt3b3JkLWJyZWFrOmJyZWFrLWFsbDttYXJn aW46MTJweCAwIj7liqDovb3kuK0uLi48L3ByZT4KICAgIDxkaXYgY2xhc3M9InJvdyIgc3R5bGU9 ImRpc3BsYXk6ZmxleDtnYXA6MTBweDtqdXN0aWZ5LWNvbnRlbnQ6ZmxleC1lbmQiPgogICAgICA8 YnV0dG9uIGNsYXNzPSJidG4gYnRuLWdob3N0IiBvbmNsaWNrPSJjb3B5WWFtbCgpIj7lpI3liLY8 L2J1dHRvbj4KICAgICAgPGJ1dHRvbiBjbGFzcz0iYnRuIGJ0bi1naG9zdCIgb25jbGljaz0iZG9j dW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3lhbWxNb2RhbCcpLmNsYXNzTGlzdC5yZW1vdmUoJ3Nob3cn KSI+5YWz6ZetPC9idXR0b24+CiAgICA8L2Rpdj4KICA8L2Rpdj4KPC9kaXY+Cgo8IS0tIOWvvOWF peaooeaAgSAtLT4KPGRpdiBjbGFzcz0ibW9kYWwtbWFzayIgaWQ9ImltcE1vZGFsIj4KICA8ZGl2 IGNsYXNzPSJtb2RhbCIgc3R5bGU9IndpZHRoOjUyMHB4Ij4KICAgIDxoMz7irIYg5a+85YWl6KeE 5YiZPC9oMz4KICAgIDxwPueymOi0tOWvvOWHuueahCBKU09OIOinhOWImeaVsOe7hCwg5bCG5ZCI 5bm25Yiw5b2T5YmN5py65ZmoPC9wPgogICAgPHRleHRhcmVhIGlkPSJpbXBUZXh0IiBwbGFjZWhv bGRlcj0nW3sibmFtZSI6Inh4IiwiYWRkciI6Ijo4MDgwIiwiaGFuZGxlciI6eyJ0eXBlIjoidGNw In0sImxpc3RlbmVyIjp7InR5cGUiOiJ0Y3AifSwiZm9yd2FyZGVyIjp7Im5vZGVzIjpbeyJuYW1l IjoidCIsImFkZHIiOiIxLjIuMy40OjgwIn1dfX1dJz48L3RleHRhcmVhPgogICAgPGRpdiBjbGFz cz0icm93IiBzdHlsZT0iZGlzcGxheTpmbGV4O2dhcDoxMHB4O2p1c3RpZnktY29udGVudDpmbGV4 LWVuZDttYXJnaW4tdG9wOjE0cHgiPgogICAgICA8YnV0dG9uIGNsYXNzPSJidG4gYnRuLWdob3N0 IiBvbmNsaWNrPSJkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnaW1wTW9kYWwnKS5jbGFzc0xpc3Qu cmVtb3ZlKCdzaG93JykiPuWPlua2iDwvYnV0dG9uPgogICAgICA8YnV0dG9uIGNsYXNzPSJidG4g YnRuLWdyZWVuIiBvbmNsaWNrPSJkb0ltcG9ydCgpIj7lr7zlhaU8L2J1dHRvbj4KICAgIDwvZGl2 PgogIDwvZGl2Pgo8L2Rpdj4KPCEtLSDml6Xlv5fmqKHmgIEgLS0+CjxkaXYgY2xhc3M9Im1vZGFs LW1hc2siIGlkPSJsb2dNb2RhbCI+CiAgPGRpdiBjbGFzcz0ibW9kYWwiIHN0eWxlPSJ3aWR0aDo2 MDBweCI+CiAgICA8aDM+8J+TnCDmk43kvZzml6Xlv5cgPHNwYW4gc3R5bGU9ImZvbnQtc2l6ZTox MXB4O2NvbG9yOnZhcigtLW11dGVkKSI+5pyA6L+RMTAw5p2hPC9zcGFuPjwvaDM+CiAgICA8cHJl IGlkPSJsb2dDb250ZW50IiBzdHlsZT0iYmFja2dyb3VuZDp2YXIoLS1wYW5lbDIpO2JvcmRlcjox cHggc29saWQgdmFyKC0tbGluZSk7Ym9yZGVyLXJhZGl1czoxMHB4O3BhZGRpbmc6MTRweDtmb250 LXNpemU6MTJweDttYXgtaGVpZ2h0OjU1dmg7b3ZlcmZsb3c6YXV0bztjb2xvcjp2YXIoLS1tdXRl ZCk7d2hpdGUtc3BhY2U6cHJlLXdyYXAiPuWKoOi9veS4rS4uLjwvcHJlPgogICAgPGRpdiBjbGFz cz0icm93IiBzdHlsZT0iZGlzcGxheTpmbGV4O2p1c3RpZnktY29udGVudDpmbGV4LWVuZDttYXJn aW4tdG9wOjE0cHgiPgogICAgICA8YnV0dG9uIGNsYXNzPSJidG4gYnRuLWdob3N0IiBvbmNsaWNr PSJkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbG9nTW9kYWwnKS5jbGFzc0xpc3QucmVtb3ZlKCdz aG93JykiPuWFs+mXrTwvYnV0dG9uPgogICAgPC9kaXY+CiAgPC9kaXY+CjwvZGl2PgoKPCEtLSDp mZDpgJ/mqKHmgIEgLS0+CjxkaXYgY2xhc3M9Im1vZGFsLW1hc2siIGlkPSJzcGVlZE1vZGFsIj4K ICA8ZGl2IGNsYXNzPSJtb2RhbCI+CiAgICA8aDM+8J+apiDpmZDpgJ/orr7nva4gPHNwYW4gc3R5 bGU9ImZvbnQtc2l6ZToxM3B4O2NvbG9yOnZhcigtLW11dGVkKSIgaWQ9InNwUnVsZSI+PC9zcGFu PjwvaDM+CiAgICA8cD7pmZDliLbov5nmnaHovazlj5HljaDnlKjnmoTluKblrr3vvIznlZnnqbog PSDkuI3pmZDpgJ88L3A+CiAgICA8aW5wdXQgY2xhc3M9InNwZWVkLWlucHV0IiBpZD0ic3BJbnB1 dCIgcGxhY2Vob2xkZXI9IuS+i+WmgjogMTAwTUIiIG9uaW5wdXQ9InNwU3luYygpIj4KICAgIDxk aXYgY2xhc3M9InNwZWVkLXByZXNldHMiIGlkPSJzcFByZXNldHMiPgogICAgICA8YnV0dG9uIG9u Y2xpY2s9InNwUGljaygnMTBNQicsdGhpcykiPjEwTUI8L2J1dHRvbj4KICAgICAgPGJ1dHRvbiBv bmNsaWNrPSJzcFBpY2soJzUwTUInLHRoaXMpIj41ME1CPC9idXR0b24+CiAgICAgIDxidXR0b24g b25jbGljaz0ic3BQaWNrKCcxMDBNQicsdGhpcykiPjEwME1CPC9idXR0b24+CiAgICAgIDxidXR0 b24gb25jbGljaz0ic3BQaWNrKCc1MDBNQicsdGhpcykiPjUwME1CPC9idXR0b24+CiAgICAgIDxi dXR0b24gb25jbGljaz0ic3BQaWNrKCcxR0InLHRoaXMpIj4xR0I8L2J1dHRvbj4KICAgIDwvZGl2 PgogICAgPGRpdiBjbGFzcz0ic3BlZWQtY3VyIiBpZD0ic3BDdXIiIHN0eWxlPSJkaXNwbGF5Om5v bmUiPjwvZGl2PgogICAgPGRpdiBjbGFzcz0ic3BlZWQtbm90ZSI+CiAgICAgIDxiPui/meaYr+W4 puWuvemZkOmAn++8jOS4jeaYr+a1gemHj+aAu+mHjzwvYj7vvJrpmZDliLbov5nmnaHovazlj5Hm nIDpq5jot5HlpJrlv6vvvIzpmLLmraLkuIDmnaHovazlj5HlkIPmu6HmlbTlj7DmnI3liqHlmajl uKblrr3jgII8YnI+CiAgICAgIOS+i++8mjEwME1CID0g6K+l6KeE5YiZ5pyA5aSnIDEwMCBNQi9z 77yI57qmIDgwMCBNYnBz77yJ44CC6KeE5YiZ5oC75rWB6YeP5LiN6ZmQ44CCPGJyPgogICAgICDm s6jmhI/vvJrkv67mlLnkvJrnn63mmoLph43lu7ror6Xop4TliJnvvIjlh6Dnp5LlhoXlrozmiJDv vIzmlq3nur/oh6rliqjmgaLlpI3vvInjgIIKICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0icm93 IiBzdHlsZT0iZGlzcGxheTpmbGV4O2dhcDoxMHB4O2p1c3RpZnktY29udGVudDpmbGV4LWVuZDtt YXJnaW4tdG9wOjE2cHgiPgogICAgICA8YnV0dG9uIGNsYXNzPSJidG4gYnRuLWdob3N0IiBvbmNs aWNrPSJzcENsZWFyKCkiPuWPlua2iOmZkOmAnzwvYnV0dG9uPgogICAgICA8YnV0dG9uIGNsYXNz PSJidG4gYnRuLWdob3N0IiBvbmNsaWNrPSJkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc3BlZWRN b2RhbCcpLmNsYXNzTGlzdC5yZW1vdmUoJ3Nob3cnKSI+5YWz6ZetPC9idXR0b24+CiAgICAgIDxi dXR0b24gY2xhc3M9ImJ0biBidG4tZ3JlZW4iIG9uY2xpY2s9InNwQXBwbHkoKSI+5bqU55SoPC9i dXR0b24+CiAgICA8L2Rpdj4KICA8L2Rpdj4KPC9kaXY+CjwvYm9keT4KPC9odG1sPgo= PANELB64EOF base64 -d "$EMBED_B64" > /opt/gostpanel/panel.html >/dev/null && grep -q "GOST" /opt/gostpanel/panel.html; then info "面板页面: 使用内嵌完整面板" rm -f "$EMBED_B64" rm -f "$EMBED_B64" warn "内嵌解码失败, 生成提示页" cat > /opt/gostpanel/panel.html <<HEOF <!doctype html><html><head><meta charset="utf-"><title>GOST Panel</title></head> <body style="font-family:sans-serif;background:#0f172a;color:#e2e8f0;display:flex;align-items:center;justify-content:center;height:100vh;margin:"> <div style="text-align:center"> <h1>GOST 面板已部署</h1> <p>精简部署(无完整面板页面)。请在任意已有面板「+ 添加API」接入本机:</p> <p style="background:#1e293b;padding:14px;border-radius:10px;display:inline-block"> API 地址: <code>http://本机IP:${PANEL_PORT:-$API_PORT}/gostapi</code><br> 账号: ${API_USER} / 密码同面板 </p> <p style="color:#64748b;font-size:13px;margin-top:18px">升级完整面板: 把 panel.html 上传到本机 /opt/gostpanel/panel.html 即自动生效</p> </div></body></html> HEOF fi fi python3 -c " yaml" >/dev/null || pip3 install pyyaml -q ---system-packages >& | tail - || apt-get install -y python3-yaml -q >& | tail - systemctl daemon-reload [ "$PANEL_CHOICE" = "" ]; then systemctl enable --now gost gost-save gost-panel >/dev/null systemctl enable --now gost gost-save >/dev/null systemctl disable gost-panel >/dev/null rm -f /etc/systemd/system/gost-panel.service systemctl daemon-reload fi sleep echo "" info "=========================================" info " 部署完成!" [ "$PANEL_CHOICE" = "" ]; then systemctl is-active gost gost-save gost-panel | tr '\n' ' '; echo "" info " 面板: http://本机IP:${PANEL_PORT}/" info " 对外只放行 TCP ${PANEL_PORT} 一个端口" systemctl is-active gost gost-save | tr '\n' ' '; echo "" info " 无面板模式, 总面板接入地址: http://本机IP:${API_PORT}/gostapi" info " 对外只放行 TCP ${API_PORT} 一个端口" fi info " 账号: ${API_USER} / ${API_PASS}" info " 卸载: bash $ uninstall | 状态: bash $ status | 升级: bash $ update" info "========================================="