bckr/main.go

282 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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)
}