forked from kay/RouterOS
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
76 lines
3.8 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|