#!/usr/bin/env python3 """ Android fleet onboarding: turn a bare Android-x86 clone into a fully-set-up phone with a fresh Instagram account. Steps: basics -> Instagram install -> account creation (device proxy) -> IMAP email verification -> app login (best-effort UI) -> DB record. """ import imaplib import json import os import re import sqlite3 import subprocess import sys import time DB_PATH = "/opt/android-fleet/fleet.db" IG_APK = "/opt/android-fleet/ig_instagram.apk" SESSIONS_DIR = "/opt/android-fleet/sessions" GMAIL = "jones.henry666q@gmail.com" APP_PASS = "hkdbecbdkncmvshg" PASSWORD = "aaaa1111" os.makedirs(SESSIONS_DIR, exist_ok=True) def log(msg): print(f"[onboard] {msg}", flush=True) def dbcon(): con = sqlite3.connect(DB_PATH, timeout=15) con.row_factory = sqlite3.Row return con def get_device(mac): con = dbcon() row = con.execute("SELECT * FROM devices WHERE mac=?", (mac,)).fetchone() con.close() return dict(row) if row else None def adb_shell(serial, cmd, timeout=45): r = subprocess.run(["adb", "-s", serial, "shell", cmd], capture_output=True, text=True, timeout=timeout) return r.stdout.strip() def adb_cmd(serial, args, timeout=60): return subprocess.run(["adb", "-s", serial] + args, capture_output=True, text=True, timeout=timeout) def next_counter(): con = dbcon() row = con.execute("SELECT COUNT(*) AS n FROM accounts").fetchone() con.close() return row["n"] + 1 def setup_basics(serial): log("basics: timezone, screen, animations") adb_shell(serial, "settings put global stay_on_while_plugged_in 3") adb_shell(serial, "settings put system screen_off_timeout 2147483647") adb_shell(serial, "settings put global window_animation_scale 0") adb_shell(serial, "settings put global transition_animation_scale 0") adb_shell(serial, "settings put global animator_duration_scale 0") adb_shell(serial, "setprop persist.sys.timezone America/Los_Angeles") adb_shell(serial, "settings put global airplane_mode_on 0") log("basics done") def install_instagram(serial): """Try the APK; arm64-only builds won't install on x86 — non-fatal.""" log("attempting Instagram APK install (arm64 builds fail on x86 — that's fine)...") try: r = adb_cmd(serial, ["install", "-r", IG_APK], timeout=300) out = (r.stdout or "") + (r.stderr or "") log(f"install result: {out.strip()[:120]}") if "Success" in out: return "installed" if "NO_MATCHING_ABIS" in out: log("APK is arm64-only — skipping app install, using API-level account") return "skipped" return "failed" except Exception as e: log(f"install error: {e}") return "failed" def fetch_verification(timeout=240): """Poll Gmail IMAP for IG confirm: 6-digit code OR confirmation link.""" end = time.time() + timeout while time.time() < end: try: M = imaplib.IMAP4_SSL("imap.gmail.com") M.login(GMAIL, APP_PASS) M.select("INBOX") typ, data = M.search(None, '(FROM "Instagram")') nums = data[0].split()[-6:] for num in reversed(nums): typ, msg = M.fetch(num, "(BODY.PEEK[])") raw = msg[0][1] body = raw.decode(errors="replace") m = re.search(r"\b(\d{6})\b", body) if m: M.logout() return {"code": m.group(1)} links = re.findall(r'https?://[^\s"<>]+(?:confirm|verify|challenge)[^\s"<>]*', body) if links: M.logout() return {"link": links[0]} M.logout() except Exception as e: log(f"imap poll: {e}") time.sleep(8) return None def create_instagram_account(serial, proxy_url, counter): log(f"creating IG account #{counter} via device proxy") from instagrapi import Client cl = Client() if proxy_url: cl.set_proxy(proxy_url) email = f"igbot{counter}+{GMAIL}" username = f"fleet.jones{counter}" status = "created" try: cl.signup(username=username, password=PASSWORD, email=email, full_name="Android User") log("signup ok, no verification needed") except Exception as e: msg = str(e).lower() log(f"signup challenge: {str(e)[:100]}") if "checkpoint" in msg or "challenge" in msg: return {"status": "checkpoint", "email": email, "username": username} if "sent" in msg or "verification" in msg or "confirm" in msg: ver = fetch_verification() if not ver: return {"status": "no_code", "email": email, "username": username} if "code" in ver: try: cl.signup(username=username, password=PASSWORD, email=email, full_name="Android User", verification_code=ver["code"]) log("signup ok after email code") except Exception as e2: log(f"verify signup failed: {str(e2)[:100]}") return {"status": f"verify_failed: {str(e2)[:60]}", "email": email, "username": username} elif "link" in ver: log(f"clicking confirm link through device proxy: {ver['link'][:60]}...") try: import requests as _rq proxies = None if proxy_url: proxies = {"http": proxy_url, "https": proxy_url} r = _rq.get(ver["link"], proxies=proxies, timeout=20, headers={"User-Agent": "Mozilla/5.0 (Linux; Android 9) AppleWebKit/537.36"}) log(f"link click: HTTP {r.status_code}") time.sleep(5) try: cl.login(username, PASSWORD) log("login after link confirm OK") except Exception as le: log(f"login after link: {str(le)[:80]}") status = "created_unverified" except Exception as ce: log(f"link click failed: {ce}") status = "created_unverified" else: return {"status": f"signup_failed: {str(e)[:60]}", "email": email, "username": username} sess = os.path.join(SESSIONS_DIR, f"{username}.json") cl.dump_settings(sess) return {"status": status, "email": email, "username": username, "session_file": sess, "password": PASSWORD} def login_app(serial, username): """Best-effort: launch IG and log in via UI taps (uiautomator-based).""" log("launching IG app for login") adb_shell(serial, "am start -n com.instagram.android/.activity.MainTabActivity", 15) time.sleep(8) def find_node(text): dump = adb_shell(serial, "uiautomator dump /sdcard/ui.xml && cat /sdcard/ui.xml", 20) m = re.search(r'text="[^"]*' + re.escape(text) + r'[^"]*"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"', dump) if m: x = (int(m.group(1)) + int(m.group(3))) // 2 y = (int(m.group(2)) + int(m.group(4))) // 2 return x, y return None try: p = find_node("Log in") if p: adb_shell(serial, f"input tap {p[0]} {p[1]}", 10) time.sleep(4) p = find_node("Username") if p: adb_shell(serial, f"input tap {p[0]} {p[1]}", 10) adb_shell(serial, f"input text {username}", 10) time.sleep(1) p = find_node("Password") if p: adb_shell(serial, f"input tap {p[0]} {p[1]}", 10) adb_shell(serial, f"input text {PASSWORD}", 10) time.sleep(1) p = find_node("Log In") or find_node("Log in") if p: adb_shell(serial, f"input tap {p[0]} {p[1]}", 10) log("login tapped") return True except Exception as e: log(f"app login UI best-effort failed: {e}") return False def login_existing(mac, username, password): """Login an existing IG account through the device's proxy. Session saved.""" dev = get_device(mac) if not dev or not dev["ip"]: return con = dbcon() pr = con.execute("SELECT * FROM proxies WHERE assigned_mac=?", (mac,)).fetchone() con.close() from instagrapi import Client cl = Client() if pr: cred = f"{pr['user']}:{pr['pass']}@" if pr["user"] else "" cl.set_proxy(f"socks5://{cred}{pr['host']}:{pr['port']}") try: cl.login(username, password) sess = os.path.join(SESSIONS_DIR, f"{username}.json") cl.dump_settings(sess) con = dbcon() con.execute("""INSERT OR REPLACE INTO accounts(mac, email, username, password, status, session_file, created) VALUES(?,?,?,?,?,?,?)""", (mac, None, username, password, "logged_in", sess, time.time())) con.commit() con.close() log(f"{mac}: login OK for {username} — session saved") except Exception as e: log(f"{mac}: login failed for {username}: {str(e)[:120]}") con = dbcon() con.execute("""INSERT OR REPLACE INTO accounts(mac, email, username, password, status, session_file, created) VALUES(?,?,?,?,?,?,?)""", (mac, None, username, password, f"login_failed: {str(e)[:60]}", None, time.time())) con.commit() con.close() def onboard(mac): dev = get_device(mac) if not dev or not dev["ip"]: log(f"device {mac} offline — aborting") return serial = f"{dev['ip']}:5555" log(f"onboarding {mac} @ {serial}") try: adb_cmd(serial, ["connect", serial], timeout=15) except Exception: pass counter = next_counter() con = dbcon() con.execute("INSERT OR REPLACE INTO accounts(mac, status, created) VALUES(?,?,?)", (mac, "onboarding", time.time())) con.commit() con.close() setup_basics(serial) try: adb_cmd(serial, ["root"], timeout=15) time.sleep(2) adb_cmd(serial, ["connect", serial], timeout=15) except Exception: pass apk_state = install_instagram(serial) # proxy for this device con = dbcon() pr = con.execute("SELECT * FROM proxies WHERE assigned_mac=?", (mac,)).fetchone() con.close() proxy_url = None if pr: cred = f"{pr['user']}:{pr['pass']}@" if pr["user"] else "" proxy_url = f"socks5://{cred}{pr['host']}:{pr['port']}" result = create_instagram_account(serial, proxy_url, counter) result["apk"] = apk_state if result.get("session_file"): login_app(serial, result["username"]) _set_status(mac, result["status"], extra=result) def _set_status(mac, status, extra=None): con = dbcon() if extra: con.execute("""UPDATE accounts SET status=?, email=?, username=?, password=?, session_file=?, created=? WHERE mac=?""", (status, extra.get("email"), extra.get("username"), extra.get("password", PASSWORD), extra.get("session_file"), time.time(), mac)) else: con.execute("UPDATE accounts SET status=? WHERE mac=?", (status, mac)) con.commit() con.close() log(f"account status: {status}") if __name__ == "__main__": if len(sys.argv) < 2: print("usage: onboard.py ") sys.exit(1) onboard(sys.argv[1])