cli and setuptools wip
This commit is contained in:
parent
142050d804
commit
07852bdd98
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1,12 +1,14 @@
|
||||||
.vagrant
|
.vagrant
|
||||||
.venv
|
.venv
|
||||||
|
.venv-pypy
|
||||||
.idea
|
.idea
|
||||||
.webassets-cache
|
.webassets-cache
|
||||||
*.pyc
|
*.pyc
|
||||||
*.egg-info
|
*.egg-info
|
||||||
*.pid
|
*.pid
|
||||||
dist
|
pidfile
|
||||||
build
|
/dist
|
||||||
|
/build
|
||||||
config.py
|
config.py
|
||||||
config.sls
|
config.sls
|
||||||
config.json
|
config.json
|
||||||
|
|
|
@ -5,6 +5,6 @@ python:
|
||||||
before_install:
|
before_install:
|
||||||
- sudo apt-get install -y libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libyaml-dev libssl-dev
|
- sudo apt-get install -y libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libyaml-dev libssl-dev
|
||||||
|
|
||||||
install: "pip install -r dev-requirements.txt"
|
install: "pip install -r requirements-dev.txt"
|
||||||
|
|
||||||
script: nosetests
|
script: nosetests
|
||||||
|
|
6
MANIFEST.in
Normal file
6
MANIFEST.in
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
include requirements.txt VERSION LICENSE
|
||||||
|
recursive-include realms/static/css *
|
||||||
|
recursive-include realms/static/fonts *
|
||||||
|
recursive-include realms/static/js *
|
||||||
|
recursive-include realms/static/vendor *
|
||||||
|
recursive-include realms/templates *
|
2
Vagrantfile
vendored
2
Vagrantfile
vendored
|
@ -6,7 +6,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
config.vm.provider :virtualbox do |vb|
|
config.vm.provider :virtualbox do |vb|
|
||||||
vb.name = "realms-wiki"
|
vb.name = "realms-wiki"
|
||||||
vb.memory = 2048
|
vb.memory = 2048
|
||||||
vb.cpus = 2
|
vb.cpus = 4
|
||||||
end
|
end
|
||||||
|
|
||||||
config.vm.provision "shell", path: "install.sh"
|
config.vm.provision "shell", path: "install.sh"
|
||||||
|
|
34
install.sh
34
install.sh
|
@ -50,23 +50,51 @@ sudo -iu ${APP_USER} bower --allow-root --config.cwd=${APP_DIR} --config.directo
|
||||||
|
|
||||||
sudo -iu ${APP_USER} virtualenv ${APP_DIR}/.venv
|
sudo -iu ${APP_USER} virtualenv ${APP_DIR}/.venv
|
||||||
|
|
||||||
sudo -iu ${APP_USER} ${APP_DIR}/.venv/bin/pip install ${APP_DIR}
|
cd ${APP_DIR} && sudo -iu ${APP_USER} ${APP_DIR}/.venv/bin/pip install -r ${APP_DIR}/requirements-dev.txt
|
||||||
|
|
||||||
echo "Installing start scripts"
|
echo "Installing start scripts"
|
||||||
cat << EOF > /usr/local/bin/realms-wiki
|
cat << EOF > /usr/local/bin/realms-wiki
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
${APP_DIR}/.venv/bin/python ${APP_DIR}/manage.py "\$@"
|
${APP_DIR}/realms-wiki "\$@"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
sudo chmod +x /usr/local/bin/realms-wiki
|
sudo chmod +x /usr/local/bin/realms-wiki
|
||||||
|
|
||||||
cat << EOF > /etc/init/realms-wiki.conf
|
cat << EOF > /etc/init/realms-wiki.conf
|
||||||
|
limit nofile 65335 65335
|
||||||
|
|
||||||
|
respawn
|
||||||
|
|
||||||
description "Realms Wiki"
|
description "Realms Wiki"
|
||||||
author "scragg@gmail.com"
|
author "scragg@gmail.com"
|
||||||
|
|
||||||
|
chdir ${APP_DIR}
|
||||||
|
|
||||||
|
env PATH=${APP_DIR}/.venv/bin:/usr/local/bin:/usr/bin:/bin:$PATH
|
||||||
|
env LC_ALL=en_US.UTF-8
|
||||||
|
env GEVENT_RESOLVER=ares
|
||||||
|
|
||||||
|
export PATH
|
||||||
|
export LC_ALL
|
||||||
|
export GEVENT_RESOLVER
|
||||||
|
|
||||||
setuid ${APP_USER}
|
setuid ${APP_USER}
|
||||||
setgid ${APP_USER}
|
setgid ${APP_USER}
|
||||||
|
|
||||||
start on runlevel [2345]
|
start on runlevel [2345]
|
||||||
stop on runlevel [!2345]
|
stop on runlevel [!2345]
|
||||||
|
|
||||||
respawn
|
respawn
|
||||||
exec /usr/local/bin/realms-wiki run
|
|
||||||
|
exec gunicorn \
|
||||||
|
--name realms-wiki \
|
||||||
|
--access-logfile - \
|
||||||
|
--error-logfile - \
|
||||||
|
--worker-class gevent \
|
||||||
|
--workers 2 \
|
||||||
|
--bind 0.0.0.0:5000 \
|
||||||
|
--user ${APP_USER} \
|
||||||
|
--group ${APP_USER} \
|
||||||
|
--chdir ${APP_DIR} \
|
||||||
|
wsgi:app
|
||||||
EOF
|
EOF
|
||||||
|
|
195
manage.py
195
manage.py
|
@ -1,195 +0,0 @@
|
||||||
from gevent import wsgi
|
|
||||||
from realms import config, app, cli, db
|
|
||||||
from realms.lib.util import random_string
|
|
||||||
from subprocess import call
|
|
||||||
import click
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
|
||||||
@click.option('--site-title',
|
|
||||||
default=config.SITE_TITLE,
|
|
||||||
prompt='Enter site title.')
|
|
||||||
@click.option('--base_url',
|
|
||||||
default=config.BASE_URL,
|
|
||||||
prompt='Enter base URL.')
|
|
||||||
@click.option('--port',
|
|
||||||
default=config.PORT,
|
|
||||||
prompt='Enter port number.')
|
|
||||||
@click.option('--secret-key',
|
|
||||||
default=config.SECRET_KEY if config.SECRET_KEY != "CHANGE_ME" else random_string(64),
|
|
||||||
prompt='Enter secret key.')
|
|
||||||
@click.option('--wiki-path',
|
|
||||||
default=config.WIKI_PATH,
|
|
||||||
prompt='Where do you want to store wiki data?',
|
|
||||||
help='Wiki Directory (git repo)')
|
|
||||||
@click.option('--allow-anon',
|
|
||||||
default=config.ALLOW_ANON,
|
|
||||||
is_flag=True,
|
|
||||||
prompt='Allow anonymous edits?')
|
|
||||||
@click.option('--registration-enabled',
|
|
||||||
default=config.REGISTRATION_ENABLED,
|
|
||||||
is_flag=True,
|
|
||||||
prompt='Enable registration?')
|
|
||||||
@click.option('--cache-type',
|
|
||||||
default=config.CACHE_TYPE,
|
|
||||||
type=click.Choice([None, 'simple', 'redis', 'memcached']),
|
|
||||||
prompt='Cache type?')
|
|
||||||
@click.option('--db-uri',
|
|
||||||
default=config.DB_URI,
|
|
||||||
prompt='Database URI, Examples: http://goo.gl/RyW0cl')
|
|
||||||
@click.pass_context
|
|
||||||
def setup(ctx, **kw):
|
|
||||||
""" Start setup wizard
|
|
||||||
"""
|
|
||||||
conf = {}
|
|
||||||
|
|
||||||
for k, v in kw.items():
|
|
||||||
conf[k.upper()] = v
|
|
||||||
|
|
||||||
config.update(conf)
|
|
||||||
|
|
||||||
if conf['CACHE_TYPE'] == 'redis':
|
|
||||||
ctx.invoke(setup_redis)
|
|
||||||
elif conf['CACHE_TYPE'] == 'memcached':
|
|
||||||
ctx.invoke(setup_memcached)
|
|
||||||
|
|
||||||
click.secho('Config saved to %s/config.json' % config.APP_PATH, fg='green')
|
|
||||||
click.secho('Type "realms-wiki run" to start server', fg='yellow')
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option('--cache-redis-host',
|
|
||||||
default=getattr(config, 'CACHE_REDIS_HOST', "127.0.0.1"),
|
|
||||||
prompt='Redis host')
|
|
||||||
@click.option('--cache-redis-port',
|
|
||||||
default=getattr(config, 'CACHE_REDIS_POST', 6379),
|
|
||||||
prompt='Redis port')
|
|
||||||
@click.option('--cache-redis-password',
|
|
||||||
default=getattr(config, 'CACHE_REDIS_PASSWORD', None),
|
|
||||||
prompt='Redis password')
|
|
||||||
@click.option('--cache-redis-db',
|
|
||||||
default=getattr(config, 'CACHE_REDIS_DB', 0),
|
|
||||||
prompt='Redis db')
|
|
||||||
def setup_redis(**kw):
|
|
||||||
conf = {}
|
|
||||||
|
|
||||||
for k, v in kw.items():
|
|
||||||
conf[k.upper()] = v
|
|
||||||
|
|
||||||
config.update(conf)
|
|
||||||
install_redis()
|
|
||||||
|
|
||||||
|
|
||||||
def get_prefix():
|
|
||||||
import sys
|
|
||||||
return sys.prefix
|
|
||||||
|
|
||||||
|
|
||||||
def get_pip():
|
|
||||||
""" Get virtualenv path for pip
|
|
||||||
"""
|
|
||||||
return get_prefix() + '/bin/pip'
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
|
||||||
@click.argument('cmd', nargs=-1)
|
|
||||||
def pip(cmd):
|
|
||||||
""" Execute pip commands for this virtualenv
|
|
||||||
"""
|
|
||||||
call(get_pip() + ' ' + ' '.join(cmd), shell=True)
|
|
||||||
|
|
||||||
|
|
||||||
def install_redis():
|
|
||||||
call([get_pip(), 'install', 'redis'])
|
|
||||||
|
|
||||||
|
|
||||||
def install_mysql():
|
|
||||||
call([get_pip(), 'install', 'MySQL-Python'])
|
|
||||||
|
|
||||||
|
|
||||||
def install_postgres():
|
|
||||||
call([get_pip(), 'install', 'psycopg2'])
|
|
||||||
|
|
||||||
|
|
||||||
def install_memcached():
|
|
||||||
call([get_pip(), 'install', 'python-memcached'])
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option('--cache-memcached-servers',
|
|
||||||
default=getattr(config, 'CACHE_MEMCACHED_SERVERS', ["127.0.0.1:11211"]),
|
|
||||||
type=click.STRING,
|
|
||||||
prompt='Memcached servers, separate with a space')
|
|
||||||
def setup_memcached(**kw):
|
|
||||||
conf = {}
|
|
||||||
|
|
||||||
for k, v in kw.items():
|
|
||||||
conf[k.upper()] = v
|
|
||||||
|
|
||||||
config.update(conf)
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
|
||||||
@click.argument('config_json')
|
|
||||||
def configure(config_json):
|
|
||||||
""" Set config.json, expects JSON encoded string
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
config.update(json.loads(config_json))
|
|
||||||
except ValueError, e:
|
|
||||||
click.secho('Config value should be valid JSON', fg='red')
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
|
||||||
@click.option('--port', default=5000)
|
|
||||||
def dev(port):
|
|
||||||
""" Run development server
|
|
||||||
"""
|
|
||||||
click.secho("Starting development server", fg='green')
|
|
||||||
app.run(host="0.0.0.0",
|
|
||||||
port=port,
|
|
||||||
debug=True)
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
|
||||||
def run():
|
|
||||||
""" Run production server
|
|
||||||
"""
|
|
||||||
click.secho("Server started. Env: %s Port: %s" % (config.ENV, config.PORT), fg='green')
|
|
||||||
wsgi.WSGIServer(('', int(config.PORT)), app).serve_forever()
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
|
||||||
def create_db():
|
|
||||||
""" Creates DB tables
|
|
||||||
"""
|
|
||||||
click.echo("Creating all tables")
|
|
||||||
db.create_all()
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
|
||||||
@click.confirmation_option(help='Are you sure you want to drop the db?')
|
|
||||||
def drop_db():
|
|
||||||
""" Drops DB tables
|
|
||||||
"""
|
|
||||||
click.echo("Dropping all tables")
|
|
||||||
db.drop_all()
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
|
||||||
def test():
|
|
||||||
""" Run tests
|
|
||||||
"""
|
|
||||||
call([get_prefix() + "/bin/nosetests", config.APP_PATH])
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
|
||||||
def version():
|
|
||||||
""" Output version
|
|
||||||
"""
|
|
||||||
with open('VERSION') as f:
|
|
||||||
return f.read().strip()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
cli()
|
|
6
realms-wiki
Executable file
6
realms-wiki
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from realms.cli import cli
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
cli()
|
|
@ -1,10 +1,4 @@
|
||||||
import sys
|
import sys
|
||||||
if 'threading' in sys.modules:
|
|
||||||
del sys.modules['threading']
|
|
||||||
|
|
||||||
# Monkey patch stdlib.
|
|
||||||
import gevent.monkey
|
|
||||||
gevent.monkey.patch_all(aggressive=False, subprocess=True)
|
|
||||||
|
|
||||||
# Set default encoding to UTF-8
|
# Set default encoding to UTF-8
|
||||||
reload(sys)
|
reload(sys)
|
||||||
|
@ -12,7 +6,6 @@ reload(sys)
|
||||||
sys.setdefaultencoding('utf-8')
|
sys.setdefaultencoding('utf-8')
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import sys
|
|
||||||
import json
|
import json
|
||||||
import httplib
|
import httplib
|
||||||
import traceback
|
import traceback
|
||||||
|
@ -74,7 +67,7 @@ class Application(Flask):
|
||||||
if hasattr(sources, 'commands'):
|
if hasattr(sources, 'commands'):
|
||||||
cli.add_command(sources.commands.cli, name=module_name)
|
cli.add_command(sources.commands.cli, name=module_name)
|
||||||
|
|
||||||
print >> sys.stderr, ' * Ready in %.2fms' % (1000.0 * (time.time() - start_time))
|
# print >> sys.stderr, ' * Ready in %.2fms' % (1000.0 * (time.time() - start_time))
|
||||||
|
|
||||||
def make_response(self, rv):
|
def make_response(self, rv):
|
||||||
if rv is None:
|
if rv is None:
|
||||||
|
|
350
realms/cli.py
Normal file
350
realms/cli.py
Normal file
|
@ -0,0 +1,350 @@
|
||||||
|
from realms import config, app, cli, db
|
||||||
|
from realms.lib.util import random_string
|
||||||
|
from subprocess import call, Popen
|
||||||
|
from multiprocessing import cpu_count
|
||||||
|
import click
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def get_user():
|
||||||
|
for name in ('SUDO_USER', 'LOGNAME', 'USER', 'LNAME', 'USERNAME'):
|
||||||
|
user = os.environ.get(name)
|
||||||
|
if user:
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def in_virtualenv():
|
||||||
|
return hasattr(sys, 'real_prefix')
|
||||||
|
|
||||||
|
|
||||||
|
def is_su():
|
||||||
|
return os.geteuid() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_pid():
|
||||||
|
try:
|
||||||
|
with file(config.PIDFILE) as f:
|
||||||
|
pid = f.read().strip()
|
||||||
|
return pid if pid and int(pid) > 0 and not call(['kill', '-s', '0', pid]) else False
|
||||||
|
except IOError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def module_exists(module_name):
|
||||||
|
try:
|
||||||
|
__import__(module_name)
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.option('--site-title',
|
||||||
|
default=config.SITE_TITLE,
|
||||||
|
prompt='Enter site title.')
|
||||||
|
@click.option('--base_url',
|
||||||
|
default=config.BASE_URL,
|
||||||
|
prompt='Enter base URL.')
|
||||||
|
@click.option('--port',
|
||||||
|
default=config.PORT,
|
||||||
|
prompt='Enter port number.')
|
||||||
|
@click.option('--secret-key',
|
||||||
|
default=config.SECRET_KEY if config.SECRET_KEY != "CHANGE_ME" else random_string(64),
|
||||||
|
prompt='Enter secret key.')
|
||||||
|
@click.option('--wiki-path',
|
||||||
|
default=config.WIKI_PATH,
|
||||||
|
prompt='Where do you want to store wiki data?',
|
||||||
|
help='Wiki Directory (git repo)')
|
||||||
|
@click.option('--allow-anon',
|
||||||
|
default=config.ALLOW_ANON,
|
||||||
|
is_flag=True,
|
||||||
|
prompt='Allow anonymous edits?')
|
||||||
|
@click.option('--registration-enabled',
|
||||||
|
default=config.REGISTRATION_ENABLED,
|
||||||
|
is_flag=True,
|
||||||
|
prompt='Enable registration?')
|
||||||
|
@click.option('--cache-type',
|
||||||
|
default=config.CACHE_TYPE,
|
||||||
|
type=click.Choice([None, 'simple', 'redis', 'memcached']),
|
||||||
|
prompt='Cache type?')
|
||||||
|
@click.option('--db-uri',
|
||||||
|
default=config.DB_URI,
|
||||||
|
prompt='Database URI? Examples: http://goo.gl/RyW0cl')
|
||||||
|
@click.pass_context
|
||||||
|
def setup(ctx, **kw):
|
||||||
|
""" Start setup wizard
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not in_virtualenv() and not is_su():
|
||||||
|
# This does not account for people the have user level python installs
|
||||||
|
# that aren't virtual environments! Should be rare I think
|
||||||
|
click.secho("Setup requires root privileges, use sudo or run as root")
|
||||||
|
return
|
||||||
|
|
||||||
|
conf = {}
|
||||||
|
|
||||||
|
for k, v in kw.items():
|
||||||
|
conf[k.upper()] = v
|
||||||
|
|
||||||
|
conf_path = config.update(conf)
|
||||||
|
|
||||||
|
if conf['CACHE_TYPE'] == 'redis':
|
||||||
|
ctx.invoke(setup_redis)
|
||||||
|
elif conf['CACHE_TYPE'] == 'memcached':
|
||||||
|
ctx.invoke(setup_memcached)
|
||||||
|
|
||||||
|
click.secho('Config saved to %s' % conf_path, fg='green')
|
||||||
|
click.secho('Type "realms-wiki start" to start server', fg='yellow')
|
||||||
|
click.secho('Type "realms-wiki dev" to start server in development mode', fg='yellow')
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option('--cache-redis-host',
|
||||||
|
default=getattr(config, 'CACHE_REDIS_HOST', "127.0.0.1"),
|
||||||
|
prompt='Redis host')
|
||||||
|
@click.option('--cache-redis-port',
|
||||||
|
default=getattr(config, 'CACHE_REDIS_POST', 6379),
|
||||||
|
prompt='Redis port')
|
||||||
|
@click.option('--cache-redis-password',
|
||||||
|
default=getattr(config, 'CACHE_REDIS_PASSWORD', None),
|
||||||
|
prompt='Redis password')
|
||||||
|
@click.option('--cache-redis-db',
|
||||||
|
default=getattr(config, 'CACHE_REDIS_DB', 0),
|
||||||
|
prompt='Redis db')
|
||||||
|
def setup_redis(**kw):
|
||||||
|
conf = {}
|
||||||
|
|
||||||
|
for k, v in kw.items():
|
||||||
|
conf[k.upper()] = v
|
||||||
|
|
||||||
|
config.update(conf)
|
||||||
|
install_redis()
|
||||||
|
|
||||||
|
|
||||||
|
def get_prefix():
|
||||||
|
return sys.prefix
|
||||||
|
|
||||||
|
|
||||||
|
def get_pip():
|
||||||
|
""" Get virtualenv path for pip
|
||||||
|
"""
|
||||||
|
if not in_virtualenv() and not is_su():
|
||||||
|
click.secho("This command requires root, use sudo or run as root")
|
||||||
|
return
|
||||||
|
|
||||||
|
if in_virtualenv():
|
||||||
|
return get_prefix() + '/bin/pip'
|
||||||
|
else:
|
||||||
|
return 'pip'
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.argument('cmd', nargs=-1)
|
||||||
|
def pip(cmd):
|
||||||
|
""" Execute pip commands, useful for virtualenvs
|
||||||
|
"""
|
||||||
|
call(get_pip() + ' ' + ' '.join(cmd), shell=True)
|
||||||
|
|
||||||
|
|
||||||
|
def install_redis():
|
||||||
|
call([get_pip(), 'install', 'redis'])
|
||||||
|
|
||||||
|
|
||||||
|
def install_mysql():
|
||||||
|
call([get_pip(), 'install', 'MySQL-Python'])
|
||||||
|
|
||||||
|
|
||||||
|
def install_postgres():
|
||||||
|
call([get_pip(), 'install', 'psycopg2'])
|
||||||
|
|
||||||
|
|
||||||
|
def install_memcached():
|
||||||
|
call([get_pip(), 'install', 'python-memcached'])
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option('--cache-memcached-servers',
|
||||||
|
default=getattr(config, 'CACHE_MEMCACHED_SERVERS', ["127.0.0.1:11211"]),
|
||||||
|
type=click.STRING,
|
||||||
|
prompt='Memcached servers, separate with a space')
|
||||||
|
def setup_memcached(**kw):
|
||||||
|
conf = {}
|
||||||
|
|
||||||
|
for k, v in kw.items():
|
||||||
|
conf[k.upper()] = v
|
||||||
|
|
||||||
|
config.update(conf)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.option('--user',
|
||||||
|
default=get_user(),
|
||||||
|
type=click.STRING,
|
||||||
|
prompt='Run as which user? (it must exist)')
|
||||||
|
@click.option('--port',
|
||||||
|
default=config.PORT,
|
||||||
|
type=click.INT,
|
||||||
|
prompt='What port to listen on?')
|
||||||
|
@click.option('--workers',
|
||||||
|
default=cpu_count() * 2 + 1,
|
||||||
|
type=click.INT,
|
||||||
|
prompt="Number of workers? (defaults to ncpu*2+1)")
|
||||||
|
def setup_upstart(**kwargs):
|
||||||
|
""" Start upstart conf creation wizard
|
||||||
|
"""
|
||||||
|
from realms.lib.util import upstart_script
|
||||||
|
import realms
|
||||||
|
|
||||||
|
print os.path.dirname(realms.__file__)
|
||||||
|
|
||||||
|
if not is_su():
|
||||||
|
click.secho("Please run this command as root or use sudo", fg='red')
|
||||||
|
return
|
||||||
|
|
||||||
|
if in_virtualenv():
|
||||||
|
app_dir = get_prefix()
|
||||||
|
path = '/'.join(sys.executable.split('/')[:-1])
|
||||||
|
else:
|
||||||
|
# Assumed root install, not sure if this matters?
|
||||||
|
app_dir = '/'
|
||||||
|
path = None
|
||||||
|
|
||||||
|
kwargs.update(dict(app_dir=app_dir, path=path))
|
||||||
|
|
||||||
|
conf_file = '/etc/init/realms-wiki.conf'
|
||||||
|
|
||||||
|
with open('/etc/init/realms-wiki.conf', 'w') as f:
|
||||||
|
f.write(upstart_script(**kwargs))
|
||||||
|
|
||||||
|
click.secho('Wrote file to %s' % conf_file, fg='yellow')
|
||||||
|
click.echo("Type 'sudo start realms-wiki' to start")
|
||||||
|
click.echo("Type 'sudo stop realms-wiki' to stop")
|
||||||
|
click.echo("Type 'sudo restart realms-wiki' to restart")
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.argument('json_string')
|
||||||
|
def configure(json_string):
|
||||||
|
""" Set config.json, expects JSON encoded string
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
config.update(json.loads(json_string))
|
||||||
|
except ValueError, e:
|
||||||
|
click.secho('Config value should be valid JSON', fg='red')
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.option('--port', default=5000)
|
||||||
|
def dev(port):
|
||||||
|
""" Run development server
|
||||||
|
"""
|
||||||
|
click.secho("Starting development server", fg='green')
|
||||||
|
app.run(host="0.0.0.0",
|
||||||
|
port=port,
|
||||||
|
debug=True)
|
||||||
|
|
||||||
|
|
||||||
|
def start_server():
|
||||||
|
if get_pid():
|
||||||
|
click.echo("Server is already running")
|
||||||
|
return
|
||||||
|
|
||||||
|
flags = '--daemon --pid %s' % config.PIDFILE
|
||||||
|
|
||||||
|
click.secho("Server started. Port: %s" % config.PORT, fg='green')
|
||||||
|
|
||||||
|
Popen('gunicorn realms:app -b 0.0.0.0:%s -k gevent %s' %
|
||||||
|
(config.PORT, flags), shell=True, executable='/bin/bash')
|
||||||
|
|
||||||
|
|
||||||
|
def stop_server():
|
||||||
|
pid = get_pid()
|
||||||
|
if not pid:
|
||||||
|
click.echo("Server is not running")
|
||||||
|
else:
|
||||||
|
click.echo("Shutting down server")
|
||||||
|
call(['kill', pid])
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
def run():
|
||||||
|
""" Run production server (alias for start)
|
||||||
|
"""
|
||||||
|
start_server()
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
def start():
|
||||||
|
""" Run server daemon
|
||||||
|
"""
|
||||||
|
start_server()
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
def stop():
|
||||||
|
""" Stop server
|
||||||
|
"""
|
||||||
|
stop_server()
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
def restart():
|
||||||
|
""" Restart server
|
||||||
|
"""
|
||||||
|
stop_server()
|
||||||
|
start_server()
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
def status():
|
||||||
|
""" Get server status
|
||||||
|
"""
|
||||||
|
pid = get_pid()
|
||||||
|
if not pid:
|
||||||
|
click.echo("Server is not running")
|
||||||
|
else:
|
||||||
|
click.echo("Server is running PID: %s" % pid)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
def create_db():
|
||||||
|
""" Creates DB tables
|
||||||
|
"""
|
||||||
|
click.echo("Creating all tables")
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.confirmation_option(help='Are you sure you want to drop the db?')
|
||||||
|
def drop_db():
|
||||||
|
""" Drops DB tables
|
||||||
|
"""
|
||||||
|
click.echo("Dropping all tables")
|
||||||
|
db.drop_all()
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
def test():
|
||||||
|
""" Run tests
|
||||||
|
"""
|
||||||
|
for mod in [('flask.ext.testing', 'Flask-Testing'), ('nose', 'nose')]:
|
||||||
|
if not module_exists(mod[0]):
|
||||||
|
call([get_pip(), 'install', mod[1]])
|
||||||
|
|
||||||
|
nosetests = get_prefix() + "/bin/nosetests" if in_virtualenv() else "nosetests"
|
||||||
|
|
||||||
|
call([nosetests, config.APP_PATH])
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
def version():
|
||||||
|
""" Output version
|
||||||
|
"""
|
||||||
|
with open('VERSION') as f:
|
||||||
|
return f.read().strip()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
cli()
|
|
@ -6,28 +6,48 @@ from urlparse import urlparse
|
||||||
def update(data):
|
def update(data):
|
||||||
conf = read()
|
conf = read()
|
||||||
conf.update(data)
|
conf.update(data)
|
||||||
save(data)
|
return save(data)
|
||||||
|
|
||||||
|
|
||||||
def read():
|
def read():
|
||||||
conf = dict()
|
conf = dict()
|
||||||
|
|
||||||
|
for k, v in os.environ.items():
|
||||||
|
if k.startswith('REALMS_'):
|
||||||
|
conf[k[7:]] = v
|
||||||
|
|
||||||
|
for loc in os.curdir, os.path.expanduser("~"), "/etc/realms-wiki", os.environ.get("REALMS_WIKI_CONF"):
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(APP_PATH, 'config.json')) as f:
|
if not loc:
|
||||||
conf = json.load(f)
|
continue
|
||||||
|
with open(os.path.join(loc, "realms-wiki.json")) as f:
|
||||||
|
conf.update(json.load(f))
|
||||||
|
break
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
for k in ['APP_PATH', 'USER_HOME']:
|
||||||
|
if k in conf:
|
||||||
|
del conf[k]
|
||||||
|
|
||||||
return conf
|
return conf
|
||||||
|
|
||||||
|
|
||||||
def save(conf):
|
def save(conf):
|
||||||
with open(os.path.join(APP_PATH, 'config.json'), 'w') as f:
|
for loc in "/etc/realms-wiki", os.path.expanduser("~"), os.curdir:
|
||||||
|
try:
|
||||||
|
with open(os.path.join(loc, 'realms-wiki.json'), 'w') as f:
|
||||||
f.write(json.dumps(conf, sort_keys=True, indent=4, separators=(',', ': ')).strip() + '\n')
|
f.write(json.dumps(conf, sort_keys=True, indent=4, separators=(',', ': ')).strip() + '\n')
|
||||||
|
return os.path.join(loc, 'realms-wiki.json')
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
|
||||||
APP_PATH = os.path.abspath(os.path.dirname(__file__) + "/../..")
|
APP_PATH = os.path.abspath(os.path.dirname(__file__) + "/../..")
|
||||||
USER_HOME = os.path.abspath(os.path.expanduser("~"))
|
USER_HOME = os.path.abspath(os.path.expanduser("~"))
|
||||||
|
|
||||||
|
# Best to change to /var/run
|
||||||
|
PIDFILE = "/tmp/realms-wiki.pid"
|
||||||
|
|
||||||
ENV = 'DEV'
|
ENV = 'DEV'
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
@ -39,7 +59,7 @@ BASE_URL = 'http://localhost'
|
||||||
SITE_TITLE = "Realms"
|
SITE_TITLE = "Realms"
|
||||||
|
|
||||||
# https://pythonhosted.org/Flask-SQLAlchemy/config.html#connection-uri-format
|
# https://pythonhosted.org/Flask-SQLAlchemy/config.html#connection-uri-format
|
||||||
DB_URI = 'sqlite:///%s/wiki.db' % USER_HOME
|
DB_URI = 'sqlite:////tmp/wiki.db'
|
||||||
# DB_URI = 'mysql://scott:tiger@localhost/mydatabase'
|
# DB_URI = 'mysql://scott:tiger@localhost/mydatabase'
|
||||||
# DB_URI = 'postgresql://scott:tiger@localhost/mydatabase'
|
# DB_URI = 'postgresql://scott:tiger@localhost/mydatabase'
|
||||||
# DB_URI = 'oracle://scott:tiger@127.0.0.1:1521/sidname'
|
# DB_URI = 'oracle://scott:tiger@127.0.0.1:1521/sidname'
|
||||||
|
@ -67,7 +87,7 @@ RECAPTCHA_OPTIONS = {}
|
||||||
SECRET_KEY = 'CHANGE_ME'
|
SECRET_KEY = 'CHANGE_ME'
|
||||||
|
|
||||||
# Path on file system where wiki data will reside
|
# Path on file system where wiki data will reside
|
||||||
WIKI_PATH = os.path.join(APP_PATH, 'wiki')
|
WIKI_PATH = '/tmp/wiki'
|
||||||
|
|
||||||
# Name of page that will act as home
|
# Name of page that will act as home
|
||||||
WIKI_HOME = 'home'
|
WIKI_HOME = 'home'
|
||||||
|
@ -78,7 +98,7 @@ REGISTRATION_ENABLED = True
|
||||||
# Used by Flask-Login
|
# Used by Flask-Login
|
||||||
LOGIN_DISABLED = ALLOW_ANON
|
LOGIN_DISABLED = ALLOW_ANON
|
||||||
|
|
||||||
# None, firepad, or togetherjs
|
# None, firepad, and/or togetherjs
|
||||||
COLLABORATION = 'togetherjs'
|
COLLABORATION = 'togetherjs'
|
||||||
|
|
||||||
# Required for firepad
|
# Required for firepad
|
||||||
|
@ -91,22 +111,7 @@ LOCKED = WIKI_LOCKED_PAGES
|
||||||
|
|
||||||
ROOT_ENDPOINT = 'wiki.page'
|
ROOT_ENDPOINT = 'wiki.page'
|
||||||
|
|
||||||
__env = {}
|
globals().update(read())
|
||||||
for k, v in os.environ.items():
|
|
||||||
if k.startswith('REALMS_'):
|
|
||||||
__env[k[7:]] = v
|
|
||||||
|
|
||||||
globals().update(__env)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(os.path.join(APP_PATH, 'config.json')) as f:
|
|
||||||
__settings = json.load(f)
|
|
||||||
for k in ['APP_PATH', 'USER_HOME']:
|
|
||||||
if k in __settings:
|
|
||||||
del __settings[k]
|
|
||||||
globals().update(__settings)
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if BASE_URL.endswith('/'):
|
if BASE_URL.endswith('/'):
|
||||||
BASE_URL = BASE_URL[-1]
|
BASE_URL = BASE_URL[-1]
|
||||||
|
|
|
@ -4,6 +4,7 @@ import hashlib
|
||||||
import json
|
import json
|
||||||
import string
|
import string
|
||||||
import random
|
import random
|
||||||
|
from jinja2 import Template
|
||||||
|
|
||||||
|
|
||||||
class AttrDict(dict):
|
class AttrDict(dict):
|
||||||
|
@ -97,3 +98,49 @@ def to_canonical(s):
|
||||||
|
|
||||||
def gravatar_url(email):
|
def gravatar_url(email):
|
||||||
return "//www.gravatar.com/avatar/" + hashlib.md5(email).hexdigest()
|
return "//www.gravatar.com/avatar/" + hashlib.md5(email).hexdigest()
|
||||||
|
|
||||||
|
def upstart_script(user='root', app_dir=None, port=5000, workers=2, path=None):
|
||||||
|
script = """
|
||||||
|
limit nofile 65335 65335
|
||||||
|
|
||||||
|
respawn
|
||||||
|
|
||||||
|
description "Realms Wiki"
|
||||||
|
author "scragg@gmail.com"
|
||||||
|
|
||||||
|
chdir {{ app_dir }}
|
||||||
|
|
||||||
|
{% if path %}
|
||||||
|
env PATH={{ path }}:/usr/local/bin:/usr/bin:/bin:$PATH
|
||||||
|
export PATH
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
env LC_ALL=en_US.UTF-8
|
||||||
|
env GEVENT_RESOLVER=ares
|
||||||
|
|
||||||
|
export LC_ALL
|
||||||
|
export GEVENT_RESOLVER
|
||||||
|
|
||||||
|
setuid {{ user }}
|
||||||
|
setgid {{ user }}
|
||||||
|
|
||||||
|
start on runlevel [2345]
|
||||||
|
stop on runlevel [!2345]
|
||||||
|
|
||||||
|
respawn
|
||||||
|
|
||||||
|
exec gunicorn \
|
||||||
|
--name realms-wiki \
|
||||||
|
--access-logfile - \
|
||||||
|
--error-logfile - \
|
||||||
|
--worker-class gevent \
|
||||||
|
--workers {{ workers }} \
|
||||||
|
--bind 0.0.0.0:{{ port }} \
|
||||||
|
--user {{ user }} \
|
||||||
|
--group {{ user }} \
|
||||||
|
--chdir {{ app_dir }} \
|
||||||
|
realms:app
|
||||||
|
|
||||||
|
"""
|
||||||
|
template = Template(script)
|
||||||
|
return template.render(user=user, app_dir=app_dir, port=port, workers=workers, path=path)
|
||||||
|
|
|
@ -15,9 +15,11 @@ class WikiTest(TestCase):
|
||||||
|
|
||||||
self.assert_200(self.client.get(url_for("wiki.create")))
|
self.assert_200(self.client.get(url_for("wiki.create")))
|
||||||
|
|
||||||
|
""" Create a test page first!
|
||||||
for route in ['page', 'edit', 'history', 'index']:
|
for route in ['page', 'edit', 'history', 'index']:
|
||||||
rv = self.client.get(url_for("wiki.%s" % route, name='test'))
|
rv = self.client.get(url_for("wiki.%s" % route, name='test'))
|
||||||
self.assert_200(rv, "wiki.%s: %s" % (route, rv.status_code))
|
self.assert_200(rv, "wiki.%s: %s" % (route, rv.status_code))
|
||||||
|
"""
|
||||||
|
|
||||||
def test_write_page(self):
|
def test_write_page(self):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -11,6 +11,7 @@ click==3.3
|
||||||
gevent==1.0.1
|
gevent==1.0.1
|
||||||
ghdiff==0.4
|
ghdiff==0.4
|
||||||
gittle==0.4.0
|
gittle==0.4.0
|
||||||
|
gunicorn==19.1.1
|
||||||
itsdangerous==0.24
|
itsdangerous==0.24
|
||||||
lxml==3.4.0
|
lxml==3.4.0
|
||||||
markdown2==2.3.0
|
markdown2==2.3.0
|
||||||
|
|
17
setup.py
17
setup.py
|
@ -1,8 +1,12 @@
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
import os
|
||||||
|
|
||||||
|
if os.environ.get('USER', '') == 'vagrant':
|
||||||
|
del os.link
|
||||||
|
|
||||||
DESCRIPTION = "Simple git based wiki"
|
DESCRIPTION = "Simple git based wiki"
|
||||||
|
|
||||||
with open('README.md') as f:
|
with open('README') as f:
|
||||||
LONG_DESCRIPTION = f.read()
|
LONG_DESCRIPTION = f.read()
|
||||||
|
|
||||||
with open('requirements.txt') as f:
|
with open('requirements.txt') as f:
|
||||||
|
@ -13,15 +17,20 @@ with open('VERSION') as f:
|
||||||
|
|
||||||
CLASSIFIERS = [
|
CLASSIFIERS = [
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: GPLv2 License',
|
'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: POSIX :: Linux',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules']
|
'Topic :: Internet :: WWW/HTTP :: Dynamic Content']
|
||||||
|
|
||||||
setup(name='realms-wiki',
|
setup(name='realms-wiki',
|
||||||
version=VERSION,
|
version=VERSION,
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
install_requires=required,
|
install_requires=required,
|
||||||
|
#scripts=['realms-wiki'],
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'realms-wiki = realms.cli:cli'
|
||||||
|
]},
|
||||||
author='Matthew Scragg',
|
author='Matthew Scragg',
|
||||||
author_email='scragg@gmail.com',
|
author_email='scragg@gmail.com',
|
||||||
maintainer='Matthew Scragg',
|
maintainer='Matthew Scragg',
|
||||||
|
|
Loading…
Reference in a new issue