├── public ├── favicon.ico ├── css │ └── .gitignore ├── js │ └── .gitignore ├── robots.txt ├── mix-manifest.json └── fonts │ └── vendor │ └── bootstrap-sass │ └── bootstrap │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── database ├── .gitignore ├── migrations │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2017_04_28_160656_create_project_users_table.php │ ├── 2017_04_03_193949_create_server_providers_table.php │ ├── 2017_04_26_163353_create_source_providers_table.php │ ├── 2017_03_31_172929_create_environments_table.php │ ├── 2017_05_10_203142_create_alerts_table.php │ ├── 2017_06_14_151436_create_stack_databases_table.php │ ├── 2017_04_27_193631_create_jobs_table.php │ ├── 2017_05_08_175021_create_ip_addresses_table.php │ ├── 2017_03_31_170538_create_projects_table.php │ ├── 2017_07_29_024811_create_certificates_table.php │ ├── 2017_05_10_150545_create_failed_jobs_table.php │ ├── 2017_08_02_155914_create_storage_providers_table.php │ ├── 2017_06_11_154558_create_daemon_generations_table.php │ ├── 2017_08_10_151936_create_hooks_table.php │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2017_07_31_191305_create_stack_tasks_table.php │ ├── 2017_04_10_190135_create_balancers_table.php │ └── 2017_04_18_203351_create_tasks_table.php ├── factories │ ├── CertificateFactory.php │ ├── StackTaskFactory.php │ ├── SourceProviderFactory.php │ ├── IpAddressFactory.php │ ├── HookFactory.php │ ├── EnvironmentFactory.php │ ├── DatabaseRestoreFactory.php │ ├── BalancerFactory.php │ ├── ServerProviderFactory.php │ ├── ServerTaskFactory.php │ ├── ServerDeploymentFactory.php │ ├── ProjectFactory.php │ ├── DatabaseBackupFactory.php │ ├── WebServerFactory.php │ ├── DeploymentFactory.php │ ├── StorageProviderFactory.php │ ├── TaskFactory.php │ ├── DatabaseFactory.php │ ├── WorkerServerFactory.php │ ├── AppServerFactory.php │ ├── UserFactory.php │ └── StackFactory.php └── seeds │ └── DatabaseSeeder.php ├── bootstrap ├── cache │ └── .gitignore └── autoload.php ├── storage ├── logs │ └── .gitignore ├── app │ ├── keys │ │ └── .gitignore │ ├── public │ │ └── .gitignore │ ├── scripts │ │ └── .gitignore │ └── .gitignore └── framework │ ├── cache │ └── .gitignore │ ├── testing │ └── .gitignore │ ├── views │ └── .gitignore │ ├── sessions │ └── .gitignore │ └── .gitignore ├── routes ├── schedule.php ├── web.php ├── channels.php └── console.php ├── resources ├── views │ ├── scripts │ │ ├── scheduler │ │ │ ├── stop.blade.php │ │ │ └── start.blade.php │ │ ├── caddy-configuration │ │ │ ├── redirect.blade.php │ │ │ ├── proxy.blade.php │ │ │ └── app.blade.php │ │ ├── daemon │ │ │ ├── start.blade.php │ │ │ ├── pause.blade.php │ │ │ ├── unpause.blade.php │ │ │ ├── restart.blade.php │ │ │ ├── stop.blade.php │ │ │ └── activate.blade.php │ │ ├── provisionable │ │ │ ├── removeKey.blade.php │ │ │ └── addKey.blade.php │ │ ├── database │ │ │ ├── provision.blade.php │ │ │ ├── network.blade.php │ │ │ └── backup.blade.php │ │ ├── php │ │ │ ├── www.conf │ │ │ ├── cli.ini │ │ │ └── fpm.ini │ │ ├── server │ │ │ └── sync.blade.php │ │ ├── tools │ │ │ ├── chown.blade.php │ │ │ └── callback.blade.php │ │ ├── node │ │ │ └── install.blade.php │ │ ├── worker │ │ │ └── provision.blade.php │ │ ├── storage-provider-configuration │ │ │ └── s3.blade.php │ │ ├── balancer │ │ │ ├── sync.blade.php │ │ │ └── provision.blade.php │ │ ├── deployment │ │ │ └── activate.blade.php │ │ └── caddy │ │ │ └── install.blade.php │ ├── mail │ │ ├── balancer │ │ │ └── provisioned.blade.php │ │ ├── stack │ │ │ └── provisioned.blade.php │ │ └── database │ │ │ └── provisioned.blade.php │ ├── home.blade.php │ └── projects │ │ └── index.blade.php ├── assets │ ├── sass │ │ ├── app.scss │ │ └── _variables.scss │ └── js │ │ ├── root.js │ │ ├── app.js │ │ └── components │ │ └── ProjectList.vue └── lang │ └── en │ ├── pagination.php │ ├── auth.php │ └── passwords.php ├── app ├── Policies │ ├── ServerPolicy.php │ ├── AppServerPolicy.php │ ├── WebServerPolicy.php │ ├── WorkerServerPolicy.php │ ├── DatabaseRestorePolicy.php │ ├── BalancerPolicy.php │ ├── DatabaseBackupPolicy.php │ ├── EnvironmentPolicy.php │ ├── DatabasePolicy.php │ └── StackPolicy.php ├── Exceptions │ ├── AlreadyDeployingException.php │ ├── StackProvisioningTimeout.php │ ├── ProvisioningTimeout.php │ └── ManifestNotFoundException.php ├── MemoizesMethods.php ├── Contracts │ ├── HasStack.php │ ├── Alertable.php │ ├── YamlParser.php │ ├── StackDefinition.php │ └── DnsProvider.php ├── Collaborator.php ├── DaemonGeneration.php ├── Listeners │ ├── CreateAlert.php │ ├── ResetDeploymentStatus.php │ ├── CheckPendingDeployments.php │ ├── TrimAlertsForProject.php │ └── UpdateLastAlertTimestampForCollaborators.php ├── Http │ ├── Controllers │ │ ├── ScheduleController.php │ │ ├── Controller.php │ │ ├── API │ │ │ ├── ServerConfigurationController.php │ │ │ ├── OwnedProjectsController.php │ │ │ ├── ProjectSizeController.php │ │ │ ├── ServerProviderSizeController.php │ │ │ ├── ServerProviderRegionController.php │ │ │ ├── CancelsDeployments.php │ │ │ ├── SshBalancerController.php │ │ │ ├── LoginController.php │ │ │ ├── SshDatabaseController.php │ │ │ ├── LastDeploymentController.php │ │ │ ├── StackServerController.php │ │ │ ├── CallbackController.php │ │ │ ├── EnvironmentHookController.php │ │ │ ├── StackSshServerController.php │ │ │ ├── StackTaskController.php │ │ │ ├── DatabaseTransferController.php │ │ │ └── MaintenancedStackController.php │ │ ├── ProjectController.php │ │ └── Auth │ │ │ ├── ForgotPasswordController.php │ │ │ ├── LoginController.php │ │ │ └── ResetPasswordController.php │ ├── Middleware │ │ ├── EncryptCookies.php │ │ ├── VerifyCsrfToken.php │ │ ├── TrimStrings.php │ │ └── RedirectIfAuthenticated.php │ └── Requests │ │ └── CreateHookRequest.php ├── Callbacks │ ├── MarkAsProvisioned.php │ ├── Dispatch.php │ ├── CheckBuild.php │ ├── CheckActivation.php │ ├── CheckServerTask.php │ ├── CheckDatabaseRestore.php │ └── CheckDatabaseBackup.php ├── Jobs │ ├── StopDaemons.php │ ├── PauseDaemons.php │ ├── ProvisionAppServer.php │ ├── ProvisionWebServer.php │ ├── StartDaemons.php │ ├── UnpauseDaemons.php │ ├── ProvisionWorkerServer.php │ ├── RestartDaemons.php │ ├── PruneTasks.php │ ├── PruneStackTasks.php │ ├── HandlesStackProvisioningFailures.php │ ├── ProvisionBalancer.php │ ├── ProvisionDatabase.php │ ├── RunStackTask.php │ ├── SyncServers.php │ ├── SyncStackNetwork.php │ ├── SyncBalancers.php │ ├── SyncServer.php │ ├── AddDnsRecord.php │ ├── SyncBalancer.php │ └── FinishTask.php ├── Scripts │ ├── Sleep.php │ ├── WriteDummyFile.php │ ├── StopDaemons.php │ ├── PauseDaemons.php │ ├── StartDaemons.php │ ├── UnpauseDaemons.php │ ├── ProvisioningScript.php │ ├── GetCurrentDirectory.php │ ├── GetAptLockStatus.php │ ├── ProvisionBalancer.php │ ├── Script.php │ ├── DaemonScript.php │ ├── WritesCaddyServerConfigurations.php │ └── ProvisionDatabase.php ├── Services │ └── LocalYamlParser.php ├── DeterminesAge.php ├── Providers │ └── BroadcastServiceProvider.php ├── WebServerRecordCreator.php ├── WorkerServerRecordCreator.php ├── IpAddress.php ├── StorageProviderClientFactory.php ├── Events │ ├── StackTaskFailed.php │ ├── StackTaskFinished.php │ ├── StackTaskRunning.php │ ├── ServerTaskFailed.php │ ├── ServerTaskFinished.php │ ├── DatabaseBackupFailed.php │ ├── DatabaseBackupFinished.php │ ├── DatabaseBackupRunning.php │ ├── DatabaseRestoreFailed.php │ ├── DatabaseRestoreRunning.php │ ├── DatabaseRestoreFinished.php │ ├── ServerDeploymentBuilt.php │ ├── ServerDeploymentFailed.php │ ├── ServerDeploymentActivated.php │ ├── ProjectShared.php │ ├── ProjectUnshared.php │ ├── AlertCreated.php │ ├── StackProvisioning.php │ ├── DeploymentActivating.php │ ├── DeploymentBuilding.php │ └── StackDeleting.php ├── ServerProviderClientFactory.php ├── SourceProviderClientFactory.php ├── Prunable.php ├── AppServerRecordCreator.php ├── ShellOutput.php ├── StackMetadata.php ├── TaskFactory.php ├── ShellResponse.php ├── Mail │ ├── StackProvisioned.php │ ├── BalancerProvisioned.php │ └── DatabaseProvisioned.php ├── Certificate.php ├── Console │ └── Kernel.php ├── Rules │ ├── StackIsPromotable.php │ └── ValidAppServerStack.php └── Alert.php ├── .gitattributes ├── .gitignore ├── Makefile ├── tests ├── Unit │ └── ExampleTest.php ├── Fakes │ └── FakeTask.php ├── CreatesApplication.php └── Feature │ ├── SyncBalancerScriptTest.php │ ├── ScheduleControllerTest.php │ ├── StoreDatabaseBackupScriptTest.php │ ├── ProvisionDatabaseScriptTest.php │ ├── ProvisionAppServerScriptTest.php │ ├── ProvisionBalancerScriptTest.php │ ├── BuildScriptTest.php │ ├── ActivateScriptTest.php │ ├── ReportHelperTest.php │ ├── EnvironmentTest.php │ ├── RestoreDatabaseBackupScriptTest.php │ ├── DeleteServerOnProviderJobTest.php │ ├── ServerConfigurationControllerTest.php │ ├── ValidDatabaseNameRuleTest.php │ └── HandlesStackProvisioningFailuresTest.php ├── readme.md ├── webpack.mix.js ├── .env.example ├── helpers.php ├── config └── view.php └── package.json /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /public/css/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /public/js/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /storage/app/keys/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /public/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/app.js": "/js/app.js" 3 | } -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !keys/ 3 | !public/ 4 | !scripts/ 5 | !.gitignore 6 | -------------------------------------------------------------------------------- /routes/schedule.php: -------------------------------------------------------------------------------- 1 | id !!}" 5 | sudo supervisorctl add daemon-{!! $generation->id !!} 6 | -------------------------------------------------------------------------------- /public/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkeash/laravel-cloud/HEAD/public/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkeash/laravel-cloud/HEAD/public/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkeash/laravel-cloud/HEAD/public/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clarkeash/laravel-cloud/HEAD/public/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /resources/views/scripts/daemon/pause.blade.php: -------------------------------------------------------------------------------- 1 | 2 | # Pause The Daemons 3 | 4 | echo "Pausing Supervisor Group: daemon-{!! $generation->id !!}" 5 | sudo supervisorctl signal USR2 daemon-{!! $generation->id !!}:* 6 | -------------------------------------------------------------------------------- /resources/views/scripts/daemon/unpause.blade.php: -------------------------------------------------------------------------------- 1 | 2 | # Unpause The Daemons 3 | 4 | echo "Unpausing Supervisor Group: daemon-{!! $generation->id !!}" 5 | sudo supervisorctl signal CONT daemon-{!! $generation->id !!}:* 6 | -------------------------------------------------------------------------------- /resources/views/scripts/daemon/restart.blade.php: -------------------------------------------------------------------------------- 1 | 2 | # Write Fresh Supervisor Configuration 3 | 4 | {!! $script->daemonConfiguration() !!} 5 | 6 | # Reload Daemons & Stop & Remove Old Ones 7 | 8 | {!! $script->activateDaemons() !!} 9 | -------------------------------------------------------------------------------- /resources/views/scripts/provisionable/removeKey.blade.php: -------------------------------------------------------------------------------- 1 | 2 | # Remove Key & Regenerate Keys File 3 | 4 | rm -f /home/cloud/.ssh/authorized_keys.d/{{ $name }} 5 | 6 | cat /home/cloud/.ssh/authorized_keys.d/* > /home/cloud/.ssh/authorized_keys 7 | -------------------------------------------------------------------------------- /app/MemoizesMethods.php: -------------------------------------------------------------------------------- 1 | /home/cloud/.ssh/authorized_keys.d/{{ $name }} << EOF 5 | # {{ $name }} 6 | {{ $key }} 7 | 8 | EOF 9 | 10 | cat /home/cloud/.ssh/authorized_keys.d/* > /home/cloud/.ssh/authorized_keys 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test share 2 | 3 | test: 4 | php vendor/bin/phpunit 5 | 6 | share: 7 | ngrok http "cloud.dev:80" -subdomain=laravel-cloud -host-header=rewrite 8 | 9 | fresh: 10 | php artisan migrate:fresh 11 | php artisan passport:install --force 12 | rm storage/app/keys/* 13 | 14 | default: test 15 | -------------------------------------------------------------------------------- /resources/views/scripts/caddy-configuration/proxy.blade.php: -------------------------------------------------------------------------------- 1 | {!! $domain !!} { 2 | {!! $tls !!} 3 | 4 | redir 301 { 5 | if {scheme} is http 6 | / https://{host}{uri} 7 | } 8 | 9 | proxy / {!! implode(' ', $proxyTo) !!} { 10 | transparent 11 | insecure_skip_verify 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Unit/ExampleTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /resources/views/scripts/database/provision.blade.php: -------------------------------------------------------------------------------- 1 | 2 | export DEBIAN_FRONTEND=noninteractive 3 | 4 | # Run Base Script 5 | 6 | @include('scripts.provisionable.base') 7 | 8 | # Run Database Installation Script 9 | 10 | @include('scripts.database.install') 11 | 12 | # Make Sure Directories Have Correct Permissions 13 | 14 | @include('scripts.tools.chown') 15 | -------------------------------------------------------------------------------- /resources/views/scripts/php/www.conf: -------------------------------------------------------------------------------- 1 | [www] 2 | user = cloud 3 | group = cloud 4 | listen = 127.0.0.1:9000 5 | 6 | listen.owner = cloud 7 | listen.group = cloud 8 | listen.mode = 0666 9 | 10 | pm = dynamic 11 | pm.max_children = 24 12 | pm.start_servers = 2 13 | pm.min_spare_servers = 1 14 | pm.max_spare_servers = 3 15 | 16 | request_terminate_timeout = 60 17 | -------------------------------------------------------------------------------- /app/Collaborator.php: -------------------------------------------------------------------------------- 1 | 'json', 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/DaemonGeneration.php: -------------------------------------------------------------------------------- 1 | belongsTo(Stack::class, 'stack_id'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Listeners/CreateAlert.php: -------------------------------------------------------------------------------- 1 | toAlert(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Controllers/ScheduleController.php: -------------------------------------------------------------------------------- 1 | id !!}" 5 | 6 | nohup bash -c "sudo supervisorctl stop daemon-{!! $generation->id !!}:* && \ 7 | sudo supervisorctl remove daemon-{!! $generation->id !!} && \ 8 | rm /etc/supervisor/conf.d/daemon-{!! $generation->id !!}.conf" > /dev/null 2>&1 & 9 | -------------------------------------------------------------------------------- /resources/views/scripts/server/sync.blade.php: -------------------------------------------------------------------------------- 1 | 2 | # Write Caddyfile For Server 3 | 4 | cat > /home/cloud/Caddyfile << EOF 5 | {!! $script->actualDomainConfiguration() !!} 6 | {!! $script->vanityDomainConfiguration() !!} 7 | EOF 8 | 9 | # Make Sure Directories Have Correct Permissions 10 | 11 | @include('scripts.tools.chown') 12 | 13 | # Restart Caddy 14 | 15 | supervisorctl signal USR1 caddy 16 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | project->name }}" project. 5 | The server's credentials are: 6 | 7 | **Server Name:** {{ $balancer->name }} 8 | 9 | **Sudo Password:** {{ $balancer->sudo_password }} 10 | 11 | Thanks,
12 | {{ config('app.name') }} 13 | @endcomponent 14 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | stack()->resetDeploymentStatus(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | provisionable) { 18 | $task->provisionable->markAsProvisioned(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | deployment); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Listeners/CheckPendingDeployments.php: -------------------------------------------------------------------------------- 1 | deployment->stack->deployPending(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Scripts/Sleep.php: -------------------------------------------------------------------------------- 1 | deployment); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Jobs/ProvisionAppServer.php: -------------------------------------------------------------------------------- 1 | provisionable = $provisionable; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Jobs/ProvisionWebServer.php: -------------------------------------------------------------------------------- 1 | provisionable = $provisionable; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Jobs/StartDaemons.php: -------------------------------------------------------------------------------- 1 | deployment); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Services/LocalYamlParser.php: -------------------------------------------------------------------------------- 1 | ranInBackground = true; 19 | 20 | return $this; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Laravel Cloud was an attempt to build a "Forge Pro" for applications that needed more robust load balancing and scaling features. 2 | 3 | This was eventually replaced by Laravel Vapor; however, I am open sourcing the code for review purposes since it is the only significant Laravel application I have written that I am able to open source. 4 | 5 | Again, this code is only for review. This code may not be used or deployed for any purpose without my consent. 6 | -------------------------------------------------------------------------------- /app/Jobs/UnpauseDaemons.php: -------------------------------------------------------------------------------- 1 | deployment); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Jobs/ProvisionWorkerServer.php: -------------------------------------------------------------------------------- 1 | provisionable = $provisionable; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /resources/views/scripts/node/install.blade.php: -------------------------------------------------------------------------------- 1 | 2 | # Install NodeJS 3 | 4 | curl --silent --location https://deb.nodesource.com/setup_8.x | bash - 5 | apt-get update 6 | sudo apt-get install -y --force-yes nodejs 7 | 8 | # Install Yarn 9 | 10 | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - 11 | echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list 12 | sudo apt-get update && sudo apt-get install yarn 13 | -------------------------------------------------------------------------------- /resources/views/scripts/php/cli.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | engine = On 3 | error_reporting = E_ALL 4 | expose_php = On 5 | log_errors = On 6 | max_execution_time = 0 7 | max_input_time = -1 8 | memory_limit = 512M 9 | output_buffering = 4096 10 | register_argc_argv = Off 11 | request_order = "GP" 12 | short_open_tag = Off 13 | variables_order = "GPCS" 14 | 15 | [CLI Server] 16 | cli_server.color = On 17 | 18 | [Date] 19 | date.timezone = UTC 20 | 21 | [Assertion] 22 | zend.assertions = -1 23 | -------------------------------------------------------------------------------- /app/DeterminesAge.php: -------------------------------------------------------------------------------- 1 | {$attribute}->lte(Carbon::now()->subMinutes(10)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /resources/views/mail/stack/provisioned.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | # Stack Created 3 | 4 | A new stack has been created for the "{{ $stack->environment->project->name }}" project. 5 | The stack's credentials are: 6 | 7 | **Stack Name:** {{ $stack->name }} 8 | 9 | @foreach ($stack->allServers() as $server) 10 | **{{ $server->name }} Sudo Password:** {{ $server->sudo_password }} 11 | 12 | @endforeach 13 | 14 | Thanks,
15 | {{ config('app.name') }} 16 | @endcomponent 17 | -------------------------------------------------------------------------------- /resources/views/scripts/scheduler/start.blade.php: -------------------------------------------------------------------------------- 1 | 2 | # Start The Scheduled Tasks 3 | 4 | rm -f /etc/cron.d/schedule-* 5 | 6 | @foreach ($deployment->schedule() as $name => $options) 7 | cat > /etc/cron.d/schedule-{{ $name }} << EOF 8 | SHELL=/bin/sh 9 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 10 | 11 | {{ $options['frequency'] }} {{ $options['user'] ?? 'cloud' }} {{ $options['command'] }} >> /home/cloud/schedule-{{ $name }}.log 2>&1 12 | EOF 13 | @endforeach 14 | -------------------------------------------------------------------------------- /resources/views/scripts/worker/provision.blade.php: -------------------------------------------------------------------------------- 1 | 2 | export DEBIAN_FRONTEND=noninteractive 3 | 4 | # Run Base Script 5 | 6 | @include('scripts.provisionable.base') 7 | 8 | # Run PHP Installation Script 9 | 10 | @include('scripts.php.install') 11 | 12 | # Make Sure Directories Have Correct Permissions 13 | 14 | @include('scripts.tools.chown') 15 | 16 | # Run The Custom Scripts 17 | 18 | @foreach ($customScripts as $customScript) 19 | {!! $customScript !!} 20 | 21 | @endforeach 22 | -------------------------------------------------------------------------------- /app/Scripts/WriteDummyFile.php: -------------------------------------------------------------------------------- 1 | /root/dummy'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /resources/views/scripts/storage-provider-configuration/s3.blade.php: -------------------------------------------------------------------------------- 1 | 2 | mkdir -p /home/cloud/.aws 3 | 4 | # Write The Credentials File 5 | 6 | cat > /home/cloud/.aws/credentials << EOF 7 | [default] 8 | aws_access_key_id = {!! $provider->meta['key'] !!} 9 | aws_secret_access_key = {!! $provider->meta['secret'] !!} 10 | EOF 11 | 12 | # Write The Configuration File 13 | 14 | cat > /home/cloud/.aws/config << EOF 15 | [default] 16 | output = json 17 | region = {!! $provider->meta['region'] !!} 18 | EOF 19 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 19 | 20 | return $app; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | 5 |
6 |
7 |
8 |
Dashboard
9 | 10 |
11 | You are logged in! 12 |
13 |
14 |
15 |
16 | 17 | @endsection 18 | -------------------------------------------------------------------------------- /app/WebServerRecordCreator.php: -------------------------------------------------------------------------------- 1 | stack->webServers(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /resources/views/projects/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 | 8 |
9 |
10 | 11 |
12 |
13 | 14 |
15 |
16 |
17 | @endsection 18 | -------------------------------------------------------------------------------- /resources/views/mail/database/provisioned.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | # Database Created 3 | 4 | A new database server has been created for the "{{ $database->project->name }}" project. 5 | The server's credentials are: 6 | 7 | **Server Name:** {{ $database->name }} 8 | 9 | **Database Username:** {{ $database->username }} 10 | 11 | **Database Password:** {{ $database->password }} 12 | 13 | **Server Sudo Password:** {{ $database->sudo_password }} 14 | 15 | Thanks,
16 | {{ config('app.name') }} 17 | @endcomponent 18 | -------------------------------------------------------------------------------- /resources/views/scripts/tools/callback.blade.php: -------------------------------------------------------------------------------- 1 | 2 | # Rewrite Script Into Another File 3 | 4 | cat > {!! $path !!} << '{!! $token !!}' 5 | {!! $task->script !!} 6 | 7 | {!! $token !!} 8 | 9 | # Invoke Script File 10 | 11 | @if ($task->timeout() > 0) 12 | timeout {!! $task->timeout() !!}s bash {!! $path !!} 13 | @else 14 | bash {!! $path !!} 15 | @endif 16 | 17 | # Call Home With ID & Status Code 18 | 19 | STATUS=$? 20 | 21 | curl --insecure {!! url('/api/callback/'.hashid_encode($task->id)) !!}?exit_code=$STATUS > /dev/null 2>&1 22 | -------------------------------------------------------------------------------- /app/WorkerServerRecordCreator.php: -------------------------------------------------------------------------------- 1 | stack->workerServers(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Jobs/RestartDaemons.php: -------------------------------------------------------------------------------- 1 | deployment->deployable->createDaemonGeneration(); 18 | 19 | return new RestartDaemonsScript($this->deployment->fresh()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/ServerConfigurationController.php: -------------------------------------------------------------------------------- 1 | authorize('view', $stack); 19 | 20 | $stack->syncServers(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /resources/views/scripts/caddy-configuration/app.blade.php: -------------------------------------------------------------------------------- 1 | {!! $domain !!} { 2 | {!! $tls !!} 3 | 4 | redir 301 { 5 | if {scheme} is http 6 | / https://{!! str_replace([':80', ':443'], '', $domain) !!}{uri} 7 | } 8 | 9 | root {!! $root !!} 10 | 11 | @if (! $index) 12 | header / X-Robots-Tag "noindex" 13 | @endif 14 | 15 | gzip 16 | fastcgi / 127.0.0.1:9000 php 17 | 18 | limits { 19 | body 50mb 20 | } 21 | 22 | rewrite { 23 | to {path} {path}/ /index.php?{query} 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Listeners/TrimAlertsForProject.php: -------------------------------------------------------------------------------- 1 | alert->project->alerts()->get(); 18 | 19 | if (count($alerts) > 30) { 20 | $alerts->slice(30 - count($alerts))->each->delete(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/OwnedProjectsController.php: -------------------------------------------------------------------------------- 1 | user()->projects->sortBy('name')->reject->archived; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Scripts/StopDaemons.php: -------------------------------------------------------------------------------- 1 | deployment->deployable->name})"; 15 | } 16 | 17 | /** 18 | * Get the name of the script to run. 19 | * 20 | * @return string 21 | */ 22 | public function scriptName() 23 | { 24 | return 'scripts.daemon.stop'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Scripts/PauseDaemons.php: -------------------------------------------------------------------------------- 1 | deployment->deployable->name})"; 15 | } 16 | 17 | /** 18 | * Get the name of the script to run. 19 | * 20 | * @return string 21 | */ 22 | public function scriptName() 23 | { 24 | return 'scripts.daemons.pause'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Scripts/StartDaemons.php: -------------------------------------------------------------------------------- 1 | deployment->deployable->name})"; 15 | } 16 | 17 | /** 18 | * Get the name of the script to run. 19 | * 20 | * @return string 21 | */ 22 | public function scriptName() 23 | { 24 | return 'scripts.daemon.start'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Listeners/UpdateLastAlertTimestampForCollaborators.php: -------------------------------------------------------------------------------- 1 | affectedIds())->update( 20 | ['last_alert_received_at' => new DateTime] 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Scripts/UnpauseDaemons.php: -------------------------------------------------------------------------------- 1 | deployment->deployable->name})"; 15 | } 16 | 17 | /** 18 | * Get the name of the script to run. 19 | * 20 | * @return string 21 | */ 22 | public function scriptName() 23 | { 24 | return 'scripts.daemon.unpause'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/views/scripts/balancer/sync.blade.php: -------------------------------------------------------------------------------- 1 | 2 | # Rewrite Caddyfile 3 | 4 | @if (count($script->balancer->project->allStacks()) > 0) 5 | cat > /home/cloud/Caddyfile << EOF 6 | {!! $script->actualDomainConfiguration() !!} 7 | {!! $script->vanityDomainConfiguration() !!} 8 | EOF 9 | @else 10 | cat > /home/cloud/Caddyfile << EOF 11 | :80 { 12 | root /home/cloud/status 13 | tls off 14 | } 15 | EOF 16 | @endif 17 | 18 | # Make Sure Directories Have Correct Permissions 19 | 20 | @include('scripts.tools.chown') 21 | 22 | # Restart Caddy 23 | 24 | supervisorctl signal USR1 caddy 25 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/ProjectSizeController.php: -------------------------------------------------------------------------------- 1 | serverProvider->sizes(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Exceptions/StackProvisioningTimeout.php: -------------------------------------------------------------------------------- 1 | stack = $stack; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | projects->contains($database->project); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const { mix } = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel application. By default, we are compiling the Sass 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | mix.js('resources/assets/js/app.js', 'public/js') 15 | .sass('resources/assets/sass/app.scss', 'public/css'); 16 | -------------------------------------------------------------------------------- /app/Policies/BalancerPolicy.php: -------------------------------------------------------------------------------- 1 | projects->contains($balancer->project); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/ServerProviderSizeController.php: -------------------------------------------------------------------------------- 1 | sizes(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Scripts/ProvisioningScript.php: -------------------------------------------------------------------------------- 1 | provisionable = $provisionable; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/ServerProviderRegionController.php: -------------------------------------------------------------------------------- 1 | regions(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/IpAddress.php: -------------------------------------------------------------------------------- 1 | morphTo(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bootstrap/autoload.php: -------------------------------------------------------------------------------- 1 | { 25 | this.projects = response.data; 26 | }); 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /app/Jobs/PruneTasks.php: -------------------------------------------------------------------------------- 1 | subDays(21)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Policies/DatabaseBackupPolicy.php: -------------------------------------------------------------------------------- 1 | projects->contains($backup->database->project); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return redirect('/home'); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /resources/views/scripts/database/network.blade.php: -------------------------------------------------------------------------------- 1 | 2 | # Rebuild UFW Rules 3 | 4 | @foreach ($previousIpAddresses as $ipAddress) 5 | ufw delete allow from {!! $ipAddress !!} to any port 3306 6 | ufw delete allow from {!! $ipAddress !!} to any port 6379 7 | ufw delete allow from {!! $ipAddress !!} to any port 11211 8 | ufw delete allow from {!! $ipAddress !!} to any port 11300 9 | @endforeach 10 | 11 | @foreach ($ipAddresses as $ipAddress) 12 | ufw allow from {!! $ipAddress !!} to any port 3306 13 | ufw allow from {!! $ipAddress !!} to any port 6379 14 | ufw allow from {!! $ipAddress !!} to any port 11211 15 | ufw allow from {!! $ipAddress !!} to any port 11300 16 | @endforeach 17 | -------------------------------------------------------------------------------- /app/Jobs/PruneStackTasks.php: -------------------------------------------------------------------------------- 1 | subDays(14)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Scripts/GetCurrentDirectory.php: -------------------------------------------------------------------------------- 1 | stack->resetDeploymentStatus(); 18 | 19 | if (! $deployment->cancel()) { 20 | return response()->json([ 21 | 'deployment' => ['We were unable to cancel this deployment.'], 22 | ], 400); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Exceptions/ProvisioningTimeout.php: -------------------------------------------------------------------------------- 1 | projectId() ?? 'Deleted'; 19 | 20 | $type = get_class($provisionable); 21 | 22 | return new static("Timed out while provisioning [{$type}] server for project [{$project}]"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Controllers/ProjectController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 17 | } 18 | 19 | /** 20 | * Show the projects dashboard. 21 | * 22 | * @return \Illuminate\Http\Response 23 | */ 24 | public function index(Request $request) 25 | { 26 | return view('projects.index', [ 27 | 'projects' => $request->user()->projects 28 | ]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /resources/views/scripts/php/fpm.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | engine = On 3 | error_reporting = E_ALL 4 | expose_php = Off 5 | log_errors = On 6 | memory_limit = 512M 7 | output_buffering = 4096 8 | post_max_size = 50M 9 | register_argc_argv = Off 10 | request_order = "GP" 11 | short_open_tag = Off 12 | upload_max_filesize = 50M 13 | variables_order = "GPCS" 14 | 15 | [CLI Server] 16 | cli_server.color = On 17 | 18 | [Date] 19 | date.timezone = UTC 20 | 21 | [Assertion] 22 | zend.assertions = -1 23 | 24 | [opcache] 25 | opcache.enable = 1 26 | opcache.interned_strings_buffer = 64 27 | opcache.max_accelerated_files = 30000 28 | opcache.memory_consumption = 512 29 | opcache.save_comments = 1 30 | opcache.validate_timestamps = 0 31 | -------------------------------------------------------------------------------- /app/Policies/EnvironmentPolicy.php: -------------------------------------------------------------------------------- 1 | projects->contains($environment->project) || 24 | $environment->creator->id == $user->id; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /app/StorageProviderClientFactory.php: -------------------------------------------------------------------------------- 1 | type) { 19 | case 'S3': 20 | return new S3($provider); 21 | default: 22 | throw new InvalidArgumentException("Invalid provider type."); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Feature/SyncBalancerScriptTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 20 | } 21 | 22 | 23 | public function test_script_can_be_rendered() 24 | { 25 | $balancer = factory(Balancer::class)->create(); 26 | 27 | $script = new SyncBalancer($balancer); 28 | 29 | $this->assertNotNull($script->script()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2017_04_28_160656_create_project_users_table.php: -------------------------------------------------------------------------------- 1 | unsignedInteger('project_id'); 18 | $table->unsignedInteger('user_id'); 19 | $table->text('permissions'); 20 | 21 | $table->unique(['project_id', 'user_id']); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Events/StackTaskFailed.php: -------------------------------------------------------------------------------- 1 | task = $task; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Jobs/HandlesStackProvisioningFailures.php: -------------------------------------------------------------------------------- 1 | stack->delete(); 19 | } catch (Exception $e) { 20 | report($e); 21 | } 22 | 23 | $this->stack->environment->project->alerts()->create([ 24 | 'type' => 'StackProvisioningFailed', 25 | 'exception' => (string) $exception, 26 | 'meta' => [], 27 | ]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/ServerProviderClientFactory.php: -------------------------------------------------------------------------------- 1 | type) { 19 | case 'DigitalOcean': 20 | return new DigitalOcean($provider); 21 | default: 22 | throw new InvalidArgumentException("Invalid provider type."); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/SourceProviderClientFactory.php: -------------------------------------------------------------------------------- 1 | type) { 19 | case 'GitHub': 20 | return new GitHub($source); 21 | default: 22 | throw new InvalidArgumentException("Invalid source control provider type."); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 18 | }); 19 | 20 | Broadcast::channel('stack.{stack}', function ($user, Stack $stack) { 21 | return $user->can('view', $stack); 22 | }); 23 | -------------------------------------------------------------------------------- /app/Events/StackTaskFinished.php: -------------------------------------------------------------------------------- 1 | task = $task; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Events/StackTaskRunning.php: -------------------------------------------------------------------------------- 1 | task = $task; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Events/ServerTaskFailed.php: -------------------------------------------------------------------------------- 1 | task = $task; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Events/ServerTaskFinished.php: -------------------------------------------------------------------------------- 1 | task = $task; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /resources/views/scripts/balancer/provision.blade.php: -------------------------------------------------------------------------------- 1 | # Run Base Script 2 | 3 | @include('scripts.provisionable.base') 4 | 5 | # Install Caddy 6 | 7 | @include('scripts.caddy.install') 8 | 9 | # Create Caddy Directories 10 | 11 | mkdir /home/cloud/status 12 | 13 | # Create Base Caddy Configuration 14 | 15 | cat > /home/cloud/status/index.html << EOF 16 | OK 17 | EOF 18 | 19 | cat > /home/cloud/Caddyfile << EOF 20 | :80 { 21 | root /home/cloud/status 22 | tls off 23 | } 24 | EOF 25 | 26 | # Make Sure Directories Have Correct Permissions 27 | 28 | @include('scripts.tools.chown') 29 | 30 | # Update The Supervisor Configuration 31 | 32 | supervisorctl reread 33 | supervisorctl update 34 | 35 | # Start Caddy 36 | 37 | supervisorctl start caddy 38 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/SshBalancerController.php: -------------------------------------------------------------------------------- 1 | authorize('view', $request->project); 19 | 20 | return $request->project->balancers() 21 | ->with('address') 22 | ->get() 23 | ->filter 24 | ->canSsh($request->user()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /database/factories/CertificateFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Certificate::class, function () { 15 | return [ 16 | 'project_id' => factory(App\Project::class), 17 | 'name' => 'Test Certificate', 18 | 'private_key' => 'private-key', 19 | 'certificate' => 'certificate', 20 | ]; 21 | }); 22 | -------------------------------------------------------------------------------- /database/factories/StackTaskFactory.php: -------------------------------------------------------------------------------- 1 | define(App\StackTask::class, function () { 15 | return [ 16 | 'stack_id' => factory(App\Stack::class), 17 | 'name' => 'Test Name', 18 | 'user' => 'cloud', 19 | 'commands' => [ 20 | 'exit 1', 21 | ], 22 | ]; 23 | }); 24 | -------------------------------------------------------------------------------- /tests/Feature/ScheduleControllerTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 20 | } 21 | 22 | 23 | public function test_prune_tasks_can_is_dispatched() 24 | { 25 | Bus::fake(); 26 | 27 | $response = $this->post('/schedule/prune-tasks'); 28 | 29 | $response->assertStatus(200); 30 | Bus::assertDispatched(PruneTasks::class); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Feature/StoreDatabaseBackupScriptTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 20 | } 21 | 22 | 23 | public function test_script_can_be_rendered() 24 | { 25 | $backup = factory(DatabaseBackup::class)->create(); 26 | 27 | $script = new StoreDatabaseBackup($backup); 28 | 29 | $this->assertNotNull($script->script()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/LoginController.php: -------------------------------------------------------------------------------- 1 | firstOrFail(); 19 | 20 | if (! Hash::check(request('password'), $user->password)) { 21 | abort(422); 22 | } 23 | 24 | $user->revokeTokens(request('host')); 25 | 26 | return ['access_token' => $user->createToken(request('host'), ['*'])->accessToken]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /database/factories/SourceProviderFactory.php: -------------------------------------------------------------------------------- 1 | define(App\SourceProvider::class, function () { 16 | return [ 17 | 'user_id' => factory(App\User::class), 18 | 'name' => 'GitHub', 19 | 'type' => 'GitHub', 20 | 'meta' => ['token' => config('services.testing.github')], 21 | ]; 22 | }); 23 | -------------------------------------------------------------------------------- /tests/Feature/ProvisionDatabaseScriptTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 20 | } 21 | 22 | 23 | public function test_script_can_be_rendered() 24 | { 25 | $database = factory(Database::class)->create(); 26 | $script = new ProvisionDatabase($database); 27 | $script = $script->script(); 28 | 29 | $this->assertNotNull($script); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Events/DatabaseBackupFailed.php: -------------------------------------------------------------------------------- 1 | backup = $backup; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/factories/IpAddressFactory.php: -------------------------------------------------------------------------------- 1 | define(App\IpAddress::class, function () { 15 | return [ 16 | 'addressable_id' => factory(App\Database::class), 17 | 'addressable_type' => App\Database::class, 18 | 'public_address' => '127.0.0.1', 19 | 'private_address' => '127.0.0.2', 20 | ]; 21 | }); 22 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 18 | })->describe('Display an inspiring quote'); 19 | 20 | 21 | Artisan::command('cloud', function () { 22 | $backup = App\DatabaseBackup::find(1); 23 | 24 | $backup->restore(); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/Feature/ProvisionAppServerScriptTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 20 | } 21 | 22 | 23 | public function test_script_can_be_rendered() 24 | { 25 | $server = factory(AppServer::class)->create(); 26 | 27 | $script = new ProvisionAppServer($server); 28 | 29 | $script = $script->script(); 30 | 31 | $this->assertNotNull($script); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Feature/ProvisionBalancerScriptTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 20 | } 21 | 22 | 23 | public function test_script_can_be_rendered() 24 | { 25 | $balancer = factory(Balancer::class)->create(); 26 | 27 | $script = new ProvisionBalancer($balancer); 28 | 29 | $script = $script->script(); 30 | 31 | $this->assertNotNull($script); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Events/DatabaseBackupFinished.php: -------------------------------------------------------------------------------- 1 | backup = $backup; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Events/DatabaseBackupRunning.php: -------------------------------------------------------------------------------- 1 | backup = $backup; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/factories/HookFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Hook::class, function () { 15 | return [ 16 | 'stack_id' => factory(App\Stack::class), 17 | 'name' => 'Test Hook', 18 | 'branch' => 'master', 19 | 'token' => str_random(40), 20 | 'meta' => [], 21 | 'published' => false, 22 | ]; 23 | }); 24 | -------------------------------------------------------------------------------- /database/migrations/2017_04_03_193949_create_server_providers_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('user_id')->index(); 19 | $table->string('name'); 20 | $table->string('type', 25); 21 | $table->text('meta'); 22 | $table->timestamps(); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /database/migrations/2017_04_26_163353_create_source_providers_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('user_id')->index(); 19 | $table->string('name'); 20 | $table->string('type', 25); 21 | $table->text('meta'); 22 | $table->timestamps(); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Feature/BuildScriptTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 20 | } 21 | 22 | 23 | public function test_script_can_be_rendered() 24 | { 25 | $deployment = factory(ServerDeployment::class)->create(); 26 | 27 | $deployment->deployable->createDaemonGeneration(); 28 | 29 | $script = new Build($deployment); 30 | 31 | $this->assertNotNull($script->script()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/factories/EnvironmentFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Environment::class, function () { 15 | return [ 16 | 'project_id' => factory(App\Project::class), 17 | 'creator_id' => factory(App\User::class), 18 | 'name' => 'production', 19 | 'encryption_key' => '', 20 | 'variables' => '', 21 | ]; 22 | }); 23 | -------------------------------------------------------------------------------- /app/Events/DatabaseRestoreFailed.php: -------------------------------------------------------------------------------- 1 | restore = $restore; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Events/DatabaseRestoreRunning.php: -------------------------------------------------------------------------------- 1 | restore = $restore; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Feature/ActivateScriptTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 20 | } 21 | 22 | 23 | public function test_script_can_be_rendered() 24 | { 25 | $deployment = factory(ServerDeployment::class)->create(); 26 | 27 | $deployment->deployable->createDaemonGeneration(); 28 | 29 | $script = new Activate($deployment); 30 | 31 | $this->assertNotNull($script->script()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Events/DatabaseRestoreFinished.php: -------------------------------------------------------------------------------- 1 | restore = $restore; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /resources/views/scripts/daemon/activate.blade.php: -------------------------------------------------------------------------------- 1 | 2 | # Stop The Previous Daemon Generations 3 | 4 | @foreach ($previousGenerations as $previousGeneration) 5 | if [ -f /etc/supervisor/conf.d/daemon-{!! $previousGeneration->id !!}.conf ] 6 | then 7 | echo "Stopping Supervisor Group: daemon-{!! $previousGeneration->id !!}" 8 | 9 | nohup bash -c "sudo supervisorctl stop daemon-{!! $previousGeneration->id !!}:* && \ 10 | sudo supervisorctl remove daemon-{!! $previousGeneration->id !!} && \ 11 | rm /etc/supervisor/conf.d/daemon-{!! $previousGeneration->id !!}.conf" > /dev/null 2>&1 & 12 | fi 13 | @endforeach 14 | 15 | # Activate The Daemon Configuration 16 | 17 | sleep 3 18 | 19 | sudo supervisorctl add daemon-{!! $generation->id !!} 20 | -------------------------------------------------------------------------------- /app/Events/ServerDeploymentBuilt.php: -------------------------------------------------------------------------------- 1 | deployment = $deployment; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Events/ServerDeploymentFailed.php: -------------------------------------------------------------------------------- 1 | deployment = $deployment; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Events/ServerDeploymentActivated.php: -------------------------------------------------------------------------------- 1 | deployment = $deployment; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/SshDatabaseController.php: -------------------------------------------------------------------------------- 1 | authorize('view', $project); 21 | 22 | return $project->databases() 23 | ->with('address') 24 | ->get() 25 | ->filter 26 | ->canSsh($request->user()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /database/factories/DatabaseRestoreFactory.php: -------------------------------------------------------------------------------- 1 | define(App\DatabaseRestore::class, function () { 15 | return [ 16 | 'database_id' => factory(App\Database::class), 17 | 'database_backup_id' => factory(App\DatabaseBackup::class), 18 | 'database_name' => 'Test Database', 19 | 'status' => 'running', 20 | 'output' => '', 21 | ]; 22 | }); 23 | -------------------------------------------------------------------------------- /tests/Feature/ReportHelperTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 21 | } 22 | 23 | 24 | public function test_helper_reports_exceptions() 25 | { 26 | $e = new Exception; 27 | $mock = Mockery::mock(); 28 | $mock->shouldReceive('report')->once()->with($e); 29 | $this->swap(ExceptionHandler::class, $mock); 30 | report($e); 31 | 32 | $this->assertTrue(true); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Prunable.php: -------------------------------------------------------------------------------- 1 | getTable().' where created_at <= ? order by id limit '.$limit, 26 | [$date->format('Y-m-d H:i:s')] 27 | ); 28 | 29 | $total += $affected; 30 | } while ($affected > 0); 31 | 32 | return $total; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/LastDeploymentController.php: -------------------------------------------------------------------------------- 1 | authorize('view', $stack); 22 | 23 | if ($deployment = $stack->lastDeployment()) { 24 | return $this->cancel($deployment); 25 | } 26 | 27 | return response()->json([ 28 | 'deployment' => ['No deployments exist for this stack.'], 29 | ], 400); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/factories/BalancerFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Balancer::class, function () { 15 | return [ 16 | 'project_id' => factory(App\Project::class), 17 | 'name' => str_random(6), 18 | 'size' => '2GB', 19 | 'provider_server_id' => 1, 20 | 'port' => 22, 21 | 'sudo_password' => str_random(40), 22 | 'status' => 'provisioned', 23 | ]; 24 | }); 25 | -------------------------------------------------------------------------------- /database/migrations/2017_03_31_172929_create_environments_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('project_id')->index(); 19 | $table->unsignedInteger('creator_id'); 20 | $table->string('name'); 21 | $table->text('encryption_key'); 22 | $table->text('variables'); 23 | $table->timestamps(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/AppServerRecordCreator.php: -------------------------------------------------------------------------------- 1 | stack->appServers(); 22 | } 23 | 24 | /** 25 | * Get the custom attributes for the servers. 26 | * 27 | * @return array 28 | */ 29 | protected function attributes() 30 | { 31 | return [ 32 | 'database_username' => 'cloud', 33 | 'database_password' => str_random(40), 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Contracts/StackDefinition.php: -------------------------------------------------------------------------------- 1 | define(App\ServerProvider::class, function () { 15 | return [ 16 | 'user_id' => function () { 17 | return factory(App\User::class)->create(); 18 | }, 19 | 'name' => 'DigitalOcean', 20 | 'type' => 'DigitalOcean', 21 | 'meta' => ['token' => config('services.testing.digital_ocean')], 22 | ]; 23 | }); 24 | -------------------------------------------------------------------------------- /app/Callbacks/Dispatch.php: -------------------------------------------------------------------------------- 1 | class = $class; 25 | } 26 | 27 | /** 28 | * Handle the callback. 29 | * 30 | * @param Task $task 31 | * @return void 32 | */ 33 | public function handle(Task $task) 34 | { 35 | if ($task->provisionable) { 36 | $class = $this->class; 37 | 38 | dispatch(new $class($task->provisionable)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Jobs/ProvisionBalancer.php: -------------------------------------------------------------------------------- 1 | provisionable = $provisionable; 20 | } 21 | 22 | /** 23 | * Perform any tasks after the server is provisioned. 24 | * 25 | * @return void 26 | */ 27 | protected function provisioned() 28 | { 29 | Mail::to($this->provisionable->project->user)->queue( 30 | new BalancerProvisioned($this->provisionable) 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Jobs/ProvisionDatabase.php: -------------------------------------------------------------------------------- 1 | provisionable = $provisionable; 20 | } 21 | 22 | /** 23 | * Perform any tasks after the server is provisioned. 24 | * 25 | * @return void 26 | */ 27 | protected function provisioned() 28 | { 29 | Mail::to($this->provisionable->project->user)->queue( 30 | new DatabaseProvisioned($this->provisionable) 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /resources/assets/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * First we will load all of this project's JavaScript dependencies which 4 | * includes Vue and other libraries. It is a great starting point when 5 | * building robust, powerful web applications using Vue and Laravel. 6 | */ 7 | 8 | require('./bootstrap'); 9 | 10 | window.Vue = require('vue'); 11 | 12 | /** 13 | * Next, we will create a fresh Vue application instance and attach it to 14 | * the page. Then, you may begin adding components to this application 15 | * or customize the JavaScript scaffolding to fit your unique needs. 16 | */ 17 | 18 | Vue.component( 19 | 'passport-personal-access-tokens', 20 | require('./components/passport/PersonalAccessTokens.vue') 21 | ); 22 | 23 | Vue.component('project-list', require('./components/ProjectList.vue')); 24 | 25 | const app = new Vue(require('./root')); 26 | -------------------------------------------------------------------------------- /tests/Feature/EnvironmentTest.php: -------------------------------------------------------------------------------- 1 | create([ 18 | 'project_id' => 1, 19 | ]); 20 | 21 | $environment->stacks()->save($stack = factory(Stack::class)->make([ 22 | 'promoted' => true, 23 | ])); 24 | 25 | $environment->stacks()->save(factory(Stack::class)->make([ 26 | 'promoted' => false, 27 | ])); 28 | 29 | $this->assertEquals($stack->id, $environment->promotedStack()->id); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/factories/ServerTaskFactory.php: -------------------------------------------------------------------------------- 1 | define(App\ServerTask::class, function () { 15 | return [ 16 | 'stack_task_id' => factory(App\StackTask::class), 17 | 'taskable_id' => factory(App\WebServer::class), 18 | 'taskable_type' => 'App\WebServer', 19 | 'task_id' => factory(App\Task::class), 20 | 'commands' => [ 21 | 'exit 1', 22 | ], 23 | ]; 24 | }); 25 | -------------------------------------------------------------------------------- /app/Scripts/GetAptLockStatus.php: -------------------------------------------------------------------------------- 1 | authorize('view', $request->stack->project()); 19 | 20 | $stack = $request->stack; 21 | 22 | $stack->load('appServers.address', 'webServers.address', 'workerServers.address'); 23 | 24 | return [ 25 | 'app' => $stack->appServers->all(), 26 | 'web' => $stack->webServers->all(), 27 | 'worker' => $stack->workerServers->all(), 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/factories/ServerDeploymentFactory.php: -------------------------------------------------------------------------------- 1 | define(App\ServerDeployment::class, function () { 15 | return [ 16 | 'deployment_id' => factory(App\Deployment::class), 17 | 'deployable_id' => factory(App\AppServer::class), 18 | 'deployable_type' => App\AppServer::class, 19 | 'build_commands' => [], 20 | 'activation_commands' => [], 21 | 'status' => 'running', 22 | ]; 23 | }); 24 | -------------------------------------------------------------------------------- /app/Events/ProjectShared.php: -------------------------------------------------------------------------------- 1 | user = $user; 37 | $this->project = $project; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/factories/ProjectFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Project::class, function () { 15 | return [ 16 | 'user_id' => factory(App\User::class), 17 | 'server_provider_id' => factory(App\ServerProvider::class), 18 | 'source_provider_id' => factory(App\SourceProvider::class), 19 | 'repository' => 'taylorotwell/hello-world', 20 | 'name' => 'Laravel', 21 | 'region' => 'nyc3', 22 | ]; 23 | }); 24 | -------------------------------------------------------------------------------- /app/Events/ProjectUnshared.php: -------------------------------------------------------------------------------- 1 | user = $user; 37 | $this->project = $project; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create([ 18 | 'name' => 'Taylor Otwell', 19 | 'email' => 'taylor@laravel.com', 20 | 'api_token' => 'laravel', 21 | ]); 22 | 23 | $provider = factory(ServerProvider::class)->create([ 24 | 'user_id' => $user->id, 25 | ]); 26 | 27 | $project = factory(Project::class)->create([ 28 | 'user_id' => $user->id, 29 | 'server_provider_id' => $provider->id, 30 | 'region' => 'nyc3', 31 | ]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/ShellOutput.php: -------------------------------------------------------------------------------- 1 | output .= $line; 26 | } 27 | 28 | /** 29 | * Render the output as a string. 30 | * 31 | * @return string 32 | */ 33 | public function __toString() 34 | { 35 | if (Str::startsWith($this->output, 'Warning:')) { 36 | $this->output = substr($this->output, strpos($this->output, "\n") + 1); 37 | } 38 | 39 | return trim($this->output); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Passwords must be at least six characters and match the confirmation.', 17 | 'reset' => 'Your password has been reset!', 18 | 'sent' => 'We have e-mailed your password reset link!', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that e-mail address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /tests/Feature/RestoreDatabaseBackupScriptTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 21 | } 22 | 23 | 24 | public function test_script_can_be_rendered() 25 | { 26 | $backup = factory(DatabaseBackup::class)->create(); 27 | 28 | $backup->restores()->save($restore = factory(DatabaseRestore::class)->make()); 29 | 30 | $script = new RestoreDatabaseBackup($restore); 31 | 32 | $this->assertNotNull($script->script()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/StackMetadata.php: -------------------------------------------------------------------------------- 1 | meta = $meta; 23 | } 24 | 25 | /** 26 | * Create a new metadata instance. 27 | * 28 | * @param array $meta 29 | * @return static 30 | */ 31 | public static function from(array $meta) 32 | { 33 | return new static($meta); 34 | } 35 | 36 | /** 37 | * Prepare the metadata. 38 | * 39 | * @return array 40 | */ 41 | public function prepare() 42 | { 43 | return $this->meta; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /database/migrations/2017_05_10_203142_create_alerts_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->unsignedInteger('project_id')->index(); 19 | $table->unsignedInteger('stack_id')->nullable()->index(); 20 | $table->string('level', 15)->default('error'); 21 | $table->string('type'); 22 | $table->text('exception'); 23 | $table->text('meta'); 24 | $table->timestamps(); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/CallbackController.php: -------------------------------------------------------------------------------- 1 | isRunning(), 404); 29 | 30 | FinishTask::dispatch( 31 | $task, (int) $request->query('exit_code') 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2017_06_14_151436_create_stack_databases_table.php: -------------------------------------------------------------------------------- 1 | unsignedInteger('stack_id'); 18 | $table->unsignedInteger('database_id'); 19 | 20 | $table->unique(['stack_id', 'database_id']); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists('stack_databases'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/TaskFactory.php: -------------------------------------------------------------------------------- 1 | timeout(); 19 | } 20 | 21 | return $provisionable->tasks()->create([ 22 | 'project_id' => $provisionable->projectId(), 23 | 'name' => $script->name(), 24 | 'user' => $script->sshAs, 25 | 'options' => $options, 26 | 'script' => (string) $script, 27 | 'output' => '', 28 | ]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/ShellResponse.php: -------------------------------------------------------------------------------- 1 | output = $output; 39 | $this->exitCode = $exitCode; 40 | $this->timedOut = $timedOut; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /database/factories/DatabaseBackupFactory.php: -------------------------------------------------------------------------------- 1 | define(App\DatabaseBackup::class, function () { 15 | return [ 16 | 'database_id' => factory(App\Database::class), 17 | 'storage_provider_id' => factory(App\StorageProvider::class), 18 | 'database_name' => 'Test Database', 19 | 'backup_path' => 'laravel-cloud-test/backups/laravel/2017-01-01-12-01-01.sql.gz', 20 | 'status' => 'running', 21 | 'output' => '', 22 | ]; 23 | }); 24 | -------------------------------------------------------------------------------- /app/Callbacks/CheckBuild.php: -------------------------------------------------------------------------------- 1 | id = $id; 26 | } 27 | 28 | /** 29 | * Handle the callback. 30 | * 31 | * @param Task $task 32 | * @return void 33 | */ 34 | public function handle(Task $task) 35 | { 36 | if ($deployment = ServerDeployment::find($this->id)) { 37 | $task->successful() 38 | ? $deployment->markAsBuilt() 39 | : $deployment->markAsFailed(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /database/migrations/2017_04_27_193631_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('queue'); 19 | $table->longText('payload'); 20 | $table->tinyInteger('attempts')->unsigned(); 21 | $table->unsignedInteger('reserved_at')->nullable(); 22 | $table->unsignedInteger('available_at'); 23 | $table->unsignedInteger('created_at'); 24 | 25 | $table->index(['queue', 'reserved_at']); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /database/factories/WebServerFactory.php: -------------------------------------------------------------------------------- 1 | define(App\WebServer::class, function () { 15 | return [ 16 | 'project_id' => factory(App\Project::class), 17 | 'stack_id' => factory(App\Stack::class), 18 | 'name' => str_random(6), 19 | 'size' => '2gb', 20 | 'provider_server_id' => 1, 21 | 'port' => 22, 22 | 'sudo_password' => str_random(40), 23 | 'meta' => [], 24 | 'status' => 'provisioned', 25 | ]; 26 | }); 27 | -------------------------------------------------------------------------------- /database/migrations/2017_05_08_175021_create_ip_addresses_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('addressable_id'); 19 | $table->string('addressable_type'); 20 | $table->string('public_address'); 21 | $table->string('private_address'); 22 | $table->timestamps(); 23 | 24 | $table->index(['addressable_id', 'addressable_type']); 25 | $table->index('public_address'); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/EnvironmentHookController.php: -------------------------------------------------------------------------------- 1 | authorize('view', $environment->project); 21 | 22 | return $environment->stacks->load('hooks.stack') 23 | ->flatMap 24 | ->hooks 25 | ->sortBy('name') 26 | ->sortBy('stack.name') 27 | ->values(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Callbacks/CheckActivation.php: -------------------------------------------------------------------------------- 1 | id = $id; 26 | } 27 | 28 | /** 29 | * Handle the callback. 30 | * 31 | * @param Task $task 32 | * @return void 33 | */ 34 | public function handle(Task $task) 35 | { 36 | if ($deployment = ServerDeployment::find($this->id)) { 37 | $task->successful() 38 | ? $deployment->markAsActivated() 39 | : $deployment->markAsFailed(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /database/factories/DeploymentFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Deployment::class, function () { 15 | return [ 16 | 'stack_id' => factory(App\Stack::class), 17 | 'branch' => 'master', 18 | 'commit_hash' => str_random(20), 19 | 'build_commands' => ['first'], 20 | 'activation_commands' => ['second'], 21 | 'directories' => ['storage'], 22 | 'daemons' => [], 23 | 'schedule' => [], 24 | 'status' => 'pending', 25 | ]; 26 | }); 27 | -------------------------------------------------------------------------------- /database/factories/StorageProviderFactory.php: -------------------------------------------------------------------------------- 1 | define(App\StorageProvider::class, function () { 16 | return [ 17 | 'user_id' => factory(App\User::class), 18 | 'name' => 'S3', 19 | 'type' => 'S3', 20 | 'meta' => [ 21 | 'key' => config('services.testing.s3_key'), 22 | 'secret' => config('services.testing.s3_secret'), 23 | 'region' => 'us-east-1', 24 | 'bucket' => 'laravel-cloud-test', 25 | ], 26 | ]; 27 | }); 28 | -------------------------------------------------------------------------------- /database/factories/TaskFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Task::class, function () { 15 | return [ 16 | 'project_id' => factory(App\Project::class), 17 | 'provisionable_id' => factory(App\Database::class), 18 | 'provisionable_type' => 'App\Database', 19 | 'name' => 'Task Name', 20 | 'user' => 'root', 21 | 'status' => 'finished', 22 | 'exit_code' => 0, 23 | 'script' => '', 24 | 'output' => '', 25 | 'options' => [], 26 | ]; 27 | }); 28 | -------------------------------------------------------------------------------- /resources/views/scripts/deployment/activate.blade.php: -------------------------------------------------------------------------------- 1 | 2 | set -e 3 | 4 | APP_PATH="/home/cloud/app" 5 | DEPLOYMENTS_PATH="/home/cloud/deployments" 6 | DEPLOYMENT_PATH="$DEPLOYMENTS_PATH/{!! $deployment->timestamp() !!}" 7 | 8 | # Activate New Deployment 9 | 10 | echo "Activating Deployment" 11 | 12 | ln -s $DEPLOYMENT_PATH /home/cloud/energize 13 | mv -Tf /home/cloud/energize $APP_PATH 14 | 15 | # Reload PHP-FPM 16 | 17 | echo "Reloading FPM" 18 | 19 | @if ($script->shouldRestartFpm()) 20 | sudo service php{{ $deployment->phpVersion() }}-fpm reload 21 | @endif 22 | 23 | # Run User Defined Activation Commands 24 | 25 | cd $DEPLOYMENT_PATH 26 | 27 | echo "Running User Activation Commands" 28 | 29 | @foreach ($deployment->activation_commands as $command) 30 | {!! $command !!} 31 | 32 | @endforeach 33 | 34 | # Delete Old Deployments 35 | 36 | echo "Purging Old Deployments" 37 | 38 | cd $DEPLOYMENTS_PATH 39 | 40 | rm -rf `ls -t | tail -n +{{ 2 + 1 }}` 41 | -------------------------------------------------------------------------------- /resources/assets/js/components/ProjectList.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 38 | -------------------------------------------------------------------------------- /tests/Feature/DeleteServerOnProviderJobTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 21 | } 22 | 23 | 24 | public function test_server_is_deleted_using_provider() 25 | { 26 | $project = factory(Project::class)->create(); 27 | 28 | $job = new DeleteServerOnProvider($project, '123'); 29 | 30 | ServerProviderClientFactory::shouldReceive('make->deleteServerById') 31 | ->with('123'); 32 | 33 | $job->handle(); 34 | 35 | $this->assertTrue(true); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Callbacks/CheckServerTask.php: -------------------------------------------------------------------------------- 1 | id = $id; 26 | } 27 | 28 | /** 29 | * Handle the callback. 30 | * 31 | * @param Task $task 32 | * @return void 33 | */ 34 | public function handle(Task $task) 35 | { 36 | if ($serverTask = ServerTask::find($this->id)) { 37 | if ($serverTask->task->successful()) { 38 | $serverTask->markAsFinished(); 39 | } else { 40 | $serverTask->markAsFailed(); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /database/factories/DatabaseFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Database::class, function () { 15 | return [ 16 | 'project_id' => factory(App\Project::class), 17 | 'name' => str_random(6), 18 | 'size' => '2gb', 19 | 'provider_server_id' => 1, 20 | 'port' => 22, 21 | 'sudo_password' => str_random(40), 22 | 'username' => str_random(40), 23 | 'password' => str_random(40), 24 | 'allows_access_from' => [], 25 | 'status' => 'provisioned', 26 | ]; 27 | }); 28 | -------------------------------------------------------------------------------- /app/Callbacks/CheckDatabaseRestore.php: -------------------------------------------------------------------------------- 1 | id = $id; 26 | } 27 | 28 | /** 29 | * Handle the callback. 30 | * 31 | * @param Task $task 32 | * @return void 33 | */ 34 | public function handle(Task $task) 35 | { 36 | if ($restore = DatabaseRestore::find($this->id)) { 37 | $task->successful() 38 | ? $restore->markAsFinished($task->output) 39 | : $restore->markAsFailed($task->exit_code, $task->output); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Events/AlertCreated.php: -------------------------------------------------------------------------------- 1 | alert = $alert; 29 | } 30 | 31 | /** 32 | * Get the user IDs affected by this alert. 33 | * 34 | * @return array 35 | */ 36 | public function affectedIds() 37 | { 38 | return collect([$this->alert->project->user])->merge( 39 | $this->alert->project->collaborators 40 | )->pluck('id')->all(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /database/factories/WorkerServerFactory.php: -------------------------------------------------------------------------------- 1 | define(App\WorkerServer::class, function () { 15 | return [ 16 | 'project_id' => factory(App\Project::class), 17 | 'stack_id' => factory(App\Stack::class), 18 | 'name' => str_random(6), 19 | 'size' => '2gb', 20 | 'provider_server_id' => 1, 21 | 'port' => 22, 22 | 'sudo_password' => str_random(40), 23 | 'meta' => [], 24 | 'status' => 'provisioned', 25 | 'daemon_status' => 'pending', 26 | ]; 27 | }); 28 | -------------------------------------------------------------------------------- /database/migrations/2017_03_31_170538_create_projects_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('user_id')->index(); 19 | $table->string('name'); 20 | $table->unsignedInteger('server_provider_id')->index(); 21 | $table->string('region', 25); 22 | $table->unsignedInteger('source_provider_id')->index(); 23 | $table->string('repository'); 24 | $table->tinyInteger('archived')->default(0); 25 | $table->timestamps(); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /helpers.php: -------------------------------------------------------------------------------- 1 | encode($value); 17 | } 18 | 19 | /** 20 | * Decode a hashid. 21 | * 22 | * @param string $value 23 | * @return int 24 | */ 25 | function hashid_decode($value) 26 | { 27 | $hashids = new Hashids\Hashids(config('app.key'), 36); 28 | 29 | return $hashids->decode($value)[0]; 30 | } 31 | 32 | /** 33 | * Get a new UUID. 34 | * 35 | * @var string 36 | */ 37 | function uuid() 38 | { 39 | $orderedTimeFactory = new UuidFactory; 40 | $orderedTimeFactory->setCodec(new OrderedTimeCodec($orderedTimeFactory->getUuidBuilder())); 41 | $orderedTimeUuid = $orderedTimeFactory->uuid1(); 42 | return (string) $orderedTimeUuid; 43 | } 44 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/StackSshServerController.php: -------------------------------------------------------------------------------- 1 | authorize('view', $request->stack->project()); 19 | 20 | $stack = $request->stack; 21 | 22 | $stack->load('appServers.address', 'webServers.address', 'workerServers.address'); 23 | 24 | return [ 25 | 'app' => $stack->appServers->filter->canSsh($request->user())->all(), 26 | 'web' => $stack->webServers->filter->canSsh($request->user())->all(), 27 | 'worker' => $stack->workerServers->filter->canSsh($request->user())->all(), 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Policies/DatabasePolicy.php: -------------------------------------------------------------------------------- 1 | projects->contains($database->project); 24 | } 25 | 26 | /** 27 | * Determine whether the user can delete the database. 28 | * 29 | * @param \App\User $user 30 | * @param \App\Database $database 31 | * @return mixed 32 | */ 33 | public function delete(User $user, Database $database) 34 | { 35 | return $user->projects->contains($database->project); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2017_07_29_024811_create_certificates_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('project_id')->index(); 19 | $table->string('name'); 20 | $table->text('private_key'); 21 | $table->text('certificate'); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('certificates'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/migrations/2017_05_10_150545_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->text('connection'); 19 | $table->text('queue'); 20 | $table->longText('payload'); 21 | $table->longText('exception'); 22 | $table->timestamp('failed_at')->useCurrent(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('failed_jobs'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Jobs/RunStackTask.php: -------------------------------------------------------------------------------- 1 | task = $task; 32 | } 33 | 34 | /** 35 | * Execute the job. 36 | * 37 | * @return void 38 | */ 39 | public function handle() 40 | { 41 | $this->task->run(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /database/migrations/2017_08_02_155914_create_storage_providers_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('user_id')->index(); 19 | $table->string('name'); 20 | $table->string('type', 25); 21 | $table->text('meta'); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('storage_providers'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Callbacks/CheckDatabaseBackup.php: -------------------------------------------------------------------------------- 1 | id = $id; 26 | } 27 | 28 | /** 29 | * Handle the callback. 30 | * 31 | * @param Task $task 32 | * @return void 33 | */ 34 | public function handle(Task $task) 35 | { 36 | if ($backup = DatabaseBackup::find($this->id)) { 37 | $backup->updateSize(); 38 | 39 | $task->successful() 40 | ? $backup->markAsFinished($task->output) 41 | : $backup->markAsFailed($task->exit_code, $task->output); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ForgotPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Jobs/SyncServers.php: -------------------------------------------------------------------------------- 1 | stack = $stack; 32 | } 33 | 34 | /** 35 | * Execute the job. 36 | * 37 | * @return void 38 | */ 39 | public function handle() 40 | { 41 | $this->stack->httpServers()->each->sync(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /database/factories/AppServerFactory.php: -------------------------------------------------------------------------------- 1 | define(App\AppServer::class, function () { 15 | return [ 16 | 'project_id' => factory(App\Project::class), 17 | 'stack_id' => factory(App\Stack::class), 18 | 'name' => str_random(6), 19 | 'size' => '2GB', 20 | 'provider_server_id' => 1, 21 | 'port' => 22, 22 | 'sudo_password' => str_random(40), 23 | 'database_username' => 'cloud', 24 | 'database_password' => str_random(40), 25 | 'meta' => [], 26 | 'status' => 'provisioned', 27 | ]; 28 | }); 29 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | define(App\User::class, function ($faker) { 15 | static $key; 16 | static $password; 17 | static $workerKey; 18 | 19 | return [ 20 | 'name' => $faker->name, 21 | 'email' => $faker->unique()->safeEmail, 22 | 'password' => $password ?: $password = bcrypt('secret'), 23 | 'remember_token' => str_random(10), 24 | 'keypair' => $key = $key ?: App\SecureShellKey::forNewUser(), 25 | 'worker_keypair' => $workerKey = $workerKey ?: App\SecureShellKey::forNewUser(), 26 | ]; 27 | }); 28 | -------------------------------------------------------------------------------- /app/Contracts/DnsProvider.php: -------------------------------------------------------------------------------- 1 | stack = $stack; 31 | } 32 | 33 | /** 34 | * Execute the job. 35 | * 36 | * @return void 37 | */ 38 | public function handle() 39 | { 40 | $this->stack->databases->each->syncNetwork(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Mail/StackProvisioned.php: -------------------------------------------------------------------------------- 1 | stack = $stack; 30 | } 31 | 32 | /** 33 | * Build the message. 34 | * 35 | * @return $this 36 | */ 37 | public function build() 38 | { 39 | return $this->subject('Stack Created') 40 | ->markdown('mail.stack.provisioned', [ 41 | 'stack' => $this->stack, 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Scripts/ProvisionBalancer.php: -------------------------------------------------------------------------------- 1 | balancer = $balancer; 34 | } 35 | 36 | /** 37 | * Get the contents of the script. 38 | * 39 | * @return string 40 | */ 41 | public function script() 42 | { 43 | return view('scripts.balancer.provision', ['script' => $this])->render(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Scripts/Script.php: -------------------------------------------------------------------------------- 1 | name ?? ''; 31 | } 32 | 33 | /** 34 | * Get the timeout for the script. 35 | * 36 | * @return int|null 37 | */ 38 | public function timeout() 39 | { 40 | return Task::DEFAULT_TIMEOUT; 41 | } 42 | 43 | /** 44 | * Render the script as a string. 45 | * 46 | * @return string 47 | */ 48 | public function __toString() 49 | { 50 | return $this->script(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/StackTaskController.php: -------------------------------------------------------------------------------- 1 | validate([ 19 | 'name' => 'required|string|max:255', 20 | 'user' => 'required|string|in:root,cloud', 21 | 'commands' => 'required|array|min:1', 22 | 'commands.*' => 'string', 23 | ]); 24 | 25 | $request->user === 'root' 26 | ? $this->authorize('delete', $request->stack) 27 | : $this->authorize('view', $request->stack); 28 | 29 | return response()->json($request->stack->dispatchTask( 30 | $request->name, $request->user, $request->commands 31 | ), 201); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Jobs/SyncBalancers.php: -------------------------------------------------------------------------------- 1 | project = $project; 32 | } 33 | 34 | /** 35 | * Execute the job. 36 | * 37 | * @return void 38 | */ 39 | public function handle() 40 | { 41 | $this->project->balancers->each->sync(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Policies/StackPolicy.php: -------------------------------------------------------------------------------- 1 | canAccessProject($stack->environment->project); 25 | } 26 | 27 | /** 28 | * Determine whether the user can delete the stack. 29 | * 30 | * @param \App\User $user 31 | * @param \App\Stack $stack 32 | * @return mixed 33 | */ 34 | public function delete(User $user, Stack $stack) 35 | { 36 | return $stack->creator->id == $user->id || 37 | $user->projects->contains($stack->environment->project); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/2017_06_11_154558_create_daemon_generations_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('generationable_id'); 19 | $table->string('generationable_type'); 20 | $table->timestamps(); 21 | 22 | $table->index(['generationable_id', 'generationable_type'], 'generationable_morphs'); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('daemon_generations'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /resources/views/scripts/database/backup.blade.php: -------------------------------------------------------------------------------- 1 | 2 | set -e 3 | 4 | # Set Variables 5 | 6 | BACKUP="{!! basename($backup->backup_path) !!}" 7 | 8 | # Create Backup Directory 9 | 10 | mkdir -p /home/cloud/backups 11 | 12 | # Create Provider Credentials File 13 | 14 | {!! $backup->configurationScript() !!} 15 | 16 | # Create Backup 17 | 18 | mysqldump --single-transaction --skip-lock-tables --quick \ 19 | -u cloud -p{!! $backup->database->password !!} \ 20 | {!! $backup->database_name !!} | gzip > /home/cloud/backups/${BACKUP} 21 | 22 | # Verify The Backup File Was Created 23 | 24 | if [ ! -e /home/cloud/backups/${BACKUP} ]; then 25 | echo "The backup was not created." 26 | 27 | exit 1 28 | fi 29 | 30 | # Upload The Backup To The Provider 31 | 32 | {!! $backup->uploadScript() !!} 33 | 34 | # Test Result Of Upload 35 | 36 | if [ "$?" -ne "0" ]; then 37 | echo "Failed to upload backup to storage provider." 38 | 39 | exit 1 40 | fi 41 | 42 | # Remove The Backup File 43 | 44 | rm -f /home/cloud/backups/${BACKUP} 45 | -------------------------------------------------------------------------------- /database/migrations/2017_08_10_151936_create_hooks_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('stack_id')->index(); 19 | $table->string('name'); 20 | $table->string('token', 40); 21 | $table->string('branch'); 22 | $table->tinyInteger('published')->default(0); 23 | $table->text('meta'); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::dropIfExists('hooks'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Certificate.php: -------------------------------------------------------------------------------- 1 | belongsTo(Project::class, 'project_id'); 32 | } 33 | 34 | /** 35 | * Determine if this certificate is active. 36 | * 37 | * @return bool 38 | */ 39 | public function active() 40 | { 41 | return $this->project->activeCertificates()->contains(function ($certificate) { 42 | return $certificate->id === $this->id; 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Events/StackProvisioning.php: -------------------------------------------------------------------------------- 1 | stack = $stack; 32 | } 33 | 34 | /** 35 | * Get the channels the event should broadcast on. 36 | * 37 | * @return Channel|array 38 | */ 39 | public function broadcastOn() 40 | { 41 | return new PrivateChannel('channel-name'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /resources/assets/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | 2 | // Body 3 | $body-bg: #f5f8fa; 4 | 5 | // Borders 6 | $laravel-border-color: darken($body-bg, 10%); 7 | $list-group-border: $laravel-border-color; 8 | $navbar-default-border: $laravel-border-color; 9 | $panel-default-border: $laravel-border-color; 10 | $panel-inner-border: $laravel-border-color; 11 | 12 | // Brands 13 | $brand-primary: #3097D1; 14 | $brand-info: #8eb4cb; 15 | $brand-success: #2ab27b; 16 | $brand-warning: #cbb956; 17 | $brand-danger: #bf5329; 18 | 19 | // Typography 20 | $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/"; 21 | $font-family-sans-serif: "Raleway", sans-serif; 22 | $font-size-base: 14px; 23 | $line-height-base: 1.6; 24 | $text-color: #636b6f; 25 | 26 | // Navbar 27 | $navbar-default-bg: #fff; 28 | 29 | // Buttons 30 | $btn-default-color: $text-color; 31 | 32 | // Inputs 33 | $input-border: lighten($text-color, 40%); 34 | $input-border-focus: lighten($brand-primary, 25%); 35 | $input-color-placeholder: lighten($text-color, 30%); 36 | 37 | // Panels 38 | $panel-default-heading-bg: #fff; 39 | -------------------------------------------------------------------------------- /app/Mail/BalancerProvisioned.php: -------------------------------------------------------------------------------- 1 | balancer = $balancer; 30 | } 31 | 32 | /** 33 | * Build the message. 34 | * 35 | * @return $this 36 | */ 37 | public function build() 38 | { 39 | return $this->subject('Balancer Created') 40 | ->markdown('mail.balancer.provisioned', [ 41 | 'balancer' => $this->balancer, 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Mail/DatabaseProvisioned.php: -------------------------------------------------------------------------------- 1 | database = $database; 30 | } 31 | 32 | /** 33 | * Build the message. 34 | * 35 | * @return $this 36 | */ 37 | public function build() 38 | { 39 | return $this->subject('Database Created') 40 | ->markdown('mail.database.provisioned', [ 41 | 'database' => $this->database, 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->string('password'); 21 | $table->rememberToken(); 22 | $table->text('public_key'); 23 | $table->text('private_key'); 24 | $table->text('public_worker_key'); 25 | $table->text('private_worker_key'); 26 | $table->string('provider_key_id')->nullable(); 27 | $table->timestamp('last_alert_received_at')->nullable(); 28 | $table->timestamps(); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Jobs/SyncServer.php: -------------------------------------------------------------------------------- 1 | server = $server; 33 | } 34 | 35 | /** 36 | * Execute the job. 37 | * 38 | * @return void 39 | */ 40 | public function handle() 41 | { 42 | $this->server->run(new SyncServerScript($this->server)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /database/factories/StackFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Stack::class, function () { 15 | return [ 16 | 'environment_id' => factory(App\Environment::class), 17 | 'creator_id' => factory(App\User::class), 18 | 'name' => 'test-stack', 19 | 'url' => App\Haiku::withToken(), 20 | 'balanced' => false, 21 | 'status' => 'pending', 22 | 'pending_deployment' => [], 23 | 'meta' => [ 24 | 'php' => '7.1', 25 | 'initial_branch' => 'master', 26 | 'initial_build_commands' => [], 27 | 'initial_activation_commands' => [], 28 | ], 29 | ]; 30 | }); 31 | -------------------------------------------------------------------------------- /database/migrations/2017_07_31_191305_create_stack_tasks_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('stack_id')->index(); 19 | $table->string('name'); 20 | $table->string('user'); 21 | $table->longText('commands'); 22 | $table->string('status')->default('pending'); 23 | $table->timestamps(); 24 | 25 | $table->index('created_at'); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('stack_tasks'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Feature/ServerConfigurationControllerTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 22 | } 23 | 24 | 25 | public function test_stack_server_configurations_can_be_rebuilt() 26 | { 27 | Bus::fake(); 28 | 29 | $stack = factory(Stack::class)->create(); 30 | 31 | $response = $this->actingAs($stack->project()->user, 'api') 32 | ->json('PUT', '/api/stack/'.$stack->id.'/server-configuration'); 33 | 34 | $response->assertStatus(200); 35 | 36 | Bus::assertDispatched(SyncServers::class, function ($job) use ($stack) { 37 | return $job->stack->id === $stack->id; 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | call(function () { 29 | PruneTasks::dispatch(); 30 | PruneStackTasks::dispatch(); 31 | })->hourly(); 32 | } 33 | 34 | /** 35 | * Register the Closure based commands for the application. 36 | * 37 | * @return void 38 | */ 39 | protected function commands() 40 | { 41 | require base_path('routes/console.php'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Scripts/DaemonScript.php: -------------------------------------------------------------------------------- 1 | deployment = $deployment; 25 | } 26 | 27 | /** 28 | * Get the name of the script to run. 29 | * 30 | * @return string 31 | */ 32 | abstract public function scriptName(); 33 | 34 | /** 35 | * Get the contents of the script. 36 | * 37 | * @return string 38 | */ 39 | public function script() 40 | { 41 | return view($this->scriptName(), [ 42 | 'script' => $this, 43 | 'generation' => $this->deployment->currentDaemonGeneration(), 44 | ])->render(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/Events/DeploymentActivating.php: -------------------------------------------------------------------------------- 1 | deployment = $deployment; 32 | } 33 | 34 | /** 35 | * Get the channels the event should broadcast on. 36 | * 37 | * @return Channel|array 38 | */ 39 | public function broadcastOn() 40 | { 41 | return new PrivateChannel('channel-name'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Events/DeploymentBuilding.php: -------------------------------------------------------------------------------- 1 | deployment = $deployment; 32 | } 33 | 34 | /** 35 | * Get the channels the event should broadcast on. 36 | * 37 | * @return Channel|array 38 | */ 39 | public function broadcastOn() 40 | { 41 | return new PrivateChannel('channel-name'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Jobs/AddDnsRecord.php: -------------------------------------------------------------------------------- 1 | stack = $stack; 33 | } 34 | 35 | /** 36 | * Execute the job. 37 | * 38 | * @param \App\Contracts\DnsProvider $dns 39 | * @return void 40 | */ 41 | public function handle(DnsProvider $dns) 42 | { 43 | $dns->addRecord($this->stack); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /database/migrations/2017_04_10_190135_create_balancers_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('project_id')->index(); 19 | $table->string('name'); 20 | $table->string('size', 25); 21 | $table->string('provider_server_id')->nullable(); 22 | $table->integer('port')->default(22); 23 | $table->string('sudo_password'); 24 | $table->timestamp('provisioning_job_dispatched_at')->nullable(); 25 | $table->string('tls', 25)->nullable(); 26 | $table->string('status', 25); 27 | $table->timestamps(); 28 | 29 | $table->unique(['project_id', 'name']); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => realpath(storage_path('framework/views')), 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/LoginController.php: -------------------------------------------------------------------------------- 1 | middleware('guest', ['except' => 'logout']); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Rules/StackIsPromotable.php: -------------------------------------------------------------------------------- 1 | stack = $stack; 26 | } 27 | 28 | /** 29 | * Determine if the validation rule passes. 30 | * 31 | * @param string $attribute 32 | * @param mixed $value 33 | * @return bool 34 | */ 35 | public function passes($attribute, $value) 36 | { 37 | return $this->stack->promotable(); 38 | } 39 | 40 | /** 41 | * Get the validation error message. 42 | * 43 | * @return string 44 | */ 45 | public function message() 46 | { 47 | return 'The specified stack is not promotable. Please verify the stack has a "serves" directive.'; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/Scripts/WritesCaddyServerConfigurations.php: -------------------------------------------------------------------------------- 1 | server->actualDomainsWithPorts())) { 17 | return ''; 18 | } 19 | 20 | return collect($this->server->actualDomainsWithPorts())->map(function ($domain) { 21 | return new CaddyServerConfiguration($this->server, $domain); 22 | })->map->render()->implode(PHP_EOL); 23 | } 24 | 25 | /** 26 | * Get the Caddy server configuration for the vanity domains. 27 | * 28 | * @return string 29 | */ 30 | public function vanityDomainConfiguration() 31 | { 32 | return collect($this->server->vanityDomainsWithPorts())->map(function ($domain) { 33 | return new CaddyServerConfiguration($this->server, $domain); 34 | })->map->render()->implode(PHP_EOL); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Feature/ValidDatabaseNameRuleTest.php: -------------------------------------------------------------------------------- 1 | create(); 19 | $rule = new ValidDatabaseName($database->project); 20 | $this->assertTrue($rule->passes('database', $database->name)); 21 | } 22 | 23 | 24 | public function test_rule_fails_when_database_doesnt_exist() 25 | { 26 | $database = factory(Database::class)->create(); 27 | $rule = new ValidDatabaseName($database->project); 28 | $this->assertFalse($rule->passes('database', 'missing')); 29 | } 30 | 31 | 32 | public function test_rule_passes_when_not_a_project() 33 | { 34 | $rule = new ValidDatabaseName('something'); 35 | $this->assertTrue($rule->passes('database', 'missing')); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ResetPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Http/Requests/CreateHookRequest.php: -------------------------------------------------------------------------------- 1 | all(), [ 31 | 'name' => 'required|string|max:255', 32 | 'branch' => [ 33 | 'required', 34 | 'string', 35 | 'max:255', 36 | new ValidBranch( 37 | $this->stack->project()->sourceProvider, $this->stack->project()->repository 38 | ) 39 | ], 40 | 'publish' => 'required|boolean', 41 | ]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Scripts/ProvisionDatabase.php: -------------------------------------------------------------------------------- 1 | database = $database; 34 | } 35 | 36 | /** 37 | * Get the contents of the script. 38 | * 39 | * @return string 40 | */ 41 | public function script() 42 | { 43 | return view('scripts.database.provision', [ 44 | 'script' => $this, 45 | 'database' => $this->database, 46 | 'databasePassword' => $this->database->password, 47 | ])->render(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/Alert.php: -------------------------------------------------------------------------------- 1 | 'json', 16 | ]; 17 | 18 | /** 19 | * The attributes that aren't mass assignable. 20 | * 21 | * @var array 22 | */ 23 | protected $guarded = []; 24 | 25 | /** 26 | * The attributes that should be hidden for arrays. 27 | * 28 | * @var array 29 | */ 30 | protected $hidden = [ 31 | 'exception', 32 | ]; 33 | 34 | /** 35 | * The event map for the model. 36 | * 37 | * Allows for object-based events for native Eloquent events. 38 | * 39 | * @var array 40 | */ 41 | protected $dispatchesEvents = [ 42 | 'created' => Events\AlertCreated::class, 43 | ]; 44 | 45 | /** 46 | * Get the project that the alert belongs to. 47 | */ 48 | public function project() 49 | { 50 | return $this->belongsTo(Project::class, 'project_id'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/DatabaseTransferController.php: -------------------------------------------------------------------------------- 1 | authorize('transfer', $database); 22 | 23 | $request->validate([ 24 | 'project_id' => [ 25 | 'required' , 26 | 'integer', 27 | Rule::exists('projects', 'id')->where(function ($query) use ($request) { 28 | $query->where('user_id', $request->user()->id); 29 | }) 30 | ], 31 | ]); 32 | 33 | $database->stacks()->detach(); 34 | 35 | tap($database)->update([ 36 | 'project_id' => $request->project_id, 37 | ])->syncNetwork(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /resources/views/scripts/caddy/install.blade.php: -------------------------------------------------------------------------------- 1 | 2 | # Install Packages 3 | 4 | apt-get install -y --force-yes supervisor 5 | curl https://getcaddy.com | bash 6 | 7 | # Configure Supervisor Autostart 8 | 9 | systemctl enable supervisor.service 10 | service supervisor start 11 | 12 | # Allow Caddy To Bind To Root Privileged Ports 13 | 14 | setcap cap_net_bind_service=+ep $(which caddy) 15 | 16 | # Create Caddy Directories 17 | 18 | mkdir /home/cloud/.caddy 19 | 20 | # Write The Caddy Supervisor Configuration 21 | 22 | # -ca "https://acme-staging.api.letsencrypt.org/directory" 23 | 24 | cat > /etc/supervisor/conf.d/caddy.conf << EOF 25 | [program:caddy] 26 | command=/usr/local/bin/caddy -conf="/home/cloud/Caddyfile" -pidfile="/home/cloud/caddy.pid" -log="/home/cloud/caddy.log" -agree -email="letsencrypt@laravel.com" 27 | user=cloud 28 | environment=HOME="/home/cloud",CADDYPATH="/home/cloud/.caddy" 29 | autostart=true 30 | autorestart=unexpected 31 | exitcodes=0,2 32 | startsecs=1 33 | startretries=3 34 | stopsignal=QUIT 35 | stopwaitsecs=10 36 | stopasgroup=false 37 | redirect_stderr=true 38 | stdout_logfile=/home/cloud/caddy.stdout 39 | stderr_logfile=/home/cloud/caddy.stderr 40 | EOF 41 | -------------------------------------------------------------------------------- /tests/Feature/HandlesStackProvisioningFailuresTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 22 | } 23 | 24 | 25 | public function test_stack_is_deleted_and_alert_created() 26 | { 27 | $stack = new HandlesStackProvisioningFailuresTestFakeStack; 28 | $stack->environment()->associate(factory(Environment::class)->create()); 29 | $job = new CreateLoadBalancerIfNecessary($stack); 30 | $job->failed(new Exception); 31 | 32 | $this->assertTrue($stack->wasDeleted); 33 | } 34 | } 35 | 36 | 37 | class HandlesStackProvisioningFailuresTestFakeStack extends Stack 38 | { 39 | public $wasDeleted = false; 40 | 41 | public function delete() 42 | { 43 | $this->wasDeleted = true; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Http/Controllers/API/MaintenancedStackController.php: -------------------------------------------------------------------------------- 1 | authorize( 21 | 'view', $stack = Stack::findOrFail($request->stack) 22 | ); 23 | 24 | $stack->update([ 25 | 'under_maintenance' => true, 26 | ]); 27 | 28 | SyncServers::dispatch($stack); 29 | } 30 | 31 | /** 32 | * Remove the given stack from maintenance mode. 33 | * 34 | * @param \App\Stack $stack 35 | * @return Response 36 | */ 37 | public function destroy(Stack $stack) 38 | { 39 | $this->authorize('view', $stack); 40 | 41 | $stack->update([ 42 | 'under_maintenance' => false, 43 | ]); 44 | 45 | SyncServers::dispatch($stack); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Jobs/SyncBalancer.php: -------------------------------------------------------------------------------- 1 | balancer = $balancer; 39 | } 40 | 41 | /** 42 | * Execute the job. 43 | * 44 | * @return void 45 | */ 46 | public function handle() 47 | { 48 | $this->balancer->syncNow(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 11 | }, 12 | "devDependencies": { 13 | "axios": "^0.16.2", 14 | "bootstrap-sass": "^3.3.7", 15 | "cross-env": "^5.0.1", 16 | "laravel-echo": "^1.3.0", 17 | "jquery": "^3.1.1", 18 | "laravel-mix": "^1.0", 19 | "lodash": "^4.17.4", 20 | "pusher-js": "^4.1.0", 21 | "vue": "^2.1.10" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /database/migrations/2017_04_18_203351_create_tasks_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->unsignedInteger('project_id')->index(); 19 | $table->unsignedInteger('provisionable_id'); 20 | $table->string('provisionable_type'); 21 | $table->string('name'); 22 | $table->string('user', 25); 23 | $table->string('status', 25)->default('pending'); 24 | $table->integer('exit_code')->nullable(); 25 | $table->longText('script'); 26 | $table->longText('output'); 27 | $table->text('options'); 28 | $table->timestamps(); 29 | 30 | $table->index(['provisionable_id', 'provisionable_type']); 31 | $table->index('created_at'); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Events/StackDeleting.php: -------------------------------------------------------------------------------- 1 | stack = $stack; 31 | } 32 | 33 | /** 34 | * Create an alert for the given instance. 35 | * 36 | * @return \App\Alert 37 | */ 38 | public function toAlert() 39 | { 40 | return $this->stack->project()->alerts()->create([ 41 | 'stack_id' => $this->stack->id, 42 | 'level' => 'info', 43 | 'type' => 'StackDeleted', 44 | 'exception' => '', 45 | 'meta' => [], 46 | ]); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/Exceptions/ManifestNotFoundException.php: -------------------------------------------------------------------------------- 1 | stack = $stack; 42 | $this->branch = $branch; 43 | $this->repository = $repository; 44 | } 45 | 46 | /** 47 | * Render the exception. 48 | * 49 | * @return Response 50 | */ 51 | public function render() 52 | { 53 | return response('Cloud manifest not found.', 404); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Jobs/FinishTask.php: -------------------------------------------------------------------------------- 1 | task = $task; 40 | $this->exitCode = $exitCode; 41 | } 42 | 43 | /** 44 | * Execute the job. 45 | * 46 | * @return void 47 | */ 48 | public function handle() 49 | { 50 | $this->task->finish($this->exitCode); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Rules/ValidAppServerStack.php: -------------------------------------------------------------------------------- 1 | request = $request; 25 | } 26 | 27 | /** 28 | * Determine if the validation rule passes. 29 | * 30 | * @param string $attribute 31 | * @param mixed $value 32 | * @return bool 33 | */ 34 | public function passes($attribute, $value) 35 | { 36 | return empty($value) || (empty($this->request->web) && empty($this->request->worker)); 37 | } 38 | 39 | /** 40 | * Get the validation error message. 41 | * 42 | * @return string 43 | */ 44 | public function message() 45 | { 46 | return 'App servers may not be provisioned with web and worker servers.'; 47 | } 48 | } 49 | --------------------------------------------------------------------------------