diff --git a/.env.sample b/.env.sample
index 32143e6..3ff8bb7 100644
--- a/.env.sample
+++ b/.env.sample
@@ -4,6 +4,9 @@ LD_CONTAINER_NAME=linkding
LD_HOST_PORT=9090
# Directory on the host system that should be mounted as data dir into the Docker container
LD_HOST_DATA_DIR=./data
+# Can be used to run linkding under a context path, for example: linkding/
+# Must end with a slash `/`
+LD_CONTEXT_PATH=
# Option to disable background tasks
LD_DISABLE_BACKGROUND_TASKS=False
diff --git a/bookmarks/templates/bookmarks/layout.html b/bookmarks/templates/bookmarks/layout.html
index 16147e9..f2be882 100644
--- a/bookmarks/templates/bookmarks/layout.html
+++ b/bookmarks/templates/bookmarks/layout.html
@@ -44,7 +44,7 @@
{% endif %}
-
+
linkding
diff --git a/bookmarks/tests/test_context_path.py b/bookmarks/tests/test_context_path.py
new file mode 100644
index 0000000..98c0c45
--- /dev/null
+++ b/bookmarks/tests/test_context_path.py
@@ -0,0 +1,53 @@
+import importlib
+
+from django.test import TestCase, override_settings
+from django.urls import reverse
+
+
+class MockUrlConf:
+ def __init__(self, module):
+ self.urlpatterns = module.urlpatterns
+
+
+class ContextPathTestCase(TestCase):
+
+ def setUp(self):
+ self.siteroot_urls = importlib.import_module('siteroot.urls')
+
+ @override_settings(LD_CONTEXT_PATH=None)
+ def tearDown(self):
+ importlib.reload(self.siteroot_urls)
+
+ @override_settings(LD_CONTEXT_PATH='linkding/')
+ def test_route_with_context_path(self):
+ module = importlib.reload(self.siteroot_urls)
+ # pass mock config instead of actual module to prevent caching the
+ # url config in django.urls.reverse
+ urlconf = MockUrlConf(module)
+ test_cases = [
+ ('bookmarks:index', '/linkding/bookmarks'),
+ ('bookmarks:bookmark-list', '/linkding/api/bookmarks/'),
+ ('login', '/linkding/login/'),
+ ('admin:bookmarks_bookmark_changelist', '/linkding/admin/bookmarks/bookmark/'),
+ ]
+
+ for url_name, expected_url in test_cases:
+ url = reverse(url_name, urlconf=urlconf)
+ self.assertEqual(expected_url, url)
+
+ @override_settings(LD_CONTEXT_PATH='')
+ def test_route_without_context_path(self):
+ module = importlib.reload(self.siteroot_urls)
+ # pass mock config instead of actual module to prevent caching the
+ # url config in django.urls.reverse
+ urlconf = MockUrlConf(module)
+ test_cases = [
+ ('bookmarks:index', '/bookmarks'),
+ ('bookmarks:bookmark-list', '/api/bookmarks/'),
+ ('login', '/login/'),
+ ('admin:bookmarks_bookmark_changelist', '/admin/bookmarks/bookmark/'),
+ ]
+
+ for url_name, expected_url in test_cases:
+ url = reverse(url_name, urlconf=urlconf)
+ self.assertEqual(expected_url, url)
\ No newline at end of file
diff --git a/bookmarks/views/settings.py b/bookmarks/views/settings.py
index c2ef3b8..7ebfade 100644
--- a/bookmarks/views/settings.py
+++ b/bookmarks/views/settings.py
@@ -73,7 +73,7 @@ def get_ttl_hash(seconds=3600):
@login_required
def integrations(request):
- application_url = request.build_absolute_uri("/bookmarks/new")
+ application_url = request.build_absolute_uri(reverse('bookmarks:new'))
api_token = Token.objects.get_or_create(user=request.user)[0]
feed_token = FeedToken.objects.get_or_create(user=request.user)[0]
all_feed_url = request.build_absolute_uri(reverse('bookmarks:feeds.all', args=[feed_token.key]))
diff --git a/docs/Options.md b/docs/Options.md
index d0173db..9a67c1e 100644
--- a/docs/Options.md
+++ b/docs/Options.md
@@ -50,4 +50,11 @@ Configures the request timeout in the uwsgi application server. This can be usef
Values: Valid port number | Default = `9090`
-Allows to set a custom port for the UWSGI server running in the container. While Docker containers have their own IP address namespace and port collisions are impossible to achieve, there are other container solutions that share one. Podman, for example, runs all containers in a pod under one namespace, which results in every port only being allowed to be assigned once. This option allows to set a custom port in order to avoid collisions with other containers.
\ No newline at end of file
+Allows to set a custom port for the UWSGI server running in the container. While Docker containers have their own IP address namespace and port collisions are impossible to achieve, there are other container solutions that share one. Podman, for example, runs all containers in a pod under one namespace, which results in every port only being allowed to be assigned once. This option allows to set a custom port in order to avoid collisions with other containers.
+
+### `LD_CONTEXT_PATH`
+
+Values: `String` | Default = None
+
+Allows configuring the context path of the website. Useful for setting up Nginx reverse proxy.
+The context path must end with a slash. For example: `linkding/`
\ No newline at end of file
diff --git a/siteroot/settings/base.py b/siteroot/settings/base.py
index a7da61a..6a8124e 100644
--- a/siteroot/settings/base.py
+++ b/siteroot/settings/base.py
@@ -107,9 +107,12 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
-LOGIN_URL = '/login'
-LOGIN_REDIRECT_URL = '/bookmarks'
-LOGOUT_REDIRECT_URL = '/login'
+# Website context path.
+LD_CONTEXT_PATH = os.getenv('LD_CONTEXT_PATH', '')
+
+LOGIN_URL = '/' + LD_CONTEXT_PATH + 'login'
+LOGIN_REDIRECT_URL = '/' + LD_CONTEXT_PATH + 'bookmarks'
+LOGOUT_REDIRECT_URL = '/' + LD_CONTEXT_PATH + 'login'
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
@@ -127,7 +130,7 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
-STATIC_URL = '/static/'
+STATIC_URL = '/' + LD_CONTEXT_PATH + 'static/'
# Collect static files in static folder
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
diff --git a/siteroot/urls.py b/siteroot/urls.py
index 2e1a8dd..689bf8d 100644
--- a/siteroot/urls.py
+++ b/siteroot/urls.py
@@ -13,6 +13,7 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
+from django.conf import settings
from django.contrib.auth import views as auth_views
from django.urls import path, include
@@ -30,6 +31,9 @@ urlpatterns = [
path('', include('bookmarks.urls')),
]
+if settings.LD_CONTEXT_PATH:
+ urlpatterns = [path(settings.LD_CONTEXT_PATH, include(urlpatterns))]
+
if DEBUG:
import debug_toolbar
diff --git a/uwsgi.ini b/uwsgi.ini
index 73d1205..ba0d3cd 100644
--- a/uwsgi.ini
+++ b/uwsgi.ini
@@ -12,6 +12,10 @@ uid = www-data
gid = www-data
buffer-size = 8192
+if-env = LD_CONTEXT_PATH
+static-map = /%(_)static=static
+endif =
+
if-env = LD_REQUEST_TIMEOUT
http-timeout = %(_)
socket-timeout = %(_)