diff --git a/.env b/.env index 358b5ff..99771d9 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ PORT=443 FQDN=example.com -USERS=exampleuser1,exampleuser2 +USERS=exampleuser1,exampleuser2@xtls-rprx-direct LOGDIR=./logs \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index e695a8a..1ba22dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,29 +5,22 @@ ENV VER_SO 2.5.20 ENV VER_NG 1.7.23 # install packages -RUN set -xe && apk add --no-cache zip unzip wget nginx certbot openssl python3 py3-jinja2 supervisor apache2-utils bash - -COPY ./opt /opt/ +RUN set -xe && apk add --no-cache unzip wget nginx certbot openssl python3 py3-jinja2 supervisor apache2-utils bash # download packages RUN set -xe && \ - wget -P /opt/zip/windows/ https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-windows-64.zip && \ - mkdir -p /opt/zip/linux && \ - wget -P /opt/zip/linux/ https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-linux-64.zip && \ - mkdir -p /opt/zip/chrome && \ - wget -P /opt/zip/chrome/ https://github.com/FelisCatus/SwitchyOmega/releases/download/v$VER_SO/SwitchyOmega_Chromium.crx && \ - wget -P /opt/nginx/download/android/ https://github.com/2dust/v2rayNG/releases/download/$VER_NG/v2rayNG_"$VER_NG"_arm64-v8a.apk && \ - wget -P /opt/zip/macos/ https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-macos-64.zip && \ - wget -P /opt/zip/macos/ https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-macos-arm64-v8a.zip + mkdir -p /downloads /downloads/others /downloads/android && \ + wget -P /downloads/others https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-windows-64.zip && \ + wget -P /downloads/others https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-linux-64.zip && \ + wget -P /downloads/others https://github.com/FelisCatus/SwitchyOmega/releases/download/v$VER_SO/SwitchyOmega_Chromium.crx && \ + wget -P /downloads/android https://github.com/2dust/v2rayNG/releases/download/$VER_NG/v2rayNG_"$VER_NG"_arm64-v8a.apk && \ + wget -P /downloads/others https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-macos-64.zip && \ + wget -P /downloads/others https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-macos-arm64-v8a.zip + +COPY ./opt /opt/ # xray -RUN set -xe && unzip /opt/zip/linux/Xray-linux-64.zip -d /opt/xray - -# create zip -RUN set -xe && \ - zip -r /opt/d2ray.zip /opt/zip && \ - mv /opt/d2ray.zip /opt/nginx/download/ && \ - rm -r /opt/zip +RUN set -xe && unzip /downloads/others/Xray-linux-64.zip -d /opt/xray # nginx RUN set -xe && addgroup www && \ @@ -35,7 +28,7 @@ RUN set -xe && addgroup www && \ chown -R www:www /opt/nginx # remove packages -RUN set -xe && apk del zip unzip wget +RUN set -xe && apk del unzip wget EXPOSE 80 VOLUME /etc/letsencrypt CMD ["sh", "/opt/init.sh"] \ No newline at end of file diff --git a/README.md b/README.md index 9a91f3b..6977f7e 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ d2ray is a single Docker container that provides easy and braindead configuratio - Easy 5-minutes setup. - Automatic generation and renewal of Let's Encrypt SSL certificates. - Packaged Xray binary on the fallback website. +- Per-user setup instructions for various architectures. ## How to use? 1. Download the `docker-compose.yml` from this repo. @@ -14,10 +15,10 @@ d2ray is a single Docker container that provides easy and braindead configuratio - See `.env` in the current repo. - `PORT`: the port to run Xray on. - `FQDN`: the domain name of your server, used to generate SSL certificates. - - `USERS`: comma separated list of users allowed access to both Xray and resource downloads. + - `USERS`: comma separated list of `USERCONF` allowed access to both Xray and resource downloads. Each `USERCONF` is of format `userid@flow`. `userid` is used as the credential for Xray. If `flow` is not specified it defaults to `xtls-rprx-direct`. For example, setting `USERS` to `user1@xtls-rprx-direct,user2` means two users with userid `user1` and `user2` and both with flow `xtls-rprx-direct`. - `LOGDIR`: the directory to store logs, currently required. 3. `docker compose up -d` -4. You can access the Xray service using an Xray client. You can access the resource downloads by accessing `https://your-domain:your-port` and clicking the `Download` button near the bottom of the page. Use one of the `USERS` as the username and password. +4. You can access the Xray service using an Xray client. You can access the per-user resource downloads by accessing `https://your-domain:your-port`, entering the `userid` in the textbox at the bottom of the page and clicking the `Download` button next to it. ## How to update? - `docker compose down` diff --git a/opt/init.py b/opt/init.py index f589d5f..7d4f0c7 100644 --- a/opt/init.py +++ b/opt/init.py @@ -6,18 +6,59 @@ import jinja2 import random import string -def parse_comma_str(users : str) -> list[str]: - return users.split(",") +def process_directory(path : str, vars : dict[str, str], delete_template : bool = True) -> None: + for f in os.listdir(path): + full_path = os.path.join(path, f) + if os.path.isdir(full_path): + process_directory(full_path, vars, delete_template) + elif f.endswith(".in"): + with open(full_path, "r") as sf: + with open(full_path[:-3], "w") as df: + template : jinja2.Template = jinja2.Template(sf.read()) + df.write(template.render(**vars)) + print(f"Processed template {full_path}.", flush=True) + if delete_template: + subprocess.check_call(f"rm {full_path}", shell=True) -def build_users_json(users: list[str]) -> str: + +def parse_user_flow(s : str) -> list[tuple[str,str]]: + users = [] + userconfs : list[str] = s.split(",") + for userconf in userconfs: + if len(userconf) == 0: + continue + ele = userconf.split("@") + username = ele[0] + if (len(ele) > 1): + flow = ele[1] + else: + flow = "xtls-rprx-direct" + users.append((username, flow)) + return users + + +def build_users_json(users: list[tuple[str, str]]) -> str: ret : str= "" for i in range(len(users)): if (i > 0): ret = ret + "," - u = users[i] - ret = ret + "{ \"id\": \"" + u + "\", \"flow\": \"xtls-rprx-direct\"}" + ret = ret + "{\"id\": \"" + users[i][0] + "\",\"flow\": \"" + users[i][1] + "\"}" return ret +NGINX_LOCATION_TEMPLATE = ''' + location /{{ USER }} { + root /opt/nginx; + autoindex on; + } +''' + +def build_nginx_locations(users: list[tuple[str, str]]) -> str: + ret = "" + for user in users: + ret += jinja2.Environment().from_string(NGINX_LOCATION_TEMPLATE).render(USER = user[0]) + "\n" + return ret + + try: opts, _ = getopt.getopt(sys.argv[1:], "u:p:f:") except getopt.GetoptError as err: @@ -31,7 +72,7 @@ fqdn : str = "example.com" for o, a in opts: if o == "-u": - users = parse_comma_str(a) + users = parse_user_flow(a) elif o == "-p": port = int(a) elif o == "-f": @@ -39,6 +80,7 @@ for o, a in opts: else: print(f"Unknown option {o}, ignoring...", flush=True) exit(1) + print("====== init.py ======", flush=True) print("Configuration:\n" + \ f" port = {port}\n" + \ @@ -54,22 +96,27 @@ else: subprocess.check_call(f"certbot certonly -n --standalone -m dummy@dummy.com --agree-tos --no-eff-email -d {fqdn}", shell=True) jinja_dict : dict[str,str] = dict() -jinja_dict["USERS"] = build_users_json(users) +jinja_dict["XRAY_USERS"] = build_users_json(users) jinja_dict["PORT"] = str(port) jinja_dict["FQDN"] = str(fqdn) +jinja_dict["NGINX_LOCATIONS"] = build_nginx_locations(users) print(f"Processing Xray config files...", flush=True) -with open("/opt/xray/d2ray.json.in", "r") as f: - with open("/opt/xray/d2ray.json", "w") as d: - template : jinja2.Template = jinja2.Template(f.read()) - d.write(template.render(**jinja_dict)) +process_directory("/opt/xray", jinja_dict) print(f"Processing Nginx config files...", flush=True) -with open("/opt/nginx/nginx.conf.in", "r") as f: - with open("/opt/nginx/nginx.conf", "w") as d: - template : jinja2.Template = jinja2.Template(f.read()) - d.write(template.render(**jinja_dict)) +process_directory("/opt/nginx", jinja_dict) + for u in users: - subprocess.check_call(f"htpasswd -b /opt/nginx/.htpasswd {u} {u}", shell=True) + print(f"Preparing directory for user {u[0]}...", flush=True) + user_dir = f"/opt/nginx/{u[0]}" + subprocess.check_call(f"mkdir -p {user_dir}", shell=True) + subprocess.check_call(f"cp -r /opt/user/* {user_dir}/", shell=True) + + jinja_dict["USER"] = u[0] + jinja_dict["FLOW"] = u[1] + process_directory(user_dir, jinja_dict) + subprocess.check_call(f"ln -s /downloads/others {user_dir}/others/downloads", shell=True) + subprocess.check_call(f"ln -s /downloads/android {user_dir}/android/downloads", shell=True) exit(0) \ No newline at end of file diff --git a/opt/nginx/.htpasswd b/opt/nginx/.htpasswd deleted file mode 100644 index e69de29..0000000 diff --git a/opt/nginx/nginx.conf.in b/opt/nginx/nginx.conf.in index 7ce07fe..a754881 100644 --- a/opt/nginx/nginx.conf.in +++ b/opt/nginx/nginx.conf.in @@ -40,11 +40,6 @@ http { index index.html; } - location /download { - root /opt/nginx/; - autoindex on; - auth_basic "Please provide credentials to access this server"; - auth_basic_user_file "/opt/nginx/.htpasswd"; - } + {{ NGINX_LOCATIONS }} } } diff --git a/opt/nginx/webroot/index.html b/opt/nginx/webroot/index.html index ff68e07..f63de70 100644 --- a/opt/nginx/webroot/index.html +++ b/opt/nginx/webroot/index.html @@ -10,7 +10,18 @@ FEATURED
Optio, beatae! Aut quis id voluptate ullam repellendus. Et sit, ipsa, non consequuntur magnam quaerat temporibus at officiis ab, expedita molestiae liber...
- + + + +