Can't upload files to libraries v2

Hi,

I am trying to upload files to a unit that I created in Libraries v2, and I get a frontend error when uploading the file.

Checking the cms logs on the host, I get:

cms-1  | During handling of the above exception, another exception occurred:
cms-1  |
cms-1  | Traceback (most recent call last):
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
cms-1  |     response = get_response(request)
cms-1  |                ^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
cms-1  |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
cms-1  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/views/decorators/csrf.py", line 65, in _view_wrapper
cms-1  |     return view_func(request, *args, **kwargs)
cms-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/views/generic/base.py", line 105, in view
cms-1  |     return self.dispatch(request, *args, **kwargs)
cms-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/utils/decorators.py", line 48, in _wrapper
cms-1  |     return bound_method(*args, **kwargs)
cms-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 515, in dispatch
cms-1  |     response = self.handle_exception(exc)
cms-1  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 475, in handle_exception
cms-1  |     self.raise_uncaught_exception(exc)
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 486, in raise_uncaught_exception
cms-1  |     raise exc
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 512, in dispatch
cms-1  |     response = handler(request, *args, **kwargs)
cms-1  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/rest_api/utils.py", line 24, in wrapped_fn
cms-1  |     return fn(*args, **kwargs)
cms-1  |            ^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/rest_api/blocks.py", line 205, in put
cms-1  |     result = api.add_library_block_static_asset_file(usage_key, file_path, file_content, request.user)
cms-1  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/api/blocks.py", line 893, in add_library_block_static_asset_file
cms-1  |     component_version = authoring_api.create_next_component_version(
cms-1  |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/openedx_learning/apps/authoring/components/api.py", line 259, in create_next_component_version
cms-1  |     content = contents_api.get_or_create_file_content(
cms-1  |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/openedx_learning/apps/authoring/contents/api.py", line 173, in get_or_create_file_content
cms-1  |     content.write_file(ContentFile(data))
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/openedx_learning/apps/authoring/contents/models.py", line 366, in write_file
cms-1  |     storage.save(self.path, file)
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/core/files/storage/base.py", line 49, in save
cms-1  |     name = self._save(name, content)
cms-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/core/files/storage/filesystem.py", line 100, in _save
cms-1  |     os.makedirs(directory, exist_ok=True)
cms-1  |   File "<frozen os>", line 215, in makedirs
cms-1  |   File "<frozen os>", line 215, in makedirs
cms-1  |   File "<frozen os>", line 225, in makedirs
cms-1  | PermissionError: [Errno 13] Permission denied: '/openedx/media-private/openedx-learning'
cms-1  | [pid: 38|app: 0|req: 425/1179] 172.18.0.2 () {70 vars in 4575 bytes} [Fri Mar  6 14:59:28 2026] PUT /api/libraries/v2/blocks/lb:CQ:Navigation:html:naviguer-dans-cette-formation-777eeeedf241/assets/static/boutons-de-navigation.png => generated 8696 bytes in 82 msecs (HTTP/1.1 500) 10 headers in 641 bytes (3 switches on core 0)

It seems like it attempts to write to a folder it can’t write to. Is there something I forgot to configure when installing OpenEdX ?

This is with Ulmo.

I know the libraries end of this, but not the Tutor/deployment end of this. That is the intended location for content libraries to upload file assets into. What is the ownership and permissions for /openedx/media-private/openedx-learning?

On the host, there is no such directory. I don’t know where tutor is expecting to bind mount this path from the container.

So, I attached the container, and inside the container, I have:

app@95b0a311944a:~/edx-platform$ ls -lah /openedx/media-private/
total 0
drwxr-xr-x. 2 root root  6 Feb 16 19:12 .
drwxr-xr-x. 1 app  app  86 Mar  5 21:38 ..
app@95b0a311944a:~/edx-platform$ ls -lahd /openedx/media-private/
drwxr-xr-x. 2 root root 6 Feb 16 19:12 /openedx/media-private/

and assuming this is the right directory, outside of the container:

[tutor@edx1 ~]$ ls -lah .local/share/tutor/data/openedx-media-private/
total 0
drwxr-xr-x.  2 root root   6 Feb 16 19:12 .
drwxr-xr-x. 11 root root 156 Feb 16 20:00 ..
[tutor@edx1 ~]$ ls -lahd .local/share/tutor/data/openedx-media-private
drwxr-xr-x. 2 root root 6 Feb 16 19:12 .local/share/tutor/data/openedx-media-private

Manually fixing the owner of this directory to uid 1000 (corresponding to user “app” inside the container) fixed the uploading problem.

The question is why isn’t this done by default by tutor ?

I am not very knowledgeable about Tutor. The main difference I see between our setups is that I’m not running things as root, so the app user in the container and my local user dormsbeehave the same uid.

tutor is not running as root either. It is running as its own user (tutor). But it’s running Docker, which does has privileges. The “app” user in the container is actually mapping to a different user outside of it (centos) simply because it has the same uid inside the container as centos has outside the container.

I believe this permissions docker image is supposed to be setting the correct permissions:

However, as you can see here, the actual script seems to omit openedx-private from the list of folders that it fixes:

So I think this is just a bug in Tutor and that setowners.sh may need to be patched to add that additional folder.

1 Like

Ok, I created tutor does not correctly set permissions for openedx-private · Issue #1353 · overhangio/tutor · GitHub

Hopefully this get addressed soon.

1 Like

Thanks @braden for your input. The fix has landed on the release branch and will be shipped with the next release. Until then, @mboisson, you can create a patch local-docker-compose-permissions-command with this line setowner $OPENEDX_USER_ID /mounts/openedx-private

1 Like