Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/feat--terminal-pty' into feat-…
Browse files Browse the repository at this point in the history
…-terminal-pty
  • Loading branch information
LEstradioto committed Sep 11, 2024
2 parents 33e9c9b + 117fbeb commit 2edcd01
Show file tree
Hide file tree
Showing 12 changed files with 273 additions and 220 deletions.
16 changes: 9 additions & 7 deletions app/Livewire/Project/Shared/Terminal.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
44 changes: 35 additions & 9 deletions app/Livewire/RunCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace App\Livewire;

use App\Models\Server;
use Livewire\Attributes\On;
use Livewire\Component;

Expand All @@ -23,33 +22,60 @@ public function mount($servers)

private function getAllActiveContainers()
{
return Server::all()->flatMap(function ($server) {
return collect($this->servers)->flatMap(function ($server) {
if (! $server->isFunctional()) {
return [];
}

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';

return [
'name' => $resource->name,
'connection_name' => $container_name,
'uuid' => $resource->uuid,
'status' => 'running',
'server' => $server,
'server_uuid' => $server->uuid,
];
}
}

if (class_basename($resource) === 'Application' || class_basename($resource) === 'Service') {
if ($server->isSwarm()) {
$container_name = $resource->uuid.'_'.$resource->uuid;
} else {
if (class_basename($resource) === 'Application') {
if (! $server->isSwarm()) {
$current_containers = getCurrentApplicationContainerStatus($server, $resource->id, includePullrequests: true);
$container_name = data_get($current_containers->first(), 'Names');
}
$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,
];
Expand Down
14 changes: 14 additions & 0 deletions bootstrap/helpers/docker.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 4 additions & 6 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ services:
retries: 10
timeout: 2s
soketi:
image: 'ghcr.io/coollabsio/coolify-realtime:latest'
ports:
- "${SOKETI_PORT:-6001}:6001"
- "6002:6002"
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ services:
networks:
- coolify
soketi:
image: 'quay.io/soketi/soketi:1.6-16-alpine'
container_name: coolify-realtime
restart: always
networks:
Expand Down
9 changes: 9 additions & 0 deletions docker/coolify-realtime/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
13 changes: 13 additions & 0 deletions docker/coolify-realtime/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -36,4 +24,4 @@ trap 'forward_signal TERM' TERM
wait -n

# Exit with status of process that exited first
exit $?
exit $?
64 changes: 32 additions & 32 deletions terminal-server.js → docker/coolify-realtime/terminal-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'];

// 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
},
});
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];

// Verify presence of required tokens
if (!laravelSession || !xsrfToken) {
return callback(false, 401, 'Unauthorized: Missing required tokens');
}

if (response.status === 200) {
// Authentication successful
callback(true);
} else {
callback(false, 401, 'Unauthorized: Invalid credentials');
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');
}
};


Expand Down
Loading

0 comments on commit 2edcd01

Please sign in to comment.