Upload via GUI (24 Dateien)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package de.deskhub.minecraft.bukkit;
|
||||
|
||||
import de.deskhub.minecraft.core.HeartbeatFile;
|
||||
import de.deskhub.minecraft.core.OpenTickets;
|
||||
import de.deskhub.minecraft.core.Settings;
|
||||
import de.deskhub.minecraft.core.Template;
|
||||
import de.deskhub.minecraft.core.TicketClient;
|
||||
@@ -28,6 +29,7 @@ public class BukkitPlugin extends JavaPlugin implements CommandExecutor {
|
||||
private Settings settings;
|
||||
private TicketClient client;
|
||||
private HeartbeatFile heartbeat;
|
||||
private OpenTickets openTickets;
|
||||
private BukkitTask beatTask;
|
||||
|
||||
@Override
|
||||
@@ -37,7 +39,9 @@ public class BukkitPlugin extends JavaPlugin implements CommandExecutor {
|
||||
if (getCommand("deskhubmc") != null) getCommand("deskhubmc").setExecutor(this);
|
||||
heartbeat = new HeartbeatFile(new File(getDataFolder(), "lebenszeichen.properties"));
|
||||
HeartbeatFile.Previous previous = heartbeat.readPrevious();
|
||||
openTickets = new OpenTickets(new File(getDataFolder(), "offene-tickets.properties"));
|
||||
long now = System.currentTimeMillis();
|
||||
resolveOpenOnStart(now);
|
||||
try {
|
||||
heartbeat.start(now);
|
||||
} catch (IOException e) {
|
||||
@@ -70,6 +74,7 @@ public class BukkitPlugin extends JavaPlugin implements CommandExecutor {
|
||||
// Beim Beenden darf gewartet werden: der Server wartet auf das Plugin, solange es nicht länger als das Zeitlimit dauert
|
||||
Map<String, String> v = Template.vars(serverName(), "Plugin auf dem Server", now);
|
||||
TicketResult r = client.create(Template.apply(settings.subjectShutdown, v), Template.apply(settings.messageShutdown, v));
|
||||
if (r.ok) openTickets.put(serverName(), r.number, now);
|
||||
getLogger().info(r.ok ? "Ticket " + r.number + " angelegt (Herunterfahren)." : "Ticket nicht angelegt: " + r.error);
|
||||
}
|
||||
}
|
||||
@@ -104,12 +109,42 @@ public class BukkitPlugin extends JavaPlugin implements CommandExecutor {
|
||||
v.put("lastSeen", previous.lastBeat > 0 ? Template.formatTime(previous.lastBeat) : "unbekannt");
|
||||
v.put("downtime", previous.lastBeat > 0 ? Template.formatDuration(now - previous.lastBeat) : "unbekannt");
|
||||
TicketResult r = client.create(Template.apply(settings.subjectCrash, v), Template.apply(settings.messageCrash, v));
|
||||
if (r.ok) getLogger().info("Ticket " + r.number + " angelegt.");
|
||||
else getLogger().warning("Ticket nicht angelegt: " + r.error);
|
||||
if (r.ok) {
|
||||
getLogger().info("Ticket " + r.number + " angelegt.");
|
||||
// Der Server läuft wieder (sonst würde dieser Code nicht laufen): Das Ausfall-Ticket gleich lösen
|
||||
resolveNow(r.number, previous.lastBeat > 0 ? previous.lastBeat : 0, now);
|
||||
} else getLogger().warning("Ticket nicht angelegt: " + r.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Beim Start: Ein beim letzten Herunterfahren angelegtes Ticket ist erledigt, der Server läuft wieder. */
|
||||
private void resolveOpenOnStart(final long now) {
|
||||
final String number = openTickets.get(serverName());
|
||||
if (number == null || !settings.resolveOnRecovery || !settings.problems().isEmpty()) return;
|
||||
final long since = openTickets.since(serverName());
|
||||
Bukkit.getScheduler().runTaskAsynchronously(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
resolveNow(number, since, now);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Blockierend (nur in einem Hintergrund-Thread aufrufen): Ticket auf Gelöst setzen und aus der Merkliste nehmen. */
|
||||
private void resolveNow(String number, long since, long now) {
|
||||
if (!settings.resolveOnRecovery || !settings.problems().isEmpty()) return;
|
||||
Map<String, String> v = Template.vars(serverName(), "Plugin auf dem Server", now);
|
||||
v.put("downtime", since > 0 ? Template.formatDuration(now - since) : "unbekannt");
|
||||
TicketResult r = client.resolve(number, Template.apply(settings.messageRecovered, v));
|
||||
if (r.ok || r.httpCode == 404) {
|
||||
openTickets.remove(serverName());
|
||||
getLogger().info(r.ok ? "Ticket " + number + " gelöst." : "Ticket " + number + " gibt es in DeskHub nicht mehr, wird nicht weiter verfolgt.");
|
||||
} else {
|
||||
getLogger().warning("Ticket " + number + " konnte nicht gelöst werden: " + r.error + " (bitte von Hand lösen)");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(final CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!sender.hasPermission("deskhubmc.admin")) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.deskhub.minecraft.bungee;
|
||||
|
||||
import de.deskhub.minecraft.core.OpenTickets;
|
||||
import de.deskhub.minecraft.core.OutageTracker;
|
||||
import de.deskhub.minecraft.core.Settings;
|
||||
import de.deskhub.minecraft.core.Template;
|
||||
@@ -36,6 +37,7 @@ public final class BungeePlugin extends Plugin {
|
||||
private Settings settings;
|
||||
private TicketClient client;
|
||||
private OutageTracker tracker;
|
||||
private OpenTickets openTickets;
|
||||
private ScheduledTask watchdog;
|
||||
|
||||
@Override
|
||||
@@ -69,6 +71,7 @@ public final class BungeePlugin extends Plugin {
|
||||
settings = new Settings(new BungeeConfig(new Configuration()));
|
||||
}
|
||||
client = new TicketClient(settings, getDescription().getVersion());
|
||||
openTickets = new OpenTickets(new File(getDataFolder(), "offene-tickets.properties"));
|
||||
tracker = new OutageTracker(settings.monitorFailures, settings.cooldownMinutes, settings.ignoreNeverOnline);
|
||||
for (String w : settings.warnings()) getLogger().warning(w);
|
||||
List<String> problems = settings.problems();
|
||||
@@ -115,6 +118,7 @@ public final class BungeePlugin extends Plugin {
|
||||
OutageTracker.Decision d = tracker.report(server, online, now);
|
||||
if (d == OutageTracker.Decision.RECOVERED) {
|
||||
getLogger().info("Server \"" + server + "\" ist wieder erreichbar.");
|
||||
resolveTicket(server, now);
|
||||
} else if (d == OutageTracker.Decision.OPEN_TICKET) {
|
||||
final long since = tracker.offlineSince(server);
|
||||
getLogger().warning("Server \"" + server + "\" ist nicht erreichbar. Es wird ein Ticket angelegt.");
|
||||
@@ -127,13 +131,37 @@ public final class BungeePlugin extends Plugin {
|
||||
public void run() {
|
||||
Map<String, String> v = Template.vars(server, "BungeeCord-Proxy", since > 0 ? since : now);
|
||||
TicketResult r = client.create(Template.apply(settings.subjectOffline, v), Template.apply(settings.messageOffline, v));
|
||||
if (r.ok) getLogger().info("Ticket " + r.number + " angelegt.");
|
||||
if (r.ok) {
|
||||
openTickets.put(server, r.number, since > 0 ? since : now);
|
||||
getLogger().info("Ticket " + r.number + " angelegt.");
|
||||
}
|
||||
else getLogger().warning("Ticket nicht angelegt: " + r.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Server wieder erreichbar: das beim Ausfall angelegte Ticket in DeskHub auf "Gelöst" setzen. */
|
||||
private void resolveTicket(final String server, final long now) {
|
||||
final String number = openTickets.get(server);
|
||||
if (number == null || !settings.resolveOnRecovery || !settings.problems().isEmpty()) return;
|
||||
final long since = openTickets.since(server);
|
||||
getProxy().getScheduler().runAsync(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Map<String, String> v = Template.vars(server, "BungeeCord-Proxy", now);
|
||||
v.put("downtime", since > 0 ? Template.formatDuration(now - since) : "unbekannt");
|
||||
TicketResult r = client.resolve(number, Template.apply(settings.messageRecovered, v));
|
||||
if (r.ok || r.httpCode == 404) {
|
||||
openTickets.remove(server);
|
||||
getLogger().info(r.ok ? "Ticket " + number + " gelöst." : "Ticket " + number + " gibt es in DeskHub nicht mehr, wird nicht weiter verfolgt.");
|
||||
} else {
|
||||
getLogger().warning("Ticket " + number + " konnte nicht gelöst werden: " + r.error + " (bitte von Hand lösen)");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final class McCommand extends Command {
|
||||
McCommand() {
|
||||
super("deskhubmc", "deskhubmc.admin", "dhmc");
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package de.deskhub.minecraft.core;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Merkt sich je Server die Nummer des Tickets, das für den aktuellen Ausfall angelegt wurde, damit es bei "wieder erreichbar" gelöst
|
||||
* werden kann. Wird in einer kleinen Datei gespeichert, damit ein Neustart des Proxys während des Ausfalls das Ticket nicht vergisst.
|
||||
*/
|
||||
public final class OpenTickets {
|
||||
private final File file;
|
||||
private final Properties props = new Properties();
|
||||
|
||||
public OpenTickets(File file) {
|
||||
this.file = file;
|
||||
if (file.isFile()) {
|
||||
try {
|
||||
InputStream in = new FileInputStream(file);
|
||||
try {
|
||||
props.load(in);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
props.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Ticketnummer des laufenden Ausfalls oder null. */
|
||||
public synchronized String get(String server) {
|
||||
String v = props.getProperty(server);
|
||||
if (v == null) return null;
|
||||
int i = v.indexOf(';');
|
||||
return i < 0 ? v : v.substring(0, i);
|
||||
}
|
||||
|
||||
/** Beginn des Ausfalls in Millisekunden (0 = unbekannt). */
|
||||
public synchronized long since(String server) {
|
||||
String v = props.getProperty(server);
|
||||
int i = v == null ? -1 : v.indexOf(';');
|
||||
if (i < 0) return 0;
|
||||
try {
|
||||
return Long.parseLong(v.substring(i + 1));
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void put(String server, String number, long sinceMillis) {
|
||||
props.setProperty(server, number + ";" + sinceMillis);
|
||||
save();
|
||||
}
|
||||
|
||||
public synchronized void remove(String server) {
|
||||
if (props.remove(server) != null) save();
|
||||
}
|
||||
|
||||
private void save() {
|
||||
try {
|
||||
File dir = file.getParentFile();
|
||||
if (dir != null && !dir.isDirectory() && !dir.mkdirs()) return;
|
||||
OutputStream out = new FileOutputStream(file);
|
||||
try {
|
||||
props.store(out, "DeskHub-Minecraft: offene Ausfall-Tickets (Server = Ticketnummer;Beginn)");
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Speichern ist nachrangig: Das Ticket wird dann nur nach einem Proxy-Neustart nicht automatisch gelöst
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,8 @@ public final class Settings {
|
||||
public final String messageShutdown;
|
||||
public final String subjectTest;
|
||||
public final String messageTest;
|
||||
public final boolean resolveOnRecovery;
|
||||
public final String messageRecovered;
|
||||
|
||||
public final int monitorIntervalSeconds;
|
||||
public final int monitorFailures;
|
||||
@@ -65,6 +67,9 @@ public final class Settings {
|
||||
this.subjectTest = c.getString("ticket.test.subject", "[Minecraft] Testticket von {server}");
|
||||
this.messageTest = c.getString("ticket.test.message", "Das ist ein Testticket des DeskHub-Minecraft-Plugins. Die Verbindung zu DeskHub funktioniert.\n\nServer: {server}\nZeit: {time}");
|
||||
|
||||
this.resolveOnRecovery = c.getBoolean("ticket.resolve-on-recovery", true);
|
||||
this.messageRecovered = c.getString("ticket.recovered.message", "Der Minecraft-Server \"{server}\" ist seit {time} wieder erreichbar (Ausfall: {downtime}). Das Ticket wurde automatisch gelöst.");
|
||||
|
||||
this.monitorIntervalSeconds = clamp(c.getInt("monitor.interval-seconds", 30), 5, 3600);
|
||||
this.monitorFailures = clamp(c.getInt("monitor.failures-before-ticket", 3), 1, 100);
|
||||
this.cooldownMinutes = clamp(c.getInt("monitor.cooldown-minutes", 30), 0, 10080);
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/** Legt über die öffentliche Ticket-API von DeskHub (POST /api/public/v1/tickets.php) ein Ticket an. Blockierend: nie im Haupt-Thread aufrufen. */
|
||||
/** Legt über die öffentliche Ticket-API von DeskHub (POST /api/public/v1/tickets.php) ein Ticket an oder löst es wieder. Blockierend: nie im Haupt-Thread aufrufen. */
|
||||
public final class TicketClient {
|
||||
private static final int MAX_RESPONSE = 65536;
|
||||
private final Settings settings;
|
||||
@@ -32,7 +32,21 @@ public final class TicketClient {
|
||||
return b.append('}').toString();
|
||||
}
|
||||
|
||||
/** Baut den JSON-Körper für "Ticket lösen" (öffentlich für Tests). */
|
||||
public String buildResolveBody(String number, String message) {
|
||||
return "{\"action\":\"resolve\",\"number\":" + Json.quote(number) + ",\"email\":" + Json.quote(settings.requesterEmail) + ",\"message\":" + Json.quote(clip(message, 2000)) + "}";
|
||||
}
|
||||
|
||||
public TicketResult create(String subject, String message) {
|
||||
return send(buildBody(subject, message));
|
||||
}
|
||||
|
||||
/** Setzt ein vom Plugin angelegtes Ticket auf "Gelöst" (Server wieder erreichbar). */
|
||||
public TicketResult resolve(String number, String message) {
|
||||
return send(buildResolveBody(number, message));
|
||||
}
|
||||
|
||||
private TicketResult send(String json) {
|
||||
if (!settings.problems().isEmpty()) {
|
||||
return TicketResult.failure(0, "Einstellungen unvollständig: " + settings.problems().get(0));
|
||||
}
|
||||
@@ -49,7 +63,7 @@ public final class TicketClient {
|
||||
c.setRequestProperty("Accept", "application/json");
|
||||
c.setRequestProperty("X-API-Key", settings.apiKey);
|
||||
c.setRequestProperty("User-Agent", userAgent);
|
||||
byte[] data = buildBody(subject, message).getBytes(StandardCharsets.UTF_8);
|
||||
byte[] data = json.getBytes(StandardCharsets.UTF_8);
|
||||
c.setFixedLengthStreamingMode(data.length);
|
||||
OutputStream out = c.getOutputStream();
|
||||
try {
|
||||
|
||||
@@ -47,6 +47,11 @@ ticket:
|
||||
Ausfalldauer: ca. {downtime}
|
||||
|
||||
Bitte die Logdateien prüfen.
|
||||
# Ist der Server wieder erreichbar (BungeeCord) bzw. neu gestartet (Spigot/Paper), wird das Ticket automatisch auf "Gelöst" gesetzt und bekommt diese Notiz.
|
||||
# Ausschalten mit false (dann bleibt das Ticket offen, bis ihr es selbst löst).
|
||||
resolve-on-recovery: true
|
||||
recovered:
|
||||
message: 'Der Minecraft-Server "{server}" ist seit {time} wieder erreichbar (Ausfall: {downtime}). Das Ticket wurde automatisch gelöst.'
|
||||
shutdown:
|
||||
subject: "[Minecraft] {server} wurde heruntergefahren"
|
||||
message: 'Der Minecraft-Server "{server}" wurde um {time} heruntergefahren.'
|
||||
|
||||
Reference in New Issue
Block a user