264 lines
5.7 KiB
Go

package node
import (
"context"
"crypto/ed25519"
"encoding/base64"
"fmt"
"log/slog"
"net"
"os"
"strings"
"koti.casa/numenor-labs/dsfx/internal/lib/crypto/identity"
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
"koti.casa/numenor-labs/dsfx/internal/lib/logging"
"koti.casa/numenor-labs/dsfx/internal/lib/network"
"koti.casa/numenor-labs/dsfx/internal/lib/storage/scoped"
"koti.casa/numenor-labs/dsfx/internal/lib/system"
"koti.casa/numenor-labs/dsfx/internal/peer/conf"
)
type Node struct {
disk disk.Disk
system system.System
config scoped.StorageScope
storage scoped.StorageScope
conf conf.Conf
}
func New(disk disk.Disk, system system.System) *Node {
conf := conf.FromSystem(system)
config := scoped.New(disk, conf.ConfigDir)
storage := scoped.New(disk, conf.StorageDir)
return &Node{disk, system, config, storage, conf}
}
func (a *Node) Run(ctx context.Context) error {
opts := &slog.HandlerOptions{
AddSource: false,
Level: slog.LevelDebug,
}
logger := slog.New(slog.NewTextHandler(a.system.Stdout(), opts))
slog.SetDefault(logger)
ctx = logging.WithContext(ctx, logger)
err := a.disk.MkdirAll(a.conf.ConfigDir, 0755)
if err != nil {
logger.ErrorContext(ctx, "failed to create config dir", slog.Any("error", err))
return err
}
err = a.disk.MkdirAll(a.conf.StorageDir, 0755)
if err != nil {
logger.ErrorContext(ctx, "failed to create storage dir", slog.Any("error", err))
return err
}
id, err := a.loadIdentity()
if err != nil {
logger.ErrorContext(ctx, "failed to read key file", slog.Any("error", err))
return err
}
admins, err := a.loadAdmins()
if err != nil {
logger.ErrorContext(ctx, "failed to read admins file", slog.Any("error", err))
return err
}
if len(admins) == 0 {
logger.WarnContext(ctx, "no admins found", slog.String("admins", "none"))
}
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
}
logger.InfoContext(ctx, "serving", slog.String("address", addr.String()))
for {
conn, err := listener.Accept()
if err != nil {
logger.ErrorContext(ctx, "accept failure", slog.Any("error", err))
continue
}
go handleConnection(ctx, conn)
}
}
// loadAdmins ...
func (c *Node) loadAdmins() ([]string, error) {
hasKeyFile, err := c.hasAdminsFile()
if err != nil {
return nil, fmt.Errorf("failed to check for admins file: %w", err)
}
if !hasKeyFile {
if err := c.createAdminsFile(); err != nil {
return nil, fmt.Errorf("failed to create admins file: %w", err)
}
}
return c.readAdminsFile()
}
// hasAdminsFile ...
func (c *Node) hasAdminsFile() (bool, error) {
f, err := c.config.Open("admins")
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
defer f.Close()
return true, nil
}
// createAdminsFile ...
func (c *Node) createAdminsFile() error {
f, err := c.config.Create("admins")
if err != nil {
return err
}
defer f.Close()
return nil
}
// readAdminsFile ...
func (c *Node) readAdminsFile() ([]string, error) {
f, err := c.config.Open("admins")
if err != nil {
return nil, err
}
defer f.Close()
keyRaw := make([]byte, 0)
_, err = f.Read(keyRaw)
if err != nil {
return nil, err
}
lines := strings.SplitSeq(string(keyRaw), "\n")
nonEmptyLines := []string{}
for line := range lines {
if strings.TrimSpace(line) != "" {
nonEmptyLines = append(nonEmptyLines, line)
}
}
return nonEmptyLines, nil
}
// loadIdentity loads the private key from the key file. If the key file does not
// exist, it creates a new key file with a generated private key and returns it.
func (c *Node) loadIdentity() (ed25519.PrivateKey, error) {
hasKeyFile, err := c.hasKeyFile()
if err != nil {
return nil, err
}
if hasKeyFile {
return c.readKeyFile()
}
if err := c.createKeyFile(); err != nil {
return nil, err
}
return c.readKeyFile()
}
// hasKeyFile checks if the key file exists in the disk storage.
func (c *Node) hasKeyFile() (bool, error) {
f, err := c.config.Open("key")
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
defer f.Close()
return true, nil
}
// createKeyFile creates a new key file with a generated private key.
func (c *Node) createKeyFile() error {
f, err := c.config.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
}
// readKeyFile reads the private key from the key file and returns it as an ed25519.PrivateKey.
func (c *Node) readKeyFile() (ed25519.PrivateKey, error) {
f, err := c.config.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
}
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
}