gabefromsimplebit commited on
Commit
5c2d1aa
·
verified ·
1 Parent(s): 4dcf967

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +64 -71
app.py CHANGED
@@ -21,45 +21,32 @@ BASE_URL = "https://gabefromsimplebit-simplebit-signer.hf.space"
21
  # -------------------------------------------------
22
  @app.route("/")
23
  def home():
24
- return jsonify({
25
- "status": "online",
26
- "service": "SimpleBit Signer"
27
- })
28
 
29
 
30
  # -------------------------------------------------
31
- # GET BUNDLE ID (safe)
32
  # -------------------------------------------------
33
- def get_actual_bundle_id(ipa_path):
34
-
35
- try:
36
- with zipfile.ZipFile(ipa_path, "r") as ipa:
37
-
38
- for name in ipa.namelist():
39
-
40
- if name.endswith("Info.plist") and ".app/" in name:
41
- with ipa.open(name) as f:
42
- plist_data = plistlib.load(f)
43
- return plist_data.get("CFBundleIdentifier")
44
-
45
- except Exception as e:
46
- print("Bundle error:", e)
47
-
48
  return None
49
 
50
 
51
  # -------------------------------------------------
52
- # FIND APP BUNDLE ANYWHERE (FIXED)
53
  # -------------------------------------------------
54
- def find_app_bundle(temp_dir):
 
 
 
 
55
 
56
- temp_dir = Path(temp_dir)
57
 
58
- for item in temp_dir.rglob("*.app"):
59
- if item.is_dir():
60
- return item
61
-
62
- return None
63
 
64
 
65
  # -------------------------------------------------
@@ -72,15 +59,12 @@ def sign():
72
 
73
  if "ipa" not in request.files:
74
  return jsonify({"error": "Missing IPA"}), 400
75
-
76
  if "p12" not in request.files:
77
  return jsonify({"error": "Missing P12"}), 400
78
-
79
  if "provision" not in request.files:
80
  return jsonify({"error": "Missing provisioning profile"}), 400
81
 
82
  zsign_path = shutil.which("zsign")
83
-
84
  if not zsign_path:
85
  return jsonify({"error": "zsign not installed"}), 500
86
 
@@ -99,30 +83,49 @@ def sign():
99
 
100
  password = request.form.get("password", "")
101
 
102
- bundle_id = get_actual_bundle_id(ipa_path) or "com.simplebit.dynamic"
103
-
104
  # -------------------------------------------------
105
- # Extract IPA
106
  # -------------------------------------------------
107
  temp_dir = tempfile.mkdtemp()
108
 
109
  with zipfile.ZipFile(ipa_path, "r") as zip_ref:
110
  zip_ref.extractall(temp_dir)
111
 
112
- print("EXTRACTED FILES:", list(Path(temp_dir).rglob("*")))
113
-
114
  app_bundle = find_app_bundle(temp_dir)
115
 
116
  if not app_bundle:
117
  shutil.rmtree(temp_dir)
118
- return jsonify({
119
- "error": "No .app bundle found in IPA"
120
- }), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
- print("FOUND APP:", app_bundle)
123
 
124
  # -------------------------------------------------
125
- # SIGN APP
126
  # -------------------------------------------------
127
  command = [
128
  zsign_path,
@@ -133,14 +136,7 @@ def sign():
133
  str(app_bundle)
134
  ]
135
 
136
- result = subprocess.run(
137
- command,
138
- capture_output=True,
139
- text=True
140
- )
141
-
142
- print(result.stdout)
143
- print(result.stderr)
144
 
145
  if result.returncode != 0:
146
  shutil.rmtree(temp_dir)
@@ -151,20 +147,22 @@ def sign():
151
  }), 500
152
 
153
  # -------------------------------------------------
154
- # REPACK IPA
155
  # -------------------------------------------------
156
- shutil.make_archive(
157
- str(job_dir / "signed"),
158
- 'zip',
159
- temp_dir
160
- )
161
 
162
- shutil.move(job_dir / "signed.zip", signed_path)
 
 
 
 
 
163
 
164
  shutil.rmtree(temp_dir)
165
 
166
- # Save bundle ID
167
- (job_dir / "bundle_id.txt").write_text(bundle_id)
168
 
169
  plist_url = f"{BASE_URL}/plist/{job_id}"
170
  install_url = f"itms-services://?action=download-manifest&url={plist_url}"
@@ -176,14 +174,14 @@ def sign():
176
 
177
 
178
  # -------------------------------------------------
179
- # PLIST
180
  # -------------------------------------------------
181
  @app.route("/plist/<job_id>")
182
  def plist(job_id):
183
 
184
  job_dir = Path(job_id)
185
 
186
- bundle_id = "com.simplebit.dynamic"
187
 
188
  if (job_dir / "bundle_id.txt").exists():
189
  bundle_id = (job_dir / "bundle_id.txt").read_text().strip()
@@ -208,6 +206,7 @@ def plist(job_id):
208
  <string>{ipa_url}</string>
209
  </dict>
210
  </array>
 
211
  <key>metadata</key>
212
  <dict>
213
  <key>bundle-identifier</key>
@@ -217,8 +216,9 @@ def plist(job_id):
217
  <key>kind</key>
218
  <string>software</string>
219
  <key>title</key>
220
- <string>SimpleBit App</string>
221
  </dict>
 
222
  </dict>
223
  </array>
224
  </dict>
@@ -232,7 +232,7 @@ def plist(job_id):
232
 
233
 
234
  # -------------------------------------------------
235
- # DOWNLOAD IPA (iOS SAFE)
236
  # -------------------------------------------------
237
  @app.route("/download/<job_id>")
238
  def download(job_id):
@@ -242,11 +242,7 @@ def download(job_id):
242
  if not ipa_path.exists():
243
  return jsonify({"error": "IPA not found"}), 404
244
 
245
- response = send_file(
246
- ipa_path,
247
- mimetype="application/octet-stream"
248
- )
249
-
250
  response.headers["Content-Disposition"] = "inline; filename=signed.ipa"
251
  return response
252
 
@@ -256,10 +252,7 @@ def download(job_id):
256
  # -------------------------------------------------
257
  @app.route("/health")
258
  def health():
259
- return jsonify({
260
- "ok": True,
261
- "zsign": shutil.which("zsign") is not None
262
- })
263
 
264
 
265
  # -------------------------------------------------
 
21
  # -------------------------------------------------
22
  @app.route("/")
23
  def home():
24
+ return jsonify({"status": "online"})
 
 
 
25
 
26
 
27
  # -------------------------------------------------
28
+ # FIND .APP BUNDLE
29
  # -------------------------------------------------
30
+ def find_app_bundle(temp_dir):
31
+ for item in Path(temp_dir).rglob("*.app"):
32
+ if item.is_dir():
33
+ return item
 
 
 
 
 
 
 
 
 
 
 
34
  return None
35
 
36
 
37
  # -------------------------------------------------
38
+ # READ INFO.PLIST
39
  # -------------------------------------------------
40
+ def read_info_plist(app_bundle):
41
+ for f in Path(app_bundle).rglob("Info.plist"):
42
+ with open(f, "rb") as fp:
43
+ return f, plistlib.load(fp)
44
+ return None, None
45
 
 
46
 
47
+ def write_info_plist(plist_path, data):
48
+ with open(plist_path, "wb") as fp:
49
+ plistlib.dump(data, fp)
 
 
50
 
51
 
52
  # -------------------------------------------------
 
59
 
60
  if "ipa" not in request.files:
61
  return jsonify({"error": "Missing IPA"}), 400
 
62
  if "p12" not in request.files:
63
  return jsonify({"error": "Missing P12"}), 400
 
64
  if "provision" not in request.files:
65
  return jsonify({"error": "Missing provisioning profile"}), 400
66
 
67
  zsign_path = shutil.which("zsign")
 
68
  if not zsign_path:
69
  return jsonify({"error": "zsign not installed"}), 500
70
 
 
83
 
84
  password = request.form.get("password", "")
85
 
 
 
86
  # -------------------------------------------------
87
+ # EXTRACT IPA
88
  # -------------------------------------------------
89
  temp_dir = tempfile.mkdtemp()
90
 
91
  with zipfile.ZipFile(ipa_path, "r") as zip_ref:
92
  zip_ref.extractall(temp_dir)
93
 
 
 
94
  app_bundle = find_app_bundle(temp_dir)
95
 
96
  if not app_bundle:
97
  shutil.rmtree(temp_dir)
98
+ return jsonify({"error": "No .app found"}), 500
99
+
100
+ # -------------------------------------------------
101
+ # READ ORIGINAL INFO
102
+ # -------------------------------------------------
103
+ plist_path, plist_data = read_info_plist(app_bundle)
104
+
105
+ if not plist_data:
106
+ shutil.rmtree(temp_dir)
107
+ return jsonify({"error": "Info.plist missing"}), 500
108
+
109
+ original_bundle_id = plist_data.get("CFBundleIdentifier", "unknown.bundle")
110
+ original_name = (
111
+ plist_data.get("CFBundleDisplayName")
112
+ or plist_data.get("CFBundleName")
113
+ or "App"
114
+ )
115
+
116
+ # -------------------------------------------------
117
+ # DO NOT CHANGE BUNDLE ID (ENTERPRISE MODE)
118
+ # -------------------------------------------------
119
+ target_bundle_id = original_bundle_id
120
+
121
+ plist_data["CFBundleIdentifier"] = target_bundle_id
122
+ plist_data["CFBundleDisplayName"] = original_name
123
+ plist_data["CFBundleName"] = original_name
124
 
125
+ write_info_plist(plist_path, plist_data)
126
 
127
  # -------------------------------------------------
128
+ # SIGN
129
  # -------------------------------------------------
130
  command = [
131
  zsign_path,
 
136
  str(app_bundle)
137
  ]
138
 
139
+ result = subprocess.run(command, capture_output=True, text=True)
 
 
 
 
 
 
 
140
 
141
  if result.returncode != 0:
142
  shutil.rmtree(temp_dir)
 
147
  }), 500
148
 
149
  # -------------------------------------------------
150
+ # BUILD CLEAN IPA
151
  # -------------------------------------------------
152
+ payload_dir = Path(job_dir) / "Payload"
153
+ payload_dir.mkdir(exist_ok=True)
 
 
 
154
 
155
+ shutil.copytree(app_bundle, payload_dir / app_bundle.name)
156
+
157
+ zip_path = Path(job_dir) / "signed"
158
+ shutil.make_archive(str(zip_path), "zip", job_dir, "Payload")
159
+
160
+ shutil.move(str(zip_path) + ".zip", signed_path)
161
 
162
  shutil.rmtree(temp_dir)
163
 
164
+ # save ORIGINAL bundle id for manifest
165
+ (job_dir / "bundle_id.txt").write_text(original_bundle_id)
166
 
167
  plist_url = f"{BASE_URL}/plist/{job_id}"
168
  install_url = f"itms-services://?action=download-manifest&url={plist_url}"
 
174
 
175
 
176
  # -------------------------------------------------
177
+ # MANIFEST
178
  # -------------------------------------------------
179
  @app.route("/plist/<job_id>")
180
  def plist(job_id):
181
 
182
  job_dir = Path(job_id)
183
 
184
+ bundle_id = "unknown"
185
 
186
  if (job_dir / "bundle_id.txt").exists():
187
  bundle_id = (job_dir / "bundle_id.txt").read_text().strip()
 
206
  <string>{ipa_url}</string>
207
  </dict>
208
  </array>
209
+
210
  <key>metadata</key>
211
  <dict>
212
  <key>bundle-identifier</key>
 
216
  <key>kind</key>
217
  <string>software</string>
218
  <key>title</key>
219
+ <string>Signed App</string>
220
  </dict>
221
+
222
  </dict>
223
  </array>
224
  </dict>
 
232
 
233
 
234
  # -------------------------------------------------
235
+ # DOWNLOAD IPA
236
  # -------------------------------------------------
237
  @app.route("/download/<job_id>")
238
  def download(job_id):
 
242
  if not ipa_path.exists():
243
  return jsonify({"error": "IPA not found"}), 404
244
 
245
+ response = send_file(ipa_path, mimetype="application/octet-stream")
 
 
 
 
246
  response.headers["Content-Disposition"] = "inline; filename=signed.ipa"
247
  return response
248
 
 
252
  # -------------------------------------------------
253
  @app.route("/health")
254
  def health():
255
+ return jsonify({"ok": True, "zsign": shutil.which("zsign") is not None})
 
 
 
256
 
257
 
258
  # -------------------------------------------------