Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: marzneshin/marznode
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.3.1
Choose a base ref
...
head repository: marzneshin/marznode
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
  • 11 commits
  • 25 files changed
  • 1 contributor

Commits on Oct 10, 2024

  1. fix some file names

    khodedawsh committed Oct 10, 2024
    Copy the full SHA
    8c7e14e View commit details

Commits on Oct 13, 2024

  1. Copy the full SHA
    032ceaa View commit details

Commits on Nov 14, 2024

  1. Copy the full SHA
    02dff3b View commit details
  2. Copy the full SHA
    d7f657f View commit details

Commits on Nov 25, 2024

  1. feat(backends): sing-box (#16)

    * feat(backends): sing-box
    
    * refactor(sing-box): rename some env variables
    
    * ci(sing-box): add sing-box to the docker image, include the path in compose
    khodedawsh authored Nov 25, 2024
    Copy the full SHA
    3a43957 View commit details
  2. Copy the full SHA
    d33af7a View commit details

Commits on Nov 29, 2024

  1. fix(sing-box): null transport by default (#17)

    * fix(sing-box): set transport(network) to null by default
    
    * fix(sing-box): add shadowtls to supported protocols
    
    * fix(sing-box): set shadowtls_version when present
    khodedawsh authored Nov 29, 2024
    Copy the full SHA
    589217c View commit details

Commits on Dec 6, 2024

  1. Copy the full SHA
    954aee8 View commit details

Commits on Dec 10, 2024

  1. Copy the full SHA
    b0ef5de View commit details

Commits on Dec 16, 2024

  1. Copy the full SHA
    c5a32ec View commit details

Commits on Dec 23, 2024

  1. Copy the full SHA
    6ae458a View commit details
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -7,13 +7,22 @@
#XRAY_ASSETS_PATH=/usr/share/xray
#XRAY_CONFIG_PATH=/etc/xray/xray_config.json
#XRAY_VLESS_REALITY_FLOW=xtls-rprx-vision
#XRAY_RESTART_ON_FAILURE=False
#XRAY_RESTART_ON_FAILURE_INTERVAL=0


#HYSTERIA_ENABLED=False
#HYSTERIA_EXECUTABLE_PATH=/usr/bin/hysteria
#HYSTERIA_CONFIG_PATH=/etc/hysteria/config.yaml


#SING_BOX_ENABLED=False
#SING_BOX_EXECUTABLE_PATH=/usr/bin/sing-box
#SING_BOX_CONFIG_PATH=/etc/sing-box/config.json
#SING_BOX_RESTART_ON_FAILURE=False
#SING_BOX_RESTART_ON_FAILURE_INTERVAL=0


#SSL_KEY_FILE=./server.key
#SSL_CERT_FILE=./server.cert
#SSL_CLIENT_CERT_FILE=./client.cert
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
FROM tobyxdd/hysteria:v2 AS hysteria-image
FROM jklolixxs/sing-box:latest AS sing-box-image

FROM python:3.12-alpine

ENV PYTHONUNBUFFERED=1

COPY --from=hysteria-image /usr/local/bin/hysteria /usr/local/bin/hysteria
COPY --from=sing-box-image /usr/local/bin/sing-box /usr/local/bin/sing-box

WORKDIR /app

3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -4,8 +4,7 @@ Just a fork of Marzban-node.

- [X] xray-core
- [X] hysteria
- [ ] wireguard
- [ ] sing-box
- [X] sing-box

## Setup Guide

2 changes: 2 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -9,6 +9,8 @@ services:
XRAY_EXECUTABLE_PATH: "/usr/local/bin/xray"
XRAY_ASSETS_PATH: "/usr/local/lib/xray"
XRAY_CONFIG_PATH: "/var/lib/marznode/xray_config.json"
SING_BOX_EXECUTABLE_PATH: "/usr/local/bin/sing-box"
HYSTERIA_EXECUTABLE_PATH: "/usr/local/bin/hysteria"
SSL_CLIENT_CERT_FILE: "/var/lib/marznode/client.pem"
SSL_KEY_FILE: "./server.key"
SSL_CERT_FILE: "./server.cert"
Original file line number Diff line number Diff line change
@@ -56,7 +56,3 @@ def list_inbounds(self):
@abstractmethod
def get_config(self):
raise NotImplementedError

@abstractmethod
def save_config(self, config: str):
raise NotImplementedError
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
import aiohttp
from aiohttp import web, ClientConnectorError

from marznode.backends.base import VPNBackend
from marznode.backends.abstract_backend import VPNBackend
from marznode.backends.hysteria2._config import HysteriaConfig
from marznode.backends.hysteria2._runner import Hysteria
from marznode.models import User, Inbound
@@ -100,7 +100,9 @@ async def add_user(self, user: User, inbound: Inbound) -> None:
self._users.update({password: user})

async def remove_user(self, user: User, inbound: Inbound) -> None:
self._users.pop(user.key)
if not self._users.get(password := generate_password(user.key)):
return
self._users.pop(password)
url = "http://127.0.0.1:" + str(self._stats_port) + "/kick"
headers = {"Authorization": self._stats_secret}

121 changes: 121 additions & 0 deletions marznode/backends/singbox/_accounts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""Implements accounts for different protocols in sing-box"""

# pylint: disable=E0611,C0115
from abc import ABC
from enum import Enum
from typing import Optional

from pydantic import (
BaseModel,
field_validator,
ValidationInfo,
ValidationError,
computed_field,
Field,
)

from marznode.utils.key_gen import generate_password, generate_uuid


class SingBoxAccount(BaseModel, ABC):
identifier: str
seed: str

@field_validator("uuid", "password", check_fields=False)
@classmethod
def generate_creds(cls, v: str, info: ValidationInfo):
if v:
return v
if "seed" in info.data:
seed = info.data["seed"]
if info.field_name == "uuid":
return str(generate_uuid(seed))
elif info.field_name == "password":
return generate_password(seed)
raise ValidationError("Both password/id and seed are empty")

def to_dict(self):
return self.model_dump(exclude={"seed", "identifier"})

def __repr__(self) -> str:
return f"<{self.__class__.__name__} {self.email}>"


class NamedAccount(SingBoxAccount):
@computed_field
@property
def name(self) -> str:
return self.identifier


class UserNamedAccount(SingBoxAccount):
@computed_field
@property
def username(self) -> str:
return self.identifier


class VMessAccount(NamedAccount, SingBoxAccount):
uuid: Optional[str] = Field(None, validate_default=True)


class XTLSFlows(str, Enum):
NONE = ""
VISION = "xtls-rprx-vision"


class VLESSAccount(NamedAccount, SingBoxAccount):
uuid: Optional[str] = Field(None, validate_default=True)
flow: XTLSFlows = XTLSFlows.NONE


class TrojanAccount(NamedAccount, SingBoxAccount):
password: Optional[str] = Field(None, validate_default=True)


class ShadowsocksAccount(NamedAccount, SingBoxAccount):
password: Optional[str] = Field(None, validate_default=True)


class TUICAccount(NamedAccount, SingBoxAccount):
uuid: Optional[str] = Field(None, validate_default=True)
password: Optional[str] = Field(None, validate_default=True)


class Hysteria2Account(NamedAccount, SingBoxAccount):
password: Optional[str] = Field(None, validate_default=True)


class NaiveAccount(UserNamedAccount, SingBoxAccount):
password: Optional[str] = Field(None, validate_default=True)


class ShadowTLSAccount(NamedAccount, SingBoxAccount):
password: Optional[str] = Field(None, validate_default=True)


class SocksAccount(UserNamedAccount, SingBoxAccount):
password: Optional[str] = Field(None, validate_default=True)


class HTTPAccount(UserNamedAccount, SingBoxAccount):
password: Optional[str] = Field(None, validate_default=True)


class MixedAccount(UserNamedAccount, SingBoxAccount):
password: Optional[str] = Field(None, validate_default=True)


accounts_map = {
"shadowsocks": ShadowsocksAccount,
"trojan": TrojanAccount,
"vmess": VMessAccount,
"vless": VLESSAccount,
"shadowtls": ShadowTLSAccount,
"tuic": TUICAccount,
"hysteria2": Hysteria2Account,
"naive": NaiveAccount,
"socks": SocksAccount,
"mixed": MixedAccount,
"http": HTTPAccount,
}
153 changes: 153 additions & 0 deletions marznode/backends/singbox/_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import json

import commentjson

from marznode.backends.singbox._accounts import accounts_map
from marznode.backends.xray._utils import get_x25519
from marznode.config import XRAY_EXECUTABLE_PATH
from marznode.models import User, Inbound
from marznode.storage import BaseStorage


class SingBoxConfig(dict):
def __init__(
self,
config: str,
api_host: str = "127.0.0.1",
api_port: int = 8080,
):
try:
# considering string as json
config = commentjson.loads(config)
except (json.JSONDecodeError, ValueError):
# considering string as file path
with open(config) as file:
config = commentjson.loads(file.read())

self.api_host = api_host
self.api_port = api_port

super().__init__(config)

self.inbounds = []
self.inbounds_by_tag = {}
self._resolve_inbounds()

self._apply_api()

def _apply_api(self):
if not self.get("experimental"):
self["experimental"] = {}
self["experimental"]["v2ray_api"] = {
"listen": self.api_host + ":" + str(self.api_port),
"stats": {"enabled": True, "users": []},
}

def _resolve_inbounds(self):
for inbound in self.get("inbounds", []):
if inbound.get("type") not in {
"shadowsocks",
"vmess",
"trojan",
"vless",
"hysteria2",
"tuic",
"shadowtls",
} or not inbound.get("tag"):
continue

settings = {
"tag": inbound["tag"],
"protocol": inbound["type"],
"port": inbound.get("listen_port"),
"network": None,
"tls": "none",
"sni": [],
"host": [],
"path": None,
"header_type": None,
"flow": None,
}

if "tls" in inbound and inbound["tls"].get("enabled") == True:
settings["tls"] = "tls"
if sni := inbound["tls"].get("server_name"):
settings["sni"].append(sni)
if inbound["tls"].get("reality", {}).get("enabled"):
settings["tls"] = "reality"
pvk = inbound["tls"]["reality"].get("private_key")

x25519 = get_x25519(XRAY_EXECUTABLE_PATH, pvk)
settings["pbk"] = x25519["public_key"]

settings["sid"] = inbound["tls"]["reality"].get("short_id", [""])[0]

if "transport" in inbound:
settings["network"] = inbound["transport"].get("type")
if settings["network"] == "ws":
settings["path"] = inbound["transport"].get("path")
elif settings["network"] == "http":
settings["path"] = inbound["transport"].get("path")
settings["network"] = "tcp"
settings["header_type"] = "http"
settings["host"] = inbound["transport"].get("host", [])
elif settings["network"] == "grpc":
settings["path"] = inbound["transport"].get("service_name")
elif settings["network"] == "httpupgrade":
settings["path"] = inbound["transport"].get("path")

if inbound["type"] == "shadowtls" and "version" in inbound:
settings["shadowtls_version"] = inbound["version"]
elif inbound["type"] == "hysteria2" and "obfs" in inbound:
try:
settings["header_type"], settings["path"] = (
inbound["obfs"]["type"],
inbound["obfs"]["password"],
)
except KeyError:
pass

self.inbounds.append(settings)
self.inbounds_by_tag[inbound["tag"]] = settings

def append_user(self, user: User, inbound: Inbound):
identifier = str(user.id) + "." + user.username
account = accounts_map[inbound.protocol](identifier=identifier, seed=user.key)
for i in self.get("inbounds", []):
if i.get("tag") == inbound.tag:
if not i.get("users"):
i["users"] = []
i["users"].append(account.to_dict())
if (
identifier
not in self["experimental"]["v2ray_api"]["stats"]["users"]
):
self["experimental"]["v2ray_api"]["stats"]["users"].append(
identifier
)
break

def pop_user(self, user: User, inbound: Inbound):
identifier = str(user.id) + "." + user.username
for i in self.get("inbounds", []):
if i.get("tag") != inbound.tag or not i.get("users"):
continue
i["users"] = [
user
for user in i["users"]
if user.get("name") != identifier and user.get("username") != identifier
]
break

def register_inbounds(self, storage: BaseStorage):
for inbound in self.list_inbounds():
storage.register_inbound(inbound)

def list_inbounds(self) -> list[Inbound]:
return [
Inbound(tag=i["tag"], protocol=i["protocol"], config=i)
for i in self.inbounds_by_tag.values()
]

def to_json(self, **json_kwargs):
return json.dumps(self, **json_kwargs)
Loading