From c52e50dc6e8399eebd99c4f96c785ab47835a300 Mon Sep 17 00:00:00 2001 From: "A. Ottr" Date: Thu, 14 Aug 2025 01:10:03 +0200 Subject: [PATCH] feat: add logger and identity gen Signed-off-by: A. Ottr --- cmd/nox/main.go | 51 ++++++++++++++++++++++++++++++++---- internal/crypto/identity.go | 25 ++++++++++++++++++ internal/crypto/recipient.go | 27 +++++++++++++++++++ internal/logging/logger.go | 47 +++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 internal/crypto/recipient.go create mode 100644 internal/logging/logger.go diff --git a/cmd/nox/main.go b/cmd/nox/main.go index fd5adc1..919ec4c 100644 --- a/cmd/nox/main.go +++ b/cmd/nox/main.go @@ -3,17 +3,22 @@ package main import ( "context" "fmt" - "log" "os" "github.com/aottr/nox/internal/config" "github.com/aottr/nox/internal/constants" + "github.com/aottr/nox/internal/crypto" + "github.com/aottr/nox/internal/logging" "github.com/aottr/nox/internal/processor" "github.com/urfave/cli/v3" ) func main() { + logging.Init() + logging.SetLevel("info") + log := logging.Get() + var configPath string var statePath string var identityPaths []string @@ -95,7 +100,43 @@ func main() { Action: func(ctx context.Context, cmd *cli.Command) error { fmt.Println("encrypting file", inputPath) fmt.Println("writing to", outputPath) - fmt.Println(cmd.StringSlice("recipient")) + recipients, err := crypto.StringsToRecipients(cmd.StringSlice("recipient")) + if err != nil { + return err + } + out, err := crypto.EncryptFile(inputPath, recipients) + if err != nil { + return err + } + fmt.Println(string(out)) + // priv, pub, err := crypto.GenerateAndWriteX25519Identity("test.key") + // if err != nil { + // return err + // } + // fmt.Println(priv) + // fmt.Println(pub) + + return nil + }, + }, + { + Name: "generate", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "output", + Usage: "path to output file", + Aliases: []string{"o"}, + Value: constants.StandardOutput, + Destination: &outputPath, + }, + }, + Action: func(ctx context.Context, cmd *cli.Command) error { + priv, pub, err := crypto.GenerateIdentity(cmd.String("output")) + if err != nil { + return err + } + fmt.Println(priv) + fmt.Println(pub) return nil }, }, @@ -135,7 +176,7 @@ func main() { Verbose: verbose, }) if err != nil { - log.Fatalf("failed to build runtime context: %v", err) + log.Error("failed to build runtime context", "error", err.Error()) } if cmd.String("app") != "" { return processor.SyncApp(rtx) @@ -154,7 +195,7 @@ func main() { IdentityPaths: identityPaths, }) if err != nil { - log.Fatalf("failed to build runtime context: %v", err) + log.Error("failed to build runtime context", "error", err.Error()) } return processor.ValidateConfig(rtx.Config) }, @@ -163,6 +204,6 @@ func main() { } if err := cmd.Run(context.Background(), os.Args); err != nil { - log.Fatal(err) + log.Error("failed to run command", "error", err.Error()) } } diff --git a/internal/crypto/identity.go b/internal/crypto/identity.go index 5176c14..15241be 100644 --- a/internal/crypto/identity.go +++ b/internal/crypto/identity.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "os" + "path/filepath" "filippo.io/age" ) @@ -36,3 +37,27 @@ func LoadAgeIdentitiesFromPaths(paths []string) ([]age.Identity, error) { return allIdentities, nil } + +func GenerateIdentity(path string) (priv string, pub string, err error) { + id, err := age.GenerateX25519Identity() + if err != nil { + return "", "", fmt.Errorf("generate identity: %w", err) + } + + priv = id.String() // "AGE-SECRET-KEY-1..." + pub = id.Recipient().String() // "age1..." + + // ensure directory exists and apply permissions + if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { + return "", "", fmt.Errorf("mkdir %s: %w", filepath.Dir(path), err) + } + if err := os.WriteFile(path, []byte(priv+"\n"), 0o600); err != nil { + return "", "", fmt.Errorf("write identity: %w", err) + } + + // write public key + if err := os.WriteFile(path+".pub", []byte(pub+"\n"), 0o644); err != nil { + return "", "", fmt.Errorf("write identity: %w", err) + } + return priv, pub, nil +} diff --git a/internal/crypto/recipient.go b/internal/crypto/recipient.go new file mode 100644 index 0000000..a1a5b23 --- /dev/null +++ b/internal/crypto/recipient.go @@ -0,0 +1,27 @@ +package crypto + +import ( + "bytes" + "fmt" + "strings" + + "filippo.io/age" +) + +func StringsToRecipients(strs []string) ([]age.Recipient, error) { + var b bytes.Buffer + for _, s := range strs { + s = strings.TrimSpace(s) + if s == "" { + continue + } + // age.ParseRecipients accepts multiple lines... + b.WriteString(s) + b.WriteByte('\n') + } + recips, err := age.ParseRecipients(&b) + if err != nil { + return nil, fmt.Errorf("parse recipients: %w", err) + } + return recips, nil +} diff --git a/internal/logging/logger.go b/internal/logging/logger.go new file mode 100644 index 0000000..75bb804 --- /dev/null +++ b/internal/logging/logger.go @@ -0,0 +1,47 @@ +package logging + +import ( + "log/slog" + "os" + "strings" + "sync" +) + +type Logger = *slog.Logger + +var ( + once sync.Once + logger *slog.Logger + logLevel = &slog.LevelVar{} +) + +func Get() Logger { + Init() + return logger +} + +func Init() { + once.Do(func() { + logLevel.Set(slog.LevelInfo) + + h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: logLevel, + }) + logger = slog.New(h) + }) +} + +func SetLevel(s string) { + switch strings.ToLower(s) { + case "debug": + logLevel.Set(slog.LevelDebug) + case "info": + logLevel.Set(slog.LevelInfo) + case "warn", "warning": + logLevel.Set(slog.LevelWarn) + case "error": + logLevel.Set(slog.LevelError) + default: + logLevel.Set(slog.LevelInfo) + } +} -- 2.51.2