alby-hub/main.go
Michael Bumann b824a17222 Merge branch 'main' into client-pubkey-flow
# By kiwiidb
# Via GitHub (2) and kiwiidb (1)
* main:
  reconnect on relay error
  reuse relay connection

# Conflicts:
#	main.go
#	service.go
2023-04-24 13:05:56 +02:00

189 lines
5.1 KiB
Go

package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"strings"
"sync"
"time"
echologrus "github.com/davrux/echo-logrus/v4"
"github.com/glebarez/sqlite"
"github.com/joho/godotenv"
"github.com/kelseyhightower/envconfig"
"github.com/labstack/echo/v4"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
log "github.com/sirupsen/logrus"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func main() {
// Load config from environment variables / .env file
godotenv.Load(".env")
cfg := &Config{}
err := envconfig.Process("", cfg)
if err != nil {
log.Fatalf("Error loading environment variables: %v", err)
}
var db *gorm.DB
if strings.HasPrefix(cfg.DatabaseUri, "postgres://") || strings.HasPrefix(cfg.DatabaseUri, "postgresql://") || strings.HasPrefix(cfg.DatabaseUri, "unix://") {
db, err = gorm.Open(postgres.Open(cfg.DatabaseUri), &gorm.Config{})
if err != nil {
log.Fatalf("Failed to open DB %v", err)
}
} else {
db, err = gorm.Open(sqlite.Open(cfg.DatabaseUri), &gorm.Config{})
if err != nil {
log.Fatalf("Failed to open DB %v", err)
}
}
// Migrate the schema
err = db.AutoMigrate(&User{}, &App{}, &NostrEvent{}, &Payment{}, &Identity{})
if err != nil {
log.Fatalf("Failed migrate DB %v", err)
}
if cfg.NostrSecretKey == "" {
if cfg.LNBackendType == AlbyBackendType {
//not allowed
log.Fatal("Nostr private key is required with this backend type.")
}
//first look up if we already have the private key in the database
//else, generate and store private key
identity := &Identity{}
err = db.FirstOrInit(identity).Error
if err != nil {
log.WithError(err).Fatal("Error retrieving private key from database")
}
if identity.Privkey == "" {
log.Info("No private key found in database, generating & saving.")
identity.Privkey = nostr.GeneratePrivateKey()
err = db.Save(identity).Error
if err != nil {
log.WithError(err).Fatal("Error saving private key to database")
}
}
cfg.NostrSecretKey = identity.Privkey
}
identityPubkey, err := nostr.GetPublicKey(cfg.NostrSecretKey)
if err != nil {
log.Fatalf("Error converting nostr privkey to pubkey: %v", err)
}
cfg.IdentityPubkey = identityPubkey
npub, err := nip19.EncodePublicKey(identityPubkey)
if err != nil {
log.Fatalf("Error converting nostr privkey to pubkey: %v", err)
}
log.Infof("Starting nostr-wallet-connect. npub: %s hex: %s", npub, identityPubkey)
svc := &Service{
cfg: cfg,
db: db,
}
if os.Getenv("DATADOG_AGENT_URL") != "" {
tracer.Start(tracer.WithService("nostr-wallet-connect"))
defer tracer.Stop()
}
echologrus.Logger = log.New()
echologrus.Logger.SetFormatter(&log.JSONFormatter{})
echologrus.Logger.SetOutput(os.Stdout)
echologrus.Logger.SetLevel(log.InfoLevel)
svc.Logger = echologrus.Logger
e := echo.New()
ctx := context.Background()
ctx, _ = signal.NotifyContext(ctx, os.Interrupt)
var wg sync.WaitGroup
switch cfg.LNBackendType {
case LNDBackendType:
lndClient, err := NewLNDService(ctx, svc, e)
if err != nil {
svc.Logger.Fatal(err)
}
svc.lnClient = lndClient
case AlbyBackendType:
oauthService, err := NewAlbyOauthService(svc, e)
if err != nil {
svc.Logger.Fatal(err)
}
svc.lnClient = oauthService
}
//register shared routes
svc.RegisterSharedRoutes(e)
//start Echo server
wg.Add(1)
go func() {
if err := e.Start(fmt.Sprintf(":%v", svc.cfg.Port)); err != nil && err != http.ErrServerClosed {
e.Logger.Fatal("shutting down the server")
}
//handle graceful shutdown
<-ctx.Done()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
e.Shutdown(ctx)
svc.Logger.Info("Echo server exited")
wg.Done()
}()
//connect to the relay
svc.Logger.Infof("Connecting to the relay: %s", cfg.Relay)
relay, err := nostr.RelayConnect(ctx, cfg.Relay)
if err != nil {
svc.Logger.Fatal(err)
}
//publish event with NIP-47 info
err = svc.PublishNip47Info(ctx, relay)
if err != nil {
svc.Logger.WithError(err).Error("Could not publish NIP47 info")
}
//Start infinite loop which will be only broken by canceling ctx (SIGINT)
//TODO: we can start this loop for multiple relays
for {
svc.Logger.Info("Subscribing to events")
sub := relay.Subscribe(ctx, svc.createFilters())
err = svc.StartSubscription(ctx, sub)
if err != nil {
//err being non-nil means that we have an error on the websocket error channel. In this case we just try to reconnect.
svc.Logger.WithError(err).Error("Got an error from the relay. Reconnecting...")
relay, err = nostr.RelayConnect(ctx, cfg.Relay)
if err != nil {
svc.Logger.Fatal(err)
}
continue
}
//err being nil means that the context was canceled and we should exit the program.
break
}
err = relay.Close()
if err != nil {
svc.Logger.Error(err)
}
svc.Logger.Info("Graceful shutdown completed. Goodbye.")
}
func (svc *Service) createFilters() nostr.Filters {
filter := nostr.Filter{
Tags: nostr.TagMap{"p": []string{svc.cfg.IdentityPubkey}},
Kinds: []int{NIP_47_REQUEST_KIND},
}
if svc.cfg.ClientPubkey != "" {
filter.Authors = []string{svc.cfg.ClientPubkey}
}
return []nostr.Filter{filter}
}