gabefromsimplebit commited on
Commit
e4fbb64
·
verified ·
1 Parent(s): 7279a02

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +130 -72
app.py CHANGED
@@ -1,4 +1,4 @@
1
- from flask import Flask, request, send_file, redirect, jsonify
2
  from flask_cors import CORS
3
 
4
  import subprocess
@@ -6,14 +6,13 @@ import plistlib
6
  import zipfile
7
  import shutil
8
  import uuid
9
- import os
10
 
11
  from pathlib import Path
12
 
13
  app = Flask(__name__)
14
  CORS(app)
15
 
16
- # CHANGE THIS TO YOUR REAL SPACE URL
17
  BASE_URL = "https://gabefromsimplebit-simplebit-signer.hf.space"
18
 
19
 
@@ -25,55 +24,86 @@ def home():
25
  })
26
 
27
 
 
 
 
28
  def get_actual_bundle_id(ipa_path):
29
- """
30
- Safely extract CFBundleIdentifier from IPA
31
- """
32
  try:
33
  with zipfile.ZipFile(ipa_path, "r") as ipa:
34
- plist_name = None
 
35
 
36
  for name in ipa.namelist():
37
- if name.startswith("Payload/") and name.endswith("Info.plist"):
38
- plist_name = name
 
 
 
 
 
 
 
39
  break
40
 
41
- if not plist_name:
 
42
  return None
43
 
44
- with ipa.open(plist_name) as plist_file:
 
 
45
  plist_data = plistlib.load(plist_file)
46
 
47
- return plist_data.get("CFBundleIdentifier")
 
 
 
 
48
 
49
  except Exception as e:
50
- print("Bundle ID extraction failed:", str(e))
51
 
52
  return None
53
 
54
 
 
 
 
55
  def save_bundle_id(job_dir, bundle_id):
 
56
  try:
57
  with open(job_dir / "bundle_id.txt", "w") as f:
58
  f.write(bundle_id)
 
59
  except Exception as e:
60
- print("Failed to save bundle ID:", str(e))
61
 
62
 
 
 
 
63
  def load_bundle_id(job_dir):
 
64
  try:
65
  with open(job_dir / "bundle_id.txt", "r") as f:
66
  return f.read().strip()
 
67
  except:
68
  return "com.simplebit.dynamic"
69
 
70
 
 
 
 
71
  @app.route("/sign", methods=["POST"])
72
  def sign():
 
73
  try:
74
- # ---------------------------------------------------
 
75
  # Validate uploads
76
- # ---------------------------------------------------
77
 
78
  if "ipa" not in request.files:
79
  return jsonify({
@@ -82,7 +112,7 @@ def sign():
82
 
83
  if "p12" not in request.files:
84
  return jsonify({
85
- "error": "Missing P12 certificate"
86
  }), 400
87
 
88
  if "provision" not in request.files:
@@ -90,9 +120,9 @@ def sign():
90
  "error": "Missing provisioning profile"
91
  }), 400
92
 
93
- # ---------------------------------------------------
94
  # Check zsign installation
95
- # ---------------------------------------------------
96
 
97
  zsign_path = shutil.which("zsign")
98
 
@@ -103,62 +133,63 @@ def sign():
103
  "error": "zsign is not installed"
104
  }), 500
105
 
106
- # ---------------------------------------------------
107
- # Create job directory
108
- # ---------------------------------------------------
109
 
110
  job_id = str(uuid.uuid4())[:8]
111
 
112
  job_dir = Path(job_id)
113
  job_dir.mkdir(exist_ok=True)
114
 
115
- # ---------------------------------------------------
116
- # Uploaded files
117
- # ---------------------------------------------------
118
-
119
- ipa = request.files["ipa"]
120
- p12 = request.files["p12"]
121
- provision = request.files["provision"]
122
-
123
- password = request.form.get("password", "")
124
 
125
  ipa_path = job_dir / "app.ipa"
126
  p12_path = job_dir / "cert.p12"
127
  prov_path = job_dir / "profile.mobileprovision"
128
- out_path = job_dir / "signed.ipa"
 
 
 
 
129
 
130
- ipa.save(ipa_path)
131
- p12.save(p12_path)
132
- provision.save(prov_path)
133
 
134
- # ---------------------------------------------------
 
 
135
  # Extract bundle ID
136
- # ---------------------------------------------------
137
 
138
  bundle_id = get_actual_bundle_id(ipa_path)
139
 
140
- print("BUNDLE ID:", bundle_id)
 
141
 
142
- # ---------------------------------------------------
143
  # zsign command
144
- # ---------------------------------------------------
145
 
146
  command = [
147
  zsign_path,
148
  "-k", str(p12_path),
149
  "-p", password,
150
  "-m", str(prov_path),
151
- "-o", str(out_path),
152
  "-z", "0",
153
  str(ipa_path)
154
  ]
155
 
156
- print("RUNNING COMMAND:")
157
  print(" ".join(command))
158
 
159
- # ---------------------------------------------------
160
- # Run signing
161
- # ---------------------------------------------------
162
 
163
  result = subprocess.run(
164
  command,
@@ -167,16 +198,19 @@ def sign():
167
  )
168
 
169
  print("RETURN CODE:", result.returncode)
 
170
  print("STDOUT:")
171
  print(result.stdout)
 
172
  print("STDERR:")
173
  print(result.stderr)
174
 
175
- # ---------------------------------------------------
176
- # Handle failure
177
- # ---------------------------------------------------
178
 
179
  if result.returncode != 0:
 
180
  return jsonify({
181
  "error": "Signing failed",
182
  "returncode": result.returncode,
@@ -184,60 +218,66 @@ def sign():
184
  "stderr": result.stderr
185
  }), 500
186
 
187
- # ---------------------------------------------------
188
- # Verify signed IPA exists
189
- # ---------------------------------------------------
 
 
190
 
191
- if not out_path.exists():
192
  return jsonify({
193
- "error": "Signed IPA missing after signing"
194
  }), 500
195
 
196
- # ---------------------------------------------------
197
- # Fallback bundle ID
198
- # ---------------------------------------------------
199
-
200
- if not bundle_id:
201
- bundle_id = "com.simplebit.dynamic"
202
 
203
  save_bundle_id(job_dir, bundle_id)
204
 
205
- # ---------------------------------------------------
206
- # OTA Install URL
207
- # ---------------------------------------------------
208
 
209
  plist_url = f"{BASE_URL}/plist/{job_id}"
210
 
211
- ota_url = (
212
  "itms-services://?action=download-manifest"
213
  f"&url={plist_url}"
214
  )
215
 
216
- return jsonify({
217
- "success": True,
218
- "job_id": job_id,
219
- "bundle_id": bundle_id,
220
- "install_url": ota_url,
221
- "plist_url": plist_url,
222
- "download_url": f"{BASE_URL}/download/{job_id}"
223
- })
224
 
225
  except Exception as e:
226
- print("FATAL ERROR:", str(e))
 
 
227
 
228
  return jsonify({
229
  "error": str(e)
230
  }), 500
231
 
232
 
 
 
 
233
  @app.route("/plist/<job_id>")
234
  def serve_plist(job_id):
 
235
  try:
 
236
  job_dir = Path(job_id)
237
 
238
  signed_ipa = job_dir / "signed.ipa"
239
 
240
  if not signed_ipa.exists():
 
241
  return jsonify({
242
  "error": "Signed IPA not found"
243
  }), 404
@@ -259,6 +299,7 @@ def serve_plist(job_id):
259
 
260
  <key>assets</key>
261
  <array>
 
262
  <dict>
263
  <key>kind</key>
264
  <string>software-package</string>
@@ -266,6 +307,7 @@ def serve_plist(job_id):
266
  <key>url</key>
267
  <string>{ipa_url}</string>
268
  </dict>
 
269
  </array>
270
 
271
  <key>metadata</key>
@@ -303,17 +345,24 @@ def serve_plist(job_id):
303
  )
304
 
305
  except Exception as e:
 
306
  return jsonify({
307
  "error": str(e)
308
  }), 500
309
 
310
 
 
 
 
311
  @app.route("/download/<job_id>")
312
  def download_ipa(job_id):
 
313
  try:
 
314
  ipa_path = Path(job_id) / "signed.ipa"
315
 
316
  if not ipa_path.exists():
 
317
  return jsonify({
318
  "error": "IPA not found"
319
  }), 404
@@ -325,20 +374,29 @@ def download_ipa(job_id):
325
  )
326
 
327
  except Exception as e:
 
328
  return jsonify({
329
  "error": str(e)
330
  }), 500
331
 
332
 
 
 
 
333
  @app.route("/health")
334
  def health():
 
335
  return jsonify({
336
  "status": "healthy",
337
  "zsign_installed": shutil.which("zsign") is not None
338
  })
339
 
340
 
 
 
 
341
  if __name__ == "__main__":
 
342
  app.run(
343
  host="0.0.0.0",
344
  port=7860,
 
1
+ from flask import Flask, request, send_file, jsonify, redirect
2
  from flask_cors import CORS
3
 
4
  import subprocess
 
6
  import zipfile
7
  import shutil
8
  import uuid
 
9
 
10
  from pathlib import Path
11
 
12
  app = Flask(__name__)
13
  CORS(app)
14
 
15
+ # CHANGE THIS TO YOUR REAL HF SPACE URL
16
  BASE_URL = "https://gabefromsimplebit-simplebit-signer.hf.space"
17
 
18
 
 
24
  })
25
 
26
 
27
+ # ---------------------------------------------------------
28
+ # Extract REAL main app bundle identifier
29
+ # ---------------------------------------------------------
30
  def get_actual_bundle_id(ipa_path):
31
+
 
 
32
  try:
33
  with zipfile.ZipFile(ipa_path, "r") as ipa:
34
+
35
+ app_plist = None
36
 
37
  for name in ipa.namelist():
38
+
39
+ # ONLY match:
40
+ # Payload/App.app/Info.plist
41
+ if (
42
+ name.startswith("Payload/")
43
+ and name.count("/") == 2
44
+ and name.endswith(".app/Info.plist")
45
+ ):
46
+ app_plist = name
47
  break
48
 
49
+ if not app_plist:
50
+ print("Main app Info.plist not found")
51
  return None
52
 
53
+ print("MAIN APP PLIST:", app_plist)
54
+
55
+ with ipa.open(app_plist) as plist_file:
56
  plist_data = plistlib.load(plist_file)
57
 
58
+ bundle_id = plist_data.get("CFBundleIdentifier")
59
+
60
+ print("REAL BUNDLE ID:", bundle_id)
61
+
62
+ return bundle_id
63
 
64
  except Exception as e:
65
+ print("Bundle extraction error:", str(e))
66
 
67
  return None
68
 
69
 
70
+ # ---------------------------------------------------------
71
+ # Save bundle ID
72
+ # ---------------------------------------------------------
73
  def save_bundle_id(job_dir, bundle_id):
74
+
75
  try:
76
  with open(job_dir / "bundle_id.txt", "w") as f:
77
  f.write(bundle_id)
78
+
79
  except Exception as e:
80
+ print("Save bundle ID error:", str(e))
81
 
82
 
83
+ # ---------------------------------------------------------
84
+ # Load bundle ID
85
+ # ---------------------------------------------------------
86
  def load_bundle_id(job_dir):
87
+
88
  try:
89
  with open(job_dir / "bundle_id.txt", "r") as f:
90
  return f.read().strip()
91
+
92
  except:
93
  return "com.simplebit.dynamic"
94
 
95
 
96
+ # ---------------------------------------------------------
97
+ # SIGN IPA
98
+ # ---------------------------------------------------------
99
  @app.route("/sign", methods=["POST"])
100
  def sign():
101
+
102
  try:
103
+
104
+ # -------------------------------------------------
105
  # Validate uploads
106
+ # -------------------------------------------------
107
 
108
  if "ipa" not in request.files:
109
  return jsonify({
 
112
 
113
  if "p12" not in request.files:
114
  return jsonify({
115
+ "error": "Missing P12 file"
116
  }), 400
117
 
118
  if "provision" not in request.files:
 
120
  "error": "Missing provisioning profile"
121
  }), 400
122
 
123
+ # -------------------------------------------------
124
  # Check zsign installation
125
+ # -------------------------------------------------
126
 
127
  zsign_path = shutil.which("zsign")
128
 
 
133
  "error": "zsign is not installed"
134
  }), 500
135
 
136
+ # -------------------------------------------------
137
+ # Create unique job folder
138
+ # -------------------------------------------------
139
 
140
  job_id = str(uuid.uuid4())[:8]
141
 
142
  job_dir = Path(job_id)
143
  job_dir.mkdir(exist_ok=True)
144
 
145
+ # -------------------------------------------------
146
+ # Paths
147
+ # -------------------------------------------------
 
 
 
 
 
 
148
 
149
  ipa_path = job_dir / "app.ipa"
150
  p12_path = job_dir / "cert.p12"
151
  prov_path = job_dir / "profile.mobileprovision"
152
+ signed_path = job_dir / "signed.ipa"
153
+
154
+ # -------------------------------------------------
155
+ # Save uploaded files
156
+ # -------------------------------------------------
157
 
158
+ request.files["ipa"].save(ipa_path)
159
+ request.files["p12"].save(p12_path)
160
+ request.files["provision"].save(prov_path)
161
 
162
+ password = request.form.get("password", "")
163
+
164
+ # -------------------------------------------------
165
  # Extract bundle ID
166
+ # -------------------------------------------------
167
 
168
  bundle_id = get_actual_bundle_id(ipa_path)
169
 
170
+ if not bundle_id:
171
+ bundle_id = "com.simplebit.dynamic"
172
 
173
+ # -------------------------------------------------
174
  # zsign command
175
+ # -------------------------------------------------
176
 
177
  command = [
178
  zsign_path,
179
  "-k", str(p12_path),
180
  "-p", password,
181
  "-m", str(prov_path),
182
+ "-o", str(signed_path),
183
  "-z", "0",
184
  str(ipa_path)
185
  ]
186
 
187
+ print("RUNNING:")
188
  print(" ".join(command))
189
 
190
+ # -------------------------------------------------
191
+ # Execute zsign
192
+ # -------------------------------------------------
193
 
194
  result = subprocess.run(
195
  command,
 
198
  )
199
 
200
  print("RETURN CODE:", result.returncode)
201
+
202
  print("STDOUT:")
203
  print(result.stdout)
204
+
205
  print("STDERR:")
206
  print(result.stderr)
207
 
208
+ # -------------------------------------------------
209
+ # Signing failed
210
+ # -------------------------------------------------
211
 
212
  if result.returncode != 0:
213
+
214
  return jsonify({
215
  "error": "Signing failed",
216
  "returncode": result.returncode,
 
218
  "stderr": result.stderr
219
  }), 500
220
 
221
+ # -------------------------------------------------
222
+ # Ensure IPA exists
223
+ # -------------------------------------------------
224
+
225
+ if not signed_path.exists():
226
 
 
227
  return jsonify({
228
+ "error": "Signed IPA missing"
229
  }), 500
230
 
231
+ # -------------------------------------------------
232
+ # Save bundle ID
233
+ # -------------------------------------------------
 
 
 
234
 
235
  save_bundle_id(job_dir, bundle_id)
236
 
237
+ # -------------------------------------------------
238
+ # Generate install link
239
+ # -------------------------------------------------
240
 
241
  plist_url = f"{BASE_URL}/plist/{job_id}"
242
 
243
+ install_url = (
244
  "itms-services://?action=download-manifest"
245
  f"&url={plist_url}"
246
  )
247
 
248
+ print("INSTALL URL:")
249
+ print(install_url)
250
+
251
+ # -------------------------------------------------
252
+ # AUTO REDIRECT TO INSTALL
253
+ # -------------------------------------------------
254
+
255
+ return redirect(install_url)
256
 
257
  except Exception as e:
258
+
259
+ print("FATAL ERROR:")
260
+ print(str(e))
261
 
262
  return jsonify({
263
  "error": str(e)
264
  }), 500
265
 
266
 
267
+ # ---------------------------------------------------------
268
+ # OTA plist
269
+ # ---------------------------------------------------------
270
  @app.route("/plist/<job_id>")
271
  def serve_plist(job_id):
272
+
273
  try:
274
+
275
  job_dir = Path(job_id)
276
 
277
  signed_ipa = job_dir / "signed.ipa"
278
 
279
  if not signed_ipa.exists():
280
+
281
  return jsonify({
282
  "error": "Signed IPA not found"
283
  }), 404
 
299
 
300
  <key>assets</key>
301
  <array>
302
+
303
  <dict>
304
  <key>kind</key>
305
  <string>software-package</string>
 
307
  <key>url</key>
308
  <string>{ipa_url}</string>
309
  </dict>
310
+
311
  </array>
312
 
313
  <key>metadata</key>
 
345
  )
346
 
347
  except Exception as e:
348
+
349
  return jsonify({
350
  "error": str(e)
351
  }), 500
352
 
353
 
354
+ # ---------------------------------------------------------
355
+ # Download signed IPA
356
+ # ---------------------------------------------------------
357
  @app.route("/download/<job_id>")
358
  def download_ipa(job_id):
359
+
360
  try:
361
+
362
  ipa_path = Path(job_id) / "signed.ipa"
363
 
364
  if not ipa_path.exists():
365
+
366
  return jsonify({
367
  "error": "IPA not found"
368
  }), 404
 
374
  )
375
 
376
  except Exception as e:
377
+
378
  return jsonify({
379
  "error": str(e)
380
  }), 500
381
 
382
 
383
+ # ---------------------------------------------------------
384
+ # Health endpoint
385
+ # ---------------------------------------------------------
386
  @app.route("/health")
387
  def health():
388
+
389
  return jsonify({
390
  "status": "healthy",
391
  "zsign_installed": shutil.which("zsign") is not None
392
  })
393
 
394
 
395
+ # ---------------------------------------------------------
396
+ # Run Flask
397
+ # ---------------------------------------------------------
398
  if __name__ == "__main__":
399
+
400
  app.run(
401
  host="0.0.0.0",
402
  port=7860,