backupy-agent/apps/agent/cmd/agent/dumpstate.go
TronoSfera 8b0c978337 feat(initial): Backupy agent + backupy-decrypt CLI
Source ports from the TronoSfera/backupy-cloud monorepo:
- apps/agent/        — Go agent (WSS client, persistent queue, Docker
                       discovery, 5 DB drivers: PG/MySQL/Mongo/Redis/SQLite,
                       pre/post hooks, Prometheus metrics)
- apps/backupy-decrypt/ — standalone CLI for client-side decryption
- packages/proto/    — protobuf wire format (generated .pb.go committed
                       so the repo builds without protoc)
- docs/              — agent spec + wire-protocol contract

Apache-2.0 license. Image published to ghcr.io/tronosfera/backupy-agent
on every v* tag via .github/workflows/release.yml (multi-arch amd64+arm64).
2026-05-17 20:22:35 +03:00

98 lines
2.7 KiB
Go

package main
import (
"encoding/hex"
"encoding/json"
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/backupy/backupy/apps/agent/internal/config"
"github.com/backupy/backupy/apps/agent/internal/state"
)
// stateDump is the pretty-printable shape of the agent's state file.
// Binary blobs are hex-encoded so the JSON stays UTF-8 clean.
type stateDump struct {
Path string `json:"path"`
SessionID string `json:"session_id,omitempty"`
ConfigVersion uint64 `json:"config_version"`
ConfigBytes string `json:"config_bytes_hex,omitempty"`
QueueDepth int `json:"queue_depth"`
Queue []dumpedJob `json:"queue,omitempty"`
LastHeartbeat int64 `json:"last_heartbeat_ms"`
Note string `json:"note,omitempty"`
Meta map[string]any `json:"meta,omitempty"`
}
type dumpedJob struct {
RunID string `json:"run_id"`
PayloadHex string `json:"payload_hex"`
}
func newDumpStateCmd() *cobra.Command {
var allowSecrets bool
cmd := &cobra.Command{
Use: "dump-state",
Short: "Debug: print state.db contents as JSON",
Long: `Dump the contents of /var/lib/backup-agent/state.db as JSON.
Refuses to print decrypted config or job payloads unless --allow-secrets
is passed, since those may include S3 presigned URLs or other sensitive
material received from the server.`,
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := config.Load()
if err != nil {
return err
}
s, err := state.Open(cfg.StateDBPath(), state.Options{AgentKey: cfg.AgentKey})
if err != nil {
return err
}
defer s.Close()
dump := stateDump{Path: cfg.StateDBPath()}
if v, raw, err := s.LoadConfig(); err == nil {
dump.ConfigVersion = v
if allowSecrets {
dump.ConfigBytes = hex.EncodeToString(raw)
} else {
dump.Note = "config bytes omitted; re-run with --allow-secrets to include"
}
}
if sid, err := s.LoadSession(); err == nil {
dump.SessionID = sid
}
if hb, err := s.LastHeartbeat(); err == nil {
dump.LastHeartbeat = hb
}
if d, err := s.QueueDepth(); err == nil {
dump.QueueDepth = d
}
if allowSecrets {
if jobs, err := s.DequeueJobs(100); err == nil {
for _, j := range jobs {
dump.Queue = append(dump.Queue, dumpedJob{
RunID: j.RunID,
PayloadHex: hex.EncodeToString(j.Payload),
})
}
}
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(dump); err != nil {
return fmt.Errorf("encode: %w", err)
}
return nil
},
}
cmd.Flags().BoolVar(&allowSecrets, "allow-secrets", false,
"include decrypted config and job payloads (may leak secrets)")
return cmd
}