mirror of
https://github.com/fcwu/docker-ubuntu-vnc-desktop
synced 2026-08-05 16:12:41 +02:00
refactor: code restructure
1. split frontend and backend 2. simplify supervisor configuration 3. align backend log format with supervisor 4. set nginx worker to 1 5. delete static web pages
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
class Default(object):
|
||||
DEBUG = True
|
||||
|
||||
|
||||
class Development(Default):
|
||||
PHASE = 'development'
|
||||
|
||||
|
||||
class Staging(Default):
|
||||
PHASE = 'staging'
|
||||
|
||||
|
||||
class Production(Default):
|
||||
PHASE = 'production'
|
||||
DEBUG = False
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
import logging
|
||||
import logging.handlers
|
||||
|
||||
|
||||
#The terminal has 8 colors with codes from 0 to 7
|
||||
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
|
||||
|
||||
#These are the sequences need to get colored ouput
|
||||
RESET_SEQ = "\033[0m"
|
||||
COLOR_SEQ = "\033[1;%dm"
|
||||
BOLD_SEQ = "\033[1m"
|
||||
|
||||
#The background is set with 40 plus the number of the color,
|
||||
#and the foreground with 30
|
||||
COLORS = {
|
||||
'WARNING': COLOR_SEQ % (30 + YELLOW) + 'WARN ' + RESET_SEQ,
|
||||
'INFO': COLOR_SEQ % (30 + WHITE) + 'INFO ' + RESET_SEQ,
|
||||
'DEBUG': COLOR_SEQ % (30 + BLUE) + 'DEBUG' + RESET_SEQ,
|
||||
'CRITICAL': COLOR_SEQ % (30 + YELLOW) + 'CRITI' + RESET_SEQ,
|
||||
'ERROR': COLOR_SEQ % (30 + RED) + 'ERROR' + RESET_SEQ,
|
||||
}
|
||||
|
||||
|
||||
class ColoredFormatter(logging.Formatter):
|
||||
def __init__(self, msg, use_color=True):
|
||||
logging.Formatter.__init__(self, msg)
|
||||
self.use_color = use_color
|
||||
|
||||
def format(self, record):
|
||||
if self.use_color:
|
||||
record.levelname = COLORS.get(record.levelname, record.levelname)
|
||||
return logging.Formatter.format(self, record)
|
||||
|
||||
|
||||
class LoggingConfiguration(object):
|
||||
COLOR_FORMAT = "%(asctime)s" + \
|
||||
" %(levelname)s %(message)s " + \
|
||||
"(" + BOLD_SEQ + "%(filename)s" + RESET_SEQ + ":%(lineno)d)"
|
||||
NO_COLOR_FORMAT = "%(asctime)s %(levelname)s " + \
|
||||
"%(message)s " + \
|
||||
"(%(filename)s:%(lineno)d)"
|
||||
FILE_FORMAT = "%(asctime)s %(levelname)s " + \
|
||||
"%(message)s "
|
||||
|
||||
@classmethod
|
||||
def set(cls, log_level, log_filename, append=None, **kwargs):
|
||||
""" Configure a rotating file logging
|
||||
"""
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(log_level)
|
||||
|
||||
COLOR_FORMAT = cls.COLOR_FORMAT
|
||||
NO_COLOR_FORMAT = cls.NO_COLOR_FORMAT
|
||||
FILE_FORMAT = cls.FILE_FORMAT
|
||||
if 'name' in kwargs:
|
||||
COLOR_FORMAT = COLOR_FORMAT.replace('%(threadName)-22s',
|
||||
'%-22s' % (kwargs['name']))
|
||||
NO_COLOR_FORMAT = NO_COLOR_FORMAT.replace(
|
||||
'%(threadName)-22s', '%-22s' % (kwargs['name']))
|
||||
FILE_FORMAT = FILE_FORMAT.replace(
|
||||
'%(threadName)-22s', '%s' % (kwargs['name']))
|
||||
|
||||
# Log to rotating file
|
||||
try:
|
||||
fh = logging.handlers.RotatingFileHandler(log_filename,
|
||||
mode='a+',
|
||||
backupCount=3)
|
||||
fh = logging.FileHandler(log_filename, mode='a+')
|
||||
fh.setFormatter(ColoredFormatter(FILE_FORMAT, False))
|
||||
fh.setLevel(log_level)
|
||||
logger.addHandler(fh)
|
||||
if not append:
|
||||
# Create a new log file on every new
|
||||
fh.doRollover()
|
||||
except:
|
||||
pass
|
||||
|
||||
# Log to sys.stderr using log level passed through command line
|
||||
if log_level != logging.NOTSET:
|
||||
log_handler = logging.StreamHandler(sys.stdout)
|
||||
if sys.platform.find('linux') >= 0:
|
||||
formatter = ColoredFormatter(COLOR_FORMAT)
|
||||
else:
|
||||
formatter = ColoredFormatter(NO_COLOR_FORMAT, False)
|
||||
log_handler.setFormatter(formatter)
|
||||
log_handler.setLevel(log_level)
|
||||
logger.addHandler(log_handler)
|
||||
@@ -0,0 +1,17 @@
|
||||
Flask==0.10.1
|
||||
Flask-Login==0.2.11
|
||||
Jinja2==2.7.3
|
||||
MarkupSafe==0.23
|
||||
Werkzeug==0.9.6
|
||||
argparse==1.2.1
|
||||
backports.ssl-match-hostname==3.4.0.2
|
||||
docker-py==0.5.3
|
||||
gevent==1.0.1
|
||||
gevent-websocket==0.9.3
|
||||
greenlet==0.4.5
|
||||
itsdangerous==0.24
|
||||
peewee==2.4.1
|
||||
requests==2.4.3
|
||||
six==1.8.0
|
||||
websocket-client==0.21.0
|
||||
wsgiref==0.1.2
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import time
|
||||
import sys
|
||||
import subprocess
|
||||
import signal
|
||||
|
||||
|
||||
def run_with_reloader(main_func, extra_files=None, interval=1):
|
||||
"""Run the given function in an independent python interpreter."""
|
||||
def find_files(directory="./"):
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for basename in files:
|
||||
if basename.endswith('.py'):
|
||||
filename = os.path.join(root, basename)
|
||||
yield filename
|
||||
|
||||
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
|
||||
try:
|
||||
os.setpgid(0, 0)
|
||||
main_func()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
return
|
||||
|
||||
procs = None
|
||||
try:
|
||||
while True:
|
||||
print('* Restarting with reloader ' + str(sys.executable))
|
||||
args = [sys.executable] + sys.argv
|
||||
new_environ = os.environ.copy()
|
||||
new_environ['WERKZEUG_RUN_MAIN'] = 'true'
|
||||
|
||||
procs = subprocess.Popen(args, env=new_environ)
|
||||
mtimes = {}
|
||||
restart = False
|
||||
while not restart:
|
||||
for filename in find_files():
|
||||
try:
|
||||
mtime = os.stat(filename).st_mtime
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
old_time = mtimes.get(filename)
|
||||
if old_time is None:
|
||||
mtimes[filename] = mtime
|
||||
continue
|
||||
elif mtime > old_time:
|
||||
print('* Detected change in %r, reloading' % filename)
|
||||
restart = True
|
||||
break
|
||||
time.sleep(interval)
|
||||
|
||||
killpg(procs.pid, signal.SIGTERM)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
killpg(procs.pid, signal.SIGTERM)
|
||||
|
||||
|
||||
def killpg(pgid, send_signal=signal.SIGKILL):
|
||||
print('kill PGID {}'.format(pgid))
|
||||
try:
|
||||
os.killpg(pgid, send_signal)
|
||||
#os.killpg(pgid, signal.SIGKILL)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
def run_server():
|
||||
import socket
|
||||
|
||||
os.environ['CONFIG'] = CONFIG
|
||||
from vnc import app
|
||||
|
||||
# websocket conflict: WebSocketHandler
|
||||
if DEBUG or STAGING:
|
||||
# from werkzeug.debug import DebuggedApplication
|
||||
app.debug = True
|
||||
# app = DebuggedApplication(app, evalex=True)
|
||||
|
||||
pgid = os.getpgid(0)
|
||||
signal.signal(signal.SIGTERM, lambda *args: killpg(pgid))
|
||||
signal.signal(signal.SIGHUP, lambda *args: killpg(pgid))
|
||||
signal.signal(signal.SIGINT, lambda *args: killpg(pgid))
|
||||
|
||||
try:
|
||||
app.run(host='', port=PORT)
|
||||
except socket.error as e:
|
||||
print(e)
|
||||
|
||||
DEBUG = True if '--debug' in sys.argv else False
|
||||
STAGING = True if '--staging' in sys.argv else False
|
||||
CONFIG = 'config.Development' if DEBUG else 'config.Production'
|
||||
CONFIG = 'config.Staging' if STAGING else CONFIG
|
||||
PORT = 6079
|
||||
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
||||
|
||||
if DEBUG or STAGING:
|
||||
main = lambda: run_with_reloader(run_server)
|
||||
else:
|
||||
main = run_server
|
||||
main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,138 @@
|
||||
from flask import (Flask,
|
||||
request,
|
||||
abort,
|
||||
)
|
||||
import os
|
||||
import json
|
||||
from functools import wraps
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
||||
# Flask app
|
||||
app = Flask('novnc2')
|
||||
CONFIG = os.environ.get('CONFIG') or 'config.Development'
|
||||
app.config.from_object('config.Default')
|
||||
app.config.from_object(CONFIG)
|
||||
FIRST = 'RESOLUTION' not in os.environ
|
||||
|
||||
|
||||
# logging
|
||||
import logging
|
||||
from log.config import LoggingConfiguration
|
||||
LoggingConfiguration.set(
|
||||
logging.DEBUG if os.getenv('DEBUG') else logging.INFO,
|
||||
'/var/log/web.log'
|
||||
)
|
||||
|
||||
|
||||
def exception_to_json(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
return result
|
||||
except (BadRequest,
|
||||
KeyError,
|
||||
ValueError,
|
||||
) as e:
|
||||
result = {'error': {'code': 400,
|
||||
'message': str(e)}}
|
||||
except PermissionDenied as e:
|
||||
result = {'error': {'code': 403,
|
||||
'message': ', '.join(e.args)}}
|
||||
except (NotImplementedError, RuntimeError, AttributeError) as e:
|
||||
result = {'error': {'code': 500,
|
||||
'message': ', '.join(e.args)}}
|
||||
return json.dumps(result)
|
||||
return wrapper
|
||||
|
||||
|
||||
class PermissionDenied(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BadRequest(Exception):
|
||||
pass
|
||||
|
||||
|
||||
HTML_INDEX = '''<html><head>
|
||||
<script type="text/javascript">
|
||||
var w = window,
|
||||
d = document,
|
||||
e = d.documentElement,
|
||||
g = d.getElementsByTagName('body')[0],
|
||||
x = w.innerWidth || e.clientWidth || g.clientWidth,
|
||||
y = w.innerHeight|| e.clientHeight|| g.clientHeight;
|
||||
var url = "redirect.html?width=" + x + "&height=" + (parseInt(y));
|
||||
window.location.href = url;
|
||||
</script>
|
||||
<title>Page Redirection</title>
|
||||
</head><body></body></html>'''
|
||||
|
||||
|
||||
HTML_REDIRECT = '''<html><head>
|
||||
<script type="text/javascript">
|
||||
var port = window.location.port;
|
||||
if (!port)
|
||||
port = window.location.protocol[4] == 's' ? 443 : 80;
|
||||
window.location.href = "vnc.html?autoconnect=1&autoscale=0&quality=3";
|
||||
</script>
|
||||
<title>Page Redirection</title>
|
||||
</head><body></body></html>'''
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return HTML_INDEX
|
||||
|
||||
|
||||
@app.route('/api/status')
|
||||
def status():
|
||||
global FIRST
|
||||
return json.dumps({
|
||||
'default_resolution': FIRST
|
||||
})
|
||||
|
||||
|
||||
@app.route('/redirect.html')
|
||||
def redirectme():
|
||||
global FIRST
|
||||
|
||||
if not FIRST:
|
||||
return HTML_REDIRECT
|
||||
|
||||
env = {'width': 1024, 'height': 768}
|
||||
if 'width' in request.args:
|
||||
env['width'] = request.args['width']
|
||||
if 'height' in request.args:
|
||||
env['height'] = request.args['height']
|
||||
|
||||
# sed
|
||||
cmd = (
|
||||
'sed -i \'s#'
|
||||
'^exec /usr/bin/Xvfb.*$'
|
||||
'#'
|
||||
'exec /usr/bin/Xvfb :1 -screen 0 {width}x{height}x16'
|
||||
'#\' /usr/local/bin/xvfb.sh'
|
||||
).format(**env),
|
||||
subprocess.check_call(cmd, shell=True)
|
||||
# supervisorctrl reload
|
||||
subprocess.check_call(['supervisorctl', 'restart', 'x:'])
|
||||
|
||||
# check all running
|
||||
for i in range(40):
|
||||
output = subprocess.check_output(['supervisorctl', 'status'])
|
||||
for line in output.strip().split('\n'):
|
||||
if line.find('RUNNING') < 0:
|
||||
break
|
||||
else:
|
||||
FIRST = False
|
||||
return HTML_REDIRECT
|
||||
time.sleep(1)
|
||||
logging.info('wait services is ready...')
|
||||
abort(500, 'service is not ready, please restart container')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host=app.config['ADDRESS'], port=app.config['PORT'])
|
||||
Reference in New Issue
Block a user