From 0ac9f29ee07a6201e41408c19f7996d4baa5cb71 Mon Sep 17 00:00:00 2001 From: litoral05 Date: Wed, 6 May 2026 11:16:12 +0100 Subject: [PATCH] Added health endpoint --- .../openvpn/health/HealthController.java | 21 ++++ .../openvpn/health/OpenVpnHealthResponse.java | 9 ++ .../openvpn/health/OpenVpnHealthService.java | 114 ++++++++++++++++++ .../openvpn/health/OpenVpnServerHealth.java | 11 ++ 4 files changed, 155 insertions(+) create mode 100644 src/main/java/com/litoralregas/openvpn/health/HealthController.java create mode 100644 src/main/java/com/litoralregas/openvpn/health/OpenVpnHealthResponse.java create mode 100644 src/main/java/com/litoralregas/openvpn/health/OpenVpnHealthService.java create mode 100644 src/main/java/com/litoralregas/openvpn/health/OpenVpnServerHealth.java diff --git a/src/main/java/com/litoralregas/openvpn/health/HealthController.java b/src/main/java/com/litoralregas/openvpn/health/HealthController.java new file mode 100644 index 0000000..df04b64 --- /dev/null +++ b/src/main/java/com/litoralregas/openvpn/health/HealthController.java @@ -0,0 +1,21 @@ +package com.litoralregas.openvpn.health; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/health") +public class HealthController { + + private final OpenVpnHealthService openVpnHealthService; + + public HealthController(OpenVpnHealthService openVpnHealthService) { + this.openVpnHealthService = openVpnHealthService; + } + + @GetMapping("/openvpn") + public OpenVpnHealthResponse getOpenVpnHealth() { + return openVpnHealthService.checkHealth(); + } +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/openvpn/health/OpenVpnHealthResponse.java b/src/main/java/com/litoralregas/openvpn/health/OpenVpnHealthResponse.java new file mode 100644 index 0000000..fef69f3 --- /dev/null +++ b/src/main/java/com/litoralregas/openvpn/health/OpenVpnHealthResponse.java @@ -0,0 +1,9 @@ +package com.litoralregas.openvpn.health; + +import java.util.List; + +public record OpenVpnHealthResponse( + String status, + List servers +) { +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/openvpn/health/OpenVpnHealthService.java b/src/main/java/com/litoralregas/openvpn/health/OpenVpnHealthService.java new file mode 100644 index 0000000..91e224b --- /dev/null +++ b/src/main/java/com/litoralregas/openvpn/health/OpenVpnHealthService.java @@ -0,0 +1,114 @@ +package com.litoralregas.openvpn.health; + +import com.litoralregas.openvpn.ssh.SshCommandResult; +import com.litoralregas.openvpn.ssh.SshService; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.List; + +@Service +public class OpenVpnHealthService { + + private final SshService sshService; + + public OpenVpnHealthService(SshService sshService) { + this.sshService = sshService; + } + + public OpenVpnHealthResponse checkHealth() { + OpenVpnServerHealth serverHealth = checkConfiguredVps(); + + String overallStatus; + + if (!serverHealth.reachable()) { + overallStatus = "OFFLINE"; + } else if (!serverHealth.openvpnServiceActive()) { + overallStatus = "DEGRADED"; + } else { + overallStatus = "ONLINE"; + } + + return new OpenVpnHealthResponse( + overallStatus, + List.of(serverHealth) + ); + } + + private OpenVpnServerHealth checkConfiguredVps() { + String checkedAt = Instant.now().toString(); + + SshCommandResult serviceResult = sshService.executeOnConfiguredVps( + """ + if systemctl is-active --quiet openvpn-server@server; then + echo ACTIVE + elif systemctl is-active --quiet openvpn@server; then + echo ACTIVE + elif systemctl is-active --quiet openvpn; then + echo ACTIVE + else + echo INACTIVE + systemctl list-units --type=service --all | grep -i openvpn || true + fi + """ + ); + + boolean reachable = serviceResult.exitCode() != -1; + boolean active = serviceResult.stdout().contains("ACTIVE"); + + Integer activeClients = null; + + if (reachable && active) { + activeClients = getActiveClientCount(); + } + + String error = null; + + if (!reachable) { + error = serviceResult.stderr(); + } else if (!active) { + error = cleanOutput(serviceResult.stdout() + "\n" + serviceResult.stderr()); + } + + return new OpenVpnServerHealth( + "Configured VPS", + reachable, + active, + activeClients, + checkedAt, + error + ); + } + + private Integer getActiveClientCount() { + SshCommandResult result = sshService.executeOnConfiguredVps( + """ + if [ -f /var/log/openvpn/status.log ]; then + grep -A 100 "Common Name" /var/log/openvpn/status.log | grep -v "Common Name" | grep -v "ROUTING TABLE" | grep -v "GLOBAL STATS" | grep -v "^$" | wc -l + elif [ -f /run/openvpn-server/status-server.log ]; then + grep -A 100 "Common Name" /run/openvpn-server/status-server.log | grep -v "Common Name" | grep -v "ROUTING TABLE" | grep -v "GLOBAL STATS" | grep -v "^$" | wc -l + else + echo 0 + fi + """ + ); + + if (result.exitCode() != 0) { + return null; + } + + try { + return Integer.parseInt(result.stdout().trim()); + } catch (NumberFormatException e) { + return null; + } + } + + private String cleanOutput(String value) { + if (value == null || value.isBlank()) { + return null; + } + + return value.trim(); + } +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/openvpn/health/OpenVpnServerHealth.java b/src/main/java/com/litoralregas/openvpn/health/OpenVpnServerHealth.java new file mode 100644 index 0000000..3d03e92 --- /dev/null +++ b/src/main/java/com/litoralregas/openvpn/health/OpenVpnServerHealth.java @@ -0,0 +1,11 @@ +package com.litoralregas.openvpn.health; + +public record OpenVpnServerHealth( + String name, + boolean reachable, + boolean openvpnServiceActive, + Integer activeClients, + String checkedAt, + String error +) { +} \ No newline at end of file