mirror of
https://git.numenor-labs.us/dsfx.git
synced 2025-04-29 08:10:34 +00:00
fix(internal/peer): automatically create key and admin files
This commit is contained in:
parent
336b371449
commit
a74cdb8b02
@ -2,30 +2,17 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
|
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
|
||||||
"koti.casa/numenor-labs/dsfx/internal/lib/system"
|
"koti.casa/numenor-labs/dsfx/internal/lib/system"
|
||||||
"koti.casa/numenor-labs/dsfx/internal/peer/node"
|
"koti.casa/numenor-labs/dsfx/internal/peer/node"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/internal/lib/storage/scoped"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
n := node.New(disk.Default(), system.Default())
|
||||||
|
|
||||||
sys := system.Default()
|
err := n.Run(context.Background())
|
||||||
|
|
||||||
configDir := sys.GetEnv("DSFXNODE_CONFIG_DIR")
|
|
||||||
if configDir == "" {
|
|
||||||
configDir = "/etc/dsfxnode/config"
|
|
||||||
}
|
|
||||||
configScope := scoped.New(disk.Default(), configDir)
|
|
||||||
|
|
||||||
err := node.New(configScope, sys).Run(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log the error and exit with a non-zero status code.
|
panic(err)
|
||||||
slog.Error("Error running dsfxnode", slog.Any("error", err))
|
|
||||||
sys.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,3 +52,21 @@ DSFX uses the following environment variables to configure its behavior:
|
|||||||
| DSFX_PORT | The port on which the DSFX server will listen | 8000 |
|
| DSFX_PORT | The port on which the DSFX server will listen | 8000 |
|
||||||
| DSFX_CONFIG_DIR | The directory where the DSFX configuration files are stored | /etc/dsfx/config |
|
| DSFX_CONFIG_DIR | The directory where the DSFX configuration files are stored | /etc/dsfx/config |
|
||||||
| DSFX_STORAGE_DIR | The directory where the DSFX storage files are stored | /etx/dsfx/data |
|
| DSFX_STORAGE_DIR | The directory where the DSFX storage files are stored | /etx/dsfx/data |
|
||||||
|
|
||||||
|
## Local Files
|
||||||
|
|
||||||
|
The DSFX server uses local files for configuration and storage. The default directories for these
|
||||||
|
files are specified in the `DSFX_CONFIG_DIR` and `DSFX_STORAGE_DIR` environment variables. You can
|
||||||
|
change these directories by setting the corresponding environment variables before starting the server.
|
||||||
|
|
||||||
|
For docker installations, it is recommended to mount the local directories to the container
|
||||||
|
using the `-v` flag. For example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name dsfx \
|
||||||
|
-p 8000:8000 \
|
||||||
|
-v /path/to/local/config:/etc/dsfx/config \
|
||||||
|
-v /path/to/local/data:/etx/dsfx/data \
|
||||||
|
koti.casa/numenorlabs/dsfx:latest
|
||||||
|
```
|
||||||
|
@ -36,6 +36,18 @@ func (d *osDisk) Mkdir(name string, perm fs.FileMode) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MkdirAll implements Disk.
|
||||||
|
// This creates a directory and any necessary parent directories.
|
||||||
|
// If the directory already exists, it does nothing.
|
||||||
|
// It returns an error if the directory cannot be created.
|
||||||
|
func (d *osDisk) MkdirAll(name string, perm fs.FileMode) error {
|
||||||
|
ts := time.Now()
|
||||||
|
err := os.MkdirAll(name, perm)
|
||||||
|
el := time.Now().Sub(ts).String()
|
||||||
|
d.logger.Debug("(io) disk.MkdirAll", "duration", el)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Open implements Disk.
|
// Open implements Disk.
|
||||||
func (d *osDisk) Open(name string) (File, error) {
|
func (d *osDisk) Open(name string) (File, error) {
|
||||||
ts := time.Now()
|
ts := time.Now()
|
||||||
|
@ -21,6 +21,8 @@ type File interface {
|
|||||||
type Disk interface {
|
type Disk interface {
|
||||||
// Mkdir creates a directory with the specified name and permissions.
|
// Mkdir creates a directory with the specified name and permissions.
|
||||||
Mkdir(name string, perm fs.FileMode) error
|
Mkdir(name string, perm fs.FileMode) error
|
||||||
|
// MkdirAll creates a directory and any necessary parent directories.
|
||||||
|
MkdirAll(name string, perm fs.FileMode) error
|
||||||
// Create creates a new file with the specified name and returns a File interface.
|
// Create creates a new file with the specified name and returns a File interface.
|
||||||
Create(name string) (File, error)
|
Create(name string) (File, error)
|
||||||
// Stat retrieves the file information for the specified name, returning
|
// Stat retrieves the file information for the specified name, returning
|
||||||
@ -34,6 +36,7 @@ type Disk interface {
|
|||||||
|
|
||||||
type MockDisk struct {
|
type MockDisk struct {
|
||||||
MkdirFunc func(name string, perm fs.FileMode) error
|
MkdirFunc func(name string, perm fs.FileMode) error
|
||||||
|
MkdirAllFunc func(name string, perm fs.FileMode) error
|
||||||
CreateFunc func(name string) (File, error)
|
CreateFunc func(name string) (File, error)
|
||||||
StatFunc func(name string) (fs.FileInfo, error)
|
StatFunc func(name string) (fs.FileInfo, error)
|
||||||
OpenFunc func(name string) (File, error)
|
OpenFunc func(name string) (File, error)
|
||||||
@ -49,6 +52,13 @@ func (t *MockDisk) Mkdir(name string, perm fs.FileMode) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *MockDisk) MkdirAll(name string, perm fs.FileMode) error {
|
||||||
|
if t.MkdirAllFunc != nil {
|
||||||
|
return t.MkdirAllFunc(name, perm)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *MockDisk) Create(name string) (File, error) {
|
func (t *MockDisk) Create(name string) (File, error) {
|
||||||
if t.CreateFunc != nil {
|
if t.CreateFunc != nil {
|
||||||
return t.CreateFunc(name)
|
return t.CreateFunc(name)
|
||||||
|
@ -38,6 +38,11 @@ func (s *scoped) Mkdir(name string, perm fs.FileMode) error {
|
|||||||
return s.disk.Mkdir(s.path(name), perm)
|
return s.disk.Mkdir(s.path(name), perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MkdirAll implements disk.Disk.
|
||||||
|
func (s *scoped) MkdirAll(name string, perm fs.FileMode) error {
|
||||||
|
return s.disk.MkdirAll(s.path(name), perm)
|
||||||
|
}
|
||||||
|
|
||||||
// Open implements disk.Disk.
|
// Open implements disk.Disk.
|
||||||
func (s *scoped) Open(name string) (disk.File, error) {
|
func (s *scoped) Open(name string) (disk.File, error) {
|
||||||
return s.disk.Open(s.path(name))
|
return s.disk.Open(s.path(name))
|
||||||
|
@ -14,82 +14,62 @@ import (
|
|||||||
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
|
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
|
||||||
"koti.casa/numenor-labs/dsfx/internal/lib/logging"
|
"koti.casa/numenor-labs/dsfx/internal/lib/logging"
|
||||||
"koti.casa/numenor-labs/dsfx/internal/lib/network"
|
"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/lib/system"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/internal/peer/conf"
|
"koti.casa/numenor-labs/dsfx/internal/peer/conf"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
config disk.Disk
|
disk disk.Disk
|
||||||
system system.System
|
system system.System
|
||||||
|
config scoped.StorageScope
|
||||||
|
storage scoped.StorageScope
|
||||||
conf conf.Conf
|
conf conf.Conf
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config disk.Disk, system system.System) *Node {
|
func New(disk disk.Disk, system system.System) *Node {
|
||||||
conf := conf.FromSystem(system)
|
conf := conf.FromSystem(system)
|
||||||
return &Node{config, system, conf}
|
|
||||||
|
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 {
|
func (a *Node) Run(ctx context.Context) error {
|
||||||
opts := &slog.HandlerOptions{
|
opts := &slog.HandlerOptions{
|
||||||
AddSource: false,
|
AddSource: true,
|
||||||
Level: slog.LevelDebug,
|
Level: slog.LevelDebug,
|
||||||
}
|
}
|
||||||
logger := slog.New(slog.NewJSONHandler(a.system.Stdout(), opts))
|
logger := slog.New(slog.NewJSONHandler(a.system.Stdout(), opts))
|
||||||
|
|
||||||
// Everything in the application will attempt to use the logger in stored in
|
|
||||||
// the context, but we also set the default with slog as a fallback. In cases
|
|
||||||
// where the context is not available, or the context is not a child of the
|
|
||||||
// context with the logger, the default logger will be used.
|
|
||||||
slog.SetDefault(logger)
|
slog.SetDefault(logger)
|
||||||
ctx = logging.WithContext(ctx, logger)
|
ctx = logging.WithContext(ctx, logger)
|
||||||
|
|
||||||
ki := &KeyInit{disk: a.config}
|
err := a.disk.MkdirAll(a.conf.ConfigDir, 0755)
|
||||||
|
|
||||||
// Check if the key file exists and is not empty
|
|
||||||
hasKey, err := ki.Has()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ErrorContext(ctx, "failed to check key file", slog.Any("error", err))
|
logger.ErrorContext(ctx, "failed to create config dir", slog.Any("error", err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasKey {
|
err = a.disk.MkdirAll(a.conf.StorageDir, 0755)
|
||||||
logger.InfoContext(ctx, "key file does not exist or is empty, generating new key")
|
|
||||||
err = ki.Init()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ErrorContext(ctx, "failed to initialize key file", slog.Any("error", err))
|
logger.ErrorContext(ctx, "failed to create storage dir", slog.Any("error", err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Read the key file
|
id, err := a.loadIdentity()
|
||||||
id, err := ki.Read()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ErrorContext(ctx, "failed to read key file", slog.Any("error", err))
|
logger.ErrorContext(ctx, "failed to read key file", slog.Any("error", err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
admins := []string{}
|
_, err = a.loadAdmins()
|
||||||
ai := &AdminInit{disk: a.config}
|
|
||||||
|
|
||||||
// Check if the admins file exists and is not empty
|
|
||||||
hasAdmins, err := ai.Has()
|
|
||||||
if err != nil {
|
|
||||||
logger.ErrorContext(ctx, "failed to check admins file", slog.Any("error", err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasAdmins {
|
|
||||||
logger.InfoContext(ctx, "admins file exists, reading admins")
|
|
||||||
admins, err = ai.Read()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ErrorContext(ctx, "failed to read admins file", slog.Any("error", err))
|
logger.ErrorContext(ctx, "failed to read admins file", slog.Any("error", err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.InfoContext(ctx, "loaded admins", slog.Any("admins", admins))
|
|
||||||
} else {
|
|
||||||
logger.WarnContext(ctx, "admins file does not exist or is empty, no admins will be loaded")
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpAddrRaw := net.JoinHostPort(a.conf.Host, a.conf.Port)
|
tcpAddrRaw := net.JoinHostPort(a.conf.Host, a.conf.Port)
|
||||||
tcpAddr, err := net.ResolveTCPAddr("tcp", tcpAddrRaw)
|
tcpAddr, err := net.ResolveTCPAddr("tcp", tcpAddrRaw)
|
||||||
@ -118,27 +98,25 @@ func (a *Node) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConnection(ctx context.Context, conn net.Conn) error {
|
// loadAdmins ...
|
||||||
defer conn.Close()
|
func (c *Node) loadAdmins() ([]string, error) {
|
||||||
|
hasKeyFile, err := c.hasAdminsFile()
|
||||||
logger := logging.FromContext(ctx)
|
|
||||||
|
|
||||||
msg := make([]byte, 1024)
|
|
||||||
n, err := conn.Read(msg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ErrorContext(ctx, "failed to read from connection", slog.Any("error", err))
|
return nil, fmt.Errorf("failed to check for admins file: %w", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.InfoContext(ctx, "received msg", slog.Int("bytes", n))
|
if !hasKeyFile {
|
||||||
|
if err := c.createAdminsFile(); err != nil {
|
||||||
return nil
|
return nil, fmt.Errorf("failed to create admins file: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type KeyInit struct{ disk disk.Disk }
|
return c.readAdminsFile()
|
||||||
|
}
|
||||||
|
|
||||||
func (ki *KeyInit) Has() (bool, error) {
|
// hasAdminsFile ...
|
||||||
f, err := ki.disk.Open("key")
|
func (c *Node) hasAdminsFile() (bool, error) {
|
||||||
|
f, err := c.config.Open("admins")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return false, nil
|
return false, nil
|
||||||
@ -147,16 +125,82 @@ func (ki *KeyInit) Has() (bool, error) {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
stats, err := f.Stat()
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createAdminsFile ...
|
||||||
|
func (c *Node) createAdminsFile() error {
|
||||||
|
f, err := c.config.Create("admins")
|
||||||
if err != nil {
|
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
|
return false, err
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
return stats.Size() > 0, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ki *KeyInit) Init() error {
|
// createKeyFile creates a new key file with a generated private key.
|
||||||
f, err := ki.disk.Create("key")
|
func (c *Node) createKeyFile() error {
|
||||||
|
f, err := c.config.Create("key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -175,8 +219,9 @@ func (ki *KeyInit) Init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ki *KeyInit) Read() (ed25519.PrivateKey, error) {
|
// readKeyFile reads the private key from the key file and returns it as an ed25519.PrivateKey.
|
||||||
f, err := ki.disk.Open("key")
|
func (c *Node) readKeyFile() (ed25519.PrivateKey, error) {
|
||||||
|
f, err := c.config.Open("key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -194,51 +239,19 @@ func (ki *KeyInit) Read() (ed25519.PrivateKey, error) {
|
|||||||
return ed25519.PrivateKey(keyRaw), nil
|
return ed25519.PrivateKey(keyRaw), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminInit struct {
|
func handleConnection(ctx context.Context, conn net.Conn) error {
|
||||||
disk disk.Disk
|
defer conn.Close()
|
||||||
}
|
|
||||||
|
|
||||||
func (ai *AdminInit) Has() (bool, error) {
|
logger := logging.FromContext(ctx)
|
||||||
f, err := ai.disk.Open("admins")
|
|
||||||
|
msg := make([]byte, 1024)
|
||||||
|
n, err := conn.Read(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
logger.ErrorContext(ctx, "failed to read from connection", slog.Any("error", err))
|
||||||
return false, nil
|
return err
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
stats, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return stats.Size() > 0, nil
|
logger.InfoContext(ctx, "received msg", slog.Int("bytes", n))
|
||||||
}
|
|
||||||
|
|
||||||
func (ai *AdminInit) Read() ([]string, error) {
|
return nil
|
||||||
f, err := ai.disk.Open("admins")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
adminsRaw := make([]byte, 0)
|
|
||||||
n, err := f.Read(adminsRaw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
return nil, fmt.Errorf("admins file is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
rawAdmins := strings.Split(string(adminsRaw), "\n")
|
|
||||||
var admins []string
|
|
||||||
for _, admin := range rawAdmins {
|
|
||||||
if admin != "" {
|
|
||||||
admins = append(admins, admin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return admins, nil
|
|
||||||
}
|
}
|
||||||
|
@ -202,6 +202,29 @@ func (sd *SimDisk) Mkdir(name string, perm fs.FileMode) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MkdirAll creates a directory and any necessary parent directories in the simulated file system.
|
||||||
|
// It behaves like os.MkdirAll.
|
||||||
|
// If the directory already exists, it does nothing.
|
||||||
|
func (sd *SimDisk) MkdirAll(name string, perm fs.FileMode) error {
|
||||||
|
if err := sd.simulateOp(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sd.mu.Lock()
|
||||||
|
defer sd.mu.Unlock()
|
||||||
|
|
||||||
|
if _, exists := sd.entries[name]; exists {
|
||||||
|
return errors.New("directory already exists")
|
||||||
|
}
|
||||||
|
sd.entries[name] = &simEntry{
|
||||||
|
name: name,
|
||||||
|
isDir: true,
|
||||||
|
perm: perm,
|
||||||
|
modTime: time.Now(),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Create creates (or truncates) a file in the simulated file system and returns a writable file.
|
// Create creates (or truncates) a file in the simulated file system and returns a writable file.
|
||||||
func (sd *SimDisk) Create(name string) (disk.File, error) {
|
func (sd *SimDisk) Create(name string) (disk.File, error) {
|
||||||
if err := sd.simulateOp(); err != nil {
|
if err := sd.simulateOp(); err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user