ethoscopy 2.0.3__tar.gz → 2.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/CLAUDE.md +35 -0
  2. ethoscopy-2.0.4/Docker/.claude/settings.local.json +41 -0
  3. ethoscopy-2.0.4/Docker/README.md +227 -0
  4. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/jupyterhub_data/jupyterhub.sqlite +0 -0
  5. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/PKG-INFO +1 -1
  6. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/pyproject.toml +1 -1
  7. ethoscopy-2.0.4/scripts/README.md +121 -0
  8. ethoscopy-2.0.4/scripts/convert_databases.sh +51 -0
  9. ethoscopy-2.0.4/scripts/convert_wal_to_delete.py +240 -0
  10. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/load.py +64 -7
  11. ethoscopy-2.0.3/Docker/.claude/settings.local.json +0 -19
  12. ethoscopy-2.0.3/Docker/README.md +0 -85
  13. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/.claude/settings.local.json +0 -0
  14. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/.codecov.yml +0 -0
  15. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/.github/workflows/ci.yml +0 -0
  16. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/.github/workflows/release.yml +0 -0
  17. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/.gitignore +0 -0
  18. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/.pre-commit-config.yaml +0 -0
  19. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/.env +0 -0
  20. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/.env.keycloak +0 -0
  21. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/Dockerfile +0 -0
  22. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/config/jupyterhub.sqlite +0 -0
  23. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/config/jupyterhub_cookie_secret +0 -0
  24. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/docker-compose.yml +0 -0
  25. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/install_r_packages.r +0 -0
  26. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/jupyterhub_data/jupyterhub-proxy.pid +0 -0
  27. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/jupyterhub_data/jupyterhub_config.py +0 -0
  28. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/jupyterhub_data/jupyterhub_cookie_secret +0 -0
  29. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/LICENSE +0 -0
  30. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/QWEN.md +0 -0
  31. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/README.md +0 -0
  32. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/TESTING.md +0 -0
  33. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/_config.yml +0 -0
  34. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/pytest.ini +0 -0
  35. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/run_tests.py +0 -0
  36. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/setup.py +0 -0
  37. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/__init__.py +0 -0
  38. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/analyse.py +0 -0
  39. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy.py +0 -0
  40. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_HMM_class.py +0 -0
  41. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_class.py +0 -0
  42. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_core.py +0 -0
  43. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_draw.py +0 -0
  44. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_periodogram_class.py +0 -0
  45. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_plotly.py +0 -0
  46. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_seaborn.py +0 -0
  47. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/metadata_db.py +0 -0
  48. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/__init__.py +0 -0
  49. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/circadian_bars.py +0 -0
  50. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/general_functions.py +0 -0
  51. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/get_HMM.py +0 -0
  52. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/get_tutorials.py +0 -0
  53. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/hmm_functions.py +0 -0
  54. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/periodogram_functions.py +0 -0
  55. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/validate_datetime.py +0 -0
  56. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/__init__.py +0 -0
  57. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/conftest.py +0 -0
  58. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/data/README.md +0 -0
  59. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/data/test_ethoscope.db +0 -0
  60. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_analyse.py +0 -0
  61. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_baseline_enhancements.py +0 -0
  62. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_behavpy.py +0 -0
  63. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_behavpy_core_simple.py +0 -0
  64. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_compatibility_classes.py +0 -0
  65. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_general_functions.py +0 -0
  66. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_get_tutorials.py +0 -0
  67. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_load.py +0 -0
  68. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_load_comprehensive.py +0 -0
  69. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_load_optimizations.py +0 -0
  70. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/1_Overview_tutorial.ipynb +0 -0
  71. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/2_HMM_tutorial.ipynb +0 -0
  72. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/3_Circadian_tutorial.ipynb +0 -0
  73. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/4_Navigating_db_tutorial.ipynb +0 -0
  74. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/5_Ethoscopy_catch22_tutorial.ipynb +0 -0
  75. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/6_Ethoscopy_to_hctsa_tutorial.ipynb +0 -0
  76. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/ethoscope_db.csv +0 -0
  77. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/jones_et_al_metadata.csv +0 -0
  78. {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/notebook_paper.ipynb +0 -0
@@ -45,6 +45,41 @@ pip install -e ".[dev]"
45
45
  - Build: `JUPYTER_HUB_TAG=5.3.0 ETHOSCOPE_LAB_TAG=1.0 docker compose build`
46
46
  - Run: `docker compose up -d` (from Docker/ directory)
47
47
 
48
+ ### Troubleshooting: Database "Malformed" Errors
49
+
50
+ **Problem**: Intermittent "database disk image is malformed" errors when loading ethoscope data in Docker
51
+
52
+ **Root Cause**: SQLite databases in WAL (Write-Ahead Logging) mode on read-only Docker mounts
53
+
54
+ **Solution Options**:
55
+ 1. **Convert databases to DELETE mode** (recommended for immediate fix)
56
+ ```bash
57
+ # Using the conversion script (from project root)
58
+ python3 scripts/convert_wal_to_delete.py /mnt/ethoscope_data/results --verbose
59
+
60
+ # Or using bash wrapper
61
+ ./scripts/convert_databases.sh /mnt/ethoscope_data/results
62
+ ```
63
+
64
+ 2. **Improved connection handling** (v2.0.4+)
65
+ - The `_connect_db()` function in `load.py` now detects WAL mode automatically
66
+ - Uses `mode=ro&nolock=1` URI parameters for WAL databases on read-only mounts
67
+ - Includes retry logic in `read_single_roi_optimized()` for resilience
68
+
69
+ **Key Implementation Details** (load.py):
70
+ - Lines 18-70: `_connect_db()` - Smart connection with WAL detection and appropriate parameters
71
+ - Lines 943-979: Retry logic in `read_single_roi_optimized()` - Handles transient errors with fresh connections
72
+
73
+ **Testing After Changes**:
74
+ ```python
75
+ import ethoscopy as etho
76
+ metadata = etho.link_meta_index('metadata.csv', '/mnt/ethoscope_results')
77
+ data = etho.load_ethoscope(metadata, reference_hour=9.0, FUN=etho.sleep_annotation)
78
+ # Should load all ROIs without "malformed" errors
79
+ ```
80
+
81
+ **See Also**: `Docker/README.md` for detailed database preparation instructions
82
+
48
83
  ## Architecture and Code Structure
49
84
 
50
85
  ### Core Architecture
@@ -0,0 +1,41 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(ls:*)",
5
+ "Bash(git checkout:*)",
6
+ "Bash(docker images:*)",
7
+ "Read(//tmp/**)",
8
+ "Bash(JUPYTER_HUB_TAG=5.3.0 ETHOSCOPE_LAB_TAG=1.1 docker compose build:*)",
9
+ "Bash(tee:*)",
10
+ "Bash(docker compose:*)",
11
+ "Bash(find:*)",
12
+ "Bash(sqlite3:*)",
13
+ "Bash(python3:*)",
14
+ "Bash(docker exec:*)",
15
+ "Bash(docker cp:*)",
16
+ "Bash(ip addr:*)",
17
+ "Bash(systemctl status:*)",
18
+ "Bash(docker ps:*)",
19
+ "Bash(wg show:*)",
20
+ "Bash(ip route:*)",
21
+ "Bash(ip rule:*)",
22
+ "Bash(docker inspect:*)",
23
+ "Bash(docker logs:*)",
24
+ "Bash(nc:*)",
25
+ "Bash(timeout 3 bash -c 'cat < /dev/tcp/192.168.254.10/8082')",
26
+ "Bash(docker network inspect:*)",
27
+ "Bash(ss:*)",
28
+ "Bash(host:*)",
29
+ "Bash(chmod:*)",
30
+ "Bash(tree:*)",
31
+ "Bash(source:*)",
32
+ "Bash(pip install:*)",
33
+ "Bash(git add:*)",
34
+ "Bash(git commit -m \"$\\(cat <<''EOF''\nfix: resolve \"database disk image is malformed\" errors for WAL-mode databases\n\nAdd comprehensive solution for SQLite WAL-mode database errors on read-only Docker mounts:\n\n- Improve connection handling in load.py:\n - Detect WAL mode databases and use appropriate connection parameters\n - Add retry logic with fresh connections for transient errors\n - Use mode=ro&nolock=1 for WAL databases on read-only filesystems\n\n- Add database conversion scripts:\n - convert_wal_to_delete.py: Python script to convert databases from WAL to DELETE mode\n - convert_databases.sh: Bash wrapper for easy bulk conversion\n - scripts/README.md: Comprehensive documentation for database utilities\n\n- Update documentation:\n - Docker/README.md: Add \"Database Preparation for Docker\" section\n - CLAUDE.md: Add troubleshooting section for database errors\n\n- Bump version to 2.0.4\n\nThis resolves intermittent loading failures for ROIs when ethoscope databases\nin WAL mode are mounted read-only in Docker containers.\n\nCo-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
35
+ "Bash(git push:*)",
36
+ "Bash(git tag:*)",
37
+ "Bash(python -m build:*)"
38
+ ],
39
+ "deny": []
40
+ }
41
+ }
@@ -0,0 +1,227 @@
1
+ # Ethoscopelab docker instance
2
+
3
+ Note: Most users will **not** need to recreate this image. These instructions are just provided as reference.
4
+ The ethoscopelab docker instance lives on dockerhub at the following address: [https://hub.docker.com/r/ggilestro/ethoscope-lab](https://hub.docker.com/r/ggilestro/ethoscope-lab) and this is what regular users should download and run. Follow instructions there and on the [ethoscopy manual](https://bookstack.lab.gilest.ro/books/ethoscopy/page/getting-started).
5
+
6
+
7
+ ## Docker files that were used to create the ethoscope-lab docker instance
8
+
9
+ The files in this folder can be used to recreate the image as uploaded on dockerhub.
10
+
11
+ The command to use to recreate that image is `JUPYTER_HUB_TAG=5.3.0 ETHOSCOPE_LAB_TAG=1.0 docker compose build`. This creates the image with the specified tag. For Docker Hub deployment, push the image: `docker push ggilestro/ethoscope-lab:1.0`. To also create a latest tag: `docker tag ggilestro/ethoscope-lab:1.0 ggilestro/ethoscope-lab:latest && docker push ggilestro/ethoscope-lab:latest`. You can verify your local images with `docker images | grep ethoscope-lab`.
12
+
13
+ After creation, the image can be run using the enclosed `docker-compose.yml` file, replacing values as fit.
14
+
15
+ ## Add new users to the JupyterHub
16
+
17
+ To add new users to the JupyterHub instance:
18
+
19
+ ### 1. Modify the jupyterhub_config.py file
20
+
21
+ Edit the `allowed_users` set on lines 14-19 to include your new usernames:
22
+
23
+ ```python
24
+ c.Authenticator.allowed_users = {
25
+ 'amadabhushi', 'ggilestro', 'mjoyce', 'lguo',
26
+ 'labguest1', 'labguest2', 'labguest3', 'labguest4',
27
+ 'labguest5', 'labguest6', 'labguest7', 'labguest8',
28
+ 'ethoscopelab', 'newuser1', 'newuser2' # Add your new users here
29
+ }
30
+ ```
31
+
32
+ To make a user an admin, add them to the `admin_users` set on line 22:
33
+
34
+ ```python
35
+ c.Authenticator.admin_users = {'ggilestro', 'newadmin'}
36
+ ```
37
+
38
+ ### 2. Restart the Docker container
39
+
40
+ After modifying the config file:
41
+
42
+ ```bash
43
+ docker compose down
44
+ docker compose up -d
45
+ ```
46
+
47
+ **Notes:**
48
+ - All users share the same password: `ethoscope` (line 11)
49
+ - The system uses DummyAuthenticator for simple shared-password authentication
50
+ - Each user gets their own home directory at `/home/{username}`
51
+ - Home directories are created automatically when users first log in
52
+
53
+ ## Mounting Home Directories as Volumes
54
+
55
+ To persist user data and notebooks across container restarts, you should mount user home directories as Docker volumes. This is done by modifying the `docker-compose.yml` file.
56
+
57
+ ### Benefits of mounting home directories:
58
+
59
+ 1. **Data Persistence**: User notebooks, data files, and configurations survive container restarts and updates
60
+ 2. **Backup and Recovery**: Easy to backup user data by copying the mounted directories
61
+ 3. **Performance**: Direct access to host filesystem, avoiding container storage overhead
62
+ 4. **Sharing**: Users can access their files from the host system if needed
63
+
64
+ ### Example volume configuration:
65
+
66
+ Add volumes to your `docker-compose.yml`:
67
+
68
+ ```yaml
69
+ services:
70
+ jupyterhub:
71
+ volumes:
72
+ - ./user_data:/home # Maps host ./user_data to container /home
73
+ - ./jupyterhub_config.py:/srv/jupyterhub/jupyterhub_config.py
74
+ ```
75
+
76
+ Or for individual user directories:
77
+
78
+ ```yaml
79
+ volumes:
80
+ - ./users/ggilestro:/home/ggilestro
81
+ - ./users/amadabhushi:/home/amadabhushi
82
+ - ./users/shared:/home/shared # Shared directory for all users
83
+ ```
84
+
85
+ This ensures all user work is preserved even when containers are recreated or updated.
86
+
87
+ ## Database Preparation for Docker
88
+
89
+ When mounting ethoscope database files into Docker containers with read-only permissions (`:ro`), you may encounter "database disk image is malformed" errors. This occurs when SQLite databases are in WAL (Write-Ahead Logging) mode but mounted on read-only filesystems where WAL companion files (`.db-wal`, `.db-shm`) cannot be accessed.
90
+
91
+ ### Understanding the Issue
92
+
93
+ **Why this happens:**
94
+ - Ethoscope databases may be created in **WAL mode** for better write performance
95
+ - WAL mode requires companion files (`.db-wal` and `.db-shm`) alongside the main `.db` file
96
+ - Docker read-only mounts (`:ro`) prevent SQLite from accessing these files properly
97
+ - This causes intermittent "database disk image is malformed" errors during data loading
98
+
99
+ **Symptoms:**
100
+ - Intermittent loading failures for specific ROIs
101
+ - Error message: "database disk image is malformed"
102
+ - Same ROI may succeed on some loads and fail on others
103
+ - More common with large databases (>1 GB)
104
+
105
+ ### Solution: Convert Databases to DELETE Mode
106
+
107
+ Before mounting databases in Docker, convert them from WAL to DELETE mode:
108
+
109
+ #### Using the Python Script (Recommended)
110
+
111
+ ```bash
112
+ # From the ethoscopy project root
113
+ python3 scripts/convert_wal_to_delete.py /mnt/ethoscope_data/results --dry-run
114
+
115
+ # Convert all databases with detailed output
116
+ python3 scripts/convert_wal_to_delete.py /mnt/ethoscope_data/results --verbose
117
+
118
+ # Force conversion even for recently modified files (use with caution)
119
+ python3 scripts/convert_wal_to_delete.py /mnt/ethoscope_data/results --force
120
+ ```
121
+
122
+ #### Using the Bash Wrapper
123
+
124
+ ```bash
125
+ # From the ethoscopy project root
126
+ ./scripts/convert_databases.sh /mnt/ethoscope_data/results
127
+ ```
128
+
129
+ #### Manual Conversion Using sqlite3
130
+
131
+ For individual databases:
132
+
133
+ ```bash
134
+ sqlite3 /path/to/database.db "PRAGMA wal_checkpoint(TRUNCATE); PRAGMA journal_mode=DELETE;"
135
+ ```
136
+
137
+ For bulk conversion:
138
+
139
+ ```bash
140
+ find /mnt/ethoscope_data/results -name "*.db" -type f -exec sqlite3 {} "PRAGMA wal_checkpoint(TRUNCATE); PRAGMA journal_mode=DELETE;" \;
141
+ ```
142
+
143
+ ### Verification
144
+
145
+ After conversion, verify the database is in DELETE mode:
146
+
147
+ ```bash
148
+ sqlite3 /path/to/database.db "PRAGMA journal_mode;"
149
+ # Should output: delete
150
+
151
+ sqlite3 /path/to/database.db "PRAGMA integrity_check;"
152
+ # Should output: ok
153
+ ```
154
+
155
+ ### Best Practices
156
+
157
+ 1. **Run conversion BEFORE starting Docker containers**
158
+ ```bash
159
+ # From the ethoscopy project root, convert databases first
160
+ ./scripts/convert_databases.sh /mnt/ethoscope_data/results
161
+
162
+ # Then start containers
163
+ cd Docker && docker compose up -d
164
+ ```
165
+
166
+ 2. **Do NOT convert databases while ethoscopes are actively writing**
167
+ - The conversion script skips files modified in the last 24 hours by default
168
+ - Use `--force` to override this safety check if needed
169
+
170
+ 3. **Test on a backup first**
171
+ ```bash
172
+ # Create a test copy
173
+ cp /path/to/database.db /tmp/test.db
174
+
175
+ # Test conversion
176
+ sqlite3 /tmp/test.db "PRAGMA wal_checkpoint(TRUNCATE); PRAGMA journal_mode=DELETE;"
177
+
178
+ # Verify it works
179
+ sqlite3 /tmp/test.db "PRAGMA integrity_check;"
180
+ ```
181
+
182
+ 4. **Consider converting at the ethoscope source** (upstream fix)
183
+ - Configure ethoscopes to use DELETE mode by default
184
+ - Prevents the need for post-processing
185
+
186
+ ### Troubleshooting
187
+
188
+ **Q: I still get "malformed" errors after conversion**
189
+
190
+ A: The ethoscopy library now includes improved connection handling that should work with both WAL and DELETE mode databases. If you still encounter issues:
191
+ 1. Verify the database was actually converted: `sqlite3 database.db "PRAGMA journal_mode;"`
192
+ 2. Check database integrity: `sqlite3 database.db "PRAGMA integrity_check;"`
193
+ 3. Ensure you're using ethoscopy version 2.0.4 or later
194
+
195
+ **Q: Can I convert databases while Docker is running?**
196
+
197
+ A: Yes, but you should restart the container after conversion:
198
+ ```bash
199
+ # From the ethoscopy project root
200
+ ./scripts/convert_databases.sh /mnt/ethoscope_data/results
201
+ cd Docker && docker compose restart ethoscope-lab
202
+ ```
203
+
204
+ **Q: Will this affect data quality or analysis?**
205
+
206
+ A: No. The conversion only changes how SQLite manages the database internally. All data remains identical and analysis results are unaffected.
207
+
208
+ **Q: How do I check if a database is in WAL mode?**
209
+
210
+ A:
211
+ ```bash
212
+ sqlite3 database.db "PRAGMA journal_mode;"
213
+ ```
214
+ Output will be either `wal` or `delete` (or `truncate`, `persist`, `memory`).
215
+
216
+ ### Emergency Quick Fix
217
+
218
+ If you need to work immediately and can't run the full conversion:
219
+
220
+ ```bash
221
+ # SSH to host machine (not inside container)
222
+ # Convert just the failing database
223
+ sqlite3 /path/to/failing/database.db "PRAGMA wal_checkpoint(TRUNCATE); PRAGMA journal_mode=DELETE;"
224
+
225
+ # Restart container
226
+ docker compose restart ethoscope-lab
227
+ ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ethoscopy
3
- Version: 2.0.3
3
+ Version: 2.0.4
4
4
  Summary: "A python based toolkit to download and anlyse data from the Ethoscope hardware system."
5
5
  Author-email: Lblackhurst29 <lblackhurst29@gmail.com>
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ethoscopy"
3
- version = "2.0.3"
3
+ version = "2.0.4"
4
4
  description = "\"A python based toolkit to download and anlyse data from the Ethoscope hardware system.\""
5
5
  authors = [{name = "Lblackhurst29",email = "lblackhurst29@gmail.com"}]
6
6
  readme = "README.md"
@@ -0,0 +1,121 @@
1
+ # Ethoscopy Database Utility Scripts
2
+
3
+ This directory contains utility scripts for managing and maintaining ethoscope SQLite databases.
4
+
5
+ ## Database Journal Mode Conversion
6
+
7
+ ### convert_wal_to_delete.py
8
+
9
+ Converts SQLite databases from WAL (Write-Ahead Logging) mode to DELETE mode. This is essential when mounting databases read-only in Docker containers, as WAL mode requires write access to create companion `.db-wal` and `.db-shm` files.
10
+
11
+ **Usage:**
12
+
13
+ ```bash
14
+ # Dry run to preview what would be converted
15
+ python3 scripts/convert_wal_to_delete.py /path/to/ethoscope_results --dry-run
16
+
17
+ # Convert all databases with detailed output
18
+ python3 scripts/convert_wal_to_delete.py /path/to/ethoscope_results --verbose
19
+
20
+ # Force conversion even for recently modified databases (use with caution)
21
+ python3 scripts/convert_wal_to_delete.py /path/to/ethoscope_results --force
22
+
23
+ # Specify custom file pattern
24
+ python3 scripts/convert_wal_to_delete.py /path/to/ethoscope_results --pattern "*.sqlite"
25
+ ```
26
+
27
+ **Features:**
28
+ - Recursively searches for all `.db` files
29
+ - Checks current journal mode before conversion
30
+ - Safety check: skips files modified within last 24 hours (unless `--force` used)
31
+ - Performs WAL checkpoint to merge uncommitted data
32
+ - Verifies database integrity after conversion
33
+ - Provides detailed progress and summary statistics
34
+
35
+ ### convert_databases.sh
36
+
37
+ Bash wrapper script that provides a simple interface to the Python conversion script.
38
+
39
+ **Usage:**
40
+
41
+ ```bash
42
+ # Simple one-line conversion
43
+ ./scripts/convert_databases.sh /path/to/ethoscope_results
44
+
45
+ # Pass additional flags to the Python script
46
+ ./scripts/convert_databases.sh /path/to/ethoscope_results --verbose
47
+ ./scripts/convert_databases.sh /path/to/ethoscope_results --dry-run
48
+ ```
49
+
50
+ ## When to Use These Scripts
51
+
52
+ ### Problem Symptoms
53
+ - Intermittent "database disk image is malformed" errors
54
+ - Errors occur when loading ethoscope data in Docker with read-only mounts
55
+ - Same ROI sometimes succeeds and sometimes fails to load
56
+ - More common with large databases (>1 GB)
57
+
58
+ ### Solution Workflow
59
+
60
+ 1. **Before starting Docker containers:**
61
+ ```bash
62
+ # Convert databases
63
+ ./scripts/convert_databases.sh /mnt/ethoscope_data/results
64
+
65
+ # Start containers
66
+ cd Docker && docker compose up -d
67
+ ```
68
+
69
+ 2. **If databases are already mounted:**
70
+ ```bash
71
+ # Convert databases
72
+ ./scripts/convert_databases.sh /mnt/ethoscope_data/results
73
+
74
+ # Restart containers to use new connections
75
+ cd Docker && docker compose restart ethoscope-lab
76
+ ```
77
+
78
+ ### Safety Notes
79
+
80
+ - **Do NOT convert databases while ethoscopes are actively writing to them**
81
+ - The script automatically skips files modified within the last 24 hours
82
+ - Use `--force` flag to override this safety check if needed
83
+ - Always test on a backup or single database first
84
+ - Conversion does not modify data, only the internal journal mode
85
+
86
+ ### Verification
87
+
88
+ Check if a database is in WAL mode:
89
+ ```bash
90
+ sqlite3 database.db "PRAGMA journal_mode;"
91
+ ```
92
+
93
+ Verify conversion was successful:
94
+ ```bash
95
+ sqlite3 database.db "PRAGMA journal_mode;" # Should return: delete
96
+ sqlite3 database.db "PRAGMA integrity_check;" # Should return: ok
97
+ ```
98
+
99
+ ## Technical Background
100
+
101
+ **Why this is needed:**
102
+
103
+ - Ethoscope databases may be created in WAL mode for better write performance
104
+ - WAL mode requires companion files (`.db-wal`, `.db-shm`) for proper operation
105
+ - Docker read-only mounts (`:ro`) prevent SQLite from accessing these files
106
+ - Without proper WAL file access, SQLite can report "database disk image is malformed"
107
+ - Converting to DELETE mode eliminates the need for companion files
108
+
109
+ **What the conversion does:**
110
+
111
+ 1. Executes `PRAGMA wal_checkpoint(TRUNCATE)` - Merges WAL data into main database
112
+ 2. Executes `PRAGMA journal_mode=DELETE` - Switches to DELETE mode
113
+ 3. Verifies database integrity with `PRAGMA integrity_check`
114
+
115
+ The conversion is safe and does not modify any data - it only changes how SQLite manages internal transactions.
116
+
117
+ ## See Also
118
+
119
+ - `Docker/README.md` - Complete documentation on database preparation for Docker
120
+ - `CLAUDE.md` - Troubleshooting section for developer reference
121
+ - `src/ethoscopy/load.py` - Implementation of improved connection handling
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ # Wrapper script to convert all ethoscope databases from WAL to DELETE mode
3
+ # Run this BEFORE docker compose up to prepare databases for read-only mounting
4
+ #
5
+ # Usage:
6
+ # ./convert_databases.sh /mnt/ethoscope_data/results
7
+ # ./convert_databases.sh /mnt/ethoscope_data/results --dry-run
8
+
9
+ set -e
10
+
11
+ # Default path if not provided
12
+ DATA_PATH="${1:-/mnt/ethoscope_data/results}"
13
+
14
+ # Check if path exists
15
+ if [ ! -d "$DATA_PATH" ]; then
16
+ echo "ERROR: Directory does not exist: $DATA_PATH"
17
+ echo "Usage: $0 <path_to_ethoscope_results> [--dry-run]"
18
+ exit 1
19
+ fi
20
+
21
+ echo "Converting SQLite databases in: $DATA_PATH"
22
+ echo "This may take a while for large datasets..."
23
+ echo ""
24
+
25
+ # Check if Python script exists
26
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ PYTHON_SCRIPT="$SCRIPT_DIR/convert_wal_to_delete.py"
28
+
29
+ if [ ! -f "$PYTHON_SCRIPT" ]; then
30
+ echo "ERROR: Python script not found: $PYTHON_SCRIPT"
31
+ exit 1
32
+ fi
33
+
34
+ # Pass all arguments to the Python script
35
+ python3 "$PYTHON_SCRIPT" "$@"
36
+
37
+ exit_code=$?
38
+
39
+ if [ $exit_code -eq 0 ]; then
40
+ echo ""
41
+ echo "✓ Database conversion completed successfully"
42
+ echo ""
43
+ echo "You can now start Docker containers with:"
44
+ echo " cd $(dirname "$SCRIPT_DIR") && docker compose up -d"
45
+ else
46
+ echo ""
47
+ echo "✗ Database conversion failed with exit code $exit_code"
48
+ echo "Please check the errors above and try again"
49
+ fi
50
+
51
+ exit $exit_code
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Convert SQLite databases from WAL mode to DELETE mode for read-only Docker mounts.
4
+
5
+ This script walks through a directory tree, finds all SQLite database files,
6
+ and converts them from WAL (Write-Ahead Logging) mode to DELETE mode.
7
+ This is necessary for ethoscope databases that will be mounted read-only in Docker.
8
+
9
+ Usage:
10
+ python convert_wal_to_delete.py /path/to/ethoscope_results
11
+ python convert_wal_to_delete.py /path/to/ethoscope_results --dry-run
12
+ python convert_wal_to_delete.py /path/to/ethoscope_results --verbose
13
+ python convert_wal_to_delete.py /path/to/ethoscope_results --force
14
+ """
15
+
16
+ import argparse
17
+ import sqlite3
18
+ import sys
19
+ from pathlib import Path
20
+ from datetime import datetime, timedelta
21
+ from typing import Tuple, List
22
+
23
+
24
+ def check_journal_mode(db_path: Path) -> str:
25
+ """Check the current journal mode of a SQLite database."""
26
+ try:
27
+ conn = sqlite3.connect(f'file:{db_path}?mode=ro', uri=True)
28
+ cursor = conn.cursor()
29
+ cursor.execute("PRAGMA journal_mode;")
30
+ mode = cursor.fetchone()[0]
31
+ conn.close()
32
+ return mode
33
+ except Exception as e:
34
+ return f"ERROR: {str(e)}"
35
+
36
+
37
+ def convert_database(db_path: Path, force: bool = False, verbose: bool = False) -> Tuple[bool, str]:
38
+ """
39
+ Convert a database from WAL to DELETE mode.
40
+
41
+ Args:
42
+ db_path: Path to the database file
43
+ force: If True, skip safety checks for recently modified files
44
+ verbose: If True, print detailed progress
45
+
46
+ Returns:
47
+ Tuple of (success, message)
48
+ """
49
+ try:
50
+ # Check if file was recently modified (within last 24 hours)
51
+ if not force:
52
+ mtime = datetime.fromtimestamp(db_path.stat().st_mtime)
53
+ if datetime.now() - mtime < timedelta(hours=24):
54
+ return False, "SKIPPED: Database modified within last 24 hours (use --force to override)"
55
+
56
+ # Check current journal mode
57
+ current_mode = check_journal_mode(db_path)
58
+ if "ERROR" in current_mode:
59
+ return False, f"FAILED: Could not read journal mode: {current_mode}"
60
+
61
+ if current_mode.lower() == 'delete':
62
+ return True, "SKIPPED: Already in DELETE mode"
63
+
64
+ if verbose:
65
+ print(f" Current mode: {current_mode}")
66
+
67
+ # Open database in read-write mode for conversion
68
+ conn = sqlite3.connect(str(db_path))
69
+ cursor = conn.cursor()
70
+
71
+ # Checkpoint the WAL file to merge changes back to main database
72
+ if verbose:
73
+ print(f" Checkpointing WAL...")
74
+ cursor.execute("PRAGMA wal_checkpoint(TRUNCATE);")
75
+
76
+ # Convert to DELETE mode
77
+ if verbose:
78
+ print(f" Converting to DELETE mode...")
79
+ cursor.execute("PRAGMA journal_mode=DELETE;")
80
+ new_mode = cursor.fetchone()[0]
81
+
82
+ # Verify integrity
83
+ if verbose:
84
+ print(f" Verifying integrity...")
85
+ cursor.execute("PRAGMA integrity_check;")
86
+ integrity = cursor.fetchone()[0]
87
+
88
+ conn.close()
89
+
90
+ if integrity.lower() != 'ok':
91
+ return False, f"FAILED: Integrity check failed: {integrity}"
92
+
93
+ if new_mode.lower() != 'delete':
94
+ return False, f"FAILED: Conversion unsuccessful, mode is {new_mode}"
95
+
96
+ return True, f"SUCCESS: Converted from {current_mode} to {new_mode}"
97
+
98
+ except sqlite3.OperationalError as e:
99
+ return False, f"FAILED: Database locked or in use: {str(e)}"
100
+ except Exception as e:
101
+ return False, f"FAILED: {str(e)}"
102
+
103
+
104
+ def find_databases(root_path: Path, pattern: str = "*.db") -> List[Path]:
105
+ """Find all database files in the directory tree."""
106
+ return list(root_path.rglob(pattern))
107
+
108
+
109
+ def main():
110
+ parser = argparse.ArgumentParser(
111
+ description="Convert SQLite databases from WAL mode to DELETE mode",
112
+ formatter_class=argparse.RawDescriptionHelpFormatter,
113
+ epilog="""
114
+ Examples:
115
+ # Dry run to see what would be converted
116
+ python convert_wal_to_delete.py /mnt/ethoscope_data/results --dry-run
117
+
118
+ # Convert all databases with verbose output
119
+ python convert_wal_to_delete.py /mnt/ethoscope_data/results --verbose
120
+
121
+ # Force conversion even for recently modified databases
122
+ python convert_wal_to_delete.py /mnt/ethoscope_data/results --force
123
+ """
124
+ )
125
+
126
+ parser.add_argument(
127
+ 'root_path',
128
+ type=Path,
129
+ help='Root directory to search for database files'
130
+ )
131
+ parser.add_argument(
132
+ '--dry-run',
133
+ action='store_true',
134
+ help='Show what would be done without making changes'
135
+ )
136
+ parser.add_argument(
137
+ '--verbose',
138
+ action='store_true',
139
+ help='Print detailed progress information'
140
+ )
141
+ parser.add_argument(
142
+ '--force',
143
+ action='store_true',
144
+ help='Force conversion even for recently modified databases'
145
+ )
146
+ parser.add_argument(
147
+ '--pattern',
148
+ type=str,
149
+ default='*.db',
150
+ help='File pattern to match (default: *.db)'
151
+ )
152
+
153
+ args = parser.parse_args()
154
+
155
+ # Validate root path
156
+ if not args.root_path.exists():
157
+ print(f"ERROR: Path does not exist: {args.root_path}", file=sys.stderr)
158
+ sys.exit(1)
159
+
160
+ if not args.root_path.is_dir():
161
+ print(f"ERROR: Path is not a directory: {args.root_path}", file=sys.stderr)
162
+ sys.exit(1)
163
+
164
+ print(f"Searching for database files in: {args.root_path}")
165
+ print(f"Pattern: {args.pattern}")
166
+
167
+ if args.dry_run:
168
+ print("DRY RUN MODE - No changes will be made")
169
+
170
+ print()
171
+
172
+ # Find all database files
173
+ databases = find_databases(args.root_path, args.pattern)
174
+
175
+ if not databases:
176
+ print("No database files found.")
177
+ sys.exit(0)
178
+
179
+ print(f"Found {len(databases)} database file(s)")
180
+ print()
181
+
182
+ # Process each database
183
+ stats = {
184
+ 'total': len(databases),
185
+ 'converted': 0,
186
+ 'skipped': 0,
187
+ 'failed': 0,
188
+ 'already_delete': 0
189
+ }
190
+
191
+ for i, db_path in enumerate(databases, 1):
192
+ rel_path = db_path.relative_to(args.root_path)
193
+ size_mb = db_path.stat().st_size / (1024 * 1024)
194
+
195
+ print(f"[{i}/{len(databases)}] {rel_path} ({size_mb:.1f} MB)")
196
+
197
+ if args.dry_run:
198
+ mode = check_journal_mode(db_path)
199
+ print(f" Current mode: {mode}")
200
+ if mode.lower() == 'wal':
201
+ print(f" Would convert from WAL to DELETE")
202
+ elif mode.lower() == 'delete':
203
+ print(f" Already in DELETE mode")
204
+ stats['skipped'] += 1
205
+ else:
206
+ success, message = convert_database(db_path, args.force, args.verbose)
207
+ print(f" {message}")
208
+
209
+ if success:
210
+ if "Already in DELETE mode" in message:
211
+ stats['already_delete'] += 1
212
+ else:
213
+ stats['converted'] += 1
214
+ elif "SKIPPED" in message:
215
+ stats['skipped'] += 1
216
+ else:
217
+ stats['failed'] += 1
218
+
219
+ print()
220
+
221
+ # Print summary
222
+ print("=" * 60)
223
+ print("SUMMARY")
224
+ print("=" * 60)
225
+ print(f"Total databases found: {stats['total']}")
226
+
227
+ if args.dry_run:
228
+ print(f"Would be processed: {stats['skipped']}")
229
+ else:
230
+ print(f"Successfully converted: {stats['converted']}")
231
+ print(f"Already in DELETE mode: {stats['already_delete']}")
232
+ print(f"Skipped: {stats['skipped']}")
233
+ print(f"Failed: {stats['failed']}")
234
+
235
+ if stats['failed'] > 0:
236
+ sys.exit(1)
237
+
238
+
239
+ if __name__ == '__main__':
240
+ main()
@@ -21,8 +21,8 @@ def _connect_db(path):
21
21
 
22
22
  When the database directory is read-only (e.g., mounted with :ro in Docker),
23
23
  SQLite cannot create journal/WAL files and will fail to open the database.
24
- This function detects read-only filesystems and uses immutable mode to bypass
25
- this limitation.
24
+ This function detects read-only filesystems and uses appropriate connection
25
+ parameters to handle WAL-mode databases safely.
26
26
 
27
27
  Args:
28
28
  path (str): Path to the SQLite database file
@@ -31,8 +31,9 @@ def _connect_db(path):
31
31
  sqlite3.Connection: Database connection object
32
32
 
33
33
  Note:
34
- When using immutable mode on an active database with WAL journaling,
35
- any uncommitted WAL data will not be visible. This is acceptable for
34
+ For WAL-mode databases on read-only mounts, this function uses mode=ro
35
+ with nolock=1 to prevent "database disk image is malformed" errors.
36
+ Any uncommitted WAL data will not be visible, which is acceptable for
36
37
  read-only mounts where the data cannot change anyway.
37
38
  """
38
39
  path_str = str(path)
@@ -40,8 +41,30 @@ def _connect_db(path):
40
41
 
41
42
  # Check if we can write to the directory
42
43
  if not os.access(dir_path, os.W_OK):
43
- # Read-only filesystem - use immutable mode to avoid journal/lock file creation
44
- return sqlite3.connect(f"file:{path_str}?immutable=1", uri=True)
44
+ # Read-only filesystem - check if database is in WAL mode
45
+ try:
46
+ # Try to detect WAL mode by opening in read-only mode first
47
+ temp_conn = sqlite3.connect(f"file:{path_str}?mode=ro", uri=True)
48
+ cursor = temp_conn.cursor()
49
+ cursor.execute("PRAGMA journal_mode;")
50
+ journal_mode = cursor.fetchone()[0].lower()
51
+ temp_conn.close()
52
+
53
+ if journal_mode == 'wal':
54
+ # WAL mode on read-only mount: use mode=ro with nolock
55
+ # This prevents "database disk image is malformed" errors
56
+ # by avoiding operations that require WAL/SHM files
57
+ return sqlite3.connect(
58
+ f"file:{path_str}?mode=ro&nolock=1",
59
+ uri=True,
60
+ timeout=10.0
61
+ )
62
+ else:
63
+ # Non-WAL mode: use immutable mode for better performance
64
+ return sqlite3.connect(f"file:{path_str}?immutable=1", uri=True)
65
+ except Exception:
66
+ # If detection fails, fall back to immutable mode
67
+ return sqlite3.connect(f"file:{path_str}?immutable=1", uri=True)
45
68
  else:
46
69
  # Normal read-write access
47
70
  return sqlite3.connect(path_str)
@@ -916,7 +939,41 @@ def read_single_roi_optimized(
916
939
  sql_query = "SELECT * FROM ROI_{} WHERE t >= {} {}".format(
917
940
  file["region_id"], min_time, max_time_condtion
918
941
  )
919
- data = pd.read_sql_query(sql_query, conn)
942
+
943
+ # Execute query with retry logic for WAL-related errors
944
+ try:
945
+ data = pd.read_sql_query(sql_query, conn)
946
+ except sqlite3.DatabaseError as e:
947
+ # Handle "database disk image is malformed" errors
948
+ # This can occur with WAL-mode databases on read-only mounts
949
+ if "malformed" in str(e).lower() or "disk image" in str(e).lower():
950
+ print(f"Warning: Database error for ROI {file['region_id']}, attempting retry with fresh connection...")
951
+
952
+ # Get database path from file metadata
953
+ db_path = file.get("path")
954
+ if not db_path:
955
+ print(f"Error: Cannot retry - database path not found in file metadata")
956
+ raise
957
+
958
+ # Create a fresh connection just for the retry
959
+ retry_conn = None
960
+ try:
961
+ retry_conn = _connect_db(db_path)
962
+ data = pd.read_sql_query(sql_query, retry_conn)
963
+ print(f"Success: ROI {file['region_id']} loaded on retry")
964
+ except Exception as retry_error:
965
+ print(f"Error: Retry failed for ROI {file['region_id']}: {retry_error}")
966
+ raise
967
+ finally:
968
+ # Clean up retry connection
969
+ if retry_conn:
970
+ try:
971
+ retry_conn.close()
972
+ except Exception:
973
+ pass
974
+ else:
975
+ # Re-raise other database errors
976
+ raise
920
977
 
921
978
  if "id" in data.columns:
922
979
  # Check if 'id' is a primary key (reuse cursor)
@@ -1,19 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(ls:*)",
5
- "Bash(git checkout:*)",
6
- "Bash(docker images:*)",
7
- "Read(//tmp/**)",
8
- "Bash(JUPYTER_HUB_TAG=5.3.0 ETHOSCOPE_LAB_TAG=1.1 docker compose build:*)",
9
- "Bash(tee:*)",
10
- "Bash(docker compose:*)",
11
- "Bash(find:*)",
12
- "Bash(sqlite3:*)",
13
- "Bash(python3:*)",
14
- "Bash(docker exec:*)",
15
- "Bash(docker cp:*)"
16
- ],
17
- "deny": []
18
- }
19
- }
@@ -1,85 +0,0 @@
1
- # Ethoscopelab docker instance
2
-
3
- Note: Most users will **not** need to recreate this image. These instructions are just provided as reference.
4
- The ethoscopelab docker instance lives on dockerhub at the following address: [https://hub.docker.com/r/ggilestro/ethoscope-lab](https://hub.docker.com/r/ggilestro/ethoscope-lab) and this is what regular users should download and run. Follow instructions there and on the [ethoscopy manual](https://bookstack.lab.gilest.ro/books/ethoscopy/page/getting-started).
5
-
6
-
7
- ## Docker files that were used to create the ethoscope-lab docker instance
8
-
9
- The files in this folder can be used to recreate the image as uploaded on dockerhub.
10
-
11
- The command to use to recreate that image is `JUPYTER_HUB_TAG=5.3.0 ETHOSCOPE_LAB_TAG=1.0 docker compose build`. This creates the image with the specified tag. For Docker Hub deployment, push the image: `docker push ggilestro/ethoscope-lab:1.0`. To also create a latest tag: `docker tag ggilestro/ethoscope-lab:1.0 ggilestro/ethoscope-lab:latest && docker push ggilestro/ethoscope-lab:latest`. You can verify your local images with `docker images | grep ethoscope-lab`.
12
-
13
- After creation, the image can be run using the enclosed `docker-compose.yml` file, replacing values as fit.
14
-
15
- ## Add new users to the JupyterHub
16
-
17
- To add new users to the JupyterHub instance:
18
-
19
- ### 1. Modify the jupyterhub_config.py file
20
-
21
- Edit the `allowed_users` set on lines 14-19 to include your new usernames:
22
-
23
- ```python
24
- c.Authenticator.allowed_users = {
25
- 'amadabhushi', 'ggilestro', 'mjoyce', 'lguo',
26
- 'labguest1', 'labguest2', 'labguest3', 'labguest4',
27
- 'labguest5', 'labguest6', 'labguest7', 'labguest8',
28
- 'ethoscopelab', 'newuser1', 'newuser2' # Add your new users here
29
- }
30
- ```
31
-
32
- To make a user an admin, add them to the `admin_users` set on line 22:
33
-
34
- ```python
35
- c.Authenticator.admin_users = {'ggilestro', 'newadmin'}
36
- ```
37
-
38
- ### 2. Restart the Docker container
39
-
40
- After modifying the config file:
41
-
42
- ```bash
43
- docker compose down
44
- docker compose up -d
45
- ```
46
-
47
- **Notes:**
48
- - All users share the same password: `ethoscope` (line 11)
49
- - The system uses DummyAuthenticator for simple shared-password authentication
50
- - Each user gets their own home directory at `/home/{username}`
51
- - Home directories are created automatically when users first log in
52
-
53
- ## Mounting Home Directories as Volumes
54
-
55
- To persist user data and notebooks across container restarts, you should mount user home directories as Docker volumes. This is done by modifying the `docker-compose.yml` file.
56
-
57
- ### Benefits of mounting home directories:
58
-
59
- 1. **Data Persistence**: User notebooks, data files, and configurations survive container restarts and updates
60
- 2. **Backup and Recovery**: Easy to backup user data by copying the mounted directories
61
- 3. **Performance**: Direct access to host filesystem, avoiding container storage overhead
62
- 4. **Sharing**: Users can access their files from the host system if needed
63
-
64
- ### Example volume configuration:
65
-
66
- Add volumes to your `docker-compose.yml`:
67
-
68
- ```yaml
69
- services:
70
- jupyterhub:
71
- volumes:
72
- - ./user_data:/home # Maps host ./user_data to container /home
73
- - ./jupyterhub_config.py:/srv/jupyterhub/jupyterhub_config.py
74
- ```
75
-
76
- Or for individual user directories:
77
-
78
- ```yaml
79
- volumes:
80
- - ./users/ggilestro:/home/ggilestro
81
- - ./users/amadabhushi:/home/amadabhushi
82
- - ./users/shared:/home/shared # Shared directory for all users
83
- ```
84
-
85
- This ensures all user work is preserved even when containers are recreated or updated.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes