Skip to content

Instantly share code, notes, and snippets.

@cloverstd
Last active March 21, 2016 14:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cloverstd/f4fb0e8821abc3cb9fd8 to your computer and use it in GitHub Desktop.
Save cloverstd/f4fb0e8821abc3cb9fd8 to your computer and use it in GitHub Desktop.
ssbee
{
"ENCRYPT_KEY": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"ENCRYPT_IV": "AAAAAAAAAAAAAAAA",
"WORKER_URL": "http://11.11.11.3:9998/worker",
"WORKER_WS_URI": "ws://11.11.11.3:9998/worker/",
"SUPERVISOR_PROCESS_NAME": "shadowsocks",
"SUPERVISOR_CTL_PORT": 4999,
"SUPERVISOR_CTL_HOST": "127.0.0.1",
"DEBUG": true
}
#!/usr/bin/env python
# encoding: utf-8
import json
import os
_config = {
"ENCRYPT_KEY": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"ENCRYPT_IV": "AAAAAAAAAAAAAAAA",
"WORKER_URL": "http://11.11.11.3:9998/worker",
"WORKER_WS_URI": "ws://11.11.11.3:9998/worker/",
"SUPERVISOR_PROCESS_NAME": "shadowsocks",
"SUPERVISOR_CTL_PORT": 4999,
"SUPERVISOR_CTL_HOST": "127.0.0.1",
"DEBUG": False
}
_node_config = {
"name": "franky",
"domain": "jp.do.hui.lu",
"ip": "11.11.11.3",
"remark": "这里是描述",
"location": "Japan Tokyo",
"method": "aes-256-cfb"
}
def read_config(_config, prompt="config"):
config = {}
for key, value in _config.items():
print "Please enter {} {}(default {})".format(prompt, key, value)
temp = raw_input('> ')
if temp == '':
temp = value
if isinstance(temp, str) and temp.isdigit():
temp = int(temp)
config[key] = temp
return config
config = read_config(_config)
node_config = read_config(_node_config, 'node_config')
print json.dumps(config, indent=4, separators=(',', ': '))
print json.dumps(node_config, indent=4, separators=(',', ': '))
save_path = raw_input('save_path > ')
if save_path == '':
save_path = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(save_path, 'config.json'), 'w') as fp:
json.dump(config, fp, indent=4, separators=(',', ': '))
with open(os.path.join(save_path, 'node.json'), 'w') as fp:
json.dump(node_config, fp, indent=4, separators=(',', ': '))
{
"name": "franky",
"domain": "jp.do.hui.lu",
"ip": "11.11.11.3",
"remark": "这里是描述",
"location": "Japan Tokyo",
"method": "aes-256-cfb"
}
#!/bin/bash
# install software
SS_USER=app
SS_APP=ssbee
APP_PATH=/home/$SS_USER/$SS_APP
sudo apt-get update -y && sudo apt-get upgrade -y
curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
sudo apt-get install -y python-pip supervisor fail2ban nodejs git
sudo useradd -m -U -s /bin/bash $SS_USER
# download node code
sudo rm -rf $APP_PATH
sudo -u app sh -c "git clone https://gist.github.com/f4fb0e8821abc3cb9fd8.git ${APP_PATH}"
sudo cp $APP_PATH/shadowsocks.json /etc/shadowsocks.json
sudo cp /etc/supervisor/supervisord.conf /etc/supervisor/supervisord.conf.bak
sudo cp $APP_PATH/supervisor.conf /etc/supervisor/supervisord.conf
sudo cp $APP_PATH/supervisor.worker.conf /etc/supervisor/conf.d/worker.conf
# iptables
sudo iptables -F
sudo iptables -A INPUT -p tcp -d 127.0.0.1 --dport 6001 -j ACCEPT
sudo iptables -A INPUT -p tcp -d 127.0.0.1 --dport 4999 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 6001 -j DROP
sudo iptables -A INPUT -p tcp --dport 4999 -j DROP
sudo sh -c "iptables-save > /etc/iptables.rules"
sudo -u app sh -c "python ${APP_PATH}/node.init.py"
sudo su - app -c "cd ${APP_PATH} && npm install"
sudo pip install shadowsocks
sudo service supervisor restart
{
"name": "ssbee",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"request": "^2.69.0",
"systeminformation": "^2.0.5",
"ws": "^1.0.1",
"xmlrpc": "^1.3.1"
}
}
{
"server": "0.0.0.0",
"port_password": {
"9999": "123456"
},
"timeout": 60,
"method": "aes-256-cfb",
"manager_address": "127.0.0.1:6001"
}
[unix_http_server]
file=/var/run/supervisor.sock
chmod=0700
[supervisord]
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
childlogdir=/var/log/supervisor
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[inet_http_server]
port = 127.0.0.1:4999
[supervisorctl]
serverurl = unix:///var/run/supervisor.sock
[program:shadowsocks]
command=ssserver -c /etc/shadowsocks.json
autorestart=true
user=nobody
stdout_logfile=/var/log/shadowsocks.out.log
stderr_logfile=/var/log/shadowsocks.err.log
[include]
files = /etc/supervisor/conf.d/*.conf
[program:worker]
command=node worker.js
numprocs=1
directory=/home/app/ssbee
autostart=true
autorestart=true
user=app
stdout_logfile=/var/log/shadowsocks-%(program_name)s.out.log
stderr_logfile=/var/log/shadowsocks-%(program_name)s.err.log
var WebSocket = require('ws');
var request = require('request');
var EventEmitter = require('events');
var dgram = require('dgram');
var si = require('systeminformation');
var crypto = require('crypto');
var xmlrpc = require('xmlrpc');
var config = require('./config.json');
var node_config = require('./node.json');
var PORT = 6001;
var HOST = '127.0.0.1';
var SYS_UPDATE_INTERVAL = 10 * 1000; // 10 s
var timeouts = {};
var DEBUG = config.DEBUG;
var supervisor_client = xmlrpc.createClient({
host: config.SUPERVISOR_CTL_HOST,
port: config.SUPERVISOR_CTL_PORT,
path: '/RPC2'
});
var ip, iface = 'eth0';
if (DEBUG) {
iface = 'eth1';
}
function Encryption(key, iv) {
this.encrypt = function(data) {
var
encipher = crypto.createCipheriv('aes-256-cbc', key, iv),
encoded = encipher.update(data, 'utf-8', 'base64');
encoded += encipher.final( 'base64' );
return encoded;
};
this.decrypt = function(data) {
var
decipher = crypto.createDecipheriv('aes-256-cbc', key, iv),
decoded = decipher.update(data, 'base64', 'utf-8');
decoded += decipher.final( 'utf-8' );
return decoded;
};
}
var encryption = new Encryption(config.ENCRYPT_KEY, config.ENCRYPT_IV);
function handshake(ip, cb) {
function inner() {
var body = node_config;
body.code = 1;
body = JSON.stringify(body);
body = encryption.encrypt(body);
request.post({
url: config.WORKER_URL,
body: body
}, function(error, response, body) {
if (!error && response.statusCode == 200){
try {
var data = JSON.parse(body);
if (data.code == 1 && data.data && data.data.node_id) {
console.log(data.data.node_id);
if (typeof cb == "function") {
cb(data.data);
}
} else {
setTimeout(function() {
inner();
console.log(data);
console.log('retry.');
}, 2000);
}
} catch (SyntaxError) {
console.log('错误的消息' + body);
}
} else {
setTimeout(function() {
inner();
console.log(error);
console.log('retry.');
}, 2000);
}
});
}
inner();
}
function dance(node_id) {
var ws_uri = config.WORKER_WS_URI + node_id;
var ws_reconnect_time = 10;
var ws, e = new EventEmitter();
function createWS() {
var ws = new WebSocket(ws_uri);
function reconnect() {
e.removeAllListeners('ss.flow');
for (var key in timeouts) {
clearTimeout(timeouts[key]);
}
setTimeout(function() {
createWS();
console.log('reconnect');
}, ws_reconnect_time * 1000);
}
function send(data) {
if (ws) {
var message;
if (typeof data == 'string') {
message = data;
} else {
message = JSON.stringify(data);
}
ws.send(encryption.encrypt(message), function(err) {
if (err) {
e.removeAllListeners('ws.send');
e.emit('ws.send', message);
console.log('ws closed' + err);
}
});
}
}
function handle_message(data) {
switch (data.code) {
case 2: // add user
console.log('新用户来了');
e.emit('ss.add', data);
break;
case 3: // remove user
console.log('要移除用户了');
e.emit('ss.remove', data);
break;
case 6: // 同步
console.log('同步');
e.emit('ss.sync', data.data);
break;
case 7: // 同步
console.log('改密码');
e.emit('ss.change_password', data.data);
break;
case 40:
console.log('停止 supervisor shadowsocks');
supervisor_client.methodCall('supervisor.stopProcess', [config.SUPERVISOR_PROCESS_NAME], function(err, res) {
if (err) {
send({
code: 40,
err: err,
status: false
});
} else {
send({
code: 40,
status: true
});
}
});
break;
case 41:
console.log('开启 supervisor shadowsocks');
supervisor_client.methodCall('supervisor.startProcess', [config.SUPERVISOR_PROCESS_NAME], function(err, res) {
if (err) {
send({
code: 41,
err: err,
status: false
});
} else {
send({
code: 41,
status: true
});
}
});
break;
default:
console.log(data);
break;
}
}
ws.on('open', function() {
console.log('ws opened');
e.removeAllListeners('ss.flow');
e.on('ws.send', function(message) {
send(message);
});
e.on('ss.flow', function(data) {
console.log('更新流量');
send({
code: 5,
data: {
node_id: node_id,
data: data
},
message: '更新流量',
});
});
// 系统信息
// 只更新一次的
si.osInfo(function(info) {
send({
code: 10,
data: info
});
});
si.cpu(function(info) {
send({
code: 11,
data: info
});
});
// 系统信息
// 不停刷新的
(function memUpdate() {
// 内存
si.mem(function(info) {
send({
code: 12,
data: info,
});
timeouts.mem = setTimeout(memUpdate, SYS_UPDATE_INTERVAL);
});
})();
(function fsSizeUpdate() {
// 硬盘容量
si.fsSize(function(info) {
send({
code: 13,
data: info
});
timeouts.fsSize = setTimeout(fsSizeUpdate, SYS_UPDATE_INTERVAL);
});
})();
(function fsStatsUpdate() {
// 硬盘读写
si.fsStats(function(info) {
send({
code: 14,
data: info
});
timeouts.fsStats = setTimeout(fsStatsUpdate, SYS_UPDATE_INTERVAL);
});
})();
(function networkStatsUpdate() {
// 网络读写
si.networkStats(iface, function(info) {
send({
code: 15,
data: info
});
timeouts.net = setTimeout(networkStatsUpdate, SYS_UPDATE_INTERVAL);
});
})();
(function currentLoadUpdate() {
// 负载
si.currentLoad(function(info) {
send({
code: 16,
data: {
load: info,
uptime: si.time().uptime,
current: si.time().current
}
});
timeouts.currentLoad = setTimeout(currentLoadUpdate, SYS_UPDATE_INTERVAL);
});
})();
(function fullLoadUpdate() {
// 负载2
si.fullLoad(function(info) {
send({
code: 17,
data: info
});
timeouts.fullLoad = setTimeout(fullLoadUpdate, SYS_UPDATE_INTERVAL);
});
})();
(function ssInfoUpdate() {
// ss 负载
si.processLoad('ssserver', function(info) {
send({
code: 18,
data: info
});
timeouts.ss = setTimeout(ssInfoUpdate, SYS_UPDATE_INTERVAL);
});
})();
(function supervisorInfoUpdate() {
// supervisor info 42
supervisor_client.methodCall('supervisor.getSupervisorVersion', [], function(err, s_version) {
if (err) {
console.log(err);
} else {
supervisor_client.methodCall('supervisor.getState', [], function(err, res) {
if (err) {
console.log(err);
} else {
send({
code: 42,
data: {
version: s_version,
statecode: res.statecode,
statename: res.statename
}
});
}
timeouts.supervisor_info = setTimeout(supervisorInfoUpdate, SYS_UPDATE_INTERVAL);
});
}
});
})();
(function supervisorShadowsocksUpdate() {
// supervisor shadowsocks 43
supervisor_client.methodCall('supervisor.getProcessInfo', [config.SUPERVISOR_PROCESS_NAME], function(err, res) {
if (err) {
console.log(err);
} else {
send({
code: 43,
data: res
});
}
timeouts.supervisor_shadowsocks = setTimeout(supervisorShadowsocksUpdate, SYS_UPDATE_INTERVAL);
});
})();
});
ws.on('message', function(message) {
// try {
message = encryption.decrypt(message);
var data = JSON.parse(message);
console.log(data);
handle_message(data);
// } catch (SyntaxError) {
console.log('错误的消息' + message);
// }
});
ws.on('close', function(data) {
console.log('ws close, wait connect.');
reconnect();
});
ws.on('error', function(data) {
console.log('ws error, wait connect.');
reconnect();
});
}
var client;
var socket_reconnect_time = 10;
function createUDP() {
// function reconnect() {
// e.removeAllListeners('ss.add');
// e.removeAllListeners('ss.remove');
// setTimeout(function() {
// createUDP();
// console.log('reconnect ss');
// }, socket_reconnect_time * 1000);
// }
function send(message, cb) {
client.send(message, 0, message.length, PORT, HOST, cb);
}
client = dgram.createSocket('udp4', function(message) {
var data = message.toString('utf-8');
if (!data) {
console.log(message);
}
if (data.startsWith('stat: ')) {
// 上报流量
data = JSON.parse(data.slice(6));
console.log(data);
e.emit('ss.flow', data);
} else if (data.startsWith('pong')) {
// 连接上了
console.log('pong');
}
e.removeAllListeners('ss.add');
e.removeAllListeners('ss.remove');
e.removeAllListeners('ss.sync');
e.removeAllListeners('ss.change_password');
e.on('ss.sync', function(users) {
users.forEach(function(user) {
e.emit('ss.add', user);
});
});
e.on('ss.change_password', function(user) {
var ss_info = {
server_port: user.port,
password: user.old_password
};
send('remove: ' + JSON.stringify(ss_info), function(err, bytes) {
console.log('remove user');
ss_info = {
server_port: user.port,
password: user.new_password
};
send('add: ' + JSON.stringify(ss_info), function(err, bytes) {
console.log('add user');
});
});
});
e.on('ss.add', function(user) {
var ss_info = {
server_port: user.port,
password: user.password
};
send('add: ' + JSON.stringify(ss_info), function(err, bytes) {
console.log('add user');
});
});
e.on('ss.remove', function(user) {
var ss_info = {
server_port: user.port,
password: user.password
};
send('remove: ' + JSON.stringify(ss_info), function(err, bytes) {
console.log('remove user');
});
});
});
// client.send('ping', 0, 'ping'.length, PORT, HOST, function(err, bytes) {
// if (err) throw err;
// console.log('UDP message sent ping to ' + HOST +':'+ PORT);
// //client.close();
// });
send('ping', function(err, bytes) {
if (err) throw err;
console.log('ping');
});
client.on('close', function() {
console.log('ss UDP close, wait connect.');
reconnect();
});
client.on('error', function() {
console.log('ss UDP error, wait connect.');
reconnect();
});
}
createWS();
createUDP();
}
si.networkInterfaces(function(info) {
info.forEach(function(net) {
if (iface == net.iface) {
ip = net.ip4;
return;
}
});
console.log('Net iface: ' + iface);
console.log('IP: ' + ip);
handshake(ip, function(data) {
// 握手
console.log(data);
dance(data.node_id);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment