]> git.proxmox.com Git - pve-manager.git/blob - PVE/CLI/pvenode.pm
pvenode: Add CLI call interface for wake on LAN
[pve-manager.git] / PVE / CLI / pvenode.pm
1 package PVE::CLI::pvenode;
2
3 use strict;
4 use warnings;
5
6 use PVE::API2::ACME;
7 use PVE::API2::ACMEAccount;
8 use PVE::API2::Certificates;
9 use PVE::API2::NodeConfig;
10 use PVE::API2::Nodes;
11 use PVE::API2::Tasks;
12
13 use PVE::CertHelpers;
14 use PVE::Certificate;
15 use PVE::Exception qw(raise_param_exc raise);
16 use PVE::JSONSchema qw(get_standard_option);
17 use PVE::NodeConfig;
18 use PVE::RPCEnvironment;
19 use PVE::CLIFormatter;
20 use PVE::RESTHandler;
21 use PVE::CLIHandler;
22
23 use Term::ReadLine;
24
25 use base qw(PVE::CLIHandler);
26
27 my $nodename = PVE::INotify::nodename();
28
29 sub setup_environment {
30 PVE::RPCEnvironment->setup_default_cli_env();
31 }
32
33 my $upid_exit = sub {
34 my $upid = shift;
35 my $status = PVE::Tools::upid_read_status($upid);
36 print "Task $status\n";
37 exit($status eq 'OK' ? 0 : -1);
38 };
39
40 sub param_mapping {
41 my ($name) = @_;
42
43 my $mapping = {
44 'upload_custom_cert' => [
45 'certificates',
46 'key',
47 ],
48 };
49
50 return $mapping->{$name};
51 }
52
53 __PACKAGE__->register_method({
54 name => 'acme_register',
55 path => 'acme_register',
56 method => 'POST',
57 description => "Register a new ACME account with a compatible CA.",
58 parameters => {
59 additionalProperties => 0,
60 properties => {
61 name => get_standard_option('pve-acme-account-name'),
62 contact => get_standard_option('pve-acme-account-contact'),
63 directory => get_standard_option('pve-acme-directory-url', {
64 optional => 1,
65 }),
66 },
67 },
68 returns => { type => 'null' },
69 code => sub {
70 my ($param) = @_;
71
72 if (!$param->{directory}) {
73 my $directories = PVE::API2::ACMEAccount->get_directories({});
74 print "Directory endpoints:\n";
75 my $i = 0;
76 while ($i < @$directories) {
77 print $i, ") ", $directories->[$i]->{name}, " (", $directories->[$i]->{url}, ")\n";
78 $i++;
79 }
80 print $i, ") Custom\n";
81
82 my $term = Term::ReadLine->new('pvenode');
83 my $get_dir_selection = sub {
84 my $selection = $term->readline("Enter selection:\n");
85 if ($selection =~ /^(\d+)$/) {
86 $selection = $1;
87 if ($selection == $i) {
88 $param->{directory} = $term->readline("Enter URL:\n");
89 return;
90 } elsif ($selection < $i && $selection >= 0) {
91 $param->{directory} = $directories->[$selection]->{url};
92 return;
93 }
94 }
95 print "Invalid selection.\n";
96 };
97
98 my $attempts = 0;
99 while (!$param->{directory}) {
100 die "Aborting.\n" if $attempts > 3;
101 $get_dir_selection->();
102 $attempts++;
103 }
104 }
105 print "\nAttempting to fetch Terms of Service from '$param->{directory}'..\n";
106 my $tos = PVE::API2::ACMEAccount->get_tos({ directory => $param->{directory} });
107 if ($tos) {
108 print "Terms of Service: $tos\n";
109 my $term = Term::ReadLine->new('pvenode');
110 my $agreed = $term->readline('Do you agree to the above terms? [y|N]');
111 die "Cannot continue without agreeing to ToS, aborting.\n"
112 if ($agreed !~ /^y$/i);
113
114 $param->{tos_url} = $tos;
115 } else {
116 print "No Terms of Service found, proceeding.\n";
117 }
118 print "\nAttempting to register account with '$param->{directory}'..\n";
119
120 $upid_exit->(PVE::API2::ACMEAccount->register_account($param));
121 }});
122
123 my $print_cert_info = sub {
124 my ($schema, $cert, $options) = @_;
125
126 my $order = [qw(filename fingerprint subject issuer notbefore notafter san)];
127 PVE::CLIFormatter::print_api_result(
128 $cert, $schema, $order, { %$options, noheader => 1, sort_key => 0 });
129 };
130
131 our $cmddef = {
132 config => {
133 get => [ 'PVE::API2::NodeConfig', 'get_config', [], { node => $nodename }, sub {
134 my ($res) = @_;
135 print PVE::NodeConfig::write_node_config($res);
136 }],
137 set => [ 'PVE::API2::NodeConfig', 'set_options', [], { node => $nodename } ],
138 },
139
140 startall => [ 'PVE::API2::Nodes::Nodeinfo', 'startall', [], { node => $nodename } ],
141 stopall => [ 'PVE::API2::Nodes::Nodeinfo', 'stopall', [], { node => $nodename } ],
142 migrateall => [ 'PVE::API2::Nodes::Nodeinfo', 'migrateall', [ 'target' ], { node => $nodename } ],
143
144 cert => {
145 info => [ 'PVE::API2::Certificates', 'info', [], { node => $nodename }, sub {
146 my ($res, $schema, $options) = @_;
147
148 if (!$options->{'output-format'} || $options->{'output-format'} eq 'text') {
149 for my $cert (sort { $a->{filename} cmp $b->{filename} } @$res) {
150 $print_cert_info->($schema->{items}, $cert, $options);
151 }
152 } else {
153 PVE::CLIFormatter::print_api_result($res, $schema, undef, $options);
154 }
155
156 }, $PVE::RESTHandler::standard_output_options],
157 set => [ 'PVE::API2::Certificates', 'upload_custom_cert', ['certificates', 'key'], { node => $nodename }, sub {
158 my ($res, $schema, $options) = @_;
159 $print_cert_info->($schema, $res, $options);
160 }, $PVE::RESTHandler::standard_output_options],
161 delete => [ 'PVE::API2::Certificates', 'remove_custom_cert', ['restart'], { node => $nodename } ],
162 },
163
164 task => {
165 list => [ 'PVE::API2::Tasks', 'node_tasks', [], { node => $nodename }, sub {
166 my ($data, $schema, $options) = @_;
167 foreach my $task (@$data) {
168 if ($task->{status} ne 'OK') {
169 $task->{status} = 'ERROR';
170 }
171 }
172 PVE::CLIFormatter::print_api_result($data, $schema, ['upid', 'type', 'id', 'user', 'starttime', 'endtime', 'status' ], $options);
173 }, $PVE::RESTHandler::standard_output_options],
174 status => [ 'PVE::API2::Tasks', 'read_task_status', [ 'upid' ], { node => $nodename }, sub {
175 my ($data, $schema, $options) = @_;
176 PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
177 }, $PVE::RESTHandler::standard_output_options],
178 # set limit to 1000000, so we see the whole log, not only the first 50 lines by default
179 log => [ 'PVE::API2::Tasks', 'read_task_log', [ 'upid' ], { node => $nodename, limit => 1000000 }, sub {
180 my ($data, $resultprops) = @_;
181 foreach my $line (@$data) {
182 print $line->{t} . "\n";
183 }
184 }],
185 },
186
187 acme => {
188 account => {
189 list => [ 'PVE::API2::ACMEAccount', 'account_index', [], {}, sub {
190 my ($res) = @_;
191 for my $acc (@$res) {
192 print "$acc->{name}\n";
193 }
194 }],
195 register => [ __PACKAGE__, 'acme_register', ['name', 'contact'], {}, $upid_exit ],
196 deactivate => [ 'PVE::API2::ACMEAccount', 'deactivate_account', ['name'], {}, $upid_exit ],
197 info => [ 'PVE::API2::ACMEAccount', 'get_account', ['name'], {}, sub {
198 my ($data, $schema, $options) = @_;
199 PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
200 }, $PVE::RESTHandler::standard_output_options],
201 update => [ 'PVE::API2::ACMEAccount', 'update_account', ['name'], {}, $upid_exit ],
202 },
203 cert => {
204 order => [ 'PVE::API2::ACME', 'new_certificate', [], { node => $nodename }, $upid_exit ],
205 renew => [ 'PVE::API2::ACME', 'renew_certificate', [], { node => $nodename }, $upid_exit ],
206 revoke => [ 'PVE::API2::ACME', 'revoke_certificate', [], { node => $nodename }, $upid_exit ],
207 },
208 },
209
210 wakeonlan => [ 'PVE::API2::Nodes::Nodeinfo', 'wakeonlan', [ 'node' ], { } ],
211
212 };
213
214 1;