From a685758881074ff75ae738374247461d2e8555e1 Mon Sep 17 00:00:00 2001 From: mrflos Date: Mon, 29 Aug 2022 00:26:46 +0300 Subject: [PATCH] wip yeswiki-installer and yeswiki-remover --- .env.example | 13 ++ README.md | 23 +++- composer.json | 4 +- composer.lock | 112 ++++++++++++++++- templates/nginx-yeswiki.pro.php | 64 ++++++++++ utils.inc.php | 207 ++++++++++++++++++++++++++++++++ yeswiki-installer.php | 109 +++++++++++++---- yeswiki-installer.utils.inc.php | 134 --------------------- yeswiki-remover.php | 65 ++++++++++ 9 files changed, 567 insertions(+), 164 deletions(-) create mode 100644 .env.example create mode 100644 templates/nginx-yeswiki.pro.php create mode 100644 utils.inc.php delete mode 100644 yeswiki-installer.utils.inc.php create mode 100755 yeswiki-remover.php diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6572978 --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# Quotas and database models +soloquota=1000000 +solomodel='modele_solo' +fermequota=10000000 +fermemodel='modele_ferme' + +# Server IP addresses in order to check if domain is really pointing to your server +ip4='127.0.0.1' +ip6='::1' + +# Mysql account with enought privileges to create users and DB +mysqluser='root' +mysqlpassword='1 very long & secure password or passphrase!' \ No newline at end of file diff --git a/README.md b/README.md index b3e9a60..c51ddf0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,24 @@ # yeswiki-installer -Installer script for YesWiki, working on an opiniated Debian 11 environment. \ No newline at end of file +Installer script for YesWiki, working on an opiniated Debian 11 environment. + +## Setup + +- clone this repository on your server +- go in the folder `cd yeswiki-installer` +- copy .env.example to .env `cp .env.example .env` +- edit .env file and change it with your informations +- add executable rights to some files `chmod +x yeswiki-installer.php yeswiki-remover.php` +- make symlinks if you want to use those commands globally and more easily +``` +sudo ln -s yeswiki.installer.php /usr/local/bin/yeswiki-installer +sudo ln -s yeswiki.remover.php /usr/local/bin/yeswiki-remover +``` + +## Usage + +**You need root acces to use those commands** + +Type `sudo yeswiki-installer` to see all the options for installation of YesWiki. + +Type `sudo yeswiki-remover` to see all the options for removal of YesWiki \ No newline at end of file diff --git a/composer.json b/composer.json index 46fac1f..d1e0984 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,7 @@ { "require": { - "league/climate": "^3.8" + "league/climate": "^3.8", + "devcoder-xyz/php-dotenv": "^1.1", + "league/plates": "^3.4" } } diff --git a/composer.lock b/composer.lock index 78c264a..8f0be61 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,52 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7804f603c1e45b579e09b65c72451f75", + "content-hash": "de592ca396a34fbfa0f874b18161bb66", "packages": [ + { + "name": "devcoder-xyz/php-dotenv", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/devcoder-xyz/php-dotenv.git", + "reference": "1f478184b7fd199f5ffb0f89c1547f61c8172042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/devcoder-xyz/php-dotenv/zipball/1f478184b7fd199f5ffb0f89c1547f61c8172042", + "reference": "1f478184b7fd199f5ffb0f89c1547f61c8172042", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "DevCoder\\": "src", + "Test\\DevCoder\\": "tests" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fad M.R", + "email": "fadymichel@devcoder.xyz" + } + ], + "description": "Parses .env files", + "support": { + "issues": "https://github.com/devcoder-xyz/php-dotenv/issues", + "source": "https://github.com/devcoder-xyz/php-dotenv/tree/1.1.1" + }, + "time": "2021-07-17T21:19:15+00:00" + }, { "name": "league/climate", "version": "3.8.2", @@ -71,6 +115,70 @@ }, "time": "2022-06-18T14:42:08+00:00" }, + { + "name": "league/plates", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/plates.git", + "reference": "6d3ee31199b536a4e003b34a356ca20f6f75496a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/plates/zipball/6d3ee31199b536a4e003b34a356ca20f6f75496a", + "reference": "6d3ee31199b536a4e003b34a356ca20f6f75496a", + "shasum": "" + }, + "require": { + "php": "^7.0|^8.0" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Plates\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Reinink", + "email": "jonathan@reinink.ca", + "role": "Developer" + }, + { + "name": "RJ Garcia", + "email": "ragboyjr@icloud.com", + "role": "Developer" + } + ], + "description": "Plates, the native PHP template system that's fast, easy to use and easy to extend.", + "homepage": "https://platesphp.com", + "keywords": [ + "league", + "package", + "templates", + "templating", + "views" + ], + "support": { + "issues": "https://github.com/thephpleague/plates/issues", + "source": "https://github.com/thephpleague/plates/tree/v3.4.0" + }, + "time": "2020-12-25T05:00:37+00:00" + }, { "name": "psr/log", "version": "3.0.0", @@ -185,5 +293,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/templates/nginx-yeswiki.pro.php b/templates/nginx-yeswiki.pro.php new file mode 100644 index 0000000..b2939ac --- /dev/null +++ b/templates/nginx-yeswiki.pro.php @@ -0,0 +1,64 @@ +# ---------------------------------------------------------------------- +# | Config file for e($domain)?> host | +# ---------------------------------------------------------------------- + +server { + listen [::]:80; + listen 80; + server_name e($domain)?>; + + return 301 https://e($domain)?>$request_uri; +} + +server { + listen [::]:443 ssl http2; + listen 443 ssl http2; + + server_name www.e($domain)?>; + + include h5bp/tls/ssl_engine.conf; + include h5bp/tls/certificate_files.conf; + include h5bp/tls/policy_strict.conf; + + return 301 $scheme://e($domain)?>$request_uri; +} + + +server { + # listen [::]:443 ssl http2 accept_filter=dataready; # for FreeBSD + # listen 443 ssl http2 accept_filter=dataready; # for FreeBSD + listen [::]:443 ssl http2; + listen 443 ssl http2; + + # The host name to respond to + server_name e($domain)?>; + + include h5bp/tls/ssl_engine.conf; + include h5bp/tls/certificate_files.conf; + include h5bp/tls/policy_strict.conf; + + # Path for static files + root /home/e($user)?>/e($domain)?>; + + # Custom error pages + include h5bp/errors/custom_errors.conf; + + # Include the basic h5bp config set + include h5bp/basic.conf; + + access_log /var/log/nginx/e($domain)?>-access.log; + error_log /var/log/nginx/e($domain)?>-error.log error; + index index.php index.html index.htm; + + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:/var/run/php-fpm-e($user)?>.sock; + fastcgi_index index.php; + include fastcgi.conf; + } +} + diff --git a/utils.inc.php b/utils.inc.php new file mode 100644 index 0000000..8b79ebf --- /dev/null +++ b/utils.inc.php @@ -0,0 +1,207 @@ + $dash_len) { + $dash_str .= substr($password, 0, $dash_len) . '-'; + $password = substr($password, $dash_len); + } + $dash_str .= $password; + return $dash_str; +} + +function createSQLUserAndDatabase($user) +{ + $pass = generatePassword(); + exec('mysql -u '.$_SERVER['mysqluser'].' -p'.$_SERVER['mysqlpassword'].' -e \'CREATE DATABASE IF NOT EXISTS '.$user.';\'', $output); + exec('mysql -u '.$_SERVER['mysqluser'].' -p'.$_SERVER['mysqlpassword'].' -e "CREATE USER IF NOT EXISTS \''.$user.'\'@\'localhost\' IDENTIFIED BY \''.$pass.'\';"', $output); + exec('mysql -u '.$_SERVER['mysqluser'].' -p'.$_SERVER['mysqlpassword'].' -e "GRANT ALL PRIVILEGES ON '.$user.'.* TO \''.$user.'\'@\'localhost\';"', $output); + exec('mysql -u '.$_SERVER['mysqluser'].' -p'.$_SERVER['mysqlpassword'].' -e "FLUSH PRIVILEGES;"', $output); + return ['database' => $user, 'user' => $user, 'password' => $pass]; +} + +function removeMySQLUserAndDatabase($user) +{ + exec('mysql -u '.$_SERVER['mysqluser'].' -p'.$_SERVER['mysqlpassword'].' -e \'DROP DATABASE IF EXISTS '.$user.';\'', $output); + exec('mysql -u '.$_SERVER['mysqluser'].' -p'.$_SERVER['mysqlpassword'].' -e "DROP USER IF EXISTS \''.$user.'\'@\'localhost\';"', $output); + exec('mysql -u '.$_SERVER['mysqluser'].' -p'.$_SERVER['mysqlpassword'].' -e "FLUSH PRIVILEGES;"', $output); + return; +} + +function createUnixUserWithQuota($user, $quota) +{ + $pass = generatePassword(); + exec('useradd -m -p "'.$pass.'" '.$user, $output); + exec('setquota -u '.$user.' '.$quota.' '.$quota.' 0 0 -a /dev/loop0', $output); + // TODO : handle errors + return ['user' => $user, 'password' => $pass, 'quota' => $quota]; +} + +function removeUnixUser($user) +{ + exec('deluser --remove-home '.$user, $output); + // TODO : handle errors + return; +} + +function createNginxConfig($domain, $user, $herseUser, $hersePass) +{ + // Create new Plates instance + $templates = new League\Plates\Engine('./templates'); + + // Render a template + echo $templates->render('nginx-yeswiki.pro', ['domain' => $domain, 'user' => $user]); + + addHerse($nginxFile, $herseUser, $hersePass); +} + +function removeNginxConfig($domain, $user) +{ +} + +function createPhpFpmConfig($user) +{ +} + +function removePhpFpmConfig($user) +{ +} + +function copyYesWikiFiles($domain, $user, $type) +{ + $destDir = '/home'.'/'.$user.'/'.$domain; + exec('mkdir -p '.$destDir, $output); + // TODO : handle errors + return; +} + +function copyYesWikiDatabase($user, $type) +{ + $databaseModel = ($type === 'solo') ? $_SERVER['solomodel'] : $_SERVER['fermemodel']; + exec('mysql -u '.$_SERVER['mysqluser'].' -p'.$_SERVER['mysqlpassword'].' -e "DUPLICATE '.$databaseModel.' TO '.$user.';"', $output); + // TODO : handle errors + return; +} + +function checkHerse($herseUser, $hersePass) +{ + if (empty($herseUser) && empty($hersePass)) { + return false; // no herse needed + } elseif (empty($herseUser) || empty($hersePass)) { + throw new Exception('You need an username AND a password to add a herse.'); + } + return true; // herse needed +} + +function addHerse(&$nginxFile, $herseUser, $hersePass) +{ + if (empty($herseUser) && empty($hersePass)) { + return ; // no herse needed + } elseif (empty($herseUser) || empty($hersePass)) { + throw new Exception('You need an username AND a password to add a herse.'); + } else { + //add herse to the domain + echo $nginxFile; + } +} + +function removeYesWiki($domain, $user) +{ + // enlever la db et le user sql + // enlever la config nginx et la conf php-fpm + // enlever le user unix et son home +} diff --git a/yeswiki-installer.php b/yeswiki-installer.php index e0f1dd2..3ad9662 100755 --- a/yeswiki-installer.php +++ b/yeswiki-installer.php @@ -1,44 +1,101 @@ #!/usr/bin/php description('yeswiki-installer, install YesWiki like a professionnal 🌈🦄'); -$climate->arguments->add([ - 'domain' => [ - 'prefix' => 'd', - 'longPrefix' => 'domain', - 'description' => 'Domain name used for installation', - 'required' => true, - 'defaultValue' => 'example.com' - ], - 'type' => [ + +if (0 == posix_getuid()) { + $absolutePathToEnvFile = __DIR__ . '/.env'; + if (file_exists($absolutePathToEnvFile)) { + (new DotEnv($absolutePathToEnvFile))->load(); + } else { + $climate->error('ERROR : No .env file found.'); + exit; + } + + $climate->arguments->add([ + 'domain' => [ + 'prefix' => 'd', + 'longPrefix' => 'domain', + 'description' => 'Domain name used for installation', + 'required' => true, + 'defaultValue' => 'example.com' + ], + 'type' => [ 'prefix' => 't', 'longPrefix' => 'type', 'description' => 'Type of installation, can be "solo" or "ferme"', 'required' => true, 'defaultValue' => 'solo' ], - 'quota' => [ + 'quota' => [ 'prefix' => 'q', 'longPrefix' => 'quota', 'description' => 'User quota for hard drive space, in bytes', 'required' => true, - 'defaultValue' => 2000000, - 'castTo' => 'int' + 'defaultValue' => $_SERVER['soloquota'], + 'castTo' => 'int' ], -]); -$climate->arguments->parse(); -$domain = $climate->arguments->get('domain'); -if (!empty($domain) && $domain !== 'example.com') { - $climate->bold()->underline()->out('Installation of YesWiki on '.$domain); - $res = installYesWiki($domain, $climate->arguments->get('quota'), $climate->arguments->get('type')); - if (!empty($res['error'])) { - $climate->error('ERROR : '.$res['error']); - } else { - $climate->dump($res); - } + 'herseuser' => [ + 'prefix' => 'hu', + 'longPrefix' => 'herseuser', + 'description' => 'Username for HTTP auth barrier', + ], + 'hersepass' => [ + 'prefix' => 'hp', + 'longPrefix' => 'hersepass', + 'description' => 'Password for HTTP auth barrier', + ], + 'confirm' => [ + 'prefix' => 'y', + 'longPrefix' => 'yes', + 'description' => 'Say yes to every confirmation check (no prompt)', + 'noValue' => true, + ], + ]); + $climate->arguments->parse(); + $domain = $climate->arguments->get('domain'); + if (!empty($domain) && $domain !== 'example.com') { + try { + $quota = $climate->arguments->get('quota'); + $type = $climate->arguments->get('type'); + $confirm = $climate->arguments->get('confirm'); + $herseUser = $climate->arguments->get('herseuser'); + $hersePass = $climate->arguments->get('hersepass'); + checkDNS($domain); + checkIfInstalled($domain); + $needHerse = checkHerse($herseUser, $hersePass); + $user = generateUserFromDomain($domain); + $climate->bold()->underline()->out('Installation of YesWiki'); + $climate->out('This will install a yeswiki on '.$domain."\n".'model '.$type.', '.str_replace('000000', 'Gb', $quota).' quota, with the user '.$user.'.'."\n".($needHerse ? 'An herse with user '.$herseUser.' and password '.$hersePass.' will be set up.' : '')); + $input = $climate->confirm('Is it all good ?'); + if ($confirm || $input->confirmed()) { + $unixUser = createUnixUserWithQuota($user, $quota); + $dbUser = createSQLUserAndDatabase($user); + createNginxConfig($domain, $user, $herseUser, $hersePass); + createPhpFpmConfig($user); + copyYesWikiFiles($domain, $user, $type); + copyYesWikiDatabase($user, $type); + $climate->shout( + 'The yeswiki was successfully installed on '.$domain.', congrats ! 🎉'."\n" + .' Unix user : '.$unixUser['user'].' with password : '.$unixUser['password'].' was created.'."\n" + .'MySQL user : '.$dbUser['user'].' with password : '.$dbUser['password'].' was created for database '.$dbUser['database'].'.'."\n" + ); + // TODO : send log, send email + } else { + $climate->info('Ok, let\'s stop here...'); + } + } catch (Exception $e) { + $climate->error('ERROR : '.$e->getMessage()); + } + } else { + $climate->usage(); + } } else { - $climate->usage(); + $climate->error('ERROR : this script needs root privilege to run.'); + exit; } diff --git a/yeswiki-installer.utils.inc.php b/yeswiki-installer.utils.inc.php deleted file mode 100644 index a2ad763..0000000 --- a/yeswiki-installer.utils.inc.php +++ /dev/null @@ -1,134 +0,0 @@ - 'Not valid domain.']; - } - $currentip = dns_get_record($domain, DNS_A + DNS_AAAA); - if ($currentip[0]['ip'] !== $ip4) { - return ['error' => 'The current ip v4 address of '.$domain.' is '.$currentip[0]['ip'].'. it should be '.$ip4]; - } - if ($currentip[1]['ipv6'] !== $ip6) { - return ['error' => 'The current ip v6 address of '.$domain.' is '.$currentip[1]['ipv6'].'. it should be '.$ip6]; - } - return true; -} - -function checkIfInstalled($domain) { - return true; -} - -function checkIfUserExist($user) { - return true; -} - -function generateUserFromDomain($domain, $recursive = null) { - $user = 'toto'; - return $user; -} - -function generatePassword($length = 32, $add_dashes = false, $available_sets = 'luds') { - $sets = array(); - if(strpos($available_sets, 'l') !== false) - $sets[] = 'abcdefghjkmnpqrstuvwxyz'; - if(strpos($available_sets, 'u') !== false) - $sets[] = 'ABCDEFGHJKMNPQRSTUVWXYZ'; - if(strpos($available_sets, 'd') !== false) - $sets[] = '23456789'; - if(strpos($available_sets, 's') !== false) - $sets[] = '!@#$%&*?'; - - $all = ''; - $password = ''; - foreach($sets as $set) - { - $password .= $set[array_rand(str_split($set))]; - $all .= $set; - } - - $all = str_split($all); - for($i = 0; $i < $length - count($sets); $i++) - $password .= $all[array_rand($all)]; - - $password = str_shuffle($password); - - if(!$add_dashes) - return $password; - - $dash_len = floor(sqrt($length)); - $dash_str = ''; - while(strlen($password) > $dash_len) - { - $dash_str .= substr($password, 0, $dash_len) . '-'; - $password = substr($password, $dash_len); - } - $dash_str .= $password; - return $dash_str; -} - -function createSQLUserAndDatabase($user) { - $pass = generatePassword(); - return ['database' => 'dbtata', 'user' => 'dbtata', 'password' => $pass]; -} - -function createUnixUserWithQuota($user, $quota) { - $pass = generatePassword(); - return ['user' => 'toto', 'password' => $pass, 'quota' => $quota]; -} - -function createNginxConfig($domain, $user) { - -} - -function createPhpFpmConfig($user) { - -} - -function copyYesWikiFiles($domain, $user) { - -} - -function copyYesWikiDatabase($databaseModel) { - -} - -function addHerse($id, $pass) { - return ['user' => $id, 'password' => $pass]; -} - -function installYesWiki($domain, $quota, $type) { - $res = checkDNS($domain); - if (!empty($res['error'])) { - return $res; - } - $res = checkIfInstalled($domain); - if (!empty($res['error'])) { - return $res; - } - $user = generateUserFromDomain($domain); - $unixUser = createUnixUserWithQuota($user, $quota); - $dbUser = createSQLUserAndDatabase($user); - createNginxConfig($domain, $user); - createPhpFpmConfig($user); - copyYesWikiFiles($domain, $user); - $databaseModel = ($type === 'solo') ? 'modelesolo' : 'modeleferme' ; - copyYesWikiDatabase($databaseModel); - - $herseUser = []; - if (!empty($herseId) && !empty($hersePass)) { - $herseUser = addHerse($herseId, $hersePass); - } - return [ - 'domain'=> $domain, - 'type' => $type, - 'user' => $unixUser, - 'db' => $dbUser, - 'herse' => $herseUser - ]; -} - -function removeYesWiki($domain, $user) { - // enlever la db et le user sql - // enlever la config nginx et la conf php-fpm - // enlever le user unix et son home -} diff --git a/yeswiki-remover.php b/yeswiki-remover.php new file mode 100755 index 0000000..baf2096 --- /dev/null +++ b/yeswiki-remover.php @@ -0,0 +1,65 @@ +#!/usr/bin/php +description('yeswiki-remover, remove a domain used for YesWiki because death is not the end 💀😈'); + +if (0 == posix_getuid()) { + $absolutePathToEnvFile = __DIR__ . '/.env'; + if (file_exists($absolutePathToEnvFile)) { + (new DotEnv($absolutePathToEnvFile))->load(); + } else { + $climate->error('ERROR : No .env file found.'); + exit; + } + + $climate->arguments->add([ + 'domain' => [ + 'prefix' => 'd', + 'longPrefix' => 'domain', + 'description' => 'Domain name that will be removed', + 'required' => true, + 'defaultValue' => 'example.com' + ], + 'confirm' => [ + 'prefix' => 'y', + 'longPrefix' => 'yes', + 'description' => 'Say yes to every confirmation check (no prompt)', + 'noValue' => true, + ], + ]); + $climate->arguments->parse(); + $domain = $climate->arguments->get('domain'); + if (!empty($domain) && $domain !== 'example.com') { + try { + $confirm = $climate->arguments->get('confirm'); + $user = findUserFromExistingDomain($domain); + $climate->bold()->underline()->out('Removal of YesWiki'); + $climate->out('This will remove '.$domain.' with the user '.$user.''."\n"); + $input = $climate->confirm('Shall we really do it ?'); + if ($confirm || $input->confirmed()) { + removeUnixUser($user); + removeNginxConfig($domain, $user); + removePhpFpmConfig($user); + removeMySQLUserAndDatabase($user); + $climate->shout( + 'The yeswiki on '.$domain.' was successfully removed, congrats ! 🎉'."\n" + ); + // TODO : send log, send email + } else { + $climate->info('Ok, let\'s stop here...'); + } + } catch (Exception $e) { + $climate->error('ERROR : '.$e->getMessage()); + } + } else { + $climate->usage(); + } +} else { + $climate->error('ERROR : this script needs root privilege to run.'); + exit; +}