Git based notes tracking/VCS daemon
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

225 lines
4.1 KiB

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