Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/hand/install/README.md

13.9 KiB, 1 run

created by r2519314175:901, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1# Installing Daimond's machine hand
2
3Daimond runs in a web page. A web page cannot start a program — there is no
4flag, no setting, and no future browser API that will change that. So if Daimond
5is to run a build or a test on your computer, the part that actually starts the
6program has to live outside the page, and something has to introduce the two.
7
8That something is your browser. Chrome will start a small program on an
9extension's behalf and connect the two by a pipe, but only if a file on this
10computer names both of them.
11
12**Two files, once.** One says which program the browser may start. The other
13says which folder that program may work in. Nothing listens on a port, nothing
14runs in the background, and there is no password to keep. That is the point of
15doing it this way: a background service on a port would be reachable by any page
16you visit, and its only defence would be a secret you had pasted somewhere. Here
17there is nothing to find and nothing to steal — the browser is the doorman.
18
19## Before you start: your browser must not be a snap or a flatpak
20
21A snap or flatpak browser **cannot run Machine Operations**, and this is worth
22checking first because everything below will otherwise appear to succeed.
23
24The confinement extends to the programs the browser starts. The hand is one of
25them, so it may only see the files in `$HOME` that are *not* hidden — and its
26journal is at `~/.local/share/daimond/hand/journal`, behind one that is. The hand
27exits before it can open the journal it would have used to say so, and the
28browser reports only "Native host has exited".
29
30`install.sh` finds these profiles and refuses them by name. The fix is a
31Chromium-family browser installed from a `.deb`. Moving the journal is not a fix:
32the browser hands the hand *its own* environment, so `DAIMOND_HAND_JOURNAL_DIR`
33never reaches it.
34
35## What you need
36
37- The **Daimond Hands** extension, in this repository at `ext/`.
38- A **folder** you are content for Daimond to work in. That folder bounds
39 everything any command can read or write, so not your home directory.
40- A **.deb** Chromium-family browser, started at least once — the profile
41 directory appears on first run, not when the package is installed.
42
43## Do this
44
45Two commands, from the top of the Daimond repository, then four things in the
46browser.
47
48### 1. Build it
49
50```sh
51cargo build --release --manifest-path hand/Cargo.toml
52```
53
54`--manifest-path`, not `-p`: the hand is its own cargo workspace, so `-p` fails.
55
56### 2. Grant a folder and register the hand
57
58```sh
59hand/install/install.sh --workspace ~/work
60```
61
62Replace `~/work` with the folder you chose; it must already exist. That one
63command creates the journal directory at mode `700`, writes your folder into
64`root.txt` beside it, and writes the host manifest into every usable browser
65profile it finds. It builds nothing, downloads nothing, starts nothing, and needs
66no root.
67
68Two things worth knowing rather than discovering:
69
70- **The hand never guesses the folder.** Without `root.txt` it refuses to serve a
71 page at all, because a guessed folder is a guess about what a command may
72 touch.
73- **`DAIMOND_HAND_ROOT` does the same job and is a trap.** It takes precedence,
74 and it will not work for a browser started from a desktop launcher, because
75 the browser hands the hand its own environment. Use the file.
76
77### 3. Load the extension, and restart the browser
78
79`install.sh` prints the path. At `chrome://extensions`, turn on Developer mode,
80click "Load unpacked", and choose `ext/`. Then restart the browser: it reads the
81registration only at startup.
82
83### 4. In Daimond, open the folder and allow the first command
84
85Both are decisions and neither is automated. The first time Daimond wants to run
86something a window opens and asks you; there is no browser permission for "may
87run programs on this computer", so that window **is** the approval, and the
88extension remembers your answer itself. Until you allow it, nothing runs.
89
90## When something is wrong, run this
91
92```sh
93hand/install/install.sh --check
94```
95
96One line per thing that has to be true — browser, registration, binary, journal
97directory, granted folder, fence, extension — and the fix printed under each that
98is not. It changes nothing. This is the first thing to run, before reading
99anything else here.
100
101Two more, both harmless:
102
103```sh
104hand/target/release/daimond-hand --report
105```
106
107prints what the fence can enforce on *this* kernel and, at greater length, what
108it cannot. Read the second list: on a kernel below Linux 7.1 it includes a way
109out of the fence entirely.
110
111```sh
112hand/target/release/daimond-hand < /dev/null
113```
114
115starts the hand the way your browser will. `the page closed the pipe` means it is
116configured and ready; anything else is a sentence naming the path, the cause and
117the fix.
118
119## What a command can actually reach
120
121Worth knowing before the first thing a daimon runs fails in a way that looks
122like a broken tool.
123
124A command runs inside a kernel fence, and the fence is built out of the folder
125you granted. On top of that the hand adds two things:
126
127- **The system paths a program needs in order to be a program**: `/usr`, `/bin`,
128 `/sbin`, `/lib*`, `/etc`, `/opt` and the harmless `/dev` devices, all
129 **read-only**. Without them nothing can start at all — not even `cat`.
130- **A private temporary directory**, writable, with `TMPDIR` pointing at it. It
131 is the one place outside your folder that a command may write, it is removed
132 when the run ends, and no other run can reach it.
133
134Everything else is refused by the kernel, not by a check somebody wrote. Reading
135a file one folder outside the grant comes back `Permission denied`, and the
136daimon is shown that refusal rather than the file.
137
138**The consequence for build tools.** A toolchain installed under your home
139directory — `~/.cargo`, `~/.rustup`, `~/.nvm`, a `node_modules` you keep
140elsewhere — is *not* reachable, and today there is no way to grant it: every
141path in the fence is built by joining a workspace-relative name onto the folder
142you granted, so nothing outside that folder can be named at all. A daimon can
143run anything under `/usr/bin` and anything inside your folder. If you want it to
144run `cargo test`, the toolchain has to be inside the folder you granted.
145
146## Variants
147
148### If you keep the binary somewhere else
149
150```sh
151hand/install/install.sh /path/to/daimond-hand
152```
153
154### If your browser keeps its profile somewhere unusual
155
156```sh
157hand/install/install.sh --dir /path/to/profile/NativeMessagingHosts /path/to/daimond-hand
158```
159
160A browser started with `--user-data-dir` reads `<that dir>/NativeMessagingHosts`
161and nothing else, which is what this is for.
162
163`--dir` writes into a snap or flatpak profile too, with the warning above, on the
164grounds that naming a directory explicitly means you know better. Expect the hand
165to disconnect on the first command.
166
167### To see what it would do, without doing it
168
169```sh
170hand/install/install.sh --list
171hand/install/install.sh --help
172```
173
174### To check the script itself
175
176```sh
177hand/install/install.sh --selftest
178```
179
180Builds throwaway home directories that look like a snap profile, a flatpak
181profile, a `.deb` profile and nothing at all, runs itself against each, and
182asserts what it says and its exit status. Nothing of yours is touched.
183
184## To take it back
185
186Three different things, and all three are worth knowing about.
187
188**Withdraw the permission.** Click the Daimond Hands icon in the toolbar.
189"Running commands on this computer" is listed there beside the sites you have
190approved, with a Revoke button. Revoking stops anything that is running at that
191moment — not at the end of the current build, immediately.
192
193**Take away the folder.** Delete `root.txt`. The hand then refuses to serve at
194all, whatever the browser says, and the refusal is a whole sentence rather than
195a silent nothing.
196
197**Remove the registration.**
198
199```sh
200hand/install/uninstall.sh # every browser found
201hand/install/uninstall.sh --dir DIR # one directory
202```
203
204That deletes the file `install.sh` wrote, so the browser can no longer start the
205hand at all. The binary is left where it is; the script did not put it there.
206
207## When it does not work
208
209Run `hand/install/install.sh --check` first. What follows is what each symptom
210usually turns out to be.
211
212**"Daimond's machine hand is not installed on this computer."** The browser found
213no file naming the host: `install.sh` has not been run, or was run for a
214different browser, or the browser has not been restarted since.
215
216**"The machine hand disconnected without finishing (Native host has exited.)"**
217on the *first* command, before anything could have crashed. The hand read its
218configuration, refused to serve, and exited. Two causes account for nearly all of
219it: a snap or flatpak browser, which cannot reach the journal at all; or no
220`root.txt`. `--check` distinguishes them. So does
221`hand/target/release/daimond-hand < /dev/null`, which prints the refusal as a
222sentence — the same sentence the browser puts in its own log, since Chrome
223captures a native host's standard error.
224
225**"…did not say which folder it was granted."** The hand answered but named no
226root: `root.txt` is missing, empty, or names a folder that does not exist.
227
228**"Installed but will not talk to this extension."** The file exists but names a
229different extension. This happens if you loaded the extension without the pinned
230key in `ext/manifest.json`, which gives it a different id. Find the real id at
231`chrome://extensions`, then:
232
233```sh
234DAIMOND_HAND_EXT_ID=<the id you see> hand/install/install.sh
235```
236
237**"Disconnected without finishing", mid-command.** The hand stopped part-way.
238Either it crashed, or it produced a single message over 1 MB, which Chrome
239silently refuses to deliver and answers by cutting the connection. The hand
240chunks its output well below that, so this should be a crash; its journal will
241say.
242
243**A command comes back `Permission denied` on a file you expected it to read.**
244That is the fence, working. See "What a command can actually reach".
245
246## The files here
247
248| File | What it is |
249|---|---|
250| `install.sh` | Writes the host manifest into each usable browser's directory, and with `--workspace` the journal directory and `root.txt` too. `--check` diagnoses an install; `--selftest` tests the script. It holds the table of browsers, so it is the one file to edit when a path changes. |
251| `uninstall.sh` | Removes the manifests again. Reads the directory list from `install.sh --paths` rather than keeping a copy, because a stale copy would silently forget the browsers it had not heard of. |
252| `com.oxedyne.daimond.hand.json` | The manifest, for reading, and for registering by hand on a platform the script does not cover yet. `install.sh` writes its own copy rather than editing this one, so the path and the extension id are decided in exactly one place. |
253| `mock_host.py` | A stand-in hand for testing, which speaks the real protocol and runs nothing. See below. |
254
255## Other platforms
256
257Linux is implemented. The others are not, and the paths are written down in
258`install.sh` rather than left to be rediscovered:
259
260- **macOS** uses the same file in a different directory
261 (`~/Library/Application Support/Google/Chrome/NativeMessagingHosts/`), and
262 additionally needs the binary signed and notarised before Gatekeeper will let
263 a browser run it. That is a packaging job, not a path.
264- **Windows** has no directory at all: the manifest is found through a registry
265 key under `HKCU\Software\Google\Chrome\NativeMessagingHosts\`, whose default
266 value is the absolute path to the JSON file, which may live anywhere.
267
268There is no fence on either yet, and the app refuses a command it cannot
269contain, so a hand on those platforms would install and then refuse everything.
270
271## Testing without the binary
272
273`mock_host.py` speaks the real framing — a 4-byte native-endian length prefix
274and UTF-8 JSON — and the real messages, and runs nothing at all. It exists
275because the failures worth testing are ones a correct hand never produces: a gap
276in the output sequence, a message over Chrome's 1 MB limit, and a host that dies
277mid-command.
278
279Register it like any other binary:
280
281```sh
282hand/install/install.sh hand/install/mock_host.py
283```
284
285It reads `mock_cfg.json`, beside it, if there is one — Chrome gives a native
286messaging host no arguments of its own, so a file is the only way in:
287
288```json
289{ "chunks": 3, "gap": false, "huge": false, "crash": false, "delay_ms": 0 }
290```
291
292and appends everything it sees and says to `mock_host.log`. Neither file belongs
293in a commit.
294
295## The three end-to-end tests
296
297Each needs nothing running, writes its host manifest into a throwaway browser
298profile of its own, and never touches yours. Headed, so under a virtual display:
299
300```sh
301xvfb-run -a -s "-screen 0 1400x900x24" node dev/verify_hand.mjs
302xvfb-run -a -s "-screen 0 1400x900x24" node dev/verify_handrun.mjs
303xvfb-run -a -s "-screen 0 1400x900x24" node dev/verify_handreal.mjs
304```
305
306| | Browser | Extension | Host | Command |
307|---|---|---|---|---|
308| `verify_hand` | real | real | **mock** | none |
309| `verify_handrun` | real | real | **mock** | none |
310| `verify_handreal` | real | real | **real** | **real** |
311
312The first two use the mock deliberately: they test the order of the output, a
313gap in it, the 1 MB disconnect, a crash mid-command, a page that goes away with
314a command running, the missing-host message, and revoking from the popup — and a
315correct hand does none of those things, so a correct hand cannot be used to test
316them. `verify_handrun` goes on to check what the *model* was shown, which is the
317only thing that matters about a tool result.
318
319`verify_handreal` is the one that proves the join. It builds the binary,
320registers it with `install.sh`, writes a `root.txt`, clicks Allow for real, and
321then has a daimon run real commands: it asserts that a nonce written to disk a
322moment earlier comes back through the model, that a real non-zero exit arrives as
323non-zero, that a file outside the fence is refused *by the kernel* and its
324contents never reach the model, that the journal on disk names what was run, and
325that a real `cargo test` compiles and runs inside the fence. It takes about
326twelve seconds after the build.