2023-08-10 13:52:54 +00:00
< ? php
namespace App\Jobs ;
2023-12-11 09:23:10 +00:00
use App\Events\BackupCreated ;
2023-08-10 16:20:12 +00:00
use App\Models\S3Storage ;
2023-08-10 13:52:54 +00:00
use App\Models\ScheduledDatabaseBackup ;
use App\Models\ScheduledDatabaseBackupExecution ;
use App\Models\Server ;
2023-11-07 11:11:47 +00:00
use App\Models\ServiceDatabase ;
2023-10-24 12:31:28 +00:00
use App\Models\StandaloneMariadb ;
2023-10-19 11:46:15 +00:00
use App\Models\StandaloneMongodb ;
2023-10-24 12:31:28 +00:00
use App\Models\StandaloneMysql ;
2023-08-10 13:52:54 +00:00
use App\Models\StandalonePostgresql ;
use App\Models\Team ;
2023-08-10 19:00:02 +00:00
use App\Notifications\Database\BackupFailed ;
use App\Notifications\Database\BackupSuccess ;
2023-08-10 13:52:54 +00:00
use Carbon\Carbon ;
use Illuminate\Bus\Queueable ;
2023-09-14 08:12:44 +00:00
use Illuminate\Contracts\Queue\ShouldBeEncrypted ;
2023-08-10 13:52:54 +00:00
use Illuminate\Contracts\Queue\ShouldQueue ;
use Illuminate\Foundation\Bus\Dispatchable ;
use Illuminate\Queue\InteractsWithQueue ;
use Illuminate\Queue\SerializesModels ;
2023-08-15 13:39:15 +00:00
use Illuminate\Support\Str ;
2025-07-18 13:47:14 +00:00
use Throwable ;
use Visus\Cuid2\Cuid2 ;
2023-08-10 13:52:54 +00:00
2024-06-10 20:43:34 +00:00
class DatabaseBackupJob implements ShouldBeEncrypted , ShouldQueue
2023-08-10 13:52:54 +00:00
{
use Dispatchable , InteractsWithQueue , Queueable , SerializesModels ;
2023-09-08 07:37:58 +00:00
public ? Team $team = null ;
2024-06-10 20:43:34 +00:00
2023-08-10 13:52:54 +00:00
public Server $server ;
2024-06-10 20:43:34 +00:00
2023-11-07 11:11:47 +00:00
public StandalonePostgresql | StandaloneMongodb | StandaloneMysql | StandaloneMariadb | ServiceDatabase $database ;
2023-08-10 13:52:54 +00:00
2023-09-08 07:37:58 +00:00
public ? string $container_name = null ;
2024-06-10 20:43:34 +00:00
2023-11-08 09:47:39 +00:00
public ? string $directory_name = null ;
2024-06-10 20:43:34 +00:00
2023-09-08 07:37:58 +00:00
public ? ScheduledDatabaseBackupExecution $backup_log = null ;
2024-06-10 20:43:34 +00:00
2023-10-10 11:10:43 +00:00
public string $backup_status = 'failed' ;
2024-06-10 20:43:34 +00:00
2023-09-08 07:37:58 +00:00
public ? string $backup_location = null ;
2024-06-10 20:43:34 +00:00
2023-08-11 14:13:53 +00:00
public string $backup_dir ;
2024-06-10 20:43:34 +00:00
2023-08-11 14:13:53 +00:00
public string $backup_file ;
2024-06-10 20:43:34 +00:00
2023-08-10 13:52:54 +00:00
public int $size = 0 ;
2024-06-10 20:43:34 +00:00
2023-09-08 07:37:58 +00:00
public ? string $backup_output = null ;
2024-06-10 20:43:34 +00:00
2024-08-16 09:53:27 +00:00
public ? string $postgres_password = null ;
2025-06-04 09:01:43 +00:00
public ? string $mongo_root_username = null ;
public ? string $mongo_root_password = null ;
2023-09-08 07:37:58 +00:00
public ? S3Storage $s3 = null ;
2023-08-10 13:52:54 +00:00
2025-07-18 13:47:14 +00:00
public $timeout = 3600 ;
public string $backup_log_uuid ;
2025-01-07 14:31:43 +00:00
public function __construct ( public ScheduledDatabaseBackup $backup )
2023-08-10 13:52:54 +00:00
{
2024-11-22 10:16:01 +00:00
$this -> onQueue ( 'high' );
2025-07-18 13:47:14 +00:00
$this -> timeout = $backup -> timeout ;
$this -> backup_log_uuid = ( string ) new Cuid2 ;
2023-08-10 13:52:54 +00:00
}
2023-08-11 14:13:53 +00:00
public function handle () : void
2023-08-10 13:52:54 +00:00
{
2023-08-24 14:14:09 +00:00
try {
2024-11-25 10:28:16 +00:00
$databasesToBackup = null ;
2025-01-07 14:31:43 +00:00
$this -> team = Team :: find ( $this -> backup -> team_id );
2024-10-03 18:51:18 +00:00
if ( ! $this -> team ) {
2025-01-07 14:31:43 +00:00
$this -> backup -> delete ();
2024-10-03 18:51:18 +00:00
return ;
}
2025-01-07 14:31:43 +00:00
if ( data_get ( $this -> backup , 'database_type' ) === \App\Models\ServiceDatabase :: class ) {
$this -> database = data_get ( $this -> backup , 'database' );
2024-10-02 13:33:14 +00:00
$this -> server = $this -> database -> service -> server ;
2025-01-07 14:31:43 +00:00
$this -> s3 = $this -> backup -> s3 ;
2024-10-02 13:33:14 +00:00
} else {
2025-01-07 14:31:43 +00:00
$this -> database = data_get ( $this -> backup , 'database' );
2024-10-02 13:33:14 +00:00
$this -> server = $this -> database -> destination -> server ;
2025-01-07 14:31:43 +00:00
$this -> s3 = $this -> backup -> s3 ;
2024-10-02 13:33:14 +00:00
}
if ( is_null ( $this -> server )) {
2025-01-07 14:31:43 +00:00
throw new \Exception ( 'Server not found?!' );
2024-10-02 13:33:14 +00:00
}
if ( is_null ( $this -> database )) {
2025-01-07 14:31:43 +00:00
throw new \Exception ( 'Database not found?!' );
2023-11-06 09:45:06 +00:00
}
2024-08-24 11:04:33 +00:00
BackupCreated :: dispatch ( $this -> team -> id );
2024-08-26 08:32:05 +00:00
2024-06-25 08:37:10 +00:00
$status = str ( data_get ( $this -> database , 'status' ));
2024-06-10 20:43:34 +00:00
if ( ! $status -> startsWith ( 'running' ) && $this -> database -> id !== 0 ) {
2023-08-24 14:14:09 +00:00
return ;
}
2025-01-07 14:31:43 +00:00
if ( data_get ( $this -> backup , 'database_type' ) === \App\Models\ServiceDatabase :: class ) {
2023-11-07 11:11:47 +00:00
$databaseType = $this -> database -> databaseType ();
$serviceUuid = $this -> database -> service -> uuid ;
2023-11-08 09:47:39 +00:00
$serviceName = str ( $this -> database -> service -> name ) -> slug ();
2024-03-26 12:50:44 +00:00
if ( str ( $databaseType ) -> contains ( 'postgres' )) {
2023-11-08 10:05:57 +00:00
$this -> container_name = " { $this -> database -> name } - $serviceUuid " ;
2024-06-10 20:43:34 +00:00
$this -> directory_name = $serviceName . '-' . $this -> container_name ;
2023-11-07 11:11:47 +00:00
$commands [] = " docker exec $this->container_name env | grep POSTGRES_ " ;
$envs = instant_remote_process ( $commands , $this -> server );
2023-11-08 10:05:57 +00:00
$envs = str ( $envs ) -> explode ( " \n " );
$user = $envs -> filter ( function ( $env ) {
return str ( $env ) -> startsWith ( 'POSTGRES_USER=' );
}) -> first ();
2025-01-07 14:31:43 +00:00
if ( $user ) {
$this -> database -> postgres_user = str ( $user ) -> after ( 'POSTGRES_USER=' ) -> value ();
} else {
$this -> database -> postgres_user = 'postgres' ;
}
2023-11-08 10:05:57 +00:00
$db = $envs -> filter ( function ( $env ) {
return str ( $env ) -> startsWith ( 'POSTGRES_DB=' );
}) -> first ();
2025-01-07 14:31:43 +00:00
if ( $db ) {
$databasesToBackup = str ( $db ) -> after ( 'POSTGRES_DB=' ) -> value ();
} else {
$databasesToBackup = $this -> database -> postgres_user ;
}
2024-08-16 09:53:27 +00:00
$this -> postgres_password = $envs -> filter ( function ( $env ) {
return str ( $env ) -> startsWith ( 'POSTGRES_PASSWORD=' );
}) -> first ();
if ( $this -> postgres_password ) {
$this -> postgres_password = str ( $this -> postgres_password ) -> after ( 'POSTGRES_PASSWORD=' ) -> value ();
}
2024-06-10 20:43:34 +00:00
} elseif ( str ( $databaseType ) -> contains ( 'mysql' )) {
2023-11-08 10:05:57 +00:00
$this -> container_name = " { $this -> database -> name } - $serviceUuid " ;
2024-06-10 20:43:34 +00:00
$this -> directory_name = $serviceName . '-' . $this -> container_name ;
2023-11-07 11:11:47 +00:00
$commands [] = " docker exec $this->container_name env | grep MYSQL_ " ;
$envs = instant_remote_process ( $commands , $this -> server );
2023-11-08 10:05:57 +00:00
$envs = str ( $envs ) -> explode ( " \n " );
$rootPassword = $envs -> filter ( function ( $env ) {
return str ( $env ) -> startsWith ( 'MYSQL_ROOT_PASSWORD=' );
}) -> first ();
if ( $rootPassword ) {
$this -> database -> mysql_root_password = str ( $rootPassword ) -> after ( 'MYSQL_ROOT_PASSWORD=' ) -> value ();
}
$db = $envs -> filter ( function ( $env ) {
return str ( $env ) -> startsWith ( 'MYSQL_DATABASE=' );
}) -> first ();
if ( $db ) {
$databasesToBackup = str ( $db ) -> after ( 'MYSQL_DATABASE=' ) -> value ();
} else {
2025-01-07 14:31:43 +00:00
throw new \Exception ( 'MYSQL_DATABASE not found' );
2023-11-08 10:05:57 +00:00
}
2024-06-10 20:43:34 +00:00
} elseif ( str ( $databaseType ) -> contains ( 'mariadb' )) {
2023-11-08 10:05:57 +00:00
$this -> container_name = " { $this -> database -> name } - $serviceUuid " ;
2024-06-10 20:43:34 +00:00
$this -> directory_name = $serviceName . '-' . $this -> container_name ;
2023-11-13 18:25:18 +00:00
$commands [] = " docker exec $this->container_name env " ;
2023-11-07 11:11:47 +00:00
$envs = instant_remote_process ( $commands , $this -> server );
2023-11-08 10:05:57 +00:00
$envs = str ( $envs ) -> explode ( " \n " );
$rootPassword = $envs -> filter ( function ( $env ) {
return str ( $env ) -> startsWith ( 'MARIADB_ROOT_PASSWORD=' );
}) -> first ();
if ( $rootPassword ) {
2023-11-13 18:25:18 +00:00
$this -> database -> mariadb_root_password = str ( $rootPassword ) -> after ( 'MARIADB_ROOT_PASSWORD=' ) -> value ();
2023-11-08 10:05:57 +00:00
} else {
$rootPassword = $envs -> filter ( function ( $env ) {
return str ( $env ) -> startsWith ( 'MYSQL_ROOT_PASSWORD=' );
}) -> first ();
if ( $rootPassword ) {
2023-11-13 18:25:18 +00:00
$this -> database -> mariadb_root_password = str ( $rootPassword ) -> after ( 'MYSQL_ROOT_PASSWORD=' ) -> value ();
2023-11-08 10:05:57 +00:00
}
}
$db = $envs -> filter ( function ( $env ) {
return str ( $env ) -> startsWith ( 'MARIADB_DATABASE=' );
}) -> first ();
if ( $db ) {
$databasesToBackup = str ( $db ) -> after ( 'MARIADB_DATABASE=' ) -> value ();
} else {
$db = $envs -> filter ( function ( $env ) {
return str ( $env ) -> startsWith ( 'MYSQL_DATABASE=' );
}) -> first ();
if ( $db ) {
$databasesToBackup = str ( $db ) -> after ( 'MYSQL_DATABASE=' ) -> value ();
} else {
2025-01-07 14:31:43 +00:00
throw new \Exception ( 'MARIADB_DATABASE or MYSQL_DATABASE not found' );
2023-11-08 10:05:57 +00:00
}
}
2025-06-04 09:01:43 +00:00
} elseif ( str ( $databaseType ) -> contains ( 'mongo' )) {
$databasesToBackup = [ '*' ];
$this -> container_name = " { $this -> database -> name } - $serviceUuid " ;
$this -> directory_name = $serviceName . '-' . $this -> container_name ;
// Try to extract MongoDB credentials from environment variables
try {
$commands = [];
$commands [] = " docker exec $this->container_name env | grep MONGO_INITDB_ " ;
$envs = instant_remote_process ( $commands , $this -> server );
if ( filled ( $envs )) {
$envs = str ( $envs ) -> explode ( " \n " );
$rootPassword = $envs -> filter ( function ( $env ) {
return str ( $env ) -> startsWith ( 'MONGO_INITDB_ROOT_PASSWORD=' );
}) -> first ();
if ( $rootPassword ) {
$this -> mongo_root_password = str ( $rootPassword ) -> after ( 'MONGO_INITDB_ROOT_PASSWORD=' ) -> value ();
}
$rootUsername = $envs -> filter ( function ( $env ) {
return str ( $env ) -> startsWith ( 'MONGO_INITDB_ROOT_USERNAME=' );
}) -> first ();
if ( $rootUsername ) {
$this -> mongo_root_username = str ( $rootUsername ) -> after ( 'MONGO_INITDB_ROOT_USERNAME=' ) -> value ();
}
}
2025-07-18 13:47:14 +00:00
2025-06-04 09:01:43 +00:00
} catch ( \Throwable $e ) {
// Continue without env vars - will be handled in backup_standalone_mongodb method
}
2023-11-07 11:11:47 +00:00
}
} else {
2023-11-08 09:47:39 +00:00
$databaseName = str ( $this -> database -> name ) -> slug () -> value ();
2023-11-07 11:11:47 +00:00
$this -> container_name = $this -> database -> uuid ;
2024-06-10 20:43:34 +00:00
$this -> directory_name = $databaseName . '-' . $this -> container_name ;
2023-11-07 11:11:47 +00:00
$databaseType = $this -> database -> type ();
2025-01-07 14:31:43 +00:00
$databasesToBackup = data_get ( $this -> backup , 'databases_to_backup' );
2023-11-07 11:11:47 +00:00
}
2024-11-24 10:39:26 +00:00
if ( blank ( $databasesToBackup )) {
2024-03-26 12:50:44 +00:00
if ( str ( $databaseType ) -> contains ( 'postgres' )) {
2023-10-13 13:45:24 +00:00
$databasesToBackup = [ $this -> database -> postgres_db ];
2025-06-04 09:01:43 +00:00
} elseif ( str ( $databaseType ) -> contains ( 'mongo' )) {
2023-10-19 15:17:38 +00:00
$databasesToBackup = [ '*' ];
2024-06-10 20:43:34 +00:00
} elseif ( str ( $databaseType ) -> contains ( 'mysql' )) {
2023-10-24 12:31:28 +00:00
$databasesToBackup = [ $this -> database -> mysql_database ];
2024-06-10 20:43:34 +00:00
} elseif ( str ( $databaseType ) -> contains ( 'mariadb' )) {
2023-10-24 12:31:28 +00:00
$databasesToBackup = [ $this -> database -> mariadb_database ];
2023-10-13 13:45:24 +00:00
} else {
return ;
}
} else {
2025-01-07 14:31:43 +00:00
if ( str ( $databaseType ) -> contains ( 'postgres' )) {
// Format: db1,db2,db3
$databasesToBackup = explode ( ',' , $databasesToBackup );
$databasesToBackup = array_map ( 'trim' , $databasesToBackup );
2025-06-04 09:01:43 +00:00
} elseif ( str ( $databaseType ) -> contains ( 'mongo' )) {
2025-01-07 14:31:43 +00:00
// Format: db1:collection1,collection2|db2:collection3,collection4
2025-06-04 09:01:43 +00:00
// Only explode if it's a string, not if it's already an array
if ( is_string ( $databasesToBackup )) {
$databasesToBackup = explode ( '|' , $databasesToBackup );
$databasesToBackup = array_map ( 'trim' , $databasesToBackup );
}
2025-01-07 14:31:43 +00:00
} elseif ( str ( $databaseType ) -> contains ( 'mysql' )) {
// Format: db1,db2,db3
$databasesToBackup = explode ( ',' , $databasesToBackup );
$databasesToBackup = array_map ( 'trim' , $databasesToBackup );
} elseif ( str ( $databaseType ) -> contains ( 'mariadb' )) {
// Format: db1,db2,db3
$databasesToBackup = explode ( ',' , $databasesToBackup );
$databasesToBackup = array_map ( 'trim' , $databasesToBackup );
} else {
return ;
}
2023-10-13 13:45:24 +00:00
}
2024-06-25 08:37:10 +00:00
$this -> backup_dir = backup_dir () . '/databases/' . str ( $this -> team -> name ) -> slug () . '-' . $this -> team -> id . '/' . $this -> directory_name ;
2023-08-24 14:14:09 +00:00
if ( $this -> database -> name === 'coolify-db' ) {
2023-10-13 13:45:24 +00:00
$databasesToBackup = [ 'coolify' ];
2024-06-10 20:43:34 +00:00
$this -> directory_name = $this -> container_name = 'coolify-db' ;
2023-08-24 14:14:09 +00:00
$ip = Str :: slug ( $this -> server -> ip );
2024-06-10 20:43:34 +00:00
$this -> backup_dir = backup_dir () . '/coolify' . " /coolify-db- $ip " ;
2023-08-24 14:14:09 +00:00
}
2025-01-07 14:31:43 +00:00
foreach ( $databasesToBackup as $database ) {
2023-10-13 13:45:24 +00:00
$size = 0 ;
try {
2024-03-26 12:50:44 +00:00
if ( str ( $databaseType ) -> contains ( 'postgres' )) {
2025-01-07 14:31:43 +00:00
$this -> backup_file = " /pg-dump- $database - " . Carbon :: now () -> timestamp . '.dmp' ;
if ( $this -> backup -> dump_all ) {
2024-10-03 10:39:45 +00:00
$this -> backup_file = '/pg-dump-all-' . Carbon :: now () -> timestamp . '.gz' ;
}
2024-06-10 20:43:34 +00:00
$this -> backup_location = $this -> backup_dir . $this -> backup_file ;
2025-01-07 14:31:43 +00:00
$this -> backup_log = ScheduledDatabaseBackupExecution :: create ([
2025-07-18 13:47:14 +00:00
'uuid' => $this -> backup_log_uuid ,
2025-01-07 14:31:43 +00:00
'database_name' => $database ,
2023-10-19 15:17:38 +00:00
'filename' => $this -> backup_location ,
2025-01-07 14:31:43 +00:00
'scheduled_database_backup_id' => $this -> backup -> id ,
2023-10-19 15:17:38 +00:00
]);
2025-01-07 14:31:43 +00:00
$this -> backup_standalone_postgresql ( $database );
2025-06-04 09:01:43 +00:00
} elseif ( str ( $databaseType ) -> contains ( 'mongo' )) {
2025-01-07 14:31:43 +00:00
if ( $database === '*' ) {
$database = 'all' ;
2023-10-19 15:17:38 +00:00
$databaseName = 'all' ;
} else {
2025-01-07 14:31:43 +00:00
if ( str ( $database ) -> contains ( ':' )) {
$databaseName = str ( $database ) -> before ( ':' );
} else {
$databaseName = $database ;
}
2023-10-19 15:17:38 +00:00
}
2024-06-10 20:43:34 +00:00
$this -> backup_file = " /mongo-dump- $databaseName - " . Carbon :: now () -> timestamp . '.tar.gz' ;
$this -> backup_location = $this -> backup_dir . $this -> backup_file ;
2025-01-07 14:31:43 +00:00
$this -> backup_log = ScheduledDatabaseBackupExecution :: create ([
2025-07-18 13:47:14 +00:00
'uuid' => $this -> backup_log_uuid ,
2023-10-19 15:17:38 +00:00
'database_name' => $databaseName ,
'filename' => $this -> backup_location ,
2025-01-07 14:31:43 +00:00
'scheduled_database_backup_id' => $this -> backup -> id ,
2023-10-19 15:17:38 +00:00
]);
2025-01-07 14:31:43 +00:00
$this -> backup_standalone_mongodb ( $database );
2024-06-10 20:43:34 +00:00
} elseif ( str ( $databaseType ) -> contains ( 'mysql' )) {
2025-01-07 14:31:43 +00:00
$this -> backup_file = " /mysql-dump- $database - " . Carbon :: now () -> timestamp . '.dmp' ;
if ( $this -> backup -> dump_all ) {
2024-10-03 10:39:45 +00:00
$this -> backup_file = '/mysql-dump-all-' . Carbon :: now () -> timestamp . '.gz' ;
}
2024-06-10 20:43:34 +00:00
$this -> backup_location = $this -> backup_dir . $this -> backup_file ;
2025-01-07 14:31:43 +00:00
$this -> backup_log = ScheduledDatabaseBackupExecution :: create ([
2025-07-18 13:47:14 +00:00
'uuid' => $this -> backup_log_uuid ,
2025-01-07 14:31:43 +00:00
'database_name' => $database ,
2023-10-24 12:31:28 +00:00
'filename' => $this -> backup_location ,
2025-01-07 14:31:43 +00:00
'scheduled_database_backup_id' => $this -> backup -> id ,
2023-10-24 12:31:28 +00:00
]);
2025-01-07 14:31:43 +00:00
$this -> backup_standalone_mysql ( $database );
2024-06-10 20:43:34 +00:00
} elseif ( str ( $databaseType ) -> contains ( 'mariadb' )) {
2025-01-07 14:31:43 +00:00
$this -> backup_file = " /mariadb-dump- $database - " . Carbon :: now () -> timestamp . '.dmp' ;
if ( $this -> backup -> dump_all ) {
2024-10-03 10:39:45 +00:00
$this -> backup_file = '/mariadb-dump-all-' . Carbon :: now () -> timestamp . '.gz' ;
}
2024-06-10 20:43:34 +00:00
$this -> backup_location = $this -> backup_dir . $this -> backup_file ;
2025-01-07 14:31:43 +00:00
$this -> backup_log = ScheduledDatabaseBackupExecution :: create ([
2025-07-18 13:47:14 +00:00
'uuid' => $this -> backup_log_uuid ,
2025-01-07 14:31:43 +00:00
'database_name' => $database ,
2023-10-24 12:31:28 +00:00
'filename' => $this -> backup_location ,
2025-01-07 14:31:43 +00:00
'scheduled_database_backup_id' => $this -> backup -> id ,
2023-10-24 12:31:28 +00:00
]);
2025-01-07 14:31:43 +00:00
$this -> backup_standalone_mariadb ( $database );
2023-10-19 15:17:38 +00:00
} else {
2025-01-07 14:31:43 +00:00
throw new \Exception ( 'Unsupported database type' );
2023-10-13 13:45:24 +00:00
}
$size = $this -> calculate_size ();
2025-01-07 14:31:43 +00:00
if ( $this -> backup -> save_s3 ) {
2023-10-13 13:45:24 +00:00
$this -> upload_to_s3 ();
}
2024-12-09 11:08:27 +00:00
2025-01-07 14:31:43 +00:00
$this -> team -> notify ( new BackupSuccess ( $this -> backup , $this -> database , $database ));
2024-12-09 11:08:27 +00:00
2023-10-13 13:45:24 +00:00
$this -> backup_log -> update ([
'status' => 'success' ,
'message' => $this -> backup_output ,
'size' => $size ,
]);
2025-01-07 14:31:43 +00:00
} catch ( \Throwable $e ) {
if ( $this -> backup_log ) {
2023-10-19 15:17:38 +00:00
$this -> backup_log -> update ([
'status' => 'failed' ,
'message' => $this -> backup_output ,
'size' => $size ,
2024-06-10 20:43:34 +00:00
'filename' => null ,
2023-10-19 15:17:38 +00:00
]);
}
2025-01-07 14:31:43 +00:00
$this -> team ? -> notify ( new BackupFailed ( $this -> backup , $this -> database , $this -> backup_output , $database ));
2023-10-13 13:45:24 +00:00
}
2023-08-24 14:14:09 +00:00
}
2025-01-13 17:39:22 +00:00
if ( $this -> backup_log && $this -> backup_log -> status === 'success' ) {
removeOldBackups ( $this -> backup );
}
2025-01-07 14:31:43 +00:00
} catch ( \Throwable $e ) {
2023-09-11 15:36:30 +00:00
throw $e ;
2023-12-11 09:23:10 +00:00
} finally {
2025-01-07 14:31:43 +00:00
if ( $this -> team ) {
2024-10-02 13:33:14 +00:00
BackupCreated :: dispatch ( $this -> team -> id );
}
2025-01-16 14:12:57 +00:00
if ( $this -> backup_log ) {
$this -> backup_log -> update ([
'finished_at' => Carbon :: now () -> toImmutable (),
]);
}
2023-08-10 16:20:12 +00:00
}
2023-08-10 13:52:54 +00:00
}
2024-06-10 20:43:34 +00:00
2023-10-19 15:17:38 +00:00
private function backup_standalone_mongodb ( string $databaseWithCollections ) : void
{
try {
2024-07-02 10:15:58 +00:00
$url = $this -> database -> internal_db_url ;
2025-06-04 09:01:43 +00:00
if ( blank ( $url )) {
// For service-based MongoDB, try to build URL from environment variables
if ( filled ( $this -> mongo_root_username ) && filled ( $this -> mongo_root_password )) {
// Use container name instead of server IP for service-based MongoDB
$url = " mongodb:// { $this -> mongo_root_username } : { $this -> mongo_root_password } @ { $this -> container_name } :27017 " ;
} else {
// If no environment variables are available, throw an exception
throw new \Exception ( 'MongoDB credentials not found. Ensure MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD environment variables are available in the container.' );
}
}
\Log :: info ( 'MongoDB backup URL configured' , [ 'has_url' => filled ( $url ), 'using_env_vars' => blank ( $this -> database -> internal_db_url )]);
2023-10-19 15:17:38 +00:00
if ( $databaseWithCollections === 'all' ) {
2024-06-10 20:43:34 +00:00
$commands [] = 'mkdir -p ' . $this -> backup_dir ;
2024-08-13 11:08:22 +00:00
if ( str ( $this -> database -> image ) -> startsWith ( 'mongo:4' )) {
2025-02-03 20:49:13 +00:00
$commands [] = " docker exec $this->container_name mongodump --uri= \" $url\ " -- gzip -- archive > $this -> backup_location " ;
2024-05-02 09:45:53 +00:00
} else {
2025-02-03 20:49:13 +00:00
$commands [] = " docker exec $this->container_name mongodump --authenticationDatabase=admin --uri= \" $url\ " -- gzip -- archive > $this -> backup_location " ;
2024-05-02 09:45:53 +00:00
}
2023-10-19 15:17:38 +00:00
} else {
2023-10-24 08:42:28 +00:00
if ( str ( $databaseWithCollections ) -> contains ( ':' )) {
$databaseName = str ( $databaseWithCollections ) -> before ( ':' );
$collectionsToExclude = str ( $databaseWithCollections ) -> after ( ':' ) -> explode ( ',' );
} else {
$databaseName = $databaseWithCollections ;
$collectionsToExclude = collect ();
}
2024-06-10 20:43:34 +00:00
$commands [] = 'mkdir -p ' . $this -> backup_dir ;
2023-10-24 08:42:28 +00:00
if ( $collectionsToExclude -> count () === 0 ) {
2024-08-13 11:08:22 +00:00
if ( str ( $this -> database -> image ) -> startsWith ( 'mongo:4' )) {
2025-02-03 20:49:13 +00:00
$commands [] = " docker exec $this->container_name mongodump --uri= \" $url\ " -- gzip -- archive > $this -> backup_location " ;
2024-05-02 09:45:53 +00:00
} else {
2025-02-03 20:49:13 +00:00
$commands [] = " docker exec $this->container_name mongodump --authenticationDatabase=admin --uri= \" $url\ " -- db $databaseName -- gzip -- archive > $this -> backup_location " ;
2024-05-02 09:45:53 +00:00
}
2023-10-24 08:42:28 +00:00
} else {
2025-01-07 14:31:43 +00:00
if ( str ( $this -> database -> image ) -> startsWith ( 'mongo:4' )) {
$commands [] = " docker exec $this->container_name mongodump --uri= $url --gzip --excludeCollection " . $collectionsToExclude -> implode ( ' --excludeCollection ' ) . " --archive > $this->backup_location " ;
} else {
2025-02-03 20:49:13 +00:00
$commands [] = " docker exec $this->container_name mongodump --authenticationDatabase=admin --uri= \" $url\ " -- db $databaseName -- gzip -- excludeCollection " . $collectionsToExclude->implode (' --excludeCollection '). " -- archive > $this -> backup_location " ;
2025-01-07 14:31:43 +00:00
}
2023-10-24 08:42:28 +00:00
}
2023-10-19 15:17:38 +00:00
}
$this -> backup_output = instant_remote_process ( $commands , $this -> server );
$this -> backup_output = trim ( $this -> backup_output );
if ( $this -> backup_output === '' ) {
$this -> backup_output = null ;
}
2025-01-07 14:31:43 +00:00
} catch ( \Throwable $e ) {
2023-10-19 15:17:38 +00:00
$this -> add_to_backup_output ( $e -> getMessage ());
throw $e ;
}
}
2024-06-10 20:43:34 +00:00
2023-10-13 13:45:24 +00:00
private function backup_standalone_postgresql ( string $database ) : void
2023-08-10 13:52:54 +00:00
{
try {
2024-06-10 20:43:34 +00:00
$commands [] = 'mkdir -p ' . $this -> backup_dir ;
2024-08-26 08:32:05 +00:00
$backupCommand = 'docker exec' ;
2024-08-16 09:53:27 +00:00
if ( $this -> postgres_password ) {
2025-05-03 11:34:40 +00:00
$backupCommand .= " -e PGPASSWORD= \" { $this -> postgres_password } \" " ;
2024-08-16 09:53:27 +00:00
}
2025-01-07 14:31:43 +00:00
if ( $this -> backup -> dump_all ) {
2024-10-03 10:39:45 +00:00
$backupCommand .= " $this->container_name pg_dumpall --username { $this -> database -> postgres_user } | gzip > $this->backup_location " ;
} else {
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username { $this -> database -> postgres_user } $database > $this->backup_location " ;
}
2024-08-16 09:53:27 +00:00
$commands [] = $backupCommand ;
2023-08-10 13:52:54 +00:00
$this -> backup_output = instant_remote_process ( $commands , $this -> server );
$this -> backup_output = trim ( $this -> backup_output );
if ( $this -> backup_output === '' ) {
$this -> backup_output = null ;
}
2025-01-07 14:31:43 +00:00
} catch ( \Throwable $e ) {
2023-09-11 15:36:30 +00:00
$this -> add_to_backup_output ( $e -> getMessage ());
2023-10-13 13:45:24 +00:00
throw $e ;
2023-08-10 13:52:54 +00:00
}
}
2024-06-10 20:43:34 +00:00
2023-10-24 12:31:28 +00:00
private function backup_standalone_mysql ( string $database ) : void
{
try {
2024-06-10 20:43:34 +00:00
$commands [] = 'mkdir -p ' . $this -> backup_dir ;
2025-01-07 14:31:43 +00:00
if ( $this -> backup -> dump_all ) {
2025-02-03 20:49:13 +00:00
$commands [] = " docker exec $this->container_name mysqldump -u root -p \" { $this -> database -> mysql_root_password } \" --all-databases --single-transaction --quick --lock-tables=false --compress | gzip > $this->backup_location " ;
2024-10-03 10:39:45 +00:00
} else {
2025-02-03 20:49:13 +00:00
$commands [] = " docker exec $this->container_name mysqldump -u root -p \" { $this -> database -> mysql_root_password } \" $database > $this->backup_location " ;
2024-10-03 10:39:45 +00:00
}
2023-10-24 12:31:28 +00:00
$this -> backup_output = instant_remote_process ( $commands , $this -> server );
$this -> backup_output = trim ( $this -> backup_output );
if ( $this -> backup_output === '' ) {
$this -> backup_output = null ;
}
2025-01-07 14:31:43 +00:00
} catch ( \Throwable $e ) {
2023-10-24 12:31:28 +00:00
$this -> add_to_backup_output ( $e -> getMessage ());
throw $e ;
}
}
2024-06-10 20:43:34 +00:00
2023-10-24 12:31:28 +00:00
private function backup_standalone_mariadb ( string $database ) : void
{
try {
2024-06-10 20:43:34 +00:00
$commands [] = 'mkdir -p ' . $this -> backup_dir ;
2025-01-07 14:31:43 +00:00
if ( $this -> backup -> dump_all ) {
2025-02-03 20:49:13 +00:00
$commands [] = " docker exec $this->container_name mariadb-dump -u root -p \" { $this -> database -> mariadb_root_password } \" --all-databases --single-transaction --quick --lock-tables=false --compress > $this->backup_location " ;
2024-10-03 10:39:45 +00:00
} else {
2025-02-03 20:49:13 +00:00
$commands [] = " docker exec $this->container_name mariadb-dump -u root -p \" { $this -> database -> mariadb_root_password } \" $database > $this->backup_location " ;
2024-10-03 10:39:45 +00:00
}
2023-10-24 12:31:28 +00:00
$this -> backup_output = instant_remote_process ( $commands , $this -> server );
$this -> backup_output = trim ( $this -> backup_output );
if ( $this -> backup_output === '' ) {
$this -> backup_output = null ;
}
2025-01-07 14:31:43 +00:00
} catch ( \Throwable $e ) {
2023-10-24 12:31:28 +00:00
$this -> add_to_backup_output ( $e -> getMessage ());
throw $e ;
}
}
2024-06-10 20:43:34 +00:00
2023-08-11 14:13:53 +00:00
private function add_to_backup_output ( $output ) : void
2023-08-10 13:52:54 +00:00
{
2025-01-07 14:31:43 +00:00
if ( $this -> backup_output ) {
$this -> backup_output = $this -> backup_output . " \n " . $output ;
} else {
$this -> backup_output = $output ;
}
2023-08-10 13:52:54 +00:00
}
2023-10-13 13:45:24 +00:00
private function calculate_size ()
2023-08-10 13:52:54 +00:00
{
2023-10-13 13:45:24 +00:00
return instant_remote_process ([ " du -b $this->backup_location | cut -f1 " ], $this -> server , false );
2023-08-10 13:52:54 +00:00
}
2023-08-11 14:13:53 +00:00
private function upload_to_s3 () : void
2023-08-10 16:20:12 +00:00
{
try {
if ( is_null ( $this -> s3 )) {
return ;
}
$key = $this -> s3 -> key ;
$secret = $this -> s3 -> secret ;
2023-10-10 11:10:43 +00:00
// $region = $this->s3->region;
2023-08-10 16:20:12 +00:00
$bucket = $this -> s3 -> bucket ;
$endpoint = $this -> s3 -> endpoint ;
2023-11-15 08:34:27 +00:00
$this -> s3 -> testConnection ( shouldSave : true );
2025-01-07 14:31:43 +00:00
if ( data_get ( $this -> backup , 'database_type' ) === \App\Models\ServiceDatabase :: class ) {
2024-09-16 12:15:06 +00:00
$network = $this -> database -> service -> destination -> network ;
} else {
$network = $this -> database -> destination -> network ;
}
2024-09-07 09:48:20 +00:00
2024-09-16 12:15:06 +00:00
$fullImageName = $this -> getFullImageName ();
2024-10-02 09:45:30 +00:00
2025-03-28 21:45:12 +00:00
$containerExists = instant_remote_process ([ " docker ps -a -q -f name=backup-of- { $this -> backup -> uuid } " ], $this -> server , false );
if ( filled ( $containerExists )) {
instant_remote_process ([ " docker rm -f backup-of- { $this -> backup -> uuid } " ], $this -> server , false );
}
2024-10-02 09:45:30 +00:00
if ( isDev ()) {
2025-05-05 06:55:44 +00:00
if ( $this -> database -> name === 'coolify-db' ) {
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/coolify/coolify-db-' . $this -> server -> ip . $this -> backup_file ;
$commands [] = " docker run -d --network { $network } --name backup-of- { $this -> backup -> uuid } --rm -v $backup_location_from : $this->backup_location :ro { $fullImageName } " ;
} else {
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/' . str ( $this -> team -> name ) -> slug () . '-' . $this -> team -> id . '/' . $this -> directory_name . $this -> backup_file ;
$commands [] = " docker run -d --network { $network } --name backup-of- { $this -> backup -> uuid } --rm -v $backup_location_from : $this->backup_location :ro { $fullImageName } " ;
}
2024-10-02 09:45:30 +00:00
} else {
2025-01-07 14:31:43 +00:00
$commands [] = " docker run -d --network { $network } --name backup-of- { $this -> backup -> uuid } --rm -v $this->backup_location : $this->backup_location :ro { $fullImageName } " ;
2024-10-02 09:45:30 +00:00
}
2025-02-03 20:49:13 +00:00
$commands [] = " docker exec backup-of- { $this -> backup -> uuid } mc config host add temporary { $endpoint } $key \" $secret\ " " ;
2025-01-07 14:31:43 +00:00
$commands [] = " docker exec backup-of- { $this -> backup -> uuid } mc cp $this->backup_location temporary/ $bucket { $this -> backup_dir } / " ;
2023-08-11 14:13:53 +00:00
instant_remote_process ( $commands , $this -> server );
2024-10-02 09:45:30 +00:00
2023-08-10 16:20:12 +00:00
$this -> add_to_backup_output ( 'Uploaded to S3.' );
2025-01-07 14:31:43 +00:00
} catch ( \Throwable $e ) {
2023-09-11 15:36:30 +00:00
$this -> add_to_backup_output ( $e -> getMessage ());
2023-10-10 11:10:43 +00:00
throw $e ;
2023-08-11 14:13:53 +00:00
} finally {
2025-01-07 14:31:43 +00:00
$command = " docker rm -f backup-of- { $this -> backup -> uuid } " ;
2024-09-16 12:15:06 +00:00
instant_remote_process ([ $command ], $this -> server );
}
}
private function getFullImageName () : string
{
2024-10-01 08:37:40 +00:00
$settings = instanceSettings ();
2024-11-12 14:18:48 +00:00
$helperImage = config ( 'constants.coolify.helper_image' );
2024-09-16 12:15:06 +00:00
$latestVersion = $settings -> helper_version ;
return " { $helperImage } : { $latestVersion } " ;
}
2025-07-18 13:47:14 +00:00
public function failed ( ? Throwable $exception ) : void
{
$log = ScheduledDatabaseBackupExecution :: where ( 'uuid' , $this -> backup_log_uuid ) -> first ();
if ( $log ) {
$log -> update ([
'status' => 'failed' ,
'message' => 'Job failed: ' . $exception -> getMessage (),
'size' => 0 ,
'filename' => null ,
]);
}
}
2023-08-10 13:52:54 +00:00
}