diff --git a/fabfile.py b/fabfile.py new file mode 100644 index 0000000000000000000000000000000000000000..16c9912f28a9db5b138c18cc2580133d4f5ed0f8 --- /dev/null +++ b/fabfile.py @@ -0,0 +1,92 @@ +"""Automated deployment with Fabric. + +To deploy to production: + + python3 -m pip install --user vps-deploy + export FABRIC_HOSTS=user@hostname + fab deploy +""" + +import os + +from fabric import task # type: ignore +from invoke.collection import Collection # type: ignore +from patchwork.files import exists +from vps_deploy import django_fabric2 as df2 # type: ignore + +hosts = os.environ['FABRIC_HOSTS'].split(',') + + +def install_essentials(c): + # ImageMagick (convert) and Inkscape required for generating badges. + c.run('sudo apt-get install -yy git python3-dev python3-venv python3-wheel build-essential python3-cairocffi python3-psycopg2 postgresql uwsgi-emperor uwsgi-plugin-python3 memcached netcat nginx certbot libmemcached-dev xmlsec1 imagemagick inkscape') + + +@task(hosts=hosts) +def deploy(c): + install_essentials(c) + df2.transfer_files_git(c) + df2.init(c) + if not exists(c, c.env.virtualenv): + c.sudo(f'mkdir -p $(dirname {c.env.virtualenv})') + c.sudo(f'chown {c.user} $(dirname {c.env.virtualenv})') + c.run('{env.python} -m venv --system-site-packages {env.virtualenv}'.format(env=c.env)) + with c.cd(c.env.project_dir): + c.run('{env.virtualenv}/bin/python -m pip install -c constraints.txt -r requirements.txt'.format(env=c.env)) + # Fixes "AttributeError: install_layout". See: + # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1003252 + # https://github.com/pypa/setuptools/issues/3278 + c.run('SETUPTOOLS_USE_DISTUTILS=stdlib {env.virtualenv}/bin/python -m pip install -c constraints.txt -r vendored_requirements.txt'.format(env=c.env)) + df2.prepare_django(c, fail_level='ERROR') + df2.fix_permissions( + c, + read=['pinaxcon', 'vendor', 'static'], + read_write=[], + ) + df2.reload_uwsgi(c) + df2.flush_memcached(c) + df2.update_nginx(c) + df2.install_scheduled_jobs( + c, + periodic_jobs=[ + 'deploy/cron/cron.daily', + ], + ) + df2.check_site_online(c) + + +# The "ns" appears to be a magic name. +ns = Collection( + deploy, + task(df2.download_postgres_db, hosts=hosts), + task(df2.mirror_postgres_db, hosts=hosts), + task(df2.mirror_media, hosts=hosts), + task(df2.django_shell, hosts=hosts), + task(df2.bash, hosts=hosts), +) +ns.configure({ + # Built-in Fabric config. + 'run': { + 'echo': True, + # Needed so local commands work. Can also use FABRIC_RUN_REPLACE_ENV. + 'replace_env': False, + }, + + # Our custom project config. + 'env': { + 'branch': 'fossy2023', + 'app_user': 'www-data', + 'db_name': 'symposion', + 'project_dir': '/srv/symposion_app', + 'media_dir': 'media', + 'virtualenv': '/srv/venvs/symposion-django-py39', + 'site_name': 'symposion', + 'requirements': 'requirements.txt', + 'settings': 'pinaxcon.settings', + 'uwsgi_conf': 'deploy/uwsgi.ini', + 'nginx_conf': 'deploy/nginx.conf', + 'python': '/usr/bin/python3.9', + 'url': 'https://2023.fossy.us/', + 'domain': '2023.fossy.us', + }, +})