(Image: Header Graphic)

Sunday, December 15, 2019

Doug's Domain

Doug Vetter, ATP/CFI

dvatp.com has been updated!

June 22, 2016: Donations to dvatp.com are now processed via Stripe. Like this site? It's easier than ever to show your appreciation.

Installing Odoo V9 on FreeBSD 10.2 with Nginx Proxy

(Image: Odoo V9 Apps Desktop)

Introduction

This is a technical document intended for people with Linux / BSD / Unix administration experience and is primarily concerned with the following:

If you're a business owner trying to decide whether Odoo would be a good fit for you I recommend you look elsewhere for advice, but I will comment that most business people will be properly advised to buy an Odoo subscription and have the application hosted in the cloud as that eliminates at least some of the administrative burden.

If it is against your company's policy to host financial data in the cloud (as it is my own) then installing locally is your only choice, and you should include the cost of hardware and consultants needed to install and maintain the system, and optionally the cost of an Enterprise License ($250/user/yr) if required.

Do not make the error in judgment that many businesses are inclined to make in their quest to save money and assume that getting the application source code for free will significantly reduce the expenses associated with the installation, routine maintenance, and training required to deploy the application.

References

These documents were instrumental in understanding Odoo and the creation of this article. While it is not strictly necessary to read them before you read this article you may find them helpful.

Why FreeBSD?

I'm sure you're wondering why I chose FreeBSD rather than Linux, particularly given that most people seem to install it on Linux, the Odoo developers provide documentation for Linux, and I have over 20 years of development experience on Linux and Unix.

Well, that's a long story, but it comes down to the fact that I (and many others) do not like the decisions Linux distribution maintainers are making these days. Notably, RedHat and Canonical, (i.e. Ubuntu) are largely abandoning the standards and practices that made Linux successful.

They are, of course, entitled to do what they want to their OS, and I'm entitled to find something like FreeBSD which is deployed on a longer time horizon -- a principle that makes it ideal for mission critical applications, including business administration software.

Configure Default Locale

FreeBSD defaults to the "C" locale. Better to have that set to UTF-8 when running Odoo. Modify /etc/login.conf to add a trailing slash to the last line of the default stanza and then add the charset and lang defintions. See the following diff:

-   :umask=022:
+   :umask=022:\
+   :charset=UTF-8:\
+   :lang=en_US.UTF-8:

LC_COLLATE should,despite these changes, be set to C because some programs still require ASCIIordering to function correctly. That's done by provisioning it manually in the default shell environment as shown in the following diff:

-   :setenv=MAIL=/var/mail/$,BLOCKSIZE=K,FTP_PASSIVE_MODE=YES:\
+   :setenv=MAIL=/var/mail/$,BLOCKSIZE=K,FTP_PASSIVE_MODE=YES,LC_COLLATE=C:\

Note that the FTP_PASSIVE_MODE definition was not present on my 10.2 system and it is not required to add it for Odoo. No one uses FTP these days anyway.

Add Odoo User

Per typical Unix policy, it is best to run applications as a non-root user and I think it's best to have a specific application owned by a specific user as it makes managing the security of the application easier.

I created a BSD regular user called "odoo". The command I used will also create a group name called "odoo" and place the user in that group. I also included this user in the wheel group. This is optional but helpful as it allows the odoo user to become root (with appropriate authentication) via "su -". This is strictly for convenience and does not represent any significant security risk.

The home directory permissions are correct by default so no modification is necessary. Specifically, the home directory should be owned by odoo, group odoo, and mode 755.

Here is the transcript of that setup process:

# adduser --system --shell=/bin/bash odoo
Full name: ODOO User
Uid (Leave empty for
default): 
Login group [odoo]: 
Login group is odoo.
Invite odoo into other groups? []: wheel
Login class [default]: 
Shell (sh csh tcsh bash rbash git-shell nologin) [sh]: bash
Home directory [/home/odoo]: 
Home directory permissions (Leave empty for default):
Use password-based authentication? [yes]: 
Use an empty password? (yes/no) [no]:
Use a random password? (yes/no) [no]:
Enter password: ********* 
Enter password again: ********* 
Lock out the account after creation? [no]: 
Username   : odoo
Password   : *****
Full Name  : ODOO User
Uid        : 1002
Class      :
Groups     : odoo wheel
Home       : /home/odoo
Home Mode  : 
Shell      : /usr/local/bin/bash
Locked     : no
OK? (yes/no): yes
adduser: INFO: Successfully added (odoo) to the user database.

Install PostgreSQL

I originally installed the latest postgres available on FreeBSD (9.4) but through the course of installing the various python dependencies required by Odoo I learned that python 2.7 (required by Odoo) has more than a few hard references to postgres 9.3...at least in the FreeBSD package ecosystem.

While I ultimately worked around those issues by using pip to install the python dependencies I wound up keeping 9.3 rather than reinstalling 9.4. Odoo does not mention a specific Postgres version requirement for their application but based on my experience in other projects anything 9.1 or later is probably good enough.

To install Postgres 9.3 install the packages:

# pkg install postgresql93-server-9.3.9_1 
# pkg install postgresql93-client-9.3.9
# pkg install postgresql-libpqxx-4.0.1_1

Incidentally when installing any package on FreeBSD via pkg the specific package version number, i.e. "-9.3.9_1", may be eliminated but my convention in documents of this type is to show the specific version installed for traceability purposes.

As as typical on FreeBSD, most packages that provide daemons disable them by default. To enable the Postgres server at startup I added this line to /etc/rc.conf:

postgresql_enable="YES"

The postgresql configuration file (postgresql.conf) is not created during the package installation process. I created and initialize it by calling this command:

/usr/local/etc/rc.d/postgresql initdb

After this command returns the configuration file will be located here:

/usr/local/pgsql/data/postgresql.conf

The database configuration defaults to listening to requests only on the local server. This is a security feature and is compatible with installations that run both postgres and the Odoo application on the same system. If the server and application are on different systems the database configuration file will need to be tweaked to listen for connections on hosts other than localhost (127.0.0.1). Search the postgres documentation for instructions on this topic.

According to the Odoo documentation the application will refuse to login to the postgres database using the default database user (called "postgres" on Linux and "pgsql" on FreeBSD) that is created during the installation of the postgres server. That's a good thing, but it does necessitate the creation of another postgres user that can "own" the database used by the Odoo application.

I created an account called "odoo" as follows:

# su - pgsql
$ createuser -s odoo

Note 1: We are initially root here, then we "su" to become the pgsql user, at which point we can execute the createuser command.

Note 2: This user is considered a postgres "superuser", hence we use the -s option with the createuser command.

At this point the odoo user is created in the database but it lacks a password. I assigned a password by logging into the default database ("template1") and executing a SQL command that adds the password to the username just created:

$ psql template1
template1=# ALTER ROLE odoo WITH PASSWORD 'odooerp1!';
template1=# \q 

Note 3: The psql command is executed as the pgsql user.

Install Odoo Source Code with Git

Git is required to pull Odoo from the source tree on github. I installed it with:

# pkg install git

Change directory into the place where you want the code to live. This is /opt/odoo for most people, but I wanted the code to live in the odoo user's home directory. In this sense I treat the odoo user as a "regular" user as opposed to a "system" user with a UID < 1000 and no shell. Some guides suggested creating this user as a system user, but I saw no point to that.

# cd /home/odoo/
# git clone https://www.github.com/odoo/odoo

Warning: This will pull ALL branches and commits within those branchesof the Odoo application and will require ~1.5G (as of the Version 9 release). Most people will find this unnecessary but I did it because I wanted the full history of the application.

The storage requirements as well as the time required to download the code may be reduced by downloading only a specific branch or only the most recent commits on that branch.

To get the entire history of a specific branch use the --branch option:

# git clone --branch 9.0 https://www.github.com/odoo/odoo

To get only te most recent commits of the branch (9.0 in my case), use the --depth option:

# git clone --depth
1 --branch 9.0 https://www.github.com/odoo/odoo

Note that this will create the path:

/home/odoo/odoo

Install Python and Related Dependencies

Odoo is written in the Python language so first of all it needs python (the interpreter) installed. Specifically, as of Version 9 it requires Python Version 2.7.9 or later within the 2.x branch.

Not that FreeBSD currently offers several versions in the 3.x realm but the Odoo documentation specifically warns against using 3.x., indicating only that it is "incompatible".

As it turns out, many other packages required for a basic FreeBSD system depend on Python 2.7 so it's likely already installed even on a "virgin" FreeBSD system. You can verify this with:

# pkg info | grep python

If Python is installed the output of this command will probably look something like this:

python-2.7_2,2   The "meta-port" for the default version of Python interpreter
python2-2_3      The "meta-port" for version 2 of the Python interpreter
python27-2.7.10_1  Interpreted object-oriented programming language

If for some reason python is not installed it may be installed using:

# pkg install python27-2.7.10_1

In PHP, composer is used to install prepared PHP packages from a variety of sources. Python has a similar package tool called "pip". This can and should be used to install any required python packages, including those required by Odoo.

To get pip on the system in the first place it is best installed from a FreeBSD package:

# pkg install py27-pip-7.0.3

This version will likely be a few revs behind the official pip version. I generally advise against overwriting files provided by a FreeBSD package but the latest version of pip can be obtained from the Python ecosystem by doing:

# pip install --upgrade pip

Note:

Resist the urge to install python package dependencies via the FreeBSD pkgtool.

On my initial installation I obtained a list of required python packages from a script someone built for ubuntu. I translated those package names to FreeBSD and attempted to install them via the 'pkg' command. That ultimately did not work as several packages on the list conflicted with one another and even forcefully removed the version of postgres I had installed at the time (9.4).

The second time around I did it the way Odoo recommends and used pip to install the packages listed in the requirements.txt file located at the top of the source tree. I had one problem with this method as well: python-ldap refused to build due to missing header files that I ultimately traced to openldap2. I found at least one of the headers in /usr/local/include, so I knew the problem was not the result of a missing package. I ultimately fixed the problem by removing the python-ldap package from the requirements.txt file, fed pip that file again to get all packages OTHER than python-ldap installed, and then manually used pip to install python-ldap, which then worked. Not sure why. However irrelevant to the context I'll just throw in a "Python Sucks" comment here because I can.

To install the Python dependencies via pip, do:

# pip install setuptools
# pip install python-ldap

Then cd into the Odoo source tree downloaded via git earlier:

# cd /home/odoo/odoo
# pip install -r requirements.txt

Install Other Dependencies

Some Odoo modules can create reports in Libreoffice compatible formats. So we need libreoffice (and its libraries) installed.

# pkg install libreoffice

The following are required to enable various features in certain modules. Rather than figure out what module requires which dependency it's just easier to install them all. Disk space is cheap.

# pkg install graphviz-2.38.0_9
# pkg install ghostscript9-base-9.06_11
# pkg install poppler-utils-0.34.0 
# pkg install antiword-0.37_4
# pkg install curl-7.44.0
# pkg install wkhtmltopdf-0.12.2.1_1

Install NodeJS and less complier

Install the node package manager:

# pkg install npm-2.14.4

--OR--

# curl -L https://npmjs.org/install.sh

Security warning:

I know everyone trusts the node devs and thousands install node by executing the node install.sh script every day but I'm not a fan of executing unknown / unsigned code directly from the net, especially as root, which explains why the above curl command doesn't do anything but download the file. If you choose to download the script rather than install the FreeBSD package equivalent I strongly advise you examine the script for malware before you execute it. If you don't know how to do that, you should probably install the FreeBSD package.

Using the newly-installed node package manager I installed less and a needed plugin:

# npm install less
# npm install less-plugin-clean-css

This resulted in npm and the other node modules being installed in:

/usr/local/lib/node_modules

Application Setup

I first created a log directory for the application server:

# mkdir /var/log/odoo
# touch /var/log/odoo/server.log
# chown odoo:odoo /var/log/odoo

Then I created an Odoo server configuration file and installed it as follows:

/usr/local/etc/odoo-server.conf

Change permissions of the configuration file as follows:

# chown odoo:odoo /etc/odoo-server.conf
# chmod 640 /etc/odoo-server.conf

The goal here is to make it owned by the odoo user and not visible to users other than root, odoo, and anyone in the wheel group that can become root because the Master Password and Database passwords are in clear text. Both have the power to destroy the database so they must be protected from prying eyes.

I then tested the Odoo application server by becoming the odoo user and then launching the server manually on the command line:

# su - odoo
$ cd odoo
$ ./openerp-server &

The output of the application should indicate the application is running and waiting for connections on a specific port as shown in the following capture:

[odoo@ren ~/odoo]$
./openerp-server &
[1] 8331
[odoo@ren ~/odoo]$
2015-10-30 01:23:33,693 8331 INFO ? openerp: OpenERP version 9.0
2015-10-30 01:23:33,693 8331 INFO ? openerp: addons paths:
['/home/odoo/.local/share/Odoo/addons/9.0',
u'/usr/home/odoo/odoo/openerp/addons', u'/usr/home/odoo/odoo/addons']
2015-10-30 01:23:33,693 8331 INFO ? openerp: database: default@default:default
2015-10-30 01:23:34,063 8331 INFO ? openerp.service.server: HTTP service
(werkzeug) running on 0.0.0.0:8069

The easist easy to determine at a later time whether the application is running or not is to search the process list:

$ ps -ax | grep openerp 

This should reveal output similar to the following:

  8331  1  I  0:01.32 python ./openerp-server (python2.7)

Prior to the implementation of automated startup and shutdown (via init script, below) the application must be terminated manually (especially prior to a system reboot) by sending it a SIGTERM using the process ID (PID) discovered with the above method. Assuming the PID is 8331 as in the above example:

# kill -15 8331

A proper termination signal like this will ultimately cause the server to shutdown, though based on my experience it may take some time (10+ seconds). As with most processes, don't ever use "kill -9" to terminate Odoo or at this point in the configuration process reboot the system without sending it the proper termination signal as that will not allow the application the time required to cleanup. Otherwise corruption of the database is a possibility, however remote given the transactional nature of most modern database implementations.

Odoo Server Configuration

To begin application configuration, I made sure the Odoo application server was running, launched a browser on the host running Odoo and then browsed to:

  http://localhost:8069

This will result in the Odoo application screen titled "Create a New Database". On this page I entered the "Master Password" and other fields.

In full disclosure I initially I received "Internal Server Error" warnings rather than the initial setup page. I looked in the Odoo application logfile (/var/log/odoo...) and realized it was due to an error in the path to the addons directory specified in the Odoo server configuration file.

To fix that I stopped the server (with kill -15), changed the definition to:

addons_path = /home/odoo/odoo/addons

and then restarted the server. Reloading the page I was then greeted with the initial setup page.

After filling out that initial display Odoo should open the standard Odoo desktop with the application modules page displayed. From here required application modules can be installed. For example, my primary reason for using Odoo was their MRP (Manufacturing) module, which would allow me to track the raw materials and other parts used to build my products. Note that some modules will install other modules as dependencies. For example, the MRP module requires the Inventory module, so it will be installed automatically.

A note about the Odoo login dialog:

Logging out of the application I noticed that the login dialog has two fields labeled "email" and "password". It turns out that the email field will accept either a username or an email address, and the requirement is based on whether the user has been assigned an email address (via Users->Edit). If an email address has been assigned it must be provided in lieu of the username in the email field. I'm not sure I agree with this convention, as some modules require an email address yet typing an email address every time to login gets old quickly. I much prefer short usernames, like "doug". This policy should in my opinion be left to the discretion of the administrator but if there is a way to configure that I haven't found it yet.

As I had not, at this point, entered an email address for the admin user I was able to login as the admin user for initial setup by entering "admin" into the email field and then entering the password set for this user via ALTER ROLE above.

Odoo Server Init Script

Once the application is known to start and stop without errors, the startup and shutdown sequence can be automated by creating an init script. Unfortunately at the time of writing no one had documented a compatible FreeBSD install script so I had to build my own init script based on the scripts built for Linux, the Odoo documentation, and a lot of testing.

Once the file is installed here

/usr/local/etc/rc.d/openerp-server
it allows the use of the following commands to start, stop and check the status of the openerp-server process.
# service openerp-server start
# service openerp-server status
# service openerp-server stop

The file was installed in /usr/local/etc rather than /etc because of the age-old Unix convention (unfortunately long since abandoned in Linux) of installing applications and related stuff in /usr/local, which wisely separates user applications from the OS core.

Note that I did not test any other commands, though because of the way the tool is integrated with the init subroutines provided by FreeBSD (/etc/rc.subr) it may advertise other commands are available. Tweaking and testing those is left as an exercise. I did not deem them necessary to my operations.

After a couple of tests I then rebooted the server to verify the openerp-server application process appeared on boot.

Install and Configure Nginx Web Server

By default the Odoo application provides its own HTTP service and responds to connection attempts on a specific port. This is useful only if you intend to access Odoo strictly from a web browser installed and running on that same host. This is too limiting for most installations as it would preclude purchasing, warehousing, etc. from accessing the system over the network, so the solution is to use a traditional webserver to proxy requests from other hosts and send them to the Odoo application server.

Nginx is ideal for this because it's lightweight. Apache would work here too but Nginx requires a smaller memory footprint and is generally faster than Apache in this configuration. Some would also argue Nginx configuration directives and file structure are a lot easier to understand, and after 20 years of building and administering apache based webapps, I tend to agree. Of course, if you're new to Nginx as I was, this would not eliminate the requirement to thoroughly understand all the needed configuration directives.

I installed nginx from the FreeBSD package system via:

# pkg install nginx-1.8.0_3,2

This process automatically created a user and group called "www".

I then created a custom nginx configuration file and installed it here:

/usr/local/etc/nginx/nginx.conf

Among the configuration needed for Odoo this changed the "run as user" to "www" so the server runs as this non-privileged user. Incidentally, I recommend saving the existing file at that location before overwriting it.

The new configuration can be tested using the usual tools:

# service nginx start
# service nginx status
# service nginx stop

Once the configuration is proven the startup of the server during boot can be tested by adding the following to /etc/rc.conf:

nginx_enable="YES"

With Nginx configured and running access to the Odoo application can by launching a browser located on a system OTHER than the host running Odoo and simply browsing to the public IP of the Odoo host:

http://10.10.20.40/

Note that the actual IP address will be the IP assigned to the Odoo host, and that due to the proxy configuration it is not necessary to enter the non-standard port number (8069). The proxy will connect this request on Port 80 to the Odoo application server listening on port 8069.

Several others have Nginx configurations outlining how to implement TLS (https) but I saw no point to that as my system was deployed on a restricted LAN behind a firewall and any remote access to the application would be over VPN. While there would technically be a security benefit to using TLS and then wrapping those encrypted packets in yet another encrypted tunnel (VPN), I didn't see my application as that important and I didn't want to (initially anyway) incur the time and complexity associated with configuration of TLS. I also didn't want to deal with the warnings that all browsers display when using a self-signed certificates. In other words, standard http worked just fine for me.

Notes and Advisories

While this is not a review of Odoo, I think it's important to point out a few things I learned during my initial installation and testing of Odoo V9.

  1. I'll say up front that if you value your sanity and you're new to deploying business-critical applications like Odoo you will want to maintain two servers -- one for production and one for internal development and testing of new releases. For God's sake, don't just update the production server codebase without doing an acceptance test elsewhere. This is the price of deploying an open source application and the source code version in particular. Remember: the community edition comes with no support so you're on your own!

  2. I advise going through the effort of installing Odoo from source as opposed to packages because a source control system (git) makes it a lot easier to control and monitor the state of the Odoo application code to prevent unauthorized changes, move forward to new releases, revert to old releases if necessary, and apply simple patches that may be internally developed or provided by the community outside or in advance of a formal release by the Odoo developers.

  3. As of Odoo V8 it is no longer possible to recover passwords by poking the database as the passwords are stored encrypted (as they should be). The admin user will have to reset a user's password through the GUI. Lose the admin password and you'll have to do a bit of SQL to reset it directly in the psql client.

  4. As of Odoo V9 the "Technical Features" checkbox has been removed from the Rights section of the Users->Edit page. In V8 this was typically enabled to add a bunch of previously hidden menus that were quite helpful during normal use of the application. V9 appears to include most of those "routine" features by default, which probably explains why they removed the Technical Features checkbox.

    However, I learned from a forum post that the "Technical Features" mode or right still exists and can be enabled by going to Help->About and clicking on the link / button "Activate the developer mode". I'll spare you a rant about how moronic it is to put this in the About dialog. Hint: it should be under the settings menu.

    In any case, once developer mode is enabled this indeed activates many menus to which the average user probably shouldn't have access, but are nevertheless important for the administrator during initial configuration and, I imagine, during normal operation to fix various operational goofs on the part of regular users.

  5. A "Manage Databases" link exists on the bottom of the login dialog. Again, I'll spare you a rant on why it doesn't belong there.

    This facility allows anyone with the master password to backup and restore databases. The backup process produces a zip file that is sent to the browser for downloading and storage elsewhere (a wise backup strategy would translate into at least two other places including one offsite).

    A very old (and sobering) adage in the admin community is that no one cares if you can backup; only that you can restore. I tested the restore and it appeared to work though I had to tweak the nginx configuration to allow the upload of files larger than the default of 1MB.

    I strongly advise everyone go through that process at least once before going to production, and then again (on the development server first) with each new update or release to the source code. You don't want to depend on a mission critical application like this only with the knowledge that "hey, someone else told me it would restore!".

  6. If you have more than one tab open to the application logged in as one or more users, sessions can be randomly closed, forcing you to reload the page (F5) and login again. I didn't know the cause initially but it went away after closing the other tabs I had open purely by accident. This is not the way this is supposed to work. A tab is a client and I should be able to have as many clients open as I want, even if each client has logged into the application from the same host as the same username, because the session ID should be unique. I'm not sure why this is not the case.

  7. Creating the init file took me longer than I'm willing to admit because I was new to BSD's init process, and I had problems integrating with the utilities provided in /etc/rc.subr.

    First, the routines do not play nicely when the name of the process contains dashes (openerp-server for example). This is because whoever wrote those routines used a rather arcane syntax for variable assignment I almost never use because I learned long ago it causes problems like this.

    I also discovered that some of the functions simply did not work as documented. This may very well have been due to an "error" on my part but that's not hard to believe given the lack of effective documentation on exactly what global variable names are reserved or expected to be provisioned prior to calling each function. This is of course the problem with shell and other languages that are not object oriented and effectively self-documenting.

    I fixed all of these issues by growing my own start/stop/status functions. This was not ideal but I wasn't about to rewrite rc.subr or change the name of the openerp server process as that would have made future testing and contributions to the project annoying.

    The server process must be run as a non-privileged user for security purposes per Odoo policy. Technically the server WILL run as root but it will complain about it, and rightfully so. The init script I provided enforces this policy and runs the application as the "odoo" user created earlier in the setup process.

    I also discovered the server process will create a process id file if launched with the --pidfile option but it won't remove the file when the process shuts down normally. I address this in the init script I developed by simply deleting the file after I've confirmed that the process has been terminated.

    One thing was painfully clear throughout my development process: the application server takes an unusually long time to shutdown as compared to most daemons I've seen. The time it takes to shutdown is also variable, depending apparently on the number of people logged in at the time, but I noticed it would occasionally take 20-30 seconds even with no one logged in. My custom init file addresses this by using a utility provided by rc.subr to spin waiting for the process to die. Note that this will delay the exit of the script (and hence any system reboot) by the time required to verify the process has exited but at least it won't rip the rug out from under it, potentially corrupting the database.

  8. This may be obvious but I'll mention it anyway -- uninstalling a module will delete all the data and relationships associated with that module from the database. The only way to restore the deleted data would be via a backup. Fortunately the application will warn about this very fact prior to removing the module. I don't think many people will be removing modules from production servers, but it is possible if Odoo doesn't impress in certain areas and that capability is moved to other systems.

  9. While the transition from openerp to Odoo branding has had a positive impact on the overall state of the application and its GUI, which I find surprisingly slick if not a bit slow, there is a dark side to all of this: monetization.

    Rather than embrace open source software the right way (by providing the entire application source code) and then charging people for SAAS / hosting / support, the people running the show have decided to develop options that are only available if you pay them a license fee of $250/user/year. Worse, the options are not modular or a la carte. You bend over for the full $250 or you get nothing. That seems foolish in this day and age and reminiscent of the horribly broken cable TV model.

    The features I noticed that are not available except in the "Enterprise Edition":

    • Bar code scanning
    • Shipping integration (UPS, FedEx, USPS)
    • QIF/QFX manual import
    • Direct bank sync

    I am particularly galled that QIF/QFX is not supported when I have to pay for the bank sync option which is likely worth at lease some reasonable fraction of the money they charge. This has to be a decision made by one of those pointy-haired sales/marketing/finance types, all of whom I request jump off a cliff at their earliest convenience.

    Note that I did not install all available modules so this is likely not a complete list.

  10. I use my web server for multiple applications, and keeping them from clashing with Odoo is more difficult because I can't tell Odoo where I want it to live. Specifically, there does not appear to be a way to change the base url of the Odoo application server such that it lives here:

    http://10.10.20.40:8069/odoo
    

    and makes all requests relative to that URL, including the static "web" data, rather than here:

    http://10.10.20.40:8069/
    

    And yes, I know about web.url.base. I was able to change that by logging in as admin, activating developer mode, and then going to Settings->Parameters but ultimately the value did not survive a restart of the openerp-server application. I have no idea why as that value has to be stored in the database. What are they doing with the new value when I click "Save"?

    And yes, I know about Nginx proxying and its rewrite capability. In fact I tested that. The problem there is while the requests are rewritten successfully, the URLs encapsulated in the responses are not, including stuff like redirects to the login page. Hence, the application does not work.

    That said, I was able to get the proxy to work provided it is provisioned to listen for Odoo requests in the URL root (/) and the /web/* locations.

Conclusion

Deploying Odoo via source on FreeBSD was a several day process but that is because I was blazing a trail and had to write and test some code to integrate it. Deploying on Linux is considerably easier because much of the hard work has been done already. If you are intent on deploying on FreeBSD I hope my work here assists you in your quest.