Spaces:
Running
Running
File size: 7,704 Bytes
54777dd b59e002 9a077eb 8ac3c5c 5bf8328 9a077eb 8ac3c5c 722d328 f802c65 2ac221e 90935a4 df1afd2 90935a4 df1afd2 90935a4 df1afd2 90935a4 2ac221e 9a077eb 2ac221e b7b0a80 5b8c5c8 b7b0a80 2ac221e 38cb880 2ac221e 9a077eb 2ac221e 067fc82 a0be3b9 4b64cf1 2007f57 9a077eb 0ed11fb 38cb880 4b64cf1 daa52a6 9a077eb 2ac221e daa52a6 9a077eb 2ac221e 9a077eb 2ac221e 9a077eb 38cb880 9a077eb 38cb880 4b64cf1 38cb880 4b64cf1 37c0dcf 9a077eb 38cb880 4b64cf1 38cb880 9a077eb 38cb880 9a077eb a0be3b9 44d10b9 26b94dc 9a077eb 4b64cf1 2ac221e 0830230 2ac221e 4b64cf1 26b94dc 1046ebd 95ce71e 0830230 4b64cf1 8ac3c5c 2ac221e 26b94dc 8ac3c5c 26b94dc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
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 \
&& pip install flask pymongo[srv] --break-system-packages
COPY config.yaml /app/librechat.yaml
#secrets
RUN --mount=type=secret,id=SUDO_SECRET,required=true \
export SUDO_SECRET=$(cat /run/secrets/SUDO_SECRET)
RUN --mount=type=secret,id=MONGO_URI,required=true \
export MONGO_URI=$(cat /run/secrets/MONGO_URI)
RUN --mount=type=secret,id=FLASK_SECRET,required=true \
export FLASK_SECRET=$(cat /run/secrets/FLASK_SECRET)
# 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"] |