martynka commited on
Commit
5bf8328
·
verified ·
1 Parent(s): 8ac3c5c

Update Dockerfile

Browse files
Files changed (1) hide show
  1. Dockerfile +210 -128
Dockerfile CHANGED
@@ -1,117 +1,43 @@
1
- # Stage 1: Base LibreChat Image
2
- FROM ghcr.io/danny-avila/librechat-dev:latest AS librechat-base
3
-
4
- # Your existing customizations
5
- USER root
6
- RUN apk update && apk add git
7
- COPY logo-pp.svg /app/client/public/assets/logo.svg
8
- COPY logo-pp.svg /app/client/dist/assets/logo.svg
9
- COPY logo-pp.svg /app/node_modules/date-fns/docs/logo.svg
10
- COPY logo-pp.svg /app/node_modules/ldapjs/docs/branding/public/media/img/logo.svg
11
- COPY logo-pp.svg /app/node_modules/playwright-core/lib/vite/recorder/playwright-logo.svg
12
- COPY logo-pp.svg /app/node_modules/playwright-core/lib/vite/traceViewer/playwright-logo.svg
13
- COPY logo-pp.svg /app/node_modules/psl/browserstack-logo.svg
14
- COPY config.yaml /app/librechat.yaml
15
-
16
- # Stage 2: Admin Panel Builder
17
- FROM python:3.9-alpine AS admin-builder
18
- WORKDIR /admin
19
- RUN pip install flask pymongo werkzeug
20
- COPY admin/ /admin/
21
-
22
- # Stage 3: Final Image
23
  FROM ghcr.io/danny-avila/librechat-dev:latest
24
 
25
- # Install additional dependencies
26
  USER root
27
- RUN apk add --no-cache \
28
  nginx \
29
  python3 \
30
  py3-pip \
31
- && pip3 install flask pymongo werkzeug
 
32
 
33
- # Copy LibreChat with your customizations from base
34
- COPY --from=librechat-base /app /app
35
-
36
- # Setup directories
37
- RUN mkdir -p /admin/templates /admin/static/css /admin/static/js
38
- RUN chmod -R 777 /app/uploads/temp \
39
  && chmod -R 777 /app/client/public/images \
40
- && chmod -R 777 /app/api/logs/ \
41
- && chmod -R 777 /app/data
42
-
43
- # Copy admin panel from builder
44
- COPY --from=admin-builder /admin /admin
45
-
46
- # NGINX Configuration
47
- COPY <<EOF /etc/nginx/nginx.conf
48
- events { worker_connections 1024; }
49
- http {
50
- server {
51
- listen 7860;
52
- client_max_body_size 20M;
53
-
54
- # LibreChat
55
- location / {
56
- proxy_pass http://localhost:3080;
57
- proxy_set_header Host \$host;
58
- proxy_set_header X-Real-IP \$remote_addr;
59
- proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
60
- }
61
-
62
- # Admin Panel
63
- location /sudo {
64
- proxy_pass http://localhost:5000;
65
- proxy_set_header Host \$host;
66
- proxy_set_header X-Sudo-Secret \$http_x_sudo_secret;
67
- }
68
-
69
- # Static files
70
- location /static {
71
- proxy_pass http://localhost:5000/static;
72
- }
73
- }
74
- }
75
- EOF
76
 
77
- # Admin Panel Files
78
- COPY <<EOF /admin/templates/login.html
79
- <!DOCTYPE html>
80
- <html>
81
- <head>
82
- <title>Admin Login</title>
83
- <link rel="stylesheet" href="/static/css/admin.css">
84
- </head>
85
- <body>
86
- <div class="login-box">
87
- <h2>Admin Portal</h2>
88
- <form id="loginForm">
89
- <input type="password" name="sudo_secret" placeholder="Sudo Secret" required>
90
- <button type="submit">Login</button>
91
- </form>
92
- </div>
93
- <script src="/static/js/login.js"></script>
94
- </body>
95
- </html>
96
- EOF
97
-
98
- COPY <<EOF /admin/app.py
99
  from flask import Flask, request, jsonify, render_template
100
  import os
101
- from pymongo import MongoClient
102
  from werkzeug.security import generate_password_hash
 
103
 
104
- app = Flask(__name__, template_folder='templates', static_folder='static')
 
 
105
  app.secret_key = os.getenv("FLASK_SECRET")
106
 
107
- # MongoDB connection (using LibreChat's DB)
108
- def get_db():
109
- client = MongoClient(os.getenv("MONGO_URI"))
110
- return client.get_database()
111
 
112
- # Authentication
113
- def is_authenticated(req):
114
- return req.headers.get('X-Sudo-Secret') == os.getenv("SUDO_SECRET")
 
 
 
115
 
116
  @app.route('/sudo')
117
  def home():
@@ -123,46 +49,208 @@ def login():
123
  return jsonify({"status": "success"})
124
  return jsonify({"error": "Invalid secret"}), 403
125
 
126
- @app.route('/sudo/dashboard')
127
- def dashboard():
128
- return render_template('dashboard.html')
129
-
130
  @app.route('/sudo/add_user', methods=['POST'])
131
  def add_user():
132
- if not is_authenticated(request):
133
  return jsonify({"error": "Unauthorized"}), 403
134
 
135
  db = get_db()
136
- db.users.insert_one({
137
- "username": request.json["username"],
138
- "password": generate_password_hash(request.json["password"]),
139
- "role": "user"
140
- })
141
- return jsonify({"status": "User added"})
 
 
 
 
142
 
143
  @app.route('/sudo/list_users', methods=['GET'])
144
  def list_users():
145
- if not is_authenticated(request):
146
  return jsonify({"error": "Unauthorized"}), 403
147
 
148
  db = get_db()
149
  return jsonify([
150
- {"username": u["username"]} for u in db.users.find({}, {"username": 1})
151
  ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
- if __name__ == "__main__":
154
- app.run(host="0.0.0.0", port=5000)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  EOF
156
 
157
- # Startup Script
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  COPY <<EOF /start.sh
159
  #!/bin/sh
160
-
161
- # Start LibreChat (using your existing backend command)
162
  cd /app/api && npm run backend &
163
 
164
  # Start Admin Panel
165
- cd /admin && python3 app.py &
166
 
167
  # Start NGINX
168
  nginx -g "daemon off;"
@@ -172,19 +260,13 @@ wait
172
  EOF
173
  RUN chmod +x /start.sh
174
 
175
- # Environment Variables
176
  ENV HOST=0.0.0.0 \
177
  PORT=3080 \
178
- SESSION_EXPIRY=900000 \
179
- REFRESH_TOKEN_EXPIRY=604800000 \
180
- SEARCH=true \
181
- MEILI_NO_ANALYTICS=true \
182
- MEILI_HOST=https://martynka-meilisearch.hf.space \
183
- CONFIG_PATH=/app/librechat.yaml \
184
- CUSTOM_FOOTER=EasierIT \
185
- MONGO_URI="$MONGO_URI" \
186
- SUDO_SECRET="$SUDO_SECRET" \
187
- FLASK_SECRET="$FLASK_SECRET"
188
 
189
  EXPOSE 7860
190
  CMD ["/start.sh"]
 
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
12
 
13
+ # Setup directories within /app
14
+ RUN mkdir -p /app/admin/{templates,static} \
15
+ && chmod -R 777 /app/uploads/temp \
 
 
 
16
  && chmod -R 777 /app/client/public/images \
17
+ && chmod -R 777 /app/api/logs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ # ===== Integrated Admin Panel =====
20
+ COPY <<EOF /app/admin/app.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  from flask import Flask, request, jsonify, render_template
22
  import os
23
+ import sqlite3
24
  from werkzeug.security import generate_password_hash
25
+ from pathlib import Path
26
 
27
+ app = Flask(__name__,
28
+ template_folder=str(Path(__file__).parent/'templates'),
29
+ static_folder=str(Path(__file__).parent/'static'))
30
  app.secret_key = os.getenv("FLASK_SECRET")
31
 
32
+ # SQLite in /app/data (ephemeral in free HF Spaces)
33
+ DB_PATH = '/app/data/admin.db'
 
 
34
 
35
+ def get_db():
36
+ Path(DB_PATH).parent.mkdir(exist_ok=True)
37
+ db = sqlite3.connect(DB_PATH)
38
+ db.execute('''CREATE TABLE IF NOT EXISTS users
39
+ (username TEXT PRIMARY KEY, password TEXT, role TEXT)''')
40
+ return db
41
 
42
  @app.route('/sudo')
43
  def home():
 
49
  return jsonify({"status": "success"})
50
  return jsonify({"error": "Invalid secret"}), 403
51
 
 
 
 
 
52
  @app.route('/sudo/add_user', methods=['POST'])
53
  def add_user():
54
+ if request.headers.get('X-Sudo-Secret') != os.getenv("SUDO_SECRET"):
55
  return jsonify({"error": "Unauthorized"}), 403
56
 
57
  db = get_db()
58
+ try:
59
+ db.execute("INSERT INTO users VALUES (?,?,?)", [
60
+ request.json["username"],
61
+ generate_password_hash(request.json["password"]),
62
+ "user"
63
+ ])
64
+ db.commit()
65
+ return jsonify({"status": "User added"})
66
+ except sqlite3.IntegrityError:
67
+ return jsonify({"error": "User exists"}), 400
68
 
69
  @app.route('/sudo/list_users', methods=['GET'])
70
  def list_users():
71
+ if request.headers.get('X-Sudo-Secret') != os.getenv("SUDO_SECRET"):
72
  return jsonify({"error": "Unauthorized"}), 403
73
 
74
  db = get_db()
75
  return jsonify([
76
+ {"username": row[0]} for row in db.execute("SELECT username FROM users")
77
  ])
78
+ EOF
79
+
80
+ # Admin templates
81
+ COPY <<EOF /app/admin/templates/login.html
82
+ <!DOCTYPE html>
83
+ <html>
84
+ <head>
85
+ <title>LibreChat Admin</title>
86
+ <link rel="stylesheet" href="/static/admin.css">
87
+ </head>
88
+ <body>
89
+ <div class="container">
90
+ <h2>LibreChat Admin</h2>
91
+ <form id="loginForm">
92
+ <input type="password" name="sudo_secret" placeholder="Admin Secret" required>
93
+ <button type="submit">Login</button>
94
+ </form>
95
+ </div>
96
+ <script>
97
+ document.getElementById('loginForm').addEventListener('submit', async (e) => {
98
+ e.preventDefault();
99
+ const response = await fetch('/sudo/login', {
100
+ method: 'POST',
101
+ headers: {'Content-Type': 'application/json'},
102
+ body: JSON.stringify({sudo_secret: e.target.sudo_secret.value})
103
+ });
104
+ if (response.ok) {
105
+ window.location.href = '/sudo/dashboard';
106
+ } else {
107
+ alert('Invalid secret!');
108
+ }
109
+ });
110
+ </script>
111
+ </body>
112
+ </html>
113
+ EOF
114
+
115
+ COPY <<EOF /app/admin/templates/dashboard.html
116
+ <!DOCTYPE html>
117
+ <html>
118
+ <head>
119
+ <title>User Management</title>
120
+ <link rel="stylesheet" href="/static/admin.css">
121
+ </head>
122
+ <body>
123
+ <div class="container">
124
+ <h1>User Management</h1>
125
+ <div class="card">
126
+ <h2>Add User</h2>
127
+ <form id="addUserForm">
128
+ <input type="text" name="username" placeholder="Username" required>
129
+ <input type="password" name="password" placeholder="Password" required>
130
+ <button type="submit">Add User</button>
131
+ </form>
132
+ </div>
133
+ <div class="card">
134
+ <h2>Current Users</h2>
135
+ <ul id="userList"></ul>
136
+ </div>
137
+ </div>
138
+ <script src="/static/admin.js"></script>
139
+ </body>
140
+ </html>
141
+ EOF
142
+
143
+ # Static files
144
+ COPY <<EOF /app/admin/static/admin.css
145
+ body {
146
+ font-family: Arial, sans-serif;
147
+ margin: 0;
148
+ padding: 20px;
149
+ background-color: #f5f5f5;
150
+ }
151
+ .container {
152
+ max-width: 800px;
153
+ margin: 0 auto;
154
+ }
155
+ .card {
156
+ background: white;
157
+ padding: 20px;
158
+ margin: 20px 0;
159
+ border-radius: 8px;
160
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
161
+ }
162
+ input, button {
163
+ width: 100%;
164
+ padding: 10px;
165
+ margin: 8px 0;
166
+ box-sizing: border-box;
167
+ }
168
+ button {
169
+ background-color: #4CAF50;
170
+ color: white;
171
+ border: none;
172
+ cursor: pointer;
173
+ }
174
+ EOF
175
+
176
+ COPY <<EOF /app/admin/static/admin.js
177
+ async function loadUsers() {
178
+ const response = await fetch('/sudo/list_users', {
179
+ headers: {'X-Sudo-Secret': localStorage.getItem('sudo_token')}
180
+ });
181
+ if (response.ok) {
182
+ const users = await response.json();
183
+ document.getElementById('userList').innerHTML = users.map(user =>
184
+ `<li>${user.username}</li>`
185
+ ).join('');
186
+ }
187
+ }
188
 
189
+ document.getElementById('addUserForm').addEventListener('submit', async (e) => {
190
+ e.preventDefault();
191
+ const formData = new FormData(e.target);
192
+ const response = await fetch('/sudo/add_user', {
193
+ method: 'POST',
194
+ headers: {
195
+ 'Content-Type': 'application/json',
196
+ 'X-Sudo-Secret': localStorage.getItem('sudo_token')
197
+ },
198
+ body: JSON.stringify({
199
+ username: formData.get('username'),
200
+ password: formData.get('password')
201
+ })
202
+ });
203
+ if (response.ok) {
204
+ e.target.reset();
205
+ await loadUsers();
206
+ }
207
+ });
208
+
209
+ // Initialize
210
+ document.addEventListener('DOMContentLoaded', () => {
211
+ if (!localStorage.getItem('sudo_token') && !window.location.pathname.includes('/login')) {
212
+ window.location.href = '/sudo';
213
+ }
214
+ loadUsers();
215
+ });
216
  EOF
217
 
218
+ # NGINX Configuration
219
+ COPY <<EOF /etc/nginx/nginx.conf
220
+ events { worker_connections 1024; }
221
+ http {
222
+ server {
223
+ listen 7860;
224
+
225
+ # LibreChat API
226
+ location / {
227
+ proxy_pass http://localhost:3080;
228
+ proxy_set_header Host \$host;
229
+ proxy_set_header X-Real-IP \$remote_addr;
230
+ }
231
+
232
+ # Admin Panel
233
+ location /sudo {
234
+ proxy_pass http://localhost:5000;
235
+ proxy_set_header X-Sudo-Secret \$http_x_sudo_secret;
236
+ }
237
+
238
+ # Static files
239
+ location /static {
240
+ alias /app/admin/static;
241
+ }
242
+ }
243
+ }
244
+ EOF
245
+
246
+ # Startup script
247
  COPY <<EOF /start.sh
248
  #!/bin/sh
249
+ # Start LibreChat
 
250
  cd /app/api && npm run backend &
251
 
252
  # Start Admin Panel
253
+ cd /app/admin && python3 app.py &
254
 
255
  # Start NGINX
256
  nginx -g "daemon off;"
 
260
  EOF
261
  RUN chmod +x /start.sh
262
 
263
+ # Environment variables
264
  ENV HOST=0.0.0.0 \
265
  PORT=3080 \
266
+ MONGO_URI="mongodb://localhost:27017/librechat" \
267
+ SUDO_SECRET="your-admin-secret" \
268
+ FLASK_SECRET="your-flask-secret" \
269
+ NODE_ENV="production"
 
 
 
 
 
 
270
 
271
  EXPOSE 7860
272
  CMD ["/start.sh"]