diff --git a/app/Livewire/Project/Shared/Terminal.php b/app/Livewire/Project/Shared/Terminal.php index 331392118..e070ee367 100644 --- a/app/Livewire/Project/Shared/Terminal.php +++ b/app/Livewire/Project/Shared/Terminal.php @@ -11,17 +11,19 @@ class Terminal extends Component #[On('send-terminal-command')] public function sendTerminalCommand($isContainer, $identifier, $serverUuid) { - $server = Server::whereUuid($serverUuid)->firstOrFail(); + $server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail(); - if (auth()->user()) { - $teams = auth()->user()->teams->pluck('id'); - if (! $teams->contains($server->team_id) && ! $teams->contains(0)) { - throw new \Exception('User is not part of the team that owns this server'); - } - } + // if (auth()->user()) { + // $teams = auth()->user()->teams->pluck('id'); + // if (! $teams->contains($server->team_id) && ! $teams->contains(0)) { + // throw new \Exception('User is not part of the team that owns this server'); + // } + // } if ($isContainer) { + ray($identifier); $status = getContainerStatus($server, $identifier); + ray($status); if ($status !== 'running') { return handleError(new \Exception('Container is not running'), $this); } diff --git a/app/Livewire/RunCommand.php b/app/Livewire/RunCommand.php index 2d01cbca0..449ab1ea9 100644 --- a/app/Livewire/RunCommand.php +++ b/app/Livewire/RunCommand.php @@ -2,7 +2,6 @@ namespace App\Livewire; -use App\Models\Server; use Livewire\Attributes\On; use Livewire\Component; @@ -23,7 +22,7 @@ class RunCommand extends Component private function getAllActiveContainers() { - return Server::all()->flatMap(function ($server) { + return collect($this->servers)->flatMap(function ($server) { if (! $server->isFunctional()) { return []; } @@ -31,25 +30,52 @@ class RunCommand extends Component return $server->definedResources() ->filter(function ($resource) { $status = method_exists($resource, 'realStatus') ? $resource->realStatus() : (method_exists($resource, 'status') ? $resource->status() : 'exited'); + return str_starts_with($status, 'running:'); }) ->map(function ($resource) use ($server) { - $container_name = $resource->uuid; + if (isDev()) { + if (data_get($resource, 'name') === 'coolify-db') { + $container_name = 'coolify-db'; - if (class_basename($resource) === 'Application' || class_basename($resource) === 'Service') { - if ($server->isSwarm()) { - $container_name = $resource->uuid.'_'.$resource->uuid; - } else { - $current_containers = getCurrentApplicationContainerStatus($server, $resource->id, includePullrequests: true); - $container_name = data_get($current_containers->first(), 'Names'); + return [ + 'name' => $resource->name, + 'connection_name' => $container_name, + 'uuid' => $resource->uuid, + 'status' => 'running', + 'server' => $server, + 'server_uuid' => $server->uuid, + ]; } } + if (class_basename($resource) === 'Application') { + if (! $server->isSwarm()) { + $current_containers = getCurrentApplicationContainerStatus($server, $resource->id, includePullrequests: true); + } + $status = $resource->status; + } elseif (class_basename($resource) === 'Service') { + $current_containers = getCurrentServiceContainerStatus($server, $resource->id); + $status = $resource->status(); + } else { + $status = getContainerStatus($server, $resource->uuid); + if ($status === 'running') { + $current_containers = collect([ + 'Names' => $resource->name, + ]); + } + } + if ($server->isSwarm()) { + $container_name = $resource->uuid.'_'.$resource->uuid; + } else { + $container_name = data_get($current_containers->first(), 'Names'); + } + return [ 'name' => $resource->name, 'connection_name' => $container_name, 'uuid' => $resource->uuid, - 'status' => $resource->status, + 'status' => $status, 'server' => $server, 'server_uuid' => $server->uuid, ]; diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 90093deb8..825668743 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -40,6 +40,20 @@ function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pul return $containers; } +function getCurrentServiceContainerStatus(Server $server, int $id): Collection +{ + $containers = collect([]); + if (! $server->isSwarm()) { + $containers = instant_remote_process(["docker ps -a --filter='label=coolify.serviceId={$id}' --format '{{json .}}' "], $server); + $containers = format_docker_command_output_to_json($containers); + $containers = $containers->filter(); + + return $containers; + } + + return $containers; +} + function format_docker_command_output_to_json($rawOutput): Collection { $outputLines = explode(PHP_EOL, $rawOutput); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 64eb1d2a4..16bb354a5 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -43,25 +43,23 @@ services: - /data/coolify/_volumes/redis/:/data # - coolify-redis-data-dev:/data soketi: + build: + context: . + dockerfile: ./docker/coolify-realtime/Dockerfile env_file: - .env ports: - "${FORWARD_SOKETI_PORT:-6001}:6001" - "6002:6002" volumes: - - ./docker/soketi-entrypoint/soketi-entrypoint.sh:/soketi-entrypoint.sh - - ./package.json:/terminal/package.json - - ./package-lock.json:/terminal/package-lock.json - - ./terminal-server.js:/terminal/terminal-server.js - ./storage:/var/www/html/storage - entrypoint: ["/bin/sh", "/soketi-entrypoint.sh"] environment: SOKETI_DEBUG: "false" SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}" SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}" SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}" vite: - image: node:alpine + image: node:20 pull_policy: always working_dir: /var/www/html # environment: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index ea882dd2d..536a7183b 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -109,6 +109,7 @@ services: retries: 10 timeout: 2s soketi: + image: 'ghcr.io/coollabsio/coolify-realtime:latest' ports: - "${SOKETI_PORT:-6001}:6001" - "6002:6002" diff --git a/docker-compose.windows.yml b/docker-compose.windows.yml index 0db35eb96..2246e1867 100644 --- a/docker-compose.windows.yml +++ b/docker-compose.windows.yml @@ -102,8 +102,8 @@ services: interval: 5s retries: 10 timeout: 2s -soketi: - image: 'quay.io/soketi/soketi:1.6-16-alpine' + soketi: + image: 'ghcr.io/coollabsio/coolify-realtime:latest' pull_policy: always container_name: coolify-realtime restart: always diff --git a/docker-compose.yml b/docker-compose.yml index 930c0a6b9..7d71ba8e1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,6 @@ services: networks: - coolify soketi: - image: 'quay.io/soketi/soketi:1.6-16-alpine' container_name: coolify-realtime restart: always networks: diff --git a/docker/coolify-realtime/Dockerfile b/docker/coolify-realtime/Dockerfile new file mode 100644 index 000000000..9a7a68376 --- /dev/null +++ b/docker/coolify-realtime/Dockerfile @@ -0,0 +1,9 @@ +FROM quay.io/soketi/soketi:1.6-16-alpine +WORKDIR /terminal +RUN apk add --no-cache openssh-client make g++ python3 +COPY docker/coolify-realtime/package.json ./ +RUN npm i +RUN npm rebuild node-pty --update-binary +COPY docker/coolify-realtime/soketi-entrypoint.sh /soketi-entrypoint.sh +COPY docker/coolify-realtime/terminal-server.js /terminal/terminal-server.js +ENTRYPOINT ["/bin/sh", "/soketi-entrypoint.sh"] diff --git a/docker/coolify-realtime/package.json b/docker/coolify-realtime/package.json new file mode 100644 index 000000000..90d4f77db --- /dev/null +++ b/docker/coolify-realtime/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", + "cookie": "^0.6.0", + "axios": "1.7.5", + "dotenv": "^16.4.5", + "node-pty": "^1.0.0", + "ws": "^8.17.0" + } +} diff --git a/docker/soketi-entrypoint/soketi-entrypoint.sh b/docker/coolify-realtime/soketi-entrypoint.sh similarity index 79% rename from docker/soketi-entrypoint/soketi-entrypoint.sh rename to docker/coolify-realtime/soketi-entrypoint.sh index 808e306e7..04e43ac81 100644 --- a/docker/soketi-entrypoint/soketi-entrypoint.sh +++ b/docker/coolify-realtime/soketi-entrypoint.sh @@ -1,16 +1,4 @@ #!/bin/sh - -# Install openssh-client -apk add --no-cache openssh-client make g++ python3 - -cd /terminal - -# Install npm dependencies -npm ci - -# Rebuild node-pty -npm rebuild node-pty --update-binary - # Function to timestamp logs timestamp() { date "+%Y-%m-%d %H:%M:%S" @@ -36,4 +24,4 @@ trap 'forward_signal TERM' TERM wait -n # Exit with status of process that exited first -exit $? \ No newline at end of file +exit $? diff --git a/terminal-server.js b/docker/coolify-realtime/terminal-server.js similarity index 81% rename from terminal-server.js rename to docker/coolify-realtime/terminal-server.js index a1555bf30..6afbd3445 100755 --- a/terminal-server.js +++ b/docker/coolify-realtime/terminal-server.js @@ -16,40 +16,40 @@ const server = http.createServer((req, res) => { }); const verifyClient = async (info, callback) => { - const cookies = cookie.parse(info.req.headers.cookie || ''); - const origin = new URL(info.origin); - const protocol = origin.protocol; - const xsrfToken = cookies['XSRF-TOKEN']; + const cookies = cookie.parse(info.req.headers.cookie || ''); + const origin = new URL(info.origin); + const protocol = origin.protocol; + const xsrfToken = cookies['XSRF-TOKEN']; - // Generate session cookie name based on APP_NAME - const appName = process.env.APP_NAME || 'laravel'; - const sessionCookieName = `${appName.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}_session`; - const laravelSession = cookies[sessionCookieName]; + // Generate session cookie name based on APP_NAME + const appName = process.env.APP_NAME || 'laravel'; + const sessionCookieName = `${appName.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}_session`; + const laravelSession = cookies[sessionCookieName]; - // Verify presence of required tokens - if (!laravelSession || !xsrfToken) { - return callback(false, 401, 'Unauthorized: Missing required tokens'); - } - - try { - // Authenticate with Laravel backend - const response = await axios.post(`${protocol}//coolify/terminal/auth`, null, { - headers: { - 'Cookie': `${sessionCookieName}=${laravelSession}`, - 'X-XSRF-TOKEN': xsrfToken - }, - }); - - if (response.status === 200) { - // Authentication successful - callback(true); - } else { - callback(false, 401, 'Unauthorized: Invalid credentials'); + // Verify presence of required tokens + if (!laravelSession || !xsrfToken) { + return callback(false, 401, 'Unauthorized: Missing required tokens'); + } + + try { + // Authenticate with Laravel backend + const response = await axios.post(`${protocol}//coolify/terminal/auth`, null, { + headers: { + 'Cookie': `${sessionCookieName}=${laravelSession}`, + 'X-XSRF-TOKEN': xsrfToken + }, + }); + + if (response.status === 200) { + // Authentication successful + callback(true); + } else { + callback(false, 401, 'Unauthorized: Invalid credentials'); + } + } catch (error) { + console.error('Authentication error:', error.message); + callback(false, 500, 'Internal Server Error'); } - } catch (error) { - console.error('Authentication error:', error.message); - callback(false, 500, 'Internal Server Error'); - } }; diff --git a/resources/views/livewire/project/shared/terminal.blade.php b/resources/views/livewire/project/shared/terminal.blade.php index 4e1a086e2..f351bd42f 100644 --- a/resources/views/livewire/project/shared/terminal.blade.php +++ b/resources/views/livewire/project/shared/terminal.blade.php @@ -1,11 +1,12 @@