all lists on 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal