mirror of
https://github.com/SideStore/SideStore.git
synced 2026-04-02 08:45:41 +02:00
Compare commits
164 Commits
cb93168516
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b072e358a | ||
|
|
cc74be4b34 | ||
|
|
eabf9dbaaa | ||
|
|
bd75d62c7b | ||
|
|
75edfad132 | ||
|
|
84c5bf40ca | ||
|
|
01e73328f8 | ||
|
|
a1f71a8149 | ||
|
|
8624a8e919 | ||
|
|
dfdac41a70 | ||
|
|
62ad755920 | ||
|
|
baf1aa7fff | ||
|
|
395d2bb657 | ||
|
|
e32f4fbeae | ||
|
|
b643f6ae01 | ||
|
|
2f0fb3cb71 | ||
|
|
0f90f9bf2d | ||
|
|
1c8541fdd4 | ||
|
|
4e6882423d | ||
|
|
7c3add0e70 | ||
|
|
c4139473fa | ||
|
|
0837f82a69 | ||
|
|
c04ebd3ec6 | ||
|
|
0e81515af9 | ||
|
|
dd4c0be75d | ||
|
|
7500dd5b17 | ||
|
|
dba84ed015 | ||
|
|
dbb1e0ff3e | ||
|
|
431900368a | ||
|
|
8843a95bfc | ||
|
|
4810f3a624 | ||
|
|
954a96b988 | ||
|
|
afd32a285d | ||
|
|
f8a0ee2c84 | ||
|
|
9d01a1c116 | ||
|
|
f6db823816 | ||
|
|
dfb018bb89 | ||
|
|
adb91439e4 | ||
|
|
368a2afad4 | ||
|
|
830b66c364 | ||
|
|
40a2137069 | ||
|
|
fa752e3ae6 | ||
|
|
71b319c8e6 | ||
|
|
fd12d36980 | ||
|
|
cdc41c7954 | ||
|
|
1fe3e5a6ca | ||
|
|
bf65bce7b5 | ||
|
|
7f3647a230 | ||
|
|
850180273c | ||
|
|
6a20d50fb6 | ||
|
|
79dc65f1c8 | ||
|
|
a9a2d8780b | ||
|
|
2286e78a55 | ||
|
|
3aa3b5df65 | ||
|
|
6cdfc211ea | ||
|
|
58ff20c690 | ||
|
|
0b865729a7 | ||
|
|
7c4cf2ae62 | ||
|
|
b0c65b04f2 | ||
|
|
f3f8036693 | ||
|
|
6e9e0aee0a | ||
|
|
ee7c97fd3e | ||
|
|
f5eb669a74 | ||
|
|
3af3aa071c | ||
|
|
857c1e03e0 | ||
|
|
c09c290973 | ||
|
|
7b8884a8ac | ||
|
|
fd797fdf88 | ||
|
|
b18ce43a64 | ||
|
|
e90b8a0e32 | ||
|
|
bb578316f1 | ||
|
|
9579d46bed | ||
|
|
a916293585 | ||
|
|
f634b52e44 | ||
|
|
a6baf59649 | ||
|
|
a505d04215 | ||
|
|
95953ca0e9 | ||
|
|
3b9b45a06f | ||
|
|
ac277aa6eb | ||
|
|
7926452661 | ||
|
|
dfb01c2ae5 | ||
|
|
bff192be4e | ||
|
|
a863b2f39a | ||
|
|
bf72a0edfc | ||
|
|
06ad6488cc | ||
|
|
12f84b2365 | ||
|
|
118f64de8a | ||
|
|
555bb3d985 | ||
|
|
25925aceef | ||
|
|
5444fdd9bb | ||
|
|
f91e0a6295 | ||
|
|
4e4f0f6a3f | ||
|
|
9706a43bc1 | ||
|
|
d748a89b47 | ||
|
|
f412f6df23 | ||
|
|
b8354d3d0e | ||
|
|
191ce15d55 | ||
|
|
7f8f4fa9a7 | ||
|
|
ad54cbadc1 | ||
|
|
eb251b89c9 | ||
|
|
9efea00d09 | ||
|
|
7df29ca23b | ||
|
|
d06520d46f | ||
|
|
1ad0fe23fc | ||
|
|
21bbcd69f8 | ||
|
|
fd920be3bf | ||
|
|
16bb57c825 | ||
|
|
bf5b1c935c | ||
|
|
1b1c7c58e2 | ||
|
|
31e8eb7996 | ||
|
|
47db2c3d5d | ||
|
|
967b9f7572 | ||
|
|
b91bcee70f | ||
|
|
25f34c6f69 | ||
|
|
b7085aaeca | ||
|
|
046d2788b9 | ||
|
|
f44ed0a947 | ||
|
|
d356045b5d | ||
|
|
a54881a1c8 | ||
|
|
e27d44eece | ||
|
|
c2cecb63ac | ||
|
|
381e09d87c | ||
|
|
efbb40982e | ||
|
|
c1a033a627 | ||
|
|
1449f8c74f | ||
|
|
3961688b73 | ||
|
|
f0da9cf8aa | ||
|
|
aa224f68c7 | ||
|
|
a02d1c49e8 | ||
|
|
226f0dcc6b | ||
|
|
bce38c8743 | ||
|
|
0e72a33af8 | ||
|
|
0677cc287e | ||
|
|
b0bfbf5513 | ||
|
|
ea86b98674 | ||
|
|
3d47d486ef | ||
|
|
3a05485c40 | ||
|
|
31d07534d0 | ||
|
|
99712f0020 | ||
|
|
c5394be883 | ||
|
|
a07657261d | ||
|
|
db00202b37 | ||
|
|
b16dda5590 | ||
|
|
f8c4c558f6 | ||
|
|
ae1bd49a99 | ||
|
|
97b04094eb | ||
|
|
675bdc63ae | ||
|
|
8be9de3b11 | ||
|
|
0403dc3278 | ||
|
|
c546ff6642 | ||
|
|
dc058938ef | ||
|
|
4984e5119f | ||
|
|
bcadc92057 | ||
|
|
625389ab96 | ||
|
|
f7e34cbbe9 | ||
|
|
0fe8d7fed9 | ||
|
|
1a1aa42e02 | ||
|
|
7ff4b48223 | ||
|
|
4801f6e8f2 | ||
|
|
ff28f6fa8f | ||
|
|
2d141afbaf | ||
|
|
06e38aae00 | ||
|
|
d8783230a7 | ||
|
|
6c479bfede |
63
.github/maintenance/cache.py
vendored
63
.github/maintenance/cache.py
vendored
@@ -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
|
||||
'''
|
||||
207
.github/workflows/alpha.yml
vendored
207
.github/workflows/alpha.yml
vendored
@@ -2,7 +2,8 @@ name: Alpha SideStore Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [develop-alpha]
|
||||
branches: [staging]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
@@ -10,7 +11,13 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-15
|
||||
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
|
||||
@@ -18,30 +25,196 @@ jobs:
|
||||
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
|
||||
|
||||
- name: Shared
|
||||
id: shared
|
||||
run: python3 scripts/ci/workflow.py shared
|
||||
# --------------------------------------------------
|
||||
# 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)
|
||||
|
||||
- name: Beta bump
|
||||
env:
|
||||
RELEASE_CHANNEL: alpha
|
||||
run: python3 scripts/ci/workflow.py bump-beta
|
||||
NORMALIZED_VERSION=$(python3 scripts/ci/workflow.py compute-normalized \
|
||||
"$MARKETING_VERSION" \
|
||||
"$BUILD_NUM" \
|
||||
"$SHORT_COMMIT")
|
||||
|
||||
- name: Version
|
||||
id: version
|
||||
run: python3 scripts/ci/workflow.py version
|
||||
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
|
||||
run: python3 scripts/ci/workflow.py build
|
||||
|
||||
- name: Encrypt logs
|
||||
id: build
|
||||
env:
|
||||
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
|
||||
run: python3 scripts/ci/workflow.py encrypt-build
|
||||
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-${{ steps.version.outputs.version }}.ipa
|
||||
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"
|
||||
|
||||
192
.github/workflows/nightly.yml
vendored
192
.github/workflows/nightly.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
push:
|
||||
branches: [develop]
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -14,6 +14,12 @@ concurrency:
|
||||
jobs:
|
||||
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:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -21,69 +27,104 @@ jobs:
|
||||
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
|
||||
|
||||
- name: Check for new changes (on schedule)
|
||||
id: check_changes
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
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
|
||||
|
||||
- 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
|
||||
|
||||
- run: brew install ldid xcbeautify
|
||||
if: steps.build_gate.outputs.should_skip != 'true'
|
||||
|
||||
# --------------------------------------------------
|
||||
# runtime env setup
|
||||
# --------------------------------------------------
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'SideStore/beta-build-num'
|
||||
ref: ${{ env.ref }}
|
||||
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
|
||||
path: 'Dependencies/beta-build-num'
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup
|
||||
- name: Setup Env
|
||||
if: steps.build_gate.outputs.should_skip != 'true'
|
||||
run: |
|
||||
BUILD_NUM=$(python3 scripts/ci/workflow.py reserve_build_number 'Dependencies/beta-build-num')
|
||||
BUILD_NUM="${{ github.run_number }}"
|
||||
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
|
||||
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commid-id)
|
||||
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commit-id)
|
||||
|
||||
QUALIFIED_VERSION=$(python3 scripts/ci/workflow.py compute-qualified \
|
||||
NORMALIZED_VERSION=$(python3 scripts/ci/workflow.py compute-normalized \
|
||||
"$MARKETING_VERSION" \
|
||||
"$BUILD_NUM" \
|
||||
"${{ env.ref }}" \
|
||||
"$SHORT_COMMIT")
|
||||
|
||||
echo "BUILD_NUM=$BUILD_NUM" >> $GITHUB_ENV
|
||||
echo "MARKETING_VERSION=$MARKETING_VERSION" >> $GITHUB_ENV
|
||||
echo "SHORT_COMMIT=$SHORT_COMMIT" >> $GITHUB_ENV
|
||||
echo "VERSION=$QUALIFIED_VERSION" >> $GITHUB_ENV
|
||||
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'
|
||||
xcode-version: "26.2"
|
||||
|
||||
- name: Restore Cache
|
||||
id: xcode-cache
|
||||
- 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 }}
|
||||
restore-keys: |
|
||||
xcode-build-cache-${{ github.ref_name }}-
|
||||
|
||||
- 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: contains(github.event.head_commit.message, '[--clean-build]')
|
||||
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: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||
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:
|
||||
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
|
||||
@@ -94,8 +135,11 @@ jobs:
|
||||
exit $STATUS
|
||||
|
||||
- name: Tests Build
|
||||
if: >
|
||||
steps.build_gate.outputs.should_skip != 'true' &&
|
||||
vars.ENABLE_TESTS == '1' &&
|
||||
vars.ENABLE_TESTS_BUILD == '1'
|
||||
id: test-build
|
||||
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
|
||||
env:
|
||||
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
|
||||
run: |
|
||||
@@ -104,7 +148,9 @@ jobs:
|
||||
exit $STATUS
|
||||
|
||||
- name: Save Cache
|
||||
if: ${{ steps.xcode-cache.outputs.cache-hit != 'true' }}
|
||||
if: >
|
||||
steps.build_gate.outputs.should_skip != 'true' &&
|
||||
steps.xcode-cache-fallback.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: |
|
||||
@@ -113,8 +159,11 @@ jobs:
|
||||
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
|
||||
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||
env:
|
||||
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
|
||||
run: |
|
||||
@@ -126,43 +175,86 @@ jobs:
|
||||
# artifacts
|
||||
# --------------------------------------------------
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: steps.build_gate.outputs.should_skip != 'true'
|
||||
with:
|
||||
name: encrypted-build-logs-${{ env.VERSION }}.zip
|
||||
path: encrypted-build-logs.zip
|
||||
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' }}
|
||||
if: >
|
||||
steps.build_gate.outputs.should_skip != 'true' &&
|
||||
vars.ENABLE_TESTS == '1' &&
|
||||
vars.ENABLE_TESTS_BUILD == '1'
|
||||
with:
|
||||
name: encrypted-tests-build-logs-${{ env.SHORT_COMMIT }}.zip
|
||||
path: encrypted-tests-build-logs.zip
|
||||
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' }}
|
||||
if: >
|
||||
steps.build_gate.outputs.should_skip != 'true' &&
|
||||
vars.ENABLE_TESTS == '1' &&
|
||||
vars.ENABLE_TESTS_RUN == '1'
|
||||
with:
|
||||
name: encrypted-tests-run-logs-${{ env.SHORT_COMMIT }}.zip
|
||||
path: encrypted-tests-run-logs.zip
|
||||
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.VERSION }}.ipa
|
||||
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.VERSION }}-dSYMs.zip
|
||||
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
|
||||
path: SideStore.dSYMs.zip
|
||||
|
||||
# --------------------------------------------------
|
||||
# deploy
|
||||
# --------------------------------------------------
|
||||
- name: Deploy
|
||||
- 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 deploy \
|
||||
Dependencies/apps-v2.json \
|
||||
"_includes/source.json" \
|
||||
"${{ env.ref_name }}" \
|
||||
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" \
|
||||
"$VERSION" \
|
||||
"${{ env.ref_name }}" \
|
||||
"com.SideStore.SideStore" \
|
||||
"SideStore.ipa"
|
||||
"$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"
|
||||
|
||||
28
.github/workflows/obsolete/alpha.yml
vendored
28
.github/workflows/obsolete/alpha.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: Alpha SideStore build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop-alpha
|
||||
|
||||
# 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 }}
|
||||
103
.github/workflows/obsolete/beta.yml
vendored
103
.github/workflows/obsolete/beta.yml
vendored
@@ -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/
|
||||
@@ -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
|
||||
82
.github/workflows/obsolete/nightly.yml
vendored
82
.github/workflows/obsolete/nightly.yml
vendored
@@ -1,82 +0,0 @@
|
||||
name: Nightly SideStore Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Runs every night at midnight UTC
|
||||
workflow_dispatch: # Allows manual trigger
|
||||
|
||||
# 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 }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Ensure full history
|
||||
|
||||
- name: Get last successful workflow run
|
||||
id: get_last_success
|
||||
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 }}
|
||||
|
||||
- name: Check for new commits since last successful build
|
||||
id: check
|
||||
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
|
||||
|
||||
echo "Has changes: $NEW_COMMITS"
|
||||
echo "New commits since last successful build:"
|
||||
echo "$COMMIT_LOG"
|
||||
|
||||
if [ "$NEW_COMMITS" -gt 0 ]; then
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
LAST_SUCCESS: ${{ env.last_success }}
|
||||
|
||||
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 }}
|
||||
|
||||
@@ -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
|
||||
358
.github/workflows/obsolete/sidestore-build.yml
vendored
358
.github/workflows/obsolete/sidestore-build.yml
vendored
@@ -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
|
||||
281
.github/workflows/obsolete/sidestore-deploy.yml
vendored
281
.github/workflows/obsolete/sidestore-deploy.yml
vendored
@@ -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
|
||||
24
.github/workflows/obsolete/sidestore-shared.yml
vendored
24
.github/workflows/obsolete/sidestore-shared.yml
vendored
@@ -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 }}
|
||||
165
.github/workflows/obsolete/sidestore-tests-build.yml
vendored
165
.github/workflows/obsolete/sidestore-tests-build.yml
vendored
@@ -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
|
||||
196
.github/workflows/obsolete/sidestore-tests-run.yml
vendored
196
.github/workflows/obsolete/sidestore-tests-run.yml
vendored
@@ -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
|
||||
132
.github/workflows/pr.yml
vendored
132
.github/workflows/pr.yml
vendored
@@ -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
|
||||
|
||||
261
.github/workflows/stable.yml
vendored
261
.github/workflows/stable.yml
vendored
@@ -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
3
.gitignore
vendored
@@ -70,3 +70,6 @@ test-recording.mp4
|
||||
test-recording.log
|
||||
altstore-sources.md
|
||||
local-build.sh
|
||||
|
||||
source-metadata.json
|
||||
release-notes.md
|
||||
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -54,10 +54,6 @@
|
||||
path = Dependencies/em_proxy
|
||||
url = https://github.com/SideStore/em_proxy
|
||||
branch = master
|
||||
[submodule "Dependencies/libfragmentzip"]
|
||||
path = Dependencies/libfragmentzip
|
||||
url = https://github.com/SideStore/libfragmentzip
|
||||
branch = master
|
||||
[submodule "Dependencies/apps-v2.json"]
|
||||
path = Dependencies/apps-v2.json
|
||||
url = https://github.com/SideStore/apps-v2.json
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "2610"
|
||||
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 = "CA60C44C93D7A30E3695DD59"
|
||||
BuildableName = "libem_proxy_static.a"
|
||||
BlueprintName = "em_proxy-staticlib"
|
||||
ReferencedContainer = "container:Dependencies/em_proxy.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 = "CA60C44C93D7A30E3695DD59"
|
||||
BuildableName = "libem_proxy_static.a"
|
||||
BlueprintName = "em_proxy-staticlib"
|
||||
ReferencedContainer = "container:Dependencies/em_proxy.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "2620"
|
||||
LastUpgradeVersion = "2630"
|
||||
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 = "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 = "CA60C44C93D7A30E3695DD59"
|
||||
BuildableName = "libem_proxy_static.a"
|
||||
BlueprintName = "em_proxy-staticlib"
|
||||
ReferencedContainer = "container:em_proxy.xcodeproj">
|
||||
BlueprintIdentifier = "A85A51412F4B4532002E2E11"
|
||||
BuildableName = "libem_proxy_swift.a"
|
||||
BlueprintName = "em_proxy-swift"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
@@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "2610"
|
||||
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 = "CA609C732349A560B9642892"
|
||||
BuildableName = "libminimuxer_static.a"
|
||||
BlueprintName = "minimuxer-staticlib"
|
||||
ReferencedContainer = "container:Dependencies/minimuxer.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 = "CA609C732349A560B9642892"
|
||||
BuildableName = "libminimuxer_static.a"
|
||||
BlueprintName = "minimuxer-staticlib"
|
||||
ReferencedContainer = "container:Dependencies/minimuxer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
3
AltStore.xcworkspace/contents.xcworkspacedata
generated
3
AltStore.xcworkspace/contents.xcworkspacedata
generated
@@ -7,6 +7,9 @@
|
||||
<FileRef
|
||||
location = "group:Dependencies/AltSign">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Dependencies/minimuxer">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Dependencies/Roxas/Roxas.xcodeproj">
|
||||
</FileRef>
|
||||
|
||||
@@ -3,6 +3,3 @@
|
||||
//
|
||||
|
||||
#import "NSAttributedString+Markdown.h"
|
||||
#import "ALTAppPatcher.h"
|
||||
|
||||
#include "fragmentzip.h"
|
||||
|
||||
@@ -92,7 +92,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
self.setTintColor()
|
||||
self.setTintColor()
|
||||
self.prepareImageCache()
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
import Nuke
|
||||
import Minimuxer
|
||||
|
||||
class BrowseViewController: UICollectionViewController, PeekPopPreviewing
|
||||
{
|
||||
|
||||
@@ -368,7 +368,7 @@ private extension FeaturedViewController
|
||||
#keyPath(StoreApp._source._apps),
|
||||
#keyPath(StoreApp.bundleIdentifier),
|
||||
StoreApp.altstoreAppID,
|
||||
#keyPath(StoreApp.installedApp),
|
||||
#keyPath(StoreApp.installedApp)
|
||||
)
|
||||
|
||||
let primaryFetchRequest = fetchRequest.copy() as! NSFetchRequest<StoreApp>
|
||||
|
||||
@@ -87,7 +87,7 @@ final class LaunchViewController: UIViewController, UIDocumentPickerDelegate {
|
||||
}
|
||||
|
||||
func start_minimuxer_threads(_ pairing_file: String) {
|
||||
targetMinimuxerAddress()
|
||||
retargetUsbmuxdAddr()
|
||||
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
|
||||
do {
|
||||
let loggingEnabled = UserDefaults.standard.isMinimuxerConsoleLoggingEnabled
|
||||
@@ -96,7 +96,7 @@ final class LaunchViewController: UIViewController, UIDocumentPickerDelegate {
|
||||
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) }
|
||||
|
||||
@@ -16,6 +16,7 @@ import WidgetKit
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
import Minimuxer
|
||||
|
||||
extension AppManager
|
||||
{
|
||||
@@ -941,69 +942,6 @@ extension AppManager
|
||||
self.run([enableJITOperation], context: context, requiresSerialQueue: true)
|
||||
}
|
||||
|
||||
func patch(resignedApp: ALTApplication, presentingViewController: UIViewController, context authContext: AuthenticatedOperationContext, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> PatchAppOperation
|
||||
{
|
||||
final class Context: InstallAppOperationContext, PatchAppContext
|
||||
{
|
||||
}
|
||||
|
||||
guard let originalBundleID = resignedApp.bundle.infoDictionary?[Bundle.Info.altBundleID] as? String else {
|
||||
let context = Context(bundleIdentifier: resignedApp.bundleIdentifier, authenticatedContext: authContext)
|
||||
completionHandler(.failure(OperationError.invalidApp))
|
||||
|
||||
return PatchAppOperation(context: context)
|
||||
}
|
||||
|
||||
let context = Context(bundleIdentifier: originalBundleID, authenticatedContext: authContext)
|
||||
context.resignedApp = resignedApp
|
||||
|
||||
let patchAppOperation = PatchAppOperation(context: context)
|
||||
let sendAppOperation = SendAppOperation(context: context)
|
||||
let installOperation = InstallAppOperation(context: context)
|
||||
|
||||
let installationProgress = Progress.discreteProgress(totalUnitCount: 100)
|
||||
installationProgress.addChild(sendAppOperation.progress, withPendingUnitCount: 40)
|
||||
installationProgress.addChild(installOperation.progress, withPendingUnitCount: 60)
|
||||
|
||||
/* Patch */
|
||||
patchAppOperation.resultHandler = { [weak patchAppOperation] (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
context.error = error
|
||||
case .success:
|
||||
// Kinda hacky that we're calling patchAppOperation's progressHandler manually, but YOLO.
|
||||
patchAppOperation?.progressHandler?(installationProgress, NSLocalizedString("Patching placeholder app...", comment: ""))
|
||||
}
|
||||
}
|
||||
|
||||
/* Send */
|
||||
sendAppOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
context.error = error
|
||||
completionHandler(.failure(error))
|
||||
case .success(_): print("App sent over AFC")
|
||||
}
|
||||
}
|
||||
sendAppOperation.addDependency(patchAppOperation)
|
||||
|
||||
|
||||
/* Install */
|
||||
installOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): completionHandler(.failure(error))
|
||||
case .success(let installedApp): completionHandler(.success(installedApp))
|
||||
}
|
||||
}
|
||||
installOperation.addDependency(sendAppOperation)
|
||||
|
||||
self.run([patchAppOperation, sendAppOperation, installOperation], context: context.authenticatedContext)
|
||||
return patchAppOperation
|
||||
}
|
||||
|
||||
func installationProgress(for app: AppProtocol) -> Progress?
|
||||
{
|
||||
os_unfair_lock_lock(self.progressLock)
|
||||
@@ -1391,80 +1329,6 @@ private extension AppManager
|
||||
}
|
||||
deactivateAppsOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||
|
||||
/* Patch App */
|
||||
let patchAppOperation = RSTAsyncBlockOperation { operation in
|
||||
do
|
||||
{
|
||||
// Only attempt to patch app if we're installing a new app, not refreshing existing app.
|
||||
// Post reboot, we install the correct jailbreak app by refreshing the patched app,
|
||||
// so this check avoids infinite recursion.
|
||||
guard case .install = appOperation else {
|
||||
operation.finish()
|
||||
return
|
||||
}
|
||||
|
||||
guard let presentingViewController = context.presentingViewController else { return operation.finish() }
|
||||
|
||||
if let error = context.error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let app = context.app else {
|
||||
throw OperationError.invalidParameters("AppManager._install.patchAppOperation: context.app is nil")
|
||||
}
|
||||
|
||||
guard let isUntetherRequired = app.bundle.infoDictionary?[Bundle.Info.untetherRequired] as? Bool,
|
||||
let minimumiOSVersionString = app.bundle.infoDictionary?[Bundle.Info.untetherMinimumiOSVersion] as? String,
|
||||
let maximumiOSVersionString = app.bundle.infoDictionary?[Bundle.Info.untetherMaximumiOSVersion] as? String,
|
||||
case let minimumiOSVersion = OperatingSystemVersion(string: minimumiOSVersionString),
|
||||
case let maximumiOSVersion = OperatingSystemVersion(string: maximumiOSVersionString)
|
||||
else { return operation.finish() }
|
||||
|
||||
let iOSVersion = ProcessInfo.processInfo.operatingSystemVersion
|
||||
let iOSVersionSupported = ProcessInfo.processInfo.isOperatingSystemAtLeast(minimumiOSVersion) &&
|
||||
(!ProcessInfo.processInfo.isOperatingSystemAtLeast(maximumiOSVersion) || maximumiOSVersion == iOSVersion)
|
||||
|
||||
guard isUntetherRequired, iOSVersionSupported, UIDevice.current.supportsFugu14 else { return operation.finish() }
|
||||
|
||||
guard let patchAppLink = app.bundle.infoDictionary?[Bundle.Info.untetherURL] as? String,
|
||||
let patchAppURL = URL(string: patchAppLink)
|
||||
else { throw OperationError.invalidApp }
|
||||
|
||||
let patchApp = AnyApp(name: app.name, bundleIdentifier: context.bundleIdentifier, url: patchAppURL, storeApp: nil)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let storyboard = UIStoryboard(name: "PatchApp", bundle: nil)
|
||||
let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
|
||||
|
||||
let patchViewController = navigationController.topViewController as! PatchViewController
|
||||
patchViewController.patchApp = patchApp
|
||||
patchViewController.completionHandler = { [weak presentingViewController] (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(OperationError.cancelled): break // Ignore
|
||||
case .failure(let error): group.context.error = error
|
||||
case .success: group.context.error = OperationError.cancelled
|
||||
}
|
||||
|
||||
operation.finish()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
presentingViewController.present(navigationController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
group.context.error = error
|
||||
operation.finish()
|
||||
}
|
||||
}
|
||||
patchAppOperation.addDependency(deactivateAppsOperation)
|
||||
|
||||
|
||||
let modifyAppExBundleIdOperation = RSTAsyncBlockOperation { operation in
|
||||
if !context.useMainProfile {
|
||||
operation.finish()
|
||||
@@ -1496,7 +1360,7 @@ private extension AppManager
|
||||
self.exportResginedAppsToDocsDir(resignedApp)
|
||||
}
|
||||
}
|
||||
resignAppOperation.addDependency(patchAppOperation)
|
||||
resignAppOperation.addDependency(deactivateAppsOperation)
|
||||
resignAppOperation.addDependency(modifyAppExBundleIdOperation)
|
||||
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
||||
|
||||
@@ -1547,7 +1411,6 @@ private extension AppManager
|
||||
verifyOperation,
|
||||
removeAppExtensionsOperation,
|
||||
deactivateAppsOperation,
|
||||
patchAppOperation,
|
||||
refreshAnisetteDataOperation,
|
||||
fetchProvisioningProfilesOperation,
|
||||
modifyAppExBundleIdOperation,
|
||||
|
||||
@@ -701,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))
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
||||
if UserDefaults.standard.enableEMPforWireguard {
|
||||
startEMProxy(bind_addr: AppConstants.Proxy.serverURL)
|
||||
}
|
||||
targetMinimuxerAddress()
|
||||
retargetUsbmuxdAddr()
|
||||
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
|
||||
do {
|
||||
// enable minimuxer console logging only if enabled in settings
|
||||
@@ -118,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 {
|
||||
|
||||
@@ -42,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))
|
||||
|
||||
@@ -88,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 {
|
||||
@@ -105,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
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
|
||||
let shortcutURLonDelay = URL(string: "shortcuts://run-shortcut?name=TurnOnDataDelay")!
|
||||
|
||||
@objc(InstallAppOperation)
|
||||
final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
{
|
||||
@@ -176,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 {
|
||||
@@ -204,6 +213,11 @@ 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")
|
||||
// Cell Shortcut
|
||||
if self.context.shouldTurnOffData {
|
||||
UIApplication.shared.open(shortcutURLonDelay, options: [:]) { _ in
|
||||
print("Cell OFF Shortcut finished execution.")}
|
||||
}
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
}))
|
||||
|
||||
@@ -220,6 +234,10 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cell Shortcut
|
||||
if self.context.shouldTurnOffData {
|
||||
UIApplication.shared.open(shortcutURLonDelay, options: [:]) { _ in print("Cell OFF Shortcut finished execution.")}
|
||||
}
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,8 @@ class InstallAppOperationContext: AppOperationContext
|
||||
|
||||
var alternateIconURL: URL?
|
||||
|
||||
var shouldTurnOffData: Bool = false
|
||||
|
||||
// Non-nil when installing from a source.
|
||||
@AsyncManaged
|
||||
var appVersion: AppVersion?
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// ALTAppPatcher.h
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 10/18/21.
|
||||
// Copyright © 2021 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ALTAppPatcher : NSObject
|
||||
|
||||
- (BOOL)patchAppBinaryAtURL:(NSURL *)appFileURL withBinaryAtURL:(NSURL *)patchFileURL error:(NSError *_Nullable *)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,143 +0,0 @@
|
||||
//
|
||||
// ALTAppPatcher.m
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 10/18/21.
|
||||
// Copied with minor modifications from sample code provided by Linus Henze.
|
||||
//
|
||||
|
||||
#import "ALTAppPatcher.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@import Roxas;
|
||||
|
||||
#define CPU_SUBTYPE_PAC 0x80000000
|
||||
#define FAT_MAGIC 0xcafebabe
|
||||
|
||||
#define ROUND_TO_PAGE(val) (((val % 0x4000) == 0) ? val : (val + (0x4000 - (val & 0x3FFF))))
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t cpuType;
|
||||
uint32_t cpuSubType;
|
||||
// Incomplete, we don't need anything else
|
||||
} MachOHeader;
|
||||
|
||||
typedef struct {
|
||||
uint32_t cpuType;
|
||||
uint32_t cpuSubType;
|
||||
uint32_t fileOffset;
|
||||
uint32_t size;
|
||||
uint32_t alignment;
|
||||
} FatArch;
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t archCount;
|
||||
FatArch archs[0];
|
||||
} FatHeader;
|
||||
|
||||
// Given two MachO files, return a FAT file with the following properties:
|
||||
// 1. installd will still see the original MachO and validate it's code signature
|
||||
// 2. The kernel will only see the injected MachO instead
|
||||
//
|
||||
// Only arm64e for now
|
||||
void *injectApp(void *originalApp, size_t originalAppSize, void *appToInject, size_t appToInjectSize, size_t *outputSize) {
|
||||
*outputSize = 0;
|
||||
|
||||
// First validate the App to inject: It must be an arm64e application
|
||||
if (appToInjectSize < sizeof(MachOHeader)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MachOHeader *injectedHeader = (MachOHeader*) appToInject;
|
||||
if (injectedHeader->cpuType != CPU_TYPE_ARM64) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (injectedHeader->cpuSubType != (CPU_SUBTYPE_ARM64E | CPU_SUBTYPE_PAC)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Ok, the App to inject is ok
|
||||
// Now build a fat header
|
||||
size_t originalAppSizeRounded = ROUND_TO_PAGE(originalAppSize);
|
||||
size_t appToInjectSizeRounded = ROUND_TO_PAGE(appToInjectSize);
|
||||
size_t totalSize = 0x4000 /* Fat Header + Alignment */ + originalAppSizeRounded + appToInjectSizeRounded;
|
||||
|
||||
void *fatBuf = malloc(totalSize);
|
||||
if (fatBuf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bzero(fatBuf, totalSize);
|
||||
|
||||
FatHeader *fatHeader = (FatHeader*) fatBuf;
|
||||
fatHeader->magic = htonl(FAT_MAGIC);
|
||||
fatHeader->archCount = htonl(2);
|
||||
|
||||
// Write first arch (original app)
|
||||
fatHeader->archs[0].cpuType = htonl(CPU_TYPE_ARM64);
|
||||
fatHeader->archs[0].cpuSubType = htonl(CPU_SUBTYPE_ARM64E); /* Note that this is not a valid cpu subtype */
|
||||
fatHeader->archs[0].fileOffset = htonl(0x4000);
|
||||
fatHeader->archs[0].size = htonl(originalAppSize);
|
||||
fatHeader->archs[0].alignment = htonl(0xE);
|
||||
|
||||
// Write second arch (injected app)
|
||||
fatHeader->archs[1].cpuType = htonl(CPU_TYPE_ARM64);
|
||||
fatHeader->archs[1].cpuSubType = htonl(CPU_SUBTYPE_ARM64E | CPU_SUBTYPE_PAC);
|
||||
fatHeader->archs[1].fileOffset = htonl(0x4000 + originalAppSizeRounded);
|
||||
fatHeader->archs[1].size = htonl(appToInjectSize);
|
||||
fatHeader->archs[1].alignment = htonl(0xE);
|
||||
|
||||
// Ok, now write the MachOs
|
||||
memcpy(fatBuf + 0x4000, originalApp, originalAppSize);
|
||||
memcpy(fatBuf + 0x4000 + originalAppSizeRounded, appToInject, appToInjectSize);
|
||||
|
||||
// We're done!
|
||||
*outputSize = totalSize;
|
||||
return fatBuf;
|
||||
}
|
||||
|
||||
@implementation ALTAppPatcher
|
||||
|
||||
- (BOOL)patchAppBinaryAtURL:(NSURL *)appFileURL withBinaryAtURL:(NSURL *)patchFileURL error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSMutableData *originalApp = [NSMutableData dataWithContentsOfURL:appFileURL options:0 error:error];
|
||||
if (originalApp == nil)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSMutableData *injectedApp = [NSMutableData dataWithContentsOfURL:patchFileURL options:0 error:error];
|
||||
if (injectedApp == nil)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
size_t outputSize = 0;
|
||||
void *output = injectApp(originalApp.mutableBytes, originalApp.length, injectedApp.mutableBytes, injectedApp.length, &outputSize);
|
||||
if (output == NULL)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
// If injectApp fails, it means the patch app is in the wrong format.
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileReadCorruptFileError userInfo:@{NSURLErrorKey: patchFileURL}];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSData *outputData = [NSData dataWithBytesNoCopy:output length:outputSize freeWhenDone:YES];
|
||||
if (![outputData writeToURL:appFileURL options:NSDataWritingAtomic error:error])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,124 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="WBb-E1-bN8">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="dx2-fp-qDX">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="WBb-E1-bN8" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" id="cVa-8m-fW6" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="barTintColor" name="SettingsBackground"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="idH-XF-rK8" kind="relationship" relationship="rootViewController" id="hSJ-tL-4nB"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="i7K-pi-SRe" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="137.68115942028987" y="137.94642857142856"/>
|
||||
</scene>
|
||||
<!--Patch View Controller-->
|
||||
<scene sceneID="gJ4-4F-79r">
|
||||
<objects>
|
||||
<viewController id="idH-XF-rK8" customClass="PatchViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="4bV-S5-z7S">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RHK-C5-7wu" customClass="RSTPlaceholderView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="PjB-Dc-9n3">
|
||||
<rect key="frame" x="20" y="736.5" width="374" height="117.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" " textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GQF-6P-Fit">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" name="Text"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SNn-Ad-ICf" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28.5" width="374" height="51"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="51" id="s4X-uf-nl9"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
|
||||
<color key="tintColor" name="SettingsHighlighted"/>
|
||||
<state key="normal" title="Install Untethered Jailbreak">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="performButtonAction" destination="idH-XF-rK8" eventType="primaryActionTriggered" id="FxO-1Y-IML"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="leJ-09-giz">
|
||||
<rect key="frame" x="0.0" y="87.5" width="374" height="30"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<state key="normal" title="Install Without Untethering">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="installRegularJailbreak" destination="idH-XF-rK8" eventType="primaryActionTriggered" id="1XB-11-Kdn"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="H6g-xA-DdL"/>
|
||||
<color key="backgroundColor" name="SettingsBackground"/>
|
||||
<constraints>
|
||||
<constraint firstItem="RHK-C5-7wu" firstAttribute="top" secondItem="4bV-S5-z7S" secondAttribute="top" id="CVK-6E-iA6"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="PjB-Dc-9n3" secondAttribute="trailing" id="GOg-JU-LIP"/>
|
||||
<constraint firstItem="RHK-C5-7wu" firstAttribute="bottom" secondItem="4bV-S5-z7S" secondAttribute="bottom" id="LPh-J8-IVx"/>
|
||||
<constraint firstItem="PjB-Dc-9n3" firstAttribute="leading" secondItem="4bV-S5-z7S" secondAttribute="leadingMargin" id="Rlg-PC-5ZN"/>
|
||||
<constraint firstItem="RHK-C5-7wu" firstAttribute="trailing" secondItem="H6g-xA-DdL" secondAttribute="trailing" id="XdZ-36-6yS"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="PjB-Dc-9n3" secondAttribute="bottom" id="hTS-nX-0xv"/>
|
||||
<constraint firstItem="RHK-C5-7wu" firstAttribute="leading" secondItem="H6g-xA-DdL" secondAttribute="leading" id="lzV-fG-Xv6"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" largeTitleDisplayMode="always" id="0J1-80-RD8">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="vtw-PQ-Dk1">
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<action selector="cancel" destination="idH-XF-rK8" id="4Wk-dv-RYG"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
<connections>
|
||||
<outlet property="cancelBarButtonItem" destination="vtw-PQ-Dk1" id="8Mh-GU-KD5"/>
|
||||
<outlet property="cancelButton" destination="leJ-09-giz" id="BNh-I3-vXc"/>
|
||||
<outlet property="pillButton" destination="SNn-Ad-ICf" id="iJg-TC-p8q"/>
|
||||
<outlet property="placeholderView" destination="RHK-C5-7wu" id="5x0-sg-HAH"/>
|
||||
<outlet property="taskDescriptionLabel" destination="GQF-6P-Fit" id="C4c-xy-kvU"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="8ev-19-qsi" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1001" y="138"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<namedColor name="SettingsBackground">
|
||||
<color red="0.0039215686274509803" green="0.50196078431372548" blue="0.51764705882352946" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<namedColor name="SettingsHighlighted">
|
||||
<color red="0.0080000003799796104" green="0.32199999690055847" blue="0.40400001406669617" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<namedColor name="Text">
|
||||
<color red="1" green="1" blue="1" alpha="0.75" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -1,257 +0,0 @@
|
||||
//
|
||||
// PatchAppOperation.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 10/13/21.
|
||||
// Copyright © 2021 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import AppleArchive
|
||||
import System
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
|
||||
protocol PatchAppContext
|
||||
{
|
||||
var bundleIdentifier: String { get }
|
||||
var temporaryDirectory: URL { get }
|
||||
|
||||
var resignedApp: ALTApplication? { get }
|
||||
var error: Error? { get }
|
||||
}
|
||||
|
||||
extension PatchAppError
|
||||
{
|
||||
enum Code: Int, ALTErrorCode, CaseIterable {
|
||||
typealias Error = PatchAppError
|
||||
|
||||
case unsupportedOperatingSystemVersion
|
||||
}
|
||||
|
||||
static func unsupportedOperatingSystemVersion(_ osVersion: OperatingSystemVersion) -> PatchAppError {
|
||||
PatchAppError(code: .unsupportedOperatingSystemVersion, osVersion: osVersion)
|
||||
}
|
||||
}
|
||||
|
||||
struct PatchAppError: ALTLocalizedError {
|
||||
let code: Code
|
||||
|
||||
var errorTitle: String?
|
||||
var errorFailure: String?
|
||||
|
||||
var osVersion: OperatingSystemVersion?
|
||||
|
||||
var errorFailureReason: String {
|
||||
switch self.code {
|
||||
case .unsupportedOperatingSystemVersion:
|
||||
let osVersionString: String
|
||||
|
||||
if let osVersion = self.osVersion?.stringValue {
|
||||
osVersionString = NSLocalizedString("iOS", comment: "") + " " + osVersion
|
||||
} else {
|
||||
osVersionString = NSLocalizedString("your device's iOS version", comment: "")
|
||||
}
|
||||
return String(format: NSLocalizedString("The OTA download URL for %@ could not be determined.", comment: ""), osVersionString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct OTAUpdate
|
||||
{
|
||||
var url: URL
|
||||
var archivePath: String
|
||||
}
|
||||
|
||||
@available(iOS 14, *)
|
||||
final class PatchAppOperation: ResultOperation<Void>
|
||||
{
|
||||
let context: PatchAppContext
|
||||
|
||||
var progressHandler: ((Progress, String) -> Void)?
|
||||
|
||||
private let appPatcher = ALTAppPatcher()
|
||||
private lazy var patchDirectory: URL = self.context.temporaryDirectory.appendingPathComponent("Patch", isDirectory: true)
|
||||
|
||||
private var cancellable: AnyCancellable?
|
||||
|
||||
init(context: PatchAppContext)
|
||||
{
|
||||
self.context = context
|
||||
|
||||
super.init()
|
||||
|
||||
self.progress.totalUnitCount = 100
|
||||
}
|
||||
|
||||
override func main()
|
||||
{
|
||||
super.main()
|
||||
|
||||
if let error = self.context.error
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
guard let resignedApp = self.context.resignedApp else {
|
||||
return self.finish(.failure(OperationError.invalidParameters("PatchAppOperation.main: self.context.resignedApp is nil")))
|
||||
}
|
||||
|
||||
self.progressHandler?(self.progress, NSLocalizedString("Downloading iOS firmware...", comment: ""))
|
||||
|
||||
self.cancellable = self.fetchOTAUpdate()
|
||||
.flatMap { self.downloadArchive(from: $0) }
|
||||
.flatMap { self.extractSpotlightFromArchive(at: $0) }
|
||||
.flatMap { self.patch(resignedApp, withBinaryAt: $0) }
|
||||
.tryMap { try FileManager.default.zipAppBundle(at: $0) }
|
||||
.tryMap { (fileURL) in
|
||||
let app = AnyApp(name: resignedApp.name, bundleIdentifier: self.context.bundleIdentifier, url: resignedApp.fileURL, storeApp: nil)
|
||||
|
||||
let destinationURL = InstalledApp.refreshedIPAURL(for: app)
|
||||
try FileManager.default.copyItem(at: fileURL, to: destinationURL, shouldReplace: true)
|
||||
}
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { completion in
|
||||
switch completion
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .finished: self.finish(.success(()))
|
||||
}
|
||||
} receiveValue: { _ in }
|
||||
}
|
||||
|
||||
override func cancel()
|
||||
{
|
||||
super.cancel()
|
||||
|
||||
self.cancellable?.cancel()
|
||||
self.cancellable = nil
|
||||
}
|
||||
}
|
||||
|
||||
private let ALTFragmentZipCallback: @convention(c) (UInt32) -> Void = { (percentageComplete) in
|
||||
guard let progress = Progress.current() else { return }
|
||||
|
||||
if percentageComplete == 100 && progress.completedUnitCount == 0
|
||||
{
|
||||
// Ignore first percentageComplete, which is always 100.
|
||||
return
|
||||
}
|
||||
|
||||
progress.completedUnitCount = Int64(percentageComplete)
|
||||
}
|
||||
|
||||
private extension PatchAppOperation
|
||||
{
|
||||
func fetchOTAUpdate() -> AnyPublisher<OTAUpdate, Error>
|
||||
{
|
||||
Just(()).tryMap {
|
||||
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
|
||||
switch (osVersion.majorVersion, osVersion.minorVersion)
|
||||
{
|
||||
case (14, 3):
|
||||
return OTAUpdate(url: URL(string: "https://updates.cdn-apple.com/2020WinterFCS/patches/001-87330/99E29969-F6B6-422A-B946-70DE2E2D73BE/com_apple_MobileAsset_SoftwareUpdate/67f9e42f5e57a20e0a87eaf81b69dd2a61311d3f.zip")!,
|
||||
archivePath: "AssetData/payloadv2/payload.042")
|
||||
|
||||
case (14, 4):
|
||||
return OTAUpdate(url: URL(string: "https://updates.cdn-apple.com/2021WinterFCS/patches/001-98606/43AF99A1-F286-43B1-A101-F9F856EA395A/com_apple_MobileAsset_SoftwareUpdate/c4985c32c344beb7b49c61919b4e39d1fd336c90.zip")!,
|
||||
archivePath: "AssetData/payloadv2/payload.042")
|
||||
|
||||
case (14, 5):
|
||||
return OTAUpdate(url: URL(string: "https://updates.cdn-apple.com/2021SpringFCS/patches/061-84483/AB525139-066E-46F8-8E85-DCE802C03BA8/com_apple_MobileAsset_SoftwareUpdate/788573ae93113881db04269acedeecabbaa643e3.zip")!,
|
||||
archivePath: "AssetData/payloadv2/payload.043")
|
||||
|
||||
default: throw PatchAppError.unsupportedOperatingSystemVersion(osVersion)
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func downloadArchive(from update: OTAUpdate) -> AnyPublisher<URL, Error>
|
||||
{
|
||||
Just(()).tryMap {
|
||||
#if targetEnvironment(simulator)
|
||||
throw PatchAppError.unsupportedOperatingSystemVersion(ProcessInfo.processInfo.operatingSystemVersion)
|
||||
#else
|
||||
|
||||
try FileManager.default.createDirectory(at: self.patchDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
let archiveURL = self.patchDirectory.appendingPathComponent("ota.archive")
|
||||
try archiveURL.withUnsafeFileSystemRepresentation { archivePath in
|
||||
guard let fz = fragmentzip_open((update.url.absoluteString as NSString).utf8String!) else {
|
||||
throw URLError(.cannotConnectToHost, userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("The connection failed because a connection cannot be made to the host.", comment: ""),
|
||||
NSURLErrorKey: update.url])
|
||||
}
|
||||
defer { fragmentzip_close(fz) }
|
||||
|
||||
self.progress.becomeCurrent(withPendingUnitCount: 100)
|
||||
defer { self.progress.resignCurrent() }
|
||||
|
||||
guard fragmentzip_download_file(fz, update.archivePath, archivePath!, ALTFragmentZipCallback) == 0 else {
|
||||
throw URLError(.networkConnectionLost, userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("The connection failed because the network connection was lost.", comment: ""),
|
||||
NSURLErrorKey: update.url])
|
||||
}
|
||||
}
|
||||
|
||||
Logger.fugu14.notice("Downloaded iOS OTA archive.")
|
||||
return archiveURL
|
||||
|
||||
#endif
|
||||
}
|
||||
.mapError { ($0 as NSError).withLocalizedFailure(NSLocalizedString("Could not download OTA archive.", comment: "")) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func extractSpotlightFromArchive(at archiveURL: URL) -> AnyPublisher<URL, Error>
|
||||
{
|
||||
Just(()).tryMap {
|
||||
#if targetEnvironment(simulator)
|
||||
throw PatchAppError.unsupportedOperatingSystemVersion(ProcessInfo.processInfo.operatingSystemVersion)
|
||||
#else
|
||||
|
||||
let spotlightPath = "Applications/Spotlight.app/Spotlight"
|
||||
let spotlightFileURL = self.patchDirectory.appendingPathComponent(spotlightPath)
|
||||
|
||||
guard let readFileStream = ArchiveByteStream.fileStream(path: FilePath(archiveURL.path), mode: .readOnly, options: [], permissions: FilePermissions(rawValue: 0o644)),
|
||||
let decompressStream = ArchiveByteStream.decompressionStream(readingFrom: readFileStream),
|
||||
let decodeStream = ArchiveStream.decodeStream(readingFrom: decompressStream),
|
||||
let readStream = ArchiveStream.extractStream(extractingTo: FilePath(self.patchDirectory.path))
|
||||
else { throw CocoaError(.fileReadCorruptFile, userInfo: [NSURLErrorKey: archiveURL]) }
|
||||
|
||||
_ = try ArchiveStream.process(readingFrom: decodeStream, writingTo: readStream) { message, filePath, data in
|
||||
guard filePath == FilePath(spotlightPath) else { return .skip }
|
||||
return .ok
|
||||
}
|
||||
|
||||
Logger.fugu14.notice("Extracted Spotlight from OTA archive.")
|
||||
return spotlightFileURL
|
||||
|
||||
#endif
|
||||
}
|
||||
.mapError { ($0 as NSError).withLocalizedFailure(NSLocalizedString("Could not extract Spotlight from OTA archive.", comment: "")) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func patch(_ app: ALTApplication, withBinaryAt patchFileURL: URL) -> AnyPublisher<URL, Error>
|
||||
{
|
||||
Just(()).tryMap {
|
||||
// executableURL may be nil, so use infoDictionary instead to determine executable name.
|
||||
// guard let appName = app.bundle.executableURL?.lastPathComponent else { throw OperationError.invalidApp }
|
||||
guard let appName = app.bundle.infoDictionary?[kCFBundleExecutableKey as String] as? String else { throw OperationError.invalidApp }
|
||||
|
||||
let temporaryAppURL = self.patchDirectory.appendingPathComponent("Patched.app", isDirectory: true)
|
||||
try FileManager.default.copyItem(at: app.fileURL, to: temporaryAppURL)
|
||||
|
||||
let appBinaryURL = temporaryAppURL.appendingPathComponent(appName, isDirectory: false)
|
||||
try self.appPatcher.patchAppBinary(at: appBinaryURL, withBinaryAt: patchFileURL)
|
||||
|
||||
Logger.fugu14.notice("Patched \(app.name, privacy: .public)!")
|
||||
return temporaryAppURL
|
||||
}
|
||||
.mapError { ($0 as NSError).withLocalizedFailure(String(format: NSLocalizedString("Could not patch %@ placeholder.", comment: ""), app.name)) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
@@ -1,494 +0,0 @@
|
||||
//
|
||||
// PatchViewController.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 10/20/21.
|
||||
// Copyright © 2021 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
|
||||
extension PatchViewController
|
||||
{
|
||||
enum Step
|
||||
{
|
||||
case confirm
|
||||
case install
|
||||
case openApp
|
||||
case patchApp
|
||||
case reboot
|
||||
case refresh
|
||||
case finish
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
final class PatchViewController: UIViewController
|
||||
{
|
||||
var patchApp: AnyApp?
|
||||
var installedApp: InstalledApp?
|
||||
|
||||
var completionHandler: ((Result<Void, Error>) -> Void)?
|
||||
|
||||
private let context = AuthenticatedOperationContext()
|
||||
|
||||
private var currentStep: Step = .confirm {
|
||||
didSet {
|
||||
DispatchQueue.main.async {
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var buttonHandler: (() -> Void)?
|
||||
private var resignedApp: ALTApplication?
|
||||
|
||||
private lazy var temporaryDirectory: URL = FileManager.default.uniqueTemporaryURL()
|
||||
|
||||
private var didEnterBackgroundObservation: NSObjectProtocol?
|
||||
private weak var cancellableProgress: Progress?
|
||||
|
||||
@IBOutlet private var placeholderView: RSTPlaceholderView!
|
||||
@IBOutlet private var taskDescriptionLabel: UILabel!
|
||||
@IBOutlet private var pillButton: PillButton!
|
||||
@IBOutlet private var cancelBarButtonItem: UIBarButtonItem!
|
||||
@IBOutlet private var cancelButton: UIButton!
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.isModalInPresentation = true
|
||||
|
||||
self.placeholderView.stackView.spacing = 20
|
||||
self.placeholderView.textLabel.textColor = .white
|
||||
|
||||
self.placeholderView.detailTextLabel.textAlignment = .left
|
||||
self.placeholderView.detailTextLabel.textColor = UIColor.white.withAlphaComponent(0.6)
|
||||
|
||||
self.buttonHandler = { [weak self] in
|
||||
self?.startProcess()
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
try FileManager.default.createDirectory(at: self.temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.fugu14.error("Failed to create temporary directory \(self.temporaryDirectory.lastPathComponent, privacy: .public). \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
self.update()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool)
|
||||
{
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if self.installedApp != nil
|
||||
{
|
||||
self.refreshApp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension PatchViewController
|
||||
{
|
||||
func update()
|
||||
{
|
||||
self.cancelButton.alpha = 0.0
|
||||
|
||||
switch self.currentStep
|
||||
{
|
||||
case .confirm:
|
||||
guard let app = self.patchApp else { break }
|
||||
|
||||
if UIDevice.current.isUntetheredJailbreakRequired
|
||||
{
|
||||
self.placeholderView.textLabel.text = NSLocalizedString("Jailbreak Requires Untethering", comment: "")
|
||||
self.placeholderView.detailTextLabel.text = String(format: NSLocalizedString("This jailbreak is untethered, which means %@ will never expire — even after 7 days or rebooting the device.\n\nInstalling an untethered jailbreak requires a few extra steps, but SideStore will walk you through the process.\n\nWould you like to continue? ", comment: ""), app.name)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.placeholderView.textLabel.text = NSLocalizedString("Jailbreak Supports Untethering", comment: "")
|
||||
self.placeholderView.detailTextLabel.text = String(format: NSLocalizedString("This jailbreak has an untethered version, which means %@ will never expire — even after 7 days or rebooting the device.\n\nInstalling an untethered jailbreak requires a few extra steps, but SideStore will walk you through the process.\n\nWould you like to continue? ", comment: ""), app.name)
|
||||
}
|
||||
|
||||
self.pillButton.setTitle(NSLocalizedString("Install Untethered Jailbreak", comment: ""), for: .normal)
|
||||
|
||||
self.cancelButton.alpha = 1.0
|
||||
|
||||
case .install:
|
||||
guard let app = self.patchApp else { break }
|
||||
|
||||
self.placeholderView.textLabel.text = String(format: NSLocalizedString("Installing %@ placeholder…", comment: ""), app.name)
|
||||
self.placeholderView.detailTextLabel.text = NSLocalizedString("A placeholder app needs to be installed in order to prepare your device for untethering.\n\nThis may take a few moments.", comment: "")
|
||||
|
||||
case .openApp:
|
||||
self.placeholderView.textLabel.text = NSLocalizedString("Continue in App", comment: "")
|
||||
self.placeholderView.detailTextLabel.text = NSLocalizedString("Please open the placeholder app and follow the instructions to continue jailbreaking your device.", comment: "")
|
||||
|
||||
self.pillButton.setTitle(NSLocalizedString("Open Placeholder", comment: ""), for: .normal)
|
||||
|
||||
case .patchApp:
|
||||
guard let app = self.patchApp else { break }
|
||||
|
||||
self.placeholderView.textLabel.text = String(format: NSLocalizedString("Patching %@ placeholder…", comment: ""), app.name)
|
||||
self.placeholderView.detailTextLabel.text = NSLocalizedString("This will take a few moments. Please do not turn off the screen or leave the app until patching is complete.", comment: "")
|
||||
|
||||
self.pillButton.setTitle(NSLocalizedString("Patch Placeholder", comment: ""), for: .normal)
|
||||
|
||||
case .reboot:
|
||||
self.placeholderView.textLabel.text = NSLocalizedString("Continue in App", comment: "")
|
||||
self.placeholderView.detailTextLabel.text = NSLocalizedString("Please open the placeholder app and follow the instructions to continue jailbreaking your device.", comment: "")
|
||||
|
||||
self.pillButton.setTitle(NSLocalizedString("Open Placeholder", comment: ""), for: .normal)
|
||||
|
||||
case .refresh:
|
||||
guard let installedApp = self.installedApp else { break }
|
||||
|
||||
self.placeholderView.textLabel.text = String(format: NSLocalizedString("Finish installing %@?", comment: ""), installedApp.name)
|
||||
self.placeholderView.detailTextLabel.text = String(format: NSLocalizedString("In order to finish jailbreaking this device, you need to install %@ then follow the instructions in the app.", comment: ""), installedApp.name)
|
||||
|
||||
self.pillButton.setTitle(String(format: NSLocalizedString("Install %@", comment: ""), installedApp.name), for: .normal)
|
||||
|
||||
case .finish:
|
||||
guard let installedApp = self.installedApp else { break }
|
||||
|
||||
self.placeholderView.textLabel.text = String(format: NSLocalizedString("Finish in %@", comment: ""), installedApp.name)
|
||||
self.placeholderView.detailTextLabel.text = String(format: NSLocalizedString("Follow the instructions in %@ to finish jailbreaking this device.", comment: ""), installedApp.name)
|
||||
|
||||
self.pillButton.setTitle(String(format: NSLocalizedString("Open %@", comment: ""), installedApp.name), for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
func present(_ error: Error, title: String)
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
let nsError = error as NSError
|
||||
|
||||
let alertController = UIAlertController(title: nsError.localizedFailure ?? title, message: error.localizedDescription, preferredStyle: .alert)
|
||||
alertController.addAction(.ok)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
|
||||
self.setProgress(nil, description: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func setProgress(_ progress: Progress?, description: String?)
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
self.pillButton.progress = progress
|
||||
self.taskDescriptionLabel.text = description ?? " " // Use non-empty string to prevent label resizing itself.
|
||||
}
|
||||
}
|
||||
|
||||
func finish(with result: Result<Void, Error>)
|
||||
{
|
||||
do
|
||||
{
|
||||
try FileManager.default.removeItem(at: self.temporaryDirectory)
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.fugu14.error("Failed to remove temporary directory \(self.temporaryDirectory.lastPathComponent, privacy: .public). \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
if let observation = self.didEnterBackgroundObservation
|
||||
{
|
||||
NotificationCenter.default.removeObserver(observation)
|
||||
}
|
||||
|
||||
self.completionHandler?(result)
|
||||
self.completionHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
private extension PatchViewController
|
||||
{
|
||||
@IBAction func performButtonAction()
|
||||
{
|
||||
self.buttonHandler?()
|
||||
}
|
||||
|
||||
@IBAction func cancel()
|
||||
{
|
||||
self.finish(with: .success(()))
|
||||
|
||||
self.cancellableProgress?.cancel()
|
||||
}
|
||||
|
||||
@IBAction func installRegularJailbreak()
|
||||
{
|
||||
guard let app = self.patchApp else { return }
|
||||
|
||||
let title: String
|
||||
let message: String
|
||||
|
||||
if UIDevice.current.isUntetheredJailbreakRequired
|
||||
{
|
||||
title = NSLocalizedString("Untethering Required", comment: "")
|
||||
message = String(format: NSLocalizedString("%@ can not jailbreak this device unless you untether it first. Are you sure you want to install without untethering?", comment: ""), app.name)
|
||||
}
|
||||
else
|
||||
{
|
||||
title = NSLocalizedString("Untethering Recommended", comment: "")
|
||||
message = String(format: NSLocalizedString("Untethering this jailbreak will prevent %@ from expiring, even after 7 days or rebooting the device. Are you sure you want to install without untethering?", comment: ""), app.name)
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Install Without Untethering", comment: ""), style: .default) { _ in
|
||||
self.finish(with: .failure(OperationError.cancelled))
|
||||
})
|
||||
alertController.addAction(.cancel)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private extension PatchViewController
|
||||
{
|
||||
func startProcess()
|
||||
{
|
||||
guard let patchApp = self.patchApp else { return }
|
||||
|
||||
self.currentStep = .install
|
||||
|
||||
if let progress = AppManager.shared.installationProgress(for: patchApp)
|
||||
{
|
||||
// Cancel pending jailbreak app installation so we can start a new one.
|
||||
progress.cancel()
|
||||
}
|
||||
|
||||
let appURL = InstalledApp.fileURL(for: patchApp)
|
||||
let cachedAppURL = self.temporaryDirectory.appendingPathComponent("Cached.app")
|
||||
|
||||
do
|
||||
{
|
||||
// Make copy of original app, so we can replace the cached patch app with it later.
|
||||
try FileManager.default.copyItem(at: appURL, to: cachedAppURL, shouldReplace: true)
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.present(error, title: NSLocalizedString("Could not back up jailbreak app.", comment: ""))
|
||||
return
|
||||
}
|
||||
|
||||
var unzippingError: Error?
|
||||
let refreshGroup = AppManager.shared.install(patchApp, presentingViewController: self, context: self.context) { result in
|
||||
do
|
||||
{
|
||||
_ = try result.get()
|
||||
|
||||
if let unzippingError = unzippingError
|
||||
{
|
||||
throw unzippingError
|
||||
}
|
||||
|
||||
// Replace cached patch app with original app so we can resume installing it post-reboot.
|
||||
try FileManager.default.copyItem(at: cachedAppURL, to: appURL, shouldReplace: true)
|
||||
|
||||
self.openApp()
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.present(error, title: String(format: NSLocalizedString("Could not install %@ placeholder.", comment: ""), patchApp.name))
|
||||
}
|
||||
}
|
||||
refreshGroup.beginInstallationHandler = { (installedApp) in
|
||||
do
|
||||
{
|
||||
// Replace patch app name with correct name.
|
||||
installedApp.name = patchApp.name
|
||||
|
||||
let ipaURL = installedApp.refreshedIPAURL
|
||||
let resignedAppURL = try FileManager.default.unzipAppBundle(at: ipaURL, toDirectory: self.temporaryDirectory)
|
||||
|
||||
self.resignedApp = ALTApplication(fileURL: resignedAppURL)
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.fugu14.error("Error unzipping app bundle: \(error.localizedDescription, privacy: .public)")
|
||||
unzippingError = error
|
||||
}
|
||||
}
|
||||
self.setProgress(refreshGroup.progress, description: nil)
|
||||
|
||||
self.cancellableProgress = refreshGroup.progress
|
||||
}
|
||||
|
||||
func openApp()
|
||||
{
|
||||
guard let patchApp = self.patchApp else { return }
|
||||
|
||||
self.setProgress(nil, description: nil)
|
||||
self.currentStep = .openApp
|
||||
|
||||
// This observation is willEnterForeground because patching starts immediately upon return.
|
||||
self.didEnterBackgroundObservation = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { (notification) in
|
||||
self.didEnterBackgroundObservation.map { NotificationCenter.default.removeObserver($0) }
|
||||
self.patchApplication()
|
||||
}
|
||||
|
||||
self.buttonHandler = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
|
||||
let openURL = InstalledApp.openAppURL(for: patchApp)
|
||||
UIApplication.shared.open(openURL) { success in
|
||||
guard !success else { return }
|
||||
self.present(OperationError.openAppFailed(name: patchApp.name), title: String(format: NSLocalizedString("Could not open %@ placeholder.", comment: ""), patchApp.name))
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
func patchApplication()
|
||||
{
|
||||
guard let resignedApp = self.resignedApp else { return }
|
||||
|
||||
self.currentStep = .patchApp
|
||||
|
||||
self.buttonHandler = { [weak self] in
|
||||
self?.patchApplication()
|
||||
}
|
||||
|
||||
let patchAppOperation = AppManager.shared.patch(resignedApp: resignedApp, presentingViewController: self, context: self.context) { result in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.present(error, title: String(format: NSLocalizedString("Could not patch %@ placeholder.", comment: ""), resignedApp.name))
|
||||
case .success: self.rebootDevice()
|
||||
}
|
||||
}
|
||||
patchAppOperation.progressHandler = { (progress, description) in
|
||||
self.setProgress(progress, description: description)
|
||||
}
|
||||
self.cancellableProgress = patchAppOperation.progress
|
||||
}
|
||||
|
||||
func rebootDevice()
|
||||
{
|
||||
guard let patchApp = self.patchApp else { return }
|
||||
|
||||
self.setProgress(nil, description: nil)
|
||||
self.currentStep = .reboot
|
||||
|
||||
self.didEnterBackgroundObservation = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { (notification) in
|
||||
self.didEnterBackgroundObservation.map { NotificationCenter.default.removeObserver($0) }
|
||||
|
||||
var patchedApps = UserDefaults.standard.patchedApps ?? []
|
||||
if !patchedApps.contains(patchApp.bundleIdentifier)
|
||||
{
|
||||
patchedApps.append(patchApp.bundleIdentifier)
|
||||
UserDefaults.standard.patchedApps = patchedApps
|
||||
}
|
||||
|
||||
self.finish(with: .success(()))
|
||||
}
|
||||
|
||||
self.buttonHandler = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
|
||||
let openURL = InstalledApp.openAppURL(for: patchApp)
|
||||
UIApplication.shared.open(openURL) { success in
|
||||
guard !success else { return }
|
||||
self.present(OperationError.openAppFailed(name: patchApp.name), title: String(format: NSLocalizedString("Could not open %@ placeholder.", comment: ""), patchApp.name))
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
func refreshApp()
|
||||
{
|
||||
guard let installedApp = self.installedApp else { return }
|
||||
|
||||
self.currentStep = .refresh
|
||||
|
||||
self.buttonHandler = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||
let tempApp = context.object(with: installedApp.objectID) as! InstalledApp
|
||||
tempApp.needsResign = true
|
||||
|
||||
let errorTitle = String(format: NSLocalizedString("Could not install %@.", comment: ""), tempApp.name)
|
||||
|
||||
do
|
||||
{
|
||||
try context.save()
|
||||
|
||||
installedApp.managedObjectContext?.perform {
|
||||
// Refreshing ensures we don't attempt to patch the app again,
|
||||
// since that is only checked when installing a new app.
|
||||
let refreshGroup = AppManager.shared.refresh([installedApp], presentingViewController: self, group: nil)
|
||||
refreshGroup.completionHandler = { [weak refreshGroup, weak self] (results) in
|
||||
guard let self = self else { return }
|
||||
|
||||
do
|
||||
{
|
||||
guard let (bundleIdentifier, result) = results.first else { throw refreshGroup?.context.error ?? OperationError.unknown() }
|
||||
_ = try result.get()
|
||||
|
||||
if var patchedApps = UserDefaults.standard.patchedApps, let index = patchedApps.firstIndex(of: bundleIdentifier)
|
||||
{
|
||||
patchedApps.remove(at: index)
|
||||
UserDefaults.standard.patchedApps = patchedApps
|
||||
}
|
||||
|
||||
self.finish()
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.present(error, title: errorTitle)
|
||||
}
|
||||
}
|
||||
self.setProgress(refreshGroup.progress, description: String(format: NSLocalizedString("Installing %@...", comment: ""), installedApp.name))
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.present(error, title: errorTitle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func finish()
|
||||
{
|
||||
guard let installedApp = self.installedApp else { return }
|
||||
|
||||
self.setProgress(nil, description: nil)
|
||||
self.currentStep = .finish
|
||||
|
||||
self.didEnterBackgroundObservation = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { (notification) in
|
||||
self.didEnterBackgroundObservation.map { NotificationCenter.default.removeObserver($0) }
|
||||
self.finish(with: .success(()))
|
||||
}
|
||||
|
||||
installedApp.managedObjectContext?.perform {
|
||||
let appName = installedApp.name
|
||||
let openURL = installedApp.openAppURL
|
||||
|
||||
self.buttonHandler = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
|
||||
UIApplication.shared.open(openURL) { success in
|
||||
guard !success else { return }
|
||||
self.present(OperationError.openAppFailed(name: appName), title: String(format: NSLocalizedString("Could not open %@.", comment: ""), appName))
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// fragmentzip.h
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 10/25/21.
|
||||
// Copyright © 2021 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef fragmentzip_h
|
||||
#define fragmentzip_h
|
||||
|
||||
typedef void fragmentzip_t;
|
||||
typedef void (*fragmentzip_process_callback_t)(unsigned int progress);
|
||||
fragmentzip_t *fragmentzip_open(const char *url);
|
||||
int fragmentzip_download_file(fragmentzip_t *info, const char *remotepath, const char *savepath, fragmentzip_process_callback_t callback);
|
||||
void fragmentzip_close(fragmentzip_t *info);
|
||||
|
||||
#endif /* fragmentzip_h */
|
||||
@@ -11,6 +11,8 @@ import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
|
||||
import Minimuxer
|
||||
|
||||
@objc(RefreshAppOperation)
|
||||
final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
{
|
||||
@@ -46,7 +48,6 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
for p in profiles {
|
||||
do {
|
||||
let bytes =
|
||||
try installProvisioningProfiles(p.value.data)
|
||||
} catch {
|
||||
self.finish(.failure(MinimuxerError.ProfileInstall))
|
||||
|
||||
@@ -201,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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import Foundation
|
||||
import Network
|
||||
import AltStoreCore
|
||||
import Minimuxer
|
||||
|
||||
@objc(SendAppOperation)
|
||||
final class SendAppOperation: ResultOperation<()>
|
||||
@@ -25,12 +26,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))
|
||||
}
|
||||
|
||||
@@ -38,16 +37,46 @@ 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) {
|
||||
// only when minimuxer is not ready and below 26.4 should we turn off data
|
||||
if #available(iOS 26.4, *) {
|
||||
context.shouldTurnOffData = false
|
||||
} else if !isMinimuxerReady {
|
||||
context.shouldTurnOffData = true
|
||||
} else {
|
||||
context.shouldTurnOffData = false
|
||||
}
|
||||
|
||||
if context.shouldTurnOffData {
|
||||
// Wait for Shortcut to Finish Before Proceeding
|
||||
UIApplication.shared.open(shortcutURLoff, options: [:]) { _ in
|
||||
print("Shortcut finished execution. Proceeding with file transfer.")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
self.processFile(at: fileURL, for: app.bundleIdentifier)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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????")
|
||||
return self.finish(.failure(OperationError(.appNotFound(name: bundleIdentifier))))
|
||||
}
|
||||
|
||||
do {
|
||||
let bytes = Data(data)
|
||||
try yeetAppAFC(app.bundleIdentifier, bytes)
|
||||
try yeetAppAFC(bundleIdentifier, bytes)
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} catch {
|
||||
@@ -55,9 +84,5 @@ final class SendAppOperation: ResultOperation<()>
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
}
|
||||
} else {
|
||||
print("IPA doesn't exist????")
|
||||
self.finish(.failure(OperationError(.appNotFound(name: resignedApp.name))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
AltStore/Resources/AltBackup.ipa
Symbolic link
1
AltStore/Resources/AltBackup.ipa
Symbolic link
@@ -0,0 +1 @@
|
||||
../../build/AltBackup.ipa
|
||||
@@ -22,7 +22,7 @@
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<stackView key="tableFooterView" opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalCentering" alignment="center" spacing="15" id="48g-cT-stR">
|
||||
<rect key="frame" x="0.0" y="2442.6666641235352" width="402" height="125"/>
|
||||
<rect key="frame" x="0.0" y="2469.3333282470703" width="402" height="125"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="900" text="Follow SideStore for updates" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XFa-MY-7cV">
|
||||
@@ -494,7 +494,7 @@
|
||||
<tableViewSection id="1fc-f1-ALD">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="7Ek-Ls-QVO" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="793.66666412353516" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="794.66666412353516" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="7Ek-Ls-QVO" id="KjD-M3-oNg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -531,13 +531,13 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="hFh-X1-ZAi" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="844.66666412353516" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="845.66666412353516" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="hFh-X1-ZAi" id="nCs-Ro-A6t">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Clear Cache…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="j4e-Mz-DlL">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Clear Cache…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="j4e-Mz-DlL">
|
||||
<rect key="frame" x="29.999999999999993" y="15.333333333333334" width="114.33333333333331" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -563,28 +563,28 @@
|
||||
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="931.33333015441895" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="932.33333015441895" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Developers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hRA-OK-Vjw">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Developers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hRA-OK-Vjw">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="86" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
|
||||
<rect key="frame" x="217" y="15.333333333333336" width="155" height="20.333333333333329"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
|
||||
<rect key="frame" x="217" y="15.333333333333334" width="155" height="20.333333333333329"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="SideStore Team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore Team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="125.33333333333333" height="20.333333333333332"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
|
||||
<rect key="frame" x="139.33333333333331" y="-1" width="15.666666666666657" height="22.333333333333332"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -608,29 +608,29 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="982.33333015441895" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="983.33333015441895" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oHX-oR-nwJ" id="hN4-i5-igu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="UI Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oqY-wY-1Vf">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UI Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oqY-wY-1Vf">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="89" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="gUq-6Q-t5X">
|
||||
<rect key="frame" x="227.33333333333337" y="15.333333333333336" width="144.66666666666663" height="20.333333333333329"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="gUq-6Q-t5X">
|
||||
<rect key="frame" x="227.33333333333337" y="15.333333333333334" width="144.66666666666663" height="20.333333333333329"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Fabian (thdev)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ylE-VL-7Fq">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fabian (thdev)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ylE-VL-7Fq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="115" height="20.333333333333332"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="e3L-vR-Jae">
|
||||
<rect key="frame" x="129" y="-1" width="15.666666666666657" height="22.333333333333332"/>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="e3L-vR-Jae">
|
||||
<rect key="frame" x="128.99999999999997" y="-1" width="15.666666666666657" height="22.333333333333332"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -653,29 +653,29 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1033.3333301544189" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1034.3333301544189" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Asset Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fGU-Fp-XgM">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Asset Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fGU-Fp-XgM">
|
||||
<rect key="frame" x="29.999999999999993" y="15.333333333333334" width="115.33333333333331" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="R8B-DW-7mY">
|
||||
<rect key="frame" x="235.33333333333337" y="15.333333333333336" width="136.66666666666663" height="20.333333333333329"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="R8B-DW-7mY">
|
||||
<rect key="frame" x="235.33333333333337" y="15.333333333333334" width="136.66666666666663" height="20.333333333333329"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Chris (LitRitt)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hId-3P-41T">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Chris (LitRitt)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hId-3P-41T">
|
||||
<rect key="frame" x="0.0" y="0.0" width="107" height="20.333333333333332"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
|
||||
<rect key="frame" x="120.99999999999997" y="-1" width="15.666666666666657" height="22.333333333333332"/>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
|
||||
<rect key="frame" x="120.99999999999999" y="-1" width="15.666666666666671" height="22.333333333333332"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -698,19 +698,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1084.3333301544189" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1085.3333301544189" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Licenses" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D6b-cd-pVK">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Licenses" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D6b-cd-pVK">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="67.333333333333329" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="s79-GQ-khr">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="s79-GQ-khr">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -739,19 +739,19 @@
|
||||
<tableViewSection headerTitle="" id="swj-Wc-IR6">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="xGq-wV-SCd" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1170.9999961853027" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1171.9999961853027" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="xGq-wV-SCd" id="G7G-sK-oO3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Enable Beta Updates" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1B5-BJ-Rkb">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable Beta Updates" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1B5-BJ-Rkb">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="169" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Blb-Dp-9QF">
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Blb-Dp-9QF">
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleEnableBetaUpdates:" destination="aMk-Xp-UL8" eventType="valueChanged" id="9Ea-BQ-DAE"/>
|
||||
@@ -774,19 +774,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="eHa-Cd-p4h" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1221.9999961853027" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1222.9999961853027" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="eHa-Cd-p4h" id="V9s-7b-vkR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Beta Updates Track" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nSh-L8-ca0" userLabel="Beta Track Label">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Beta Updates Track" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nSh-L8-ca0" userLabel="Beta Track Label">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="159.66666666666666" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" ambiguous="YES" showsMenuAsPrimaryAction="YES" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" changesSelectionAsPrimaryAction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kYQ-zz-vjQ" userLabel="Beta Track Drop Down Button">
|
||||
<button opaque="NO" contentMode="scaleToFill" showsMenuAsPrimaryAction="YES" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" changesSelectionAsPrimaryAction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kYQ-zz-vjQ" userLabel="Beta Track Drop Down Button">
|
||||
<rect key="frame" x="301.66666666666669" y="8.3333333333333321" width="70.333333333333314" height="34.333333333333343"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="50" id="n1r-LA-2uh"/>
|
||||
@@ -815,19 +815,19 @@
|
||||
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1308.6666622161865" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1309.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Send Feedback" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMI-Aj-nQF">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Send Feedback" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMI-Aj-nQF">
|
||||
<rect key="frame" x="29.999999999999993" y="15.333333333333334" width="125.33333333333331" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -849,19 +849,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1359.6666622161865" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1360.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="187.66666666666666" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -886,19 +886,19 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VrV-qI-zXF" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1410.6666622161865" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1411.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VrV-qI-zXF" id="w1r-uY-4pD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="SideJITServer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="46q-DB-5nc">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideJITServer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="46q-DB-5nc">
|
||||
<rect key="frame" x="29.999999999999993" y="15.333333333333334" width="115.33333333333331" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wvD-eZ-nQI">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="wvD-eZ-nQI">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -920,19 +920,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1461.6666622161865" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1462.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="140" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -954,19 +954,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1512.6666622161865" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1513.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Anisette Servers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Anisette Servers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="135.66666666666666" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -987,20 +987,54 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="LEH-wv-o2q" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1564.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="LEH-wv-o2q" id="AP9-UO-vZk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="VPN Configuration" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iie-Nj-lki" userLabel="VPN Configuration">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="151.66666666666666" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="639-RC-1ii">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="iie-Nj-lki" firstAttribute="centerY" secondItem="AP9-UO-vZk" secondAttribute="centerY" id="Mxs-Yx-TRW"/>
|
||||
<constraint firstItem="iie-Nj-lki" firstAttribute="leading" secondItem="AP9-UO-vZk" secondAttribute="leadingMargin" id="f7R-Ql-UHL"/>
|
||||
<constraint firstItem="639-RC-1ii" firstAttribute="centerY" secondItem="AP9-UO-vZk" secondAttribute="centerY" id="tew-j2-B3V"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="639-RC-1ii" secondAttribute="trailing" id="ygE-k7-OKf"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="7rt-MT-kFH" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1563.6666622161865" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1615.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="7rt-MT-kFH" id="mZL-UA-6V0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Enable EMP for wireguard" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZH7-ZA-Epf" userLabel="Enable EMP for wireguard">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable EMP for wireguard" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZH7-ZA-Epf" userLabel="Enable EMP for wireguard">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="209" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8qE-hE-Ujn">
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8qE-hE-Ujn">
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleEnableEMPforWireguard:" destination="aMk-Xp-UL8" eventType="valueChanged" id="B0Q-Jb-fox"/>
|
||||
@@ -1023,19 +1057,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="q6e-PG-mTq" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1614.6666622161865" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1666.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="q6e-PG-mTq" id="PRJ-Ed-P86">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Enable AppId Customization" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fq4-2u-Lgd" userLabel="Enable AppId Customization">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable AppId Customization" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fq4-2u-Lgd" userLabel="Enable AppId Customization">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="230" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="fXx-wl-F5H">
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="fXx-wl-F5H">
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleEnableAppIdCustomization:" destination="aMk-Xp-UL8" eventType="valueChanged" id="gtP-5M-9Ms"/>
|
||||
@@ -1062,7 +1096,7 @@
|
||||
<tableViewSection id="ZhW-yK-wdJ">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="qjD-UK-myl" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1701.6666622161865" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1752.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qjD-UK-myl" id="bcu-KT-Xee">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1090,7 +1124,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="dNh-fp-vBs" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1752.6666622161865" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1803.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="dNh-fp-vBs" id="Meb-tV-6br">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1118,7 +1152,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Y6h-Bo-yec" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1803.6666622161865" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1854.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Y6h-Bo-yec" id="4Jf-I6-v7z">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1146,7 +1180,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="dLk-d6-X4T" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1854.6666622161865" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1905.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="dLk-d6-X4T" id="Okl-3m-rde">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1178,7 +1212,7 @@
|
||||
<tableViewSection headerTitle="" id="lLQ-K0-XSb">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="daQ-mk-yqC" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1941.3333282470703" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1992.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="daQ-mk-yqC" id="ZkW-ZR-twy">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1213,7 +1247,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="hRP-jU-2hd" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1992.3333282470703" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2043.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="hRP-jU-2hd" id="JhE-O4-pRg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1248,7 +1282,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="JoN-Aj-XtZ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="2043.3333282470703" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2094.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="JoN-Aj-XtZ" id="v8Q-VQ-Q1h">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1283,7 +1317,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="QOO-bO-4M5" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="2094.3333282470703" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2145.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="QOO-bO-4M5" id="VTT-z5-C89">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1311,7 +1345,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="ToB-H7-2lR" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="2145.3333282470703" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2196.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ToB-H7-2lR" id="Acf-xV-Isn">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1339,7 +1373,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="xtI-eU-LFb" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="2196.3333282470703" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2247.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="xtI-eU-LFb" id="bc9-41-6mE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1373,7 +1407,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="pvu-IV-Poa" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="2247.3333282470703" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2298.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="pvu-IV-Poa" id="zck-an-8cK">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1408,7 +1442,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="9By-QW-Jw9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="2298.3333282470703" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2349.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="9By-QW-Jw9" id="Dzq-gE-zyT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1443,7 +1477,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="LzP-Qb-bmC" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="2349.3333282470703" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2400.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="LzP-Qb-bmC" id="3rE-h0-8kb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
|
||||
@@ -77,6 +77,7 @@ extension SettingsViewController
|
||||
case refreshSideJITServer
|
||||
case resetPairingFile
|
||||
case anisetteServers
|
||||
case vpnConfiguration
|
||||
case enableEMPForWiregaurd
|
||||
case customizeAppId
|
||||
}
|
||||
@@ -1364,9 +1365,19 @@ extension SettingsViewController
|
||||
handleRefreshResult(result)
|
||||
})
|
||||
|
||||
let anisetteServersController = UIHostingController(rootView: anisetteServersView)
|
||||
let vc = UIHostingController(rootView: anisetteServersView)
|
||||
self.prepare(for: UIStoryboardSegue(identifier: "anisetteServers", source: self, destination: vc), sender: nil)
|
||||
|
||||
self.prepare(for: UIStoryboardSegue(identifier: "anisetteServers", source: self, destination: anisetteServersController), sender: nil)
|
||||
case .vpnConfiguration:
|
||||
let vpnConfigurationView = VPNConfigurationView()
|
||||
let vc = UIHostingController(rootView: vpnConfigurationView)
|
||||
|
||||
let appearance = UINavigationBarAppearance()
|
||||
appearance.configureWithDefaultBackground() // gives solid background
|
||||
vc.navigationItem.scrollEdgeAppearance = appearance
|
||||
vc.navigationItem.standardAppearance = appearance
|
||||
|
||||
navigationController?.pushViewController(vc, animated: true)
|
||||
case .refreshAttempts, .enableEMPForWiregaurd, .customizeAppId: break
|
||||
}
|
||||
case .signing:
|
||||
|
||||
119
AltStore/Settings/VPNConfigurationView.swift
Normal file
119
AltStore/Settings/VPNConfigurationView.swift
Normal file
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// VPNConfiguration.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 02/03/26.
|
||||
// Copyright © 2026 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
private typealias SButton = SwiftUI.Button
|
||||
|
||||
struct VPNConfigurationView: View {
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@StateObject private var config = TunnelConfig.shared
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section(header: Text("Discovered from network")) {
|
||||
Group {
|
||||
networkConfigRow(label: "Tunnel IP", text: $config.deviceIP, editable: false)
|
||||
networkConfigRow(label: "Device IP", text: $config.fakeIP, editable: false)
|
||||
networkConfigRow(label: "Subnet Mask", text: $config.subnetMask, editable: false)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
networkConfigRow(
|
||||
label: "Device IP",
|
||||
text: Binding<String?>(get: { config.overrideFakeIP }, set: { config.overrideFakeIP = $0 ?? "" }),
|
||||
editable: true
|
||||
)
|
||||
networkConfigRow(
|
||||
label: "Active",
|
||||
text: Binding<String?>(get: { config.overrideActive }, set: { _ in }),
|
||||
editable: false
|
||||
)
|
||||
} header: {
|
||||
Text("User Configuration")
|
||||
} footer: {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text("Note: ")
|
||||
Text("'Device IP' is mandatory and should match exactly as in the VPN's configuration")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("VPN Configuration")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
SButton("Confirm") {
|
||||
commitChanges()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func commitChanges() {
|
||||
bindTunnelConfig()
|
||||
}
|
||||
|
||||
private func dismiss() {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
|
||||
private func networkConfigRow(
|
||||
label: LocalizedStringKey,
|
||||
text: Binding<String?>,
|
||||
editable: Bool
|
||||
) -> some View {
|
||||
|
||||
let proxy = Binding<String>(
|
||||
get: { text.wrappedValue ?? "N/A" },
|
||||
set: { text.wrappedValue = $0.isEmpty || $0 == "N/A" ? nil : $0 }
|
||||
)
|
||||
|
||||
return HStack {
|
||||
Text(label)
|
||||
.foregroundColor(editable ? .primary : .gray)
|
||||
Spacer()
|
||||
TextField(label, text: proxy)
|
||||
.multilineTextAlignment(.trailing)
|
||||
.foregroundColor(editable ? .secondary : .gray)
|
||||
.disabled(!editable)
|
||||
.keyboardType(.numbersAndPunctuation)
|
||||
.onChange(of: proxy.wrappedValue) { newValue in
|
||||
guard editable else { return }
|
||||
proxy.wrappedValue =
|
||||
newValue.filter { "0123456789.".contains($0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class TunnelConfig: ObservableObject {
|
||||
|
||||
static let shared = TunnelConfig()
|
||||
|
||||
private static let defaultOverrideIP: String = {
|
||||
if #available(iOS 26.4, *) { return "192.168.1.50" }
|
||||
return "10.7.0.1"
|
||||
}()
|
||||
|
||||
@Published var deviceIP: String?
|
||||
@Published var subnetMask: String?
|
||||
@Published var fakeIP: String?
|
||||
@Published var overrideFakeIP: String = overrideIPStorage {
|
||||
didSet { Self.overrideIPStorage = overrideFakeIP }
|
||||
}
|
||||
@Published var overrideEffective: Bool = false
|
||||
|
||||
private static var overrideIPStorage: String {
|
||||
get { UserDefaults.standard.string(forKey: "TunnelOverrideFakeIP") ?? defaultOverrideIP }
|
||||
set { UserDefaults.standard.set(newValue, forKey: "TunnelOverrideFakeIP") }
|
||||
}
|
||||
|
||||
var overrideActive: String { overrideEffective ? "Yes" : "No" }
|
||||
}
|
||||
@@ -61,35 +61,6 @@ final class TabBarController: UITabBarController
|
||||
self.initialSegue = nil
|
||||
self.performSegue(withIdentifier: identifier, sender: sender)
|
||||
}
|
||||
else if let patchedApps = UserDefaults.standard.patchedApps, !patchedApps.isEmpty
|
||||
{
|
||||
// Check if we need to finish installing untethered jailbreak.
|
||||
let activeApps = InstalledApp.fetchActiveApps(in: DatabaseManager.shared.viewContext)
|
||||
guard let patchedApp = activeApps.first(where: { patchedApps.contains($0.bundleIdentifier) }) else { return }
|
||||
|
||||
self.performSegue(withIdentifier: "finishJailbreak", sender: patchedApp)
|
||||
}
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||
{
|
||||
guard let identifier = segue.identifier else { return }
|
||||
|
||||
switch identifier
|
||||
{
|
||||
case "finishJailbreak":
|
||||
guard let installedApp = sender as? InstalledApp else { return }
|
||||
|
||||
let navigationController = segue.destination as! UINavigationController
|
||||
|
||||
let patchViewController = navigationController.viewControllers.first as! PatchViewController
|
||||
patchViewController.installedApp = installedApp
|
||||
patchViewController.completionHandler = { [weak self] _ in
|
||||
self?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
override func performSegue(withIdentifier identifier: String, sender: Any?)
|
||||
|
||||
@@ -399,6 +399,40 @@ private extension DatabaseManager
|
||||
// For backwards compatibility reasons, we cannot use localApp's buildVersion as storeBuildVersion,
|
||||
// or else the latest update will _always_ be considered new because we don't use buildVersions in our source (yet).
|
||||
installedApp = InstalledApp(resignedApp: localApp, originalBundleIdentifier: StoreApp.altstoreAppID, certificateSerialNumber: serialNumber, storeBuildVersion: nil, context: context)
|
||||
|
||||
// figure out if the current AltStoreApp is signed with "Use Main Profie" option
|
||||
// by checking if the first extension's entitlement's application-identifier matches current one
|
||||
repeat {
|
||||
guard let pluginURL = Bundle.main.builtInPlugInsURL else {
|
||||
installedApp.useMainProfile = true
|
||||
break
|
||||
}
|
||||
guard let pluginFolders = try? FileManager.default.contentsOfDirectory(at: pluginURL, includingPropertiesForKeys: nil) else {
|
||||
installedApp.useMainProfile = true
|
||||
break
|
||||
}
|
||||
|
||||
guard let pluginFolder = pluginFolders.first, let altPluginApp = ALTApplication(fileURL: pluginFolder) else {
|
||||
installedApp.useMainProfile = true
|
||||
break
|
||||
}
|
||||
|
||||
let entitlements = altPluginApp.entitlements
|
||||
guard let appId = entitlements[ALTEntitlement.applicationIdentifier] as? String else {
|
||||
installedApp.useMainProfile = false
|
||||
print("no ALTEntitlementApplicationIdentifier???")
|
||||
break
|
||||
}
|
||||
|
||||
if appId.hasSuffix(Bundle.main.bundleIdentifier!) {
|
||||
installedApp.useMainProfile = true
|
||||
} else {
|
||||
installedApp.useMainProfile = false
|
||||
}
|
||||
|
||||
|
||||
} while(false)
|
||||
|
||||
installedApp.storeApp = storeApp
|
||||
}
|
||||
|
||||
|
||||
2
Dependencies/AltSign
vendored
2
Dependencies/AltSign
vendored
Submodule Dependencies/AltSign updated: 4cffa3cf45...7efe511440
2
Dependencies/apps-v2.json
vendored
2
Dependencies/apps-v2.json
vendored
Submodule Dependencies/apps-v2.json updated: e0914d3463...9724b1c56d
2
Dependencies/em_proxy
vendored
2
Dependencies/em_proxy
vendored
Submodule Dependencies/em_proxy updated: c151b90652...816dc73350
254
Dependencies/em_proxy.xcodeproj/project.pbxproj
vendored
254
Dependencies/em_proxy.xcodeproj/project.pbxproj
vendored
@@ -1,254 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 53;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
A8A5B08D2F4C387300572B4A /* em_proxy.h in Sources */ = {isa = PBXBuildFile; fileRef = 9999259129A45319005CF020 /* em_proxy.h */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXBuildRule section */
|
||||
CA6094FFF692AC6C1400ACA8 /* PBXBuildRule */ = {
|
||||
isa = PBXBuildRule;
|
||||
compilerSpec = com.apple.compilers.proxy.script;
|
||||
filePatterns = "*/em_proxy.h";
|
||||
fileType = pattern.proxy;
|
||||
inputFiles = (
|
||||
);
|
||||
isEditable = 0;
|
||||
name = "Cargo project build";
|
||||
outputFiles = (
|
||||
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
|
||||
"$(TARGET_BUILD_DIR)/$(FULL_PRODUCT_NAME)",
|
||||
"$(SRCROOT)/Dependencies/em_proxy/em_proxy.h",
|
||||
);
|
||||
script = "# generated with cargo-xcode 1.5.0\n# modified to use prebuilt binaries\n\nset -eu;\n\nBUILT_SRC=\"./em_proxy/$LIB_FILE_NAME.a\"\necho Generating Static lib: $BUILT_SRC\nln -f -- \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\" || cp \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\necho \"$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"minimuxer/target/${CARGO_XCODE_TARGET_TRIPLE}/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n#if [ -f \"$DEP_FILE_SRC\" ]; then\n# DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n# cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n# echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\n#fi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\n#FILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\n#touch \"$FILE_LIST\"\n#if ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n# echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\n#fi\n";
|
||||
};
|
||||
/* End PBXBuildRule section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
9999259129A45319005CF020 /* em_proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = em_proxy.h; path = em_proxy/em_proxy.h; sourceTree = "<group>"; };
|
||||
CA60C44C93D7916DE57E6EBD /* libem_proxy_static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libem_proxy_static.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
CA6094FFF69222869D176AE5 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA60C44C93D7916DE57E6EBD /* libem_proxy_static.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA6094FFF692D65BC3C892A8 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9999259129A45319005CF020 /* em_proxy.h */,
|
||||
CA6094FFF69222869D176AE5 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
CA60C44C93D7A30E3695DD59 /* em_proxy-staticlib */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = CA603DD75FB4A30E3695DD59 /* Build configuration list for PBXNativeTarget "em_proxy-staticlib" */;
|
||||
buildPhases = (
|
||||
9987603529A4610700818586 /* Fetch prebuilt binaries */,
|
||||
CA60445C3036A30E3695DD59 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
CA6094FFF692AC6C1400ACA8 /* PBXBuildRule */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "em_proxy-staticlib";
|
||||
productName = libem_proxy_static.a;
|
||||
productReference = CA60C44C93D7916DE57E6EBD /* libem_proxy_static.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
CA6094FFF692E04653AD465F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
TargetAttributes = {
|
||||
CA60C44C93D7A30E3695DD59 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = CA6094FFF69280E02D6C7F57 /* Build configuration list for PBXProject "em_proxy" */;
|
||||
compatibilityVersion = "Xcode 11.4";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = CA6094FFF692D65BC3C892A8;
|
||||
productRefGroup = CA6094FFF69222869D176AE5 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
CA60C44C93D7A30E3695DD59 /* em_proxy-staticlib */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
9987603529A4610700818586 /* Fetch prebuilt binaries */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Fetch prebuilt binaries";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
./em_proxy/em_proxy.h,
|
||||
./em_proxy/em_proxy.swift,
|
||||
"./em_proxy/libem_proxy-ios.a",
|
||||
"./em_proxy/libem_proxy-sim.a",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "#!bash\npwd\nchmod +x ./fetch-prebuilt.sh \n./fetch-prebuilt.sh em_proxy\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
CA60445C3036A30E3695DD59 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A8A5B08D2F4C387300572B4A /* em_proxy.h in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
CA604DFE779BA30E3695DD59 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = libem_proxy.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = libem_proxy.a;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
LIB_FILE_NAME = "";
|
||||
"LIB_FILE_NAME[sdk=iphoneos*]" = "libem_proxy-ios";
|
||||
"LIB_FILE_NAME[sdk=iphonesimulator*]" = "libem_proxy-sim";
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.5;
|
||||
PRODUCT_NAME = em_proxy_static;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
TVOS_DEPLOYMENT_TARGET = 11.5;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA609A517351228BE02872F8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target";
|
||||
CARGO_XCODE_BUILD_MODE = debug;
|
||||
CARGO_XCODE_FEATURES = "";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = aarch64;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = i686;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = x86_64;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = darwin;
|
||||
CURRENT_PROJECT_VERSION = 0.1;
|
||||
MARKETING_VERSION = 0.1.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = em_proxy;
|
||||
SDKROOT = macosx;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
CA609A5173513CC16B37690B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target";
|
||||
CARGO_XCODE_BUILD_MODE = release;
|
||||
CARGO_XCODE_FEATURES = "";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = aarch64;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = i686;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = x86_64;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = darwin;
|
||||
CURRENT_PROJECT_VERSION = 0.1;
|
||||
MARKETING_VERSION = 0.1.0;
|
||||
PRODUCT_NAME = em_proxy;
|
||||
SDKROOT = macosx;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA60DE07A83FA30E3695DD59 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = libem_proxy.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = libem_proxy.a;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
LIB_FILE_NAME = "";
|
||||
"LIB_FILE_NAME[sdk=iphoneos*]" = "libem_proxy-ios";
|
||||
"LIB_FILE_NAME[sdk=iphonesimulator*]" = "libem_proxy-sim";
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.5;
|
||||
PRODUCT_NAME = em_proxy_static;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
TVOS_DEPLOYMENT_TARGET = 11.5;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
CA603DD75FB4A30E3695DD59 /* Build configuration list for PBXNativeTarget "em_proxy-staticlib" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA604DFE779BA30E3695DD59 /* Release */,
|
||||
CA60DE07A83FA30E3695DD59 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
CA6094FFF69280E02D6C7F57 /* Build configuration list for PBXProject "em_proxy" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA609A5173513CC16B37690B /* Release */,
|
||||
CA609A517351228BE02872F8 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = CA6094FFF692E04653AD465F /* Project object */;
|
||||
}
|
||||
118
Dependencies/fetch-prebuilt.sh
vendored
118
Dependencies/fetch-prebuilt.sh
vendored
@@ -1,118 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Ensure we are in Dependencies directory
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Detect if Homebrew is in /opt/homebrew (Apple Silicon) or /usr/local (Intel)
|
||||
if [[ -d "/opt/homebrew" ]]; then
|
||||
export PATH="/opt/homebrew/bin:$PATH"
|
||||
elif [[ -d "/usr/local" ]]; then
|
||||
export PATH="/usr/local/bin:$PATH"
|
||||
fi
|
||||
|
||||
# Check if wget and curl are installed; if not, install them via Homebrew
|
||||
if ! command -v wget &> /dev/null; then
|
||||
echo "wget not found, attempting to install via Homebrew..."
|
||||
if command -v brew &> /dev/null; then
|
||||
brew install wget
|
||||
else
|
||||
echo "Homebrew is not installed. Please install Homebrew and rerun the script."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! command -v curl &> /dev/null; then
|
||||
echo "curl not found, attempting to install via Homebrew..."
|
||||
if command -v brew &> /dev/null; then
|
||||
brew install curl
|
||||
else
|
||||
echo "Homebrew is not installed. Please install Homebrew and rerun the script."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
check_for_update() {
|
||||
if [ -f ".skip-prebuilt-fetch-$1" ]; then
|
||||
echo "Skipping prebuilt fetch for $1 since .skip-prebuilt-fetch-$1 exists. If you are developing $1 alongside SideStore, don't remove this file, or this script will replace your locally built binaries with the ones built by GitHub Actions."
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -f ".last-prebuilt-fetch-$1" ]; then
|
||||
echo "0,none" > ".last-prebuilt-fetch-$1"
|
||||
fi
|
||||
|
||||
LAST_FETCH=`cat .last-prebuilt-fetch-$1 | perl -n -e '/([0-9]*),([^ ]*)$/ && print $1'`
|
||||
LAST_COMMIT=`cat .last-prebuilt-fetch-$1 | perl -n -e '/([0-9]*),([^ ]*)$/ && print $2'`
|
||||
|
||||
# Check if required library files exist
|
||||
FORCE_DOWNLOAD=false
|
||||
if [ ! -f "$1/lib$1-sim.a" ] || [ ! -f "$1/lib$1-ios.a" ]; then
|
||||
echo "Required libraries missing for $1, forcing download..."
|
||||
FORCE_DOWNLOAD=true
|
||||
fi
|
||||
|
||||
# Download if:
|
||||
# 1. Libraries are missing (FORCE_DOWNLOAD), or
|
||||
# 2. Last fetch was over 1 hour ago, or
|
||||
# 3. Force flag was passed
|
||||
if [ "$FORCE_DOWNLOAD" = true ] || [[ $LAST_FETCH -lt $(expr $(date +%s) - 3600) ]] || [[ "$2" == "force" ]]; then
|
||||
echo "Checking $1 for update"
|
||||
echo
|
||||
LATEST_COMMIT=`curl https://api.github.com/repos/SideStore/$1/releases/latest | perl -n -e '/Commit: https:\\/\\/github\\.com\\/[^\\/]*\\/[^\\/]*\\/commit\\/([^"]*)/ && print $1'`
|
||||
echo
|
||||
echo "Last commit: $LAST_COMMIT"
|
||||
echo "Latest commit: $LATEST_COMMIT"
|
||||
|
||||
NOT_UPTODATE=false
|
||||
if [[ "$LAST_COMMIT" != "$LATEST_COMMIT" ]]; then
|
||||
echo "Found update on the remote: https://api.github.com/repos/SideStore/$1/releases/latest"
|
||||
NOT_UPTODATE=true
|
||||
fi
|
||||
|
||||
# Download if:
|
||||
# 1. Libraries are missing (FORCE_DOWNLOAD), or
|
||||
# 2. New commit is available
|
||||
if [ "$FORCE_DOWNLOAD" = true ] || [ "$NOT_UPTODATE" = true ] ;then
|
||||
echo "downloading binaries"
|
||||
echo
|
||||
if [[ "$1" != "minimuxer" ]]; then
|
||||
wget -O "$1/lib$1-sim.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-sim.a"
|
||||
wget -O "$1/lib$1-ios.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-ios.a"
|
||||
wget -O "$1/$1.h" "https://github.com/SideStore/$1/releases/latest/download/$1.h"
|
||||
wget -O "$1/$1.swift" "https://github.com/SideStore/$1/releases/latest/download/$1.swift"
|
||||
echo
|
||||
else
|
||||
wget -O "$1/lib$1-sim.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-sim.a"
|
||||
wget -O "$1/lib$1-ios.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-ios.a"
|
||||
wget -O "$1/generated.zip" "https://github.com/SideStore/$1/releases/latest/download/generated.zip"
|
||||
echo
|
||||
echo "Unzipping generated.zip"
|
||||
cd "$1"
|
||||
unzip ./generated.zip
|
||||
cp -v generated/* .
|
||||
# Remove all files except ones that comes checked-in from minimuxer repository
|
||||
find generated -type f ! -name 'minimuxer-Bridging-Header.h' ! -name 'minimuxer-helpers.swift' -exec rm -v {} \;
|
||||
rm generated.zip
|
||||
rmdir generated/
|
||||
cd ..
|
||||
echo "Done"
|
||||
fi
|
||||
else
|
||||
echo "Up-to-date"
|
||||
fi
|
||||
echo "$(date +%s),$LATEST_COMMIT" > ".last-prebuilt-fetch-$1"
|
||||
else
|
||||
echo "It hasn't been 1 hour and force was not specified, skipping update check for $1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Allow for Xcode to check minimuxer and em_proxy separately by skipping the update check if the other one is specified as an argument
|
||||
if [[ "$1" != "em_proxy" ]]; then
|
||||
check_for_update minimuxer "$1"
|
||||
if [[ "$1" != "minimuxer" ]]; then
|
||||
echo
|
||||
fi
|
||||
fi
|
||||
if [[ "$1" != "minimuxer" ]]; then
|
||||
check_for_update em_proxy "$1"
|
||||
fi
|
||||
BIN
Dependencies/libcurl/libcurl.a
vendored
BIN
Dependencies/libcurl/libcurl.a
vendored
Binary file not shown.
1
Dependencies/libfragmentzip
vendored
1
Dependencies/libfragmentzip
vendored
Submodule Dependencies/libfragmentzip deleted from b7f9272acf
2
Dependencies/minimuxer
vendored
2
Dependencies/minimuxer
vendored
Submodule Dependencies/minimuxer updated: 9035aa25ae...d00e5feb20
264
Dependencies/minimuxer.xcodeproj/project.pbxproj
vendored
264
Dependencies/minimuxer.xcodeproj/project.pbxproj
vendored
@@ -1,264 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 53;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
A8A5B07F2F4C355800572B4A /* minimuxer.h in Sources */ = {isa = PBXBuildFile; fileRef = A8A5B07D2F4C34FF00572B4A /* minimuxer.h */; };
|
||||
A8A5B0802F4C355800572B4A /* SwiftBridgeCore.h in Sources */ = {isa = PBXBuildFile; fileRef = A8A5B07E2F4C34FF00572B4A /* SwiftBridgeCore.h */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXBuildRule section */
|
||||
CA6012A875F9AC6C1400ACA8 /* PBXBuildRule */ = {
|
||||
isa = PBXBuildRule;
|
||||
compilerSpec = com.apple.compilers.proxy.script;
|
||||
filePatterns = "*/minimuxer.h";
|
||||
fileType = pattern.proxy;
|
||||
inputFiles = (
|
||||
);
|
||||
isEditable = 0;
|
||||
name = "Cargo project build";
|
||||
outputFiles = (
|
||||
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
|
||||
"$(TARGET_BUILD_DIR)/$(FULL_PRODUCT_NAME)",
|
||||
"$(SRCROOT)/Dependencies/minimuxer/minimuxer.h",
|
||||
);
|
||||
script = "# generated with cargo-xcode 1.5.0\n# modified to use prebuilt binaries\n\nset -eu;\n\nBUILT_SRC=\"./minimuxer/${LIB_FILE_NAME}.a\"\necho Generating Static lib: $BUILT_SRC\nln -f -- \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\" || cp \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\necho \"$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"minimuxer/target/${CARGO_XCODE_TARGET_TRIPLE}/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n#if [ -f \"$DEP_FILE_SRC\" ]; then\n# DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n# cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n# echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\n#fi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\n#FILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\n#touch \"$FILE_LIST\"\n#if ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n# echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\n#fi\n";
|
||||
};
|
||||
/* End PBXBuildRule section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
A8A5B07D2F4C34FF00572B4A /* minimuxer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = minimuxer.h; path = minimuxer/minimuxer.h; sourceTree = "<group>"; };
|
||||
A8A5B07E2F4C34FF00572B4A /* SwiftBridgeCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SwiftBridgeCore.h; path = minimuxer/SwiftBridgeCore.h; sourceTree = "<group>"; };
|
||||
A8A5B09C2F4C454900572B4A /* minimuxer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "minimuxer-Bridging-Header.h"; path = "minimuxer/minimuxer-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
CA609C732349C7AAD9FA67C4 /* libminimuxer_static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminimuxer_static.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
CA6012A875F922869D176AE5 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA609C732349C7AAD9FA67C4 /* libminimuxer_static.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA6012A875F9D65BC3C892A8 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8A5B07D2F4C34FF00572B4A /* minimuxer.h */,
|
||||
A8A5B07E2F4C34FF00572B4A /* SwiftBridgeCore.h */,
|
||||
A8A5B09C2F4C454900572B4A /* minimuxer-Bridging-Header.h */,
|
||||
CA6012A875F922869D176AE5 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
CA609C732349A560B9642892 /* minimuxer-staticlib */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = CA600589A243A560B9642892 /* Build configuration list for PBXNativeTarget "minimuxer-staticlib" */;
|
||||
buildPhases = (
|
||||
9987603629A4611D00818586 /* Fetch prebuilt binaries */,
|
||||
CA600F638141A560B9642892 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
CA6012A875F9AC6C1400ACA8 /* PBXBuildRule */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "minimuxer-staticlib";
|
||||
productName = libminimuxer_static.a;
|
||||
productReference = CA609C732349C7AAD9FA67C4 /* libminimuxer_static.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
CA6012A875F9E04653AD465F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
TargetAttributes = {
|
||||
CA609C732349A560B9642892 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = CA6012A875F980E02D6C7F57 /* Build configuration list for PBXProject "minimuxer" */;
|
||||
compatibilityVersion = "Xcode 11.4";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = CA6012A875F9D65BC3C892A8;
|
||||
productRefGroup = CA6012A875F922869D176AE5 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
CA609C732349A560B9642892 /* minimuxer-staticlib */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
9987603629A4611D00818586 /* Fetch prebuilt binaries */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Fetch prebuilt binaries";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
./minimuxer/minimuxer.h,
|
||||
./minimuxer/SwiftBridgeCore.h,
|
||||
./minimuxer/minimuxer.swift,
|
||||
./minimuxer/SwiftBridgeCore.swift,
|
||||
"./minimuxer/minimuxer-Bridging-Header.h",
|
||||
"./minimuxer/minimuxer-helpers.swift",
|
||||
"./minimuxer/libminimuxer-ios.a",
|
||||
"./minimuxer/libminimuxer-sim.a",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "#!bash\npwd\nchmod +x ./fetch-prebuilt.sh \n./fetch-prebuilt.sh minimuxer\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
CA600F638141A560B9642892 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A8A5B07F2F4C355800572B4A /* minimuxer.h in Sources */,
|
||||
A8A5B0802F4C355800572B4A /* SwiftBridgeCore.h in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
CA6008D36272A560B9642892 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = libminimuxer.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = libminimuxer.a;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
LIB_FILE_NAME = "";
|
||||
"LIB_FILE_NAME[sdk=iphoneos*]" = "libminimuxer-ios";
|
||||
"LIB_FILE_NAME[sdk=iphonesimulator*]" = "libminimuxer-sim";
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.5;
|
||||
PRODUCT_NAME = minimuxer_static;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
TVOS_DEPLOYMENT_TARGET = 11.5;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
CA602DE9FCEDA560B9642892 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = libminimuxer.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = libminimuxer.a;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
LIB_FILE_NAME = "";
|
||||
"LIB_FILE_NAME[sdk=iphoneos*]" = "libminimuxer-ios";
|
||||
"LIB_FILE_NAME[sdk=iphonesimulator*]" = "libminimuxer-sim";
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.5;
|
||||
PRODUCT_NAME = minimuxer_static;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
TVOS_DEPLOYMENT_TARGET = 11.5;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA60A20F8EA6228BE02872F8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target";
|
||||
CARGO_XCODE_BUILD_MODE = debug;
|
||||
CARGO_XCODE_FEATURES = "";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = aarch64;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = i686;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = x86_64;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = darwin;
|
||||
CURRENT_PROJECT_VERSION = 0.1;
|
||||
MARKETING_VERSION = 0.1.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = minimuxer;
|
||||
SDKROOT = macosx;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
CA60A20F8EA63CC16B37690B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target";
|
||||
CARGO_XCODE_BUILD_MODE = release;
|
||||
CARGO_XCODE_FEATURES = "";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = aarch64;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = i686;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = x86_64;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = darwin;
|
||||
CURRENT_PROJECT_VERSION = 0.1;
|
||||
MARKETING_VERSION = 0.1.0;
|
||||
PRODUCT_NAME = minimuxer;
|
||||
SDKROOT = macosx;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
CA600589A243A560B9642892 /* Build configuration list for PBXNativeTarget "minimuxer-staticlib" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA602DE9FCEDA560B9642892 /* Release */,
|
||||
CA6008D36272A560B9642892 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
CA6012A875F980E02D6C7F57 /* Build configuration list for PBXProject "minimuxer" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA60A20F8EA63CC16B37690B /* Release */,
|
||||
CA60A20F8EA6228BE02872F8 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = CA6012A875F9E04653AD465F /* Project object */;
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?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 = "CA609C732349A560B9642892"
|
||||
BuildableName = "libminimuxer_static.a"
|
||||
BlueprintName = "minimuxer-staticlib"
|
||||
ReferencedContainer = "container:minimuxer.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 = "CA609C732349A560B9642892"
|
||||
BuildableName = "libminimuxer_static.a"
|
||||
BlueprintName = "minimuxer-staticlib"
|
||||
ReferencedContainer = "container:minimuxer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
3
Makefile
3
Makefile
@@ -374,8 +374,7 @@ ipa-altbackup: checkPaths copy-altbackup
|
||||
@echo " Copying from $(ALT_APP_SRC) into $(ALT_APP_PAYLOAD_DST)"
|
||||
@cp -R -f "$(ALT_APP_SRC)/." "$(ALT_APP_PAYLOAD_DST)/$(TARGET_NAME)"
|
||||
@pushd "$(ALT_APP_DST_ARCHIVE)" && zip -r "../../$(ALT_APP_IPA_DST)" Payload || popd
|
||||
@cp -f "$(ALT_APP_IPA_DST)" AltStore/Resources
|
||||
@echo " IPA created: AltStore/Resources/AltBackup.ipa"
|
||||
@echo " IPA created: build/AltBackup.ipa"
|
||||
|
||||
clean-altbackup:
|
||||
@echo ""
|
||||
|
||||
@@ -6,79 +6,178 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
private import minimuxer
|
||||
import Minimuxer
|
||||
|
||||
func bindTunnelConfig() {
|
||||
defer { print("[SideStore] bindTunnelConfig() completed") }
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
print("[SideStore] bindTunnelConfig() is no-op on simulator")
|
||||
#else
|
||||
print("[SideStore] bindTunnelConfig() invoked")
|
||||
|
||||
Task { @MainActor in
|
||||
let config = TunnelConfig.shared
|
||||
Minimuxer.bindTunnelConfig(
|
||||
TunnelConfigBinding(
|
||||
setDeviceIP: { value in Task { @MainActor in config.deviceIP = value } },
|
||||
setFakeIP: { value in Task { @MainActor in config.fakeIP = value } },
|
||||
setSubnetMask: { value in Task { @MainActor in config.subnetMask = value } },
|
||||
getOverrideFakeIP: { config.overrideFakeIP },
|
||||
setOverrideEffective: { value in Task { @MainActor in config.overrideEffective = value } }
|
||||
)
|
||||
)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
var isMinimuxerReady: Bool {
|
||||
var result = true
|
||||
#if targetEnvironment(simulator)
|
||||
print("isMinimuxerReady property is always true on simulator")
|
||||
return true
|
||||
print("[SideStore] isMinimuxerReady = true on simulator")
|
||||
#else
|
||||
return minimuxer.ready()
|
||||
result = Minimuxer.ready()
|
||||
print("[SideStore] isMinimuxerReady = \(result)")
|
||||
#endif
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
func retargetUsbmuxdAddr() {
|
||||
defer { print("[SideStore] retargetUsbmuxdAddr() completed") }
|
||||
#if targetEnvironment(simulator)
|
||||
print("[SideStore] retargetUsbmuxdAddr() is no-op on simulator")
|
||||
#else
|
||||
print("[SideStore] retargetUsbmuxdAddr() invoked")
|
||||
Minimuxer.retargetUsbmuxdAddr()
|
||||
#endif
|
||||
}
|
||||
|
||||
func minimuxerStartWithLogger(_ pairingFile: String,_ logPath: String,_ loggingEnabled: Bool) throws {
|
||||
func minimuxerStartWithLogger(_ pairingFile: String, _ logPath: String, _ loggingEnabled: Bool) throws {
|
||||
defer { print("[SideStore] minimuxerStartWithLogger(pairingFile, logPath, dest, loggingEnabled) completed") }
|
||||
#if targetEnvironment(simulator)
|
||||
print("minimuxerStartWithLogger(\(pairingFile), \(logPath), \(loggingEnabled) is no-op on simulator")
|
||||
print("[SideStore] minimuxerStartWithLogger(pairingFile, logPath, loggingEnabled) is no-op on simulator")
|
||||
#else
|
||||
try minimuxer.startWithLogger(pairingFile, logPath, loggingEnabled)
|
||||
#endif
|
||||
}
|
||||
// refresh config if any
|
||||
bindTunnelConfig()
|
||||
// observe network route changes (and update device endpoint from vpn(utun))
|
||||
NetworkObserver.shared.start()
|
||||
|
||||
func targetMinimuxerAddress() {
|
||||
#if targetEnvironment(simulator)
|
||||
print("targetMinimuxerAddress() is no-op on simulator")
|
||||
#else
|
||||
minimuxer.target_minimuxer_address()
|
||||
print("[SideStore] minimuxerStartWithLogger(pairingFile, logPath, dest, loggingEnabled) invoked")
|
||||
try Minimuxer.startWithLogger(pairingFile: pairingFile,
|
||||
logPath: logPath,
|
||||
isConsoleLoggingEnabled: loggingEnabled)
|
||||
#endif
|
||||
}
|
||||
|
||||
func installProvisioningProfiles(_ profileData: Data) throws {
|
||||
defer { print("[SideStore] installProvisioningProfiles(profileData) completed") }
|
||||
#if targetEnvironment(simulator)
|
||||
print("installProvisioningProfiles(\(profileData)) is no-op on simulator")
|
||||
print("[SideStore] installProvisioningProfiles(profileData) is no-op on simulator")
|
||||
#else
|
||||
try minimuxer.install_provisioning_profile(profileData.toRustByteSlice().forRust())
|
||||
print("[SideStore] installProvisioningProfiles(profileData) invoked")
|
||||
try Minimuxer.installProvisioningProfile(profile: profileData)
|
||||
#endif
|
||||
}
|
||||
|
||||
func removeProvisioningProfile(_ id: String) throws {
|
||||
defer { print("[SideStore] removeProvisioningProfile(id) completed") }
|
||||
#if targetEnvironment(simulator)
|
||||
print("[SideStore] removeProvisioningProfile(id) is no-op on simulator")
|
||||
#else
|
||||
print("[SideStore] removeProvisioningProfile(id) invoked")
|
||||
try Minimuxer.removeProvisioningProfile(id: id)
|
||||
#endif
|
||||
}
|
||||
|
||||
func removeApp(_ bundleId: String) throws {
|
||||
defer { print("[SideStore] removeApp(bundleId) completed") }
|
||||
#if targetEnvironment(simulator)
|
||||
print("removeApp(\(bundleId)) is no-op on simulator")
|
||||
print("[SideStore] removeApp(bundleId) is no-op on simulator")
|
||||
#else
|
||||
try minimuxer.remove_app(bundleId)
|
||||
print("[SideStore] removeApp(bundleId) invoked")
|
||||
try Minimuxer.removeApp(bundleId: bundleId)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
func yeetAppAFC(_ bundleId: String, _ rawBytes: Data) throws {
|
||||
defer { print("[SideStore] yeetAppAFC(bundleId, rawBytes) completed") }
|
||||
#if targetEnvironment(simulator)
|
||||
print("yeetAppAFC(\(bundleId), \(rawBytes)) is no-op on simulator")
|
||||
print("[SideStore] yeetAppAFC(bundleId, rawBytes) is no-op on simulator")
|
||||
#else
|
||||
try minimuxer.yeet_app_afc(bundleId, rawBytes.toRustByteSlice().forRust())
|
||||
print("[SideStore] yeetAppAFC(bundleId, rawBytes) invoked")
|
||||
try Minimuxer.yeetAppAfc(bundleId: bundleId, ipaBytes: rawBytes)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
func installIPA(_ bundleId: String) throws {
|
||||
defer { print("[SideStore] installIPA(bundleId) completed") }
|
||||
#if targetEnvironment(simulator)
|
||||
print("installIPA(\(bundleId)) is no-op on simulator")
|
||||
print("[SideStore] installIPA(bundleId) is no-op on simulator")
|
||||
#else
|
||||
try minimuxer.install_ipa(bundleId)
|
||||
print("[SideStore] installIPA(bundleId) invoked")
|
||||
try Minimuxer.installIpa(bundleId: bundleId)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
func fetchUDID() -> String? {
|
||||
defer { print("[SideStore] fetchUDID() completed") }
|
||||
#if targetEnvironment(simulator)
|
||||
print("fetchUDID() is no-op on simulator")
|
||||
print("[SideStore] fetchUDID() is no-op on simulator")
|
||||
return "XXXXX-XXXX-XXXXX-XXXX"
|
||||
#else
|
||||
return minimuxer.fetch_udid()?.toString()
|
||||
print("[SideStore] fetchUDID() invoked")
|
||||
return Minimuxer.fetchUDID()
|
||||
#endif
|
||||
}
|
||||
|
||||
func debugApp(_ appId: String) throws {
|
||||
defer { print("[SideStore] debugApp(appId) completed") }
|
||||
#if targetEnvironment(simulator)
|
||||
print("[SideStore] debugApp(appId) is no-op on simulator")
|
||||
#else
|
||||
print("[SideStore] debugApp(appId) invoked")
|
||||
try Minimuxer.debugApp(appId: appId)
|
||||
#endif
|
||||
}
|
||||
|
||||
func attachDebugger(_ pid: UInt32) throws {
|
||||
defer { print("[SideStore] attachDebugger(pid) completed") }
|
||||
#if targetEnvironment(simulator)
|
||||
print("[SideStore] attachDebugger(pid) is no-op on simulator")
|
||||
#else
|
||||
print("[SideStore] attachDebugger(pid) invoked")
|
||||
try Minimuxer.attachDebugger(pid: pid)
|
||||
#endif
|
||||
}
|
||||
|
||||
func startAutoMounter(_ docsPath: String) {
|
||||
defer { print("[SideStore] startAutoMounter(docsPath) completed") }
|
||||
#if targetEnvironment(simulator)
|
||||
print("[SideStore] startAutoMounter(docsPath) is no-op on simulator")
|
||||
#else
|
||||
print("[SideStore] startAutoMounter(docsPath) invoked")
|
||||
Minimuxer.startAutoMounter(docsPath: docsPath)
|
||||
#endif
|
||||
}
|
||||
|
||||
func dumpProfiles(_ docsPath: String) throws -> String {
|
||||
defer { print("[SideStore] dumpProfiles(docsPath) completed") }
|
||||
#if targetEnvironment(simulator)
|
||||
print("[SideStore] dumpProfiles(docsPath) is no-op on simulator")
|
||||
return ""
|
||||
#else
|
||||
print("[SideStore] dumpProfiles(docsPath) invoked")
|
||||
return try Minimuxer.dumpProfiles(docsPath: docsPath)
|
||||
#endif
|
||||
}
|
||||
|
||||
func setMinimuxerDebug(_ debug: Bool) {
|
||||
defer { print("[SideStore] setMinimuxerDebug(debug) completed") }
|
||||
print("[SideStore] setMinimuxerDebug(debug) invoked")
|
||||
Minimuxer.setDebug(debug)
|
||||
}
|
||||
|
||||
extension MinimuxerError: @retroactive LocalizedError {
|
||||
public var failureReason: String? {
|
||||
@@ -89,41 +188,38 @@ extension MinimuxerError: @retroactive LocalizedError {
|
||||
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")
|
||||
return createService(name: "debug")
|
||||
case .LookupApps:
|
||||
return self.getFromDevice(name: "installed apps")
|
||||
return getFromDevice(name: "installed apps")
|
||||
case .FindApp:
|
||||
return self.getFromDevice(name: "path to the app")
|
||||
return getFromDevice(name: "path to the app")
|
||||
case .BundlePath:
|
||||
return self.getFromDevice(name: "bundle path")
|
||||
return getFromDevice(name: "bundle path")
|
||||
case .MaxPacket:
|
||||
return self.setArgument(name: "max packet")
|
||||
return setArgument(name: "max packet")
|
||||
case .WorkingDirectory:
|
||||
return self.setArgument(name: "working directory")
|
||||
return setArgument(name: "working directory")
|
||||
case .Argv:
|
||||
return self.setArgument(name: "argv")
|
||||
return setArgument(name: "argv")
|
||||
case .LaunchSuccess:
|
||||
return self.getFromDevice(name: "launch success")
|
||||
return 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")
|
||||
return createService(name: "instproxy")
|
||||
case .CreateAfc:
|
||||
return self.createService(name: "AFC")
|
||||
return 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: "")
|
||||
return NSLocalizedString("AFC was unable to manage files on the device.", comment: "")
|
||||
case .InstallApp(let message):
|
||||
return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "")
|
||||
return NSLocalizedString("Unable to install the app: \(message)", comment: "")
|
||||
case .UninstallApp:
|
||||
return NSLocalizedString("Unable to uninstall the app", comment: "")
|
||||
|
||||
case .CreateMisagent:
|
||||
return self.createService(name: "misagent")
|
||||
return createService(name: "misagent")
|
||||
case .ProfileInstall:
|
||||
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
case .ProfileRemove:
|
||||
@@ -164,14 +260,14 @@ extension MinimuxerError: @retroactive LocalizedError {
|
||||
}
|
||||
|
||||
fileprivate func createService(name: String) -> String {
|
||||
return String(format: NSLocalizedString("Cannot start a %@ server on the device.", comment: ""), name)
|
||||
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)
|
||||
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)
|
||||
String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,20 @@ def run(cmd: str) -> str:
|
||||
return subprocess.check_output(cmd, shell=True, text=True).strip()
|
||||
|
||||
|
||||
def commit_exists(rev: str) -> bool:
|
||||
if not rev:
|
||||
return False
|
||||
try:
|
||||
subprocess.check_output(
|
||||
f"git rev-parse --verify {rev}^{{commit}}",
|
||||
shell=True,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def head_commit():
|
||||
return run("git rev-parse HEAD")
|
||||
|
||||
@@ -35,12 +49,8 @@ def repo_url():
|
||||
|
||||
|
||||
def commit_messages(start, end="HEAD"):
|
||||
try:
|
||||
out = run(f"git log {start}..{end} --pretty=format:%s")
|
||||
return out.splitlines() if out else []
|
||||
except subprocess.CalledProcessError:
|
||||
fallback = run("git rev-parse HEAD~5")
|
||||
return run(f"git log {fallback}..{end} --pretty=format:%s").splitlines()
|
||||
|
||||
|
||||
def authors(range_expr, fmt="%an"):
|
||||
@@ -76,8 +86,34 @@ def fmt_author(author):
|
||||
# release note generation
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def resolve_start_commit(last_successful: str):
|
||||
if commit_exists(last_successful):
|
||||
return last_successful
|
||||
|
||||
try:
|
||||
return run("git rev-parse HEAD~10")
|
||||
except Exception:
|
||||
return first_commit()
|
||||
|
||||
|
||||
def generate_release_notes(last_successful, tag, branch):
|
||||
current = head_commit()
|
||||
|
||||
# fallback if missing/invalid
|
||||
if not last_successful or not commit_exists(last_successful):
|
||||
try:
|
||||
last_successful = run("git rev-parse HEAD~10")
|
||||
except Exception:
|
||||
last_successful = first_commit()
|
||||
|
||||
messages = commit_messages(last_successful, current)
|
||||
|
||||
# fallback if empty range
|
||||
if not messages:
|
||||
try:
|
||||
last_successful = run("git rev-parse HEAD~10")
|
||||
except Exception:
|
||||
last_successful = first_commit()
|
||||
messages = commit_messages(last_successful, current)
|
||||
|
||||
section = f"{TAG_MARKER} {tag}\n"
|
||||
@@ -89,8 +125,14 @@ def generate_release_notes(last_successful, tag, branch):
|
||||
for m in messages:
|
||||
section += f"{fmt_msg(m)}\n"
|
||||
|
||||
prev_authors = authors(branch)
|
||||
new_authors = authors(f"{last_successful}..{current}") - prev_authors
|
||||
if commit_exists(branch):
|
||||
previous_range = branch
|
||||
else:
|
||||
previous_range = last_successful
|
||||
|
||||
prev_authors = authors(previous_range)
|
||||
recent_authors = authors(f"{last_successful}..{current}")
|
||||
new_authors = recent_authors - prev_authors
|
||||
|
||||
if new_authors:
|
||||
section += f"\n{HEADER_MARKER} New Contributors\n"
|
||||
@@ -101,13 +143,26 @@ def generate_release_notes(last_successful, tag, branch):
|
||||
url = repo_url()
|
||||
section += (
|
||||
f"\n{HEADER_MARKER} Full Changelog: "
|
||||
f"[{last_successful[:8]}...{current[:8]}]"
|
||||
f"[{ref_display(last_successful)}...{ref_display(current)}]"
|
||||
f"({url}/compare/{last_successful}...{current})\n"
|
||||
)
|
||||
|
||||
return section
|
||||
|
||||
|
||||
def ref_display(ref):
|
||||
try:
|
||||
tag = run(f'git describe --tags --exact-match "{ref}" 2>/dev/null || true').strip()
|
||||
|
||||
# allow only semantic version tags: X.Y.Z
|
||||
if re.fullmatch(r'\d+\.\d+\.\d+', tag):
|
||||
return tag
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return ref[:8]
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# markdown update
|
||||
# ----------------------------------------------------------
|
||||
@@ -170,7 +225,7 @@ def update_release_md(existing, new_section, tag):
|
||||
# retrieval
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def retrieve_tag(tag, file_path):
|
||||
def retrieve_tag(tag, file_path: Path):
|
||||
if not file_path.exists():
|
||||
return ""
|
||||
|
||||
@@ -209,30 +264,20 @@ def main():
|
||||
" generate_release_notes.py --retrieve <tag> [--output-dir DIR]"
|
||||
)
|
||||
|
||||
# parse optional output dir
|
||||
output_dir = Path.cwd()
|
||||
|
||||
if "--output-dir" in args:
|
||||
idx = args.index("--output-dir")
|
||||
try:
|
||||
output_dir = Path(args[idx + 1]).resolve()
|
||||
except IndexError:
|
||||
sys.exit("Missing value for --output-dir")
|
||||
|
||||
del args[idx:idx + 2]
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
release_file = output_dir / "release-notes.md"
|
||||
|
||||
# retrieval mode
|
||||
if args[0] == "--retrieve":
|
||||
if len(args) < 2:
|
||||
sys.exit("Missing tag after --retrieve")
|
||||
|
||||
print(retrieve_tag(args[1], release_file))
|
||||
return
|
||||
|
||||
# generation mode
|
||||
last_successful = args[0]
|
||||
tag = args[1] if len(args) > 1 else head_commit()
|
||||
branch = args[2] if len(args) > 2 else (
|
||||
@@ -241,12 +286,7 @@ def main():
|
||||
|
||||
new_section = generate_release_notes(last_successful, tag, branch)
|
||||
|
||||
existing = (
|
||||
release_file.read_text()
|
||||
if release_file.exists()
|
||||
else ""
|
||||
)
|
||||
|
||||
existing = release_file.read_text() if release_file.exists() else ""
|
||||
updated = update_release_md(existing, new_section, tag)
|
||||
|
||||
release_file.write_text(updated)
|
||||
|
||||
@@ -5,16 +5,33 @@ import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# helpers
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def resolve_script(name: str) -> Path:
|
||||
p = Path.cwd() / name
|
||||
if p.exists():
|
||||
return p
|
||||
return SCRIPT_DIR / name
|
||||
|
||||
|
||||
def sh(cmd: str, cwd: Path) -> str:
|
||||
try:
|
||||
return subprocess.check_output(
|
||||
cmd, shell=True, cwd=cwd
|
||||
cmd,
|
||||
shell=True,
|
||||
cwd=cwd,
|
||||
stderr=subprocess.STDOUT,
|
||||
).decode().strip()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e.output.decode(), file=sys.stderr)
|
||||
raise SystemExit(f"Command failed: {cmd}")
|
||||
|
||||
|
||||
def file_size(path: Path) -> int:
|
||||
@@ -38,36 +55,26 @@ def sha256(path: Path) -> str:
|
||||
def main():
|
||||
p = argparse.ArgumentParser()
|
||||
|
||||
p.add_argument(
|
||||
"--repo-root",
|
||||
required=True,
|
||||
help="Repo used for git history + release notes",
|
||||
)
|
||||
p.add_argument("--repo-root", required=True)
|
||||
p.add_argument("--ipa", required=True)
|
||||
p.add_argument("--output-dir", required=True)
|
||||
|
||||
p.add_argument(
|
||||
"--ipa",
|
||||
required=True,
|
||||
help="Path to IPA file",
|
||||
"--output-name",
|
||||
default="source_metadata.json",
|
||||
)
|
||||
|
||||
p.add_argument(
|
||||
"--output-dir",
|
||||
required=True,
|
||||
help="Output Directory where source_metadata.json is written",
|
||||
)
|
||||
|
||||
p.add_argument(
|
||||
"--release-notes-dir",
|
||||
required=True,
|
||||
help="Output Directory where release-notes.md is generated/read",
|
||||
)
|
||||
p.add_argument("--release-notes-dir", required=True)
|
||||
|
||||
p.add_argument("--release-tag", required=True)
|
||||
p.add_argument("--version", required=True)
|
||||
p.add_argument("--marketing-version", required=True)
|
||||
p.add_argument("--short-commit", required=True)
|
||||
p.add_argument("--release-channel", required=True)
|
||||
p.add_argument("--bundle-id", required=True)
|
||||
|
||||
# optional
|
||||
p.add_argument("--last-successful-commit")
|
||||
|
||||
p.add_argument("--is-beta", action="store_true")
|
||||
|
||||
args = p.parse_args()
|
||||
@@ -86,22 +93,30 @@ def main():
|
||||
notes_dir.mkdir(parents=True, exist_ok=True)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
out_file = out_dir / "source_metadata.json"
|
||||
out_file = out_dir / args.output_name
|
||||
|
||||
# ------------------------------------------------------
|
||||
# ensure release notes exist
|
||||
# generate release notes
|
||||
# ------------------------------------------------------
|
||||
|
||||
print("Generating release notes…")
|
||||
|
||||
sh(
|
||||
(
|
||||
"python3 generate_release_notes.py "
|
||||
f"{args.short_commit} {args.release_tag} "
|
||||
f"--output-dir \"{notes_dir}\""
|
||||
),
|
||||
cwd=repo_root,
|
||||
script = resolve_script("generate_release_notes.py")
|
||||
|
||||
if args.last_successful_commit:
|
||||
gen_cmd = (
|
||||
f"python3 {script} "
|
||||
f"{args.last_successful_commit} {args.release_tag} "
|
||||
f'--output-dir "{notes_dir}"'
|
||||
)
|
||||
else:
|
||||
gen_cmd = (
|
||||
f"python3 {script} "
|
||||
f"{args.short_commit} {args.release_tag} "
|
||||
f'--output-dir "{notes_dir}"'
|
||||
)
|
||||
|
||||
sh(gen_cmd, cwd=repo_root)
|
||||
|
||||
# ------------------------------------------------------
|
||||
# retrieve release notes
|
||||
@@ -109,7 +124,7 @@ def main():
|
||||
|
||||
notes = sh(
|
||||
(
|
||||
"python3 generate_release_notes.py "
|
||||
f"python3 {script} "
|
||||
f"--retrieve {args.release_tag} "
|
||||
f"--output-dir \"{notes_dir}\""
|
||||
),
|
||||
@@ -120,19 +135,11 @@ def main():
|
||||
# compute metadata
|
||||
# ------------------------------------------------------
|
||||
|
||||
now = datetime.datetime.now(datetime.UTC)
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
formatted = now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
human = now.strftime("%c")
|
||||
|
||||
localized_description = f"""
|
||||
This is release for:
|
||||
- version: "{args.version}"
|
||||
- revision: "{args.short_commit}"
|
||||
- timestamp: "{human}"
|
||||
|
||||
Release Notes:
|
||||
{notes}
|
||||
""".strip()
|
||||
localized_description = getFormattedLocalizedDescription(args.marketing_version, args.short_commit, human, notes)
|
||||
|
||||
metadata = {
|
||||
"is_beta": bool(args.is_beta),
|
||||
@@ -154,6 +161,16 @@ Release Notes:
|
||||
|
||||
print(f"Wrote {out_file}")
|
||||
|
||||
def getFormattedLocalizedDescription(marketing_version, short_commit, human, notes):
|
||||
return f"""
|
||||
This is release for:
|
||||
- version: "{marketing_version}"
|
||||
- revision: "{short_commit}"
|
||||
- timestamp: "{human}"
|
||||
|
||||
Release Notes:
|
||||
{notes}
|
||||
""".lstrip("\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -5,176 +5,177 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
'''
|
||||
metadata.json template
|
||||
|
||||
{
|
||||
"version_ipa": "0.0.0",
|
||||
"version_date": "2000-12-18T00:00:00Z",
|
||||
"is_beta": true,
|
||||
"release_channel": "alpha",
|
||||
"size": 0,
|
||||
"sha256": "",
|
||||
"localized_description": "Invalid Update",
|
||||
"download_url": "https://github.com/SideStore/SideStore/releases/download/0.0.0/SideStore.ipa",
|
||||
"bundle_identifier": "com.SideStore.SideStore"
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# args
|
||||
# metadata
|
||||
# ----------------------------------------------------------
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python3 update_apps.py <metadata.json> <source.json>")
|
||||
sys.exit(1)
|
||||
def load_metadata(metadata_file: Path):
|
||||
if not metadata_file.exists():
|
||||
raise SystemExit(f"Missing metadata file: {metadata_file}")
|
||||
|
||||
metadata_file = Path(sys.argv[1])
|
||||
source_file = Path(sys.argv[2])
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# load metadata
|
||||
# ----------------------------------------------------------
|
||||
|
||||
if not metadata_file.exists():
|
||||
print(f"Missing metadata file: {metadata_file}")
|
||||
sys.exit(1)
|
||||
|
||||
with open(metadata_file, "r", encoding="utf-8") as f:
|
||||
with open(metadata_file, "r", encoding="utf-8") as f:
|
||||
meta = json.load(f)
|
||||
|
||||
VERSION_IPA = meta.get("version_ipa")
|
||||
VERSION_DATE = meta.get("version_date")
|
||||
IS_BETA = meta.get("is_beta")
|
||||
RELEASE_CHANNEL = meta.get("release_channel")
|
||||
SIZE = meta.get("size")
|
||||
SHA256 = meta.get("sha256")
|
||||
LOCALIZED_DESCRIPTION = meta.get("localized_description")
|
||||
DOWNLOAD_URL = meta.get("download_url")
|
||||
BUNDLE_IDENTIFIER = meta.get("bundle_identifier")
|
||||
print(" ====> Required parameter list <====")
|
||||
for k, v in meta.items():
|
||||
print(f"{k}: {v}")
|
||||
|
||||
print(" ====> Required parameter list <====")
|
||||
print("Bundle Identifier:", BUNDLE_IDENTIFIER)
|
||||
print("Version:", VERSION_IPA)
|
||||
print("Version Date:", VERSION_DATE)
|
||||
print("IsBeta:", IS_BETA)
|
||||
print("ReleaseChannel:", RELEASE_CHANNEL)
|
||||
print("Size:", SIZE)
|
||||
print("Sha256:", SHA256)
|
||||
print("Localized Description:", LOCALIZED_DESCRIPTION)
|
||||
print("Download URL:", DOWNLOAD_URL)
|
||||
required = [
|
||||
"bundle_identifier",
|
||||
"version_ipa",
|
||||
"version_date",
|
||||
"release_channel",
|
||||
"size",
|
||||
"sha256",
|
||||
"localized_description",
|
||||
"download_url",
|
||||
]
|
||||
|
||||
for r in required:
|
||||
if not meta.get(r):
|
||||
raise SystemExit("One or more required metadata fields missing")
|
||||
|
||||
meta["size"] = int(meta["size"])
|
||||
meta["release_channel"] = meta["release_channel"].lower()
|
||||
|
||||
return meta
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# validation
|
||||
# source loading
|
||||
# ----------------------------------------------------------
|
||||
|
||||
if (
|
||||
not BUNDLE_IDENTIFIER
|
||||
or not VERSION_IPA
|
||||
or not VERSION_DATE
|
||||
or not RELEASE_CHANNEL
|
||||
or not SIZE
|
||||
or not SHA256
|
||||
or not LOCALIZED_DESCRIPTION
|
||||
or not DOWNLOAD_URL
|
||||
):
|
||||
print("One or more required metadata fields missing")
|
||||
sys.exit(1)
|
||||
|
||||
SIZE = int(SIZE)
|
||||
RELEASE_CHANNEL = RELEASE_CHANNEL.lower()
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# load or create source.json
|
||||
# ----------------------------------------------------------
|
||||
|
||||
if source_file.exists():
|
||||
def load_source(source_file: Path):
|
||||
if source_file.exists():
|
||||
with open(source_file, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
else:
|
||||
else:
|
||||
print("source.json missing — creating minimal structure")
|
||||
data = {
|
||||
"version": 2,
|
||||
"apps": []
|
||||
}
|
||||
data = {"version": 2, "apps": []}
|
||||
|
||||
if int(data.get("version", 1)) < 2:
|
||||
print("Only v2 and above are supported")
|
||||
sys.exit(1)
|
||||
if int(data.get("version", 1)) < 2:
|
||||
raise SystemExit("Only v2 and above are supported")
|
||||
|
||||
return data
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# ensure app entry exists
|
||||
# locate app
|
||||
# ----------------------------------------------------------
|
||||
|
||||
apps = data.setdefault("apps", [])
|
||||
def ensure_app(data, bundle_id):
|
||||
apps = data.setdefault("apps", [])
|
||||
|
||||
app = next(
|
||||
(a for a in apps if a.get("bundleIdentifier") == BUNDLE_IDENTIFIER),
|
||||
None
|
||||
)
|
||||
app = next(
|
||||
(a for a in apps if a.get("bundleIdentifier") == bundle_id),
|
||||
None,
|
||||
)
|
||||
|
||||
if app is None:
|
||||
if app is None:
|
||||
print("App entry missing — creating new app entry")
|
||||
app = {
|
||||
"bundleIdentifier": BUNDLE_IDENTIFIER,
|
||||
"releaseChannels": []
|
||||
"bundleIdentifier": bundle_id,
|
||||
"releaseChannels": [],
|
||||
}
|
||||
apps.append(app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# update logic
|
||||
# update storefront
|
||||
# ----------------------------------------------------------
|
||||
|
||||
if RELEASE_CHANNEL == "stable":
|
||||
def update_storefront_if_needed(app, meta):
|
||||
if meta["release_channel"] == "stable":
|
||||
app.update({
|
||||
"version": VERSION_IPA,
|
||||
"versionDate": VERSION_DATE,
|
||||
"size": SIZE,
|
||||
"sha256": SHA256,
|
||||
"localizedDescription": LOCALIZED_DESCRIPTION,
|
||||
"downloadURL": DOWNLOAD_URL,
|
||||
"version": meta["version_ipa"],
|
||||
"versionDate": meta["version_date"],
|
||||
"size": meta["size"],
|
||||
"sha256": meta["sha256"],
|
||||
"localizedDescription": meta["localized_description"],
|
||||
"downloadURL": meta["download_url"],
|
||||
})
|
||||
|
||||
channels = app.setdefault("releaseChannels", [])
|
||||
|
||||
new_version = {
|
||||
"version": VERSION_IPA,
|
||||
"date": VERSION_DATE,
|
||||
"localizedDescription": LOCALIZED_DESCRIPTION,
|
||||
"downloadURL": DOWNLOAD_URL,
|
||||
"size": SIZE,
|
||||
"sha256": SHA256,
|
||||
}
|
||||
# ----------------------------------------------------------
|
||||
# update release channel (ORIGINAL FORMAT)
|
||||
# ----------------------------------------------------------
|
||||
|
||||
tracks = [t for t in channels if t.get("track") == RELEASE_CHANNEL]
|
||||
def update_release_channel(app, meta):
|
||||
channels = app.setdefault("releaseChannels", [])
|
||||
|
||||
if len(tracks) > 1:
|
||||
print(f"Multiple tracks named {RELEASE_CHANNEL}")
|
||||
sys.exit(1)
|
||||
new_version = {
|
||||
"version": meta["version_ipa"],
|
||||
"date": meta["version_date"],
|
||||
"localizedDescription": meta["localized_description"],
|
||||
"downloadURL": meta["download_url"],
|
||||
"size": meta["size"],
|
||||
"sha256": meta["sha256"],
|
||||
}
|
||||
|
||||
if not tracks:
|
||||
tracks = [
|
||||
t for t in channels
|
||||
if isinstance(t, dict)
|
||||
and t.get("track") == meta["release_channel"]
|
||||
]
|
||||
|
||||
if len(tracks) > 1:
|
||||
raise SystemExit(f"Multiple tracks named {meta['release_channel']}")
|
||||
|
||||
if not tracks:
|
||||
channels.insert(0, {
|
||||
"track": RELEASE_CHANNEL,
|
||||
"track": meta["release_channel"],
|
||||
"releases": [new_version],
|
||||
})
|
||||
else:
|
||||
tracks[0]["releases"][0] = new_version
|
||||
else:
|
||||
track = tracks[0]
|
||||
releases = track.setdefault("releases", [])
|
||||
|
||||
if not releases:
|
||||
releases.append(new_version)
|
||||
else:
|
||||
releases[0] = new_version
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# save
|
||||
# ----------------------------------------------------------
|
||||
|
||||
print("\nUpdated Sources File:\n")
|
||||
print(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
def save_source(source_file: Path, data):
|
||||
print("\nUpdated Sources File:\n")
|
||||
print(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
|
||||
with open(source_file, "w", encoding="utf-8") as f:
|
||||
source_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(source_file, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print("JSON successfully updated.")
|
||||
print("JSON successfully updated.")
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# main
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python3 update_apps.py <metadata.json> <source.json>")
|
||||
sys.exit(1)
|
||||
|
||||
metadata_file = Path(sys.argv[1])
|
||||
source_file = Path(sys.argv[2])
|
||||
|
||||
meta = load_metadata(metadata_file)
|
||||
data = load_source(source_file)
|
||||
|
||||
app = ensure_app(data, meta["bundle_identifier"])
|
||||
|
||||
update_storefront_if_needed(app, meta)
|
||||
update_release_channel(app, meta)
|
||||
|
||||
save_source(source_file, data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -6,10 +6,13 @@ import datetime
|
||||
from pathlib import Path
|
||||
import time
|
||||
import json
|
||||
|
||||
import re
|
||||
from posix import getcwd
|
||||
|
||||
# REPO ROOT relative to script dir
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SCRIPTS = ROOT / 'scripts/ci'
|
||||
BUILD_SETTINGS_OUTFILE = "project-build-settings.txt"
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# helpers
|
||||
@@ -55,92 +58,69 @@ def getenv(name, default=""):
|
||||
def short_commit():
|
||||
return runAndGet("git rev-parse --short HEAD")
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# BUILD NUMBER RESERVATION
|
||||
# ----------------------------------------------------------
|
||||
def count_new_commits(last_commit):
|
||||
if not last_commit or not last_commit.strip():
|
||||
return 0
|
||||
|
||||
def reserve_build_number(repo, max_attempts=5):
|
||||
repo = Path(repo).resolve()
|
||||
version_json = repo / "version.json"
|
||||
try:
|
||||
total = int(runAndGet("git rev-list --count HEAD"))
|
||||
if total == 1:
|
||||
head = runAndGet("git rev-parse HEAD")
|
||||
return 1 if head != last_commit else 0
|
||||
|
||||
def utc_now():
|
||||
return datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
def read():
|
||||
branch = runAndGet("git rev-parse --abbrev-ref HEAD", cwd=repo)
|
||||
|
||||
defaults = {
|
||||
"build": 0,
|
||||
"issued_at": utc_now(),
|
||||
"tag": branch,
|
||||
}
|
||||
|
||||
if version_json.exists():
|
||||
data = json.loads(version_json.read_text())
|
||||
else:
|
||||
data = {}
|
||||
|
||||
# fill missing fields
|
||||
for k, v in defaults.items():
|
||||
data.setdefault(k, v)
|
||||
|
||||
# ensure tag always tracks current branch
|
||||
data["tag"] = branch
|
||||
|
||||
version_json.write_text(json.dumps(data, indent=2) + "\n")
|
||||
return data
|
||||
|
||||
def write(data):
|
||||
version_json.write_text(json.dumps(data, indent=2) + "\n")
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
run("git fetch --depth=1 origin HEAD", check=False, cwd=repo)
|
||||
run("git reset --hard FETCH_HEAD", check=False, cwd=repo)
|
||||
|
||||
data = read()
|
||||
data["build"] += 1
|
||||
data["issued_at"] = utc_now()
|
||||
|
||||
write(data)
|
||||
|
||||
run("git add version.json", check=False, cwd=repo)
|
||||
|
||||
print("---- DEBUG reserve_build_number ----", file=sys.stderr)
|
||||
print(f"attempt: {attempt}", file=sys.stderr)
|
||||
print(f"data: {data!r}", file=sys.stderr)
|
||||
print(version_json.read_text(), file=sys.stderr)
|
||||
print("------------------------------------", file=sys.stderr)
|
||||
|
||||
run(f"git commit -m '{data['tag']} - build no: {data['build']}' || true", check=False, cwd=repo)
|
||||
|
||||
rc = subprocess.call("git push", shell=True, cwd=repo)
|
||||
|
||||
if rc == 0:
|
||||
print(f"Reserved build #{data['build']}", file=sys.stderr)
|
||||
return data["build"]
|
||||
|
||||
print("Push rejected, retrying...", file=sys.stderr)
|
||||
time.sleep(2)
|
||||
|
||||
raise SystemExit("Failed reserving build number")
|
||||
out = runAndGet(f"git rev-list --count {last_commit}..HEAD")
|
||||
return int(out)
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# MARKETING VERSION
|
||||
# PROJECT INFO
|
||||
# ----------------------------------------------------------
|
||||
def dump_project_settings(outdir=None):
|
||||
outfile = Path(outdir).resolve() / BUILD_SETTINGS_OUTFILE if outdir else BUILD_SETTINGS_OUTFILE
|
||||
run(f"xcodebuild -showBuildSettings 2>&1 > '{outfile}'")
|
||||
|
||||
def get_marketing_version():
|
||||
return runAndGet("grep MARKETING_VERSION Build.xcconfig | sed -e 's/MARKETING_VERSION = //g'")
|
||||
def _extract_setting(cmd):
|
||||
out = runAndGet(cmd + " || true").strip() # prevent grep failure from aborting
|
||||
return out if out else None
|
||||
|
||||
def set_marketing_version(qualified):
|
||||
run(
|
||||
f"sed -E "
|
||||
f"'s/^MARKETING_VERSION = .*/MARKETING_VERSION = {qualified}/' "
|
||||
f"-i '' {ROOT}/Build.xcconfig"
|
||||
def _read_dumped_build_setting(name):
|
||||
return _extract_setting(
|
||||
f"cat '{BUILD_SETTINGS_OUTFILE}' "
|
||||
f"| grep '{name} = ' "
|
||||
"| tail -1 "
|
||||
"| sed -e 's/.*= //g'"
|
||||
)
|
||||
|
||||
def compute_qualified_version(marketing, build_num, channel, short):
|
||||
date = datetime.datetime.now(datetime.UTC).strftime("%Y.%m.%d")
|
||||
return f"{marketing}-{channel}.{date}.{build_num}+{short}"
|
||||
def query_build_setting(name):
|
||||
return _extract_setting(
|
||||
f"xcodebuild -showBuildSettings 2>&1 "
|
||||
f"| grep '{name} = ' "
|
||||
"| tail -1 "
|
||||
"| sed -e 's/.*= //g'"
|
||||
)
|
||||
|
||||
def get_product_name(): return query_build_setting("PRODUCT_NAME")
|
||||
def get_bundle_id(): return query_build_setting("PRODUCT_BUNDLE_IDENTIFIER")
|
||||
def read_product_name(): return _read_dumped_build_setting("PRODUCT_NAME")
|
||||
def read_bundle_id(): return _read_dumped_build_setting("PRODUCT_BUNDLE_IDENTIFIER")
|
||||
|
||||
def get_marketing_version():
|
||||
return runAndGet(f"grep MARKETING_VERSION {ROOT}/Build.xcconfig | sed -e 's/MARKETING_VERSION = //g'")
|
||||
|
||||
def set_marketing_version(version):
|
||||
run(
|
||||
f"sed -E -i '' "
|
||||
f"'s/^MARKETING_VERSION = .*/MARKETING_VERSION = {version}/' "
|
||||
f"{ROOT}/Build.xcconfig"
|
||||
)
|
||||
|
||||
|
||||
def compute_normalized_version(marketing, build_num, short):
|
||||
now = datetime.datetime.now(datetime.UTC)
|
||||
date = now.strftime("%Y%m%d") # normalized date
|
||||
base = marketing.strip()
|
||||
return f"{base}-{date}.{build_num}+{short}"
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# CLEAN
|
||||
@@ -160,16 +140,12 @@ def clean_spm_cache():
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def build():
|
||||
run("make clean")
|
||||
run("rm -rf ~/Library/Developer/Xcode/DerivedData/*", check=False)
|
||||
run("mkdir -p build/logs")
|
||||
|
||||
run(
|
||||
"set -o pipefail && "
|
||||
"NSUnbufferedIO=YES make -B build "
|
||||
"2>&1 | tee -a build/logs/build.log | xcbeautify --renderer github-actions"
|
||||
)
|
||||
|
||||
run("make fakesign | tee -a build/logs/build.log")
|
||||
run("make ipa | tee -a build/logs/build.log")
|
||||
run("zip -r -9 ./SideStore.dSYMs.zip ./SideStore.xcarchive/dSYMs")
|
||||
@@ -239,13 +215,13 @@ def tests_run(model):
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def encrypt_logs(name):
|
||||
default_pwd = "12345"
|
||||
pwd = getenv("BUILD_LOG_ZIP_PASSWORD", default_pwd)
|
||||
|
||||
if pwd == default_pwd:
|
||||
print("Warning: BUILD_LOG_ZIP_PASSWORD not set, using fallback password", file=sys.stderr)
|
||||
|
||||
run(f'cd build/logs && zip -e -P "{pwd}" ../../{name}.zip *')
|
||||
pwd = getenv("BUILD_LOG_ZIP_PASSWORD")
|
||||
cwd = getcwd()
|
||||
if not pwd or not pwd.strip():
|
||||
print("BUILD_LOG_ZIP_PASSWORD not set — logs will be uploaded UNENCRYPTED", file=sys.stderr)
|
||||
run(f'cd {cwd}/build/logs && zip -r {cwd}/{name}.zip *')
|
||||
return
|
||||
run(f'cd {cwd}/build/logs && zip -e -P "{pwd}" {cwd}/{name}.zip *')
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# RELEASE NOTES
|
||||
@@ -253,62 +229,84 @@ def encrypt_logs(name):
|
||||
|
||||
def release_notes(tag):
|
||||
run(
|
||||
f"python3 generate_release_notes.py "
|
||||
f"python3 {SCRIPTS}/generate_release_notes.py "
|
||||
f"{tag} "
|
||||
f"--repo-root {ROOT} "
|
||||
f"--output-dir {ROOT}"
|
||||
)
|
||||
|
||||
def retrieve_release_notes(tag):
|
||||
return runAndGet(
|
||||
f"python3 {SCRIPTS}/generate_release_notes.py "
|
||||
f"--retrieve {tag} "
|
||||
f"--output-dir {ROOT}"
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# DEPLOY SOURCE.JSON
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def deploy(repo, source_json, release_tag, short_commit, marketing_version, version, channel, bundle_id, ipa_name):
|
||||
repo = Path(repo).resolve()
|
||||
def generate_metadata(release_tag, short_commit, marketing_version, channel, bundle_id, ipa_name, last_successful_commit=None):
|
||||
ipa_path = ROOT / ipa_name
|
||||
|
||||
if not repo.exists():
|
||||
raise SystemExit(f"{repo} repo missing")
|
||||
metadata = 'source-metadata.json'
|
||||
|
||||
if not ipa_path.exists():
|
||||
raise SystemExit(f"{ipa_path} missing")
|
||||
|
||||
run(f"pushd {repo}", check=True)
|
||||
try:
|
||||
# source_json is RELATIVE to repo
|
||||
if not Path(source_json).exists():
|
||||
raise SystemExit(f"{source_json} missing inside repo")
|
||||
|
||||
run(
|
||||
f"python3 {ROOT}/generate_source_metadata.py "
|
||||
cmd = (
|
||||
f"python3 {SCRIPTS}/generate_source_metadata.py "
|
||||
f"--repo-root {ROOT} "
|
||||
f"--ipa {ipa_path} "
|
||||
f"--output-dir . "
|
||||
f"--release-notes-dir . "
|
||||
f"--output-dir {ROOT} "
|
||||
f"--output-name {metadata} "
|
||||
f"--release-notes-dir {ROOT} "
|
||||
f"--release-tag {release_tag} "
|
||||
f"--version {version} "
|
||||
f"--marketing-version {marketing_version} "
|
||||
f"--short-commit {short_commit} "
|
||||
f"--release-channel {channel} "
|
||||
f"--bundle-id {bundle_id}"
|
||||
)
|
||||
|
||||
if last_successful_commit:
|
||||
cmd += f" --last-successful-commit {last_successful_commit}"
|
||||
|
||||
run(cmd)
|
||||
|
||||
def deploy(repo, source_json, release_tag, marketing_version):
|
||||
repo = (ROOT / repo).resolve()
|
||||
source_json_path = repo / source_json
|
||||
metadata = 'source-metadata.json'
|
||||
|
||||
if not repo.exists():
|
||||
raise SystemExit(f"{repo} repo missing")
|
||||
|
||||
if not (repo / ".git").exists():
|
||||
print("Repo is not a git repository, skipping deploy", file=sys.stderr)
|
||||
return
|
||||
|
||||
if not source_json_path.exists():
|
||||
raise SystemExit(f"{source_json} missing inside repo")
|
||||
|
||||
run("git config user.name 'GitHub Actions'", check=False)
|
||||
run("git config user.email 'github-actions@github.com'", check=False)
|
||||
|
||||
run(f"python3 {ROOT}/scripts/update_source_metadata.py '{source_json}'")
|
||||
# ------------------------------------------------------
|
||||
run("git fetch origin main", check=False, cwd=repo)
|
||||
run("git switch main || git switch -c main origin/main", cwd=repo)
|
||||
run("git reset --hard origin/main", cwd=repo)
|
||||
# ------------------------------------------------------
|
||||
|
||||
max_attempts = 5
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
run("git fetch --depth=1 origin HEAD", check=False)
|
||||
run("git reset --hard FETCH_HEAD", check=False)
|
||||
if attempt > 1:
|
||||
run("git fetch --depth=1 origin HEAD", check=False, cwd=repo)
|
||||
run("git reset --hard FETCH_HEAD", check=False, cwd=repo)
|
||||
|
||||
# regenerate after reset so we don't lose changes
|
||||
run(f"python3 {ROOT}/scripts/update_source_metadata.py '{source_json}'")
|
||||
run(f"git add --verbose {source_json}", check=False)
|
||||
run(f"git commit -m '{release_tag} - deployed {version}' || true", check=False)
|
||||
run(f"python3 {SCRIPTS}/update_source_metadata.py '{ROOT}/{metadata}' '{source_json_path}'", cwd=repo)
|
||||
run(f"git add --verbose {source_json}", cwd=repo)
|
||||
run(f"git commit -m '{release_tag} - deployed {marketing_version}' || true", cwd=repo)
|
||||
|
||||
rc = subprocess.call("git push", shell=True)
|
||||
rc = subprocess.call("git push", shell=True, cwd=repo)
|
||||
|
||||
if rc == 0:
|
||||
print("Deploy push succeeded", file=sys.stderr)
|
||||
@@ -319,8 +317,159 @@ def deploy(repo, source_json, release_tag, short_commit, marketing_version, vers
|
||||
else:
|
||||
raise SystemExit("Deploy push failed after retries")
|
||||
|
||||
finally:
|
||||
run("popd", check=False)
|
||||
|
||||
def last_successful_commit(is_stable, tag=None):
|
||||
is_stable = str(is_stable).lower() in ("1", "true", "yes")
|
||||
|
||||
try:
|
||||
if is_stable:
|
||||
prev_tag = runAndGet(
|
||||
r'git tag --sort=-v:refname '
|
||||
r'| grep -E "^[0-9]+\.[0-9]+\.[0-9]+$" '
|
||||
r'| sed -n "2p" || true'
|
||||
).strip()
|
||||
|
||||
if prev_tag:
|
||||
return runAndGet(f'git rev-parse "{prev_tag}^{{commit}}"')
|
||||
|
||||
return None # ← changed
|
||||
|
||||
if tag:
|
||||
exists = subprocess.call(
|
||||
f'git rev-parse -q --verify "refs/tags/{tag}"',
|
||||
shell=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
) == 0
|
||||
|
||||
if exists:
|
||||
return runAndGet(f'git rev-parse "{tag}^{{commit}}"')
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def upload_release(release_name, release_tag, commit_sha, repo, upstream_tag_recommended, is_stable=False):
|
||||
is_stable = str(is_stable).lower() in ("1", "true", "yes")
|
||||
draft = False
|
||||
prerelease = True
|
||||
latest = False
|
||||
|
||||
if is_stable:
|
||||
prerelease = False
|
||||
latest = True
|
||||
|
||||
token = getenv("GH_TOKEN")
|
||||
if token:
|
||||
os.environ["GH_TOKEN"] = token
|
||||
|
||||
metadata_path = ROOT / "source-metadata.json"
|
||||
|
||||
if not metadata_path.exists():
|
||||
raise SystemExit("source-metadata.json missing")
|
||||
|
||||
meta = json.loads(metadata_path.read_text())
|
||||
|
||||
marketing_version = meta.get("version_ipa")
|
||||
build_datetime = meta.get("version_date")
|
||||
|
||||
dt = datetime.datetime.fromisoformat(
|
||||
build_datetime.replace("Z", "+00:00")
|
||||
)
|
||||
built_time = dt.strftime("%a %b %d %H:%M:%S %Y")
|
||||
built_date = dt.strftime("%Y-%m-%d")
|
||||
|
||||
release_notes = runAndGet(
|
||||
f"python3 {SCRIPTS}/generate_release_notes.py "
|
||||
f"--retrieve {release_tag} "
|
||||
f"--output-dir {ROOT}"
|
||||
)
|
||||
|
||||
if is_stable:
|
||||
release_notes = re.sub(
|
||||
r'(?im)^[ \t]*#{1,6}[ \t]*what[’\']?s[ \t]+changed[ \t]*$',
|
||||
"## What's Changed",
|
||||
release_notes,
|
||||
flags=re.IGNORECASE | re.MULTILINE,
|
||||
)
|
||||
|
||||
upstream_block = ""
|
||||
if upstream_tag_recommended and upstream_tag_recommended.strip():
|
||||
tag = upstream_tag_recommended.strip()
|
||||
upstream_block = (
|
||||
f"If you want to try out new features early but want a lower chance of bugs, "
|
||||
f"you can look at [{repo} {tag}]"
|
||||
f"(https://github.com/{repo}/releases?q={tag}).\n\n"
|
||||
)
|
||||
|
||||
header = getFormattedUploadMsg(
|
||||
release_name, commit_sha, repo, upstream_block,
|
||||
built_time, built_date, marketing_version, is_stable,
|
||||
)
|
||||
|
||||
body = header + release_notes.lstrip() + "\n"
|
||||
|
||||
body_file = ROOT / "release_body.md"
|
||||
body_file.write_text(body, encoding="utf-8")
|
||||
|
||||
draft_flag = "--draft" if draft else ""
|
||||
prerelease_flag = "--prerelease" if prerelease else ""
|
||||
latest_flag = "--latest=true" if latest else ""
|
||||
|
||||
# create release if it doesn't exist
|
||||
exists = subprocess.call(
|
||||
f'gh release view "{release_tag}"',
|
||||
shell=True,
|
||||
cwd=ROOT,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
) == 0
|
||||
|
||||
if exists:
|
||||
run(
|
||||
f'gh release edit "{release_tag}" '
|
||||
f'--title "{release_name}" '
|
||||
f'--notes-file "{body_file}" '
|
||||
f'{draft_flag} {prerelease_flag} {latest_flag}'
|
||||
)
|
||||
else:
|
||||
run(
|
||||
f'gh release create "{release_tag}" '
|
||||
f'--title "{release_name}" '
|
||||
f'--notes-file "{body_file}" '
|
||||
f'{draft_flag} {prerelease_flag} {latest_flag}'
|
||||
)
|
||||
|
||||
run(
|
||||
f'gh release upload "{release_tag}" '
|
||||
f'SideStore.ipa SideStore.dSYMs.zip build-logs.zip '
|
||||
f'--clobber'
|
||||
)
|
||||
|
||||
run(f'git tag -f "{release_tag}" "{commit_sha}"')
|
||||
run(f'git push origin "refs/tags/{release_tag}" --force')
|
||||
|
||||
|
||||
def getFormattedUploadMsg(release_name, commit_sha, repo, upstream_block, built_time, built_date, marketing_version, is_stable):
|
||||
experimental_header = ""
|
||||
if not is_stable:
|
||||
experimental_header = f"""
|
||||
This is an ⚠️ **EXPERIMENTAL** ⚠️ {release_name} build for commit [{commit_sha}](https://github.com/{repo}/commit/{commit_sha}).
|
||||
|
||||
{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!**
|
||||
|
||||
""".lstrip("\n")
|
||||
|
||||
header = f"""
|
||||
{experimental_header}{upstream_block}## Build Info
|
||||
|
||||
Built at (UTC): `{built_time}`
|
||||
Built at (UTC date): `{built_date}`
|
||||
Commit SHA: `{commit_sha}`
|
||||
Version: `{marketing_version}`
|
||||
""".lstrip("\n")
|
||||
return header
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# ENTRYPOINT
|
||||
@@ -330,15 +479,20 @@ COMMANDS = {
|
||||
# ----------------------------------------------------------
|
||||
# SHARED
|
||||
# ----------------------------------------------------------
|
||||
"commid-id" : (short_commit, 0, ""),
|
||||
"commit-id" : (short_commit, 0, ""),
|
||||
"count-new-commits" : (count_new_commits, 1, "<last_successful_commit>"),
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# VERSION / MARKETING
|
||||
# PROJECT INFO
|
||||
# ----------------------------------------------------------
|
||||
"get-marketing-version" : (get_marketing_version, 0, ""),
|
||||
"set-marketing-version" : (set_marketing_version, 1, "<qualified_version>"),
|
||||
"compute-qualified" : (compute_qualified_version, 4, "<marketing> <build_num> <channel> <short_commit>"),
|
||||
"reserve_build_number" : (reserve_build_number, 1, "<repo>"),
|
||||
"set-marketing-version" : (set_marketing_version, 1, "<normalized_version>"),
|
||||
"compute-normalized" : (compute_normalized_version,3, "<marketing> <build_num> <short_commit>"),
|
||||
"get-product-name" : (get_product_name, 0, ""),
|
||||
"get-bundle-id" : (get_bundle_id, 0, ""),
|
||||
"dump-project-settings" : (dump_project_settings, 0, ""),
|
||||
"read-product-name" : (read_product_name, 0, ""),
|
||||
"read-bundle-id" : (read_bundle_id, 0, ""),
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# CLEAN
|
||||
@@ -363,16 +517,22 @@ COMMANDS = {
|
||||
# ----------------------------------------------------------
|
||||
# LOG ENCRYPTION
|
||||
# ----------------------------------------------------------
|
||||
"encrypt-build" : (lambda: encrypt_logs("encrypted-build-logs"), 0, ""),
|
||||
"encrypt-tests-build" : (lambda: encrypt_logs("encrypted-tests-build-logs"), 0, ""),
|
||||
"encrypt-tests-run" : (lambda: encrypt_logs("encrypted-tests-run-logs"), 0, ""),
|
||||
"encrypt-build" : (lambda: encrypt_logs("build-logs"), 0, ""),
|
||||
"encrypt-tests-build" : (lambda: encrypt_logs("tests-build-logs"), 0, ""),
|
||||
"encrypt-tests-run" : (lambda: encrypt_logs("tests-run-logs"), 0, ""),
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# RELEASE / DEPLOY
|
||||
# ----------------------------------------------------------
|
||||
"last-successful-commit" : (last_successful_commit, 1, "<is_stable> [tag]"),
|
||||
"release-notes" : (release_notes, 1, "<tag>"),
|
||||
"deploy" : (deploy, 9, "<repo> <source_json> <release_tag> <short_commit> <marketing_version> <version> <channel> <bundle_id> <ipa_name>"),
|
||||
}
|
||||
"retrieve-release-notes" : (retrieve_release_notes, 1, "<tag>"),
|
||||
"generate-metadata" : (generate_metadata, 7,
|
||||
"<release_tag> <short_commit> <marketing_version> <channel> <bundle_id> <ipa_name> [last_successful_commit]"),
|
||||
"deploy" : (deploy, 4,
|
||||
"<repo> <source_json> <release_tag> <marketing_version>"),
|
||||
"upload-release" : (upload_release, 5,
|
||||
"<release_name> <release_tag> <commit_sha> <repo> <upstream_tag_recommended> [is_stable]"),}
|
||||
|
||||
def main():
|
||||
def usage():
|
||||
@@ -398,9 +558,14 @@ def main():
|
||||
suffix = f" {arg_usage}" if arg_usage else ""
|
||||
raise SystemExit(f"Usage: workflow.py {cmd}{suffix}")
|
||||
|
||||
args = sys.argv[2:2 + argc]
|
||||
func(*args) if argc else func()
|
||||
args = sys.argv[2:]
|
||||
|
||||
result = func(*args) if args else func()
|
||||
|
||||
# ONLY real outputs go to stdout
|
||||
if result is not None:
|
||||
sys.stdout.write(str(result))
|
||||
sys.stdout.flush()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -75,6 +75,10 @@
|
||||
{
|
||||
"identifier": "thatstel.la.altsource",
|
||||
"sourceURL": "https://alt.thatstel.la/"
|
||||
},
|
||||
{
|
||||
"identifier": "com.deliacheminot.mona",
|
||||
"sourceURL": "https://raw.githubusercontent.com/delia-cheminot/mona-hrt/refs/heads/main/ios_source.json"
|
||||
}
|
||||
],
|
||||
"sources": [
|
||||
@@ -148,6 +152,10 @@
|
||||
{
|
||||
"identifier": "thatstel.la.altsource",
|
||||
"sourceURL": "https://alt.thatstel.la/"
|
||||
},
|
||||
{
|
||||
"identifier": "com.deliacheminot.mona",
|
||||
"sourceURL": "https://raw.githubusercontent.com/delia-cheminot/mona-hrt/refs/heads/main/ios_source.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user