diff --git a/.gitignore b/.gitignore index 98c70bf..64297fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ tailbone_theo.egg-info/ +dev/settings.ini machines/theo-server/.vagrant/ machines/theo-server/fabenv.py machines/theo-server/fabric.yaml diff --git a/dev/bootstrap.py b/dev/bootstrap.py new file mode 100644 index 0000000..4056851 --- /dev/null +++ b/dev/bootstrap.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8; -*- +""" +Bootstrap development for Theo +""" + +import os +import subprocess +import sys + + +here = os.path.abspath(os.path.dirname(__file__)) + + +def bootstrap(): + + if not inside_virtualenv(): + return + + # install invoke, sphinx + subprocess.run(['pip', 'install', 'invoke', 'Sphinx'], + check=True) + + # run bootstrap task + os.chdir(here) + try: + completed = subprocess.run(['invoke', '--echo', 'bootstrap']) + except KeyboardInterrupt: + sys.exit(130) # 128 + SIGINT + else: + sys.exit(completed.returncode) + + +def inside_virtualenv(): + if not (hasattr(sys, 'real_prefix') or + (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)): + print("\nNot running inside a virtual environment!\n\n" + "Please create and activate that first, e.g. like:\n\n" + " python -m venv /srv/envs/theo\n" + " source /srv/envs/theo/bin/activate\n") + return False + return True + + +if __name__ == '__main__': + bootstrap() diff --git a/dev/rattail.conf b/dev/rattail.conf new file mode 100644 index 0000000..5cb4dec --- /dev/null +++ b/dev/rattail.conf @@ -0,0 +1,131 @@ + +############################################################ +# +# Development config for Theo +# +############################################################ + + +############################## +# rattail +############################## + +[rattail] +timezone.default = America/Chicago +datadir = /app/data +batch.files = /app/data/batch +workdir = /app/work + +[rattail.config] +# include = /etc/rattail/rattail.conf +configure_logging = true +usedb = true +preferdb = true + +[rattail.db] +default.url = postgresql://:@/ +versioning.enabled = true + +[rattail.mail] +send_emails = false +smtp.server = localhost +templates = rattail:templates/mail +default.prefix = [Rattail] +default.from = rattail@localhost +default.to = root@localhost +# default.enabled = false + +[rattail.pod] +pictures.gtin.root_url = https://rattailproject.org/pod/pictures/gtin + +[rattail.upgrades] +files = /app/data/upgrades + +############################## +# alembic +############################## + +[alembic] +script_location = rattail.db:alembic +version_locations = rattail.db:alembic/versions + + +############################## +# logging +############################## + +[loggers] +keys = root, exc_logger, beaker, txn, sqlalchemy, django_db, flufl_bounce, requests + +[handlers] +keys = file, console, email + +[formatters] +keys = generic, console + +[logger_root] +handlers = file, console +level = DEBUG + +[logger_exc_logger] +qualname = exc_logger +handlers = email +level = ERROR + +[logger_beaker] +qualname = beaker +handlers = +level = INFO + +[logger_txn] +qualname = txn +handlers = +level = INFO + +[logger_sqlalchemy] +qualname = sqlalchemy.engine +handlers = +# handlers = file +# level = INFO + +[logger_django_db] +qualname = django.db.backends +handlers = +level = INFO +# level = DEBUG + +[logger_flufl_bounce] +qualname = flufl.bounce +handlers = +level = WARNING + +[logger_requests] +qualname = requests +handlers = +# level = WARNING + +[handler_file] +class = handlers.RotatingFileHandler +args = ('/app/log/rattail.log', 'a', 1000000, 100, 'utf_8') +formatter = generic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +formatter = console +# formatter = generic +# level = INFO +# level = WARNING + +[handler_email] +class = handlers.SMTPHandler +args = ('localhost', 'rattail@localhost', ['root@localhost'], "[Rattail] Logging") +formatter = generic +level = ERROR + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(funcName)s: %(message)s +datefmt = %Y-%m-%d %H:%M:%S + +[formatter_console] +format = %(levelname)-5.5s [%(name)s][%(threadName)s] %(funcName)s: %(message)s diff --git a/dev/tasks.py b/dev/tasks.py new file mode 100644 index 0000000..f00ce20 --- /dev/null +++ b/dev/tasks.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8; -*- +""" +Development Tasks for Theo +""" + +import os +import configparser +import shutil +import sys + +from invoke import task +from sphinx.util.console import bold +from sphinx.cmd.quickstart import do_prompt + +from bootstrap import inside_virtualenv + + +here = os.path.abspath(os.path.dirname(__file__)) + + +@task +def bootstrap(c): + """ + Bootstrap a development environment. + """ + if not inside_virtualenv(): + sys.exit(1) + + envdir = sys.prefix + upgrade_pip(c) + install_app_package(c) + appdir = make_appdir(c, envdir) + + info = collect_info() + make_configs(c, envdir, appdir, info) + check_db(c, envdir, appdir) + install_db_schema(c, envdir, appdir) + make_admin_user(c, envdir, appdir, info) + + print() + print(bold("============================================================")) + print() + print(bold(" Okay, you should be ready to go!")) + print() + print(bold("============================================================")) + print() + print("start your development web app with this command:") + print() + print(" cd {}".format(envdir)) + print(" bin/pserve --reload file+ini:app/web.conf") + print() + print("then check out your development web app at:") + print() + print(" http://localhost:9080") + print() + + +def collect_info(): + """ + Collect misc. dev info from user + """ + info = {} + print() + print(bold("Welcome to Theo, the order system.")) + + config = configparser.ConfigParser() + if config.read(os.path.join(here, 'settings.ini')): + if config.has_section('devbootstrap'): + info = dict(config.items('devbootstrap')) + + try: + print() + print("Please enter the details for your Theo database.") + print() + info['dbhost'] = do_prompt('DB host', + default=info.get('dbhost', 'localhost')) + info['dbname'] = do_prompt('DB name', + default=info.get('dbname', 'theo')) + info['dbuser'] = do_prompt('DB user', + default=info.get('dbuser', 'rattail')) + info['dbpass'] = do_prompt('DB password', + default=info.get('dbpass')) + + print() + print("Please enter the details for your Theo admin user.") + print() + info['theouser'] = do_prompt('Theo username', + default=info.get('theouser', 'admin')) + info['theopass'] = do_prompt('Theo password', + default=info.get('theopass')) + + except (KeyboardInterrupt, EOFError): + print("\n[Interrupted.]") + sys.exit(130) # 128 + SIGINT + + return info + + +def upgrade_pip(c): + """ + Upgrade pip and friends + """ + c.run('pip install -U pip') + c.run('pip install -U setuptools wheel') + + +def install_app_package(c): + """ + Install the Theo app package + """ + project = os.path.abspath(os.path.join(here, os.pardir)) + c.run('pip install -e {}'.format(project)) + c.run('pip install tailbone-theo[app]') + + +def make_appdir(c, envdir): + """ + Create the 'app' dir for virtual env + """ + appdir = os.path.join(envdir, 'app') + if not os.path.exists(appdir): + c.run('{} make-appdir'.format(os.path.join(envdir, 'bin', 'rattail'))) + return appdir + + +def make_configs(c, envdir, appdir, info): + """ + Create app config files + """ + # rattail.conf + if not os.path.exists(os.path.join(appdir, 'rattail.conf')): + with open('rattail.conf') as f: + contents = f.read() + contents = contents.replace('', envdir) + contents = contents.replace('', info['dbhost']) + contents = contents.replace('', info['dbname']) + contents = contents.replace('', info['dbuser']) + contents = contents.replace('', info['dbpass']) + with open(os.path.join(appdir, 'rattail.conf'), 'w') as f: + f.write(contents) + + # quiet.conf + if not os.path.exists(os.path.join(appdir, 'quiet.conf')): + c.run('{}/bin/rattail make-config -T quiet -O {}'.format(envdir, appdir)) + + # web.conf + if not os.path.exists(os.path.join(appdir, 'web.conf')): + with open('web.conf') as f: + contents = f.read() + contents = contents.replace('', envdir) + with open(os.path.join(appdir, 'web.conf'), 'w') as f: + f.write(contents) + + +def check_db(c, envdir, appdir): + """ + Do basic sanity checks for Theo database + """ + c.run('{}/bin/rattail -c {}/quiet.conf --no-versioning checkdb'.format(envdir, appdir)) + + +def install_db_schema(c, envdir, appdir): + """ + Install the schema for Theo database + """ + c.run('{}/bin/alembic -c {}/rattail.conf upgrade heads'.format(envdir, appdir)) + + +def make_admin_user(c, envdir, appdir, info): + """ + Make an admin user in the Theo database + """ + c.run('{}/bin/rattail -c {}/quiet.conf make-user --admin {} --password {}'.format( + envdir, appdir, info['theouser'], info['theopass'])) diff --git a/dev/web.conf b/dev/web.conf new file mode 100644 index 0000000..a00b7e7 --- /dev/null +++ b/dev/web.conf @@ -0,0 +1,56 @@ + +############################################################ +# +# Development web app config for Theo +# +############################################################ + + +############################## +# rattail +############################## + +[rattail.config] +include = %(here)s/rattail.conf + + +############################## +# pyramid +############################## + +[app:main] +use = egg:Tailbone_Theo + +pyramid.reload_templates = true +pyramid.debug_all = true +pyramid.default_locale_name = en +# you can enable this if you find it helpful +#pyramid.includes = pyramid_debugtoolbar + +beaker.session.type = file +beaker.session.data_dir = %(here)s/sessions/data +beaker.session.lock_dir = %(here)s/sessions/lock +# this secret should only be used in development! +beaker.session.secret = b15zXlYNNIfzRjDjjyOL +beaker.session.key = rattail + +exclog.extra_info = true + +rattail.config = %(__file__)s + +[server:main] +use = egg:waitress#main +# to bind to all interfaces, set host to 0.0.0.0 +host = 127.0.0.1 +port = 9080 + + +############################## +# logging +############################## + +[handler_console] +level = INFO + +[handler_file] +args = ('/app/log/web.log', 'a', 1000000, 100, 'utf_8')