package wsclient import ( "bytes" "context" "encoding/json" "fmt" "io" stdhttp "net/http" "strings" "time" "gitea.dcglab.co.uk/steve/restic-manager/internal/api" ) // EnrollRequest is what we POST to /api/agents/enroll. type EnrollRequest struct { Token string `json:"token"` HostName string `json:"hostname"` OS api.HostOS `json:"os"` Arch api.HostArch `json:"arch"` AgentVersion string `json:"agent_version"` ResticVersion string `json:"restic_version"` } // EnrollResponse is what the server hands back. type EnrollResponse struct { HostID string `json:"host_id"` AgentToken string `json:"agent_token"` CertPinSHA256 string `json:"cert_pin_sha256,omitempty"` } // Enroll exchanges a one-time enrollment token for persistent agent // credentials. Called by the install script on first run. func Enroll(ctx context.Context, serverURL string, req EnrollRequest) (*EnrollResponse, error) { body, err := json.Marshal(req) if err != nil { return nil, fmt.Errorf("agent enroll: marshal: %w", err) } postURL := strings.TrimRight(serverURL, "/") + "/api/agents/enroll" httpReq, err := stdhttp.NewRequestWithContext(ctx, stdhttp.MethodPost, postURL, bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("agent enroll: build request: %w", err) } httpReq.Header.Set("Content-Type", "application/json") client := &stdhttp.Client{Timeout: 30 * time.Second} res, err := client.Do(httpReq) if err != nil { return nil, fmt.Errorf("agent enroll: post: %w", err) } defer res.Body.Close() rawRes, _ := io.ReadAll(res.Body) if res.StatusCode != stdhttp.StatusCreated { return nil, fmt.Errorf("agent enroll: server returned %d: %s", res.StatusCode, rawRes) } var er EnrollResponse if err := json.Unmarshal(rawRes, &er); err != nil { return nil, fmt.Errorf("agent enroll: parse response: %w", err) } if er.AgentToken == "" || er.HostID == "" { return nil, fmt.Errorf("agent enroll: incomplete response: %+v", er) } return &er, nil }