diff --git a/README.md b/README.md new file mode 100644 index 0000000..1adae5a --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# ShadowsocksR-support +一个在服务器端生成ShadowsockR服务器订阅的简单实现 + +### 设计目的 +- 防止因为DNS解析导致SSR服务器ip被BAN +- 降低因服务器新增/迁移,SSR设置更新等的工作量 + +### 部署方式 +- 主服务器的部署 + 1. 拷贝php目录下文件到主服务器 + 2. 修改`config.json`配置文件 + 3. 配置Nginx/apache等Web服务器 +- SSR服务器的部署 + 1. 将python目录下的拷贝到部署SSR的服务器 + 2. 修改`config.json`配置文件 + 3. 添加开机启动(必须设置时间延迟,否则会导致网络错误) + 1. 打开`/etc/rc.d/rc.local` + 2. 添加 + ``` + ( + sleep 120 + nohup python /项目路径/python/main.py > /项目路径/python/log.out 2>&1 + )& + ``` + 4. 添加定时任务 + 1. 控制台输入`crontab -e` + 2. 添加 + ``` + 0 * * * * python /项目路径/python/main.py & + ``` + +### config.json配置文件 +- 主服务器config.json + ``` + { + "group": "PowerByBafflingBUG", //SSR GROUP + "token": "password" //验证,用于确认权限(弱验证) + } + ``` + +- SSR服务器config.json + ``` + { + "ss-config-file": ["ss_config1","ss_config2"], //SS服务器的配置文件路径 + "ssr-config-file": ["ssr_config1","ssr_config2"], //SSR服务器的配置文件路径 + "main-server": "ssr.example.com", //主服务器的域名/IP + "host": "0.0.0.0", //当前服务器的外网IP + "token": "password", //验证,需与主服务器一致 + "ssl": false //主服务器是否使用SSL(https) + } + ``` + +### SSR服务器订阅地址 +**http(s)://[main-server]/?token=[token]** + +示例:*http://example.com/?token=password* + +### 运行流程 +1. SSR服务器每小时检测本地各配置文件(包括SS配置文件,SSR配置文件,SSRS配置文件)是否被更新 +2. - 若有更新 向主服务器推送新的SSR链接 + - 若无更新 向主服务器发送SSR服务器在线确认 + > - 若主服务器不存在当前服务器的记录 重新推送SSR链接到主服务器 +3. 主服务器记录各SSR服务器的推送/确认时间,且抛弃3小时未确认的服务器的SSR链接 + +### 写在最后 +- 这个一个个人项目,没有做很多的兼容性判断。可能会在其他环境下无法运行 +- python的运行环境是2.7 +- 选择python和php的原因是我现在使用的两台服务器的运行环境 \ No newline at end of file diff --git a/php/active.php b/php/active.php new file mode 100644 index 0000000..630a7c8 --- /dev/null +++ b/php/active.php @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/php/config.json b/php/config.json new file mode 100644 index 0000000..ae4bf41 --- /dev/null +++ b/php/config.json @@ -0,0 +1,4 @@ +{ + "group": "PowerByBafflingBUG", + "token": "password" +} \ No newline at end of file diff --git a/php/group.php b/php/group.php new file mode 100644 index 0000000..14d87a6 --- /dev/null +++ b/php/group.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/php/index.php b/php/index.php new file mode 100644 index 0000000..0a3d2df --- /dev/null +++ b/php/index.php @@ -0,0 +1,18 @@ + $value){ + if((strtotime(date("y-m-d H:i:s"))-strtotime($value["lasttime"]))/3600 < 3.0){ + $ssr .= $value["ssr"]; + } + } + echo base64_encode($ssr); + } +?> \ No newline at end of file diff --git a/php/post.php b/php/post.php new file mode 100644 index 0000000..4a14382 --- /dev/null +++ b/php/post.php @@ -0,0 +1,18 @@ + $_POST["ssr"], + "lasttime" => date("y-m-d H:i:s") + ); + $json = json_encode($url); + file_put_contents('./ssrURL.json', $json); + } +?> \ No newline at end of file diff --git a/php/ssrURL.json b/php/ssrURL.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/php/ssrURL.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/python/MD5.json b/python/MD5.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/python/MD5.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/python/config.json b/python/config.json new file mode 100644 index 0000000..a47c72f --- /dev/null +++ b/python/config.json @@ -0,0 +1,8 @@ +{ + "ss-config-file": ["ss_config1","ss_config2"], + "ssr-config-file": ["ssr_config1","ssr_config2"], + "main-server": "ssr.example.com", + "host": "0.0.0.0", + "token": "password", + "ssl": false +} \ No newline at end of file diff --git a/python/main.py b/python/main.py new file mode 100644 index 0000000..aa44a90 --- /dev/null +++ b/python/main.py @@ -0,0 +1,46 @@ +import json +import os +from network import * +from ssrs import * + +if __name__ == "__main__": + update = False + dir_path = os.path.dirname(os.path.realpath(__file__)) + try: + MD5_file = open(dir_path + '/MD5.json', 'r') + fileMD5 = json.load(MD5_file) + except: + fileMD5 = {} + update = True + finally: + MD5_file.close() + update = new_config(dir_path + '/config.json', fileMD5, update) + config_file = open(dir_path + '/config.json', 'r') + config = json.load(config_file) + config_file.close() + group = getgroup('http://' if not config['ssl'] else 'https://' + config['main-server'] + '/group.php') + if 'group' not in fileMD5.keys() or group != fileMD5['group']: + print(group) + update = True + fileMD5['group'] = group + ssr_url = '' + for ss_config_file_name in config['ss-config-file']: + update = new_config(ss_config_file_name, fileMD5, update) + ssr_url += ss2URL(ss_config_file_name, config, group) + for ssr_config_file_name in config['ssr-config-file']: + update = new_config(ssr_config_file_name, fileMD5, update) + ssr_url += ssr2URL(ssr_config_file_name, config, group) + if update: + print(ssr_url) + MD5_file = open(dir_path + '/MD5.json', 'w') + json.dump(fileMD5, MD5_file) + MD5_file.close() + res = post('http://' if not config['ssl'] else 'https://' + config['main-server'] + '/post.php', ssr_url, + config['token'], config['host']) + else: + state = active('http://' if not config['ssl'] else 'https://' + config['main-server'] + '/active.php', config['token'], config['host']) + if state.find('error:2') >= 0: + MD5_file = open(dir_path + '/MD5.json', 'w') + MD5_file.truncate() + MD5_file.close() + os.system('python ' + dir_path + '/ssrs.py') diff --git a/python/network.py b/python/network.py new file mode 100644 index 0000000..eb7a604 --- /dev/null +++ b/python/network.py @@ -0,0 +1,21 @@ +import urllib +import urllib2 + + +def getgroup(url): + res = urllib2.urlopen(url) + return str(res.read()) + + +def post(url, data, token, host): + parm = urllib.urlencode({'ssr': data, "token": token, "host": host}) + req = urllib2.Request(url, parm) + res = urllib2.urlopen(req) + return str(res.read()) + + +def active(url, token, host): + parm = urllib.urlencode({"token": token, "host": host}) + req = urllib2.Request(url, parm) + res = urllib2.urlopen(req) + return str(res.read()) diff --git a/python/ssrs.py b/python/ssrs.py new file mode 100644 index 0000000..576b814 --- /dev/null +++ b/python/ssrs.py @@ -0,0 +1,41 @@ +import base64 +import json +import hashlib +from network import * + +def new_config(file, fileMD5, update): + config_file = open(file, 'rb') + md5 = hashlib.md5(config_file.read()).hexdigest() + config_file.close() + if file in fileMD5.keys() and fileMD5[file] == md5: + return update + fileMD5[file] = md5 + return True + + +def ssr2URL(file, config, group): + ssr_config_file = open(file) + ssr_config = json.load(ssr_config_file) + param_str = "obfsparam=" + base64.urlsafe_b64encode(ssr_config['obfs_param'].encode() if not ssr_config['obfs_param'] is None else ''.encode()).decode().rstrip('=') + if 'protocol_param' in ssr_config.keys() and (not ssr_config['protocol_param'] == ""): + param_str += '&protoparam=' + base64.urlsafe_b64encode(ssr_config['protocol_param'].encode()).decode().rstrip('=') + if 'remarks' in ssr_config.keys() and (not ssr_config['remarks'] == ""): + param_str += '&remarks=' + base64.urlsafe_b64encode(ssr_config['remarks'].encode()).decode().rstrip('=') + param_str += '&group=' + base64.urlsafe_b64encode(group.encode()).decode().rstrip('=') + main_part = config["host"] + ':' + str(ssr_config['server_port']) + ':' + ssr_config['protocol'] + ':' + ssr_config['method'] + ':' + ssr_config['obfs'] + ':' + base64.urlsafe_b64encode(ssr_config['password'].encode()).decode().rstrip('=') + ssr_config_file.close() + b64 = base64.urlsafe_b64encode((main_part + '/?' + param_str).encode()).decode().rstrip('=') + return 'ssr://' + b64 + '\n' + + +def ss2URL(file, config, group): + ss_config_file = open(file) + ss_config = json.load(ss_config_file) + param_str = "obfsparam=" + '' + if 'remarks' in ss_config.keys() and (not ss_config['remarks'] == ""): + param_str += '&remarks=' + base64.urlsafe_b64encode(ss_config['remarks'].encode()).decode().rstrip('=') + param_str += '&group=' + base64.urlsafe_b64encode(group.encode()).decode().rstrip('=') + main_part = config["host"] + ':' + str(ss_config['server_port']) + ':' + 'origin' + ':' + ss_config['method'] + ':' + 'plain' + ':' + base64.urlsafe_b64encode(ss_config['password'].encode()).decode().rstrip('=') + ss_config_file.close() + b64 = base64.urlsafe_b64encode((main_part + '/?' + param_str).encode()).decode().rstrip('=') + return 'ssr://' + b64 + '\n'