pull server upstream at 55d3fb7e (v0.11.0)

master
alex 1 year ago
commit 30189cd485

4
server/.gitignore vendored

@ -14,3 +14,7 @@ __pycache__
/etebase_server_settings.py
/secret.txt
/build
/dist
/*.egg-info

@ -1,5 +1,21 @@
# Changelog
## Version 0.11.0
- Update deps for Python 3.11
## Version 0.10.0
- Replace the deprecated aioredis with redis-py
- Optimize how we fetch the current (latest) revision of items
## Version 0.9.1
- Update pinned Django version (only matters if using `requirements.txt`).
## Version 0.9.0
- Add LDAP support for checking the validity of a username
- Allow specifying engine-specific database options
- Fix crash on shutdown when redis isn't used
- Reorganize the code to be a valid Python package
## Version 0.8.3
- Fix compatibility with latest fastapi

@ -63,6 +63,12 @@ Now you can initialise our django app.
./manage.py migrate
```
Create static files:
```
./manage.py collectstatic
```
And you are done! You can now run the debug server just to see everything works as expected by running:
```
@ -151,7 +157,7 @@ Instead of having to create Django users manually when signup up Etebase users,
For example, this makes sense when putting an Etebase server in production.
However, this does come with the added risk that everybody with access to your server will be able to sign up.
In order to set it up, comment out the line `ETEBASE_CREATE_USER_FUNC = "django_etebase.utils.create_user_blocked"` in `server/settings.py` and restart your Etebase server.
In order to set it up, comment out the line `ETEBASE_CREATE_USER_FUNC = "etebase_server.django.utils.create_user_blocked"` in `server/settings.py` and restart your Etebase server.
# License
@ -177,3 +183,5 @@ Become a financial contributor and help us sustain our community!
[![ilovept](https://github.com/ilovept.png?size=40)](https://github.com/ilovept)
[![ryanleesipes](https://github.com/ryanleesipes.png?size=40)](https://github.com/ryanleesipes)
[![DanielG](https://github.com/DanielG.png?size=40)](https://github.com/DanielG)
[![Kanaye](https://github.com/Kanaye.png?size=40)](https://github.com/Kanaye)

@ -1,3 +0,0 @@
from django.dispatch import Signal
user_signed_up = Signal(providing_args=["request", "user"])

@ -1,4 +1,4 @@
FROM python:3.9.0-alpine
FROM python:3.11.0-alpine
ARG ETESYNC_VERSION

@ -9,4 +9,4 @@ allowed_host1 = *
[database]
engine = django.db.backends.sqlite3
name = /db.sqlite3
name = /data/db.sqlite3

@ -18,3 +18,18 @@ allowed_host1 = example.com
[database]
engine = django.db.backends.sqlite3
name = db.sqlite3
[database-options]
; Add engine-specific options here, such as postgresql parameter key words
;[ldap]
;server = <The URL to your LDAP server>
;search_base = <Your search base>
;filter = <Your LDAP filter query. '%%s' will be substituted for the username>
; In case a cache TTL of 1 hour is too short for you, set `cache_ttl` to the preferred
; amount of hours a cache entry should be viewed as valid:
;cache_ttl = 5
;bind_dn = <Your LDAP "user" to bind as. Must be a bind user>
; Either specify the password directly, or provide a password file
;bind_pw = <The password to authenticate as your bind user>
;bind_pw_file = /path/to/the/file.txt

@ -7,7 +7,7 @@ django_application = get_asgi_application()
def create_application():
from etebase_fastapi.main import create_application
from etebase_server.fastapi.main import create_application
app = create_application()

@ -2,4 +2,5 @@ from django.apps import AppConfig
class DjangoEtebaseConfig(AppConfig):
name = "django_etebase"
name = "etebase_server.django"
label = "django_etebase"

@ -4,7 +4,7 @@ from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django_etebase.models
from etebase_server.django.models import chunk_directory_path
class Migration(migrations.Migration):
@ -85,7 +85,7 @@ class Migration(migrations.Migration):
),
(
"chunkFile",
models.FileField(max_length=150, unique=True, upload_to=django_etebase.models.chunk_directory_path),
models.FileField(max_length=150, unique=True, upload_to=chunk_directory_path),
),
(
"item",

@ -3,7 +3,7 @@
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django_etebase.models
from etebase_server.django.models import generate_stoken_uid
class Migration(migrations.Migration):
@ -21,7 +21,7 @@ class Migration(migrations.Migration):
"uid",
models.CharField(
db_index=True,
default=django_etebase.models.generate_stoken_uid,
default=generate_stoken_uid,
max_length=43,
unique=True,
validators=[

@ -2,7 +2,7 @@
import django.core.validators
from django.db import migrations, models
import django_etebase.models
from etebase_server.django.models import generate_stoken_uid
class Migration(migrations.Migration):
@ -62,7 +62,7 @@ class Migration(migrations.Migration):
name="uid",
field=models.CharField(
db_index=True,
default=django_etebase.models.generate_stoken_uid,
default=generate_stoken_uid,
max_length=43,
unique=True,
validators=[

@ -2,7 +2,7 @@
from django.db import migrations
from django_etebase.models import AccessLevels
from etebase_server.django.models import AccessLevels
def change_access_level_to_int(apps, schema_editor):

@ -96,7 +96,7 @@ class CollectionItem(models.Model):
@cached_property
def content(self) -> "CollectionItemRevision":
return self.revisions.get(current=True)
return self.revisions.filter(current=True)[0]
@property
def etag(self) -> str:

@ -0,0 +1,4 @@
from django.dispatch import Signal
# Provides arguments "request" and "user"
user_signed_up = Signal()

@ -2,4 +2,4 @@ from django.apps import AppConfig
class TokenAuthConfig(AppConfig):
name = "django_etebase.token_auth"
name = "etebase_server.django.token_auth"

@ -3,7 +3,7 @@
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django_etebase.token_auth import models as token_auth_models
from etebase_server.django.token_auth import models as token_auth_models
class Migration(migrations.Migration):

@ -1,7 +1,7 @@
from django.db import models
from django.utils import timezone
from django.utils.crypto import get_random_string
from myauth.models import get_typed_user_model
from etebase_server.myauth.models import get_typed_user_model
User = get_typed_user_model()

@ -3,7 +3,7 @@ from dataclasses import dataclass
from django.db.models import QuerySet
from django.core.exceptions import PermissionDenied
from myauth.models import UserType, get_typed_user_model
from etebase_server.myauth.models import UserType, get_typed_user_model
from . import app_settings

@ -6,9 +6,9 @@ from fastapi.security import APIKeyHeader
from django.utils import timezone
from django.db.models import QuerySet
from django_etebase import models
from django_etebase.token_auth.models import AuthToken, get_default_expiry
from myauth.models import UserType, get_typed_user_model
from etebase_server.django import models
from etebase_server.django.token_auth.models import AuthToken, get_default_expiry
from etebase_server.myauth.models import UserType, get_typed_user_model
from .exceptions import AuthenticationFailed
from .utils import get_object_or_404
from .db_hack import django_db_cleanup_decorator

@ -6,7 +6,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.staticfiles import StaticFiles
from django_etebase import app_settings
from etebase_server.django import app_settings
from .exceptions import CustomHttpException
from .msgpack import MsgpackResponse
@ -43,7 +43,7 @@ def create_application(prefix="", middlewares=[]):
app.include_router(websocket_router, prefix=f"{BASE_PATH}/ws", tags=["websocket"])
if settings.DEBUG:
from etebase_fastapi.routers.test_reset_view import test_reset_view_router
from .routers.test_reset_view import test_reset_view_router
app.include_router(test_reset_view_router, prefix=f"{BASE_PATH}/test/authentication")

@ -1,7 +1,7 @@
import typing as t
import aioredis
from redis import asyncio as aioredis
from django_etebase import app_settings
from etebase_server.django import app_settings
class RedisWrapper:
@ -12,12 +12,11 @@ class RedisWrapper:
async def setup(self):
if self.redis_uri is not None:
self.redis = await aioredis.create_redis_pool(self.redis_uri)
self.redis = await aioredis.from_url(self.redis_uri)
async def close(self):
if self.redis is not None:
self.redis.close()
await self.redis.wait_closed()
if hasattr(self, "redis"):
await self.redis.close()
@property
def is_active(self):

@ -14,12 +14,12 @@ from django.db import transaction
from django.utils.functional import cached_property
from fastapi import APIRouter, Depends, status, Request
from django_etebase import app_settings, models
from django_etebase.token_auth.models import AuthToken
from django_etebase.models import UserInfo
from django_etebase.signals import user_signed_up
from django_etebase.utils import create_user, get_user_queryset, CallbackContext
from myauth.models import UserType, get_typed_user_model
from etebase_server.django import app_settings, models
from etebase_server.django.token_auth.models import AuthToken
from etebase_server.django.models import UserInfo
from etebase_server.django.signals import user_signed_up
from etebase_server.django.utils import create_user, get_user_queryset, CallbackContext
from etebase_server.myauth.models import UserType, get_typed_user_model
from ..exceptions import AuthenticationFailed, transform_validation_error, HttpError
from ..msgpack import MsgpackRoute
from ..utils import BaseModel, permission_responses, msgpack_encode, msgpack_decode, get_user_username_email_kwargs

@ -7,8 +7,8 @@ from django.db import transaction, IntegrityError
from django.db.models import Q, QuerySet
from fastapi import APIRouter, Depends, status, Request, BackgroundTasks
from django_etebase import models
from myauth.models import UserType
from etebase_server.django import models
from etebase_server.myauth.models import UserType
from .authentication import get_authenticated_user
from .websocket import get_ticket, TicketRequest, TicketOut
from ..exceptions import HttpError, transform_validation_error, PermissionDenied, ValidationError
@ -396,7 +396,7 @@ def item_create(item_model: CollectionItemIn, collection: models.Collection, val
if not created:
# We don't have to use select_for_update here because the unique constraint on current guards against
# the race condition. But it's a good idea because it'll lock and wait rather than fail.
current_revision = instance.revisions.filter(current=True).select_for_update().first()
current_revision = instance.revisions.filter(current=True).select_for_update()[0]
assert current_revision is not None
current_revision.current = None
current_revision.save()

@ -4,9 +4,9 @@ from django.db import transaction, IntegrityError
from django.db.models import QuerySet
from fastapi import APIRouter, Depends, status, Request
from django_etebase import models
from django_etebase.utils import get_user_queryset, CallbackContext
from myauth.models import UserType, get_typed_user_model
from etebase_server.django import models
from etebase_server.django.utils import get_user_queryset, CallbackContext
from etebase_server.myauth.models import UserType, get_typed_user_model
from .authentication import get_authenticated_user
from ..exceptions import HttpError, PermissionDenied
from ..msgpack import MsgpackRoute

@ -4,8 +4,8 @@ from django.db import transaction
from django.db.models import QuerySet
from fastapi import APIRouter, Depends, status
from django_etebase import models
from myauth.models import UserType, get_typed_user_model
from etebase_server.django import models
from etebase_server.myauth.models import UserType, get_typed_user_model
from .authentication import get_authenticated_user
from ..msgpack import MsgpackRoute
from ..utils import get_object_or_404, BaseModel, permission_responses, PERMISSIONS_READ, PERMISSIONS_READWRITE

@ -3,11 +3,11 @@ from django.db import transaction
from django.shortcuts import get_object_or_404
from fastapi import APIRouter, Request, status
from django_etebase.utils import get_user_queryset, CallbackContext
from etebase_server.django.utils import get_user_queryset, CallbackContext
from .authentication import SignupIn, signup_save
from ..msgpack import MsgpackRoute
from ..exceptions import HttpError
from myauth.models import get_typed_user_model
from etebase_server.myauth.models import get_typed_user_model
test_reset_view_router = APIRouter(route_class=MsgpackRoute, tags=["test helpers"])
User = get_typed_user_model()

@ -1,16 +1,17 @@
import asyncio
import typing as t
import aioredis
from redis import asyncio as aioredis
from redis.exceptions import ConnectionError
from asgiref.sync import sync_to_async
from django.db.models import QuerySet
from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect, status
import nacl.encoding
import nacl.utils
from django_etebase import models
from django_etebase.utils import CallbackContext, get_user_queryset
from myauth.models import UserType, get_typed_user_model
from etebase_server.django import models
from etebase_server.django.utils import CallbackContext, get_user_queryset
from etebase_server.myauth.models import UserType, get_typed_user_model
from ..dependencies import get_collection_queryset, get_item_queryset
from ..exceptions import NotSupported
@ -51,7 +52,7 @@ async def get_ticket(
uid = nacl.encoding.URLSafeBase64Encoder.encode(nacl.utils.random(32))
ticket_model = TicketInner(user=user.id, req=ticket_request)
ticket_raw = msgpack_encode(ticket_model.dict())
await redisw.redis.set(uid, ticket_raw, expire=TICKET_VALIDITY_SECONDS * 1000)
await redisw.redis.set(uid, ticket_raw, ex=TICKET_VALIDITY_SECONDS * 1000)
return TicketOut(ticket=uid)
@ -103,9 +104,9 @@ async def send_item_updates(
async def redis_connector(websocket: WebSocket, ticket_model: TicketInner, user: UserType, stoken: t.Optional[str]):
async def producer_handler(r: aioredis.Redis, ws: WebSocket):
pubsub = r.pubsub()
channel_name = f"col.{ticket_model.req.collection}"
(channel,) = await r.psubscribe(channel_name)
assert isinstance(channel, aioredis.Channel)
await pubsub.subscribe(channel_name)
# Send missing items if we are not up to date
queryset: QuerySet[models.Collection] = get_collection_queryset(user)
@ -117,12 +118,20 @@ async def redis_connector(websocket: WebSocket, ticket_model: TicketInner, user:
return
await send_item_updates(websocket, collection, user, stoken)
async def handle_message():
msg = await pubsub.get_message(ignore_subscribe_messages=True, timeout=20)
message_raw = t.cast(t.Optional[t.Tuple[str, bytes]], msg)
if message_raw:
_, message = message_raw
await ws.send_bytes(message)
try:
while True:
# We wait on the websocket so we fail if web sockets fail or get data
receive = asyncio.create_task(websocket.receive())
done, pending = await asyncio.wait(
{receive, channel.wait_message()}, return_when=asyncio.FIRST_COMPLETED
{receive, handle_message()},
return_when=asyncio.FIRST_COMPLETED,
)
for task in pending:
task.cancel()
@ -131,12 +140,7 @@ async def redis_connector(websocket: WebSocket, ticket_model: TicketInner, user:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
return
message_raw = t.cast(t.Optional[t.Tuple[str, bytes]], await channel.get())
if message_raw:
_, message = message_raw
await ws.send_bytes(message)
except aioredis.errors.ConnectionClosedError:
except ConnectionError:
await websocket.close(code=status.WS_1012_SERVICE_RESTART)
except WebSocketDisconnect:
pass

@ -3,7 +3,7 @@ import typing as t
from django.db.models import QuerySet
from fastapi import status
from django_etebase.models import Stoken
from etebase_server.django.models import Stoken
from .exceptions import HttpError

@ -10,9 +10,9 @@ from pydantic import BaseModel as PyBaseModel
from django.db.models import Model, QuerySet
from django.core.exceptions import ObjectDoesNotExist
from django_etebase import app_settings
from django_etebase.models import AccessLevels
from myauth.models import UserType, get_typed_user_model
from etebase_server.django import app_settings
from etebase_server.django.models import AccessLevels
from etebase_server.myauth.models import UserType, get_typed_user_model
from .exceptions import HttpError, HttpErrorOut

@ -2,4 +2,5 @@ from django.apps import AppConfig
class MyauthConfig(AppConfig):
name = "myauth"
name = "etebase_server.myauth"
label = "myauth"

@ -1,6 +1,6 @@
from django import forms
from django.contrib.auth.forms import UsernameField
from myauth.models import get_typed_user_model
from etebase_server.myauth.models import get_typed_user_model
User = get_typed_user_model()

@ -0,0 +1,109 @@
import logging
from django.utils import timezone
from django.conf import settings
from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
from etebase_server.django.utils import CallbackContext
from etebase_server.myauth.models import get_typed_user_model, UserType
from etebase_server.fastapi.dependencies import get_authenticated_user
from etebase_server.fastapi.exceptions import PermissionDenied as FastAPIPermissionDenied
from fastapi import Depends
import ldap
User = get_typed_user_model()
def ldap_setting(name, default):
"""Wrapper around django.conf.settings"""
return getattr(settings, f"LDAP_{name}", default)
class LDAPConnection:
__instance__ = None
__user_cache = {} # Username -> Valid until
@staticmethod
def get_instance():
"""To get a Singleton"""
if not LDAPConnection.__instance__:
return LDAPConnection()
else:
return LDAPConnection.__instance__
def __init__(self):
# Cache some settings
self.__LDAP_FILTER = ldap_setting("FILTER", "")
self.__LDAP_SEARCH_BASE = ldap_setting("SEARCH_BASE", "")
# The time a cache entry is valid (in hours)
try:
self.__LDAP_CACHE_TTL = int(ldap_setting("CACHE_TTL", ""))
except ValueError:
logging.error("Invalid value for cache_ttl. Defaulting to 1 hour")
self.__LDAP_CACHE_TTL = 1
password = ldap_setting("BIND_PW", "")
if not password:
pw_file = ldap_setting("BIND_PW_FILE", "")
if pw_file:
with open(pw_file, "r") as f:
password = f.read().replace("\n", "")
self.__ldap_connection = ldap.initialize(ldap_setting("SERVER", ""))
try:
self.__ldap_connection.simple_bind_s(ldap_setting("BIND_DN", ""), password)
except ldap.LDAPError as err:
logging.error(f"LDAP Error occuring during bind: {err.desc}")
def __is_cache_valid(self, username):
"""Returns True if the cache entry is still valid. Returns False otherwise."""
if username in self.__user_cache:
if timezone.now() <= self.__user_cache[username]:
# Cache entry is still valid
return True
return False
def __remove_cache(self, username):
del self.__user_cache[username]
def has_user(self, username):
"""
Since we don't care about the password and so authentication
another way, all we care about is whether the user exists.
"""
if self.__is_cache_valid(username):
return True
if username in self.__user_cache:
self.__remove_cache(username)
filterstr = self.__LDAP_FILTER.replace("%s", username)
try:
result = self.__ldap_connection.search_s(self.__LDAP_SEARCH_BASE, ldap.SCOPE_SUBTREE, filterstr=filterstr)
except ldap.NO_RESULTS_RETURNED:
# We handle the specific error first and the the generic error, as
# we may expect ldap.NO_RESULTS_RETURNED, but not any other error
return False
except ldap.LDAPError as err:
logging.error(f"Error occured while performing an LDAP query: {err.desc}")
return False
if len(result) == 1:
self.__user_cache[username] = timezone.now() + timezone.timedelta(hours=self.__LDAP_CACHE_TTL)
return True
return False
def is_user_in_ldap(user: UserType = Depends(get_authenticated_user)):
if not LDAPConnection.get_instance().has_user(user.username):
raise FastAPIPermissionDenied(detail="User not in LDAP directory.")
def create_user(context: CallbackContext, *args, **kwargs):
"""
A create_user function which first checks if the user already exists in the
configured LDAP directory.
"""
if not LDAPConnection.get_instance().has_user(kwargs["username"]):
raise DjangoPermissionDenied("User not in the LDAP directory.")
return User.objects.create_user(*args, **kwargs)

@ -1,7 +1,7 @@
# Generated by Django 3.0.3 on 2020-05-15 08:01
from django.db import migrations, models
import myauth.models
import etebase_server.myauth.models as myauth_models
class Migration(migrations.Migration):
@ -19,7 +19,7 @@ class Migration(migrations.Migration):
help_text="Required. 150 characters or fewer. Letters, digits and ./+/-/_ only.",
max_length=150,
unique=True,
validators=[myauth.models.UnicodeUsernameValidator()],
validators=[myauth_models.UnicodeUsernameValidator()],
verbose_name="username",
),
),

@ -1,7 +1,7 @@
# Generated by Django 3.1.1 on 2020-11-19 08:10
from django.db import migrations, models
import myauth.models
import etebase_server.myauth.models as myauth_models
class Migration(migrations.Migration):
@ -14,7 +14,7 @@ class Migration(migrations.Migration):
migrations.AlterModelManagers(
name="user",
managers=[
("objects", myauth.models.UserManager()),
("objects", myauth_models.UserManager()),
],
),
migrations.AlterField(
@ -30,7 +30,7 @@ class Migration(migrations.Migration):
help_text="Required. 150 characters or fewer. Letters, digits and ./-/_ only.",
max_length=150,
unique=True,
validators=[myauth.models.UnicodeUsernameValidator()],
validators=[myauth_models.UnicodeUsernameValidator()],
verbose_name="username",
),
),

@ -15,7 +15,8 @@ import configparser
from .utils import get_secret_from_file
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SOURCE_DIR = os.path.dirname(os.path.abspath(__file__))
BASE_DIR = os.path.dirname(SOURCE_DIR)
AUTH_USER_MODEL = "myauth.User"
@ -54,9 +55,9 @@ INSTALLED_APPS = [
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"myauth.apps.MyauthConfig",
"django_etebase.apps.DjangoEtebaseConfig",
"django_etebase.token_auth.apps.TokenAuthConfig",
"etebase_server.myauth.apps.MyauthConfig",
"etebase_server.django.apps.DjangoEtebaseConfig",
"etebase_server.django.token_auth.apps.TokenAuthConfig",
]
MIDDLEWARE = [
@ -74,7 +75,7 @@ ROOT_URLCONF = "etebase_server.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(BASE_DIR, "templates")],
"DIRS": [os.path.join(SOURCE_DIR, "templates")],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
@ -139,6 +140,8 @@ config_locations = [
"/etc/etebase-server/etebase-server.ini",
]
ETEBASE_CREATE_USER_FUNC = "etebase_server.django.utils.create_user_blocked"
# Use config file if present
if any(os.path.isfile(x) for x in config_locations):
config = configparser.ConfigParser()
@ -164,10 +167,30 @@ if any(os.path.isfile(x) for x in config_locations):
if "database" in config:
DATABASES = {"default": {x.upper(): y for x, y in config.items("database")}}
ETEBASE_CREATE_USER_FUNC = "django_etebase.utils.create_user_blocked"
if "database-options" in config:
DATABASES["default"]["OPTIONS"] = config["database-options"]
if "ldap" in config:
ldap = config["ldap"]
LDAP_SERVER = ldap.get("server", "")
LDAP_SEARCH_BASE = ldap.get("search_base", "")
LDAP_FILTER = ldap.get("filter", "")
LDAP_BIND_DN = ldap.get("bind_dn", "")
LDAP_BIND_PW = ldap.get("bind_pw", "")
LDAP_BIND_PW_FILE = ldap.get("bind_pw_file", "")
LDAP_CACHE_TTL = ldap.get("cache_ttl", "")
if not LDAP_BIND_DN:
raise Exception("LDAP enabled but bind_dn is not set!")
if not LDAP_BIND_PW and not LDAP_BIND_PW_FILE:
raise Exception("LDAP enabled but both bind_pw and bind_pw_file are not set!")
# Configure EteBase to use LDAP
ETEBASE_CREATE_USER_FUNC = "etebase_server.myauth.ldap.create_user"
ETEBASE_API_PERMISSIONS_READ = ["etebase_server.myauth.ldap.is_user_in_ldap"]
# Efficient file streaming (for large files)
SENDFILE_BACKEND = "etebase_fastapi.sendfile.backends.simple"
SENDFILE_BACKEND = "etebase_server.fastapi.sendfile.backends.simple"
SENDFILE_ROOT = MEDIA_ROOT
# Make an `etebase_server_settings` module available to override settings.

@ -1,25 +1,13 @@
import os
from django.conf import settings
from django.conf.urls import url
from django.contrib import admin
from django.urls import path, re_path
from django.urls import path
from django.views.generic import TemplateView
from django.views.static import serve
from django.contrib.staticfiles import finders
urlpatterns = [
url(r"^admin/", admin.site.urls),
path("admin/", admin.site.urls),
path("", TemplateView.as_view(template_name="success.html")),
]
if settings.DEBUG:
def serve_static(request, path):
filename = finders.find(path)
dirname = os.path.dirname(filename)
basename = os.path.basename(filename)
return serve(request, basename, dirname)
urlpatterns += [re_path(r"^static/(?P<path>.*)$", serve_static)]

@ -13,6 +13,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.core.management import utils
import os
import stat
def get_secret_from_file(path):
@ -21,6 +23,7 @@ def get_secret_from_file(path):
return f.read().strip()
except EnvironmentError:
with open(path, "w") as f:
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
secret_key = utils.get_random_secret_key()
f.write(secret_key)
return secret_key

@ -1,22 +0,0 @@
# Running `etebase` under `nginx` and `uwsgi`
This configuration assumes that etebase server has been installed in the home folder of a non privileged user
called `EtebaseUser` following the instructions in <https://github.com/etesync/server>. Also that static
files have been collected at `/srv/http/etebase_server` by running the following commands:
```shell
sudo mkdir -p /srv/http/etebase_server/static
sudo chown -R EtebaseUser /srv/http/etebase_server
sudo su EtebaseUser
cd /path/to/etebase
ln -s /srv/http/etebase_server/static static
./manage.py collectstatic
```
It is also assumed that `nginx` and `uwsgi` have been installed system wide by `root`, and that `nginx` is running as user/group `www-data`.
In this setup, `uwsgi` running as a `systemd` service as `root` creates a unix socket with read-write access
to both `EtebaseUser` and `nginx`. It then drops its `root` privilege and runs `etebase` as `EtebaseUser`.
`nginx` listens on the `https` port (or a non standard port `https` port if desired), delivers static pages directly
and for everything else, communicates with `etebase` over the unix socket.

@ -1,15 +0,0 @@
# uwsgi configuration file
# typical location of this file would be /etc/uwsgi/sites/etebase.ini
[uwsgi]
socket = /path/to/etebase_server.sock
chown-socket = EtebaseUser:www-data
chmod-socket = 660
vacuum = true
uid = EtebaseUser
chdir = /path/to/etebase
home = %(chdir)/.venv
module = etebase_server.wsgi
master = true

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save