diff --git a/deploy/ochenstarik-server-monitor-manager.sh b/deploy/ochenstarik-server-monitor-manager.sh index 7b2a603..d6957af 100755 --- a/deploy/ochenstarik-server-monitor-manager.sh +++ b/deploy/ochenstarik-server-monitor-manager.sh @@ -689,6 +689,31 @@ install_node_from_code() { fi } +refresh_agent_uid() { + local env_file="$ETC_DIR/agent.env" agent_uid temp line found=0 + [[ -f "$env_file" && ! -L "$env_file" ]] \ + || fail "Agent environment is missing or unsafe: $env_file" + agent_uid="$(id -u "$AGENT_USER")" + temp="$(mktemp "$ETC_DIR/.agent.env.XXXXXXXX")" + if ! while IFS= read -r line || [[ -n "$line" ]]; do + if [[ "$line" == SMM_AgentUid=* ]]; then + printf 'SMM_AgentUid=%s\n' "$agent_uid" + found=1 + else + printf '%s\n' "$line" + fi + done <"$env_file" >"$temp"; then + rm -f -- "$temp" + fail "Could not refresh SMM_AgentUid in agent.env." + fi + if [[ "$found" == "0" ]]; then + printf 'SMM_AgentUid=%s\n' "$agent_uid" >>"$temp" + fi + chown root:"$AGENT_USER" "$temp" + chmod 0640 "$temp" + mv -fT -- "$temp" "$env_file" +} + update_role() { local role="$1" archive="$2" binary unit user backup_id require_root @@ -700,6 +725,9 @@ update_role() { *) fail "Unknown role: $role" ;; esac [[ -x "$TEMP_DIR/$role/$binary" ]] || fail "$role binary is missing." + if [[ "$role" == "agent" ]]; then + refresh_agent_uid + fi systemctl stop "$unit" if [[ "$role" == "agent" ]]; then systemctl stop "$PROVISIONING_HELPER_UNIT" 2>/dev/null || true diff --git a/src/ServerMonitorManager.Desktop/MainPage.xaml.cs b/src/ServerMonitorManager.Desktop/MainPage.xaml.cs index a05a31f..d64ab39 100644 --- a/src/ServerMonitorManager.Desktop/MainPage.xaml.cs +++ b/src/ServerMonitorManager.Desktop/MainPage.xaml.cs @@ -58,6 +58,33 @@ public sealed partial class MainPage : Page DeleteServerButton_Click(this, new RoutedEventArgs()); } + internal async Task ConfirmHostKeyFromPageAsync(ServerViewModel? server) + { + if (server is null) + { + ShowInfo("Сервер не выбран", "Выберите профиль для подтверждения host key.", InfoBarSeverity.Warning); + return; + } + + var fingerprint = await ConfirmHostKeyAsync(server.Profile); + if (fingerprint is null) + { + return; + } + + var index = Servers.IndexOf(server); + if (index < 0) + { + return; + } + var confirmed = new ServerViewModel(server.Profile with { HostKeyFingerprint = fingerprint }); + Servers[index] = confirmed; + ServerList.SelectedItem = confirmed; + await SaveProfilesAsync(); + await RefreshServerAsync(confirmed); + ShowInfo("SSH host key подтверждён", confirmed.Name, InfoBarSeverity.Success); + } + internal void OpenTerminalFromPage(ServerViewModel? server) { ServerList.SelectedItem = server; @@ -121,6 +148,18 @@ public sealed partial class MainPage : Page RenderHistory(); } + var pendingNames = Servers + .Where(server => server.HostKeyPendingConfirmation) + .Select(server => server.Name) + .ToArray(); + if (pendingNames.Length > 0) + { + ShowInfo( + "Требуется подтверждение SSH host key", + $"Нажмите «Подтвердить host key» в карточке: {string.Join(", ", pendingNames)}.", + InfoBarSeverity.Warning); + } + if (Servers.Count > 0) { await RefreshAllAsync(); @@ -628,7 +667,16 @@ public sealed partial class MainPage : Page checked((int)portBox.Value), userBox.Text.Trim(), hubBox.IsChecked == true); - var hostKeyFingerprint = await ConfirmHostKeyAsync(updatedProfile); + var knownHostsDirectory = Path.Combine( + ApplicationData.Current.LocalFolder.Path, + "ssh", + "known-hosts"); + var hostKeyFingerprint = SshHostKeyTrust.CanReusePin( + knownHostsDirectory, + selected.Profile, + updatedProfile) + ? selected.Profile.HostKeyFingerprint + : await ConfirmHostKeyAsync(updatedProfile); if (hostKeyFingerprint is null) { return; @@ -698,7 +746,8 @@ public sealed partial class MainPage : Page WarningValueText.Text = warnings.ToString(CultureInfo.InvariantCulture); WarningDetailText.Text = warnings == 0 ? "Нет предупреждений" : "Проверьте доступность и ресурсы"; HeaderStatusText.Text = $"SSH monitoring · {Servers.Count} сервер(а) · обновлено {DateTime.Now:HH:mm:ss}"; - if (Servers.Any(server => server.IsHub) || _control.IsConfigured) + if (Servers.Any(server => server.IsHub && !server.HostKeyPendingConfirmation) + || _control.IsConfigured) { await RefreshMeshAsync(showSuccess: false); } @@ -711,6 +760,14 @@ public sealed partial class MainPage : Page private async Task RefreshServerAsync(ServerViewModel server) { + if (server.HostKeyPendingConfirmation) + { + server.Status = "Требуется подтверждение host key"; + server.IsOnline = false; + server.HasWarning = true; + return; + } + server.Status = "Подключение…"; try { diff --git a/src/ServerMonitorManager.Desktop/Pages/ServersPage.xaml b/src/ServerMonitorManager.Desktop/Pages/ServersPage.xaml index 55ca948..71c688a 100644 --- a/src/ServerMonitorManager.Desktop/Pages/ServersPage.xaml +++ b/src/ServerMonitorManager.Desktop/Pages/ServersPage.xaml @@ -51,6 +51,7 @@ + @@ -67,6 +68,12 @@ +