aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2021-09-14 02:28:09 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2021-09-30 00:36:14 +0200
commita9f52ec521e45204ad9363dd143b32ac9910b6b3 (patch)
tree6257e8385c240890e1f8c443e8aa886de09ad523
parente4e0de77cd6c9882fa7ff7c3cdd0ed9fce8a59d8 (diff)
downloadNix-a9f52ec521e45204ad9363dd143b32ac9910b6b3.tar.gz
Nix-a9f52ec521e45204ad9363dd143b32ac9910b6b3.tar.zst
Nix-a9f52ec521e45204ad9363dd143b32ac9910b6b3.zip
Add flask app paste
-rw-r--r--flakes/paste/flake.lock42
-rw-r--r--flakes/paste/flake.nix138
-rw-r--r--flakes/paste/paste/paste.py124
-rw-r--r--flakes/private/paste/flake.lock72
-rw-r--r--flakes/private/paste/flake.nix23
-rw-r--r--modules/default.nix1
-rw-r--r--modules/private/websites/tools/tools/default.nix28
-rw-r--r--modules/private/websites/tools/tools/ympd.nix8
8 files changed, 419 insertions, 17 deletions
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 @@
1{
2 "nodes": {
3 "flake-utils": {
4 "locked": {
5 "lastModified": 1631561581,
6 "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=",
7 "owner": "numtide",
8 "repo": "flake-utils",
9 "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19",
10 "type": "github"
11 },
12 "original": {
13 "owner": "numtide",
14 "repo": "flake-utils",
15 "type": "github"
16 }
17 },
18 "nixpkgs": {
19 "locked": {
20 "lastModified": 1631570365,
21 "narHash": "sha256-vc6bfo0hijpicdUDiui2DvZXmpIP2iqOFZRcpMOuYPo=",
22 "owner": "NixOS",
23 "repo": "nixpkgs",
24 "rev": "df7113c0727881519248d4c7d080324e0ee3327b",
25 "type": "github"
26 },
27 "original": {
28 "owner": "NixOS",
29 "repo": "nixpkgs",
30 "type": "github"
31 }
32 },
33 "root": {
34 "inputs": {
35 "flake-utils": "flake-utils",
36 "nixpkgs": "nixpkgs"
37 }
38 }
39 },
40 "root": "root",
41 "version": 7
42}
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 @@
1{
2 inputs.flake-utils.url = "github:numtide/flake-utils";
3 inputs.nixpkgs.url = "github:NixOS/nixpkgs";
4
5 description = "Pastebin-like service";
6
7 outputs = { self, flake-utils, nixpkgs }: flake-utils.lib.eachDefaultSystem (system:
8 let
9 pkgs = import nixpkgs { inherit system; overlays = []; };
10 in rec {
11 hydraJobs = checks;
12 checks = pkgs.lib.optionalAttrs (builtins.elem system pkgs.lib.systems.doubles.linux) {
13 test =
14 let testing = import (nixpkgs + "/nixos/lib/testing-python.nix") { inherit system; };
15 in testing.makeTest {
16 nodes = {
17 server = { pkgs, ... }: {
18 imports = [ self.nixosModule ];
19 config = {
20 environment.systemPackages = [ pkgs.curl ];
21 services.httpd = {
22 enable = true;
23 adminAddr = "foo@example.org";
24 extraConfig = ''
25 ProxyPass / unix:///run/paste/gunicorn.sock|http://localhost/
26 ProxyPassReverse / unix:///run/paste/gunicorn.sock|http://localhost/
27 '';
28 };
29 services.paste.enable = true;
30 };
31 };
32 };
33 testScript = ''
34 start_all()
35 server.wait_for_unit("httpd.service")
36 server.wait_for_unit("paste.service")
37 server.wait_until_succeeds("[ -S /run/paste/gunicorn.sock ]", 10)
38 server.succeed("curl -f http://localhost/")
39 server.succeed("curl -f http://localhost/ | grep -q 'Get the source'")
40 '';
41 };
42 };
43 }) // rec {
44 nixosModule = { config, lib, pkgs, ... }:
45 let
46 cfg = config.services.paste;
47 in {
48 options = {
49 services.paste = {
50 enable = lib.mkOption {
51 type = lib.types.bool;
52 default = false;
53 description = "Whether to enable the pastebin service";
54 };
55
56 dataDir = lib.mkOption {
57 type = lib.types.path;
58 default = "/var/lib/paste";
59 description = ''
60 The directory where Paste stores its data.
61 '';
62 };
63
64 socketsDir = lib.mkOption {
65 type = lib.types.str;
66 default = "/run/paste";
67 description = "Socket which is used for communication with Paste.";
68 };
69
70 webDirectory = lib.mkOption {
71 type = lib.types.nullOr lib.types.str;
72 default = null;
73 description = "Subdirectory url to which the app will be served";
74 };
75
76 # Output variables
77 systemdStateDirectory = lib.mkOption {
78 type = lib.types.str;
79 # Use ReadWritePaths= instead if varDir is outside of /var/lib
80 default = assert lib.strings.hasPrefix "/var/lib/" cfg.dataDir;
81 lib.strings.removePrefix "/var/lib/" cfg.dataDir;
82 description = ''
83 Adjusted paste data directory for systemd
84 '';
85 readOnly = true;
86 };
87 systemdRuntimeDirectory = lib.mkOption {
88 type = lib.types.str;
89 # Use ReadWritePaths= instead if socketsDir is outside of /run
90 default = assert lib.strings.hasPrefix "/run/" cfg.socketsDir;
91 lib.strings.removePrefix "/run/" cfg.socketsDir;
92 description = ''
93 Adjusted paste sockets directory for systemd
94 '';
95 readOnly = true;
96 };
97 sockets = lib.mkOption {
98 type = lib.types.attrsOf lib.types.path;
99 default = {
100 pid = "${cfg.socketsDir}/gunicorn.pid";
101 gunicorn = "${cfg.socketsDir}/gunicorn.sock";
102 };
103 readOnly = true;
104 description = ''
105 Paste sockets
106 '';
107 };
108 };
109 };
110
111 config = lib.mkIf cfg.enable {
112 systemd.services.paste = {
113 description = "Pastebin like service";
114 after = [ "network.target" ];
115 wantedBy = [ "multi-user.target" ];
116
117 serviceConfig = {
118 Environment = pkgs.lib.optionals (cfg.webDirectory != null) [ "SCRIPT_NAME=${cfg.webDirectory}" ];
119 Type = "simple";
120 User = config.services.httpd.user;
121 ExecStart = let
122 python = pkgs.python3.withPackages (p: [p.gunicorn p.flask p.pygments p.python_magic ]);
123 in
124 "${python}/bin/gunicorn -w4 -p ${cfg.sockets.pid} -e PASTE_DIRECTORY=${cfg.dataDir} --bind unix:${cfg.sockets.gunicorn} --chdir ${./paste} paste:app";
125 Restart = "always";
126 RestartSec = "5s";
127 PIDFile = cfg.sockets.pid;
128 RuntimeDirectory = cfg.systemdRuntimeDirectory;
129 StateDirectory = cfg.systemdStateDirectory;
130 StandardOutput = "journal";
131 StandardError = "inherit";
132 };
133
134 };
135 };
136 };
137 };
138}
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 @@
1import os
2import secrets
3from flask import Flask, abort, request, Response, url_for
4from pygments.formatters.html import HtmlFormatter
5from pygments import highlight
6import pygments.lexers as lexers
7import base64
8import magic
9import mimetypes
10
11magic = magic.Magic(mime=True)
12
13config = {
14 "directory": os.environ["PASTE_DIRECTORY"],
15 "self_paste_id": "abcd123",
16 "max_content_length": 16 * 1000 * 1000
17 }
18
19app = Flask(__name__)
20app.config['MAX_CONTENT_LENGTH'] = config["max_content_length"]
21
22def file_location(paste_id):
23 if paste_id == config["self_paste_id"]:
24 return os.path.realpath(__file__)
25 else:
26 return os.path.join(config['directory'], paste_id + ".dat")
27
28def read_paste(paste_id):
29 file = file_location(paste_id)
30 if os.path.isfile(file):
31 content = open(file, "rb").read()
32 mime = magic.from_buffer(content)
33 if mime.startswith("text/x-script."):
34 mime="text/plain"
35 return (content, mime)
36 else:
37 abort(404)
38
39def generate_paste_id(n=3, attempts=0):
40 path = secrets.token_hex(n)[:n]
41 file = file_location(path)
42 if os.path.isfile(file):
43 attempts = attempts + 1
44 if attempts > 5:
45 n = n + 1
46 return generate_paste_id(n, attempts)
47 return path
48
49@app.route('/', methods=["GET"])
50def index():
51 return Response('''<pre>
52$ curl -X POST --data-binary @{self} {host}
53{paste}
54
55-> GET {paste}
56 guesses mimetype
57-> GET {paste}/raw
58 text/plain
59-> GET {paste}/bin
60 application/octet-stream
61-> GET {paste}/b64
62 base64 encoded
63-> GET {paste}/ub64
64 tries to decode base64
65-> GET {paste}/python
66 pretty-print python (replace with anything known by pygments)
67-> GET {paste}/guess
68 pretty-print (language guessed by pygments)
69-> GET {paste}/download
70 force download of file
71</pre>
72<a href="{paste}/py">Get the source</a>
73'''.format(host=url_for('post_paste', _external=True, _scheme="https"),
74 paste=url_for('get_paste', _external=True, _scheme="https", paste_id=config["self_paste_id"]),
75 self=os.path.basename(__file__)
76 ), mimetype="text/html")
77
78@app.route('/', methods=["POST"])
79def post_paste():
80 content = request.get_data()
81 paste_id = generate_paste_id()
82 with open(file_location(paste_id), "wb") as f:
83 f.write(content)
84 return url_for('get_paste', _external=True, _scheme="https", paste_id=paste_id) + "\n"
85
86
87@app.route('/<paste_id>', methods=["GET"])
88def get_paste(paste_id):
89 content, mime = read_paste(paste_id)
90 return Response(content, mimetype=mime)
91
92@app.route('/<paste_id>/<lang>', methods=["GET"])
93def get_paste_with(paste_id, lang):
94 content, mime = read_paste(paste_id)
95 if lang == "raw":
96 return Response(content, mimetype="text/plain")
97 elif lang == "bin":
98 return Response(content, mimetype="application/octet-stream")
99 elif lang == "b64":
100 return Response(base64.encodebytes(content), mimetype="text/plain")
101 elif lang == "download":
102 extension = mimetypes.guess_extension(mime, strict=False)
103 if extension is None:
104 cd = "attachment"
105 else:
106 cd = 'attachment; filename="{}{}"'.format(paste_id, extension)
107 return Response(content, mimetype=mime,
108 headers={"Content-Disposition": cd})
109 elif lang == "ub64":
110 try:
111 return base64.b64decode(content)
112 except:
113 abort(400)
114 try:
115 if lang == "guess":
116 lexer = lexers.guess_lexer(content)
117 else:
118 lexer = lexers.find_lexer_class_by_name(lang)()
119 except:
120 abort(400)
121 fmter = HtmlFormatter(
122 noclasses=True, full=True, style="colorful", linenos="table")
123
124 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 @@
1{
2 "nodes": {
3 "flake-utils": {
4 "locked": {
5 "lastModified": 1631561581,
6 "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=",
7 "owner": "numtide",
8 "repo": "flake-utils",
9 "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19",
10 "type": "github"
11 },
12 "original": {
13 "owner": "numtide",
14 "repo": "flake-utils",
15 "type": "github"
16 }
17 },
18 "nix-lib": {
19 "locked": {
20 "lastModified": 1631655525,
21 "narHash": "sha256-8U7zAdbjNItXo6eqI/rhtOa3LUPGD6yE9PTZQkrSGHo=",
22 "owner": "NixOS",
23 "repo": "nixpkgs",
24 "rev": "cf0caf529c33c140863ebfa43691f7b69fe2233c",
25 "type": "github"
26 },
27 "original": {
28 "owner": "NixOS",
29 "repo": "nixpkgs",
30 "type": "github"
31 }
32 },
33 "nixpkgs": {
34 "locked": {
35 "lastModified": 1631570365,
36 "narHash": "sha256-vc6bfo0hijpicdUDiui2DvZXmpIP2iqOFZRcpMOuYPo=",
37 "owner": "NixOS",
38 "repo": "nixpkgs",
39 "rev": "df7113c0727881519248d4c7d080324e0ee3327b",
40 "type": "github"
41 },
42 "original": {
43 "owner": "NixOS",
44 "repo": "nixpkgs",
45 "type": "github"
46 }
47 },
48 "paste": {
49 "inputs": {
50 "flake-utils": "flake-utils",
51 "nixpkgs": "nixpkgs"
52 },
53 "locked": {
54 "narHash": "sha256-oSabBrUGIkY8lKktXlIM4uYSVYI54wKnIjjVZwMOd70=",
55 "path": "../../paste",
56 "type": "path"
57 },
58 "original": {
59 "path": "../../paste",
60 "type": "path"
61 }
62 },
63 "root": {
64 "inputs": {
65 "nix-lib": "nix-lib",
66 "paste": "paste"
67 }
68 }
69 },
70 "root": "root",
71 "version": 7
72}
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 @@
1{
2 inputs.paste = {
3 path = "../../paste";
4 type = "path";
5 };
6 inputs.nix-lib.url = "github:NixOS/nixpkgs";
7
8 description = "Private configuration for paste";
9 outputs = { self, nix-lib, paste }:
10 let
11 cfg = name': { config, lib, pkgs, name, ... }: {
12 config = lib.mkIf (name == name') {
13 services.paste = {
14 enable = true;
15 webDirectory = "/paste";
16 };
17 };
18 };
19 in
20 paste.outputs //
21 { nixosModules = paste.nixosModules or {} // nix-lib.lib.genAttrs ["eldiron"] cfg; };
22}
23
diff --git a/modules/default.nix b/modules/default.nix
index 1b09c94..7ce1cc2 100644
--- a/modules/default.nix
+++ b/modules/default.nix
@@ -13,6 +13,7 @@ in
13 mediagoblin = ./webapps/mediagoblin.nix; 13 mediagoblin = ./webapps/mediagoblin.nix;
14 peertube = (flakeCompat ../flakes/peertube).nixosModule; 14 peertube = (flakeCompat ../flakes/peertube).nixosModule;
15 fiche = ./webapps/fiche.nix; 15 fiche = ./webapps/fiche.nix;
16 paste = (flakeCompat ../flakes/paste).nixosModule;
16 17
17 opendmarc = (flakeCompat ../flakes/opendmarc).nixosModule; 18 opendmarc = (flakeCompat ../flakes/opendmarc).nixosModule;
18 openarc = (flakeCompat ../flakes/openarc).nixosModule; 19 openarc = (flakeCompat ../flakes/openarc).nixosModule;
diff --git a/modules/private/websites/tools/tools/default.nix b/modules/private/websites/tools/tools/default.nix
index 6464206..ac92ef4 100644
--- a/modules/private/websites/tools/tools/default.nix
+++ b/modules/private/websites/tools/tools/default.nix
@@ -1,5 +1,7 @@
1{ lib, pkgs, config, ... }: 1{ lib, pkgs, config, ... }:
2let 2let
3 flakeCompat = import ../../../../../lib/flake-compat.nix;
4
3 adminer = pkgs.callPackage ./adminer.nix { 5 adminer = pkgs.callPackage ./adminer.nix {
4 inherit (pkgs.webapps) adminer; 6 inherit (pkgs.webapps) adminer;
5 }; 7 };
@@ -64,6 +66,9 @@ let
64 cfg = config.myServices.websites.tools.tools; 66 cfg = config.myServices.websites.tools.tools;
65 pcfg = config.services.phpfpm.pools; 67 pcfg = config.services.phpfpm.pools;
66in { 68in {
69 imports =
70 builtins.attrValues (flakeCompat ../../../../../flakes/private/paste).nixosModules;
71
67 options.myServices.websites.tools.tools = { 72 options.myServices.websites.tools.tools = {
68 enable = lib.mkEnableOption "enable tools website"; 73 enable = lib.mkEnableOption "enable tools website";
69 }; 74 };
@@ -165,13 +170,16 @@ in {
165 (phpbb.apache.vhostConf pcfg.phpbb.socket) 170 (phpbb.apache.vhostConf pcfg.phpbb.socket)
166 (dmarc-reports.apache.vhostConf pcfg.dmarc-reports.socket) 171 (dmarc-reports.apache.vhostConf pcfg.dmarc-reports.socket)
167 '' 172 ''
168 Alias /paste /var/lib/fiche 173 <Location "/paste/">
169 <Directory "/var/lib/fiche"> 174 ProxyPass unix://${config.services.paste.sockets.gunicorn}|http://tools.immae.eu/paste/
170 DirectoryIndex index.txt index.html 175 ProxyPassReverse unix://${config.services.paste.sockets.gunicorn}|http://tools.immae.eu/paste/
171 AllowOverride None 176 ProxyPreserveHost on
172 Require all granted 177 </Location>
173 Options -Indexes 178 <Location "/paste">
174 </Directory> 179 ProxyPass unix://${config.services.paste.sockets.gunicorn}|http://tools.immae.eu/paste/
180 ProxyPassReverse unix://${config.services.paste.sockets.gunicorn}|http://tools.immae.eu/paste/
181 ProxyPreserveHost on
182 </Location>
175 183
176 Alias /BIP39 /var/lib/buildbot/outputs/immae/bip39 184 Alias /BIP39 /var/lib/buildbot/outputs/immae/bip39
177 <Directory "/var/lib/buildbot/outputs/immae/bip39"> 185 <Directory "/var/lib/buildbot/outputs/immae/bip39">
@@ -437,12 +445,6 @@ in {
437 paths = [ "/var/secrets/webapps/tools-wallabag" ]; 445 paths = [ "/var/secrets/webapps/tools-wallabag" ];
438 }; 446 };
439 447
440 services.fiche = {
441 enable = true;
442 port = config.myEnv.ports.fiche;
443 domain = "tools.immae.eu/paste";
444 https = true;
445 };
446 }; 448 };
447} 449}
448 450
diff --git a/modules/private/websites/tools/tools/ympd.nix b/modules/private/websites/tools/tools/ympd.nix
index 72d45d4..531b1a9 100644
--- a/modules/private/websites/tools/tools/ympd.nix
+++ b/modules/private/websites/tools/tools/ympd.nix
@@ -26,12 +26,12 @@ let
26 ProxyPass ws://${config.webPort}/ws 26 ProxyPass ws://${config.webPort}/ws
27 </Location> 27 </Location>
28 <Location "/mpd/music.mp3"> 28 <Location "/mpd/music.mp3">
29 ProxyPass unix:///run/mpd/mp3.sock|http://tools.immae.eu/ 29 ProxyPass unix:///run/mpd/mp3.sock|http://tools.immae.eu/mpd/mp3
30 ProxyPassReverse unix:///run/mpd/mp3.sock|http://tools.immae.eu/ 30 ProxyPassReverse unix:///run/mpd/mp3.sock|http://tools.immae.eu/mpd/mp3
31 </Location> 31 </Location>
32 <Location "/mpd/music.ogg"> 32 <Location "/mpd/music.ogg">
33 ProxyPass unix:///run/mpd/ogg.sock|http://tools.immae.eu/ 33 ProxyPass unix:///run/mpd/ogg.sock|http://tools.immae.eu/mpd/ogg
34 ProxyPassReverse unix:///run/mpd/ogg.sock|http://tools.immae.eu/ 34 ProxyPassReverse unix:///run/mpd/ogg.sock|http://tools.immae.eu/mpd/ogg
35 </Location> 35 </Location>
36 ''; 36 '';
37 }; 37 };