feat: Add backend support for one-click Rust server deployment
All checks were successful
Test Asgard Runner / test (push) Successful in 2s
All checks were successful
Test Asgard Runner / test (push) Successful in 2s
Add deploy endpoint, DTO, NATS command publisher, and WebSocket bridge subscription to support the one-click server deployment feature via the companion agent. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
41
backend-nest/src/modules/servers/dto/deploy-server.dto.ts
Normal file
41
backend-nest/src/modules/servers/dto/deploy-server.dto.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { IsString, IsInt, Min, Max, MinLength } from 'class-validator';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class DeployServerDto {
|
||||||
|
@ApiProperty({ example: 'My Rust Server', description: 'Server hostname' })
|
||||||
|
@IsString()
|
||||||
|
server_name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 100, description: 'Maximum player slots' })
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
@Max(500)
|
||||||
|
max_players: number;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 4000, description: 'World size (1000-8000)' })
|
||||||
|
@IsInt()
|
||||||
|
@Min(1000)
|
||||||
|
@Max(8000)
|
||||||
|
world_size: number;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 12345, description: 'Map seed' })
|
||||||
|
@IsInt()
|
||||||
|
seed: number;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 28015, description: 'Server game port' })
|
||||||
|
@IsInt()
|
||||||
|
@Min(1024)
|
||||||
|
@Max(65535)
|
||||||
|
server_port: number;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 28016, description: 'RCON port' })
|
||||||
|
@IsInt()
|
||||||
|
@Min(1024)
|
||||||
|
@Max(65535)
|
||||||
|
rcon_port: number;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'changeme', description: 'RCON password (min 6 chars)' })
|
||||||
|
@IsString()
|
||||||
|
@MinLength(6)
|
||||||
|
rcon_password: string;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
|||||||
import { ServersService } from './servers.service';
|
import { ServersService } from './servers.service';
|
||||||
import { UpdateServerConfigDto } from './dto/update-config.dto';
|
import { UpdateServerConfigDto } from './dto/update-config.dto';
|
||||||
import { SendCommandDto } from './dto/send-command.dto';
|
import { SendCommandDto } from './dto/send-command.dto';
|
||||||
|
import { DeployServerDto } from './dto/deploy-server.dto';
|
||||||
import { CurrentTenant } from '../../common/decorators/current-tenant.decorator';
|
import { CurrentTenant } from '../../common/decorators/current-tenant.decorator';
|
||||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
||||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||||
@@ -62,4 +63,14 @@ export class ServersController {
|
|||||||
async restartServer(@CurrentTenant() licenseId: string) {
|
async restartServer(@CurrentTenant() licenseId: string) {
|
||||||
return await this.serversService.restartServer(licenseId);
|
return await this.serversService.restartServer(licenseId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('deploy')
|
||||||
|
@RequirePermission('server.manage')
|
||||||
|
@ApiOperation({ summary: 'Deploy Rust server via companion agent' })
|
||||||
|
async deployServer(
|
||||||
|
@CurrentTenant() licenseId: string,
|
||||||
|
@Body() dto: DeployServerDto,
|
||||||
|
) {
|
||||||
|
return await this.serversService.deployServer(licenseId, dto);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ServerConnection } from '../../entities/server-connection.entity';
|
|||||||
import { ServerConfig } from '../../entities/server-config.entity';
|
import { ServerConfig } from '../../entities/server-config.entity';
|
||||||
import { NatsService } from '../../services/nats.service';
|
import { NatsService } from '../../services/nats.service';
|
||||||
import { UpdateServerConfigDto } from './dto/update-config.dto';
|
import { UpdateServerConfigDto } from './dto/update-config.dto';
|
||||||
|
import { DeployServerDto } from './dto/deploy-server.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServersService {
|
export class ServersService {
|
||||||
@@ -86,4 +87,12 @@ export class ServersService {
|
|||||||
await this.natsService.sendServerCommand(licenseId, 'restart');
|
await this.natsService.sendServerCommand(licenseId, 'restart');
|
||||||
return { message: 'Restart command sent' };
|
return { message: 'Restart command sent' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deploy Rust server via companion agent
|
||||||
|
*/
|
||||||
|
async deployServer(licenseId: string, dto: DeployServerDto) {
|
||||||
|
await this.natsService.sendDeployCommand(licenseId, { ...dto });
|
||||||
|
return { message: 'Deployment started' };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ export class NatsBridgeService implements OnModuleInit {
|
|||||||
this.emit(licenseId, 'server_status', data);
|
this.emit(licenseId, 'server_status', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.nats.subscribe('corrosion.*.deploy.status', (data, subject) => {
|
||||||
|
const licenseId = subject.split('.')[1];
|
||||||
|
this.emit(licenseId, 'deploy_status', data);
|
||||||
|
});
|
||||||
|
|
||||||
this.logger.log('NATS bridge subscriptions initialized');
|
this.logger.log('NATS bridge subscriptions initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,4 +70,13 @@ export class NatsService implements OnModuleInit, OnModuleDestroy {
|
|||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Publish a deploy command to a specific license's companion agent */
|
||||||
|
async sendDeployCommand(licenseId: string, config: Record<string, unknown>): Promise<void> {
|
||||||
|
await this.publish(`corrosion.${licenseId}.cmd.deploy`, {
|
||||||
|
action: 'deploy',
|
||||||
|
config,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user