Template
Add server user/protocol migrate export-import for connect_domain moves.
This commit is contained in:
@@ -23,7 +23,7 @@ import time
|
||||
import urllib.request
|
||||
import zipfile
|
||||
import signal
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
import io
|
||||
from fastapi.responses import JSONResponse, RedirectResponse, HTMLResponse, StreamingResponse, FileResponse
|
||||
from starlette.background import BackgroundTask
|
||||
@@ -103,7 +103,7 @@ else:
|
||||
application_path = os.path.dirname(__file__)
|
||||
|
||||
DATA_FILE = os.path.join(application_path, 'data.json') # legacy JSON; used only for one-shot import / export
|
||||
CURRENT_VERSION = "v2.7.0"
|
||||
CURRENT_VERSION = "v2.7.1"
|
||||
RELEASES_REPO_URL = repo_url()
|
||||
RELEASES_API_LATEST = api_latest_url()
|
||||
BIN_DIR = os.environ.get('TUNNEL_BIN_DIR', os.path.join(application_path, 'bin'))
|
||||
@@ -4159,6 +4159,103 @@ async def api_protocol_backup_restore(request: Request, server_id: int, req: Bac
|
||||
return JSONResponse({'error': str(e)}, status_code=500)
|
||||
|
||||
|
||||
@app.get('/api/servers/{server_id}/migrate/export', tags=["Servers"])
|
||||
async def api_server_migrate_export(
|
||||
request: Request,
|
||||
server_id: int,
|
||||
include_protocols: bool = True,
|
||||
):
|
||||
"""Export users/connections (+ protocol state) for domain-preserving server migration."""
|
||||
if not _check_admin(request):
|
||||
return JSONResponse({'error': 'Forbidden'}, status_code=403)
|
||||
try:
|
||||
data = await load_data_async()
|
||||
if server_id >= len(data['servers']):
|
||||
return JSONResponse({'error': 'Server not found'}, status_code=404)
|
||||
server = data['servers'][server_id]
|
||||
from managers.migrate_manager import export_migrate_zip
|
||||
|
||||
def _do_export():
|
||||
ssh = None
|
||||
try:
|
||||
if include_protocols:
|
||||
ssh = get_ssh(server)
|
||||
ssh.connect()
|
||||
return export_migrate_zip(
|
||||
ssh,
|
||||
data,
|
||||
server_id,
|
||||
include_protocol_backups=include_protocols,
|
||||
protocol_container_name_fn=protocol_container_name,
|
||||
)
|
||||
finally:
|
||||
if ssh:
|
||||
ssh.disconnect()
|
||||
|
||||
zip_bytes, summary = await asyncio.to_thread(_do_export)
|
||||
stamp = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')
|
||||
safe_name = re.sub(r'[^\w.-]+', '_', server.get('name') or server.get('host') or 'server')
|
||||
filename = f'amnezia-migrate-{safe_name}-{stamp}.zip'
|
||||
headers = {
|
||||
'Content-Disposition': f'attachment; filename="{filename}"',
|
||||
'X-Migrate-Users': str(summary.get('users', 0)),
|
||||
'X-Migrate-Connections': str(summary.get('connections', 0)),
|
||||
'X-Migrate-Protocols': ','.join(summary.get('protocols') or []),
|
||||
}
|
||||
return StreamingResponse(io.BytesIO(zip_bytes), media_type='application/zip', headers=headers)
|
||||
except Exception as e:
|
||||
logger.exception('Server migrate export failed')
|
||||
return JSONResponse({'error': str(e)}, status_code=500)
|
||||
|
||||
|
||||
@app.post('/api/servers/{server_id}/migrate/import', tags=["Servers"])
|
||||
async def api_server_migrate_import(
|
||||
request: Request,
|
||||
server_id: int,
|
||||
file: UploadFile = File(...),
|
||||
restore_protocols: bool = True,
|
||||
):
|
||||
"""Import migrate ZIP onto this server (remap users/connections, restore protocol state)."""
|
||||
if not _check_admin(request):
|
||||
return JSONResponse({'error': 'Forbidden'}, status_code=403)
|
||||
try:
|
||||
content = await file.read()
|
||||
if not content:
|
||||
return JSONResponse({'error': 'Empty file'}, status_code=400)
|
||||
from managers.migrate_manager import import_migrate_zip
|
||||
|
||||
async with DATA_LOCK:
|
||||
data = load_data()
|
||||
if server_id >= len(data['servers']):
|
||||
return JSONResponse({'error': 'Server not found'}, status_code=404)
|
||||
server = data['servers'][server_id]
|
||||
|
||||
def _do_import():
|
||||
ssh = None
|
||||
try:
|
||||
if restore_protocols:
|
||||
ssh = get_ssh(server)
|
||||
ssh.connect()
|
||||
return import_migrate_zip(
|
||||
ssh,
|
||||
data,
|
||||
server_id,
|
||||
content,
|
||||
restore_protocols=restore_protocols,
|
||||
protocol_container_name_fn=protocol_container_name,
|
||||
)
|
||||
finally:
|
||||
if ssh:
|
||||
ssh.disconnect()
|
||||
|
||||
result = await asyncio.to_thread(_do_import)
|
||||
save_data(data)
|
||||
return {'status': 'success', **result}
|
||||
except Exception as e:
|
||||
logger.exception('Server migrate import failed')
|
||||
return JSONResponse({'error': str(e)}, status_code=400)
|
||||
|
||||
|
||||
@app.post('/api/servers/{server_id}/backups/export-clients', tags=["Protocols"])
|
||||
async def api_protocol_export_clients(request: Request, server_id: int, req: ProtocolRequest):
|
||||
"""Download a ZIP with all reconstructable WireGuard/AWG client .conf files."""
|
||||
|
||||
Reference in New Issue
Block a user