Skip to content

Commit

Permalink
Merge pull request #97 from vst/vst/add-host-clock-information
Browse files Browse the repository at this point in the history
Add Host Clock Sync Information
  • Loading branch information
vst authored Jan 15, 2025
2 parents e446887 + 5f3c0ab commit 31e3092
Showing 7 changed files with 443 additions and 72 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -25,13 +25,16 @@ jobs:
fetch-depth: 0

- name: "Install Nix"
if: "${{ steps.release.outputs.release_created }}"
uses: "DeterminateSystems/nix-installer-action@v16"

- name: "Use Nix Cache"
if: "${{ steps.release.outputs.release_created }}"
uses: "DeterminateSystems/magic-nix-cache-action@v8"

## TODO: This should not be necessary, but nixpkgs v24.11 requires it.
- name: "Update Haskell Package List"
if: "${{ steps.release.outputs.release_created }}"
run: |
nix-shell --pure --run "cabal update --ignore-project"
25 changes: 25 additions & 0 deletions src/HostPatrol/Remote.hs
Original file line number Diff line number Diff line change
@@ -85,6 +85,7 @@ compileHostReport ch = do
_hostReportTimezone <- _toParseError _hostName $ _getParse pure "HOSTPATROL_GENERAL_TIMEZONE" kvs
_hostReportCloud <- _mkCloud _hostName kvs
_hostReportHardware <- _mkHardware _hostName kvs
_hostReportClock <- _mkClock _hostName =<< _fetchHostClockInfo h
_hostReportKernel <- _mkKernel _hostName kvs
_hostReportDistribution <- _mkDistribution _hostName kvs
_hostReportDockerContainers <- _fetchHostDockerContainers h
@@ -177,6 +178,17 @@ _fetchHostCloudInfo h@Types.Host {..} =
parseKVs <$> _toSshError _hostName (Z.Ssh.runScript (getHostSshConfig h) $(embedStringFile "src/scripts/cloud.sh") ["bash"])


-- | Attempts to retrieve remote host clock information and return it
-- as a list of key/value tuples.
_fetchHostClockInfo
:: MonadIO m
=> MonadError HostPatrolError m
=> Types.Host
-> m [(T.Text, T.Text)]
_fetchHostClockInfo h@Types.Host {..} =
parseKVs <$> _toSshError _hostName (Z.Ssh.runScript (getHostSshConfig h) $(embedStringFile "src/scripts/clock.sh") ["bash"])


-- | Attempts to retrieve remote host docker containers information and return it.
--
-- Returns 'Nothing' if remote host is not identified as a Docker
@@ -284,6 +296,19 @@ _mkHardware h kvs =
pure Types.Hardware {..}


-- | Smart constructor for remote host clock information.
_mkClock
:: MonadError HostPatrolError m
=> Z.Ssh.Destination
-> [(T.Text, T.Text)]
-> m Types.Clock
_mkClock h kvs =
_toParseError h $ do
_clockNtpAvailability <- _getParse pure "HOSTPATROL_CLOCK_NTP" kvs
_clockTimeSyncStatus <- _getParse pure "HOSTPATROL_CLOCK_NTP_SYNCHRONIZED" kvs
pure Types.Clock {..}


-- | Smart constructor for remote host kernel information.
_mkKernel
:: MonadError HostPatrolError m
25 changes: 25 additions & 0 deletions src/HostPatrol/Types.hs
Original file line number Diff line number Diff line change
@@ -136,6 +136,7 @@ data HostReport = HostReport
, _hostReportTimezone :: !T.Text
, _hostReportCloud :: !Cloud
, _hostReportHardware :: !Hardware
, _hostReportClock :: !Clock
, _hostReportKernel :: !Kernel
, _hostReportDistribution :: !Distribution
, _hostReportDockerContainers :: !(Maybe [DockerContainer])
@@ -160,6 +161,7 @@ instance ADC.HasCodec HostReport where
<*> ADC.requiredField "timezone" "Timezone of the host." ADC..= _hostReportTimezone
<*> ADC.requiredField "cloud" "Cloud information." ADC..= _hostReportCloud
<*> ADC.requiredField "hardware" "Hardware information." ADC..= _hostReportHardware
<*> ADC.requiredField "clock" "Clock information." ADC..= _hostReportClock
<*> ADC.requiredField "kernel" "Kernel information." ADC..= _hostReportKernel
<*> ADC.requiredField "distribution" "Distribution information." ADC..= _hostReportDistribution
<*> ADC.requiredField "dockerContainers" "List of Docker containers if the host is a Docker host." ADC..= _hostReportDockerContainers
@@ -233,6 +235,29 @@ instance ADC.HasCodec Hardware where
<*> ADC.requiredField "diskRoot" "Total disk space of root (`/`) filesystem (in GB)." ADC..= _hardwareDiskRoot


-- * Clock Information


-- | Data definition for host's clock information.
data Clock = Clock
{ _clockNtpAvailability :: !T.Text
, _clockTimeSyncStatus :: !T.Text
}
deriving (Eq, Generic, Show)
deriving (Aeson.FromJSON, Aeson.ToJSON) via (ADC.Autodocodec Clock)


instance ADC.HasCodec Clock where
codec =
_codec ADC.<?> "Clock Information"
where
_codec =
ADC.object "Clock" $
Clock
<$> ADC.requiredField "ntp_availability" "Indicates NTP availability and enablement." ADC..= _clockNtpAvailability
<*> ADC.requiredField "time_sync_status" "Indicates time synchronisation status." ADC..= _clockTimeSyncStatus


-- * Kernel Information


43 changes: 43 additions & 0 deletions src/scripts/clock.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env sh

###################
# SHELL BEHAVIOUR #
###################

# Stop on errors:
set -e

###############
# DEFINITIONS #
###############

# Prints a key/value pair in SHELL variable format. Value is printed
# within double-quotes, and double-quotes in the variable are escaped.
_print_var() {
printf '%s="%s"\n' "${1}" "$(echo "${2}" | sed 's/"/\\"/g')"
}

##########
# CHECKS #
##########

## Check if timedatectl is available:
if ! command -v timedatectl >/dev/null; then
_print_var "HOSTPATROL_CLOCK_NTP" "timedatectl not available"
_print_var "HOSTPATROL_CLOCK_NTP_SYNCHRONIZED" "timedatectl not available"
exit 0
fi

## Check if we have a recent enough version of timedatectl:
if ! timedatectl show 2>/dev/null; then
_print_var "HOSTPATROL_CLOCK_NTP" "timedatectl failed (too old?)"
_print_var "HOSTPATROL_CLOCK_NTP_SYNCHRONIZED" "timedatectl failed (too old?)"
exit 0
fi

#############
# PROCEDURE #
#############

_print_var "HOSTPATROL_CLOCK_NTP" "$(timedatectl show --property=NTP --value)"
_print_var "HOSTPATROL_CLOCK_NTP_SYNCHRONIZED" "$(timedatectl show --property=NTPSynchronized --value)"
28 changes: 19 additions & 9 deletions website/src/components/report/ShowHostDetails.tsx
Original file line number Diff line number Diff line change
@@ -56,15 +56,25 @@ export function ShowHostDetails({ host, data }: { host: HostReport; data: HostPa
</div>
</h1>

<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
<KVBox
title="Hardware"
kvs={[
{ key: 'CPU', value: host.hardware.cpuCount },
{ key: 'Memory', value: host.hardware.ramTotal },
{ key: 'Disk', value: host.hardware.diskRoot },
]}
/>
<div className="h-100 grid grid-cols-1 gap-4 lg:grid-cols-3">
<div className="flex flex-col space-y-4">
<KVBox
title="Hardware"
kvs={[
{ key: 'CPU', value: host.hardware.cpuCount },
{ key: 'Memory', value: host.hardware.ramTotal },
{ key: 'Disk', value: host.hardware.diskRoot },
]}
/>

<KVBox
title="Clock"
kvs={[
{ key: 'NTP', value: host.clock.ntp_availability },
{ key: 'Time Sync', value: host.clock.time_sync_status },
]}
/>
</div>

<KVBox
title="Cloud"
12 changes: 11 additions & 1 deletion website/src/components/report/TabulateHosts.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HostReport } from '@/lib/data';
import { Chip } from '@nextui-org/chip';
import { Radio, RadioGroup, Select, SelectItem, Selection, Slider } from '@nextui-org/react';
import { Radio, RadioGroup, Select, SelectItem, Selection, Slider, Tooltip } from '@nextui-org/react';
import { Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@nextui-org/table';
import Image from 'next/image';
import Link from 'next/link';
@@ -346,6 +346,7 @@ export function TabulateHosts({
<TableColumn key="systemd" align="end">
Systemd
</TableColumn>
<TableColumn key="clock-sync">Clock Sync</TableColumn>
<TableColumn key="tags">Tags</TableColumn>
</TableHeader>
<TableBody items={filteredHosts}>
@@ -397,6 +398,15 @@ export function TabulateHosts({
<TableCell>
{host.systemdServices.length} / {host.systemdTimers.length}
</TableCell>
<TableCell>
<Tooltip content={host.clock.time_sync_status}>
<span className={host.clock.time_sync_status === 'yes' ? 'text-green-600' : 'text-orange-600'}>
{host.clock.time_sync_status.length > 10
? host.clock.time_sync_status.slice(0, 8) + '...'
: host.clock.time_sync_status}
</span>
</Tooltip>
</TableCell>
<TableCell className="space-x-1">
{(host.host.tags || []).map((x) => (
<Chip key={x} size="sm" color="primary" variant="flat" radius="sm">
Loading

0 comments on commit 31e3092

Please sign in to comment.