feat(backend): suspend instance by software

This commit is contained in:
anatawa12 2025-03-31 13:10:05 +09:00
parent 49f1f7194d
commit fa731ac717
No known key found for this signature in database
GPG Key ID: 9CA909848B8E4EA6
11 changed files with 137 additions and 41 deletions

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class DeliverSuspendedSoftware1743403874305 {
name = 'DeliverSuspendedSoftware1743403874305'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "deliverSuspendedSoftware" jsonb NOT NULL DEFAULT '[]'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deliverSuspendedSoftware"`);
}
}

View File

@ -166,6 +166,7 @@
"rxjs": "7.8.2",
"sanitize-html": "2.15.0",
"secure-json-parse": "3.0.2",
"semver": "7.7.1",
"sharp": "0.33.5",
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",

View File

@ -6,10 +6,12 @@
import { URL, domainToASCII } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import RE2 from 're2';
import semver from 'semver';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
import { MiMeta } from '@/models/Meta.js';
import { MiMeta, SoftwareSuspension } from '@/models/Meta.js';
import { MiInstance } from '@/models/Instance.js';
@Injectable()
export class UtilityService {
@ -143,4 +145,20 @@ export class UtilityService {
const host = this.extractDbHost(uri);
return this.isFederationAllowedHost(host);
}
@bindThis
public isDeliverSuspendedSoftware(software: Pick<MiInstance, 'softwareName' | 'softwareVersion'>): SoftwareSuspension | undefined {
if (software.softwareName == null) return undefined;
if (software.softwareVersion == null) {
// software version is null; suspend iff versionRange is *
return this.meta.deliverSuspendedSoftware.find(x =>
x.software === software.softwareName
&& x.versionRange.trim() === '*');
} else {
const softwareVersion = software.softwareVersion;
return this.meta.deliverSuspendedSoftware.find(x =>
x.software === software.softwareName
&& semver.satisfies(softwareVersion, x.versionRange, { includePrerelease: true }));
}
}
}

View File

@ -31,6 +31,7 @@ export class InstanceEntityService {
me?: { id: MiUser['id']; } | null | undefined,
): Promise<Packed<'FederationInstance'>> {
const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
const softwareSuspended = this.utilityService.isDeliverSuspendedSoftware(instance);
return {
id: instance.id,
@ -41,8 +42,8 @@ export class InstanceEntityService {
followingCount: instance.followingCount,
followersCount: instance.followersCount,
isNotResponding: instance.isNotResponding,
isSuspended: instance.suspensionState !== 'none',
suspensionState: instance.suspensionState,
isSuspended: instance.suspensionState !== 'none' || Boolean(softwareSuspended),
suspensionState: instance.suspensionState === 'none' && softwareSuspended ? 'softwareSuspended' : instance.suspensionState,
isBlocked: this.utilityService.isBlockedHost(this.meta.blockedHosts, instance.host),
softwareName: instance.softwareName,
softwareVersion: instance.softwareVersion,

View File

@ -664,4 +664,14 @@ export class MiMeta {
nullable: true,
})
public googleAnalyticsMeasurementId: string | null;
@Column('jsonb', {
default: [],
})
public deliverSuspendedSoftware: SoftwareSuspension[];
}
export type SoftwareSuspension = {
software: string,
versionRange: string,
};

View File

@ -48,7 +48,7 @@ export const packedFederationInstanceSchema = {
suspensionState: {
type: 'string',
nullable: false, optional: false,
enum: ['none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding'],
enum: ['none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding', 'softwareSuspended'],
},
isBlocked: {
type: 'boolean',

View File

@ -71,6 +71,15 @@ export class DeliverProcessorService {
return 'skip (suspended)';
}
const i = await (this.meta.enableStatsForFederatedInstances
? this.federatedInstanceService.fetchOrRegister(host)
: this.federatedInstanceService.fetch(host));
// suspend server by software
if (i != null && this.utilityService.isDeliverSuspendedSoftware(i)) {
return 'skip (software suspended)';
}
try {
await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest);
@ -79,10 +88,6 @@ export class DeliverProcessorService {
// Update instance stats
process.nextTick(async () => {
const i = await (this.meta.enableStatsForFederatedInstances
? this.federatedInstanceService.fetchOrRegister(host)
: this.federatedInstanceService.fetch(host));
if (i == null) return;
if (i.isNotResponding) {

View File

@ -528,6 +528,24 @@ export const meta = {
optional: false, nullable: false,
},
},
deliverSuspendedSoftware: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
software: {
type: 'string',
optional: false, nullable: false,
},
versionRange: {
type: 'string',
optional: false, nullable: false,
},
},
},
},
},
},
} as const;
@ -672,6 +690,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl,
federation: instance.federation,
federationHosts: instance.federationHosts,
deliverSuspendedSoftware: instance.deliverSuspendedSoftware,
};
});
}

View File

@ -185,6 +185,17 @@ export const paramDef = {
type: 'string',
},
},
deliverSuspendedSoftware: {
type: 'array',
items: {
type: 'object',
properties: {
software: { type: 'string' },
versionRange: { type: 'string' },
},
required: ['software', 'versionRange'],
},
},
},
required: [],
} as const;
@ -671,6 +682,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.federation = ps.federation;
}
if (ps.deliverSuspendedSoftware !== undefined) {
set.deliverSuspendedSoftware = ps.deliverSuspendedSoftware;
}
if (Array.isArray(ps.federationHosts)) {
set.federationHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase());
}

View File

@ -4933,7 +4933,7 @@ export type components = {
isNotResponding: boolean;
isSuspended: boolean;
/** @enum {string} */
suspensionState: 'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding';
suspensionState: 'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding' | 'softwareSuspended';
isBlocked: boolean;
/** @example misskey */
softwareName: string | null;
@ -8662,6 +8662,10 @@ export type operations = {
/** @enum {string} */
federation: 'all' | 'specified' | 'none';
federationHosts: string[];
deliverSuspendedSoftware: {
software: string;
versionRange: string;
}[];
};
};
};
@ -11007,6 +11011,10 @@ export type operations = {
/** @enum {string} */
federation?: 'all' | 'none' | 'specified';
federationHosts?: string[];
deliverSuspendedSoftware?: {
software: string;
versionRange: string;
}[];
};
};
};

View File

@ -389,6 +389,9 @@ importers:
secure-json-parse:
specifier: 3.0.2
version: 3.0.2
semver:
specifier: 7.7.1
version: 7.7.1
sharp:
specifier: 0.33.5
version: 0.33.5
@ -9491,8 +9494,8 @@ packages:
engines: {node: '>=10'}
hasBin: true
semver@7.6.3:
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
semver@7.7.1:
resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
engines: {node: '>=10'}
hasBin: true
@ -12570,7 +12573,7 @@ snapshots:
nopt: 5.0.0
npmlog: 5.0.1
rimraf: 3.0.2
semver: 7.6.3
semver: 7.7.1
tar: 6.2.1
transitivePeerDependencies:
- encoding
@ -12790,7 +12793,7 @@ snapshots:
'@npmcli/fs@3.1.0':
dependencies:
semver: 7.6.3
semver: 7.7.1
'@nuxt/opencollective@0.4.1':
dependencies:
@ -12912,7 +12915,7 @@ snapshots:
'@opentelemetry/instrumentation': 0.57.1(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
forwarded-parse: 2.1.2
semver: 7.6.3
semver: 7.7.1
transitivePeerDependencies:
- supports-color
@ -13045,7 +13048,7 @@ snapshots:
'@types/shimmer': 1.2.0
import-in-the-middle: 1.11.2
require-in-the-middle: 7.3.0
semver: 7.6.3
semver: 7.7.1
shimmer: 1.2.1
transitivePeerDependencies:
- supports-color
@ -13057,7 +13060,7 @@ snapshots:
'@types/shimmer': 1.2.0
import-in-the-middle: 1.11.2
require-in-the-middle: 7.3.0
semver: 7.6.3
semver: 7.7.1
shimmer: 1.2.1
transitivePeerDependencies:
- supports-color
@ -13069,7 +13072,7 @@ snapshots:
'@types/shimmer': 1.2.0
import-in-the-middle: 1.11.2
require-in-the-middle: 7.3.0
semver: 7.6.3
semver: 7.7.1
shimmer: 1.2.1
transitivePeerDependencies:
- supports-color
@ -14033,7 +14036,7 @@ snapshots:
jsdoc-type-pratt-parser: 4.1.0
process: 0.11.10
recast: 0.23.6
semver: 7.6.3
semver: 7.7.1
util: 0.12.5
ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)
optionalDependencies:
@ -14188,7 +14191,7 @@ snapshots:
fast-glob: 3.3.3
minimatch: 9.0.4
piscina: 4.4.0
semver: 7.6.3
semver: 7.7.1
slash: 3.0.0
source-map: 0.7.4
optionalDependencies:
@ -14857,7 +14860,7 @@ snapshots:
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.4
semver: 7.6.3
semver: 7.7.1
ts-api-utils: 2.0.1(typescript@5.8.2)
typescript: 5.8.2
transitivePeerDependencies:
@ -14871,7 +14874,7 @@ snapshots:
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.4
semver: 7.6.3
semver: 7.7.1
ts-api-utils: 2.0.1(typescript@5.8.2)
typescript: 5.8.2
transitivePeerDependencies:
@ -15636,7 +15639,7 @@ snapshots:
bin-version-check@5.1.0:
dependencies:
bin-version: 6.0.0
semver: 7.6.3
semver: 7.7.1
semver-truncate: 3.0.0
bin-version@6.0.0:
@ -15747,7 +15750,7 @@ snapshots:
ioredis: 5.6.0
msgpackr: 1.11.2
node-abort-controller: 3.1.1
semver: 7.6.3
semver: 7.7.1
tslib: 2.8.1
uuid: 9.0.1
transitivePeerDependencies:
@ -16343,7 +16346,7 @@ snapshots:
process: 0.11.10
proxy-from-env: 1.0.0
request-progress: 3.0.0
semver: 7.6.3
semver: 7.7.1
supports-color: 8.1.1
tmp: 0.2.3
tree-kill: 1.2.2
@ -16389,7 +16392,7 @@ snapshots:
process: 0.11.10
proxy-from-env: 1.0.0
request-progress: 3.0.0
semver: 7.6.3
semver: 7.7.1
supports-color: 8.1.1
tmp: 0.2.3
tree-kill: 1.2.2
@ -16676,7 +16679,7 @@ snapshots:
'@one-ini/wasm': 0.1.1
commander: 10.0.1
minimatch: 9.0.1
semver: 7.6.3
semver: 7.7.1
ee-first@1.1.1: {}
@ -17063,7 +17066,7 @@ snapshots:
natural-compare: 1.4.0
nth-check: 2.1.1
postcss-selector-parser: 6.1.2
semver: 7.6.3
semver: 7.7.1
vue-eslint-parser: 10.1.1(eslint@9.22.0)
xml-name-validator: 4.0.0
@ -17425,7 +17428,7 @@ snapshots:
process-warning: 4.0.0
rfdc: 1.4.1
secure-json-parse: 3.0.2
semver: 7.6.3
semver: 7.7.1
toad-cache: 3.7.0
fastq@1.17.1:
@ -18325,7 +18328,7 @@ snapshots:
'@babel/parser': 7.25.6
'@istanbuljs/schema': 0.1.3
istanbul-lib-coverage: 3.2.2
semver: 7.6.3
semver: 7.7.1
transitivePeerDependencies:
- supports-color
@ -18712,7 +18715,7 @@ snapshots:
jest-util: 29.7.0
natural-compare: 1.4.0
pretty-format: 29.7.0
semver: 7.6.3
semver: 7.7.1
transitivePeerDependencies:
- supports-color
@ -19094,7 +19097,7 @@ snapshots:
make-dir@4.0.0:
dependencies:
semver: 7.6.3
semver: 7.7.1
make-fetch-happen@13.0.0:
dependencies:
@ -19720,7 +19723,7 @@ snapshots:
node-abi@3.62.0:
dependencies:
semver: 7.6.3
semver: 7.7.1
node-abort-controller@3.1.1: {}
@ -19765,7 +19768,7 @@ snapshots:
make-fetch-happen: 13.0.0
nopt: 7.2.0
proc-log: 4.2.0
semver: 7.6.3
semver: 7.7.1
tar: 6.2.1
which: 4.0.0
transitivePeerDependencies:
@ -19784,7 +19787,7 @@ snapshots:
ignore-by-default: 1.0.1
minimatch: 3.1.2
pstree.remy: 1.1.8
semver: 7.6.3
semver: 7.7.1
simple-update-notifier: 2.0.0
supports-color: 5.5.0
touch: 3.1.0
@ -19820,7 +19823,7 @@ snapshots:
dependencies:
hosted-git-info: 4.1.0
is-core-module: 2.15.1
semver: 7.6.3
semver: 7.7.1
validate-npm-package-license: 3.0.4
normalize-path@3.0.0: {}
@ -21027,7 +21030,7 @@ snapshots:
semver-truncate@3.0.0:
dependencies:
semver: 7.6.3
semver: 7.7.1
semver@5.7.1: {}
@ -21037,7 +21040,7 @@ snapshots:
dependencies:
lru-cache: 6.0.0
semver@7.6.3: {}
semver@7.7.1: {}
send@0.19.0:
dependencies:
@ -21099,7 +21102,7 @@ snapshots:
dependencies:
color: 4.2.3
detect-libc: 2.0.3
semver: 7.6.3
semver: 7.7.1
optionalDependencies:
'@img/sharp-darwin-arm64': 0.33.5
'@img/sharp-darwin-x64': 0.33.5
@ -21178,7 +21181,7 @@ snapshots:
simple-update-notifier@2.0.0:
dependencies:
semver: 7.6.3
semver: 7.7.1
sinon@18.0.1:
dependencies:
@ -22205,7 +22208,7 @@ snapshots:
vscode-languageclient@9.0.1:
dependencies:
minimatch: 5.1.2
semver: 7.6.3
semver: 7.7.1
vscode-languageserver-protocol: 3.17.5
vscode-languageserver-protocol@3.17.5:
@ -22260,7 +22263,7 @@ snapshots:
espree: 10.3.0
esquery: 1.6.0
lodash: 4.17.21
semver: 7.6.3
semver: 7.7.1
transitivePeerDependencies:
- supports-color