From 2dfeb54306eee23f0be63087ef47fdcc553daeeb Mon Sep 17 00:00:00 2001 From: Adis Durakovic Date: Sat, 6 Jan 2024 18:09:01 +0100 Subject: [PATCH] chore: refactoring --- app.go | 191 ++---------------- frontend/app.config.ts | 7 +- frontend/app.vue | 1 + frontend/components/Backup/ExcludeOptions.vue | 38 ++-- frontend/components/HeaderNavBar.vue | 25 ++- frontend/components/Repository/List.vue | 3 + frontend/components/Repository/Snapshots.vue | 107 +++++++--- frontend/components/Schedule/List.vue | 59 ++++++ frontend/components/Schedule/New.vue | 96 +++++++++ frontend/composables/useJobs.ts | 17 +- frontend/composables/useLogs.ts | 9 + frontend/composables/useSettings.ts | 9 +- frontend/composables/useSocket.ts | 21 ++ frontend/layouts/default.vue | 6 +- frontend/nuxt.config.ts | 2 +- frontend/package.json | 2 + frontend/package.json.md5 | 2 +- frontend/pages/logs.vue | 8 + frontend/pages/schedules/index.vue | 5 +- frontend/pnpm-lock.yaml | 18 ++ frontend/tailwind.config.js | 22 +- frontend/wailsjs/go/main/App.d.ts | 15 +- frontend/wailsjs/go/main/App.js | 16 -- frontend/wailsjs/go/models.ts | 37 +--- go.mod | 14 +- go.sum | 28 ++- main.go | 28 ++- restic/restic.go => restic.go | 85 +++----- scheduler.go | 134 ++++++++++++ server.go | 88 ++++++++ settings.go | 87 ++++++++ 31 files changed, 815 insertions(+), 365 deletions(-) create mode 100644 frontend/components/Schedule/List.vue create mode 100644 frontend/components/Schedule/New.vue create mode 100644 frontend/composables/useLogs.ts create mode 100644 frontend/composables/useSocket.ts create mode 100644 frontend/pages/logs.vue rename restic/restic.go => restic.go (52%) create mode 100644 scheduler.go create mode 100644 server.go create mode 100644 settings.go diff --git a/app.go b/app.go index a48b4d4..272c1c9 100644 --- a/app.go +++ b/app.go @@ -2,64 +2,31 @@ package main import ( "context" - "encoding/json" "fmt" - "io" - "log" "os" - "resticity/restic" - "sync" - "time" - "github.com/adrg/xdg" "github.com/energye/systray" "github.com/energye/systray/icon" - "github.com/go-co-op/gocron/v2" "github.com/google/uuid" "github.com/wailsapp/wails/v2/pkg/runtime" ) // App struct type App struct { - ctx context.Context - jmu sync.Mutex - scheduler gocron.Scheduler - runningJobs []BackupJob + ctx context.Context + scheduler *Scheduler + restic *Restic + settings *Settings } type BackupJob struct { - BackupId string `json:"backup_id"` - RepositoryId string `json:"repository_id"` - JobId uuid.UUID `json:"job_id"` -} - -type Settings struct { - Repositories []restic.Repository `json:"repositories"` - Backups []restic.Backup `json:"backups"` - Schedules []restic.Schedule `json:"schedules"` -} - -func settingsFile() string { - return xdg.ConfigHome + "/resticity.json" -} - -func GetSettings() Settings { - data := Settings{} - if file, err := os.Open(settingsFile()); err == nil { - if str, err := io.ReadAll(file); err == nil { - if err := json.Unmarshal([]byte(str), &data); err == nil { - return data - } - } - } else { - fmt.Println("error", err) - } - return data + JobId uuid.UUID `json:"job_id"` + Schedule Schedule `json:"schedule"` } // NewApp creates a new App application struct -func NewApp() *App { - return &App{} +func NewApp(restic *Restic, scheduler *Scheduler, settings *Settings) *App { + return &App{restic: restic, scheduler: scheduler, settings: settings} } func (a *App) systemTray() { @@ -92,26 +59,16 @@ func (a *App) systemTray() { func (a *App) startup(ctx context.Context) { a.ctx = ctx go systray.Run(a.systemTray, func() {}) - if s, err := gocron.NewScheduler(); err == nil { - go s.Start() - a.scheduler = s - a.RescheduleBackups() - } - -} -// Greet returns a greeting for the given name -func (a *App) Greet(name string) string { - return fmt.Sprintf("Hello %s, It's show time!", name) } func (a *App) GetBackupJobs() []BackupJob { - return a.runningJobs + return a.scheduler.RunningJobs } -func (a *App) Snapshots(id string) []restic.Snapshot { - s := GetSettings() - var r *restic.Repository +func (a *App) Snapshots(id string) []Snapshot { + s := a.settings.Config + var r *Repository for i := range s.Repositories { if s.Repositories[i].Id == id { r = &s.Repositories[i] @@ -119,132 +76,24 @@ func (a *App) Snapshots(id string) []restic.Snapshot { } fmt.Println(r) if r != nil { - return restic.Snapshots(*r) + return a.restic.Snapshots(*r) } - return []restic.Snapshot{} - -} - -func (a *App) Settings() Settings { - return GetSettings() -} + return []Snapshot{} -func (a *App) SaveSettings(data Settings) { - a.RescheduleBackups() - fmt.Println("Saving settings") - if str, err := json.MarshalIndent(data, " ", " "); err == nil { - fmt.Println("Settings saved") - if err := os.WriteFile(settingsFile(), str, 0644); err != nil { - fmt.Println("error", err) - } else { - a.RescheduleBackups() - } - } else { - fmt.Println("error", err) - } } func (a *App) StopBackup(id uuid.UUID) { - a.scheduler.RemoveJob(id) - a.RescheduleBackups() -} - -func (a *App) RescheduleBackups() { - s := GetSettings() - for _, b := range s.Backups { - - // todo: run missed backups - /* - - get last snapshot - - parse cron as date/duration whatever - - compare last snapshot date with cron date and job.NextRun() - */ - - for _, t := range b.Targets { - - jobName := "BACKUP-" + b.Id + "-TARGET-" + t - var job gocron.Job - for j := range a.scheduler.Jobs() { - if a.scheduler.Jobs()[j].Name() == jobName { - job = a.scheduler.Jobs()[j] - break - } - } - if job != nil { - a.scheduler.RemoveJob(job.ID()) - } - if b.Cron == "" { - continue - } - a.scheduler.NewJob( - gocron.CronJob(b.Cron, false), - gocron.NewTask(func(backup restic.Backup) { - // actual backup - log.Print("doing job", backup.Name) - time.Sleep(30 * time.Second) - }, b), - gocron.WithTags("backup:"+b.Id, "repository:"+t), - gocron.WithName(jobName), - gocron.WithEventListeners( - gocron.BeforeJobRuns(func(jobID uuid.UUID, jobName string) { - a.jmu.Lock() - defer a.jmu.Unlock() - a.runningJobs = append( - a.runningJobs, - BackupJob{ - BackupId: b.Id, - RepositoryId: t, - JobId: jobID, - }, - ) - }), - gocron.AfterJobRuns( - func(jobID uuid.UUID, jobName string) { - a.jmu.Lock() - defer a.jmu.Unlock() - for i := range a.runningJobs { - if a.runningJobs[i].BackupId == b.Id && - a.runningJobs[i].RepositoryId == t { - a.runningJobs = append( - a.runningJobs[:i], - a.runningJobs[i+1:]...) - break - } - } - // do something after the job completes - - }, - ), - gocron.AfterJobRunsWithError( - func(jobID uuid.UUID, jobName string, err error) { - a.jmu.Lock() - defer a.jmu.Unlock() - for i := range a.runningJobs { - if a.runningJobs[i].BackupId == b.Id && - a.runningJobs[i].RepositoryId == t { - a.runningJobs = append( - a.runningJobs[:i], - a.runningJobs[i+1:]...) - break - } - } - }, - ), - ), - ) - } - - } - + // a.scheduler.RemoveJob(id) + // a.RescheduleBackups() } -func (a *App) CheckRepository(r restic.Repository) string { +func (a *App) CheckRepository(r Repository) string { files, err := os.ReadDir(r.Path) if err != nil { return err.Error() } if len(files) > 0 { - if err := restic.Check(r); err != nil { + if err := a.restic.Check(r); err != nil { return err.Error() } else { return "REPO_OK_EXISTING" @@ -254,8 +103,8 @@ func (a *App) CheckRepository(r restic.Repository) string { return "REPO_OK_EMPTY" } -func (a *App) InitializeRepository(r restic.Repository) string { - if err := restic.Initialize(r); err != nil { +func (a *App) InitializeRepository(r Repository) string { + if err := a.restic.Initialize(r); err != nil { return err.Error() } diff --git a/frontend/app.config.ts b/frontend/app.config.ts index 5dfc651..ba0e3bd 100644 --- a/frontend/app.config.ts +++ b/frontend/app.config.ts @@ -1 +1,6 @@ -export default defineAppConfig({}) +export default defineAppConfig({ + ui: { + primary: 'sky', + gray: 'resticity', + }, +}) diff --git a/frontend/app.vue b/frontend/app.vue index dbb9b3d..b9842b5 100644 --- a/frontend/app.vue +++ b/frontend/app.vue @@ -9,6 +9,7 @@ diff --git a/frontend/components/Backup/ExcludeOptions.vue b/frontend/components/Backup/ExcludeOptions.vue index 5939e17..a497050 100644 --- a/frontend/components/Backup/ExcludeOptions.vue +++ b/frontend/components/Backup/ExcludeOptions.vue @@ -7,36 +7,31 @@

Files and Folders

Pattern for files and folders to exclude. One per line.

- +

File

Exclude items listed in specific files.

- +

Exclude if present

Excludes a folder if it contains any of these files.

- -
-
@@ -50,6 +45,13 @@ }, }) + const units = [ + { name: 'KiB', value: 'K' }, + { name: 'MiB', value: 'M' }, + { name: 'GiB', value: 'G' }, + { name: 'TiB', value: 'T' }, + ] + const filesAndFolders = ref(fromPropsArray('--exclude')) const ifPresent = ref(fromPropsArray('--exclude-if-present')) const listedInFiles = ref(fromPropsArray('--exclude-file')) diff --git a/frontend/components/HeaderNavBar.vue b/frontend/components/HeaderNavBar.vue index 5a8aaaf..512136a 100644 --- a/frontend/components/HeaderNavBar.vue +++ b/frontend/components/HeaderNavBar.vue @@ -5,16 +5,33 @@ diff --git a/frontend/components/Repository/List.vue b/frontend/components/Repository/List.vue index 3adce39..4247d34 100644 --- a/frontend/components/Repository/List.vue +++ b/frontend/components/Repository/List.vue @@ -22,6 +22,9 @@
Backup running
+
+ Sync running +
diff --git a/frontend/components/Repository/Snapshots.vue b/frontend/components/Repository/Snapshots.vue index 97c63b4..6daabcc 100644 --- a/frontend/components/Repository/Snapshots.vue +++ b/frontend/components/Repository/Snapshots.vue @@ -3,29 +3,25 @@

Snapshots

-
- - - - - - - - - - - - - - - -
IDHostTimePathsTags
- {{ snapshot.id.slice(0, 8) }} - {{ snapshot.hostname }}{{ format(new Date(snapshot.time), 'dd.MM.yyyy H:I:s') }} - {{ snapshot.paths.join(',') }} - - {{ tag }} -
+ Prune selected Snapshots + + + + + +
@@ -33,9 +29,72 @@ diff --git a/frontend/components/Schedule/New.vue b/frontend/components/Schedule/New.vue new file mode 100644 index 0000000..e8134c5 --- /dev/null +++ b/frontend/components/Schedule/New.vue @@ -0,0 +1,96 @@ + + + diff --git a/frontend/composables/useJobs.ts b/frontend/composables/useJobs.ts index 8e083f6..113b141 100644 --- a/frontend/composables/useJobs.ts +++ b/frontend/composables/useJobs.ts @@ -1,23 +1,26 @@ export const useJobs = defineStore('useJobs', () => { const running = ref>>([]) - function init() { - // const interval = setInterval(async () => { - // running.value = await GetBackupJobs() - // }, 300) + + function scheduleIsRunning(id: string) { + return running.value?.find((job: BackupJob) => job.schedule.id === id) ? true : false } function repoIsRunning(id: string) { - return running.value?.find((job: BackupJob) => job.repository_id === id) ? true : false + return running.value?.find((job: BackupJob) => job.schedule.to_repository_id === id) ? true : false + } + function repoIsSynching(id: string) { + return running.value?.find((job: BackupJob) => job.schedule.from_repository_id === id) ? true : false } function backupIsRunning(id: string) { - return running.value?.find((job: BackupJob) => job.backup_id === id) ? true : false + return running.value?.find((job: BackupJob) => job.schedule.backup_id === id) ? true : false } return { running, - init, + scheduleIsRunning, repoIsRunning, + repoIsSynching, backupIsRunning, } }) diff --git a/frontend/composables/useLogs.ts b/frontend/composables/useLogs.ts new file mode 100644 index 0000000..ce226a8 --- /dev/null +++ b/frontend/composables/useLogs.ts @@ -0,0 +1,9 @@ +export const useLogs = defineStore('useLogs', () => { + const out = ref('') + const err = ref('') + + return { + out, + err, + } +}) diff --git a/frontend/composables/useSettings.ts b/frontend/composables/useSettings.ts index fe711c9..665ee2e 100644 --- a/frontend/composables/useSettings.ts +++ b/frontend/composables/useSettings.ts @@ -1,11 +1,14 @@ export const useSettings = defineStore('useSettings', () => { - const settings = ref>>() + const settings = ref() async function init() { - settings.value = await Settings() + const res = await useFetch('http://127.0.0.1:11278/api/config', { method: 'GET' }) + settings.value = res.data.value + console.log(settings.value) } async function save() { console.log('SHOULD SAVE') - await SaveSettings(settings.value!) + // await SaveSettings(settings.value!) + await useFetch('http://localhost:11278/api/config', { method: 'POST', body: settings.value }) } return { settings, diff --git a/frontend/composables/useSocket.ts b/frontend/composables/useSocket.ts new file mode 100644 index 0000000..9397862 --- /dev/null +++ b/frontend/composables/useSocket.ts @@ -0,0 +1,21 @@ +export const useSocket = defineStore('useSocket', () => { + function init() { + const jobsocket = new WebSocket('ws://127.0.0.1:11278/api/ws') + jobsocket.onmessage = (event) => { + try { + const data = JSON.parse(event.data) + useJobs().running = data.jobs + useLogs().out = data.out + useLogs().err = data.err + console.log(data) + } catch (e) { + useJobs().running = [] + console.error(e) + } + } + } + + return { + init, + } +}) diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue index 643400f..1685e81 100644 --- a/frontend/layouts/default.vue +++ b/frontend/layouts/default.vue @@ -1,12 +1,8 @@ - - diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts index 7e351d7..b835bc8 100644 --- a/frontend/nuxt.config.ts +++ b/frontend/nuxt.config.ts @@ -1,7 +1,7 @@ // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ ssr: false, - modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'], + modules: ['@pinia/nuxt', '@nuxt/ui'], devtools: { enabled: true }, nitro: { static: true }, diff --git a/frontend/package.json b/frontend/package.json index 1b5de62..5a38577 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,8 @@ "@fortawesome/free-regular-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/vue-fontawesome": "^3.0.5", + "@iconify-json/fa6-regular": "^1.1.18", + "@iconify-json/logos": "^1.1.42", "@nuxt/ui": "^2.11.1", "@pinia/nuxt": "^0.5.1", "@tailwindcss/typography": "^0.5.10", diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 57b2c55..88d0b4f 100755 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -6664c4fa5c8c1cb055d6fd92793a84e1 \ No newline at end of file +2fc3fbd6f039d2821a09c9284844bbb2 \ No newline at end of file diff --git a/frontend/pages/logs.vue b/frontend/pages/logs.vue new file mode 100644 index 0000000..338768e --- /dev/null +++ b/frontend/pages/logs.vue @@ -0,0 +1,8 @@ + diff --git a/frontend/pages/schedules/index.vue b/frontend/pages/schedules/index.vue index 51a7a63..8d64d85 100644 --- a/frontend/pages/schedules/index.vue +++ b/frontend/pages/schedules/index.vue @@ -1,6 +1,3 @@ diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 4933684..103183a 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -20,6 +20,12 @@ dependencies: '@fortawesome/vue-fontawesome': specifier: ^3.0.5 version: 3.0.5(@fortawesome/fontawesome-svg-core@6.5.1)(vue@3.3.13) + '@iconify-json/fa6-regular': + specifier: ^1.1.18 + version: 1.1.18 + '@iconify-json/logos': + specifier: ^1.1.42 + version: 1.1.42 '@nuxt/ui': specifier: ^2.11.1 version: 2.11.1(nuxt@3.9.0)(vite@5.0.10)(vue@3.3.13) @@ -699,12 +705,24 @@ packages: vue: 3.3.13 dev: false + /@iconify-json/fa6-regular@1.1.18: + resolution: {integrity: sha512-+lLtiTHf02rxeC/9R6vzJi9eGcuubzeHfTt/HWvDnovz2Kt5NEntW8foUSLeLo7kPU7RNvea68lt7QM9HYFloQ==} + dependencies: + '@iconify/types': 2.0.0 + dev: false + /@iconify-json/heroicons@1.1.19: resolution: {integrity: sha512-uW2F9vdGll59W21ocBl+wR4Ve+/1CsmzBqPTuOaR3CbKzqnJKwzGASvC4Op0uTieFVWfBaevnzcRxwNo73J29g==} dependencies: '@iconify/types': 2.0.0 dev: false + /@iconify-json/logos@1.1.42: + resolution: {integrity: sha512-/f+frtPm3m3Z30oy8Pk+QqRDkbmAiIaWGPl5CmsCXm15MVfvw9a/V/gD7WzdyuSGAZcFuQaqbHXj92y/n+2ifg==} + dependencies: + '@iconify/types': 2.0.0 + dev: false + /@iconify/collections@1.0.376: resolution: {integrity: sha512-eqq+aR0n8GKkcL6VNqONn5p9FeXah0vcMeR4ZV+o3MqafAQUfdASawYzJEUm4jby9+aZp3vtv1c8ecWe6cyeRQ==} dependencies: diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index ba6fffd..f065e7d 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -3,15 +3,19 @@ module.exports = { theme: { extend: { colors: { - 'primary': '#cba6f7', - 'secondary': '#74c7ec', - 'accent': '#94e2d5', - 'neutral': '#313244', - 'base-100': '#1e1e2e', - 'info': '#74c7ec', - 'success': '#a6e3a1', - 'warning': '#f9e2af', - 'error': '#f38ba8', + resticity: { + 50: '#cdd6f4', + 100: '#bac2de', + 200: '#a6adc8', + 300: '#9399b2', + 400: '#7f849c', + 500: '#6c7086', + 600: '#585b70', + 700: '#45475a', + 800: '#313244', + 900: '#1e1e2e', + 950: '#11111b', + }, }, }, }, diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 4476f15..9a62497 100755 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -1,25 +1,16 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT -import {restic} from '../models'; import {main} from '../models'; import {uuid} from '../models'; -export function CheckRepository(arg1:restic.Repository):Promise; +export function CheckRepository(arg1:main.Repository):Promise; export function GetBackupJobs():Promise>; -export function Greet(arg1:string):Promise; - -export function InitializeRepository(arg1:restic.Repository):Promise; - -export function RescheduleBackups():Promise; - -export function SaveSettings(arg1:main.Settings):Promise; +export function InitializeRepository(arg1:main.Repository):Promise; export function SelectDirectory(arg1:string):Promise; -export function Settings():Promise; - -export function Snapshots(arg1:string):Promise>; +export function Snapshots(arg1:string):Promise>; export function StopBackup(arg1:uuid.UUID):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index f89e393..78827be 100755 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -10,30 +10,14 @@ export function GetBackupJobs() { return window['go']['main']['App']['GetBackupJobs'](); } -export function Greet(arg1) { - return window['go']['main']['App']['Greet'](arg1); -} - export function InitializeRepository(arg1) { return window['go']['main']['App']['InitializeRepository'](arg1); } -export function RescheduleBackups() { - return window['go']['main']['App']['RescheduleBackups'](); -} - -export function SaveSettings(arg1) { - return window['go']['main']['App']['SaveSettings'](arg1); -} - export function SelectDirectory(arg1) { return window['go']['main']['App']['SelectDirectory'](arg1); } -export function Settings() { - return window['go']['main']['App']['Settings'](); -} - export function Snapshots(arg1) { return window['go']['main']['App']['Snapshots'](arg1); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index b638c90..acce8c7 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -1,27 +1,15 @@ export namespace main { - export interface BackupJob { - backup_id: string; - repository_id: string; - job_id: number[]; - } - export interface Settings { - repositories: restic.Repository[]; - backups: restic.Backup[]; - schedules: restic.Schedule[]; - } - -} - -export namespace restic { - - export interface Backup { + export interface Schedule { id: string; - path: string; - name: string; + backup_id: string; + to_repository_id: string; + from_repository_id: string; cron: string; - backup_params: string[][]; - targets: string[]; + } + export interface BackupJob { + job_id: number[]; + schedule: Schedule; } export interface Options { b2_account_id: string; @@ -35,18 +23,13 @@ export namespace restic { id: string; name: string; type: number; - prune_params: Param[]; + prune_params: string[][]; path: string; password: string; // Go type: Options options: any; } - export interface Schedule { - backup_id: string; - to_repository_id: string; - from_repository_id: string; - cron: string; - } + export interface Snapshot { id: string; time: string; diff --git a/go.mod b/go.mod index cedf2df..dfa3f3a 100644 --- a/go.mod +++ b/go.mod @@ -9,39 +9,49 @@ require ( github.com/energye/systray v1.0.2 github.com/go-co-op/gocron/v2 v2.1.1 github.com/go-errors/errors v1.5.1 + github.com/goccy/go-json v0.10.2 + github.com/gofiber/contrib/websocket v1.3.0 + github.com/gofiber/fiber/v2 v2.51.0 github.com/google/uuid v1.5.0 github.com/labstack/gommon v0.4.0 github.com/wailsapp/wails/v2 v2.7.1 ) require ( + github.com/andybalholm/brotli v1.0.6 // indirect github.com/bep/debounce v1.2.1 // indirect + github.com/fasthttp/websocket v1.5.7 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect github.com/jonboulle/clockwork v0.4.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/labstack/echo/v4 v4.10.2 // indirect github.com/leaanthony/go-ansi-parser v1.6.0 // indirect github.com/leaanthony/gosod v1.0.3 // indirect github.com/leaanthony/slicer v1.6.0 // indirect github.com/leaanthony/u v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/samber/lo v1.38.1 // indirect + github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c // indirect github.com/tkrajina/go-reflector v0.5.6 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect github.com/wailsapp/go-webview2 v1.0.10 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect golang.org/x/crypto v0.16.0 // indirect golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect golang.org/x/net v0.19.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 47fb2a6..984820f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -7,21 +9,31 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/energye/systray v1.0.2 h1:63R4prQkANtpM2CIA4UrDCuwZFt+FiygG77JYCsNmXc= github.com/energye/systray v1.0.2/go.mod h1:sp7Q/q/I4/w5ebvpSuJVep71s9Bg7L9ZVp69gBASehM= +github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KHau4= +github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU= github.com/go-co-op/gocron/v2 v2.1.1 h1:vQPaVzCFUbfNTKjLYPCUiLlgE3mJ78XfYCo+CTfutHs= github.com/go-co-op/gocron/v2 v2.1.1/go.mod h1:0MfNAXEchzeSH1vtkZrTAcSMWqyL435kL6CA4b0bjrg= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofiber/contrib/websocket v1.3.0 h1:XADFAGorer1VJ1bqC4UkCjqS37kwRTV0415+050NrMk= +github.com/gofiber/contrib/websocket v1.3.0/go.mod h1:xguaOzn2ZZ759LavtosEP+rcxIgBEE/rdumPINhR+Xo= +github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ= +github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= @@ -44,8 +56,10 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -59,6 +73,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= @@ -69,9 +85,13 @@ github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQ github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/wailsapp/go-webview2 v1.0.10 h1:PP5Hug6pnQEAhfRzLCoOh2jJaPdrqeRgJKZhyYyDV/w= github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= @@ -99,8 +119,8 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= diff --git a/main.go b/main.go index 9443054..3d4b28d 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,12 @@ package main import ( + "bytes" "embed" + "fmt" "github.com/wailsapp/wails/v2" + "github.com/wailsapp/wails/v2/pkg/logger" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) @@ -12,13 +15,30 @@ import ( var assets embed.FS func main() { + var errb bytes.Buffer + var outb bytes.Buffer + restic := NewRestic(&errb, &outb) + settings := NewSettings() + if scheduler, err := NewScheduler(settings, restic); err == nil { + scheduler.RescheduleBackups() + go RunServer(scheduler, restic, settings, &errb, &outb) + Desktop(scheduler, restic, settings) + } else { + fmt.Println("SCHEDULER ERROR", err) + } + +} + +func Desktop(scheduler *Scheduler, restic *Restic, settings *Settings) { // Create an instance of the app structure - app := NewApp() + app := NewApp(restic, scheduler, settings) // Create application with options err := wails.Run(&options.App{ - Title: "resticity", - Width: 1024, - Height: 768, + Title: "resticity", + Width: 1024, + Height: 768, + HideWindowOnClose: true, + LogLevel: logger.ERROR, AssetServer: &assetserver.Options{ Assets: assets, }, diff --git a/restic/restic.go b/restic.go similarity index 52% rename from restic/restic.go rename to restic.go index d9b7fdf..da27c52 100644 --- a/restic/restic.go +++ b/restic.go @@ -1,15 +1,28 @@ -package restic +package main import ( "bytes" "encoding/json" "fmt" "os/exec" + "time" "github.com/go-errors/errors" "github.com/labstack/gommon/log" ) +type Restic struct { + errb *bytes.Buffer + outb *bytes.Buffer +} + +func NewRestic(outb *bytes.Buffer, errb *bytes.Buffer) *Restic { + r := &Restic{} + r.errb = errb + r.outb = outb + return r +} + type B2Options struct { B2AccountId string `json:"b2_account_id"` B2AccountKey string `json:"b2_account_key"` @@ -37,29 +50,6 @@ const ( GOOGLE RepositoryType = iota ) -type Param struct { - k string - v string -} - -type BackupParam struct { - Param -} - -type PruneParam struct { - Param -} - -type Repository struct { - Id string `json:"id"` - Name string `json:"name"` - Type RepositoryType `json:"type"` - PruneParams []Param `json:"prune_params"` - Path string `json:"path"` - Password string `json:"password"` - Options Options `json:"options"` -} - type Snapshot struct { Id string `json:"id"` Time string `json:"time"` @@ -73,34 +63,19 @@ type Snapshot struct { ProgramVersion string `json:"program_version"` } -type Backup struct { - Id string `json:"id"` - Path string `json:"path"` - Name string `json:"name"` - Cron string `json:"cron"` - BackupParams [][]string `json:"backup_params"` - Targets []string `json:"targets"` -} - -type Schedule struct { - BackupId string `json:"backup_id"` - ToRepositoryId string `json:"to_repository_id"` - FromRepositoryId string `json:"from_repository_id"` - Cron string `json:"cron"` -} +func (r *Restic) core(repository Repository, cmd ...string) (string, error) { -func core(r Repository, cmd ...string) (string, error) { - var errb bytes.Buffer - cmds := []string{"-r", r.Path, "--json"} + cmds := []string{"-r", repository.Path, "--json"} cmds = append(cmds, cmd...) c := exec.Command("/usr/bin/restic", cmds...) - c.Stderr = &errb - c.Env = append(c.Env, "RESTIC_PASSWORD="+r.Password) + c.Stderr = r.errb + c.Stdout = r.outb + c.Env = append(c.Env, "RESTIC_PASSWORD="+repository.Password) log.Print(c.Env) if out, err := c.Output(); err != nil { - fmt.Println(errb.String()) - return "", errors.New(errb.String()) + fmt.Println(r.errb.String()) + return "", errors.New(r.errb.String()) } else { fmt.Println(string(out)) return string(out), nil @@ -108,22 +83,22 @@ func core(r Repository, cmd ...string) (string, error) { } -func Check(r Repository) error { - if _, err := core(r, "check"); err != nil { +func (r *Restic) Check(repository Repository) error { + if _, err := r.core(repository, "check"); err != nil { return err } return nil } -func Initialize(r Repository) error { - if _, err := core(r, "init"); err != nil { +func (r *Restic) Initialize(repository Repository) error { + if _, err := r.core(repository, "init"); err != nil { return err } return nil } -func Snapshots(r Repository) []Snapshot { - if res, err := core(r, "snapshots"); err == nil { +func (r *Restic) Snapshots(repository Repository) []Snapshot { + if res, err := r.core(repository, "snapshots"); err == nil { var data []Snapshot if err := json.Unmarshal([]byte(res), &data); err == nil { return data @@ -132,3 +107,9 @@ func Snapshots(r Repository) []Snapshot { return []Snapshot{} } + +func (r *Restic) RunBackup(backup Backup, toRepository Repository, fromRepository Repository) { + fmt.Println("RUNNING BACKUP") + time.Sleep(30 * time.Second) + // r.Snapshots(toRepository) +} diff --git a/scheduler.go b/scheduler.go new file mode 100644 index 0000000..4d26657 --- /dev/null +++ b/scheduler.go @@ -0,0 +1,134 @@ +package main + +import ( + "fmt" + "sync" + + "github.com/go-co-op/gocron/v2" + "github.com/google/uuid" +) + +type Scheduler struct { + gocron gocron.Scheduler + restic *Restic + RunningJobs []BackupJob + jmu sync.Mutex + settings *Settings +} + +func NewScheduler(settings *Settings, restic *Restic) (*Scheduler, error) { + + s := &Scheduler{} + s.settings = settings + s.restic = restic + if scheduler, err := gocron.NewScheduler(); err == nil { + s.gocron = scheduler + s.gocron.Start() + return s, nil + } else { + return nil, err + } + +} + +func (s *Scheduler) DeleteBackgroundJob(jobID uuid.UUID) { + s.jmu.Lock() + defer s.jmu.Unlock() + for i := range s.RunningJobs { + if s.RunningJobs[i].JobId == jobID { + s.RunningJobs = append( + s.RunningJobs[:i], + s.RunningJobs[i+1:]...) + break + } + } +} + +func (s *Scheduler) GetRunningJobs() []BackupJob { + s.jmu.Lock() + defer s.jmu.Unlock() + return s.RunningJobs +} + +func (s *Scheduler) RescheduleBackups() { + fmt.Println("Rescheduling backups") + config := s.settings.Config + + for i := range config.Schedules { + schedule := config.Schedules[i] + fmt.Println("SCHEDULE", s) + var job gocron.Job + for j := range s.gocron.Jobs() { + if s.gocron.Jobs()[j].Name() == schedule.Id { + job = s.gocron.Jobs()[j] + } + } + if job != nil { + fmt.Println("Found job") + s.gocron.RemoveJob(job.ID()) + } + if schedule.Cron == "" { + continue + } + + _, err := s.gocron.NewJob( + gocron.CronJob(schedule.Cron, false), + gocron.NewTask(func() { + var toRepository Repository + var fromRepository Repository + var backup Backup + for _, r := range s.settings.Config.Repositories { + if r.Id == schedule.ToRepositoryId { + toRepository = r + } + if r.Id == schedule.FromRepositoryId { + fromRepository = r + } + } + for _, b := range s.settings.Config.Backups { + if b.Id == schedule.BackupId { + backup = b + break + } + + } + s.restic.RunBackup(backup, toRepository, fromRepository) + }), + gocron.WithName(schedule.Id), + gocron.WithTags( + "backup:"+schedule.BackupId, + "repository:"+schedule.ToRepositoryId, + "fromrepository:"+schedule.ToRepositoryId, + ), + gocron.WithEventListeners( + gocron.BeforeJobRuns(func(jobID uuid.UUID, jobName string) { + s.jmu.Lock() + defer s.jmu.Unlock() + s.RunningJobs = append( + s.RunningJobs, + BackupJob{ + JobId: jobID, + Schedule: schedule, + }, + ) + fmt.Println("BEFORE JOB RUNS", len(s.RunningJobs)) + }), + gocron.AfterJobRuns( + func(jobID uuid.UUID, jobName string) { + s.DeleteBackgroundJob(jobID) + + }, + ), + gocron.AfterJobRunsWithError( + func(jobID uuid.UUID, jobName string, err error) { + s.DeleteBackgroundJob(jobID) + }, + ), + )) + + if err != nil { + fmt.Println("ERROR", err) + } + } + +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..2942e30 --- /dev/null +++ b/server.go @@ -0,0 +1,88 @@ +package main + +import ( + "bytes" + "fmt" + "log" + "time" + + "github.com/goccy/go-json" + "github.com/gofiber/contrib/websocket" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" +) + +func RunServer( + scheduler *Scheduler, + restic *Restic, + settings *Settings, + errb *bytes.Buffer, + outb *bytes.Buffer, +) { + server := fiber.New() + server.Use(cors.New()) + + api := server.Group("/api") + + api.Use("/ws", func(c *fiber.Ctx) error { + // IsWebSocketUpgrade returns true if the client + // requested upgrade to the WebSocket protocol. + if websocket.IsWebSocketUpgrade(c) { + c.Locals("allowed", true) + return c.Next() + } + return fiber.ErrUpgradeRequired + }) + + api.Get("/ws", websocket.New(func(c *websocket.Conn) { + // c.Locals is added to the *websocket.Conn + + // websocket.Conn bindings https://pkg.go.dev/github.com/fasthttp/websocket?tab=doc#pkg-index + + for { + jobs := scheduler.GetRunningJobs() + data := make(map[string]any) + data["jobs"] = jobs + data["out"] = outb.String() + data["err"] = errb.String() + fmt.Println(data) + if d, err := json.Marshal(data); err == nil { + if err = c.WriteMessage(websocket.TextMessage, d); err != nil { + log.Println("Error writing to socket:", err) + } + } else { + log.Println("Error marshalling data:", err) + } + time.Sleep(1 * time.Second) + } + + })) + + config := api.Group("/config") + repositories := api.Group("/repositories") + backups := api.Group("/backups") + config.Get("/", func(c *fiber.Ctx) error { + return c.JSON(settings.Config) + }) + config.Post("/", func(c *fiber.Ctx) error { + + s := new(Config) + if err := c.BodyParser(s); err != nil { + c.SendStatus(500) + return c.SendString(err.Error()) + } + settings.Save(*s) + scheduler.RescheduleBackups() + return c.SendString("OK") + }) + + repositories.Get("/", func(c *fiber.Ctx) error { + return c.SendString("Hello, World!") + }) + + backups.Get("/", func(c *fiber.Ctx) error { + return c.SendString("Hello, World!") + }) + + server.Listen(":11278") +} diff --git a/settings.go b/settings.go new file mode 100644 index 0000000..2155d54 --- /dev/null +++ b/settings.go @@ -0,0 +1,87 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/adrg/xdg" +) + +type Settings struct { + Config Config +} + +type Repository struct { + Id string `json:"id"` + Name string `json:"name"` + Type RepositoryType `json:"type"` + PruneParams [][]string `json:"prune_params"` + Path string `json:"path"` + Password string `json:"password"` + Options Options `json:"options"` +} + +type Backup struct { + Id string `json:"id"` + Path string `json:"path"` + Name string `json:"name"` + Cron string `json:"cron"` + BackupParams [][]string `json:"backup_params"` + Targets []string `json:"targets"` +} + +type Schedule struct { + Id string `json:"id"` + BackupId string `json:"backup_id"` + ToRepositoryId string `json:"to_repository_id"` + FromRepositoryId string `json:"from_repository_id"` + Cron string `json:"cron"` +} + +type Config struct { + Repositories []Repository `json:"repositories"` + Backups []Backup `json:"backups"` + Schedules []Schedule `json:"schedules"` +} + +func NewSettings() *Settings { + s := &Settings{} + s.Config = s.readFile() + return s +} + +func (s *Settings) readFile() Config { + data := Config{} + if file, err := os.Open(settingsFile()); err == nil { + if str, err := io.ReadAll(file); err == nil { + if err := json.Unmarshal([]byte(str), &data); err == nil { + return data + } + } + } else { + fmt.Println("error", err) + } + return data +} + +func settingsFile() string { + return xdg.ConfigHome + "/resticity.json" +} + +func (s *Settings) Save(data Config) error { + s.Config = data + fmt.Println("Saving settings") + if str, err := json.MarshalIndent(s.Config, " ", " "); err == nil { + fmt.Println("Settings saved") + if err := os.WriteFile(settingsFile(), str, 0644); err != nil { + fmt.Println("error", err) + return err + } + } else { + fmt.Println("error", err) + return err + } + return nil +}