mirror of
https://github.com/getAlby/lndhub.go.git
synced 2025-12-18 13:14:56 +01:00
* Update Makefile * Optionally load test DB from env variable * Add option to soft-delete a user This allows users to be marked as deleted. An additional middleware checks if a user is deleted or deactivated and rejects requests for those as StatusUnauthorized. note: the middelware adds an additional DB query to load the user.
250 lines
7.4 KiB
Go
250 lines
7.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/getAlby/lndhub.go/lnd"
|
|
"github.com/getAlby/lndhub.go/rabbitmq"
|
|
ddEcho "gopkg.in/DataDog/dd-trace-go.v1/contrib/labstack/echo.v4"
|
|
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
|
|
|
"github.com/getAlby/lndhub.go/db"
|
|
"github.com/getAlby/lndhub.go/db/migrations"
|
|
"github.com/getAlby/lndhub.go/docs"
|
|
"github.com/getAlby/lndhub.go/lib"
|
|
"github.com/getAlby/lndhub.go/lib/service"
|
|
"github.com/getAlby/lndhub.go/lib/tokens"
|
|
"github.com/getAlby/lndhub.go/lib/transport"
|
|
"github.com/getsentry/sentry-go"
|
|
"github.com/joho/godotenv"
|
|
"github.com/kelseyhightower/envconfig"
|
|
"github.com/labstack/echo/v4"
|
|
echoSwagger "github.com/swaggo/echo-swagger"
|
|
"github.com/uptrace/bun/migrate"
|
|
)
|
|
|
|
// @title LndHub.go
|
|
// @version 0.9.0
|
|
// @description Accounting wrapper for the Lightning Network providing separate accounts for end-users.
|
|
|
|
// @contact.name Alby
|
|
// @contact.url https://getalby.com
|
|
// @contact.email hello@getalby.com
|
|
|
|
// @license.name GNU GPLv3
|
|
// @license.url https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
|
|
// @BasePath /
|
|
|
|
// @securitydefinitions.oauth2.password OAuth2Password
|
|
// @tokenUrl /auth
|
|
// @schemes https http
|
|
func main() {
|
|
|
|
c := &service.Config{}
|
|
|
|
// Load configruation from environment variables
|
|
err := godotenv.Load(".env")
|
|
if err != nil {
|
|
fmt.Println("Failed to load .env file")
|
|
}
|
|
err = envconfig.Process("", c)
|
|
if err != nil {
|
|
log.Fatalf("Error loading environment variables: %v", err)
|
|
}
|
|
|
|
// Setup logging to STDOUT or a configrued log file
|
|
logger := lib.Logger(c.LogFilePath)
|
|
|
|
// Open a DB connection based on the configured DATABASE_URI
|
|
dbConn, err := db.Open(c)
|
|
if err != nil {
|
|
logger.Fatalf("Error initializing db connection: %v", err)
|
|
}
|
|
|
|
// Migrate the DB
|
|
//Todo: use timeout for startupcontext
|
|
startupCtx := context.Background()
|
|
migrator := migrate.NewMigrator(dbConn, migrations.Migrations)
|
|
err = migrator.Init(startupCtx)
|
|
if err != nil {
|
|
logger.Fatalf("Error initializing db migrator: %v", err)
|
|
}
|
|
_, err = migrator.Migrate(startupCtx)
|
|
if err != nil {
|
|
logger.Fatalf("Error migrating database: %v", err)
|
|
}
|
|
// Setup exception tracking with Sentry if configured
|
|
// sentry init needs to happen before the echo middlewares are added
|
|
if c.SentryDSN != "" {
|
|
if err = sentry.Init(sentry.ClientOptions{
|
|
Dsn: c.SentryDSN,
|
|
IgnoreErrors: []string{"401"},
|
|
EnableTracing: c.SentryTracesSampleRate > 0,
|
|
TracesSampleRate: c.SentryTracesSampleRate,
|
|
}); err != nil {
|
|
logger.Errorf("sentry init error: %v", err)
|
|
}
|
|
}
|
|
// Init new LND client
|
|
lnCfg, err := lnd.LoadConfig()
|
|
if err != nil {
|
|
logger.Fatalf("Error loading LN config: %v", err)
|
|
}
|
|
lndClient, err := lnd.InitLNClient(lnCfg, logger, startupCtx)
|
|
if err != nil {
|
|
logger.Fatalf("Error initializing the %s connection: %v", lnCfg.LNClientType, err)
|
|
}
|
|
|
|
logger.Infof("Connected to %s: %s", lnCfg.LNClientType, lndClient.GetMainPubkey())
|
|
|
|
// If no RABBITMQ_URI was provided we will not attempt to create a client
|
|
// No rabbitmq features will be available in this case.
|
|
var rabbitmqClient rabbitmq.Client
|
|
if c.RabbitMQUri != "" {
|
|
amqpClient, err := rabbitmq.DialAMQP(c.RabbitMQUri, rabbitmq.WithAmqpLogger(logger))
|
|
if err != nil {
|
|
logger.Fatal(err)
|
|
}
|
|
|
|
defer amqpClient.Close()
|
|
|
|
rabbitmqClient, err = rabbitmq.NewClient(amqpClient,
|
|
rabbitmq.WithLogger(logger),
|
|
rabbitmq.WithLndInvoiceExchange(c.RabbitMQLndInvoiceExchange),
|
|
rabbitmq.WithLndHubInvoiceExchange(c.RabbitMQLndhubInvoiceExchange),
|
|
rabbitmq.WithLndInvoiceConsumerQueueName(c.RabbitMQInvoiceConsumerQueueName),
|
|
rabbitmq.WithLndPaymentExchange(c.RabbitMQLndPaymentExchange),
|
|
rabbitmq.WithLndPaymentConsumerQueueName(c.RabbitMQPaymentConsumerQueueName),
|
|
)
|
|
if err != nil {
|
|
logger.Fatal(err)
|
|
}
|
|
|
|
// close the connection gently at the end of the runtime
|
|
defer rabbitmqClient.Close()
|
|
}
|
|
|
|
svc := &service.LndhubService{
|
|
Config: c,
|
|
DB: dbConn,
|
|
LndClient: lndClient,
|
|
Logger: logger,
|
|
InvoicePubSub: service.NewPubsub(),
|
|
RabbitMQClient: rabbitmqClient,
|
|
}
|
|
|
|
//init echo server
|
|
e := transport.InitEcho(c, logger)
|
|
//if Datadog is configured, add datadog middleware
|
|
if c.DatadogAgentUrl != "" {
|
|
tracer.Start(tracer.WithAgentAddr(c.DatadogAgentUrl))
|
|
defer tracer.Stop()
|
|
e.Use(ddEcho.Middleware(ddEcho.WithServiceName("lndhub.go")))
|
|
}
|
|
|
|
logMw := transport.CreateLoggingMiddleware(logger)
|
|
// strict rate limit for requests for sending payments
|
|
strictRateLimitMiddleware := transport.CreateRateLimitMiddleware(c.StrictRateLimit, c.BurstRateLimit)
|
|
secured := e.Group("", tokens.Middleware(c.JWTSecret), svc.ValidateUserMiddleware(), logMw)
|
|
securedWithStrictRateLimit := e.Group("", tokens.Middleware(c.JWTSecret), svc.ValidateUserMiddleware(), strictRateLimitMiddleware, logMw)
|
|
|
|
transport.RegisterLegacyEndpoints(svc, e, secured, securedWithStrictRateLimit, strictRateLimitMiddleware, tokens.AdminTokenMiddleware(c.AdminToken), logMw)
|
|
transport.RegisterV2Endpoints(svc, e, secured, securedWithStrictRateLimit, strictRateLimitMiddleware, tokens.AdminTokenMiddleware(c.AdminToken), logMw)
|
|
|
|
//Swagger API spec
|
|
docs.SwaggerInfo.Host = c.Host
|
|
e.GET("/swagger/*", echoSwagger.WrapHandler)
|
|
|
|
var backgroundWg sync.WaitGroup
|
|
backGroundCtx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
|
|
// Subscribe to LND invoice updates in the background
|
|
backgroundWg.Add(1)
|
|
go func() {
|
|
err = svc.StartInvoiceRoutine(backGroundCtx)
|
|
if err != nil {
|
|
sentry.CaptureException(err)
|
|
//we want to restart in case of an error here
|
|
svc.Logger.Fatal(err)
|
|
}
|
|
svc.Logger.Info("Invoice routine done")
|
|
backgroundWg.Done()
|
|
}()
|
|
|
|
// Check the status of all pending outgoing payments
|
|
backgroundWg.Add(1)
|
|
go func() {
|
|
err = svc.StartPendingPaymentRoutine(backGroundCtx)
|
|
if err != nil {
|
|
sentry.CaptureException(err)
|
|
//in case of an error here no restart is necessary
|
|
svc.Logger.Error(err)
|
|
}
|
|
|
|
svc.Logger.Info("Pending payment check routines done")
|
|
backgroundWg.Done()
|
|
}()
|
|
|
|
//Start webhook subscription
|
|
if svc.Config.WebhookUrl != "" {
|
|
backgroundWg.Add(1)
|
|
go func() {
|
|
svc.StartWebhookSubscription(backGroundCtx, svc.Config.WebhookUrl)
|
|
svc.Logger.Info("Webhook routine done")
|
|
backgroundWg.Done()
|
|
}()
|
|
}
|
|
//Start rabbit publisher
|
|
if svc.RabbitMQClient != nil {
|
|
backgroundWg.Add(1)
|
|
go func() {
|
|
err = svc.RabbitMQClient.StartPublishInvoices(backGroundCtx,
|
|
svc.SubscribeIncomingOutgoingInvoices,
|
|
svc.EncodeInvoiceWithUserLogin,
|
|
)
|
|
if err != nil {
|
|
svc.Logger.Error(err)
|
|
sentry.CaptureException(err)
|
|
}
|
|
|
|
svc.Logger.Info("Rabbit invoice publisher done")
|
|
backgroundWg.Done()
|
|
}()
|
|
}
|
|
|
|
//Start Prometheus server if necessary
|
|
var echoPrometheus *echo.Echo
|
|
if svc.Config.EnablePrometheus {
|
|
go transport.StartPrometheusEcho(logger, svc, e)
|
|
}
|
|
|
|
// Start server
|
|
go func() {
|
|
if err := e.Start(fmt.Sprintf(":%v", c.Port)); err != nil && err != http.ErrServerClosed {
|
|
e.Logger.Fatal("shutting down the server")
|
|
}
|
|
}()
|
|
|
|
<-backGroundCtx.Done()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
if err := e.Shutdown(ctx); err != nil {
|
|
e.Logger.Fatal(err)
|
|
}
|
|
if echoPrometheus != nil {
|
|
if err := echoPrometheus.Shutdown(ctx); err != nil {
|
|
e.Logger.Fatal(err)
|
|
}
|
|
}
|
|
//Wait for graceful shutdown of background routines
|
|
backgroundWg.Wait()
|
|
svc.Logger.Info("LNDhub exiting gracefully. Goodbye.")
|
|
}
|