Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 117 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,107 @@
# AMDL wrapper

The Apple Music account side of [amdl](https://github.com/AMDL-Web/apple-music-downloader),
in two parts:
Holds an Apple Music account session for [amdl](https://github.com/AMDL-Web/apple-music-downloader).
amdl asks it for playlists, decryption keys, lyrics, web-playback data, licenses
and the account's music token over HTTP (amdl's `wrapper.address`); it does the
Apple-side work amdl cannot do itself.

| Part | Directory | What it does | License |
| --- | --- | --- | --- |
| **wrapper-lite** | this root (`lite/`, launchers, `rootfs/`, `qemu/`) | Runs Apple Music's Android libraries for one logged-in account and serves `/m3u8`, `/key`, `/lyrics`, `/webplayback`, `/license` and `/token` on one HTTP port. | MIT |
| **wrapper-manager** | [`manager/`](manager/) | Starts, logs in, restarts and restores wrapper-lite instances behind one wrapper-lite-compatible port, with `POST /login` (two-step 2FA) and `POST /logout`. | AGPL-3.0 ([`manager/LICENSE`](manager/LICENSE)) |

amdl talks to either one over HTTP (`wrapper.address`). A bare wrapper-lite is
enough for downloading; the manager adds the login API that amdl's console and
desktop app use to sign an account in. amdl's desktop app can also run
wrapper-lite itself in a QEMU guest built by this repository's `build-lite`
workflow.

Neither part has authentication: never expose its port beyond the machines that
need it. `GET /token` returns the account's music token.

## wrapper-lite

> **AMDL-Web fork.** wrapper-lite is upstream
> [`WorldObservationLog/wrapper@lite`](https://github.com/WorldObservationLog/wrapper/tree/lite)
> plus two patches to `lite/playback.cpp`. It releases the
> FairPlay decrypt context of each `/key` once its template has been copied:
> upstream keeps every context, and after 1024 successful `/key` calls in one
> process every later `/key` fails with `KDCanProcessCKC status: -42786` until
> the process restarts. And it fetches the persistent key once per `/key`
> instead of twice: each fetch is a license round trip to Apple, so `/key`
> takes about half as long. The fork also adds `GET /token`. Builds come from
> upstream's `build-lite` workflow, with its macOS job moved to `macos-15` since
> GitHub retired `macos-14`; a change under `manager/` alone does not run it.
> Upstream changes are merged in, not rebased. The manager comes from
> [`WorldObservationLog/wrapper-manager@v2`](https://github.com/WorldObservationLog/wrapper-manager/tree/v2)
> and is merged in under `manager/` with `git subtree`.

A lightweight single-port HTTP wrapper for Apple Music decryption.

It provides six endpoints on one HTTP port: M3U8, Key, Lyrics, License, WebPlayback, and Token.

> **Recommended way to run**: use the `wrapper-lite-qemu` launcher — it boots a
> self-contained QEMU guest and forwards the HTTP API to the host, so you don't
> need a rooted device or a native chroot. See
> [Run with QEMU](#run-with-qemu-recommended).

## Build
| **wrapper-lite** | this root (`lite/`, launchers, `rootfs/`, `qemu/`) | Runs Apple Music's Android libraries for one logged-in account and serves the [HTTP API](#http-api) on one port. | MIT |
| **wrapper-manager** | [`manager/`](manager/) | Starts, logs in, restarts and restores up to 10 wrapper-lite instances behind one wrapper-lite-compatible port, with `POST /login` (two-step 2FA) and `POST /logout`. | AGPL-3.0 ([`manager/LICENSE`](manager/LICENSE)) |

A bare wrapper-lite can serve downloads. The manager adds the login API that
amdl's console and desktop app use to sign accounts in. Neither part has
authentication: keep its port on the machines that need it (`GET /token`
returns the account's music token). The Cloudflare deployment adds a key.

## Run it

**amdl desktop app.** Nothing to do here. The app downloads the QEMU guest
(`manager/guest/`), pinned by digest, and runs the manager inside it on this
machine.

**Docker, or a Linux host.** Run the manager, then log in from amdl's console.

```shell
git clone -b lite https://github.com/AMDL-Web/wrapper
cd wrapper/manager
docker compose up -d # privileged container; publishes 8080 on every interface
```

Without Docker (Linux x86_64, user namespaces and chroot available):

```shell
cd wrapper/manager
go build -o wrapper-manager .
./wrapper-manager # binds localhost:8080; -host sets the address
```

The API has no authentication, so keep 8080 off networks you do not trust (to
bind the Docker port to localhost only, change `ports` in
`manager/docker-compose.yml` to `127.0.0.1:8080:8080`). On first start the manager installs the wrapper-lite payload into `data/wrapper/`.
Flags, endpoints and payload pinning are in [`manager/README.md`](manager/README.md).

**Cloudflare.** One-click deploy of the manager behind a static key, with an
encrypted backup in R2 and a daily token renewal. See
[`cloudflare/README.md`](cloudflare/README.md).

Then set the address (and key, for Cloudflare) in amdl's Settings; the amdl docs
cover [the wrapper](https://github.com/AMDL-Web/apple-music-downloader/blob/dev/docs/install.md#the-wrapper)
and [its configuration](https://github.com/AMDL-Web/apple-music-downloader/blob/dev/docs/configuration.md#wrapper).

## Release assets

Every successful push build on `lite` publishes its artifacts as GitHub
pre-releases ([`publish.yml`](.github/workflows/publish.yml)); an asset is never
replaced once published, so its sha256 can be pinned:

| Tag | Asset | From |
| --- | --- | --- |
| `guest-<sha12>` | `manager-qemu-guest.zip` | `manager.yml`: the QEMU guest of the manager (`manager/guest/`) |
| `lite-<sha12>` | `wrapper-lite-linux-x86_64.zip` | `build-lite.yml`: the native payload the manager installs |

`<sha12>` is the first 12 characters of the commit. The native payload carries
22 of the 98 libraries in `rootfs/system/lib64`, the ones wrapper-lite loads;
`tools/slim-rootfs.py` removes the rest when the package is assembled, and its
`KEEP` list says why each stays. The repository's `rootfs/` stays whole.

## What differs from upstream

This is a fork of [`WorldObservationLog/wrapper@lite`](https://github.com/WorldObservationLog/wrapper/tree/lite)
with [`wrapper-manager@v2`](https://github.com/WorldObservationLog/wrapper-manager/tree/v2)
merged in under `manager/`. Upstream changes are merged in, not rebased.

wrapper-lite (`lite/`, launchers):

- **1024-key fix.** Upstream keeps the FairPlay decrypt context of every `/key`;
after 1024 successful calls in one process every later `/key` fails with
`KDCanProcessCKC status: -42786` until the restart. The fork frees each
context once its template has been copied. It also fetches the persistent key
once per `/key` instead of twice, which halves a `/key` (each fetch is a
license round trip to Apple).
- **`GET /token`**, with `refreshed_at` and `next_refresh_at`.
- **Stale token refresh at start.** A process that starts when the cached token
is older than `--token-refresh-interval` refreshes it a few seconds in, not a
full interval later (a woken container, a machine back from a restart).
- **`APPLE_ID` file and `/status` `name`.** Login records the Apple ID in
`<base-dir>/APPLE_ID`; `/status` reports it so clients can label the account.
- **Root launcher in a private mount namespace.** `wrapper-lite` run as root
mounts in its own namespace instead of the host's.
- **Slimmed native payload** (see above) and `macos-15` in `build-lite` (GitHub
retired `macos-14`).

wrapper-manager (`manager/`): up to 10 accounts, `/status.instances`,
`/token?instance=`, login cancel, `POST /shutdown` and durable writes for the
QEMU guest, the admin API, and a payload pinned by URL and sha256. The details
are in [`manager/README.md`](manager/README.md).

A change under `manager/`, `cloudflare/` or a `.md` file alone does not run
`build-lite`.

## wrapper-lite reference

### Build

```bash
mkdir -p build && cd build
Expand All @@ -63,7 +121,7 @@ Build outputs:
- `wrapper-lite` — privileged host launcher
- `wrapper-lite-rootless` — rootless host launcher (user namespaces)

## Run natively
### Run natively

```bash
# Login, cache tokens, then exit
Expand All @@ -78,21 +136,15 @@ Build outputs:
- Interactive prompt when a TTY is available.
- With `--code-from-file`, the code is read from `data/2fa.txt`.

The native package (the `wrapper-lite-linux-x86_64` artifact) carries 22 of the
98 libraries in `rootfs/system/lib64`: the ones wrapper-lite loads.
`tools/slim-rootfs.py` removes the rest when the package is assembled, so the
repository's `rootfs/` stays whole. Its `KEEP` list says why each library stays.

## Run with QEMU (recommended)
### Run with QEMU

`wrapper-lite-qemu` is the recommended launcher. It boots the prebuilt guest
`wrapper-lite-qemu` boots the prebuilt guest
(the `qemu/` assets) in QEMU and forwards the guest's HTTP service to the host,
so no rooted device or native chroot is required. Prebuilt releases already
bundle QEMU plus its firmware in `qemu/bin/` for Linux, macOS, Windows and
Android.
so no rooted device or native chroot is required. Packages that bundle QEMU
and its firmware put them in `qemu/bin/`.

```bash
# one-time: build the launcher (or download a prebuilt release)
# one-time: build the launcher
c++ -std=c++11 -O2 -o wrapper-lite-qemu wrapper-lite-qemu.cpp

# login first (forwards --login to the guest lite), then serve
Expand Down Expand Up @@ -121,7 +173,7 @@ packages place them next to the binary).
See [QEMU launcher arguments](#qemu-launcher-arguments) for the full option
list.

## Docker
### Docker

```bash
docker build -t wrapper-lite:local .
Expand All @@ -131,7 +183,7 @@ docker run --privileged -p 12340:12340 \
wrapper-lite:local
```

## HTTP API
### HTTP API

All responses use:

Expand All @@ -149,7 +201,7 @@ All responses use:
| `/status` | GET | returns `regions` (list of storefront codes this wrapper can serve) and `name` (the Apple ID the account logged in with, from `<base-dir>/APPLE_ID`; `""` for an account logged in before lite recorded it) |
| `/token` | GET | returns `region`, `music_token`, `refreshed_at`, `next_refresh_at`; 503 until a music token exists |

## lite arguments
### lite arguments

| Argument | Default | Description |
|----------|---------|-------------|
Expand All @@ -166,7 +218,7 @@ All responses use:
| `--token-refresh-interval` | `1800` | background refresh interval in seconds; at start, the first refresh comes when `<base-dir>/MUSIC_TOKEN` (written only when a new token is obtained) reaches that age, at least 5 s in |
| `--cors` | off | send CORS headers (`Access-Control-Allow-Origin: *`) and answer `OPTIONS` preflights so browser pages on any origin can call the API; the API has no authentication, so only enable it on instances you trust to be reachable |

## Device info
### Device info

`lite` identifies itself to Apple's servers with a device-info string of the
form:
Expand All @@ -187,7 +239,7 @@ The trailing `AndroidID` is derived automatically:

Pass `--device-info <string>` to override the entire string explicitly.

## QEMU launcher arguments
### QEMU launcher arguments

| Argument | Default | Description |
|----------|---------|-------------|
Expand All @@ -213,11 +265,13 @@ Environment variables (fallbacks; command-line flags take precedence):
| `LITE_QEMU_ACCEL` | auto | force acceleration |
| `QEMU_BIN` | auto | QEMU binary path |

## Build notes

- Release: SSL verification is enabled by default. Pass `--debug` to disable it temporarily.
- Debug: SSL verification is disabled by default.
## License and credit

## License
wrapper-lite is MIT ([`LICENSE`](LICENSE)); wrapper-manager is AGPL-3.0
([`manager/LICENSE`](manager/LICENSE)). Both come from
[WorldObservationLog](https://github.com/WorldObservationLog)'s
[wrapper](https://github.com/WorldObservationLog/wrapper) and
[wrapper-manager](https://github.com/WorldObservationLog/wrapper-manager);
this fork keeps their history.

This project is licensed under the [MIT License](LICENSE).
Loading
Loading