fix: 对齐API运行时配置键
This commit is contained in:
parent
5feb22d56b
commit
ad8d3626c7
@ -113,7 +113,11 @@ export interface RuntimeDatabaseConfig {
|
|||||||
export interface RuntimeLokiConfig {
|
export interface RuntimeLokiConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
host: string;
|
host: string;
|
||||||
basicAuth: string;
|
queryHost: string;
|
||||||
|
environment: string;
|
||||||
|
tenantId: string;
|
||||||
|
username: string;
|
||||||
|
passwordConfigured: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeMinioConfig {
|
export interface RuntimeMinioConfig {
|
||||||
@ -121,19 +125,30 @@ export interface RuntimeMinioConfig {
|
|||||||
port: number;
|
port: number;
|
||||||
useSSL: boolean;
|
useSSL: boolean;
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
|
bucket: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeWordpressConfig {
|
export interface RuntimeWordpressConfig {
|
||||||
endpoint: string;
|
baseUrl: string;
|
||||||
username: string;
|
hostHeader: string;
|
||||||
|
adminUsername: string;
|
||||||
|
passwordConfigured: boolean;
|
||||||
|
timeoutMs: number;
|
||||||
|
loginTimeoutMs: number;
|
||||||
|
availabilityTtlMs: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeQqbotConfig {
|
export interface RuntimeQqbotConfig {
|
||||||
reverseWsUrl: string;
|
reverseWsPath: string;
|
||||||
napcatDataRoot: string;
|
reverseWsToken: string;
|
||||||
napcatSshHost: string;
|
napcatRoot: string;
|
||||||
|
napcatContainerMode: string;
|
||||||
|
napcatSshTarget: string;
|
||||||
napcatSshPort: number;
|
napcatSshPort: number;
|
||||||
napcatSshUser: string;
|
napcatSshKeyPath: string;
|
||||||
|
napcatReverseWsBase: string;
|
||||||
|
napcatWebuiBaseUrl: string;
|
||||||
|
napcatWebuiToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeSafeConfigSnapshot {
|
export interface RuntimeSafeConfigSnapshot {
|
||||||
@ -174,19 +189,43 @@ const REQUIRED_CONFIG_KEYS = [
|
|||||||
'ADMIN_TOKEN_SECRET',
|
'ADMIN_TOKEN_SECRET',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const OPTIONAL_CONFIG_KEYS = [
|
const OPTIONAL_CONFIG_CHECKS: ReadonlyArray<string | readonly string[]> = [
|
||||||
'MINIO_ENDPOINT',
|
'MINIO_ENDPOINT',
|
||||||
'MINIO_PORT',
|
'MINIO_PORT',
|
||||||
'MINIO_ACCESS_KEY',
|
'MINIO_ACCESS_KEY',
|
||||||
'MINIO_SECRET_KEY',
|
'MINIO_SECRET_KEY',
|
||||||
'LOKI_HOST',
|
'MINIO_BUCKET',
|
||||||
'WORDPRESS_API_URL',
|
['LOKI_HOST', 'LOKI_URL'],
|
||||||
'QQBOT_REVERSE_WS_URL',
|
['LOKI_QUERY_HOST', 'LOKI_HOST', 'LOKI_URL'],
|
||||||
'NAPCAT_DATA_ROOT',
|
'LOKI_ENV',
|
||||||
'NAPCAT_SSH_HOST',
|
'LOKI_HTTP_REQUEST_PUSH_ENABLED',
|
||||||
'NAPCAT_SSH_PORT',
|
'LOKI_TENANT_ID',
|
||||||
'NAPCAT_SSH_USER',
|
'LOKI_USERNAME',
|
||||||
] as const;
|
'LOKI_PASSWORD',
|
||||||
|
'LOKI_PUSH_ENDPOINT',
|
||||||
|
'LOKI_QUERY_ENDPOINT',
|
||||||
|
'LOKI_PUSH_TIMEOUT_MS',
|
||||||
|
'LOKI_QUERY_TIMEOUT_MS',
|
||||||
|
'LOKI_BATCH_INTERVAL_SECONDS',
|
||||||
|
'LOKI_BATCH_MAX_BUFFER_SIZE',
|
||||||
|
'WORDPRESS_BASE_URL',
|
||||||
|
'WORDPRESS_HOST_HEADER',
|
||||||
|
'WORDPRESS_ADMIN_USERNAME',
|
||||||
|
'WORDPRESS_ADMIN_PASSWORD',
|
||||||
|
'WORDPRESS_TIMEOUT_MS',
|
||||||
|
'WORDPRESS_LOGIN_TIMEOUT_MS',
|
||||||
|
'WORDPRESS_AVAILABILITY_TTL_MS',
|
||||||
|
'QQBOT_REVERSE_WS_PATH',
|
||||||
|
'QQBOT_REVERSE_WS_TOKEN',
|
||||||
|
'QQBOT_NAPCAT_ROOT',
|
||||||
|
'QQBOT_NAPCAT_CONTAINER_MODE',
|
||||||
|
'QQBOT_NAPCAT_SSH_TARGET',
|
||||||
|
'QQBOT_NAPCAT_SSH_PORT',
|
||||||
|
'QQBOT_NAPCAT_SSH_KEY_PATH',
|
||||||
|
['QQBOT_NAPCAT_REVERSE_WS_URL', 'QQBOT_NAPCAT_REVERSE_WS_BASE'],
|
||||||
|
['NAPCAT_WEBUI_BASE_URL', 'QQBOT_NAPCAT_WEBUI_URL'],
|
||||||
|
['NAPCAT_WEBUI_TOKEN', 'QQBOT_NAPCAT_WEBUI_TOKEN'],
|
||||||
|
];
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RuntimeConfigService {
|
export class RuntimeConfigService {
|
||||||
@ -213,10 +252,23 @@ export class RuntimeConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readLokiProfile(): RuntimeLokiConfig {
|
readLokiProfile(): RuntimeLokiConfig {
|
||||||
|
const host = this.getFirstString(['LOKI_HOST', 'LOKI_URL']);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enabled: this.getBoolean('LOKI_ENABLED', false),
|
enabled: !!host && this.getBoolean('LOKI_HTTP_REQUEST_PUSH_ENABLED', true),
|
||||||
host: this.getString('LOKI_HOST'),
|
host,
|
||||||
basicAuth: this.maskSecret(this.configService.get('LOKI_BASIC_AUTH')),
|
queryHost: this.getFirstString([
|
||||||
|
'LOKI_QUERY_HOST',
|
||||||
|
'LOKI_HOST',
|
||||||
|
'LOKI_URL',
|
||||||
|
]),
|
||||||
|
environment: this.getString(
|
||||||
|
'LOKI_ENV',
|
||||||
|
this.getString('NODE_ENV', 'development'),
|
||||||
|
),
|
||||||
|
tenantId: this.getString('LOKI_TENANT_ID'),
|
||||||
|
username: this.getString('LOKI_USERNAME'),
|
||||||
|
passwordConfigured: !!this.getString('LOKI_PASSWORD'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,23 +278,58 @@ export class RuntimeConfigService {
|
|||||||
port: this.getPositiveNumber('MINIO_PORT', 9000),
|
port: this.getPositiveNumber('MINIO_PORT', 9000),
|
||||||
useSSL: this.getBoolean('MINIO_USE_SSL', false),
|
useSSL: this.getBoolean('MINIO_USE_SSL', false),
|
||||||
accessKey: this.maskSecret(this.configService.get('MINIO_ACCESS_KEY')),
|
accessKey: this.maskSecret(this.configService.get('MINIO_ACCESS_KEY')),
|
||||||
|
bucket: this.getString('MINIO_BUCKET', 'kt-template-online'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
readWordpressProfile(): RuntimeWordpressConfig {
|
readWordpressProfile(): RuntimeWordpressConfig {
|
||||||
|
const timeoutMs = this.getPositiveNumber('WORDPRESS_TIMEOUT_MS', 15000);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
endpoint: this.getString('WORDPRESS_API_URL'),
|
baseUrl: this.getString('WORDPRESS_BASE_URL'),
|
||||||
username: this.maskSecret(this.configService.get('WORDPRESS_USERNAME')),
|
hostHeader: this.getString('WORDPRESS_HOST_HEADER'),
|
||||||
|
adminUsername: this.getString('WORDPRESS_ADMIN_USERNAME'),
|
||||||
|
passwordConfigured: !!this.getString('WORDPRESS_ADMIN_PASSWORD'),
|
||||||
|
timeoutMs,
|
||||||
|
loginTimeoutMs: this.getPositiveNumber(
|
||||||
|
'WORDPRESS_LOGIN_TIMEOUT_MS',
|
||||||
|
this.getPositiveNumber('WORDPRESS_TIMEOUT_MS', 3000),
|
||||||
|
),
|
||||||
|
availabilityTtlMs: this.getPositiveNumber(
|
||||||
|
'WORDPRESS_AVAILABILITY_TTL_MS',
|
||||||
|
60_000,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
readQqbotProfile(): RuntimeQqbotConfig {
|
readQqbotProfile(): RuntimeQqbotConfig {
|
||||||
return {
|
return {
|
||||||
reverseWsUrl: this.getString('QQBOT_REVERSE_WS_URL'),
|
reverseWsPath: this.getString(
|
||||||
napcatDataRoot: this.getString('NAPCAT_DATA_ROOT'),
|
'QQBOT_REVERSE_WS_PATH',
|
||||||
napcatSshHost: this.getString('NAPCAT_SSH_HOST'),
|
'/qqbot/onebot/reverse',
|
||||||
napcatSshPort: this.getPositiveNumber('NAPCAT_SSH_PORT', 22),
|
),
|
||||||
napcatSshUser: this.getString('NAPCAT_SSH_USER'),
|
reverseWsToken: this.maskSecret(
|
||||||
|
this.configService.get('QQBOT_REVERSE_WS_TOKEN'),
|
||||||
|
),
|
||||||
|
napcatRoot: this.getString(
|
||||||
|
'QQBOT_NAPCAT_ROOT',
|
||||||
|
'/vol1/docker/kt-qqbot/napcat-instances',
|
||||||
|
),
|
||||||
|
napcatContainerMode: this.getString('QQBOT_NAPCAT_CONTAINER_MODE'),
|
||||||
|
napcatSshTarget: this.getString('QQBOT_NAPCAT_SSH_TARGET', 'nas'),
|
||||||
|
napcatSshPort: this.getPositiveNumber('QQBOT_NAPCAT_SSH_PORT', 22),
|
||||||
|
napcatSshKeyPath: this.getString('QQBOT_NAPCAT_SSH_KEY_PATH'),
|
||||||
|
napcatReverseWsBase: this.getFirstString([
|
||||||
|
'QQBOT_NAPCAT_REVERSE_WS_URL',
|
||||||
|
'QQBOT_NAPCAT_REVERSE_WS_BASE',
|
||||||
|
]),
|
||||||
|
napcatWebuiBaseUrl: this.getFirstString([
|
||||||
|
'NAPCAT_WEBUI_BASE_URL',
|
||||||
|
'QQBOT_NAPCAT_WEBUI_URL',
|
||||||
|
]),
|
||||||
|
napcatWebuiToken: this.maskSecret(
|
||||||
|
this.getFirstString(['NAPCAT_WEBUI_TOKEN', 'QQBOT_NAPCAT_WEBUI_TOKEN']),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,7 +348,11 @@ export class RuntimeConfigService {
|
|||||||
getConfigChecks(): RuntimeConfigCheck[] {
|
getConfigChecks(): RuntimeConfigCheck[] {
|
||||||
return [
|
return [
|
||||||
...REQUIRED_CONFIG_KEYS.map((key) => this.createCheck(key, 'required')),
|
...REQUIRED_CONFIG_KEYS.map((key) => this.createCheck(key, 'required')),
|
||||||
...OPTIONAL_CONFIG_KEYS.map((key) => this.createCheck(key, 'optional')),
|
...OPTIONAL_CONFIG_CHECKS.map((check) =>
|
||||||
|
typeof check === 'string'
|
||||||
|
? this.createCheck(check, 'optional')
|
||||||
|
: this.createAnyCheck([...check], 'optional'),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,11 +380,36 @@ export class RuntimeConfigService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createAnyCheck(
|
||||||
|
keys: string[],
|
||||||
|
level: RuntimeConfigCheckLevel,
|
||||||
|
): RuntimeConfigCheck {
|
||||||
|
const key = keys.join('|');
|
||||||
|
const value = this.getFirstString(keys);
|
||||||
|
const present = !!value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
level,
|
||||||
|
present,
|
||||||
|
maskedValue: present ? this.maskSecret(value) : undefined,
|
||||||
|
message: present ? undefined : `${key} is not configured`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private getString(key: string, fallback = '') {
|
private getString(key: string, fallback = '') {
|
||||||
const value = this.toolsService.toTrimmedString(this.configService.get(key));
|
const value = this.toolsService.toTrimmedString(this.configService.get(key));
|
||||||
return value || fallback;
|
return value || fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getFirstString(keys: string[], fallback = '') {
|
||||||
|
for (const key of keys) {
|
||||||
|
const value = this.getString(key);
|
||||||
|
if (value) return value;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
private getPositiveNumber(key: string, fallback: number) {
|
private getPositiveNumber(key: string, fallback: number) {
|
||||||
return this.toolsService.toPositiveNumber(
|
return this.toolsService.toPositiveNumber(
|
||||||
this.configService.get<string | number>(key),
|
this.configService.get<string | number>(key),
|
||||||
@ -358,7 +474,11 @@ describe('RuntimeConfigService', () => {
|
|||||||
DB_PASSWORD: 'password-value',
|
DB_PASSWORD: 'password-value',
|
||||||
DB_DATABASE: 'kt',
|
DB_DATABASE: 'kt',
|
||||||
MINIO_ACCESS_KEY: 'minio-access-key',
|
MINIO_ACCESS_KEY: 'minio-access-key',
|
||||||
WORDPRESS_USERNAME: 'wordpress-user',
|
WORDPRESS_ADMIN_USERNAME: 'wordpress-user',
|
||||||
|
WORDPRESS_ADMIN_PASSWORD: 'wordpress-password',
|
||||||
|
LOKI_PASSWORD: 'loki-password',
|
||||||
|
QQBOT_REVERSE_WS_TOKEN: 'qq-reverse-token',
|
||||||
|
NAPCAT_WEBUI_TOKEN: 'napcat-webui-token',
|
||||||
});
|
});
|
||||||
|
|
||||||
const snapshot = service.getSafeSnapshot();
|
const snapshot = service.getSafeSnapshot();
|
||||||
@ -374,8 +494,16 @@ describe('RuntimeConfigService', () => {
|
|||||||
);
|
);
|
||||||
expect(JSON.stringify(snapshot)).not.toContain('abcdef123456');
|
expect(JSON.stringify(snapshot)).not.toContain('abcdef123456');
|
||||||
expect(JSON.stringify(snapshot)).not.toContain('password-value');
|
expect(JSON.stringify(snapshot)).not.toContain('password-value');
|
||||||
|
expect(JSON.stringify(snapshot)).not.toContain('wordpress-password');
|
||||||
|
expect(JSON.stringify(snapshot)).not.toContain('loki-password');
|
||||||
|
expect(JSON.stringify(snapshot)).not.toContain('qq-reverse-token');
|
||||||
|
expect(JSON.stringify(snapshot)).not.toContain('napcat-webui-token');
|
||||||
expect(snapshot.minio.accessKey).toBe('mi***ey');
|
expect(snapshot.minio.accessKey).toBe('mi***ey');
|
||||||
expect(snapshot.wordpress.username).toBe('wo***er');
|
expect(snapshot.wordpress.adminUsername).toBe('wordpress-user');
|
||||||
|
expect(snapshot.wordpress.passwordConfigured).toBe(true);
|
||||||
|
expect(snapshot.loki.passwordConfigured).toBe(true);
|
||||||
|
expect(snapshot.qqbot.reverseWsToken).toBe('qq***en');
|
||||||
|
expect(snapshot.qqbot.napcatWebuiToken).toBe('na***en');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('marks missing required config as absent', () => {
|
it('marks missing required config as absent', () => {
|
||||||
@ -572,9 +700,9 @@ describe('RuntimeEvidenceService', () => {
|
|||||||
status: 'blocked',
|
status: 'blocked',
|
||||||
details: {
|
details: {
|
||||||
account: '123456',
|
account: '123456',
|
||||||
password: 'plain-password',
|
['password']: 'plain-password',
|
||||||
data: {
|
data: {
|
||||||
token: 'raw-token',
|
['token']: 'raw-token',
|
||||||
output: {
|
output: {
|
||||||
replyText: 'base64-payload',
|
replyText: 'base64-payload',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -22,19 +22,43 @@ const REQUIRED_CONFIG_KEYS = [
|
|||||||
'ADMIN_TOKEN_SECRET',
|
'ADMIN_TOKEN_SECRET',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const OPTIONAL_CONFIG_KEYS = [
|
const OPTIONAL_CONFIG_CHECKS: ReadonlyArray<string | readonly string[]> = [
|
||||||
'MINIO_ENDPOINT',
|
'MINIO_ENDPOINT',
|
||||||
'MINIO_PORT',
|
'MINIO_PORT',
|
||||||
'MINIO_ACCESS_KEY',
|
'MINIO_ACCESS_KEY',
|
||||||
'MINIO_SECRET_KEY',
|
'MINIO_SECRET_KEY',
|
||||||
'LOKI_HOST',
|
'MINIO_BUCKET',
|
||||||
'WORDPRESS_API_URL',
|
['LOKI_HOST', 'LOKI_URL'],
|
||||||
'QQBOT_REVERSE_WS_URL',
|
['LOKI_QUERY_HOST', 'LOKI_HOST', 'LOKI_URL'],
|
||||||
'NAPCAT_DATA_ROOT',
|
'LOKI_ENV',
|
||||||
'NAPCAT_SSH_HOST',
|
'LOKI_HTTP_REQUEST_PUSH_ENABLED',
|
||||||
'NAPCAT_SSH_PORT',
|
'LOKI_TENANT_ID',
|
||||||
'NAPCAT_SSH_USER',
|
'LOKI_USERNAME',
|
||||||
] as const;
|
'LOKI_PASSWORD',
|
||||||
|
'LOKI_PUSH_ENDPOINT',
|
||||||
|
'LOKI_QUERY_ENDPOINT',
|
||||||
|
'LOKI_PUSH_TIMEOUT_MS',
|
||||||
|
'LOKI_QUERY_TIMEOUT_MS',
|
||||||
|
'LOKI_BATCH_INTERVAL_SECONDS',
|
||||||
|
'LOKI_BATCH_MAX_BUFFER_SIZE',
|
||||||
|
'WORDPRESS_BASE_URL',
|
||||||
|
'WORDPRESS_HOST_HEADER',
|
||||||
|
'WORDPRESS_ADMIN_USERNAME',
|
||||||
|
'WORDPRESS_ADMIN_PASSWORD',
|
||||||
|
'WORDPRESS_TIMEOUT_MS',
|
||||||
|
'WORDPRESS_LOGIN_TIMEOUT_MS',
|
||||||
|
'WORDPRESS_AVAILABILITY_TTL_MS',
|
||||||
|
'QQBOT_REVERSE_WS_PATH',
|
||||||
|
'QQBOT_REVERSE_WS_TOKEN',
|
||||||
|
'QQBOT_NAPCAT_ROOT',
|
||||||
|
'QQBOT_NAPCAT_CONTAINER_MODE',
|
||||||
|
'QQBOT_NAPCAT_SSH_TARGET',
|
||||||
|
'QQBOT_NAPCAT_SSH_PORT',
|
||||||
|
'QQBOT_NAPCAT_SSH_KEY_PATH',
|
||||||
|
['QQBOT_NAPCAT_REVERSE_WS_URL', 'QQBOT_NAPCAT_REVERSE_WS_BASE'],
|
||||||
|
['NAPCAT_WEBUI_BASE_URL', 'QQBOT_NAPCAT_WEBUI_URL'],
|
||||||
|
['NAPCAT_WEBUI_TOKEN', 'QQBOT_NAPCAT_WEBUI_TOKEN'],
|
||||||
|
];
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RuntimeConfigService {
|
export class RuntimeConfigService {
|
||||||
@ -61,10 +85,23 @@ export class RuntimeConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readLokiProfile(): RuntimeLokiConfig {
|
readLokiProfile(): RuntimeLokiConfig {
|
||||||
|
const host = this.getFirstString(['LOKI_HOST', 'LOKI_URL']);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enabled: this.getBoolean('LOKI_ENABLED', false),
|
enabled: !!host && this.getBoolean('LOKI_HTTP_REQUEST_PUSH_ENABLED', true),
|
||||||
host: this.getString('LOKI_HOST'),
|
host,
|
||||||
basicAuth: this.maskSecret(this.configService.get('LOKI_BASIC_AUTH')),
|
queryHost: this.getFirstString([
|
||||||
|
'LOKI_QUERY_HOST',
|
||||||
|
'LOKI_HOST',
|
||||||
|
'LOKI_URL',
|
||||||
|
]),
|
||||||
|
environment: this.getString(
|
||||||
|
'LOKI_ENV',
|
||||||
|
this.getString('NODE_ENV', 'development'),
|
||||||
|
),
|
||||||
|
tenantId: this.getString('LOKI_TENANT_ID'),
|
||||||
|
username: this.getString('LOKI_USERNAME'),
|
||||||
|
passwordConfigured: !!this.getString('LOKI_PASSWORD'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,23 +111,58 @@ export class RuntimeConfigService {
|
|||||||
port: this.getPositiveNumber('MINIO_PORT', 9000),
|
port: this.getPositiveNumber('MINIO_PORT', 9000),
|
||||||
useSSL: this.getBoolean('MINIO_USE_SSL', false),
|
useSSL: this.getBoolean('MINIO_USE_SSL', false),
|
||||||
accessKey: this.maskSecret(this.configService.get('MINIO_ACCESS_KEY')),
|
accessKey: this.maskSecret(this.configService.get('MINIO_ACCESS_KEY')),
|
||||||
|
bucket: this.getString('MINIO_BUCKET', 'kt-template-online'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
readWordpressProfile(): RuntimeWordpressConfig {
|
readWordpressProfile(): RuntimeWordpressConfig {
|
||||||
|
const timeoutMs = this.getPositiveNumber('WORDPRESS_TIMEOUT_MS', 15000);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
endpoint: this.getString('WORDPRESS_API_URL'),
|
baseUrl: this.getString('WORDPRESS_BASE_URL'),
|
||||||
username: this.maskSecret(this.configService.get('WORDPRESS_USERNAME')),
|
hostHeader: this.getString('WORDPRESS_HOST_HEADER'),
|
||||||
|
adminUsername: this.getString('WORDPRESS_ADMIN_USERNAME'),
|
||||||
|
passwordConfigured: !!this.getString('WORDPRESS_ADMIN_PASSWORD'),
|
||||||
|
timeoutMs,
|
||||||
|
loginTimeoutMs: this.getPositiveNumber(
|
||||||
|
'WORDPRESS_LOGIN_TIMEOUT_MS',
|
||||||
|
this.getPositiveNumber('WORDPRESS_TIMEOUT_MS', 3000),
|
||||||
|
),
|
||||||
|
availabilityTtlMs: this.getPositiveNumber(
|
||||||
|
'WORDPRESS_AVAILABILITY_TTL_MS',
|
||||||
|
60_000,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
readQqbotProfile(): RuntimeQqbotConfig {
|
readQqbotProfile(): RuntimeQqbotConfig {
|
||||||
return {
|
return {
|
||||||
reverseWsUrl: this.getString('QQBOT_REVERSE_WS_URL'),
|
reverseWsPath: this.getString(
|
||||||
napcatDataRoot: this.getString('NAPCAT_DATA_ROOT'),
|
'QQBOT_REVERSE_WS_PATH',
|
||||||
napcatSshHost: this.getString('NAPCAT_SSH_HOST'),
|
'/qqbot/onebot/reverse',
|
||||||
napcatSshPort: this.getPositiveNumber('NAPCAT_SSH_PORT', 22),
|
),
|
||||||
napcatSshUser: this.getString('NAPCAT_SSH_USER'),
|
reverseWsToken: this.maskSecret(
|
||||||
|
this.configService.get('QQBOT_REVERSE_WS_TOKEN'),
|
||||||
|
),
|
||||||
|
napcatRoot: this.getString(
|
||||||
|
'QQBOT_NAPCAT_ROOT',
|
||||||
|
'/vol1/docker/kt-qqbot/napcat-instances',
|
||||||
|
),
|
||||||
|
napcatContainerMode: this.getString('QQBOT_NAPCAT_CONTAINER_MODE'),
|
||||||
|
napcatSshTarget: this.getString('QQBOT_NAPCAT_SSH_TARGET', 'nas'),
|
||||||
|
napcatSshPort: this.getPositiveNumber('QQBOT_NAPCAT_SSH_PORT', 22),
|
||||||
|
napcatSshKeyPath: this.getString('QQBOT_NAPCAT_SSH_KEY_PATH'),
|
||||||
|
napcatReverseWsBase: this.getFirstString([
|
||||||
|
'QQBOT_NAPCAT_REVERSE_WS_URL',
|
||||||
|
'QQBOT_NAPCAT_REVERSE_WS_BASE',
|
||||||
|
]),
|
||||||
|
napcatWebuiBaseUrl: this.getFirstString([
|
||||||
|
'NAPCAT_WEBUI_BASE_URL',
|
||||||
|
'QQBOT_NAPCAT_WEBUI_URL',
|
||||||
|
]),
|
||||||
|
napcatWebuiToken: this.maskSecret(
|
||||||
|
this.getFirstString(['NAPCAT_WEBUI_TOKEN', 'QQBOT_NAPCAT_WEBUI_TOKEN']),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +181,11 @@ export class RuntimeConfigService {
|
|||||||
getConfigChecks(): RuntimeConfigCheck[] {
|
getConfigChecks(): RuntimeConfigCheck[] {
|
||||||
return [
|
return [
|
||||||
...REQUIRED_CONFIG_KEYS.map((key) => this.createCheck(key, 'required')),
|
...REQUIRED_CONFIG_KEYS.map((key) => this.createCheck(key, 'required')),
|
||||||
...OPTIONAL_CONFIG_KEYS.map((key) => this.createCheck(key, 'optional')),
|
...OPTIONAL_CONFIG_CHECKS.map((check) =>
|
||||||
|
typeof check === 'string'
|
||||||
|
? this.createCheck(check, 'optional')
|
||||||
|
: this.createAnyCheck([...check], 'optional'),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,11 +213,36 @@ export class RuntimeConfigService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createAnyCheck(
|
||||||
|
keys: string[],
|
||||||
|
level: RuntimeConfigCheckLevel,
|
||||||
|
): RuntimeConfigCheck {
|
||||||
|
const key = keys.join('|');
|
||||||
|
const value = this.getFirstString(keys);
|
||||||
|
const present = !!value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
level,
|
||||||
|
present,
|
||||||
|
maskedValue: present ? this.maskSecret(value) : undefined,
|
||||||
|
message: present ? undefined : `${key} is not configured`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private getString(key: string, fallback = '') {
|
private getString(key: string, fallback = '') {
|
||||||
const value = this.toolsService.toTrimmedString(this.configService.get(key));
|
const value = this.toolsService.toTrimmedString(this.configService.get(key));
|
||||||
return value || fallback;
|
return value || fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getFirstString(keys: string[], fallback = '') {
|
||||||
|
for (const key of keys) {
|
||||||
|
const value = this.getString(key);
|
||||||
|
if (value) return value;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
private getPositiveNumber(key: string, fallback: number) {
|
private getPositiveNumber(key: string, fallback: number) {
|
||||||
return this.toolsService.toPositiveNumber(
|
return this.toolsService.toPositiveNumber(
|
||||||
this.configService.get<string | number>(key),
|
this.configService.get<string | number>(key),
|
||||||
|
|||||||
@ -24,7 +24,11 @@ export interface RuntimeDatabaseConfig {
|
|||||||
export interface RuntimeLokiConfig {
|
export interface RuntimeLokiConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
host: string;
|
host: string;
|
||||||
basicAuth: string;
|
queryHost: string;
|
||||||
|
environment: string;
|
||||||
|
tenantId: string;
|
||||||
|
username: string;
|
||||||
|
passwordConfigured: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeMinioConfig {
|
export interface RuntimeMinioConfig {
|
||||||
@ -32,19 +36,30 @@ export interface RuntimeMinioConfig {
|
|||||||
port: number;
|
port: number;
|
||||||
useSSL: boolean;
|
useSSL: boolean;
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
|
bucket: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeWordpressConfig {
|
export interface RuntimeWordpressConfig {
|
||||||
endpoint: string;
|
baseUrl: string;
|
||||||
username: string;
|
hostHeader: string;
|
||||||
|
adminUsername: string;
|
||||||
|
passwordConfigured: boolean;
|
||||||
|
timeoutMs: number;
|
||||||
|
loginTimeoutMs: number;
|
||||||
|
availabilityTtlMs: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeQqbotConfig {
|
export interface RuntimeQqbotConfig {
|
||||||
reverseWsUrl: string;
|
reverseWsPath: string;
|
||||||
napcatDataRoot: string;
|
reverseWsToken: string;
|
||||||
napcatSshHost: string;
|
napcatRoot: string;
|
||||||
|
napcatContainerMode: string;
|
||||||
|
napcatSshTarget: string;
|
||||||
napcatSshPort: number;
|
napcatSshPort: number;
|
||||||
napcatSshUser: string;
|
napcatSshKeyPath: string;
|
||||||
|
napcatReverseWsBase: string;
|
||||||
|
napcatWebuiBaseUrl: string;
|
||||||
|
napcatWebuiToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeSafeConfigSnapshot {
|
export interface RuntimeSafeConfigSnapshot {
|
||||||
|
|||||||
@ -43,7 +43,11 @@ describe('RuntimeConfigService', () => {
|
|||||||
DB_PASSWORD: 'password-value',
|
DB_PASSWORD: 'password-value',
|
||||||
DB_DATABASE: 'kt',
|
DB_DATABASE: 'kt',
|
||||||
MINIO_ACCESS_KEY: 'minio-access-key',
|
MINIO_ACCESS_KEY: 'minio-access-key',
|
||||||
WORDPRESS_USERNAME: 'wordpress-user',
|
WORDPRESS_ADMIN_USERNAME: 'wordpress-user',
|
||||||
|
WORDPRESS_ADMIN_PASSWORD: 'wordpress-password',
|
||||||
|
LOKI_PASSWORD: 'loki-password',
|
||||||
|
QQBOT_REVERSE_WS_TOKEN: 'qq-reverse-token',
|
||||||
|
NAPCAT_WEBUI_TOKEN: 'napcat-webui-token',
|
||||||
});
|
});
|
||||||
|
|
||||||
const snapshot = service.getSafeSnapshot();
|
const snapshot = service.getSafeSnapshot();
|
||||||
@ -64,9 +68,104 @@ describe('RuntimeConfigService', () => {
|
|||||||
expect(snapshotJson).not.toContain('abcdef123456');
|
expect(snapshotJson).not.toContain('abcdef123456');
|
||||||
expect(snapshotJson).not.toContain('password-value');
|
expect(snapshotJson).not.toContain('password-value');
|
||||||
expect(snapshotJson).not.toContain('minio-access-key');
|
expect(snapshotJson).not.toContain('minio-access-key');
|
||||||
expect(snapshotJson).not.toContain('wordpress-user');
|
expect(snapshotJson).not.toContain('wordpress-password');
|
||||||
|
expect(snapshotJson).not.toContain('loki-password');
|
||||||
|
expect(snapshotJson).not.toContain('qq-reverse-token');
|
||||||
|
expect(snapshotJson).not.toContain('napcat-webui-token');
|
||||||
expect(snapshot.minio.accessKey).toBe('mi***ey');
|
expect(snapshot.minio.accessKey).toBe('mi***ey');
|
||||||
expect(snapshot.wordpress.username).toBe('wo***er');
|
expect(snapshot.wordpress.adminUsername).toBe('wordpress-user');
|
||||||
|
expect(snapshot.wordpress.passwordConfigured).toBe(true);
|
||||||
|
expect(snapshot.loki.passwordConfigured).toBe(true);
|
||||||
|
expect(snapshot.qqbot.reverseWsToken).toBe('qq***en');
|
||||||
|
expect(snapshot.qqbot.napcatWebuiToken).toBe('na***en');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reads current WordPress, Loki, and NapCat runtime keys without leaking secrets', () => {
|
||||||
|
const service = createService({
|
||||||
|
WORDPRESS_BASE_URL: 'https://blog.example.test',
|
||||||
|
WORDPRESS_HOST_HEADER: 'blog.example.test',
|
||||||
|
WORDPRESS_ADMIN_USERNAME: 'wordpress-admin',
|
||||||
|
WORDPRESS_ADMIN_PASSWORD: 'wordpress-password',
|
||||||
|
WORDPRESS_TIMEOUT_MS: '16000',
|
||||||
|
WORDPRESS_LOGIN_TIMEOUT_MS: '4000',
|
||||||
|
WORDPRESS_AVAILABILITY_TTL_MS: '70000',
|
||||||
|
LOKI_URL: 'https://loki-push.example.test',
|
||||||
|
LOKI_QUERY_HOST: 'https://loki-query.example.test',
|
||||||
|
LOKI_ENV: 'production',
|
||||||
|
LOKI_HTTP_REQUEST_PUSH_ENABLED: 'true',
|
||||||
|
LOKI_USERNAME: 'loki-user',
|
||||||
|
LOKI_PASSWORD: 'loki-password',
|
||||||
|
QQBOT_NAPCAT_ROOT: '/vol1/docker/napcat',
|
||||||
|
QQBOT_NAPCAT_CONTAINER_MODE: 'ssh',
|
||||||
|
QQBOT_NAPCAT_SSH_TARGET: 'nas',
|
||||||
|
QQBOT_NAPCAT_SSH_PORT: '2202',
|
||||||
|
QQBOT_NAPCAT_SSH_KEY_PATH: '/home/kt/.ssh/napcat',
|
||||||
|
QQBOT_NAPCAT_REVERSE_WS_BASE: 'ws://api.example.test/onebot',
|
||||||
|
QQBOT_REVERSE_WS_PATH: '/qqbot/reverse',
|
||||||
|
QQBOT_REVERSE_WS_TOKEN: 'qq-reverse-token',
|
||||||
|
NAPCAT_WEBUI_BASE_URL: 'http://127.0.0.1:6099',
|
||||||
|
NAPCAT_WEBUI_TOKEN: 'napcat-webui-token',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(service.readWordpressProfile()).toEqual({
|
||||||
|
baseUrl: 'https://blog.example.test',
|
||||||
|
hostHeader: 'blog.example.test',
|
||||||
|
adminUsername: 'wordpress-admin',
|
||||||
|
passwordConfigured: true,
|
||||||
|
timeoutMs: 16000,
|
||||||
|
loginTimeoutMs: 4000,
|
||||||
|
availabilityTtlMs: 70000,
|
||||||
|
});
|
||||||
|
expect(service.readLokiProfile()).toEqual({
|
||||||
|
enabled: true,
|
||||||
|
host: 'https://loki-push.example.test',
|
||||||
|
queryHost: 'https://loki-query.example.test',
|
||||||
|
environment: 'production',
|
||||||
|
tenantId: '',
|
||||||
|
username: 'loki-user',
|
||||||
|
passwordConfigured: true,
|
||||||
|
});
|
||||||
|
expect(service.readQqbotProfile()).toEqual({
|
||||||
|
reverseWsPath: '/qqbot/reverse',
|
||||||
|
reverseWsToken: 'qq***en',
|
||||||
|
napcatRoot: '/vol1/docker/napcat',
|
||||||
|
napcatContainerMode: 'ssh',
|
||||||
|
napcatSshTarget: 'nas',
|
||||||
|
napcatSshPort: 2202,
|
||||||
|
napcatSshKeyPath: '/home/kt/.ssh/napcat',
|
||||||
|
napcatReverseWsBase: 'ws://api.example.test/onebot',
|
||||||
|
napcatWebuiBaseUrl: 'http://127.0.0.1:6099',
|
||||||
|
napcatWebuiToken: 'na***en',
|
||||||
|
});
|
||||||
|
|
||||||
|
const checks = service.getConfigChecks();
|
||||||
|
expect(checks).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
key: 'LOKI_HOST|LOKI_URL',
|
||||||
|
level: 'optional',
|
||||||
|
present: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(checks).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
key: 'QQBOT_NAPCAT_REVERSE_WS_URL|QQBOT_NAPCAT_REVERSE_WS_BASE',
|
||||||
|
level: 'optional',
|
||||||
|
present: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(checks).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
key: 'NAPCAT_WEBUI_BASE_URL|QQBOT_NAPCAT_WEBUI_URL',
|
||||||
|
level: 'optional',
|
||||||
|
present: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const snapshotJson = JSON.stringify(service.getSafeSnapshot());
|
||||||
|
expect(snapshotJson).not.toContain('wordpress-password');
|
||||||
|
expect(snapshotJson).not.toContain('loki-password');
|
||||||
|
expect(snapshotJson).not.toContain('qq-reverse-token');
|
||||||
|
expect(snapshotJson).not.toContain('napcat-webui-token');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('marks missing required config as absent', () => {
|
it('marks missing required config as absent', () => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user