From 4f5f6bb54e2939de83aa3569d2b24d2ff809b69f Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Jul 17 2018 14:23:31 +0000 Subject: Ticket 49813 - Revised interactive installer Description: Removed some advanced settings from the install questions. Moved the signal handlers to non-verbose runs. Fixed some mixed case issues. Added option for sample entries. Added "interactive" argument, and restored "fromfile" from "install". https://pagure.io/389-ds-base/issue/49813 Reviewed by: mmuehlfeldrh & spichugi(Thanks!!) --- diff --git a/docker/389ds_poc/Dockerfile b/docker/389ds_poc/Dockerfile index 24fcdb3..216e1de 100644 --- a/docker/389ds_poc/Dockerfile +++ b/docker/389ds_poc/Dockerfile @@ -33,7 +33,7 @@ RUN dnf install -y 389-ds-base/dist/rpms/*389*.rpm && \ # Create the example setup inf. It's valid for containers! # Build the instance from the new installer tools. -RUN /usr/sbin/dscreate create-template > /root/ds-setup.inf && /usr/sbin/dscreate -v install /root/ds-setup.inf --containerised +RUN /usr/sbin/dscreate create-template > /root/ds-setup.inf && /usr/sbin/dscreate -v fromfile /root/ds-setup.inf --containerised # Finally add the volumes, they will inherit the contents of these directories. VOLUME /etc/dirsrv diff --git a/src/cockpit/389-console/index.html b/src/cockpit/389-console/index.html index 90f2949..211693b 100644 --- a/src/cockpit/389-console/index.html +++ b/src/cockpit/389-console/index.html @@ -418,14 +418,6 @@ Secure Port
- -
-
- -
-
@@ -447,12 +439,16 @@ class="ds-input" type="text" id="backend-suffix">
-

+ +
+
+
diff --git a/src/cockpit/389-console/js/servers.js b/src/cockpit/389-console/js/servers.js index 81dd944..c5ae837 100644 --- a/src/cockpit/389-console/js/servers.js +++ b/src/cockpit/389-console/js/servers.js @@ -37,8 +37,8 @@ var create_full_template = "config_dir = /etc/dirsrv/slapd-{instance_name}\n" + "data_dir = /usr/share\n" + "db_dir = /var/lib/dirsrv/slapd-{instance_name}/db\n" + - "user = USER\n" + - "group = GROUP\n" + + "user = dirsrv\n" + + "group = dirsrv\n" + "initconfig_dir = /etc/sysconfig\n" + "inst_dir = /usr/lib64/dirsrv/slapd-{instance_name}\n" + "instance_name = localhost\n" + @@ -64,8 +64,8 @@ var create_inf_template = "config_version = 2\n" + "full_machine_name = FQDN\n\n" + "[slapd]\n" + - "user = USER\n" + - "group = GROUP\n" + + "user = dirsrv\n" + + "group = dirsrv\n" + "instance_name = INST_NAME\n" + "port = PORT\n" + "root_dn = ROOTDN\n" + @@ -153,12 +153,11 @@ function clear_inst_form() { $("#create-inst-port").val("389"); $("#create-inst-secureport").val("636"); $("#create-inst-rootdn").val("cn=Directory Manager"); - $("#create-inst-user").val("dirsrv"); - $("#create-inst-group").val("dirsrv"); $("#rootdn-pw").val(""); $("#rootdn-pw-confirm").val(""); $("#backend-suffix").val(""); $("#backend-name").val(""); + $("#create-sample-entries").prop('checked', false); $("#create-inst-tls").prop('checked', true); clear_inst_input(); } @@ -1111,24 +1110,6 @@ $(document).ready( function() { setup_inf = setup_inf.replace('SECURE_PORT', secure_port); } - // DS User - var server_user = $("#create-inst-user").val(); - if (server_user == ""){ - report_err($("#create-inst-user"), 'You must provide the server user name'); - return; - } else { - setup_inf = setup_inf.replace('USER', server_user); - } - - // DS Group - var server_group = $("#create-inst-group").val(); - if (server_group == ""){ - report_err($("#create-inst-group"), 'You must provide the server group name'); - return; - } else { - setup_inf = setup_inf.replace('GROUP', server_group); - } - // Root DN var server_rootdn = $("#create-inst-rootdn").val(); if (server_rootdn == ""){ @@ -1182,6 +1163,11 @@ $(document).ready( function() { report_err($("#backend-suffix"), 'Invalid DN for Backend Suffix'); return; } + if ( $("#create-sample-entries").is(":checked") ) { + setup_inf += '\nsample_entries = yes\n'; + } else { + setup_inf += '\nsample_entries = no\n'; + } } /* @@ -1231,7 +1217,7 @@ $(document).ready( function() { /* * Next, create the instance... */ - cmd = [DSCREATE, 'install', setup_file]; + cmd = [DSCREATE, 'fromfile', setup_file]; cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV] }).fail(function(ex) { // Failed to create the new instance! cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password diff --git a/src/lib389/cli/dsconf b/src/lib389/cli/dsconf index 5b56b7a..9cf89b3 100755 --- a/src/lib389/cli/dsconf +++ b/src/lib389/cli/dsconf @@ -86,8 +86,6 @@ def signal_handler(signal, frame): print('\n\nExiting...') sys.exit(0) -signal.signal(signal.SIGINT, signal_handler) - if __name__ == '__main__': @@ -115,6 +113,9 @@ if __name__ == '__main__': parser.print_help() sys.exit(1) + if not args.verbose: + signal.signal(signal.SIGINT, signal_handler) + # Connect # We don't need a basedn, because the config objects derive it properly inst = None diff --git a/src/lib389/cli/dscreate b/src/lib389/cli/dscreate index 17708d0..dca37c0 100755 --- a/src/lib389/cli/dscreate +++ b/src/lib389/cli/dscreate @@ -25,13 +25,16 @@ parser.add_argument('-v', '--verbose', action='store_true', default=False, dest='verbose') subparsers = parser.add_subparsers(help="action") -install_parser = subparsers.add_parser('install', help="Create an instance of Directory Server from an inf answer file") -install_parser.add_argument('file', nargs="?", default=None, help="Inf file to use with prepared answers. You can generate an example of this with 'dscreate create-template'") -install_parser.add_argument('-n', '--dryrun', help="Validate system and configurations only. Do not alter the system.", - action='store_true', default=False) -install_parser.add_argument('-c', '--containerised', help="Indicate to the installer that this is running in a container. Used to disable systemd native components, even if they are installed.", - action='store_true', default=False) -install_parser.set_defaults(func=cli_instance.instance_create) +fromfile_parser = subparsers.add_parser('fromfile', help="Create an instance of Directory Server from an inf answer file") +fromfile_parser.add_argument('file', help="Inf file to use with prepared answers. You can generate an example of this with 'dscreate create-template'") +fromfile_parser.add_argument('-n', '--dryrun', help="Validate system and configurations only. Do not alter the system.", + action='store_true', default=False) +fromfile_parser.add_argument('-c', '--containerised', help="Indicate to the installer that this is running in a container. Used to disable systemd native components, even if they are installed.", + action='store_true', default=False) +fromfile_parser.set_defaults(func=cli_instance.instance_create) + +interactive_parser = subparsers.add_parser('interactive', help="Start interactive installer for Directory Server installation") +interactive_parser.set_defaults(func=cli_instance.instance_create_interactive) template_parser = subparsers.add_parser('create-template', help="Display an example inf answer file, or provide a file name to write it to disk.") template_parser.add_argument('template_file', nargs="?", default=None, help="Write example template to this file") @@ -43,8 +46,6 @@ def signal_handler(signal, frame): print('\n\nExiting interactive installation...') sys.exit(0) -signal.signal(signal.SIGINT, signal_handler) - if __name__ == '__main__': args = parser.parse_args() @@ -64,6 +65,9 @@ if __name__ == '__main__': parser.print_help() sys.exit(1) + if not args.verbose: + signal.signal(signal.SIGINT, signal_handler) + inst = DirSrv(verbose=args.verbose) result = False diff --git a/src/lib389/cli/dsctl b/src/lib389/cli/dsctl index f5e8943..69a028e 100755 --- a/src/lib389/cli/dsctl +++ b/src/lib389/cli/dsctl @@ -48,8 +48,6 @@ def signal_handler(signal, frame): print('\n\nExiting...') sys.exit(0) -signal.signal(signal.SIGINT, signal_handler) - if __name__ == '__main__': args = parser.parse_args() @@ -80,6 +78,7 @@ if __name__ == '__main__': if args.verbose: insts = inst.list(serverid=args.instance) else: + signal.signal(signal.SIGINT, signal_handler) try: insts = inst.list(serverid=args.instance) except PermissionError: diff --git a/src/lib389/cli/dsidm b/src/lib389/cli/dsidm index a856a76..c65f6a8 100755 --- a/src/lib389/cli/dsidm +++ b/src/lib389/cli/dsidm @@ -71,9 +71,6 @@ def signal_handler(signal, frame): print('\n\nExiting...') sys.exit(0) -signal.signal(signal.SIGINT, signal_handler) - - if __name__ == '__main__': @@ -105,6 +102,10 @@ if __name__ == '__main__': if dsrc_inst['basedn'] is None: log.error("Must provide a basedn!") + sys.ext(1) + + if not args.verbose: + signal.signal(signal.SIGINT, signal_handler) ldapurl = args.instance diff --git a/src/lib389/lib389/cli_ctl/instance.py b/src/lib389/lib389/cli_ctl/instance.py index fe8e039..bd5d37d 100644 --- a/src/lib389/lib389/cli_ctl/instance.py +++ b/src/lib389/lib389/cli_ctl/instance.py @@ -52,14 +52,17 @@ def instance_status(inst, log, args): log.info("Instance is not running") +def instance_create_interactive(inst, log, args): + sd = SetupDs(args.verbose, False, log, False) + return sd.create_from_cli() + + def instance_create(inst, log, args): if args.containerised: log.debug("Containerised features requested.") + sd = SetupDs(args.verbose, args.dryrun, log, args.containerised) - if args.file is None: - # Interactive installer - return sd.create_from_cli() - elif sd.create_from_inf(args.file): + if sd.create_from_inf(args.file): # print("Sucessfully created instance") return True else: diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py index 04cad30..b1a5cbe 100644 --- a/src/lib389/lib389/instance/setup.py +++ b/src/lib389/lib389/instance/setup.py @@ -37,9 +37,9 @@ def get_port(port, default_port, secure=False): # Get the port number for the interactive installer and validate it while 1: if secure: - val = input('\nEnter Secure Port Number [{}]: '.format(default_port)) + val = input('\nEnter secure port number [{}]: '.format(default_port)) else: - val = input('\nEnter Port Number [{}]: '.format(default_port)) + val = input('\nEnter port number [{}]: '.format(default_port)) if val != "" or default_port == "": # Validate port is number and in a valid range @@ -239,12 +239,12 @@ class SetupDs(object): 'schema_dir': ds_paths.schema_dir} # Start asking questions, beginning with the hostname... - val = input('\nEnter System\'s Hostname [{}]: '.format(general['full_machine_name'])) + val = input('\nEnter system\'s hostname [{}]: '.format(general['full_machine_name'])) if val != "": general['full_machine_name'] = val # Strict host name checking - msg = ("\nUse strict hostname verification (set to \"off\" if using GSSAPI behind a load balancer) [on]: ") + msg = ("\nUse strict hostname verification (set to \"no\" if using GSSAPI behind a load balancer) [yes]: ") while 1: val = input(msg) if val != "": @@ -261,65 +261,10 @@ class SetupDs(object): else: break - # Get and check user - while 1: - val = input('\nSystem user the server will run as [{}]: '.format(slapd['user'])) - if val != "": - # Check is user exists - try: - pwd.getpwnam(val) - except KeyError: - print("User \"{}\" does not exist, please choose an existing user".format(val)) - continue - slapd['user'] = val - else: - # Use default, but double check dirsrv exists... - try: - pwd.getpwnam(slapd['user']) - except KeyError: - print("User \"{}\" does not exist, please choose an existing user".format(val)) - continue - break - - # Get and check the group - while 1: - val = input('\nSystem group the server will belong to [{}]: '.format(slapd['group'])) - if val != "": - # Check is user exists - try: - grp.getgrnam(val) - except KeyError: - print("Group \"{}\" does not exist, please choose an existing group".format(val)) - continue - slapd['group'] = val - else: - # Use default, but double check dirsrv exists... - try: - grp.getgrnam(slapd['user']) - except KeyError: - print("Group \"{}\" does not exist, please choose an existing group".format(val)) - continue - break - - # Prefix - while 1: - val = input('\nInstallation prefix [{}]: '.format(slapd['prefix'])) - if val != "": - if not val.startswith('/'): - print("Not a valid path\n") - continue - if not os.path.isdir(val): - print("Prefix directory does not exist") - continue - slapd['prefix'] = val - break - else: - break - # Instance name - adjust defaults once set while 1: slapd['instance_name'] = general['full_machine_name'].split('.', 1)[0] - val = input('\nEnter The Server\'s Indentifer Name [{}]: '.format(slapd['instance_name'])) + val = input('\nEnter the instance name [{}]: '.format(slapd['instance_name'])) if val != "": if ' ' in val: print("Server identifier can not contain a space") @@ -335,10 +280,7 @@ class SetupDs(object): continue # Check if server id is taken - if slapd['prefix'] != "/usr": - inst_dir = slapd['prefix'] + slapd['config_dir'] + "/" + val - else: - inst_dir = slapd['config_dir'] + "/" + val + inst_dir = slapd['config_dir'] + "/" + val if os.path.isdir(inst_dir): print("Server identifier \"{}\" is already taken, please choose a new name".format(val)) continue @@ -370,17 +312,9 @@ class SetupDs(object): port = get_port(slapd['port'], "") slapd['port'] = port - # Secure Port - if not socket_check_open('::1', slapd['secure_port']): - port = get_port(slapd['secure_port'], slapd['secure_port'], secure=True) - else: - # Port 636 is already taken, pick another port - port = get_port(slapd['secure_port'], "", secure=True) - slapd['secure_port'] = port - # Self-Signed Cert DB while 1: - val = input('\nCreate Self-Signed Certificate Database [yes]: ') + val = input('\nCreate self-signed certificate database [yes]: ') if val != "": if val.lower() == 'no' or val.lower() == "n": slapd['self_sign_cert'] = False @@ -395,6 +329,15 @@ class SetupDs(object): # use default break + # Secure Port (only if using self signed cert) + if slapd['self_sign_cert']: + if not socket_check_open('::1', slapd['secure_port']): + port = get_port(slapd['secure_port'], slapd['secure_port'], secure=True) + else: + # Port 636 is already taken, pick another port + port = get_port(slapd['secure_port'], "", secure=True) + slapd['secure_port'] = port + # Root DN while 1: val = input('\nEnter Directory Manager DN [{}]: '.format(slapd['root_dn'])) @@ -412,12 +355,12 @@ class SetupDs(object): # Root DN Password while 1: - rootpw1 = getpass.getpass('\nEnter Directory Manager Password: ') + rootpw1 = getpass.getpass('\nEnter the Directory Manager password: ') if rootpw1 == '': print('Password can not be empty') continue - rootpw2 = getpass.getpass('Confirm Directory Manager Password: ') + rootpw2 = getpass.getpass('Confirm the Directory Manager Password: ') if rootpw1 != rootpw2: print('Passwords do not match') continue @@ -454,6 +397,22 @@ class SetupDs(object): backend['suffix'] = suffix break + # Add sample entries? + while 1: + val = input("\nCreate sample entries in the suffix [no]: ".format(suffix)) + if val != "": + if val.lower() == "no" or val.lower() == "n": + break + if val.lower() == "yes" or val.lower() == "y": + backend['sample_entries'] = INSTALL_LATEST_CONFIG + break + + # Unknown value + print ("Value \"{}\" is invalid, please use \"yes\" or \"no\"".format(val)) + continue + else: + break + # Are you ready? while 1: val = input('\nAre you ready to install? [no]: ') @@ -476,8 +435,7 @@ class SetupDs(object): Will trigger a create from the settings stored in inf_path """ # Get the inf file - if self.verbose: - self.log.info("Using inf from %s", inf_path) + self.log.debug("Using inf from %s" % inf_path) if not os.path.isfile(inf_path): self.log.error("%s is not a valid file path", inf_path) return False @@ -489,9 +447,7 @@ class SetupDs(object): self.log.error("Exception %s occured", e) return False - if self.verbose: - self.log.info("Configuration %s", config.sections()) - + self.log.debug("Configuration %s" % config.sections()) (general, slapd, backends) = self._validate_ds_config(config) # Actually do the setup now. @@ -502,8 +458,7 @@ class SetupDs(object): def _prepare_ds(self, general, slapd, backends): assert_c(general['defaults'] is not None, "Configuration defaults in section [general] not found") - if self.verbose: - self.log.info("PASSED: using config settings %s", general['defaults']) + self.log.debug("PASSED: using config settings %s" % general['defaults']) # Validate our arguments. assert_c(slapd['user'] is not None, "Configuration user in section [slapd] not found") # check the user exists @@ -516,22 +471,19 @@ class SetupDs(object): # Check that we are running as this user / group, or that we are root. assert_c(os.geteuid() == 0 or getpass.getuser() == slapd['user'], "Not running as user root or %s, may not have permission to continue" % slapd['user']) - if self.verbose: - self.log.info("PASSED: user / group checking") + self.log.debug("PASSED: user / group checking") assert_c(general['full_machine_name'] is not None, "Configuration full_machine_name in section [general] not found") assert_c(general['strict_host_checking'] is not None, "Configuration strict_host_checking in section [general] not found") if general['strict_host_checking'] is True: # Check it resolves with dns assert_c(socket.gethostbyname(general['full_machine_name']), "Strict hostname check failed. Check your DNS records for %s" % general['full_machine_name']) - if self.verbose: - self.log.info("PASSED: Hostname strict checking") + self.log.debug("PASSED: Hostname strict checking") assert_c(slapd['prefix'] is not None, "Configuration prefix in section [slapd] not found") if (slapd['prefix'] != ""): assert_c(os.path.exists(slapd['prefix']), "Prefix location '%s' not found" % slapd['prefix']) - if self.verbose: - self.log.info("PASSED: prefix checking") + self.log.debug("PASSED: prefix checking") # We need to know the prefix before we can do the instance checks assert_c(slapd['instance_name'] is not None, "Configuration instance_name in section [slapd] not found") @@ -544,8 +496,7 @@ class SetupDs(object): insts = ds.list(serverid=slapd['instance_name']) assert_c(len(insts) == 0, "Another instance named '%s' may already exist" % slapd['instance_name']) - if self.verbose: - self.log.info("PASSED: instance checking") + self.log.debug("PASSED: instance checking") assert_c(slapd['root_dn'] is not None, "Configuration root_dn in section [slapd] not found") # Assert this is a valid DN @@ -571,17 +522,15 @@ class SetupDs(object): self._raw_secure_password = password_generate() self._secure_password = password_hash(self._raw_secure_password, bin_dir=slapd['bin_dir']) - if self.verbose: - self.log.info("INFO: temp root password set to %s", self._raw_secure_password) - self.log.info("PASSED: root user checking") + self.log.debug("INFO: temp root password set to %s" % self._raw_secure_password) + self.log.debug("PASSED: root user checking") assert_c(slapd['port'] is not None, "Configuration port in section [slapd] not found") assert_c(socket_check_open('::1', slapd['port']) is False, "port %s is already in use" % slapd['port']) # We enable secure port by default. assert_c(slapd['secure_port'] is not None, "Configuration secure_port in section [slapd] not found") assert_c(socket_check_open('::1', slapd['secure_port']) is False, "secure_port %s is already in use" % slapd['secure_port']) - if self.verbose: - self.log.info("PASSED: network avaliability checking") + self.log.debug("PASSED: network avaliability checking") # Make assert_cions of the paths? @@ -685,7 +634,8 @@ class SetupDs(object): # Should create the symlink we need, but without starting it. subprocess.check_call(["/usr/bin/systemctl", "enable", - "dirsrv@%s" % slapd['instance_name']], stderr=subprocess.DEVNULL) + "dirsrv@%s" % slapd['instance_name']]) + # Else we need to detect other init scripts? # Bind sockets to our type? @@ -789,8 +739,8 @@ class SetupDs(object): base_config_inst.apply_config(install=True) # Setup TLS with the instance. - ds_instance.config.set('nsslapd-secureport', '%s' % slapd['secure_port']) if slapd['self_sign_cert']: + ds_instance.config.set('nsslapd-secureport', '%s' % slapd['secure_port']) ds_instance.config.set('nsslapd-security', 'on') # Create the backends as listed