inital code for snapshoting on cron
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
build:
|
||||
go build .
|
||||
|
||||
package: build
|
||||
mkdir -p snapz-1
|
||||
cp snapzd snapz-1
|
||||
cp systemd/snapzd.service snapz-1/snapzd.service
|
||||
tar -czvf snapzd.tar.gz snapz-1
|
||||
|
||||
makepkg: package
|
||||
makepkg -f --skipinteg
|
||||
|
||||
pacinstall: makepkg
|
||||
sudo pacman -U snapz-1-1-x86_64.pkg.tar.zst
|
||||
|
||||
clean:
|
||||
rm snapzd
|
||||
rm -r snapz-1
|
||||
rm snapzd.tar.gz
|
||||
rm snapz-1-1-x86_64.pkg.tar.zst
|
||||
rm -r src
|
||||
rm -r pkg
|
||||
@@ -0,0 +1,52 @@
|
||||
# This is an example PKGBUILD file. Use this as a start to creating your own,
|
||||
# and remove these comments. For more information, see 'man PKGBUILD'.
|
||||
# NOTE: Please fill out the license field for your package! If it is unknown,
|
||||
# then please put 'unknown'.
|
||||
|
||||
# Maintainer: Your Name <youremail@domain.com>
|
||||
pkgname=snapz
|
||||
pkgver=1
|
||||
pkgrel=1
|
||||
epoch=
|
||||
pkgdesc=""
|
||||
arch=('x86_64')
|
||||
url=""
|
||||
license=('GPL')
|
||||
groups=()
|
||||
depends=()
|
||||
makedepends=()
|
||||
checkdepends=()
|
||||
optdepends=()
|
||||
provides=()
|
||||
conflicts=()
|
||||
replaces=()
|
||||
backup=()
|
||||
options=()
|
||||
install=
|
||||
changelog=
|
||||
source=("file://snapzd.tar.gz")
|
||||
noextract=()
|
||||
md5sums=()
|
||||
validpgpkeys=()
|
||||
|
||||
prepare() {
|
||||
echo "$PWD"
|
||||
}
|
||||
|
||||
build() {
|
||||
echo "foo"
|
||||
}
|
||||
|
||||
check() {
|
||||
cd "$pkgname-$pkgver"
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$pkgname-$pkgver"
|
||||
mkdir -p "$pkgdir/usr/bin"
|
||||
mkdir -p "$pkgdir/etc/snapz/"
|
||||
cp snapzd "$pkgdir/usr/bin"
|
||||
|
||||
mkdir -p "$pkgdir/usr/lib/systemd/system/"
|
||||
cp snapzd.service "$pkgdir/usr/lib/systemd/system/"
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
SNAPZ_CONFIG_ROOT = "/etc/snapz"
|
||||
SNAPZ_JOBS_FILE = "jobs.toml"
|
||||
SNAPZ_DATASET_DIRECTORY = "datasets.toml"
|
||||
)
|
||||
|
||||
/*
|
||||
[[job]]
|
||||
name = "example"
|
||||
description = "example job"
|
||||
cron = "* /1 * * * *"
|
||||
recursive = true
|
||||
*/
|
||||
|
||||
type JobConfig struct {
|
||||
Name string `toml:"name"`
|
||||
Description string `toml:"description"`
|
||||
Cron string `toml:"cron"`
|
||||
Dataset string `toml:"dataset"`
|
||||
Recursive bool `toml:"recusrive"`
|
||||
}
|
||||
|
||||
type JobsConfig struct {
|
||||
Jobs []JobConfig `toml:"job"`
|
||||
}
|
||||
|
||||
func getConfigPaths(filename string) []string {
|
||||
global_file_path := filepath.Join(SNAPZ_CONFIG_ROOT, filename)
|
||||
return []string{global_file_path}
|
||||
}
|
||||
|
||||
func GetJobs() ([]JobConfig, error) {
|
||||
job_file_paths := getConfigPaths(SNAPZ_JOBS_FILE)
|
||||
jobs := make([]JobConfig, 0)
|
||||
for _, file := range job_file_paths {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jobsConfig JobsConfig
|
||||
err = toml.Unmarshal(data, &jobsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zap.S().Infof("adding %v jobs to queue", len(jobsConfig.Jobs))
|
||||
jobs = append(jobs, jobsConfig.Jobs...)
|
||||
}
|
||||
|
||||
return jobs, nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/go-co-op/gocron"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
||||
|
||||
type Schedule struct {
|
||||
Every int
|
||||
Unit string
|
||||
At []string
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (s Schedule) ToGocron() *gocron.Scheduler {
|
||||
out := gocron.NewScheduler(time.Local)
|
||||
out = out.Every(s.Every)
|
||||
|
||||
switch s.Unit {
|
||||
case "Day":
|
||||
out = out.Day()
|
||||
case "Hour":
|
||||
out = out.Hour()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, at := range s.At {
|
||||
out = out.At(at)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
type SnapJob struct {
|
||||
Name string `yaml:"name"`
|
||||
DatasetPath string `yaml:"dataset_path"`
|
||||
Description string `yaml:"descriptions"`
|
||||
Recursive bool `yaml:"recursive"`
|
||||
Schedules []Schedule `yaml:"schedules"`
|
||||
Cron string `yaml:"cron"`
|
||||
}
|
||||
|
||||
func LoadSnap(path string) (*SnapJob, error) {
|
||||
configFile, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var jobInfo SnapJob
|
||||
err = yaml.Unmarshal(configFile, &jobInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &jobInfo, nil
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
name: "SnapshotUserdata"
|
||||
description: "Daily snapshot of userdata/home for backup to network storage"
|
||||
dataset_path: "userdata/home"
|
||||
recursive: true
|
||||
cron: "*/1 * * * *"
|
||||
# Makes a snapshot once a day at 8:00PM local time named autosnap@<RFC3339>
|
||||
@@ -0,0 +1,16 @@
|
||||
module git.ohea.xyz/spaceman/snapzd
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/go-co-op/gocron v1.18.0
|
||||
|
||||
require (
|
||||
github.com/mistifyio/go-zfs v2.1.1+incompatible // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
git.ohea.xyz/golang/config v0.0.0-20221002005232-8a901413a8b0 h1:a8ygEuzmqFDxXmf+e1IseDKBcAtkaIwfL3k4PIVVVr8=
|
||||
git.ohea.xyz/golang/config v0.0.0-20221002005232-8a901413a8b0/go.mod h1:86PbXJ2WdqQ+3hYqrnv3ukgKNRK9nQfThnlY03FAO0g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-co-op/gocron v1.18.0 h1:SxTyJ5xnSN4byCq7b10LmmszFdxQlSQJod8s3gbnXxA=
|
||||
github.com/go-co-op/gocron v1.18.0/go.mod h1:sD/a0Aadtw5CpflUJ/lpP9Vfdk979Wl1Sg33HPHg0FY=
|
||||
github.com/mistifyio/go-zfs v2.1.1+incompatible h1:gAMO1HM9xBRONLHHYnu5iFsOJUiJdNZo6oqSENd4eW8=
|
||||
github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -0,0 +1,7 @@
|
||||
[[job]]
|
||||
name = "dailysnap"
|
||||
description = "snapshot home dir daily"
|
||||
cron = "* * * * *"
|
||||
dataset = "userdata/home"
|
||||
rescursive = false
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.ohea.xyz/spaceman/snapzd/configuration"
|
||||
"github.com/go-co-op/gocron"
|
||||
"github.com/mistifyio/go-zfs"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func CreateSnapshotJob(job_info *configuration.JobConfig) func() {
|
||||
return func() {
|
||||
fs, err := zfs.Filesystems(job_info.Dataset)
|
||||
if err != nil {
|
||||
zap.S().Errorf("failed to start job %v: %v", job_info.Name, err)
|
||||
return
|
||||
}
|
||||
if len(fs) != 1 {
|
||||
zap.S().Errorf("dataset path returned %v datasets. Expected 1", len(fs))
|
||||
return
|
||||
}
|
||||
to_snapshot := fs[0]
|
||||
snaptime := time.Now()
|
||||
snapname := fmt.Sprintf("snapz.%s.%s", job_info.Name, snaptime.Format(time.RFC3339))
|
||||
|
||||
|
||||
snapshot, err := to_snapshot.Snapshot(snapname, job_info.Recursive)
|
||||
if err != nil {
|
||||
zap.S().Errorf("failed to create snapshot: %v: ", snapname, err)
|
||||
return
|
||||
}
|
||||
zap.S().Infof("created snapshot: %v", snapshot.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func configureLogging() error {
|
||||
logger, err := zap.NewDevelopment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
zap.ReplaceGlobals(logger)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := configureLogging()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to initalize logging, can't continue: %v", err)
|
||||
return
|
||||
}
|
||||
zap.S().Info("begining conifg scan")
|
||||
jobs, err := configuration.GetJobs()
|
||||
if err != nil {
|
||||
zap.S().Fatalf("failed to read configuration: %v", err)
|
||||
}
|
||||
|
||||
scheduler := gocron.NewScheduler(time.Local)
|
||||
|
||||
for _, job := range jobs {
|
||||
zap.S().Infof("discovered job: %v", job.Name)
|
||||
zap.S().Infof("adding job. Name: %v, Dataset: %v, Cron: %v", job.Name, job.Dataset, job.Cron)
|
||||
scheduler.Cron(job.Cron).SingletonMode().Tag(job.Name).Do(CreateSnapshotJob(&job))
|
||||
}
|
||||
|
||||
scheduler.StartBlocking()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=snapz snapshot daemon
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/snapzd
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user