package api import ( "encoding/json" "time" ) // HostOS / HostArch are constrained string types. The store stores them // raw, but agent metadata collection should populate them from these // constants so we don't end up with both "linux" and "Linux" rows. type HostOS string const ( OSLinux HostOS = "linux" OSWindows HostOS = "windows" ) type HostArch string const ( ArchAmd64 HostArch = "amd64" ArchArm64 HostArch = "arm64" ) // HelloPayload is the agent's first message after WS auth. The server // upserts a Host row, marks it online, and (if protocol_version is // acceptable) responds with a config.update + schedule.set burst. type HelloPayload struct { ProtocolVersion int `json:"protocol_version"` AgentVersion string `json:"agent_version"` ResticVersion string `json:"restic_version"` Hostname string `json:"hostname"` OS HostOS `json:"os"` Arch HostArch `json:"arch"` BootTime time.Time `json:"boot_time,omitempty"` } // HeartbeatPayload is sent by the agent every 30s. It carries no data // today; presence is the signal. Future fields (load, free disk) can // land here without bumping protocol_version. type HeartbeatPayload struct { SentAt time.Time `json:"sent_at"` } // JobKind is the operation an agent is being asked to run, or just ran. type JobKind string const ( JobBackup JobKind = "backup" JobInit JobKind = "init" JobForget JobKind = "forget" JobPrune JobKind = "prune" JobCheck JobKind = "check" JobUnlock JobKind = "unlock" ) // JobStatus is the lifecycle state of a job. type JobStatus string const ( JobQueued JobStatus = "queued" JobRunning JobStatus = "running" JobSucceeded JobStatus = "succeeded" JobFailed JobStatus = "failed" JobCancelled JobStatus = "cancelled" ) // CommandRunPayload is the server → agent dispatch for a run-now job. // // For kind=backup, Includes/Excludes/Tag are populated from the source // group the operator (or schedule) targeted; the agent runs one restic // backup invocation per command.run, tagging the snapshot with Tag (= // the source group's name) so retention can target it later via // `restic forget --tag`. // // For kind=forget, RetentionPolicy is the typed keep-* set as raw JSON // (the agent doesn't share the store package's typed struct). // // Args is preserved as a generic free-form slice for kinds that don't // fit the structured fields (e.g. unlock takes none; init takes none). type CommandRunPayload struct { JobID string `json:"job_id"` Kind JobKind `json:"kind"` Args []string `json:"args,omitempty"` Includes []string `json:"includes,omitempty"` Excludes []string `json:"excludes,omitempty"` Tag string `json:"tag,omitempty"` RetentionPolicy json.RawMessage `json:"retention_policy,omitempty"` } // CommandCancelPayload is the server → agent cancel signal. type CommandCancelPayload struct { JobID string `json:"job_id"` } // CommandResultPayload acks a command.run dispatch (the agent has // accepted the job and persisted it locally) — this is *not* the job // completion. job.finished signals that. type CommandResultPayload struct { JobID string `json:"job_id"` Accepted bool `json:"accepted"` Error string `json:"error,omitempty"` } // JobStartedPayload — agent has begun execution. type JobStartedPayload struct { JobID string `json:"job_id"` Kind JobKind `json:"kind"` StartedAt time.Time `json:"started_at"` } // JobProgressPayload — agent's periodic status while a job is running. // Field set chosen to match what restic --json emits for `backup`; // other kinds populate the subset that makes sense. type JobProgressPayload struct { JobID string `json:"job_id"` PercentDone float64 `json:"percent_done"` FilesDone int64 `json:"files_done"` TotalFiles int64 `json:"total_files"` BytesDone int64 `json:"bytes_done"` TotalBytes int64 `json:"total_bytes"` ETASeconds int64 `json:"eta_seconds"` ThroughputBps int64 `json:"throughput_bps"` } // JobFinishedPayload — agent reports terminal state. type JobFinishedPayload struct { JobID string `json:"job_id"` Status JobStatus `json:"status"` ExitCode int `json:"exit_code"` FinishedAt time.Time `json:"finished_at"` Stats json.RawMessage `json:"stats,omitempty"` // restic summary blob Error string `json:"error,omitempty"` } // LogStreamLine is one entry of the live job log. type LogStreamLine struct { JobID string `json:"job_id"` Seq int64 `json:"seq"` TS time.Time `json:"ts"` Stream LogStream `json:"stream"` Payload string `json:"payload"` } // LogStream identifies which channel a log line came from. type LogStream string const ( LogStdout LogStream = "stdout" LogStderr LogStream = "stderr" LogEvent LogStream = "event" // parsed restic --json event ) // SnapshotsReportPayload — agent dumps its full snapshot list after // each successful backup, so the server can refresh its projection. type SnapshotsReportPayload struct { Snapshots []Snapshot `json:"snapshots"` } // Snapshot is the projection mirrored from `restic snapshots --json`. // SizeBytes / FileCount come from the embedded summary block on // restic 0.16+; older clients leave them at zero (the UI degrades // gracefully). type Snapshot struct { ID string `json:"id"` // long restic snapshot ID ShortID string `json:"short_id"` // 8-hex-char form Time time.Time `json:"time"` Hostname string `json:"hostname"` Paths []string `json:"paths"` Tags []string `json:"tags,omitempty"` SizeBytes int64 `json:"size_bytes,omitempty"` FileCount int64 `json:"file_count,omitempty"` } // RepoStatsPayload — agent reports periodic repo health facts derived // from `restic stats` and lock-file inspection. type RepoStatsPayload struct { SizeBytes int64 `json:"size_bytes"` SnapshotCount int `json:"snapshot_count"` DedupRatio float64 `json:"dedup_ratio"` LastCheckAt time.Time `json:"last_check_at,omitempty"` LastCheckStatus string `json:"last_check_status,omitempty"` LockState string `json:"lock_state"` // locked|unlocked } // Schedule is the agent-facing view of a slim Schedule row plus its // resolved bundle of source groups. The agent's cron only needs to know // when to fire (CronExpr + Enabled) and which schedule fired (ID); the // SourceGroups are carried for forensic logs and so a future agent that // elects to dispatch jobs locally has the data, but the server-side // dispatch path uses the schedule's group list directly. Manual // schedules are gone — Run-now targets a source group, not a schedule. type Schedule struct { ID string `json:"id"` CronExpr string `json:"cron_expr"` Enabled bool `json:"enabled"` SourceGroups []ScheduleSourceGroup `json:"source_groups,omitempty"` } // ScheduleSourceGroup is the resolved-at-push-time view of a source // group attached to a schedule. The agent doesn't need source_group_id // — Name is the snapshot tag and is unique per host. type ScheduleSourceGroup struct { Name string `json:"name"` Includes []string `json:"includes,omitempty"` Excludes []string `json:"excludes,omitempty"` RetentionPolicy json.RawMessage `json:"retention_policy,omitempty"` RetryMax int `json:"retry_max,omitempty"` RetryBackoffSeconds int `json:"retry_backoff_seconds,omitempty"` } // ScheduleSetPayload — server pushes the full canonical schedule list // for a host. Agent reconciles its local cron and replies with // ScheduleAckPayload carrying the same Version. An empty Schedules // list is a valid push that disables every cron entry. type ScheduleSetPayload struct { Version int64 `json:"version"` Schedules []Schedule `json:"schedules"` } // ScheduleAckPayload — agent confirms it has applied a given version. type ScheduleAckPayload struct { Version int64 `json:"version"` AppliedAt time.Time `json:"applied_at"` } // ScheduleFirePayload — agent reports a local cron entry just fired. // Server is expected to look up the schedule, build a CommandRun // payload from it, persist a job row, and return MsgCommandRun on // the same connection. ScheduledAt is the wall-clock time the // agent's cron fired (audit / forensic value when network jitter // pushes the actual command.run dispatch later). type ScheduleFirePayload struct { ScheduleID string `json:"schedule_id"` ScheduledAt time.Time `json:"scheduled_at"` } // ConfigUpdatePayload — server pushes per-host config (currently just // repo connection details). Empty fields mean "leave existing alone"; // to clear something, send an explicit zero value. type ConfigUpdatePayload struct { RepoURL string `json:"repo_url,omitempty"` RepoPassword string `json:"repo_password,omitempty"` // sensitive RepoUsername string `json:"repo_username,omitempty"` RepoCredential string `json:"repo_credential,omitempty"` // sensitive (for rest server basic auth) HookShell string `json:"hook_shell,omitempty"` } // AgentUpdateAvailablePayload — informational only; the agent does // NOT self-update. See spec.md §4.2 for the package-manager-based // update model. type AgentUpdateAvailablePayload struct { LatestVersion string `json:"latest_version"` PackageURL string `json:"package_url"` // apt repo / choco source Changelog string `json:"changelog,omitempty"` }