dsfx/cmd/dsfxnode/node/node.go

244 lines
5.2 KiB
Go

package node
import (
"context"
"crypto/ed25519"
"encoding/base64"
"fmt"
"log/slog"
"net"
"os"
"strings"
"koti.casa/numenor-labs/dsfx/cmd/dsfxnode/conf"
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
"koti.casa/numenor-labs/dsfx/pkg/disk"
"koti.casa/numenor-labs/dsfx/pkg/logging"
"koti.casa/numenor-labs/dsfx/pkg/network"
"koti.casa/numenor-labs/dsfx/pkg/system"
)
type Node struct {
config disk.Disk
system system.System
conf conf.Conf
}
func New(config disk.Disk, system system.System) *Node {
conf := conf.FromSystem(system)
return &Node{config, system, conf}
}
func (a *Node) Run(ctx context.Context) error {
opts := &slog.HandlerOptions{
AddSource: false,
Level: slog.LevelDebug,
}
logger := slog.New(slog.NewJSONHandler(a.system.Stdout(), opts))
// Everything in the application will attempt to use the logger in stored in
// the context, but we also set the default with slog as a fallback. In cases
// where the context is not available, or the context is not a child of the
// context with the logger, the default logger will be used.
slog.SetDefault(logger)
ctx = logging.WithContext(ctx, logger)
ki := &KeyInit{disk: a.config}
// Check if the key file exists and is not empty
hasKey, err := ki.Has()
if err != nil {
logger.ErrorContext(ctx, "failed to check key file", slog.Any("error", err))
return err
}
if !hasKey {
logger.InfoContext(ctx, "key file does not exist or is empty, generating new key")
err = ki.Init()
if err != nil {
logger.ErrorContext(ctx, "failed to initialize key file", slog.Any("error", err))
return err
}
}
// Read the key file
id, err := ki.Read()
if err != nil {
logger.ErrorContext(ctx, "failed to read key file", slog.Any("error", err))
return err
}
admins := []string{}
ai := &AdminInit{disk: a.config}
// Check if the admins file exists and is not empty
hasAdmins, err := ai.Has()
if err != nil {
logger.ErrorContext(ctx, "failed to check admins file", slog.Any("error", err))
return err
}
if hasAdmins {
logger.InfoContext(ctx, "admins file exists, reading admins")
admins, err = ai.Read()
if err != nil {
logger.ErrorContext(ctx, "failed to read admins file", slog.Any("error", err))
return err
}
logger.InfoContext(ctx, "loaded admins", slog.Any("admins", admins))
} else {
logger.WarnContext(ctx, "admins file does not exist or is empty, no admins will be loaded")
}
tcpAddrRaw := net.JoinHostPort(a.conf.Host, a.conf.Port)
tcpAddr, err := net.ResolveTCPAddr("tcp", tcpAddrRaw)
if err != nil {
return err
}
addr := network.FromTCPAddr(tcpAddr, id.Public().(ed25519.PublicKey))
// ---------------------------------------------------------------------------
// Listener
listener, err := network.Listen(ctx, id, addr)
if err != nil {
logger.ErrorContext(ctx, "listener error", slog.Any("error", err))
return err
}
for {
conn, err := listener.Accept()
if err != nil {
logger.ErrorContext(ctx, "accept failure", slog.Any("error", err))
continue
}
go handleConnection(ctx, conn)
}
}
func handleConnection(ctx context.Context, conn net.Conn) error {
defer conn.Close()
logger := logging.FromContext(ctx)
msg := make([]byte, 1024)
n, err := conn.Read(msg)
if err != nil {
logger.ErrorContext(ctx, "failed to read from connection", slog.Any("error", err))
return err
}
logger.InfoContext(ctx, "received msg", slog.Int("bytes", n))
return nil
}
type KeyInit struct{ disk disk.Disk }
func (ki *KeyInit) Has() (bool, error) {
f, err := ki.disk.Open("key")
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
defer f.Close()
stats, err := f.Stat()
if err != nil {
return false, err
}
return stats.Size() > 0, nil
}
func (ki *KeyInit) Init() error {
f, err := ki.disk.Create("key")
if err != nil {
return err
}
defer f.Close()
privkey, err := identity.Generate()
if err != nil {
return err
}
_, err = f.Write([]byte(base64.StdEncoding.EncodeToString(privkey)))
if err != nil {
return err
}
return nil
}
func (ki *KeyInit) Read() (ed25519.PrivateKey, error) {
f, err := ki.disk.Open("key")
if err != nil {
return nil, err
}
defer f.Close()
keyRaw := make([]byte, ed25519.PrivateKeySize)
n, err := f.Read(keyRaw)
if err != nil {
return nil, err
}
if n != ed25519.PrivateKeySize {
return nil, fmt.Errorf("key file is not the correct size: %d", n)
}
return ed25519.PrivateKey(keyRaw), nil
}
type AdminInit struct {
disk disk.Disk
}
func (ai *AdminInit) Has() (bool, error) {
f, err := ai.disk.Open("admins")
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
defer f.Close()
stats, err := f.Stat()
if err != nil {
return false, err
}
return stats.Size() > 0, nil
}
func (ai *AdminInit) Read() ([]string, error) {
f, err := ai.disk.Open("admins")
if err != nil {
return nil, err
}
defer f.Close()
adminsRaw := make([]byte, 0)
n, err := f.Read(adminsRaw)
if err != nil {
return nil, err
}
if n == 0 {
return nil, fmt.Errorf("admins file is empty")
}
rawAdmins := strings.Split(string(adminsRaw), "\n")
var admins []string
for _, admin := range rawAdmins {
if admin != "" {
admins = append(admins, admin)
}
}
return admins, nil
}