commit 71a32d0630c94aabe0730b27c27ea7ef822640a0 Author: serr Date: Fri May 2 20:37:24 2025 +0300 new diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b51d7b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.bckr.json \ No newline at end of file diff --git a/bckr.exe b/bckr.exe new file mode 100644 index 0000000..a672150 Binary files /dev/null and b/bckr.exe differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c8beda6 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module main + +go 1.23.2 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/main.go b/main.go new file mode 100644 index 0000000..4c2f35f --- /dev/null +++ b/main.go @@ -0,0 +1,281 @@ +package main + +import ( + "archive/zip" + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "mime/multipart" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "slices" + "syscall" + "time" +) + +func init() { + logFile, err := os.OpenFile(".bckr.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + log.Fatal(err) + } + log.SetPrefix("bckr | ") + log.SetOutput(logFile) +} + +func main() { + + hideConsoleWindow() + + for { + + // Загрузка конфига + config, err := loadConfig(".bckr.json") + if err != nil { + log.Fatal(err) + } + // + + // Создание архива + archiveName := "backup_" + time.Now().Format("2006-01-02_15-04-05") + ".zip" + if err := createArchive(archiveName, config.TargetsFilesOrDirs, config.IgnoreFilesOrDirs); err != nil { + log.Println(err) + goto sleep + } + log.Println("Archive created successfully:", archiveName) + // + + // Отправка архива + if err := TGBotSendFile(config.TGYourBotToken, config.TGYourUserId, archiveName, ""); err != nil { + log.Println(err) + goto sleep + } + log.Println("Archive sent successfully:", archiveName) + // + + // Сон + sleep: + os.Remove(archiveName) + time.Sleep(time.Duration(config.IntervalInSeconds) * time.Second) + // + } + +} + +type config struct { + TGYourBotToken string + TGYourUserId string + IntervalInSeconds uint64 + TargetsFilesOrDirs []string + IgnoreFilesOrDirs []string +} + +func loadConfig(configPath string) (*config, error) { + cfg := &config{} + configFile, err := os.ReadFile(configPath) + if err != nil { + return nil, err + } + err = json.Unmarshal(configFile, cfg) + if err != nil { + return nil, err + } + return cfg, nil +} + +func createArchive(archiveName string, targets, ignore []string) error { + // Создание файла архива + archiveFile, err := os.Create(archiveName) + if err != nil { + return err + } + defer archiveFile.Close() + // + + // Полный путь до архива + archiveFileAbsPath, err := filepath.Abs(archiveName) + if err != nil { + return err + } + // + + // writer для ZIP-архива + zipWriter := zip.NewWriter(archiveFile) + defer zipWriter.Close() + // + + // Преобразуем ignore-пути в абсолютные для сравнения + var ignorePaths []string + for _, path := range ignore { + absPath, err := filepath.Abs(path) + if err != nil { + return err + } + ignorePaths = append(ignorePaths, absPath) + } + // И не архивируем сами себя + ignorePaths = append(ignorePaths, archiveFileAbsPath) + // + + // Проверяет, нужно ли игнорировать путь + shouldIgnore := func(path string) bool { + absPath, err := filepath.Abs(path) + if err != nil { + return false + } + + if slices.Contains(ignorePaths, absPath) { + return true + } + + return false + } + // + + // Обрабатываем каждый целевой путь + for _, target := range targets { + // Проверяем существование пути + if _, err := os.Stat(target); os.IsNotExist(err) { + log.Printf("Warning: target path does not exist: %s", target) + continue + } + + // Рекурсивно обходим начиная с target + err := filepath.Walk(target, func(filePath string, fileInfo os.FileInfo, err error) error { + if err != nil { + // ошибка доступа + return err + } + + // Пропускаем игнорируемые пути + if shouldIgnore(filePath) { + if fileInfo.IsDir() { + return filepath.SkipDir // Пропускаем всю директорию + } + return nil // Пропускаем файл + } + + // Создаем заголовок файла в архиве + header, err := zip.FileInfoHeader(fileInfo) + if err != nil { + return err + } + + // Устанавливаем правильное имя файла в архиве + header.Name, err = filepath.Rel(filepath.Dir(target), filePath) + if err != nil { + return err + } + + // Для директорий добавляем слеш в конец + if fileInfo.IsDir() { + header.Name += "/" + } else { + // Устанавливаем метод сжатия для файликов + header.Method = zip.Deflate + } + + // Создаем запись в архиве + writer, err := zipWriter.CreateHeader(header) + if err != nil { + return err + } + + // Если это файл - копируем его содержимое + if !fileInfo.IsDir() { + file, err := os.Open(filePath) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(writer, file) + if err != nil { + return err + } + } + + return nil + }) + + if err != nil { + return err + } + } + + return nil +} + +func TGBotSendFile(botToken, userId, filePath, caption string) error { + apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendDocument", botToken) + + file, err := os.Open(filePath) + if err != nil { + return err + } + defer file.Close() + + body := &bytes.Buffer{} + + writer := multipart.NewWriter(body) + + part, err := writer.CreateFormFile("document", filepath.Base(file.Name())) + if err != nil { + return err + } + + _, err = io.Copy(part, file) + if err != nil { + return err + } + + writer.WriteField("chat_id", userId) + writer.WriteField("caption", caption) + + err = writer.Close() + if err != nil { + return err + } + + req, err := http.NewRequest("POST", apiURL, body) + if err != nil { + return err + } + + req.Header.Set("Content-Type", writer.FormDataContentType()) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + return nil +} + +func hideConsoleWindow() { + if runtime.GOOS == "windows" { + hideConsoleWindowWindows() + } else if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { + hideConsoleWindowUnix() + } +} + +func hideConsoleWindowWindows() { + kernel32 := syscall.NewLazyDLL("kernel32.dll") + proc := kernel32.NewProc("FreeConsole") + proc.Call() +} + +func hideConsoleWindowUnix() { + cmd := exec.Command("nohup", os.Args[0]) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Start() + os.Exit(0) +}