client-exec CLI

This commit is contained in:
Ian Gulliver
2026-10-21 10:48:59 -07:00
parent 48a05d92de
commit fab6ce5831
16 changed files with 760 additions and 364 deletions
+52 -6
View File
@@ -1266,8 +1266,8 @@ paths:
get:
operationId: getTailnet
description: >
The state of byre's own tailnet node. It is the only request byre answers on its loopback address, where it
needs no identity, so the app on the same Mac can sign the node in before it is reachable on the tailnet.
The state of byre's own tailnet node. byre answers it on its loopback address, where it needs no identity, so
the app on the same Mac can sign the node in before it is reachable on the tailnet.
responses:
"200":
description: The node's state.
@@ -1275,6 +1275,29 @@ paths:
application/json:
schema: { $ref: "#/components/schemas/Tailnet" }
/api/clients/exec:
post:
operationId: execClient
description: >
Runs a command on a client Mac as client_bash does, for `byre client-exec`. It needs the loopback token as a
bearer token, which byre puts in BYRE_TOKEN for everything it runs.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/ClientExec" }
responses:
"200":
description: The command ran; exit_code is -1 when its timeout killed it.
content:
application/json:
schema: { $ref: "#/components/schemas/ClientExecResult" }
"503":
description: No such client is connected, or it didn't acknowledge the request within 2 seconds, or it disconnected before replying.
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
/api/admin/usage-commands:
get:
operationId: getUsageCommands
@@ -2210,9 +2233,9 @@ components:
description: Work a task agent, or a merge, asks a connected Mac to do, sent over /api/clients/connect.
properties:
id: { type: string }
op: { type: string, enum: [bash, upload, download, read_file, write_file, hands, sign] }
command: { type: string, description: "bash: run with zsh as a login shell in the home directory, in its own process group." }
timeout_ms: { type: integer, description: "bash, sign: kill the process group after this long." }
op: { type: string, enum: [bash, exec, upload, download, read_file, write_file, hands, sign] }
command: { type: string, description: "bash, exec: run with zsh as a login shell in the home directory, in its own process group." }
timeout_ms: { type: integer, description: "bash, exec, sign: kill the process group after this long." }
ident: { type: string, description: "sign: the committer, as \"Name <email>\", to sign as when the Mac's git config has no user.signingkey." }
transfer: { type: string, description: "upload: the transfer id to get from /api/clients/transfers/{transferId}. download: the transfer id to put the archive to." }
dest: { type: string, description: "upload: where the archive's one entry ends up, absolute or starting with ~/; whatever is there is replaced." }
@@ -2227,17 +2250,40 @@ components:
ClientReply:
type: object
required: [id]
description: >
The app sends one reply with accepted set as soon as it receives a request, so byre knows within a moment that
the Mac is listening, then one final reply without it.
properties:
id: { type: string, description: The request's id. }
accepted: { type: boolean, description: "Set only on the first reply, which says the app received the request and started on it." }
error: { type: string, description: Set when the request couldn't be carried out. }
exit_code: { type: integer, description: "bash, sign: the exit status, -1 when it was killed by its timeout." }
exit_code: { type: integer, description: "bash, exec, sign: the exit status, -1 when it was killed by its timeout." }
output: { type: string, description: "bash: stdout and stderr together, at most their last 64 KiB. sign: the signing program's stderr, where it writes its GnuPG status lines, and the login shell's output." }
stdout: { type: string, format: byte, description: "exec: everything the command wrote to stdout." }
stderr: { type: string, format: byte, description: "exec: everything the command wrote to stderr." }
data: { type: string, format: byte, description: "read_file: the file's contents. sign: the detached signature." }
result:
type: object
additionalProperties: true
description: "hands: the tool's MCP CallToolResult."
ClientExec:
type: object
required: [command, timeout_ms]
properties:
client: { type: string, description: "The client's name or id; absent for the most recently active one." }
command: { type: string, description: Run with zsh as a login shell in the home directory, in its own process group. }
timeout_ms: { type: integer, description: Kill the process group after this long. }
ClientExecResult:
type: object
required: [client, exit_code, stdout, stderr]
properties:
client: { type: string, description: The id of the client that ran it. }
exit_code: { type: integer }
stdout: { type: string, format: byte }
stderr: { type: string, format: byte }
TurnRef:
type: object
required: [session_id, task_id, vendor]
+88
View File
@@ -0,0 +1,88 @@
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"net/http"
"os"
"strings"
"time"
"g.fc.run/ai/byre/internal/api"
)
const (
execUnavailable = 75
execTimedOut = 124
execGrace = time.Minute
)
func clientExecCmd(args []string) {
fs := flag.NewFlagSet("client-exec", flag.ExitOnError)
client := fs.String("client", "", "the client's name or id; the most recently active one when absent")
timeout := fs.Int("timeout", 30, "kill the command after this many seconds")
fs.Parse(args)
if fs.NArg() == 0 || *timeout <= 0 {
usage()
}
token := os.Getenv("BYRE_TOKEN")
if token == "" {
execFail(1, "BYRE_TOKEN is not set; byre sets it for everything it runs")
}
words := make([]string, fs.NArg())
for i, w := range fs.Args() {
words[i] = "'" + strings.ReplaceAll(w, "'", `'\''`) + "'"
}
req := api.ClientExec{Command: strings.Join(words, " "), TimeoutMs: *timeout * 1000}
if *client != "" {
req.Client = client
}
body, err := json.Marshal(req)
if err != nil {
execFail(1, err.Error())
}
hr, err := http.NewRequest(http.MethodPost, loopbackURL+"/api/clients/exec", bytes.NewReader(body))
if err != nil {
execFail(1, err.Error())
}
hr.Header.Set("Authorization", "Bearer "+token)
hr.Header.Set("Content-Type", "application/json")
hc := &http.Client{Timeout: time.Duration(*timeout)*time.Second + execGrace}
resp, err := hc.Do(hr)
if err != nil {
execFail(execUnavailable, err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
e := api.Error{}
err = json.NewDecoder(resp.Body).Decode(&e)
if err != nil {
e.Message = resp.Status
}
code := 1
if resp.StatusCode == http.StatusServiceUnavailable {
code = execUnavailable
}
execFail(code, e.Message)
}
res := api.ClientExecResult{}
err = json.NewDecoder(resp.Body).Decode(&res)
if err != nil {
execFail(1, fmt.Sprintf("decode reply: %s", err))
}
os.Stdout.Write(res.Stdout)
os.Stderr.Write(res.Stderr)
if res.ExitCode == -1 {
execFail(execTimedOut, fmt.Sprintf("killed on %s after %ds", res.Client, *timeout))
}
os.Exit(res.ExitCode)
}
func execFail(code int, msg string) {
fmt.Fprintln(os.Stderr, "byre client-exec: "+msg)
os.Exit(code)
}
+5 -1
View File
@@ -26,13 +26,17 @@ func main() {
grepCmd(os.Args[2:])
case "list":
listCmd(os.Args[2:])
case "client-exec":
clientExecCmd(os.Args[2:])
case "screen":
screenCmd()
default:
usage()
}
}
func usage() {
fmt.Fprintln(os.Stderr, "usage: byre serve | byre install | byre check | byre run [--resume ID] | byre signing CHECKOUT | byre grep [--glob GLOB] [--ignore-case] [--files-only] PATTERN PATH | byre list PATH")
fmt.Fprintln(os.Stderr, "usage: byre serve | byre install | byre check | byre run [--resume ID] | byre signing CHECKOUT | byre grep [--glob GLOB] [--ignore-case] [--files-only] PATTERN PATH | byre list PATH | byre client-exec [--client NAME] [--timeout SECS] -- CMD... | byre screen")
os.Exit(2)
}
+39
View File
@@ -0,0 +1,39 @@
package main
import (
"fmt"
"os"
"os/exec"
"howett.net/plist"
)
type ioregRoot struct {
IOConsoleUsers []struct {
UID int `plist:"kCGSSessionUserIDKey"`
Locked bool `plist:"CGSSessionScreenIsLocked"`
} `plist:"IOConsoleUsers"`
}
func screenCmd() {
out, err := exec.Command("ioreg", "-n", "Root", "-d1", "-a").Output()
if err != nil {
fatal("ioreg", "err", err)
}
root := ioregRoot{}
_, err = plist.Unmarshal(out, &root)
if err != nil {
fatal("decode ioreg", "err", err)
}
state := "no-session"
for _, u := range root.IOConsoleUsers {
if u.UID != os.Getuid() {
continue
}
state = "unlocked"
if u.Locked {
state = "locked"
}
}
fmt.Println(state)
}
+5 -2
View File
@@ -73,6 +73,9 @@ func serveCmd(args []string) {
if err := os.WriteFile(filepath.Join(*dataDir, "loopback-token"), []byte(token), 0o600); err != nil {
fatal("write loopback credential", "err", err)
}
if err := os.Setenv("BYRE_TOKEN", token); err != nil {
fatal("export loopback credential", "err", err)
}
home, err := os.UserHomeDir()
if err != nil {
fatal("home directory", "err", err)
@@ -101,7 +104,7 @@ func serveCmd(args []string) {
}
defer ts.Close()
srv, err := server.New(*dataDir, *ports, ts)
srv, err := server.New(*dataDir, *ports, ts, token)
if err != nil {
fatal("start server", "err", err)
}
@@ -115,7 +118,7 @@ func serveCmd(args []string) {
go updateDaily(ctx)
base := func(net.Listener) context.Context { return ctx }
loopback := &http.Server{Addr: *listenAddr, Handler: srv.LoopbackHandler(token), BaseContext: base}
loopback := &http.Server{Addr: *listenAddr, Handler: srv.LoopbackHandler(), BaseContext: base}
tailnet := &http.Server{Handler: srv.Handler(), BaseContext: base}
go func() {
+2
View File
@@ -105,6 +105,8 @@ byre never signs with a key on the machine it runs on. When a project checkout's
- The app pings every 10s and reconnects after 25s with no pong. That catches a socket byre dropped while the Mac slept: the close never reached the Mac, and without something sent on it the app would wait on it indefinitely.
- `log show --info --last 10m --predicate 'subsystem == "run.fc.byre.mac"'` on a client shows `client request <id>: <op>` lines and `byre-hands` starts and exits, which are info level and kept only in memory, so they cover the last few minutes at most; connection drops (`client connection closed`, `client connection got no pong`) are notices, kept on disk, so `log show --start` reaches much further back for them. byre's log shows `client connected` and `client disconnected`.
- The app acknowledges every request the moment it receives one; a client that hasn't within 2s fails the call, so a dead connection fails fast rather than at the request's own timeout.
- `byre client-exec` (how to use it, and `byre screen`, is the `client-exec` knowledge-base article) calls `POST /api/clients/exec` on the loopback address with the loopback token, which byre exports as `BYRE_TOKEN` to everything it starts. byre's log has a `client exec` line per call with the client, command, exit status and duration, never the output, since it may hold credentials.
## Screen and app control
+1
View File
@@ -78,6 +78,7 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20260915211658-a6f909f08a72 // indirect
howett.net/plist v1.0.0 // indirect
modernc.org/libc v1.77.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.12.1 // indirect
+2
View File
@@ -166,6 +166,7 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jellydator/ttlcache/v3 v3.4.1 h1:bOdXmXiycyK6E6Qjyuj5vl+/vU3SCOoDs8a86NbHjAQ=
github.com/jellydator/ttlcache/v3 v3.4.1/go.mod h1:j7LO12PNghFg5+0v9budMAT4rDK4JY969jb9vOdOBBk=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90=
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
@@ -401,6 +402,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+1 -11
View File
@@ -10,7 +10,6 @@ import (
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"time"
@@ -220,17 +219,8 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
}
}
func (m *Manager) recentClients() []clients.Client {
rows := m.clients.List()
sort.SliceStable(rows, func(i, j int) bool {
a, b := rows[i].LastActionAt, rows[j].LastActionAt
return a != nil && (b == nil || a.After(*b))
})
return rows
}
func (m *Manager) listClients() string {
rows := m.recentClients()
rows := m.clients.Recent()
if len(rows) == 0 {
return "No client Macs are connected."
}
+37
View File
@@ -0,0 +1,37 @@
How a script or plain shell runs a command on a client Mac without an agent (byre client-exec), and how to check either Mac's screen lock (byre screen).
# Running commands on a client Mac from a shell
`client_bash` needs an agent. For a script or plain shell on the machine byre runs on, such as a wrapper that fetches a token from the human's Mac, use:
```
byre client-exec [--client NAME] [--timeout SECS] -- CMD...
```
`byre` is `/Applications/Byre.app/Contents/Helpers/byre` on every Mac with the app.
- It runs CMD as `client_bash` does: zsh as a login shell in the home directory, in the app's session, as the human. That session is what GUI-only commands need.
- Without `--client` it picks the most recently active client, the one `list_clients` lists first.
- Each argument reaches the client as one word, so shell syntax needs `-- zsh -c '...'`.
- The command's stdout and stderr come back separately and byte for byte, once it ends, written to client-exec's own stdout and stderr.
- Exit status: the command's own; 75 when no client is connected, it didn't acknowledge the request within 2s, or it disconnected; 124 when `--timeout` (default 30s) killed it. A one-line `byre client-exec: ...` on stderr says why.
## Credentials
It needs `BYRE_TOKEN`, which byre sets for everything it starts: agents, their tools and the processes those run, and the task's terminals. A process byre didn't start reads it from `loopback-token` in byre's data directory (`~/Library/Application Support/byre/` by default); it changes every time byre restarts, so read it at each call:
```
BYRE_TOKEN=$(<"$HOME/Library/Application Support/byre/loopback-token") byre client-exec -- ...
```
byre logs each call (client, command, exit status, duration), never its output.
## Screen lock
`byre screen` prints `unlocked`, `locked` or `no-session` (no GUI login for this user) for the Mac it runs on, and exits 0. Check the server Mac with `byre screen` and the client with:
```
byre client-exec -- /Applications/Byre.app/Contents/Helpers/byre screen
```
client-exec runs commands whatever the lock state; a script that needs an unlocked screen checks first and fails fast itself.
+1 -1
View File
@@ -15,7 +15,7 @@ const signTimeout = 2 * time.Minute
func (m *Manager) signer(ctx context.Context, user api.User) func(payload []byte) ([]byte, error) {
return func(payload []byte) ([]byte, error) {
mine := []clients.Client{}
for _, c := range m.recentClients() {
for _, c := range m.clients.Recent() {
if c.Login == user.Login {
mine = append(mine, c)
}
+380 -318
View File
@@ -142,6 +142,7 @@ func (e AttachmentKind) Valid() bool {
const (
Bash ClientRequestOp = "bash"
Download ClientRequestOp = "download"
Exec ClientRequestOp = "exec"
Hands ClientRequestOp = "hands"
ReadFile ClientRequestOp = "read_file"
Sign ClientRequestOp = "sign"
@@ -156,6 +157,8 @@ func (e ClientRequestOp) Valid() bool {
return true
case Download:
return true
case Exec:
return true
case Hands:
return true
case ReadFile:
@@ -698,15 +701,39 @@ type BoardPostCreate struct {
Text string `json:"text"`
}
// ClientReply defines model for ClientReply.
// ClientExec defines model for ClientExec.
type ClientExec struct {
// Client The client's name or id; absent for the most recently active one.
Client *string `json:"client,omitempty"`
// Command Run with zsh as a login shell in the home directory
Command string `json:"command"`
// TimeoutMs Kill the process group after this long.
TimeoutMs int `json:"timeout_ms"`
}
// ClientExecResult defines model for ClientExecResult.
type ClientExecResult struct {
// Client The id of the client that ran it.
Client string `json:"client"`
ExitCode int `json:"exit_code"`
Stderr []byte `json:"stderr"`
Stdout []byte `json:"stdout"`
}
// ClientReply The app sends one reply with accepted set as soon as it receives a request, so byre knows within a moment that the Mac is listening, then one final reply without it.
type ClientReply struct {
// Accepted Set only on the first reply, which says the app received the request and started on it.
Accepted *bool `json:"accepted,omitempty"`
// Data read_file: the file's contents. sign: the detached signature.
Data *[]byte `json:"data,omitempty"`
// Error Set when the request couldn't be carried out.
Error *string `json:"error,omitempty"`
// ExitCode bash, sign: the exit status, -1 when it was killed by its timeout.
// ExitCode bash, exec, sign: the exit status, -1 when it was killed by its timeout.
ExitCode *int `json:"exit_code,omitempty"`
// Id The request's id.
@@ -717,6 +744,12 @@ type ClientReply struct {
// Result hands: the tool's MCP CallToolResult.
Result *map[string]interface{} `json:"result,omitempty"`
// Stderr exec: everything the command wrote to stderr.
Stderr *[]byte `json:"stderr,omitempty"`
// Stdout exec: everything the command wrote to stdout.
Stdout *[]byte `json:"stdout,omitempty"`
}
// ClientRequest Work a task agent, or a merge, asks a connected Mac to do, sent over /api/clients/connect.
@@ -724,7 +757,7 @@ type ClientRequest struct {
// Arguments hands: the tool's arguments.
Arguments *map[string]interface{} `json:"arguments,omitempty"`
// Command bash: run with zsh as a login shell in the home directory, in its own process group.
// Command bash, exec: run with zsh as a login shell in the home directory, in its own process group.
Command *string `json:"command,omitempty"`
// Data write_file: the file's new contents; parent directories are created. sign: the unsigned commit object, which the Mac signs with the program its git config names for its gpg.format (openpgp or x509), called as git calls it.
@@ -741,7 +774,7 @@ type ClientRequest struct {
// Path download, read_file, write_file: the file (or, for download, file or directory) on the client, absolute or starting with ~/.
Path *string `json:"path,omitempty"`
// TimeoutMs bash, sign: kill the process group after this long.
// TimeoutMs bash, exec, sign: kill the process group after this long.
TimeoutMs *int `json:"timeout_ms,omitempty"`
// Tool hands: the byre-hands tool to call.
@@ -1744,6 +1777,9 @@ type PutWorkflowParams struct {
// UpdateUsageCommandsJSONRequestBody defines body for UpdateUsageCommands for application/json ContentType.
type UpdateUsageCommandsJSONRequestBody = UsageCommands
// ExecClientJSONRequestBody defines body for ExecClient for application/json ContentType.
type ExecClientJSONRequestBody = ClientExec
// SetClientSyncJSONRequestBody defines body for SetClientSync for application/json ContentType.
type SetClientSyncJSONRequestBody = ClientSync
@@ -1846,6 +1882,9 @@ type ServerInterface interface {
// (GET /api/clients/connect)
ConnectClient(w http.ResponseWriter, r *http.Request)
// (POST /api/clients/exec)
ExecClient(w http.ResponseWriter, r *http.Request)
// (PUT /api/clients/sync)
SetClientSync(w http.ResponseWriter, r *http.Request)
@@ -2191,6 +2230,20 @@ func (siw *ServerInterfaceWrapper) ConnectClient(w http.ResponseWriter, r *http.
handler.ServeHTTP(w, r)
}
// ExecClient operation middleware
func (siw *ServerInterfaceWrapper) ExecClient(w http.ResponseWriter, r *http.Request) {
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.ExecClient(w, r)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// SetClientSync operation middleware
func (siw *ServerInterfaceWrapper) SetClientSync(w http.ResponseWriter, r *http.Request) {
@@ -4455,6 +4508,7 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/events", wrapper.StreamEvents)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/admin/status", wrapper.GetStatus)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tailnet", wrapper.GetTailnet)
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/clients/exec", wrapper.ExecClient)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/admin/usage-commands", wrapper.GetUsageCommands)
m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/admin/usage-commands", wrapper.UpdateUsageCommands)
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/admin/permissions", wrapper.CheckPermissions)
@@ -4471,320 +4525,328 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
// const string: with thousands of chunks the chained `+` fold is several
// times slower for the Go compiler than parsing a slice literal.
var swaggerSpec = []string{
"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",
}
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
+28 -3
View File
@@ -23,8 +23,11 @@ const (
pingInterval = 30 * time.Second
pingTimeout = 15 * time.Second
expireAfter = 24 * time.Hour
ackTimeout = 2 * time.Second
)
var ErrUnavailable = errors.New("client unavailable")
type Client struct {
ID string
Login string
@@ -150,9 +153,12 @@ func (r *Registry) Serve(ctx context.Context, ws *websocket.Conn, id, login, hos
ws.Close(websocket.StatusUnsupportedData, "messages must be ClientReply")
return
}
accepted := reply.Accepted != nil && *reply.Accepted
c.mu.Lock()
ch := c.pending[reply.Id]
if !accepted {
delete(c.pending, reply.Id)
}
c.mu.Unlock()
if ch == nil {
slog.Warn("client reply for no request", "host", host, "id", reply.Id)
@@ -204,6 +210,15 @@ func (r *Registry) List() []Client {
return out
}
func (r *Registry) Recent() []Client {
rows := r.List()
sort.SliceStable(rows, func(i, j int) bool {
a, b := rows[i].LastActionAt, rows[j].LastActionAt
return a != nil && (b == nil || a.After(*b))
})
return rows
}
func (r *Registry) Call(ctx context.Context, name string, req api.ClientRequest) (api.ClientReply, error) {
r.mu.Lock()
matches := []entry{}
@@ -220,7 +235,7 @@ func (r *Registry) Call(ctx context.Context, name string, req api.ClientRequest)
r.mu.Unlock()
slices.Sort(connected)
if len(matches) == 0 {
return api.ClientReply{}, fmt.Errorf("no client named %q is connected; connected: %s", name, strings.Join(connected, ", "))
return api.ClientReply{}, fmt.Errorf("%w: no client named %q is connected; connected: %s", ErrUnavailable, name, strings.Join(connected, ", "))
}
if len(matches) > 1 {
matched := make([]string, len(matches))
@@ -233,7 +248,7 @@ func (r *Registry) Call(ctx context.Context, name string, req api.ClientRequest)
c := matches[0].conn
req.Id = ids.New(ids.ClientRequest)
ch := make(chan api.ClientReply, 1)
ch := make(chan api.ClientReply, 2)
c.mu.Lock()
c.pending[req.Id] = ch
c.mu.Unlock()
@@ -252,18 +267,28 @@ func (r *Registry) Call(ctx context.Context, name string, req api.ClientRequest)
return api.ClientReply{}, fmt.Errorf("send to %s: %w", name, err)
}
ack := time.NewTimer(ackTimeout)
defer ack.Stop()
for {
select {
case reply := <-ch:
if reply.Accepted != nil && *reply.Accepted {
ack.Stop()
continue
}
if reply.Error != nil {
return reply, errors.New(*reply.Error)
}
return reply, nil
case <-ack.C:
return api.ClientReply{}, fmt.Errorf("%w: %s didn't acknowledge the request within %s", ErrUnavailable, name, ackTimeout)
case <-c.done:
return api.ClientReply{}, fmt.Errorf("%s disconnected before replying", name)
return api.ClientReply{}, fmt.Errorf("%w: %s disconnected before replying", ErrUnavailable, name)
case <-ctx.Done():
return api.ClientReply{}, fmt.Errorf("%s didn't reply: %w", name, ctx.Err())
}
}
}
func (r *Registry) AddTransfer(t Transfer) string {
id := ids.New(ids.Transfer)
+54
View File
@@ -2,6 +2,7 @@ package server
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
@@ -11,10 +12,12 @@ import (
"os"
"os/exec"
"path/filepath"
"time"
"github.com/coder/websocket"
"g.fc.run/ai/byre/internal/api"
"g.fc.run/ai/byre/internal/clients"
)
func (s *Server) ConnectClient(w http.ResponseWriter, r *http.Request) {
@@ -60,6 +63,57 @@ func (s *Server) SetClientSync(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
const execGrace = 30 * time.Second
func (s *Server) ExecClient(w http.ResponseWriter, r *http.Request) {
if !s.bearer(r) {
writeError(w, http.StatusUnauthorized, errors.New("exec needs the loopback token as a bearer token"))
return
}
req := api.ClientExec{}
if !readJSON(w, r, &req) {
return
}
if req.TimeoutMs <= 0 {
writeError(w, http.StatusBadRequest, errors.New("timeout_ms must be positive"))
return
}
name := ""
if req.Client != nil {
name = *req.Client
}
if name == "" {
recent := s.clients.Recent()
if len(recent) == 0 {
writeError(w, http.StatusServiceUnavailable, errors.New("no client Macs are connected"))
return
}
name = recent[0].ID
}
timeout := time.Duration(req.TimeoutMs) * time.Millisecond
ctx, cancel := context.WithTimeout(r.Context(), timeout+execGrace)
defer cancel()
start := time.Now()
reply, err := s.clients.Call(ctx, name, api.ClientRequest{Op: api.Exec, Command: &req.Command, TimeoutMs: &req.TimeoutMs})
if err != nil {
slog.Info("client exec", "client", name, "command", req.Command, "err", err, "duration", time.Since(start))
status := http.StatusInternalServerError
if errors.Is(err, clients.ErrUnavailable) {
status = http.StatusServiceUnavailable
}
writeError(w, status, err)
return
}
if reply.ExitCode == nil || reply.Stdout == nil || reply.Stderr == nil {
writeError(w, http.StatusInternalServerError, fmt.Errorf("%s's reply has no exit_code, stdout or stderr", name))
return
}
slog.Info("client exec", "client", name, "command", req.Command, "exit", *reply.ExitCode, "duration", time.Since(start))
writeJSON(w, http.StatusOK, api.ClientExecResult{Client: name, ExitCode: *reply.ExitCode, Stdout: *reply.Stdout, Stderr: *reply.Stderr})
}
func (s *Server) GetClientTransfer(w http.ResponseWriter, r *http.Request, transferID string) {
t, ok := s.clients.Transfer(transferID)
if !ok || t.Receive {
+10 -3
View File
@@ -35,6 +35,7 @@ type Server struct {
terminals *terminal.Terminals
tailnet *local.Client
hostname string
token string
startedAt time.Time
usersMu sync.Mutex
@@ -43,7 +44,7 @@ type Server struct {
type ctxKey struct{}
func New(dataDir string, portFirst int, ts *tsnet.Server) (*Server, error) {
func New(dataDir string, portFirst int, ts *tsnet.Server, token string) (*Server, error) {
err := os.MkdirAll(dataDir, 0o700)
if err != nil {
return nil, err
@@ -62,6 +63,7 @@ func New(dataDir string, portFirst int, ts *tsnet.Server) (*Server, error) {
hub: newHub(),
tailnet: lc,
hostname: ts.Hostname,
token: token,
startedAt: time.Now().UTC(),
users: map[string]string{},
}
@@ -130,11 +132,12 @@ func (s *Server) Handler() http.Handler {
return http.NewCrossOriginProtection().Handler(mux)
}
func (s *Server) LoopbackHandler(token string) http.Handler {
func (s *Server) LoopbackHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/tailnet", s.GetTailnet)
mux.HandleFunc("POST /api/clients/exec", s.ExecClient)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if subtle.ConstantTimeCompare([]byte(r.Header.Get("Authorization")), []byte("Bearer "+token)) != 1 {
if !s.bearer(r) {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
@@ -143,6 +146,10 @@ func (s *Server) LoopbackHandler(token string) http.Handler {
})
}
func (s *Server) bearer(r *http.Request) bool {
return subtle.ConstantTimeCompare([]byte(r.Header.Get("Authorization")), []byte("Bearer "+s.token)) == 1
}
func (s *Server) GetTailnet(w http.ResponseWriter, r *http.Request) {
st, err := s.tailnet.StatusWithoutPeers(r.Context())
if err != nil {
+46 -10
View File
@@ -134,6 +134,11 @@ final class Remote {
}
log.info("client request \(req.id, privacy: .public): \(req.op.rawValue, privacy: .public)")
Task {
do {
try await task.send(.string(String(decoding: try JSONEncoder().encode(ClientReply(id: req.id, accepted: true)), as: UTF8.self)))
} catch {
log.error("acknowledge client request \(req.id, privacy: .public): \(errorText(error), privacy: .public)")
}
var reply: ClientReply
do {
reply = try await perform(req)
@@ -154,6 +159,10 @@ final class Remote {
let home = FileManager.default.homeDirectoryForCurrentUser.path
let (code, output) = try await spawn(["/bin/zsh", "-l", "-c", req.command ?? ""], in: home, timeoutMs: req.timeoutMs ?? 120_000)
return ClientReply(id: req.id, exitCode: code, output: output)
case .exec:
let home = FileManager.default.homeDirectoryForCurrentUser.path
let (code, stdout, stderr) = try await spawnSplit(["/bin/zsh", "-l", "-c", req.command ?? ""], in: home, timeoutMs: req.timeoutMs ?? 120_000)
return ClientReply(id: req.id, exitCode: code, stdout: Base64EncodedData(stdout), stderr: Base64EncodedData(stderr))
case .upload:
try await upload(transfer: req.transfer ?? "", to: req.dest ?? "")
return ClientReply(id: req.id)
@@ -299,24 +308,52 @@ private func start(_ p: Process) throws -> AsyncStream<Void> {
}
private func spawn(_ argv: [String], in dir: String, timeoutMs: Int, input: FileHandle = .nullDevice) async throws -> (Int, String) {
let fm = FileManager.default
let out = fm.temporaryDirectory.appending(path: "byre-\(UUID().uuidString).log")
let out = try sinkFile()
defer {
try? fm.removeItem(at: out)
try? FileManager.default.removeItem(at: out)
}
fm.createFile(atPath: out.path, contents: nil, attributes: [.posixPermissions: 0o600])
let sink = try FileHandle(forWritingTo: out)
defer {
try? sink.close()
}
let code = try await run(argv, in: dir, timeoutMs: timeoutMs, input: input, stdout: sink, stderr: sink)
return (code, tail(out))
}
private func spawnSplit(_ argv: [String], in dir: String, timeoutMs: Int) async throws -> (Int, Data, Data) {
let fm = FileManager.default
let out = try sinkFile()
let err = try sinkFile()
defer {
try? fm.removeItem(at: out)
try? fm.removeItem(at: err)
}
let outSink = try FileHandle(forWritingTo: out)
let errSink = try FileHandle(forWritingTo: err)
defer {
try? outSink.close()
try? errSink.close()
}
let code = try await run(argv, in: dir, timeoutMs: timeoutMs, input: .nullDevice, stdout: outSink, stderr: errSink)
return (code, try Data(contentsOf: out), try Data(contentsOf: err))
}
private func sinkFile() throws -> URL {
let url = FileManager.default.temporaryDirectory.appending(path: "byre-\(UUID().uuidString).log")
guard FileManager.default.createFile(atPath: url.path, contents: nil, attributes: [.posixPermissions: 0o600]) else {
throw ApiError(status: 0, message: "create \(url.path)")
}
return url
}
private func run(_ argv: [String], in dir: String, timeoutMs: Int, input: FileHandle, stdout: FileHandle, stderr: FileHandle) async throws -> Int {
let p = Process()
p.executableURL = URL(filePath: argv[0])
p.arguments = Array(argv.dropFirst())
p.currentDirectoryURL = URL(filePath: dir)
p.standardInput = input
p.standardOutput = sink
p.standardError = sink
p.standardOutput = stdout
p.standardError = stderr
let exit: AsyncStream<Void>
do {
exit = try start(p)
@@ -332,14 +369,13 @@ private func spawn(_ argv: [String], in dir: String, timeoutMs: Int, input: File
timer.cancel()
let timedOut = (try? await timer.value) != nil
let output = tail(out)
if timedOut {
return (-1, output)
return -1
}
if p.terminationReason == .exit {
return (Int(p.terminationStatus), output)
return Int(p.terminationStatus)
}
return (128 + Int(p.terminationStatus), output)
return 128 + Int(p.terminationStatus)
}
private func tail(_ file: URL) -> String {