Files
beads/internal/daemonrunner/process.go
Steve Yegge b5839b656d Fix compilation errors in internal/daemonrunner package
Created missing files:
- logger.go: Logger type, setupLogger method, and env helpers
- signals_unix.go: Unix signal definitions (SIGTERM, SIGINT, SIGHUP)
- signals_windows.go: Windows signal definitions
- sync.go: Sync loop implementation with export/import/validation helpers

Fixed errors:
- Added missing 'version' parameter to acquireDaemonLock call
- Removed duplicate setupLock method from process.go (kept in daemon.go)
- Removed duplicate startRPCServer from daemon.go (kept in rpc.go)
- Fixed LogPath -> LogFile config field reference
- Removed unused 'io' import from process.go

Implementation notes:
- exportToJSONL: Full implementation with dependencies, labels, comments
- importFromJSONL: Placeholder (TODO: extract from cmd/bd/import.go)
- countDBIssues: Uses SQL COUNT(*) optimization with fallback
- validatePostImport: Checks for data loss
- runSyncLoop/runEventLoop: Main daemon event loops with signal handling

All packages now compile successfully with 'go build ./...'

Amp-Thread-ID: https://ampcode.com/threads/T-36a7f730-3420-426f-9e23-f13d5fa089c4
Co-authored-by: Amp <amp@ampcode.com>
2025-11-01 19:10:27 -07:00

79 lines
1.9 KiB
Go

package daemonrunner
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"time"
)
var ErrDaemonLocked = errors.New("daemon lock already held by another process")
// DaemonLockInfo represents the metadata stored in the daemon.lock file
type DaemonLockInfo struct {
PID int `json:"pid"`
Database string `json:"database"`
Version string `json:"version"`
StartedAt time.Time `json:"started_at"`
}
// DaemonLock represents a held lock on the daemon.lock file
type DaemonLock struct {
file *os.File
path string
}
// Close releases the daemon lock
func (l *DaemonLock) Close() error {
if l.file == nil {
return nil
}
err := l.file.Close()
l.file = nil
return err
}
// acquireDaemonLock attempts to acquire an exclusive lock on daemon.lock
func acquireDaemonLock(beadsDir string, dbPath string, version string) (*DaemonLock, error) {
lockPath := filepath.Join(beadsDir, "daemon.lock")
// Open or create the lock file
// #nosec G304 - controlled path from config
f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
return nil, fmt.Errorf("cannot open lock file: %w", err)
}
// Try to acquire exclusive non-blocking lock
if err := flockExclusive(f); err != nil {
_ = f.Close()
if err == ErrDaemonLocked {
return nil, ErrDaemonLocked
}
return nil, fmt.Errorf("cannot lock file: %w", err)
}
// Write JSON metadata to the lock file
lockInfo := DaemonLockInfo{
PID: os.Getpid(),
Database: dbPath,
Version: version,
StartedAt: time.Now().UTC(),
}
_ = f.Truncate(0)
_, _ = f.Seek(0, 0)
encoder := json.NewEncoder(f)
encoder.SetIndent("", " ")
_ = encoder.Encode(lockInfo)
_ = f.Sync()
// Also write PID file for Windows compatibility
pidFile := filepath.Join(beadsDir, "daemon.pid")
_ = os.WriteFile(pidFile, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0600)
return &DaemonLock{file: f, path: lockPath}, nil
}