Setting up a Flask Web Server
This blog recounts my experiences in setting up the web server for this blog site. I originally started posting on Blogger but that platform wasn't for me. As I wanted to further my experience with Python and I had discovered Miguel Grinberg's excellent Flask Mega-Tutorial, I thought that it would be more interesting and educational to setup my own.
As always I got a bit more than I had bargained for which was further motivation to document my process as it might save some of you a bit of time. For one, Miguel first wrote the blog in 2012 and portions of his instructions are a bit dated now. Further, not all modules he used (especially SQLAlchemy) still seem to work correctly.
My version of this blog website is a more lightweight version of Miguel's Flask blog app as I assume a knowledge of SQL versus relying on any ORM model. I'm a big fan of reducing scaffolding as much as possible.
I would advise that you first consider the versions of all your software to ensure everything is compatible. That is a lesson I learned that hard way and cost me several hours to figure out why my blog app wasn't working with my web server. Keen on using a virtual environment of the latest Python version (3.5) I realized that Apache on Fedora 23 uses Python 3.4. So, before setting up your virtual environment make sure you are starting with the right version of Python!
Setting up the Database
I initially wanted to use MariaDB as Miguel's tutorial was based on a prototype using SQLite followed by deployment with MySQL. However as I was having trouble with the SQLAlchemy support on Python3 I decided I might as well try another database since I would be setting everything up using plain old SQL. I ended up settling on Postgres since it seemingly is popular with many Python web developers and has good Python3 support, and would be another learning experience for me as I had no prior experience with it.
The install was mostly straightforward. Don't forget to
follow the instructions for setting up the firewall as well as SELinux as shown on the link. The only issue I had
was with the initial login as su - postgres
did not work for me. I ended up figuring
out the initial login by doing further searching on the web and finding an Ubuntu solution that happens to work in Fedora as well:
sudo -u postgres psql
. I really can't say that I understand the difference between the two or why one
works but the other doesn't. That's research for another day.
The basic commands for a new Postgres install are summarized below:
sudo dnf install postgresql-server postgresql-contrib
sudo systemctl enable postgresql
sudo postgresql-setup --initdb --unit postgresql
sudo systemctl start postgresql
firewall-cmd --permanent --add-port=5432/tcp
firewall-cmd --add-port=5432/tcp
Edit /var/lib/pgsql/data/pg_hba.conf
to confirm user authentication- password for localhost is probably the simplest initially:
TYPE DATABASE USER ADDRESS METHOD
host all all localhost password
Finally, setup your Postgres user/password and database:
sudo -u postgres psql
Configuring the Web Server
Flask has a handy web server to help test the setup, but at some point you'll want to setup your production
web server which in my case was Apache. I also
installed mod_wsgi for Apache to interact with Flask.
Note that any reference to myapp
below is just a placeholder and should be replaced by your app name.
sudo dnf install python3-mod_wsgi
cd /var/www
sudo mkdir myapp
sudo vi myapp.wsgi
Enter the following code into the file:
activate_this = '/path/to/your/activate_this.py
with open(activate_this) as file_:
exec(file_.read(), dict(__file__=activate_this))
from app import app as application
app
in from app
refers to the __init__.py
file in the app
folder that is in the same directory as the activate_this.py
file. The app
in import app
is the app variable defined in __init__.py
. More on this later.
A visual representation of the Apache file structure is shown below:
/
|
var/
|
www/
|
myapp/
|
myapp.wsgi
SELinux was probably the main consideration in setting up Apache
setsebool -P httpd_can_network_connect on
setsebool -P httpd_can_network_connect_db on
setsebool -P httpd_unified on
Setup the configuration file for the server using:
sudo vi /etc/httpd/conf.d/z_myapp.conf
The 'z' in front of the filename is to ensure that the configuration file runs after all the default
configuration files as any conf
files in conf.d
are run in alphabetic order. The
code for the .conf file is:
WSGISocketPrefix run/wsgi
WSGIScriptAlias /myapp /var/www/myapp/myapp.wsgi
WSGIRestrictStdout Off
ServerName myapp.com
WSGIDaemonProcess myapp user=username group=usergroup threads=5
WSGIScriptAlias / /var/www/myapp/myapp.wsgi
WSGIProcessGroup myapp
WSGIApplicationGroup %{GLOBAL}
Require all granted
Again, replace myapp
with your app name as well as username
and usergroup
with your respective username and usergroup.
Start the web server:
sudo firewall-cmd --add-service:http
sudo firewall-cmd --permanent --add-service:http
sudo firewall-cmd --add-service:https
sudo firewall-cmd --permanent --add-service:https
sudo systemctl start httpd
sudo systemctl enable httpd
Finally setup the environment and paths in the activate_this
file in the myapp
folder of the virtual environment.
This file ensures that the virtual environment is recreated within the wsgi module of the web server.
The Virtual Environment
I use Anaconda for my virtual environment and set it up minimally with the base conda install via the installer, then installed the following:
conda create --name web flask flask-wtf psycopg2
activate web
pip install flask-marshmallow
The psycopg2 package is for my Postgres database. I ended up installing my web app inside the conda envs directory structure for my virtual environment although I'm quite sure that isn't a best-practice. Consequently, trying to map Miguel's file structure to my own setup was a minor brain tease due to this decision as well as Miguel's tendency to name everything 'app', which I've attempted to disambiguate as much as possible. For clarity, I've included a visual summary of my directory structure below:
user home
|
miniconda3/
|
envs/
|
web/
|
bin/ conda-meta/ flask/ include/ lib/ share/ ssl/
|
myapp/
|
activate_this.py app/ config.py run.py
|
forms.py __init__.py static/ templates/ views.py
The activate_this.py
is a simple script that mod_wsgi first runs within its Python instance. In my
case I use it to set os.path['environ']
and sys.path
to be the same as they would be if I
just printed them in my virtual environment:
import os, sys
os.environ['PATH'] = '/your/path/number/one:/your/path/number/two:/etc/etc/etc'
sys.path = ['/sys/path/number/one', '/sys/path/number/two', '/etc/etc/etc']
Finally, to appease SELinux:
sudo chcon -R -t httpd_sys_content_t web
Deploying: Using a Domain Name
After some research I chose Namecheap to register my domain name. I hadn't tried anyother domain registry before so I had no point of comparison but my experience was inline with the reviews I had read and I had little trouble. I did have to go through their Knowledgebase a couple of times to figure out how to use DDNS but their interface is clean and relatively easy to navigate. Ultimately I downloaded their software to setup DDNS as my router didn't support a custom domain name server in its DDNS setup options.
Upgrade to Fedora 24
upgrade postgres, upgrade python virtual env to python3.5, upgrade wgsi/activate_this.py links,and reset SELinux permissionsThe Initial Postgres Login Explained
So after a bit of research I believe that I understand why sudo -u postgres psql
worked
for me but su - postgres
didn't. Initially Postgres is setup for ident/peer local authentication,
which means that you need to login to Postgres using the postgres user account created on your machine during
installation. However, this account is locked which means only the superuser can log into it.
Consequently, there are 2 ways of initially logging in to the database.
Method 1- The Long Way
- Login as root via
su
- Then login as postgres using
su postgres
- Login to the database using
psql
Method 2- The Quick Way
Does the same thing in one line: sudo -u postgres psql
. This works because sudo
executes code
as the superuser, in this case running psql
as user postgres.
References:
- http://serverfault.com/questions/110154/whats-the-default-superuser-username-password-for-postgres-after-a-new-install
- http://www.cyberciti.biz/faq/linux-locking-an-account/