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 +4 -4
- data/CHANGELOG.md +127 -54
- data/README.md +61 -9
- data/lib/ask/local/cli/boot.rb +145 -174
- data/lib/ask/local/cli/routes.rb +17 -13
- data/lib/ask/local/cli/system.rb +332 -70
- data/lib/ask/local/cli.rb +4 -2
- data/lib/ask/local/command.rb +30 -0
- data/lib/ask/local/config.rb +342 -59
- data/lib/ask/local/hosts.rb +8 -0
- data/lib/ask/local/ownership.rb +0 -7
- data/lib/ask/local/procfile.rb +139 -0
- data/lib/ask/local/proxy.rb +12 -1
- data/lib/ask/local/resolver.rb +62 -94
- data/lib/ask/local/runner.rb +8 -3
- data/lib/ask/local/trust.rb +21 -11
- data/lib/ask/local/version.rb +1 -1
- data/lib/ask/skills/ask-local/SKILL.md +57 -41
- data/lib/ask-local.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4407cbb75f7a6a5347fd2b71963a4229dc925c7d36438ae885c25c04759c94a6
|
|
4
|
+
data.tar.gz: 3ac24692630768264da89e9ce37e75403cbb3e2686ce1c18d6b1a99483d9c85d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0419a2e0b1b62733f3687fffc1eafee535380aaa636a04d77814fc229b92521369fefe7b394f73533686b85a4c5975bc6e3837d985b3a387efff9b43dcd4f6d9'
|
|
7
|
+
data.tar.gz: b88ce440466e46a9c47af5a6ac4072d4ffc4ef774b17c4e07e531c4f9698b284edd364ec09d3467d05caa31eebadcd29a93796d7f01e400df4ea97787670a306
|
data/CHANGELOG.md
CHANGED
|
@@ -1,69 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [0.
|
|
3
|
+
## [0.2.1] — 2026-09-08
|
|
4
4
|
|
|
5
|
-
Patch release focused on
|
|
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
|
-
###
|
|
10
|
+
### Fixed
|
|
8
11
|
|
|
9
|
-
-
|
|
10
|
-
`
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 outright — it 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
|
|
32
|
+
### Added
|
|
23
33
|
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
`
|
|
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
|
-
###
|
|
50
|
+
### Changed
|
|
34
51
|
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
61
|
+
### Tests
|
|
56
62
|
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
31
|
+
## Port 443: one-time setup, then never again
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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)
|
|
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` |
|
|
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 |
|