#!/bin/bash if [ ! -x /usr/bin/ruby ]; then printf '此电脑缺少修复工具需要的系统运行组件,请联系支持。\n' read -r repair_close exit 1 fi /usr/bin/ruby --disable-gems - <<'CS_NETWORK_RUBY' # Built-in macOS Ruby host adapter; guest recovery stays the reviewed Python owner. require 'json' require 'open3' require 'socket' require 'timeout' require 'shellwords' require 'tempfile' require 'fileutils' require 'ipaddr' require 'base64' module CodeSandboxNetworkCustomer class Error < StandardError; end ROOT = File.expand_path('~/Library/Application Support/code-sandbox') MESSAGES = {'healthy'=>'沙箱联网正常','recovered'=>'沙箱联网已恢复','cooldown'=>'已尝试修复,稍后再运行一次', 'upstream_unavailable'=>'联网复测仍未通过,请联系支持检查网络线路','blocked'=>'修复条件未通过,请将检查结果发给支持人员'} def self.execute(argv, input='', seconds=12) out=err=nil; status=nil Open3.popen3(*argv, :pgroup=>true) do |stdin,stdout,stderr,wait| reader=Thread.new { begin; stdout.read; rescue IOError; ''; end }; errors=Thread.new { begin; stderr.read; rescue IOError; ''; end } begin Timeout.timeout(seconds) do stdin.write(input); stdin.close status=wait.value; out=reader.value; err=errors.value end rescue Timeout::Error Process.kill('TERM',-wait.pid) rescue nil raise Error,'检查超时,请稍后重试' ensure stdin.close rescue nil; stdout.close rescue nil; stderr.close rescue nil end end [status.exitstatus,out,err] end def self.atomic(path, bytes, expected=nil, mode=0600) raise Error,'配置文件位置已变化,请联系支持检查' if File.symlink?(path) FileUtils.mkdir_p(File.dirname(path),:mode=>0700) Tempfile.create(['.network-repair-',''],File.dirname(path)) do |f| f.binmode; f.write(bytes); f.flush; f.fsync; f.chmod(mode) raise Error,'配置正在变化,请稍后重试' if expected && File.binread(path)!=expected File.rename(f.path,path) end end def self.outbound(cfg) rows=cfg.fetch('outbounds',[]).select { |x| x['tag']=='proxy-out' } raise Error,'此网络配置需要由应用恢复,请将检查结果发给支持人员' unless rows.size==1 && rows[0]['type']=='socks' rows[0] end def self.aligned(cfg,credential,server,port) row=outbound(cfg) raise Error,'沙箱连接地址已变化,请联系支持检查' unless row['server']==server && row['server_port']==port copy=JSON.parse(JSON.generate(cfg)); outbound(copy).merge!(credential); copy end def self.authenticate(port,credential) u=credential.fetch('username').b; p=credential.fetch('password').b raise Error,'本机连接配置无效,请联系支持检查' unless u.bytesize.between?(1,255) && p.bytesize.between?(1,255) Timeout.timeout(4) do Socket.tcp('127.0.0.1',port,:connect_timeout=>3) do |socket| socket.write([5,1,2].pack('C*')) raise Error,'本机网络隔离组件尚未就绪,请先打开应用' unless socket.read(2)==[5,2].pack('C*') socket.write([1,u.bytesize].pack('C*')+u+[p.bytesize].pack('C')+p) raise Error,'本机拒绝当前连接配置,请联系支持检查' unless socket.read(2)==[1,0].pack('C*') end end rescue Timeout::Error, SystemCallError, IOError raise Error,'本机网络隔离组件尚未就绪,请先打开应用' end def self.repair(id,guest_code,root=ROOT) raise Error,'目标沙箱名称无效' unless id.match?(/\Asbx-[a-zA-Z0-9_-]+\z/) version=execute(['/usr/libexec/PlistBuddy','-c','Print :CFBundleShortVersionString','/Applications/Code Sandbox.app/Contents/Info.plist'])[1].strip return {'status'=>'new_app','message'=>'此版本请使用应用自身的网络恢复;请将检查结果发给支持人员'} unless version=='0.4.14' name='code-sandbox-'+id code,text=execute([File.join(root,'runtime/lima/bin/limactl'),'list','--format','{{.Name}} {{.Status}}']) raise Error,'沙箱状态读取失败,请先打开应用' unless code==0 return {'status'=>'stopped','message'=>'请先从应用启动要修复的沙箱,再运行此文件'} unless text.lines.map(&:split).include?([name,'Running']) base=File.join(root,'browsers',id) credential=JSON.parse(File.binread(File.join(base,'host-proxy-socks-credential.json'))) raise Error,'本机连接配置无效,请联系支持检查' unless credential.keys.sort==['password','username'] && credential.values.all? { |x| x.is_a?(String) && !x.empty? } host_path=File.join(base,'host-singbox.json'); host_raw=File.binread(host_path); host=JSON.parse(host_raw) inbound=host.fetch('inbounds',[]).select { |x| x['tag']=='vm-socks-in' } raise Error,'本机连接配置正在变化,请稍后重试' unless inbound.size==1 && inbound[0].fetch('users',[]).include?(credential) port=inbound[0]['listen_port']; raise Error,'本机连接地址无效,请联系支持检查' unless port.is_a?(Integer) && port.between?(1,65535) authenticate(port,credential) path=File.join(root,'runtimes',id,'sing-box.json'); original=File.binread(path); cfg=JSON.parse(original); server=outbound(cfg)['server'] begin ip=IPAddr.new(server) private_ip=ip.ipv4? && ['10.0.0.0/8','172.16.0.0/12','192.168.0.0/16'].any? { |n| IPAddr.new(n).include?(ip) } rescue IPAddr::InvalidAddressError, TypeError private_ip=false end raise Error,'此沙箱的本机连接地址需要由支持人员检查' unless private_ip updated=aligned(cfg,credential,server,port) if updated!=cfg raise Error,'配置正在变化,请稍后重试' unless File.binread(host_path)==host_raw && JSON.parse(File.binread(File.join(base,'host-proxy-socks-credential.json')))==credential backup=File.join(root,'support-backups','network-customer'); FileUtils.mkdir_p(backup,:mode=>0700) saved=File.join(backup,'sing-box-'+Process.pid.to_s+'-'+(Time.now.to_r*1_000_000_000).to_i.to_s+'.json') File.open(saved,File::WRONLY|File::CREAT|File::EXCL,0600) { |f| f.write(original); f.flush; f.fsync } atomic(path,JSON.pretty_generate(updated)+"\n",original) end raise Error,'配置正在变化,请稍后重试' unless File.binread(host_path)==host_raw && JSON.parse(File.binread(File.join(base,'host-proxy-socks-credential.json')))==credential ssh=File.expand_path('~/.lima/'+name+'/ssh.config'); raise Error,'沙箱连接尚未就绪,请先打开应用' unless File.file?(ssh) body="exec(__import__('base64').b64decode('#{Base64.strict_encode64(guest_code)}'))" command='sudo -n python3 -c '+Shellwords.escape(body) payload=credential.merge('server'=>server,'port'=>port,'repair'=>true,'dropin'=>"[Unit]\nAfter=systemd-networkd.service\nPartOf=systemd-networkd.service\n") code,out=execute(['/usr/bin/ssh','-T','-F',ssh,'-o','BatchMode=yes','-o','ConnectTimeout=5','lima-'+name,command],JSON.generate(payload),48) result=JSON.parse(out); raise Error,'网络修复未完成,请将检查结果发给支持人员' unless result.is_a?(Hash) && result['status'] result['source_changed']=updated!=cfg; result end def self.main(guest_code) root=ROOT; dir=File.join(root,'support','customer-network'); FileUtils.mkdir_p(dir,:mode=>0700); File.chmod(0700,dir) result=nil File.open(File.join(dir,'repair.lock'),'a',0600) do |lock| raise Error,'已有网络检查正在执行,请等该窗口结束' unless lock.flock(File::LOCK_EX|File::LOCK_NB) code,text=execute([File.join(root,'runtime/lima/bin/limactl'),'list','--format','{{.Name}} {{.Status}}']) raise Error,'沙箱状态读取失败,请先打开应用' unless code==0 names=text.lines.map(&:split).select { |r| r.size==2 && r[1]=='Running' && r[0].start_with?('code-sandbox-sbx-') }.map { |r| r[0].sub('code-sandbox-','') } raise Error,'请先从应用启动要修复的沙箱,再运行此文件' if names.empty? id=names[0] if names.size>1 names.each_with_index { |n,i| puts "#{i+1}. #{n}" }; print '请选择要修复的沙箱编号:' answer=STDIN.gets.to_s.strip; raise Error,'未选择沙箱' unless answer.match?(/\A[0-9]+\z/) && answer.to_i.between?(1,names.size) id=names[answer.to_i-1] end puts '正在检查沙箱网络,必要时修复网络隔离组件。已有网络请求可能短暂中断。' result=repair(id,guest_code) atomic(File.join(dir,'修复结果.json'),JSON.pretty_generate(result)+"\n") end puts result['message'] || MESSAGES[result['status']] || '修复未完成,请将检查结果发给支持人员' puts '检查记录:'+File.join(dir,'修复结果.json') ['healthy','recovered'].include?(result['status']) ? 0 : 1 rescue Error => e warn e.message atomic(File.join(dir,'修复结果.json'),JSON.pretty_generate({'status'=>'blocked','message'=>e.message})+"\n") rescue nil warn '检查记录:'+File.join(dir,'修复结果.json') if dir 1 rescue StandardError message='网络检查未完成,请先打开应用;若仍失败,请联系支持。'; warn message atomic(File.join(dir,'修复结果.json'),JSON.pretty_generate({'status'=>'blocked','message'=>message})+"\n") rescue nil warn '检查记录:'+File.join(dir,'修复结果.json') if dir 1 end end begin; STDIN.reopen('/dev/tty'); rescue SystemCallError; end code=CodeSandboxNetworkCustomer.main(Base64.strict_decode64('CmltcG9ydCBqc29uLCBvcywgcGF0aGxpYiwgcmUsIHNodXRpbCwgc3VicHJvY2VzcywgdGVtcGZpbGUsIHRpbWUsIHN5cwpjbGFzcyBHdWFyZEVycm9yKEV4Y2VwdGlvbik6IHBhc3MKZGVmIHJ1bihhcmdzLCB0aW1lb3V0PTEwKToKICAgIHA9c3VicHJvY2Vzcy5ydW4oYXJncyxzdGRvdXQ9c3VicHJvY2Vzcy5QSVBFLHN0ZGVycj1zdWJwcm9jZXNzLlBJUEUsdGltZW91dD10aW1lb3V0KQogICAgcmV0dXJuIHAucmV0dXJuY29kZSxwLnN0ZG91dC5kZWNvZGUoZXJyb3JzPSJyZXBsYWNlIikKZGVmIGxvYWQocGF0aCk6IHJldHVybiBqc29uLmxvYWRzKHBhdGhsaWIuUGF0aChwYXRoKS5yZWFkX3RleHQoKSkKZGVmIG91dGJvdW5kKGcpOgogICAgYT1beCBmb3IgeCBpbiBnLmdldCgib3V0Ym91bmRzIixbXSkgaWYgeC5nZXQoInRhZyIpPT0icHJveHktb3V0Il0KICAgIGlmIGxlbihhKSE9MSBvciBhWzBdLmdldCgidHlwZSIpIT0ic29ja3MiOiByYWlzZSBHdWFyZEVycm9yKCLnvZHnu5zphY3nva7kuI3pgILnlKjmnKzmrKHkv67lpI0iKQogICAgcmV0dXJuIGFbMF0KZGVmIGVuZHBvaW50KGcscCk6CiAgICBvPW91dGJvdW5kKGcpCiAgICBpZiBvLmdldCgic2VydmVyIikhPXBbInNlcnZlciJdIG9yIG8uZ2V0KCJzZXJ2ZXJfcG9ydCIpIT1wWyJwb3J0Il06CiAgICAgICAgcmFpc2UgR3VhcmRFcnJvcigi572R57uc5Zyw5Z2A5bey5Y+Y5YyW77yM5L+u5aSN5bey5pqC5YGcIikKICAgIHJldHVybiBvCmRlZiB3cml0ZShwYXRoLGRhdGEsYmFja3VwPVRydWUsbW9kZT0wbzYwMCk6CiAgICBwYXRoPXBhdGhsaWIuUGF0aChwYXRoKQogICAgaWYgcGF0aC5pc19zeW1saW5rKCk6IHJhaXNlIEd1YXJkRXJyb3IoIumFjee9ruaWh+S7tuS9jee9ruW3suWPmOWMlu+8jOS/ruWkjeW3suaaguWBnCIpCiAgICBvbGQ9cGF0aC5yZWFkX2J5dGVzKCkgaWYgcGF0aC5leGlzdHMoKSBlbHNlIE5vbmUKICAgIGlmIG9sZD09ZGF0YTogcmV0dXJuIEZhbHNlCiAgICByb290PXBhdGhsaWIuUGF0aCgiL3Zhci9iYWNrdXBzL2NvZGUtc2FuZGJveC1uZXR3b3JrLXN1cHBvcnQiKQogICAgcm9vdC5ta2Rpcihtb2RlPTBvNzAwLHBhcmVudHM9VHJ1ZSxleGlzdF9vaz1UcnVlKQogICAgaWYgYmFja3VwIGFuZCBvbGQgaXMgbm90IE5vbmU6CiAgICAgICAgZmQsYj10ZW1wZmlsZS5ta3N0ZW1wKHByZWZpeD1wYXRoLm5hbWUrIi0iLGRpcj1zdHIocm9vdCkpCiAgICAgICAgd2l0aCBvcy5mZG9wZW4oZmQsIndiIikgYXMgZjogZi53cml0ZShvbGQpCiAgICAgICAgb3MuY2htb2QoYiwwbzYwMCkKICAgIHBhdGgucGFyZW50Lm1rZGlyKG1vZGU9MG83NTUscGFyZW50cz1UcnVlLGV4aXN0X29rPVRydWUpCiAgICBmZCx0PXRlbXBmaWxlLm1rc3RlbXAocHJlZml4PSIubmV0d29yay1yZXBhaXItIixkaXI9c3RyKHBhdGgucGFyZW50KSkKICAgIHRyeToKICAgICAgICB3aXRoIG9zLmZkb3BlbihmZCwid2IiKSBhcyBmOiBmLndyaXRlKGRhdGEpOyBmLmZsdXNoKCk7IG9zLmZzeW5jKGYuZmlsZW5vKCkpCiAgICAgICAgb3MuY2htb2QodCxtb2RlKQogICAgICAgIGlmIChwYXRoLnJlYWRfYnl0ZXMoKSBpZiBwYXRoLmV4aXN0cygpIGVsc2UgTm9uZSkhPW9sZDoKICAgICAgICAgICAgcmFpc2UgR3VhcmRFcnJvcigi6YWN572u5q2j5Zyo5Y+Y5YyW77yM56iN5ZCO6YeN6K+VIikKICAgICAgICBvcy5yZXBsYWNlKHQscGF0aCkKICAgIGZpbmFsbHk6CiAgICAgICAgaWYgb3MucGF0aC5leGlzdHModCk6IG9zLnVubGluayh0KQogICAgcmV0dXJuIFRydWUKZGVmIGZpcmV3YWxsX3JlYWR5KHRleHQscCk6CiAgICBuYXJyb3c9YW55KCJtZXRhIHNrdWlkIDkwMDAiIGluIGxpbmUgYW5kCiAgICAgICAgKCJpcCBkYWRkciAiK3BbInNlcnZlciJdKyIgIikgaW4gbGluZSBhbmQKICAgICAgICAoInRjcCBkcG9ydCAiK3N0cihwWyJwb3J0Il0pKyIgIikgaW4gbGluZSBhbmQKICAgICAgICAiIGFjY2VwdCIgaW4gbGluZSBhbmQgJ2NvbW1lbnQgImhvc3QtcHJveHktb25seSInIGluIGxpbmUgZm9yIGxpbmUgaW4gdGV4dC5zcGxpdGxpbmVzKCkpCiAgICByZXR1cm4gInBvbGljeSBkcm9wOyIgaW4gdGV4dCBhbmQgbmFycm93IGFuZCAnIm1hbmFnZWQtdHVubmVsIicgaW4gdGV4dAoKZGVmIGNvbmZpZ3VyZWRfdHVubmVsKGNvbmZpZyk6CiAgICBpbnRlcmZhY2VzPVt4LmdldCgiaW50ZXJmYWNlX25hbWUiKSBmb3IgeCBpbiBjb25maWcuZ2V0KCJpbmJvdW5kcyIsW10pIGlmIHguZ2V0KCJ0eXBlIik9PSJ0dW4iXQogICAgaWYgbGVuKGludGVyZmFjZXMpIT0xIG9yIG5vdCBpc2luc3RhbmNlKGludGVyZmFjZXNbMF0sc3RyKSBvciBub3QgcmUuZnVsbG1hdGNoKHIiY3MtdHVuMHxjcy10dW4tWzAtOWEtZl17OH0iLGludGVyZmFjZXNbMF0pOgogICAgICAgIHJhaXNlIEd1YXJkRXJyb3IoIuaymeeusee9kee7nOaOpeWPo+WwmuacquehruiupO+8jOS/ruWkjeW3suaaguWBnCIpCiAgICByZXR1cm4gaW50ZXJmYWNlc1swXQoKZGVmIHJvdXRlX21hdGNoZXMocm91dGUsdHVubmVsKToKICAgIHJldHVybiBib29sKHJlLnNlYXJjaChyIlxiZGV2ICIrcmUuZXNjYXBlKHR1bm5lbCkrciIoPzpcc3wkKSIscm91dGUpKQoKZGVmIHJlY29uY2lsZShwKToKICAgIGlmIG9zLmdldGV1aWQoKSE9MDogcmFpc2UgR3VhcmRFcnJvcigi57y65bCR5rKZ566x5YaF5L+u5aSN5p2D6ZmQIikKICAgIGZvciBrIGluICgidXNlcm5hbWUiLCJwYXNzd29yZCIpOgogICAgICAgIGlmIG5vdCBpc2luc3RhbmNlKHAuZ2V0KGspLHN0cikgb3Igbm90IHBba106IHJhaXNlIEd1YXJkRXJyb3IoIui/nuaOpemFjee9ruaXoOaViCIpCiAgICBzb3VyY2U9bG9hZCgiL29wdC9jb2RlLXNhbmRib3gvc2luZy1ib3guanNvbiIpCiAgICBzcmM9ZW5kcG9pbnQoc291cmNlLHApCiAgICBpZiBhbnkoc3JjLmdldChrKSE9cFtrXSBmb3IgayBpbiAoInVzZXJuYW1lIiwicGFzc3dvcmQiKSk6CiAgICAgICAgcmFpc2UgR3VhcmRFcnJvcigi5rKZ566x5ZCv5Yqo6YWN572u5bCa5pyq5ZCM5q2l77yM56iN5ZCO6YeN6K+VIikKICAgIGNvZGUsZnc9cnVuKFsibmZ0IiwiLW4iLCItcyIsImxpc3QiLCJ0YWJsZSIsImluZXQiLCJjb2RlX3NhbmRib3giXSkKICAgIGlmIGNvZGUgb3Igbm90IGZpcmV3YWxsX3JlYWR5KGZ3LHApOiByYWlzZSBHdWFyZEVycm9yKCLnvZHnu5zpmpTnprvop4TliJnkuI7lvZPliY3ov57mjqXkuI3ljLnphY3vvIzkv67lpI3lt7LmmoLlgZwiKQogICAgY29uZmlnPWxvYWQoIi9ldGMvc2luZy1ib3gvY29uZmlnLmpzb24iKTsgb3V0PWVuZHBvaW50KGNvbmZpZyxwKQogICAgdHVubmVsPWNvbmZpZ3VyZWRfdHVubmVsKGNvbmZpZykKICAgIGlmIGNvbmZpZ3VyZWRfdHVubmVsKHNvdXJjZSkhPXR1bm5lbCBvciBub3QgYW55KCdvaWZuYW1lICInK3R1bm5lbCsnIicgaW4gbGluZSBhbmQgIiBhY2NlcHQiIGluIGxpbmUgYW5kICdjb21tZW50ICJtYW5hZ2VkLXR1bm5lbCInIGluIGxpbmUgZm9yIGxpbmUgaW4gZncuc3BsaXRsaW5lcygpKToKICAgICAgICByYWlzZSBHdWFyZEVycm9yKCLmspnnrrHnvZHnu5zmjqXlj6PkuI7pmpTnprvop4TliJnkuI3kuIDoh7TvvIzkv67lpI3lt7LmmoLlgZwiKQogICAgY2hhbmdlZD1hbnkob3V0LmdldChrKSE9cFtrXSBmb3IgayBpbiAoInVzZXJuYW1lIiwicGFzc3dvcmQiKSkKICAgIHRyeToKICAgICAgICBsaXZlPWVuZHBvaW50KGxvYWQoIi9ydW4vY29kZS1zYW5kYm94L3NpbmctYm94Lmpzb24iKSxwKQogICAgICAgIGxpdmVfbWF0Y2g9YWxsKGxpdmUuZ2V0KGspPT1wW2tdIGZvciBrIGluICgidXNlcm5hbWUiLCJwYXNzd29yZCIpKQogICAgZXhjZXB0IChGaWxlTm90Rm91bmRFcnJvcixWYWx1ZUVycm9yKTogbGl2ZV9tYXRjaD1GYWxzZQogICAgY29kZSx1aWQ9cnVuKFsiaWQiLCItdSIsIndvcmtzcGFjZSJdKQogICAgaWYgY29kZSBvciBub3QgdWlkLnN0cmlwKCkuaXNkaWdpdCgpOiByYWlzZSBHdWFyZEVycm9yKCLmspnnrrHnu4jnq6/otKblj7fkuI3lj6/nlKgiKQogICAgcm91dGVfY21kPVsiaXAiLCItNCIsInJvdXRlIiwiZ2V0IiwiMS4xLjEuMSIsInVpZCIsdWlkLnN0cmlwKCldCiAgICBjb2RlLHJvdXRlPXJ1bihyb3V0ZV9jbWQpOyByb3V0ZV9vaz1jb2RlPT0wIGFuZCByb3V0ZV9tYXRjaGVzKHJvdXRlLHR1bm5lbCkKICAgIGFjdGl2ZT1ydW4oWyJzeXN0ZW1jdGwiLCJpcy1hY3RpdmUiLCItLXF1aWV0IiwiY29kZS1zYW5kYm94LXNpbmdib3guc2VydmljZSJdKVswXT09MAogICAgbmVlZGVkPWNoYW5nZWQgb3Igbm90IGxpdmVfbWF0Y2ggb3Igbm90IHJvdXRlX29rIG9yIG5vdCBhY3RpdmUKICAgIHJlc3VsdD17InN0YXR1cyI6ImhlYWx0aHkiLCJyZXN0YXJ0ZWQiOkZhbHNlLCJjcmVkZW50aWFsX2NoYW5nZWQiOmNoYW5nZWQsInJvdXRlX3JlYWR5Ijpyb3V0ZV9va30KICAgIGlmIG5lZWRlZCBhbmQgbm90IHBbInJlcGFpciJdOgogICAgICAgIHJlc3VsdFsic3RhdHVzIl09Im5lZWRzX3JlcGFpciI7IHJldHVybiByZXN1bHQKICAgIHN0YXRlX3BhdGg9cGF0aGxpYi5QYXRoKCIvdmFyL2xpYi9jb2RlLXNhbmRib3gtbmV0d29yay1zdXBwb3J0L3N0YXRlLmpzb24iKQogICAgc3RhdGU9bG9hZChzdGF0ZV9wYXRoKSBpZiBzdGF0ZV9wYXRoLmV4aXN0cygpIGVsc2Uge30KICAgIGlmIG5lZWRlZCBhbmQgdGltZS50aW1lKCktc3RhdGUuZ2V0KCJsYXN0X3Jlc3RhcnQiLDApPDMwMDoKICAgICAgICByZXN1bHRbInN0YXR1cyJdPSJjb29sZG93biI7IHJldHVybiByZXN1bHQKICAgIGlmIHBbInJlcGFpciJdOgogICAgICAgIGRyb3A9cGF0aGxpYi5QYXRoKCIvZXRjL3N5c3RlbWQvc3lzdGVtL2NvZGUtc2FuZGJveC1zaW5nYm94LnNlcnZpY2UuZC85MC1zdXBwb3J0LW5ldHdvcmtkLmNvbmYiKQogICAgICAgIGRlc2lyZWQ9cFsiZHJvcGluIl0uZW5jb2RlKCkKICAgICAgICBpZiBkcm9wLmV4aXN0cygpIGFuZCBkcm9wLnJlYWRfYnl0ZXMoKSE9ZGVzaXJlZDoKICAgICAgICAgICAgcmFpc2UgR3VhcmRFcnJvcigi5bey5pyJ572R57uc5oGi5aSN6K6+572u5LiN5ZCM77yM5L+u5aSN5bey5pqC5YGcIikKICAgICAgICBpZiBub3QgZHJvcC5leGlzdHMoKSBhbmQgd3JpdGUoZHJvcCxkZXNpcmVkLG1vZGU9MG82NDQpOgogICAgICAgICAgICBpZiBydW4oWyJzeXN0ZW1jdGwiLCJkYWVtb24tcmVsb2FkIl0pWzBdOiByYWlzZSBHdWFyZEVycm9yKCLnvZHnu5zmgaLlpI3orr7nva7mnKrog73nlJ/mlYgiKQogICAgaWYgbmVlZGVkOgogICAgICAgIGlmIGNoYW5nZWQ6CiAgICAgICAgICAgIG91dC51cGRhdGUoe2s6cFtrXSBmb3IgayBpbiAoInVzZXJuYW1lIiwicGFzc3dvcmQiKX0pCiAgICAgICAgICAgIHdyaXRlKCIvZXRjL3NpbmctYm94L2NvbmZpZy5qc29uIiwoanNvbi5kdW1wcyhjb25maWcsaW5kZW50PTIpKyJcbiIpLmVuY29kZSgpKQogICAgICAgIHdyaXRlKHN0YXRlX3BhdGgsKGpzb24uZHVtcHMoeyJsYXN0X3Jlc3RhcnQiOnRpbWUudGltZSgpfSkrIlxuIikuZW5jb2RlKCksYmFja3VwPUZhbHNlKQogICAgICAgIGlmIHJ1bihbInN5c3RlbWN0bCIsInJlc3RhcnQiLCJjb2RlLXNhbmRib3gtc2luZ2JveC5zZXJ2aWNlIl0sMjApWzBdOgogICAgICAgICAgICByYWlzZSBHdWFyZEVycm9yKCLnvZHnu5zpmpTnprvnu4Tku7bmnKrog73ph43mlrDlkK/liqgiKQogICAgICAgIHJlc3VsdFsicmVzdGFydGVkIl09VHJ1ZQogICAgICAgIGVuZD10aW1lLm1vbm90b25pYygpKzUKICAgICAgICB3aGlsZSB0aW1lLm1vbm90b25pYygpPGVuZDoKICAgICAgICAgICAgY29kZSxyb3V0ZT1ydW4ocm91dGVfY21kKQogICAgICAgICAgICBpZiBjb2RlPT0wIGFuZCByb3V0ZV9tYXRjaGVzKHJvdXRlLHR1bm5lbCk6IGJyZWFrCiAgICAgICAgICAgIHRpbWUuc2xlZXAoLjI1KQogICAgICAgIHJlc3VsdFsicm91dGVfcmVhZHkiXT1jb2RlPT0wIGFuZCByb3V0ZV9tYXRjaGVzKHJvdXRlLHR1bm5lbCkKICAgICAgICBpZiBub3QgcmVzdWx0WyJyb3V0ZV9yZWFkeSJdOiByYWlzZSBHdWFyZEVycm9yKCLmspnnrrHnvZHnu5zot6/nlLHlsJrmnKrmgaLlpI0iKQogICAgICAgIHJ1bihbInJlc29sdmVjdGwiLCJmbHVzaC1jYWNoZXMiXSkKICAgIHBhc3NlZD1GYWxzZQogICAgZm9yIHVybCBpbiAoImh0dHBzOi8vd3d3LmFwcGxlLmNvbSIsImh0dHBzOi8vYXBpLmFudGhyb3BpYy5jb20iKToKICAgICAgICBjb2RlLGh0dHA9cnVuKFsicnVudXNlciIsIi11Iiwid29ya3NwYWNlIiwiLS0iLCJjdXJsIiwiLS1kaXNhYmxlIiwiLS1ub3Byb3h5IiwiKiIsIi1JIiwiLXNTIiwKICAgICAgICAgICItLWNvbm5lY3QtdGltZW91dCIsIjQiLCItLW1heC10aW1lIiwiNiIsIi1vIiwiL2Rldi9udWxsIiwiLXciLCIle2h0dHBfY29kZX0iLHVybF0sOCkKICAgICAgICBpZiBjb2RlPT0wIGFuZCByZS5mdWxsbWF0Y2gociJbMS01XVswLTldezJ9IixodHRwKTogcGFzc2VkPVRydWU7IGJyZWFrCiAgICByZXN1bHRbImh0dHBzX3JlYWR5Il09cGFzc2VkCiAgICByZXN1bHRbInN0YXR1cyJdPSJyZWNvdmVyZWQiIGlmIHBhc3NlZCBhbmQgbmVlZGVkIGVsc2UgImhlYWx0aHkiIGlmIHBhc3NlZCBlbHNlICJ1cHN0cmVhbV91bmF2YWlsYWJsZSIKICAgIHJldHVybiByZXN1bHQKaWYgX19uYW1lX189PSJfX21haW5fXyI6CiAgICB0cnk6CiAgICAgICAgcD1qc29uLmxvYWQoc3lzLnN0ZGluKQogICAgICAgIHByaW50KGpzb24uZHVtcHMocmVjb25jaWxlKHApLGVuc3VyZV9hc2NpaT1GYWxzZSkpCiAgICBleGNlcHQgR3VhcmRFcnJvciBhcyBlOgogICAgICAgIHByaW50KGpzb24uZHVtcHMoeyJzdGF0dXMiOiJibG9ja2VkIiwibWVzc2FnZSI6c3RyKGUpfSxlbnN1cmVfYXNjaWk9RmFsc2UpKTsgc3lzLmV4aXQoMSkKICAgIGV4Y2VwdCBFeGNlcHRpb246CiAgICAgICAgcHJpbnQoanNvbi5kdW1wcyh7InN0YXR1cyI6ImJsb2NrZWQiLCJtZXNzYWdlIjoi572R57uc5qOA5p+l5pyq5a6M5oiQ77yM56iN5ZCO6YeN6K+VIn0sZW5zdXJlX2FzY2lpPUZhbHNlKSk7IHN5cy5leGl0KDEpCg==')) puts; puts '按回车关闭。'; STDIN.gets if STDIN.tty? exit code CS_NETWORK_RUBY