mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e3ebc15a0 | ||
|
|
6840f73a83 | ||
|
|
543e57d0bf | ||
|
|
a7b074449f | ||
|
|
1092450d97 |
+6
-1
@@ -31,6 +31,8 @@ RUN npm run build
|
||||
|
||||
FROM appwrite/base:0.1.0 as final
|
||||
|
||||
RUN apk add mysql-client
|
||||
|
||||
LABEL maintainer="team@appwrite.io"
|
||||
|
||||
ARG VERSION=dev
|
||||
@@ -147,12 +149,14 @@ RUN mkdir -p /storage/uploads && \
|
||||
mkdir -p /storage/certificates && \
|
||||
mkdir -p /storage/functions && \
|
||||
mkdir -p /storage/debug && \
|
||||
mkdir -p /storage/backups && \
|
||||
chown -Rf www-data.www-data /storage/uploads && chmod -Rf 0755 /storage/uploads && \
|
||||
chown -Rf www-data.www-data /storage/cache && chmod -Rf 0755 /storage/cache && \
|
||||
chown -Rf www-data.www-data /storage/config && chmod -Rf 0755 /storage/config && \
|
||||
chown -Rf www-data.www-data /storage/certificates && chmod -Rf 0755 /storage/certificates && \
|
||||
chown -Rf www-data.www-data /storage/functions && chmod -Rf 0755 /storage/functions && \
|
||||
chown -Rf www-data.www-data /storage/debug && chmod -Rf 0755 /storage/debug
|
||||
chown -Rf www-data.www-data /storage/debug && chmod -Rf 0755 /storage/debug && \
|
||||
chown -Rf www-data.www-data /storage/backups && chmod -Rf 0755 /storage/backups
|
||||
|
||||
# Executables
|
||||
RUN chmod +x /usr/local/bin/doctor && \
|
||||
@@ -169,6 +173,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/test && \
|
||||
chmod +x /usr/local/bin/vars && \
|
||||
chmod +x /usr/local/bin/worker-audits && \
|
||||
chmod +x /usr/local/bin/worker-backups && \
|
||||
chmod +x /usr/local/bin/worker-certificates && \
|
||||
chmod +x /usr/local/bin/worker-databases && \
|
||||
chmod +x /usr/local/bin/worker-deletes && \
|
||||
|
||||
@@ -1185,6 +1185,89 @@ $collections = [
|
||||
],
|
||||
],
|
||||
|
||||
'backups' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('backups'),
|
||||
'name' => 'backups',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('projectInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('projectId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('description'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 1024,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('path'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 512,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('status'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => 'processing',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_project'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
'users' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('users'),
|
||||
|
||||
@@ -185,7 +185,7 @@ return [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Console',
|
||||
'version' => '0.0.1',
|
||||
'version' => '0.0.3',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-console',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
@@ -197,8 +197,8 @@ return [
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-console.git',
|
||||
'gitBranch' => 'main',
|
||||
'gitRepoName' => 'sdk-for-console',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitRepoName' => 'christyjacob4-appwrite-console',
|
||||
'gitUserName' => '',
|
||||
],
|
||||
[
|
||||
'key' => 'cli',
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
Submodule app/console updated: 5e2a40c1e3...a0dfd89382
@@ -4,7 +4,8 @@ use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Validator\Event;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Validator\Event as EventValidator;
|
||||
use Appwrite\Network\Validator\CNAME;
|
||||
use Utopia\Validator\Domain as DomainValidator;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
@@ -659,7 +660,7 @@ App::post('/v1/projects/:projectId/webhooks')
|
||||
->label('sdk.response.model', Response::MODEL_WEBHOOK)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
|
||||
->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('events', null, new ArrayList(new EventValidator(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('url', null, new URL(['http', 'https']), 'Webhook URL.')
|
||||
->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
|
||||
->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
|
||||
@@ -782,7 +783,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('webhookId', '', new UID(), 'Webhook unique ID.')
|
||||
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
|
||||
->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('events', null, new ArrayList(new EventValidator(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('url', null, new URL(['http', 'https']), 'Webhook URL.')
|
||||
->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
|
||||
->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
|
||||
@@ -1538,3 +1539,292 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/projects/:projectId/backups')
|
||||
->desc('Create Backup')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'createBackup')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BACKUP)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('name', '', new Text(256), 'Backup name.')
|
||||
->param('description', '', new Text(1024), 'Backup description.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $name, string $description, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$backup = $dbForConsole->createDocument('backups', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'status' => 'pending',
|
||||
]));
|
||||
|
||||
$event = new Event(Event::BACKUPS_QUEUE_NAME, Event::BACKUPS_CLASS_NAME);
|
||||
$event
|
||||
->setProject($project)
|
||||
->setPayload([
|
||||
'type' => 'backup',
|
||||
'backupId' => $backup->getId(),
|
||||
])
|
||||
->trigger();
|
||||
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($backup, Response::MODEL_BACKUP);
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/backups')
|
||||
->desc('List Backups')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'listBackups')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BACKUP_LIST)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$backups = $dbForConsole->find('backups', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::limit(5000)
|
||||
]);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'backups' => $backups,
|
||||
'total' => count($backups),
|
||||
]), Response::MODEL_BACKUP_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/backups/:backupId')
|
||||
->desc('Get Backup')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'getBackup')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BACKUP)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('backupId', '', new UID(), 'Backup unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $backupId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$backup = $dbForConsole->findOne('backups', [
|
||||
Query::equal('$id', [$backupId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($backup === false || $backup->isEmpty()) {
|
||||
throw new Exception(Exception::BACKUP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($backup, Response::MODEL_BACKUP);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId/backups/:backupId')
|
||||
->desc('Delete Backup')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'deleteBackup')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('backupId', '', new UID(), 'Backup unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $backupId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$backup = $dbForConsole->findOne('backups', [
|
||||
Query::equal('$id', [$backupId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($backup === false || $backup->isEmpty()) {
|
||||
throw new Exception(Exception::BACKUP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$dbForConsole->deleteDocument('backups', $backupId)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to delete backup from DB.');
|
||||
}
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/backups/:backupId')
|
||||
->desc('Update Backup')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateBackup')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BACKUP)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('backupId', '', new UID(), 'Backup unique ID.')
|
||||
->param('name', null, new Text(256), 'Backup name.', true)
|
||||
->param('description', null, new Text(1024), 'Backup description.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $backupId, ?string $name, ?string $description, ?string $schedule, ?int $keep, ?array $webhooks, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$backup = $dbForConsole->findOne('backups', [
|
||||
Query::equal('$id', [$backupId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($backup === false || $backup->isEmpty()) {
|
||||
throw new Exception(Exception::BACKUP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$backup
|
||||
->setAttribute('name', $name ?? $backup->getAttribute('name'))
|
||||
->setAttribute('description', $description ?? $backup->getAttribute('description'));
|
||||
|
||||
if(!$dbForConsole->updateDocument('backups', $backup->getId(), $backup)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to update backup in DB.');
|
||||
}
|
||||
|
||||
$response->dynamic($backup, Response::MODEL_BACKUP);
|
||||
});
|
||||
|
||||
App::post('/v1/projects/:projectId/backups/:backupId/restore')
|
||||
->desc('Restore Backup')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'restoreBackup')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('backupId', '', new UID(), 'Backup unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $backupId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$backup = $dbForConsole->findOne('backups', [
|
||||
Query::equal('$id', [$backupId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($backup === false || $backup->isEmpty()) {
|
||||
throw new Exception(Exception::BACKUP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$event = new Event(Event::BACKUPS_QUEUE_NAME, Event::BACKUPS_CLASS_NAME);
|
||||
$event
|
||||
->setProject($project)
|
||||
->setPayload([
|
||||
'type' => 'restore',
|
||||
'backupId' => $backup->getId(),
|
||||
])
|
||||
->trigger();
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED)->noContent();
|
||||
});
|
||||
|
||||
// App::get('/v1/projects/:projectId/backups/:backupId/export')
|
||||
// ->desc('Export Backup')
|
||||
// ->groups(['api', 'projects'])
|
||||
// ->label('scope', 'projects.write')
|
||||
// ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
// ->label('sdk.namespace', 'projects')
|
||||
// ->label('sdk.method', 'exportBackup')
|
||||
// ->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
// ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
// ->label('sdk.response.model', Response::MODEL_BACKUP)
|
||||
// ->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
// ->param('backupId', '', new UID(), 'Backup unique ID.')
|
||||
// ->inject('response')
|
||||
// ->inject('dbForConsole')
|
||||
// ->action(function (string $projectId, string $backupId, Response $response, Database $dbForConsole) {
|
||||
|
||||
// $project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
// if ($project->isEmpty()) {
|
||||
// throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
// }
|
||||
|
||||
// $backup = $dbForConsole->findOne('backups', [
|
||||
// Query::equal('_uid', [$backupId]),
|
||||
// Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
// ]);
|
||||
|
||||
// if ($backup === false || $backup->isEmpty()) {
|
||||
// throw new Exception(Exception::BACKUP_NOT_FOUND);
|
||||
// }
|
||||
|
||||
// $backupCollection = $dbForConsole->getCollection('backup_' . $backup->getId());
|
||||
|
||||
// $collections = $backupCollection->listCollections();
|
||||
|
||||
// $export = [];
|
||||
|
||||
// foreach ($collections as $collection) {
|
||||
// $documents = $collection->find();
|
||||
|
||||
// foreach ($documents as $document) {
|
||||
// $export[$collection->getId()][] = $document->getArrayCopy();
|
||||
// }
|
||||
// }
|
||||
|
||||
// $response->json($export);
|
||||
// });
|
||||
@@ -114,6 +114,7 @@ const APP_STORAGE_FUNCTIONS = '/storage/functions';
|
||||
const APP_STORAGE_BUILDS = '/storage/builds';
|
||||
const APP_STORAGE_CACHE = '/storage/cache';
|
||||
const APP_STORAGE_CERTIFICATES = '/storage/certificates';
|
||||
const APP_STORAGE_BACKUPS = '/storage/backups';
|
||||
const APP_STORAGE_CONFIG = '/storage/config';
|
||||
const APP_STORAGE_READ_BUFFER = 20 * (1000 * 1000); //20MB other names `APP_STORAGE_MEMORY_LIMIT`, `APP_STORAGE_MEMORY_BUFFER`, `APP_STORAGE_READ_LIMIT`, `APP_STORAGE_BUFFER_LIMIT`
|
||||
const APP_SOCIAL_TWITTER = 'https://twitter.com/appwrite';
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Resque\Worker;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Storage\Device\Local;
|
||||
|
||||
/**
|
||||
* Todo - allow backups of specific tables
|
||||
* Todo - delete temporary files generated during backup
|
||||
* Todo - Backup certificates ?
|
||||
* Todo - Backup Functions
|
||||
* Todo - How to backup files?
|
||||
*/
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
Console::title('Backups V1 Worker');
|
||||
Console::success(APP_NAME . ' backups worker v1 has started');
|
||||
|
||||
class BackupsV1 extends Worker
|
||||
{
|
||||
public function getName(): string
|
||||
{
|
||||
return "backups";
|
||||
}
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$project = $this->args['project'];
|
||||
$type = $this->args['payload']['type'];
|
||||
$backupId = $this->args['payload']['backupId'];
|
||||
|
||||
$project = new Document($project);
|
||||
|
||||
/** Create a database dump of all the tables in this project database */
|
||||
switch ($type) {
|
||||
case 'backup':
|
||||
$this->backup($project, $backupId);
|
||||
break;
|
||||
case 'restore':
|
||||
$this->restore($project, $backupId);
|
||||
break;
|
||||
default:
|
||||
Console::error('Unknown backup type: ' . $type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function backup(Document $project, string $backupId)
|
||||
{
|
||||
try {
|
||||
$dbForConsole = $this->getConsoleDB();
|
||||
|
||||
/** Update the backup state */
|
||||
$backup = $dbForConsole->getDocument('backups', $backupId);
|
||||
if ($backup->isEmpty()) {
|
||||
Console::error('Backup not found: ' . $backupId);
|
||||
return;
|
||||
}
|
||||
|
||||
$backup->setAttribute('status', 'processing');
|
||||
$dbForConsole->updateDocument('backups', $backupId, $backup);
|
||||
|
||||
/** Create list of tables to backup */
|
||||
$dbForProject = $this->getProjectDB($project->getId(), $project);
|
||||
$tables = $dbForProject->listCollections(1000);
|
||||
$tablesToBackup = [];
|
||||
foreach ($tables as $table) {
|
||||
$tablesToBackup[] = $dbForProject->getNamespace() . '_' .$table->getId();
|
||||
$tablesToBackup[] = $dbForProject->getNamespace() . '_' .$table->getId() . '_perms';
|
||||
}
|
||||
|
||||
/** Backup the metadata tables */
|
||||
$tablesToBackup[] = $dbForProject->getNamespace() . '__metadata';
|
||||
$tablesToBackup[] = $dbForProject->getNamespace() . '__metadata_perms';
|
||||
|
||||
/**
|
||||
* Create Temporary Backup File
|
||||
*/
|
||||
$sqldumpFilename = $backupId . '.sql';
|
||||
$compressedFilename = $backupId . '.tar.gz';
|
||||
$tmpSqlDump = "/tmp/backups/$backupId/$sqldumpFilename";
|
||||
$tmpBackup = "/tmp/backups/$backupId/$compressedFilename";
|
||||
if (!\file_exists(\dirname($tmpBackup))) {
|
||||
if (!@\mkdir(\dirname($tmpBackup), 0755, true)) {
|
||||
throw new Exception("Failed to create temporary directory", 500);
|
||||
}
|
||||
}
|
||||
|
||||
$deviceBackups = $this->getBackupsDevice($project->getId());
|
||||
$path = $deviceBackups->getPath($compressedFilename);
|
||||
|
||||
$command = 'mysqldump -alv -h' . App::getEnv('_APP_DB_HOST') . ' -u' . App::getEnv('_APP_DB_USER') . ' -p' . App::getEnv('_APP_DB_PASS') . ' ' . App::getEnv('_APP_DB_SCHEMA') . ' ' . implode(' ', $tablesToBackup) . ' > ' . $tmpSqlDump;
|
||||
|
||||
Console::info('Running command: ' . $command);
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$stdin = '';
|
||||
$return = Console::execute($command, $stdin, $stdout, $stderr);
|
||||
|
||||
if ($return !== 0) {
|
||||
throw new Exception('Failed to create backup: ' . $backupId);
|
||||
}
|
||||
|
||||
/** Compress the SQL dump file */
|
||||
$command = 'tar -czf ' . $tmpBackup . ' -C /tmp/backups/' . $backupId . ' ' . $sqldumpFilename;
|
||||
Console::info('Running command: ' . $command);
|
||||
$return = Console::execute($command, $stdin, $stdout, $stderr);
|
||||
if ($return !== 0) {
|
||||
throw new Exception('Failed to compress backup: ' . $backupId);
|
||||
}
|
||||
|
||||
/** Zip the Backup file and move it to the path */
|
||||
$return = $deviceBackups->move($tmpBackup, $path);
|
||||
if ($return === false) {
|
||||
throw new Exception('Failed to move backup: ' . $backupId);
|
||||
}
|
||||
|
||||
$backup->setAttribute('path', $path);
|
||||
$backup->setAttribute('status', 'completed');
|
||||
$dbForConsole->updateDocument('backups', $backup->getId(), $backup);
|
||||
|
||||
} catch (Exception $e) {
|
||||
Console::error($e->getMessage());
|
||||
$backup->setAttribute('status', 'failed');
|
||||
$dbForConsole->updateDocument('backups', $backup->getId(), $backup);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function restore(Document $project, string $backupId) {
|
||||
/** Write a function to read the backup and restore it */
|
||||
try {
|
||||
$dbForConsole = $this->getConsoleDB();
|
||||
|
||||
/** Update the backup state */
|
||||
$backup = $dbForConsole->getDocument('backups', $backupId);
|
||||
if ($backup->isEmpty()) {
|
||||
Console::error('Backup not found: ' . $backupId);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Temporary file to untar and store the resultant backup
|
||||
*/
|
||||
$sqldumpFilename = $backupId . '.sql';
|
||||
$compressedFilename = $backupId . '.tar.gz';
|
||||
$tmpSqlDump = "/tmp/backups/$backupId/$sqldumpFilename";
|
||||
$tmpBackup = "/tmp/backups/$backupId/$compressedFilename";
|
||||
if (!\file_exists(\dirname($tmpBackup))) {
|
||||
if (!@\mkdir(\dirname($tmpBackup), 0755, true)) {
|
||||
throw new Exception("Failed to create temporary directory", 500);
|
||||
}
|
||||
}
|
||||
|
||||
/** load the backup file to a temporary directory */
|
||||
$localDevice = new Local();
|
||||
$deviceBackups = $this->getBackupsDevice($project->getId());
|
||||
|
||||
$path = $backup->getAttribute('path', '');
|
||||
|
||||
$buffer = $deviceBackups->read($path);
|
||||
if (!$localDevice->write($tmpBackup, $buffer)) {
|
||||
throw new Exception('Failed to copy backup to temporary directory', 500);
|
||||
};
|
||||
|
||||
// Untar the file in path and store the resulting sql file in tmpSqlDump
|
||||
$command = 'tar -xzf ' . $tmpBackup . ' -C /tmp/backups/' . $backupId . ' ' . $sqldumpFilename;
|
||||
Console::info('Running command: ' . $command);
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$stdin = '';
|
||||
$return = Console::execute($command, $stdin, $stdout, $stderr);
|
||||
|
||||
if ($return !== 0) {
|
||||
throw new Exception('Failed to extract backup: ' . $backupId);
|
||||
}
|
||||
|
||||
/** Restore the backup from the .sql file located at $tmpSqlDump */
|
||||
$command = 'mysql -h' . App::getEnv('_APP_DB_HOST') . ' -u' . App::getEnv('_APP_DB_USER') . ' -p' . App::getEnv('_APP_DB_PASS') . ' ' . App::getEnv('_APP_DB_SCHEMA') . ' < ' . $tmpSqlDump;
|
||||
|
||||
Console::info('Running command: ' . $command);
|
||||
$return = Console::execute($command, $stdin, $stdout, $stderr);
|
||||
|
||||
if ($return !== 0) {
|
||||
throw new Exception('Failed to restore backup: ' . $backupId);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function shutdown(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
|
||||
then
|
||||
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||
else
|
||||
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||
fi
|
||||
|
||||
INTERVAL=1 QUEUE='v1-backups' APP_INCLUDE='/usr/src/code/app/workers/backups.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
|
||||
@@ -252,6 +252,35 @@ services:
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-backups:
|
||||
entrypoint: worker-backups
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-backups
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- appwrite-backups:/storage/backups:rw
|
||||
depends_on:
|
||||
- redis
|
||||
- mariadb
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-webhooks:
|
||||
entrypoint: worker-webhooks
|
||||
<<: *x-logging
|
||||
@@ -845,6 +874,7 @@ volumes:
|
||||
appwrite-redis:
|
||||
appwrite-cache:
|
||||
appwrite-uploads:
|
||||
appwrite-backups:
|
||||
appwrite-certificates:
|
||||
appwrite-functions:
|
||||
appwrite-builds:
|
||||
|
||||
@@ -35,6 +35,9 @@ class Event
|
||||
public const MESSAGING_QUEUE_NAME = 'v1-messaging';
|
||||
public const MESSAGING_CLASS_NAME = 'MessagingV1';
|
||||
|
||||
public const BACKUPS_QUEUE_NAME = 'v1-backups';
|
||||
public const BACKUPS_CLASS_NAME = 'BackupsV1';
|
||||
|
||||
protected string $queue = '';
|
||||
protected string $class = '';
|
||||
protected string $event = '';
|
||||
|
||||
@@ -170,6 +170,9 @@ class Exception extends \Exception
|
||||
public const VARIABLE_NOT_FOUND = 'variable_not_found';
|
||||
public const VARIABLE_ALREADY_EXISTS = 'variable_already_exists';
|
||||
|
||||
/** Backups */
|
||||
public const BACKUP_NOT_FOUND = 'backup_not_found';
|
||||
|
||||
/** Platform */
|
||||
public const PLATFORM_NOT_FOUND = 'platform_not_found';
|
||||
|
||||
|
||||
@@ -285,6 +285,16 @@ abstract class Worker
|
||||
return $this->getDevice(APP_STORAGE_UPLOADS . '/app-' . $projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Backups Device
|
||||
* @param string $projectId of the project
|
||||
* @return Device
|
||||
*/
|
||||
protected function getBackupsDevice(string $projectId): Device
|
||||
{
|
||||
return $this->getDevice(APP_STORAGE_BACKUPS . '/app-' . $projectId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Builds Storage Device
|
||||
|
||||
@@ -30,6 +30,7 @@ use Appwrite\Utopia\Response\Model\AttributeEnum;
|
||||
use Appwrite\Utopia\Response\Model\AttributeIP;
|
||||
use Appwrite\Utopia\Response\Model\AttributeURL;
|
||||
use Appwrite\Utopia\Response\Model\AttributeDatetime;
|
||||
use Appwrite\Utopia\Response\Model\Backup;
|
||||
use Appwrite\Utopia\Response\Model\BaseList;
|
||||
use Appwrite\Utopia\Response\Model\Collection;
|
||||
use Appwrite\Utopia\Response\Model\Database;
|
||||
@@ -205,6 +206,8 @@ class Response extends SwooleResponse
|
||||
public const MODEL_DOMAIN_LIST = 'domainList';
|
||||
public const MODEL_VARIABLE = 'variable';
|
||||
public const MODEL_VARIABLE_LIST = 'variableList';
|
||||
public const MODEL_BACKUP = 'backup';
|
||||
public const MODEL_BACKUP_LIST = 'backupList';
|
||||
|
||||
// Health
|
||||
public const MODEL_HEALTH_STATUS = 'healthStatus';
|
||||
@@ -274,6 +277,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE))
|
||||
->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false))
|
||||
->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE))
|
||||
->setModel(new BaseList('Backups List', self::MODEL_BACKUP_LIST, 'backups', self::MODEL_BACKUP))
|
||||
// Entities
|
||||
->setModel(new Database())
|
||||
->setModel(new Collection())
|
||||
@@ -288,6 +292,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new AttributeIP())
|
||||
->setModel(new AttributeURL())
|
||||
->setModel(new AttributeDatetime())
|
||||
->setModel(new Backup())
|
||||
->setModel(new Index())
|
||||
->setModel(new ModelDocument())
|
||||
->setModel(new Log())
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Backup extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Bucket ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Bucket creation time in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Bucket update date in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$permissions', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Backup permissions. [Learn more about permissions](/docs/permissions).',
|
||||
'default' => [],
|
||||
'example' => ['read("any")'],
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Bucket name.',
|
||||
'default' => '',
|
||||
'example' => 'Documents',
|
||||
])
|
||||
->addRule('description', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Allowed file extensions.',
|
||||
'default' => [],
|
||||
'example' => 'This Backup was created on the 21st of March 2023',
|
||||
'array' => false
|
||||
])
|
||||
->addRule('status', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Status of the backup. One of pending, processing, created, failed.',
|
||||
'default' => [],
|
||||
'example' => 'pending',
|
||||
'array' => false
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Backup';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Collection
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_BACKUP;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user