theme / web.py
eaglercraft's picture
init
6933a36
Raw
History Blame Contribute Delete
4.31 kB
#!/usr/bin/env python3
import os
import subprocess
import tempfile
import threading
import time
from pathlib import Path
from flask import Flask, Response, jsonify, render_template_string, request
SETUP_FLAG = Path("/tmp/setup_mode")
START_SCRIPT = "/opt/server/start.sh"
UPLOAD_DIR = Path("/tmp/world_upload")
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 1024 * 1024 * 1024
SETUP_HTML = r"""
<!DOCTYPE html>
<html>
<head>
<title>Server Setup</title>
</head>
<body>
<h1>First-Run Setup</h1>
<label>HuggingFace Token</label><br>
<input type="password" id="hf-token" placeholder="hf_..."><br><br>
<label>World ZIP</label><br>
<input type="file" id="fi" accept=".zip"><br><br>
<div id="fname"></div><br>
<button id="btn" disabled onclick="submitUpload()">
Upload & Start Server
</button>
<progress id="progress" value="0" max="100" style="display:none;"></progress>
<div id="msg"></div>
<script>
let file = null;
const fi = document.getElementById("fi");
const btn = document.getElementById("btn");
const fname = document.getElementById("fname");
const progress = document.getElementById("progress");
const msg = document.getElementById("msg");
function check() {
const token = document.getElementById("hf-token").value.trim();
btn.disabled = !(file && token);
}
document.getElementById("hf-token").addEventListener("input", check);
fi.addEventListener("change", () => {
if (!fi.files[0]) return;
file = fi.files[0];
fname.textContent =
`${file.name} (${(file.size / 1048576).toFixed(1)} MB)`;
check();
});
function submitUpload() {
const token = document.getElementById("hf-token").value.trim();
if (!file || !token) return;
btn.disabled = true;
progress.style.display = "block";
msg.textContent = "Uploading...";
const fd = new FormData();
fd.append("world", file);
fd.append("hf_token", token);
const xhr = new XMLHttpRequest();
xhr.open("POST", "/setup/upload");
xhr.upload.onprogress = e => {
if (e.lengthComputable) {
progress.value = (e.loaded / e.total) * 100;
}
};
xhr.onload = () => {
let r;
try {
r = JSON.parse(xhr.responseText);
} catch {
r = { ok: false, message: xhr.responseText };
}
if (r.ok) {
msg.textContent =
"Done! Server is starting. Refresh in ~30 seconds.";
} else {
msg.textContent =
"Error: " + (r.message || "Unknown error");
btn.disabled = false;
}
};
xhr.onerror = () => {
msg.textContent = "Network error";
btn.disabled = false;
};
xhr.send(fd);
}
</script>
</body>
</html>
"""
NOT_FOUND_HTML = """
<!DOCTYPE html>
<html lang="en">
<body><p>pp</p></body>
</html>
"""
@app.route("/")
def root():
if SETUP_FLAG.exists():
return render_template_string(SETUP_HTML)
return Response(NOT_FOUND_HTML, 200, {"Content-Type": "text/html"})
@app.route("/setup/upload", methods=["POST"])
def setup_upload():
if not SETUP_FLAG.exists():
return jsonify({"ok": False, "message": "Not in setup mode"}), 403
hf_token = (request.form.get("hf_token") or "").strip()
if not hf_token:
return jsonify({"ok": False, "message": "HF token is required"}), 400
f = request.files.get("world")
if not f or not f.filename.endswith(".zip"):
return jsonify({"ok": False, "message": "A .zip file is required"}), 400
tmp_path = UPLOAD_DIR / "world_upload.zip"
f.save(str(tmp_path))
os.environ["HF_TOKEN"] = hf_token
import importlib, sys
sys.path.insert(0, "/opt/server")
import sync_world
importlib.reload(sync_world)
ok = sync_world.store_uploaded_zip(str(tmp_path))
if not ok:
return jsonify({"ok": False,
"message": "HF upload failed — check token / repo id"}), 500
SETUP_FLAG.unlink(missing_ok=True)
def _restart():
time.sleep(1)
subprocess.Popen(["/bin/bash", START_SCRIPT])
threading.Thread(target=_restart, daemon=True).start()
return jsonify({"ok": True})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, threaded=True)