Q:

Get user's home folder under Python process started by supervisord

I would like to store login secrets in a file from the current user's account in a Django configuration. I'm using the recommended portable way to get the home folder, as in:
os.path.expanduser("~")

This worked in all environments, both locally and both when started with gunicorn -D config.wsgi on a server.
My problem is however that I introduced supervisord to control the gunicorn process and now this function doesn't work, it simply returns /.
This is the relevant section of supervisord.conf
[program:kek_django]
command=.../venv/bin/gunicorn config.wsgi
directory=.../django
user=testuser

Under this environmet, os.path.expanduser("~") becomes /.
Can you tell me either how to fix this problem either by fixing the environment or the function used to detect the home directory?
note: OS is FreeBSD 10, if that is relevant
update: os.environ reports the following under the running process:
'SUPERVISOR_SERVER_URL': 'unix:///var/run/supervisor/supervisor.sock',
'RC_PID': '84177',
'SERVER_SOFTWARE': 'gunicorn/19.3.0',
'SUPERVISOR_ENABLED': '1', 
'SUPERVISOR_PROCESS_NAME': 'test_django',
'PWD': '/',
'DJANGO_SETTINGS_MODULE': 'config.settings.production', 
'SUPERVISOR_GROUP_NAME': 'test_django', 
'PATH': '/sbin:/bin:/usr/sbin:/usr/bin',
'HOME': '/'

A:

As supervisord's docs for Subprocess Environment say:

No shell is executed by supervisord when it runs a subprocess, so environment variables such as USER, PATH, HOME, SHELL, LOGNAME, etc. are not changed from their defaults or otherwise reassigned. This is particularly important to note when you are running a program from a supervisord run as root with a user= stanza in the configuration. Unlike cron, supervisord does not attempt to divine and override “fundamental” environment variables like USER, PATH, HOME, and LOGNAME when it performs a setuid to the user defined within the user= program config option. If you need to set environment variables for a particular program that might otherwise be set by a shell invocation for a particular user, you must do it explicitly within the environment= program config option. An example of setting these enviroment variables is as below.

[program:apache2]
command=/home/chrism/bin/httpd -c "ErrorLog /dev/stdout" -DFOREGROUND
user=chrism
environment=HOME="/home/chrism",USER="chrism"

So, that's the actual fix. (If you construct the supervisord.conf file dynamically and need to know how to look those values up dynamically, I can explain that, but it's pretty easy, and I don't think you need it anyway.)
[program:kek_django]
command=.../venv/bin/gunicorn config.wsgi
directory=.../django
user=testuser
environment=HOME="/home/testuser"

If this doesn't make sense to you, consider:
If you're running supervisord as root, it doesn't have testuser's HOME or anything else. And all it does is setuid(testuser), which just changes its user ID; it doesn't give the shell, or any other part of the system, any opportunity to set up the variables for testuser. Most similar tools have workarounds to fake it, following in the well-worn footsteps of how cron works, but supervisord intentionally chose not to do that.

Alternatively, as the docs for expanduser say:

On Unix, an initial ~ is replaced by the environment variable HOME if it is set; otherwise the current user’s home directory is looked up in the password directory through the built-in module pwd. An initial ~user is looked up directly in the password directory.

And a quick look at the source shows that it does this in the most obvious way possible.
So there are three obvious workarounds from within your code:

Use ~testuser instead of ~ (and you can even generate that programmatically from the username if you want).
Write your own expanduser function that just does the pwd.getpwuid(os.getuid()).pw_dir without checking for HOME.
Manually set HOME to pwd.getpwuid(os.getuid()).pw_dir at startup if it's /.

