mirror of
https://git.numenor-labs.us/dsfx.git
synced 2025-04-29 08:10:34 +00:00
244 lines
5.2 KiB
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
|
|
}
|