1
0
mirror of https://github.com/Tygs/0bin.git synced 2023-08-10 21:13:00 +03:00

Merge branch 'v2' of github.com:Tygs/0bin into v2

This commit is contained in:
papee 2020-08-13 15:25:49 +02:00
commit 836fa17bc4
10 changed files with 123 additions and 117 deletions

View File

@ -1,6 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
from zerobin.cmd import main
main()

5
zerobin/__main__.py Normal file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env python
from zerobin.cli import main
main()

View File

@ -7,16 +7,20 @@
import sys import sys
import re import re
import os
import zerobin
from zerobin.utils import ( from zerobin.utils import (
settings, settings,
SettingsValidationError, SettingsValidationError,
ensure_var_env, ensure_app_context,
hash_password, hash_password,
) )
from zerobin.routes import get_app from zerobin.routes import get_app
from zerobin.paste import Paste from zerobin.paste import Paste
from bottle import run from bottle import run
import clize import clize
@ -26,11 +30,9 @@ def runserver(
*, *,
host="", host="",
port="", port="",
config_dir="",
data_dir="",
debug=None, debug=None,
user="",
group="",
settings_file="",
compressed_static=None,
version=False, version=False,
paste_id_length=None, paste_id_length=None,
server="paste", server="paste",
@ -39,23 +41,18 @@ def runserver(
print("0bin V%s" % settings.VERSION) print("0bin V%s" % settings.VERSION)
sys.exit(0) sys.exit(0)
settings.HOST = host or settings.HOST
settings.PORT = port or settings.PORT
settings.USER = user or settings.USER
settings.GROUP = group or settings.GROUP
settings.PASTE_ID_LENGTH = paste_id_length or settings.PASTE_ID_LENGTH
settings.DEBUG = bool(debug) if debug is not None else settings.DEBUG
ensure_var_env()
try: try:
_, app = get_app(debug, settings_file, compressed_static, settings=settings) settings, app = get_app(debug=debug, config_dir=config_dir, data_dir=data_dir,)
except SettingsValidationError as err: except SettingsValidationError as err:
print("Configuration error: %s" % err.message, file=sys.stderr) print("Configuration error: %s" % err.message, file=sys.stderr)
sys.exit(1) sys.exit(1)
settings.HOST = host or os.environ.get("ZEROBIN_HOST", settings.HOST)
settings.PORT = port or os.environ.get("ZEROBIN_PORT", settings.PORT)
if settings.DEBUG: if settings.DEBUG:
print(f"Admin URL: http://{settings.HOST}:{settings.PORT}{settings.ADMIN_URL}") print(f"Admin URL: {settings.ADMIN_URL}")
print()
run( run(
app, host=settings.HOST, port=settings.PORT, reloader=True, server=server, app, host=settings.HOST, port=settings.PORT, reloader=True, server=server,
) )
@ -100,7 +97,7 @@ def delete_paste(*pastes, quiet=False):
print("Paste {} doesn't exist".format(paste_uuid)) print("Paste {} doesn't exist".format(paste_uuid))
def print_admin_url(): def infos():
""" Print the route to the 0bin admin. """ Print the route to the 0bin admin.
The admin route is generated by zerobin so that bots won't easily The admin route is generated by zerobin so that bots won't easily
@ -123,8 +120,16 @@ def print_admin_url():
""" """
ensure_var_env() ensure_app_context()
print(settings.ADMIN_URL) print(f"Zerobin version: {zerobin.__version__}")
print(f"Admin URL (to moderate pastes): {settings.ADMIN_URL}")
print(f"Data dir (pastes and counter): {settings.DATA_DIR}")
print(
f"Config dir (config file, secret key, admin password and custom views): {settings.CONFIG_DIR}"
)
print(
f"Static files dir (to configure apache, nging, etc.): {settings.STATIC_FILES_ROOT}"
)
def set_admin_password(password): def set_admin_password(password):
@ -134,17 +139,17 @@ def set_admin_password(password):
""" """
ensure_var_env() ensure_app_context()
settings.ADMIN_PASSWORD_FILE.write_bytes(hash_password(password)) settings.ADMIN_PASSWORD_FILE.write_bytes(hash_password(password))
def main(): def main():
subcommands = [runserver, delete_paste, print_admin_url, set_admin_password] subcommands = [runserver, delete_paste, infos, set_admin_password]
subcommand_names = [ subcommand_names = [
clize.util.name_py2cli(name) clize.util.name_py2cli(name)
for name in clize.util.dict_from_names(subcommands).keys() for name in clize.util.dict_from_names(subcommands).keys()
] ]
if len(sys.argv) < 2 or sys.argv[1] not in subcommand_names: if len(sys.argv) < 2 or sys.argv[1] not in subcommand_names:
sys.argv.insert(1, subcommand_names[0]) sys.argv.insert(1, subcommand_names[0])
clize.run(runserver, delete_paste, print_admin_url, set_admin_password) clize.run(runserver, delete_paste, infos, set_admin_password)

View File

@ -1,57 +1,19 @@
from zerobin import ROOT_DIR # Get error messages and auto reload.
# Don't set this to True in production
# Path to the directory that will contains all variable content, such
# as pastes, the secret key, etc
VAR_DIR = ROOT_DIR.parent / "var"
# debug will get you error messages and auto reload
# don't set this to True in production
DEBUG = False DEBUG = False
# Should the application serve static files on it's own ? # Port and host the embedded python server should be using
# If yes, set the absolute path to the static files.
# If no, set it to None
# In dev this is handy, in prod you probably want the HTTP servers
# to serve it, but it's OK for small traffic to set it to True in prod too.
STATIC_FILES_ROOT = ROOT_DIR / "static"
# If True, will link the compressed verion of the js and css files,
# otherwise, will use the ordinary files
COMPRESSED_STATIC_FILES = False
# A tuple of absolute paths of directory where to look the template for
# the first one will be the first to be looked into
# if you want to override, it needs to be it a directory at the begining of
# this tuple. By default, custom_views is meant for that purpose.
TEMPLATE_DIRS = (
VAR_DIR / "custom_views",
ROOT_DIR / "views",
)
# Port and host the embeded python server should be using
# You can also specify them using the --host and --port script options
# which have priority on these settings
HOST = "127.0.0.1" HOST = "127.0.0.1"
PORT = "8000" PORT = "3255"
# User and group the server should run as. Set to None if it should be the
# current user. Some OS don't support it and if so, it will be ignored.
USER = None
GROUP = None
# Display a tiny counter for pastes created. # Display a tiny counter for pastes created.
# Be carreful if your site have to many pastes this can hurt your hard drive performances.
# Refresh counter interval. Default to every minute after a paste.
DISPLAY_COUNTER = True DISPLAY_COUNTER = True
REFRESH_COUNTER = 60 * 1 # Fill this if you want to # Refresh counter interval.
ADMIN_CREDENTIALS = { REFRESH_COUNTER = 60 # in seconds
"username": None,
"password": None,
}
# Names/links to insert in the footer.
# Names/links to insert in the menu bar. # Any link with "mailto:" will be escaped to limit spam, but displayed
# Any link with "mailto:" will be escaped to prevent spam # correctly to the user using JS.
MENU = ( MENU = (
("Create paste", "/"), # internal link. First link will be highlited ("Create paste", "/"), # internal link. First link will be highlited
("Github", "https://github.com/Tygs/0bin"), # external link ("Github", "https://github.com/Tygs/0bin"), # external link
@ -60,11 +22,11 @@ MENU = (
("Zerobin Pastebin", "https://www.0bin.net/"), # Thanks the authors :) ("Zerobin Pastebin", "https://www.0bin.net/"), # Thanks the authors :)
) )
# limit size of pasted text in bytes. Be careful allowing too much size can # Size limit of the paste content in bytes. Be careful, allowing a size too big can
# slow down user's browser # slow down the user's browser
MAX_SIZE = 1024 * 500 MAX_SIZE = 1024 * 500
# length of base64-like paste-id string in the url, int from 4 to 27 (length of sha1 digest) # Length of the paste-id string in the url, int from 4 to 27 (length of sha1 digest)
# total number of unique pastes can be calculated as 2^(6*PASTE_ID_LENGTH) # total number of unique pastes can be calculated as 2^(6*PASTE_ID_LENGTH)
# for PASTE_ID_LENGTH=8, for example, it's 2^(6*8) = 281 474 976 710 656 # for PASTE_ID_LENGTH=8, for example, it's 2^(6*8) = 281 474 976 710 656
PASTE_ID_LENGTH = 8 PASTE_ID_LENGTH = 8

View File

@ -27,15 +27,15 @@ from beaker.middleware import SessionMiddleware
from zerobin import __version__ from zerobin import __version__
from zerobin.utils import ( from zerobin.utils import (
settings,
SettingsValidationError, SettingsValidationError,
ensure_var_env, ensure_app_context,
check_password, check_password,
settings,
) )
from zerobin.paste import Paste from zerobin.paste import Paste
ensure_var_env() ensure_app_context()
GLOBAL_CONTEXT = { GLOBAL_CONTEXT = {
@ -235,33 +235,33 @@ def server_static(filename):
return static_file(filename, root=settings.STATIC_FILES_ROOT) return static_file(filename, root=settings.STATIC_FILES_ROOT)
def get_app(debug=None, settings_file="", compressed_static=None, settings=settings): def get_app(debug=None, config_dir="", data_dir=""):
""" """
Return a tuple (settings, app) configured using passed Return a tuple (settings, app) configured using passed
parameters and/or a setting file. parameters and/or a setting file.
""" """
settings_file = settings_file or os.environ.get("ZEROBIN_SETTINGS_FILE") data_dir = data_dir or os.environ.get("ZEROBIN_DATA_DIR")
config_dir = config_dir or os.environ.get("ZEROBIN_CONFIG_DIR")
if settings_file: ensure_app_context(config_dir=config_dir, data_dir=data_dir)
settings.update_with_file(os.path.realpath(settings_file))
settings.DEBUG = bool(debug or os.environ.get("ZEROBIN_DEBUG", settings.DEBUG))
settings.DISPLAY_COUNTER = bool(
os.environ.get("ZEROBIN_DISPLAY_COUNTER", settings.DISPLAY_COUNTER)
)
settings.REFRESH_COUNTER = int(
os.environ.get("ZEROBIN_REFRESH_COUNTER", settings.REFRESH_COUNTER)
)
settings.MAX_SIZE = int(os.environ.get("ZEROBIN_MAX_SIZE", settings.MAX_SIZE))
settings.PASTE_ID_LENGTH = int(
os.environ.get("ZEROBIN_PASTE_ID_LENGTH", settings.PASTE_ID_LENGTH)
)
if settings.PASTE_ID_LENGTH < 4: if settings.PASTE_ID_LENGTH < 4:
raise SettingsValidationError("PASTE_ID_LENGTH cannot be lower than 4") raise SettingsValidationError("PASTE_ID_LENGTH cannot be lower than 4")
if compressed_static is not None:
settings.COMPRESSED_STATIC_FILES = compressed_static
if debug is not None:
settings.DEBUG = debug
# make sure the templates can be loaded
for d in reversed(settings.TEMPLATE_DIRS):
bottle.TEMPLATE_PATH.insert(0, d)
if settings.DEBUG:
bottle.debug(True)
return settings, app return settings, app

View File

@ -46,9 +46,6 @@
/* body & other stuff */ /* body & other stuff */
body {
padding-bottom: 40px;
}
.blk-space { .blk-space {
height: 20px; height: 20px;
@ -101,16 +98,24 @@ blockquote {
/* Footer */ /* Footer */
#app {
display: flex;
min-height: 100vh;
flex-direction: column;
}
#wrap-content {
flex: 1;
}
.footer { .footer {
text-align: center; text-align: center;
bottom: 0px;
position: fixed;
height: 60px; height: 60px;
width: 100%; width: 100%;
position: relative;
display: block; display: block;
margin: 0px 0px 0px 0px; margin: 0px 0px 0px 0px;
padding: 8px 0 0 0; padding: 8px 0 0 0;
left: 0;
background-color: #424141; background-color: #424141;
} }

View File

@ -18,8 +18,6 @@ setTimeout(function () {
document.getElementById('content').focus() document.getElementById('content').focus()
}, 100) }, 100)
const app = new Vue({ const app = new Vue({
el: '#app', el: '#app',

View File

@ -8,6 +8,13 @@ import hashlib
import secrets import secrets
from functools import partial from functools import partial
from pathlib import Path
import bottle
from appdirs import AppDirs
import zerobin
from zerobin import default_settings from zerobin import default_settings
@ -92,7 +99,7 @@ def as_unicode(obj):
return str(obj) return str(obj)
def ensure_var_env(): def ensure_app_context(data_dir=None, config_dir=None):
""" Ensure all the variable things we generate are available. """ Ensure all the variable things we generate are available.
This will make sure we have: This will make sure we have:
@ -101,20 +108,39 @@ def ensure_var_env():
- a content dir - a content dir
- a secret key - a secret key
- an admin URL - an admin URL
This function is idempotent if nothing touch the files it created.
""" """
settings.VAR_DIR.mkdir(exist_ok=True, parents=True) app_dirs = AppDirs("0bin", "tygs")
settings.PASTE_FILES_ROOT = settings.VAR_DIR / "content"
settings.DATA_DIR = Path(data_dir or app_dirs.user_data_dir).expanduser()
settings.DATA_DIR.mkdir(exist_ok=True, parents=True)
settings.CONFIG_DIR = Path(config_dir or app_dirs.user_config_dir).expanduser()
settings.CONFIG_DIR.mkdir(exist_ok=True, parents=True)
settings.STATIC_FILES_ROOT = zerobin.ROOT_DIR / "static"
settings.PASTE_FILES_ROOT = settings.DATA_DIR / "pastes"
settings.PASTE_FILES_ROOT.mkdir(exist_ok=True) settings.PASTE_FILES_ROOT.mkdir(exist_ok=True)
settings.SESSIONS_DIR = settings.VAR_DIR / "sessions"
settings.SESSIONS_DIR = settings.DATA_DIR / "sessions"
settings.SESSIONS_DIR.mkdir(exist_ok=True) settings.SESSIONS_DIR.mkdir(exist_ok=True)
secret_key_file = settings.VAR_DIR / "secret_key" bottle.TEMPLATE_PATH.insert(0, zerobin.ROOT_DIR / "views")
CUSTOM_VIEWS_DIR = settings.CONFIG_DIR / "custom_views"
CUSTOM_VIEWS_DIR.mkdir(exist_ok=True)
bottle.TEMPLATE_PATH.insert(0, CUSTOM_VIEWS_DIR)
secret_key_file = settings.CONFIG_DIR / "secret_key"
if not secret_key_file.is_file(): if not secret_key_file.is_file():
secret_key_file.write_text(secrets.token_urlsafe(64)) secret_key_file.write_text(secrets.token_urlsafe(64))
settings.SECRET_KEY = secret_key_file.read_text() settings.SECRET_KEY = secret_key_file.read_text()
admin_password_file = settings.VAR_DIR / "admin_password" admin_password_file = settings.CONFIG_DIR / "admin_password"
if not secret_key_file.is_file(): if not secret_key_file.is_file():
admin_password_file.write_text( admin_password_file.write_text(
"No password set. Use the set_admin_passord command. Don't write this file by hand." "No password set. Use the set_admin_passord command. Don't write this file by hand."
@ -124,6 +150,13 @@ def ensure_var_env():
payload = ("admin" + settings.SECRET_KEY).encode("ascii") payload = ("admin" + settings.SECRET_KEY).encode("ascii")
settings.ADMIN_URL = "/admin/" + hashlib.sha256(payload).hexdigest() + "/" settings.ADMIN_URL = "/admin/" + hashlib.sha256(payload).hexdigest() + "/"
settings_file = settings.CONFIG_DIR / "settings.py"
if not settings_file.is_file():
default_config = (zerobin.ROOT_DIR / "default_settings.py").read_text()
settings_file.write_text(default_config)
settings.update_with_file(settings_file)
def hash_password(password): def hash_password(password):
return hashlib.scrypt( return hashlib.scrypt(

View File

@ -12,7 +12,7 @@
<link rel="icon" href="/static/img/favicon.ico" /> <link rel="icon" href="/static/img/favicon.ico" />
%if settings.COMPRESSED_STATIC_FILES: %if not settings.DEBUG:
<link href="/static/css/style.min.css?{{ VERSION }}" rel="stylesheet" /> <link href="/static/css/style.min.css?{{ VERSION }}" rel="stylesheet" />
%else: %else:
<link href="/static/css/prettify.css" rel="stylesheet" /> <link href="/static/css/prettify.css" rel="stylesheet" />
@ -94,10 +94,11 @@
</div> </div>
<script src="/static/js/vue.js"></script>
%if settings.COMPRESSED_STATIC_FILES: %if not settings.DEBUG:
<script src="/static/js/main.min.js?{{ VERSION }}"></script> <script src="/static/js/main.min.js?{{ VERSION }}"></script>
%else: %else:
<script src="/static/js/vue.js"></script>
<script src="/static/js/sjcl.js"></script> <script src="/static/js/sjcl.js"></script>
<script src="/static/js/behavior.js?{{ VERSION }}"></script> <script src="/static/js/behavior.js?{{ VERSION }}"></script>
%end %end
@ -107,8 +108,8 @@
</script> </script>
%if settings.COMPRESSED_STATIC_FILES: %if not settings.DEBUG:
<script src="/static/js/additional.min.js?{{ settings.VERSION }}"></script> <script src="/static/js/additional.min.js?{{ VERSION }}"></script>
%else: %else:
<script src="/static/js/lzw.js"></script> <script src="/static/js/lzw.js"></script>
<script src="/static/js/prettify.min.js"></script> <script src="/static/js/prettify.min.js"></script>

3
zerobin/wsgi.py Normal file
View File

@ -0,0 +1,3 @@
from zerobin.wsgi import setup_app
settings, app = get_app()