librechat / Dockerfile
martynka's picture
Update Dockerfile
4b64cf1 verified
raw
history blame
4.43 kB
# 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"]