Добавил OTA publish helper
Этот коммит содержится в:
@@ -0,0 +1,108 @@
|
|||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
|
import paramiko
|
||||||
|
|
||||||
|
from backend.android_tools import find_apks
|
||||||
|
from backend.projects_registry import find_project_by_workspace
|
||||||
|
from backend.runtime_logs import add_log
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_name(value: str) -> str:
|
||||||
|
return value.strip().replace('/', '-').replace(' ', '-').lower()
|
||||||
|
|
||||||
|
|
||||||
|
def _latest_json(settings: dict) -> dict:
|
||||||
|
notes_raw = settings.get('notes') or ''
|
||||||
|
notes = [line.strip() for line in notes_raw.splitlines() if line.strip()]
|
||||||
|
if not notes:
|
||||||
|
notes = ['Немного нового']
|
||||||
|
|
||||||
|
version = settings.get('version') or '0.0.1'
|
||||||
|
build = int(settings.get('build') or 1)
|
||||||
|
public_base_url = settings.get('public_base_url') or ''
|
||||||
|
latest_apk_name = settings.get('latest_apk_name') or 'pulse-latest.apk'
|
||||||
|
versioned_apk_name = settings.get('versioned_apk_name') or f'pulse-{version}+{build}.apk'
|
||||||
|
|
||||||
|
return {
|
||||||
|
'version': version,
|
||||||
|
'build': build,
|
||||||
|
'notes': notes,
|
||||||
|
'apk_url': urljoin(public_base_url.rstrip('/') + '/', latest_apk_name),
|
||||||
|
'versioned_apk_url': urljoin(public_base_url.rstrip('/') + '/', versioned_apk_name),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _mkdir_p_sftp(sftp, remote_path: str) -> None:
|
||||||
|
path = remote_path.strip('/')
|
||||||
|
if not path:
|
||||||
|
return
|
||||||
|
current = ''
|
||||||
|
for part in path.split('/'):
|
||||||
|
current += '/' + part
|
||||||
|
try:
|
||||||
|
sftp.stat(current)
|
||||||
|
except IOError:
|
||||||
|
sftp.mkdir(current)
|
||||||
|
|
||||||
|
|
||||||
|
def publish_project_ota(workspace: str) -> dict:
|
||||||
|
project = find_project_by_workspace(workspace)
|
||||||
|
if not project:
|
||||||
|
return {'success': False, 'message': 'Project not found in registry'}
|
||||||
|
|
||||||
|
settings = project.get('ota') or {}
|
||||||
|
if not settings.get('enabled'):
|
||||||
|
return {'success': False, 'message': 'OTA publish disabled'}
|
||||||
|
|
||||||
|
apks = find_apks(workspace)
|
||||||
|
if not apks:
|
||||||
|
return {'success': False, 'message': 'APK not found'}
|
||||||
|
|
||||||
|
host = settings.get('sftp_host') or ''
|
||||||
|
username = settings.get('sftp_username') or ''
|
||||||
|
password = settings.get('sftp_password') or ''
|
||||||
|
remote_path = settings.get('remote_path') or ''
|
||||||
|
|
||||||
|
if not host or not username or not password or not remote_path:
|
||||||
|
return {'success': False, 'message': 'SFTP settings are incomplete'}
|
||||||
|
|
||||||
|
latest_apk_name = settings.get('latest_apk_name') or 'pulse-latest.apk'
|
||||||
|
version = settings.get('version') or '0.0.1'
|
||||||
|
build = int(settings.get('build') or 1)
|
||||||
|
versioned_apk_name = settings.get('versioned_apk_name') or f'pulse-{version}+{build}.apk'
|
||||||
|
latest_json_name = settings.get('latest_json_name') or 'latest.json'
|
||||||
|
|
||||||
|
latest_json = _latest_json(settings)
|
||||||
|
tmp_json = Path('/tmp') / f'devconsole-{_safe_name(project.get("name", "project"))}-latest.json'
|
||||||
|
tmp_json.write_text(json.dumps(latest_json, ensure_ascii=False, indent=4), encoding='utf-8')
|
||||||
|
|
||||||
|
apk_path = apks[0]
|
||||||
|
|
||||||
|
add_log(f'OTA publish started: {project.get("name")} -> {host}:{remote_path}')
|
||||||
|
|
||||||
|
transport = paramiko.Transport((host, int(settings.get('sftp_port') or 22)))
|
||||||
|
try:
|
||||||
|
transport.connect(username=username, password=password)
|
||||||
|
sftp = paramiko.SFTPClient.from_transport(transport)
|
||||||
|
remote_dir = '/' + remote_path.strip('/')
|
||||||
|
_mkdir_p_sftp(sftp, remote_dir)
|
||||||
|
|
||||||
|
sftp.put(apk_path, f'{remote_dir}/{latest_apk_name}')
|
||||||
|
sftp.put(apk_path, f'{remote_dir}/{versioned_apk_name}')
|
||||||
|
sftp.put(tmp_json.as_posix(), f'{remote_dir}/{latest_json_name}')
|
||||||
|
sftp.close()
|
||||||
|
finally:
|
||||||
|
transport.close()
|
||||||
|
|
||||||
|
add_log(f'OTA publish completed: {latest_json.get("apk_url")}')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'apk': apk_path,
|
||||||
|
'latest_json': latest_json,
|
||||||
|
'uploaded': [latest_apk_name, versioned_apk_name, latest_json_name],
|
||||||
|
'published_at': datetime.utcnow().isoformat() + 'Z',
|
||||||
|
}
|
||||||
Ссылка в новой задаче
Block a user