mirror of
https://github.com/itzg/docker-minecraft-server.git
synced 2026-08-13 02:33:17 +00:00
feat: load env vars from file or archive at startup (#4053)
This commit is contained in:
@@ -156,6 +156,54 @@ Disabling mods within docker compose files:
|
|||||||
mod2.jar
|
mod2.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Loading container configuration from a pack
|
||||||
|
|
||||||
|
A pack can ship its own container configuration so that the server type, version,
|
||||||
|
and other variables travel with the pack rather than being declared by the user.
|
||||||
|
At startup, before `TYPE` is dispatched, the container can load environment
|
||||||
|
variables from a file on disk, a URL, an entry inside an archive, or from the
|
||||||
|
`.env` of each `GENERIC_PACK(S)` entry.
|
||||||
|
|
||||||
|
- `LOAD_ENV_FROM_GENERIC_PACK`: when `true`, each entry in `GENERIC_PACKS` (after
|
||||||
|
`GENERIC_PACKS_PREFIX`/`SUFFIX` expansion) is probed for a top-level `.env`
|
||||||
|
and each one found is sourced in the same order the packs are applied (later
|
||||||
|
packs override earlier ones, matching the layering of the unpack itself). Packs
|
||||||
|
without a `.env` are skipped without error. URLs are downloaded into
|
||||||
|
`/data/packs/` and reused by the regular generic-pack unpack step, so they are
|
||||||
|
not fetched twice.
|
||||||
|
- `LOAD_ENV_FROM_FILE`: container path or URL of a shell-style env file (one
|
||||||
|
`KEY=VALUE` per line). Comments and blank lines are allowed.
|
||||||
|
- `LOAD_ENV_FROM_ARCHIVE`: container path or URL of a zip/tar archive containing
|
||||||
|
an env file. The entry is sourced into the environment.
|
||||||
|
- `LOAD_ENV_FROM_ARCHIVE_ENTRY`: relative path of the env file inside the archive.
|
||||||
|
Defaults to `.env`.
|
||||||
|
|
||||||
|
These can be combined. Load order is: generic packs first, then
|
||||||
|
`LOAD_ENV_FROM_FILE`, then `LOAD_ENV_FROM_ARCHIVE` — later loads override
|
||||||
|
earlier ones, and all of them **override** values passed via `docker run -e` (or
|
||||||
|
compose `environment:`), so the pack's declared values win.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run -d \
|
||||||
|
-e EULA=TRUE \
|
||||||
|
-e GENERIC_PACK=https://cdn.example.org/my-pack.zip \
|
||||||
|
-e LOAD_ENV_FROM_GENERIC_PACK=true \
|
||||||
|
itzg/minecraft-server
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `my-pack.zip` contains a `.env` at its root such as:
|
||||||
|
|
||||||
|
```env
|
||||||
|
TYPE=FABRIC
|
||||||
|
VERSION=1.21.1
|
||||||
|
FABRIC_LOADER_VERSION=0.16.0
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
The env file is sourced by `bash`, so any shell syntax it contains will be
|
||||||
|
evaluated. Only point these variables at sources you trust. `EULA` cannot be
|
||||||
|
set this way — it is checked before the env file is loaded.
|
||||||
|
|
||||||
## Mods/plugins list
|
## Mods/plugins list
|
||||||
|
|
||||||
You may also download or copy over individual mods/plugins using the `MODS` or `PLUGINS` environment variables. Both are a comma or newline delimited list of
|
You may also download or copy over individual mods/plugins using the `MODS` or `PLUGINS` environment variables. Both are a comma or newline delimited list of
|
||||||
|
|||||||
@@ -152,6 +152,31 @@ fi
|
|||||||
|
|
||||||
cd /data || exit 1
|
cd /data || exit 1
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
# Optionally load environment variables from a file or archive entry,
|
||||||
|
# allowing packs/artifacts to declare TYPE, VERSION and other settings
|
||||||
|
# inside-out. Loaded values override anything passed in through docker -e.
|
||||||
|
# Generic packs are processed first so that LOAD_ENV_FROM_FILE and
|
||||||
|
# LOAD_ENV_FROM_ARCHIVE can override any values they set.
|
||||||
|
|
||||||
|
if isTrue "${LOAD_ENV_FROM_GENERIC_PACK:-false}"; then
|
||||||
|
if ! loadEnvFromGenericPack; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${LOAD_ENV_FROM_FILE:-} ]]; then
|
||||||
|
if ! loadEnvFromFile "${LOAD_ENV_FROM_FILE}"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${LOAD_ENV_FROM_ARCHIVE:-} ]]; then
|
||||||
|
if ! loadEnvFromArchive "${LOAD_ENV_FROM_ARCHIVE}" "${LOAD_ENV_FROM_ARCHIVE_ENTRY:-.env}"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
export DECLARED_TYPE=${TYPE^^}
|
export DECLARED_TYPE=${TYPE^^}
|
||||||
export DECLARED_VERSION="$VERSION"
|
export DECLARED_VERSION="$VERSION"
|
||||||
|
|
||||||
|
|||||||
+116
-3
@@ -26,6 +26,117 @@ function applyResultsFile() {
|
|||||||
set +a
|
set +a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadEnvFromFile() {
|
||||||
|
local source=${1?Missing required source argument}
|
||||||
|
local downloaded=
|
||||||
|
|
||||||
|
if isURL "$source"; then
|
||||||
|
mkdir -p /data/.tmp
|
||||||
|
downloaded=$(mktemp -p /data/.tmp)
|
||||||
|
log "Downloading env file from $source"
|
||||||
|
if ! get -o "$downloaded" "$source"; then
|
||||||
|
logError "Failed to download env file from $source"
|
||||||
|
rm -f "$downloaded"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
log "Loading env vars from $source"
|
||||||
|
applyResultsFile "$downloaded"
|
||||||
|
rm -f "$downloaded"
|
||||||
|
elif [ -f "$source" ]; then
|
||||||
|
log "Loading env vars from $source"
|
||||||
|
applyResultsFile "$source"
|
||||||
|
else
|
||||||
|
logError "Env file not found: $source"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadEnvFromArchive() {
|
||||||
|
local source=${1?Missing required source argument}
|
||||||
|
local entry=${2:-.env}
|
||||||
|
local archive=
|
||||||
|
local downloaded=
|
||||||
|
local tmpdir
|
||||||
|
local rc=0
|
||||||
|
|
||||||
|
mkdir -p /data/.tmp
|
||||||
|
|
||||||
|
if isURL "$source"; then
|
||||||
|
downloaded=$(mktemp -p /data/.tmp)
|
||||||
|
log "Downloading archive from $source"
|
||||||
|
if ! get -o "$downloaded" "$source"; then
|
||||||
|
logError "Failed to download archive from $source"
|
||||||
|
rm -f "$downloaded"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
archive=$downloaded
|
||||||
|
elif [ -f "$source" ]; then
|
||||||
|
archive=$source
|
||||||
|
else
|
||||||
|
logError "Archive not found: $source"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmpdir=$(mktemp -d -p /data/.tmp)
|
||||||
|
if extract "$archive" "$tmpdir" "$entry" && [ -f "$tmpdir/$entry" ]; then
|
||||||
|
log "Loading env vars from '$entry' in $source"
|
||||||
|
applyResultsFile "$tmpdir/$entry"
|
||||||
|
else
|
||||||
|
logError "Failed to load env entry '$entry' from $source"
|
||||||
|
rc=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$tmpdir"
|
||||||
|
[[ -n "$downloaded" ]] && rm -f "$downloaded"
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadEnvFromGenericPack() {
|
||||||
|
: "${GENERIC_PACKS:=${GENERIC_PACK:-}}"
|
||||||
|
: "${GENERIC_PACKS_PREFIX:=}"
|
||||||
|
: "${GENERIC_PACKS_SUFFIX:=}"
|
||||||
|
|
||||||
|
if [[ -z "${GENERIC_PACKS}" ]]; then
|
||||||
|
logWarning "LOAD_ENV_FROM_GENERIC_PACK is set but GENERIC_PACK(S) is empty"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p /data/.tmp
|
||||||
|
IFS=',' read -ra packs <<< "${GENERIC_PACKS}"
|
||||||
|
local loaded=0
|
||||||
|
local pack packEntry packFile tmpdir
|
||||||
|
for packEntry in "${packs[@]}"; do
|
||||||
|
pack="${GENERIC_PACKS_PREFIX}${packEntry}${GENERIC_PACKS_SUFFIX}"
|
||||||
|
if isURL "$pack"; then
|
||||||
|
mkdir -p /data/packs
|
||||||
|
if ! packFile=$(get -o /data/packs --output-filename --skip-up-to-date "$pack"); then
|
||||||
|
logError "Failed to download generic pack $pack"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
packFile=$pack
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$packFile" ]]; then
|
||||||
|
logError "Generic pack not found: $packFile"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmpdir=$(mktemp -d -p /data/.tmp)
|
||||||
|
# Packs without a .env are valid — silently skip; the unpack step still applies them.
|
||||||
|
if extract "$packFile" "$tmpdir" .env 2>/dev/null && [ -f "$tmpdir/.env" ]; then
|
||||||
|
log "Loading env vars from .env in $pack"
|
||||||
|
applyResultsFile "$tmpdir/.env"
|
||||||
|
loaded=$((loaded + 1))
|
||||||
|
fi
|
||||||
|
rm -rf "$tmpdir"
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( loaded == 0 )); then
|
||||||
|
logWarning "LOAD_ENV_FROM_GENERIC_PACK is set but no pack in GENERIC_PACK(S) contained a .env"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
function join_by() {
|
function join_by() {
|
||||||
local d=$1
|
local d=$1
|
||||||
shift
|
shift
|
||||||
@@ -445,17 +556,19 @@ function isType() {
|
|||||||
function extract() {
|
function extract() {
|
||||||
src=${1?}
|
src=${1?}
|
||||||
destDir=${2?}
|
destDir=${2?}
|
||||||
|
shift 2
|
||||||
|
# remaining args are paths within the archive to extract; if none, extract everything
|
||||||
|
|
||||||
type=$(file -b --mime-type "${src}")
|
type=$(file -b --mime-type "${src}")
|
||||||
case "${type}" in
|
case "${type}" in
|
||||||
application/zip)
|
application/zip)
|
||||||
unzip -o -q -d "${destDir}" "${src}"
|
unzip -o -q -d "${destDir}" "${src}" "$@"
|
||||||
;;
|
;;
|
||||||
application/x-tar | application/gzip | application/x-gzip | application/x-bzip2)
|
application/x-tar | application/gzip | application/x-gzip | application/x-bzip2)
|
||||||
tar -C "${destDir}" -xf "${src}"
|
tar -C "${destDir}" -xf "${src}" "$@"
|
||||||
;;
|
;;
|
||||||
application/zstd | application/x-zstd)
|
application/zstd | application/x-zstd)
|
||||||
tar -C "${destDir}" --use-compress-program=unzstd -xf "${src}"
|
tar -C "${destDir}" --use-compress-program=unzstd -xf "${src}" "$@"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
logError "Unsupported archive type: $type"
|
logError "Unsupported archive type: $type"
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
services:
|
||||||
|
mc:
|
||||||
|
image: ${IMAGE_TO_TEST:-itzg/minecraft-server}
|
||||||
|
environment:
|
||||||
|
EULA: "true"
|
||||||
|
SETUP_ONLY: "true"
|
||||||
|
LOAD_ENV_FROM_ARCHIVE: /test/load-env.zip
|
||||||
|
MOTD: from-compose
|
||||||
|
LOG_TIMESTAMP: "true"
|
||||||
|
DEBUG: "true"
|
||||||
|
# the following are only used to speed up test execution
|
||||||
|
TYPE: CUSTOM
|
||||||
|
CUSTOM_SERVER: /servers/fake.jar
|
||||||
|
VERSION: 1.18.1
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- ./load-env.zip:/test/load-env.zip
|
||||||
|
- ./fake.jar:/servers/fake.jar
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
mc-image-helper assert propertyEquals --file=server.properties --property=motd --expect=from-archive
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
services:
|
||||||
|
mc:
|
||||||
|
image: ${IMAGE_TO_TEST:-itzg/minecraft-server}
|
||||||
|
environment:
|
||||||
|
EULA: "true"
|
||||||
|
SETUP_ONLY: "true"
|
||||||
|
LOAD_ENV_FROM_FILE: /test/load-env.env
|
||||||
|
MOTD: from-compose
|
||||||
|
LOG_TIMESTAMP: "true"
|
||||||
|
DEBUG: "true"
|
||||||
|
# the following are only used to speed up test execution
|
||||||
|
TYPE: CUSTOM
|
||||||
|
CUSTOM_SERVER: /servers/fake.jar
|
||||||
|
VERSION: 1.18.1
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- ./load-env.env:/test/load-env.env
|
||||||
|
- ./fake.jar:/servers/fake.jar
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Loaded by LOAD_ENV_FROM_FILE during start-configuration
|
||||||
|
MOTD=from-env-file
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
mc-image-helper assert propertyEquals --file=server.properties --property=motd --expect=from-env-file
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
services:
|
||||||
|
mc:
|
||||||
|
image: ${IMAGE_TO_TEST:-itzg/minecraft-server}
|
||||||
|
environment:
|
||||||
|
EULA: "true"
|
||||||
|
SETUP_ONLY: "true"
|
||||||
|
GENERIC_PACK: /packs/pack.zip
|
||||||
|
LOAD_ENV_FROM_GENERIC_PACK: "true"
|
||||||
|
MOTD: from-compose
|
||||||
|
LOG_TIMESTAMP: "true"
|
||||||
|
DEBUG: "true"
|
||||||
|
# the following are only used to speed up test execution
|
||||||
|
TYPE: CUSTOM
|
||||||
|
CUSTOM_SERVER: /servers/fake.jar
|
||||||
|
VERSION: 1.18.1
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- ./pack.zip:/packs/pack.zip
|
||||||
|
- ./fake.jar:/servers/fake.jar
|
||||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
|||||||
|
mc-image-helper assert propertyEquals --file=server.properties --property=motd --expect=from-generic-pack
|
||||||
|
mc-image-helper assert fileExists config/dummy.yml
|
||||||
Reference in New Issue
Block a user