Introduction
After you have successfully tested Tryton, you want to put Tryton into production. Most of the time you want to have Tryton running on a real server instead of running it on your desktop machine. Also you want to have Tryton to start automatically after a reboot or start.
In this guide we build upon the guide from https://discuss.tryton.org/t/install-tryton-in-a-python-virtual-environment-on-ubuntu so you know how to setup Tryton in a virtual environment.
There are a few things to consider, so before you start:
- We use Ubuntu as base but RPM flavors like RedHat, RockyLinux etc should work the same.
- You have a fully running Tryton already with database connection etc.
- You understand what WSGI, systemd and NginX is.
- This guide gives you an example how you can run Tryton in a production environment.
Note: We use the following variables:
trydbuser
as the database usertrydbpass
as the database passwordtrydb
as the database nametrypyenv
as the name for the virtual environmenttrysysuser
as the name for the system user which will run the Tryton server/srv/tryloc
as the place where thevenv
will be put
You can choose whatever you want and replace the names accordingly.
We use /srv/tryloc
so other services can reach this location easily otherwise Apparmor or SELinux will kick in and deny access.
Setup base system
The system consists of several parts which are connected. First there is Gunicorn which basically runs Tryton. Then there is NginX which connects to Gunicorn and sends data from the Tryton clients to Gunicorn which then runs it inside Tryton. NginX is not nessecarrely needed, but it will be a good addition.
Install Gunicorn
The internal webserver (Werkzeug) in Tryton should only be used for development purposes and not for production. In this guide we use Gunicorn as the WSGI server. Gunicorn will be installed in the venv
so make sure the venv
is activated. As Gunicorn is using workers, we use the gevent
workers.
(tryloc) trysysuser@docsrv:~$ pip install gunicorn[gevent]
Create inside your venv
a new file called gunicorn.conf.py
and put the contents
import os
os.environ['TRYTOND_CONFIG'] = '/srv/tryloc/trytond.conf'
into it and save the file and close it. This file will be used by Gunicorn to get some configuration options for Tryton.
Info: Eventually you can also put more environment variables into this file like the list of databases or the logging configuration. Take a look a the Tryton documentation Configuration file for Tryton â Tryton server
Now let see if you can run Gunicorn from the commandline.
(tryloc) trysysuser@docsrv:~$ gunicorn --workers=5 --worker-class=gevent -b 0.0.0.0:8000 trytond.application:app
This will spin up 5 workers which are bound to port 8000. Connect with the desktop client to the server. Using the ip address should be enough. Also enter trydb
as the database name. You should be able to get into Tryton without any error.
Create systemd file for Gunicorn
Now that Gunicorn runs, we can automate the start and stop with a systemd file so that it runs on boot or restart. The files should go into /etc/systemd/system
which is only accessible by root so you have to use sudo
for that. Create a new file
testadmin@docsrv:~$ sudo nano /etc/systemd/system/trytond.service
and add the following content:
[Unit]
Description=The Trytond ERP server
After=network.target
Requires=network.target
[Service]
Type=notify
User=trysysuser
Group=trysysuser
RuntimeDirectory=trytond
WorkingDirectory=/srv/tryloc
ExecStart=/srv/tryloc/bin/gunicorn \
--workers=5 \
--worker-class=gevent \
--bind=0.0.0.0:8000 \
'trytond.application:app'
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Info: The ExecStart options can be changed and tuned. Consult the documentation of Gunicorn
which options are available.
Warning: When running SELinux you can be bitten by wrong labels on the different files. Check the /var/log/audit/audit.log
for more information and use the proper tools to understand whatâs going on.
Notify systemd that there was a change in a service file.
testadmin@docsrv:~$ sudo systemctl daemon-reload
Now you can start Tryton with:
testadmin@docsrv:~$ sudo systemctl start trytond
And stop it with:
testadmin@docsrv:~$ sudo systemctl stop trytond
To start Tryton on boottime execute
testadmin@docsrv:~$ sudo systemctl enable trytond
You check the status with:
testadmin@docsrv:~$ sudo systemctl status trytond
You should now be able to connect to Tryton, even after a reboot when you have enabled Tryton to be started at boottime.
You have now a production ready system which you can use.
Putting everything behind a proxy server like NginX
For a more secure environment and help with offloading, caching, loadbalancing etc you can use a proxy server. This server sits between Gunicorn and the client. Configuration of TLS/SSL between a proxy server and the client is easier to setup then doing it through Gunicorn. A proxy server can also manage multiple different Tryton installations through subdomains.
In short, when you are going to open up your production environment to the internet you have to use a proxy server to make your environment more secure and stable (Deploying Gunicorn â Gunicorn 21.2.0 documentation).
Installing NginX
Gunicorn itself strongly suggest to use NginX as proxy server (Deploying Gunicorn â Gunicorn 21.2.0 documentation) so we are also going that route. Install NginX with:
testadmin@docsrv:~$ sudo apt-get install nginx
After installing, check if NginX is running
testadmin@docsrv:~$ sudo systemctl status nginx
If everything is working, you will see a green dot and green active (running)
. Test if you can reach your server with your webbrowser. You must see a welcome page of some sort depending on the distribution you use. To start NginX on boot or restart we must tell systemd to enable NginX on boottime
testadmin@docsrv:~$ sudo systemctl enable nginx
Connecting Gunicorn and NginX
We have now a running Gunicorn and Nginx, both are reachable from the network which is not needed for Gunicorn. So change /etc/systemd/system/trytond.service
and replace --bind=0.0.0.0:8000
with --bind=127.0.0.1:8000
so Gunicorn will only listen on localhost.
After the change you have to tell systemd that you have changed something
testadmin@docsrv:~$ sudo systemctl daemon-reload
After a stop and start from trytond
you cannot reach Tryton from the network anymore. However doing
testadmin@docsrv:~$ curl http://localhost:8000
will give you the same answer as before.
Now we have to create a configuration for NginX to tell how it can reach Gunicorn.
testadmin@docsrv:~$ sudo nano /etc/nginx/sites-available/trytond.conf
Add the following content.
upstream trytond_server {
server 127.0.0.1:8000 fail_timeout=0;
}
server {
listen 80;
client_max_body_size 4G;
# set the correct host(s) for your site
server_name docsrv.local trytond.docsrv.local;
keepalive_timeout 5;
# path for static files
root /srv/www;
location / {
# checks for static file, if not found proxy to app
try_files $uri @proxy_to_app;
}
location @proxy_to_app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
proxy_redirect off;
proxy_pass http://trytond_server;
}
error_page 500 502 503 504 /500.html;
location = /500.html {
root /srv/www;
}
}
You have to enable the site available by creating a symbolic link to the sites-enabled
, which NginX reads from during startup.
testadmin@docsrv:~$ sudo ln -s /etc/nginx/sites-available/trytond.conf /etc/nginx/sites-enabled/
Info: Debian based distributions like Ubuntu are using sites-available
and sites-enabled
. For other distributions use the conf.d
sub directory (you can also directly use that on Ubuntu).
Warning: If there is a default
in /etc/nginx/sites-enabled/
remove it.
After enabling the site itâs time to see if the syntax is correct
testadmin@docsrv:~$ sudo nginx -t
It will output something like
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Restart NginX and see if you can connect.
testadmin@docsrv:~$ sudo systemctl restart nginx
Start the desktop client and try to connect. Make sure you have <hostname or ip>:80
added in the Host
field because we are now connecting to Tryton through port 80 and not 8000.
Replace connecting between NginX and Gunicorn with a Unix socket
Currently you are using the HTTP port 8000 for the connecting between NginX and Gunicorn. When you have multiple instances of Tryton or want to run another service you will get in trouble very quickly. In that case itâs better to use Unix sockets which are basically âfilesâ on the filesystem and are most of the time in /var/run
. Replacing
the HTTP port with a Unix socket is not that complicated. First we create a new systemd file which will create a socket for us.
testadmin@docsrv:~$ sudo nano /etc/systemd/system/trytond.socket
And add the following content
[Unit]
Description=Trytond Gunicorn socket
[Socket]
ListenStream=/run/trytond/trytond.sock
# Our service won't need permissions for the socket, since it
# inherits the file descriptor by socket activation
# only the nginx daemon will need access to the socket
SocketUser=www-data
# Optionally restrict the socket permissions even more.
#SocketMode=XXX
[Install]
WantedBy=sockets.target
Warning: On RPM based systems the user is nginx
instead of www-data
Save the file and then edit the /etc/systemd/system/trytond.service
and make some changes. The complete file should like this
[Unit]
Description=The Trytond ERP server
After=network.target
Requires=network.target
Requires=trytond.socket
[Service]
Type=notify
User=trysysuser
Group=trysysuser
RuntimeDirectory=trytond
WorkingDirectory=/srv/tryloc
ExecStart=/srv/tryloc/bin/gunicorn \
--workers=5 \
--worker-class=gevent \
'trytond.application:app'
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Because we added and made changes to a systemd file, we need to tell systemd about it.
testadmin@docsrv:~$ sudo systemctl daemon-reload
Start the socket with:
testadmin@docsrv:~$ sudo systemctl start trytond.socket
Stop the socket with:
testadmin@docsrv:~$ sudo systemctl stop trytond.socket
Check the status of the socket with:
testadmin@docsrv:~$ sudo systemctl status trytond.socket
Enable the socket to start on (re)boot:
testadmin@docsrv:~$ sudo systemctl enable trytond.socket
Because the service file is also changed, we need to restart that one as well:
testadmin@docsrv:~$ sudo systemctl restart trytond
Whatâs left is the change in the NginX configuration. Edit the file /etc/nginx/sites-available/trytond.conf
testadmin@docsrv:~$ sudo nano /etc/nginx/sites-available/trytond.conf
and change
upstream trytond_server {
server 127.0.0.1:8000 fail_timeout=0;
}
To
upstream trytond_server {
server unix:/var/run/trytond/trytond.sock fail_timeout=0;
}
After the change NginX have to be restarted
testadmin@docsrv:~$ sudo systemctl restart nginx
Test if the connecting to Tryton still works.
Getting the workers and task schedulers to work
Tryton has the ability to execute tasks through workers or a task scheduler. Both (workers and task scheduler) are separate programs which are not running at the moment because we didnât start them. Both donât need interaction from the outside so they can just be started and Gunicorn is not needed.
First we create a new systemd service file for the task scheduler:
testadmin@docsrv:~$ sudo nano /etc/systemd/system/trytond-cron.service
And add the following content:
[Unit]
Description=The task scheduler for Trytond server
After=network.target
Requires=network.target
[Service]
User=trysysuser
Group=trysysuser
WorkingDirectory=/srv/tryloc
ExecStart=/srv/tryloc/bin/trytond-cron -c /srv/tryloc/trytond.conf -d <your-database>
[Install]
WantedBy=multi-user.target
We have to do the same for the worker:
testadmin@docsrv:~$ sudo nano /etc/systemd/system/trytond-worker.service
And add the following content:
[Unit]
Description=The workers for Trytond server
After=network.target
Requires=network.target
[Service]
User=trysysuser
Group=trysysuser
WorkingDirectory=/srv/tryloc
ExecStart=/srv/tryloc/bin/trytond-worker -c /srv/tryloc/trytond.conf -d <your-database>
[Install]
WantedBy=multi-user.target
Warning: To make the workers do work, you have to enable them in the trytond.conf
. See Configuration file for Tryton â Tryton server
Edit the trytond.conf
to enable the workers. Make sure you are the right user.
trysysuser@docsrv:~$ nano /srv/tryloc/trytond.conf
And add or change
[queue]
worker = True
Save and close the file.
Again we go through systemd train:
testadmin@docsrv:~$ sudo systemctl daemon-reload
Because we changed the trytond.conf
file we have to restart the Trytond service.
testadmin@docsrv:~$ sudo systemctl restart trytond
testadmin@docsrv:~$ sudo systemctl restart trytond.socket
Start the workers and task scheduler with:
testadmin@docsrv:~$ sudo systemctl start trytond-worker
testadmin@docsrv:~$ sudo systemctl start trytond-cron
Stop them with:
testadmin@docsrv:~$ sudo systemctl stop trytond-worker
testadmin@docsrv:~$ sudo systemctl stop trytond-cron
Check their status with:
testadmin@docsrv:~$ sudo systemctl status trytond-worker
testadmin@docsrv:~$ sudo systemctl status trytond-cron
Enable them to start on (re)boot:
testadmin@docsrv:~$ sudo systemctl enable trytond-worker
testadmin@docsrv:~$ sudo systemctl enable trytond-cron
Securing the connection between client and NginX with SSL/TLS
Adding SSL/TLS certificates to NginX is quite straight forward and many tutorials exists on the internet. Therefore we are not going to dig into this because others do a way better job on that then I can. It also depends on the type of certificate you are going to use. Is it Letâs Encrypt or from an other trusted party etc. So please search the internet how to enable SSL/TLS with NginX.
However I want to point you to Client Usage â Tryton desktop client and read carefully the Warning and Note.
Also keep your firewall as closed as possible, only open ports which are needed to be open to the public. Also look for Apparmor or SELinux to make your environment even more secure.
With this all you have a secure production environment.
Running Gunicorn as another user
Instead of running Gunicor as trysysuser
you can also use another user to run Gunicorn. This makes the system a bit safer because we can give that user only read and execute permissions of the different files.
First we need to create a new user called trygun
which cannot login because it has no shell. Alongside the user we also create a group with the same name. Choose whatever username you like
testadmin@docsrv:~$ sudo adduser --system --no-create-home --group --shell /usr/sbin/nologin trygun
We can now use that username and group in our systemd files. You have to edit:
- /etc/systemd/system/trytond.service
- /etc/systemd/system/trytond-cron.service
- /etc/systemd/system/trytond-worker.service
And replace:
User=trysysuser
withUser=trygun
Group=trysysuser
withGroup=trygun
Problem is that trygun
doesnât have access permissions into /srv/tryloc
because itâs the home of the trysysuser
. We must give trygun
access to that directory which we are going to do with setfacl
. Most of the time this package is installed but use the following command will make sure it exists on your system.
testadmin@docsrv:~$ sudo apt-get install acl
With everything in place we can lock down the /srv/tryloc
directory to only be accessed by trysysuser
.
testadmin@docsrv:~$ sudo chmod 0700 /srv/tryloc
With setfacl
we add read and execute permissions to /srv/tryloc
specifically for trygun
.
testadmin@docsrv:~$ sudo setfacl -m u:trygun:r-X /srv/tryloc/
Info: Mind the capital X
.
Eventually you can byte-compile the Python code inside your venv
so itâs not needed to that on runtime. Run as trysysuser
with an active venv
(tryloc) trysysuser@docsrv:~$ python -c "import compileall; compileall.compile_dir('lib/', maxlevels=10, force=True, optimize=1)"
Extra steps (Expert mode!)
When you have multiple Tryton instances running for production and testing etc. you have to create multiple systemd service files for each instance. This can be a bit tedious and itâs a good candidate for using systemd unit template files. Read more about them here systemd.unit and systemd.unit
The idea here is that we are going to use the instance
name as the variable which connects everything together.
Warning: When going further you probably need to start over with new venv's
and databases with the right names. You cannot just rename them.
To set this up:
- The
instance
name can be anything, in this example it will betrytonrun1
- The instances will all live in
/srv/tryloc
- The socket files will be in
/var/run/trytond
- The name of the database must be the same as
instance
, in this case it will betrytonrun1
- All the instances are run as the same user
trysysuser
- For NginX you still have to add separate files for each instance. To keep things lined up use the
instance
name as the filename, in this casetrytonrun1.conf
- The instances can be reached under their own subdomain
<instance>.<yourdomain>.<tld>
First make sure you have a Tryton system running with the right database name and in a venv
in the right location.
The filename of the systemd files are in the form trytond@.service
mention the @
sign which indicates itâs a template.
We start off with the trytond@.socket
file.
testadmin@docsrv:~$ sudo nano /etc/systemd/system/trytond@.socket
And add the following content
[Unit]
Description=Gunicorn sockets for different Tryton instances
[Socket]
ListenStream=/run/trytond/%i.sock
# Our service won't need permissions for the socket, since it
# inherits the file descriptor by socket activation
# only the nginx daemon will need access to the socket
SocketUser=www-data
# Optionally restrict the socket permissions even more.
#SocketMode=XXX
[Install]
WantedBy=sockets.target
Next we create the file trytond@.service
.
testadmin@docsrv:~$ sudo nano /etc/systemd/system/trytond@.service
And add the following content
[Unit]
Description=Run several instances of the Trytond server
After=network.target
Requires=network.target
Requires=trytond@%i.socket
[Service]
Type=notify
User=trysysuser
Group=trysysuser
RuntimeDirectory=trytond
WorkingDirectory=/srv/tryloc/%i
ExecStart=/srv/tryloc/%i/bin/gunicorn \
--workers=5 \
--worker-class=gevent \
'trytond.application:app'
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
We also create a new file for the task scheduler:
testadmin@docsrv:~$ sudo nano /etc/systemd/system/trytond-cron@.service
And add the following content:
[Unit]
Description=The task scheduler for instances of the Trytond server
After=network.target
Requires=network.target
[Service]
User=trysysuser
Group=trysysuser
WorkingDirectory=/srv/tryloc/%i
ExecStart=/srv/tryloc/%i/bin/trytond-cron -c /srv/tryloc/trytond.conf -d %i
[Install]
WantedBy=multi-user.target
And for the worker as well.
testadmin@docsrv:~$ sudo nano /etc/systemd/system/trytond-worker@.service
With following content:
[Unit]
Description=The workers for instances of the Trytond server
After=network.target
Requires=network.target
[Service]
User=trysysuser
Group=trysysuser
WorkingDirectory=/srv/tryloc/%i
ExecStart=/srv/tryloc/%i/bin/trytond-worker -c /srv/tryloc/trytond.conf -d %i
[Install]
WantedBy=multi-user.target
As you have (hopefully) noted we use the %i
as the variable. In this example it will be replaced with trytonrun1
when the files are run by systemd.
Disable all the systemd files created and enabled in the sections above. Eventually you can remove the other files without the @
symbol. After you have done that, systemd should be updated with the changes.
testadmin@docsrv:~$ sudo systemctl daemon-reload
You can now start the trytonrun1
instance of Tryton with:
testadmin@docsrv:~$ sudo systemctl start trytond@trytonrun1.socket
testadmin@docsrv:~$ sudo systemctl start trytond@trytonrun1
testadmin@docsrv:~$ sudo systemctl start trytond-worker@trytonrun1
testadmin@docsrv:~$ sudo systemctl start trytond-cron@trytonrun1
Replace start
with stop
or enable
to stop and start at boottime respectively.
Last thing left is to change the NginX configuration to meet the new socket name and create difference between the different instances.
Rename the NginX configuration file trytond.conf
to trytonrun1.conf
to keep things in line.
testadmin@docsrv:~$ sudo mv /etc/nginx/sites-available/trytond.conf /etc/nginx/sites-available/trytonrun1.conf
Then edit the file and change the socket name and server_name. Your file should look something like
upstream trytonrun1 {
server unix:/var/run/trytond/trytonrun1.sock fail_timeout=0;
}
server {
listen 80;
client_max_body_size 4G;
# set the correct host(s) for your site
server_name trytonrun1.docsrv.local;
keepalive_timeout 5;
# path for static files
root /srv/www/trytonrun1;
location / {
# checks for static file, if not found proxy to app
try_files $uri @proxy_to_app;
}
location @proxy_to_app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
proxy_redirect off;
proxy_pass http://trytonrun1;
}
error_page 500 502 503 504 /500.html;
location = /500.html {
root /srv/www/trytonrun1;
}
}
Remove the link in /etc/nginx/sites-enabled
testadmin@docsrv:~$ sudo rm /etc/nginx/sites-enabled/trytond.conf
And create a new link to the renamed file
testadmin@docsrv:~$ sudo ln -s /etc/nginx/sites-available/trytonrun1.conf /etc/nginx/sites-enabled/
Test if the syntax of the file is still correct
testadmin@docsrv:~$ sudo nginx -t
it will output something like
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Restart NginX and see if you can connect.
testadmin@docsrv:~$ sudo systemctl restart nginx
If you cannot connect, try a reboot of the system becasue a lot has changed. After all this, you can do a cleanup an make more adjustments so it fits better your needs.