Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
280 changes: 208 additions & 72 deletions main.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/python
#!/usr/bin/python3
import os
import errno
from re import T
import socket
from shutil import rmtree
import gi
import urllib.request
Expand All @@ -14,9 +14,15 @@

gi.require_version("Gtk", "3.0")
gi.require_version("Handy", "1")
gi.require_version("AppIndicator3", "0.1")
try:
gi.require_version("AppIndicator3", "0.1")
except (ValueError, ImportError):
gi.require_version("AyatanaAppIndicator3", "0.1")
gi.require_version("Notify", "0.7")
from gi.repository import Gtk, AppIndicator3 as appindicator
try:
from gi.repository import Gtk, AppIndicator3 as appindicator
except (ValueError, ImportError):
from gi.repository import Gtk, AyatanaAppIndicator3 as appindicator
from gi.repository import GLib
from gi.repository import GObject, Handy
from gi.repository import GdkPixbuf
Expand Down Expand Up @@ -75,6 +81,19 @@ def connectioncheck():
return False


def port_open(host, port, timeout=1.0):
"""Return True if a TCP connection to host:port succeeds within `timeout`.

Used to probe the local anisette-server without spawning curl/subshells.
No external dependencies, no shell, fast.
"""
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except OSError:
return False


# Check version
with open(resource_path("resources/version"), "r", encoding="utf-8") as f:
LocalVersion = f.readline().strip()
Expand Down Expand Up @@ -297,72 +316,104 @@ def wait_for_t(self, t):

def lol321actualfunction(self):
global installedcheck
self.lbl1.set_text("Checking if anisette_server is already running...")
self.loadaltlinux.set_fraction(0.1)
command = 'curl 127.0.0.1:6969 | grep -q "{"'
CheckRun = subprocess.run(command, shell=True)
if not os.path.isfile(f"{(altlinuxpath)}/anisette_server"):
self.lbl1.set_text("Downloading anisette_server...")
r = requests.get(
"https://github.com/Dadoum/Provision/releases/download/1.1.0/anisette_server-x86_64",
allow_redirects=True,
)
open(f"{(altlinuxpath)}/anisette_server", "wb").write(r.content)
subprocess.run(f"chmod +x {(altlinuxpath)}/anisette_server", shell=True)
subprocess.run(f"chmod 755 {(altlinuxpath)}/anisette_server", shell=True)
self.loadaltlinux.set_fraction(0.2)
self.lbl1.set_text("Downloading Apple Music APK...")
r = requests.get(
"https://apps.mzstatic.com/content/android-apple-music-apk/applemusic.apk",
allow_redirects=True,
)
open(f"{(altlinuxpath)}/am.apk", "wb").write(r.content)
os.makedirs(f"{(altlinuxpath)}/lib/x86_64")
self.loadaltlinux.set_fraction(0.3)
self.lbl1.set_text("Extracting necessary libraries...")
CheckRunB = subprocess.run(
f'unzip -j "{(altlinuxpath)}/am.apk" "lib/x86_64/libstoreservicescore.so" -d "{(altlinuxpath)}/lib/x86_64"',
shell=True,
)
CheckRunC = subprocess.run(
f'unzip -j "{(altlinuxpath)}/am.apk" "lib/x86_64/libCoreADI.so" -d "{(altlinuxpath)}/lib/x86_64"',
shell=True,
)
silentremove(f"{(altlinuxpath)}/am.apk")
self.loadaltlinux.set_fraction(0.4)
self.lbl1.set_text("Starting anisette_server...")
subprocess.run(f"cd {(altlinuxpath)} && ./anisette_server &", shell=True)
self.loadaltlinux.set_fraction(0.5)
finished = False
while not finished:
CheckRun5 = subprocess.run(command, shell=True)
if CheckRun5.returncode == 0:
finished = True
else:
sleep(1)
if not os.path.isfile(f"{(altlinuxpath)}/AltServer"):
self.lbl1.set_text("Downloading AltServer...")
self.loadaltlinux.set_fraction(0.6)
r = requests.get(
"https://github.com/NyaMisty/AltServer-Linux/releases/download/v0.0.5/AltServer-x86_64",
allow_redirects=True,
)
open(f"{(altlinuxpath)}/AltServer", "wb").write(r.content)
subprocess.run(f"chmod +x {(altlinuxpath)}/AltServer", shell=True)
subprocess.run(f"chmod 755 {(altlinuxpath)}/AltServer", shell=True)
if not os.path.isfile(f"{(altlinuxpath)}/AltStore.ipa"):
self.lbl1.set_text("Downloading AltStore...")
self.loadaltlinux.set_fraction(0.7)
r = requests.get(
"https://cdn.altstore.io/file/altstore/altstore-beta.ipa",
allow_redirects=True,
)
open(f"{(altlinuxpath)}/AltStore.ipa", "wb").write(r.content)
subprocess.run(f"chmod 755 {(altlinuxpath)}/AltStore.ipa", shell=True)
self.lbl1.set_text("Starting AltServer...")
self.loadaltlinux.set_fraction(1.0)
subprocess.run(f"{(altlinuxpath)}/AltServer &", shell=True)
return 0
try:
self.lbl1.set_text("Checking if anisette_server is already running...")
self.loadaltlinux.set_fraction(0.1)
anisette_already_up = port_open("127.0.0.1", 6969)
if not os.path.isfile(f"{(altlinuxpath)}/anisette_server"):
self.lbl1.set_text("Downloading anisette_server...")
r = requests.get(
"https://github.com/Dadoum/Provision/releases/download/2.0.0/anisette-server-x86_64",
allow_redirects=True,
)
r.raise_for_status()
open(f"{(altlinuxpath)}/anisette_server", "wb").write(r.content)
subprocess.run(f"chmod +x {(altlinuxpath)}/anisette_server", shell=True)
subprocess.run(f"chmod 755 {(altlinuxpath)}/anisette_server", shell=True)
self.loadaltlinux.set_fraction(0.2)
self.lbl1.set_text("Downloading Apple Music APK...")
r = requests.get(
"https://apps.mzstatic.com/content/android-apple-music-apk/applemusic.apk",
allow_redirects=True,
)
r.raise_for_status()
open(f"{(altlinuxpath)}/am.apk", "wb").write(r.content)
os.makedirs(f"{(altlinuxpath)}/lib/x86_64")
self.loadaltlinux.set_fraction(0.3)
self.lbl1.set_text("Extracting necessary libraries...")
CheckRunB = subprocess.run(
f'unzip -j "{(altlinuxpath)}/am.apk" "lib/x86_64/libstoreservicescore.so" -d "{(altlinuxpath)}/lib/x86_64"',
shell=True,
)
CheckRunC = subprocess.run(
f'unzip -j "{(altlinuxpath)}/am.apk" "lib/x86_64/libCoreADI.so" -d "{(altlinuxpath)}/lib/x86_64"',
shell=True,
)
silentremove(f"{(altlinuxpath)}/am.apk")
self.loadaltlinux.set_fraction(0.4)
# If anisette wasn't already serving on :6969, start it ourselves.
if not anisette_already_up:
self.lbl1.set_text("Starting anisette_server...")
# Start the server detached so the worker thread can return
# even if the server takes a moment to bind.
with open(f"{(altlinuxpath)}/anisette.log", "ab") as anisette_log:
subprocess.Popen(
f"cd {(altlinuxpath)} && exec ./anisette_server",
shell=True,
stdout=anisette_log,
stderr=anisette_log,
start_new_session=True,
)
self.loadaltlinux.set_fraction(0.5)
# Bounded wait: probe port 6969 every second for up to 60s.
# Previously this used `curl 127.0.0.1:6969 | grep -q "{"`,
# which spins forever if curl isn't on PATH or if the server
# never binds (e.g. Apple's CDN blocks the library download).
wait_ok = False
for _ in range(60):
if port_open("127.0.0.1", 6969):
wait_ok = True
break
sleep(1)
if not wait_ok:
raise RuntimeError(
"anisette_server did not start listening on 127.0.0.1:6969 "
"within 60s. See ~/.local/share/altlinux/anisette.log for details."
)
if not os.path.isfile(f"{(altlinuxpath)}/AltServer"):
self.lbl1.set_text("Downloading AltServer...")
self.loadaltlinux.set_fraction(0.6)
r = requests.get(
"https://github.com/NyaMisty/AltServer-Linux/releases/download/v0.0.5/AltServer-x86_64",
allow_redirects=True,
)
r.raise_for_status()
open(f"{(altlinuxpath)}/AltServer", "wb").write(r.content)
subprocess.run(f"chmod +x {(altlinuxpath)}/AltServer", shell=True)
subprocess.run(f"chmod 755 {(altlinuxpath)}/AltServer", shell=True)
if not os.path.isfile(f"{(altlinuxpath)}/AltStore.ipa"):
self.lbl1.set_text("Downloading AltStore...")
self.loadaltlinux.set_fraction(0.7)
r = requests.get(
"https://cdn.altstore.io/file/altstore/altstore-beta.ipa",
allow_redirects=True,
)
r.raise_for_status()
open(f"{(altlinuxpath)}/AltStore.ipa", "wb").write(r.content)
subprocess.run(f"chmod 755 {(altlinuxpath)}/AltStore.ipa", shell=True)
self.lbl1.set_text("Starting AltServer...")
self.loadaltlinux.set_fraction(1.0)
subprocess.run(f"{(altlinuxpath)}/AltServer &", shell=True)
return 0
except Exception as exc: # noqa: BLE001 - we want to show the user anything
# Without this guard, an exception on the worker thread is
# silently swallowed by Python and the splash window freezes
# forever. Surface the real cause instead.
GLib.idle_add(self._show_bootstrap_error, repr(exc))
return 1

def _show_bootstrap_error(self, message):
openwindow(OopsAnisette, message=message)


class login(Gtk.Window):
Expand Down Expand Up @@ -927,6 +978,91 @@ def on_info_clicked2(self, widget):
quitit()


class OopsAnisette(Handy.Window):
"""Error window shown when bootstrap (download + anisette start) fails.

Unlike Oops/OopsInternet this carries the real exception text so the
user (or a support thread) can see *why* it failed instead of staring
at a frozen splash screen forever.
"""

def __init__(self, message=""):
super().__init__(title="Error")
self.present()
self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
self.set_resizable(False)
self.set_size_request(520, 200)
self.set_border_width(10)

# WindowHandle
handle = Handy.WindowHandle()
self.add(handle)
box = Gtk.VBox()
vb = Gtk.VBox(spacing=0, orientation=Gtk.Orientation.VERTICAL)

# Headerbar
self.hb = Handy.HeaderBar()
self.hb.set_show_close_button(True)
self.hb.props.title = "Error"
vb.pack_start(self.hb, False, True, 0)

pixbuf = Gtk.IconTheme.get_default().load_icon(
"dialog-error-symbolic", 48, 0
)
image = Gtk.Image.new_from_pixbuf(pixbuf)
image.show()
image.set_margin_top(10)
vb.pack_start(image, True, True, 0)

lbl1 = Gtk.Label(
label=(
"AltLinux could not finish setting itself up.\n"
"anisette-server did not start, or a required download failed."
)
)
lbl1.set_property("margin_left", 15)
lbl1.set_property("margin_right", 15)
lbl1.set_justify(Gtk.Justification.CENTER)
lbl1.set_margin_top(10)

# Render the real exception text in a scrollable, monospaced view
# so long tracebacks stay readable. Escape any markup so a hostile
# error string can't break the label.
scroller = Gtk.ScrolledWindow()
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scroller.set_property("margin_left", 15)
scroller.set_property("margin_right", 15)
scroller.set_margin_top(8)
scroller.set_size_request(480, 140)

detail = Gtk.TextView()
detail.set_editable(False)
detail.set_cursor_visible(False)
detail.set_monospace(True)
detail.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
detail.get_buffer().set_text(
(message or "Unknown error.").replace("<", "&lt;")
)
scroller.add(detail)

button = Gtk.Button(label="Quit")
button.set_property("margin_left", 125)
button.set_property("margin_right", 125)
button.set_margin_top(10)
button.connect("clicked", self.on_info_clicked2)

handle.add(vb)
vb.pack_start(lbl1, expand=False, fill=True, padding=0)
vb.pack_start(scroller, True, True, 0)
vb.pack_start(button, False, False, 10)
box.add(vb)
self.add(box)
self.show_all()

def on_info_clicked2(self, widget):
quitit()


def notify():
if (connectioncheck()) == True:
LatestVersion = (
Expand Down Expand Up @@ -972,8 +1108,8 @@ def showurl(_):
quitit()


def openwindow(window):
w = window()
def openwindow(window, **kwargs):
w = window(**kwargs)
w.show_all()


Expand Down