1+ #! /bin/bash
2+ set -euo pipefail # Exit on errors and undefined variables
3+
4+ # --------------------------
5+ # Start timer
6+ # --------------------------
7+ start_time=$( date +%s)
8+
9+ # --------------------------
10+ # CI Environment or Local excecution
11+ # --------------------------
12+ if [ " ${IS_CI:- false} " = " TRUE" ]; then
13+ echo " Running in CI environment"
14+ else
15+ # --------------------------
16+ # Choose operating mode
17+ # --------------------------
18+ echo " How do you want to proceed?"
19+ echo " [1] Create a copy and make changes on that (safe mode)"
20+ echo " [2] Make changes to the existing database"
21+ read -p " Enter your choice [1]: " mode_choice
22+
23+ # Default to safe mode (1) if user enters nothing
24+ mode_choice=${mode_choice:- 1}
25+
26+ if [ " $mode_choice " != " 1" ] && [ " $mode_choice " != " 2" ]; then
27+ echo " ❌ Invalid choice. Aborting."
28+ exit 1
29+ fi
30+
31+ # If user chooses to modify existing database, ask for confirmation
32+ if [ " $mode_choice " = " 2" ]; then
33+ echo
34+ echo " ⚠️ WARNING: This will delete all content in the default database! ⚠️"
35+ echo " ⏳ Press Ctrl+C to cancel if you do not wish to proceed."
36+ echo
37+ read -p " 🔑 Type 'I Agree' to continue: " user_input
38+
39+ if [ " $user_input " != " I Agree" ]; then
40+ echo " ❌ Incorrect agreement. Aborting."
41+ exit 1
42+ fi
43+ fi
44+
45+ # --------------------------
46+ # Configuration
47+ # --------------------------
48+ DB_FILE=" thin-db.sql"
49+ WORKDIR=" /var/www/html" # DDEV web container's docroot
50+
51+ # Define patterns for tables to truncate
52+ TRUNCATE_PATTERNS=(
53+ " ^node"
54+ " ^media"
55+ " ^taxonomy_term"
56+ " ^feeds"
57+ " ^path_alias"
58+ " ^content_moderation"
59+ " ^crop"
60+ " ^cache_"
61+ " ^redirect"
62+ " file_managed$"
63+ " s3fs_file$"
64+ " search_api_item$"
65+ " admin_audit_trail$"
66+ " taxonomy_index$"
67+ " file_usage$"
68+ " simple_sitemap$"
69+ " batch$"
70+ )
71+
72+ # Get original database details
73+ ORIGINAL_DB=$( drush sql:connect --database=default | sed -n ' s/.*--database=\([^ ]*\).*/\1/p' )
74+ DRUSH_ARGS=" "
75+
76+ if [ " $mode_choice " = " 1" ]; then
77+ # Create a copy of the database using DDEV's mysql command
78+ TARGET_DB=" ${ORIGINAL_DB} _thin"
79+ echo " Creating working copy of database..."
80+
81+ # Add db_copy configuration to settings.ddev.php
82+ SETTINGS_FILE=" docroot/sites/default/settings.ddev.php"
83+ DB_COPY_CONFIG=$( cat << EOF
84+
85+ // Automatically added by thin-db-export script - START
86+ \$ databases['${TARGET_DB} ']['default'] = [
87+ 'database' => '${TARGET_DB} ',
88+ 'username' => 'db',
89+ 'password' => 'db',
90+ 'host' => 'db',
91+ 'port' => '3306',
92+ 'driver' => 'mysql',
93+ ];
94+ // Automatically added by thin-db-export script - END
95+ EOF
96+ )
97+
98+ # Backup original file and append configuration
99+ if [ -f " $SETTINGS_FILE " ]; then
100+ cp " $SETTINGS_FILE " " ${SETTINGS_FILE} .bak"
101+ echo " $DB_COPY_CONFIG " >> " $SETTINGS_FILE "
102+ echo " ✅ Added ${TARGET_DB} configuration to settings.ddev.php"
103+ else
104+ echo " ❌ Error: settings.ddev.php not found!"
105+ exit 5
106+ fi
107+
108+ # Create temporary SQL file for database creation
109+ TEMP_SQL=" /tmp/create_db.sql"
110+ echo " DROP DATABASE IF EXISTS ${TARGET_DB} ; CREATE DATABASE ${TARGET_DB} ;" > " $TEMP_SQL "
111+
112+ # Use mysql root user to create the database
113+ mysql -uroot -proot < " $TEMP_SQL "
114+ rm " $TEMP_SQL "
115+
116+ # Grant privileges to the 'db' user
117+ mysql -uroot -proot -e " GRANT ALL PRIVILEGES ON ${TARGET_DB} .* TO 'db'@'%';"
118+
119+ # Copy the complete structure first
120+ echo " Copying database structure..."
121+ STRUCTURE_FILE=" /tmp/db_structure.sql"
122+ drush sql:dump \
123+ --extra=" --skip-comments --skip-dump-date --no-data" \
124+ --result-file=" $STRUCTURE_FILE "
125+
126+ # Import the structure to the new database
127+ drush sql:query --database=" ${TARGET_DB} " --file=" $STRUCTURE_FILE "
128+ rm -f " $STRUCTURE_FILE "
129+
130+ # Copy only the essential data (excluding content tables)
131+ echo " Copying essential data..."
132+
133+ # Disable foreign key checks for data copy
134+ drush sql:query --database=" ${TARGET_DB} " " SET FOREIGN_KEY_CHECKS=0;"
135+
136+ # First handle users and related tables in the correct order
137+ CORE_TABLES=(
138+ " users"
139+ " users_field_data"
140+ " user__roles"
141+ " user__user_picture"
142+ " user__field_pending_expire_sent"
143+ " user__field_password_expiration"
144+ )
145+
146+ for table in " ${CORE_TABLES[@]} " ; do
147+ echo " Copying core table: $table "
148+ drush sql:query --database=" ${TARGET_DB} " " TRUNCATE TABLE \` ${table} \` ;"
149+ drush sql:query " REPLACE INTO ${TARGET_DB} .$table SELECT * FROM ${ORIGINAL_DB} .$table "
150+ done
151+
152+ # Then copy remaining tables
153+ TABLES_TO_COPY=$( drush sql:query "
154+ SELECT TABLE_NAME
155+ FROM information_schema.TABLES
156+ WHERE TABLE_SCHEMA = '${ORIGINAL_DB} '
157+ AND TABLE_NAME NOT REGEXP '^(node|media|taxonomy_term|feeds|path_alias|content_moderation|crop|cache|redirect)'
158+ AND TABLE_NAME NOT IN (
159+ 'file_managed', 's3fs_file', 'search_api_item', 'admin_audit_trail',
160+ 'taxonomy_index', 'file_usage', 'simple_sitemap', 'batch',
161+ 'users', 'users_field_data', 'user__roles', 'user__user_picture',
162+ 'user__field_pending_expire_sent', 'user__field_password_expiration'
163+ )
164+ " )
165+
166+ for table in $TABLES_TO_COPY ; do
167+ echo " Copying table: $table "
168+ drush sql:query --database=" ${TARGET_DB} " " TRUNCATE TABLE \` ${table} \` ;"
169+ drush sql:query " INSERT INTO ${TARGET_DB} .$table SELECT * FROM ${ORIGINAL_DB} .$table "
170+ done
171+
172+ # Re-enable foreign key checks
173+ drush sql:query --database=" ${TARGET_DB} " " SET FOREIGN_KEY_CHECKS=1;"
174+
175+ DRUSH_ARGS=" --database=${TARGET_DB} "
176+ echo " ✅ Database copy created successfully."
177+ fi
178+
179+ # --------------------------
180+ # Delete All Content
181+ # --------------------------
182+ echo " 🗑️ Deleting all content..."
183+
184+ # Disable foreign key checks
185+ drush sql:query $DRUSH_ARGS " SET FOREIGN_KEY_CHECKS=0;"
186+
187+ # Get the target database name based on mode
188+ DB_NAME=$( [ " $mode_choice " = " 1" ] && echo " $TARGET_DB " || echo " $ORIGINAL_DB " )
189+
190+ # Truncate tables using improved pattern matching
191+ for pattern in " ${TRUNCATE_PATTERNS[@]} " ; do
192+ echo " Truncating tables matching: ${pattern} "
193+ drush sql:query $DRUSH_ARGS " SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '${DB_NAME} ' AND TABLE_NAME REGEXP '${pattern} '" \
194+ | grep -v ' ^TABLE_NAME$' \
195+ | while read -r table; do
196+ [ -z " ${table} " ] && continue
197+ echo " Truncating ${table} "
198+ drush sql:query $DRUSH_ARGS " TRUNCATE TABLE \` ${table} \` ;"
199+ done
200+ done
201+
202+ # --------------------------
203+ # Additional Cleanup
204+ # --------------------------
205+ echo " 🧹 Cleaning rebuildable data..."
206+ drush sql:query $DRUSH_ARGS "
207+ TRUNCATE cachetags;
208+ TRUNCATE cache_entity;
209+ TRUNCATE cache_menu;
210+ TRUNCATE cache_render;
211+ TRUNCATE cache_data;
212+ DELETE FROM key_value WHERE collection IN (
213+ 'pathauto_state.media',
214+ 'pathauto_state.node',
215+ 'pathauto_state.taxonomy_term',
216+ 'pathauto_state.user'
217+ );
218+ "
219+
220+ # Re-enable foreign key checks
221+ drush sql:query $DRUSH_ARGS " SET FOREIGN_KEY_CHECKS=1;"
222+
223+ # --------------------------
224+ # Sanitize Users
225+ # --------------------------
226+ echo " 👤 Deleting users..."
227+ drush sql:query $DRUSH_ARGS "
228+ DELETE FROM users_field_data WHERE uid != 1;
229+ DELETE FROM users WHERE uid != 1;
230+ DELETE FROM user__roles WHERE entity_id != 1;
231+ DELETE FROM user__user_picture WHERE entity_id != 1;
232+ DELETE FROM user__field_pending_expire_sent WHERE entity_id != 1;
233+ DELETE FROM user__field_password_expiration WHERE entity_id != 1;
234+ "
235+
236+ # --------------------------
237+ # Export Database
238+ # --------------------------
239+ echo " 📦 Exporting database..."
240+
241+ # Generate precise exclusion list
242+ EXCLUDED_TABLES=$( drush sql:query $DRUSH_ARGS "
243+ SELECT GROUP_CONCAT(TABLE_NAME SEPARATOR ',')
244+ FROM information_schema.TABLES
245+ WHERE TABLE_SCHEMA = '${DB_NAME} '
246+ AND TABLE_NAME IN (
247+ 'node',
248+ 'node_field_data',
249+ 'node__body',
250+ 'media',
251+ 'taxonomy_term_data',
252+ 'taxonomy_term_field_data',
253+ 'feeds_feed',
254+ 'path_alias',
255+ 'content_moderation_state',
256+ 'crop',
257+ 'cache',
258+ 'cache_bootstrap',
259+ 'cache_config',
260+ 'cache_container',
261+ 'cache_data',
262+ 'cache_default',
263+ 'cache_discovery',
264+ 'cache_dynamic_page_cache',
265+ 'cache_entity',
266+ 'cache_menu',
267+ 'cache_render',
268+ 'cache_toolbar',
269+ 'redirect',
270+ 'file_managed',
271+ 's3fs_file',
272+ 'search_api_item',
273+ 'admin_audit_trail',
274+ 'taxonomy_index',
275+ 'file_usage',
276+ 'simple_sitemap',
277+ 'batch'
278+ )
279+ " )
280+
281+ drush sql:dump $DRUSH_ARGS \
282+ --structure-tables-list=" ${EXCLUDED_TABLES} " \
283+ --ordered-dump \
284+ --extra=" --skip-comments --skip-dump-date --single-transaction --no-tablespaces" \
285+ --result-file=" ${WORKDIR} /${DB_FILE} "
286+
287+ # --------------------------
288+ # Compress and Verify
289+ # --------------------------
290+ echo " 🗜️ Compressing..."
291+ cd " ${WORKDIR} "
292+ gzip -9 " ${DB_FILE} "
293+
294+ mkdir -p " ${WORKDIR} /backups/"
295+ mv " ${DB_FILE} .gz" " ${WORKDIR} /backups/"
296+
297+ echo " ✅ Done! Final file: ${WORKDIR} /backups/${DB_FILE} .gz"
298+ echo " File size: $( du -h ${WORKDIR} /backups/${DB_FILE} .gz | cut -f1) "
299+
300+ # Cleanup
301+ if [ " $mode_choice " = " 1" ]; then
302+ echo " Cleaning up..."
303+
304+ # Remove database configuration from settings.ddev.php
305+ SETTINGS_FILE=" docroot/sites/default/settings.ddev.php"
306+ if [ -f " $SETTINGS_FILE " ]; then
307+ # Use sed to delete the added block
308+ sed -i ' /\/\/ Automatically added by thin-db-export script - START/,/\/\/ Automatically added by thin-db-export script - END/d' " $SETTINGS_FILE "
309+
310+ # Remove backup file
311+ rm -f " ${SETTINGS_FILE} .bak"
312+
313+ echo " ✅ Removed ${TARGET_DB} configuration from settings.ddev.php"
314+ else
315+ echo " ⚠️ settings.ddev.php not found during cleanup"
316+ fi
317+
318+ # Drop the temporary database
319+ echo " Cleaning up temporary database..."
320+ drush sql:query " DROP DATABASE ${TARGET_DB} "
321+ fi
322+
323+ fi
324+
325+ # --------------------------
326+ # End timer and report duration
327+ # --------------------------
328+ end_time=$( date +%s)
329+ duration=$(( end_time - start_time))
330+
331+ echo " Completed in ${duration} seconds"
0 commit comments