Install on FreeBSD using packages as well as Gunicorn, supervisord and nginx

We have to install some stuff.

    pkg install py38-Flask
    pkg install py38-Flask-Flatpages
    pkg install py38-Flask-sqlalchemy
    pkg install py38-Flask-migrate
    pkg install py38-Flask-WTF

to run ResearchNotes. We also need to serve it using Gunicorn, supervisor and nginix

   pkg install py38-gunicron
   pkg install py38-supervisor
   pkg install nginx

Remember, you need your database engine binding. Sqlite3 might already be around but the ones for other stuff might missing (see SQLAlchemy). It depends also, what else is running on the same system.

   pkg install py38-sqlite3

After installing nginix, thw user www and grpoup www should exist. We should use it for also installing ResearchNotes. This is the only packages, we want to install by pip. In this way, all common packages will be updated by the package manager and we do not have to take care of the local environment. There are some pro and cons to this - e.g. the python environments mix. But if we have a dedicated server, this should not be a problem because not much other stuff will run in parallel (best chance are things like fail2ban and/or a samba server).

  pkg install py38-pip

Now, we need to install the app. But we do not want to run it in root context. So, we should create a user researchnotes (or similar).

  adduser

we change into the user contest and install Researchnotes.

 su researchnotes
 pip install ResearchNotes-1.x.x-py3-none-any.whl --user

This will install the package into the local user directory. We will have to make it accessable later for nginix. It also will need to include as a path for setting up the Gunicorn app.

We can use a Gunicorn app as follow

import sys
sys.path.append('/home/researchnotes/.local/lib/python3.8/site-packages/')

from werkzeug.middleware.proxy_fix import ProxyFix

from ResearchNotes import create_app

app_wsgi = create_app("/usr/local/www/researchnotes/config.py")

app = ProxyFix(app_wsgi, x_for=1, x_host=1)

if __name__ == "__main__":
    app.run(host='0.0.0.0')

Test run the app

   python3.8 wsgi.py

If work, test run with Gunicorn

 gunicorn -b <your_net_workInterface> wsgi:app

Finally, we set up supervisord with it .conf:

  ; Sample supervisor config file.

  [supervisord]
  logfile=/var/log/supervisord.log ; (main log file;default $CWD/supervisord.log)
  logfile_maxbytes=50MB       ; (max main logfile bytes b4 rotation;default 50MB)
  logfile_backups=10          ; (num of main logfile rotation backups;default 10)
  loglevel=info              ; (log level;default info; others: debug,warn,trace)
  pidfile=/var/run/supervisor/supervisord.pid ; (supervisord pidfile;default        supervisord.pid)
  nodaemon=false              ; (start in foreground if true;default false)
  minfds=1024                 ; (min. avail startup file descriptors;default 1024)
  minprocs=200                ; (min. avail process descriptors;default 200)

  [program:researchnotes]
  environment= PYTHONPATH=/root/.local/lib/python3.8/site-packages/ResearchNotes/
  command=/usr/local/bin/gunicorn w=4 --preload -b localhost:8000 wsgi:app
  directory=/usr/local/www/researchnotes
  user=researchnotes
  autostart=true
  autorestart=true
  stopasgroup=true
  killasgroup=true

One has to include the paramter –preload. Otherwise, the app has different session keys and cannot switch between workers. ALso, sqlite3 access shown some problems (at least under Linux).

Finally, we have to do a chmod go=+r+w+x for the home directory of researchnotes. Otherwise, nginx cannot deliver the static files. We start everything with setting up nginx.


    user  www;
    worker_processes  1;

    events {
        worker_connections  1024;
    }


    http {
      include       mime.types;
      default_type  application/octet-stream;

      log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

      #access_log  logs/access.log  main;

      sendfile        on;
     #tcp_nopush     on;

     #keepalive_timeout  0;
      keepalive_timeout  65;

      #gzip  on;

    server {
      # listen on port 443 (https)
      listen 80;
      server_name wintermute.ifi.unicamp.br;

      # location of the self-signed SSL certificate
      #ssl_certificate /home/ubuntu/microblog/certs/cert.pem;
      #ssl_certificate_key /home/ubuntu/microblog/certs/key.pem;

      # write access and error logs to /var/log
       access_log /var/log/nginx/researchnotes_access.log;
       error_log /var/log/nginx/researchnotes_error.log;

      #Increase uplaod size to 100 MB as we have to get measurements uploaded.
      client_max_body_size 100M;

       # Use secure headers to avoid XSS and many other things
       add_header X-Content-Type-Options nosniff;
       add_header X-XSS-Protection "1; mode=block";
       add_header X-Robots-Tag none;
       add_header X-Download-Options noopen;
       add_header X-Permitted-Cross-Domain-Policies none;
       add_header X-Frame-Options "SAMEORIGIN" always;
       add_header Referrer-Policy "no-referrer";
       add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' 'unsafe-eval'; frame-src 'self'; object-src 'self'";



       location / {
           # forward application requests to the gunicorn server
           proxy_pass http://localhost:8000;
           proxy_redirect off;
           proxy_set_header Host $host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   
        }

        location ^~ /static {
            # handle static files directly, without forwarding to the application
            alias  /home/researchnotes/.local/lib/python3.8/site-packages/ResearchNotes/static;
            expires 30d;
        }

      }
    }

You will have to adapt settings in your firewall to let the outside talk to nginx. On the other hand, you want to block access to the Gunicorn socket (which should anyway be localhost) or your database.