This commit is contained in:
parent
63e83e3275
commit
e6c6e90619
21
README.md
21
README.md
@ -6,24 +6,23 @@ 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:
|
||||
- `PORT`: the port Xray listens on.
|
||||
- `TARGET_HOST`: the target host to redirect non proxy connections.
|
||||
- `TARGET_PORT`: the target port to redirect non proxy connections.
|
||||
- `TARGET_SNI`: comma separated list of the target website's SNIs.
|
||||
- `USERS`: comma separated list of usernames that can access Xray.
|
||||
- `LOG_LEVEL`: the verbosity of Xray logs. Default: `warn`.
|
||||
- `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`.
|
||||
- `TARGET_SNI`: comma separated list of the target website's SNIs. `Required`.
|
||||
- `PRIVATE_KEY` : server's private key. `Optional`.
|
||||
- `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.
|
||||
|
||||
## Docker Volume
|
||||
All d2ray logs and private/public key pairs are stored in `/etc/d2ray` in the container. You can mount an external folder to that location to persist settings. See the example `docker-compose.yml`.
|
||||
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
|
||||
d2ray checks whether a key file exists at path `/etc/xray/certs/keys` and generates a new key pair if not found.
|
||||
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.
|
||||
|
||||
You can either supply a pre-generated private key using `xray x25519` or let d2ray generate one. The corresponding public key is printed to the container log (`docker logs`), which clients use to connect.
|
||||
|
||||
If you are generating the keys yourself, the key file must contain exactly the output of `xray x25519`.
|
||||
To make d2ray regenerate a new key pair, manually delete the key file `/etc/xray/certs/keys` from the mounted volume.
|
||||
|
||||
## How To Update?
|
||||
- `docker compose down`
|
||||
|
@ -10,10 +10,11 @@ services:
|
||||
- 8443:8443
|
||||
environment:
|
||||
- PORT=8443
|
||||
- TARGET_HOST=example.com
|
||||
- TARGET_HOST=www.apple.com
|
||||
- TARGET_PORT=443
|
||||
- TARGET_SNI=www.example.com,example.com
|
||||
- USERS=exampleuser1,exampleuser2
|
||||
- TARGET_SNI=www.apple.com,apple.com
|
||||
- USERS=alice,bob,eve
|
||||
- PRIVATE_KEY=KE5MCI5e395fub55O1lsNPzvWw9nNAyCaecRSp3BvHg # Do NOT use this random key
|
||||
- LOG_LEVEL=warn
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
|
130
opt/init.py
130
opt/init.py
@ -16,51 +16,76 @@ class d2args:
|
||||
target_host : str
|
||||
target_sni : str
|
||||
log_level : str
|
||||
private_key : str
|
||||
public_key : str
|
||||
users : list[str]
|
||||
def __init__(self) -> None:
|
||||
self.port = 443
|
||||
self.target_host = "localhost"
|
||||
self.target_port = 443
|
||||
self.target_sni = "localhost"
|
||||
self.log_level = "warn"
|
||||
self.users = [''.join(random.choices(string.ascii_uppercase + string.digits, k=24))]
|
||||
self._from_env()
|
||||
|
||||
def from_env(self) -> None:
|
||||
env = os.getenv("PORT")
|
||||
if env != None:
|
||||
self.port = int(env)
|
||||
@staticmethod
|
||||
def _get_env(name : str, default : str = None, required : bool = True) -> str:
|
||||
env = os.getenv(name)
|
||||
if env == None:
|
||||
if required:
|
||||
raise Exception(f"Missing environment variable \"{name}\".")
|
||||
else:
|
||||
return default
|
||||
return env
|
||||
|
||||
env = os.getenv("TARGET_PORT")
|
||||
if env != None:
|
||||
self.target_port = int(env)
|
||||
|
||||
env = os.getenv("TARGET_SNI")
|
||||
if env != None:
|
||||
self.target_sni = env.split(",")
|
||||
@staticmethod
|
||||
def _parse_xray_x25519_output(stdout : str) -> tuple[str, str]:
|
||||
skey = None
|
||||
pkey = None
|
||||
lines = stdout.split("\n")
|
||||
if len(lines) < 2:
|
||||
raise Exception(f"Unknown Xray output format:\n\"{stdout}\"\n")
|
||||
|
||||
env = os.getenv("TARGET_HOST")
|
||||
if env != None:
|
||||
self.target_host = env
|
||||
priv_key_hdr = "Private key: "
|
||||
pub_key_hdr = "Public key: "
|
||||
for line in lines:
|
||||
if line.startswith(priv_key_hdr):
|
||||
skey = line[len(priv_key_hdr):]
|
||||
elif line.startswith(pub_key_hdr):
|
||||
pkey = line[len(pub_key_hdr):]
|
||||
if (skey == None) or (pkey == None):
|
||||
raise Exception(f"Unable to extract private or public key from Xray output:\n\"{stdout}\"\n")
|
||||
return (skey.strip(), pkey.strip())
|
||||
|
||||
env = os.getenv("LOG_LEVEL")
|
||||
if env != None:
|
||||
self.log_level = env
|
||||
def _from_env(self) -> None:
|
||||
self.target_host = self._get_env("TARGET_HOST")
|
||||
self.target_sni = self._get_env("TARGET_SNI").split(",")
|
||||
self.users = self._get_env("USERS").split(",")
|
||||
|
||||
env = os.getenv("USERS")
|
||||
if env != None:
|
||||
self.users = env.split(",")
|
||||
self.port = int(self._get_env("PORT", default="443", required=False))
|
||||
self.target_port = int(self._get_env("TARGET_PORT", default="443", required=False))
|
||||
self.log_level = self._get_env("LOG_LEVEL", default="warn", required=False)
|
||||
|
||||
def __str__(self):
|
||||
self.private_key = self._get_env("PRIVATE_KEY", default=None, required=False)
|
||||
if (self.private_key == None):
|
||||
print(f"Private key not provided.", flush=True)
|
||||
if not KEY_FILE.exists():
|
||||
print(f"Key file {KEY_FILE} not found. Generating new keys...")
|
||||
self.private_key, _ = self._parse_xray_x25519_output(subprocess.check_output(f"{XRAY_BIN} x25519", shell = True).decode())
|
||||
with open(KEY_FILE, "w") as f:
|
||||
f.write(self.private_key)
|
||||
else:
|
||||
print(f"Reading from key file {KEY_FILE} ...")
|
||||
with open(KEY_FILE, "r") as f:
|
||||
self.private_key = f.read().strip()
|
||||
|
||||
_ , 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"
|
||||
f"Target Port: {self.target_port}\n"
|
||||
f"Target Host: {self.target_host}\n"
|
||||
f"Target SNI: {', '.join(self.target_sni)}\n"
|
||||
f"Log Level: {self.log_level}\n"
|
||||
f"Users: {', '.join(self.users)}"
|
||||
f"Users: {', '.join(self.users)}\n"
|
||||
f"Public Key: {self.public_key}"
|
||||
)
|
||||
return ret
|
||||
|
||||
|
||||
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)
|
||||
@ -81,7 +106,7 @@ def build_target_snis(snis : list[str]) -> str:
|
||||
def build_users_json(users: list[str]) -> str:
|
||||
return ', '.join(["{\"id\": \"" + item + "\", \"flow\": \"xtls-rprx-vision\"}" for item in users])
|
||||
|
||||
def build_jinja_dict(args : d2args, skey : str) -> dict[str, str]:
|
||||
def build_jinja_dict(args : d2args) -> dict[str, str]:
|
||||
jinja_dict : dict[str,str] = dict()
|
||||
jinja_dict["PORT"] = str(args.port)
|
||||
|
||||
@ -93,56 +118,21 @@ def build_jinja_dict(args : d2args, skey : str) -> dict[str, str]:
|
||||
jinja_dict["LOG_LEVEL"] = args.log_level
|
||||
|
||||
jinja_dict["USERS"] = build_users_json(args.users)
|
||||
jinja_dict["PRIVATE_KEY"] = skey
|
||||
|
||||
jinja_dict["PRIVATE_KEY"] = args.private_key
|
||||
return jinja_dict
|
||||
|
||||
def parse_xray_x25519_output(stdout : str) -> tuple[str, str]:
|
||||
skey = None
|
||||
pkey = None
|
||||
lines = stdout.split("\n")
|
||||
if len(lines) < 2:
|
||||
raise Exception(f"Unknown Xray output format:\n\"{stdout}\"\n")
|
||||
|
||||
priv_key_hdr = "Private key: "
|
||||
pub_key_hdr = "Public key: "
|
||||
for line in lines:
|
||||
if line.startswith(priv_key_hdr):
|
||||
skey = line[len(priv_key_hdr):]
|
||||
elif line.startswith(pub_key_hdr):
|
||||
pkey = line[len(pub_key_hdr):]
|
||||
if (skey == None) or (pkey == None):
|
||||
raise Exception(f"Unable to extract private or public key from Xray output:\n\"{stdout}\"\n")
|
||||
return (skey.strip(), pkey.strip())
|
||||
|
||||
def main():
|
||||
print(f"Initializing d2ray...", flush=True)
|
||||
args = d2args()
|
||||
args.from_env()
|
||||
|
||||
print(f"Checking key file...", flush=True)
|
||||
if not KEY_FILE.exists():
|
||||
print(f"Key file not found at {KEY_FILE}. Generating...")
|
||||
out = subprocess.check_output(f"{XRAY_BIN} x25519", shell = True).decode()
|
||||
with open(KEY_FILE, "w") as f:
|
||||
f.write(out)
|
||||
|
||||
with open(KEY_FILE, "r") as f:
|
||||
out = f.read()
|
||||
print(f"\nConfiguration:\n{str(args)}\n", flush=True)
|
||||
|
||||
print(f"Reading keys...", flush=True)
|
||||
skey, pkey = parse_xray_x25519_output(out)
|
||||
|
||||
print(f"Verifying public key...", flush=True)
|
||||
_, _pkey = parse_xray_x25519_output(subprocess.check_output(f"{XRAY_BIN} x25519 -i {skey}", shell = True).decode())
|
||||
if (_pkey != pkey):
|
||||
print(f"Unmatching public key: expected \"{_pkey}\" but key file provided \"{pkey}\". Please verify the key file.", flush=True)
|
||||
|
||||
print(f"\nConfigurations:\n{str(args)}\nPublic key: {pkey}\n", flush=True)
|
||||
|
||||
template = build_jinja_dict(args, skey)
|
||||
template = build_jinja_dict(args)
|
||||
|
||||
print(f"Processing config files...", flush=True)
|
||||
process_directory("/opt/xray", template)
|
||||
|
||||
print(f"Initialization completed.\n", flush=True)
|
||||
|
||||
main()
|
||||
|
Loading…
Reference in New Issue
Block a user