package main import ( "context" "encoding/json" "fmt" "log" "log/slog" "os" "os/signal" "git.t-juice.club/torjus/natstonotify/bus" "git.t-juice.club/torjus/natstonotify/server" "github.com/nats-io/nats.go" "github.com/nats-io/nkeys" "github.com/urfave/cli/v3" ) const Version = "v0.1.0" func connectNats() (*nats.Conn, error) { natsURL, ok := os.LookupEnv("NATS_URL") if !ok { return nil, fmt.Errorf("NATS_URL not set") } nkey, ok := os.LookupEnv("NATS_NKEY") if !ok { return nil, fmt.Errorf("NATS_NKEY not set") } kp, err := nkeys.FromSeed([]byte(nkey)) if err != nil { return nil, err } pub, err := kp.PublicKey() if err != nil { return nil, err } opt := nats.Nkey(pub, kp.Sign) nc, err := nats.Connect(natsURL, opt) if err != nil { return nil, err } return nc, nil } func main() { // Setup logging logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ Level: slog.LevelDebug, })) slog.SetDefault(logger) cmd := &cli.Command{ EnableShellCompletion: true, Usage: "NATS-powered notification service", Commands: []*cli.Command{ { Name: "server", Usage: "Start the server", EnableShellCompletion: true, Action: func(ctx context.Context, cmd *cli.Command) error { b, err := bus.NewNotifyBus() if err != nil { fmt.Println("Error creating notification bus: ", err) return cli.Exit(err, 1) } info, err := b.ServerInfo() if err != nil { return cli.Exit(err, 1) } logger.Info("Connected to notification bus", "bus_name", info.Name, "bus_version", info.Version) defer b.Close() nc, err := connectNats() if err != nil { logger.Error("Error connecting to NATS", "error", err) return cli.Exit(err, 1) } srv, err := server.New(nc, b) if err != nil { return cli.Exit(err, 1) } go func() { <-ctx.Done() srv.Close() }() slog.Info("Server started", "version", Version) if err := srv.Start(); err != nil { slog.Info("Server exited with error", "error", err) return cli.Exit(err, 2) } slog.Info("Server stopped") return nil }, }, { Name: "notify", Aliases: []string{"n"}, Usage: "Send a notification", ArgsUsage: "SUMMARY [BODY]", EnableShellCompletion: true, Flags: []cli.Flag{ &cli.BoolFlag{ Name: "local", Usage: "Send a local notification", }, }, Action: func(ctx context.Context, cmd *cli.Command) error { if cmd.NArg() < 1 { return cli.Exit("notify requires exactly one argument", 1) } summary := cmd.Args().Get(0) body := cmd.Args().Get(1) bn := bus.BusNotification{ Summary: summary, Body: body, } local := cmd.Bool("local") if local { b, err := bus.NewNotifyBus() if err != nil { fmt.Println("Error creating notification bus: ", err) return cli.Exit(err, 1) } defer b.Close() _, err = b.Notify(bn) if err != nil { fmt.Println("Sending message: ", err) return cli.Exit(err, 1) } } else { nc, err := connectNats() if err != nil { fmt.Printf("Error connecting to NATS: %s\n", err) return cli.Exit(err, 1) } defer nc.Drain() //nolint: errcheck data, err := json.Marshal(bn) if err != nil { return cli.Exit(err, 1) } if err := nc.PublishMsg(&nats.Msg{ Subject: server.DefaultSubject, Data: data, }); err != nil { fmt.Printf("Error sending message: %s\n", err) } nc.Flush() fmt.Printf("Published message to %s: %s", server.DefaultSubject, data) } return nil }, }, }, } ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() if err := cmd.Run(ctx, os.Args); err != nil { log.Fatal(err) } }