Native Shell
Native Shell은 모든 플랫폼의 앱 안에서 만들어지는 AI UI입니다
Sideloading that runs on the device itself: install, sign and refresh apps on Apple Vision Pro, iPhone, iPad and Apple TV with a free Apple ID
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Apparition is a sideloader that runs on the device itself. Install apps from AltStore-format sources on Apple Vision Pro, iPhone, iPad or Apple TV, sign them with your own Apple ID (a free one works) or your own certificate, keep them refreshed, and launch them with JIT for emulators such as UTM. A computer puts Apparition on the device the first time. After that, refreshing and JIT go either through the desktop app left running as Apparition's companion, which doesn't need to be on the same Wi-Fi (Tailscale works), or through LocalDevVPN on the device, with no computer at all.
Install apps from AltStore and SideStore sources, from a GitHub project's releases
(github:owner/repo), or from an IPA you have. Apps published as a .deb package, as Kodi's
are, install the same way: Apparition unpacks the app from the package. On the device itself,
download the .ipa or .deb in Safari and share it to Apparition (or open it from Files,
or use Library > Install from a File): it names the app and asks before installing. A link like
apparition://source?url=https%3A%2F%2Fexample.com%2Fsource.json opens Add Source with the
source filled in, ready to add, for a source's own "Add to Apparition" button.
Sign with a free Apple ID, which Apple limits to apps that last 7 days and two certificates, or with a paid account's certificate and profile, which last a year and need no sign-in. An app that asks for the increased memory limit (UTM, emulators) gets it, with a free account too; extended virtual addressing needs a paid account.
Refresh apps before they expire, on the device: from the app, a Shortcuts automation or in the background, through the companion or LocalDevVPN.
JIT that works on current chips. iOS 26 on A15 and M2 and later, and visionOS 27 on
Vision Pro, no longer let an app run code it generates just because a debugger attached once
and set CS_DEBUGGED. That old trick is what SideStore's JIT button still does, and why JIT
on Vision Pro looked impossible. Launch with JIT keeps the debugger attached and prepares
the memory the app asks for, on the device itself: ARMSX2's 305 MiB took about two seconds.
It speaks the JIT26 requests StikDebug defined (and UTM's own), so apps built for them work as
they are, at home or away from it through the companion. An app can also ask for JIT for
itself while it runs, with an apparition://enable-jit?bundle-id=…&pid=… link (the
stikdebug:// link works too, so Madeira's Enable JIT button finds Apparition); Apparition
then stays its debugger until the app closes. A link without a pid launches the app anew
under the debugger, as StikDebug does. One with a StikDebug JIT script (script-data, as
LiveContainer sends) runs the script as the app's debugger, after asking you: a script can
read and change everything in the app. On iPhone and iPad it keeps running in the
background by keeping coarse location updates on, which it drops unread: allow location
while using the app when asked. From a computer,
apparition jit <bundle ID> [--attach] does the same. Tested on Vision Pro with UTM (an
Alpine VM) and ARMSX2, the PS2 emulator, running GTA San Andreas
(video).
Pair once, then work over Wi-Fi. Vision Pro and Apple TV pair over Wi-Fi with the code they show in Settings, no cable or Developer Strap. An iPhone or iPad pairs over a USB cable the first time (it has no pairing code); after that it's reached over Wi-Fi. Apparition keeps a copy of its pairing in the device's Keychain, so reinstalling it doesn't unpair it.
Share one certificate between your devices and computers, so a free account's two certificates never run out one per device: nearby over the network, checked by a six-digit code both screens show, or as a file sealed under five words. Signing stops and asks before it would make a second.
Export the certificate as a .p12 for an app that signs with one, such as LiveContainer, which then runs any number of apps under a single App ID. See For other apps.
xattr -dr com.apple.quarantine /Applications/Apparition.app.The command line, apparition, does the same from a terminal; see Installing Apparition from a
computer, which also says how to put it on your
PATH.
About Apple IDs. Use a spare Apple ID for sideloading if you can. Apparition never stores the password unless you turn on Remember password on the device, where it stays in the Keychain, and it sends it only to Apple. The first sign-in from a computer or device Apple hasn't seen asks for the code Apple sends to your other devices. Since 12 October 2026 Apple checks the "anisette" data that comes with a sign-in more strictly; see Anisette for what that means and how Apparition handles it.
A free Apple ID has two development certificates. Every Apparition can sign with the same one once it has that one's key, so the way to stay under two is to share it rather than revoke (see Sharing one certificate). Signing doesn't make a second certificate while Apparition elsewhere has one: it stops and offers to get that one's key, or to make one anyway. When both slots are taken, Apparition offers to receive another Apparition's key first, then lists the certificates and revokes one only after you confirm. Apple doesn't always free the slot right away: after a revoke it can keep refusing a new certificate ("Maximum number of certificates reached") with only one listed, possibly because it counts a certificate that free accounts can't list. If so, try again later, or share the certificate you already have from another device.
The desktop app's Developer view connects to an appstore
Mac build service. Set a password on that service with node server.js set-password, then
enter its address and password in Apparition. Use http://localhost:8484 on the Mac that
runs it, or its HTTPS address from another computer. The service needs the new
apparition-service version 1 API; older servers need updating first.
Projects and device targets currently come from the service's config.json. Choose a
project's branch and exact platform/device target, then Build & Install. Launch, debug
access, live patching and Debug configuration are separate options. The view shows queued
and running jobs, cancellation, build logs, uploaded DebugTrace files, crash reports and
basic inspection of discovered apps. New builds offer Build details and Download:
the saved artifact is checked against its manifest's SHA-256 and size before replacing
your chosen destination file. Registered builds also shows artifacts created outside
the service queue, such as terminal builds. Manifests record bundle identities, binary UUIDs, symbols,
toolchain and before/after source/dependency fingerprints. Device debug access still asks
for approval on the device; connecting to the service does not grant it.
Only the service address is remembered on disk. The service password field clears when connecting; its session credential stays in Rust memory, outside the page. Disconnect revokes that session. After quitting Apparition, connect again. This connection is separate from the companion relay and does not share its signing credentials.
With the native build prerequisites ready, node scripts/verify-build-service.cjs runs a
real build-only integration check through an isolated authenticated service, then verifies
the manifest and downloaded artifact. It never installs or launches an app. It uses the
sibling appstore checkout by default (APPARITION_SERVICE_DIR overrides it), keeps its
private temporary config/logs under ignored build/, and stops its own service afterward.
Apparition's companion works over Tailscale, so a computer left at home keeps signing and refreshing apps, and launching them with JIT, wherever the device is. Doing this alongside Tailscale is the new part: AltStore needs its computer on the same Wi-Fi, and SideStore and StikDebug need LocalDevVPN, which takes the device's only VPN slot.
New to Tailscale? It puts your devices on one private network wherever they are, free for personal use, with nothing to open on your router. The same network reaches a Mac's Screen Sharing or a game streaming PC from anywhere, for example with Longwave, a remote desktop app for Vision Pro, iPhone and iPad.
100.x.y.z:49160, shown in Tailscale's
menu on the computer), then Use and Check. Bonjour doesn't cross Tailscale, so Find
companion only finds it at home.Only encrypted traffic crosses the companion, and it only connects back to the device that asked. If a Windows or Linux companion doesn't answer over Tailscale while it does at home, its firewall is blocking the Tailscale network: allow TCP 49160 and 6969 there too (see How it works).
Sources are AltStore/SideStore JSON, or github:owner/repo for apps published only as
release assets (UTM's visionOS build).
Installed apps keep their IPA in Documents/apps, so a refresh re-signs and reinstalls
without the network, and their data survives. With "Remember password" on, the password
stays in this device's Keychain, and a refresh can sign in with nobody present (Apple
skips 2FA for a device it already trusts). Background tasks ask to run this about every
12 hours: a short app-refresh task, and a processing task for refreshes that outlast its
~30 s (uploading UTM takes 40 to 150 s). They need the companion or LocalDevVPN, and their
firing is not verified yet (scheduleBackgroundRefresh is the test hook).
The companion replaces LocalDevVPN: the headset resets connections it makes to its own
RemotePairing service, so Apparition asks a companion on a computer (TCP 49160) to connect
back to it. The headset then sees the connection coming from that computer. The companion
only ever connects back to the address that asked, and it carries only ciphertext
(pair-verify, then TLS-PSK) with no keys. With the headset on Tailscale, which LocalDevVPN no
longer occupies, a companion at home keeps signing and JIT working from anywhere. Tested
2026-10-11 with LocalDevVPN off: tunnel, refresh (109 s over the LAN, 150 s over Tailscale)
and JIT launch. On the LAN, Apparition finds the companion over mDNS ("Find companion"); over
Tailscale, type its address. The companion is the desktop app, which keeps it running from
the menu bar or tray, or apparition relay on the command line (see below); both run
core/src/companion.rs.
Windows asks to allow the companion through its firewall the first time it starts from a
desktop session; elsewhere allow inbound TCP 49160 (and 6969 to offer anisette), e.g. from an
elevated PowerShell: New-NetFirewallRule -DisplayName "Apparition" -Direction Inbound -Protocol TCP -LocalPort 49160,6969 -Action Allow -Profile Private. On Linux, open the same
ports if a firewall runs (sudo ufw allow 49160,6969/tcp, or firewalld's --add-port), and
UDP 5353 for mDNS. On macOS, Local Network
privacy blocks an unbundled binary's multicast, so apparition relay there can't advertise
itself: use the desktop app, or type the address in Apparition. apparition browse lists the
companions mDNS sees from a machine. (The relay protocol was tested 2026-10-11 from macOS over
Tailscale and from a Windows 11 VM on the LAN, then with the Swift and Rust companions this
app replaces.)
Lab/ is a test app for JIT experiments, launched through the probe's launchJIT.
Its first experiment (2026-10-11, visionOS 27.2): bytes the debugger writes into the
app's own signed __TEXT keep running after it detaches, including code first run
only after the detach and a site rewritten to branch to a trampoline. Apparition serves
this through an experimental JIT26 command, x16 = 0x4150 (copy x2 bytes from x1 to x0).
The intended product is Apparition for building, installing, running and diagnosing apps.
The appstore Mac service supplies build jobs, artifacts and report storage; its
build-and-sign remains the build executor. DebugTrace stays an independent SDK. The
existing AltStore feed remains compatible, while an explicit job API will expose build
progress, errors and cancellation to Apparition. Keep the Node backend initially.
Implementation order:
UIDeviceFamily to identify iPhone/iPad apps running in compatibility mode. They show
project/platform, installed and retained build identity, build-only and separately
labelled device-install actions, JIT launch, and reports filtered to the installed
CFBundleVersion. A retained build can be downloaded into Apparition's normal
install review and signing flow without rebuilding it. Per-app automatic update
preparation watches retained artifacts while the service refreshes, downloads a newer
build and opens that same review; installation still requires confirmation. Crash text
and trace text files can be opened and closed on device.
Apps without a project match stay in Other Apps. Next: finish installation/session
relationships and round out device-side report navigation.
A Mac with Xcode executes Apple builds; other desktop platforms can connect to it.
Service credentials stay separate from the existing relay.Structural Swift changes need a separate approach: recreate a component with selected state migration, or rebuild/relaunch with state restoration. Executable-memory access alone cannot change the layout of instances that already exist. A debugger-assisted backend must retain per-patch user approval before it becomes a product feature.
In desktop Apparition's Developer view, use Register a project to add an existing
checkout on the Mac service host. Enter its Mac path, device names, platform aliases and
bundle identifiers; its build-signing.conf must already be set up. Registration saves
the project without building it or changing signing material. Local branch names are
optional; the current checkout remains available. The Mac service validates the checkout
and updates its configuration atomically, preserving existing settings.
When the device build service is connected, the Library matches installed debuggable apps against configured project target bundle IDs. Matching cards keep build-only separate from installing to the project's configured device, and show retained artifacts and reports for the installed build number when available. Apps with no configured project match remain in Other Apps. The service connection is shared with Developer.
On the Mac with the complete build-tools checkout and Node 18+:
apparition xcode register --app /path/to/Products/MyApp.app --project /path/to/repo \
--symbols /path/to/Products/MyApp.app.dSYM
apparition xcode configure --spec project.yml --target MyApp
configure writes project.apparition.yml and generates a MyApp (Apparition) scheme.
Use that spec for subsequent XcodeGen runs; the original spec stays intact. Set
APPARITION_CLI if the CLI is outside PATH and APPARITION_BUILD_TOOLS if the tools
are outside the packaged location or the checkout behind build-and-sign on PATH.
The scheme performs one normal Xcode build, then retains its artifact and UUID-matched
symbols in the same registry the developer service exposes. Registration without a
pre-build context records observed source separately; it cannot establish what source
was compiled. Unsigned products remain unsigned and need signing before installation.
Registration does not install, launch or attach a debugger. Stop an Xcode debug session before asking Apparition to own JIT for that process. This handoff does not replace the device's approvals for live inspection or code changes.
The lab now runs a second experiment before the original experiment detaches. It branches from an optimized compiler-generated C function to a replacement already linked in the app, then restores the original instruction. A caller in the target's translation unit deliberately inlines the original function. The harness is in a separate translation unit, and the Lab target explicitly enables optimization and disables LTO.
Build and install the isolated lab, then launch it through Apparition's debugger:
xcodegen
SCHEME_NAME=ApparitionLab TARGET_NAME=ApparitionLab \
BUILD_BUNDLE_ID=com.illixion.apparition.lab \
~/bin/build-and-sign --platform iOS --device AVP
apparition jit com.illixion.apparition.lab --device AVP --wait 60
xcrun devicectl device copy from --device AVP \
--domain-type appDataContainer --domain-identifier com.illixion.apparition.lab \
--source Documents/optimized-patch-report.json \
--destination build/optimized-patch-report.json
The report records baseline/patched/restored return values, loop checksums, write results,
and five timings per phase in nanoseconds per call. Breadcrumbs are synced before risky
steps to Documents/optimized-patch-steps.txt. A normal launch reports not-traced and
does not issue debugger requests. Use a fresh launch for each experiment.
Verified on a physical Vision Pro on 2026-10-29: writes succeeded, ordinary calls returned
[11,17,11], and the inlined caller returned [11,11,11]; loop checksums matched all three
phases. Tiny-function timings varied substantially, including after restoration, so they
establish no application-level performance improvement. This proves optimized entry
redirection and the stale-inline limitation. It does not yet demonstrate Swift-native
patching, dynamically generated replacement code, arbitrary type changes, or an alternative
to DebugTrace's current injection backend. Ordinary-launch controls also remain unpatched.
The lab also tests optimized native Swift entries with a separately compiled, development-
signed dylib delivered into its Documents directory after the app build. It runs before the
C experiments detach the debugger. Only the dylib's address getters have a C ABI; patched
functions use Swift's native calling convention, without -enable-implicit-dynamic or
@_dynamicReplacement.
python3 scripts/run-swift-entry-lab.py --platform visionOS --device AVP
# For a paired iPhone instead:
python3 scripts/run-swift-entry-lab.py --platform iOS --device ixPhone
The runner builds and installs only com.illixion.apparition.lab, compiles and signs the
replacement, copies it to the device, launches through Apparition JIT, and validates both
the patched run and an ordinary-launch control. --skip-build reuses an already installed
lab of the selected platform. It overwrites the device report with a pending marker before
each launch, so a crash cannot reuse an old success. Reports stay under ignored
build/swift-entry-lab/<platform>/. Runtime checkpoints are in
Documents/swift-entry-steps.txt.
Verified on physical Vision Pro, 2026-10-29, both native visionOS and iOS compatibility builds:
[11,17,11] before patching, after patching and
after restoration. Both a Swift function reference and a normal compiled Swift call
observed the replacement. The latter reads an opaque runtime input on every call to
prevent constant specialization and reuse of a pure function's earlier result.String -> String entry returned original:hello, replacement:hello, then
original:hello. All 1,000 heap-backed String calls matched the replacement result.This is a single-threaded, ARM64 lab proof using signed dylib loading. It has no general function discovery or dependency tracking, does not allocate anonymous JIT code, and does not test custom app types, generics, async/throwing methods, pointer authentication, active stack frames or concurrent patch publication. Existing inlined or constant-folded results can still retain old behavior. Keep the dylib loaded for the process lifetime. Production adoption needs thread coordination, ABI validation, compiler dependency handling and the same per-patch device approval as DebugTrace injection. No application performance claim follows from these correctness tests.
Background refresh is not reliable, so the dependable way to keep apps signed is a Shortcuts automation. Apparition offers three actions: Refresh Apps (apps that expire within 3 days, or all with "All Apps"; returns how many it refreshed), Launch with JIT (an installed app, through the companion or LocalDevVPN) and Open Apparition. Each opens Apparition and runs there, because a refresh takes 40 to 150 s. Siri knows them too: "Refresh apps in Apparition", "Launch UTM with JIT in Apparition".
To refresh every day: Shortcuts, Automation, New Automation, Time of Day, pick a time you are usually wearing the headset, Daily, Run Immediately, then add Apparition's Refresh Apps action. It needs "Remember password" on and the companion (or LocalDevVPN) reachable. Apps refreshed are reported as "Refreshed UTM; expires Oct 18"; when nothing is due it says when the next app expires; and when nothing could be refreshed the action fails with the reason (not signed in, companion unreachable), so the automation shows it.
With a paid developer account, Apparition can sign without an Apple ID (Settings > Signing): import an Apple Development or Distribution certificate as a .p12, plus development, ad hoc or enterprise profiles that list the headset. Apps then sign on the headset for as long as their profile lasts, usually a year, with no sign-in, anisette server or free-account slots. A wildcard profile keeps each app's own bundle ID, and development profiles keep Launch with JIT working. The .p12 password is only used to open the file; the decrypted key is kept in Apparition's container, like the free account's key.
With an Apple ID and an imported certificate (a free account for trying apps out, a paid team for keeping them), Apparition asks what signs a new app before its first install: Install in Browse, or an .ipa you open. It also lists a copy of the app that another team signed, if the headset has one. A copy signed by another team can't be replaced, so the choice has to come first. The Apple ID adds its team ID to the bundle ID and a certificate keeps it, so the two kinds install side by side with data of their own. Where a certificate from another team would need the same bundle ID, Apparition says so before installing, rather than passing on installd's error.
Changing an app's signer later (its menu in the Library) changes its bundle ID too, as does an Apple ID that signs for another team. The app comes back as a new copy; Apparition then copies the old copy's files to it through the same tunnel (house_arrest), and removes the old copy only once every file arrived. Turn it off in Settings > Signing. Caches are left behind, and so are Keychain items and app group data, which belong to the signing team. Only a development-signed app can have its data read, so a distribution-signed one keeps its data in the old copy.
Releases are listed on apparition.illixion.com (repo
apparition.illixion.com, whose README has the steps). GitHub Actions builds each release's
files (.github/workflows/release.yml, running scripts/package-release.sh on macOS and native
Linux runners) and attests their provenance: gh attestation verify <file> -R illixion/Apparition checks a download came from this repository's workflow. The site then
signs the files' SHA256SUMS with a key in release-signers, which never goes near CI. core/src/release.rs checks that signature against the keys compiled in,
then every download's hash, so the files can be hosted anywhere. apparition install
with no IPA installs the latest release this way.
installer/ is apparition, the command line for Windows, Linux and macOS: it puts
Apparition (or any IPA) on the headset without Xcode, using the same Rust core, and runs the
companion (apparition relay, apparition anisette serve|up|down):
apparition pair # with Settings > General > Remote Devices open; type the PIN
apparition pair --usb # an iPhone or iPad: a cable until it trusts this computer, then Wi-Fi works too
apparition pairing hand-off # give an Apparition already installed there its pairing
apparition install --apple-id [email protected] # on Windows add --anisette <url>
apparition install --p12 development.p12 --profile wildcard.mobileprovision
apparition install Other.ipa --apple-id [email protected] --anisette http://host:6969
apparition install kodi.deb --apple-id [email protected] # a .deb package: the app inside is installed
apparition sign UTM.ipa --apple-id [email protected] # sign only, for a paired device; nothing installed
apparition relay # the companion, with this computer's anisette (not on Windows); until Ctrl-C
apparition on your PATHOn a Mac the command line is inside the app (from 0.3.3), so updating the app updates
it. Run it once to put apparition where every terminal finds it:
/Applications/Apparition.app/Contents/MacOS/apparition-cli link
That makes ~/.local/bin/apparition, a symlink into the app, and says what to add to your
PATH if that folder isn't on it (export PATH="$HOME/.local/bin:$PATH" in ~/.zshrc).
--dir /usr/local/bin links somewhere else (needs sudo) and --remove undoes it. The link
points at the app's location, so run link again if you move the app. The command inside the
app is called apparition-cli because a Mac treats apparition and the app's own Apparition
as one name. On Linux, link does the same for wherever you unpacked the tarball; on Windows, put
the folder holding apparition-cli.exe on your PATH and run apparition-cli --version.
The Windows command line needs a distinct name too: apparition.exe collides with the
desktop's Apparition.exe on a case-insensitive filesystem.
apparition with no arguments (or --help) prints the full text; this is the shape of it.
| Command | What it does |
|---|---|
devices | Lists the devices on this network that are paired or waiting to pair. |
pair [<address>[:<port>]] | Pairs with a headset or Apple TV showing its pairing screen; finds it over mDNS without an address. |
pair --usb [<UDID>] | Pairs an iPhone or iPad. Despite the name, the cable is only needed until it trusts this computer; after that this works over Wi-Fi. Also makes the pairing for Apparition on the device. |
pairing hand-off | Gives Apparition, already installed on an iPhone, iPad or Apple TV, its pairing with that device, without reinstalling it. |
install [<app.ipa or .deb>] | Signs and installs. With no file, installs the latest Apparition, checked against the release signature. Signs with --apple-id <email> (and --anisette <url> where there's no built-in one) or --p12 <file> --profile <file>. |
sign <app.ipa or .deb> | Signs for a paired device and installs nothing; says which capabilities the app got. |
jit <bundle ID> | Is the app's debugger from this computer so it can use JIT: launches it, or with --attach takes the running process, and serves its JIT requests. |
certificates, app-ids | Show what uses up a free account's two certificates and ten App IDs, and free one you choose after you confirm. |
signing receive, send, hand-off, share, import | Move the Apple ID's certificate key between your devices and computers; see Sharing one certificate below. |
forget <name or UDID> | Forgets a paired device. |
relay | Is the companion for Apparition on your devices until Ctrl-C (the same as the desktop app's Companion tab). |
anisette serve, up, down, compose | Serve this computer's built-in anisette, or run the self-hosted server with your Docker. |
anisette import [--accept-unverified] <file>…, anisette import --reset | Troubleshooting, for "Couldn't fetch Apple's anisette libraries": use the Apple Music APK, or its two libraries, from a file you got from Apple, in place of the download. uses files that don't match the tested version (an unsupported setup, warned about everywhere). goes back to fetching Apple's. Steps in . |
Anything that signs in asks for the Apple ID's password, and for the 2FA code when Apple sends
one, in the terminal; neither is kept unless you turn on Remember password on the device.
APPARITION_LOG=<file> writes the sign-in's progress (no secrets) to a file, for working out
why a sign-in failed.
On Linux, pair --usb needs usbmuxd running. Nothing else there makes the device trust the
computer (Finder and Apple Devices do on macOS and Windows), so Apparition asks it itself and
saves the record to usbmuxd; there's no need for idevicepair.
Without an IPA it installs the latest signed release of Apparition (see Releases).
It pairs over the network once (the pairing is kept), signs with your own certificate or an
Apple ID, and installs. Installing Apparition also hands it what signed it, through its Files
inbox: the .p12 and profiles (it asks for the .p12's password once), or the Apple ID's
certificate key as a .apparitionsigning file. With the key, Apparition signs with the same
certificate as the computer once signed in to the same Apple ID, so the two use one of a free
account's two certificates instead of one each. For an Apparition installed some other way,
apparition signing hand-off --apple-id <email> [--device <name>] puts the key in its inbox on
its own.
Apparition reaches the device it runs on with a pairing of its own,
apparition_rp_pairing.plist in its Documents folder. On Vision Pro it makes that itself in
Setup. On iPhone, iPad and Apple TV it gets it from the computer that paired the device:
installing Apparition hands it over. Anything that needs the device (an install, a refresh, a
JIT request from an app such as Madeira) stops at once and says which of two things is wrong:
Apparition isn't paired. The file is missing, for example after Apparition was deleted and installed again some other way. Apparition keeps a copy in the Keychain (this device only, never synced) and puts the file back from it at launch, so this only happens when that copy is gone too (it was never made, or the new copy is signed by another team). On iPhone or iPad, from a computer that trusts the device, run
apparition pair --usb
apparition pairing hand-off [--device <name>]
Wi-Fi is enough; the cable is only needed while the device doesn't trust the computer. On an
Apple TV, run apparition pair with Remote App and Devices open (it shows a code twice), then
pairing hand-off. On Vision Pro, pair again in Setup.
The device no longer trusts Apparition's pairing. The file is there, but the device turns it down, for example after the device was erased or its trust settings were reset. Pair again the same way.
Settings > Forget Pairing deletes both the file and the Keychain copy.
Between devices, and from a device back to a computer, the key goes one of two ways.
Nearby, on the same network. On the device that needs it, Settings > Get Signing from
Another Device (or Get It from Another Device where signing stopped) waits for it; so do
apparition signing receive and the desktop app's Receive from a Nearby Device. On the one
that has it, Settings > Share Signing with Another Device lists what's waiting under Nearby;
on a computer, apparition signing send --apple-id <email> or the desktop app's Send to a
Nearby Device. Both screens show a six-digit code, and each person confirms that the two match.
It's numeric comparison, as Bluetooth pairing does it (core/src/nearby.rs): the sender commits
to its X25519 key before it sees the receiver's, so something in between would get two
different codes, all but one time in a million, and the key, sealed with ChaCha20-Poly1305
under the agreed key, goes only to a device whose person confirmed. Where Bonjour can't reach
(Tailscale, or a command line on macOS whose terminal has no Local Network access), type the
address the receiving screen shows (--to <address>:<port>). This is the way on Apple TV, which
has no AirDrop.
As a file, for a device on another network. Share Signing > Make a Key File in Apparition
(or apparition signing share --apple-id <email>) writes a .apparitionkey file and shows
five words. Send the file any way (AirDrop, Messages, a USB stick), open it in Apparition on
the other device, or on a computer in the desktop app's signing step or with apparition signing import, and type the words. The words make the key with Argon2id (64 MiB, four
passes) and the file is ChaCha20-Poly1305, so the file alone is useless. The words come from
the EFF's short wordlist 2.0 (CC BY 3.0 US, in core/assets/),
where each word is known by its first three letters, so case, spacing and a mistyped ending
don't matter.
Some apps sign other apps themselves and need a certificate with its private key:
LiveContainer's JIT-less mode, which iOS 26 and later
require, is the one we've tested. Settings > Share Signing with Another Device > Export
Certificate as .p12 (0.3.5 and later) writes the Apple ID's certificate and key as a .p12
under a password of six new words, to copy and share separately. It signs in if it has to and
makes no new certificate, so the other app signs with the one Apparition already uses. The file
is deleted when the screen closes. Anyone with the file and its password can sign apps as your
Apple ID, so give it only to an app you trust; if it gets out, revoke the certificate in
Certificates & App IDs.
An update of an app that is open on the headset finishes when it's closed. It never revokes a certificate on its own: with no free slot it offers to receive another Apparition's key, then to revoke one you choose.
cd installer && cargo build --release # this machine, into target/ at the root (one workspace with desktop/)
cargo build --release --target x86_64-pc-windows-gnu # Windows, from macOS with mingw-w64
desktop/ is Apparition for macOS, Windows and Linux (Tauri v2): the same installer as a
guided app, so nobody needs a terminal, and the companion, kept running from the menu bar or
tray after the window closes (its Companion tab: the relay and its connections, the anisette
server offered to devices, opening at login). As an installer, it finds the headset over mDNS (or takes its address) and pairs with
the PIN it shows, or pairs an iPhone or iPad over its cable (like pair --usb), signs in to an Apple ID (asking for the 2FA code) or opens a .p12 and
profiles, installs the latest signed release (or an IPA or .deb you pick) with progress, and hands
Apparition its signing at the end. When a free account's certificate or App ID slots are full
it lists them and frees one only after you type revoke or delete. It drives the same
apparition_core::api as the command line and shares its pairings and Apple ID data, so a
headset paired with one is paired with both. Passwords go from the app's own fields to the
core and nowhere else.
Desktop update checks are off by default, including for existing installations. Choose automatic checks on step 6, Done, or with the Companion > Updates toggle. When enabled, the app contacts apparition.illixion.com on launch and every six hours of wall-clock time (a laptop that slept past a check catches up on wake), including while running from the tray, for releases hosted on GitHub. A failed check retries after 15 minutes. When off, Get started offers a one-time check without enabling future checks; declining makes no update request. The tray/menu-bar menu has an updates section: a greyed-out No Updates line, which becomes Update to x.y.z... when one is available, and a Check Automatically checkbox mirroring the setting. A badge on the icon and a banner in every app view also announce an update; the banner disappears once a check finds nothing newer. Update & restart downloads and installs it only when clicked; the companion briefly stops while restarting. Development builds are never replaced.
Updates use the existing GitHub Releases assets and release workflow: the pinned release
keys must verify the YubiKey-signed SHA256SUMS SSH signature, then the downloaded file must
match that signed hash before it is extracted or installed. Version, OS, architecture
and package format are bound to the signed filename; older or equal versions are refused.
An app-only release does not hide a newer desktop release.
On macOS the whole ad-hoc-signed .app (including its CLI and entitled anisette helper) is
staged beside the installed app, its code-signing seal is checked, and the bundles are
atomically exchanged. The Applications folder must be writable by your account; use
~/Applications if necessary. Updating does not re-sign the helper or require a new
quarantine exception. On Windows the portable app, apparition-cli.exe and
WebView2Loader.dll update together: a Windows PowerShell helper waits for the app to
exit, replaces the files, restarts it, and rolls back if installation or launch fails.
The helper is passed to PowerShell as a command, not a script file, so a Group Policy
execution policy doesn't block it. PowerShell locked to Constrained Language mode
(AppLocker or WDAC) does: the banner then says the computer's policy blocks the updater
and links to the Releases page for a manual download.
Keep all three in a user-writable folder. On Linux an AppImage replaces itself atomically and keeps
its executable permissions. A Debian installation instead asks for system authorization
through PolicyKit and installs the verified .deb with dpkg; it needs pkexec and a
desktop authorization agent. If dpkg reports missing dependencies, repair them with
your package manager before retrying. The unpublished Flatpak prototype has no update
repository and must still be updated manually. Settings, pairings and signing credentials
stay outside these application packages and are not replaced.
Before signing, the desktop installer and native iPhone/iPad, Vision Pro and Apple TV apps
list the IPA's embedded extensions and let you choose which to include. Apple ID installs show the number of new App IDs needed and let
you reuse an existing registration by installing under its bundle identifier. Assignments
and extension choices are saved per team, device and source app in
app-id-assignments.json beside the desktop's pairing database, including reservations
made before an install fails. The native apps store the same choices in library.json,
with the team and source identifier; Apple TV mirrors that state to its Keychain vault.
An occupied identifier replaces the app already using it, and the previous library entry
stops refreshing over its replacement. Forgetting a native Library entry keeps its ID
reservations while that copy is installed; a fresh device listing releases reservations
once a forgotten copy is gone. Failed replacements retain old extension reservations until
installation succeeds. These databases are local to each installation;
assignments are not synchronized between computers and devices.
Assignments are per device, so one registered App ID can serve different apps on different devices (and platforms): the same bundle identifier can be the target of one app on a Vision Pro and of another on an Apple TV or iPhone, and one app can get a different identifier on each device. Apparition only looks for conflicts within one team and device. An identifier that is occupied replaces the app using it on the device being installed to, never on another device. Apple registers one App ID per team, not per platform, so these share its capabilities (App Groups, for one) and its free-account limits. Because each computer keeps its own assignments, and none of them can see what other devices or installers use, Apparition cannot warn that an identifier is already in use somewhere else; reusing one across devices is your call.
The review asks whether to keep existing app data or uninstall the destination first. Uninstalling discards its app container before the new install, so a failed installation leaves the old app removed. When an app moves from a known previous identifier, Keep copies Documents and Library using the same migration code as changing its signer on the device. The source is removed only after every file arrives. Incomplete or queued transfers keep the source and can be retried. Keychain items and shared app group containers cannot be migrated or cleared by this choice. Registrations may also be used by other devices or installers; the local assignment database cannot determine that account-wide ownership. ExtensionKit helpers are currently unavailable with Apple ID signing; imported profiles can still sign selected helpers when they cover them. Ordinary app extensions each need a compatible registration; the installer reuses matching child identifiers where present. Certificate installs preview the actual identifier chosen by the provisioning profile and disable extensions whose profiles are missing. Both frontends check the App ID allowance before signing. Native Library > More > Review Install Options reopens the picker for a kept app, including file imports. Foreground updates review the new archive; unattended refreshes retain the saved identity and extension selection and never add new extensions. Changing a native app's signer requires reviewing its install options before refreshing. Existing library records retain their previous refresh behavior until reviewed. Cancelling a review keeps the previous archive available for refresh.
After Apple ID sign-in, Manage App IDs is available on the signing and install screens. It shows Apple's remaining allowance, registered bundle identifiers and expiry dates, with search, refresh and deletion after confirmation. Deleting an ID may not restore a free account's weekly allowance before it expires.
scripts/install-tauri-cli.sh # once (or cargo install tauri-cli --version '^2' --locked)
cd desktop && cargo tauri build --bundles app # this Mac
cargo tauri build --target universal-apple-darwin --bundles app # arm64 + x86_64
cargo build --release --features custom-protocol --target x86_64-pc-windows-gnu # Windows exe
docker build -t apparition-desktop-linux -f ../scripts/desktop-linux.Dockerfile ../scripts
docker run --rm -v "$PWD/..:/src" -w /src/desktop -v apparition-desktop-target:/target \
-e CARGO_TARGET_DIR=/target apparition-desktop-linux sh -c 'mkdir -p /src/build &&
cargo tauri build --bundles deb,appimage && cd /target/release/bundle &&
cp appimage/*.AppImage deb/*.deb /src/build/' # Linux, into build/
The page is plain HTML, CSS and JavaScript in desktop/ui/ (no npm), under a strict CSP. A
debug build opens on any step with APPARITION_DESKTOP_PREVIEW=signing (or pair, pin,
usbpair, twofactor, install, progress, slots, done, companion), can read a scratch release site with
APPARITION_RELEASE_SITE and APPARITION_RELEASE_SIGNERS, and can move the companion's ports with
APPARITION_COMPANION_PORTS=<relay>,<anisette>; release builds ignore all four.
scripts/package-release.sh puts the app in the macOS and Windows installer zips next to the
command line, and with --linux builds a .deb and an AppImage per architecture (Ubuntu 22.04,
so glibc 2.35 or newer). The Linux release includes direct .deb and AppImage downloads as well
as an archive with both and the headless command line. Opening the app again shows the running
one, which is also the way back to its window on a Linux desktop with no tray (niri or plain
GNOME, say).
A Flatpak prototype packages both Linux architectures against a GNOME runtime, with the CLI included for diagnostics. See building and testing the Flatpak for bundle creation, NixOS/Fusion USB setup, permissions and current integration gaps.
[!NOTE] Sign-in works with the built-in anisette again (from 0.4.3). From 29 October 2026 Apple refused it, and for a day or so nothing but a Mac's anisette signed in. The anisette was never the problem: Apple had put a firewall rule in front of its sign-in servers that rejected the
Content-Typesideloaders have always sent, so the request was turned away before Apple looked at it. Apparition sendstext/xmlnow and Apple accepts the Android libraries' anisette as before. A Mac's anisette, a macOS VM or a third-party server still work, if you prefer one. Details below.
Signing in to Apple needs "anisette" data that identifies the computer. On macOS and Linux
(arm64 and x86_64) the installer makes its own: it fetches two libraries from Apple's CDN the first time (the ones Apple Music for
Android uses; nothing of Apple's is stored in this repository), loads them in its own process
(core/src/anisette/) and talks to Apple through them. Nothing to run or install. On Windows, and
wherever you prefer it, use a server: Apparition ships none, so enter the URL of
any anisette-v3 server, or self-host one with docker compose -f selfhost/anisette/compose.yaml up -d (Dadoum's anisette-v3-server, pulled from its own registry and pinned by
digest), and enter http://<host>:6969. The server sees this device's anisette identity
and IP address, never the Apple ID or password, which go straight to Apple. A third-party
server is optional now; use one only if you'd rather not have the built-in one.
Since 12 October 2026 Apple checks that the anisette matches the device the sign-in claims
to be, and refuses the sign-in (-80045, "This action cannot be completed at this time")
when it doesn't. isideload, like most sideloaders, claimed a Mac while sending the Android
libraries' anisette. Apparition claims Android for that anisette (core/src/claimed_platform.rs),
which Apple accepts. Where the libraries are used — a server that makes its anisette with them,
say — the claim still follows the server
(Claim::after_sign_in; Dadoum's anisette-v3-server reports a Mac but runs those libraries, so it
gets the Android claim, while jkcoxson's native-c server reports a Mac and is claimed a Mac). A
sign-in Apple asks a 2FA code for goes through the usual prompt; one it doesn't is asked for the
code Apple pushes to your trusted devices if the Xcode token is refused (core/vendor/isideload,
code_after_login).
On a Mac the built-in provider asks macOS for the Mac's own anisette instead, and the sign-in
claims a Mac and goes through the usual 2FA (core/src/anisette/native.rs):
apparition-anisette-helper signed with them, next to apparition and inside the app, and
macOS runs it only with SIP off and sudo nvram boot-args="amfi_get_out_of_my_way=1".
Otherwise the Android libraries are used, which sign in as before.In a macOS virtual machine, Apple also needs the host's attestation, which the helper forwards
along with the rest of AuthKit's headers; it's only valid inside a desktop session, so the server
runs as a login item. selfhost/macos-vm/setup.sh arranges all of
this — see Self-hosting anisette in a macOS VM.
Provisioning goes through the Android libraries either way, so a device's stored identity works
with any server. With a paid developer account there is also a route with no sign-in at all:
make the certificate and a wildcard or single-App-ID profile on Apple's developer website and
sign with them (--p12 … --profile …, or the app's Signing screen).
Release builds aren't notarized, so the built-in provider needs no entitlement today. It maps
the libraries into memory and makes them executable itself, so if the macOS apps are ever
notarized (hardened runtime), their entitlements must include
com.apple.security.cs.allow-unsigned-executable-memory, or loading fails.
The companion can offer the headset an anisette server too: this computer's own (built in,
served to the network on port 6969), or the same compose file run with your Docker (Docker
Desktop, OrbStack or Colima), from the desktop app's Companion tab or apparition relay --anisette builtin|local. It then advertises the server, and Apparition offers "Use its
anisette server" next to the companion; nothing switches on its own.
Apple's servers sometimes refuse a request that another one would take: sign-in then fails "403
Forbidden". Apparition retries on another server (core/vendor/isideload/src/auth/edge.rs): it
avoids the server that refused for an hour and asks another, looked up over DNS-over-HTTPS (Google's
and Cloudflare's) if your own DNS knows no other, and does the same once when a server can't be
connected to. Only that lookup goes elsewhere, never the Apple ID. If every server it finds
refuses, the error says so; try again later or on another network.
The header corner of the desktop app always says where this computer's sign-in gets its anisette (built-in, Docker or another server) and what the companion offers, so a screenshot shows the setup.
From about 29 October Apple refused the anisette the built-in provider makes: provisioning
(gsd.apple.com) and sign-in (gsa.apple.com) answered an HTML 403 Forbidden with no Apple
error code. It looked like Apple had stopped accepting the Android libraries' anisette, and for
a day it was worked around by choosing a different Apple server, since the first servers to
refuse were only some of them. On 30 October it was traced to the request's Content-Type:
Apple's servers reject the legacy text/x-xml-plist at provisioning, and (the opposite way)
application/x-xml-plist at sign-in. text/xml passes both, Apple parses the body the same and
answers as usual. With that one header the same anisette is accepted, so it never was the
anisette, the serial or the network. Apple may change the rule again; the same symptom would be
a 403 before any Apple error code.
The built-in anisette needs two libraries, libstoreservicescore.so and libCoreADI.so, from
Apple's CDN (apps.mzstatic.com), once. Where that fails (a firewall or proxy, no route to the
CDN, Apple moving the file), signing in stops with "Couldn't fetch Apple's anisette libraries from
Apple's CDN". Work through this in order:
Try again. The desktop app shows a Try again button, which keeps what you typed for five
minutes; on the command line run the command again. Most failures are the network. Check that
apps.mzstatic.com opens from this computer, and a VPN or proxy that blocks it.
Give Apparition the files yourself. Only after step 1 fails does the desktop app show "Advanced: use Apple Music's libraries from a file", under the Try again button; it isn't there on a working setup, so nobody is sent looking for files they don't need. On the command line it is always available:
apparition anisette import ~/Downloads/applemusic.apk
apparition anisette import libstoreservicescore.so libCoreADI.so
Either the Apple Music APK for Android, or both libraries from its lib/arm64-v8a or
lib/x86_64 folder (the one for this computer's processor). What you give replaces what
Apparition would otherwise use, until you replace it again.
Where to get them. From Apple only. The APK is at
https://apps.mzstatic.com/content/android-apple-music-apk/applemusic.apk, the address
Apple's own Android page links to; download it on a network that reaches it, such as a phone's
hotspot, and import it. Or copy the two libraries from another computer where Apparition has
already signed in: they are in the anisette folder beside its apple-id folder (macOS
~/Library/Application Support/Apparition Installer/anisette, Linux
~/.local/share/Apparition Installer/anisette, in the Flatpak
~/.var/app/com.illixion.apparition.desktop/data/Apparition Installer/anisette). Don't use an APK
from anywhere else: these libraries run inside Apparition, with everything Apparition can do,
and the app can't tell that a file came from Apple.
If no file matches. Apparition checks one thing: each file's SHA-256 against the version it
was tested with (the APK Apple's CDN held on 29 October 2026), and refuses a mismatch with
"isn't the version of the libraries Apparition was built with". If Apple has updated the
libraries and taken the old address down, the desktop app offers "Use these files anyway", and
on the command line --accept-unverified does the same:
apparition anisette import --accept-unverified libstoreservicescore.so libCoreADI.so
It still checks that each is a 64-bit library for this computer's processor, under the right name. This is an unsupported setup, and says so for as long as it lasts: in red in the desktop app's header corner and above the sign-in form, and at every command that starts the built-in anisette. Sign-in may fail or behave unpredictably; please say so in any bug report.
scripts/build-core.sh # builds the Rust core (isideload + idevice) for visionOS and iOS
scripts/build-core.sh visionos-sim # adds the visionOS simulator (rustup target add aarch64-apple-visionos-sim)
xcodegen # generates Apparition.xcodeproj from project.yml
build-and-sign # or open Apparition.xcodeproj and Run
PLATFORM=iPad build-and-sign --no-mcp # the lab app on iPad (PLATFORM=iOS for iPhone)
Licensed MIT. Signing (core/signer) builds on apple-codesign
(MPL-2.0), used unmodified from crates.io.
--accept-unverified--resetbrowse | Lists the companions mDNS finds, for 10 seconds: a check that multicast works. |
link | Puts apparition on your PATH (above). |
Going back. apparition anisette import --reset (the desktop app's "Go back to Apple's own
libraries", on the red banner) forgets the cached libraries, supplied or fetched, and the next
sign-in fetches Apple's own.