-
Notifications
You must be signed in to change notification settings - Fork 0
/
apps.py
380 lines (330 loc) · 12.4 KB
/
apps.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
import click
import os
import shutil
import json
import socket
import as_gitcontroller as gc
from as_fscontroller import read_appfile, write_appfile
import as_extracting
import as_installer as installer
def check_create(path):
if not os.path.exists(path):
os.mkdir(path)
APPSOURCE_INDEX = "https://github.com/bahusvel/AppSource-Index.git"
APPSOURCE_REPO = "https://github.com/bahusvel/AppSource.git"
APPSOURCE_REPO_ID = "bahusvel/AppSource"
APPSTORAGE = click.get_app_dir("AppSource")
check_create(APPSTORAGE)
STORAGEINDEX = APPSTORAGE+"/index"
STORAGEIOS = STORAGEINDEX+"/ios"
STORAGEMACOSX = STORAGEINDEX+"/macosx"
STORAGE_LOCAL_INDEX = APPSTORAGE+"/localindex"
STORAGECLI = APPSTORAGE+"/cli"
STORAGEBUILD = APPSTORAGE+"/build"
check_create(STORAGEBUILD)
STORAGESETTINGS = APPSTORAGE+"/settings.json"
STORAGECERTS = APPSTORAGE + "/certs"
check_create(STORAGECERTS)
settings_dict = {}
class TYPE:
IOS = "IOS"
MACOSX = "MACOSX"
@click.group()
def apps():
global settings_dict
click.echo("Welcome to AppSource")
if not os.path.exists(STORAGESETTINGS):
open(STORAGESETTINGS, "w").close()
with open(STORAGESETTINGS, "r+") as settings_file:
fcontents = settings_file.read()
if fcontents is not "":
settings_dict = json.loads(fcontents)
else:
settings_dict = {}
if "group_id" not in settings_dict:
click.secho("This software requires a new Group ID that will be used to sign the applications." + ""
"This Group ID must match your wildcard App ID and provisioning profile, and it will be used for all of your apps")
group_id = click.prompt("Please enter the Group ID in format [TLD].[GROUPNAME] e.g: com.bahus")
settings_dict["group_id"] = group_id
if "store_github_account" not in settings_dict:
click.secho("AppSource uses and regularly interacts with GitHub, a lot of its functionality depends on this integration." + ""
"Allow it to remember your github credentials so that you wont be nagged to enter them every time.")
settings_dict["store_github_account"] = click.confirm("Would you like the system to remember your github credentials? ", default=True)
if settings_dict["store_github_account"] and "github_username" not in settings_dict:
settings_dict["github_username"] = click.prompt("Please enter your GitHub Username")
settings_dict["github_password"] = click.prompt("Please enter your GitHub Password", hide_input=True)
if settings_dict["store_github_account"] and "appsource_stared" not in settings_dict:
try:
gc.github_star(gc.github_get_repo_by_name(APPSOURCE_REPO_ID))
settings_dict["appsource_stared"] = True
except Exception:
click.secho("Failed starring AppSource")
#write the settings back
settings_file.seek(0)
json.dump(settings_dict, settings_file)
@click.group()
def certs():
pass
@click.command()
@click.option("--cert", prompt=True)
@click.option("--key", prompt=True)
def import_ca(cert, key):
shutil.copyfile(cert, STORAGECERTS+"/ca.cer")
shutil.copyfile(key, STORAGECERTS+"/ca.key")
@click.command()
def generate_ca():
os.system("openssl genrsa -out \"{}/ca.key\" 2048".format(STORAGECERTS))
os.system("openssl req -x509 -sha256 -new -key \"{}/ca.key\" -out \"{}/ca.cer\" -days 730 -subj /CN=\"AppSource CA\"".format(STORAGECERTS, STORAGECERTS))
@click.command()
@click.option("--destination", prompt=True)
def export_ca(destination):
shutil.copy(STORAGECERTS+"/ca.cer", destination)
shutil.copy(STORAGECERTS+"/ca.key", destination)
@click.command()
@click.option("--hostname")
@click.option("--destination")
@click.option("--p12/--no-p12", default=False)
def generate_ssl(hostname, destination, p12):
if not os.path.exists(STORAGECERTS + "/ca.cer"):
click.secho("CA is not present, cannot make ssl certificate withou CA")
exit(-1)
if destination is None:
destination = STORAGECERTS
os.system("openssl genrsa -out \"{}/ssl.key\" 2048".format(destination))
if hostname is None:
hostname = socket.gethostname()
os.system("openssl req -new -out \"{}/ssl.req\" -key \"{}/ssl.key\" -subj /CN={}".format(destination, destination, hostname))
os.system("openssl x509 -req -sha256 -in \"{}/ssl.req\" -out \"{}/ssl.cer\" -CAkey \"{}/ca.key\" -CA \"{}/ca.cer\" -days 365 -CAcreateserial -CAserial serial".format(destination, destination, STORAGECERTS, STORAGECERTS))
if destination is not None and p12 is True:
os.system("openssl pkcs12 -export -out \"{}/pkcs.p12\" -in \"{}/ssl.cer\" -inkey \"{}/ssl.key\" -name AppSource -passout pass:appsource".format(destination, destination, destination))
@click.command()
@click.option("--url")
@click.argument("name")
def get(url, name):
get_backend(url, name, os.getcwd())
def get_backend(url, name, path):
if name is None and url is None:
name = click.prompt("Enter the module name")
if name is not None and url is None:
file = "{}/{}.json".format(STORAGEIOS, name)
if os.path.exists(file):
appdict = read_appfile(file)
url = appdict["repo"]
else:
click.secho("The module you requested is not in the index", err=True)
url = click.prompt("Please enter the url for the module")
if url is not None:
github_id = as_extracting.extract_github_id(url)
if name is not None:
appid = name
else:
appid = "github.{}.{}".format(github_id[0], github_id[1])
app_path = path+"/"+appid
if os.path.exists(app_path):
shutil.rmtree(app_path)
gc.gitclone(url, aspath=app_path)
return appdict, app_path
def get_identity():
identities = installer.get_identities()
click.secho(
"""The app you have chosen will need to be signed,
in order to sign the app you need to have an active Apple Developer account,
you are also requried to generate wildcard AppID and Mobile Provisioning Profile for that AppID"""
)
if len(identities) > 1:
s_identity = click.prompt("Please choose the signing identity", type=click.Choice(identities))
elif len(identities) == 1:
s_identity = identities[0]
else:
click.secho("No developer identities were found on your machine!", err=True)
exit(1)
return s_identity
@click.command()
@click.option("--url")
@click.argument("name")
def install(url, name):
appdict, app_path = get_backend(url, name, STORAGEBUILD)
s_identity = get_identity()
installer.build_prep(app_path, settings_dict["group_id"])
#compiling
workspace = installer.get_workspace(app_path)
project = installer.get_project(app_path)
if workspace is not None:
click.secho("The app your are building uses a workspace")
installer.refresh_workspaces(project)
schemes = installer.get_schemes(workspace)
if appdict["name"] in schemes:
scheme = appdict["name"]
else:
scheme = click.prompt("Please enter the scheme name to build the app", type=click.Choice(schemes))
installer.build_workspace(workspace, scheme, s_identity)
elif project is not None:
installer.build_project(project, s_identity)
else:
click.secho("Could not find project or workspace, cannot build the app", err=True)
exit(1)
# installing
bundles = installer.deep_folder_find(app_path, ".app")
if len(bundles) > 1:
bundle = click.prompt("Please choose the correct bundle to deploy", type=click.Choice(bundles))
elif len(bundles) == 1:
bundle = bundles[0]
else:
click.secho("Could not find an App Bundle, maybe compiler failed?", err=True)
locate = click.confirm("Would you like to locate it manually?", default=False)
if not locate:
exit(1)
bundle = click.prompt("Please enter the app bundle location")
installer.install_to_device(bundle)
@click.command()
@click.option("--searchmethod", type=click.Choice(["QUICK", "FULL"]))
@click.argument("search_term")
def search(search_term, searchmethod):
if searchmethod is None:
searchmethod = "QUICK"
apps = search_backend(search_term, entity_type=TYPE.IOS, searchtype=searchmethod)
for app in apps:
appdict = read_appfile(STORAGEIOS + "/" + app)
click.secho("{} ({}) @ {}".format(appdict["name"], app[:-5], appdict["repo"]))
if len(apps) == 0:
click.secho("Nothing found for \"{}\"".format(search_term))
click.secho("Make sure to update your index using: \"apps update\"")
@click.command()
@click.argument("ipa_path")
@click.option("--profile_path")
def resign(ipa_path, profile_path):
if not (ipa_path.endswith(".ipa") or ipa_path.endswith(".IPA")):
click.secho("Non IPA File was supplied")
ipa_resign_tool = STORAGECLI+"/ipa_sign.sh"
s_identity = get_identity()
if profile_path is None:
profiles = installer.filtered_profiles(team=installer.team_id_from_identity(s_identity), app_id="*")
profile = click.prompt("Please choose a profile to embed into IPA", type=click.Choice(list(profiles.keys())))
profile_path = profiles[profile]
os.chdir(os.path.dirname(ipa_path))
os.system("bash \"{}\" {} {} \"{}\"".format(ipa_resign_tool, ipa_path, profile_path, s_identity))
def search_backend(term, searchtype="QUICK", entity_type=TYPE.IOS):
if searchtype == "QUICK":
items = list_type(entity_type)
return list(filter(lambda x: term in x.lower(), items))
elif searchtype == "FULL":
pass
else:
raise Exception("Invalid search method " + searchtype)
def list_type(entity_type):
if entity_type == TYPE.IOS:
searchpath = STORAGEIOS
elif entity_type == TYPE.MACOSX:
searchpath = STORAGEMACOSX
else:
raise Exception(str(entity_type) + "is not supported")
items = os.listdir(searchpath)
return list(filter(lambda x: x.endswith(".json"), items))
@click.command()
def update():
if not os.path.exists(STORAGEINDEX):
os.chdir(APPSTORAGE)
gc.gitclone(APPSOURCE_INDEX, aspath="index")
else:
os.chdir(STORAGEINDEX)
gc.gitpull()
@click.command()
@click.option("--local/--remote", default=False)
def upgrade(local):
if not local:
if not os.path.exists(STORAGECLI):
os.chdir(APPSTORAGE)
gc.gitclone(APPSOURCE_REPO, aspath="cli")
os.chdir(STORAGECLI)
else:
os.chdir(STORAGECLI)
gc.gitpull()
if not local:
os.system("pip install --upgrade .")
else:
os.system("pip install --upgrade --editable .")
@click.command()
def clean():
shutil.rmtree(STORAGEBUILD)
os.mkdir(STORAGEBUILD)
@click.command()
def sync():
sync_backend()
def sync_backend():
commited = False
if not os.path.exists(".git"):
if click.confirm("This is not a git repository, would you like to initialize it as git?", default=True):
gc.gitinit()
else:
click.secho("Operation aborted", err=True)
exit(1)
if gc.getremote() != "":
if gc.isdiff():
if click.confirm("You have modified the app do you want to commit?"):
msg = click.prompt("Please enter a commit message")
gc.gitupsync(msg)
commited = True
gc.gitpull()
if commited:
gc.gitpush()
else:
click.secho("You do not have git remote setup", err=True)
if click.confirm("Do you want one setup automatically?"):
gc.github_login()
name = click.prompt("Please enter the name for the repo")
remote = gc.github_create_repo(name).clone_url
else:
click.secho("You will have to create a repository manually and provide the clone url")
remote = click.prompt("Please enter the url")
gc.addremote(remote)
gc.gitadd(".")
gc.gitcommit("Initializing repo")
gc.gitpush(create_branch=True)
@click.command()
def publish():
entitypath = os.getcwd()
username = gc.github_login().get_user().login
sync_backend()
localindex = gc.get_appsource_index()
if localindex is None:
localindex = gc.fork_on_github("bahusvel/AppSource-Index")
if not os.path.exists(STORAGE_LOCAL_INDEX):
os.chdir(APPSTORAGE)
gc.gitclone(localindex.clone_url, aspath="localindex")
os.chdir(STORAGE_LOCAL_INDEX)
gc.gitpull()
# create corfile here
app_dict = {}
app_id = click.prompt("Please enter the App ID for your application")
app_dict["name"] = app_id[app_id.rfind(".")+1:]
app_dict["description"] = click.prompt("Please enter a short description for your app")
os.chdir(entitypath)
app_dict["repo"] = gc.getremote()
public_appfile_path = STORAGE_LOCAL_INDEX+"/ios/" + app_id + ".json"
write_appfile(app_dict, public_appfile_path)
os.chdir(STORAGE_LOCAL_INDEX)
gc.gitadd(public_appfile_path)
gc.gitcommit("Added " + app_id)
gc.gitpush()
try:
gc.github_pull_request(localindex.full_name, username, "AppSource-Index", "Add " + app_id)
except Exception:
click.secho("Pull request failed, please create one manualy", err=True)
# certs layout
certs.add_command(import_ca)
certs.add_command(generate_ca)
certs.add_command(export_ca)
certs.add_command(generate_ssl)
# command layout
apps.add_command(install)
apps.add_command(search)
apps.add_command(update)
apps.add_command(upgrade)
apps.add_command(publish)
apps.add_command(sync)
apps.add_command(clean)
apps.add_command(get)
apps.add_command(resign)
apps.add_command(certs)
if __name__ == '__main__':
apps()