dsfx/internal/peer/peer.go

307 lines
6.6 KiB
Go
Raw Normal View History

package peer
import (
"context"
"crypto/ed25519"
"encoding/base64"
"fmt"
"io"
"log/slog"
"net"
"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"
)
const (
// DefaultConfigDir is the default directory for the dsfx configuration.
DefaultConfigDir = "/etc/dsfx/config"
// DefaultStorageDir is the default directory for the dsfx storage.
DefaultStorageDir = "/etc/dsfx/data"
// DefaultHost is the default host for the dsfxctl application.
DefaultHost = "0.0.0.0"
// DefaultPort is the default port for the dsfxctl application.
DefaultPort = "8000"
)
// Conf holds the configuration for the dsfxctl application.
type Conf struct {
// Directories
ConfigDir string
StorageDir string
// Networking
Host string
Port string
}
func loadConfigFromSystem(sys system.System) Conf {
var c Conf
c.ConfigDir = sys.GetEnv("DSFX_CONFIG_DIR")
if c.ConfigDir == "" {
c.ConfigDir = DefaultConfigDir
}
c.StorageDir = sys.GetEnv("DSFX_STORAGE_DIR")
if c.StorageDir == "" {
c.StorageDir = DefaultStorageDir
}
c.Host = sys.GetEnv("DSFX_HOST")
if c.Host == "" {
c.Host = DefaultHost
}
c.Port = sys.GetEnv("DSFX_PORT")
if c.Port == "" {
c.Port = DefaultPort
}
return c
}
type Peer struct {
disk disk.Disk
system system.System
config scoped.StorageScope
storage scoped.StorageScope
conf Conf
}
func New(disk disk.Disk, system system.System) *Peer {
conf := loadConfigFromSystem(system)
config := scoped.New(disk, conf.ConfigDir)
storage := scoped.New(disk, conf.StorageDir)
return &Peer{disk, system, config, storage, conf}
}
func (p *Peer) Run(ctx context.Context) error {
opts := &slog.HandlerOptions{
AddSource: false,
Level: slog.LevelInfo,
}
logger := slog.New(slog.NewTextHandler(p.system.Stdout(), opts))
slog.SetDefault(logger)
ctx = logging.WithContext(ctx, logger)
err := p.disk.MkdirAll(p.conf.ConfigDir, 0755)
if err != nil {
logger.ErrorContext(ctx, "failed to create config dir", slog.Any("error", err))
return err
}
err = p.disk.MkdirAll(p.conf.StorageDir, 0755)
if err != nil {
logger.ErrorContext(ctx, "failed to create storage dir", slog.Any("error", err))
return err
}
id, err := p.loadIdentity()
if err != nil {
logger.ErrorContext(ctx, "failed to read key file", slog.Any("error", err))
return err
}
admins, err := p.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(p.conf.Host, p.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 p.handleConnection(ctx, conn)
}
}
// loadAdmins ...
func (p *Peer) loadAdmins() ([]string, error) {
hasKeyFile, err := p.hasAdminsFile()
if err != nil {
return nil, fmt.Errorf("failed to check for admins file: %w", err)
}
if !hasKeyFile {
if err := p.createAdminsFile(); err != nil {
return nil, fmt.Errorf("failed to create admins file: %w", err)
}
}
return p.readAdminsFile()
}
// hasAdminsFile ...
func (p *Peer) hasAdminsFile() (bool, error) {
f, err := p.config.Open("admins")
if err != nil {
return false, err
}
defer f.Close()
return true, nil
}
// createAdminsFile ...
func (p *Peer) createAdminsFile() error {
f, err := p.config.Create("admins")
if err != nil {
return err
}
defer f.Close()
return nil
}
// readAdminsFile ...
func (p *Peer) readAdminsFile() ([]string, error) {
f, err := p.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 (p *Peer) loadIdentity() (ed25519.PrivateKey, error) {
hasKeyFile, err := p.hasKeyFile()
if err != nil {
return nil, err
}
if hasKeyFile {
return p.readKeyFile()
}
if err := p.createKeyFile(); err != nil {
return nil, err
}
return p.readKeyFile()
}
// hasKeyFile checks if the key file exists in the disk storage.
func (p *Peer) hasKeyFile() (bool, error) {
f, err := p.config.Open("ed25519.key")
if err != nil {
return false, err
}
defer f.Close()
return true, nil
}
// createKeyFile creates a new key file with a generated private key.
func (p *Peer) createKeyFile() error {
f, err := p.config.Create("ed25519.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 (p *Peer) readKeyFile() (ed25519.PrivateKey, error) {
f, err := p.config.Open("ed25519.key")
if err != nil {
return nil, err
}
defer f.Close()
keyRawBase64, err := io.ReadAll(f)
if err != nil {
return nil, err
}
keyRaw, err := base64.StdEncoding.DecodeString(string(keyRawBase64))
if err != nil {
return nil, err
}
if len(keyRaw) != ed25519.PrivateKeySize {
return nil, fmt.Errorf("key file is not the correct size: %d", len(keyRaw))
}
return ed25519.PrivateKey(keyRaw), nil
}
func (p *Peer) 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
}