11#! /usr/bin/env bash
22#
3- # backup.sh
3+ # backup.sh - stops the pg-node stack, archives /opt/pg-node and
4+ # /var/lib/pg-node into backup.tar.gz, then restarts the stack.
45#
5- # Stops the pg-node Docker Compose stack, archives its application
6- # and data directories into backup.tar.gz, then restarts the stack.
7- #
8- # Fixed application paths:
9- # /opt/pg-node -> application / compose project
10- # /var/lib/pg-node -> persistent data
11- # /opt/pg-node/docker-compose.yml -> compose file
12- #
13- # Usage:
14- # sudo ./backup.sh
6+ # Usage: sudo ./backup.sh
157#
168set -Eeuo pipefail
179
18- # --------------------------------------------------------------------------
19- # Constants
20- # --------------------------------------------------------------------------
2110readonly APP_DIR=" /opt/pg-node"
2211readonly DATA_DIR=" /var/lib/pg-node"
2312readonly COMPOSE_FILE=" ${APP_DIR} /docker-compose.yml"
2413readonly BACKUP_FILE=" backup.tar.gz"
2514readonly BACKUP_PATH=" $( pwd) /${BACKUP_FILE} "
15+ readonly LOG_FILE=" /var/log/pg-node-backup.log"
2616
27- # --------------------------------------------------------------------------
28- # Colors
29- # --------------------------------------------------------------------------
3017readonly COLOR_RED=" \033[0;31m"
3118readonly COLOR_GREEN=" \033[0;32m"
3219readonly COLOR_YELLOW=" \033[0;33m"
@@ -38,52 +25,80 @@ log_ok() { echo -e "${COLOR_GREEN}[ OK ]${COLOR_RESET} $*"; }
3825log_warn () { echo -e " ${COLOR_YELLOW} [WARN]${COLOR_RESET} $* " ; }
3926log_err () { echo -e " ${COLOR_RED} [FAIL]${COLOR_RESET} $* " >&2 ; }
4027
41- # --------------------------------------------------------------------------
42- # Error handling / cleanup
43- # --------------------------------------------------------------------------
28+ # Runs a command with output hidden (only a spinner is shown); full output
29+ # goes to LOG_FILE.
30+ run_quiet () {
31+ local desc=" $1 "
32+ shift
33+
34+ echo " ----- $( date ' +%Y-%m-%d %H:%M:%S' ) :: ${desc} -----" >> " ${LOG_FILE} "
35+
36+ ( " $@ " ) >> " ${LOG_FILE} " 2>&1 &
37+ local pid=$!
38+
39+ if [[ -t 1 ]]; then
40+ local frames=' |/-\'
41+ local i=0
42+ while kill -0 " ${pid} " 2> /dev/null; do
43+ i=$(( (i + 1 ) % 4 ))
44+ printf " \r${COLOR_BLUE} [....]${COLOR_RESET} %s %s" " ${desc} " " ${frames: $i : 1} "
45+ sleep 0.2
46+ done
47+ else
48+ printf " ${COLOR_BLUE} [....]${COLOR_RESET} %s\n" " ${desc} "
49+ fi
50+
51+ wait " ${pid} "
52+ local exit_code=$?
53+
54+ if [[ " ${exit_code} " -eq 0 ]]; then
55+ printf " \r${COLOR_GREEN} [ OK ]${COLOR_RESET} %s\n" " ${desc} "
56+ else
57+ printf " \r${COLOR_RED} [FAIL]${COLOR_RESET} %s (see: %s)\n" " ${desc} " " ${LOG_FILE} "
58+ fi
59+
60+ return " ${exit_code} "
61+ }
62+
63+ init_log () {
64+ mkdir -p " $( dirname " ${LOG_FILE} " ) "
65+ : > " ${LOG_FILE} "
66+ log_info " Logging full output to: ${LOG_FILE} "
67+ }
68+
4469STACK_STOPPED=0
4570
46- # Always try to bring the stack back up if we stopped it and something
47- # failed afterwards, so we don't leave the service down.
4871on_error () {
4972 local exit_code=$?
50- log_err " An error occurred (exit code ${exit_code} ) on line ${BASH_LINENO[0]} ."
73+ log_err " Error (exit code ${exit_code} ) on line ${BASH_LINENO[0]} . See ${LOG_FILE} "
5174 if [[ " ${STACK_STOPPED} " -eq 1 ]]; then
52- log_warn " Attempting to restart the Docker Compose stack after failure..."
53- docker compose -f " ${COMPOSE_FILE} " up -d || \
54- log_err " Failed to restart the stack automatically. Please check manually."
75+ log_warn " Attempting to restart the stack after failure..."
76+ docker compose -f " ${COMPOSE_FILE} " up -d || log_err " Failed to restart the stack automatically."
5577 fi
5678 exit " ${exit_code} "
5779}
5880trap on_error ERR
5981
60- # --------------------------------------------------------------------------
61- # Checks
62- # --------------------------------------------------------------------------
63-
64- # 1. Verify root privileges
6582check_root () {
6683 if [[ " ${EUID} " -ne 0 ]]; then
6784 log_err " This script must be run as root (use sudo)."
6885 exit 1
6986 fi
70- log_ok " Running with root privileges ."
87+ log_ok " Running as root."
7188}
7289
73- # 2. Verify Docker is installed
7490check_docker () {
7591 if ! command -v docker & > /dev/null; then
76- log_err " Docker is not installed. Please install Docker before running this script. "
92+ log_err " Docker is not installed."
7793 exit 1
7894 fi
7995 if ! docker compose version & > /dev/null; then
8096 log_err " Docker Compose plugin is not installed."
8197 exit 1
8298 fi
83- log_ok " Docker and Docker Compose plugin are present."
99+ log_ok " Docker and Compose plugin present."
84100}
85101
86- # Verify required paths exist
87102check_paths () {
88103 if [[ ! -f " ${COMPOSE_FILE} " ]]; then
89104 log_err " Compose file not found at ${COMPOSE_FILE} ."
@@ -94,29 +109,17 @@ check_paths() {
94109 exit 1
95110 fi
96111 if [[ ! -d " ${DATA_DIR} " ]]; then
97- log_warn " Data directory ${DATA_DIR} does not exist. It will be skipped in the archive ."
112+ log_warn " Data directory ${DATA_DIR} does not exist; it will be skipped."
98113 fi
99114 log_ok " Required paths verified."
100115}
101116
102- # --------------------------------------------------------------------------
103- # Steps
104- # --------------------------------------------------------------------------
105-
106- # 3. Stop the Docker Compose project
107117stop_stack () {
108- log_info " Stopping Docker Compose stack..."
109- docker compose -f " ${COMPOSE_FILE} " down
118+ run_quiet " Stopping Docker Compose stack" docker compose -f " ${COMPOSE_FILE} " down
110119 STACK_STOPPED=1
111- log_ok " Stack stopped."
112120}
113121
114- # 4. Create compressed archive
115122create_backup () {
116- log_info " Creating backup archive: ${BACKUP_FILE} "
117-
118- # Build list of existing directories only, to avoid tar failing
119- # if one of them is missing.
120123 local targets=()
121124 [[ -d " ${APP_DIR} " ]] && targets+=(" ${APP_DIR} " )
122125 [[ -d " ${DATA_DIR} " ]] && targets+=(" ${DATA_DIR} " )
@@ -126,36 +129,30 @@ create_backup() {
126129 exit 1
127130 fi
128131
129- # Archive with paths relative to / so restore can extract to / directly .
130- tar -czpf " ${BACKUP_PATH} " -C / \
132+ # Paths relative to / so restore.sh can extract straight to /.
133+ run_quiet " Creating backup archive " tar -czpf " ${BACKUP_PATH} " -C / \
131134 $( printf ' %s\n' " ${targets[@]} " | sed ' s|^/||' )
132135
133- log_ok " Archive created at ${BACKUP_PATH} "
136+ log_ok " Archive created: ${BACKUP_PATH} "
134137}
135138
136- # 5. Start the project again
137139start_stack () {
138- log_info " Starting Docker Compose stack..."
139- docker compose -f " ${COMPOSE_FILE} " up -d
140+ run_quiet " Starting Docker Compose stack" docker compose -f " ${COMPOSE_FILE} " up -d
140141 STACK_STOPPED=0
141- log_ok " Stack started."
142142}
143143
144- # 6. Print backup size and success message
145144print_summary () {
146145 local size
147146 size=$( du -h " ${BACKUP_PATH} " | cut -f1)
148147 echo
149148 log_ok " Backup completed successfully."
150- log_info " Backup file : ${BACKUP_PATH} "
151- log_info " Backup size : ${size} "
149+ log_info " File : ${BACKUP_PATH} "
150+ log_info " Size : ${size} "
152151}
153152
154- # --------------------------------------------------------------------------
155- # Main
156- # --------------------------------------------------------------------------
157153main () {
158154 check_root
155+ init_log
159156 check_docker
160157 check_paths
161158 stop_stack
0 commit comments