iOS: release/dev/prov builds, publish.sh, dev icon variants
release = org.cdnutter.<name> (main, Gitea release + Sideload) dev = org.cdnutter.<name>-dev (dev branch, Gitea pre-release + Sideload Developer) prov = org.cdnutter.<name>-prov (personal signed, never published) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fabca5d1aa
commit
ec1cbdfe75
9 files changed
+132
-58
No files matched your search
@@ -338,12 +338,20 @@ docs/specs.txt original design doc for the desktop separator
|
||||
|
||||
```sh
|
||||
cd ios
|
||||
scripts/build-ipa.sh # unsigned build/public/stemify-<version>.ipa
|
||||
scripts/build-remote.sh # from DevOps: build on White-M1 and pull IPAs back to build/{public,prov}
|
||||
scripts/build-remote.sh # from DevOps: dev build on WHITE-M1, pulled back to build/dev
|
||||
scripts/build-remote.sh release # release build, build/release
|
||||
scripts/build-remote.sh prov # personal signed build, build/prov
|
||||
scripts/build-ipa.sh release|dev|prov # same thing directly on the Mac
|
||||
xcodegen generate # or open Stemify.xcodeproj in Xcode
|
||||
```
|
||||
|
||||
The version lives in `ios/project.yml` (`MARKETING_VERSION` and `CURRENT_PROJECT_VERSION`). To release, tag `ios-v<version>`, attach `stemify-<version>.ipa`, then run `GITEA_TOKEN=... ios/scripts/make-source.py` and commit the new `ios/source.json`.
|
||||
There are three builds, each with its own bundle ID so they install side by side:
|
||||
|
||||
- `org.cdnutter.stemify` (build/release): releases from `main`. `scripts/publish.sh release` tags `ios-v<version>`, uploads the IPA to Gitea and Sideload, and regenerates `ios/source.json`.
|
||||
- `org.cdnutter.stemify-dev` (build/dev): "Stemify Dev" with the ribbon icon, built from the `dev` branch. `scripts/publish.sh dev` makes a Gitea pre-release `ios-v<version>-dev.<build>` and uploads it to Sideload Developer. Bump `CURRENT_PROJECT_VERSION` for each one.
|
||||
- `org.cdnutter.stemify-prov` (build/prov): "Stemify Prov", signed with the personal profile in `ios/prov/` so CarPlay and Siri work. Never published.
|
||||
|
||||
The version lives in `ios/project.yml` (`VERSION_LABEL`, `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION`).
|
||||
|
||||
**Android** needs JDK 21 and the Android SDK (`android/local.properties` points at it):
|
||||
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
{"images":[{"filename":"icon.png","idiom":"universal","platform":"ios","size":"1024x1024"}],"info":{"author":"xcode","version":1}}
|
||||
{
|
||||
"images" : [
|
||||
{ "filename" : "icon.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" },
|
||||
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon-dark.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" },
|
||||
{ "appearances" : [ { "appearance" : "luminosity", "value" : "tinted" } ], "filename" : "icon-tinted.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
+36
-38
@@ -1,23 +1,37 @@
|
||||
#!/bin/bash
|
||||
# Builds build/public/stemify-<version>.ipa on a Mac: ad-hoc signed, for releases and re-signing with Sideloadly/AltStore.
|
||||
# With prov/Stemify.mobileprovision, prov/key.p12 and P12_PASS set, it also writes a signed
|
||||
# build/prov/stemify-<version>-signed.ipa (keeps CarPlay/Siri) for the owner's own device.
|
||||
# Finished IPAs are kept per version; build/work is scratch and is wiped each run.
|
||||
# "build-ipa.sh dev" builds only build/dev/stemify-<version>-dev.ipa: bundle id org.cdnutter.stemify-dev,
|
||||
# named "Stemify Dev", ad-hoc signed, so it installs next to the release app. For internal tests, never released.
|
||||
# "build-ipa.sh prov" builds only the signed prov IPA (no public one).
|
||||
# ios/scripts/build-remote.sh runs this on White-M1 and copies build/public and build/prov back to DevOps.
|
||||
# Builds one Stemify IPA on a Mac. ios/scripts/build-remote.sh runs this on WHITE-M1 from DevOps.
|
||||
# build-ipa.sh release build/release/stemify-<v>.ipa org.cdnutter.stemify, ad-hoc signed. Gitea releases (main) + Sideload.
|
||||
# build-ipa.sh dev build/dev/stemify-<v>-dev.ipa org.cdnutter.stemify-dev, "Stemify Dev", ribbon icon.
|
||||
# Gitea pre-releases (dev branch) + Sideload Developer.
|
||||
# build-ipa.sh prov build/prov/stemify-<v>-prov.ipa org.cdnutter.stemify-prov, "Stemify Prov", signed with the
|
||||
# personal profile (CarPlay, Siri). Never published anywhere.
|
||||
# prov needs prov/Stemify.mobileprovision (App ID org.cdnutter.stemify-prov), prov/key.p12 and P12_PASS.
|
||||
# prov/StemifyWidgets.mobileprovision (org.cdnutter.stemify-prov.widgets) is optional, without it the widgets are left out.
|
||||
# build/work is scratch and is wiped each run.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
export PATH=/opt/homebrew/bin:$PATH
|
||||
B=build; OUT=$B/work; APP=$OUT/Payload/Stemify.app; EXT=$APP/PlugIns/StemifyWidgets.appex
|
||||
MODE=${1:-release}
|
||||
if [ "$MODE" = dev ]; then APP_ID=org.cdnutter.stemify-dev; APP_NAME="Stemify Dev"; APP_ICON=AppIconDev; DD=$B/dd-dev
|
||||
else APP_ID=org.cdnutter.stemify; APP_NAME=Stemify; APP_ICON=AppIcon; DD=$B/dd; fi
|
||||
rm -rf $OUT && mkdir -p $OUT/Payload $B/public $B/prov $B/dev
|
||||
MODE=${1:-dev}
|
||||
case $MODE in
|
||||
release) APP_ID=org.cdnutter.stemify; APP_NAME=Stemify; APP_ICON=AppIcon; SUFFIX= ;;
|
||||
dev) APP_ID=org.cdnutter.stemify-dev; APP_NAME="Stemify Dev"; APP_ICON=AppIconDev; SUFFIX=-dev ;;
|
||||
prov) APP_ID=org.cdnutter.stemify-prov; APP_NAME="Stemify Prov"; APP_ICON=AppIcon; SUFFIX=-prov ;;
|
||||
*) echo "usage: $0 release|dev|prov" >&2; exit 1 ;;
|
||||
esac
|
||||
DD=$B/dd-$MODE
|
||||
# "1.0 RC 1" -> 1.0-rc1, used in file names and release tags.
|
||||
VERSION=$(sed -nE 's/^ *VERSION_LABEL: "(.*)"/\1/p' project.yml | tr 'A-Z' 'a-z' | sed -E 's/ rc /-rc/; s/ /-/g')
|
||||
VERSION=$(sed -nE 's/^ *VERSION_LABEL: "(.*)"/\1/p' project.yml | tr 'A-Z' 'a-z' | sed -E 's/ (rc|beta) /-\1/; s/ /-/g')
|
||||
IPA=$B/$MODE/stemify-$VERSION$SUFFIX.ipa
|
||||
|
||||
if [ "$MODE" = prov ]; then
|
||||
for f in prov/Stemify.mobileprovision prov/key.p12; do [ -f $f ] || { echo "missing $f" >&2; exit 1; }; done
|
||||
[ -n "${P12_PASS:-}" ] || { echo "P12_PASS not set" >&2; exit 1; }
|
||||
PROF_ID=$(security cms -D -i prov/Stemify.mobileprovision | plutil -extract Entitlements.application-identifier raw -)
|
||||
[ "${PROF_ID#*.}" = "$APP_ID" ] || { echo "prov/Stemify.mobileprovision is for ${PROF_ID#*.}, need a profile for $APP_ID" >&2; exit 1; }
|
||||
fi
|
||||
|
||||
rm -rf $OUT && mkdir -p $OUT/Payload $B/$MODE
|
||||
# xcodebuild compiles the app, the widget extension, the asset catalog and the App Intents metadata.
|
||||
xcodegen generate -q
|
||||
xcodebuild -project Stemify.xcodeproj -scheme Stemify -configuration Release -sdk iphoneos \
|
||||
@@ -25,30 +39,16 @@ xcodebuild -project Stemify.xcodeproj -scheme Stemify -configuration Release -sd
|
||||
cp -R $DD/Build/Products/Release-iphoneos/Stemify.app $APP
|
||||
[ -d $EXT ] || { echo "widget extension missing" >&2; exit 1; }
|
||||
|
||||
# Ad-hoc signature carrying only the App Group, so Sideloadly/AltStore keep the widgets' shared data when re-signing.
|
||||
cat > $OUT/group.plist <<PL
|
||||
if [ "$MODE" != prov ]; then
|
||||
# Ad-hoc signature carrying only the App Group, so Sideloadly/AltStore keep the widgets' shared data when re-signing.
|
||||
cat > $OUT/group.plist <<PL
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0"><dict><key>com.apple.security.application-groups</key><array><string>group.$APP_ID</string></array></dict></plist>
|
||||
PL
|
||||
codesign -f -s - --entitlements $OUT/group.plist $EXT
|
||||
codesign -f -s - --entitlements $OUT/group.plist $APP
|
||||
|
||||
if [ "$MODE" = dev ]; then
|
||||
rm -f $B/dev/stemify-$VERSION-dev.ipa
|
||||
(cd $OUT && zip -qry ../dev/stemify-$VERSION-dev.ipa Payload)
|
||||
echo "$B/dev/stemify-$VERSION-dev.ipa"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Public build, for the releases and AltStore/Sideloadly.
|
||||
if [ "$MODE" != prov ]; then
|
||||
rm -f $B/public/stemify-$VERSION.ipa
|
||||
(cd $OUT && zip -qry ../public/stemify-$VERSION.ipa Payload)
|
||||
echo "$B/public/stemify-$VERSION.ipa"
|
||||
fi
|
||||
|
||||
if [ -f prov/Stemify.mobileprovision ] && [ -f prov/key.p12 ] && [ -n "${P12_PASS:-}" ]; then
|
||||
codesign -f -s - --entitlements $OUT/group.plist $EXT
|
||||
codesign -f -s - --entitlements $OUT/group.plist $APP
|
||||
else
|
||||
KC=$PWD/$OUT/sign.keychain-db; KP=$(uuidgen)
|
||||
security create-keychain -p "$KP" $KC
|
||||
security set-keychain-settings -lut 3600 $KC
|
||||
@@ -62,7 +62,6 @@ if [ -f prov/Stemify.mobileprovision ] && [ -f prov/key.p12 ] && [ -n "${P12_PAS
|
||||
ID=$(security find-identity -v -p codesigning $KC | sed -nE 's/.*"(Apple Development:.*)"/\1/p' | head -1)
|
||||
security cms -D -i prov/Stemify.mobileprovision > $OUT/prov.plist
|
||||
/usr/libexec/PlistBuddy -x -c 'Print :Entitlements' $OUT/prov.plist > $OUT/ent.plist
|
||||
# The widgets need their own profile (org.cdnutter.stemify.widgets with the App Group). Without one they're left out.
|
||||
if [ -f prov/StemifyWidgets.mobileprovision ]; then
|
||||
security cms -D -i prov/StemifyWidgets.mobileprovision > $OUT/wprov.plist
|
||||
/usr/libexec/PlistBuddy -x -c 'Print :Entitlements' $OUT/wprov.plist > $OUT/went.plist
|
||||
@@ -75,9 +74,8 @@ if [ -f prov/Stemify.mobileprovision ] && [ -f prov/key.p12 ] && [ -n "${P12_PAS
|
||||
codesign -f -s "$ID" --entitlements $OUT/ent.plist --timestamp=none $APP
|
||||
security list-keychains -d user -s $(security list-keychains -d user | tr -d "\"" | grep -v sign.keychain)
|
||||
security delete-keychain $KC
|
||||
# Personal build for this device only: stays in build/prov (gitignored), never released.
|
||||
rm -f $B/prov/stemify-$VERSION-signed.ipa
|
||||
(cd $OUT && zip -qry ../prov/stemify-$VERSION-signed.ipa Payload)
|
||||
echo "$B/prov/stemify-$VERSION-signed.ipa (signed as $ID)"
|
||||
fi
|
||||
|
||||
rm -f $IPA
|
||||
(cd $OUT && zip -qry ../$MODE/$(basename $IPA) Payload)
|
||||
echo "$IPA"
|
||||
@@ -1,23 +1,17 @@
|
||||
#!/bin/bash
|
||||
# Run from DevOps: syncs ios/ to White-M1, builds there, and pulls the IPAs back into ios/build.
|
||||
# build-remote.sh dev + prov (the usual build): build/dev and build/prov
|
||||
# build-remote.sh public public + prov, only when a public release is asked for: build/public and build/prov
|
||||
# build-remote.sh dev dev only
|
||||
# Run from DevOps: syncs ios/ to WHITE-M1, builds there, and pulls the IPAs back into ios/build/<mode>.
|
||||
# build-remote.sh dev build (the usual one): build/dev
|
||||
# build-remote.sh release release build, only when a release is asked for: build/release
|
||||
# build-remote.sh prov personal signed build: build/prov
|
||||
# build-remote.sh dev prov ... several at once
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
H=${BUILD_HOST:-white-m1}; R=stemify-build/ios
|
||||
case "${1:-}" in
|
||||
"") MODES="dev prov" ;;
|
||||
public) MODES="release" ;;
|
||||
dev) MODES="dev" ;;
|
||||
*) echo "usage: $0 [public|dev]" >&2; exit 1 ;;
|
||||
esac
|
||||
MODES=${*:-dev}
|
||||
for m in $MODES; do case $m in release|dev|prov) ;; *) echo "usage: $0 [release|dev|prov]..." >&2; exit 1 ;; esac; done
|
||||
rsync -a --exclude build --exclude prov --exclude Stemify.xcodeproj ./ $H:$R/
|
||||
for m in $MODES; do
|
||||
ssh $H "cd $R && P12_PASS=\$(cat prov/cert_pass.txt 2>/dev/null) bash scripts/build-ipa.sh $m"
|
||||
mkdir -p build/$m && rsync -a $H:$R/build/$m/ build/$m/
|
||||
done
|
||||
for m in $MODES; do
|
||||
case $m in dev) DIRS=dev ;; prov) DIRS=prov ;; release) DIRS="public prov" ;; esac
|
||||
for d in $DIRS; do mkdir -p build/$d; rsync -a $H:$R/build/$d/ build/$d/; done
|
||||
done
|
||||
ls build/*/stemify-$(sed -nE 's/^ *VERSION_LABEL: "(.*)"/\1/p' project.yml | tr 'A-Z' 'a-z' | sed -E 's/ rc /-rc/; s/ /-/g')*.ipa 2>/dev/null
|
||||
ls build/{release,dev,prov}/stemify-$(sed -nE 's/^ *VERSION_LABEL: "(.*)"/\1/p' project.yml | tr 'A-Z' 'a-z' | sed -E 's/ (rc|beta) /-\1/; s/ /-/g')*.ipa 2>/dev/null
|
||||
@@ -36,7 +36,8 @@ 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:
|
||||
# Pre-releases are dev builds (org.cdnutter.stemify-dev), they go to Sideload Developer instead.
|
||||
if not rel["tag_name"].startswith("ios-v") or not ipa or rel["prerelease"]:
|
||||
continue
|
||||
versions.append({
|
||||
"version": v.split("-")[0],
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
# Publishes an IPA built by build-remote.sh. Run from DevOps with everything committed.
|
||||
# publish.sh dev on the dev branch: Gitea pre-release ios-v<v>-dev.<build> + Sideload Developer (WHITE-M1 :8097)
|
||||
# publish.sh release on main, only when the owner says so: Gitea release ios-v<v> + Sideload (:8093), then source.json
|
||||
# Release notes come from $NOTES, else the last commit subject. prov builds are never published.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
MODE=${1:-}; NAME=stemify; TAGP=ios-v
|
||||
API=https://git.iamchrisama.com/api/v1/repos/cdnutter/stemify
|
||||
T=$(sed -n 's#.*://cdnutter:\([^@]*\)@.*#\1#p' ~/.config/gitea/cdnutter.credentials | head -1)
|
||||
V=$(sed -nE 's/^ *VERSION_LABEL: "(.*)"/\1/p' project.yml | tr 'A-Z' 'a-z' | sed -E 's/ (rc|beta) /-\1/; s/ /-/g')
|
||||
BUILD=$(sed -nE 's/^ *CURRENT_PROJECT_VERSION: "(.*)"/\1/p' project.yml)
|
||||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
die() { echo "$*" >&2; exit 1; }
|
||||
case $MODE in
|
||||
dev) WANT=dev; IPA=build/dev/$NAME-$V-dev.ipa; TAG=$TAGP$V-dev.$BUILD; PRE=true; UP=http://10.0.0.11:8097 ;;
|
||||
release) WANT=main; IPA=build/release/$NAME-$V.ipa; TAG=$TAGP$V; PRE=false; UP=http://10.0.0.11:8093 ;;
|
||||
*) die "usage: $0 dev|release" ;;
|
||||
esac
|
||||
[ "$BRANCH" = "$WANT" ] || die "$MODE builds publish from $WANT (on $BRANCH)"
|
||||
[ -f "$IPA" ] || die "missing $IPA, run scripts/build-remote.sh $MODE"
|
||||
git diff --quiet HEAD || die "uncommitted changes"
|
||||
[ "$(curl -s -o /dev/null -w '%{http_code}' -H "Authorization: token $T" $API/releases/tags/$TAG)" = 404 ] \
|
||||
|| die "$TAG already exists, bump CURRENT_PROJECT_VERSION (dev) or VERSION_LABEL (release)"
|
||||
git push -q -u origin $BRANCH
|
||||
BODY=${NOTES:-$(git log -1 --format=%s)}
|
||||
ID=$(jq -n --arg t "$TAG" --arg c "$(git rev-parse HEAD)" --arg b "$BODY" --argjson p $PRE \
|
||||
'{tag_name:$t, target_commitish:$c, name:$t, body:$b, prerelease:$p}' |
|
||||
curl -fsS -H "Authorization: token $T" -H 'Content-Type: application/json' -d @- $API/releases | jq -r .id)
|
||||
curl -fsS -o /dev/null -H "Authorization: token $T" -F "attachment=@$IPA" "$API/releases/$ID/assets?name=$(basename $IPA)"
|
||||
curl -fsS -o /dev/null -X POST --data-binary @$IPA $UP/api/upload/ipa
|
||||
echo "published $TAG ($(basename $IPA)) to Gitea and $UP"
|
||||
if [ $MODE = release ]; then
|
||||
GITEA_TOKEN=$T python3 scripts/make-source.py
|
||||
git add source.json && git commit -qm "iOS source.json for $TAG" && git push -q origin main
|
||||
fi
|
||||
@@ -174,6 +174,36 @@
|
||||
"downloadURL": "https://git.iamchrisama.com/cdnutter/stemify/releases/download/ios-v1.0.3/stemify-1.0.3.ipa",
|
||||
"size": 1319672,
|
||||
"minOSVersion": "17.0"
|
||||
},
|
||||
{
|
||||
"version": "1.0.2",
|
||||
"marketingVersion": "1.0.2",
|
||||
"buildVersion": "1.0.2",
|
||||
"date": "2026-10-20T08:53:24-07:00",
|
||||
"localizedDescription": "Unsigned, iOS 17 or newer. Sideload `Stemify.ipa` with Sideloadly or AltStore.\n\n- Shuffle on the Songs screen now picks from the whole library instead of only the songs loaded so far (the A's and B's)\n\nIncludes everything in 1.0.1. CarPlay and Siri still need entitlements a free Apple ID can't sign.",
|
||||
"downloadURL": "https://git.iamchrisama.com/cdnutter/stemify/releases/download/ios-v1.0.2/stemify-1.0.2.ipa",
|
||||
"size": 1319672,
|
||||
"minOSVersion": "17.0"
|
||||
},
|
||||
{
|
||||
"version": "1.0.1",
|
||||
"marketingVersion": "1.0.1",
|
||||
"buildVersion": "1.0.1",
|
||||
"date": "2026-10-20T08:50:56-07:00",
|
||||
"localizedDescription": "Unsigned, iOS 17 or newer. Sideload `Stemify.ipa` with Sideloadly or AltStore.\n\n- The mini player no longer covers the bottom of lists, and it is now a solid card that matches the app instead of system glass\n- More room between the title and the scrubber on Now Playing\n- The play button no longer spins forever after you change tracks\n- The stems and queue buttons now animate their panels in and out\n- Springier button presses and play/pause transitions\n\nCarPlay and Siri still need entitlements a free Apple ID can't sign.",
|
||||
"downloadURL": "https://git.iamchrisama.com/cdnutter/stemify/releases/download/ios-v1.0.1/stemify-1.0.1.ipa",
|
||||
"size": 1317680,
|
||||
"minOSVersion": "17.0"
|
||||
},
|
||||
{
|
||||
"version": "1.0",
|
||||
"marketingVersion": "1.0",
|
||||
"buildVersion": "1.0",
|
||||
"date": "2026-10-20T07:16:41-07:00",
|
||||
"localizedDescription": "First iOS build. Unsigned, iOS 17 or newer.\n\nSideload `Stemify.ipa` with Sideloadly or AltStore, which sign it with your Apple ID.\n\nCarPlay and Siri need entitlements a free Apple ID can't sign, so on a free sideload they won't show up. Everything on the phone works. Not yet tested on a device.",
|
||||
"downloadURL": "https://git.iamchrisama.com/cdnutter/stemify/releases/download/ios-v1.0/stemify-1.0.ipa",
|
||||
"size": 1305649,
|
||||
"minOSVersion": "17.0"
|
||||
}
|
||||
],
|
||||
"appPermissions": {
|
||||
|
||||
Reference in new issue
Block a user