From 455f66db3582fef120128403152cfe6fc8bed926 Mon Sep 17 00:00:00 2001 From: LYJW131 <153256373+LYJW131@users.noreply.github.com> Date: Tue, 3 Nov 2026 18:57:37 +0800 Subject: [PATCH 1/2] docs: tidy the READMEs for amdl 2.0 The root README now says what the repo is, the shortest way to run it (amdl desktop app, Docker or Linux host, Cloudflare), the release assets and what differs from upstream. The manager README drops the nightly.link, build-manager-qemu and AppleMusicDecrypt text, documents payload pinning and -update-wrapper. The QEMU READMEs no longer repeat the root's launcher notes. Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: LYJW131 <153256373+LYJW131@users.noreply.github.com> --- README.md | 178 ++++++++++++++++++++++++++-------------- manager/README.md | 143 +++++++++++++++++--------------- manager/guest/README.md | 21 +++-- qemu/README.md | 5 +- 4 files changed, 206 insertions(+), 141 deletions(-) diff --git a/README.md b/README.md index 41571ec..1c5129a 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,105 @@ # 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 # API on localhost:8080; the container is privileged +``` + +Without Docker (Linux x86_64, user namespaces and chroot available): + +```shell +cd wrapper/manager +go build -o wrapper-manager . +./wrapper-manager -host 0.0.0.0 -port 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-` | `manager-qemu-guest.zip` | `manager.yml`: the QEMU guest of the manager (`manager/guest/`) | +| `lite-` | `wrapper-lite-linux-x86_64.zip` | `build-lite.yml`: the native payload the manager installs | + +`` 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 + `/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 @@ -63,7 +119,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 @@ -78,21 +134,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 @@ -121,7 +171,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 . @@ -131,7 +181,7 @@ docker run --privileged -p 12340:12340 \ wrapper-lite:local ``` -## HTTP API +### HTTP API All responses use: @@ -149,7 +199,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 `/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 | |----------|---------|-------------| @@ -166,7 +216,7 @@ All responses use: | `--token-refresh-interval` | `1800` | background refresh interval in seconds; at start, the first refresh comes when `/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: @@ -187,7 +237,7 @@ The trailing `AndroidID` is derived automatically: Pass `--device-info ` to override the entire string explicitly. -## QEMU launcher arguments +### QEMU launcher arguments | Argument | Default | Description | |----------|---------|-------------| @@ -213,11 +263,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). diff --git a/manager/README.md b/manager/README.md index 10bbe77..9abb3cc 100644 --- a/manager/README.md +++ b/manager/README.md @@ -1,32 +1,30 @@ # wrapper-manager -> **AMDL-Web fork, in [AMDL-Web/wrapper](https://github.com/AMDL-Web/wrapper) under `manager/`.** -> Identical to -> [upstream `v2`](https://github.com/WorldObservationLog/wrapper-manager/tree/v2) -> except that it installs wrapper-lite from -> [AMDL-Web/wrapper's `lite` branch](https://github.com/AMDL-Web/wrapper/tree/lite), -> which frees the FairPlay decrypt context of each `/key`; upstream's build fails -> every `/key` with `KDCanProcessCKC status: -42786` after 1024 per process. A -> payload installed from anywhere else (no matching `data/wrapper/.payload-source`) -> is replaced on the next start; account data is kept. Upstream changes are merged -> in, not rebased. - -An HTTP management/proxy gateway that runs and supervises **multiple -[wrapper-lite](https://github.com/WorldObservationLog/wrapper) account -instances** (branch `lite`) behind one HTTP endpoint. +An HTTP gateway that runs and supervises **multiple +[wrapper-lite](../README.md) account instances** behind one port, for +[amdl](https://github.com/AMDL-Web/apple-music-downloader). This is the +[AMDL-Web/wrapper](https://github.com/AMDL-Web/wrapper) fork of +[upstream `v2`](https://github.com/WorldObservationLog/wrapper-manager/tree/v2), +merged in under `manager/` (upstream changes are merged in, not rebased). What +differs, besides the payload below: up to 10 accounts, `instances` in `/status`, +`/token`, cancelable logins, `/shutdown`, durable writes, the admin API and +`-launcher`, `-payload-url` and `-payload-sha256`. Each logged-in account is a long-lived `wrapper-lite-rootless` child process -(service mode) listening on a private random port. wrapper-manager starts, -logs in, logs out, restarts and restores those instances, and exposes a -wrapper-lite-compatible HTTP API that aggregates them: a request carrying an -`adamId` is forwarded to an instance whose Apple storefront region can serve -that item, and the wrapper-lite response envelope is passed through unchanged. - -> **Not a decryptor.** This project no longer provides sample decryption and no -> longer speaks gRPC. Decryption is performed locally by -> [AppleMusicDecrypt](https://github.com/WorldObservationLog/AppleMusicDecrypt) -> v3 (Temari); wrapper-manager only supplies the `/key` `ctx`/`state` template -> and `/m3u8`/`/license` upstreams that the decryptor consumes over HTTP. +(service mode) listening on a private random port. The manager starts, logs in, +logs out, restarts and restores them, and exposes a wrapper-lite-compatible HTTP +API that aggregates them: a request carrying an `adamId` is forwarded to an +instance whose Apple storefront region can serve that item, and the wrapper-lite +response envelope is passed through unchanged. + +The manager does not decrypt. It supplies the `/key` `ctx`/`state` template and +the `/m3u8` and `/license` upstreams; the client (amdl) decrypts locally. + +The payload it installs is this repository's `lite` branch build, which frees +the FairPlay decrypt context of each `/key`; upstream's fails every `/key` with +`KDCanProcessCKC status: -42786` after 1024 per process. A payload installed +from anywhere else (no matching `data/wrapper/.payload-source`) is replaced on +the next start; account data is kept. ## Features @@ -43,9 +41,9 @@ that item, and the wrapper-lite response envelope is passed through unchanged. logged out). - **Startup recovery** — persisted accounts in `data/instances.json` are relaunched automatically (service mode only, no re-login). -- **First-run self-provisioning** — downloads the native wrapper-lite payload - from nightly.link automatically when `data/wrapper/` is missing or was - installed from another source. +- **First-run self-provisioning** — installs the native wrapper-lite payload + when `data/wrapper/` is missing or was installed from another source; a + payload can be [pinned by URL and sha256](#payload). ## Requirements @@ -57,7 +55,7 @@ that item, and the wrapper-lite response envelope is passed through unchanged. user namespaces); see [Deploy](#deploy-docker-compose). With `-launcher root` the manager uses `wrapper-lite` instead, which needs root (it mounts and chroots itself) but no user namespace. -- Internet access to `nightly.link` on first run (payload download) and to +- Internet access on first run (payload download, see [Payload](#payload)) and to Apple services (`amp-api.music.apple.com`, etc.) for login, region probing and upstream requests. A configured `-proxy` applies to all of them. @@ -68,12 +66,25 @@ go build -o wrapper-manager . ./wrapper-manager ``` -On first start the manager downloads and unpacks the wrapper-lite native -package for your architecture from -`https://nightly.link/AMDL-Web/wrapper/workflows/build-lite/lite/wrapper-lite-linux-x86_64.zip`. -It is stored under `data/wrapper/`, with the URL it came from in -`data/wrapper/.payload-source`. Use `-prepare` to perform only this download and exit -(handy for pre-seeding images). +`-prepare` performs only the payload install and exits (handy for pre-seeding +images); `-update-wrapper` reinstalls the newest payload and exits. + +### Payload + +By default the manager installs the newest `build-lite` artifact on `lite` +(`wrapper-lite-linux-x86_64.zip`, `…-aarch64.zip` on arm64) into `data/wrapper/`, +recording where it came from in `data/wrapper/.payload-source`. To run a fixed +build, pin a release asset of this repository (tags `lite-`, see the +[root README](../README.md#release-assets)) and its digest: + +```shell +./wrapper-manager -payload-url \ + https://github.com/AMDL-Web/wrapper/releases/download/lite-/wrapper-lite-linux-x86_64.zip \ + -payload-sha256 +``` + +A download that does not match the digest is refused and the manager exits. +`-mirror` only rewrites the default URL, not one given with `-payload-url`. ### Command-line flags @@ -81,12 +92,13 @@ It is stored under `data/wrapper/`, with the URL it came from in |---|---|---| | `-host` | `localhost` | Host the HTTP server binds to (use `0.0.0.0` in containers). | | `-port` | `8080` | Port of the HTTP server. | -| `-mirror` | `false` | Download wrapper-lite through `gh-proxy.com` instead of nightly.link (for users in China). | +| `-mirror` | `false` | Download the default payload through `gh-proxy.com` (for users in China). | | `-debug` | `false` | Enable debug output. | | `-proxy` | *(empty)* | HTTP(S) proxy used by the manager for downloads/Apple probes and passed through to wrapper-lite (`--proxy`). | -| `-prepare` | `false` | Only download the required wrapper-lite payload, then exit. | +| `-prepare` | `false` | Only install the wrapper-lite payload, then exit. | +| `-update-wrapper` | `false` | Reinstall the newest payload even when one is installed, then exit. | | `-launcher` | `rootless` | `rootless` starts each wrapper-lite with `wrapper-lite-rootless` (user namespaces); `root` uses `wrapper-lite`, which needs root and suits containers that refuse a user namespace's `/proc` mount, such as Cloudflare Containers. | -| `-payload-url` | *(empty)* | wrapper-lite payload zip to install instead of the newest `build-lite` artifact on `lite`, e.g. one run's `https://nightly.link/AMDL-Web/wrapper/actions/runs//wrapper-lite-linux-x86_64.zip`. | +| `-payload-url` | *(empty)* | wrapper-lite payload zip to install instead of the newest `build-lite` artifact on `lite`; see [Payload](#payload). | | `-payload-sha256` | *(empty)* | Lowercase hex sha256 the `-payload-url` zip must have. A download that differs is refused, nothing is installed and the manager exits, even when another payload is already installed; the digest is part of `data/wrapper/.payload-source`, so a new digest reinstalls. Needs `-payload-url`. | | `-admin-port` | `0` (off) | Port of the [admin API](#admin-api) on `-host`. Never expose it. | | `-device-info` | `Music/5.0.2/Android/10/Pixel 8/7663314/en-US/en-US` | Optional Apple Music device-info string (`--device-info` pass-through to wrapper-lite). | @@ -116,7 +128,7 @@ instance"` (retry later). | Method | Path | Parameters | Notes | |---|---|---|---| | `GET` | `/m3u8` | `adamId` | Playlist for the item. | -| `GET` | `/key` | `adamId`, `uri` (required) | Key/decryption context — `data` carries the `ctx`/`state` template used by AppleMusicDecrypt v3 + local Temari. The prefetch URI `skd://itunes.apple.com/P000000000/s1/e1` is only accepted with `adamId=0` (upstream rejects it for a real track). | +| `GET` | `/key` | `adamId`, `uri` (required) | Key/decryption context — `data` carries the `ctx`/`state` template that amdl decrypts with locally. The prefetch URI `skd://itunes.apple.com/P000000000/s1/e1` is only accepted with `adamId=0` (upstream rejects it for a real track). | | `GET` | `/lyrics` | `adamId`, `language?`, `syllable?`, `script?` | Prefers an instance whose storefront supports the language and has lyrics (probed), falls back to region selection. `script` (transliteration, default `en-Latn`, e.g. `ja-Latn`) is forwarded to the instance. | | `GET` | `/webplayback` | `adamId` | Web-playback session data. | | `POST` | `/license` | JSON body `{"adamId","challenge","uri","drm-type"?}` | License request forwarded to the selected instance. Optional `drm-type`: `wv` (Widevine, default) or `pr` (PlayReady). | @@ -233,7 +245,7 @@ Everything lives under `data/` relative to the working directory: ``` data/ -├── wrapper/ # wrapper-lite payload (auto-downloaded) +├── wrapper/ # wrapper-lite payload (see Payload) │ ├── wrapper-lite-rootless # rootless launcher (login + service entry) │ ├── wrapper-lite │ └── rootfs/ # chroot userland used by the launcher @@ -258,27 +270,34 @@ data/ ## Run on any OS via QEMU (wrapper-manager-qemu) The native manager requires Linux (it launches `wrapper-lite-rootless`, which -needs user namespaces + chroot). To run it on **Windows / macOS / any Linux** -without a Linux host, boot the whole manager inside a single QEMU Linux guest: +needs user namespaces + chroot). To run it on Windows, macOS or any Linux without +a Linux host, boot the whole manager inside a single QEMU Linux guest. amdl's +desktop app does this for you; this section is for running it by hand. ``` -Windows / macOS / Linux host +host └─ wrapper-manager-qemu (Go launcher, cross-platform) └─ QEMU x86_64 guest: vmlinuz-lite-qemu + wrapper-manager-initramfs └─ wrapper-manager -host 0.0.0.0 -port 8080 (Linux native) └─ wrapper-lite-rootless instances (per account) ``` -The launcher is modeled after upstream -[wrapper-lite-qemu](https://github.com/WorldObservationLog/wrapper/blob/lite/wrapper-lite-qemu.cpp): +The launcher: -- Locates `qemu-system-x86_64` (`--qemu-bin` > `QEMU_BIN` > `PATH` > bundled). -- Auto-selects acceleration: KVM (Linux) / WHPX (Windows) / TCG (macOS & - fallback) with automatic fallback to TCG when the accel is unavailable. +- Locates `qemu-system-x86_64` (`--qemu-bin` > `QEMU_BIN` > `PATH` > bundled + in `/qemu/bin`). +- Auto-selects acceleration: KVM (Linux) / WHPX (Windows) / TCG (macOS and + fallback). - Boots the guest kernel + initramfs, attaches a persistent `data.img` and - forwards `hostPort` → guest manager port (8080). -- Guest images live in `/guest/` (`--assets-dir` to override); the - persistent data image defaults to `~/.wrapper-manager/data.img`. + forwards `--host-port` to the guest manager port (8080). +- Reads guest images from `/guest/` (`--assets-dir` to override); the + data image defaults to `~/.wrapper-manager/data.img`. + +The guest itself is built by [`guest/`](guest/README.md) and published as the +`manager-qemu-guest.zip` release asset of each `guest-` tag +([root README](../README.md#release-assets)); the launcher binaries for +linux x86_64/aarch64, windows and macOS are 14-day artifacts of the `manager` +workflow. ### Build @@ -286,19 +305,11 @@ The launcher is modeled after upstream # 1. the launcher (any platform) go build -o wrapper-manager-qemu ./cmd/wrapper-manager-qemu -# 2. the guest (on Linux; see guest/README.md). The kernel and its e1000 and -# qemu_fw_cfg modules come from the build-lite qemu-assets artifact. -curl -fsSL -o qemu-assets.zip \ - "https://nightly.link/AMDL-Web/wrapper/workflows/build-lite/lite/qemu-assets.zip" -unzip -o qemu-assets.zip vmlinuz-lite-qemu lite-initramfs.cpio.gz -d kernel-assets/ +# 2. the guest (on Linux), from kernel-assets/ as described in guest/README.md KERNEL_ASSETS=kernel-assets ./guest/build.sh out # kernel + initramfs KERNEL_ASSETS=kernel-assets CREATE_DATA_IMG=1 ./guest/build.sh out # also a fresh data.img ``` -Prebuilt artifacts (launchers for linux/windows/macos + guest image) are -published by the `build-manager-qemu` workflow and are fetchable through -nightly.link. - ### Run ```shell @@ -311,9 +322,9 @@ Flags: `--host --host-port --guest-port --memory --smp --accel --qemu-bin --assets-dir --data-dir --kernel --initrd`; environment fallbacks `QEMU_BIN / HOST_PORT / GUEST_PORT / MEMORY / SMP`. -Guest networking uses QEMU user-mode NAT (`10.0.2.15`); the guest downloads the -wrapper-lite payload itself on first boot (nightly.link) and keeps everything -inside `data.img`, so account state and payload survive restarts. +Guest networking uses QEMU user-mode NAT (`10.0.2.15`); the guest installs the +wrapper-lite payload itself on first boot and keeps everything inside +`data.img`, so account state and payload survive restarts. ## Deploy (docker compose) @@ -323,7 +334,7 @@ Docker blocks by default — the compose file runs the container with seccomp/apparmor profile that permits `unshare`/user namespaces instead. ```shell -git clone https://github.com/AMDL-Web/wrapper +git clone -b lite https://github.com/AMDL-Web/wrapper cd wrapper/manager docker compose up -d ``` @@ -339,5 +350,5 @@ For users in China: uncomment the Go module proxy line in `Dockerfile` and add ## Related projects -- [WorldObservationLog/wrapper](https://github.com/WorldObservationLog/wrapper) — upstream wrapper / wrapper-lite (branch `lite`). -- [WorldObservationLog/AppleMusicDecrypt](https://github.com/WorldObservationLog/AppleMusicDecrypt) — decryptor that consumes this manager's `/m3u8` `/key` `/license` API. +- [AMDL-Web/apple-music-downloader](https://github.com/AMDL-Web/apple-music-downloader) — amdl, the client of this API. +- [WorldObservationLog/wrapper-manager](https://github.com/WorldObservationLog/wrapper-manager) — upstream wrapper-manager (branch `v2`). diff --git a/manager/guest/README.md b/manager/guest/README.md index b84a18a..90de2a0 100644 --- a/manager/guest/README.md +++ b/manager/guest/README.md @@ -1,7 +1,7 @@ # wrapper-manager QEMU guest Assets for booting the whole wrapper-manager inside a QEMU Linux guest so it -runs on Windows / macOS / any Linux host (see the parent README). +runs on Windows / macOS / any Linux host (see the [manager README](../README.md#run-on-any-os-via-qemu-wrapper-manager-qemu)). ## Layout @@ -18,21 +18,24 @@ certificates and the kernel modules. ## Build +Run from `manager/`. Requires go, cpio, gzip, e2fsprogs, file, kmod, glibc and +ca-certificates (any Debian/Ubuntu). + ```shell -# requires: go, cpio, gzip, e2fsprogs, file, kmod, glibc + ca-certificates (any Debian/Ubuntu) -curl -fsSL -o qemu-assets.zip \ - "https://nightly.link/AMDL-Web/wrapper/workflows/build-lite/lite/qemu-assets.zip" -unzip -o qemu-assets.zip vmlinuz-lite-qemu lite-initramfs.cpio.gz -d kernel-assets/ KERNEL_ASSETS=kernel-assets ./guest/build.sh out # kernel + initramfs KERNEL_ASSETS=kernel-assets CREATE_DATA_IMG=1 ./guest/build.sh out # also a fresh 512MB data.img ``` +`kernel-assets/` holds `vmlinuz-lite-qemu` and `lite-initramfs.cpio.gz` from the +`qemu-assets` artifact of a `build-lite` run on `lite` (the "Fetch the build-lite +kernel and its modules" step of `.github/workflows/manager.yml` does this). CI +boots the result and publishes it as the `manager-qemu-guest.zip` asset of a +`guest-` release ([root README](../../README.md#release-assets)). + ## Kernel -`vmlinuz-lite-qemu` is **not** built here. It is the kernel produced by the -[AMDL-Web/wrapper](https://github.com/AMDL-Web/wrapper) (a fork of -[WorldObservationLog/wrapper](https://github.com/WorldObservationLog/wrapper)) -`build-lite` workflow and is distributed in its `qemu-assets` artifact. +`vmlinuz-lite-qemu` is **not** built here. It is the kernel produced by this +repository's `build-lite` workflow, in its `qemu-assets` artifact. build.sh copies it next to the initramfs, and takes `e1000.ko` and `qemu_fw_cfg.ko` from the same artifact's `lite-initramfs.cpio.gz`: modules built for another kernel do not load, which leaves the guest without network diff --git a/qemu/README.md b/qemu/README.md index e50f7ca..bbd5181 100644 --- a/qemu/README.md +++ b/qemu/README.md @@ -75,6 +75,5 @@ Windows WHPX uses: ## QEMU lookup order -1. `QEMU_BIN` -2. `PATH` -3. `qemu/bin/` +`--qemu-bin` / `QEMU_BIN`, then the bundled `qemu/bin/` when it is usable, then +`PATH`. See the [root README](../README.md#run-with-qemu). From 6f9c58036356fc08ee5e8f7531088790bc3cfaf8 Mon Sep 17 00:00:00 2001 From: LYJW131 <153256373+LYJW131@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:00:36 +0800 Subject: [PATCH 2/2] docs: keep the quickstart on localhost and say no arm64 payload is published Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: LYJW131 <153256373+LYJW131@users.noreply.github.com> --- README.md | 8 +++++--- manager/README.md | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1c5129a..c816f4e 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ machine. ```shell git clone -b lite https://github.com/AMDL-Web/wrapper cd wrapper/manager -docker compose up -d # API on localhost:8080; the container is privileged +docker compose up -d # privileged container; publishes 8080 on every interface ``` Without Docker (Linux x86_64, user namespaces and chroot available): @@ -34,10 +34,12 @@ Without Docker (Linux x86_64, user namespaces and chroot available): ```shell cd wrapper/manager go build -o wrapper-manager . -./wrapper-manager -host 0.0.0.0 -port 8080 +./wrapper-manager # binds localhost:8080; -host sets the address ``` -On first start the manager installs the wrapper-lite payload into `data/wrapper/`. +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 diff --git a/manager/README.md b/manager/README.md index 9abb3cc..93c4e1b 100644 --- a/manager/README.md +++ b/manager/README.md @@ -72,7 +72,7 @@ images); `-update-wrapper` reinstalls the newest payload and exits. ### Payload By default the manager installs the newest `build-lite` artifact on `lite` -(`wrapper-lite-linux-x86_64.zip`, `…-aarch64.zip` on arm64) into `data/wrapper/`, +(`wrapper-lite-linux-x86_64.zip`; no arm64 build is published) into `data/wrapper/`, recording where it came from in `data/wrapper/.payload-source`. To run a fixed build, pin a release asset of this repository (tags `lite-`, see the [root README](../README.md#release-assets)) and its digest: