From 09662337bbd40921e2f4bdf74c0b08ffedb1a3b5 Mon Sep 17 00:00:00 2001 From: agessaman Date: Wed, 11 Feb 2026 22:26:34 -0800 Subject: [PATCH] fix: Enhance database schema migration for improved reliability - Updated the database migration logic to handle missing tables gracefully, ensuring that migrations for existing installations do not block the process. - Added checks for the existence of specific columns in the `repeater_contacts`, `complete_contact_tracking`, and `mesh_connections` tables, allowing for more robust schema updates. - Introduced logging for migration errors to improve visibility and troubleshooting during the migration process. - Ensured that the migration runs seamlessly for users accessing the web viewer without prior bot startup, enhancing user experience. --- Dockerfile | 4 +- docker-setup.sh | 7 +-- modules/repeater_manager.py | 120 ++++++++++++++++++++---------------- 3 files changed, 72 insertions(+), 59 deletions(-) diff --git a/Dockerfile b/Dockerfile index 38b9481..d6915b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,9 +54,9 @@ ENV PATH=/home/meshcore/.local/bin:$PATH \ # Switch to non-root user USER meshcore -# Health check +# Health check: verify the main process (PID 1, the bot) is still running HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ - CMD python3 -c "import sys; sys.exit(0)" || exit 1 + CMD ["sh", "-c", "kill -0 1"] # Default command CMD ["python3", "meshcore_bot.py", "--config", "/data/config/config.ini"] diff --git a/docker-setup.sh b/docker-setup.sh index 8a4a339..f45d123 100755 --- a/docker-setup.sh +++ b/docker-setup.sh @@ -36,18 +36,17 @@ update_config() { awk -v section="$section" -v key="$key" -v value="$value" ' /^\[/ { + # Emit missing key at end of target section before we update state + leaving_target = (in_section && need_add) in_section = ($0 == "[" section "]") need_add = in_section + if (leaving_target) { print key " = " value } } in_section && $0 ~ "^" key "[[:space:]]*=" { print key " = " value need_add = 0 next } - need_add && in_section && $0 !~ /^\[/ { - print key " = " value - need_add = 0 - } { print } END { if (need_add && in_section) { print key " = " value } diff --git a/modules/repeater_manager.py b/modules/repeater_manager.py index 84da61e..18db43d 100644 --- a/modules/repeater_manager.py +++ b/modules/repeater_manager.py @@ -211,68 +211,82 @@ class RepeaterManager: raise def _migrate_database_schema(self): - """Handle database schema migration for existing installations""" - try: - # Check if the new location columns exist in repeater_contacts - with sqlite3.connect(self.db_path, timeout=30.0) as conn: - cursor = conn.cursor() - cursor.execute("PRAGMA table_info(repeater_contacts)") - columns = [row[1] for row in cursor.fetchall()] - - # Add missing location columns if they don't exist - new_columns = [ - ('latitude', 'REAL'), - ('longitude', 'REAL'), - ('city', 'TEXT'), - ('state', 'TEXT'), - ('country', 'TEXT') - ] - - for column_name, column_type in new_columns: - if column_name not in columns: - self.logger.info(f"Adding missing column to repeater_contacts: {column_name}") - cursor.execute(f"ALTER TABLE repeater_contacts ADD COLUMN {column_name} {column_type}") + """Handle database schema migration for existing installations. + Each table is migrated in isolation so one missing table or error does not block the rest. + Important for web viewer: migration runs when RepeaterManager is created, so contacts page + works for users who open the viewer without having started the bot after upgrade. + """ + with sqlite3.connect(self.db_path, timeout=30.0) as conn: + cursor = conn.cursor() + + # repeater_contacts: add location columns only if table exists + try: + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='repeater_contacts'") + if cursor.fetchone(): + cursor.execute("PRAGMA table_info(repeater_contacts)") + columns = [row[1] for row in cursor.fetchall()] + for column_name, column_type in [ + ('latitude', 'REAL'), ('longitude', 'REAL'), ('city', 'TEXT'), + ('state', 'TEXT'), ('country', 'TEXT') + ]: + if column_name not in columns: + self.logger.info(f"Adding missing column to repeater_contacts: {column_name}") + cursor.execute(f"ALTER TABLE repeater_contacts ADD COLUMN {column_name} {column_type}") + conn.commit() + except Exception as e: + self.logger.warning(f"Migration repeater_contacts: {e}") + + # complete_contact_tracking: path columns and is_starred (required for contacts page) + try: + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='complete_contact_tracking'") + if cursor.fetchone(): + cursor.execute("PRAGMA table_info(complete_contact_tracking)") + tracking_columns = [row[1] for row in cursor.fetchall()] + for column_name, column_type in [ + ('out_path', 'TEXT'), ('out_path_len', 'INTEGER'), ('snr', 'REAL') + ]: + if column_name not in tracking_columns: + self.logger.info(f"Adding missing column to complete_contact_tracking: {column_name}") + cursor.execute(f"ALTER TABLE complete_contact_tracking ADD COLUMN {column_name} {column_type}") + conn.commit() + if 'is_starred' not in tracking_columns: + self.logger.info("Adding is_starred column to complete_contact_tracking") + cursor.execute("ALTER TABLE complete_contact_tracking ADD COLUMN is_starred BOOLEAN DEFAULT 0") conn.commit() - - # Check if the new path columns exist in complete_contact_tracking - cursor.execute("PRAGMA table_info(complete_contact_tracking)") - tracking_columns = [row[1] for row in cursor.fetchall()] - - # Add missing path columns if they don't exist - path_columns = [ - ('out_path', 'TEXT'), - ('out_path_len', 'INTEGER'), - ('snr', 'REAL') - ] - - for column_name, column_type in path_columns: - if column_name not in tracking_columns: - self.logger.info(f"Adding missing column to complete_contact_tracking: {column_name}") - cursor.execute(f"ALTER TABLE complete_contact_tracking ADD COLUMN {column_name} {column_type}") - conn.commit() - - # Add is_starred column for path command bias - if 'is_starred' not in tracking_columns: - self.logger.info("Adding is_starred column to complete_contact_tracking") - cursor.execute("ALTER TABLE complete_contact_tracking ADD COLUMN is_starred BOOLEAN DEFAULT 0") - conn.commit() - - # Check if observed_paths table exists and add packet_hash column if needed + except Exception as e: + self.logger.warning(f"Migration complete_contact_tracking: {e}") + + # observed_paths: packet_hash + try: cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='observed_paths'") if cursor.fetchone(): cursor.execute("PRAGMA table_info(observed_paths)") observed_paths_columns = [row[1] for row in cursor.fetchall()] - if 'packet_hash' not in observed_paths_columns: self.logger.info("Adding packet_hash column to observed_paths") cursor.execute("ALTER TABLE observed_paths ADD COLUMN packet_hash TEXT") conn.commit() - # Index will be created in _init_repeater_tables() after migration completes - - self.logger.info("Database schema migration completed") - - except Exception as e: - self.logger.error(f"Error during database schema migration: {e}") + except Exception as e: + self.logger.warning(f"Migration observed_paths: {e}") + + # mesh_connections: graph/viewer columns + try: + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='mesh_connections'") + if cursor.fetchone(): + cursor.execute("PRAGMA table_info(mesh_connections)") + mc_columns = [row[1] for row in cursor.fetchall()] + for col_name, col_type in [ + ('from_public_key', 'TEXT'), ('to_public_key', 'TEXT'), + ('avg_hop_position', 'REAL'), ('geographic_distance', 'REAL'), + ]: + if col_name not in mc_columns: + self.logger.info(f"Adding missing column to mesh_connections: {col_name}") + cursor.execute(f"ALTER TABLE mesh_connections ADD COLUMN {col_name} {col_type}") + conn.commit() + except Exception as e: + self.logger.warning(f"Migration mesh_connections: {e}") + + self.logger.info("Database schema migration completed") async def track_contact_advertisement(self, advert_data: Dict, signal_info: Dict = None, packet_hash: Optional[str] = None) -> bool: """Track any contact advertisement in the complete tracking database"""