|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# ============================================================================= |
| 4 | +# Restore a PostgreSQL backup from the "postgres/backups" directory |
| 5 | +# ============================================================================= |
| 6 | + |
| 7 | +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
| 8 | +BACKUPS_DIR="$SCRIPT_DIR/../postgres/backups" |
| 9 | + |
| 10 | +# ============================================================================= |
| 11 | +# Color output helpers |
| 12 | +# ============================================================================= |
| 13 | + |
| 14 | +RED='\033[0;31m' |
| 15 | +ORANGE='\033[0;33m' |
| 16 | +GREEN='\033[0;32m' |
| 17 | +NC='\033[0m' |
| 18 | + |
| 19 | +print_error() { echo -e "${RED}$1${NC}"; } |
| 20 | +print_warning() { echo -e "${ORANGE}$1${NC}"; } |
| 21 | +print_info() { echo -e "${GREEN}$1${NC}"; } |
| 22 | + |
| 23 | +# ============================================================================= |
| 24 | +# Section 1: Scan backup directory and display file list |
| 25 | +# ============================================================================= |
| 26 | + |
| 27 | +files=() |
| 28 | +if [ -d "$BACKUPS_DIR" ]; then |
| 29 | + while IFS= read -r -d '' file; do |
| 30 | + files+=("$(basename "$file")") |
| 31 | + done < <(find "$BACKUPS_DIR" -maxdepth 1 -type f -print0 | sort -z) |
| 32 | +fi |
| 33 | + |
| 34 | +if [ ${#files[@]} -eq 0 ]; then |
| 35 | + print_warning 'No backup files were found in the "postgres/backups" directory.' |
| 36 | + exit 0 |
| 37 | +fi |
| 38 | + |
| 39 | +print_info "Available backup files:" |
| 40 | +echo "" |
| 41 | +for i in "${!files[@]}"; do |
| 42 | + echo " $((i + 1)). ${files[$i]}" |
| 43 | +done |
| 44 | +echo "" |
| 45 | + |
| 46 | +# ============================================================================= |
| 47 | +# Section 2: User selects a backup file by number |
| 48 | +# ============================================================================= |
| 49 | + |
| 50 | +while true; do |
| 51 | + read -r -p "Select a backup file and enter its number (1-${#files[@]}): " choice |
| 52 | + |
| 53 | + if ! [[ "$choice" =~ ^[0-9]+$ ]]; then |
| 54 | + print_error "Enter the file number" |
| 55 | + continue |
| 56 | + fi |
| 57 | + |
| 58 | + if [ "$choice" -lt 1 ] || [ "$choice" -gt ${#files[@]} ]; then |
| 59 | + print_error "Entered number does not match any file from the list" |
| 60 | + continue |
| 61 | + fi |
| 62 | + |
| 63 | + BACKUP_FILENAME="${files[$((choice - 1))]}" |
| 64 | + break |
| 65 | +done |
| 66 | + |
| 67 | +echo "" |
| 68 | +print_info "Selected file: $BACKUP_FILENAME" |
| 69 | +echo "" |
| 70 | + |
| 71 | +# ============================================================================= |
| 72 | +# Section 2.1: Select docker-compose configuration to start containers |
| 73 | +# ============================================================================= |
| 74 | + |
| 75 | +echo "" |
| 76 | +print_info "Select a docker-compose configuration to start containers after restoring the backup:" |
| 77 | +echo "" |
| 78 | +echo " 1. Root (from sources)" |
| 79 | +echo " 2. Root (stable)" |
| 80 | +echo " 3. Root (latest)" |
| 81 | +echo " 4. Frontend" |
| 82 | +echo " 5. Backend" |
| 83 | +echo "" |
| 84 | + |
| 85 | +# 2.1.2 User selects a configuration by number |
| 86 | +while true; do |
| 87 | + read -r -p "Enter configuration number (1-5): " COMPOSE_FILE |
| 88 | + |
| 89 | + if ! [[ "$COMPOSE_FILE" =~ ^[0-9]+$ ]]; then |
| 90 | + print_error "Enter the file number" |
| 91 | + continue |
| 92 | + fi |
| 93 | + |
| 94 | + if [ "$COMPOSE_FILE" -lt 1 ] || [ "$COMPOSE_FILE" -gt 5 ]; then |
| 95 | + print_error "Entered number does not match any file from the list" |
| 96 | + continue |
| 97 | + fi |
| 98 | + |
| 99 | + break |
| 100 | +done |
| 101 | + |
| 102 | +case "$COMPOSE_FILE" in |
| 103 | + 1) COMPOSE_LABEL="Root (from sources)" ;; |
| 104 | + 2) COMPOSE_LABEL="Root (stable)" ;; |
| 105 | + 3) COMPOSE_LABEL="Root (latest)" ;; |
| 106 | + 4) COMPOSE_LABEL="Frontend" ;; |
| 107 | + 5) COMPOSE_LABEL="Backend" ;; |
| 108 | +esac |
| 109 | + |
| 110 | +echo "" |
| 111 | +print_info "Selected configuration: $COMPOSE_LABEL" |
| 112 | +echo "" |
| 113 | + |
| 114 | +# ============================================================================= |
| 115 | +# Section 2.2: Select .env file based on docker-compose choice (section 2.1) |
| 116 | +# ============================================================================= |
| 117 | + |
| 118 | +case "$COMPOSE_FILE" in |
| 119 | + 1|2|3) ENV_FILE="$SCRIPT_DIR/../.env" ;; |
| 120 | + 4) ENV_FILE="$SCRIPT_DIR/../frontend/.env" ;; |
| 121 | + 5) ENV_FILE="$SCRIPT_DIR/../backend/.env" ;; |
| 122 | +esac |
| 123 | + |
| 124 | +if [ ! -f "$ENV_FILE" ]; then |
| 125 | + print_error "Error: .env file not found at $ENV_FILE. Cannot proceed without database configuration." |
| 126 | + exit 1 |
| 127 | +fi |
| 128 | + |
| 129 | +# ============================================================================= |
| 130 | +# Section 3: Read .env file and extract database configuration |
| 131 | +# ============================================================================= |
| 132 | + |
| 133 | +POSTGRES_HOST="postgres" |
| 134 | +POSTGRES_USER="postgres_user" |
| 135 | +POSTGRES_PASSWORD="postgres_password" |
| 136 | +POSTGRES_DB="postgres_db" |
| 137 | + |
| 138 | +while IFS='=' read -r key value || [ -n "$key" ]; do |
| 139 | + key=$(echo "$key" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') |
| 140 | + value=$(echo "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') |
| 141 | + |
| 142 | + [ -z "$key" ] && continue |
| 143 | + [[ "$key" == \#* ]] && continue |
| 144 | + |
| 145 | + value=$(echo "$value" | sed 's/[[:space:]]\+#.*$//;s/[[:space:]]*$//') |
| 146 | + |
| 147 | + case "$key" in |
| 148 | + POSTGRES_HOST) POSTGRES_HOST="$value" ;; |
| 149 | + POSTGRES_USER) POSTGRES_USER="$value" ;; |
| 150 | + POSTGRES_PASSWORD) POSTGRES_PASSWORD="$value" ;; |
| 151 | + POSTGRES_DB) POSTGRES_DB="$value" ;; |
| 152 | + esac |
| 153 | +done < "$ENV_FILE" |
| 154 | + |
| 155 | +# ============================================================================= |
| 156 | +# Section 3.1: Confirm database overwrite |
| 157 | +# ============================================================================= |
| 158 | + |
| 159 | +while true; do |
| 160 | + print_warning "WARNING: This action will overwrite the database \"$POSTGRES_DB\"." |
| 161 | + read -r -p "Continue? (y/n): " confirm |
| 162 | + |
| 163 | + case "$confirm" in |
| 164 | + y) break ;; |
| 165 | + n) print_warning "Operation cancelled."; exit 0 ;; |
| 166 | + *) ;; |
| 167 | + esac |
| 168 | +done |
| 169 | + |
| 170 | +echo "" |
| 171 | + |
| 172 | +PROJECT_DIR="$SCRIPT_DIR/.." |
| 173 | + |
| 174 | +# ============================================================================= |
| 175 | +# Section 4: Prepare containers |
| 176 | +# ============================================================================= |
| 177 | + |
| 178 | +# 4.1 Stop all pneumatic containers |
| 179 | +output=$(docker compose -f "$PROJECT_DIR/docker-compose.yml" down 2>&1) |
| 180 | +if [ $? -eq 0 ]; then |
| 181 | + print_info "Containers successfully removed" |
| 182 | +else |
| 183 | + print_error "$output" |
| 184 | + exit 1 |
| 185 | +fi |
| 186 | + |
| 187 | +# 4.2 Start the postgres container |
| 188 | +output=$(docker compose -f "$PROJECT_DIR/docker-compose.src.yml" up -d postgres 2>&1) |
| 189 | +if [ $? -eq 0 ]; then |
| 190 | + print_info "Container \"postgres\" is running" |
| 191 | +else |
| 192 | + print_error "$output" |
| 193 | + exit 1 |
| 194 | +fi |
| 195 | + |
| 196 | +# ============================================================================= |
| 197 | +# Section 5: Drop, recreate, and restore the database |
| 198 | +# ============================================================================= |
| 199 | + |
| 200 | +# 5.1 Wait for the database to become available |
| 201 | +print_info "Waiting for the database to become available..." |
| 202 | +MAX_RETRIES=30 |
| 203 | +RETRY_COUNT=0 |
| 204 | +while true; do |
| 205 | + docker exec pneumatic-postgres pg_isready -U "$POSTGRES_USER" -h "$POSTGRES_HOST" > /dev/null 2>&1 |
| 206 | + if [ $? -eq 0 ]; then |
| 207 | + print_info "Database is ready" |
| 208 | + break |
| 209 | + fi |
| 210 | + |
| 211 | + RETRY_COUNT=$((RETRY_COUNT + 1)) |
| 212 | + if [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; then |
| 213 | + print_error "Database is not available after ${MAX_RETRIES} seconds" |
| 214 | + exit 1 |
| 215 | + fi |
| 216 | + |
| 217 | + sleep 1 |
| 218 | +done |
| 219 | + |
| 220 | +# 5.2 Drop the existing database |
| 221 | +output=$(docker exec -e "PGPASSWORD=$POSTGRES_PASSWORD" pneumatic-postgres dropdb -U "$POSTGRES_USER" -h "$POSTGRES_HOST" "$POSTGRES_DB" 2>&1) |
| 222 | +if [ $? -eq 0 ]; then |
| 223 | + print_info "Database successfully dropped" |
| 224 | +else |
| 225 | + print_error "$output" |
| 226 | + exit 1 |
| 227 | +fi |
| 228 | + |
| 229 | +# 5.3 Create a new database |
| 230 | +output=$(docker exec -e "PGPASSWORD=$POSTGRES_PASSWORD" pneumatic-postgres createdb -U "$POSTGRES_USER" -h "$POSTGRES_HOST" --owner "$POSTGRES_USER" "$POSTGRES_DB" 2>&1) |
| 231 | +if [ $? -eq 0 ]; then |
| 232 | + print_info "Database successfully created" |
| 233 | +else |
| 234 | + print_error "$output" |
| 235 | + exit 1 |
| 236 | +fi |
| 237 | + |
| 238 | +# 5.4 Restore from backup |
| 239 | +print_info "Restoring database \"$POSTGRES_DB\" from file \"$BACKUP_FILENAME\", this may take a few minutes..." |
| 240 | +output=$(docker exec -i -e "PGPASSWORD=$POSTGRES_PASSWORD" pneumatic-postgres psql -U "$POSTGRES_USER" -h "$POSTGRES_HOST" "$POSTGRES_DB" < "$BACKUPS_DIR/$BACKUP_FILENAME" 2>&1) |
| 241 | +if [ $? -eq 0 ]; then |
| 242 | + print_info "Backup successfully restored" |
| 243 | +else |
| 244 | + print_error "$output" |
| 245 | + exit 1 |
| 246 | +fi |
| 247 | + |
| 248 | +# ============================================================================= |
| 249 | +# Section 6: Run the selected docker-compose command |
| 250 | +# ============================================================================= |
| 251 | + |
| 252 | +case "$COMPOSE_FILE" in |
| 253 | + 1) |
| 254 | + output=$(docker compose -f "$PROJECT_DIR/docker-compose.src.yml" up -d 2>&1) |
| 255 | + ;; |
| 256 | + 2) |
| 257 | + output=$(TAG=stable docker compose -f "$PROJECT_DIR/docker-compose.yml" up -d 2>&1) |
| 258 | + ;; |
| 259 | + 3) |
| 260 | + output=$(TAG=latest docker compose -f "$PROJECT_DIR/docker-compose.yml" up -d 2>&1) |
| 261 | + ;; |
| 262 | + 4) |
| 263 | + # Compose resolves .env from the compose file's directory; pass the selected file explicitly. |
| 264 | + output=$(docker compose --env-file "$ENV_FILE" -f "$PROJECT_DIR/frontend/docker-compose.yml" up -d 2>&1) |
| 265 | + ;; |
| 266 | + 5) |
| 267 | + output=$(docker compose --env-file "$ENV_FILE" -f "$PROJECT_DIR/backend/docker-compose.yml" up -d 2>&1) |
| 268 | + ;; |
| 269 | +esac |
| 270 | + |
| 271 | +if [ $? -eq 0 ]; then |
| 272 | + print_info "Containers successfully started" |
| 273 | +else |
| 274 | + print_error "$output" |
| 275 | + exit 1 |
| 276 | +fi |
| 277 | + |
| 278 | +echo "" |
| 279 | +print_info "Done! Database '${POSTGRES_DB}' has been successfully restored from '${BACKUP_FILENAME}'." |
| 280 | + |
| 281 | +echo "" |
| 282 | +echo -e "${GREEN}" |
| 283 | +cat <<'EOF' |
| 284 | + __ |
| 285 | + . \ |
| 286 | + - \ |
| 287 | + / /_ .---. |
| 288 | + / | \\,.\/--.// ) |
| 289 | + | \// )/ / |
| 290 | + \ ^ ^ / ) |
| 291 | + .____. .___/ |
| 292 | + .\ \ | / |
| 293 | + \______/ |
| 294 | + Done! |
| 295 | +EOF |
| 296 | +echo -e "${NC}" |
0 commit comments