├── .gitignore ├── awes-io ├── LICENSE.md ├── composer.json ├── src ├── NewCommand.php ├── DemoCommand.php ├── KeyCommand.php ├── TokenCommand.php └── BaseCommand.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /awes-io: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new AwesIO\Installer\Console\NewCommand); 12 | $app->add(new AwesIO\Installer\Console\DemoCommand); 13 | $app->add(new AwesIO\Installer\Console\KeyCommand); 14 | $app->add(new AwesIO\Installer\Console\TokenCommand); 15 | 16 | $app->run(); 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Taylor Otwell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awes-io/installer", 3 | "description": "AwesIO application installer.", 4 | "keywords": ["laravel", "awes-io"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Awescode GmbH", 9 | "email": "info@awescode.de", 10 | "homepage": "https://www.awescode.de" 11 | }, 12 | { 13 | "name": "Ivan Slesarenko", 14 | "email": "info@boomdraw.com", 15 | "homepage": "https://boomdraw.com", 16 | "role": "Developer" 17 | }, 18 | { 19 | "name": "Galymzhan Begimov", 20 | "email": "begimov@gmail.com", 21 | "homepage": "https://github.com/begimov" 22 | } 23 | ], 24 | "autoload": { 25 | "psr-4": { 26 | "AwesIO\\Installer\\Console\\": "src/" 27 | } 28 | }, 29 | "require": { 30 | "ext-zip": "*", 31 | "guzzlehttp/guzzle": "~6.0", 32 | "symfony/console": "~3.0|~4.0", 33 | "symfony/filesystem": "~3.0|~4.0", 34 | "symfony/process": "~3.0|~4.0", 35 | "begimov/thanks": "^1.1" 36 | }, 37 | "bin": [ 38 | "awes-io" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/NewCommand.php: -------------------------------------------------------------------------------- 1 | setName('new') 20 | ->setDescription('Create a new AwesIO application') 21 | ->addArgument('name', InputArgument::OPTIONAL) 22 | ->addOption('key', 'k', InputOption::VALUE_OPTIONAL, 'Adds PackageKit CDN key to .env') 23 | ->addOption('token', 't', InputOption::VALUE_OPTIONAL, 'Adds PackageKit token to composer.json') 24 | ->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces install even if the directory already exists'); 25 | } 26 | 27 | /** 28 | * Download the temporary Zip to the given file. 29 | * 30 | * @param string $zipFile 31 | * @return $this 32 | */ 33 | protected function download($zipFile) 34 | { 35 | $response = (new Client)->get('https://github.com/awes-io/awes-io/archive/master.zip'); 36 | 37 | file_put_contents($zipFile, $response->getBody()); 38 | 39 | return $this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DemoCommand.php: -------------------------------------------------------------------------------- 1 | setName('demo') 20 | ->setDescription('Create a new AwesIO demo application') 21 | ->addArgument('name', InputArgument::OPTIONAL) 22 | ->addOption('key', 'k', InputOption::VALUE_OPTIONAL, 'Adds PackageKit CDN key to .env') 23 | ->addOption('token', 't', InputOption::VALUE_OPTIONAL, 'Adds PackageKit token to composer.json') 24 | ->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces install even if the directory already exists'); 25 | } 26 | 27 | /** 28 | * Download the temporary Zip to the given file. 29 | * 30 | * @param string $zipFile 31 | * @return $this 32 | */ 33 | protected function download($zipFile) 34 | { 35 | $response = (new Client)->get('https://github.com/awes-io/demo/archive/master.zip'); 36 | 37 | file_put_contents($zipFile, $response->getBody()); 38 | 39 | return $this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/KeyCommand.php: -------------------------------------------------------------------------------- 1 | setName('key') 21 | ->setDescription('Add PackageKit CDN key to .env') 22 | ->addOption('path', 'p', InputOption::VALUE_OPTIONAL, 'Adds path to .env dir') 23 | ->addOption('key', 'k', InputOption::VALUE_OPTIONAL, 'Adds PackageKit CDN key to .env'); 24 | } 25 | 26 | /** 27 | * Execute the command. 28 | * 29 | * @param \Symfony\Component\Console\Input\InputInterface $input 30 | * @param \Symfony\Component\Console\Output\OutputInterface $output 31 | * @return void 32 | */ 33 | protected function execute(InputInterface $input, OutputInterface $output) 34 | { 35 | $this->input = $input; 36 | $this->output = $output; 37 | 38 | $directory = $input->getOption('path'); 39 | if (empty($directory)) { 40 | $directory = getcwd(); 41 | } elseif (substr($directory, 0, 1) !== '/') { 42 | $directory = getcwd() . '/' . $directory; 43 | } 44 | 45 | $output->writeln('Writing PackageKit cdn key to .env...'); 46 | 47 | if (!$this->key = $input->getOption('key')) { 48 | $this->getKey(); 49 | } 50 | 51 | $this->setKey($directory); 52 | 53 | $output->writeln('PackageKit cdn key is set.'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | [![Analytics](https://ga-beacon.appspot.com/UA-134431636-1/awes-io/installer)](https://github.com/awes-io/installer) 4 | 5 | ## Server Requirements 6 | 7 | * PHP >= 7.1.3 8 | * OpenSSL PHP Extension 9 | * PDO PHP Extension 10 | * Mbstring PHP Extension 11 | * Tokenizer PHP Extension 12 | * XML PHP Extension 13 | * Ctype PHP Extension 14 | * JSON PHP Extension 15 | * BCMath PHP Extension 16 | 17 | ## Installing AwesIO 18 | 19 | [Awes.IO](https://www.awes.io) utilizes [Composer](https://getcomposer.org/) to manage its dependencies. So, before using [Awes.IO](https://www.awes.io), make sure you have Composer installed on your machine. 20 | 21 | First, download the [Awes.IO](https://www.awes.io) installer using Composer: 22 | ```bash 23 | composer global require awes-io/installer 24 | ``` 25 | 26 | Make sure to place composer's system-wide vendor bin directory in your `$PATH` so the awes-io executable can be located by your system. This directory exists in different locations based on your operating system; however, some common locations include: 27 | 28 | - macOS: `$HOME/.composer/vendor/bin`, command: `export PATH=~/.composer/vendor/bin:$PATH` 29 | - GNU / Linux Distributions: `$HOME/.config/composer/vendor/bin` 30 | - Windows: `%USERPROFILE%\AppData\Roaming\Composer\vendor\bin` 31 | 32 | Once installed, the `awes-io new` command will create a fresh [Awes.IO](https://www.awes.io) installation in the directory you specify. For instance, `awes-io new crm` will create a directory named `crm` containing a fresh [Awes.IO](https://www.awes.io) installation with all of [Awes.IO](https://www.awes.io)'s dependencies already installed: 33 | 34 | ```bash 35 | awes-io new crm 36 | ``` 37 | 38 | To create demo project use `awes-io demo` command 39 | 40 | ```bash 41 | awes-io demo crm 42 | ``` 43 | -------------------------------------------------------------------------------- /src/TokenCommand.php: -------------------------------------------------------------------------------- 1 | setName('token') 21 | ->setDescription('Add PackageKit token to composer.json') 22 | ->addOption('path', 'p', InputOption::VALUE_OPTIONAL, 'Adds path to composer.json dir') 23 | ->addOption('token', 't', InputOption::VALUE_OPTIONAL, 'Adds PackageKit token to composer.json'); 24 | } 25 | 26 | /** 27 | * Execute the command. 28 | * 29 | * @param \Symfony\Component\Console\Input\InputInterface $input 30 | * @param \Symfony\Component\Console\Output\OutputInterface $output 31 | * @return void 32 | */ 33 | protected function execute(InputInterface $input, OutputInterface $output) 34 | { 35 | $this->input = $input; 36 | $this->output = $output; 37 | 38 | $directory = $input->getOption('path'); 39 | if (empty($directory)) { 40 | $directory = getcwd(); 41 | } elseif (substr($directory, 0, 1) !== '/') { 42 | $directory = getcwd() . '/' . $directory; 43 | } 44 | 45 | $output->writeln('Writing PackageKit token to composer.json...'); 46 | 47 | if (!$this->token = $input->getOption('token')) { 48 | $this->getToken(); 49 | } 50 | 51 | $this->setToken($directory); 52 | 53 | $output->writeln('PackageKit token is set.'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/BaseCommand.php: -------------------------------------------------------------------------------- 1 | client = new Client([ 50 | 'base_uri' => 'https://repo.pkgkit.com', 51 | ]); 52 | 53 | parent::__construct(); 54 | } 55 | 56 | /** 57 | * Execute the command. 58 | * 59 | * @param \Symfony\Component\Console\Input\InputInterface $input 60 | * @param \Symfony\Component\Console\Output\OutputInterface $output 61 | * @return void 62 | */ 63 | protected function execute(InputInterface $input, OutputInterface $output) 64 | { 65 | $this->input = $input; 66 | $this->output = $output; 67 | 68 | if (!extension_loaded('zip')) { 69 | throw new RuntimeException('The Zip PHP extension is not installed. Please install it and try again.'); 70 | } 71 | 72 | $directory = ($input->getArgument('name')) ? getcwd() . '/' . $input->getArgument('name') : getcwd(); 73 | 74 | if (!$input->getOption('force')) { 75 | $this->verifyApplicationDoesntExist($directory); 76 | } 77 | 78 | $output->writeln('Crafting application...'); 79 | 80 | $this->download($zipFile = $this->makeFilename()) 81 | ->extract($zipFile, $directory) 82 | ->prepareWritableDirectories($directory, $output) 83 | ->cleanUp($zipFile); 84 | 85 | if (!$this->key = $input->getOption('key')) { 86 | $this->getKey(); 87 | } 88 | 89 | if (!$this->token = $input->getOption('token')) { 90 | $this->getToken(); 91 | } 92 | 93 | $this->setToken($directory); 94 | 95 | $composer = $this->findComposer(); 96 | 97 | $commands = [ 98 | $composer . ' install --no-scripts', 99 | $composer . ' run-script post-root-package-install', 100 | $composer . ' run-script post-create-project-cmd', 101 | $composer . ' run-script post-autoload-dump', 102 | ]; 103 | 104 | if ($input->getOption('no-ansi')) { 105 | $commands = array_map(function ($value) { 106 | return $value . ' --no-ansi'; 107 | }, $commands); 108 | } 109 | 110 | if ($input->getOption('quiet')) { 111 | $commands = array_map(function ($value) { 112 | return $value . ' --quiet'; 113 | }, $commands); 114 | } 115 | 116 | $process = new Process(implode(' && ', $commands), $directory, null, null, null); 117 | 118 | if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { 119 | $process->setTty(true); 120 | } 121 | 122 | $process->run(function ($type, $line) use ($output) { 123 | $output->write($line); 124 | }); 125 | 126 | $this->setKey($directory); 127 | 128 | $helper = $this->getHelper('question'); 129 | $question = new ConfirmationQuestion('Do you want to support us by giving Github star? [Y/n]', true); 130 | if ($helper->ask($input, $output, $question)) { 131 | (new Process('composer thanks', $directory))->run(); 132 | } 133 | 134 | $output->writeln('Application ready! Build something amazing.'); 135 | } 136 | 137 | /** 138 | * Get PackageKit token 139 | */ 140 | protected function getToken() 141 | { 142 | $helper = $this->getHelper('question'); 143 | $question = new Question("Please enter your API-TOKEN from Package Kit \n"); 144 | $this->token = $helper->ask($this->input, $this->output, $question); 145 | } 146 | 147 | /** 148 | * Get PackageKit key 149 | */ 150 | protected function getKey() 151 | { 152 | $helper = $this->getHelper('question'); 153 | $question = new Question("Please enter your PKGKIT_CDN_KEY. You can get it for free on https://www.pkgkit.com/awes-io/create \n"); 154 | $this->key = $helper->ask($this->input, $this->output, $question); 155 | } 156 | 157 | /** 158 | * Check PackageKit composer token 159 | * 160 | * @return bool 161 | */ 162 | protected function checkToken(): bool 163 | { 164 | if (empty($this->token)) { 165 | return false; 166 | } 167 | 168 | try { 169 | $response = $this->client->get('validate?token=' . $this->token); 170 | return $response->getStatusCode() == 200; 171 | } catch (RequestException $e) { 172 | if ($e->hasResponse() && $e->getResponse()->getStatusCode() == 403) { 173 | return false; 174 | } 175 | throw $e; 176 | } 177 | } 178 | 179 | /** 180 | * Check PackageKit cdn key 181 | * 182 | * @return bool 183 | */ 184 | protected function checkKey(): bool 185 | { 186 | if (empty($this->key)) { 187 | return false; 188 | } 189 | 190 | try { 191 | $response = $this->client->get('check?key=' . $this->key); 192 | return $response->getStatusCode() == 200; 193 | } catch (RequestException $e) { 194 | if ($e->hasResponse() && $e->getResponse()->getStatusCode() == 403) { 195 | return false; 196 | } 197 | throw $e; 198 | } 199 | } 200 | 201 | /** 202 | * Set PackageKit token to composer.json 203 | * 204 | * @param string $directory 205 | */ 206 | protected function setToken(string $directory) 207 | { 208 | while (!$this->checkToken()) { 209 | $this->output->writeln('Token is invalid.'); 210 | $this->getToken(); 211 | } 212 | 213 | $file = $directory . '/composer.json'; 214 | $composer = file_get_contents($file); 215 | $composer = json_decode($composer, true); 216 | 217 | foreach ($composer['repositories'] as &$repository) { 218 | if ($repository['url'] === 'https://repo.pkgkit.com') { 219 | $repository['options']['http']['header'] = ['API-TOKEN: ' . $this->token]; 220 | } 221 | } 222 | 223 | $composer = json_encode($composer, JSON_PRETTY_PRINT + JSON_UNESCAPED_SLASHES); 224 | file_put_contents($file, $composer); 225 | } 226 | 227 | /** 228 | * Set PackageKit key to .env 229 | * 230 | * @param string $directory 231 | */ 232 | protected function setKey(string $directory) 233 | { 234 | $var = 'PKGKIT_CDN_KEY'; 235 | while (!$this->checkKey()) { 236 | $this->output->writeln('Key is invalid.'); 237 | $this->getKey(); 238 | } 239 | 240 | $file = $directory . '/.env'; 241 | $env = file_get_contents($file); 242 | if (strpos($env, $var) == false) { 243 | $env = $env . "{$var}={$this->key}\n"; 244 | } else { 245 | $val = explode($var, $env, 2)[1]; 246 | $val = explode("\n", $val, 2)[0]; 247 | $val = $var . $val; 248 | $env = str_replace($val, "{$var}={$this->key}", $env); 249 | } 250 | file_put_contents($file, $env); 251 | } 252 | 253 | /** 254 | * Verify that the application does not already exist. 255 | * 256 | * @param string $directory 257 | * @return void 258 | */ 259 | protected function verifyApplicationDoesntExist($directory) 260 | { 261 | if ((is_dir($directory) || is_file($directory)) && $directory != getcwd()) { 262 | throw new RuntimeException('Application already exists!'); 263 | } 264 | } 265 | 266 | /** 267 | * Generate a random temporary filename. 268 | * 269 | * @return string 270 | */ 271 | protected function makeFilename() 272 | { 273 | return getcwd() . '/awes-io_' . md5(time() . uniqid()) . '.zip'; 274 | } 275 | 276 | /** 277 | * Extract the Zip file into the given directory. 278 | * 279 | * @param string $zipFile 280 | * @param string $directory 281 | * @return $this 282 | */ 283 | protected function extract(string $zipFile, string $directory): self 284 | { 285 | $tmpdir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . md5(time() . uniqid()); 286 | 287 | $archive = new ZipArchive; 288 | 289 | $archive->open($zipFile); 290 | 291 | $archive->extractTo($tmpdir); 292 | 293 | $this->moveExtracted($tmpdir, $directory); 294 | 295 | $archive->close(); 296 | 297 | return $this; 298 | } 299 | 300 | /** 301 | * Move extracted from temp dir to project dir 302 | * 303 | * @param string $directory 304 | */ 305 | protected function moveExtracted(string $tmpdir, string $directory): void 306 | { 307 | $filesystem = new Filesystem; 308 | 309 | $subdir = array_diff(scandir($tmpdir), ['..', '.']); 310 | if (count($subdir) == 1) { 311 | $subdir = $tmpdir . DIRECTORY_SEPARATOR . array_shift($subdir); 312 | } else { 313 | $subdir = $tmpdir; 314 | } 315 | 316 | $filesystem->mirror($subdir, $directory, null, ['override' => true, 'delete' => false]); 317 | $filesystem->remove($tmpdir); 318 | } 319 | 320 | /** 321 | * Clean-up the Zip file. 322 | * 323 | * @param string $zipFile 324 | * @return $this 325 | */ 326 | protected function cleanUp($zipFile): self 327 | { 328 | @chmod($zipFile, 0777); 329 | 330 | @unlink($zipFile); 331 | 332 | return $this; 333 | } 334 | 335 | /** 336 | * Make sure the storage and bootstrap cache directories are writable. 337 | * 338 | * @param string $appDirectory 339 | * @param \Symfony\Component\Console\Output\OutputInterface $output 340 | * @return $this 341 | */ 342 | protected function prepareWritableDirectories($appDirectory, OutputInterface $output) 343 | { 344 | $filesystem = new Filesystem; 345 | 346 | try { 347 | $filesystem->chmod($appDirectory . DIRECTORY_SEPARATOR . "bootstrap/cache", 0755, 0000, true); 348 | $filesystem->chmod($appDirectory . DIRECTORY_SEPARATOR . "storage", 0755, 0000, true); 349 | } catch (IOExceptionInterface $e) { 350 | $output->writeln('You should verify that the "storage" and "bootstrap/cache" directories are writable.'); 351 | } 352 | 353 | return $this; 354 | } 355 | 356 | /** 357 | * Get the composer command for the environment. 358 | * 359 | * @return string 360 | */ 361 | protected function findComposer() 362 | { 363 | $composerPath = getcwd() . '/composer.phar'; 364 | 365 | if (file_exists($composerPath)) { 366 | return '"' . PHP_BINARY . '" ' . $composerPath; 367 | } 368 | 369 | return 'composer'; 370 | } 371 | } 372 | --------------------------------------------------------------------------------