hsume2-localtunnel 0.3.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Manifest +9 -0
- data/README.rdoc +60 -0
- data/Rakefile +17 -0
- data/bin/localtunnel +47 -0
- data/hsume2-localtunnel.gemspec +45 -0
- data/lib/localtunnel/net_ssh_gateway_patch.rb +39 -0
- data/lib/localtunnel/tunnel.rb +89 -0
- data/lib/localtunnel/tunnel_app.rb +15 -0
- data/lib/localtunnel.rb +2 -0
- data/server.py +106 -0
- metadata +164 -0
data/Manifest
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
= localtunnel -- instant public tunnel to your local web server
|
2
|
+
|
3
|
+
== Install
|
4
|
+
|
5
|
+
To get the dependencies if you don't have them, type:
|
6
|
+
|
7
|
+
sudo apt-get install ruby ruby1.8-dev rubygems1.8 libopenssl-ruby
|
8
|
+
|
9
|
+
Now you can install localtunnel with RubyGems:
|
10
|
+
|
11
|
+
sudo gem install localtunnel
|
12
|
+
|
13
|
+
or to get the source:
|
14
|
+
|
15
|
+
git clone http://github.com/progrium/localtunnel.git
|
16
|
+
|
17
|
+
== Usage
|
18
|
+
|
19
|
+
localtunnel [options] <localport>
|
20
|
+
-k, --key FILE upload a public key for authentication
|
21
|
+
|
22
|
+
Localtunnel is a client to a free and open source reverse tunneling
|
23
|
+
service made specifically for web traffic. It's intended to be used to
|
24
|
+
temporarily expose local web servers to the greater Internet for
|
25
|
+
debugging, unit tests, demos, etc.
|
26
|
+
|
27
|
+
This is how you make your local port 8080 public:
|
28
|
+
|
29
|
+
$ localtunnel 8080
|
30
|
+
Port 8080 is now publicly accessible from http://8bv2.localtunnel.com ...
|
31
|
+
|
32
|
+
Using localtunnel is comparable to using SSH reverse/remote port
|
33
|
+
forwarding on a remote host that has GatewayPorts enabled, but without
|
34
|
+
all the configuration or the need of a host. The localtunnel command
|
35
|
+
works with a server component that is running on localtunnel.com,
|
36
|
+
which is provided as a free service.
|
37
|
+
|
38
|
+
If you have never run localtunnel before, you'll need to upload a public
|
39
|
+
key to authenticate. You do this once:
|
40
|
+
|
41
|
+
$ localtunnel -k ~/.ssh/id_rsa.pub 8080
|
42
|
+
|
43
|
+
After that, you shouldn't have to use -k again.
|
44
|
+
|
45
|
+
Localtunnel can be started before or after the local web server. It
|
46
|
+
tunnels through to the url given in that status message "publicly
|
47
|
+
accessible from..." for as long as the command is running. The tunnel
|
48
|
+
is closed if the command exits.
|
49
|
+
|
50
|
+
== Contributors
|
51
|
+
|
52
|
+
andyl (andy@r210.com)
|
53
|
+
Charles Merriam (charles.merriam@gmail.com)
|
54
|
+
Hunter Gillane (hunter.gillane@gmail.com)
|
55
|
+
Michael Sofaer (msofaer@pivotallabs.com)
|
56
|
+
Jeff Lindsay (progrium@gmail.com)
|
57
|
+
|
58
|
+
== License
|
59
|
+
|
60
|
+
MIT
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('hsume2-localtunnel', '0.3.beta') do |p|
|
6
|
+
p.description = "instant public tunnel to your local web server"
|
7
|
+
p.url = "http://github.com/hsume2/localtunnel"
|
8
|
+
p.author = "Jeff Lindsay"
|
9
|
+
p.email = "jeff.lindsay@twilio.com"
|
10
|
+
p.has_rdoc = false
|
11
|
+
p.rdoc_pattern = //
|
12
|
+
p.rdoc_options = []
|
13
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
14
|
+
p.executable_pattern = ["bin/*"]
|
15
|
+
p.runtime_dependencies = ["json >=1.2.4", "net-ssh >=2.0.22", "net-ssh-gateway >=1.0.1", "sinatra >=1.3.2", "POpen4 >=0.1.4"]
|
16
|
+
p.development_dependencies = []
|
17
|
+
end
|
data/bin/localtunnel
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Copyright (c) 2010 Jeff Lindsay
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person
|
5
|
+
# obtaining a copy of this software and associated documentation
|
6
|
+
# files (the "Software"), to deal in the Software without
|
7
|
+
# restriction, including without limitation the rights to use,
|
8
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the
|
10
|
+
# Software is furnished to do so, subject to the following
|
11
|
+
# conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
18
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
20
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
21
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
22
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
23
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
|
25
|
+
require 'rubygems'
|
26
|
+
require 'optparse'
|
27
|
+
require 'localtunnel'
|
28
|
+
|
29
|
+
key = nil
|
30
|
+
options = OptionParser.new do |o|
|
31
|
+
o.banner = "Usage: localtunnel [options] <localport>"
|
32
|
+
o.on("-k", "--key FILE", "upload a public key for authentication") do |k|
|
33
|
+
key = File.exist?(k.to_s) ? File.open(k).read : nil
|
34
|
+
end
|
35
|
+
o.on('-h', "--help", "show this help") { puts o; exit }
|
36
|
+
end
|
37
|
+
|
38
|
+
args = options.parse!
|
39
|
+
local_port = args[0]
|
40
|
+
unless local_port.to_i.between?(1, 65535)
|
41
|
+
puts options
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
|
45
|
+
t = LocalTunnel::Tunnel.new(local_port, key)
|
46
|
+
t.register_tunnel
|
47
|
+
t.start_tunnel
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{hsume2-localtunnel}
|
5
|
+
s.version = "0.3.beta"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Jeff Lindsay"]
|
9
|
+
s.date = %q{2012-01-25}
|
10
|
+
s.default_executable = %q{localtunnel}
|
11
|
+
s.description = %q{instant public tunnel to your local web server}
|
12
|
+
s.email = %q{jeff.lindsay@twilio.com}
|
13
|
+
s.executables = ["localtunnel"]
|
14
|
+
s.extra_rdoc_files = ["README.rdoc", "Rakefile", "bin/localtunnel", "lib/localtunnel.rb", "lib/localtunnel/net_ssh_gateway_patch.rb", "lib/localtunnel/tunnel.rb", "lib/localtunnel/tunnel_app.rb", "server.py", "hsume2-localtunnel.gemspec"]
|
15
|
+
s.files = ["Manifest", "README.rdoc", "Rakefile", "bin/localtunnel", "lib/localtunnel.rb", "lib/localtunnel/net_ssh_gateway_patch.rb", "lib/localtunnel/tunnel.rb", "lib/localtunnel/tunnel_app.rb", "server.py", "hsume2-localtunnel.gemspec"]
|
16
|
+
s.homepage = %q{http://github.com/hsume2/localtunnel}
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{hsume2-localtunnel}
|
19
|
+
s.rubygems_version = %q{1.5.3}
|
20
|
+
s.summary = %q{instant public tunnel to your local web server}
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
s.specification_version = 3
|
24
|
+
|
25
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
26
|
+
s.add_runtime_dependency(%q<json>, [">= 1.2.4"])
|
27
|
+
s.add_runtime_dependency(%q<net-ssh>, [">= 2.0.22"])
|
28
|
+
s.add_runtime_dependency(%q<net-ssh-gateway>, [">= 1.0.1"])
|
29
|
+
s.add_runtime_dependency(%q<sinatra>, [">= 1.3.2"])
|
30
|
+
s.add_runtime_dependency(%q<POpen4>, [">= 0.1.4"])
|
31
|
+
else
|
32
|
+
s.add_dependency(%q<json>, [">= 1.2.4"])
|
33
|
+
s.add_dependency(%q<net-ssh>, [">= 2.0.22"])
|
34
|
+
s.add_dependency(%q<net-ssh-gateway>, [">= 1.0.1"])
|
35
|
+
s.add_dependency(%q<sinatra>, [">= 1.3.2"])
|
36
|
+
s.add_dependency(%q<POpen4>, [">= 0.1.4"])
|
37
|
+
end
|
38
|
+
else
|
39
|
+
s.add_dependency(%q<json>, [">= 1.2.4"])
|
40
|
+
s.add_dependency(%q<net-ssh>, [">= 2.0.22"])
|
41
|
+
s.add_dependency(%q<net-ssh-gateway>, [">= 1.0.1"])
|
42
|
+
s.add_dependency(%q<sinatra>, [">= 1.3.2"])
|
43
|
+
s.add_dependency(%q<POpen4>, [">= 0.1.4"])
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'net/ssh/gateway'
|
4
|
+
|
5
|
+
# http://groups.google.com/group/capistrano/browse_thread/thread/455c0c8a6faa9cc8?pli=1
|
6
|
+
class Net::SSH::Gateway
|
7
|
+
# Opens a SSH tunnel from a port on a remote host to a given host and port
|
8
|
+
# on the local side
|
9
|
+
# (equivalent to openssh -R parameter)
|
10
|
+
def open_remote(port, host, remote_port, remote_host = "127.0.0.1")
|
11
|
+
ensure_open!
|
12
|
+
|
13
|
+
@session_mutex.synchronize do
|
14
|
+
@session.forward.remote(port, host, remote_port, remote_host)
|
15
|
+
end
|
16
|
+
|
17
|
+
if block_given?
|
18
|
+
begin
|
19
|
+
yield [remote_port, remote_host]
|
20
|
+
ensure
|
21
|
+
close_remote(remote_port, remote_host)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
return [remote_port, remote_host]
|
25
|
+
end
|
26
|
+
rescue Errno::EADDRINUSE
|
27
|
+
retry
|
28
|
+
end
|
29
|
+
|
30
|
+
# Cancels port-forwarding over an open port that was previously opened via
|
31
|
+
# #open_remote.
|
32
|
+
def close_remote(port, host = "127.0.0.1")
|
33
|
+
ensure_open!
|
34
|
+
|
35
|
+
@session_mutex.synchronize do
|
36
|
+
@session.forward.cancel_remote(port, host)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'net/ssh/gateway'
|
4
|
+
require 'net/http'
|
5
|
+
require 'uri'
|
6
|
+
require 'json'
|
7
|
+
require 'open4'
|
8
|
+
|
9
|
+
require 'localtunnel/net_ssh_gateway_patch'
|
10
|
+
|
11
|
+
module LocalTunnel; end
|
12
|
+
|
13
|
+
require 'localtunnel/tunnel_app'
|
14
|
+
|
15
|
+
class LocalTunnel::Tunnel
|
16
|
+
|
17
|
+
SHELL_HOOK_FILE = "./.localtunnel_callback"
|
18
|
+
|
19
|
+
attr_accessor :port, :key, :host
|
20
|
+
|
21
|
+
def initialize(port, key)
|
22
|
+
@port = port
|
23
|
+
@key = key
|
24
|
+
@host = ""
|
25
|
+
end
|
26
|
+
|
27
|
+
def register_tunnel(key=@key)
|
28
|
+
url = URI.parse("http://open.localtunnel.com/")
|
29
|
+
if key
|
30
|
+
resp = JSON.parse(Net::HTTP.post_form(url, {"key" => key}).body)
|
31
|
+
else
|
32
|
+
resp = JSON.parse(Net::HTTP.get(url))
|
33
|
+
end
|
34
|
+
if resp.has_key? 'error'
|
35
|
+
puts " [Error] #{resp['error']}"
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
@host = resp['host'].split(':').first
|
39
|
+
@tunnel = resp
|
40
|
+
return resp
|
41
|
+
rescue
|
42
|
+
puts " [Error] Unable to register tunnel. Perhaps service is down?"
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
|
46
|
+
def start_tunnel
|
47
|
+
port = @port
|
48
|
+
tunnel = @tunnel
|
49
|
+
|
50
|
+
TunnelApp.tunnel = tunnel['host']
|
51
|
+
tunnel_pid = fork { TunnelApp.run! }
|
52
|
+
|
53
|
+
at_exit do
|
54
|
+
Process.kill('KILL', tunnel_pid)
|
55
|
+
Process.waitpid(tunnel_pid)
|
56
|
+
end
|
57
|
+
|
58
|
+
error = ''
|
59
|
+
|
60
|
+
status = Open4::popen4("ssh -nNT -g -R *:#{tunnel['through_port']}:0.0.0.0:#{port} #{tunnel['user']}@#{@host} -o PasswordAuthentication=no") do |pid, i, o, e|
|
61
|
+
puts " " << tunnel['banner'] if tunnel.has_key? 'banner'
|
62
|
+
if File.exists?(File.expand_path(SHELL_HOOK_FILE))
|
63
|
+
system "#{SHELL_HOOK_FILE} ""#{tunnel['host']}""" if File.exists?(File.expand_path(SHELL_HOOK_FILE))
|
64
|
+
if !$?.success?
|
65
|
+
puts " An error occurred executing the callback hook #{SHELL_HOOK_FILE}"
|
66
|
+
puts " (Make sure it is executable)"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
puts " Port #{port} is now publicly accessible from http://#{tunnel['host']} ..."
|
70
|
+
|
71
|
+
begin
|
72
|
+
while str = e.gets
|
73
|
+
error += str
|
74
|
+
puts str
|
75
|
+
end
|
76
|
+
rescue Interrupt
|
77
|
+
exit
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
if error =~ /Permission denied/
|
82
|
+
possible_key = Dir[File.expand_path('~/.ssh/*.pub')].first
|
83
|
+
puts " Failed to authenticate. If this is your first tunnel, you need to"
|
84
|
+
puts " upload a public key using the -k option. Try this:\n\n"
|
85
|
+
puts " localtunnel -k #{possible_key ? possible_key : '~/path/to/key'} #{port}"
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/localtunnel.rb
ADDED
data/server.py
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
try:
|
2
|
+
from twisted.internet import pollreactor
|
3
|
+
pollreactor.install()
|
4
|
+
except: pass
|
5
|
+
from twisted.internet import protocol, reactor, defer, task
|
6
|
+
from twisted.web import http, proxy, resource, server
|
7
|
+
from twisted.python import log
|
8
|
+
import sys, time
|
9
|
+
import urlparse
|
10
|
+
import socket
|
11
|
+
import simplejson
|
12
|
+
import re
|
13
|
+
|
14
|
+
SSH_USER = 'localtunnel'
|
15
|
+
AUTHORIZED_KEYS = '/home/localtunnel/.ssh/authorized_keys'
|
16
|
+
PORT_RANGE = [32000, 64000]
|
17
|
+
BANNER = "This localtunnel service is brought to you by Twilio."
|
18
|
+
SSH_OPTIONS = 'command="/bin/echo Shell access denied",no-agent-forwarding,no-pty,no-user-rc,no-X11-forwarding '
|
19
|
+
KEY_REGEX = re.compile(r'^ssh-(\w{3}) [^\n]+$')
|
20
|
+
|
21
|
+
def port_available(port):
|
22
|
+
try:
|
23
|
+
socket.create_connection(['127.0.0.1', port]).close()
|
24
|
+
return False
|
25
|
+
except socket.error:
|
26
|
+
return True
|
27
|
+
|
28
|
+
def baseN(num,b=32,numerals="23456789abcdefghijkmnpqrstuvwxyz"):
|
29
|
+
return ((num == 0) and "0" ) or (baseN(num // b, b).lstrip("0") + numerals[num % b])
|
30
|
+
|
31
|
+
class LocalTunnelReverseProxy(proxy.ReverseProxyResource):
|
32
|
+
isLeaf = True
|
33
|
+
|
34
|
+
def __init__(self, user, host='127.0.0.1'):
|
35
|
+
self.user = user
|
36
|
+
self.tunnels = {}
|
37
|
+
proxy.ReverseProxyResource.__init__(self, host, None, None)
|
38
|
+
|
39
|
+
def find_tunnel_name(self):
|
40
|
+
name = baseN(abs(hash(time.time())))[0:4]
|
41
|
+
if (name in self.tunnels and not port_available(self.tunnels[name])) or name == 'open':
|
42
|
+
time.sleep(0.1)
|
43
|
+
return self.find_tunnel_name()
|
44
|
+
return name
|
45
|
+
|
46
|
+
def find_tunnel_port(self):
|
47
|
+
port = PORT_RANGE[0]
|
48
|
+
start_time = time.time()
|
49
|
+
while not port_available(port):
|
50
|
+
if time.time()-start_time > 3:
|
51
|
+
raise Exception("No port available")
|
52
|
+
port += 1
|
53
|
+
if port >= PORT_RANGE[1]: port = PORT_RANGE[0]
|
54
|
+
return port
|
55
|
+
|
56
|
+
def garbage_collect(self):
|
57
|
+
for name in self.tunnels:
|
58
|
+
if port_available(self.tunnels[name]):
|
59
|
+
del self.tunnels[name]
|
60
|
+
|
61
|
+
def install_key(self, key):
|
62
|
+
if not KEY_REGEX.match(key.strip()):
|
63
|
+
return False
|
64
|
+
key = ''.join([SSH_OPTIONS, key.strip(), "\n"])
|
65
|
+
fr = open(AUTHORIZED_KEYS, 'r')
|
66
|
+
if not key in fr.readlines():
|
67
|
+
fa = open(AUTHORIZED_KEYS, 'a')
|
68
|
+
fa.write(key)
|
69
|
+
fa.close()
|
70
|
+
fr.close()
|
71
|
+
return True
|
72
|
+
|
73
|
+
def register_tunnel(self, superhost, key=None):
|
74
|
+
if key and not self.install_key(key): return simplejson.dumps(dict(error="Invalid key."))
|
75
|
+
name = self.find_tunnel_name()
|
76
|
+
port = self.find_tunnel_port()
|
77
|
+
self.tunnels[name] = port
|
78
|
+
return simplejson.dumps(
|
79
|
+
dict(through_port=port, user=self.user, host='%s.%s' % (name, superhost), banner=BANNER))
|
80
|
+
|
81
|
+
def render(self, request):
|
82
|
+
host = request.getHeader('host')
|
83
|
+
name, superhost = host.split('.', 1)
|
84
|
+
if host.startswith('open.'):
|
85
|
+
request.setHeader('Content-Type', 'application/json')
|
86
|
+
return self.register_tunnel(superhost, request.args.get('key', [None])[0])
|
87
|
+
else:
|
88
|
+
if not name in self.tunnels: return "Not found"
|
89
|
+
|
90
|
+
request.content.seek(0, 0)
|
91
|
+
clientFactory = self.proxyClientFactoryClass(
|
92
|
+
request.method, request.uri, request.clientproto,
|
93
|
+
request.getAllHeaders(), request.content.read(), request)
|
94
|
+
self.reactor.connectTCP(self.host, self.tunnels[name], clientFactory)
|
95
|
+
return server.NOT_DONE_YET
|
96
|
+
|
97
|
+
#if 'location' in request.responseHeaders and host in request.responseHeaders['location']:
|
98
|
+
# # Strip out the port they think they need
|
99
|
+
# p = re.compile(r'%s\:\d+' % host)
|
100
|
+
# location = p.sub(host, request.responseHeaders['location'])
|
101
|
+
# request.responseHeaders['location'] = location
|
102
|
+
|
103
|
+
|
104
|
+
log.startLogging(sys.stdout)
|
105
|
+
reactor.listenTCP(80, server.Site(LocalTunnelReverseProxy(SSH_USER)))
|
106
|
+
reactor.run()
|
metadata
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hsume2-localtunnel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31098141
|
5
|
+
prerelease: 4
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 3
|
9
|
+
- beta
|
10
|
+
version: 0.3.beta
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Jeff Lindsay
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-01-25 00:00:00 -08:00
|
19
|
+
default_executable: localtunnel
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: json
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 2
|
33
|
+
- 4
|
34
|
+
version: 1.2.4
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: net-ssh
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 35
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 0
|
49
|
+
- 22
|
50
|
+
version: 2.0.22
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: net-ssh-gateway
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 21
|
62
|
+
segments:
|
63
|
+
- 1
|
64
|
+
- 0
|
65
|
+
- 1
|
66
|
+
version: 1.0.1
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sinatra
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 31
|
78
|
+
segments:
|
79
|
+
- 1
|
80
|
+
- 3
|
81
|
+
- 2
|
82
|
+
version: 1.3.2
|
83
|
+
type: :runtime
|
84
|
+
version_requirements: *id004
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: POpen4
|
87
|
+
prerelease: false
|
88
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 19
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
- 1
|
97
|
+
- 4
|
98
|
+
version: 0.1.4
|
99
|
+
type: :runtime
|
100
|
+
version_requirements: *id005
|
101
|
+
description: instant public tunnel to your local web server
|
102
|
+
email: jeff.lindsay@twilio.com
|
103
|
+
executables:
|
104
|
+
- localtunnel
|
105
|
+
extensions: []
|
106
|
+
|
107
|
+
extra_rdoc_files:
|
108
|
+
- README.rdoc
|
109
|
+
- Rakefile
|
110
|
+
- bin/localtunnel
|
111
|
+
- lib/localtunnel.rb
|
112
|
+
- lib/localtunnel/net_ssh_gateway_patch.rb
|
113
|
+
- lib/localtunnel/tunnel.rb
|
114
|
+
- lib/localtunnel/tunnel_app.rb
|
115
|
+
- server.py
|
116
|
+
- hsume2-localtunnel.gemspec
|
117
|
+
files:
|
118
|
+
- Manifest
|
119
|
+
- README.rdoc
|
120
|
+
- Rakefile
|
121
|
+
- bin/localtunnel
|
122
|
+
- lib/localtunnel.rb
|
123
|
+
- lib/localtunnel/net_ssh_gateway_patch.rb
|
124
|
+
- lib/localtunnel/tunnel.rb
|
125
|
+
- lib/localtunnel/tunnel_app.rb
|
126
|
+
- server.py
|
127
|
+
- hsume2-localtunnel.gemspec
|
128
|
+
has_rdoc: true
|
129
|
+
homepage: http://github.com/hsume2/localtunnel
|
130
|
+
licenses: []
|
131
|
+
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options: []
|
134
|
+
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
hash: 3
|
143
|
+
segments:
|
144
|
+
- 0
|
145
|
+
version: "0"
|
146
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
|
+
none: false
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
hash: 11
|
152
|
+
segments:
|
153
|
+
- 1
|
154
|
+
- 2
|
155
|
+
version: "1.2"
|
156
|
+
requirements: []
|
157
|
+
|
158
|
+
rubyforge_project: hsume2-localtunnel
|
159
|
+
rubygems_version: 1.5.3
|
160
|
+
signing_key:
|
161
|
+
specification_version: 3
|
162
|
+
summary: instant public tunnel to your local web server
|
163
|
+
test_files: []
|
164
|
+
|