rb-portless 0.3.0.dev.20260630.053bdf1 → 0.3.0.dev.20260630.68c9c34
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 +4 -4
- data/CHANGELOG.md +2 -19
- data/lib/portless/cli.rb +5 -0
- data/lib/portless/config.rb +4 -12
- data/lib/portless/health.rb +2 -4
- data/lib/portless/hosts.rb +5 -0
- data/lib/portless/multi.rb +23 -3
- data/lib/portless/proxy.rb +7 -10
- data/lib/portless/runner.rb +36 -4
- data/lib/portless/share/ngrok.rb +2 -7
- data/lib/portless/share/tailscale.rb +12 -46
- data/lib/portless/state.rb +1 -0
- data/lib/portless/version.rb +1 -1
- data/lib/rb-portless.rb +0 -1
- metadata +1 -2
- data/lib/portless/run_support.rb +0 -44
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 749216011c6c43a8048dcbfaed840ea189a5fd62a99b2744d7e11ac482ba6f9f
|
|
4
|
+
data.tar.gz: c5f29b73ecb5ddcfe726b8db6cbf95863b9da45bd41e29dae51e98eb56cc91e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5cc9a46f38311264470ceb33b83406200f52b7aa693597309ec0d77d835beea88b30325a64cff7a47d7c9ab7cf3041109371f8e1b04115f2810850c623f76725
|
|
7
|
+
data.tar.gz: d5183f6f9d86daff7023528a93cc9a150a7732c3d5977a96b63134a7eac6e0ddb391fb4deee4952683adaf86c84bde678493afe65d78aa88f6953f87f0bd49fb
|
data/CHANGELOG.md
CHANGED
|
@@ -6,23 +6,8 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [0.3.0]
|
|
8
8
|
|
|
9
|
-
### Fixed / hardened
|
|
10
|
-
|
|
11
|
-
- **Health probes can't hang.** Added a read timeout to the TLS and plain probes
|
|
12
|
-
so a port that accepts but never answers no longer blocks `discover_port`.
|
|
13
|
-
|
|
14
9
|
### Added
|
|
15
10
|
|
|
16
|
-
- **Risky-TLD warning.** Warn when the configured `tld` ends in a real/reserved
|
|
17
|
-
TLD (`dev`, `app`, `local`, …) that could intercept live traffic.
|
|
18
|
-
- **More tests** — `Proxy#call` is now public, so the proxy's routing + error
|
|
19
|
-
logic is unit-tested (404 / 508 loop guard / 502 dead-backend, all stamped with
|
|
20
|
-
the health header) plus health probes and privilege logic (42 tests). The
|
|
21
|
-
successful byte-forward + **WebSocket upgrade relay** (ActionCable) need a live
|
|
22
|
-
reactor and are verified end-to-end manually — async-http servers can't be torn
|
|
23
|
-
down in-process without deadlock.
|
|
24
|
-
|
|
25
|
-
|
|
26
11
|
- **Startup banner.** Running a dev server through rb-portless now prints a clear
|
|
27
12
|
banner with the named URL(s) it's reachable at — not just `127.0.0.1:port`.
|
|
28
13
|
- **Monorepo / multi-app.** A `portless.json` `apps` map runs several apps under
|
|
@@ -31,10 +16,8 @@ All notable changes to this project are documented here. The format follows
|
|
|
31
16
|
detects the LAN IP, registers `<name>.local`, and publishes it over mDNS
|
|
32
17
|
(`dns-sd`/`avahi-publish`). `--ip` overrides the detected address.
|
|
33
18
|
- **Public sharing (experimental).** `--ngrok`, `--tailscale`, `--funnel` expose
|
|
34
|
-
the app via ngrok / your tailnet (their CLIs, installed separately).
|
|
35
|
-
tool is
|
|
36
|
-
`ngrok config add-authtoken`, "enable HTTPS in your tailnet DNS settings",
|
|
37
|
-
"run `tailscale up`") rather than failing silently — mirroring portless. Tailscale is **non-destructive**: it reads
|
|
19
|
+
the app via ngrok / your tailnet (their CLIs, installed separately). Degrade
|
|
20
|
+
gracefully when the tool is absent. Tailscale is **non-destructive**: it reads
|
|
38
21
|
`tailscale serve status`, picks a free HTTPS port (never clobbering your
|
|
39
22
|
existing serve/funnel config), registers with `--yes`, and removes only the
|
|
40
23
|
port it created on exit — mirroring portless's port-conflict handling.
|
data/lib/portless/cli.rb
CHANGED
|
@@ -205,6 +205,11 @@ module Portless
|
|
|
205
205
|
i ? Integer(args[i + 1], exception: false) : nil
|
|
206
206
|
end
|
|
207
207
|
|
|
208
|
+
def todo(name, desc, _args = nil)
|
|
209
|
+
warn "rb-portless #{name}: #{desc} — not yet implemented (#{Portless::VERSION})"
|
|
210
|
+
exit 1
|
|
211
|
+
end
|
|
212
|
+
|
|
208
213
|
def rest(command)
|
|
209
214
|
@argv.first == command ? @argv[1..] : @argv
|
|
210
215
|
end
|
data/lib/portless/config.rb
CHANGED
|
@@ -7,7 +7,9 @@ module Portless
|
|
|
7
7
|
# defaults. Mirrors portless's config.ts/auto.ts: name is inferred from the
|
|
8
8
|
# config, the git root, or the directory; tld defaults to "localhost".
|
|
9
9
|
class Config
|
|
10
|
-
|
|
10
|
+
DEFAULT_SCRIPT = "dev"
|
|
11
|
+
|
|
12
|
+
attr_reader :name, :tld, :script, :app_port, :tls, :apps
|
|
11
13
|
|
|
12
14
|
def self.load(dir = Dir.pwd)
|
|
13
15
|
new(read_file(dir), dir)
|
|
@@ -17,6 +19,7 @@ module Portless
|
|
|
17
19
|
@dir = dir
|
|
18
20
|
@name = sanitize_label(data["name"] || infer_name(dir))
|
|
19
21
|
@tld = (data["tld"] || Constants::DEFAULT_TLD).to_s
|
|
22
|
+
@script = data["script"] || DEFAULT_SCRIPT
|
|
20
23
|
@app_port = data["appPort"] || data["app_port"]
|
|
21
24
|
@tls = data.fetch("tls", true)
|
|
22
25
|
# Monorepo: { "apps": { "web": "bin/rails server", "api": "node api.js" } }.
|
|
@@ -29,17 +32,6 @@ module Portless
|
|
|
29
32
|
tld.split(".").include?(name) ? tld : "#{name}.#{tld}"
|
|
30
33
|
end
|
|
31
34
|
|
|
32
|
-
# Real/reserved TLDs that can intercept live traffic or clash with mDNS.
|
|
33
|
-
RISKY_TLDS = %w[dev app page zip mov local].freeze
|
|
34
|
-
|
|
35
|
-
# A warning string if the tld looks risky, else nil. (.localhost / .test are safe.)
|
|
36
|
-
def tld_warning
|
|
37
|
-
last = tld.split(".").last
|
|
38
|
-
return unless RISKY_TLDS.include?(last)
|
|
39
|
-
|
|
40
|
-
"tld \".#{last}\" is a real/reserved TLD — prefer \".localhost\" so you don't intercept real traffic"
|
|
41
|
-
end
|
|
42
|
-
|
|
43
35
|
def self.read_file(dir)
|
|
44
36
|
json = File.join(dir, "portless.json")
|
|
45
37
|
return JSON.parse(File.read(json)) if File.exist?(json)
|
data/lib/portless/health.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "socket"
|
|
4
4
|
require "openssl"
|
|
5
|
-
require "timeout"
|
|
6
5
|
|
|
7
6
|
module Portless
|
|
8
7
|
# "Is *our* proxy on this port?" — every proxied response carries the
|
|
@@ -27,8 +26,7 @@ module Portless
|
|
|
27
26
|
ssl.sync_close = true
|
|
28
27
|
ssl.connect
|
|
29
28
|
ssl.write(REQUEST)
|
|
30
|
-
|
|
31
|
-
marker?(Timeout.timeout(timeout) { ssl.read(4096) })
|
|
29
|
+
marker?(ssl.read(4096))
|
|
32
30
|
rescue StandardError
|
|
33
31
|
false
|
|
34
32
|
ensure
|
|
@@ -40,7 +38,7 @@ module Portless
|
|
|
40
38
|
Socket.tcp("127.0.0.1", port, connect_timeout: timeout) do |sock|
|
|
41
39
|
sock.write(REQUEST)
|
|
42
40
|
sock.close_write
|
|
43
|
-
marker?(
|
|
41
|
+
marker?(sock.read(4096))
|
|
44
42
|
end
|
|
45
43
|
rescue StandardError
|
|
46
44
|
false
|
data/lib/portless/hosts.rb
CHANGED
|
@@ -24,6 +24,11 @@ module Portless
|
|
|
24
24
|
write(strip_block(read))
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
def managed_hostnames
|
|
28
|
+
read[/#{Regexp.escape(Constants::HOSTS_BEGIN)}\n(.*?)#{Regexp.escape(Constants::HOSTS_END)}/m, 1]
|
|
29
|
+
.to_s.scan(/^\s*127\.0\.0\.1\s+(\S+)/).flatten
|
|
30
|
+
end
|
|
31
|
+
|
|
27
32
|
def build_block(hostnames)
|
|
28
33
|
return "" if hostnames.empty?
|
|
29
34
|
lines = hostnames.uniq.map { |h| "127.0.0.1\t#{h}" }
|
data/lib/portless/multi.rb
CHANGED
|
@@ -7,8 +7,6 @@ module Portless
|
|
|
7
7
|
# supervised + torn down together. Ruby sets env per-spawn, so there's no
|
|
8
8
|
# NODE_OPTIONS loader hack (cf. portless's turbo.ts).
|
|
9
9
|
class Multi
|
|
10
|
-
include RunSupport
|
|
11
|
-
|
|
12
10
|
App = Struct.new(:name, :hostname, :port, :url, :pid, keyword_init: true)
|
|
13
11
|
|
|
14
12
|
def initialize(config: Config.load, route_store: RouteStore.new)
|
|
@@ -20,7 +18,7 @@ module Portless
|
|
|
20
18
|
def run
|
|
21
19
|
raise Error, "no apps defined — add an \"apps\" map to portless.json" if @config.apps.empty?
|
|
22
20
|
|
|
23
|
-
ensure_trusted
|
|
21
|
+
ensure_trusted if @config.tls
|
|
24
22
|
proxy_port = Daemon.ensure_running(tls: @config.tls)
|
|
25
23
|
@apps = @config.apps.map { |name, command| start_app(name, command, proxy_port) }
|
|
26
24
|
|
|
@@ -59,5 +57,27 @@ module Portless
|
|
|
59
57
|
@apps.each { |app| @route_store.remove(app.hostname, owner_pid: Process.pid) }
|
|
60
58
|
kill_all("TERM")
|
|
61
59
|
end
|
|
60
|
+
|
|
61
|
+
def child_env(port, url)
|
|
62
|
+
{
|
|
63
|
+
"PORT" => port.to_s,
|
|
64
|
+
"HOST" => "127.0.0.1",
|
|
65
|
+
"PORTLESS_URL" => url,
|
|
66
|
+
"SSL_CERT_FILE" => (File.exist?(State.ca_cert) ? State.ca_cert : nil)
|
|
67
|
+
}.compact
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def display_url(hostname, proxy_port)
|
|
71
|
+
scheme = @config.tls ? "https" : "http"
|
|
72
|
+
default = @config.tls ? Constants::HTTPS_PORT : Constants::HTTP_PORT
|
|
73
|
+
suffix = proxy_port && proxy_port != default ? ":#{proxy_port}" : ""
|
|
74
|
+
"#{scheme}://#{hostname}#{suffix}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def ensure_trusted
|
|
78
|
+
Trust.install! if Privilege.interactive? && !Trust.trusted?
|
|
79
|
+
rescue Portless::Error
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
62
82
|
end
|
|
63
83
|
end
|
data/lib/portless/proxy.rb
CHANGED
|
@@ -51,10 +51,13 @@ module Portless
|
|
|
51
51
|
routes.find { |r| host.end_with?(".#{r.hostname}") }
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def make_server(endpoint)
|
|
57
|
+
Async::HTTP::Server.for(endpoint) { |request| handle(request) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def handle(request)
|
|
58
61
|
host = request_host(request)
|
|
59
62
|
route = route_for(host)
|
|
60
63
|
return error(404, "No app is registered for #{host}.") unless route
|
|
@@ -69,12 +72,6 @@ module Portless
|
|
|
69
72
|
error(502, "Backend for #{host} is not responding (#{e.class}).")
|
|
70
73
|
end
|
|
71
74
|
|
|
72
|
-
private
|
|
73
|
-
|
|
74
|
-
def make_server(endpoint)
|
|
75
|
-
Async::HTTP::Server.for(endpoint) { |request| call(request) }
|
|
76
|
-
end
|
|
77
|
-
|
|
78
75
|
def build_forward(request, host, hops)
|
|
79
76
|
headers = Protocol::HTTP::Headers.new
|
|
80
77
|
request.headers.each do |key, value|
|
data/lib/portless/runner.rb
CHANGED
|
@@ -6,8 +6,6 @@ module Portless
|
|
|
6
6
|
# group (forwarding signals), and deregister on exit. Rails/puma respect PORT
|
|
7
7
|
# natively. Mirrors portless's runApp/spawnCommand.
|
|
8
8
|
class Runner
|
|
9
|
-
include RunSupport
|
|
10
|
-
|
|
11
9
|
def initialize(command:, config: Config.load, route_store: RouteStore.new, options: {})
|
|
12
10
|
@command = Array(command)
|
|
13
11
|
@config = config
|
|
@@ -23,8 +21,7 @@ module Portless
|
|
|
23
21
|
command = Frameworks.inject(command, port) # --port/--host for vite/astro/etc.
|
|
24
22
|
hostname = @config.hostname
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
ensure_trusted
|
|
24
|
+
ensure_trusted if @config.tls
|
|
28
25
|
proxy_port = Daemon.ensure_running(tls: @config.tls)
|
|
29
26
|
@route_store.add(hostname: hostname, port: port, pid: Process.pid, force: true)
|
|
30
27
|
|
|
@@ -43,6 +40,13 @@ module Portless
|
|
|
43
40
|
|
|
44
41
|
private
|
|
45
42
|
|
|
43
|
+
def display_url(hostname, proxy_port)
|
|
44
|
+
scheme = @config.tls ? "https" : "http"
|
|
45
|
+
default = @config.tls ? Constants::HTTPS_PORT : Constants::HTTP_PORT
|
|
46
|
+
suffix = proxy_port && proxy_port != default ? ":#{proxy_port}" : ""
|
|
47
|
+
"#{scheme}://#{hostname}#{suffix}"
|
|
48
|
+
end
|
|
49
|
+
|
|
46
50
|
# ── LAN mode (--lan) ──────────────────────────────────────────────────
|
|
47
51
|
# Register a `<name>.local` route, publish it over mDNS, and surface the URL
|
|
48
52
|
# so phones/tablets on the Wi-Fi can reach the app.
|
|
@@ -79,6 +83,24 @@ module Portless
|
|
|
79
83
|
Share::Tailscale.stop(@tailscale) if @tailscale
|
|
80
84
|
end
|
|
81
85
|
|
|
86
|
+
# Trust the local CA on first run (like portless), so HTTPS works without
|
|
87
|
+
# browser warnings out of the box. Interactive only — macOS prompts for
|
|
88
|
+
# keychain auth; in CI/no-TTY we skip with a hint rather than hang. Never
|
|
89
|
+
# blocks the run if trusting fails.
|
|
90
|
+
def ensure_trusted
|
|
91
|
+
return if Trust.trusted?
|
|
92
|
+
|
|
93
|
+
unless Privilege.interactive?
|
|
94
|
+
warn "rb-portless: CA not trusted — run `rb-portless trust` (HTTPS shows warnings until then)"
|
|
95
|
+
return
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
warn "rb-portless: trusting the local CA (first run)…"
|
|
99
|
+
Trust.install!
|
|
100
|
+
rescue Portless::Error => e
|
|
101
|
+
warn "rb-portless: couldn't auto-trust the CA (#{e.message}) — run `rb-portless trust`"
|
|
102
|
+
end
|
|
103
|
+
|
|
82
104
|
# Run the child in its own process group so we can signal the whole tree,
|
|
83
105
|
# forwarding INT/TERM and propagating its exit status.
|
|
84
106
|
def supervise(command, port, url)
|
|
@@ -100,6 +122,16 @@ module Portless
|
|
|
100
122
|
nil
|
|
101
123
|
end
|
|
102
124
|
|
|
125
|
+
def child_env(port, url)
|
|
126
|
+
{
|
|
127
|
+
"PORT" => port.to_s,
|
|
128
|
+
"HOST" => "127.0.0.1",
|
|
129
|
+
"PORTLESS_URL" => url,
|
|
130
|
+
# Let the app's own server-side TLS verification trust our CA.
|
|
131
|
+
"SSL_CERT_FILE" => (File.exist?(State.ca_cert) ? State.ca_cert : nil)
|
|
132
|
+
}.compact
|
|
133
|
+
end
|
|
134
|
+
|
|
103
135
|
# Explicit command wins; bare `rb-portless` falls back to the project's dev
|
|
104
136
|
# runner (bin/dev, then bin/rails server).
|
|
105
137
|
def resolved_command
|
data/lib/portless/share/ngrok.rb
CHANGED
|
@@ -16,10 +16,7 @@ module Portless
|
|
|
16
16
|
API = "http://127.0.0.1:4040/api/tunnels"
|
|
17
17
|
|
|
18
18
|
def start(hostname:, backend_port:)
|
|
19
|
-
unless Portless.which("ngrok")
|
|
20
|
-
warn "rb-portless: ngrok not found — install it (https://ngrok.com/download) to use --ngrok"
|
|
21
|
-
return nil
|
|
22
|
-
end
|
|
19
|
+
return nil unless Portless.which("ngrok")
|
|
23
20
|
|
|
24
21
|
pid = Process.spawn("ngrok", "http", backend_port.to_s, "--host-header=#{hostname}",
|
|
25
22
|
out: File::NULL, err: File::NULL)
|
|
@@ -29,11 +26,9 @@ module Portless
|
|
|
29
26
|
{ pid: pid, url: url }
|
|
30
27
|
else
|
|
31
28
|
stop(pid: pid)
|
|
32
|
-
warn "rb-portless: ngrok didn't produce a public URL — is your authtoken set? (`ngrok config add-authtoken <token>`)"
|
|
33
29
|
nil
|
|
34
30
|
end
|
|
35
|
-
rescue StandardError
|
|
36
|
-
warn "rb-portless: ngrok failed (#{e.message})"
|
|
31
|
+
rescue StandardError
|
|
37
32
|
nil
|
|
38
33
|
end
|
|
39
34
|
|
|
@@ -18,44 +18,21 @@ module Portless
|
|
|
18
18
|
FUNNEL_PORTS = [ 443, 8443, 10_000 ].freeze # Funnel supports only these
|
|
19
19
|
|
|
20
20
|
def start(backend_port:, funnel: false)
|
|
21
|
-
unless Portless.which("tailscale")
|
|
22
|
-
warn "rb-portless: tailscale not found — install it (https://tailscale.com/download) to use --tailscale"
|
|
23
|
-
return nil
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
status = status_json
|
|
27
|
-
unless status
|
|
28
|
-
warn "rb-portless: tailscale isn't connected — run `tailscale up`, then retry"
|
|
29
|
-
return nil
|
|
30
|
-
end
|
|
31
|
-
unless capability?(status, "https")
|
|
32
|
-
warn "rb-portless: tailscale HTTPS certs aren't enabled — turn on HTTPS in your tailnet DNS settings"
|
|
33
|
-
return nil
|
|
34
|
-
end
|
|
35
|
-
if funnel && !capability?(status, "funnel")
|
|
36
|
-
warn "rb-portless: tailscale Funnel isn't enabled for this node — enable it, then retry --funnel"
|
|
37
|
-
return nil
|
|
38
|
-
end
|
|
21
|
+
return nil unless Portless.which("tailscale")
|
|
39
22
|
|
|
40
23
|
mode = funnel ? "funnel" : "serve"
|
|
41
24
|
port = available_port(funnel: funnel)
|
|
42
|
-
unless port
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
warn "rb-portless: `tailscale #{mode}` failed to register"
|
|
50
|
-
return nil
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
base = dns_name(status)
|
|
25
|
+
return nil unless port # all preferred ports already in use → don't fight it
|
|
26
|
+
|
|
27
|
+
ok = system("tailscale", mode, "--bg", "--yes", "--https=#{port}",
|
|
28
|
+
"http://127.0.0.1:#{backend_port}", out: File::NULL, err: File::NULL)
|
|
29
|
+
return nil unless ok
|
|
30
|
+
|
|
31
|
+
base = magic_dns_url
|
|
54
32
|
return (off(mode, port) and nil) unless base
|
|
55
33
|
|
|
56
34
|
{ mode: mode, port: port, url: format_url(base, port) }
|
|
57
|
-
rescue StandardError
|
|
58
|
-
warn "rb-portless: tailscale sharing failed (#{e.message})"
|
|
35
|
+
rescue StandardError
|
|
59
36
|
nil
|
|
60
37
|
end
|
|
61
38
|
|
|
@@ -97,25 +74,14 @@ module Portless
|
|
|
97
74
|
[]
|
|
98
75
|
end
|
|
99
76
|
|
|
100
|
-
def
|
|
77
|
+
def magic_dns_url
|
|
101
78
|
json = JSON.parse(`tailscale status --json 2>/dev/null`)
|
|
102
|
-
json.
|
|
79
|
+
dns = json.dig("Self", "DNSName").to_s.chomp(".")
|
|
80
|
+
dns.empty? ? nil : "https://#{dns}"
|
|
103
81
|
rescue StandardError
|
|
104
82
|
nil
|
|
105
83
|
end
|
|
106
84
|
|
|
107
|
-
def dns_name(status)
|
|
108
|
-
dns = status.dig("Self", "DNSName").to_s.chomp(".")
|
|
109
|
-
dns.empty? ? nil : "https://#{dns}"
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Does this node have the HTTPS / Funnel capability? (mirrors portless)
|
|
113
|
-
def capability?(status, name)
|
|
114
|
-
node = status["Self"] || {}
|
|
115
|
-
names = Array(node["Capabilities"]) + (node["CapMap"] || {}).keys
|
|
116
|
-
names.any? { |cap| down = cap.to_s.downcase; down == name || down.end_with?("/#{name}") }
|
|
117
|
-
end
|
|
118
|
-
|
|
119
85
|
def format_url(base, port)
|
|
120
86
|
base = base.chomp("/")
|
|
121
87
|
port == 443 ? base : "#{base}:#{port}"
|
data/lib/portless/state.rb
CHANGED
data/lib/portless/version.rb
CHANGED
data/lib/rb-portless.rb
CHANGED
|
@@ -22,7 +22,6 @@ require_relative "portless/lan_ip"
|
|
|
22
22
|
require_relative "portless/mdns"
|
|
23
23
|
require_relative "portless/share/ngrok"
|
|
24
24
|
require_relative "portless/share/tailscale"
|
|
25
|
-
require_relative "portless/run_support"
|
|
26
25
|
require_relative "portless/runner"
|
|
27
26
|
require_relative "portless/multi"
|
|
28
27
|
require_relative "portless/cli"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rb-portless
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.0.dev.20260630.
|
|
4
|
+
version: 0.3.0.dev.20260630.68c9c34
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Afonso
|
|
@@ -74,7 +74,6 @@ files:
|
|
|
74
74
|
- lib/portless/rails.rb
|
|
75
75
|
- lib/portless/rails_hosts.rb
|
|
76
76
|
- lib/portless/route_store.rb
|
|
77
|
-
- lib/portless/run_support.rb
|
|
78
77
|
- lib/portless/runner.rb
|
|
79
78
|
- lib/portless/service.rb
|
|
80
79
|
- lib/portless/share/ngrok.rb
|
data/lib/portless/run_support.rb
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Portless
|
|
4
|
-
# Shared bits of the two run paths (single-app Runner + multi-app Multi):
|
|
5
|
-
# the child env, the display URL, and first-run CA trust. Both expect a
|
|
6
|
-
# `@config`.
|
|
7
|
-
module RunSupport
|
|
8
|
-
private
|
|
9
|
-
|
|
10
|
-
def child_env(port, url)
|
|
11
|
-
{
|
|
12
|
-
"PORT" => port.to_s,
|
|
13
|
-
"HOST" => "127.0.0.1",
|
|
14
|
-
"PORTLESS_URL" => url,
|
|
15
|
-
# Let the app's own server-side TLS verification trust our CA.
|
|
16
|
-
"SSL_CERT_FILE" => (File.exist?(State.ca_cert) ? State.ca_cert : nil)
|
|
17
|
-
}.compact
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def display_url(hostname, proxy_port)
|
|
21
|
-
scheme = @config.tls ? "https" : "http"
|
|
22
|
-
default = @config.tls ? Constants::HTTPS_PORT : Constants::HTTP_PORT
|
|
23
|
-
suffix = proxy_port && proxy_port != default ? ":#{proxy_port}" : ""
|
|
24
|
-
"#{scheme}://#{hostname}#{suffix}"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Trust the local CA on first run (HTTPS only, interactive; never blocks the
|
|
28
|
-
# run), so HTTPS works without browser warnings — like portless.
|
|
29
|
-
def ensure_trusted
|
|
30
|
-
return unless @config.tls
|
|
31
|
-
return if Trust.trusted?
|
|
32
|
-
|
|
33
|
-
unless Privilege.interactive?
|
|
34
|
-
warn "rb-portless: CA not trusted — run `rb-portless trust` (HTTPS shows warnings until then)"
|
|
35
|
-
return
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
warn "rb-portless: trusting the local CA (first run)…"
|
|
39
|
-
Trust.install!
|
|
40
|
-
rescue Portless::Error => e
|
|
41
|
-
warn "rb-portless: couldn't auto-trust the CA (#{e.message}) — run `rb-portless trust`"
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|