clean up code some
All checks were successful
CI / build (push) Successful in 23s

This commit is contained in:
Jonathan Apodaca 2025-05-15 22:42:53 -06:00
parent 1922138133
commit 2573a3dbfe

View File

@ -46,78 +46,6 @@ func init() {
localDbFilePath = ".envvault.json.enc"
}
type EnvStore struct {
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 {
Init InitCmd `cmd:"" help:"Initialize the vault."`
List ListCmd `cmd:"" help:"List all environment variables."`
@ -208,81 +136,84 @@ func main() {
}
func subcommandInitVault() { // {{{
internal.Log.Println("Initializing vault")
if _, err := os.Stat(keyFilePath); os.IsNotExist(err) {
internal.Log.Println("Key file does not exist, creating new vault")
fmt.Fprint(os.Stderr, "Enter a new master password: ")
password, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Fprintln(os.Stderr)
if err != nil {
internal.Log.Printf("Failed to read password: %v", err)
log.Fatal(err)
}
fmt.Fprint(os.Stderr, "Enter a new master password (again): ")
password2, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Fprintln(os.Stderr)
if err != nil {
internal.Log.Printf("Failed to read confirmation password: %v", err)
log.Fatal(err)
}
if !bytes.Equal(password, password2) {
internal.Log.Println("Passwords do not match")
log.Fatal("Passwords do not match")
}
internal.Log.Println("Passwords match, caching password")
cachedPassword = password
// Generate a random key
internal.Log.Println("Generating random key")
var key [32]byte
if _, err := rand.Read(key[:]); err != nil {
internal.Log.Printf("Failed to generate random key: %v", err)
log.Fatal(err)
}
// Encrypt the key with the password
internal.Log.Println("Encrypting key with password")
encryptedKey := encryptKeyWithPassword(key[:], password)
internal.Log.Printf("Creating key directory: %s", filepath.Dir(keyFilePath))
if err := os.MkdirAll(filepath.Dir(keyFilePath), 0700); err != nil {
internal.Log.Printf("Failed to create key directory: %v", err)
log.Fatal(err)
}
internal.Log.Printf("Writing encrypted key to: %s", keyFilePath)
if err := os.WriteFile(keyFilePath, encryptedKey, 0600); err != nil {
internal.Log.Printf("Failed to write key file: %v", err)
log.Fatal(err)
}
// Initialize the db file
internal.Log.Println("Initializing empty environment store")
emptyStore := &EnvStore{Vars: make(map[string]string)}
saveGlobalEnvStore(emptyStore)
internal.Log.Println("Vault initialized successfully")
fmt.Fprintln(os.Stderr, "Vault initialized successfully.")
} else {
if exists(keyFilePath) {
internal.Log.Println("Vault already initialized")
fmt.Fprintln(os.Stderr, "Vault already initialized.")
return
}
internal.Log.Println("Initializing vault")
fmt.Fprint(os.Stderr, "Enter a new master password: ")
password, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Fprintln(os.Stderr)
if err != nil {
internal.Log.Printf("Failed to read password: %v", err)
log.Fatal(err)
}
fmt.Fprint(os.Stderr, "Enter a new master password (again): ")
password2, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Fprintln(os.Stderr)
if err != nil {
internal.Log.Printf("Failed to read confirmation password: %v", err)
log.Fatal(err)
}
if !bytes.Equal(password, password2) {
internal.Log.Println("Passwords do not match")
log.Fatal("Passwords do not match")
}
internal.Log.Println("Passwords match, caching password")
cachedPassword = password
// Generate a random key
internal.Log.Println("Generating random key")
var key [32]byte
if _, err := rand.Read(key[:]); err != nil {
internal.Log.Printf("Failed to generate random key: %v", err)
log.Fatal(err)
}
// Encrypt the key with the password
internal.Log.Println("Encrypting key with password")
encryptedKey := encryptKeyWithPassword(key[:], password)
internal.Log.Printf("Creating key directory: %s", filepath.Dir(keyFilePath))
if err := os.MkdirAll(filepath.Dir(keyFilePath), 0700); err != nil {
internal.Log.Printf("Failed to create key directory: %v", err)
log.Fatal(err)
}
internal.Log.Printf("Writing encrypted key to: %s", keyFilePath)
if err := os.WriteFile(keyFilePath, encryptedKey, 0600); err != nil {
internal.Log.Printf("Failed to write key file: %v", err)
log.Fatal(err)
}
// Initialize the db file
internal.Log.Println("Initializing empty environment store")
emptyStore := &EnvStore{filePath: dbFilePath, Vars: make(map[string]string)}
if err := emptyStore.Save(); err != nil {
internal.Log.Printf("Failed to initialize global environment store: %v", err)
log.Fatal(err)
}
internal.Log.Println("Vault initialized successfully")
fmt.Fprintln(os.Stderr, "Vault initialized successfully.")
}
// }}}
func subcommandAddEnvVar(cmd AddCmd) { // {{{
var store *EnvStore
var storeKind StoreKind
if cmd.Local {
store = loadLocalEnvStore()
internal.Log.Printf("Adding environment variable '%s' to local vault", cmd.Name)
storeKind = StoreKindLocal
} else {
store = loadGlobalEnvStore()
internal.Log.Printf("Adding environment variable '%s' to global vault", cmd.Name)
storeKind = StoreKindGlobal
}
store, err := NewEnvStore(storeKind)
if err != nil {
log.Fatal(err)
}
if cmd.Value == "" {
@ -296,28 +227,23 @@ func subcommandAddEnvVar(cmd AddCmd) { // {{{
}
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)
if err := store.Save(); err != nil {
log.Fatal(err)
}
fmt.Fprintf(os.Stderr, "Environment variable '%s' added to vault.\n", cmd.Name)
} // }}}
func subcommandRmEnvVar(name string, localOnly bool) { // {{{
localExists := false
// Check local store if it exists
if _, err := os.Stat(localDbFilePath); err == nil {
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)
if exists(localDbFilePath) {
localStore, err := NewEnvStoreFromPath(localDbFilePath)
if err != nil {
log.Fatal(err)
}
localStore.RemoveEnvVar(name)
if err := localStore.Save(); err != nil {
log.Fatal(err)
}
} else if localOnly {
fmt.Fprintf(os.Stderr, "Local vault does not exist.\n")
@ -325,31 +251,28 @@ func subcommandRmEnvVar(name string, localOnly bool) { // {{{
// 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)
globalStore, err := NewEnvStoreFromPath(dbFilePath)
if err != nil {
log.Fatal(err)
}
globalStore.RemoveEnvVar(name)
if err := globalStore.Save(); err != nil {
log.Fatal(err)
}
}
} // }}}
func subcommandTagEnvVar(cmd TagCmd) { // {{{
var store *EnvStore
// Determine which store to use
var storeKind StoreKind
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")
storeKind = StoreKindLocal
} else {
store = loadGlobalEnvStore()
internal.Log.Printf("Using global vault")
storeKind = StoreKindGlobal
}
store, err := NewEnvStore(storeKind)
if err != nil {
log.Fatal(err)
}
// Check if the variable exists
@ -387,12 +310,6 @@ func subcommandTagEnvVar(cmd TagCmd) { // {{{
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)
@ -400,26 +317,21 @@ func subcommandTagEnvVar(cmd TagCmd) { // {{{
}
// Save the store
if cmd.Local {
saveLocalEnvStore(store)
} else {
saveGlobalEnvStore(store)
if err := store.Save(); err != nil {
log.Fatal(err)
}
} // }}}
func subcommandListEnvVars(cmdArgs ListCmd) { // {{{
var store *EnvStore
// Determine which store to use
var storeKind StoreKind
if cmdArgs.Local {
if _, err := os.Stat(localDbFilePath); os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Local vault does not exist.\n")
return
}
store = loadLocalEnvStore()
storeKind = StoreKindLocal
} else {
// Default: load and merge stores
store = loadEnvStore()
storeKind = StoreKindGlobal
}
store, err := NewEnvStore(storeKind)
if err != nil {
log.Fatal(err)
}
// Sort keys for consistent output
@ -452,21 +364,17 @@ func subcommandListEnvVars(cmdArgs ListCmd) { // {{{
} // }}}
func subcommandExecCommand(cmdArgs ExecCmd) { // {{{
var store *EnvStore
// Determine which store to use
var storeKind StoreKind
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")
storeKind = StoreKindLocal
} else {
// Default: load and merge stores
store = loadEnvStore()
internal.Log.Println("Using merged vaults for execution")
storeKind = StoreKindGlobal
}
store, err := NewEnvStore(storeKind)
if err != nil {
log.Fatal(err)
}
envVars := os.Environ()
for candidateEnvName, candidateEnvValue := range store.Vars {
// Skip if not matching tag filter
@ -628,6 +536,191 @@ func subcommandRekeyVault() { // {{{
//
////////////////////////////////////////////////////////////////////////////////
func exists(path string) bool {
_, err := os.Stat(path)
if err != nil && os.IsNotExist(err) {
return false
}
return err == nil
}
type EnvStore struct { // {{{
filePath string
Vars map[string]string `json:"vars"`
Tags map[string][]string `json:"tags,omitempty"`
}
func NewEnvStoreFromPath(filePath string) (*EnvStore, error) {
key := loadKey()
store := EnvStore{
filePath: filePath,
Vars: make(map[string]string),
Tags: make(map[string][]string),
}
data, err := os.ReadFile(filePath)
if err != nil {
if os.IsNotExist(err) {
return &store, nil
}
return nil, err
}
if len(data) == 0 {
return &store, nil
}
var nonce [24]byte
copy(nonce[:], data[:24])
decrypted, ok := secretbox.Open(nil, data[24:], &nonce, &key)
if !ok {
log.Fatal("Decryption failed")
}
if err := json.Unmarshal(decrypted, &store); err != nil {
log.Fatal(err)
}
// Initialize Tags map if it's nil
if store.Tags == nil {
store.Tags = make(map[string][]string)
}
return &store, nil
}
type StoreKind int
const (
StoreKindGlobal = iota
StoreKindLocal
StoreKindMerged
)
func NewEnvStore(kind StoreKind) (*EnvStore, error) {
globalStore, globalErr := NewEnvStoreFromPath(dbFilePath)
localStore, localErr := NewEnvStoreFromPath(localDbFilePath)
if kind == StoreKindGlobal {
if globalErr != nil {
return nil, globalErr
}
return globalStore, nil
}
if kind == StoreKindLocal {
if localErr != nil {
return nil, localErr
}
return localStore, nil
}
if globalErr != nil {
return nil, globalErr
}
if localErr != nil {
return nil, localErr
}
// Override/add local vars and tags
maps.Copy(globalStore.Vars, localStore.Vars)
maps.Copy(globalStore.Tags, localStore.Tags)
return globalStore, nil
}
func (s *EnvStore) Save() error {
key := loadKey()
data, err := json.Marshal(s)
if err != nil {
return err
}
var nonce [24]byte
if _, err := rand.Read(nonce[:]); err != nil {
return err
}
encrypted := secretbox.Seal(nonce[:], data, &nonce, &key)
if err := os.MkdirAll(filepath.Dir(s.filePath), 0700); err != nil {
return err
}
if err := os.WriteFile(s.filePath, encrypted, 0600); err != nil {
return err
}
return nil
}
// 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
}
func (s *EnvStore) RemoveEnvVar(name string) {
delete(s.Vars, name)
delete(s.Tags, name)
} // }}}
func loadPassword() []byte { // {{{
internal.Log.Println("Getting password without caching")
@ -673,14 +766,14 @@ func loadPassword() []byte { // {{{
} // }}}
func loadKey() [32]byte { // {{{
// Get password but don't cache it yet
password := loadPassword()
encryptedKey, err := os.ReadFile(keyFilePath)
if err != nil {
log.Fatal(err)
}
// Get password but don't cache it yet
password := loadPassword()
decryptedKey, err := decryptKeyWithPassword(encryptedKey, password)
if err != nil {
log.Fatal("Invalid password or corrupted key file")
@ -753,118 +846,4 @@ func decryptKeyWithPassword(encryptedKey, password []byte) ([]byte, error) { //
return decrypted, nil
} // }}}
func loadStoreFile(filePath string) *EnvStore { // {{{
key := loadKey()
data, err := os.ReadFile(filePath)
if err != nil {
if os.IsNotExist(err) {
return &EnvStore{
Vars: make(map[string]string),
Tags: make(map[string][]string),
}
}
log.Fatal(err)
}
var store EnvStore
if len(data) > 0 {
var nonce [24]byte
copy(nonce[:], data[:24])
decrypted, ok := secretbox.Open(nil, data[24:], &nonce, &key)
if !ok {
log.Fatal("Decryption failed")
}
if err := json.Unmarshal(decrypted, &store); err != nil {
log.Fatal(err)
}
// Initialize Tags map if it's nil
if store.Tags == nil {
store.Tags = make(map[string][]string)
}
} else {
return &EnvStore{
Vars: make(map[string]string),
Tags: make(map[string][]string),
}
}
return &store
} // }}}
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()
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(dbFilePath, encrypted, 0600); err != nil {
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)
}
} // }}}
// }}}