#76 app path prefix when behind proxy_pass (#91)

Support for running in a sub-path under proxy_pass (Running changedetection.io behind a reverse proxy sub directory) 
More here https://github.com/dgtlmoon/changedetection.io/wiki/Running-changedetection.io-behind-a-reverse-proxy-sub-directory
pull/138/head
dgtlmoon 4 years ago committed by GitHub
parent c0b6233912
commit e6fadc44fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -468,11 +468,12 @@ def changedetection_app(config=None, datastore_o=None):
form.notification_body.data = datastore.data['settings']['application']['notification_body'] form.notification_body.data = datastore.data['settings']['application']['notification_body']
# Password unset is a GET # Password unset is a GET
if request.values.get('removepassword') == 'true': if request.values.get('removepassword') == 'yes':
from pathlib import Path from pathlib import Path
datastore.data['settings']['application']['password'] = False datastore.data['settings']['application']['password'] = False
flash("Password protection removed.", 'notice') flash("Password protection removed.", 'notice')
flask_login.logout_user() flask_login.logout_user()
return redirect(url_for('settings_page'))
if request.method == 'POST' and form.validate(): if request.method == 'POST' and form.validate():
@ -577,7 +578,7 @@ def changedetection_app(config=None, datastore_o=None):
if uuid == 'first': if uuid == 'first':
uuid = list(datastore.data['watching'].keys()).pop() uuid = list(datastore.data['watching'].keys()).pop()
extra_stylesheets = ['/static/styles/diff.css'] extra_stylesheets = [url_for('static_content', group='styles', filename='diff.css')]
try: try:
watch = datastore.data['watching'][uuid] watch = datastore.data['watching'][uuid]
except KeyError: except KeyError:
@ -634,7 +635,7 @@ def changedetection_app(config=None, datastore_o=None):
if uuid == 'first': if uuid == 'first':
uuid = list(datastore.data['watching'].keys()).pop() uuid = list(datastore.data['watching'].keys()).pop()
extra_stylesheets = ['/static/styles/diff.css'] extra_stylesheets = [url_for('static_content', group='styles', filename='diff.css')]
try: try:
watch = datastore.data['watching'][uuid] watch = datastore.data['watching'][uuid]

@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Self hosted website change detection."> <meta name="description" content="Self hosted website change detection.">
<title>Change Detection{{extra_title}}</title> <title>Change Detection{{extra_title}}</title>
<link rel="stylesheet" href="/static/styles/pure-min.css"> <link rel="stylesheet" href="{{url_for('static_content', group='styles', filename='pure-min.css')}}">
<link rel="stylesheet" href="/static/styles/styles.css?ver=1000"> <link rel="stylesheet" href="{{url_for('static_content', group='styles', filename='styles.css')}}">
{% if extra_stylesheets %} {% if extra_stylesheets %}
{% for m in extra_stylesheets %} {% for m in extra_stylesheets %}
<link rel="stylesheet" href="{{ m }}?ver=1000"> <link rel="stylesheet" href="{{ m }}?ver=1000">
@ -21,7 +21,7 @@
{% if has_password and not current_user.is_authenticated %} {% if has_password and not current_user.is_authenticated %}
<a class="pure-menu-heading" href="https://github.com/dgtlmoon/changedetection.io" rel="noopener"><strong>Change</strong>Detection.io</a> <a class="pure-menu-heading" href="https://github.com/dgtlmoon/changedetection.io" rel="noopener"><strong>Change</strong>Detection.io</a>
{% else %} {% else %}
<a class="pure-menu-heading" href="/"><strong>Change</strong>Detection.io</a> <a class="pure-menu-heading" href="{{url_for('index')}}"><strong>Change</strong>Detection.io</a>
{% endif %} {% endif %}
{% if current_diff_url %} {% if current_diff_url %}
<a class=current-diff-url href="{{ current_diff_url }}"><span style="max-width: 30%; overflow: hidden;">{{ current_diff_url }}</span></a> <a class=current-diff-url href="{{ current_diff_url }}"><span style="max-width: 30%; overflow: hidden;">{{ current_diff_url }}</span></a>
@ -35,13 +35,13 @@
{% if current_user.is_authenticated or not has_password %} {% if current_user.is_authenticated or not has_password %}
{% if not current_diff_url %} {% if not current_diff_url %}
<li class="pure-menu-item"> <li class="pure-menu-item">
<a href="/backup" class="pure-menu-link">BACKUP</a> <a href="{{ url_for('get_backup')}}" class="pure-menu-link">BACKUP</a>
</li> </li>
<li class="pure-menu-item"> <li class="pure-menu-item">
<a href="/import" class="pure-menu-link">IMPORT</a> <a href="{{ url_for('import_page')}}" class="pure-menu-link">IMPORT</a>
</li> </li>
<li class="pure-menu-item"> <li class="pure-menu-item">
<a href="/settings" class="pure-menu-link">SETTINGS</a> <a href="{{ url_for('settings_page')}}" class="pure-menu-link">SETTINGS</a>
</li> </li>
{% else %} {% else %}
<li class="pure-menu-item"> <li class="pure-menu-item">
@ -55,7 +55,7 @@
{% endif %} {% endif %}
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<li class="pure-menu-item"><a href="/logout" class="pure-menu-link">LOG OUT</a></li> <li class="pure-menu-item"><a href="{{url_for('logout')}}" class="pure-menu-link">LOG OUT</a></li>
{% endif %} {% endif %}
<li class="pure-menu-item"><a class="github-link" href="https://github.com/dgtlmoon/changedetection.io"> <li class="pure-menu-item"><a class="github-link" href="https://github.com/dgtlmoon/changedetection.io">
<svg class="octicon octicon-mark-github v-align-middle" height="32" viewBox="0 0 16 16" <svg class="octicon octicon-mark-github v-align-middle" height="32" viewBox="0 0 16 16"

@ -67,14 +67,13 @@ SMTPS - mailtos://user:pass@mail.domain.com?to=receivingAddress@example.com
</div> </div>
<br/> <br/>
<div class="pure-control-group"> <div class="pure-control-group">
<a href="/" class="pure-button button-small button-cancel">Cancel</a> <a href="{{url_for('index')}}" class="pure-button button-small button-cancel">Cancel</a>
<a href="/api/delete?uuid={{uuid}}" <a href="{{url_for('api_delete', uuid=uuid)}}"
class="pure-button button-small button-error ">Delete</a> class="pure-button button-small button-error ">Delete</a>
</div> </div>
</fieldset> </fieldset>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}

@ -26,7 +26,7 @@
</div> </div>
<br/> <br/>
<div class="pure-control-group"> <div class="pure-control-group">
<a href="/" class="pure-button button-small button-cancel">Cancel</a> <a href="{{url_for('index')}}" class="pure-button button-small button-cancel">Cancel</a>
</div> </div>
</fieldset> </fieldset>
</form> </form>

@ -97,11 +97,9 @@ SMTPS - mailtos://user:pass@mail.domain.com?to=receivingAddress@example.com") }}
</div> </div>
<br/> <br/>
<div class="pure-control-group"> <div class="pure-control-group">
<a href="/" class="pure-button button-small button-cancel">Back</a> <a href="{{url_for('index')}}" class="pure-button button-small button-cancel">Back</a>
<a href="/scrub" class="pure-button button-small button-cancel">Delete History Snapshot Data</a> <a href="{{url_for('scrub_page')}}" class="pure-button button-small button-cancel">Delete History Snapshot Data</a>
</div> </div>
</fieldset> </fieldset>
</form> </form>

@ -15,10 +15,10 @@
<!-- user/pass r = requests.get('https://api.github.com/user', auth=('user', 'pass')) --> <!-- user/pass r = requests.get('https://api.github.com/user', auth=('user', 'pass')) -->
</form> </form>
<div> <div>
<a href="/" class="pure-button button-tag {{'active' if not active_tag }}">All</a> <a href="{{url_for('index')}}" class="pure-button button-tag {{'active' if not active_tag }}">All</a>
{% for tag in tags %} {% for tag in tags %}
{% if tag != "" %} {% if tag != "" %}
<a href="/?tag={{ tag}}" class="pure-button button-tag {{'active' if active_tag == tag }}">{{ tag }}</a> <a href="{{url_for('index', tag=tag) }}" class="pure-button button-tag {{'active' if active_tag == tag }}">{{ tag }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
@ -45,7 +45,8 @@
{% if watch.paused is defined and watch.paused != False %}paused{% endif %} {% if watch.paused is defined and watch.paused != False %}paused{% endif %}
{% if watch.newest_history_key| int > watch.last_viewed| int %}unviewed{% endif %}"> {% if watch.newest_history_key| int > watch.last_viewed| int %}unviewed{% endif %}">
<td class="inline">{{ loop.index }}</td> <td class="inline">{{ loop.index }}</td>
<td class="inline paused-state state-{{watch.paused}}"><a href="/?pause={{ watch.uuid}}{% if active_tag %}&tag={{active_tag}}{% endif %}"><img src="/static/images/pause.svg" alt="Pause"/></a></td> <td class="inline paused-state state-{{watch.paused}}"><a href="{{url_for('index', pause=watch.uuid, tag=active_tag)}}"><img src="{{url_for('static_content', group='images', filename='pause.svg')}}" alt="Pause"/></a></td>
<td class="title-col inline">{{watch.title if watch.title is not none and watch.title|length > 0 else watch.url}} <td class="title-col inline">{{watch.title if watch.title is not none and watch.title|length > 0 else watch.url}}
<a class="external" target="_blank" rel="noopener" href="{{ watch.url }}"></a> <a class="external" target="_blank" rel="noopener" href="{{ watch.url }}"></a>
{% if watch.last_error is defined and watch.last_error != False %} {% if watch.last_error is defined and watch.last_error != False %}
@ -63,14 +64,14 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
<a href="/api/checknow?uuid={{ watch.uuid}}{% if request.args.get('tag') %}&tag={{request.args.get('tag')}}{% endif %}" <a href="{{ url_for('api_watch_checknow', uuid=watch.uuid, tag=request.args.get('tag')) }}"
class="pure-button button-small pure-button-primary">Recheck</a> class="pure-button button-small pure-button-primary">Recheck</a>
<a href="/edit/{{ watch.uuid}}" class="pure-button button-small pure-button-primary">Edit</a> <a href="{{ url_for('edit_page', uuid=watch.uuid)}}" class="pure-button button-small pure-button-primary">Edit</a>
{% if watch.history|length >= 2 %} {% if watch.history|length >= 2 %}
<a href="/diff/{{ watch.uuid}}" target="{{watch.uuid}}" class="pure-button button-small pure-button-primary">Diff</a> <a href="{{ url_for('diff_history_page', uuid=watch.uuid) }}" target="{{watch.uuid}}" class="pure-button button-small pure-button-primary">Diff</a>
{% else %} {% else %}
{% if watch.history|length == 1 %} {% if watch.history|length == 1 %}
<a href="/preview/{{ watch.uuid}}" target="{{watch.uuid}}" class="pure-button button-small pure-button-primary">Preview</a> <a href="{{ url_for('preview_page', uuid=watch.uuid)}}" target="{{watch.uuid}}" class="pure-button button-small pure-button-primary">Preview</a>
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
@ -81,15 +82,15 @@
<ul id="post-list-buttons"> <ul id="post-list-buttons">
{% if has_unviewed %} {% if has_unviewed %}
<li> <li>
<a href="/api/mark-all-viewed" class="pure-button button-tag ">Mark all viewed</a> <a href="{{url_for('mark_all_viewed', tag=request.args.get('tag')) }}" class="pure-button button-tag ">Mark all viewed</a>
</li> </li>
{% endif %} {% endif %}
<li> <li>
<a href="/api/checknow{% if active_tag%}?tag={{active_tag}}{%endif%}" class="pure-button button-tag ">Recheck <a href="{{ url_for('api_watch_checknow', tag=active_tag) }}" class="pure-button button-tag ">Recheck
all {% if active_tag%}in "{{active_tag}}"{%endif%}</a> all {% if active_tag%}in "{{active_tag}}"{%endif%}</a>
</li> </li>
<li> <li>
<a href="{{ url_for('index', tag=active_tag , rss=true)}}"><img id="feed-icon" src="/static/images/Generic_Feed-icon.svg" height="15px"></a> <a href="{{ url_for('index', tag=active_tag , rss=true)}}"><img id="feed-icon" src="{{url_for('static_content', group='images', filename='Generic_Feed-icon.svg')}}" height="15px"></a>
</li> </li>
</ul> </ul>
</div> </div>

@ -46,7 +46,7 @@ def test_check_access_control(app, client):
assert b"LOG OUT" in res.data assert b"LOG OUT" in res.data
# Now remove the password so other tests function, @todo this should happen before each test automatically # Now remove the password so other tests function, @todo this should happen before each test automatically
res = c.get(url_for("settings_page", removepassword="true"), res = c.get(url_for("settings_page", removepassword="yes"),
follow_redirects=True) follow_redirects=True)
assert b"Password protection removed." in res.data assert b"Password protection removed." in res.data
@ -93,7 +93,7 @@ def test_check_access_no_remote_access_to_remove_password(app, client):
assert b"Password protection enabled." in res.data assert b"Password protection enabled." in res.data
assert b"Login" in res.data assert b"Login" in res.data
res = c.get(url_for("settings_page", removepassword="true"), res = c.get(url_for("settings_page", removepassword="yes"),
follow_redirects=True) follow_redirects=True)
assert b"Password protection removed." not in res.data assert b"Password protection removed." not in res.data

@ -65,6 +65,18 @@ def main(argv):
has_password=datastore.data['settings']['application']['password'] != False has_password=datastore.data['settings']['application']['password'] != False
) )
# Proxy sub-directory support
# Set environment var USE_X_SETTINGS=1 on this script
# And then in your proxy_pass settings
#
# proxy_set_header Host "localhost";
# proxy_set_header X-Forwarded-Prefix /app;
if os.getenv('USE_X_SETTINGS'):
print ("USE_X_SETTINGS is ENABLED\n")
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_prefix=1, x_host=1)
if ssl_mode: if ssl_mode:
# @todo finalise SSL config, but this should get you in the right direction if you need it. # @todo finalise SSL config, but this should get you in the right direction if you need it.
eventlet.wsgi.server(eventlet.wrap_ssl(eventlet.listen(('', port)), eventlet.wsgi.server(eventlet.wrap_ssl(eventlet.listen(('', port)),

@ -17,8 +17,14 @@ services:
# Base URL of your changedetection.io install (Added to notification alert # Base URL of your changedetection.io install (Added to notification alert
# - BASE_URL="https://mysite.com" # - BASE_URL="https://mysite.com"
# Respect proxy_pass type settings, `proxy_set_header Host "localhost";` and `proxy_set_header X-Forwarded-Prefix /app;`
# More here https://github.com/dgtlmoon/changedetection.io/wiki/Running-changedetection.io-behind-a-reverse-proxy-sub-directory
# - USE_X_SETTINGS=1
# Comment out ports: when using behind a reverse proxy , enable networks: etc.
ports: ports:
- 5000:5000 - 5000:5000
restart: always restart: always
volumes: volumes:

Loading…
Cancel
Save