Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f195ff2
Add durable automatic Control enrollment state
BenCodez Aug 29, 2026
1d3a856
Test durable automatic credential generation
BenCodez Aug 29, 2026
c00222f
Prepare hosted Control for verifier enrollment
BenCodez Aug 29, 2026
8d65374
Install local and backend Control verifiers
BenCodez Aug 29, 2026
e09dc1a
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
cf921ad
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
67c7ea5
Add automatic backend Control enrollment
BenCodez Aug 29, 2026
dedc30a
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
e5bf0fd
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
47379aa
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
a400d38
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
f1aa392
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
caeb20c
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
3c900e6
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
3bbdf3a
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
e7da7fe
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
09179b6
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
26fc3a6
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
122d148
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
5d1c378
Automate hosted Control credential enrollment
BenCodez Aug 29, 2026
ecddc3e
Use the backend proxy enable accessor
BenCodez Aug 29, 2026
2f4bce1
Document command-free WebUI setup
BenCodez Aug 29, 2026
9790a0a
Close automatic enrollment lifecycle races
BenCodez Aug 29, 2026
4e0e4e5
Close automatic enrollment lifecycle races
BenCodez Aug 29, 2026
4fa58cb
Close automatic enrollment lifecycle races
BenCodez Aug 29, 2026
58a4263
Close automatic enrollment lifecycle races
BenCodez Aug 29, 2026
3f10767
Confirm backend Control authentication before enrollment
BenCodez Aug 29, 2026
b74b345
Confirm backend Control authentication before enrollment
BenCodez Aug 29, 2026
377ec39
Confirm backend Control authentication before enrollment
BenCodez Aug 29, 2026
383c7da
Confirm backend Control authentication before enrollment
BenCodez Aug 29, 2026
2eced81
Bind enrollment completion to connector route
BenCodez Aug 29, 2026
ceafcd7
Bind enrollment completion to connector route
BenCodez Aug 29, 2026
7cae72f
Bind enrollment completion to connector route
BenCodez Aug 29, 2026
77dacfe
Bind enrollment completion to connector route
BenCodez Aug 29, 2026
a5a1763
Verify hosted routes before backend auto-enrollment
BenCodez Aug 29, 2026
9b692ab
Reconcile local hosted Control before connector
BenCodez Aug 29, 2026
755b70a
Keep hosted Control independent from connector enrollment
BenCodez Aug 29, 2026
dc6388c
Reject loopback routes for proxy backend enrollment
BenCodez Aug 29, 2026
ec15c72
Document proxy backend endpoint requirements
BenCodez Aug 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
import com.bencodez.votingplugin.config.SpecialRewardsConfig;
import com.bencodez.votingplugin.config.VotingPluginConfigHealth;
import com.bencodez.votingplugin.cooldown.CoolDownCheck;
import com.bencodez.votingplugin.control.BackendControlAutoEnrollment;
import com.bencodez.votingplugin.control.BackendControlConnector;
import com.bencodez.votingplugin.data.ServerData;
import com.bencodez.votingplugin.discord.DiscordHandler;
Expand All @@ -96,6 +97,7 @@
import com.bencodez.votingplugin.placeholders.VotingPluginExpansion;
import com.bencodez.votingplugin.presets.VoteSitePresetSetupHandler;
import com.bencodez.votingplugin.proxy.control.HostedControlManager;
import com.bencodez.votingplugin.util.ControlCredentialFile.PendingAutoEnrollment;
import com.bencodez.votingplugin.rewards.VotingPluginRewardRegistrar;
import com.bencodez.votingplugin.servicesites.ServiceSiteHandler;
import com.bencodez.votingplugin.signs.Signs;
Expand Down Expand Up @@ -163,6 +165,7 @@ public class VotingPluginMain extends AdvancedCorePlugin {
private final ProcessedVoteCache backendProcessedVoteCache = new ProcessedVoteCache();
private final AtomicReference<GlobalMessageHandler> backendPluginMessageTarget = new AtomicReference<>();
private PluginMessageHandler backendPluginMessageRelay;
private volatile BackendControlAutoEnrollment backendControlAutoEnrollment;
private volatile BackendControlConnector backendControlConnector;
private final ScheduledExecutorService backendControlConnectorLifecycle = Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(runnable, "votingplugin-control-backend-connector-lifecycle");
Expand Down Expand Up @@ -785,19 +788,31 @@ private void startBackendHostedControl() {
backendHostedControlActiveConfiguration = configuration;
if (!configuration.enabled()) return;
try {
backendHostedControlManager = HostedControlManager.create(getDataFolder().toPath(), configuration,
message -> getLogger().info("[Control Host] " + message));
backendHostedControlManager = createBackendHostedControlManager(configuration);
backendHostedControlActiveManager = backendHostedControlManager;
if (backendHostedControlActiveManager != null) backendHostedControlManagers.add(backendHostedControlActiveManager);
if (backendHostedControlActiveManager != null) backendHostedControlActiveManager.start();
} catch (IllegalArgumentException e) {
} catch (IOException | IllegalArgumentException e) {
backendHostedControlManager = null;
backendHostedControlActiveManager = null;
getLogger().warning("[Control Host] Bukkit hosting configuration is invalid; VotingPlugin remains active: "
+ e.getMessage());
}
}

private HostedControlManager createBackendHostedControlManager(
HostedControlManager.HostConfiguration configuration) throws IOException {
PendingAutoEnrollment enrollment = null;
try {
enrollment = BackendControlAutoEnrollment.prepareLocal(this, configuration);
} catch (IOException | IllegalArgumentException enrollmentFailure) {
getLogger().warning(
"[Control] Automatic local credential enrollment was skipped because its connector settings are invalid");
}
return HostedControlManager.create(getDataFolder().toPath(), configuration, enrollment,
message -> getLogger().info("[Control Host] " + message));
}

private HostedControlManager.HostConfiguration readBackendHostedControlConfiguration() {
ConfigurationSection hosted = getConfigFile().getData().getConfigurationSection("Control.Hosted");
if (hosted == null) {
Expand Down Expand Up @@ -828,9 +843,8 @@ private synchronized void restartBackendHostedControlIfChanged() {
}
HostedControlManager replacement;
try {
replacement = HostedControlManager.create(getDataFolder().toPath(), replacementConfiguration,
message -> getLogger().info("[Control Host] " + message));
} catch (IllegalArgumentException e) {
replacement = createBackendHostedControlManager(replacementConfiguration);
} catch (IOException | IllegalArgumentException e) {
getLogger().warning("[Control Host] Applied Bukkit hosting settings are invalid; the current host is unchanged: "
+ e.getMessage());
return;
Expand Down Expand Up @@ -932,6 +946,7 @@ private void replaceBackendHostedControl(HostedControlManager replacement,
Runnable publication = () -> {
backendHostedControlActiveManager = replacement;
backendHostedControlActiveConfiguration = replacementConfiguration;
restartBackendControlConnector();
};
BackendControlConnector connector = backendControlConnector;
if (connector == null) publication.run();
Expand Down Expand Up @@ -961,8 +976,7 @@ private void restoreBackendHostedControl(HostedControlManager.HostConfiguration
}
HostedControlManager restored = null;
try {
restored = HostedControlManager.create(getDataFolder().toPath(), previousConfiguration,
message -> getLogger().info("[Control Host] " + message));
restored = createBackendHostedControlManager(previousConfiguration);
if (restored != null) backendHostedControlManagers.add(restored);
if (restored != null && !restored.startAndWaitForInitialResult()) {
closeBackendHostedControlManager(restored, true);
Expand Down Expand Up @@ -1033,6 +1047,8 @@ private void stopBackendHostedControlLifecycle() {

private void startBackendControlConnector() {
try {
refreshBackendControlAutoEnrollment();
if (backendControlAutoEnrollment != null && backendControlAutoEnrollment.isAwaitingCredential()) return;
backendControlConnector = BackendControlConnector.create(this);
if (backendControlConnector != null) backendControlConnector.start();
} catch (Exception e) {
Expand All @@ -1041,6 +1057,31 @@ private void startBackendControlConnector() {
}
}

private synchronized void refreshBackendControlAutoEnrollment() throws IOException {
BackendControlAutoEnrollment previous = backendControlAutoEnrollment;
backendControlAutoEnrollment = null;
if (previous != null) previous.close();
BackendControlAutoEnrollment replacement = BackendControlAutoEnrollment.create(this,
getActiveBackendHostedControlConfigurationSnapshot());
backendControlAutoEnrollment = replacement;
if (replacement != null) replacement.start();
}

private synchronized void clearBackendControlAutoEnrollment() {
BackendControlAutoEnrollment previous = backendControlAutoEnrollment;
backendControlAutoEnrollment = null;
if (previous != null) previous.close();
}

/** True while a newly configured local host must be published before its connector is created. */
private boolean backendControlAwaitsHostedPublication() {
HostedControlManager.HostConfiguration configured = readBackendHostedControlConfiguration();
if (configured.equals(backendHostedControlActiveConfiguration)) return false;
ConfigurationSection control = getConfigFile().getData().getConfigurationSection("Control.Backend");
return control != null && control.getBoolean("Enabled", false)
&& HostedControlManager.isDirectLocalEndpoint(control.getString("Endpoint", ""), configured);
}

/** Refreshes the optional Control services after their Config.yml settings were applied. */
public synchronized void restartBackendControlConnector() {
backendControlConnectorReconcileRequested = true;
Expand Down Expand Up @@ -1092,7 +1133,15 @@ private void reconcileBackendControlConnector() {
BackendControlConnector replacement;
boolean creationFailed = false;
try {
replacement = BackendControlConnector.create(this);
if (backendControlAwaitsHostedPublication()) {
clearBackendControlAutoEnrollment();
replacement = null;
} else {
refreshBackendControlAutoEnrollment();
BackendControlAutoEnrollment enrollment = backendControlAutoEnrollment;
replacement = enrollment != null && enrollment.isAwaitingCredential()
? null : BackendControlConnector.create(this);
}
} catch (Exception e) {
replacement = null;
creationFailed = true;
Expand Down Expand Up @@ -1135,6 +1184,9 @@ public synchronized boolean hasDeferredBackendControlReconciliation() {
private void stopBackendControlConnectorLifecycle() {
backendControlConnectorStopping = true;
backendControlConnectorLifecycle.shutdownNow();
BackendControlAutoEnrollment enrollment = backendControlAutoEnrollment;
backendControlAutoEnrollment = null;
if (enrollment != null) enrollment.close();
BackendControlConnector connector = backendControlConnector;
backendControlConnector = null;
if (connector != null) {
Expand Down Expand Up @@ -1167,6 +1219,9 @@ public synchronized void restartBackendProxyHandler() {
if (!bungeeSettings.isUseBungeecoord()) {
backendProxyHandler = null;
if (previous != null) previous.close();
BackendControlAutoEnrollment enrollment = backendControlAutoEnrollment;
backendControlAutoEnrollment = null;
if (enrollment != null) enrollment.close();
return;
}
BungeeMethod replacementMethod = BungeeMethod.getByName(bungeeSettings.getBungeeMethod());
Expand All @@ -1182,6 +1237,11 @@ public synchronized void restartBackendProxyHandler() {
}
backendProxyHandler = replacement;
if (previous != null) previous.close();
try {
refreshBackendControlAutoEnrollment();
} catch (IOException e) {
getLogger().warning("[Control] Automatic backend enrollment was not refreshed: " + e.getMessage());
}
}

/** Keeps one plugin-message listener for the plugin lifetime and atomically swaps its active backend handler. */
Expand All @@ -1203,6 +1263,18 @@ public void deactivateBackendPluginMessageHandler(GlobalMessageHandler target) {
backendPluginMessageTarget.compareAndSet(target, null);
}

/** Handles the non-secret acknowledgement for a pending proxy-mediated enrollment. */
public void handleBackendControlEnrollmentResult(com.bencodez.simpleapi.servercomm.codec.JsonEnvelope envelope) {
BackendControlAutoEnrollment enrollment = backendControlAutoEnrollment;
if (enrollment != null) enrollment.handle(envelope);
}

/** Confirms that the generated credential authenticated to the configured Control endpoint. */
public void backendControlAuthenticated(String nodeId, String credentialFile, String endpoint, String verifier) {
BackendControlAutoEnrollment enrollment = backendControlAutoEnrollment;
if (enrollment != null) enrollment.connectorAuthenticated(nodeId, credentialFile, endpoint, verifier);
}

private void migrateVoteBroadcast(Config configFile) {
ConfigurationSection cfg = configFile.getData();
// If new section exists, do nothing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public void register(GlobalMessageHandler messages, BungeeMethod method) {
messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_DELAY_REJECTED) {
@Override public void onReceive(JsonEnvelope msg) { handleWireVoteDelayRejected(msg); }
});
messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_CONTROL_ENROLLMENT_RESULT) {
@Override public void onReceive(JsonEnvelope msg) { plugin.handleBackendControlEnrollmentResult(msg); }
});

if (method.supportsBackendPresence()) {
messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_PRESENCE_RESYNC_REQUEST) {
Expand Down
Loading
Loading