new
commit
71a32d0630
|
@ -0,0 +1 @@
|
|||
.bckr.json
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue