UniversalAdmin 0.1.0-alpha.1
Added
-
Anonymous usage statistics (
dev.universaladmin.telemetry):
InstallationIdentity/InstallationIdentityStore(128 bits from
SecureRandom, derived from nothing, persisted in
installation-id.yml),TelemetryPayload(exactly six fields),
TelemetryClientwithHttpTelemetryClient(JDK HTTP client, short
timeouts, no redirects, response discarded) andNoOpTelemetryClient,
TelemetryService(enabled check on every heartbeat, player counts read
on the main thread, the request runs in the background, failures are
swallowed and only warned once per run),TelemetryScheduler(a
delayed start, an interval with jitter, its own daemon thread), and
TelemetryBootstrap(wiring with three outcomes: off / no endpoint /
active). New settingstelemetry.enabled(switchable live),
telemetry.endpoint(empty by default - nothing is sent by
default), andtelemetry.interval. Full documentation including
everything that's never collected:
docs/user/telemetry.md. -
Tests for telemetry with zero real network requests: id
generation/persistence, "disabled means zero requests", a payload
without player identities, serialization, an endpoint failure with no
side effect, jitter bounds, scheduler cleanup. -
LICENSE(Apache-2.0, unmodified official license text) and a
licensing decision with rationale in
docs/release/licensing.md. -
docs/development/architecture-rules.md- the binding architecture,
package, threading, settings, module, security, dependency, and test
rules as regular developer documentation. -
docs/release/releasing.md- a reproducible release process
(version → CHANGELOG → build → commit → tag → push). -
.github/workflows/release.yml- av*tag automatically produces a
GitHub release: a tag-vs-project-version check, a build including
tests, the shaded jar plus a SHA-256 file as assets, prerelease
detection for-alpha/-beta/-rc.contents: write, no additional
secrets. Also callable directly (workflow_call, with ataginput) by
auto-release.ymlbelow, not only by its own tag-push trigger. -
.github/workflows/auto-release.yml- releases automatically after any
push tomainthat changes more than docs/CI metadata: bumps the
-alpha.Ncounter inbuild.gradle.kts, moves theCHANGELOG.md
[Unreleased]section into a dated one, commits, tags, and calls
release.yml. Skips a beta/rc/stable version bump (a bigger decision
than this workflow makes on its own) and its own release commits (no
loop). See docs/release/releasing.md. -
A
printVersionGradle task as the single source of the project
version for CI (the tag check, artifact naming). -
Issue forms (
bug_report.yml,feature_request.yml) and
ISSUE_TEMPLATE/config.ymlpointing at private vulnerability reporting
instead of public security issues. -
Project architecture defined and documented (see ARCHITECTURE.md,
docs/architecture/, ADRs under docs/architecture/decisions/). -
Gradle setup (Java 25 toolchain, Shadow plugin, SQLite/MySQL drivers,
JUnit 5). -
Plugin bootstrap (
UniversalAdminPlugin) with manual dependency wiring. -
Core abstractions:
Module,Action/ActionResult,GuiPage,
Repository/Migration,PermissionRegistry,ServiceRegistry,
AuditService,MessageService,NotificationService,TaskScheduler. -
Eight module skeletons: Players, Moderation, Server, Worlds, Whitelist,
Performance, Audit Log, Settings.Playersas the complete reference
implementation (repository → service → action, including a migration). -
Test infrastructure with example tests (registry, permission
validation, a service with an in-memory fake, migrations against a
real SQLite database). -
Core bootstrap lifecycle (
onLoad/onEnable/onDisable) with a clear
separation between critical core components (config, scheduler,
storage/migrations, shared registries - failures abort the whole
plugin start) and individual modules (failures are isolated, see
ModuleManager). -
Internal module lifecycle system:
ModuleDescriptor(metadata
including dependencies, settings/permission namespace, an optional GUI
icon placeholder),ModuleState
(DISCOVERED/LOADED/ENABLED/DISABLED/FAILED),ModuleRegistry
(state tracking),ModuleManager(dependency ordering, failure
isolation),ModuleResources(automatic release of
listeners/tasks/registry entries on disable). See
docs/architecture/modules.md. -
PluginStatus/ComponentStatus- a live snapshot of version, uptime,
active/failed modules, coarse database/web status. -
/admincommand (aliases/ua,/uadmin) as a status placeholder,
renderingPluginStatus. -
unregister(...)onActionRegistry/GuiRegistry/PermissionRegistry
for module-disable cleanup. -
Tests for the module lifecycle system (
ModuleManagerTest):
registration, a duplicate module id, lifecycle ordering, failure
isolation, disable cleanup, dependency ordering, cycle detection, a
missing dependency. -
Typed settings system (
dev.universaladmin.settings):
SettingKey<T>/SettingType<T>/SettingValidator<T>/SettingDefinition<T>/
SettingValue<T>/SettingRegistry/SettingsService, with built-in
types for string/boolean/int/long/double/Duration/enum/string list,
validators for min/max/regex/multiple-of, and namespacing between core
settings and future module/extension settings. See
docs/development/settings.md and ADR 0007. -
CoreSettings- registers the completeconfig.ymltree (general,
database,gui,audit,modules,performance,maintenance,
web);modules.*now actually controls whether a built-in module
gets registered at all. -
config.ymlversioning:config-version,ConfigMigration/
ConfigMigrationRunner(dev.universaladmin.config) - an existing
user config gets migrated on future schema changes instead of
overwritten. -
A safe
/admin reload(ReloadConfigAction, permission
universaladmin.reload) - only ever reloads UniversalAdmin's own
config.yml, never Bukkit's global/reload. Restart-required
settings are reported as "pending restart" on change instead of
applied live. -
Multi-language message system:
lang/en_US.yml(default/fallback) and
lang/de_DE.yml,YamlLocaleMessageServicewith a fallback chain
(active locale →en_US→ a visible[missing: ...]marker with a
one-time debug log),ComponentMessagesas a MiniMessage renderer for
in-game output, kept separate from plain string resolution (for web
reuse). -
Tests for the settings/localization system: defaults, invalid values
(parse/validation errors → fallback instead of a crash), enum parsing,
duration parsing, duplicate setting keys, locale fallback, parameter
interpolation. -
DatabaseHealth(DISCONNECTED/CONNECTING/READY/FAILED),
tracked byStorageServiceand mapped onto the existing
ComponentStatusinUniversalAdmin#status()- see
docs/architecture/storage.md#health. -
SQLite pragmas in
DataSourceFactory(journal_mode=WAL,
synchronous=NORMAL,foreign_keys=on,busy_timeout=5000). -
Transactions(dev.universaladmin.storage) - a helper for repository
methods that need to run multiple statements atomically (commit/
rollback) over one connection. -
StorageException- a generic unchecked wrapper forSQLExceptionin
the storage foundation itself (StorageService,Transactions). -
docs/user/database.md- user documentation for SQLite vs.
MySQL/MariaDB (when which, creating a MySQL user, behavior on startup
failure, backups). -
Tests:
StorageServiceTest(SQLite init, health states, failure on an
invalid path),TransactionsTest(commit, rollback on error),
JdbcPlayerProfileRepositoryTest(the repository foundation end to
end against a real SQLite database), database-type parsing in
SettingTypesTest, an explicit "no duplicate migration run" assertion
inMigrationRunnerTest. -
A reusable in-game GUI framework (
dev.universaladmin.gui):
AbstractGuiPage/AbstractListGuiPage(navigation, permission-driven
visibility, pagination, async loading with loading/empty/error state),
GuiSession/GuiSessionManager(per-player state with no held
Playerreferences, cleanup on disconnect/a real inventory close),
GuiListeneras the single, centrally registered click/drag handler,
GuiLayout/Paginationas pure, tested slot/pagination logic,
IconProvider/MaterialIconProvideras the central material
resolution,ConfirmationDialog(danger level NORMAL/WARNING/
DANGEROUS),SelectionDialog,GuiTextInput(a free-text/search flow
via the Paper dialog API instead of packet hacks/anvil/sign) - see
docs/development/gui-framework.md. -
MainMenuPage: the/adminmain menu with one button per built-in
module, filtered by actualModuleState.ENABLEDand permission;
PlaceholderGuiPageas the "not built yet" target page per module. A
new permissionuniversaladmin.menu.open. -
ModuleDescriptor.icon()set for all eight built-in modules
(previouslynulleverywhere, seeGuiIcon). -
Tests for the GUI framework:
PaginationTest,GuiLayoutTest,
GuiSessionTest,GuiSessionManagerTest,GuiButtonVisibilityTest,
GuiClickContextTest(the navigation stack),GuiListenerTest
(click cancel/dispatch, close/quit cleanup). -
GuiView#onChange- a live-sync hookGuiListenerruns one tick after a
click/drag it let through, on top of the existingonClose. Used by the
Players module's inventory/ender chest editors and the new Staff-Mode
Ender Chest Inspector tool so an edit mirrors onto the real target
immediately instead of only when the viewer closes the GUI. -
Staff-Mode Ender Chest Inspector tool (
EnderChestInspectorPage),
alongside a now-live (not read-only) Inventory Inspector - both gated on
the Players module's own...inventory.edit/...enderchest.edit
permissions. See docs/user/modules/staff-tools.md. -
Staff-Mode Teleport Picker tool: opens a player list, then a
"teleport to them"/"bring them" choice, routed through the Players
module's own auditedTeleportPlayerAction- replaces Random Teleport. -
Staff-Mode target tracking now searches by look direction within a
configurable range (moderation.staffmode.target-range-blocks, default
40) instead of a 6-block, wall-blocked ray trace, and sends a persistent
actionbar naming the current target. The Player Inspector tool's head
always shows the current target now, regardless of which tool is held. -
Per-action success messages in the Moderation GUI (
ModerationGuiActions)- naming the target, reason, and duration where relevant - instead of a
single generic "Done." for every action.
- naming the target, reason, and duration where relevant - instead of a
-
Audit log GUI filtering by actor, module, and time range
(AuditLogFilterPage), on top of the existing success/failure toggle -
the query layer already supported all of this, only the GUI was missing.
See docs/user/audit-log.md.
Changed
-
The binding development rules now live under
docs/development/architecture-rules.md; every reference in source
code and documentation points there. -
README.mdbrought to a public-facing state (status, features,
requirements, quick start, database, permissions, anonymous
statistics, extensions/web app explicitly marked as planned, license). -
CONTRIBUTING.mdandSECURITY.mdreworked: setup, architecture,
tests, style, PR flow and private vulnerability reporting respectively,
a telemetry/privacy section, and supported versions. -
.github/workflows/build.ymlruns onpush/pull_requestagainst
main, sets up JDK 25 (build toolchain) and 21 (runs Gradle), uploads
the jar under a versioned artifact name. -
The PR template gained checklist items for secrets, localization,
threading, and telemetry documentation. -
.gitignoregained build/IDE/runtime/database/secret patterns as well
as purely local notes files. -
Target Paper version set to 26.2 (
paper-api:26.2.build.115-stable).
Paper 26.2 is only published for JVM 25+, so the Java toolchain was
raised from the originally planned version 21 to 25 - see
build.gradle.ktsandplugin.yml(api-version: '26.2'). -
Moduleis now two-phase (onLoadthenonEnable) instead of just
onEnable;id()/displayName()are replaced bydescriptor()
(ModuleDescriptor). All eight built-in modules updated. -
UniversalAdminCommandnow shows aPluginStatusreport instead of
just the list of loaded modules, and is registered under/admin
instead of/ua. -
ConfigService/YamlConfigServiceremoved, replaced by
SettingsService/YamlSettingsService(see above).
UniversalAdmin.config()is nowUniversalAdmin.settings()
(SettingsService) plusUniversalAdmin.settingRegistry()
(SettingRegistry). -
messages_en_US.yml(a file at the plugin root) replaced by
lang/en_US.yml/lang/de_DE.yml;YamlMessageService(one fixed
locale) replaced byYamlLocaleMessageService(multiple locales, a
fallback chain, reads the active locale live fromgeneral.language). -
/admin(no argument) now opensMainMenuPagefor a player instead
of the text status report; for console/command blocks, the status
report is unchanged./admin reloadunchanged.UniversalAdmin's
constructor gained a new required field,GuiFramework
(UniversalAdmin#guiFramework()). -
Players complete: a player browser GUI (online/offline/last-seen/
search), a profile page, ~20 actions (teleport, heal, effects,
gamemode, inventory/ender chest editor), fine-grained permissions,
audit. See docs/user/modules/players.md. -
Moderation complete: a punishment repository/service (kick/ban/
tempban/ipban/mute/tempmute/warn/freeze/unban/unmute/removewarn/
unfreeze), join/chat enforcement, a GUI wizard, fine-grained
permissions, audit, plus vanish/godmode/no-collision/staff mode
(crash-safe snapshot/recovery,/admin staff recover). See
docs/user/modules/moderation.md and
docs/user/modules/staff-tools.md. -
Server complete: a live dashboard (version/uptime/players/memory/
CPU/DB status/modules), broadcast (message/title/actionbar), its own
maintenance-mode system (repository/service, join enforcement,
allow-list), shutdown/restart with dangerous-action confirmation and a
configurable countdown + cancel,/admin server ..., fine-grained
permissions, audit. See docs/user/modules/server.md. -
Worlds complete: a world browser/profile (environment/seed/spawn/
border/players/chunks/entities/time/weather/difficulty),
teleport/spawn/time/weather/difficulty actions, world border
management, a dynamic gamerule GUI (readsWorld#getGameRules()at
runtime), fine-grained permissions (seed kept separate), audit. See
docs/user/modules/worlds.md. -
Whitelist complete: native whitelist wrapping (enable/disable/
list/add/remove) plus its own metadata (added-by/at, reason, notes,
expiration), time-limited entries with a join check and an hourly
sweep, a strict ownership model, fine-grained permissions, audit. See
docs/user/modules/whitelist.md. -
Audit Log complete (GUI side): a full
AuditEvent(actor/action/
module/target/source/success/reason/old-new value/world position/
metadata/correlation id), automatically populated byActionExecutor,
a filtered, paginatedAuditService#query, a working GUI (a list with
a success/failure filter, a detail page), hourly configurable
retention./admin auditcommands still open. See
docs/user/audit-log.md. -
Performance complete: cached TPS/MSPT/memory/world/entity sampling
on a configurable interval (never recomputed per GUI render), a
dashboard, a per-world performance view, an entity overview (grouped
by type/world), a short in-memory history, staff alerts on TPS/MSPT/
memory thresholds, a narrowly scoped Entity Clear (never players,
configurable protected types, preview, confirmation, audit),
fine-grained permissions. See
docs/user/modules/performance.md.
Fixed
- Critical: every module-owned table was never created
(player_profiles,punishments,server_maintenance_state,
whitelist_entries,vanish_state, ...) -UniversalAdminPlugin
only calledstorage.migrations().runPending()once, before any
module was even enabled. But every module only registers its own
migration(s) during its ownonEnable- without a second
runPending()call afterward, those migrations never ran. Affected
every module except Audit Log (whose two migrations were registered
before that one, single call) - among other things, the cause of
"storage error" in the Moderation/punishment module, "unexpected
error" in maintenance mode, and broken whitelist/vanish state on join.
See docs/architecture/threading.md. worlds.gui.actionexisted twice as a sibling key in
lang/en_US.yml/de_DE.yml(once for success/error messages, once
for button labels) - YAML doesn't really allow that, Bukkit's loader
silently takes the last entry (a "duplicate keys found" warning at
startup). Button labels renamed toworlds.gui.buttons.- Player lists (Players, Whitelist, audit log detail) showed the generic
Steve head everywhere instead of the real player skin -GuiItem
gained a newplayerHead(OfflinePlayer, ...)factory method
(SkullMeta#setOwningPlayer), now used by every player-representing
tile. - Critical: the plugin didn't start at all on a real Paper server
(HikariConfig: Failed to load driver class dev.universaladmin.libs.sqlite.JDBC).
Two interacting Shadow-plugin issues, neither detectable by the test
suite (which runs against unshaded, unminimized dependencies) - only
surfaced by an actual server start:shadowJar { minimize() }stripped both JDBC drivers from the jar
entirely, because they're only loaded reflectively via
HikariConfig#setDriverClassName- invisible tominimize()'s
static-reachability analysis. Both drivers are now explicitly
excluded from minimization.org.sqlitewas being relocated like any other dependency - which
breaks the JNI linking of the bundled native SQLite library
(ClassNotFoundException: org/sqlite/core/NativeDB), since its
compiled binary hardcodes the original class name.org.sqliteis
now deliberately no longer relocated (but still ships bundled) - see
docs/development/architecture-rules.md
andbuild.gradle.kts.- A new Gradle task,
verifyShadedJarDrivers(runs as part of
check/build): opens a real SQLite connection exclusively through
the finished, built jar (no other classpath entry), so this exact
class of failure can never come back unnoticed again. See
ShadedJarDriverSmokeTestMain.
PlayerProfileIndexMigration/ModerationPunishmentIndexMigration
usedCREATE INDEX IF NOT EXISTS, which real MySQL (unlike
SQLite/MariaDB) doesn't support - removed, sinceMigrationRunner
already guarantees every migration runs at most once. See
docs/architecture/storage.md#dialect-differences.- Freezing a player sent the target two notices for one click of the
Freeze Tool -PlayerInteractEntityEventfires once per hand for a
single physical right-click, andStaffModeGuardListenerdispatched on
both. Now only the main-hand firing is handled. core:performance.clear-entitiesfailed to record its audit event on
every run (IllegalArgumentException: Unsupported audit metadata value type ... ListN) - its metadata put the selected entity types in
directly as aList, which the audit metadata codec only ever accepted
String/Number/Boolean/null for. Now joined into a single string, same as
the value already shown in the audit summary.
Full Changelog: https://github.com/nicki41/UniversalAdmin/commits/v0.1.0-alpha.1