Spaces:
Running
Running
Update Dockerfile
Browse files- Dockerfile +37 -63
Dockerfile
CHANGED
@@ -158,7 +158,7 @@ EOF
|
|
158 |
|
159 |
# ===== Fixed Combined Server =====
|
160 |
COPY <<"EOF" /app/combined_server.py
|
161 |
-
from flask import Flask, request, jsonify, render_template,
|
162 |
from werkzeug.middleware.proxy_fix import ProxyFix
|
163 |
from werkzeug.security import generate_password_hash
|
164 |
import os
|
@@ -171,8 +171,9 @@ import threading
|
|
171 |
import time
|
172 |
|
173 |
app = Flask(__name__,
|
174 |
-
|
175 |
-
static_folder='/app/admin/static'
|
|
|
176 |
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)
|
177 |
app.secret_key = os.getenv("FLASK_SECRET")
|
178 |
|
@@ -187,106 +188,79 @@ def get_db():
|
|
187 |
return db
|
188 |
|
189 |
# ===== Admin Routes =====
|
|
|
190 |
@app.route('/sudo')
|
191 |
def admin_home():
|
192 |
return render_template('login.html')
|
193 |
|
|
|
194 |
@app.route('/sudo/login', methods=['POST'])
|
195 |
def login():
|
196 |
if request.json.get('sudo_secret') == os.getenv("SUDO_SECRET"):
|
197 |
-
return jsonify({
|
|
|
|
|
|
|
|
|
198 |
return jsonify({"error": "Invalid secret"}), 403
|
199 |
|
|
|
200 |
@app.route('/sudo/dashboard')
|
201 |
def dashboard():
|
202 |
return render_template('dashboard.html')
|
203 |
|
204 |
-
|
205 |
-
def add_user():
|
206 |
-
if request.headers.get('X-Sudo-Secret') != os.getenv("SUDO_SECRET"):
|
207 |
-
return jsonify({"error": "Unauthorized"}), 403
|
208 |
-
|
209 |
-
db = get_db()
|
210 |
-
try:
|
211 |
-
db.execute("INSERT INTO users VALUES (?,?,?)", [
|
212 |
-
request.json["username"],
|
213 |
-
generate_password_hash(request.json["password"]),
|
214 |
-
"user"
|
215 |
-
])
|
216 |
-
db.commit()
|
217 |
-
return jsonify({"status": "User added"})
|
218 |
-
except sqlite3.IntegrityError:
|
219 |
-
return jsonify({"error": "User exists"}), 400
|
220 |
-
|
221 |
-
@app.route('/sudo/list_users', methods=['GET'])
|
222 |
-
def list_users():
|
223 |
-
if request.headers.get('X-Sudo-Secret') != os.getenv("SUDO_SECRET"):
|
224 |
-
return jsonify({"error": "Unauthorized"}), 403
|
225 |
-
|
226 |
-
db = get_db()
|
227 |
-
return jsonify([
|
228 |
-
{"username": row[0]} for row in db.execute("SELECT username FROM users")
|
229 |
-
])
|
230 |
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
db = get_db()
|
237 |
-
db.execute("DELETE FROM users WHERE username=?", [request.json["username"]])
|
238 |
-
db.commit()
|
239 |
-
return jsonify({"status": "User removed"})
|
240 |
|
241 |
# ===== LibreChat Proxy =====
|
242 |
def is_librechat_ready():
|
243 |
try:
|
244 |
-
return requests.get('http://localhost:3080', timeout=1).status_code < 500
|
245 |
except:
|
246 |
return False
|
247 |
|
248 |
@app.route('/', defaults={'path': ''})
|
249 |
-
@app.route('/<path:path>'
|
250 |
def proxy(path):
|
251 |
# Don't proxy admin routes
|
252 |
-
if path.startswith('sudo/'
|
253 |
-
return
|
254 |
|
255 |
-
# Wait for
|
256 |
-
|
257 |
-
|
258 |
-
break
|
259 |
-
time.sleep(1)
|
260 |
-
else:
|
261 |
-
return make_response("LibreChat backend not ready", 503)
|
262 |
|
263 |
-
# Forward request
|
264 |
resp = requests.request(
|
265 |
method=request.method,
|
266 |
url=f"http://localhost:3080/{path}",
|
267 |
-
headers={
|
268 |
data=request.get_data(),
|
269 |
cookies=request.cookies,
|
270 |
-
allow_redirects=False
|
271 |
-
timeout=30
|
272 |
)
|
273 |
|
274 |
-
#
|
275 |
-
response = make_response(resp.content, resp.status_code)
|
276 |
-
for
|
277 |
-
if
|
278 |
-
response.headers[
|
279 |
return response
|
280 |
|
281 |
def start_librechat():
|
282 |
-
subprocess.
|
283 |
|
284 |
if __name__ == "__main__":
|
285 |
-
# Start LibreChat
|
286 |
threading.Thread(target=start_librechat, daemon=True).start()
|
287 |
|
288 |
-
# Start
|
289 |
-
|
|
|
290 |
EOF
|
291 |
|
292 |
# Startup script
|
|
|
158 |
|
159 |
# ===== Fixed Combined Server =====
|
160 |
COPY <<"EOF" /app/combined_server.py
|
161 |
+
from flask import Flask, request, jsonify, render_template, send_from_directory
|
162 |
from werkzeug.middleware.proxy_fix import ProxyFix
|
163 |
from werkzeug.security import generate_password_hash
|
164 |
import os
|
|
|
171 |
import time
|
172 |
|
173 |
app = Flask(__name__,
|
174 |
+
static_url_path='/admin/static',
|
175 |
+
static_folder='/app/admin/static',
|
176 |
+
template_folder='/app/admin/templates')
|
177 |
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)
|
178 |
app.secret_key = os.getenv("FLASK_SECRET")
|
179 |
|
|
|
188 |
return db
|
189 |
|
190 |
# ===== Admin Routes =====
|
191 |
+
@app.route('/admin')
|
192 |
@app.route('/sudo')
|
193 |
def admin_home():
|
194 |
return render_template('login.html')
|
195 |
|
196 |
+
@app.route('/admin/login', methods=['POST'])
|
197 |
@app.route('/sudo/login', methods=['POST'])
|
198 |
def login():
|
199 |
if request.json.get('sudo_secret') == os.getenv("SUDO_SECRET"):
|
200 |
+
return jsonify({
|
201 |
+
"status": "success",
|
202 |
+
"token": os.getenv("SUDO_SECRET"),
|
203 |
+
"redirect": "/admin/dashboard"
|
204 |
+
})
|
205 |
return jsonify({"error": "Invalid secret"}), 403
|
206 |
|
207 |
+
@app.route('/admin/dashboard')
|
208 |
@app.route('/sudo/dashboard')
|
209 |
def dashboard():
|
210 |
return render_template('dashboard.html')
|
211 |
|
212 |
+
# [Keep other admin routes...]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
|
214 |
+
# Serve admin static files
|
215 |
+
@app.route('/admin/static/<path:filename>')
|
216 |
+
def admin_static(filename):
|
217 |
+
return send_from_directory('/app/admin/static', filename)
|
|
|
|
|
|
|
|
|
|
|
218 |
|
219 |
# ===== LibreChat Proxy =====
|
220 |
def is_librechat_ready():
|
221 |
try:
|
222 |
+
return requests.get('http://localhost:3080/api', timeout=1).status_code < 500
|
223 |
except:
|
224 |
return False
|
225 |
|
226 |
@app.route('/', defaults={'path': ''})
|
227 |
+
@app.route('/<path:path>')
|
228 |
def proxy(path):
|
229 |
# Don't proxy admin routes
|
230 |
+
if path.startswith(('admin/', 'sudo/', 'admin', 'sudo')):
|
231 |
+
return "Route not found", 404
|
232 |
|
233 |
+
# Wait for backend
|
234 |
+
if not is_librechat_ready():
|
235 |
+
return "LibreChat backend starting...", 503
|
|
|
|
|
|
|
|
|
236 |
|
237 |
+
# Forward request
|
238 |
resp = requests.request(
|
239 |
method=request.method,
|
240 |
url=f"http://localhost:3080/{path}",
|
241 |
+
headers={k: v for k, v in request.headers if k.lower() != 'host'},
|
242 |
data=request.get_data(),
|
243 |
cookies=request.cookies,
|
244 |
+
allow_redirects=False
|
|
|
245 |
)
|
246 |
|
247 |
+
# Build response
|
248 |
+
response = app.make_response((resp.content, resp.status_code))
|
249 |
+
for k, v in resp.headers.items():
|
250 |
+
if k.lower() not in ['content-length', 'transfer-encoding']:
|
251 |
+
response.headers[k] = v
|
252 |
return response
|
253 |
|
254 |
def start_librechat():
|
255 |
+
subprocess.Popen(["npm", "run", "backend"], cwd="/app")
|
256 |
|
257 |
if __name__ == "__main__":
|
258 |
+
# Start LibreChat
|
259 |
threading.Thread(target=start_librechat, daemon=True).start()
|
260 |
|
261 |
+
# Start server
|
262 |
+
print("Starting combined server on port 7860")
|
263 |
+
serve(app, host='0.0.0.0', port=7860, threads=4)
|
264 |
EOF
|
265 |
|
266 |
# Startup script
|