Skip to content

Commit

Permalink
Merge pull request #2029 from liberapay/aws-upgrade
Browse files Browse the repository at this point in the history
This branch upgrades the Liberapay webapp in several ways. The production servers will now run Amazon Linux v2 instead of the old v1, Python 3.8 instead of 3.6 (closes #1703), `gunicorn` instead of Apache's `httpd` with `mod_wsgi`, and `cloudflared` instead of an Amazon load balancer. This last point fixes #1093 and will zero out a part of our AWS bill, but those savings won't lower the overall bill because on the other hand I've increased the resources allocated to the database and webapp.

Although it wasn't part of the plan, this branch can be considered a step towards #1727.
  • Loading branch information
Changaco authored May 23, 2021
2 parents 8e3b6fd + f4120f1 commit e909cde
Show file tree
Hide file tree
Showing 33 changed files with 271 additions and 313 deletions.
13 changes: 0 additions & 13 deletions .ebextensions/01_apache.config

This file was deleted.

13 changes: 0 additions & 13 deletions .ebextensions/01_env.config

This file was deleted.

6 changes: 0 additions & 6 deletions .ebextensions/01_ld.config

This file was deleted.

3 changes: 0 additions & 3 deletions .ebextensions/01_log_dir.config

This file was deleted.

7 changes: 0 additions & 7 deletions .ebextensions/01_yum.config

This file was deleted.

11 changes: 0 additions & 11 deletions .ebextensions/02_pip.config

This file was deleted.

13 changes: 0 additions & 13 deletions .ebextensions/03_checkconf.config

This file was deleted.

1 change: 1 addition & 0 deletions .platform/confighooks
5 changes: 5 additions & 0 deletions .platform/files/cloudflared.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
tunnel: liberapay-prod
credentials-file: /etc/cloudflared/liberapay-prod.json

ingress:
- service: unix:/var/app/current/socket
43 changes: 43 additions & 0 deletions .platform/files/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[Unit]
Description=Cloudflare Tunnel daemon for web app #%i
After=network.target webapp@%i.service
Wants=network.target
BindsTo=webapp@%i.service

[Service]
Type=notify
ExecStart=/usr/local/bin/cloudflared --no-autoupdate tunnel --config /etc/cloudflared/cloudflared.conf run
User=cloudflared
Group=cloudflared
Restart=on-failure
RestartSec=5s

CapabilityBoundingSet=
AmbientCapabilities=
PrivateUsers=true

NoNewPrivileges=true
LimitNOFILE=1048576
UMask=0077

ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ProtectHostname=true
ProtectClock=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictRealtime=true
RestrictSUIDSGID=true
RemoveIPC=true

SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
SystemCallArchitectures=native
7 changes: 7 additions & 0 deletions .platform/files/pgenv.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

export PGHOST=$(/opt/elasticbeanstalk/bin/get-config environment -k PGHOST)
export PGPORT=$(/opt/elasticbeanstalk/bin/get-config environment -k PGPORT)
export PGDATABASE=$(/opt/elasticbeanstalk/bin/get-config environment -k PGDATABASE)
export PGUSER=$(/opt/elasticbeanstalk/bin/get-config environment -k PGUSER)
export PGPASSWORD=$(/opt/elasticbeanstalk/bin/get-config environment -k PGPASSWORD)
21 changes: 21 additions & 0 deletions .platform/files/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[Unit]
Description=Web application daemon #%i
Requires=webapp@%i.socket

[Service]
User=webapp
Group=webapp
Type=notify
WorkingDirectory=/var/app/current/
EnvironmentFile=/opt/elasticbeanstalk/deployment/env
Sockets=webapp@%i.socket
ExecStart=/var/app/venv/staging-LQM1lest/bin/python app.py --bind fd:3

Restart=always

StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=web

# When stopping, send the initial SIGTERM to the main process only.
KillMode=mixed
9 changes: 9 additions & 0 deletions .platform/files/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Unit]
Description=Socket of web application daemon #%i

[Socket]
ListenStream=/var/app/%i/socket
Service=webapp@%i.service
SocketUser=webapp
SocketGroup=cloudflared
SocketMode=0660
37 changes: 37 additions & 0 deletions .platform/hooks/postdeploy/01_deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash

# Tell bash to be strict and log everything
set -eux

# Compute the deployment ID
deploy_id=$(($(cat /var/app/_deploy_id 2>/dev/null || echo 0) + 1))
max_deploy_id=$((deploy_id + 99))
while systemctl is-active --quiet webapp@$deploy_id.service; do
let deploy_id++
if [ $deploy_id -gt $max_deploy_id ]; then
echo "this script appears to be stuck in an infinite loop, exiting"
exit 1
fi
done

# Rename the app directory
app_dir=$(pwd)
rm -rf /var/app/$deploy_id
mv $app_dir /var/app/$deploy_id
ln -s /var/app/$deploy_id $app_dir

# Start the new instance and its proxy
systemctl start webapp@$deploy_id.service cloudflared@$deploy_id.service

# Save the new deployment ID
echo $deploy_id >/var/app/_deploy_id

# Stop the old instance(s) and their proxies
let i=1
while [ $i -lt $deploy_id ]; do
systemctl stop cloudflared@$i.service
systemctl stop webapp@$i.service
systemctl stop webapp@$i.socket
rm -rf /var/app/$i
let i++
done
44 changes: 44 additions & 0 deletions .platform/hooks/prebuild/01_install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash

# Tell bash to be strict and log everything
set -eux

# Install libffi-devel for misaka, and htop for when I want to look at what's going on
yum install -y libffi-devel htop
# Install PostgreSQL client tools and libraries
amazon-linux-extras install -y postgresql11

# Automatically set the PG* environment variables so that `psql` connects to the liberapay database by default
install -m 644 -o root -g root -t /etc/profile.d .platform/files/pgenv.sh

# Install the systemd service files for the webapp and cloudflared
install -m 644 -o root -g root -t /etc/systemd/system .platform/files/[email protected]
install -m 644 -o root -g root -t /etc/systemd/system .platform/files/[email protected]
install -m 644 -o root -g root -t /etc/systemd/system .platform/files/[email protected]
systemctl daemon-reload

# Install cloudflared, directly from GitHub
if ! which cloudflared 2>/dev/null || [ $(cloudflared version) != "cloudflared version 2021.5.8 "* ]; then
wget https://github.com/cloudflare/cloudflared/releases/download/2021.5.8/cloudflared-linux-amd64
hash=$(sha256sum cloudflared-linux-amd64 | cut -d' ' -f1)
expected_hash=224cd850cb042a5da1d15432063ed04bf8764241de769338e65c44639ed6c28e
if [ $hash != $expected_hash ]; then
echo "cloudflared binary downloaded from GitHub doesn't match expected hash: $hash != $expected_hash"
exit 1
fi
install -m 755 -o root -g root cloudflared-linux-amd64 /usr/local/bin/cloudflared
rm cloudflared-linux-amd64
fi

# Create the cloudflared system user and group
groupadd -r cloudflared || true
useradd -r -g cloudflared cloudflared || true

# Install the Cloudflare Tunnel configuration and credentials files
install -o cloudflared -g cloudflared -m 755 -d /etc/cloudflared
install -o cloudflared -g cloudflared -m 644 -t /etc/cloudflared .platform/files/cloudflared.conf
if ! [ -f /etc/cloudflared/liberapay-prod.json ]; then
aws s3 cp s3://serverfiles.liberapay.org/liberapay-prod.json liberapay-prod.json
install -o cloudflared -g cloudflared -m 644 -t /etc/cloudflared liberapay-prod.json
rm liberapay-prod.json
fi
3 changes: 3 additions & 0 deletions .platform/hooks/predeploy/01_pip.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash -eu

/var/app/venv/staging-*/bin/pip install --require-hashes -r requirements_base.txt
3 changes: 3 additions & 0 deletions .platform/hooks/predeploy/99_check_config.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash -eu

sudo -u webapp -E PYTHONPATH=. /var/app/venv/staging-*/bin/python liberapay/wireup.py
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
language: python
jobs:
include:
- python: 3.6
env: TOXENV=py36
- python: 3.8
env: TOXENV=py38
- python: 3.9
env: TOXENV=py39
addons:
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
python := "$(shell { command -v python3.6 || command -v python3 || command -v python || echo false; } 2>/dev/null)"
python := "$(shell { command -v python3.8 || command -v python3 || command -v python || echo false; } 2>/dev/null)"

# Set the relative path to installed binaries under the project virtualenv.
# NOTE: Creating a virtualenv on Windows places binaries in the 'Scripts' directory.
Expand Down Expand Up @@ -28,7 +28,7 @@ $(env): requirements*.txt
rehash-requirements:
$(env_bin)/$(pip) install hashin
for f in requirements*.txt; do \
sed -E -e '/^ *#/d' -e '/^ +--hash/d' -e 's/(; .+)?\\$$//' $$f | xargs $(env_bin)/hashin -r $$f -p 3.6 -p 3.7 -p 3.8 -p 3.9; \
sed -E -e '/^ *#/d' -e '/^ +--hash/d' -e 's/(; .+)?\\$$//' $$f | xargs $(env_bin)/hashin -r $$f -p 3.8 -p 3.9; \
done

clean:
Expand All @@ -39,7 +39,7 @@ schema: $(env)
$(with_local_env) ./recreate-schema.sh

schema-diff: test-schema
eb ssh liberapay-prod -c 'pg_dump -sO' | sed -e '/^INFO: /d' >prod.sql
eb ssh liberapay -c 'pg_dump -sO' | sed -e '/^INFO: /d' >prod.sql
$(with_tests_env) sh -c 'pg_dump -sO "$$DATABASE_URL"' >local.sql
sed -E -e '/^--/d' -e '/^\s*$$/d' -e '/^SET /d' -e 's/\bpg_catalog\.//g' -i prod.sql local.sql
diff -uw prod.sql local.sql
Expand Down
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: sleep 7d
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ The python code inside simplates is only for request-specific logic, common back

Make sure you have the following dependencies installed first:

- python ≥ 3.6
- python ≥ 3.8
- including the C headers of python and libffi, which are packaged separately in many Linux distributions
- postgresql 13 (see [the official download & install docs](https://www.postgresql.org/download/))
- make
Expand Down Expand Up @@ -182,12 +182,12 @@ All new dependencies need to be audited to check that they don't contain malicio
We use [pip's Hash-Checking Mode](https://pip.pypa.io/en/stable/reference/pip_install/#hash-checking-mode) to protect ourselves from dependency tampering. Thus, when adding or upgrading a dependency the new hashes need to be computed and put in the requirements file. For that you can use [hashin](https://github.com/peterbe/hashin):

pip install hashin
hashin package==x.y -r requirements_base.txt -p 3.6 -p 3.7 -p 3.8 -p 3.9
hashin package==x.y -r requirements_base.txt -p 3.8 -p 3.9
# note: we have several requirements files, use the right one

If for some reason you need to rehash all requirements, run `make rehash-requirements`.

To upgrade all the dependencies in a requirements file, run `hashin -u -r requirements_XXX.txt -p 3.6 -p 3.7 -p 3.8 -p 3.9`. You may have to run extra `hashin` commands if new subdependencies are missing.
To upgrade all the dependencies in a requirements file, run `hashin -u -r requirements_XXX.txt -p 3.8 -p 3.9`. You may have to run extra `hashin` commands if new subdependencies are missing.

### Processing personal data

Expand Down
20 changes: 7 additions & 13 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,21 @@
"""

from os import environ as env, execlp
import sys

_canonical_host = env['CANONICAL_HOST']
_production = _canonical_host == 'liberapay.com'

if __name__ == '__main__':
# Exec gunicorn, ask it to read its settings from this file
program = 'gunicorn'
execlp(program, program, 'liberapay.main:website', '--config', 'app.py')
execlp(program, program, 'liberapay.main:website', '--config', 'app.py', *sys.argv[1:])

accesslog = '-' # stderr
accesslog = '-' # stdout
access_log_format = (
'%(t)s %(s)s %(L)ss %({Host}i)s "%(r)s" %(b)s "%(f)s"'
) if sys.stdin.isatty() else (
'%(s)s %(L)s %({Host}i)s "%(r)s" %(b)s "%(f)s"'
)

bind = [
env['OPENSHIFT_PYTHON_IP'] + ':' + env['OPENSHIFT_PYTHON_PORT']
if _production else
_canonical_host
]

chdir = env['OPENSHIFT_REPO_DIR'] if _production else ''

if _production:
pidfile = env['OPENSHIFT_DATA_DIR'] + '/gunicorn.pid'
if ':' in _canonical_host:
bind = [_canonical_host]
2 changes: 1 addition & 1 deletion backup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ if [ "$(basename "$dest_dir")" != 'liberapay' ]; then echo "parent directory sho
dest_dir="$dest_dir/backups"
mkdir -p "$dest_dir"
dest="$dest_dir/$(date -u -Iseconds).psql"
eb ssh liberapay-prod -c "pg_dump -Fc" > "$dest"
eb ssh liberapay -c "pg_dump -Fc" > "$dest"
chmod 400 "$dest"
ls -lh "$dest_dir" | tail -10
4 changes: 2 additions & 2 deletions cli/check-python-version.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys

if sys.version_info < (3, 6, 0):
print("Liberapay requires Python >= 3.6, but %s is version %s.%s" %
if sys.version_info < (3, 8, 0):
print("Liberapay requires Python >= 3.8, but %s is version %s.%s" %
(sys.executable, sys.version_info[0], sys.version_info[1]))
sys.exit(1)
if sys.version_info >= (3, 10, 0):
Expand Down
Loading

0 comments on commit e909cde

Please sign in to comment.