diff --git a/README.md b/README.md index 2102342..f60126c 100644 --- a/README.md +++ b/README.md @@ -367,10 +367,18 @@ Build an unsigned IPA on a Mac with Xcode and xcodegen (`brew install xcodegen`) ``` cd ios -scripts/build-ipa.sh # writes build/Stemify.ipa +scripts/build-ipa.sh # writes build/stemify-.ipa ``` -Sideload `build/Stemify.ipa` with Sideloadly or AltStore, which sign it with your Apple ID. To work in Xcode instead, run `xcodegen generate` and open `Stemify.xcodeproj`. +Sideload the IPA with Sideloadly or AltStore, which sign it with your Apple ID. + +To get updates in AltStore, SideStore or Feather, add this source: + +``` +https://git.iamchrisama.com/cdnutter/stemify/raw/branch/main/ios/source.json +``` + +After uploading a new release (tag `ios-v`, asset `stemify-.ipa`), run `ios/scripts/make-source.py` to regenerate `ios/source.json` and commit it. To work in Xcode instead, run `xcodegen generate` and open `Stemify.xcodeproj`. CarPlay and Siri need the `carplay-audio` and `siri` entitlements. A free Apple ID can't sign those, so on a free sideload the app works on the phone but won't show up in CarPlay or Siri. A paid developer account covers Siri; CarPlay audio also needs Apple to approve the entitlement for your team. diff --git a/ios/project.yml b/ios/project.yml index caff9ed..9b188dd 100644 --- a/ios/project.yml +++ b/ios/project.yml @@ -1,13 +1,13 @@ name: Stemify options: - bundleIdPrefix: com.stemify + bundleIdPrefix: org.cdnutter deploymentTarget: iOS: "17.0" settings: base: SWIFT_VERSION: "5.0" - MARKETING_VERSION: "1.0.2" - CURRENT_PROJECT_VERSION: "3" + MARKETING_VERSION: "1.0.3" + CURRENT_PROJECT_VERSION: "4" targets: Stemify: type: application @@ -15,7 +15,7 @@ targets: sources: [Stemify] settings: base: - PRODUCT_BUNDLE_IDENTIFIER: com.stemify.mobile + PRODUCT_BUNDLE_IDENTIFIER: org.cdnutter.stemify TARGETED_DEVICE_FAMILY: "1" ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon CODE_SIGN_ENTITLEMENTS: Stemify/Stemify.entitlements diff --git a/ios/scripts/build-ipa.sh b/ios/scripts/build-ipa.sh index 695c00e..f4bc4f6 100755 --- a/ios/scripts/build-ipa.sh +++ b/ios/scripts/build-ipa.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Builds an unsigned Stemify.ipa on a Mac, for re-signing with Sideloadly/AltStore. +# Builds an unsigned build/stemify-.ipa on a Mac, for re-signing with Sideloadly/AltStore. # Uses swiftc directly so it works even when xcodebuild/actool need -runFirstLaunch. set -euo pipefail cd "$(dirname "$0")/.." @@ -7,14 +7,17 @@ export PATH=/opt/homebrew/bin:$PATH OUT=build; APP=$OUT/Payload/Stemify.app rm -rf $OUT && mkdir -p $APP SDK=$(xcrun --sdk iphoneos --show-sdk-path) +# Version and build number come from project.yml. +VERSION=$(sed -nE 's/^ *MARKETING_VERSION: "(.*)"/\1/p' project.yml) +BUILD=$(sed -nE 's/^ *CURRENT_PROJECT_VERSION: "(.*)"/\1/p' project.yml) xcrun -sdk iphoneos swiftc -target arm64-apple-ios17.0 -sdk "$SDK" -O -wmo -swift-version 5 \ -module-name Stemify -parse-as-library $(find Stemify -name '*.swift') -o $APP/Stemify # Info.plist: xcodegen's generated one with the build settings filled in. xcodegen generate -q -sed -e 's/$(DEVELOPMENT_LANGUAGE)/en/; s/$(EXECUTABLE_NAME)/Stemify/; s/$(PRODUCT_BUNDLE_IDENTIFIER)/com.stemify.mobile/' \ - -e 's/$(PRODUCT_NAME)/Stemify/; s/$(MARKETING_VERSION)/1.0.2/; s/$(CURRENT_PROJECT_VERSION)/3/' \ +sed -e 's/$(DEVELOPMENT_LANGUAGE)/en/; s/$(EXECUTABLE_NAME)/Stemify/; s/$(PRODUCT_BUNDLE_IDENTIFIER)/org.cdnutter.stemify/' \ + -e "s/\$(PRODUCT_NAME)/Stemify/; s/\$(MARKETING_VERSION)/$VERSION/; s/\$(CURRENT_PROJECT_VERSION)/$BUILD/" \ -e 's/$(PRODUCT_MODULE_NAME)/Stemify/' Stemify/Info.plist > $APP/Info.plist P=$APP/Info.plist plutil -insert MinimumOSVersion -string 17.0 $P @@ -31,5 +34,5 @@ SRC=Stemify/Resources/Assets.xcassets/AppIcon.appiconset/icon.png sips -z 120 120 $SRC --out $APP/AppIcon60x60@2x.png >/dev/null sips -z 180 180 $SRC --out $APP/AppIcon60x60@3x.png >/dev/null -(cd $OUT && zip -qry Stemify.ipa Payload) -echo "$OUT/Stemify.ipa" +(cd $OUT && zip -qry stemify-$VERSION.ipa Payload) +echo "$OUT/stemify-$VERSION.ipa" diff --git a/ios/scripts/make-source.py b/ios/scripts/make-source.py new file mode 100755 index 0000000..06e0732 --- /dev/null +++ b/ios/scripts/make-source.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Writes ios/source.json, an AltStore source (SideStore and Feather read it too), from the +ios-v* releases on Gitea. Only releases with a stemify-.ipa are listed, since older +ones used a different bundle ID. Set GITEA_TOKEN if the repo is private.""" +import json, os, re, urllib.request + +REPO = "https://git.iamchrisama.com/cdnutter/stemify" +API = "https://git.iamchrisama.com/api/v1/repos/cdnutter/stemify" +RAW = f"{REPO}/raw/branch/main" +HERE = os.path.dirname(os.path.abspath(__file__)) + + +def get(url): + req = urllib.request.Request(url) + if os.environ.get("GITEA_TOKEN"): + req.add_header("Authorization", "token " + os.environ["GITEA_TOKEN"]) + with urllib.request.urlopen(req) as r: + return json.load(r) + + +yml = open(os.path.join(HERE, "..", "project.yml")).read() +builds = {} # only the current build number is known locally; older ones fall back to the version +m = re.search(r'MARKETING_VERSION: "(.*)"', yml), re.search(r'CURRENT_PROJECT_VERSION: "(.*)"', yml) +builds[m[0][1]] = m[1][1] + +versions = [] +for rel in get(API + "/releases?limit=50"): + v = rel["tag_name"].removeprefix("ios-v") + ipa = next((a for a in rel["assets"] if a["name"] == f"stemify-{v}.ipa"), None) + if not rel["tag_name"].startswith("ios-v") or not ipa: + continue + versions.append({ + "version": v, + "buildVersion": builds.get(v, v), + "date": rel["published_at"], + "localizedDescription": rel["body"].strip() or f"Stemify {v}", + "downloadURL": ipa["browser_download_url"], + "size": ipa["size"], + "minOSVersion": "17.0", + }) + +source = { + "name": "Stemify", + "identifier": "org.cdnutter.stemify.source", + "sourceURL": f"{RAW}/ios/source.json", + "iconURL": f"{RAW}/ios/Stemify/Resources/Assets.xcassets/AppIcon.appiconset/icon.png", + "website": REPO, + "apps": [{ + "name": "Stemify", + "bundleIdentifier": "org.cdnutter.stemify", + "developerName": "cdnutter", + "subtitle": "Plex music player with live stem mixing.", + "localizedDescription": "Plays your Plex library and lets you mute vocals or remix stems on the fly. " + "Works offline with downloads, and in the car with CarPlay.", + "iconURL": f"{RAW}/ios/Stemify/Resources/Assets.xcassets/AppIcon.appiconset/icon.png", + "tintColor": "#FA2D48", + "category": "entertainment", + "versions": versions, + "appPermissions": { + "entitlements": ["com.apple.developer.carplay-audio", "com.apple.developer.siri"], + "privacy": { + "NSLocalNetworkUsageDescription": "Stemify talks to Plex and the stems server on your home network.", + "NSSiriUsageDescription": "Ask Siri to play music from your Plex library.", + }, + }, + }], + "news": [], +} + +with open(os.path.join(HERE, "..", "source.json"), "w") as f: + json.dump(source, f, indent=2) + f.write("\n") +print(f"source.json: {len(versions)} version(s)")