-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/keepass provider #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9b46ecb
b58a947
839f03b
75f168e
5cbc49d
7240c64
8699f98
fe7fe78
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # Database | ||
| DATABASE_URL= | ||
| JWT_SECRET= | ||
| PORT=3000 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| provider: keepass | ||
| schema_file: .env.example | ||
| env_file: .env | ||
| keepass_database: test-secrets.kdbx | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This config file should not be committed to the project root — it hardcodes If you need this for test fixtures, place it under |
||
| keepass_group: dotenv | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,3 +43,4 @@ Thumbs.db | |
| *.swp | ||
| .vscode/ | ||
| .idea/ | ||
| *.kdbx | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,15 @@ | ||
| module dotenv-sync | ||
|
|
||
| go 1.22 | ||
| go 1.25.0 | ||
|
|
||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This bumps the minimum Go version from 1.22 to 1.25.0 — a significant breaking change for anyone on an older Go.
|
||
| require ( | ||
| github.com/spf13/cobra v1.8.1 | ||
| golang.org/x/term v0.43.0 | ||
| gopkg.in/yaml.v3 v3.0.1 | ||
| ) | ||
|
|
||
| require ( | ||
| github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||
| github.com/spf13/pflag v1.0.5 // indirect | ||
| golang.org/x/sys v0.44.0 // indirect | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,16 +9,24 @@ import ( | |
| "dotenv-sync/internal/config" | ||
| "dotenv-sync/internal/provider" | ||
| "dotenv-sync/internal/provider/bitwarden" | ||
| "dotenv-sync/internal/provider/keepass" | ||
| "dotenv-sync/internal/report" | ||
| "dotenv-sync/pkg/dotenvsync" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var providerFactory = func(cfg config.Config) provider.Provider { | ||
| return bitwarden.NewAdapter(cfg) | ||
| switch cfg.Provider { | ||
| case "keepass": | ||
| return keepass.NewAdapter(cfg) | ||
| default: | ||
| return bitwarden.NewAdapter(cfg) | ||
| } | ||
| } | ||
|
|
||
| var pushProviderFactory = func(cfg config.Config) provider.PushProvider { | ||
| // KeePass does not yet implement PushProvider (ds push is Bitwarden-only for now). | ||
| // All push operations fall through to Bitwarden regardless of config. | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Please add a guard in the push command (or here) that returns a clear error like "push is not supported for the keepass provider" when |
||
| return bitwarden.NewAdapter(cfg) | ||
| } | ||
|
|
||
|
|
@@ -74,6 +82,7 @@ func NewRootCommand(s streams) *cobra.Command { | |
| cmd.AddCommand(newInitCommand(s, opts)) | ||
| cmd.AddCommand(newMissingCommand(s, opts)) | ||
| cmd.AddCommand(newReverseCommand(s, opts)) | ||
| cmd.AddCommand(newScaffoldCommand(s, opts)) | ||
| return cmd | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| package cli | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
|
|
||
| "dotenv-sync/internal/envfile" | ||
| "dotenv-sync/internal/provider/keepass" | ||
| "dotenv-sync/internal/report" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| func newScaffoldCommand(s streams, opts *rootOptions) *cobra.Command { | ||
| var dryRun bool | ||
| cmd := &cobra.Command{ | ||
| Use: "scaffold", | ||
| Short: "Seed KeePass with blank entries for all provider-managed schema keys", | ||
| Long: `scaffold reads .env.example and creates a blank KeePass entry for every | ||
| provider-managed key (blank value in .env.example) that does not already exist | ||
| in the configured group. Existing entries are skipped, never overwritten. | ||
|
|
||
| After scaffold, open KeePassXC, fill in the real secret values, then run ds sync. | ||
|
|
||
| Only supported when provider is keepass.`, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| _, cfg, setupPassword, err := ensureConfig(s, opts) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // scaffold is KeePass-only. | ||
| if cfg.Provider != "keepass" { | ||
| return report.NewAppError("E007", report.ExitOperational, | ||
| "scaffold requires provider: keepass", | ||
| "scaffold cannot create entries in Bitwarden via this command", | ||
| "set provider: keepass in .envsync.yaml and retry", nil) | ||
| } | ||
|
|
||
| // Parse the schema to find provider-managed keys (blank values). | ||
| schema, err := envfile.ParseFile(cfg.SchemaFile, envfile.KindSchema) | ||
| if err != nil { | ||
| return report.NewAppError("E004", report.ExitOperational, | ||
| "schema file missing", | ||
| "scaffold cannot determine which keys to create", | ||
| "create .env.example or run 'ds init'", err) | ||
| } | ||
|
|
||
| // Collect keys that need entries — blank value = provider-managed. | ||
| var keys []string | ||
| for _, line := range schema.Lines { | ||
| if line.LineType == envfile.LineAssignment && line.ManagedByProvider { | ||
| keys = append(keys, line.Key) | ||
| } | ||
| } | ||
|
|
||
| if len(keys) == 0 { | ||
| fmt.Fprintln(s.stdout, "No provider-managed keys found in schema — nothing to scaffold.") | ||
| return nil | ||
| } | ||
|
|
||
| if dryRun { | ||
| fmt.Fprintf(s.stdout, "Would scaffold %d key(s) into %s (group: %s):\n", len(keys), cfg.KeePassDatabase, cfg.KeePassGroup) | ||
| for _, key := range keys { | ||
| fmt.Fprintln(s.stdout, report.ChangeLine("add", key, "[DRY-RUN]")) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // Build the adapter. If setup just ran, reuse the password already | ||
| // collected so the user is not prompted a second time. | ||
| adapter := keepass.NewAdapter(cfg) | ||
| if setupPassword != "" { | ||
| adapter.SetPassword(setupPassword) | ||
| } else if err := adapter.EnsurePassword(cmd.Context()); err != nil { | ||
| return report.NewAppError("E003", report.ExitOperational, | ||
| "KeePass master password could not be read", | ||
| "scaffold cannot create entries without vault access", | ||
| "check your master password and retry", err) | ||
| } | ||
|
|
||
| created, skipped, failed := 0, 0, 0 | ||
| for _, key := range keys { | ||
| err := adapter.CreateEntry(cmd.Context(), key) | ||
| if err == nil { | ||
| fmt.Fprintln(s.stdout, report.ChangeLine("add", key, "[CREATED]")) | ||
| created++ | ||
| } else if errors.Is(err, keepass.ErrEntryExists) { | ||
| fmt.Fprintln(s.stdout, report.ChangeLine("skip", key, "[EXISTS]")) | ||
| skipped++ | ||
| } else { | ||
| fmt.Fprintln(s.stdout, report.ChangeLine("error", key, "[FAILED]")) | ||
| failed++ | ||
| } | ||
| } | ||
|
|
||
| summary := report.Summary{Added: created, Unchanged: skipped, Error: failed} | ||
| fmt.Fprintln(s.stdout, report.SummaryLine(report.StatusWritten, cfg.KeePassDatabase, summary, "")) | ||
| fmt.Fprintln(s.stdout, "Open KeePassXC, fill in the secret values, then run 'ds sync'.") | ||
| return nil | ||
| }, | ||
| } | ||
| cmd.Flags().BoolVar(&dryRun, "dry-run", false, "preview without creating entries") | ||
| return cmd | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same concern — this appears to be a demo/test artifact rather than something that belongs in the project root. The project generates
.env.exampleviads init; committing a sample one here may confuse contributors or conflict with the actual project schema.Please remove, or move to
testdata/if needed for tests.