Skip to content

Commit 81af580

Browse files
committed
Add some automation for provisioning servers
1 parent 9d9cd99 commit 81af580

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
lines changed

fabfile.py

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

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

0 commit comments

Comments
 (0)