GPack/simple_serve.py
2026-03-26 13:17:41 +01:00

93 lines
3.1 KiB
Python

#!/usr/bin/env python3
import http.server
import os
import sys
import urllib.parse
directory = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else ".")
mods_root = os.path.join(directory, "mods")
def _path_under_mods(rel: str) -> str | None:
"""Map URL path segment after /mods to a path only inside ``mods_root``. Returns None if unsafe."""
full = os.path.realpath(os.path.join(mods_root, rel))
root = os.path.realpath(mods_root)
if full == root or full.startswith(root + os.sep):
return full
return None
HELLO_PAGE = b"""\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Modpack Install</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #111; color: #ddd;
max-width: 640px; margin: 40px auto; padding: 0 20px; line-height: 1.6; }
h1 { color: #fff; margin-bottom: 1rem; font-size: 1.4rem; }
h2 { color: #aaa; font-size: .9rem; font-weight: 400; margin: 1.2rem 0 .4rem; }
ol { padding-left: 1.2rem; }
li { margin-bottom: .5rem; }
code { background: #222; padding: 2px 6px; border-radius: 4px; font-size: .9em; }
pre { background: #222; padding: 10px 14px; border-radius: 6px; overflow-x: auto;
margin: .4rem 0; font-size: .85em; }
a { color: #7af; }
</style>
</head>
<body>
<h1>Modpack Setup</h1>
<ol>
<li>Install <a href="https://prismlauncher.org/download/">Prism Launcher</a></li>
<p>If on Arch:</p>
<pre>sudo pacman -S prism-launcher</pre>
<li>Create a <strong>1.20.1 Forge 47.4.10</strong> instance, launch it once, then close</li>
<li>Download mods:
<pre>wget -rnH -R "index.html*" http://barrys.cloud/mods/</pre>
</li>
<li>In Prism, right-click the instance &rarr; <strong>Folder</strong> &rarr; replace the <code>mods/</code> dir with the downloaded one</li>
<li>Launch &amp; connect to <code>barrys.cloud</code></li>
</ol>
</body>
</html>
"""
class Handler(http.server.SimpleHTTPRequestHandler):
def translate_path(self, path):
path = urllib.parse.unquote(urllib.parse.urlparse(path).path)
if path == "/mods" or path.startswith("/mods/"):
rel = path[len("/mods"):].lstrip("/")
mapped = _path_under_mods(rel)
return mapped if mapped is not None else os.path.join(mods_root, "__invalid__")
return os.path.join(mods_root, "__invalid__")
def do_GET(self):
if self.path == "/":
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.send_header("Content-Length", len(HELLO_PAGE))
self.end_headers()
self.wfile.write(HELLO_PAGE)
return
if not self.path.startswith("/mods"):
self.send_error(404)
return
super().do_GET()
def main() -> None:
port = 80
try:
http.server.HTTPServer(("0.0.0.0", port), Handler).serve_forever()
except OSError as e:
print(f"bind failed on 0.0.0.0:{port}: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()