Spaces:
Running
Running
Update Dockerfile
Browse files- Dockerfile +60 -28
Dockerfile
CHANGED
@@ -8,15 +8,18 @@ RUN apk update && apk add --no-cache \
|
|
8 |
python3 \
|
9 |
py3-pip \
|
10 |
sqlite \
|
11 |
-
&& pip3 install flask werkzeug
|
12 |
-
|
13 |
-
|
14 |
-
|
|
|
15 |
&& mkdir -p /app/data \
|
|
|
|
|
16 |
&& chmod -R 777 /app/client/public/images \
|
17 |
&& chmod -R 777 /app/api/logs
|
18 |
|
19 |
-
# =====
|
20 |
COPY <<"EOF" /app/admin/app.py
|
21 |
from flask import Flask, request, jsonify, render_template
|
22 |
import os
|
@@ -27,9 +30,16 @@ from pathlib import Path
|
|
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
|
33 |
DB_PATH = '/app/data/admin.db'
|
34 |
|
35 |
def get_db():
|
@@ -79,6 +89,19 @@ def list_users():
|
|
79 |
return jsonify([
|
80 |
{"username": row[0]} for row in db.execute("SELECT username FROM users")
|
81 |
])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
EOF
|
83 |
|
84 |
# Admin templates
|
@@ -163,7 +186,6 @@ button {
|
|
163 |
}
|
164 |
EOF
|
165 |
|
166 |
-
# Properly escaped JavaScript
|
167 |
COPY <<"EOF" /app/admin/static/admin.js
|
168 |
async function loadUsers() {
|
169 |
const response = await fetch('/sudo/list_users', {
|
@@ -212,47 +234,53 @@ document.getElementById('addUserForm').addEventListener('submit', async (e) => {
|
|
212 |
});
|
213 |
|
214 |
document.addEventListener('DOMContentLoaded', () => {
|
215 |
-
|
|
|
216 |
window.location.href = '/sudo';
|
|
|
|
|
217 |
}
|
218 |
-
loadUsers();
|
219 |
});
|
220 |
EOF
|
221 |
|
222 |
-
# Add delete endpoint to app.py
|
223 |
-
RUN echo '@app.route("/sudo/remove_user", methods=["POST"])' >> /app/admin/app.py
|
224 |
-
RUN echo 'def remove_user():' >> /app/admin/app.py
|
225 |
-
RUN echo ' if request.headers.get("X-Sudo-Secret") != os.getenv("SUDO_SECRET"):' >> /app/admin/app.py
|
226 |
-
RUN echo ' return jsonify({"error": "Unauthorized"}), 403' >> /app/admin/app.py
|
227 |
-
RUN echo ' db = get_db()' >> /app/admin/app.py
|
228 |
-
RUN echo ' db.execute("DELETE FROM users WHERE username=?", [request.json["username"]])' >> /app/admin/app.py
|
229 |
-
RUN echo ' db.commit()' >> /app/admin/app.py
|
230 |
-
RUN echo ' return jsonify({"status": "User removed"})' >> /app/admin/app.py
|
231 |
-
|
232 |
# NGINX Configuration
|
233 |
COPY <<"EOF" /etc/nginx/nginx.conf
|
234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
235 |
http {
|
|
|
|
|
|
|
|
|
|
|
|
|
236 |
server {
|
237 |
listen 7860;
|
238 |
-
|
239 |
-
|
240 |
location / {
|
241 |
proxy_pass http://localhost:3080;
|
242 |
proxy_set_header Host \$host;
|
243 |
proxy_set_header X-Real-IP \$remote_addr;
|
|
|
244 |
}
|
245 |
-
|
246 |
-
# Admin Panel
|
247 |
location /sudo {
|
248 |
proxy_pass http://localhost:5000;
|
249 |
proxy_set_header X-Sudo-Secret \$http_x_sudo_secret;
|
250 |
}
|
251 |
-
|
252 |
-
# Static files
|
253 |
location /static {
|
254 |
alias /app/admin/static;
|
255 |
}
|
|
|
|
|
|
|
256 |
}
|
257 |
}
|
258 |
EOF
|
@@ -260,8 +288,12 @@ EOF
|
|
260 |
# Startup script
|
261 |
COPY <<"EOF" /start.sh
|
262 |
#!/bin/sh
|
263 |
-
#
|
264 |
-
|
|
|
|
|
|
|
|
|
265 |
|
266 |
# Start Admin Panel
|
267 |
cd /app/admin && python3 app.py &
|
|
|
8 |
python3 \
|
9 |
py3-pip \
|
10 |
sqlite \
|
11 |
+
&& pip3 install flask werkzeug \
|
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 |
+
&& chmod -R 777 /app/uploads/temp \
|
19 |
&& chmod -R 777 /app/client/public/images \
|
20 |
&& chmod -R 777 /app/api/logs
|
21 |
|
22 |
+
# ===== Admin Panel =====
|
23 |
COPY <<"EOF" /app/admin/app.py
|
24 |
from flask import Flask, request, jsonify, render_template
|
25 |
import os
|
|
|
30 |
app = Flask(__name__,
|
31 |
template_folder=str(Path(__file__).parent/'templates'),
|
32 |
static_folder=str(Path(__file__).parent/'static'))
|
33 |
+
|
34 |
+
# Validate secrets
|
35 |
+
required_secrets = ['FLASK_SECRET', 'SUDO_SECRET']
|
36 |
+
for secret in required_secrets:
|
37 |
+
if not os.getenv(secret):
|
38 |
+
raise RuntimeError(f"Missing required secret: {secret}")
|
39 |
+
|
40 |
app.secret_key = os.getenv("FLASK_SECRET")
|
41 |
|
42 |
+
# SQLite database
|
43 |
DB_PATH = '/app/data/admin.db'
|
44 |
|
45 |
def get_db():
|
|
|
89 |
return jsonify([
|
90 |
{"username": row[0]} for row in db.execute("SELECT username FROM users")
|
91 |
])
|
92 |
+
|
93 |
+
@app.route('/sudo/remove_user', methods=['POST'])
|
94 |
+
def remove_user():
|
95 |
+
if request.headers.get('X-Sudo-Secret') != os.getenv("SUDO_SECRET"):
|
96 |
+
return jsonify({"error": "Unauthorized"}), 403
|
97 |
+
|
98 |
+
db = get_db()
|
99 |
+
db.execute("DELETE FROM users WHERE username=?", [request.json["username"]])
|
100 |
+
db.commit()
|
101 |
+
return jsonify({"status": "User removed"})
|
102 |
+
|
103 |
+
if __name__ == "__main__":
|
104 |
+
app.run(host="0.0.0.0", port=5000)
|
105 |
EOF
|
106 |
|
107 |
# Admin templates
|
|
|
186 |
}
|
187 |
EOF
|
188 |
|
|
|
189 |
COPY <<"EOF" /app/admin/static/admin.js
|
190 |
async function loadUsers() {
|
191 |
const response = await fetch('/sudo/list_users', {
|
|
|
234 |
});
|
235 |
|
236 |
document.addEventListener('DOMContentLoaded', () => {
|
237 |
+
const token = localStorage.getItem('sudo_token');
|
238 |
+
if (!token && !window.location.pathname.includes('login')) {
|
239 |
window.location.href = '/sudo';
|
240 |
+
} else if (token) {
|
241 |
+
loadUsers();
|
242 |
}
|
|
|
243 |
});
|
244 |
EOF
|
245 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
246 |
# NGINX Configuration
|
247 |
COPY <<"EOF" /etc/nginx/nginx.conf
|
248 |
+
user nginx;
|
249 |
+
worker_processes auto;
|
250 |
+
|
251 |
+
events {
|
252 |
+
worker_connections 1024;
|
253 |
+
}
|
254 |
+
|
255 |
http {
|
256 |
+
include /etc/nginx/mime.types;
|
257 |
+
default_type application/octet-stream;
|
258 |
+
sendfile on;
|
259 |
+
keepalive_timeout 65;
|
260 |
+
client_max_body_size 20M;
|
261 |
+
|
262 |
server {
|
263 |
listen 7860;
|
264 |
+
server_name localhost;
|
265 |
+
|
266 |
location / {
|
267 |
proxy_pass http://localhost:3080;
|
268 |
proxy_set_header Host \$host;
|
269 |
proxy_set_header X-Real-IP \$remote_addr;
|
270 |
+
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
271 |
}
|
272 |
+
|
|
|
273 |
location /sudo {
|
274 |
proxy_pass http://localhost:5000;
|
275 |
proxy_set_header X-Sudo-Secret \$http_x_sudo_secret;
|
276 |
}
|
277 |
+
|
|
|
278 |
location /static {
|
279 |
alias /app/admin/static;
|
280 |
}
|
281 |
+
|
282 |
+
access_log /dev/stdout;
|
283 |
+
error_log /dev/stderr;
|
284 |
}
|
285 |
}
|
286 |
EOF
|
|
|
288 |
# Startup script
|
289 |
COPY <<"EOF" /start.sh
|
290 |
#!/bin/sh
|
291 |
+
# Fix permissions
|
292 |
+
chown -R nginx:nginx /var/lib/nginx
|
293 |
+
chmod -R 770 /var/lib/nginx
|
294 |
+
|
295 |
+
# Start LibreChat (using the correct command)
|
296 |
+
cd /app && npm run start:backend &
|
297 |
|
298 |
# Start Admin Panel
|
299 |
cd /app/admin && python3 app.py &
|