@tsconfigdotjson/vibe-os 0.1.0 → 0.2.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.
- package/README.md +112 -52
- package/dist/web/assets/index-B77Qk8B5.js.gz +0 -0
- package/dist/web/assets/index-CH5Em-Mo.css.gz +0 -0
- package/dist/web/ssh.wasm.gz +0 -0
- package/dist/web/wasm_exec.js.gz +0 -0
- package/package.json +1 -1
- package/server/assets.generated.ts +1 -1
- package/server/cli.ts +14 -0
- package/server/config.ts +24 -1
- package/server/doctor.ts +26 -15
- package/server/harness.ts +1 -0
- package/server/setup.ts +1151 -0
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ edit, no `sshd_config` change, and no root.
|
|
|
46
46
|
- [Deploying on a VPS](#deploying-on-a-vps)
|
|
47
47
|
- [What the box needs](#what-the-box-needs)
|
|
48
48
|
- [Install](#install)
|
|
49
|
+
- [Setup](#setup)
|
|
49
50
|
- [Behind Tailscale](#behind-tailscale)
|
|
50
51
|
- [Firewall](#firewall)
|
|
51
52
|
- [Running it as a service](#running-it-as-a-service)
|
|
@@ -55,6 +56,8 @@ edit, no `sshd_config` change, and no root.
|
|
|
55
56
|
- [Security](#security)
|
|
56
57
|
- [CLI reference](#cli-reference)
|
|
57
58
|
- [Development](#development)
|
|
59
|
+
- [Building from source](#building-from-source)
|
|
60
|
+
- [Releasing](#releasing)
|
|
58
61
|
- [Project status](#project-status)
|
|
59
62
|
- [Contributing](#contributing)
|
|
60
63
|
- [License](#license)
|
|
@@ -66,50 +69,31 @@ edit, no `sshd_config` change, and no root.
|
|
|
66
69
|
### Try it locally
|
|
67
70
|
|
|
68
71
|
```bash
|
|
69
|
-
docker
|
|
72
|
+
docker run --rm -p 127.0.0.1:8080:80 -e VIBE_OS_NO_TOKEN=1 ghcr.io/tsconfigdotjson/vibe-os
|
|
70
73
|
open http://localhost:8080
|
|
71
74
|
```
|
|
72
75
|
|
|
73
|
-
A blank Debian box with sshd, dtach and git,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
### Or
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
>
|
|
94
|
-
> before continuing. Do not skip this.
|
|
95
|
-
> 6. Install the service: `sudo vibe-os install-service --port 7681 --host
|
|
96
|
-
> 127.0.0.1 --token`. Run that with sudo from the user account, not as root.
|
|
97
|
-
> 7. Put HTTPS in front: `sudo tailscale serve --bg 7681`.
|
|
98
|
-
> 8. Close the box down with ufw: allow in on `tailscale0`, allow `41641/udp`,
|
|
99
|
-
> default deny incoming, then enable. Confirm you can still reach the box
|
|
100
|
-
> over the tailnet in a second session before enabling it.
|
|
101
|
-
> 9. Turn off SSH password authentication by writing
|
|
102
|
-
> `PasswordAuthentication no` to `/etc/ssh/sshd_config.d/01-hardening.conf`.
|
|
103
|
-
> That file has to sort before `50-cloud-init.conf`, which sets it to yes.
|
|
104
|
-
> Run `sshd -t` before reloading.
|
|
105
|
-
> 10. Run `sudo vibe-os doctor` one more time and paste the output, along with
|
|
106
|
-
> the URL and token from `journalctl -u vibe-os`.
|
|
107
|
-
>
|
|
108
|
-
> Tell me what you are about to run before you run anything that opens a port
|
|
109
|
-
> or changes sshd.
|
|
110
|
-
|
|
111
|
-
Read what it proposes before you let it run. Step 8 can lock you out of the box
|
|
112
|
-
if the tailnet is not actually working yet.
|
|
76
|
+
A blank Debian box with sshd, dtach and git, the same shape as a fresh VPS. The
|
|
77
|
+
token is off, so the port is on loopback only: a vibe-os with no token is a
|
|
78
|
+
shell for anyone who can reach it. The image has no harness, so profiles open a
|
|
79
|
+
shell; from a clone, `docker compose up --build` adds Claude Code.
|
|
80
|
+
|
|
81
|
+
### Or set up a VPS
|
|
82
|
+
|
|
83
|
+
On a fresh Debian or Ubuntu box, as any user with sudo (or as root, which
|
|
84
|
+
creates one):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
curl -fsSL https://raw.githubusercontent.com/tsconfigdotjson/vibe-os/main/install.sh | sh
|
|
88
|
+
vibe-os setup
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`setup` runs the same checks as `doctor` and offers to fix each one, printing
|
|
92
|
+
every command before it runs it. See [Setup](#setup) for what it does.
|
|
93
|
+
|
|
94
|
+
For an agent with SSH access to the box, the prompt is one line:
|
|
95
|
+
|
|
96
|
+
> Install vibe-os with `curl -fsSL https://raw.githubusercontent.com/tsconfigdotjson/vibe-os/main/install.sh | sh`, then run `vibe-os setup --yes` and show me its output.
|
|
113
97
|
|
|
114
98
|
---
|
|
115
99
|
|
|
@@ -418,11 +402,14 @@ invocation, so without it a profile opens a shell and does nothing else.
|
|
|
418
402
|
### Install
|
|
419
403
|
|
|
420
404
|
```bash
|
|
421
|
-
|
|
422
|
-
scp dist/bin/vibe-os-linux-x64 you@host:/usr/local/bin/vibe-os
|
|
423
|
-
ssh you@host 'chmod +x /usr/local/bin/vibe-os'
|
|
405
|
+
curl -fsSL https://raw.githubusercontent.com/tsconfigdotjson/vibe-os/main/install.sh | sh
|
|
424
406
|
```
|
|
425
407
|
|
|
408
|
+
It installs the latest [release](https://github.com/tsconfigdotjson/vibe-os/releases)
|
|
409
|
+
for Linux x64, Linux arm64 or Apple Silicon to `/usr/local/bin` (or
|
|
410
|
+
`~/.local/bin` without sudo), after checking its SHA-256. Set `VIBE_OS_VERSION`
|
|
411
|
+
to pin a tag, or `VIBE_OS_INSTALL_DIR` to choose the directory.
|
|
412
|
+
|
|
426
413
|
The binary carries the whole app, including the 20MB SSH WASM runtime. No Bun,
|
|
427
414
|
Node or npm on the target.
|
|
428
415
|
|
|
@@ -446,6 +433,53 @@ sudo vibe-os doctor # adds the sshd and firewall checks
|
|
|
446
433
|
`doctor` evaluates the flags it is given and cannot see a systemd unit, so pass
|
|
447
434
|
the same flags the service uses to get a true answer.
|
|
448
435
|
|
|
436
|
+
### Setup
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
vibe-os setup
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Each step checks first and skips itself when there is nothing to do, so running
|
|
443
|
+
it again on a configured box changes nothing. In order:
|
|
444
|
+
|
|
445
|
+
1. **User.** As root, it offers to create a user with passwordless sudo and
|
|
446
|
+
root's SSH keys, then stops so you can log in as them and run it again.
|
|
447
|
+
2. **Prerequisites.** `openssh-server`, `openssh-client`, `dtach`, `git`,
|
|
448
|
+
`curl` and optionally `gh`, through apt, dnf, yum, pacman or zypper.
|
|
449
|
+
3. **sshd**, enabled and started if nothing answers on port 22.
|
|
450
|
+
4. **Harnesses.** Claude Code, then optionally Hermes and Cursor.
|
|
451
|
+
5. **Tailscale**, installed and brought up (it prints a login URL).
|
|
452
|
+
6. **The service**, on `127.0.0.1:7681` with a generated token kept in
|
|
453
|
+
`~/.vibe-os/config.json`. An installed unit is left alone unless you pass a
|
|
454
|
+
different `--port` or `--host`.
|
|
455
|
+
7. **`tailscale serve --bg 7681`**, for HTTPS on the tailnet.
|
|
456
|
+
8. **SSH hardening.** Writes `01-hardening.conf` to turn off password logins,
|
|
457
|
+
runs `sshd -t` and reloads. Skipped if your user has no SSH key yet.
|
|
458
|
+
9. **Firewall**, with ufw: allow the tailnet and `41641/udp`, deny the rest.
|
|
459
|
+
10. **`doctor`**, run with sudo, then the URL with `?token=`.
|
|
460
|
+
|
|
461
|
+
The firewall step can lock you out, so before enabling ufw it arms a systemd
|
|
462
|
+
timer that disables it again in five minutes. Setup then waits for you to
|
|
463
|
+
confirm from a **new** session over the tailnet:
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
ssh <user>@<machine>.<tailnet>.ts.net vibe-os confirm-firewall
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
If that never arrives, ufw turns itself off. Your provider's firewall is
|
|
470
|
+
separate; leave `41641/udp` open there.
|
|
471
|
+
|
|
472
|
+
| flag | |
|
|
473
|
+
| --- | --- |
|
|
474
|
+
| `-y`, `--yes` | take the default answer to every question |
|
|
475
|
+
| `--firewall` | with `--yes`, do the firewall step too (it is skipped otherwise) |
|
|
476
|
+
| `--hermes`, `--cursor` | install those harnesses too |
|
|
477
|
+
| `--user <name>` | as root, the user to create |
|
|
478
|
+
| `--port`, `--host` | where the service listens (default `7681`, `127.0.0.1`) |
|
|
479
|
+
|
|
480
|
+
With `--yes`, an agent runs the firewall confirmation from a second SSH
|
|
481
|
+
connection while setup waits in the first.
|
|
482
|
+
|
|
449
483
|
### Behind Tailscale
|
|
450
484
|
|
|
451
485
|
The recommended setup. You stop needing to bind port 80, and you stop needing
|
|
@@ -681,12 +715,8 @@ accept that `gh` will not work.
|
|
|
681
715
|
### Updating a running box
|
|
682
716
|
|
|
683
717
|
```bash
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
ssh you@host 'sudo systemctl stop vibe-os \
|
|
687
|
-
&& sudo mv /tmp/vibe-os /usr/local/bin/vibe-os \
|
|
688
|
-
&& sudo chmod +x /usr/local/bin/vibe-os \
|
|
689
|
-
&& sudo systemctl start vibe-os'
|
|
718
|
+
curl -fsSL https://raw.githubusercontent.com/tsconfigdotjson/vibe-os/main/install.sh | sh
|
|
719
|
+
sudo systemctl restart vibe-os
|
|
690
720
|
```
|
|
691
721
|
|
|
692
722
|
Nothing is lost by that restart. Two things it does not pick up:
|
|
@@ -758,6 +788,8 @@ in a session could otherwise ask what you last copied.
|
|
|
758
788
|
| `vibe-os` / `vibe-os start` | serve the UI and the SSH bridge |
|
|
759
789
|
| `vibe-os attach [window]` | attach this terminal to a window's session |
|
|
760
790
|
| `vibe-os doctor` | check this machine is ready, and say what is missing |
|
|
791
|
+
| `vibe-os setup` | fix what `doctor` finds, asking before each change |
|
|
792
|
+
| `vibe-os confirm-firewall` | tell a waiting `setup` the tailnet still gets in |
|
|
761
793
|
| `sudo vibe-os install-service` | write and enable a systemd unit |
|
|
762
794
|
| `sudo vibe-os install-browser` | run one Chrome on a virtual display |
|
|
763
795
|
| `vibe-os connect-hermes` | point Hermes' browser tools at that Chrome |
|
|
@@ -810,6 +842,9 @@ in a session could otherwise ask what you last copied.
|
|
|
810
842
|
--no-vnc-password serve the display with no authentication
|
|
811
843
|
```
|
|
812
844
|
|
|
845
|
+
`setup` takes the [flags above](#setup) as well as `--port`, `--host`,
|
|
846
|
+
`--workspace` and `--state-dir`, which it passes to the service.
|
|
847
|
+
|
|
813
848
|
`connect-hermes` takes one:
|
|
814
849
|
|
|
815
850
|
```
|
|
@@ -854,6 +889,32 @@ step of its own.
|
|
|
854
889
|
its SHA-256 verified before extraction. To move it, set `SSHTERM_VERSION`, run
|
|
855
890
|
`bun run fetch-wasm`, and copy the checksum it prints into `PINNED`.
|
|
856
891
|
|
|
892
|
+
### Building from source
|
|
893
|
+
|
|
894
|
+
```bash
|
|
895
|
+
bun install && bun run build
|
|
896
|
+
bun scripts/compile.ts linux-x64 # or linux-arm64, darwin-arm64; none for all
|
|
897
|
+
scp dist/bin/vibe-os-linux-x64 you@host:/tmp/vibe-os
|
|
898
|
+
ssh you@host 'sudo install -m 0755 /tmp/vibe-os /usr/local/bin/vibe-os'
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
### Releasing
|
|
902
|
+
|
|
903
|
+
Bump `version` in `package.json`, merge, then tag the merge commit:
|
|
904
|
+
|
|
905
|
+
```bash
|
|
906
|
+
git tag v0.2.0 && git push origin v0.2.0
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
The tag must match `package.json`. The workflow publishes the binaries with
|
|
910
|
+
`SHA256SUMS` as a GitHub Release, pushes `ghcr.io/tsconfigdotjson/vibe-os`, and
|
|
911
|
+
publishes `@tsconfigdotjson/vibe-os` to npm with provenance, through npm's
|
|
912
|
+
[trusted publishing](https://docs.npmjs.com/trusted-publishers). A hyphenated
|
|
913
|
+
tag such as `v0.2.0-rc.1` is a prerelease: the installer skips it, and npm gets
|
|
914
|
+
it under the `next` dist-tag rather than `latest`.
|
|
915
|
+
|
|
916
|
+
### Icons
|
|
917
|
+
|
|
857
918
|
App icons are generated from `public/icon.svg`:
|
|
858
919
|
|
|
859
920
|
```bash
|
|
@@ -867,8 +928,7 @@ rsvg-convert -w 512 -h 512 public/icon-maskable.svg -o public/icon-maskable-512.
|
|
|
867
928
|
|
|
868
929
|
## Project status
|
|
869
930
|
|
|
870
|
-
Usable and in daily use, but young.
|
|
871
|
-
build one or run the container.
|
|
931
|
+
Usable and in daily use, but young.
|
|
872
932
|
|
|
873
933
|
Everything in [Deploying on a VPS](#deploying-on-a-vps) has been walked end to
|
|
874
934
|
end on a fresh OVHcloud VPS on Ubuntu 26.04, behind Tailscale, serving HTTPS.
|
|
Binary file
|
|
Binary file
|
package/dist/web/ssh.wasm.gz
CHANGED
|
Binary file
|
package/dist/web/wasm_exec.js.gz
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsconfigdotjson/vibe-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A coding desktop in the browser, on a box you own. Git worktrees per piece of work, terminals that survive a reload, and SSH that runs in the tab.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,7 @@ import a18 from "../dist/web/wasm_exec.js" with { type: 'file' };
|
|
|
26
26
|
import a19 from "../dist/web/wasm_exec.js.br" with { type: 'file' };
|
|
27
27
|
import a20 from "../dist/web/wasm_exec.js.gz" with { type: 'file' };
|
|
28
28
|
|
|
29
|
-
export const BUILD_ID = "
|
|
29
|
+
export const BUILD_ID = "4784e0916592";
|
|
30
30
|
|
|
31
31
|
export const EMBEDDED: Record<string, string> = {
|
|
32
32
|
"/apple-touch-icon.png": a0,
|
package/server/cli.ts
CHANGED
|
@@ -43,6 +43,7 @@ import { startServer } from "./index.ts";
|
|
|
43
43
|
import { color, describeError, log } from "./log.ts";
|
|
44
44
|
import { reapLeaks } from "./reaper.ts";
|
|
45
45
|
import { ENTRY, FETCH_WASM, IS_COMPILED } from "./runtime.ts";
|
|
46
|
+
import { confirmFirewall, runSetup } from "./setup.ts";
|
|
46
47
|
|
|
47
48
|
const run = promisify(execFile);
|
|
48
49
|
const BUN = process.execPath;
|
|
@@ -56,6 +57,7 @@ const HELP = `
|
|
|
56
57
|
vibe-os [start] serve the UI and the SSH bridge
|
|
57
58
|
vibe-os attach [window] attach a real terminal to a window's session
|
|
58
59
|
vibe-os doctor check this machine is ready
|
|
60
|
+
vibe-os setup fix what doctor finds, asking first
|
|
59
61
|
vibe-os install-service write and enable a systemd unit (needs root)
|
|
60
62
|
vibe-os install-browser run one Chrome on a virtual display (needs root)
|
|
61
63
|
vibe-os connect-hermes point Hermes' browser tools at that Chrome
|
|
@@ -113,6 +115,14 @@ const HELP = `
|
|
|
113
115
|
|
|
114
116
|
${color.bold("doctor")}
|
|
115
117
|
--reap kill processes that outlived their workspace or session
|
|
118
|
+
|
|
119
|
+
${color.bold("setup")}
|
|
120
|
+
-y, --yes take the default answer to every question
|
|
121
|
+
--firewall close the box to all but the tailnet with ufw; needed
|
|
122
|
+
with --yes, which otherwise leaves the firewall alone
|
|
123
|
+
--hermes install Hermes too (asked otherwise, default no)
|
|
124
|
+
--cursor install Cursor too (asked otherwise, default no)
|
|
125
|
+
--user <name> as root: the user to create and run vibe-os as
|
|
116
126
|
`;
|
|
117
127
|
|
|
118
128
|
function version(): string {
|
|
@@ -810,6 +820,10 @@ export async function main(argv: string[]): Promise<number> {
|
|
|
810
820
|
if (values.reap) await reapLeaks(config);
|
|
811
821
|
return printChecks(await runDoctor(config));
|
|
812
822
|
}
|
|
823
|
+
case "setup":
|
|
824
|
+
return runSetup(config, values);
|
|
825
|
+
case "confirm-firewall":
|
|
826
|
+
return confirmFirewall(config);
|
|
813
827
|
case "install-service":
|
|
814
828
|
return installService(config, argv);
|
|
815
829
|
case "install-browser":
|
package/server/config.ts
CHANGED
|
@@ -93,6 +93,11 @@ export const OPTION_SPEC = {
|
|
|
93
93
|
// doctor only, same reasoning as the install-browser block below: parseArgs
|
|
94
94
|
// is strict, so a flag it has never heard of is an error.
|
|
95
95
|
reap: { type: "boolean" as const },
|
|
96
|
+
// setup only, for the same reason.
|
|
97
|
+
yes: { type: "boolean" as const, short: "y" },
|
|
98
|
+
firewall: { type: "boolean" as const },
|
|
99
|
+
hermes: { type: "boolean" as const },
|
|
100
|
+
cursor: { type: "boolean" as const },
|
|
96
101
|
// install-browser only. Kept here because parseArgs is strict, and a flag it
|
|
97
102
|
// has never heard of is an error rather than something a subcommand can read.
|
|
98
103
|
geometry: { type: "string" as const },
|
|
@@ -151,6 +156,24 @@ export function parseCliArgs(argv: string[]): {
|
|
|
151
156
|
return { values: values as RawOptions, positionals };
|
|
152
157
|
}
|
|
153
158
|
|
|
159
|
+
/**
|
|
160
|
+
* The user this process runs as.
|
|
161
|
+
*
|
|
162
|
+
* Bun's `os.userInfo().username` comes from `$USER`, and reads "unknown" when
|
|
163
|
+
* that is unset, which `docker exec`, cron and some `su` invocations all do.
|
|
164
|
+
* `id` asks the passwd database for the real uid instead.
|
|
165
|
+
*/
|
|
166
|
+
async function currentUser(): Promise<string> {
|
|
167
|
+
const name = os.userInfo().username;
|
|
168
|
+
if (name && name !== "unknown") return name;
|
|
169
|
+
try {
|
|
170
|
+
const { stdout } = await run("id", ["-un"], { timeout: 5_000 });
|
|
171
|
+
return stdout.trim() || name;
|
|
172
|
+
} catch {
|
|
173
|
+
return name;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
154
177
|
async function hasDtach(): Promise<boolean> {
|
|
155
178
|
try {
|
|
156
179
|
// dtach exits non-zero with no mode, so its usage text is the liveness
|
|
@@ -334,7 +357,7 @@ export async function resolveConfig(values: RawOptions): Promise<Config> {
|
|
|
334
357
|
process.env.VIBE_OS_SSH_ADVERTISE ??
|
|
335
358
|
undefined,
|
|
336
359
|
user: userName(
|
|
337
|
-
values.user ?? process.env.VIBE_OS_USER ??
|
|
360
|
+
values.user ?? process.env.VIBE_OS_USER ?? (await currentUser()),
|
|
338
361
|
"user",
|
|
339
362
|
),
|
|
340
363
|
stateDir,
|
package/server/doctor.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from "./browser.ts";
|
|
27
27
|
import type { Config } from "./config.ts";
|
|
28
28
|
import {
|
|
29
|
+
CLAUDE_INSTALL,
|
|
29
30
|
CURSOR_INSTALL,
|
|
30
31
|
discoverCursor,
|
|
31
32
|
discoverHermes,
|
|
@@ -161,7 +162,7 @@ const fatal = (label: string, detail: string, fix?: string): Check => ({
|
|
|
161
162
|
fatal: true,
|
|
162
163
|
});
|
|
163
164
|
|
|
164
|
-
function probeTcp(
|
|
165
|
+
export function probeTcp(
|
|
165
166
|
host: string,
|
|
166
167
|
port: number,
|
|
167
168
|
timeout = 2000,
|
|
@@ -473,6 +474,25 @@ export async function homeFor(user: string): Promise<string | null> {
|
|
|
473
474
|
}
|
|
474
475
|
}
|
|
475
476
|
|
|
477
|
+
/**
|
|
478
|
+
* `sshd -T` output as a map.
|
|
479
|
+
*
|
|
480
|
+
* sshd -T lowercases keywords and prints repeated ones on their own lines;
|
|
481
|
+
* joining preserves multi-value keywords like allowusers.
|
|
482
|
+
*/
|
|
483
|
+
export function parseSshdConfig(stdout: string): Map<string, string> {
|
|
484
|
+
const map = new Map<string, string>();
|
|
485
|
+
for (const line of stdout.split("\n")) {
|
|
486
|
+
const trimmed = line.trim();
|
|
487
|
+
if (!trimmed) continue;
|
|
488
|
+
const space = trimmed.indexOf(" ");
|
|
489
|
+
const key = space === -1 ? trimmed : trimmed.slice(0, space);
|
|
490
|
+
const value = space === -1 ? "" : trimmed.slice(space + 1);
|
|
491
|
+
map.set(key, map.has(key) ? `${map.get(key)} ${value}` : value);
|
|
492
|
+
}
|
|
493
|
+
return map;
|
|
494
|
+
}
|
|
495
|
+
|
|
476
496
|
/**
|
|
477
497
|
* sshd's effective configuration, evaluated for the user who will log in.
|
|
478
498
|
*
|
|
@@ -491,18 +511,7 @@ async function sshdEffectiveConfig(
|
|
|
491
511
|
["-T", "-C", `user=${user},host=localhost,addr=127.0.0.1`],
|
|
492
512
|
{ timeout: 10_000, maxBuffer: 4 * 1024 * 1024 },
|
|
493
513
|
);
|
|
494
|
-
|
|
495
|
-
for (const line of stdout.split("\n")) {
|
|
496
|
-
const trimmed = line.trim();
|
|
497
|
-
if (!trimmed) continue;
|
|
498
|
-
const space = trimmed.indexOf(" ");
|
|
499
|
-
// sshd -T lowercases keywords and prints repeated ones on their own
|
|
500
|
-
// lines; joining preserves multi-value keywords like allowusers.
|
|
501
|
-
const key = space === -1 ? trimmed : trimmed.slice(0, space);
|
|
502
|
-
const value = space === -1 ? "" : trimmed.slice(space + 1);
|
|
503
|
-
map.set(key, map.has(key) ? `${map.get(key)} ${value}` : value);
|
|
504
|
-
}
|
|
505
|
-
return map;
|
|
514
|
+
return parseSshdConfig(stdout);
|
|
506
515
|
} catch {
|
|
507
516
|
// not this path, or not root — try the next
|
|
508
517
|
}
|
|
@@ -641,7 +650,7 @@ export async function runDoctor(config: Config): Promise<Check[]> {
|
|
|
641
650
|
: bad(
|
|
642
651
|
"claude",
|
|
643
652
|
"not on PATH — profiles using the Claude harness fall back to a shell",
|
|
644
|
-
|
|
653
|
+
CLAUDE_INSTALL,
|
|
645
654
|
),
|
|
646
655
|
);
|
|
647
656
|
|
|
@@ -671,8 +680,10 @@ export async function runDoctor(config: Config): Promise<Check[]> {
|
|
|
671
680
|
);
|
|
672
681
|
} else {
|
|
673
682
|
const mine = os.homedir();
|
|
683
|
+
// Root is checking on someone's behalf (`sudo vibe-os doctor`, or the end
|
|
684
|
+
// of setup), not running the server, so where its own home is says nothing.
|
|
674
685
|
checks.push(
|
|
675
|
-
home === mine
|
|
686
|
+
home === mine || process.getuid?.() === 0
|
|
676
687
|
? ok("login user", `${user} (${home})`)
|
|
677
688
|
: bad(
|
|
678
689
|
"login user",
|
package/server/harness.ts
CHANGED
|
@@ -110,6 +110,7 @@ const UVX_PATHS = [`${home}/.local/bin/uvx`, "/usr/local/bin/uvx"];
|
|
|
110
110
|
* one when there is nothing to configure. Both used to be about to grow their
|
|
111
111
|
* own copy of a URL that has to be right.
|
|
112
112
|
*/
|
|
113
|
+
export const CLAUDE_INSTALL = "curl -fsSL https://claude.ai/install.sh | bash";
|
|
113
114
|
export const HERMES_INSTALL =
|
|
114
115
|
"curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash";
|
|
115
116
|
export const UV_INSTALL = "curl -LsSf https://astral.sh/uv/install.sh | sh";
|