From 76e43da781bff3665cbaa70777c5ae0b87f8ee24 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 15 Jun 2020 11:01:32 +0300 Subject: [PATCH 1/8] Added latest version endpoint (hidden from API) --- app/controllers/api/health.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index db95fe5db1..9cfd8c1af4 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -20,6 +20,15 @@ $utopia->get('/v1/health') } ); +$utopia->get('/v1/health/version') + ->desc('Get Version') + ->label('scope', 'public') + ->action( + function () use ($response) { + $response->json(['version' => APP_VERSION_STABLE]); + } + ); + $utopia->get('/v1/health/db') ->desc('Get DB') ->label('scope', 'health.read') @@ -124,7 +133,7 @@ $utopia->get('/v1/health/queue/tasks') ); $utopia->get('/v1/health/queue/logs') -->desc('Get Logs Queue') + ->desc('Get Logs Queue') ->label('scope', 'health.read') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'health') From 09ad2ffd9013a1a92da0dc692d22b18772fe37b3 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 15 Jun 2020 11:01:48 +0300 Subject: [PATCH 2/8] Added doctor CLI --- Dockerfile | 1 + app/tasks/init.php | 218 +++++++++++++++++++++++++++++++++++++++++++++ bin/doctor | 3 + 3 files changed, 222 insertions(+) create mode 100755 bin/doctor diff --git a/Dockerfile b/Dockerfile index 1f41c3e331..c4e7e72c82 100644 --- a/Dockerfile +++ b/Dockerfile @@ -166,6 +166,7 @@ COPY ./docker/supervisord.conf /etc/supervisord.conf # Executables RUN chmod +x /usr/local/bin/start +RUN chmod +x /usr/local/bin/doctor RUN chmod +x /usr/local/bin/migrate RUN chmod +x /usr/local/bin/test diff --git a/app/tasks/init.php b/app/tasks/init.php index 71619381d0..f9260f39cb 100644 --- a/app/tasks/init.php +++ b/app/tasks/init.php @@ -5,8 +5,10 @@ require_once __DIR__.'/../init.php'; global $request; +use Appwrite\Storage\Device\Local; use Utopia\CLI\CLI; use Utopia\CLI\Console; +use Utopia\Domains\Domain; $cli = new CLI(); @@ -26,4 +28,220 @@ $cli ]); }); +$cli + ->task('doctor') + ->desc('Validate server health') + ->action(function () use ($request, $register) { + Console::log(" __ ____ ____ _ _ ____ __ ____ ____ __ __ + / _\ ( _ \( _ \/ )( \( _ \( )(_ _)( __) ( )/ \ +/ \ ) __/ ) __/\ /\ / ) / )( )( ) _) _ )(( O ) +\_/\_/(__) (__) (_/\_)(__\_)(__) (__) (____)(_)(__)\__/ "); + + Console::log("\n".'Running '.APP_NAME.' Doctor 🩺 for version '.$request->getServer('_APP_VERSION', 'UNKNOWN').' ...'."\n"); + + Console::log('Checking for production best practices...'); + + try { + $domain = new Domain($request->getServer('_APP_DOMAIN')); + + if(!$domain->isKnown() || $domain->isTest()) { + Console::log('🔴 Hostname has a public suffix'); + } + else { + Console::log('🟢 Hostname has a public suffix'); + } + } catch (\Throwable $th) { + //throw $th; + } + + try { + $domain = new Domain($request->getServer('_APP_DOMAIN_TARGET')); + + if(!$domain->isKnown() || $domain->isTest()) { + Console::log('🔴 CNAME target has a public suffix'); + } + else { + Console::log('🟢 CNAME target has a public suffix'); + } + } catch (\Throwable $th) { + //throw $th; + } + + try { + if($request->getServer('_APP_OPENSSL_KEY_V1', 'your-secret-key') === 'your-secret-key') { + Console::log('🔴 Using a unique secret key for encryption'); + } + else { + Console::log('🟢 Using a unique secret key for encryption'); + } + } catch (\Throwable $th) { + //throw $th; + } + + try { + if($request->getServer('_APP_ENV', 'development') === 'development') { + Console::log('🔴 App enviornment is set for production'); + } + else { + Console::log('🟢 App enviornment is set for production'); + } + } catch (\Throwable $th) { + //throw $th; + } + + sleep(1); + + try { + Console::log("\n".'Checking connectivity...'); + } catch (\Throwable $th) { + //throw $th; + } + + try { + $register->get('db'); /* @var $db PDO */ + Console::success('Database............connected 👍'); + } catch (\Throwable $th) { + Console::error('Database.........disconnected 👎'); + } + + try { + $register->get('cache'); + Console::success('Queue...............connected 👍'); + } catch (\Throwable $th) { + Console::error('Queue............disconnected 👎'); + } + + try { + $register->get('cache'); + Console::success('Cache...............connected 👍'); + } catch (\Throwable $th) { + Console::error('Cache............disconnected 👎'); + } + + try { + $mail = $register->get('smtp'); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */ + + $mail->addAddress('demo@example.com', 'Example.com'); + $mail->Subject = 'Test SMTP Connection'; + $mail->Body = 'Hello World'; + $mail->AltBody = 'Hello World'; + + $mail->send(); + Console::success('SMTP................connected 👍'); + } catch (\Throwable $th) { + Console::error('SMTP.............disconnected 👎'); + } + + $host = $request->getServer('_APP_STATSD_HOST', 'telegraf'); + $port = $request->getServer('_APP_STATSD_PORT', 8125); + + if($fp = @fsockopen('udp://'.$host, $port, $errCode, $errStr, 2)){ + Console::success('StatsD..............connected 👍'); + fclose($fp); + } else { + Console::error('StatsD...........disconnected 👎'); + } + + $host = $request->getServer('_APP_INFLUXDB_HOST', ''); + $port = $request->getServer('_APP_INFLUXDB_PORT', ''); + + if($fp = @fsockopen($host, $port, $errCode, $errStr, 2)){ + Console::success('InfluxDB............connected 👍'); + fclose($fp); + } else { + Console::error('InfluxDB.........disconnected 👎'); + } + + sleep(1); + + Console::log(''); + Console::log('Checking volumes...'); + + $device = new Local(APP_STORAGE_UPLOADS.'/'); + + // Upload + + if (is_readable($device->getRoot())) { + Console::success('Upload Volume........readable 👍'); + } + else { + Console::error('Upload Volume......unreadable 👎'); + } + + if (is_writable($device->getRoot())) { + Console::success('Upload Volume.......writeable 👍'); + } + else { + Console::error('Upload Volume.....unwriteable 👎'); + } + + // Cache + + if (is_readable($device->getRoot().'/../cache')) { + Console::success('Cache Volume.........readable 👍'); + } + else { + Console::error('Cache Volume.......unreadable 👎'); + } + + if (is_writable($device->getRoot().'/../cache')) { + Console::success('Cache Volume........writeable 👍'); + } + else { + Console::error('Cache Volume......unwriteable 👎'); + } + + // Config + + if (is_readable($device->getRoot().'/../config')) { + Console::success('Config Volume........readable 👍'); + } + else { + Console::error('Config Volume......unreadable 👎'); + } + + if (is_writable($device->getRoot().'/../config')) { + Console::success('Config Volume.......writeable 👍'); + } + else { + Console::error('Config Volume.....unwriteable 👎'); + } + + // Certs + + if (is_readable($device->getRoot().'/../certificates')) { + Console::success('Certs Volume.........readable 👍'); + } + else { + Console::error('Certs Volume.......unreadable 👎'); + } + + if (is_writable($device->getRoot().'/../certificates')) { + Console::success('Certs Volume........writeable 👍'); + } + else { + Console::error('Certs Volume......unwriteable 👎'); + } + + + try { + Console::log(''); + $version = json_decode(@file_get_contents($request->getServer('_APP_HOME', 'http://localhost').'/v1/health/version'), true); + + if($version && isset($version['version'])) { + if(version_compare($version['version'], $request->getServer('_APP_VERSION', 'UNKNOWN')) === 0) { + Console::info('You are running the latest version of '.APP_NAME.'! 🥳'); + } + else { + Console::info('A new version ('.$version['version'].') is available! 🥳'."\n"); + } + } + else { + Console::error('Failed to check for a newer version'."\n"); + } + } catch (\Throwable $th) { + Console::error('Failed to check for a newer version'."\n"); + } + }); + $cli->run(); diff --git a/bin/doctor b/bin/doctor new file mode 100755 index 0000000000..4934a11e1e --- /dev/null +++ b/bin/doctor @@ -0,0 +1,3 @@ +#!/bin/bash + +php /usr/share/nginx/html/app/tasks/init.php doctor \ No newline at end of file From d007efe9de0e19fbe7fe9da1635c242f8c9591d2 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 15 Jun 2020 11:19:03 +0300 Subject: [PATCH 3/8] Updated emoji --- app/tasks/init.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tasks/init.php b/app/tasks/init.php index f9260f39cb..d0b8840ca0 100644 --- a/app/tasks/init.php +++ b/app/tasks/init.php @@ -37,7 +37,7 @@ $cli / \ ) __/ ) __/\ /\ / ) / )( )( ) _) _ )(( O ) \_/\_/(__) (__) (_/\_)(__\_)(__) (__) (____)(_)(__)\__/ "); - Console::log("\n".'Running '.APP_NAME.' Doctor 🩺 for version '.$request->getServer('_APP_VERSION', 'UNKNOWN').' ...'."\n"); + Console::log("\n".'Running '.APP_NAME.' Doctor 👩‍⚕️ for version '.$request->getServer('_APP_VERSION', 'UNKNOWN').' ...'."\n"); Console::log('Checking for production best practices...'); From f4023dcfb9e5c9dce599fa85ae9dd1ff23f71309 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 15 Jun 2020 16:54:19 +0300 Subject: [PATCH 4/8] Added abuse test --- CHANGES.md | 3 ++- app/tasks/init.php | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d7902bbd8c..0ae4acdc6a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,7 +13,8 @@ - New UI micro-interactions and styles fixes (@AnatoleLucet) - UI performance & accessibility improvments - Updated ClamAV conntainer to version 1.0.9 -- All emails are now sent asynchronously for improved performance (@TorstenDittmann) +- All emails are now sent asynchronously for improved performance (@) +- New Doctor CLI to debug the Appwrite server ([#415](https://github.com/appwrite/appwrite/issues/415)) ## Bug Fixes diff --git a/app/tasks/init.php b/app/tasks/init.php index d0b8840ca0..b5247d3f35 100644 --- a/app/tasks/init.php +++ b/app/tasks/init.php @@ -89,6 +89,17 @@ $cli //throw $th; } + try { + if($request->getServer('_APP_OPTIONS_ABUSE', 'disabled') === 'disabled') { + Console::log('🔴 Abuse protection is enabled'); + } + else { + Console::log('🟢 Abuse protection is enabled'); + } + } catch (\Throwable $th) { + //throw $th; + } + sleep(1); try { From 6668765f3e429ca281c8425092a12c33e8365915 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 16 Jun 2020 09:12:29 +0300 Subject: [PATCH 5/8] Updated Emoji --- app/tasks/init.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tasks/init.php b/app/tasks/init.php index b5247d3f35..c8a63c1e9d 100644 --- a/app/tasks/init.php +++ b/app/tasks/init.php @@ -37,7 +37,7 @@ $cli / \ ) __/ ) __/\ /\ / ) / )( )( ) _) _ )(( O ) \_/\_/(__) (__) (_/\_)(__\_)(__) (__) (____)(_)(__)\__/ "); - Console::log("\n".'Running '.APP_NAME.' Doctor 👩‍⚕️ for version '.$request->getServer('_APP_VERSION', 'UNKNOWN').' ...'."\n"); + Console::log("\n".'👩‍⚕️ Running '.APP_NAME.' Doctor for version '.$request->getServer('_APP_VERSION', 'UNKNOWN').' ...'."\n"); Console::log('Checking for production best practices...'); From 7a896dcf8239a147137aa4c00e265b6bef98b432 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 17 Jun 2020 14:18:28 +0300 Subject: [PATCH 6/8] Added disk usage check --- app/tasks/init.php | 104 +++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 60 deletions(-) diff --git a/app/tasks/init.php b/app/tasks/init.php index c8a63c1e9d..43d8803019 100644 --- a/app/tasks/init.php +++ b/app/tasks/init.php @@ -6,6 +6,7 @@ require_once __DIR__.'/../init.php'; global $request; use Appwrite\Storage\Device\Local; +use Appwrite\Storage\Storage; use Utopia\CLI\CLI; use Utopia\CLI\Console; use Utopia\Domains\Domain; @@ -100,7 +101,7 @@ $cli //throw $th; } - sleep(1); + sleep(0.2); try { Console::log("\n".'Checking connectivity...'); @@ -163,75 +164,58 @@ $cli Console::error('InfluxDB.........disconnected 👎'); } - sleep(1); + sleep(0.2); Console::log(''); Console::log('Checking volumes...'); - $device = new Local(APP_STORAGE_UPLOADS.'/'); + foreach ([ + 'Uploads' => APP_STORAGE_UPLOADS, + 'Cache' => APP_STORAGE_CACHE, + 'Config' => APP_STORAGE_CONFIG, + 'Certs' => APP_STORAGE_CERTIFICATES + ] as $key => $volume) { + $device = new Local($volume); - // Upload - - if (is_readable($device->getRoot())) { - Console::success('Upload Volume........readable 👍'); - } - else { - Console::error('Upload Volume......unreadable 👎'); + if (is_readable($device->getRoot())) { + Console::success('🟢 '.$key.' Volume is readable'); + } + else { + Console::error('🔴 '.$key.' Volume is unreadable'); + } + + if (is_writable($device->getRoot())) { + Console::success('🟢 '.$key.' Volume is writeable'); + } + else { + Console::error('🔴 '.$key.' Volume is unwriteable'); + } } - if (is_writable($device->getRoot())) { - Console::success('Upload Volume.......writeable 👍'); - } - else { - Console::error('Upload Volume.....unwriteable 👎'); - } + sleep(0.2); - // Cache + Console::log(''); + Console::log('Checking disk space usage...'); - if (is_readable($device->getRoot().'/../cache')) { - Console::success('Cache Volume.........readable 👍'); - } - else { - Console::error('Cache Volume.......unreadable 👎'); - } + foreach ([ + 'Uploads' => APP_STORAGE_UPLOADS, + 'Cache' => APP_STORAGE_CACHE, + 'Config' => APP_STORAGE_CONFIG, + 'Certs' => APP_STORAGE_CERTIFICATES + ] as $key => $volume) { + $device = new Local($volume); - if (is_writable($device->getRoot().'/../cache')) { - Console::success('Cache Volume........writeable 👍'); - } - else { - Console::error('Cache Volume......unwriteable 👎'); - } - - // Config - - if (is_readable($device->getRoot().'/../config')) { - Console::success('Config Volume........readable 👍'); - } - else { - Console::error('Config Volume......unreadable 👎'); - } - - if (is_writable($device->getRoot().'/../config')) { - Console::success('Config Volume.......writeable 👍'); - } - else { - Console::error('Config Volume.....unwriteable 👎'); - } - - // Certs - - if (is_readable($device->getRoot().'/../certificates')) { - Console::success('Certs Volume.........readable 👍'); - } - else { - Console::error('Certs Volume.......unreadable 👎'); - } - - if (is_writable($device->getRoot().'/../certificates')) { - Console::success('Certs Volume........writeable 👍'); - } - else { - Console::error('Certs Volume......unwriteable 👎'); + $percentage = (($device->getPartitionTotalSpace() - $device->getPartitionFreeSpace()) + / $device->getPartitionTotalSpace()) * 100; + + $message = $key.' Volume has '.Storage::human($device->getPartitionFreeSpace()) . ' free space ('.round($percentage, 2).'% used)'; + + if ($percentage < 80) { + Console::success('🟢 ' . $message); + } + else { + Console::error('🔴 ' . $message); + } } From e7899f0ec23da306b61ce512e747a0c5c9dcecfb Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 17 Jun 2020 14:39:46 +0300 Subject: [PATCH 7/8] Added more checks --- app/tasks/init.php | 114 ++++++++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/app/tasks/init.php b/app/tasks/init.php index 43d8803019..ba077a385e 100644 --- a/app/tasks/init.php +++ b/app/tasks/init.php @@ -5,6 +5,7 @@ require_once __DIR__.'/../init.php'; global $request; +use Appwrite\ClamAV\Network; use Appwrite\Storage\Device\Local; use Appwrite\Storage\Storage; use Utopia\CLI\CLI; @@ -42,63 +43,64 @@ $cli Console::log('Checking for production best practices...'); - try { - $domain = new Domain($request->getServer('_APP_DOMAIN')); + $domain = new Domain($request->getServer('_APP_DOMAIN')); - if(!$domain->isKnown() || $domain->isTest()) { - Console::log('🔴 Hostname has a public suffix'); - } - else { - Console::log('🟢 Hostname has a public suffix'); - } - } catch (\Throwable $th) { - //throw $th; + if(!$domain->isKnown() || $domain->isTest()) { + Console::log('🔴 Hostname has a public suffix'); + } + else { + Console::log('🟢 Hostname has a public suffix'); } - try { - $domain = new Domain($request->getServer('_APP_DOMAIN_TARGET')); + $domain = new Domain($request->getServer('_APP_DOMAIN_TARGET')); - if(!$domain->isKnown() || $domain->isTest()) { - Console::log('🔴 CNAME target has a public suffix'); - } - else { - Console::log('🟢 CNAME target has a public suffix'); - } - } catch (\Throwable $th) { - //throw $th; + if(!$domain->isKnown() || $domain->isTest()) { + Console::log('🔴 CNAME target has a public suffix'); + } + else { + Console::log('🟢 CNAME target has a public suffix'); } - try { - if($request->getServer('_APP_OPENSSL_KEY_V1', 'your-secret-key') === 'your-secret-key') { - Console::log('🔴 Using a unique secret key for encryption'); - } - else { - Console::log('🟢 Using a unique secret key for encryption'); - } - } catch (\Throwable $th) { - //throw $th; + if($request->getServer('_APP_OPENSSL_KEY_V1', 'your-secret-key') === 'your-secret-key') { + Console::log('🔴 Using a unique secret key for encryption'); + } + else { + Console::log('🟢 Using a unique secret key for encryption'); } - try { - if($request->getServer('_APP_ENV', 'development') === 'development') { - Console::log('🔴 App enviornment is set for production'); - } - else { - Console::log('🟢 App enviornment is set for production'); - } - } catch (\Throwable $th) { - //throw $th; + if($request->getServer('_APP_ENV', 'development') === 'development') { + Console::log('🔴 App enviornment is set for production'); + } + else { + Console::log('🟢 App enviornment is set for production'); } - try { - if($request->getServer('_APP_OPTIONS_ABUSE', 'disabled') === 'disabled') { - Console::log('🔴 Abuse protection is enabled'); - } - else { - Console::log('🟢 Abuse protection is enabled'); - } - } catch (\Throwable $th) { - //throw $th; + if($request->getServer('_APP_OPTIONS_ABUSE', 'disabled') === 'disabled') { + Console::log('🔴 Abuse protection is enabled'); + } + else { + Console::log('🟢 Abuse protection is enabled'); + } + + $authWhitelistEmails = $request->getServer('_APP_CONSOLE_WHITELIST_EMAILS', null); + $authWhitelistIPs = $request->getServer('_APP_CONSOLE_WHITELIST_IPS', null); + $authWhitelistDomains = $request->getServer('_APP_CONSOLE_WHITELIST_DOMAINS', null); + + if(empty($authWhitelistEmails) + && empty($authWhitelistDomains) + && empty($authWhitelistIPs) + ) { + Console::log('🔴 Console access limits are disabled'); + } + else { + Console::log('🟢 Console access limits are enabled'); + } + + if(empty($request->getServer('_APP_OPTIONS_FORCE_HTTPS', null))) { + Console::log('🔴 HTTP force option is disabled'); + } + else { + Console::log('🟢 HTTP force option is enabled'); } sleep(0.2); @@ -130,6 +132,18 @@ $cli Console::error('Cache............disconnected 👎'); } + if($request->getServer('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled + $antiVirus = new Network('clamav', 3310); + + if((@$antiVirus->ping())) { + Console::success('AntiVirus...........connected 👍'); + } + else { + Console::error('AntiVirus........disconnected 👎'); + } + + } + try { $mail = $register->get('smtp'); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */ @@ -232,11 +246,13 @@ $cli } } else { - Console::error('Failed to check for a newer version'."\n"); + //Console::error('Failed to check for a newer version'."\n"); } } catch (\Throwable $th) { - Console::error('Failed to check for a newer version'."\n"); + //Console::error('Failed to check for a newer version'."\n"); } + + Console::info('A new version (0.7.0) is available! 🥳'."\n"); }); $cli->run(); From e1d906690b1de48c02adead43908da5763e711f5 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 17 Jun 2020 14:51:20 +0300 Subject: [PATCH 8/8] Fix for custom message --- app/tasks/init.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/tasks/init.php b/app/tasks/init.php index ba077a385e..f3c5b9f724 100644 --- a/app/tasks/init.php +++ b/app/tasks/init.php @@ -246,13 +246,11 @@ $cli } } else { - //Console::error('Failed to check for a newer version'."\n"); + Console::error('Failed to check for a newer version'."\n"); } } catch (\Throwable $th) { - //Console::error('Failed to check for a newer version'."\n"); + Console::error('Failed to check for a newer version'."\n"); } - - Console::info('A new version (0.7.0) is available! 🥳'."\n"); }); $cli->run();