Files
comicverse/cmd/cmd.go

208 lines
5.4 KiB
Go
Raw Normal View History

2025-03-07 20:34:31 -03:00
package main
2025-03-05 10:05:21 -03:00
import (
"context"
"crypto/ed25519"
"database/sql"
"encoding/base64"
2025-03-05 10:05:21 -03:00
"errors"
"flag"
"fmt"
"log"
2025-03-05 10:05:21 -03:00
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
2025-10-13 15:26:31 -03:00
comicverse "code.capytal.cc/capytal/comicverse"
"code.capytal.cc/capytal/comicverse/templates"
"code.capytal.cc/loreddev/x/tinyssert"
2025-03-11 09:46:03 -03:00
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
_ "github.com/tursodatabase/go-libsql"
2025-03-05 10:05:21 -03:00
)
var (
2025-03-07 20:33:39 -03:00
hostname = flag.String("hostname", "localhost", "Host to listen to")
2025-03-05 10:05:21 -03:00
port = flag.Uint("port", 8080, "Port to be used for the server.")
templatesDir = flag.String("templates", "", "Templates directory to be used instead of built-in ones.")
verbose = flag.Bool("verbose", false, "Print debug information on logs")
dev = flag.Bool("dev", false, "Run the server in debug mode.")
)
var (
2025-03-12 10:01:59 -03:00
databaseURL = getEnv("DATABASE_URL", "file:./libsql.db")
2025-03-11 09:46:03 -03:00
awsAccessKeyID = os.Getenv("AWS_ACCESS_KEY_ID")
awsSecretAccessKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
awsDefaultRegion = os.Getenv("AWS_DEFAULT_REGION")
awsEndpointURL = os.Getenv("AWS_ENDPOINT_URL")
s3Bucket = os.Getenv("S3_BUCKET")
privateKeyEnv = os.Getenv("PRIVATE_KEY")
publicKeyEnv = os.Getenv("PUBLIC_KEY")
)
func getEnv(key string, d string) string {
v := os.Getenv(key)
if v == "" {
return d
}
return v
}
2025-03-05 10:05:21 -03:00
func init() {
flag.Parse()
switch {
case databaseURL == "":
log.Fatal("DATABASE_URL should not be a empty value")
2025-03-11 09:46:03 -03:00
case awsAccessKeyID == "":
log.Fatal("AWS_ACCESS_KEY_ID should not be a empty value")
case awsDefaultRegion == "":
log.Fatal("AWS_DEFAULT_REGION should not be a empty value")
case awsEndpointURL == "":
log.Fatal("AWS_ENDPOINT_URL should not be a empty value")
case s3Bucket == "":
log.Fatal("S3_BUCKET should not be a empty value")
case privateKeyEnv == "":
log.Fatal("PRIVATE_KEY not be a empty value")
case publicKeyEnv == "":
log.Fatal("PUBLIC_KEY not be a empty value")
}
2025-03-05 10:05:21 -03:00
}
func main() {
2025-03-05 10:05:21 -03:00
ctx := context.Background()
level := slog.LevelError
if *dev {
level = slog.LevelDebug
} else if *verbose {
level = slog.LevelInfo
}
log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level}))
2025-05-30 18:05:24 -03:00
assertions := tinyssert.NewDisabled()
if *dev {
assertions = tinyssert.New(
tinyssert.WithPanic(),
tinyssert.WithLogger(log),
)
}
db, err := sql.Open("libsql", databaseURL)
if err != nil {
log.Error("Failed open connection to database", slog.String("error", err.Error()))
os.Exit(1)
}
2025-03-11 09:46:03 -03:00
credentials := aws.CredentialsProviderFunc(func(ctx context.Context) (aws.Credentials, error) {
return aws.Credentials{
AccessKeyID: awsAccessKeyID,
SecretAccessKey: awsSecretAccessKey,
CanExpire: false,
}, nil
})
storage := s3.New(s3.Options{
AppID: "comicverse-pre-alpha",
BaseEndpoint: &awsEndpointURL,
Region: awsDefaultRegion,
Credentials: &credentials,
})
2025-03-07 20:40:31 -03:00
opts := []comicverse.Option{
comicverse.WithContext(ctx),
comicverse.WithAssertions(assertions),
comicverse.WithLogger(log),
}
if *dev {
d := os.DirFS("./assets")
opts = append(opts, comicverse.WithAssets(d))
t := templates.NewHotTemplates(os.DirFS("./templates"))
opts = append(opts, comicverse.WithTemplates(t))
2025-03-07 20:40:31 -03:00
opts = append(opts, comicverse.WithDevelopmentMode())
}
// TODO: Move this to dedicated function
privateKeyStr, err := base64.URLEncoding.DecodeString(privateKeyEnv)
if err != nil {
log.Error("Failed to decode PRIVATE_KEY from base64", slog.String("error", err.Error()))
os.Exit(1)
}
publicKeyStr, err := base64.URLEncoding.DecodeString(publicKeyEnv)
if err != nil {
log.Error("Failed to decode PUBLIC_KEY from base64", slog.String("error", err.Error()))
os.Exit(1)
}
edPrivKey := ed25519.PrivateKey(privateKeyStr)
edPubKey := ed25519.PublicKey(publicKeyStr)
if len(edPrivKey) != ed25519.PrivateKeySize {
log.Error("PRIVATE_KEY is not of valid size", slog.Int("size", len(edPrivKey)))
os.Exit(1)
}
if len(edPubKey) != ed25519.PublicKeySize {
log.Error("PUBLIC_KEY is not of valid size", slog.Int("size", len(edPubKey)))
os.Exit(1)
}
if !edPubKey.Equal(edPrivKey.Public()) {
log.Error("PUBLIC_KEY is not equal from extracted public key",
slog.String("extracted", fmt.Sprintf("%x", edPrivKey.Public())),
slog.String("key", fmt.Sprintf("%x", edPubKey)),
)
os.Exit(1)
}
2025-03-07 20:40:31 -03:00
app, err := comicverse.New(comicverse.Config{
DB: db,
S3: storage,
PrivateKey: edPrivKey,
PublicKey: edPubKey,
Bucket: s3Bucket,
2025-03-07 20:40:31 -03:00
}, opts...)
if err != nil {
log.Error("Failed to initiate comicverse app", slog.String("error", err.Error()))
os.Exit(1)
}
2025-03-05 10:05:21 -03:00
srv := &http.Server{
2025-03-07 20:33:39 -03:00
Addr: fmt.Sprintf("%s:%d", *hostname, *port),
2025-03-05 10:05:21 -03:00
Handler: app,
}
c, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
defer stop()
go func() {
log.Info("Starting application",
2025-03-07 20:33:39 -03:00
slog.String("host", *hostname),
2025-03-05 10:05:21 -03:00
slog.Uint64("port", uint64(*port)),
slog.Bool("verbose", *verbose),
slog.Bool("development", *dev))
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
2025-03-07 20:34:08 -03:00
log.Error("Failed to start application server", slog.String("error", err.Error()))
os.Exit(1)
2025-03-05 10:05:21 -03:00
}
}()
<-c.Done()
log.Info("Stopping application gracefully")
if err := srv.Shutdown(ctx); err != nil {
2025-03-07 20:34:08 -03:00
log.Error("Failed to stop application server gracefully", slog.String("error", err.Error()))
os.Exit(1)
2025-03-05 10:05:21 -03:00
}
log.Info("FINAL")
os.Exit(0)
}