#49838 Ticket 49813 - Revised interactive installer
Closed 3 years ago by spichugi. Opened 5 years ago by mreynolds.
mreynolds/389-ds-base ticket49813  into  master

file modified
+1 -1
@@ -33,7 +33,7 @@ 

  

  # 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

@@ -418,14 +418,6 @@ 

                    Secure Port</label><input class="ds-input" type="text" value="636" id="create-inst-secureport" required />

                </div>

                <div>

-                 <label for="create-inst-user" class="ds-config-label" title="Server user that the server runs as">

-                   Server User</label><input class="ds-input" value="dirsrv" type="text" id="create-inst-user" required />

-               </div>

-               <div>

-                 <label for="create-inst-group" class="ds-config-label" title="Server group that the server runs as">

-                   Server Group</label><input class="ds-input" value="dirsrv" type="text" id="create-inst-group" required />

-               </div>

-               <div>

                  <label for="create-inst-rootdn" class="ds-config-label" title="The DN for the unrestricted  user">

                    Directory Manager DN</label><input class="ds-input" value="cn=Directory Manager" type="text" id="create-inst-rootdn" required />

                </div>
@@ -447,12 +439,16 @@ 

                    class="ds-input" type="text" id="backend-suffix">

                </div>

                <div>

-                 <p></p>

+                 <label for="create-sample-entries" class="ds-config-label" title="Create sample entries in the suffix">Create Sample Entries </label><input

+                   type="checkbox" class="ds-input ds-config-checkbox" id="create-sample-entries">

+               </div>

+               <hr>

+               <div>

                  <input type="checkbox" class="ds-config-checkbox" id="create-inst-tls" checked><label

                    for="create-inst-tls" class="ds-label" title="Create a self-signed certificate database">Create Self Signed Certificate DB</label>

                </div>

                <div id="create-inst-spinner" class="ds-center" hidden>

-                 <p></p>

+                 <hr>

                  <p><span class="spinner spinner-xs spinner-inline"></span> Creating instance...</p>

                </div>

              </div>

@@ -37,8 +37,8 @@ 

    "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 @@ 

    "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 @@ 

    $("#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 @@ 

          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 @@ 

            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 @@ 

                /*

                 * 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

file modified
+3 -2
@@ -86,8 +86,6 @@ 

      print('\n\nExiting...')

      sys.exit(0)

  

- signal.signal(signal.SIGINT, signal_handler)

- 

  

  if __name__ == '__main__':

  
@@ -115,6 +113,9 @@ 

          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

file modified
+13 -9
@@ -25,13 +25,16 @@ 

                      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 @@ 

      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 @@ 

          parser.print_help()

          sys.exit(1)

  

+     if not args.verbose:

+         signal.signal(signal.SIGINT, signal_handler)

+ 

      inst = DirSrv(verbose=args.verbose)

  

      result = False

file modified
+1 -2
@@ -48,8 +48,6 @@ 

      print('\n\nExiting...')

      sys.exit(0)

  

- signal.signal(signal.SIGINT, signal_handler)

- 

  

  if __name__ == '__main__':

      args = parser.parse_args()
@@ -80,6 +78,7 @@ 

      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:

file modified
+4 -3
@@ -71,9 +71,6 @@ 

      print('\n\nExiting...')

      sys.exit(0)

  

- signal.signal(signal.SIGINT, signal_handler)

- 

- 

  

  if __name__ == '__main__':

  
@@ -105,6 +102,10 @@ 

  

      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

  

@@ -52,14 +52,17 @@ 

          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:

@@ -37,9 +37,9 @@ 

      # 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 @@ 

                   '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 @@ 

              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 @@ 

                      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 @@ 

              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 @@ 

                  # 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 @@ 

  

          # 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 @@ 

                  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 @@ 

          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 @@ 

              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 @@ 

      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 @@ 

          # 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 @@ 

          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 @@ 

          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 @@ 

              # 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 @@ 

          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

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: ?

Maybe it makes sense to ask this question earlier (before the LDAPS port). If no self-signed cert will be created, enabling LDAPS doesn't make sense and the LDAPS port question can be skipped.

It seems that this scenario is currently also not covered in the INF file. Maybe, if the LDAPS port in the INF file is set to 0, TLS won't be enabled.

Yes and no, there is no "real" harm setting a secure port even if you aren't using TLS. Feel free to file a ticket to address LDAPS vs self-signed cert validation

rebased onto a276089f2c01e2da3f6f367dafe5deed02e66d8c

5 years ago

@mmuehlfeldrh - made the changes you requested to interactive installer and fromfile installer

rebased onto 0fdfaf8a9bda76d382ce502b1610943f77f9c046

5 years ago

The code looks good to me.

One small thing, we have an error in the cockpit instance install action:

usage: dscreate [-h] [-v] {fromfile,interactive,create-template} ...
dscreate: error: invalid choice: 'install' (choose from 'fromfile', 'interactive', 'create-template')

The code looks good to me.
One small thing, we have an error in the cockpit instance install action:
usage: dscreate [-h] [-v] {fromfile,interactive,create-template} ...
dscreate: error: invalid choice: 'install' (choose from 'fromfile', 'interactive', 'create-template')

This is fixed in the UI PR https://pagure.io/389-ds-base/pull-request/49839

Sorry these tickets are overlapping :-/

All CLI installation options work and the code looks good to me. You have my ack.
But maybe Marc has some additional info...

I'm reviewing this now too :)

I need to get a build done, so I'm going to merge this shortly. We can always tweak it later...

rebased onto c6ad83f82b76d82066a6922e4329e3e1d7e3a375

5 years ago

rebased onto 4f5f6bb

5 years ago

Pull-Request has been merged by mreynolds

5 years ago

That's okay, I'm happy with all of it @mreynolds, looks like you made most of the changes I suggested :)

389-ds-base is moving from Pagure to Github. This means that new issues and pull requests
will be accepted only in 389-ds-base's github repository.

This pull request has been cloned to Github as issue and is available here:
- https://github.com/389ds/389-ds-base/issues/2897

If you want to continue to work on the PR, please navigate to the github issue,
download the patch from the attachments and file a new pull request.

Thank you for understanding. We apologize for all inconvenience.

Pull-Request has been closed by spichugi

3 years ago