daemon

package
v0.0.0-...-8603278 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: MIT Imports: 41 Imported by: 0

Documentation

Overview

Package daemon is the public library API for running the tailsync synchronization service. Embedders (CLI, Android app wrappers, other hosts) construct a Daemon with New and call Daemon.Run.

The CLI lives in cmd/tailsync. Android/gomobile bind packages should live in the app repository and depend on this package rather than a bind surface here.

Unstable hooks

Config.AfterReconcile, Config.AfterSyncPeers, and Config.AfterNotify exist for in-module tests. They are not a supported production embedder API and may change or be removed without notice.

Index

Constants

View Source
const (
	DefaultPort         = 5960
	DefaultScanInterval = 30 * time.Second
	DefaultSyncInterval = 45 * time.Second
	// DefaultWatchDebounce is how long to wait after an FS event before
	// reconciling when filesystem watching is active.
	DefaultWatchDebounce = time.Second
	// DefaultBlockSize is the rsync-style delta block size when Config.BlockSize
	// is zero (4096 bytes).
	DefaultBlockSize = 4096
	// DefaultMaxFileBytes is the max single-file size loaded into memory for
	// transfer/delta (v1 keeps whole-file buffers). Wire framing is capped
	// separately so peer messages cannot allocate unboundedly.
	DefaultMaxFileBytes = 64 << 20 // 64 MiB
	// DefaultDialTimeout is how long an outbound peer dial/handshake may block
	// before failing during discovery.
	DefaultDialTimeout = 30 * time.Second
	// DefaultTombstoneTTL is how long deletion tombstones are retained before
	// garbage collection when Config.TombstoneTTL is zero (30 days).
	DefaultTombstoneTTL = 30 * 24 * time.Hour
	// DefaultDiscoveryConcurrency caps concurrent discovery dials (in-flight only).
	DefaultDiscoveryConcurrency = 32
	// DefaultPullStreamConcurrency caps concurrent pull streams across peers
	// (each may buffer up to MaxFileBytes).
	DefaultPullStreamConcurrency = 8
	// DefaultHeartbeatInterval is the app-level ping interval on each session.
	DefaultHeartbeatInterval = 20 * time.Second
)

Default configuration values applied by New when the corresponding Config field is zero. CLI flags and embedder UIs should reference these rather than re-hardcoding.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Dir is the directory to synchronize (required).
	Dir string
	// StateDir holds the index and, when NetMode is NetModeTSNet, tsnet state.
	// Defaults to Dir/.tailsync.
	StateDir string
	// Hostname is the protocol / tsnet node name.
	//   - NetModeTSNet: advertised tsnet hostname (default tailsync-<os-hostname>).
	//   - NetModeHost: always overwritten from LocalAPI Self (MagicDNS → HostName →
	//     StableID); any configured value is ignored for protocol identity.
	//   - NetModePlain: wire-protocol node id when set.
	Hostname string
	// Port is the UDP port for QUIC peer sessions over the tailnet (or localhost in plain mode).
	Port int
	// AuthKey is an optional Tailscale auth key for NetModeTSNet
	// (else interactive login / existing tsnet state). Unused in host mode.
	AuthKey string
	// ScanInterval is the safety-net full rescan period. When filesystem
	// watching is active, local edits are reconciled via debounced FS events;
	// this interval still walks the tree to catch missed events.
	ScanInterval time.Duration
	// SyncInterval is the backup peer pull period for catch-up (offline peers,
	// missed notifies). Local index changes fan out best-effort notifies; peers
	// pull on their own. Correctness does not depend on notify delivery.
	SyncInterval time.Duration
	// WatchDebounce is how long to wait after an FS event before reconciling
	// (0 = DefaultWatchDebounce). Ignored when DisableWatch is set or watch
	// fails to start.
	WatchDebounce time.Duration
	// DisableWatch skips filesystem watching and relies on ScanInterval only.
	// Useful in tests and on platforms where watching is unavailable.
	DisableWatch bool
	// BlockSize for rsync-style signatures.
	BlockSize int
	// MaxFileBytes rejects local files larger than this for serve/pull (0 = default).
	MaxFileBytes int64
	// DialTimeout is the max wait for an outbound peer QUIC dial+handshake
	// during discovery (0 = DefaultDialTimeout).
	DialTimeout time.Duration
	// DiscoveryConcurrency caps concurrent discovery dials (0 = default 32).
	// The semaphore is in-flight only (released before backoff sleep).
	DiscoveryConcurrency int
	// PullStreamConcurrency caps concurrent content pull streams across all
	// peers (0 = default 8). Separate from discovery concurrency.
	PullStreamConcurrency int
	// HeartbeatInterval is the app-level ping period on each peer session
	// (0 = DefaultHeartbeatInterval). QUIC also has its own keep-alive.
	HeartbeatInterval time.Duration
	// TombstoneTTL drops old deletion tombstones from the index (0 = DefaultTombstoneTTL).
	TombstoneTTL time.Duration
	// Logger defaults to slog.Default().
	Logger *slog.Logger
	// NetMode selects networking: host (default), tsnet, or plain. See NetMode constants.
	NetMode NetMode
	// ListenHost is used when NetMode is NetModePlain (default 127.0.0.1).
	ListenHost string
	// Peers is an optional explicit list of peer addresses (host:port) for tests
	// and overrides. When empty, discovery uses mesh trust on Online status
	// peers: untagged Self → same UserID; tagged Self → peer must carry MeshTag
	// (excludes self, sharees, Mullvad, and identity mismatches). When set,
	// status discovery is skipped (test determinism), but host/tsnet handshakes
	// still enforce WhoIs + the same trust policy. Soft dial failures and
	// backoff handle nodes that are Online but not running tailsync.
	Peers []string
	// MeshTag is the ACL tag peers must share when this machine is tagged
	// (for example "tag:tailsync"). Required at listen when Self is tagged
	// and must appear on Self. Must be empty when Self is untagged. Non-empty
	// values are trimmed, lowercased, and checked with Tailscale's tag rules
	// at [New] (tag:<letter…>, letters/digits/dashes only).
	MeshTag string
	// OnReady, if non-nil, is called once after the daemon is listening and before
	// the main loop. Used by library hosts so Start/lifecycle wrappers can wait
	// for listen success or a fast failure. Must not block indefinitely.
	OnReady func()
	// OnAuthURL, if non-nil, is called when interactive Tailscale login is needed
	// during NetModeTSNet bring-up and an auth/login URL is available (for example
	// browser login when AuthKey is empty and no enrolled tsnet state exists).
	// Invoked from a background goroutine while Up is still waiting. Called at
	// most once per distinct URL per listen attempt. Must return quickly.
	// Not used for host or plain modes. Never receives AuthKey material.
	OnAuthURL func(url string)
	// AfterReconcile, if non-nil, is called after each successful reconcile with
	// whether peer-visible local index content changed.
	//
	// Unstable / test-only: not a supported production API; may change or be
	// removed without notice. Must return quickly and must not call back into
	// the daemon.
	AfterReconcile func(changed bool)
	// AfterSyncPeers, if non-nil, is called after each pullPeers batch completes
	// (including empty peer lists).
	//
	// Unstable / test-only: not a supported production API; may change or be
	// removed without notice. Must return quickly and must not call back into
	// the daemon.
	AfterSyncPeers func()
	// AfterNotify, if non-nil, is called when scheduleNotify actually schedules
	// at least one notify goroutine (not when deduped or no candidates; not
	// after each peer dial finishes).
	//
	// Unstable / test-only: not a supported production API; may change or be
	// removed without notice. Must return quickly and must not call back into
	// the daemon.
	AfterNotify func()
}

Config holds daemon configuration.

type Daemon

type Daemon struct {
	// contains filtered or unexported fields
}

Daemon is the synchronization service.

Locking:

  • syncMu serializes multi-step local reconcile and remote apply (decide → optional network → re-check LWW → disk/index commit). Concurrent peer applies may run network I/O in parallel while unlocked; only decide and commit hold syncMu. The main Run loop keeps reconcile→notify responsive; pull batches run on a single-flight worker (pullWG) and do not block notify scheduling. Notify fan-out is fire-and-forget and does not hold syncMu.
  • index.Index has its own RWMutex for map access. Holding syncMu does not replace index locks; index methods still lock internally. Callers that need a stable multi-step view of the index relative to disk must hold syncMu around the whole operation (see reconcile, applyRemote).

func New

func New(cfg Config) (*Daemon, error)

New constructs a Daemon from cfg (does not start it).

func (*Daemon) InjectNetworkChange

func (d *Daemon) InjectNetworkChange()

InjectNetworkChange signals tsnet's netmon that host connectivity changed (Android ConnectivityManager updates). No-op when not in tsnet mode, before Up succeeds, or after network backend teardown. Safe concurrent with Daemon.Run and Run shutdown (context cancel): copies the inject func under netMu, then invokes it outside the lock.

func (*Daemon) Run

func (d *Daemon) Run(ctx context.Context) error

Run starts the daemon until ctx is cancelled.

Main loop roles:

  • FS watch (debounced) and ScanInterval both request reconcile
  • successful reconcile with peer-visible changes fans out best-effort notifies on persistent peer sessions (fire-and-forget)
  • SyncInterval and inbound notifies schedule pull on a single-flight background worker so long pull batches do not delay reconcile→notify
  • peer discovery and session accept run in the peer manager

type NetMode

type NetMode int

NetMode selects how the daemon attaches to the network.

const (
	// NetModeHost uses the system tailscaled via LocalAPI (default).
	// Traffic stays on the host Tailscale identity; no extra node is registered.
	NetModeHost NetMode = iota
	// NetModeTSNet runs an embedded tsnet node (registers as a separate machine).
	NetModeTSNet
	// NetModePlain uses plain QUIC (UDP) on ListenHost (tests only).
	NetModePlain
)

func (NetMode) String

func (m NetMode) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL