| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "path/filepath" |
| 6 | "strings" |
| 7 | |
| 8 | "reasonix/internal/config" |
| 9 | "reasonix/internal/fileutil" |
| 10 | ) |
| 11 | |
| 12 | // upsertDotEnv stores KEY=value in Reasonix's global .env and applies it to the |
| 13 | // running process so a rebuild picks it up without a restart. |
| 14 | func upsertDotEnv(key, value string) error { |
| 15 | _, err := config.SetCredential(key, value) |
| 16 | return err |
| 17 | } |
| 18 | |
| 19 | func removeDotEnv(key string) error { |
| 20 | return config.RemoveCredential(key) |
| 21 | } |
| 22 | |
| 23 | // upsertEnvFile merges KEY=value into a KEY=value file at path, preserving |
| 24 | // comments and unrelated lines, writing atomically via a sibling temp + rename. |
| 25 | func upsertEnvFile(path, key, value string) error { |
| 26 | key = strings.TrimSpace(key) |
| 27 | if key == "" { |
| 28 | return nil |
| 29 | } |
| 30 | var lines []string |
| 31 | if b, err := readFileUTF8(path); err == nil { |
| 32 | lines = strings.Split(strings.TrimRight(string(b), "\n"), "\n") |
| 33 | } |
| 34 | replaced := false |
| 35 | for i, ln := range lines { |
| 36 | t := strings.TrimSpace(ln) |
| 37 | if t == "" || strings.HasPrefix(t, "#") { |
| 38 | continue |
| 39 | } |
| 40 | if k, _, ok := strings.Cut(t, "="); ok && strings.TrimSpace(k) == key { |
| 41 | lines[i] = key + "=" + value |
| 42 | replaced = true |
| 43 | break |
| 44 | } |
| 45 | } |
| 46 | if !replaced { |
| 47 | lines = append(lines, key+"="+value) |
| 48 | } |
| 49 | out := strings.Join(lines, "\n") + "\n" |
| 50 | |
| 51 | dir := filepath.Dir(path) |
| 52 | if dir != "" && dir != "." { |
| 53 | if err := os.MkdirAll(dir, 0o755); err != nil { |
| 54 | return err |
| 55 | } |
| 56 | } |
| 57 | tmp, err := os.CreateTemp(dir, "credentials.*.tmp") |
| 58 | if err != nil { |
| 59 | return err |
| 60 | } |
| 61 | tmpPath := tmp.Name() |
| 62 | if _, err := tmp.WriteString(out); err != nil { |
| 63 | tmp.Close() |
| 64 | os.Remove(tmpPath) |
| 65 | return err |
| 66 | } |
| 67 | if err := tmp.Close(); err != nil { |
| 68 | os.Remove(tmpPath) |
| 69 | return err |
| 70 | } |
| 71 | if err := fileutil.ReplaceFile(tmpPath, path); err != nil { |
| 72 | os.Remove(tmpPath) |
| 73 | return err |
| 74 | } |
| 75 | return os.Setenv(key, value) |
| 76 | } |
| 77 | |
| 78 | func removeEnvFile(path, key string) error { |
| 79 | key = strings.TrimSpace(key) |
| 80 | if key == "" { |
| 81 | return nil |
| 82 | } |
| 83 | data, err := readFileUTF8(path) |
| 84 | if err != nil { |
| 85 | if os.IsNotExist(err) { |
| 86 | return os.Unsetenv(key) |
| 87 | } |
| 88 | return err |
| 89 | } |
| 90 | lines := strings.Split(strings.TrimRight(string(data), "\n"), "\n") |
| 91 | outLines := make([]string, 0, len(lines)) |
| 92 | for _, ln := range lines { |
| 93 | t := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(ln), "export ")) |
| 94 | if t == "" || strings.HasPrefix(t, "#") { |
| 95 | outLines = append(outLines, ln) |
| 96 | continue |
| 97 | } |
| 98 | if k, _, ok := strings.Cut(t, "="); ok && strings.TrimSpace(k) == key { |
| 99 | continue |
| 100 | } |
| 101 | outLines = append(outLines, ln) |
| 102 | } |
| 103 | out := "" |
| 104 | if len(outLines) > 0 { |
| 105 | out = strings.Join(outLines, "\n") + "\n" |
| 106 | } |
| 107 | |
| 108 | dir := filepath.Dir(path) |
| 109 | if dir != "" && dir != "." { |
| 110 | if err := os.MkdirAll(dir, 0o755); err != nil { |
| 111 | return err |
| 112 | } |
| 113 | } |
| 114 | tmp, err := os.CreateTemp(dir, "credentials.*.tmp") |
| 115 | if err != nil { |
| 116 | return err |
| 117 | } |
| 118 | tmpPath := tmp.Name() |
| 119 | if _, err := tmp.WriteString(out); err != nil { |
| 120 | tmp.Close() |
| 121 | os.Remove(tmpPath) |
| 122 | return err |
| 123 | } |
| 124 | if err := tmp.Close(); err != nil { |
| 125 | os.Remove(tmpPath) |
| 126 | return err |
| 127 | } |
| 128 | if err := fileutil.ReplaceFile(tmpPath, path); err != nil { |
| 129 | os.Remove(tmpPath) |
| 130 | return err |
| 131 | } |
| 132 | return os.Unsetenv(key) |
| 133 | } |
| 134 |