diff --git a/internal/cli/client.go b/internal/cli/client.go index de3c6531..f48ca5f0 100644 --- a/internal/cli/client.go +++ b/internal/cli/client.go @@ -66,10 +66,23 @@ type Client struct { // web UI's address (only `whr github app create` uses it): the API is on a unix // socket in the state directory (D29, §7.5). type ClientConfig struct { - Listen string `json:"listen"` - APITokenFile string `json:"api_token_file"` - StateDir string `json:"state_dir"` - PublicURL string `json:"public_url"` + Listen string `json:"listen"` + APITokenFile string `json:"api_token_file"` + StateDir string `json:"state_dir"` + PublicURL lenientString `json:"public_url"` +} + +// lenientString decodes a JSON string and leaves any other value empty, so a +// malformed public_url cannot make every client command fail; `whr serve` +// validates the whole file. +type lenientString string + +func (l *lenientString) UnmarshalJSON(b []byte) error { + var s string + if json.Unmarshal(b, &s) == nil { + *l = lenientString(s) + } + return nil } // ReadClientConfig reads listen, api_token_file and state_dir from the diff --git a/internal/cli/doctor.go b/internal/cli/doctor.go index 6200ac78..bca30cb1 100644 --- a/internal/cli/doctor.go +++ b/internal/cli/doctor.go @@ -104,6 +104,9 @@ func newDoctor(st *state) *cobra.Command { } rs := doctor.Run(cmd.Context(), checks, skipped) repair := repairContext{Account: whrUser} + if cmd.Flags().Changed("prefix") { + repair.Prefix = prefix + } for i, r := range rs { context := repair if other { // command adds it to the fixes that are for whr's account only diff --git a/internal/cli/github.go b/internal/cli/github.go index d207370b..d32a00bf 100644 --- a/internal/cli/github.go +++ b/internal/cli/github.go @@ -86,16 +86,17 @@ It listens on the configuration's "listen" address while it waits, so stop if ip := net.ParseIP(host); err != nil || ip == nil || !ip.IsLoopback() { return usageError{fmt.Sprintf("--listen %q is not a loopback address: the forwarder reaches whr there (D29)", listen)} } + fromFlag := publicURL != "" switch { case local: publicURL = "http://" + listen case publicURL == "" && ccErr == nil: - publicURL = cc.PublicURL + publicURL = string(cc.PublicURL) } if publicURL == "" { return usageError{"no public name: set public_url in the configuration (whr setup asks for it) or pass --public-url whr.example.ts.net; to try it on the host itself, pass --local and open the link in a browser on this Mac"} } - if !local && !loopbackHTTP(publicURL) { + if !local && (!fromFlag || !loopbackHTTP(publicURL)) { n, err := config.NormalizePublicURL(publicURL) if err != nil { return usageError{"public name: " + err.Error()} @@ -145,7 +146,9 @@ It listens on the configuration's "listen" address while it waits, so stop ui := render.Writer{W: st.env.Stderr, S: st.style(st.env.Stderr, false)} ui.Command("tailscale serve status") fmt.Fprintln(st.env.Stderr, "To create the mapping (never use a public funnel):") - ui.Command("tailscale serve --bg " + port) + if isDigits(port) { + ui.Command("tailscale serve --bg " + port) + } fmt.Fprintln(st.env.Stderr, "Or use --local on this Mac.") } @@ -185,8 +188,21 @@ It listens on the configuration's "listen" address while it waits, so stop return cmd } +func isDigits(s string) bool { + if s == "" { + return false + } + for _, r := range s { + if r < '0' || r > '9' { + return false + } + } + return true +} + // loopbackHTTP is an explicit http:// address on a loopback host: a test double -// or a local trial, which githubapp.CheckBaseURL still checks. +// or a local trial named with --public-url only (a configuration value is normalised +// like config.Load and the doctor do), which githubapp.CheckBaseURL still checks. func loopbackHTTP(raw string) bool { u, err := url.Parse(raw) if err != nil || u.Scheme != "http" { diff --git a/internal/cli/github_test.go b/internal/cli/github_test.go index 76cafcc7..1052559c 100644 --- a/internal/cli/github_test.go +++ b/internal/cli/github_test.go @@ -195,3 +195,31 @@ func TestGitHubAppCreateWithoutAPublicNameSaysWhatToDo(t *testing.T) { t.Errorf("--local: %q", stderr.String()) } } + +// A loopback http:// public name is a test double the flag may name; a +// configuration value goes through the same normalisation as config.Load and +// the doctor (issue #399). +func TestGitHubAppCreateRefusesAnHTTPPublicURLFromTheConfiguration(t *testing.T) { + addr := freeAddr(t) + dir := t.TempDir() + cfg := filepath.Join(dir, "config.json") + body := fmt.Sprintf(`{"listen": %q, "api_token_file": "/x", "public_url": "http://127.0.0.1:9"}`, addr) + if err := os.WriteFile(cfg, []byte(body), 0o600); err != nil { + t.Fatal(err) + } + done, _, stderr := runCLIAsync("github", "app", "create", "--config", cfg, "--ttl", "1s", "--key-dir", dir) + if code := <-done; code != exitcode.Usage || !strings.Contains(stderr.String(), "public name") { + t.Errorf("exit %d, stderr %q", code, stderr.String()) + } +} + +func TestReadClientConfigIgnoresANonStringPublicURL(t *testing.T) { + cfg := filepath.Join(t.TempDir(), "config.json") + if err := os.WriteFile(cfg, []byte(`{"api_token_file": "/x", "public_url": 5}`), 0o600); err != nil { + t.Fatal(err) + } + cc, err := ReadClientConfig(cfg) + if err != nil || cc.PublicURL != "" { + t.Fatalf("got %+v, %v", cc, err) + } +} diff --git a/internal/cli/help.go b/internal/cli/help.go index 984b038e..d553b5e7 100644 --- a/internal/cli/help.go +++ b/internal/cli/help.go @@ -12,7 +12,7 @@ import ( ) // descColumn finds where a flag or command description starts on a line like -// " --dev use a ...": text, two or more spaces, then more text. +// " --listen use a ...": text, two or more spaces, then more text. var descColumn = regexp.MustCompile(`^( *\S.*?\S {2,})\S`) // hangWrap wraps every line over 80 columns at spaces, except a command line diff --git a/internal/cli/prefix_setup_test.go b/internal/cli/prefix_setup_test.go index bbc40f73..95634540 100644 --- a/internal/cli/prefix_setup_test.go +++ b/internal/cli/prefix_setup_test.go @@ -28,7 +28,6 @@ func userPrefixRig(t *testing.T) (*setupRig, string) { } r.env.Executable = func() (string, error) { return r.exe, nil } r.env.UID = os.Getuid() // the checks compare owners with the running account - r.host.outputs["/usr/bin/dscl . -read /Users/werner NFSHomeDirectory"] = "NFSHomeDirectory: " + home + "\n" return r, home } @@ -268,4 +267,11 @@ func TestAUserOwnedPrefixWorksWithAWarning(t *testing.T) { if !strings.Contains(out, "warn\tprefix\t") || strings.Contains(out, "fail\tprefix\t") && !strings.Contains(out, "does not exist") { t.Fatalf("stdout %q, stderr %q", out, errOut) } + // a fix line that names whr setup keeps the prefix the doctor was given + // (issue #399) + for _, line := range strings.Split(out, "\n") { + if strings.Contains(line, "whr setup") && !strings.Contains(line, "--prefix") { + t.Errorf("a fix line lacks the prefix: %q", line) + } + } } diff --git a/internal/cli/setup_test.go b/internal/cli/setup_test.go index cef04a12..e834148d 100644 --- a/internal/cli/setup_test.go +++ b/internal/cli/setup_test.go @@ -297,11 +297,12 @@ func TestDoctorRunsEveryCheckReadOnlyAndNamesTheFix(t *testing.T) { if idx("power") >= idx("config-dir") || idx("config-dir") >= idx("service-install") { t.Errorf("host steps, then user steps, in the wizard's order: %v", order) } - if got := lines["power"][3]; got != "whr setup host --only power" { + pfx := " --prefix " + shellArgument(filepath.Dir(filepath.Dir(r.exe))) + if got := lines["power"][3]; got != "whr setup host --only power"+pfx { t.Errorf("power fix %q", got) } // run as werner, not whr: the user phase says so, and says to run as workharbor - if f := lines["config-base"]; f[0] != "not_verified" || !strings.Contains(f[2], "check it as workharbor") || f[3] != "whr setup --only config-base (run as workharbor)" { + if f := lines["config-base"]; f[0] != "not_verified" || !strings.Contains(f[2], "check it as workharbor") || f[3] != "whr setup --only config-base"+pfx+" (run as workharbor)" { t.Errorf("config-base: %q", f) } if f := lines["config"]; f[0] != "fail" || !strings.Contains(f[3], "whr setup") { @@ -448,9 +449,10 @@ func TestDoctorRepairsKeepSelectedAccount(t *testing.T) { for _, account := range []string{"workharbor", "whr", "operator", "operator's"} { r := newSetupRig(t) _, out, _ := r.run("doctor", "--user", account) - suffix := "" + // the doctor keeps the prefix it was given (issue #399) + suffix := " --prefix " + shellArgument(filepath.Dir(filepath.Dir(r.exe))) if account != "workharbor" { - suffix = " --user " + shellArgument(account) + suffix += " --user " + shellArgument(account) } want := "whr setup host --only workharbor-user" + suffix if !strings.Contains(out, want) { @@ -565,7 +567,7 @@ func TestTheHostPartWarnsWhenTheAccountCannotSudo(t *testing.T) { if code == exitcode.OK || code == exitcode.Usage { t.Errorf("non-admin: exit %d, stderr %q", code, errOut) } - for _, want := range []string{"cannot sudo", "sudo pmset", "an administrator"} { + for _, want := range []string{"cannot sudo", "sudo pmset", "an administrator", "need an administrator (sudo)", "this run is not complete"} { if !strings.Contains(errOut, want) { t.Errorf("non-admin: stderr lacks %q: %q", want, errOut) } diff --git a/internal/cli/testdata/doctor_json.golden b/internal/cli/testdata/doctor_json.golden index 31ee7292..df07bf08 100644 --- a/internal/cli/testdata/doctor_json.golden +++ b/internal/cli/testdata/doctor_json.golden @@ -1 +1 @@ -{"checks":[{"check":"workharbor-user","step":2,"status":"not_verified","detail":"dscl did not say whether operator exists: exit status 1","phase":"host","fix":"whr setup host --only workharbor-user --user 'operator'"},{"check":"login-picture","step":2,"status":"not_verified","detail":"dscl did not say what the picture is: exit status 1","phase":"host","fix":"whr setup host --only login-picture --user 'operator'"},{"check":"workspace-folders","step":3,"status":"not_verified","detail":"needs a valid configuration: the workspace roots are read from it, and it is not written yet","phase":"host","fix":"whr setup --only config-base --user 'operator' (run as operator)"},{"check":"autologout","step":2,"status":"not_verified","detail":"defaults did not answer, so the setting is not known: exit status 1","phase":"host","fix":"whr setup host --only autologout --user 'operator'"},{"check":"workspace-volume","step":3,"status":"not_verified","detail":"needs a valid configuration: the workspace roots are read from it, and it is not written yet","phase":"host","fix":"whr setup --only config-base --user 'operator' (run as operator)"},{"check":"power","step":4,"status":"not_verified","detail":"pmset did not answer: exit status 1","phase":"host","fix":"whr setup host --only power --user 'operator'"},{"check":"media-analysis","step":4,"status":"not_verified","detail":"ps did not answer: exit status 1","phase":"host","fix":"whr setup host --only media-analysis --user 'operator'"},{"check":"spotlight","step":4,"status":"not_verified","detail":"needs a valid configuration: the workspace roots are read from it, and it is not written yet","phase":"host","fix":"whr setup --only config-base --user 'operator' (run as operator)"},{"check":"firewall","step":8,"status":"not_verified","detail":"socketfilterfw did not answer: exit status 1","phase":"host","fix":"whr setup host --only firewall --user 'operator'"},{"check":"ssh-keys-only","step":8,"status":"fail","detail":"/etc/ssh/sshd_config.d/100-whr.conf is not there: password logins are not refused","phase":"host","fix":"whr setup host --only ssh-keys-only --user 'operator'"},{"check":"filevault","step":3,"status":"not_verified","detail":"fdesetup did not answer: exit status 1","phase":"host","fix":"whr setup host --only filevault --user 'operator'"},{"check":"homebrew","step":5,"status":"fail","detail":"Homebrew is not installed at /opt/homebrew","phase":"host","fix":"whr setup host --only homebrew --user 'operator'"},{"check":"brew-packages","step":5,"status":"fail","detail":"not installed: container, git, gh","phase":"host","fix":"whr setup host --only brew-packages --user 'operator'"},{"check":"brew-pin","step":6,"status":"not_verified","detail":"brew did not answer: exit status 1","phase":"host","fix":"whr setup host --only brew-pin --user 'operator'"},{"check":"prefix","step":13,"status":"ok","detail":"/whr exists and only the administrator writes it","phase":"host"},{"check":"system-config","step":13,"status":"not_verified","detail":"needs a valid configuration: the system config is built from it, and it is not written yet","phase":"host","fix":"whr setup --only config-base --user 'operator' (run as operator)"},{"check":"screen-sharing","step":8,"status":"not_verified","detail":"macOS keeps this under privacy controls (TCC) that a command cannot read or set","phase":"host","fix":"whr setup host --only screen-sharing --user 'operator'"},{"check":"tailscale","step":7,"status":"not_verified","detail":"signing in is the human's; whr does not check a third party's state","phase":"host","fix":"whr setup host --only tailscale --user 'operator'"},{"check":"tailscale-serve","step":7,"status":"not_verified","detail":"the tailscale forward list did not answer: exit status 1","phase":"host","fix":"whr setup host --only tailscale-serve --user 'operator'"},{"check":"config-dir","step":4,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only config-dir --user 'operator' (run as operator)"},{"check":"api-token","step":1,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only api-token --user 'operator' (run as operator)"},{"check":"agent-key","step":3,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only agent-key --user 'operator' (run as operator)"},{"check":"ssh-ca","step":3,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only ssh-ca --user 'operator' (run as operator)"},{"check":"container-start","step":4,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only container-start --user 'operator' (run as operator)"},{"check":"container-kernel","step":4,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only container-kernel --user 'operator' (run as operator)"},{"check":"standard-user-check","step":4,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only standard-user-check --user 'operator' (run as operator)"},{"check":"config-base","step":1,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only config-base --user 'operator' (run as operator)"},{"check":"public-url","step":2,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only public-url --user 'operator' (run as operator)"},{"check":"github-app","step":2,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only github-app --user 'operator' (run as operator)"},{"check":"config-github","step":2,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only config-github --user 'operator' (run as operator)"},{"check":"tool-store","step":4,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only tool-store --user 'operator' (run as operator)"},{"check":"service-install","step":4,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only service-install --user 'operator' (run as operator)"},{"check":"drop-admin","step":2,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only drop-admin --user 'operator' (run as operator)"},{"check":"config","step":1,"status":"fail","detail":"open /Users/workharbor/.config/whr/config.json: no such file or directory","fix":"whr setup --only config-base --user 'operator' (run as operator)"},{"check":"account","step":2,"status":"not_verified","detail":"dseditgroup did not say whether operator is an administrator (its output format is unverified on macOS 26)"},{"check":"server","step":1,"status":"fail","detail":"the supervisor did not accept the token: configuration: open /Users/workharbor/.config/whr/config.json: no such file or directory (is the supervisor running?)","fix":"whr setup --only service-install --user 'operator' (run as operator)"},{"check":"forge-key","step":2,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --user 'operator' (run as operator)"},{"check":"bot-key","step":2,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --user 'operator' (run as operator)"},{"check":"api-clients","step":2,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --user 'operator' (run as operator)"},{"check":"forge-app","step":2,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --user 'operator' (run as operator)"},{"check":"forge-board","step":2,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --user 'operator' (run as operator)"},{"check":"forge-workflow","step":2,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --user 'operator' (run as operator)"},{"check":"forge-limits","step":2,"status":"not_verified","detail":"that the bot cannot bypass branch protection, and that merge, tag, release and deploy stay forbidden, is enforced by the forge adapter but not checked against your repositories"},{"check":"agent-login","step":3,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --user 'operator' (run as operator)"},{"check":"runtime","step":4,"status":"ok","detail":"/opt/homebrew/bin/container found; whether its service is running is not checked"},{"check":"mounts","step":4,"status":"ok","detail":"the host-side mount check refuses the home directory; the runtime's own refusal is not measured here"},{"check":"egress","step":4,"status":"not_verified","detail":"default-deny egress needs a live environment; run the Apple Container live suite (-tags applecontainer)"},{"check":"reboot","step":4,"status":"not_verified","detail":"an agent session surviving a reboot is unverified (design §12)"},{"check":"capacity","step":4,"status":"not_verified","detail":"room for 4 concurrent environments (§8) is not measured"},{"check":"lane-agents","step":5,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --user 'operator' (run as operator)"},{"check":"notifications","step":5,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --user 'operator' (run as operator)"}],"ok":false,"schema_version":1} +{"checks":[{"check":"workharbor-user","step":2,"status":"not_verified","detail":"dscl did not say whether operator exists: exit status 1","phase":"host","fix":"whr setup host --only workharbor-user --prefix '/whr' --user 'operator'"},{"check":"login-picture","step":2,"status":"not_verified","detail":"dscl did not say what the picture is: exit status 1","phase":"host","fix":"whr setup host --only login-picture --prefix '/whr' --user 'operator'"},{"check":"workspace-folders","step":3,"status":"not_verified","detail":"needs a valid configuration: the workspace roots are read from it, and it is not written yet","phase":"host","fix":"whr setup --only config-base --prefix '/whr' --user 'operator' (run as operator)"},{"check":"autologout","step":2,"status":"not_verified","detail":"defaults did not answer, so the setting is not known: exit status 1","phase":"host","fix":"whr setup host --only autologout --prefix '/whr' --user 'operator'"},{"check":"workspace-volume","step":3,"status":"not_verified","detail":"needs a valid configuration: the workspace roots are read from it, and it is not written yet","phase":"host","fix":"whr setup --only config-base --prefix '/whr' --user 'operator' (run as operator)"},{"check":"power","step":4,"status":"not_verified","detail":"pmset did not answer: exit status 1","phase":"host","fix":"whr setup host --only power --prefix '/whr' --user 'operator'"},{"check":"media-analysis","step":4,"status":"not_verified","detail":"ps did not answer: exit status 1","phase":"host","fix":"whr setup host --only media-analysis --prefix '/whr' --user 'operator'"},{"check":"spotlight","step":4,"status":"not_verified","detail":"needs a valid configuration: the workspace roots are read from it, and it is not written yet","phase":"host","fix":"whr setup --only config-base --prefix '/whr' --user 'operator' (run as operator)"},{"check":"firewall","step":8,"status":"not_verified","detail":"socketfilterfw did not answer: exit status 1","phase":"host","fix":"whr setup host --only firewall --prefix '/whr' --user 'operator'"},{"check":"ssh-keys-only","step":8,"status":"fail","detail":"/etc/ssh/sshd_config.d/100-whr.conf is not there: password logins are not refused","phase":"host","fix":"whr setup host --only ssh-keys-only --prefix '/whr' --user 'operator'"},{"check":"filevault","step":3,"status":"not_verified","detail":"fdesetup did not answer: exit status 1","phase":"host","fix":"whr setup host --only filevault --prefix '/whr' --user 'operator'"},{"check":"homebrew","step":5,"status":"fail","detail":"Homebrew is not installed at /opt/homebrew","phase":"host","fix":"whr setup host --only homebrew --prefix '/whr' --user 'operator'"},{"check":"brew-packages","step":5,"status":"fail","detail":"not installed: container, git, gh","phase":"host","fix":"whr setup host --only brew-packages --prefix '/whr' --user 'operator'"},{"check":"brew-pin","step":6,"status":"not_verified","detail":"brew did not answer: exit status 1","phase":"host","fix":"whr setup host --only brew-pin --prefix '/whr' --user 'operator'"},{"check":"prefix","step":13,"status":"ok","detail":"/whr exists and only the administrator writes it","phase":"host"},{"check":"system-config","step":13,"status":"not_verified","detail":"needs a valid configuration: the system config is built from it, and it is not written yet","phase":"host","fix":"whr setup --only config-base --prefix '/whr' --user 'operator' (run as operator)"},{"check":"screen-sharing","step":8,"status":"not_verified","detail":"macOS keeps this under privacy controls (TCC) that a command cannot read or set","phase":"host","fix":"whr setup host --only screen-sharing --prefix '/whr' --user 'operator'"},{"check":"tailscale","step":7,"status":"not_verified","detail":"signing in is the human's; whr does not check a third party's state","phase":"host","fix":"whr setup host --only tailscale --prefix '/whr' --user 'operator'"},{"check":"tailscale-serve","step":7,"status":"not_verified","detail":"the tailscale forward list did not answer: exit status 1","phase":"host","fix":"whr setup host --only tailscale-serve --prefix '/whr' --user 'operator'"},{"check":"config-dir","step":4,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only config-dir --prefix '/whr' --user 'operator' (run as operator)"},{"check":"api-token","step":1,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only api-token --prefix '/whr' --user 'operator' (run as operator)"},{"check":"agent-key","step":3,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only agent-key --prefix '/whr' --user 'operator' (run as operator)"},{"check":"ssh-ca","step":3,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only ssh-ca --prefix '/whr' --user 'operator' (run as operator)"},{"check":"container-start","step":4,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only container-start --prefix '/whr' --user 'operator' (run as operator)"},{"check":"container-kernel","step":4,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only container-kernel --prefix '/whr' --user 'operator' (run as operator)"},{"check":"standard-user-check","step":4,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only standard-user-check --prefix '/whr' --user 'operator' (run as operator)"},{"check":"config-base","step":1,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only config-base --prefix '/whr' --user 'operator' (run as operator)"},{"check":"public-url","step":2,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only public-url --prefix '/whr' --user 'operator' (run as operator)"},{"check":"github-app","step":2,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only github-app --prefix '/whr' --user 'operator' (run as operator)"},{"check":"config-github","step":2,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only config-github --prefix '/whr' --user 'operator' (run as operator)"},{"check":"tool-store","step":4,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only tool-store --prefix '/whr' --user 'operator' (run as operator)"},{"check":"service-install","step":4,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only service-install --prefix '/whr' --user 'operator' (run as operator)"},{"check":"drop-admin","step":2,"status":"not_verified","detail":"this check describes the account operator runs as: check it as operator","phase":"user","fix":"whr setup --only drop-admin --prefix '/whr' --user 'operator' (run as operator)"},{"check":"config","step":1,"status":"fail","detail":"open /Users/workharbor/.config/whr/config.json: no such file or directory","fix":"whr setup --only config-base --prefix '/whr' --user 'operator' (run as operator)"},{"check":"account","step":2,"status":"not_verified","detail":"dseditgroup did not say whether operator is an administrator (its output format is unverified on macOS 26)"},{"check":"server","step":1,"status":"fail","detail":"the supervisor did not accept the token: configuration: open /Users/workharbor/.config/whr/config.json: no such file or directory (is the supervisor running?)","fix":"whr setup --only service-install --prefix '/whr' --user 'operator' (run as operator)"},{"check":"forge-key","step":2,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --prefix '/whr' --user 'operator' (run as operator)"},{"check":"bot-key","step":2,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --prefix '/whr' --user 'operator' (run as operator)"},{"check":"api-clients","step":2,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --prefix '/whr' --user 'operator' (run as operator)"},{"check":"forge-app","step":2,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --prefix '/whr' --user 'operator' (run as operator)"},{"check":"forge-board","step":2,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --prefix '/whr' --user 'operator' (run as operator)"},{"check":"forge-workflow","step":2,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --prefix '/whr' --user 'operator' (run as operator)"},{"check":"forge-limits","step":2,"status":"not_verified","detail":"that the bot cannot bypass branch protection, and that merge, tag, release and deploy stay forbidden, is enforced by the forge adapter but not checked against your repositories"},{"check":"agent-login","step":3,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --prefix '/whr' --user 'operator' (run as operator)"},{"check":"runtime","step":4,"status":"ok","detail":"/opt/homebrew/bin/container found; whether its service is running is not checked"},{"check":"mounts","step":4,"status":"ok","detail":"the host-side mount check refuses the home directory; the runtime's own refusal is not measured here"},{"check":"egress","step":4,"status":"not_verified","detail":"default-deny egress needs a live environment; run the Apple Container live suite (-tags applecontainer)"},{"check":"reboot","step":4,"status":"not_verified","detail":"an agent session surviving a reboot is unverified (design §12)"},{"check":"capacity","step":4,"status":"not_verified","detail":"room for 4 concurrent environments (§8) is not measured"},{"check":"lane-agents","step":5,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --prefix '/whr' --user 'operator' (run as operator)"},{"check":"notifications","step":5,"status":"fail","detail":"needs a valid configuration (see the config check)","fix":"whr setup --prefix '/whr' --user 'operator' (run as operator)"}],"ok":false,"schema_version":1} diff --git a/internal/cli/testdata/doctor_nocolor.golden b/internal/cli/testdata/doctor_nocolor.golden index a4614cab..d095f25e 100644 --- a/internal/cli/testdata/doctor_nocolor.golden +++ b/internal/cli/testdata/doctor_nocolor.golden @@ -13,80 +13,80 @@ Legend (colour is only decoration: the words are always printed) ? unverified workharbor-user dscl did not say whether operator exists | ACTION to fix workharbor-user, run - whr setup host --only workharbor-user --user 'operator' + whr setup host --only workharbor-user --prefix '/whr' --user 'operator' ? unverified login-picture dscl did not say what the picture is | ACTION to fix login-picture, run - whr setup host --only login-picture --user 'operator' + whr setup host --only login-picture --prefix '/whr' --user 'operator' ? unverified workspace-folders needs a valid configuration: the workspace roots are read from it, and it is not written yet | ACTION to fix workspace-folders, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified autologout defaults did not answer, so the setting is not known | ACTION to fix autologout, run - whr setup host --only autologout --user 'operator' + whr setup host --only autologout --prefix '/whr' --user 'operator' ? unverified workspace-volume needs a valid configuration: the workspace roots are read from it, and it is not written yet | ACTION to fix workspace-volume, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified power pmset did not answer | ACTION to fix power, run - whr setup host --only power --user 'operator' + whr setup host --only power --prefix '/whr' --user 'operator' ? unverified media-analysis ps did not answer | ACTION to fix media-analysis, run - whr setup host --only media-analysis --user 'operator' + whr setup host --only media-analysis --prefix '/whr' --user 'operator' ? unverified spotlight needs a valid configuration: the workspace roots are read from it, and it is not written yet | ACTION to fix spotlight, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified firewall socketfilterfw did not answer | ACTION to fix firewall, run - whr setup host --only firewall --user 'operator' + whr setup host --only firewall --prefix '/whr' --user 'operator' x FAIL ssh-keys-only /etc/ssh/sshd_config.d/100-whr.conf is not there: password logins are not refused | ACTION to fix ssh-keys-only, run - whr setup host --only ssh-keys-only --user 'operator' + whr setup host --only ssh-keys-only --prefix '/whr' --user 'operator' ? unverified filevault fdesetup did not answer | ACTION to fix filevault, run - whr setup host --only filevault --user 'operator' + whr setup host --only filevault --prefix '/whr' --user 'operator' x FAIL homebrew Homebrew is not installed at /opt/homebrew | ACTION to fix homebrew, run - whr setup host --only homebrew --user 'operator' + whr setup host --only homebrew --prefix '/whr' --user 'operator' x FAIL brew-packages not installed: container, git, gh | ACTION to fix brew-packages, run - whr setup host --only brew-packages --user 'operator' + whr setup host --only brew-packages --prefix '/whr' --user 'operator' ? unverified brew-pin brew did not answer | ACTION to fix brew-pin, run - whr setup host --only brew-pin --user 'operator' + whr setup host --only brew-pin --prefix '/whr' --user 'operator' + ok prefix /whr exists and only the administrator writes it @@ -94,25 +94,25 @@ run as operator config is built from it, and it is not written yet | ACTION to fix system-config, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified screen-sharing macOS keeps this under privacy controls (TCC) that a command cannot read or set | ACTION to fix screen-sharing, run - whr setup host --only screen-sharing --user 'operator' + whr setup host --only screen-sharing --prefix '/whr' --user 'operator' ? unverified tailscale signing in is the human's; whr does not check a third party's state | ACTION to fix tailscale, run - whr setup host --only tailscale --user 'operator' + whr setup host --only tailscale --prefix '/whr' --user 'operator' ? unverified tailscale-serve the tailscale forward list did not answer | ACTION to fix tailscale-serve, run - whr setup host --only tailscale-serve --user 'operator' + whr setup host --only tailscale-serve --prefix '/whr' --user 'operator' == User steps (whr setup, as workharbor) == @@ -120,98 +120,98 @@ run as operator runs as: check it as operator | ACTION to fix config-dir, run - whr setup --only config-dir --user 'operator' + whr setup --only config-dir --prefix '/whr' --user 'operator' run as operator ? unverified api-token this check describes the account operator runs as: check it as operator | ACTION to fix api-token, run - whr setup --only api-token --user 'operator' + whr setup --only api-token --prefix '/whr' --user 'operator' run as operator ? unverified agent-key this check describes the account operator runs as: check it as operator | ACTION to fix agent-key, run - whr setup --only agent-key --user 'operator' + whr setup --only agent-key --prefix '/whr' --user 'operator' run as operator ? unverified ssh-ca this check describes the account operator runs as: check it as operator | ACTION to fix ssh-ca, run - whr setup --only ssh-ca --user 'operator' + whr setup --only ssh-ca --prefix '/whr' --user 'operator' run as operator ? unverified container-start this check describes the account operator runs as: check it as operator | ACTION to fix container-start, run - whr setup --only container-start --user 'operator' + whr setup --only container-start --prefix '/whr' --user 'operator' run as operator ? unverified container-kernel this check describes the account operator runs as: check it as operator | ACTION to fix container-kernel, run - whr setup --only container-kernel --user 'operator' + whr setup --only container-kernel --prefix '/whr' --user 'operator' run as operator ? unverified standard-user-check this check describes the account operator runs as: check it as operator | ACTION to fix standard-user-check, run - whr setup --only standard-user-check --user 'operator' + whr setup --only standard-user-check --prefix '/whr' --user 'operator' run as operator ? unverified config-base this check describes the account operator runs as: check it as operator | ACTION to fix config-base, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified public-url this check describes the account operator runs as: check it as operator | ACTION to fix public-url, run - whr setup --only public-url --user 'operator' + whr setup --only public-url --prefix '/whr' --user 'operator' run as operator ? unverified github-app this check describes the account operator runs as: check it as operator | ACTION to fix github-app, run - whr setup --only github-app --user 'operator' + whr setup --only github-app --prefix '/whr' --user 'operator' run as operator ? unverified config-github this check describes the account operator runs as: check it as operator | ACTION to fix config-github, run - whr setup --only config-github --user 'operator' + whr setup --only config-github --prefix '/whr' --user 'operator' run as operator ? unverified tool-store this check describes the account operator runs as: check it as operator | ACTION to fix tool-store, run - whr setup --only tool-store --user 'operator' + whr setup --only tool-store --prefix '/whr' --user 'operator' run as operator ? unverified service-install this check describes the account operator runs as: check it as operator | ACTION to fix service-install, run - whr setup --only service-install --user 'operator' + whr setup --only service-install --prefix '/whr' --user 'operator' run as operator ? unverified drop-admin this check describes the account operator runs as: check it as operator | ACTION to fix drop-admin, run - whr setup --only drop-admin --user 'operator' + whr setup --only drop-admin --prefix '/whr' --user 'operator' run as operator @@ -220,7 +220,7 @@ run as operator no such file or directory | ACTION to fix config, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified account dseditgroup did not say whether operator @@ -231,49 +231,49 @@ run as operator such file or directory (is the supervisor running?) | ACTION to fix server, run - whr setup --only service-install --user 'operator' + whr setup --only service-install --prefix '/whr' --user 'operator' run as operator x FAIL forge-key needs a valid configuration (see the config check) | ACTION to fix forge-key, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator x FAIL bot-key needs a valid configuration (see the config check) | ACTION to fix bot-key, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator x FAIL api-clients needs a valid configuration (see the config check) | ACTION to fix api-clients, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator x FAIL forge-app needs a valid configuration (see the config check) | ACTION to fix forge-app, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator x FAIL forge-board needs a valid configuration (see the config check) | ACTION to fix forge-board, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator x FAIL forge-workflow needs a valid configuration (see the config check) | ACTION to fix forge-workflow, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ? unverified forge-limits that the bot cannot bypass branch @@ -284,7 +284,7 @@ run as operator check) | ACTION to fix agent-login, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator + ok runtime /opt/homebrew/bin/container found; whether its @@ -302,14 +302,14 @@ run as operator check) | ACTION to fix lane-agents, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator x FAIL notifications needs a valid configuration (see the config check) | ACTION to fix notifications, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ------------------------------------------------------------ @@ -318,208 +318,208 @@ Summary: 3 ok, 14 FAIL, 34 not verified (?), 0 WARN, 0 skipped What you need to do now 1. workharbor-user: dscl did not say whether operator exists - whr setup host --only workharbor-user --user 'operator' + whr setup host --only workharbor-user --prefix '/whr' --user 'operator' 2. login-picture: dscl did not say what the picture is - whr setup host --only login-picture --user 'operator' + whr setup host --only login-picture --prefix '/whr' --user 'operator' 3. workspace-folders: needs a valid configuration: the workspace roots are read from it, and it is not written yet (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 4. autologout: defaults did not answer, so the setting is not known - whr setup host --only autologout --user 'operator' + whr setup host --only autologout --prefix '/whr' --user 'operator' 5. workspace-volume: needs a valid configuration: the workspace roots are read from it, and it is not written yet (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 6. power: pmset did not answer - whr setup host --only power --user 'operator' + whr setup host --only power --prefix '/whr' --user 'operator' 7. media-analysis: ps did not answer - whr setup host --only media-analysis --user 'operator' + whr setup host --only media-analysis --prefix '/whr' --user 'operator' 8. spotlight: needs a valid configuration: the workspace roots are read from it, and it is not written yet (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 9. firewall: socketfilterfw did not answer - whr setup host --only firewall --user 'operator' + whr setup host --only firewall --prefix '/whr' --user 'operator' 10. ssh-keys-only: /etc/ssh/sshd_config.d/100-whr.conf is not there: password logins are not refused - whr setup host --only ssh-keys-only --user 'operator' + whr setup host --only ssh-keys-only --prefix '/whr' --user 'operator' 11. filevault: fdesetup did not answer - whr setup host --only filevault --user 'operator' + whr setup host --only filevault --prefix '/whr' --user 'operator' 12. homebrew: Homebrew is not installed at /opt/homebrew - whr setup host --only homebrew --user 'operator' + whr setup host --only homebrew --prefix '/whr' --user 'operator' 13. brew-packages: not installed: container, git, gh - whr setup host --only brew-packages --user 'operator' + whr setup host --only brew-packages --prefix '/whr' --user 'operator' 14. brew-pin: brew did not answer - whr setup host --only brew-pin --user 'operator' + whr setup host --only brew-pin --prefix '/whr' --user 'operator' 15. system-config: needs a valid configuration: the system config is built from it, and it is not written yet (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 16. screen-sharing: macOS keeps this under privacy controls (TCC) that a command cannot read or set - whr setup host --only screen-sharing --user 'operator' + whr setup host --only screen-sharing --prefix '/whr' --user 'operator' 17. tailscale: signing in is the human's; whr does not check a third party's state - whr setup host --only tailscale --user 'operator' + whr setup host --only tailscale --prefix '/whr' --user 'operator' 18. tailscale-serve: the tailscale forward list did not answer - whr setup host --only tailscale-serve --user 'operator' + whr setup host --only tailscale-serve --prefix '/whr' --user 'operator' 19. config-dir: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only config-dir --user 'operator' + whr setup --only config-dir --prefix '/whr' --user 'operator' 20. api-token: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only api-token --user 'operator' + whr setup --only api-token --prefix '/whr' --user 'operator' 21. agent-key: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only agent-key --user 'operator' + whr setup --only agent-key --prefix '/whr' --user 'operator' 22. ssh-ca: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only ssh-ca --user 'operator' + whr setup --only ssh-ca --prefix '/whr' --user 'operator' 23. container-start: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only container-start --user 'operator' + whr setup --only container-start --prefix '/whr' --user 'operator' 24. container-kernel: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only container-kernel --user 'operator' + whr setup --only container-kernel --prefix '/whr' --user 'operator' 25. standard-user-check: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only standard-user-check --user 'operator' + whr setup --only standard-user-check --prefix '/whr' --user 'operator' 26. config-base: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 27. public-url: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only public-url --user 'operator' + whr setup --only public-url --prefix '/whr' --user 'operator' 28. github-app: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only github-app --user 'operator' + whr setup --only github-app --prefix '/whr' --user 'operator' 29. config-github: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only config-github --user 'operator' + whr setup --only config-github --prefix '/whr' --user 'operator' 30. tool-store: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only tool-store --user 'operator' + whr setup --only tool-store --prefix '/whr' --user 'operator' 31. service-install: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only service-install --user 'operator' + whr setup --only service-install --prefix '/whr' --user 'operator' 32. drop-admin: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only drop-admin --user 'operator' + whr setup --only drop-admin --prefix '/whr' --user 'operator' 33. config: open /Users/workharbor/.config/whr/config.json: no such file or directory (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 34. server: the supervisor did not accept the token: configuration: open /Users/workharbor/.config/whr/config.json: no such file or directory (is the supervisor running?) (run as operator) - whr setup --only service-install --user 'operator' + whr setup --only service-install --prefix '/whr' --user 'operator' 35. forge-key: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 36. bot-key: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 37. api-clients: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 38. forge-app: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 39. forge-board: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 40. forge-workflow: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 41. agent-login: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 42. lane-agents: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 43. notifications: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' whr: some checks failed; fix them and check again with: diff --git a/internal/cli/testdata/doctor_plain.golden b/internal/cli/testdata/doctor_plain.golden index a4614cab..d095f25e 100644 --- a/internal/cli/testdata/doctor_plain.golden +++ b/internal/cli/testdata/doctor_plain.golden @@ -13,80 +13,80 @@ Legend (colour is only decoration: the words are always printed) ? unverified workharbor-user dscl did not say whether operator exists | ACTION to fix workharbor-user, run - whr setup host --only workharbor-user --user 'operator' + whr setup host --only workharbor-user --prefix '/whr' --user 'operator' ? unverified login-picture dscl did not say what the picture is | ACTION to fix login-picture, run - whr setup host --only login-picture --user 'operator' + whr setup host --only login-picture --prefix '/whr' --user 'operator' ? unverified workspace-folders needs a valid configuration: the workspace roots are read from it, and it is not written yet | ACTION to fix workspace-folders, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified autologout defaults did not answer, so the setting is not known | ACTION to fix autologout, run - whr setup host --only autologout --user 'operator' + whr setup host --only autologout --prefix '/whr' --user 'operator' ? unverified workspace-volume needs a valid configuration: the workspace roots are read from it, and it is not written yet | ACTION to fix workspace-volume, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified power pmset did not answer | ACTION to fix power, run - whr setup host --only power --user 'operator' + whr setup host --only power --prefix '/whr' --user 'operator' ? unverified media-analysis ps did not answer | ACTION to fix media-analysis, run - whr setup host --only media-analysis --user 'operator' + whr setup host --only media-analysis --prefix '/whr' --user 'operator' ? unverified spotlight needs a valid configuration: the workspace roots are read from it, and it is not written yet | ACTION to fix spotlight, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified firewall socketfilterfw did not answer | ACTION to fix firewall, run - whr setup host --only firewall --user 'operator' + whr setup host --only firewall --prefix '/whr' --user 'operator' x FAIL ssh-keys-only /etc/ssh/sshd_config.d/100-whr.conf is not there: password logins are not refused | ACTION to fix ssh-keys-only, run - whr setup host --only ssh-keys-only --user 'operator' + whr setup host --only ssh-keys-only --prefix '/whr' --user 'operator' ? unverified filevault fdesetup did not answer | ACTION to fix filevault, run - whr setup host --only filevault --user 'operator' + whr setup host --only filevault --prefix '/whr' --user 'operator' x FAIL homebrew Homebrew is not installed at /opt/homebrew | ACTION to fix homebrew, run - whr setup host --only homebrew --user 'operator' + whr setup host --only homebrew --prefix '/whr' --user 'operator' x FAIL brew-packages not installed: container, git, gh | ACTION to fix brew-packages, run - whr setup host --only brew-packages --user 'operator' + whr setup host --only brew-packages --prefix '/whr' --user 'operator' ? unverified brew-pin brew did not answer | ACTION to fix brew-pin, run - whr setup host --only brew-pin --user 'operator' + whr setup host --only brew-pin --prefix '/whr' --user 'operator' + ok prefix /whr exists and only the administrator writes it @@ -94,25 +94,25 @@ run as operator config is built from it, and it is not written yet | ACTION to fix system-config, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified screen-sharing macOS keeps this under privacy controls (TCC) that a command cannot read or set | ACTION to fix screen-sharing, run - whr setup host --only screen-sharing --user 'operator' + whr setup host --only screen-sharing --prefix '/whr' --user 'operator' ? unverified tailscale signing in is the human's; whr does not check a third party's state | ACTION to fix tailscale, run - whr setup host --only tailscale --user 'operator' + whr setup host --only tailscale --prefix '/whr' --user 'operator' ? unverified tailscale-serve the tailscale forward list did not answer | ACTION to fix tailscale-serve, run - whr setup host --only tailscale-serve --user 'operator' + whr setup host --only tailscale-serve --prefix '/whr' --user 'operator' == User steps (whr setup, as workharbor) == @@ -120,98 +120,98 @@ run as operator runs as: check it as operator | ACTION to fix config-dir, run - whr setup --only config-dir --user 'operator' + whr setup --only config-dir --prefix '/whr' --user 'operator' run as operator ? unverified api-token this check describes the account operator runs as: check it as operator | ACTION to fix api-token, run - whr setup --only api-token --user 'operator' + whr setup --only api-token --prefix '/whr' --user 'operator' run as operator ? unverified agent-key this check describes the account operator runs as: check it as operator | ACTION to fix agent-key, run - whr setup --only agent-key --user 'operator' + whr setup --only agent-key --prefix '/whr' --user 'operator' run as operator ? unverified ssh-ca this check describes the account operator runs as: check it as operator | ACTION to fix ssh-ca, run - whr setup --only ssh-ca --user 'operator' + whr setup --only ssh-ca --prefix '/whr' --user 'operator' run as operator ? unverified container-start this check describes the account operator runs as: check it as operator | ACTION to fix container-start, run - whr setup --only container-start --user 'operator' + whr setup --only container-start --prefix '/whr' --user 'operator' run as operator ? unverified container-kernel this check describes the account operator runs as: check it as operator | ACTION to fix container-kernel, run - whr setup --only container-kernel --user 'operator' + whr setup --only container-kernel --prefix '/whr' --user 'operator' run as operator ? unverified standard-user-check this check describes the account operator runs as: check it as operator | ACTION to fix standard-user-check, run - whr setup --only standard-user-check --user 'operator' + whr setup --only standard-user-check --prefix '/whr' --user 'operator' run as operator ? unverified config-base this check describes the account operator runs as: check it as operator | ACTION to fix config-base, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified public-url this check describes the account operator runs as: check it as operator | ACTION to fix public-url, run - whr setup --only public-url --user 'operator' + whr setup --only public-url --prefix '/whr' --user 'operator' run as operator ? unverified github-app this check describes the account operator runs as: check it as operator | ACTION to fix github-app, run - whr setup --only github-app --user 'operator' + whr setup --only github-app --prefix '/whr' --user 'operator' run as operator ? unverified config-github this check describes the account operator runs as: check it as operator | ACTION to fix config-github, run - whr setup --only config-github --user 'operator' + whr setup --only config-github --prefix '/whr' --user 'operator' run as operator ? unverified tool-store this check describes the account operator runs as: check it as operator | ACTION to fix tool-store, run - whr setup --only tool-store --user 'operator' + whr setup --only tool-store --prefix '/whr' --user 'operator' run as operator ? unverified service-install this check describes the account operator runs as: check it as operator | ACTION to fix service-install, run - whr setup --only service-install --user 'operator' + whr setup --only service-install --prefix '/whr' --user 'operator' run as operator ? unverified drop-admin this check describes the account operator runs as: check it as operator | ACTION to fix drop-admin, run - whr setup --only drop-admin --user 'operator' + whr setup --only drop-admin --prefix '/whr' --user 'operator' run as operator @@ -220,7 +220,7 @@ run as operator no such file or directory | ACTION to fix config, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified account dseditgroup did not say whether operator @@ -231,49 +231,49 @@ run as operator such file or directory (is the supervisor running?) | ACTION to fix server, run - whr setup --only service-install --user 'operator' + whr setup --only service-install --prefix '/whr' --user 'operator' run as operator x FAIL forge-key needs a valid configuration (see the config check) | ACTION to fix forge-key, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator x FAIL bot-key needs a valid configuration (see the config check) | ACTION to fix bot-key, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator x FAIL api-clients needs a valid configuration (see the config check) | ACTION to fix api-clients, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator x FAIL forge-app needs a valid configuration (see the config check) | ACTION to fix forge-app, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator x FAIL forge-board needs a valid configuration (see the config check) | ACTION to fix forge-board, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator x FAIL forge-workflow needs a valid configuration (see the config check) | ACTION to fix forge-workflow, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ? unverified forge-limits that the bot cannot bypass branch @@ -284,7 +284,7 @@ run as operator check) | ACTION to fix agent-login, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator + ok runtime /opt/homebrew/bin/container found; whether its @@ -302,14 +302,14 @@ run as operator check) | ACTION to fix lane-agents, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator x FAIL notifications needs a valid configuration (see the config check) | ACTION to fix notifications, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ------------------------------------------------------------ @@ -318,208 +318,208 @@ Summary: 3 ok, 14 FAIL, 34 not verified (?), 0 WARN, 0 skipped What you need to do now 1. workharbor-user: dscl did not say whether operator exists - whr setup host --only workharbor-user --user 'operator' + whr setup host --only workharbor-user --prefix '/whr' --user 'operator' 2. login-picture: dscl did not say what the picture is - whr setup host --only login-picture --user 'operator' + whr setup host --only login-picture --prefix '/whr' --user 'operator' 3. workspace-folders: needs a valid configuration: the workspace roots are read from it, and it is not written yet (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 4. autologout: defaults did not answer, so the setting is not known - whr setup host --only autologout --user 'operator' + whr setup host --only autologout --prefix '/whr' --user 'operator' 5. workspace-volume: needs a valid configuration: the workspace roots are read from it, and it is not written yet (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 6. power: pmset did not answer - whr setup host --only power --user 'operator' + whr setup host --only power --prefix '/whr' --user 'operator' 7. media-analysis: ps did not answer - whr setup host --only media-analysis --user 'operator' + whr setup host --only media-analysis --prefix '/whr' --user 'operator' 8. spotlight: needs a valid configuration: the workspace roots are read from it, and it is not written yet (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 9. firewall: socketfilterfw did not answer - whr setup host --only firewall --user 'operator' + whr setup host --only firewall --prefix '/whr' --user 'operator' 10. ssh-keys-only: /etc/ssh/sshd_config.d/100-whr.conf is not there: password logins are not refused - whr setup host --only ssh-keys-only --user 'operator' + whr setup host --only ssh-keys-only --prefix '/whr' --user 'operator' 11. filevault: fdesetup did not answer - whr setup host --only filevault --user 'operator' + whr setup host --only filevault --prefix '/whr' --user 'operator' 12. homebrew: Homebrew is not installed at /opt/homebrew - whr setup host --only homebrew --user 'operator' + whr setup host --only homebrew --prefix '/whr' --user 'operator' 13. brew-packages: not installed: container, git, gh - whr setup host --only brew-packages --user 'operator' + whr setup host --only brew-packages --prefix '/whr' --user 'operator' 14. brew-pin: brew did not answer - whr setup host --only brew-pin --user 'operator' + whr setup host --only brew-pin --prefix '/whr' --user 'operator' 15. system-config: needs a valid configuration: the system config is built from it, and it is not written yet (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 16. screen-sharing: macOS keeps this under privacy controls (TCC) that a command cannot read or set - whr setup host --only screen-sharing --user 'operator' + whr setup host --only screen-sharing --prefix '/whr' --user 'operator' 17. tailscale: signing in is the human's; whr does not check a third party's state - whr setup host --only tailscale --user 'operator' + whr setup host --only tailscale --prefix '/whr' --user 'operator' 18. tailscale-serve: the tailscale forward list did not answer - whr setup host --only tailscale-serve --user 'operator' + whr setup host --only tailscale-serve --prefix '/whr' --user 'operator' 19. config-dir: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only config-dir --user 'operator' + whr setup --only config-dir --prefix '/whr' --user 'operator' 20. api-token: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only api-token --user 'operator' + whr setup --only api-token --prefix '/whr' --user 'operator' 21. agent-key: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only agent-key --user 'operator' + whr setup --only agent-key --prefix '/whr' --user 'operator' 22. ssh-ca: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only ssh-ca --user 'operator' + whr setup --only ssh-ca --prefix '/whr' --user 'operator' 23. container-start: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only container-start --user 'operator' + whr setup --only container-start --prefix '/whr' --user 'operator' 24. container-kernel: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only container-kernel --user 'operator' + whr setup --only container-kernel --prefix '/whr' --user 'operator' 25. standard-user-check: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only standard-user-check --user 'operator' + whr setup --only standard-user-check --prefix '/whr' --user 'operator' 26. config-base: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 27. public-url: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only public-url --user 'operator' + whr setup --only public-url --prefix '/whr' --user 'operator' 28. github-app: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only github-app --user 'operator' + whr setup --only github-app --prefix '/whr' --user 'operator' 29. config-github: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only config-github --user 'operator' + whr setup --only config-github --prefix '/whr' --user 'operator' 30. tool-store: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only tool-store --user 'operator' + whr setup --only tool-store --prefix '/whr' --user 'operator' 31. service-install: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only service-install --user 'operator' + whr setup --only service-install --prefix '/whr' --user 'operator' 32. drop-admin: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only drop-admin --user 'operator' + whr setup --only drop-admin --prefix '/whr' --user 'operator' 33. config: open /Users/workharbor/.config/whr/config.json: no such file or directory (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 34. server: the supervisor did not accept the token: configuration: open /Users/workharbor/.config/whr/config.json: no such file or directory (is the supervisor running?) (run as operator) - whr setup --only service-install --user 'operator' + whr setup --only service-install --prefix '/whr' --user 'operator' 35. forge-key: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 36. bot-key: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 37. api-clients: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 38. forge-app: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 39. forge-board: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 40. forge-workflow: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 41. agent-login: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 42. lane-agents: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 43. notifications: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' whr: some checks failed; fix them and check again with: diff --git a/internal/cli/testdata/doctor_tty.golden b/internal/cli/testdata/doctor_tty.golden index aa16a441..6deeefd2 100644 --- a/internal/cli/testdata/doctor_tty.golden +++ b/internal/cli/testdata/doctor_tty.golden @@ -13,80 +13,80 @@ Legend (colour is only decoration: the words are always printed) ? unverified workharbor-user dscl did not say whether operator exists ▌ ACTION to fix workharbor-user, run - whr setup host --only workharbor-user --user 'operator' + whr setup host --only workharbor-user --prefix '/whr' --user 'operator' ? unverified login-picture dscl did not say what the picture is ▌ ACTION to fix login-picture, run - whr setup host --only login-picture --user 'operator' + whr setup host --only login-picture --prefix '/whr' --user 'operator' ? unverified workspace-folders needs a valid configuration: the workspace roots are read from it, and it is not written yet ▌ ACTION to fix workspace-folders, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified autologout defaults did not answer, so the setting is not known ▌ ACTION to fix autologout, run - whr setup host --only autologout --user 'operator' + whr setup host --only autologout --prefix '/whr' --user 'operator' ? unverified workspace-volume needs a valid configuration: the workspace roots are read from it, and it is not written yet ▌ ACTION to fix workspace-volume, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified power pmset did not answer ▌ ACTION to fix power, run - whr setup host --only power --user 'operator' + whr setup host --only power --prefix '/whr' --user 'operator' ? unverified media-analysis ps did not answer ▌ ACTION to fix media-analysis, run - whr setup host --only media-analysis --user 'operator' + whr setup host --only media-analysis --prefix '/whr' --user 'operator' ? unverified spotlight needs a valid configuration: the workspace roots are read from it, and it is not written yet ▌ ACTION to fix spotlight, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified firewall socketfilterfw did not answer ▌ ACTION to fix firewall, run - whr setup host --only firewall --user 'operator' + whr setup host --only firewall --prefix '/whr' --user 'operator' ✗ FAIL ssh-keys-only /etc/ssh/sshd_config.d/100-whr.conf is not there: password logins are not refused ▌ ACTION to fix ssh-keys-only, run - whr setup host --only ssh-keys-only --user 'operator' + whr setup host --only ssh-keys-only --prefix '/whr' --user 'operator' ? unverified filevault fdesetup did not answer ▌ ACTION to fix filevault, run - whr setup host --only filevault --user 'operator' + whr setup host --only filevault --prefix '/whr' --user 'operator' ✗ FAIL homebrew Homebrew is not installed at /opt/homebrew ▌ ACTION to fix homebrew, run - whr setup host --only homebrew --user 'operator' + whr setup host --only homebrew --prefix '/whr' --user 'operator' ✗ FAIL brew-packages not installed: container, git, gh ▌ ACTION to fix brew-packages, run - whr setup host --only brew-packages --user 'operator' + whr setup host --only brew-packages --prefix '/whr' --user 'operator' ? unverified brew-pin brew did not answer ▌ ACTION to fix brew-pin, run - whr setup host --only brew-pin --user 'operator' + whr setup host --only brew-pin --prefix '/whr' --user 'operator' ✓ ok prefix /whr exists and only the administrator writes it @@ -94,25 +94,25 @@ run as operator config is built from it, and it is not written yet ▌ ACTION to fix system-config, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified screen-sharing macOS keeps this under privacy controls (TCC) that a command cannot read or set ▌ ACTION to fix screen-sharing, run - whr setup host --only screen-sharing --user 'operator' + whr setup host --only screen-sharing --prefix '/whr' --user 'operator' ? unverified tailscale signing in is the human's; whr does not check a third party's state ▌ ACTION to fix tailscale, run - whr setup host --only tailscale --user 'operator' + whr setup host --only tailscale --prefix '/whr' --user 'operator' ? unverified tailscale-serve the tailscale forward list did not answer ▌ ACTION to fix tailscale-serve, run - whr setup host --only tailscale-serve --user 'operator' + whr setup host --only tailscale-serve --prefix '/whr' --user 'operator' ━━ User steps (whr setup, as workharbor) ━━ @@ -120,98 +120,98 @@ run as operator runs as: check it as operator ▌ ACTION to fix config-dir, run - whr setup --only config-dir --user 'operator' + whr setup --only config-dir --prefix '/whr' --user 'operator' run as operator ? unverified api-token this check describes the account operator runs as: check it as operator ▌ ACTION to fix api-token, run - whr setup --only api-token --user 'operator' + whr setup --only api-token --prefix '/whr' --user 'operator' run as operator ? unverified agent-key this check describes the account operator runs as: check it as operator ▌ ACTION to fix agent-key, run - whr setup --only agent-key --user 'operator' + whr setup --only agent-key --prefix '/whr' --user 'operator' run as operator ? unverified ssh-ca this check describes the account operator runs as: check it as operator ▌ ACTION to fix ssh-ca, run - whr setup --only ssh-ca --user 'operator' + whr setup --only ssh-ca --prefix '/whr' --user 'operator' run as operator ? unverified container-start this check describes the account operator runs as: check it as operator ▌ ACTION to fix container-start, run - whr setup --only container-start --user 'operator' + whr setup --only container-start --prefix '/whr' --user 'operator' run as operator ? unverified container-kernel this check describes the account operator runs as: check it as operator ▌ ACTION to fix container-kernel, run - whr setup --only container-kernel --user 'operator' + whr setup --only container-kernel --prefix '/whr' --user 'operator' run as operator ? unverified standard-user-check this check describes the account operator runs as: check it as operator ▌ ACTION to fix standard-user-check, run - whr setup --only standard-user-check --user 'operator' + whr setup --only standard-user-check --prefix '/whr' --user 'operator' run as operator ? unverified config-base this check describes the account operator runs as: check it as operator ▌ ACTION to fix config-base, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified public-url this check describes the account operator runs as: check it as operator ▌ ACTION to fix public-url, run - whr setup --only public-url --user 'operator' + whr setup --only public-url --prefix '/whr' --user 'operator' run as operator ? unverified github-app this check describes the account operator runs as: check it as operator ▌ ACTION to fix github-app, run - whr setup --only github-app --user 'operator' + whr setup --only github-app --prefix '/whr' --user 'operator' run as operator ? unverified config-github this check describes the account operator runs as: check it as operator ▌ ACTION to fix config-github, run - whr setup --only config-github --user 'operator' + whr setup --only config-github --prefix '/whr' --user 'operator' run as operator ? unverified tool-store this check describes the account operator runs as: check it as operator ▌ ACTION to fix tool-store, run - whr setup --only tool-store --user 'operator' + whr setup --only tool-store --prefix '/whr' --user 'operator' run as operator ? unverified service-install this check describes the account operator runs as: check it as operator ▌ ACTION to fix service-install, run - whr setup --only service-install --user 'operator' + whr setup --only service-install --prefix '/whr' --user 'operator' run as operator ? unverified drop-admin this check describes the account operator runs as: check it as operator ▌ ACTION to fix drop-admin, run - whr setup --only drop-admin --user 'operator' + whr setup --only drop-admin --prefix '/whr' --user 'operator' run as operator @@ -220,7 +220,7 @@ run as operator no such file or directory ▌ ACTION to fix config, run - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' run as operator ? unverified account dseditgroup did not say whether operator @@ -231,49 +231,49 @@ run as operator such file or directory (is the supervisor running?) ▌ ACTION to fix server, run - whr setup --only service-install --user 'operator' + whr setup --only service-install --prefix '/whr' --user 'operator' run as operator ✗ FAIL forge-key needs a valid configuration (see the config check) ▌ ACTION to fix forge-key, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ✗ FAIL bot-key needs a valid configuration (see the config check) ▌ ACTION to fix bot-key, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ✗ FAIL api-clients needs a valid configuration (see the config check) ▌ ACTION to fix api-clients, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ✗ FAIL forge-app needs a valid configuration (see the config check) ▌ ACTION to fix forge-app, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ✗ FAIL forge-board needs a valid configuration (see the config check) ▌ ACTION to fix forge-board, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ✗ FAIL forge-workflow needs a valid configuration (see the config check) ▌ ACTION to fix forge-workflow, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ? unverified forge-limits that the bot cannot bypass branch @@ -284,7 +284,7 @@ run as operator check) ▌ ACTION to fix agent-login, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ✓ ok runtime /opt/homebrew/bin/container found; whether its @@ -302,14 +302,14 @@ run as operator check) ▌ ACTION to fix lane-agents, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ✗ FAIL notifications needs a valid configuration (see the config check) ▌ ACTION to fix notifications, run - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' run as operator ──────────────────────────────────────────────────────────── @@ -318,208 +318,208 @@ Summary: 3 ok, 14 FAIL, 34 not verified (?), 0 WARN, 0 skipped What you need to do now 1. workharbor-user: dscl did not say whether operator exists - whr setup host --only workharbor-user --user 'operator' + whr setup host --only workharbor-user --prefix '/whr' --user 'operator' 2. login-picture: dscl did not say what the picture is - whr setup host --only login-picture --user 'operator' + whr setup host --only login-picture --prefix '/whr' --user 'operator' 3. workspace-folders: needs a valid configuration: the workspace roots are read from it, and it is not written yet (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 4. autologout: defaults did not answer, so the setting is not known - whr setup host --only autologout --user 'operator' + whr setup host --only autologout --prefix '/whr' --user 'operator' 5. workspace-volume: needs a valid configuration: the workspace roots are read from it, and it is not written yet (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 6. power: pmset did not answer - whr setup host --only power --user 'operator' + whr setup host --only power --prefix '/whr' --user 'operator' 7. media-analysis: ps did not answer - whr setup host --only media-analysis --user 'operator' + whr setup host --only media-analysis --prefix '/whr' --user 'operator' 8. spotlight: needs a valid configuration: the workspace roots are read from it, and it is not written yet (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 9. firewall: socketfilterfw did not answer - whr setup host --only firewall --user 'operator' + whr setup host --only firewall --prefix '/whr' --user 'operator' 10. ssh-keys-only: /etc/ssh/sshd_config.d/100-whr.conf is not there: password logins are not refused - whr setup host --only ssh-keys-only --user 'operator' + whr setup host --only ssh-keys-only --prefix '/whr' --user 'operator' 11. filevault: fdesetup did not answer - whr setup host --only filevault --user 'operator' + whr setup host --only filevault --prefix '/whr' --user 'operator' 12. homebrew: Homebrew is not installed at /opt/homebrew - whr setup host --only homebrew --user 'operator' + whr setup host --only homebrew --prefix '/whr' --user 'operator' 13. brew-packages: not installed: container, git, gh - whr setup host --only brew-packages --user 'operator' + whr setup host --only brew-packages --prefix '/whr' --user 'operator' 14. brew-pin: brew did not answer - whr setup host --only brew-pin --user 'operator' + whr setup host --only brew-pin --prefix '/whr' --user 'operator' 15. system-config: needs a valid configuration: the system config is built from it, and it is not written yet (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 16. screen-sharing: macOS keeps this under privacy controls (TCC) that a command cannot read or set - whr setup host --only screen-sharing --user 'operator' + whr setup host --only screen-sharing --prefix '/whr' --user 'operator' 17. tailscale: signing in is the human's; whr does not check a third party's state - whr setup host --only tailscale --user 'operator' + whr setup host --only tailscale --prefix '/whr' --user 'operator' 18. tailscale-serve: the tailscale forward list did not answer - whr setup host --only tailscale-serve --user 'operator' + whr setup host --only tailscale-serve --prefix '/whr' --user 'operator' 19. config-dir: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only config-dir --user 'operator' + whr setup --only config-dir --prefix '/whr' --user 'operator' 20. api-token: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only api-token --user 'operator' + whr setup --only api-token --prefix '/whr' --user 'operator' 21. agent-key: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only agent-key --user 'operator' + whr setup --only agent-key --prefix '/whr' --user 'operator' 22. ssh-ca: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only ssh-ca --user 'operator' + whr setup --only ssh-ca --prefix '/whr' --user 'operator' 23. container-start: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only container-start --user 'operator' + whr setup --only container-start --prefix '/whr' --user 'operator' 24. container-kernel: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only container-kernel --user 'operator' + whr setup --only container-kernel --prefix '/whr' --user 'operator' 25. standard-user-check: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only standard-user-check --user 'operator' + whr setup --only standard-user-check --prefix '/whr' --user 'operator' 26. config-base: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 27. public-url: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only public-url --user 'operator' + whr setup --only public-url --prefix '/whr' --user 'operator' 28. github-app: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only github-app --user 'operator' + whr setup --only github-app --prefix '/whr' --user 'operator' 29. config-github: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only config-github --user 'operator' + whr setup --only config-github --prefix '/whr' --user 'operator' 30. tool-store: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only tool-store --user 'operator' + whr setup --only tool-store --prefix '/whr' --user 'operator' 31. service-install: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only service-install --user 'operator' + whr setup --only service-install --prefix '/whr' --user 'operator' 32. drop-admin: this check describes the account operator runs as: check it as operator (run as operator) - whr setup --only drop-admin --user 'operator' + whr setup --only drop-admin --prefix '/whr' --user 'operator' 33. config: open /Users/workharbor/.config/whr/config.json: no such file or directory (run as operator) - whr setup --only config-base --user 'operator' + whr setup --only config-base --prefix '/whr' --user 'operator' 34. server: the supervisor did not accept the token: configuration: open /Users/workharbor/.config/whr/config.json: no such file or directory (is the supervisor running?) (run as operator) - whr setup --only service-install --user 'operator' + whr setup --only service-install --prefix '/whr' --user 'operator' 35. forge-key: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 36. bot-key: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 37. api-clients: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 38. forge-app: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 39. forge-board: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 40. forge-workflow: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 41. agent-login: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 42. lane-agents: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' 43. notifications: needs a valid configuration (see the config check) (run as operator) - whr setup --user 'operator' + whr setup --prefix '/whr' --user 'operator' whr: some checks failed; fix them and check again with: diff --git a/internal/config/config.go b/internal/config/config.go index b4f06649..6f2b9bbc 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -444,7 +444,8 @@ func (e *Error) Error() string { // Load reads and validates the file at path. The error is an *Error for a // configuration problem and an ordinary error for an unreadable file. func Load(path string) (*Config, error) { - f, err := os.Open(path) //nolint:gosec // the operator names the config file + // O_NONBLOCK: opening a FIFO must not wait for a writer + f, err := os.OpenFile(path, os.O_RDONLY|syscall.O_NONBLOCK, 0) //nolint:gosec // the operator names the config file if err != nil { return nil, err } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index e298b81c..9757b07e 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -8,7 +8,9 @@ import ( "slices" "sort" "strings" + "syscall" "testing" + "time" "github.com/wstein/workharbor/internal/policy" ) @@ -546,3 +548,21 @@ func TestNaturalLessOrdersIndexesNumerically(t *testing.T) { t.Errorf("got %v, want %v", got, want) } } + +// A FIFO named as the configuration must not hang the process (#493). +func TestLoadDoesNotHangOnAFIFO(t *testing.T) { + fifo := filepath.Join(t.TempDir(), "config.json") + if err := syscall.Mkfifo(fifo, 0o600); err != nil { + t.Skip(err) + } + done := make(chan error, 1) + go func() { _, err := Load(fifo); done <- err }() + select { + case err := <-done: + if err == nil { + t.Error("a FIFO loaded as a configuration") + } + case <-time.After(3 * time.Second): + t.Fatal("Load hangs on a FIFO") + } +} diff --git a/internal/doctor/folders.go b/internal/doctor/folders.go index 32bb01d6..25d26fbd 100644 --- a/internal/doctor/folders.go +++ b/internal/doctor/folders.go @@ -201,7 +201,8 @@ func (d Deps) workspaceFoldersStep() Check { // No Sudo preview commands: the wizard asks for the password before // Build when a fix has them, even if Build then refuses. Desc shows // them, and sudo is asked only once Build has returned commands. - Desc: "for each workspace root, with sudo: mkdir -p ; chown -h " + d.account() + " ; chmod -h 0" + workspaceMode + " (only what is missing)", + NeedsSudo: true, + Desc: "for each workspace root, with sudo: mkdir -p ; chown -h " + d.account() + " ; chmod -h 0" + workspaceMode + " (only what is missing)", Build: func(ctx context.Context, p Prompter) ([]Cmd, error) { roots, _, msg := d.workspaceRoots() if msg != "" { diff --git a/internal/doctor/folders_test.go b/internal/doctor/folders_test.go index 422ba135..04e318f3 100644 --- a/internal/doctor/folders_test.go +++ b/internal/doctor/folders_test.go @@ -214,3 +214,46 @@ func TestAMountedExternalVolumeIsNotCalledUnmounted(t *testing.T) { t.Errorf("%s", msg) } } + +func TestARootWhoseOwnerChangedDuringTheConfirmRunsNothing(t *testing.T) { + d, r, root := folderDeps(t, "ws") + if err := os.Mkdir(root, 0o700); err != nil { + t.Fatal(err) + } + r["df -P "+root] = dfHeader + "/dev/disk3s1 100 1 99 1% /System/Volumes/Data" + r["stat -f %Su %Lp "+root] = "alice 700\n" + p := &hookPrompter{hook: func() { r["stat -f %Su %Lp "+root] = "bob 700\n" }} + cmds, err := folderStep(t, d).Fix.Build(context.Background(), p) + if err == nil || len(cmds) != 0 || !strings.Contains(err.Error(), "changed while you were asked") { + t.Errorf("owner: %v %v", cmds, err) + } + r["stat -f %Su %Lp "+root] = "alice 700\n" + p = &hookPrompter{hook: func() { r["stat -f %Su %Lp "+root] = "alice 777\n" }} + cmds, err = folderStep(t, d).Fix.Build(context.Background(), p) + if err == nil || len(cmds) != 0 || !strings.Contains(err.Error(), "changed while you were asked") { + t.Errorf("mode: %v %v", cmds, err) + } +} + +// The fix uses sudo although its preview shows none: the wizard must know +// before it asks "Ready to run this?" (#507). +func TestTheWorkspaceFoldersFixSaysItNeedsSudo(t *testing.T) { + d, _, _ := folderDeps(t, "ws") + if !folderStep(t, d).Fix.NeedsSudo { + t.Error("the workspace-folders fix does not declare NeedsSudo") + } +} + +func TestWorkspaceVolumesEscapesARootInItsMessages(t *testing.T) { + base := t.TempDir() + root := filepath.Join(base, "gone\x1b[31m") + cfg := filepath.Join(base, "config.json") + if err := os.WriteFile(cfg, []byte(`{"roots":{"workspaces":["`+strings.ReplaceAll(root, "\x1b", `\u001b`)+`"]}}`), 0o600); err != nil { + t.Fatal(err) + } + d := Deps{GOOS: "darwin", Runner: scripted{}, ConfigPath: cfg, Home: base} + _, st, msg := d.workspaceVolumes(context.Background()) + if st != NotVerified || strings.ContainsRune(msg, 0x1b) { + t.Errorf("%s %q", st, msg) + } +} diff --git a/internal/doctor/host.go b/internal/doctor/host.go index b1ba130f..ec8ad5fe 100644 --- a/internal/doctor/host.go +++ b/internal/doctor/host.go @@ -1803,15 +1803,15 @@ func (d Deps) workspaceVolumes(ctx context.Context) ([]string, Status, string) { for _, root := range roots { resolved, err := filepath.EvalSymlinks(root) if err != nil { - return nil, NotVerified, "the workspace root " + root + " cannot be resolved, so its disk is not known: " + oneLine(err.Error()) + return nil, NotVerified, "the workspace root " + textsafe.Escape(root) + " cannot be resolved, so its disk is not known: " + textsafe.Escape(oneLine(err.Error())) } out, err := d.output(ctx, "df", "-P", resolved) if err != nil { - return nil, NotVerified, "df did not say which disk " + resolved + " is on: " + oneLine(err.Error()) + return nil, NotVerified, "df did not say which disk " + textsafe.Escape(resolved) + " is on: " + textsafe.Escape(oneLine(err.Error())) } m := dfMount.FindStringSubmatch(out) if m == nil { - return nil, NotVerified, "df's answer for " + resolved + " could not be read" + return nil, NotVerified, "df's answer for " + textsafe.Escape(resolved) + " could not be read" } mount := strings.TrimSpace(m[1]) if internalMounts[mount] || seen[mount] { diff --git a/internal/doctor/systemconfig_test.go b/internal/doctor/systemconfig_test.go index fef0b2f5..8bfabb15 100644 --- a/internal/doctor/systemconfig_test.go +++ b/internal/doctor/systemconfig_test.go @@ -181,6 +181,11 @@ func TestWorkspaceRootsLookupOrder(t *testing.T) { if u := d.needsRoots(t.Context()); u != nil { t.Fatalf("roots steps unreachable: %+v", u) } + for _, name := range []string{"workspace-folders", "workspace-volume", "spotlight"} { + if u := steps(t, d)[name].Reach(t.Context()); u != nil { + t.Errorf("%s must be reachable by the roots rule: %+v", name, u) + } + } if u := steps(t, d)["system-config"].Reach(t.Context()); u == nil { t.Fatal("system-config must stay unreachable: it needs the full user config") }