Spaces:
Running
Running
# Use official LibreChat base image | |
FROM ghcr.io/danny-avila/librechat-dev:latest | |
# Install Python dependencies | |
USER root | |
RUN apk update && apk add --no-cache \ | |
python3 \ | |
py3-pip \ | |
sqlite \ | |
&& pip3 install flask werkzeug waitress requests pymongo waitress --break-system-packages | |
# Setup directory structure | |
RUN mkdir -p /app/{admin/{templates,static},data,uploads,client/public/images,api/logs} \ | |
&& mkdir -p /app/uploads/temp \ | |
&& chown -R 1000:1000 /app \ | |
&& chmod -R 777 /app/uploads \ | |
&& chmod -R 777 /app/client/public/images \ | |
&& chmod -R 777 /app/api/logs | |
# ===== ADMIN PANEL (Adapted from LibreChat community) ===== | |
COPY <<"EOF" /app/admin/app.py | |
from flask import Flask, request, jsonify, render_template | |
from pymongo import MongoClient | |
from werkzeug.security import generate_password_hash | |
import os | |
from waitress import serve | |
app = Flask(__name__, template_folder='/app/admin/templates') | |
app.secret_key = os.getenv("FLASK_SECRET") | |
# Connect to LibreChat's MongoDB | |
client = MongoClient(os.getenv("MONGO_URI", "mongodb://localhost:27017")) | |
db = client.get_default_database() | |
@app.route('/admin') | |
def admin_home(): | |
return render_template('login.html') | |
@app.route('/admin/login', methods=['POST']) | |
def login(): | |
if request.json.get('admin_secret') == os.getenv("SUDO_SECRET"): | |
return jsonify({"status": "success", "token": os.getenv("SUDO_SECRET")}) | |
return jsonify({"error": "Invalid secret"}), 403 | |
@app.route('/admin/users', methods=['GET']) | |
def list_users(): | |
if request.headers.get('X-Admin-Token') != os.getenv("SUDO_SECRET"): | |
return jsonify({"error": "Unauthorized"}), 403 | |
users = list(db.users.find({}, {"_id": 0, "username": 1, "email": 1})) | |
return jsonify(users) | |
@app.route('/admin/users', methods=['POST']) | |
def add_user(): | |
if request.headers.get('X-Admin-Token') != os.getenv("SUDO_SECRET"): | |
return jsonify({"error": "Unauthorized"}), 403 | |
user_data = { | |
"username": request.json["username"], | |
"password": generate_password_hash(request.json["password"]), | |
"email": request.json.get("email", ""), | |
"role": "user" | |
} | |
db.users.insert_one(user_data) | |
return jsonify({"status": "User added"}) | |
@app.route('/admin/users', methods=['DELETE']) | |
def delete_user(): | |
if request.headers.get('X-Admin-Token') != os.getenv("SUDO_SECRET"): | |
return jsonify({"error": "Unauthorized"}), 403 | |
db.users.delete_one({"username": request.json["username"]}) | |
return jsonify({"status": "User deleted"}) | |
if __name__ == "__main__": | |
serve(app, host='0.0.0.0', port=5000) | |
EOF | |
# ===== ADMIN TEMPLATES ===== | |
COPY <<"EOF" /app/admin/templates/login.html | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Admin Login</title> | |
<style> | |
body { font-family: Arial; padding: 20px; } | |
form { max-width: 400px; margin: 0 auto; } | |
input, button { width: 100%; padding: 8px; margin: 5px 0; } | |
</style> | |
</head> | |
<body> | |
<form id="loginForm"> | |
<h2>Admin Login</h2> | |
<input type="password" placeholder="Admin Secret" required> | |
<button type="submit">Login</button> | |
</form> | |
<script> | |
document.getElementById('loginForm').onsubmit = async (e) => { | |
e.preventDefault(); | |
const response = await fetch('/admin/login', { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify({ admin_secret: e.target[0].value }) | |
}); | |
if (response.ok) { | |
localStorage.setItem('admin_token', await response.text()); | |
window.location.href = '/admin/dashboard'; | |
} else { | |
alert('Invalid secret!'); | |
} | |
}; | |
</script> | |
</body> | |
</html> | |
EOF | |
# Startup script | |
COPY <<"EOF" /app/start.sh | |
#!/bin/sh | |
# Start the combined server | |
cd /app && npm run start:backend & | |
python3 /app/admin/app.py & | |
wait | |
EOF | |
RUN chmod +x /app/start.sh | |
# Environment variables | |
ENV HOST=0.0.0.0 \ | |
PORT=3080 \ | |
SESSION_EXPIRY=900000 \ | |
REFRESH_TOKEN_EXPIRY=604800000 \ | |
SEARCH=true \ | |
MEILI_NO_ANALYTICS=true \ | |
MEILI_HOST=https://martynka-meilisearch.hf.space \ | |
CONFIG_PATH=/app/librechat.yaml \ | |
CUSTOM_FOOTER=EasierIT \ | |
MONGO_URI="$MONGO_URI" \ | |
SUDO_SECRET="$SUDO_SECRET" \ | |
FLASK_SECRET="$FLASK_SECRET" \ | |
NODE_ENV=production | |
EXPOSE 7860 | |
CMD ["/app/start.sh"] |