2.0.0 Beta : Self-hosted Release
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sqlite3
|
||||
|
||||
from database.paths import DATABASE_CODE_ROOT, PROJECT_ROOT
|
||||
|
||||
|
||||
DATABASE_LAYOUT = {
|
||||
'L1': ('L1', 'L1.db'),
|
||||
'L2': ('L2', 'L2.db'),
|
||||
'L3': ('L3', 'L3.db'),
|
||||
'Web': ('Web', 'Web_App.sqlite'),
|
||||
}
|
||||
|
||||
|
||||
def _sha256(path):
|
||||
digest = hashlib.sha256()
|
||||
with path.open('rb') as file:
|
||||
for chunk in iter(lambda: file.read(1024 * 1024), b''):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _sqlite_copy(source, target):
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
source_db = sqlite3.connect(str(source))
|
||||
target_db = sqlite3.connect(str(target))
|
||||
try:
|
||||
source_db.backup(target_db)
|
||||
result = target_db.execute('PRAGMA quick_check').fetchone()[0]
|
||||
if result != 'ok':
|
||||
raise RuntimeError(f'{target.name} quick_check failed: {result}')
|
||||
finally:
|
||||
source_db.close()
|
||||
target_db.close()
|
||||
|
||||
|
||||
def migrate_data(source_root, target_root, include_imports=False):
|
||||
source_root = Path(source_root).expanduser().resolve()
|
||||
target_root = Path(target_root).expanduser().resolve()
|
||||
if source_root == target_root:
|
||||
raise ValueError('Source and target data roots must differ')
|
||||
|
||||
target_root.mkdir(parents=True, exist_ok=True)
|
||||
manifest = {
|
||||
'created_at': datetime.now(timezone.utc).isoformat(),
|
||||
'source_root': str(source_root),
|
||||
'target_root': str(target_root),
|
||||
'databases': {},
|
||||
}
|
||||
for name, (directory, filename) in DATABASE_LAYOUT.items():
|
||||
source = source_root / directory / filename
|
||||
target = target_root / directory / filename
|
||||
if not source.exists():
|
||||
raise FileNotFoundError(f'Missing source database: {source}')
|
||||
_sqlite_copy(source, target)
|
||||
manifest['databases'][name] = {
|
||||
'path': str(target),
|
||||
'size_bytes': target.stat().st_size,
|
||||
'sha256': _sha256(target),
|
||||
'quick_check': 'ok',
|
||||
}
|
||||
|
||||
if include_imports:
|
||||
legacy_imports = PROJECT_ROOT / 'output_arena'
|
||||
if legacy_imports.exists():
|
||||
destination = target_root / 'imports'
|
||||
shutil.copytree(
|
||||
legacy_imports,
|
||||
destination,
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
manifest['imports'] = str(destination)
|
||||
|
||||
manifest_path = target_root / 'migration-manifest.json'
|
||||
manifest_path.write_text(
|
||||
json.dumps(manifest, ensure_ascii=True, indent=2),
|
||||
encoding='utf-8',
|
||||
)
|
||||
return manifest_path
|
||||
|
||||
|
||||
def _parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'--source',
|
||||
default=str(DATABASE_CODE_ROOT),
|
||||
help='Existing runtime data root',
|
||||
)
|
||||
parser.add_argument('--target', required=True)
|
||||
parser.add_argument('--include-imports', action='store_true')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = _parse_args()
|
||||
path = migrate_data(
|
||||
args.source,
|
||||
args.target,
|
||||
include_imports=args.include_imports,
|
||||
)
|
||||
print(f'Migration completed: {path}')
|
||||
|
||||
Reference in New Issue
Block a user