yamine 0.19.0 → 0.21.0
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 +132 -0
- data/README.md +58 -14
- data/lib/yamine/certs.rb +107 -7
- data/lib/yamine/cli/boot.rb +5 -4
- data/lib/yamine/cli/system.rb +303 -83
- data/lib/yamine/cli/worktree.rb +6 -1
- data/lib/yamine/cli.rb +1 -1
- data/lib/yamine/doctor.rb +99 -0
- data/lib/yamine/errors.rb +4 -0
- data/lib/yamine/hosts.rb +76 -8
- data/lib/yamine/privileged_payload.rb +419 -0
- data/lib/yamine/proxy.rb +166 -33
- data/lib/yamine/proxy_control.rb +76 -6
- data/lib/yamine/version.rb +1 -1
- data/lib/yamine.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 73d2fe319b2c8af2c4bf821285dc76af42705e532761a8b2351377696928a114
|
|
4
|
+
data.tar.gz: 5a92b4760142642deed0a5a0131536094ac553b79759924daa7cc2157dec90a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 859e65d51d8e417cb1c747cf2d95a8336d4266fd57347d611ca3744baf4538dde86190fc5bc46023cbf28a0c12faceb7d2e3f84895afd62617e2c78e7fd1fc46
|
|
7
|
+
data.tar.gz: 8fc48c3d08d4ef3dfb5f579f41583ce3f315c90e896289279b072a5f9e6d66013eaa2b02f66eeb8ce3e983d2d4664287aa3e868e33606bc4f687f7975cd9c25a
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,138 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.21.0] — 2026-09-28
|
|
6
|
+
|
|
7
|
+
### Security
|
|
8
|
+
|
|
9
|
+
- **The privileged service no longer runs user-writable code.** The
|
|
10
|
+
launchd/systemd unit used to execute the gem's `bin`+`lib` straight
|
|
11
|
+
out of the user's home (a version-stamped, user-writable gem
|
|
12
|
+
directory), so anything that could write those paths — a gem upgrade,
|
|
13
|
+
a `bundle install`, an agent editing the gem — ran as root at every
|
|
14
|
+
boot. `service install` now stages `bin`+`lib` into a root-owned
|
|
15
|
+
directory at a stable, version-independent path (`/Library/Application
|
|
16
|
+
Support/yamine` on macOS, `/usr/local/share/yamine` on Linux):
|
|
17
|
+
copied aside, ownership/modes locked, verified fail-closed (a bad
|
|
18
|
+
tree registers no unit and leaves the running daemon untouched), then
|
|
19
|
+
swung live with an atomic symlink swap plus a root-owned `VERSION`
|
|
20
|
+
marker. The unit pins only that path.
|
|
21
|
+
- **The passwordless grant can no longer introduce root-executed
|
|
22
|
+
code.** `yamine sudoers` used to grant the version-stamped gem path
|
|
23
|
+
with `service install --internal` — effectively arbitrary code as
|
|
24
|
+
root for anyone who could write the gem, and stale after every
|
|
25
|
+
release. It now grants only the staged, version-independent payload:
|
|
26
|
+
`hosts sync` (the recurring agent need) and `service uninstall`
|
|
27
|
+
(which only deletes yamine's own files). Staging new root code stays
|
|
28
|
+
a human interactive sudo; a non-interactive `service install` fails
|
|
29
|
+
fast saying so. If your `/etc/sudoers.d/yamine` still pins a
|
|
30
|
+
version-stamped gem path, re-run `yamine sudoers` and replace it.
|
|
31
|
+
- **`hosts sync` is strictly validated before anything is written.**
|
|
32
|
+
Route names are user-writable and the sync runs as root, so every
|
|
33
|
+
name must now be well-formed (no newline/breakout), written only
|
|
34
|
+
inside yamine's managed block, and under `.localhost` or a domain in
|
|
35
|
+
the root-owned staged allowlist. Anything else fails closed naming
|
|
36
|
+
the policy. Custom `proxy.host` domains need one human step: append
|
|
37
|
+
the parent domain to the staged `allowed-tlds` file.
|
|
38
|
+
- **`yamine doctor` gains a `service` check.** It names a legacy unit
|
|
39
|
+
(payload in a user home or version-stamped gem path) with the exact
|
|
40
|
+
migration (`sudo yamine service install`), warns on staged-vs-CLI
|
|
41
|
+
version skew, fails on any root-executed path that is not root-owned
|
|
42
|
+
or is group/other-writable, and always reports the service
|
|
43
|
+
interpreter's path and writability. The legacy daemon keeps working
|
|
44
|
+
until the next elevated install.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- `service install` (human, interactive, elevated) migrates a legacy
|
|
49
|
+
install in place and prints the interpreter caveat; `service
|
|
50
|
+
uninstall` removes the staged payload as well as the unit, scoped to
|
|
51
|
+
yamine's own files.
|
|
52
|
+
- Non-interactive (`sudo -n`) failure messaging points at the new
|
|
53
|
+
provisioning story: humans install the 443 service once per machine,
|
|
54
|
+
agents keep hosts entries fresh through the grant.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- **The printed grant now matches what the CLI invokes, byte for
|
|
59
|
+
byte.** Elevated re-execs went through `env YAMINE_STATE_DIR=…`,
|
|
60
|
+
which made the command `/usr/bin/env` — a path no rule names — so
|
|
61
|
+
every passwordless attempt fell through to the password-required
|
|
62
|
+
admin rule; and the staged macOS path's space was unescaped in the
|
|
63
|
+
sudoers output, so that rule could never match either. No
|
|
64
|
+
environment crosses sudo anymore (no `env` prefix, no SETENV): the
|
|
65
|
+
root half derives the invoking user's state dir from `SUDO_USER`,
|
|
66
|
+
and spaces are backslash-escaped when the rules are printed. Pinned
|
|
67
|
+
by a parity test that parses the printed specs and asserts equality
|
|
68
|
+
with the captured elevated argv.
|
|
69
|
+
- **The `--no-service` sudo daemon no longer executes the gem
|
|
70
|
+
directory as root.** A privileged `proxy start` spawns the staged
|
|
71
|
+
root-owned payload, staging it first under the same
|
|
72
|
+
human-authorized sudo (`service stage --internal`, never granted)
|
|
73
|
+
when missing. The unprivileged spawn path is unchanged.
|
|
74
|
+
|
|
75
|
+
### Open caveat (not closed, stated plainly)
|
|
76
|
+
- **The interpreter is still user-writable.** No root-owned Ruby ≥ 3.2
|
|
77
|
+
exists on a stock machine, so the daemon necessarily runs the
|
|
78
|
+
invoking (user-writable) Ruby. The payload is root-owned; the
|
|
79
|
+
interpreter is not. Doctor reports it truthfully and the README says
|
|
80
|
+
so. Fixed only by a root-owned Ruby new enough for the gem; yamine
|
|
81
|
+
will not vendor or stage an interpreter.
|
|
82
|
+
|
|
83
|
+
## [0.20.0] — 2026-09-28
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
|
|
87
|
+
- **Every proxy socket wait is bounded by an idle timeout
|
|
88
|
+
(`YAMINE_PROXY_IDLE_TIMEOUT`, default 60s).** Silence is capped,
|
|
89
|
+
total duration is not: the timer resets on each byte transferred, so
|
|
90
|
+
a slow-but-moving transfer runs as long as it needs while a stalled
|
|
91
|
+
peer or a stale keep-alive socket is dropped instead of pinning a
|
|
92
|
+
thread until the proxy stops answering.
|
|
93
|
+
- **`yamine doctor` gains a "serving ca" check.** It verifies the
|
|
94
|
+
certificate the proxy actually serves against the CA now on disk and
|
|
95
|
+
names the fix when they disagree — so a regenerated CA shows up in
|
|
96
|
+
doctor instead of surfacing later as `ERR_CERT_AUTHORITY_INVALID` in
|
|
97
|
+
the browser.
|
|
98
|
+
|
|
99
|
+
### Changed
|
|
100
|
+
|
|
101
|
+
- **`yamine proxy start` no longer spawns a second proxy when one
|
|
102
|
+
already serves the port.** It reports that idempotently (new
|
|
103
|
+
`ProxyAlreadyRunningError`) instead of recording a dead PID over
|
|
104
|
+
real state — the shape that left the recorded PID pointing at
|
|
105
|
+
nothing while the real proxy kept serving.
|
|
106
|
+
- **`yamine proxy start` on a privileged port elevates the way the
|
|
107
|
+
boot path does.** One sudo prompt when interactive, or a failure
|
|
108
|
+
pointing at `yamine setup` when not — never a doomed unprivileged
|
|
109
|
+
child that dies on `EACCES` and leaves its PID behind.
|
|
110
|
+
- **`yamine proxy stop` tells the truth about a proxy it cannot
|
|
111
|
+
signal.** On a dead recorded PID it probes the port first: a proxy
|
|
112
|
+
still serving — the root launchd/systemd service, which the CLI
|
|
113
|
+
cannot kill — yields a "still serving" answer with the restart
|
|
114
|
+
command and the recorded state kept, instead of "Removed stale
|
|
115
|
+
proxy state" while the proxy kept running.
|
|
116
|
+
- **`yamine clean` refuses while a proxy it cannot stop is serving.**
|
|
117
|
+
It exits 1 instead of untrusting the CA and deleting the state
|
|
118
|
+
directory out from under a live root service.
|
|
119
|
+
- **A running proxy reloads the signing CA when the pair on disk
|
|
120
|
+
changes.** It re-reads the CA files and drops cached host
|
|
121
|
+
certificates, so a regenerated CA takes effect without a restart —
|
|
122
|
+
previously only a restart fixed it, and until then the proxy kept
|
|
123
|
+
signing certificates no browser trusts.
|
|
124
|
+
- **The TLS handshake moved out of the acceptor into the connection
|
|
125
|
+
thread.** A client that connects and sends nothing can no longer pin
|
|
126
|
+
an acceptor; acceptors now survive per-connection errors and keep
|
|
127
|
+
accepting.
|
|
128
|
+
|
|
129
|
+
### Fixed
|
|
130
|
+
|
|
131
|
+
- **A spawned daemon that dies before serving fails immediately with
|
|
132
|
+
the proxy log tail instead of a timeout.** A child killed by
|
|
133
|
+
`EACCES` on a privileged port (or anything else fatal at bind)
|
|
134
|
+
used to burn the whole wait and then record a PID that was never
|
|
135
|
+
alive; it now fails fast and records nothing.
|
|
136
|
+
|
|
5
137
|
## [0.19.0] — 2026-09-27
|
|
6
138
|
|
|
7
139
|
### Added
|
data/README.md
CHANGED
|
@@ -54,21 +54,35 @@ to get back to 443.
|
|
|
54
54
|
|
|
55
55
|
Binding 443 is privileged, so yamine installs a **root-owned launchd
|
|
56
56
|
service** (macOS) or systemd unit (Linux) that binds 443 at boot — the
|
|
57
|
-
same model as puma-dev and portless. Installing it needs
|
|
58
|
-
machine
|
|
59
|
-
`https://<app>.localhost` with no
|
|
57
|
+
same model as puma-dev and portless. Installing it needs an interactive
|
|
58
|
+
sudo **once per machine** (one Touch ID tap); after that, every `yamine`
|
|
59
|
+
run in any project gets a clean `https://<app>.localhost` with no
|
|
60
|
+
elevation and no prompt.
|
|
60
61
|
|
|
61
|
-
**Human (interactive):** run setup once — it
|
|
62
|
-
service, syncs hosts, and verifies:
|
|
62
|
+
**Human (interactive):** run setup once — it stages a root-owned copy of
|
|
63
|
+
yamine, installs the service, trusts the CA, syncs hosts, and verifies:
|
|
63
64
|
|
|
64
65
|
```bash
|
|
65
66
|
yamine setup
|
|
67
|
+
# or, for just the service: sudo yamine service install
|
|
66
68
|
```
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
The unit runs a **staged payload**, not your gem directory: the install
|
|
71
|
+
copies yamine's `bin`+`lib` into a root-owned directory at a stable,
|
|
72
|
+
version-independent path (`/Library/Application Support/yamine` on
|
|
73
|
+
macOS, `/usr/local/share/yamine` on Linux), verifies root ownership and
|
|
74
|
+
modes fail-closed (a bad tree registers nothing), and pins that path in
|
|
75
|
+
the unit. Ordinary user-space writes — a gem upgrade, a `bundle
|
|
76
|
+
install`, an agent editing the gem — can no longer change what runs as
|
|
77
|
+
root, and upgrades never stale the setup. `yamine doctor` names a legacy
|
|
78
|
+
install (a unit still pointing at a user-writable gem path) with the
|
|
79
|
+
exact migration, plus staged-vs-CLI version skew and any ownership
|
|
80
|
+
problem.
|
|
81
|
+
|
|
82
|
+
**Agents: the steady-state grant.** New project ⇒ new hostname ⇒ Safari
|
|
83
|
+
needs the `/etc/hosts` entry; that recurring privileged need stays
|
|
84
|
+
agent-invocable without a prompt. Install the scoped passwordless-sudo
|
|
85
|
+
rules once (as an admin):
|
|
72
86
|
|
|
73
87
|
```bash
|
|
74
88
|
yamine sudoers > /tmp/yamine.sudoers
|
|
@@ -76,11 +90,41 @@ sudo install -o root -g wheel -m 440 /tmp/yamine.sudoers /etc/sudoers.d/yamine
|
|
|
76
90
|
sudo install -o root -g root -m 440 /tmp/yamine.sudoers /etc/sudoers.d/yamine # Linux
|
|
77
91
|
```
|
|
78
92
|
|
|
79
|
-
`yamine sudoers` prints rules
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
`yamine sudoers` prints rules for the **staged payload only** — `hosts
|
|
94
|
+
sync` (hostnames strictly validated: well-formed, written only inside
|
|
95
|
+
yamine's managed block, and only under `.localhost` or an allowlisted
|
|
96
|
+
domain) and `service uninstall` (which only deletes yamine's own
|
|
97
|
+
files). Installing or upgrading the staged payload moves user-writable
|
|
98
|
+
source into root-owned paths, so it deliberately stays a human
|
|
99
|
+
interactive sudo — no grant will ever cover it, and a non-interactive
|
|
100
|
+
`service install` fails fast saying so. To undo the grant:
|
|
101
|
+
`sudo rm /etc/sudoers.d/yamine`.
|
|
102
|
+
|
|
103
|
+
If you installed an older yamine, your `/etc/sudoers.d/yamine` may still
|
|
104
|
+
pin a version-stamped gem path (`.../gems/yamine-X.Y.Z/...`) including
|
|
105
|
+
an install rule — re-run `yamine sudoers` and replace the file. The old
|
|
106
|
+
rules go stale every release (which pushed people toward `NOPASSWD:
|
|
107
|
+
ALL`); the new ones survive upgrades.
|
|
108
|
+
|
|
109
|
+
**Custom domains** (e.g. `proxy.host: myapp.local.example.com` for OAuth
|
|
110
|
+
parity) need their parent domain allowlisted once by a human — `hosts
|
|
111
|
+
sync` refuses anything outside `.localhost` and the staged allowlist,
|
|
112
|
+
because an unvalidated root hosts write could point a real vendor domain
|
|
113
|
+
at loopback:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
echo 'local.example.com' | sudo tee -a "/Library/Application Support/yamine/allowed-tlds" # macOS
|
|
117
|
+
echo 'local.example.com' | sudo tee -a /usr/local/share/yamine/allowed-tlds # Linux
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Open caveat: the interpreter.** The unit runs the invoking Ruby, and
|
|
121
|
+
there is no root-owned Ruby ≥ 3.2 on a stock machine (`/usr/bin/ruby` is
|
|
122
|
+
2.6), so the daemon necessarily runs a user-writable interpreter today.
|
|
123
|
+
The payload is root-owned; the interpreter is not — `yamine doctor`
|
|
124
|
+
reports its path and writability truthfully. Anything that can write
|
|
125
|
+
that Ruby can change what runs as root. This hole stays open until the
|
|
126
|
+
machine has a root-owned Ruby new enough for the gem; yamine will not
|
|
127
|
+
vendor or stage an interpreter to pretend otherwise.
|
|
84
128
|
|
|
85
129
|
## The one-file model
|
|
86
130
|
|
data/lib/yamine/certs.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "digest"
|
|
3
4
|
require "fileutils"
|
|
4
5
|
require "openssl"
|
|
5
6
|
|
|
@@ -170,23 +171,32 @@ module Yamine
|
|
|
170
171
|
end
|
|
171
172
|
|
|
172
173
|
# Build an SSLContext whose SNI callback serves the right cert per host.
|
|
174
|
+
#
|
|
175
|
+
# The CA pair on disk can be regenerated under a live proxy (`yamine
|
|
176
|
+
# clean`/`trust`/`setup` rewrite it). A proxy that keeps signing with
|
|
177
|
+
# its boot-time CA then mints leaves no browser trusts
|
|
178
|
+
# (ERR_CERT_AUTHORITY_INVALID against the new CA), so the SNI
|
|
179
|
+
# callback — which already runs per handshake — reloads the pair
|
|
180
|
+
# when it changes and drops every leaf minted from the old one.
|
|
173
181
|
def server_context(dir = state_dir)
|
|
174
|
-
|
|
182
|
+
watcher = CaWatcher.new(dir)
|
|
175
183
|
cache = CertCache.new(CACHE_SIZE)
|
|
184
|
+
watcher.on_reload = -> { cache.clear }
|
|
176
185
|
ctx = OpenSSL::SSL::SSLContext.new
|
|
177
|
-
ctx.cert =
|
|
178
|
-
ctx.key = ca_key
|
|
186
|
+
ctx.cert, ctx.key = watcher.current
|
|
179
187
|
# ruby-openssl versions differ in how the callback receives its
|
|
180
188
|
# arguments: [[socket, name]] (one array arg), (socket, name), or
|
|
181
189
|
# (name). Flatten defensively — a raise inside the callback
|
|
182
190
|
# surfaces as an unrecognized-name handshake alert.
|
|
183
191
|
ctx.servername_cb = lambda do |*args|
|
|
184
192
|
host = Array(args).flatten.last.to_s.downcase
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
watcher.with_current do |ca_cert, ca_key|
|
|
194
|
+
entry = cache.fetch(host) do
|
|
195
|
+
cert, key = mint_host(host, ca_cert, ca_key)
|
|
196
|
+
[cert, key]
|
|
197
|
+
end
|
|
198
|
+
entry ? OpenSSL::SSL::SSLContext.new.tap { |c| c.cert, c.key = entry } : nil
|
|
188
199
|
end
|
|
189
|
-
entry ? OpenSSL::SSL::SSLContext.new.tap { |c| c.cert, c.key = entry } : nil
|
|
190
200
|
end
|
|
191
201
|
ctx
|
|
192
202
|
end
|
|
@@ -252,6 +262,96 @@ module Yamine
|
|
|
252
262
|
def size
|
|
253
263
|
@mutex.synchronize { @store.size }
|
|
254
264
|
end
|
|
265
|
+
|
|
266
|
+
# Every entry is signed by one CA generation; a reload must not
|
|
267
|
+
# leave old-CA leaves behind to be served after the swap.
|
|
268
|
+
def clear
|
|
269
|
+
@mutex.synchronize do
|
|
270
|
+
@store.clear
|
|
271
|
+
@order.clear
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# The CA pair a live server_context signs with, reloaded when the
|
|
277
|
+
# files on disk change. The hot path is two stat calls per
|
|
278
|
+
# handshake; the pair is only re-read (and the leaf cache only
|
|
279
|
+
# dropped) when a stat snapshot moves AND the digest differs, so a
|
|
280
|
+
# touch without content change costs nothing. One mutex around
|
|
281
|
+
# check+reload+mint keeps a swap from racing a handshake into a
|
|
282
|
+
# mixed pair or a stale cache hit. A failed reload keeps serving
|
|
283
|
+
# the previous CA — a raise inside the SNI callback would abort
|
|
284
|
+
# the handshake outright.
|
|
285
|
+
class CaWatcher
|
|
286
|
+
def initialize(dir)
|
|
287
|
+
@dir = dir
|
|
288
|
+
@mutex = Mutex.new
|
|
289
|
+
@cert, @key = Certs.load_ca(dir)
|
|
290
|
+
@stat = stat_snapshot
|
|
291
|
+
@digest = pair_digest
|
|
292
|
+
@on_reload = nil
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Called (once per swap, under the lock) after a reload. The
|
|
296
|
+
# server_context sets this to drop its leaf cache.
|
|
297
|
+
attr_writer :on_reload
|
|
298
|
+
|
|
299
|
+
def current
|
|
300
|
+
[@cert, @key]
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def with_current
|
|
304
|
+
@mutex.synchronize do
|
|
305
|
+
reload_if_changed!
|
|
306
|
+
yield @cert, @key
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
private
|
|
311
|
+
|
|
312
|
+
def paths
|
|
313
|
+
Certs.ca_paths(@dir)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def stat_snapshot
|
|
317
|
+
[paths[:cert], paths[:key]].map do |path|
|
|
318
|
+
stat = File.stat(path)
|
|
319
|
+
[stat.mtime, stat.size]
|
|
320
|
+
end
|
|
321
|
+
rescue SystemCallError
|
|
322
|
+
nil
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def pair_digest
|
|
326
|
+
Digest::SHA256.hexdigest(File.read(paths[:cert]) + File.read(paths[:key]))
|
|
327
|
+
rescue SystemCallError, OpenSSL::OpenSSLError
|
|
328
|
+
nil
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def reload_if_changed!
|
|
332
|
+
snapshot = stat_snapshot
|
|
333
|
+
return if !snapshot.nil? && snapshot == @stat
|
|
334
|
+
|
|
335
|
+
digest = pair_digest
|
|
336
|
+
if !digest.nil? && digest == @digest
|
|
337
|
+
# Touched but unchanged: adopt the snapshot so the hot path
|
|
338
|
+
# goes back to two stats instead of a digest per handshake.
|
|
339
|
+
@stat = snapshot
|
|
340
|
+
return
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# The serving SSLContext is frozen once the server accepts, so
|
|
344
|
+
# the default certificate cannot be swapped mid-flight — and it
|
|
345
|
+
# does not need to be: every real client sends SNI, and the
|
|
346
|
+
# per-host context this callback returns carries the new CA.
|
|
347
|
+
cert, key = Certs.load_ca(@dir)
|
|
348
|
+
@cert, @key = cert, key
|
|
349
|
+
@stat = stat_snapshot
|
|
350
|
+
@digest = digest || pair_digest
|
|
351
|
+
@on_reload&.call
|
|
352
|
+
rescue CertError, OpenSSL::OpenSSLError, SystemCallError
|
|
353
|
+
nil
|
|
354
|
+
end
|
|
255
355
|
end
|
|
256
356
|
end
|
|
257
357
|
end
|
data/lib/yamine/cli/boot.rb
CHANGED
|
@@ -940,10 +940,11 @@ module Yamine
|
|
|
940
940
|
privileged = port < 1024 && !ProxyControl.root?
|
|
941
941
|
if privileged && !ctx.interactive?
|
|
942
942
|
$stderr.puts "Error: proxy is not running and port #{port} needs root."
|
|
943
|
-
$stderr.puts " Human: run this once — yamine setup"
|
|
944
|
-
$stderr.puts " Agent/CI:
|
|
945
|
-
$stderr.puts "
|
|
946
|
-
$stderr.puts "
|
|
943
|
+
$stderr.puts " Human: run this once in a terminal — yamine setup (or: sudo yamine service install)"
|
|
944
|
+
$stderr.puts " Agent/CI: the 443 service is installed by a human once per machine — it cannot be provisioned passwordlessly."
|
|
945
|
+
$stderr.puts " Steady-state hosts sync works via the grant instead:"
|
|
946
|
+
$stderr.puts " yamine sudoers > /tmp/yamine.sudoers"
|
|
947
|
+
$stderr.puts " sudo install -o root -g wheel -m 440 /tmp/yamine.sudoers /etc/sudoers.d/yamine"
|
|
947
948
|
$stderr.puts " Or start the proxy by hand: sudo yamine proxy start"
|
|
948
949
|
exit 1
|
|
949
950
|
end
|