made profiles better

This commit is contained in:
uprightbass360
2025-11-05 01:27:26 -05:00
parent b827757bec
commit 8e7959d7fe
13 changed files with 345 additions and 38 deletions

View File

@@ -412,14 +412,28 @@ The remote deployment process transfers:
- ❌ Build artifacts (source code, compilation files stay local)
### Module Presets
- Drop comma-separated module lists into `profiles/*.conf` (for example `profiles/playerbot-modules.conf`).
- Define JSON presets in `profiles/*.json`. Each file contains:
- `modules` (array, required) list of `MODULE_*` identifiers to enable.
- `label` (string, optional) text shown in the setup menu (emoji welcome).
- `description` (string, optional) short help text for maintainers.
- `order` (number, optional) determines the menu position (lower appears first).
Example:
```json
{
"modules": ["MODULE_ELUNA", "MODULE_SOLO_LFG", "MODULE_SOLOCRAFT"],
"label": "⭐ Suggested Modules",
"description": "Baseline solo-friendly quality of life mix",
"order": 1
}
```
- `setup.sh` automatically adds these presets to the module menu and enables the listed modules when selected or when `--module-config <name>` is provided.
- Built-in presets:
- `profiles/suggested-modules.conf` default solo-friendly QoL stack.
- `profiles/playerbots-suggested-modules.conf` suggested stack plus playerbots.
- `profiles/playerbot-only.conf` playerbot-focused profile (adjust `--playerbot-max-bots`).
- `profiles/suggested-modules.json` default solo-friendly QoL stack.
- `profiles/playerbots-suggested-modules.json` suggested stack plus playerbots.
- `profiles/playerbots-only.json` playerbot-focused profile (adjust `--playerbot-max-bots`).
- Custom example:
- `profiles/sam.conf` Sam's playerbot-focused profile (set `--playerbot-max-bots 3000` when using this preset).
- `profiles/sam.json` Sam's playerbot-focused profile (set `--playerbot-max-bots 3000` when using this preset).
---

View File

@@ -1 +0,0 @@
MODULE_1V1_ARENA,MODULE_ACCOUNTWIDE_SYSTEMS,MODULE_ACCOUNT_ACHIEVEMENTS,MODULE_ACCOUNT_MOUNTS,MODULE_ACORE_SUBSCRIPTIONS,MODULE_ACTIVE_CHAT,MODULE_AHBOT,MODULE_AIO,MODULE_AIO_BLACKJACK,MODULE_ANTIFARMING,MODULE_AOE_LOOT,MODULE_ARAC,MODULE_ARENA_REPLAY,MODULE_ASSISTANT,MODULE_AUTOBALANCE,MODULE_AUTO_REVIVE,MODULE_AZEROTHSHARD,MODULE_BG_SLAVERYVALLEY,MODULE_BLACK_MARKET_AUCTION_HOUSE,MODULE_BOSS_ANNOUNCER,MODULE_BREAKING_NEWS,MODULE_CARBON_COPY,MODULE_CHALLENGE_MODES,MODULE_DISCORD_NOTIFIER,MODULE_DUEL_RESET,MODULE_DUNGEON_RESPAWN,MODULE_DYNAMIC_TRADER,MODULE_DYNAMIC_XP,MODULE_ELUNA,MODULE_ELUNA_SCRIPTS,MODULE_ELUNA_TS,MODULE_EVENT_SCRIPTS,MODULE_EXCHANGE_NPC,MODULE_FIREWORKS,MODULE_GAIN_HONOR_GUARD,MODULE_GLOBAL_CHAT,MODULE_GLOBAL_MAIL_BANKING_AUCTIONS,MODULE_GUILDHOUSE,MODULE_HARDCORE_MODE,MODULE_HORADRIC_CUBE,MODULE_INDIVIDUAL_PROGRESSION,MODULE_INSTANCE_RESET,MODULE_ITEM_LEVEL_UP,MODULE_KEEP_OUT,MODULE_LEARN_SPELLS,MODULE_LEVEL_GRANT,MODULE_LEVEL_UP_REWARD,MODULE_LOTTERY_LUA,MODULE_LUA_AH_BOT,MODULE_MORPHSUMMON,MODULE_MULTIVENDOR,MODULE_NPCBOT_EXTENDED_COMMANDS,MODULE_NPC_BEASTMASTER,MODULE_NPC_BUFFER,MODULE_NPC_ENCHANTER,MODULE_NPC_FREE_PROFESSIONS,MODULE_NPC_TALENT_TEMPLATE,MODULE_OLLAMA_CHAT,MODULE_PHASED_DUELS,MODULE_PLAYERBOTS,MODULE_PLAYER_BOT_LEVEL_BRACKETS,MODULE_POCKET_PORTAL,MODULE_PREMIUM,MODULE_PRESTIGE_DRAFT_MODE,MODULE_PROGRESSION_SYSTEM,MODULE_PROMOTION_AZEROTHCORE,MODULE_PVP_TITLES,MODULE_RANDOM_ENCHANTS,MODULE_REAGENT_BANK,MODULE_RECRUIT_A_FRIEND,MODULE_RESURRECTION_SCROLL,MODULE_REWARD_PLAYED_TIME,MODULE_SEND_AND_BIND,MODULE_SERVER_AUTO_SHUTDOWN,MODULE_SOLOCRAFT,MODULE_SOLO_LFG,MODULE_STATBOOSTER,MODULE_SYSTEM_VIP,MODULE_TEMP_ANNOUNCEMENTS,MODULE_TIC_TAC_TOE,MODULE_TIME_IS_TIME,MODULE_TRANSMOG,MODULE_TRANSMOG_AIO,MODULE_TREASURE_CHEST_SYSTEM,MODULE_ULTIMATE_FULL_LOOT_PVP,MODULE_WAR_EFFORT,MODULE_WEEKEND_XP,MODULE_WHO_LOGGED,MODULE_WORGOBLIN,MODULE_ZONE_CHECK,MODULE_ZONE_DIFFICULTY

98
profiles/all-modules.json Normal file
View File

@@ -0,0 +1,98 @@
{
"modules": [
"MODULE_1V1_ARENA",
"MODULE_ACCOUNTWIDE_SYSTEMS",
"MODULE_ACCOUNT_ACHIEVEMENTS",
"MODULE_ACCOUNT_MOUNTS",
"MODULE_ACORE_SUBSCRIPTIONS",
"MODULE_ACTIVE_CHAT",
"MODULE_AHBOT",
"MODULE_AIO",
"MODULE_AIO_BLACKJACK",
"MODULE_ANTIFARMING",
"MODULE_AOE_LOOT",
"MODULE_ARAC",
"MODULE_ARENA_REPLAY",
"MODULE_ASSISTANT",
"MODULE_AUTOBALANCE",
"MODULE_AUTO_REVIVE",
"MODULE_AZEROTHSHARD",
"MODULE_BG_SLAVERYVALLEY",
"MODULE_BLACK_MARKET_AUCTION_HOUSE",
"MODULE_BOSS_ANNOUNCER",
"MODULE_BREAKING_NEWS",
"MODULE_CARBON_COPY",
"MODULE_CHALLENGE_MODES",
"MODULE_DISCORD_NOTIFIER",
"MODULE_DUEL_RESET",
"MODULE_DUNGEON_RESPAWN",
"MODULE_DYNAMIC_TRADER",
"MODULE_DYNAMIC_XP",
"MODULE_ELUNA",
"MODULE_ELUNA_SCRIPTS",
"MODULE_ELUNA_TS",
"MODULE_EVENT_SCRIPTS",
"MODULE_EXCHANGE_NPC",
"MODULE_FIREWORKS",
"MODULE_GAIN_HONOR_GUARD",
"MODULE_GLOBAL_CHAT",
"MODULE_GLOBAL_MAIL_BANKING_AUCTIONS",
"MODULE_GUILDHOUSE",
"MODULE_HARDCORE_MODE",
"MODULE_HORADRIC_CUBE",
"MODULE_INDIVIDUAL_PROGRESSION",
"MODULE_INSTANCE_RESET",
"MODULE_ITEM_LEVEL_UP",
"MODULE_KEEP_OUT",
"MODULE_LEARN_SPELLS",
"MODULE_LEVEL_GRANT",
"MODULE_LEVEL_UP_REWARD",
"MODULE_LOTTERY_LUA",
"MODULE_LUA_AH_BOT",
"MODULE_MORPHSUMMON",
"MODULE_MULTIVENDOR",
"MODULE_NPCBOT_EXTENDED_COMMANDS",
"MODULE_NPC_BEASTMASTER",
"MODULE_NPC_BUFFER",
"MODULE_NPC_ENCHANTER",
"MODULE_NPC_FREE_PROFESSIONS",
"MODULE_NPC_TALENT_TEMPLATE",
"MODULE_OLLAMA_CHAT",
"MODULE_PHASED_DUELS",
"MODULE_PLAYERBOTS",
"MODULE_PLAYER_BOT_LEVEL_BRACKETS",
"MODULE_POCKET_PORTAL",
"MODULE_PREMIUM",
"MODULE_PRESTIGE_DRAFT_MODE",
"MODULE_PROGRESSION_SYSTEM",
"MODULE_PROMOTION_AZEROTHCORE",
"MODULE_PVP_TITLES",
"MODULE_RANDOM_ENCHANTS",
"MODULE_REAGENT_BANK",
"MODULE_RECRUIT_A_FRIEND",
"MODULE_RESURRECTION_SCROLL",
"MODULE_REWARD_PLAYED_TIME",
"MODULE_SEND_AND_BIND",
"MODULE_SERVER_AUTO_SHUTDOWN",
"MODULE_SOLOCRAFT",
"MODULE_SOLO_LFG",
"MODULE_STATBOOSTER",
"MODULE_SYSTEM_VIP",
"MODULE_TEMP_ANNOUNCEMENTS",
"MODULE_TIC_TAC_TOE",
"MODULE_TIME_IS_TIME",
"MODULE_TRANSMOG",
"MODULE_TRANSMOG_AIO",
"MODULE_TREASURE_CHEST_SYSTEM",
"MODULE_ULTIMATE_FULL_LOOT_PVP",
"MODULE_WAR_EFFORT",
"MODULE_WEEKEND_XP",
"MODULE_WHO_LOGGED",
"MODULE_WORGOBLIN",
"MODULE_ZONE_CHECK",
"MODULE_ZONE_DIFFICULTY"
],
"label": "\ud83e\udde9 All Modules",
"description": "Enable every optional module in the repository",
"order": 5
}

View File

@@ -1 +0,0 @@
MODULE_PLAYERBOTS,MODULE_ELUNA,MODULE_ELUNA_TS

View File

@@ -0,0 +1,10 @@
{
"modules": [
"MODULE_PLAYERBOTS",
"MODULE_ELUNA",
"MODULE_ELUNA_TS"
],
"label": "\ud83e\udde9 Playerbots Only",
"description": "Minimal preset that only enables playerbot prerequisites",
"order": 6
}

View File

@@ -1 +0,0 @@
MODULE_PLAYERBOTS,MODULE_SOLO_LFG,MODULE_SOLOCRAFT,MODULE_AUTOBALANCE,MODULE_TRANSMOG,MODULE_NPC_BUFFER,MODULE_LEARN_SPELLS,MODULE_FIREWORKS

View File

@@ -0,0 +1,15 @@
{
"modules": [
"MODULE_PLAYERBOTS",
"MODULE_SOLO_LFG",
"MODULE_SOLOCRAFT",
"MODULE_AUTOBALANCE",
"MODULE_TRANSMOG",
"MODULE_NPC_BUFFER",
"MODULE_LEARN_SPELLS",
"MODULE_FIREWORKS"
],
"label": "\ud83e\udd16 Playerbots + Suggested modules",
"description": "Suggested stack plus playerbots enabled",
"order": 2
}

View File

@@ -1 +0,0 @@
MODULE_ELUNA,MODULE_PLAYERBOTS,MODULE_PLAYER_BOT_LEVEL_BRACKETS,MODULE_SOLO_LFG,MODULE_TRANSMOG,MODULE_NPC_BUFFER,MODULE_LEARN_SPELLS,MODULE_FIREWORKS,MODULE_REAGENT_BANK,MODULE_BLACK_MARKET_AUCTION_HOUSE,MODULE_1V1_ARENA,MODULE_ACCOUNT_ACHIEVEMENTS,MODULE_BREAKING_NEWS,MODULE_BOSS_ANNOUNCER,MODULE_AUTO_REVIVE,MODULE_ELUNA_TS,MODULE_NPC_BEASTMASTER,MODULE_NPC_ENCHANTER,MODULE_RANDOM_ENCHANTS,MODULE_INSTANCE_RESET,MODULE_TIME_IS_TIME,MODULE_GAIN_HONOR_GUARD,MODULE_ARAC

30
profiles/sam.json Normal file
View File

@@ -0,0 +1,30 @@
{
"modules": [
"MODULE_ELUNA",
"MODULE_PLAYERBOTS",
"MODULE_PLAYER_BOT_LEVEL_BRACKETS",
"MODULE_SOLO_LFG",
"MODULE_TRANSMOG",
"MODULE_NPC_BUFFER",
"MODULE_LEARN_SPELLS",
"MODULE_FIREWORKS",
"MODULE_REAGENT_BANK",
"MODULE_BLACK_MARKET_AUCTION_HOUSE",
"MODULE_1V1_ARENA",
"MODULE_ACCOUNT_ACHIEVEMENTS",
"MODULE_BREAKING_NEWS",
"MODULE_BOSS_ANNOUNCER",
"MODULE_AUTO_REVIVE",
"MODULE_ELUNA_TS",
"MODULE_NPC_BEASTMASTER",
"MODULE_NPC_ENCHANTER",
"MODULE_RANDOM_ENCHANTS",
"MODULE_INSTANCE_RESET",
"MODULE_TIME_IS_TIME",
"MODULE_GAIN_HONOR_GUARD",
"MODULE_ARAC"
],
"label": "\ud83e\udde9 Sam",
"description": "Sam's playerbot-centric preset (use high bot counts)",
"order": 7
}

View File

@@ -1 +0,0 @@
MODULE_ELUNA,MODULE_SOLO_LFG,MODULE_SOLOCRAFT,MODULE_AUTOBALANCE,MODULE_TRANSMOG,MODULE_NPC_BUFFER,MODULE_LEARN_SPELLS,MODULE_FIREWORKS

View File

@@ -0,0 +1,15 @@
{
"modules": [
"MODULE_ELUNA",
"MODULE_SOLO_LFG",
"MODULE_SOLOCRAFT",
"MODULE_AUTOBALANCE",
"MODULE_TRANSMOG",
"MODULE_NPC_BUFFER",
"MODULE_LEARN_SPELLS",
"MODULE_FIREWORKS"
],
"label": "\u2b50 Suggested Modules",
"description": "Baseline solo-friendly quality of life mix",
"order": 1
}

109
scripts/setup_profiles.py Executable file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""
Expose profile metadata for setup.sh.
Profiles are JSON documents with at least:
{
"modules": ["MODULE_FOO", "MODULE_BAR"],
"label": "...", # optional
"description": "..." # optional
}
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
from typing import Iterable, List, Tuple
def normalize_modules(raw_modules: Iterable[str], profile: Path) -> List[str]:
"""Return a cleaned list of module identifiers."""
modules: List[str] = []
for item in raw_modules:
if not isinstance(item, str):
raise ValueError(f"Profile {profile.name}: module entries must be strings")
value = item.strip()
if not value:
continue
modules.append(value)
if not modules:
raise ValueError(f"Profile {profile.name}: modules list cannot be empty")
return modules
def sanitize(text: str | None) -> str:
if not text:
return ""
return str(text).replace("\t", " ").replace("\n", " ").strip()
def load_profile(path: Path) -> Tuple[str, List[str], str, str, int]:
try:
data = json.loads(path.read_text())
except json.JSONDecodeError as exc:
raise ValueError(f"Profile {path.name}: invalid JSON - {exc}") from exc
raw_modules = data.get("modules")
if not isinstance(raw_modules, list):
raise ValueError(f"Profile {path.name}: 'modules' must be a list")
modules = normalize_modules(raw_modules, path)
name = data.get("name") or path.stem
label = sanitize(data.get("label")) or " ".join(part.capitalize() for part in name.replace("-", " ").split())
description = sanitize(data.get("description"))
order_raw = data.get("order")
try:
order = int(order_raw) if order_raw is not None else 10000
except (TypeError, ValueError):
raise ValueError(f"Profile {path.name}: 'order' must be an integer") from None
return name, modules, label, description, order
def cmd_list(directory: Path) -> int:
if not directory.is_dir():
print(f"ERROR: Profile directory not found: {directory}", file=sys.stderr)
return 1
profiles: List[Tuple[str, List[str], str, str, int]] = []
for candidate in sorted(directory.glob("*.json")):
try:
profiles.append(load_profile(candidate))
except ValueError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 1
profiles.sort(key=lambda item: item[4])
for name, modules, label, description, order in profiles:
modules_csv = ",".join(modules)
print("\t".join([name, modules_csv, label, description, str(order)]))
return 0
COMMANDS = {
"list": cmd_list,
}
def main(argv: List[str]) -> int:
if len(argv) != 3:
print(f"Usage: {argv[0]} <command> <profiles-dir>", file=sys.stderr)
return 1
command = argv[1]
handler = COMMANDS.get(command)
if handler is None:
valid = ", ".join(sorted(COMMANDS))
print(f"Unknown command '{command}'. Valid commands: {valid}", file=sys.stderr)
return 1
directory = Path(argv[2])
return handler(directory)
if __name__ == "__main__":
sys.exit(main(sys.argv))

View File

@@ -350,6 +350,7 @@ EOF
MODULE_MANIFEST_PATH="$SCRIPT_DIR/config/modules.json"
MODULE_MANIFEST_HELPER="$SCRIPT_DIR/scripts/setup_manifest.py"
MODULE_PROFILES_HELPER="$SCRIPT_DIR/scripts/setup_profiles.py"
ENV_TEMPLATE_FILE="$SCRIPT_DIR/.env.template"
declare -a MODULE_KEYS=()
@@ -566,7 +567,7 @@ Options:
--backup-retention-hours N Hourly backup retention (default 6)
--backup-daily-time HH Daily backup hour 00-23 (default 09)
--module-mode MODE suggested, playerbots, manual, or none
--module-config NAME Use preset NAME from profiles/<NAME>.conf
--module-config NAME Use preset NAME from profiles/<NAME>.json
--enable-modules LIST Comma-separated module list (MODULE_* or shorthand)
--playerbot-enabled 0|1 Override PLAYERBOT_ENABLED flag
--playerbot-min-bots N Override PLAYERBOT_MIN_BOTS value
@@ -926,24 +927,28 @@ fi
local MODE_SELECTION=""
local MODE_PRESET_NAME=""
declare -A MODULE_PRESET_CONFIGS=()
declare -a MODULE_PRESET_ORDER=()
declare -A MODULE_PRESET_LABELS=()
declare -A MODULE_PRESET_DESCRIPTIONS=()
declare -A MODULE_PRESET_ORDER=()
local CONFIG_DIR="$SCRIPT_DIR/profiles"
if [ ! -x "$MODULE_PROFILES_HELPER" ]; then
say ERROR "Profile helper not found or not executable at $MODULE_PROFILES_HELPER"
exit 1
fi
if [ -d "$CONFIG_DIR" ]; then
while IFS= read -r preset_path; do
[ -n "$preset_path" ] || continue
local preset_name
preset_name="$(basename "$preset_path" .conf)"
local preset_value
preset_value="$(tr -d '\r' < "$preset_path" | tr '\n' ',' | sed -E 's/,+/,/g; s/^,//; s/,$//')"
MODULE_PRESET_CONFIGS["$preset_name"]="$preset_value"
MODULE_PRESET_ORDER+=("$preset_name")
done < <(find "$CONFIG_DIR" -maxdepth 1 -type f -name '*.conf' -print | sort)
while IFS=$'\t' read -r preset_name preset_modules preset_label preset_desc preset_order; do
[ -n "$preset_name" ] || continue
MODULE_PRESET_CONFIGS["$preset_name"]="$preset_modules"
MODULE_PRESET_LABELS["$preset_name"]="$preset_label"
MODULE_PRESET_DESCRIPTIONS["$preset_name"]="$preset_desc"
MODULE_PRESET_ORDER["$preset_name"]="${preset_order:-10000}"
done < <(python3 "$MODULE_PROFILES_HELPER" list "$CONFIG_DIR")
fi
local missing_presets=0
for required_preset in "$DEFAULT_PRESET_SUGGESTED" "$DEFAULT_PRESET_PLAYERBOTS"; do
if [ -z "${MODULE_PRESET_CONFIGS[$required_preset]:-}" ]; then
say ERROR "Missing module preset profiles/${required_preset}.conf"
say ERROR "Missing module preset profiles/${required_preset}.json"
missing_presets=1
fi
done
@@ -998,25 +1003,38 @@ fi
# Module config
say HEADER "MODULE PRESET"
echo "1) ⭐ Suggested Modules"
echo "2) 🤖 Playerbots + Suggested modules"
echo "1) ${MODULE_PRESET_LABELS[$DEFAULT_PRESET_SUGGESTED]:-⭐ Suggested Modules}"
echo "2) ${MODULE_PRESET_LABELS[$DEFAULT_PRESET_PLAYERBOTS]:-🤖 Playerbots + Suggested modules}"
echo "3) ⚙️ Manual selection"
echo "4) 🚫 No modules"
local menu_index=5
declare -A MENU_PRESET_INDEX=()
if [ ${#MODULE_PRESET_ORDER[@]} -gt 0 ]; then
for preset_name in "${MODULE_PRESET_ORDER[@]}"; do
local -a ORDERED_PRESETS=()
for preset_name in "${!MODULE_PRESET_CONFIGS[@]}"; do
if [ "$preset_name" = "$DEFAULT_PRESET_SUGGESTED" ] || [ "$preset_name" = "$DEFAULT_PRESET_PLAYERBOTS" ]; then
continue
fi
local order="${MODULE_PRESET_ORDER[$preset_name]:-10000}"
ORDERED_PRESETS+=("$(printf '%05d::%s' "$order" "$preset_name")")
done
if [ ${#ORDERED_PRESETS[@]} -gt 0 ]; then
IFS=$'\n' ORDERED_PRESETS=($(printf '%s\n' "${ORDERED_PRESETS[@]}" | sort))
fi
for entry in "${ORDERED_PRESETS[@]}"; do
local preset_name="${entry#*::}"
[ -n "${MODULE_PRESET_CONFIGS[$preset_name]:-}" ] || continue
local pretty_name
if [ -n "${MODULE_PRESET_LABELS[$preset_name]:-}" ]; then
pretty_name="${MODULE_PRESET_LABELS[$preset_name]}"
else
pretty_name=$(echo "$preset_name" | tr '_-' ' ' | awk '{for(i=1;i<=NF;i++){$i=toupper(substr($i,1,1)) substr($i,2)}}1')
echo "${menu_index}) 🧩 ${pretty_name} (profiles/${preset_name}.conf)"
fi
echo "${menu_index}) ${pretty_name} (profiles/${preset_name}.json)"
MENU_PRESET_INDEX[$menu_index]="$preset_name"
menu_index=$((menu_index + 1))
done
fi
local max_option=$((menu_index - 1))
if [ "$NON_INTERACTIVE" = "1" ] && [ -z "$MODE_SELECTION" ]; then
@@ -1085,11 +1103,13 @@ fi
if [ "$MODE_SELECTION" = "1" ]; then
MODE_PRESET_NAME="$DEFAULT_PRESET_SUGGESTED"
apply_module_preset "${MODULE_PRESET_CONFIGS[$DEFAULT_PRESET_SUGGESTED]}"
module_mode_label="preset 1 (Suggested)"
local preset_label="${MODULE_PRESET_LABELS[$DEFAULT_PRESET_SUGGESTED]:-Suggested Modules}"
module_mode_label="preset 1 (${preset_label})"
elif [ "$MODE_SELECTION" = "2" ]; then
MODE_PRESET_NAME="$DEFAULT_PRESET_PLAYERBOTS"
apply_module_preset "${MODULE_PRESET_CONFIGS[$DEFAULT_PRESET_PLAYERBOTS]}"
module_mode_label="preset 2 (Playerbots + Suggested)"
local preset_label="${MODULE_PRESET_LABELS[$DEFAULT_PRESET_PLAYERBOTS]:-Playerbots + Suggested}"
module_mode_label="preset 2 (${preset_label})"
elif [ "$MODE_SELECTION" = "3" ]; then
MODE_PRESET_NAME=""
say INFO "Answer y/n for each module (organized by category)"
@@ -1195,7 +1215,8 @@ fi
else
say WARNING "Preset '${MODE_PRESET_NAME}' did not contain any module selections."
fi
module_mode_label="preset (${MODE_PRESET_NAME})"
local preset_label="${MODULE_PRESET_LABELS[$MODE_PRESET_NAME]:-$MODE_PRESET_NAME}"
module_mode_label="preset (${preset_label})"
fi
auto_enable_module_dependencies