Compare commits
32 Commits
5ebc707fe6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d9131fa9e | |||
| abf1aeba7c | |||
| ad2f3a1990 | |||
| 0edee0b703 | |||
| fdb79d3cef | |||
| 01915039e7 | |||
| f9dbd0b85c | |||
| 409f9e91f2 | |||
| 93f9595464 | |||
| c89411cfa6 | |||
| 6ec480acda | |||
|
|
0501c151a4 | ||
| 91a440035d | |||
| 1a6e2e4ffc | |||
| 3009747874 | |||
| 99f76eb5d6 | |||
| 5a65c51f54 | |||
| 324c5df69c | |||
| 6f854d5165 | |||
| 5cca957009 | |||
| 05003b386e | |||
|
|
f7989a865d | ||
| 47c7afda4e | |||
|
|
a9196e6e51 | ||
| fc6325e5b6 | |||
|
|
b8fabf2bd8 | ||
| cbeb8b0ba2 | |||
|
|
c644aa60d2 | ||
| 4904e8d0a6 | |||
|
|
1eb57044ad | ||
| 803b077b3d | |||
| 6503e4b5bd |
12
.env.example
12
.env.example
@@ -15,6 +15,7 @@ TZ=Europe/Paris
|
|||||||
|
|
||||||
### Backup restoration
|
### Backup restoration
|
||||||
#FILE_NAME=
|
#FILE_NAME=
|
||||||
|
|
||||||
### AWS S3 Storage
|
### AWS S3 Storage
|
||||||
#ACCESS_KEY=
|
#ACCESS_KEY=
|
||||||
#SECRET_KEY=
|
#SECRET_KEY=
|
||||||
@@ -43,19 +44,30 @@ TZ=Europe/Paris
|
|||||||
#FTP_USER=
|
#FTP_USER=
|
||||||
#FTP_PORT=21
|
#FTP_PORT=21
|
||||||
#REMOTE_PATH=
|
#REMOTE_PATH=
|
||||||
|
|
||||||
|
## Azure Blob Storage
|
||||||
|
AZURE_STORAGE_CONTAINER_NAME=
|
||||||
|
AZURE_STORAGE_ACCOUNT_NAME=
|
||||||
|
AZURE_STORAGE_ACCOUNT_KEY=
|
||||||
|
|
||||||
#### Backup encryption
|
#### Backup encryption
|
||||||
#GPG_PUBLIC_KEY=/config/public_key.asc
|
#GPG_PUBLIC_KEY=/config/public_key.asc
|
||||||
#GPG_PRIVATE_KEY=/config/private_key.asc
|
#GPG_PRIVATE_KEY=/config/private_key.asc
|
||||||
#GPG_PASSPHRASE=Your strong passphrase
|
#GPG_PASSPHRASE=Your strong passphrase
|
||||||
|
|
||||||
## For multiple database backup on Docker or Docker in Swarm mode
|
## For multiple database backup on Docker or Docker in Swarm mode
|
||||||
#BACKUP_CONFIG_FILE=/config/config.yaml
|
#BACKUP_CONFIG_FILE=/config/config.yaml
|
||||||
|
|
||||||
### Database restoration
|
### Database restoration
|
||||||
#FILE_NAME=
|
#FILE_NAME=
|
||||||
|
|
||||||
### Notification
|
### Notification
|
||||||
#BACKUP_REFERENCE=K8s/Paris cluster
|
#BACKUP_REFERENCE=K8s/Paris cluster
|
||||||
|
|
||||||
## Telegram
|
## Telegram
|
||||||
#TG_TOKEN=
|
#TG_TOKEN=
|
||||||
#TG_CHAT_ID=
|
#TG_CHAT_ID=
|
||||||
|
|
||||||
### Email
|
### Email
|
||||||
#MAIL_HOST=
|
#MAIL_HOST=
|
||||||
#MAIL_PORT=
|
#MAIL_PORT=
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.23.3 AS build
|
FROM golang:1.23.4 AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG appVersion=""
|
ARG appVersion=""
|
||||||
# Copy the source code.
|
# Copy the source code.
|
||||||
@@ -9,7 +9,7 @@ RUN go mod download
|
|||||||
# Build
|
# Build
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-X 'github.com/jkaninda/pg-bkup/utils.Version=${appVersion}'" -o /app/pg-bkup
|
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
|
ENV TZ=UTC
|
||||||
ARG WORKDIR="/config"
|
ARG WORKDIR="/config"
|
||||||
ARG BACKUPDIR="/backup"
|
ARG BACKUPDIR="/backup"
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ services:
|
|||||||
- DB_NAME=database
|
- DB_NAME=database
|
||||||
- DB_USERNAME=username
|
- DB_USERNAME=username
|
||||||
- DB_PASSWORD=password
|
- 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
|
# pg-bkup container must be connected to the same network with your database
|
||||||
networks:
|
networks:
|
||||||
- web
|
- web
|
||||||
@@ -67,7 +69,7 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/pg-bkup
|
image: jkaninda/pg-bkup
|
||||||
container_name: pg-bkup
|
container_name: pg-bkup
|
||||||
command: backup -d database --cron-expression "0 1 * * *"
|
command: backup -d database --cron-expression @midnight
|
||||||
volumes:
|
volumes:
|
||||||
- ./backup:/backup
|
- ./backup:/backup
|
||||||
environment:
|
environment:
|
||||||
@@ -76,7 +78,9 @@ services:
|
|||||||
- DB_NAME=database
|
- DB_NAME=database
|
||||||
- DB_USERNAME=username
|
- DB_USERNAME=username
|
||||||
- DB_PASSWORD=password
|
- 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
|
#Delete old backup created more than specified days ago
|
||||||
#- BACKUP_RETENTION_DAYS=7
|
#- BACKUP_RETENTION_DAYS=7
|
||||||
# pg-bkup container must be connected to the same network with your database
|
# pg-bkup container must be connected to the same network with your database
|
||||||
|
|||||||
@@ -38,12 +38,16 @@ services:
|
|||||||
- DB_NAME=database
|
- DB_NAME=database
|
||||||
- DB_USERNAME=username
|
- DB_USERNAME=username
|
||||||
- DB_PASSWORD=password
|
- DB_PASSWORD=password
|
||||||
|
# You can also use JDBC format
|
||||||
|
#- DB_URL=jdbc:postgresql://postgres:5432/database?user=username&password=password
|
||||||
## Target database
|
## Target database
|
||||||
- TARGET_DB_HOST=target-postgres
|
- TARGET_DB_HOST=target-postgres
|
||||||
- TARGET_DB_PORT=5432
|
- TARGET_DB_PORT=5432
|
||||||
- TARGET_DB_NAME=dbname
|
- TARGET_DB_NAME=dbname
|
||||||
- TARGET_DB_USERNAME=username
|
- TARGET_DB_USERNAME=username
|
||||||
- TARGET_DB_PASSWORD=password
|
- 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
|
# mysql-bkup container must be connected to the same network with your database
|
||||||
networks:
|
networks:
|
||||||
- web
|
- web
|
||||||
|
|||||||
@@ -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
|
- AWS S3 or any S3-compatible object storage
|
||||||
- FTP
|
- FTP
|
||||||
- SSH-compatible storage
|
- SSH-compatible storage
|
||||||
|
- Azure Blob storage
|
||||||
|
|
||||||
- **Data Security:**
|
- **Data Security:**
|
||||||
- Backups can be encrypted using **GPG** to ensure confidentiality.
|
- Backups can be encrypted using **GPG** to ensure confidentiality.
|
||||||
|
|||||||
@@ -32,43 +32,48 @@ Backup, restore and migrate targets, schedule and retention are configured using
|
|||||||
|
|
||||||
## Environment variables
|
## Environment variables
|
||||||
|
|
||||||
| Name | Requirement | Description |
|
| Name | Requirement | Description |
|
||||||
|------------------------|---------------------------------------------------------------|-----------------------------------------------------------------|
|
|------------------------------|---------------------------------------------------------------|-----------------------------------------------------------------|
|
||||||
| DB_PORT | Optional, default 5432 | Database port number |
|
| DB_PORT | Optional, default 5432 | Database port number |
|
||||||
| DB_HOST | Required | Database host |
|
| DB_HOST | Required | Database host |
|
||||||
| DB_NAME | Optional if it was provided from the -d flag | Database name |
|
| DB_NAME | Optional if it was provided from the -d flag | Database name |
|
||||||
| DB_USERNAME | Required | Database user name |
|
| DB_USERNAME | Required | Database user name |
|
||||||
| DB_PASSWORD | Required | Database password |
|
| DB_PASSWORD | Required | Database password |
|
||||||
| AWS_ACCESS_KEY | Optional, required for S3 storage | AWS S3 Access Key |
|
| DB_URL | Optional | Database URL in JDBC URI format |
|
||||||
| AWS_SECRET_KEY | Optional, required for S3 storage | AWS S3 Secret Key |
|
| AWS_ACCESS_KEY | Optional, required for S3 storage | AWS S3 Access Key |
|
||||||
| AWS_BUCKET_NAME | Optional, required for S3 storage | AWS S3 Bucket Name |
|
| AWS_SECRET_KEY | Optional, required for S3 storage | AWS S3 Secret Key |
|
||||||
| AWS_BUCKET_NAME | Optional, required for S3 storage | AWS S3 Bucket Name |
|
| AWS_BUCKET_NAME | Optional, required for S3 storage | AWS S3 Bucket Name |
|
||||||
| AWS_REGION | Optional, required for S3 storage | AWS Region |
|
| AWS_BUCKET_NAME | Optional, required for S3 storage | AWS S3 Bucket Name |
|
||||||
| AWS_DISABLE_SSL | Optional, required for S3 storage | Disable SSL |
|
| AWS_REGION | Optional, required for S3 storage | AWS Region |
|
||||||
| AWS_FORCE_PATH_STYLE | Optional, required for S3 storage | Force path style |
|
| AWS_DISABLE_SSL | Optional, required for S3 storage | Disable SSL |
|
||||||
| FILE_NAME | Optional if it was provided from the --file flag | Database file to restore (extensions: .sql, .sql.gz) |
|
| AWS_FORCE_PATH_STYLE | Optional, required for S3 storage | Force path style |
|
||||||
| GPG_PASSPHRASE | Optional, required to encrypt and restore backup | GPG passphrase |
|
| FILE_NAME | Optional if it was provided from the --file flag | Database file to restore (extensions: .sql, .sql.gz) |
|
||||||
| GPG_PUBLIC_KEY | Optional, required to encrypt backup | GPG public key, used to encrypt backup (/config/public_key.asc) |
|
| GPG_PASSPHRASE | Optional, required to encrypt and restore backup | GPG passphrase |
|
||||||
| BACKUP_CRON_EXPRESSION | Optional if it was provided from the `--cron-expression` flag | Backup cron expression for docker in scheduled mode |
|
| GPG_PUBLIC_KEY | Optional, required to encrypt backup | GPG public key, used to encrypt backup (/config/public_key.asc) |
|
||||||
| BACKUP_RETENTION_DAYS | Optional | Delete old backup created more than specified days ago |
|
| BACKUP_CRON_EXPRESSION | Optional if it was provided from the `--cron-expression` flag | Backup cron expression for docker in scheduled mode |
|
||||||
| SSH_HOST | Optional, required for SSH storage | ssh remote hostname or ip |
|
| BACKUP_RETENTION_DAYS | Optional | Delete old backup created more than specified days ago |
|
||||||
| SSH_USER | Optional, required for SSH storage | ssh remote user |
|
| SSH_HOST | Optional, required for SSH storage | ssh remote hostname or ip |
|
||||||
| SSH_PASSWORD | Optional, required for SSH storage | ssh remote user's password |
|
| SSH_USER | Optional, required for SSH storage | ssh remote user |
|
||||||
| SSH_IDENTIFY_FILE | Optional, required for SSH storage | ssh remote user's private key |
|
| SSH_PASSWORD | Optional, required for SSH storage | ssh remote user's password |
|
||||||
| SSH_PORT | Optional, required for SSH storage | ssh remote server port |
|
| SSH_IDENTIFY_FILE | Optional, required for SSH storage | ssh remote user's private key |
|
||||||
| REMOTE_PATH | Optional, required for SSH or FTP storage | remote path (/home/toto/backup) |
|
| SSH_PORT | Optional, required for SSH storage | ssh remote server port |
|
||||||
| FTP_HOST | Optional, required for FTP storage | FTP host name |
|
| REMOTE_PATH | Optional, required for SSH or FTP storage | remote path (/home/toto/backup) |
|
||||||
| FTP_PORT | Optional, required for FTP storage | FTP server port number |
|
| FTP_HOST | Optional, required for FTP storage | FTP host name |
|
||||||
| FTP_USER | Optional, required for FTP storage | FTP user |
|
| FTP_PORT | Optional, required for FTP storage | FTP server port number |
|
||||||
| FTP_PASSWORD | Optional, required for FTP storage | FTP user password |
|
| FTP_USER | Optional, required for FTP storage | FTP user |
|
||||||
| TARGET_DB_HOST | Optional, required for database migration | Target database host |
|
| FTP_PASSWORD | Optional, required for FTP storage | FTP user password |
|
||||||
| TARGET_DB_PORT | Optional, required for database migration | Target database port |
|
| TARGET_DB_HOST | Optional, required for database migration | Target database host |
|
||||||
| TARGET_DB_NAME | Optional, required for database migration | Target database name |
|
| TARGET_DB_PORT | Optional, required for database migration | Target database port |
|
||||||
| TARGET_DB_USERNAME | Optional, required for database migration | Target database username |
|
| TARGET_DB_NAME | Optional, required for database migration | Target database name |
|
||||||
| TARGET_DB_PASSWORD | Optional, required for database migration | Target database password |
|
| TARGET_DB_USERNAME | Optional, required for database migration | Target database username |
|
||||||
| TG_TOKEN | Optional, required for Telegram notification | Telegram token (`BOT-ID:BOT-TOKEN`) |
|
| TARGET_DB_URL | Optional | Database URL in JDBC URI format |
|
||||||
| TG_CHAT_ID | Optional, required for Telegram notification | Telegram Chat ID |
|
| TARGET_DB_PASSWORD | Optional, required for database migration | Target database password |
|
||||||
| TZ | Optional | Time Zone |
|
| 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
|
## Run in Scheduled mode
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -5,7 +5,7 @@ go 1.23.2
|
|||||||
require (
|
require (
|
||||||
github.com/go-mail/mail v2.3.1+incompatible
|
github.com/go-mail/mail v2.3.1+incompatible
|
||||||
github.com/jkaninda/encryptor v0.0.0-20241013064832-ed4bd6a1b221
|
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/robfig/cron/v3 v3.0.1
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -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/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 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.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 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
|
||||||
github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
|
github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
|
||||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -1,4 +1,3 @@
|
|||||||
// Package main /
|
|
||||||
/*
|
/*
|
||||||
MIT License
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/jkaninda/pg-bkup/cmd"
|
import "github.com/jkaninda/pg-bkup/cmd"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func azureBackup(db *dbConfig, config *BackupConfig) {
|
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())
|
startTime = time.Now().Format(utils.TimeFormat())
|
||||||
|
|
||||||
// Backup database
|
// 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 ")
|
utils.Info("Uploading backup archive to Azure Blob storage ... done ")
|
||||||
|
|
||||||
// Send notification
|
// Send notification
|
||||||
utils.NotifySuccess(&utils.NotificationData{
|
utils.NotifySuccess(&utils.NotificationData{
|
||||||
File: finalFileName,
|
File: finalFileName,
|
||||||
BackupSize: backupSize,
|
BackupSize: utils.ConvertBytes(uint64(backupSize)),
|
||||||
Database: db.dbName,
|
Database: db.dbName,
|
||||||
Storage: config.storage,
|
Storage: config.storage,
|
||||||
BackupLocation: filepath.Join(config.remotePath, finalFileName),
|
BackupLocation: filepath.Join(config.remotePath, finalFileName),
|
||||||
|
|||||||
@@ -277,7 +277,6 @@ func localBackup(db *dbConfig, config *BackupConfig) {
|
|||||||
utils.Error("Error: %s", err)
|
utils.Error("Error: %s", err)
|
||||||
}
|
}
|
||||||
backupSize = fileInfo.Size()
|
backupSize = fileInfo.Size()
|
||||||
utils.Info("Backup name is %s", finalFileName)
|
|
||||||
localStorage := local.NewStorage(local.Config{
|
localStorage := local.NewStorage(local.Config{
|
||||||
LocalPath: tmpPath,
|
LocalPath: tmpPath,
|
||||||
RemotePath: storagePath,
|
RemotePath: storagePath,
|
||||||
@@ -286,11 +285,13 @@ func localBackup(db *dbConfig, config *BackupConfig) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatal("Error copying backup file: %s", err)
|
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))
|
utils.Info("Backup saved in %s", filepath.Join(storagePath, finalFileName))
|
||||||
// Send notification
|
// Send notification
|
||||||
utils.NotifySuccess(&utils.NotificationData{
|
utils.NotifySuccess(&utils.NotificationData{
|
||||||
File: finalFileName,
|
File: finalFileName,
|
||||||
BackupSize: backupSize,
|
BackupSize: utils.ConvertBytes(uint64(backupSize)),
|
||||||
Database: db.dbName,
|
Database: db.dbName,
|
||||||
Storage: config.storage,
|
Storage: config.storage,
|
||||||
BackupLocation: filepath.Join(storagePath, finalFileName),
|
BackupLocation: filepath.Join(storagePath, finalFileName),
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ type FTPConfig struct {
|
|||||||
host string
|
host string
|
||||||
user string
|
user string
|
||||||
password string
|
password string
|
||||||
port string
|
port int
|
||||||
remotePath string
|
remotePath string
|
||||||
}
|
}
|
||||||
type AzureConfig struct {
|
type AzureConfig struct {
|
||||||
@@ -94,7 +94,7 @@ type SSHConfig struct {
|
|||||||
user string
|
user string
|
||||||
password string
|
password string
|
||||||
hostName string
|
hostName string
|
||||||
port string
|
port int
|
||||||
identifyFile string
|
identifyFile string
|
||||||
}
|
}
|
||||||
type AWSConfig struct {
|
type AWSConfig struct {
|
||||||
@@ -109,6 +109,14 @@ type AWSConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initDbConfig(cmd *cobra.Command) *dbConfig {
|
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
|
// Set env
|
||||||
utils.GetEnv(cmd, "dbname", "DB_NAME")
|
utils.GetEnv(cmd, "dbname", "DB_NAME")
|
||||||
dConf := dbConfig{}
|
dConf := dbConfig{}
|
||||||
@@ -149,7 +157,7 @@ func loadSSHConfig() (*SSHConfig, error) {
|
|||||||
user: os.Getenv("SSH_USER"),
|
user: os.Getenv("SSH_USER"),
|
||||||
password: os.Getenv("SSH_PASSWORD"),
|
password: os.Getenv("SSH_PASSWORD"),
|
||||||
hostName: os.Getenv("SSH_HOST"),
|
hostName: os.Getenv("SSH_HOST"),
|
||||||
port: os.Getenv("SSH_PORT"),
|
port: utils.GetIntEnv("SSH_PORT"),
|
||||||
identifyFile: os.Getenv("SSH_IDENTIFY_FILE"),
|
identifyFile: os.Getenv("SSH_IDENTIFY_FILE"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -159,7 +167,7 @@ func loadFtpConfig() *FTPConfig {
|
|||||||
fConfig.host = utils.GetEnvVariable("FTP_HOST", "FTP_HOST_NAME")
|
fConfig.host = utils.GetEnvVariable("FTP_HOST", "FTP_HOST_NAME")
|
||||||
fConfig.user = os.Getenv("FTP_USER")
|
fConfig.user = os.Getenv("FTP_USER")
|
||||||
fConfig.password = os.Getenv("FTP_PASSWORD")
|
fConfig.password = os.Getenv("FTP_PASSWORD")
|
||||||
fConfig.port = os.Getenv("FTP_PORT")
|
fConfig.port = utils.GetIntEnv("FTP_PORT")
|
||||||
fConfig.remotePath = os.Getenv("REMOTE_PATH")
|
fConfig.remotePath = os.Getenv("REMOTE_PATH")
|
||||||
err := utils.CheckEnvVars(ftpVars)
|
err := utils.CheckEnvVars(ftpVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -293,6 +301,20 @@ func initRestoreConfig(cmd *cobra.Command) *RestoreConfig {
|
|||||||
return &rConfig
|
return &rConfig
|
||||||
}
|
}
|
||||||
func initTargetDbConfig() *targetDbConfig {
|
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 := targetDbConfig{}
|
||||||
tdbConfig.targetDbHost = os.Getenv("TARGET_DB_HOST")
|
tdbConfig.targetDbHost = os.Getenv("TARGET_DB_HOST")
|
||||||
tdbConfig.targetDbPort = utils.EnvWithDefault("TARGET_DB_PORT", "5432")
|
tdbConfig.targetDbPort = utils.EnvWithDefault("TARGET_DB_PORT", "5432")
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/jkaninda/pg-bkup/utils"
|
"github.com/jkaninda/pg-bkup/utils"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -36,7 +37,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func intro() {
|
func intro() {
|
||||||
fmt.Println("Starting PosgreSQL Backup...")
|
fmt.Println("Starting PostgresSQL Backup...")
|
||||||
fmt.Printf("Version: %s\n", utils.Version)
|
fmt.Printf("Version: %s\n", utils.Version)
|
||||||
fmt.Println("Copyright (c) 2024 Jonas Kaninda")
|
fmt.Println("Copyright (c) 2024 Jonas Kaninda")
|
||||||
}
|
}
|
||||||
@@ -69,6 +70,9 @@ func deleteTemp() {
|
|||||||
func testDatabaseConnection(db *dbConfig) {
|
func testDatabaseConnection(db *dbConfig) {
|
||||||
|
|
||||||
utils.Info("Connecting to %s database ...", db.dbName)
|
utils.Info("Connecting to %s database ...", db.dbName)
|
||||||
|
// Set database name for notification error
|
||||||
|
utils.DatabaseName = db.dbName
|
||||||
|
|
||||||
// Test database connection
|
// Test database connection
|
||||||
query := "SELECT version();"
|
query := "SELECT version();"
|
||||||
|
|
||||||
@@ -193,3 +197,34 @@ func RemoveLastExtension(filename string) string {
|
|||||||
}
|
}
|
||||||
return filename
|
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
|
||||||
|
}
|
||||||
|
|||||||
149
pkg/remote.go
149
pkg/remote.go
@@ -52,12 +52,13 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sshStorage, err := ssh.NewStorage(ssh.Config{
|
sshStorage, err := ssh.NewStorage(ssh.Config{
|
||||||
Host: sshConfig.hostName,
|
Host: sshConfig.hostName,
|
||||||
Port: sshConfig.port,
|
Port: sshConfig.port,
|
||||||
User: sshConfig.user,
|
User: sshConfig.user,
|
||||||
Password: sshConfig.password,
|
Password: sshConfig.password,
|
||||||
RemotePath: config.remotePath,
|
IdentifyFile: sshConfig.identifyFile,
|
||||||
LocalPath: tmpPath,
|
RemotePath: config.remotePath,
|
||||||
|
LocalPath: tmpPath,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatal("Error creating SSH storage: %s", err)
|
utils.Fatal("Error creating SSH storage: %s", err)
|
||||||
@@ -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 ")
|
utils.Info("Uploading backup archive to remote storage ... done ")
|
||||||
// Send notification
|
// Send notification
|
||||||
utils.NotifySuccess(&utils.NotificationData{
|
utils.NotifySuccess(&utils.NotificationData{
|
||||||
File: finalFileName,
|
File: finalFileName,
|
||||||
BackupSize: backupSize,
|
BackupSize: utils.ConvertBytes(uint64(backupSize)),
|
||||||
Database: db.dbName,
|
Database: db.dbName,
|
||||||
Storage: config.storage,
|
Storage: config.storage,
|
||||||
BackupLocation: filepath.Join(config.remotePath, finalFileName),
|
BackupLocation: filepath.Join(config.remotePath, finalFileName),
|
||||||
@@ -103,72 +106,6 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
|
|||||||
utils.Info("Backup completed successfully")
|
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) {
|
func remoteRestore(db *dbConfig, conf *RestoreConfig) {
|
||||||
utils.Info("Restore database from remote server")
|
utils.Info("Restore database from remote server")
|
||||||
sshConfig, err := loadSSHConfig()
|
sshConfig, err := loadSSHConfig()
|
||||||
@@ -214,3 +151,69 @@ func ftpRestore(db *dbConfig, conf *RestoreConfig) {
|
|||||||
}
|
}
|
||||||
RestoreDatabase(db, conf)
|
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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// Package internal /
|
|
||||||
/*
|
/*
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
|||||||
@@ -89,11 +89,12 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
utils.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
|
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 ")
|
utils.Info("Uploading backup archive to remote storage S3 ... done ")
|
||||||
// Send notification
|
// Send notification
|
||||||
utils.NotifySuccess(&utils.NotificationData{
|
utils.NotifySuccess(&utils.NotificationData{
|
||||||
File: finalFileName,
|
File: finalFileName,
|
||||||
BackupSize: backupSize,
|
BackupSize: utils.ConvertBytes(uint64(backupSize)),
|
||||||
Database: db.dbName,
|
Database: db.dbName,
|
||||||
Storage: config.storage,
|
Storage: config.storage,
|
||||||
BackupLocation: filepath.Join(config.remotePath, finalFileName),
|
BackupLocation: filepath.Join(config.remotePath, finalFileName),
|
||||||
|
|||||||
@@ -45,14 +45,12 @@ var (
|
|||||||
// dbHVars Required environment variables for database
|
// dbHVars Required environment variables for database
|
||||||
var dbHVars = []string{
|
var dbHVars = []string{
|
||||||
"DB_HOST",
|
"DB_HOST",
|
||||||
"DB_PORT",
|
|
||||||
"DB_PASSWORD",
|
"DB_PASSWORD",
|
||||||
"DB_USERNAME",
|
"DB_USERNAME",
|
||||||
"DB_NAME",
|
"DB_NAME",
|
||||||
}
|
}
|
||||||
var tdbRVars = []string{
|
var tdbRVars = []string{
|
||||||
"TARGET_DB_HOST",
|
"TARGET_DB_HOST",
|
||||||
"TARGET_DB_PORT",
|
|
||||||
"TARGET_DB_NAME",
|
"TARGET_DB_NAME",
|
||||||
"TARGET_DB_USERNAME",
|
"TARGET_DB_USERNAME",
|
||||||
"TARGET_DB_PASSWORD",
|
"TARGET_DB_PASSWORD",
|
||||||
|
|||||||
@@ -1,18 +1,69 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Hi,</h2>
|
<h2>🔴 Urgent: Database Backup Failure Notification</h2>
|
||||||
<p>An error occurred during database backup.</p>
|
<p>Dear Team,</p>
|
||||||
<h3>Failure Details:</h3>
|
<p>An error occurred during the database backup process. Please review the details below and take the necessary actions:</p>
|
||||||
<ul>
|
|
||||||
<li>Error Message: {{.Error}}</li>
|
<div class="details">
|
||||||
<li>Date: {{.EndTime}}</li>
|
<h3>Failure Details:</h3>
|
||||||
<li>Backup Reference: {{.BackupReference}} </li>
|
<ul>
|
||||||
</ul>
|
<li><strong>Database Name:</strong> {{.DatabaseName}}</li>
|
||||||
<p>©2024 <a href="https://github.com/jkaninda/pg-bkup">pg-bkup</a></p>
|
<li><strong>Date:</strong> {{.EndTime}}</li>
|
||||||
|
<li><strong>Backup Reference:</strong> {{.BackupReference}}</li>
|
||||||
|
<li><strong>Error Message:</strong> {{.Error}}</li>
|
||||||
|
</ul>
|
||||||
|
</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>
|
||||||
|
© 2024 <a href="https://github.com/jkaninda/pg-bkup">pg-bkup</a> | Automated Backup System
|
||||||
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,23 +1,70 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Hi,</h2>
|
<h2>✅ Database Backup Successful</h2>
|
||||||
<p>Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.</p>
|
<p>Hi,</p>
|
||||||
<h3>Backup Details:</h3>
|
<p>The backup process for the <strong>{{.Database}}</strong> database was successfully completed. Please find the details below:</p>
|
||||||
<ul>
|
|
||||||
<li>Database Name: {{.Database}}</li>
|
<div class="details">
|
||||||
<li>Backup Start Time: {{.StartTime}}</li>
|
<h3>Backup Details:</h3>
|
||||||
<li>Backup End Time: {{.EndTime}}</li>
|
<ul>
|
||||||
<li>Backup Storage: {{.Storage}}</li>
|
<li><strong>Database Name:</strong> {{.Database}}</li>
|
||||||
<li>Backup Location: {{.BackupLocation}}</li>
|
<li><strong>Backup Start Time:</strong> {{.StartTime}}</li>
|
||||||
<li>Backup Size: {{.BackupSize}} bytes</li>
|
<li><strong>Backup End Time:</strong> {{.EndTime}}</li>
|
||||||
<li>Backup Reference: {{.BackupReference}} </li>
|
<li><strong>Backup Storage:</strong> {{.Storage}}</li>
|
||||||
</ul>
|
<li><strong>Backup Location:</strong> {{.BackupLocation}}</li>
|
||||||
<p>Best regards,</p>
|
<li><strong>Backup Size:</strong> {{.BackupSize}}</li>
|
||||||
<p>©2024 <a href="https://github.com/jkaninda/pg-bkup">pg-bkup</a></p>
|
<li><strong>Backup Reference:</strong> {{.BackupReference}}</li>
|
||||||
|
</ul>
|
||||||
|
</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>
|
||||||
|
© 2024 <a href="https://github.com/jkaninda/pg-bkup">pg-bkup</a> | Automated Backup System
|
||||||
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
🔴 Urgent: Database Backup Failure Notification
|
🔴 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:
|
Failure Details:
|
||||||
|
- Database Name: {{.DatabaseName}}
|
||||||
- Date: {{.EndTime}}
|
- Date: {{.EndTime}}
|
||||||
- Backup Reference: {{.BackupReference}}
|
- Backup Reference: {{.BackupReference}}
|
||||||
- Error Message: {{.Error}}
|
- Error Message: {{.Error}}
|
||||||
|
We recommend investigating the issue as soon as possible to prevent potential data loss or service disruptions.
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
✅ Database Backup Notification – {{.Database}}
|
✅ Database Backup Successful
|
||||||
|
|
||||||
Hi,
|
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:
|
Backup Details:
|
||||||
- Database Name: {{.Database}}
|
- Database Name: {{.Database}}
|
||||||
@@ -8,5 +10,7 @@ Backup Details:
|
|||||||
- Backup EndTime: {{.EndTime}}
|
- Backup EndTime: {{.EndTime}}
|
||||||
- Backup Storage: {{.Storage}}
|
- Backup Storage: {{.Storage}}
|
||||||
- Backup Location: {{.BackupLocation}}
|
- Backup Location: {{.BackupLocation}}
|
||||||
- Backup Size: {{.BackupSize}} bytes
|
- Backup Size: {{.BackupSize}}
|
||||||
- Backup Reference: {{.BackupReference}}
|
- Backup Reference: {{.BackupReference}}
|
||||||
|
|
||||||
|
You can access the backup at the specified location if needed.
|
||||||
@@ -37,7 +37,7 @@ type MailConfig struct {
|
|||||||
}
|
}
|
||||||
type NotificationData struct {
|
type NotificationData struct {
|
||||||
File string
|
File string
|
||||||
BackupSize int64
|
BackupSize string
|
||||||
Database string
|
Database string
|
||||||
StartTime string
|
StartTime string
|
||||||
EndTime string
|
EndTime string
|
||||||
@@ -50,6 +50,7 @@ type ErrorMessage struct {
|
|||||||
EndTime string
|
EndTime string
|
||||||
Error string
|
Error string
|
||||||
BackupReference string
|
BackupReference string
|
||||||
|
DatabaseName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadMailConfig gets mail environment variables and returns MailConfig
|
// loadMailConfig gets mail environment variables and returns MailConfig
|
||||||
@@ -81,3 +82,5 @@ func backupReference() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const templatePath = "/config/templates"
|
const templatePath = "/config/templates"
|
||||||
|
|
||||||
|
var DatabaseName = ""
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ func NotifyError(error string) {
|
|||||||
Error: error,
|
Error: error,
|
||||||
EndTime: time.Now().Format(TimeFormat()),
|
EndTime: time.Now().Format(TimeFormat()),
|
||||||
BackupReference: os.Getenv("BACKUP_REFERENCE"),
|
BackupReference: os.Getenv("BACKUP_REFERENCE"),
|
||||||
|
DatabaseName: DatabaseName,
|
||||||
}, "email-error.tmpl")
|
}, "email-error.tmpl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("Could not parse error template: %v", err)
|
Error("Could not parse error template: %v", err)
|
||||||
@@ -183,6 +184,7 @@ func NotifyError(error string) {
|
|||||||
Error: error,
|
Error: error,
|
||||||
EndTime: time.Now().Format(TimeFormat()),
|
EndTime: time.Now().Format(TimeFormat()),
|
||||||
BackupReference: os.Getenv("BACKUP_REFERENCE"),
|
BackupReference: os.Getenv("BACKUP_REFERENCE"),
|
||||||
|
DatabaseName: DatabaseName,
|
||||||
}, "telegram-error.tmpl")
|
}, "telegram-error.tmpl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("Could not parse error template: %v", err)
|
Error("Could not parse error template: %v", err)
|
||||||
|
|||||||
@@ -254,7 +254,19 @@ func CronNextTime(cronExpr string) time.Time {
|
|||||||
next := schedule.Next(now)
|
next := schedule.Next(now)
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
func UsageErrorf(cmd *cobra.Command, message string, args ...interface{}) error {
|
|
||||||
msg := fmt.Sprintf(message, args...)
|
// ConvertBytes converts bytes to a human-readable string with the appropriate unit (bytes, MiB, or GiB).
|
||||||
return fmt.Errorf("%s\nSee '%s -h' for help and examples", msg, cmd.CommandPath())
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user