package rig import ( "fmt" "io" "os" "path/filepath" ) // CopyOverlay copies files from /.runtime/overlay/ to the destination path. // This allows storing gitignored files (like .env) that services need at their root. // The overlay is copied non-recursively - only files, not subdirectories. // File permissions from the source are preserved. // // Structure: // // rig/ // .runtime/ // overlay/ // .env <- Copied to destPath // config.json <- Copied to destPath // // Returns nil if the overlay directory doesn't exist (nothing to copy). // Individual file copy failures are logged as warnings but don't stop the process. func CopyOverlay(rigPath, destPath string) error { overlayDir := filepath.Join(rigPath, ".runtime", "overlay") // Check if overlay directory exists entries, err := os.ReadDir(overlayDir) if err != nil { if os.IsNotExist(err) { // No overlay directory - not an error, just nothing to copy return nil } return fmt.Errorf("reading overlay dir: %w", err) } // Copy each file (not directories) from overlay to destination for _, entry := range entries { if entry.IsDir() { // Skip subdirectories - only copy files at overlay root continue } srcPath := filepath.Join(overlayDir, entry.Name()) dstPath := filepath.Join(destPath, entry.Name()) if err := copyFilePreserveMode(srcPath, dstPath); err != nil { // Log warning but continue - don't fail spawn for overlay issues fmt.Printf("Warning: could not copy overlay file %s: %v\n", entry.Name(), err) continue } } return nil } // copyFilePreserveMode copies a file from src to dst, preserving the source file's permissions. func copyFilePreserveMode(src, dst string) error { // Get source file info for permissions srcInfo, err := os.Stat(src) if err != nil { return fmt.Errorf("stat source: %w", err) } // Open source file srcFile, err := os.Open(src) if err != nil { return fmt.Errorf("open source: %w", err) } defer srcFile.Close() // Create destination file with same permissions dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, srcInfo.Mode().Perm()) if err != nil { return fmt.Errorf("create destination: %w", err) } defer dstFile.Close() // Copy contents if _, err := io.Copy(dstFile, srcFile); err != nil { return fmt.Errorf("copy contents: %w", err) } return nil }