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: true, Level: slog.LevelDebug, } logger := slog.New(slog.NewJSONHandler(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 } _, err = a.loadAdmins() if err != nil { logger.ErrorContext(ctx, "failed to read admins file", slog.Any("error", err)) return err } 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) } } // 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 }