From a9f52ec521e45204ad9363dd143b32ac9910b6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Tue, 14 Sep 2021 02:28:09 +0200 Subject: Add flask app paste --- flakes/paste/flake.lock | 42 ++++++++++++ flakes/paste/flake.nix | 138 ++++++++++++++++++++++++++++++++++++++++ flakes/paste/paste/paste.py | 124 ++++++++++++++++++++++++++++++++++++ flakes/private/paste/flake.lock | 72 +++++++++++++++++++++ flakes/private/paste/flake.nix | 23 +++++++ 5 files changed, 399 insertions(+) create mode 100644 flakes/paste/flake.lock create mode 100644 flakes/paste/flake.nix create mode 100644 flakes/paste/paste/paste.py create mode 100644 flakes/private/paste/flake.lock create mode 100644 flakes/private/paste/flake.nix (limited to 'flakes') diff --git a/flakes/paste/flake.lock b/flakes/paste/flake.lock new file mode 100644 index 0000000..559e64b --- /dev/null +++ b/flakes/paste/flake.lock @@ -0,0 +1,42 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1631561581, + "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1631570365, + "narHash": "sha256-vc6bfo0hijpicdUDiui2DvZXmpIP2iqOFZRcpMOuYPo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "df7113c0727881519248d4c7d080324e0ee3327b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flakes/paste/flake.nix b/flakes/paste/flake.nix new file mode 100644 index 0000000..08d0681 --- /dev/null +++ b/flakes/paste/flake.nix @@ -0,0 +1,138 @@ +{ + inputs.flake-utils.url = "github:numtide/flake-utils"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs"; + + description = "Pastebin-like service"; + + outputs = { self, flake-utils, nixpkgs }: flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; overlays = []; }; + in rec { + hydraJobs = checks; + checks = pkgs.lib.optionalAttrs (builtins.elem system pkgs.lib.systems.doubles.linux) { + test = + let testing = import (nixpkgs + "/nixos/lib/testing-python.nix") { inherit system; }; + in testing.makeTest { + nodes = { + server = { pkgs, ... }: { + imports = [ self.nixosModule ]; + config = { + environment.systemPackages = [ pkgs.curl ]; + services.httpd = { + enable = true; + adminAddr = "foo@example.org"; + extraConfig = '' + ProxyPass / unix:///run/paste/gunicorn.sock|http://localhost/ + ProxyPassReverse / unix:///run/paste/gunicorn.sock|http://localhost/ + ''; + }; + services.paste.enable = true; + }; + }; + }; + testScript = '' + start_all() + server.wait_for_unit("httpd.service") + server.wait_for_unit("paste.service") + server.wait_until_succeeds("[ -S /run/paste/gunicorn.sock ]", 10) + server.succeed("curl -f http://localhost/") + server.succeed("curl -f http://localhost/ | grep -q 'Get the source'") + ''; + }; + }; + }) // rec { + nixosModule = { config, lib, pkgs, ... }: + let + cfg = config.services.paste; + in { + options = { + services.paste = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to enable the pastebin service"; + }; + + dataDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/paste"; + description = '' + The directory where Paste stores its data. + ''; + }; + + socketsDir = lib.mkOption { + type = lib.types.str; + default = "/run/paste"; + description = "Socket which is used for communication with Paste."; + }; + + webDirectory = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Subdirectory url to which the app will be served"; + }; + + # Output variables + systemdStateDirectory = lib.mkOption { + type = lib.types.str; + # Use ReadWritePaths= instead if varDir is outside of /var/lib + default = assert lib.strings.hasPrefix "/var/lib/" cfg.dataDir; + lib.strings.removePrefix "/var/lib/" cfg.dataDir; + description = '' + Adjusted paste data directory for systemd + ''; + readOnly = true; + }; + systemdRuntimeDirectory = lib.mkOption { + type = lib.types.str; + # Use ReadWritePaths= instead if socketsDir is outside of /run + default = assert lib.strings.hasPrefix "/run/" cfg.socketsDir; + lib.strings.removePrefix "/run/" cfg.socketsDir; + description = '' + Adjusted paste sockets directory for systemd + ''; + readOnly = true; + }; + sockets = lib.mkOption { + type = lib.types.attrsOf lib.types.path; + default = { + pid = "${cfg.socketsDir}/gunicorn.pid"; + gunicorn = "${cfg.socketsDir}/gunicorn.sock"; + }; + readOnly = true; + description = '' + Paste sockets + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.paste = { + description = "Pastebin like service"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Environment = pkgs.lib.optionals (cfg.webDirectory != null) [ "SCRIPT_NAME=${cfg.webDirectory}" ]; + Type = "simple"; + User = config.services.httpd.user; + ExecStart = let + python = pkgs.python3.withPackages (p: [p.gunicorn p.flask p.pygments p.python_magic ]); + in + "${python}/bin/gunicorn -w4 -p ${cfg.sockets.pid} -e PASTE_DIRECTORY=${cfg.dataDir} --bind unix:${cfg.sockets.gunicorn} --chdir ${./paste} paste:app"; + Restart = "always"; + RestartSec = "5s"; + PIDFile = cfg.sockets.pid; + RuntimeDirectory = cfg.systemdRuntimeDirectory; + StateDirectory = cfg.systemdStateDirectory; + StandardOutput = "journal"; + StandardError = "inherit"; + }; + + }; + }; + }; + }; +} diff --git a/flakes/paste/paste/paste.py b/flakes/paste/paste/paste.py new file mode 100644 index 0000000..86666b8 --- /dev/null +++ b/flakes/paste/paste/paste.py @@ -0,0 +1,124 @@ +import os +import secrets +from flask import Flask, abort, request, Response, url_for +from pygments.formatters.html import HtmlFormatter +from pygments import highlight +import pygments.lexers as lexers +import base64 +import magic +import mimetypes + +magic = magic.Magic(mime=True) + +config = { + "directory": os.environ["PASTE_DIRECTORY"], + "self_paste_id": "abcd123", + "max_content_length": 16 * 1000 * 1000 + } + +app = Flask(__name__) +app.config['MAX_CONTENT_LENGTH'] = config["max_content_length"] + +def file_location(paste_id): + if paste_id == config["self_paste_id"]: + return os.path.realpath(__file__) + else: + return os.path.join(config['directory'], paste_id + ".dat") + +def read_paste(paste_id): + file = file_location(paste_id) + if os.path.isfile(file): + content = open(file, "rb").read() + mime = magic.from_buffer(content) + if mime.startswith("text/x-script."): + mime="text/plain" + return (content, mime) + else: + abort(404) + +def generate_paste_id(n=3, attempts=0): + path = secrets.token_hex(n)[:n] + file = file_location(path) + if os.path.isfile(file): + attempts = attempts + 1 + if attempts > 5: + n = n + 1 + return generate_paste_id(n, attempts) + return path + +@app.route('/', methods=["GET"]) +def index(): + return Response('''
+$ curl -X POST --data-binary @{self} {host}
+{paste}
+
+-> GET {paste}
+   guesses mimetype
+-> GET {paste}/raw
+   text/plain
+-> GET {paste}/bin
+   application/octet-stream
+-> GET {paste}/b64
+   base64 encoded
+-> GET {paste}/ub64
+   tries to decode base64
+-> GET {paste}/python
+   pretty-print python (replace with anything known by pygments)
+-> GET {paste}/guess
+   pretty-print (language guessed by pygments)
+-> GET {paste}/download
+   force download of file
+
+Get the source +'''.format(host=url_for('post_paste', _external=True, _scheme="https"), + paste=url_for('get_paste', _external=True, _scheme="https", paste_id=config["self_paste_id"]), + self=os.path.basename(__file__) + ), mimetype="text/html") + +@app.route('/', methods=["POST"]) +def post_paste(): + content = request.get_data() + paste_id = generate_paste_id() + with open(file_location(paste_id), "wb") as f: + f.write(content) + return url_for('get_paste', _external=True, _scheme="https", paste_id=paste_id) + "\n" + + +@app.route('/', methods=["GET"]) +def get_paste(paste_id): + content, mime = read_paste(paste_id) + return Response(content, mimetype=mime) + +@app.route('//', methods=["GET"]) +def get_paste_with(paste_id, lang): + content, mime = read_paste(paste_id) + if lang == "raw": + return Response(content, mimetype="text/plain") + elif lang == "bin": + return Response(content, mimetype="application/octet-stream") + elif lang == "b64": + return Response(base64.encodebytes(content), mimetype="text/plain") + elif lang == "download": + extension = mimetypes.guess_extension(mime, strict=False) + if extension is None: + cd = "attachment" + else: + cd = 'attachment; filename="{}{}"'.format(paste_id, extension) + return Response(content, mimetype=mime, + headers={"Content-Disposition": cd}) + elif lang == "ub64": + try: + return base64.b64decode(content) + except: + abort(400) + try: + if lang == "guess": + lexer = lexers.guess_lexer(content) + else: + lexer = lexers.find_lexer_class_by_name(lang)() + except: + abort(400) + fmter = HtmlFormatter( + noclasses=True, full=True, style="colorful", linenos="table") + + return Response(highlight(content, lexer, fmter), mimetype="text/html") diff --git a/flakes/private/paste/flake.lock b/flakes/private/paste/flake.lock new file mode 100644 index 0000000..939d589 --- /dev/null +++ b/flakes/private/paste/flake.lock @@ -0,0 +1,72 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1631561581, + "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nix-lib": { + "locked": { + "lastModified": 1631655525, + "narHash": "sha256-8U7zAdbjNItXo6eqI/rhtOa3LUPGD6yE9PTZQkrSGHo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "cf0caf529c33c140863ebfa43691f7b69fe2233c", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1631570365, + "narHash": "sha256-vc6bfo0hijpicdUDiui2DvZXmpIP2iqOFZRcpMOuYPo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "df7113c0727881519248d4c7d080324e0ee3327b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "paste": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "narHash": "sha256-oSabBrUGIkY8lKktXlIM4uYSVYI54wKnIjjVZwMOd70=", + "path": "../../paste", + "type": "path" + }, + "original": { + "path": "../../paste", + "type": "path" + } + }, + "root": { + "inputs": { + "nix-lib": "nix-lib", + "paste": "paste" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flakes/private/paste/flake.nix b/flakes/private/paste/flake.nix new file mode 100644 index 0000000..71314e8 --- /dev/null +++ b/flakes/private/paste/flake.nix @@ -0,0 +1,23 @@ +{ + inputs.paste = { + path = "../../paste"; + type = "path"; + }; + inputs.nix-lib.url = "github:NixOS/nixpkgs"; + + description = "Private configuration for paste"; + outputs = { self, nix-lib, paste }: + let + cfg = name': { config, lib, pkgs, name, ... }: { + config = lib.mkIf (name == name') { + services.paste = { + enable = true; + webDirectory = "/paste"; + }; + }; + }; + in + paste.outputs // + { nixosModules = paste.nixosModules or {} // nix-lib.lib.genAttrs ["eldiron"] cfg; }; +} + -- cgit v1.2.3