Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
100
tundra/tools/collect-device-ssh.py
Normal file
100
tundra/tools/collect-device-ssh.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
"""Read a Linux phone inventory through an already authenticated SSH master."""
|
||||
import argparse
|
||||
import ipaddress
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
from tundra import ROOT, now, save
|
||||
|
||||
READS = {
|
||||
'identity': ['id'],
|
||||
'os-release': ['cat', '/etc/os-release'],
|
||||
'kernel': ['uname', '-a'],
|
||||
'cmdline': ['cat', '/proc/cmdline'],
|
||||
'partitions': ['lsblk', '-b', '-J', '-o', 'NAME,TYPE,SIZE,FSTYPE,PARTLABEL,MOUNTPOINTS'],
|
||||
'partition-links': ['ls', '-l', '/dev/disk/by-partlabel', '/dev/block/by-name'],
|
||||
'mounts': ['cat', '/proc/mounts'],
|
||||
'fstab': ['cat', '/etc/fstab'],
|
||||
'deviceinfo': ['ls', '-la', '/etc/deviceinfo'],
|
||||
'boot-files': ['ls', '-l', '/boot'],
|
||||
'devices': ['ls', '-l', '/dev/dri', '/dev/input', '/dev/video0', '/dev/binder'],
|
||||
'services': ['systemctl', 'list-units', '--type=service', '--state=running', '--no-pager', '--plain'],
|
||||
'packages': ['dpkg-query', '-W', '-f', '${binary:Package}\t${Version}\n'],
|
||||
'android-properties': ['getprop'],
|
||||
}
|
||||
|
||||
|
||||
def identify(model, compatible, android_device=''):
|
||||
model = model.replace('\0', '').strip()
|
||||
tokens = compatible.strip().split('\0')
|
||||
android_device = android_device.strip()
|
||||
if model == 'Google Pixel 3a XL' or 'google,bonito' in tokens or android_device == 'bonito':
|
||||
return 'wrong-device-pixel-3a-xl'
|
||||
if model == 'Google Pixel 3a' or 'google,sargo' in tokens or android_device == 'sargo':
|
||||
return 'sargo'
|
||||
return 'unverified'
|
||||
|
||||
|
||||
def ssh_base(socket, host, user):
|
||||
# ProxyCommand=false prevents a new connection if the master disappears.
|
||||
return ['ssh', '-F', '/dev/null', '-S', str(socket),
|
||||
'-o', 'BatchMode=yes', '-o', 'ProxyCommand=false',
|
||||
'-o', 'ForwardAgent=no', '-o', 'ClearAllForwardings=yes',
|
||||
f'{user}@{host}']
|
||||
|
||||
|
||||
def capture(base, command):
|
||||
try:
|
||||
result = subprocess.run(base + [shlex.join(command)], capture_output=True,
|
||||
text=True, errors='replace', timeout=20)
|
||||
output = result.stdout
|
||||
if command == READS['packages']:
|
||||
output = '\n'.join(line for line in output.splitlines() if re.search(
|
||||
r'android|droidian|hybris|halium|linux-|phosh|phoc|wayland|mesa|firmware|modem|pulse|pipewire',
|
||||
line, re.I))
|
||||
return {'command': command, 'returncode': result.returncode,
|
||||
'stdout': output[:65536], 'stderr': result.stderr[:4096],
|
||||
'truncated': len(output) > 65536}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {'command': command, 'returncode': None, 'error': 'timeout'}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--control-socket', type=Path, required=True)
|
||||
parser.add_argument('--host', default='10.15.19.82')
|
||||
parser.add_argument('--user', default='droidian')
|
||||
args = parser.parse_args()
|
||||
ipaddress.ip_address(args.host)
|
||||
if not re.fullmatch(r'[a-z_][a-z0-9_-]*', args.user):
|
||||
parser.error('Invalid SSH user')
|
||||
socket = args.control_socket.resolve()
|
||||
if not socket.is_socket():
|
||||
parser.error('Open the authenticated SSH master in your terminal first')
|
||||
os.umask(0o077)
|
||||
base = ssh_base(socket, args.host, args.user)
|
||||
subprocess.run(base[:-1] + ['-O', 'check', base[-1]], check=True)
|
||||
records = {name: capture(base, ['cat', path]) for name, path in (
|
||||
('model', '/proc/device-tree/model'),
|
||||
('compatible', '/proc/device-tree/compatible'))}
|
||||
records['android-device'] = capture(base, ['getprop', 'ro.product.device'])
|
||||
value = lambda name: records[name].get('stdout', '') if records[name].get('returncode') == 0 else ''
|
||||
identity = identify(value('model'), value('compatible'), value('android-device'))
|
||||
if identity == 'sargo':
|
||||
for name, command in READS.items():
|
||||
records[name] = capture(base, command)
|
||||
path = ROOT/'logs/device'/('ssh-'+now().replace(':', '-')+'.json')
|
||||
save(path, {'recordedAt': now(), 'transport': 'ssh-existing-master',
|
||||
'identity': identity, 'readOnly': True,
|
||||
'flashAuthorizedByThisReport': False, 'records': records})
|
||||
print(f'Inventory: {path}\nIdentity: {identity}')
|
||||
return 0 if identity == 'sargo' else 2
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Loading…
Reference in a new issue