From 2689ce3b1e1675db21ca751d77064d9938b462e0 Mon Sep 17 00:00:00 2001 From: jysperm Date: Mon, 10 Nov 2014 07:58:30 +0800 Subject: [PATCH] refactor shadowsocks --- INSTALL.md | 2 + core/pluggable.coffee | 6 +- plugin/shadowsocks/index.coffee | 26 +-- plugin/shadowsocks/service.coffee | 106 --------- plugin/shadowsocks/shadowsocks.coffee | 207 +++++++++++++++++- plugin/shadowsocks/template/config.conf | 16 -- plugin/shadowsocks/template/supervisor.conf | 4 - plugin/supervisor/supervisor.coffee | 1 - .../{tempalte => template}/program.conf | 2 +- 9 files changed, 215 insertions(+), 155 deletions(-) delete mode 100644 plugin/shadowsocks/service.coffee delete mode 100644 plugin/shadowsocks/template/config.conf delete mode 100644 plugin/shadowsocks/template/supervisor.conf rename plugin/supervisor/{tempalte => template}/program.conf (83%) diff --git a/INSTALL.md b/INSTALL.md index ea5ec88..5508f07 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -130,6 +130,8 @@ apt-get install python-pip python-m2crypto pip install shadowsocks + mkdir /etc/shadowsocks + vi /etc/default/supervisor ulimit -n 51200 diff --git a/core/pluggable.coffee b/core/pluggable.coffee index cc9ffaa..6520d2a 100644 --- a/core/pluggable.coffee +++ b/core/pluggable.coffee @@ -1,4 +1,4 @@ -{async, path, harp, jade, temp, fs, _} = app.libs +{async, path, harp, jade, tmp, fs, _, child_process} = app.libs {i18n, config, logger} = app exports.plugins = {} @@ -176,7 +176,7 @@ exports.Plugin = class Plugin template_path = "#{__dirname}/../plugin/#{@NAME}/template/#{name}" fs.readFile template_path, (err, template_file) -> - callback _.template(template_file) view_data + callback _.template(template_file.toString()) view_data @writeConfigFile: (filename, content, callback) -> tmp.file @@ -190,5 +190,5 @@ exports.Plugin = class Plugin child_process.exec "sudo cp #{filepath} #{filename}", (err) -> logger.error err if err - fs.unlink path, -> + fs.unlink filepath, -> callback() diff --git a/plugin/shadowsocks/index.coffee b/plugin/shadowsocks/index.coffee index d69a2dd..eb13b9d 100644 --- a/plugin/shadowsocks/index.coffee +++ b/plugin/shadowsocks/index.coffee @@ -1,5 +1,4 @@ {pluggable, config, utils} = app -{Financials} = app.models exports = module.exports = class ShadowSocksPlugin extends pluggable.Plugin @NAME: 'shadowsocks' @@ -26,31 +25,16 @@ if config.plugins.shadowsocks.green_style exports.registerHook 'view.panel.widgets', generator: (req, callback) -> - Financials.find - account_id: account._id - type: 'usage_billing' - 'payload.service': 'shadowsocks' - , (err, financials) -> - time_range = - traffic_24hours: 24 * 3600 * 1000 - traffic_7days: 7 * 24 * 3600 * 1000 - traffic_30days: 30 * 24 * 3600 * 1000 - - result = {} - - for name, range of time_range - logs = _.filter financials, (i) -> - return i.created_at.getTime() > Date.now() - range - - result[name] = _.reduce logs, (memo, i) -> - return memo + i.payload.traffic_mb - , 0 - + shadowsocks.accountUsage req.account, (result) -> _.extend result, transfer_remainder: account.billing.balance / config.plugins.shadowsocks.price_bucket / (1000 * 1000 * 1000 / config.plugins.shadowsocks.billing_bucket) exports.render 'widget', req, result, callback +exports.registerHook 'app.started', + action: -> + shadowsocks.initSupervisor -> + exports.registerServiceHook 'enable', filter: (req, callback) -> shadowsocks.initAccount req.account, callback diff --git a/plugin/shadowsocks/service.coffee b/plugin/shadowsocks/service.coffee deleted file mode 100644 index 34384f8..0000000 --- a/plugin/shadowsocks/service.coffee +++ /dev/null @@ -1,106 +0,0 @@ -delete: (account, callback) -> - queryIptablesInfo (iptables_info) -> - port = account.attribute.plugin.shadowsocks.port - - billing_traffic = iptables_info[port].bytes - account.attribute.plugin.shadowsocks.last_traffic_value - billing_traffic = iptables_info[port].bytes if billing_traffic < 0 - billing_traffic += account.attribute.plugin.shadowsocks.pending_traffic - - amount = billing_traffic / BILLING_BUCKET * config.plugins.shadowsocks.price_bucket - - mAccount.update {_id: account._id}, - $unset: - 'attribute.plugin.shadowsocks': true - $inc: - 'attribute.balance': -amount - , -> - async.parallel [ - (callback) -> - child_process.exec "sudo iptables -D OUTPUT #{iptables_info[port].num}", callback - - (callback) -> - child_process.exec 'sudo iptables-save | sudo tee /etc/iptables.rules', callback - - (callback) -> - child_process.exec "sudo rm /etc/supervisor/conf.d/#{account.username}.conf", callback - - (callback) -> - child_process.exec 'sudo supervisorctl update', callback - ], -> - if amount > 0 - mBalance.create account, 'service_billing', -amount, - service: 'shadowsocks' - traffic_mb: billing_traffic / BILLING_BUCKET * 100 - is_force: true - , -> - callback() - else - callback() - -restart: (account, callback) -> - config_content = _.template (fs.readFileSync path.join(__dirname, 'template/config.conf')).toString(), account.attribute.plugin.shadowsocks - - pluggable.writeConfig "/etc/shadowsocks/#{account.username}.json", config_content, -> - child_process.exec "sudo chmod +r /etc/shadowsocks/#{account.username}.json", -> - config_content = _.template (fs.readFileSync path.join(__dirname, 'template/supervisor.conf')).toString(), - account: account - - pluggable.writeConfig "/etc/supervisor/conf.d/#{account.username}.conf", config_content, -> - child_process.exec 'sudo supervisorctl update', -> - callback() - -restartAccount: (account, callback) -> - child_process.exec "sudo supervisorctl restart shadowsocks-#{account.username}", -> - callback() - -monitoring: -> - queryIptablesInfo (iptables_info) -> - async.map _.values(iptables_info), (item, callback) -> - {port, bytes} = item - - mAccount.findOne - 'attribute.plugin.shadowsocks.port': port - , (err, account) -> - unless account - return callback() - - {pending_traffic, last_traffic_value} = account.attribute.plugin.shadowsocks - - new_traffic = bytes - last_traffic_value - - if new_traffic < 0 - new_traffic = bytes - - new_pending_traffic = pending_traffic + new_traffic - - billing_bucket = Math.floor pending_traffic / BILLING_BUCKET - - new_pending_traffic -= billing_bucket * BILLING_BUCKET - - if billing_bucket > 0 - amount = billing_bucket * config.plugins.shadowsocks.price_bucket - - mAccount.update {_id: account._id}, - $set: - 'attribute.plugin.shadowsocks.pending_traffic': new_pending_traffic - 'attribute.plugin.shadowsocks.last_traffic_value': bytes - $inc: - 'attribute.balance': -amount - , (err) -> - mBalance.create account, 'service_billing', -amount, - service: 'shadowsocks' - traffic_mb: billing_bucket * 100 - is_force: false - , -> - callback() - else if pending_traffic != new_pending_traffic or last_traffic_value != bytes - mAccount.update {_id: account._id}, - $set: - 'attribute.plugin.shadowsocks.pending_traffic': new_pending_traffic - 'attribute.plugin.shadowsocks.last_traffic_value': bytes - , (err) -> - callback() - else - callback() - - , -> diff --git a/plugin/shadowsocks/shadowsocks.coffee b/plugin/shadowsocks/shadowsocks.coffee index dd7577b..09a7a97 100644 --- a/plugin/shadowsocks/shadowsocks.coffee +++ b/plugin/shadowsocks/shadowsocks.coffee @@ -1,10 +1,37 @@ -{_, child_process} = app.libs +{_, child_process, async, fs} = app.libs {logger, utils} = app -{Account} = app.models +{Account, Financials} = app.models + +supervisor = require '../supervisor/supervisor' BILLING_BUCKET = config.plugins.shadowsocks.billing_bucket -exports.generateConfigure = (users, options) -> +exports.initSupervisor = (callback) -> + supervisor.programsStatus (program_status) -> + async.each config.plugins.shadowsocks.available_ciphers, (method, callback) -> + program_name = "shadowsocks-#{method}" + + if program_name in _.keys program_status + return callback() + + shadowsocks_config_file = "/etc/shadowsocks/#{method}.json" + configure = exports.generateConfigure [], + method: method + + fs.writeFile shadowsocks_config_file, configure, -> + supervisor.writeConfig {username: 'nobody'}, + command: "ssserver -c #{shadowsocks_config_file}" + name: program_name + autostart: true + autorestart: true + , -> + supervisor.updateProgram {}, {program_name: program_name}, -> + callback() + + , -> + callback() + +exports.generateConfigure = (users, options = {}) -> configure = server: '0.0.0.0' local_port: 1080 @@ -82,7 +109,181 @@ exports.initAccount = (account, callback) -> callback() exports.deleteAccount = (account, callback) -> + queryIptablesInfo (iptables_info) -> + {port, method} = account.pluggable.shadowsocks + + billing_traffic = iptables_info[port].bytes - account.pluggable.shadowsocks.last_traffic_value + billing_traffic = iptables_info[port].bytes if billing_traffic < 0 + billing_traffic += account.pluggable.shadowsocks.pending_traffic + + amount = billing_traffic / BILLING_BUCKET * config.plugins.shadowsocks.price_bucket + + account.update + $unset: + 'pluggable.shadowsocks': true + $inc: + 'billing.balance': -amount + , -> + async.series [ + (callback) -> + child_process.exec "sudo iptables -D OUTPUT #{iptables_info[port].num}", callback + + (callback) -> + child_process.exec 'sudo iptables-save | sudo tee /etc/iptables.rules', callback + + (callback) -> + exports.deleteAccountConfigure account, callback + + (callback) -> + supervisor.updateProgram {}, {program_name: "shadowsocks-#{method}"}, callback + + ], -> + if amount > 0 + Financials.create + account_id: account._id + type: 'usage_billing' + amount: -amount + payload: + service: 'shadowsocks' + traffic_mb: billing_traffic / (1000 * 1000) + , -> + callback() + else + callback() + +exports.accountUsage = (account, callback) -> + Financials.find + account_id: account._id + type: 'usage_billing' + 'payload.service': 'shadowsocks' + , (err, financials) -> + time_range = + traffic_24hours: 24 * 3600 * 1000 + traffic_7days: 7 * 24 * 3600 * 1000 + traffic_30days: 30 * 24 * 3600 * 1000 + + result = {} + + for name, range of time_range + logs = _.filter financials, (i) -> + return i.created_at.getTime() > Date.now() - range + + result[name] = _.reduce logs, (memo, i) -> + return memo + i.payload.traffic_mb + , 0 + + callback result exports.updateConfigure = (account, callback) -> + {port, method, password} = account.pluggable.shadowsocks + original_method = null + + async.eachSeries config.plugins.shadowsocks.available_ciphers, (method, callback) -> + shadowsocks_config_file = "/etc/shadowsocks/#{method}.json" + + fs.readFile shadowsocks_config_file, (err, content) -> + if port.toString() in _.keys JSON.parse(content).port_password + original_method = method + callback true + else + callback() + + , -> + async.series [ + (callback) -> + shadowsocks_config_file = "/etc/shadowsocks/#{method}.json" + + fs.readFile shadowsocks_config_file, (err, content) -> + config = JSON.parse content + config.port_password[port] = password + + fs.writeFile shadowsocks_config_file, JSON.stringify(config), -> + callback() + + (callback) -> + supervisor.updateProgram {}, {program_name: "shadowsocks-#{method}"}, -> + callback() + + (callback) -> + if original_method == original_method + return callback() + + account = account.toObject() + account.pluggable.shadowsocks.method = original_method + + exports.deleteAccountConfigure account, -> + supervisor.updateProgram {}, {program_name: "shadowsocks-#{original_method}"}, -> + callback() + + ], -> + callback() + +exports.deleteAccountConfigure = (account, callback) -> + {port, method} = account.pluggable.shadowsocks + shadowsocks_config_file = "/etc/shadowsocks/#{method}.json" + + fs.readFile shadowsocks_config_file, (err, content) -> + config = JSON.parse content + delete config.port_password[port] + + fs.writeFile shadowsocks_config_file, JSON.stringify(config), -> + callback() exports.monitoring = -> + queryIptablesInfo (iptables_info) -> + async.each _.values(iptables_info), (item, callback) -> + {port, bytes} = item + + Account.findOne + 'pluggable.shadowsocks.port': port + , (err, account) -> + unless account + return callback() + + {pending_traffic, last_traffic_value} = account.pluggable.shadowsocks + + new_traffic = bytes - last_traffic_value + + if new_traffic < 0 + new_traffic = bytes + + new_pending_traffic = pending_traffic + new_traffic + + billing_bucket = Math.floor pending_traffic / BILLING_BUCKET + + new_pending_traffic -= billing_bucket * BILLING_BUCKET + + if billing_bucket > 0 + amount = billing_bucket * config.plugins.shadowsocks.price_bucket + + account.update + $set: + 'pluggable.shadowsocks.pending_traffic': new_pending_traffic + 'pluggable.shadowsocks.last_traffic_value': bytes + $inc: + 'billing.balance': -amount + , (err) -> + logger.error err if err + + Financials.create + account_id: account._id + type: 'usage_billing' + amount: -amount + payload: + service: 'shadowsocks' + traffic_mb: (billing_bucket * BILLING_BUCKET) / 1000 * 1000 + , -> + callback() + + else if pending_traffic != new_pending_traffic or last_traffic_value != bytes + account.update + $set: + 'pluggable.shadowsocks.pending_traffic': new_pending_traffic + 'pluggable.shadowsocks.last_traffic_value': bytes + , (err) -> + callback() + + else + callback() + + , -> diff --git a/plugin/shadowsocks/template/config.conf b/plugin/shadowsocks/template/config.conf deleted file mode 100644 index a1d849a..0000000 --- a/plugin/shadowsocks/template/config.conf +++ /dev/null @@ -1,16 +0,0 @@ -{ - "server": "0.0.0.0", - "local_port": 1080, - "port_password": { -<% users.forEach(function(user) { %> - "<%= user.port %>": "<%= %>" -<% }); %> - "8381": "foobar1", - "8382": "foobar2", - "8383": "foobar3", - "8384": "foobar4" - }, - "timeout": 60, - "method": "aes-256-cfb", - "workers": 2 -} diff --git a/plugin/shadowsocks/template/supervisor.conf b/plugin/shadowsocks/template/supervisor.conf deleted file mode 100644 index a150398..0000000 --- a/plugin/shadowsocks/template/supervisor.conf +++ /dev/null @@ -1,4 +0,0 @@ -[program:shadowsocks-<%= account.username %>] -command=ssserver -c /etc/shadowsocks/<%= account.username %>.json -autorestart=true -user=nobody diff --git a/plugin/supervisor/supervisor.coffee b/plugin/supervisor/supervisor.coffee index 04923ef..4db8179 100644 --- a/plugin/supervisor/supervisor.coffee +++ b/plugin/supervisor/supervisor.coffee @@ -32,7 +32,6 @@ exports.updateProgram = (account, program, callback) -> exports.writeConfig = (account, program, callback) -> SupervisorPlugin.renderTemplate 'program.conf', - name_prefix: '@' account: account program: program , (configure) -> diff --git a/plugin/supervisor/tempalte/program.conf b/plugin/supervisor/template/program.conf similarity index 83% rename from plugin/supervisor/tempalte/program.conf rename to plugin/supervisor/template/program.conf index a970518..c1b2223 100644 --- a/plugin/supervisor/tempalte/program.conf +++ b/plugin/supervisor/template/program.conf @@ -1,4 +1,4 @@ -[program:<%= name_prefix %><%= account.username %>-<%= program.name %>] +[program:<%= program.program_name %>] command = <%= program.command %> process_name = <%= program.name %> autostart = <%= program.autostart.toString() %>