consign 0.1.1
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.
- checksums.yaml +7 -0
- data/lib/consign/railtie.rb +27 -0
- data/lib/consign/tunnel.rb +178 -0
- data/lib/consign/version.rb +3 -0
- data/lib/consign.rb +15 -0
- metadata +59 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8c62636b6885f9f8bff11cae0039659e05ce5808744d52fca7afee29d9616183
|
|
4
|
+
data.tar.gz: 73a23bf8e80a50b3d0f1e12d6323887a761aa5eb8ddb0d2d5ed73fbe93e9c562
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b690e81fe0be9e0dc41a213db28dc094f78dd5a209301b0aabae1c3ce65b37cc0c4b0c5a82b0a68e269fa7be4ca06a61821003a547e608f0e649ff01ac877392
|
|
7
|
+
data.tar.gz: 5b9f940ceb11132378cd6ded5840a32ce80f015d74019ebd0c6878117f54e15415c8d867b2197030bbfd37b81692ebee14963bd238dd247c841797025c55ea42
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Consign
|
|
2
|
+
class Railtie < ::Rails::Railtie
|
|
3
|
+
config.before_configuration do
|
|
4
|
+
Consign.api_key = ENV["CONSIGN_API_KEY"]
|
|
5
|
+
|
|
6
|
+
host = Consign.local_mode ? ".lvh.me" : ".consign.dev"
|
|
7
|
+
Rails.application.config.hosts << host
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
server do
|
|
11
|
+
if Rails.env.development? && Consign.api_key.present?
|
|
12
|
+
Thread.new { Consign::Tunnel.start(port: detect_port) }
|
|
13
|
+
else
|
|
14
|
+
puts "[Consign] Tunnel disabled. Set CONSIGN_API_KEY to enable." if Rails.env.development?
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
def self.detect_port
|
|
20
|
+
if (idx = ARGV.index("-p") || ARGV.index("--port"))
|
|
21
|
+
ARGV[idx + 1].to_i
|
|
22
|
+
else
|
|
23
|
+
ENV.fetch("PORT", 3000).to_i
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
require "socket"
|
|
2
|
+
require "net/http"
|
|
3
|
+
require "uri"
|
|
4
|
+
require "json"
|
|
5
|
+
require "openssl"
|
|
6
|
+
|
|
7
|
+
module Consign
|
|
8
|
+
class Tunnel
|
|
9
|
+
MAX_RETRIES = 10
|
|
10
|
+
RETRY_DELAY = 1
|
|
11
|
+
|
|
12
|
+
def self.start(port: nil)
|
|
13
|
+
new(port: port).start
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(port: nil)
|
|
17
|
+
@local_port = port || ENV.fetch("PORT", "3000").to_i
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def start
|
|
21
|
+
log "🔐 Authenticating with #{control_url}..."
|
|
22
|
+
|
|
23
|
+
if config = fetch_configuration
|
|
24
|
+
log "🔌 Connecting to tunnel server..."
|
|
25
|
+
run_tunnel_loop(config)
|
|
26
|
+
else
|
|
27
|
+
log "❌ Failed to authenticate after multiple attempts.", :error
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
def fetch_configuration
|
|
33
|
+
attempt_with_retries(max: MAX_RETRIES) do
|
|
34
|
+
response = post_authentication
|
|
35
|
+
|
|
36
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
37
|
+
JSON.parse(response.body)
|
|
38
|
+
else
|
|
39
|
+
raise "Authentication failed: #{response.code}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def run_tunnel_loop(config)
|
|
45
|
+
loop do
|
|
46
|
+
begin
|
|
47
|
+
connect_and_serve(config)
|
|
48
|
+
rescue Interrupt
|
|
49
|
+
break
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
log "⚠️ Tunnel disconnected: #{e.message}. Reconnecting...", :warn
|
|
52
|
+
sleep RETRY_DELAY
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def connect_and_serve(config)
|
|
58
|
+
socket = open_socket(config["server_host"], config["server_port"])
|
|
59
|
+
return unless perform_handshake(socket, config)
|
|
60
|
+
|
|
61
|
+
log_connected(config)
|
|
62
|
+
|
|
63
|
+
while line = socket.gets
|
|
64
|
+
handle_request(socket, JSON.parse(line))
|
|
65
|
+
end
|
|
66
|
+
ensure
|
|
67
|
+
socket&.close
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def open_socket(host, port)
|
|
71
|
+
socket = TCPSocket.new(host, port)
|
|
72
|
+
if port == 443
|
|
73
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
|
74
|
+
socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
|
|
75
|
+
socket.hostname = host
|
|
76
|
+
socket.connect
|
|
77
|
+
end
|
|
78
|
+
socket
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def perform_handshake(socket, config)
|
|
82
|
+
req = "GET /_connect HTTP/1.1\r\n" \
|
|
83
|
+
"Host: #{config['server_host']}\r\n" \
|
|
84
|
+
"Connection: Upgrade\r\n" \
|
|
85
|
+
"Upgrade: consign\r\n" \
|
|
86
|
+
"X-Consign-Subdomain: #{config['subdomain']}\r\n" \
|
|
87
|
+
"X-Consign-Token: #{config['otp']}\r\n" \
|
|
88
|
+
"\r\n"
|
|
89
|
+
socket.write(req)
|
|
90
|
+
|
|
91
|
+
read_handshake_response(socket)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def read_handshake_response(socket)
|
|
95
|
+
buffer = ""
|
|
96
|
+
while (line = socket.gets)
|
|
97
|
+
buffer += line
|
|
98
|
+
break if line == "\r\n"
|
|
99
|
+
end
|
|
100
|
+
buffer.start_with?("HTTP/1.1 101")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def handle_request(socket, req)
|
|
104
|
+
Thread.new do
|
|
105
|
+
begin
|
|
106
|
+
response = forward_to_local(req)
|
|
107
|
+
socket.puts({ id: req['id'], status: response.code.to_i, headers: response.to_hash, body: response.body }.to_json)
|
|
108
|
+
rescue StandardError => e
|
|
109
|
+
socket.puts({ id: req['id'], status: 502, headers: {}, body: "Bad Gateway: #{e.message}" }.to_json)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def forward_to_local(req)
|
|
115
|
+
uri = URI("http://127.0.0.1:#{@local_port}#{req['url']}")
|
|
116
|
+
|
|
117
|
+
Net::HTTP.start(uri.hostname, uri.port) do |http|
|
|
118
|
+
local_req = Net::HTTPGenericRequest.new(req['method'], true, true, req['url'])
|
|
119
|
+
req['headers']&.each { |k, v| local_req[k] = v.first }
|
|
120
|
+
|
|
121
|
+
if req['host']
|
|
122
|
+
local_req['Host'] = req['host']
|
|
123
|
+
local_req['X-Forwarded-Host'] = req['host']
|
|
124
|
+
local_req['X-Forwarded-Proto'] = "http" # or https if we were terminating TLS locally
|
|
125
|
+
local_req['X-Forwarded-Port'] = "80"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
local_req.body = req['body'] if req['body']
|
|
129
|
+
http.request(local_req)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def post_authentication
|
|
134
|
+
uri = URI("#{control_url}/api/v1/tunnels")
|
|
135
|
+
req = Net::HTTP::Post.new(uri)
|
|
136
|
+
req["Authorization"] = "Token token=\"#{Consign.api_key}\""
|
|
137
|
+
|
|
138
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
139
|
+
http.request(req)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def attempt_with_retries(max:)
|
|
144
|
+
attempts = 0
|
|
145
|
+
begin
|
|
146
|
+
yield
|
|
147
|
+
rescue StandardError => e
|
|
148
|
+
attempts += 1
|
|
149
|
+
if attempts < max
|
|
150
|
+
sleep RETRY_DELAY
|
|
151
|
+
retry
|
|
152
|
+
end
|
|
153
|
+
nil
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def control_url
|
|
158
|
+
ENV.fetch("CONSIGN_URL", default_host)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def default_host
|
|
162
|
+
Consign.local_mode ? "http://localhost:3000" : "https://www.consign.dev"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def log_connected(config)
|
|
166
|
+
display_host = config["server_host"]
|
|
167
|
+
display_host = "lvh.me" if display_host == "127.0.0.1"
|
|
168
|
+
display_host = display_host.sub(/^tunnel\./, "") if display_host.start_with?("tunnel.")
|
|
169
|
+
|
|
170
|
+
log "✅ Tunnel active: https://#{config['subdomain']}.#{display_host}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def log(msg, level = :info)
|
|
174
|
+
output = "[Consign] #{msg}"
|
|
175
|
+
defined?(Rails) ? Rails.logger.send(level, output) : puts(output)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
data/lib/consign.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require "consign/version"
|
|
2
|
+
require "consign/railtie"
|
|
3
|
+
require "consign/tunnel"
|
|
4
|
+
|
|
5
|
+
module Consign
|
|
6
|
+
mattr_accessor :api_key
|
|
7
|
+
mattr_accessor :local_mode
|
|
8
|
+
|
|
9
|
+
@@environment = :production
|
|
10
|
+
@@local_mode = false
|
|
11
|
+
|
|
12
|
+
def self.configure
|
|
13
|
+
yield self
|
|
14
|
+
end
|
|
15
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: consign
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Consign Team
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: railties
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.0'
|
|
26
|
+
description: Expose your local Rails server to the world with a public subdomain.
|
|
27
|
+
email:
|
|
28
|
+
- hello@consign.dev
|
|
29
|
+
executables: []
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- lib/consign.rb
|
|
34
|
+
- lib/consign/railtie.rb
|
|
35
|
+
- lib/consign/tunnel.rb
|
|
36
|
+
- lib/consign/version.rb
|
|
37
|
+
homepage: https://www.consign.dev
|
|
38
|
+
licenses:
|
|
39
|
+
- MIT
|
|
40
|
+
metadata:
|
|
41
|
+
homepage_uri: https://www.consign.dev
|
|
42
|
+
rdoc_options: []
|
|
43
|
+
require_paths:
|
|
44
|
+
- lib
|
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: 3.0.0
|
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
requirements: []
|
|
56
|
+
rubygems_version: 4.0.3
|
|
57
|
+
specification_version: 4
|
|
58
|
+
summary: Zero-config tunneling for Rails
|
|
59
|
+
test_files: []
|