From 2162be61d140fcac7b6ac64ff2513c6d0d75822e Mon Sep 17 00:00:00 2001
From: Anonymous <>
Date: Sat, 24 Feb 2024 17:54:55 +0800
Subject: [PATCH] makes caddy optional
---
.env.example | 3 +-
README.md | 61 +++++++++++++++++++++++++++++++---------
caddy/Caddyfile | 8 +-----
caddy/Dockerfile | 8 ------
docker-compose-caddy.yml | 48 +++++++++++++++++++++++++++++++
docker-compose.yml | 12 --------
mirrors/Dockerfile | 9 ++++++
mirrors/config.py | 8 ++++--
mirrors/server.py | 42 ++++++++++++++++++++++-----
9 files changed, 146 insertions(+), 53 deletions(-)
create mode 100644 docker-compose-caddy.yml
diff --git a/.env.example b/.env.example
index 5825b57..7e97b0c 100644
--- a/.env.example
+++ b/.env.example
@@ -1,8 +1,7 @@
-
# for caddy
CLOUDFLARE_DNS_API_TOKEN=xxxx
-# for caddy and mirrors
+SCHEME=http
BASE_DOMAIN=local.homeinfra.org
# for aria2 and mirrors
diff --git a/README.md b/README.md
index 0273239..db98c28 100644
--- a/README.md
+++ b/README.md
@@ -5,9 +5,8 @@
LightMirrors是一个开源的缓存镜像站服务,用于加速软件包下载和镜像拉取。
目前支持**DockerHub**、PyPI、PyTorch、NPM等镜像缓存服务。
-
-
+
[](https://github.com/NoCLin/LightMirrors)
[](https://github.com/NoCLin/LightMirrors)
@@ -23,41 +22,75 @@ LightMirrors是一个开源的缓存镜像站服务,用于加速软件包下
### Prerequisites
- docker + docker-compose.
-- 一个域名,设置 `*.local.homeinfra.org` 的A记录指向您的服务器.
+- 一个域名,设置 `*.yourdomain` 的A记录指向您服务器的IP.
+ - `*.local.homeinfra.org` 默认指向 `127.0.0.1`,本地测试可以直接使用。
- 代理服务器(如有必要).
-- 一个Cloudflare账户(非强制,也可以使用其他DNS服务,请自行修改Caddy)
+
+> 如果需要使用HTTPS,可以在外层新增一个HTTP网关(如Caddy),请参考后续章节。
+
+### QuickStart
+
+```bash
+
+cp .env.example .env
+docker-compose up
+
+```
### Deployment
修改 `.env` 文件,设置下列参数:
- `BASE_DOMAIN`: 基础域名,如 `local.homeinfra.org`,镜像站将会使用 `*.local.homeinfra.org` 的子域名。
-- `CLOUDFLARE_DNS_API_TOKEN`,Cloudflare的API Token,用于管理DNS申请HTTPS证书。
- `RPC_SECRET`:Aria2的RPC密钥。
- `all_proxy`:代理服务器地址,如有必要。
+- `SCHEME`:`http` 或 `https`。
+
+如果您需要HTTPS,请确保docker-compose.yml文件中开放443端口,并使用`cloudflare` 相关的Caddyfile和Dockerfile.
```bash
docker-compose up
```
+测试命令:
+
+```bash
+docker pull docker.local.homeinfra.org/alpine
+pip3 download -i http://pypi.local.homeinfra.org/simple/ jinja2 --trusted-host pypi.local.homeinfra.org
+pip3 download -i http://torch.local.homeinfra.org/whl/ torch --trusted-host torch.local.homeinfra.org
+```
+
## Design
-LightMirrors依赖于三个组件:
+LightMirrors依赖于两个组件:
-- aria2 + Aria2Ng : 下载器与管理UI。
-- mirrors: 镜像HTTP服务器。
-- caddy: HTTP网关。
+- aria2 : 下载器与管理UI。
+- mirrors: 镜像HTTP服务器,根据不同域名转发请求到不同模块。
+ - Aria2Ng
+ - PyPI
+ - DockerHub
+ - ...
## Test
> 假设我们的域名为 local.homeinfra.org
-| subdomain | source | test command |
-|-----------|---------------------------------|-------------------------------------------------------------------|
-| pypi | https://pypi.org | `pip3 download -i https://pypi.local.homeinfra.org/simple jinja2` |
-| torch | https://download.pytorch.org | `pip3 download -i https://torch.local.homeinfra.org/whl/ torch` |
-| dockerhub | https://registry-1.docker.io/v2 | `docker pull docker.local.homeinfra.org/alpine` |
+| subdomain | source | test command | test command (http) |
+|-----------|---------------------------------|-------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
+| pypi | https://pypi.org | `pip3 download -i https://pypi.local.homeinfra.org/simple jinja2` | `pip3 download -i http://pypi.local.homeinfra.org/simple jinja2 --trusted-host pypi.local.homeinfra.org` |
+| torch | https://download.pytorch.org | `pip3 download -i https://torch.local.homeinfra.org/whl/ torch` | `pip3 download -i http://torch.local.homeinfra.org/whl/ torch --trusted-host torch.local.homeinfra.org` |
+| dockerhub | https://registry-1.docker.io/v2 | `docker pull docker.local.homeinfra.org/alpine` | `docker pull docker.local.homeinfra.org/alpine` |
+## HTTPS
+
+在 .env 中配置 `SCHEME=https` 与 CLOUDFLARE_DNS_API_TOKEN。
+本项目提供了一个基于Cloudflare DNS的Caddyfile和Dockerfile,可以直接使用。如果您希望使用其他DNS Provider,请自行修改。
+
+配置完成后,执行下列命令:
+
+```bash
+docker-compose -f docker-compose-caddy.yml up
+```
## Star History
diff --git a/caddy/Caddyfile b/caddy/Caddyfile
index e4111c2..9fbc0f9 100644
--- a/caddy/Caddyfile
+++ b/caddy/Caddyfile
@@ -4,12 +4,6 @@
dns cloudflare {env.CLOUDFLARE_DNS_API_TOKEN}
}
- reverse_proxy http://lightmirrors:8080
+ reverse_proxy http://lightmirrors:80
- @aria2 host aria2.{$BASE_DOMAIN}
- handle @aria2 {
- root * /wwwroot
- file_server
- reverse_proxy /jsonrpc http://aria2:6800
- }
}
\ No newline at end of file
diff --git a/caddy/Dockerfile b/caddy/Dockerfile
index bf50fe3..3b820c2 100644
--- a/caddy/Dockerfile
+++ b/caddy/Dockerfile
@@ -10,14 +10,6 @@ FROM caddy:2.7.6-alpine
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
-RUN mkdir -p /wwwroot
-WORKDIR /wwwroot
-
-# Optimization for China as the project is aimed at Chinese users
-ADD https://hub.gitmirror.com/https://github.com/mayswind/AriaNg/releases/download/1.3.7/AriaNg-1.3.7.zip /wwwroot/
-
-RUN unzip AriaNg-1.3.7.zip && rm AriaNg-1.3.7.zip
-
ENTRYPOINT ["caddy"]
CMD ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
\ No newline at end of file
diff --git a/docker-compose-caddy.yml b/docker-compose-caddy.yml
new file mode 100644
index 0000000..555e875
--- /dev/null
+++ b/docker-compose-caddy.yml
@@ -0,0 +1,48 @@
+services:
+ lightmirrors:
+ image: lightmirrors/mirrors
+ build: ./mirrors
+ volumes:
+ - ./mirrors:/app
+ - ./data/cache:/app/cache
+ env_file:
+ - .env
+ networks:
+ - app
+ restart: unless-stopped
+ # for linux
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
+ ports:
+ - "80:80"
+ caddy:
+ image: lightmirrors/caddy
+ build:
+ context: caddy
+ dockerfile: Dockerfile
+
+ ports:
+ - "443:443"
+ volumes:
+ - ./caddy/Caddyfile:/etc/caddy/Caddyfile
+ - ./caddy/caddy_data:/data/caddy
+ env_file:
+ - .env
+ networks:
+ - app
+ restart: unless-stopped
+ aria2:
+ image: lightmirrors/aria2
+ build: ./aria2
+ volumes:
+ - ./aria2/aria2.conf:/aria2.conf
+ - ./data/cache:/app/cache
+ - ./data/aria2:/data/
+ networks:
+ - app
+ env_file:
+ - .env
+ restart: unless-stopped
+networks:
+ app:
+ driver: bridge
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index f63372f..ce5f3b0 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -13,20 +13,8 @@ services:
# for linux
extra_hosts:
- "host.docker.internal:host-gateway"
- caddy:
- image: lightmirrors/caddy
- build: ./caddy
ports:
- "80:80"
- - "443:443"
- volumes:
- - ./caddy/Caddyfile:/etc/caddy/Caddyfile
- - ./caddy/caddy_data:/data/caddy
- env_file:
- - .env
- networks:
- - app
- restart: unless-stopped
aria2:
image: lightmirrors/aria2
build: ./aria2
diff --git a/mirrors/Dockerfile b/mirrors/Dockerfile
index aec3db5..530adbc 100644
--- a/mirrors/Dockerfile
+++ b/mirrors/Dockerfile
@@ -1,5 +1,14 @@
FROM python:3.11-alpine
+RUN mkdir -p /wwwroot
+WORKDIR /wwwroot
+
+# Optimization for China as the project is aimed at Chinese users
+ADD https://hub.gitmirror.com/https://github.com/mayswind/AriaNg/releases/download/1.3.7/AriaNg-1.3.7.zip /wwwroot/
+
+RUN unzip AriaNg-1.3.7.zip && rm AriaNg-1.3.7.zip
+
+
ADD requirements.txt /app/requirements.txt
RUN pip install -r /app/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/
diff --git a/mirrors/config.py b/mirrors/config.py
index ef42740..1fb5e9b 100644
--- a/mirrors/config.py
+++ b/mirrors/config.py
@@ -2,10 +2,12 @@ import os
ARIA2_RPC_URL = os.environ.get("ARIA2_RPC_URL", 'http://aria2:6800/jsonrpc')
RPC_SECRET = os.environ.get("RPC_SECRET", '')
-BASE_DOMAIN = os.environ.get("BASE_DOMAIN", '127.0.0.1.nip.io')
+BASE_DOMAIN = os.environ.get("BASE_DOMAIN", 'local.homeinfra.org')
PROXY = os.environ.get("PROXY", None)
-CACHE_DIR = os.environ.get("CACHE_DIR", "/app/cache/")
+SCHEME = os.environ.get("SCHEME", None)
+assert SCHEME in ["http", "https"]
+CACHE_DIR = os.environ.get("CACHE_DIR", "/app/cache/")
EXTERNAL_HOST_ARIA2 = f"aria2." + BASE_DOMAIN
-EXTERNAL_URL_ARIA2 = f"https://" + EXTERNAL_HOST_ARIA2
+EXTERNAL_URL_ARIA2 = f"{SCHEME}://{EXTERNAL_HOST_ARIA2}/aria2/index.html"
diff --git a/mirrors/server.py b/mirrors/server.py
index 5c4a636..6383224 100644
--- a/mirrors/server.py
+++ b/mirrors/server.py
@@ -2,11 +2,14 @@ import base64
import signal
import urllib.parse
+import httpx
import uvicorn
from fastapi import FastAPI
from starlette.requests import Request
+from starlette.responses import RedirectResponse, Response
+from starlette.staticfiles import StaticFiles
-from config import BASE_DOMAIN, RPC_SECRET, EXTERNAL_URL_ARIA2, EXTERNAL_HOST_ARIA2
+from config import BASE_DOMAIN, RPC_SECRET, EXTERNAL_URL_ARIA2, EXTERNAL_HOST_ARIA2, SCHEME
from sites.docker import docker
from sites.npm import npm
from sites.pypi import pypi
@@ -14,6 +17,26 @@ from sites.torch import torch
app = FastAPI()
+app.mount("/aria2/", StaticFiles(directory="/wwwroot/"), name="static", )
+
+
+async def aria2(request: Request, call_next):
+ if request.url.path == "/":
+ return RedirectResponse("/aria2/index.html")
+ if request.url.path == "/jsonrpc":
+ async with httpx.AsyncClient(mounts={
+ "all://": httpx.AsyncHTTPTransport()
+ }) as client:
+ data = (await request.body())
+ response = await client.request(url="http://aria2:6800/jsonrpc",
+ method=request.method,
+ headers=request.headers, content=data)
+ headers = response.headers
+ headers.pop("content-length", None)
+ headers.pop("content-encoding", None)
+ return Response(content=response.content, status_code=response.status_code, headers=headers)
+ return await call_next(request)
+
@app.middleware("http")
async def capture_request(request: Request, call_next: callable):
@@ -21,6 +44,9 @@ async def capture_request(request: Request, call_next: callable):
if not hostname.endswith(f".{BASE_DOMAIN}"):
return await call_next(request)
+ if hostname.startswith("aria2."):
+ return await aria2(request, call_next)
+
if hostname.startswith("pypi."):
return await pypi(request)
if hostname.startswith("torch."):
@@ -35,24 +61,26 @@ async def capture_request(request: Request, call_next: callable):
if __name__ == '__main__':
signal.signal(signal.SIGINT, signal.SIG_DFL)
- port = 8080
- print(f"Server started at https://*.{BASE_DOMAIN})")
+ port = 80
+ print(f"Server started at {SCHEME}://*.{BASE_DOMAIN})")
for dn in ["pypi", "torch", "docker", "npm"]:
- print(f" - https://{dn}.{BASE_DOMAIN}")
+ print(f" - {SCHEME}://{dn}.{BASE_DOMAIN}")
aria2_secret = base64.b64encode(RPC_SECRET.encode()).decode()
params = {
- 'protocol': 'https',
+ 'protocol': SCHEME,
'host': EXTERNAL_HOST_ARIA2,
- 'port': '443',
+ 'port': '443' if SCHEME == 'https' else '80',
'interface': 'jsonrpc',
'secret': aria2_secret
}
query_string = urllib.parse.urlencode(params)
- aria2_url_with_auth = EXTERNAL_URL_ARIA2 + "/#!/settings/rpc/set?" + query_string
+ aria2_url_with_auth = EXTERNAL_URL_ARIA2 + "#!/settings/rpc/set?" + query_string
print(f"Download manager (Aria2) at {aria2_url_with_auth}")
+ # FIXME: only proxy headers if SCHME is https
+ # reload only in dev mode
uvicorn.run(app="server:app", host="0.0.0.0", port=port, reload=True, proxy_headers=True, forwarded_allow_ips="*")