mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-03-28 17:42:45 +01:00
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:
@@ -26,7 +26,7 @@ class Config:
|
|||||||
MC_ARCHIVE_RETENTION_DAYS = int(os.getenv('MC_ARCHIVE_RETENTION_DAYS', '7'))
|
MC_ARCHIVE_RETENTION_DAYS = int(os.getenv('MC_ARCHIVE_RETENTION_DAYS', '7'))
|
||||||
|
|
||||||
# v2: Database
|
# 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)
|
# v2: TCP connection (alternative to serial, e.g. meshcore-proxy)
|
||||||
MC_TCP_HOST = os.getenv('MC_TCP_HOST', '') # empty = use serial
|
MC_TCP_HOST = os.getenv('MC_TCP_HOST', '') # empty = use serial
|
||||||
|
|||||||
119
app/main.py
119
app/main.py
@@ -4,10 +4,13 @@ mc-webui v2 — Flask application entry point
|
|||||||
Direct device communication via meshcore library (no bridge).
|
Direct device communication via meshcore library (no bridge).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
from flask import Flask, request as flask_request
|
from flask import Flask, request as flask_request
|
||||||
from flask_socketio import SocketIO, emit
|
from flask_socketio import SocketIO, emit
|
||||||
from app.config import config, runtime_config
|
from app.config import config, runtime_config
|
||||||
@@ -49,6 +52,92 @@ db = None
|
|||||||
device_manager = 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():
|
def create_app():
|
||||||
"""Create and configure Flask application"""
|
"""Create and configure Flask application"""
|
||||||
global db, device_manager
|
global db, device_manager
|
||||||
@@ -78,12 +167,12 @@ def create_app():
|
|||||||
logging.getLogger().addHandler(log_handler)
|
logging.getLogger().addHandler(log_handler)
|
||||||
app.log_handler = log_handler
|
app.log_handler = log_handler
|
||||||
|
|
||||||
# v2: Initialize database
|
# v2: Initialize database (auto-detect device-named DB or use default)
|
||||||
db = Database(config.db_path)
|
db_path = _resolve_db_path()
|
||||||
|
db = Database(db_path)
|
||||||
app.db = db
|
app.db = db
|
||||||
|
|
||||||
# Migrate settings from .webui_settings.json to DB (one-time)
|
# Migrate settings from .webui_settings.json to DB (one-time)
|
||||||
from pathlib import Path
|
|
||||||
settings_file = Path(config.MC_CONFIG_DIR) / ".webui_settings.json"
|
settings_file = Path(config.MC_CONFIG_DIR) / ".webui_settings.json"
|
||||||
if settings_file.exists() and db.get_setting('manual_add_contacts') is None:
|
if settings_file.exists() and db.get_setting('manual_add_contacts') is None:
|
||||||
logger.info("Migrating settings from .webui_settings.json to database...")
|
logger.info("Migrating settings from .webui_settings.json to database...")
|
||||||
@@ -104,23 +193,31 @@ def create_app():
|
|||||||
# Start device connection in background (non-blocking)
|
# Start device connection in background (non-blocking)
|
||||||
device_manager.start()
|
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():
|
def _wait_for_device_name():
|
||||||
"""Wait for device manager to connect and update runtime config."""
|
"""Wait for device manager to connect and update runtime config."""
|
||||||
for _ in range(60): # wait up to 60 seconds
|
for _ in range(60): # wait up to 60 seconds
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
if device_manager.is_connected:
|
if device_manager.is_connected:
|
||||||
runtime_config.set_device_name(
|
dev_name = device_manager.device_name
|
||||||
device_manager.device_name, "device"
|
runtime_config.set_device_name(dev_name, "device")
|
||||||
)
|
logger.info(f"Device name resolved: {dev_name}")
|
||||||
logger.info(f"Device name resolved: {device_manager.device_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
|
# Auto-migrate v1 data if .msgs file exists and DB is empty
|
||||||
try:
|
try:
|
||||||
from app.migrate_v1 import should_migrate, migrate_v1_data
|
from app.migrate_v1 import should_migrate, migrate_v1_data
|
||||||
from pathlib import Path
|
|
||||||
data_dir = Path(config.MC_CONFIG_DIR)
|
data_dir = Path(config.MC_CONFIG_DIR)
|
||||||
dev_name = device_manager.device_name
|
|
||||||
if should_migrate(db, data_dir, dev_name):
|
if should_migrate(db, data_dir, dev_name):
|
||||||
logger.info("v1 .msgs file detected with empty DB — starting migration")
|
logger.info("v1 .msgs file detected with empty DB — starting migration")
|
||||||
result = migrate_v1_data(db, data_dir, dev_name)
|
result = migrate_v1_data(db, data_dir, dev_name)
|
||||||
@@ -139,7 +236,7 @@ def create_app():
|
|||||||
init_retention_schedule(db=db)
|
init_retention_schedule(db=db)
|
||||||
|
|
||||||
logger.info(f"mc-webui v2 started — transport: {'TCP' if config.use_tcp else 'serial'}")
|
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
|
return app
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ services:
|
|||||||
- MC_SERIAL_PORT=${MC_SERIAL_PORT:-auto}
|
- MC_SERIAL_PORT=${MC_SERIAL_PORT:-auto}
|
||||||
- MC_DEVICE_NAME=${MC_DEVICE_NAME:-MeshCore}
|
- MC_DEVICE_NAME=${MC_DEVICE_NAME:-MeshCore}
|
||||||
- MC_CONFIG_DIR=/data
|
- MC_CONFIG_DIR=/data
|
||||||
- MC_DB_PATH=/data/mc-webui.db
|
|
||||||
- MC_TCP_HOST=${MC_TCP_HOST:-}
|
- MC_TCP_HOST=${MC_TCP_HOST:-}
|
||||||
- MC_TCP_PORT=${MC_TCP_PORT:-5555}
|
- MC_TCP_PORT=${MC_TCP_PORT:-5555}
|
||||||
- MC_BACKUP_ENABLED=${MC_BACKUP_ENABLED:-true}
|
- MC_BACKUP_ENABLED=${MC_BACKUP_ENABLED:-true}
|
||||||
|
|||||||
Reference in New Issue
Block a user