]> granicus.if.org Git - icinga2/blob - lib/cli/nodewizardcommand.cpp
31fdfe00fef54a3ad0912c0f3bd999bde9e346f1
[icinga2] / lib / cli / nodewizardcommand.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/)  *
4  *                                                                            *
5  * This program is free software; you can redistribute it and/or              *
6  * modify it under the terms of the GNU General Public License                *
7  * as published by the Free Software Foundation; either version 2             *
8  * of the License, or (at your option) any later version.                     *
9  *                                                                            *
10  * This program is distributed in the hope that it will be useful,            *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
13  * GNU General Public License for more details.                               *
14  *                                                                            *
15  * You should have received a copy of the GNU General Public License          *
16  * along with this program; if not, write to the Free Software Foundation     *
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
18  ******************************************************************************/
19
20 #include "cli/nodewizardcommand.hpp"
21 #include "cli/nodeutility.hpp"
22 #include "cli/featureutility.hpp"
23 #include "cli/apisetuputility.hpp"
24 #include "remote/apilistener.hpp"
25 #include "remote/pkiutility.hpp"
26 #include "base/logger.hpp"
27 #include "base/console.hpp"
28 #include "base/application.hpp"
29 #include "base/tlsutility.hpp"
30 #include "base/scriptglobal.hpp"
31 #include "base/exception.hpp"
32 #include <boost/algorithm/string/join.hpp>
33 #include <boost/algorithm/string/replace.hpp>
34 #include <boost/algorithm/string/case_conv.hpp>
35 #include <iostream>
36 #include <string>
37 #include <fstream>
38 #include <vector>
39
40 using namespace icinga;
41 namespace po = boost::program_options;
42
43 REGISTER_CLICOMMAND("node/wizard", NodeWizardCommand);
44
45 String NodeWizardCommand::GetDescription() const
46 {
47         return "Wizard for Icinga 2 node setup.";
48 }
49
50 String NodeWizardCommand::GetShortDescription() const
51 {
52         return "wizard for node setup";
53 }
54
55 ImpersonationLevel NodeWizardCommand::GetImpersonationLevel() const
56 {
57         return ImpersonateRoot;
58 }
59
60 int NodeWizardCommand::GetMaxArguments() const
61 {
62         return -1;
63 }
64
65 void NodeWizardCommand::InitParameters(boost::program_options::options_description& visibleDesc,
66         boost::program_options::options_description& hiddenDesc) const
67 {
68         visibleDesc.add_options()
69                 ("verbose", "increase log level");
70 }
71
72 /**
73  * The entry point for the "node wizard" CLI command.
74  *
75  * @returns An exit status.
76  */
77 int NodeWizardCommand::Run(const boost::program_options::variables_map& vm,
78         const std::vector<std::string>& ap) const
79 {
80         if (!vm.count("verbose"))
81                 Logger::SetConsoleLogSeverity(LogCritical);
82
83         /*
84          * The wizard will get all information from the user,
85          * and then call all required functions.
86          */
87
88         std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundBlue)
89                 << "Welcome to the Icinga 2 Setup Wizard!\n"
90                 << "\n"
91                 << "We will guide you through all required configuration details.\n"
92                 << "\n"
93                 << ConsoleColorTag(Console_Normal);
94
95         /* 0. master or node setup?
96          * 1. Ticket
97          * 2. Master information for autosigning
98          * 3. Trusted cert location
99          * 4. CN to use (defaults to FQDN)
100          * 5. Local CA
101          * 6. New self signed certificate
102          * 7. Request signed certificate from master
103          * 8. copy key information to /var/lib/icinga2/certs
104          * 9. enable ApiListener feature
105          * 10. generate zones.conf with endpoints and zone objects
106          * 11. set NodeName = cn in constants.conf
107          * 12. reload icinga2, or tell the user to
108          */
109
110         std::string answer;
111         /* master or satellite/client setup */
112         std::cout << ConsoleColorTag(Console_Bold)
113                 << "Please specify if this is a satellite/client setup "
114                 << "('n' installs a master setup)" << ConsoleColorTag(Console_Normal)
115                 << " [Y/n]: ";
116         std::getline (std::cin, answer);
117
118         boost::algorithm::to_lower(answer);
119
120         String choice = answer;
121
122         std::cout << "\n";
123
124         int res = 0;
125
126         if (choice.Contains("n"))
127                 res = MasterSetup();
128         else
129                 res = ClientSetup();
130
131         if (res != 0)
132                 return res;
133
134         std::cout << "\n";
135         std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen)
136                 << "Done.\n\n"
137                 << ConsoleColorTag(Console_Normal);
138
139         std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundRed)
140                 << "Now restart your Icinga 2 daemon to finish the installation!\n"
141                 << ConsoleColorTag(Console_Normal);
142
143         return 0;
144 }
145
146 int NodeWizardCommand::ClientSetup() const
147 {
148         std::string answer;
149         String choice;
150         bool connectToParent = false;
151
152         std::cout << "Starting the Client/Satellite setup routine...\n\n";
153
154         /* CN */
155         std::cout << ConsoleColorTag(Console_Bold)
156                 << "Please specify the common name (CN)"
157                 << ConsoleColorTag(Console_Normal)
158                 << " [" << Utility::GetFQDN() << "]: ";
159
160         std::getline(std::cin, answer);
161
162         if (answer.empty())
163                 answer = Utility::GetFQDN();
164
165         String cn = answer;
166         cn = cn.Trim();
167
168         std::vector<std::string> endpoints;
169
170         String endpointBuffer;
171
172         std::cout << ConsoleColorTag(Console_Bold)
173                 << "\nPlease specify the parent endpoint(s) (master or satellite) where this node should connect to:"
174                 << ConsoleColorTag(Console_Normal) << "\n";
175         String parentEndpointName;
176
177 wizard_endpoint_loop_start:
178
179         std::cout << ConsoleColorTag(Console_Bold)
180                 << "Master/Satellite Common Name" << ConsoleColorTag(Console_Normal)
181                 << " (CN from your master/satellite node): ";
182
183         std::getline(std::cin, answer);
184
185         if (answer.empty()) {
186                 Log(LogWarning, "cli", "Master/Satellite CN is required! Please retry.");
187                 goto wizard_endpoint_loop_start;
188         }
189
190         endpointBuffer = answer;
191         endpointBuffer = endpointBuffer.Trim();
192
193         std::cout << "\nDo you want to establish a connection to the parent node "
194                 << ConsoleColorTag(Console_Bold) << "from this node?"
195                 << ConsoleColorTag(Console_Normal) << " [Y/n]: ";
196
197         std::getline (std::cin, answer);
198         boost::algorithm::to_lower(answer);
199         choice = answer;
200
201         String parentEndpointPort = "5665";
202
203         if (choice.Contains("n")) {
204                 connectToParent = false;
205
206                 Log(LogWarning, "cli", "Node to master/satellite connection setup skipped");
207                 std::cout << "Connection setup skipped. Please configure your parent node to\n"
208                         << "connect to this node by setting the 'host' attribute for the node Endpoint object.\n";
209
210         } else  {
211                 connectToParent = true;
212
213                 std::cout << ConsoleColorTag(Console_Bold)
214                         << "Please specify the master/satellite connection information:"
215                         << ConsoleColorTag(Console_Normal) << "\n"
216                         << ConsoleColorTag(Console_Bold) << "Master/Satellite endpoint host"
217                         << ConsoleColorTag(Console_Normal) << " (IP address or FQDN): ";
218
219                 std::getline(std::cin, answer);
220
221                 if (answer.empty()) {
222                         Log(LogWarning, "cli", "Please enter the parent endpoint (master/satellite) connection information.");
223                         goto wizard_endpoint_loop_start;
224                 }
225
226                 String tmp = answer;
227                 tmp = tmp.Trim();
228
229                 endpointBuffer += "," + tmp;
230                 parentEndpointName = tmp;
231
232                 std::cout << ConsoleColorTag(Console_Bold)
233                         << "Master/Satellite endpoint port" << ConsoleColorTag(Console_Normal)
234                         << " [" << parentEndpointPort << "]: ";
235
236                 std::getline(std::cin, answer);
237
238                 if (!answer.empty())
239                         parentEndpointPort = answer;
240
241                 endpointBuffer += "," + parentEndpointPort.Trim();
242         }
243
244         endpoints.push_back(endpointBuffer);
245
246         std::cout << ConsoleColorTag(Console_Bold) << "\nAdd more master/satellite endpoints?"
247                 << ConsoleColorTag(Console_Normal) << " [y/N]: ";
248         std::getline (std::cin, answer);
249
250         boost::algorithm::to_lower(answer);
251
252         choice = answer;
253
254         if (choice.Contains("y"))
255                 goto wizard_endpoint_loop_start;
256
257         /* Extract parent node information. */
258         String parentHost, parentPort;
259
260         for (const String& endpoint : endpoints) {
261                 std::vector<String> tokens = endpoint.Split(",");
262
263                 if (tokens.size() > 1)
264                         parentHost = tokens[1];
265
266                 if (tokens.size() > 2)
267                         parentPort = tokens[2];
268         }
269
270         /* workaround for fetching the master cert */
271         String certsDir = ApiListener::GetCertsDir();
272         Utility::MkDirP(certsDir, 0700);
273
274         String user = ScriptGlobal::Get("RunAsUser");
275         String group = ScriptGlobal::Get("RunAsGroup");
276
277         if (!Utility::SetFileOwnership(certsDir, user, group)) {
278                 Log(LogWarning, "cli")
279                         << "Cannot set ownership for user '" << user
280                         << "' group '" << group
281                         << "' on file '" << certsDir << "'. Verify it yourself!";
282         }
283
284         String nodeCert = certsDir + "/" + cn + ".crt";
285         String nodeKey = certsDir + "/" + cn + ".key";
286
287         if (Utility::PathExists(nodeKey))
288                 NodeUtility::CreateBackupFile(nodeKey, true);
289         if (Utility::PathExists(nodeCert))
290                 NodeUtility::CreateBackupFile(nodeCert);
291
292         if (PkiUtility::NewCert(cn, nodeKey, Empty, nodeCert) > 0) {
293                 Log(LogCritical, "cli")
294                         << "Failed to create new self-signed certificate for CN '"
295                         << cn << "'. Please try again.";
296                 return 1;
297         }
298
299         /* fix permissions: root -> icinga daemon user */
300         if (!Utility::SetFileOwnership(nodeKey, user, group)) {
301                 Log(LogWarning, "cli")
302                         << "Cannot set ownership for user '" << user
303                         << "' group '" << group
304                         << "' on file '" << nodeKey << "'. Verify it yourself!";
305         }
306
307         std::shared_ptr<X509> trustedParentCert;
308
309         /* Check whether we should connect to the parent node and present its trusted certificate. */
310         if (connectToParent) {
311                 //save-cert and store the master certificate somewhere
312                 Log(LogInformation, "cli")
313                         << "Fetching public certificate from master ("
314                         << parentHost << ", " << parentPort << "):\n";
315
316                 trustedParentCert = PkiUtility::FetchCert(parentHost, parentPort);
317                 if (!trustedParentCert) {
318                         Log(LogCritical, "cli", "Peer did not present a valid certificate.");
319                         return 1;
320                 }
321
322                 std::cout << ConsoleColorTag(Console_Bold) << "Parent certificate information:\n"
323                         << ConsoleColorTag(Console_Normal) << PkiUtility::GetCertificateInformation(trustedParentCert)
324                         << ConsoleColorTag(Console_Bold) << "\nIs this information correct?"
325                         << ConsoleColorTag(Console_Normal) << " [y/N]: ";
326
327                 std::getline (std::cin, answer);
328                 boost::algorithm::to_lower(answer);
329                 if (answer != "y") {
330                         Log(LogWarning, "cli", "Process aborted.");
331                         return 1;
332                 }
333
334                 Log(LogInformation, "cli", "Received trusted parent certificate.\n");
335         }
336
337 wizard_ticket:
338         String nodeCA = certsDir + "/ca.crt";
339         String ticket;
340
341         /* Check whether we can connect to the parent node and fetch the client and CA certificate. */
342         if (connectToParent) {
343                 std::cout << ConsoleColorTag(Console_Bold)
344                         << "\nPlease specify the request ticket generated on your Icinga 2 master "
345                         << ConsoleColorTag(Console_Normal) << "(optional)"
346                         << ConsoleColorTag(Console_Bold) << "."
347                         << ConsoleColorTag(Console_Normal) << "\n"
348                         << " (Hint: # icinga2 pki ticket --cn '" << cn << "'): ";
349
350                 std::getline(std::cin, answer);
351
352                 if (answer.empty()) {
353                         std::cout << ConsoleColorTag(Console_Bold) << "\n"
354                                 << "No ticket was specified. Please approve the certificate signing request manually\n"
355                                 << "on the master (see 'icinga2 ca list' and 'icinga2 ca sign --help' for details)."
356                                 << ConsoleColorTag(Console_Normal) << "\n";
357                 }
358
359                 ticket = answer;
360                 ticket = ticket.Trim();
361
362                 if (ticket.IsEmpty()) {
363                         Log(LogInformation, "cli")
364                                 << "Requesting certificate without a ticket.";
365                 } else {
366                         Log(LogInformation, "cli")
367                                 << "Requesting certificate with ticket '" << ticket << "'.";
368                 }
369
370                 if (Utility::PathExists(nodeCA))
371                         NodeUtility::CreateBackupFile(nodeCA);
372                 if (Utility::PathExists(nodeCert))
373                         NodeUtility::CreateBackupFile(nodeCert);
374
375                 if (PkiUtility::RequestCertificate(parentHost, parentPort, nodeKey,
376                         nodeCert, nodeCA, trustedParentCert, ticket) > 0) {
377                         Log(LogCritical, "cli")
378                                 << "Failed to fetch signed certificate from master '"
379                                 << parentHost << ", "
380                                 << parentPort << "'. Please try again.";
381                         goto wizard_ticket;
382                 }
383
384                 /* fix permissions (again) when updating the signed certificate */
385                 if (!Utility::SetFileOwnership(nodeCert, user, group)) {
386                         Log(LogWarning, "cli")
387                                 << "Cannot set ownership for user '" << user
388                                 << "' group '" << group << "' on file '"
389                                 << nodeCert << "'. Verify it yourself!";
390                 }
391         } else {
392                 /* We cannot retrieve the parent certificate.
393                  * Tell the user to manually copy the ca.crt file
394                  * into LocalStateDir + "/lib/icinga2/certs"
395                  */
396
397                 std::cout <<  ConsoleColorTag(Console_Bold)
398                         << "\nNo connection to the parent node was specified.\n\n"
399                         << "Please copy the public CA certificate from your master/satellite\n"
400                         << "into '" << nodeCA << "' before starting Icinga 2.\n"
401                         << ConsoleColorTag(Console_Normal);
402
403                 if (Utility::PathExists(nodeCA)) {
404                         std::cout <<  ConsoleColorTag(Console_Bold)
405                                 << "\nFound public CA certificate in '" << nodeCA << "'.\n"
406                                 << "Please verify that it is the same as on your master/satellite.\n"
407                                 << ConsoleColorTag(Console_Normal);
408                 }
409
410         }
411
412         /* apilistener config */
413         std::cout << ConsoleColorTag(Console_Bold)
414                 << "Please specify the API bind host/port "
415                 << ConsoleColorTag(Console_Normal) << "(optional)"
416                 << ConsoleColorTag(Console_Bold) << ":\n";
417
418         std::cout << ConsoleColorTag(Console_Bold)
419                 << "Bind Host" << ConsoleColorTag(Console_Normal) << " []: ";
420
421         std::getline(std::cin, answer);
422
423         String bindHost = answer;
424         bindHost = bindHost.Trim();
425
426         std::cout << ConsoleColorTag(Console_Bold)
427                 << "Bind Port" << ConsoleColorTag(Console_Normal) << " []: ";
428
429         std::getline(std::cin, answer);
430
431         String bindPort = answer;
432         bindPort = bindPort.Trim();
433
434         std::cout << ConsoleColorTag(Console_Bold) << "\n"
435                 << "Accept config from parent node?" << ConsoleColorTag(Console_Normal)
436                 << " [y/N]: ";
437         std::getline(std::cin, answer);
438         boost::algorithm::to_lower(answer);
439         choice = answer;
440
441         String acceptConfig = choice.Contains("y") ? "true" : "false";
442
443         std::cout << ConsoleColorTag(Console_Bold)
444                 << "Accept commands from parent node?" << ConsoleColorTag(Console_Normal)
445                 << " [y/N]: ";
446         std::getline(std::cin, answer);
447         boost::algorithm::to_lower(answer);
448         choice = answer;
449
450         String acceptCommands = choice.Contains("y") ? "true" : "false";
451
452         std::cout << "\n";
453
454         std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen)
455                 << "Reconfiguring Icinga...\n"
456                 << ConsoleColorTag(Console_Normal);
457
458         /* disable the notifications feature on client nodes */
459         Log(LogInformation, "cli", "Disabling the Notification feature.");
460
461         FeatureUtility::DisableFeatures({ "notification" });
462
463         Log(LogInformation, "cli", "Enabling the ApiListener feature.");
464
465         FeatureUtility::EnableFeatures({ "api" });
466
467         String apiConfPath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf";
468         NodeUtility::CreateBackupFile(apiConfPath);
469
470         std::fstream fp;
471         String tempApiConfPath = Utility::CreateTempFile(apiConfPath + ".XXXXXX", 0644, fp);
472
473         fp << "/**\n"
474                 << " * The API listener is used for distributed monitoring setups.\n"
475                 << " */\n"
476                 << "object ApiListener \"api\" {\n"
477                 << "  accept_config = " << acceptConfig << "\n"
478                 << "  accept_commands = " << acceptCommands << "\n";
479
480         if (!bindHost.IsEmpty())
481                 fp << "  bind_host = \"" << bindHost << "\"\n";
482         if (!bindPort.IsEmpty())
483                 fp << "  bind_port = " << bindPort << "\n";
484
485         fp << "}\n";
486
487         fp.close();
488
489 #ifdef _WIN32
490         _unlink(apiConfPath.CStr());
491 #endif /* _WIN32 */
492
493         if (rename(tempApiConfPath.CStr(), apiConfPath.CStr()) < 0) {
494                 BOOST_THROW_EXCEPTION(posix_error()
495                         << boost::errinfo_api_function("rename")
496                         << boost::errinfo_errno(errno)
497                         << boost::errinfo_file_name(tempApiConfPath));
498         }
499
500         /* Zones configuration. */
501         Log(LogInformation, "cli", "Generating local zones.conf.");
502
503         /* Setup command hardcodes this as FQDN */
504         String endpointName = cn;
505
506         /* Different local zone name. */
507         std::cout << "\nLocal zone name [" + endpointName + "]: ";
508         std::getline(std::cin, answer);
509
510         if (answer.empty())
511                 answer = endpointName;
512
513         String zoneName = answer;
514         zoneName = zoneName.Trim();
515
516         /* Different parent zone name. */
517         std::cout << "Parent zone name [master]: ";
518         std::getline(std::cin, answer);
519
520         if (answer.empty())
521                 answer = "master";
522
523         String parentZoneName = answer;
524         parentZoneName = parentZoneName.Trim();
525
526         /* Global zones. */
527         std::vector<String> globalZones { "global-templates", "director-global" };
528
529         std::cout << "\nDo you want to specify additional global zones? [y/N]: ";
530
531         std::getline(std::cin, answer);
532         boost::algorithm::to_lower(answer);
533         choice = answer;
534
535 wizard_global_zone_loop_start:
536         if (choice.Contains("y")) {
537                 std::cout << "\nPlease specify the name of the global Zone: ";
538
539                 std::getline(std::cin, answer);
540
541                 if (answer.empty()) {
542                         std::cout << "\nName of the global Zone is required! Please retry.";
543                         goto wizard_global_zone_loop_start;
544                 }
545
546                 String globalZoneName = answer;
547                 globalZoneName = globalZoneName.Trim();
548
549                 if (std::find(globalZones.begin(), globalZones.end(), globalZoneName) != globalZones.end()) {
550                         std::cout << "The global zone '" << globalZoneName << "' is already specified."
551                                 << " Please retry.";
552                         goto wizard_global_zone_loop_start;
553                 }
554
555                 globalZones.push_back(globalZoneName);
556
557                 std::cout << "\nDo you want to specify another global zone? [y/N]: ";
558
559                 std::getline(std::cin, answer);
560                 boost::algorithm::to_lower(answer);
561                 choice = answer;
562
563                 if (choice.Contains("y"))
564                         goto wizard_global_zone_loop_start;
565         } else
566                 Log(LogInformation, "cli", "No additional global Zones have been specified");
567
568         /* Generate node configuration. */
569         NodeUtility::GenerateNodeIcingaConfig(endpointName, zoneName, parentZoneName, endpoints, globalZones);
570
571         if (cn != Utility::GetFQDN()) {
572                 Log(LogWarning, "cli")
573                         << "CN '" << cn << "' does not match the default FQDN '"
574                         << Utility::GetFQDN() << "'. Requires update for NodeName constant in constants.conf!";
575         }
576
577         NodeUtility::UpdateConstant("NodeName", cn);
578         NodeUtility::UpdateConstant("ZoneName", cn);
579
580         if (!ticket.IsEmpty()) {
581                 String ticketPath = ApiListener::GetCertsDir() + "/ticket";
582
583                 String tempTicketPath = Utility::CreateTempFile(ticketPath + ".XXXXXX", 0600, fp);
584
585                 if (!Utility::SetFileOwnership(tempTicketPath, user, group)) {
586                         Log(LogWarning, "cli")
587                                 << "Cannot set ownership for user '" << user
588                                 << "' group '" << group
589                                 << "' on file '" << tempTicketPath << "'. Verify it yourself!";
590                 }
591
592                 fp << ticket;
593
594                 fp.close();
595
596 #ifdef _WIN32
597                 _unlink(ticketPath.CStr());
598 #endif /* _WIN32 */
599
600                 if (rename(tempTicketPath.CStr(), ticketPath.CStr()) < 0) {
601                         BOOST_THROW_EXCEPTION(posix_error()
602                                 << boost::errinfo_api_function("rename")
603                                 << boost::errinfo_errno(errno)
604                                 << boost::errinfo_file_name(tempTicketPath));
605                 }
606         }
607
608         return 0;
609 }
610
611 int NodeWizardCommand::MasterSetup() const
612 {
613         std::string answer;
614         String choice;
615
616         std::cout << ConsoleColorTag(Console_Bold) << "Starting the Master setup routine...\n\n";
617
618         /* CN */
619         std::cout << ConsoleColorTag(Console_Bold)
620                 << "Please specify the common name" << ConsoleColorTag(Console_Normal)
621                 << " (CN) [" << Utility::GetFQDN() << "]: ";
622
623         std::getline(std::cin, answer);
624
625         if (answer.empty())
626                 answer = Utility::GetFQDN();
627
628         String cn = answer;
629         cn = cn.Trim();
630
631         std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen)
632                 << "Reconfiguring Icinga...\n"
633                 << ConsoleColorTag(Console_Normal);
634
635         /* check whether the user wants to generate a new certificate or not */
636         String existing_path = ApiListener::GetCertsDir() + "/" + cn + ".crt";
637
638         std::cout << ConsoleColorTag(Console_Normal)
639                 << "Checking for existing certificates for common name '" << cn << "'...\n";
640
641         if (Utility::PathExists(existing_path)) {
642                 std::cout << "Certificate '" << existing_path << "' for CN '"
643                         << cn << "' already existing. Skipping certificate generation.\n";
644         } else {
645                 std::cout << "Certificates not yet generated. Running 'api setup' now.\n";
646                 ApiSetupUtility::SetupMasterCertificates(cn);
647         }
648
649         std::cout << ConsoleColorTag(Console_Bold)
650                 << "Generating master configuration for Icinga 2.\n"
651                 << ConsoleColorTag(Console_Normal);
652
653         ApiSetupUtility::SetupMasterApiUser();
654
655         if (!FeatureUtility::CheckFeatureEnabled("api"))
656                 ApiSetupUtility::SetupMasterEnableApi();
657         else
658                 std::cout << "'api' feature already enabled.\n";
659
660         /* Setup command hardcodes this as FQDN */
661         String endpointName = cn;
662
663         /* Different zone name. */
664         std::cout << "\nMaster zone name [master]: ";
665         std::getline(std::cin, answer);
666
667         if (answer.empty())
668                 answer = "master";
669
670         String zoneName = answer;
671         zoneName = zoneName.Trim();
672
673         /* Global zones. */
674         std::vector<String> globalZones { "global-templates", "director-global" };
675
676         std::cout << "\nDo you want to specify additional global zones? [y/N]: ";
677
678         std::getline(std::cin, answer);
679         boost::algorithm::to_lower(answer);
680         choice = answer;
681
682 wizard_global_zone_loop_start:
683         if (choice.Contains("y")) {
684                 std::cout << "\nPlease specify the name of the global Zone: ";
685
686                 std::getline(std::cin, answer);
687
688                 if (answer.empty()) {
689                         std::cout << "\nName of the global Zone is required! Please retry.";
690                         goto wizard_global_zone_loop_start;
691                 }
692
693                 String globalZoneName = answer;
694                 globalZoneName = globalZoneName.Trim();
695
696                 if (std::find(globalZones.begin(), globalZones.end(), globalZoneName) != globalZones.end()) {
697                         std::cout << "The global zone '" << globalZoneName << "' is already specified."
698                                 << " Please retry.";
699                         goto wizard_global_zone_loop_start;
700                 }
701
702                 globalZones.push_back(globalZoneName);
703
704                 std::cout << "\nDo you want to specify another global zone? [y/N]: ";
705
706                 std::getline(std::cin, answer);
707                 boost::algorithm::to_lower(answer);
708                 choice = answer;
709
710                 if (choice.Contains("y"))
711                         goto wizard_global_zone_loop_start;
712         } else
713                 Log(LogInformation, "cli", "No additional global Zones have been specified");
714
715         /* Generate master configuration. */
716         NodeUtility::GenerateNodeMasterIcingaConfig(endpointName, zoneName, globalZones);
717
718         /* apilistener config */
719         std::cout << ConsoleColorTag(Console_Bold)
720                 << "Please specify the API bind host/port "
721                 << ConsoleColorTag(Console_Normal) << "(optional)"
722                 << ConsoleColorTag(Console_Bold) << ":\n";
723
724         std::cout << ConsoleColorTag(Console_Bold)
725                 << "Bind Host" << ConsoleColorTag(Console_Normal) << " []: ";
726
727         std::getline(std::cin, answer);
728
729         String bindHost = answer;
730         bindHost = bindHost.Trim();
731
732         std::cout << ConsoleColorTag(Console_Bold)
733                 << "Bind Port" << ConsoleColorTag(Console_Normal) << " []: ";
734
735         std::getline(std::cin, answer);
736
737         String bindPort = answer;
738         bindPort = bindPort.Trim();
739
740         /* api feature is always enabled, check above */
741         String apiConfPath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf";
742         NodeUtility::CreateBackupFile(apiConfPath);
743
744         std::fstream fp;
745         String tempApiConfPath = Utility::CreateTempFile(apiConfPath + ".XXXXXX", 0644, fp);
746
747         fp << "/**\n"
748                 << " * The API listener is used for distributed monitoring setups.\n"
749                 << " */\n"
750                 << "object ApiListener \"api\" {\n";
751
752         if (!bindHost.IsEmpty())
753                 fp << "  bind_host = \"" << bindHost << "\"\n";
754         if (!bindPort.IsEmpty())
755                 fp << "  bind_port = " << bindPort << "\n";
756
757         fp << "\n"
758                 << "  ticket_salt = TicketSalt\n"
759                 << "}\n";
760
761         fp.close();
762
763 #ifdef _WIN32
764         _unlink(apiConfPath.CStr());
765 #endif /* _WIN32 */
766
767         if (rename(tempApiConfPath.CStr(), apiConfPath.CStr()) < 0) {
768                 BOOST_THROW_EXCEPTION(posix_error()
769                         << boost::errinfo_api_function("rename")
770                         << boost::errinfo_errno(errno)
771                         << boost::errinfo_file_name(tempApiConfPath));
772         }
773
774         /* update constants.conf with NodeName = CN + TicketSalt = random value */
775         if (cn != Utility::GetFQDN()) {
776                 Log(LogWarning, "cli")
777                         << "CN '" << cn << "' does not match the default FQDN '"
778                         << Utility::GetFQDN() << "'. Requires an update for the NodeName constant in constants.conf!";
779         }
780
781         NodeUtility::UpdateConstant("NodeName", cn);
782         NodeUtility::UpdateConstant("ZoneName", cn);
783
784         String salt = RandomString(16);
785
786         NodeUtility::UpdateConstant("TicketSalt", salt);
787
788         return 0;
789 }