martynka commited on
Commit
26b94dc
·
verified ·
1 Parent(s): 35599bf

Update Dockerfile

Browse files
Files changed (1) hide show
  1. Dockerfile +116 -192
Dockerfile CHANGED
@@ -1,150 +1,32 @@
1
  # Use official LibreChat base image
2
  FROM ghcr.io/danny-avila/librechat-dev:latest
3
 
4
- # Install system dependencies
5
  USER root
6
  RUN apk update && apk add --no-cache \
7
- nginx \
8
  python3 \
9
  py3-pip \
10
  sqlite \
11
- && pip3 install flask werkzeug --break-system-packages \
12
- && mkdir -p /var/lib/nginx/tmp \
13
- && chown -R nginx:nginx /var/lib/nginx \
14
- && chmod -R 770 /var/lib/nginx \
15
- && mkdir -p /app/admin/{templates,static} \
16
- && mkdir -p /app/data \
17
  && chown -R 1000:1000 /app \
18
- && mkdir -p /app/uploads/temp \
19
- && chmod -R 777 /app/uploads/temp \
20
  && chmod -R 777 /app/client/public/images \
21
- && chmod -R 777 /app/api/logs \
22
- && mkdir -p /app/{nginx/{logs,tmp,client_body},admin/{templates,static},data,uploads,client/public/images,api/logs} \
23
- && mkdir -p /app/nginx/client_body \
24
- && chmod -R 777 /app/nginx/client_body
25
- # 2. Recompile NGINX with custom paths (critical fix)
26
- RUN apk add --no-cache --virtual .build-deps \
27
- build-base \
28
- linux-headers \
29
- openssl-dev \
30
- pcre-dev \
31
- zlib-dev \
32
- && wget http://nginx.org/download/nginx-1.24.0.tar.gz \
33
- && tar xzf nginx-1.24.0.tar.gz \
34
- && cd nginx-1.24.0 \
35
- && ./configure \
36
- --prefix=/app/nginx \
37
- --sbin-path=/usr/sbin/nginx \
38
- --conf-path=/app/nginx/nginx.conf \
39
- --error-log-path=/app/nginx/logs/error.log \
40
- --pid-path=/app/nginx/nginx.pid \
41
- --lock-path=/app/nginx/nginx.lock \
42
- --http-log-path=/app/nginx/logs/access.log \
43
- --http-client-body-temp-path=/app/nginx/client_body \
44
- --http-proxy-temp-path=/app/nginx/tmp \
45
- --http-fastcgi-temp-path=/app/nginx/tmp \
46
- --user=nginx \
47
- --group=nginx \
48
- && make \
49
- && make install \
50
- && apk del .build-deps \
51
- && rm -rf /nginx-1.24.0*
52
- # ===== Admin Panel =====
53
- COPY <<"EOF" /app/admin/app.py
54
- from flask import Flask, request, jsonify, render_template
55
- import os
56
- import sqlite3
57
- from werkzeug.security import generate_password_hash
58
- from pathlib import Path
59
-
60
- app = Flask(__name__,
61
- template_folder=str(Path(__file__).parent/'templates'),
62
- static_folder=str(Path(__file__).parent/'static'))
63
-
64
- # Validate secrets
65
- required_secrets = ['FLASK_SECRET', 'SUDO_SECRET']
66
- for secret in required_secrets:
67
- if not os.getenv(secret):
68
- raise RuntimeError(f"Missing required secret: {secret}")
69
-
70
- app.secret_key = os.getenv("FLASK_SECRET")
71
-
72
- # SQLite database
73
- DB_PATH = '/app/data/admin.db'
74
-
75
- def get_db():
76
- Path(DB_PATH).parent.mkdir(exist_ok=True)
77
- db = sqlite3.connect(DB_PATH)
78
- db.execute('''CREATE TABLE IF NOT EXISTS users
79
- (username TEXT PRIMARY KEY, password TEXT, role TEXT)''')
80
- return db
81
-
82
- @app.route('/sudo')
83
- def home():
84
- return render_template('login.html')
85
-
86
- @app.route('/sudo/login', methods=['POST'])
87
- def login():
88
- if request.json.get('sudo_secret') == os.getenv("SUDO_SECRET"):
89
- return jsonify({"status": "success"})
90
- return jsonify({"error": "Invalid secret"}), 403
91
-
92
- @app.route('/sudo/dashboard')
93
- def dashboard():
94
- return render_template('dashboard.html')
95
-
96
- @app.route('/sudo/add_user', methods=['POST'])
97
- def add_user():
98
- if request.headers.get('X-Sudo-Secret') != os.getenv("SUDO_SECRET"):
99
- return jsonify({"error": "Unauthorized"}), 403
100
-
101
- db = get_db()
102
- try:
103
- db.execute("INSERT INTO users VALUES (?,?,?)", [
104
- request.json["username"],
105
- generate_password_hash(request.json["password"]),
106
- "user"
107
- ])
108
- db.commit()
109
- return jsonify({"status": "User added"})
110
- except sqlite3.IntegrityError:
111
- return jsonify({"error": "User exists"}), 400
112
-
113
- @app.route('/sudo/list_users', methods=['GET'])
114
- def list_users():
115
- if request.headers.get('X-Sudo-Secret') != os.getenv("SUDO_SECRET"):
116
- return jsonify({"error": "Unauthorized"}), 403
117
-
118
- db = get_db()
119
- return jsonify([
120
- {"username": row[0]} for row in db.execute("SELECT username FROM users")
121
- ])
122
-
123
- @app.route('/sudo/remove_user', methods=['POST'])
124
- def remove_user():
125
- if request.headers.get('X-Sudo-Secret') != os.getenv("SUDO_SECRET"):
126
- return jsonify({"error": "Unauthorized"}), 403
127
-
128
- db = get_db()
129
- db.execute("DELETE FROM users WHERE username=?", [request.json["username"]])
130
- db.commit()
131
- return jsonify({"status": "User removed"})
132
-
133
- if __name__ == "__main__":
134
- app.run(host="0.0.0.0", port=5000)
135
- EOF
136
 
137
- # Admin templates
138
  COPY <<"EOF" /app/admin/templates/login.html
139
  <!DOCTYPE html>
140
  <html>
141
  <head>
142
- <title>LibreChat Admin</title>
143
  <link rel="stylesheet" href="/static/admin.css">
144
  </head>
145
  <body>
146
  <div class="container">
147
- <h2>LibreChat Admin</h2>
148
  <form id="loginForm">
149
  <input type="password" name="sudo_secret" placeholder="Admin Secret" required>
150
  <button type="submit">Login</button>
@@ -183,7 +65,7 @@ COPY <<"EOF" /app/admin/templates/dashboard.html
183
  </html>
184
  EOF
185
 
186
- # Static files
187
  COPY <<"EOF" /app/admin/static/admin.css
188
  body {
189
  font-family: Arial, sans-serif;
@@ -273,81 +155,123 @@ document.addEventListener('DOMContentLoaded', () => {
273
  });
274
  EOF
275
 
276
- COPY <<"EOF" /app/nginx/nginx.conf
277
- worker_processes auto;
278
-
279
- error_log /app/nginx/logs/error.log warn;
280
- pid /tmp/nginx.pid;
 
 
 
 
 
 
281
 
282
- events {
283
- worker_connections 1024;
284
- }
 
 
285
 
286
- http {
287
- include /etc/nginx/mime.types;
288
- default_type application/octet-stream;
289
- sendfile on;
290
- keepalive_timeout 65;
291
- client_max_body_size 20M;
292
-
293
- client_body_temp_path /app/nginx/client_body;
294
- proxy_temp_path /app/nginx/tmp;
295
- fastcgi_temp_path /app/nginx/tmp;
296
- uwsgi_temp_path /app/nginx/tmp;
297
- scgi_temp_path /app/nginx/tmp;
298
 
299
- server {
300
- listen 7860;
301
- server_name localhost;
302
- root /app;
 
 
303
 
304
- # LibreChat API
305
- location / {
306
- proxy_pass http://localhost:3080;
307
- proxy_set_header Host \$host;
308
- proxy_set_header X-Real-IP \$remote_addr;
309
- proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
310
- }
311
 
312
- # Admin Panel
313
- location /sudo {
314
- proxy_pass http://localhost:5000;
315
- proxy_set_header X-Sudo-Secret \$http_x_sudo_secret;
316
- }
317
 
318
- # Static files
319
- location /static {
320
- alias /app/admin/static;
321
- expires 30d;
322
- access_log off;
323
- }
324
 
325
- access_log /app/nginx/logs/access.log;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
- }
328
- }
329
- EOF
330
- RUN chmod 777 /app/nginx/nginx.conf
331
- # Startup script
332
- COPY <<"EOF" /start.sh
333
- #!/bin/sh
334
- # Fix permissions
335
- #chown -R nginx:nginx /var/lib/nginx
336
- #chmod -R 770 /var/lib/nginx
337
 
338
- # Start LibreChat (using the correct command)
339
- cd /app && npm run backend &
 
 
 
 
 
 
 
340
 
341
- # Start Admin Panel
342
- cd /app/admin && python3 app.py &
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
 
344
- # Start NGINX
345
- nginx -c /app/nginx/nginx.conf -g "daemon off;"
 
 
 
 
 
346
 
347
- # Keep container running
348
- wait
 
 
 
349
  EOF
350
- RUN chmod +x /start.sh
351
 
352
  # Environment variables
353
  ENV HOST=0.0.0.0 \
@@ -365,4 +289,4 @@ ENV HOST=0.0.0.0 \
365
  NODE_ENV=production
366
 
367
  EXPOSE 7860
368
- CMD ["/start.sh"]
 
1
  # Use official LibreChat base image
2
  FROM ghcr.io/danny-avila/librechat-dev:latest
3
 
4
+ # Install Python dependencies
5
  USER root
6
  RUN apk update && apk add --no-cache \
 
7
  python3 \
8
  py3-pip \
9
  sqlite \
10
+ && pip3 install flask werkzeug waitress requests --break-package-system
11
+
12
+ # Setup directory structure
13
+ RUN mkdir -p /app/{admin/{templates,static},data,uploads,client/public/images,api/logs} \
 
 
14
  && chown -R 1000:1000 /app \
15
+ && chmod -R 777 /app/uploads \
 
16
  && chmod -R 777 /app/client/public/images \
17
+ && chmod -R 777 /app/api/logs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ # ===== Admin Templates =====
20
  COPY <<"EOF" /app/admin/templates/login.html
21
  <!DOCTYPE html>
22
  <html>
23
  <head>
24
+ <title>Admin Login</title>
25
  <link rel="stylesheet" href="/static/admin.css">
26
  </head>
27
  <body>
28
  <div class="container">
29
+ <h2>Admin Portal</h2>
30
  <form id="loginForm">
31
  <input type="password" name="sudo_secret" placeholder="Admin Secret" required>
32
  <button type="submit">Login</button>
 
65
  </html>
66
  EOF
67
 
68
+ # ===== Admin Static Files =====
69
  COPY <<"EOF" /app/admin/static/admin.css
70
  body {
71
  font-family: Arial, sans-serif;
 
155
  });
156
  EOF
157
 
158
+ # ===== Combined Server =====
159
+ COPY <<"EOF" /app/combined_server.py
160
+ from flask import Flask, request, jsonify, render_template, make_response
161
+ from werkzeug.middleware.proxy_fix import ProxyFix
162
+ from werkzeug.security import generate_password_hash
163
+ import os
164
+ import sqlite3
165
+ from pathlib import Path
166
+ import subprocess
167
+ import requests
168
+ from waitress import serve
169
 
170
+ app = Flask(__name__,
171
+ template_folder='/app/admin/templates',
172
+ static_folder='/app/admin/static')
173
+ app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)
174
+ app.secret_key = os.getenv("FLASK_SECRET")
175
 
176
+ # Database setup
177
+ DB_PATH = '/app/data/admin.db'
 
 
 
 
 
 
 
 
 
 
178
 
179
+ def get_db():
180
+ Path(DB_PATH).parent.mkdir(exist_ok=True)
181
+ db = sqlite3.connect(DB_PATH)
182
+ db.execute('''CREATE TABLE IF NOT EXISTS users
183
+ (username TEXT PRIMARY KEY, password TEXT, role TEXT)''')
184
+ return db
185
 
186
+ # Admin routes
187
+ @app.route('/sudo')
188
+ def admin_home():
189
+ return render_template('login.html')
 
 
 
190
 
191
+ @app.route('/sudo/login', methods=['POST'])
192
+ def login():
193
+ if request.json.get('sudo_secret') == os.getenv("SUDO_SECRET"):
194
+ return jsonify({"status": "success"})
195
+ return jsonify({"error": "Invalid secret"}), 403
196
 
197
+ @app.route('/sudo/dashboard')
198
+ def dashboard():
199
+ return render_template('dashboard.html')
 
 
 
200
 
201
+ @app.route('/sudo/add_user', methods=['POST'])
202
+ def add_user():
203
+ if request.headers.get('X-Sudo-Secret') != os.getenv("SUDO_SECRET"):
204
+ return jsonify({"error": "Unauthorized"}), 403
205
+
206
+ db = get_db()
207
+ try:
208
+ db.execute("INSERT INTO users VALUES (?,?,?)", [
209
+ request.json["username"],
210
+ generate_password_hash(request.json["password"]),
211
+ "user"
212
+ ])
213
+ db.commit()
214
+ return jsonify({"status": "User added"})
215
+ except sqlite3.IntegrityError:
216
+ return jsonify({"error": "User exists"}), 400
217
 
218
+ @app.route('/sudo/list_users', methods=['GET'])
219
+ def list_users():
220
+ if request.headers.get('X-Sudo-Secret') != os.getenv("SUDO_SECRET"):
221
+ return jsonify({"error": "Unauthorized"}), 403
222
+
223
+ db = get_db()
224
+ return jsonify([
225
+ {"username": row[0]} for row in db.execute("SELECT username FROM users")
226
+ ])
 
227
 
228
+ @app.route('/sudo/remove_user', methods=['POST'])
229
+ def remove_user():
230
+ if request.headers.get('X-Sudo-Secret') != os.getenv("SUDO_SECRET"):
231
+ return jsonify({"error": "Unauthorized"}), 403
232
+
233
+ db = get_db()
234
+ db.execute("DELETE FROM users WHERE username=?", [request.json["username"]])
235
+ db.commit()
236
+ return jsonify({"status": "User removed"})
237
 
238
+ # LibreChat proxy
239
+ @app.route('/', defaults={'path': ''})
240
+ @app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
241
+ def proxy(path):
242
+ if path.startswith('sudo/') or path == 'sudo':
243
+ return make_response("Not Found", 404)
244
+
245
+ resp = requests.request(
246
+ method=request.method,
247
+ url=f"http://localhost:3080/{path}",
248
+ headers={key: value for (key, value) in request.headers if key != 'Host'},
249
+ data=request.get_data(),
250
+ cookies=request.cookies,
251
+ allow_redirects=False
252
+ )
253
+
254
+ response = make_response(resp.content, resp.status_code)
255
+ for key, value in resp.headers.items():
256
+ if key.lower() not in ['content-encoding', 'content-length', 'transfer-encoding']:
257
+ response.headers[key] = value
258
+ return response
259
 
260
+ if __name__ == "__main__":
261
+ # Start LibreChat backend
262
+ subprocess.Popen(["npm", "run", "start:backend"], cwd="/app")
263
+
264
+ # Start combined server
265
+ serve(app, host='0.0.0.0', port=7860, threads=4)
266
+ EOF
267
 
268
+ # Startup script
269
+ COPY <<"EOF" /app/start.sh
270
+ #!/bin/sh
271
+ # Start the combined server
272
+ python3 /app/combined_server.py
273
  EOF
274
+ RUN chmod +x /app/start.sh
275
 
276
  # Environment variables
277
  ENV HOST=0.0.0.0 \
 
289
  NODE_ENV=production
290
 
291
  EXPOSE 7860
292
+ CMD ["/app/start.sh"]