diff --git a/Dockerfile b/Dockerfile index 9e40424..0cd1790 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM alpine:latest ENV VER_XRAY 1.8.3 # install packages -RUN set -xe && apk add --no-cache unzip wget openssl python3 py3-jinja2 supervisor apache2-utils bash +RUN set -xe && apk add --no-cache unzip wget openssl python3 py3-jinja2 supervisor apache2-utils bash libqrencode # download packages RUN set -xe && \ @@ -14,11 +14,6 @@ RUN set -xe && \ COPY ./opt /opt/ -# nginx -# RUN set -xe && addgroup www && \ -# adduser -H -D -S -s /bin/false www -G www && \ -# chown -R www:www /opt/nginx - # remove packages RUN set -xe && apk del unzip wget diff --git a/README.md b/README.md index 3037781..f95ebca 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ d2ray is a single Docker container that provides easy 5-minute setups and braind ## Quickstart 1. You can start with the example `docker-compose.yml` from this repo. 2. Adjust environment variables: + - `HOST`: the hostname/IP of the server. `REQUIRED`. - `PORT`: the port Xray listens on. `Optional, default = 443`. - `TARGET_HOST`: the target host to redirect non proxy connections. `Required`. - `TARGET_PORT`: the target port to redirect non proxy connections. `Optional, default = 443`. @@ -14,13 +15,14 @@ d2ray is a single Docker container that provides easy 5-minute setups and braind - `USERS`: comma separated list of usernames that can access Xray. `Required`. - `LOG_LEVEL`: the verbosity of Xray logs. `Optional, default = warn`. 3. `docker compose up -d` -4. Test your connection. +4. Check the container log using `docker logs` for per user shareable links and QR codes supported by most Xray apps. These can also be found under `/etc/xray/users/[USERNAME]` folders. +5. Test your connection. ## Docker Volume The logs and private key are stored in `/etc/d2ray` in the container. You can mount an external folder to that location to persist settings. Otherwise d2ray creates an anonymous Docker volume. ## Key Generation -If `PRIVATE_KEY` is provided, d2ray uses that key. Otherwise, d2ray generates a new key pair and persists it in `/etc/xray/certs/keys`. The corresponding public key is always printed to the container log (`docker logs`), which clients use to connect. +If `PRIVATE_KEY` is provided, d2ray uses that key. Otherwise, d2ray generates a new key pair and persists it in `/etc/xray/certs/keys`. The corresponding public key is always printed to the container log, which clients use to connect. To make d2ray regenerate a new key pair, manually delete the key file `/etc/xray/certs/keys` from the mounted volume. diff --git a/docker-compose.yml b/docker-compose.yml index b439870..c062d8f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,7 @@ services: ports: - 8443:8443 environment: + - HOST=myvps.com - PORT=8443 - TARGET_HOST=www.apple.com - TARGET_PORT=443 diff --git a/opt/init.py b/opt/init.py index d1b07f0..196d5fa 100644 --- a/opt/init.py +++ b/opt/init.py @@ -2,15 +2,18 @@ import os import subprocess import jinja2 import random +import sys import string import pathlib CONFIG_DIR = pathlib.Path("/etc/d2ray") KEY_FILE = CONFIG_DIR.joinpath("certs/keys") LOG_DIR = CONFIG_DIR.joinpath("logs") +QR_DIR = CONFIG_DIR.joinpath("users") XRAY_BIN = pathlib.Path("/opt/xray/xray") class d2args: + host : str port : int target_port : int target_host : str @@ -52,6 +55,7 @@ class d2args: return (skey.strip(), pkey.strip()) def _from_env(self) -> None: + self.host = self._get_env("HOST") self.target_host = self._get_env("TARGET_HOST") self.target_sni = self._get_env("TARGET_SNI").split(",") self.users = self._get_env("USERS").split(",") @@ -76,7 +80,8 @@ class d2args: _ , self.public_key = self._parse_xray_x25519_output(subprocess.check_output(f"{XRAY_BIN} x25519 -i {self.private_key}", shell = True).decode()) def __str__(self) -> str: - ret = (f"Port: {self.port}\n" + ret = (f"Host: {self.host}\n" + f"Port: {self.port}\n" f"Target Port: {self.target_port}\n" f"Target Host: {self.target_host}\n" f"Target SNI: {', '.join(self.target_sni)}\n" @@ -85,6 +90,19 @@ class d2args: f"Public Key: {self.public_key}" ) return ret + + def get_shareable_links(self) -> dict[str, str]: + ret = {} + for user in self.users: + ret[user] = (f"vless://{user}@{self.host}:{self.port}/?" + "flow=xtls-rprx-vision&" + "type=tcp&security=reality&" + "fp=chrome&" + f"sni={','.join(self.target_sni)}&" + f"pbk={self.public_key}#" + f"{self.host}" + ) + return ret def process_directory(path : str, vars : dict[str, str], delete_template : bool = True) -> None: for f in os.listdir(path): @@ -133,6 +151,22 @@ def main(): print(f"Processing config files...", flush=True) process_directory("/opt/xray", template) + print(f"Generating shareable links...", flush=True) + links = args.get_shareable_links() + + for user, link in links.items(): + dir = QR_DIR.joinpath(user) + os.makedirs(str(dir), exist_ok=True) + linkf = dir.joinpath("link.txt") + with open(str(linkf), "w") as f: + f.write(link + "\n") + subprocess.check_output(f"qrencode -o {str(dir.joinpath('qrcode.png'))} < {linkf}", shell=True) + print("") + print(f"User \"{user}\":", flush=True) + print(f"{link}") + print(subprocess.check_output(f"qrencode -t ansiutf8 < {linkf}", shell=True).decode()) + print("") + print(f"Initialization completed.\n", flush=True) main() diff --git a/opt/init.sh b/opt/init.sh index 33e5a27..784d534 100644 --- a/opt/init.sh +++ b/opt/init.sh @@ -3,6 +3,7 @@ mkdir -p /etc/d2ray/logs/xray mkdir -p /etc/d2ray/logs/supervisord mkdir -p /etc/d2ray/certs +rm -rf /etc/d2ray/users python3 /opt/init.py retval=$?