mirror of
https://github.com/HackPlan/RootPanel.git
synced 2026-03-27 22:44:32 +08:00
refactor part of shadowsocks core
This commit is contained in:
@@ -80,6 +80,7 @@
|
||||
[program:RootPanel]
|
||||
command=node /home/rpadmin/RootPanel/start.js
|
||||
autorestart=true
|
||||
redirect_stderr = true
|
||||
user=rpadmin
|
||||
|
||||
service nginx restart
|
||||
@@ -129,14 +130,12 @@
|
||||
apt-get install python-pip python-m2crypto
|
||||
pip install shadowsocks
|
||||
|
||||
mkdir /etc/shadowsocks
|
||||
|
||||
vi /etc/default/supervisor
|
||||
|
||||
ulimit -n 51200
|
||||
|
||||
iptables -A OUTPUT -p tcp --dport 25 -j DROP
|
||||
iptables -A OUTPUT -d smtp.postmarkapp.com -j ACCEPT
|
||||
iptables -A OUTPUT -p tcp --dport 25 -d smtp.postmarkapp.com -j ACCEPT
|
||||
|
||||
### Runtime
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Financials = mongoose.Schema
|
||||
type:
|
||||
required: true
|
||||
type: String
|
||||
enum: ['deposit', 'billing']
|
||||
enum: ['deposit', 'billing', 'usage_billing']
|
||||
|
||||
amount:
|
||||
required: true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{pluggable, config} = app
|
||||
{pluggable, config, utils} = app
|
||||
{Financials} = app.models
|
||||
|
||||
exports = module.exports = class ShadowSocksPlugin extends pluggable.Plugin
|
||||
@NAME: 'shadowsocks'
|
||||
@@ -25,17 +26,39 @@ if config.plugins.shadowsocks.green_style
|
||||
|
||||
exports.registerHook 'view.panel.widgets',
|
||||
generator: (req, callback) ->
|
||||
exports.render 'widget', req,
|
||||
transfer_remainder: account.billing.balance / config.plugins.shadowsocks.price_bucket / (1000 * 1000 * 1000 / config.plugins.shadowsocks.billing_bucket)
|
||||
traffic_24hours: null
|
||||
traffic_7days: null
|
||||
traffic_30days: null
|
||||
, 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
|
||||
|
||||
_.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.registerServiceHook 'enable',
|
||||
filter: (req, callback) ->
|
||||
shadowsocks.initAccount req.account, callback
|
||||
|
||||
exports.registerServiceHook 'disable',
|
||||
filter: (req, callback) ->
|
||||
shadowsocks.deleteAccount req.account, callback
|
||||
|
||||
app.express.use '/plugin/shadowsocks', require './router'
|
||||
|
||||
setInterval shadowsocks.monitoring, config.plugins.shadowsocks.monitor_cycle
|
||||
|
||||
@@ -1,19 +1,34 @@
|
||||
{utils} = app
|
||||
{markdown, fs, path, express} = app.libs
|
||||
{renderAccount, requireInService, requireAuthenticate} = require '../../core/middleware'
|
||||
{requireInService} = app.middleware
|
||||
|
||||
shadowsocks = require './shadowsocks'
|
||||
|
||||
module.exports = exports = express.Router()
|
||||
|
||||
exports.use requireInService 'shadowsocks'
|
||||
|
||||
exports.post '/reset_password', (req, res) ->
|
||||
password = mAccount.randomString 10
|
||||
password = utils.randomString 10
|
||||
|
||||
mAccount.update _id: req.account._id,
|
||||
req.account.update
|
||||
$set:
|
||||
'attribute.plugin.shadowsocks.password': password
|
||||
'pluggable.shadowsocks.password': password
|
||||
, ->
|
||||
req.account.attribute.plugin.shadowsocks.password = password
|
||||
req.account.pluggable.shadowsocks.password = password
|
||||
|
||||
service.restart req.account, ->
|
||||
service.restartAccount req.account, ->
|
||||
res.json {}
|
||||
shadowsocks.updateConfigure req.account, ->
|
||||
res.json {}
|
||||
|
||||
exports.post '/switch_method', (req, res) ->
|
||||
unless req.body.method in config.plugins.shadowsocks.available_ciphers
|
||||
return res.error 'invalid_method'
|
||||
|
||||
req.account.update
|
||||
$set:
|
||||
'pluggable.shadowsocks.method': method
|
||||
, ->
|
||||
req.account.pluggable.shadowsocks.method = method
|
||||
|
||||
shadowsocks.updateConfigure req.account, ->
|
||||
res.json {}
|
||||
|
||||
@@ -1,203 +1,106 @@
|
||||
child_process = require 'child_process'
|
||||
path = require 'path'
|
||||
jade = require 'jade'
|
||||
fs = require 'fs'
|
||||
delete: (account, callback) ->
|
||||
queryIptablesInfo (iptables_info) ->
|
||||
port = account.attribute.plugin.shadowsocks.port
|
||||
|
||||
plugin = require '../../core/pluggable'
|
||||
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
|
||||
|
||||
mAccount = require '../../core/model/account'
|
||||
mBalance = require '../../core/model/balance_log'
|
||||
amount = billing_traffic / BILLING_BUCKET * config.plugins.shadowsocks.price_bucket
|
||||
|
||||
BILLING_BUCKET = config.plugins.shadowsocks.billing_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
|
||||
|
||||
generatePort = (callback) ->
|
||||
port = 10000 + Math.floor Math.random() * 10000
|
||||
(callback) ->
|
||||
child_process.exec 'sudo iptables-save | sudo tee /etc/iptables.rules', callback
|
||||
|
||||
mAccount.findOne
|
||||
'attribute.plugin.shadowsocks.port': port
|
||||
, (err, result) ->
|
||||
if result
|
||||
generatePort callback
|
||||
else
|
||||
callback port
|
||||
(callback) ->
|
||||
child_process.exec "sudo rm /etc/supervisor/conf.d/#{account.username}.conf", callback
|
||||
|
||||
queryIptablesInfo = (callback) ->
|
||||
child_process.exec 'sudo iptables -n -v -L -t filter -x --line-numbers', (err, stdout) ->
|
||||
lines = stdout.split '\n'
|
||||
iptables_info = {}
|
||||
(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()
|
||||
|
||||
do ->
|
||||
CHAIN_OUTPUT = 'Chain OUTPUT'
|
||||
is_chain_output = false
|
||||
restart: (account, callback) ->
|
||||
config_content = _.template (fs.readFileSync path.join(__dirname, 'template/config.conf')).toString(), account.attribute.plugin.shadowsocks
|
||||
|
||||
for item in lines
|
||||
if is_chain_output
|
||||
if item
|
||||
try
|
||||
[num, pkts, bytes, prot, opt, in_, out, source, destination, prot, port] = item.split /\s+/
|
||||
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
|
||||
|
||||
unless num == 'num'
|
||||
port = port.match(/spt:(\d+)/)[1]
|
||||
pluggable.writeConfig "/etc/supervisor/conf.d/#{account.username}.conf", config_content, ->
|
||||
child_process.exec 'sudo supervisorctl update', ->
|
||||
callback()
|
||||
|
||||
iptables_info[port.toString()] =
|
||||
num: parseInt num
|
||||
pkts: parseInt pkts
|
||||
bytes: parseInt bytes
|
||||
port: parseInt port
|
||||
catch e
|
||||
continue
|
||||
restartAccount: (account, callback) ->
|
||||
child_process.exec "sudo supervisorctl restart shadowsocks-#{account.username}", ->
|
||||
callback()
|
||||
|
||||
if item[0...CHAIN_OUTPUT.length] == CHAIN_OUTPUT
|
||||
is_chain_output = true
|
||||
monitoring: ->
|
||||
queryIptablesInfo (iptables_info) ->
|
||||
async.map _.values(iptables_info), (item, callback) ->
|
||||
{port, bytes} = item
|
||||
|
||||
callback iptables_info
|
||||
mAccount.findOne
|
||||
'attribute.plugin.shadowsocks.port': port
|
||||
, (err, account) ->
|
||||
unless account
|
||||
return callback()
|
||||
|
||||
module.exports =
|
||||
enable: (account, callback) ->
|
||||
generatePort (port) ->
|
||||
password = mAccount.randomString 10
|
||||
{pending_traffic, last_traffic_value} = account.attribute.plugin.shadowsocks
|
||||
|
||||
mAccount.findAndModify _id: account._id, {},
|
||||
$set:
|
||||
'attribute.plugin.shadowsocks':
|
||||
port: port
|
||||
password: password
|
||||
pending_traffic: 0
|
||||
last_traffic_value: 0
|
||||
, new: true, (err, account) ->
|
||||
child_process.exec "sudo iptables -I OUTPUT -p tcp --sport #{port}", ->
|
||||
child_process.exec 'sudo iptables-save | sudo tee /etc/iptables.rules', ->
|
||||
module.exports.restart account, ->
|
||||
callback()
|
||||
new_traffic = bytes - last_traffic_value
|
||||
|
||||
delete: (account, callback) ->
|
||||
queryIptablesInfo (iptables_info) ->
|
||||
port = account.attribute.plugin.shadowsocks.port
|
||||
if new_traffic < 0
|
||||
new_traffic = bytes
|
||||
|
||||
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
|
||||
new_pending_traffic = pending_traffic + new_traffic
|
||||
|
||||
amount = billing_traffic / BILLING_BUCKET * config.plugins.shadowsocks.price_bucket
|
||||
billing_bucket = Math.floor pending_traffic / BILLING_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
|
||||
new_pending_traffic -= billing_bucket * BILLING_BUCKET
|
||||
|
||||
(callback) ->
|
||||
child_process.exec 'sudo iptables-save | sudo tee /etc/iptables.rules', callback
|
||||
if billing_bucket > 0
|
||||
amount = billing_bucket * config.plugins.shadowsocks.price_bucket
|
||||
|
||||
(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
|
||||
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_traffic / BILLING_BUCKET * 100
|
||||
is_force: true
|
||||
traffic_mb: billing_bucket * 100
|
||||
is_force: false
|
||||
, ->
|
||||
callback()
|
||||
else
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
, ->
|
||||
|
||||
widget: (account, callback) ->
|
||||
mBalance.find
|
||||
account_id: account._id
|
||||
type: 'service_billing'
|
||||
'attribute.service': 'shadowsocks'
|
||||
.toArray (err, balance_logs) ->
|
||||
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 balance_logs, (i) ->
|
||||
return i.created_at.getTime() > Date.now() - range
|
||||
|
||||
result[name] = _.reduce logs, (memo, i) ->
|
||||
return memo + i.attribute.traffic_mb
|
||||
, 0
|
||||
|
||||
jade.renderFile path.join(__dirname, 'view/widget.jade'), _.extend(result, account: account), (err, html) ->
|
||||
throw err if err
|
||||
callback html
|
||||
, ->
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
{_, child_process} = app.libs
|
||||
{logger, utils} = app
|
||||
{Account} = app.models
|
||||
|
||||
BILLING_BUCKET = config.plugins.shadowsocks.billing_bucket
|
||||
|
||||
exports.generateConfigure = (users, options) ->
|
||||
configure =
|
||||
server: '0.0.0.0'
|
||||
local_port: 1080
|
||||
port_password: {}
|
||||
timeout: 60
|
||||
method: options.method ? 'aes-256-cfb'
|
||||
workers: 2
|
||||
|
||||
for user in users
|
||||
configure.port_password[user.port] = user.password
|
||||
|
||||
return JSON.stringify configure
|
||||
|
||||
exports.generatePort = (port) ->
|
||||
port = 10000 + Math.floor Math.random() * 10000
|
||||
|
||||
Account.findOne
|
||||
'pluggable.shadowsocks.port': port
|
||||
, (err, result) ->
|
||||
if result
|
||||
generatePort callback
|
||||
else
|
||||
callback port
|
||||
|
||||
exports.queryIptablesInfo = (callback) ->
|
||||
child_process.exec 'sudo iptables -n -v -L -t filter -x --line-numbers', (err, stdout) ->
|
||||
lines = stdout.split '\n'
|
||||
iptables_info = {}
|
||||
|
||||
do ->
|
||||
CHAIN_OUTPUT = 'Chain OUTPUT'
|
||||
is_chain_output = false
|
||||
|
||||
for item in lines
|
||||
if is_chain_output
|
||||
if item
|
||||
try
|
||||
[num, pkts, bytes, prot, opt, in_, out, source, destination, prot, port] = item.split /\s+/
|
||||
|
||||
unless num == 'num'
|
||||
port = port.match(/spt:(\d+)/)[1]
|
||||
|
||||
iptables_info[port.toString()] =
|
||||
num: parseInt num
|
||||
pkts: parseInt pkts
|
||||
bytes: parseInt bytes
|
||||
port: parseInt port
|
||||
|
||||
catch e
|
||||
continue
|
||||
|
||||
if item[ ... CHAIN_OUTPUT.length] == CHAIN_OUTPUT
|
||||
is_chain_output = true
|
||||
|
||||
callback iptables_info
|
||||
|
||||
exports.initAccount = (account, callback) ->
|
||||
exports.generatePort (port) ->
|
||||
password = utils.randomString 10
|
||||
|
||||
Account.findByIdAndUpdate account._id,
|
||||
$set:
|
||||
'pluggable.shadowsocks':
|
||||
port: port
|
||||
method: _.first config.plugins.shadowsocks.available_ciphers
|
||||
password: password
|
||||
pending_traffic: 0
|
||||
last_traffic_value: 0
|
||||
, (err, account) ->
|
||||
logger.error err if err
|
||||
|
||||
child_process.exec "sudo iptables -I OUTPUT -p tcp --sport #{port}", ->
|
||||
child_process.exec 'sudo iptables-save | sudo tee /etc/iptables.rules', ->
|
||||
exports.updateConfigure account, ->
|
||||
callback()
|
||||
|
||||
exports.deleteAccount = (account, callback) ->
|
||||
|
||||
exports.updateConfigure = (account, callback) ->
|
||||
|
||||
exports.monitoring = ->
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
{
|
||||
"server": "0.0.0.0",
|
||||
"server_port": <%= port %>,
|
||||
"local_port": 1080,
|
||||
"password": "<%= password %>",
|
||||
"port_password": {
|
||||
<% users.forEach(function(user) { %>
|
||||
"<%= user.port %>": "<%= %>"
|
||||
<% }); %>
|
||||
"8381": "foobar1",
|
||||
"8382": "foobar2",
|
||||
"8383": "foobar3",
|
||||
"8384": "foobar4"
|
||||
},
|
||||
"timeout": 60,
|
||||
"method": "aes-256-cfb"
|
||||
"method": "aes-256-cfb",
|
||||
"workers": 2
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ module.exports =
|
||||
shadowsocks:
|
||||
green_style: true
|
||||
|
||||
available_ciphers: ['aes-256-cfb', 'rc4-md5']
|
||||
|
||||
billing_bucket: 100 * 1024 * 1024
|
||||
monitor_cycle: 5 * 60 * 1000
|
||||
price_bucket: 0.06
|
||||
|
||||
Reference in New Issue
Block a user