This commit is contained in:
Eric Lay 2025-01-18 06:24:08 +00:00
commit 1d23fba328
48 changed files with 5104 additions and 0 deletions

38
README.md Normal file
View File

@ -0,0 +1,38 @@
<h1 align="center">UNIT3D Community Edition Installer</h1>
<p align="center">
🎉<b>A Big Thanks To All Our <a href="https://github.com/HDInnovations/UNIT3D-Community-Edition/graphs/contributors">Contributors</a> and <a href="https://github.com/sponsors/HDVinnie">Sponsors</a></b>🎉
</p>
<p align="center"><b>NOTE: This only works for a fresh server with nothing on it but a new OS install!</b></p>
## This Repository
Installer for the [UNIT3D-Community-Edition](https://github.com/HDInnovations/UNIT3D-Community-Edition).
**Officially Supported OS's**
- Ubuntu 22.04 LTS (Jammy Jellyfish)
- Ubuntu 20.04 LTS (Focal Fossa)
**Unstable WIP**
- Ubuntu 24.04 LTS (Noble Numbat)
**We offer install and tuning services for a small price if not comfortable installing and tuninng server yourself. Otherwise if want to install yurself run commannd below.**
**To install run the following:** (and follow the instructions. must be a fresh deicated server with nothing on it besides supported OS. Also must have a proper valid domain pointing to your server IP via A RECORD and CNAME for www)
```
sudo apt -y install git
git clone https://github.com/HDInnovations/UNIT3D-Installer.git installer
cd installer
sudo ./install.sh
```
**NOTE: If you are running UNIT3D-Community-Edition on a non HTTPS instance you MUST change the following configs.**
```
.env <-- SESSION_SECURE_COOKIE must be set to false
config/secure-headers.php <-- HTTP Strict Transport Security must be set to false
config/secure-headers.php <-- Content Security Policy must be disabled
```
### Suggestions and/or Bug Reporting
We encourage the use of [GitHub Issues](https://github.com/HDInnovations/UNIT3D-INSTALLER/issues/new)!

29
artisan Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env php
<?php
use Symfony\Component\Console\Application;
set_time_limit(0);
date_default_timezone_set('America/New_York');
// include the composer autoloader
require_once __DIR__ . '/vendor/autoload.php';
// include our custom helpers
require_once __DIR__ . '/src/Helpers/helpers.php';
$app = new Application();
// register our commands
$app->addCommands([
new App\Commands\InstallCommand(),
]);
// run the app
try {
$app->run();
} catch (Exception $e) {
die($e->getMessage());
}
?>

21
box.json Normal file
View File

@ -0,0 +1,21 @@
{
"chmod": "0755",
"directories": [
"src"
],
"files": [
""
],
"finder": [
{
"name": "*.php",
"exclude": [
],
"in": "vendor"
}
],
"git-version": "package_version",
"main": "artisan",
"output": "unit3d.phar",
"stub": true
}

22
composer.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "unit3d/installer",
"description": "Installer for UNIT3D",
"type": "project",
"require": {
"php": ">=8.3",
"ext-dom": "*",
"symfony/console": "^4.4.49",
"symfony/process": "^4.4.44",
"symfony/yaml": "^4.4.45",
"symfony/var-dumper": "^4.4.47"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"license": "MIT",
"require-dev": {
"phpunit/phpunit": "^9.6.15"
}
}

2670
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

51
install.sh Executable file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env bash
source tools/colors.sh
# Detect OS
case $(head -n1 /etc/issue | cut -f 1 -d ' ') in
Ubuntu) type="ubuntu" ;;
*) type='' ;;
esac
# Unable to detect OS Properly
# Note: OVH and other providers remove the contents of /etc/issue in their OS templates
# so we need to ask the user manually to tell us what the OS is as a Fallback
# Ref: https://github.com/ServNX/UNIT3D-INSTALLER/issues/8
if [ "$type" = '' ]; then
echo -e "\n$Red We was unable to automatically determine your OS! $Color_Off"
echo -e "\n$Purple This can happen if you are using an OS template from a provider like OVH amongst others. $Color_Off\n"
PS3='Please select the # for your OS: '
options=("Ubuntu 24.04" "Ubuntu 22.04" "Ubuntu 20.04" "Quit")
select opt in "${options[@]}"
do
case $opt in
"Ubuntu 24.04")
echo 'Ubuntu 24.04 LTS \n \l' > /etc/issue
type='ubuntu'
break
;;
"Ubuntu 22.04")
echo 'Ubuntu 22.04 LTS \n \l' > /etc/issue
type='ubuntu'
break
;;
"Ubuntu 20.04")
echo 'Ubuntu 20.04 LTS \n \l' > /etc/issue
type='ubuntu'
break
;;
"Quit")
exit 0
;;
*)
echo -e "$Red Invalid Option $REPLY $Color_Off"
;;
esac
done
fi
if [ -e $type.sh ]; then
bash ./$type.sh
fi

21
phpunit.xml Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Application Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>

37
src/BaseTestCase.php Normal file
View File

@ -0,0 +1,37 @@
<?php
namespace App;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Style\SymfonyStyle;
abstract class BaseTestCase extends TestCase
{
protected $input;
protected $output;
protected $io;
protected $salt;
protected $config;
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
$this->input = new ArgvInput();
$this->output = new ConsoleOutput();
$this->io = new SymfonyStyle($this->input, $this->output);
}
protected function getOsConfig($path)
{
}
protected function setOsConfig($path, $value)
{
}
}

180
src/Classes/Config.php Normal file
View File

@ -0,0 +1,180 @@
<?php
namespace App\Classes;
use RecursiveArrayIterator;
use RecursiveIteratorIterator;
class Config
{
/**
* @var array
*/
protected $values = [];
public function __construct()
{
$this->build();
}
public function get($path, $default = null)
{
$array = $this->values;
if (!empty($path)) {
$keys = explode('.', $path);
foreach ($keys as $key) {
if (isset($array[$key])) {
$array = $array[$key];
} else {
return $default;
}
}
}
return $array;
}
public function set($path, $value)
{
if (!empty($path)) {
$at = &$this->values;
$keys = explode('.', $path);
while (count($keys) > 0) {
if (count($keys) === 1) {
if (is_array($at)) {
$at[array_shift($keys)] = $value;
} else {
throw new \RuntimeException("Can not set value at this path ($path) because is not array.");
}
} else {
$key = array_shift($keys);
if (!isset($at[$key])) {
$at[$key] = array();
}
$at = &$at[$key];
}
}
} else {
$this->values = $value;
}
return $value;
}
public function app($path, $value = null)
{
$path = "app.$path";
return $value ? $this->set($path, $value) : $this->get($path);
}
public function os($path, $value = null)
{
$path = 'os.' . distname() . '.' . $path;
return $value ? $this->set($path, $value) : $this->get($path);
}
public function add($path, array $values)
{
$get = (array)$this->get($path);
$this->set($path, $this->arrayMergeRecursiveDistinct($get, $values));
}
public function have($path)
{
$keys = explode('.', $path);
$array = $this->values;
foreach ($keys as $key) {
if (isset($array[$key])) {
$array = $array[$key];
} else {
return false;
}
}
return true;
}
public function setValues($values)
{
$this->values = $values;
}
public function getValues()
{
return $this->values;
}
public function search($searchKey, $array = null)
{
if ($array == null) {
$array = $this->values;
}
//create a recursive iterator to loop over the array recursively
$iter = new RecursiveIteratorIterator(
new RecursiveArrayIterator($array),
RecursiveIteratorIterator::SELF_FIRST);
//loop over the iterator
foreach ($iter as $key => $value) {
//if the key matches our search
if ($key === $searchKey) {
//add the current key
$keys = array($key);
//loop up the recursive chain
for ($i = $iter->getDepth() - 1; $i >= 0; $i--) {
//add each parent key
array_unshift($keys, $iter->getSubIterator($i)->key());
}
//return our output array
return array('path' => implode('.', $keys), 'value' => $value);
}
}
//return false if not found
return false;
}
protected function arrayMergeRecursiveDistinct(array &$array1, array &$array2)
{
$merged = $array1;
foreach ($array2 as $key => &$value) {
if (is_array($value) && isset ($merged[$key]) && is_array($merged[$key])) {
if (is_int($key)) {
$merged[] = $this->arrayMergeRecursiveDistinct($merged[$key], $value);
} else {
$merged[$key] = $this->arrayMergeRecursiveDistinct($merged[$key], $value);
}
} else {
if (is_int($key)) {
$merged[] = $value;
} else {
$merged[$key] = $value;
}
}
}
return $merged;
}
protected function build()
{
$directory = __DIR__ . '/../Configs';
$files = scandir($directory);
foreach ($files as $file) {
if (is_file("$directory/$file")) {
$conf = require $directory . "/$file";
$this->values[basename($file, '.php')] = $conf;
}
}
}
}

127
src/Classes/Process.php Normal file
View File

@ -0,0 +1,127 @@
<?php
namespace App\Classes;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\InputStream;
use Symfony\Component\Process\Process as SymfonyProcess;
class Process
{
/**
* @var SymfonyStyle $io
*/
private $io;
/**
* @var bool $debug
*/
private $debug = false;
/**
* Process constructor.
* @param SymfonyStyle $io
*/
public function __construct(SymfonyStyle $io)
{
$this->io = $io;
}
/**
* Executes a new Process instance
*
* @param string $cmd
* @return string
*/
public function execute($command, array $input = null, $force = false, $timeout = 3600, $cwd = null, array $env = null)
{
$this->io->writeln("\n<fg=cyan>$command</>");
$process = new SymfonyProcess($command, $cwd, $env, null, $timeout);
$process->setIdleTimeout(900);
$inputStream = null;
if ($input !== null && is_array($input)) {
$inputStream = new InputStream();
$process->setInput($inputStream);
!$this->debug ?: $this->io->writeln('[debug] Pty is on');
$process->setPty(true);
}
$bar = $this->progressStart();
$process->start();
$process->wait(function ($type, $buffer) use ($bar, $input, $inputStream) {
!$this->debug ?: $this->io->writeln("[debug] $buffer");
if ($input !== null && is_array($input)) {
$last = null;
foreach($input as $expect => $send) {
if (str_contains($buffer, $expect) && $expect !== $last) {
$inputStream->write($send . "\n");
$last = $expect;
}
usleep(5000);
}
}
$bar->advance();
usleep(200000);
});
$this->progressStop($bar);
$process->stop();
if (!$process->isSuccessful()) {
if (!$force) {
$this->io->error($process->getErrorOutput());
die();
}
$this->io->writeln("\n<fg=red>[Warning]</> " . $process->getErrorOutput());
}
return $process;
}
/**
* @return ProgressBar
*/
protected function progressStart()
{
$bar = $this->io->createProgressBar();
$bar->setBarCharacter('<fg=magenta>=</>');
$bar->setFormat('[%bar%] (<fg=cyan>%message%</>)');
$bar->setMessage('Please Wait ...');
//$bar->setRedrawFrequency(20); todo: may be useful for platforms like CentOS
$bar->start();
return $bar;
}
/**
* @param $bar
*/
protected function progressStop(ProgressBar $bar)
{
$bar->setMessage("<fg=green>Done!</>");
$bar->finish();
}
/**
* @param bool $debug
*/
public function setDebug(bool $debug)
{
$this->debug = $debug;
}
}

View File

@ -0,0 +1,107 @@
<?php
namespace App\Commands;
use App\Classes\Config;
use App\Installer\Database\DatabaseSetup;
use App\Installer\Nginx\NginxSetup;
use App\Installer\PHP\PhpSetup;
use App\Installer\Policies\InstallerPolicies;
use App\Installer\Prerequisites\Prerequisites;
use App\Installer\Server\ServerSetup;
use App\Installer\UNIT3D\Unit3dSetup;
use App\Traits\ConsoleTools;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class InstallCommand extends Command
{
use ConsoleTools;
private $steps = [
InstallerPolicies::class => 'Validating Installer Policies',
ServerSetup::class => 'Server Setup',
Prerequisites::class => 'Prerequisites',
DatabaseSetup::class => 'Configuring & Securing Database',
PhpSetup::class => 'PHP & PHP-FPM Configuration',
NginxSetup::class => 'Nginx Setup & Configurations',
Unit3dSetup::class => 'UNIT3D-Community-Edition Settings and Configuration',
];
/**
* @var SymfonyStyle
*/
private $io;
/**
* @var Config
*/
private $config;
protected function configure()
{
$this
->setName('install')
->setDescription('Provisions Server')
->setHelp('Provisions Server and installs the UNIT3D Platform.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->io = new SymfonyStyle($input, $output);
$this->config = new Config();
$this->displayIntro();
foreach ($this->steps as $class => $header) {
$this->head($header);
(new $class($this->io, $this->config))->handle();
$this->done();
}
$this->head("Finalizing Install");
$this->info();
$this->done();
}
private function info()
{
$db = $this->config->app('db');
$dbuser = $this->config->app('dbuser');
$dbpass = $this->config->app('dbpass');
$domain = $this->config->app('hostname');
$owner = $this->config->app('owner');
$password = $this->config->app('password');
$this->io->writeln([
'<fg=magenta>Please run "certbot renew --dry-run" manually to test your LetsEncrypt renewal process!!!</>',
]);
$this->io->note([
'Database: ' . $db,
'Database User: ' . $dbuser,
'Database Password: ' . $dbpass
]);
$this->io->writeln([
'<fg=green>UNIT3D-Community-Edition has been successfully installed!</>',
' ',
"Visit <fg=green>$domain</> in a browser",
' ',
"Login: <fg=green>$owner</>",
"Password: <fg=green>$password</>"
]);
}
private function done()
{
$this->io->writeln("<fg=green>[OK] Done!</>");
}
}

53
src/Configs/app.php Normal file
View File

@ -0,0 +1,53 @@
<?php
use App\Installer\Database\MySqlSetup;
return [
'min_php_version' => '8.2',
'repository' => 'https://github.com/HDInnovations/UNIT3D-Community-Edition.git',
'database_installers' => [
/**
* Map to the Installer class
*/
'MySql' => MySqlSetup::class,
],
/*
* Dynamically set configuration defaults and placeholders
*
* These do NOT need policy classes
*/
/* Main Server */
'server_name' => '',
'ip' => '',
'hostname' => '',
'ssl' => true,
'owner' => '',
'owner_email' => '',
'password' => '',
/* Database */
'database_driver' => 'MySql',
'db' => '',
'dbuser' => '',
'dbpass' => '',
'dbrootpass' => '',
/* Mail */
'mail_driver' => 'smtp',
'mail_host' => '',
'mail_port' => '',
'mail_username' => '',
'mail_password' => '',
'mail_from_name' => '',
/* Chat */
'echo-port' => '',
/* API Keys */
'tmdb-key' => '',
];

32
src/Configs/os.php Normal file
View File

@ -0,0 +1,32 @@
<?php
return [
/*
* Ubuntu
*/
'ubuntu' => [
'pkg_manager' => 'apt-get',
'web-user' => 'www-data',
'install_dir' => '/var/www/html',
'nginx-sites-available_path' => '/etc/nginx/sites-available',
'software' => [
'build-essential' => 'Basic C/C++ Development Environment',
'nginx' => 'Web Server',
'mysql-server' => 'Database Server',
'supervisor' => 'A Process Control System',
'nodejs' => 'JavaScript Run-time Environment (Includes npm)',
'git' => 'Version Control',
'tmux' => 'Screen Multiplexer',
'vim' => 'Text Editor',
'wget' => 'Transfer Data From A Server',
'zip' => 'Compress Files',
'unzip' => 'Decompress Files',
'htop' => 'Monitor Server Resources',
'cron' => 'Process Scheduling Daemon',
],
]
];

199
src/Helpers/helpers.php Normal file
View File

@ -0,0 +1,199 @@
<?php
use Symfony\Component\VarDumper\VarDumper;
use Symfony\Component\Yaml\Yaml;
if (!function_exists('installed')) {
function installed($name)
{
if (shell_exec("command -v $name") != '') {
return true;
}
return false;
}
}
if (!function_exists('base_path')) {
function base_path($path = '')
{
return __DIR__ . "/../../$path";
}
}
if (!function_exists('resource_path')) {
function resource_path($path = '')
{
return __DIR__ . "/../Resources/$path";
}
}
if (!function_exists('str_contains')) {
function str_contains($haystack, $needles)
{
foreach ((array)$needles as $needle) {
if ($needle != '' && mb_strpos($haystack, $needle) !== false) {
return true;
}
}
return false;
}
}
if (!function_exists('str_random')) {
/**
* Generate a more truly "random" alpha-numeric string.
*
* @param int $length
* @return string
*/
function str_random($length = 16)
{
$string = '';
while (($len = strlen($string)) < $length) {
$size = $length - $len;
$bytes = random_bytes($size);
$string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
}
return $string;
}
}
if (!function_exists('fqdn')) {
function fqdn()
{
return trim(exec('hostname -f'));
}
}
if (!function_exists('hostname')) {
function hostname()
{
return trim(exec('hostname'));
}
}
if (!function_exists('ip')) {
function ip()
{
return trim(explode(' ', exec('hostname -I'))[0]);
}
}
if (!function_exists('dd')) {
function dd($var)
{
VarDumper::dump($var);
die();
}
}
if (!function_exists('array_find')) {
function array_find($array, $searchKey = '')
{
//create a recursive iterator to loop over the array recursively
$iter = new RecursiveIteratorIterator(
new RecursiveArrayIterator($array),
RecursiveIteratorIterator::SELF_FIRST);
//loop over the iterator
foreach ($iter as $key => $value) {
//if the key matches our search
if ($key === $searchKey) {
//add the current key
$keys = array($key);
//loop up the recursive chain
for ($i = $iter->getDepth() - 1; $i >= 0; $i--) {
//add each parent key
array_unshift($keys, $iter->getSubIterator($i)->key());
}
//return our output array
return array('path' => implode('.', $keys), 'value' => $value);
}
}
//return false if not found
return false;
}
}
if (!function_exists('distinfo')) {
function distinfo()
{
$distname = strtolower(trim(shell_exec('head -n1 /etc/issue | cut -f 1 -d \' \'')));
$distver = trim(shell_exec('head -n1 /etc/issue | cut -f 2 -d \' \''));
$lts = (trim(shell_exec('head -n1 /etc/issue | cut -f 3 -d \' \'') === 'LTS'));
preg_match("/^[0-9]..[0-9]./m", $distver, $matches);
$mainver = $matches[0];
switch ($mainver) {
case "24.04":
$relname = "(Noble Numbat)";
break;
case "22.04":
$relname = "(Jammy Jellyfish)";
break;
case "20.04":
$relname = "(Focal Fossa)";
break;
default:
$relname = "UNKNOWN";
}
return array(
'name' => $distname,
'version' => $distver,
'mainver' => $mainver,
'relname' => $relname,
'lts' => $lts
);
}
}
if (!function_exists('distname')) {
function distname()
{
return strtolower(distinfo()['name']);
}
}
if (!function_exists('distversion')) {
function distversion()
{
return strtolower(distinfo()['version']);
}
}
if (!function_exists('distmainver')) {
function distmainver()
{
return strtolower(distinfo()['mainver']);
}
}
if (!function_exists('distrelname')) {
function distrelname()
{
return strtolower(distinfo()['name']);
}
}
if (!function_exists('distlts')) {
function distlts()
{
return strtolower(distinfo()['lts']);
}
}
if (!function_exists('memory')) {
function memory()
{
return shell_exec("grep 'MemTotal' /proc/meminfo |tr ' ' '\n' |grep [0-9]") != '';
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace App\Installer;
use App\Classes\Config;
use App\Classes\Process;
use App\Traits\ConsoleTools;
use Symfony\Component\Console\Style\SymfonyStyle;
abstract class BaseInstaller
{
use ConsoleTools;
/**
* @var SymfonyStyle $io
*/
protected $io;
/**
* @var Process
*/
protected $process;
/**
* @var Config
*/
protected $config;
/**
* @var $pkg_manager
*/
protected $pkg_manager;
/**
* @var int $timeout
*/
protected $timeout = 15;
public function __construct(SymfonyStyle $io, Config $config)
{
$this->io = $io;
$this->process = new Process($this->io);
$this->config = $config;
$this->pkg_manager = $config->get('os.'.distname().'.pkg_manager');
}
abstract public function handle();
protected function install($pkgs)
{
$this->process->execute($this->pkg_manager . " install -y $pkgs");
}
protected function process(array $commands, $force = false)
{
foreach ($commands as $cmd) {
$this->process->execute($cmd, null, $force);
}
}
protected function createFromStub(array $fr, $stub, $to)
{
$stub = resource_path() . distname() . '/' . $stub;
$file = file_get_contents($stub);
if ($file === false) {
$this->throwError("'$stub' error getting file contents. Please report this bug.");
}
$contents = str_replace(array_keys($fr), array_values($fr), $file);
file_put_contents($to, $contents);
return true;
}
protected function setTimeout($timeout)
{
$this->timeout = $timeout;
return $this;
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Installer\Database;
use App\Installer\BaseInstaller;
class DatabaseSetup extends BaseInstaller
{
public function handle()
{
$driver = $this->config->app('database_driver');
$class = $this->config->app('database_installers.' . $driver);
(new $class($this->io, $this->config))->handle();
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace App\Installer\Database;
use App\Installer\BaseInstaller;
class MySqlSetup extends BaseInstaller
{
public function handle()
{
$root_pass = $this->config->app('dbrootpass');
$db = $this->config->app('db');
$dbuser = $this->config->app('dbuser');
$dbpass = $this->config->app('dbpass');
switch (true) {
case (memory() >= 1200000 && memory() < 3900000):
$mycnf = "my-medium.cnf";
break;
case (memory() >= 3900000):
$mycnf = "my-large.cnf";
break;
default:
$mycnf = "my-small.cnf";
}
$this->process(['cp -f ' . resource_path(distname() . "/mysql/$mycnf") . ' /etc/mysql/my.cnf']);
if (distname() === 'ubuntu') {
if ((distmainver() === '20.04' || distmainver() === '22.04' || distmainver() === '24.04') && !is_dir('/var/lib/mysql')) {
$this->process([
'mkdir /var/lib/mysql',
'chown mysql:mysql /var/lib/mysql',
'mysqld --initialize-insecure',
]);
}
}
$this->createFromStub(
[
'{{PASSWORD}}' => $root_pass
],
'mysql/.my.cnf',
'/root/.my.cnf'
);
$this->process([
'update-rc.d mysql defaults',
'service mysql start',
"mysqladmin -u root password $root_pass",
'chmod 600 /root/.my.cnf'
]);
/*
* Critical
*/
$this->process([
"mysql -e \"DROP USER IF EXISTS $dbuser\"",
"mysql -e \"DROP DATABASE IF EXISTS $db\"",
"mysql -e \"CREATE DATABASE $db\"",
"mysql -e \"CREATE USER '$dbuser'@'localhost' IDENTIFIED BY '$dbpass';\"",
"mysql -e \"GRANT ALL PRIVILEGES ON $db . * TO '$dbuser'@'localhost'\"",
"mysql -e \"ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '$root_pass'\"",
"mysql -e \"DELETE FROM mysql.user WHERE User=''\"",
"mysql -e \"DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')\"",
]);
/*
* Non-Critical
*/
$this->process([
"mysql -e \"DROP DATABASE IF EXISTS test\"",
"mysql -e \"DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'\"",
], true);
$this->process(["mysql -e \"FLUSH PRIVILEGES\""]);
$this->io->writeln(' ');
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Installer\Nginx;
use App\Installer\BaseInstaller;
class NginxSetup extends BaseInstaller
{
public function handle()
{
$default = $this->config->os('nginx-sites-available_path') . '/default';
$echo_port = $this->config->app('echo-port');
$fqdn = $this->config->app('hostname');
$email = $this->config->app('owner_email');
$ssl = $this->config->app('ssl');
if (file_exists($default)) {
$this->process(["rm -rf $default"]);
}
$this->createFromStub([
'{{FQDN}}' => $fqdn
], 'nginx/default.site', $default);
$this->process([
"ufw allow 'Nginx Full'",
"ufw delete allow 'Nginx HTTP'",
"ufw allow $echo_port",
"ufw enable",
"systemctl restart nginx"
]);
$this->install('certbot python3-certbot-nginx');
if ($ssl == 'yes') {
$this->process([
"certbot --redirect --nginx -n --agree-tos --email=$email -d $fqdn -d www.$fqdn --rsa-key-size 2048",
]);
}
$this->io->writeln(' ');
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Installer\PHP;
use App\Installer\BaseInstaller;
class PhpSetup extends BaseInstaller
{
public function handle()
{
$fqdn = $this->config->app('hostname');
$pool = trim(shell_exec('find /etc/php* -type d \( -name "pool.d" -o -name "*fpm.d" \)'));
$php_fpm = trim(shell_exec('ls /etc/init.d/php*.*-fpm* |cut -f 4 -d /'));
$this->createFromStub(
[
'{{FQDN}}' => $this->config->app('hostname'),
'{{WEBUSER}}' => strtolower($this->config->os('web-user')),
],
'php-fpm/php-fpm.conf',
"$pool/$fqdn.conf"
);
//$this->process(["cp -f " . resource_path() . distname() . "/php-fpm/php-fpm.sock $pool/",]);
if (!is_link("/etc/init.d/php-fpm")) {
$this->process(["ln -s /etc/init.d/$php_fpm /etc/init.d/php-fpm > /dev/null 2>&1"]);
}
foreach (explode("\n", trim(shell_exec('find /etc/php* -name php.ini'))) as $file) {
$this->process([
"sed -i 's/;date.timezone =/date.timezone = UTC/g' $file",
"sed -i 's%_open_tag = Off%_open_tag = On%g' $file",
"sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=1/g' $file"
]);
}
$this->process([
"systemctl restart $php_fpm",
"update-rc.d $php_fpm defaults",
"service $php_fpm start"
]);
$this->io->writeln(' ');
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Installer\Policies;
class AppKeyExists extends BasePolicy
{
public function allows($param = null)
{
if (!array_key_exists('app', $this->config->getValues())) {
$this->throwError(
"The key 'app' was not found in config!
Inside the 'Configs' directory there should be a file named 'app.php'
with the application configuration."
);
}
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Installer\Policies;
class AppNotInstalled extends BasePolicy
{
public function allows($param = null)
{
if (is_dir($this->config->os('install_dir') . DIRECTORY_SEPARATOR . 'app')) {
$this->throwError('UNIT3D-Community-Edition already installed ... Exiting installer');
}
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Installer\Policies;
use App\Classes\Config;
use App\Traits\ConsoleTools;
use Symfony\Component\Console\Style\SymfonyStyle;
abstract class BasePolicy
{
use ConsoleTools;
/**
* @var SymfonyStyle
*/
protected $io;
/**
* @var Config
*/
protected $config;
public function __construct(SymfonyStyle $io, Config $config)
{
$this->io = $io;
$this->config = $config;
}
public function handle($param = null)
{
$this->allows($param);
}
abstract public function allows($param = null);
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Installer\Policies;
class DatabaseDriverKeyExists extends BasePolicy
{
public function allows($param = null)
{
if (!array_key_exists('database_driver', $this->config->get('app'))) {
$this->throwError("The key 'database_driver' was not found in 'app' config!
Please fix this and try again.
Example: 'database_driver' => 'MySql',"
);
}
$driver = $this->config->app('database_driver');
if (!is_string($driver)) {
$this->throwError("The key 'database_driver' is NOT a string in 'app' config!
'database_driver' should be a string value of the Default database driver.
Example: 'database_driver' => 'MySql',"
);
}
if (!array_key_exists($driver, $this->config->app('database_installers'))) {
$this->throwError("The key 'database_driver' is invalid in 'app' config!
'database_driver' should match a key listed in 'database_installers' array.
Example: 'database_driver' => 'MySql',
'database_installers' => [
'MySql' => MySqlInstaller::class,
],"
);
}
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Installer\Policies;
class DatabaseInstallersKeyExists extends BasePolicy
{
public function allows($param = null)
{
if (!array_key_exists('database_installers', $this->config->get('app'))) {
$this->throwError(
"The key 'database_installers' was not found in 'app' config!
Please fix this and try again.
Example: 'database_installers' => [
'MySql' => MySqlInstaller::class,
],"
);
}
$database_installers = $this->config->app('database_installers');
if (!is_array($database_installers)) {
$this->throwError(
"The key 'database_installers' is NOT an array in 'app' config!
'database_installers' should be an array of database drivers
mapped with their respected installer class.
Example: 'database_installers' => [
'MySql' => MySqlInstaller::class,
],"
);
}
if (count($database_installers) <= 0) {
$this->throwError(
"The key 'database_installers' is an empty array in 'app' config!
'database_installers' should be an array of at least 1 database driver
mapped with its respected installer class.
Example: 'database_installers' => [
'MySql' => MySqlInstaller::class,
],"
);
}
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Installer\Policies;
class InstallDirKeyExists extends BasePolicy
{
public function allows($param = null)
{
if (!array_key_exists('install_dir', $this->config->get('os.' . distname()))) {
$this->throwError(
"The key 'install_dir' was not found in 'app' config!
Please fix this and try again.
Example: 'install_dir' => '/var/www/html',"
);
}
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Installer\Policies;
class InstallerPolicies extends BasePolicy
{
private $steps = [
/*
* Configuration policies
*/
AppKeyExists::class,
InstallDirKeyExists::class,
PhpVersionKeyExists::class,
DatabaseInstallersKeyExists::class,
DatabaseDriverKeyExists::class,
/*
* User and Server State policies
*/
IsPrivilegedUser::class,
AppNotInstalled::class,
IsPhpVersionCompat::class
];
public function allows($param = null)
{
foreach ($this->steps as $class) {
(new $class($this->io, $this->config))->allows($param);
}
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Installer\Policies;
class IsPhpVersionCompat extends BasePolicy
{
public function allows($param = null)
{
$phpv = $this->config->app('min_php_version');
if (version_compare(PHP_VERSION, $phpv, '<')) {
$this->throwError('PHP Version is not compatible with UNIT3D-Community-Edition. Install PHP ' . $phpv . ' or later...');
}
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Installer\Policies;
class IsPrivilegedUser extends BasePolicy
{
public function allows($param = null)
{
$who = trim(shell_exec('whoami'));
if ($who !== 'root') {
$this->throwError('Must be ran as root or using sudo!');
}
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Installer\Policies;
class PhpVersionKeyExists extends BasePolicy
{
public function allows($param = null)
{
if (!array_key_exists('min_php_version', $this->config->get('app'))) {
$this->throwError(
"The key 'min_php_version' was not found in 'app' config!
Please fix this and try again.
Example: 'min_php_version' => '8.1',"
);
}
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Installer\Prerequisites;
use App\Installer\BaseInstaller;
class Prerequisites extends BaseInstaller
{
public function handle()
{
$software = $this->config->os('software');
$this->io->writeln("<fg=red>!! WARNING !!</> We are preparing to install software on your server. Please review and confirm!\n");
$this->seperator();
foreach ($software as $pkg => $desc) {
$this->io->writeln("* <fg=blue>'$pkg':</> <fg=yellow>$desc</>");
}
$this->seperator();
if (!$this->io->confirm('Do you wish to continue?', true)) {
$this->throwError('Aborted ...');
};
$this->process(['curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash -']);
$pkgs = implode(' ', array_keys($software));
$this->install($pkgs);
$this->process(['npm install -g laravel-echo-server']);
$this->install('ufw');
$this->process(['ufw allow 8443']);
$this->io->writeln('');
}
}

View File

@ -0,0 +1,166 @@
<?php
namespace App\Installer\Server;
use App\Installer\BaseInstaller;
class ServerSetup extends BaseInstaller
{
public function handle()
{
$this->server();
$this->user();
$this->database();
$this->mail();
$this->chat();
$this->apiKeys();
}
protected function server()
{
$server_name = $this->question('Server Name', hostname());
$this->config->app('server_name', trim($server_name));
do {
$hostname = strtolower($this->question('The domain your going to use. ( Example: example.com )', fqdn()));
$valid = (str_contains($hostname, '.') || $hostname === 'localhost');
if (!$valid) {
$this->warning("Invalid Format:");
$this->io->writeln("<fg=blue>Must be a fully qualified domain name. Examples:</>");
$this->io->listing([
fqdn() . '.com',
'server.' . fqdn() . '.com',
'example.com',
'server.example.com',
'localhost'
]);
}
} while (!$valid);
$this->config->app('hostname', trim($hostname));
$ip = $this->question('Primary IP Address', ip());
$this->config->app('ip', trim($ip));
$ssl = $this->io->choice('Enable SSL (https)', ['yes', 'no'], 'yes');
$this->config->app('ssl', $ssl);
}
protected function user()
{
$this->io->writeln('<fg=blue>User Settings</>');
$this->seperator();
$dbowner = $this->question('Owner Username', '');
$this->config->app('owner', $dbowner);
$dbpass = $this->question('Owner Password', '');
$this->config->app('password', $dbpass);
$default = 'admin@' . $this->config->app('hostname');
$email = $this->question('Owner Email', $default);
$this->config->app('owner_email', trim($email));
}
protected function database()
{
$this->io->writeln('<fg=blue>Database Settings</>');
$this->seperator();
$driver_choices = array_keys($this->config->app('database_installers'));
$default_driver = $this->config->app('database_driver');
$driver = $this->io->choice('Choose a database driver', $driver_choices, $default_driver);
$this->config->app('database_driver', $driver);
$this->io->writeln('<fg=red>Special Characters Are Not Working At This Time!</>');
$db_root_pass = $this->question('DB Server Root Password', '');
$this->config->app('dbrootpass', $db_root_pass);
$db = $this->question('UNIT3D DB Name', 'unit3d');
$this->config->app('db', $db);
$dbuser = $this->question('UNIT3D DB User', 'unit3d');
$this->config->app('dbuser', $dbuser);
$this->io->writeln('<fg=red>Special Characters Are Not Working At This Time!</>');
$dbpass = $this->question('UNIT3D DB Password', '');
$this->config->app('dbpass', $dbpass);
}
protected function chat()
{
$this->io->writeln('<fg=blue>Chat Settings</>');
$this->seperator();
$port = $this->question('Chat Listening Port', '8443');
$this->config->app('echo-port', $port);
}
protected function apiKeys()
{
$this->io->writeln('<fg=blue>API Keys</>');
$this->seperator();
$this->io->writeln('<fg=magenta>Obtaining an TMDB Key</>:');
$this->io->listing([
'Visit <fg=cyan>https://www.themoviedb.org/</>',
'Create Free Account',
'Visit <fg=cyan>https://www.themoviedb.org/settings/api</>'
]);
$key = $this->question('TMDB Key', '');
$this->config->app('tmdb-key', $key);
}
protected function mail()
{
$this->io->writeln('<fg=blue>Mail Settings</>');
$this->io->writeln('(Used for things like invites, registration, ect.)');
$this->seperator();
$this->io->writeln('<fg=blue>/* You will need a provider like Resend. */</>');
$this->io->writeln('<fg=cyan>https://resend.com</>');
$this->io->writeln('Ref: <fg=cyan>https://laravel.com/docs/11.x/mail#introduction</>');
$value = $this->io->choice('Mail Driver', [
"smtp",
"sendmail",
"mailgun",
"mandrill",
"ses",
"sparkpost",
"log",
"array"
], 'smtp');
$this->config->app('mail_driver', $value);
$value = $this->question('Mail Host', '');
$this->config->app('mail_host', $value);
$value = $this->question('Mail Port', '587');
$this->config->app('mail_port', $value);
$value = $this->question('Mail Username', '');
$this->config->app('mail_username', $value);
$value = $this->question('Mail Password', '');
$this->config->app('mail_password', $value);
$value = $this->question('Mail From Name', '');
$this->config->app('mail_from_name', $value);
}
}

View File

@ -0,0 +1,160 @@
<?php
namespace App\Installer\UNIT3D;
use App\Installer\BaseInstaller;
class Unit3dSetup extends BaseInstaller
{
public function handle()
{
$this->clone();
$this->env();
$this->perms();
$this->crons();
$this->setup();
}
protected function clone()
{
$this->io->writeln('<fg=blue>Cloning Source Files</>');
$this->seperator();
$install_dir = $this->config->os('install_dir');
$url = $this->config->app('repository');
if (is_dir($install_dir)) {
$this->process(["rm -rf $install_dir"]);
}
$this->process(["git clone $url $install_dir"]);
if (!is_dir($install_dir)) {
$this->throwError('Something went wrong with the cloning process. Please report this bug!');
}
}
protected function env()
{
$this->io->writeln("\n\n<fg=blue>Preparing the '.env' File</>");
$this->seperator();
$install_dir = $this->config->os('install_dir');
if (file_exists("$install_dir/.env")) {
$this->process(["rm $install_dir/.env"]);
}
$this->createFromStub(
[
'{{PROTOCOL}}' => $this->config->app('ssl') == 'yes' ? 'https' : 'http',
'{{FQDN}}' => $this->config->app('hostname'),
'{{DBDRIVER}}' => strtolower($this->config->app('database_driver')),
'{{DB}}' => $this->config->app('db'),
'{{DBUSER}}' => $this->config->app('dbuser'),
'{{DBPASS}}' => $this->config->app('dbpass'),
'{{OWNER}}' => $this->config->app('owner'),
'{{OWNEREMAIL}}' => $this->config->app('owner_email'),
'{{OWNERPASSWORD}}' => $this->config->app('password'),
'{{TMDBAPIKEY}}' => $this->config->app('tmdb-key'),
'{{MAILDRIVER}}' => $this->config->app('mail_driver'),
'{{MAILHOST}}' => $this->config->app('mail_host'),
'{{MAILPORT}}' => $this->config->app('mail_port'),
'{{MAILUSERNAME}}' => $this->config->app('mail_username'),
'{{MAILPASSWORD}}' => $this->config->app('mail_password'),
'{{MAILFROMNAME}}' => $this->config->app('mail_from_name')
],
'../.env.stub',
"$install_dir/.env"
);
$this->io->writeln('<fg=green>OK</>');
}
protected function perms()
{
$this->io->writeln("\n<fg=blue>Setting Permissions</>");
$this->seperator();
$install_dir = $this->config->os('install_dir');
$web_user = $this->config->os('web-user');
$this->process([
"chown -R $web_user:$web_user /etc/letsencrypt",
"chown -R $web_user:$web_user " . dirname($install_dir),
"find $install_dir -type d -exec chmod 0775 '{}' + -or -type f -exec chmod 0664 '{}' +",
"chmod 750 $install_dir/artisan",
"chmod 640 $install_dir/.env"
]);
}
protected function setup()
{
$this->io->writeln("\n\n<fg=blue>Setting Up Web Site</>");
$this->seperator();
$install_dir = $this->config->os('install_dir');
$fqdn = $this->config->app('hostname');
$web_user = $this->config->os('web-user');
$echo_port = $this->config->app('echo-port');
$protocol = $this->config->app('ssl') == 'yes' ? 'https' : 'http';
$this->createFromStub([
'{{FQDN}}' => $fqdn,
'{{PORT}}' => $echo_port,
'{{PROTOCOL}}' => $protocol,
], '../laravel-echo-server.stub', '/var/www/html/laravel-echo-server.json');
$this->process([
"chown -R $web_user:$web_user $install_dir/laravel-echo-server.json",
]);
$this->createFromStub([
'{{INSTALLDIR}}' => $install_dir,
'{{WEBUSER}}' => $web_user,
], 'supervisor/app.conf', '/etc/supervisor/conf.d/unit3d.conf');
$this->process([
'supervisorctl reread',
'supervisorctl update',
'supervisorctl reload'
]);
$www_cmds = [
'laravel-echo-server client:add',
'composer install -q',
'bun install',
'bun run build',
'php artisan key:generate',
'php artisan migrate --seed',
'php artisan auto:email-blacklist-update',
'php artisan test:email'
];
foreach ($www_cmds as $cmd) {
$this->process([
"su $web_user -s /bin/bash --command=\"cd $install_dir && $cmd\""
], true);
}
$this->io->writeln(' ');
}
protected function crons()
{
$this->io->writeln("\n\n<fg=blue>Setting Up Crontabs</>");
$this->seperator();
$install_dir = $this->config->os('install_dir');
$this->process([
"(crontab -l ; echo \"* * * * * php $install_dir/artisan schedule:run >> /dev/null 2>&1\") | crontab -"
]);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Installer\Utilities;
use App\Installer\BaseInstaller;
class RestartService extends BaseInstaller
{
public function handle($target = null, $service = null)
{
if ($target === null || $service === null) {
$this->throwError(
"Null Argument supplied in handle method for RestartService::class.
Expecting string value"
);
}
$this->salt->execute($target, 'service.restart', [$service]);
}
}

49
src/Resources/.env.stub Normal file
View File

@ -0,0 +1,49 @@
APP_ENV=prod
APP_KEY=
APP_DEBUG=false
APP_URL={{PROTOCOL}}://{{FQDN}}
VITE_ECHO_ADDRESS={{PROTOCOL}}://{{FQDN}}:8443
LOG_CHANNEL=daily
LOG_LEVEL=info
DB_CONNECTION={{DBDRIVER}}
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE={{DB}}
DB_USERNAME={{DBUSER}}
DB_PASSWORD={{DBPASS}}
BROADCAST_CONNECTION=redis
CACHE_STORE=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
SESSION_CONNECTION=session
SESSION_LIFETIME=120
SESSION_SECURE_COOKIE=true
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER={{MAILDRIVER}}
MAIL_HOST={{MAILHOST}}
MAIL_PORT={{MAILPORT}}
MAIL_USERNAME={{MAILUSERNAME}}
MAIL_PASSWORD={{MAILPASSWORD}}
MAIL_ENCRYPTION=TLS
MAIL_FROM_NAME={{MAILFROMNAME}}
MAIL_FROM_ADDRESS={{OWNEREMAIL}}
DEFAULT_OWNER_NAME={{OWNER}}
DEFAULT_OWNER_EMAIL={{OWNEREMAIL}}
DEFAULT_OWNER_PASSWORD={{OWNERPASSWORD}}
TMDB_API_KEY={{TMDBAPIKEY}}
TWITCH_CLIENT_ID=
TWITCH_CLIENT_SECRET=
TRACKER_KEY=
TRACKER_HOST=127.0.0.1
TRACKER_PORT=6969

14
src/Resources/intro.stub Normal file
View File

@ -0,0 +1,14 @@
<fg=white>
__ __ _ __ ____ ______ _____ ____ ______ _ __ ______ __ _ __ _
/ / / // | / // _//_ __/|__ / / __ \ / ____/____ ____ ___ ____ ___ __ __ ____ (_)/ /_ __ __ / ____/____/ /(_)/ /_ (_)____ ____
/ / / // |/ / / / / / /_ < / / / /______ / / / __ \ / __ `__ \ / __ `__ \ / / / // __ \ / // __// / / /______ / __/ / __ // // __// // __ \ / __ \
/ /_/ // /| /_/ / / / ___/ // /_/ //_____// /___ / /_/ // / / / / // / / / / // /_/ // / / // // /_ / /_/ //_____// /___ / /_/ // // /_ / // /_/ // / / /
\____//_/ |_//___/ /_/ /____//_____/ \____/ \____//_/ /_/ /_//_/ /_/ /_/ \__,_//_/ /_//_/ \__/ \__, / /_____/ \__,_//_/ \__//_/ \____//_/ /_/
/____/
</>
*-----------------------------------------------*
| Copyright: 2017-2024 |
| Founder: HDVinnie |
| Maintainers: HDVinnie, Roardom & Community |
*-----------------------------------------------*

View File

@ -0,0 +1,27 @@
{
"authHost": "{{PROTOCOL}}://{{FQDN}}",
"authEndpoint": "/broadcasting/auth",
"clients": [],
"database": "redis",
"databaseConfig": {
"redis": {},
"sqlite": {
"databasePath": "/database/laravel-echo-server.sqlite"
}
},
"devMode": false,
"host": null,
"port": "{{PORT}}",
"protocol": "{{PROTOCOL}}",
"socketio": {},
"sslCertPath": "/etc/letsencrypt/live/{{FQDN}}/cert.pem",
"sslKeyPath": "/etc/letsencrypt/live/{{FQDN}}/privkey.pem",
"sslCertChainPath": "/etc/letsencrypt/live/{{FQDN}}/fullchain.pem",
"sslPassphrase": "",
"apiOriginAllow": {
"allowCors": false,
"allowOrigin": "",
"allowMethods": "",
"allowHeaders": ""
}
}

View File

@ -0,0 +1,2 @@
[client]
password={{PASSWORD}}

View File

@ -0,0 +1,40 @@
[client]
port=3306
socket=/var/run/mysqld/mysqld.sock
[mysqld_safe]
socket=/var/run/mysqld/mysqld.sock
[mysqld]
user=mysql
pid-file=/var/run/mysqld/mysqld.pid
socket=/var/run/mysqld/mysqld.sock
port=3306
basedir=/usr
datadir=/var/lib/mysql
tmpdir=/tmp
lc-messages-dir=/usr/share/mysql
log_error=/var/log/mysql/error.log
symbolic-links=0
skip-external-locking
key_buffer_size = 256M
max_allowed_packet = 32M
table_open_cache = 256
sort_buffer_size = 1M
read_buffer_size = 1M
read_rnd_buffer_size = 4M
myisam_sort_buffer_size = 64M
thread_cache_size = 8
#innodb_use_native_aio = 0
innodb_file_per_table
max_connections=200
max_user_connections=50
wait_timeout=10
interactive_timeout=50
long_query_time=5
!includedir /etc/mysql/conf.d/

View File

@ -0,0 +1,40 @@
[client]
port=3306
socket=/var/run/mysqld/mysqld.sock
[mysqld_safe]
socket=/var/run/mysqld/mysqld.sock
[mysqld]
user=mysql
pid-file=/var/run/mysqld/mysqld.pid
socket=/var/run/mysqld/mysqld.sock
port=3306
basedir=/usr
datadir=/var/lib/mysql
tmpdir=/tmp
lc-messages-dir=/usr/share/mysql
log_error=/var/log/mysql/error.log
symbolic-links=0
skip-external-locking
key_buffer_size = 16M
max_allowed_packet = 16M
table_open_cache = 64
sort_buffer_size = 512K
net_buffer_length = 8K
read_buffer_size = 256K
read_rnd_buffer_size = 512K
myisam_sort_buffer_size = 8M
#innodb_use_native_aio = 0
innodb_file_per_table
max_connections=70
max_user_connections=30
wait_timeout=10
interactive_timeout=50
long_query_time=5
!includedir /etc/mysql/conf.d/

View File

@ -0,0 +1,40 @@
[client]
port=3306
socket=/var/run/mysqld/mysqld.sock
[mysqld_safe]
socket=/var/run/mysqld/mysqld.sock
[mysqld]
user=mysql
pid-file=/var/run/mysqld/mysqld.pid
socket=/var/run/mysqld/mysqld.sock
port=3306
basedir=/usr
datadir=/var/lib/mysql
tmpdir=/tmp
lc-messages-dir=/usr/share/mysql
log_error=/var/log/mysql/error.log
symbolic-links=0
skip-external-locking
key_buffer_size = 16K
max_allowed_packet = 1M
table_open_cache = 4
sort_buffer_size = 64K
read_buffer_size = 256K
read_rnd_buffer_size = 256K
net_buffer_length = 2K
thread_stack = 240K
#innodb_use_native_aio = 0
innodb_file_per_table
max_connections=30
max_user_connections=20
wait_timeout=10
interactive_timeout=50
long_query_time=5
!includedir /etc/mysql/conf.d/

View File

@ -0,0 +1,37 @@
server {
listen 80 default_server;
root /var/www/html/public;
index index.php;
server_name {{FQDN}} www.{{FQDN}};
location ~* \.(jpg|jpeg|png|gif|ico|svg)$ {
expires 365d;
}
location ~* \.(gif|png|jpg|jpeg|svg|css|js|ico)$ {
valid_referers none blocked {{FQDN}} www.{{FQDN}};
if ($invalid_referer) {
return 403;
}
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/{{FQDN}}.sock;
}
location /index.php {
return 301 {{FQDN}};
}
location ~* ^.*(\.(?:git|svn|htaccess|github))$ {
return 403;
}
}

View File

@ -0,0 +1,16 @@
[{{FQDN}}]
listen = /var/run/php/{{FQDN}}.sock
listen.backlog = 511
listen.owner = {{WEBUSER}}
listen.group = {{WEBUSER}}
listen.mode=0660
user = {{WEBUSER}}
group = {{WEBUSER}}
pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 10
pm.max_requests = 0
chdir = /

View File

@ -0,0 +1,20 @@
[program:unit3d-queue]
process_name=%(program_name)s_%(process_num)02d
command=php {{INSTALLDIR}}/artisan queue:work --tries=1 --max-jobs=1000 --max-time=3600
startsecs = 0
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=4
redirect_stderr=true
stopwaitsecs=3600
[program:unit3d-chat-server]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/node /usr/bin/laravel-echo-server start --dir={{INSTALLDIR}}
autostart=true
autorestart=true
user={{WEBUSER}}
numprocs=1

View File

@ -0,0 +1,88 @@
<?php
namespace App\Traits;
trait ConsoleTools
{
/**
* Formats a warning output
*
* @param string $msg
*/
protected function warning($msg)
{
$this->io->writeln("<bg=white;fg=yellow>[Warning] $msg</>\n");
}
/**
* Returns the installer error and exits the installer
*
* @param array|string $error
*/
protected function throwError($error = 'Unknown Error ...')
{
$this->io->writeln("<fg=red>$error</>");
exit(1);
}
protected function dump($var)
{
$this->io->writeln('<fg=red>---VAR DUMP---');
var_dump($var);
$this->io->writeln('--------------</>');
}
/**
* Displays a the intro
*/
protected function displayIntro()
{
$stub = file_get_contents(__DIR__ . '/../Resources/intro.stub');
$this->io->text($stub);
}
/**
* Writes a seperator
*/
protected function seperator()
{
$this->io->writeln(str_repeat('=', 80));
}
/**
* Formats a header
*
* @param string $text
*/
protected function head($text)
{
if ($text !== null) {
$this->io->writeln("\n<fg=blue>" . str_repeat('=', 80));
$this->io->writeln(' ' . $text . str_repeat(' ', (76 - strlen($text))));
$this->io->writeln(str_repeat('=', 80) . "</>\n");
}
}
protected function success()
{
$this->io->writeln("\n<fg=white;bg=green>[OK] Done!" . str_repeat(' ', 70) . "</>");
}
protected function question($question, $default = '')
{
do {
$answer = $this->io->ask($question, $default);
$valid = ($answer !== '' && strpos($answer, ' ') === false);
if (!$valid) {
$this->warning('Cannot be empty or contain spaces!');
}
} while (!$valid);
return trim($answer);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Tests;
use App\BaseTestCase;
class MasterBootstrapperTest extends BaseTestCase
{
/** @test */
public function it_installed()
{
}
}

7
tests/bootstrap.php Normal file
View File

@ -0,0 +1,7 @@
<?php
// include the composer autoloader
require_once __DIR__ . '/../vendor/autoload.php';
// include our custom helpers
require_once __DIR__ . '/../src/Helpers/helpers.php';

74
tools/colors.sh Normal file
View File

@ -0,0 +1,74 @@
#!/usr/bin/env bash
# Reset
Color_Off='\033[0m' # Text Reset
# Regular Colors
Black='\033[0;30m' # Black
Red='\033[0;31m' # Red
Green='\033[0;32m' # Green
Yellow='\033[0;33m' # Yellow
Blue='\033[0;34m' # Blue
Purple='\033[0;35m' # Purple
Cyan='\033[0;36m' # Cyan
White='\033[0;37m' # White
# Bold
BBlack='\033[1;30m' # Black
BRed='\033[1;31m' # Red
BGreen='\033[1;32m' # Green
BYellow='\033[1;33m' # Yellow
BBlue='\033[1;34m' # Blue
BPurple='\033[1;35m' # Purple
BCyan='\033[1;36m' # Cyan
BWhite='\033[1;37m' # White
# Underline
UBlack='\033[4;30m' # Black
URed='\033[4;31m' # Red
UGreen='\033[4;32m' # Green
UYellow='\033[4;33m' # Yellow
UBlue='\033[4;34m' # Blue
UPurple='\033[4;35m' # Purple
UCyan='\033[4;36m' # Cyan
UWhite='\033[4;37m' # White
# Background
On_Black='\033[40m' # Black
On_Red='\033[41m' # Red
On_Green='\033[42m' # Green
On_Yellow='\033[43m' # Yellow
On_Blue='\033[44m' # Blue
On_Purple='\033[45m' # Purple
On_Cyan='\033[46m' # Cyan
On_White='\033[47m' # White
# High Intensity
IBlack='\033[0;90m' # Black
IRed='\033[0;91m' # Red
IGreen='\033[0;92m' # Green
IYellow='\033[0;93m' # Yellow
IBlue='\033[0;94m' # Blue
IPurple='\033[0;95m' # Purple
ICyan='\033[0;96m' # Cyan
IWhite='\033[0;97m' # White
# Bold High Intensity
BIBlack='\033[1;90m' # Black
BIRed='\033[1;91m' # Red
BIGreen='\033[1;92m' # Green
BIYellow='\033[1;93m' # Yellow
BIBlue='\033[1;94m' # Blue
BIPurple='\033[1;95m' # Purple
BICyan='\033[1;96m' # Cyan
BIWhite='\033[1;97m' # White
# High Intensity backgrounds
On_IBlack='\033[0;100m' # Black
On_IRed='\033[0;101m' # Red
On_IGreen='\033[0;102m' # Green
On_IYellow='\033[0;103m' # Yellow
On_IBlue='\033[0;104m' # Blue
On_IPurple='\033[0;105m' # Purple
On_ICyan='\033[0;106m' # Cyan
On_IWhite='\033[0;107m' # White

138
ubuntu.sh Executable file
View File

@ -0,0 +1,138 @@
#!/usr/bin/env bash
export DEBIAN_FRONTEND=noninteractive
export DEBCONF_NOWARNINGS=yes
source tools/colors.sh
rm -rf /var/lib/dpkg/lock
rm -rf /var/cache/debconf/*.*
echo -e "\n\n$Purple Preparing Environment For The Installer ... $Color_Off"
echo "============================================="
check_locale() {
echo -e "\n$Cyan Setting UTF8 ...$Color_Off"
apt-get -qq update
apt-get install -qq apt-utils language-pack-en-base > /dev/null
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
apt-get install -qq software-properties-common > /dev/null
echo -e "$IGreen OK $Color_Off"
}
# Adds PPA's
add_ppa() {
echo -e "\n$Cyan Adding PPA Repositories ... $Color_Off"
for ppa in "$@"; do
add-apt-repository -y $ppa > /dev/null 2>&1
check $? "Adding $ppa Failed!"
done
echo -e "$IGreen OK $Color_Off"
}
# Installs Environment Prerequisites
add_pkgs() {
# Update apt
echo -e "\n$Cyan Updating Packages ... $Color_Off"
apt-get -qq update > /dev/null
check $? "Updating packages Failed!"
echo -e "$IGreen OK $Color_Off"
# PHP
echo -e "\n$Cyan Installing PHP ... $Color_Off"
apt-get -qq install curl php-pear php8.3-common php8.3-cli php8.3-fpm php8.3-{redis,bcmath,curl,dev,gd,igbinary,intl,mbstring,mysql,opcache,readline,xml,zip} > /dev/null
check $? "Installing PHP Failed!"
echo -e "$IGreen OK $Color_Off"
# Redis
echo -e "\n$Cyan Installing Redis ... $Color_Off"
curl -fsSL https://packages.redis.io/gpg | sudo gpg --yes --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list > /dev/null
apt-get -qq update > /dev/null
apt-get -qq install redis > /dev/null
echo -e "$IGreen OK $Color_Off"
# Symlink Redis and Enable
echo -e "\n$Cyan Symlink and Enabling Redis ... $Color_Off"
systemctl -q enable --now redis-server
systemctl is-active --quiet redis-server && echo -e "$IGreen OK $Color_Off"||echo -e "$IRed NOK $Color_Off"
# PHP Redis
echo -e "\n$Cyan Installing PHP Redis ... $Color_Off"
printf "\n" | pecl install redis > /dev/null
echo -e "$IGreen OK $Color_Off"
# Update Dependencies
echo -e "\n$Cyan Updating Dependencies ... $Color_Off"
apt-get -qq upgrade > /dev/null
echo -e "$IGreen OK $Color_Off"
# Bun
echo -e "\n$Cyan Installing Bun ... $Color_Off"
apt-get -qq install unzip > /dev/null
curl -fsSL https://bun.sh/install | bash >/dev/null 2>&1
mv /root/.bun/bin/bun /usr/local/bin/
chmod a+x /usr/local/bin/bun
. ~/.bashrc
echo -e "$IGreen OK $Color_Off"
}
# Installs Composer
install_composer() {
echo -e "\n$Cyan Installing Composer ... $Color_Off"
php -r "readfile('http://getcomposer.org/installer');" | sudo php -- --install-dir=/usr/bin/ --filename=composer > /dev/null
check $? "Installing Composer Failed!"
echo -e "$IGreen OK $Color_Off"
}
# Adds installer packages
installer_pkgs() {
echo -e "\n$Cyan Adding Installer Packages ... $Color_Off"
composer install -qq > /dev/null 2>&1
check $? "Adding Installer Packages Failed!"
echo -e "$IGreen OK $Color_Off"
}
# Checks the returned code
check() {
if [ $1 -ne 0 ]; then
echo -e "$Red Error: $2 \n Please try re-running the script via 'sudo ./install.sh' $Color_Off"
exit $1
fi
}
check_locale
add_ppa ppa:ondrej/php
add_pkgs
install_composer
installer_pkgs
echo -e "\n$Purple Launching The Installer ... $Color_Off"
echo "============================================="
php artisan install