A more elegant solution to the permissions error
In my last post I demoed a web app written in python for low-power and -bandwidth access to Metlink Real Time Information. The app uses the Flask “microframework” and is hosted on my server with Nginx.
This last component is important. When testing a flask application you generally get a startup message like this:
(rti) [petra@thyme RTI]$ python rti.py
* Serving Flask app "rti" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
I’m not sure that running this particular app on the
dev server behind a reverse proxy would actually cause
any issues, but I decided to heed the warning anyway.
The simplest method according
to the documentation seems to be to use the
flup
fastcgi server, which involves
connecting nginx to a socket file created via a script
along the lines of the following:
#!/usr/bin/python
from flup.server.fcgi import WSGIServer
from yourapplication import app
if __name__ == '__main__':
='/path/to/fcgi.sock').run() WSGIServer(application, bindAddress
The docs assume that this script is run (whether with an init file or within something like GNU screen) by the same user as your web server, however for complicated reasons I don’t want to do that. But if you don’t you get a “permission denied” error in your nginx log when it tries to access the socket file, which of course breaks everything.
This isn’t really mentioned anywhere and is very
frustrating, but I eventually found reference to the
issue in this
blog post. The solution they use is to
chmod 777
the socket file in another
terminal as root, which was an acceptable stopgap for me
at the time.
Doing it better
Still, the sheer awkwardness of this method has taken
up an unreasonable amount of my brainspace. I should
probably write that init file now that I know the thing
works, but how exactly am I supposed to implement that
bodge? Because the fastcgi script doesn’t exit until the
server does I can’t just add
&& chmod fcgi.sock
on the end of
the command, and I need root don’t I?
Some investigation today has resolved the matter cleanly though. First: you don’t need root to set file permissions of your own file. I’m not sure why neither I nor the writer of the original post caught that honestly, but there you go.
Second: there probably is a way to get the
WGSIServer(...).run()
line to fork off and
let you include the chmod
as part of the
one-liner, however this is unnecessary because…
Third: it’s possible to set the permissions of the
socket file in the WGSIServer()
constructor, it’s just not very well documented. To do
it, add a umask
parameter like this:
if __name__ == '__main__':
='/path/to/fcgi.sock', umask=0).run() WSGIServer(application, bindAddress
How this works
Buried in
the flup source code is documentation of the
umask
parameter:
If binding to a UNIX socket, umask may be set to specify what the umask is to be changed to before the socket is created in the filesystem. After the socket is created, the previous umask is restored.
umask
itself has its own man
page but the gist is that that files are created
with the inverse mode
bits to the value given. We want the (octal) value
777, which would be specified in python as
0o777
, and which is inverted with
~0o777
. However we need to trim the
unneeded prefix bits that will have been flipped to on
to get the proper mask, which can be done by
&
ing it with 0o777
, and of
course 0o777 & ~0o777 == 0o000 == 0
.
(If for some reason you needed arbitrary mode bits you
could calculate the mask with
0o777 & ~(mode)
, but I don’t need that
here).
Conclusion
This is a very long way of saying that I wish those extra few characters had been included in the Flask documentation. Even if I managed to figure this out, has everyone else who has had this issue? Probably not: Flask apps can be powerful, but they’re also neat toy projects and they should really be accessible to people who aren’t prepared to dive into the source and into man pages. That’s probably not what they’re using Python for!