parent
6cf94b0efe
commit
9cc13191d4
@ -1,117 +1,57 @@
|
|||||||
from bottle import route, request, run, redirect, template, default_app, abort
|
from os import environ
|
||||||
import os
|
|
||||||
from datetime import datetime
|
|
||||||
from mimetypes import guess_type
|
|
||||||
|
|
||||||
base_hosts = [
|
from sanic import Sanic
|
||||||
'drop.bakatrouble.pw',
|
from sanic.request import Request
|
||||||
'drop.bakatrouble.me',
|
from sanic.response import text, html, file_stream, redirect
|
||||||
'127.0.0.1.xip.io:8080',
|
from sanic.exceptions import abort
|
||||||
]
|
|
||||||
|
|
||||||
|
from utils import get_j2env, get_sort_icon, get_sort_link, resolve_path, list_dir
|
||||||
|
|
||||||
def format_size(size):
|
|
||||||
for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB']:
|
|
||||||
if size < 2048:
|
|
||||||
return f'{size} {unit}'
|
|
||||||
else:
|
|
||||||
size //= 1024
|
|
||||||
return f'{size} PiB'
|
|
||||||
|
|
||||||
|
DEBUG = environ.get('ENV', '').upper() != 'PRODUCTION'
|
||||||
|
|
||||||
def format_date(ts):
|
app = Sanic()
|
||||||
return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
|
app.static('/~static/', '~static/', use_content_range=True, stream_large_files=True)
|
||||||
|
j2env = get_j2env(DEBUG)
|
||||||
|
|
||||||
|
|
||||||
def get_sort_icon(test, sort):
|
@app.route('/')
|
||||||
if sort == test:
|
@app.route('/<path:path>')
|
||||||
return '<i class="sort descending icon"></i>'
|
async def index(request: Request, path=''):
|
||||||
elif sort == f'-{test}':
|
domain = request.host
|
||||||
return '<i class="sort ascending icon"></i>'
|
query = f'?{request.query_string}' if request.query_string else ''
|
||||||
else:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
def get_sort_link(current, sort, hidden):
|
|
||||||
return f'?sort={"-" if current == sort else ""}{sort}{"&hidden" if hidden else ""}'
|
|
||||||
|
|
||||||
|
|
||||||
def get_file_icon(name):
|
|
||||||
mime = guess_type(name)[0]
|
|
||||||
if mime:
|
|
||||||
t = mime.split('/')[0]
|
|
||||||
if t in ['text', 'image', 'audio', 'video']:
|
|
||||||
return t
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
class ListEntry:
|
|
||||||
def __init__(self, dir, name):
|
|
||||||
path = os.path.join(dir, name)
|
|
||||||
self.name = name
|
|
||||||
self.isdir = os.path.isdir(path)
|
|
||||||
self.size = os.path.getsize(path)
|
|
||||||
self.created = os.path.getctime(path)
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_path(domain, path):
|
|
||||||
if domain in base_hosts:
|
|
||||||
return os.path.join(os.path.dirname(__file__), 'files', *path.split('/'))
|
|
||||||
else:
|
|
||||||
for host in base_hosts:
|
|
||||||
if host in domain:
|
|
||||||
return os.path.join(os.path.dirname(__file__), 'subdomain_files',
|
|
||||||
domain[:domain.index(host)-1], *path.split('/'))
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
|
|
||||||
def list_dir(dir, sort=None, hidden=False):
|
|
||||||
lst = [ListEntry(dir, name) for name in os.listdir(dir) if hidden or not name.startswith('.')]
|
|
||||||
lst = sorted(lst, key={
|
|
||||||
'name': lambda item: (not item.isdir, item.name.lower()),
|
|
||||||
'-name': lambda item: (item.isdir, item.name.lower()),
|
|
||||||
'size': lambda item: (not item.isdir, item.size),
|
|
||||||
'-size': lambda item: (item.isdir, item.size),
|
|
||||||
'created': lambda item: (not item.isdir, item.created),
|
|
||||||
'-created': lambda item: (item.isdir, item.created)
|
|
||||||
}[sort], reverse=sort.startswith('-'))
|
|
||||||
return lst
|
|
||||||
|
|
||||||
|
|
||||||
@route('/')
|
|
||||||
@route('/<path:path>')
|
|
||||||
def index(path=''):
|
|
||||||
domain = request.urlparts.netloc
|
|
||||||
query = request.urlparts.query
|
|
||||||
try:
|
try:
|
||||||
resolved_path = resolve_path(domain, path)
|
resolved_path, resolved_query = resolve_path(domain, path)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 'GTFO'
|
return text('GTFO', 400)
|
||||||
if os.path.isdir(resolved_path):
|
|
||||||
|
if resolved_path.is_dir():
|
||||||
if path and path[-1] != '/':
|
if path and path[-1] != '/':
|
||||||
return redirect(f'/{path}/')
|
return redirect(f'/{path}/')
|
||||||
|
|
||||||
hidden = 'hidden' in request.GET
|
hidden = request.args.get('hidden') is not None
|
||||||
sort = request.GET.get('sort', 'name')
|
sort = request.args.get('sort', 'name')
|
||||||
if sort not in ['name', '-name', 'size', '-size', 'created', '-created']:
|
if sort not in ['name', '-name', 'size', '-size', 'created', '-created']:
|
||||||
sort = 'name'
|
sort = 'name'
|
||||||
|
|
||||||
return template('filelist',
|
return html(j2env.get_template('filelist.tpl').render(
|
||||||
lst=list_dir(resolved_path, sort, hidden),
|
lst=list_dir(resolved_path, sort, hidden, root=not resolved_query),
|
||||||
format_size=format_size,
|
get_sort_icon=get_sort_icon,
|
||||||
format_date=format_date,
|
get_sort_link=get_sort_link,
|
||||||
get_sort_icon=get_sort_icon,
|
sort=sort,
|
||||||
get_sort_link=get_sort_link,
|
hidden=hidden,
|
||||||
sort=sort,
|
path=resolved_query,
|
||||||
hidden=hidden,
|
query=query,
|
||||||
path=path,
|
))
|
||||||
query=query,
|
elif resolved_path.is_file():
|
||||||
guess_type=guess_type,
|
return await file_stream(resolved_path)
|
||||||
get_file_icon=get_file_icon)
|
|
||||||
else:
|
abort(404, 'Path was not found')
|
||||||
return abort(404)
|
|
||||||
|
|
||||||
application = default_app()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
run(host='localhost', port=8080, debug=True, reloader=True)
|
if DEBUG:
|
||||||
|
app.run(host='localhost', port=8080, debug=True, auto_reload=True)
|
||||||
|
else:
|
||||||
|
app.run(sock='/tmp/drop.sock', workers=2)
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
BASE_HOSTS = [
|
||||||
|
'drop.bakatrouble.pw',
|
||||||
|
'drop.bakatrouble.me',
|
||||||
|
'127.0.0.1.xip.io:8080',
|
||||||
|
'localhost:8080',
|
||||||
|
]
|
||||||
|
|
||||||
|
SORT_KEYS = {
|
||||||
|
'name': lambda item: (not item.is_dir, item.name.lower()),
|
||||||
|
'-name': lambda item: (item.is_dir, item.name.lower()),
|
||||||
|
'size': lambda item: (not item.is_dir, item.size),
|
||||||
|
'-size': lambda item: (item.is_dir, item.size),
|
||||||
|
'created': lambda item: (not item.is_dir, item.created),
|
||||||
|
'-created': lambda item: (item.is_dir, item.created),
|
||||||
|
}
|
||||||
|
|
||||||
|
ICONS = {
|
||||||
|
'sort_asc': 'icon-up-dir',
|
||||||
|
'sort_desc': 'icon-down-dir',
|
||||||
|
'types': {
|
||||||
|
'parent': 'icon-level-up',
|
||||||
|
'dir': 'icon-folder',
|
||||||
|
'file': 'icon-doc',
|
||||||
|
'text': 'icon-doc-text',
|
||||||
|
'image': 'icon-picture-outline',
|
||||||
|
'music': 'icon-music-outline',
|
||||||
|
'video': 'icon-video',
|
||||||
|
},
|
||||||
|
}
|
@ -1,65 +1,45 @@
|
|||||||
server {
|
location @drop_app {
|
||||||
server_name drop.bakatrouble.pw;
|
proxy_pass unix:///tmp/drop.sock;
|
||||||
|
proxy_set_header Host $host;
|
||||||
location /_ {
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
alias /srv/apps/drop/files;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
include uwsgi_params;
|
|
||||||
uwsgi_pass unix:///tmp/drop.sock;
|
|
||||||
}
|
|
||||||
|
|
||||||
listen 443 ssl; # managed by Certbot
|
|
||||||
ssl_certificate /etc/letsencrypt/live/bakatrouble.pw/fullchain.pem; # managed by Certbot
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/bakatrouble.pw/privkey.pem; # managed by Certbot
|
|
||||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
|
||||||
|
|
||||||
|
|
||||||
if ($scheme != "https") {
|
server {
|
||||||
return 301 https://$host$request_uri;
|
server_name ~^drop\.bakatrouble\.(pw|me)$;
|
||||||
} # managed by Certbot
|
root /srv/apps/drop/files;
|
||||||
|
|
||||||
|
location /~static/ {
|
||||||
|
alias /srv/apps/drop/~static;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri @drop_app;
|
||||||
|
}
|
||||||
|
|
||||||
|
include /etc/nginx/letsencrypt-serv.conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
server_name ~^(?<dir>.*)\.drop\.bakatrouble\.pw$;
|
server_name ~^(?<dir>.*)\.drop\.bakatrouble\.(pw|me)$;
|
||||||
|
root /srv/apps/drop/subdomain_files/$dir;
|
||||||
location /_ {
|
|
||||||
alias /srv/apps/drop/subdomain_files/$dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
include uwsgi_params;
|
|
||||||
uwsgi_pass unix:///tmp/drop.sock;
|
|
||||||
}
|
|
||||||
|
|
||||||
listen 443 ssl; # managed by Certbot
|
location /~static/ {
|
||||||
ssl_certificate /etc/letsencrypt/live/bakatrouble.pw/fullchain.pem; # managed by Certbot
|
alias /srv/apps/drop/~static;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/bakatrouble.pw/privkey.pem; # managed by Certbot
|
}
|
||||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri @drop_app;
|
||||||
|
}
|
||||||
|
|
||||||
if ($scheme != "https") {
|
include /etc/nginx/letsencrypt-serv.conf;
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
} # managed by Certbot
|
|
||||||
|
|
||||||
}
|
}
|
||||||
server {
|
|
||||||
if ($host = drop.bakatrouble.pw) {
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
} # managed by Certbot
|
|
||||||
|
|
||||||
if ($host ~ ^(?<dir>.*)\.drop\.bakatrouble\.pw$) {
|
server {
|
||||||
|
if ($host ~ ^(.*\.)?drop\.bakatrouble\.(pw|me)$) {
|
||||||
return 301 https://$host$request_uri;
|
return 301 https://$host$request_uri;
|
||||||
} # managed by Certbot
|
}
|
||||||
|
|
||||||
|
listen 80;
|
||||||
listen 80;
|
server_name ~^(.*\.)?drop\.bakatrouble\.(pw|me)$;
|
||||||
server_name drop.bakatrouble.pw ~^(?<dir>.*)\.drop\.bakatrouble\.pw$;
|
return 404;
|
||||||
return 404; # managed by Certbot
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,9 +1,10 @@
|
|||||||
[program:drop]
|
[program:drop]
|
||||||
user = arch
|
user = arch
|
||||||
directory = /srv/apps/drop
|
directory = /srv/apps/drop
|
||||||
command = /srv/apps/drop/venv/bin/uwsgi --ini /srv/apps/drop/configs/uwsgi.ini
|
command = /srv/apps/drop/venv/bin/python /srv/apps/drop/autoindex.py
|
||||||
autostart = true
|
autostart = true
|
||||||
autorestart = true
|
autorestart = true
|
||||||
stderr_logfile = /srv/apps/drop/logs/uwsgi_err.log
|
stderr_logfile = /srv/apps/drop/logs/app.log
|
||||||
stdout_logfile = /srv/apps/drop/logs/uwsgi_out.log
|
stdout_logfile = /srv/apps/drop/logs/app.log
|
||||||
stopsignal = INT
|
stopsignal = INT
|
||||||
|
environment = ENV=PRODUCTION
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
[uwsgi]
|
|
||||||
socket = /tmp/drop.sock
|
|
||||||
chmod-socket = 666
|
|
||||||
module = autoindex:application
|
|
||||||
master = true
|
|
||||||
processes = 2
|
|
||||||
enable-threads = true
|
|
@ -0,0 +1,9 @@
|
|||||||
|
aiofiles==0.4.0
|
||||||
|
httptools==0.0.13
|
||||||
|
Jinja2==2.10
|
||||||
|
MarkupSafe==1.1.1
|
||||||
|
multidict==4.5.2
|
||||||
|
sanic==18.12.0
|
||||||
|
ujson==1.35
|
||||||
|
uvloop==0.12.1
|
||||||
|
websockets==6.0
|
@ -0,0 +1,97 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from mimetypes import guess_type
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
|
from conf import BASE_HOSTS, SORT_KEYS, ICONS
|
||||||
|
|
||||||
|
|
||||||
|
def icon_html(cls):
|
||||||
|
return f'<i class="{cls}"></i>'
|
||||||
|
|
||||||
|
|
||||||
|
class ListEntry:
|
||||||
|
def __init__(self, path: Path):
|
||||||
|
self.name = path.name
|
||||||
|
self.mime = guess_type(self.name)[0]
|
||||||
|
self.is_dir = path.is_dir()
|
||||||
|
self.size = path.stat().st_size
|
||||||
|
self.created = path.stat().st_ctime
|
||||||
|
|
||||||
|
@property
|
||||||
|
def link_name(self):
|
||||||
|
if self.is_dir:
|
||||||
|
return f'{self.name}/'
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_name(self):
|
||||||
|
if self.is_dir:
|
||||||
|
return f'{self.name}/'
|
||||||
|
return f'{self.name} [{self.mime}]'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
if self.name == '..':
|
||||||
|
return icon_html(ICONS['types']['parent'])
|
||||||
|
elif self.is_dir:
|
||||||
|
return icon_html(ICONS['types']['dir'])
|
||||||
|
elif self.mime:
|
||||||
|
t = self.mime.split('/')[0]
|
||||||
|
if t in ICONS['types']:
|
||||||
|
return icon_html(ICONS['types'][t])
|
||||||
|
return icon_html(ICONS['types']['file'])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def formatted_date(self):
|
||||||
|
return datetime.fromtimestamp(self.created).strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def formatted_size(self):
|
||||||
|
size = self.size
|
||||||
|
for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB']:
|
||||||
|
if size < 2048:
|
||||||
|
return f'{size} {unit}'
|
||||||
|
else:
|
||||||
|
size //= 1024
|
||||||
|
return f'{size} PiB'
|
||||||
|
|
||||||
|
|
||||||
|
def get_j2env(debug=False):
|
||||||
|
return Environment(loader=FileSystemLoader([Path('.').absolute()]),
|
||||||
|
trim_blocks=True, optimized=debug, cache_size=0 if debug else 400)
|
||||||
|
|
||||||
|
|
||||||
|
def get_sort_icon(test, sort):
|
||||||
|
if sort == test:
|
||||||
|
return icon_html(ICONS['sort_asc'])
|
||||||
|
elif sort == f'-{test}':
|
||||||
|
return icon_html(ICONS['sort_desc'])
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def get_sort_link(current, sort, hidden):
|
||||||
|
return f'?sort={"-" if current == sort else ""}{sort}{"&hidden" if hidden else ""}'
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_path(domain, path):
|
||||||
|
path_parts = [i for i in path.split('/') if i not in ['.', '..']]
|
||||||
|
joined_parts = '/'.join(path_parts)
|
||||||
|
if domain in BASE_HOSTS:
|
||||||
|
return Path('files').joinpath(*path_parts), joined_parts
|
||||||
|
else:
|
||||||
|
for host in BASE_HOSTS:
|
||||||
|
if host in domain:
|
||||||
|
return Path('subdomain_files').joinpath(domain[:domain.index(host)-1], *path_parts), joined_parts
|
||||||
|
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
|
||||||
|
def list_dir(directory: Path, sort, hidden=False, root=True):
|
||||||
|
lst = [ListEntry(path) for path in directory.iterdir() if hidden or not path.name.startswith('.')]
|
||||||
|
lst = sorted(lst, key=SORT_KEYS[sort], reverse=sort.startswith('-'))
|
||||||
|
if not root:
|
||||||
|
lst = [ListEntry(Path('..'))] + lst
|
||||||
|
return lst
|
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue