add lock
subcommand; add --local flag to rm and exec
All checks were successful
CI / build (push) Successful in 23s
All checks were successful
CI / build (push) Successful in 23s
This commit is contained in:
parent
dac1a06e1a
commit
1922138133
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
/build
|
/build
|
||||||
/envvault
|
/envvault
|
||||||
.aider*
|
.aider*
|
||||||
|
.envvault.json.enc
|
||||||
|
@ -6,16 +6,18 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
"golang.org/x/crypto/nacl/secretbox"
|
"golang.org/x/crypto/nacl/secretbox"
|
||||||
"golang.org/x/crypto/scrypt"
|
"golang.org/x/crypto/scrypt"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
"slices"
|
|
||||||
|
|
||||||
"git.jrop.me/jonathan/envvault/internal"
|
"git.jrop.me/jonathan/envvault/internal"
|
||||||
"git.jrop.me/jonathan/envvault/internal/config"
|
"git.jrop.me/jonathan/envvault/internal/config"
|
||||||
@ -24,9 +26,14 @@ import (
|
|||||||
|
|
||||||
var cachedPassword []byte
|
var cachedPassword []byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
EnvStoreCurrentVersion = 1
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
keyFilePath string
|
keyFilePath string
|
||||||
dbFilePath string
|
dbFilePath string
|
||||||
|
localDbFilePath string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -36,10 +43,79 @@ func init() {
|
|||||||
}
|
}
|
||||||
keyFilePath = filepath.Join(homeDir, ".local/cache/envvault/key")
|
keyFilePath = filepath.Join(homeDir, ".local/cache/envvault/key")
|
||||||
dbFilePath = filepath.Join(homeDir, ".local/cache/envvault/db.json.enc")
|
dbFilePath = filepath.Join(homeDir, ".local/cache/envvault/db.json.enc")
|
||||||
|
localDbFilePath = ".envvault.json.enc"
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnvStore struct {
|
type EnvStore struct {
|
||||||
Vars map[string]string `json:"vars"`
|
Vars map[string]string `json:"vars"`
|
||||||
|
Tags map[string][]string `json:"tags,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTags returns all tags for a given variable name
|
||||||
|
func (s *EnvStore) GetTags(varName string) []string {
|
||||||
|
if s.Tags == nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return s.Tags[varName]
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTags sets the tags for a given variable name
|
||||||
|
func (s *EnvStore) SetTags(varName string, tags []string) {
|
||||||
|
if s.Tags == nil {
|
||||||
|
s.Tags = make(map[string][]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a union of existing and new tags
|
||||||
|
existingTags := s.Tags[varName]
|
||||||
|
unionTags := make([]string, 0, len(existingTags)+len(tags))
|
||||||
|
|
||||||
|
// Add existing tags to the union
|
||||||
|
for _, tag := range existingTags {
|
||||||
|
unionTags = append(unionTags, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new tags if they don't already exist
|
||||||
|
for _, tag := range tags {
|
||||||
|
if !slices.Contains(unionTags, tag) {
|
||||||
|
unionTags = append(unionTags, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Tags[varName] = unionTags
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveTags removes the specified tags from a variable
|
||||||
|
func (s *EnvStore) RemoveTags(varName string, tagsToRemove []string) {
|
||||||
|
if s.Tags == nil || len(s.Tags[varName]) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
existingTags := s.Tags[varName]
|
||||||
|
remainingTags := make([]string, 0, len(existingTags))
|
||||||
|
|
||||||
|
// Keep only tags that are not in tagsToRemove
|
||||||
|
for _, tag := range existingTags {
|
||||||
|
if !slices.Contains(tagsToRemove, tag) {
|
||||||
|
remainingTags = append(remainingTags, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Tags[varName] = remainingTags
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasAnyTag returns true if the variable has any of the specified tags
|
||||||
|
func (s *EnvStore) HasAnyTag(varName string, tags []string) bool {
|
||||||
|
if len(tags) == 0 {
|
||||||
|
return true // No tags specified, include all variables
|
||||||
|
}
|
||||||
|
|
||||||
|
varTags := s.GetTags(varName)
|
||||||
|
for _, tag := range tags {
|
||||||
|
if slices.Contains(varTags, tag) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
@ -47,31 +123,47 @@ type CLI struct {
|
|||||||
List ListCmd `cmd:"" help:"List all environment variables."`
|
List ListCmd `cmd:"" help:"List all environment variables."`
|
||||||
Add AddCmd `cmd:"" help:"Add an environment variable."`
|
Add AddCmd `cmd:"" help:"Add an environment variable."`
|
||||||
Rm RmCmd `cmd:"" help:"Remove an environment variable."`
|
Rm RmCmd `cmd:"" help:"Remove an environment variable."`
|
||||||
Exec ExecCmd `cmd:"" help:"Execute a command with environment variables." alias:"x"`
|
Tag TagCmd `cmd:"" help:"Add or remove tags from an environment variable."`
|
||||||
|
Exec ExecCmd `cmd:"" help:"Execute a command with environment variables." alias:"x"`
|
||||||
Rekey RekeyCmd `cmd:"" help:"Change the master password."`
|
Rekey RekeyCmd `cmd:"" help:"Change the master password."`
|
||||||
Daemon DaemonCmd `cmd:"" help:"Start the password caching daemon."`
|
Daemon DaemonCmd `cmd:"" help:"Start the password caching daemon."`
|
||||||
|
Lock LockCmd `cmd:"" help:"Lock the vault by stopping the password caching daemon."`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InitCmd struct{}
|
type InitCmd struct{}
|
||||||
type ListCmd struct {
|
type ListCmd struct {
|
||||||
Values bool `short:"v" help:"Show values of environment variables." default:"false"`
|
Values bool `short:"v" help:"Show values of environment variables." default:"false"`
|
||||||
|
Local bool `short:"l" help:"List only from local vault." default:"false"`
|
||||||
|
Tags []string `short:"t" help:"Filter by tags (can be specified multiple times)."`
|
||||||
}
|
}
|
||||||
type AddCmd struct {
|
type AddCmd struct {
|
||||||
Name string `arg:"" help:"Name of the environment variable."`
|
Name string `arg:"" help:"Name of the environment variable."`
|
||||||
Value string `arg:"" optional:"" help:"Value of the environment variable."`
|
Value string `arg:"" help:"Value of the environment variable." optional:""`
|
||||||
|
Local bool ` help:"Add to local vault instead of global." short:"l" default:"false"`
|
||||||
}
|
}
|
||||||
type RmCmd struct {
|
type RmCmd struct {
|
||||||
Name string `arg:"" help:"Name of the environment variable to remove."`
|
Name string `arg:"" help:"Name of the environment variable to remove."`
|
||||||
|
Local bool ` help:"Remove from local vault only." short:"l" default:"false"`
|
||||||
|
}
|
||||||
|
type TagCmd struct {
|
||||||
|
Name string `arg:"" help:"Name of the environment variable."`
|
||||||
|
Tags []string `arg:"" help:"Tags to add or remove." optional:""`
|
||||||
|
Remove bool ` help:"Remove tags instead of adding them." short:"r" default:"false"`
|
||||||
|
Local bool ` help:"Operate on local vault only." short:"l" default:"false"`
|
||||||
|
List bool ` help:"List tags for the variable." short:"L" default:"false"`
|
||||||
}
|
}
|
||||||
type ExecCmd struct {
|
type ExecCmd struct {
|
||||||
Env []string `short:"e" help:"Environment variables to set."`
|
Env []string `short:"e" help:"Environment variables to set."`
|
||||||
Cmd string `arg:"" help:"Command to execute." passthrough:"all"`
|
Local bool `short:"l" help:"Use only local vault." default:"false"`
|
||||||
Args []string `arg:"" optional:"" help:"Arguments for the command."`
|
Tags []string `short:"t" help:"Filter by tags (can be specified multiple times)."`
|
||||||
|
Cmd string ` help:"Command to execute." arg:"" passthrough:"all"`
|
||||||
|
Args []string ` help:"Arguments for the command." arg:"" optional:""`
|
||||||
}
|
}
|
||||||
type RekeyCmd struct{}
|
type RekeyCmd struct{}
|
||||||
type DaemonCmd struct {
|
type DaemonCmd struct {
|
||||||
Timeout int `help:"Password cache timeout in minutes." default:"5"`
|
Timeout int `help:"Password cache timeout in minutes." default:"5"`
|
||||||
}
|
}
|
||||||
|
type LockCmd struct{}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
internal.Log.Println("Starting envvault")
|
internal.Log.Println("Starting envvault")
|
||||||
@ -86,11 +178,11 @@ func main() {
|
|||||||
case "init":
|
case "init":
|
||||||
subcommandInitVault()
|
subcommandInitVault()
|
||||||
case "add <name>":
|
case "add <name>":
|
||||||
subcommandAddEnvVar(cli.Add.Name, cli.Add.Value)
|
subcommandAddEnvVar(cli.Add)
|
||||||
case "add <name> <value>":
|
case "add <name> <value>":
|
||||||
subcommandAddEnvVar(cli.Add.Name, cli.Add.Value)
|
subcommandAddEnvVar(cli.Add)
|
||||||
case "rm <name>":
|
case "rm <name>":
|
||||||
subcommandRmEnvVar(cli.Rm.Name)
|
subcommandRmEnvVar(cli.Rm.Name, cli.Rm.Local)
|
||||||
case "list":
|
case "list":
|
||||||
subcommandListEnvVars(cli.List)
|
subcommandListEnvVars(cli.List)
|
||||||
case "exec <cmd>":
|
case "exec <cmd>":
|
||||||
@ -101,6 +193,12 @@ func main() {
|
|||||||
subcommandRekeyVault()
|
subcommandRekeyVault()
|
||||||
case "daemon":
|
case "daemon":
|
||||||
subcommandStartDaemon(cli.Daemon.Timeout)
|
subcommandStartDaemon(cli.Daemon.Timeout)
|
||||||
|
case "lock":
|
||||||
|
subcommandLockVault()
|
||||||
|
case "tag <name>":
|
||||||
|
subcommandTagEnvVar(cli.Tag)
|
||||||
|
case "tag <name> <tags>":
|
||||||
|
subcommandTagEnvVar(cli.Tag)
|
||||||
default:
|
default:
|
||||||
internal.Log.Printf("Unknown command: %s", command)
|
internal.Log.Printf("Unknown command: %s", command)
|
||||||
log.Fatal("Unknown command")
|
log.Fatal("Unknown command")
|
||||||
@ -165,7 +263,7 @@ func subcommandInitVault() { // {{{
|
|||||||
// Initialize the db file
|
// Initialize the db file
|
||||||
internal.Log.Println("Initializing empty environment store")
|
internal.Log.Println("Initializing empty environment store")
|
||||||
emptyStore := &EnvStore{Vars: make(map[string]string)}
|
emptyStore := &EnvStore{Vars: make(map[string]string)}
|
||||||
saveEnvStore(emptyStore)
|
saveGlobalEnvStore(emptyStore)
|
||||||
|
|
||||||
internal.Log.Println("Vault initialized successfully")
|
internal.Log.Println("Vault initialized successfully")
|
||||||
fmt.Fprintln(os.Stderr, "Vault initialized successfully.")
|
fmt.Fprintln(os.Stderr, "Vault initialized successfully.")
|
||||||
@ -177,47 +275,205 @@ func subcommandInitVault() { // {{{
|
|||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
func subcommandAddEnvVar(name, value string) { // {{{
|
func subcommandAddEnvVar(cmd AddCmd) { // {{{
|
||||||
store := loadEnvStore()
|
var store *EnvStore
|
||||||
if value == "" {
|
if cmd.Local {
|
||||||
fmt.Fprintf(os.Stderr, "Enter value for %s: ", name)
|
store = loadLocalEnvStore()
|
||||||
|
internal.Log.Printf("Adding environment variable '%s' to local vault", cmd.Name)
|
||||||
|
} else {
|
||||||
|
store = loadGlobalEnvStore()
|
||||||
|
internal.Log.Printf("Adding environment variable '%s' to global vault", cmd.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Value == "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "Enter value for %s: ", cmd.Name)
|
||||||
inputValue, err := term.ReadPassword(int(os.Stdin.Fd()))
|
inputValue, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||||
fmt.Fprintln(os.Stderr)
|
fmt.Fprintln(os.Stderr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
value = string(inputValue)
|
cmd.Value = string(inputValue)
|
||||||
|
}
|
||||||
|
store.Vars[cmd.Name] = cmd.Value
|
||||||
|
|
||||||
|
if cmd.Local {
|
||||||
|
saveLocalEnvStore(store)
|
||||||
|
fmt.Fprintf(os.Stderr, "Environment variable '%s' added to local vault.\n", cmd.Name)
|
||||||
|
} else {
|
||||||
|
saveGlobalEnvStore(store)
|
||||||
|
fmt.Fprintf(os.Stderr, "Environment variable '%s' added to global vault.\n", cmd.Name)
|
||||||
}
|
}
|
||||||
store.Vars[name] = value
|
|
||||||
saveEnvStore(store)
|
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
func subcommandRmEnvVar(name string) { // {{{
|
func subcommandRmEnvVar(name string, localOnly bool) { // {{{
|
||||||
store := loadEnvStore()
|
localExists := false
|
||||||
if _, exists := store.Vars[name]; exists {
|
|
||||||
delete(store.Vars, name)
|
// Check local store if it exists
|
||||||
saveEnvStore(store)
|
if _, err := os.Stat(localDbFilePath); err == nil {
|
||||||
fmt.Fprintf(os.Stderr, "Environment variable '%s' removed.\n", name)
|
localStore := loadLocalEnvStore()
|
||||||
|
if _, exists := localStore.Vars[name]; exists {
|
||||||
|
delete(localStore.Vars, name)
|
||||||
|
saveLocalEnvStore(localStore)
|
||||||
|
localExists = true
|
||||||
|
fmt.Fprintf(os.Stderr, "Environment variable '%s' removed from local vault.\n", name)
|
||||||
|
} else if localOnly {
|
||||||
|
fmt.Fprintf(os.Stderr, "Environment variable '%s' not found in local vault.\n", name)
|
||||||
|
}
|
||||||
|
} else if localOnly {
|
||||||
|
fmt.Fprintf(os.Stderr, "Local vault does not exist.\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check global store if not local-only
|
||||||
|
if !localOnly {
|
||||||
|
globalStore := loadGlobalEnvStore()
|
||||||
|
if _, exists := globalStore.Vars[name]; exists {
|
||||||
|
delete(globalStore.Vars, name)
|
||||||
|
saveGlobalEnvStore(globalStore)
|
||||||
|
fmt.Fprintf(os.Stderr, "Environment variable '%s' removed from global vault.\n", name)
|
||||||
|
} else if !localExists {
|
||||||
|
fmt.Fprintf(os.Stderr, "Environment variable '%s' not found in global vault.\n", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // }}}
|
||||||
|
|
||||||
|
func subcommandTagEnvVar(cmd TagCmd) { // {{{
|
||||||
|
var store *EnvStore
|
||||||
|
|
||||||
|
// Determine which store to use
|
||||||
|
if cmd.Local {
|
||||||
|
if _, err := os.Stat(localDbFilePath); os.IsNotExist(err) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Local vault does not exist.\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
store = loadLocalEnvStore()
|
||||||
|
internal.Log.Printf("Using local vault")
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(os.Stderr, "Environment variable '%s' not found.\n", name)
|
store = loadGlobalEnvStore()
|
||||||
|
internal.Log.Printf("Using global vault")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the variable exists
|
||||||
|
if _, exists := store.Vars[cmd.Name]; !exists {
|
||||||
|
vaultType := "global"
|
||||||
|
if cmd.Local {
|
||||||
|
vaultType = "local"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Environment variable '%s' not found in %s vault.\n",
|
||||||
|
cmd.Name,
|
||||||
|
vaultType)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If list flag is set, just list the tags and return
|
||||||
|
if cmd.List {
|
||||||
|
tags := store.GetTags(cmd.Name)
|
||||||
|
if len(tags) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "No tags for '%s'\n", cmd.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, tag := range tags {
|
||||||
|
fmt.Println(tag)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no tags provided and not in list mode, show error
|
||||||
|
if len(cmd.Tags) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "No tags provided. Use --list to view tags.\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process tags
|
||||||
|
if cmd.Remove {
|
||||||
|
internal.Log.Printf("Removing tags %v from variable '%s'", cmd.Tags, cmd.Name)
|
||||||
|
store.RemoveTags(cmd.Name, cmd.Tags)
|
||||||
|
fmt.Fprintf(
|
||||||
|
os.Stderr,
|
||||||
|
"Removed tags from '%s': %s\n",
|
||||||
|
cmd.Name,
|
||||||
|
strings.Join(cmd.Tags, ", "),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
internal.Log.Printf("Adding tags %v to variable '%s'", cmd.Tags, cmd.Name)
|
||||||
|
store.SetTags(cmd.Name, cmd.Tags)
|
||||||
|
fmt.Fprintf(os.Stderr, "Added tags to '%s': %s\n", cmd.Name, strings.Join(cmd.Tags, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the store
|
||||||
|
if cmd.Local {
|
||||||
|
saveLocalEnvStore(store)
|
||||||
|
} else {
|
||||||
|
saveGlobalEnvStore(store)
|
||||||
}
|
}
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
func subcommandListEnvVars(cmdArgs ListCmd) { // {{{
|
func subcommandListEnvVars(cmdArgs ListCmd) { // {{{
|
||||||
store := loadEnvStore()
|
var store *EnvStore
|
||||||
for k, v := range store.Vars {
|
|
||||||
|
// Determine which store to use
|
||||||
|
if cmdArgs.Local {
|
||||||
|
if _, err := os.Stat(localDbFilePath); os.IsNotExist(err) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Local vault does not exist.\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
store = loadLocalEnvStore()
|
||||||
|
} else {
|
||||||
|
// Default: load and merge stores
|
||||||
|
store = loadEnvStore()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort keys for consistent output
|
||||||
|
var keys []string
|
||||||
|
for k := range store.Vars {
|
||||||
|
// Filter by tags if specified
|
||||||
|
if store.HasAnyTag(k, cmdArgs.Tags) {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(keys)
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
v := store.Vars[k]
|
||||||
|
tags := store.GetTags(k)
|
||||||
|
tagStr := ""
|
||||||
|
if len(tags) > 0 {
|
||||||
|
tagStr = fmt.Sprintf(" (tags: %s)", strings.Join(tags, ", "))
|
||||||
|
}
|
||||||
if cmdArgs.Values {
|
if cmdArgs.Values {
|
||||||
fmt.Printf("%s=%s\n", k, v)
|
fmt.Printf("%s=%s", k, v)
|
||||||
|
fmt.Fprintf(os.Stderr, "%s", tagStr)
|
||||||
|
fmt.Println()
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s\n", k)
|
fmt.Printf("%s", k)
|
||||||
|
fmt.Fprintf(os.Stderr, "%s", tagStr)
|
||||||
|
fmt.Println()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
func subcommandExecCommand(cmdArgs ExecCmd) { // {{{
|
func subcommandExecCommand(cmdArgs ExecCmd) { // {{{
|
||||||
store := loadEnvStore()
|
var store *EnvStore
|
||||||
|
|
||||||
|
// Determine which store to use
|
||||||
|
if cmdArgs.Local {
|
||||||
|
if _, err := os.Stat(localDbFilePath); os.IsNotExist(err) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Local vault does not exist.\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
store = loadLocalEnvStore()
|
||||||
|
internal.Log.Println("Using local vault only for execution")
|
||||||
|
} else {
|
||||||
|
// Default: load and merge stores
|
||||||
|
store = loadEnvStore()
|
||||||
|
internal.Log.Println("Using merged vaults for execution")
|
||||||
|
}
|
||||||
envVars := os.Environ()
|
envVars := os.Environ()
|
||||||
for candidateEnvName, candidateEnvValue := range store.Vars {
|
for candidateEnvName, candidateEnvValue := range store.Vars {
|
||||||
|
// Skip if not matching tag filter
|
||||||
|
if !store.HasAnyTag(candidateEnvName, cmdArgs.Tags) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if len(cmdArgs.Env) == 0 {
|
if len(cmdArgs.Env) == 0 {
|
||||||
// If no env vars are specified, add all env vars
|
// If no env vars are specified, add all env vars
|
||||||
envVars = append(envVars, fmt.Sprintf("%s=%s", candidateEnvName, candidateEnvValue))
|
envVars = append(envVars, fmt.Sprintf("%s=%s", candidateEnvName, candidateEnvValue))
|
||||||
@ -266,6 +522,29 @@ func subcommandStartDaemon(timeoutMinutes int) { // {{{
|
|||||||
}
|
}
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
|
func subcommandLockVault() { // {{{
|
||||||
|
internal.Log.Println("Locking vault by stopping daemon")
|
||||||
|
|
||||||
|
socketPath := daemon.GetSocketPath()
|
||||||
|
client := daemon.NewClient(socketPath)
|
||||||
|
|
||||||
|
if !client.IsRunning() {
|
||||||
|
internal.Log.Println("Daemon is not running, nothing to lock")
|
||||||
|
fmt.Println("Vault is already locked (daemon not running)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
internal.Log.Println("Sending kill command to daemon")
|
||||||
|
err := client.KillDaemon()
|
||||||
|
if err != nil {
|
||||||
|
internal.Log.Printf("Failed to kill daemon: %v", err)
|
||||||
|
log.Fatalf("Failed to lock vault: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal.Log.Println("Daemon stopped successfully")
|
||||||
|
fmt.Println("Vault locked successfully")
|
||||||
|
} // }}}
|
||||||
|
|
||||||
func subcommandRekeyVault() { // {{{
|
func subcommandRekeyVault() { // {{{
|
||||||
// Ask for current master password
|
// Ask for current master password
|
||||||
fmt.Fprint(os.Stderr, "Enter current master password: ")
|
fmt.Fprint(os.Stderr, "Enter current master password: ")
|
||||||
@ -307,6 +586,21 @@ func subcommandRekeyVault() { // {{{
|
|||||||
log.Fatal("Passwords do not match")
|
log.Fatal("Passwords do not match")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if daemon is running and kill it
|
||||||
|
socketPath := daemon.GetSocketPath()
|
||||||
|
client := daemon.NewClient(socketPath)
|
||||||
|
|
||||||
|
if client.IsRunning() {
|
||||||
|
internal.Log.Println("Daemon is running, stopping it before rekey")
|
||||||
|
fmt.Println("Stopping password daemon before changing master password...")
|
||||||
|
err := client.KillDaemon()
|
||||||
|
if err != nil {
|
||||||
|
internal.Log.Printf("Failed to kill daemon: %v", err)
|
||||||
|
log.Fatalf("Failed to stop daemon: %v", err)
|
||||||
|
}
|
||||||
|
internal.Log.Println("Daemon stopped successfully")
|
||||||
|
}
|
||||||
|
|
||||||
// Encrypt the master key with the new password
|
// Encrypt the master key with the new password
|
||||||
newEncryptedKey := encryptKeyWithPassword(masterKey, newPassword)
|
newEncryptedKey := encryptKeyWithPassword(masterKey, newPassword)
|
||||||
|
|
||||||
@ -460,15 +754,16 @@ func decryptKeyWithPassword(encryptedKey, password []byte) ([]byte, error) { //
|
|||||||
return decrypted, nil
|
return decrypted, nil
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
func loadEnvStore() *EnvStore { // {{{
|
func loadStoreFile(filePath string) *EnvStore { // {{{
|
||||||
key := loadKey()
|
key := loadKey()
|
||||||
if err := os.MkdirAll(filepath.Dir(dbFilePath), 0700); err != nil {
|
|
||||||
log.Fatal(err)
|
data, err := os.ReadFile(filePath)
|
||||||
}
|
|
||||||
data, err := os.ReadFile(dbFilePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return &EnvStore{Vars: make(map[string]string)}
|
return &EnvStore{
|
||||||
|
Vars: make(map[string]string),
|
||||||
|
Tags: make(map[string][]string),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -484,13 +779,61 @@ func loadEnvStore() *EnvStore { // {{{
|
|||||||
if err := json.Unmarshal(decrypted, &store); err != nil {
|
if err := json.Unmarshal(decrypted, &store); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
// Initialize Tags map if it's nil
|
||||||
|
if store.Tags == nil {
|
||||||
|
store.Tags = make(map[string][]string)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return &EnvStore{Vars: make(map[string]string)}
|
return &EnvStore{
|
||||||
|
Vars: make(map[string]string),
|
||||||
|
Tags: make(map[string][]string),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &store
|
return &store
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
func saveEnvStore(store *EnvStore) { // {{{
|
func loadGlobalEnvStore() *EnvStore { // {{{
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dbFilePath), 0700); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return loadStoreFile(dbFilePath)
|
||||||
|
} // }}}
|
||||||
|
|
||||||
|
func loadLocalEnvStore() *EnvStore { // {{{
|
||||||
|
return loadStoreFile(localDbFilePath)
|
||||||
|
} // }}}
|
||||||
|
|
||||||
|
func loadEnvStore() *EnvStore { // {{{
|
||||||
|
// Load global store first
|
||||||
|
globalStore := loadGlobalEnvStore()
|
||||||
|
|
||||||
|
// Check if local store exists
|
||||||
|
if _, err := os.Stat(localDbFilePath); os.IsNotExist(err) {
|
||||||
|
// No local store, just return global
|
||||||
|
return globalStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load local store
|
||||||
|
localStore := loadLocalEnvStore()
|
||||||
|
|
||||||
|
// Merge stores (local takes precedence)
|
||||||
|
mergedStore := &EnvStore{
|
||||||
|
Vars: make(map[string]string),
|
||||||
|
Tags: make(map[string][]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy all global vars and tags
|
||||||
|
maps.Copy(mergedStore.Vars, globalStore.Vars)
|
||||||
|
maps.Copy(mergedStore.Tags, globalStore.Tags)
|
||||||
|
|
||||||
|
// Override/add local vars and tags
|
||||||
|
maps.Copy(mergedStore.Vars, localStore.Vars)
|
||||||
|
maps.Copy(mergedStore.Tags, localStore.Tags)
|
||||||
|
|
||||||
|
return mergedStore
|
||||||
|
} // }}}
|
||||||
|
|
||||||
|
func saveGlobalEnvStore(store *EnvStore) { // {{{
|
||||||
key := loadKey()
|
key := loadKey()
|
||||||
data, err := json.Marshal(store)
|
data, err := json.Marshal(store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -507,3 +850,21 @@ func saveEnvStore(store *EnvStore) { // {{{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
|
func saveLocalEnvStore(store *EnvStore) { // {{{
|
||||||
|
key := loadKey()
|
||||||
|
data, err := json.Marshal(store)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonce [24]byte
|
||||||
|
if _, err := rand.Read(nonce[:]); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
encrypted := secretbox.Seal(nonce[:], data, &nonce, &key)
|
||||||
|
|
||||||
|
if err := os.WriteFile(localDbFilePath, encrypted, 0600); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
} // }}}
|
||||||
|
@ -111,6 +111,29 @@ func (c *Client) RetrievePassword() ([]byte, error) {
|
|||||||
return []byte(resp.Data), nil
|
return []byte(resp.Data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KillDaemon sends a kill command to the daemon to stop it
|
||||||
|
func (c *Client) KillDaemon() error {
|
||||||
|
internal.DaemonLog.Printf("Sending kill command to daemon")
|
||||||
|
msg := Message{
|
||||||
|
Command: "kill",
|
||||||
|
}
|
||||||
|
|
||||||
|
internal.DaemonLog.Printf("Sending kill message to daemon")
|
||||||
|
resp, err := c.sendMessage(msg)
|
||||||
|
if err != nil {
|
||||||
|
internal.DaemonLog.Printf("Failed to send kill message: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !resp.Success {
|
||||||
|
internal.DaemonLog.Printf("Daemon reported error: %s", resp.Error)
|
||||||
|
return fmt.Errorf("failed to kill daemon: %s", resp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal.DaemonLog.Printf("Kill command sent successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// sendMessage sends a message to the daemon and returns the response
|
// sendMessage sends a message to the daemon and returns the response
|
||||||
func (c *Client) sendMessage(msg Message) (*Response, error) {
|
func (c *Client) sendMessage(msg Message) (*Response, error) {
|
||||||
internal.DaemonLog.Printf("Connecting to daemon socket: %s", c.socketPath)
|
internal.DaemonLog.Printf("Connecting to daemon socket: %s", c.socketPath)
|
||||||
|
@ -217,6 +217,23 @@ func handleConnection(conn net.Conn, cache *PasswordCache) {
|
|||||||
internal.DaemonLog.Println("Password retrieved, sending success response")
|
internal.DaemonLog.Println("Password retrieved, sending success response")
|
||||||
encoder.Encode(Response{Success: true, Data: string(password)})
|
encoder.Encode(Response{Success: true, Data: string(password)})
|
||||||
|
|
||||||
|
case "kill":
|
||||||
|
internal.DaemonLog.Println("Kill command received, shutting down daemon")
|
||||||
|
encoder.Encode(Response{Success: true})
|
||||||
|
// Close the connection before exiting
|
||||||
|
conn.Close()
|
||||||
|
// Clean up the socket file
|
||||||
|
os.Remove(conn.LocalAddr().String())
|
||||||
|
internal.DaemonLog.Printf("Removed socket file: %s", conn.LocalAddr().String())
|
||||||
|
// Exit with success status
|
||||||
|
internal.DaemonLog.Println("Daemon shutting down gracefully")
|
||||||
|
// Use goroutine to allow response to be sent before exit
|
||||||
|
go func() {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
|
||||||
default:
|
default:
|
||||||
internal.DaemonLog.Printf("Unknown command received: %s", msg.Command)
|
internal.DaemonLog.Printf("Unknown command received: %s", msg.Command)
|
||||||
encoder.Encode(Response{Success: false, Error: "Unknown command"})
|
encoder.Encode(Response{Success: false, Error: "Unknown command"})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user