Autarch/web/routes/archon.py

262 lines
8.2 KiB
Python
Raw Normal View History

"""Archon route — privileged Android device management via ArchonServer."""
from flask import Blueprint, render_template, request, jsonify
from web.auth import login_required
archon_bp = Blueprint('archon', __name__, url_prefix='/archon')
@archon_bp.route('/')
@login_required
def index():
return render_template('archon.html')
@archon_bp.route('/shell', methods=['POST'])
@login_required
def shell():
"""Run a shell command on the connected device via ADB."""
from core.hardware import get_hardware_manager
mgr = get_hardware_manager()
data = request.get_json(silent=True) or {}
command = data.get('command', '').strip()
if not command:
return jsonify({'error': 'No command'})
# Find connected device
devices = mgr.adb_devices()
if not devices:
return jsonify({'stdout': '', 'stderr': 'No ADB device connected', 'exit_code': -1})
serial = devices[0].get('serial', '')
result = mgr.adb_shell(serial, command)
return jsonify({
'stdout': result.get('output', ''),
'stderr': '',
'exit_code': result.get('returncode', -1),
})
@archon_bp.route('/pull', methods=['POST'])
@login_required
def pull():
"""Pull a file from device to AUTARCH server."""
from core.hardware import get_hardware_manager
mgr = get_hardware_manager()
data = request.get_json(silent=True) or {}
remote = data.get('remote', '').strip()
if not remote:
return jsonify({'error': 'No remote path'})
devices = mgr.adb_devices()
if not devices:
return jsonify({'error': 'No ADB device connected'})
serial = devices[0].get('serial', '')
result = mgr.adb_pull(serial, remote)
return jsonify(result)
@archon_bp.route('/push', methods=['POST'])
@login_required
def push():
"""Push a file from AUTARCH server to device."""
from core.hardware import get_hardware_manager
mgr = get_hardware_manager()
data = request.get_json(silent=True) or {}
local = data.get('local', '').strip()
remote = data.get('remote', '').strip()
if not local or not remote:
return jsonify({'error': 'Missing local or remote path'})
devices = mgr.adb_devices()
if not devices:
return jsonify({'error': 'No ADB device connected'})
serial = devices[0].get('serial', '')
result = mgr.adb_push(serial, local, remote)
return jsonify(result)
@archon_bp.route('/packages', methods=['GET'])
@login_required
def packages():
"""List installed packages."""
from core.hardware import get_hardware_manager
mgr = get_hardware_manager()
devices = mgr.adb_devices()
if not devices:
return jsonify({'error': 'No device'})
serial = devices[0].get('serial', '')
show_system = request.args.get('system', 'false') == 'true'
flag = '-f' if not show_system else '-f -s'
result = mgr.adb_shell(serial, f'pm list packages {flag}')
output = result.get('output', '')
pkgs = []
for line in output.strip().split('\n'):
if line.startswith('package:'):
# format: package:/path/to/apk=com.package.name
parts = line[8:].split('=', 1)
if len(parts) == 2:
pkgs.append({'apk': parts[0], 'package': parts[1]})
else:
pkgs.append({'apk': '', 'package': parts[0]})
return jsonify({'packages': pkgs, 'count': len(pkgs)})
@archon_bp.route('/grant', methods=['POST'])
@login_required
def grant_permission():
"""Grant a permission to a package."""
from core.hardware import get_hardware_manager
mgr = get_hardware_manager()
data = request.get_json(silent=True) or {}
package = data.get('package', '').strip()
permission = data.get('permission', '').strip()
if not package or not permission:
return jsonify({'error': 'Missing package or permission'})
devices = mgr.adb_devices()
if not devices:
return jsonify({'error': 'No device'})
serial = devices[0].get('serial', '')
result = mgr.adb_shell(serial, f'pm grant {package} {permission}')
return jsonify({
'success': result.get('returncode', -1) == 0,
'output': result.get('output', ''),
})
@archon_bp.route('/revoke', methods=['POST'])
@login_required
def revoke_permission():
"""Revoke a permission from a package."""
from core.hardware import get_hardware_manager
mgr = get_hardware_manager()
data = request.get_json(silent=True) or {}
package = data.get('package', '').strip()
permission = data.get('permission', '').strip()
if not package or not permission:
return jsonify({'error': 'Missing package or permission'})
devices = mgr.adb_devices()
if not devices:
return jsonify({'error': 'No device'})
serial = devices[0].get('serial', '')
result = mgr.adb_shell(serial, f'pm revoke {package} {permission}')
return jsonify({
'success': result.get('returncode', -1) == 0,
'output': result.get('output', ''),
})
@archon_bp.route('/app-ops', methods=['POST'])
@login_required
def app_ops():
"""Set an appops permission for a package."""
from core.hardware import get_hardware_manager
mgr = get_hardware_manager()
data = request.get_json(silent=True) or {}
package = data.get('package', '').strip()
op = data.get('op', '').strip()
mode = data.get('mode', '').strip() # allow, deny, ignore, default
if not package or not op or not mode:
return jsonify({'error': 'Missing package, op, or mode'})
devices = mgr.adb_devices()
if not devices:
return jsonify({'error': 'No device'})
serial = devices[0].get('serial', '')
result = mgr.adb_shell(serial, f'cmd appops set {package} {op} {mode}')
return jsonify({
'success': result.get('returncode', -1) == 0,
'output': result.get('output', ''),
})
@archon_bp.route('/settings-cmd', methods=['POST'])
@login_required
def settings_cmd():
"""Read or write Android system settings."""
from core.hardware import get_hardware_manager
mgr = get_hardware_manager()
data = request.get_json(silent=True) or {}
namespace = data.get('namespace', 'system').strip() # system, secure, global
action = data.get('action', 'get').strip() # get, put
key = data.get('key', '').strip()
value = data.get('value', '').strip()
if namespace not in ('system', 'secure', 'global'):
return jsonify({'error': 'Invalid namespace'})
if not key:
return jsonify({'error': 'Missing key'})
devices = mgr.adb_devices()
if not devices:
return jsonify({'error': 'No device'})
serial = devices[0].get('serial', '')
if action == 'put' and value:
cmd = f'settings put {namespace} {key} {value}'
else:
cmd = f'settings get {namespace} {key}'
result = mgr.adb_shell(serial, cmd)
return jsonify({
'value': result.get('output', '').strip(),
'exit_code': result.get('returncode', -1),
})
@archon_bp.route('/file-list', methods=['POST'])
@login_required
def file_list():
"""List files in a directory on the device."""
from core.hardware import get_hardware_manager
mgr = get_hardware_manager()
data = request.get_json(silent=True) or {}
path = data.get('path', '/').strip()
devices = mgr.adb_devices()
if not devices:
return jsonify({'error': 'No device'})
serial = devices[0].get('serial', '')
result = mgr.adb_shell(serial, f'ls -la {path}')
return jsonify({
'path': path,
'output': result.get('output', ''),
'exit_code': result.get('returncode', -1),
})
@archon_bp.route('/file-copy', methods=['POST'])
@login_required
def file_copy():
"""Copy a file on the device (elevated shell can access protected paths)."""
from core.hardware import get_hardware_manager
mgr = get_hardware_manager()
data = request.get_json(silent=True) or {}
src = data.get('src', '').strip()
dst = data.get('dst', '').strip()
if not src or not dst:
return jsonify({'error': 'Missing src or dst'})
devices = mgr.adb_devices()
if not devices:
return jsonify({'error': 'No device'})
serial = devices[0].get('serial', '')
result = mgr.adb_shell(serial, f'cp -r {src} {dst}')
return jsonify({
'success': result.get('returncode', -1) == 0,
'output': result.get('output', ''),
})