Created
March 19, 2017 03:46
-
-
Save gruis/4c57f676d0d69d05eff9ad4042a3dfe0 to your computer and use it in GitHub Desktop.
Run localtunnel in a restart loop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# Run localtunnel in a loop that will restart it if it crashes. When restarting | |
# due to consecutive crashes the loop will wait for an increasing period of | |
# time before starting localtunnel. You can control the wait time calculation | |
# with the `--backoff` option, and the `--wait` option. | |
# | |
# @see https://github.com/localtunnel/localtunnel/issues/81 | |
# | |
# @example run on port 3000 with an auto assigned subdomain | |
# localtunnel.rb | |
# | |
# @example run on port 8080 with an auto assigned subdomain | |
# localtunnel.rb -p 8080 | |
# | |
# @example run with custom subdomain | |
# localtunnel.rb -s mydomain | |
require 'optparse' | |
require 'open3' | |
require 'logger' | |
options = { | |
port: 3000, | |
wait: 0, | |
backoff: 1.2, | |
wait_max: 60 | |
} | |
parser = OptionParser.new do|opts| | |
opts.banner = "Usage: localtunnel.rb [options]" | |
opts.on('-s', '--subdomain subdomain', 'Subdomain') { |s| options[:subdomain] = s } | |
opts.on('-p', '--port port', 'Port') { |p| options[:port] = p; } | |
opts.on( | |
'-w', '--wait SEC', Integer, | |
"number of seconds to before restarting; default: #{options[:wait]}" | |
) { |w| options[:wait] = w; } | |
opts.on( | |
'-b', '--backoff SEC', Float, | |
"exponent for backoff calculation; default: #{options[:backoff]}" | |
) { |b| options[:backoff] = b; } | |
opts.on( | |
'-m', '--wait-max SEC', Integer, | |
"max number of seconds to wait before restarting after consecutive failures; default: #{options[:max_wait]}" | |
) { |m| options[:max_wait] = m; } | |
opts.on('-h', '--help', 'Displays Help') do | |
puts opts | |
exit | |
end | |
end | |
parser.parse! | |
# A run time of less than 10 seconds counts as consecutive failure | |
MIN_RUN_TIME=10 | |
# TODO: make logger configurable on the CLI | |
logger = Logger.new($stderr) | |
logger.level = Logger::INFO | |
backoff = lambda do |st, rt| | |
return options[:wait] if rt >= MIN_RUN_TIME | |
[[st, 2].max ** options[:backoff].to_f, options[:wait_max]].min.round(2) | |
end | |
wait = lambda do |rt, st| | |
backoff.call(st, rt).tap do |t| | |
if t > 0 | |
logger.info "restarting in #{t} seconds" | |
sleep t | |
end | |
end | |
end | |
time = lambda do |&blk| | |
t = Time.now | |
blk.call | |
Time.now - t | |
end | |
cmd = "lt --port #{options[:port]}" | |
cmd += " --subdomain #{options[:subdomain]}" if options[:subdomain] && !options[:subdomain].empty? | |
launch_count = 0 | |
exit_status = nil | |
run_time = MIN_RUN_TIME + 1 | |
sleep_time = options[:wait] | |
begin | |
sleep_time = wait.call(run_time, sleep_time) | |
launch_count += 1 | |
run_time = time.call do | |
Open3.popen3(cmd) do |sin, sout, serr, thr| | |
logger.info "cnt: #{launch_count}; pid: #{thr.pid}; cmd: #{cmd.inspect}" | |
readers = [sout, serr] | |
begin | |
readers.each { |io| logger.info io.read_nonblock(64).chomp } | |
rescue IO::WaitReadable | |
readers, _ = IO.select([sout, serr]) | |
retry if thr.status | |
end | |
begin | |
exit_status = thr.value | |
rescue Interrupt | |
exit_status = 0 | |
end | |
end | |
end | |
logger.info "terminated; return code: #{exit_status.to_i}" | |
end until exit_status.to_i == 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment