package client import ( "context" "crypto/ed25519" "encoding/base64" "errors" "log/slog" "net" "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 dsfxctl configuration. DefaultConfigDir = "/etc/dsfxctl" // DefaultHost is the default host for the dsfxctl application. DefaultHost = "0.0.0.0" ) // Conf holds the configuration for the dsfxctl application. type Conf struct { // Directories ConfigDir string // Networking Host string } func loadConfigFromSystem(sys system.System) Conf { var c Conf c.ConfigDir = sys.GetEnv("DSFXCTL_CONFIG_DIR") if c.ConfigDir == "" { c.ConfigDir = DefaultConfigDir } c.Host = sys.GetEnv("DSFXCTL_HOST") if c.Host == "" { c.Host = DefaultHost } return c } // Client represents the client application for dsfxctl. type Client struct { // resources disk disk.Disk system system.System // configuration conf Conf // storage scopes configScope disk.Disk } // New creates a new Client instance with the provided disk, system, and // configuration. func New(disk disk.Disk, system system.System) *Client { conf := loadConfigFromSystem(system) return &Client{ // resources disk: disk, system: system, // configuration conf: conf, // storage scopes configScope: scoped.New(disk, conf.ConfigDir), } } // Run executes the main logic of the application. func (a *Client) Run(ctx context.Context) error { // --------------------------------------------------------------------------- // Logger opts := &slog.HandlerOptions{ AddSource: false, Level: slog.LevelDebug, } logger := slog.New(slog.NewTextHandler(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) ctx = logging.WithContext(ctx, logger) keyFile, err := a.configScope.Open("key") if err != nil { logger.WarnContext(ctx, "key file is missing, reinitializing") logger.WarnContext(ctx, "if this is your first time running dsfxctl, you can ignore this") } if keyFile == nil { logger.InfoContext(ctx, "generating new key") keyFile, err = a.configScope.Create("key") if err != nil { logger.ErrorContext(ctx, "failed to create key file", slog.Any("error", err)) return err } privkey, err := identity.Generate() if err != nil { logger.ErrorContext(ctx, "failed to generate key", slog.Any("error", err)) return err } _, err = keyFile.Write([]byte(base64.StdEncoding.EncodeToString(privkey))) if err != nil { logger.ErrorContext(ctx, "failed to write key", slog.Any("error", err)) return err } } defer keyFile.Close() keyRaw := make([]byte, ed25519.PrivateKeySize) n, err := keyFile.Read(keyRaw) if err != nil { logger.ErrorContext(ctx, "failed to read key file", slog.Any("error", err)) return err } if n != ed25519.PrivateKeySize { logger.ErrorContext(ctx, "key file is not the correct size", slog.Int("size", n)) return err } id := ed25519.PrivateKey(keyRaw) laddr := network.NewAddr( net.ParseIP("0.0.0.0"), 0, // port 0 means any available port identity.ToPublicKey(id), ) logger.DebugContext(ctx, "using addr", slog.String("address", laddr.String())) switch a.system.Arg(0) { case "test": raddrRaw := a.system.Arg(1) if raddrRaw == "" { logger.ErrorContext(ctx, "no remote address provided") return err } testConnection(ctx, id, laddr, raddrRaw) case "": return errors.New("no command provided") default: return errors.New("unknown command: " + a.system.Arg(0)) } return nil } func testConnection(ctx context.Context, id ed25519.PrivateKey, laddr *network.Addr, raddrRaw string) { logger := logging.FromContext(context.Background()) raddr, err := network.ParseAddr(raddrRaw) if err != nil { logger.ErrorContext(ctx, "failed to parse server address", slog.Any("error", err)) return } conn, err := network.Dial(ctx, id, laddr, raddr) if err != nil { logger.ErrorContext(ctx, "failed to connect", slog.Any("error", err)) return } defer conn.Close() }