Files
llmgw/internal/join/serve.go
Ilker ManapandClaude Opus 5.5 f5c79052b1 Let a machine join a setup by code, dialling out, with nothing logging in
The setup screens can reach a database machine by a join code instead of
ssh: they listen on 7444 and show a short code; llmgw join dials out,
checks the setup by fingerprint, admits itself with the code's secret, and
runs what the setup sends until it is done. A joined machine is one more
onboard.Remote, so checks and the Patroni install run over it unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 21:49:15 +02:00

127 lines
3.4 KiB
Go

package join
import (
"bufio"
"bytes"
"context"
"crypto/sha256"
"crypto/subtle"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"net"
"os"
"os/exec"
"strings"
"time"
)
// Serve dials the setup a code names and runs what it sends until it says it
// is done. Each script is reported to log by its first line, so whoever ran
// `llmgw join` sees what is being done to their machine.
func Serve(ctx context.Context, c Code, log func(string)) error {
d := tls.Dialer{
NetDialer: &net.Dialer{Timeout: 10 * time.Second},
Config: &tls.Config{
MinVersion: tls.VersionTLS13,
// Trusted by the fingerprint in the code, not by an authority:
// the listener's certificate is made for this setup alone.
InsecureSkipVerify: true, //nolint:gosec // verified below, by fingerprint
},
}
want := c.Fingerprint[:]
d.Config.VerifyPeerCertificate = func(raw [][]byte, _ [][]*x509.Certificate) error {
if len(raw) == 0 {
return errors.New("the setup sent no certificate")
}
sum := sha256.Sum256(raw[0])
if subtle.ConstantTimeCompare(sum[:16], want) != 1 {
return errors.New("the machine at that address is not the setup this code is for")
}
return nil
}
conn, err := d.DialContext(ctx, "tcp", c.Addr)
if err != nil {
return fmt.Errorf("cannot reach the setup at %s: %w; it listens there only while it waits for this machine", c.Addr, err)
}
defer conn.Close()
host, _ := os.Hostname()
local, _, _ := net.SplitHostPort(conn.LocalAddr().String())
b, _ := json.Marshal(hello{Secret: c.Secret[:], Hostname: host, Addr: local})
if _, err := conn.Write(append(b, '\n')); err != nil {
return err
}
r := bufio.NewReader(conn)
line, err := r.ReadBytes('\n')
if err != nil {
return fmt.Errorf("the setup closed the connection: %w", err)
}
var ack response
if err := json.Unmarshal(line, &ack); err != nil {
return errors.New("the setup answered something unreadable")
}
if ack.Error != "" {
return errors.New(ack.Error)
}
log(fmt.Sprintf("joined the setup at %s; it is setting this machine up", c.Addr))
for {
line, err := r.ReadBytes('\n')
if err != nil {
return fmt.Errorf("the setup went away before it said it was done: %w", err)
}
var req request
if err := json.Unmarshal(line, &req); err != nil {
return errors.New("the setup sent something unreadable")
}
if req.Bye {
log("the setup is done with this machine")
return nil
}
log(summary(req.Script))
resp := run(ctx, req)
out, _ := json.Marshal(resp)
if _, err := conn.Write(append(out, '\n')); err != nil {
return err
}
}
}
// run runs one script as the setup asked.
func run(ctx context.Context, req request) response {
cmd := exec.CommandContext(ctx, "sh", "-c", req.Script)
if req.Stdin != nil {
cmd.Stdin = bytes.NewReader(req.Stdin)
}
var stdout, stderr bytes.Buffer
cmd.Stdout, cmd.Stderr = &stdout, &stderr
err := cmd.Run()
resp := response{Stdout: stdout.Bytes(), Stderr: stderr.Bytes()}
var exit *exec.ExitError
switch {
case errors.As(err, &exit):
resp.Exit = exit.ExitCode()
case err != nil:
resp.Error = err.Error()
}
return resp
}
// summary is a script's first line that says something.
func summary(script string) string {
for _, l := range strings.Split(script, "\n") {
l = strings.TrimSpace(l)
if l == "" || l == "set -e" || strings.HasPrefix(l, "#") {
continue
}
if len(l) > 100 {
l = l[:100] + "…"
}
return " " + l
}
return " (an empty script)"
}