Compare commits

...
Author SHA1 Message Date
Jeppe B f4dd49208a Finish Coolify GitHub runner management 2026-06-02 11:46:44 +02:00
5 changed files with 291 additions and 1 deletions
@@ -53,6 +53,7 @@ class coolify_manager
'targets' => $this->listTargets(),
'availability' => $this->availabilitySummary(),
'load_balancer' => $this->loadBalancerSummary(),
'github_runners' => $this->githubRunnerSummary(),
];
}
@@ -1371,6 +1372,54 @@ class coolify_manager
];
}
public function githubRunnerSummary(): array
{
$this->ensureSchema();
$repositories = $this->githubRunnerRepositories([]);
$labels = $this->githubRunnerLabels(null);
$countPerRepo = $this->githubRunnerCount(null);
$serviceUuid = $this->nullableString($this->coolifyConfigValue('github_runner_service_uuid', ''));
$token = $this->githubRunnerToken([]);
$config = [
'configured' => $serviceUuid !== null,
'service_uuid' => $serviceUuid,
'service_name' => $this->githubRunnerServiceName(null),
'repositories' => $repositories,
'labels' => $labels,
'count_per_repo' => $countPerRepo,
'desired_runner_count' => count($repositories) * $countPerRepo,
'compose_hash' => $this->composeHash($this->githubRunnerComposeTemplate($repositories, $labels, $countPerRepo)),
'token_set' => $token !== '',
'token_source' => trim((string)(getenv('GITHUB_RUNNER_TOKEN') ?: getenv('GITHUB_TOKEN') ?: '')) !== '' ? 'env' : ($token !== '' ? 'config' : null),
];
$base = [
'status' => $serviceUuid === null ? 'not_configured' : 'configured',
'config' => $config,
'service' => null,
'last_error' => null,
];
if ($serviceUuid === null || !coolify_schema_bootstrap::tablesExist()) {
return $base;
}
try {
$instanceId = $this->defaultInstanceId();
$instance = $this->getInstance($instanceId);
$service = $this->clientForInstance($instance)->getService($serviceUuid);
return array_replace($base, [
'status' => 'ok',
'service' => self::redactCoolifyResponse($service),
]);
} catch (Throwable $throwable) {
return array_replace($base, [
'status' => 'unknown',
'last_error' => $throwable->getMessage(),
]);
}
}
public function deployGithubRunners(array $input, ?int $actorUserId = null): array
{
$this->ensureSchema();
@@ -8,10 +8,22 @@ require_once WD . '/modules/coolify/config/coolify_hetzner_load_balancer_id_c.ph
require_once WD . '/modules/coolify/config/coolify_lb_automation_enabled_c.php';
require_once WD . '/modules/coolify/config/coolify_lb_automation_mode_c.php';
require_once WD . '/modules/coolify/config/coolify_public_gateway_host_c.php';
require_once WD . '/modules/coolify/config/coolify_github_runner_backend_repository_c.php';
require_once WD . '/modules/coolify/config/coolify_github_runner_count_per_repo_c.php';
require_once WD . '/modules/coolify/config/coolify_github_runner_frontend_repository_c.php';
require_once WD . '/modules/coolify/config/coolify_github_runner_labels_c.php';
require_once WD . '/modules/coolify/config/coolify_github_runner_service_uuid_c.php';
require_once WD . '/modules/coolify/config/coolify_github_runner_token_c.php';
use modules\coolify\config\coolify_hetzner_cloud_api_token_c;
use modules\coolify\config\coolify_hetzner_load_balancer_id_c;
use modules\coolify\config\coolify_enabled_c;
use modules\coolify\config\coolify_github_runner_backend_repository_c;
use modules\coolify\config\coolify_github_runner_count_per_repo_c;
use modules\coolify\config\coolify_github_runner_frontend_repository_c;
use modules\coolify\config\coolify_github_runner_labels_c;
use modules\coolify\config\coolify_github_runner_service_uuid_c;
use modules\coolify\config\coolify_github_runner_token_c;
use modules\coolify\config\coolify_lb_automation_enabled_c;
use modules\coolify\config\coolify_lb_automation_mode_c;
use modules\coolify\config\coolify_public_gateway_host_c;
@@ -29,6 +41,12 @@ class coolify_c
public coolify_hetzner_load_balancer_id_c $hetzner_load_balancer_id;
public coolify_hetzner_cloud_api_token_c $hetzner_cloud_api_token;
public coolify_public_gateway_host_c $public_gateway_host;
public coolify_github_runner_token_c $github_runner_token;
public coolify_github_runner_service_uuid_c $github_runner_service_uuid;
public coolify_github_runner_frontend_repository_c $github_runner_frontend_repository;
public coolify_github_runner_backend_repository_c $github_runner_backend_repository;
public coolify_github_runner_labels_c $github_runner_labels;
public coolify_github_runner_count_per_repo_c $github_runner_count_per_repo;
public function __construct()
{
@@ -40,6 +58,12 @@ class coolify_c
coolify_hetzner_load_balancer_id_c::class,
coolify_hetzner_cloud_api_token_c::class,
coolify_public_gateway_host_c::class,
coolify_github_runner_token_c::class,
coolify_github_runner_service_uuid_c::class,
coolify_github_runner_frontend_repository_c::class,
coolify_github_runner_backend_repository_c::class,
coolify_github_runner_labels_c::class,
coolify_github_runner_count_per_repo_c::class,
]);
$this->enabled = new coolify_enabled_c();
$this->lb_automation_enabled = new coolify_lb_automation_enabled_c();
@@ -47,12 +71,18 @@ class coolify_c
$this->hetzner_load_balancer_id = new coolify_hetzner_load_balancer_id_c();
$this->hetzner_cloud_api_token = new coolify_hetzner_cloud_api_token_c();
$this->public_gateway_host = new coolify_public_gateway_host_c();
$this->github_runner_token = new coolify_github_runner_token_c();
$this->github_runner_service_uuid = new coolify_github_runner_service_uuid_c();
$this->github_runner_frontend_repository = new coolify_github_runner_frontend_repository_c();
$this->github_runner_backend_repository = new coolify_github_runner_backend_repository_c();
$this->github_runner_labels = new coolify_github_runner_labels_c();
$this->github_runner_count_per_repo = new coolify_github_runner_count_per_repo_c();
}
public function getConfigRequest(): array
{
return array_map(static function (array $row): array {
if (($row['variable'] ?? '') === 'hetzner_cloud_api_token') {
if (in_array(($row['variable'] ?? ''), ['hetzner_cloud_api_token', 'github_runner_token'], true)) {
$secretSet = trim((string)($row['value'] ?? '')) !== '';
$row['value'] = $secretSet ? '[redacted]' : '';
$row['secret_set'] = $secretSet;
+162
View File
@@ -11217,6 +11217,46 @@ paths:
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
/superuser/coolify/github-runners:
get:
tags:
- Superuser
summary: Coolify-managed GitHub Actions runner state
operationId: getSuperuserCoolifyGithubRunners
responses:
'200':
description: GitHub runner summary returned successfully
content:
application/json:
schema:
$ref: '#/components/schemas/SuperuserCoolifyGithubRunnerResponse'
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
/superuser/coolify/github-runners/deploy:
post:
tags:
- Superuser
summary: Deploy Coolify-managed GitHub Actions runners
operationId: deploySuperuserCoolifyGithubRunners
requestBody:
required: false
content:
application/json:
schema:
$ref: '#/components/schemas/SuperuserCoolifyGithubRunnerDeployRequest'
responses:
'200':
description: GitHub runner deployment result returned
content:
application/json:
schema:
$ref: '#/components/schemas/SuperuserCoolifyGithubRunnerDeployResponse'
'409': { $ref: '#/components/responses/Conflict' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
/superuser/coolify/gateways:
get:
tags:
@@ -13512,6 +13552,8 @@ components:
additionalProperties: true
load_balancer:
$ref: '#/components/schemas/SuperuserCoolifyLoadBalancer'
github_runners:
$ref: '#/components/schemas/SuperuserCoolifyGithubRunnerSummary'
meta:
type: object
additionalProperties: true
@@ -13564,6 +13606,126 @@ components:
type: object
additionalProperties: true
SuperuserCoolifyGithubRunnerResponse:
type: object
properties:
success:
type: boolean
data:
$ref: '#/components/schemas/SuperuserCoolifyGithubRunnerSummary'
meta:
type: object
additionalProperties: true
includes:
type: object
additionalProperties: true
SuperuserCoolifyGithubRunnerDeployRequest:
type: object
properties:
dry_run:
type: boolean
default: true
enforce:
type: boolean
default: false
instance_id:
type: integer
service_uuid:
type: string
service_name:
type: string
frontend_repository:
type: string
example: copenhagentruckwash/pleno-vue
backend_repository:
type: string
example: copenhagentruckwash/api
labels:
type: string
example: self-hosted,Linux,X64,default
count_per_repo:
type: integer
minimum: 1
maximum: 10
default: 1
github_token:
type: string
writeOnly: true
persist_token:
type: boolean
default: false
project_uuid:
type: string
environment_name:
type: string
environment_uuid:
type: string
server_uuid:
type: string
destination_uuid:
type: string
SuperuserCoolifyGithubRunnerDeployResponse:
type: object
properties:
success:
type: boolean
data:
type: object
additionalProperties: true
meta:
type: object
additionalProperties: true
includes:
type: object
additionalProperties: true
SuperuserCoolifyGithubRunnerSummary:
type: object
properties:
status:
type: string
enum: [not_configured, configured, ok, unknown]
config:
type: object
properties:
configured:
type: boolean
service_uuid:
type: string
nullable: true
service_name:
type: string
repositories:
type: object
additionalProperties:
type: string
labels:
type: array
items:
type: string
count_per_repo:
type: integer
desired_runner_count:
type: integer
compose_hash:
type: string
token_set:
type: boolean
token_source:
type: string
nullable: true
enum: [env, config]
service:
type: object
nullable: true
additionalProperties: true
last_error:
type: string
nullable: true
SuperuserCoolifyLoadBalancerResponse:
type: object
properties:
@@ -97,6 +97,40 @@ class superuserCoolifyRoute
'superuser_coolify_manage' => 'Deploy the latest Coolify API code for the public gateway host',
]);
$this->get('/superuser/coolify/github-runners', function () {
global $response;
$this->requirePermission('superuser_coolify_view');
$response->success((new coolify_manager())->githubRunnerSummary());
}, [
'superuser_coolify_view' => 'View Coolify-managed GitHub Actions runner state',
]);
$this->post('/superuser/coolify/github-runners/deploy', function () {
global $response;
$this->requirePermission('superuser_coolify_manage');
try {
$parameters = $this->getParametersAsArray();
$parameters['dry_run'] = array_key_exists('dry_run', $parameters)
? filter_var($parameters['dry_run'], FILTER_VALIDATE_BOOLEAN)
: !filter_var($parameters['enforce'] ?? false, FILTER_VALIDATE_BOOLEAN);
$result = (new coolify_manager())->deployGithubRunners(
$parameters,
$this->actorUserId()
);
if (($result['ok'] ?? false) !== true) {
$response->error($result, 409);
}
$response->success($result);
} catch (Throwable $throwable) {
$response->error(['message' => $throwable->getMessage()], 409);
}
}, [
'superuser_coolify_manage' => 'Create or update the Coolify-managed GitHub Actions runner service',
]);
$this->get('/superuser/coolify/gateways', function () {
global $response;
@@ -587,6 +587,12 @@ it('defines Coolify schema, route permissions, and replication integration hooks
expect($schema)->toContain('65.21.214.30');
expect($schema)->toContain('23.88.23.183');
expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'enabled'");
expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'github_runner_token'");
expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'github_runner_service_uuid'");
expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'github_runner_frontend_repository'");
expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'github_runner_backend_repository'");
expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'github_runner_labels'");
expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'github_runner_count_per_repo'");
expect($route)->toContain('/superuser/coolify');
expect($route)->toContain('/superuser/coolify/load-balancer');
@@ -594,6 +600,8 @@ it('defines Coolify schema, route permissions, and replication integration hooks
expect($route)->toContain('/superuser/coolify/load-balancer/routes/deploy');
expect($route)->toContain('/superuser/coolify/load-balancer/api/deploy');
expect($route)->toContain('/superuser/coolify/gateways');
expect($route)->toContain('/superuser/coolify/github-runners');
expect($route)->toContain('/superuser/coolify/github-runners/deploy');
expect($route)->toContain('/superuser/coolify/gateways/{id}/test');
expect($route)->toContain('/superuser/coolify/instances/{id}/test');
expect($route)->toContain('/superuser/coolify/instances/{id}/placement');
@@ -660,6 +668,8 @@ it('defines Coolify schema, route permissions, and replication integration hooks
expect($manager)->toContain('reconcileLoadBalancer');
expect($manager)->toContain('deployGatewayApplicationRoutes');
expect($manager)->toContain('deployGatewayApiCode');
expect($manager)->toContain('deployGithubRunners');
expect($manager)->toContain('githubRunnerSummary');
expect($manager)->toContain('gateway_api_code_deploy');
expect($manager)->toContain('deploy_gateway_route_after_code');
expect($manager)->toContain('loadBalancerReleaseGatewayTargets');
@@ -723,6 +733,8 @@ it('defines Coolify schema, route permissions, and replication integration hooks
expect($cron)->toContain('CoolifyLoadBalancerReconcileCron');
expect($coolifyConfig)->toContain('[redacted]');
expect($coolifyConfig)->toContain('secret_set');
expect($coolifyConfig)->toContain('github_runner_token');
expect($coolifyConfig)->toContain('github_runner_service_uuid');
expect($tokenConfig)->toContain('replication_secret_box::encrypt');
expect($openapi)->toContain('/superuser/coolify:');
expect($openapi)->toContain('operationId: getSuperuserCoolifyLoadBalancer');
@@ -730,10 +742,13 @@ it('defines Coolify schema, route permissions, and replication integration hooks
expect($openapi)->toContain('operationId: deploySuperuserCoolifyGatewayRoutes');
expect($openapi)->toContain('operationId: deploySuperuserCoolifyGatewayApiCode');
expect($openapi)->toContain('operationId: listSuperuserCoolifyGateways');
expect($openapi)->toContain('operationId: getSuperuserCoolifyGithubRunners');
expect($openapi)->toContain('operationId: deploySuperuserCoolifyGithubRunners');
expect($openapi)->toContain('operationId: testSuperuserCoolifyGateway');
expect($openapi)->toContain('operationId: discoverSuperuserCoolifyInstancePlacement');
expect($openapi)->toContain('operationId: createSuperuserCoolifyTarget');
expect($openapi)->toContain('SuperuserCoolifyTarget');
expect($openapi)->toContain('SuperuserCoolifyLoadBalancer');
expect($openapi)->toContain('SuperuserCoolifyGithubRunnerSummary');
expect($openapi)->toContain('SuperuserCoolifyGateway');
});