0, 'path' => '/', 'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off', 'httponly' => true, 'samesite' => 'Strict']); session_name('plugin_site_admin'); session_start(); $cfg = site_config(); $error = null; $message = null; function admin_csrf(): string { return $_SESSION['csrf'] ??= bin2hex(random_bytes(24)); } function admin_check_csrf(): bool { return hash_equals($_SESSION['csrf'] ?? '', (string) ($_POST['csrf'] ?? '')); } /** Höchstens 5 Fehlversuche pro IP in 15 Minuten. */ function admin_throttled(bool $record = false): bool { $file = site_storage() . '/login_attempts.json'; $data = is_file($file) ? (json_decode((string) file_get_contents($file), true) ?: []) : []; $ip = $_SERVER['REMOTE_ADDR'] ?? '?'; $now = time(); $data[$ip] = array_values(array_filter($data[$ip] ?? [], fn($t) => $t > $now - 900)); if ($record) { $data[$ip][] = $now; file_put_contents($file, json_encode($data), LOCK_EX); } return count($data[$ip]) >= 5; } $loggedIn = !empty($_SESSION['admin']); /** PHP verwirft bei zu großem Upload das ganze Formular (kein $_POST, kein Token) – das ist keine Abmeldung. */ $tooBig = $_SERVER['REQUEST_METHOD'] === 'POST' && !$_POST && !$_FILES && (int) ($_SERVER['CONTENT_LENGTH'] ?? 0) > 0; if ($tooBig) { $error = 'Die Datei ist größer als die Servergrenze (post_max_size ' . ini_get('post_max_size') . ', upload_max_filesize ' . ini_get('upload_max_filesize') . '). Bitte die Grenzen erhöhen bzw. das Docker-Image neu bauen. Du bleibst angemeldet.'; } if ($_SERVER['REQUEST_METHOD'] === 'POST' && !$tooBig) { $action = $_POST['action'] ?? ''; if ($action === 'login') { if ($cfg['admin_password_hash'] === '') { $error = 'Die Verwaltung ist nicht eingerichtet: In config.php fehlt admin_password_hash.'; } elseif (admin_throttled()) { $error = 'Zu viele Fehlversuche. Bitte in 15 Minuten erneut versuchen.'; } elseif (hash_equals((string) $cfg['admin_user'], (string) ($_POST['user'] ?? '')) && password_verify((string) ($_POST['password'] ?? ''), (string) $cfg['admin_password_hash'])) { session_regenerate_id(true); $_SESSION['admin'] = true; header('Location: admin.php'); exit; } else { admin_throttled(true); usleep(400000); $error = 'Benutzername oder Passwort ist falsch.'; } } elseif (!$loggedIn || !admin_check_csrf()) { http_response_code(403); $error = 'Ungültige Anfrage. Bitte neu anmelden.'; $loggedIn = false; } elseif ($action === 'logout') { session_destroy(); header('Location: admin.php'); exit; } elseif ($action === 'publish') { $zip = $_FILES['package'] ?? []; if (($zip['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) { $error = 'Upload-Fehler (Code ' . ($zip['error'] ?? '?') . '). Prüft upload_max_filesize und post_max_size.'; } elseif (strtolower(pathinfo((string) $zip['name'], PATHINFO_EXTENSION)) !== 'zip') { $error = 'Bitte eine ZIP-Datei hochladen.'; } else { $prev = $_FILES['preview'] ?? []; $hasPreview = ($prev['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK; $res = site_publish((string) ($_POST['type'] ?? ''), $zip['tmp_name'], $_POST, $hasPreview ? $prev['tmp_name'] : null, $hasPreview ? (string) $prev['name'] : null); if ($res['ok']) $message = "Veröffentlicht: {$res['slug']} {$res['version']} (" . ($res['signed'] ? 'signiert' : 'NICHT signiert, kein Signierschlüssel eingerichtet') . ').'; else $error = $res['error']; } } elseif ($action === 'publish_app') { $upload = $_FILES['app_file'] ?? []; if (($upload['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) { $error = 'App-Upload fehlgeschlagen (Code ' . ($upload['error'] ?? '?') . '). Prüft upload_max_filesize, post_max_size und die Servergrenze.'; } else { $res = site_publish_app((string) ($_POST['platform'] ?? ''), (string) $upload['tmp_name'], trim((string) ($_POST['version'] ?? '')), (string) ($_POST['notes'] ?? '')); if ($res['ok']) $message = 'App-Version veröffentlicht.'; else $error = $res['error']; } } elseif ($action === 'delete_app') { $id = (int) ($_POST['id'] ?? 0); $stmt = site_db()->prepare('SELECT platform,file FROM app_releases WHERE id = ?'); $stmt->execute([$id]); $release = $stmt->fetch(); if ($release) { site_db()->prepare('DELETE FROM app_releases WHERE id = ?')->execute([$id]); @unlink(site_storage() . '/apps/' . $release['platform'] . '/' . basename($release['file'])); $message = 'App-Version entfernt.'; } else $error = 'App-Version wurde nicht gefunden.'; } elseif (in_array($action, ['push_block', 'push_unblock', 'push_delete'], true)) { $pid = (string) ($_POST['id'] ?? ''); if (preg_match('/^[a-f0-9]{24}$/', $pid)) { $pp = push_db(); if ($action === 'push_delete') $pp->prepare('DELETE FROM push_installs WHERE id = ?')->execute([$pid]); else $pp->prepare('UPDATE push_installs SET blocked = ? WHERE id = ?')->execute([$action === 'push_block' ? 1 : 0, $pid]); $message = ['push_block' => 'Installation gesperrt.', 'push_unblock' => 'Installation entsperrt.', 'push_delete' => 'Installation gelöscht (sie meldet sich beim nächsten Senden von selbst neu an).'][$action]; } else { $error = 'Ungültige Installation.'; } } elseif ($action === 'feature' || $action === 'unfeature') { $message = site_set_featured((int) ($_POST['id'] ?? 0), $action === 'feature') ? ($action === 'feature' ? 'Als „Empfohlen“ markiert. DeskHub zeigt es nach der nächsten Katalog-Aktualisierung (spätestens in wenigen Minuten).' : 'Empfehlung entfernt.') : null; $error = $message ? null : 'Eintrag nicht gefunden.'; } elseif ($action === 'delete') { $message = site_delete_version((int) ($_POST['id'] ?? 0)) ? 'Version entfernt.' : null; $error = $message ? null : 'Version nicht gefunden.'; } } $tabs = site_tabs(); $tab = (string) ($_GET['tab'] ?? 'overview'); if (!isset($tabs[$tab])) $tab = 'overview'; $csrf = $loggedIn ? admin_csrf() : ''; /** Kleines POST-Formular mit nur einem Knopf. */ function admin_btn(string $action, array $fields, string $label, string $class = 'link', string $confirm = ''): string { $h = '
'; foreach ($fields as $k => $v) $h .= ''; return $h . '
'; } site_header($loggedIn ? $tabs[$tab][0] . ' · Verwaltung' : 'Anmelden', $tab, $loggedIn); ?>

Anmelden

Verwaltung der DeskHub-Plugins, Themes und Apps.

null, 'windows' => null, 'minecraft' => null]; foreach ($appReleases as $r) if (array_key_exists($r['platform'], $latestApp) && $latestApp[$r['platform']] === null) $latestApp[$r['platform']] = $r; $pushSa = push_service_account(); $pushRows = push_db()->query('SELECT * FROM push_installs ORDER BY COALESCE(last_used_at, created_at) DESC LIMIT 200')->fetchAll(); $plugins = site_items('plugin'); $themes = site_items('theme'); ?>

Übersicht

Alles Wichtige auf einen Blick: veröffentlichte Pakete, aktuelle App-Versionen und der Zustand der Dienste.

Plugins
veröffentlicht
Themes
veröffentlicht
Windows
aktuelle App-Version
Android
aktuelle App-Version
Push-Geräte
angemeldete Installationen

Systemstatus

Einrichtung dieser Webseite

Signierschlüssel
Push-Dienst
Zugangsschlüssel für Katalog und Downloads

Schnellzugriff

Häufige Aufgaben

Plugin oder Theme hochladen App-Version veröffentlichen DeskHub-Einstellungen

Pakete

Plugins und Themes für den DeskHub-Katalog hochladen und verwalten.

Neues Paket veröffentlichen

ZIP-Datei mit plugin.json bzw. Theme-CSS

Nur für Themes (bei Plugins steht alles in der plugin.json)
['Plugins', $plugins], 'theme' => ['Themes', $themes]] as $t => [$label, $items]): ?>

veröffentlicht

Noch keine veröffentlicht.
★ Empfohlen 🌐 () (int) $it['id']], !empty($it['featured']) ? 'Empfehlung entfernen' : 'Als Empfehlung markieren', 'ghost sm') ?>
Downloads signiert' : 'nicht signiert' ?> (int) $v['id']], 'Entfernen', 'link danger', 'Version wirklich entfernen?') ?>

Apps

Windows-, Android-App und Minecraft-Plugin veröffentlichen. Die Apps zeigen ihren Nutzern automatisch „Update verfügbar“, sobald hier eine höhere Version liegt.

Neue App-Version hochladen

Windows-Setups dürfen bis 300 MB groß sein (PHP-Grenzen upload_max_filesize / post_max_size beachten).

'Windows', 'android' => 'Android', 'minecraft' => 'Minecraft'] as $pf => $pl): $mine = array_values(array_filter($appReleases, fn($r) => $r['platform'] === $pf)); $cur = $latestApp[$pf]; ?>

'Setup (EXE)', 'android' => 'Installationsdatei (APK)', 'minecraft' => 'Plugin (JAR)'][$pf] ?>

aktuell ' . site_h($cur['version']) . '' : 'noch nichts veröffentlicht' ?>
Noch keine Version veröffentlicht.
VersionDatumGröße
aktuell' : '' ?> MB (int) $release['id']], 'Entfernen', 'link danger', 'Diese App-Version wirklich entfernen?') ?>

Push-Dienst

Zentraler Push-Dienst der Android-App. Hier siehst du die angemeldeten DeskHub-Installationen.

Angemeldete Installationen

Einträge

Noch keine DeskHub-Installation angemeldet.
NameAngemeldetZuletzt genutztGesendetStatus
gesperrt' : 'aktiv' ?> $p['id']], $blocked ? 'Entsperren' : 'Sperren') ?>   $p['id']], 'Löschen', 'link danger', 'Installation wirklich löschen?') ?>

Einstellungen

Einrichtung für DeskHub und Konto.

Signierschlüsselphp einrichten.php) – ohne Schlüssel ist der geprüfte Modus nicht nutzbar.' ?>

Einstellungen für DeskHub (offizieller Katalog)

Diesen Inhalt in includes/catalog_defaults.php von DeskHub einsetzen, bevor ihr ein Release baut. Dann installieren eure Kunden Plugins nur aus diesem Katalog, und nur signierte.

Konto

Du bist als Administrator angemeldet.