- Startup passed on ANY scan error: WalkDir stopped at the first unreadable dir and Scan discarded what it had found, while search kept walking and served a planted symlink. Scan now records unreadable dirs and keeps going; startup refuses on symlinks or unreadable dirs and only lets a missing brain dir pass. - Watch went silent while a scan error persisted, and marked a set as reported before Notify, so an ntfy outage lost the alert. It now alerts on unreadable dirs and scan errors too, and records a set as reported only after Notify succeeds (retried each tick). - Only the root .git is skipped; a nested .git is scanned. - A non-positive BRAIN_SYMLINK_SCAN_INTERVAL fell into time.NewTicker and crashlooped the server; it now falls back to the default. - Findings are quoted in alerts, so a newline in a name cannot merge two sets; alertingWarnings names the tripwire when ntfy is unset. - Test names match what they assert (two-set dedupe, symlinked entries inside a symlinked root, nested .git). Flipped: startup ignoring unreadable dirs, .git skipped at any depth, and no retry each turn a test red. Watch tests stable over -count=10 and under -race. Refs #157 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
40 lines
1.3 KiB
Go
40 lines
1.3 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/mathiasbq/hyperguild/ingestion/internal/symlinkguard"
|
|
)
|
|
|
|
// startupSymlinkCheck is the hyperguild#157 tripwire at boot: a symlink in the
|
|
// brain dir would let brain readers serve a file from outside the brain, so
|
|
// the server does not start until it is removed. A directory the scan cannot
|
|
// read could hide one, so it refuses on that too. A missing brain dir (first
|
|
// boot) is the only case it lets pass.
|
|
func startupSymlinkCheck(brainDir string) error {
|
|
r, err := symlinkguard.Scan(brainDir)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("refusing to start: cannot scan the brain dir %s (hyperguild#157): %w", brainDir, err)
|
|
}
|
|
if !r.Clean() {
|
|
return fmt.Errorf("refusing to start: brain dir %s has %s (hyperguild#157); fix them first", brainDir, r)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// symlinkScanInterval reads BRAIN_SYMLINK_SCAN_INTERVAL (seconds), falling
|
|
// back to the default on an unset, non-numeric or non-positive value.
|
|
func symlinkScanInterval(getenv func(string) string) time.Duration {
|
|
if s, err := strconv.Atoi(getenv("BRAIN_SYMLINK_SCAN_INTERVAL")); err == nil && s > 0 {
|
|
return time.Duration(s) * time.Second
|
|
}
|
|
return symlinkguard.DefaultInterval
|
|
}
|