package main import ( "bufio" "bytes" "fmt" "log" "os" "os/exec" "path" "path/filepath" "regexp" "strings" "github.com/fsnotify/fsnotify" ) type Watcher interface { AddDirectory(string) error Watch() Close() Root() string } type HomeWatcher struct { watcher *fsnotify.Watcher root string raw chan Event } func NewHomeWatcher(rawEvents chan Event) (Watcher, error) { watcher, err := fsnotify.NewWatcher() if err != nil { return nil, err } hw := &HomeWatcher{ watcher: watcher, root: "/home", raw: rawEvents, } out, err := exec.Command("sh", "-c", "stats | jq .active_users[] | tr -d '\"'").Output() if err != nil { return nil, fmt.Errorf("failed to call and process stats: %w", err) } scanner := bufio.NewScanner(bytes.NewReader(out)) for scanner.Scan() { username := strings.TrimSpace(scanner.Text()) home := filepath.Join(hw.Root(), username) if err := hw.AddDirectory(home); err != nil { return nil, err } } return hw, nil } func (hw *HomeWatcher) AddDirectory(path string) error { fileCount := 0 filepath.Walk(path, func(path string, info os.FileInfo, err error) error { if err != nil { return nil } if info.IsDir() && strings.HasPrefix(info.Name(), ".") { return filepath.SkipDir } fileCount++ if info.Mode()&os.ModeSymlink != 0 { return nil } err = hw.watcher.Add(path) if err != nil && err.Error() != "permission denied" && err.Error() != "no such file or directory" { return fmt.Errorf("died at file %d. info: %#v error: %w\n", fileCount, info, err) } log.Printf("watching %s\n", path) return nil }) return nil } func (hw *HomeWatcher) Root() string { return hw.root } func (hw *HomeWatcher) Close() { hw.watcher.Close() } func (hw *HomeWatcher) Watch() { unre := regexp.MustCompile("^/home/([^/]+)/.*$") for { select { case event, ok := <-hw.watcher.Events: if !ok { return } matches := unre.FindStringSubmatch(event.Name) if matches == nil { log.Printf("could not extract username: %s\n", event) } un := matches[1] hw.raw <- Event{ Username: un, Type: eventHomeActivity, Flavor: fmt.Sprintf("%s is knocking about in %s", un, path.Dir(event.Name)), } // event.Name is filepath log.Println("event:", event) fmt.Printf("DBG %#v\n", event) if event.Op&fsnotify.Write == fsnotify.Write { log.Println("modified file:", event.Name) } case err, ok := <-hw.watcher.Errors: if !ok { return } log.Println("error:", err) } } }