Skip to content

Commit

Permalink
add shadow gate
Browse files Browse the repository at this point in the history
  • Loading branch information
sgrtye committed Jan 4, 2025
1 parent 31203e3 commit de5cb2e
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 0 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/action-shadowgate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Shadowgate build

on:
push:
paths:
- 'shadowgate/**'
workflow_dispatch:

jobs:
Shadowgate:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v4

- name: Build and push to Docker Hub
uses: ./.github/actions/Docker
with:
REGISTRY_ADDRESS: ${{ secrets.DOCKER_HUB_ADDRESS }}
REGISTRY_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.DOCKER_HUB_TOKEN }}
REGISTRY_NAME_SPACE: ${{ secrets.DOCKER_HUB_NAME_SPACE }}
DOCKER_BUILD_NAME: 'shadowgate'
DOCKER_BUILD_PATH: './shadowgate'
8 changes: 8 additions & 0 deletions shadowgate/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM python:3.12-alpine

ENV TZ=Europe/London

COPY . /app
RUN pip install --no-cache-dir -r /app/requirements.txt

CMD ["python", "-u", "/app/main.py"]
99 changes: 99 additions & 0 deletions shadowgate/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import os
import aiohttp
from aiohttp import web
from xui import *


PROXY_URL: str | None = os.environ.get("PROXY_URL")
PROXY_PORT: str | None = os.environ.get("PROXY_PORT")
PROXY_PATH: str | None = os.environ.get("PROXY_PATH")
HOST_DOMAIN: str | None = os.environ.get("HOST_DOMAIN")
XUI_USERNAME: str | None = os.environ.get("XUI_USERNAME")
XUI_PASSWORD: str | None = os.environ.get("XUI_PASSWORD")

if (
PROXY_URL is None
or PROXY_PORT is None
or PROXY_PATH is None
or HOST_DOMAIN is None
or XUI_USERNAME is None
or XUI_PASSWORD is None
):
print("Environment variables not fulfilled")
raise SystemExit


async def forward_to_xui(request):
target_url = f"{PROXY_URL}:{PROXY_PORT}{PROXY_PATH}{request.path_qs}"
return await forward_request(request, target_url)


async def forward_to_proxy(request, port: str):
target_url = f"{PROXY_URL}:{port}{request.path_qs}"
return await forward_request(
request, target_url, upgrade_connection=True, timeout=300
)


async def forward_request(request, target_url, upgrade_connection=False, timeout=None):
async with aiohttp.ClientSession() as session:
headers = {
key: value
for key, value in request.headers.items()
if key.lower() not in ["host", "connection", "upgrade"]
}
headers.update(
{
"Host": request.headers.get("Host", ""),
"X-Real-IP": request.remote,
"X-Forwarded-For": request.headers.get(
"X-Forwarded-For", request.remote
),
}
)
if upgrade_connection:
headers["Connection"] = "upgrade"
headers["Upgrade"] = request.headers.get("Upgrade", "")

try:
async with session.request(
method=request.method,
url=target_url,
headers=headers,
data=await request.read(),
timeout=timeout,
) as response:
resp_headers = {
key: value
for key, value in response.headers.items()
if key.lower()
!= "transfer-encoding" # Avoid issues with chunked transfer
}
body = await response.read()
return web.Response(
status=response.status, headers=resp_headers, body=body
)

except Exception as e:
return web.Response(status=500, text=f"Error while forwarding: {str(e)}")


async def start_proxy(inbounds: list[dict[str, str]]):
app = web.Application()
app.router.add_route("*", PROXY_PATH + "{tail:.*}", forward_to_xui)
for inbound in inbounds:
app.router.add_route(
"*",
inbound["path"] + "{tail:.*}",
lambda request: forward_to_proxy(request, inbound["port"]),
)

return app


if __name__ == "__main__":
inbounds = get_inbounds(
f"{PROXY_URL}:{PROXY_PORT}{PROXY_PATH}", XUI_USERNAME, XUI_PASSWORD
)
print(inbounds)
web.run_app(start_proxy(inbounds), host="0.0.0.0", port=80)
2 changes: 2 additions & 0 deletions shadowgate/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
aiohttp
requests
43 changes: 43 additions & 0 deletions shadowgate/xui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import json
import time
import requests

xui_session = requests.Session()
xui_rate_limit_time: float = time.time()


def xui_login(url: str, username: str, password: str) -> None:
global xui_rate_limit_time

if (sleep_time := 5 - (time.time() - xui_rate_limit_time)) > 0:
time.sleep(sleep_time)
xui_rate_limit_time = time.time()

xui_session.post(
url + "/login",
data={"username": username, "password": password},
)


def get_xui_info(url: str, path_suffix: str, username: str, password: str) -> dict:
while (info := xui_session.post(url + path_suffix)).status_code != 200:
xui_login(url, username, password)

return info.json()


def get_inbounds(url: str, username: str, password: str) -> list[dict[str, str]]:
response = get_xui_info(url, "/xui/inbound/list", username, password)

results = []
for inbound in response["obj"]:
info = {
"port": str(inbound["port"]),
"path": json.loads(inbound["streamSettings"])["wsSettings"]["path"],
}
results.append(info)

return results


__ALL__ = ["get_inbounds"]

0 comments on commit de5cb2e

Please sign in to comment.