phase01
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
|
||||
export default defineConfig({
|
||||
schema: './src/schema/index.ts',
|
||||
out: './drizzle',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@source/database",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"lint": "eslint src/",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:seed": "tsx src/seed.ts",
|
||||
"db:studio": "drizzle-kit studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"drizzle-orm": "^0.38.0",
|
||||
"postgres": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"drizzle-kit": "^0.30.0",
|
||||
"tsx": "^4.19.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import postgres from 'postgres';
|
||||
import * as schema from './schema';
|
||||
|
||||
export function createDb(connectionString: string) {
|
||||
const client = postgres(connectionString);
|
||||
return drizzle(client, { schema });
|
||||
}
|
||||
|
||||
export type Database = ReturnType<typeof createDb>;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './schema';
|
||||
export * from './client';
|
||||
@@ -0,0 +1,19 @@
|
||||
import { pgTable, uuid, varchar, jsonb, timestamp } from 'drizzle-orm/pg-core';
|
||||
import { organizations } from './organizations';
|
||||
import { users } from './users';
|
||||
import { servers } from './servers';
|
||||
|
||||
export const auditLogs = pgTable('audit_logs', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
organizationId: uuid('organization_id')
|
||||
.notNull()
|
||||
.references(() => organizations.id, { onDelete: 'cascade' }),
|
||||
userId: uuid('user_id')
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
serverId: uuid('server_id').references(() => servers.id, { onDelete: 'set null' }),
|
||||
action: varchar('action', { length: 255 }).notNull(),
|
||||
metadata: jsonb('metadata').default({}).notNull(),
|
||||
ipAddress: varchar('ip_address', { length: 45 }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { pgTable, uuid, varchar, bigint, text, boolean, timestamp } from 'drizzle-orm/pg-core';
|
||||
import { servers } from './servers';
|
||||
|
||||
export const backups = pgTable('backups', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
serverId: uuid('server_id')
|
||||
.notNull()
|
||||
.references(() => servers.id, { onDelete: 'cascade' }),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
sizeBytes: bigint('size_bytes', { mode: 'number' }),
|
||||
cdnPath: text('cdn_path'),
|
||||
checksum: text('checksum'),
|
||||
isLocked: boolean('is_locked').default(false).notNull(),
|
||||
completedAt: timestamp('completed_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { pgTable, uuid, varchar, integer, text, jsonb, timestamp } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const games = pgTable('games', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
slug: varchar('slug', { length: 100 }).notNull().unique(),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
dockerImage: text('docker_image').notNull(),
|
||||
defaultPort: integer('default_port').notNull(),
|
||||
configFiles: jsonb('config_files').default([]).notNull(),
|
||||
startupCommand: text('startup_command').notNull(),
|
||||
stopCommand: text('stop_command'),
|
||||
environmentVars: jsonb('environment_vars').default([]).notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
export * from './users';
|
||||
export * from './organizations';
|
||||
export * from './nodes';
|
||||
export * from './games';
|
||||
export * from './servers';
|
||||
export * from './backups';
|
||||
export * from './plugins';
|
||||
export * from './schedules';
|
||||
export * from './audit-logs';
|
||||
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
pgTable,
|
||||
uuid,
|
||||
varchar,
|
||||
integer,
|
||||
bigint,
|
||||
text,
|
||||
boolean,
|
||||
timestamp,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { organizations } from './organizations';
|
||||
import { servers } from './servers';
|
||||
|
||||
export const nodes = pgTable('nodes', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
organizationId: uuid('organization_id')
|
||||
.notNull()
|
||||
.references(() => organizations.id, { onDelete: 'cascade' }),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
fqdn: varchar('fqdn', { length: 255 }).notNull(),
|
||||
daemonPort: integer('daemon_port').default(8443).notNull(),
|
||||
grpcPort: integer('grpc_port').default(50051).notNull(),
|
||||
location: varchar('location', { length: 255 }),
|
||||
memoryTotal: bigint('memory_total', { mode: 'number' }).notNull(),
|
||||
diskTotal: bigint('disk_total', { mode: 'number' }).notNull(),
|
||||
memoryOveralloc: integer('memory_overalloc').default(0).notNull(),
|
||||
diskOveralloc: integer('disk_overalloc').default(0).notNull(),
|
||||
daemonToken: text('daemon_token').notNull(),
|
||||
tlsEnabled: boolean('tls_enabled').default(true).notNull(),
|
||||
isOnline: boolean('is_online').default(false).notNull(),
|
||||
lastHeartbeat: timestamp('last_heartbeat', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const allocations = pgTable('allocations', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
nodeId: uuid('node_id')
|
||||
.notNull()
|
||||
.references(() => nodes.id, { onDelete: 'cascade' }),
|
||||
serverId: uuid('server_id').references(() => servers.id, { onDelete: 'set null' }),
|
||||
ip: varchar('ip', { length: 45 }).notNull(),
|
||||
port: integer('port').notNull(),
|
||||
isDefault: boolean('is_default').default(false).notNull(),
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { pgTable, uuid, varchar, integer, timestamp, jsonb, pgEnum } from 'drizzle-orm/pg-core';
|
||||
import { users } from './users';
|
||||
|
||||
export const orgRoleEnum = pgEnum('org_role', ['admin', 'user']);
|
||||
|
||||
export const organizations = pgTable('organizations', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
slug: varchar('slug', { length: 255 }).notNull().unique(),
|
||||
ownerId: uuid('owner_id')
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
maxServers: integer('max_servers').default(0).notNull(),
|
||||
maxNodes: integer('max_nodes').default(0).notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const organizationMembers = pgTable('organization_members', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
organizationId: uuid('organization_id')
|
||||
.notNull()
|
||||
.references(() => organizations.id, { onDelete: 'cascade' }),
|
||||
userId: uuid('user_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
role: orgRoleEnum('role').default('user').notNull(),
|
||||
customPermissions: jsonb('custom_permissions').default({}).notNull(),
|
||||
joinedAt: timestamp('joined_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
pgTable,
|
||||
uuid,
|
||||
varchar,
|
||||
text,
|
||||
boolean,
|
||||
timestamp,
|
||||
pgEnum,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { games } from './games';
|
||||
import { servers } from './servers';
|
||||
|
||||
export const pluginSourceEnum = pgEnum('plugin_source', ['spiget', 'manual']);
|
||||
|
||||
export const plugins = pgTable('plugins', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
gameId: uuid('game_id')
|
||||
.notNull()
|
||||
.references(() => games.id, { onDelete: 'cascade' }),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
slug: varchar('slug', { length: 255 }).notNull(),
|
||||
description: text('description'),
|
||||
source: pluginSourceEnum('source').notNull(),
|
||||
externalId: varchar('external_id', { length: 255 }),
|
||||
downloadUrl: text('download_url'),
|
||||
version: varchar('version', { length: 100 }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const serverPlugins = pgTable('server_plugins', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
serverId: uuid('server_id')
|
||||
.notNull()
|
||||
.references(() => servers.id, { onDelete: 'cascade' }),
|
||||
pluginId: uuid('plugin_id')
|
||||
.notNull()
|
||||
.references(() => plugins.id, { onDelete: 'cascade' }),
|
||||
installedVersion: varchar('installed_version', { length: 100 }),
|
||||
isActive: boolean('is_active').default(true).notNull(),
|
||||
installedAt: timestamp('installed_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
pgTable,
|
||||
uuid,
|
||||
varchar,
|
||||
text,
|
||||
boolean,
|
||||
jsonb,
|
||||
timestamp,
|
||||
pgEnum,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { servers } from './servers';
|
||||
|
||||
export const scheduleActionEnum = pgEnum('schedule_action', ['command', 'power', 'backup']);
|
||||
|
||||
export const scheduleTypeEnum = pgEnum('schedule_type', [
|
||||
'interval',
|
||||
'daily',
|
||||
'weekly',
|
||||
'cron',
|
||||
]);
|
||||
|
||||
export const scheduledTasks = pgTable('scheduled_tasks', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
serverId: uuid('server_id')
|
||||
.notNull()
|
||||
.references(() => servers.id, { onDelete: 'cascade' }),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
action: scheduleActionEnum('action').notNull(),
|
||||
payload: text('payload').notNull(),
|
||||
scheduleType: scheduleTypeEnum('schedule_type').notNull(),
|
||||
scheduleData: jsonb('schedule_data').notNull(),
|
||||
isActive: boolean('is_active').default(true).notNull(),
|
||||
lastRunAt: timestamp('last_run_at', { withTimezone: true }),
|
||||
nextRunAt: timestamp('next_run_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
pgTable,
|
||||
uuid,
|
||||
varchar,
|
||||
integer,
|
||||
bigint,
|
||||
text,
|
||||
jsonb,
|
||||
timestamp,
|
||||
pgEnum,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { organizations } from './organizations';
|
||||
import { nodes } from './nodes';
|
||||
import { games } from './games';
|
||||
import { users } from './users';
|
||||
|
||||
export const serverStatusEnum = pgEnum('server_status', [
|
||||
'installing',
|
||||
'running',
|
||||
'stopped',
|
||||
'suspended',
|
||||
'error',
|
||||
]);
|
||||
|
||||
export const servers = pgTable('servers', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
uuid: varchar('uuid', { length: 36 }).notNull().unique(),
|
||||
organizationId: uuid('organization_id')
|
||||
.notNull()
|
||||
.references(() => organizations.id, { onDelete: 'cascade' }),
|
||||
nodeId: uuid('node_id')
|
||||
.notNull()
|
||||
.references(() => nodes.id),
|
||||
gameId: uuid('game_id')
|
||||
.notNull()
|
||||
.references(() => games.id),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
description: text('description'),
|
||||
status: serverStatusEnum('status').default('installing').notNull(),
|
||||
memoryLimit: bigint('memory_limit', { mode: 'number' }).notNull(),
|
||||
diskLimit: bigint('disk_limit', { mode: 'number' }).notNull(),
|
||||
cpuLimit: integer('cpu_limit').default(100).notNull(),
|
||||
port: integer('port').notNull(),
|
||||
additionalPorts: jsonb('additional_ports').default([]).notNull(),
|
||||
environment: jsonb('environment').default({}).notNull(),
|
||||
startupOverride: text('startup_override'),
|
||||
installedAt: timestamp('installed_at', { withTimezone: true }),
|
||||
suspendedAt: timestamp('suspended_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const serverSubusers = pgTable('server_subusers', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
serverId: uuid('server_id')
|
||||
.notNull()
|
||||
.references(() => servers.id, { onDelete: 'cascade' }),
|
||||
userId: uuid('user_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
permissions: jsonb('permissions').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { pgTable, uuid, varchar, text, boolean, timestamp } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
email: varchar('email', { length: 255 }).notNull().unique(),
|
||||
username: varchar('username', { length: 100 }).notNull().unique(),
|
||||
passwordHash: text('password_hash').notNull(),
|
||||
isSuperAdmin: boolean('is_super_admin').default(false).notNull(),
|
||||
avatarUrl: text('avatar_url'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
import { createDb } from './client';
|
||||
import { games } from './schema/games';
|
||||
|
||||
async function seed() {
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
if (!databaseUrl) {
|
||||
console.error('DATABASE_URL is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const db = createDb(databaseUrl);
|
||||
|
||||
console.log('Seeding games...');
|
||||
|
||||
await db
|
||||
.insert(games)
|
||||
.values([
|
||||
{
|
||||
slug: 'minecraft-java',
|
||||
name: 'Minecraft: Java Edition',
|
||||
dockerImage: 'itzg/minecraft-server:latest',
|
||||
defaultPort: 25565,
|
||||
startupCommand: '/start',
|
||||
stopCommand: 'stop',
|
||||
configFiles: JSON.stringify([
|
||||
{
|
||||
path: 'server.properties',
|
||||
parser: 'properties',
|
||||
editableKeys: [
|
||||
'server-port',
|
||||
'max-players',
|
||||
'motd',
|
||||
'difficulty',
|
||||
'gamemode',
|
||||
'level-seed',
|
||||
'pvp',
|
||||
'spawn-protection',
|
||||
'view-distance',
|
||||
'online-mode',
|
||||
'white-list',
|
||||
],
|
||||
},
|
||||
{ path: 'ops.json', parser: 'json' },
|
||||
{ path: 'whitelist.json', parser: 'json' },
|
||||
{ path: 'bukkit.yml', parser: 'yaml' },
|
||||
{ path: 'spigot.yml', parser: 'yaml' },
|
||||
]),
|
||||
environmentVars: JSON.stringify([
|
||||
{ key: 'EULA', default: 'TRUE', description: 'Accept Minecraft EULA', required: true },
|
||||
{
|
||||
key: 'TYPE',
|
||||
default: 'PAPER',
|
||||
description: 'Server type (VANILLA, PAPER, SPIGOT, FORGE, FABRIC)',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'VERSION',
|
||||
default: 'LATEST',
|
||||
description: 'Minecraft version',
|
||||
required: true,
|
||||
},
|
||||
{ key: 'MEMORY', default: '1G', description: 'JVM memory allocation', required: false },
|
||||
]),
|
||||
},
|
||||
{
|
||||
slug: 'cs2',
|
||||
name: 'Counter-Strike 2',
|
||||
dockerImage: 'cm2network/csgo:latest',
|
||||
defaultPort: 27015,
|
||||
startupCommand:
|
||||
'./srcds_run -game csgo -console -usercon +game_type 0 +game_mode 0 +mapgroup mg_active +map de_dust2',
|
||||
stopCommand: 'quit',
|
||||
configFiles: JSON.stringify([
|
||||
{
|
||||
path: 'csgo/cfg/server.cfg',
|
||||
parser: 'keyvalue',
|
||||
editableKeys: [
|
||||
'hostname',
|
||||
'sv_password',
|
||||
'rcon_password',
|
||||
'sv_cheats',
|
||||
'mp_autoteambalance',
|
||||
'mp_limitteams',
|
||||
],
|
||||
},
|
||||
{ path: 'csgo/cfg/autoexec.cfg', parser: 'keyvalue' },
|
||||
]),
|
||||
environmentVars: JSON.stringify([
|
||||
{
|
||||
key: 'SRCDS_TOKEN',
|
||||
default: '',
|
||||
description: 'Steam Game Server Login Token',
|
||||
required: true,
|
||||
},
|
||||
{ key: 'SRCDS_RCONPW', default: '', description: 'RCON password', required: false },
|
||||
{ key: 'SRCDS_PW', default: '', description: 'Server password', required: false },
|
||||
{
|
||||
key: 'SRCDS_MAXPLAYERS',
|
||||
default: '16',
|
||||
description: 'Max players',
|
||||
required: false,
|
||||
},
|
||||
]),
|
||||
},
|
||||
])
|
||||
.onConflictDoNothing();
|
||||
|
||||
console.log('Seed completed successfully!');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
seed().catch((err) => {
|
||||
console.error('Seed failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package gamepanel.daemon;
|
||||
|
||||
// Empty message for parameterless RPCs
|
||||
message Empty {}
|
||||
|
||||
// === Node ===
|
||||
|
||||
message NodeStatus {
|
||||
string version = 1;
|
||||
bool is_healthy = 2;
|
||||
int64 uptime_seconds = 3;
|
||||
int32 active_servers = 4;
|
||||
}
|
||||
|
||||
message NodeStats {
|
||||
double cpu_percent = 1;
|
||||
int64 memory_used = 2;
|
||||
int64 memory_total = 3;
|
||||
int64 disk_used = 4;
|
||||
int64 disk_total = 5;
|
||||
}
|
||||
|
||||
// === Server Lifecycle ===
|
||||
|
||||
message ServerIdentifier {
|
||||
string uuid = 1;
|
||||
}
|
||||
|
||||
message PortMapping {
|
||||
int32 host_port = 1;
|
||||
int32 container_port = 2;
|
||||
string protocol = 3; // "tcp" or "udp"
|
||||
}
|
||||
|
||||
message CreateServerRequest {
|
||||
string uuid = 1;
|
||||
string docker_image = 2;
|
||||
int64 memory_limit = 3;
|
||||
int64 disk_limit = 4;
|
||||
int32 cpu_limit = 5;
|
||||
string startup_command = 6;
|
||||
map<string, string> environment = 7;
|
||||
repeated PortMapping ports = 8;
|
||||
repeated string install_plugin_urls = 9;
|
||||
}
|
||||
|
||||
message ServerResponse {
|
||||
string uuid = 1;
|
||||
string status = 2;
|
||||
}
|
||||
|
||||
// === Power ===
|
||||
|
||||
enum PowerAction {
|
||||
START = 0;
|
||||
STOP = 1;
|
||||
RESTART = 2;
|
||||
KILL = 3;
|
||||
}
|
||||
|
||||
message PowerRequest {
|
||||
string uuid = 1;
|
||||
PowerAction action = 2;
|
||||
}
|
||||
|
||||
// === Server Status ===
|
||||
|
||||
message ServerStatus {
|
||||
string uuid = 1;
|
||||
string state = 2;
|
||||
double cpu_percent = 3;
|
||||
int64 memory_bytes = 4;
|
||||
int64 disk_bytes = 5;
|
||||
int64 network_rx = 6;
|
||||
int64 network_tx = 7;
|
||||
int64 uptime_seconds = 8;
|
||||
}
|
||||
|
||||
message ServerResourceStats {
|
||||
string uuid = 1;
|
||||
double cpu_percent = 2;
|
||||
int64 memory_bytes = 3;
|
||||
int64 disk_bytes = 4;
|
||||
int64 network_rx = 5;
|
||||
int64 network_tx = 6;
|
||||
string state = 7;
|
||||
}
|
||||
|
||||
// === Console ===
|
||||
|
||||
message ConsoleOutput {
|
||||
string uuid = 1;
|
||||
string line = 2;
|
||||
int64 timestamp = 3;
|
||||
}
|
||||
|
||||
message CommandRequest {
|
||||
string uuid = 1;
|
||||
string command = 2;
|
||||
}
|
||||
|
||||
// === Files ===
|
||||
|
||||
message FileEntry {
|
||||
string name = 1;
|
||||
string path = 2;
|
||||
bool is_directory = 3;
|
||||
int64 size = 4;
|
||||
int64 modified_at = 5;
|
||||
string mime_type = 6;
|
||||
}
|
||||
|
||||
message FileListRequest {
|
||||
string uuid = 1;
|
||||
string path = 2;
|
||||
}
|
||||
|
||||
message FileListResponse {
|
||||
repeated FileEntry files = 1;
|
||||
}
|
||||
|
||||
message FileReadRequest {
|
||||
string uuid = 1;
|
||||
string path = 2;
|
||||
}
|
||||
|
||||
message FileContent {
|
||||
bytes data = 1;
|
||||
string mime_type = 2;
|
||||
}
|
||||
|
||||
message FileWriteRequest {
|
||||
string uuid = 1;
|
||||
string path = 2;
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
message FileDeleteRequest {
|
||||
string uuid = 1;
|
||||
repeated string paths = 2;
|
||||
}
|
||||
|
||||
message CompressRequest {
|
||||
string uuid = 1;
|
||||
repeated string paths = 2;
|
||||
string destination = 3;
|
||||
}
|
||||
|
||||
message DecompressRequest {
|
||||
string uuid = 1;
|
||||
string path = 2;
|
||||
string destination = 3;
|
||||
}
|
||||
|
||||
// === Backup ===
|
||||
|
||||
message BackupRequest {
|
||||
string server_uuid = 1;
|
||||
string backup_id = 2;
|
||||
string cdn_upload_url = 3;
|
||||
}
|
||||
|
||||
message BackupResponse {
|
||||
string backup_id = 1;
|
||||
int64 size_bytes = 2;
|
||||
string checksum = 3;
|
||||
bool success = 4;
|
||||
}
|
||||
|
||||
message BackupIdentifier {
|
||||
string server_uuid = 1;
|
||||
string backup_id = 2;
|
||||
}
|
||||
|
||||
message RestoreBackupRequest {
|
||||
string server_uuid = 1;
|
||||
string backup_id = 2;
|
||||
string cdn_download_url = 3;
|
||||
}
|
||||
|
||||
// === Players ===
|
||||
|
||||
message Player {
|
||||
string name = 1;
|
||||
string uuid = 2;
|
||||
int64 connected_at = 3;
|
||||
}
|
||||
|
||||
message PlayerList {
|
||||
repeated Player players = 1;
|
||||
int32 max_players = 2;
|
||||
}
|
||||
|
||||
// === Install Progress ===
|
||||
|
||||
message InstallProgress {
|
||||
string uuid = 1;
|
||||
int32 percent = 2;
|
||||
string message = 3;
|
||||
}
|
||||
|
||||
// === Service Definition ===
|
||||
|
||||
service DaemonService {
|
||||
// Node
|
||||
rpc GetNodeStatus(Empty) returns (NodeStatus);
|
||||
rpc StreamNodeStats(Empty) returns (stream NodeStats);
|
||||
|
||||
// Server lifecycle
|
||||
rpc CreateServer(CreateServerRequest) returns (ServerResponse);
|
||||
rpc DeleteServer(ServerIdentifier) returns (Empty);
|
||||
rpc ReinstallServer(ServerIdentifier) returns (Empty);
|
||||
|
||||
// Power
|
||||
rpc SetPowerState(PowerRequest) returns (Empty);
|
||||
rpc GetServerStatus(ServerIdentifier) returns (ServerStatus);
|
||||
|
||||
// Console
|
||||
rpc StreamConsole(ServerIdentifier) returns (stream ConsoleOutput);
|
||||
rpc SendCommand(CommandRequest) returns (Empty);
|
||||
|
||||
// Files
|
||||
rpc ListFiles(FileListRequest) returns (FileListResponse);
|
||||
rpc ReadFile(FileReadRequest) returns (FileContent);
|
||||
rpc WriteFile(FileWriteRequest) returns (Empty);
|
||||
rpc DeleteFiles(FileDeleteRequest) returns (Empty);
|
||||
rpc CompressFiles(CompressRequest) returns (FileContent);
|
||||
rpc DecompressFile(DecompressRequest) returns (Empty);
|
||||
|
||||
// Backup
|
||||
rpc CreateBackup(BackupRequest) returns (BackupResponse);
|
||||
rpc RestoreBackup(RestoreBackupRequest) returns (Empty);
|
||||
rpc DeleteBackup(BackupIdentifier) returns (Empty);
|
||||
|
||||
// Stats
|
||||
rpc StreamServerStats(ServerIdentifier) returns (stream ServerResourceStats);
|
||||
|
||||
// Install progress
|
||||
rpc StreamInstallProgress(ServerIdentifier) returns (stream InstallProgress);
|
||||
|
||||
// Players
|
||||
rpc GetActivePlayers(ServerIdentifier) returns (PlayerList);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@source/proto",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"generate": "buf generate"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Proto generated types will be exported here after running `pnpm generate`
|
||||
// For now, this is a placeholder
|
||||
|
||||
export const PROTO_PATH = new URL('../daemon.proto', import.meta.url).pathname;
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@source/shared",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"lint": "eslint src/",
|
||||
"build": "tsc"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './permissions';
|
||||
export * from './roles';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,57 @@
|
||||
export const PERMISSIONS = {
|
||||
// Server
|
||||
'server.create': 'Create servers',
|
||||
'server.read': 'View servers',
|
||||
'server.update': 'Update server settings',
|
||||
'server.delete': 'Delete servers',
|
||||
|
||||
// Console
|
||||
'console.read': 'View console output',
|
||||
'console.write': 'Send console commands',
|
||||
|
||||
// Files
|
||||
'files.read': 'View and download files',
|
||||
'files.write': 'Create, edit, and upload files',
|
||||
'files.delete': 'Delete files',
|
||||
'files.archive': 'Compress and decompress files',
|
||||
|
||||
// Backup
|
||||
'backup.create': 'Create backups',
|
||||
'backup.restore': 'Restore backups',
|
||||
'backup.delete': 'Delete backups',
|
||||
'backup.download': 'Download backups',
|
||||
|
||||
// Schedule
|
||||
'schedule.read': 'View scheduled tasks',
|
||||
'schedule.manage': 'Create, edit, and delete scheduled tasks',
|
||||
|
||||
// Subuser
|
||||
'subuser.read': 'View subusers',
|
||||
'subuser.manage': 'Create, edit, and delete subusers',
|
||||
|
||||
// Plugin
|
||||
'plugin.read': 'View installed plugins',
|
||||
'plugin.manage': 'Install and remove plugins',
|
||||
|
||||
// Config
|
||||
'config.read': 'View server config',
|
||||
'config.write': 'Edit server config',
|
||||
|
||||
// Power
|
||||
'power.start': 'Start server',
|
||||
'power.stop': 'Stop server',
|
||||
'power.restart': 'Restart server',
|
||||
'power.kill': 'Kill server',
|
||||
|
||||
// Node (org admin)
|
||||
'node.read': 'View nodes',
|
||||
'node.manage': 'Manage nodes and allocations',
|
||||
|
||||
// Organization
|
||||
'org.settings': 'Manage organization settings',
|
||||
'org.members': 'Manage organization members',
|
||||
} as const;
|
||||
|
||||
export type Permission = keyof typeof PERMISSIONS;
|
||||
|
||||
export const ALL_PERMISSIONS = Object.keys(PERMISSIONS) as Permission[];
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { Permission } from './permissions';
|
||||
import { ALL_PERMISSIONS } from './permissions';
|
||||
|
||||
export const ROLES = {
|
||||
admin: {
|
||||
name: 'Admin',
|
||||
description: 'Full access to the organization',
|
||||
permissions: ALL_PERMISSIONS,
|
||||
},
|
||||
user: {
|
||||
name: 'User',
|
||||
description: 'Limited access, must be granted server-specific permissions',
|
||||
permissions: [
|
||||
'server.read',
|
||||
'console.read',
|
||||
'files.read',
|
||||
'backup.create',
|
||||
'backup.download',
|
||||
'schedule.read',
|
||||
'plugin.read',
|
||||
'config.read',
|
||||
'power.start',
|
||||
'power.stop',
|
||||
'power.restart',
|
||||
] as Permission[],
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type Role = keyof typeof ROLES;
|
||||
@@ -0,0 +1,24 @@
|
||||
export type ServerStatus = 'installing' | 'running' | 'stopped' | 'suspended' | 'error';
|
||||
|
||||
export type PowerAction = 'start' | 'stop' | 'restart' | 'kill';
|
||||
|
||||
export type ScheduleType = 'interval' | 'daily' | 'weekly' | 'cron';
|
||||
|
||||
export type ScheduleAction = 'command' | 'power' | 'backup';
|
||||
|
||||
export type PluginSource = 'spiget' | 'manual';
|
||||
|
||||
export interface PaginationParams {
|
||||
page: number;
|
||||
perPage: number;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
data: T[];
|
||||
meta: {
|
||||
page: number;
|
||||
perPage: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@source/ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"lint": "eslint src/",
|
||||
"build": "tsc"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"tailwind-merge": "^2.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { cn } from './lib/utils';
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user