📄 gost-deploy.sh 📝 编辑 ⬇ 下载 ← 返回列表
#!/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 "========================================="