From 2e94156ffe50268ac13ecb5e566fc12c027f470e Mon Sep 17 00:00:00 2001 From: quackerd Date: Sat, 16 Jan 2021 17:53:44 -0500 Subject: [PATCH] new format --- LICENSE | 2 +- client.json => client_conf.in | 12 +- client_obj.in | 4 + config.yml | 32 ++++ configure.py | 176 +++++++++++++++++++++ docker-compose.yml => docker-compose.in | 22 +-- example.conf | 5 - nginx/nginx/site-confs/default => nginx.in | 4 +- v2ray/config.json => server.in | 7 +- setup.py | 158 ------------------ watchtower.in | 8 + 11 files changed, 243 insertions(+), 187 deletions(-) rename client.json => client_conf.in (91%) create mode 100644 client_obj.in create mode 100644 config.yml create mode 100644 configure.py rename docker-compose.yml => docker-compose.in (70%) delete mode 100644 example.conf rename nginx/nginx/site-confs/default => nginx.in (89%) rename v2ray/config.json => server.in (81%) delete mode 100644 setup.py create mode 100644 watchtower.in diff --git a/LICENSE b/LICENSE index 204b93d..2ec0089 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -MIT License Copyright (c) +MIT License Copyright (c) 2021 quackerd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/client.json b/client_conf.in similarity index 91% rename from client.json rename to client_conf.in index 4f464de..f265715 100644 --- a/client.json +++ b/client_conf.in @@ -68,7 +68,7 @@ "inbounds": [ { - "port": 1080, + "port": {{ port }}, "listen": "127.0.0.1", "protocol": "socks", "sniffing": { @@ -88,12 +88,12 @@ "settings": { "vnext": [ { - "address": "{{ server_name }}", + "address": "{{ subdomain }}.{{ domain }}", "port": 443, "users": [ { - "id": "{{ uuid }}", - "alterId": 64, + "id": "{{ id }}", + "alterId": {{ alterid }}, "security": "none" } ] @@ -104,7 +104,7 @@ "network": "ws", "security": "tls", "wsSettings": { - "path": "/{{ path }}" + "path": "{{ path }}" } } }, @@ -119,4 +119,4 @@ "tag": "block" } ] -} +} \ No newline at end of file diff --git a/client_obj.in b/client_obj.in new file mode 100644 index 0000000..4701b9c --- /dev/null +++ b/client_obj.in @@ -0,0 +1,4 @@ +{ + "id": "{{ id }}", + "alterId": {{ alterid }} +} \ No newline at end of file diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..a994839 --- /dev/null +++ b/config.yml @@ -0,0 +1,32 @@ +server: + # the domain name + domain: domain.tld + # the subdomain name. leave empty for naked domain. + subdomain: example + # email. your email for the registered SSL cert. leave empty for a dummy email. + email: + # the user/group to run the docker-compose stack. + # defaults to the current user + # or you can manually set, e.g.: + # uid: 1000 + # gid: 1000 + uid: + gid: + # the path for websocket. Do NOT include the leading slash. default: auto-generated. + path: + # whether or not to enable watchtower to auto update docker containers. default: false + watchtower: False + +clients: + - name: example_user1 + # default id: auto-generated uuid + id: + # default alterid: 64 + alterid: + # port for local socks5. default: 1080 + port: + - name: example_user2 + # or you can set them manually + id: 5058b990-6be5-438d-9c02-4b06b5d927d0 + alterid: 32 + port: 6666 diff --git a/configure.py b/configure.py new file mode 100644 index 0000000..7a48f1b --- /dev/null +++ b/configure.py @@ -0,0 +1,176 @@ +import getopt +import sys +import uuid +import pwd +import jinja2 +import random +import os +import string +import yaml + +OUTPUT_DIR = "build" +CLIENT_OUTPUT_DIR = "clients" +DEFAULT_EMAIL = "dummy@dummy.org" +CONF_FILE = "config.yml" +SERVER_IN = "server.in" +SERVER_PATH = "v2ray" +SERVER_FN = "config.json" +NGINX_IN = "nginx.in" +NGINX_PATH = "nginx/nginx/site-confs" +NGINX_FN = "default" +DEFAULT_ALTERID = 64 +CLIENT_OBJ_IN = "client_obj.in" +CLIENT_CONF_IN = "client_conf.in" +DOCKER_IN = "docker-compose.in" +WATCHTOWER_IN = "watchtower.in" +DEFAULT_WATCHTOWER_ENABLE = False +DEFAULT_CLIENT_PORT = 1080 + +def yaml_key_exists_else(mapping : [], name : str, other_val = None, nullable = True): + if (name in mapping) and (mapping[name] != None): + return mapping[name] + else: + if not nullable: + raise Exception("Key " + name + " must not be null.") + else: + return other_val + +class Client: + def __init__(self, conf_obj): + self.name = str(yaml_key_exists_else(conf_obj, 'name', nullable=False)) + self.id = str(yaml_key_exists_else(conf_obj, 'id', other_val=uuid.uuid4())) + self.alterid = int(yaml_key_exists_else(conf_obj,'alterid', other_val=DEFAULT_ALTERID)) + self.port = int(yaml_key_exists_else(conf_obj, 'port', other_val=DEFAULT_CLIENT_PORT)) + + def print(self, ident): + pre = "" + for i in range(ident): + pre += " " + + print(pre + "{") + print(pre + " id: " + self.id) + print(pre + " alterid: " + str(self.alterid)) + print(pre + " port: " + str(self.port)) + print(pre + "}") + + + +class Config: + @staticmethod + def __randomString(stringLength=16): + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for i in range(stringLength)) + + def print(self): + print("Server configuration:") + print(" domain: " + self.domain) + print(" email: " + self.email) + print(" subdomain: " + self.subdomain) + print(" uid: " + str(self.uid)) + print(" gid: " + str(self.gid)) + print(" path: " + self.path) + print(" clients:") + for i in range(len(self.clients)): + self.clients[i].print(8) + + def __init__(self, f): + conf = yaml.safe_load(f) + conf_srv = conf['server'] + + self.domain = str(yaml_key_exists_else(conf_srv, 'domain', nullable=False)) + self.email = str(yaml_key_exists_else(conf_srv, 'email', other_val=DEFAULT_EMAIL)) + self.subdomain = str(yaml_key_exists_else(conf_srv, 'subdomain', other_val="")) + self.subdomain_only = len(self.subdomain) == 0 + self.uid = int(yaml_key_exists_else(conf_srv, 'uid', other_val=os.getuid())) + self.gid = int(yaml_key_exists_else(conf_srv, 'gid', other_val=os.getgid())) + self.path = str(yaml_key_exists_else(conf_srv, 'path', other_val=self.__randomString())) + self.watchtower = bool(yaml_key_exists_else(conf_srv, 'watchtower', other_val=DEFAULT_WATCHTOWER_ENABLE)) + + self.clients = [] + conf_clients = conf['clients'] + for i in range(len(conf_clients)): + self.clients.append(Client(conf_clients[i])) + +def main(): + with open(CONF_FILE, 'r') as f: + conf = Config(f) + conf.print() + + template_dict = {} + template_dict['uid'] = conf.uid + template_dict['gid'] = conf.gid + template_dict['subdomain'] = conf.subdomain + template_dict['subdomain_only'] = conf.subdomain_only + template_dict['domain'] = conf.domain + template_dict['email'] = conf.email + template_dict['path'] = conf.path + + if conf.watchtower: + with open(WATCHTOWER_IN, "r") as f: + template_dict['watchtower'] = f.read() + else: + template_dict['watchtower'] = "" + + clients = "" + with open(CLIENT_OBJ_IN, "r") as f: + client_obj = f.read() + for i in range(len(conf.clients)): + if i > 0: + clients += ",\n" + clients += jinja2.Template(client_obj).render(id = conf.clients[i].id, + alterid = conf.clients[i].alterid) + template_dict['clients'] = clients + + print("Generating files...") + + # create output dir + os.makedirs(OUTPUT_DIR, exist_ok=True) + + # generate docker-compose.yml + path = os.path.join(OUTPUT_DIR, "docker-compose.yml") + with open(DOCKER_IN, "r") as f: + template = jinja2.Template(f.read()) + with open(path, "w") as f: + f.write(template.render(template_dict)) + + # generate NGINX conf + with open(NGINX_IN, "r") as f: + template = jinja2.Template(f.read()) + path = os.path.join(OUTPUT_DIR, NGINX_PATH) + os.makedirs(path, exist_ok=True) + path = os.path.join(path, NGINX_FN) + with open(path, "w") as f: + f.write(template.render(template_dict)) + + # generate v2ray conf + with open(SERVER_IN, "r") as f: + template = jinja2.Template(f.read()) + path = os.path.join(OUTPUT_DIR, SERVER_PATH) + os.makedirs(path, exist_ok=True) + path = os.path.join(path, SERVER_FN) + with open(path, "w") as f: + f.write(template.render(template_dict)) + + # generate client confs + path = os.path.join(OUTPUT_DIR, CLIENT_OUTPUT_DIR) + os.makedirs(path, exist_ok=True) + with open(CLIENT_CONF_IN, "r") as f: + client_conf_temp = jinja2.Template(f.read()) + for i in range(len(conf.clients)): + template_dict['id'] = conf.clients[i].id + template_dict['port'] = conf.clients[i].port + template_dict['alterid'] = conf.clients[i].alterid + epath = os.path.join(path, conf.clients[i].name + "_" + conf.clients[i].id) + os.makedirs(epath, exist_ok=True) + with open(os.path.join(epath, SERVER_FN), "w") as f: + f.write(client_conf_temp.render(template_dict)) + + # chown + os.chown(OUTPUT_DIR, conf.uid, conf.gid) + for dirpath, dirnames, filenames in os.walk(OUTPUT_DIR): + os.chown(dirpath, conf.uid, conf.gid) + for fname in filenames: + os.chown(os.path.join(dirpath, fname), conf.uid, conf.gid) + print("Please find the generated files in the build directory. To start the stack, run docker-compose up -d in the build directory.") + +main() \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.in similarity index 70% rename from docker-compose.yml rename to docker-compose.in index b1b728b..e17a329 100644 --- a/docker-compose.yml +++ b/docker-compose.in @@ -1,18 +1,18 @@ -version: '3.4' +version: '3.0' networks: - lan: + br-v2ray: external: false services: nginx: - container_name: nginx + container_name: v2ray_nginx image: linuxserver/letsencrypt - restart: always + restart: unless-stopped cap_add: - NET_ADMIN networks: - - lan + - br-v2ray environment: - PUID={{ uid }} - PGID={{ gid }} @@ -20,9 +20,9 @@ services: - URL={{ domain }} - SUBDOMAINS={{ subdomain }} - VALIDATION=http - - EMAIL={{ email }} + - EMAIL=dummy@dummy.com - DHLEVEL=2048 - - ONLY_SUBDOMAINS={{ only_sub }} + - ONLY_SUBDOMAINS={{ subdomain_only }} - STAGING=false ports: - 80:80 @@ -31,11 +31,13 @@ services: - ./nginx:/config v2ray: - container_name: v2ray + container_name: v2ray_v2ray image: teddysun/v2ray - restart: always + restart: unless-stopped networks: - - lan + - br-v2ray command: ["v2ray","-config=/etc/v2ray/config.json"] volumes: - ./v2ray:/etc/v2ray + +{{ watchtower }} diff --git a/example.conf b/example.conf deleted file mode 100644 index d9965a5..0000000 --- a/example.conf +++ /dev/null @@ -1,5 +0,0 @@ -d example.com -s v2ray -e email@domain.tld -u 00000000-0000-0000-0000-000000000000 -p somepath \ No newline at end of file diff --git a/nginx/nginx/site-confs/default b/nginx.in similarity index 89% rename from nginx/nginx/site-confs/default rename to nginx.in index 9b0851f..5f855f6 100644 --- a/nginx/nginx/site-confs/default +++ b/nginx.in @@ -17,10 +17,10 @@ server { # all ssl related config moved to ssl.conf include /config/nginx/ssl.conf; - server_name {{server_name}}; + server_name {{ subdomain }}.{{ domain }}; location /{{ path }} { proxy_redirect off; - proxy_pass http://v2ray:8080; + proxy_pass http://v2ray_v2ray:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; diff --git a/v2ray/config.json b/server.in similarity index 81% rename from v2ray/config.json rename to server.in index cc7ce2c..4274b4e 100644 --- a/v2ray/config.json +++ b/server.in @@ -6,10 +6,7 @@ "protocol": "vmess", "settings": { "clients": [ - { - "id": "{{ uuid }}", - "alterId": 64 - } + {{ clients }} ] }, "streamSettings": { @@ -26,4 +23,4 @@ "settings": {} } ] -} +} \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 3af8dc2..0000000 --- a/setup.py +++ /dev/null @@ -1,158 +0,0 @@ -import getopt -import sys -import uuid -import pwd -import jinja2 -import random -import os -import string - -def randomString(stringLength=16): - letters = string.ascii_lowercase - return ''.join(random.choice(letters) for i in range(stringLength)) - -def read_conf(f): - ret = {} - f = open(f, "r") - lines = f.readlines() - for line in lines: - line = line.strip() - eline = line.split(' ') - if len(eline) >= 2: - ret[eline[0]] = eline[1] - return ret - -def usage(): - print("Usage: python setup.py [options]\n\n\ - options:\n\ - -h : show usage.\n\ - -d domain : your domain - mydomain.tld.\n\ - [-u uuid] : the uuid of the user. Optional.\n\ - [-p path] : the path of the websocket. Optional.\n\ - [-s subdomain] : your subdomain. Optional.\n\ - [-e email] : your email. Optional.\n\ - [-c conf] : load config from file. Optional.\n\n") - -def main(): - email = None - subdomain = None - domain = None - conf_file = None - uid = os.getuid() - gid = os.getgid() - v_uuid = None - v_path = None - - try: - opts , _ = getopt.getopt(sys.argv[1:], "hd:s:e:c:u:p:") - except getopt.GetoptError as err: - print(str(err)) - usage() - sys.exit(1) - - for o, a in opts: - if o == "-h": - usage() - sys.exit(0) - elif o == "-d": - if domain != None: - print("Can specify maximum ONE domain.") - sys.exit(1) - else: - domain = a - elif o == "-s": - if subdomain != None: - print("Can specify maximum ONE subdomain.") - sys.exit(1) - else: - subdomain = a - elif o == "-e": - if email != None: - print("Can specify maximum ONE email.") - else: - email = a - elif o == "-c": - conf_file = a - elif o == "-u": - v_uuid = a - elif o == "-s": - v_path = a - - # overwrite stuff with conf file - conf = read_conf(conf_file) - - if "d" in conf: - domain = conf["d"] - if "s" in conf: - subdomain = conf["s"] - if "e" in conf: - email = conf["e"] - if "u" in conf: - v_uuid = conf["u"] - if "p" in conf: - v_path = conf["p"] - - # generate settings if not specified - if v_uuid == None: - v_uuid = uuid.uuid4() - - if v_path == None: - v_path = randomString() - - if domain == None: - print("Must specify a domain.") - sys.exit(1) - - server_name = None - if subdomain == None: - server_name = domain - else: - server_name = subdomain + "." + domain - - # process docker-compose - with open("docker-compose.yml", "r") as file: - template = jinja2.Template(file.read()) - - output = template.render(uid = uid, gid = gid, domain = domain, \ - subdomain = (subdomain if subdomain != None else ""), \ - only_sub = ("true" if subdomain != None else "false"), \ - email = ("dummy@dummy.com" if email == None else email)) - - with open("docker-compose.yml", "w") as file: - file.write(output) - - # process v2ray/config - with open("v2ray/config.json", "r") as file: - template = jinja2.Template(file.read()) - - output = template.render(uuid = v_uuid, path = v_path) - - with open("v2ray/config.json", "w") as file: - file.write(output) - - # process nginx/nginx/site-confs/default - with open("nginx/nginx/site-confs/default", "r") as file: - template = jinja2.Template(file.read()) - - output = template.render(server_name = server_name, path = v_path) - - with open("nginx/nginx/site-confs/default", "w") as file: - file.write(output) - - # process client.json - with open("client.json", "r") as file: - template = jinja2.Template(file.read()) - - output = template.render(uuid = v_uuid, path = v_path, server_name = server_name) - - with open("client.json", "w") as file: - file.write(output) - - print("Processed all files. The detailed client config is written to client.conf.\n" + \ - " Summary:\n" + \ - " Server Address: " + server_name + "\n" \ - " Path: " + v_path + "\n" \ - " UUID: " + str(v_uuid) + "\n" \ - "Please run docker-compose up -d to start the service.") - -main() diff --git a/watchtower.in b/watchtower.in new file mode 100644 index 0000000..b6bcaf5 --- /dev/null +++ b/watchtower.in @@ -0,0 +1,8 @@ + watchtower: + container_name: v2ray_watchtower + image: containrrr/watchtower + restart: unless-stopped + networks: + - br-v2ray + volumes: + - /var/run/docker.sock:/var/run/docker.sock