package peer 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" ) 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.LevelDebug, } 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 { if os.IsNotExist(err) { return false, 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("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 (p *Peer) createKeyFile() error { f, err := p.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 (p *Peer) readKeyFile() (ed25519.PrivateKey, error) { f, err := p.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 (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 }