From 533802e7752a0462612ec01deddeb3fbd03bb89a Mon Sep 17 00:00:00 2001 From: David Morley Date: Fri, 13 Jul 2018 10:56:43 -0700 Subject: [PATCH] Pullissues (#184) * Pull some things after curl is a success rather than for all * fix ip pull if ip * fix loading spinner css * allow php-cgi * allow php-cgi * == not === * more cli * tricky pods have node info but no home page so users can not even access the pod * implode and make them a version that conforms * less nice to dash * dont barf on bad pods * space * fix ratings * less jumping on table * view items * shortversion is not really ok for dev, hack to make it decent for now * store version in checks * fix div * make charts responsive for mobile * Catch invalid GeoIP database. * Small code simplification and remove unnecessary else statement. * Minor code changes, purely visual. --- css/poduptime.css | 29 +++++- db/add.php | 2 +- db/migration00005.sql | 1 + db/pods_apiv1.sql | 4 +- db/pull-masterversions.php | 2 +- db/pull.php | 195 +++++++++++++++++++++---------------- db/pull.sh | 4 +- db/tables.sql | 1 + rate.php | 28 +++--- show.php | 4 +- showfull.php | 10 +- 11 files changed, 164 insertions(+), 116 deletions(-) diff --git a/css/poduptime.css b/css/poduptime.css index 4ce9177..76b79e2 100644 --- a/css/poduptime.css +++ b/css/poduptime.css @@ -29,7 +29,7 @@ height: 16px; background: url('/images/smlogo.png') 0 0; display: inline-block; - margin: 0 2px; + margin: 0; } .smlogo-twitter { @@ -63,10 +63,9 @@ .mycluster { width: 35px; height: 35px; - background-color: blue; text-align: left; font-size: 17px; - background: url('/node_modules/leaflet/dist/images/marker-icon-2x.png') repeat-y right bottom; + background: blue url('/node_modules/leaflet/dist/images/marker-icon-2x.png') repeat-y right bottom; background-size: 25px 37px; } @@ -172,6 +171,30 @@ height: 100%; } +@-webkit-keyframes featherlightLoader { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes featherlightLoader { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + .featherlight-close-icon { font-size: 30px; } diff --git a/db/add.php b/db/add.php index 2462ca8..602fc2e 100644 --- a/db/add.php +++ b/db/add.php @@ -106,7 +106,7 @@ curl_setopt($chss, CURLOPT_NOBODY, 0); $outputssl = curl_exec($chss); curl_close($chss); -if (stripos($outputssl, 'openRegistrations') !== false) { +if ($outputssl && stripos($outputssl, 'openRegistrations') !== false) { $log->lwrite('Your pod has ssl and is valid ' . $_domain); echo 'Your pod has ssl and is valid
'; diff --git a/db/migration00005.sql b/db/migration00005.sql index 334deaf..1317d6c 100644 --- a/db/migration00005.sql +++ b/db/migration00005.sql @@ -5,3 +5,4 @@ UPDATE pods SET status=1 WHERE status IS NULL; ALTER TABLE pods ALTER status SET DEFAULT 1; ALTER TABLE rating_comments DROP COLUMN admin; ALTER TABLE rating_comments RENAME TO ratingcomments; +ALTER TABLE checks ADD version text; diff --git a/db/pods_apiv1.sql b/db/pods_apiv1.sql index c40a35d..12c9fc7 100644 --- a/db/pods_apiv1.sql +++ b/db/pods_apiv1.sql @@ -1,6 +1,7 @@ DROP TABLE pods_apiv1; CREATE TABLE pods_apiv1 AS SELECT * FROM pods; +ALTER TABLE pods_apiv1 ADD Hgitdate text, ADD Hgitref text, ADD Hruntime text, ADD Hencoding text, ADD longversion text, ADD ptr text, ADD whois text, ADD postalcode text, ADD connection text, ADD pingdomlast text, ADD adminrating decimal, ADD hidden boolean, ADD secure boolean; ALTER TABLE pods_apiv1 RENAME COLUMN stats_apikey TO pingdomurl; ALTER TABLE pods_apiv1 RENAME COLUMN service_xmpp TO xmpp; @@ -20,8 +21,7 @@ ALTER TABLE pods_apiv1 ALTER COLUMN secure TYPE text USING secure::text; ALTER TABLE pods_apiv1 ALTER COLUMN signup TYPE text USING signup::text; ALTER TABLE pods_apiv1 ALTER COLUMN responsetimelast7 TYPE text USING responsetimelast7::smallint; -ALTER TABLE pods_apiv1 ADD Hgitdate text, ADD Hgitref text, ADD Hruntime text, ADD Hencoding text, ADD longversion text, ADD ptr text, ADD whois text, ADD postalcode text, ADD connection text, ADD pingdomlast text, ADD adminrating decimal, ADD hidden boolean, ADD secure boolean; - + ALTER TABLE pods_apiv1 DROP podmin_statement, DROP sslexpire, DROP dnssec, DROP publickey, DROP podmin_notify; UPDATE pods_apiv1 SET hgitdate = 'unsupported'; diff --git a/db/pull-masterversions.php b/db/pull-masterversions.php index 2abde6b..4a3ce17 100644 --- a/db/pull-masterversions.php +++ b/db/pull-masterversions.php @@ -30,7 +30,7 @@ $softwares = [ 'pleroma' => ['repo' => 'pleroma%2fpleroma', 'gitsite' => 'git.pleroma.social', 'gittype' => 'gitlab', 'devbranch' => 'develop'], 'socialhome' => ['repo' => 'jaywink/socialhome', 'gitsite' => 'api.github.com', 'gittype' => 'github', 'devbranch' => ''], 'social-relay' => ['repo' => 'jaywink/social-relay', 'gitsite' => 'api.github.com', 'gittype' => 'github', 'devbranch' => ''], - 'ganggo' => ['repo' => 'ganggo/ganggo', 'gitsite' => 'api.github.com', 'gittype' => 'github', 'devbranch' => ''], + 'ganggo' => ['repo' => 'ganggo%2fganggo', 'gitsite' => 'git.feneas.org', 'gittype' => 'gitlab', 'devbranch' => ''], ]; $opts = [ diff --git a/db/pull.php b/db/pull.php index 079171f..34c52db 100644 --- a/db/pull.php +++ b/db/pull.php @@ -6,9 +6,12 @@ declare(strict_types=1); -if ($_SERVER['SERVER_ADDR'] !== $_SERVER['REMOTE_ADDR']) { - header('HTTP/1.0 403 Forbidden'); - exit; +if (!in_array(PHP_SAPI, ['cgi-fcgi', 'cli'])) { + $referer = ($_SERVER['HTTP_REFERER'] ? parse_url($_SERVER['HTTP_REFERER'])['host'] : ''); + if ($referer !== $_SERVER['SERVER_NAME']) { + header('HTTP/1.0 403 Forbidden'); + exit; + } } use GeoIp2\Database\Reader; @@ -37,10 +40,11 @@ $sqldebug && R::fancyDebug(true); R::testConnection() || die('Error in DB connection'); R::usePartialBeans(true); -// Setup GeoIP Database -$reader = new Reader($geoip2db); try { + // Setup GeoIP Database + $reader = new Reader($geoip2db); + $sql = ' SELECT domain, score, date_created, weight, podmin_notify, email, masterversion, shortversion, status FROM pods @@ -59,6 +63,8 @@ try { } } catch (\RedBeanPHP\RedException $e) { die('Error in SQL query: ' . $e->getMessage()); +} catch (\MaxMind\Db\Reader\InvalidDatabaseException $e) { + die('Invalid GeoIP database: ' . $e->getMessage()); } foreach ($pods as $pod) { @@ -84,28 +90,17 @@ foreach ($pods as $pod) { _debug('Domain', $domain); - $user_ratings = []; - foreach ($ratings as $rating) { - $admin_ratings[] = $rating['rating']; + $user_rating = 0; + if ($user_ratings = array_column($ratings, 'rating')) { + $user_rating = round(array_sum($user_ratings) / count($user_ratings), 2); } - $user_rating = empty($user_ratings) ? 0 : round(array_sum($user_ratings) / count($user_ratings), 2); - - $d = new DOMDocument; - libxml_use_internal_errors(true); - $d->loadHTMLFile('https://' . $domain); - $body = $d->getElementsByTagName('body')->item(0); - if ($body->nodeValue) { - $ld = new Language; - $detectedlanguage = strtoupper(key($ld->detect($body->nodeValue)->bestResults()->close())); - _debug('Detected Language', $detectedlanguage); - } + // Default link to fetch node info. + $link = "https://{$domain}/nodeinfo/1.0"; - if ($infos = file_get_contents('https://' . $domain . '/.well-known/nodeinfo')) { + if (($infos = @file_get_contents("https://{$domain}/.well-known/nodeinfo")) !== false) { $info = json_decode($infos, true); $link = max($info['links'])['href']; - } else { - $link = 'https://' . $domain . '/nodeinfo/1.0'; } _debug('Nodeinfo link', $link); @@ -136,9 +131,9 @@ foreach ($pods as $pod) { $jsonssl = ($outputssl ? json_decode($outputssl) : null); if ($jsonssl !== null) { - $xdver = $jsonssl->software->version ?? 0; - preg_match_all('((?:\d(.|-)?)+(\.|-)\d+\.*)', $xdver, $dverr); - $shortversion = $dverr[0][0] ?? '0.0.0.0'; + $version = $jsonssl->software->version ?? 0; + preg_match_all('((?:\d(.|-)?)+(\.)\d+\.*)', $version, $sversion); + $shortversion = $sversion[0][0] ?? '0.0.0.0'; $signup = ($jsonssl->openRegistrations ?? false) === true; $softwarename = $jsonssl->software->name ?? 'unknown'; $name = $jsonssl->metadata->nodeName ?? $softwarename; @@ -171,6 +166,7 @@ foreach ($pods as $pod) { $c['local_posts'] = $local_posts; $c['comment_counts'] = $comment_counts; $c['shortversion'] = $shortversion; + $c['version'] = $version; if ($write) { R::store($c); } else { @@ -180,10 +176,86 @@ foreach ($pods as $pod) { die('Error in SQL query: ' . $e->getMessage()); } + _debug('Version code', $shortversion); + + try { + $masterdata = R::getRow(' + SELECT version, devlastcommit, releasedate + FROM masterversions + WHERE software = ? + ORDER BY id + DESC LIMIT 1 + ', [$softwarename]); + } catch (\RedBeanPHP\RedException $e) { + die('Error in SQL query: ' . $e->getMessage()); + } + + $masterversion = ($masterdata['version'] ?? '0.0.0.0'); + $devlastcommit = ($masterdata['devlastcommit'] ?? date('Y-m-d H:i:s')); + $releasedate = ($masterdata['releasedate'] ?? date('Y-m-d H:i:s')); + _debug('Masterversion', $masterversion); + $masterversioncheck = explode('.', $masterversion); + $shortversioncheck = (strpos($shortversion, '.') ? explode('.', $shortversion) : implode('.', ['0', preg_replace('/\D/', '', $shortversion), '0'])); + //this is still off with a pod with v1 as total version. cant explode that, won't have a [0] or [1] later to use either + + _debug('Days since master code release', date_diff(new DateTime($releasedate), new DateTime())->format('%d')); + + try { + $lastpodupdates = R::getRow(' + SELECT DISTINCT ON (shortversion, date_checked) shortversion, date_checked + FROM checks + WHERE domain = ? + AND shortversion IS NOT NULL + ORDER BY shortversion DESC, date_checked + LIMIT 1 + ', [$domain]); + } catch (\RedBeanPHP\RedException $e) { + die('Error in SQL query: ' . $e->getMessage()); + } + + $lastdatechecked = ($lastpodupdates['date_checked'] ?? date('Y-m-d H:i:s')); + $devlastdays = $devlastcommit ? date_diff(new DateTime($devlastcommit), new DateTime())->format('%a') : 30;//tmp//if no dev branch then what? + + _debug('Dev last commit was ', $devlastdays); + $updategap = date_diff(new DateTime($lastdatechecked), new DateTime($devlastcommit))->format('%a'); + + if (strpos($version, 'dev') !== false || strpos($version, 'rc') !== false || $shortversioncheck > $masterversioncheck) { + //tmp//if pod is on the development branch - see when you last updated your pod and when the last commit was made to dev branch - if the repo is active and your not updating every 120 days why are you on dev branch? + + if ($updategap + $devlastdays > 400) { + _debug('Outdated', 'Yes'); + $score -= 2; + } + } elseif (($masterversioncheck[1] - $shortversioncheck[1]) > 1) { + ///tmp/If pod is two versions off AND it's been more than 60 days since that release came out AND your on the master production branch + _debug('Outdated', 'Yes'); + $score -= 2; + $updategap = date_diff(new DateTime($lastdatechecked), new DateTime($releasedate))->format('%a'); + } elseif ($updategap - date_diff(new DateTime($releasedate), new DateTime())->format('%a') > 90) { + _debug('Outdated', 'Yes'); + $score -= 2; + $updategap = date_diff(new DateTime($lastdatechecked), new DateTime($releasedate))->format('%a'); + } else { + $updategap = date_diff(new DateTime($lastdatechecked), new DateTime($releasedate))->format('%a'); + } + _debug('Pod code was updated after ', $updategap); + + $d = new DOMDocument; + libxml_use_internal_errors(true); + $d->loadHTMLFile('https://' . $domain); + $body = $d->getElementsByTagName('body')->item(0); + if ($body->nodeValue) { + $ld = new Language; + $detectedlanguage = strtoupper(key($ld->detect($body->nodeValue)->bestResults()->close())); + _debug('Detected Language', $detectedlanguage); + } else { + $score -= 1; + } + $status = PodStatus::UP; } - if (!$jsonssl) { + if (!$jsonssl && !$body) { _debug('Connection', 'Can not connect to pod'); try { @@ -205,7 +277,6 @@ foreach ($pods as $pod) { $status = PodStatus::DOWN; } - _debug('Version code', $shortversion); _debug('Signup Open', $signup); $dnsserver = !empty($dnsserver) ? $dnsserver : '1.1.1.1'; @@ -228,16 +299,17 @@ foreach ($pods as $pod) { _debug('IPv4', $ip); _debug('IPv6', $ipv6); - $geo = $reader->city($ip); - $countryname = $geo->country->name ?? null ?: null; - $country = $geo->country->isoCode ?? null ?: null; - $city = $geo->city->name ?? null ?: null; - $state = $geo->mostSpecificSubdivision->name ?? null ?: null; - $lat = $geo->location->latitude ?? null ?: 0; - $long = $geo->location->longitude ?? null ?: 0; - - _debug('Location', json_encode($geo->raw), true); + if ($ip) { + $geo = $reader->city($ip); + $countryname = ($geo->country->name ?? null) ?: null; + $country = ($geo->country->isoCode ?? null) ?: null; + $city = ($geo->city->name ?? null) ?: null; + $state = ($geo->mostSpecificSubdivision->name ?? null) ?: null; + $lat = ($geo->location->latitude ?? null) ?: 0; + $long = ($geo->location->longitude ?? null) ?: 0; + _debug('Location', json_encode($geo->raw), true); + } echo $newline; $statslastdate = date('Y-m-d H:i:s'); @@ -262,55 +334,6 @@ foreach ($pods as $pod) { _debug('Uptime', $uptime); - try { - $masterdata = R::getRow('SELECT version, devlastcommit, releasedate FROM masterversions WHERE software = ? ORDER BY id DESC LIMIT 1', [$softwarename]); - } catch (\RedBeanPHP\RedException $e) { - die('Error in SQL query: ' . $e->getMessage()); - } - - $masterversion = ($masterdata['version'] ?? '0.0.0.0'); - $devlastcommit = ($masterdata['devlastcommit'] ?? date('Y-m-d H:i:s')); - $releasedate = ($masterdata['releasedate'] ?? date('Y-m-d H:i:s')); - _debug('Masterversion', $masterversion); - $masterversioncheck = explode('.', $masterversion); - $shortversioncheck = (strpos($shortversion, '.') ? explode('.', $shortversion) : $shortversion); - //this is still off with a pod with v1 as total version. cant explode that, won't have a [0] or [1] later to use either - - _debug('Days since master code release', date_diff(new DateTime($releasedate), new DateTime())->format('%d')); - - try { - $lastpodupdates = R::getRow('SELECT DISTINCT ON (shortversion) shortversion, date_checked FROM checks WHERE domain = ? AND shortversion IS NOT NULL ORDER BY shortversion DESC LIMIT 1', [$domain]); - } catch (\RedBeanPHP\RedException $e) { - die('Error in SQL query: ' . $e->getMessage()); - } - - $lastdatechecked = ($lastpodupdates['date_checked'] ?? date('Y-m-d H:i:s')); - $devlastdays = $devlastcommit ? date_diff(new DateTime($devlastcommit), new DateTime())->format('%a') : 30;//tmp//if no dev branch then what? - - _debug('Dev git last commit was ', $devlastdays); - $updategap = date_diff(new DateTime($lastdatechecked), new DateTime($devlastcommit))->format('%a'); - - if (strpos($xdver, 'dev') !== false || strpos($xdver, 'rc') !== false || $shortversioncheck > $masterversioncheck) { - //tmp//if pod is on the development branch - see when you last updated your pod and when the last commit was made to dev branch - if the repo is active and your not updating every 120 days why are you on dev branch? - - if ($updategap + $devlastdays > 130) { - _debug('Outdated', 'Yes'); - $score -= 2; - } - } elseif (($masterversioncheck[1] - $shortversioncheck[1]) > 1) { - ///tmp/If pod is two versions off AND it's been more than 60 days since that release came out AND your on the master production branch - _debug('Outdated', 'Yes'); - $score -= 2; - $updategap = date_diff(new DateTime($lastdatechecked), new DateTime($releasedate))->format('%a'); - } elseif ($updategap - date_diff(new DateTime($releasedate), new DateTime())->format('%a') > 90) { - _debug('Outdated', 'Yes'); - $score -= 2; - $updategap = date_diff(new DateTime($lastdatechecked), new DateTime($releasedate))->format('%a'); - } else { - $updategap = date_diff(new DateTime($lastdatechecked), new DateTime($releasedate))->format('%a'); - } - _debug('Pod code was updated after ', $updategap); - if ($score < 70 && $notify && !(isset($argv) && in_array('develop', $argv, true))) { $to = $email; $headers = ['From: ' . $adminemail, 'Bcc: ' . $adminemail]; @@ -356,7 +379,7 @@ foreach ($pods as $pod) { $p['sslvalid'] = $outputsslerror; $p['dnssec'] = $dnssec; $p['sslexpire'] = $sslexpire; - if ($dbstatus === PodStatus::UP && $status === PodStatus::UP) { + if ($dbstatus == PodStatus::UP && $status == PodStatus::UP) { $p['shortversion'] = $shortversion; $p['signup'] = $signup; $p['total_users'] = $total_users; diff --git a/db/pull.sh b/db/pull.sh index 0547359..8c53b14 100755 --- a/db/pull.sh +++ b/db/pull.sh @@ -25,7 +25,7 @@ if ! wget -q --spider --tries=2 --timeout=15 https://www.google.com; then fi echo "$HAPPY" -if [ "$HOUR" = 23 ] || [ "$@" = 'init' ]; then +if [ "$HOUR" = 23 ] || [ "$1" = 'init' ]; then printf "%s" "Pulling in master versions..." if php pull-masterversions.php; then echo "$HAPPY" @@ -44,7 +44,7 @@ if [ "$HOUR" = 23 ] || [ "$@" = 'init' ]; then else echo "$SAD" fi - if [ "$DAY" = 23 ] || [ "$@" = 'init' ]; then + if [ "$DAY" = 23 ] || [ "$1" = 'init' ]; then printf "%s" "Updating CA..." CACERT_FILE="$(php -r "include __DIR__ . '/../config.php'; echo \$cafullpath;")" if curl -Lss https://curl.haxx.se/ca/cacert.pem -o "$CACERT_FILE"; then diff --git a/db/tables.sql b/db/tables.sql index 5624066..b413514 100644 --- a/db/tables.sql +++ b/db/tables.sql @@ -86,6 +86,7 @@ CREATE TABLE checks ( local_posts int, comment_counts int, shortversion text, + version text, date_checked timestamp DEFAULT current_timestamp ); diff --git a/rate.php b/rate.php index 751c30d..38387c0 100644 --- a/rate.php +++ b/rate.php @@ -25,8 +25,8 @@ R::usePartialBeans(true); +
getMessage()); } - echo '

Podupti.me ratings for ' . $_domain . ' pod

'; + echo '
Ratings for ' . $_domain . '
'; if (empty($ratings)) { echo 'This pod has no rating yet!'; } else { foreach ($ratings as $rating) { - echo '
Comment from: ' . $rating['username'] . '
Rating: ' . $rating['rating'] . '
'; - echo '
' . $rating['comment'] . '
' . date('Y-m-d', strtotime($rating['date_created'])) . '
'; + echo '
Comment from: ' . $rating['username'] . '
Rating: ' . $rating['rating'] . '
'; + echo '
' . $rating['comment'] . '
' . date('Y-m-d', strtotime($rating['date_created'])) . '
'; } } ?>
- diff --git a/show.php b/show.php index 8ce8141..97c8b16 100644 --- a/show.php +++ b/show.php @@ -47,11 +47,11 @@ try { $humanmonitored = Carbon::now()->subDays($pod['daysmonitored'])->diffForHumans(null, true); $tip = "This {$pod['softwarename']} pod's uptime is {$pod['uptime_alltime']}% over {$humanmonitored}."; if (($_COOKIE['domain'] ?? null) === $pod['domain']) { - echo '
' . $pod['domain'] . '
'; + echo '
' . $pod['domain'] . '
'; } else { echo '
' . $pod['domain'] . '
'; } - echo '' . ($pod['uptime_alltime'] > 0 ? $pod['uptime_alltime'] . '%' : '') . ''; + echo '' . ($pod['uptime_alltime'] > 0 ? $pod['uptime_alltime'] . '%' : '') . ''; if ($pod['active_users_halfyear'] > 0) { echo '' . $pod['active_users_halfyear'] . ''; } else { diff --git a/showfull.php b/showfull.php index c2f055d..a22f2fa 100644 --- a/showfull.php +++ b/showfull.php @@ -71,7 +71,7 @@ try { $tip = "Over the last {$humanmonitored} pod uptime was {$pod['uptime_alltime']}% and response time from Los Angeles was {$pod['latency']}ms, with a SSL cert that expires {$humansslexpire}. This pod was last checked {$humanlastcheck}"; if (($_COOKIE['domain'] ?? null) === $pod['domain']) { - echo '' . $pod['domain'] . ''; + echo '' . $pod['domain'] . ''; } else { echo '' . $pod['domain'] . ''; } @@ -96,17 +96,17 @@ try { echo '
' . $version . '
'; echo '' . $pod['softwarename'] . ''; - echo '' . ($pod['uptime_alltime'] > 0 ? $pod['uptime_alltime'] . '%' : '') . ''; + echo '' . ($pod['uptime_alltime'] > 0 ? $pod['uptime_alltime'] . '%' : '') . ''; echo '' . ($pod['ipv6'] ? 'Y' : 'N') . ''; echo '' . ($pod['latency'] > 0 ? $pod['latency'] : '') . ''; echo '' . ($pod['signup'] ? 'Y' : 'N') . ''; - echo '' . ($pod['total_users'] > 0 ? $pod['total_users'] : '') . ''; + echo '' . ($pod['total_users'] > 0 ? $pod['total_users'] : '') . ''; echo '' . ($pod['active_users_halfyear'] > 0 ? $pod['active_users_halfyear'] : '') . ''; echo '' . ($pod['active_users_monthly'] > 0 ? $pod['active_users_monthly'] : '') . ''; echo '' . ($pod['local_posts'] > 0 ? $pod['local_posts'] : '') . ''; echo '' . ($pod['comment_counts'] > 0 ? $pod['comment_counts'] : '') . ''; echo '
' . $pod['monthsmonitored'] . '
'; - echo '' . $pod['userrating'] . ''; + echo '' . $pod['userrating'] . ''; echo '
' . $pod['score'] . '
'; echo '' . ($pod['dnssec'] ? 'Y' : 'N') . ''; if ($country_code === $pod['country']) { @@ -115,7 +115,7 @@ try { echo '' . $pod['country'] . ''; } echo '' . ($pod['detectedlanguage'] ?: '') . ''; - echo ''; + echo ''; $pod['service_facebook'] && print ''; $pod['service_twitter'] && print ''; $pod['service_tumblr'] && print ''; -- GitLab