aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile30
-rw-r--r--README.md45
-rw-r--r--__init__.py77
-rw-r--r--app.cfg5
-rw-r--r--deployment-config.mk4
5 files changed, 161 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a4b2b97
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,30 @@
+.PHONY: deploy
+
+include deployment-config.mk
+
+SSH_OPTS := -q -o ControlMaster=auto -o ControlPath=.ssh-deployment.sock
+
+deploy:
+ @echo " SSH $(WEB_SERVER)"
+ @ssh $(SSH_OPTS) -Nf $(WEB_SERVER)
+
+ @echo " RSYNC . $(WEB_SERVER):$(FLASK_PATH)"
+ @ssh -t $(SSH_OPTS) $(WEB_SERVER) "sudo -u $(FLASK_USER) -v"
+ @rsync -aizm --delete-excluded --exclude=.ssh-deployment.sock --exclude=.git --exclude=Makefile --exclude="*.pyc" --exclude="*.mk" --exclude=.gitignore --exclude="*.swp" \
+ --filter="P pastes/" --rsh="ssh $(SSH_OPTS)" --rsync-path="sudo -n -u $(FLASK_USER) rsync" \
+ . "$(WEB_SERVER):$(FLASK_PATH)"
+
+ @echo " CHMOD 750/640 $(WEB_SERVER):$(FLASK_PATH)/*"
+ @ssh -t $(SSH_OPTS) $(WEB_SERVER) "sudo find -P '$(FLASK_PATH)' \! -path '$(FLASK_PATH)/pastes/*' -type f -exec chmod 640 {} \;; \
+ sudo find -P '$(FLASK_PATH)' \! -path '$(FLASK_PATH)/pastes/*' -type d -exec chmod 750 {} \;;"
+
+ @echo " CHOWN $(FLASK_USER):$(NGINX_USER) $(WEB_SERVER):$(FLASK_PATH)/pastes"
+ @ssh -t $(SSH_OPTS) $(WEB_SERVER) "sudo mkdir -p '$(FLASK_PATH)/pastes'; sudo chown $(FLASK_USER):$(NGINX_USER) '$(FLASK_PATH)' '$(FLASK_PATH)/pastes'"
+ @echo " CHMOD 710 $(WEB_SERVER):$(FLASK_PATH)"
+ @ssh -t $(SSH_OPTS) $(WEB_SERVER) "sudo chmod 710 '$(FLASK_PATH)'"
+
+ @echo " UWSGI restart $(WEB_SERVER)"
+ @ssh -t $(SSH_OPTS) $(WEB_SERVER) "sudo /etc/init.d/uwsgi restart"
+
+ @echo " SSH $(WEB_SERVER)"
+ @ssh -O exit $(SSH_OPTS) $(WEB_SERVER)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..450502e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,45 @@
+## [Aleph Paste](https://git.zx2c4.com/alephpaste/about)
+#### by [Jason A. Donenfeld](mailto:jason@zx2c4.com)
+
+This is a basic boring private pastebin. It doesn't do much.
+
+ - https://א.cc/ID -- plain text
+ - https://א.cc/ID/ -- auto-guess syntax highlighting
+ - https://א.cc/ID/LANG -- use LANG syntax highlighting
+
+### NGINX Configuration Block
+
+```
+location / {
+ include uwsgi_params;
+ uwsgi_pass unix:/var/run/uwsgi-apps/alephpaste.socket;
+}
+location /pastes/ {
+ internal;
+ alias /var/www/uwsgi/alephpaste/pastes/;
+}
+```
+
+### CLI Script
+
+```
+#!/bin/bash
+DOMAIN="https://א.cc"
+alp() {
+ local opts="--basic --user zx2c4:hGha6tahjofaip4Osa7"
+ while getopts ":hd:" x; do
+ case $x in
+ h) echo "alp [-d ID]"; return;;
+ d) curl $opts -X DELETE "$DOMAIN"/$OPTARG; return;;
+ esac
+ done
+ trap 'rm -f "$file"' EXIT
+ file="$(mktemp)"
+ cat > "$file"
+ url="$(curl $opts -F "paste=@$file" "$DOMAIN")"
+ xclip -selection clipboard <<<"$url"
+ echo "$url"
+}
+
+alp $*
+```
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..4c7f696
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,77 @@
+from pygments.lexers import guess_lexer, guess_lexer_for_filename
+from pygments.formatters import HtmlFormatter
+from pygments import highlight
+from flask import Flask, Response, request, abort, redirect
+from random import SystemRandom
+from functools import wraps
+import string
+import os.path
+
+app = Flask(__name__)
+app.config.from_pyfile(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'app.cfg'))
+rng = SystemRandom()
+
+def check_auth(username, password):
+ # Side channel attack on string comparison!
+ return username == app.config['USERNAME'] and password == app.config['PASSWORD']
+
+def requires_auth(f):
+ @wraps(f)
+ def decorated(*args, **kwargs):
+ auth = request.authorization
+ if not auth or not check_auth(auth.username, auth.password):
+ return Response('Wrong username/password', 401, {'WWW-Authenticate': 'Basic realm="Aleph Paste"'})
+ return f(*args, **kwargs)
+ return decorated
+
+@app.route('/')
+def landing():
+ return redirect('https://git.zx2c4.com/alephpaste/about/', code=302)
+
+@app.route('/', methods=['POST'])
+@requires_auth
+def new_paste():
+ if 'paste' not in request.files:
+ abort(400)
+ while True:
+ paste = ''.join(rng.choice(string.ascii_letters + string.digits) for _ in range(8))
+ file_name = os.path.join(app.config['FILES_PATH'], paste + '.txt')
+ if not os.path.exists(file_name):
+ break
+ f = request.files['paste']
+ f.save(file_name)
+ f.close()
+ return app.config['URI_BASE'] + '/' + paste + '\n'
+
+
+@app.route('/<paste>', methods=['DELETE'])
+@requires_auth
+def delete_paste(paste):
+ try:
+ os.unlink(os.path.join(app.config['FILES_PATH'], paste + '.txt'))
+ except:
+ abort(404)
+ return ''
+
+@app.route('/<paste>')
+def send_paste(paste):
+ return Response(mimetype='text/plain', headers={'X-Accel-Redirect': os.path.join(app.config['FILES_ACCEL'], paste + '.txt')})
+
+@app.route('/<paste>/')
+def send_highlighted_paste_guess(paste):
+ return send_highlighted_paste(paste, None)
+
+@app.route('/<paste>/<ftype>')
+def send_highlighted_paste(paste, ftype):
+ try:
+ f = open(os.path.join(app.config['FILES_PATH'], paste + '.txt'), 'r')
+ text = f.read()
+ f.close()
+ if ftype is None or len(ftype) == 0:
+ lexer = guess_lexer(text)
+ else:
+ lexer = guess_lexer_for_filename(paste + '.' + ftype, text)
+ formatter = HtmlFormatter(style='pastie', full=True, title='{0} - Aleph Paste'.format(paste), linenos='table', anchorlinenos=True, lineanchors="line")
+ return highlight(text, lexer, formatter)
+ except:
+ return send_paste(paste)
diff --git a/app.cfg b/app.cfg
new file mode 100644
index 0000000..89bf1cf
--- /dev/null
+++ b/app.cfg
@@ -0,0 +1,5 @@
+FILES_PATH = "/var/www/uwsgi/alephpaste/pastes"
+FILES_ACCEL = "/pastes"
+USERNAME = "zx2c4"
+PASSWORD = "hGha6tahjofaip4Osa7"
+URI_BASE = "https://א.cc"
diff --git a/deployment-config.mk b/deployment-config.mk
new file mode 100644
index 0000000..114382a
--- /dev/null
+++ b/deployment-config.mk
@@ -0,0 +1,4 @@
+WEB_SERVER := metheny.zx2c4.com
+NGINX_USER := nginx
+FLASK_USER := alephpaste
+FLASK_PATH := /var/www/uwsgi/alephpaste