Documentation
¶
Overview ¶
Package traffic analyses Caddy access logs to produce per-service bot-detection signals used to distinguish genuine human inactivity from automated keepalive pings.
Motivation ¶
Hobby-tier services sleep after 30 minutes of genuine inactivity, but external cron jobs (e.g. cron-job.org, UptimeRobot) are commonly used to prevent sleep by pinging a service on a fixed schedule. A naive last-request timestamp is trivially defeated by a single automated pinger.
Algorithm ¶
Three independent signals are evaluated over a rolling 1-hour window per service. A service is considered genuinely idle only when ALL three signals agree:
Subnet diversity — real sites attract visitors from many networks. Threshold: fewer than 3 unique /24 subnets in the window.
Cadence regularity — human visits are aperiodic; cron jobs are metronomic. Measured as the coefficient of variation (stddev/mean) of inter-request gaps. Threshold: CV < 0.2 (highly regular).
Path diversity — humans browse multiple pages; ping bots hit only / or /health. Threshold: fewer than 2 unique request paths in the window.
If any one signal suggests human-like traffic, the service remains awake.
Data source ¶
Signals are derived from Caddy's per-domain JSON access logs written to ~/.dployr/logs/caddy/{domain}.log. No additional instrumentation is required. The computed signals are included in the v1.1 node update and evaluated on base.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CaddyLogDir ¶
func CaddyLogDir() string
CaddyLogDir returns the canonical path to the Caddy per-domain log directory.
Types ¶
type LogEntry ¶
type LogEntry struct {
TS float64 `json:"ts"` // Unix timestamp with fractional seconds
Request LogRequest `json:"request"`
Status int `json:"status"`
}
LogEntry represents a single line from Caddy's JSON access log. Fields match Caddy's default structured log output.
func ReadLastHour ¶
ReadLastHour reads a Caddy JSON access log file and returns all entries whose timestamp falls within the past hour. Lines that cannot be parsed are silently skipped so a single malformed line never blocks analysis.
type LogRequest ¶
type LogRequest struct {
RemoteIP string `json:"remote_ip"`
URI string `json:"uri"`
Method string `json:"method"`
}
LogRequest holds the per-request fields we care about for signal computation.
type Signals ¶
type Signals struct {
// Domain is the Caddy virtual-host domain this log file belongs to.
Domain string `json:"domain"`
// WindowHours is the observation window used (always 1).
WindowHours int `json:"window_hours"`
// RequestCount is the total number of requests seen in the window.
RequestCount int `json:"request_count"`
// UniqueSubnets is the count of distinct /24 networks that sent requests.
// Fewer than 3 suggests a single automated source.
UniqueSubnets int `json:"unique_subnets"`
// CadenceCV is the coefficient of variation (stddev/mean) of inter-request
// gap durations in seconds. Values below 0.2 indicate metronomic (bot-like)
// cadence. Set to 1.0 when fewer than 2 requests are present.
CadenceCV float64 `json:"cadence_cv"`
// UniquePaths is the count of distinct URI paths (query strings stripped).
// Fewer than 2 suggests a bot hitting only / or /health.
UniquePaths int `json:"unique_paths"`
// LastRequestAt is the Unix millisecond timestamp of the most recent request
// in the window, or 0 if the window is empty.
LastRequestAt int64 `json:"last_request_at"`
}
Signals holds the computed bot-detection signals for a single service over a rolling 1-hour observation window.