diff --git a/README.md b/README.md index 91b1ccf..01920ad 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Python 3.6+ #### Master Python packages: `requests bs4` #### Server -Python packages: `flask` +Python packages: `flask passlib argon2_cffi` #### Slave Python packages: ` ` diff --git a/config_server.py.template b/config_server.py.template new file mode 100644 index 0000000..83c5e57 --- /dev/null +++ b/config_server.py.template @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +""" +Configuration settings for overwrought_server.py. +`modpack_name` is used for vanity purposes when naming the modpack. +`pw_hash` is the argon2-hashed version of the modpack owner's password. +`mods_dir` is the directory to place the mod files in for download. Your +web server/reverse proxy eg. Nginx should allow access to this directory. +""" +modpack_name = "motherlode1" +pw_hash = "PW_HASH" +mods_dir = "/var/www/overwrought/mods" +""" diff --git a/overwrought_server.py b/overwrought_server.py new file mode 100644 index 0000000..6ad68e0 --- /dev/null +++ b/overwrought_server.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +""" +The overwrought server, for exchanging mods from master to slave recipients. +""" +import json +import sqlite3 +import threading + +from passlib.hash import argon2 +from flask import Flask, send_file, request, abort + +import config_server + +app = Flask(__name__) +app.config['db_lock'] = threading.Lock() + +@app.route('/') +def index(): + """ + Main index page. Displays the mod summary page. + """ + return send_file(config_server.modpack_name + '.html') + +@app.route('/get') +def get(): + """ + Returns all mod file entries contained in the database as JSON. + Determing which files to update is left as an exercise up to the + client. + """ + app.config.get('db_lock').acquire() + con = sqlite3.connect('modpack.db') + cur = sqlite3.cursor() + mods = cur.execute('SELECT filename FROM mod').fetchall() + app.config.get('db_lock').release() + + mods = [mod[0] for mod in mods] + return json.dumps(mods) + +@app.route('/post', methods=['POST']) +def post(): + """ + Allows the modpack owner to upload new mods and a new database to the + server. Owner must be validated. + """ + password = request.form.get('password') + if not argon2.verify(password, config_server.pw_hash): + abort(401) + + db = request.files.get('database') + app.config.get('db_lock').acquire() + db.save('modpack.db') + app.config.get('db_lock').release() + + for fname, file in request.files.items(): + if fname == 'modpack.db': + continue + file.save(os.path.join(config_server.mods_dir, fname)) + +def generate_hash(password): + """ + A utility for generating an argon2 password hash. + """ + pw_hash = argon2.hash(password) + return pw_hash + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="The overwrought server. Use gunicorn to start." + ) + parse.add_argument( + 'action', + choices=['hash'], + help="What action to perform.", + ) + parse.add_argument( + 'target', + help="The target to perform the action on." + ) + args.parse_args() + + if args.action == "hash": + pw_hash = generate_hash(args.target) + print(pw_hash)