Skip to content

Commit 57fb850

Browse files
committed
[server] Delete the Python server
`hark serve` has been the running service since the previous commit, against the same whisper backend, serving both machines. src/hark/ is no longer what runs, so it goes. Removed: src/hark/ (app, config, sanitize, whisper, audio, plists), the six pytest modules that exercised it, launchd/*.plist.template, the FastAPI/uvicorn/httpx dependencies, and the wheel packaging. pyproject is now a test harness rather than a package: nothing imports hark, and the suite drives install-server.sh and install-client.sh as subprocesses, which is what a user actually runs. WHAT WAS PORTED FIRST, rather than dropped with it: test_launchd_config_sync.py was the drift guard — the plists are what launchd runs, and a wrong one shows up only as a restart loop and a spawn error in a log nobody is watching. Its invariants now apply to the shell renderer: both plists render, no placeholder survives, ports match config, whisper never leaves loopback, the plist points at the installed bundle rather than the build tree, no WorkingDirectory, and a wildcard bind is refused before anything is written. Rendering moved into render_plists() above the source guard so the tests can exercise it — the first attempt defined it below and every test got "command not found". TWO DELIBERATE DIFFERENCES, both recorded as tests: The server's plist now carries no address at all, because `hark serve` reads config.toml directly. uvicorn needed --host baked in, which is precisely the two-copies-of-one-fact the drift guard existed to police. `bind = ""` falls back to loopback instead of being refused. In Python that value went straight to a socket API where empty spells the wildcard; here it never reaches one, and defaulting to the safe end beats refusing. Verified before committing: the running service is untouched by the deletion and still answers 200. Solves: hark #2 — retiring the Python server, step 3 of 3 Tests: 66 pytest (27 for the server installer, drift guard included), 53 SwiftPM, shellcheck
1 parent 5104956 commit 57fb850

23 files changed

Lines changed: 228 additions & 1747 deletions

README.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,20 @@ re-renders and reloads.
114114

115115
`./install-server.sh --doctor` re-runs the checks alone, read-only.
116116

117-
The plists are **rendered from templates** in `launchd/`, never edited by
118-
hand, because launchd reads its own XML and cannot see `config.py` — so the
119-
two drift silently. `tests/test_launchd_config_sync.py` renders the templates
120-
and asserts they agree with config, including that the ASR server is never
121-
bound off loopback.
117+
The plists are **rendered by `install-server.sh`**, never edited by hand.
118+
`tests/test_install_server_doctor.py` renders them against a fabricated config
119+
and asserts they agree with it — including that the ASR server is never bound
120+
off loopback.
122121

123-
A wildcard bind is refused by `hark.plists` itself, not only by the test
124-
suite — `install-server.sh` stops rather than installing a plist that listens
125-
on every interface. Any other address is accepted, since the two-machine setup
126-
binds to a private one on purpose.
122+
The server's own plist carries **no address at all**: `hark serve` reads
123+
`~/.config/hark/config.toml` directly, so there is one copy of that fact rather
124+
than two that can disagree. (`uvicorn` needed `--host` baked into the plist,
125+
which is what the old drift guard existed to police.)
126+
127+
A wildcard bind is refused twice: by `install-server.sh` before a plist is
128+
written, and by `hark serve` at startup. The second is the real enforcement;
129+
the first is what turns a launchd crash-loop into a message. Any other address
130+
is accepted, since the two-machine setup binds to a private one on purpose.
127131

128132
The shared secret lives at `~/.config/hark/key` (mode 600), outside the repo.
129133

@@ -441,13 +445,11 @@ install-server.sh transcription side: deps, model, plists, services
441445
install-client.sh builds + installs the agent, plus --doctor
442446
swift/
443447
Sources/hark/ the agent — hotkey, capture, paste, overlay
444-
Sources/HarkCore/ config, client, WAV, sanitise, server
448+
Sources/HarkCore/ config, client, WAV, sanitise, the HTTP server
445449
Packaging/build-app.sh assembles and signs Hark.app
446450
Tests/ SwiftPM suite (48)
447451
config.example.toml shape of ~/.config/hark/config.toml
448-
src/hark/ the Python HTTP service (still the one in use)
449-
launchd/ plist templates, rendered by hark.plists
450-
tests/ pytest suite
452+
tests/ pytest suite — drives the installers as subprocesses
451453
.github/workflows/ci.yml pytest + shellcheck + the signed bundle build
452454
docs/ design specs
453455
```

install-server.sh

Lines changed: 70 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,75 @@ run_doctor() {
241241

242242
# Sourcing this file defines the check_* functions and stops here, so the test
243243
# suite can exercise them without running an install. Everything below this
244+
render_plists() {
245+
log "Rendering launchd plists from config..."
246+
mkdir -p "$LAUNCH_AGENTS"
247+
248+
BIND="$(config_value server bind 127.0.0.1)"
249+
# Refused here as well as in `hark serve`. The server exits with an
250+
# explanation, but launchd answers that with a crash loop, so catching it at
251+
# render is the difference between a message and a restart storm.
252+
case "$(printf '%s' "$BIND" | tr -d '[:space:]')" in
253+
0.0.0.0|::|"")
254+
err "server.bind is \"${BIND}\", which listens on every network interface."
255+
err "hark's response is pasted into whatever has focus, so this lets anyone"
256+
err "who can reach this machine choose what gets typed."
257+
err "Use 127.0.0.1, or this machine's private (tailnet/VPN/LAN) address."
258+
return 1
259+
;;
260+
esac
261+
PORT="$(config_value server port 8911)"
262+
WHISPER_PORT="$(config_value whisper port 8910)"
263+
WHISPER_BIN="$(command -v whisper-server || echo /opt/homebrew/bin/whisper-server)"
264+
265+
cat > "$LAUNCH_AGENTS/com.drycodeworks.hark.plist" <<PLIST
266+
<?xml version="1.0" encoding="UTF-8"?>
267+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
268+
<plist version="1.0">
269+
<dict>
270+
<key>Label</key><string>com.drycodeworks.hark</string>
271+
<key>ProgramArguments</key>
272+
<array>
273+
<string>${APP_DST}/Contents/MacOS/hark</string>
274+
<string>serve</string>
275+
</array>
276+
<key>RunAtLoad</key><true/>
277+
<key>KeepAlive</key><true/>
278+
<key>StandardOutPath</key><string>/tmp/hark.log</string>
279+
<key>StandardErrorPath</key><string>/tmp/hark.err</string>
280+
</dict>
281+
</plist>
282+
PLIST
283+
284+
cat > "$LAUNCH_AGENTS/com.drycodeworks.hark-whisper.plist" <<PLIST
285+
<?xml version="1.0" encoding="UTF-8"?>
286+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
287+
<plist version="1.0">
288+
<dict>
289+
<key>Label</key><string>com.drycodeworks.hark-whisper</string>
290+
<key>ProgramArguments</key>
291+
<array>
292+
<string>${WHISPER_BIN}</string>
293+
<string>--model</string>
294+
<string>${MODEL_PATH}</string>
295+
<string>--host</string>
296+
<string>127.0.0.1</string>
297+
<string>--port</string>
298+
<string>${WHISPER_PORT}</string>
299+
<string>--language</string>
300+
<string>en</string>
301+
</array>
302+
<key>RunAtLoad</key><true/>
303+
<key>KeepAlive</key><true/>
304+
<key>StandardOutPath</key><string>/tmp/hark-whisper.log</string>
305+
<key>StandardErrorPath</key><string>/tmp/hark-whisper.err</string>
306+
</dict>
307+
</plist>
308+
PLIST
309+
310+
log "Rendered both plists (hark: ${BIND}:${PORT}, whisper: 127.0.0.1:${WHISPER_PORT})"
311+
}
312+
244313
# line only runs when the script is executed directly.
245314
if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
246315
return 0
@@ -353,60 +422,7 @@ fi
353422
# all. The wildcard-bind check is enforced by `hark serve` itself at startup —
354423
# it refuses 0.0.0.0 with an explanation — so this does not re-implement it.
355424

356-
log "Rendering launchd plists from config..."
357-
mkdir -p "$LAUNCH_AGENTS"
358-
359-
BIND="$(config_value server bind 127.0.0.1)"
360-
PORT="$(config_value server port 8911)"
361-
WHISPER_PORT="$(config_value whisper port 8910)"
362-
WHISPER_BIN="$(command -v whisper-server || echo /opt/homebrew/bin/whisper-server)"
363-
364-
cat > "$LAUNCH_AGENTS/com.drycodeworks.hark.plist" <<PLIST
365-
<?xml version="1.0" encoding="UTF-8"?>
366-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
367-
<plist version="1.0">
368-
<dict>
369-
<key>Label</key><string>com.drycodeworks.hark</string>
370-
<key>ProgramArguments</key>
371-
<array>
372-
<string>${APP_DST}/Contents/MacOS/hark</string>
373-
<string>serve</string>
374-
</array>
375-
<key>RunAtLoad</key><true/>
376-
<key>KeepAlive</key><true/>
377-
<key>StandardOutPath</key><string>/tmp/hark.log</string>
378-
<key>StandardErrorPath</key><string>/tmp/hark.err</string>
379-
</dict>
380-
</plist>
381-
PLIST
382-
383-
cat > "$LAUNCH_AGENTS/com.drycodeworks.hark-whisper.plist" <<PLIST
384-
<?xml version="1.0" encoding="UTF-8"?>
385-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
386-
<plist version="1.0">
387-
<dict>
388-
<key>Label</key><string>com.drycodeworks.hark-whisper</string>
389-
<key>ProgramArguments</key>
390-
<array>
391-
<string>${WHISPER_BIN}</string>
392-
<string>--model</string>
393-
<string>${MODEL_PATH}</string>
394-
<string>--host</string>
395-
<string>127.0.0.1</string>
396-
<string>--port</string>
397-
<string>${WHISPER_PORT}</string>
398-
<string>--language</string>
399-
<string>en</string>
400-
</array>
401-
<key>RunAtLoad</key><true/>
402-
<key>KeepAlive</key><true/>
403-
<key>StandardOutPath</key><string>/tmp/hark-whisper.log</string>
404-
<key>StandardErrorPath</key><string>/tmp/hark-whisper.err</string>
405-
</dict>
406-
</plist>
407-
PLIST
408-
409-
log "Rendered both plists (hark: ${BIND}:${PORT}, whisper: 127.0.0.1:${WHISPER_PORT})"
425+
render_plists
410426

411427
# --- 6. Load the services -----------------------------------------------------
412428

launchd/com.drycodeworks.hark-whisper.plist.template

Lines changed: 0 additions & 32 deletions
This file was deleted.

launchd/com.drycodeworks.hark.plist.template

Lines changed: 0 additions & 29 deletions
This file was deleted.

pyproject.toml

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,12 @@
22
name = "hark"
33
version = "0.1.0"
44
requires-python = ">=3.12"
5-
dependencies = [
6-
"fastapi>=0.115",
7-
"uvicorn>=0.32",
8-
"httpx>=0.27",
9-
]
5+
dependencies = []
106

117
[dependency-groups]
12-
dev = ["pytest>=8.3", "pytest-asyncio>=0.24", "respx>=0.21"]
13-
14-
[build-system]
15-
requires = ["hatchling"]
16-
build-backend = "hatchling.build"
17-
18-
[tool.hatch.build.targets.wheel]
19-
packages = ["src/hark"]
8+
dev = ["pytest>=8.3"]
209

2110
[tool.pytest.ini_options]
22-
pythonpath = ["src"]
23-
asyncio_mode = "auto"
11+
# No pythonpath and no asyncio: nothing here imports a package any more. The
12+
# suite drives install-server.sh and install-client.sh as subprocesses, which
13+
# is what a user actually runs.

src/hark/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)