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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8938cad2e70c50e9e39f2078fe9a8590b6f0324a136cd1da9f5a9258a0604c4f
4
- data.tar.gz: 5fe88139d39e877e53b0adf46a87bd20ee361b23f3f858ca74d2039df3ee0562
3
+ metadata.gz: 0e0f20f5dbecd72651bb3e1d7509f41ae93d2a901336189d7776b58747008917
4
+ data.tar.gz: 90d742ec41737333d3f55a6552505379a8b097373eb95031edd32973f6a42689
5
5
  SHA512:
6
- metadata.gz: e49cc73d6937721e37c41c4489755252c1cc815f1b687b2187a62c6e1a12c3c496d9f3eaf24a948957488ef0b8928f21862c4920225b707988938d53fb0f51bb
7
- data.tar.gz: b4ef3864d39837cb27896997d22c24e4ee496c004bfa0a296a534ead61ed1a555936457e55600bcccc942ca41d776c9456ab4bd3dacfd81c7809719b1768bdd1
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
@@ -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
 
@@ -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)
@@ -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). A
6
- # ca.trusted fingerprint marker short-circuits the check. Linux/Windows land in
7
- # phase 3. Mirrors portless's trustCA paths.
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 (phase 3)"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Portless
4
- VERSION = "0.4.0.dev.20260630.0d72797"
4
+ VERSION = "0.4.0.dev.20260630.0e2b377"
5
5
  end
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.4.0.dev.20260630.0d72797
4
+ version: 0.4.0.dev.20260630.0e2b377
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Afonso