package remote import ( "bytes" "context" "encoding/json" "fmt" "io" "net" "net/http" "git.faercol.me/faercol/http-boot-config/config/logger" "git.faercol.me/faercol/http-boot-config/config/prober" "github.com/google/uuid" ) const enrollURL = "/enroll" type enrollEFIOption struct { Name string `json:"name"` DevicePath string `json:"device_path"` } type enrollPayload struct { Name string `json:"name"` Options map[string]enrollEFIOption `json:"options"` SelectedOption string `json:"selected_option"` } type enrollRespPayloadJSON struct { ID string `json:"ID"` MulticastGroup string `json:"multicast_group"` MulticastPort int `json:"multicast_port"` } type EnrollConfig struct { ID uuid.UUID MulticastGroup net.IP MulticastPort int } func enrollToServer(ctx context.Context, host string, name string, apps []prober.EfiApp) (EnrollConfig, error) { payload := enrollPayload{ Name: name, Options: make(map[string]enrollEFIOption), } for _, a := range apps { appID := uuid.New() payload.Options[appID.String()] = enrollEFIOption{Name: a.Name, DevicePath: a.DevicePath} if a.Active { payload.SelectedOption = appID.String() } } dat, err := json.Marshal(payload) if err != nil { return EnrollConfig{}, fmt.Errorf("failed to serialize payload: %w", err) } subCtx, cancel := context.WithTimeout(ctx, reqTimeout) defer cancel() req, err := http.NewRequestWithContext(subCtx, http.MethodPost, host+enrollURL, bytes.NewBuffer(dat)) if err != nil { return EnrollConfig{}, fmt.Errorf("failed to initialize request: %w", err) } resp, err := http.DefaultClient.Do(req) if err != nil { return EnrollConfig{}, fmt.Errorf("failed to do query to remote boot server: %w", err) } if resp.StatusCode != http.StatusOK { return EnrollConfig{}, fmt.Errorf("unexpected returncode %d", resp.StatusCode) } var respPayload enrollRespPayloadJSON respDat, err := io.ReadAll(resp.Body) if err != nil { return EnrollConfig{}, fmt.Errorf("failed to read response from server: %w", err) } if err := json.Unmarshal(respDat, &respPayload); err != nil { return EnrollConfig{}, fmt.Errorf("failed to deserialize data from server: %w", err) } newID, err := uuid.Parse(respPayload.ID) if err != nil { return EnrollConfig{}, fmt.Errorf("invalid UUID %q from server: %w", respPayload.ID, err) } return EnrollConfig{ID: newID, MulticastGroup: net.ParseIP(respPayload.MulticastGroup), MulticastPort: respPayload.MulticastPort}, nil } func Enroll(ctx context.Context, host, name string, l *logger.SimpleLogger) (EnrollConfig, error) { apps, err := prober.GetEFIApps() if err != nil { return EnrollConfig{}, fmt.Errorf("failed to get list of available EFI applications: %w", err) } newConf, err := enrollToServer(ctx, host, name, apps) if err != nil { return EnrollConfig{}, fmt.Errorf("failed to enroll client to remote host: %w", err) } l.Infof("Successfully enrolled new client, ID is %q", newConf.ID.String()) return newConf, nil }