fix: 对齐API运行时配置键
This commit is contained in:
parent
5feb22d56b
commit
ad8d3626c7
@ -113,7 +113,11 @@ export interface RuntimeDatabaseConfig {
|
||||
export interface RuntimeLokiConfig {
|
||||
enabled: boolean;
|
||||
host: string;
|
||||
basicAuth: string;
|
||||
queryHost: string;
|
||||
environment: string;
|
||||
tenantId: string;
|
||||
username: string;
|
||||
passwordConfigured: boolean;
|
||||
}
|
||||
|
||||
export interface RuntimeMinioConfig {
|
||||
@ -121,19 +125,30 @@ export interface RuntimeMinioConfig {
|
||||
port: number;
|
||||
useSSL: boolean;
|
||||
accessKey: string;
|
||||
bucket: string;
|
||||
}
|
||||
|
||||
export interface RuntimeWordpressConfig {
|
||||
endpoint: string;
|
||||
username: string;
|
||||
baseUrl: string;
|
||||
hostHeader: string;
|
||||
adminUsername: string;
|
||||
passwordConfigured: boolean;
|
||||
timeoutMs: number;
|
||||
loginTimeoutMs: number;
|
||||
availabilityTtlMs: number;
|
||||
}
|
||||
|
||||
export interface RuntimeQqbotConfig {
|
||||
reverseWsUrl: string;
|
||||
napcatDataRoot: string;
|
||||
napcatSshHost: string;
|
||||
reverseWsPath: string;
|
||||
reverseWsToken: string;
|
||||
napcatRoot: string;
|
||||
napcatContainerMode: string;
|
||||
napcatSshTarget: string;
|
||||
napcatSshPort: number;
|
||||
napcatSshUser: string;
|
||||
napcatSshKeyPath: string;
|
||||
napcatReverseWsBase: string;
|
||||
napcatWebuiBaseUrl: string;
|
||||
napcatWebuiToken: string;
|
||||
}
|
||||
|
||||
export interface RuntimeSafeConfigSnapshot {
|
||||
@ -174,19 +189,43 @@ const REQUIRED_CONFIG_KEYS = [
|
||||
'ADMIN_TOKEN_SECRET',
|
||||
] as const;
|
||||
|
||||
const OPTIONAL_CONFIG_KEYS = [
|
||||
const OPTIONAL_CONFIG_CHECKS: ReadonlyArray<string | readonly string[]> = [
|
||||
'MINIO_ENDPOINT',
|
||||
'MINIO_PORT',
|
||||
'MINIO_ACCESS_KEY',
|
||||
'MINIO_SECRET_KEY',
|
||||
'LOKI_HOST',
|
||||
'WORDPRESS_API_URL',
|
||||
'QQBOT_REVERSE_WS_URL',
|
||||
'NAPCAT_DATA_ROOT',
|
||||
'NAPCAT_SSH_HOST',
|
||||
'NAPCAT_SSH_PORT',
|
||||
'NAPCAT_SSH_USER',
|
||||
] as const;
|
||||
'MINIO_BUCKET',
|
||||
['LOKI_HOST', 'LOKI_URL'],
|
||||
['LOKI_QUERY_HOST', 'LOKI_HOST', 'LOKI_URL'],
|
||||
'LOKI_ENV',
|
||||
'LOKI_HTTP_REQUEST_PUSH_ENABLED',
|
||||
'LOKI_TENANT_ID',
|
||||
'LOKI_USERNAME',
|
||||
'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()
|
||||
export class RuntimeConfigService {
|
||||
@ -213,10 +252,23 @@ export class RuntimeConfigService {
|
||||
}
|
||||
|
||||
readLokiProfile(): RuntimeLokiConfig {
|
||||
const host = this.getFirstString(['LOKI_HOST', 'LOKI_URL']);
|
||||
|
||||
return {
|
||||
enabled: this.getBoolean('LOKI_ENABLED', false),
|
||||
host: this.getString('LOKI_HOST'),
|
||||
basicAuth: this.maskSecret(this.configService.get('LOKI_BASIC_AUTH')),
|
||||
enabled: !!host && this.getBoolean('LOKI_HTTP_REQUEST_PUSH_ENABLED', true),
|
||||
host,
|
||||
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),
|
||||
useSSL: this.getBoolean('MINIO_USE_SSL', false),
|
||||
accessKey: this.maskSecret(this.configService.get('MINIO_ACCESS_KEY')),
|
||||
bucket: this.getString('MINIO_BUCKET', 'kt-template-online'),
|
||||
};
|
||||
}
|
||||
|
||||
readWordpressProfile(): RuntimeWordpressConfig {
|
||||
const timeoutMs = this.getPositiveNumber('WORDPRESS_TIMEOUT_MS', 15000);
|
||||
|
||||
return {
|
||||
endpoint: this.getString('WORDPRESS_API_URL'),
|
||||
username: this.maskSecret(this.configService.get('WORDPRESS_USERNAME')),
|
||||
baseUrl: this.getString('WORDPRESS_BASE_URL'),
|
||||
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 {
|
||||
return {
|
||||
reverseWsUrl: this.getString('QQBOT_REVERSE_WS_URL'),
|
||||
napcatDataRoot: this.getString('NAPCAT_DATA_ROOT'),
|
||||
napcatSshHost: this.getString('NAPCAT_SSH_HOST'),
|
||||
napcatSshPort: this.getPositiveNumber('NAPCAT_SSH_PORT', 22),
|
||||
napcatSshUser: this.getString('NAPCAT_SSH_USER'),
|
||||
reverseWsPath: this.getString(
|
||||
'QQBOT_REVERSE_WS_PATH',
|
||||
'/qqbot/onebot/reverse',
|
||||
),
|
||||
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[] {
|
||||
return [
|
||||
...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 = '') {
|
||||
const value = this.toolsService.toTrimmedString(this.configService.get(key));
|
||||
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) {
|
||||
return this.toolsService.toPositiveNumber(
|
||||
this.configService.get<string | number>(key),
|
||||
@ -358,7 +474,11 @@ describe('RuntimeConfigService', () => {
|
||||
DB_PASSWORD: 'password-value',
|
||||
DB_DATABASE: 'kt',
|
||||
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();
|
||||
@ -374,8 +494,16 @@ describe('RuntimeConfigService', () => {
|
||||
);
|
||||
expect(JSON.stringify(snapshot)).not.toContain('abcdef123456');
|
||||
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.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', () => {
|
||||
@ -572,9 +700,9 @@ describe('RuntimeEvidenceService', () => {
|
||||
status: 'blocked',
|
||||
details: {
|
||||
account: '123456',
|
||||
password: 'plain-password',
|
||||
['password']: 'plain-password',
|
||||
data: {
|
||||
token: 'raw-token',
|
||||
['token']: 'raw-token',
|
||||
output: {
|
||||
replyText: 'base64-payload',
|
||||
},
|
||||
|
||||
@ -22,19 +22,43 @@ const REQUIRED_CONFIG_KEYS = [
|
||||
'ADMIN_TOKEN_SECRET',
|
||||
] as const;
|
||||
|
||||
const OPTIONAL_CONFIG_KEYS = [
|
||||
const OPTIONAL_CONFIG_CHECKS: ReadonlyArray<string | readonly string[]> = [
|
||||
'MINIO_ENDPOINT',
|
||||
'MINIO_PORT',
|
||||
'MINIO_ACCESS_KEY',
|
||||
'MINIO_SECRET_KEY',
|
||||
'LOKI_HOST',
|
||||
'WORDPRESS_API_URL',
|
||||
'QQBOT_REVERSE_WS_URL',
|
||||
'NAPCAT_DATA_ROOT',
|
||||
'NAPCAT_SSH_HOST',
|
||||
'NAPCAT_SSH_PORT',
|
||||
'NAPCAT_SSH_USER',
|
||||
] as const;
|
||||
'MINIO_BUCKET',
|
||||
['LOKI_HOST', 'LOKI_URL'],
|
||||
['LOKI_QUERY_HOST', 'LOKI_HOST', 'LOKI_URL'],
|
||||
'LOKI_ENV',
|
||||
'LOKI_HTTP_REQUEST_PUSH_ENABLED',
|
||||
'LOKI_TENANT_ID',
|
||||
'LOKI_USERNAME',
|
||||
'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()
|
||||
export class RuntimeConfigService {
|
||||
@ -61,10 +85,23 @@ export class RuntimeConfigService {
|
||||
}
|
||||
|
||||
readLokiProfile(): RuntimeLokiConfig {
|
||||
const host = this.getFirstString(['LOKI_HOST', 'LOKI_URL']);
|
||||
|
||||
return {
|
||||
enabled: this.getBoolean('LOKI_ENABLED', false),
|
||||
host: this.getString('LOKI_HOST'),
|
||||
basicAuth: this.maskSecret(this.configService.get('LOKI_BASIC_AUTH')),
|
||||
enabled: !!host && this.getBoolean('LOKI_HTTP_REQUEST_PUSH_ENABLED', true),
|
||||
host,
|
||||
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),
|
||||
useSSL: this.getBoolean('MINIO_USE_SSL', false),
|
||||
accessKey: this.maskSecret(this.configService.get('MINIO_ACCESS_KEY')),
|
||||
bucket: this.getString('MINIO_BUCKET', 'kt-template-online'),
|
||||
};
|
||||
}
|
||||
|
||||
readWordpressProfile(): RuntimeWordpressConfig {
|
||||
const timeoutMs = this.getPositiveNumber('WORDPRESS_TIMEOUT_MS', 15000);
|
||||
|
||||
return {
|
||||
endpoint: this.getString('WORDPRESS_API_URL'),
|
||||
username: this.maskSecret(this.configService.get('WORDPRESS_USERNAME')),
|
||||
baseUrl: this.getString('WORDPRESS_BASE_URL'),
|
||||
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 {
|
||||
return {
|
||||
reverseWsUrl: this.getString('QQBOT_REVERSE_WS_URL'),
|
||||
napcatDataRoot: this.getString('NAPCAT_DATA_ROOT'),
|
||||
napcatSshHost: this.getString('NAPCAT_SSH_HOST'),
|
||||
napcatSshPort: this.getPositiveNumber('NAPCAT_SSH_PORT', 22),
|
||||
napcatSshUser: this.getString('NAPCAT_SSH_USER'),
|
||||
reverseWsPath: this.getString(
|
||||
'QQBOT_REVERSE_WS_PATH',
|
||||
'/qqbot/onebot/reverse',
|
||||
),
|
||||
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[] {
|
||||
return [
|
||||
...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 = '') {
|
||||
const value = this.toolsService.toTrimmedString(this.configService.get(key));
|
||||
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) {
|
||||
return this.toolsService.toPositiveNumber(
|
||||
this.configService.get<string | number>(key),
|
||||
|
||||
@ -24,7 +24,11 @@ export interface RuntimeDatabaseConfig {
|
||||
export interface RuntimeLokiConfig {
|
||||
enabled: boolean;
|
||||
host: string;
|
||||
basicAuth: string;
|
||||
queryHost: string;
|
||||
environment: string;
|
||||
tenantId: string;
|
||||
username: string;
|
||||
passwordConfigured: boolean;
|
||||
}
|
||||
|
||||
export interface RuntimeMinioConfig {
|
||||
@ -32,19 +36,30 @@ export interface RuntimeMinioConfig {
|
||||
port: number;
|
||||
useSSL: boolean;
|
||||
accessKey: string;
|
||||
bucket: string;
|
||||
}
|
||||
|
||||
export interface RuntimeWordpressConfig {
|
||||
endpoint: string;
|
||||
username: string;
|
||||
baseUrl: string;
|
||||
hostHeader: string;
|
||||
adminUsername: string;
|
||||
passwordConfigured: boolean;
|
||||
timeoutMs: number;
|
||||
loginTimeoutMs: number;
|
||||
availabilityTtlMs: number;
|
||||
}
|
||||
|
||||
export interface RuntimeQqbotConfig {
|
||||
reverseWsUrl: string;
|
||||
napcatDataRoot: string;
|
||||
napcatSshHost: string;
|
||||
reverseWsPath: string;
|
||||
reverseWsToken: string;
|
||||
napcatRoot: string;
|
||||
napcatContainerMode: string;
|
||||
napcatSshTarget: string;
|
||||
napcatSshPort: number;
|
||||
napcatSshUser: string;
|
||||
napcatSshKeyPath: string;
|
||||
napcatReverseWsBase: string;
|
||||
napcatWebuiBaseUrl: string;
|
||||
napcatWebuiToken: string;
|
||||
}
|
||||
|
||||
export interface RuntimeSafeConfigSnapshot {
|
||||
|
||||
@ -43,7 +43,11 @@ describe('RuntimeConfigService', () => {
|
||||
DB_PASSWORD: 'password-value',
|
||||
DB_DATABASE: 'kt',
|
||||
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();
|
||||
@ -64,9 +68,104 @@ describe('RuntimeConfigService', () => {
|
||||
expect(snapshotJson).not.toContain('abcdef123456');
|
||||
expect(snapshotJson).not.toContain('password-value');
|
||||
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.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', () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user