mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
126
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ece6b6184 | ||
|
|
e90c1df8d8 | ||
|
|
78ffcf4e33 | ||
|
|
0374816228 | ||
|
|
237369db1c | ||
|
|
f7070d6fa0 | ||
|
|
0b94761386 | ||
|
|
2e129d2d47 | ||
|
|
717975b456 | ||
|
|
6d6a43e3e3 | ||
|
|
cdd8f04e97 | ||
|
|
9505d30d43 | ||
|
|
a106454a5c | ||
|
|
c27797a2e1 | ||
|
|
ef53918299 | ||
|
|
27eee9f9ed | ||
|
|
d4c943376a | ||
|
|
b40776e005 | ||
|
|
f5eb3964c2 | ||
|
|
50a52ddc24 | ||
|
|
b521137149 | ||
|
|
3dc82bcaca | ||
|
|
69480622f0 | ||
|
|
6663d1cd80 | ||
|
|
0e01cb4b2e | ||
|
|
ee629bfa46 | ||
|
|
cccf8e0204 | ||
|
|
b4f5adea59 | ||
|
|
6e28d1a333 | ||
|
|
0b88a75ec4 | ||
|
|
9142e44144 | ||
|
|
a8ee7f42e8 | ||
|
|
8a965b2ce3 | ||
|
|
4fdd76dc95 | ||
|
|
fbae9f859f | ||
|
|
1c857c982e | ||
|
|
40f0936dc7 | ||
|
|
bebe3a217f | ||
|
|
b3222534ed | ||
|
|
3ccb38f9ec | ||
|
|
bb3aa1a172 | ||
|
|
77deacbd40 | ||
|
|
9b97317bdc | ||
|
|
ce03231ca0 | ||
|
|
43759ae273 | ||
|
|
ecd960a6fd | ||
|
|
9da6a6f5ec | ||
|
|
20bc1c4c98 | ||
|
|
f787acecdb | ||
|
|
f2070cac15 | ||
|
|
95841c0418 | ||
|
|
08daa7cf6d | ||
|
|
47352e4062 | ||
|
|
1488bf1d20 | ||
|
|
2aa3b1e296 | ||
|
|
aaeddfe780 | ||
|
|
66fe902152 | ||
|
|
4c3ea3c073 | ||
|
|
b59d6051b6 | ||
|
|
ab2b37fc86 | ||
|
|
777340a626 | ||
|
|
b27a16ccaf | ||
|
|
92e08d3a2d | ||
|
|
d6128282f5 | ||
|
|
63ed3ae19c | ||
|
|
a294bf24fc | ||
|
|
0e9c61ac86 | ||
|
|
2cec0f5469 | ||
|
|
b2f0249146 | ||
|
|
b0807da8ee | ||
|
|
125e388ded | ||
|
|
d9ece4c761 | ||
|
|
d3607471eb | ||
|
|
d1f7bd26c8 | ||
|
|
7296027e43 | ||
|
|
1ce00b139d | ||
|
|
2ebd500d6b | ||
|
|
6b5b3ec865 | ||
|
|
5fa1da3610 | ||
|
|
e5f841e353 | ||
|
|
3a38b492f5 | ||
|
|
5f4f6cd9c5 | ||
|
|
f2a337d86e | ||
|
|
71b4c483c2 | ||
|
|
d9569df1cc | ||
|
|
d1efcd6a61 | ||
|
|
cea7ab8d4c | ||
|
|
cfc178b046 | ||
|
|
50aba6a1c2 | ||
|
|
5fd5c875fc | ||
|
|
1ceaa599b5 | ||
|
|
1138b4fd59 | ||
|
|
fae5c5b92e | ||
|
|
9a446ec0ec | ||
|
|
675ad1d0c6 | ||
|
|
a2463e114e | ||
|
|
9fab5c46f1 | ||
|
|
d730b41b5f | ||
|
|
66228a2462 | ||
|
|
fddac97835 | ||
|
|
9499cbf9f7 | ||
|
|
01f55445f1 | ||
|
|
875662c066 | ||
|
|
30ef106406 | ||
|
|
e4f653be13 | ||
|
|
53df65a3c4 | ||
|
|
2f8b526c01 | ||
|
|
c1cf6acc7a | ||
|
|
dd25899f74 | ||
|
|
fc2f1cef31 | ||
|
|
03aeea857c | ||
|
|
b2c7a3eb33 | ||
|
|
128b9d1b1b | ||
|
|
55a6732444 | ||
|
|
d8ea72b49e | ||
|
|
c32f8e49e2 | ||
|
|
1989ff873e | ||
|
|
f164676b44 | ||
|
|
970c35b31a | ||
|
|
38eb24d0ad | ||
|
|
186b92d5af | ||
|
|
7ad08bfef2 | ||
|
|
6f87757c53 | ||
|
|
4f0dd56afc | ||
|
|
7918ee2f20 | ||
|
|
748abb29cf |
@@ -26,6 +26,16 @@ _APP_DB_SCHEMA=appwrite
|
||||
_APP_DB_USER=user
|
||||
_APP_DB_PASS=password
|
||||
_APP_DB_ROOT_PASS=rootsecretpassword
|
||||
_APP_CONNECTIONS_MAX=3100
|
||||
_APP_POOL_CLIENTS=14
|
||||
_APP_CONNECTIONS_DB_REPLICAS=db_fra1_03=mariadb://root:rootsecretpassword@mariadb:3306/appwrite
|
||||
_APP_CONNECTIONS_DB_PROJECT=db_fra1_02=mariadb://user:password@mariadb:3306/appwrite
|
||||
_APP_CONNECTIONS_DB_CONSOLE=db_fra1_01=mariadb://user:password@mariadb:3306/appwrite
|
||||
_APP_CONNECTIONS_CACHE=redis_fra1_01=redis://redis:6379
|
||||
_APP_CONNECTIONS_QUEUE=redis_fra1_01=redis://redis:6379
|
||||
_APP_CONNECTIONS_PUBSUB=redis_fra1_01=redis://redis:6379
|
||||
_APP_CONNECTIONS_STORAGE=local://localhost
|
||||
_APP_CONNECTIONS_BACKUPS_STORAGE=dospaces://ACCESS:SECRET@default/backups-v1?region=fra1
|
||||
_APP_STORAGE_DEVICE=Local
|
||||
_APP_STORAGE_S3_ACCESS_KEY=
|
||||
_APP_STORAGE_S3_SECRET=
|
||||
@@ -83,8 +93,6 @@ _APP_USAGE_AGGREGATION_INTERVAL=5
|
||||
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
|
||||
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
|
||||
_APP_USAGE_STATS=enabled
|
||||
_APP_LOGGING_PROVIDER=
|
||||
_APP_LOGGING_CONFIG=
|
||||
_APP_GRAPHQL_MAX_BATCH_SIZE=10
|
||||
_APP_GRAPHQL_MAX_COMPLEXITY=250
|
||||
_APP_GRAPHQL_MAX_DEPTH=3
|
||||
|
||||
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- cl-*
|
||||
- appwrite-*
|
||||
|
||||
jobs:
|
||||
build-publish:
|
||||
|
||||
+1
-1
@@ -12,4 +12,4 @@
|
||||
debug/
|
||||
app/sdks
|
||||
dev/yasd_init.php
|
||||
.phpunit.result.cache
|
||||
.phpunit.result.cache
|
||||
@@ -74,6 +74,9 @@ RUN mkdir -p /storage/uploads && \
|
||||
# Executables
|
||||
RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/maintenance && \
|
||||
chmod +x /usr/local/bin/db-backup && \
|
||||
chmod +x /usr/local/bin/db-restore && \
|
||||
chmod +x /usr/local/bin/volume-sync && \
|
||||
chmod +x /usr/local/bin/usage && \
|
||||
chmod +x /usr/local/bin/install && \
|
||||
chmod +x /usr/local/bin/upgrade && \
|
||||
@@ -119,6 +122,12 @@ RUN echo "default_socket_timeout=-1" >> /usr/local/etc/php/conf.d/appwrite.ini
|
||||
RUN echo "opcache.jit_buffer_size=100M" >> /usr/local/etc/php/conf.d/appwrite.ini
|
||||
RUN echo "opcache.jit=1235" >> /usr/local/etc/php/conf.d/appwrite.ini
|
||||
|
||||
RUN \
|
||||
apk update \
|
||||
&& apk add --no-cache \
|
||||
docker \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD [ "php", "app/http.php", "-dopcache.preload=opcache.preload=/usr/src/code/app/preload.php" ]
|
||||
+1
-1
Submodule app/console updated: e965738987...0f9e5e7839
@@ -614,6 +614,12 @@ $register->set('pools', function () {
|
||||
'multiple' => true,
|
||||
'schemes' => ['mariadb', 'mysql'],
|
||||
],
|
||||
'replica' => [
|
||||
'type' => 'database',
|
||||
'dsns' => App::getEnv('_APP_CONNECTIONS_DB_REPLICAS', $fallbackForDB),
|
||||
'multiple' => true,
|
||||
'schemes' => ['mariadb', 'mysql'],
|
||||
],
|
||||
'queue' => [
|
||||
'type' => 'queue',
|
||||
'dsns' => App::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis),
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php backup $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php restore $@
|
||||
+1
-1
@@ -64,7 +64,7 @@
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/queue": "0.5.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "0.17.*",
|
||||
"utopia-php/storage": "0.18.*",
|
||||
"utopia-php/swoole": "0.5.*",
|
||||
"utopia-php/vcs": "0.5.*",
|
||||
"utopia-php/websocket": "0.1.*",
|
||||
|
||||
Generated
+16
-83
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6ff937a260c3e0c09de9eb5e073d830d",
|
||||
"content-hash": "06c2610579f319495ea7d2d28f42d376",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -851,72 +851,6 @@
|
||||
},
|
||||
"time": "2021-10-08T21:21:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/pint",
|
||||
"version": "v1.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/pint.git",
|
||||
"reference": "e60e2112ee779ce60f253695b273d1646a17d6f1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/pint/zipball/e60e2112ee779ce60f253695b273d1646a17d6f1",
|
||||
"reference": "e60e2112ee779ce60f253695b273d1646a17d6f1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"ext-xml": "*",
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.11.0",
|
||||
"illuminate/view": "^9.32.0",
|
||||
"laravel-zero/framework": "^9.2.0",
|
||||
"mockery/mockery": "^1.5.1",
|
||||
"nunomaduro/larastan": "^2.2.0",
|
||||
"nunomaduro/termwind": "^1.14.0",
|
||||
"pestphp/pest": "^1.22.1"
|
||||
},
|
||||
"bin": [
|
||||
"builds/pint"
|
||||
],
|
||||
"type": "project",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Seeders\\": "database/seeders/",
|
||||
"Database\\Factories\\": "database/factories/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nuno Maduro",
|
||||
"email": "enunomaduro@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "An opinionated code formatter for PHP.",
|
||||
"homepage": "https://laravel.com",
|
||||
"keywords": [
|
||||
"format",
|
||||
"formatter",
|
||||
"lint",
|
||||
"linter",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/pint/issues",
|
||||
"source": "https://github.com/laravel/pint"
|
||||
},
|
||||
"time": "2022-11-29T16:25:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/csv",
|
||||
"version": "9.7.1",
|
||||
@@ -2808,16 +2742,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/storage",
|
||||
"version": "0.17.0",
|
||||
"version": "0.18.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/storage.git",
|
||||
"reference": "efec5376c02d3d8330f1beb1469e6d6e313e21ee"
|
||||
"reference": "e1d4f22aba2898a33368d1e1ce89f805db85f842"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/storage/zipball/efec5376c02d3d8330f1beb1469e6d6e313e21ee",
|
||||
"reference": "efec5376c02d3d8330f1beb1469e6d6e313e21ee",
|
||||
"url": "https://api.github.com/repos/utopia-php/storage/zipball/e1d4f22aba2898a33368d1e1ce89f805db85f842",
|
||||
"reference": "e1d4f22aba2898a33368d1e1ce89f805db85f842",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2857,9 +2791,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/storage/issues",
|
||||
"source": "https://github.com/utopia-php/storage/tree/0.17.0"
|
||||
"source": "https://github.com/utopia-php/storage/tree/0.18.0"
|
||||
},
|
||||
"time": "2023-08-21T11:28:36+00:00"
|
||||
"time": "2023-10-18T08:42:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/swoole",
|
||||
@@ -2914,26 +2848,25 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/system",
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/system.git",
|
||||
"reference": "01bf0d283aded0ee0a7a6e5ff540acf64270ab27"
|
||||
"reference": "4593d4d334b0c15879c4744a826e0362924c5d66"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/system/zipball/01bf0d283aded0ee0a7a6e5ff540acf64270ab27",
|
||||
"reference": "01bf0d283aded0ee0a7a6e5ff540acf64270ab27",
|
||||
"url": "https://api.github.com/repos/utopia-php/system/zipball/4593d4d334b0c15879c4744a826e0362924c5d66",
|
||||
"reference": "4593d4d334b0c15879c4744a826e0362924c5d66",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"laravel/pint": "1.2.*",
|
||||
"php": ">=8.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
"vimeo/psalm": "4.0.1"
|
||||
"laravel/pint": "1.13.*",
|
||||
"phpstan/phpstan": "1.10.*",
|
||||
"phpunit/phpunit": "9.6.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -2965,9 +2898,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/system/issues",
|
||||
"source": "https://github.com/utopia-php/system/tree/0.7.1"
|
||||
"source": "https://github.com/utopia-php/system/tree/0.7.2"
|
||||
},
|
||||
"time": "2023-08-30T09:14:37+00:00"
|
||||
"time": "2023-10-20T01:39:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/vcs",
|
||||
|
||||
+57
-1
@@ -121,6 +121,14 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_CONNECTIONS_MAX
|
||||
- _APP_POOL_CLIENTS
|
||||
- _APP_CONNECTIONS_DB_PROJECT
|
||||
- _APP_CONNECTIONS_DB_REPLICAS
|
||||
- _APP_CONNECTIONS_DB_CONSOLE
|
||||
- _APP_CONNECTIONS_CACHE
|
||||
- _APP_CONNECTIONS_QUEUE
|
||||
- _APP_CONNECTIONS_PUBSUB
|
||||
- _APP_SMTP_HOST
|
||||
- _APP_SMTP_PORT
|
||||
- _APP_SMTP_SECURE
|
||||
@@ -791,7 +799,7 @@ services:
|
||||
- OPR_PROXY_HEALTHCHECK=enabled
|
||||
|
||||
mariadb:
|
||||
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
|
||||
image: mysql:8.0.33 # fix issues when upgrading using: mysql_upgrade -u root -p
|
||||
container_name: appwrite-mariadb
|
||||
<<: *x-logging
|
||||
networks:
|
||||
@@ -820,6 +828,53 @@ services:
|
||||
# - SMARTHOST_HOST=smtp
|
||||
# - SMARTHOST_PORT=587
|
||||
|
||||
xtrabackup:
|
||||
image: percona/percona-xtrabackup:8.0.33
|
||||
container_name: xtrabackup
|
||||
command: sleep infinity
|
||||
volumes:
|
||||
- backup-data:/backups:rw
|
||||
- appwrite-mariadb:/var/lib/mysql:r
|
||||
networks:
|
||||
- appwrite
|
||||
user: 'root:root'
|
||||
|
||||
appwrite-backup:
|
||||
image: appwrite-dev
|
||||
<<: *x-logging
|
||||
entrypoint: db-backup
|
||||
container_name: appwrite-backup
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- backup-data:/backups:rw
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- _APP_CONNECTIONS_DB_REPLICAS
|
||||
- _APP_CONNECTIONS_BACKUPS_STORAGE
|
||||
|
||||
appwrite-restore:
|
||||
command: sleep infinity
|
||||
container_name: appwrite-backup-restore
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- backup-data:/backups:rw
|
||||
- appwrite-mariadb:/var/lib/mysql:rw
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- _APP_CONNECTIONS_DB_REPLICAS
|
||||
- _APP_CONNECTIONS_BACKUPS_STORAGE
|
||||
|
||||
redis:
|
||||
image: redis:7.0.4-alpine
|
||||
<<: *x-logging
|
||||
@@ -971,4 +1026,5 @@ volumes:
|
||||
appwrite-builds:
|
||||
appwrite-influxdb:
|
||||
appwrite-config:
|
||||
backup-data:
|
||||
# appwrite-chronograf:
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Services;
|
||||
|
||||
use Appwrite\Platform\Tasks\Backup;
|
||||
use Appwrite\Platform\Tasks\Restore;
|
||||
use Utopia\Platform\Service;
|
||||
use Appwrite\Platform\Tasks\Doctor;
|
||||
use Appwrite\Platform\Tasks\Install;
|
||||
@@ -40,6 +42,8 @@ class Tasks extends Service
|
||||
->addAction(VolumeSync::getName(), new VolumeSync())
|
||||
->addAction(Specs::getName(), new Specs())
|
||||
->addAction(CalcTierStats::getName(), new CalcTierStats())
|
||||
->addAction(Backup::getName(), new Backup())
|
||||
->addAction(Restore::getName(), new Restore())
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Exception;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\DOSpaces;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Backup extends Action
|
||||
{
|
||||
public const BACKUPS_PATH = '/backups';
|
||||
public const BACKUP_INTERVAL_SECONDS = 60 * 60 * 4; // 4 hours;
|
||||
public const COMPRESS_ALGORITHM = 'zstd'; // https://www.percona.com/blog/get-your-backup-to-half-of-its-size-introducing-zstd-support-in-percona-xtrabackup/
|
||||
public const CLEANUP_LOCAL_FILES_SECONDS = 60 * 60 * 24 * 30; // 2 days?
|
||||
public const CLEANUP_CLOUD_FILES_SECONDS = 60 * 60 * 24 * 30; // 14 days?;
|
||||
public const UPLOAD_CHUNK_SIZE = 5 * 1024 * 1024; // Must be greater than 5MB;
|
||||
public const RETRY_BACKUP = 1;
|
||||
public const RETRY_TAR = 1;
|
||||
public const RETRY_UPLOAD = 2;
|
||||
public const VERSION = 'v1';
|
||||
protected ?DSN $dsn = null;
|
||||
protected ?string $database = null;
|
||||
protected ?DOSpaces $s3 = null;
|
||||
protected string $xtrabackupContainerId;
|
||||
protected int $processors = 1;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Backup a database')
|
||||
->param('database', null, new Text(20), 'Database name, for example: db_fra1_03')
|
||||
->inject('pools')
|
||||
->inject('logError')
|
||||
->callback(fn(string $database, Group $pools, callable $logError) => $this->action($database, $pools, $logError));
|
||||
}
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'backup';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function action(string $database, Group $pools, callable $logError): void
|
||||
{
|
||||
$this->checkEnvVariables();
|
||||
|
||||
$this->database = $database;
|
||||
$this->dsn = $this->getDsn($database);
|
||||
if (is_null($this->dsn)) {
|
||||
throw new Exception('No DSN match');
|
||||
}
|
||||
|
||||
|
||||
console::info('Trying to connect to ' . $this->dsn->getHost());
|
||||
|
||||
$dsn = new DSN(App::getEnv('_APP_CONNECTIONS_BACKUPS_STORAGE', ''));
|
||||
$this->s3 = new DOSpaces('/' . $database . '/' . self::VERSION, $dsn->getUser(), $dsn->getPassword(), $dsn->getPath(), $dsn->getParam('region'));
|
||||
|
||||
try {
|
||||
$this->retry(function () use ($pools, $database) {
|
||||
$pools
|
||||
->get('replica_' . $database)
|
||||
->pop()
|
||||
->getResource();
|
||||
}, 10, 5);
|
||||
} catch (Exception $error) {
|
||||
throw new Exception('Failed to connect to database: ' . $error->getMessage());
|
||||
}
|
||||
|
||||
$this->setContainerId();
|
||||
$this->setProcessors();
|
||||
|
||||
Console::loop(function () use ($logError) {
|
||||
try {
|
||||
$this->start();
|
||||
} catch (Exception $error) {
|
||||
Console::error('[Error] Time: ' . date('Y-m-d H:i:s'));
|
||||
Console::error('[Error] Message: ' . $error->getMessage());
|
||||
Console::error('[Error] File: ' . $error->getFile());
|
||||
Console::error('[Error] Line: ' . $error->getLine());
|
||||
|
||||
$logError($error, 'backup', 'action');
|
||||
}
|
||||
}, self::BACKUP_INTERVAL_SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function start(): void
|
||||
{
|
||||
$start = microtime(true);
|
||||
$time = date('Y_m_d_H_i_s');
|
||||
|
||||
self::log('--- Backup Start ' . $time . ' --- ');
|
||||
|
||||
$path = self::BACKUPS_PATH . '/' . $this->database . '/' . self::VERSION;
|
||||
$local = new Local($path . '/' . $time);
|
||||
$local->setTransferChunkSize(self::UPLOAD_CHUNK_SIZE);
|
||||
|
||||
$tarFile = $local->getPath($time . '.tar.gz');
|
||||
$backups = $local->getRoot() . '/files';
|
||||
|
||||
$this->backup($backups);
|
||||
$this->tar($backups, $tarFile);
|
||||
$this->upload($tarFile, $local);
|
||||
|
||||
if (!unlink($tarFile)) {
|
||||
Console::error('Error deleting: ' . $tarFile);
|
||||
}
|
||||
|
||||
$this->cleanLocalFiles($path);
|
||||
$this->cleanCloudFiles();
|
||||
|
||||
self::log('--- Backup Finish ' . (microtime(true) - $start) . ' seconds --- ' . PHP_EOL . PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function backup(string $target)
|
||||
{
|
||||
self::log('Xtrabackup start');
|
||||
|
||||
$start = microtime(true);
|
||||
|
||||
if (!file_exists(self::BACKUPS_PATH)) {
|
||||
throw new Exception('Mount directory does not exist');
|
||||
}
|
||||
|
||||
if (!file_exists($target) && !mkdir($target, 0755, true)) {
|
||||
throw new Exception('Error creating directory: ' . $target);
|
||||
}
|
||||
|
||||
$filename = basename($target);
|
||||
$logfile = $target . '/../backup.log';
|
||||
|
||||
$args = [
|
||||
'xtrabackup',
|
||||
'--user=' . $this->dsn->getUser(),
|
||||
'--password=' . $this->dsn->getPassword(),
|
||||
'--host=' . $this->dsn->getHost(),
|
||||
'--port=' . $this->dsn->getPort(),
|
||||
'--backup',
|
||||
'--strict',
|
||||
'--history="' . $this->database . '|' . pathinfo($filename, PATHINFO_FILENAME) . '"', // PERCONA_SCHEMA.xtrabackup_history
|
||||
'--slave-info',
|
||||
'--safe-slave-backup',
|
||||
'--safe-slave-backup-timeout=300',
|
||||
'--check-privileges', // checks if Percona XtraBackup has all the required privileges.
|
||||
'--target-dir=' . $target,
|
||||
'--compress=' . self::COMPRESS_ALGORITHM,
|
||||
'--compress-threads=' . $this->processors,
|
||||
'--parallel=' . $this->processors,
|
||||
'2> ' . $logfile,
|
||||
];
|
||||
|
||||
$this->retry(function () use ($args, $logfile, $target) {
|
||||
shell_exec('docker exec ' . $this->xtrabackupContainerId . ' ' . implode(' ', $args));
|
||||
$stderr = shell_exec('tail -1 ' . $logfile);
|
||||
if (!str_contains($stderr, 'completed OK!')) {
|
||||
shell_exec('rm -rf ' . $target . '/*');
|
||||
throw new Exception(' Backup failed: ' . $stderr);
|
||||
}
|
||||
}, self::RETRY_BACKUP);
|
||||
|
||||
if (!unlink($logfile)) {
|
||||
throw new Exception('Error deleting: ' . $logfile);
|
||||
}
|
||||
|
||||
self::log('Xtrabackup Finish ' . (microtime(true) - $start) . ' seconds');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function tar(string $directory, string $file)
|
||||
{
|
||||
self::log('Tar start');
|
||||
$start = microtime(true);
|
||||
|
||||
$this->retry(function () use ($directory, $file) {
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$cmd = 'cd ' . $directory . ' && tar -zcf ' . $file . ' . && cd ' . getcwd();
|
||||
Console::execute($cmd, '', $stdout, $stderr);
|
||||
if (!empty($stderr)) {
|
||||
throw new Exception($stderr);
|
||||
}
|
||||
}, self::RETRY_TAR);
|
||||
|
||||
if (!file_exists($file)) {
|
||||
throw new Exception('Tar file not found: ' . $file);
|
||||
}
|
||||
|
||||
self::log('Tar finish ' . (microtime(true) - $start) . ' seconds');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function upload(string $file, Device $local)
|
||||
{
|
||||
$start = microtime(true);
|
||||
self::log('Upload start');
|
||||
$filename = basename($file);
|
||||
|
||||
if (!$this->s3->exists('/')) {
|
||||
throw new Exception('Can\'t read s3 root directory');
|
||||
}
|
||||
|
||||
$destination = $this->s3->getRoot() . '/' . $filename;
|
||||
|
||||
$this->retry(function () use ($local, $file, $destination) {
|
||||
if (!$local->transfer($file, $destination, $this->s3)) {
|
||||
throw new Exception('Error uploading to ' . $destination);
|
||||
}
|
||||
}, self::RETRY_UPLOAD);
|
||||
|
||||
if (!$this->s3->exists($destination)) {
|
||||
throw new Exception('File not found on cloud: ' . $destination);
|
||||
}
|
||||
|
||||
self::log('Upload finish ' . (microtime(true) - $start) . ' seconds');
|
||||
}
|
||||
|
||||
public static function log(string $message): void
|
||||
{
|
||||
if (!empty($message)) {
|
||||
Console::log(date('Y-m-d H:i:s') . ' ' . $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function checkEnvVariables(): void
|
||||
{
|
||||
foreach (
|
||||
[
|
||||
'_APP_CONNECTIONS_BACKUPS_STORAGE',
|
||||
'_APP_CONNECTIONS_DB_REPLICAS',
|
||||
] as $env
|
||||
) {
|
||||
if (empty(App::getEnv($env))) {
|
||||
throw new Exception('Can\'t read ' . $env);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getDsn(string $database): ?DSN
|
||||
{
|
||||
foreach (explode(',', App::getEnv('_APP_CONNECTIONS_DB_REPLICAS', '')) as $project) {
|
||||
[$db, $dsn] = explode('=', $project);
|
||||
if ($db === $database) {
|
||||
return new DSN($dsn);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setContainerId()
|
||||
{
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
Console::execute('docker ps -f "name=xtrabackup" --format "{{.ID}}"', '', $stdout, $stderr);
|
||||
if (!empty($stderr)) {
|
||||
throw new Exception('Error setting container Id: ' . $stderr);
|
||||
}
|
||||
|
||||
$containerId = str_replace(PHP_EOL, '', $stdout);
|
||||
if (empty($containerId)) {
|
||||
throw new Exception('Xtrabackup Container ID not found');
|
||||
}
|
||||
|
||||
$this->xtrabackupContainerId = $containerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setProcessors()
|
||||
{
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
Console::execute('docker exec ' . $this->xtrabackupContainerId . ' nproc', '', $stdout, $stderr);
|
||||
if (!empty($stderr)) {
|
||||
throw new Exception('Error setting processors: ' . $stderr);
|
||||
}
|
||||
|
||||
$processors = str_replace(PHP_EOL, '', $stdout);
|
||||
$processors = empty($processors) ? 1 : intval($processors);
|
||||
$this->processors = $processors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function retry(callable $action, int $retries, int $sleep = 1)
|
||||
{
|
||||
try {
|
||||
return $action();
|
||||
} catch (Exception $e) {
|
||||
if ($retries > 0) {
|
||||
Console::warning('Retrying (' . $retries . ') ' . $e->getMessage());
|
||||
sleep($sleep);
|
||||
return $this->retry($action, $retries - 1, $sleep);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function cleanLocalFiles(string $path)
|
||||
{
|
||||
self::log('Clean local start');
|
||||
|
||||
$start = microtime(true);
|
||||
|
||||
$folder = scandir($path);
|
||||
if ($folder === false) {
|
||||
throw new Exception('Scan directory error ' . $path);
|
||||
}
|
||||
|
||||
foreach ($folder as $item) {
|
||||
$filename = $item . '.tar.gz';
|
||||
$fullPath = $path . '/' . $item;
|
||||
if ($this->isDelete($item, self::CLEANUP_LOCAL_FILES_SECONDS)) {
|
||||
// Check if file exist on cloud before delete
|
||||
if ($this->s3->exists($this->s3->getRoot() . '/' . $filename)) {
|
||||
$delete = true;
|
||||
} else {
|
||||
if ($this->isDelete($item, self::CLEANUP_CLOUD_FILES_SECONDS)) {
|
||||
$delete = true;
|
||||
} else {
|
||||
Console::warning('Skipping delete not found on cloud: ' . $filename . ' ');
|
||||
$delete = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($delete === true) {
|
||||
Console::success('Deleting: ' . $fullPath);
|
||||
shell_exec('rm -rf ' . $fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::log('Clean local finish ' . (microtime(true) - $start) . ' seconds');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function cleanCloudFiles(): void
|
||||
{
|
||||
self::log('Clean cloud start');
|
||||
$start = microtime(true);
|
||||
$files = $this->s3->getFiles($this->s3->getRoot());
|
||||
|
||||
if ($files['KeyCount'] > 0) {
|
||||
foreach ($files['Contents'] as $file) {
|
||||
$date = basename(basename($file['Key']), '.tar.gz');
|
||||
if ($this->isDelete($date, self::CLEANUP_CLOUD_FILES_SECONDS)) {
|
||||
if ($this->s3->delete($file['Key'])) {
|
||||
Console::success('Deleting: ' . $file['Key']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::log('Clean cloud finish ' . (microtime(true) - $start) . ' seconds');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $item
|
||||
* @param int $seconds
|
||||
* @return bool
|
||||
*/
|
||||
public function isDelete(string $item, int $seconds): bool
|
||||
{
|
||||
if (!str_contains($item, '_')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[$year, $month, $day, $hour, $minute, $second] = explode('_', $item);
|
||||
$date = $year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second;
|
||||
|
||||
try {
|
||||
$now = new \DateTime();
|
||||
$backupDate = new \DateTime($date);
|
||||
if (($now->getTimestamp() - $backupDate->getTimestamp()) > $seconds) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Exception;
|
||||
use Utopia\App;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\DOSpaces;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Restore extends Action
|
||||
{
|
||||
public const BACKUPS_PATH = '/backups';
|
||||
public const DATADIR = '/var/lib/mysql';
|
||||
public const DOWNLOAD_CHUNK_SIZE = 40 * 1024 * 1024; // Must be greater than 5MB;
|
||||
public const VERSION = 'v1';
|
||||
public const RETRY_DOWNLOAD = 5;
|
||||
public const RETRY_PREPARE = 5;
|
||||
public const RETRY_DECOMPRESS = 5;
|
||||
public const RETRY_TAR = 5;
|
||||
protected ?DOSpaces $s3 = null;
|
||||
protected string $xtrabackupContainerId;
|
||||
protected int $processors = 1;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Restore a DB')
|
||||
->param('id', '', new Text(19), 'The backup identification, We can take it from backups directory (Y_m_d_H_i_s)')
|
||||
->param('cloud', null, new Boolean(true), 'Download backup from cloud or use local directory')
|
||||
->param('database', null, new Text(15), 'The Database name for example db_fra1_01')
|
||||
->callback(fn ($id, $cloud, $project) => $this->action($id, $cloud, $project));
|
||||
}
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'restore';
|
||||
}
|
||||
|
||||
public function action(string $id, string $cloud, string $database): void
|
||||
{
|
||||
try {
|
||||
$this->checkEnvVariables();
|
||||
$this->setContainerId();
|
||||
$this->setProcessors();
|
||||
|
||||
$datadir = self::DATADIR;
|
||||
|
||||
try {
|
||||
$dsn = new DSN(App::getEnv('_APP_CONNECTIONS_BACKUPS_STORAGE', ''));
|
||||
$this->s3 = new DOSpaces('/' . $database . '/' . self::VERSION, $dsn->getUser(), $dsn->getPassword(), $dsn->getPath(), $dsn->getParam('region'));
|
||||
$this->s3->setTransferChunkSize(self::DOWNLOAD_CHUNK_SIZE);
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception($e->getMessage() . 'Invalid DSN.');
|
||||
}
|
||||
|
||||
if (!file_exists($datadir)) {
|
||||
throw new Exception('Datadir not found: ' . $datadir);
|
||||
}
|
||||
|
||||
if (count(scandir($datadir)) > 2) {
|
||||
throw new Exception($datadir . ' must be empty!');
|
||||
}
|
||||
|
||||
$this->log('--- Restore Start ' . $id . ' --- ');
|
||||
|
||||
$filename = $id . '.tar.gz';
|
||||
$start = microtime(true);
|
||||
$cloud = $cloud === 'true' || $cloud === '1';
|
||||
|
||||
$local = new Local(self::BACKUPS_PATH . '/restore/' . $id);
|
||||
$files = $local->getRoot() . '/files';
|
||||
|
||||
if (file_exists($local->getRoot())) {
|
||||
shell_exec('mv ' . $local->getRoot() . ' ' . $local->getRoot() . '/../DELETE_ME/' . time());
|
||||
}
|
||||
|
||||
if (!mkdir($files, 0755, true)) {
|
||||
throw new Exception('Error creating directory: ' . $files);
|
||||
}
|
||||
|
||||
if ($cloud) {
|
||||
$this->download($local->getPath($filename), $local); // Fast!
|
||||
$this->untar($local->getPath($filename), $files);
|
||||
} else {
|
||||
$source = self::BACKUPS_PATH . '/' . $database . '/' . self::VERSION . '/' . $id;
|
||||
$this->log('Copying... ' . $source);
|
||||
|
||||
if (!file_exists($source)) {
|
||||
throw new Exception('Source not found: ' . $source);
|
||||
}
|
||||
|
||||
shell_exec('cp -r ' . $source . ' ' . $local->getRoot() . '/../');
|
||||
}
|
||||
|
||||
if (!file_exists($files)) {
|
||||
throw new Exception('Files directory not found: ' . $files);
|
||||
}
|
||||
|
||||
$this->decompress($files); // Uncompressed is long!
|
||||
$this->prepare($files);
|
||||
$this->restore($files, $cloud, $datadir);
|
||||
|
||||
$this->log('Restore Finish in ' . (microtime(true) - $start) . ' seconds');
|
||||
} catch (Exception $e) {
|
||||
//todo: send alerts sentry?
|
||||
Console::error(date('Y-m-d H:i:s ') . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function download(string $file, Device $local)
|
||||
{
|
||||
$filename = basename($file);
|
||||
$path = $this->s3->getPath($filename);
|
||||
|
||||
if (!$this->s3->exists($path)) {
|
||||
throw new Exception('File: ' . $path . ' does not exist on cloud');
|
||||
}
|
||||
|
||||
$this->retry(function () use ($path, $file, $local, $filename) {
|
||||
if (file_exists($local->getPath('tmp_' . $filename))) {
|
||||
Console::warning('Deleting tmp_' . $filename);
|
||||
if (!$local->deletePath('tmp_' . $filename)) {
|
||||
throw new Exception('Error deleting tmp download dir');
|
||||
}
|
||||
}
|
||||
|
||||
$this->log('Download start');
|
||||
|
||||
if (!$this->s3->transfer($path, $file, $local)) {
|
||||
throw new Exception('Error Downloading ' . $file);
|
||||
}
|
||||
}, self::RETRY_DOWNLOAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function untar(string $file, string $directory)
|
||||
{
|
||||
$this->log('Untar Start');
|
||||
|
||||
$this->retry(function () use ($file, $directory) {
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$cmd = 'tar -xzf ' . $file . ' -C ' . $directory;
|
||||
Console::execute($cmd, '', $stdout, $stderr);
|
||||
if (!empty($stderr)) {
|
||||
throw new Exception($stderr);
|
||||
}
|
||||
}, self::RETRY_TAR);
|
||||
|
||||
if (!file_exists($file)) {
|
||||
throw new Exception('Restore file not found: ' . $file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function decompress(string $target)
|
||||
{
|
||||
$this->log('Decompress start');
|
||||
|
||||
$logfile = $target . '/../log.txt';
|
||||
|
||||
$args = [
|
||||
'xtrabackup',
|
||||
'--decompress',
|
||||
'--strict',
|
||||
'--remove-original', // Time consuming.
|
||||
'--compress-threads=' . $this->processors,
|
||||
'--parallel=' . $this->processors,
|
||||
'--target-dir=' . $target,
|
||||
'2> ' . $logfile,
|
||||
];
|
||||
|
||||
$this->retry(function () use ($args, $logfile) {
|
||||
shell_exec('docker exec ' . $this->xtrabackupContainerId . ' ' . implode(' ', $args));
|
||||
|
||||
$stderr = shell_exec('tail -1 ' . $logfile);
|
||||
if (!str_contains($stderr, 'completed OK!')) {
|
||||
throw new Exception('Decompress failed');
|
||||
}
|
||||
}, self::RETRY_DECOMPRESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function prepare(string $target)
|
||||
{
|
||||
$this->log('Prepare start');
|
||||
|
||||
if (!file_exists($target)) {
|
||||
throw new Exception('prepare error directory not found: ' . $target);
|
||||
}
|
||||
|
||||
$logfile = $target . '/../log.txt';
|
||||
|
||||
$args = [
|
||||
'xtrabackup',
|
||||
'--prepare',
|
||||
'--parallel=' . $this->processors,
|
||||
'--strict',
|
||||
'--target-dir=' . $target,
|
||||
'2> ' . $logfile,
|
||||
];
|
||||
|
||||
$this->retry(function () use ($args, $logfile) {
|
||||
$cmd = 'docker exec ' . $this->xtrabackupContainerId . ' ' . implode(' ', $args);
|
||||
shell_exec($cmd);
|
||||
|
||||
$stderr = shell_exec('tail -1 ' . $logfile);
|
||||
if (!str_contains($stderr, 'completed OK!')) {
|
||||
throw new Exception(' Prepare failed:' . $stderr);
|
||||
}
|
||||
}, self::RETRY_PREPARE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function restore(string $target, bool $cloud, string $datadir)
|
||||
{
|
||||
$this->log('Restore start');
|
||||
|
||||
if (!file_exists($target)) {
|
||||
throw new Exception('Error Restoring directory not found: ' . $target);
|
||||
}
|
||||
|
||||
$logfile = $target . '/../log.txt';
|
||||
|
||||
$args = [
|
||||
'xtrabackup',
|
||||
'--move-back',
|
||||
'--strict',
|
||||
'--parallel=' . $this->processors,
|
||||
'--target-dir=' . $target,
|
||||
'--datadir=' . $datadir,
|
||||
'2> ' . $logfile,
|
||||
];
|
||||
|
||||
$cmd = 'docker exec ' . $this->xtrabackupContainerId . ' ' . implode(' ', $args);
|
||||
shell_exec($cmd);
|
||||
|
||||
$stderr = shell_exec('tail -1 ' . $logfile);
|
||||
if (!str_contains($stderr, 'completed OK!')) {
|
||||
throw new Exception('Restore failed: ' . $stderr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function checkEnvVariables(): void
|
||||
{
|
||||
foreach (
|
||||
[
|
||||
'_APP_CONNECTIONS_BACKUPS_STORAGE',
|
||||
'_APP_CONNECTIONS_DB_REPLICAS',
|
||||
] as $env
|
||||
) {
|
||||
if (empty(App::getEnv($env))) {
|
||||
throw new Exception('Can\'t read ' . $env);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function log(string $message): void
|
||||
{
|
||||
if (!empty($message)) {
|
||||
Console::log(date('Y-m-d H:i:s') . ' ' . $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setContainerId()
|
||||
{
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
Console::execute('docker ps -f "name=xtrabackup" --format "{{.ID}}"', '', $stdout, $stderr);
|
||||
if (!empty($stderr)) {
|
||||
throw new Exception('Error setting container Id: ' . $stderr);
|
||||
}
|
||||
|
||||
$containerId = str_replace(PHP_EOL, '', $stdout);
|
||||
if (empty($containerId)) {
|
||||
throw new Exception('Xtrabackup Container ID not found');
|
||||
}
|
||||
|
||||
$this->xtrabackupContainerId = $containerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setProcessors()
|
||||
{
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
Console::execute('docker exec ' . $this->xtrabackupContainerId . ' nproc', '', $stdout, $stderr);
|
||||
if (!empty($stderr)) {
|
||||
throw new Exception('Error setting processors: ' . $stderr);
|
||||
}
|
||||
|
||||
$processors = str_replace(PHP_EOL, '', $stdout);
|
||||
$processors = empty($processors) ? 1 : intval($processors);
|
||||
$this->processors = $processors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function retry(callable $action, int $retries, int $sleep = 1)
|
||||
{
|
||||
try {
|
||||
return $action();
|
||||
} catch (Exception $e) {
|
||||
if ($retries > 0) {
|
||||
Console::warning('Retrying (' . $retries . ') ' . $e->getMessage());
|
||||
sleep($sleep);
|
||||
return $this->retry($action, $retries - 1, $sleep);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user