From 311a65b465bd5cc6baac048f2db7f143b2e051bb Mon Sep 17 00:00:00 2001 From: Denis Lehmann Date: Sun, 24 Apr 2022 17:56:59 +0200 Subject: [PATCH] clean repo --- flake.lock | 12 +-- flake.nix | 153 ++++++++++++++++++++++++++++--- raincloud.py | 177 ------------------------------------ static/favicon.png | Bin 2242 -> 0 bytes static/style.css | 108 ---------------------- templates/authenticate.html | 7 -- templates/base.html | 18 ---- templates/directory.html | 27 ------ 8 files changed, 147 insertions(+), 355 deletions(-) delete mode 100755 raincloud.py delete mode 100644 static/favicon.png delete mode 100644 static/style.css delete mode 100644 templates/authenticate.html delete mode 100644 templates/base.html delete mode 100644 templates/directory.html diff --git a/flake.lock b/flake.lock index d8c1ef6..8f8641f 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "flake-utils": { "locked": { - "lastModified": 1648297722, - "narHash": "sha256-W+qlPsiZd8F3XkzXOzAoR+mpFqzm3ekQkJNa+PIh1BQ=", + "lastModified": 1649676176, + "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=", "owner": "numtide", "repo": "flake-utils", - "rev": "0f8662f1319ad6abf89b3380dd2722369fc51ade", + "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678", "type": "github" }, "original": { @@ -17,11 +17,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1649225869, - "narHash": "sha256-u1zLtPmQzhT9mNXyM8Ey9pk7orDrIKdwooeGDEXm5xM=", + "lastModified": 1650701402, + "narHash": "sha256-XKfstdtqDg+O+gNBx1yGVKWIhLgfEDg/e2lvJSsp9vU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "b6966d911da89e5a7301aaef8b4f0a44c77e103c", + "rev": "bc41b01dd7a9fdffd32d9b03806798797532a5fe", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 49d1596..a16e2c6 100644 --- a/flake.nix +++ b/flake.nix @@ -1,27 +1,156 @@ { - description = "Stream media files over SSH"; - nixConfig.bash-prompt = "\[\\e[1mmincloud-dev\\e[0m:\\w\]$ "; + description = "Self-hosted file sharing cloud for you and your friends"; inputs = { nixpkgs.url = github:nixos/nixpkgs/nixos-unstable; flake-utils.url = github:numtide/flake-utils; }; - outputs = { self, nixpkgs, flake-utils }: + outputs = { self, nixpkgs, flake-utils }: { - flake-utils.lib.eachDefaultSystem - (system: - let - pkgs = nixpkgs.legacyPackages.${system}; - in - { - # Development shell + nixosModule = { config, ... }: + with nixpkgs.lib; + let + system = config.nixpkgs.localSystem.system; + + python = nixpkgs.legacyPackages.${system}.python; + flask = nixpkgs.legacyPackages.${system}.python3Packages.flask; + gunicorn = nixpkgs.legacyPackages.${system}.python3Packages.gunicorn; + raincloud = self.packages.${system}.raincloud; + toml = nixpkgs.legacyPackages.${system}.python3Packages.toml; + + cfg = config.services.raincloud; + + raincloud_config = nixpkgs.legacyPackages.${system}.writeText "raincloud_config.py" '' + CLOUD_NAME = "${cfg.cloudName}" + SECRET_KEY = "${cfg.secretKey}" + BASE_PATH = "${cfg.basePath}" + ''; + in + { + options.services.raincloud = { + + enable = mkEnableOption "Enable raincloud WSGI server"; + + address = mkOption { + type = types.str; + default = "127.0.0.1"; + example = "0.0.0.0"; + description = "Bind address of the server"; + }; + + port = mkOption { + type = types.str; + default = "8000"; + example = "4000"; + description = "Port on which the server listens"; + }; + + user = mkOption { + type = types.str; + default = "raincloud"; + description = "User under which the server runs"; + }; + + group = mkOption { + type = types.str; + default = "raincloud"; + description = "Group under which the server runs"; + }; + + cloudName = mkOption { + type = types.str; + default = "raincloud"; + description = "Name of the raincloud"; + }; + + basePath = mkOption { + type = types.str; + description = "Base path of the raincloud"; + }; + + secretKey = mkOption { + type = types.str; + default = "i_am_a_key"; + description = "Flask secret key"; + }; + }; + + config = mkIf cfg.enable { + + systemd.services.raincloud = { + description = "Enable raincloud WSGI server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + restartIfChanged = true; + + environment = + let + penv = python.buildEnv.override { + extraLibs = [ flask raincloud toml ]; + }; + in + { + PYTHONPATH = "${penv}/${python.sitePackages}/"; + }; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + Restart = "always"; + RestartSec = "5s"; + PermissionsStartOnly = true; + + ExecStart = '' + ${gunicorn}/bin/gunicorn "raincloud:app('${raincloud_config}')" \ + --bind=${cfg.address}:${cfg.port} + ''; + }; + }; + + users.users = mkIf (cfg.user == "raincloud") { + raincloud = { + group = cfg.group; + isSystemUser = true; + }; + }; + + users.groups = mkIf (cfg.group == "raincloud") { + raincloud = { }; + }; + + }; + + }; + + } // flake-utils.lib.eachDefaultSystem + (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { + + # Package + packages.raincloud = + pkgs.python3Packages.buildPythonPackage rec { + name = "raincloud"; + src = self; + propagatedBuildInputs = with pkgs; [ + python3Packages.flask + python3Packages.toml + ]; + }; + defaultPackage = self.packages.${system}.raincloud; + + # Development shell devShell = pkgs.mkShell { buildInputs = with pkgs; [ python3 python3Packages.flask - python3Packages.toml + python3Packages.gunicorn + python3Packages.toml ]; }; } - ); + ); } diff --git a/raincloud.py b/raincloud.py deleted file mode 100755 index 858ff3c..0000000 --- a/raincloud.py +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env python - -import crypt -import toml -import uuid -import werkzeug -from datetime import datetime, timedelta -from hmac import compare_digest as compare_hash -from flask import ( - abort, - 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" -sessions = [] - - -class RaincloudIOException(Exception): - pass - - -def clean_sessions(): - global sessions - sessions = [s for s in sessions if s[0] > datetime.now() - timedelta(days=1)] - - -def validate_session(directory, id_): - global sessions - valid_dates = [s[0] for s in sessions if s[1] == directory and s[2] == id_] - if len(valid_dates) > 0 and valid_dates[0] > datetime.now() - timedelta(days=1): - return True - return False - - -def delete_session(id_): - global sessions - sessions = [s for s in sessions if s[2] != id_] - - -def get_session_id(): - global sessions - ids = [s[2] for s in sessions] - id_ = uuid.uuid4() - while id_ in ids: - id_ = uuid.uuid4() - return id_ - - -def get_config(directory): - """Load a 'rc.toml' 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["hashed_password"] = ( - parsed_config["hashed_password"] - if "hashed_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 RaincloudIOException("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 directory(directory, filename=None): - global sessions - try: - - # Clean sessions - clean_sessions() - - # Logout - if request.method == "POST" and "logout" in request.form: - delete_session(session[directory]) - return redirect(url_for("directory", directory=directory)) - - config = get_config(directory) - if config["hashed_password"]: - authenticated = ( - True - if directory in session - and validate_session(directory, session[directory]) - else False - ) - - if not authenticated: - if request.method == "POST": - if compare_hash( - config["hashed_password"], - crypt.crypt( - request.form["password"], config["hashed_password"] - ), - ): - id_ = get_session_id() - session[directory] = id_ - sessions.append((datetime.now(), directory, id_)) - return redirect(url_for("directory", 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( - "directory.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: - abort(403) - - # 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) - - # Reload - return redirect(url_for("directory", directory=directory)) - else: - abort(403) - - except RaincloudIOException as e: - print(e) - abort(404) - - -app.secret_key = "raincloud" -app.run(host="0.0.0.0", debug=True) diff --git a/static/favicon.png b/static/favicon.png deleted file mode 100644 index e5d3ba18ebeaa2c7a48d937a9d56c88eeab74653..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2242 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 - - - -{% endblock %} diff --git a/templates/base.html b/templates/base.html deleted file mode 100644 index b291d51..0000000 --- a/templates/base.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - -{{ config["directory"] }} | {{ cloud_name }} -
- -
- {% block content %}{% endblock %} -
-
diff --git a/templates/directory.html b/templates/directory.html deleted file mode 100644 index 92bef8a..0000000 --- a/templates/directory.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "base.html" %} -{% block menu %} - {% if config["upload"] %} -
- -
- {% endif %} - {% if config["hashed_password"] %} -
- - -
- {% endif %} -{% endblock %} -{% block content %} - {% for f in files %} -
-

{{ f["name"] }}

- {% if config["download"] %} - Download - {% endif %} -
- {% endfor %} -{% endblock %}