Spaces:
Paused
Paused
Upload folder using huggingface_hub
Browse files- examples/redis_space/Dockerfile +20 -0
- examples/redis_space/app.py +60 -0
- examples/redis_space/backup_manager.py +68 -0
- examples/redis_space/start.sh +18 -0
- local_ws_proxy.py +63 -0
examples/redis_space/Dockerfile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-slim
|
| 2 |
+
|
| 3 |
+
# Install Redis
|
| 4 |
+
RUN apt-get update && apt-get install -y redis-server && rm -rf /var/lib/apt/lists/*
|
| 5 |
+
|
| 6 |
+
WORKDIR /app
|
| 7 |
+
|
| 8 |
+
# Install Python dependencies
|
| 9 |
+
RUN pip install fastapi uvicorn websockets huggingface_hub schedule
|
| 10 |
+
|
| 11 |
+
COPY . .
|
| 12 |
+
|
| 13 |
+
# Set up Redis data directory
|
| 14 |
+
RUN mkdir -p /data
|
| 15 |
+
RUN sed -i 's/^dir .*/dir \/data/' /etc/redis/redis.conf
|
| 16 |
+
|
| 17 |
+
# Add start script
|
| 18 |
+
RUN chmod +x start.sh
|
| 19 |
+
|
| 20 |
+
CMD ["./start.sh"]
|
examples/redis_space/app.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
from fastapi import FastAPI, WebSocket
|
| 3 |
+
from fastapi.responses import HTMLResponse
|
| 4 |
+
import logging
|
| 5 |
+
|
| 6 |
+
logging.basicConfig(level=logging.INFO)
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
app = FastAPI()
|
| 10 |
+
|
| 11 |
+
@app.get("/")
|
| 12 |
+
def read_root():
|
| 13 |
+
return HTMLResponse("<h1>Redis WebSocket Proxy Running</h1>")
|
| 14 |
+
|
| 15 |
+
@app.websocket("/ws")
|
| 16 |
+
async def websocket_endpoint(websocket: WebSocket):
|
| 17 |
+
await websocket.accept()
|
| 18 |
+
logger.info("WebSocket connection accepted.")
|
| 19 |
+
|
| 20 |
+
try:
|
| 21 |
+
# Connect to local Redis TCP port
|
| 22 |
+
reader, writer = await asyncio.open_connection("127.0.0.1", 6379)
|
| 23 |
+
logger.info("Connected to local Redis.")
|
| 24 |
+
except Exception as e:
|
| 25 |
+
logger.error(f"Failed to connect to local Redis: {e}")
|
| 26 |
+
await websocket.close()
|
| 27 |
+
return
|
| 28 |
+
|
| 29 |
+
async def pipe_ws_to_tcp():
|
| 30 |
+
try:
|
| 31 |
+
while True:
|
| 32 |
+
data = await websocket.receive_bytes()
|
| 33 |
+
writer.write(data)
|
| 34 |
+
await writer.drain()
|
| 35 |
+
except Exception as e:
|
| 36 |
+
logger.info(f"ws_to_tcp closed: {e}")
|
| 37 |
+
|
| 38 |
+
async def pipe_tcp_to_ws():
|
| 39 |
+
try:
|
| 40 |
+
while True:
|
| 41 |
+
data = await reader.read(4096)
|
| 42 |
+
if not data:
|
| 43 |
+
break
|
| 44 |
+
await websocket.send_bytes(data)
|
| 45 |
+
except Exception as e:
|
| 46 |
+
logger.info(f"tcp_to_ws closed: {e}")
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
await asyncio.gather(
|
| 50 |
+
pipe_ws_to_tcp(),
|
| 51 |
+
pipe_tcp_to_ws(),
|
| 52 |
+
return_exceptions=True
|
| 53 |
+
)
|
| 54 |
+
finally:
|
| 55 |
+
writer.close()
|
| 56 |
+
try:
|
| 57 |
+
await websocket.close()
|
| 58 |
+
except:
|
| 59 |
+
pass
|
| 60 |
+
logger.info("Connection closed.")
|
examples/redis_space/backup_manager.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import time
|
| 3 |
+
import schedule
|
| 4 |
+
from huggingface_hub import HfApi, hf_hub_download
|
| 5 |
+
import subprocess
|
| 6 |
+
import logging
|
| 7 |
+
|
| 8 |
+
logging.basicConfig(level=logging.INFO)
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 12 |
+
DATASET_REPO = os.getenv("DATASET_REPO", "augment17/agentscope-redis-backup")
|
| 13 |
+
DUMP_FILE = "/data/dump.rdb"
|
| 14 |
+
LOCAL_DUMP = "dump.rdb"
|
| 15 |
+
|
| 16 |
+
api = HfApi(token=HF_TOKEN)
|
| 17 |
+
|
| 18 |
+
def ensure_repo():
|
| 19 |
+
try:
|
| 20 |
+
api.repo_info(repo_id=DATASET_REPO, repo_type="dataset")
|
| 21 |
+
except Exception:
|
| 22 |
+
logger.info(f"Creating dataset repo {DATASET_REPO}...")
|
| 23 |
+
api.create_repo(repo_id=DATASET_REPO, repo_type="dataset", private=True)
|
| 24 |
+
|
| 25 |
+
def restore_backup():
|
| 26 |
+
ensure_repo()
|
| 27 |
+
try:
|
| 28 |
+
logger.info("Checking for existing Redis backup...")
|
| 29 |
+
downloaded_file = hf_hub_download(
|
| 30 |
+
repo_id=DATASET_REPO,
|
| 31 |
+
repo_type="dataset",
|
| 32 |
+
filename=LOCAL_DUMP,
|
| 33 |
+
token=HF_TOKEN,
|
| 34 |
+
local_dir="/data"
|
| 35 |
+
)
|
| 36 |
+
logger.info(f"Restored backup from {downloaded_file}")
|
| 37 |
+
except Exception as e:
|
| 38 |
+
logger.info(f"No existing backup found or failed to restore: {e}")
|
| 39 |
+
|
| 40 |
+
def perform_backup():
|
| 41 |
+
logger.info("Performing Redis backup...")
|
| 42 |
+
try:
|
| 43 |
+
# Trigger Redis bgsave
|
| 44 |
+
subprocess.run(["redis-cli", "save"], check=True)
|
| 45 |
+
if os.path.exists(DUMP_FILE):
|
| 46 |
+
api.upload_file(
|
| 47 |
+
path_or_fileobj=DUMP_FILE,
|
| 48 |
+
path_in_repo=LOCAL_DUMP,
|
| 49 |
+
repo_id=DATASET_REPO,
|
| 50 |
+
repo_type="dataset",
|
| 51 |
+
token=HF_TOKEN
|
| 52 |
+
)
|
| 53 |
+
logger.info("Backup uploaded to Hugging Face Datasets successfully.")
|
| 54 |
+
else:
|
| 55 |
+
logger.error("dump.rdb not found after save!")
|
| 56 |
+
except Exception as e:
|
| 57 |
+
logger.error(f"Backup failed: {e}")
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
mode = os.getenv("MODE", "restore")
|
| 61 |
+
if mode == "restore":
|
| 62 |
+
restore_backup()
|
| 63 |
+
elif mode == "cron":
|
| 64 |
+
logger.info("Starting backup cron schedule (every 30 minutes)...")
|
| 65 |
+
schedule.every(30).minutes.do(perform_backup)
|
| 66 |
+
while True:
|
| 67 |
+
schedule.run_pending()
|
| 68 |
+
time.sleep(60)
|
examples/redis_space/start.sh
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -e
|
| 3 |
+
|
| 4 |
+
# Restore backup from HF Datasets
|
| 5 |
+
echo "Restoring database from Hugging Face Datasets..."
|
| 6 |
+
MODE=restore python backup_manager.py
|
| 7 |
+
|
| 8 |
+
# Start Redis Server in background
|
| 9 |
+
echo "Starting Redis server..."
|
| 10 |
+
redis-server /etc/redis/redis.conf &
|
| 11 |
+
|
| 12 |
+
# Start backup cron loop in background
|
| 13 |
+
echo "Starting background backup loop..."
|
| 14 |
+
MODE=cron python backup_manager.py &
|
| 15 |
+
|
| 16 |
+
# Start FastAPI WebSocket Proxy in foreground (port 7860 for HF Spaces)
|
| 17 |
+
echo "Starting FastAPI WebSocket proxy..."
|
| 18 |
+
uvicorn app:app --host 0.0.0.0 --port 7860
|
local_ws_proxy.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import os
|
| 3 |
+
import websockets
|
| 4 |
+
import logging
|
| 5 |
+
|
| 6 |
+
logging.basicConfig(level=logging.INFO)
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
# The remote HF Space websocket URL (e.g., wss://augment17-redis.hf.space/ws)
|
| 10 |
+
REMOTE_WS_URL = os.getenv("REMOTE_REDIS_WS_URL")
|
| 11 |
+
LOCAL_PORT = int(os.getenv("LOCAL_REDIS_PORT", 6379))
|
| 12 |
+
|
| 13 |
+
async def handle_client(reader, writer):
|
| 14 |
+
if not REMOTE_WS_URL:
|
| 15 |
+
logger.error("REMOTE_REDIS_WS_URL is not set!")
|
| 16 |
+
writer.close()
|
| 17 |
+
return
|
| 18 |
+
|
| 19 |
+
logger.info("New local TCP connection. Connecting to remote WebSocket...")
|
| 20 |
+
try:
|
| 21 |
+
async with websockets.connect(REMOTE_WS_URL) as ws:
|
| 22 |
+
logger.info("Connected to remote WebSocket.")
|
| 23 |
+
|
| 24 |
+
async def pipe_tcp_to_ws():
|
| 25 |
+
try:
|
| 26 |
+
while True:
|
| 27 |
+
data = await reader.read(4096)
|
| 28 |
+
if not data:
|
| 29 |
+
break
|
| 30 |
+
await ws.send(data)
|
| 31 |
+
except Exception as e:
|
| 32 |
+
logger.info(f"tcp_to_ws closed: {e}")
|
| 33 |
+
|
| 34 |
+
async def pipe_ws_to_tcp():
|
| 35 |
+
try:
|
| 36 |
+
while True:
|
| 37 |
+
data = await ws.recv()
|
| 38 |
+
writer.write(data)
|
| 39 |
+
await writer.drain()
|
| 40 |
+
except Exception as e:
|
| 41 |
+
logger.info(f"ws_to_tcp closed: {e}")
|
| 42 |
+
|
| 43 |
+
await asyncio.gather(
|
| 44 |
+
pipe_tcp_to_ws(),
|
| 45 |
+
pipe_ws_to_tcp(),
|
| 46 |
+
return_exceptions=True
|
| 47 |
+
)
|
| 48 |
+
except Exception as e:
|
| 49 |
+
logger.error(f"WebSocket connection failed: {e}")
|
| 50 |
+
finally:
|
| 51 |
+
writer.close()
|
| 52 |
+
logger.info("Local TCP connection closed.")
|
| 53 |
+
|
| 54 |
+
async def main():
|
| 55 |
+
server = await asyncio.start_server(handle_client, '127.0.0.1', LOCAL_PORT)
|
| 56 |
+
addr = server.sockets[0].getsockname()
|
| 57 |
+
logger.info(f"Local WebSocket-to-TCP proxy listening on {addr}")
|
| 58 |
+
|
| 59 |
+
async with server:
|
| 60 |
+
await server.serve_forever()
|
| 61 |
+
|
| 62 |
+
if __name__ == '__main__':
|
| 63 |
+
asyncio.run(main())
|