diff --git a/pasarguard.sh b/pasarguard.sh index 3b54452..c954960 100755 --- a/pasarguard.sh +++ b/pasarguard.sh @@ -668,6 +668,8 @@ backup_command() { fi key=$(echo "$key" | xargs) value=$(echo "$value" | xargs) + # Remove surrounding quotes from value if present + value=$(echo "$value" | sed -E 's/^["'\''](.*)["'\'']$/\1/') if [[ "$key" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then export "$key"="$value" else @@ -690,9 +692,14 @@ backup_command() { local db_name="" local container_name="" - # Parse SQLALCHEMY_DATABASE_URL from environment + # SQLALCHEMY_DATABASE_URL should already be loaded from .env above + # Just log what we have + echo "SQLALCHEMY_DATABASE_URL from environment: ${SQLALCHEMY_DATABASE_URL:-not set}" >>"$log_file" + if [ -z "$SQLALCHEMY_DATABASE_URL" ]; then - SQLALCHEMY_DATABASE_URL=$(grep "^SQLALCHEMY_DATABASE_URL" "$ENV_FILE" | sed -E 's/^[^=]*=\s*"?([^"]*)"?/\1/' | tr -d '"') + colorized_echo red "Error: SQLALCHEMY_DATABASE_URL not found in .env file or not set" + echo "Please check $ENV_FILE for SQLALCHEMY_DATABASE_URL" >>"$log_file" + error_messages+=("SQLALCHEMY_DATABASE_URL not found in .env file") fi if [ -n "$SQLALCHEMY_DATABASE_URL" ]; then @@ -700,7 +707,7 @@ backup_command() { # Extract database type from scheme if [[ "$SQLALCHEMY_DATABASE_URL" =~ ^sqlite ]]; then - db_type="sqlite" + db_type="sqlite" # Extract SQLite file path # SQLite URLs: sqlite:///relative/path or sqlite:////absolute/path local sqlite_url_part="${SQLALCHEMY_DATABASE_URL#*://}" @@ -782,15 +789,38 @@ backup_command() { detect_compose case $db_type in mariadb) - container_name=$(docker compose -f "$COMPOSE_FILE" ps -q mariadb 2>/dev/null || echo "mariadb") + # Try to get running container ID first, then try container name + container_name=$($COMPOSE -f "$COMPOSE_FILE" -p "$APP_NAME" ps -q mariadb 2>/dev/null) + if [ -z "$container_name" ]; then + # If not running, try to get container name from docker compose + container_name=$($COMPOSE -f "$COMPOSE_FILE" -p "$APP_NAME" ps --format json mariadb 2>/dev/null | jq -r '.Name' 2>/dev/null | head -n 1) + [ -z "$container_name" ] && container_name="mariadb" + fi ;; mysql) - container_name=$(docker compose -f "$COMPOSE_FILE" ps -q mysql 2>/dev/null || echo "mysql") + # Try to get running container ID first, then try container name + container_name=$($COMPOSE -f "$COMPOSE_FILE" -p "$APP_NAME" ps -q mysql 2>/dev/null) + if [ -z "$container_name" ]; then + # If not running, try to get container name from docker compose + container_name=$($COMPOSE -f "$COMPOSE_FILE" -p "$APP_NAME" ps --format json mysql 2>/dev/null | jq -r '.Name' 2>/dev/null | head -n 1) + [ -z "$container_name" ] && container_name="mysql" + fi ;; postgresql|timescaledb) - container_name=$(docker compose -f "$COMPOSE_FILE" ps -q postgresql 2>/dev/null || docker compose -f "$COMPOSE_FILE" ps -q timescaledb 2>/dev/null || echo "postgresql") + # Try postgresql first + container_name=$($COMPOSE -f "$COMPOSE_FILE" -p "$APP_NAME" ps -q postgresql 2>/dev/null) + if [ -z "$container_name" ]; then + # Try timescaledb + container_name=$($COMPOSE -f "$COMPOSE_FILE" -p "$APP_NAME" ps -q timescaledb 2>/dev/null) + fi + if [ -z "$container_name" ]; then + # Try to get container name from docker compose + container_name=$($COMPOSE -f "$COMPOSE_FILE" -p "$APP_NAME" ps --format json postgresql timescaledb 2>/dev/null | jq -r '.Name' 2>/dev/null | head -n 1) + [ -z "$container_name" ] && container_name="postgresql" + fi ;; esac + echo "Container name/ID for $db_type: $container_name" >>"$log_file" fi fi fi @@ -798,75 +828,260 @@ backup_command() { if [ -n "$db_type" ]; then echo "Database detected: $db_type" >>"$log_file" echo "Database host: ${db_host:-localhost}" >>"$log_file" + colorized_echo blue "Database detected: $db_type" case $db_type in mariadb) - # Use root password from env if available, otherwise use parsed password - local backup_user="${db_user:-root}" - local backup_password="${MYSQL_ROOT_PASSWORD:-$db_password}" - if [[ "$db_host" == "127.0.0.1" || "$db_host" == "localhost" || "$db_host" == "::1" ]] && [ -n "$container_name" ]; then # Local Docker container - if ! docker exec "$container_name" mariadb-dump -u "$backup_user" -p"$backup_password" --all-databases --ignore-database=mysql --ignore-database=performance_schema --ignore-database=information_schema --ignore-database=sys --events --triggers >"$temp_dir/db_backup.sql" 2>>"$log_file"; then - error_messages+=("MariaDB dump failed.") + # Try root user with MYSQL_ROOT_PASSWORD first for all databases backup + if [ -n "${MYSQL_ROOT_PASSWORD:-}" ]; then + colorized_echo blue "Backing up all MariaDB databases from container: $container_name (using root user)" + if docker exec "$container_name" mariadb-dump -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases --ignore-database=mysql --ignore-database=performance_schema --ignore-database=information_schema --ignore-database=sys --events --triggers >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + colorized_echo green "MariaDB backup completed successfully (all databases)" + else + # Fallback to SQL URL credentials for specific database + colorized_echo yellow "Root backup failed, falling back to app user for specific database" + local backup_user="${db_user:-${DB_USER:-}}" + local backup_password="${db_password:-${DB_PASSWORD:-}}" + + if [ -z "$backup_password" ] || [ -z "$db_name" ]; then + colorized_echo red "Error: Cannot fallback - missing database name or password in SQLALCHEMY_DATABASE_URL" + error_messages+=("MariaDB backup failed - root backup failed and fallback credentials incomplete.") + else + colorized_echo blue "Backing up MariaDB database '$db_name' from container: $container_name (using app user)" + if ! docker exec "$container_name" mariadb-dump -u "$backup_user" -p"$backup_password" "$db_name" --events --triggers >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + colorized_echo red "MariaDB dump failed. Check log file for details." + error_messages+=("MariaDB dump failed.") + else + colorized_echo green "MariaDB backup completed successfully" + fi + fi + fi + else + # No MYSQL_ROOT_PASSWORD, use SQL URL credentials for specific database + local backup_user="${db_user:-${DB_USER:-}}" + local backup_password="${db_password:-${DB_PASSWORD:-}}" + + if [ -z "$backup_password" ]; then + colorized_echo red "Error: Database password not found. Check MYSQL_ROOT_PASSWORD or SQLALCHEMY_DATABASE_URL in .env" + error_messages+=("MariaDB password not found.") + elif [ -z "$db_name" ]; then + colorized_echo red "Error: Database name not found in SQLALCHEMY_DATABASE_URL" + error_messages+=("MariaDB database name not found.") + else + colorized_echo blue "Backing up MariaDB database '$db_name' from container: $container_name (using app user)" + if ! docker exec "$container_name" mariadb-dump -u "$backup_user" -p"$backup_password" "$db_name" --events --triggers >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + colorized_echo red "MariaDB dump failed. Check log file for details." + error_messages+=("MariaDB dump failed.") + else + colorized_echo green "MariaDB backup completed successfully" + fi + fi fi else # Remote database - would need mariadb-client installed + colorized_echo red "Remote MariaDB backup not yet supported. Please use local database or install mariadb-client." error_messages+=("Remote MariaDB backup not yet supported. Please use local database or install mariadb-client.") fi ;; mysql) - # Use root password from env if available, otherwise use parsed password - local backup_user="${db_user:-root}" - local backup_password="${MYSQL_ROOT_PASSWORD:-$db_password}" - - if [[ "$db_host" == "127.0.0.1" || "$db_host" == "localhost" || "$db_host" == "::1" ]] && [ -n "$container_name" ]; then - # Local Docker container - databases=$(docker exec "$container_name" mysql -u "$backup_user" -p"$backup_password" -e "SHOW DATABASES;" 2>>"$log_file" | grep -Ev "^(Database|mysql|performance_schema|information_schema|sys)$") - if [ -z "$databases" ]; then - echo "No user databases found to backup" >>"$log_file" - elif ! docker exec "$container_name" mysqldump -u "$backup_user" -p"$backup_password" --databases $databases --events --triggers >"$temp_dir/db_backup.sql" 2>>"$log_file"; then - error_messages+=("MySQL dump failed.") + if [[ "$db_host" == "127.0.0.1" || "$db_host" == "localhost" || "$db_host" == "::1" ]]; then + if [ -z "$container_name" ]; then + colorized_echo red "Error: MySQL container not found. Is the container running?" + echo "MySQL container not found. Container name: ${container_name:-empty}" >>"$log_file" + error_messages+=("MySQL container not found or not running.") + else + # Local Docker container + # Try root user with MYSQL_ROOT_PASSWORD first for all databases backup + if [ -n "${MYSQL_ROOT_PASSWORD:-}" ]; then + colorized_echo blue "Backing up all MySQL databases from container: $container_name (using root user)" + databases=$(docker exec "$container_name" mysql -u root -p"$MYSQL_ROOT_PASSWORD" -e "SHOW DATABASES;" 2>>"$log_file" | grep -Ev "^(Database|mysql|performance_schema|information_schema|sys)$") + if [ -z "$databases" ]; then + colorized_echo yellow "No user databases found, falling back to specific database backup" + # Fallback to SQL URL credentials + local backup_user="${db_user:-${DB_USER:-}}" + local backup_password="${db_password:-${DB_PASSWORD:-}}" + + if [ -z "$backup_password" ] || [ -z "$db_name" ]; then + colorized_echo red "Error: Cannot fallback - missing database name or password in SQLALCHEMY_DATABASE_URL" + error_messages+=("MySQL backup failed - no databases found and fallback credentials incomplete.") + else + colorized_echo blue "Backing up MySQL database '$db_name' from container: $container_name (using app user)" + if ! docker exec "$container_name" mysqldump -u "$backup_user" -p"$backup_password" "$db_name" --events --triggers >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + colorized_echo red "MySQL dump failed. Check log file for details." + error_messages+=("MySQL dump failed.") + else + colorized_echo green "MySQL backup completed successfully" + fi + fi + elif ! docker exec "$container_name" mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --databases $databases --events --triggers >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + # Root backup failed, fallback to SQL URL credentials + colorized_echo yellow "Root backup failed, falling back to app user for specific database" + local backup_user="${db_user:-${DB_USER:-}}" + local backup_password="${db_password:-${DB_PASSWORD:-}}" + + if [ -z "$backup_password" ] || [ -z "$db_name" ]; then + colorized_echo red "Error: Cannot fallback - missing database name or password in SQLALCHEMY_DATABASE_URL" + error_messages+=("MySQL backup failed - root backup failed and fallback credentials incomplete.") + else + colorized_echo blue "Backing up MySQL database '$db_name' from container: $container_name (using app user)" + if ! docker exec "$container_name" mysqldump -u "$backup_user" -p"$backup_password" "$db_name" --events --triggers >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + colorized_echo red "MySQL dump failed. Check log file for details." + error_messages+=("MySQL dump failed.") + else + colorized_echo green "MySQL backup completed successfully" + fi + fi + else + colorized_echo green "MySQL backup completed successfully (all databases)" + fi + else + # No MYSQL_ROOT_PASSWORD, use SQL URL credentials for specific database + local backup_user="${db_user:-${DB_USER:-}}" + local backup_password="${db_password:-${DB_PASSWORD:-}}" + + if [ -z "$backup_password" ]; then + colorized_echo red "Error: Database password not found. Check MYSQL_ROOT_PASSWORD or SQLALCHEMY_DATABASE_URL in .env" + error_messages+=("MySQL password not found.") + elif [ -z "$db_name" ]; then + colorized_echo red "Error: Database name not found in SQLALCHEMY_DATABASE_URL" + error_messages+=("MySQL database name not found.") + else + colorized_echo blue "Backing up MySQL database '$db_name' from container: $container_name (using app user)" + if ! docker exec "$container_name" mysqldump -u "$backup_user" -p"$backup_password" "$db_name" --events --triggers >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + colorized_echo red "MySQL dump failed. Check log file for details." + error_messages+=("MySQL dump failed.") + else + colorized_echo green "MySQL backup completed successfully" + fi + fi + fi fi else # Remote database - would need mysql-client installed + colorized_echo red "Remote MySQL backup not yet supported. Please use local database or install mysql-client." error_messages+=("Remote MySQL backup not yet supported. Please use local database or install mysql-client.") fi ;; postgresql) - # Use parsed credentials with fallback to env - local backup_user="${db_user:-${DB_USER:-postgres}}" - local backup_password="${db_password:-${DB_PASSWORD:-}}" - if [[ "$db_host" == "127.0.0.1" || "$db_host" == "localhost" || "$db_host" == "::1" ]] && [ -n "$container_name" ]; then # Local Docker container - if [ -n "$backup_password" ]; then - export PGPASSWORD="$backup_password" - fi - if ! docker exec "$container_name" pg_dumpall -U "$backup_user" >"$temp_dir/db_backup.sql" 2>>"$log_file"; then - error_messages+=("PostgreSQL dump failed.") + # Try postgres superuser with DB_PASSWORD first for pg_dumpall (all databases) + if [ -n "${DB_PASSWORD:-}" ]; then + colorized_echo blue "Backing up all PostgreSQL databases from container: $container_name (using postgres superuser)" + export PGPASSWORD="$DB_PASSWORD" + if docker exec "$container_name" pg_dumpall -U postgres >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + colorized_echo green "PostgreSQL backup completed successfully (all databases)" + unset PGPASSWORD + else + # Fallback to pg_dump with SQL URL credentials + unset PGPASSWORD + colorized_echo yellow "pg_dumpall failed, falling back to pg_dump for specific database" + local backup_user="${db_user:-${DB_USER:-postgres}}" + local backup_password="${db_password:-${DB_PASSWORD:-}}" + + if [ -z "$backup_password" ] || [ -z "$db_name" ]; then + colorized_echo red "Error: Cannot fallback - missing database name or password in SQLALCHEMY_DATABASE_URL" + error_messages+=("PostgreSQL backup failed - pg_dumpall failed and fallback credentials incomplete.") + else + colorized_echo blue "Backing up PostgreSQL database '$db_name' from container: $container_name (using app user)" + export PGPASSWORD="$backup_password" + if ! docker exec "$container_name" pg_dump -U "$backup_user" -d "$db_name" --clean --if-exists >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + colorized_echo red "PostgreSQL dump failed. Check log file for details." + error_messages+=("PostgreSQL dump failed.") + else + colorized_echo green "PostgreSQL backup completed successfully" + fi + unset PGPASSWORD + fi + fi + else + # No DB_PASSWORD, use SQL URL credentials for pg_dump + local backup_user="${db_user:-${DB_USER:-postgres}}" + local backup_password="${db_password:-${DB_PASSWORD:-}}" + + if [ -z "$backup_password" ]; then + colorized_echo red "Error: Database password not found. Check DB_PASSWORD or SQLALCHEMY_DATABASE_URL in .env" + error_messages+=("PostgreSQL password not found.") + elif [ -z "$db_name" ]; then + colorized_echo red "Error: Database name not found in SQLALCHEMY_DATABASE_URL" + error_messages+=("PostgreSQL database name not found.") + else + colorized_echo blue "Backing up PostgreSQL database '$db_name' from container: $container_name (using app user)" + export PGPASSWORD="$backup_password" + if ! docker exec "$container_name" pg_dump -U "$backup_user" -d "$db_name" --clean --if-exists >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + colorized_echo red "PostgreSQL dump failed. Check log file for details." + error_messages+=("PostgreSQL dump failed.") + else + colorized_echo green "PostgreSQL backup completed successfully" + fi + unset PGPASSWORD + fi fi - unset PGPASSWORD else # Remote database - would need postgresql-client installed + colorized_echo red "Remote PostgreSQL backup not yet supported. Please use local database or install postgresql-client." error_messages+=("Remote PostgreSQL backup not yet supported. Please use local database or install postgresql-client.") fi ;; timescaledb) - # Use parsed credentials with fallback to env - local backup_user="${db_user:-${DB_USER:-postgres}}" - local backup_password="${db_password:-${DB_PASSWORD:-}}" - if [[ "$db_host" == "127.0.0.1" || "$db_host" == "localhost" || "$db_host" == "::1" ]] && [ -n "$container_name" ]; then # Local Docker container - if [ -n "$backup_password" ]; then - export PGPASSWORD="$backup_password" - fi - if ! docker exec "$container_name" pg_dumpall -U "$backup_user" >"$temp_dir/db_backup.sql" 2>>"$log_file"; then - error_messages+=("TimescaleDB dump failed.") + # Try postgres superuser with DB_PASSWORD first for pg_dumpall (all databases) + if [ -n "${DB_PASSWORD:-}" ]; then + colorized_echo blue "Backing up all TimescaleDB databases from container: $container_name (using postgres superuser)" + export PGPASSWORD="$DB_PASSWORD" + if docker exec "$container_name" pg_dumpall -U postgres >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + colorized_echo green "TimescaleDB backup completed successfully (all databases)" + unset PGPASSWORD + else + # Fallback to pg_dump with SQL URL credentials + unset PGPASSWORD + colorized_echo yellow "pg_dumpall failed, falling back to pg_dump for specific database" + local backup_user="${db_user:-${DB_USER:-postgres}}" + local backup_password="${db_password:-${DB_PASSWORD:-}}" + + if [ -z "$backup_password" ] || [ -z "$db_name" ]; then + colorized_echo red "Error: Cannot fallback - missing database name or password in SQLALCHEMY_DATABASE_URL" + error_messages+=("TimescaleDB backup failed - pg_dumpall failed and fallback credentials incomplete.") + else + colorized_echo blue "Backing up TimescaleDB database '$db_name' from container: $container_name (using app user)" + export PGPASSWORD="$backup_password" + if ! docker exec "$container_name" pg_dump -U "$backup_user" -d "$db_name" --clean --if-exists >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + colorized_echo red "TimescaleDB dump failed. Check log file for details." + error_messages+=("TimescaleDB dump failed.") + else + colorized_echo green "TimescaleDB backup completed successfully" + fi + unset PGPASSWORD + fi + fi + else + # No DB_PASSWORD, use SQL URL credentials for pg_dump + local backup_user="${db_user:-${DB_USER:-postgres}}" + local backup_password="${db_password:-${DB_PASSWORD:-}}" + + if [ -z "$backup_password" ]; then + colorized_echo red "Error: Database password not found. Check DB_PASSWORD or SQLALCHEMY_DATABASE_URL in .env" + error_messages+=("TimescaleDB password not found.") + elif [ -z "$db_name" ]; then + colorized_echo red "Error: Database name not found in SQLALCHEMY_DATABASE_URL" + error_messages+=("TimescaleDB database name not found.") + else + colorized_echo blue "Backing up TimescaleDB database '$db_name' from container: $container_name (using app user)" + export PGPASSWORD="$backup_password" + if ! docker exec "$container_name" pg_dump -U "$backup_user" -d "$db_name" --clean --if-exists >"$temp_dir/db_backup.sql" 2>>"$log_file"; then + colorized_echo red "TimescaleDB dump failed. Check log file for details." + error_messages+=("TimescaleDB dump failed.") + else + colorized_echo green "TimescaleDB backup completed successfully" + fi + unset PGPASSWORD + fi fi - unset PGPASSWORD else # Remote database - would need postgresql-client installed + colorized_echo red "Remote TimescaleDB backup not yet supported. Please use local database or install postgresql-client." error_messages+=("Remote TimescaleDB backup not yet supported. Please use local database or install postgresql-client.") fi ;; @@ -880,6 +1095,10 @@ backup_command() { fi ;; esac + else + colorized_echo yellow "Warning: No database type detected. Skipping database backup." + echo "Warning: No database type detected." >>"$log_file" + echo "SQLALCHEMY_DATABASE_URL: ${SQLALCHEMY_DATABASE_URL:-not set}" >>"$log_file" fi cp "$APP_DIR/.env" "$temp_dir/" 2>>"$log_file" @@ -894,8 +1113,13 @@ backup_command() { rm -rf "$temp_dir" if [ ${#error_messages[@]} -gt 0 ]; then + colorized_echo red "Backup completed with errors:" + for error in "${error_messages[@]}"; do + colorized_echo red " - $error" + done + colorized_echo yellow "Check log file: $log_file" send_backup_error_to_telegram "${error_messages[*]}" "$log_file" - return + return 1 fi colorized_echo green "Backup created: $backup_file" send_backup_to_telegram "$backup_file"