feat: Config can be loaded from any path with --config-file (-c) flag.

This commit is contained in:
2026-06-15 08:18:34 -04:00
parent e23ea960d7
commit 4a26ee8145
15 changed files with 133 additions and 166 deletions

View File

@@ -2,22 +2,17 @@
## Current State ## Current State
- 101 tests, all passing - 104 tests, all passing
- Strong coverage: crypto (100%), ssh (90%), db CRUD + env_file + update_dir, config save/load + paths, scan, features, cant_scan, parse_args - Strong coverage: crypto, ssh, db CRUD + env_file + update_dir, config save/load + paths, scan, features, cant_scan, parse_args, `-c`/`--config-file` flag
- Misleading test files: `cmd_check_test`, `cmd_list_test`, `cmd_nushell_completion_test` don't test their namesake procs - Misleading test files: `cmd_check_test`, `cmd_list_test`, `cmd_nushell_completion_test` don't test their namesake procs
- Biggest remaining gap: all `cmd_*` handlers untested - Biggest remaining gap: all `cmd_*` handlers untested
## Next: `load_config` / `save_config` path param + `-c`/`--config-file` flag ## Command handler tests
- Refactor `load_config(path: string = "")` and `save_config(cfg, force, path: string = "")` — empty string defaults to `~/.envr/config.json`
- Add `-c`/`--config-file` to `parse_args` (now testable)
- Wire through `main.odin` so commands receive the config path
- Unblocks command handler tests with fixture configs
## Command handlers (need DB + filesystem fixtures) Stdout will be captured by redirecting `os.stdout` to a pipe.
### `cmd_version` (cmd_version.odin) ### `cmd_version` (cmd_version.odin)
- Test default output (prints VERSION) - Test default output (prints VERSION)
- Capture stdout, assert content
### `cmd_list` (cmd_list.odin) ### `cmd_list` (cmd_list.odin)
- Test TTY path: fixture DB with rows, capture table output - Test TTY path: fixture DB with rows, capture table output
@@ -69,7 +64,6 @@
## Notes ## Notes
- All command handler tests will need stdout capture. Consider extracting a helper or using `io.Writer` injection.
- DB integration tests should use in-memory SQLite (`:memory:`) where possible. - DB integration tests should use in-memory SQLite (`:memory:`) where possible.
- Temp dir fixtures should follow the pattern in `scan_test.odin`. - Temp dir fixtures should follow the pattern in `scan_test.odin`.
- External dependency tests (`fd`, `git`) should use `#assert` to ensure the dependency is present rather than silently skipping (TODO 28). - External dependency tests (`fd`, `git`) should use `#assert` to ensure the dependency is present rather than silently skipping (TODO 28).

View File

@@ -8,10 +8,11 @@ import "core:os"
import "core:strings" import "core:strings"
Command :: struct { Command :: struct {
name: string, name: string,
args: [dynamic]string, args: [dynamic]string,
flags: map[string]string, flags: map[string]string,
bool_set: map[string]bool, bool_set: map[string]bool,
config_path: string,
} }
CommandInfo :: struct { CommandInfo :: struct {
@@ -94,6 +95,16 @@ parse_args :: proc(args: []string) -> (cmd: Command, ok: bool) {
} }
} }
if val, ok := cmd.flags["config-file"]; ok {
cmd.config_path = val
} else if val, ok := cmd.flags["c"]; ok {
cmd.config_path = val
} else {
// FIXME: Handle err
home, _ := os.user_home_dir(context.allocator)
cmd.config_path = default_config_path(home)
}
if has_flag(&cmd, "help") { if has_flag(&cmd, "help") {
print_command_help(cmd.name) print_command_help(cmd.name)
return Command{}, false return Command{}, false
@@ -146,7 +157,7 @@ write_command_help :: proc(name: string, w: io.Writer) -> bool {
fmt.wprintf(w, "\n%s\n", info.long, flush = false) fmt.wprintf(w, "\n%s\n", info.long, flush = false)
} }
fmt.wprintf(w, "\nFlags:\n -h, --help help for %s\n", info.name, flush = false) fmt.wprintf(w, "\nFlags:\n -h, --help help for %s\n -c, --config-file <path> config file (default \"~/.envr/config.json\")\n", info.name, flush = false)
return true return true
} }
@@ -227,6 +238,7 @@ Available Commands:
` `
Flags: Flags:
-h, --help help for envr -h, --help help for envr
-c, --config-file <path> config file (default "~/.envr/config.json")
Use "envr [command] --help" for more information about a command. Use "envr [command] --help" for more information about a command.
`, `,

View File

@@ -315,3 +315,44 @@ test_parse_args_flag_then_positional_then_flag :: proc(t: ^testing.T) {
} }
@(test) @(test)
test_parse_args_config_file_long_flag :: proc(t: ^testing.T) {
cmd, ok := parse_args([]string{"envr", "list", "--config-file", "/custom/config.json"})
testing.expect(t, ok, "should succeed")
if !ok do return
defer delete(cmd.args)
defer delete(cmd.flags)
defer delete(cmd.bool_set)
testing.expect(t, cmd.config_path == "/custom/config.json", "config_path should be set from --config-file")
}
@(test)
test_parse_args_config_file_short_flag :: proc(t: ^testing.T) {
cmd, ok := parse_args([]string{"envr", "list", "-c", "/custom/config.json"})
testing.expect(t, ok, "should succeed")
if !ok do return
defer delete(cmd.args)
defer delete(cmd.flags)
defer delete(cmd.bool_set)
testing.expect(t, cmd.config_path == "/custom/config.json", "config_path should be set from -c")
}
@(test)
test_parse_args_config_file_defaults :: proc(t: ^testing.T) {
cmd, ok := parse_args([]string{"envr", "list"})
testing.expect(t, ok, "should succeed")
if !ok do return
defer delete(cmd.args)
defer delete(cmd.flags)
defer delete(cmd.bool_set)
testing.expect(t, len(cmd.config_path) > 0, "config_path should default to non-empty path")
testing.expect(
t,
strings.contains(cmd.config_path, ".envr"),
"default config_path should contain .envr dir, got %s",
)
}

View File

@@ -21,7 +21,7 @@ cmd_backup :: proc(cmd: ^Command) {
return return
} }
db, db_ok := db_open() db, db_ok := db_open(cmd.config_path)
if !db_ok { if !db_ok {
// TODO: log a message // TODO: log a message
return return

View File

@@ -31,7 +31,7 @@ cmd_check :: proc(cmd: ^Command) {
abs_path = resolved abs_path = resolved
} }
db, db_ok := db_open() db, db_ok := db_open(cmd.config_path)
if !db_ok { if !db_ok {
return return
} }

View File

@@ -2,7 +2,6 @@ package main
import "core:fmt" import "core:fmt"
import "core:os" import "core:os"
import "core:path/filepath"
cmd_edit_config :: proc(cmd: ^Command) { cmd_edit_config :: proc(cmd: ^Command) {
editor := os.get_env("EDITOR", context.allocator) editor := os.get_env("EDITOR", context.allocator)
@@ -11,11 +10,7 @@ cmd_edit_config :: proc(cmd: ^Command) {
return return
} }
config_path, join_err := filepath.join([]string{envr_dir(), "config.json"}) config_path := cmd.config_path
if join_err != nil {
fmt.printf("Error building config path: %v\n", join_err)
return
}
_, stat_err := os.stat(config_path, context.allocator) _, stat_err := os.stat(config_path, context.allocator)
if stat_err != nil { if stat_err != nil {

View File

@@ -5,7 +5,7 @@ import "core:fmt"
cmd_init :: proc(cmd: ^Command) { cmd_init :: proc(cmd: ^Command) {
force := has_flag(cmd, "force") || has_flag(cmd, "f") force := has_flag(cmd, "force") || has_flag(cmd, "f")
_, cfg_exists := load_config() _, cfg_exists := load_config(cmd.config_path)
if cfg_exists && !force { if cfg_exists && !force {
fmt.println("You have already initialized envr.") fmt.println("You have already initialized envr.")
fmt.println("Run again with the --force flag if you want to reinitialize.") fmt.println("Run again with the --force flag if you want to reinitialize.")
@@ -41,7 +41,7 @@ cmd_init :: proc(cmd: ^Command) {
return return
} }
cfg := new_config(selected_paths[:]) cfg := new_config(selected_paths[:], cmd.config_path)
if !save_config(cfg, force = force) { if !save_config(cfg, force = force) {
return return
} }

View File

@@ -14,7 +14,7 @@ ListEntry :: struct {
} }
cmd_list :: proc(cmd: ^Command) { cmd_list :: proc(cmd: ^Command) {
db, db_ok := db_open() db, db_ok := db_open(cmd.config_path)
if !db_ok { if !db_ok {
return return
} }

View File

@@ -28,7 +28,7 @@ cmd_remove :: proc(cmd: ^Command) {
abs_path = resolved abs_path = resolved
} }
db, db_ok := db_open() db, db_ok := db_open(cmd.config_path)
if !db_ok { if !db_ok {
return return
} }

View File

@@ -29,7 +29,7 @@ cmd_restore :: proc(cmd: ^Command) {
abs_path = resolved abs_path = resolved
} }
db, db_ok := db_open() db, db_ok := db_open(cmd.config_path)
if !db_ok { if !db_ok {
return return
} }

View File

@@ -14,7 +14,7 @@ cmd_scan :: proc(cmd: ^Command) {
return return
} }
db, db_ok := db_open() db, db_ok := db_open(cmd.config_path)
if !db_ok { if !db_ok {
return return
} }

View File

@@ -14,7 +14,7 @@ SyncEntry :: struct {
// TODO: Check for quiet failures. // TODO: Check for quiet failures.
cmd_sync :: proc(cmd: ^Command) { cmd_sync :: proc(cmd: ^Command) {
db, db_ok := db_open() db, db_ok := db_open(cmd.config_path)
if !db_ok { if !db_ok {
return return
} }

View File

@@ -18,21 +18,18 @@ ScanConfig :: struct {
} }
Config :: struct { Config :: struct {
Keys: [dynamic]SshKeyPair `json:"keys"`, Keys: [dynamic]SshKeyPair `json:"keys"`,
ScanConfig: ScanConfig `json:"scan"`, ScanConfig: ScanConfig `json:"scan"`,
config_path: string `json:"-"`,
} }
load_config :: proc() -> (Config, bool) { default_config_path :: proc(home: string) -> string {
home, home_err := os.user_home_dir(context.temp_allocator) // FIXME: catch error
if home_err != nil { path, _ := filepath.join([]string{home, ".envr", "config.json"})
fmt.printf("Error getting home dir: %v\n", home_err) return path
return Config{}, false }
}
config_path, join_err := filepath.join([]string{home, ".envr", "config.json"})
if join_err != nil {
return Config{}, false
}
load_config :: proc(config_path: string) -> (Config, bool) {
data, read_err := os.read_entire_file_from_path(config_path, context.allocator) data, read_err := os.read_entire_file_from_path(config_path, context.allocator)
if read_err != nil { if read_err != nil {
fmt.println("No config file found. Please run `envr init` to generate one.") fmt.println("No config file found. Please run `envr init` to generate one.")
@@ -45,6 +42,7 @@ load_config :: proc() -> (Config, bool) {
fmt.printf("Error parsing config: %v\n", err) fmt.printf("Error parsing config: %v\n", err)
return Config{}, false return Config{}, false
} }
cfg.config_path = config_path
return cfg, true return cfg, true
} }
@@ -55,15 +53,12 @@ delete_config :: proc(cfg: Config) {
delete(cfg.ScanConfig.Include) delete(cfg.ScanConfig.Include)
} }
envr_dir :: proc() -> string { envr_dir :: proc(config_path: string) -> string {
home, _ := os.user_home_dir(context.allocator) return filepath.dir(config_path)
dir, _ := filepath.join([]string{home, ".envr"})
return dir
} }
data_encrypted_path :: proc() -> string { data_encrypted_path :: proc(config_path: string) -> string {
dir := envr_dir() path, _ := filepath.join([]string{envr_dir(config_path), "data.envr"})
path, _ := filepath.join([]string{dir, "data.envr"})
return path return path
} }
@@ -113,7 +108,10 @@ find_ssh_private_keys :: proc() -> (keys: [dynamic]string, ok: bool) {
return return
} }
new_config :: proc(private_key_paths: []string) -> Config { new_config :: proc(
private_key_paths: []string,
cfg_path: string = "~/.envr/config.json",
) -> Config {
keys := make([dynamic]SshKeyPair, 0, len(private_key_paths)) keys := make([dynamic]SshKeyPair, 0, len(private_key_paths))
for priv in private_key_paths { for priv in private_key_paths {
// TODO: Is this bad? // TODO: Is this bad?
@@ -136,30 +134,22 @@ new_config :: proc(private_key_paths: []string) -> Config {
Include = include, Include = include,
} }
return Config{Keys = keys, ScanConfig = scan_cfg} return Config{Keys = keys, ScanConfig = scan_cfg, config_path = cfg_path}
} }
save_config :: proc(cfg: Config, force: bool = false) -> bool { save_config :: proc(cfg: Config, force: bool = false) -> bool {
home, home_err := os.user_home_dir(context.allocator) config_dir := envr_dir(cfg.config_path)
if home_err != nil {
fmt.printf("Error getting home dir: %v\n", home_err)
return false
}
config_dir, _ := filepath.join([]string{home, ".envr"})
if !os.exists(config_dir) { if !os.exists(config_dir) {
mkdir_err := os.make_directory(config_dir) mkdir_err := os.make_directory(config_dir)
if mkdir_err != nil { if mkdir_err != nil {
fmt.printf("Error creating ~/.envr directory: %v\n", mkdir_err) fmt.printf("Error creating %s directory: %v\n", config_dir, mkdir_err)
return false return false
} }
} }
config_path, _ := filepath.join([]string{config_dir, "config.json"}) if os.exists(cfg.config_path) && !force {
info, stat_err := os.stat(cfg.config_path, context.allocator)
if os.exists(config_path) && !force {
info, stat_err := os.stat(config_path, context.allocator)
if stat_err == nil { if stat_err == nil {
defer os.file_info_delete(info, context.allocator) defer os.file_info_delete(info, context.allocator)
if info.size > 0 { if info.size > 0 {
@@ -175,7 +165,7 @@ save_config :: proc(cfg: Config, force: bool = false) -> bool {
return false return false
} }
write_err := os.write_entire_file(config_path, data) write_err := os.write_entire_file(cfg.config_path, data)
if write_err != nil { if write_err != nil {
fmt.printf("Error writing config: %v\n", write_err) fmt.printf("Error writing config: %v\n", write_err)
return false return false

View File

@@ -2,6 +2,7 @@ package main
import "core:fmt" import "core:fmt"
import "core:os" import "core:os"
import "core:path/filepath"
import "core:strings" import "core:strings"
import "core:sync" import "core:sync"
import "core:testing" import "core:testing"
@@ -69,27 +70,19 @@ test_new_config_exclude_patterns :: proc(t: ^testing.T) {
@(test) @(test)
test_save_load_config_roundtrip :: proc(t: ^testing.T) { test_save_load_config_roundtrip :: proc(t: ^testing.T) {
sync.mutex_lock(&home_mutex)
defer sync.mutex_unlock(&home_mutex)
old_home := os.get_env("HOME", context.temp_allocator)
defer {
if old_home != "" {
os.set_env("HOME", old_home)
}
}
base := fmt.tprintf("/tmp/envr-test-cfg-rt-%d", os.get_pid()) base := fmt.tprintf("/tmp/envr-test-cfg-rt-%d", os.get_pid())
os.mkdir_all(base) os.mkdir_all(base)
defer os.remove_all(base) defer os.remove_all(base)
os.set_env("HOME", base)
cfg := new_config([]string{"/home/user/.ssh/id_ed25519"}) cfgPath, err := filepath.join([]string{base, "config.json"}, context.temp_allocator)
testing.expect(t, err == nil, "cfgPath should build successfully")
cfg := new_config([]string{"/home/user/.ssh/id_ed25519"}, cfgPath)
defer delete_config(cfg) defer delete_config(cfg)
testing.expect(t, save_config(cfg, force=true), "save should succeed") testing.expect(t, save_config(cfg, force = true), "save should succeed")
loaded, ok := load_config() loaded, ok := load_config(cfg.config_path)
testing.expect(t, ok, "load should succeed") testing.expect(t, ok, "load should succeed")
if !ok do return if !ok do return
defer delete_config(loaded) defer delete_config(loaded)
@@ -105,106 +98,62 @@ test_save_load_config_roundtrip :: proc(t: ^testing.T) {
@(test) @(test)
test_load_config_missing :: proc(t: ^testing.T) { test_load_config_missing :: proc(t: ^testing.T) {
sync.mutex_lock(&home_mutex) _, ok := load_config("/tmp/envr-test-cfg-nonexistent/config.json")
defer sync.mutex_unlock(&home_mutex)
old_home := os.get_env("HOME", context.temp_allocator)
defer {
if old_home != "" {
os.set_env("HOME", old_home)
}
}
base := fmt.tprintf("/tmp/envr-test-cfg-missing-%d", os.get_pid())
os.mkdir_all(base)
defer os.remove_all(base)
os.set_env("HOME", base)
_, ok := load_config()
testing.expect(t, !ok, "missing config should return false") testing.expect(t, !ok, "missing config should return false")
} }
@(test) @(test)
test_save_config_no_clobber :: proc(t: ^testing.T) { test_save_config_no_clobber :: proc(t: ^testing.T) {
sync.mutex_lock(&home_mutex)
defer sync.mutex_unlock(&home_mutex)
old_home := os.get_env("HOME", context.temp_allocator)
defer {
if old_home != "" {
os.set_env("HOME", old_home)
}
}
base := fmt.tprintf("/tmp/envr-test-cfg-noclobber-%d", os.get_pid()) base := fmt.tprintf("/tmp/envr-test-cfg-noclobber-%d", os.get_pid())
os.mkdir_all(base) os.mkdir_all(base)
defer os.remove_all(base) defer os.remove_all(base)
os.set_env("HOME", base)
cfg := new_config([]string{"/home/user/.ssh/key1"}) cfgPath, err := filepath.join([]string{base, "config.json"}, context.temp_allocator)
testing.expect(t, err == nil, "cfgPath should build successfully")
cfg := new_config([]string{"/home/user/.ssh/key1"}, cfgPath)
defer delete_config(cfg) defer delete_config(cfg)
testing.expect(t, save_config(cfg, force=true), "first save should succeed") testing.expect(t, save_config(cfg, force = true), "first save should succeed")
cfg2 := new_config([]string{"/home/user/.ssh/key2"}) cfg2 := new_config([]string{"/home/user/.ssh/key2"}, cfgPath)
defer delete_config(cfg2) defer delete_config(cfg2)
testing.expect(t, !save_config(cfg2), "second save without force should fail") testing.expect(t, !save_config(cfg2), "second save without force should fail")
} }
@(test) @(test)
test_save_config_force_overwrites :: proc(t: ^testing.T) { test_save_config_force_overwrites :: proc(t: ^testing.T) {
sync.mutex_lock(&home_mutex)
defer sync.mutex_unlock(&home_mutex)
old_home := os.get_env("HOME", context.temp_allocator)
defer {
if old_home != "" {
os.set_env("HOME", old_home)
}
}
base := fmt.tprintf("/tmp/envr-test-cfg-force-%d", os.get_pid()) base := fmt.tprintf("/tmp/envr-test-cfg-force-%d", os.get_pid())
os.mkdir_all(base) os.mkdir_all(base)
defer os.remove_all(base) defer os.remove_all(base)
os.set_env("HOME", base)
cfg := new_config([]string{"/home/user/.ssh/key1"}) cfgPath, err := filepath.join([]string{base, "config.json"}, context.temp_allocator)
testing.expect(t, err == nil, "cfgPath should build successfully")
cfg := new_config([]string{"/home/user/.ssh/key1"}, cfgPath)
defer delete_config(cfg) defer delete_config(cfg)
testing.expect(t, save_config(cfg, force=true), "first save should succeed") testing.expect(t, save_config(cfg, force = true), "first save should succeed")
cfg2 := new_config([]string{"/home/user/.ssh/key2"}) cfg2 := new_config([]string{"/home/user/.ssh/key2"}, cfgPath)
defer delete_config(cfg2) defer delete_config(cfg2)
testing.expect(t, save_config(cfg2, force=true), "force save should overwrite") testing.expect(t, save_config(cfg2, force = true), "force save should overwrite")
loaded, ok := load_config() loaded, ok := load_config(cfgPath)
testing.expect(t, ok, "load should succeed") testing.expect(t, ok, "load should succeed")
if !ok do return if !ok do return
defer delete_config(loaded) defer delete_config(loaded)
testing.expect(t, len(loaded.Keys) == 1, "should have 1 key") testing.expect(t, len(loaded.Keys) == 1, "should have 1 key")
testing.expect(t, loaded.Keys[0].Private == "/home/user/.ssh/key2", "should be the overwritten key") testing.expect(
t,
loaded.Keys[0].Private == "/home/user/.ssh/key2",
"should be the overwritten key",
)
} }
@(test) @(test)
test_envr_dir :: proc(t: ^testing.T) { test_envr_dir :: proc(t: ^testing.T) {
sync.mutex_lock(&home_mutex) dir := envr_dir("/tmp/envr-fake-home-envrdir/.envr/config.json")
defer sync.mutex_unlock(&home_mutex) testing.expectf(t, strings.has_suffix(dir, ".envr"), "dir should end with .envr, got %s", dir)
old_home := os.get_env("HOME", context.temp_allocator)
defer {
if old_home != "" {
os.set_env("HOME", old_home)
}
}
os.set_env("HOME", "/tmp/envr-fake-home-envrdir")
dir := envr_dir()
testing.expectf(
t,
strings.has_suffix(dir, ".envr"),
"dir should end with .envr, got %s",
dir,
)
testing.expectf( testing.expectf(
t, t,
strings.contains(dir, "envr-fake-home-envrdir"), strings.contains(dir, "envr-fake-home-envrdir"),
@@ -215,19 +164,7 @@ test_envr_dir :: proc(t: ^testing.T) {
@(test) @(test)
test_data_encrypted_path :: proc(t: ^testing.T) { test_data_encrypted_path :: proc(t: ^testing.T) {
sync.mutex_lock(&home_mutex) p := data_encrypted_path("/tmp/envr-fake-home-datapath/config.json")
defer sync.mutex_unlock(&home_mutex)
old_home := os.get_env("HOME", context.temp_allocator)
defer {
if old_home != "" {
os.set_env("HOME", old_home)
}
}
os.set_env("HOME", "/tmp/envr-fake-home-datapath")
p := data_encrypted_path()
testing.expectf(t, strings.has_suffix(p, "data.envr"), "should end with data.envr, got %s", p) testing.expectf(t, strings.has_suffix(p, "data.envr"), "should end with data.envr, got %s", p)
testing.expectf(t, strings.contains(p, ".envr"), "should contain .envr dir, got %s", p) testing.expectf(t, strings.contains(p, ".envr"), "should contain .envr dir, got %s", p)
} }
@@ -247,9 +184,7 @@ test_search_paths_expands_tilde :: proc(t: ^testing.T) {
os.set_env("HOME", "/tmp/envr-fake-home-search") os.set_env("HOME", "/tmp/envr-fake-home-search")
cfg := Config { cfg := Config {
ScanConfig = ScanConfig { ScanConfig = ScanConfig{Include = make([dynamic]string, 0, 1)},
Include = make([dynamic]string, 0, 1),
},
} }
defer delete(cfg.ScanConfig.Include) defer delete(cfg.ScanConfig.Include)
append(&cfg.ScanConfig.Include, "~") append(&cfg.ScanConfig.Include, "~")

12
db.odin
View File

@@ -48,13 +48,13 @@ make_temp_path :: proc() -> string {
return strings.to_string(b) return strings.to_string(b)
} }
db_open :: proc() -> (Db, bool) { db_open :: proc(cfg_path: string) -> (Db, bool) {
cfg, ok := load_config() cfg, ok := load_config(cfg_path)
if !ok { if !ok {
return Db{}, false return Db{}, false
} }
data_path := data_encrypted_path() data_path := data_encrypted_path(cfg.config_path)
_, stat_err := os.stat(data_path, context.allocator) _, stat_err := os.stat(data_path, context.allocator)
db: ^rawptr db: ^rawptr
@@ -108,8 +108,8 @@ db_close :: proc(d: ^Db) {
return return
} }
data_path := data_encrypted_path() data_path := data_encrypted_path(d.cfg.config_path)
envr_d := envr_dir() envr_d := envr_dir(d.cfg.config_path)
os.mkdir_all(envr_d) os.mkdir_all(envr_d)
write_err := os.write_entire_file(data_path, encrypted) write_err := os.write_entire_file(data_path, encrypted)
@@ -186,7 +186,7 @@ db_vacuum_to_file :: proc(db: ^rawptr, path: string) -> bool {
} }
db_restore_from_encrypted :: proc(db: ^rawptr, cfg: Config) -> bool { db_restore_from_encrypted :: proc(db: ^rawptr, cfg: Config) -> bool {
data_path := data_encrypted_path() data_path := data_encrypted_path(cfg.config_path)
encrypted_data, read_err := os.read_entire_file_from_path(data_path, context.temp_allocator) encrypted_data, read_err := os.read_entire_file_from_path(data_path, context.temp_allocator)
if read_err != nil { if read_err != nil {
fmt.printf("Error reading encrypted database: %v\n", read_err) fmt.printf("Error reading encrypted database: %v\n", read_err)