public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Luca Vornheder <luca@vornheder.cloud>
To: pve-devel@lists.proxmox.com
Cc: Luca Vornheder <luca@vornheder.cloud>
Subject: [PATCH manager 1/3] api: nodes: add opt-in endpoint to read the local IPMI SEL
Date: Fri, 18 Sep 2026 18:45:07 +0200	[thread overview]
Message-ID: <20260918164509.46468-2-luca@vornheder.cloud> (raw)
In-Reply-To: <20260918164509.46468-1-luca@vornheder.cloud>

Add a per-node 'ipmi-sel' option to the node config and a new
GET /nodes/{node}/ipmi-sel endpoint, which returns the System Event Log
of the node's local BMC as parsed entries, read via 'ipmitool sel
elist'.

The feature is opt-in. As long as the option is not set, the endpoint
fails with 501 (Not Implemented), so clients can tell a disabled
feature apart from a real error. Access requires Sys.Syslog on the
node, like the other log endpoints.

With the 'download' parameter the raw ipmitool output is streamed as a
file instead, analogous to the task log download.

Signed-off-by: Luca Vornheder <luca@vornheder.cloud>
---
 PVE/API2/Nodes.pm | 111 ++++++++++++++++++++++++++++++++++++++++++++++
 PVE/NodeConfig.pm |   8 ++++
 2 files changed, 119 insertions(+)

diff --git a/PVE/API2/Nodes.pm b/PVE/API2/Nodes.pm
index 2ca3244..bc3008a 100644
--- a/PVE/API2/Nodes.pm
+++ b/PVE/API2/Nodes.pm
@@ -239,6 +239,7 @@ __PACKAGE__->register_method({
             { name => 'firewall' },
             { name => 'hardware' },
             { name => 'hosts' },
+            { name => 'ipmi-sel' },
             { name => 'journal' },
             { name => 'lxc' },
             { name => 'migrateall' },
@@ -1124,6 +1125,116 @@ __PACKAGE__->register_method({
     },
 });
 
+my $ipmitool_bin = '/usr/bin/ipmitool';
+
+__PACKAGE__->register_method({
+    name => 'ipmi_sel',
+    path => 'ipmi-sel',
+    method => 'GET',
+    description =>
+        "Read this node's local IPMI System Event Log (SEL), as reported by its BMC."
+        . " Requires 'ipmitool' to be installed and a local IPMI interface to be present,"
+        . " and must be enabled first via the node's 'ipmi-sel' option.",
+    proxyto => 'node',
+    permissions => {
+        check => ['perm', '/nodes/{node}', ['Sys.Syslog']],
+    },
+    protected => 1,
+    download_allowed => 1,
+    parameters => {
+        additionalProperties => 0,
+        properties => {
+            node => get_standard_option('pve-node'),
+            download => {
+                type => 'boolean',
+                optional => 1,
+                description => "Whether to return the raw 'ipmitool sel elist' output as a"
+                    . " downloadable file, instead of parsed JSON entries.",
+            },
+        },
+    },
+    returns => {
+        type => 'array',
+        items => {
+            type => 'object',
+            properties => {
+                id => { type => 'string' },
+                timestamp => { type => 'string', optional => 1 },
+                sensor => { type => 'string' },
+                description => { type => 'string' },
+                direction => { type => 'string', optional => 1 },
+            },
+        },
+    },
+    code => sub {
+        my ($param) = @_;
+
+        my $node = $param->{node};
+
+        my $conf = PVE::NodeConfig::load_config($node);
+        raise(
+            "IPMI SEL log is not enabled for this node, enable it in the node's"
+                . " 'Options' first.\n",
+            code => HTTP_NOT_IMPLEMENTED,
+        ) if !$conf->{'ipmi-sel'};
+
+        die "'ipmitool' is not installed\n" if !-x $ipmitool_bin;
+
+        if ($param->{download}) {
+            # needs a real pipe (fd), not an in-memory filehandle: the async
+            # streaming code in pve-http-server needs to select()/poll() on it
+            open(my $fh, '-|', $ipmitool_bin, 'sel', 'elist')
+                or die "could not run 'ipmitool sel elist' for download - $!\n";
+
+            return {
+                download => {
+                    fh => $fh,
+                    stream => 1,
+                    'content-type' => 'text/plain',
+                    'content-disposition' => "attachment; filename=\"ipmi-sel-${node}.log\"",
+                },
+            };
+        }
+
+        my $raw = '';
+        PVE::Tools::run_command(
+            [$ipmitool_bin, 'sel', 'elist'],
+            outfunc => sub {
+                my ($line) = @_;
+                $raw .= "$line\n";
+            },
+        );
+
+        my $entries = [];
+        for my $line (split(/\n/, $raw)) {
+            my @fields = map { s/^\s+|\s+$//gr } split(/\|/, $line);
+            my $id = shift @fields;
+            next if !defined($id) || $id !~ /^[0-9a-fA-F]+$/;
+
+            my $entry = { id => $id };
+
+            # normal format is '<id> | <date> | <time> | <sensor> | <description>
+            # [| <direction>]'; right after a BMC reset entries can lack a resolvable
+            # date and print a single 'Pre-Init Time-stamp' field instead.
+            if (@fields >= 2 && $fields[1] =~ m/^\d{2}:\d{2}:\d{2}$/) {
+                $entry->{timestamp} = (shift @fields) . ' ' . (shift @fields);
+            } elsif (@fields) {
+                $entry->{timestamp} = shift @fields;
+            }
+
+            $entry->{direction} = pop(@fields)
+                if @fields > 1 && $fields[-1] =~ m/^(?:Asserted|Deasserted)$/;
+
+            $entry->{sensor} = shift(@fields) // '';
+            $entry->{description} = join(' | ', @fields);
+
+            push @$entries, $entry;
+        }
+
+        return $entries;
+    },
+});
+
 my $sslcert;
 
 my $shell_cmd_map = {
diff --git a/PVE/NodeConfig.pm b/PVE/NodeConfig.pm
index cc1a2a2..d99a3fd 100644
--- a/PVE/NodeConfig.pm
+++ b/PVE/NodeConfig.pm
@@ -108,6 +108,14 @@ my $confdesc = {
         default => 80,
         optional => 1,
     },
+    'ipmi-sel' => {
+        description => "Enable the IPMI System Event Log (SEL) view for this node in the"
+            . " web interface. Reads the SEL of this node's local BMC via 'ipmitool', so"
+            . " that binary and a local IPMI interface need to be available on the node.",
+        type => 'boolean',
+        default => 0,
+        optional => 1,
+    },
 };
 
 my $wakeonlan_desc = {
-- 
2.50.1 (Apple Git-155)




  reply	other threads:[~2026-09-21  7:55 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-18 16:45 [PATCH manager 0/3] add opt-in IPMI SEL log tab for nodes Luca Vornheder
2026-09-18 16:45 ` Luca Vornheder [this message]
2026-09-18 16:45 ` [PATCH manager 2/3] fix #7656: ui: node: add opt-in IPMI SEL log tab Luca Vornheder
2026-09-18 16:45 ` [PATCH manager 3/3] d/control: recommend ipmitool Luca Vornheder

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260918164509.46468-2-luca@vornheder.cloud \
    --to=luca@vornheder.cloud \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal