@@ -18,6 +18,7 @@ import (
1818 "github.com/CodeMonkeyCybersecurity/eos/pkg/eos_io"
1919 "github.com/CodeMonkeyCybersecurity/eos/pkg/interaction"
2020 "github.com/CodeMonkeyCybersecurity/eos/pkg/shared"
21+ "github.com/CodeMonkeyCybersecurity/eos/pkg/vault"
2122 "github.com/uptrace/opentelemetry-go-extra/otelzap"
2223 "go.uber.org/zap"
2324)
@@ -270,83 +271,137 @@ func (c *Client) handleRepositoryNotInitialized(resticOutput string) error {
270271func (c * Client ) getRepositoryPassword () (string , error ) {
271272 logger := otelzap .Ctx (c .rc .Ctx )
272273
273- // 1. Repository-local password file (created by quick backup generator)
274+ // 1. Vault-managed secret (preferred when Vault is configured)
275+ if isVaultConfigured () {
276+ if password , err := c .readPasswordFromVault (); err == nil {
277+ recordPasswordSource ("vault" , true )
278+ return password , nil
279+ } else {
280+ recordPasswordSource ("vault" , false )
281+ logger .Warn ("Failed to read repository password from Vault" ,
282+ zap .String ("repository" , c .repository .Name ),
283+ zap .Error (err ))
284+ }
285+ }
286+
287+ // 2. Repository-local password file (created by quick backup generator)
274288 localPasswordPath := filepath .Join (c .repository .URL , ".password" )
275289 if password , err := readPasswordFile (localPasswordPath ); err == nil {
290+ recordPasswordSource ("repo_password_file" , true )
276291 logger .Debug ("Using repository-local password file" ,
277292 zap .String ("path" , localPasswordPath ))
278293 return password , nil
279294 } else if err != nil && ! errors .Is (err , os .ErrNotExist ) {
295+ recordPasswordSource ("repo_password_file" , false )
280296 logger .Warn ("Failed to read repository-local password file" ,
281297 zap .String ("path" , localPasswordPath ),
282298 zap .Error (err ))
283299 }
284300
285- // 2 . Global secrets directory fallback (used by managed repositories)
301+ // 3 . Global secrets directory fallback (used by managed repositories)
286302 secretsPasswordPath := filepath .Join (secretsDirPath , fmt .Sprintf ("%s.password" , c .repository .Name ))
287303 if password , err := readPasswordFile (secretsPasswordPath ); err == nil {
304+ recordPasswordSource ("secrets_password_file" , true )
288305 logger .Debug ("Using secrets directory password file" ,
289306 zap .String ("path" , secretsPasswordPath ))
290307 return password , nil
291308 } else if err != nil && ! errors .Is (err , os .ErrNotExist ) {
309+ recordPasswordSource ("secrets_password_file" , false )
292310 logger .Warn ("Failed to read secrets directory password file" ,
293311 zap .String ("path" , secretsPasswordPath ),
294312 zap .Error (err ))
295313 }
296314
297- // 3 . Repository `.env` file (temporary secret storage during Vault testing )
315+ // 4 . Repository `.env` file (compatibility fallback )
298316 envPath := filepath .Join (c .repository .URL , ".env" )
299317 if password , err := readPasswordFromEnvFile (envPath ); err == nil {
318+ recordPasswordSource ("repo_env" , true )
300319 logger .Debug ("Using repository .env file for restic password" ,
301320 zap .String ("path" , envPath ))
302321 return password , nil
303322 } else if err != nil && ! errors .Is (err , os .ErrNotExist ) {
323+ recordPasswordSource ("repo_env" , false )
304324 logger .Warn ("Failed to read repository .env file" ,
305325 zap .String ("path" , envPath ),
306326 zap .Error (err ))
307327 }
308328
309- // 4a . Secrets directory .env file (fallback for non-local repositories)
329+ // 5 . Secrets directory .env file (fallback for non-local repositories)
310330 secretsEnvPath := filepath .Join (secretsDirPath , fmt .Sprintf ("%s.env" , c .repository .Name ))
311331 if password , err := readPasswordFromEnvFile (secretsEnvPath ); err == nil {
332+ recordPasswordSource ("secrets_env" , true )
312333 logger .Debug ("Using secrets .env file for restic password" ,
313334 zap .String ("path" , secretsEnvPath ))
314335 return password , nil
315336 } else if err != nil && ! errors .Is (err , os .ErrNotExist ) {
337+ recordPasswordSource ("secrets_env" , false )
316338 logger .Warn ("Failed to read secrets .env file" ,
317339 zap .String ("path" , secretsEnvPath ),
318340 zap .Error (err ))
319341 }
320342
321- // 4. Environment variable overrides (least preferred, but supported for manual ops)
322- if password := strings .TrimSpace (os .Getenv ("RESTIC_PASSWORD" )); password != "" {
323- logger .Warn ("Using RESTIC_PASSWORD environment variable; prefer password files for security" )
324- return password , nil
325- }
326-
343+ // 6. Environment variable overrides (least preferred)
327344 if passwordFile := strings .TrimSpace (os .Getenv ("RESTIC_PASSWORD_FILE" )); passwordFile != "" {
328345 if password , err := readPasswordFile (passwordFile ); err == nil {
346+ recordPasswordSource ("env_var" , true )
329347 logger .Warn ("Using RESTIC_PASSWORD_FILE override; prefer managed password files" ,
330348 zap .String ("path" , passwordFile ))
331349 return password , nil
332350 }
351+ recordPasswordSource ("env_var" , false )
352+ }
353+
354+ // 7. Raw environment variable override
355+ if password := strings .TrimSpace (os .Getenv ("RESTIC_PASSWORD" )); password != "" {
356+ recordPasswordSource ("env_var" , true )
357+ logger .Warn ("Using RESTIC_PASSWORD environment variable; prefer password files for security" )
358+ return password , nil
333359 }
334360
335361 missingErr := fmt .Errorf ("restic repository password not found; expected password file at %s, secrets fallback at %s, or RESTIC_PASSWORD in %s" ,
336362 localPasswordPath , secretsPasswordPath , envPath )
337363
364+ // 8. Interactive wizard fallback
338365 password , wizardErr := c .runPasswordWizard (localPasswordPath , secretsPasswordPath , []string {envPath , secretsEnvPath })
339366 if wizardErr == nil {
367+ recordPasswordSource ("wizard" , true )
340368 return password , nil
341369 }
342370 if wizardErr != nil && ! errors .Is (wizardErr , errPasswordWizardSkipped ) {
371+ recordPasswordSource ("wizard" , false )
343372 logger .Warn ("Password setup wizard failed" ,
344373 zap .Error (wizardErr ))
345374 }
346375
347376 return "" , missingErr
348377}
349378
379+ func (c * Client ) readPasswordFromVault () (string , error ) {
380+ var secret map [string ]interface {}
381+ vaultPath := fmt .Sprintf ("%s/%s" , VaultPasswordPathPrefix , c .repository .Name )
382+ if err := vault .ReadFromVault (c .rc , vaultPath , & secret ); err != nil {
383+ return "" , err
384+ }
385+
386+ raw , ok := secret [VaultPasswordKey ]
387+ if ! ok {
388+ return "" , fmt .Errorf ("vault secret %q missing key %q" , vaultPath , VaultPasswordKey )
389+ }
390+ password , ok := raw .(string )
391+ if ! ok {
392+ return "" , fmt .Errorf ("vault secret %q contains non-string password" , vaultPath )
393+ }
394+ password = strings .TrimSpace (password )
395+ if password == "" {
396+ return "" , fmt .Errorf ("vault secret %q contains empty password" , vaultPath )
397+ }
398+ return password , nil
399+ }
400+
401+ func isVaultConfigured () bool {
402+ return strings .TrimSpace (os .Getenv ("VAULT_ADDR" )) != ""
403+ }
404+
350405// InitRepository initializes a new restic repository
351406func (c * Client ) InitRepository () error {
352407 logger := otelzap .Ctx (c .rc .Ctx )
@@ -839,16 +894,9 @@ func (c *Client) executeHooks(hooks []string, hookType string) error {
839894 ctx , cancel := context .WithTimeout (c .rc .Ctx , HookTimeout )
840895 defer cancel ()
841896
842- cmd := exec .CommandContext (ctx , "sh" , "-c" , hookCmd )
843-
844- // Capture output
845- var stdout , stderr bytes.Buffer
846- cmd .Stdout = & stdout
847- cmd .Stderr = & stderr
848-
849897 // Execute hook
850898 start := time .Now ()
851- err := cmd . Run ( )
899+ err := RunHookWithSettings ( ctx , logger , hookCmd , c . config . Settings )
852900 duration := time .Since (start )
853901
854902 // Check for timeout
@@ -866,27 +914,14 @@ func (c *Client) executeHooks(hooks []string, hookType string) error {
866914 zap .String ("type" , hookType ),
867915 zap .String ("command" , hookCmd ),
868916 zap .Duration ("duration" , duration ),
869- zap .String ("stdout" , stdout .String ()),
870- zap .String ("stderr" , stderr .String ()),
871917 zap .Error (err ))
872- return fmt .Errorf ("hook failed: %w\n stdout: %s\n stderr: %s" ,
873- err , stdout .String (), stderr .String ())
918+ return fmt .Errorf ("hook failed: %w" , err )
874919 }
875920
876921 logger .Info ("Hook completed successfully" ,
877922 zap .String ("type" , hookType ),
878923 zap .String ("command" , hookCmd ),
879- zap .Duration ("duration" , duration ),
880- zap .Int ("stdout_bytes" , stdout .Len ()),
881- zap .Int ("stderr_bytes" , stderr .Len ()))
882-
883- // Log output if present (for debugging)
884- if stdout .Len () > 0 {
885- logger .Debug ("Hook stdout" , zap .String ("output" , stdout .String ()))
886- }
887- if stderr .Len () > 0 {
888- logger .Debug ("Hook stderr" , zap .String ("output" , stderr .String ()))
889- }
924+ zap .Duration ("duration" , duration ))
890925 }
891926
892927 logger .Info ("All hooks completed successfully" ,
0 commit comments