From 39eb97cbceeb332c21eaeeb4843b58b34667cfb2 Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Sun, 13 Feb 2022 22:11:38 -0500 Subject: refactor filenames and improve docs --- README-bgstack15.md | 51 ---------- README.md | 58 +++++++++-- config.cfg.example | 17 ++++ config.cfg.tpl | 17 ---- initdb.py | 2 +- manage.py | 2 +- pastebin.py | 278 --------------------------------------------------- stackbin.bin | 1 - stackbin.py | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++++ stackbin.wsgi.ini | 5 +- 10 files changed, 353 insertions(+), 359 deletions(-) delete mode 100644 README-bgstack15.md create mode 100644 config.cfg.example delete mode 100644 config.cfg.tpl delete mode 100755 pastebin.py create mode 100755 stackbin.py diff --git a/README-bgstack15.md b/README-bgstack15.md deleted file mode 100644 index 67b143e..0000000 --- a/README-bgstack15.md +++ /dev/null @@ -1,51 +0,0 @@ - -# Overview -This is my proposed solution to my pastebin problem. - -# Features - -* Admin page which can list parents, children, and provide link to delete pastes. -* Editable titles -* "Reply to" pastes to make parent/children relationships -* UUIDs instead of sequential integer ID numbers -* Private pastes (accessible to admin, and to users with the whole link) - -# Instructions - -Generate new db. - - python3 initdb.py - -Run server in development mode. - - FLASK_APP=pastebin.py FLASK_DEBUG=True flask run --host='0.0.0.0' - -Run the server in a full wsgi environment for the cleanup timer to operate. - - ./stackbin.bin - -# Improvements -I still need to work on these tasks: - -## Development - -* Protect the /admin/ page - -## Release - -* Document centos7 dependencies -* Deploy to prod - -# Alternatives - -## Unresearched -https://github.com/yasoob/logit-bin -https://github.com/AWilliams17/PasteMate -https://github.com/bsamadi/flask-pastebin - -## Attempted -https://github.com/Tygs/0bin sounds cool but it uses a stack I'm unfamiliar with and it had some issues and I didn't want to bother with it. diff --git a/README.md b/README.md index f1bdbab..be7d7eb 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,53 @@ -### Flask-Pastebin + +# Overview +This is my proposed solution to my pastebin problem. -This is a fork of [mitsuhiko/pastebin](https://github.com/mitsuhiko/flask-pastebin) +# Features -#### How to use +* Admin page which can list parents, children, and provide link to delete pastes. +* Editable titles +* "Reply to" pastes to make parent/children relationships +* UUIDs instead of sequential integer ID numbers +* Private pastes (accessible to admin, and to users with the whole link) - 1. make a virtualenv - 2. `pip install -r requirements.txt` - 3. `cp config.cfg.tpl config.cfg` - 4. `python manage.py initdb` - 5. `python manage.py server` +# Instructions + +Configure the application with `config.cfg` based on `config.cfg.example`. + +Generate new db. + + python3 initdb.py + +Run server in development mode. + + FLASK_APP=stackbin.py FLASK_DEBUG=True flask run --host='0.0.0.0' + +Run the server in a full wsgi environment for the cleanup timer to operate. + + ./stackbin.bin + +# Improvements +I still need to work on these tasks: + +## Development + +* Protect the /admin/ page + +## Release + +* Document centos7 dependencies +* Deploy to prod + +# Alternatives +This is a very diverged fork of [su27/flask-pastebin](https://github.com/su27/flask-pastebin) which itself was a fork of the original [mitsuhiko/pastebin](https://github.com/mitsuhiko/flask-pastebin). The original had a few additional features worth reviewing. + +## Unresearched +https://github.com/yasoob/logit-bin +https://github.com/AWilliams17/PasteMate +https://github.com/bsamadi/flask-pastebin + +## Attempted +https://github.com/Tygs/0bin sounds cool but it uses a stack I'm unfamiliar with and it had some issues and I didn't want to bother with it. diff --git a/config.cfg.example b/config.cfg.example new file mode 100644 index 0000000..23fb072 --- /dev/null +++ b/config.cfg.example @@ -0,0 +1,17 @@ +DEBUG=False +SQLALCHEMY_DATABASE_URI='sqlite:///pastebin.db' +SECRET_KEY='development-key' +SALT='jackson' +DELETESALT='differentstring' +APPNAME='stackbin' +# LOOP_DELAY in seconds is how many seconds between running the expiration cleanup task +LOOP_DELAY = 5 * 3 + +# Disable this variable entirely, to disable any choices for expiration +# Any very simple expression for relative time can be used here. This will be processed by pyparsedate. +EXPIRATION_OPTIONS = [ + "never", + "1 day", + "1 hour", + "15 minutes" +] diff --git a/config.cfg.tpl b/config.cfg.tpl deleted file mode 100644 index 23fb072..0000000 --- a/config.cfg.tpl +++ /dev/null @@ -1,17 +0,0 @@ -DEBUG=False -SQLALCHEMY_DATABASE_URI='sqlite:///pastebin.db' -SECRET_KEY='development-key' -SALT='jackson' -DELETESALT='differentstring' -APPNAME='stackbin' -# LOOP_DELAY in seconds is how many seconds between running the expiration cleanup task -LOOP_DELAY = 5 * 3 - -# Disable this variable entirely, to disable any choices for expiration -# Any very simple expression for relative time can be used here. This will be processed by pyparsedate. -EXPIRATION_OPTIONS = [ - "never", - "1 day", - "1 hour", - "15 minutes" -] diff --git a/initdb.py b/initdb.py index da4f05a..ba25591 100644 --- a/initdb.py +++ b/initdb.py @@ -1,3 +1,3 @@ -from pastebin import db +from stackbin import db no_wsgi = True db.create_all() diff --git a/manage.py b/manage.py index c19b039..ce535fd 100644 --- a/manage.py +++ b/manage.py @@ -1,6 +1,6 @@ from flask_script import Manager, Server from flask_script.commands import Clean -from pastebin import app, db +from stackbin import app, db manager = Manager(app) diff --git a/pastebin.py b/pastebin.py deleted file mode 100755 index d06dc6e..0000000 --- a/pastebin.py +++ /dev/null @@ -1,278 +0,0 @@ -from datetime import datetime, timedelta -from itsdangerous import Signer -from flask import (Flask, request, url_for, redirect, g, render_template, session, abort) -from flask_sqlalchemy import SQLAlchemy -from pytimeparse.timeparse import timeparse # python3-pytimeparse -# uwsgidecorators load will fail when using initdb.py but is also not necessary -try: - from uwsgidecorators import timer # python3-uwsgidecorators -except: - pass -import time - -## ripped from https://stackoverflow.com/questions/183042/how-can-i-use-uuids-in-sqlalchemy/812363#812363 -from sqlalchemy import types -from sqlalchemy.dialects.mysql.base import MSBinary -from sqlalchemy.schema import Column -import uuid -class UUID(types.TypeDecorator): - impl = MSBinary - def __init__(self): - self.impl.length = 16 - self.cache_ok = False # to shut up some useless warning - types.TypeDecorator.__init__(self,length=self.impl.length) - def process_bind_param(self,value,dialect=None): - if value and isinstance(value,uuid.UUID): - return value.bytes - elif value and not isinstance(value,uuid.UUID): - raise ValueError('value %s is not a valid uuid.UUID' % value) - else: - return None - def process_result_value(self,value,dialect=None): - if value: - return uuid.UUID(bytes=value) - else: - return None - def is_mutable(self): - return False -id_column_name = "id" -def id_column(): - return Column(id_column_name,UUID(),primary_key=True,default=uuid.uuid4) - -def get_signed(string, salt="blank"): - return Signer(app.secret_key, salt=salt).sign(str(string)) - -def get_unsigned(string, salt="blank"): - return Signer(app.secret_key, salt=salt).unsign(str(string)).decode("utf-8") - -app = Flask(__name__) -app.config.from_pyfile('config.cfg') -db = SQLAlchemy(app) - -def url_for_other_page(page): - args = request.view_args.copy() - args['page'] = page - return url_for(request.endpoint, **args) -app.jinja_env.globals['url_for_other_page'] = url_for_other_page -app.jinja_env.globals['appname'] = app.config['APPNAME'] - -def refresh_string(delay,url): - """ - Returns a string for html content for redirecting the user back after the - requested delay, to the requested url. - """ - return f'' - -@app.before_request -def check_user_status(): - g.user = None - if 'user_id' in session: - g.user = User.query.get(session['user_id']) - -class Paste(db.Model): - id = id_column() - code = db.Column(db.Text) - title = db.Column(db.Text) - pub_date = db.Column(db.DateTime) - exp_date = db.Column(db.DateTime) - user_id = db.Column(db.Integer, db.ForeignKey('user.id')) - is_private = db.Column(db.Boolean) - parent_id = db.Column(UUID(), db.ForeignKey('paste.id')) - parent = db.relationship('Paste', lazy=True, backref='children', uselist=False, remote_side=[id]) - - def __init__(self, user, code, title, relative_expiration_seconds, parent=None, is_private=False): - self.user = user - self.code = code - self.title = title - self.is_private = is_private - u = datetime.utcnow() - try: - # this will fail on POSTed value of "never" for exp - b = timedelta(seconds=relative_expiration_seconds) - except: - # so timedelta() will return 0 seconds, which makes the exp_date = pub_date, which is treated - # by the cleanup logic and jinja2 templates as never-expires. - b = timedelta() - self.pub_date = u - self.exp_date = u + b - self.parent = parent - -class User(db.Model): - id = db.Column(db.Integer, primary_key=True) - display_name = db.Column(db.String(120)) - fb_id = db.Column(db.String(30), unique=True) - pastes = db.relationship(Paste, lazy='dynamic', backref='user') - -@app.route('/', methods=['GET', 'POST']) -def new_paste(): - parent = None - reply_to = request.args.get('reply_to') - if reply_to is not None: - try: - parent = Paste.query.get(uuid.UUID(reply_to)) - except: - parent = Paste.query.get(reply_to) - if request.method == 'POST' and request.form['code']: - is_private = bool(request.form.get('is_private')) - title = "Untitled paste" - if request.form['pastetitle'] and request.form['pastetitle'] != "Enter title here": - title = request.form['pastetitle'] - relative_expiration_seconds = 0 - exp = 0 # start with an empty value - if 'exp' in request.form and request.form['exp']: - exp_opt = request.form['exp'] - if exp_opt not in app.config['EXPIRATION_OPTIONS']: - try: - exp = timeparse(f"+ {app.config['EXPIRATION_OPTIONS'][0]}") - except: - exp = 60 * 60 # failsafe, 1 hour - print(f"WARNING: requested expiration \"{exp_opt}\" is not in the list of options {app.config['EXPIRATION_OPTIONS']}, so will use {exp}") - else: - try: - exp = timeparse(f"+ {exp_opt}") - except: - exp = 0 - paste = Paste( - g.user, - request.form['code'], - title, - exp, - parent=parent, - is_private=is_private - ) - db.session.add(paste) - db.session.commit() - sign = get_signed(paste.id, salt=app.config['SALT']) \ - if is_private else None - return redirect(url_for('show_paste', paste_id=paste.id, s=sign)) - try: - exp_opts = app.config['EXPIRATION_OPTIONS'] - except: - exp_opts = None - return render_template('new_paste.html', parent=parent, exp_opts = exp_opts) - -# This @timer is from the uwsgidecorators -try: - @timer(app.config['LOOP_DELAY']) - def cleanup_expired_pastes(num): - # num is useless. - """ - Every LOOP_DELAY seconds, find any entries that have expired and then delete them. - """ - all1 = Paste.query.all() - need_commit = False - for p in all1: - # the exp_date != pub_date is very important, because anything with "never" expires - # is stored in the db as exp_date = pub_date - if p.exp_date and p.exp_date != p.pub_date and p.exp_date <= datetime.utcnow(): - print(f"INFO: deleting paste \"{p.title}\" with expiration {p.exp_date}.") - Paste.query.filter(Paste.id == p.id).delete() - need_commit = True - # only run the commit once! - if need_commit: - db.session.commit() -except: - pass - -@app.route('//') -@app.route('/') -def show_paste(paste_id): - try: - paste = Paste.query.options(db.eagerload('children')).get_or_404(paste_id) - except: - paste = Paste.query.options(db.eagerload('children')).get_or_404(uuid.UUID(paste_id)) - if paste.is_private: - try: - sign = request.args.get('s', '') - assert str(paste.id) == \ - get_unsigned(sign, salt=app.config['SALT']) - except: - abort(403) - parent = None - if paste.parent_id: - try: - parent = Paste.query.get(uuid.UUID(paste.parent_id)) - except: - parent = Paste.query.get(paste.parent_id) - children = [] - if paste.children: - for i in paste.children: - j = None - try: - j = Paste.query.get(uuid.UUID(i.id)) - except: - j = Paste.query.get(i.id) - if j: - k = j.id, j.title - children.append(k) - return render_template('show_paste.html', paste=paste, parent=parent, children=children) - -@app.route('//delete/', methods=['POST']) -@app.route('//delete', methods=['POST']) -def delete_paste(paste_id): - try: - paste = Paste.query.options(db.eagerload('children')).get_or_404(paste_id) - except: - paste = Paste.query.options(db.eagerload('children')).get_or_404(uuid.UUID(paste_id)) - sign = str(request.form['s']) - try: - assert str(paste.id) == get_unsigned(sign, salt=app.config['DELETESALT']) - except: - abort(403) - try: - Paste.query.filter(Paste.id == paste.id).delete() - db.session.commit() - message = refresh_string(1, url_for("admin")) + "OK" - return message,200 - except: - return f"failure to delete object. Select here to return to the admin panel.",500 - -def get_all_pastes(): - """ - Get custom arrangement of pastes for Admin view - """ - all1 = Paste.query.all() - all2 = [] - for p1 in all1: - parent_id = None - parent_title = None - children = [] - if p1.parent_id: - parent_id = p1.parent_id - try: - parent_title = Paste.query.get(p1.parent_id).title - except: - parent_title = "" # works better than None for the parent column of the generated html - if p1.children: - for c1 in p1.children: - child = Paste.query.get(c1.id) - child_title = child.title - c2 = c1.id, child_title - children.append(c2) - private = None - if p1.is_private: - private = get_signed(p1.id, salt=app.config['SALT']) - p2 = { - "id": p1.id, - "title": p1.title, - "pub_date": p1.pub_date, - "exp_date": p1.exp_date, - "private": private, - "user_id": p1.user_id, - "is_private": p1.is_private, - "parent": (parent_id, parent_title), - "children": children, - "delete": get_signed(p1.id, salt=app.config['DELETESALT']).decode("utf-8") - } - all2.append(p2) - return all2 - -@app.route('/admin/') -@app.route('/admin') -def admin(): - all_pastes = get_all_pastes() - return render_template('admin.html', pastes = all_pastes) - -if __name__ == "__main__": - manager.add_command('runserver', Server(host=app.config["APP_HOST"], port=app.config["APP_PORT"])) - app.run() diff --git a/stackbin.bin b/stackbin.bin index d3b2da3..4fb3241 100755 --- a/stackbin.bin +++ b/stackbin.bin @@ -1,7 +1,6 @@ #!/bin/sh # Reference: fuss.bin from fuss project # Startdate: 2022-02-13 19:25 -# Goal: see if uwsgi reacts to the @timer directives in the pastebin.py file, because flask run doesn't. thisscript="$( readlink -f "${0}" )" COMMAND="" grep -qiE 'ID=.*(rhel|centos|fedora)' /etc/os-release && COMMAND="${COMMAND} uwsgi" || \ diff --git a/stackbin.py b/stackbin.py new file mode 100755 index 0000000..aac3f49 --- /dev/null +++ b/stackbin.py @@ -0,0 +1,281 @@ +# File: stackbin.py +# SPDX-License-Identifier: GPL-3.0 +# Authors: mitsuhiko, su27, bgstack15 +from datetime import datetime, timedelta +from itsdangerous import Signer +from flask import (Flask, request, url_for, redirect, g, render_template, session, abort) +from flask_sqlalchemy import SQLAlchemy +from pytimeparse.timeparse import timeparse # python3-pytimeparse +# uwsgidecorators load will fail when using initdb.py but is also not necessary +try: + from uwsgidecorators import timer # python3-uwsgidecorators +except: + pass +import time + +## ripped from https://stackoverflow.com/questions/183042/how-can-i-use-uuids-in-sqlalchemy/812363#812363 +from sqlalchemy import types +from sqlalchemy.dialects.mysql.base import MSBinary +from sqlalchemy.schema import Column +import uuid +class UUID(types.TypeDecorator): + impl = MSBinary + def __init__(self): + self.impl.length = 16 + self.cache_ok = False # to shut up some useless warning + types.TypeDecorator.__init__(self,length=self.impl.length) + def process_bind_param(self,value,dialect=None): + if value and isinstance(value,uuid.UUID): + return value.bytes + elif value and not isinstance(value,uuid.UUID): + raise ValueError('value %s is not a valid uuid.UUID' % value) + else: + return None + def process_result_value(self,value,dialect=None): + if value: + return uuid.UUID(bytes=value) + else: + return None + def is_mutable(self): + return False +id_column_name = "id" +def id_column(): + return Column(id_column_name,UUID(),primary_key=True,default=uuid.uuid4) + +def get_signed(string, salt="blank"): + return Signer(app.secret_key, salt=salt).sign(str(string)) + +def get_unsigned(string, salt="blank"): + return Signer(app.secret_key, salt=salt).unsign(str(string)).decode("utf-8") + +app = Flask(__name__) +app.config.from_pyfile('config.cfg') +db = SQLAlchemy(app) + +def url_for_other_page(page): + args = request.view_args.copy() + args['page'] = page + return url_for(request.endpoint, **args) +app.jinja_env.globals['url_for_other_page'] = url_for_other_page +app.jinja_env.globals['appname'] = app.config['APPNAME'] + +def refresh_string(delay,url): + """ + Returns a string for html content for redirecting the user back after the + requested delay, to the requested url. + """ + return f'' + +@app.before_request +def check_user_status(): + g.user = None + if 'user_id' in session: + g.user = User.query.get(session['user_id']) + +class Paste(db.Model): + id = id_column() + code = db.Column(db.Text) + title = db.Column(db.Text) + pub_date = db.Column(db.DateTime) + exp_date = db.Column(db.DateTime) + user_id = db.Column(db.Integer, db.ForeignKey('user.id')) + is_private = db.Column(db.Boolean) + parent_id = db.Column(UUID(), db.ForeignKey('paste.id')) + parent = db.relationship('Paste', lazy=True, backref='children', uselist=False, remote_side=[id]) + + def __init__(self, user, code, title, relative_expiration_seconds, parent=None, is_private=False): + self.user = user + self.code = code + self.title = title + self.is_private = is_private + u = datetime.utcnow() + try: + # this will fail on POSTed value of "never" for exp + b = timedelta(seconds=relative_expiration_seconds) + except: + # so timedelta() will return 0 seconds, which makes the exp_date = pub_date, which is treated + # by the cleanup logic and jinja2 templates as never-expires. + b = timedelta() + self.pub_date = u + self.exp_date = u + b + self.parent = parent + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + display_name = db.Column(db.String(120)) + fb_id = db.Column(db.String(30), unique=True) + pastes = db.relationship(Paste, lazy='dynamic', backref='user') + +@app.route('/', methods=['GET', 'POST']) +def new_paste(): + parent = None + reply_to = request.args.get('reply_to') + if reply_to is not None: + try: + parent = Paste.query.get(uuid.UUID(reply_to)) + except: + parent = Paste.query.get(reply_to) + if request.method == 'POST' and request.form['code']: + is_private = bool(request.form.get('is_private')) + title = "Untitled paste" + if request.form['pastetitle'] and request.form['pastetitle'] != "Enter title here": + title = request.form['pastetitle'] + relative_expiration_seconds = 0 + exp = 0 # start with an empty value + if 'exp' in request.form and request.form['exp']: + exp_opt = request.form['exp'] + if exp_opt not in app.config['EXPIRATION_OPTIONS']: + try: + exp = timeparse(f"+ {app.config['EXPIRATION_OPTIONS'][0]}") + except: + exp = 60 * 60 # failsafe, 1 hour + print(f"WARNING: requested expiration \"{exp_opt}\" is not in the list of options {app.config['EXPIRATION_OPTIONS']}, so will use {exp}") + else: + try: + exp = timeparse(f"+ {exp_opt}") + except: + exp = 0 + paste = Paste( + g.user, + request.form['code'], + title, + exp, + parent=parent, + is_private=is_private + ) + db.session.add(paste) + db.session.commit() + sign = get_signed(paste.id, salt=app.config['SALT']) \ + if is_private else None + return redirect(url_for('show_paste', paste_id=paste.id, s=sign)) + try: + exp_opts = app.config['EXPIRATION_OPTIONS'] + except: + exp_opts = None + return render_template('new_paste.html', parent=parent, exp_opts = exp_opts) + +# This @timer is from the uwsgidecorators +try: + @timer(app.config['LOOP_DELAY']) + def cleanup_expired_pastes(num): + # num is useless. + """ + Every LOOP_DELAY seconds, find any entries that have expired and then delete them. + """ + all1 = Paste.query.all() + need_commit = False + for p in all1: + # the exp_date != pub_date is very important, because anything with "never" expires + # is stored in the db as exp_date = pub_date + if p.exp_date and p.exp_date != p.pub_date and p.exp_date <= datetime.utcnow(): + print(f"INFO: deleting paste \"{p.title}\" with expiration {p.exp_date}.") + Paste.query.filter(Paste.id == p.id).delete() + need_commit = True + # only run the commit once! + if need_commit: + db.session.commit() +except: + pass + +@app.route('//') +@app.route('/') +def show_paste(paste_id): + try: + paste = Paste.query.options(db.eagerload('children')).get_or_404(paste_id) + except: + paste = Paste.query.options(db.eagerload('children')).get_or_404(uuid.UUID(paste_id)) + if paste.is_private: + try: + sign = request.args.get('s', '') + assert str(paste.id) == \ + get_unsigned(sign, salt=app.config['SALT']) + except: + abort(403) + parent = None + if paste.parent_id: + try: + parent = Paste.query.get(uuid.UUID(paste.parent_id)) + except: + parent = Paste.query.get(paste.parent_id) + children = [] + if paste.children: + for i in paste.children: + j = None + try: + j = Paste.query.get(uuid.UUID(i.id)) + except: + j = Paste.query.get(i.id) + if j: + k = j.id, j.title + children.append(k) + return render_template('show_paste.html', paste=paste, parent=parent, children=children) + +@app.route('//delete/', methods=['POST']) +@app.route('//delete', methods=['POST']) +def delete_paste(paste_id): + try: + paste = Paste.query.options(db.eagerload('children')).get_or_404(paste_id) + except: + paste = Paste.query.options(db.eagerload('children')).get_or_404(uuid.UUID(paste_id)) + sign = str(request.form['s']) + try: + assert str(paste.id) == get_unsigned(sign, salt=app.config['DELETESALT']) + except: + abort(403) + try: + Paste.query.filter(Paste.id == paste.id).delete() + db.session.commit() + message = refresh_string(1, url_for("admin")) + "OK" + return message,200 + except: + return f"failure to delete object. Select here to return to the admin panel.",500 + +def get_all_pastes(): + """ + Get custom arrangement of pastes for Admin view + """ + all1 = Paste.query.all() + all2 = [] + for p1 in all1: + parent_id = None + parent_title = None + children = [] + if p1.parent_id: + parent_id = p1.parent_id + try: + parent_title = Paste.query.get(p1.parent_id).title + except: + parent_title = "" # works better than None for the parent column of the generated html + if p1.children: + for c1 in p1.children: + child = Paste.query.get(c1.id) + child_title = child.title + c2 = c1.id, child_title + children.append(c2) + private = None + if p1.is_private: + private = get_signed(p1.id, salt=app.config['SALT']) + p2 = { + "id": p1.id, + "title": p1.title, + "pub_date": p1.pub_date, + "exp_date": p1.exp_date, + "private": private, + "user_id": p1.user_id, + "is_private": p1.is_private, + "parent": (parent_id, parent_title), + "children": children, + "delete": get_signed(p1.id, salt=app.config['DELETESALT']).decode("utf-8") + } + all2.append(p2) + return all2 + +@app.route('/admin/') +@app.route('/admin') +def admin(): + all_pastes = get_all_pastes() + return render_template('admin.html', pastes = all_pastes) + +if __name__ == "__main__": + manager.add_command('runserver', Server(host=app.config["APP_HOST"], port=app.config["APP_PORT"])) + app.run() diff --git a/stackbin.wsgi.ini b/stackbin.wsgi.ini index cac4511..4b38ea8 100644 --- a/stackbin.wsgi.ini +++ b/stackbin.wsgi.ini @@ -1,11 +1,12 @@ [uwsgi] plugins = logfile http-socket = 0.0.0.0:5000 -wsgi-file = pastebin.py +wsgi-file = stackbin.py callable = app -touch-reload = pastebin.py +touch-reload = stackbin.py touch-reload = config.cfg touch-reload = stackbin.wsgi.ini +# the template files are not necessary, because flask always loads it from disk for each request req-logger = file:log/req.log # to get strftime format fields, you need double percent signs logdate = "%%FT%%T" -- cgit