Initial save server
This commit is contained in:
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
FROM python:3.13-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY app.py .
|
||||||
|
|
||||||
|
VOLUME ["/saves"]
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
|
||||||
59
app.py
Normal file
59
app.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
from flask import Flask, request, jsonify, send_from_directory, abort
|
||||||
|
from flask_cors import CORS
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
CORS(app)
|
||||||
|
|
||||||
|
SAVES_DIR = os.environ.get("SAVES_DIR", "/saves")
|
||||||
|
os.makedirs(SAVES_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
def safe_filename(name):
|
||||||
|
name = re.sub(r"[^\w\-. ]", "", name).strip()
|
||||||
|
if not name:
|
||||||
|
abort(400, "Invalid save name")
|
||||||
|
return name + ".puC"
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/saves", methods=["GET"])
|
||||||
|
def list_saves():
|
||||||
|
files = [
|
||||||
|
f[:-4] for f in os.listdir(SAVES_DIR)
|
||||||
|
if f.endswith(".puC")
|
||||||
|
]
|
||||||
|
return jsonify(sorted(files))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/saves/<name>", methods=["GET"])
|
||||||
|
def get_save(name):
|
||||||
|
filename = safe_filename(name)
|
||||||
|
path = os.path.join(SAVES_DIR, filename)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
abort(404)
|
||||||
|
return send_from_directory(SAVES_DIR, filename, as_attachment=True)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/saves/<name>", methods=["POST"])
|
||||||
|
def store_save(name):
|
||||||
|
filename = safe_filename(name)
|
||||||
|
data = request.get_data()
|
||||||
|
if not data:
|
||||||
|
abort(400, "No data")
|
||||||
|
with open(os.path.join(SAVES_DIR, filename), "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
return jsonify({"ok": True, "name": name})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/saves/<name>", methods=["DELETE"])
|
||||||
|
def delete_save(name):
|
||||||
|
filename = safe_filename(name)
|
||||||
|
path = os.path.join(SAVES_DIR, filename)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
abort(404)
|
||||||
|
os.remove(path)
|
||||||
|
return jsonify({"ok": True})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(host="0.0.0.0", port=5000)
|
||||||
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
services:
|
||||||
|
pu-saves:
|
||||||
|
build: .
|
||||||
|
container_name: pu-saves
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./saves:/saves
|
||||||
|
networks:
|
||||||
|
- caddy_default
|
||||||
|
|
||||||
|
networks:
|
||||||
|
caddy_default:
|
||||||
|
external: true
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
flask==3.1.0
|
||||||
|
flask-cors==5.0.1
|
||||||
|
gunicorn==23.0.0
|
||||||
Reference in New Issue
Block a user