From 8b65750d389f424675a8c70040058510d8ea9b73 Mon Sep 17 00:00:00 2001 From: Ludovic Brun Date: Sat, 5 Nov 2022 16:14:06 +0100 Subject: [PATCH] 3.3.2 --- README.md | 2 +- www/controllers/Autoloader.php | 4 + www/controllers/Common.php | 46 +++++++++ www/controllers/Mirror.php | 141 ++++++++++++++++----------- www/controllers/Planification.php | 4 +- www/controllers/Repo.php | 82 +++++++++++----- www/includes/footer.inc.php | 8 +- www/includes/manage-sources.inc.php | 80 +++++++-------- www/operations/execute.php | 2 +- www/public/planifications.php | 2 +- www/public/resources/styles/main.css | 10 +- www/version | 2 +- 12 files changed, 245 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index c4eb3790..50f91900 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ yum install nginx php-fpm php-cli php-pdo php-xml sqlite **Installation on a Debian system** (you will need to have access to a repository providing PHP8.1 packages): ``` -apt update && apt install nginx php-fpm php-cli php8.1-sqlite3 php8.1-xml sqlite3 +apt update && apt install nginx php-fpm php-cli php8.1-sqlite3 php8.1-xml php8.1-curl sqlite3 ```

SQLite

diff --git a/www/controllers/Autoloader.php b/www/controllers/Autoloader.php index bf1c226c..d2615eef 100644 --- a/www/controllers/Autoloader.php +++ b/www/controllers/Autoloader.php @@ -182,6 +182,10 @@ private static function checkPhpModules() $__LOAD_PHP_MODULES_ERROR++; $__LOAD_PHP_MODULES_MESSAGES[] = " - xml module for PHP is not installed or disabled."; } + if (!in_array('curl', $modules)) { + $__LOAD_PHP_MODULES_ERROR++; + $__LOAD_PHP_MODULES_MESSAGES[] = " - curl module for PHP is not installed or disabled."; + } if (!defined('__LOAD_PHP_MODULES_ERROR')) { define('__LOAD_PHP_MODULES_ERROR', $__LOAD_PHP_MODULES_ERROR); diff --git a/www/controllers/Common.php b/www/controllers/Common.php index 07c565fa..aec69d1c 100644 --- a/www/controllers/Common.php +++ b/www/controllers/Common.php @@ -683,4 +683,50 @@ public static function gunzip(string $filename) throw new Exception('Error while closing gziped file: ' . $filename); } } + + /** + * Uncompress specified xz file 'file.xz' to 'file' + */ + public static function xzUncompress(string $filename) + { + $myprocess = new Process('/usr/bin/xz --decompress ' . $filename); + $myprocess->execute(); + $content = $myprocess->getOutput(); + $myprocess->close(); + + if ($myprocess->getExitCode() != 0) { + throw new Exception('Error while uncompressing xz file ' . $filename . ': ' . $content); + } + } + + /** + * Return true if distant URL file exists + */ + public static function urlFileExists(string $url, string $sslCertificatePath = null, string $sslPrivateKeyPath = null) + { + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_NOBODY, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + /** + * If a custom SSL certificate and key have been specified + */ + if (!empty($sslCertificatePath)) { + curl_setopt($ch, CURLOPT_SSLCERT, $sslCertificatePath); + } + if (!empty($sslPrivateKeyPath)) { + curl_setopt($ch, CURLOPT_SSLKEY, $sslPrivateKeyPath); + } + + curl_exec($ch); + + $responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + curl_close($ch); + + if ($responseCode != 200) { + return false; + } + + return true; + } } diff --git a/www/controllers/Mirror.php b/www/controllers/Mirror.php index a6d9adc7..309486b3 100644 --- a/www/controllers/Mirror.php +++ b/www/controllers/Mirror.php @@ -26,8 +26,8 @@ class Mirror private $workingDir; private $outputToFile = false; private $outputFile; - private $customCertificate; - private $customPrivateKey; + private $sslCustomCertificate; + private $sslCustomPrivateKey; public function setType(string $type) { @@ -79,14 +79,14 @@ public function setSyncSource(string $syncSource) $this->syncSource = $syncSource; } - public function setCustomCertificate(string $path) + public function setSslCustomCertificate(string $path) { - $this->customCertificate = $path; + $this->sslCustomCertificate = $path; } - public function setCustomPrivateKey(string $path) + public function setSslCustomPrivateKey(string $path) { - $this->customPrivateKey = $path; + $this->sslCustomPrivateKey = $path; } /** @@ -134,10 +134,22 @@ private function getReleaseFile() { $this->logOutput(PHP_EOL . '- Getting Release indices file ... '); - $this->download($this->url . '/dists/' . $this->dist . '/InRelease', $this->workingDir . '/InRelease'); - $this->download($this->url . '/dists/' . $this->dist . '/Release', $this->workingDir . '/Release'); - $this->download($this->url . '/dists/' . $this->dist . '/Release.gpg', $this->workingDir . '/Release.gpg'); + /** + * Check that Release.xx file exists before downloading it to prevent error message displaying for nothing + */ + if (Common::urlFileExists($this->url . '/dists/' . $this->dist . '/InRelease', $this->sslCustomCertificate, $this->sslCustomPrivateKey)) { + $this->download($this->url . '/dists/' . $this->dist . '/InRelease', $this->workingDir . '/InRelease'); + } + if (Common::urlFileExists($this->url . '/dists/' . $this->dist . '/Release', $this->sslCustomCertificate, $this->sslCustomPrivateKey)) { + $this->download($this->url . '/dists/' . $this->dist . '/Release', $this->workingDir . '/Release'); + } + if (Common::urlFileExists($this->url . '/dists/' . $this->dist . '/Release.gpg', $this->sslCustomCertificate, $this->sslCustomPrivateKey)) { + $this->download($this->url . '/dists/' . $this->dist . '/Release.gpg', $this->workingDir . '/Release.gpg'); + } + /** + * Print an error and quit if no Release file has been found + */ if (!file_exists($this->workingDir . '/InRelease') and !file_exists($this->workingDir . '/Release') and !file_exists($this->workingDir . '/Release.gpg')) { $this->logError('Error', 'Could not download Release indices file'); } @@ -265,7 +277,7 @@ private function parsePrimaryPackagesList(string $primaryFile) $packageChecksum = $data['checksum']; /** - * If path and md5sum have been parsed, had them to the global rpm packages list array + * If path and checksum have been parsed, had them to the global rpm packages list array */ $this->rpmPackagesLocation[] = array('location' => $packageLocation, 'checksum' => $packageChecksum); } @@ -312,24 +324,37 @@ private function parseReleaseIndiceFile() * Packages pattern to search in the Release file * e.g: main/binary-amd64/Packages */ - $regex = $this->section . '/binary-' . $arch . '/Packages'; + $regex = $this->section . '/binary-' . $arch . '/Packages($|.gz$|.xz$)'; /** * Parse the whole file, searching for the desired lines */ foreach ($content as $line) { - if (preg_match("#$regex$#", $line)) { + if (preg_match("#$regex#", $line)) { /** * Explode the line to separate hashes and location */ $splittedLine = explode(' ', trim($line)); /** - * We only need the location with its md5sum (32 caracters long) - * e.g: 006b66a6902a5c0ae6a921f7dab4238c 44688496 main/binary-amd64/Packages + * We only need the location with its SHA256 (64 caracters long) + * e.g: bd29d2ec28c10fec66a139d8e9a88ca01ff0f2533ca3fab8dc33c13b533059c1 1279885 main/binary-amd64/Packages */ - if (strlen($splittedLine[0]) == '32') { - $this->packagesIndicesLocation[] = array('location' => end($splittedLine), 'md5sum' => $splittedLine[0]); + if (strlen($splittedLine[0]) == '64') { + $location = end($splittedLine); + $checksum = $splittedLine[0]; + + /** + * Include this Package.xx file only if it does really exist on the remote server (sometimes it can be declared in Release but not exists...) + */ + if (Common::urlFileExists($this->url . '/dists/' . $this->dist . '/' . $location, $this->sslCustomCertificate, $this->sslCustomPrivateKey)) { + $this->packagesIndicesLocation[] = array('location' => $location, 'checksum' => $checksum); + + /** + * Then ignore all next Package.xx indices file from the same arch as at least one has been found + */ + break 1; + } } } } @@ -446,36 +471,47 @@ private function parsePackagesIndiceFile() */ foreach ($this->packagesIndicesLocation as $packageIndice) { $packageIndicesLocation = $packageIndice['location']; - $packageIndexMd5 = $packageIndice['md5sum']; + $packageIndicesChecksum = $packageIndice['checksum']; + $packageIndicesName = preg_split('#/#', $packageIndicesLocation); + $packageIndicesName = end($packageIndicesName); /** - * Download Packages.gz file using its location + * Download Packages.xx file using its location */ - if (!$this->download($this->url . '/dists/' . $this->dist . '/' . $packageIndicesLocation . '.gz', $this->workingDir . '/Packages.gz')) { - $this->logError('Error while downloading Packages.gz indices file: ' . $this->url . '/' . $packageIndicesLocation, 'Could not download Packages indices file'); + if (!$this->download($this->url . '/dists/' . $this->dist . '/' . $packageIndicesLocation, $this->workingDir . '/' . $packageIndicesName)) { + $this->logError('Error while downloading ' . $packageIndicesName . ' indices file: ' . $this->url . '/' . $packageIndicesLocation, 'Could not download ' . $packageIndicesName . ' indices file'); } /** - * Gunzip Packages.gz + * Then check that the Packages.xx file's checksum matches the one that what specified in Release file */ - try { - \Controllers\Common::gunzip($this->workingDir . '/Packages.gz'); - } catch (Exception $e) { - $this->logError($e, 'Error while uncompressing Packages.gz'); + if (hash_file('sha256', $this->workingDir . '/' . $packageIndicesName) !== $packageIndicesChecksum) { + $this->logError($packageIndicesName . ' indices file\'s SHA256 checksum does not match the SHA256 checksum specified in the Release file ' . $packageIndicesChecksum, 'Could not verify Packages indices file'); } /** - * Then check that the gunzip Packages file's md5 is the same as the one that what specified in Release file + * Uncompress Packages.xx if it is compressed (.gz or .xz) */ - if (md5_file($this->workingDir . '/Packages') !== $packageIndexMd5) { - $this->logError('Packages indices file\'s md5 (' . md5_file($this->workingDir . '/Packages') . ') does not match the md5 specified in the Release file ' . $packageIndexMd5, 'Could not verify Packages indices file'); + if (preg_match('/.gz$/i', $packageIndicesName)) { + try { + Common::gunzip($this->workingDir . '/' . $packageIndicesName); + } catch (Exception $e) { + $this->logError($e, 'Error while uncompressing ' . $packageIndicesName); + } + } + if (preg_match('/.xz$/i', $packageIndicesName)) { + try { + Common::xzUncompress($this->workingDir . '/Packages.xz'); + } catch (Exception $e) { + $this->logError($e, 'Error while uncompressing Packages.xz'); + } } /** * Get all .deb packages location from the uncompressed Packages file */ $packageLocation = ''; - $packageMd5 = ''; + $packageChecksum = ''; $handle = fopen($this->workingDir . '/Packages', 'r'); if ($handle) { @@ -488,19 +524,19 @@ private function parsePackagesIndiceFile() } /** - * Get deb md5sum + * Get deb SHA256 */ - if (preg_match('/^MD5sum:\s+(.*)/im', $line)) { - $packageMd5 = trim(str_replace('MD5sum: ', '', $line)); + if (preg_match('/^SHA256:\s+(.*)/im', $line)) { + $packageChecksum = trim(str_replace('SHA256: ', '', $line)); } /** - * If location and md5sum have been parsed, had them to the global deb packages list array + * If location and checksum have been parsed, had them to the global deb packages list array */ - if (!empty($packageLocation) and !empty($packageMd5)) { - $this->debPackagesLocation[] = array('location' => $packageLocation, 'md5sum' => $packageMd5); + if (!empty($packageLocation) and !empty($packageChecksum)) { + $this->debPackagesLocation[] = array('location' => $packageLocation, 'checksum' => $packageChecksum); - unset($packageLocation, $packageMd5); + unset($packageLocation, $packageChecksum); } } @@ -690,28 +726,19 @@ private function download(string $url, string $savePath) $localFile = fopen($savePath, "w"); $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); // set remote file url - curl_setopt($ch, CURLOPT_FILE, $localFile); // set output file - curl_setopt($ch, CURLOPT_TIMEOUT, 30); // set timeout - // curl_setopt($ch, CURLOPT_PORT , 443); - // curl_setopt($ch, CURLOPT_VERBOSE, 0); - // curl_setopt($ch, CURLOPT_HEADER, 0); - // curl_setopt($ch, CURLOPT_SSLCERT, getcwd() . "/public_cert.pem"); - // curl_setopt($ch, CURLOPT_SSLKEY, getcwd() . "/private.pem"); - // curl_setopt($ch, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt"); - // curl_setopt($ch, CURLOPT_POST, 1); - // curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); - // curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - // curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); + curl_setopt($ch, CURLOPT_URL, $url); // set remote file url + curl_setopt($ch, CURLOPT_FILE, $localFile); // set output file + curl_setopt($ch, CURLOPT_TIMEOUT, 120); // set timeout + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // follow redirect /** * If a custom ssl certificate and private key must be used */ - if (!empty($this->customCertificate)) { - curl_setopt($ch, CURLOPT_SSLCERT, $this->customCertificate); + if (!empty($this->sslCustomCertificate)) { + curl_setopt($ch, CURLOPT_SSLCERT, $this->sslCustomCertificate); } - if (!empty($this->customPrivateKey)) { - curl_setopt($ch, CURLOPT_SSLKEY, $this->customPrivateKey); + if (!empty($this->sslCustomPrivateKey)) { + curl_setopt($ch, CURLOPT_SSLKEY, $this->sslCustomPrivateKey); } /** @@ -937,7 +964,7 @@ private function downloadDebPackages($url) */ foreach ($this->debPackagesLocation as $debPackage) { $debPackageLocation = $debPackage['location']; - $debPackageMd5 = $debPackage['md5sum']; + $debPackageChecksum = $debPackage['checksum']; $debPackageName = preg_split('#/#', $debPackageLocation); $debPackageName = end($debPackageName); $packageCounter++; @@ -955,10 +982,10 @@ private function downloadDebPackages($url) } /** - * Check that downloaded deb package's md5 matches the md5sum specified by the Packages file + * Check that downloaded deb package's sha256 matches the sha256 specified by the Packages file */ - if (md5_file($this->workingDir . '/packages/' . $debPackageName) != $debPackageMd5) { - $this->logError('md5 does not match', 'Error while retrieving packages'); + if (hash_file('sha256', $this->workingDir . '/packages/' . $debPackageName) != $debPackageChecksum) { + $this->logError('SHA256 does not match', 'Error while retrieving packages'); } /** diff --git a/www/controllers/Planification.php b/www/controllers/Planification.php index d0266b9f..4d5df99b 100644 --- a/www/controllers/Planification.php +++ b/www/controllers/Planification.php @@ -684,7 +684,7 @@ public function exec() /** * On peut récupérer toutes les informations du repo à partir de l'Id de repo et de l'Id de snapshot */ - $this->repo->getAllById($repoId, $mostRecentSnapId); + $this->repo->getAllById($repoId, $mostRecentSnapId, '', false); /** * Si le snapshot de repo est de type 'local' alors on passe au repo suivant @@ -1343,7 +1343,7 @@ private function getInfo(string $id) */ if (!empty($planInfo['Id_snap'])) { $this->repo->setSnapId($planInfo['Id_snap']); - $this->repo->getAllById('', $this->repo->getSnapId()); + $this->repo->getAllById('', $this->repo->getSnapId(), '', false); } if (!empty($planInfo['Id_group'])) { $this->group->setId($planInfo['Id_group']); diff --git a/www/controllers/Repo.php b/www/controllers/Repo.php index 62ca8ba7..4a0ccc87 100644 --- a/www/controllers/Repo.php +++ b/www/controllers/Repo.php @@ -1998,11 +1998,6 @@ private function getPackages() throw new Exception('Local repo snapshot cannot be updated'); } - /** - * Get source repo Url - */ - $this->getFullSource($this->packageType, $this->source); - /** * 2. Si il s'agit d'un nouveau repo, on vérifie qu'un repo du même nom avec un ou plusieurs snapshots actifs n'existe pas déjà. * Un repo peut exister et n'avoir aucun snapshot / environnement rattachés (il sera invisible dans la liste) mais dans ce cas cela ne doit pas empêcher la création d'un nouveau repo @@ -2076,15 +2071,6 @@ private function getPackages() $this->workingDir = REPOS_DIR . '/download-mirror-' . $this->name . '-' . $this->dist . '-' . $this->section . '-' . time(); } - /** - * Si le répertoire existe déjà, on le supprime - */ - if (is_dir($repoPath)) { - if (!Common::deleteRecursive($repoPath)) { - throw new Exception('Cannot delete existing directory: ' . $repoPath); - } - } - /** * 3. Retrieving packages */ @@ -2096,13 +2082,27 @@ private function getPackages() */ if ($this->packageType == 'deb') { try { + /** + * Get source repo informations + */ $mysource = new Source(); $sourceDetails = $mysource->getAll('deb', $this->source); + + /** + * Check source repo informations + */ + if (empty($sourceDetails)) { + throw new Exception('Could not retrieve source repo informations. Does the source repo still exists?'); + } + if (empty($sourceDetails['Url'])) { + throw new Exception('Could not retrieve source repo URL. Check source repo configuration.'); + } + unset($mysource); $mymirror = new Mirror(); $mymirror->setType('deb'); - $mymirror->setUrl($this->fullUrl); + $mymirror->setUrl($sourceDetails['Url']); $mymirror->setWorkingDir($this->workingDir); $mymirror->setDist($this->dist); $mymirror->setSection($this->section); @@ -2113,10 +2113,10 @@ private function getPackages() $mymirror->setOutputFile($this->op->log->steplog); $mymirror->outputToFile(true); if (!empty($sourceDetails['Ssl_certificate_path'])) { - $mymirror->setCustomCertificate('/etc/ssl/nginx/nginx-repo.crt'); + $mymirror->setSslCustomCertificate('/etc/ssl/nginx/nginx-repo.crt'); } if (!empty($sourceDetails['Ssl_private_key_path'])) { - $mymirror->setCustomPrivateKey('/etc/ssl/nginx/nginx-repo.key'); + $mymirror->setSslCustomPrivateKey('/etc/ssl/nginx/nginx-repo.key'); } $mymirror->mirror(); @@ -2131,8 +2131,14 @@ private function getPackages() /** * Renaming working dir name to final name + * First delete the target directory if it already exists */ - if (!rename($this->workingDir, REPOS_DIR . '/' . $this->name . '/' . $this->dist . '/' . $this->targetDateFormatted . '_' . $this->section)) { + if (is_dir($repoPath)) { + if (!Common::deleteRecursive($repoPath)) { + throw new Exception('Cannot delete existing directory: ' . $repoPath); + } + } + if (!rename($this->workingDir, $repoPath)) { throw new Exception('Could not rename working directory ' . $this->workingDir); } } catch (Exception $e) { @@ -2143,13 +2149,27 @@ private function getPackages() if ($this->packageType == 'rpm') { try { + /** + * Get source repo informations + */ $mysource = new Source(); $sourceDetails = $mysource->getAll('rpm', $this->source); + + /** + * Check source repo informations + */ + if (empty($sourceDetails)) { + throw new Exception('Could not retrieve source repo informations. Does the source repo still exists?'); + } + if (empty($sourceDetails['Url'])) { + throw new Exception('Could not retrieve source repo URL. Check source repo configuration.'); + } + unset($mysource); $mymirror = new Mirror(); $mymirror->setType('rpm'); - $mymirror->setUrl($this->fullUrl); + $mymirror->setUrl($sourceDetails['Url']); $mymirror->setWorkingDir($this->workingDir); $mymirror->setArch($this->targetArch); $mymirror->setSyncSource($this->targetSourcePackage); @@ -2163,17 +2183,23 @@ private function getPackages() $mymirror->setGpgKeyUrl($sourceDetails['Gpgkey']); } if (!empty($sourceDetails['Ssl_certificate_path'])) { - $mymirror->setCustomCertificate($sourceDetails['Ssl_certificate_path']); + $mymirror->setSslCustomCertificate($sourceDetails['Ssl_certificate_path']); } if (!empty($sourceDetails['Ssl_private_key_path'])) { - $mymirror->setCustomPrivateKey($sourceDetails['Ssl_private_key_path']); + $mymirror->setSslCustomPrivateKey($sourceDetails['Ssl_private_key_path']); } $mymirror->mirror(); /** * Renaming working dir name to final name + * First delete the target directory if it already exists */ - if (!rename($this->workingDir, REPOS_DIR . '/' . $this->targetDateFormatted . '_' . $this->name)) { + if (is_dir($repoPath)) { + if (!Common::deleteRecursive($repoPath)) { + throw new Exception('Cannot delete existing directory: ' . $repoPath); + } + } + if (!rename($this->workingDir, $repoPath)) { throw new Exception('Could not rename working directory ' . $this->workingDir); } } catch (Exception $e) { @@ -3450,6 +3476,7 @@ public function printRepoList(array $reposList) $this->time = $repo['Time']; $this->type = $repo['Type']; $this->signed = $repo['Signed']; + $this->arch = $repo['Arch']; if (!empty($repo['Description'])) { $this->description = $repo['Description']; } else { @@ -3573,11 +3600,12 @@ private function printRepoLine() */ if ($this->snapId != $this->lastSnapId) : if ($this->snapOpIsRunning($this->snapId) === true) : ?> - + envId) ? 'env-id="' . $this->envId . '"' : ''; ?> repo-type="type ?>" title="Select and execute an action."> - - + type == "mirror") { - echo ''; + echo ''; } elseif ($this->type == "local") { - echo ''; + echo ''; } else { echo ''; } diff --git a/www/includes/footer.inc.php b/www/includes/footer.inc.php index bc65722e..6c5beb92 100644 --- a/www/includes/footer.inc.php +++ b/www/includes/footer.inc.php @@ -1,10 +1,10 @@ diff --git a/www/includes/manage-sources.inc.php b/www/includes/manage-sources.inc.php index ceec61c2..957350cb 100644 --- a/www/includes/manage-sources.inc.php +++ b/www/includes/manage-sources.inc.php @@ -87,45 +87,6 @@ - - -
-

Imported GPG signing keys

- - -

These are the public GPG signing keys you have imported.

- - - - - - - - -
- - - -
- - -
Import a GPG key:
- -
-
- - -
-
- + endif; + + /** + * Get imported GPG signing keys + */ + $knownPublicKeys = \Controllers\Common::getGpgTrustedKeys(); ?> + +
+

Imported GPG signing keys

+ + +

These are the public GPG signing keys you have imported.

+ + + + + + + + +
+ + + +
+ + +
Import a GPG key:
+ +
+
+ + +
+
+
\ No newline at end of file diff --git a/www/operations/execute.php b/www/operations/execute.php index 9bcafbe7..f0531758 100644 --- a/www/operations/execute.php +++ b/www/operations/execute.php @@ -340,7 +340,7 @@ /** * On récupère toutes les infos du repo en base de données */ - $repo->getAllById('', $snapId); + $repo->getAllById('', $snapId, '', false); /** * Si un environnement devra pointer sur le nouveau snapshot diff --git a/www/public/planifications.php b/www/public/planifications.php index 098e5e84..dd38d85f 100644 --- a/www/public/planifications.php +++ b/www/public/planifications.php @@ -149,7 +149,7 @@ /** * Récupération de toutes les infos concernant le repo */ - $repo->getAllById('', $planSnapId); + $repo->getAllById('', $planSnapId, '', false); $planName = $repo->getName(); $planDist = $repo->getDist(); $planSection = $repo->getSection(); diff --git a/www/public/resources/styles/main.css b/www/public/resources/styles/main.css index e572ac58..32973808 100644 --- a/www/public/resources/styles/main.css +++ b/www/public/resources/styles/main.css @@ -1088,16 +1088,18 @@ textarea { footer { height: 400px; margin-top: 100px; - padding: 10px; /* marge intérieur */ + padding-top: 50px; box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.5) inset, 0px 2px 2px 0px rgba(109, 109, 109, 0.2); background-color:#182b3e; + text-align: center; +} +footer p { + color: gray; } -footer p { color: gray; } -/* lien vers github dans le footer */ -#github img { width: 25px; } +footer #github img { width: 25px; } #hideAllReposGroups { position: absolute; diff --git a/www/version b/www/version index bea438e9..47725433 100644 --- a/www/version +++ b/www/version @@ -1 +1 @@ -3.3.1 +3.3.2