Revert "merge: integrate extension auto-update"
This reverts commit322475860a, reversing changes made tof52d9d7413.
This commit is contained in:
@@ -44,11 +44,6 @@ import {
|
||||
normalizeRequestId,
|
||||
writeEmergencyDiagnostic
|
||||
} from './diagnostics.js';
|
||||
import {
|
||||
ExtensionUpdateError,
|
||||
createExtensionUpdateService,
|
||||
type ExtensionUpdateController
|
||||
} from './extension-updates.js';
|
||||
|
||||
export function aiServiceConnected(databaseIsReady: boolean, probe: Record<string, unknown>): boolean {
|
||||
return databaseIsReady && probe.configured === true && probe.reachable === true;
|
||||
@@ -69,8 +64,6 @@ const accountCreateSchema = z.object({
|
||||
password: z.string().min(1),
|
||||
role: z.enum(['admin', 'team_lead', 'user']).default('user'),
|
||||
erp_account: z.string().trim().max(200).optional(),
|
||||
extension_ecs_region_id: z.string().trim().max(64).optional(),
|
||||
extension_ecs_instance_id: z.string().trim().max(80).optional(),
|
||||
business_route_ids: z.array(
|
||||
z.string().trim().refine((routeId) => Boolean(businessRouteById(routeId)), '业务类型不存在。')
|
||||
).max(BUSINESS_ROUTES.length).default([])
|
||||
@@ -80,16 +73,8 @@ const accountCreateSchema = z.object({
|
||||
const accountUpdateSchema = z.object({
|
||||
role: z.enum(['admin', 'team_lead', 'user']).optional(),
|
||||
is_active: z.boolean().optional(),
|
||||
erp_account: z.string().trim().max(200).nullable().optional(),
|
||||
extension_ecs_region_id: z.string().trim().max(64).nullable().optional(),
|
||||
extension_ecs_instance_id: z.string().trim().max(80).nullable().optional()
|
||||
}).refine((body) => (
|
||||
body.role !== undefined
|
||||
|| body.is_active !== undefined
|
||||
|| body.erp_account !== undefined
|
||||
|| body.extension_ecs_region_id !== undefined
|
||||
|| body.extension_ecs_instance_id !== undefined
|
||||
), {
|
||||
erp_account: z.string().trim().max(200).nullable().optional()
|
||||
}).refine((body) => body.role !== undefined || body.is_active !== undefined || body.erp_account !== undefined, {
|
||||
message: '至少提供一个账号更新字段。'
|
||||
});
|
||||
|
||||
@@ -142,12 +127,8 @@ const heartbeatSchema = z.object({
|
||||
extension_version: z.string().max(80).optional(),
|
||||
erp_account: z.string().trim().max(200).optional(),
|
||||
erp_account_matched: z.boolean().optional(),
|
||||
extension_update_safe: z.boolean().optional(),
|
||||
metadata: z.record(z.unknown()).optional()
|
||||
});
|
||||
const extensionReleasePublishSchema = z.object({
|
||||
package_base64: z.string().min(4).max(70_000_000)
|
||||
});
|
||||
const automationSettingsSchema = z.object({ enabled: z.boolean() });
|
||||
const parserRoutingUpdateSchema = z.object({
|
||||
mode: z.enum(['ai', 'shadow', 'auto', 'program']),
|
||||
@@ -415,14 +396,12 @@ export async function buildServer({
|
||||
config = loadConfig(),
|
||||
parser,
|
||||
startParserLoop = true,
|
||||
loggerDestination,
|
||||
extensionUpdates: extensionUpdatesOverride
|
||||
loggerDestination
|
||||
}: {
|
||||
config?: AppConfig;
|
||||
parser?: ExternalParser;
|
||||
startParserLoop?: boolean;
|
||||
loggerDestination?: DestinationStream;
|
||||
extensionUpdates?: ExtensionUpdateController;
|
||||
} = {}) {
|
||||
const requestStartedAt = new WeakMap<FastifyRequest, bigint>();
|
||||
const app = Fastify({
|
||||
@@ -430,11 +409,7 @@ export async function buildServer({
|
||||
logController: new LogController({ disableRequestLogging: true }),
|
||||
genReqId: (rawRequest) => normalizeRequestId(rawRequest.headers['x-request-id']),
|
||||
trustProxy: true,
|
||||
bodyLimit: Math.min(75_000_000, Math.max(
|
||||
2_000_000,
|
||||
Math.ceil(config.ARTIFACT_MAX_BYTES * 1.4) + 1_000_000,
|
||||
Math.ceil(config.EXTENSION_UPDATE_MAX_PACKAGE_BYTES * 1.4) + 1_000_000
|
||||
))
|
||||
bodyLimit: Math.min(75_000_000, Math.max(2_000_000, Math.ceil(config.ARTIFACT_MAX_BYTES * 1.4) + 1_000_000))
|
||||
});
|
||||
await app.register(cookie);
|
||||
await app.register(helmet, { contentSecurityPolicy: false });
|
||||
@@ -536,11 +511,6 @@ export async function buildServer({
|
||||
warn: (metadata, message) => app.log.warn(metadata, message),
|
||||
error: (metadata, message) => app.log.error(metadata, message)
|
||||
});
|
||||
const extensionUpdates = extensionUpdatesOverride || createExtensionUpdateService(config, {
|
||||
info: (metadata, message) => app.log.info(metadata, message),
|
||||
warn: (metadata, message) => app.log.warn(metadata, message),
|
||||
error: (metadata, message) => app.log.error(metadata, message)
|
||||
});
|
||||
const externalParser = parser || await loadExternalParser();
|
||||
const parserOrchestrator = new ParserOrchestrator(externalParser);
|
||||
const activeParseWorkers = new Map<string, string>();
|
||||
@@ -1013,8 +983,6 @@ export async function buildServer({
|
||||
password: body.password,
|
||||
role: body.role,
|
||||
erpAccount: body.erp_account,
|
||||
extensionEcsRegionId: body.extension_ecs_region_id,
|
||||
extensionEcsInstanceId: body.extension_ecs_instance_id,
|
||||
businessRouteIds: body.business_route_ids
|
||||
}, requestId(request));
|
||||
return { ok: true, account };
|
||||
@@ -1028,9 +996,7 @@ export async function buildServer({
|
||||
const account = await auth.updateAccount(session.user, userId, {
|
||||
role: body.role,
|
||||
isActive: body.is_active,
|
||||
erpAccount: body.erp_account,
|
||||
extensionEcsRegionId: body.extension_ecs_region_id,
|
||||
extensionEcsInstanceId: body.extension_ecs_instance_id
|
||||
erpAccount: body.erp_account
|
||||
}, requestId(request));
|
||||
await agentBus?.reload();
|
||||
return { ok: true, account };
|
||||
@@ -1395,77 +1361,13 @@ export async function buildServer({
|
||||
contextFor(session, request),
|
||||
body.connection_id,
|
||||
body.extension_version || '',
|
||||
{
|
||||
...(body.metadata || {}),
|
||||
extension_update_safe: body.extension_update_safe === true
|
||||
},
|
||||
body.metadata || {},
|
||||
{
|
||||
erpAccount: body.erp_account || '',
|
||||
erpAccountMatched: body.erp_account_matched === true
|
||||
}
|
||||
);
|
||||
const { extension_update_target: updateTarget, ...publicWorker } = worker;
|
||||
const extensionUpdate = await extensionUpdates.observeHeartbeat({
|
||||
organizationId: session.user.organizationId,
|
||||
userId: session.user.id,
|
||||
connectionId: body.connection_id,
|
||||
currentVersion: body.extension_version || '',
|
||||
extensionUpdateSafe: body.extension_update_safe === true,
|
||||
target: {
|
||||
ecsRegionId: updateTarget.ecs_region_id,
|
||||
ecsInstanceId: updateTarget.ecs_instance_id,
|
||||
serverUpdateSafe: updateTarget.server_update_safe
|
||||
}
|
||||
});
|
||||
return { ok: true, connected: true, ...publicWorker, extension_update: extensionUpdate };
|
||||
});
|
||||
|
||||
app.get('/api/extension-updates/releases', async (request) => {
|
||||
const session = await requireAdminSession(request);
|
||||
return {
|
||||
ok: true,
|
||||
enabled: config.EXTENSION_AUTO_UPDATE_ENABLED,
|
||||
releases: await extensionUpdates.listReleases(session.user.organizationId)
|
||||
};
|
||||
});
|
||||
|
||||
app.post('/api/extension-updates/releases', {
|
||||
config: { rateLimit: { max: 5, timeWindow: '1 minute' } }
|
||||
}, async (request) => {
|
||||
const session = await requireAdminMutationSession(request);
|
||||
const body = extensionReleasePublishSchema.parse(request.body);
|
||||
const normalized = body.package_base64.replace(/\s+/gu, '');
|
||||
if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(normalized)) {
|
||||
throw new ExtensionUpdateError('extension_package_base64_invalid', '插件包编码无效。');
|
||||
}
|
||||
const content = Buffer.from(normalized, 'base64');
|
||||
const canonical = content.toString('base64').replace(/=+$/u, '');
|
||||
if (canonical !== normalized.replace(/=+$/u, '')) {
|
||||
throw new ExtensionUpdateError('extension_package_base64_invalid', '插件包编码无效。');
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
release: await extensionUpdates.publishRelease({
|
||||
organizationId: session.user.organizationId,
|
||||
actorUserId: session.user.id,
|
||||
requestId: requestId(request),
|
||||
content
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
app.get('/api/extension-updates/package/:token', {
|
||||
// A fleet may share one outbound NAT address and start together after a
|
||||
// release. The HMAC token is already release/organization/host/expiry
|
||||
// scoped, so keep only a generous abuse ceiling here.
|
||||
config: { rateLimit: { max: 300, timeWindow: '1 minute' } }
|
||||
}, async (request, reply) => {
|
||||
const params = request.params as { token: string };
|
||||
const download = await extensionUpdates.downloadPackage(params.token);
|
||||
reply.header('Cache-Control', 'private, no-store, max-age=0');
|
||||
reply.header('Content-Type', 'application/zip');
|
||||
reply.header('Content-Disposition', attachmentContentDisposition(download.fileName));
|
||||
return reply.send(download.content);
|
||||
return { ok: true, connected: true, ...worker };
|
||||
});
|
||||
|
||||
app.get('/api/audit', async (request) => {
|
||||
@@ -1586,7 +1488,7 @@ export async function buildServer({
|
||||
});
|
||||
|
||||
app.setErrorHandler((error, request, reply) => {
|
||||
if (error instanceof AuthError || error instanceof TaskError || error instanceof ExtensionUpdateError) {
|
||||
if (error instanceof AuthError || error instanceof TaskError) {
|
||||
request.log.warn({
|
||||
diagnostic_event: 'http.request.rejected',
|
||||
diagnostic_stage: 'http',
|
||||
@@ -1633,14 +1535,13 @@ export async function buildServer({
|
||||
}
|
||||
|
||||
app.addHook('onClose', async () => {
|
||||
extensionUpdates.close();
|
||||
app.log.info({
|
||||
diagnostic_event: 'service.closing',
|
||||
diagnostic_stage: 'shutdown'
|
||||
}, 'control plane closing');
|
||||
await closePool();
|
||||
});
|
||||
return { app, auth, tasks, agentBus, channelService, extensionUpdates };
|
||||
return { app, auth, tasks, agentBus, channelService };
|
||||
}
|
||||
|
||||
function installProcessDiagnostics(app: Awaited<ReturnType<typeof buildServer>>['app']): void {
|
||||
|
||||
Reference in New Issue
Block a user