Reputation: 43
I use MKdocs to create a very sensitive documentation, and I want it to be securely available online.
My project is:
(this way there is no persistent clear data on the server)
Documentation gets edited only locally, encrypted and rsynced to server.
Now, I'm blocked on serving site on flask step, because the MKdocs site is structured like this:
├── 404.html
├── documentation
│ ├── Folder 1
│ │ ├── Sub folder 1
│ │ │ └── index.html
│ │ └── Sub Folder 2
│ │ └── index.html
│ ├── Folder 2
├── css
│ └── ...
├── fonts
│ └── ...
├── img
│ └── ...
├── index.html
├── js
│ └── ...
├── search
│ └── ...
└── ...
I tried to put this content under ./static/site/
then replacing all the local href
and src
links with the flask
{{ url_for('static', filename='site/[original_content]) }}
But as far I can tell it can't work on a static call like
app.send_static_file('site/index.html')
Because there is no replacement of {{ .. }}
content (and I expected this problem, it is a static page).
The question: Is there a way to serve a static site with a "complex" structure like this, with flask?
MORE and not necessary information, spoiler is not part of question!:
As far as I could accept any suggestion on how to do the whole thing differently, now, I would like to achieve it.
Other solution I thought of:
- serving with nginx the static website, on an empty folder, where flask with the previous mechanics would decrypt the volume. I don't know how to detect the end of the nginx session to encrypt again.
- Condense ALL the structure in one single big HTML file. This would be awesome but I lost really too much time searching and trying, but... no luck in my research. (but this would be my referred solution)
Upvotes: 1
Views: 1813
Reputation: 43
I had a lot of problems with your code, I redirected to /bridge/index.html
after decrypt, and anything clicked from there on would begin with /bridge/index.html/resource/p1/p2/etc..
After that I had to reconsider what where p1, p2 and others, the solution I found BASED totally on Dauros solution, that I really like is:
@app.route('/<path:p1>/')
@app.route('/<path:p1>/<path:p2>/')
@app.route('/<path:p1>/<path:p2>/<path:p3>/')
@app.route('/<path:p1>/<path:p2>/<path:p3>/<path:p4>/')
@app.route('/<path:p1>/<path:p2>/<path:p3>/<path:p4>/<path:p5>/')
@app.route('/<path:p1>/<path:p2>/<path:p3>/<path:p4>/<path:p5>/<path:p6>/')
def bridge(p1=None, p2=None, p3=None, p4=None, p5=None, p6=None):
# Permissions checking...
# I'm planning on using basic auth from nginx to even try to show a page
resource = '/'.join([p for p in (p1,p2,p3,p4,p5,p6) if p])
the_file = resource.split('/')[-1]
the_path = '/'.join(resource.split('/')[:-1])
if p1 in ('css', 'fonts', 'img', 'js', 'search'):
return send_from_directory(f'templates/bridge/{the_path}/', the_file)
else:
template = f'bridge/{resource}/index.html'
return render_template(template)
@app.route('/', methods=HTTP_METHODS)
def home():
method = request.method
if method == "GET":
# TO BE CHANGED, checking if volume is already decrypted.
if os.path.exists(f"{BASE_FODLER}/locked"):
# decrypt page
resp = make_response(render_template('decrypt.html'))
return resp
else:
template = 'bridge/index.html'
return render_template(template)
elif method == "POST":
if 'password' in request.form:
decrypt_key = request.form['password']
# decryption omitted
template = 'bridge/index.html'
return render_template(template)
else:
return not_allowed_ans()
else:
return not_allowed_ans()
return SendOk()
As you see, from bottom, template = 'bridge/index.html'
and then return render_template(template)
bring the bridge site on root mystime.com/
this means all the route have to be root.
Any "link" will be then: mysite.com/folder_1/sub_folder_1/index.html
After that I made it simple for me to add pX in future (would be nice a single route with dynamic PXs, BUT, this is far enough for a static documentation!)
Upvotes: 2
Reputation: 10502
We can consider MkDocs's rendered HTML pages as simple Flask templates. So all we have to do is to create a Flask endpoint that first do the permissions-checking then based on the URL, serves the rendered MkDocs HTML file or a related static file.
Let's call our new endpoint bridge
. First, put everything from MkDocs site
directory to Flask's templates/bridge
directory. My example MkDocs tree looks like this:
$ tree templates/bridge
templates/bridge
├── 404.html
├── css
│ ├── base.css
│ ├── bootstrap.min.css
│ └── font-awesome.min.css
├── dir1
│ ├── sub1
│ │ └── index.html
│ └── sub2
│ └── index.html
├── dir2
│ ├── sub1
│ │ └── index.html
│ └── sub2
│ └── index.html
├── fonts
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ ├── fontawesome-webfont.woff
│ └── fontawesome-webfont.woff2
├── img
│ ├── favicon.ico
│ └── grid.png
├── index.html
├── js
│ ├── base.js
│ ├── bootstrap.min.js
│ └── jquery-1.10.2.min.js
├── search
│ ├── lunr.js
│ ├── main.js
│ ├── search_index.json
│ └── worker.js
├── sitemap.xml
└── sitemap.xml.gz
And our new Flask bridge
endpoint:
from flask import Flask, render_template, send_from_directory
app = Flask(__name__)
@app.route('/bridge/')
@app.route('/bridge/<path:p1>/')
@app.route('/bridge/<path:p1>/<path:p2>/')
@app.route('/bridge/<path:p1>/<path:p2>/<path:p3>/')
def bridge(p1=None, p2=None, p3=None):
# Permissions checking...
# Serve MkDocs's static files requested from CSS files
if p1 == 'css' and p2 in ('img', 'fonts'):
# CSS fix, e.g. /bridge/css/img/example.png -> /bridge/img/example.png
return send_from_directory(f'templates/bridge/{p2}/', p3)
# Serve MkDocs's static files
if p1 in ('css', 'js', 'fonts', 'search'):
return send_from_directory(f'templates/bridge/{p1}/', p2)
# Serve rendered MkDocs HTML files
if p3 != None:
template = f'bridge/{p1}/{p2}/{p3}/index.html'
elif p2 != None:
template = f'bridge/{p1}/{p2}/index.html'
elif p1 != None:
template = f'bridge/{p1}/index.html'
else:
template = 'bridge/index.html'
return render_template(template)
As you see we created a couple of path definitions. Since MkDocs uses relative paths everywhere, the URL path dir1/sub1/
in MkDocs will become https://yoursite.com/bridge/dir1/sub1/
so we can catch them with this URL routing scheme and the URL parts will land into p1
, p2
, p3
path variables that we will use to serve the corresponding content.
There are two types of content: static files (e.g. CSS files or images) and HTML content files. The static files will be in the css
, js
, images
, fonts
, etc. directory, so when p1
equals one of these we use Flask's send_from_directory
function to serve them. Static files referenced from CSS files need to have a specific treatment because MkDocs uses relative paths there as well.
And for the rendered index.html
files we just need to determine the nesting level based on the path and return the selected index.html
file as a normal Flask template. Since MkDocs uses relative URLs we don't have to modify anything in the rendered HTML files, every URL will receive the /bridge/
prefix, so we can serve them with our bridge
endpoint.
You should do the permissions checking at the beginning of bridge
(or as a decorator, depending your solution). You may need to add p4
and/or p5
path variables if you have more deeply nested content, but it's a straightforward extension of my example.
Note: the 404 error page will be also served by Flask.
Upvotes: 3