mirror of
https://github.com/HackPlan/RootPanel.git
synced 2026-04-24 03:35:59 +08:00
261 lines
7.6 KiB
CoffeeScript
261 lines
7.6 KiB
CoffeeScript
{_, child_process, async, fs} = app.libs
|
|
{logger, utils, config} = app
|
|
{Account, Financials} = app.models
|
|
|
|
supervisor = require '../supervisor/supervisor'
|
|
|
|
ShadowsocksPlugin = require './index'
|
|
|
|
BILLING_BUCKET = config.plugins.shadowsocks?.billing_bucket ? 100 * 1024 * 1024
|
|
|
|
exports.initSupervisor = (callback) ->
|
|
supervisor.programsStatus (program_status) ->
|
|
async.each config.plugins.shadowsocks.available_ciphers, (method, callback) ->
|
|
program_name = "shadowsocks-#{method}"
|
|
|
|
if program_name in _.pluck program_status, 'name'
|
|
return callback()
|
|
|
|
exports.writeSupervisorConfigure method, ->
|
|
supervisor.updateProgram {}, {program_name: program_name}, ->
|
|
callback()
|
|
|
|
, callback
|
|
|
|
exports.writeSupervisorConfigure = (method, callback) ->
|
|
program_name = "shadowsocks-#{method}"
|
|
|
|
configure = exports.generateConfigure [],
|
|
method: method
|
|
|
|
filename = "/etc/shadowsocks/#{method}.json"
|
|
ShadowsocksPlugin.writeConfigFile filename, configure, {mode: 0o755}, ->
|
|
supervisor.writeConfig {username: 'nobody'},
|
|
program_name: program_name
|
|
command: "ssserver -c #{filename}"
|
|
name: program_name
|
|
autostart: true
|
|
autorestart: true
|
|
stdout_logfile: false
|
|
, ->
|
|
callback()
|
|
|
|
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 = (callback) ->
|
|
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 ->
|
|
callback()
|
|
|
|
exports.deleteAccount = (account, callback) ->
|
|
exports.queryIptablesInfo (iptables_info) ->
|
|
{port} = 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.updateConfigure 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 = (callback) ->
|
|
async.eachSeries config.plugins.shadowsocks.available_ciphers, (method, callback) ->
|
|
Account.find
|
|
'pluggable.shadowsocks.method': method
|
|
, (err, accounts) ->
|
|
users = _.map accounts, (account) ->
|
|
return account.pluggable.shadowsocks
|
|
|
|
configure = exports.generateConfigure users,
|
|
method: method
|
|
|
|
filename = "/etc/shadowsocks/#{method}.json"
|
|
ShadowsocksPlugin.writeConfigFile filename, configure, {mode: 0o755}, ->
|
|
supervisor.updateProgram {}, {program_name: "shadowsocks-#{method}"}, ->
|
|
supervisor.programControl {}, {program_name: "shadowsocks-#{method}"}, 'restart', ->
|
|
callback()
|
|
|
|
, ->
|
|
callback()
|
|
|
|
exports.monitoring = ->
|
|
exports.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()
|
|
|
|
, ->
|