feat: name database file after device name for multi-device support

Database file is now named {device_name}.db (e.g. MarWoj.db) instead of
the generic mc-webui.db. On first boot, mc-webui.db is automatically
renamed once the device name is detected. On subsequent boots, the
existing device-named DB is found by scanning the config directory.

This enables future multi-device support where each MeshCore device
has its own separate database file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-03-20 21:07:58 +01:00
parent ca0ba37be5
commit a1f2a1c5ef
3 changed files with 109 additions and 13 deletions

View File

@@ -26,7 +26,7 @@ class Config:
MC_ARCHIVE_RETENTION_DAYS = int(os.getenv('MC_ARCHIVE_RETENTION_DAYS', '7'))
# v2: Database
MC_DB_PATH = os.getenv('MC_DB_PATH', '') # empty = auto: {MC_CONFIG_DIR}/mc-webui.db
MC_DB_PATH = os.getenv('MC_DB_PATH', '') # empty = auto: {MC_CONFIG_DIR}/{device_name}.db
# v2: TCP connection (alternative to serial, e.g. meshcore-proxy)
MC_TCP_HOST = os.getenv('MC_TCP_HOST', '') # empty = use serial

View File

@@ -4,10 +4,13 @@ mc-webui v2 — Flask application entry point
Direct device communication via meshcore library (no bridge).
"""
import json
import logging
import re
import shlex
import threading
import time
from pathlib import Path
from flask import Flask, request as flask_request
from flask_socketio import SocketIO, emit
from app.config import config, runtime_config
@@ -49,6 +52,92 @@ db = None
device_manager = None
def _sanitize_db_name(name: str) -> str:
"""Sanitize device name for use as database filename."""
sanitized = re.sub(r'[<>:"/\\|?*\x00-\x1f]', '_', name)
sanitized = sanitized.strip('. ')
return sanitized or 'device'
def _resolve_db_path() -> Path:
"""Resolve database path, preferring existing device-named DB files.
Priority:
1. Explicit MC_DB_PATH that is NOT mc-webui.db -> use as-is
2. Existing device-named .db file in config dir (most recently modified)
3. Existing mc-webui.db (legacy, will be renamed on device connect)
4. New mc-webui.db (will be renamed on device connect)
"""
if config.MC_DB_PATH:
p = Path(config.MC_DB_PATH)
if p.name != 'mc-webui.db':
return p
db_dir = p.parent
else:
db_dir = Path(config.MC_CONFIG_DIR)
# Scan for existing device-named DBs (anything except mc-webui.db)
try:
existing = sorted(
[f for f in db_dir.glob('*.db')
if f.name != 'mc-webui.db' and f.is_file()],
key=lambda f: f.stat().st_mtime,
reverse=True
)
if existing:
logger.info(f"Found device-named database: {existing[0].name}")
return existing[0]
except OSError:
pass
# Fallback: mc-webui.db (legacy or new install)
return db_dir / 'mc-webui.db'
def _migrate_db_to_device_name(db, device_name: str):
"""Rename DB file to match device name if needed.
Handles three cases:
- Current DB already matches device name -> no-op
- Target DB exists (different device was here before) -> switch to it
- Target DB doesn't exist -> rename current DB files
"""
safe_name = _sanitize_db_name(device_name)
current = db.db_path
target = current.parent / f"{safe_name}.db"
if current.resolve() == target.resolve():
return
if target.exists():
# Target DB already exists — switch to it
db.db_path = target
db._init_db()
logger.info(f"Switched to existing database: {target.name}")
return
# Checkpoint WAL to merge pending writes before rename
try:
with db._connect() as conn:
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
except Exception as e:
logger.warning(f"WAL checkpoint before rename: {e}")
# Rename DB + WAL + SHM files
for suffix in ['', '-wal', '-shm']:
src = Path(str(current) + suffix)
dst = Path(str(target) + suffix)
if src.exists():
try:
src.rename(dst)
except OSError as e:
logger.error(f"Failed to rename {src.name} -> {dst.name}: {e}")
return # abort migration
db.db_path = target
logger.info(f"Database renamed: {current.name} -> {target.name}")
def create_app():
"""Create and configure Flask application"""
global db, device_manager
@@ -78,12 +167,12 @@ def create_app():
logging.getLogger().addHandler(log_handler)
app.log_handler = log_handler
# v2: Initialize database
db = Database(config.db_path)
# v2: Initialize database (auto-detect device-named DB or use default)
db_path = _resolve_db_path()
db = Database(db_path)
app.db = db
# Migrate settings from .webui_settings.json to DB (one-time)
from pathlib import Path
settings_file = Path(config.MC_CONFIG_DIR) / ".webui_settings.json"
if settings_file.exists() and db.get_setting('manual_add_contacts') is None:
logger.info("Migrating settings from .webui_settings.json to database...")
@@ -104,23 +193,31 @@ def create_app():
# Start device connection in background (non-blocking)
device_manager.start()
# Update runtime config when device connects, then run v1 migration if needed
# Update runtime config when device connects, then run migrations if needed
def _wait_for_device_name():
"""Wait for device manager to connect and update runtime config."""
for _ in range(60): # wait up to 60 seconds
time.sleep(1)
if device_manager.is_connected:
runtime_config.set_device_name(
device_manager.device_name, "device"
)
logger.info(f"Device name resolved: {device_manager.device_name}")
dev_name = device_manager.device_name
runtime_config.set_device_name(dev_name, "device")
logger.info(f"Device name resolved: {dev_name}")
# Rename DB to match device name (mc-webui.db -> {name}.db)
_migrate_db_to_device_name(db, dev_name)
# Ensure device info is stored in current DB
if device_manager.self_info:
db.set_device_info(
public_key=device_manager.self_info.get('public_key', ''),
name=dev_name,
self_info=json.dumps(device_manager.self_info, default=str)
)
# Auto-migrate v1 data if .msgs file exists and DB is empty
try:
from app.migrate_v1 import should_migrate, migrate_v1_data
from pathlib import Path
data_dir = Path(config.MC_CONFIG_DIR)
dev_name = device_manager.device_name
if should_migrate(db, data_dir, dev_name):
logger.info("v1 .msgs file detected with empty DB — starting migration")
result = migrate_v1_data(db, data_dir, dev_name)
@@ -139,7 +236,7 @@ def create_app():
init_retention_schedule(db=db)
logger.info(f"mc-webui v2 started — transport: {'TCP' if config.use_tcp else 'serial'}")
logger.info(f"Database: {config.db_path}")
logger.info(f"Database: {db.db_path}")
return app

View File

@@ -18,7 +18,6 @@ services:
- MC_SERIAL_PORT=${MC_SERIAL_PORT:-auto}
- MC_DEVICE_NAME=${MC_DEVICE_NAME:-MeshCore}
- MC_CONFIG_DIR=/data
- MC_DB_PATH=/data/mc-webui.db
- MC_TCP_HOST=${MC_TCP_HOST:-}
- MC_TCP_PORT=${MC_TCP_PORT:-5555}
- MC_BACKUP_ENABLED=${MC_BACKUP_ENABLED:-true}