rb-portless 0.4.0.dev.20260630.0d72797 → 0.4.0.dev.20260630.0e2b377
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/AGENTS.md +1 -0
- data/lib/portless/daemon.rb +2 -0
- data/lib/portless/run_support.rb +18 -1
- data/lib/portless/trust.rb +70 -4
- data/lib/portless/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0e0f20f5dbecd72651bb3e1d7509f41ae93d2a901336189d7776b58747008917
|
|
4
|
+
data.tar.gz: 90d742ec41737333d3f55a6552505379a8b097373eb95031edd32973f6a42689
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 11ff15a4154575a926025f57192d9f663325b62df8054ba2fe403ad88db33aceebc4ae683082f5e4a6bc70f0ace369658dac8a35cd99e626e8d844cf577252b4
|
|
7
|
+
data.tar.gz: 99f4b875811a3f0a6a8c51a588d57d1877de0c722d232916e066654e738e12d47261adf2621b4581c7224932054cf7cbcabff75907e4cda4d421c409a238647d
|
data/AGENTS.md
CHANGED
|
@@ -64,6 +64,7 @@ injected as `PORT`; the proxy routes the named host to it.
|
|
|
64
64
|
- **Phase 2 ✅** HTTP/2, full command surface (doctor/clean/prune/alias/get/hosts),
|
|
65
65
|
boot service (launchd/systemd), daemon lifecycle.
|
|
66
66
|
- **Phase 3 ✅ (partial)** framework `--port`/`--host` injection, Linux CA trust,
|
|
67
|
+
Firefox/Chrome NSS trust (`certutil` into each profile + `~/.pki/nssdb`),
|
|
67
68
|
optional `portless/rails` railtie.
|
|
68
69
|
|
|
69
70
|
- **Phase 4 ✅** startup banner; monorepo multi-app (`apps` map); LAN mode
|
data/lib/portless/daemon.rb
CHANGED
|
@@ -54,6 +54,8 @@ module Portless
|
|
|
54
54
|
return spawn_detached(port: Constants::FALLBACK_PROXY_PORT, tls: tls)
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
warn "rb-portless: binding :#{port} needs sudo (it's a privileged port) — " \
|
|
58
|
+
"enter your password to serve #{tls ? 'HTTPS' : 'HTTP'} without a port number"
|
|
57
59
|
ok = Privilege.reexec_with_sudo([ "proxy", "start", "--port", port.to_s, tls ? "--tls" : "--no-tls" ])
|
|
58
60
|
return wait_until_running(port) if ok
|
|
59
61
|
|
data/lib/portless/run_support.rb
CHANGED
|
@@ -8,13 +8,30 @@ module Portless
|
|
|
8
8
|
private
|
|
9
9
|
|
|
10
10
|
def child_env(port, url)
|
|
11
|
-
{
|
|
11
|
+
base = {
|
|
12
12
|
"PORT" => port.to_s,
|
|
13
13
|
"HOST" => "127.0.0.1",
|
|
14
14
|
"PORTLESS_URL" => url,
|
|
15
15
|
# Let the app's own server-side TLS verification trust our CA.
|
|
16
16
|
"SSL_CERT_FILE" => (File.exist?(State.ca_cert) ? State.ca_cert : nil)
|
|
17
17
|
}.compact
|
|
18
|
+
# Our own bundle (rb-portless is loaded via the app's Bundler binstub) must
|
|
19
|
+
# not leak into the dev command — a foreman-style `bin/dev` isn't in the
|
|
20
|
+
# app Gemfile and each Procfile process re-enters Bundler via its binstub.
|
|
21
|
+
unbundled_overrides.merge(base)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# ENV deltas that undo Bundler for the child (a nil value unsets the key).
|
|
25
|
+
# Empty when we're not running under Bundler at all.
|
|
26
|
+
def unbundled_overrides
|
|
27
|
+
return {} unless defined?(Bundler) && Bundler.respond_to?(:unbundled_env)
|
|
28
|
+
|
|
29
|
+
target = Bundler.unbundled_env
|
|
30
|
+
(ENV.keys | target.keys).each_with_object({}) do |key, deltas|
|
|
31
|
+
deltas[key] = target[key] if ENV[key] != target[key]
|
|
32
|
+
end
|
|
33
|
+
rescue StandardError
|
|
34
|
+
{}
|
|
18
35
|
end
|
|
19
36
|
|
|
20
37
|
def display_url(hostname, proxy_port)
|
data/lib/portless/trust.rb
CHANGED
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module Portless
|
|
4
4
|
# Install / remove our CA in the OS trust store so browsers accept the per-host
|
|
5
|
-
# certs. macOS uses the login keychain (GUI/Touch-ID auth — no sudo)
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# certs. macOS uses the login keychain (GUI/Touch-ID auth — no sudo); Linux
|
|
6
|
+
# drops it in the distro anchor dir + runs update-ca-trust (sudo). Firefox and
|
|
7
|
+
# Chrome-on-Linux ignore the OS store and read their own NSS DBs, so we also
|
|
8
|
+
# install via `certutil` into every Firefox profile + `~/.pki/nssdb`. A
|
|
9
|
+
# ca.trusted fingerprint marker short-circuits the check. Mirrors mkcert/
|
|
10
|
+
# portless's trustCA paths.
|
|
8
11
|
module Trust
|
|
9
12
|
module_function
|
|
10
13
|
|
|
14
|
+
# The nickname our CA carries inside NSS DBs (Firefox/Chrome).
|
|
15
|
+
NSS_NICKNAME = "rb-portless Local CA"
|
|
16
|
+
|
|
11
17
|
def certs = @certs ||= Certs.new
|
|
12
18
|
|
|
13
19
|
def trusted?
|
|
@@ -24,12 +30,14 @@ module Portless
|
|
|
24
30
|
else
|
|
25
31
|
raise Error, unsupported_message
|
|
26
32
|
end
|
|
33
|
+
install_nss # Firefox (every OS) + Chrome-on-Linux read their own NSS DBs.
|
|
27
34
|
write_marker
|
|
28
35
|
end
|
|
29
36
|
|
|
30
37
|
def uninstall!
|
|
31
38
|
return unless File.exist?(State.ca_cert)
|
|
32
39
|
|
|
40
|
+
uninstall_nss
|
|
33
41
|
if Constants::MACOS
|
|
34
42
|
system("security", "remove-trusted-cert", State.ca_cert)
|
|
35
43
|
elsif linux?
|
|
@@ -78,6 +86,64 @@ module Portless
|
|
|
78
86
|
Privilege.reexec_with_sudo([ "trust" ]) || raise(Error, "sudo required to trust the CA on Linux")
|
|
79
87
|
end
|
|
80
88
|
|
|
89
|
+
# ── Firefox / Chrome NSS DBs (no sudo — they live under $HOME) ─────────
|
|
90
|
+
# Browsers that ship their own cert store ignore the OS trust anchor, so add
|
|
91
|
+
# the CA to each NSS DB directly. Best-effort: a missing certutil or a single
|
|
92
|
+
# failing profile must never abort the trust flow.
|
|
93
|
+
def install_nss
|
|
94
|
+
dbs = nss_dbs
|
|
95
|
+
return if dbs.empty?
|
|
96
|
+
return warn_no_certutil unless certutil
|
|
97
|
+
|
|
98
|
+
dbs.each do |db|
|
|
99
|
+
system(certutil, "-A", "-d", "sql:#{db}", "-t", "C,,", "-n", NSS_NICKNAME, "-i", State.ca_cert,
|
|
100
|
+
out: File::NULL, err: File::NULL)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def uninstall_nss
|
|
105
|
+
return unless certutil
|
|
106
|
+
|
|
107
|
+
nss_dbs.each do |db|
|
|
108
|
+
system(certutil, "-D", "-d", "sql:#{db}", "-n", NSS_NICKNAME, out: File::NULL, err: File::NULL)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Every NSS DB worth trusting into: Chrome's shared store plus each Firefox
|
|
113
|
+
# profile. An NSS sql DB is a directory holding a cert9.db.
|
|
114
|
+
def nss_dbs(home = Dir.home)
|
|
115
|
+
([ File.join(home, ".pki", "nssdb") ] + firefox_profiles(home))
|
|
116
|
+
.select { |db| File.exist?(File.join(db, "cert9.db")) }
|
|
117
|
+
.uniq
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Firefox keeps a profile dir (each with its own cert9.db) under a handful of
|
|
121
|
+
# roots depending on packaging — native, Snap, Flatpak, and macOS.
|
|
122
|
+
def firefox_profiles(home = Dir.home)
|
|
123
|
+
[
|
|
124
|
+
File.join(home, ".mozilla", "firefox"),
|
|
125
|
+
File.join(home, "snap", "firefox", "common", ".mozilla", "firefox"),
|
|
126
|
+
File.join(home, ".var", "app", "org.mozilla.firefox", ".mozilla", "firefox"),
|
|
127
|
+
File.join(home, "Library", "Application Support", "Firefox", "Profiles")
|
|
128
|
+
].select { |root| File.directory?(root) }.flat_map do |root|
|
|
129
|
+
Dir.children(root).map { |child| File.join(root, child) }
|
|
130
|
+
.select { |dir| File.exist?(File.join(dir, "cert9.db")) }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def warn_no_certutil
|
|
135
|
+
warn "rb-portless: install `nss`/`libnss3-tools` (certutil) to trust the CA in Firefox/Chrome"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# certutil's path, or nil. Memoised; resolved off PATH so we never shell out
|
|
139
|
+
# just to probe for it.
|
|
140
|
+
def certutil
|
|
141
|
+
return @certutil if defined?(@certutil)
|
|
142
|
+
|
|
143
|
+
@certutil = ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).map { |dir| File.join(dir, "certutil") }
|
|
144
|
+
.find { |path| File.executable?(path) && !File.directory?(path) }
|
|
145
|
+
end
|
|
146
|
+
|
|
81
147
|
def linux? = RbConfig::CONFIG["host_os"] =~ /linux/i
|
|
82
148
|
|
|
83
149
|
def marker_matches?
|
|
@@ -98,7 +164,7 @@ module Portless
|
|
|
98
164
|
end
|
|
99
165
|
|
|
100
166
|
def unsupported_message
|
|
101
|
-
"automatic CA trust isn't wired for this OS yet — trust #{State.ca_cert} manually
|
|
167
|
+
"automatic CA trust isn't wired for this OS yet — trust #{State.ca_cert} manually"
|
|
102
168
|
end
|
|
103
169
|
end
|
|
104
170
|
end
|
data/lib/portless/version.rb
CHANGED