Add backup support for Code Flow #9
10 changed files with 201 additions and 45 deletions
|
@ -35,8 +35,9 @@ The repository manages the services hosted on Flow Platform with the use of http
|
||||||
----
|
----
|
||||||
$ mage -l
|
$ mage -l
|
||||||
Targets:
|
Targets:
|
||||||
|
backup creates a backup of the specified service.
|
||||||
clean cleans the workspace.
|
clean cleans the workspace.
|
||||||
deploy deploys the services to the Flow Platform.
|
deploy deploys the service(s) to the Flow Platform.
|
||||||
prepare prepares the service's build directory.
|
prepare prepares the service's build directory.
|
||||||
shutdown shuts down the Flow Platform in a given environment.
|
shutdown shuts down the Flow Platform in a given environment.
|
||||||
stop stops a running service within the Flow Platform.
|
stop stops a running service within the Flow Platform.
|
||||||
|
@ -59,3 +60,27 @@ mage prepare production forgejo
|
||||||
----
|
----
|
||||||
mage deploy production forgejo
|
mage deploy production forgejo
|
||||||
----
|
----
|
||||||
|
|
||||||
|
== Backup Guide
|
||||||
|
|
||||||
|
There is support for backing up the data for some of the services, e.g. `forgejo`. You can use mage to run the backup for a specific service.
|
||||||
|
|
||||||
|
[source,console]
|
||||||
|
----
|
||||||
|
mage backup production forgejo
|
||||||
|
----
|
||||||
|
|
||||||
|
Here mage will stop the running service and recreate the container running the specific backup script for that service. If there is no backup support for a particular service mage will inform you of this.
|
||||||
|
|
||||||
|
[source,console]
|
||||||
|
----
|
||||||
|
$ mage backup production traefik
|
||||||
|
Backup is not supported for "traefik".
|
||||||
|
----
|
||||||
|
|
||||||
|
Currently the service will not automatically resume after the backup is completed but you can achieve this with a chain command.
|
||||||
|
|
||||||
|
[source,console]
|
||||||
|
----
|
||||||
|
mage backup production forgejo && mage deploy production forgejo
|
||||||
|
----
|
||||||
|
|
2
config
2
config
|
@ -1 +1 @@
|
||||||
Subproject commit d332a51218fc2b5cb152fd2439df061940c07dab
|
Subproject commit 1766c5ec179d869fc4999523cba036b75e0b4fd7
|
47
internal/actions/backup.go
Normal file
47
internal/actions/backup.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"flow/services/internal/config"
|
||||||
|
"flow/services/internal/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
const containerRunModeBackup = "backup"
|
||||||
|
|
||||||
|
func Backup(environment, service string) error {
|
||||||
|
cfg, err := config.NewConfig(environment)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to load the configuration; %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch service {
|
||||||
|
case services.Forgejo:
|
||||||
|
cfg.Forgejo.ContainerRunMode = containerRunModeBackup
|
||||||
|
default:
|
||||||
|
fmt.Printf("Backup is not supported for %q.\n", service)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := backup(cfg, environment, service); err != nil {
|
||||||
|
return fmt.Errorf("error performing a backup of %q; %w", service, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func backup(cfg config.Config, environment, service string) error {
|
||||||
|
if err := stop(cfg.Docker.Host, environment, service); err != nil {
|
||||||
|
return fmt.Errorf("unable to stop %q; %w", service, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := prepareCompose(cfg, environment); err != nil {
|
||||||
|
return fmt.Errorf("unable to regenerate the compose file for the backup; %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deploy(cfg.Docker.Host, environment, service, false); err != nil {
|
||||||
|
return fmt.Errorf("error deploying the backup container; %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -2,13 +2,27 @@ package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flow/services/internal"
|
"flow/services/internal"
|
||||||
|
"flow/services/internal/config"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/magefile/mage/sh"
|
"github.com/magefile/mage/sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Deploy(dockerHost, environment, service string, daemon bool) error {
|
func Deploy(environment, service string) error {
|
||||||
|
cfg, err := config.NewConfig(environment)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to load the configuration; %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deploy(cfg.Docker.Host, environment, service, true); err != nil {
|
||||||
|
return fmt.Errorf("error deploying %q; %w", service, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deploy(dockerHost, environment, service string, daemon bool) error {
|
||||||
os.Setenv("DOCKER_HOST", dockerHost)
|
os.Setenv("DOCKER_HOST", dockerHost)
|
||||||
|
|
||||||
command := []string{
|
command := []string{
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
"flow/services/internal"
|
"flow/services/internal"
|
||||||
"flow/services/internal/bundle"
|
"flow/services/internal/bundle"
|
||||||
|
@ -141,6 +142,7 @@ func render(cfg config.Config, environment, component string) error {
|
||||||
|
|
||||||
funcMap := template.FuncMap{
|
funcMap := template.FuncMap{
|
||||||
"default": defaultValue,
|
"default": defaultValue,
|
||||||
|
"timestamp": timestamp(),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
|
@ -246,3 +248,11 @@ func defaultValue(value, defaultValue string) string {
|
||||||
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func timestamp() func() string {
|
||||||
|
now := time.Now().Format("20060102-1504")
|
||||||
|
|
||||||
|
return func() string {
|
||||||
|
return now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
38
internal/actions/shutdown.go
Normal file
38
internal/actions/shutdown.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"flow/services/internal"
|
||||||
|
"flow/services/internal/config"
|
||||||
|
|
||||||
|
"github.com/magefile/mage/sh"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Shutdown(environment string) error {
|
||||||
|
cfg, err := config.NewConfig(environment)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to load the configuration; %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := shutdown(cfg.Docker.Host, environment); err != nil {
|
||||||
|
return fmt.Errorf("unable to shutdown the %q environment; %w", environment, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shutdown(dockerHost, environment string) error {
|
||||||
|
os.Setenv("DOCKER_HOST", dockerHost)
|
||||||
|
|
||||||
|
command := []string{
|
||||||
|
"docker",
|
||||||
|
"compose",
|
||||||
|
"--project-directory",
|
||||||
|
fmt.Sprintf("%s/%s/compose", internal.RootBuildDir, environment),
|
||||||
|
"down",
|
||||||
|
}
|
||||||
|
|
||||||
|
return sh.RunV(command[0], command[1:]...)
|
||||||
|
}
|
|
@ -2,13 +2,27 @@ package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flow/services/internal"
|
"flow/services/internal"
|
||||||
|
"flow/services/internal/config"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/magefile/mage/sh"
|
"github.com/magefile/mage/sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Stop(dockerHost, environment, service string) error {
|
func Stop(environment, service string) error {
|
||||||
|
cfg, err := config.NewConfig(environment)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to load the configuration; %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stop(cfg.Docker.Host, environment, service); err != nil {
|
||||||
|
return fmt.Errorf("unable to stop %q; %w", service, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop(dockerHost, environment, service string) error {
|
||||||
os.Setenv("DOCKER_HOST", dockerHost)
|
os.Setenv("DOCKER_HOST", dockerHost)
|
||||||
|
|
||||||
command := []string{
|
command := []string{
|
||||||
|
|
|
@ -78,6 +78,8 @@ type Forgejo struct {
|
||||||
Oauth2JwtSigningAlgo string `json:"oauth2JwtSigningAlgo"`
|
Oauth2JwtSigningAlgo string `json:"oauth2JwtSigningAlgo"`
|
||||||
Oauth2JwtSecret string `json:"oauth2JwtSecret"`
|
Oauth2JwtSecret string `json:"oauth2JwtSecret"`
|
||||||
Actions ForgejoActions `json:"actions"`
|
Actions ForgejoActions `json:"actions"`
|
||||||
|
Backups ForgejoBackups `json:"backups"`
|
||||||
|
ContainerRunMode string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ForgejoActions struct {
|
type ForgejoActions struct {
|
||||||
|
@ -85,6 +87,10 @@ type ForgejoActions struct {
|
||||||
DefaultActionsURL string `json:"defaultActionsURL"`
|
DefaultActionsURL string `json:"defaultActionsURL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ForgejoBackups struct {
|
||||||
|
ContainerDirectory string `json:"containerDirectory"`
|
||||||
|
}
|
||||||
|
|
||||||
type Gotosocial struct {
|
type Gotosocial struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -139,21 +145,21 @@ type LandingPageLinks struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig(environment string) (Config, error) {
|
func NewConfig(environment string) (Config, error) {
|
||||||
var c Config
|
var cfg Config
|
||||||
|
|
||||||
path := filepath.Join(configDir, environment, configFileName)
|
path := filepath.Join(configDir, environment, configFileName)
|
||||||
|
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c, fmt.Errorf("unable to open the file; %w", err)
|
return cfg, fmt.Errorf("unable to open the file; %w", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
decoder := json.NewDecoder(f)
|
decoder := json.NewDecoder(f)
|
||||||
|
|
||||||
if err = decoder.Decode(&c); err != nil {
|
if err = decoder.Decode(&cfg); err != nil {
|
||||||
return c, fmt.Errorf("unable to decode JSON data; %w", err)
|
return cfg, fmt.Errorf("unable to decode JSON data; %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"flow/services/internal"
|
"flow/services/internal"
|
||||||
"flow/services/internal/actions"
|
"flow/services/internal/actions"
|
||||||
"flow/services/internal/config"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -14,6 +13,15 @@ import (
|
||||||
"github.com/magefile/mage/sh"
|
"github.com/magefile/mage/sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Backup creates a backup of the specified service.
|
||||||
|
func Backup(environment, service string) error {
|
||||||
|
if err := actions.Backup(environment, service); err != nil {
|
||||||
|
return fmt.Errorf("error running Backup for %q; %w", service, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Clean cleans the workspace.
|
// Clean cleans the workspace.
|
||||||
func Clean(environment string) error {
|
func Clean(environment string) error {
|
||||||
buildDir := filepath.Join(internal.RootBuildDir, environment)
|
buildDir := filepath.Join(internal.RootBuildDir, environment)
|
||||||
|
@ -42,13 +50,8 @@ func Deploy(environment, service string) error {
|
||||||
mg.F(Prepare, environment, service),
|
mg.F(Prepare, environment, service),
|
||||||
)
|
)
|
||||||
|
|
||||||
cfg, err := config.NewConfig(environment)
|
if err := actions.Deploy(environment, service); err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("error running Deploy for %q; %w", service, err)
|
||||||
return fmt.Errorf("unable to load the configuration; %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := actions.Deploy(cfg.Docker.Host, environment, service, true); err != nil {
|
|
||||||
return fmt.Errorf("error deploying %q; %w", service, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -57,7 +60,7 @@ func Deploy(environment, service string) error {
|
||||||
// Prepare prepares the service's build directory.
|
// Prepare prepares the service's build directory.
|
||||||
func Prepare(environment, service string) error {
|
func Prepare(environment, service string) error {
|
||||||
if err := actions.Prepare(environment, service); err != nil {
|
if err := actions.Prepare(environment, service); err != nil {
|
||||||
return fmt.Errorf("error running preparations; %w", err)
|
return fmt.Errorf("error running Prepare for %q; %w", service, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -65,33 +68,17 @@ func Prepare(environment, service string) error {
|
||||||
|
|
||||||
// Shutdown shuts down the Flow Platform in a given environment.
|
// Shutdown shuts down the Flow Platform in a given environment.
|
||||||
func Shutdown(environment string) error {
|
func Shutdown(environment string) error {
|
||||||
cfg, err := config.NewConfig(environment)
|
if err := actions.Shutdown(environment); err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("error running Shutdown for %q; %w", environment, err)
|
||||||
return fmt.Errorf("unable to load the configuration; %w", err)
|
}
|
||||||
}
|
|
||||||
|
return nil
|
||||||
os.Setenv("DOCKER_HOST", cfg.Docker.Host)
|
}
|
||||||
|
|
||||||
command := []string{
|
// Stop stops a running service within the Flow Platform.
|
||||||
"docker",
|
func Stop(environment, service string) error {
|
||||||
"compose",
|
if err := actions.Stop(environment, service); err != nil {
|
||||||
"--project-directory",
|
return fmt.Errorf("error running Stop for %q; %w", service, err)
|
||||||
fmt.Sprintf("%s/%s/compose", internal.RootBuildDir, environment),
|
|
||||||
"down",
|
|
||||||
}
|
|
||||||
|
|
||||||
return sh.RunV(command[0], command[1:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops a running service within the Flow Platform.
|
|
||||||
func Stop(environment, service string) error {
|
|
||||||
cfg, err := config.NewConfig(environment)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load the configuration; %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := actions.Stop(cfg.Docker.Host, environment, service); err != nil {
|
|
||||||
return fmt.Errorf("error stopping %q; %w", service, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -55,13 +55,28 @@ services:
|
||||||
image: "localhost/flow/forgejo:{{ .Forgejo.Version }}"
|
image: "localhost/flow/forgejo:{{ .Forgejo.Version }}"
|
||||||
build:
|
build:
|
||||||
context: "../forgejo"
|
context: "../forgejo"
|
||||||
|
{{- if eq .Forgejo.ContainerRunMode "backup" }}
|
||||||
|
command:
|
||||||
|
- forgejo
|
||||||
|
- dump
|
||||||
|
- --config
|
||||||
|
- {{ .Forgejo.AppIni }}
|
||||||
|
- --file
|
||||||
|
- {{ .Forgejo.Backups.ContainerDirectory }}/forgejo-backup-v{{ .Forgejo.Version }}-{{ timestamp }}.tar.gz
|
||||||
|
- --type
|
||||||
|
- tar.gz
|
||||||
|
{{- end }}
|
||||||
expose:
|
expose:
|
||||||
- "{{ .Forgejo.SshPort }}"
|
- "{{ .Forgejo.SshPort }}"
|
||||||
- "{{ .Forgejo.HttpPort }}"
|
- "{{ .Forgejo.HttpPort }}"
|
||||||
networks:
|
networks:
|
||||||
flow:
|
flow:
|
||||||
ipv4_address: "{{ .Forgejo.ContainerIpv4Address }}"
|
ipv4_address: "{{ .Forgejo.ContainerIpv4Address }}"
|
||||||
|
{{- if eq .Forgejo.ContainerRunMode "backup" }}
|
||||||
|
restart: "no"
|
||||||
|
{{- else }}
|
||||||
restart: "always"
|
restart: "always"
|
||||||
|
{{- end }}
|
||||||
volumes:
|
volumes:
|
||||||
{{- template "defaultVolumes" }}
|
{{- template "defaultVolumes" }}
|
||||||
# Forgejo data volume
|
# Forgejo data volume
|
||||||
|
|
Loading…
Reference in a new issue