librechat / Dockerfile
martynka's picture
Update Dockerfile
170a8ce verified
raw
history blame
7.82 kB
FROM ghcr.io/danny-avila/librechat-dev:latest
# Install dependencies
USER root
RUN apk update && apk add --no-cache \
caddy \
python3 \
py3-pip \
py3-dotenv \
git
RUN pip install flask pymongo[srv] --break-system-packages
COPY config.yaml /app/librechat.yaml
#secrets
RUN --mount=type=secret,example=SUDO_SECRET,required=true cat /run/secrets/SUDO_SECRET > /app/sudo.sec
RUN export SUDO_SECRET=$(cat /app/sudo.sec)
RUN --mount=type=secret,example=MONGO_URI,required=true cat /run/secrets/MONGO_URI > /app/mongo.sec
RUN export MONGO_URI=$(cat /app/mongo.sec)
RUN --mount=type=secret,example=FLASK_SECRET,required=true cat /run/secrets/FLASK_SECRET > /app/flask.sec
RUN export FLASK_SECRET=$(cat /app/flask.sec)
# Create admin structure
RUN mkdir -p /app/sudo/{templates,static} \
&& chown -R 1000:1000 /app
# 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" \
ADMIN_SECRET="$SUDO_SECRET" \
FLASK_SECRET="$FLASK_SECRET" \
NODE_ENV=production
# HTML Admin Panel
COPY <<"EOF" /app/sudo/templates/index.html
<!DOCTYPE html>
<html>
<head>
<title>LibreChat Admin</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
.container { max-width: 1000px; margin: 0 auto; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
button { padding: 6px 12px; cursor: pointer; }
.login-form { max-width: 400px; margin: 50px auto; }
</style>
</head>
<body>
<div id="login" class="login-form" style="display: block;">
<h2>Admin Login</h2>
<input type="password" id="password" placeholder="Admin Password">
<button onclick="login()">Login</button>
</div>
<div id="admin-panel" class="container" style="display: none;">
<h1>User Management</h1>
<div>
<input type="text" id="new-username" placeholder="Username">
<input type="password" id="new-password" placeholder="Password">
<button onclick="addUser()">Add User</button>
</div>
<table id="users-table">
<thead>
<tr>
<th>Username</th>
<th>Actions</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<script>
let authToken = '';
async function login() {
const password = document.getElementById('password').value;
const response = await fetch('/sudo/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password })
});
if (response.ok) {
const data = await response.json();
authToken = data.token;
document.getElementById('login').style.display = 'none';
document.getElementById('admin-panel').style.display = 'block';
loadUsers();
} else {
alert('Login failed!');
}
}
async function loadUsers() {
const response = await fetch('/sudo/users', {
headers: { 'X-Auth-Token': authToken }
});
const users = await response.json();
const tbody = document.querySelector('#users-table tbody');
tbody.innerHTML = users.map(user => `
<tr>
<td>${user.username}</td>
<td>
<button onclick="deleteUser('${user.username}')">Delete</button>
</td>
</tr>
`).join('');
}
async function addUser() {
const username = document.getElementById('new-username').value;
const password = document.getElementById('new-password').value;
const response = await fetch('/sudo/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Auth-Token': authToken
},
body: JSON.stringify({ username, password })
});
if (response.ok) {
loadUsers();
document.getElementById('new-username').value = '';
document.getElementById('new-password').value = '';
}
}
async function deleteUser(username) {
if (confirm(`Delete ${username}?`)) {
await fetch(`/sudo/users/${username}`, {
method: 'DELETE',
headers: { 'X-Auth-Token': authToken }
});
loadUsers();
}
}
</script>
</body>
</html>
EOF
# Admin Backend
COPY <<"EOF" /app/sudo/app.py
from flask import Flask, request, jsonify, render_template
from pymongo.mongo_client import MongoClient
from pymongo.server_api import ServerApi
from werkzeug.security import generate_password_hash
import os
import hmac
from functools import wraps
app = Flask(__name__, template_folder='/app/sudo/templates')
app.secret_key = os.getenv("FLASK_SECRET")
# MongoDB connection
uri = os.getenv("MONGO_URI")
client = MongoClient(uri, server_api=ServerApi('1'))
db = client['librechat']
ADMIN_SECRET = os.getenv("ADMIN_SECRET")
# Authentication decorator
def require_auth(f):
@wraps(f)
def wrapper(*args, **kwargs):
auth_token = request.headers.get('X-Auth-Token')
if not auth_token or not hmac.compare_digest(auth_token, ADMIN_SECRET):
return jsonify({"error": "Unauthorized"}), 403
return f(*args, **kwargs)
return wrapper
# Routes
@app.route('/sudo')
def admin_panel():
return render_template('index.html')
@app.route('/sudo/login', methods=['POST'])
def login():
if not hmac.compare_digest(request.json.get('password') or '', ADMIN_SECRET):
return jsonify({"error": "Invalid credentials"}), 401
return jsonify({"token": ADMIN_SECRET})
@app.route('/sudo/users', methods=['GET'])
@require_auth
def list_users():
users = list(db.users.find({}, {"_id": 0, "username": 1}))
return jsonify(users)
@app.route('/sudo/users', methods=['POST'])
@require_auth
def add_user():
user_data = {
"username": request.json["username"],
"password": generate_password_hash(request.json["password"]),
"role": "user"
}
db.users.insert_one(user_data)
return jsonify({"status": "User added"})
@app.route('/sudo/users/<username>', methods=['DELETE'])
@require_auth
def delete_user(username):
result = db.users.delete_one({"username": username})
if result.deleted_count == 0:
return jsonify({"error": "User not found"}), 404
return jsonify({"status": "User deleted"})
@app.route('/sudo/debug')
def debug():
return jsonify({
"expected_password": os.getenv("ADMIN_SECRET", "NOT_SET!"),
"flask_secret_set": bool(os.getenv("FLASK_SECRET")),
"mongo_connected": bool(client)
})
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000)
EOF
# Caddy Configuration
RUN mkdir -p /app/caddy/
COPY Caddyfile /app/caddy/Caddyfile
# Startup script
COPY <<"EOF" /app/start.sh
#!/bin/sh
# Start the combined server
cd /app && npm run backend &
python3 /app/sudo/app.py &
caddy run --config /app/caddy/Caddyfile &
wait
EOF
RUN chmod +x /app/start.sh
EXPOSE 7860
CMD ["/app/start.sh"]