2025-03-21 20:23:15 -04:00
|
|
|
package node
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/ed25519"
|
|
|
|
"encoding/base64"
|
|
|
|
"fmt"
|
|
|
|
"log/slog"
|
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
|
2025-03-21 22:51:04 -04:00
|
|
|
"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"
|
2025-03-22 13:06:51 -04:00
|
|
|
"koti.casa/numenor-labs/dsfx/internal/lib/storage/scoped"
|
2025-03-21 22:51:04 -04:00
|
|
|
"koti.casa/numenor-labs/dsfx/internal/lib/system"
|
|
|
|
|
|
|
|
"koti.casa/numenor-labs/dsfx/internal/peer/conf"
|
2025-03-21 20:23:15 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
type Node struct {
|
2025-03-22 13:06:51 -04:00
|
|
|
disk disk.Disk
|
|
|
|
system system.System
|
|
|
|
config scoped.StorageScope
|
|
|
|
storage scoped.StorageScope
|
|
|
|
conf conf.Conf
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
func New(disk disk.Disk, system system.System) *Node {
|
2025-03-21 20:23:15 -04:00
|
|
|
conf := conf.FromSystem(system)
|
2025-03-22 13:06:51 -04:00
|
|
|
|
|
|
|
config := scoped.New(disk, conf.ConfigDir)
|
|
|
|
storage := scoped.New(disk, conf.StorageDir)
|
|
|
|
|
|
|
|
return &Node{disk, system, config, storage, conf}
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Node) Run(ctx context.Context) error {
|
|
|
|
opts := &slog.HandlerOptions{
|
2025-03-22 13:18:27 -04:00
|
|
|
AddSource: false,
|
2025-03-21 20:23:15 -04:00
|
|
|
Level: slog.LevelDebug,
|
|
|
|
}
|
2025-03-22 13:18:27 -04:00
|
|
|
logger := slog.New(slog.NewTextHandler(a.system.Stdout(), opts))
|
2025-03-21 20:23:15 -04:00
|
|
|
|
|
|
|
slog.SetDefault(logger)
|
|
|
|
ctx = logging.WithContext(ctx, logger)
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
err := a.disk.MkdirAll(a.conf.ConfigDir, 0755)
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
2025-03-22 13:06:51 -04:00
|
|
|
logger.ErrorContext(ctx, "failed to create config dir", slog.Any("error", err))
|
2025-03-21 20:23:15 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
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
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
id, err := a.loadIdentity()
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
|
|
|
logger.ErrorContext(ctx, "failed to read key file", slog.Any("error", err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2025-03-22 13:18:27 -04:00
|
|
|
admins, err := a.loadAdmins()
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
2025-03-22 13:06:51 -04:00
|
|
|
logger.ErrorContext(ctx, "failed to read admins file", slog.Any("error", err))
|
2025-03-21 20:23:15 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2025-03-22 13:18:27 -04:00
|
|
|
if len(admins) == 0 {
|
|
|
|
logger.WarnContext(ctx, "no admins found", slog.String("admins", "none"))
|
|
|
|
}
|
|
|
|
|
2025-03-21 20:23:15 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2025-03-22 13:18:27 -04:00
|
|
|
logger.InfoContext(ctx, "serving", slog.String("address", addr.String()))
|
|
|
|
|
2025-03-21 20:23:15 -04:00
|
|
|
for {
|
|
|
|
conn, err := listener.Accept()
|
|
|
|
if err != nil {
|
|
|
|
logger.ErrorContext(ctx, "accept failure", slog.Any("error", err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
go handleConnection(ctx, conn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
// loadAdmins ...
|
|
|
|
func (c *Node) loadAdmins() ([]string, error) {
|
|
|
|
hasKeyFile, err := c.hasAdminsFile()
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
2025-03-22 13:06:51 -04:00
|
|
|
return nil, fmt.Errorf("failed to check for admins file: %w", err)
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
if !hasKeyFile {
|
|
|
|
if err := c.createAdminsFile(); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create admins file: %w", err)
|
|
|
|
}
|
|
|
|
}
|
2025-03-21 20:23:15 -04:00
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
return c.readAdminsFile()
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
// hasAdminsFile ...
|
|
|
|
func (c *Node) hasAdminsFile() (bool, error) {
|
|
|
|
f, err := c.config.Open("admins")
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// createAdminsFile ...
|
|
|
|
func (c *Node) createAdminsFile() error {
|
|
|
|
f, err := c.config.Create("admins")
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
2025-03-22 13:06:51 -04:00
|
|
|
return err
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
2025-03-22 13:06:51 -04:00
|
|
|
defer f.Close()
|
2025-03-21 20:23:15 -04:00
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
return nil
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
// readAdminsFile ...
|
|
|
|
func (c *Node) readAdminsFile() ([]string, error) {
|
|
|
|
f, err := c.config.Open("admins")
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
2025-03-22 13:06:51 -04:00
|
|
|
return nil, err
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
keyRaw := make([]byte, 0)
|
|
|
|
_, err = f.Read(keyRaw)
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
2025-03-22 13:06:51 -04:00
|
|
|
return nil, err
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
lines := strings.SplitSeq(string(keyRaw), "\n")
|
|
|
|
|
|
|
|
nonEmptyLines := []string{}
|
|
|
|
for line := range lines {
|
|
|
|
if strings.TrimSpace(line) != "" {
|
|
|
|
nonEmptyLines = append(nonEmptyLines, line)
|
|
|
|
}
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
return nonEmptyLines, nil
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
// 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()
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
if hasKeyFile {
|
|
|
|
return c.readKeyFile()
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
if err := c.createKeyFile(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2025-03-21 20:23:15 -04:00
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
return c.readKeyFile()
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
// hasKeyFile checks if the key file exists in the disk storage.
|
|
|
|
func (c *Node) hasKeyFile() (bool, error) {
|
|
|
|
f, err := c.config.Open("key")
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
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")
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
2025-03-22 13:06:51 -04:00
|
|
|
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
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
return nil
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
// 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")
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
keyRaw := make([]byte, ed25519.PrivateKeySize)
|
|
|
|
n, err := f.Read(keyRaw)
|
2025-03-21 20:23:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2025-03-22 13:06:51 -04:00
|
|
|
if n != ed25519.PrivateKeySize {
|
|
|
|
return nil, fmt.Errorf("key file is not the correct size: %d", n)
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
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
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 13:06:51 -04:00
|
|
|
logger.InfoContext(ctx, "received msg", slog.Int("bytes", n))
|
|
|
|
|
|
|
|
return nil
|
2025-03-21 20:23:15 -04:00
|
|
|
}
|