cloudbackup

package
v1.3.7 Latest Latest
Warning

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

Go to latest
Published: Sep 14, 2026 License: AGPL-3.0 Imports: 20 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
var (
	ErrDestinationNotFound = errors.New("backup destination not found")
	ErrProviderUnknown     = errors.New("unknown backup provider")
	ErrInvalidSchedule     = errors.New("invalid schedule")
	ErrUnauthorized        = errors.New("not authorized for this destination")
	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 ("application/gzip").
	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 CustomCurrencyRule added in v1.3.7

type CustomCurrencyRule struct {
	Name        string                        `json:"name"`
	Description *string                       `json:"description,omitempty"`
	Emoji       *string                       `json:"emoji,omitempty"`
	Definition  models.CustomCurrencyRuleBody `json:"definition"`
	Enabled     bool                          `json:"enabled"`
	Notify      bool                          `json:"notify"`
}

CustomCurrencyRule is the portable half of a user-authored currency rule. Sharing state (isShared, shareToken, importedFrom) is deliberately excluded: a share token is unique across the installation and belongs to the rule it was minted for, not to a copy restored elsewhere.

func NewCustomCurrencyRule added in v1.3.7

func NewCustomCurrencyRule(r *models.CustomCurrencyRule) CustomCurrencyRule

NewCustomCurrencyRule projects a stored rule onto its portable half.

type DefaultJSONBuilder

type DefaultJSONBuilder struct {
	Flights     *service.FlightService
	Aircraft    *service.AircraftService
	Licenses    *service.LicenseService
	Credentials *service.CredentialService
	ClassRating *service.ClassRatingService
	// Contacts, CustomCurrency and Notifications back the payload sections of
	// the same name. A nil service omits its section rather than failing the
	// backup.
	Contacts       *service.ContactService
	CustomCurrency *currency.CustomService
	Notifications  *service.NotificationService
	// AttachCrew is called with the flight slice before serialisation.
	// 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 per-resource services to produce a stable backup payload matching the ExportDataJSON wire format.

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".

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.

func (*DefaultJSONBuilder) Gather added in v1.3.7

func (b *DefaultJSONBuilder) Gather(ctx context.Context, userID uuid.UUID) (Payload, error)

Gather collects every section of a user's backup in canonical order. It is the single definition of what a backup contains: GET /exports/json writes the result directly, a cloud backup run gzips it.

type FlightBaseline added in v1.3.7

type FlightBaseline struct {
	BaselineDate        time.Time `json:"baselineDate"`
	TotalFlights        int       `json:"totalFlights"`
	TotalMinutes        int       `json:"totalMinutes"`
	PICMinutes          int       `json:"picMinutes"`
	SICMinutes          int       `json:"sicMinutes"`
	DualMinutes         int       `json:"dualMinutes"`
	DualGivenMinutes    int       `json:"dualGivenMinutes"`
	MultiPilotMinutes   int       `json:"multiPilotMinutes"`
	NightMinutes        int       `json:"nightMinutes"`
	IFRMinutes          int       `json:"ifrMinutes"`
	SoloMinutes         int       `json:"soloMinutes"`
	CrossCountryMinutes int       `json:"crossCountryMinutes"`
	PICUSMinutes        int       `json:"picusMinutes"`
	SPICMinutes         int       `json:"spicMinutes"`
	ExaminerMinutes     int       `json:"examinerMinutes"`
	ReliefMinutes       int       `json:"reliefMinutes"`
	LandingsDay         int       `json:"landingsDay"`
	LandingsNight       int       `json:"landingsNight"`
	Notes               *string   `json:"notes,omitempty"`
}

FlightBaseline is the portable half of a user's carried-forward hours snapshot. models.FlightBaseline carries no JSON tags, so the wire shape is declared here.

func NewFlightBaseline added in v1.3.7

func NewFlightBaseline(b *models.FlightBaseline) *FlightBaseline

NewFlightBaseline projects a stored baseline onto its portable half.

func (FlightBaseline) ToModel added in v1.3.7

func (b FlightBaseline) ToModel(userID uuid.UUID) *models.FlightBaseline

ToModel rebuilds a storable baseline owned by the given user.

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 LicenseWithRatings added in v1.3.7

type LicenseWithRatings struct {
	License      *models.License       `json:"license"`
	ClassRatings []*models.ClassRating `json:"classRatings"`
}

LicenseWithRatings pairs a licence with its class ratings so a restore can wire ratings to freshly minted licence IDs.

type NotificationPreferences added in v1.3.7

type NotificationPreferences struct {
	EmailEnabled      bool     `json:"emailEnabled"`
	EnabledCategories []string `json:"enabledCategories"`
	WarningDays       []int64  `json:"warningDays"`
	CheckHour         int      `json:"checkHour"`
}

NotificationPreferences is the portable half of a user's notification settings; identifiers and timestamps are reassigned on restore.

func NewNotificationPreferences added in v1.3.7

func NewNotificationPreferences(p *models.NotificationPreferences) *NotificationPreferences

NewNotificationPreferences projects stored preferences onto their portable half.

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 Payload added in v1.3.7

type Payload struct {
	ExportedAt          string               `json:"exportedAt"`
	Version             string               `json:"version"`
	Format              string               `json:"format"`
	Flights             []*models.Flight     `json:"flights"`
	Aircraft            []*models.Aircraft   `json:"aircraft"`
	Licenses            []LicenseWithRatings `json:"licenses"`
	Credentials         []*models.Credential `json:"credentials"`
	Contacts            []*models.Contact    `json:"contacts"`
	CustomCurrencyRules []CustomCurrencyRule `json:"customCurrencyRules"`
	// NotificationPreferences and FlightBaseline are single-row settings and
	// are omitted when the user has none.
	NotificationPreferences *NotificationPreferences `json:"notificationPreferences,omitempty"`
	FlightBaseline          *FlightBaseline          `json:"flightBaseline,omitempty"`
}

Payload is the wire layout of one backup, shared by GET /exports/json, POST /imports/json and every cloud backup run. Field order matches the original ExportDataJSON output; new sections are appended.

Every section holds data a user owns and would expect to survive moving to another server. Sections added here must be restored by ImportDataJSON in the same change.

type RunRequest

type RunRequest struct {
	DestinationID uuid.UUID
	// UserID must match the destination's owner; the runner re-checks it.
	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.

  • One scheduler per API process; runLocks guarantees at-most-once per destination per process.
  • Ticks every minute, with a small delay on the first tick.
  • Per-user concurrency cap: at most one run per user can be in flight across all of their destinations.

func NewScheduler

func NewScheduler(svc *Service, tick time.Duration, logger *log.Logger) *Scheduler

NewScheduler constructs a scheduler attached to svc. tick is the period of the loop; pass 0 for the default 60s.

func (*Scheduler) Start

func (s *Scheduler) Start(ctx context.Context)

Start launches the scheduler loop in a goroutine. Cancel the supplied context (or call Stop) to terminate.

func (*Scheduler) Stop

func (s *Scheduler) Stop()

Stop signals the loop to exit and waits for it to drain in-flight ticks.

type Service

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

Service is the cloud backup application service. Construct via New.

func New

func New(opts Options) (*Service, error)

New constructs a Service. Returns an error if any required dependency is missing.

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

func (s *Service) DeleteDestination(ctx context.Context, destinationID, userID uuid.UUID) error

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) GetRun

func (s *Service) GetRun(ctx context.Context, runID, userID uuid.UUID) (*models.BackupRun, error)

GetRun returns a single run record, 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

func (s *Service) ListProviders() []provider.Provider

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) Provider

func (s *Service) Provider(name string) (provider.Provider, error)

Provider returns the registered provider by name, or ErrProviderUnknown.

func (*Service) RunOnce

func (s *Service) RunOnce(ctx context.Context, req RunRequest) (*models.BackupRun, error)

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:

  1. Look up & authorise the destination.
  2. Build the JSON payload.
  3. Optionally short-circuit ("skipped") if the payload hash matches the last successful run.
  4. Upload via the provider.
  5. Prune old objects to honour RetentionCount.
  6. 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.

func (*Service) SetScheduler

func (s *Service) SetScheduler(sc *Scheduler)

SetScheduler attaches a scheduler to this service.

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, updating last_error / status to reflect the result.

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 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 (SSRF mitigation).
Package netguard restricts the network destinations the cloud-backup subsystem is allowed to connect to (SSRF mitigation).
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.

Jump to

Keyboard shortcuts

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