commit 7e4df5c34c130403d34e45d8867e3a65b86986f2 Author: Denis Lehmann Date: Sun Apr 10 23:39:47 2022 +0200 initial commit diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d8c1ef6 --- /dev/null +++ b/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1648297722, + "narHash": "sha256-W+qlPsiZd8F3XkzXOzAoR+mpFqzm3ekQkJNa+PIh1BQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "0f8662f1319ad6abf89b3380dd2722369fc51ade", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1649225869, + "narHash": "sha256-u1zLtPmQzhT9mNXyM8Ey9pk7orDrIKdwooeGDEXm5xM=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "b6966d911da89e5a7301aaef8b4f0a44c77e103c", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..49d1596 --- /dev/null +++ b/flake.nix @@ -0,0 +1,27 @@ +{ + description = "Stream media files over SSH"; + nixConfig.bash-prompt = "\[\\e[1mmincloud-dev\\e[0m:\\w\]$ "; + inputs = { + nixpkgs.url = github:nixos/nixpkgs/nixos-unstable; + flake-utils.url = github:numtide/flake-utils; + }; + + outputs = { self, nixpkgs, flake-utils }: + + flake-utils.lib.eachDefaultSystem + (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { + # Development shell + devShell = pkgs.mkShell { + buildInputs = with pkgs; [ + python3 + python3Packages.flask + python3Packages.toml + ]; + }; + } + ); +} diff --git a/raincloud.py b/raincloud.py new file mode 100755 index 0000000..4be21c8 --- /dev/null +++ b/raincloud.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python + +import toml +import werkzeug +from flask import ( + Flask, + render_template, + redirect, + request, + send_from_directory, + session, + url_for, +) +from pathlib import Path +from werkzeug.utils import secure_filename + +base_path = Path("public") +cloud_name = "raincloud" + + +class MincloudIOException(Exception): + pass + + +def get_config(directory): + """Load a 'mincloud.conf' file from given directory. + + Parameters: + directory - basepath of 'mincloud.conf' + + Returns: Dictionary of config parameters + """ + path = base_path / directory / "rc.toml" + + if path.exists(): + config = {} + config["directory"] = directory + + parsed_config = toml.load(path) + config["password"] = ( + parsed_config["password"] if "password" in parsed_config else None + ) + config["download"] = ( + parsed_config["download"] if "download" in parsed_config else False + ) + config["upload"] = ( + parsed_config["upload"] if "upload" in parsed_config else False + ) + return config + + else: + raise MincloudIOException("No raincloud directory") + + +def get_files(directory): + path = base_path / directory + file_paths = [f for f in path.glob("*") if f.is_file()] + files = [] + for p in file_paths: + if p.name != "rc.toml": + files.append({"name": p.name}) + return files + + +app = Flask(__name__) + + +@app.route("/", methods=["GET", "POST"]) +@app.route("//", methods=["GET"]) +def files(directory, filename=None): + + try: + config = get_config(directory) + if config["password"]: + authenticated = False + if directory in session and session[directory] == config["password"]: + authenticated = True + + if not authenticated: + if request.method == "POST": + if request.form["password"] == config["password"]: + session[directory] = config["password"] + return redirect(url_for("files", directory=directory)) + else: + return render_template( + "authenticate.html", cloud_name=cloud_name, config=config + ) + + if request.method == "GET": + # List + if not filename: + files = get_files(directory) + return render_template( + "files.html", cloud_name=cloud_name, config=config, files=files + ) + + # Download + else: + if config["download"] and filename != "rc.toml": + return send_from_directory(base_path / directory, filename) + else: + return "Not allowed" + + # Upload + elif request.method == "POST": + if config["upload"]: + f = request.files["file"] + filename = secure_filename(f.filename) + if filename != "rc.toml": + f.save(base_path / directory / filename) + + # Return new file list + files = get_files(directory) + return render_template( + "files.html", cloud_name=cloud_name, config=config, files=files + ) + else: + return "No upload allowed" + + except MincloudIOException as e: + print(e) + return "No 404 file" + + +app.secret_key = "raincloud" +app.run(host="0.0.0.0", debug=True) diff --git a/static/favicon.png b/static/favicon.png new file mode 100644 index 0000000..e5d3ba1 Binary files /dev/null and b/static/favicon.png differ diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..9d23ceb --- /dev/null +++ b/static/style.css @@ -0,0 +1,3 @@ +body { + font-family: sans-serif; +} diff --git a/templates/authenticate.html b/templates/authenticate.html new file mode 100644 index 0000000..66f98c7 --- /dev/null +++ b/templates/authenticate.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} +{% block content %} +
+ + +
+{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..10ce608 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,7 @@ + + + + +{{ config["directory"] }} - {{ cloud_name }} +

{{ config["directory"] }}

+{% block content %}{% endblock %} diff --git a/templates/files.html b/templates/files.html new file mode 100644 index 0000000..1a423f1 --- /dev/null +++ b/templates/files.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% block content %} + {% if config["upload"] %} +
+ + +
+ {% endif %} +
    + {% for f in files %} +
  • + {{ f["name"] }} + {% if config["download"] %} + Download + {% endif %} +
  • + {% endfor %} +
+{% endblock %}