UNPKG

shadowsocks-manager

Version:

A shadowsocks manager tool for multi user and traffic control.

656 lines (619 loc) 23 kB
const log4js = require('log4js'); const logger = log4js.getLogger('account'); const cluster = require('cluster'); const process = require('process'); const redis = appRequire('init/redis').redis; const knex = appRequire('init/knex').knex; const cron = appRequire('init/cron'); const flow = appRequire('plugins/flowSaver/flow'); const manager = appRequire('services/manager'); const config = appRequire('services/config').all(); const webguiTag = appRequire('plugins/webgui_tag'); const { createHash } = require('crypto'); let acConfig = {}; if(config.plugins.account_checker && config.plugins.account_checker.use) { acConfig = config.plugins.account_checker; } const speed = acConfig.speed || 5; const sleepTime = 100; const accountFlow = appRequire('plugins/account/accountFlow'); const accountPlugin = appRequire('plugins/account'); const isTelegram = config.plugins.webgui_telegram && config.plugins.webgui_telegram.use; let telegram; if(isTelegram) { telegram = appRequire('plugins/webgui_telegram/admin'); } const sleep = time => new Promise(resolve => setTimeout(resolve, time)); const randomInt = max => { return Math.ceil(Math.random() * max % max); }; const modifyAccountFlow = async (serverId, accountId, nextCheckTime) => { await knex('account_flow').update({ checkTime: Date.now(), nextCheckTime, }).where({ serverId, accountId }); }; const portList = {}; const updatePorts = async server => { if(!portList[server.id] || Date.now() - portList[server.id].update >= 35 * 1000) { const ports = (await manager.send({ command: 'list' }, { host: server.host, port: server.port, password: server.password, })).map(m => m.port); portList[server.id] = { ports, update: Date.now(), }; } return portList[server.id].ports; }; const isPortExists = async (server, account) => { // const ports = (await manager.send({ command: 'list' }, { // host: server.host, // port: server.port, // password: server.password, // })).map(m => m.port); const ports = await updatePorts(server); if(ports.includes(server.shift + account.port)) { return true; } else { return false; } }; const isAccountActive = (server, account) => { return !!account.active; }; const hasServer = (server, account) => { if(!account.server) { return true; } const serverList = JSON.parse(account.server); if(serverList.indexOf(server.id) >= 0) { return true; } modifyAccountFlow(server.id, account.id, Date.now() + randomInt(7 * 24 * 3600 * 1000)); return false; }; const isExpired = (server, account) => { if(account.type >= 2 && account.type <= 5) { let timePeriod = 0; if(account.type === 2) { timePeriod = 7 * 86400 * 1000; } if(account.type === 3) { timePeriod = 30 * 86400 * 1000; } if(account.type === 4) { timePeriod = 1 * 86400 * 1000; } if(account.type === 5) { timePeriod = 3600 * 1000; } const data = JSON.parse(account.data); const expireTime = data.create + data.limit * timePeriod; account.expireTime = expireTime; let nextCheckTime = data.create; while(nextCheckTime <= Date.now()) { nextCheckTime += timePeriod; } if(expireTime <= Date.now() || data.create >= Date.now()) { if(account.active && account.autoRemove && expireTime + account.autoRemoveDelay < Date.now()) { // knex('account_plugin').delete().where({ id: account.id }).then(); accountPlugin.delAccount(account.id); } else if(account.active && account.autoRemove && expireTime + account.autoRemoveDelay >= Date.now()) { modifyAccountFlow(server.id, account.id, expireTime + account.autoRemoveDelay); } else if(account.active && !account.autoRemove) { modifyAccountFlow(server.id, account.id, Date.now() + randomInt(7 * 24 * 3600 * 1000)); } return true; } else { modifyAccountFlow(server.id, account.id, nextCheckTime); return false; } } else { modifyAccountFlow(server.id, account.id, Date.now() + 24 * 3600 * 1000); return false; } }; const isBaned = async (server, account) => { const accountFlowInfo = await knex('account_flow').where({ serverId: server.id, accountId: account.id, status: 'ban', }).then(s => s[0]); if(!accountFlowInfo) { return false; } if(!accountFlowInfo.autobanTime || Date.now() > accountFlowInfo.autobanTime) { await knex('account_flow').update({ status: 'checked' }).where({ id: accountFlowInfo.id }); return false; } await knex('account_flow').update({ nextCheckTime: accountFlowInfo.autobanTime }).where({ id: accountFlowInfo.id }); return true; }; const isOverFlow = async (server, account) => { let realFlow = 0; const writeFlow = async (serverId, accountId, flow) => { const exists = await knex('account_flow').where({ serverId, accountId }).then(s => s[0]); if(exists) { await knex('account_flow').update({ flow, checkTime: Date.now(), checkFlowTime: Date.now(), }).where({ id: exists.id }); } }; const writeFlowForOtherServer = async (serverId, accountId) => { await knex('account_flow').update({ checkFlowTime: Date.now(), }).where({ accountId }) .whereNotIn('serverId', [ serverId ]) .where('flow', '>', 0); }; const checkFlowForOtherServer = async (serverId, accountId) => { await knex('account_flow').update({ nextCheckTime: Date.now(), }).where({ accountId }).whereNotIn('serverId', [ serverId ]); }; if((account.type >= 2 && account.type <= 5) || (account.type === 1 && account.data)) { let timePeriod = 0; if(account.type === 2) { timePeriod = 7 * 86400 * 1000; } if(account.type === 3) { timePeriod = 30 * 86400 * 1000; } if(account.type === 4) { timePeriod = 1 * 86400 * 1000; } if(account.type === 5) { timePeriod = 3600 * 1000; } const data = JSON.parse(account.data); if(data.flow <= 0) { return false; } let startTime = data.create; if (account.type === 1) { startTime = Date.now() - 24 * 60 * 60 * 1000 * 365 * 3; } else { while(startTime + timePeriod <= Date.now()) { startTime += timePeriod; } } const endTime = Date.now(); const isMultiServerFlow = !!account.multiServerFlow; let servers = []; if(isMultiServerFlow) { servers = await knex('server').where({}); } else { servers = await knex('server').where({ id: server.id }); } const flows = await flow.getFlowFromSplitTimeWithScale(servers.map(m => m.id), account.id, startTime, endTime); const serverObj = {}; servers.forEach(server => { serverObj[server.id] = server; }); flows.forEach(flo => { flo.forEach(f => { if(serverObj[f.id]) { if(!serverObj[f.id].flow) { serverObj[f.id].flow = f.sumFlow; } else { serverObj[f.id].flow += f.sumFlow; } } }); }); let sumFlow = 0; for(const s in serverObj) { const flow = serverObj[s].flow || 0; if(+s === server.id) { realFlow = flow; } sumFlow += Math.ceil(flow * serverObj[s].scale); } const flowPacks = await knex('webgui_flow_pack').where({ accountId: account.id }).whereBetween('createTime', [startTime, endTime]); const flowWithFlowPacks = flowPacks.reduce((a, b) => { return { flow: a.flow + b.flow }; }, { flow: data.flow }).flow; await writeFlow(server.id, account.id, realFlow); if(account.multiServerFlow && sumFlow < flowWithFlowPacks) { await writeFlowForOtherServer(server.id, account.id); } if(account.multiServerFlow && sumFlow >= flowWithFlowPacks) { await checkFlowForOtherServer(server.id, account.id); } return sumFlow >= flowWithFlowPacks; } else { await writeFlow(server.id, account.id, 0); return false; } }; const deletePort = async (server, account) => { // console.log(`del ${ server.name } ${ account.port }`); const portNumber = server.shift + account.port; await manager.send({ command: 'del', port: portNumber, }, { host: server.host, port: server.port, password: server.password, }).catch(); }; const runCommand = async cmd => { const exec = require('child_process').exec; return new Promise((resolve, reject) => { exec(cmd, (err, stdout, stderr) => { if(err) { return reject(stderr); } else { return resolve(stdout); } }); }); }; const generateAccountKey = async account => { const privateKey = await runCommand('wg genkey'); const publicKey = await runCommand(`echo '${ privateKey.trim() }' | wg pubkey`); await knex('account_plugin').update({ key: publicKey.trim() + ':' + privateKey.trim(), }).where({ id: account.id }); return publicKey.trim(); }; const addPort = async (server, account) => { // console.log(`add ${ server.name } ${ account.port }`); if(server.type === 'WireGuard') { let publicKey = account.key; if(!publicKey) { publicKey = await generateAccountKey(account); } if(publicKey.includes(':')) { publicKey = publicKey.split(':')[0]; } const portNumber = server.shift + account.port; await manager.send({ command: 'add', port: portNumber, password: publicKey, }, { host: server.host, port: server.port, password: server.password, }).catch(); } else if(server.type === 'Trojan') { const portNumber = account.port; const pwd = createHash('sha224') .update(`${portNumber}:${account.password}`, 'utf8') .digest('hex'); await manager.send({ command: 'add', port: portNumber, password: pwd, }, { host: server.host, port: server.port, password: server.password, }).catch(); } else if(server.type === 'Shadowsocks') { const portNumber = server.shift + account.port; await manager.send({ command: 'add', port: portNumber, password: account.password, }, { host: server.host, port: server.port, password: server.password, }).catch(); } }; const deleteExtraPorts = async serverInfo => { try { const currentPorts = await manager.send({ command: 'list' }, { host: serverInfo.host, port: serverInfo.port, password: serverInfo.password, }); const accounts = await knex('account_plugin').where({}); const accountObj = {}; accounts.forEach(account => { accountObj[account.port] = account; }); for(const p of currentPorts) { if(accountObj[p.port - serverInfo.shift]) { continue; } await sleep(sleepTime); await deletePort(serverInfo, { port: p.port - serverInfo.shift }); } } catch(err) { logger.error(err); } }; const checkAccount = async (serverId, accountId) => { try { const serverInfo = await knex('server').where({ id: serverId }).then(s => s[0]); if(!serverInfo) { await knex('account_flow').delete().where({ serverId }); return; } const accountInfo = await knex('account_plugin').where({ id: accountId }).then(s => s[0]); if(!accountInfo) { await knex('account_flow').delete().where({ serverId, accountId }); return; } const tags = await webguiTag.getTags('server', serverId); if(tags.includes('#_pause') || tags.includes('#pause')) { return; } // 检查当前端口是否存在 const exists = await isPortExists(serverInfo, accountInfo); // 检查账号是否激活 if(!isAccountActive(serverInfo, accountInfo)) { exists && await deletePort(serverInfo, accountInfo); return; } // 检查账号是否包含该服务器 if(!hasServer(serverInfo, accountInfo)) { // await modifyAccountFlow(serverInfo.id, accountInfo.id, 20 * 60 * 1000 + randomInt(30000)); exists && await deletePort(serverInfo, accountInfo); return; } // 检查账号是否过期 if(isExpired(serverInfo, accountInfo)) { exists && await deletePort(serverInfo, accountInfo); return; } // 检查账号是否被ban if(await isBaned(serverInfo, accountInfo)) { exists && await deletePort(serverInfo, accountInfo); return; } // 检查账号是否超流量 if(await isOverFlow(serverInfo, accountInfo)) { exists && await deletePort(serverInfo, accountInfo); return; } !exists && await addPort(serverInfo, accountInfo); } catch(err) { logger.error(err); } }; let time = 120; cron.loop( async() => { const start = Date.now(); try { await sleep(sleepTime); const servers = await knex('server').where({}); const totalAccounts = await knex('account_plugin').select([ 'id' ]); for(const server of servers) { await sleep(1000); await deleteExtraPorts(server); } await sleep(sleepTime); if(servers.length * totalAccounts.length > 1000) { const ids = await knex('account_flow') .select(['id']) .orderBy('id', 'ASC') .limit(Math.ceil(servers.length * totalAccounts.length / 1000)); await knex('account_flow').delete() .whereIn('id', ids.map(m => m.id)); } const accounts = await knex('account_plugin').select([ 'account_plugin.id as id', ]) .count('account_flow.serverId as count') .leftOuterJoin('account_flow', 'account_flow.accountId', 'account_plugin.id') .groupBy('account_plugin.id') .having('count', '<', servers.length); // const accounts = await knex('account_plugin').select([ // 'account_plugin.id as id', // ]).crossJoin('server') // .leftJoin('account_flow', function () { // this // .on('account_flow.serverId', 'server.id') // .on('account_flow.accountId', 'account_plugin.id'); // }).whereNull('account_flow.id'); for(const account of accounts) { await sleep(sleepTime); await accountFlow.add(account.id); } const end = Date.now(); if(end - start <= time * 1000) { await sleep(time * 1000 - (end - start)); } if(time <= 600) { time += 10; } } catch(err) { logger.error(err); const end = Date.now(); if(end - start <= time * 1000) { await sleep(time * 1000 - (end - start)); } } }, 'AccountCheckerDeleteExtraPorts', 360, ); cron.minute(async () => { await knex('account_flow').delete() .where('nextCheckTime', '<', Date.now() - 3 * 60 * 60 * 1000) .orderBy('nextCheckTime', 'asc'); }, 'DeleteInvalidAccountFlow', 30); cron.minute(async () => { const servers = await knex('server').select(); for(const server of servers) { const tags = await webguiTag.getTags('server', server.id); const checkServerStatus = async (server, retry = 0) => { try { const result = await manager.send({ command: 'version' }, { host: server.host, port: server.port + server.shift, password: server.password, }); if(result.isGfw && !tags.includes('#_hide') && tags.includes('#autohide')) { await webguiTag.addTags('server', server.id, ['#_hide']); isTelegram && telegram.push(`服务器[${ server.id }][${ server.name }]已离线`); } else if (tags.includes('#_hide')) { await webguiTag.delTags('server', server.id, ['#_hide']); isTelegram && telegram.push(`服务器[${ server.id }][${ server.name }]已重新上线`); } if(result.isGfw && !tags.includes('#_pause') && tags.includes('#autopause')) { await webguiTag.addTags('server', server.id, ['#_pause']); } else if (tags.includes('#_pause')) { await webguiTag.delTags('server', server.id, ['#_pause']); } } catch(err) { if(retry < 3) { logger.error('server', server.id, server.name, 'retry', retry, err); await sleep(10 * 1000); await checkServerStatus(server, retry + 1); return; } if(!tags.includes('#_hide') && tags.includes('#autohide')) { await webguiTag.addTags('server', server.id, ['#_hide']); isTelegram && telegram.push(`服务器[${ server.id }][${ server.name }]已离线`); } if(!tags.includes('#_pause') && tags.includes('#autopause')) { await webguiTag.addTags('server', server.id, ['#_pause']); } } }; checkServerStatus(server); } }, 'CheckServerStatus', 3); (async () => { const serverNumber = await knex('server').select(['id']).then(s => s.length); const accountNumber = await knex('account_plugin').select(['id']).then(s => s.length); if(serverNumber * accountNumber > 300) { while(true) { try { const accountLeft = await redis.lpop('CheckAccount:Queue'); const accountRight = await redis.rpop('CheckAccount:Queue'); const queueLength = await redis.llen('CheckAccount:Queue'); if(!accountLeft || queueLength < 10) { const mark = await redis.setnx('CheckAccount:Mark', 1); if(mark) { redis.expire('CheckAccount:Mark', 5); let accounts = []; try { const datas = await knex('account_flow').select() .where('nextCheckTime', '<', Date.now()) .orderBy('nextCheckTime', 'desc') // .limit(200) .offset(0); accounts = [...accounts, ...datas]; } catch(err) { logger.error(err); } try { const datas = await knex('account_flow').select() .where('updateTime', '>', Date.now() - 8 * 60 * 1000) .where('checkFlowTime', '<', Date.now() - 10 * 60 * 1000) .whereNotIn('id', accounts.map(account => account.id)) .orderBy('updateTime', 'desc') .limit(50) .offset(0); accounts = [...accounts, ...datas]; } catch(err) { logger.error(err); } try { datas = await knex('account_flow').select() .whereNotIn('id', accounts.map(account => account.id)) .orderByRaw('rand()').limit(accounts.length < 30 ? 35 - accounts.length : 5); accounts = [...accounts, ...datas]; } catch(err) { } try { datas = await knex('account_flow').select() .whereNotIn('id', accounts.map(account => account.id)) .orderByRaw('random()').limit(accounts.length < 30 ? 35 - accounts.length : 5); accounts = [...accounts, ...datas]; } catch(err) { } if(accounts.length) { logger.info(`Add [${ accounts.length }] elements to queue`); await redis.lpush('CheckAccount:Queue', accounts.map(m => `${ m.serverId }:${ m.accountId }`)); } redis.del('CheckAccount:Mark'); await sleep(5000); }; } if(accountLeft) { const serverId = +accountLeft.split(':')[0]; const accountId = +accountLeft.split(':')[1]; const start = Date.now(); await checkAccount(serverId, accountId).catch(err => {}); if(Date.now() - start < (1000 / speed)) { await sleep(1000 / speed - (Date.now() - start)); } } if(accountRight) { const serverId = +accountRight.split(':')[0]; const accountId = +accountRight.split(':')[1]; const start = Date.now(); await checkAccount(serverId, accountId).catch(err => {}); if(Date.now() - start < (1000 / speed)) { await sleep(1000 / speed - (Date.now() - start)); } } } catch(err) { logger.error(err); await sleep(5000); } } } else { while(true) { await sleep(randomInt(2000)); const start = Date.now(); let accounts = []; const redis = appRequire('init/redis').redis; const keys = await redis.keys('CheckAccount:*'); const ids = keys.length === 0 ? [] : (await redis.mget(keys)).map(m => JSON.parse(m)).reduce((a, b) => { return b ? [...a, ...b] : a; }, []); try { const datas = await knex('account_flow').select() .where('nextCheckTime', '<', Date.now()) .whereNotIn('id', ids) .orderBy('nextCheckTime', 'asc') .limit(400) .offset(0); accounts = [...accounts, ...datas]; } catch(err) { logger.error(err); } try { const datas = await knex('account_flow').select() .whereNotIn('id', ids) .whereNotIn('id', accounts.map(account => account.id)) .where('updateTime', '>', Date.now() - 8 * 60 * 1000) .where('checkFlowTime', '<', Date.now() - 10 * 60 * 1000) .orderBy('updateTime', 'desc') .limit(400) .offset(0); accounts = [...accounts, ...datas]; } catch(err) { logger.error(err); } try { datas = await knex('account_flow').select() .whereNotIn('id', ids) .whereNotIn('id', accounts.map(account => account.id)) .orderByRaw('rand()').limit(accounts.length < 30 ? 35 - accounts.length : 5); accounts = [...accounts, ...datas]; } catch(err) { } try { datas = await knex('account_flow').select() .whereNotIn('id', ids) .whereNotIn('id', accounts.map(account => account.id)) .orderByRaw('random()').limit(accounts.length < 30 ? 35 - accounts.length : 5); accounts = [...accounts, ...datas]; } catch(err) { } try { if(accounts.length <= 120) { for(const account of accounts) { const start = Date.now(); await checkAccount(account.serverId, account.accountId).catch(err => {}); const time = 60 * 1000 / accounts.length - (Date.now() - start); await sleep((time <= 0 || time > sleepTime) ? sleepTime : time); } } else { await Promise.all(accounts.map((account, index) => { return sleep(index * (60 + Math.ceil(accounts.length % 10)) * 1000 / accounts.length).then(() => { return checkAccount(account.serverId, account.accountId).catch(err => {}); }); })); } if(accounts.length) { await redis.set(`CheckAccount:${ process.uptime() }:${ cluster.worker.id }`, JSON.stringify(accounts.map(account => account.id)), 'EX', 45); logger.info(`check ${ accounts.length } accounts, ${ Date.now() - start } ms`); if(accounts.length < 30) { await sleep((30 - accounts.length) * 1000); } } else { logger.info('no need to check'); await sleep(30 * 1000); } } catch (err) { const end = Date.now(); if(end - start <= 60 * 1000) { await sleep(60 * 1000 - (end - start)); } } } } })();