// notesd - daemon for syncing notes with a git repo
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing/object"
|
|
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
|
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
|
"github.com/spf13/pflag"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
type Config struct {
|
|
Folder string `yaml:"folder"`
|
|
ForcePull bool `yaml:"force-pull"`
|
|
Repo struct {
|
|
User string `yaml:"username"`
|
|
Author string `yaml:"author"`
|
|
eMail string `yaml:"email"`
|
|
APIKey string `yaml:"api-key"`
|
|
SSHUser string `yaml:"ssh-user"`
|
|
} `yaml:"git-repo"`
|
|
}
|
|
|
|
type lastUpdate struct {
|
|
File string
|
|
Time time.Time
|
|
}
|
|
|
|
func check(e error) {
|
|
if e != nil {
|
|
log.Println(e)
|
|
}
|
|
}
|
|
|
|
func panic(e error) {
|
|
if e != nil {
|
|
log.Panicln(e)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
|
|
var cfg Config
|
|
var configPath string
|
|
var confFile *os.File
|
|
|
|
userHome := os.Getenv("HOME")
|
|
|
|
pflag.StringVarP(&configPath, "config", "c", "", "Config file to use")
|
|
pflag.Parse()
|
|
|
|
// Config
|
|
|
|
// Default config path
|
|
if configPath == "" {
|
|
configPath = userHome + "/.config/notesd/config.yaml"
|
|
}
|
|
|
|
confFile, err := os.Open(configPath)
|
|
check(err)
|
|
|
|
defer confFile.Close()
|
|
|
|
decoder := yaml.NewDecoder(confFile)
|
|
err = decoder.Decode(&cfg)
|
|
panic(err)
|
|
|
|
// Watcher/Repo
|
|
|
|
watcher, err := fsnotify.NewWatcher()
|
|
check(err)
|
|
|
|
defer watcher.Close()
|
|
|
|
homeRx := regexp.MustCompile("^~.*")
|
|
|
|
if homeRx.Match([]byte(cfg.Folder)) {
|
|
cfg.Folder = strings.Replace(cfg.Folder, "~", userHome, 1)
|
|
}
|
|
|
|
repo, err := git.PlainOpen(cfg.Folder)
|
|
check(err)
|
|
|
|
w, err := repo.Worktree()
|
|
check(err)
|
|
|
|
var pull git.PullOptions
|
|
var push git.PullOptions
|
|
|
|
// git setup
|
|
|
|
if cfg.Repo.APIKey != "" {
|
|
pull.Auth = &http.BasicAuth{
|
|
Username: cfg.Repo.User,
|
|
Password: cfg.Repo.APIKey,
|
|
}
|
|
push.Auth = &http.BasicAuth{
|
|
Username: cfg.Repo.User,
|
|
Password: cfg.Repo.APIKey,
|
|
}
|
|
} else if cfg.Repo.SSHUser != "" {
|
|
publicKeys, err := ssh.DefaultAuthBuilder(cfg.Repo.SSHUser)
|
|
check(err)
|
|
pull.Auth = ssh.AuthMethod(publicKeys)
|
|
push.Auth = ssh.AuthMethod(publicKeys)
|
|
} else {
|
|
fmt.Println("No valid auth methods found. Not starting.")
|
|
fmt.Println("Please specify rather `api-key` or `ssh-key` and `ssh-passwd`(if applicable) in the config file.")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// force pull of remote
|
|
if cfg.ForcePull {
|
|
|
|
pull.Force = true
|
|
|
|
err = w.Pull(&pull)
|
|
check(err)
|
|
|
|
}
|
|
|
|
done := make(chan bool)
|
|
|
|
var status git.Status
|
|
|
|
go func() {
|
|
for {
|
|
select {
|
|
case event, ok := <-watcher.Events:
|
|
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
log.Println("event:", event)
|
|
|
|
var filePath []string
|
|
var fileName string
|
|
|
|
if event.Op&fsnotify.Write == fsnotify.Write ||
|
|
event.Op&fsnotify.Rename == fsnotify.Rename ||
|
|
event.Op&fsnotify.Chmod == fsnotify.Chmod ||
|
|
event.Op&fsnotify.Create == fsnotify.Create {
|
|
|
|
status, err = w.Status()
|
|
check(err)
|
|
|
|
if status.IsClean() {
|
|
continue
|
|
}
|
|
|
|
log.Println("modified file:", event.Name)
|
|
|
|
filePath = strings.Split(event.Name, "/")
|
|
fileName = filePath[len(filePath)-1]
|
|
|
|
NewFile := cfg.Folder + "/" + fileName
|
|
|
|
// Check if file to be pushed was pushed less than 1.5 seconds ago
|
|
// If so, return so we don't push 8 commits per file change
|
|
|
|
err = watcher.Add(NewFile)
|
|
check(err)
|
|
|
|
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
|
|
|
filePath = strings.Split(event.Name, "/")
|
|
fileName = filePath[len(filePath)-1]
|
|
|
|
NewFile := cfg.Folder + "/" + fileName
|
|
err = watcher.Remove(NewFile)
|
|
check(err)
|
|
|
|
}
|
|
|
|
_, err = w.Add(".")
|
|
check(err)
|
|
|
|
commit, err := w.Commit(fmt.Sprintln(time.Now()), &git.CommitOptions{
|
|
All: true,
|
|
Author: &object.Signature{
|
|
Name: cfg.Repo.Author,
|
|
Email: cfg.Repo.eMail,
|
|
When: time.Now(),
|
|
},
|
|
})
|
|
|
|
check(err)
|
|
|
|
repo.CommitObject(commit)
|
|
|
|
var o git.PushOptions
|
|
|
|
err = repo.Push(&o)
|
|
|
|
check(err)
|
|
|
|
log.Printf("Pushed: %s\n", fileName)
|
|
|
|
case err, ok := <-watcher.Errors:
|
|
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
check(err)
|
|
|
|
}
|
|
}
|
|
}()
|
|
|
|
err = watcher.Add(cfg.Folder)
|
|
panic(err)
|
|
<-done
|
|
|
|
}
|