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.
This commit is contained in:
agessaman
2026-02-11 22:26:34 -08:00
parent 9fc0450727
commit 09662337bb
3 changed files with 72 additions and 59 deletions
+2 -2
View File
@@ -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"]
+3 -4
View File
@@ -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 }
+67 -53
View File
@@ -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"""