initial commit
This commit is contained in:
132
plan/11-full-airgap-mirror.md
Normal file
132
plan/11-full-airgap-mirror.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Full client air-gap — DNS + TLS transparent mirror
|
||||
|
||||
## Summary
|
||||
|
||||
Goal: a guest laptop with **zero internet** can install + launch the modded
|
||||
instance entirely from the LAN. The chosen architecture is a **transparent
|
||||
mirror**: dnsmasq points the real upstream hostnames at our host, Caddy serves
|
||||
byte-exact copies of the files at the same paths, and `tls internal` mints certs
|
||||
those hostnames validate against (guests trust the Caddy root CA once).
|
||||
|
||||
The launcher is **unmodified** — it requests the real Mojang/Prism/NeoForge URLs;
|
||||
DNS just resolves them to us. This is cleaner than rewriting Prism-format
|
||||
metadata, and it makes the `10-assets-mirror.md` "Assets Server" field
|
||||
**redundant** (the launcher hits the real resources host, which now resolves to
|
||||
the LAN). `10` is kept as the lighter objects-only alternative.
|
||||
|
||||
## Decision record
|
||||
|
||||
- **TLS for LAN party = Caddy `tls internal`** (not step-ca). Zero extra infra;
|
||||
Caddy is the local CA. step-ca stays a future-homelab reminder (see `02-caddy`
|
||||
open item). Guests import the Caddy root CA once.
|
||||
- **Full air-gap chosen** over pre-seed.
|
||||
|
||||
## Hosts to mirror (NeoForge 1.21.1 instance)
|
||||
|
||||
| Host | Serves | Notes |
|
||||
|---|---|---|
|
||||
| `meta.prismlauncher.org` | Prism-format component meta (MC, LWJGL, NeoForge) | path `/v1/...` |
|
||||
| `piston-meta.mojang.com` | version manifest + version json | |
|
||||
| `piston-data.mojang.com` | client.jar, asset index, some objects | |
|
||||
| `libraries.minecraft.net` | vanilla MC libraries (maven layout) | |
|
||||
| `maven.neoforged.net` | NeoForge libraries | more mavens may appear → capture catches them |
|
||||
| `resources.download.minecraft.net` | asset objects (~400 MB+) | the bulk |
|
||||
|
||||
**Not mirrored — auth runtime** (`session.minecraft.net`, `textures.minecraft.net`,
|
||||
`api.mojang.com`, `sessionserver.mojang.com`): these are redirected to **drasl**
|
||||
by the authlib-injector account already; leave them to drasl.
|
||||
|
||||
## How it works
|
||||
|
||||
```
|
||||
guest laptop (LAN DNS = our dnsmasq, trusts Caddy root CA)
|
||||
│ resolves meta.prismlauncher.org, libraries.minecraft.net, … → HOST_LAN_IP
|
||||
▼
|
||||
dnsmasq ── spoof A-records ──► Caddy (tls internal, cert per host)
|
||||
└─ static file_server, byte-exact path mirror
|
||||
```
|
||||
|
||||
### dnsmasq (extends 01)
|
||||
|
||||
Add explicit spoof records alongside the `*.${BASE_DOMAIN}` wildcard:
|
||||
|
||||
```
|
||||
address=/meta.prismlauncher.org/${HOST_LAN_IP}
|
||||
address=/piston-meta.mojang.com/${HOST_LAN_IP}
|
||||
address=/piston-data.mojang.com/${HOST_LAN_IP}
|
||||
address=/libraries.minecraft.net/${HOST_LAN_IP}
|
||||
address=/maven.neoforged.net/${HOST_LAN_IP}
|
||||
address=/resources.download.minecraft.net/${HOST_LAN_IP}
|
||||
```
|
||||
> Only effective while the guest uses our dnsmasq as resolver. Fully reversible —
|
||||
> off-LAN the real hosts resolve normally. Keep this list in sync with whatever
|
||||
> the capture step (below) actually observed.
|
||||
|
||||
### Caddy (extends 02)
|
||||
|
||||
One vhost per spoofed host, each rooted at its mirror tree, all `tls internal`:
|
||||
|
||||
```
|
||||
piston-data.mojang.com, libraries.minecraft.net, resources.download.minecraft.net,
|
||||
meta.prismlauncher.org, piston-meta.mojang.com, maven.neoforged.net {
|
||||
tls internal
|
||||
root * /srv/mirror/{host}
|
||||
file_server
|
||||
}
|
||||
```
|
||||
(Practically: a small snippet per host, or Caddy on-demand TLS + a path-routed
|
||||
`map`. Keep explicit per-host for clarity.) Add each as a **network alias** on
|
||||
Caddy too, so the `minecraft`/tooling containers resolve them internally.
|
||||
|
||||
Mount `./mirror/upstream/<host>:/srv/mirror/<host>:ro`.
|
||||
|
||||
## Capture (the actual work) — `tooling/mirror-airgap.sh`
|
||||
|
||||
Byte-exact mirroring needs the exact URL set. Capture it once, online:
|
||||
|
||||
1. **Record**: launch the instance once with the launcher pointed at a logging
|
||||
forward-proxy (mitmproxy `--mode reverse`/transparent, or even Caddy as a
|
||||
logging reverse proxy). Collect every requested absolute URL.
|
||||
2. **Fetch**: for each URL, download to `mirror/upstream/<host>/<path>`,
|
||||
preserving path exactly. Verify SHA1 where the source json provides it
|
||||
(libraries, assets). Halt on mismatch.
|
||||
3. **Assets shortcut**: the objects (resources.download…) can be filled directly
|
||||
from the asset index (same logic as `10-assets-mirror.md`) without proxy
|
||||
capture — reuse that loop into `mirror/upstream/resources.download.minecraft.net/<2hex>/<hash>`.
|
||||
4. **Verify offline**: re-run a launch on a second machine with internet cut and
|
||||
DNS set to our dnsmasq + CA trusted. Fix any 404 → missing URL → add to fetch.
|
||||
|
||||
> Cautious by design: the proxy-capture enumerates whatever hosts/paths are *truly*
|
||||
> hit (incl. unforeseen maven mirrors), so the mirror is complete rather than
|
||||
> guessed. Re-capture if MC/NeoForge versions change.
|
||||
|
||||
## Guest one-time setup (offline)
|
||||
|
||||
1. Set laptop DNS → our dnsmasq (LAN IP). (Often via DHCP/router, or manual.)
|
||||
2. Import the **Caddy root CA** into the OS/browser trust store.
|
||||
3. Install Fjord, add authlib-injector account (`auth.${BASE_DOMAIN}`), import pack,
|
||||
launch. All downloads resolve to the LAN mirror.
|
||||
|
||||
No launcher API-field overrides needed in this architecture.
|
||||
|
||||
## Open / reminders
|
||||
|
||||
- [ ] **Reminder (kept):** graduate `tls internal` → step-ca for the persistent
|
||||
homelab; revisit cert distribution then.
|
||||
- [ ] Confirm exact `meta.prismlauncher.org` path layout Fjord requests (fork may
|
||||
pin a different meta host/version).
|
||||
- [ ] Verify Caddy `tls internal` certs validate in Fjord's Qt network stack with
|
||||
the root CA imported (no extra pinning).
|
||||
- [ ] mitmproxy vs Caddy-log for the capture step — pick one.
|
||||
- [ ] CA-trust UX per OS (Windows/macOS/Linux) — document for guests.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Write `tooling/mirror-airgap.sh` (capture + fetch + sha1 verify + offline re-test)
|
||||
- [ ] Add spoof `address=` lines to `dnsmasq/dnsmasq.conf.tmpl` (01)
|
||||
- [ ] Add per-host `tls internal` vhosts + network aliases + mounts to Caddy (02)
|
||||
- [ ] Export Caddy root CA; build a guest CA-import mini-guide (link from landing page)
|
||||
- [ ] Run capture online for MC 1.21.1 + NeoForge 21.1.x; populate `mirror/upstream/`
|
||||
- [ ] Offline acceptance test on a clean machine (internet cut, LAN DNS, CA trusted)
|
||||
- [ ] `.gitignore` already covers `mirror/`
|
||||
```
|
||||
Reference in New Issue
Block a user