Compare commits
7 Commits
landing
...
ff645de2c8
| Author | SHA1 | Date | |
|---|---|---|---|
| ff645de2c8 | |||
| 6df885eb13 | |||
| 6fac1647aa | |||
| a6155819b6 | |||
| 3185fea4c1 | |||
| f3c38da56c | |||
| 21e6080e78 |
@@ -37,7 +37,7 @@ DISTRIBUTION_WEB_ROOT=/home/oier/projects/mc-mods/ulicraft-group/ulicraft-distri
|
|||||||
# Only needed to issue real TLS certs. DNS-01 needs no inbound ports.
|
# Only needed to issue real TLS certs. DNS-01 needs no inbound ports.
|
||||||
LE_EMAIL=you@example.com
|
LE_EMAIL=you@example.com
|
||||||
# space-separated subdomains; apex ${BASE_DOMAIN} is always included
|
# space-separated subdomains; apex ${BASE_DOMAIN} is always included
|
||||||
LE_SUBDOMAINS=auth pack distribution
|
LE_SUBDOMAINS=auth pack distribution www avatar status
|
||||||
# OVH API creds (DNS zone write). Create at https://eu.api.ovh.com/createToken/
|
# OVH API creds (DNS zone write). Create at https://eu.api.ovh.com/createToken/
|
||||||
# OVH_CK can be blank on first run — acme.sh prints an auth URL, then paste it.
|
# OVH_CK can be blank on first run — acme.sh prints an auth URL, then paste it.
|
||||||
OVH_END_POINT=ovh-eu
|
OVH_END_POINT=ovh-eu
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ runtime/authlib-injector.jar
|
|||||||
|
|
||||||
# rendered configs (from tooling/render-config.sh)
|
# rendered configs (from tooling/render-config.sh)
|
||||||
drasl/config/config.toml
|
drasl/config/config.toml
|
||||||
|
nmsr/config.toml
|
||||||
dnsmasq/dnsmasq.conf
|
dnsmasq/dnsmasq.conf
|
||||||
# packwiz pack files rendered from pack/**/*.tmpl by tooling/render-pack.sh
|
# packwiz pack files rendered from pack/**/*.tmpl by tooling/render-pack.sh
|
||||||
# (domain-dependent build output); source of truth is the .tmpl + custom/ jars.
|
# (domain-dependent build output); source of truth is the .tmpl + custom/ jars.
|
||||||
|
|||||||
125
README.md
125
README.md
@@ -14,8 +14,15 @@ Mojang/launcher/NeoForge assets so guest laptops need no internet.
|
|||||||
| `caddy` | caddy:alpine | ingress: landing, auth/packwiz proxy, asset mirror (`tls internal`) |
|
| `caddy` | caddy:alpine | ingress: landing, auth/packwiz proxy, asset mirror (`tls internal`) |
|
||||||
| `mc-backup` | itzg/mc-backup | world backups every 6h via RCON |
|
| `mc-backup` | itzg/mc-backup | world backups every 6h via RCON |
|
||||||
|
|
||||||
Optional layers: `avahi` (mDNS `.local`), `dnsmasq` (turnkey DNS) — only if you
|
Optional layers (each its own `docker-compose.<name>.yml`):
|
||||||
don't run your own DNS.
|
- `avahi` (mDNS `.local`), `dnsmasq` (turnkey DNS) — only if you don't run your
|
||||||
|
own DNS.
|
||||||
|
- `nmsr` (NickAcPT/nmsr-rs, built from source) — skin/avatar renderer at
|
||||||
|
`avatar.${BASE_DOMAIN}`, sourced from Drasl. See **Avatar renderer** below.
|
||||||
|
- `distribution` — extra static vhost at `distribution.${BASE_DOMAIN}` whose web
|
||||||
|
root (`DISTRIBUTION_WEB_ROOT`) lives in another repo.
|
||||||
|
- `status` (louislam/uptime-kuma) — uptime monitoring + public status page at
|
||||||
|
`status.${BASE_DOMAIN}`. See **Status monitoring** below.
|
||||||
|
|
||||||
Config lives in `.env`: `BASE_DOMAIN` (default `ulicraft.lan`), `HOST_LAN_IP`,
|
Config lives in `.env`: `BASE_DOMAIN` (default `ulicraft.lan`), `HOST_LAN_IP`,
|
||||||
`RCON_PASSWORD`, `ENABLE_MDNS`. Services are subdomains: `auth.`, `packwiz.`,
|
`RCON_PASSWORD`, `ENABLE_MDNS`. Services are subdomains: `auth.`, `packwiz.`,
|
||||||
@@ -80,6 +87,108 @@ tooling/build-stack.sh --up core # base only (debugging)
|
|||||||
`--up` prints the DNS records to add for that mode. Down:
|
`--up` prints the DNS records to add for that mode. Down:
|
||||||
`docker compose -f docker-compose.yml -f docker-compose.static.yml -f docker-compose.mirror.yml down`
|
`docker compose -f docker-compose.yml -f docker-compose.static.yml -f docker-compose.mirror.yml down`
|
||||||
|
|
||||||
|
## Public TLS (host nginx in front of caddy)
|
||||||
|
|
||||||
|
The LAN path above runs caddy on `:80`. To expose the stack publicly with real
|
||||||
|
certs, the machine's own nginx terminates TLS and reverse-proxies every vhost to
|
||||||
|
caddy by Host header. Caddy stays the single router (apex landing, `www.` →
|
||||||
|
apex, `auth.` → drasl, `pack.` packwiz, `distribution.` static, `avatar.` →
|
||||||
|
nmsr). HTTPS-only: HTTP 301s to HTTPS and every TLS vhost sends HSTS.
|
||||||
|
|
||||||
|
1. **Issue certs** (OVH DNS-01, no inbound ports needed):
|
||||||
|
```sh
|
||||||
|
# .env: BASE_DOMAIN, LE_EMAIL, OVH_* creds,
|
||||||
|
# LE_SUBDOMAINS="auth pack distribution www avatar status"
|
||||||
|
tooling/issue-letsencrypt.sh # → certs/<name>/{cert,key}.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Bind caddy to a localhost-only port** so only nginx reaches it (`.env`):
|
||||||
|
```sh
|
||||||
|
CADDY_HTTP_PORT=8880
|
||||||
|
CADDY_HTTP_BIND=127.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Bring up the caddy ingress** (+ static, + distribution/nmsr if used):
|
||||||
|
```sh
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.caddy.yml \
|
||||||
|
-f docker-compose.static.yml -f docker-compose.distribution.yml \
|
||||||
|
-f docker-compose.nmsr.yml -f docker-compose.status.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Render + install the nginx vhost** with `tooling/render-nginx.sh`. It pulls
|
||||||
|
`BASE_DOMAIN` + `CADDY_HTTP_PORT` from `.env`, defaults `APP_DIR` to the repo
|
||||||
|
checkout (where `certs/` live), and renders `nginx/ulicraft-caddy.conf.tmpl`:
|
||||||
|
```sh
|
||||||
|
tooling/render-nginx.sh # preview to stdout (dry run)
|
||||||
|
tooling/render-nginx.sh --install # write, symlink, nginx -t, reload
|
||||||
|
```
|
||||||
|
Overrides via env: `APP_DIR=/path` (if the repo isn't at the cert location),
|
||||||
|
`SITE_NAME=foo.conf`, `TEMPLATE=nginx/ulicraft.conf.tmpl` (the static variant —
|
||||||
|
nginx serves the files itself + proxies only drasl; pick one, not both).
|
||||||
|
|
||||||
|
Re-run step 4 whenever you add a subdomain so its server block is rendered.
|
||||||
|
|
||||||
|
## Avatar renderer (NMSR)
|
||||||
|
|
||||||
|
`avatar.${BASE_DOMAIN}` renders players' skins/avatars via
|
||||||
|
[NMSR-aas](https://github.com/NickAcPT/nmsr-rs), sourced from **Drasl** (not
|
||||||
|
Mojang). No upstream image exists, so it's built from source — `docker/nmsr/`
|
||||||
|
pins a commit; bump `ARG NMSR_REF` to update.
|
||||||
|
|
||||||
|
- **Config**: `nmsr/config.toml.tmpl` → rendered to `nmsr/config.toml` by
|
||||||
|
`tooling/render-config.sh`. `[mojank]` points every Mojang endpoint at
|
||||||
|
`http://auth.${BASE_DOMAIN}`; Drasl serves the Mojang-compatible routes at its
|
||||||
|
bare BaseURL and embeds absolute skin URLs that NMSR fetches directly.
|
||||||
|
`allow_offline_mode_uuids = true` (Drasl issues offline v3 UUIDs).
|
||||||
|
- **Network**: skin resolution stays internal over `mcnet` (the caddy alias
|
||||||
|
`auth.${BASE_DOMAIN}` → drasl) — no public round-trip. caddy reverse-proxies
|
||||||
|
`avatar.` → `nmsr:8080`. The host port is published on `127.0.0.1:9898` only
|
||||||
|
(local debugging; not reachable from outside).
|
||||||
|
- **Endpoints** (NMSR): e.g. `https://avatar.${BASE_DOMAIN}/skin/<player>`,
|
||||||
|
`/face/<player>`, `/fullbody/<player>` — by username or UUID.
|
||||||
|
|
||||||
|
Bring up: add `-f docker-compose.nmsr.yml` (with `--build`) as shown above. The
|
||||||
|
first build is heavy (Rust compile). Headless rendering uses lavapipe (software
|
||||||
|
Vulkan); if renders fail, check `docker logs nmsr` for Vulkan device init.
|
||||||
|
|
||||||
|
`distribution.${BASE_DOMAIN}` serves a static site whose web root lives in
|
||||||
|
another repo: set `DISTRIBUTION_WEB_ROOT` (absolute host path) in `.env`; it's
|
||||||
|
bind-mounted read-only via `docker-compose.distribution.yml`.
|
||||||
|
|
||||||
|
## Status monitoring (Uptime Kuma)
|
||||||
|
|
||||||
|
`status.${BASE_DOMAIN}` runs [Uptime Kuma](https://github.com/louislam/uptime-kuma)
|
||||||
|
— uptime probes for every vhost **and** a native Minecraft-protocol ping (player
|
||||||
|
count + up/down), plus a public status page. The container joins `mcnet`, so it
|
||||||
|
can probe the stack by internal service name without leaving the host. caddy
|
||||||
|
reverse-proxies `status.` → `uptime-kuma:3001`; the host port is published on
|
||||||
|
`127.0.0.1:3001` only (first-run admin setup / local debugging).
|
||||||
|
|
||||||
|
Bring up (layer on the caddy ingress):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.caddy.yml \
|
||||||
|
-f docker-compose.status.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open `https://status.${BASE_DOMAIN}` (or `http://127.0.0.1:3001` first run),
|
||||||
|
create the admin account, and add monitors. Kuma has no config-as-code — add
|
||||||
|
these once via the UI (data persists in the `kuma_data` volume):
|
||||||
|
|
||||||
|
| Monitor | Type | Target |
|
||||||
|
|---|---|---|
|
||||||
|
| Minecraft | Minecraft Server | `minecraft` : `25565` |
|
||||||
|
| Drasl auth | HTTP(s) | `https://auth.${BASE_DOMAIN}` |
|
||||||
|
| Landing (apex) | HTTP(s) | `https://${BASE_DOMAIN}` |
|
||||||
|
| Packwiz | HTTP(s) | `https://pack.${BASE_DOMAIN}` |
|
||||||
|
| Distribution | HTTP(s) | `https://distribution.${BASE_DOMAIN}` |
|
||||||
|
| Avatar (NMSR) | HTTP(s) | `https://avatar.${BASE_DOMAIN}` |
|
||||||
|
|
||||||
|
Internal-name targets (`minecraft`, `drasl:25585`, `nmsr:8080`) isolate "service
|
||||||
|
down" from "ingress/TLS/DNS down"; public-URL targets test the whole chain. Mix
|
||||||
|
as you like. `status` is in the default `LE_SUBDOMAINS` so its TLS cert issues
|
||||||
|
with the rest; the nginx vhost adds websocket upgrade headers for Kuma's live UI.
|
||||||
|
|
||||||
## Join (guests)
|
## Join (guests)
|
||||||
|
|
||||||
1. Open `http://ulicraft.lan`, download FjordLauncherUnlocked for your OS.
|
1. Open `http://ulicraft.lan`, download FjordLauncherUnlocked for your OS.
|
||||||
@@ -95,15 +204,21 @@ tooling/build-stack.sh --up core # base only (debugging)
|
|||||||
```
|
```
|
||||||
docker-compose.yml # core: drasl, minecraft, mc-backup, caddy
|
docker-compose.yml # core: drasl, minecraft, mc-backup, caddy
|
||||||
.static.yml .mirror.yml # online/airgap layers
|
.static.yml .mirror.yml # online/airgap layers
|
||||||
|
.caddy.yml .nginx.yml # ingress paths (caddy-front vs nginx static)
|
||||||
|
.nmsr.yml .distribution.yml # avatar renderer / extra static vhost
|
||||||
.avahi.yml .dnsmasq.yml # optional DNS layers
|
.avahi.yml .dnsmasq.yml # optional DNS layers
|
||||||
caddy/Caddyfile + conf.d/*.caddy # ingress vhost snippets (core/static/mirror)
|
caddy/Caddyfile + conf.d/*.caddy # ingress vhost snippets (core/static/mirror/
|
||||||
|
# distribution/avatar)
|
||||||
drasl/config/config.toml.tmpl # auth config (rendered)
|
drasl/config/config.toml.tmpl # auth config (rendered)
|
||||||
|
nmsr/config.toml.tmpl # avatar renderer config, Drasl-backed (rendered)
|
||||||
dnsmasq/dnsmasq.conf.tmpl # optional DNS config (rendered)
|
dnsmasq/dnsmasq.conf.tmpl # optional DNS config (rendered)
|
||||||
docker/avahi/ # mDNS responder image
|
docker/avahi/ docker/nmsr/ # built-from-source images (mDNS, NMSR)
|
||||||
pack/ # packwiz modpack source
|
pack/ # packwiz modpack source
|
||||||
landing/ # Astro site → www/
|
landing/ # Astro site → www/
|
||||||
tooling/ # build-stack, render-config, dns-records,
|
tooling/ # build-stack, render-config, render-nginx,
|
||||||
|
# dns-records, issue-letsencrypt,
|
||||||
# mirror-mods, mirror-airgap, fetch-launcher
|
# mirror-mods, mirror-airgap, fetch-launcher
|
||||||
|
nginx/*.conf.tmpl # host-nginx TLS vhost templates
|
||||||
mirror/ # vendored jars + launcher + asset mirror (gitignored)
|
mirror/ # vendored jars + launcher + asset mirror (gitignored)
|
||||||
plan/ # full design docs (00–12)
|
plan/ # full design docs (00–12)
|
||||||
```
|
```
|
||||||
|
|||||||
5
caddy/conf.d/40-avatar.caddy
Normal file
5
caddy/conf.d/40-avatar.caddy
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Avatar toggle — NMSR skin/avatar renderer (docker-compose.nmsr.yml).
|
||||||
|
# Mounted only when the nmsr override is present.
|
||||||
|
http://avatar.{$BASE_DOMAIN} {
|
||||||
|
reverse_proxy nmsr:8080
|
||||||
|
}
|
||||||
5
caddy/conf.d/50-status.caddy
Normal file
5
caddy/conf.d/50-status.caddy
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Status toggle — Uptime Kuma monitoring + public status page
|
||||||
|
# (docker-compose.status.yml). Mounted only when the status override is present.
|
||||||
|
http://status.{$BASE_DOMAIN} {
|
||||||
|
reverse_proxy uptime-kuma:3001
|
||||||
|
}
|
||||||
32
docker-compose.nmsr.yml
Normal file
32
docker-compose.nmsr.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# ──────────────────────────────────────────────────────────────────
|
||||||
|
# NMSR-aas — avatar/skin renderer at avatar.${BASE_DOMAIN}.
|
||||||
|
# ──────────────────────────────────────────────────────────────────
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.caddy.yml \
|
||||||
|
# -f docker-compose.nmsr.yml up -d --build
|
||||||
|
#
|
||||||
|
# Built from source (no upstream image) — see docker/nmsr/Dockerfile.
|
||||||
|
# Renders YOUR players' skins by pulling profiles from Drasl over the internal
|
||||||
|
# mcnet network (config: nmsr/config.toml, rendered from .tmpl).
|
||||||
|
#
|
||||||
|
# Caddy reverse-proxies avatar.${BASE_DOMAIN} -> nmsr:8080 internally. The host
|
||||||
|
# port is published on 127.0.0.1:9898 ONLY (local debugging / not reachable from
|
||||||
|
# outside) — public access goes through nginx -> caddy.
|
||||||
|
services:
|
||||||
|
# Mount the avatar vhost snippet into caddy (the caddy override owns the base
|
||||||
|
# config; this adds one more conf.d file like the distribution override does).
|
||||||
|
caddy:
|
||||||
|
volumes:
|
||||||
|
- ./caddy/conf.d/40-avatar.caddy:/etc/caddy/conf.d/40-avatar.caddy:ro
|
||||||
|
|
||||||
|
nmsr:
|
||||||
|
build:
|
||||||
|
context: ./docker/nmsr
|
||||||
|
image: ulicraft/nmsr:local
|
||||||
|
container_name: nmsr
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:9898:8080"
|
||||||
|
volumes:
|
||||||
|
- ./nmsr/config.toml:/nmsr/config.toml:ro
|
||||||
|
networks:
|
||||||
|
- mcnet
|
||||||
36
docker-compose.status.yml
Normal file
36
docker-compose.status.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# ──────────────────────────────────────────────────────────────────
|
||||||
|
# Uptime Kuma — status/monitoring for every vhost + the Minecraft server,
|
||||||
|
# reachable at status.${BASE_DOMAIN}.
|
||||||
|
# ──────────────────────────────────────────────────────────────────
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.caddy.yml \
|
||||||
|
# -f docker-compose.status.yml up -d
|
||||||
|
#
|
||||||
|
# Joins mcnet so monitors can probe the stack BY ITS INTERNAL SERVICE NAME
|
||||||
|
# (drasl:25585, nmsr:8080, minecraft:25565) without leaving the host — and can
|
||||||
|
# also hit the public https vhosts end-to-end through the host's DNS.
|
||||||
|
#
|
||||||
|
# Caddy reverse-proxies status.${BASE_DOMAIN} -> uptime-kuma:3001 internally
|
||||||
|
# (caddy/conf.d/50-status.caddy). The host port is published on 127.0.0.1:3001
|
||||||
|
# ONLY (local debugging / first-run admin setup) — public access goes through
|
||||||
|
# nginx -> caddy. Add status.${BASE_DOMAIN} to LE_SUBDOMAINS for its TLS cert.
|
||||||
|
#
|
||||||
|
# Monitors are stored in the kuma_data volume (Kuma has no config-as-code);
|
||||||
|
# add them once via the web UI — see README for the recommended monitor list.
|
||||||
|
services:
|
||||||
|
caddy:
|
||||||
|
volumes:
|
||||||
|
- ./caddy/conf.d/50-status.caddy:/etc/caddy/conf.d/50-status.caddy:ro
|
||||||
|
|
||||||
|
uptime-kuma:
|
||||||
|
image: louislam/uptime-kuma:1
|
||||||
|
container_name: uptime-kuma
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:3001:3001"
|
||||||
|
volumes:
|
||||||
|
- kuma_data:/app/data
|
||||||
|
networks:
|
||||||
|
- mcnet
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
kuma_data:
|
||||||
33
docker/nmsr/Dockerfile
Normal file
33
docker/nmsr/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# NMSR-as-a-Service (https://github.com/NickAcPT/nmsr-rs) — built from source.
|
||||||
|
# Upstream ships no published image, so we vendor a pinned build. Adapted from
|
||||||
|
# the upstream Dockerfile but: pinned to a commit (reproducible), and the config
|
||||||
|
# is bind-mounted at runtime (not COPYed) so it can change without a rebuild.
|
||||||
|
#
|
||||||
|
# Pin — bump deliberately:
|
||||||
|
ARG NMSR_REF=948ba4bc027b
|
||||||
|
|
||||||
|
FROM rust:slim-bookworm AS builder
|
||||||
|
ARG NMSR_REF
|
||||||
|
WORKDIR /tmp
|
||||||
|
RUN apt-get update -y \
|
||||||
|
&& apt-get --no-install-recommends install -y git libssl-dev pkg-config ca-certificates
|
||||||
|
RUN git clone https://github.com/NickAcPT/nmsr-rs/
|
||||||
|
WORKDIR /tmp/nmsr-rs
|
||||||
|
RUN git checkout "${NMSR_REF}"
|
||||||
|
# target-cpu=native: this image is built on the host that runs it (self-host).
|
||||||
|
RUN RUSTFLAGS="-Ctarget-cpu=native" \
|
||||||
|
cargo build --release --bin nmsr-aas --features ears --package nmsr-aas
|
||||||
|
|
||||||
|
FROM rust:slim-bookworm
|
||||||
|
# mesa-vulkan-drivers provides lavapipe (software Vulkan) for headless rendering.
|
||||||
|
RUN apt-get update -y \
|
||||||
|
&& apt-get --no-install-recommends install -y mesa-vulkan-drivers ca-certificates
|
||||||
|
WORKDIR /nmsr
|
||||||
|
COPY --from=builder /tmp/nmsr-rs/target/release/nmsr-aas /nmsr/nmsr-aas
|
||||||
|
ENV NMSR_USE_SMAA=1 \
|
||||||
|
NMSR_SAMPLE_COUNT=1 \
|
||||||
|
WGPU_BACKEND=vulkan \
|
||||||
|
RUST_BACKTRACE=1
|
||||||
|
EXPOSE 8080
|
||||||
|
# config.toml is bind-mounted by docker-compose.nmsr.yml.
|
||||||
|
CMD ["/nmsr/nmsr-aas", "-c", "/nmsr/config.toml"]
|
||||||
@@ -32,7 +32,7 @@ upstream ulicraft_caddy {
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
listen [::]:80;
|
listen [::]:80;
|
||||||
server_name ${BASE_DOMAIN} auth.${BASE_DOMAIN} pack.${BASE_DOMAIN} distribution.${BASE_DOMAIN};
|
server_name ${BASE_DOMAIN} www.${BASE_DOMAIN} auth.${BASE_DOMAIN} pack.${BASE_DOMAIN} distribution.${BASE_DOMAIN} avatar.${BASE_DOMAIN} status.${BASE_DOMAIN};
|
||||||
return 301 https://$host$request_uri;
|
return 301 https://$host$request_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,13 +40,13 @@ server {
|
|||||||
# caddy routes by the forwarded Host header (apex -> www, auth -> drasl,
|
# caddy routes by the forwarded Host header (apex -> www, auth -> drasl,
|
||||||
# pack -> packwiz files).
|
# pack -> packwiz files).
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl http2;
|
||||||
listen [::]:443 ssl;
|
listen [::]:443 ssl http2;
|
||||||
http2 on;
|
|
||||||
server_name ${BASE_DOMAIN};
|
server_name ${BASE_DOMAIN};
|
||||||
|
|
||||||
ssl_certificate ${APP_DIR}/certs/${BASE_DOMAIN}/cert.pem;
|
ssl_certificate ${APP_DIR}/certs/${BASE_DOMAIN}/cert.pem;
|
||||||
ssl_certificate_key ${APP_DIR}/certs/${BASE_DOMAIN}/key.pem;
|
ssl_certificate_key ${APP_DIR}/certs/${BASE_DOMAIN}/key.pem;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
client_max_body_size 4m; # skin uploads (auth) reuse the same proxy block
|
client_max_body_size 4m; # skin uploads (auth) reuse the same proxy block
|
||||||
|
|
||||||
@@ -59,14 +59,27 @@ server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# www -> apex canonical redirect (needs its own cert; covered by LE_SUBDOMAINS).
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl http2;
|
||||||
listen [::]:443 ssl;
|
listen [::]:443 ssl http2;
|
||||||
http2 on;
|
server_name www.${BASE_DOMAIN};
|
||||||
|
|
||||||
|
ssl_certificate ${APP_DIR}/certs/www.${BASE_DOMAIN}/cert.pem;
|
||||||
|
ssl_certificate_key ${APP_DIR}/certs/www.${BASE_DOMAIN}/key.pem;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
|
return 301 https://${BASE_DOMAIN}$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
server_name auth.${BASE_DOMAIN};
|
server_name auth.${BASE_DOMAIN};
|
||||||
|
|
||||||
ssl_certificate ${APP_DIR}/certs/auth.${BASE_DOMAIN}/cert.pem;
|
ssl_certificate ${APP_DIR}/certs/auth.${BASE_DOMAIN}/cert.pem;
|
||||||
ssl_certificate_key ${APP_DIR}/certs/auth.${BASE_DOMAIN}/key.pem;
|
ssl_certificate_key ${APP_DIR}/certs/auth.${BASE_DOMAIN}/key.pem;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
client_max_body_size 4m; # skin uploads
|
client_max_body_size 4m; # skin uploads
|
||||||
|
|
||||||
@@ -80,13 +93,13 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl http2;
|
||||||
listen [::]:443 ssl;
|
listen [::]:443 ssl http2;
|
||||||
http2 on;
|
|
||||||
server_name pack.${BASE_DOMAIN};
|
server_name pack.${BASE_DOMAIN};
|
||||||
|
|
||||||
ssl_certificate ${APP_DIR}/certs/pack.${BASE_DOMAIN}/cert.pem;
|
ssl_certificate ${APP_DIR}/certs/pack.${BASE_DOMAIN}/cert.pem;
|
||||||
ssl_certificate_key ${APP_DIR}/certs/pack.${BASE_DOMAIN}/key.pem;
|
ssl_certificate_key ${APP_DIR}/certs/pack.${BASE_DOMAIN}/key.pem;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://ulicraft_caddy;
|
proxy_pass http://ulicraft_caddy;
|
||||||
@@ -98,13 +111,13 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl http2;
|
||||||
listen [::]:443 ssl;
|
listen [::]:443 ssl http2;
|
||||||
http2 on;
|
|
||||||
server_name distribution.${BASE_DOMAIN};
|
server_name distribution.${BASE_DOMAIN};
|
||||||
|
|
||||||
ssl_certificate ${APP_DIR}/certs/distribution.${BASE_DOMAIN}/cert.pem;
|
ssl_certificate ${APP_DIR}/certs/distribution.${BASE_DOMAIN}/cert.pem;
|
||||||
ssl_certificate_key ${APP_DIR}/certs/distribution.${BASE_DOMAIN}/key.pem;
|
ssl_certificate_key ${APP_DIR}/certs/distribution.${BASE_DOMAIN}/key.pem;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://ulicraft_caddy;
|
proxy_pass http://ulicraft_caddy;
|
||||||
@@ -114,3 +127,44 @@ server {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
server_name avatar.${BASE_DOMAIN};
|
||||||
|
|
||||||
|
ssl_certificate ${APP_DIR}/certs/avatar.${BASE_DOMAIN}/cert.pem;
|
||||||
|
ssl_certificate_key ${APP_DIR}/certs/avatar.${BASE_DOMAIN}/key.pem;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://ulicraft_caddy;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
server_name status.${BASE_DOMAIN};
|
||||||
|
|
||||||
|
ssl_certificate ${APP_DIR}/certs/status.${BASE_DOMAIN}/cert.pem;
|
||||||
|
ssl_certificate_key ${APP_DIR}/certs/status.${BASE_DOMAIN}/key.pem;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://ulicraft_caddy;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Uptime Kuma uses websockets (socket.io) for live status updates.
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ server {
|
|||||||
# /ca.crt from the caddy path is intentionally omitted: LE certs are publicly
|
# /ca.crt from the caddy path is intentionally omitted: LE certs are publicly
|
||||||
# trusted, so guests need no CA import.
|
# trusted, so guests need no CA import.
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl http2;
|
||||||
listen [::]:443 ssl;
|
listen [::]:443 ssl http2;
|
||||||
http2 on;
|
|
||||||
server_name ${BASE_DOMAIN};
|
server_name ${BASE_DOMAIN};
|
||||||
|
|
||||||
ssl_certificate ${APP_DIR}/certs/${BASE_DOMAIN}/cert.pem;
|
ssl_certificate ${APP_DIR}/certs/${BASE_DOMAIN}/cert.pem;
|
||||||
ssl_certificate_key ${APP_DIR}/certs/${BASE_DOMAIN}/key.pem;
|
ssl_certificate_key ${APP_DIR}/certs/${BASE_DOMAIN}/key.pem;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
# Launcher downloads (populated by tooling/fetch-launcher.sh -> ./mirror/launcher).
|
# Launcher downloads (populated by tooling/fetch-launcher.sh -> ./mirror/launcher).
|
||||||
location /launcher/ {
|
location /launcher/ {
|
||||||
@@ -51,13 +51,13 @@ server {
|
|||||||
|
|
||||||
# Pack — packwiz metadata (./pack) + custom jars (./custom at /custom/).
|
# Pack — packwiz metadata (./pack) + custom jars (./custom at /custom/).
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl http2;
|
||||||
listen [::]:443 ssl;
|
listen [::]:443 ssl http2;
|
||||||
http2 on;
|
|
||||||
server_name pack.${BASE_DOMAIN};
|
server_name pack.${BASE_DOMAIN};
|
||||||
|
|
||||||
ssl_certificate ${APP_DIR}/certs/pack.${BASE_DOMAIN}/cert.pem;
|
ssl_certificate ${APP_DIR}/certs/pack.${BASE_DOMAIN}/cert.pem;
|
||||||
ssl_certificate_key ${APP_DIR}/certs/pack.${BASE_DOMAIN}/key.pem;
|
ssl_certificate_key ${APP_DIR}/certs/pack.${BASE_DOMAIN}/key.pem;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
# Locally-hosted custom mod jars live outside ./pack; expose them at /custom/.
|
# Locally-hosted custom mod jars live outside ./pack; expose them at /custom/.
|
||||||
location /custom/ {
|
location /custom/ {
|
||||||
@@ -73,13 +73,13 @@ server {
|
|||||||
|
|
||||||
# Auth — reverse proxy to drasl (published on localhost by the nginx override).
|
# Auth — reverse proxy to drasl (published on localhost by the nginx override).
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl http2;
|
||||||
listen [::]:443 ssl;
|
listen [::]:443 ssl http2;
|
||||||
http2 on;
|
|
||||||
server_name auth.${BASE_DOMAIN};
|
server_name auth.${BASE_DOMAIN};
|
||||||
|
|
||||||
ssl_certificate ${APP_DIR}/certs/auth.${BASE_DOMAIN}/cert.pem;
|
ssl_certificate ${APP_DIR}/certs/auth.${BASE_DOMAIN}/cert.pem;
|
||||||
ssl_certificate_key ${APP_DIR}/certs/auth.${BASE_DOMAIN}/key.pem;
|
ssl_certificate_key ${APP_DIR}/certs/auth.${BASE_DOMAIN}/key.pem;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
client_max_body_size 4m; # skin uploads
|
client_max_body_size 4m; # skin uploads
|
||||||
|
|
||||||
|
|||||||
37
nmsr/config.toml.tmpl
Normal file
37
nmsr/config.toml.tmpl
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# NMSR-aas config — renders to nmsr/config.toml (gitignored) via
|
||||||
|
# tooling/render-config.sh. Only ${BASE_DOMAIN} is substituted.
|
||||||
|
#
|
||||||
|
# Skins come from Drasl, NOT Mojang. Drasl exposes Mojang-compatible routes at
|
||||||
|
# the bare BaseURL:
|
||||||
|
# session_server -> {url}/session/minecraft/profile/{uuid}
|
||||||
|
# mojang_api_server -> {url}/users/profiles/minecraft/{name}
|
||||||
|
# and embeds absolute skin URLs (http://auth.${BASE_DOMAIN}/web/texture/skin/…)
|
||||||
|
# in the profile, which NMSR fetches directly (textures_server is only a
|
||||||
|
# fallback). NMSR resolves auth.${BASE_DOMAIN} internally over the docker
|
||||||
|
# network (caddy holds the mcnet alias → drasl), so no public round-trip.
|
||||||
|
|
||||||
|
[server]
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 8080
|
||||||
|
|
||||||
|
[caching]
|
||||||
|
cleanup_interval = "1h"
|
||||||
|
resolve_cache_duration = "15m"
|
||||||
|
texture_cache_duration = "48h"
|
||||||
|
|
||||||
|
[caching.cache_biases]
|
||||||
|
|
||||||
|
[mojank]
|
||||||
|
# Point every Mojang endpoint at Drasl (internal, plain http via the caddy alias).
|
||||||
|
session_server = "http://auth.${BASE_DOMAIN}"
|
||||||
|
textures_server = "http://auth.${BASE_DOMAIN}"
|
||||||
|
mojang_api_server = "http://auth.${BASE_DOMAIN}"
|
||||||
|
session_server_rate_limit = 10
|
||||||
|
# This is an offline-mode (authlib-injector) server — Drasl issues v3 offline
|
||||||
|
# UUIDs, so they must be accepted.
|
||||||
|
allow_offline_mode_uuids = true
|
||||||
|
use_dashless_uuids = false
|
||||||
|
|
||||||
|
[rendering]
|
||||||
|
sample_count = 1
|
||||||
|
use_smaa = true
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
# OVH_CK OVH consumer key (obtained on first run — see below)
|
# OVH_CK OVH consumer key (obtained on first run — see below)
|
||||||
# OVH_END_POINT OVH API endpoint (e.g. ovh-eu)
|
# OVH_END_POINT OVH API endpoint (e.g. ovh-eu)
|
||||||
# optional:
|
# optional:
|
||||||
# LE_SUBDOMAINS space-separated (default: "auth pack distribution")
|
# LE_SUBDOMAINS space-separated (default: "auth pack distribution www avatar status")
|
||||||
# LE_STAGING=1 use the LE staging CA (untrusted, for dry runs)
|
# LE_STAGING=1 use the LE staging CA (untrusted, for dry runs)
|
||||||
#
|
#
|
||||||
# First-time OVH consumer key: set OVH_AK/OVH_AS/OVH_END_POINT, leave OVH_CK
|
# First-time OVH consumer key: set OVH_AK/OVH_AS/OVH_END_POINT, leave OVH_CK
|
||||||
@@ -53,7 +53,7 @@ ACME="${ACME_SH:-$HOME/.acme.sh/acme.sh}"
|
|||||||
then re-run (or set ACME_SH=/path/to/acme.sh)."
|
then re-run (or set ACME_SH=/path/to/acme.sh)."
|
||||||
|
|
||||||
CERT_DIR="${CERT_DIR:-certs}"
|
CERT_DIR="${CERT_DIR:-certs}"
|
||||||
read -r -a SUBDOMAINS <<< "${LE_SUBDOMAINS:-auth pack distribution}"
|
read -r -a SUBDOMAINS <<< "${LE_SUBDOMAINS:-auth pack distribution www avatar status}"
|
||||||
|
|
||||||
if [ "${LE_STAGING:-0}" = "1" ]; then
|
if [ "${LE_STAGING:-0}" = "1" ]; then
|
||||||
SERVER="letsencrypt_test"; warn "STAGING mode — certs will NOT be trusted by browsers"
|
SERVER="letsencrypt_test"; warn "STAGING mode — certs will NOT be trusted by browsers"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ render() { # $1=template $2=output
|
|||||||
}
|
}
|
||||||
|
|
||||||
render drasl/config/config.toml.tmpl drasl/config/config.toml
|
render drasl/config/config.toml.tmpl drasl/config/config.toml
|
||||||
|
render nmsr/config.toml.tmpl nmsr/config.toml
|
||||||
render dnsmasq/dnsmasq.conf.tmpl dnsmasq/dnsmasq.conf
|
render dnsmasq/dnsmasq.conf.tmpl dnsmasq/dnsmasq.conf
|
||||||
|
|
||||||
# pack templates (mods/*.pw.toml.tmpl, CustomSkinLoader, …) + packwiz index.
|
# pack templates (mods/*.pw.toml.tmpl, CustomSkinLoader, …) + packwiz index.
|
||||||
|
|||||||
61
tooling/render-nginx.sh
Executable file
61
tooling/render-nginx.sh
Executable file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# render-nginx.sh — render an nginx vhost template with the .env values.
|
||||||
|
#
|
||||||
|
# Substitutes $BASE_DOMAIN, $APP_DIR, $CADDY_HTTP_PORT into a template and
|
||||||
|
# prints the result to stdout, or installs it with --install (writes to
|
||||||
|
# sites-available, symlinks sites-enabled, tests, reloads nginx).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# tooling/render-nginx.sh # render caddy-front template -> stdout
|
||||||
|
# tooling/render-nginx.sh --install # render + install + reload nginx
|
||||||
|
# TEMPLATE=nginx/ulicraft.conf.tmpl tooling/render-nginx.sh # the static variant
|
||||||
|
#
|
||||||
|
# .env vars used:
|
||||||
|
# BASE_DOMAIN required
|
||||||
|
# CADDY_HTTP_PORT caddy-front template only (localhost port nginx proxies to)
|
||||||
|
# optional:
|
||||||
|
# APP_DIR host path to this repo (cert files live under APP_DIR/certs)
|
||||||
|
# — defaults to the repo root (this checkout)
|
||||||
|
# TEMPLATE template to render (default nginx/ulicraft-caddy.conf.tmpl)
|
||||||
|
# SITE_NAME installed filename (default ulicraft.conf)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.." # repo root
|
||||||
|
|
||||||
|
log() { printf '\n\033[1;32m==>\033[0m %s\n' "$*"; }
|
||||||
|
warn() { printf '\033[1;33m!!\033[0m %s\n' "$*" >&2; }
|
||||||
|
die() { printf '\033[1;31mxx\033[0m %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
[ -f .env ] || die ".env not found (copy .env.example and fill it)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
set -a; . ./.env; set +a
|
||||||
|
: "${BASE_DOMAIN:?BASE_DOMAIN unset in .env}"
|
||||||
|
|
||||||
|
TEMPLATE="${TEMPLATE:-nginx/ulicraft-caddy.conf.tmpl}"
|
||||||
|
[ -f "$TEMPLATE" ] || die "template not found: $TEMPLATE"
|
||||||
|
APP_DIR="${APP_DIR:-$PWD}"
|
||||||
|
SITE_NAME="${SITE_NAME:-ulicraft.conf}"
|
||||||
|
|
||||||
|
# CADDY_HTTP_PORT only matters for the caddy-front template; default 80 so the
|
||||||
|
# static template (which never references it) still renders cleanly.
|
||||||
|
export BASE_DOMAIN APP_DIR
|
||||||
|
export CADDY_HTTP_PORT="${CADDY_HTTP_PORT:-80}"
|
||||||
|
|
||||||
|
rendered="$(envsubst '$BASE_DOMAIN $APP_DIR $CADDY_HTTP_PORT' < "$TEMPLATE")"
|
||||||
|
|
||||||
|
if [ "${1:-}" != "--install" ]; then
|
||||||
|
printf '%s\n' "$rendered"
|
||||||
|
warn "dry run — pipe to a file or pass --install to deploy"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
command -v nginx >/dev/null 2>&1 || die "nginx not found on PATH"
|
||||||
|
avail="/etc/nginx/sites-available/$SITE_NAME"
|
||||||
|
enabled="/etc/nginx/sites-enabled/$SITE_NAME"
|
||||||
|
|
||||||
|
log "installing $TEMPLATE -> $avail (APP_DIR=$APP_DIR, CADDY_HTTP_PORT=$CADDY_HTTP_PORT)"
|
||||||
|
printf '%s\n' "$rendered" | sudo tee "$avail" >/dev/null
|
||||||
|
sudo ln -sf "$avail" "$enabled"
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
log "done — nginx reloaded"
|
||||||
Reference in New Issue
Block a user