package agentsync import ( "fmt" "log" "net" "os" "runtime" "strings" "time" "github.com/mrhid6/keymanager/agent/internal/config" grpcclient "github.com/mrhid6/keymanager/agent/internal/grpc" "github.com/mrhid6/keymanager/agent/internal/keys" ) func Run(cfg *config.Config) error { client, err := grpcclient.New(cfg.ServerURL, cfg.TLS) if err != nil { return fmt.Errorf("dial grpc: %w", err) } defer client.Close() // Register if we have a pre-reg token if cfg.PreRegToken != "" { log.Println("registering with server...") hostname, _ := os.Hostname() ipAddress := localIP() osInfo := fmt.Sprintf("%s %s", runtime.GOOS, runtime.GOARCH) agentToken, err := client.Register(cfg.ServerID, cfg.PreRegToken, hostname, ipAddress, osInfo) if err != nil { return fmt.Errorf("registration failed: %w", err) } cfg.AgentToken = agentToken cfg.PreRegToken = "" if err := config.Save(cfg); err != nil { return fmt.Errorf("save config: %w", err) } log.Println("registration successful") // Reconnect with potentially updated state client.Close() client, err = grpcclient.New(cfg.ServerURL, cfg.TLS) if err != nil { return fmt.Errorf("reconnect: %w", err) } } if cfg.AgentToken == "" { return fmt.Errorf("no agent token available — registration required") } ticker := time.NewTicker(cfg.PollInterval) defer ticker.Stop() // Run immediately on startup if err := poll(client, cfg); err != nil { log.Printf("poll error: %v", err) } for range ticker.C { if err := poll(client, cfg); err != nil { log.Printf("poll error: %v", err) } } return nil } func poll(client *grpcclient.Client, cfg *config.Config) error { desired, err := client.SyncKeys(cfg.ServerID, cfg.AgentToken) if err != nil { return fmt.Errorf("SyncKeys: %w", err) } current, err := keys.ReadAuthorizedKeys() if err != nil { return fmt.Errorf("read authorized_keys: %w", err) } if !keys.StateChanged(current, desired) { log.Println("authorized_keys unchanged, skipping write") return nil } if err := keys.WriteAuthorizedKeys(desired); err != nil { return fmt.Errorf("write authorized_keys: %w", err) } log.Printf("authorized_keys updated (%d keys)", len(desired)) return nil } func localIP() string { addrs, err := net.InterfaceAddrs() if err != nil { return "" } for _, addr := range addrs { if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() { if ipNet.IP.To4() != nil { return ipNet.IP.String() } } } return "" } // GenerateAndUpload generates an SSH keypair and uploads the public key to the server. func GenerateAndUpload(cfg *config.Config, label string) error { client, err := grpcclient.New(cfg.ServerURL, cfg.TLS) if err != nil { return err } defer client.Close() keyPath := fmt.Sprintf("/root/.ssh/keymanager_%s", strings.ReplaceAll(label, " ", "_")) pubKey, err := keys.GenerateKeyPair(keyPath, label) if err != nil { return err } keyID, err := client.UploadGeneratedKey(cfg.ServerID, cfg.AgentToken, pubKey, label) if err != nil { return err } log.Printf("uploaded generated key %s (key_id=%s)", label, keyID) return nil }