108 lines
4.5 KiB
Perl
108 lines
4.5 KiB
Perl
#!/usr/bin/perl
|
|
# Internal bridge to Proxmox's notification and cluster configuration libraries.
|
|
use strict;
|
|
use warnings;
|
|
use JSON;
|
|
use PVE::Cluster;
|
|
use PVE::DataCenterConfig;
|
|
use PVE::INotify;
|
|
use PVE::Notify;
|
|
use PVE::Tools;
|
|
|
|
my $json = JSON->new->canonical;
|
|
my $policy_file = '/etc/pve/priv/tapm-update-policy.json';
|
|
my $receipts_file = '/etc/pve/priv/tapm-update-receipts.json';
|
|
|
|
sub read_json {
|
|
my ($path) = @_;
|
|
return {} if !-e $path;
|
|
return $json->decode(PVE::Tools::file_get_contents($path));
|
|
}
|
|
|
|
sub snapshot {
|
|
PVE::Cluster::cfs_update();
|
|
my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
|
return { policy => read_json($policy_file), notify => $dc->{notify} // {} };
|
|
}
|
|
|
|
sub locked {
|
|
my ($code) = @_;
|
|
my $result;
|
|
PVE::Cluster::cfs_lock_domain('tapm-update-notifications', 10, sub {
|
|
$result = $code->();
|
|
});
|
|
die $@ if $@;
|
|
return $result;
|
|
}
|
|
|
|
my $result;
|
|
my $ok = eval {
|
|
die "root required" if $> != 0;
|
|
PVE::INotify::inotify_init();
|
|
my $input = do { local $/; <STDIN> };
|
|
my $request = $json->decode($input);
|
|
my $action = $request->{action} // '';
|
|
if ($action eq 'read') {
|
|
$result = snapshot();
|
|
} elsif ($action eq 'apply') {
|
|
$result = locked(sub {
|
|
my $current = snapshot();
|
|
die "configuration changed" if $json->encode($current) ne $json->encode($request->{expected});
|
|
my $policy = $request->{policy};
|
|
die "invalid mode" if ($policy->{mode} // '') !~ /\A(?:all|version|disabled)\z/;
|
|
die "invalid owner" if ($policy->{owner} // '') !~ /\A[A-Za-z0-9][A-Za-z0-9.-]{0,62}\z/;
|
|
my $response;
|
|
PVE::Cluster::cfs_lock_file('datacenter.cfg', 10, sub {
|
|
my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
|
die "notification settings changed" if $json->encode($dc->{notify} // {}) ne $json->encode($current->{notify});
|
|
# Preserve all unrelated notification options and datacenter settings.
|
|
my $before = $json->decode($json->encode($dc));
|
|
PVE::Tools::file_set_contents('/etc/pve/priv/tapm-update-policy.previous.json', $json->encode($current));
|
|
$dc->{notify} //= {};
|
|
$dc->{notify}->{'package-updates'} = $policy->{mode} eq 'all' ? 'always' : 'never';
|
|
PVE::Cluster::cfs_write_file('datacenter.cfg', $dc);
|
|
eval { PVE::Tools::file_set_contents($policy_file, $json->encode($policy)); };
|
|
if (my $err = $@) {
|
|
PVE::Cluster::cfs_write_file('datacenter.cfg', $before);
|
|
die $err;
|
|
}
|
|
$response = { applied => JSON::true };
|
|
});
|
|
die $@ if $@;
|
|
return $response;
|
|
});
|
|
} elsif ($action eq 'send') {
|
|
$result = locked(sub {
|
|
my $current = snapshot();
|
|
my $policy = $current->{policy};
|
|
# An old scheduler must not send after a policy/owner change.
|
|
return { sent => JSON::false } if ($policy->{mode} // '') ne 'version'
|
|
|| ($policy->{owner} // '') ne PVE::INotify::nodename()
|
|
|| ($policy->{generation} // '') ne ($request->{generation} // '')
|
|
|| ($current->{notify}->{'package-updates'} // '') ne 'never';
|
|
my $node = $request->{node};
|
|
die "invalid node" if ($node // '') !~ /\A[A-Za-z0-9][A-Za-z0-9.-]{0,62}\z/;
|
|
for my $key (qw(installed available)) {
|
|
die "invalid version" if ($request->{$key} // '') !~ /\A[0-9][A-Za-z0-9.+:~\-]{0,100}\z/;
|
|
}
|
|
my $receipts = read_json($receipts_file);
|
|
# Keep history so repository switches do not re-announce an old candidate.
|
|
return { sent => JSON::false } if $receipts->{$node}->{$request->{available}};
|
|
my $data = { hostname => $node, installed => $request->{installed}, available => $request->{available} };
|
|
PVE::Notify::info('tapm-pve-version', $data, { type => 'package-updates', hostname => $node });
|
|
$receipts->{$node}->{$request->{available}} = time();
|
|
PVE::Tools::file_set_contents($receipts_file, $json->encode($receipts));
|
|
return { sent => JSON::true };
|
|
});
|
|
} else {
|
|
die "unknown operation";
|
|
}
|
|
1;
|
|
};
|
|
if (!$ok) {
|
|
# Do not expose package names, credentials or raw backend errors in output.
|
|
print STDERR "Proxmox notification operation failed; verify cluster quorum, configuration and notification delivery.\n";
|
|
exit 1;
|
|
}
|
|
print $json->encode($result), "\n";
|