Compare commits

...

206 Commits

Author SHA1 Message Date
mahee96
dfdac41a70 vpn-configuration: fix - properly report active status 2026-03-03 13:24:48 +05:30
mahee96
62ad755920 vpn-configuration: fix typo in sentence 2026-03-03 12:29:22 +05:30
mahee96
baf1aa7fff vpn-configuration: updated to reflect actual implementation 2026-03-03 12:27:12 +05:30
mahee96
395d2bb657 minimuxer: gutted discovery mechanism for finding the fake deviceIP from uTun routes coz it is impossible to walk subnet and kernel route table doesn't tell active associations but just possible routes attempted 2026-03-03 12:20:47 +05:30
mahee96
e32f4fbeae vpn-configuration: removed UI updating state despite bindTunnelConfig() driving it just behind 2026-03-03 10:57:12 +05:30
mahee96
b643f6ae01 minimuxer: fix some stale state issues during VPN up/down cycles or changing config 2026-03-03 10:49:36 +05:30
mahee96
2f0fb3cb71 minimuxer: added back file headers info about authors 2026-03-03 09:59:48 +05:30
mahee96
0f90f9bf2d DeviceEndpoint: fix all minimuxer woes by properly handling usbmuxd signals for listdevices and listen 2026-03-03 09:20:39 +05:30
mahee96
1c8541fdd4 MinimuxerWrapper: remove spam logs 2026-03-03 04:33:07 +05:30
mahee96
4e6882423d Heartbeat: improved logging intent 2026-03-03 04:32:46 +05:30
mahee96
7c3add0e70 IfaceScanner: fix some state update issues 2026-03-03 04:02:07 +05:30
mahee96
c4139473fa IfaceScanner: used the user specified IP to directly test if device is available there instead of assuming it to be under the subnet of tunnel coz it is fake endpoint interceptable by the tunnel's included routes 2026-03-03 03:23:00 +05:30
mahee96
0837f82a69 minimuxer: added binding for the cached tunnel config values so that application layer can provide and listen to updates. 2026-03-03 02:57:39 +05:30
mahee96
c04ebd3ec6 vpn-configuration: added new swiftUI view to settings to view discovered vpn settings and provide an override for deviceIP (if required) by user. 2026-03-03 02:54:58 +05:30
mahee96
0e81515af9 minimuxer: fix usbmuxd socket error handling - restart on first error and dont wait for 5 lol 2026-03-02 17:05:38 +05:30
mahee96
dd4c0be75d minimuxer: match API name 2026-03-02 16:57:56 +05:30
mahee96
7500dd5b17 minimuxer: usbmuxer was not handling socket kill or death properly when app was backgrounded and came into foreground 2026-03-02 16:57:37 +05:30
mahee96
dba84ed015 minimuxer: IfaceScanner - updated to skim thru kernel routing tables and find the included routes of our uTun 2026-03-02 14:59:08 +05:30
mahee96
dbb1e0ff3e minimuxer: gut checks for allowRange/whitelist since we control it on the VPN side 2026-03-02 10:14:21 +05:30
mahee96
431900368a pbxproj - switch to sidestore repos for minimuxer 2026-03-02 08:56:45 +05:30
mahee96
8843a95bfc minimuxer: added empty header dirs required by xcframework 2026-03-02 08:28:25 +05:30
mahee96
4810f3a624 minimuxer: removed target dir from exlcudes since it is in .gitignore 2026-03-02 08:25:37 +05:30
mahee96
954a96b988 minimuxer: added the xcframework libs which was missed before 2026-03-02 08:22:49 +05:30
mahee96
afd32a285d pbxproj - removed local references and used github references for altsign and minimuxer spm packages 2026-03-02 08:11:36 +05:30
mahee96
f8a0ee2c84 cellular-refresh: disable shortcuts spawning until this feature is complete since it interferes with testing for now. 2026-03-02 08:03:41 +05:30
mahee96
9d01a1c116 minimuxer: allow specific network address for sidestore tunnels 2026-03-02 07:58:21 +05:30
mahee96
f6db823816 bug-fix: be on right thread when doing UI work or accessing UI references/UIApplication 2026-03-02 07:34:35 +05:30
mahee96
dfb018bb89 minimuxer: added dynamic lookup of peer for utun(VPN) when not in P2P which is required for sidestore 2026-03-02 07:34:07 +05:30
mahee96
adb91439e4 minimuxer: added dynamic lookup of peer for utun(VPN) when not in P2P which is required for sidestore 2026-03-02 07:33:51 +05:30
mahee96
368a2afad4 bug-fix: ensure main thread when using UIApplication.shared 2026-03-02 04:54:40 +05:30
mahee96
830b66c364 MinimuxerWrapper - for now lets use hardcoded addresses as before - so commented out dynamic discovery 2026-03-02 04:54:20 +05:30
mahee96
40a2137069 minimuxer: install - added device connection check before requesting afc services 2026-03-02 04:53:37 +05:30
mahee96
fa752e3ae6 minimuxer: muxer works now - but has heartbeat issue due to getDevice() failing sometimes, will investigate next. 2026-03-02 03:24:45 +05:30
mahee96
71b319c8e6 pbxproj: restore required slowly - added all missing source files (.h, .c, .cpp) for libimobiledevice and added missing entries under products for libimobiledevice, libem_proxy-swift 2026-03-01 18:00:37 +05:30
mahee96
fd12d36980 pbxproj: restore required slowly - added back libimobiledevice compilation sources (.h, .c, .cpp) 2026-03-01 16:49:03 +05:30
mahee96
cdc41c7954 submodules: updated to latest 2026-03-01 16:31:45 +05:30
mahee96
1fe3e5a6ca pbxproj: restore required slowly - cleanup + added em_proxy target libs properly 2026-03-01 16:31:45 +05:30
mahee96
bf65bce7b5 em_proxy: updated submodule to latest 2026-03-01 16:13:05 +05:30
mahee96
7f3647a230 pbxproj: restore required slowly - streamline em_proxy dependencies 2026-03-01 16:08:38 +05:30
mahee96
850180273c em_proxy: consolidate em_proxy stuff into its own repo (moved em_proxy.xcodeproj, fetch-prebuilt.sh into its repo) 2026-03-01 16:08:07 +05:30
mahee96
6a20d50fb6 pbxproj: restore required slowly - added back xcode auto-discovered roxas related framework, product and xcodeproj (xcode won't let me live in peace otherwise) - now total pbxproj lines = 2.5K 2026-03-01 15:41:35 +05:30
mahee96
79dc65f1c8 pbxproj: restore required slowly - added back xcode auto-discovered framework, xcodeproj (xcode is persistent on adding this back into pbxproj even if I ignore or remove it). 2026-03-01 15:38:00 +05:30
mahee96
a9a2d8780b pbxproj: restore required slowly - added Dependencies/** references after careful vetting by GROUP reference instead of FOLDER reference in xcode 2026-03-01 15:36:22 +05:30
mahee96
2286e78a55 pbxproj: restore required slowly - added Dependencies bare reference 2026-03-01 15:23:01 +05:30
mahee96
3aa3b5df65 pbxproj: restore required slowly - removed Dependencies as a folder and will add back as group coz xcode scans the frameworks and xcodeproj files again and again each time due to it being folder type reference 2026-03-01 15:04:06 +05:30
mahee96
6cdfc211ea minimuxer: check-in package.resolved 2026-03-01 14:57:35 +05:30
mahee96
58ff20c690 minimuxer: reverted to using pre-compiled(and checked-in) RustBridge.xcframework which will be accessible only by MinimuxerBridge which will expose it to minimuxer swift layer under Sources/*.swift . 2026-03-01 14:55:17 +05:30
mahee96
0b865729a7 pbxproj: restore required slowly - added em_proxy swift source and static lib reference 2026-03-01 14:55:12 +05:30
mahee96
7c4cf2ae62 pbxproj: cleanup - removed obsolete xcscheme 2026-03-01 14:54:09 +05:30
mahee96
b0c65b04f2 pbxproj: restore required slowly - added depdencies dir reference 2026-03-01 13:11:39 +05:30
mahee96
f3f8036693 pbxproj: massive cleanup - removed duplicate and stale entries - bring down from 10K to 1.5K lines 2026-03-01 13:06:27 +05:30
mahee96
ee7c97fd3e workspace: added minimuxer SPM package 2026-03-01 01:49:12 +05:30
mahee96
f5eb669a74 MinimuxerWrapper: cleanup 2026-03-01 01:48:45 +05:30
mahee96
3af3aa071c MinimuxerWrapper: streamline the logging 2026-03-01 01:21:06 +05:30
mahee96
857c1e03e0 minimuxer: use newly exposed Swift wrappers in MinimuxerWrapper.swift 2026-03-01 01:20:47 +05:30
mahee96
c09c290973 minimuxer: removed old minimuxer binaries fetch from the fetch script 2026-03-01 01:20:47 +05:30
mahee96
7b8884a8ac pbxproj: removed minimuxer as a static lib and switched to SPM based 2026-03-01 01:18:18 +05:30
mahee96
fd797fdf88 minimuxer: removed old xcodeproj 2026-03-01 01:18:18 +05:30
mahee96
b18ce43a64 minimuxer: removed old xcodeproj 2026-03-01 01:18:18 +05:30
mahee96
e90b8a0e32 minimuxer: switch to build tool plugin for rust build with SPM target 2026-03-01 01:18:18 +05:30
mahee96
bb578316f1 minimuxer: use zip-foundation instead of process for zip/unzip 2026-02-28 15:08:17 +05:30
mahee96
9579d46bed minimuxer: added few more files that were missed 2026-02-28 14:56:17 +05:30
mahee96
a916293585 minimuxer: added Package.swift which was missed 2026-02-28 14:46:51 +05:30
mahee96
f634b52e44 minimuxer: updated wrapper to use spm based reference of swift-minimuxer 2026-02-28 14:23:18 +05:30
mahee96
a6baf59649 minimuxer: full rewrite in swift from rust 2026-02-28 14:22:55 +05:30
ny
a505d04215 fix: 26.4 patch, add correct dest
- update minimuxer
2026-02-27 22:21:41 -05:00
mahee96
95953ca0e9 IfManager: added String representation for readability 2026-02-28 06:45:12 +05:30
mahee96
3b9b45a06f pbxproj: was missing references to IfManager.swift 2026-02-28 04:41:33 +05:30
mahee96
ac277aa6eb minimuxer: added @nythepegasus's patch as-is 2026-02-28 04:24:03 +05:30
mahee96
7926452661 minimuxer: added @nythepegasus's patch as-is 2026-02-28 04:23:58 +05:30
mahee96
dfb01c2ae5 minimuxer: updated submodule to latest 2026-02-28 03:52:35 +05:30
mahee96
bff192be4e ALTSign cleanup 2026-02-25 08:16:42 +05:30
mahee96
a863b2f39a ALTSign cleanup 2026-02-25 08:15:43 +05:30
mahee96
bf72a0edfc CI: add what xcode is suggesting (xcode noise from auto create schemes) 2026-02-25 08:15:31 +05:30
mahee96
06ad6488cc CI: changelog in md was incorrect in release notes 2026-02-25 08:08:46 +05:30
mahee96
12f84b2365 CI: fixes for author 2026-02-25 07:46:54 +05:30
mahee96
118f64de8a CI: multiple fixes 2026-02-25 07:33:55 +05:30
mahee96
555bb3d985 CI: remove harcoded repo name and use incoming 2026-02-25 03:30:57 +05:30
mahee96
25925aceef CI: create tag if required 2026-02-25 03:18:31 +05:30
mahee96
5444fdd9bb CI: handle zipping irrespective of encryption is possible or not 2026-02-25 03:14:50 +05:30
mahee96
f91e0a6295 fix compilation issue after recent merge 2026-02-25 03:00:34 +05:30
mahee96
4e4f0f6a3f Merge branch 'cell-staging' into develop 2026-02-25 02:50:13 +05:30
mahee96
9706a43bc1 ci - decouple source_metadata.json generation from deploy 2026-02-25 02:41:58 +05:30
mahee96
d748a89b47 ci - do not encrypt build logs if password unavailable (forks CI friendly) 2026-02-25 01:45:23 +05:30
mahee96
f412f6df23 Merge branch 'cell-shortcut' into cell-staging
# Conflicts:
#	AltStore/Operations/SendAppOperation.swift
2026-02-25 01:20:56 +05:30
mahee96
b8354d3d0e cleanup - xcode brought these new entries for schemes 2026-02-25 01:18:09 +05:30
mahee96
191ce15d55 Merge branch 'staging' into develop 2026-02-25 00:50:28 +05:30
mahee96
7f8f4fa9a7 ci: fixes for yml 2026-02-25 00:42:02 +05:30
mahee96
ad54cbadc1 ci: fixes for yml 2026-02-25 00:40:09 +05:30
mahee96
eb251b89c9 ci: fixes for yml 2026-02-25 00:30:07 +05:30
mahee96
9efea00d09 ci: use runner number as build number 2026-02-25 00:25:11 +05:30
mahee96
7df29ca23b ci: fixes in workflow.py 2026-02-25 00:10:40 +05:30
mahee96
d06520d46f ci: fix - tag was not pushed 2026-02-25 00:08:41 +05:30
mahee96
1ad0fe23fc ci: fix - version number was inconsistent across deployment of beta channels 2026-02-25 00:04:42 +05:30
mahee96
21bbcd69f8 ci: updated stable workflow as per workflow.py and other workflows 2026-02-24 22:07:36 +05:30
mahee96
fd920be3bf ci: updated PR workflow to reflect latest 2026-02-24 21:15:28 +05:30
mahee96
16bb57c825 ci: use proper messaging for upstream tag 2026-02-24 20:51:25 +05:30
mahee96
bf5b1c935c ci: cleanup - removed obsolete stuffs 2026-02-24 20:43:35 +05:30
mahee96
1b1c7c58e2 ci: moved cache maintenance script into obsolete 2026-02-24 20:43:03 +05:30
Huge_Black
31e8eb7996 Merge pull request #1183 from LiveContainer/develop
Fix issues profile/afc and use main profile issue
2026-02-24 23:07:11 +08:00
mahee96
47db2c3d5d ci: fix typo in brew command 2026-02-24 20:35:50 +05:30
mahee96
967b9f7572 ci: removed caching for ldid and xcbeautify coz they take much more time to restore than install 2026-02-24 20:32:02 +05:30
mahee96
b91bcee70f ci: added some names to few steps 2026-02-24 20:27:46 +05:30
mahee96
25f34c6f69 ci: added back the nightly schedule checking for new commits and only then build 2026-02-24 20:23:12 +05:30
mahee96
b7085aaeca ci: improve speed by caching brew install step and reading project settings from dumped xcodebuild -showProjectSettings instead of invoking each time. 2026-02-24 18:54:41 +05:30
mahee96
046d2788b9 ci: fix indentation in release notes 2026-02-24 18:20:27 +05:30
mahee96
f44ed0a947 ci: fix indentation in release notes 2026-02-24 18:19:54 +05:30
Huge_Black
d356045b5d bug-fix: fix crash when viewing app ids 2026-02-24 16:08:15 +08:00
Huge_Black
a54881a1c8 bug-fix: fix profile not installed 2026-02-24 16:08:15 +08:00
Huge_Black
e27d44eece fix build issue 2026-02-24 16:08:15 +08:00
Huge_Black
c2cecb63ac bug-fix: fix useMainProfile not saved / not set during initialization
SideStore is killed by iOS before CoreData commit changes to db.

Detect if the app is installed with useMainProfile by checking if applicationIdentifier matches
2026-02-24 16:08:15 +08:00
Huge_Black
381e09d87c bug-fix: Fix crash when installing apps 2026-02-24 16:08:15 +08:00
mahee96
efbb40982e ci: fix indentation in release notes 2026-02-24 13:20:14 +05:30
mahee96
c1a033a627 staging: added some redundant files to gitignore 2026-02-24 13:14:40 +05:30
mahee96
1449f8c74f staging: prepare new branch for alpha release channels and high velocity development 2026-02-24 13:04:44 +05:30
mahee96
3961688b73 ci: more fixes 2026-02-24 12:42:55 +05:30
mahee96
f0da9cf8aa ci: more fixes 2026-02-24 12:25:12 +05:30
mahee96
aa224f68c7 ci: more fixes 2026-02-24 09:13:09 +05:30
mahee96
a02d1c49e8 ci: more fixes 2026-02-24 08:42:52 +05:30
mahee96
226f0dcc6b CI: improve more ci worflow 2026-02-24 08:19:56 +05:30
mahee96
bce38c8743 CI: improve more ci worflow 2026-02-24 07:41:06 +05:30
mahee96
0e72a33af8 CI: improve more ci worflow 2026-02-24 07:23:20 +05:30
mahee96
0677cc287e CI: improve more ci worflow 2026-02-24 07:21:14 +05:30
mahee96
b0bfbf5513 CI: improve more ci worflow 2026-02-24 07:12:56 +05:30
mahee96
ea86b98674 CI: improve more ci worflow 2026-02-24 07:00:17 +05:30
mahee96
3d47d486ef CI: improve more ci worflow 2026-02-24 06:22:03 +05:30
mahee96
3a05485c40 CI: improve more ci worflow 2026-02-24 05:47:38 +05:30
mahee96
31d07534d0 CI: improve more ci worflow 2026-02-24 05:27:15 +05:30
mahee96
99712f0020 CI: improve more ci worflow 2026-02-24 03:58:47 +05:30
mahee96
c5394be883 CI: improve more ci worflow 2026-02-24 03:53:26 +05:30
mahee96
a07657261d CI: improve more ci worflow 2026-02-24 03:47:15 +05:30
mahee96
db00202b37 CI: improve more ci worflow 2026-02-24 03:33:26 +05:30
mahee96
b16dda5590 CI: improve more ci worflow 2026-02-24 03:28:46 +05:30
mahee96
f8c4c558f6 CI: improve more ci worflow 2026-02-24 03:21:19 +05:30
mahee96
ae1bd49a99 CI: improve more ci worflow 2026-02-24 03:16:26 +05:30
mahee96
97b04094eb CI: improve more ci worflow 2026-02-24 03:13:55 +05:30
mahee96
675bdc63ae CI: improve more ci worflow 2026-02-24 02:59:08 +05:30
mahee96
8be9de3b11 CI: improve more ci worflow 2026-02-24 02:53:51 +05:30
mahee96
0403dc3278 CI: improve more ci worflow 2026-02-24 02:43:02 +05:30
mahee96
c546ff6642 CI: improve more ci worflow 2026-02-24 02:40:34 +05:30
mahee96
dc058938ef altsign updated to latest 2026-02-24 02:40:30 +05:30
mahee96
4984e5119f CI: improve more ci worflow 2026-02-24 02:29:13 +05:30
mahee96
bcadc92057 CI: improve more ci worflow 2026-02-24 02:25:50 +05:30
mahee96
cb93168516 CI: improve more ci worflow 2026-02-24 02:22:45 +05:30
mahee96
4a688f20fc CI: improve more ci worflow 2026-02-24 02:20:10 +05:30
mahee96
d37ba80ca1 CI: improve more ci worflow 2026-02-24 02:14:13 +05:30
mahee96
a3190fb016 re added openSSL from new path 2026-02-24 02:14:10 +05:30
mahee96
aec39c0ccd updated altsign to use xcframework for openSSL which was causing huge download of 1.2 GB each time 2026-02-24 02:13:48 +05:30
mahee96
c82662a72b CI: improve more ci worflow 2026-02-24 01:54:41 +05:30
mahee96
a6315442fb CI: improve more ci worflow 2026-02-24 01:54:05 +05:30
mahee96
97d8da1eab CI: improve more ci worflow 2026-02-24 01:51:35 +05:30
mahee96
4975ea4fcd CI: improve more ci worflow 2026-02-24 01:41:36 +05:30
mahee96
88f7122d8d CI: improve more ci worflow 2026-02-24 01:36:33 +05:30
mahee96
50482a9dc4 CI: improve more ci worflow 2026-02-24 01:26:13 +05:30
mahee96
af04388aea CI: improve more ci worflow 2026-02-24 01:20:37 +05:30
mahee96
7553e25bbf CI: improve more ci worflow 2026-02-24 01:10:38 +05:30
mahee96
7c01ba0db6 CI: improve more ci worflow 2026-02-24 00:59:05 +05:30
mahee96
e55351dbb0 CI: improve more ci worflow 2026-02-24 00:41:33 +05:30
mahee96
97ee0b2dac CI: full rewrite - moved logic into ci.py and kept workflow scripts mostly dummy 2026-02-23 20:21:59 +05:30
mahee96
8fc38300d5 try adding libfragmentzip again with explicit dependency 2026-02-23 16:26:56 +05:30
mahee96
5460fdfec5 try adding libfragmentzip again with explicit dependency 2026-02-23 14:42:37 +05:30
mahee96
ded65e30d6 dependencies: fix few more issues with project reference 2026-02-23 14:28:53 +05:30
mahee96
b0fab7550f dependencies: fix few more issues with project reference 2026-02-23 12:51:36 +05:30
mahee96
10154954f0 dependencies: covert most back to folders except few that are required by build for reference 2026-02-23 12:45:24 +05:30
mahee96
59892247a6 dependencies: fix missing header by adding to output list 2026-02-23 12:30:19 +05:30
mahee96
f350c9fa49 dependencies: use group format for folders to see if that fixes chaining issue 2026-02-23 12:24:07 +05:30
mahee96
32fb3d706c dependencies: use group format for folders to see if that fixes chaining issue 2026-02-23 12:12:51 +05:30
mahee96
232dec2c36 wrappers: keep imports them private to file scope for encapsulation 2026-02-23 11:53:17 +05:30
mahee96
e9772afbf6 minimuxer: fix some wrapper methods 2026-02-23 11:45:15 +05:30
mahee96
014d7b8346 build: fix some more path issues 2026-02-23 11:43:46 +05:30
mahee96
8d90def5d8 minimuxer: fix some wrapper methods 2026-02-23 11:43:23 +05:30
mahee96
e891b7ec93 minimuxer: use wrapper methods 2026-02-23 11:30:11 +05:30
mahee96
e5dbf2b677 schemes: do not auto create 2026-02-23 11:29:47 +05:30
mahee96
4ff35e36e4 fix: attempt to fix the dependency chaining issue 2026-02-23 10:29:02 +05:30
mahee96
3927cbd438 cleanup: removed more stuffs - datastructure tests and ui tests which we don't actively use anyways...they can still be used by running manually if required. 2026-02-23 02:12:33 +05:30
mahee96
23d5123d07 libcurl: added back as dependency 2026-02-23 00:55:08 +05:30
mahee96
f24b728223 xcschemes: disabled unecessary ones 2026-02-22 23:47:00 +05:30
mahee96
d93df1ce9c bug-fix: Cargo project build in em_proxy, minimuxer weren't declaring the artifacts as outputs causing them to be missed being generated during manual clean and re-run 2026-02-22 23:46:21 +05:30
mahee96
016ef38c92 cleanup: removed more unused commented out code 2026-02-22 22:58:11 +05:30
mahee96
267e7bd688 submodule: updated to latest 2026-02-22 21:46:46 +05:30
mahee96
42091a98be submodule: updated to latest 2026-02-22 21:29:05 +05:30
mahee96
6d4cbf1c59 refactor: streamline the repo again and removed unused files from altstore 2026-02-22 21:27:31 +05:30
mahee96
747a9c7b5a debloat: converted xcode groups to xcode folder + removed unused files 2026-02-22 16:17:01 +05:30
mahee96
eed895f145 debloat: removed more patreon stuff carried over from altstore 2.0 2026-02-22 15:38:23 +05:30
mahee96
8fe551d849 cleanup: removed more unused commented out code 2026-02-22 15:19:00 +05:30
mahee96
d670618cbe debloat: removed more patreon stuff carried over from altstore 2.0 2026-02-22 15:18:51 +05:30
mahee96
f16651040f cleanup: removed more unused commented out code 2026-02-22 15:01:18 +05:30
mahee96
e028b768f4 debloat: removed more patreon stuff carried over from altstore 2.0 2026-02-22 15:00:25 +05:30
mahee96
a6be43da53 debloat: remove patreon stuff carried over from altstore 2.0...not required by sidestore in-app since sidestore manages in web + remove old tests from altstore 2026-02-22 14:54:01 +05:30
mahee96
c807154873 cleanup: removed analytics stuff - we can add a decent analytics management later if we need 2026-02-22 13:57:43 +05:30
mahee96
1103a7f0b4 bug-fix: should be checking for iconName and not imageName anymore 2026-02-22 11:27:05 +05:30
mahee96
8a63725113 icons: use 256x256 for preview imageset since 75x75 was too poor 2026-02-22 11:19:50 +05:30
mahee96
4a73efe680 icons: fix incorrect name entry 2026-02-22 11:01:15 +05:30
mahee96
be4d62fb1e bug-fix: emp for wireguard requires an enum entry for the indexPath calc 2026-02-22 10:59:04 +05:30
mahee96
7683447eea icons: change app icon screen was not showing icon previews - added tiny 75x75 imageset assets since iOS 18+ cannot use AppIconSet for UIImage(named:) 2026-02-22 10:58:32 +05:30
spidy123222
625389ab96 Add Exit Shortcut 2025-04-08 15:19:33 -07:00
spidy123222
f7e34cbbe9 Rewrite SendAppOperation execution to allow to wait for shortcut execution. 2025-04-08 15:19:33 -07:00
spidy123222
0fe8d7fed9 Move to SendAppOperation 2025-04-08 15:19:33 -07:00
spidy123222
1a1aa42e02 move it behind pendiungunitcount 60 2025-04-08 15:19:33 -07:00
spidy123222
7ff4b48223 Move attempt to a higher Stage. 2025-04-08 15:19:33 -07:00
spidy123222
4801f6e8f2 Attempt a million 2025-04-08 15:19:33 -07:00
Spidy123222
ff28f6fa8f Add files via upload
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2025-04-08 15:19:33 -07:00
Spidy123222
2d141afbaf remove from install apps
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2025-04-08 15:19:33 -07:00
Spidy123222
06e38aae00 Hopefully fix problem
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2025-04-08 15:19:33 -07:00
Spidy123222
d8783230a7 fix error for open link
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2025-04-08 15:19:33 -07:00
Spidy123222
6c479bfede test open URL
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2025-04-08 15:19:33 -07:00
197 changed files with 4198 additions and 10961 deletions

View File

@@ -1,63 +0,0 @@
import requests
import sys
import os
# Your GitHub Personal Access Token
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
# Repository details
REPO_OWNER = "SideStore"
REPO_NAME = "SideStore"
API_URL = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/actions/caches"
# Common headers for GitHub API calls
HEADERS = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {GITHUB_TOKEN}"
}
def list_caches():
response = requests.get(API_URL, headers=HEADERS)
if response.status_code != 200:
print(f"Failed to list caches. HTTP {response.status_code}")
print("Response:", response.text)
sys.exit(1)
data = response.json()
return data.get("actions_caches", [])
def delete_cache(cache_id):
delete_url = f"{API_URL}/{cache_id}"
response = requests.delete(delete_url, headers=HEADERS)
return response.status_code
def main():
caches = list_caches()
if not caches:
print("No caches found.")
return
print("Found caches:")
for cache in caches:
print(f"ID: {cache.get('id')}, Key: {cache.get('key')}")
print("\nDeleting caches...")
for cache in caches:
cache_id = cache.get("id")
status = delete_cache(cache_id)
if status == 204:
print(f"Successfully deleted cache with ID: {cache_id}")
else:
print(f"Failed to delete cache with ID: {cache_id}. HTTP status code: {status}")
print("All caches processed.")
if __name__ == "__main__":
main()
### How to use
'''
just export the GITHUB_TOKEN and then run this script via `python3 cache.py' to delete the caches
'''

View File

@@ -1,28 +1,220 @@
name: Alpha SideStore build
name: Alpha SideStore Build
on:
push:
branches:
- develop-alpha
branches: [staging]
workflow_dispatch:
# cancel duplicate run if from same branch
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
Reusable-build:
uses: ./.github/workflows/reusable-sidestore-build.yml
with:
# bundle_id: "com.SideStore.SideStore.Alpha"
bundle_id: "com.SideStore.SideStore"
# bundle_id_suffix: ".Alpha"
is_beta: true
publish: ${{ vars.PUBLISH_ALPHA_UPDATES == 'true' }}
is_shared_build_num: false
release_tag: "alpha"
release_name: "Alpha"
upstream_tag: "nightly"
upstream_name: "Nightly"
secrets:
CROSS_REPO_PUSH_KEY: ${{ secrets.CROSS_REPO_PUSH_KEY }}
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
build:
runs-on: macos-26
env:
DEPLOY_KEY: ${{ secrets.CROSS_REPO_PUSH_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NAME: Alpha
CHANNEL: alpha
UPSTREAM_CHANNEL: "nightly"
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Find Last Successful commit
run: |
LAST_SUCCESSFUL_COMMIT=$(python3 scripts/ci/workflow.py last-successful-commit \
"false" "${{ env.CHANNEL }}" || echo "")
echo "LAST_SUCCESSFUL_COMMIT=$LAST_SUCCESSFUL_COMMIT" | tee -a $GITHUB_ENV
- run: brew install ldid xcbeautify
# --------------------------------------------------
# runtime env setup
# --------------------------------------------------
- name: Setup Env
run: |
BUILD_NUM="${{ github.run_number }}"
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commit-id)
NORMALIZED_VERSION=$(python3 scripts/ci/workflow.py compute-normalized \
"$MARKETING_VERSION" \
"$BUILD_NUM" \
"$SHORT_COMMIT")
python3 scripts/ci/workflow.py set-marketing-version "$NORMALIZED_VERSION"
echo "BUILD_NUM=$BUILD_NUM" | tee -a $GITHUB_ENV
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: "26.2"
- name: Restore Cache (exact)
id: xcode-cache-exact
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Restore Cache (last)
if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-
# --------------------------------------------------
# build and test
# --------------------------------------------------
- name: Clean
if: contains(github.event.head_commit.message, '[--clean-build]')
run: |
python3 scripts/ci/workflow.py clean
python3 scripts/ci/workflow.py clean-derived-data
python3 scripts/ci/workflow.py clean-spm-cache
- name: Boot simulator (async)
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
run: |
mkdir -p build/logs
python3 scripts/ci/workflow.py boot-sim-async "iPhone 17 Pro"
- name: Build
id: build
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-build
echo "encrypted=true" >> $GITHUB_OUTPUT
exit $STATUS
- name: Tests Build
id: test-build
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1'
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py tests-build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-tests-build
exit $STATUS
- name: Save Cache
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Tests Run
id: test-run
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py tests-run "iPhone 17 Pro"; STATUS=$?
python3 scripts/ci/workflow.py encrypt-tests-run
exit $STATUS
# --------------------------------------------------
# artifacts
# --------------------------------------------------
- uses: actions/upload-artifact@v4
with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip
- uses: actions/upload-artifact@v4
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1'
with:
name: tests-build-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-build-logs.zip
- uses: actions/upload-artifact@v4
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
with:
name: tests-run-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-run-logs.zip
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore.ipa
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip
- uses: actions/checkout@v4
if: env.DEPLOY_KEY != ''
with:
repository: "SideStore/apps-v2.json"
ref: "main"
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
path: "SideStore/apps-v2.json"
- name: Generate Metadata
run: |
python3 scripts/ci/workflow.py dump-project-settings
PRODUCT_NAME=$(python3 scripts/ci/workflow.py read-product-name)
BUNDLE_ID=$(python3 scripts/ci/workflow.py read-bundle-id)
IPA_NAME="$PRODUCT_NAME.ipa"
python3 scripts/ci/workflow.py generate-metadata \
"$CHANNEL" \
"$SHORT_COMMIT" \
"$MARKETING_VERSION" \
"$CHANNEL" \
"$BUNDLE_ID" \
"$IPA_NAME" \
"$LAST_SUCCESSFUL_COMMIT"
- name: Deploy
if: env.DEPLOY_KEY != ''
run: |
SOURCE_JSON="_includes/source.json"
python3 scripts/ci/workflow.py deploy \
SideStore/apps-v2.json \
"$SOURCE_JSON" \
"$CHANNEL" \
"$MARKETING_VERSION"
# --------------------------------------------------
# upload release to GH
# --------------------------------------------------
- name: Upload Release
run: |
python3 scripts/ci/workflow.py upload-release \
"$RELEASE_NAME" \
"$CHANNEL" \
"$GITHUB_SHA" \
"$GITHUB_REPOSITORY" \
"$UPSTREAM_CHANNEL"

View File

@@ -1,103 +0,0 @@
name: Beta SideStore build
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' # example: 1.0.0-beta.1
jobs:
build:
name: Build and upload SideStore Beta
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-14'
version: '15.4'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: brew install ldid
- name: Change version to tag
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: ${{ matrix.version }}
- name: Cache Build
uses: irgaly/xcode-cache@v1
with:
key: xcode-cache-deriveddata-${{ github.sha }}
restore-keys: xcode-cache-deriveddata
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
- name: Fakesign app
run: make fakesign
- name: Convert to IPA
run: make ipa
- name: Get current date
id: date
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
- name: Get current date in AltStore date form
id: date_altstore
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Upload to new beta release
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.version.outputs.version }}
tag_name: ${{ github.ref_name }}
draft: true
prerelease: true
files: SideStore.ipa
body: |
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
Beta builds are hand-picked builds from development commits that will allow you to try out new features earlier than normal. However, **they might contain bugs and other issues. Use at your own risk!**
## Changelog
- TODO
## Build Info
Built at (UTC): `${{ steps.date.outputs.date }}`
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
Commit SHA: `${{ github.sha }}`
Version: `${{ steps.version.outputs.version }}`
- name: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./*.dSYM/

View File

@@ -1,34 +0,0 @@
#!/usr/bin/env bash
# Ensure we are in root directory
cd "$(dirname "$0")/../.."
DATE=`date -u +'%Y.%m.%d'`
BUILD_NUM=1
# Use RELEASE_CHANNEL from the environment variable or default to "beta"
RELEASE_CHANNEL=${RELEASE_CHANNEL:-"beta"}
write() {
sed -e "/MARKETING_VERSION = .*/s/$/-$RELEASE_CHANNEL.$DATE.$BUILD_NUM+$(git rev-parse --short HEAD)/" -i '' Build.xcconfig
echo "$DATE,$BUILD_NUM" > build_number.txt
}
if [ ! -f "build_number.txt" ]; then
write
exit 0
fi
LAST_DATE=`cat build_number.txt | perl -n -e '/([^,]*),([^ ]*)$/ && print $1'`
LAST_BUILD_NUM=`cat build_number.txt | perl -n -e '/([^,]*),([^ ]*)$/ && print $2'`
# if [[ "$DATE" != "$LAST_DATE" ]]; then
# write
# else
# BUILD_NUM=`expr $LAST_BUILD_NUM + 1`
# write
# fi
# Build number is always incremental
BUILD_NUM=`expr $LAST_BUILD_NUM + 1`
write

View File

@@ -1,82 +1,260 @@
name: Nightly SideStore Build
on:
push:
branches:
- develop
branches: [develop]
schedule:
- cron: '0 0 * * *' # Runs every night at midnight UTC
workflow_dispatch: # Allows manual trigger
- cron: "0 0 * * *"
workflow_dispatch:
# cancel duplicate run if from same branch
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
check-changes:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
outputs:
has_changes: ${{ steps.check.outputs.has_changes }}
build:
runs-on: macos-26
env:
DEPLOY_KEY: ${{ secrets.CROSS_REPO_PUSH_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NAME: Nightly
CHANNEL: nightly
UPSTREAM_CHANNEL: ""
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Ensure full history
submodules: recursive
fetch-depth: 0
- name: Get last successful workflow run
id: get_last_success
- name: Find Last Successful commit
run: |
LAST_SUCCESS=$(gh run list --workflow "Nightly SideStore Build" --json createdAt,conclusion \
--jq '[.[] | select(.conclusion=="success")][0].createdAt' || echo "")
echo "Last successful run: $LAST_SUCCESS"
echo "last_success=$LAST_SUCCESS" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LAST_SUCCESSFUL_COMMIT=$(python3 scripts/ci/workflow.py last-successful-commit \
"false" "${{ env.CHANNEL }}" || echo "")
echo "LAST_SUCCESSFUL_COMMIT=$LAST_SUCCESSFUL_COMMIT" | tee -a $GITHUB_ENV
- name: Check for new commits since last successful build
id: check
- name: Check for new changes (on schedule)
id: check_changes
if: github.event_name == 'schedule'
run: |
if [ -n "$LAST_SUCCESS" ]; then
NEW_COMMITS=$(git rev-list --count --since="$LAST_SUCCESS" origin/develop)
COMMIT_LOG=$(git log --since="$LAST_SUCCESS" --pretty=format:"%h %s" origin/develop)
else
NEW_COMMITS=1
COMMIT_LOG=$(git log -n 10 --pretty=format:"%h %s" origin/develop) # Show last 10 commits if no history
fi
NEW_COMMITS=$(python3 scripts/ci/workflow.py count-new-commits "$LAST_SUCCESSFUL_COMMIT")
SHOULD_BUILD=$([ "${NEW_COMMITS:-0}" -ge 1 ] && echo true || echo false)
echo "should_build=$SHOULD_BUILD" >> $GITHUB_OUTPUT
echo "NEW_COMMITS=$NEW_COMMITS" | tee -a $GITHUB_ENV
echo "Has changes: $NEW_COMMITS"
echo "New commits since last successful build:"
echo "$COMMIT_LOG"
- name: Should Skip Building (on schedule)
id: build_gate
run: |
SHOULD_SKIP=$(
{ [ "${{ github.event_name }}" = "schedule" ] && \
[ "${{ steps.check_changes.outputs.should_build }}" != "true" ]; \
} && echo true || echo false
)
echo "should_skip=$SHOULD_SKIP" >> $GITHUB_OUTPUT
if [ "$NEW_COMMITS" -gt 0 ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
- run: brew install ldid xcbeautify
if: steps.build_gate.outputs.should_skip != 'true'
# --------------------------------------------------
# runtime env setup
# --------------------------------------------------
- name: Setup Env
if: steps.build_gate.outputs.should_skip != 'true'
run: |
BUILD_NUM="${{ github.run_number }}"
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commit-id)
NORMALIZED_VERSION=$(python3 scripts/ci/workflow.py compute-normalized \
"$MARKETING_VERSION" \
"$BUILD_NUM" \
"$SHORT_COMMIT")
python3 scripts/ci/workflow.py set-marketing-version "$NORMALIZED_VERSION"
echo "BUILD_NUM=$BUILD_NUM" | tee -a $GITHUB_ENV
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
- name: Setup Xcode
if: steps.build_gate.outputs.should_skip != 'true'
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: "26.2"
- name: Restore Cache (exact)
if: steps.build_gate.outputs.should_skip != 'true'
id: xcode-cache-exact
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Restore Cache (last)
if: >
steps.build_gate.outputs.should_skip != 'true' &&
steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-
# --------------------------------------------------
# build and test
# --------------------------------------------------
- name: Clean
if: steps.build_gate.outputs.should_skip != 'true' && contains(github.event.head_commit.message, '[--clean-build]')
run: |
python3 scripts/ci/workflow.py clean
python3 scripts/ci/workflow.py clean-derived-data
python3 scripts/ci/workflow.py clean-spm-cache
- name: Boot simulator (async)
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
run: |
mkdir -p build/logs
python3 scripts/ci/workflow.py boot-sim-async "iPhone 17 Pro"
- name: Build
if: steps.build_gate.outputs.should_skip != 'true'
id: build
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LAST_SUCCESS: ${{ env.last_success }}
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-build
echo "encrypted=true" >> $GITHUB_OUTPUT
exit $STATUS
Reusable-build:
if: |
always() &&
(github.event_name == 'push' ||
(github.event_name == 'schedule' && needs.check-changes.result == 'success' && needs.check-changes.outputs.has_changes == 'true'))
needs: check-changes
uses: ./.github/workflows/reusable-sidestore-build.yml
with:
# bundle_id: "com.SideStore.SideStore.Nightly"
bundle_id: "com.SideStore.SideStore"
# bundle_id_suffix: ".Nightly"
is_beta: true
publish: ${{ vars.PUBLISH_NIGHTLY_UPDATES == 'true' }}
is_shared_build_num: false
release_tag: "nightly"
release_name: "Nightly"
upstream_tag: "0.5.10"
upstream_name: "Stable"
secrets:
CROSS_REPO_PUSH_KEY: ${{ secrets.CROSS_REPO_PUSH_KEY }}
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
- name: Tests Build
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1'
id: test-build
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py tests-build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-tests-build
exit $STATUS
- name: Save Cache
if: >
steps.build_gate.outputs.should_skip != 'true' &&
steps.xcode-cache-fallback.outputs.cache-hit != 'true'
uses: actions/cache/save@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Tests Run
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
id: test-run
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
python3 scripts/ci/workflow.py tests-run "iPhone 17 Pro"; STATUS=$?
python3 scripts/ci/workflow.py encrypt-tests-run
exit $STATUS
# --------------------------------------------------
# artifacts
# --------------------------------------------------
- uses: actions/upload-artifact@v4
if: steps.build_gate.outputs.should_skip != 'true'
with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip
- uses: actions/upload-artifact@v4
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1'
with:
name: tests-build-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-build-logs.zip
- uses: actions/upload-artifact@v4
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
with:
name: tests-run-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-run-logs.zip
- uses: actions/upload-artifact@v4
if: steps.build_gate.outputs.should_skip != 'true'
with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore.ipa
- uses: actions/upload-artifact@v4
if: steps.build_gate.outputs.should_skip != 'true'
with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip
- uses: actions/checkout@v4
if: steps.build_gate.outputs.should_skip != 'true' && env.DEPLOY_KEY != ''
with:
repository: "SideStore/apps-v2.json"
ref: "main"
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
path: "SideStore/apps-v2.json"
- name: Generate Metadata
if: steps.build_gate.outputs.should_skip != 'true'
run: |
python3 scripts/ci/workflow.py dump-project-settings
PRODUCT_NAME=$(python3 scripts/ci/workflow.py read-product-name)
BUNDLE_ID=$(python3 scripts/ci/workflow.py read-bundle-id)
IPA_NAME="$PRODUCT_NAME.ipa"
python3 scripts/ci/workflow.py generate-metadata \
"$CHANNEL" \
"$SHORT_COMMIT" \
"$MARKETING_VERSION" \
"$CHANNEL" \
"$BUNDLE_ID" \
"$IPA_NAME" \
"$LAST_SUCCESSFUL_COMMIT"
- name: Deploy
if: steps.build_gate.outputs.should_skip != 'true' && env.DEPLOY_KEY != ''
run: |
SOURCE_JSON="_includes/source.json"
python3 scripts/ci/workflow.py deploy \
SideStore/apps-v2.json \
"$SOURCE_JSON" \
"$CHANNEL" \
"$MARKETING_VERSION"
# --------------------------------------------------
# upload release to GH
# --------------------------------------------------
- name: Upload Release
if: steps.build_gate.outputs.should_skip != 'true'
run: |
python3 scripts/ci/workflow.py upload-release \
"$RELEASE_NAME" \
"$CHANNEL" \
"$GITHUB_SHA" \
"$GITHUB_REPOSITORY" \
"$UPSTREAM_CHANNEL"

View File

@@ -1,98 +1,90 @@
name: Pull Request SideStore build
on:
pull_request:
# types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
types: [opened, synchronize, reopened, ready_for_review]
concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
build:
name: Build and upload SideStore
if: ${{ github.event.pull_request.draft == false }}
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-14'
version: '16.1'
runs-on: macos-26
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 1 # shallow clone just for PR
- name: Install dependencies
run: brew install ldid
- run: brew install ldid xcbeautify
- name: Install xcbeautify
run: brew install xcbeautify
- name: Add PR suffix to version
run: sed -e "/MARKETING_VERSION = .*/s/\$/-pr.${{ github.event.pull_request.number }}+$(git rev-parse --short ${COMMIT:-HEAD})/" -i '' Build.xcconfig
env:
COMMIT: ${{ github.event.pull_request.head.sha }}
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Env
run: |
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(git rev-parse --short ${{ github.event.pull_request.head.sha }})
NORMALIZED_VERSION="${MARKETING_VERSION}-pr.${{ github.event.pull_request.number }}+${SHORT_COMMIT}"
python3 scripts/ci/workflow.py set-marketing-version "$NORMALIZED_VERSION"
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: ${{ matrix.version }}
xcode-version: "26.2"
- name: Cache Build
uses: irgaly/xcode-cache@v1
- name: Restore Cache (exact)
id: xcode-cache-exact
uses: actions/cache/restore@v3
with:
key: xcode-cache-deriveddata-${{ github.sha }}
restore-keys: xcode-cache-deriveddata-
swiftpm-cache-key: xcode-cache-sourcedata-${{ github.sha }}
swiftpm-cache-restore-keys: |
xcode-cache-sourcedata-
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: List Files and derived data
- name: Restore Cache (last)
if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-
- name: Build
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
ls -la .
echo ""
python3 scripts/ci/workflow.py build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-build
mv SideStore.ipa SideStore-${{ env.MARKETING_VERSION }}.ipa
exit $STATUS
echo ">>>>>>>>> SideStore <<<<<<<<<<"
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Dependencies <<<<<<<<<<"
find Dependencies -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
echo ""
- name: Build SideStore
run: NSUnbufferedIO=YES make build 2>&1 | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
- name: Fakesign app
run: make fakesign
- name: Convert to IPA
run: make ipa
- name: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v4
- name: Save Cache
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./SideStore.xcarchive/dSYMs/*
name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore-${{ env.MARKETING_VERSION }}.ipa
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip

View File

@@ -1,105 +0,0 @@
name: Reusable SideStore Build
on:
workflow_call:
inputs:
is_beta:
required: false
default: false
type: boolean
publish:
required: false
default: false
type: boolean
is_shared_build_num:
required: false
default: true
type: boolean
release_name:
required: true
type: string
release_tag:
required: true
type: string
upstream_tag:
required: true
type: string
upstream_name:
required: true
type: string
bundle_id:
default: com.SideStore.SideStore
required: true
type: string
bundle_id_suffix:
default: ''
required: false
type: string
secrets:
# GITHUB_TOKEN:
# required: true
CROSS_REPO_PUSH_KEY:
required: true
BUILD_LOG_ZIP_PASSWORD:
required: false
# since build cache, test-build cache, test-run cache are involved, out of order exec if serialization is on individual jobs will wreak all sorts of havoc
# so we serialize on the entire workflow
concurrency:
group: serialize-workflow
jobs:
shared:
uses: ./.github/workflows/sidestore-shared.yml
secrets: inherit
build:
needs: shared
uses: ./.github/workflows/sidestore-build.yml
with:
is_beta: ${{ inputs.is_beta }}
is_shared_build_num: ${{ inputs.is_shared_build_num }}
release_tag: ${{ inputs.release_tag }}
short_commit: ${{ needs.shared.outputs.short-commit }}
bundle_id: ${{ inputs.bundle_id }}
bundle_id_suffix: ${{ inputs.bundle_id_suffix }}
secrets: inherit
# tests-build:
# if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
# needs: shared
# uses: ./.github/workflows/sidestore-tests-build.yml
# with:
# release_tag: ${{ inputs.release_tag }}
# short_commit: ${{ needs.shared.outputs.short-commit }}
# secrets: inherit
# tests-run:
# if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
# needs: [shared, tests-build]
# uses: ./.github/workflows/sidestore-tests-run.yml
# with:
# release_tag: ${{ inputs.release_tag }}
# short_commit: ${{ needs.shared.outputs.short-commit }}
# secrets: inherit
deploy:
# needs: [shared, build, tests-build, tests-run] # Keep tests-run in needs
needs: [shared, build] # Keep tests-run in needs
if: ${{ always() && (needs.tests-run.result == 'skipped' || needs.tests-run.result == 'success') }}
uses: ./.github/workflows/sidestore-deploy.yml
with:
is_beta: ${{ inputs.is_beta }}
publish: ${{ inputs.publish }}
release_name: ${{ inputs.release_name }}
release_tag: ${{ inputs.release_tag }}
upstream_tag: ${{ inputs.upstream_tag }}
upstream_name: ${{ inputs.upstream_name }}
version: ${{ needs.build.outputs.version }}
short_commit: ${{ needs.shared.outputs.short-commit }}
release_channel: ${{ needs.build.outputs.release-channel }}
marketing_version: ${{ needs.build.outputs.marketing-version }}
bundle_id: ${{ inputs.bundle_id }}
secrets: inherit

View File

@@ -1,358 +0,0 @@
name: SideStore Build
on:
workflow_call:
inputs:
is_beta:
type: boolean
is_shared_build_num:
type: boolean
release_tag:
type: string
bundle_id:
type: string
bundle_id_suffix:
type: string
short_commit:
type: string
secrets:
CROSS_REPO_PUSH_KEY:
required: true
BUILD_LOG_ZIP_PASSWORD:
required: false
outputs:
version:
value: ${{ jobs.build.outputs.version }}
marketing-version:
value: ${{ jobs.build.outputs.marketing-version }}
release-channel:
value: ${{ jobs.build.outputs.release-channel }}
jobs:
build:
name: Build SideStore - ${{ inputs.release_tag }}
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-26'
version: '26.0'
runs-on: ${{ matrix.os }}
outputs:
version: ${{ steps.version.outputs.version }}
marketing-version: ${{ steps.marketing-version.outputs.MARKETING_VERSION }}
release-channel: ${{ steps.release-channel.outputs.RELEASE_CHANNEL }}
steps:
- name: Set beta status
run: echo "IS_BETA=${{ inputs.is_beta }}" >> $GITHUB_ENV
shell: bash
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Install dependencies - ldid & xcbeautify
run: |
brew install ldid xcbeautify
- name: Set ref based on is_shared_build_num
if: ${{ inputs.is_beta }}
id: set_ref
run: |
if [ "${{ inputs.is_shared_build_num }}" == "true" ]; then
echo "ref=main" >> $GITHUB_ENV
else
echo "ref=${{ inputs.release_tag }}" >> $GITHUB_ENV
fi
shell: bash
- name: Checkout SideStore/beta-build-num repo
if: ${{ inputs.is_beta }}
uses: actions/checkout@v4
with:
repository: 'SideStore/beta-build-num'
ref: ${{ env.ref }}
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
path: 'SideStore/beta-build-num'
- name: Copy build_number.txt to repo root
if: ${{ inputs.is_beta }}
run: |
cp SideStore/beta-build-num/build_number.txt .
echo "cat build_number.txt"
cat build_number.txt
shell: bash
- name: Echo Build.xcconfig
run: |
echo "cat Build.xcconfig"
cat Build.xcconfig
shell: bash
- name: Set Release Channel info for build number bumper
id: release-channel
run: |
RELEASE_CHANNEL="${{ inputs.release_tag }}"
echo "RELEASE_CHANNEL=${RELEASE_CHANNEL}" >> $GITHUB_ENV
echo "RELEASE_CHANNEL=${RELEASE_CHANNEL}" >> $GITHUB_OUTPUT
echo "RELEASE_CHANNEL=${RELEASE_CHANNEL}"
shell: bash
- name: Increase build number for beta builds
if: ${{ inputs.is_beta }}
run: |
bash .github/workflows/increase-beta-build-num.sh
shell: bash
- name: Extract MARKETING_VERSION from Build.xcconfig
id: version
run: |
version=$(grep MARKETING_VERSION Build.xcconfig | sed -e 's/MARKETING_VERSION = //g')
echo "version=$version" >> $GITHUB_OUTPUT
echo "version=$version"
shell: bash
- name: Set MARKETING_VERSION
if: ${{ inputs.is_beta }}
id: marketing-version
run: |
# Extract version number (e.g., "0.6.0")
version=$(echo "${{ steps.version.outputs.version }}" | sed -E 's/^[^0-9]*([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
# Extract date (YYYYMMDD) (e.g., "20250205")
date=$(echo "${{ steps.version.outputs.version }}" | sed -E 's/.*\.([0-9]{4})\.([0-9]{2})\.([0-9]{2})\..*/\1\2\3/')
# Extract build number (e.g., "2")
build_num=$(echo "${{ steps.version.outputs.version }}" | sed -E 's/.*\.([0-9]+)\+.*/\1/')
# Combine them into the final output
MARKETING_VERSION="${version}-${date}.${build_num}+${{ inputs.short_commit }}"
echo "MARKETING_VERSION=$MARKETING_VERSION" >> $GITHUB_ENV
echo "MARKETING_VERSION=$MARKETING_VERSION" >> $GITHUB_OUTPUT
echo "MARKETING_VERSION=$MARKETING_VERSION"
shell: bash
- name: Echo Updated Build.xcconfig, build_number.txt
if: ${{ inputs.is_beta }}
run: |
cat Build.xcconfig
cat build_number.txt
shell: bash
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: ${{ matrix.version }}
- name: (Build) Restore Xcode & SwiftPM Cache (Exact match)
id: xcode-cache-restore
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-cache-build-${{ github.ref_name }}-${{ github.sha }}
- name: (Build) Restore Xcode & SwiftPM Cache (Last Available)
id: xcode-cache-restore-recent
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-cache-build-${{ github.ref_name }}-
# - name: (Build) Cache Build
# uses: irgaly/xcode-cache@v1.8.1
# with:
# key: xcode-cache-deriveddata-build-${{ github.ref_name }}-${{ github.sha }}
# restore-keys: xcode-cache-deriveddata-build-${{ github.ref_name }}-
# swiftpm-cache-key: xcode-cache-sourcedata-build-${{ github.ref_name }}-${{ github.sha }}
# swiftpm-cache-restore-keys: |
# xcode-cache-sourcedata-build-${{ github.ref_name }}-
- name: (Build) Clean previous build artifacts
# using 'tee' to intercept stdout and log for detailed build-log
run: |
make clean
mkdir -p build/logs
shell: bash
- name: (Build) List Files and derived data
if: always()
shell: bash
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
ls -la .
echo ""
echo ">>>>>>>>> SideStore <<<<<<<<<<"
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Dependencies <<<<<<<<<<"
find Dependencies -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
echo ""
- name: Set BundleID Suffix for Sidestore build
run: |
echo "BUNDLE_ID_SUFFIX=${{ inputs.bundle_id_suffix }}" >> $GITHUB_ENV
shell: bash
- name: Build SideStore.xcarchive
# using 'tee' to intercept stdout and log for detailed build-log
run: |
NSUnbufferedIO=YES make -B build 2>&1 | tee -a build/logs/build.log | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
shell: bash
- name: Fakesign app
run: make fakesign | tee -a build/logs/build.log
shell: bash
- name: Convert to IPA
run: make ipa | tee -a build/logs/build.log
shell: bash
- name: (Build) Save Xcode & SwiftPM Cache
id: cache-save
if: ${{ steps.xcode-cache-restore.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-cache-build-${{ github.ref_name }}-${{ github.sha }}
- name: (Build) List Files and Build artifacts
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
ls -la .
echo ""
echo ">>>>>>>>> Build <<<<<<<<<<"
find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> SideStore <<<<<<<<<<"
find SideStore -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> SideStore.xcarchive <<<<<<<<<<"
find SideStore.xcarchive -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
echo ""
shell: bash
- name: Encrypt build-logs for upload
id: encrypt-build-log
run: |
DEFAULT_BUILD_LOG_PASSWORD=12345
BUILD_LOG_ZIP_PASSWORD=${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
BUILD_LOG_ZIP_PASSWORD=${BUILD_LOG_ZIP_PASSWORD:-$DEFAULT_BUILD_LOG_PASSWORD}
if [ "$BUILD_LOG_ZIP_PASSWORD" == "$DEFAULT_BUILD_LOG_PASSWORD" ]; then
echo "Warning: BUILD_LOG_ZIP_PASSWORD is not set. Defaulting to '${DEFAULT_BUILD_LOG_PASSWORD}'."
fi
pushd build/logs && zip -e -P "$BUILD_LOG_ZIP_PASSWORD" ../../encrypted-build-logs.zip * || popd
echo "::set-output name=encrypted::true"
shell: bash
- name: Upload encrypted-build-logs.zip
id: attach-encrypted-build-log
if: ${{ always() && steps.encrypt-build-log.outputs.encrypted == 'true' }}
uses: actions/upload-artifact@v4
with:
name: encrypted-build-logs-${{ steps.version.outputs.version }}.zip
path: encrypted-build-logs.zip
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore.ipa
- name: Zip dSYMs
run: zip -r -9 ./SideStore.dSYMs.zip ./SideStore.xcarchive/dSYMs
shell: bash
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYMs.zip
path: SideStore.dSYMs.zip
- name: Keep rolling the build numbers for each successful build
if: ${{ inputs.is_beta }}
run: |
pushd SideStore/beta-build-num/
echo "Configure Git user (committer details)"
git config user.name "GitHub Actions"
git config user.email "github-actions@github.com"
echo "Adding files to commit"
git add --verbose build_number.txt
git commit -m " - updated for ${{ inputs.release_tag }} - ${{ inputs.short_commit }} deployment" || echo "No changes to commit"
echo "Pushing to remote repo"
git push --verbose
popd
shell: bash
- name: Get last successful commit
id: get_last_commit
run: |
# Try to get the last successful workflow run commit
LAST_SUCCESS_SHA=$(gh run list --branch "${{ github.ref_name }}" --status success --json headSha --jq '.[0].headSha')
echo "LAST_SUCCESS_SHA=$LAST_SUCCESS_SHA" >> $GITHUB_OUTPUT
echo "LAST_SUCCESS_SHA=$LAST_SUCCESS_SHA" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
- name: Create release notes
run: |
LAST_SUCCESS_SHA=${{ steps.get_last_commit.outputs.LAST_SUCCESS_SHA}}
echo "Last successful commit SHA: $LAST_SUCCESS_SHA"
FROM_COMMIT=$LAST_SUCCESS_SHA
# Check if we got a valid SHA
if [ -z "$LAST_SUCCESS_SHA" ] || [ "$LAST_SUCCESS_SHA" = "null" ]; then
echo "No successful run found, using initial commit of branch"
# Get the first commit of the branch (initial commit)
FROM_COMMIT=$(git rev-list --max-parents=0 HEAD)
fi
python3 update_release_notes.py $FROM_COMMIT ${{ inputs.release_tag }} ${{ github.ref_name }}
# cat release-notes.md
shell: bash
- name: Upload release-notes.md
uses: actions/upload-artifact@v4
with:
name: release-notes-${{ inputs.short_commit }}.md
path: release-notes.md
- name: Upload update_release_notes.py
uses: actions/upload-artifact@v4
with:
name: update_release_notes-${{ inputs.short_commit }}.py
path: update_release_notes.py
- name: Upload update_apps.py
uses: actions/upload-artifact@v4
with:
name: update_apps-${{ inputs.short_commit }}.py
path: update_apps.py

View File

@@ -1,281 +0,0 @@
name: SideStore Deploy
on:
workflow_call:
inputs:
is_beta:
type: boolean
publish:
type: boolean
release_name:
type: string
release_tag:
type: string
upstream_tag:
type: string
upstream_name:
type: string
version:
type: string
short_commit:
type: string
marketing_version:
type: string
release_channel:
type: string
bundle_id:
type: string
secrets:
CROSS_REPO_PUSH_KEY:
required: true
# GITHUB_TOKEN:
# required: true
jobs:
deploy:
name: Deploy SideStore - ${{ inputs.release_tag }}
runs-on: macos-15
steps:
- name: Download IPA artifact
uses: actions/download-artifact@v4
with:
name: SideStore-${{ inputs.version }}.ipa
- name: Download dSYM artifact
uses: actions/download-artifact@v4
with:
name: SideStore-${{ inputs.version }}-dSYMs.zip
- name: Download encrypted-build-logs artifact
uses: actions/download-artifact@v4
with:
name: encrypted-build-logs-${{ inputs.version }}.zip
- name: Download encrypted-tests-build-logs artifact
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
uses: actions/download-artifact@v4
with:
name: encrypted-tests-build-logs-${{ inputs.short_commit }}.zip
- name: Download encrypted-tests-run-logs artifact
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
uses: actions/download-artifact@v4
with:
name: encrypted-tests-run-logs-${{ inputs.short_commit }}.zip
- name: Download tests-recording artifact
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
uses: actions/download-artifact@v4
with:
name: tests-recording-${{ inputs.short_commit }}.mp4
- name: Download test-results artifact
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
uses: actions/download-artifact@v4
with:
name: test-results-${{ inputs.short_commit }}.zip
- name: Download release-notes.md
uses: actions/download-artifact@v4
with:
name: release-notes-${{ inputs.short_commit }}.md
- name: Download update_release_notes.py
uses: actions/download-artifact@v4
with:
name: update_release_notes-${{ inputs.short_commit }}.py
- name: Download update_apps.py
uses: actions/download-artifact@v4
with:
name: update_apps-${{ inputs.short_commit }}.py
- name: Read release notes
id: release_notes
run: |
CONTENT=$(python3 update_release_notes.py --retrieve ${{ inputs.release_tag }})
echo "content<<EOF" >> $GITHUB_OUTPUT
echo "$CONTENT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
shell: bash
- name: List files before upload
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
find . -maxdepth 4 -exec ls -ld {} + || true # List contents if directory exists
echo ""
shell: bash
- name: Get current date
id: date
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
shell: bash
- name: Get current date in AltStore date form
id: date_altstore
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
shell: bash
- name: List files to upload
id: list_uploads
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
find . -maxdepth 4 -exec ls -ld {} + || true # List contents if directory exists
echo ""
FILES="SideStore.ipa SideStore.dSYMs.zip encrypted-build-logs.zip"
if [[ "${{ vars.ENABLE_TESTS }}" == "1" && "${{ vars.ENABLE_TESTS_BUILD }}" == "1" ]]; then
FILES="$FILES encrypted-tests-build-logs.zip"
fi
if [[ "${{ vars.ENABLE_TESTS }}" == "1" && "${{ vars.ENABLE_TESTS_RUN }}" == "1" ]]; then
FILES="$FILES encrypted-tests-run-logs.zip test-results.zip tests-recording.mp4"
fi
echo "Final upload list:"
for f in $FILES; do
if [[ -f "$f" ]]; then
echo " ✓ $f"
else
echo " - $f (missing)"
fi
done
echo "files=$FILES" >> $GITHUB_OUTPUT
- name: Set Upstream Recommendation
id: upstream_recommendation
run: |
UPSTREAM_NAME=$(echo "${{ inputs.upstream_name }}" | tr '[:upper:]' '[:lower:]')
if [[ "$UPSTREAM_NAME" != "nightly" ]]; then
echo "content<<EOF" >> $GITHUB_OUTPUT
echo "If you want to try out new features early but want a lower chance of bugs, you can look at [SideStore ${{ inputs.upstream_name }}](https://github.com/${{ github.repository }}/releases?q=${{ inputs.upstream_tag }})." >> $GITHUB_OUTPUT
echo "" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "content=" >> $GITHUB_OUTPUT
fi
shell: bash
- name: Upload to releases
uses: IsaacShelton/update-existing-release@v1.3.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
release: ${{ inputs.release_name }}
tag: ${{ inputs.release_tag }}
prerelease: ${{ inputs.is_beta }}
files: ${{ steps.list_uploads.outputs.files }}
body: |
This is an ⚠️ **EXPERIMENTAL** ⚠️ ${{ inputs.release_name }} build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
${{ inputs.release_name }} builds are **extremely experimental builds only meant to be used by developers and beta testers. They often contain bugs and experimental features. Use at your own risk!**
${{ steps.upstream_recommendation.outputs.content }}
## Build Info
Built at (UTC): `${{ steps.date.outputs.date }}`
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
Commit SHA: `${{ github.sha }}`
Version: `${{ inputs.version }}`
${{ steps.release_notes.outputs.content }}
- name: Get formatted date
run: |
FORMATTED_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "Formatted date: $FORMATTED_DATE"
echo "FORMATTED_DATE=$FORMATTED_DATE" >> $GITHUB_ENV
shell: bash
- name: Get size of IPA in bytes (macOS/Linux)
run: |
if [[ "$(uname)" == "Darwin" ]]; then
# macOS
IPA_SIZE=$(stat -f %z SideStore.ipa)
else
# Linux
IPA_SIZE=$(stat -c %s SideStore.ipa)
fi
echo "IPA size in bytes: $IPA_SIZE"
echo "IPA_SIZE=$IPA_SIZE" >> $GITHUB_ENV
shell: bash
- name: Compute SHA-256 of IPA
run: |
SHA256_HASH=$(shasum -a 256 SideStore.ipa | awk '{ print $1 }')
echo "SHA-256 Hash: $SHA256_HASH"
echo "SHA256_HASH=$SHA256_HASH" >> $GITHUB_ENV
shell: bash
- name: Set Release Info variables
run: |
echo "IS_BETA=${{ inputs.is_beta }}" >> $GITHUB_ENV
echo "BUNDLE_IDENTIFIER=${{ inputs.bundle_id }}" >> $GITHUB_ENV
echo "VERSION_IPA=${{ inputs.marketing_version }}" >> $GITHUB_ENV
echo "VERSION_DATE=$FORMATTED_DATE" >> $GITHUB_ENV
echo "RELEASE_CHANNEL=${{ inputs.release_channel }}" >> $GITHUB_ENV
echo "SIZE=$IPA_SIZE" >> $GITHUB_ENV
echo "SHA256=$SHA256_HASH" >> $GITHUB_ENV
echo "DOWNLOAD_URL=https://github.com/SideStore/SideStore/releases/download/${{ inputs.release_tag }}/SideStore.ipa" >> $GITHUB_ENV
# Format localized description
get_description() {
cat <<EOF
This is release for:
- version: "${{ inputs.version }}"
- revision: "${{ inputs.short_commit }}"
- timestamp: "${{ steps.date.outputs.date }}"
Release Notes:
${{ steps.release_notes.outputs.content }}
EOF
}
LOCALIZED_DESCRIPTION=$(get_description)
echo "$LOCALIZED_DESCRIPTION"
# multiline strings
echo "LOCALIZED_DESCRIPTION<<EOF" >> $GITHUB_ENV
echo "$LOCALIZED_DESCRIPTION" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
shell: bash
- name: Check if Publish updates is set
id: check_publish
run: |
echo "Publish updates to source.json = ${{ inputs.publish }}"
shell: bash
- name: Checkout SideStore/apps-v2.json
if: ${{ inputs.is_beta && inputs.publish }}
uses: actions/checkout@v4
with:
repository: 'SideStore/apps-v2.json'
ref: 'main' # this branch is shared by all beta builds, so beta build workflows are serialized
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
path: 'SideStore/apps-v2.json'
# for stable builds, let the user manually edit the source.json
- name: Publish to SideStore/apps-v2.json
if: ${{ inputs.is_beta && inputs.publish }}
id: publish-release
shell: bash
run: |
# Copy and execute the update script
pushd SideStore/apps-v2.json/
# Configure Git user (committer details)
git config user.name "GitHub Actions"
git config user.email "github-actions@github.com"
# update the source.json
python3 ../../update_apps.py "./_includes/source.json"
# Commit changes and push using SSH
git add --verbose ./_includes/source.json
git commit -m " - updated for ${{ inputs.short_commit }} deployment" || echo "No changes to commit"
git push --verbose
popd

View File

@@ -1,24 +0,0 @@
name: SideStore Shared
on:
workflow_call:
outputs:
short-commit:
value: ${{ jobs.shared.outputs.short-commit }}
jobs:
shared:
name: Shared Steps
strategy:
fail-fast: false
runs-on: 'macos-15'
steps:
- name: Set short commit hash
id: commit-id
run: |
# SHORT_COMMIT="${{ github.sha }}"
SHORT_COMMIT=${GITHUB_SHA:0:7}
echo "Short commit hash: $SHORT_COMMIT"
echo "SHORT_COMMIT=$SHORT_COMMIT" >> $GITHUB_OUTPUT
outputs:
short-commit: ${{ steps.commit-id.outputs.SHORT_COMMIT }}

View File

@@ -1,165 +0,0 @@
name: SideStore Tests Build
on:
workflow_call:
inputs:
release_tag:
type: string
short_commit:
type: string
secrets:
BUILD_LOG_ZIP_PASSWORD:
required: false
jobs:
tests-build:
name: Tests-Build SideStore - ${{ inputs.release_tag }}
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-26'
version: '26.0'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies - xcbeautify
run: |
brew install xcbeautify
shell: bash
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: '26.0'
# - name: (Tests-Build) Cache Build
# uses: irgaly/xcode-cache@v1.8.1
# with:
# key: xcode-cache-deriveddata-test-${{ github.ref_name }}-${{ github.sha }}
# # tests shouldn't restore cache unless it is same build
# # restore-keys: xcode-cache-deriveddata-test-${{ github.ref_name }}-
# swiftpm-cache-key: xcode-cache-sourcedata-test-${{ github.ref_name }}-${{ github.sha }}
# swiftpm-cache-restore-keys: |
# xcode-cache-sourcedata-test-${{ github.ref_name }}-
# delete-used-deriveddata-cache: true
- name: (Tests-Build) Restore Xcode & SwiftPM Cache (Exact match)
id: xcode-cache-restore
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }}
- name: (Tests-Build) Restore Xcode & SwiftPM Cache (Last Available)
id: xcode-cache-restore-recent
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-cache-tests-${{ github.ref_name }}-
- name: Clean Derived Data (if required)
if: ${{ vars.PERFORM_CLEAN_TESTS_BUILD == '1' }}
run: |
rm -rf ~/Library/Developer/Xcode/DerivedData/
make clean
xcodebuild clean
shell: bash
- name: (Tests-Build) Clean previous build artifacts
run: |
make clean
mkdir -p build/logs
shell: bash
- name: (Tests-Build) List Files and derived data
shell: bash
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
ls -la .
echo ""
echo ">>>>>>>>> SideStore <<<<<<<<<<"
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Dependencies <<<<<<<<<<"
find Dependencies -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
echo ""
- name: Build SideStore Tests
# using 'tee' to intercept stdout and log for detailed build-log
shell: bash
run: |
NSUnbufferedIO=YES make -B build-tests 2>&1 | tee -a build/logs/tests-build.log | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
- name: (Tests-Build) Save Xcode & SwiftPM Cache
id: cache-save
if: ${{ steps.xcode-cache-restore.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }}
- name: (Tests-Build) List Files and Build artifacts
if: always()
shell: bash
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
ls -la .
echo ""
echo ">>>>>>>>> Build <<<<<<<<<<"
find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
find ~/Library/Developer/Xcode/DerivedData -maxdepth 8 -exec ls -ld {} + | grep "Build/Products" >> tests-build-deriveddata.txt || true
echo ""
- uses: actions/upload-artifact@v4
if: always()
with:
name: tests-build-deriveddata-${{ inputs.short_commit }}.txt
path: tests-build-deriveddata.txt
- name: Encrypt tests-build-logs for upload
id: encrypt-test-log
if: always()
shell: bash
run: |
DEFAULT_BUILD_LOG_PASSWORD=12345
BUILD_LOG_ZIP_PASSWORD=${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
BUILD_LOG_ZIP_PASSWORD=${BUILD_LOG_ZIP_PASSWORD:-$DEFAULT_BUILD_LOG_PASSWORD}
if [ "$BUILD_LOG_ZIP_PASSWORD" == "$DEFAULT_BUILD_LOG_PASSWORD" ]; then
echo "Warning: BUILD_LOG_ZIP_PASSWORD is not set. Defaulting to '${DEFAULT_BUILD_LOG_PASSWORD}'."
fi
pushd build/logs && zip -e -P "$BUILD_LOG_ZIP_PASSWORD" ../../encrypted-tests-build-logs.zip * || popd
echo "::set-output name=encrypted::true"
- name: Upload encrypted-tests-build-logs.zip
id: attach-encrypted-test-log
if: always() && steps.encrypt-test-log.outputs.encrypted == 'true'
uses: actions/upload-artifact@v4
with:
name: encrypted-tests-build-logs-${{ inputs.short_commit }}.zip
path: encrypted-tests-build-logs.zip

View File

@@ -1,196 +0,0 @@
name: SideStore Tests Run
on:
workflow_call:
inputs:
release_tag:
type: string
short_commit:
type: string
secrets:
BUILD_LOG_ZIP_PASSWORD:
required: false
jobs:
tests-run:
name: Tests-Run SideStore - ${{ inputs.release_tag }}
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-26'
version: '26.0'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Boot Simulator async(nohup) for testing
run: |
mkdir -p build/logs
nohup make -B boot-sim-async </dev/null >> build/logs/tests-run.log 2>&1 &
shell: bash
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: '26.0'
# - name: (Tests-Run) Cache Build
# uses: irgaly/xcode-cache@v1.8.1
# with:
# # This comes from
# key: xcode-cache-deriveddata-test-${{ github.ref_name }}-${{ github.sha }}
# swiftpm-cache-key: xcode-cache-sourcedata-test-${{ github.ref_name }}-${{ github.sha }}
- name: (Tests-Build) Restore Xcode & SwiftPM Cache (Exact match) [from tests-build job]
id: xcode-cache-restore
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }}
- name: (Tests-Run) Clean previous build artifacts
run: |
make clean
mkdir -p build/logs
shell: bash
- name: (Tests-Run) List Files and derived data
shell: bash
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
ls -la .
echo ""
echo ">>>>>>>>> SideStore <<<<<<<<<<"
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Dependencies <<<<<<<<<<"
find Dependencies -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
find ~/Library/Developer/Xcode/DerivedData -maxdepth 8 -exec ls -ld {} + | grep "Build/Products" >> tests-run-deriveddata.txt || true
echo ""
- uses: actions/upload-artifact@v4
if: always()
with:
name: tests-run-deriveddata-${{ inputs.short_commit }}.txt
path: tests-run-deriveddata.txt
# we expect simulator to have been booted by now, so exit otherwise
- name: Simulator Boot Check
run: |
mkdir -p build/logs
make -B sim-boot-check | tee -a build/logs/tests-run.log
exit ${PIPESTATUS[0]}
shell: bash
- name: Start Recording UI tests (if DEBUG_RECORD_TESTS is set to 1)
if: ${{ vars.DEBUG_RECORD_TESTS == '1' }}
run: |
nohup xcrun simctl io booted recordVideo -f tests-recording.mp4 --codec h264 </dev/null > tests-recording.log 2>&1 &
RECORD_PID=$!
echo "RECORD_PID=$RECORD_PID" >> $GITHUB_ENV
shell: bash
- name: Run SideStore Tests
# using 'tee' to intercept stdout and log for detailed build-log
run: |
make run-tests 2>&1 | tee -a build/logs/tests-run.log && exit ${PIPESTATUS[0]}
# NSUnbufferedIO=YES make -B run-tests 2>&1 | tee build/logs/tests-run.log | xcpretty -r junit --output ./build/tests/test-results.xml && exit ${PIPESTATUS[0]}
shell: bash
- name: Stop Recording tests
if: ${{ always() && env.RECORD_PID != '' }}
run: |
kill -INT ${{ env.RECORD_PID }}
shell: bash
- name: (Tests-Run) List Files and Build artifacts
if: always()
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
ls -la .
echo ""
echo ">>>>>>>>> Build <<<<<<<<<<"
find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
echo ""
shell: bash
- name: Encrypt tests-run-logs for upload
id: encrypt-test-log
if: always()
run: |
DEFAULT_BUILD_LOG_PASSWORD=12345
BUILD_LOG_ZIP_PASSWORD=${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
BUILD_LOG_ZIP_PASSWORD=${BUILD_LOG_ZIP_PASSWORD:-$DEFAULT_BUILD_LOG_PASSWORD}
if [ "$BUILD_LOG_ZIP_PASSWORD" == "$DEFAULT_BUILD_LOG_PASSWORD" ]; then
echo "Warning: BUILD_LOG_ZIP_PASSWORD is not set. Defaulting to '${DEFAULT_BUILD_LOG_PASSWORD}'."
fi
pushd build/logs && zip -e -P "$BUILD_LOG_ZIP_PASSWORD" ../../encrypted-tests-run-logs.zip * || popd
echo "::set-output name=encrypted::true"
shell: bash
- name: Upload encrypted-tests-run-logs.zip
id: attach-encrypted-test-log
if: always() && steps.encrypt-test-log.outputs.encrypted == 'true'
uses: actions/upload-artifact@v4
with:
name: encrypted-tests-run-logs-${{ inputs.short_commit }}.zip
path: encrypted-tests-run-logs.zip
- name: Print tests-recording.log contents (if exists)
if: ${{ always() && env.RECORD_PID != '' }}
run: |
if [ -f tests-recording.log ]; then
echo "tests-recording.log found. Its contents:"
cat tests-recording.log
else
echo "tests-recording.log not found."
fi
shell: bash
- name: Check for tests-recording.mp4 presence
id: check-recording
if: ${{ always() && env.RECORD_PID != '' }}
run: |
if [ -f tests-recording.mp4 ]; then
echo "::set-output name=found::true"
echo "tests-recording.mp4 found."
else
echo "tests-recording.mp4 not found, skipping upload."
echo "::set-output name=found::false"
fi
shell: bash
- name: Upload tests-recording.mp4
id: upload-recording
if: ${{ always() && steps.check-recording.outputs.found == 'true' }}
uses: actions/upload-artifact@v4
with:
name: tests-recording-${{ inputs.short_commit }}.mp4
path: tests-recording.mp4
- name: Zip test-results
run: zip -r -9 ./test-results.zip ./build/tests
shell: bash
- name: Upload Test Artifacts
uses: actions/upload-artifact@v4
with:
name: test-results-${{ inputs.short_commit }}.zip
path: test-results.zip

View File

@@ -1,242 +1,135 @@
name: Stable SideStore build
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
- '[0-9]+.[0-9]+.[0-9]+'
workflow_dispatch:
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build SideStore - stable (on tag push)
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-26'
version: '26.0'
runs-on: ${{ matrix.os }}
name: Build SideStore - stable
runs-on: macos-26
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CHANNEL: stable
UPSTREAM_CHANNEL: ""
steps:
- name: Checkout code
uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Echo Build.xcconfig
- name: Find Last Successful commit
run: |
echo "cat Build.xcconfig"
cat Build.xcconfig
shell: bash
LAST_SUCCESSFUL_COMMIT=$(python3 scripts/ci/workflow.py last-successful-commit \
"true" || echo "")
echo "LAST_SUCCESSFUL_COMMIT=$LAST_SUCCESSFUL_COMMIT" | tee -a $GITHUB_ENV
# - name: Change MARKETING_VERSION to the pushed tag that triggered this build
# run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
- run: brew install ldid xcbeautify
- name: Echo Updated Build.xcconfig
- name: Setup Env
run: |
cat Build.xcconfig
shell: bash
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commit-id)
- name: Extract MARKETING_VERSION from Build.xcconfig
id: version
run: |
version=$(grep MARKETING_VERSION Build.xcconfig | sed -e 's/MARKETING_VERSION = //g')
echo "version=$version" >> $GITHUB_OUTPUT
echo "version=$version"
echo "MARKETING_VERSION=$version" >> $GITHUB_ENV
echo "MARKETING_VERSION=$version" >> $GITHUB_OUTPUT
echo "MARKETING_VERSION=$version"
shell: bash
- name: Fail the build if pushed tag and embedded MARKETING_VERSION in Build.xcconfig are mismatching
run: |
if [ "$MARKETING_VERSION" != "${{ github.ref_name }}" ]; then
echo 'Version mismatch: $tag != $marketing_version ... '
echo " expected-tag : $MARKETING_VERSION"
echo " pushed-tag : ${{ github.ref_name }}"
echo "Version mismatch"
echo "Build.xcconfig: $MARKETING_VERSION"
echo "Tag: ${{ github.ref_name }}"
exit 1
fi
echo 'Version matches: $tag == $marketing_version ... '
echo " expected-tag : $MARKETING_VERSION"
echo " pushed-tag : ${{ github.ref_name }}"
shell: bash
- name: Install dependencies - ldid & xcbeautify
run: |
brew install ldid xcbeautify
echo "MARKETING_VERSION=$MARKETING_VERSION" | tee -a $GITHUB_ENV
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: ${{ matrix.version }}
xcode-version: "26.0"
- name: (Build) Restore Xcode & SwiftPM Cache (Exact match)
id: xcode-cache-restore
- name: Restore Cache (exact)
id: xcode-cache-exact
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-cache-build-stable-${{ github.sha }}
key: xcode-build-cache-stable-${{ github.sha }}
- name: (Build) Restore Xcode & SwiftPM Cache (Last Available)
id: xcode-cache-restore-recent
- name: Restore Cache (last)
if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback
uses: actions/cache/restore@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-cache-build-stable-
key: xcode-build-cache-stable-
- name: (Build) Clean previous build artifacts
- name: Build
id: build
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
make clean
mkdir -p build/logs
shell: bash
python3 scripts/ci/workflow.py build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-build
exit $STATUS
- name: (Build) List Files and derived data
if: always()
shell: bash
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
ls -la .
echo ""
echo ">>>>>>>>> SideStore <<<<<<<<<<"
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Dependencies <<<<<<<<<<"
find Dependencies -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
echo ""
- name: Build SideStore.xcarchive
# using 'tee' to intercept stdout and log for detailed build-log
run: |
NSUnbufferedIO=YES make -B build 2>&1 | tee -a build/logs/build.log | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
shell: bash
- name: Fakesign app
run: make fakesign | tee -a build/logs/build.log
shell: bash
- name: Convert to IPA
run: make ipa | tee -a build/logs/build.log
shell: bash
- name: (Build) Save Xcode & SwiftPM Cache
id: cache-save
if: ${{ steps.xcode-cache-restore.outputs.cache-hit != 'true' }}
- name: Save Cache
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-cache-build-stable-${{ github.sha }}
key: xcode-build-cache-stable-${{ github.sha }}
- name: (Build) List Files and Build artifacts
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
ls -la .
echo ""
echo ">>>>>>>>> Build <<<<<<<<<<"
find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> SideStore <<<<<<<<<<"
find SideStore -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> SideStore.xcarchive <<<<<<<<<<"
find SideStore.xcarchive -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
echo ""
shell: bash
- name: Encrypt build-logs for upload
id: encrypt-build-log
run: |
DEFAULT_BUILD_LOG_PASSWORD=12345
BUILD_LOG_ZIP_PASSWORD=${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
BUILD_LOG_ZIP_PASSWORD=${BUILD_LOG_ZIP_PASSWORD:-$DEFAULT_BUILD_LOG_PASSWORD}
if [ "$BUILD_LOG_ZIP_PASSWORD" == "$DEFAULT_BUILD_LOG_PASSWORD" ]; then
echo "Warning: BUILD_LOG_ZIP_PASSWORD is not set. Defaulting to '${DEFAULT_BUILD_LOG_PASSWORD}'."
fi
pushd build/logs && zip -e -P "$BUILD_LOG_ZIP_PASSWORD" ../../encrypted-build-logs.zip * || popd
echo "::set-output name=encrypted::true"
shell: bash
- name: Upload encrypted-build-logs.zip
id: attach-encrypted-build-log
if: ${{ always() && steps.encrypt-build-log.outputs.encrypted == 'true' }}
uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v4
with:
name: encrypted-build-logs-${{ steps.version.outputs.version }}.zip
path: encrypted-build-logs.zip
name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore.ipa
- name: Zip dSYMs
run: zip -r -9 ./SideStore.dSYMs.zip ./SideStore.xcarchive/dSYMs
shell: bash
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYMs.zip
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip
- name: Get current date
id: date
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
shell: bash
- name: Generate Metadata
run: |
python3 scripts/ci/workflow.py dump-project-settings
PRODUCT_NAME=$(python3 scripts/ci/workflow.py read-product-name)
BUNDLE_ID=$(python3 scripts/ci/workflow.py read-bundle-id)
IPA_NAME="$PRODUCT_NAME.ipa"
- name: Get current date in AltStore date form
id: date_altstore
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
shell: bash
python3 scripts/ci/workflow.py generate-metadata \
"${{ github.ref_name }}" \
"$SHORT_COMMIT" \
"$MARKETING_VERSION" \
"$CHANNEL" \
"$BUNDLE_ID" \
"$IPA_NAME" \
"$LAST_SUCCESSFUL_COMMIT"
- name: Upload to releases
uses: IsaacShelton/update-existing-release@v1.3.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
release: ${{ github.ref_name }} # name
tag: ${{ github.ref_name }}
# stick with what the user pushed, do not use latest commit or anything,
# ex: if we want to go back to previous release due to hot issue, dev can create a new tag pointing to that older working tag/commit so as to keep it as an update (to revert major issue)
# in this case we do not want the tag to be auto-updated to latest
updateTag: false
prerelease: false
files: >
SideStore.ipa
SideStore.dSYMs.zip
encrypted-build-logs.zip
body: |
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
## Changelog
- TODO
## Build Info
Built at (UTC): `${{ steps.date.outputs.date }}`
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
Commit SHA: `${{ github.sha }}`
Version: `${{ steps.version.outputs.version }}`
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python3 scripts/ci/workflow.py upload-release \
"${{ github.ref_name }}" \
"${{ github.ref_name }}" \
"$GITHUB_SHA" \
"$GITHUB_REPOSITORY" \
"$UPSTREAM_CHANNEL" \
"true"

3
.gitignore vendored
View File

@@ -70,3 +70,6 @@ test-recording.mp4
test-recording.log
altstore-sources.md
local-build.sh
source-metadata.json
release-notes.md

20
.gitmodules vendored
View File

@@ -46,23 +46,23 @@
#sidestore dependencies
[submodule "SideStore/minimuxer"]
path = SideStore/minimuxer
[submodule "Dependencies/minimuxer"]
path = Dependencies/minimuxer
url = https://github.com/SideStore/minimuxer
branch = master
[submodule "SideStore/em_proxy"]
path = SideStore/em_proxy
[submodule "Dependencies/em_proxy"]
path = Dependencies/em_proxy
url = https://github.com/SideStore/em_proxy
branch = master
[submodule "SideStore/libfragmentzip"]
path = SideStore/libfragmentzip
[submodule "Dependencies/libfragmentzip"]
path = Dependencies/libfragmentzip
url = https://github.com/SideStore/libfragmentzip
branch = master
[submodule "SideStore/apps-v2.json"]
path = SideStore/apps-v2.json
[submodule "Dependencies/apps-v2.json"]
path = Dependencies/apps-v2.json
url = https://github.com/SideStore/apps-v2.json
branch = main
[submodule "SideStore/AltSign"]
path = SideStore/AltSign
[submodule "Dependencies/AltSign"]
path = Dependencies/AltSign
url = https://github.com/SideStore/AltSign
branch = master

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2610"
LastUpgradeVersion = "2620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
@@ -15,10 +15,10 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CA60C44C93D7A30E3695DD59"
BuildableName = "libem_proxy_static.a"
BlueprintName = "em_proxy-staticlib"
ReferencedContainer = "container:em_proxy.xcodeproj">
BlueprintIdentifier = "BF66EE7D2501AE50007EE018"
BuildableName = "AltStoreCore.framework"
BlueprintName = "AltStoreCore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -50,10 +50,10 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CA60C44C93D7A30E3695DD59"
BuildableName = "libem_proxy_static.a"
BlueprintName = "em_proxy-staticlib"
ReferencedContainer = "container:em_proxy.xcodeproj">
BlueprintIdentifier = "BF66EE7D2501AE50007EE018"
BuildableName = "AltStoreCore.framework"
BlueprintName = "AltStoreCore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>

View File

@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF989166250AABF3002ACF50"
BuildableName = "AltWidgetExtension.appex"
BlueprintName = "AltWidgetExtension"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "_XCWidgetKind"
value = ""
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetDefaultView"
value = "timeline"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetFamily"
value = "systemMedium"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2610"
LastUpgradeVersion = "2630"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
@@ -15,10 +15,10 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CA609C732349A560B9642892"
BuildableName = "libminimuxer_static.a"
BlueprintName = "minimuxer-staticlib"
ReferencedContainer = "container:minimuxer.xcodeproj">
BlueprintIdentifier = "A85A51412F4B4532002E2E11"
BuildableName = "libem_proxy_swift.a"
BlueprintName = "em_proxy-swift"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -50,10 +50,10 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CA609C732349A560B9642892"
BuildableName = "libminimuxer_static.a"
BlueprintName = "minimuxer-staticlib"
ReferencedContainer = "container:minimuxer.xcodeproj">
BlueprintIdentifier = "A85A51412F4B4532002E2E11"
BuildableName = "libem_proxy_swift.a"
BlueprintName = "em_proxy-swift"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF45872A2298D31600BD7491"
BuildableName = "libimobiledevice.a"
BlueprintName = "libimobiledevice"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF45872A2298D31600BD7491"
BuildableName = "libimobiledevice.a"
BlueprintName = "libimobiledevice"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -5,7 +5,10 @@
location = "group:AltStore.xcodeproj">
</FileRef>
<FileRef
location = "group:SideStore/AltSign">
location = "group:Dependencies/AltSign">
</FileRef>
<FileRef
location = "group:Dependencies/minimuxer">
</FileRef>
<FileRef
location = "group:Dependencies/Roxas/Roxas.xcodeproj">

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
<false/>
</dict>
</plist>

View File

@@ -1,115 +0,0 @@
//
// AnalyticsManager.swift
// AltStore
//
// Created by Riley Testut on 3/31/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
import AltStoreCore
import AppCenter
import AppCenterAnalytics
import AppCenterCrashes
#if DEBUG
private let appCenterAppSecret = "73532d3e-e573-4693-99a4-9f85840bbb44"
#elseif RELEASE
private let appCenterAppSecret = "73532d3e-e573-4693-99a4-9f85840bbb44"
#else
private let appCenterAppSecret = "73532d3e-e573-4693-99a4-9f85840bbb44"
#endif
extension AnalyticsManager
{
enum EventProperty: String
{
case name
case bundleIdentifier
case developerName
case version
case buildVersion
case size
case tintColor
case sourceIdentifier
case sourceURL
case patreonURL
case pledgeAmount
case pledgeCurrency
}
enum Event
{
case installedApp(InstalledApp)
case updatedApp(InstalledApp)
case refreshedApp(InstalledApp)
var name: String {
switch self
{
case .installedApp: return "installed_app"
case .updatedApp: return "updated_app"
case .refreshedApp: return "refreshed_app"
}
}
var properties: [EventProperty: String] {
let properties: [EventProperty: String?]
switch self
{
case .installedApp(let app), .updatedApp(let app), .refreshedApp(let app):
let appBundleURL = InstalledApp.fileURL(for: app)
let appBundleSize = FileManager.default.directorySize(at: appBundleURL)
properties = [
.name: app.name,
.bundleIdentifier: app.bundleIdentifier,
.developerName: app.storeApp?.developerName,
.version: app.version,
.buildVersion: app.buildVersion,
.size: appBundleSize?.description,
.tintColor: app.storeApp?.tintColor?.hexString,
.sourceIdentifier: app.storeApp?.sourceIdentifier,
.sourceURL: app.storeApp?.source?.sourceURL.absoluteString,
.patreonURL: app.storeApp?.source?.patreonURL?.absoluteString,
.pledgeAmount: app.storeApp?.pledgeAmount?.description,
.pledgeCurrency: app.storeApp?.pledgeCurrency
]
}
return properties.compactMapValues { $0 }
}
}
}
final class AnalyticsManager
{
static let shared = AnalyticsManager()
private init()
{
}
}
extension AnalyticsManager
{
func start()
{
AppCenter.start(withAppSecret: appCenterAppSecret, services: [
Analytics.self,
Crashes.self
])
}
func trackEvent(_ event: Event)
{
let properties = event.properties.reduce(into: [:]) { (properties, item) in
properties[item.key.rawValue] = item.value
}
Analytics.trackEvent(event.name, withProperties: properties)
}
}

View File

@@ -7,7 +7,6 @@
//
import UIKit
import AltStoreCore
import Roxas

View File

@@ -8,7 +8,6 @@
import UIKit
import SwiftUI
import AltStoreCore
import Roxas

View File

@@ -7,7 +7,6 @@
//
import SwiftUI
import AltStoreCore
@available(iOS 16, *)

View File

@@ -7,7 +7,6 @@
//
import UIKit
import AltStoreCore
import Roxas
@@ -392,7 +391,6 @@ private extension AppViewController
{
var buttonAction: AppBannerView.AppAction?
// if let installedApp = self.app.installedApp, let latestVersion = self.app.latestAvailableVersion, !installedApp.matches(latestVersion), !self.app.isPledgeRequired || self.app.isPledged
if let installedApp = self.app.installedApp, installedApp.hasUpdate
{
// Explicitly set button action to .update if there is an update available, even if it's not supported.
@@ -543,7 +541,6 @@ extension AppViewController
{
if let installedApp = self.app.installedApp
{
// if let latestVersion = self.app.latestAvailableVersion, !installedApp.matches(latestVersion), !self.app.isPledgeRequired || self.app.isPledged
if let latestVersion = self.app.latestAvailableVersion, installedApp.hasUpdate
{
self.updateApp(installedApp, to: latestVersion)

View File

@@ -1,27 +0,0 @@
//
// PermissionPopoverViewController.swift
// AltStore
//
// Created by Riley Testut on 7/23/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import AltStoreCore
final class PermissionPopoverViewController: UIViewController
{
var permission: AppPermission!
@IBOutlet private var nameLabel: UILabel!
@IBOutlet private var descriptionLabel: UILabel!
override func viewDidLoad()
{
super.viewDidLoad()
self.nameLabel.text = self.permission.localizedName ?? self.permission.permission.rawValue
self.descriptionLabel.text = self.permission.usageDescription
}
}

View File

@@ -7,7 +7,6 @@
//
import UIKit
import AltStoreCore
extension AppScreenshotCollectionViewCell

View File

@@ -7,7 +7,6 @@
//
import UIKit
import AltStoreCore
import Roxas

View File

@@ -7,7 +7,6 @@
//
import UIKit
import AltStoreCore
import Roxas

View File

@@ -7,7 +7,6 @@
//
import UIKit
import AltStoreCore
import Roxas

View File

@@ -1,5 +1,5 @@
//
// Proxy.swift
// AppConstants.swift
// SideStore
//
// Created by Joseph Mattiello on 11/7/22.
@@ -8,7 +8,7 @@
import Foundation
public extension Consts {
public enum AppConstants {
enum Proxy {
static let address = "127.0.0.1"
static let port = "51820"

View File

@@ -10,11 +10,10 @@ import UIKit
import UserNotifications
import AVFoundation
import Intents
import AltStoreCore
import AltSign
import Roxas
import EmotionalDamage
import Nuke
@@ -93,14 +92,12 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
}
}
AnalyticsManager.shared.start()
self.setTintColor()
self.prepareImageCache()
// TODO: @mahee96: find if we need to start em_proxy as in altstore?
if UserDefaults.standard.enableEMPforWireguard {
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
startEMProxy(bind_addr: AppConstants.Proxy.serverURL)
}
SecureValueTransformer.register()
@@ -127,7 +124,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
// Make sure to update SceneDelegate.sceneDidEnterBackground() as well.
// TODO: @mahee96: find if we need to stop em_proxy as in altstore?
if UserDefaults.standard.enableEMPforWireguard {
stop_em_proxy()
stopEMProxy()
}
guard let oneMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date()) else { return }
@@ -146,10 +143,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
{
AppManager.shared.update()
if UserDefaults.standard.enableEMPforWireguard {
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
startEMProxy(bind_addr: AppConstants.Proxy.serverURL)
}
PatreonAPI.shared.refreshPatreonAccount()
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
@@ -249,13 +244,6 @@ private extension AppDelegate
switch host
{
case "patreon":
DispatchQueue.main.async {
NotificationCenter.default.post(name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
}
return true
case "appbackupresponse":
let result: Result<Void, Error>

View File

@@ -7,7 +7,6 @@
//
import UIKit
import AltStoreCore
import AltSign
import Roxas

View File

@@ -8,12 +8,11 @@
import UIKit
import Combine
import minimuxer
import AltStoreCore
import Roxas
import Nuke
import Minimuxer
class BrowseViewController: UICollectionViewController, PeekPopPreviewing
{
@@ -557,7 +556,7 @@ private extension BrowseViewController
return
}
if !minimuxer.ready() {
if !isMinimuxerReady {
let toastView = ToastView(error: MinimuxerError.NoConnection)
toastView.show(in: self)
return

View File

@@ -7,7 +7,6 @@
//
import UIKit
import AltStoreCore
import Roxas
@@ -363,13 +362,13 @@ private extension FeaturedViewController
let sourceHasRemainingAppsPredicate = NSPredicate(format:
"""
SUBQUERY(%K, $app,
($app.%K != %@) AND ($app.%K == nil) AND (($app.%K == NO) OR ($app.%K == NO) OR ($app.%K == YES))
($app.%K != %@) AND ($app.%K == nil)
).@count > 0
""",
#keyPath(StoreApp._source._apps),
#keyPath(StoreApp.bundleIdentifier), StoreApp.altstoreAppID,
#keyPath(StoreApp.installedApp),
#keyPath(StoreApp.isPledgeRequired), #keyPath(StoreApp.isHiddenWithoutPledge), #keyPath(StoreApp.isPledged)
#keyPath(StoreApp._source._apps),
#keyPath(StoreApp.bundleIdentifier),
StoreApp.altstoreAppID,
#keyPath(StoreApp.installedApp)
)
let primaryFetchRequest = fetchRequest.copy() as! NSFetchRequest<StoreApp>

View File

@@ -7,7 +7,6 @@
//
import UIKit
import AltStoreCore
import Roxas
@@ -167,29 +166,7 @@ extension AppBannerView
self.subtitleLabel.text = NSLocalizedString("Sideloaded", comment: "")
self.accessibilityLabel = values.name
}
if let storeApp = app.storeApp, storeApp.isPledgeRequired
{
// Always show button label for Patreon apps.
self.buttonLabel.isHidden = false
if storeApp.isPledged
{
self.buttonLabel.text = NSLocalizedString("Pledged", comment: "")
}
else if storeApp.installedApp != nil
{
self.buttonLabel.text = NSLocalizedString("Pledge Expired", comment: "")
}
else
{
self.buttonLabel.text = NSLocalizedString("Join Patreon", comment: "")
}
}
else
{
self.buttonLabel.isHidden = true
}
self.buttonLabel.isHidden = true
if let source = app.storeApp?.source, showSourceIcon
{
@@ -294,15 +271,6 @@ extension AppBannerView
self.button.accessibilityLabel = String(format: NSLocalizedString("Install %@", comment: ""), app.name)
self.button.accessibilityValue = buttonTitle
}
else if let amount = storeApp.pledgeAmount, let currencyCode = storeApp.pledgeCurrency, !storeApp.prefersCustomPledge, #available(iOS 15, *)
{
let price = amount.formatted(.currency(code: currencyCode).presentation(.narrow).precision(.fractionLength(0...2)))
let buttonTitle = String(format: NSLocalizedString("%@/mo", comment: ""), price)
self.button.setTitle(buttonTitle, for: .normal)
self.button.accessibilityLabel = String(format: NSLocalizedString("Pledge %@ a month", comment: ""), price)
self.button.accessibilityValue = String(format: NSLocalizedString("%@ a month", comment: ""), price)
}
else
{
let buttonTitle = NSLocalizedString("Pledge", comment: "")

View File

@@ -7,7 +7,6 @@
//
import UIKit
import AltStoreCore
import Roxas

View File

@@ -7,7 +7,6 @@
//
import UIKit
import AltStoreCore
import Roxas

View File

@@ -7,7 +7,6 @@
//
import Roxas
import AltStoreCore
extension TimeInterval

View File

@@ -1,13 +0,0 @@
//
// Consts.swift
// SideStore
//
// Created by Joseph Mattiello on 11/7/22.
// Copyright © 2022 Riley Testut. All rights reserved.
//
import Foundation
public enum Consts {
}

View File

@@ -8,7 +8,6 @@
import AppIntents
import WidgetKit
import AltStoreCore
// Shouldn't conform types we don't own to protocols we don't own, so make custom

View File

@@ -7,8 +7,6 @@
//
import Foundation
import minimuxer
import AltStoreCore
@available(iOS 14, *)
@@ -41,7 +39,7 @@ final class IntentHandler: NSObject, RefreshAllIntentHandling
// Give ourselves 9 extra seconds before starting handle() timeout timer.
// 10 seconds or longer results in timeout regardless.
self.queue.asyncAfter(deadline: .now() + 8.0) {
if minimuxer.ready() {
if isMinimuxerReady {
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
} else {
self.finish(intent, response: RefreshAllIntentResponse(code: .failure, userActivity: nil))
@@ -90,7 +88,7 @@ final class IntentHandler: NSObject, RefreshAllIntentHandling
// We took too long to finish and return the final result,
// so we'll now present a normal notification when finished.
operation.presentsFinishedNotification = true
if minimuxer.ready() {
if isMinimuxerReady {
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
} else {
self.finish(intent, response: RefreshAllIntentResponse(code: .failure, userActivity: nil))

View File

@@ -8,8 +8,7 @@
import UIKit
import Roxas
import EmotionalDamage
import minimuxer
import WidgetKit
import AltSign
@@ -77,7 +76,7 @@ final class LaunchViewController: UIViewController, UIDocumentPickerDelegate {
detectAndImportAccountFile()
if UserDefaults.standard.enableEMPforWireguard {
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
startEMProxy(bind_addr: AppConstants.Proxy.serverURL)
}
guard let pf = fetchPairingFile() else {
displayError("Device pairing file not found.")
@@ -88,16 +87,16 @@ final class LaunchViewController: UIViewController, UIDocumentPickerDelegate {
}
func start_minimuxer_threads(_ pairing_file: String) {
target_minimuxer_address()
retargetUsbmuxdAddr()
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
do {
let loggingEnabled = UserDefaults.standard.isMinimuxerConsoleLoggingEnabled
try minimuxer.startWithLogger(pairing_file, documentsDirectory, loggingEnabled)
try minimuxerStartWithLogger(pairing_file, documentsDirectory, loggingEnabled)
} catch {
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent(pairingFileName))
displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR")")
}
start_auto_mounter(documentsDirectory)
startAutoMounter(documentsDirectory)
}
func fetchPairingFile() -> String? { PairingFileManager.shared.fetchPairingFile(presentingVC: self) }
@@ -199,8 +198,6 @@ extension LaunchViewController {
didFinishLaunching = true
AppManager.shared.update()
AppManager.shared.updatePatronsIfNeeded()
PatreonAPI.shared.refreshPatreonAccount()
AppManager.shared.updateAllSources { result in
guard case .failure(let error) = result else { return }
Logger.main.error("Failed to update sources on launch. \(error.localizedDescription, privacy: .public)")

View File

@@ -13,16 +13,14 @@ import MobileCoreServices
import Intents
import Combine
import WidgetKit
import minimuxer
import AltStoreCore
import AltSign
import Roxas
import Minimuxer
extension AppManager
{
static let didFetchSourceNotification = Notification.Name("io.sidestore.AppManager.didFetchSource")
static let didUpdatePatronsNotification = Notification.Name("io.sidestore.AppManager.didUpdatePatrons")
static let didAddSourceNotification = Notification.Name("io.sidestore.AppManager.didAddSource")
static let didRemoveSourceNotification = Notification.Name("io.sidestore.AppManager.didRemoveSource")
static let willInstallAppFromNewSourceNotification = Notification.Name("io.sidestore.AppManager.willInstallAppFromNewSource")
@@ -590,34 +588,6 @@ extension AppManager
return updateKnownSourcesOperation
}
func updatePatronsIfNeeded()
{
// guard self.operationQueue.operations.allSatisfy({ !($0 is UpdatePatronsOperation) }) else {
// // There's already an UpdatePatronsOperation running.
// return
// }
//
// self.updatePatronsResult = nil
//
// let updatePatronsOperation = UpdatePatronsOperation()
// updatePatronsOperation.resultHandler = { (result) in
// do
// {
// try result.get()
// self.updatePatronsResult = .success(())
// }
// catch
// {
// print("Error updating Friend Zone Patrons:", error)
// self.updatePatronsResult = .failure(error)
// }
//
// NotificationCenter.default.post(name: AppManager.didUpdatePatronsNotification, object: self)
// }
//
// self.run([updatePatronsOperation], context: nil)
}
func updateAllSources(completion: @escaping (Result<Void, Error>) -> Void)
{
self.updateSourcesResult = nil
@@ -1028,6 +998,7 @@ extension AppManager
case .failure(let error): completionHandler(.failure(error))
case .success(let installedApp): completionHandler(.success(installedApp))
}
//UIApplication.shared.open(shortcutURLon, options: [:], completionHandler: nil)
}
installOperation.addDependency(sendAppOperation)
@@ -1265,17 +1236,6 @@ private extension AppManager
assert(context.authenticatedContext === group.context)
context.beginInstallationHandler = { (installedApp) in
switch appOperation
{
case .update where installedApp.bundleIdentifier == StoreApp.altstoreAppID:
// AltStore will quit before installation finishes,
// so assume if we get this far the update will finish successfully.
let event = AnalyticsManager.Event.updatedApp(installedApp)
AnalyticsManager.shared.trackEvent(event)
default: break
}
group.beginInstallationHandler?(installedApp)
}
@@ -1296,20 +1256,6 @@ private extension AppManager
}
}
var verifyPledgeOperation: VerifyAppPledgeOperation?
if let storeApp = app.storeApp
{
verifyPledgeOperation = VerifyAppPledgeOperation(storeApp: storeApp, presentingViewController: context.presentingViewController)
verifyPledgeOperation?.resultHandler = { result in
switch result
{
case .failure(let error):
context.error = error
case .success: break
}
}
}
/* Download */
let downloadedAppURL = context.temporaryDirectory.appendingPathComponent("Cached.app")
let downloadOperation = DownloadAppOperation(app: downloadingApp, destinationURL: downloadedAppURL, context: context)
@@ -1332,12 +1278,6 @@ private extension AppManager
}
progress.addChild(downloadOperation.progress, withPendingUnitCount: 25)
if let verifyPledgeOperation
{
downloadOperation.addDependency(verifyPledgeOperation)
}
/* Verify App */
let permissionsMode = UserDefaults.shared.permissionCheckingDisabled ? .none : permissionReviewMode
let verifyOperation = VerifyAppOperation(permissionsMode: permissionsMode, context: context, customBundleId: app.bundleIdentifier)
@@ -1605,7 +1545,6 @@ private extension AppManager
// Operations picked for request
var operations = [
verifyPledgeOperation,
downloadOperation,
verifyOperation,
removeAppExtensionsOperation,
@@ -1623,8 +1562,6 @@ private extension AppManager
if let storeApp = downloadingApp.storeApp, storeApp.isPledgeRequired
{
// Patreon apps may require authenticating with WebViewController,
// so make sure to run DownloadAppOperation serially.
self.run([downloadOperation], context: group.context, requiresSerialQueue: true)
if let index = operations.firstIndex(of: downloadOperation)
@@ -1744,7 +1681,7 @@ private extension AppManager
// Fall back to installation if AltServer doesn't support newer provisioning profile requests,
// OR if the cached app could not be found and we may need to redownload it.
app.managedObjectContext?.performAndWait { // Must performAndWait to ensure we add operations before we return.
if minimuxer.ready() {
if isMinimuxerReady {
let installProgress = self._install(app, operation: operation, group: group) { (result) in
completionHandler(result)
}
@@ -2153,27 +2090,6 @@ private extension AppManager
self.scheduleExpirationWarningLocalNotification(for: installedApp)
}
let event: AnalyticsManager.Event?
switch operation
{
case .install: event = .installedApp(installedApp)
case .refresh: event = .refreshedApp(installedApp)
case .update where installedApp.bundleIdentifier == StoreApp.altstoreAppID:
// AltStore quits before update finishes, so we've preemptively logged this update event.
// In case AltStore doesn't quit, such as when update has a different bundle identifier,
// make sure we don't log this update event a second time.
event = nil
case .update: event = .updatedApp(installedApp)
case .activate, .deactivate, .backup, .restore: event = nil
}
if let event = event
{
AnalyticsManager.shared.trackEvent(event)
}
// Ask widgets to be refreshed
WidgetCenter.shared.reloadAllTimelines()
@@ -2258,7 +2174,7 @@ private extension AppManager
switch operation
{
case _ where requiresSerialQueue: fallthrough
case is InstallAppOperation, is RefreshAppOperation, is BackupAppOperation, is VerifyAppPledgeOperation:
case is InstallAppOperation, is RefreshAppOperation, is BackupAppOperation:
if let installAltStoreOperation = operation as? InstallAppOperation, installAltStoreOperation.context.bundleIdentifier == StoreApp.altstoreAppID
{
// Add dependencies on previous serial operations in `context` to ensure re-installing AltStore goes last.

View File

@@ -8,7 +8,6 @@
import Foundation
import CoreData
import AltStoreCore
extension AppManager

View File

@@ -11,11 +11,9 @@ import MobileCoreServices
import Intents
import Combine
import UniformTypeIdentifiers
import AltStoreCore
import AltSign
import Roxas
import minimuxer
import SemanticVersion
import Nuke
@@ -120,7 +118,6 @@ class MyAppsViewController: UICollectionViewController, PeekPopPreviewing
{
super.viewIsAppearing(animated)
// Ensure the button for each app reflects correct Patreon status.
self.collectionView.reloadData()
self.update()
@@ -169,7 +166,7 @@ class MyAppsViewController: UICollectionViewController, PeekPopPreviewing
var minimuxerStatus: Bool {
// added isMinimuxerStatusCheckEnabled to forcefully ignore minimuxer status if status check is disabled in settings
guard !UserDefaults.standard.isMinimuxerStatusCheckEnabled || minimuxer.ready() else {
guard !UserDefaults.standard.isMinimuxerStatusCheckEnabled || isMinimuxerReady else {
ToastView(error: (OperationError.noWiFi as NSError).withLocalizedTitle("No Wi-Fi or VPN!")).show(in: self)
return false
}
@@ -544,11 +541,6 @@ private extension MyAppsViewController
{
print("[ALTLog] Failed to fetch updates:", error)
}
if let patreonAccount = DatabaseManager.shared.patreonAccount(), patreonAccount.isAltStorePatron, PatreonAPI.shared.isAuthenticated
{
self.dataSource.predicate = nil
}
}
}
@@ -1995,8 +1987,6 @@ extension MyAppsViewController
for action in actions where !allowedActions.contains(action)
{
// Disable options for Patreon apps that we are no longer pledged to.
if let action = action as? UIAction
{
action.attributes = .disabled

View File

@@ -9,7 +9,6 @@
import UIKit
import SafariServices
import Combine
import AltStoreCore
import Roxas

View File

@@ -9,10 +9,8 @@
import Foundation
import Roxas
import Network
import AltStoreCore
import AltSign
import minimuxer
private extension UIColor
{
@@ -703,7 +701,7 @@ private extension AuthenticationOperation
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
{
guard let udid = fetch_udid()?.toString() else {
guard let udid = fetchUDID() else {
return completionHandler(.failure(OperationError.unknownUDID))
}

View File

@@ -8,10 +8,8 @@
import UIKit
import CoreData
import AltStoreCore
import EmotionalDamage
import minimuxer
typealias RefreshError = RefreshErrorCode.Error
enum RefreshErrorCode: Int, ALTErrorEnum, CaseIterable
@@ -101,15 +99,15 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
}
if UserDefaults.standard.enableEMPforWireguard {
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
startEMProxy(bind_addr: AppConstants.Proxy.serverURL)
}
target_minimuxer_address()
retargetUsbmuxdAddr()
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
do {
// enable minimuxer console logging only if enabled in settings
let isMinimuxerConsoleLoggingEnabled = UserDefaults.standard.isMinimuxerConsoleLoggingEnabled
try minimuxer.startWithLogger(
try minimuxerStartWithLogger(
try String(contentsOf: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")),
documentsDirectory,
isMinimuxerConsoleLoggingEnabled
@@ -120,7 +118,7 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
if #available(iOS 17, *) {
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
} else {
start_auto_mounter(documentsDirectory)
startAutoMounter(documentsDirectory)
}
self.managedObjectContext.perform {

View File

@@ -7,7 +7,6 @@
//
import Foundation
import AltStoreCore
import AltSign

View File

@@ -7,11 +7,9 @@
//
import Foundation
import AltStoreCore
import AltSign
import Roxas
import minimuxer
@objc(DeactivateAppOperation)
final class DeactivateAppOperation: ResultOperation<InstalledApp>
@@ -44,7 +42,7 @@ final class DeactivateAppOperation: ResultOperation<InstalledApp>
for profile in allIdentifiers {
do {
try remove_provisioning_profile(profile)
try removeProvisioningProfile(profile)
self.progress.completedUnitCount += 1
installedApp.isActive = false
self.finish(.success(installedApp))

View File

@@ -9,7 +9,6 @@
import Foundation
import WebKit
import UniformTypeIdentifiers
import AltStoreCore
import AltSign
import Roxas
@@ -30,8 +29,6 @@ final class DownloadAppOperation: ResultOperation<ALTApplication>
private let session = URLSession(configuration: .default)
private let temporaryDirectory = FileManager.default.uniqueTemporaryURL()
private var downloadPatreonAppContinuation: CheckedContinuation<URL, Error>?
init(app: AppProtocol, destinationURL: URL, context: InstallAppOperationContext)
{
self.app = app
@@ -224,12 +221,6 @@ private extension DownloadAppOperation
fileURL = sourceURL
self.progress.completedUnitCount += 3
}
else if let host = sourceURL.host, host.lowercased().hasSuffix("patreon.com") && sourceURL.path.lowercased() == "/file"
{
// Patreon app
fileURL = try await downloadPatreonApp(from: sourceURL)
self.printWithTid("downloadPatreonApp: completed at \(fileURL.path)")
}
else
{
// Regular app
@@ -323,162 +314,6 @@ private extension DownloadAppOperation
self.printWithTid("download started: \(downloadURL)")
}
}
func downloadPatreonApp(from patreonURL: URL) async throws -> URL
{
guard !UserDefaults.shared.skipPatreonDownloads else {
// Skip all hacks, take user straight to Patreon post.
return try await downloadFromPatreonPost()
}
do
{
// User is pledged to this app, attempt to download.
let fileURL = try await downloadFile(from: patreonURL)
return fileURL
}
catch URLError.noPermissionsToReadFile
{
guard let presentingViewController = self.context.presentingViewController else { throw OperationError.pledgeRequired(appName: self.appName) }
// Attempt to sign-in again in case our Patreon session has expired.
try await withCheckedThrowingContinuation { continuation in
PatreonAPI.shared.authenticate(presentingViewController: presentingViewController) { result in
do
{
let account = try result.get()
try account.managedObjectContext?.save()
continuation.resume()
}
catch
{
continuation.resume(throwing: error)
}
}
}
do
{
// Success, so try to download once more now that we're definitely authenticated.
let fileURL = try await downloadFile(from: patreonURL)
return fileURL
}
catch URLError.noPermissionsToReadFile
{
// We know authentication succeeded, so failure must mean user isn't patron/on the correct tier,
// or that our hacky workaround for downloading Patreon attachments has failed.
// Either way, taking them directly to the post serves as a decent fallback.
return try await downloadFromPatreonPost()
}
}
func downloadFromPatreonPost() async throws -> URL
{
guard let presentingViewController = self.context.presentingViewController else { throw OperationError.pledgeRequired(appName: self.appName) }
let downloadURL: URL
if let components = URLComponents(url: patreonURL, resolvingAgainstBaseURL: false),
let postItem = components.queryItems?.first(where: { $0.name == "h" }),
let postID = postItem.value,
let patreonPostURL = URL(string: "https://www.patreon.com/posts/" + postID)
{
downloadURL = patreonPostURL
}
else
{
downloadURL = patreonURL
}
return try await downloadFromPatreon(downloadURL, presentingViewController: presentingViewController)
}
}
@MainActor
func downloadFromPatreon(_ patreonURL: URL, presentingViewController: UIViewController) async throws -> URL
{
let webViewController = WebViewController(url: patreonURL)
webViewController.delegate = self
webViewController.webView.navigationDelegate = self
let navigationController = UINavigationController(rootViewController: webViewController)
presentingViewController.present(navigationController, animated: true)
let downloadURL: URL
do
{
defer {
navigationController.dismiss(animated: true)
}
downloadURL = try await withCheckedThrowingContinuation { continuation in
self.downloadPatreonAppContinuation = continuation
}
}
let fileURL = try await downloadFile(from: downloadURL)
return fileURL
}
}
}
extension DownloadAppOperation: WebViewControllerDelegate
{
func webViewControllerDidFinish(_ webViewController: WebViewController)
{
guard let continuation = self.downloadPatreonAppContinuation else { return }
self.downloadPatreonAppContinuation = nil
continuation.resume(throwing: CancellationError())
}
}
extension DownloadAppOperation: WKNavigationDelegate
{
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy
{
guard #available(iOS 14.5, *), navigationAction.shouldPerformDownload else { return .allow }
guard let continuation = self.downloadPatreonAppContinuation else { return .allow }
self.downloadPatreonAppContinuation = nil
if let downloadURL = navigationAction.request.url
{
continuation.resume(returning: downloadURL)
}
else
{
continuation.resume(throwing: URLError(.badURL))
}
return .cancel
}
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse) async -> WKNavigationResponsePolicy
{
// Called for Patreon attachments
guard !navigationResponse.canShowMIMEType else { return .allow }
guard let continuation = self.downloadPatreonAppContinuation else { return .allow }
self.downloadPatreonAppContinuation = nil
guard let response = navigationResponse.response as? HTTPURLResponse, let responseURL = response.url,
let mimeType = response.mimeType, let type = UTType(mimeType: mimeType),
type.conforms(to: .ipa) || type.conforms(to: .zip) || type.conforms(to: .application)
else {
continuation.resume(throwing: OperationError.invalidApp)
return .cancel
}
continuation.resume(returning: responseURL)
return .cancel
}
}

View File

@@ -8,9 +8,7 @@
import UIKit
import Combine
import minimuxer
import UniformTypeIdentifiers
import AltStoreCore
enum SideJITServerErrorType: Error {
@@ -90,7 +88,7 @@ final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
var retries = 3
while (retries > 0){
do {
try debug_app(installedApp.resignedBundleIdentifier)
try debugApp(installedApp.resignedBundleIdentifier)
self.finish(.success(()))
retries = 0
} catch {
@@ -107,7 +105,7 @@ final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
@available(iOS 17, *)
func enableJITSideJITServer(serverURL: URL, installedApp: InstalledApp, completion: @escaping (Result<Void, SideJITServerErrorType>) -> Void) {
guard let udid = fetch_udid()?.toString() else {
guard let udid = fetchUDID() else {
completion(.failure(.other("Unable to get UDID")))
return
}

View File

@@ -9,7 +9,6 @@
import Foundation
import AltSign
import AltStoreCore
import minimuxer
extension OperationError
{
@@ -301,99 +300,3 @@ struct OperationError: ALTLocalizedError {
}
}
}
extension MinimuxerError: LocalizedError {
public var failureReason: String? {
switch self {
case .NoDevice:
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
case .NoConnection:
return NSLocalizedString("Unable to connect to the device, make sure LocalDevVPN is enabled and you're connected to Wi-Fi. This could mean an invalid pairing.", comment: "")
case .PairingFile:
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use iloader to replace it.", comment: "")
case .CreateDebug:
return self.createService(name: "debug")
case .LookupApps:
return self.getFromDevice(name: "installed apps")
case .FindApp:
return self.getFromDevice(name: "path to the app")
case .BundlePath:
return self.getFromDevice(name: "bundle path")
case .MaxPacket:
return self.setArgument(name: "max packet")
case .WorkingDirectory:
return self.setArgument(name: "working directory")
case .Argv:
return self.setArgument(name: "argv")
case .LaunchSuccess:
return self.getFromDevice(name: "launch success")
case .Detach:
return NSLocalizedString("Unable to detach from the app's process", comment: "")
case .Attach:
return NSLocalizedString("Unable to attach to the app's process", comment: "")
case .CreateInstproxy:
return self.createService(name: "instproxy")
case .CreateAfc:
return self.createService(name: "AFC")
case .RwAfc:
return NSLocalizedString("AFC was unable to manage files on the device. Ensure Wi-Fi and LocalDevVPN are connected. If they both are, replace your pairing using iloader.", comment: "")
case .InstallApp(let message):
return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "")
case .UninstallApp:
return NSLocalizedString("Unable to uninstall the app", comment: "")
case .CreateMisagent:
return self.createService(name: "misagent")
case .ProfileInstall:
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
case .ProfileRemove:
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
case .CreateLockdown:
return NSLocalizedString("Unable to connect to lockdown", comment: "")
case .CreateCoreDevice:
return NSLocalizedString("Unable to connect to core device proxy", comment: "")
case .CreateSoftwareTunnel:
return NSLocalizedString("Unable to create software tunnel", comment: "")
case .CreateRemoteServer:
return NSLocalizedString("Unable to connect to remote server", comment: "")
case .CreateProcessControl:
return NSLocalizedString("Unable to connect to process control", comment: "")
case .GetLockdownValue:
return NSLocalizedString("Unable to get value from lockdown", comment: "")
case .Connect:
return NSLocalizedString("Unable to connect to TCP port", comment: "")
case .Close:
return NSLocalizedString("Unable to close TCP port", comment: "")
case .XpcHandshake:
return NSLocalizedString("Unable to get services from XPC", comment: "")
case .NoService:
return NSLocalizedString("Device did not contain service", comment: "")
case .InvalidProductVersion:
return NSLocalizedString("Service version was in an unexpected format", comment: "")
case .CreateFolder:
return NSLocalizedString("Unable to create DDI folder", comment: "")
case .DownloadImage:
return NSLocalizedString("Unable to download DDI", comment: "")
case .ImageLookup:
return NSLocalizedString("Unable to lookup DDI images", comment: "")
case .ImageRead:
return NSLocalizedString("Unable to read images to memory", comment: "")
case .Mount:
return NSLocalizedString("Mount failed", comment: "")
}
}
fileprivate func createService(name: String) -> String {
return String(format: NSLocalizedString("Cannot start a %@ server on the device.", comment: ""), name)
}
fileprivate func getFromDevice(name: String) -> String {
return String(format: NSLocalizedString("Cannot fetch %@ from the device.", comment: ""), name)
}
fileprivate func setArgument(name: String) -> String {
return String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name)
}
}

View File

@@ -5,7 +5,6 @@
// Created by Riley Testut on 5/3/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import AltStoreCore
extension SourceError

View File

@@ -5,7 +5,6 @@
// Created by Riley Testut on 5/11/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import AltStoreCore
import AltSign

View File

@@ -9,7 +9,6 @@
import Foundation
import CommonCrypto
import Starscream
import AltStoreCore
import AltSign
import Roxas

View File

@@ -7,7 +7,6 @@
//
import Foundation
import AltStoreCore
import AltSign
import Roxas

View File

@@ -7,7 +7,6 @@
//
import Foundation
import AltStoreCore
import AltSign
import Roxas

View File

@@ -8,7 +8,6 @@
import Foundation
import CoreData
import AltStoreCore
import Roxas
import SemanticVersion
@@ -171,13 +170,7 @@ final class FetchSourceOperation: ResultOperation<Source>
let identifier = source.identifier
if identifier == Source.altStoreIdentifier, let skipPatreonDownloads = source.userInfo?[.skipPatreonDownloads]
{
UserDefaults.shared.skipPatreonDownloads = (skipPatreonDownloads == "true")
}
try self.verify(source, response: response)
try self.verifyPledges(for: source, in: childContext)
try childContext.save()
@@ -264,63 +257,6 @@ private extension FetchSourceOperation
}
}
func verifyPledges(for source: Source, in context: NSManagedObjectContext) throws
{
guard let patreonURL = source.patreonURL, let patreonAccount = DatabaseManager.shared.patreonAccount(in: context) else { return }
let normalizedPatreonURL = try patreonURL.normalized()
guard let pledge = patreonAccount.pledges.first(where: { pledge in
do
{
let normalizedCampaignURL = try pledge.campaignURL.normalized()
return normalizedCampaignURL == normalizedPatreonURL
}
catch
{
Logger.main.error("Failed to normalize Patreon URL \(pledge.campaignURL, privacy: .public). \(error.localizedDescription, privacy: .public)")
return false
}
}) else { return }
// User is pledged to this source's Patreon, so check which apps they're pledged to.
// We only assign `isPledged = true` because false is already the default,
// and only one check needs to be true for isPledged to be true.
for app in source.apps where app.isPledgeRequired
{
if let requiredAppPledge = app.pledgeAmount
{
if pledge.amount >= requiredAppPledge
{
app.isPledged = true
continue
}
}
if let tierIDs = app._tierIDs
{
let tier = pledge.tiers.first { tierIDs.contains($0.identifier) }
if tier != nil
{
app.isPledged = true
continue
}
}
if let rewardID = app._rewardID
{
let reward = pledge.rewards.first { $0.identifier == rewardID }
if reward != nil
{
app.isPledged = true
continue
}
}
}
}
func verifySourceNotBlocked(_ source: Source, response: URLResponse?) throws
{
guard let blockedSources = UserDefaults.shared.blockedSources else { return }

View File

@@ -1,65 +0,0 @@
//
// FetchTrustedSourcesOperation.swift
// AltStore
//
// Created by Riley Testut on 4/13/22.
// Copyright © 2022 Riley Testut. All rights reserved.
//
import Foundation
private extension URL
{
#if STAGING
static let trustedSources = URL(string: "https://raw.githubusercontent.com/SideStore/SideStore/develop/trustedapps.json")!
#else
static let trustedSources = URL(string: "https://raw.githubusercontent.com/SideStore/SideStore/develop/trustedapps.json")!
#endif
}
extension FetchTrustedSourcesOperation
{
struct TrustedSource: Decodable
{
var identifier: String
var sourceURL: URL?
}
private struct Response: Decodable
{
var version: Int
var sources: [FetchTrustedSourcesOperation.TrustedSource]
}
}
final class FetchTrustedSourcesOperation: ResultOperation<[FetchTrustedSourcesOperation.TrustedSource]>
{
override func main()
{
super.main()
let dataTask = URLSession.shared.dataTask(with: .trustedSources) { (data, response, error) in
do
{
if let response = response as? HTTPURLResponse
{
guard response.statusCode != 404 else {
self.finish(.failure(URLError(.fileDoesNotExist, userInfo: [NSURLErrorKey: URL.trustedSources])))
return
}
}
guard let data = data else { throw error! }
let response = try Foundation.JSONDecoder().decode(Response.self, from: data)
self.finish(.success(response.sources))
}
catch
{
self.finish(.failure(error))
}
}
dataTask.resume()
}
}

View File

@@ -7,11 +7,11 @@
//
import Foundation
import Network
import AltStoreCore
import AltSign
import Roxas
import minimuxer
let shortcutURLonDelay = URL(string: "shortcuts://run-shortcut?name=TurnOnDataDelay")!
@objc(InstallAppOperation)
final class InstallAppOperation: ResultOperation<InstalledApp>
@@ -178,6 +178,13 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
var installing = true
if installedApp.storeApp?.bundleIdentifier.range(of: Bundle.Info.appbundleIdentifier) != nil {
do {
// we need to flush changes to the disk now in case the changes are lost when iOS kills current process
try installedApp.managedObjectContext?.save()
} catch {
print("Failed to flush installedApp to disk: \(error)")
}
// Reinstalling ourself will hang until we leave the app, so we need to exit it without force closing
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
if UIApplication.shared.applicationState != .active {
@@ -206,7 +213,14 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
let alert = UIAlertController(title: "Finish Refresh", message: "Please reopen SideStore after the process is finished.To finish refreshing, SideStore must be moved to the background. To do this, you can either go to the Home Screen manually or by hitting Continue. Please reopen SideStore after doing this.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default, handler: { _ in
print("Going home")
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
// Cell Shortcut
DispatchQueue.main.async{
// UIApplication.shared.open(shortcutURLonDelay, options: [:]) { _ in
// print("Cell OFF Shortcut finished execution.")
// }
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
}
}))
DispatchQueue.main.async {
@@ -222,12 +236,19 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
}
}
}
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
DispatchQueue.main.async {
// Cell Shortcut
// UIApplication.shared.open(shortcutURLonDelay, options: [:]) { _ in
// print("Cell OFF Shortcut finished execution.")
// }
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
}
}
}
do {
try install_ipa(installedApp.bundleIdentifier)
try installIPA(installedApp.bundleIdentifier)
installing = false
installedApp.refreshedDate = Date()
self.finish(.success(installedApp))

View File

@@ -9,7 +9,6 @@
import Foundation
import CoreData
import Network
import AltStoreCore
import AltSign

View File

@@ -10,7 +10,6 @@ import UIKit
import Combine
import AppleArchive
import System
import AltStoreCore
import AltSign
import Roxas
@@ -212,7 +211,6 @@ private extension PatchAppOperation
#if targetEnvironment(simulator)
throw PatchAppError.unsupportedOperatingSystemVersion(ProcessInfo.processInfo.operatingSystemVersion)
#else
let spotlightPath = "Applications/Spotlight.app/Spotlight"
let spotlightFileURL = self.patchDirectory.appendingPathComponent(spotlightPath)

View File

@@ -8,7 +8,6 @@
import UIKit
import Combine
import AltStoreCore
import AltSign
import Roxas

View File

@@ -7,11 +7,11 @@
//
import Foundation
import AltStoreCore
import AltSign
import Roxas
import minimuxer
import Minimuxer
@objc(RefreshAppOperation)
final class RefreshAppOperation: ResultOperation<InstalledApp>
@@ -48,8 +48,7 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
for p in profiles {
do {
let bytes = p.value.data.toRustByteSlice()
try install_provisioning_profile(bytes.forRust())
try installProvisioningProfiles(p.value.data)
} catch {
self.finish(.failure(MinimuxerError.ProfileInstall))
}

View File

@@ -8,7 +8,6 @@
import Foundation
import CoreData
import AltStoreCore
import AltSign

View File

@@ -7,7 +7,6 @@
//
import Foundation
import AltStoreCore
@objc(RemoveAppBackupOperation)

View File

@@ -7,7 +7,6 @@
//
import Foundation
import AltStoreCore
import Roxas
import AltSign

View File

@@ -7,9 +7,7 @@
//
import Foundation
import AltStoreCore
import minimuxer
@objc(RemoveAppOperation)
final class RemoveAppOperation: ResultOperation<InstalledApp>
@@ -43,7 +41,7 @@ final class RemoveAppOperation: ResultOperation<InstalledApp>
let resignedBundleIdentifier = installedApp.resignedBundleIdentifier
do {
try remove_app(resignedBundleIdentifier)
try removeApp(resignedBundleIdentifier)
} catch {
return self.finish(.failure(error))
}

View File

@@ -8,10 +8,8 @@
import Foundation
import Roxas
import AltStoreCore
import AltSign
import minimuxer
@objc(ResignAppOperation)
final class ResignAppOperation: ResultOperation<ALTApplication>
@@ -203,7 +201,7 @@ private extension ResignAppOperation
if app.isAltStoreApp
{
guard let udid = fetch_udid()?.toString() as? String else { throw OperationError.unknownUDID }
guard let udid = fetchUDID() else { throw OperationError.unknownUDID }
guard Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) is String else { throw OperationError.unknownUDID }
additionalValues[Bundle.Info.devicePairingString] = "<insert pairing file here>"
additionalValues[Bundle.Info.deviceID] = udid
@@ -224,7 +222,7 @@ private extension ResignAppOperation
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
}
}
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = fetch_udid()?.toString() as? String
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = fetchUDID()
{
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
additionalValues[Bundle.Info.deviceID] = udid

View File

@@ -7,9 +7,9 @@
//
import Foundation
import Network
import AltStoreCore
import minimuxer
import Minimuxer
@objc(SendAppOperation)
final class SendAppOperation: ResultOperation<()>
@@ -27,12 +27,10 @@ final class SendAppOperation: ResultOperation<()>
self.progress.totalUnitCount = 1
}
override func main()
{
override func main() {
super.main()
if let error = self.context.error
{
if let error = self.context.error {
return self.finish(.failure(error))
}
@@ -40,26 +38,39 @@ final class SendAppOperation: ResultOperation<()>
return self.finish(.failure(OperationError.invalidParameters("SendAppOperation.main: self.resignedApp is nil")))
}
// self.context.resignedApp.fileURL points to the app bundle, but we want the .ipa.
let shortcutURLoff = URL(string: "shortcuts://run-shortcut?name=TurnOffData")!
let app = AnyApp(name: resignedApp.name, bundleIdentifier: self.context.bundleIdentifier, url: resignedApp.fileURL, storeApp: nil)
let fileURL = InstalledApp.refreshedIPAURL(for: app)
print("AFC App `fileURL`: \(fileURL.absoluteString)")
if let data = NSData(contentsOf: fileURL) {
do {
let bytes = Data(data).toRustByteSlice()
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
self.progress.completedUnitCount += 1
self.finish(.success(()))
} catch {
self.finish(.failure(MinimuxerError.RwAfc))
self.progress.completedUnitCount += 1
self.finish(.success(()))
}
} else {
// Wait for Shortcut to Finish Before Proceeding
DispatchQueue.main.async {
// UIApplication.shared.open(shortcutURLoff, options: [:]) { _ in
// print("Shortcut finished execution. Proceeding with file transfer.")
DispatchQueue.global().async {
self.processFile(at: fileURL, for: app.bundleIdentifier)
}
// }
}
}
private func processFile(at fileURL: URL, for bundleIdentifier: String) {
guard let data = NSData(contentsOf: fileURL) else {
print("IPA doesn't exist????")
self.finish(.failure(OperationError(.appNotFound(name: resignedApp.name))))
return self.finish(.failure(OperationError(.appNotFound(name: bundleIdentifier))))
}
do {
let bytes = Data(data)
try yeetAppAFC(bundleIdentifier, bytes)
self.progress.completedUnitCount += 1
self.finish(.success(()))
} catch {
self.finish(.failure(MinimuxerError.RwAfc))
self.progress.completedUnitCount += 1
self.finish(.success(()))
}
}
}

View File

@@ -7,7 +7,6 @@
//
import Foundation
import AltStoreCore
private extension URL

View File

@@ -1,108 +0,0 @@
//
// UpdatePatronsOperation.swift
// AltStore
//
// Created by Riley Testut on 4/11/22.
// Copyright © 2022 Riley Testut. All rights reserved.
//
import Foundation
import CoreData
import AltStoreCore
private extension URL
{
#if STAGING
static let patreonInfo = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altstore/patreon.json")!
#else
static let patreonInfo = URL(string: "https://cdn.altstore.io/file/altstore/altstore/patreon.json")!
#endif
}
extension UpdatePatronsOperation
{
private struct Response: Decodable
{
var version: Int
var accessToken: String
var refreshID: String
}
}
final class UpdatePatronsOperation: ResultOperation<Void>
{
let context: NSManagedObjectContext
init(context: NSManagedObjectContext = DatabaseManager.shared.persistentContainer.newBackgroundContext())
{
self.context = context
}
override func main()
{
super.main()
let dataTask = URLSession.shared.dataTask(with: .patreonInfo) { (data, response, error) in
do
{
if let response = response as? HTTPURLResponse
{
guard response.statusCode != 404 else {
self.finish(.failure(URLError(.fileDoesNotExist, userInfo: [NSURLErrorKey: URL.patreonInfo])))
return
}
}
guard let data = data else { throw error! }
let response = try AltStoreCore.JSONDecoder().decode(Response.self, from: data)
Keychain.shared.patreonCreatorAccessToken = response.accessToken
let previousRefreshID = UserDefaults.shared.patronsRefreshID
guard response.refreshID != previousRefreshID else {
self.finish(.success(()))
return
}
PatreonAPI.shared.fetchPatrons { (result) in
self.context.perform {
do
{
let patrons = try result.get()
let managedPatrons = patrons.compactMap { ManagedPatron(patron: $0, context: self.context) }
let patronIDs = Set(managedPatrons.map { $0.identifier })
let nonFriendZonePredicate = NSPredicate(format: "NOT (%K IN %@)", #keyPath(ManagedPatron.identifier), patronIDs)
let nonFriendZonePatrons = ManagedPatron.all(satisfying: nonFriendZonePredicate, in: self.context)
for managedPatron in nonFriendZonePatrons
{
self.context.delete(managedPatron)
}
try self.context.save()
UserDefaults.shared.patronsRefreshID = response.refreshID
self.finish(.success(()))
Logger.main.notice("Updated Friend Zone Patrons! Refresh ID: \(response.refreshID, privacy: .public)")
}
catch let error as NSError
{
Logger.main.error("Failed to update Friend Zone Patrons. \(error.localizedDebugDescription ?? error.localizedDescription, privacy: .public)")
self.finish(.failure(error))
}
}
}
}
catch
{
self.finish(.failure(error))
}
}
dataTask.resume()
}
}

View File

@@ -8,7 +8,6 @@
import Foundation
import CryptoKit
import AltStoreCore
import AltSign
import Roxas

View File

@@ -1,281 +0,0 @@
//
// VerifyAppPledgeOperation.swift
// AltStore
//
// Created by Riley Testut on 12/6/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import Combine
import AltStoreCore
class VerifyAppPledgeOperation: ResultOperation<Void>
{
@AsyncManaged
private(set) var storeApp: StoreApp
private let presentingViewController: UIViewController?
private var openPatreonPageContinuation: CheckedContinuation<Void, Never>?
private var cancellable: AnyCancellable?
init(storeApp: StoreApp, presentingViewController: UIViewController?)
{
self.storeApp = storeApp
self.presentingViewController = presentingViewController
}
override func main()
{
super.main()
// _Don't_ rethrow earlier errors, or else user will only be taken to Patreon post if connected to same Wi-Fi as AltServer.
// if let error = self.context.error
// {
// self.finish(.failure(error))
// return
// }
Task<Void, Never>.detached(priority: .medium) {
do
{
guard await self.$storeApp.isPledgeRequired else { return self.finish(.success(())) }
if let presentingViewController = self.presentingViewController
{
// Ask user to connect Patreon account if they are signed-in to Patreon inside WebViewController, but haven't yet signed in through AltStore settings.
// This is most likely because the user joined a Patreon campaign directly through WebViewController before connecting Patreon account in settings.
try await self.connectPatreonAccountIfNeeded(presentingViewController: presentingViewController)
}
do
{
try await self.verifyPledge()
}
catch let error as OperationError where error.code == .pledgeRequired || error.code == .pledgeInactive
{
guard
let presentingViewController = self.presentingViewController,
let source = await self.$storeApp.source,
let patreonURL = await self.$storeApp.perform({ _ in source.patreonURL })
else { throw error }
let components = URLComponents(url: patreonURL, resolvingAgainstBaseURL: false)
let lastPathComponent = components?.path.components(separatedBy: "/").last
let username = lastPathComponent ?? patreonURL.lastPathComponent
let checkoutURL: URL
if await self.$storeApp.prefersCustomPledge, let customPledgeURL = URL(string: "https://www.patreon.com/checkout/" + username + "?rid=0&custom=1")
{
checkoutURL = customPledgeURL
let action = await UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default)
try await presentingViewController.presentConfirmationAlert(title: NSLocalizedString("Custom Pledge", comment: ""),
message: NSLocalizedString("This app supports custom pledges. Pledge any amount on Patreon to receive access.", comment: ""),
primaryAction: action)
}
else if !username.isEmpty, let url = URL(string: "https://www.patreon.com/join/" + username)
{
// Prefer /join URL over campaign homepage.
// URL format from https://support.patreon.com/hc/en-us/articles/360044376211-Managing-members-with-custom-pledges
checkoutURL = url
}
else
{
checkoutURL = patreonURL
}
// Direct user to Patreon page if they're not already pledged.
await self.openPatreonPage(checkoutURL, presentingViewController: presentingViewController)
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
if let patreonAccount = await context.performAsync({ DatabaseManager.shared.patreonAccount(in: context) })
{
// Patreon account is connected, so we'll update it via API to see if pledges changed.
// If so, we'll re-fetch the source to update pledge statuses.
try await self.updatePledges(for: source, account: patreonAccount)
}
else
{
// Patreon account is not connected, so prompt user to connect it.
try await self.connectPatreonAccountIfNeeded(presentingViewController: presentingViewController)
}
do
{
try await self.verifyPledge()
}
catch
{
// Ignore error, but cancel remainder of operation.
throw CancellationError()
}
}
self.finish(.success(()))
}
catch
{
self.finish(.failure(error))
}
}
}
}
private extension VerifyAppPledgeOperation
{
func verifyPledge() async throws
{
let (appName, isPledged) = await self.$storeApp.perform { ($0.name, $0.isPledged) }
if !PatreonAPI.shared.isAuthenticated || !isPledged
{
let isInstalled = await self.$storeApp.installedApp != nil
if isInstalled
{
// Assume if there is an InstalledApp, the user had previously pledged to this app.
throw OperationError.pledgeInactive(appName: appName)
}
else
{
throw OperationError.pledgeRequired(appName: appName)
}
}
}
func connectPatreonAccountIfNeeded(presentingViewController: UIViewController) async throws
{
guard !PatreonAPI.shared.isAuthenticated, let authCookie = PatreonAPI.shared.authCookies.first(where: { $0.name.lowercased() == "session_id" }) else { return }
Logger.sideload.debug("Patreon Auth cookie: \(authCookie.name)=\(authCookie.value)")
let message = NSLocalizedString("You're signed into Patreon but haven't connected your account with SideStore.\n\nPlease connect your account to download Patreon-exclusive apps.", comment: "")
let action = await UIAlertAction(title: NSLocalizedString("Connect Patreon Account", comment: ""), style: .default)
do
{
_ = try await presentingViewController.presentConfirmationAlert(title: NSLocalizedString("Patreon Account Detected", comment: ""),
message: message, actions: [action])
}
catch
{
// Ignore and continue
return
}
try await withCheckedThrowingContinuation { continuation in
PatreonAPI.shared.authenticate(presentingViewController: presentingViewController) { result in
do
{
let account = try result.get()
try account.managedObjectContext?.save()
continuation.resume()
}
catch
{
continuation.resume(throwing: error)
}
}
}
if let source = await self.$storeApp.source
{
// Fetch source to update pledge status now that account is connected.
try await self.update(source)
}
}
func updatePledges(@AsyncManaged for source: Source, @AsyncManaged account: PatreonAccount) async throws
{
guard PatreonAPI.shared.isAuthenticated else { return }
let previousPledgeIDs = Set(await $account.perform { $0.pledges.map(\.identifier) })
let updatedPledgeIDs = try await withCheckedThrowingContinuation { continuation in
PatreonAPI.shared.fetchAccount { (result: Result<PatreonAccount, Swift.Error>) in
do
{
let account = try result.get()
let pledgeIDs = Set(account.pledges.map(\.identifier))
try account.managedObjectContext?.save()
continuation.resume(returning: pledgeIDs)
}
catch
{
Logger.sideload.error("Failed to update Patreon account. \(error.localizedDescription, privacy: .public)")
continuation.resume(throwing: error)
}
}
}
if updatedPledgeIDs != previousPledgeIDs
{
// Active pledges changed, so fetch source to update pledge status.
try await self.update(source)
}
}
func update(@AsyncManaged _ source: Source) async throws
{
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
_ = try await AppManager.shared.fetchSource(sourceURL: $source.sourceURL, managedObjectContext: context)
try await context.performAsync {
try context.save()
}
}
@MainActor
func openPatreonPage(_ patreonURL: URL, presentingViewController: UIViewController) async
{
let webViewController = WebViewController(url: patreonURL)
webViewController.delegate = self
let navigationController = UINavigationController(rootViewController: webViewController)
presentingViewController.present(navigationController, animated: true)
// Automatically dismiss if user completes checkout flow.
self.cancellable = webViewController.webView.publisher(for: \.url, options: [.new])
.compactMap { $0 }
.compactMap { URLComponents(url: $0, resolvingAgainstBaseURL: false) }
.compactMap { components in
let lastPathComponent = components.path.components(separatedBy: "/").last
return lastPathComponent?.lowercased()
}
.filter { $0 == "membership" }
.receive(on: RunLoop.main)
.sink { [weak self] url in
guard let continuation = self?.openPatreonPageContinuation else { return }
self?.openPatreonPageContinuation = nil
continuation.resume()
}
await withCheckedContinuation { continuation in
self.openPatreonPageContinuation = continuation
}
// Cache auth cookies just in case user signed in.
await PatreonAPI.shared.saveAuthCookies()
navigationController.dismiss(animated: true)
self.cancellable = nil
}
}
extension VerifyAppPledgeOperation: WebViewControllerDelegate
{
func webViewControllerDidFinish(_ webViewController: WebViewController)
{
guard let continuation = self.openPatreonPageContinuation else { return }
self.openPatreonPageContinuation = nil
continuation.resume()
}
}

View File

@@ -5,7 +5,6 @@
// Created by Riley Testut on 5/23/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import AltStoreCore
@dynamicMemberLookup

View File

@@ -8,6 +8,8 @@
<key>name</key>
<string>Original</string>
<key>imageName</key>
<string>App</string>
<key>iconName</key>
<string>AppIcon</string>
</dict>
</array>
@@ -18,66 +20,88 @@
<string>Blue</string>
<key>imageName</key>
<string>Blue</string>
<key>iconName</key>
<string>BlueIcon</string>
</dict>
<dict>
<key>name</key>
<string>Dark</string>
<key>imageName</key>
<string>Dark</string>
<key>iconName</key>
<string>DarkIcon</string>
</dict>
<dict>
<key>name</key>
<string>Honeydew</string>
<key>imageName</key>
<string>Honeydew</string>
<key>iconName</key>
<string>HoneydewIcon</string>
</dict>
<dict>
<key>name</key>
<string>Pride</string>
<key>imageName</key>
<string>Pride</string>
<key>iconName</key>
<string>PrideIcon</string>
</dict>
<dict>
<key>name</key>
<string>Sandy</string>
<key>imageName</key>
<string>Sandy</string>
<key>iconName</key>
<string>SandyIcon</string>
</dict>
<dict>
<key>name</key>
<string>Sky</string>
<key>imageName</key>
<string>Sky</string>
<key>iconName</key>
<string>SkyIcon</string>
</dict>
<dict>
<key>name</key>
<string>Snow</string>
<key>imageName</key>
<string>Snow</string>
<key>iconName</key>
<string>SnowIcon</string>
</dict>
<dict>
<key>name</key>
<string>Starburst</string>
<key>imageName</key>
<string>Starburst</string>
<key>iconName</key>
<string>StarburstIcon</string>
</dict>
<dict>
<key>name</key>
<string>Storm</string>
<key>imageName</key>
<string>Storm</string>
<key>iconName</key>
<string>StormIcon</string>
</dict>
<dict>
<key>name</key>
<string>Vista</string>
<key>imageName</key>
<string>Vista</string>
<key>iconName</key>
<string>VistaIcon</string>
</dict>
<dict>
<key>name</key>
<string>Winter</string>
<key>imageName</key>
<string>Winter</string>
<key>iconName</key>
<string>WinterIcon</string>
</dict>
</array>
</dict>

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "App.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Blue.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

Before

Width:  |  Height:  |  Size: 313 KiB

After

Width:  |  Height:  |  Size: 313 KiB

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Honeydew.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Pride.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

Before

Width:  |  Height:  |  Size: 719 KiB

After

Width:  |  Height:  |  Size: 719 KiB

Some files were not shown because too many files have changed in this diff Show More