6.0 KiB
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 (see02-caddyopen 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:
- 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. - 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. - 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 intomirror/upstream/resources.download.minecraft.net/<2hex>/<hash>. - 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)
- Set laptop DNS → our dnsmasq (LAN IP). (Often via DHCP/router, or manual.)
- Import the Caddy root CA into the OS/browser trust store.
- 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.orgpath layout Fjord requests (fork may pin a different meta host/version). - Verify Caddy
tls internalcerts 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 todnsmasq/dnsmasq.conf.tmpl(01) - Add per-host
tls internalvhosts + 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)
.gitignorealready coversmirror/