Compare commits
10 Commits
5ebc707fe6
...
91a440035d
| Author | SHA1 | Date | |
|---|---|---|---|
| 91a440035d | |||
| 1a6e2e4ffc | |||
| 99f76eb5d6 | |||
| 324c5df69c | |||
| 5cca957009 | |||
|
|
f7989a865d | ||
|
|
a9196e6e51 | ||
|
|
b8fabf2bd8 | ||
|
|
c644aa60d2 | ||
|
|
1eb57044ad |
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=
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
103
pkg/remote.go
103
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)
|
||||||
@@ -103,6 +104,51 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
|
|||||||
utils.Info("Backup completed successfully")
|
utils.Info("Backup completed successfully")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
func remoteRestore(db *dbConfig, conf *RestoreConfig) {
|
||||||
|
utils.Info("Restore database from remote server")
|
||||||
|
sshConfig, err := loadSSHConfig()
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatal("Error loading ssh config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sshStorage, err := ssh.NewStorage(ssh.Config{
|
||||||
|
Host: sshConfig.hostName,
|
||||||
|
Port: sshConfig.port,
|
||||||
|
User: sshConfig.user,
|
||||||
|
Password: sshConfig.password,
|
||||||
|
IdentifyFile: sshConfig.identifyFile,
|
||||||
|
RemotePath: conf.remotePath,
|
||||||
|
LocalPath: tmpPath,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatal("Error creating SSH storage: %s", err)
|
||||||
|
}
|
||||||
|
err = sshStorage.CopyFrom(conf.file)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatal("Error copying backup file: %s", err)
|
||||||
|
}
|
||||||
|
RestoreDatabase(db, conf)
|
||||||
|
}
|
||||||
|
func ftpRestore(db *dbConfig, conf *RestoreConfig) {
|
||||||
|
utils.Info("Restore database from FTP server")
|
||||||
|
ftpConfig := loadFtpConfig()
|
||||||
|
ftpStorage, err := ftp.NewStorage(ftp.Config{
|
||||||
|
Host: ftpConfig.host,
|
||||||
|
Port: ftpConfig.port,
|
||||||
|
User: ftpConfig.user,
|
||||||
|
Password: ftpConfig.password,
|
||||||
|
RemotePath: conf.remotePath,
|
||||||
|
LocalPath: tmpPath,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatal("Error creating SSH storage: %s", err)
|
||||||
|
}
|
||||||
|
err = ftpStorage.CopyFrom(conf.file)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatal("Error copying backup file: %s", err)
|
||||||
|
}
|
||||||
|
RestoreDatabase(db, conf)
|
||||||
|
}
|
||||||
func ftpBackup(db *dbConfig, config *BackupConfig) {
|
func ftpBackup(db *dbConfig, config *BackupConfig) {
|
||||||
utils.Info("Backup database to the remote FTP server")
|
utils.Info("Backup database to the remote FTP server")
|
||||||
startTime = time.Now().Format(utils.TimeFormat())
|
startTime = time.Now().Format(utils.TimeFormat())
|
||||||
@@ -169,48 +215,3 @@ func ftpBackup(db *dbConfig, config *BackupConfig) {
|
|||||||
deleteTemp()
|
deleteTemp()
|
||||||
utils.Info("Backup completed successfully")
|
utils.Info("Backup completed successfully")
|
||||||
}
|
}
|
||||||
func remoteRestore(db *dbConfig, conf *RestoreConfig) {
|
|
||||||
utils.Info("Restore database from remote server")
|
|
||||||
sshConfig, err := loadSSHConfig()
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatal("Error loading ssh config: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sshStorage, err := ssh.NewStorage(ssh.Config{
|
|
||||||
Host: sshConfig.hostName,
|
|
||||||
Port: sshConfig.port,
|
|
||||||
User: sshConfig.user,
|
|
||||||
Password: sshConfig.password,
|
|
||||||
IdentifyFile: sshConfig.identifyFile,
|
|
||||||
RemotePath: conf.remotePath,
|
|
||||||
LocalPath: tmpPath,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatal("Error creating SSH storage: %s", err)
|
|
||||||
}
|
|
||||||
err = sshStorage.CopyFrom(conf.file)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatal("Error copying backup file: %s", err)
|
|
||||||
}
|
|
||||||
RestoreDatabase(db, conf)
|
|
||||||
}
|
|
||||||
func ftpRestore(db *dbConfig, conf *RestoreConfig) {
|
|
||||||
utils.Info("Restore database from FTP server")
|
|
||||||
ftpConfig := loadFtpConfig()
|
|
||||||
ftpStorage, err := ftp.NewStorage(ftp.Config{
|
|
||||||
Host: ftpConfig.host,
|
|
||||||
Port: ftpConfig.port,
|
|
||||||
User: ftpConfig.user,
|
|
||||||
Password: ftpConfig.password,
|
|
||||||
RemotePath: conf.remotePath,
|
|
||||||
LocalPath: tmpPath,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatal("Error creating SSH storage: %s", err)
|
|
||||||
}
|
|
||||||
err = ftpStorage.CopyFrom(conf.file)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatal("Error copying backup file: %s", err)
|
|
||||||
}
|
|
||||||
RestoreDatabase(db, conf)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// Package internal /
|
|
||||||
/*
|
/*
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
|||||||
@@ -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>Error Message:</strong> {{.Error}}</li>
|
||||||
|
<li><strong>Date:</strong> {{.EndTime}}</li>
|
||||||
|
<li><strong>Backup Reference:</strong> {{.BackupReference}}</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>Dear Team,</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}} bytes</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,
|
|
||||||
Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.
|
Dear Team,
|
||||||
|
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}}
|
||||||
@@ -10,3 +12,5 @@ Backup Details:
|
|||||||
- Backup Location: {{.BackupLocation}}
|
- Backup Location: {{.BackupLocation}}
|
||||||
- Backup Size: {{.BackupSize}} bytes
|
- Backup Size: {{.BackupSize}} bytes
|
||||||
- Backup Reference: {{.BackupReference}}
|
- Backup Reference: {{.BackupReference}}
|
||||||
|
|
||||||
|
You can access the backup at the specified location if needed.
|
||||||
@@ -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,3 @@ 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...)
|
|
||||||
return fmt.Errorf("%s\nSee '%s -h' for help and examples", msg, cmd.CommandPath())
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user