diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0635f20 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# 테스트경로 +testA/ +testB/ + +# 실행파일 +backup diff --git a/backup b/backup deleted file mode 100755 index 155bb61..0000000 Binary files a/backup and /dev/null differ diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..ed5dd0e --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "flag" + "fmt" + "os" + "time" + + "git.lhk.o-r.kr/freerer2/simple_backup/internal/backup" + "git.lhk.o-r.kr/freerer2/simple_backup/internal/path" +) + +func main() { + if len(os.Args) < 3 { + fmt.Println("사용법: backup <원본경로> <백업경로> [옵션]") + os.Exit(1) + } + src := os.Args[1] + dst := os.Args[2] + + fs := flag.NewFlagSet("backup", flag.ExitOnError) + groupBy := fs.String("group-by", "", "백업 폴더 구조 기준: year, mon, day, hour, min, sec") + snapshot := fs.String("snapshot", "", "스냅샷 기준: y, ym, ymd") + dryRun := fs.Bool("dry-run", false, "복사하지 않고 어떤 파일이 복사되는지 출력") + verbose := fs.Bool("verbose", false, "복사 로그 자세히 출력") + force := fs.Bool("force", false, "속성 무시하고 무조건 덮어쓰기") + _ = fs.Parse(os.Args[3:]) + + now := time.Now() + backupPath := dst + + if *groupBy != "" { + backupPath = path.GetGroupByFolder(dst, *groupBy, now) + } + + if *snapshot != "" { + var snapshotFolder string + switch *snapshot { + case "y": + snapshotFolder = fmt.Sprintf("%d", now.Year()) + case "ym": + snapshotFolder = fmt.Sprintf("%d%02d", now.Year(), int(now.Month())) + case "ymd": + snapshotFolder = fmt.Sprintf("%d%02d%02d", now.Year(), int(now.Month()), now.Day()) + default: + fmt.Printf("오류: 지원하지 않는 snapshot 옵션입니다: %s\n", *snapshot) + os.Exit(1) + } + backupPath = fmt.Sprintf("%s/snapshots/%s", dst, snapshotFolder) + } + + fmt.Println("원본 경로:", src) + fmt.Println("최종 백업 경로:", backupPath) + if *dryRun { + fmt.Println("Dry-run 모드 활성화됨") + } + if *verbose { + fmt.Println("Verbose 모드 활성화됨") + } + if *force { + fmt.Println("Force 모드 활성화됨") + } + + if err := backup.RunBackup(src, backupPath, *dryRun, *verbose, *force); err != nil { + fmt.Println("백업 중 오류 발생:", err) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod index 5dd4e88..6963e16 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module backup +module git.lhk.o-r.kr/freerer2/simple_backup go 1.24.5 diff --git a/internal/backup/backup.go b/internal/backup/backup.go new file mode 100644 index 0000000..977743b --- /dev/null +++ b/internal/backup/backup.go @@ -0,0 +1,61 @@ +package backup + +import ( + "fmt" + "git.lhk.o-r.kr/freerer2/simple_backup/internal/copy" + "os" + "path/filepath" +) + +// RunBackup: 백업 실행 함수 +func RunBackup(src, dst string, dryRun, verbose, force bool) error { + if !dryRun { + // 백업 폴더 생성 + if err := os.MkdirAll(dst, 0755); err != nil { + return fmt.Errorf("백업 폴더 생성 실패: %w", err) + } + } + + return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + relPath, err := filepath.Rel(src, path) + if err != nil { + return err + } + targetPath := filepath.Join(dst, relPath) + + if info.IsDir() { + if verbose { + fmt.Println("디렉터리 생성:", targetPath) + } + if !dryRun { + return os.MkdirAll(targetPath, info.Mode()) + } + return nil + } + + if dryRun { + fmt.Println("복사 예정 파일:", targetPath) + return nil + } + + // force 옵션이 없으면 수정 시간 비교 + if !force { + dstInfo, err := os.Stat(targetPath) + if err == nil && !info.ModTime().After(dstInfo.ModTime()) { + if verbose { + fmt.Println("복사 스킵 (업데이트 필요 없음):", path) + } + return nil + } + } + + if verbose { + fmt.Println("파일 복사:", path, "→", targetPath) + } + return copy.RunCopy(path, targetPath) + }) +} diff --git a/internal/copy/copy.go b/internal/copy/copy.go new file mode 100644 index 0000000..2137e26 --- /dev/null +++ b/internal/copy/copy.go @@ -0,0 +1,49 @@ +package copy + +import ( + "io" + "os" +) + +// CopyFile 복사 함수: srcFile → dstFile +func RunCopy(srcFile, dstFile string) error { + src, err := os.Open(srcFile) + if err != nil { + return err + } + + defer func(src *os.File) { + err := src.Close() + if err != nil { + + } + }(src) + + dst, err := os.Create(dstFile) + if err != nil { + return err + } + + defer func(dst *os.File) { + err := dst.Close() + if err != nil { + + } + }(dst) + + _, err = io.Copy(dst, src) + if err != nil { + return err + } + + // 원본 파일의 권한을 복사 + info, err := os.Stat(srcFile) + if err == nil { + err := os.Chmod(dstFile, info.Mode()) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/path/path.go b/internal/path/path.go new file mode 100644 index 0000000..25a89bf --- /dev/null +++ b/internal/path/path.go @@ -0,0 +1,29 @@ +package path + +import ( + "fmt" + "time" +) + +// GetGroupByFolder: group-by 옵션에 따라 백업 경로 생성 +func GetGroupByFolder(basePath, groupBy string, t time.Time) string { + switch groupBy { + case "year": + return fmt.Sprintf("%s/%d", basePath, t.Year()) + case "mon": + return fmt.Sprintf("%s/%d%02d", basePath, t.Year(), t.Month()) + case "day": + return fmt.Sprintf("%s/%d%02d%02d", basePath, t.Year(), t.Month(), t.Day()) + case "hour": + return fmt.Sprintf("%s/%d%02d%02d_%02d", basePath, t.Year(), t.Month(), t.Day(), t.Hour()) + case "min": + return fmt.Sprintf("%s/%d%02d%02d_%02d%02d", basePath, t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) + case "sec": + return fmt.Sprintf("%s/%d%02d%02d_%02d%02d%02d", basePath, t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) + case "": + return basePath + default: + fmt.Printf("지원하지 않는 group-by 옵션입니다: %s\n", groupBy) + return basePath + } +} diff --git a/main.go b/main.go deleted file mode 100644 index 75348ea..0000000 --- a/main.go +++ /dev/null @@ -1,104 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - "time" -) - -func main() { - - // 위치 인자 처리 - if len(os.Args) < 3 { - fmt.Println("사용법: backup <원본경로> <백업경로> [옵션]") - os.Exit(1) - } - src := os.Args[1] - dst := os.Args[2] - - // flag 인자 정의 (3번째 인자부터 처리) - fs := flag.NewFlagSet("backup", flag.ExitOnError) - - groupBy := fs.String("group-by", "", "백업 폴더 구조 기준: year, mon, day, hour, min, sec") - snapshot := fs.String("snapshot", "", "스냅샷 기준: y, ym, ymd") - dryRun := fs.Bool("dry-run", false, "복사하지 않고 어떤 파일이 복사되는지 출력") - verbose := fs.Bool("verbose", false, "복사 로그 자세히 출력") - force := fs.Bool("force", false, "속성 무시하고 무조건 덮어쓰기") - - _ = fs.Parse(os.Args[3:]) // 옵션 인자 처리 - - now := time.Now() - - // 스냅샷 폴더 이름 생성 - var snapshotFolder string - switch *snapshot { - case "y": - snapshotFolder = fmt.Sprintf("%d", now.Year()) - case "ym": - snapshotFolder = fmt.Sprintf("%d%02d", now.Year(), int(now.Month())) - case "ymd": - snapshotFolder = fmt.Sprintf("%d%02d%02d", now.Year(), int(now.Month()), now.Day()) - case "": - // 없음 - default: - fmt.Printf("오류: 지원하지 않는 snapshot 옵션입니다: %s\n", *snapshot) - os.Exit(1) - } - - // 출력 확인 - // 그룹 폴더 이름 생성 - fmt.Println("원본 경로:", src) - fmt.Println("백업 경로:", dst) - backupPath := dst - if *groupBy != "" { - fmt.Println("백업 그룹 기준:", *groupBy) - if *groupBy != "" { - backupPath = getGroupByFolder(dst, *groupBy, now) - } - // 이후에 backupPath 폴더를 생성하고, 파일 복사 진행 - fmt.Println("최종 백업 경로:", backupPath) - } - if *snapshot != "" { - fmt.Println("스냅샷 폴더 이름:", snapshotFolder) - } - if *dryRun { - fmt.Println("Dry-run 모드 활성화됨") - } - if *verbose { - fmt.Println("Verbose 모드 활성화됨") - } - if *force { - fmt.Println("Force 모드 활성화됨") - } - - // 이후 로직: 폴더 생성, 복사 등... - -} - -// groupBy 옵션에 따른 폴더 경로 생성 함수 -func getGroupByFolder(basePath, groupBy string, t time.Time) string { - folder := basePath - - switch groupBy { - case "year": - folder = fmt.Sprintf("%s/%d", folder, t.Year()) - case "mon": - folder = fmt.Sprintf("%s/%d%02d", folder, t.Year(), t.Month()) - case "day": - folder = fmt.Sprintf("%s/%d%02d%02d", folder, t.Year(), t.Month(), t.Day()) - case "hour": - folder = fmt.Sprintf("%s/%d%02d%02d_%02d", folder, t.Year(), t.Month(), t.Day(), t.Hour()) - case "min": - folder = fmt.Sprintf("%s/%d%02d%02d_%02d%02d", folder, t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) - case "sec": - folder = fmt.Sprintf("%s/%d%02d%02d_%02d%02d%02d", folder, t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) - case "": - // 아무것도 안함 - default: - fmt.Printf("지원하지 않는 group-by 옵션입니다: %s\n", groupBy) - os.Exit(1) - } - - return folder -} diff --git a/testA/s.go b/testA/s.go new file mode 100644 index 0000000..4ea0069 --- /dev/null +++ b/testA/s.go @@ -0,0 +1 @@ +package testA