Compare commits

..

32 Commits

Author SHA1 Message Date
8d9131fa9e Merge pull request 'refactor' (#1) from refactor into main
Some checks failed
Deploy Documenation site to GitHub Pages / build (push) Failing after 11m2s
Deploy Documenation site to GitHub Pages / deploy (push) Has been skipped
Lint / Run on Ubuntu (push) Failing after 30m28s
Reviewed-on: https://git.jkantech.com/jkaninda/pg-bkup/pulls/1
2024-12-12 16:01:25 +01:00
abf1aeba7c chore: add convert bytes to a human-readable string with the appropriate unit (bytes, MiB, or GiB)
All checks were successful
Lint / Run on Ubuntu (push) Successful in 20m39s
Lint / Run on Ubuntu (pull_request) Successful in 20m14s
2024-12-12 12:43:06 +01:00
ad2f3a1990 Merge pull request #151 from jkaninda/refactor
docs: update features
2024-12-10 10:15:34 +01:00
0edee0b703 docs: update features 2024-12-10 10:14:43 +01:00
fdb79d3cef Merge pull request #150 from jkaninda/refactor
chore: add convert backup size from bytes to Mib
2024-12-10 10:13:30 +01:00
01915039e7 Merge branch 'main' of github.com:jkaninda/pg-bkup into refactor 2024-12-10 10:12:27 +01:00
f9dbd0b85c Merge pull request #148 from jkaninda/develop
feat: add support Database source jdbc uri format
2024-12-10 10:11:31 +01:00
409f9e91f2 Merge pull request #149 from jkaninda/dependabot/docker/golang-1.23.4
chore(deps): bump golang from 1.23.3 to 1.23.4
2024-12-10 10:11:07 +01:00
93f9595464 chore: remove db port from required vars 2024-12-10 10:10:33 +01:00
c89411cfa6 chore: add convert backup size from bytes to Mib 2024-12-10 09:58:39 +01:00
6ec480acda Merge branch 'main' of github.com:jkaninda/pg-bkup into refactor 2024-12-10 09:48:07 +01:00
dependabot[bot]
0501c151a4 chore(deps): bump golang from 1.23.3 to 1.23.4
Bumps golang from 1.23.3 to 1.23.4.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-09 06:56:30 +00:00
91a440035d refactor: refactoring of code
All checks were successful
Lint / Run on Ubuntu (push) Successful in 20m9s
2024-12-08 13:39:54 +01:00
1a6e2e4ffc feat: add Database source jdbc uri format 2024-12-08 13:33:32 +01:00
3009747874 Merge pull request #147 from jkaninda/refactor
Fix grammar issues in azure.go
2024-12-07 23:59:27 +01:00
99f76eb5d6 Fix grammar issues in azure.go 2024-12-07 23:58:48 +01:00
5a65c51f54 Merge pull request #146 from jkaninda/develop
fix: SSH storage key identify file
2024-12-07 20:38:38 +01:00
324c5df69c fix: SSH storage key identitify file 2024-12-07 20:27:09 +01:00
6f854d5165 Merge pull request #145 from jkaninda/develop
chore: update notification template
2024-12-07 18:00:53 +01:00
5cca957009 chore: update notification template 2024-12-07 16:50:08 +01:00
05003b386e Merge pull request #144 from jkaninda/refactor
chore: correct pg name
2024-12-07 03:48:50 +01:00
Jonas Kaninda
f7989a865d chore: correct pg name 2024-12-07 03:47:55 +01:00
47c7afda4e Merge pull request #143 from jkaninda/refactor
chore: update .env.example
2024-12-07 03:28:34 +01:00
Jonas Kaninda
a9196e6e51 chore: update .env.example 2024-12-07 03:28:12 +01:00
fc6325e5b6 Merge pull request #142 from jkaninda/refactor
chore: update .env.example
2024-12-07 03:27:40 +01:00
Jonas Kaninda
b8fabf2bd8 chore: update .env.example 2024-12-07 03:26:45 +01:00
cbeb8b0ba2 Merge pull request #141 from jkaninda/refactor
chore: update base image
2024-12-07 02:32:14 +01:00
Jonas Kaninda
c644aa60d2 chore: update base image 2024-12-07 02:31:45 +01:00
4904e8d0a6 Merge pull request #140 from jkaninda/refactor
refactor: clean up code
2024-12-07 02:30:39 +01:00
Jonas Kaninda
1eb57044ad refactor: clean up code 2024-12-07 02:28:16 +01:00
803b077b3d Merge pull request #139 from jkaninda/refactor
fix: fix s3 remote path when backing up multiple databases
2024-12-07 02:11:34 +01:00
6503e4b5bd Merge pull request #138 from jkaninda/refactor
feat: add Azure Blob storage
2024-12-06 21:36:05 +01:00
24 changed files with 379 additions and 169 deletions

View File

@@ -15,6 +15,7 @@ TZ=Europe/Paris
### Backup restoration
#FILE_NAME=
### AWS S3 Storage
#ACCESS_KEY=
#SECRET_KEY=
@@ -43,19 +44,30 @@ TZ=Europe/Paris
#FTP_USER=
#FTP_PORT=21
#REMOTE_PATH=
## Azure Blob Storage
AZURE_STORAGE_CONTAINER_NAME=
AZURE_STORAGE_ACCOUNT_NAME=
AZURE_STORAGE_ACCOUNT_KEY=
#### Backup encryption
#GPG_PUBLIC_KEY=/config/public_key.asc
#GPG_PRIVATE_KEY=/config/private_key.asc
#GPG_PASSPHRASE=Your strong passphrase
## For multiple database backup on Docker or Docker in Swarm mode
#BACKUP_CONFIG_FILE=/config/config.yaml
### Database restoration
#FILE_NAME=
### Notification
#BACKUP_REFERENCE=K8s/Paris cluster
## Telegram
#TG_TOKEN=
#TG_CHAT_ID=
### Email
#MAIL_HOST=
#MAIL_PORT=

View File

@@ -1,4 +1,4 @@
FROM golang:1.23.3 AS build
FROM golang:1.23.4 AS build
WORKDIR /app
ARG appVersion=""
# Copy the source code.
@@ -9,7 +9,7 @@ RUN go mod download
# Build
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-X 'github.com/jkaninda/pg-bkup/utils.Version=${appVersion}'" -o /app/pg-bkup
FROM alpine:3.20.3
FROM alpine:3.21.0
ENV TZ=UTC
ARG WORKDIR="/config"
ARG BACKUPDIR="/backup"

View File

@@ -38,6 +38,8 @@ services:
- DB_NAME=database
- DB_USERNAME=username
- DB_PASSWORD=password
# You can also use JDBC format
#- DB_URL=jdbc:postgresql://postgres:5432/database?user=user&password=password
# pg-bkup container must be connected to the same network with your database
networks:
- web
@@ -67,7 +69,7 @@ services:
# for a list of available releases.
image: jkaninda/pg-bkup
container_name: pg-bkup
command: backup -d database --cron-expression "0 1 * * *"
command: backup -d database --cron-expression @midnight
volumes:
- ./backup:/backup
environment:
@@ -76,7 +78,9 @@ services:
- DB_NAME=database
- DB_USERNAME=username
- DB_PASSWORD=password
- BACKUP_CRON_EXPRESSION=0 1 * * *
- BACKUP_CRON_EXPRESSION=@midnight
# You can also use JDBC format
#- DB_URL=jdbc:postgresql://postgres:5432/database?user=user&password=password
#Delete old backup created more than specified days ago
#- BACKUP_RETENTION_DAYS=7
# pg-bkup container must be connected to the same network with your database

View File

@@ -38,12 +38,16 @@ services:
- DB_NAME=database
- DB_USERNAME=username
- DB_PASSWORD=password
# You can also use JDBC format
#- DB_URL=jdbc:postgresql://postgres:5432/database?user=username&password=password
## Target database
- TARGET_DB_HOST=target-postgres
- TARGET_DB_PORT=5432
- TARGET_DB_NAME=dbname
- TARGET_DB_USERNAME=username
- TARGET_DB_PASSWORD=password
# You can also use JDBC format
#- TARGET_DB_URL=jdbc:postgresql://target-postgres:5432/dbname?user=username&password=password
# mysql-bkup container must be connected to the same network with your database
networks:
- web

View File

@@ -17,6 +17,7 @@ It supports a variety of storage options and ensures data security through GPG e
- AWS S3 or any S3-compatible object storage
- FTP
- SSH-compatible storage
- Azure Blob storage
- **Data Security:**
- Backups can be encrypted using **GPG** to ensure confidentiality.

View File

@@ -33,12 +33,13 @@ Backup, restore and migrate targets, schedule and retention are configured using
## Environment variables
| Name | Requirement | Description |
|------------------------|---------------------------------------------------------------|-----------------------------------------------------------------|
|------------------------------|---------------------------------------------------------------|-----------------------------------------------------------------|
| DB_PORT | Optional, default 5432 | Database port number |
| DB_HOST | Required | Database host |
| DB_NAME | Optional if it was provided from the -d flag | Database name |
| DB_USERNAME | Required | Database user name |
| DB_PASSWORD | Required | Database password |
| DB_URL | Optional | Database URL in JDBC URI format |
| AWS_ACCESS_KEY | Optional, required for S3 storage | AWS S3 Access Key |
| AWS_SECRET_KEY | Optional, required for S3 storage | AWS S3 Secret Key |
| AWS_BUCKET_NAME | Optional, required for S3 storage | AWS S3 Bucket Name |
@@ -65,10 +66,14 @@ Backup, restore and migrate targets, schedule and retention are configured using
| TARGET_DB_PORT | Optional, required for database migration | Target database port |
| TARGET_DB_NAME | Optional, required for database migration | Target database name |
| TARGET_DB_USERNAME | Optional, required for database migration | Target database username |
| TARGET_DB_URL | Optional | Database URL in JDBC URI format |
| TARGET_DB_PASSWORD | Optional, required for database migration | Target database password |
| TG_TOKEN | Optional, required for Telegram notification | Telegram token (`BOT-ID:BOT-TOKEN`) |
| TG_CHAT_ID | Optional, required for Telegram notification | Telegram Chat ID |
| TZ | Optional | Time Zone |
| AZURE_STORAGE_CONTAINER_NAME | Optional, required for Azure Blob Storage storage | Azure storage container name |
| AZURE_STORAGE_ACCOUNT_NAME | Optional, required for Azure Blob Storage storage | Azure storage account name |
| AZURE_STORAGE_ACCOUNT_KEY | Optional, required for Azure Blob Storage storage | Azure storage account key |
---
## Run in Scheduled mode

2
go.mod
View File

@@ -5,7 +5,7 @@ go 1.23.2
require (
github.com/go-mail/mail v2.3.1+incompatible
github.com/jkaninda/encryptor v0.0.0-20241013064832-ed4bd6a1b221
github.com/jkaninda/go-storage v0.1.2
github.com/jkaninda/go-storage v0.1.3
github.com/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.8.1
gopkg.in/yaml.v3 v3.0.1

2
go.sum
View File

@@ -46,6 +46,8 @@ github.com/jkaninda/encryptor v0.0.0-20241013064832-ed4bd6a1b221 h1:AwkCf7el1kze
github.com/jkaninda/encryptor v0.0.0-20241013064832-ed4bd6a1b221/go.mod h1:9F8ZJ+ZXE8DZBo77+aneGj8LMjrYXX6eFUCC/uqZOUo=
github.com/jkaninda/go-storage v0.1.2 h1:d7+TRPjmHXdSqO0wne3KAB8zt9ih8lf5D8aL4n7/Dds=
github.com/jkaninda/go-storage v0.1.2/go.mod h1:zVRnLprBk/9AUz2+za6Y03MgoNYrqKLy3edVtjqMaps=
github.com/jkaninda/go-storage v0.1.3 h1:lEpHVgFLKSvjsi/6tAek96Y07za3vxmsXF2/+jiCMZU=
github.com/jkaninda/go-storage v0.1.3/go.mod h1:zVRnLprBk/9AUz2+za6Y03MgoNYrqKLy3edVtjqMaps=
github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=

View File

@@ -1,4 +1,3 @@
// Package main /
/*
MIT License
@@ -22,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package main
import "github.com/jkaninda/pg-bkup/cmd"

View File

@@ -10,7 +10,7 @@ import (
)
func azureBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to the remote FTP server")
utils.Info("Backup database to Azure Blob Storage")
startTime = time.Now().Format(utils.TimeFormat())
// Backup database
@@ -57,13 +57,14 @@ func azureBackup(db *dbConfig, config *BackupConfig) {
}
}
utils.Info("Backup name is %s", finalFileName)
utils.Info("Backup size: %s", utils.ConvertBytes(uint64(backupSize)))
utils.Info("Uploading backup archive to Azure Blob storage ... done ")
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
BackupSize: backupSize,
BackupSize: utils.ConvertBytes(uint64(backupSize)),
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName),

View File

@@ -277,7 +277,6 @@ func localBackup(db *dbConfig, config *BackupConfig) {
utils.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
utils.Info("Backup name is %s", finalFileName)
localStorage := local.NewStorage(local.Config{
LocalPath: tmpPath,
RemotePath: storagePath,
@@ -286,11 +285,13 @@ func localBackup(db *dbConfig, config *BackupConfig) {
if err != nil {
utils.Fatal("Error copying backup file: %s", err)
}
utils.Info("Backup name is %s", finalFileName)
utils.Info("Backup size: %s", utils.ConvertBytes(uint64(backupSize)))
utils.Info("Backup saved in %s", filepath.Join(storagePath, finalFileName))
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
BackupSize: backupSize,
BackupSize: utils.ConvertBytes(uint64(backupSize)),
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(storagePath, finalFileName),

View File

@@ -80,7 +80,7 @@ type FTPConfig struct {
host string
user string
password string
port string
port int
remotePath string
}
type AzureConfig struct {
@@ -94,7 +94,7 @@ type SSHConfig struct {
user string
password string
hostName string
port string
port int
identifyFile string
}
type AWSConfig struct {
@@ -109,6 +109,14 @@ type AWSConfig struct {
}
func initDbConfig(cmd *cobra.Command) *dbConfig {
jdbcUri := os.Getenv("DB_URL")
if len(jdbcUri) != 0 {
config, err := convertJDBCToDbConfig(jdbcUri)
if err != nil {
utils.Fatal("Error: %v", err.Error())
}
return config
}
// Set env
utils.GetEnv(cmd, "dbname", "DB_NAME")
dConf := dbConfig{}
@@ -149,7 +157,7 @@ func loadSSHConfig() (*SSHConfig, error) {
user: os.Getenv("SSH_USER"),
password: os.Getenv("SSH_PASSWORD"),
hostName: os.Getenv("SSH_HOST"),
port: os.Getenv("SSH_PORT"),
port: utils.GetIntEnv("SSH_PORT"),
identifyFile: os.Getenv("SSH_IDENTIFY_FILE"),
}, nil
}
@@ -159,7 +167,7 @@ func loadFtpConfig() *FTPConfig {
fConfig.host = utils.GetEnvVariable("FTP_HOST", "FTP_HOST_NAME")
fConfig.user = os.Getenv("FTP_USER")
fConfig.password = os.Getenv("FTP_PASSWORD")
fConfig.port = os.Getenv("FTP_PORT")
fConfig.port = utils.GetIntEnv("FTP_PORT")
fConfig.remotePath = os.Getenv("REMOTE_PATH")
err := utils.CheckEnvVars(ftpVars)
if err != nil {
@@ -293,6 +301,20 @@ func initRestoreConfig(cmd *cobra.Command) *RestoreConfig {
return &rConfig
}
func initTargetDbConfig() *targetDbConfig {
jdbcUri := os.Getenv("TARGET_DB_URL")
if len(jdbcUri) != 0 {
config, err := convertJDBCToDbConfig(jdbcUri)
if err != nil {
utils.Fatal("Error: %v", err.Error())
}
return &targetDbConfig{
targetDbHost: config.dbHost,
targetDbPort: config.dbPort,
targetDbName: config.dbName,
targetDbPassword: config.dbPassword,
targetDbUserName: config.dbUserName,
}
}
tdbConfig := targetDbConfig{}
tdbConfig.targetDbHost = os.Getenv("TARGET_DB_HOST")
tdbConfig.targetDbPort = utils.EnvWithDefault("TARGET_DB_PORT", "5432")

View File

@@ -29,6 +29,7 @@ import (
"fmt"
"github.com/jkaninda/pg-bkup/utils"
"gopkg.in/yaml.v3"
"net/url"
"os"
"os/exec"
"path/filepath"
@@ -36,7 +37,7 @@ import (
)
func intro() {
fmt.Println("Starting PosgreSQL Backup...")
fmt.Println("Starting PostgresSQL Backup...")
fmt.Printf("Version: %s\n", utils.Version)
fmt.Println("Copyright (c) 2024 Jonas Kaninda")
}
@@ -69,6 +70,9 @@ func deleteTemp() {
func testDatabaseConnection(db *dbConfig) {
utils.Info("Connecting to %s database ...", db.dbName)
// Set database name for notification error
utils.DatabaseName = db.dbName
// Test database connection
query := "SELECT version();"
@@ -193,3 +197,34 @@ func RemoveLastExtension(filename string) string {
}
return filename
}
func convertJDBCToDbConfig(jdbcURI string) (*dbConfig, error) {
// Remove the "jdbc:" prefix
jdbcURI = strings.TrimPrefix(jdbcURI, "jdbc:")
// Parse the URI
u, err := url.Parse(jdbcURI)
if err != nil {
return &dbConfig{}, fmt.Errorf("failed to parse JDBC URI: %v", err)
}
// Extract components
host := u.Hostname()
port := u.Port()
if port == "" {
port = "5432" // Default PostgreSQL port
}
database := strings.TrimPrefix(u.Path, "/")
params, _ := url.ParseQuery(u.RawQuery)
username := params.Get("user")
password := params.Get("password")
// Validate essential fields
if host == "" || database == "" || username == "" {
return &dbConfig{}, fmt.Errorf("incomplete JDBC URI: missing host, database, or username")
}
return &dbConfig{
dbHost: host,
dbPort: port,
dbName: database,
dbUserName: username,
dbPassword: password,
}, nil
}

View File

@@ -56,6 +56,7 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
Port: sshConfig.port,
User: sshConfig.user,
Password: sshConfig.password,
IdentifyFile: sshConfig.identifyFile,
RemotePath: config.remotePath,
LocalPath: tmpPath,
})
@@ -87,11 +88,13 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
}
}
utils.Info("Backup name is %s", finalFileName)
utils.Info("Backup size: %s", utils.ConvertBytes(uint64(backupSize)))
utils.Info("Uploading backup archive to remote storage ... done ")
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
BackupSize: backupSize,
BackupSize: utils.ConvertBytes(uint64(backupSize)),
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName),
@@ -103,72 +106,6 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup completed successfully")
}
func ftpBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to the remote FTP server")
startTime = time.Now().Format(utils.TimeFormat())
// Backup database
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
}
utils.Info("Uploading backup archive to the remote FTP server ... ")
utils.Info("Backup name is %s", finalFileName)
ftpConfig := loadFtpConfig()
ftpStorage, err := ftp.NewStorage(ftp.Config{
Host: ftpConfig.host,
Port: ftpConfig.port,
User: ftpConfig.user,
Password: ftpConfig.password,
RemotePath: config.remotePath,
LocalPath: tmpPath,
})
if err != nil {
utils.Fatal("Error creating SSH storage: %s", err)
}
err = ftpStorage.Copy(finalFileName)
if err != nil {
utils.Fatal("Error copying backup file: %s", err)
}
utils.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
// Get backup info
fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName))
if err != nil {
utils.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
// Delete backup file from tmp folder
err = utils.DeleteFile(filepath.Join(tmpPath, finalFileName))
if err != nil {
utils.Error("Error deleting file: %v", err)
}
if config.prune {
err := ftpStorage.Prune(config.backupRetention)
if err != nil {
utils.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
}
}
utils.Info("Uploading backup archive to the remote FTP server ... done ")
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
BackupSize: backupSize,
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName),
StartTime: startTime,
EndTime: time.Now().Format(utils.TimeFormat()),
})
// Delete temp
deleteTemp()
utils.Info("Backup completed successfully")
}
func remoteRestore(db *dbConfig, conf *RestoreConfig) {
utils.Info("Restore database from remote server")
sshConfig, err := loadSSHConfig()
@@ -214,3 +151,69 @@ func ftpRestore(db *dbConfig, conf *RestoreConfig) {
}
RestoreDatabase(db, conf)
}
func ftpBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to the remote FTP server")
startTime = time.Now().Format(utils.TimeFormat())
// Backup database
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
}
utils.Info("Uploading backup archive to the remote FTP server ... ")
ftpConfig := loadFtpConfig()
ftpStorage, err := ftp.NewStorage(ftp.Config{
Host: ftpConfig.host,
Port: ftpConfig.port,
User: ftpConfig.user,
Password: ftpConfig.password,
RemotePath: config.remotePath,
LocalPath: tmpPath,
})
if err != nil {
utils.Fatal("Error creating SSH storage: %s", err)
}
err = ftpStorage.Copy(finalFileName)
if err != nil {
utils.Fatal("Error copying backup file: %s", err)
}
utils.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
// Get backup info
fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName))
if err != nil {
utils.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
// Delete backup file from tmp folder
err = utils.DeleteFile(filepath.Join(tmpPath, finalFileName))
if err != nil {
utils.Error("Error deleting file: %v", err)
}
if config.prune {
err := ftpStorage.Prune(config.backupRetention)
if err != nil {
utils.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
}
}
utils.Info("Backup name is %s", finalFileName)
utils.Info("Backup size: %s", utils.ConvertBytes(uint64(backupSize)))
utils.Info("Uploading backup archive to the remote FTP server ... done ")
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
BackupSize: utils.ConvertBytes(uint64(backupSize)),
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName),
StartTime: startTime,
EndTime: time.Now().Format(utils.TimeFormat()),
})
// Delete temp
deleteTemp()
utils.Info("Backup completed successfully")
}

View File

@@ -1,4 +1,3 @@
// Package internal /
/*
MIT License

View File

@@ -89,11 +89,12 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
}
}
utils.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
utils.Info("Backup size: %s", utils.ConvertBytes(uint64(backupSize)))
utils.Info("Uploading backup archive to remote storage S3 ... done ")
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
BackupSize: backupSize,
BackupSize: utils.ConvertBytes(uint64(backupSize)),
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName),

View File

@@ -45,14 +45,12 @@ var (
// dbHVars Required environment variables for database
var dbHVars = []string{
"DB_HOST",
"DB_PORT",
"DB_PASSWORD",
"DB_USERNAME",
"DB_NAME",
}
var tdbRVars = []string{
"TARGET_DB_HOST",
"TARGET_DB_PORT",
"TARGET_DB_NAME",
"TARGET_DB_USERNAME",
"TARGET_DB_PASSWORD",

View File

@@ -1,18 +1,69 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>🔴 Urgent: Database Backup Failure Notification</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔴 Urgent: Database Backup Failure</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f8f9fa;
color: #333;
margin: 0;
padding: 20px;
}
h2 {
color: #d9534f;
}
.details {
background-color: #ffffff;
border: 1px solid #ddd;
padding: 15px;
border-radius: 5px;
margin-top: 10px;
}
.details ul {
list-style-type: none;
padding: 0;
}
.details li {
margin: 5px 0;
}
a {
color: #0275d8;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
footer {
margin-top: 20px;
font-size: 0.9em;
color: #6c757d;
}
</style>
</head>
<body>
<h2>Hi,</h2>
<p>An error occurred during database backup.</p>
<h2>🔴 Urgent: Database Backup Failure Notification</h2>
<p>Dear Team,</p>
<p>An error occurred during the database backup process. Please review the details below and take the necessary actions:</p>
<div class="details">
<h3>Failure Details:</h3>
<ul>
<li>Error Message: {{.Error}}</li>
<li>Date: {{.EndTime}}</li>
<li>Backup Reference: {{.BackupReference}} </li>
<li><strong>Database Name:</strong> {{.DatabaseName}}</li>
<li><strong>Date:</strong> {{.EndTime}}</li>
<li><strong>Backup Reference:</strong> {{.BackupReference}}</li>
<li><strong>Error Message:</strong> {{.Error}}</li>
</ul>
<p>©2024 <a href="https://github.com/jkaninda/pg-bkup">pg-bkup</a></p>
</div>
<p>We recommend investigating the issue as soon as possible to prevent potential data loss or service disruptions.</p>
<p>For more information, visit the <a href="https://jkaninda.github.io/pg-bkup">pg-bkup documentation</a>.</p>
<footer>
&copy; 2024 <a href="https://github.com/jkaninda/pg-bkup">pg-bkup</a> | Automated Backup System
</footer>
</body>
</html>

View File

@@ -1,23 +1,70 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>✅ Database Backup Notification {{.Database}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>✅ Database Backup Successful {{.Database}}</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f8f9fa;
color: #333;
margin: 0;
padding: 20px;
}
h2 {
color: #5cb85c;
}
.details {
background-color: #ffffff;
border: 1px solid #ddd;
padding: 15px;
border-radius: 5px;
margin-top: 10px;
}
.details ul {
list-style-type: none;
padding: 0;
}
.details li {
margin: 5px 0;
}
a {
color: #0275d8;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
footer {
margin-top: 20px;
font-size: 0.9em;
color: #6c757d;
}
</style>
</head>
<body>
<h2>Hi,</h2>
<p>Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.</p>
<h2>✅ Database Backup Successful</h2>
<p>Hi,</p>
<p>The backup process for the <strong>{{.Database}}</strong> database was successfully completed. Please find the details below:</p>
<div class="details">
<h3>Backup Details:</h3>
<ul>
<li>Database Name: {{.Database}}</li>
<li>Backup Start Time: {{.StartTime}}</li>
<li>Backup End Time: {{.EndTime}}</li>
<li>Backup Storage: {{.Storage}}</li>
<li>Backup Location: {{.BackupLocation}}</li>
<li>Backup Size: {{.BackupSize}} bytes</li>
<li>Backup Reference: {{.BackupReference}} </li>
<li><strong>Database Name:</strong> {{.Database}}</li>
<li><strong>Backup Start Time:</strong> {{.StartTime}}</li>
<li><strong>Backup End Time:</strong> {{.EndTime}}</li>
<li><strong>Backup Storage:</strong> {{.Storage}}</li>
<li><strong>Backup Location:</strong> {{.BackupLocation}}</li>
<li><strong>Backup Size:</strong> {{.BackupSize}}</li>
<li><strong>Backup Reference:</strong> {{.BackupReference}}</li>
</ul>
<p>Best regards,</p>
<p>©2024 <a href="https://github.com/jkaninda/pg-bkup">pg-bkup</a></p>
</div>
<p>You can access the backup at the specified location if needed. Thank you for using <a href="https://jkaninda.github.io/pg-bkup/">pg-bkup</a>.</p>
<footer>
&copy; 2024 <a href="https://github.com/jkaninda/pg-bkup">pg-bkup</a> | Automated Backup System
</footer>
</body>
</html>

View File

@@ -1,8 +1,11 @@
🔴 Urgent: Database Backup Failure Notification
Hi,
An error occurred during database backup.
Dear Team,
An error occurred during the database backup process.
Please review the details below and take the necessary actions:
Failure Details:
- Database Name: {{.DatabaseName}}
- Date: {{.EndTime}}
- Backup Reference: {{.BackupReference}}
- Error Message: {{.Error}}
We recommend investigating the issue as soon as possible to prevent potential data loss or service disruptions.

View File

@@ -1,6 +1,8 @@
Database Backup Notification {{.Database}}
✅ Database Backup Successful
Hi,
Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.
The backup process for the {{.Database}} database was successfully completed.
Please find the details below:
Backup Details:
- Database Name: {{.Database}}
@@ -8,5 +10,7 @@ Backup Details:
- Backup EndTime: {{.EndTime}}
- Backup Storage: {{.Storage}}
- Backup Location: {{.BackupLocation}}
- Backup Size: {{.BackupSize}} bytes
- Backup Size: {{.BackupSize}}
- Backup Reference: {{.BackupReference}}
You can access the backup at the specified location if needed.

View File

@@ -37,7 +37,7 @@ type MailConfig struct {
}
type NotificationData struct {
File string
BackupSize int64
BackupSize string
Database string
StartTime string
EndTime string
@@ -50,6 +50,7 @@ type ErrorMessage struct {
EndTime string
Error string
BackupReference string
DatabaseName string
}
// loadMailConfig gets mail environment variables and returns MailConfig
@@ -81,3 +82,5 @@ func backupReference() string {
}
const templatePath = "/config/templates"
var DatabaseName = ""

View File

@@ -167,6 +167,7 @@ func NotifyError(error string) {
Error: error,
EndTime: time.Now().Format(TimeFormat()),
BackupReference: os.Getenv("BACKUP_REFERENCE"),
DatabaseName: DatabaseName,
}, "email-error.tmpl")
if err != nil {
Error("Could not parse error template: %v", err)
@@ -183,6 +184,7 @@ func NotifyError(error string) {
Error: error,
EndTime: time.Now().Format(TimeFormat()),
BackupReference: os.Getenv("BACKUP_REFERENCE"),
DatabaseName: DatabaseName,
}, "telegram-error.tmpl")
if err != nil {
Error("Could not parse error template: %v", err)

View File

@@ -254,7 +254,19 @@ func CronNextTime(cronExpr string) time.Time {
next := schedule.Next(now)
return next
}
func UsageErrorf(cmd *cobra.Command, message string, args ...interface{}) error {
msg := fmt.Sprintf(message, args...)
return fmt.Errorf("%s\nSee '%s -h' for help and examples", msg, cmd.CommandPath())
// ConvertBytes converts bytes to a human-readable string with the appropriate unit (bytes, MiB, or GiB).
func ConvertBytes(bytes uint64) string {
const (
MiB = 1024 * 1024
GiB = MiB * 1024
)
switch {
case bytes >= GiB:
return fmt.Sprintf("%.2f GiB", float64(bytes)/float64(GiB))
case bytes >= MiB:
return fmt.Sprintf("%.2f MiB", float64(bytes)/float64(MiB))
default:
return fmt.Sprintf("%d bytes", bytes)
}
}