ask-local 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9b2c40e96d2ccde2095d698a79684a3dec1335afe9ac4acc1773405f520002d
4
- data.tar.gz: a2a63d630679488b6236a499bb954fa2cf70028c2f00d5dd34c2f76a21ef057b
3
+ metadata.gz: 4407cbb75f7a6a5347fd2b71963a4229dc925c7d36438ae885c25c04759c94a6
4
+ data.tar.gz: 3ac24692630768264da89e9ce37e75403cbb3e2686ce1c18d6b1a99483d9c85d
5
5
  SHA512:
6
- metadata.gz: dc721fac709387e593ed445f03080e994fcd2614ec5ff74e92ba4ccccf17dea61d59029ab187eaffe1eca52a088c817a36d86cdf7e7c13a8d3fd5878d8284f7a
7
- data.tar.gz: 16c12424f982fc3db86c2fd210cae69e7890abc22c3f1cdd8c747cfac9ab2c15952c431ee72557144d5dfccd8bc9c95aebe0ab94b6cb3b30c95faa33329a8b5a
6
+ metadata.gz: '0419a2e0b1b62733f3687fffc1eafee535380aaa636a04d77814fc229b92521369fefe7b394f73533686b85a4c5975bc6e3837d985b3a387efff9b43dcd4f6d9'
7
+ data.tar.gz: b88ce440466e46a9c47af5a6ac4072d4ffc4ef774b17c4e07e531c4f9698b284edd364ec09d3467d05caa31eebadcd29a93796d7f01e400df4ea97787670a306
data/CHANGELOG.md CHANGED
@@ -1,69 +1,77 @@
1
1
  # Changelog
2
2
 
3
- ## [0.1.1] — 2026-09-06
3
+ ## [0.2.1] — 2026-09-08
4
4
 
5
- Patch release focused on workstation setup, URL correctness, and proxy reliability.
5
+ Patch release focused on making `ask-local setup` dependable end-to-end:
6
+ it now installs the 443 service and *finishes* (hosts + doctor), works
7
+ passwordless for agents, survives first-run edge cases, and shows what it
8
+ is doing while it works.
6
9
 
7
- ### Added — `ask-local setup` & `ask-local start`
10
+ ### Fixed
8
11
 
9
- - `ask-local setup` one-shot workstation setup for clean
10
- `https://<app>.localhost` URLs: trust the local CA, serve port 443
11
- (root launchd/systemd service when possible, sudo daemon otherwise),
12
- sync `/etc/hosts`, and verify with `doctor`. Each step reports
13
- `==>` / `ok` and the first failure aborts with the specific fix.
14
- - `ask-local start`one-setup-and-go entry point: an idempotent
15
- workstation-check-then-boot (`ask-local setup` if needed, then the
16
- app). `ask-local` bare is an alias for it; `ask-local setup` stays for
17
- explicit re-setup.
18
- - `askl` — shell-friendly alias binary (`bin/askl`, same entry point as
19
- `bin/ask-local`). Keep `ask-local` in logs and docs so `grep` stays
20
- useful.
12
+ - **`setup` no longer stops after installing the service.** The elevated
13
+ install used to `exit` mid-flow, so `/etc/hosts` was never synced and
14
+ `doctor` never ran despite the "setup complete" promise. Install and
15
+ uninstall now return booleans and the CLI owns the exit codes, so
16
+ `setup` always reaches the hosts and doctor steps. (`service uninstall`
17
+ also crashed outrightit called its handler without the required
18
+ context argument.)
19
+ - **A stale user-owned plist is healed on install.** `File.write` keeps
20
+ an existing file's owner, so a leftover plist from an older version
21
+ stayed user-owned and launchd refused it forever with "Bootstrap
22
+ failed: 5". Install now chowns the plist to root explicitly (launchd
23
+ and systemd).
24
+ - **The TLS proxy survives plaintext probes.** The health probe sends
25
+ plain HTTP before TLS; an unhandled `SSL_accept` error used to kill
26
+ the acceptor thread, and with both acceptors gone the daemon exited
27
+ before the readiness probe ever succeeded. A plaintext connection is
28
+ now just one dropped connection.
29
+ - `clean` referenced the CA common name unqualified (NameError) — fixed.
30
+ - `setup --no-service` printed a duplicate "2/3" step — numbered 1/4–4/4.
21
31
 
22
- ### Added — DNS-rebinding & log hygiene
32
+ ### Added
23
33
 
24
- - DNS-rebinding boundary: foreign `Host` headers get a bare 404 naming
25
- nothing; only hosts under our own configured TLDs see the route-listing
26
- 404. The proxy takes `--tld` (persisted to `proxy.tlds`) so the boundary
27
- follows custom domains. The `X-Ask-Local: 1` health header marks our
28
- proxy responses (including 404s) for the `ours?` probe.
29
- - Log rotation `proxy.log` and per-app backend logs rotate at 5MB
30
- (`ASK_LOCAL_LOG_MAX_BYTES`, one generation) before each write. A new
31
- `doctor` disk-usage check warns past 100MB of state.
34
+ - `ask-local sudoers` prints the scoped NOPASSWD rules so agents and
35
+ CI can install and run the 443 service without a TTY. Non-interactive
36
+ elevation uses `sudo -n` (never prompts) and points at the grant when
37
+ it is missing; interactive failures just say re-run.
38
+ - The root install syncs `/etc/hosts` under elevation, so Safari works
39
+ the moment setup finishes. `setup`'s hosts step verifies the block is
40
+ already present instead of failing unprivileged on re-runs, and
41
+ `ask-local hosts sync` re-runs itself elevated when the direct write
42
+ needs root.
43
+ - The CA is trusted into the System keychain silently while elevated
44
+ (all users, no GUI popup); unprivileged trust keeps the login
45
+ keychain.
46
+ - Progress UX: the root half of the install prints stage lines as it
47
+ works, and the proxy-start wait shows a rotating spinner on a terminal
48
+ (dots elsewhere), resolving to a clean "proxy is up" line.
32
49
 
33
- ### Fixed
50
+ ### Changed
34
51
 
35
- - **Silent `:1355` URL fallback removed.** Privileged-port (443) bind
36
- failure is now a hard error pointing at `ask-local setup`, never a
37
- booted app on `https://app.localhost:1355` that silently corrupts
38
- downstream consumers of `ASK_LOCAL_URL`. The only port-suffixed URLs
39
- are the ones you explicitly ask for (`proxy start -p 1355`).
40
- - **Health probe `130+?` hang fixed.** The TLS probe's `connect` sat
41
- outside the timeout: a TLS handshake against a foreign plain-HTTP
42
- server blocked in `connect` for 60s+. Connect is now inside the
43
- timeout, plain HTTP is tried first (our proxy answers plain HTTP via
44
- byte-peeking even on the TLS port), and any HTTP response without our
45
- header short-circuits as foreign — only silent servers wait for the
46
- timeout.
47
- - `start` dispatch was missing from the dispatcher despite being in
48
- `SUBCOMMANDS`, so `ask-local start` fell through to `run_named` with
49
- "start" as an app name. The kamal-help append drifted to a 6-space
50
- indent. Both are fixed and pinned by tests.
51
- - `base64` declared as a runtime dependency (it left the default gems in
52
- Ruby 3.4).
53
- - Missing `require "optparse"` lost in the CLI split.
52
+ - launchctl uses the modern system-domain verbs
53
+ (`bootstrap`/`bootout`/`enable`/`kickstart` the puma-dev/portless
54
+ pattern); the legacy `load`/`unload` are rejected by current macOS.
55
+ The best-effort pre-install bootout is silenced — its "Boot-out
56
+ failed: 5" noise on a first install looked like a failure.
57
+ - Every privileged execution path (sudo re-exec, launchctl, systemctl,
58
+ `security`) flows through one injectable `Command` seam, so the test
59
+ suite never shells out to real privileged commands.
54
60
 
55
- ### Tests — new coverage for this patch
61
+ ### Tests
56
62
 
57
- - `start_test.rb` help, fast-path vs. needs-setup branching, and the
58
- non-interactive hard-error message.
59
- - `setup_test.rb` four-step orchestration (all-steps-stubbed), first-failure
60
- abort with fix text, `--no-service` flag, and the three `ensure_proxy!`
61
- hard-error paths (non-interactive, foreign port, spawn failure) plus
62
- explicit-port URL honesty and responding-foreign-server fast classification.
63
- - Pinned under `bundle exec rake test` (fast unit suite); no `test:e2e`
64
- needed for these.
63
+ - Hermetic and CI-safe suite: no real sudo/security/launchctl in tests;
64
+ exact-argument expectations on the `Command` seam, source-pinned
65
+ regressions for the launchctl verbs, root-owned plist and stage
66
+ progress.
67
+ - New coverage: setup runs the real elevation path to "Setup complete"
68
+ (regression for the mid-flow exit), hosts-step write/verify/skip
69
+ behavior, `Hosts.synced?`, terminal spinner vs. off-tty dots, e2e
70
+ proof that a TLS daemon survives the plaintext probe, and both
71
+ elevation-failure hint paths. 182 unit + 23 e2e, all green.
65
72
 
66
- ## [0.2.0] — Unreleased
73
+
74
+ ## [0.2.0] — 2026-09-07
67
75
 
68
76
  ### Changed
69
77
 
@@ -129,6 +137,71 @@ Patch release focused on workstation setup, URL correctness, and proxy reliabili
129
137
  file checks now resolve against `destination_root`.
130
138
  - Port-flag injection no longer double-sets an explicit `$PORT`.
131
139
 
140
+
141
+ ## [0.1.1] — 2026-09-06
142
+
143
+ Patch release focused on workstation setup, URL correctness, and proxy reliability.
144
+
145
+ ### Added — `ask-local setup` & `ask-local start`
146
+
147
+ - `ask-local setup` — one-shot workstation setup for clean
148
+ `https://<app>.localhost` URLs: trust the local CA, serve port 443
149
+ (root launchd/systemd service when possible, sudo daemon otherwise),
150
+ sync `/etc/hosts`, and verify with `doctor`. Each step reports
151
+ `==>` / `ok` and the first failure aborts with the specific fix.
152
+ - `ask-local start` — one-setup-and-go entry point: an idempotent
153
+ workstation-check-then-boot (`ask-local setup` if needed, then the
154
+ app). `ask-local` bare is an alias for it; `ask-local setup` stays for
155
+ explicit re-setup.
156
+ - `askl` — shell-friendly alias binary (`bin/askl`, same entry point as
157
+ `bin/ask-local`). Keep `ask-local` in logs and docs so `grep` stays
158
+ useful.
159
+
160
+ ### Added — DNS-rebinding & log hygiene
161
+
162
+ - DNS-rebinding boundary: foreign `Host` headers get a bare 404 naming
163
+ nothing; only hosts under our own configured TLDs see the route-listing
164
+ 404. The proxy takes `--tld` (persisted to `proxy.tlds`) so the boundary
165
+ follows custom domains. The `X-Ask-Local: 1` health header marks our
166
+ proxy responses (including 404s) for the `ours?` probe.
167
+ - Log rotation — `proxy.log` and per-app backend logs rotate at 5MB
168
+ (`ASK_LOCAL_LOG_MAX_BYTES`, one generation) before each write. A new
169
+ `doctor` disk-usage check warns past 100MB of state.
170
+
171
+ ### Fixed
172
+
173
+ - **Silent `:1355` URL fallback removed.** Privileged-port (443) bind
174
+ failure is now a hard error pointing at `ask-local setup`, never a
175
+ booted app on `https://app.localhost:1355` that silently corrupts
176
+ downstream consumers of `ASK_LOCAL_URL`. The only port-suffixed URLs
177
+ are the ones you explicitly ask for (`proxy start -p 1355`).
178
+ - **Health probe `130+?` hang fixed.** The TLS probe's `connect` sat
179
+ outside the timeout: a TLS handshake against a foreign plain-HTTP
180
+ server blocked in `connect` for 60s+. Connect is now inside the
181
+ timeout, plain HTTP is tried first (our proxy answers plain HTTP via
182
+ byte-peeking even on the TLS port), and any HTTP response without our
183
+ header short-circuits as foreign — only silent servers wait for the
184
+ timeout.
185
+ - `start` dispatch was missing from the dispatcher despite being in
186
+ `SUBCOMMANDS`, so `ask-local start` fell through to `run_named` with
187
+ "start" as an app name. The kamal-help append drifted to a 6-space
188
+ indent. Both are fixed and pinned by tests.
189
+ - `base64` declared as a runtime dependency (it left the default gems in
190
+ Ruby 3.4).
191
+ - Missing `require "optparse"` lost in the CLI split.
192
+
193
+ ### Tests — new coverage for this patch
194
+
195
+ - `start_test.rb` — help, fast-path vs. needs-setup branching, and the
196
+ non-interactive hard-error message.
197
+ - `setup_test.rb` — four-step orchestration (all-steps-stubbed), first-failure
198
+ abort with fix text, `--no-service` flag, and the three `ensure_proxy!`
199
+ hard-error paths (non-interactive, foreign port, spawn failure) plus
200
+ explicit-port URL honesty and responding-foreign-server fast classification.
201
+ - Pinned under `bundle exec rake test` (fast unit suite); no `test:e2e`
202
+ needed for these.
203
+
204
+
132
205
  ## [0.1.0]
133
206
 
134
207
  ### Added
data/README.md CHANGED
@@ -28,14 +28,66 @@ The only port-suffixed URLs are the ones you explicitly ask for:
28
28
  `ask-local proxy start -p 1355` (CI/sandboxes where 443 is impossible).
29
29
  There the suffix is honest, and `ASK_LOCAL_URL` carries it faithfully.
30
30
 
31
- ## How it works
31
+ ## Port 443: one-time setup, then never again
32
32
 
33
- 1. `ask-local` infers your app name (Rails module, gemspec,
34
- `package.json`, git root, or directory) and boots itmanaged Rack
35
- apps on a unix socket (zero TCP ports), anything else via `PORT`.
36
- 2. Registers `hostname -> backend -> pid` in `~/.ask-local/routes.json`.
37
- 3. The reverse proxy (HTTPS on 443, per-host certs from a local CA)
38
- routes by `Host` header to your app.
33
+ Binding 443 is privileged, so ask-local installs a **root-owned launchd
34
+ service** (macOS) or systemd unit (Linux) that binds 443 at boot the
35
+ same model as puma-dev and portless. Installing it needs sudo **once per
36
+ machine**; after that, every `ask-local` run in any project gets a clean
37
+ `https://<app>.localhost` with no elevation and no prompt.
38
+
39
+ **Human (interactive):** run setup once — it trusts the CA, installs the
40
+ service, syncs hosts, and verifies:
41
+
42
+ ```bash
43
+ ask-local setup
44
+ ```
45
+
46
+ **Agent / CI (no TTY):** the same commands fail fast with guidance,
47
+ because sudo needs a terminal. To pre-provision a machine or image so
48
+ agents can install the service without a prompt, install the scoped
49
+ passwordless-sudo rules once (as an admin):
50
+
51
+ ```bash
52
+ ask-local sudoers > /tmp/ask-local.sudoers
53
+ sudo install -o root -g wheel -m 440 /tmp/ask-local.sudoers /etc/sudoers.d/ask-local # macOS
54
+ sudo install -o root -g root -m 440 /tmp/ask-local.sudoers /etc/sudoers.d/ask-local # Linux
55
+ ```
56
+
57
+ `ask-local sudoers` prints rules scoped to ask-local's own service
58
+ re-exec — the gem's exact ruby + bin path with the `service install
59
+ --internal` / `service uninstall --internal` subcommands — never a bare
60
+ interpreter. Re-run it after upgrading the gem if the install path
61
+ changes. To undo: `sudo rm /etc/sudoers.d/ask-local`.
62
+
63
+ ## The one-file model
64
+
65
+ Every app declares `config/local.yml` (Kamal-style) — the single source
66
+ of truth for service name, proxy TLD/host, processes, and env:
67
+
68
+ ```yaml
69
+ service: myapp
70
+ proxy:
71
+ tld: localhost
72
+ processes:
73
+ web:
74
+ cmd: bundle exec puma -b tcp://127.0.0.1:$PORT config.ru
75
+ proxy: true
76
+ worker:
77
+ cmd: bundle exec sidekiq
78
+ proxy: false
79
+ ```
80
+
81
+ `ask-local init` creates the file (migrating an existing Procfile);
82
+ Rails apps need no extra gem — ask-local injects `RAILS_DEVELOPMENT_HOSTS`
83
+ so the proxied hostname is allowed automatically. `ask-local`
84
+ then boots every process, assigns each a `$PORT`, injects
85
+ `ASK_LOCAL_URL`, registers routes for HTTP processes, supervises the
86
+ whole tree, and cleans up when one exits.
87
+
88
+ Variants are file overlays: `config/local.<variant>.yml` deep-merges on
89
+ top of `config/local.yml`, selected by `ASK_LOCAL_VARIANT` (Kamal's
90
+ destination pattern).
39
91
 
40
92
  `.localhost` resolves to loopback natively in Chrome, Firefox, and Edge —
41
93
  no DNS server, no `/etc/resolver`. Safari may need `ask-local hosts sync`.
@@ -110,7 +162,7 @@ Procfile lines that are compound (`&&`, `||`, `|`, `;`) are refused with
110
162
  guidance rather than silently mis-injected.
111
163
 
112
164
  For Rails integration (hosts, Action Cable origins, Procfile rewrite,
113
- generators), see `ask-local-rails`.
165
+ generators) deprecated; core covers Rails now.
114
166
 
115
167
  ## WebSockets
116
168
 
@@ -149,7 +201,7 @@ Run-mode (TCP) routes and static aliases are never supervised.
149
201
 
150
202
  | Gem | How ask-local helps |
151
203
  |---|---|
152
- | `ask-rails` | `ask_local:install` equivalent wiring: `config.hosts` patterns, `allowed_request_origins` for Cable, mailer/OmniAuth hosts from `ASK_LOCAL_URL` |
204
+ | `ask-rails` | ask-local core injects `RAILS_DEVELOPMENT_HOSTS`; Cable origins + helpers live in the deprecated ask-local-rails |
153
205
  | `ask-rails-harness` | Its 9 Rails tools (routes, models, DB, logs) run against the app the proxy serves; `DevUrl` gives the agent the stable URL instead of a guessed port |
154
206
  | `ask-app-server` | The JSON-RPC/stdio session host sits behind `https://api.<app>.localhost`; editor/IDE clients use `ask-local get` output |
155
207
  | `ask-mcp` | MCP servers get named URLs per service (`mcp.<app>.localhost`), no port coordination across servers |