Skip to content

Commit

Permalink
refactor: 配置文件改为 JSON5
Browse files Browse the repository at this point in the history
  • Loading branch information
SALTWOOD committed Jan 11, 2025
1 parent f30f2cb commit bf1f3af
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 96 deletions.
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"express": "^4.21.2",
"express-async-errors": "^3.1.1",
"got": "^14.4.2",
"json5": "^2.2.3",
"jsonwebtoken": "^9.0.2",
"rimraf": "^6.0.1",
"socket.io": "^4.7.5",
Expand Down
191 changes: 141 additions & 50 deletions src/Config.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,157 @@
import dotenv from 'dotenv'
import env from 'env-var'
import { defaultInstance } from './RateLimiter.js'
import json5 from 'json5';
import { defaultInstance } from './RateLimiter.js';
import fs from 'fs';

export const version = "3.2.1-pre3";

export class Config {
public static instance: Config

public readonly enableRequestCertificate: boolean = env.get('ENABLE_REQUEST_CERTIFICATE').default("false").asBool();
public readonly dnsType: string = env.get('DNS_TYPE').default("cloudflare").asString();
public readonly dnsSecretId: string = env.get('DNS_SECRET_ID').default("").asString();
public readonly dnsSecretToken: string = env.get('DNS_SECRET_TOKEN').default("").asString();
public readonly dnsDomain: string = env.get('DNS_DOMAIN').default("").asString();
public readonly domainContactEmail: string = env.get('DOMAIN_CONTACT_EMAIL').default("").asString();
// public readonly acmeStaging: boolean = env.get('ACME_STAGING').default("false").asBool();

public readonly zerosslKid: string = env.get('ZEROSSL_KID').default("").asString();
public readonly zerosslHmacKey: string = env.get('ZEROSSL_HMAC_KEY').default("").asString();

public readonly githubOAuthClientId: string = env.get('GITHUB_OAUTH_CLIENT_ID').default("").asString();
public readonly githubOAuthClientSecret: string = env.get('GITHUB_OAUTH_CLIENT_SECRET').default("").asString();
public readonly githubOAuthCallbackUrl: string = env.get('GITHUB_OAUTH_CALLBACK_URL').default("").asString();
public readonly githubUrl: string = env.get('GITHUB_URL').default("github.com").asString();
public readonly githubApiUrl: string = env.get('GITHUB_API_URL').default("api.github.com").asString();
public readonly host: string = env.get('HOST').default("127.0.0.1").asString();
public readonly port: number = env.get('PORT').default(9388).asPortNumber();
public readonly concurrency: number = env.get('CONCURRENCY').default(10).asIntPositive();
public readonly forceNoOpen: boolean = env.get('FORCE_NO_OPEN').default("false").asBool();
public readonly noWarden: boolean = env.get('NO_WARDEN').default("false").asBool();
public readonly forceHttps: boolean = env.get('FORCE_HTTPS').default("false").asBool() || this.enableRequestCertificate;
public readonly failAttemptsToBan: number = env.get('FAIL_ATTEMPTS_TO_BAN').default(0).asIntPositive();
public readonly failAttemptsDuration: number = env.get('FAIL_ATTEMPTS_DURATION').default(0).asIntPositive();
public readonly requestRateLimit: number = env.get('REQUEST_RATE_LIMIT').default(0).asIntPositive();
public readonly autoUpdateDuration: number = env.get('AUTO_UPDATE_DURATION').default(0).asIntPositive();
public readonly lastActivityDays: number = env.get('LAST_ACTIVITY_DAYS').default(15).asIntPositive();

// 开发变量
public readonly sourceIpHeader: string = env.get('SOURCE_IP_HEADER').default("x-real-ip").asString();
public readonly debug: boolean = env.get('DEBUG').default("false").asBool();
public readonly disableAccessLog: boolean = env.get('DISABLE_ACCESS_LOG').default("false").asBool();
private static _instance: Config;
public static readonly FILENAME = 'config.json5';
private static _fsWatcher: fs.FSWatcher;

public readonly dns: {
type: string;
secretId: string;
secretToken: string;
domain: string;
contactEmail: string;
} = {
type: "cloudflare",
secretId: "",
secretToken: "",
domain: "",
contactEmail: ""
};

public readonly github: {
oAuthClientId: string;
oAuthClientSecret: string;
oAuthCallbackUrl: string;
url: string;
apiUrl: string;
} = {
oAuthClientId: "",
oAuthClientSecret: "",
oAuthCallbackUrl: "",
url: "github.com",
apiUrl: "api.github.com"
};

public readonly server: {
host: string;
port: number;
concurrency: number;
forceNoOpen: boolean;
noWarden: boolean;
forceHttps: boolean;
requestCert: boolean;
} = {
host: "127.0.0.1",
port: 9388,
concurrency: 10,
forceNoOpen: false,
noWarden: false,
forceHttps: false,
requestCert: false
};

public readonly security: {
failAttemptsToBan: number;
failAttemptsDuration: number;
requestRateLimit: number;
} = {
failAttemptsToBan: 0,
failAttemptsDuration: 0,
requestRateLimit: 0
};

public readonly update: {
checkInterval: number;
shownDays: number;
} = {
checkInterval: 0,
shownDays: 15
};

public readonly dev: {
sourceIpHeader: string;
debug: boolean;
disableAccessLog: boolean;
} = {
sourceIpHeader: "x-real-ip",
debug: false,
disableAccessLog: false
};

public readonly zerossl: {
kid: string;
hmacKey: string;
} = {
kid: "",
hmacKey: ""
};

public static readonly version: string = version;

private constructor() { }
private constructor() {
this.loadConfig();
}

public static getInstance(): Config {
if (!Config.instance) {
Config.init();
private loadConfig(): void {
if (fs.existsSync(Config.FILENAME)) {
const data = fs.readFileSync(Config.FILENAME, 'utf-8');
const configData = json5.parse(data);

Object.keys(configData).forEach((key) => {
if (key in this) {
this.validateAndAssign(key as keyof Config, configData[key]);
}
});
}
return Config.instance;
defaultInstance.RATE_LIMIT = this.security.requestRateLimit;
}

public get instance(): Config {
return Config.instance;
private validateAndAssign(field: keyof Config, value: any): void {
const fieldType = typeof this[field];
if (fieldType === 'string' && typeof value !== 'string') {
throw new Error(`Invalid type for field "${field}". Expected string but got ${typeof value}.`);
}
if (fieldType === 'number' && typeof value !== 'number') {
throw new Error(`Invalid type for field "${field}". Expected number but got ${typeof value}.`);
}
if (fieldType === 'boolean' && typeof value !== 'boolean') {
throw new Error(`Invalid type for field "${field}". Expected boolean but got ${typeof value}.`);
}
if (fieldType === 'object' && typeof value === 'object' && value !== null) {
const expectedObjectType = this[field] as unknown as object;
if (Array.isArray(value)) {
throw new Error(`Invalid type for field "${field}". Expected object but got array.`);
}
Object.keys(expectedObjectType).forEach(subKey => {
if (!(subKey in value)) {
(value as any)[subKey] = (expectedObjectType as any)[subKey];
}
});
}
(this as any)[field] = value;
}

public static init() {
if (!Config.instance) {
Config.instance = new Config();
defaultInstance.RATE_LIMIT = Config.instance.requestRateLimit;
public static getInstance(): Config {
if (!Config._instance) {
Config._instance = new Config();
Config._fsWatcher = fs.watch(Config.FILENAME, () => {
console.log('[Config] Config file changed. Reloading...');
Config._instance.loadConfig();
});
}
return Config._instance;
}

public static get instance(): Config {
return Config.getInstance();
}
}

dotenv.config()
public static get fsWatcher(): fs.FSWatcher {
return Config._fsWatcher;
}
}
2 changes: 1 addition & 1 deletion src/RateLimiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class RateLimiter {
next(); // 速率限制功能关闭,直接处理请求
return;
}
const ip = (req.headers[Config.instance.sourceIpHeader] as string).split(',')[0] || req.ip; // 根据请求的IP地址进行限速
const ip = (req.headers[Config.instance.dev.sourceIpHeader] as string).split(',')[0] || req.ip; // 根据请求的IP地址进行限速
if (!ip) throw new Error('No IP address provided.');
const currentTime = Utilities.getTimestamp();

Expand Down
Loading

0 comments on commit bf1f3af

Please sign in to comment.