jej4 / app.py
makeitfr's picture
Upload folder using huggingface_hub
e731992 verified
Raw
History Blame Contribute Delete
3.61 kB
#!/usr/bin/env python3
"""
FastAPI server that wraps xm_launcher.
On startup, the miner auto-starts in a background thread.
The FastAPI server runs alongside it via uvicorn.
"""
import os
import sys
import threading
from pathlib import Path
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.responses import JSONResponse
# Database imports
import psycopg2
from psycopg2.extras import DictCursor
from dotenv import load_dotenv
load_dotenv()
# Import the miner launcher
from xm_launcher import (
XMRigLauncher,
_stop_event,
)
BASE_DIR = Path(__file__).parent.absolute()
def get_db_conn():
db_url = os.getenv("DATABASE_URL")
if not db_url:
return None
try:
return psycopg2.connect(db_url)
except Exception as e:
print(f"[api] DB Connect Error: {e}", flush=True)
return None
BASE_DIR = Path(__file__).parent.absolute()
def run_miner_background():
"""Run the miner in a background thread."""
try:
db_url = os.getenv("DATABASE_URL")
if not db_url:
print("[server] ERROR: DATABASE_URL not set in environment!", flush=True)
return
launcher = XMRigLauncher()
print("[server] Starting miner in background (DB Mode)...", flush=True)
launcher.start_pool_mining(db_url)
except Exception as e:
print(f"[server] Miner error: {e}", flush=True)
import traceback
traceback.print_exc()
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Start the miner on server startup."""
miner_thread = threading.Thread(target=run_miner_background, daemon=True)
miner_thread.start()
print("[server] Miner thread started", flush=True)
yield
# Shutdown
print("[server] Shutting down miner...", flush=True)
_stop_event.set()
app = FastAPI(title="Mineo v4", lifespan=lifespan)
@app.get("/")
async def root():
return {"status": "running", "service": "mineo_v4"}
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/job")
async def current_job():
"""Return the current active pool job from the database."""
conn = get_db_conn()
if not conn:
return JSONResponse(status_code=500, content={"error": "Database connection failed"})
try:
cur = conn.cursor(cursor_factory=DictCursor)
cur.execute("SELECT * FROM mining_jobs ORDER BY id DESC LIMIT 1")
row = cur.fetchone()
if row:
# Convert row to dict
return {k: v for k, v in row.items()}
return JSONResponse(status_code=404, content={"error": "No jobs in database"})
finally:
conn.close()
@app.get("/result")
async def current_result():
"""Return the latest mining result from the database."""
conn = get_db_conn()
if not conn:
return JSONResponse(status_code=500, content={"error": "Database connection failed"})
try:
cur = conn.cursor(cursor_factory=DictCursor)
cur.execute("SELECT * FROM mining_results ORDER BY id DESC LIMIT 1")
row = cur.fetchone()
if row:
row_dict = dict(row)
# Serialize datetimes to ISO format for JSON compatibility
if row_dict.get('found_at'):
row_dict['found_at'] = row_dict['found_at'].isoformat()
if row_dict.get('submitted_at'):
row_dict['submitted_at'] = row_dict['submitted_at'].isoformat()
return row_dict
return JSONResponse(status_code=404, content={"error": "No results yet"})
finally:
conn.close()