Skip to content

Commit 53f2088

Browse files
committed
Add some automation for provisioning servers
1 parent 9d9cd99 commit 53f2088

File tree

4 files changed

+233
-30
lines changed

4 files changed

+233
-30
lines changed

fabfile.py

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import tempfile
2+
import secrets
3+
import pathlib
4+
import string
5+
6+
from fabric import task
7+
8+
9+
10+
PYTHON_VERSION = "3.7.9"
11+
12+
13+
@task
14+
def provision(conn, domain):
15+
postgres_password = "".join((
16+
secrets.choice(string.ascii_letters + string.digits)
17+
for _ in range(12)
18+
))
19+
django_secret_key = "".join((
20+
secrets.choice(string.ascii_letters + string.digits)
21+
for _ in range(64)
22+
))
23+
24+
# Install apt deps
25+
apt_deps = " ".join(APT_DEPENDENCIES)
26+
conn.run(f"apt install -y {apt_deps}")
27+
28+
# Setup PyEnv
29+
conn.run("curl https://pyenv.run | bash")
30+
_append_bashrc(conn, PYENV_BASHRC)
31+
conn.run(f"pyenv install {PYTHON_VERSION}")
32+
conn.run(f"pyenv global {PYTHON_VERSION}")
33+
34+
# Create the `web` user with their own home director and group
35+
conn.run("useradd --create-home --user-group web")
36+
37+
# Clone the repository
38+
conn.run("git clone https://github.com/pipermerriam/ethereum-function-signature-registry.git /home/web/ethereum-function-signature-registry")
39+
40+
#
41+
# Setup and Install project dependencies
42+
#
43+
conn.run("pip install virtualenv")
44+
conn.run("python -m virtualenv /home/web/venv")
45+
46+
conn.run("/home/web/venv/bin/pip install -r /home/web/ethereum-function-signature-registry/requirements.txt")
47+
48+
with tempfile.TemporaryDirectory() as base_path:
49+
dotenv_file_path = pathlib.Path(base_path) / '.env'
50+
dotenv_file_path.write_text(DOTENV.format(
51+
DOMAIN=domain,
52+
POSTGRES_PASSWORD=postgres_password,
53+
SECRET_KEY=django_secret_key,
54+
))
55+
56+
conn.put(str(dotenv_file_path), remote='/home/web/ethereum-function-signature-registry/.env')
57+
58+
59+
Setup Postgres User and Database
60+
61+
with tempfile.TemporaryDirectory() as base_path:
62+
# systemd service for worker
63+
pgpass_file_path = pathlib.Path(base_path) / '.pgpass'
64+
pgpass_file_path.write_text(f"*.*.*.bytes4.{postgres_password}")
65+
66+
conn.put(str(pgpass_file_path), remote='/root/.pgpass')
67+
conn.run("chmod 600 /root/.pgpass")
68+
69+
conn.run(f"sudo -u postgres psql -c \"CREATE ROLE bytes4 PASSWORD '{postgres_password}' SUPERUSER LOGIN;\"")
70+
conn.run("sudo -u postgres createdb --no-password bytes4")
71+
72+
#
73+
# Setup Redis
74+
#
75+
conn.run('sed -i "s/supervised no/supervised systemd/g" /etc/redis/redis.conf')
76+
conn.run("service redis restart")
77+
78+
#
79+
# Setup config files for uwsgi/nginx/4byte-worker
80+
#
81+
with tempfile.TemporaryDirectory() as base_path:
82+
# systemd service for worker
83+
worker_service_file_path = pathlib.Path(base_path) / '4byte.service'
84+
worker_service_file_path.write_text(SYSTEMD_WORKER_SERVICE)
85+
86+
conn.put(str(worker_service_file_path), remote='/etc/systemd/system/4byte.service')
87+
88+
# nginx configuration file
89+
nginx_4byte_conf = pathlib.Path(base_path) / '4byte'
90+
nginx_4byte_conf.write_text(NGINX_4BYTE.format(DOMAIN=domain))
91+
92+
conn.put(str(nginx_4byte_conf), remote='/etc/nginx/sites-available/4byte')
93+
conn.run('ln -s /etc/nginx/sites-available/4byte /etc/nginx/sites-enabled/')
94+
95+
# uwsgi configuration file
96+
uwsgi_4byte_conf = pathlib.Path(base_path) / '4byte.ini'
97+
uwsgi_4byte_conf.write_text(UWSGI_CONF)
98+
99+
conn.put(str(uwsgi_4byte_conf), remote='/etc/uwsgi/apps-available/4byte.ini')
100+
conn.run('ln -s /etc/uwsgi/apps-available/4byte.ini /etc/uwsgi/apps-enabled/')
101+
102+
103+
104+
105+
def _append_bashrc(conn, content: str) -> None:
106+
for line in content.splitlines():
107+
if not line:
108+
continue
109+
conn.run(line)
110+
conn.run(f"echo '{line}' >> /root/.bashrc")
111+
112+
113+
APT_DEPENDENCIES = (
114+
# build
115+
"automake",
116+
"build-essential",
117+
"curl",
118+
"gcc",
119+
"git",
120+
"gpg",
121+
"software-properties-common",
122+
"pkg-config",
123+
"zlib1g",
124+
"zlib1g-dev",
125+
"libbz2-dev",
126+
"libreadline-dev",
127+
"libssl-dev",
128+
"libsqlite3-dev",
129+
"libffi-dev",
130+
# application
131+
"nginx",
132+
"uwsgi",
133+
"uwsgi-plugin-python3",
134+
"redis-server",
135+
"postgresql",
136+
"postgresql-contrib",
137+
"postgresql-server-dev-11",
138+
# convenience
139+
"htop",
140+
"tmux",
141+
)
142+
143+
144+
SYSTEMD_WORKER_SERVICE = """[Unit]
145+
Description=4byte worker
146+
After=network.target
147+
StartLimitIntervalSec=0
148+
149+
[Service]
150+
WorkingDirectory=/home/web/ethereum-function-signature-registry
151+
Type=simple
152+
Restart=always
153+
RestartSec=1
154+
User=web
155+
ExecStartPre=
156+
ExecStart=/home/web/venv/bin/python /home/web/ethereum-function-signature-registry/manage.py run_huey --verbosity 3
157+
"""
158+
159+
160+
PYENV_BASHRC = """export PATH="/root/.pyenv/bin:$PATH"
161+
eval "$(pyenv init -)"
162+
eval "$(pyenv virtualenv-init -)"
163+
"""
164+
165+
NGINX_4BYTE = """server {{
166+
server_name {DOMAIN};
167+
168+
listen 80;
169+
listen [::]:80;
170+
171+
if ($host = {DOMAIN}) {{
172+
return 301 https://$host$request_uri;
173+
}} # managed by Certbot
174+
175+
return 404; # managed by Certbot
176+
}}
177+
178+
179+
server {{
180+
server_name {DOMAIN}; # customize with your domain name
181+
182+
location / {{
183+
# django running in uWSGI
184+
uwsgi_pass unix:///run/uwsgi/app/4byte/socket;
185+
include uwsgi_params;
186+
uwsgi_read_timeout 300s;
187+
client_max_body_size 32m;
188+
}}
189+
190+
# location /static/ {{
191+
# # static files
192+
# alias /home/web/static/; # ending slash is required
193+
# }}
194+
195+
# location /media/ {{
196+
# # media files, uploaded by users
197+
# alias /home/web/media/; # ending slash is required
198+
# }}
199+
200+
listen 443 ssl; # managed by Certbot
201+
ssl_certificate /etc/letsencrypt/live/{DOMAIN}/fullchain.pem; # managed by Certbot
202+
ssl_certificate_key /etc/letsencrypt/live/{DOMAIN}/privkey.pem; # managed by Certbot
203+
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
204+
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
205+
206+
207+
}}
208+
"""
209+
210+
211+
UWSGI_CONF = """[uwsgi]
212+
plugin=python3
213+
uid=web
214+
chdir=/home/web/ethereum-function-signature-registry
215+
module=func_sig_registry.wsgi:application
216+
master=True
217+
vacuum=True
218+
max-requests=5000
219+
processes=4
220+
virtualenv=/home/web/venv
221+
"""
222+
223+
224+
DOTENV = """DATABASE_URL=postgres://bytes4:{POSTGRES_PASSWORD}@127.0.0.1:5432/bytes4
225+
DJANGO_ALLOWED_HOSTS={DOMAIN}
226+
DJANGO_DEBUG=False
227+
DJANGO_DEBUG_TOOLBAR_ENABLED=False
228+
DJANGO_SECRET_KEY={SECRET_KEY}
229+
DJANGO_SECURE_SSL_REDIRECT=False
230+
HUEY_WORKER_TYPE=thread
231+
REDIS_URL=redis://127.0.0.1:6379
232+
"""

func_sig_registry/settings.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@
4646
'func_sig_registry.registry',
4747
'rest_framework',
4848
'django_tables2',
49-
'storages',
50-
's3_folder_storage',
5149
'huey.contrib.djhuey',
5250
'corsheaders',
5351
]
@@ -183,31 +181,6 @@
183181
)
184182

185183

186-
# AWS Configuration
187-
DEFAULT_S3_PATH = "media"
188-
STATIC_S3_PATH = "static"
189-
190-
AWS_ACCESS_KEY_ID = env.get('AWS_ACCESS_KEY_ID', type=str, default=None)
191-
AWS_SECRET_ACCESS_KEY = env.get('AWS_SECRET_ACCESS_KEY', type=str, default=None)
192-
AWS_STORAGE_BUCKET_NAME = env.get('AWS_STORAGE_BUCKET_NAME', type=str, default=None)
193-
AWS_DEFAULT_REGION = env.get('AWS_DEFAULT_REGION', type=str, default=None)
194-
195-
# Boto config
196-
AWS_REDUCED_REDUNDANCY = True
197-
AWS_QUERYSTRING_AUTH = False
198-
AWS_S3_FILE_OVERWRITE = True
199-
AWS_S3_SECURE_URLS = True
200-
AWS_IS_GZIPPED = False
201-
AWS_PRELOAD_METADATA = True
202-
AWS_HEADERS = {
203-
"Cache-Control": "public, max-age=86400",
204-
}
205-
206-
if AWS_DEFAULT_REGION:
207-
# Fix for https://github.com/boto/boto/issues/621
208-
AWS_S3_HOST = "s3-{0}.amazonaws.com".format(AWS_DEFAULT_REGION)
209-
210-
211184
# DRF
212185
REST_FRAMEWORK = {
213186
'DEFAULT_PERMISSION_CLASSES': [

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ factory-boy==2.7.0
66
fake-factory==0.7.4
77
hypothesis==3.5.3
88
tox==2.4.1
9+
fabric==2.5.0

requirements.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ django-tables2>=1.2.3,<1.3.0
33
djangorestframework>=3.3.3,<3.4.0
44
ipython>=6.2.1
55
pysha3==1.0.2
6-
django-s3-folder-storage>=0.3,<0.4
7-
django-storages>=1.4.1,<1.5.0
8-
boto>=2.41.0,<3.0.0
96
env-excavator>=1.5.0,<1.6.0
107
python-dotenv>=0.5.1,<0.6.0
118
psycopg2-binary>=2.7.4,<2.8.0

0 commit comments

Comments
 (0)