v0.10.0: Docker + Update-Funktion + deploy.sh
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class AppCheckUpdateCommand extends Command
|
||||
{
|
||||
protected $signature = 'app:check-update {--json : Ausgabe als JSON}';
|
||||
protected $description = 'Prüft ob ein Update auf Gitea verfügbar ist und speichert das Ergebnis im Cache';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$current = config('version.current');
|
||||
$giteaUrl = rtrim(config('version.gitea_url'), '/');
|
||||
$repo = config('version.gitea_repo');
|
||||
|
||||
if (empty($giteaUrl)) {
|
||||
$this->warn('GITEA_URL ist nicht konfiguriert.');
|
||||
Cache::put('app_update_available', null, now()->addHours(1));
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::timeout(8)
|
||||
->get("{$giteaUrl}/api/v1/repos/{$repo}/releases/latest");
|
||||
|
||||
if (!$response->successful()) {
|
||||
$this->warn("Gitea nicht erreichbar (HTTP {$response->status()}).");
|
||||
Cache::put('app_update_check_error', "HTTP {$response->status()}", now()->addHours(1));
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$latest = $response->json('tag_name', '');
|
||||
$available = !empty($latest) && version_compare(
|
||||
ltrim($latest, 'v'),
|
||||
ltrim($current, 'v'),
|
||||
'>'
|
||||
);
|
||||
|
||||
// Ergebnis 6 Stunden cachen
|
||||
Cache::put('app_update_available', $available ? $latest : false, now()->addHours(6));
|
||||
Cache::put('app_update_checked_at', now()->toDateTimeString(), now()->addHours(6));
|
||||
Cache::forget('app_update_check_error');
|
||||
|
||||
if ($this->option('json')) {
|
||||
$this->line(json_encode([
|
||||
'current' => $current,
|
||||
'latest' => $latest,
|
||||
'available' => $available,
|
||||
]));
|
||||
} else {
|
||||
if ($available) {
|
||||
$this->info("✓ Update verfügbar: {$current} → {$latest}");
|
||||
} else {
|
||||
$this->info("✓ Kein Update — aktuelle Version: {$current}");
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error('Verbindungsfehler: ' . $e->getMessage());
|
||||
Cache::put('app_update_check_error', $e->getMessage(), now()->addHours(1));
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class AppInstallUpdateCommand extends Command
|
||||
{
|
||||
protected $signature = 'app:install-update {--tag= : Bestimmten Git-Tag installieren}';
|
||||
protected $description = 'Installiert das neueste Update vom Gitea-Repository';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$projectPath = base_path();
|
||||
|
||||
$this->info('');
|
||||
$this->info('╔══════════════════════════════════════╗');
|
||||
$this->info('║ Network-MGMT Update ║');
|
||||
$this->info('╚══════════════════════════════════════╝');
|
||||
$this->info('');
|
||||
|
||||
// ── 1. Tags holen ──────────────────────────────────────────────────────
|
||||
$this->info('▶ Git: Aktuelle Tags holen ...');
|
||||
$this->exec("cd {$projectPath} && git fetch --tags 2>&1");
|
||||
|
||||
// ── 2. Ziel-Tag bestimmen ──────────────────────────────────────────────
|
||||
if ($this->option('tag')) {
|
||||
$targetTag = $this->option('tag');
|
||||
} else {
|
||||
$targetTag = trim(shell_exec(
|
||||
"cd {$projectPath} && git describe --tags \$(git rev-list --tags --max-count=1) 2>/dev/null"
|
||||
));
|
||||
}
|
||||
|
||||
if (empty($targetTag)) {
|
||||
$this->error('Kein Release-Tag im Repository gefunden.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$currentVersion = config('version.current');
|
||||
$this->info(" Aktuell: {$currentVersion}");
|
||||
$this->info(" Ziel: {$targetTag}");
|
||||
$this->info('');
|
||||
|
||||
if ($targetTag === $currentVersion) {
|
||||
$this->warn('Bereits auf der neuesten Version. Abbruch.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
// ── 3. Wartungsmodus ───────────────────────────────────────────────────
|
||||
$this->info('▶ Wartungsmodus aktivieren ...');
|
||||
Artisan::call('down', ['--render' => 'errors.maintenance']);
|
||||
|
||||
try {
|
||||
// ── 4. Git Checkout ────────────────────────────────────────────────
|
||||
$this->info("▶ Git: Checkout {$targetTag} ...");
|
||||
$this->exec("cd {$projectPath} && git checkout {$targetTag} 2>&1");
|
||||
|
||||
// ── 5. Composer ────────────────────────────────────────────────────
|
||||
$this->info('▶ Composer: Abhängigkeiten aktualisieren ...');
|
||||
$this->exec("cd {$projectPath} && composer install --no-dev --optimize-autoloader --no-interaction 2>&1");
|
||||
|
||||
// ── 6. Assets ──────────────────────────────────────────────────────
|
||||
if (file_exists("{$projectPath}/package.json")) {
|
||||
$this->info('▶ Node: Assets neu bauen ...');
|
||||
$this->exec("cd {$projectPath} && npm ci --silent 2>&1");
|
||||
$this->exec("cd {$projectPath} && npm run build 2>&1");
|
||||
}
|
||||
|
||||
// ── 7. Migrationen ─────────────────────────────────────────────────
|
||||
$this->info('▶ Datenbank: Migrationen ausführen ...');
|
||||
Artisan::call('migrate', ['--force' => true, '--no-interaction' => true]);
|
||||
$this->line(Artisan::output());
|
||||
|
||||
// ── 8. Cache leeren und neu aufbauen ───────────────────────────────
|
||||
$this->info('▶ Cache: Neu aufbauen ...');
|
||||
Artisan::call('config:cache');
|
||||
Artisan::call('route:cache');
|
||||
Artisan::call('view:cache');
|
||||
|
||||
// ── 9. Update-Cache zurücksetzen ───────────────────────────────────
|
||||
Cache::forget('app_update_available');
|
||||
Cache::forget('app_update_checked_at');
|
||||
|
||||
$this->info('');
|
||||
$this->info("✓ Update auf {$targetTag} erfolgreich abgeschlossen!");
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->error('Update fehlgeschlagen: ' . $e->getMessage());
|
||||
Artisan::call('up');
|
||||
return Command::FAILURE;
|
||||
|
||||
} finally {
|
||||
// ── 10. Wartungsmodus deaktivieren ─────────────────────────────────
|
||||
$this->info('▶ Wartungsmodus deaktivieren ...');
|
||||
Artisan::call('up');
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function exec(string $cmd): void
|
||||
{
|
||||
$output = shell_exec($cmd);
|
||||
if ($output) {
|
||||
foreach (explode("\n", trim($output)) as $line) {
|
||||
if (trim($line)) {
|
||||
$this->line(" {$line}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\NetworkDevice;
|
||||
use App\Models\NetworkHost;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class NetworkBackfillDevicesCommand extends Command
|
||||
{
|
||||
protected $signature = 'network:backfill-devices
|
||||
{--dry-run : Nur anzeigen, was gemacht würde, ohne zu schreiben}';
|
||||
|
||||
protected $description = 'Erstellt NetworkDevice-Einträge für bestehende network_hosts ohne device_id';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$dryRun = $this->option('dry-run');
|
||||
|
||||
// Alle Hosts ohne device_id, neueste zuerst (für korrekte last_seen_at)
|
||||
$hosts = NetworkHost::query()
|
||||
->join('network_scans', 'network_scans.id', '=', 'network_hosts.scan_id')
|
||||
->whereNull('network_hosts.device_id')
|
||||
->select([
|
||||
'network_hosts.*',
|
||||
'network_scans.created_at as scan_time',
|
||||
])
|
||||
->orderByDesc('network_scans.created_at')
|
||||
->get();
|
||||
|
||||
$this->info("Gefunden: {$hosts->count()} Hosts ohne device_id");
|
||||
|
||||
if ($hosts->isEmpty()) {
|
||||
$this->info('Nichts zu tun.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$created = 0;
|
||||
$linked = 0;
|
||||
|
||||
foreach ($hosts as $host) {
|
||||
$device = null;
|
||||
|
||||
// 1. Per MAC suchen/erstellen
|
||||
if (!empty($host->mac_address)) {
|
||||
$device = NetworkDevice::where('mac_address', $host->mac_address)->first();
|
||||
|
||||
if (!$device) {
|
||||
if (!$dryRun) {
|
||||
$device = NetworkDevice::create([
|
||||
'mac_address' => $host->mac_address,
|
||||
'current_ip' => $host->ip_address,
|
||||
'hostname' => $host->hostname,
|
||||
'mac_vendor' => $host->mac_vendor,
|
||||
'status' => $host->status,
|
||||
'first_seen_at' => $host->scan_time,
|
||||
'last_seen_at' => $host->scan_time,
|
||||
]);
|
||||
$created++;
|
||||
}
|
||||
$this->line(" + Gerät (MAC) {$host->mac_address} / {$host->ip_address}");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Per Hostname suchen/erstellen (kein MAC)
|
||||
elseif (!empty($host->hostname)) {
|
||||
$device = NetworkDevice::whereNull('mac_address')
|
||||
->where('hostname', $host->hostname)
|
||||
->first();
|
||||
|
||||
if (!$device) {
|
||||
if (!$dryRun) {
|
||||
$device = NetworkDevice::create([
|
||||
'mac_address' => null,
|
||||
'current_ip' => $host->ip_address,
|
||||
'hostname' => $host->hostname,
|
||||
'mac_vendor' => null,
|
||||
'status' => $host->status,
|
||||
'first_seen_at' => $host->scan_time,
|
||||
'last_seen_at' => $host->scan_time,
|
||||
]);
|
||||
$created++;
|
||||
}
|
||||
$this->line(" + Gerät (Hostname) {$host->hostname} / {$host->ip_address}");
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Per IP suchen/erstellen (weder MAC noch Hostname)
|
||||
else {
|
||||
$device = NetworkDevice::whereNull('mac_address')
|
||||
->where('current_ip', $host->ip_address)
|
||||
->first();
|
||||
|
||||
if (!$device) {
|
||||
if (!$dryRun) {
|
||||
$device = NetworkDevice::create([
|
||||
'mac_address' => null,
|
||||
'current_ip' => $host->ip_address,
|
||||
'hostname' => null,
|
||||
'mac_vendor' => null,
|
||||
'status' => $host->status,
|
||||
'first_seen_at' => $host->scan_time,
|
||||
'last_seen_at' => $host->scan_time,
|
||||
]);
|
||||
$created++;
|
||||
}
|
||||
$this->line(" + Gerät (IP) {$host->ip_address}");
|
||||
}
|
||||
}
|
||||
|
||||
// Host mit Gerät verknüpfen
|
||||
if ($device && !$dryRun) {
|
||||
$host->update(['device_id' => $device->id]);
|
||||
|
||||
// Älteste first_seen_at beibehalten
|
||||
if ($host->scan_time < $device->first_seen_at) {
|
||||
$device->update(['first_seen_at' => $host->scan_time]);
|
||||
}
|
||||
|
||||
$linked++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
$this->warn('Dry-Run – keine Änderungen geschrieben.');
|
||||
} else {
|
||||
$this->info("✓ Fertig: {$created} Geräte neu erstellt, {$linked} Hosts verknüpft.");
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user