Merge pull request #1002 from coollabsio/ijpatricio-wip-1

First SSH command on remote host.
This commit is contained in:
Joao Patricio 2023-03-21 09:04:16 +00:00 committed by GitHub
commit 1bb3117b05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 2809 additions and 172 deletions

View File

@ -1,39 +1,24 @@
############################################################################################################ ############################################################################################################
# Development Environment # Development Environment
# Userid for the user that will run the application inside the container # User and group id for the user that will run the application inside the container
# Run in your terminal: `id -u` and `id -g` and that's the results
USERID= USERID=
GROUPID=
############################################################################################################ ############################################################################################################
APP_NAME=Laravel APP_NAME=Laravel
APP_SERVICE=php
APP_ENV=local APP_ENV=local
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=pgsql DB_CONNECTION=pgsql
DB_HOST=postgres DB_HOST=postgres
DB_PORT=5432 DB_PORT=5432
DB_DATABASE=coolify DB_DATABASE=coolify
DB_USERNAME=sail DB_USERNAME=coolify
DB_PASSWORD=password DB_PASSWORD=password
BROADCAST_DRIVER=log QUEUE_CONNECTION=database
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

29
.github/workflows/docker-image.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Docker Image CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "*" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build the Docker image
run: |
docker run --rm -u "$(id -u):$(id -g)" \
-v "$(pwd):/app" \
-w /app composer:2 \
composer install --ignore-platform-reqs
TAG=$(date +%s) ./vendor/bin/sail build
TAG=$(date +%s) ./vendor/bin/sail up -d
sleep 1
./vendor/bin/sail ps
# Now to create .env
# Await database is created
# pest

View File

@ -1,26 +0,0 @@
FROM serversideup/php:8.2-fpm-nginx
ARG NODE_VERSION=18
ARG POSTGRES_VERSION=15
RUN apt-get update \
&& curl -sLS https://deb.nodesource.com/setup_$NODE_VERSION.x | bash - \
&& apt-get install -y nodejs \
&& npm install -g npm
RUN apt-get install -y php-pgsql
RUN apt-get -y autoremove \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
# Adding test ssh key
RUN mkdir -p ~/.ssh
RUN echo "-----BEGIN OPENSSH PRIVATE KEY-----\n\
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n\
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk\n\
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA\n\
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV\n\
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==\n\
-----END OPENSSH PRIVATE KEY-----" >> ~/.ssh/id_ed25519
RUN chmod 0600 ~/.ssh/id_ed25519

View File

@ -0,0 +1,54 @@
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class RunCommand extends Component
{
public $activity;
public $isKeepAliveOn = false;
public $manualKeepAlive = false;
public $command = 'ls';
public function render()
{
return view('livewire.run-command');
}
public function runCommand()
{
$this->isKeepAliveOn = true;
$this->activity = coolifyProcess($this->command, 'testing-host');
}
public function runSleepingBeauty()
{
$this->isKeepAliveOn = true;
$this->activity = coolifyProcess('x=1; while [ $x -le 40 ]; do sleep 0.1 && echo "Welcome $x times" $(( x++ )); done', 'testing-host');
}
public function runDummyProjectBuild()
{
$this->isKeepAliveOn = true;
$this->activity = coolifyProcess(<<<EOT
cd projects/dummy-project
~/.docker/cli-plugins/docker-compose build --no-cache
EOT, 'testing-host');
}
public function polling()
{
$this->activity?->refresh();
if (data_get($this->activity, 'properties.exitCode') !== null) {
$this->isKeepAliveOn = false;
}
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace App\Jobs;
use App\Services\ProcessStatus;
use Illuminate\Support\Facades\DB;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Process\InvokedProcess;
use Illuminate\Process\ProcessResult;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Process;
use Spatie\Activitylog\Contracts\Activity;
class ExecuteCoolifyProcess implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $throttleIntervalMS = 500;
protected $timeStart;
protected $currentTime;
protected $lastWriteAt = 0;
protected string $stdOutIncremental = '';
protected string $stdErrIncremental = '';
/**
* Create a new job instance.
*/
public function __construct(
public Activity $activity,
){}
/**
* Execute the job.
*/
public function handle(): ProcessResult
{
$this->timeStart = hrtime(true);
$user = $this->activity->getExtraProperty('user');
$destination = $this->activity->getExtraProperty('destination');
$port = $this->activity->getExtraProperty('port');
$command = $this->activity->getExtraProperty('command');
$delimiter = 'EOF-COOLIFY-SSH';
$sshCommand = 'ssh '
. '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
. '-o PasswordAuthentication=no '
. '-o RequestTTY=no '
// Quiet mode. Causes most warning and diagnostic messages to be suppressed.
// Errors are still out put. This is to silence for example, that warning
// Permanently added <host and key type> to the list of known hosts.
. '-q '
. "-p {$port} "
. "{$user}@{$destination} "
. " 'bash -se' << \\$delimiter" . PHP_EOL
. $command . PHP_EOL
. $delimiter;
$process = Process::start($sshCommand, $this->handleOutput(...));
$processResult = $process->wait();
$status = match ($processResult->exitCode()) {
0 => ProcessStatus::FINISHED,
default => ProcessStatus::ERROR,
};
$this->activity->properties = $this->activity->properties->merge([
'exitCode' => $processResult->exitCode(),
'stdout' => $processResult->output(),
'stderr' => $processResult->errorOutput(),
'status' => $status,
]);
$this->activity->save();
return $processResult;
}
protected function handleOutput(string $type, string $output)
{
$this->currentTime = $this->elapsedTime();
if ($type === 'out') {
$this->stdOutIncremental .= $output;
} else {
$this->stdErrIncremental .= $output;
}
$this->activity->description .= $output;
if ($this->isAfterLastThrottle()) {
// Let's write to database.
DB::transaction(function () {
$this->activity->save();
$this->lastWriteAt = $this->currentTime;
});
}
}
/**
* Decides if it's time to write again to database.
*
* @return bool
*/
protected function isAfterLastThrottle()
{
// If DB was never written, then we immediately decide we have to write.
if ($this->lastWriteAt === 0) {
return true;
}
return ($this->currentTime - $this->throttleIntervalMS) > $this->lastWriteAt;
}
protected function elapsedTime(): int
{
$timeMs = (hrtime(true) - $this->timeStart) / 1_000_000;
return intval($timeMs);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Services;
use App\Jobs\ExecuteCoolifyProcess;
use Illuminate\Process\ProcessResult;
use Spatie\Activitylog\Contracts\Activity;
class CoolifyProcess
{
protected Activity $activity;
// TODO Left 'root' as default user instead of 'coolify' because
// there's a task at TODO.md to run docker without sudo
public function __construct(
protected string $destination,
protected string $command,
protected ?int $port = 22,
protected ?string $user = 'root',
){
$this->activity = activity()
->withProperties([
'type' => 'COOLIFY_PROCESS',
'user' => $this->user,
'destination' => $this->destination,
'port' => $this->port,
'command' => $this->command,
'status' => ProcessStatus::HOLDING,
])
->log("Awaiting to start command...\n\n");
}
public function __invoke(): Activity|ProcessResult
{
$job = new ExecuteCoolifyProcess($this->activity);
if (app()->environment('testing')) {
return $job->handle();
}
dispatch($job);
return $this->activity;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Services;
enum ProcessStatus: string
{
case HOLDING = 'holding';
case IN_PROGRESS = 'in_progress';
case FINISHED = 'finished';
case ERROR = 'error';
}

24
bootstrap/helpers.php Normal file
View File

@ -0,0 +1,24 @@
<?php
use App\Services\CoolifyProcess;
use Illuminate\Process\ProcessResult;
use Spatie\Activitylog\Contracts\Activity;
if (! function_exists('coolifyProcess')) {
/**
* Run a Coolify Process, which SSH's into a machine to run the command(s).
*
*/
function coolifyProcess($command, $destination): Activity|ProcessResult
{
$process = resolve(CoolifyProcess::class, [
'destination' => $destination,
'command' => $command,
]);
$activityLog = $process();
return $activityLog;
}
}

View File

@ -9,7 +9,10 @@
"guzzlehttp/guzzle": "^7.2", "guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^10.0", "laravel/framework": "^10.0",
"laravel/sanctum": "^3.2", "laravel/sanctum": "^3.2",
"laravel/tinker": "^2.8" "laravel/tinker": "^2.8",
"livewire/livewire": "^2.12",
"spatie/laravel-activitylog": "^4.7",
"spatie/laravel-ray": "^1.32"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.9.1", "fakerphp/faker": "^1.9.1",
@ -17,10 +20,14 @@
"laravel/sail": "^1.18", "laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4", "mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^7.0", "nunomaduro/collision": "^7.0",
"pestphp/pest": "^2.0",
"phpunit/phpunit": "^10.0", "phpunit/phpunit": "^10.0",
"spatie/laravel-ignition": "^2.0" "spatie/laravel-ignition": "^2.0"
}, },
"autoload": { "autoload": {
"files": [
"bootstrap/helpers.php"
],
"psr-4": { "psr-4": {
"App\\": "app/", "App\\": "app/",
"Database\\Factories\\": "database/factories/", "Database\\Factories\\": "database/factories/",

1821
composer.lock generated

File diff suppressed because it is too large Load Diff

108
config/ray.php Normal file
View File

@ -0,0 +1,108 @@
<?php
return [
/*
* This setting controls whether data should be sent to Ray.
*
* By default, `ray()` will only transmit data in non-production environments.
*/
'enable' => env('RAY_ENABLED', true),
/*
* When enabled, all cache events will automatically be sent to Ray.
*/
'send_cache_to_ray' => env('SEND_CACHE_TO_RAY', false),
/*
* When enabled, all things passed to `dump` or `dd`
* will be sent to Ray as well.
*/
'send_dumps_to_ray' => env('SEND_DUMPS_TO_RAY', true),
/*
* When enabled all job events will automatically be sent to Ray.
*/
'send_jobs_to_ray' => env('SEND_JOBS_TO_RAY', false),
/*
* When enabled, all things logged to the application log
* will be sent to Ray as well.
*/
'send_log_calls_to_ray' => env('SEND_LOG_CALLS_TO_RAY', true),
/*
* When enabled, all queries will automatically be sent to Ray.
*/
'send_queries_to_ray' => env('SEND_QUERIES_TO_RAY', false),
/**
* When enabled, all duplicate queries will automatically be sent to Ray.
*/
'send_duplicate_queries_to_ray' => env('SEND_DUPLICATE_QUERIES_TO_RAY', false),
/*
* When enabled, slow queries will automatically be sent to Ray.
*/
'send_slow_queries_to_ray' => env('SEND_SLOW_QUERIES_TO_RAY', false),
/**
* Queries that are longer than this number of milliseconds will be regarded as slow.
*/
'slow_query_threshold_in_ms' => env('RAY_SLOW_QUERY_THRESHOLD_IN_MS', 500),
/*
* When enabled, all requests made to this app will automatically be sent to Ray.
*/
'send_requests_to_ray' => env('SEND_REQUESTS_TO_RAY', false),
/**
* When enabled, all Http Client requests made by this app will be automatically sent to Ray.
*/
'send_http_client_requests_to_ray' => env('SEND_HTTP_CLIENT_REQUESTS_TO_RAY', false),
/*
* When enabled, all views that are rendered automatically be sent to Ray.
*/
'send_views_to_ray' => env('SEND_VIEWS_TO_RAY', false),
/*
* When enabled, all exceptions will be automatically sent to Ray.
*/
'send_exceptions_to_ray' => env('SEND_EXCEPTIONS_TO_RAY', true),
/*
* When enabled, all deprecation notices will be automatically sent to Ray.
*/
'send_deprecated_notices_to_ray' => env('SEND_DEPRECATED_NOTICES_TO_RAY', false),
/*
* The host used to communicate with the Ray app.
* When using Docker on Mac or Windows, you can replace localhost with 'host.docker.internal'
* When using Docker on Linux, you can replace localhost with '172.17.0.1'
* When using Homestead with the VirtualBox provider, you can replace localhost with '10.0.2.2'
* When using Homestead with the Parallels provider, you can replace localhost with '10.211.55.2'
*/
'host' => env('RAY_HOST', 'host.docker.internal'),
/*
* The port number used to communicate with the Ray app.
*/
'port' => env('RAY_PORT', 23517),
/*
* Absolute base path for your sites or projects in Homestead,
* Vagrant, Docker, or another remote development server.
*/
'remote_path' => env('RAY_REMOTE_PATH', null),
/*
* Absolute base path for your sites or projects on your local
* computer where your IDE or code editor is running on.
*/
'local_path' => env('RAY_LOCAL_PATH', null),
/*
* When this setting is enabled, the package will not try to format values sent to Ray.
*/
'always_send_raw_values' => false,
];

View File

@ -0,0 +1,27 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateActivityLogTable extends Migration
{
public function up()
{
Schema::connection(config('activitylog.database_connection'))->create(config('activitylog.table_name'), function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('log_name')->nullable();
$table->text('description');
$table->nullableMorphs('subject', 'subject');
$table->nullableMorphs('causer', 'causer');
$table->json('properties')->nullable();
$table->timestamps();
$table->index('log_name');
});
}
public function down()
{
Schema::connection(config('activitylog.database_connection'))->dropIfExists(config('activitylog.table_name'));
}
}

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddEventColumnToActivityLogTable extends Migration
{
public function up()
{
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
$table->string('event')->nullable()->after('subject_type');
});
}
public function down()
{
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
$table->dropColumn('event');
});
}
}

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddBatchUuidColumnToActivityLogTable extends Migration
{
public function up()
{
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
$table->uuid('batch_uuid')->nullable()->after('properties');
});
}
public function down()
{
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
$table->dropColumn('batch_uuid');
});
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('jobs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('jobs');
}
};

View File

@ -1,24 +1,32 @@
version: '3.8' version: '3.8'
services: services:
php: php:
image: "coolify:${TAG:-4}"
build: build:
context: . context: ./docker/dev
dockerfile: Dockerfile dockerfile: Dockerfile
args:
WWWGROUP: '${WWWGROUP}'
ports: ports:
- "${APP_PORT:-8000}:80" - "${APP_PORT:-8000}:80"
- "${VITE_PORT:-5173}:${VITE_PORT:-5173}" - "${VITE_PORT:-5173}:${VITE_PORT:-5173}"
environment: environment:
PGID: "${USERID:-9999}" WWWUSER: "${WWWUSER}"
PUID: "${USERID:-9999}" LARAVEL_SAIL: 1
SSL_MODE: 'off' XDEBUG_MODE: "${SAIL_XDEBUG_MODE:-off}"
XDEBUG_CONFIG: "${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}"
volumes: volumes:
- .:/var/www/html:cached - .:/var/www/html
networks:
- coolify
postgres: postgres:
image: postgres:15-alpine image: postgres:15-alpine
ports: ports:
- "${FORWARD_DB_PORT:-5432}:5432" - "${FORWARD_DB_PORT:-5432}:5432"
volumes: volumes:
- db-coolify:/var/lib/postgresql/data - db-coolify:/var/lib/postgresql/data
networks:
- coolify
environment: environment:
POSTGRES_USER: "${DB_USERNAME}" POSTGRES_USER: "${DB_USERNAME}"
POSTGRES_PASSWORD: "${DB_PASSWORD}" POSTGRES_PASSWORD: "${DB_PASSWORD}"
@ -34,11 +42,22 @@ services:
] ]
retries: 3 retries: 3
timeout: 5s timeout: 5s
mailpit: testing-host:
image: "axllent/mailpit:latest" container_name: coolify-testing-host
ports: image: coolify-testing-host
- "${FORWARD_MAILPIT_PORT:-1025}:1025" build:
- "${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025" dockerfile: Dockerfile
context: ./docker/testing-host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./docker/testing-host/supervisord.conf:/etc/supervisor/conf.d/supervisord.conf
networks:
- coolify
volumes: volumes:
db-coolify: db-coolify:
driver: local driver: local
networks:
coolify:
driver: bridge

93
docker/dev/Dockerfile Normal file
View File

@ -0,0 +1,93 @@
FROM ubuntu:22.04
ARG WWWGROUP
ARG TARGETPLATFORM
ARG NODE_VERSION=18
ARG POSTGRES_VERSION=14
# https://download.docker.com/linux/static/stable/
ARG DOCKER_VERSION=20.10.18
# https://github.com/docker/compose/releases
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
ARG DOCKER_COMPOSE_VERSION=2.6.1
# https://github.com/buildpacks/pack/releases
ARG PACK_VERSION=v0.27.0
WORKDIR /var/www/html
ENV DEBIAN_FRONTEND noninteractive
ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update \
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git git-lfs supervisor sqlite3 libcap2-bin libpng-dev python2 dnsutils \
&& curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /usr/share/keyrings/ppa_ondrej_php.gpg > /dev/null \
&& echo "deb [signed-by=/usr/share/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
&& apt-get update \
&& apt-get install -y php8.2-cli php8.2-dev \
php8.2-pgsql php8.2-sqlite3 php8.2-gd \
php8.2-curl \
php8.2-imap php8.2-mysql php8.2-mbstring \
php8.2-xml php8.2-zip php8.2-bcmath php8.2-soap \
php8.2-intl php8.2-readline \
php8.2-ldap \
php8.2-msgpack php8.2-igbinary php8.2-redis php8.2-swoole \
php8.2-memcached php8.2-pcov php8.2-xdebug \
&& php -r "readfile('https://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
&& curl -sLS https://deb.nodesource.com/setup_$NODE_VERSION.x | bash - \
&& apt-get install -y nodejs \
&& npm install -g npm \
&& curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarn.gpg >/dev/null \
&& echo "deb [signed-by=/usr/share/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
&& curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/pgdg.gpg >/dev/null \
&& echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
&& apt-get update \
&& apt-get install -y yarn \
&& apt-get install -y mysql-client \
&& apt-get install -y postgresql-client-$POSTGRES_VERSION \
&& apt-get -y autoremove \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.2
RUN groupadd --force -g $WWWGROUP sail
RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
USER sail
RUN mkdir -p ~/.ssh
RUN echo "-----BEGIN OPENSSH PRIVATE KEY-----\n\
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n\
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk\n\
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA\n\
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV\n\
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==\n\
-----END OPENSSH PRIVATE KEY-----" >> ~/.ssh/id_ed25519
RUN chmod 0600 ~/.ssh/id_ed25519
USER root
COPY start-container /usr/local/bin/start-container
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY php.ini /etc/php/8.2/cli/conf.d/99-sail.ini
RUN chmod +x /usr/local/bin/start-container
# Install Docker CLI, Docker Compose, and Pack
RUN mkdir -p ~/.docker/cli-plugins
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-$DOCKER_VERSION -o /usr/bin/docker
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-$DOCKER_COMPOSE_VERSION -o ~/.docker/cli-plugins/docker-compose -o /home/sail/.docker/cli-plugins/docker-compose
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/pack-$PACK_VERSION -o /usr/local/bin/pack
RUN curl -sSL https://nixpacks.com/install.sh | bash
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack
RUN chmod +x /usr/local/bin/pack
RUN mkdir -p /home/sail/.docker/cli-plugins
RUN cp ~/.docker/cli-plugins/docker-compose /home/sail/.docker/cli-plugins/docker-compose
RUN chown -R sail:sail /home/sail
EXPOSE 8000
ENTRYPOINT ["start-container"]

4
docker/dev/php.ini Normal file
View File

@ -0,0 +1,4 @@
[PHP]
post_max_size = 100M
upload_max_filesize = 100M
variables_order = EGPCS

View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
if [ ! -z "$WWWUSER" ]; then
usermod -u $WWWUSER sail
fi
if [ ! -d /.composer ]; then
mkdir /.composer
fi
chmod -R ugo+rw /.composer
if [ $# -gt 0 ]; then
exec gosu $WWWUSER "$@"
else
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
fi

View File

@ -0,0 +1,27 @@
[supervisord]
nodaemon=true
user=root
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
[program:php]
command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80
user=sail
environment=LARAVEL_SAIL="1"
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan queue:listen
user=sail
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
numprocs=8
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/worker.log
stopwaitsecs=3600

20
docker/staging/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
FROM serversideup/php:8.2-fpm-nginx
ARG NODE_VERSION=18
ARG POSTGRES_VERSION=15
RUN apt-get update \
&& curl -sLS https://deb.nodesource.com/setup_$NODE_VERSION.x | bash - \
&& apt-get install -y nodejs \
&& npm install -g npm
RUN apt-get install -y php-pgsql openssh-client
RUN apt-get -y autoremove \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
USER root
# S6 Overlay config
COPY --chmod=755 etc/s6-overlay/ /etc/s6-overlay/

View File

@ -0,0 +1,2 @@
#!/command/execlineb -P
su webuser -c "php /var/www/html/artisan queue:listen --tries=3"

View File

@ -0,0 +1 @@
longrun

View File

@ -0,0 +1,67 @@
FROM ubuntu:22.04
ARG TARGETPLATFORM
# https://download.docker.com/linux/static/stable/
ARG DOCKER_VERSION=20.10.18
# https://github.com/docker/compose/releases
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
ARG DOCKER_COMPOSE_VERSION=2.6.1
# https://github.com/buildpacks/pack/releases
ARG PACK_VERSION=v0.27.0
ENV DEBIAN_FRONTEND noninteractive
ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update \
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git git-lfs supervisor \
sqlite3 libcap2-bin libpng-dev python2 dnsutils openssh-server sudo \
&& apt-get -y autoremove \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Setup sshd
RUN ssh-keygen -A
RUN mkdir -p /run/sshd
# Install Docker CLI, Docker Compose, and Pack
RUN mkdir -p ~/.docker/cli-plugins
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-$DOCKER_VERSION -o /usr/bin/docker
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-$DOCKER_COMPOSE_VERSION -o ~/.docker/cli-plugins/docker-compose
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/pack-$PACK_VERSION -o /usr/local/bin/pack
RUN curl -sSL https://nixpacks.com/install.sh | bash
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack
RUN groupadd docker
# Setup coolify user
RUN useradd -ms /bin/bash coolify
RUN usermod -aG sudo coolify
RUN usermod -aG docker coolify
RUN echo 'coolify ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
# Setup ssh'ing into the destination as Coolify User
USER coolify
RUN mkdir -p ~/.ssh
RUN echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuGmoeGq/pojrsyP1pszcNVuZx9iFkCELtxrh31QJ68 coolify@coolify-instance" >> ~/.ssh/authorized_keys
USER root
# Setup ssh'ing into the destination as Root
RUN mkdir -p ~/.ssh
RUN echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuGmoeGq/pojrsyP1pszcNVuZx9iFkCELtxrh31QJ68 coolify@coolify-instance" >> ~/.ssh/authorized_keys
EXPOSE 22
COPY start-container /usr/local/bin/start-container
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
RUN chmod +x /usr/local/bin/start-container
WORKDIR /root
# Prepare projects
RUN mkdir -p projects
COPY dummy-project projects/dummy-project
ENTRYPOINT ["start-container"]

View File

@ -0,0 +1,31 @@
FROM ubuntu:22.04
ARG WWWGROUP
WORKDIR /var/www/html
ENV DEBIAN_FRONTEND noninteractive
ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update \
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git git-lfs supervisor sqlite3 libcap2-bin libpng-dev python2 dnsutils \
&& curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /usr/share/keyrings/ppa_ondrej_php.gpg > /dev/null \
&& echo "deb [signed-by=/usr/share/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
&& apt-get update \
&& apt-get install -y php8.2-cli php8.2-dev \
php8.2-curl php8.2-mbstring php8.2-xml php8.2-zip php8.2-bcmath \
php8.2-intl php8.2-readline php8.2-msgpack php8.2-igbinary \
&& php -r "readfile('https://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
&& apt-get -y autoremove \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.2
EXPOSE 8000
COPY . .
ENTRYPOINT ["php", "-S", "0.0.0.0:8000"]

View File

@ -0,0 +1,10 @@
version: "3"
services:
dummy-project:
image: dummy-project
build:
context: .
dockerfile: Dockerfile
extra_hosts:
- "host.docker.internal:host-gateway"

View File

@ -0,0 +1,3 @@
<?php
echo "Hello World! " . date('Y-m-d H:i:s');

View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf

View File

@ -0,0 +1,13 @@
[supervisord]
nodaemon=true
user=root
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
[program:sshd]
command=/usr/sbin/sshd -D
user=root
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

View File

@ -1,3 +1,3 @@
@tailwind base; /* @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities; */

View File

@ -0,0 +1,3 @@
<button wire:loading.remove {{ $attributes }} class="btn btn-primary rounded-none btn-xs no-animation"> {{ $slot }} </button>
<button wire:loading class="btn btn-disabled rounded-none btn-xs no-animation"> {{ $slot }}</button>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" data-theme="light" class="h-full">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $title ?? 'Coolify' }}</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
@vite(['resources/js/app.js', 'resources/css/app.css'])
@livewireStyles
</head>
<body class="h-full">
@auth
<x-navbar />
@endauth
<main class="h-full pt-10 p-4">
{{ $slot }}
</main>
@livewireScripts
</body>
</html>

View File

@ -0,0 +1,15 @@
<nav class="flex space-x-4 p-2 fixed right-0">
@env('local')
<a href="/run-command">Run command</a>
{{-- <livewire:create-token /> --}}
<a href="/debug">Debug</a>
<a href="/debug/logs" target="_blank">Debug Logs</a>
{{-- <a href="/debug/inertia">Debug(inertia)</a> --}}
@endenv
<a href="/">Dashboard</a>
<a href="/profile">Profile</a>
<form action="/logout" method="POST">
@csrf
<button type="submit">Logout</button>
</form>
</nav>

View File

@ -0,0 +1,5 @@
<x-layout>
<livewire:run-command />
</x-layout>

View File

@ -0,0 +1,29 @@
<div>
<div>
<label for="command">
<input class="py-2 rounded ring-1" id="command" wire:model="command" type="text" />
</label>
<button wire:click="runCommand">Run command</button>
<button wire:click="runSleepingBeauty">Run sleeping beauty</button>
<button wire:click="runDummyProjectBuild">Build DummyProject</button>
</div>
<div>
<input id="manualKeepAlive" name="manualKeepAlive" type="checkbox" wire:model="manualKeepAlive">
<label for="manualKeepAlive">Real-time logs</label>
@if ($isKeepAliveOn || $manualKeepAlive)
Polling...
@endif
</div>
@isset($activity?->id)
<div>
Activity: <span>{{ $activity?->id ?? 'waiting' }}</span>
</div>
<pre style="width: 100%;overflow-y: scroll;"
@if ($isKeepAliveOn || $manualKeepAlive) wire:poll.750ms="polling" @endif> {{ data_get($activity, 'description') }}</pre>
<div>
<div>Details:</div>
<pre style="width: 100%;overflow-y: scroll;">{{ json_encode(data_get($activity, 'properties'), JSON_PRETTY_PRINT) }}</pre>
</div>
@endisset
</div>

View File

@ -11,8 +11,12 @@
<body class="antialiased"> <body class="antialiased">
<h1 class="text-3xl font-bold"> <h1 class="text-3xl font-bold">
Hello from Coolify! Coolify v4
</h1> </h1>
<p class="mt-4">
<a href="/demo"> See demo </a>
</p>
</body> </body>
</html> </html>

View File

@ -16,3 +16,5 @@
Route::get('/', function () { Route::get('/', function () {
return view('welcome'); return view('welcome');
}); });
Route::view('/demo', 'demo');

39
run Executable file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Inspired on https://github.com/adriancooney/Taskfile
# Install an alias, to be able to simply execute `run` => echo 'alias run=./run' >> ~/.aliases
#
set -e
function help {
echo "$0 <task> <args>"
echo "Tasks:"
compgen -A function | cat -n
}
function default {
help
}
function bash {
docker-compose exec -u $(id -u) php bash
}
# The user with native SSH capability
function coolify-bash {
docker-compose exec -u coolify php bash
}
function root-bash {
docker-compose exec php bash
}
# Usage: ./Taskfile envFile:set FOOBAR abc
# This will set the FOOBAR variable to "abc" in the .env file
function envFile:set {
sed -i "s#^$1=.*#$1=$2#g" .env
}
TIMEFORMAT="Task completed in %3lR"
time "${@:-default}"

View File

@ -0,0 +1,32 @@
<?php
use Tests\Support\Output;
it('starts a docker container correctly', function () {
$coolifyNamePrefix = 'coolify_test_';
$format = '{"ID":"{{ .ID }}", "Image": "{{ .Image }}", "Names":"{{ .Names }}"}';
$areThereCoolifyTestContainers = "docker ps --filter=\"name={$coolifyNamePrefix}*\" --format '{$format}' ";
// Generate a known name
$containerName = 'coolify_test_' . now()->format('Ymd_his');
$host = 'testing-host';
// Assert there's no containers start with coolify_test_*
$processResult = coolifyProcess($areThereCoolifyTestContainers, $host);
$containers = Output::containerList($processResult->output());
expect($containers)->toBeEmpty();
// start a container nginx -d --name = $containerName
$processResult = coolifyProcess("docker run -d --name {$containerName} nginx", $host);
expect($processResult->successful())->toBeTrue();
// docker ps name = $container
$processResult = coolifyProcess($areThereCoolifyTestContainers, $host);
$containers = Output::containerList($processResult->output());
expect($containers->where('Names', $containerName)->count())->toBe(1);
// Stop testing containers
$processResult = coolifyProcess("docker stop $(docker ps --filter='name={$coolifyNamePrefix}*' -q)", $host);
expect($processResult->successful())->toBeTrue();
});

View File

@ -1,19 +0,0 @@
<?php
namespace Tests\Feature;
// use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*/
public function test_the_application_returns_a_successful_response(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}

48
tests/Pest.php Normal file
View File

@ -0,0 +1,48 @@
<?php
/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "uses()" function to bind a different classes or traits.
|
*/
uses(
Tests\TestCase::class,
// Illuminate\Foundation\Testing\RefreshDatabase::class,
)->in('Feature');
/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/
expect()->extend('toBeOne', function () {
return $this->toBe(1);
});
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/
function something()
{
// ..
}

17
tests/Support/Output.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace Tests\Support;
use Illuminate\Support\Collection;
class Output
{
public static function containerList($rawOutput): Collection
{
$outputLines = explode(PHP_EOL, $rawOutput);
return collect($outputLines)
->reject(fn($line) => empty($line))
->map(fn($outputLine) => json_decode($outputLine, flags: JSON_THROW_ON_ERROR));
}
}

View File

@ -1,16 +1,5 @@
<?php <?php
namespace Tests\Unit; test('that true is true', function () {
expect(true)->toBeTrue();
use PHPUnit\Framework\TestCase; });
class ExampleTest extends TestCase
{
/**
* A basic test example.
*/
public function test_that_true_is_true(): void
{
$this->assertTrue(true);
}
}