282 lines
5.9 KiB
Go
282 lines
5.9 KiB
Go
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)
|
||
}
|