Documentation
¶
Overview ¶
Package cloudbackup is the user-facing service for cloud backup destinations. It owns:
- building the JSON backup payload (a stable, ordered representation of the pilot's data);
- CRUD over backup destinations (with AES-256-GCM encryption of credentials at rest);
- executing one-off backup runs (BuildJSON → upload → retention prune → audit log);
- a process-local scheduler that ticks every minute and queues due destinations.
The package depends only on the repository layer and the provider plugin registry. HTTP handlers, JWT, and Gin live elsewhere.
Index ¶
- Variables
- type BuildMetadata
- type CreateDestinationInput
- type DefaultJSONBuilder
- type JSONBuilder
- type Options
- type RunRequest
- type Scheduler
- type Service
- func (s *Service) CreateDestination(ctx context.Context, in CreateDestinationInput) (*models.BackupDestination, error)
- func (s *Service) DeleteDestination(ctx context.Context, destinationID, userID uuid.UUID) error
- func (s *Service) GetDestination(ctx context.Context, destinationID, userID uuid.UUID) (*models.BackupDestination, error)
- func (s *Service) GetRun(ctx context.Context, runID, userID uuid.UUID) (*models.BackupRun, error)
- func (s *Service) ListDestinations(ctx context.Context, userID uuid.UUID) ([]*models.BackupDestination, error)
- func (s *Service) ListProviders() []provider.Provider
- func (s *Service) ListRuns(ctx context.Context, destinationID, userID uuid.UUID, page, pageSize int) ([]*models.BackupRun, int, error)
- func (s *Service) Provider(name string) (provider.Provider, error)
- func (s *Service) RunOnce(ctx context.Context, req RunRequest) (*models.BackupRun, error)
- func (s *Service) SetScheduler(sc *Scheduler)
- func (s *Service) TestDestination(ctx context.Context, destinationID, userID uuid.UUID) (bool, string, error)
- func (s *Service) UpdateDestination(ctx context.Context, destinationID, userID uuid.UUID, ...) (*models.BackupDestination, error)
- type UpdateDestinationInput
Constants ¶
This section is empty.
Variables ¶
var ( ErrDestinationNotFound = errors.New("backup destination not found") ErrProviderUnknown = errors.New("unknown backup provider") ErrInvalidSchedule = errors.New("invalid schedule") ErrConcurrentRun = errors.New("a backup is already running for this destination") )
Errors returned by the service layer. These map cleanly onto HTTP status codes in the handler layer.
Functions ¶
This section is empty.
Types ¶
type BuildMetadata ¶
type BuildMetadata struct {
SHA256 string
SizeBytes int64
FlightCount int
AircraftCount int
LicenseCount int
CredentialCount int
// ContentType describes the bytes returned by BuildJSON. Currently always
// "application/gzip" — JSON wrapped in gzip — but we expose it explicitly
// so the provider can set the correct Content-Type header.
ContentType string
// Filename is a recommended object name, e.g. "ninerlog-backup-2025-...".
Filename string
}
BuildMetadata accompanies a generated backup payload. The counts feed the BackupRun audit record and the SHA-256 powers the "skip if unchanged" optimization.
type CreateDestinationInput ¶
type CreateDestinationInput struct {
UserID uuid.UUID
Provider string
DisplayName string
Config provider.Config
Credentials provider.Credentials
Schedule models.BackupSchedule
ScheduleHourUTC int
ScheduleDayOfWeek *int
ScheduleDayOfMonth *int
RetentionCount int
Enabled bool
}
CreateDestinationInput is the service-level input for creating a backup destination. Credentials are accepted as a map of strings keyed by the provider's CredentialSchema.
type DefaultJSONBuilder ¶
type DefaultJSONBuilder struct {
Flights *service.FlightService
Aircraft *service.AircraftService
Licenses *service.LicenseService
Credentials *service.CredentialService
ClassRating *service.ClassRatingService
// AttachCrew is called with the flight slice before serialisation so the
// crew-table fallback that the export pathway relies on still fires.
// Optional.
AttachCrew func(ctx context.Context, flights []*models.Flight)
// SortFlights is called with the flight slice before serialisation.
// Optional; defaults to chronological order (date, off-block, id).
SortFlights func(flights []*models.Flight)
// Version is embedded in the payload. Defaults to "1.0".
Version string
// Format is embedded in the payload. Defaults to "NinerLog JSON Backup".
Format string
// Now returns the timestamp used for exportedAt and the filename.
// Defaults to time.Now().UTC.
Now func() time.Time
}
DefaultJSONBuilder is the production implementation of JSONBuilder. It composes the existing per-resource services to produce a stable backup payload that matches the on-the-wire JSON export ExportDataJSON has emitted since v1.0.
Stability guarantees:
- Top-level keys: exportedAt, version, format, flights, aircraft, licenses, credentials.
- Flights are sorted chronologically (date, then off-block/departure).
- Aircraft are sorted by registration.
- Licenses are sorted by id; class ratings are sorted by id.
- Credentials are sorted by id.
- The exportedAt field is excluded from the SHA-256 fingerprint used for "skip if unchanged" so a re-run on an otherwise-identical dataset is correctly identified as a no-op.
func (*DefaultJSONBuilder) BuildJSON ¶
func (b *DefaultJSONBuilder) BuildJSON(ctx context.Context, userID uuid.UUID) (io.ReadCloser, BuildMetadata, error)
BuildJSON gathers the user's data, serialises it to gzipped JSON, and returns a reader along with metadata for the BackupRun audit log.
type JSONBuilder ¶
type JSONBuilder interface {
BuildJSON(ctx context.Context, userID uuid.UUID) (io.ReadCloser, BuildMetadata, error)
}
JSONBuilder is implemented by anything that can produce the data half of a backup. The default implementation queries the existing flight / aircraft / license / credential services.
type Options ¶
type Options struct {
DestinationRepo repository.BackupDestinationRepository
RunRepo repository.BackupRunRepository
Registry *provider.Registry
Crypto *cryptoutil.AEAD
Builder JSONBuilder
// Clock is the function used everywhere "now" is needed. Defaults to
// time.Now.UTC.
Clock func() time.Time
}
Options bundles the dependencies for New.
type RunRequest ¶
type RunRequest struct {
DestinationID uuid.UUID
// UserID must match the destination's owner; the runner re-checks this
// to guarantee scheduler bugs cannot leak data across tenants.
UserID uuid.UUID
Trigger models.BackupRunTrigger
}
RunRequest describes a single backup execution.
type Scheduler ¶
type Scheduler struct {
// contains filtered or unexported fields
}
Scheduler is a process-local goroutine that periodically asks the repository for due destinations and dispatches them via Service.RunOnce.
Design notes:
- One scheduler per API process. In a horizontally-scaled deployment (which we are not yet doing) you would want a leadership election or row-level lock; the current design only guarantees at-most-once per destination per process due to runLocks.
- Ticks every minute, with a small jitter on the first tick to spread out load when multiple instances of the API restart simultaneously.
- Per-user concurrency cap: at most one run per user can be in flight across all of their destinations.
func NewScheduler ¶
NewScheduler constructs a scheduler attached to svc. tick is the period of the loop; pass 0 for the default 60s.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the cloud backup application service. Construct via New.
func (*Service) CreateDestination ¶
func (s *Service) CreateDestination(ctx context.Context, in CreateDestinationInput) (*models.BackupDestination, error)
CreateDestination validates the provider/config/credentials combination, authenticates against the provider, encrypts the credential blob, and persists the destination. The supplied credentials are never written to disk in plaintext nor returned in the response.
func (*Service) DeleteDestination ¶
DeleteDestination removes a destination (and cascades to its runs).
func (*Service) GetDestination ¶
func (s *Service) GetDestination(ctx context.Context, destinationID, userID uuid.UUID) (*models.BackupDestination, error)
GetDestination returns one destination by ID, scoped to the user.
func (*Service) ListDestinations ¶
func (s *Service) ListDestinations(ctx context.Context, userID uuid.UUID) ([]*models.BackupDestination, error)
ListDestinations returns all destinations owned by the user.
func (*Service) ListProviders ¶
ListProviders returns all registered providers, sorted by name.
func (*Service) ListRuns ¶
func (s *Service) ListRuns(ctx context.Context, destinationID, userID uuid.UUID, page, pageSize int) ([]*models.BackupRun, int, error)
ListRuns returns audit-log rows for one destination scoped to the user.
func (*Service) RunOnce ¶
RunOnce executes a single backup and records the result. It is safe to call concurrently; the second concurrent call against the same destination returns ErrConcurrentRun.
The high-level flow is:
- Look up & authorise the destination.
- Build the JSON payload.
- Optionally short-circuit ("skipped") if the payload hash matches the last successful run.
- Upload via the provider.
- Prune old objects to honour RetentionCount.
- Persist a BackupRun row and update the destination's status.
Errors at any step are recorded as a "failed" BackupRun with a sanitised message; the function returns the original error so callers can surface it.
func (*Service) SetScheduler ¶
SetScheduler attaches a scheduler to this service so that handler-side "destination updated" hints can propagate. Returning the scheduler also lets callers Start/Stop it during process lifecycle.
func (*Service) TestDestination ¶
func (s *Service) TestDestination(ctx context.Context, destinationID, userID uuid.UUID) (bool, string, error)
TestDestination decrypts the stored credentials and re-runs the provider's Validate routine. It also updates last_error / status to reflect the result so the UI's freshly-displayed status matches reality.
func (*Service) UpdateDestination ¶
func (s *Service) UpdateDestination(ctx context.Context, destinationID, userID uuid.UUID, in UpdateDestinationInput) (*models.BackupDestination, error)
UpdateDestination applies a partial update. Credentials are intentionally not modifiable here — to rotate, delete and recreate the destination.
type UpdateDestinationInput ¶
type UpdateDestinationInput struct {
DisplayName *string
Schedule *models.BackupSchedule
ScheduleHourUTC *int
ScheduleDayOfWeek *int
ScheduleDayOfMonth *int
RetentionCount *int
Enabled *bool
}
UpdateDestinationInput is the service-level input for partial updates. All pointer fields are optional.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package netguard restricts the network destinations the cloud-backup subsystem is allowed to connect to, mitigating server-side request forgery (SSRF).
|
Package netguard restricts the network destinations the cloud-backup subsystem is allowed to connect to, mitigating server-side request forgery (SSRF). |
|
Package provider defines the contract every cloud backup provider plugin must satisfy.
|
Package provider defines the contract every cloud backup provider plugin must satisfy. |
|
s3
Package s3 implements the cloud backup provider contract against Amazon S3 and S3-compatible object stores (MinIO, Backblaze B2, Cloudflare R2, Wasabi, etc.) using the minio-go client.
|
Package s3 implements the cloud backup provider contract against Amazon S3 and S3-compatible object stores (MinIO, Backblaze B2, Cloudflare R2, Wasabi, etc.) using the minio-go client. |
|
sftp
Package sftp implements the cloud backup provider contract against any SSH server with SFTP enabled (OpenSSH, Dropbear, atmoz/sftp, hosting providers offering SFTP-only accounts, etc.) using golang.org/x/crypto/ssh and github.com/pkg/sftp.
|
Package sftp implements the cloud backup provider contract against any SSH server with SFTP enabled (OpenSSH, Dropbear, atmoz/sftp, hosting providers offering SFTP-only accounts, etc.) using golang.org/x/crypto/ssh and github.com/pkg/sftp. |
|
webdav
Package webdav implements the cloud backup provider contract against any RFC 4918 compliant WebDAV server (Nextcloud, ownCloud, mailbox.org, Box, Apache mod_dav, …) using the studio-b12/gowebdav client.
|
Package webdav implements the cloud backup provider contract against any RFC 4918 compliant WebDAV server (Nextcloud, ownCloud, mailbox.org, Box, Apache mod_dav, …) using the studio-b12/gowebdav client. |