service

package
v0.4.82 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: Apache-2.0 Imports: 98 Imported by: 0

Documentation

Overview

@Author: LinkLeong link@icewhale.com

*@Date: 2022-07-12 09:48:56
*@LastEditors: LinkLeong
*@LastEditTime: 2022-09-02 22:10:05
*@FilePath: /CasaOS/service/service.go
*@Description:
*@Website: https://www.casaos.io
*Copyright (c) 2022 by icewhale, All Rights Reserved.

Index

Constants

View Source
const (
	GitWebhookChecking  = "checking"
	GitWebhookCoalesced = "coalesced"
	GitWebhookQueued    = "queued"
	// GitWebhookHeld is a push that started no check: an operating system upgrade is open.
	GitWebhookHeld    = "held"
	GitWebhookIgnored = "ignored"
	GitWebhookPong    = "pong"
)

Answers a forge is given: 200 for GitWebhookPong, 202 for the others.

View Source
const BackupFormatVersion = 1

BackupFormatVersion is bumped when the layout changes in a way a reader must know about.

View Source
const GitComposeExample = `services:
  app:
    build: .
    ports:
      - "8080:8080"
    restart: unless-stopped
`

GitComposeExample is what a repository without a compose file is told to add.

View Source
const ManifestFileName = "manifest.json"

ManifestFileName sits at the root of a backup.

View Source
const SystemBackupName = "casaos-system"

SystemBackupName is the app name the box's own backups are filed under.

Variables

View Source
var (
	ErrNotAppStore             = fmt.Errorf("not an appstore")
	ErrDefaultAppStoreNotFound = fmt.Errorf("default appstore not found")
)
View Source
var (
	ErrRestoreNeedsDestination = errors.New("a restore needs a destination")
	ErrRestoreNeedsApp         = errors.New("a restore needs an app")
	ErrRestoreNeedsStamp       = errors.New("a restore needs the backup to restore from")
)
View Source
var (
	ErrComposeAppNotFound                  = fmt.Errorf("compose app not found")
	ErrComposeAppNotMatch                  = fmt.Errorf("compose app not match")
	ErrComposeExtensionNameXCasaOSNotFound = fmt.Errorf("extension `%s` not found", common.ComposeExtensionNameXCasaOS)
	ErrComposeFileNotFound                 = fmt.Errorf("compose file not found")
	ErrInvalidComposeAppStatus             = fmt.Errorf("invalid compose app status")
	ErrMainAppNotFound                     = fmt.Errorf("main app not found")
	ErrNotFoundInAppStore                  = fmt.Errorf("not found in app store")
	ErrSetStoreAppID                       = fmt.Errorf("failed to set store app ID")
	ErrStoreInfoNotFound                   = fmt.Errorf("store info not found")
	ErrMainServiceNotSpecified             = fmt.Errorf("main service not been specified")
)
View Source
var (
	// ErrGitAppNameTaken is answered 409.
	ErrGitAppNameTaken = errors.New("the name is already used by an app, a compose project or an app deployed from git")
	// ErrGitAppDeployed is answered 409: a deployed app goes with its uninstall.
	ErrGitAppDeployed = errors.New("the app has been deployed: uninstall it instead")
)
View Source
var (
	// ErrGitWebhookNotFound is an unknown app and a webhook turned off alike, so that the
	// route does not tell which apps exist.
	ErrGitWebhookNotFound  = errors.New("not found")
	ErrGitWebhookSignature = errors.New("invalid signature")
	ErrGitWebhookTooLarge  = errors.New("the body is over 5 MiB")
)

Refusals of a delivery, answered 404, 401 and 413.

View Source
var BackupHoldPath = "/var/lib/casaos/backups/holding.json"

BackupHoldPath records which apps are being held still, and which containers were running when they were.

View Source
var BackupLogPath = "/var/lib/casaos/backups/runs.json"

BackupLogPath is where the record lives. A variable so a test can put it somewhere of its own.

View Source
var BackupSchedulePath = "/var/lib/casaos/backups/schedules.json"

BackupSchedulePath is where the standing arrangements live.

View Source
var ErrAppStoreSourceExists = fmt.Errorf("appstore source already exists")
View Source
var ErrGitAppNotFound = errors.New("not an app deployed from a git repository")

ErrGitAppNotFound is an app that is neither registered nor adoptable.

View Source
var ErrGitNoComposeFile = errors.New("the repository has no compose.yaml, compose.yml, docker-compose.yaml or docker-compose.yml at its root, and the app's .env names none in COMPOSE_FILE")

ErrGitNoComposeFile is a repository `docker compose up` would refuse at its root.

View Source
var ErrUpdateAllRunning = errors.New("an update of every app is already running")

ErrUpdateAllRunning is answered to a second run asked for while one is going.

View Source
var (
	NewVersionApp map[string]string // TODO - make use of NewVersionApp map
)
View Source
var NoUpdateBlacklist = []string{
	"johnguan/stable-diffusion-webui:latest",
}

the patch is have no choice the digest compare is not work for these images I don't know why, but I have to do this I will remove the patch after I rewrite the digest compare

View Source
var ResticCheckPath = "/var/lib/casaos/backups/restic-checks.json"

ResticCheckPath is where the last check of each destination is written down.

View Source
var StatsDir = "/var/lib/casaos/stats/app_management"

StatsDir is where the sampler keeps its history and its alerts.

Functions

func ApiService

func ApiService() (api.Compose, client.APIClient, error)

func ApplyDataPath added in v0.4.82

func ApplyDataPath(root string) string

ApplyDataPath points stats, git-app state, backups and image-update cache at root. Empty root is /var/lib/casaos. gitAppsDataRoot and os-upgrade/busy stay on the box paths. restic.KeyDir follows root (same path as the packaged default). restic.CacheDir stays /var/cache/casaos/restic unless root is not the default.

func ApplyGitAppsDataRoot added in v0.4.82

func ApplyGitAppsDataRoot(v string)

func ApplyRetention added in v0.4.50

func ApplyRetention(ctx context.Context, destination, app string, keep int, runner retentionRunner)

ApplyRetention deletes the runs of `app` at `destination` beyond the last `keep`. Zero or less keeps everything. Scheduled runs and runs somebody asked for share the pool: a retention counts backups, not reasons.

func Begin added in v0.4.57

func Begin(app, kind string) (end func(), err error)

Begin claims app for an operation of the given kind, or refuses with ErrAppBusy. end releases it; calling it more than once is harmless.

func BuildCatalog

func BuildCatalog(storeRoot string) (map[string]*ComposeApp, error)

func CheckGitApps added in v0.4.57

func CheckGitApps(ctx context.Context)

CheckGitApps checks every registered git app, one after another, and starts what the automatic rebuild may deploy. An app busy with another operation is checked next time.

Nothing is checked while an operating system upgrade is open, looked at before each app rather than once: a tick is as long as its checks, and the upgrade can open between two of them. A check only reads, but with the git binary the upgrade replaces, and what it would write down is an error about that.

func CheckImageUpdates

func CheckImageUpdates(ctx context.Context) (*codegen.ImageUpdateCheckResult, error)

CheckImageUpdates asks every installed app's registry what its tags point at now and compares that with the copy on disk.

This is what sees an app that came from no app store. The store's upgradable list compares tags against a catalogue, so it is blind to an app nobody publishes a catalogue entry for, and equally blind to a tag that has been republished under the same name -- which is what `latest` does every time.

func CheckImageUpdatesForApp

func CheckImageUpdatesForApp(ctx context.Context, composeApp *ComposeApp) error

CheckImageUpdatesForApp answers for one app and records it, leaving every other app's answer alone.

This is what makes the per-app button honest. It is called `Check then update`, and for an app that came from no store there is nothing else to check: without this it would answer from whatever the last sweep happened to leave in the cache, or from nothing at all if no sweep has run.

func CurrentUpdateAllRun added in v0.4.52

func CurrentUpdateAllRun() *codegen.ComposeAppUpdateRun

CurrentUpdateAllRun is a copy of the run in progress or of the last one, or nil when none has run since this service started.

func DanglingImages

func DanglingImages(ctx context.Context) (*codegen.DanglingImages, error)

DanglingImages reports what PruneDanglingImages would free, without freeing it.

func DeleteGitApp added in v0.4.57

func DeleteGitApp(ctx context.Context, name string) error

DeleteGitApp removes a registered app no deployment has succeeded for: what a failed first deployment left (its containers, networks and images), the clone of a created app, its state and its secrets.

func GenerateYAMLFromComposeApp

func GenerateYAMLFromComposeApp(compose ComposeApp) ([]byte, error)

GenerateYAMLFromComposeApp writes an app loaded by the settings pipeline (ComposeAppFromSettingsYAML, LoadComposeAppForEditing) back to compose text; its strings are already escaped for compose.

func GitAppGrid added in v0.4.57

func GitAppGrid(app string) *codegen.GitAppGrid

GitAppGrid is what the app card shows of a registered git app, nil for any other app. It reads the state file alone: the grid never waits on git or on a registry.

func GitAppNames added in v0.4.57

func GitAppNames() ([]string, error)

GitAppNames is every registered git app, sorted.

func GitAppsWithoutContainers added in v0.4.57

func GitAppsWithoutContainers(listed []string) []codegen.WebAppGridItem

GitAppsWithoutContainers are the grid items of the registered git apps missing from the compose list, which only knows apps that have containers: never deployed, or left without one by a failed first deployment. The owner still reaches their Repository tab.

func ImageUpdateAvailable

func ImageUpdateAvailable(appName string) *bool

ImageUpdateAvailable reports whether an update is on offer for an app, or nil if nothing has looked since this service started. Nil is not "up to date": the dashboard shows a badge for true and nothing for either other case, and conflating them would claim an app is current when nobody has looked.

func IsDue added in v0.4.37

func IsDue(schedule BackupSchedule, now time.Time) (bool, error)

IsDue reports whether this schedule has a slot it has not run yet.

func LoadCategoryMap

func LoadCategoryMap(storeRoot string) map[string]codegen.CategoryInfo

func LoadImageUpdates

func LoadImageUpdates()

LoadImageUpdates restores what the last run found, so the dashboard's first paint after a restart shows the badges it showed before it.

func LoadRecommend

func LoadRecommend(storeRoot string) []string

func NewResticChecker added in v0.4.82

func NewResticChecker() resticChecker

NewResticChecker is what the tick drives in production.

func NewScheduledBackupRunner added in v0.4.37

func NewScheduledBackupRunner() *scheduledBackupRunner

NewScheduledBackupRunner is what the scheduler drives in production.

func ParseEnvFile

func ParseEnvFile(text []byte) (map[string]string, error)

ParseEnvFile validates `.env` text as the runtime will read it; a reserved key is refused rather than ignored.

func PlanUpdateAll added in v0.4.52

func PlanUpdateAll(apps map[string]*ComposeApp, available func(*ComposeApp) bool, storeCompose func(string) (*ComposeApp, error)) []codegen.ComposeAppUpdatePlanApp

PlanUpdateAll says what updating every app at once would do, from what is already known: the catalogue, and the last image check. It asks no registry; the caller does first, if it wants the answer fresh. `available` is the update button's own answer for an app, `storeCompose` the catalogue's compose file for a store app.

func PruneDanglingImages

func PruneDanglingImages(ctx context.Context) (*codegen.DanglingImages, error)

PruneDanglingImages deletes them and reports what the daemon says it actually freed, which can be less than DanglingImages estimated if something claimed a layer since.

func PublishEventWrapper

func PublishEventWrapper(ctx context.Context, eventType message_bus.EventType, properties map[string]string)

func ReceiveGitWebhook added in v0.4.61

func ReceiveGitWebhook(app string, header http.Header, body io.Reader) (string, error)

ReceiveGitWebhook is a forge's delivery for app: the answer the forge is given, or one of the refusals. A refused delivery writes nothing, and no body, no header's value and no signature reaches the log.

func RecordBackupRun added in v0.4.37

func RecordBackupRun(record BackupRunRecord) error

RecordBackupRun appends one run and keeps the file to its last few hundred.

A record that cannot be written is an error the caller hears about rather than a silence: losing the run is bad, and not knowing the log is broken is worse.

func RecoverGitApps added in v0.4.57

func RecoverGitApps(ctx context.Context)

RecoverGitApps runs once, at startup. An operation the process did not live to finish is recorded as interrupted and cleared, and nothing is retried: the commit joins those the automatic rebuild leaves alone. The worktrees of builds and the clones cut short are removed.

func RecoverHeldApps added in v0.4.38

func RecoverHeldApps(docker containerStopStarter)

RecoverHeldApps starts back anything a backup stopped and never restarted.

Run at startup. Starting a container that is already running is what StartContainer does nothing about, so this is safe to run when the note is merely stale -- and a stale note is a far better problem than an app nobody noticed was off.

func ResticChecks added in v0.4.82

func ResticChecks() (map[string]ResticCheck, error)

ResticChecks reads what is known of the last checks, by destination.

func RootFor added in v0.4.37

func RootFor(app, stamp string) string

RootFor is where one run of a backup goes on a destination.

The app first, then the moment, so a destination holding several apps stays readable in a file browser and one app's history sits together. `stamp` is passed in rather than taken from the clock here: a plan that reads the clock cannot be tested, and the caller already knows when the run started.

func RunDueBackups added in v0.4.37

func RunDueBackups(ctx context.Context, now time.Time, runner backupRunner)

RunDueBackups runs everything that has an unrun slot, records each one, and applies retention.

It returns rather than loops, and takes `now`, so the whole of the decision can be exercised in a test. main drives it from the crontab that is already there,

which is the only part that reads a clock.

A schedule's LastRun is written whether the run SUCCEEDED or not. Retrying a failing backup every minute until the disk fills is worse than waiting for tomorrow's slot, and the failure is in the log where somebody can see it. It is written when that backup ends, in the file as it is then, not for all of them when the tick does: a tick can last hours, the schedules can be saved meanwhile, and a process that stops half way owes only what it did not do.

One tick at a time. Cron starts one every minute whether the last is over or not, and a tick that finds another running leaves its backups to it: what falls due meanwhile is found by the tick after that one ends. Otherwise the backups owed after a hold, which can take longer than a minute, would be started again by every tick until they were recorded.

While an operating system upgrade is open nothing runs and nothing is recorded: the slot stays unrun, so what was skipped runs once when the upgrade is closed. The tick says so in one line.

func RunDueResticChecks added in v0.4.82

func RunDueResticChecks(ctx context.Context, now time.Time, checker resticChecker)

RunDueResticChecks checks the destinations kept with restic that are due: never checked, last checked a week ago, or a day ago when that check found a fault.

Like the backups it waits while an operating system upgrade is open, and it checks one destination at a time. A tick that finds another running leaves the work to it.

func RunsToPurge added in v0.4.37

func RunsToPurge(stamps []string, keep int) []string

RunsToPurge names the runs that fall outside a retention of `keep`.

Runs are named by their stamp, which sorts, so the newest are simply the last. `keep` of zero or less keeps everything -- deleting backups is not something to do because a field defaulted, and a schedule saved without a retention means "no policy", not "keep none".

One run is always kept whatever the arithmetic says. A retention that empties a destination is not a retention.

func SaveBackupSchedules added in v0.4.37

func SaveBackupSchedules(schedules []BackupSchedule) error

SaveBackupSchedules replaces the lot.

func SetStats added in v0.4.70

func SetStats(s *dockerstats.Sampler)

SetStats puts a sampler in place of the running one, for tests.

func SplitInstallEnv added in v0.4.74

func SplitInstallEnv(text []byte) ([]byte, []byte, error)

SplitInstallEnv takes the .env out of compose text (x-casaos-env) and returns the text without it. Text without the key comes back unchanged, byte for byte.

func Standardize

func Standardize(text string) string

func StartResticCheck added in v0.4.82

func StartResticCheck(ctx context.Context, now time.Time, checker resticChecker, destination string) (<-chan struct{}, bool)

StartResticCheck checks one destination now, in the background, and says whether it started: not while another check runs, since each one reads the whole repository. The channel closes when the check is over.

func StartStats added in v0.4.70

func StartStats(ctx context.Context) error

StartStats opens the sampler and runs it until ctx ends or StopStats; it does nothing when the sampler is already running. An error leaves it not running: the stats routes answer 503 and the rest of the service is unaffected.

func StartUpdateAll added in v0.4.52

func StartUpdateAll(ids []string, update func(ctx context.Context, id string) (codegen.ComposeAppUpdateRunAppState, string)) (*codegen.ComposeAppUpdateRun, error)

StartUpdateAll runs the apps named, in that order, one after another, and returns the run as it starts. `update` does one app and says what became of it.

func Stats added in v0.4.70

func Stats() *dockerstats.Sampler

Stats is the running sampler, nil when it is not running.

func StopStats added in v0.4.70

func StopStats()

StopStats stops the sampler and closes its files; it does nothing when the sampler is not running.

func StoreRoot

func StoreRoot(workdir string) (string, error)

func Systemd added in v0.4.45

func Systemd() unitControl

Systemd is the real thing.

func UpdateOneForRun added in v0.4.52

func UpdateOneForRun(ctx context.Context, id string) (codegen.ComposeAppUpdateRunAppState, string)

UpdateOneForRun is what the run does to one app: what the update button does, the check its name promises included, waited for; and the reason, when nothing happens.

func VolumesToRemove added in v0.4.38

func VolumesToRemove(volumes []ContainerVolume, requested []string) (remove []string, refused map[string]string)

VolumesToRemove filters what the caller asked to delete down to what may be.

The caller's list is never trusted: a screen drawn a minute ago is a screen that has been overtaken, and by the time somebody presses the button another container may have started using one of these. Everything is re-decided here against what the daemon says now.

A name the container does not mount at all is refused outright rather than passed through -- that is the shape of a request that would delete somebody else's volume through this container's door.

func WatchDocker added in v0.4.67

func WatchDocker(ctx context.Context)

WatchDocker follows Docker's events for the apps' containers until ctx ends, and publishes what they mean. A stream that breaks -- Docker restarting, or not up yet -- is followed again after a pause that doubles up to longestPause.

Types

type App

type App types.ServiceConfig

func (*App) StoreInfo

func (a *App) StoreInfo() (codegen.AppStoreInfo, error)

type AppOperation added in v0.4.65

type AppOperation struct {
	App  string
	Kind string
}

AppOperation is one app something runs on, and the kind of operation it is.

func RunningOperations added in v0.4.65

func RunningOperations() []AppOperation

RunningOperations lists what runs on the apps right now: what Begin holds, and what markInProgress says runs without a hold. One entry per app, the hold's kind over a mark's, sorted by app so that the same operations always read the same. Empty when nothing runs: the core asks before it updates the box by itself, and waits while anything is listed.

type AppStore

type AppStore interface {
	Catalog() (map[string]*ComposeApp, error)
	CategoryMap() (map[string]codegen.CategoryInfo, error)
	ComposeApp(id string) (*ComposeApp, error)
	Recommend() ([]string, error)
	UpdateCatalog() error
	WorkDir() (string, error)
}

func AppStoreByURL

func AppStoreByURL(appstoreURL string) (AppStore, error)

func NewDefaultAppStore

func NewDefaultAppStore() (AppStore, error)

type AppStoreManagement

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

func NewAppStoreManagement

func NewAppStoreManagement() *AppStoreManagement

func (*AppStoreManagement) AppStoreList

func (a *AppStoreManagement) AppStoreList() []codegen.AppStoreMetadata

func (*AppStoreManagement) AppStoreMap

func (a *AppStoreManagement) AppStoreMap() (map[string]AppStore, error)

func (*AppStoreManagement) Catalog

func (a *AppStoreManagement) Catalog() (map[string]*ComposeApp, error)

func (*AppStoreManagement) CategoryMap

func (a *AppStoreManagement) CategoryMap() (map[string]codegen.CategoryInfo, error)

AppStore interface

func (*AppStoreManagement) ChangeGlobal

func (a *AppStoreManagement) ChangeGlobal(key string, value string) error

func (*AppStoreManagement) ComposeApp

func (a *AppStoreManagement) ComposeApp(id string) (*ComposeApp, error)

func (*AppStoreManagement) DeleteGlobal

func (a *AppStoreManagement) DeleteGlobal(key string) error

func (*AppStoreManagement) FinishUpgrade

func (a *AppStoreManagement) FinishUpgrade(appID string)

func (*AppStoreManagement) ForgetUpgradable

func (a *AppStoreManagement) ForgetUpgradable(appID string)

ForgetUpgradable drops one app's cached answer so the next question is asked afresh. An hour of cache is right for a list rendered in the background and wrong immediately after someone pressed a button with the word "check" on it.

func (*AppStoreManagement) IsUpdateAvailable

func (a *AppStoreManagement) IsUpdateAvailable(composeApp *ComposeApp) bool

func (*AppStoreManagement) IsUpdateAvailableWith

func (a *AppStoreManagement) IsUpdateAvailableWith(composeApp *ComposeApp, storeComposeApp *ComposeApp) (bool, string, error)

IsUpdateAvailableWith decides an update SERVICE BY SERVICE, because that is what an update writes: every service the catalogue names takes the catalogue's image.

It is on offer when no service would go backwards and something would actually change -- the catalogue names an image this app does not run, or the registries say an image it does run has moved. Reading the MAIN service's tag alone made a catalogue that bumps only a sidecar invisible, which is what froze the multi-service stacks; and demanding every image be identical before believing the registries threw their answer away over any difference at all, including a service the owner added by hand.

It answers with a reason whenever it refuses, because "no update, and nothing on screen saying why" is indistinguishable from a broken button.

func (*AppStoreManagement) IsUpdating

func (a *AppStoreManagement) IsUpdating(appID string) bool

func (*AppStoreManagement) OnAppStoreRegister

func (a *AppStoreManagement) OnAppStoreRegister(fn func(string) error)

func (*AppStoreManagement) OnAppStoreUnregister

func (a *AppStoreManagement) OnAppStoreUnregister(fn func(string) error)

func (*AppStoreManagement) Recommend

func (a *AppStoreManagement) Recommend() ([]string, error)

func (*AppStoreManagement) RegisterAppStore

func (a *AppStoreManagement) RegisterAppStore(ctx context.Context, appstoreURL string, callbacks ...func(*codegen.AppStoreMetadata)) error

func (*AppStoreManagement) RegisterAppStoreSync

func (a *AppStoreManagement) RegisterAppStoreSync(ctx context.Context, appstoreURL string, callbacks ...func(*codegen.AppStoreMetadata)) error

TODO: refactor the function and above function

func (*AppStoreManagement) StartUpgrade

func (a *AppStoreManagement) StartUpgrade(appID string)

func (*AppStoreManagement) UnregisterAppStore

func (a *AppStoreManagement) UnregisterAppStore(appStoreID uint) error

func (*AppStoreManagement) UpdateAvailability

func (a *AppStoreManagement) UpdateAvailability(composeApp *ComposeApp) (bool, string)

UpdateAvailability is IsUpdateAvailable plus why not. The reason is empty when an update is on offer and when the app is simply current; it is filled only when the catalogue holds something this app must not be given -- an older image for one of its services, a service an update cannot create, a pair of tags nothing can order.

func (*AppStoreManagement) UpdateCatalog

func (a *AppStoreManagement) UpdateCatalog() error

func (*AppStoreManagement) WorkDir

func (a *AppStoreManagement) WorkDir() (string, error)

type BackupCopier added in v0.4.37

type BackupCopier interface {
	CopyDirectory(ctx context.Context, source, destinationName, destinationPath string) error
	CopyFile(ctx context.Context, source, destinationName, destinationPath string) error
}

BackupCopier is the destination, as this needs it. *rclone.Client is one.

type BackupEntry added in v0.4.37

type BackupEntry struct {
	Kind BackupKind `json:"kind"`
	// Path on the host. Empty for an entry that was skipped before it could be
	// resolved, and for a named volume until Docker has been asked where it lives.
	Path string `json:"path"`
	// Service that mounts it, empty for the app's own files
	Service string `json:"service,omitempty"`
	// Target inside the container, for a volume or a bind
	Target string `json:"target,omitempty"`
	// Volume name as the compose file writes it, for a named volume
	Volume string `json:"volume,omitempty"`
	// Skip is why this is not going in the backup. Empty means it is.
	Skip string `json:"skip,omitempty"`
}

BackupEntry is one thing on disk that belongs to an app -- or one that looked like it did and does not, which is worth saying rather than dropping.

func SystemBackupInventory added in v0.4.45

func SystemBackupInventory(exists func(string) bool) []BackupEntry

SystemBackupInventory lists what the box is, as backup entries.

func (BackupEntry) IsIncluded added in v0.4.37

func (e BackupEntry) IsIncluded() bool

IsIncluded reports whether this entry is part of the backup.

type BackupGit added in v0.4.57

type BackupGit struct {
	Remote string `json:"remote"`
	Branch string `json:"branch"`
	Commit string `json:"commit"`
	// An app that follows tags: the mode, the tag the commit was deployed as, and the tag
	// filter. Omitted for a branch app, whose origin stays what older versions wrote.
	Follow      string `json:"follow,omitempty"`
	Tag         string `json:"tag,omitempty"`
	TagPattern  string `json:"tag_pattern,omitempty"`
	Prereleases bool   `json:"prereleases,omitempty"`
}

BackupGit is a git app's origin as a backup records it. Never a key or a token.

type BackupHeldApp added in v0.4.43

type BackupHeldApp struct {
	App string `json:"app"`
	// Installed says whether this box runs the app now. A restore of one that is
	// not installs it first, from the backup's own compose file, which is worth
	// saying before the button is pressed.
	Installed bool `json:"installed"`
	// Stamps of its runs, newest first.
	Stamps []string `json:"stamps"`
}

BackupHeldApp is one app a destination holds backups of.

func DestinationContents added in v0.4.43

func DestinationContents(_ context.Context, lister backupLister, destination string, installed map[string]bool) ([]BackupHeldApp, error)

DestinationContents lists the apps a destination holds backups of, and their runs, newest first. installed is what this box runs now.

type BackupKind added in v0.4.37

type BackupKind string

BackupKind says why a path is in the list, which is also what a restore needs to know to put it back.

const (
	// BackupKindCompose is the app's docker-compose.yml
	BackupKindCompose BackupKind = "compose"
	// BackupKindEnv is the .env beside it
	BackupKindEnv BackupKind = "env"
	// BackupKindVolume is a named volume, which Docker keeps somewhere of its own
	BackupKindVolume BackupKind = "volume"
	// BackupKindBind is a host path a service mounts directly
	BackupKindBind BackupKind = "bind"
)

type BackupManifest added in v0.4.37

type BackupManifest struct {
	// FormatVersion so a later reader knows what it is looking at. A backup outlives
	// the code that wrote it, which is the whole point of one.
	FormatVersion int    `json:"format_version"`
	App           string `json:"app"`
	// Operations, in the order they were planned
	Operations []BackupOperation `json:"operations"`
	// Skipped says what was deliberately left out, with the reason, so the person
	// restoring finds out here rather than by noticing something missing later.
	Skipped []BackupEntry `json:"skipped,omitempty"`
	// ContainersStopped records whether the app was held still while this was
	// copied. A backup taken from a running database may not restore, and the only
	// thing worse than knowing that is not knowing it.
	ContainersStopped bool `json:"containers_stopped"`
	// Git is where the code of an app deployed from its repository comes from. Optional, so
	// the format version stays: an older reader ignores it.
	Git *BackupGit `json:"git,omitempty"`
}

BackupManifest is written at the root of every backup, and is the only thing a restore needs to read.

func BackupOnDemand added in v0.4.39

func BackupOnDemand(ctx context.Context, app *ComposeApp, docker backupDocker, copier BackupCopier, opts BackupOptions) (BackupManifest, error)

BackupOnDemand runs one backup somebody asked for, and records it.

func PlanBackup added in v0.4.37

func PlanBackup(app string, inventory []BackupEntry, volumeMountpoints map[string]string) BackupManifest

PlanBackup turns an app's inventory into the copies that would make it.

`volumeMountpoints` maps a compose volume name to where Docker actually keeps it. A volume with no entry is one the daemon could not place, and it is moved to the skipped list rather than copied from a guessed path -- a guess that happens to name an empty folder produces a backup that reports success and restores nothing.

func PlanSystemBackup added in v0.4.45

func PlanSystemBackup(exists func(string) bool) BackupManifest

PlanSystemBackup is the plan for the box, worked out from what is here now.

func RunBackup added in v0.4.37

func RunBackup(ctx context.Context, app *ComposeApp, docker backupDocker, copier BackupCopier, opts BackupOptions) (BackupManifest, error)

RunBackup copies an app to a destination and returns the manifest it wrote.

The manifest goes last, after everything it describes. A run interrupted half-way leaves files and no manifest, which reads as what it is -- an incomplete backup -- rather than as a complete one missing its contents.

func RunSystemBackup added in v0.4.45

func RunSystemBackup(ctx context.Context, copier BackupCopier, units unitControl, opts SystemBackupOptions) (BackupManifest, error)

RunSystemBackup copies the box's own state to a destination.

func SystemBackupOnDemand added in v0.4.45

func SystemBackupOnDemand(ctx context.Context, copier BackupCopier, units unitControl, opts SystemBackupOptions) (BackupManifest, error)

SystemBackupOnDemand runs one backup of the box and records it.

func (*BackupManifest) MarkBindFiles added in v0.4.37

func (m *BackupManifest) MarkBindFiles(isFile func(path string) bool)

MarkBindFiles corrects the operations whose source turned out to be a single file rather than a folder.

A bind mounts either, and the compose file does not say which -- `./php.ini` and `./config` look identical until something checks the disk. rclone needs to be told: a file copied as though it were a directory copies its whole parent, which on `/DATA/photos/one.jpg` means the entire photo library.

`isFile` is passed in rather than called here so the planner stays testable without a filesystem.

func (*BackupManifest) TotalBySource added in v0.4.37

func (m *BackupManifest) TotalBySource() []string

TotalBySource is what a plan addresses, deduplicated and sorted: two services mounting the same folder is one thing to copy, not two.

type BackupOperation added in v0.4.37

type BackupOperation struct {
	Kind BackupKind `json:"kind"`
	// Source on this host
	Source string `json:"source"`
	// Destination relative to the root of this backup, always with forward slashes
	Destination string `json:"destination"`
	// Directory is true for a folder, false for a single file. rclone copies the
	// two with different calls, and getting it wrong copies a file's parent.
	Directory bool `json:"directory"`
	// Volume is the compose name of the volume this came from, when it came from one
	Volume string `json:"volume,omitempty"`
	// Service that mounted it, and where
	Service string `json:"service,omitempty"`
	Target  string `json:"target,omitempty"`
}

BackupOperation is one copy: one host path to one place under the backup root.

type BackupOptions added in v0.4.37

type BackupOptions struct {
	// Destination is the name of an rclone remote this project manages
	Destination string
	// Stamp names this run inside the destination. Passed in, not read from a
	// clock, so a caller can name a run and find it again.
	Stamp string
	// HoldStill stops the app's containers for the length of the copy. Off means a
	// copy taken from a running app, which for anything holding a database may not
	// restore -- the manifest records which was done.
	HoldStill bool
	// Containers answers which containers the app has, for HoldStill. Nil means ask
	// the app, which is what production does; a test supplies its own so that
	// exercising the hold does not require a daemon -- and does not leave the
	// daemon's client goroutines behind for the next test to trip over.
	Containers func(context.Context) (map[string][]codegen.ContainerSummary, error)
}

BackupOptions is one run.

type BackupRestorer added in v0.4.41

type BackupRestorer interface {
	RestoreDirectory(ctx context.Context, destinationName, remotePath, hostPath string) error
	RestoreFile(ctx context.Context, destinationName, remotePath, hostPath string) error
	Fetch(ctx context.Context, destinationName, remotePath string) ([]byte, error)
}

BackupRestorer is the half of the rclone client a restore needs.

type BackupRunRecord added in v0.4.37

type BackupRunRecord struct {
	App         string    `json:"app"`
	Destination string    `json:"destination"`
	Stamp       string    `json:"stamp"`
	StartedAt   time.Time `json:"started_at"`
	FinishedAt  time.Time `json:"finished_at"`
	// Scheduled tells a run that went off by itself from one somebody asked for.
	Scheduled bool `json:"scheduled"`
	// ContainersStopped says whether the app was held still, which decides whether
	// anything holding a database can be trusted to restore.
	ContainersStopped bool `json:"containers_stopped"`
	Copied            int  `json:"copied"`
	SkippedCount      int  `json:"skipped_count"`
	// Error is empty when the run succeeded.
	Error string `json:"error,omitempty"`
	// Restore marks a run that put the app back from a backup rather than one
	// that took a backup. Same log, because a person looking for what happened
	// to an app wants both in one place, newest first.
	Restore bool `json:"restore,omitempty"`
}

BackupRunRecord is one run, finished or failed.

func BackupRuns added in v0.4.37

func BackupRuns() ([]BackupRunRecord, error)

BackupRuns is what happened, newest first.

func (BackupRunRecord) Succeeded added in v0.4.37

func (r BackupRunRecord) Succeeded() bool

Succeeded reports whether this run finished.

type BackupSchedule added in v0.4.37

type BackupSchedule struct {
	App         string        `json:"app"`
	Destination string        `json:"destination"`
	Every       ScheduleEvery `json:"every"`
	// At is local time, "HH:MM". Local because somebody chose it looking at their
	// own clock, and "back up at 3am" means their 3am.
	At string `json:"at"`
	// Weekday applies to weekly only.
	Weekday time.Weekday `json:"weekday"`
	// Keep is how many runs survive at the destination. Zero keeps everything,
	// which is the safe reading of a field nobody filled in -- deleting backups is
	// not something to do because a number defaulted.
	Keep int `json:"keep"`
	// HoldStill stops the app for the copy.
	HoldStill bool `json:"hold_still"`
	Enabled   bool `json:"enabled"`
	// LastRun is when this last went off, and the only thing that stops it going
	// off again for the same slot.
	LastRun time.Time `json:"last_run"`
}

BackupSchedule is one standing arrangement: this app, to this destination, at this time.

func BackupSchedules added in v0.4.37

func BackupSchedules() ([]BackupSchedule, error)

BackupSchedules reads the standing arrangements.

A file that will not parse is an error, not an empty list: starting over silently would switch off every backup on the box and leave nothing saying so.

func DueSchedules added in v0.4.37

func DueSchedules(schedules []BackupSchedule, now time.Time) (due []BackupSchedule, broken []error)

DueSchedules is every schedule with an unrun slot, in a stable order.

A schedule whose time of day cannot be read is reported rather than skipped: silently ignoring it is how a box backs nothing up for months while its interface says it is scheduled.

type ComposeApp

type ComposeApp codegen.ComposeApp

func ComposeAppFromSettingsYAML

func ComposeAppFromSettingsYAML(buf []byte, keep map[string]struct{}) (*ComposeApp, error)

ComposeAppFromSettingsYAML parses the compose YAML the settings page (or any YAML-speaking editor) sends back to ApplyComposeAppSettings. Interpolation must stay ON: that YAML came from GenerateYAMLFromComposeApp and already has `$` escaped as `$$`; substituteEscaping collapses it and escapes the result exactly once (#1988). keep is ComposeApp.EnvKeys(): references to the app's `.env` are copied verbatim instead of being resolved.

func LoadComposeAppForEditing

func LoadComposeAppForEditing(appID, configFile string, keep map[string]struct{}) (*ComposeApp, error)

LoadComposeAppForEditing loads an installed app for the settings editor: a reference to a key of keep or to a key the runtime defines (settingsKeep) comes out as written, a bare `KEY` entry for one of them as `${KEY}`, and `.env` itself is never read. LoadComposeAppFromConfigFile (what docker runs) keeps resolving everything.

func LoadComposeAppFromConfigFile

func LoadComposeAppFromConfigFile(appID string, configFile string) (*ComposeApp, error)

func NewComposeAppFromYAML

func NewComposeAppFromYAML(yaml []byte, skipInterpolation, skipValidation bool) (*ComposeApp, error)

func NewComposeAppWithEnv added in v0.4.74

func NewComposeAppWithEnv(text, env []byte) (resolved, kept *ComposeApp, err error)

NewComposeAppWithEnv loads compose text installed with its .env, twice. resolved takes the .env's values, an `env_file: .env` included: it is what the checks before the install look at (the ports). kept leaves every reference to a key of the .env as written: it is what the install writes beside the .env, so that the app's .env settings go on applying, as for an app whose .env was added later (see EnvKeys). A key CasaOS sets itself is refused, as there. COMPOSE_PROFILES in the .env turns profiles on, as docker compose reads it: resolved, what the checks look at, has the services of the profiles it names; kept, what is written, has every service with its profiles, and the runtime reads the same .env to run the ones that are on.

func (*ComposeApp) App

func (a *ComposeApp) App(name string) *App

TODO rename the function to service and add error return value

func (*ComposeApp) Apply

func (a *ComposeApp) Apply(ctx context.Context, newComposeYAML []byte) error

func (*ComposeApp) ApplyEnv

func (a *ComposeApp) ApplyEnv(ctx context.Context, env []byte) error

ApplyEnv replaces the `.env` of the app (empty removes it) and re-creates it from its unchanged compose file: a restart would keep the old environment, the apply reloads the project first.

func (*ComposeApp) Apps

func (a *ComposeApp) Apps() map[string]*App

func (*ComposeApp) AuthorType

func (a *ComposeApp) AuthorType() codegen.StoreAppAuthorType

func (*ComposeApp) BackupInventory added in v0.4.37

func (a *ComposeApp) BackupInventory() []BackupEntry

BackupInventory is everything on disk that belongs to this app, decided without copying anything and without asking Docker.

It lists what is left OUT as well as what goes in, each with its reason. A backup that quietly drops a mount is discovered on the day it is restored, which is the worst possible day to discover it.

A named volume comes back carrying its name and no path: where Docker put it is the daemon's answer, asked separately, and the list of which volumes belong to this app does not depend on the daemon being reachable.

func (*ComposeApp) Containers

func (a *ComposeApp) Containers(ctx context.Context) (map[string][]api.ContainerSummary, error)

func (*ComposeApp) Create

func (a *ComposeApp) Create(ctx context.Context, options api.CreateOptions, service api.Compose) error

func (*ComposeApp) DockerVolumeName added in v0.4.37

func (a *ComposeApp) DockerVolumeName(composeName string) string

DockerVolumeName is the name Docker actually knows a compose volume by.

A compose file says `backend-storage`; Docker stores it as `<project>_backend-storage` unless the file declares a name of its own or marks it external. compose-go has already worked that out at load time, so the answer is read rather than rebuilt -- rebuilding it is how a backup addresses a volume that does not exist and reports success having copied nothing.

func (*ComposeApp) EnvFile

func (a *ComposeApp) EnvFile() string

EnvFile is the `.env` docker compose reads next to the compose file of an installed app.

func (*ComposeApp) EnvFileText

func (a *ComposeApp) EnvFileText() ([]byte, error)

EnvFileText returns the raw `.env`, empty when there is none.

func (*ComposeApp) EnvKeys

func (a *ComposeApp) EnvKeys() (map[string]struct{}, error)

EnvKeys is the keep-set of the settings round trip: the names defined in the app's `.env`. Values of those stay `${KEY}` references in docker-compose.yml instead of being baked in, otherwise the first settings save would copy the secrets into the compose file and every later `.env` edit would be a no-op. A reserved key (see reservedEnvKey) is left out: the editor then shows the value the runtime uses. A missing file yields an empty set; an unreadable one is an error, an empty set would bake every value on the next save.

func (*ComposeApp) GetPortsInUse

func (*ComposeApp) GetPortsInUseFor added in v0.4.77

func (a *ComposeApp) GetPortsInUseFor(profiles []string) (*codegen.ComposeAppValidationErrorsPortsInUse, error)

GetPortsInUseFor is GetPortsInUse for what runs with the profiles on: the ports of a service behind a profile that is off (a proxy that is not installed here) are no conflict.

func (*ComposeApp) HealthCheck

func (a *ComposeApp) HealthCheck() (bool, error)

func (*ComposeApp) IsUncontrolled

func (a *ComposeApp) IsUncontrolled() bool

IsUncontrolled reports whether the app declares `x-casaos.is_uncontrolled`.

The read this replaces was one expression whose FIRST type assertion had no comma-ok:

Extensions[x].(map[string]interface{})[y].(bool)

The guard above it tests whether the KEY is present, which is not the same as the value being a map: `x-casaos:` with nothing after it parses to a nil value under a present key, and asserting that to a map type panics -- taking down whichever service called it. The copy of this line in the app grid did exactly that to a whole host in v0.4.61.

Indexing a nil map is fine in Go. Asserting a nil interface to a map type is not.

func (*ComposeApp) Logs

func (a *ComposeApp) Logs(ctx context.Context, lines int, services ...string) ([]byte, error)

Logs returns the logs of the app. Naming services narrows it to those; naming none keeps every service of the app, which is what the app-wide viewer asks for.

func (*ComposeApp) MainService

func (a *ComposeApp) MainService() (*App, error)

MainService is the service MainServiceName names. Reading it out of StoreInfo instead meant a stack with no `x-casaos` had no main service at all, and every caller of this and of MainTag failed on it -- including the check that runs when the owner saves the settings of such an app.

func (*ComposeApp) MainServiceName

func (a *ComposeApp) MainServiceName() string

MainServiceName is the service an app's interface is about: what `x-casaos.main` names, and otherwise the alphabetically first service, which is the rule StoreInfo has always applied when that key is absent.

A stack somebody assembled by hand has no `x-casaos` at all, and gets the same fallback rather than no answer. The extension carries presentation; which service leads is a question about the compose file, and it has an answer either way.

func (*ComposeApp) MainTag

func (a *ComposeApp) MainTag() (string, error)

func (*ComposeApp) Pull

func (a *ComposeApp) Pull(ctx context.Context) error

func (*ComposeApp) PullAndApply

func (a *ComposeApp) PullAndApply(ctx context.Context, newComposeYAML []byte) error

func (*ComposeApp) PullAndInstall

func (a *ComposeApp) PullAndInstall(ctx context.Context) error

func (*ComposeApp) SetStatus

func (a *ComposeApp) SetStatus(ctx context.Context, status codegen.RequestComposeAppStatus) error

func (*ComposeApp) SetStoreAppID

func (a *ComposeApp) SetStoreAppID(storeAppID string) (string, bool)

func (*ComposeApp) SetTitle

func (a *ComposeApp) SetTitle(title, lang string)

func (*ComposeApp) SetUncontrolled

func (a *ComposeApp) SetUncontrolled(uncontrolled bool) error

func (*ComposeApp) StoreInfo

func (a *ComposeApp) StoreInfo(includeApps bool) (*codegen.ComposeAppStoreInfo, error)

func (*ComposeApp) SupportsArchitecture

func (a *ComposeApp) SupportsArchitecture(arch string) bool

SupportsArchitecture reports whether the compose app runs on arch (a runtime.GOARCH value: amd64, arm64, arm). A missing or empty `architectures` list means every architecture, matching the UI (AppPanel.vue `unuseable`).

func (*ComposeApp) Uninstall

func (a *ComposeApp) Uninstall(ctx context.Context, deleteConfigFolder bool) error

func (*ComposeApp) Up

func (a *ComposeApp) Up(ctx context.Context, service api.Compose) error

func (*ComposeApp) UpWithCheckRequire

func (a *ComposeApp) UpWithCheckRequire(ctx context.Context, service api.Compose) error

func (*ComposeApp) Update

func (a *ComposeApp) Update(ctx context.Context) error

Update is what the update button does: the app is replaced in the background and the request returns at once; the events of the update say the rest.

func (*ComposeApp) UpdateEventPropertiesFromStoreInfo

func (a *ComposeApp) UpdateEventPropertiesFromStoreInfo(eventProperties map[string]string) error

Try to update AppIcon and AppTitle in given event properties from store info

func (*ComposeApp) UpdateNow added in v0.4.52

func (a *ComposeApp) UpdateNow(ctx context.Context) error

UpdateNow is Update waited for: the run of every app at once does them one after another (compose_update_all.go).

func (*ComposeApp) WriteEnvFile

func (a *ComposeApp) WriteEnvFile(text []byte) error

WriteEnvFile replaces the `.env`; an empty body removes it.

type ComposeService

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

func NewComposeService

func NewComposeService() *ComposeService

func (*ComposeService) Install

func (s *ComposeService) Install(ctx context.Context, composeApp *ComposeApp, env []byte) error

Install writes the app's compose file, and env as its .env beside it when there is one (see NewComposeAppWithEnv), then installs it.

func (*ComposeService) IsInstalling

func (s *ComposeService) IsInstalling(appName string) bool

func (*ComposeService) List

func (s *ComposeService) List(ctx context.Context) (map[string]*ComposeApp, error)

func (*ComposeService) PrepareWorkingDirectory

func (s *ComposeService) PrepareWorkingDirectory(name string) (string, error)

func (*ComposeService) RecoverAppsWaitingForStorage

func (s *ComposeService) RecoverAppsWaitingForStorage(ctx context.Context)

RecoverAppsWaitingForStorage starts compose apps that were abandoned because the default storage was not ready when they first tried to start. It is meant to be called periodically after service startup; it stops acting once the recovery pass has run or the wait window has expired.

func (*ComposeService) Status

func (s *ComposeService) Status(ctx context.Context, appID string) (string, error)

func (*ComposeService) Uninstall

func (s *ComposeService) Uninstall(ctx context.Context, composeApp *ComposeApp, deleteConfigFolder bool) error

type ContainerVolume added in v0.4.38

type ContainerVolume struct {
	// Name is the volume's name in Docker.
	Name string `json:"name"`
	// Target is where the container mounts it.
	Target string `json:"target"`
	// Size in bytes, or -1 when the driver cannot say. Shown so nobody deletes
	// 1.4 GB thinking it is a cache.
	Size int64 `json:"size"`
	// Containers is how many containers reference it, this one included, or -1
	// when the daemon cannot say.
	Containers int64 `json:"containers"`
	// Removable is false when this must not be offered for deletion at all.
	Removable bool `json:"removable"`
	// Reason says why, when it is not.
	Reason string `json:"reason,omitempty"`
}

ContainerVolume is one named volume a container mounts.

func DescribeVolumesForRemoval added in v0.4.38

func DescribeVolumesForRemoval(volumes []ContainerVolume) []ContainerVolume

DescribeVolumesForRemoval marks which of a container's volumes may be offered.

A volume another container still uses is never offered: deleting it takes data out from under something that is running, and the person clicking is looking at a card for a different container entirely.

A daemon that cannot count references is treated as "cannot say", which is treated as "do not offer". Guessing wrong in that direction costs disk space; guessing wrong in the other costs somebody's database.

type DockerService

type DockerService interface {
	// image
	IsExistImage(imageName string) bool
	PullImage(ctx context.Context, imageName string) error
	PullLatestImage(ctx context.Context, imageName string) (bool, error)
	RemoveImage(name string) error

	// container
	CheckContainerHealth(id string) (bool, error)
	CreateContainer(m model.CustomizationPostData, id string) (containerID string, err error)
	CreateContainerShellSession(container, row, col string) (hr types.HijackedResponse, err error)
	DescribeContainer(ctx context.Context, name string) (*types.ContainerJSON, error)
	GetContainer(id string) (types.Container, error)
	GetContainerAppList(name, image, state *string) (*[]model.MyAppList, *[]model.MyAppList)
	GetContainerByName(name string) (*types.Container, error)
	GetContainerLog(name string) ([]byte, error)
	GetContainerStats() []model.DockerStatsModel
	RecreateContainer(ctx context.Context, id string, pull bool, force bool) error
	RemoveContainer(name string, update bool) error
	RestartContainer(id string) error
	SampleContainerStats(ctx context.Context, ids []string) map[string]*container.StatsResponse
	VolumeMountpoints(ctx context.Context, names []string) map[string]string
	VolumeUsage(ctx context.Context) map[string]docker.VolumeSpace
	RemoveVolume(ctx context.Context, name string) error
	RenameContainer(name, id string) (err error)
	StartContainer(name string) error
	StopContainer(id string) error

	// network
	GetNetworkList() []network.Summary

	// docker server
	GetServerInfo() (system.Info, error)
}

func NewDockerService

func NewDockerService() DockerService

type ErrAppBusy added in v0.4.57

type ErrAppBusy struct {
	App     string
	Running string
}

ErrAppBusy is the refusal, naming what holds the app. The API answers it with 409.

func (ErrAppBusy) Error added in v0.4.57

func (e ErrAppBusy) Error() string

type GitAppChanges added in v0.4.57

type GitAppChanges struct {
	Branch     *string
	AutoDeploy *bool
	Access     *string
	Token      *string
	// WebhookEnabled turns the webhook on, with a new secret when it has none, or off,
	// forgetting the secret.
	WebhookEnabled *bool
	// RegenerateWebhookSecret replaces the secret at once; refused while the webhook is off.
	RegenerateWebhookSecret *bool
	// Follow changes the mode: the folder of a cloned app is put on the branch or detached.
	// TagPattern and Prereleases change the tags an app that follows them may deploy.
	Follow      *string
	TagPattern  *string
	Prereleases *bool
}

GitAppChanges changes a git app; nil leaves a field as it is.

type GitAppRegistration added in v0.4.57

type GitAppRegistration struct {
	Name   string
	URL    string
	Branch string
	Access string
	Token  string
	// Follow is gitFollowBranch, empty for the same, or gitFollowTags; TagPattern and
	// Prereleases choose the tags an app that follows them may deploy.
	Follow      string
	TagPattern  string
	Prereleases bool
}

GitAppRegistration is an app to create from a repository.

type GitAppView added in v0.4.57

type GitAppView struct {
	App        string           `json:"app"`
	Origin     string           `json:"origin"`
	Dir        string           `json:"dir"`
	Remote     string           `json:"remote"`
	Branch     string           `json:"branch"`
	Access     string           `json:"access"`
	PublicKey  string           `json:"public_key,omitempty"`
	TokenSet   bool             `json:"token_set"`
	AutoDeploy bool             `json:"auto_deploy"`
	AutoPaused bool             `json:"auto_paused"`
	Blocked    bool             `json:"blocked"`
	EnvTracked bool             `json:"env_tracked"`
	Cloned     bool             `json:"cloned"`
	Head       *GitHeadView     `json:"head"`
	Deployed   *GitDeployedView `json:"deployed"`
	Check      *GitCheckView    `json:"check"`
	NewCommits bool             `json:"new_commits"`
	State      string           `json:"state"`
	Operation  *gitOperation    `json:"operation"`
	History    []GitHistoryView `json:"history"`
	Compose    *GitComposeView  `json:"compose"`
	// ComposeExample is set when the last check found no compose file in the repository.
	ComposeExample *string        `json:"compose_example"`
	EnvTemplate    *string        `json:"env_template"`
	BuildLog       string         `json:"build_log"`
	Webhook        GitWebhookView `json:"webhook"`
	// Follow is `branch` or `tags`. TagPattern and Prereleases choose the tags an app that
	// follows them may deploy, and are kept while it follows a branch.
	Follow      string `json:"follow"`
	TagPattern  string `json:"tag_pattern"`
	Prereleases bool   `json:"prereleases"`
}

GitAppView is the `GitApp` of the API.

func CheckGitApp added in v0.4.57

func CheckGitApp(ctx context.Context, name string) (*GitAppView, error)

CheckGitApp is "Check now". An adoptable app is adopted here, so that a folder that cannot be adopted is refused with its reason; the check itself runs in the background, then whatever the automatic rebuild may deploy, and the app is returned as the check starts.

func CreateGitApp added in v0.4.57

func CreateGitApp(ctx context.Context, registration GitAppRegistration) (*GitAppView, error)

CreateGitApp registers an app to deploy from a repository. Nothing is cloned: a check does that, once the owner has given the repository the deploy key.

func DeployGitApp added in v0.4.57

func DeployGitApp(ctx context.Context, name, commit string, env *string) (*GitAppView, error)

DeployGitApp deploys commit, or the remote's latest when commit is empty (for an app that follows tags, the highest eligible tag), in the background. A commit of the history is a revert. env is written as .env first.

func DeployGitAppTag added in v0.4.62

func DeployGitAppTag(ctx context.Context, name, tag string, env *string) (*GitAppView, error)

DeployGitAppTag deploys tag, one of the eligible tags of an app that follows tags, lower ones included, or the highest when tag is empty, in the background. A tag whose commit ran is a revert. env is written as .env first.

func GetGitApp added in v0.4.57

func GetGitApp(ctx context.Context, name string) (*GitAppView, error)

GetGitApp is the app as the API answers it.

func UpdateGitApp added in v0.4.57

func UpdateGitApp(ctx context.Context, name string, changes GitAppChanges) (*GitAppView, error)

UpdateGitApp changes the branch, the automatic rebuild, the access, the webhook or the mode of a git app, and adopts an adoptable one.

type GitCheckView added in v0.4.62

type GitCheckView struct {
	At           time.Time `json:"at"`
	RemoteCommit string    `json:"remote_commit"`
	RemoteTag    string    `json:"remote_tag"`
	TagMoved     bool      `json:"tag_moved"`
	Error        string    `json:"error"`
}

GitCheckView is the `check` of the API: the last check, and whether it found the deployed tag on another commit.

type GitComposeServiceView added in v0.4.57

type GitComposeServiceView struct {
	Name      string   `json:"name"`
	Build     bool     `json:"build"`
	Image     string   `json:"image"`
	Ports     []string `json:"ports"`
	Volumes   []string `json:"volumes"`
	Sensitive []string `json:"sensitive"`
}

type GitComposeView added in v0.4.57

type GitComposeView struct {
	Files    []string                `json:"files"`
	Services []GitComposeServiceView `json:"services"`
}

type GitDeployedView added in v0.4.57

type GitDeployedView struct {
	Commit  string    `json:"commit"`
	Subject string    `json:"subject"`
	At      time.Time `json:"at"`
	Tag     string    `json:"tag"`
}

type GitHeadView added in v0.4.57

type GitHeadView struct {
	Commit            string `json:"commit"`
	Subject           string `json:"subject"`
	TrackedFilesClean bool   `json:"tracked_files_clean"`
}

type GitHistoryView added in v0.4.57

type GitHistoryView struct {
	Commit     string    `json:"commit"`
	Subject    string    `json:"subject"`
	At         time.Time `json:"at"`
	Outcome    string    `json:"outcome"`
	Reason     string    `json:"reason"`
	Revertable bool      `json:"revertable"`
	Tag        string    `json:"tag"`
}

type GitRequestError added in v0.4.57

type GitRequestError string

GitRequestError is a request that cannot be carried out as it is; the API answers 400 with its message, which says what to change.

func (GitRequestError) Error added in v0.4.57

func (e GitRequestError) Error() string

type GitTag added in v0.4.62

type GitTag struct {
	Name   string `json:"name"`
	Commit string `json:"commit"`
}

GitTag is a tag of the remote and the commit it names.

func GitAppTags added in v0.4.62

func GitAppTags(ctx context.Context, name string) ([]GitTag, error)

GitAppTags is the eligible tags of an app that follows tags, highest first, at most 50. It asks the remote, as a check does.

type GitWebhookDelivery added in v0.4.61

type GitWebhookDelivery struct {
	At     time.Time `json:"at"`
	Forge  string    `json:"forge"`
	Event  string    `json:"event"`
	Result string    `json:"result"`
}

GitWebhookDelivery is the last delivery whose signature was valid.

type GitWebhookView added in v0.4.61

type GitWebhookView struct {
	Enabled bool   `json:"enabled"`
	Path    string `json:"path"`
	// Secret is set while the webhook is on.
	Secret       string              `json:"secret,omitempty"`
	LastDelivery *GitWebhookDelivery `json:"last_delivery"`
}

GitWebhookView is the `webhook` of a git app in the API.

type ProgressDetail

type ProgressDetail struct {
	Current int64 `json:"current"`
	Total   int64 `json:"total"`
}

type PullOut

type PullOut struct {
	Status         StatusType     `json:"status"`
	ProgressDetail ProgressDetail `json:"progressDetail"`
	Id             string         `json:"id"`
}

type ResticCheck added in v0.4.82

type ResticCheck struct {
	At    time.Time `json:"at"`
	OK    bool      `json:"ok"`
	Error string    `json:"error,omitempty"`
}

ResticCheck is the last check of one destination.

type RestoreInstaller added in v0.4.41

type RestoreInstaller func(ctx context.Context, name string, compose, env []byte) (*ComposeApp, error)

RestoreInstaller puts an app that is not installed back from its compose file and, when the backup holds one, its .env, and returns it once its containers exist -- so that its named volumes exist too and have somewhere to be restored.

type RestoreOptions added in v0.4.41

type RestoreOptions struct {
	Destination string
	App         string
	Stamp       string
	// Containers lists the app's containers; nil means ask Docker. A seam for the
	// tests, as on BackupOptions.
	Containers func(ctx context.Context) (map[string][]codegen.ContainerSummary, error)
}

RestoreOptions is one restore.

type RestoreReport added in v0.4.41

type RestoreReport struct {
	// Restored, in the order it happened, with this host's paths
	Restored []BackupOperation
	// Missing is in the backup and has nowhere to go on this app: a volume it no
	// longer declares, a bind mount it no longer has. Reported, never guessed.
	Missing []BackupOperation
	// Skipped is what the backup itself left out, copied from its manifest so the
	// person restoring finds out here rather than by noticing something absent.
	Skipped []BackupEntry
	// Installed is true when the app was not installed and has been, from the
	// backup's own compose file.
	Installed bool
}

RestoreReport is what a restore did, and what it could not.

func RestoreBackup added in v0.4.41

func RestoreBackup(ctx context.Context, installed *ComposeApp, docker backupDocker, restorer BackupRestorer, install RestoreInstaller, opts RestoreOptions) (RestoreReport, error)

RestoreBackup puts opts.App back the way the backup at opts.Stamp had it.

installed is the app as it is now, or nil when it is not installed at all -- in which case it is installed first, from the compose file the backup holds. The app is held still for the length of the copy either way, and started again afterwards, whatever happened in between.

func RestoreOnDemand added in v0.4.41

func RestoreOnDemand(ctx context.Context, installed *ComposeApp, docker backupDocker, restorer BackupRestorer, install RestoreInstaller, opts RestoreOptions) (RestoreReport, error)

RestoreOnDemand runs one restore somebody asked for, and records it in the same log as the backups, marked as a restore.

func RestoreSystemBackup added in v0.4.45

func RestoreSystemBackup(ctx context.Context, restorer BackupRestorer, units unitControl, opts SystemRestoreOptions) (RestoreReport, error)

RestoreSystemBackup puts the box's own state back the way the backup had it.

Where things go is this box's list, never the manifest's paths, as with an app. The services holding the files are stopped for the copy and started again whatever happens; rclone is restarted last, after everything has been copied through it, if its configuration was among what came back.

func SystemRestoreOnDemand added in v0.4.45

func SystemRestoreOnDemand(ctx context.Context, restorer BackupRestorer, units unitControl, opts SystemRestoreOptions) (RestoreReport, error)

SystemRestoreOnDemand runs one restore of the box and records it.

type ScheduleEvery added in v0.4.37

type ScheduleEvery string

ScheduleEvery is how often a schedule comes round.

const (
	ScheduleDaily  ScheduleEvery = "daily"
	ScheduleWeekly ScheduleEvery = "weekly"
)

type Services

type Services interface {
	AppStoreManagement() *AppStoreManagement

	// Git() *GitService
	Compose() *ComposeService
	Docker() DockerService
	Gateway() external.ManagementService
	Notify() external.NotifyService
	MessageBus() *message_bus.ClientWithResponses
}
var (
	MyService Services
)

func NewService

func NewService(RuntimePath string) Services

type StatusType

type StatusType string
const (
	Pull         StatusType = "Pulling fs layer"
	PullComplete StatusType = "Pull complete"
	// AlreadyExists is what the daemon sends for a layer this host already has. It
	// arrives INSTEAD of the pulling/complete pair, never alongside it, so a layer
	// reported this way is one that is both known and finished.
	AlreadyExists StatusType = "Already exists"
)

type SystemBackupOptions added in v0.4.45

type SystemBackupOptions struct {
	Destination string
	Stamp       string
	// HoldStill stops the services that hold the files open for the copy.
	HoldStill bool
}

SystemBackupOptions is one backup of the box.

type SystemRestoreOptions added in v0.4.45

type SystemRestoreOptions struct {
	Destination string
	Stamp       string
}

SystemRestoreOptions is one restore of the box.

type Throttler

type Throttler struct {
	InvokeInterval time.Duration
	LastInvokeTime time.Time
}

func NewThrottler

func NewThrottler(interval time.Duration) *Throttler

func (*Throttler) ThrottleFunc

func (t *Throttler) ThrottleFunc(f func())

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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