Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
25 changes: 25 additions & 0 deletions src/HostPatrol/Remote.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions src/HostPatrol/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ data HostReport = HostReport
, _hostReportTimezone :: !T.Text
, _hostReportCloud :: !Cloud
, _hostReportHardware :: !Hardware
, _hostReportClock :: !Clock
, _hostReportKernel :: !Kernel
, _hostReportDistribution :: !Distribution
, _hostReportDockerContainers :: !(Maybe [DockerContainer])
Expand All @@ -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
Expand Down Expand Up @@ -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


Expand Down
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
Expand Up @@ -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"
Expand Down
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';
Expand Down Expand Up @@ -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}>
Expand Down Expand Up @@ -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">
Expand Down
Loading
Loading