create auto-restart script and auto-compile js
This commit is contained in:
parent
9a595f1576
commit
d4c1d320b0
15
.gitignore
vendored
15
.gitignore
vendored
|
@ -1,8 +1,13 @@
|
|||
*.env
|
||||
*.patch
|
||||
|
||||
# Python cache
|
||||
__pycache__
|
||||
/build
|
||||
*.pyc
|
||||
|
||||
# Extra dirs
|
||||
/build
|
||||
/data
|
||||
/bin
|
||||
/bin
|
||||
/misc
|
||||
|
||||
# Misc files
|
||||
nohup.out
|
||||
webapp/js/__target__
|
||||
|
|
|
@ -91,6 +91,10 @@ def first_setup():
|
|||
def settings():
|
||||
setresults = db.query('SELECT * FROM settings').dictresult()
|
||||
|
||||
if setresults == []:
|
||||
logging.warning('Can\'t find settings in the database')
|
||||
return
|
||||
|
||||
set_dict = {}
|
||||
|
||||
for line in setresults:
|
||||
|
@ -100,5 +104,13 @@ def settings():
|
|||
return set_dict
|
||||
|
||||
|
||||
def update_timestamps():
|
||||
for token in cache.token.items:
|
||||
db.update('auth_tokens', {'id': cache.token.items[token]['id']}, access=cache.token.items[token]['timestamp'])
|
||||
|
||||
for token in cache.cookie.items:
|
||||
db.update('login_cookies', {'id': cache.cookie.items[token]['id']}, access=cache.cookie.items[token]['timestamp'])
|
||||
|
||||
|
||||
newtrans(first_setup())
|
||||
SETTINGS = settings()
|
||||
|
|
|
@ -22,10 +22,10 @@ pyenv = env.get('PYENV', 'default').lower()
|
|||
|
||||
|
||||
try:
|
||||
if pyenv in ['prod', 'default']:
|
||||
if pyenv == 'prod':
|
||||
load_envbash(stor_path+'/prod.env')
|
||||
|
||||
elif pyenv == 'dev':
|
||||
elif pyenv in ['dev', 'default']:
|
||||
load_envbash(stor_path+'/dev.env')
|
||||
|
||||
else:
|
||||
|
@ -108,7 +108,7 @@ logging.addHandler(console)
|
|||
|
||||
if pyenv in ['prod', 'default']:
|
||||
if pyenv == 'default':
|
||||
logging.warning('No environment specified. Assuming production')
|
||||
logging.warning('No environment specified. Assuming development')
|
||||
logging.warning('Set "PYENV" to "prod" or "dev" to disable this warning')
|
||||
|
||||
logging.debug('Starting in production mode')
|
||||
|
|
11
dist/database.sql
vendored
11
dist/database.sql
vendored
|
@ -24,20 +24,23 @@ CREATE TABLE IF NOT EXISTS statuses (
|
|||
timestamp FLOAT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tokens (
|
||||
CREATE TABLE IF NOT EXISTS auth_tokens (
|
||||
id SERIAL PRIMARY KEY,
|
||||
userid INT NOT NULL,
|
||||
appid INT NOT NULL,
|
||||
token TEXT NOT NULL
|
||||
token TEXT NOT NULL,
|
||||
timestamp INT NOT NULL,
|
||||
access INT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS login_cookies (
|
||||
id SERIAL PRIMARY KEY,
|
||||
userid INT NOT NULL,
|
||||
cookie TEXT UNIQUE NOT NULL,
|
||||
timestamp INT NOT NULL,
|
||||
address TEXT,
|
||||
agent TEXT
|
||||
agent TEXT,
|
||||
timestamp INT NOT NULL,
|
||||
access INT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS guests (
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
from jinja2 import BaseLoader, TemplateNotFound
|
||||
from os.path import join, exists, getmtime, isfile
|
||||
from os import environ as env
|
||||
|
||||
from config import stor_path, script_path, logging
|
||||
from simplecache import LRUCache
|
||||
|
||||
|
||||
pyenv = env.get('PYENV', 'dev').lower()
|
||||
|
||||
|
||||
class CustomLoader(BaseLoader):
|
||||
|
||||
|
@ -18,15 +24,16 @@ class CustomLoader(BaseLoader):
|
|||
logging.debug('Can\'t find custom template file: '+custom_path+template)
|
||||
path = join(self.path, template)
|
||||
|
||||
try:
|
||||
mtime = getmtime(path)
|
||||
|
||||
except FileNotFoundError:
|
||||
if isfile(path) == False:
|
||||
logging.error('Can\'t find template file: '+path)
|
||||
path = join(self.path, 'missing.html')
|
||||
mtime = getmtime(path)
|
||||
|
||||
mtime = getmtime(path)
|
||||
|
||||
with open(path) as f:
|
||||
logging.debug(f'Loading template: {path}')
|
||||
source = f.read()
|
||||
|
||||
return source, path, lambda: mtime == getmtime(path)
|
||||
get_time = lambda: mtime == getmtime(path)
|
||||
|
||||
return source, path, get_time
|
||||
|
|
|
@ -5,17 +5,6 @@
|
|||
{% set user = None %}
|
||||
{% endif %}
|
||||
|
||||
{% set colors %}
|
||||
<div class="submenu">
|
||||
<details>
|
||||
<summary class="item"><a>Colors</a></summary>
|
||||
{% for theme in themes() %}
|
||||
<div class="item"><a href="javascript:void(0);" onclick="javascript:theme('{{theme}}');" class="theme_link">{{theme}}</a></div>
|
||||
{% endfor %}
|
||||
</details>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
@ -44,47 +33,7 @@
|
|||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="user_panel" class="menu menu-right">
|
||||
{% if user != None %}
|
||||
<details>
|
||||
<summary id="menu_title"><a class="text">{{user.name}}</a></summary>
|
||||
<div class="item"><a href="https://{{domain}}/">Home</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/welcome">User Panel</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/@{{user.handle}}">Profile</a></div>
|
||||
<div class="submenu">
|
||||
<details>
|
||||
<summary class="item"><a class="text">Settings</a></summary>
|
||||
<div class="item"><a href="https://{{domain}}/settings#profile">Profile</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/settings#account">Account</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/settings#options">Options</a></div>
|
||||
{% if user.permissions < 2 %}<div class="item"><a href="https://{{domain}}/admin">Admin</a></div>{% endif %}
|
||||
</details>
|
||||
</div>
|
||||
{{colors}}
|
||||
<div class="submenu" id="toot_panel">
|
||||
<details>
|
||||
<summary class="item"><a class="text">Toot!</a></summary>
|
||||
<form action="/poast" method="post">
|
||||
<input type="text" name="warning" placeholder="Content Warning"><br>
|
||||
<textarea id="toot_box" name="text" placeholder="*notices your post* OwO what's this?" maxlength="{{settings.char_limit}}"></textarea><br>
|
||||
<input type="submit" value="YEET!">
|
||||
</form>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class="item"><a href="https://{{domain}}/logout">Logout</a></div>
|
||||
</details>
|
||||
{% else %}
|
||||
<details>
|
||||
<summary id="menu_title">Guest</summary>
|
||||
<div class="item"><a href="https://{{domain}}/">Home</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/login">Login</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/register">Register</a></div>
|
||||
{{colors}}
|
||||
</details>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% include "components/menu.html" %}
|
||||
<div id="header">
|
||||
<h1 class="title"><a href="https://{{domain}}">{{name}}</a></h1>
|
||||
</div>
|
||||
|
|
8
frontend/templates/components/colors.html
Normal file
8
frontend/templates/components/colors.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div class="submenu">
|
||||
<details>
|
||||
<summary class="item"><a>Colors</a></summary>
|
||||
{% for theme in themes() %}
|
||||
<div class="item"><a href="javascript:void(0);" onclick="javascript:theme('{{theme}}');" class="theme_link">{{theme}}</a></div>
|
||||
{% endfor %}
|
||||
</details>
|
||||
</div>
|
40
frontend/templates/components/menu.html
Normal file
40
frontend/templates/components/menu.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
<div id="user_panel" class="menu menu-right">
|
||||
{% if user != None %}
|
||||
<details>
|
||||
<summary id="menu_title"><a class="text">{{user.name}}</a></summary>
|
||||
<div class="item"><a href="https://{{domain}}/">Home</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/welcome">User Panel</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/@{{user.handle}}">Profile</a></div>
|
||||
<div class="submenu">
|
||||
<details>
|
||||
<summary class="item"><a class="text">Settings</a></summary>
|
||||
<div class="item"><a href="https://{{domain}}/settings#profile">Profile</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/settings#account">Account</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/settings#options">Options</a></div>
|
||||
{% if user.permissions < 2 %}<div class="item"><a href="https://{{domain}}/admin">Admin</a></div>{% endif %}
|
||||
</details>
|
||||
</div>
|
||||
{% include "components/colors.html" %}
|
||||
<div class="submenu" id="toot_panel">
|
||||
<details>
|
||||
<summary class="item"><a class="text">Toot!</a></summary>
|
||||
<form action="/poast" method="post">
|
||||
<input type="text" name="warning" placeholder="Content Warning"><br>
|
||||
<textarea id="toot_box" name="text" placeholder="*notices your post* OwO what's this?" maxlength="{{settings.char_limit}}"></textarea><br>
|
||||
<input type="submit" value="YEET!">
|
||||
</form>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class="item"><a href="https://{{domain}}/logout">Logout</a></div>
|
||||
</details>
|
||||
{% else %}
|
||||
<details>
|
||||
<summary id="menu_title">Guest</summary>
|
||||
<div class="item"><a href="https://{{domain}}/">Home</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/login">Login</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/register">Register</a></div>
|
||||
{% include "components/colors.html" %}
|
||||
</details>
|
||||
{% endif %}
|
||||
</div>
|
|
@ -4,8 +4,7 @@
|
|||
|
||||
{% block content %}
|
||||
<center>
|
||||
<font size=2>It's only pre-alpha</font><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
|
||||
<font size=1>merp</font><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
|
||||
<font size=1>I'm just gonna end up shitposting with this damn thing</font>
|
||||
<font size=2>I don't know what I'm doing tbh</font><br><br><br><br><br><br><br>
|
||||
<font style="font-size: 8px">merp</font><br><br><br><br><br><br><br>
|
||||
</center>
|
||||
{% endblock %}
|
||||
|
|
|
@ -59,6 +59,15 @@
|
|||
|
||||
<div id="options" class="section">
|
||||
<h2>Options</h2>
|
||||
<form action="/settings" method="post">
|
||||
<div class="grid-container">
|
||||
<div class="grid-item">
|
||||
Colors
|
||||
<input type="radio" name="color" value="{{color}}" id="color-{{color}}"><label for="color-{{color}}">{{color}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
74
reload.py
Executable file
74
reload.py
Executable file
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/env python3
|
||||
import time
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
os.environ['PYENV'] = 'dev'
|
||||
from config import logging
|
||||
|
||||
|
||||
pid = ''
|
||||
|
||||
|
||||
def run(restart=False):
|
||||
global pid
|
||||
|
||||
if restart == True and pid != '':
|
||||
logging.info('Terminating process...')
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
|
||||
logging.info('Starting process...')
|
||||
proc = subprocess.Popen(['nohup', './server.py'], preexec_fn=os.setpgrp)
|
||||
pid = proc.pid
|
||||
|
||||
logging.info(f'Process PID: {pid}')
|
||||
|
||||
|
||||
class MyHandler(FileSystemEventHandler):
|
||||
def on_modified(self, event):
|
||||
filename, ext = os.path.splitext(os.path.relpath(event.src_path))
|
||||
|
||||
if event.event_type == 'modified' and ext == '.py' and re.search(ignore_paths, filename) == None and filename not in ignore_filenames:
|
||||
logging.info('Restarting server...')
|
||||
run(restart=True)
|
||||
|
||||
|
||||
def safe_stop():
|
||||
logging.info('Stopping process watcher')
|
||||
observer.stop()
|
||||
observer.join()
|
||||
|
||||
if pid != '':
|
||||
logging.info(f'Killing process: {pid}')
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
|
||||
sys.exit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
observer = Observer()
|
||||
observer.schedule(MyHandler(), path, recursive=True)
|
||||
|
||||
ignore_paths = 'webapp/js|bin|dist|misc|data'
|
||||
ignore_filenames = ['reload']
|
||||
|
||||
logging.info('Starting process watcher')
|
||||
observer.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(100)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
safe_stop()
|
39
routes.py
39
routes.py
|
@ -3,13 +3,15 @@ import aiohttp
|
|||
import jinja2
|
||||
import aiohttp_jinja2
|
||||
import json
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from jinja2 import FileSystemLoader, select_autoescape
|
||||
|
||||
from config import config, logging
|
||||
|
||||
from backend import mastodon_api, api, middleware, wellknown
|
||||
from backend.database import SETTINGS, newtrans, get
|
||||
from backend.database import SETTINGS, newtrans, get, update_timestamps
|
||||
from frontend import views, resources, user, redirects
|
||||
from functions import color, todate, themes
|
||||
from frontend.template_loader import CustomLoader
|
||||
|
@ -109,6 +111,7 @@ web.router.add_get('/socks', redirects.socks)
|
|||
async def start_web():
|
||||
runner = aiohttp.web.AppRunner(web, access_log_format='%{X-Real-Ip}i "%r" %s %b "%{User-Agent}i"')
|
||||
await runner.setup()
|
||||
|
||||
listen = config['listen']
|
||||
port = config['port']
|
||||
|
||||
|
@ -119,7 +122,35 @@ async def start_web():
|
|||
await site.start()
|
||||
|
||||
|
||||
async def save_tokens():
|
||||
while True:
|
||||
logging.debug('Saving updated timestamps for login and auth tokens to the database')
|
||||
update_timestamps()
|
||||
|
||||
await asyncio.sleep(600)
|
||||
|
||||
|
||||
def safe_stop(*args):
|
||||
logging.debug('Saving updated timestamps')
|
||||
update_timestamps()
|
||||
|
||||
logging.info('Bye')
|
||||
sys.exit()
|
||||
|
||||
|
||||
def main():
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio.ensure_future(start_web())
|
||||
loop.run_forever()
|
||||
signal.signal(signal.SIGHUP, safe_stop)
|
||||
signal.signal(signal.SIGINT, safe_stop)
|
||||
signal.signal(signal.SIGQUIT, safe_stop)
|
||||
signal.signal(signal.SIGTERM, safe_stop)
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio.ensure_future(start_web())
|
||||
asyncio.ensure_future(save_tokens())
|
||||
loop.run_forever()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
safe_stop()
|
||||
|
|
35
server.py
35
server.py
|
@ -1,12 +1,33 @@
|
|||
#!/usr/bin/env python3
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from os import environ as ENV
|
||||
|
||||
import routes
|
||||
|
||||
from config import logging
|
||||
from webapp import precompile
|
||||
|
||||
logging.debug('OvO')
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
import routes
|
||||
routes.main()
|
||||
class MyHandler(FileSystemEventHandler):
|
||||
def on_modified(self, event):
|
||||
if event.event_type == 'modified' and event.src_path[-3:] == '.py' and event.src_path.startswith('webapp/js/__target__') == False:
|
||||
logging.info(event.src_path)
|
||||
precompile()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.info('Bye')
|
||||
|
||||
if __name__ == "__main__":
|
||||
if ENV.get('PYENV', 'default').lower() in ['dev', 'default']:
|
||||
path = 'webapp/js'
|
||||
observer = Observer()
|
||||
observer.schedule(MyHandler(), path, recursive=True)
|
||||
|
||||
logging.info('Starting javascript watcher')
|
||||
observer.start()
|
||||
|
||||
routes.main()
|
||||
|
||||
if ENV.get('PYENV', 'default').lower() in ['dev', 'default']:
|
||||
logging.info('Stopping javascript watcher')
|
||||
observer.stop()
|
||||
observer.join()
|
||||
|
|
38
server.spec
Normal file
38
server.spec
Normal file
|
@ -0,0 +1,38 @@
|
|||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
|
||||
a = Analysis(['server.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[
|
||||
('frontend/templates/static', 'static'),
|
||||
('frontend/templates/templates', 'templates'),
|
||||
('frontend/templates/themes', 'themes'),
|
||||
('dist', 'dist')
|
||||
],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='social',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=True,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True )
|
7
setup.py
7
setup.py
|
@ -91,3 +91,10 @@ if __name__ == '__main__':
|
|||
|
||||
except KeyboardInterrupt:
|
||||
print('Bye!')
|
||||
|
||||
|
||||
'''
|
||||
I'll need these later
|
||||
user_check = pre_db.query(f"SELECT 1 FROM pg_roles WHERE rolname='{DB_CONFIG['host']}'")
|
||||
perms_check = pre_db.query(f"SELECT rolcreatedb FROM pg_authid WHERE rolname = '{DB_CONFIG['host']}'")
|
||||
'''
|
||||
|
|
|
@ -72,14 +72,18 @@ class TTLCache:
|
|||
|
||||
|
||||
def store(self, key, value):
|
||||
while len(self.items) >= self.maxsize:
|
||||
while len(self.items) >= self.maxsize and self.maxsize != 0:
|
||||
timestamp = int(datetime.timestamp(datetime.now()))
|
||||
self.items.popitem(last=False)
|
||||
|
||||
if (key in self.items) == False:
|
||||
timestamp = int(datetime.timestamp(datetime.now()))
|
||||
data = {'data': value, 'timestamp': timestamp + self.ttl}
|
||||
data = {'data': value, 'timestamp': timestamp}
|
||||
self.items[key] = data
|
||||
|
||||
if self.items[key]['timestamp'] + self.ttl < timestamp:
|
||||
del self.items[key]
|
||||
|
||||
self.items[key]['timestamp'] = timestamp
|
||||
self.items.move_to_end(key)
|
||||
|
||||
|
||||
|
@ -88,3 +92,35 @@ class TTLCache:
|
|||
return self.items[key]['data']
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class LRUCache:
|
||||
def __init__(self, maxsize=1024):
|
||||
self.items = OrderedDict()
|
||||
self.maxsize = maxsize
|
||||
|
||||
|
||||
def invalidate(self, key):
|
||||
if key in self.items:
|
||||
del self.items[key]
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def store(self, key, value):
|
||||
while len(self.items) >= self.maxsize and self.maxsize != 0:
|
||||
self.items.popitem(last=False)
|
||||
|
||||
if (key in self.items) == False:
|
||||
self.items[key] = value
|
||||
|
||||
self.items.move_to_end(key)
|
||||
|
||||
|
||||
def fetch(self, key):
|
||||
if key in self.items:
|
||||
return self.items[key]
|
||||
|
||||
return None
|
||||
|
|
6
webapp/__init__.py
Normal file
6
webapp/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
import os
|
||||
|
||||
from config import script_path
|
||||
|
||||
def precompile():
|
||||
os.system(f'transcrypt --map {script_path}/webapp/js/__init__.py')
|
1
webapp/js/__init__.py
Normal file
1
webapp/js/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
print('OwO')
|
Loading…
Reference in a new issue