Skip to content

Instantly share code, notes, and snippets.

@gruis
Created March 19, 2017 03:46
Show Gist options
  • Save gruis/4c57f676d0d69d05eff9ad4042a3dfe0 to your computer and use it in GitHub Desktop.
Save gruis/4c57f676d0d69d05eff9ad4042a3dfe0 to your computer and use it in GitHub Desktop.
Run localtunnel in a restart loop
#!/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