Files
RouterOS/RouterOSAssistant/Core/Services/UpdateService.swift
T
KayandClaude Sonnet 5 e72da84843 M14: Update-Check (Software+Firmware) + Karten-Design fuer Sektionen
Verbinden-Tab zeigt jetzt die volle Routerboard-Info (Modell/Revision/
Seriennummer/Firmware, aus /system routerboard) sowie einen
RouterOS-Software-Update-Check (/system package update: Kanal/
installierte/neueste Version/Status, "Jetzt pruefen"/"Update
installieren"), dazu "Firmware aktualisieren" fuer die
Routerboard-Bootloader-Firmware und "Jetzt neu starten" danach. Alle
vier Aktionen live bestaetigt, inklusive der zuvor unsicheren Frage, ob
/system routerboard upgrade's normalerweise interaktive Bestaetigung
den nicht-interaktiven SSH-Weg dieser App blockiert (tut es nicht).

Bug 20 gefunden und gefixt: fetchMenuItems' Singleton-Fallback (Bug 8)
reagierte nur auf eine geworfene Exception fuer "bad parameter terse",
aber RouterOS liefert diesen Fehler fuer /system routerboard mit
Exit-Code 0 zurueck (dasselbe Bug-10-Muster, diesmal beim Lesen statt
Schreiben) - die Routerboard-Sektion blieb dadurch leer, ohne Fehler.
Fix: zusaetzlich den Output-Text selbst pruefen, nicht nur die Exception.

Design-Durchgang: Verbinden-Detailseite/Sicherungen/Geraete liefen auf
nackter List ohne Rahmen - umgestellt auf Form+.formStyle(.grouped),
denselben nativen macOS-Karten-Look, den Wizard und Experte-Tab schon
hatten, fuer eine einheitliche App. Dark Mode auf Nachfrage gepueft und
ohne Codeaenderung bestaetigt funktionierend.

HANDOFF.md/CHATLOG.md aktualisiert: M14, Bug 20, Design-Durchgang,
Dark-Mode-Bestaetigung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTgRxJTzaQwaRkngbaE1GJ
2026-09-14 16:34:50 +02:00

76 lines
3.8 KiB
Swift

import Foundation
/// RouterOS software updates (`/system package update`) and RouterBOARD bootloader firmware
/// updates (`/system routerboard upgrade`) — two independent MikroTik update mechanisms
/// (confirmed via docs): package updates are the RouterOS operating system version itself;
/// routerboard firmware is the bootloader, which only has a newer version available after a
/// newer RouterOS package has already been installed (the firmware file ships bundled inside
/// RouterOS packages, not distributed on its own). Always runs over a dedicated SSH connection,
/// same reasoning as BackupService/FactoryResetService: these are one-shot system actions, not
/// menu items to add/set.
final class UpdateService {
/// Triggers a fresh check, waits (matching MikroTik's own documented scripting example's
/// timing: "check-for-updates once; :delay 3s"), then reads the result via the generic
/// menu-item engine — no polling loop, since RouterOS doesn't document a closed set of
/// intermediate "still checking" states to poll against; this mirrors the doc's own example
/// exactly rather than guessing at retry logic.
func checkForUpdates(for credentials: RouterOSCredentials) async throws -> PackageUpdateInfo {
let transport = SSHTransport(credentials: credentials)
try await transport.connect()
do {
try await transport.checkForPackageUpdates()
try await Task.sleep(nanoseconds: 3_000_000_000)
let items = try await transport.fetchMenuItems(menuPath: "/system package update", restPath: "system/package/update")
await transport.disconnect()
guard let fields = items.first?.fields else {
throw RouterOSError.invalidResponse("system/package/update")
}
return PackageUpdateInfo(
channel: fields["channel"] ?? "unbekannt",
installedVersion: fields["installed-version"] ?? "unbekannt",
latestVersion: fields["latest-version"] ?? "unbekannt",
status: fields["status"] ?? "unbekannt"
)
} catch {
await transport.disconnect()
throw error
}
}
/// Reboots immediately — needed after `upgradeRouterboardFirmware` (not automatic, confirmed
/// via docs) or anytime else a manual restart is wanted.
func reboot(for credentials: RouterOSCredentials) async throws {
let transport = SSHTransport(credentials: credentials)
try await transport.connect()
try await transport.reboot()
await transport.disconnect()
}
/// Downloads, installs, and (confirmed via docs) automatically reboots.
func installUpdate(for credentials: RouterOSCredentials) async throws {
let transport = SSHTransport(credentials: credentials)
try await transport.connect()
// The router may reboot as part of this command; the connection dying mid-command
// instead of returning cleanly is an expected outcome here, not a failure.
try? await transport.installPackageUpdate()
await transport.disconnect()
}
/// Applies the RouterBOARD bootloader firmware bundled in the currently installed RouterOS
/// package. Returns RouterOS' own confirmation text — see
/// `SSHTransport.upgradeRouterboardFirmware`'s doc comment for what's confirmed vs. not about
/// this command's behavior over non-interactive SSH.
func upgradeRouterboardFirmware(for credentials: RouterOSCredentials) async throws -> String {
let transport = SSHTransport(credentials: credentials)
try await transport.connect()
do {
let output = try await transport.upgradeRouterboardFirmware()
await transport.disconnect()
return output
} catch {
await transport.disconnect()
throw error
}
}
}