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 =$this->e($domain)?> host |
+# ----------------------------------------------------------------------
+
+server {
+ listen [::]:80;
+ listen 80;
+ server_name =$this->e($domain)?>;
+
+ return 301 https://=$this->e($domain)?>$request_uri;
+}
+
+server {
+ listen [::]:443 ssl http2;
+ listen 443 ssl http2;
+
+ server_name www.=$this->e($domain)?>;
+
+ include h5bp/tls/ssl_engine.conf;
+ include h5bp/tls/certificate_files.conf;
+ include h5bp/tls/policy_strict.conf;
+
+ return 301 $scheme://=$this->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 =$this->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/=$this->e($user)?>/=$this->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/=$this->e($domain)?>-access.log;
+ error_log /var/log/nginx/=$this->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-=$this->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;
+}