samiulttr commited on
Commit
6c7f70b
·
verified ·
1 Parent(s): 64f8331

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +22 -18
  2. app.py +132 -0
  3. requirements.txt +3 -0
Dockerfile CHANGED
@@ -1,25 +1,29 @@
1
- FROM ghcr.io/ggml-org/llama.cpp:server
 
2
 
3
- WORKDIR /app
 
4
 
5
- RUN apt-get update && apt-get install -y --no-install-recommends \
6
- python3 python3-pip curl bash \
7
- && apt-get clean && rm -rf /var/lib/apt/lists/*
 
8
 
9
- RUN pip3 install --no-cache-dir --break-system-packages \
10
- "huggingface_hub==0.27.0" \
11
- "hf-transfer==0.1.8" \
12
- "requests==2.32.3" \
13
- "fastapi==0.115.5" \
14
- "uvicorn==0.32.1" \
15
- "httpx==0.28.1"
16
 
17
- COPY download_model.py /app/download_model.py
18
- COPY api_server.py /app/api_server.py
19
- COPY start.sh /app/start.sh
 
20
 
21
- RUN chmod +x /app/start.sh && mkdir -p /app/models
 
 
 
22
 
23
  EXPOSE 7860
24
- ENTRYPOINT []
25
- CMD ["/bin/bash", "/app/start.sh"]
 
 
 
 
1
+ # ── Base image ────────────────────────────────────────────────────────────────
2
+ FROM python:3.11-slim
3
 
4
+ RUN apt-get update && apt-get install -y --no-install-recommends curl \
5
+ && rm -rf /var/lib/apt/lists/*
6
 
7
+ # ── Non-root user (required by Hugging Face Spaces) ──────────────────────────
8
+ RUN useradd -m -u 1000 user
9
+ ENV HOME=/home/user \
10
+ PATH=/home/user/.local/bin:$PATH
11
 
12
+ WORKDIR $HOME/app
 
 
 
 
 
 
13
 
14
+ # ── Install Python dependencies ───────────────────────────────────────────────
15
+ COPY --chown=user requirements.txt .
16
+ RUN pip install --no-cache-dir --upgrade pip \
17
+ && pip install --no-cache-dir -r requirements.txt
18
 
19
+ # ── Copy application code ─────────────────────────────────────────────────────
20
+ COPY --chown=user . .
21
+
22
+ USER user
23
 
24
  EXPOSE 7860
25
+
26
+ HEALTHCHECK --interval=60s --timeout=10s --start-period=15s \
27
+ CMD curl -f http://localhost:7860/health || exit 1
28
+
29
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ public_relay/app.py — Public HF Space: Telegram ⇄ Private Space bridge
3
+ ========================================================================
4
+ এই Space-টা PUBLIC থাকবে, তাই Telegram সরাসরি এর সাথে কথা বলতে পারে
5
+ (webhook পাঠাতে পারে, কোনো auth token ছাড়াই)। এটা একটা হালকা relay মাত্র —
6
+ কোনো ভারী dependency (CrewAI, Playwright ইত্যাদি) নেই।
7
+
8
+ কাজ দুইটা:
9
+ 1) Telegram → এখানে POST /webhook → Private Space-এর /webhook-এ ফরওয়ার্ড
10
+ (Private Space private বলে HF access token দিয়ে auth করে পাঠানো হয়)
11
+ 2) Private Space → এখানে POST /bot{token}/{method} বা GET /file/bot{token}/{path}
12
+ → সরাসরি api.telegram.org-এ ফরওয়ার্ড, রেসপন্স ফেরত
13
+
14
+ HF Secrets (এই Public Space-এ):
15
+ PRIVATE_SPACE_URL — যেমন: https://your-username-private-space.hf.space
16
+ PRIVATE_SPACE_TOKEN — HF access token (Private Space-এ পৌঁছানোর জন্য;
17
+ Settings → Access Tokens থেকে বানান, 'read' যথেষ্ট)
18
+ RELAY_SECRET — শেয়ার্ড সিক্রেট, Telegram webhook verify করতে এবং
19
+ Private Space-কেও নিশ্চিত করতে যে কল এই relay থেকেই এসেছে
20
+ """
21
+
22
+ import os
23
+ import logging
24
+
25
+ import httpx
26
+ from fastapi import FastAPI, Request, Response
27
+ import uvicorn
28
+
29
+ logging.basicConfig(
30
+ format="%(asctime)s | %(name)s | %(levelname)s | %(message)s",
31
+ level=logging.INFO,
32
+ )
33
+ logger = logging.getLogger("public_relay")
34
+
35
+ PRIVATE_SPACE_URL = os.getenv("PRIVATE_SPACE_URL", "").rstrip("/")
36
+ PRIVATE_SPACE_TOKEN = os.getenv("PRIVATE_SPACE_TOKEN", "").strip()
37
+ RELAY_SECRET = os.getenv("RELAY_SECRET", "").strip() or None
38
+
39
+ app = FastAPI(title="Telegram Public Relay")
40
+ client = httpx.AsyncClient(timeout=httpx.Timeout(60.0, connect=15.0))
41
+
42
+
43
+ @app.on_event("shutdown")
44
+ async def _shutdown():
45
+ await client.aclose()
46
+
47
+
48
+ # ── দিক ১: Telegram → Private Space ──────────────────────────────────────────
49
+ @app.post("/webhook")
50
+ async def telegram_webhook(request: Request):
51
+ if RELAY_SECRET:
52
+ got = request.headers.get("X-Telegram-Bot-Api-Secret-Token", "")
53
+ if got != RELAY_SECRET:
54
+ logger.warning("Webhook: invalid secret token from Telegram")
55
+ return Response(status_code=403, content='{"error":"forbidden"}', media_type="application/json")
56
+
57
+ if not PRIVATE_SPACE_URL:
58
+ logger.error("PRIVATE_SPACE_URL সেট করা নেই")
59
+ return Response(status_code=500, content='{"error":"private_space_not_configured"}', media_type="application/json")
60
+
61
+ body = await request.body()
62
+ headers = {"Content-Type": "application/json"}
63
+ if PRIVATE_SPACE_TOKEN:
64
+ headers["Authorization"] = f"Bearer {PRIVATE_SPACE_TOKEN}"
65
+ if RELAY_SECRET:
66
+ headers["X-Telegram-Bot-Api-Secret-Token"] = RELAY_SECRET
67
+
68
+ try:
69
+ resp = await client.post(f"{PRIVATE_SPACE_URL}/webhook", content=body, headers=headers)
70
+ return Response(content=resp.content, status_code=resp.status_code, media_type="application/json")
71
+ except httpx.HTTPError as e:
72
+ logger.error("Private Space forward failed: %s", e)
73
+ return Response(status_code=502, content='{"error":"private_space_unreachable"}', media_type="application/json")
74
+
75
+
76
+ # ── দিক ২: Private Space → Telegram (Bot API কল) ─────────────────────────────
77
+ @app.post("/bot{token}/{method}")
78
+ async def relay_api_call(token: str, method: str, request: Request):
79
+ body = await request.body()
80
+ content_type = request.headers.get("content-type", "application/json")
81
+ try:
82
+ resp = await client.post(
83
+ f"https://api.telegram.org/bot{token}/{method}",
84
+ content=body,
85
+ headers={"Content-Type": content_type},
86
+ )
87
+ return Response(
88
+ content=resp.content,
89
+ status_code=resp.status_code,
90
+ media_type=resp.headers.get("content-type", "application/json"),
91
+ )
92
+ except httpx.HTTPError as e:
93
+ logger.error("Telegram API relay failed (%s): %s", method, e)
94
+ return Response(status_code=502, content='{"ok": false, "description": "telegram_unreachable"}', media_type="application/json")
95
+
96
+
97
+ # ── Private Space → Telegram (ফাইল ডাউনলোড) ──────────────────────────────────
98
+ @app.get("/file/bot{token}/{file_path:path}")
99
+ async def relay_file_download(token: str, file_path: str):
100
+ try:
101
+ resp = await client.get(f"https://api.telegram.org/file/bot{token}/{file_path}")
102
+ return Response(
103
+ content=resp.content,
104
+ status_code=resp.status_code,
105
+ media_type=resp.headers.get("content-type", "application/octet-stream"),
106
+ )
107
+ except httpx.HTTPError as e:
108
+ logger.error("Telegram file relay failed: %s", e)
109
+ return Response(status_code=502, content='{"error":"telegram_unreachable"}', media_type="application/json")
110
+
111
+
112
+ @app.get("/health")
113
+ async def health():
114
+ return {
115
+ "status": "ok",
116
+ "private_space_configured": bool(PRIVATE_SPACE_URL),
117
+ "relay_secret_configured": bool(RELAY_SECRET),
118
+ }
119
+
120
+
121
+ @app.get("/")
122
+ async def index():
123
+ return {"status": "running", "role": "telegram-public-relay"}
124
+
125
+
126
+ def main():
127
+ port = int(os.getenv("PORT", "7860"))
128
+ uvicorn.run(app, host="0.0.0.0", port=port)
129
+
130
+
131
+ if __name__ == "__main__":
132
+ main()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ fastapi>=0.110.0
2
+ uvicorn[standard]>=0.27.0
3
+ httpx>=0.27.0