Compare commits

...
Author SHA1 Message Date
fogelito 0f1d4cdc82 Space 2023-09-12 19:33:36 +03:00
fogelito 314655ad02 tar when using lz4? 2023-09-12 19:06:33 +03:00
fogelito 1383e59d84 small update 2023-09-11 17:57:06 +03:00
fogelito 1319731790 Remove file on error 2023-09-07 18:01:53 +03:00
fogelito a7c6674a83 Upload attempts 2023-09-06 17:15:07 +03:00
fogelito faf9b42f9d Upload attempts 2023-09-06 16:08:27 +03:00
fogelito 3c7ac83ad7 Cleanups 2023-09-05 19:37:54 +03:00
fogelito 84410ce080 remove comment 2023-09-03 18:46:21 +03:00
fogelito 6fe521259b Cleanup 2023-09-03 18:29:11 +03:00
fogelito 67e4dd64e7 Cleanup 2023-09-03 18:27:31 +03:00
fogelito 0d8fbb87c8 Clean tmp dir 2023-08-20 19:01:35 +03:00
fogelito ddb847cf7b changes compression 2023-08-20 12:52:39 +03:00
fogelito f4be4eb6e2 Revert processors check 2023-08-16 17:01:13 +03:00
fogelito 5b7e8a87b8 use all $this->processors 2023-08-16 16:38:26 +03:00
fogelito 7c3daf2df3 Add restore parallel 2023-08-16 16:35:24 +03:00
fogelito 0bf709a0da Add space 2023-08-16 15:33:21 +03:00
fogelito 6d839d013f add decompress 2023-08-16 15:31:26 +03:00
fogelito a39d4073b1 add decompress 2023-08-16 15:18:32 +03:00
fogelito 9ebbc70b7d composer.lock 2023-08-16 12:26:50 +03:00
fogelito 468a561bbd Update "utopia-php/storage" tag 0.16.* 2023-08-16 12:17:06 +03:00
fogelito 46c31252ed Remove Dsn from database Restore.php 2023-08-15 15:46:30 +03:00
fogelito 67dc104a6d changes 2023-08-15 15:25:15 +03:00
fogelito fee17987b0 Restore.php 2023-08-14 20:17:48 +03:00
fogelito 90e8395578 Restore.php 2023-08-14 20:06:00 +03:00
fogelito 302e0f6341 backup stream 2023-08-14 16:15:07 +03:00
fogelito bebe3a217f Merge remote-tracking branch 'origin/db-pools-backups' into db-pools-backups 2023-08-14 12:23:22 +03:00
fogelito b3222534ed .env 2023-08-14 12:23:12 +03:00
shimon 3ccb38f9ec Merge branch 'db-pools-backups' of github.com:appwrite/appwrite into db-pools-backups 2023-08-14 10:48:29 +03:00
shimon bb3aa1a172 container id fix 2023-08-14 10:48:15 +03:00
fogelito 77deacbd40 Remove xtra line 2023-08-13 20:55:44 +03:00
shimon 9b97317bdc container id fix 2023-08-13 20:54:05 +03:00
shimon ce03231ca0 container id fix 2023-08-13 19:39:38 +03:00
shimon 43759ae273 container id fix 2023-08-13 19:18:22 +03:00
shimon ecd960a6fd container id fix 2023-08-13 18:34:26 +03:00
shimon 9da6a6f5ec container id fix 2023-08-13 18:24:31 +03:00
shimon 20bc1c4c98 container id fix 2023-08-13 17:53:41 +03:00
fogelito f787acecdb Merge remote-tracking branch 'origin/db-pools-backups' into db-pools-backups 2023-08-13 15:43:28 +03:00
fogelito f2070cac15 default value for App::getEnv 2023-08-13 15:43:16 +03:00
shimon 95841c0418 container id fix 2023-08-13 14:48:31 +03:00
shimon 08daa7cf6d container id fix 2023-08-13 14:47:26 +03:00
fogelito 47352e4062 composer.lock 2023-08-13 14:05:20 +03:00
fogelito 1488bf1d20 App::getEnv default value 2023-08-13 13:39:06 +03:00
fogelito 2aa3b1e296 nproc + setContainerId as function 2023-08-13 13:36:25 +03:00
fogelito aaeddfe780 Restore title logs 2023-08-13 12:31:44 +03:00
fogelito 66fe902152 Merge remote-tracking branch 'origin/db-pools-backups' into db-pools-backups 2023-08-13 12:11:48 +03:00
fogelito 4c3ea3c073 logs title update 2023-08-13 12:11:18 +03:00
shimon b59d6051b6 container id fix 2023-08-13 11:36:38 +03:00
shimon ab2b37fc86 container id fix 2023-08-12 08:09:35 +03:00
shimon 777340a626 container id fix 2023-08-12 08:03:22 +03:00
shimon b27a16ccaf container id fix 2023-08-11 00:45:37 +03:00
shimon 92e08d3a2d container id fix 2023-08-11 00:03:30 +03:00
shimon d6128282f5 container id fix 2023-08-10 21:32:50 +03:00
shimon 63ed3ae19c container id fix 2023-08-10 18:18:00 +03:00
shimon a294bf24fc container id fix 2023-08-10 18:15:01 +03:00
shimon 0e9c61ac86 container id fix 2023-08-10 17:54:40 +03:00
shimon 2cec0f5469 connection debug 2023-08-09 23:37:31 +03:00
shimon b2f0249146 connection debug 2023-08-09 23:24:56 +03:00
fogelito b0807da8ee check empty 2023-08-09 23:13:52 +03:00
fogelito 125e388ded Exec by id 2023-08-09 23:08:07 +03:00
fogelito d9ece4c761 CHANGES 2023-08-09 21:06:10 +03:00
shimon d3607471eb connection debug 2023-08-09 11:45:05 +03:00
shimon d1f7bd26c8 connection debug 2023-08-09 11:31:50 +03:00
shimon 7296027e43 Merge remote-tracking branch 'origin/db-pools-backups' into db-pools-backups 2023-08-09 11:18:11 +03:00
shimon 1ce00b139d connection debug 2023-08-09 11:16:17 +03:00
fogelito 2ebd500d6b .gitignore remove unused folders 2023-08-09 11:12:06 +03:00
fogelito 6b5b3ec865 _DO_SPACES keys 2023-08-09 11:10:49 +03:00
shimon 5fa1da3610 init::pools replica addition 2023-08-09 10:55:52 +03:00
shimon e5f841e353 init::pools replica addition 2023-08-09 10:14:26 +03:00
shimon 3a38b492f5 ci file ref update 2023-08-08 22:57:34 +03:00
fogelito 5f4f6cd9c5 remove chown 2023-08-08 18:12:16 +03:00
fogelito f2a337d86e varlibmysql resolve 2023-08-08 17:06:46 +03:00
fogelito 71b4c483c2 chown 2023-08-08 15:39:50 +03:00
fogelito d9569df1cc pushd popd 2023-08-08 14:53:08 +03:00
fogelito d1efcd6a61 s3 constructor 2023-08-08 12:32:24 +03:00
fogelito cea7ab8d4c Check file before download 2023-08-08 11:52:33 +03:00
fogelito cfc178b046 addressing comments part 1 2023-08-08 08:46:13 +03:00
50aba6a1c2 Update src/Appwrite/Platform/Tasks/Backup.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2023-08-07 23:52:45 +03:00
5fd5c875fc Update src/Appwrite/Platform/Tasks/Backup.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2023-08-07 23:52:14 +03:00
1ceaa599b5 Update src/Appwrite/Platform/Tasks/Backup.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2023-08-07 23:50:34 +03:00
fogelito 1138b4fd59 named volumes 2023-08-07 22:11:01 +03:00
fogelito fae5c5b92e last changes 2023-08-07 18:45:17 +03:00
fogelito 9a446ec0ec last changes 2023-08-07 18:45:02 +03:00
fogelito 675ad1d0c6 check stopped container 2023-08-07 17:58:54 +03:00
fogelito a2463e114e last changes 2023-08-07 17:53:21 +03:00
fogelito 9fab5c46f1 dsn changes 2023-08-07 15:54:09 +03:00
fogelito d730b41b5f varlibmysql 2023-08-06 21:50:05 +03:00
fogelito 66228a2462 docker-compose.yml mounts 2023-08-06 21:47:41 +03:00
fogelito fddac97835 Restore.php 2023-08-06 15:55:33 +03:00
fogelito e4f653be13 Restore.php 2023-08-02 19:10:45 +03:00
fogelito 53df65a3c4 Compress 2023-08-01 17:57:33 +03:00
fogelito 2f8b526c01 Restore.php 2023-08-01 10:53:51 +03:00
fogelito c1cf6acc7a Fix Incremental Base Bug 2023-07-31 15:22:33 +03:00
fogelito dd25899f74 Fullbackup functions 2023-07-31 14:05:51 +03:00
fogelito fc2f1cef31 Incremental Backup.php 2023-07-30 20:41:14 +03:00
fogelito b2c7a3eb33 Transfer big data 2023-07-27 14:55:25 +03:00
fogelito 55a6732444 Transfer big data 2023-07-25 18:38:26 +03:00
fogelito 970c35b31a Transfer big data 2023-07-23 19:05:35 +03:00
fogelito 38eb24d0ad Restore.php 2023-07-19 12:34:00 +03:00
fogelito 186b92d5af Restore.php + name Changes 2023-07-17 20:38:31 +03:00
fogelito 7ad08bfef2 SO spaces 2023-07-13 15:39:29 +03:00
fogelito 6f87757c53 SO spaces 2023-07-13 15:38:49 +03:00
fogelito 4f0dd56afc clean up 2023-07-12 15:27:06 +03:00
fogelito 7918ee2f20 Add extract directory 2023-07-12 15:22:06 +03:00
fogelito 748abb29cf structure 2023-07-12 14:55:09 +03:00
14 changed files with 875 additions and 108 deletions
+2
View File
@@ -27,12 +27,14 @@ _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://DO006P96DUQVNMLTB8TP:QwxTmU1JfjWsuHecNGiDDvp1UNrMhgCnk5rvJ2FIpm0@default/backups-v1?region=fra1
_APP_STORAGE_ANTIVIRUS=disabled
_APP_STORAGE_ANTIVIRUS_HOST=clamav
_APP_STORAGE_ANTIVIRUS_PORT=3310
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
with:
fetch-depth: 2
submodules: recursive
ref: feat-db-pools-master
ref: db-pools-backups
- name: Login to Docker Hub
uses: docker/login-action@v2
+1
View File
@@ -11,3 +11,4 @@ debug/
app/sdks
dev/yasd_init.php
.phpunit.result.cache
/backup-data/
+8
View File
@@ -123,6 +123,8 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/calc-tier-stats && \
chmod +x /usr/local/bin/patch-delete-project-collections && \
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 && \
@@ -159,6 +161,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" ]
+6
View File
@@ -532,6 +532,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),
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php backup $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php restore $@
+2 -2
View File
@@ -50,7 +50,7 @@
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.28.*",
"utopia-php/domains": "1.1.*",
"utopia-php/domains": "0.3.*",
"utopia-php/dsn": "0.1.*",
"utopia-php/framework": "0.26.*",
"utopia-php/image": "0.5.*",
@@ -63,7 +63,7 @@
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.5.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.13.*",
"utopia-php/storage": "dev-s3-getfiles as 0.16.0",
"utopia-php/swoole": "0.5.*",
"utopia-php/websocket": "0.1.0",
"resque/php-resque": "1.3.6",
Generated
+118 -102
View File
@@ -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": "329498d12fabaa764c4178881cb07788",
"content-hash": "2972c8c647bca05756f87f2bb39b035c",
"packages": [
{
"name": "adhocore/jwt",
@@ -408,22 +408,22 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "7.7.0",
"version": "7.8.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "fb7566caccf22d74d1ab270de3551f72a58399f5"
"reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5",
"reference": "fb7566caccf22d74d1ab270de3551f72a58399f5",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9",
"reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0",
"guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
"guzzlehttp/promises": "^1.5.3 || ^2.0.1",
"guzzlehttp/psr7": "^1.9.1 || ^2.5.1",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
@@ -514,7 +514,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.7.0"
"source": "https://github.com/guzzle/guzzle/tree/7.8.0"
},
"funding": [
{
@@ -530,20 +530,20 @@
"type": "tidelift"
}
],
"time": "2023-05-21T14:04:53+00:00"
"time": "2023-08-27T10:20:53+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "2.0.0",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6"
"reference": "111166291a0f8130081195ac4556a5587d7f1b5d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6",
"reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6",
"url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d",
"reference": "111166291a0f8130081195ac4556a5587d7f1b5d",
"shasum": ""
},
"require": {
@@ -597,7 +597,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/2.0.0"
"source": "https://github.com/guzzle/promises/tree/2.0.1"
},
"funding": [
{
@@ -613,20 +613,20 @@
"type": "tidelift"
}
],
"time": "2023-05-21T13:50:22+00:00"
"time": "2023-08-03T15:11:55+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "2.5.0",
"version": "2.6.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "b635f279edd83fc275f822a1188157ffea568ff6"
"reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6",
"reference": "b635f279edd83fc275f822a1188157ffea568ff6",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727",
"reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727",
"shasum": ""
},
"require": {
@@ -713,7 +713,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.5.0"
"source": "https://github.com/guzzle/psr7/tree/2.6.1"
},
"funding": [
{
@@ -729,7 +729,7 @@
"type": "tidelift"
}
],
"time": "2023-04-17T16:11:26+00:00"
"time": "2023-08-27T10:13:57+00:00"
},
{
"name": "influxdb/influxdb-php",
@@ -1903,16 +1903,16 @@
},
{
"name": "utopia-php/database",
"version": "0.28.0",
"version": "0.28.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "ef6506af1c09c22f5dc1e7859159d323f7fafa94"
"reference": "b9f62a9496e33aa8182c49540a649c1f36806a42"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/ef6506af1c09c22f5dc1e7859159d323f7fafa94",
"reference": "ef6506af1c09c22f5dc1e7859159d323f7fafa94",
"url": "https://api.github.com/repos/utopia-php/database/zipball/b9f62a9496e33aa8182c49540a649c1f36806a42",
"reference": "b9f62a9496e33aa8182c49540a649c1f36806a42",
"shasum": ""
},
"require": {
@@ -1951,29 +1951,31 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.28.0"
"source": "https://github.com/utopia-php/database/tree/0.28.2"
},
"time": "2022-10-31T09:58:46+00:00"
"time": "2023-08-20T16:22:59+00:00"
},
{
"name": "utopia-php/domains",
"version": "v1.1.0",
"version": "0.3.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/domains.git",
"reference": "1665e1d9932afa3be63b5c1e0dcfe01fe77d8e73"
"reference": "aaa8c9a96c69ccb397997b1f4f2299c66f77eefb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/domains/zipball/1665e1d9932afa3be63b5c1e0dcfe01fe77d8e73",
"reference": "1665e1d9932afa3be63b5c1e0dcfe01fe77d8e73",
"url": "https://api.github.com/repos/utopia-php/domains/zipball/aaa8c9a96c69ccb397997b1f4f2299c66f77eefb",
"reference": "aaa8c9a96c69ccb397997b1f4f2299c66f77eefb",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=8.0",
"utopia-php/framework": "0.*.*"
},
"require-dev": {
"phpunit/phpunit": "^7.0"
"laravel/pint": "1.2.*",
"phpunit/phpunit": "^9.3"
},
"type": "library",
"autoload": {
@@ -1989,6 +1991,10 @@
{
"name": "Eldad Fux",
"email": "eldad@appwrite.io"
},
{
"name": "Wess Cope",
"email": "wess@appwrite.io"
}
],
"description": "Utopia Domains library is simple and lite library for parsing web domains. This library is aiming to be as simple and easy to learn and use.",
@@ -2005,9 +2011,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/domains/issues",
"source": "https://github.com/utopia-php/domains/tree/master"
"source": "https://github.com/utopia-php/domains/tree/0.3.2"
},
"time": "2020-02-23T07:40:02+00:00"
"time": "2023-07-19T16:39:24+00:00"
},
{
"name": "utopia-php/dsn",
@@ -2622,26 +2628,32 @@
},
{
"name": "utopia-php/storage",
"version": "0.13.2",
"version": "dev-s3-getfiles",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
"reference": "ad1c00f24ca56e73888acc2af3deee4919b1194b"
"reference": "76e440f1374bf23bb82f28701786af1a5a3b2d05"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/ad1c00f24ca56e73888acc2af3deee4919b1194b",
"reference": "ad1c00f24ca56e73888acc2af3deee4919b1194b",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/76e440f1374bf23bb82f28701786af1a5a3b2d05",
"reference": "76e440f1374bf23bb82f28701786af1a5a3b2d05",
"shasum": ""
},
"require": {
"ext-brotli": "*",
"ext-fileinfo": "*",
"ext-lz4": "*",
"ext-snappy": "*",
"ext-xz": "*",
"ext-zlib": "*",
"ext-zstd": "*",
"php": ">=8.0",
"utopia-php/framework": "0.*.*"
"utopia-php/framework": "0.*.*",
"utopia-php/system": "0.*.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
"phpunit/phpunit": "^9.3",
"vimeo/psalm": "4.0.1"
},
@@ -2655,12 +2667,6 @@
"license": [
"MIT"
],
"authors": [
{
"name": "Eldad Fux",
"email": "eldad@appwrite.io"
}
],
"description": "A simple Storage library to manage application storage",
"keywords": [
"framework",
@@ -2671,9 +2677,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
"source": "https://github.com/utopia-php/storage/tree/0.13.2"
"source": "https://github.com/utopia-php/storage/tree/s3-getfiles"
},
"time": "2022-12-20T11:11:35+00:00"
"time": "2023-09-05T08:27:10+00:00"
},
{
"name": "utopia-php/swoole",
@@ -3253,16 +3259,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.15.5",
"version": "v4.17.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e"
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"shasum": ""
},
"require": {
@@ -3303,9 +3309,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1"
},
"time": "2023-05-19T20:20:00+00:00"
"time": "2023-08-13T19:53:39+00:00"
},
{
"name": "phar-io/manifest",
@@ -3530,16 +3536,16 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.7.2",
"version": "1.7.3",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "b2fe4d22a5426f38e014855322200b97b5362c0d"
"reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b2fe4d22a5426f38e014855322200b97b5362c0d",
"reference": "b2fe4d22a5426f38e014855322200b97b5362c0d",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419",
"reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419",
"shasum": ""
},
"require": {
@@ -3582,9 +3588,9 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.2"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.3"
},
"time": "2023-05-30T18:13:47+00:00"
"time": "2023-08-12T11:01:26+00:00"
},
{
"name": "phpspec/prophecy",
@@ -3656,16 +3662,16 @@
},
{
"name": "phpstan/phpdoc-parser",
"version": "1.22.0",
"version": "1.24.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c"
"reference": "3510b0a6274cc42f7219367cb3abfc123ffa09d6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ec58baf7b3c7f1c81b3b00617c953249fb8cf30c",
"reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/3510b0a6274cc42f7219367cb3abfc123ffa09d6",
"reference": "3510b0a6274cc42f7219367cb3abfc123ffa09d6",
"shasum": ""
},
"require": {
@@ -3697,22 +3703,22 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.22.0"
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.0"
},
"time": "2023-06-01T12:35:21+00:00"
"time": "2023-09-07T20:46:32+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.26",
"version": "9.2.27",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1"
"reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
"reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1",
"reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1",
"shasum": ""
},
"require": {
@@ -3768,7 +3774,8 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26"
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27"
},
"funding": [
{
@@ -3776,7 +3783,7 @@
"type": "github"
}
],
"time": "2023-03-06T12:58:08+00:00"
"time": "2023-07-26T13:44:30+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -4628,16 +4635,16 @@
},
{
"name": "sebastian/global-state",
"version": "5.0.5",
"version": "5.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2"
"reference": "bde739e7565280bda77be70044ac1047bc007e34"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2",
"reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34",
"reference": "bde739e7565280bda77be70044ac1047bc007e34",
"shasum": ""
},
"require": {
@@ -4680,7 +4687,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
"source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5"
"source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6"
},
"funding": [
{
@@ -4688,7 +4695,7 @@
"type": "github"
}
],
"time": "2022-02-14T08:28:10+00:00"
"time": "2023-08-02T09:26:13+00:00"
},
{
"name": "sebastian/lines-of-code",
@@ -5187,16 +5194,16 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.27.0",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"shasum": ""
},
"require": {
@@ -5211,7 +5218,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -5249,7 +5256,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
},
"funding": [
{
@@ -5265,20 +5272,20 @@
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
"time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.27.0",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
"reference": "42292d99c55abe617799667f454222c54c60e229"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
"reference": "42292d99c55abe617799667f454222c54c60e229",
"shasum": ""
},
"require": {
@@ -5293,7 +5300,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -5332,7 +5339,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
},
"funding": [
{
@@ -5348,7 +5355,7 @@
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
"time": "2023-07-28T09:04:16+00:00"
},
{
"name": "textalk/websocket",
@@ -5451,16 +5458,16 @@
},
{
"name": "twig/twig",
"version": "v3.6.1",
"version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd"
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd",
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
"shasum": ""
},
"require": {
@@ -5470,7 +5477,7 @@
},
"require-dev": {
"psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
"symfony/phpunit-bridge": "^5.4.9|^6.3"
},
"type": "library",
"autoload": {
@@ -5506,7 +5513,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.6.1"
"source": "https://github.com/twigphp/Twig/tree/v3.7.1"
},
"funding": [
{
@@ -5518,12 +5525,21 @@
"type": "tidelift"
}
],
"time": "2023-06-08T12:52:13+00:00"
"time": "2023-08-28T11:09:02+00:00"
}
],
"aliases": [
{
"package": "utopia-php/storage",
"version": "dev-s3-getfiles",
"alias": "0.16.0",
"alias_normalized": "0.16.0.0"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {
"utopia-php/storage": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@@ -5547,5 +5563,5 @@
"platform-overrides": {
"php": "8.0"
},
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.2.0"
}
+53 -1
View File
@@ -126,6 +126,7 @@ services:
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_CONNECTIONS_DB_PROJECT
- _APP_CONNECTIONS_DB_REPLICAS
- _APP_CONNECTIONS_DB_CONSOLE
- _APP_CONNECTIONS_CACHE
- _APP_CONNECTIONS_QUEUE
@@ -721,7 +722,7 @@ services:
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
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:
@@ -748,6 +749,56 @@ 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:
entrypoint: db-backup
command:
- --database=db_fra1_03
<<: *x-logging
container_name: appwrite-backup
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
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
@@ -910,4 +961,5 @@ volumes:
appwrite-config:
appwrite-functions:
appwrite-builds:
#backup-data:
# appwrite-chronograf:
+4
View File
@@ -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;
@@ -48,6 +50,8 @@ class Tasks extends Service
->addAction(CalcUsersStats::getName(), new CalcUsersStats())
->addAction(CalcTierStats::getName(), new CalcTierStats())
->addAction(PatchDeleteProjectCollections::getName(), new PatchDeleteProjectCollections())
->addAction(Backup::getName(), new Backup())
->addAction(Restore::getName(), new Restore())
;
}
}
+388
View File
@@ -0,0 +1,388 @@
<?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 * 7; // 2 days?
public const CLEANUP_CLOUD_FILES_SECONDS = 60 * 60 * 24 * 14; // 14 days?;
public const UPLOAD_CHUNK_SIZE = 5 * 1024 * 1024; // Must be greater than 5MB;
protected string $filename;
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_01')
->inject('pools')
->callback(fn(string $database, Group $pools) => $this->action($database, $pools));
}
public static function getName(): string
{
return 'backup';
}
/**
* @throws Exception
*/
public function action(string $database, Group $pools): void
{
$this->checkEnvVariables();
$this->database = $database;
$this->dsn = $this->getDsn($database);
if (is_null($this->dsn)) {
throw new Exception('No DSN match');
}
$dsn = new DSN(App::getEnv('_APP_CONNECTIONS_BACKUPS_STORAGE', ''));
$this->s3 = new DOSpaces('/' . $database . '/full', $dsn->getUser(), $dsn->getPassword(), $dsn->getPath(), $dsn->getParam('region'));
$attempts = 0;
$max = 10;
$sleep = 5;
do {
try {
$attempts++;
$pools
->get('replica_' . $database)
->pop()
->getResource();
break; // leave the do-while if successful
} catch (Exception $e) {
Console::warning("Database not ready. Retrying connection ({$attempts})...");
if ($attempts >= $max) {
throw new Exception('Failed to connect to database: ' . $e->getMessage());
}
sleep($sleep);
}
} while ($attempts < $max);
$this->setContainerId();
$this->setProcessors();
Console::loop(function () {
try {
$this->start();
} catch (Exception $e) {
//todo: send alerts to admin!
// todo: Do we want to terminate the script? or wait for next backup iteration?
Console::error($e->getMessage());
}
}, self::BACKUP_INTERVAL_SECONDS);
}
/**
* @throws Exception
*/
public function start(): void
{
$start = microtime(true);
$this->filename = date('Y_m_d_H_i_s') . '.xbstream';
//$this->filename = '2023_09_06_08_19_24.xbstream';
self::log('--- Backup Start ' . $this->filename . ' --- ');
$local = new Local(self::BACKUPS_PATH . '/' . $this->database . '/full');
$local->setTransferChunkSize(self::UPLOAD_CHUNK_SIZE);
$this->fullBackup($local);
$max = 1;
$i = 0;
while (true) {
$i++;
try {
$this->upload($local);
break;
} catch (Exception $e) {
if ($i <= $max) {
Console::warning($e->getMessage() . ' (' . $i . ' attempt)');
} else {
throw new Exception($e->getMessage());
}
}
}
$this->cleanLocalFiles($local);
$this->cleanCloudFiles();
self::log('--- Backup Finish ' . (microtime(true) - $start) . ' seconds --- ' . PHP_EOL . PHP_EOL);
}
/**
* @throws Exception
*/
public function fullBackup(Device $device)
{
self::log('Backup start');
$target = $device->getRoot();
$log = $device->getPath($this->filename . '.log');
$file = $device->getPath($this->filename);
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);
}
$args = [
'xtrabackup',
'--user=' . $this->dsn->getUser(),
'--password=' . $this->dsn->getPassword(),
'--host=' . $this->dsn->getHost(),
'--port=' . $this->dsn->getPort(),
'--backup',
'--stream=xbstream',
'--strict',
'--history="' . $this->database . '|' . pathinfo($this->filename, PATHINFO_FILENAME) . '"', // PERCONA_SCHEMA.xtrabackup_history
'--slave-info',
'--safe-slave-backup', // https://docs.percona.com/percona-xtrabackup/8.0/make-backup-in-replication-env.html
'--safe-slave-backup-timeout=300',
'--check-privileges', // checks if Percona XtraBackup has all the required privileges.
'--target-dir=./',
//'--compress=' . self::COMPRESS_ALGORITHM,
'--compress=lz4',
'--compress-threads=' . $this->processors,
'--parallel=' . $this->processors,
'> ' . $file,
'2> ' . $log,
];
$cmd = 'docker exec ' . $this->xtrabackupContainerId . ' ' . implode(' ', $args);
Console::success($cmd);
shell_exec($cmd);
$stderr = shell_exec('tail -1 ' . $log);
if (!str_contains($stderr, 'completed OK!')) {
unlink($file);
throw new Exception('Backup failed: ' . $stderr);
}
if (!unlink($log)) {
throw new Exception('Error deleting: ' . $log);
}
// tar if using lz4...
// $tar = basename($this->filename, '.xbstream') . '.tar.gz';
// $cmd = 'cd ' . $target . ' && tar zcf ' . $tar . ' ' . $this->filename . ' && cd ' . getcwd();
// console::success($cmd);
// shell_exec($cmd);
}
/**
* @throws Exception
*/
public function upload(Device $local)
{
self::log('Upload start');
$file = $local->getPath($this->filename);
if (!$this->s3->exists('/')) {
throw new Exception('Can\'t read s3 root directory');
}
$destination = $this->s3->getRoot() . '/' . $this->filename;
if (!$local->transfer($file, $destination, $this->s3)) {
throw new Exception('Error uploading to ' . $destination);
}
if (!$this->s3->exists($destination)) {
throw new Exception('File not found in destination: ' . $destination);
}
}
public function cleanLocalFiles(Device $local)
{
self::log('cleanLocalFiles start');
$folder = scandir($local->getRoot());
if ($folder !== false) {
foreach ($folder as $item) {
if ($this->isDelete($item, self::CLEANUP_LOCAL_FILES_SECONDS)) {
if (str_ends_with($item, '.xbstream.log')) {
$delete = true;
} else {
// Check if file exist on cloud before delete
if ($this->s3->exists($this->s3->getRoot() . '/' . $item)) {
$delete = true;
} else {
if ($this->isDelete($item, self::CLEANUP_CLOUD_FILES_SECONDS)) {
$delete = true;
} else {
Console::warning('Skipping delete not found on cloud: ' . $local->getPath($item) . ' ');
$delete = false;
}
}
}
if ($delete === true) {
if (unlink($local->getPath($item))) {
Console::success($local->getPath($item) . ' Deleted!');
}
}
}
}
}
}
/**
* @throws Exception
*/
public function cleanCloudFiles(): void
{
self::log('cleanCloudFiles start');
$files = $this->s3->getFiles($this->s3->getRoot());
if ($files['KeyCount'] > 0) {
foreach ($files['Contents'] as $file) {
if ($this->isDelete(basename($file['Key']), self::CLEANUP_CLOUD_FILES_SECONDS)) {
if ($this->s3->delete($file['Key'])) {
Console::success($file['Key'] . ' Deleted!');
}
}
}
}
}
/**
* @param string $item
* @param int $seconds
* @return bool
*/
public function isDelete(string $item, int $seconds): bool
{
if (str_ends_with($item, '.xbstream')) {
$item = substr($item, 0, -9);
} elseif (str_ends_with($item, '.xbstream.log')) {
$item = substr($item, 0, -13);
} else {
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;
}
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 = intval($processors);
if ($processors === 0) {
throw new Exception('Set Processors Error');
}
$this->processors = $processors;
}
}
+285
View File
@@ -0,0 +1,285 @@
<?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';
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')
->param('cloud', null, new Boolean(true), 'Download backup from cloud or use local directory')
->param('database', null, new Text(10), '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
{
$this->checkEnvVariables();
$this->setContainerId();
$this->setProcessors();
$datadir = self::DATADIR;
try {
$dsn = new DSN(App::getEnv('_APP_CONNECTIONS_BACKUPS_STORAGE', ''));
$this->s3 = new DOSpaces('/' . $database . '/full', $dsn->getUser(), $dsn->getPassword(), $dsn->getPath(), $dsn->getParam('region'));
$this->s3->setTransferChunkSize(20 * 1024 * 1024); // 5MB is the minimum
} catch (\Exception $e) {
Console::error($e->getMessage() . 'Invalid DSN.');
Console::exit();
}
if (!file_exists($datadir)) {
Console::error('Datadir not found: ' . $datadir);
Console::exit();
}
if (file_exists($datadir . '/sys') || file_exists($datadir . '/appwrite')) {
Console::error('Datadir ' . $datadir . ' must be empty!');
Console::exit();
}
$this->log('--- Restore Start ' . $id . ' --- ');
$start = microtime(true);
$filename = $id . '.xbstream';
$cloud = $cloud === 'true' || $cloud === '1';
$destination = new Local(self::BACKUPS_PATH . '/restore/' . $id);
if (!file_exists($destination->getRoot()) && !mkdir($destination->getRoot(), 0755, true)) {
Console::error('Error creating directory: ' . $destination->getRoot());
Console::exit();
}
if ($cloud) {
$stream = $destination->getPath($filename);
if (!file_exists($stream)) {
$this->download($stream, $destination);
}
} else {
$stream = self::BACKUPS_PATH . '/' . $database . '/full/' . $filename;
}
if (!file_exists($stream)) {
Console::error('File not found: ' . $stream);
Console::exit();
}
$files = $destination->getRoot() . '/files';
if (file_exists($files)) {
Console::error('Directory exist: ' . $files);
Console::exit();
}
if (!file_exists($files) && !mkdir($files, 0755, true)) {
Console::error('Error creating directory: ' . $files);
Console::exit();
}
$this->decompress($stream, $files);
$this->prepare($files);
$this->restore($files, $datadir);
$this->log('Restore Finish in ' . (microtime(true) - $start) . ' seconds');
}
public function download(string $file, Device $local)
{
$filename = basename($file);
$tmp = $local->getRoot() . '/tmp_' . $filename;
if (file_exists($tmp)) { // todo: this should be taken care of in the lib, cleanup previous iteration
Console::error('Deleting: ' . $tmp);
$local->delete($tmp, true);
}
$this->log('Download start');
$path = $this->s3->getPath($filename);
if (!$this->s3->exists($path)) {
Console::error('File: ' . $path . ' does not exist on cloud');
Console::exit();
}
try {
if (!$this->s3->transfer($path, $file, $local)) {
throw new Exception('Transfer failed ' . $file);
}
} catch (Exception $e) {
Console::error($e->getMessage());
Console::exit();
}
}
public function decompress(string $file, string $target)
{
$this->log('Xbstream decompress start');
$args = [
'xbstream -x < ' . $file,
'--decompress',
'--decompress-threads=' . intval($this->processors / 2),
'--parallel=' . $this->processors,
'-C ' . $target,
];
$stdout = '';
$stderr = '';
$cmd = 'docker exec -i ' . $this->xtrabackupContainerId . ' ' . implode(' ', $args);
Console::execute($cmd, '', $stdout, $stderr);
if (!empty($stderr)) {
Console::error('Xbstream Error: ' . $stderr);
Console::exit();
}
}
public function prepare(string $target)
{
$this->log('Prepare start');
if (!file_exists($target)) {
Console::error('prepare error directory not found: ' . $target);
Console::exit();
}
$logfile = $target . '/../prepare.txt';
$args = [
'xtrabackup',
'--prepare',
'--parallel=' . $this->processors,
'--strict',
'--target-dir=' . $target,
'2> ' . $logfile,
];
$cmd = 'docker exec ' . $this->xtrabackupContainerId . ' ' . implode(' ', $args);
shell_exec($cmd);
$stderr = shell_exec('tail -1 ' . $logfile);
if (!str_contains($stderr, 'completed OK!')) {
Console::error('Prepare error: ' . $stderr);
Console::exit();
}
}
public function restore(string $target, string $datadir)
{
$this->log('Restore start');
if (!file_exists($target)) {
Console::error('restore error directory not found: ' . $target);
Console::exit();
}
$logfile = $target . '/../restore.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!')) {
Console::error(date('Y-m-d H:i:s') . ' Restore failed: ' . $stderr);
Console::exit();
}
}
public function checkEnvVariables(): void
{
foreach (
[
'_APP_CONNECTIONS_BACKUPS_STORAGE',
'_APP_CONNECTIONS_DB_REPLICAS',
] as $env
) {
if (empty(App::getEnv($env))) {
Console::error('Can\'t read ' . $env);
Console::exit();
}
}
}
public function log(string $message): void
{
if (!empty($message)) {
Console::log(date('Y-m-d H:i:s') . ' ' . $message);
}
}
public function setContainerId()
{
$stdout = '';
$stderr = '';
Console::execute('docker ps -f "name=xtrabackup" --format "{{.ID}}"', '', $stdout, $stderr);
if (!empty($stderr)) {
Console::error('Error setting container Id: ' . $stderr);
Console::exit();
}
$containerId = str_replace(PHP_EOL, '', $stdout);
if (empty($containerId)) {
Console::error('Xtrabackup Container ID not found');
Console::exit();
}
$this->xtrabackupContainerId = $containerId;
}
public function setProcessors()
{
$stdout = '';
$stderr = '';
Console::execute('docker exec ' . $this->xtrabackupContainerId . ' nproc', '', $stdout, $stderr);
if (!empty($stderr)) {
Console::error('Error setting processors: ' . $stderr);
Console::exit();
}
$processors = str_replace(PHP_EOL, '', $stdout);
$processors = intval($processors);
if ($processors === 0) {
Console::error('Set Processors Error');
Console::exit();
}
$this->processors = $processors;
}
}
@@ -128,8 +128,7 @@ trait DatabasesBase
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertTrue($response['body']['enabled']);
}
/**
* @depends testCreateCollection
*/