Compare commits
10 Commits
c279f35e14
...
6e91749f59
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e91749f59 | |||
| 853405afe7 | |||
| f7858d4ec4 | |||
| 39be74d028 | |||
| c40cbe7594 | |||
| 58f0312018 | |||
| 8c3d85f691 | |||
| 2405eee57d | |||
| e1ccdda961 | |||
| 5172316919 |
10
.env.example
10
.env.example
@@ -17,3 +17,13 @@ ENABLE_MDNS=false
|
|||||||
RCON_PASSWORD=change-me-to-something-random
|
RCON_PASSWORD=change-me-to-something-random
|
||||||
# Only needed if using CurseForge-exclusive mods:
|
# Only needed if using CurseForge-exclusive mods:
|
||||||
# CF_API_KEY=your-curseforge-api-key
|
# CF_API_KEY=your-curseforge-api-key
|
||||||
|
|
||||||
|
# ── Blessing Skin auth variant (docker-compose.blessingskin.yml) ──────
|
||||||
|
# Only needed when running the Blessing Skin override instead of Drasl.
|
||||||
|
BS_DB_NAME=blessingskin
|
||||||
|
BS_DB_USER=blessingskin
|
||||||
|
BS_DB_PASSWORD=change-me-bs-db
|
||||||
|
BS_DB_ROOT_PASSWORD=change-me-bs-root
|
||||||
|
# Generate once and paste here so it survives container recreates:
|
||||||
|
# docker run --rm azusamikan/blessing-skin-server-docker:latest php artisan key:generate --show
|
||||||
|
BS_APP_KEY=base64:replace-with-generated-key
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,6 +6,11 @@ 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
|
||||||
dnsmasq/dnsmasq.conf
|
dnsmasq/dnsmasq.conf
|
||||||
|
# packwiz pack files rendered from pack/**/*.tmpl by tooling/render-pack.sh
|
||||||
|
# (domain-dependent build output); source of truth is the .tmpl + custom/ jars.
|
||||||
|
pack/mods/*.pw.toml
|
||||||
|
pack/index.toml
|
||||||
|
pack/CustomSkinLoader/CustomSkinLoader.json
|
||||||
|
|
||||||
# vendored / mirrored binaries
|
# vendored / mirrored binaries
|
||||||
mirror/
|
mirror/
|
||||||
|
|||||||
14
caddy/conf.d/00-core-blessingskin.caddy
Normal file
14
caddy/conf.d/00-core-blessingskin.caddy
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Core ingress — Blessing Skin variant. Replaces 00-core.caddy when the
|
||||||
|
# docker-compose.blessingskin.yml override is in play (mounted at the same
|
||||||
|
# target path). auth.* now points at Blessing Skin instead of Drasl.
|
||||||
|
#
|
||||||
|
# Blessing Skin serves BOTH its web UI (/) and the Yggdrasil API (/api/yggdrasil)
|
||||||
|
# on the same vhost, so a single reverse_proxy covers everything.
|
||||||
|
http://auth.{$BASE_DOMAIN} {
|
||||||
|
reverse_proxy blessing-skin:80
|
||||||
|
}
|
||||||
|
|
||||||
|
http://pack.{$BASE_DOMAIN} {
|
||||||
|
root * /srv/pack
|
||||||
|
file_server browse
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ http://auth.{$BASE_DOMAIN} {
|
|||||||
reverse_proxy drasl:25585
|
reverse_proxy drasl:25585
|
||||||
}
|
}
|
||||||
|
|
||||||
http://packwiz.{$BASE_DOMAIN} {
|
http://pack.{$BASE_DOMAIN} {
|
||||||
root * /srv/pack
|
root * /srv/pack
|
||||||
file_server browse
|
file_server browse
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
custom/CustomSkinLoader_Universal-14.28.jar
Normal file
BIN
custom/CustomSkinLoader_Universal-14.28.jar
Normal file
Binary file not shown.
BIN
custom/Jade-1.21.1-NeoForge-15.10.5.jar
Normal file
BIN
custom/Jade-1.21.1-NeoForge-15.10.5.jar
Normal file
Binary file not shown.
BIN
custom/JadeAddons-1.21.1-NeoForge-6.1.0.jar
Normal file
BIN
custom/JadeAddons-1.21.1-NeoForge-6.1.0.jar
Normal file
Binary file not shown.
BIN
custom/JustEnoughProfessions-neoforge-1.21.1-4.0.5.jar
Normal file
BIN
custom/JustEnoughProfessions-neoforge-1.21.1-4.0.5.jar
Normal file
Binary file not shown.
BIN
custom/JustEnoughResources-NeoForge-1.21.1-1.6.0.17.jar
Normal file
BIN
custom/JustEnoughResources-NeoForge-1.21.1-1.6.0.17.jar
Normal file
Binary file not shown.
BIN
custom/MouseTweaks-neoforge-mc1.21-2.26.1.jar
Normal file
BIN
custom/MouseTweaks-neoforge-mc1.21-2.26.1.jar
Normal file
Binary file not shown.
BIN
custom/balm-neoforge-1.21.1-21.0.58.jar
Normal file
BIN
custom/balm-neoforge-1.21.1-21.0.58.jar
Normal file
Binary file not shown.
BIN
custom/inventoryessentials-neoforge-1.21.1-21.1.15.jar
Normal file
BIN
custom/inventoryessentials-neoforge-1.21.1-21.1.15.jar
Normal file
Binary file not shown.
BIN
custom/jei-1.21.1-neoforge-19.27.0.340.jar
Normal file
BIN
custom/jei-1.21.1-neoforge-19.27.0.340.jar
Normal file
Binary file not shown.
103
docker-compose.blessingskin.yml
Normal file
103
docker-compose.blessingskin.yml
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# ──────────────────────────────────────────────────────────────────
|
||||||
|
# Blessing Skin auth variant — use INSTEAD of Drasl.
|
||||||
|
# ──────────────────────────────────────────────────────────────────
|
||||||
|
# Layer this over the base file:
|
||||||
|
#
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.blessingskin.yml up -d
|
||||||
|
#
|
||||||
|
# What it does vs the base stack:
|
||||||
|
# - drasl → left running but UNUSED + unreachable (Caddy no longer
|
||||||
|
# routes to it; it has no host port). Compose merges
|
||||||
|
# depends_on, so it can't be removed from an override —
|
||||||
|
# idling it is the clean option. `docker compose ... stop
|
||||||
|
# drasl` after up if you want it down.
|
||||||
|
# - caddy → auth.* now reverse-proxies blessing-skin:80
|
||||||
|
# - mariadb → new; Blessing Skin's datastore (no SQLite support)
|
||||||
|
# - blessing-skin → new; PHP skin server + yggdrasil-api plugin
|
||||||
|
# - minecraft → authlib-injector now points at /api/yggdrasil,
|
||||||
|
# ONLINE_MODE=TRUE (real session validation against BSS)
|
||||||
|
#
|
||||||
|
# First-run is a WEB WIZARD — see plan/03b-blessing-skin.md. The yggdrasil-api
|
||||||
|
# plugin must be installed + enabled from the BSS admin panel before the
|
||||||
|
# /api/yggdrasil endpoint (and thus Minecraft login) works.
|
||||||
|
#
|
||||||
|
# DB creds + APP_KEY come from .env (see .env.example: BS_* vars).
|
||||||
|
# ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
services:
|
||||||
|
caddy:
|
||||||
|
# Swap the core ingress conf for the Blessing Skin one. Compose MERGES
|
||||||
|
# volumes by container path, so mounting the BSS conf at the SAME target
|
||||||
|
# (/etc/caddy/conf.d/00-core.caddy) shadows the base 00-core.caddy; all
|
||||||
|
# other base mounts (pack, custom, static, ca) are kept automatically.
|
||||||
|
volumes:
|
||||||
|
- ./caddy/conf.d/00-core-blessingskin.caddy:/etc/caddy/conf.d/00-core.caddy:ro
|
||||||
|
depends_on:
|
||||||
|
- blessing-skin
|
||||||
|
|
||||||
|
mariadb:
|
||||||
|
image: mariadb:11
|
||||||
|
container_name: mariadb
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MARIADB_DATABASE: ${BS_DB_NAME}
|
||||||
|
MARIADB_USER: ${BS_DB_USER}
|
||||||
|
MARIADB_PASSWORD: ${BS_DB_PASSWORD}
|
||||||
|
MARIADB_ROOT_PASSWORD: ${BS_DB_ROOT_PASSWORD}
|
||||||
|
TZ: "Europe/Madrid"
|
||||||
|
volumes:
|
||||||
|
- bs_db:/var/lib/mysql
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
networks:
|
||||||
|
- mcnet
|
||||||
|
|
||||||
|
blessing-skin:
|
||||||
|
image: azusamikan/blessing-skin-server-docker:latest
|
||||||
|
container_name: blessing-skin
|
||||||
|
restart: unless-stopped
|
||||||
|
# Internal-only — reached via Caddy at auth.${BASE_DOMAIN}.
|
||||||
|
environment:
|
||||||
|
APP_URL: "http://auth.${BASE_DOMAIN}"
|
||||||
|
# Persist APP_KEY across recreates (else sessions/encryption break).
|
||||||
|
# Generate once: docker run --rm azusamikan/blessing-skin-server-docker:latest php artisan key:generate --show
|
||||||
|
APP_KEY: ${BS_APP_KEY}
|
||||||
|
DB_DRIVER: "mysql"
|
||||||
|
DB_HOST: "mariadb"
|
||||||
|
DB_PORT: "3306"
|
||||||
|
DB_DATABASE: ${BS_DB_NAME}
|
||||||
|
DB_USERNAME: ${BS_DB_USER}
|
||||||
|
DB_PASSWORD: ${BS_DB_PASSWORD}
|
||||||
|
TZ: "Europe/Madrid"
|
||||||
|
volumes:
|
||||||
|
- bs_storage:/app/storage # uploaded skins/capes + textures
|
||||||
|
- bs_plugins:/app/plugins # yggdrasil-api plugin lives here
|
||||||
|
depends_on:
|
||||||
|
mariadb:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- mcnet
|
||||||
|
|
||||||
|
minecraft:
|
||||||
|
environment:
|
||||||
|
# authlib-injector API root for Blessing Skin's yggdrasil-api plugin
|
||||||
|
# is <site>/api/yggdrasil (NOT /authlib-injector like Drasl).
|
||||||
|
JVM_OPTS: "-javaagent:/extras/authlib-injector.jar=http://auth.${BASE_DOMAIN}/api/yggdrasil"
|
||||||
|
# authlib-injector needs real auth: online-mode TRUE so the server
|
||||||
|
# validates sessions against Blessing Skin. (Drasl variant used FALSE.)
|
||||||
|
ONLINE_MODE: "TRUE"
|
||||||
|
# Keep FALSE for safety on 1.21+. The yggdrasil-api plugin DOES sign
|
||||||
|
# profile keys, so you MAY flip this to TRUE if signed chat is wanted.
|
||||||
|
ENFORCE_SECURE_PROFILE: "FALSE"
|
||||||
|
# Merged with the base depends_on (drasl, caddy); just adds blessing-skin so
|
||||||
|
# Minecraft starts after the skin server is up.
|
||||||
|
depends_on:
|
||||||
|
- blessing-skin
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
bs_db:
|
||||||
|
bs_storage:
|
||||||
|
bs_plugins:
|
||||||
@@ -26,7 +26,15 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
- ./caddy/conf.d/00-core.caddy:/etc/caddy/conf.d/00-core.caddy:ro
|
- ./caddy/conf.d/00-core.caddy:/etc/caddy/conf.d/00-core.caddy:ro
|
||||||
|
- ./caddy/conf.d/10-static.caddy:/etc/caddy/conf.d/10-static.caddy:ro
|
||||||
|
- ./www:/srv/www:ro
|
||||||
|
- ./mirror/launcher:/srv/launcher:ro
|
||||||
|
- ./caddy/caddy-root-ca.crt:/srv/ca-pub/ca.crt:ro
|
||||||
- ./pack:/srv/pack:ro
|
- ./pack:/srv/pack:ro
|
||||||
|
# Custom (locally-hosted) mod jars live OUTSIDE ./pack so packwiz refresh
|
||||||
|
# doesn't index them as direct files — they're external download targets
|
||||||
|
# referenced by pack/mods/*.pw.toml. Served at pack.${BASE_DOMAIN}/custom/.
|
||||||
|
- ./custom:/srv/pack/custom:ro
|
||||||
networks:
|
networks:
|
||||||
mcnet:
|
mcnet:
|
||||||
# Only the stack's own subdomains in the base. The upstream spoof-host
|
# Only the stack's own subdomains in the base. The upstream spoof-host
|
||||||
@@ -35,8 +43,7 @@ services:
|
|||||||
# containers, breaking the ONLINE NeoForge install during pre-bake.
|
# containers, breaking the ONLINE NeoForge install during pre-bake.
|
||||||
aliases:
|
aliases:
|
||||||
- "auth.${BASE_DOMAIN}"
|
- "auth.${BASE_DOMAIN}"
|
||||||
- "packwiz.${BASE_DOMAIN}"
|
- "pack.${BASE_DOMAIN}"
|
||||||
|
|
||||||
drasl:
|
drasl:
|
||||||
image: unmojang/drasl:latest
|
image: unmojang/drasl:latest
|
||||||
container_name: drasl
|
container_name: drasl
|
||||||
@@ -81,7 +88,7 @@ services:
|
|||||||
# ── Server config ───────────────────────────────────────────
|
# ── Server config ───────────────────────────────────────────
|
||||||
DIFFICULTY: "normal"
|
DIFFICULTY: "normal"
|
||||||
MODE: "survival"
|
MODE: "survival"
|
||||||
MOTD: "LAN party — kitchen sink"
|
MOTD: "Uli LAN party"
|
||||||
MAX_PLAYERS: "10"
|
MAX_PLAYERS: "10"
|
||||||
VIEW_DISTANCE: "10"
|
VIEW_DISTANCE: "10"
|
||||||
SIMULATION_DISTANCE: "8"
|
SIMULATION_DISTANCE: "8"
|
||||||
@@ -89,7 +96,7 @@ services:
|
|||||||
ALLOW_FLIGHT: "TRUE" # many tech mods need this
|
ALLOW_FLIGHT: "TRUE" # many tech mods need this
|
||||||
|
|
||||||
# ── Mod source: packwiz pack served by caddy ────────────────
|
# ── Mod source: packwiz pack served by caddy ────────────────
|
||||||
PACKWIZ_URL: "http://packwiz.${BASE_DOMAIN}/pack.toml"
|
PACKWIZ_URL: "http://pack.${BASE_DOMAIN}/pack.toml"
|
||||||
|
|
||||||
# ── RCON for remote admin ───────────────────────────────────
|
# ── RCON for remote admin ───────────────────────────────────
|
||||||
ENABLE_RCON: "TRUE"
|
ENABLE_RCON: "TRUE"
|
||||||
|
|||||||
3
pack/.packwizignore
Normal file
3
pack/.packwizignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Template sources for pack metadata/config — rendered to their real files by
|
||||||
|
# tooling/render-pack.sh. packwiz must NOT index the templates themselves.
|
||||||
|
*.tmpl
|
||||||
11
pack/CustomSkinLoader/CustomSkinLoader.json.tmpl
Normal file
11
pack/CustomSkinLoader/CustomSkinLoader.json.tmpl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"enable": true,
|
||||||
|
"loadlist": [
|
||||||
|
{
|
||||||
|
"name": "Ulicraft Skins",
|
||||||
|
"type": "MojangAPI",
|
||||||
|
"apiRoot": "https://auth.${BASE_DOMAIN}/account/",
|
||||||
|
"sessionRoot": "https://auth.${BASE_DOMAIN}/session/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
hash-format = "sha256"
|
|
||||||
files = []
|
|
||||||
8
pack/mods/balm-neoforge-1-21-1-21-0-58.pw.toml.tmpl
Normal file
8
pack/mods/balm-neoforge-1-21-1-21-0-58.pw.toml.tmpl
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
name = "balm-neoforge-1.21.1-21.0.58"
|
||||||
|
filename = "balm-neoforge-1.21.1-21.0.58.jar"
|
||||||
|
side = "both"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
url = "http://pack.${BASE_DOMAIN}/custom/balm-neoforge-1.21.1-21.0.58.jar"
|
||||||
|
hash-format = "sha256"
|
||||||
|
hash = "aa2463747bad4c2351869226ea7bce7839510dfd2ac387f8d1588b1ce9309c67"
|
||||||
8
pack/mods/customskinloader-universal-14-28.pw.toml.tmpl
Normal file
8
pack/mods/customskinloader-universal-14-28.pw.toml.tmpl
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
name = "CustomSkinLoader_Universal-14.28"
|
||||||
|
filename = "CustomSkinLoader_Universal-14.28.jar"
|
||||||
|
side = "both"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
url = "http://pack.${BASE_DOMAIN}/custom/CustomSkinLoader_Universal-14.28.jar"
|
||||||
|
hash-format = "sha256"
|
||||||
|
hash = "0008fb0a91dd1e84d52162cfe96addb5f6aed3b76d426ea7305d7624e31f1894"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
name = "inventoryessentials-neoforge-1.21.1-21.1.15"
|
||||||
|
filename = "inventoryessentials-neoforge-1.21.1-21.1.15.jar"
|
||||||
|
side = "both"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
url = "http://pack.${BASE_DOMAIN}/custom/inventoryessentials-neoforge-1.21.1-21.1.15.jar"
|
||||||
|
hash-format = "sha256"
|
||||||
|
hash = "b7a5c5526a45256551543419ba2b5d54e56c585d76a645a8cea8cb8ea843ae2c"
|
||||||
8
pack/mods/jade-1-21-1-neoforge-15-10-5.pw.toml.tmpl
Normal file
8
pack/mods/jade-1-21-1-neoforge-15-10-5.pw.toml.tmpl
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
name = "Jade-1.21.1-NeoForge-15.10.5"
|
||||||
|
filename = "Jade-1.21.1-NeoForge-15.10.5.jar"
|
||||||
|
side = "both"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
url = "http://pack.${BASE_DOMAIN}/custom/Jade-1.21.1-NeoForge-15.10.5.jar"
|
||||||
|
hash-format = "sha256"
|
||||||
|
hash = "067bb4b007e1d6f6b79f0afe99c91252aa825472b99a76d33a60d24442f9e92d"
|
||||||
8
pack/mods/jadeaddons-1-21-1-neoforge-6-1-0.pw.toml.tmpl
Normal file
8
pack/mods/jadeaddons-1-21-1-neoforge-6-1-0.pw.toml.tmpl
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
name = "JadeAddons-1.21.1-NeoForge-6.1.0"
|
||||||
|
filename = "JadeAddons-1.21.1-NeoForge-6.1.0.jar"
|
||||||
|
side = "both"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
url = "http://pack.${BASE_DOMAIN}/custom/JadeAddons-1.21.1-NeoForge-6.1.0.jar"
|
||||||
|
hash-format = "sha256"
|
||||||
|
hash = "5f242668ad710092964d4d9afda8a94fa2401665ea13eea67d55d07e4dff8a64"
|
||||||
8
pack/mods/jei-1-21-1-neoforge-19-27-0-340.pw.toml.tmpl
Normal file
8
pack/mods/jei-1-21-1-neoforge-19-27-0-340.pw.toml.tmpl
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
name = "jei-1.21.1-neoforge-19.27.0.340"
|
||||||
|
filename = "jei-1.21.1-neoforge-19.27.0.340.jar"
|
||||||
|
side = "both"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
url = "http://pack.${BASE_DOMAIN}/custom/jei-1.21.1-neoforge-19.27.0.340.jar"
|
||||||
|
hash-format = "sha256"
|
||||||
|
hash = "8aaf547432f1b4958239b036356b910692fe40f858c3073d996f56bbf7c99826"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
name = "JustEnoughProfessions-neoforge-1.21.1-4.0.5"
|
||||||
|
filename = "JustEnoughProfessions-neoforge-1.21.1-4.0.5.jar"
|
||||||
|
side = "both"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
url = "http://pack.${BASE_DOMAIN}/custom/JustEnoughProfessions-neoforge-1.21.1-4.0.5.jar"
|
||||||
|
hash-format = "sha256"
|
||||||
|
hash = "b320b47f1791f56df9e1a05138f0e312e6e54541945ff15f2f1fd398cd1d4d9d"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
name = "JustEnoughResources-NeoForge-1.21.1-1.6.0.17"
|
||||||
|
filename = "JustEnoughResources-NeoForge-1.21.1-1.6.0.17.jar"
|
||||||
|
side = "both"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
url = "http://pack.${BASE_DOMAIN}/custom/JustEnoughResources-NeoForge-1.21.1-1.6.0.17.jar"
|
||||||
|
hash-format = "sha256"
|
||||||
|
hash = "7a47d69b5530704690d5a5f0726cd74a2d6c57df17dd4a8589c4ab5212f91460"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
name = "MouseTweaks-neoforge-mc1.21-2.26.1"
|
||||||
|
filename = "MouseTweaks-neoforge-mc1.21-2.26.1.jar"
|
||||||
|
side = "both"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
url = "http://pack.${BASE_DOMAIN}/custom/MouseTweaks-neoforge-mc1.21-2.26.1.jar"
|
||||||
|
hash-format = "sha256"
|
||||||
|
hash = "68e6f4201c5de97b77929a7215c9552495696ca6a3bf3ae4eacc34e135f6cc8b"
|
||||||
@@ -6,7 +6,7 @@ pack-format = "packwiz:1.1.0"
|
|||||||
[index]
|
[index]
|
||||||
file = "index.toml"
|
file = "index.toml"
|
||||||
hash-format = "sha256"
|
hash-format = "sha256"
|
||||||
hash = "7b81c9e9d9e6e21a9865a2dded0dfd77e7683d679b98fe5b0114eaed9a31f17e"
|
hash = "9bca0b1a4434050835cab46116d7c14aafec971ff5a95a1ee0c4232c8bcbc355"
|
||||||
|
|
||||||
[versions]
|
[versions]
|
||||||
minecraft = "1.21.1"
|
minecraft = "1.21.1"
|
||||||
|
|||||||
80
plan/03b-blessing-skin.md
Normal file
80
plan/03b-blessing-skin.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Blessing Skin — auth + skin server (Drasl alternative)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Drop-in alternative to Drasl (`plan/03-drasl.md`). Blessing Skin Server (BSS) is
|
||||||
|
a PHP skin station; the **yggdrasil-api plugin** gives it a Yggdrasil API that
|
||||||
|
authlib-injector talks to — same end result as Drasl, different internals.
|
||||||
|
|
||||||
|
Run it as an override **instead of** Drasl:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.blessingskin.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
The override (`docker-compose.blessingskin.yml`) repoints Caddy's `auth.*` vhost
|
||||||
|
at `blessing-skin:80`, adds `mariadb` + `blessing-skin`, and switches the
|
||||||
|
Minecraft authlib endpoint to `/api/yggdrasil`. Drasl stays defined but idle and
|
||||||
|
unreachable (compose merges `depends_on`, so an override can't remove it);
|
||||||
|
`docker compose … stop drasl` after up if you want it down.
|
||||||
|
|
||||||
|
## Drasl vs Blessing Skin — what changes
|
||||||
|
|
||||||
|
| | Drasl | Blessing Skin |
|
||||||
|
|---|---|---|
|
||||||
|
| Backend | single Go binary | PHP app + **MariaDB** (no SQLite) |
|
||||||
|
| Config | rendered `config.toml` | **web install wizard** + DB |
|
||||||
|
| Yggdrasil API | built in | **yggdrasil-api plugin** (install + enable) |
|
||||||
|
| authlib endpoint | `…/authlib-injector` | `…/api/yggdrasil` |
|
||||||
|
| OIDC/Keycloak | supported (future) | not native (OAuth plugins exist) |
|
||||||
|
| Server online-mode | repo used FALSE | **TRUE** (real session validation) |
|
||||||
|
|
||||||
|
## Setup (first run)
|
||||||
|
|
||||||
|
1. `cp .env.example .env`, fill the `BS_*` vars. Generate `BS_APP_KEY`:
|
||||||
|
```
|
||||||
|
docker run --rm azusamikan/blessing-skin-server-docker:latest \
|
||||||
|
php artisan key:generate --show
|
||||||
|
```
|
||||||
|
2. Bring the stack up with the override (command above).
|
||||||
|
3. Open `http://auth.${BASE_DOMAIN}` → BSS install wizard. DB host = `mariadb`,
|
||||||
|
port `3306`, name/user/pass = the `BS_*` values. Create the admin account.
|
||||||
|
4. Admin panel → **Plugins → Plugin Market** → install **yggdrasil-api** →
|
||||||
|
enable it. (Needs internet; for air-gap, pre-place the plugin jar/zip into
|
||||||
|
the `bs_plugins` volume.) After enabling, the API root is live at
|
||||||
|
`http://auth.${BASE_DOMAIN}/api/yggdrasil`.
|
||||||
|
5. Each player: register on the BSS site, add a character whose name == their
|
||||||
|
in-game player name, upload a skin.
|
||||||
|
6. Restart minecraft if it came up before the plugin was enabled.
|
||||||
|
|
||||||
|
## Client (Prism / authlib-injector)
|
||||||
|
|
||||||
|
- authlib-injector URL: `http://auth.${BASE_DOMAIN}/api/yggdrasil`
|
||||||
|
- Login = BSS **email + password** (BSS uses email as the account id).
|
||||||
|
- Must match the server's `-javaagent` arg exactly (same host + path).
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **`/api/yggdrasil`, not `/authlib-injector`.** Wrong path → silent "Invalid
|
||||||
|
session". Server `JVM_OPTS` and every client must agree.
|
||||||
|
- **ONLINE_MODE=TRUE.** authlib-injector only authenticates when online-mode is
|
||||||
|
on; the server then validates the join against BSS. (The Drasl variant in this
|
||||||
|
repo set FALSE — do not copy that here.)
|
||||||
|
- **Secure profile (1.21+).** Override ships `ENFORCE_SECURE_PROFILE=FALSE` for
|
||||||
|
safety. yggdrasil-api *does* sign profile keys, so TRUE may work — flip and
|
||||||
|
test if signed chat matters; revert on "Invalid signature".
|
||||||
|
- **APP_KEY must persist.** Set `BS_APP_KEY` in `.env`. Losing it invalidates
|
||||||
|
all sessions and breaks encrypted data.
|
||||||
|
- **DB is the source of truth.** `bs_db` volume holds accounts; `bs_storage`
|
||||||
|
holds skins; `bs_plugins` holds the yggdrasil-api plugin. Back these up.
|
||||||
|
- **Air-gap:** the plugin market needs internet. Install + enable yggdrasil-api
|
||||||
|
while online (it persists in `bs_plugins`), or stage the plugin manually.
|
||||||
|
|
||||||
|
## Image note
|
||||||
|
|
||||||
|
`azusamikan/blessing-skin-server-docker:latest` is a community image and the
|
||||||
|
env-var names (`DB_*`, `APP_KEY`) may differ slightly by tag — if auto-config
|
||||||
|
doesn't take, the web wizard is the reliable path. Official source:
|
||||||
|
https://github.com/bs-community/blessing-skin-server ,
|
||||||
|
plugin: https://github.com/bs-community/yggdrasil-api , authlib-injector setup:
|
||||||
|
https://github.com/bs-community/blessing-skin-manual/blob/master/man/yggdrasil-api/authlib-injector.md
|
||||||
132
tooling/add-custom-mod.sh
Executable file
132
tooling/add-custom-mod.sh
Executable file
@@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# add-custom-mod.sh — add LOCAL jar(s) to the packwiz pack as "external files".
|
||||||
|
#
|
||||||
|
# Implements the "Other external files" flow from
|
||||||
|
# https://packwiz.infra.link/tutorials/creating/adding-mods/ : a <slug>.pw.toml
|
||||||
|
# that points at a stable download URL + sha256, then refresh. Here the metadata
|
||||||
|
# is written as a TEMPLATE (<slug>.pw.toml.tmpl) with a ${BASE_DOMAIN}
|
||||||
|
# placeholder and rendered by render-config.sh, so the domain is never frozen.
|
||||||
|
#
|
||||||
|
# Since the jars are local (no public URL), we HOST them on the caddy pack
|
||||||
|
# server: each jar is copied to the top-level ./custom/ dir (OUTSIDE ./pack, so
|
||||||
|
# packwiz refresh won't index it as a direct file). Caddy bind-mounts ./custom
|
||||||
|
# at /srv/pack/custom, so it is served at
|
||||||
|
# http://pack.${BASE_DOMAIN}/custom/<filename>
|
||||||
|
# which is exactly what minecraft fetches via PACKWIZ_URL on install.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# tooling/add-custom-mod.sh <mod.jar> [--name "Nice Name"] [--side S] [--force]
|
||||||
|
# tooling/add-custom-mod.sh <dir/> [--side S] [--force] # all *.jar in dir
|
||||||
|
# # (--name not allowed)
|
||||||
|
# S = both|client|server (default both)
|
||||||
|
#
|
||||||
|
# Single-jar name defaults to the filename without .jar; folder mode derives
|
||||||
|
# each name from its filename. packwiz refresh runs ONCE at the end.
|
||||||
|
# Cautious: halts on missing tool/var, bad side, whitespace in a filename, an
|
||||||
|
# empty folder, or an existing metadata/custom jar (unless --force).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.." # repo root
|
||||||
|
|
||||||
|
# ---- helpers ---------------------------------------------------------------
|
||||||
|
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; }
|
||||||
|
|
||||||
|
slugify() { # lowercase, non-alnum -> hyphen, squeeze + trim hyphens
|
||||||
|
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' \
|
||||||
|
| sed -E 's/[^a-z0-9]+/-/g; s/-+/-/g; s/^-//; s/-$//'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- args ------------------------------------------------------------------
|
||||||
|
TARGET=""; NAME=""; SIDE="both"; FORCE=0
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--name) NAME="${2:?--name needs a value}"; shift 2 ;;
|
||||||
|
--side) SIDE="${2:?--side needs a value}"; shift 2 ;;
|
||||||
|
--force) FORCE=1; shift ;;
|
||||||
|
-h|--help) sed -n '2,24p' "$0"; exit 0 ;;
|
||||||
|
-*) die "unknown flag: $1" ;;
|
||||||
|
*) [ -z "$TARGET" ] || die "only one jar or dir at a time (got extra: $1)"; TARGET="$1"; shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -n "$TARGET" ] || die "no jar/dir given. usage: tooling/add-custom-mod.sh <mod.jar|dir/> [--side both|client|server]"
|
||||||
|
[ -e "$TARGET" ] || die "path not found: $TARGET"
|
||||||
|
case "$SIDE" in both|client|server) ;; *) die "invalid --side '$SIDE' (both|client|server)" ;; esac
|
||||||
|
|
||||||
|
# ---- preflight -------------------------------------------------------------
|
||||||
|
command -v packwiz >/dev/null 2>&1 || die "missing required tool: packwiz (https://packwiz.infra.link)"
|
||||||
|
command -v sha256sum >/dev/null 2>&1 || die "missing required tool: sha256sum"
|
||||||
|
command -v envsubst >/dev/null 2>&1 || die "missing required tool: envsubst (gettext)"
|
||||||
|
[ -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}"
|
||||||
|
[ -f pack/pack.toml ] || die "pack/pack.toml not found — run from a packwiz pack (see plan/04-packwiz.md)"
|
||||||
|
|
||||||
|
# ---- collect jars ----------------------------------------------------------
|
||||||
|
declare -a JARS=()
|
||||||
|
if [ -d "$TARGET" ]; then
|
||||||
|
[ -z "$NAME" ] || die "--name not allowed with a folder (names are derived per-jar)"
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$TARGET"/*.jar; do JARS+=("$f"); done
|
||||||
|
shopt -u nullglob
|
||||||
|
[ "${#JARS[@]}" -gt 0 ] || die "no .jar files in folder: $TARGET"
|
||||||
|
else
|
||||||
|
JARS=("$TARGET")
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p custom pack/mods
|
||||||
|
|
||||||
|
# ---- per-jar processing (no refresh; refresh once at the end) --------------
|
||||||
|
add_one() { # $1 = jar path. Uses NAME (single-jar only), SIDE, FORCE, BASE_DOMAIN.
|
||||||
|
local jar="$1" filename name slug meta dest url hash
|
||||||
|
[ -f "$jar" ] || die "not a file: $jar"
|
||||||
|
filename="$(basename -- "$jar")"
|
||||||
|
case "$filename" in *.jar) ;; *) die "not a .jar: $filename" ;; esac
|
||||||
|
case "$filename" in *[[:space:]]*) die "filename has whitespace: '$filename' — rename it (URL-unsafe)" ;; esac
|
||||||
|
|
||||||
|
name="$NAME"; [ -n "$name" ] || name="${filename%.jar}"
|
||||||
|
slug="$(slugify "$name")"
|
||||||
|
[ -n "$slug" ] || die "could not derive a slug from name '$name'"
|
||||||
|
|
||||||
|
# Metadata is a TEMPLATE: the download URL keeps a literal ${BASE_DOMAIN}
|
||||||
|
# placeholder, rendered to the real .pw.toml by render-config.sh at deploy so
|
||||||
|
# the domain is never frozen into a committed file.
|
||||||
|
meta="pack/mods/${slug}.pw.toml.tmpl"
|
||||||
|
dest="custom/${filename}"
|
||||||
|
url_tmpl='http://pack.${BASE_DOMAIN}/custom/'"${filename}"
|
||||||
|
|
||||||
|
if [ "$FORCE" -ne 1 ]; then
|
||||||
|
[ -e "$meta" ] && die "metadata exists: $meta (use --force to overwrite)"
|
||||||
|
[ -e "$dest" ] && die "hosted jar exists: $dest (use --force to overwrite)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
hash="$(sha256sum -- "$jar" | cut -d' ' -f1)"
|
||||||
|
# Skip the copy when the source already IS the hosted file (regen-in-place,
|
||||||
|
# e.g. `add-custom-mod.sh custom --force` to rewrite metadata).
|
||||||
|
[ "$dest" -ef "$jar" ] || cp -- "$jar" "$dest"
|
||||||
|
cat > "$meta" <<EOF
|
||||||
|
name = "${name}"
|
||||||
|
filename = "${filename}"
|
||||||
|
side = "${SIDE}"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
url = "${url_tmpl}"
|
||||||
|
hash-format = "sha256"
|
||||||
|
hash = "${hash}"
|
||||||
|
EOF
|
||||||
|
echo " + ${filename} -> http://pack.${BASE_DOMAIN}/custom/${filename} (${SIDE}, sha256 ${hash:0:12}…)"
|
||||||
|
}
|
||||||
|
|
||||||
|
log "adding ${#JARS[@]} jar(s), side=${SIDE}"
|
||||||
|
for j in "${JARS[@]}"; do add_one "$j"; done
|
||||||
|
|
||||||
|
# ---- render + refresh once -------------------------------------------------
|
||||||
|
# render-pack.sh renders all pack templates (BASE_DOMAIN only) + rebuilds the
|
||||||
|
# packwiz index. Single source of pack-render logic, shared with render-config.
|
||||||
|
log "rendering metadata + refreshing index"
|
||||||
|
tooling/render-pack.sh
|
||||||
|
log "done — ${#JARS[@]} mod(s) added to the pack"
|
||||||
|
echo " commit: custom/*.jar + pack/mods/*.pw.toml.tmpl (rendered .pw.toml/index are gitignored)"
|
||||||
@@ -19,3 +19,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 dnsmasq/dnsmasq.conf.tmpl dnsmasq/dnsmasq.conf
|
render dnsmasq/dnsmasq.conf.tmpl dnsmasq/dnsmasq.conf
|
||||||
|
|
||||||
|
# pack templates (mods/*.pw.toml.tmpl, CustomSkinLoader, …) + packwiz index.
|
||||||
|
# Delegated to render-pack.sh (BASE_DOMAIN only — single source of pack render).
|
||||||
|
tooling/render-pack.sh
|
||||||
|
|||||||
33
tooling/render-pack.sh
Executable file
33
tooling/render-pack.sh
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# render-pack.sh — render every pack template (pack/**/*.tmpl) by injecting
|
||||||
|
# ${BASE_DOMAIN}, then rebuild the packwiz index.
|
||||||
|
#
|
||||||
|
# Pack templates are domain-dependent build output (download URLs, skin API
|
||||||
|
# roots, …). This renderer needs ONLY ${BASE_DOMAIN} — no dnsmasq HOST_LAN_IP —
|
||||||
|
# so it can run while curating mods before the full deploy config exists.
|
||||||
|
# Both tooling/render-config.sh (deploy) and tooling/add-custom-mod.sh call it.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.." # repo root
|
||||||
|
|
||||||
|
[ -f .env ] && { set -a; . ./.env; set +a; }
|
||||||
|
: "${BASE_DOMAIN:?BASE_DOMAIN unset (set in .env)}"
|
||||||
|
command -v envsubst >/dev/null 2>&1 || { echo "missing required tool: envsubst (gettext)" >&2; exit 1; }
|
||||||
|
command -v packwiz >/dev/null 2>&1 || { echo "missing required tool: packwiz" >&2; exit 1; }
|
||||||
|
[ -f pack/pack.toml ] || { echo "pack/pack.toml not found" >&2; exit 1; }
|
||||||
|
|
||||||
|
shopt -s globstar nullglob
|
||||||
|
tmpls=(pack/**/*.tmpl)
|
||||||
|
shopt -u globstar nullglob
|
||||||
|
|
||||||
|
for t in "${tmpls[@]}"; do
|
||||||
|
# Substitute ONLY ${BASE_DOMAIN} so any other literal $-syntax is preserved.
|
||||||
|
envsubst '${BASE_DOMAIN}' < "$t" > "${t%.tmpl}"
|
||||||
|
echo "rendered ${t%.tmpl}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# index.toml is gitignored build output; seed a minimal one so packwiz refresh
|
||||||
|
# (which won't bootstrap a missing index) has something to populate.
|
||||||
|
[ -f pack/index.toml ] || printf 'hash-format = "sha256"\n' > pack/index.toml
|
||||||
|
( cd pack && packwiz refresh )
|
||||||
|
echo "packwiz index refreshed (${#tmpls[@]} template(s))"
|
||||||
Reference in New Issue
Block a user