Skip to content

Commit e574745

Browse files
Delete code instances and introduce ballooning to KVM egg!
1 parent 89a83c3 commit e574745

19 files changed

Lines changed: 167 additions & 837 deletions

File tree

backend/src/app.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { setupConfig } from './config';
1212
import { createActivityLog } from './handlers/logHandler';
1313
import { AppDataSource } from './config/typeorm';
1414
import { scheduleStudentReverifyJob } from './jobs/studentReverifyJob';
15-
import { scheduleCodeInstanceIdleJob } from './jobs/codeInstanceIdleJob';
1615
import { scheduleMetricsCollectionJob } from './jobs/metricsCollectionJob';
1716
import { scheduleExportJobRunner } from './jobs/exportJobRunner';
1817
import { scheduleDeletionExecutionJob } from './jobs/deletionExecutionJob';
@@ -488,7 +487,6 @@ export async function initApp() {
488487
setupMiddleware(app);
489488
registerRoutes(app);
490489
try { scheduleStudentReverifyJob(); } catch (e) { console.error('Failed to schedule student reverify job:', e); }
491-
try { scheduleCodeInstanceIdleJob(); } catch (e) { console.error('Failed to schedule code instance idle job:', e); }
492490
try { scheduleMetricsCollectionJob(); } catch (e) { console.error('Failed to schedule metrics collection job:', e); }
493491
try { scheduleExportJobRunner(); } catch (e) { console.error('Failed to schedule export job runner:', e); }
494492
try { scheduleDeletionExecutionJob(); } catch (e) { console.error('Failed to schedule deletion execution job:', e); }

backend/src/handlers/adminHandler.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4886,12 +4886,10 @@ isSuspicious: true if fraudScore >= 50`;
48864886
try { portalDescriptions = JSON.parse(map['portalDescriptions']); } catch { }
48874887
}
48884888
const featureToggles = await getPanelFeatureToggles();
4889-
const codeInstancesEnabled = map['codeInstancesEnabled'] !== 'false';
48904889
return {
48914890
registrationEnabled: map['registrationEnabled'] !== 'false',
48924891
registrationNotice: map['registrationNotice'] || '',
48934892
portalDescriptions: portalDescriptions || null,
4894-
codeInstancesEnabled,
48954893
geoBlockCountries: map['geoBlockCountries'] || '',
48964894
countryAgeRules: map['countryAgeRules'] || '',
48974895
billingCurrency: (map['billingCurrency'] || 'USD').toUpperCase(),
@@ -4907,7 +4905,6 @@ isSuspicious: true if fraudScore >= 50`;
49074905
registrationEnabled: t.Boolean(),
49084906
registrationNotice: t.String(),
49094907
portalDescriptions: t.Optional(t.Any()),
4910-
codeInstancesEnabled: t.Boolean(),
49114908
featureToggles: t.Record(t.String(), t.Boolean()),
49124909
geoBlockCountries: t.String(),
49134910
countryAgeRules: t.Optional(t.String()),
@@ -4943,11 +4940,9 @@ isSuspicious: true if fraudScore >= 50`;
49434940
try { portalDescriptions = JSON.parse(map['portalDescriptions']); } catch { }
49444941
}
49454942
const featureToggles = await getPanelFeatureToggles();
4946-
const codeInstancesEnabled = map['codeInstancesEnabled'] !== 'false';
49474943
return {
49484944
registrationEnabled: map['registrationEnabled'] !== 'false',
49494945
registrationNotice: map['registrationNotice'] || '',
4950-
codeInstancesEnabled,
49514946
portalDescriptions: portalDescriptions || null,
49524947
geoBlockCountries: map['geoBlockCountries'] || '',
49534948
countryAgeRules: map['countryAgeRules'] || '',
@@ -4964,7 +4959,6 @@ isSuspicious: true if fraudScore >= 50`;
49644959
200: t.Object({
49654960
registrationEnabled: t.Boolean(),
49664961
registrationNotice: t.String(),
4967-
codeInstancesEnabled: t.Boolean(),
49684962
portalDescriptions: t.Optional(t.Any()),
49694963
geoBlockCountries: t.String(),
49704964
countryAgeRules: t.Optional(t.String()),
@@ -5068,7 +5062,7 @@ isSuspicious: true if fraudScore >= 50`;
50685062
if (adminErr !== true) return adminErr;
50695063
const repo = AppDataSource.getRepository(PanelSetting);
50705064
const body = ctx.body as any;
5071-
const allowed = ['registrationEnabled', 'registrationNotice', 'codeInstancesEnabled', 'geoBlockCountries', 'countryAgeRules', 'billingCurrency', 'billingTaxRules', 'gamblingEnabled', 'gamblingResourceLuckyChance', 'gamblingPowerDenyChance'];
5065+
const allowed = ['registrationEnabled', 'registrationNotice', 'geoBlockCountries', 'countryAgeRules', 'billingCurrency', 'billingTaxRules', 'gamblingEnabled', 'gamblingResourceLuckyChance', 'gamblingPowerDenyChance'];
50725066
for (const key of allowed) {
50735067
if (body[key] !== undefined) {
50745068
let value = typeof body[key] === 'boolean' ? String(body[key]) : String(body[key]);
@@ -5106,7 +5100,6 @@ isSuspicious: true if fraudScore >= 50`;
51065100
registrationEnabled: map['registrationEnabled'] !== 'false',
51075101
registrationNotice: map['registrationNotice'] || '',
51085102
portalDescriptions: portalDescriptions || null,
5109-
codeInstancesEnabled: map['codeInstancesEnabled'] !== 'false',
51105103
geoBlockCountries: map['geoBlockCountries'] || '',
51115104
countryAgeRules: map['countryAgeRules'] || '',
51125105
billingCurrency: (map['billingCurrency'] || 'USD').toUpperCase(),
@@ -5124,7 +5117,6 @@ isSuspicious: true if fraudScore >= 50`;
51245117
registrationEnabled: t.Optional(t.Boolean()),
51255118
registrationNotice: t.Optional(t.String()),
51265119
portalDescriptions: t.Optional(t.Any()),
5127-
codeInstancesEnabled: t.Optional(t.Boolean()),
51285120
geoBlockCountries: t.Optional(t.String()),
51295121
billingCurrency: t.Optional(t.String()),
51305122
billingTaxRules: t.Optional(t.String()),

backend/src/handlers/remoteHandler.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -875,9 +875,7 @@ export async function saveServerConfig(params: {
875875
kvmPassthroughEnabled?: boolean;
876876
allocations?: Record<string, any>;
877877
processConfig?: Record<string, any>;
878-
isCodeInstance?: boolean;
879878
lastActivityAt?: Date;
880-
codeInstanceMinutesUsed?: number;
881879
hibernated?: boolean;
882880
}): Promise<ServerConfig> {
883881
if (!Number.isFinite(params.memory) || params.memory < 0) throw new Error('Invalid memory value');
@@ -908,9 +906,7 @@ export async function saveServerConfig(params: {
908906
kvmPassthroughEnabled: params.kvmPassthroughEnabled ?? false,
909907
allocations: params.allocations ?? null,
910908
processConfig: normalizeProcessConfig(params.processConfig ?? null),
911-
isCodeInstance: params.isCodeInstance ?? false,
912909
lastActivityAt: params.lastActivityAt ?? null,
913-
codeInstanceMinutesUsed: params.codeInstanceMinutesUsed ?? 0,
914910
});
915911
return r.save(cfg);
916912
}
@@ -928,10 +924,7 @@ export async function saveServerConfig(params: {
928924
keep.disk = params.disk;
929925
keep.cpu = params.cpu;
930926
keep.swap = params.swap ?? keep.swap ?? 0;
931-
keep.ioWeight = params.ioWeight ?? keep.ioWeight ?? 500;
932-
keep.isCodeInstance = params.isCodeInstance ?? keep.isCodeInstance;
933927
keep.lastActivityAt = params.lastActivityAt ?? keep.lastActivityAt;
934-
keep.codeInstanceMinutesUsed = params.codeInstanceMinutesUsed ?? keep.codeInstanceMinutesUsed ?? 0;
935928
keep.hibernated = params.hibernated ?? keep.hibernated ?? false;
936929
keep.eggId = params.eggId ?? keep.eggId;
937930
keep.skipEggScripts = params.skipEggScripts ?? keep.skipEggScripts ?? false;

backend/src/handlers/serverHandler.ts

Lines changed: 3 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ export async function serverRoutes(app: any, prefix = '') {
469469
const where: any[] = [{ userId: user.id }];
470470
if (subuserUuids.length) where.push({ uuid: In(subuserUuids) });
471471
const found = await cfgRepo.find({ where });
472-
return found.filter((c: any) => !c.isCodeInstance);
472+
return found;
473473
})();
474474

475475
const cfgMap = new Map(configs.map((c: any) => [c.uuid, c]));
@@ -1188,64 +1188,10 @@ export async function serverRoutes(app: any, prefix = '') {
11881188
};
11891189
}
11901190

1191-
let isCodeInstance = !!body.isCodeInstance || Number(body.eggId) === 264;
1192-
1193-
if (isCodeInstance && !isAdmin) {
1194-
const settingRepo = AppDataSource.getRepository(
1195-
require('../models/panelSetting.entity').PanelSetting
1196-
);
1197-
const setting = await settingRepo.findOneBy({ key: 'codeInstancesEnabled' });
1198-
if (setting?.value === 'false') {
1199-
ctx.set.status = 403;
1200-
return { error: 'Code instance creation is temporarily disabled by an administrator.' };
1201-
}
1202-
}
1203-
1204-
const allUserServers = !isAdmin
1191+
const existingRegularServers = !isAdmin
12051192
? await cfgRepo().find({ where: { userId: ownerId } })
12061193
: [];
12071194

1208-
const existingCodeInstances = allUserServers.filter((s: any) => s.isCodeInstance);
1209-
const existingRegularServers = allUserServers.filter((s: any) => !s.isCodeInstance);
1210-
1211-
if (isCodeInstance && !isAdmin) {
1212-
const allowedPortalTypes = ['educational', 'paid', 'enterprise'];
1213-
if (!allowedPortalTypes.includes(effectivePortalType)) {
1214-
ctx.set.status = 403;
1215-
return { error: 'Code Instances are available only for educational or higher plans.' };
1216-
}
1217-
1218-
if (existingCodeInstances.length >= 2) {
1219-
ctx.set.status = 403;
1220-
return { error: 'Maximum 2 Code Instances are allowed concurrently.' };
1221-
}
1222-
1223-
const existingCIMem = existingCodeInstances.reduce((sum: number, i: any) => sum + (i.memory || 0), 0);
1224-
const existingCICpu = existingCodeInstances.reduce((sum: number, i: any) => sum + (i.cpu || 0), 0);
1225-
const existingCIDisk = existingCodeInstances.reduce((sum: number, i: any) => sum + (i.disk || 0), 0);
1226-
1227-
if (existingCIMem + memory > 8192) {
1228-
ctx.set.status = 403;
1229-
return { error: 'Total Code Instance memory limit is 8192 MB.' };
1230-
}
1231-
if (existingCICpu + cpu > 6) {
1232-
ctx.set.status = 403;
1233-
return { error: 'Total Code Instance CPU limit is 6 cores.' };
1234-
}
1235-
if (existingCIDisk + disk > 102400) {
1236-
ctx.set.status = 403;
1237-
return { error: 'Total Code Instance disk limit is 100000 MB.' };
1238-
}
1239-
1240-
const usedMinutes = existingCodeInstances.reduce(
1241-
(sum: number, i: any) => sum + (i.codeInstanceMinutesUsed || 0), 0
1242-
);
1243-
if (usedMinutes >= 150 * 60) {
1244-
ctx.set.status = 403;
1245-
return { error: 'Monthly Code Instance usage limit of 150 hours reached.' };
1246-
}
1247-
}
1248-
12491195
if (!isAdmin) {
12501196
if (limits.serverLimit != null && limits.serverLimit > 0) {
12511197
if (existingRegularServers.length >= limits.serverLimit) {
@@ -1272,10 +1218,6 @@ export async function serverRoutes(app: any, prefix = '') {
12721218
}
12731219
}
12741220

1275-
if (isCodeInstance) {
1276-
eggId = 264;
1277-
}
1278-
12791221
if (!eggId) {
12801222
ctx.set.status = 400;
12811223
return { error: 'eggId is required' };
@@ -1480,9 +1422,7 @@ export async function serverRoutes(app: any, prefix = '') {
14801422
disk,
14811423
cpu,
14821424
eggId: egg.id,
1483-
isCodeInstance,
14841425
kvmPassthroughEnabled,
1485-
lastActivityAt: isCodeInstance ? new Date() : undefined,
14861426
...(autoAllocation ? { allocations: autoAllocation } : {}),
14871427
});
14881428

@@ -4320,88 +4260,4 @@ export async function serverRoutes(app: any, prefix = '') {
43204260
response: { 200: t.Object({ host: t.String(), port: t.Number(), username: t.String(), proxied: t.Boolean() }), 401: t.Object({ error: t.String() }), 403: t.Object({ error: t.String() }), 404: t.Object({ error: t.String() }), 500: t.Object({ error: t.String() }) },
43214261
detail: { summary: 'Get SFTP connection info', tags: ['Servers'] }
43224262
});
4323-
4324-
app.get(prefix + '/infrastructure/code-instances', async (ctx: any) => {
4325-
const user = ctx.user as User;
4326-
if (!user) {
4327-
ctx.set.status = 401;
4328-
return { error: 'Unauthorized' };
4329-
}
4330-
4331-
const instances = await cfgRepo().find({ where: { userId: user.id, isCodeInstance: true } });
4332-
return instances.map((cfg) => ({
4333-
uuid: cfg.uuid,
4334-
name: cfg.name,
4335-
nodeId: cfg.nodeId,
4336-
memory: cfg.memory,
4337-
disk: cfg.disk,
4338-
cpu: cfg.cpu,
4339-
status: cfg.hibernated ? 'hibernated' : 'active',
4340-
lastActivityAt: cfg.lastActivityAt,
4341-
createdAt: cfg.createdAt,
4342-
codeInstanceMinutesUsed: cfg.codeInstanceMinutesUsed,
4343-
}));
4344-
}, {
4345-
beforeHandle: [authenticate],
4346-
response: { 200: t.Array(t.Any()), 401: t.Object({ error: t.String() }) },
4347-
detail: { summary: 'List code instances for the current user', tags: ['Code Instances'] }
4348-
});
4349-
4350-
app.post(prefix + '/infrastructure/code-instances/:uuid/ping', async (ctx: any) => {
4351-
const { uuid } = ctx.params as any;
4352-
const user = ctx.user as User;
4353-
if (!user) {
4354-
ctx.set.status = 401;
4355-
return { error: 'Unauthorized' };
4356-
}
4357-
4358-
const cfg = await cfgRepo().findOneBy({ uuid, isCodeInstance: true });
4359-
if (!cfg || cfg.userId !== user.id) {
4360-
ctx.set.status = 404;
4361-
return { error: 'Code Instance not found' };
4362-
}
4363-
4364-
cfg.lastActivityAt = new Date();
4365-
await cfgRepo().save(cfg);
4366-
4367-
await createActivityLog({ userId: user.id, action: 'codeInstance:ping', targetId: uuid, targetType: 'code-instance', ipAddress: ctx.ip });
4368-
return { success: true };
4369-
}, {
4370-
beforeHandle: [authenticate],
4371-
response: { 200: t.Object({ success: t.Boolean() }), 401: t.Object({ error: t.String() }), 404: t.Object({ error: t.String() }) },
4372-
detail: { summary: 'Ping code instance activity', tags: ['Code Instances'] }
4373-
});
4374-
4375-
app.post(prefix + '/infrastructure/code-instances/:uuid/stop', async (ctx: any) => {
4376-
const { uuid } = ctx.params as any;
4377-
const user = ctx.user as User;
4378-
if (!user) {
4379-
ctx.set.status = 401;
4380-
return { error: 'Unauthorized' };
4381-
}
4382-
4383-
const cfg = await cfgRepo().findOneBy({ uuid, isCodeInstance: true });
4384-
if (!cfg || cfg.userId !== user.id) {
4385-
ctx.set.status = 404;
4386-
return { error: 'Code Instance not found' };
4387-
}
4388-
4389-
try {
4390-
const svc = await serviceFor(uuid);
4391-
await svc.powerServer(uuid, 'stop').catch(() => { });
4392-
await svc.serverRequest(uuid, '', 'delete').catch(() => { });
4393-
} catch (e: any) {
4394-
// skip
4395-
}
4396-
4397-
await removeServerConfig(uuid).catch(() => { });
4398-
await nodeSvc.unmapServer(uuid).catch(() => { });
4399-
4400-
await createActivityLog({ userId: user.id, action: 'codeInstance:stop', targetId: uuid, targetType: 'code-instance', ipAddress: ctx.ip });
4401-
return { success: true };
4402-
}, {
4403-
beforeHandle: [authenticate],
4404-
response: { 200: t.Object({ success: t.Boolean() }), 401: t.Object({ error: t.String() }), 404: t.Object({ error: t.String() }) },
4405-
detail: { summary: 'Stop and delete code instance', tags: ['Code Instances'] }
4406-
});
4407-
}
4263+
}

backend/src/handlers/ticketHandler.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,9 @@ export async function ticketRoutes(app: any, prefix = '') {
431431
}
432432

433433
ticket.aiTouched = true;
434+
if (dir.escalate) {
435+
ticket.aiDisabled = true;
436+
}
434437
ticket.status = dir.escalate || changes.applied.priority === 'urgent' ? 'awaiting_staff_reply' : 'replied';
435438
await repo.save(ticket);
436439

@@ -1162,6 +1165,7 @@ Valid subpaths: /dashboard/*, /wings, /billing, /organisations, /docs, /ai, /inf
11621165

11631166
if (pushedSender === 'user' && userEscalated) {
11641167
saved.status = 'awaiting_staff_reply';
1168+
saved.aiDisabled = true;
11651169
await repo.save(saved);
11661170
try { await createActivityLog({ userId: user.id, action: 'ticket:escalate:user', targetId: String(saved.id), targetType: 'ticket', metadata: { reason: 'user requested escalation/no access' }, ipAddress: '' }); } catch (e) { }
11671171
} else {

backend/src/jobs/codeInstanceIdleJob.ts

Lines changed: 0 additions & 63 deletions
This file was deleted.

backend/src/models/serverConfig.entity.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,9 @@ export class ServerConfig {
101101
@Column('json', { nullable: true })
102102
processConfig?: Record<string, any>;
103103

104-
@Column({ default: false })
105-
isCodeInstance: boolean;
106-
107104
@Column({ nullable: true, type: 'datetime' })
108105
lastActivityAt?: Date;
109106

110-
@Column({ default: 0 })
111-
codeInstanceMinutesUsed: number;
112-
113107
@Column({ default: 0 })
114108
maxDatabases: number;
115109

0 commit comments

Comments
 (0)