update
This commit is contained in:
parent
6c3cd690de
commit
9eb8cee7d8
576
app.go
Normal file
576
app.go
Normal file
@ -0,0 +1,576 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AppConfig struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Schedule []string `json:"schedule"`
|
||||||
|
Sources []Location `json:"src"`
|
||||||
|
Destinations []Location `json:"dest"`
|
||||||
|
Before map[string]Location `json:"before"`
|
||||||
|
After map[string]Location `json:"after"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AppConfig) getTime() time.Time {
|
||||||
|
for _, v := range a.Sources {
|
||||||
|
return cfg.Box[v.Box()].ssh.now
|
||||||
|
}
|
||||||
|
for _, v := range a.Destinations {
|
||||||
|
return cfg.Box[v.Box()].ssh.now
|
||||||
|
}
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AppConfig) getSchedule() (string, error) {
|
||||||
|
var schedule string
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.getSchedule : %s : Start", a.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshSnapshot := make(map[string]bool)
|
||||||
|
for _, v := range a.Sources {
|
||||||
|
refreshSnapshot[v.Box()] = true
|
||||||
|
}
|
||||||
|
for k, _ := range refreshSnapshot {
|
||||||
|
err := cfg.Box[k].ssh.getSnapshotList()
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.getSchedule : %s : getSnapshotList(%s) : %s", a.Name, k, err)
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *schedFlag != "" {
|
||||||
|
schedule = *schedFlag
|
||||||
|
} else if a.needYearlySnapshot() {
|
||||||
|
schedule = "yearly"
|
||||||
|
} else if a.needMonthlySnapshot() {
|
||||||
|
schedule = "monthly"
|
||||||
|
} else if a.needWeeklySnapshot() {
|
||||||
|
schedule = "weekly"
|
||||||
|
} else if a.needDailySnapshot() {
|
||||||
|
schedule = "daily"
|
||||||
|
} else if a.needHourlySnapshot() {
|
||||||
|
schedule = "hourly"
|
||||||
|
} else {
|
||||||
|
return schedule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret, ok := cfg.Zfsnap[schedule]; !ok {
|
||||||
|
return "", fmt.Errorf("no retention for %s", schedule)
|
||||||
|
} else {
|
||||||
|
re := regexp.MustCompile(`^([0-9]+[ymwdhMs]{1}|forever)$`)
|
||||||
|
if !re.MatchString(ret) {
|
||||||
|
return "", fmt.Errorf("wrong retention format for %s", schedule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return schedule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AppConfig) needYearlySnapshot() bool {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.needYearlySnapshot : %s : Start", a.Name)
|
||||||
|
}
|
||||||
|
ret := false
|
||||||
|
|
||||||
|
// schedule enabled for app ?
|
||||||
|
for _, v := range a.Schedule {
|
||||||
|
if v == "yearly" {
|
||||||
|
ret = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ret {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// finding out the timestamps existing
|
||||||
|
timeSource := make(map[string]map[time.Time]struct{})
|
||||||
|
timeTotal := make(map[time.Time]struct{})
|
||||||
|
re := regexp.MustCompile(`^yearly-(?P<Date>[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}.[0-9]{2}.[0-9]{2})--([0-9]+[ymwdhMs]{1}|forever)$`)
|
||||||
|
for _, src := range a.Sources {
|
||||||
|
timeSource[string(src)] = make(map[time.Time]struct{})
|
||||||
|
for _, snap := range cfg.Box[src.Box()].ssh.snapshot {
|
||||||
|
if src.Path() == snap.Path() {
|
||||||
|
if re.MatchString(snap.Name()) {
|
||||||
|
dateString := re.ReplaceAllString(snap.Name(), "${Date}")
|
||||||
|
dateTime, err := time.Parse("2006-01-02_15.04.05", dateString)
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.needYearlySnapshot : %s : time.Parse(%s) : %s", a.Name, dateString, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timeSource[string(src)][dateTime] = struct{}{}
|
||||||
|
timeTotal[dateTime] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleaning up the available timestamps for common timestamps
|
||||||
|
for t, _ := range timeTotal {
|
||||||
|
for _, v := range timeSource {
|
||||||
|
if _, ok := v[t]; !ok {
|
||||||
|
delete(timeTotal, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finding an eligible timestamp
|
||||||
|
now := a.getTime()
|
||||||
|
for t, _ := range timeTotal {
|
||||||
|
if t.Year() == now.Year() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no timestamp => need the snapshot !
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AppConfig) needMonthlySnapshot() bool {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.needMonthlySnapshot : %s : Start", a.Name)
|
||||||
|
}
|
||||||
|
ret := false
|
||||||
|
|
||||||
|
// schedule enabled for app ?
|
||||||
|
for _, v := range a.Schedule {
|
||||||
|
if v == "monthly" {
|
||||||
|
ret = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ret {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// finding out the timestamps existing
|
||||||
|
timeSource := make(map[string]map[time.Time]struct{})
|
||||||
|
timeTotal := make(map[time.Time]struct{})
|
||||||
|
re := regexp.MustCompile(`^(yearly|monthly)-(?P<Date>[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}.[0-9]{2}.[0-9]{2})--([0-9]+[ymwdhMs]{1}|forever)$`)
|
||||||
|
for _, src := range a.Sources {
|
||||||
|
timeSource[string(src)] = make(map[time.Time]struct{})
|
||||||
|
for _, snap := range cfg.Box[src.Box()].ssh.snapshot {
|
||||||
|
if src.Path() == snap.Path() {
|
||||||
|
if re.MatchString(snap.Name()) {
|
||||||
|
dateString := re.ReplaceAllString(snap.Name(), "${Date}")
|
||||||
|
dateTime, err := time.Parse("2006-01-02_15.04.05", dateString)
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.needYearlySnapshot : %s : time.Parse(%s) : %s", a.Name, dateString, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timeSource[string(src)][dateTime] = struct{}{}
|
||||||
|
timeTotal[dateTime] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleaning up the available timestamps for common timestamps
|
||||||
|
for t, _ := range timeTotal {
|
||||||
|
for _, v := range timeSource {
|
||||||
|
if _, ok := v[t]; !ok {
|
||||||
|
delete(timeTotal, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finding an eligible timestamp
|
||||||
|
now := a.getTime()
|
||||||
|
for t, _ := range timeTotal {
|
||||||
|
if t.Year() == now.Year() && t.Month() == now.Month() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no timestamp => need the snapshot !
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AppConfig) needWeeklySnapshot() bool {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.needWeeklySnapshot : %s : Start", a.Name)
|
||||||
|
}
|
||||||
|
ret := false
|
||||||
|
|
||||||
|
// schedule enabled for app ?
|
||||||
|
for _, v := range a.Schedule {
|
||||||
|
if v == "weekly" {
|
||||||
|
ret = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ret {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// finding out the timestamps existing
|
||||||
|
timeSource := make(map[string]map[time.Time]struct{})
|
||||||
|
timeTotal := make(map[time.Time]struct{})
|
||||||
|
re := regexp.MustCompile(`^(yearly|monthly|weekly)-(?P<Date>[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}.[0-9]{2}.[0-9]{2})--([0-9]+[ymwdhMs]{1}|forever)$`)
|
||||||
|
for _, src := range a.Sources {
|
||||||
|
timeSource[string(src)] = make(map[time.Time]struct{})
|
||||||
|
for _, snap := range cfg.Box[src.Box()].ssh.snapshot {
|
||||||
|
if src.Path() == snap.Path() {
|
||||||
|
if re.MatchString(snap.Name()) {
|
||||||
|
dateString := re.ReplaceAllString(snap.Name(), "${Date}")
|
||||||
|
dateTime, err := time.Parse("2006-01-02_15.04.05", dateString)
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.needYearlySnapshot : %s : time.Parse(%s) : %s", a.Name, dateString, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timeSource[string(src)][dateTime] = struct{}{}
|
||||||
|
timeTotal[dateTime] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleaning up the available timestamps for common timestamps
|
||||||
|
for t, _ := range timeTotal {
|
||||||
|
for _, v := range timeSource {
|
||||||
|
if _, ok := v[t]; !ok {
|
||||||
|
delete(timeTotal, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finding an eligible timestamp
|
||||||
|
now := a.getTime()
|
||||||
|
nowYear, nowWeek := now.ISOWeek()
|
||||||
|
for t, _ := range timeTotal {
|
||||||
|
snapYear, snapWeek := t.ISOWeek()
|
||||||
|
if nowYear == snapYear && nowWeek == snapWeek {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no timestamp => need the snapshot !
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AppConfig) needDailySnapshot() bool {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.needDailySnapshot : %s : Start", a.Name)
|
||||||
|
}
|
||||||
|
ret := false
|
||||||
|
|
||||||
|
// schedule enabled for app ?
|
||||||
|
for _, v := range a.Schedule {
|
||||||
|
if v == "daily" {
|
||||||
|
ret = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ret {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// finding out the timestamps existing
|
||||||
|
timeSource := make(map[string]map[time.Time]struct{})
|
||||||
|
timeTotal := make(map[time.Time]struct{})
|
||||||
|
re := regexp.MustCompile(`^(yearly|monthly|weekly|daily)-(?P<Date>[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}.[0-9]{2}.[0-9]{2})--([0-9]+[ymwdhMs]{1}|forever)$`)
|
||||||
|
for _, src := range a.Sources {
|
||||||
|
timeSource[string(src)] = make(map[time.Time]struct{})
|
||||||
|
for _, snap := range cfg.Box[src.Box()].ssh.snapshot {
|
||||||
|
if src.Path() == snap.Path() {
|
||||||
|
if re.MatchString(snap.Name()) {
|
||||||
|
dateString := re.ReplaceAllString(snap.Name(), "${Date}")
|
||||||
|
dateTime, err := time.Parse("2006-01-02_15.04.05", dateString)
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.needYearlySnapshot : %s : time.Parse(%s) : %s", a.Name, dateString, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timeSource[string(src)][dateTime] = struct{}{}
|
||||||
|
timeTotal[dateTime] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleaning up the available timestamps for common timestamps
|
||||||
|
for t, _ := range timeTotal {
|
||||||
|
for _, v := range timeSource {
|
||||||
|
if _, ok := v[t]; !ok {
|
||||||
|
delete(timeTotal, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finding an eligible timestamp
|
||||||
|
now := a.getTime()
|
||||||
|
for t, _ := range timeTotal {
|
||||||
|
if t.Year() == now.Year() && t.Month() == now.Month() && t.Day() == now.Day() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no timestamp => need the snapshot !
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AppConfig) needHourlySnapshot() bool {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.needHourlySnapshot : %s : Start", a.Name)
|
||||||
|
}
|
||||||
|
ret := false
|
||||||
|
|
||||||
|
// schedule enabled for app ?
|
||||||
|
for _, v := range a.Schedule {
|
||||||
|
if v == "hourly" {
|
||||||
|
ret = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ret {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// finding out the timestamps existing
|
||||||
|
timeSource := make(map[string]map[time.Time]struct{})
|
||||||
|
timeTotal := make(map[time.Time]struct{})
|
||||||
|
re := regexp.MustCompile(`^(yearly|monthly|weekly|daily|hourly)-(?P<Date>[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}.[0-9]{2}.[0-9]{2})--([0-9]+[ymwdhMs]{1}|forever)$`)
|
||||||
|
for _, src := range a.Sources {
|
||||||
|
timeSource[string(src)] = make(map[time.Time]struct{})
|
||||||
|
for _, snap := range cfg.Box[src.Box()].ssh.snapshot {
|
||||||
|
if src.Path() == snap.Path() {
|
||||||
|
if re.MatchString(snap.Name()) {
|
||||||
|
dateString := re.ReplaceAllString(snap.Name(), "${Date}")
|
||||||
|
dateTime, err := time.Parse("2006-01-02_15.04.05", dateString)
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.needYearlySnapshot : %s : time.Parse(%s) : %s", a.Name, dateString, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timeSource[string(src)][dateTime] = struct{}{}
|
||||||
|
timeTotal[dateTime] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleaning up the available timestamps for common timestamps
|
||||||
|
for t, _ := range timeTotal {
|
||||||
|
for _, v := range timeSource {
|
||||||
|
if _, ok := v[t]; !ok {
|
||||||
|
delete(timeTotal, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finding an eligible timestamp
|
||||||
|
now := a.getTime()
|
||||||
|
for t, _ := range timeTotal {
|
||||||
|
if t.Year() == now.Year() && t.Month() == now.Month() && t.Day() == now.Day() && t.Hour() == now.Hour() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no timestamp => need the snapshot !
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AppConfig) RunAppBackup() error {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Start", a.Name)
|
||||||
|
}
|
||||||
|
schedule, err := a.getSchedule()
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Error getting schedule : %s", a.Name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if schedule == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, src := range a.Sources {
|
||||||
|
if !cfg.Box[src.Box()].ssh.isZFS(src.Path()) {
|
||||||
|
return fmt.Errorf("No path %s on source", string(src))
|
||||||
|
}
|
||||||
|
for _, dest := range a.Destinations {
|
||||||
|
if !cfg.Box[dest.Box()].ssh.isZFS(dest.Path() + "/" + src.Box() + "/" + src.Path()) {
|
||||||
|
err := cfg.Box[dest.Box()].ssh.createZFS(dest.Path() + "/" + src.Box() + "/" + src.Path())
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Error creating %s on %s", a.Name, dest.Path()+"/"+src.Box()+"/"+src.Path(), dest.Box())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range a.Before {
|
||||||
|
re := regexp.MustCompile(k)
|
||||||
|
if re.MatchString(schedule) {
|
||||||
|
err := cfg.Box[v.Box()].ssh.exec(v.Path())
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Error executing %s", a.Name, string(v))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshSnapshot := make(map[string]bool)
|
||||||
|
takeSnapshot := make(map[string]string)
|
||||||
|
delSnapshot := make(map[string]string)
|
||||||
|
for _, v := range a.Sources {
|
||||||
|
takeSnapshot[v.Box()] = takeSnapshot[v.Box()] + " " + v.Path()
|
||||||
|
refreshSnapshot[v.Box()] = true
|
||||||
|
for _, v2 := range a.Destinations {
|
||||||
|
delSnapshot[v2.Box()] = delSnapshot[v2.Box()] + " " + v2.Path() + "/" + v.Box() + "/" + v.Path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range takeSnapshot {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : taking snapshot on %s for %s", a.Name, k, v)
|
||||||
|
}
|
||||||
|
err := cfg.Box[k].ssh.exec("/usr/sbin/zfsnap snapshot -p " + schedule + "- -a " + cfg.Zfsnap[schedule] + v)
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Error executing zfsnap on %s", a.Name, k)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range a.Destinations {
|
||||||
|
refreshSnapshot[v.Box()] = true
|
||||||
|
}
|
||||||
|
for k, _ := range refreshSnapshot {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : refreshing snapshots for source %s", a.Name, k)
|
||||||
|
}
|
||||||
|
err := cfg.Box[k].ssh.getSnapshotList()
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Error getting snapshots on %s", a.Name, k)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, src := range a.Sources {
|
||||||
|
for _, dest := range a.Destinations {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Sending snapshots from %s to %s", a.Name, string(src), string(dest))
|
||||||
|
}
|
||||||
|
dLastSnapshot, err := cfg.Box[dest.Box()].ssh.getLastSnapshot(dest.Path() + "/" + src.Box() + "/" + src.Path())
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : No snapshot for %s on %s", a.Name, string(src), dest.Box())
|
||||||
|
}
|
||||||
|
sFirstSnapshot, err := cfg.Box[src.Box()].ssh.getFirstSnapshot(src.Path())
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : No snapshot for %s", a.Name, string(src))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Initializing snapshot on %s from %s", a.Name, dest.Box(), string(sFirstSnapshot))
|
||||||
|
}
|
||||||
|
err = cfg.Box[dest.Box()].ssh.exec("/usr/bin/ssh root@" + src.Box() + " /sbin/zfs send " + string(sFirstSnapshot) + " | /sbin/zfs recv -F " + dest.Path() + "/" + src.Box() + "/" + src.Path())
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Initializing snapshot on %s from %s failed (%s)", a.Name, dest.Box(), string(sFirstSnapshot), err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var sCurrSnapshot Snapshot
|
||||||
|
sNextSnapshot := sFirstSnapshot
|
||||||
|
for !cfg.Box[src.Box()].ssh.isLastSnapshot(sNextSnapshot) {
|
||||||
|
sCurrSnapshot = sNextSnapshot
|
||||||
|
sNextSnapshot, err = cfg.Box[src.Box()].ssh.getNextSnapshot(sNextSnapshot)
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Sending incrementally %s to %s", a.Name, string(sNextSnapshot), dest.Box())
|
||||||
|
}
|
||||||
|
err = cfg.Box[dest.Box()].ssh.exec("/usr/bin/ssh root@" + src.Box() + " /sbin/zfs send -I " + string(sCurrSnapshot) + " " + string(sNextSnapshot) + " | /sbin/zfs recv " + dest.Path() + "/" + src.Box() + "/" + src.Path())
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Sending snapshot on %s from %s failed (%s)", a.Name, dest.Box(), string(sNextSnapshot), err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : All snapshots sent for %s", a.Name, string(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Last snapshot on %s is %s", a.Name, dest.Box(), string(dLastSnapshot))
|
||||||
|
}
|
||||||
|
var sCurrSnapshot Snapshot
|
||||||
|
sNextSnapshot := Snapshot(string(dLastSnapshot)[len(string(dest))+2:])
|
||||||
|
for !cfg.Box[src.Box()].ssh.isLastSnapshot(sNextSnapshot) {
|
||||||
|
sCurrSnapshot = sNextSnapshot
|
||||||
|
sNextSnapshot, err = cfg.Box[src.Box()].ssh.getNextSnapshot(sNextSnapshot)
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Sending incrementally %s to %s", a.Name, string(sNextSnapshot), dest.Box())
|
||||||
|
}
|
||||||
|
err = cfg.Box[dest.Box()].ssh.exec("/usr/bin/ssh root@" + src.Box() + " /sbin/zfs send -I " + string(sCurrSnapshot) + " " + string(sNextSnapshot) + " | /sbin/zfs recv " + dest.Path() + "/" + src.Box() + "/" + src.Path())
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Sending snapshot on %s from %s failed (%s)", a.Name, dest.Box(), string(sNextSnapshot), err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range takeSnapshot {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : cleaning snapshot on %s for %s", a.Name, k, v)
|
||||||
|
}
|
||||||
|
err := cfg.Box[k].ssh.exec("/usr/sbin/zfsnap destroy -p hourly- -p daily- -p weekly- -p monthly- -p yearly- " + v)
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Error executing zfsnap on %s", a.Name, k)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range delSnapshot {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : cleaning snapshot on %s for %s", a.Name, k, v)
|
||||||
|
}
|
||||||
|
err := cfg.Box[k].ssh.exec("/usr/sbin/zfsnap destroy -p hourly- -p daily- -p weekly- -p monthly- -p yearly- " + v)
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Error executing zfsnap on %s", a.Name, k)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range a.After {
|
||||||
|
re := regexp.MustCompile(k)
|
||||||
|
if re.MatchString(schedule) {
|
||||||
|
err := cfg.Box[v.Box()].ssh.exec(v.Path())
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("AppConfig.RunAppBackup : %s : Error executing %s on %s", a.Name, v.Path(), v.Box())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
564
backup.go
564
backup.go
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/csv"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -10,46 +9,22 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Zfsnap map[string]string `json:"zfsnap"`
|
Zfsnap map[string]string `json:"zfsnap"`
|
||||||
Box map[string]BoxConfig `json:"box"`
|
Box map[string]*BoxConfig `json:"box"`
|
||||||
Apps []AppConfig `json:apps`
|
Apps []AppConfig `json:apps`
|
||||||
ssh map[string]*SSHConfig
|
Timezone string `json:"timezone"`
|
||||||
}
|
|
||||||
|
|
||||||
type Location string
|
|
||||||
|
|
||||||
type Snapshot string
|
|
||||||
|
|
||||||
type AppConfig struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Schedule []string `json:"schedule"`
|
|
||||||
Sources []Location `json:"src"`
|
|
||||||
Destinations []Location `json:"dest"`
|
|
||||||
Before map[string]Location `json:"before"`
|
|
||||||
After map[string]Location `json:"after"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSHConfig struct {
|
|
||||||
signer ssh.Signer
|
|
||||||
config *ssh.ClientConfig
|
|
||||||
client *ssh.Client
|
|
||||||
logged bool
|
|
||||||
name string
|
|
||||||
zfs map[string]string
|
|
||||||
snapshot []Snapshot
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BoxConfig struct {
|
type BoxConfig struct {
|
||||||
Addr string `json:"addr"`
|
Addr string `json:"addr"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
|
ssh *SSHConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -60,38 +35,11 @@ var (
|
|||||||
cfg Config
|
cfg Config
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l Location) Box() string {
|
|
||||||
s := strings.Split(string(l), `:`)
|
|
||||||
return s[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l Location) Path() string {
|
|
||||||
s := strings.Split(string(l), `:`)
|
|
||||||
return s[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l Location) Valid() bool {
|
|
||||||
s := strings.Split(string(l), `:`)
|
|
||||||
return len(s) == 2
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Snapshot) Path() string {
|
|
||||||
s2 := strings.Split(string(s), `@`)
|
|
||||||
return s2[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Snapshot) Name() string {
|
|
||||||
s2 := strings.Split(string(s), `@`)
|
|
||||||
return s2[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Snapshot) Append(path string) Snapshot {
|
|
||||||
s2 := strings.Split(string(s), `@`)
|
|
||||||
return Snapshot(s2[0] + "/" + path + "@" + s2[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
//Load config from file
|
//Load config from file
|
||||||
func (c *Config) Load() error {
|
func (c *Config) Load() error {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.Load : Start")
|
||||||
|
}
|
||||||
b, err := ioutil.ReadFile(*cfgFile)
|
b, err := ioutil.ReadFile(*cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if *debugFlag {
|
if *debugFlag {
|
||||||
@ -108,13 +56,12 @@ func (c *Config) Load() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.ssh = make(map[string]*SSHConfig)
|
|
||||||
for k, v := range c.Box {
|
for k, v := range c.Box {
|
||||||
s := &SSHConfig{
|
s := &SSHConfig{
|
||||||
logged: false,
|
logged: false,
|
||||||
name: k,
|
name: k,
|
||||||
}
|
}
|
||||||
cfg.ssh[k] = s
|
v.ssh = s
|
||||||
keyRaw, err := ioutil.ReadFile(v.Key)
|
keyRaw, err := ioutil.ReadFile(v.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if *debugFlag {
|
if *debugFlag {
|
||||||
@ -165,7 +112,8 @@ func (c *Config) Load() error {
|
|||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
session.Stdout = &b
|
session.Stdout = &b
|
||||||
err = session.Run("/usr/sbin/zfsnap --version")
|
|
||||||
|
err = session.Run("TZ=\"" + cfg.Timezone + "\" /usr/sbin/zfsnap --version")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if *debugFlag {
|
if *debugFlag {
|
||||||
log.Printf("Config.Load : client.NewSession(%s) : %s", k, err)
|
log.Printf("Config.Load : client.NewSession(%s) : %s", k, err)
|
||||||
@ -177,7 +125,16 @@ func (c *Config) Load() error {
|
|||||||
}
|
}
|
||||||
session.Close()
|
session.Close()
|
||||||
s.logged = true
|
s.logged = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, box := range c.Box {
|
||||||
|
err = box.ssh.getTime()
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("Config.Load : ssh.getTime() : %s", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, app := range c.Apps {
|
for _, app := range c.Apps {
|
||||||
@ -185,7 +142,7 @@ func (c *Config) Load() error {
|
|||||||
if !src.Valid() {
|
if !src.Valid() {
|
||||||
return fmt.Errorf("Source not valid : %s", string(src))
|
return fmt.Errorf("Source not valid : %s", string(src))
|
||||||
}
|
}
|
||||||
if _, ok := cfg.ssh[src.Box()]; !ok {
|
if _, ok := cfg.Box[src.Box()]; !ok {
|
||||||
return fmt.Errorf("No box defined for source : %s", string(src))
|
return fmt.Errorf("No box defined for source : %s", string(src))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,7 +150,7 @@ func (c *Config) Load() error {
|
|||||||
if !dest.Valid() {
|
if !dest.Valid() {
|
||||||
return fmt.Errorf("Destination not valid : %s", string(dest))
|
return fmt.Errorf("Destination not valid : %s", string(dest))
|
||||||
}
|
}
|
||||||
if _, ok := cfg.ssh[dest.Box()]; !ok {
|
if _, ok := cfg.Box[dest.Box()]; !ok {
|
||||||
return fmt.Errorf("No box defined for destination : %s", string(dest))
|
return fmt.Errorf("No box defined for destination : %s", string(dest))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,7 +165,7 @@ func (c *Config) Load() error {
|
|||||||
if !before.Valid() {
|
if !before.Valid() {
|
||||||
return fmt.Errorf("Before not valid : %s", string(before))
|
return fmt.Errorf("Before not valid : %s", string(before))
|
||||||
}
|
}
|
||||||
if _, ok := cfg.ssh[before.Box()]; !ok {
|
if _, ok := cfg.Box[before.Box()]; !ok {
|
||||||
return fmt.Errorf("No box defined for before : %s", string(before))
|
return fmt.Errorf("No box defined for before : %s", string(before))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,7 +180,7 @@ func (c *Config) Load() error {
|
|||||||
if !after.Valid() {
|
if !after.Valid() {
|
||||||
return fmt.Errorf("After not valid : %s", string(after))
|
return fmt.Errorf("After not valid : %s", string(after))
|
||||||
}
|
}
|
||||||
if _, ok := cfg.ssh[after.Box()]; !ok {
|
if _, ok := cfg.Box[after.Box()]; !ok {
|
||||||
return fmt.Errorf("No box defined for after : %s", string(after))
|
return fmt.Errorf("No box defined for after : %s", string(after))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,449 +194,6 @@ func (c *Config) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SSHConfig) getLastSnapshot(path string) (Snapshot, error) {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getLastSnapshot : Start %s:%s (%d snapshots)", s.name, path, len(s.snapshot))
|
|
||||||
}
|
|
||||||
var last Snapshot
|
|
||||||
for _, v := range s.snapshot {
|
|
||||||
if v.Path() == path {
|
|
||||||
last = v
|
|
||||||
} else {
|
|
||||||
if len(string(last)) > 0 {
|
|
||||||
return last, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(string(last)) > 0 {
|
|
||||||
return last, nil
|
|
||||||
}
|
|
||||||
return last, fmt.Errorf("no snapshot")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHConfig) isLastSnapshot(snapshot Snapshot) bool {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.isLastSnapshot : Start %s:%s", s.name, string(snapshot))
|
|
||||||
}
|
|
||||||
_, err := s.getNextSnapshot(snapshot)
|
|
||||||
if err != nil {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHConfig) getFirstSnapshot(path string) (Snapshot, error) {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getFirstSnapshot : Start %s:%s", s.name, path)
|
|
||||||
}
|
|
||||||
var first Snapshot
|
|
||||||
for _, v := range s.snapshot {
|
|
||||||
if v.Path() == path {
|
|
||||||
first = v
|
|
||||||
return first, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return first, fmt.Errorf("no snapshot")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHConfig) getNextSnapshot(snapshot Snapshot) (Snapshot, error) {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getNextSnapshot : Start %s:%s", s.name, string(snapshot))
|
|
||||||
}
|
|
||||||
var next Snapshot
|
|
||||||
for id, v := range s.snapshot {
|
|
||||||
if v == snapshot {
|
|
||||||
if len(s.snapshot) > id+1 {
|
|
||||||
next = s.snapshot[id+1]
|
|
||||||
if next.Path() == snapshot.Path() {
|
|
||||||
return next, nil
|
|
||||||
} else {
|
|
||||||
return next, fmt.Errorf("no snapshot")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return next, fmt.Errorf("no snapshot")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return next, fmt.Errorf("no snapshot")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHConfig) getSnapshotList() error {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getSnapshotList : %s : Start", s.name)
|
|
||||||
}
|
|
||||||
if !s.logged {
|
|
||||||
return fmt.Errorf("Client %s not logged in.", s.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
session, err := s.client.NewSession()
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getSnapshotList : %s : client.NewSession() : %s", s.name, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
session.Stdout = &b
|
|
||||||
err = session.Run("/usr/sbin/zfs list -H -t snapshot -o name")
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getSnapshotList : %s : session.Run() : %s", s.name, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.snapshot = make([]Snapshot, 0)
|
|
||||||
|
|
||||||
csvReader := csv.NewReader(&b)
|
|
||||||
csvReader.Comma = '\t'
|
|
||||||
csvReader.FieldsPerRecord = 1
|
|
||||||
|
|
||||||
csvData, err := csvReader.ReadAll()
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getSnapshotList : %s : csvReader.ReadAll() : %s", s.name, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rec := range csvData {
|
|
||||||
s.snapshot = append(s.snapshot, Snapshot(rec[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getSnapshotList : %s : read %d zfs snapshots", s.name, len(s.snapshot))
|
|
||||||
}
|
|
||||||
|
|
||||||
session.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHConfig) getZFSList() error {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getZFSList : Start %s", s.name)
|
|
||||||
}
|
|
||||||
if !s.logged {
|
|
||||||
return fmt.Errorf("Client %s not logged in.", s.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
session, err := s.client.NewSession()
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getZFSList : client.NewSession(%s) : %s", s.name, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
session.Stdout = &b
|
|
||||||
err = session.Run("/sbin/zfs list -H -o name,mountpoint")
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getZFSList : session.Run(%s) : %s", s.name, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.zfs = make(map[string]string)
|
|
||||||
|
|
||||||
csvReader := csv.NewReader(&b)
|
|
||||||
csvReader.Comma = '\t'
|
|
||||||
csvReader.FieldsPerRecord = 2
|
|
||||||
|
|
||||||
csvData, err := csvReader.ReadAll()
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getZFSList : csvReader.ReadAll(%s) : %s", s.name, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rec := range csvData {
|
|
||||||
s.zfs[rec[0]] = rec[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.getZFSList : %s : read %d zfs file systems", s.name, len(s.zfs))
|
|
||||||
}
|
|
||||||
|
|
||||||
session.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHConfig) isZFS(path string) bool {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.isZFS : Start %s:%s", s.name, path)
|
|
||||||
}
|
|
||||||
if len(s.zfs) == 0 {
|
|
||||||
err := s.getZFSList()
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.isZFS : s.getZFSList(%s) : %s", s.name, err)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, ok := s.zfs[path]
|
|
||||||
if ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHConfig) exec(cmd string) error {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.exec : Start %s on %s", cmd, s.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
session, err := s.client.NewSession()
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.exec : client(%s).NewSession(%s) : %s", s.name, cmd, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.Run(cmd)
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.exec : session(%s).Run(%s) : %s", s.name, cmd, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
session.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHConfig) createZFS(path string) error {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.createZFS : Start %s:%s", s.name, path)
|
|
||||||
}
|
|
||||||
if len(s.zfs) == 0 {
|
|
||||||
err := s.getZFSList()
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.createZFS : s.getZFSList(%s) : %s", s.name, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p := strings.Split(path, `/`)
|
|
||||||
var base string
|
|
||||||
for _, d := range p {
|
|
||||||
if base == "" {
|
|
||||||
base = d
|
|
||||||
} else {
|
|
||||||
base = base + `/` + d
|
|
||||||
}
|
|
||||||
if _, ok := s.zfs[base]; !ok {
|
|
||||||
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.createZFS : Creating %s:%s", s.name, base)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.exec("/sbin/zfs create -o mountpoint=none " + base)
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("SSHConfig.createZFS : s.exec(%s) : %s", s.name, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.zfs[base] = "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AppConfig) RunAppSchedule(schedule string) error {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Start", a.Name, schedule)
|
|
||||||
}
|
|
||||||
for _, src := range a.Sources {
|
|
||||||
if !cfg.ssh[src.Box()].isZFS(src.Path()) {
|
|
||||||
return fmt.Errorf("No path %s on source", string(src))
|
|
||||||
}
|
|
||||||
for _, dest := range a.Destinations {
|
|
||||||
if !cfg.ssh[dest.Box()].isZFS(dest.Path() + "/" + src.Box() + "/" + src.Path()) {
|
|
||||||
err := cfg.ssh[dest.Box()].createZFS(dest.Path() + "/" + src.Box() + "/" + src.Path())
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Error creating %s on %s", a.Name, schedule, dest.Path()+"/"+src.Box()+"/"+src.Path(), dest.Box())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k, v := range a.Before {
|
|
||||||
re := regexp.MustCompile(k)
|
|
||||||
if re.MatchString(schedule) {
|
|
||||||
err := cfg.ssh[v.Box()].exec(v.Path())
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Error executing %s", a.Name, schedule, string(v))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshSnapshot := make(map[string]bool)
|
|
||||||
takeSnapshot := make(map[string]string)
|
|
||||||
delSnapshot := make(map[string]string)
|
|
||||||
for _, v := range a.Sources {
|
|
||||||
takeSnapshot[v.Box()] = takeSnapshot[v.Box()] + " " + v.Path()
|
|
||||||
refreshSnapshot[v.Box()] = true
|
|
||||||
for _, v2 := range a.Destinations {
|
|
||||||
delSnapshot[v2.Box()] = delSnapshot[v2.Box()] + " " + v2.Path() + "/" + v.Box() + "/" + v.Path()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k, v := range takeSnapshot {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : taking snapshot on %s for %s", a.Name, schedule, k, v)
|
|
||||||
}
|
|
||||||
err := cfg.ssh[k].exec("/usr/sbin/zfsnap snapshot -a " + cfg.Zfsnap[schedule] + v)
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Error executing zfsnap on %s", a.Name, schedule, k)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, v := range a.Destinations {
|
|
||||||
refreshSnapshot[v.Box()] = true
|
|
||||||
}
|
|
||||||
for k, _ := range refreshSnapshot {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : refreshing snapshots for source %s", a.Name, schedule, k)
|
|
||||||
}
|
|
||||||
err := cfg.ssh[k].getSnapshotList()
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Error getting snapshots on %s", a.Name, schedule, k)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, src := range a.Sources {
|
|
||||||
for _, dest := range a.Destinations {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Sending snapshots from %s to %s", a.Name, schedule, string(src), string(dest))
|
|
||||||
}
|
|
||||||
dLastSnapshot, err := cfg.ssh[dest.Box()].getLastSnapshot(dest.Path() + "/" + src.Box() + "/" + src.Path())
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : No snapshot for %s on %s", a.Name, schedule, string(src), dest.Box())
|
|
||||||
}
|
|
||||||
sFirstSnapshot, err := cfg.ssh[src.Box()].getFirstSnapshot(src.Path())
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : No snapshot for %s", a.Name, schedule, string(src))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Initializing snapshot on %s from %s", a.Name, schedule, dest.Box(), string(sFirstSnapshot))
|
|
||||||
}
|
|
||||||
err = cfg.ssh[dest.Box()].exec("/usr/bin/ssh root@" + src.Box() + " /sbin/zfs send " + string(sFirstSnapshot) + " | /sbin/zfs recv -F " + dest.Path() + "/" + src.Box() + "/" + src.Path())
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Initializing snapshot on %s from %s failed (%s)", a.Name, schedule, dest.Box(), string(sFirstSnapshot), err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var sCurrSnapshot Snapshot
|
|
||||||
sNextSnapshot := sFirstSnapshot
|
|
||||||
for !cfg.ssh[src.Box()].isLastSnapshot(sNextSnapshot) {
|
|
||||||
sCurrSnapshot = sNextSnapshot
|
|
||||||
sNextSnapshot, err = cfg.ssh[src.Box()].getNextSnapshot(sNextSnapshot)
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Sending incrementally %s to %s", a.Name, schedule, string(sNextSnapshot), dest.Box())
|
|
||||||
}
|
|
||||||
err = cfg.ssh[dest.Box()].exec("/usr/bin/ssh root@" + src.Box() + " /sbin/zfs send -I " + string(sCurrSnapshot) + " " + string(sNextSnapshot) + " | /sbin/zfs recv " + dest.Path() + "/" + src.Box() + "/" + src.Path())
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Sending snapshot on %s from %s failed (%s)", a.Name, schedule, dest.Box(), string(sNextSnapshot), err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : All snapshots sent for %s", a.Name, schedule, string(src))
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Last snapshot on %s is %s", a.Name, schedule, dest.Box(), string(dLastSnapshot))
|
|
||||||
}
|
|
||||||
var sCurrSnapshot Snapshot
|
|
||||||
sNextSnapshot := Snapshot(string(dLastSnapshot)[len(string(dest))+2:])
|
|
||||||
for !cfg.ssh[src.Box()].isLastSnapshot(sNextSnapshot) {
|
|
||||||
sCurrSnapshot = sNextSnapshot
|
|
||||||
sNextSnapshot, err = cfg.ssh[src.Box()].getNextSnapshot(sNextSnapshot)
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Sending incrementally %s to %s", a.Name, schedule, string(sNextSnapshot), dest.Box())
|
|
||||||
}
|
|
||||||
err = cfg.ssh[dest.Box()].exec("/usr/bin/ssh root@" + src.Box() + " /sbin/zfs send -I " + string(sCurrSnapshot) + " " + string(sNextSnapshot) + " | /sbin/zfs recv " + dest.Path() + "/" + src.Box() + "/" + src.Path())
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Sending snapshot on %s from %s failed (%s)", a.Name, schedule, dest.Box(), string(sNextSnapshot), err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k, v := range takeSnapshot {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : cleaning snapshot on %s for %s", a.Name, schedule, k, v)
|
|
||||||
}
|
|
||||||
err := cfg.ssh[k].exec("/usr/sbin/zfsnap destroy" + v)
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Error executing zfsnap on %s", a.Name, schedule, k)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k, v := range delSnapshot {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : cleaning snapshot on %s for %s", a.Name, schedule, k, v)
|
|
||||||
}
|
|
||||||
err := cfg.ssh[k].exec("/usr/sbin/zfsnap destroy" + v)
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Error executing zfsnap on %s", a.Name, schedule, k)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k, v := range a.After {
|
|
||||||
re := regexp.MustCompile(k)
|
|
||||||
if re.MatchString(schedule) {
|
|
||||||
err := cfg.ssh[v.Box()].exec(v.Path())
|
|
||||||
if err != nil {
|
|
||||||
if *debugFlag {
|
|
||||||
log.Printf("RunAppSchedule(%s) : %s : Error executing %s on %s", a.Name, schedule, v.Path(), v.Box())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@ -689,50 +203,28 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
schedule := *schedFlag
|
err = RunBackup()
|
||||||
if schedule == "" {
|
|
||||||
log.Printf("Main : Finding out schedule.")
|
|
||||||
now := time.Now()
|
|
||||||
if now.Day() == 1 && int(now.Month()) == 1 {
|
|
||||||
schedule = "yearly"
|
|
||||||
} else if now.Day() == 1 {
|
|
||||||
schedule = "monthly"
|
|
||||||
} else if now.Weekday().String() == "Monday" {
|
|
||||||
schedule = "weekly"
|
|
||||||
} else {
|
|
||||||
schedule = "daily"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = RunSchedule(schedule)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Cannot run schedule (%s)", err)
|
log.Printf("Cannot run schedule (%s)", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//RunSchedule run all backup targets where schedule is registered
|
//RunBackup run all backup targets where schedule is registered
|
||||||
func RunSchedule(schedule string) error {
|
func RunBackup() error {
|
||||||
if *debugFlag {
|
if *debugFlag {
|
||||||
log.Printf("RunSchedule(%s) : Start", schedule)
|
log.Printf("RunBackup() : Start")
|
||||||
}
|
|
||||||
if _, ok := cfg.Zfsnap[schedule]; !ok {
|
|
||||||
return fmt.Errorf("No retention defined for %s schedule", schedule)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, app := range cfg.Apps {
|
for _, app := range cfg.Apps {
|
||||||
for _, schedName := range app.Schedule {
|
err := app.RunAppBackup()
|
||||||
if schedName == schedule {
|
|
||||||
err := app.RunAppSchedule(schedule)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if *debugFlag {
|
if *debugFlag {
|
||||||
log.Printf("RunSchedule(%s) : Error running %s", schedule, app.Name)
|
log.Printf("RunBackup() : Error running %s", app.Name)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
20
location.go
Normal file
20
location.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type Location string
|
||||||
|
|
||||||
|
func (l Location) Box() string {
|
||||||
|
s := strings.Split(string(l), `:`)
|
||||||
|
return s[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) Path() string {
|
||||||
|
s := strings.Split(string(l), `:`)
|
||||||
|
return s[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) Valid() bool {
|
||||||
|
s := strings.Split(string(l), `:`)
|
||||||
|
return len(s) == 2
|
||||||
|
}
|
20
snapshot.go
Normal file
20
snapshot.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type Snapshot string
|
||||||
|
|
||||||
|
func (s Snapshot) Path() string {
|
||||||
|
s2 := strings.Split(string(s), `@`)
|
||||||
|
return s2[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Snapshot) Name() string {
|
||||||
|
s2 := strings.Split(string(s), `@`)
|
||||||
|
return s2[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Snapshot) Append(path string) Snapshot {
|
||||||
|
s2 := strings.Split(string(s), `@`)
|
||||||
|
return Snapshot(s2[0] + "/" + path + "@" + s2[1])
|
||||||
|
}
|
331
ssh.go
Normal file
331
ssh.go
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/csv"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SSHConfig struct {
|
||||||
|
signer ssh.Signer
|
||||||
|
config *ssh.ClientConfig
|
||||||
|
client *ssh.Client
|
||||||
|
logged bool
|
||||||
|
name string
|
||||||
|
zfs map[string]string
|
||||||
|
snapshot []Snapshot
|
||||||
|
now time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSHConfig) getLastSnapshot(path string) (Snapshot, error) {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getLastSnapshot : Start %s:%s (%d snapshots)", s.name, path, len(s.snapshot))
|
||||||
|
}
|
||||||
|
var last Snapshot
|
||||||
|
for _, v := range s.snapshot {
|
||||||
|
if v.Path() == path {
|
||||||
|
last = v
|
||||||
|
} else {
|
||||||
|
if len(string(last)) > 0 {
|
||||||
|
return last, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(string(last)) > 0 {
|
||||||
|
return last, nil
|
||||||
|
}
|
||||||
|
return last, fmt.Errorf("no snapshot")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSHConfig) isLastSnapshot(snapshot Snapshot) bool {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.isLastSnapshot : Start %s:%s", s.name, string(snapshot))
|
||||||
|
}
|
||||||
|
_, err := s.getNextSnapshot(snapshot)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSHConfig) getFirstSnapshot(path string) (Snapshot, error) {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getFirstSnapshot : Start %s:%s", s.name, path)
|
||||||
|
}
|
||||||
|
var first Snapshot
|
||||||
|
for _, v := range s.snapshot {
|
||||||
|
if v.Path() == path {
|
||||||
|
first = v
|
||||||
|
return first, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return first, fmt.Errorf("no snapshot")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSHConfig) getNextSnapshot(snapshot Snapshot) (Snapshot, error) {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getNextSnapshot : Start %s:%s", s.name, string(snapshot))
|
||||||
|
}
|
||||||
|
var next Snapshot
|
||||||
|
for id, v := range s.snapshot {
|
||||||
|
if v == snapshot {
|
||||||
|
if len(s.snapshot) > id+1 {
|
||||||
|
next = s.snapshot[id+1]
|
||||||
|
if next.Path() == snapshot.Path() {
|
||||||
|
return next, nil
|
||||||
|
} else {
|
||||||
|
return next, fmt.Errorf("no snapshot")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return next, fmt.Errorf("no snapshot")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next, fmt.Errorf("no snapshot")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSHConfig) getSnapshotList() error {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getSnapshotList : %s : Start", s.name)
|
||||||
|
}
|
||||||
|
if !s.logged {
|
||||||
|
return fmt.Errorf("Client %s not logged in.", s.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := s.client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getSnapshotList : %s : client.NewSession() : %s", s.name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
session.Stdout = &b
|
||||||
|
|
||||||
|
err = session.Run("TZ=\"" + cfg.Timezone + "\" /usr/sbin/zfs list -H -t snapshot -o name")
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getSnapshotList : %s : session.Run() : %s", s.name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.snapshot = make([]Snapshot, 0)
|
||||||
|
|
||||||
|
csvReader := csv.NewReader(&b)
|
||||||
|
csvReader.Comma = '\t'
|
||||||
|
csvReader.FieldsPerRecord = 1
|
||||||
|
|
||||||
|
csvData, err := csvReader.ReadAll()
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getSnapshotList : %s : csvReader.ReadAll() : %s", s.name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rec := range csvData {
|
||||||
|
s.snapshot = append(s.snapshot, Snapshot(rec[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getSnapshotList : %s : read %d zfs snapshots", s.name, len(s.snapshot))
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSHConfig) getZFSList() error {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getZFSList : %s : Start", s.name)
|
||||||
|
}
|
||||||
|
if !s.logged {
|
||||||
|
return fmt.Errorf("Client %s not logged in.", s.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := s.client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getZFSList : %s : client.NewSession() : %s", s.name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
session.Stdout = &b
|
||||||
|
|
||||||
|
err = session.Run("TZ=\"" + cfg.Timezone + "\" /sbin/zfs list -H -o name,mountpoint")
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getZFSList : %s : session.Run() : %s", s.name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.zfs = make(map[string]string)
|
||||||
|
|
||||||
|
csvReader := csv.NewReader(&b)
|
||||||
|
csvReader.Comma = '\t'
|
||||||
|
csvReader.FieldsPerRecord = 2
|
||||||
|
|
||||||
|
csvData, err := csvReader.ReadAll()
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getZFSList : %s : csvReader.ReadAll() : %s", s.name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rec := range csvData {
|
||||||
|
s.zfs[rec[0]] = rec[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getZFSList : %s : read %d zfs file systems", s.name, len(s.zfs))
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSHConfig) isZFS(path string) bool {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.isZFS : Start %s:%s", s.name, path)
|
||||||
|
}
|
||||||
|
if len(s.zfs) == 0 {
|
||||||
|
err := s.getZFSList()
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.isZFS : s.getZFSList(%s) : %s", s.name, err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, ok := s.zfs[path]
|
||||||
|
if ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSHConfig) getTime() error {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getTime : %s : Start", s.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := s.client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getTime : %s : client.NewSession() : %s", s.name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
session.Stdout = &b
|
||||||
|
|
||||||
|
err = session.Run("TZ=\"" + cfg.Timezone + "\" /usr/bin/date +\"%F %T\"")
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getTime : %s : session.Run() : %s", s.name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.now, err = time.Parse("2006-01-02 15:04:05\n", b.String())
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getTime : %s : time.Parse() : %s", s.name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.getTime : %s : now is %s", s.name, s.now.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSHConfig) exec(cmd string) error {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.exec : %s : Start %s", s.name, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := s.client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.exec : %s : client().NewSession(%s) : %s", s.name, cmd, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = session.Run("TZ=\"" + cfg.Timezone + "\" " + cmd)
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.exec : session(%s).Run(%s) : %s", s.name, cmd, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSHConfig) createZFS(path string) error {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.createZFS : Start %s:%s", s.name, path)
|
||||||
|
}
|
||||||
|
if len(s.zfs) == 0 {
|
||||||
|
err := s.getZFSList()
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.createZFS : s.getZFSList(%s) : %s", s.name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p := strings.Split(path, `/`)
|
||||||
|
var base string
|
||||||
|
for _, d := range p {
|
||||||
|
if base == "" {
|
||||||
|
base = d
|
||||||
|
} else {
|
||||||
|
base = base + `/` + d
|
||||||
|
}
|
||||||
|
if _, ok := s.zfs[base]; !ok {
|
||||||
|
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.createZFS : Creating %s:%s", s.name, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.exec("/sbin/zfs create -o mountpoint=none " + base)
|
||||||
|
if err != nil {
|
||||||
|
if *debugFlag {
|
||||||
|
log.Printf("SSHConfig.createZFS : s.exec(%s) : %s", s.name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.zfs[base] = "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user