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

Lorem ipsum dolor sit, amet consectetur adipisicing elit

Optio, beatae! Aut quis id voluptate ullam repellendus. Et sit, ipsa, non consequuntur magnam quaerat temporibus at officiis ab, expedita molestiae liber...

- + + + +

diff --git a/opt/nginx/download/android/配置教程.txt b/opt/user/android/配置教程.txt.in similarity index 63% rename from opt/nginx/download/android/配置教程.txt rename to opt/user/android/配置教程.txt.in index 0c50867..ca0ad56 100644 --- a/opt/nginx/download/android/配置教程.txt +++ b/opt/user/android/配置教程.txt.in @@ -3,10 +3,10 @@ 3. 右上角箭头->新加配置文件->手动输入[VLess] 别名:随便起 -地址:服务器地址(就是当前网站的地址,比如你在https://xxx.quacker.net/download你的服务器地址就是xxx.quacker.net) -端口:443 -用户id:你的密码 -流控(flow):xtls-rprx-direct +地址:{{ FQDN }} +端口:{{ PORT }} +用户id:{{ USER }} +流控(flow):{{ FLOW }} 传输协议:tcp 伪装类型:none 底层传输安全:xtls @@ -16,4 +16,4 @@ 以上没有提到的选项统统默认值 4. 右上角对勾保存 -5. 选择刚创建的链接,点击右下角"V"开启或关闭VPN. \ No newline at end of file +5. 选择刚创建的链接,点击右下角"V"开启或关闭VPN. diff --git a/opt/user/ios/配置教程.txt.in b/opt/user/ios/配置教程.txt.in new file mode 100644 index 0000000..1cb962b --- /dev/null +++ b/opt/user/ios/配置教程.txt.in @@ -0,0 +1,16 @@ +1. 去AppStore下载shadowrocket软件(需要非中国区) +2. 打开shadowrocket app +3. 右上角+号 + +类型:VLESS +地址:{{ FQDN }} +端口:{{ PORT }} +UUID:{{ USER }} +TLS: 开启 +XTLS Direct: 开启 +快速打开: 开启 + +以上没有提到的选项统统默认值 + +4. 右上角完成保存 +5. 返回主页面,选择刚创建的链接,然后开启链接即开启vpn \ No newline at end of file diff --git a/opt/zip/macos/config.json b/opt/user/others/config.json.in similarity index 91% rename from opt/zip/macos/config.json rename to opt/user/others/config.json.in index b05f251..14abe90 100644 --- a/opt/zip/macos/config.json +++ b/opt/user/others/config.json.in @@ -82,13 +82,13 @@ "settings": { "vnext": [ { - "address": "%SERVER%", - "port": 443, + "address": "{{ FQDN }}", + "port": {{ PORT }}, "users": [ { - "id": "%PASSWORD%", + "id": "{{ USER }}", "encryption": "none", - "flow": "xtls-rprx-direct", + "flow": "{{ FLOW }}", "level": 0 } ] @@ -99,7 +99,7 @@ "network": "tcp", "security": "xtls", "xtlsSettings": { - "serverName": "%SERVER%", + "serverName": "{{ FQDN }}", "allowInsecure": false, "alpn": ["h2","http/1.1"] } diff --git a/opt/zip/macos/run.sh b/opt/user/others/macos.sh similarity index 100% rename from opt/zip/macos/run.sh rename to opt/user/others/macos.sh diff --git a/opt/zip/windows/run.bat b/opt/user/others/windows.bat similarity index 100% rename from opt/zip/windows/run.bat rename to opt/user/others/windows.bat diff --git a/opt/user/others/配置教程.txt b/opt/user/others/配置教程.txt new file mode 100644 index 0000000..e59b9ee --- /dev/null +++ b/opt/user/others/配置教程.txt @@ -0,0 +1,55 @@ +分两步: +1. 配置VPN程序,此程序负责与VPN服务器交流 +2. 配置浏览器,让浏览器把所有网站请求交给VPN程序处理 + +**** 1. 配置 VPN程序 **** +1. 请在downloads目录里下载与系统相适应的程序和配置文件: + - Windows 64 bits (Intel CPU): Xray-windows-64.zip + config.json + windows.bat + - MacOS 64 bits (Intel CPU): Xray-macos-64.zip + config.json + macos.sh + - MacOS ARM (M Series CPU): Xray-macos-arm64-v8a.zip + config.json + macos.sh + +2. 如果仅需要浏览器联网,请下载 Google Chrome 浏览器和 downloads 目录里的 SwitchyOmega_Chromium.crx + 如果需要全局代理(例如除浏览器外其他应用程序),请先完成 Google Chrome 的配置,然后从Google下载 Proxifier 后请看 "配置全局代理" 部分 + +3. 解压缩下载的zip文件,将 config.json 和 windows.bat 或 macos.sh 拷贝到解压缩后的目录下 + +4. 双击 windows.bat 或 macos.sh 即可运行VPN程序,MacOS系统有可能需要右键 macos.sh => 用应用程序打开(Open With) => 其他(Other) => 在新打开的窗口下方选择框里选"所有应用程序"(All Applications) => 找到 终端"Terminal" 并勾选 永久以程序打开(Always Open With) => 确定 + + +**** 2. 配置 Google Chrome **** +1. 安装下载的SwitchyOmega插件: +- 打开 Google Chrome +- 打开网址 "chrome://extensions" +- 打开右上角 开发者模式(Developer mode) +- 在新出现的工具栏选择第一项 "Load unpacked"(加载插件) +- 在新出现的对话框选择下载的 SwitchyOmega_Chromium.crx + +2. 配置SwitchyOmega插件: +- 打开SwitchyOmega插件配置菜单(浏览器右上角点击SwitchyOmega图标然后点最后一项配置,没有的话在插件下拉里面找,找到可以Pin到浏览器上). +- 在左边菜单新建一个profile, 类型选择第一个"Proxy(代理) Profile",名字取"VPN", 点击 “创建” +- Protocol(协议)选择"SOCKS5", 服务器(Server)填写127.0.0.1, 端口(Port)写1080,点击左边菜单"应用" (Apply changes) + + +**** 3. 配置全局代理 (Optional) **** +!! 此部分仅适用需要全局代理的用户,在配置全局代理前请先配置好Google Chrome !! +1. 在配置好的 Google Chrome 上访问 https://www.proxifier.com/,下载并安装 ("Download Proxifier 31-day Free Trial") + +2. 打开Proxifier。点击左上角菜单 "Profile" -> "Proxy Servers" -> "Add" + - Address 填写 127.0.0.1 + - Port 填写 1080 + - Protocol 选 SOCKS Version 5 + 然后点OK + +3. 窗口 "Do you want Proxifier to use this proxy by default?" -> Yes,然后点OK + +4. 重新启动 Proxifier 即可启动全局代理,第一次运行 Proxifier 会询问是否将 Xray.exe 加入 Exclusion,选Yes即可。 + + +**** 4. 注意事项 **** +1. 启动VPN时请启动VPN程序并且 在SwitchyOmega插件菜单选择 "VPN" 或者打开Proxifier + +2. 关闭VPN时请 当不需要VPN时,只需在SwitchyOmega插件选择"Direct"直连。 + +3. 不需要用VPN时请关闭VPN,降低被ban的概率。 + +4. 请每过一段时间(~ 1-3 months)从本站下载最新的VPN软件并重新配置,VPN软件的bug修复和新的协议会降低被ban的概率。 \ No newline at end of file diff --git a/opt/nginx/download/配置说明.txt b/opt/user/请读我.txt similarity index 100% rename from opt/nginx/download/配置说明.txt rename to opt/user/请读我.txt diff --git a/opt/xray/d2ray.json.in b/opt/xray/d2ray.json.in index 54995a8..b62756f 100644 --- a/opt/xray/d2ray.json.in +++ b/opt/xray/d2ray.json.in @@ -10,7 +10,7 @@ "protocol": "vless", "settings": { "clients": [ - {{ USERS }} + {{ XRAY_USERS }} ], "decryption": "none", "fallbacks": [ diff --git a/opt/zip/windows/config.json b/opt/zip/windows/config.json deleted file mode 100644 index b05f251..0000000 --- a/opt/zip/windows/config.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "dns": { - "servers": [ - "223.5.5.5", - "114.114.114.114", - { - "address": "8.8.8.8", - "port": 53, - "domains": [ - "geosite:geolocation-!cn" - ] - }, - { - "address": "1.1.1.1", - "port": 53, - "domains": [ - "geosite:geolocation-!cn" - ] - } - ] - }, - - "routing": { - "domainStrategy": "IPIfNonMatch", - "rules": [ - { - "type": "field", - "outboundTag": "direct", - "ip": [ - "223.5.5.5", - "114.114.114.114" - ] - }, - { - "type": "field", - "outboundTag": "proxy", - "ip": [ - "8.8.8.8", - "1.1.1.1" - ] - }, - { - "type": "field", - "outboundTag": "direct", - "ip": [ - "geoip:cn", - "geoip:private" - ] - }, - { - "type": "field", - "outboundTag": "direct", - "domain": ["geosite:cn"] - }, - { - "type": "field", - "outboundTag": "proxy", - "network": "udp,tcp" - } - ] - }, - - "inbounds": [ - { - "port": 1080, - "listen": "127.0.0.1", - "protocol": "socks", - "sniffing": { - "enabled": true, - "destOverride": ["http", "tls"] - }, - "settings": { - "auth": "noauth", - "udp": false - } - } - ], - "outbounds": [ - { - "tag": "proxy", - "protocol": "vless", - "settings": { - "vnext": [ - { - "address": "%SERVER%", - "port": 443, - "users": [ - { - "id": "%PASSWORD%", - "encryption": "none", - "flow": "xtls-rprx-direct", - "level": 0 - } - ] - } - ] - }, - "streamSettings": { - "network": "tcp", - "security": "xtls", - "xtlsSettings": { - "serverName": "%SERVER%", - "allowInsecure": false, - "alpn": ["h2","http/1.1"] - } - } - }, - { - "protocol": "freedom", - "settings": {}, - "tag": "direct" - }, - { - "protocol": "blackhole", - "settings": {}, - "tag": "block" - } - ] -} \ No newline at end of file diff --git a/opt/zip/配置教程.txt b/opt/zip/配置教程.txt deleted file mode 100644 index 5b472c6..0000000 --- a/opt/zip/配置教程.txt +++ /dev/null @@ -1,53 +0,0 @@ -VPN陪置教程 - -分两步: -1. 配置VPN程序,此程序负责与VPN服务器交流 -2. 配置浏览器,让浏览器把所有网站请求交给VPN程序处理 -很简单明了,成功的VPN以上配置缺一不可 - -!! 以下所有路径都是相对于下载的zip文件的解压目录 !! -!! 比如zip文件解压到了D:\vpn,文中说的windows目录就是D:\vpn\windows !! - - -**** 1. 配置 VPN程序 **** -Windows系统请看这里: -- 进入当前文件夹 windows 目录 -- 解压 Xray-windows-64.zip -- 用文本编辑器打开 config.json,搜索并替换 *所有* (划重点*所有*): - %PASSWORD% => 你的密码. 比如密码是12345的话 文件中所有的 "%PASSWORD%" 应该替换成 "12345", 以下同理 - %SERVER% => 你的服务器地址(这个是你下载zip文件的地址,比如你从https://xxx.quacker.net/download下载你的服务器地址就是xxx.quacker.net) -- 保存 config.json -- 拷贝 config.json 和 run.bat 进入解压后的文件夹 -- 进入解压后文件夹双击 run.bat 运行VPN程序 - -MacOS系统请看这里: -- 进入当前文件夹 macos 目录 -- 解压 Xray-macos-64.zip 或者 Xray-macos-arm64-v8a.zip (Apple M系芯片请用arm64) -- 用文本编辑器打开 config.json,搜索并替换 *所有* (划重点*所有*): - %PASSWORD% => 你的密码(和你的网站下载密码相同). 比如密码是12345的话 文件中所有的 "%PASSWORD%" 应该替换成 "12345", 以下同理 - %SERVER% => 你的服务器地址(这个是你下载zip文件的地址,比如你从https://xxx.quacker.net/download下载你的服务器地址就是xxx.quacker.net) -- 保存 config.json -- 右键 run.sh => 用应用程序打开(Open With) => 其他(Other) => 在新打开的窗口下方选择框里选"所有应用程序"(All Applications) => 找到 终端"Terminal" 并勾选 永久以程序打开(Always Open With) => 确定 -- 拷贝 config.json 和 run.sh 进入解压后的文件夹 -- 进入解压后文件夹双击 run.sh 运行VPN程序 - - -**** 2. 配置 浏览器 **** -首先请下载Google Chrome浏览器 - -然后需要安装SwitchyOmega插件: -- 打开 Google Chrome -- 打开网址 "chrome://extensions" -- 打开右上角 开发者模式(Developer mode) -- 在新出现的工具栏选择第一项 "Load unpacked"(加载插件) -- 在新出现的对话框选择chrome目录 - -然后配置SwitchyOmega插件: -- 打开SwitchyOmega插件配置菜单(浏览器右上角点击SwitchyOmega图标然后点最后一项配置,没有的话在插件下拉里面找,找到可以Pin到浏览器上). -- 在左边菜单新建一个profile, 类型选择第一个"Proxy(代理) Profile",名字取"VPN", 点击 “创建” -- Protocol(协议)选择"SOCKS5", 服务器(Server)填写127.0.0.1, 端口(Port)写1080,点击左边菜单"应用" (Apply changes) - - -**** 3. 注意事项 **** -当需要VPN时,1.启动VPN程序并且 2.在SwitchyOmega插件菜单选择"VPN"。 -当不需要VPN时,只需在SwitchyOmega插件选择"Direct"直连。 \ No newline at end of file