Linux.Utils.InstallDeb

Install a deb package and configure it with debconf answers. The package may either be specified by name or be an uploaded file. If the package already exists, it may be optionally reconfigured with debconf answers.


name: Linux.Utils.InstallDeb
author: Andreas Misje – @misje
description: |
   Install a deb package and configure it with debconf answers. The package
   may either be specified by name or be an uploaded file. If the package
   already exists, it may be optionally reconfigured with debconf answers.

type: CLIENT

required_permissions:
   - EXECVE

reference:
   - https://manpages.debian.org/bookworm/debconf-doc/debconf-devel.7.en.html#Type

parameters:
   - name: DebName
     description: |
        Package to install (by name). Ignored if DebFile is set. An absolute path
        to a deb file that already exists on the system is also accepted.

   - name: DebFile
     description: |
        Package to install (by file). Remember to click "Upload"! When set,
        DebName is ignored. Use DebName with an absolute file path if the
        file already exists on the system and does not need to be uploaded.
     type: upload

   - name: UpdateSources
     description: |
        Run `apt-get update` before installing the package. This is not necessary
        if the package has no dependencies, and it should be disabled if there
        is no Internet.
     type: bool
     default: True

   - name: ForceConfNew
     type: bool
     description: |
        Use the configuration delivered by the package instead of keeping the
        local changes.

   - name: ReconfigureIfInstalled
     type: bool
     description: |
        If the package is already installed, run pre-seed debconf and
        `dpkg-reconfigure` instead.

   - name: DebConfValues
     type: csv
     description: |
        debconf is a system used by many packages for interactive configuration.
        When using a non-interactive frontend (like this artifact), answers may
        by provided as a "pre-seed" file. Example line:

        "wireshark-common/install-setuid,boolean,false"
     default: |
        Key,Type,Value

column_types:
  - name: Stdout
    type: nobreak

  - name: Stderr
    type: nobreak

sources:
  - precondition:
      SELECT OS From info() where OS = 'linux'

    query: |
     LET Package <= if(
         condition=DebFile,
         then=tempfile(data=DebFile, extension='_amd64.deb'),
         else=DebName)

     /* The file name is lost from the uploaded file, so extract it from the
        package instead: */
     LET PackageInfo = SELECT Stdout
       FROM execve(argv=['/usr/bin/dpkg-deb', '--field', Package, 'Package'])

     LET PackageName = if(
         condition=DebFile,
         then=PackageInfo[0].Stdout[:-1], // remove "\n"
         else=DebName)

     /* The file format is "package_name question type answer": */
     LET PreSeedLines = SELECT
                               join(
                                 sep=' ',
                                 array=(PackageName, Key, Type, Value, )) AS Line
       FROM DebConfValues

     LET PreSeedFile <= tempfile(data=join(sep='\n', array=PreSeedLines.Line))

     LET AptOpts <= ('-f', '-y', '-o', 'Debug::pkgProblemResolver=yes', '--no-install-recommends', ) + if(
         condition=ForceConfNew,
         then=('-o', 'Dpkg::Options::="--force-confnew"', ),
         else=[])

     LET PreSeed = SELECT
                          'Pre-seed debconf' AS Step,
                          *
       FROM if(
         condition=DebConfValues,
         then={
           SELECT *
           FROM execve(argv=['/usr/bin/debconf-set-selections', PreSeedFile, ])
           WHERE log(
             message=format(
               format='Pre-seeding %v',
               args=[PackageName, ]),
             level='INFO')
         })

     LET Install = SELECT *
       FROM chain(
         a_update={
           SELECT
                  'Update index' AS Step,
                  *
           FROM if(
             condition=UpdateSources,
             then={
               SELECT *
               FROM execve(argv=['/usr/bin/apt-get', '-y', 'update'])
               WHERE log(
                 message='Updating package index before installing',
                 level='INFO')
             })
         },
         b_debconf=PreSeed,
         c_install={
           SELECT
                  'Installing package' AS Step,
                  *
           FROM execve(argv=('/usr/bin/apt-get', 'install', ) + AptOpts + (Package, ))
           WHERE log(
             message=format(
               format='Installing deb package %v',
               args=[PackageName, ]),
             level='INFO')
         })

     LET IsInstalled = SELECT *
       FROM stat(
         filename=path_join(
           components=('/var/lib/dpkg/info', PackageName + '.list')))
       WHERE log(
         message=format(
           format='Package %v is already installed',
           args=[PackageName, ]),
         level='INFO')

     SELECT *
     FROM if(
       condition=IsInstalled,
       then=if(
         condition=ReconfigureIfInstalled,
         then={
           SELECT *
           FROM chain(
             a_debconf=PreSeed,
             b_reconfigure={
               SELECT
                      'Reconfiguring package' AS Step,
                      *
               FROM execve(argv=['/usr/sbin/dpkg-reconfigure', PackageName, ],
                           env=dict(
                             DEBIAN_FRONTEND='noninteractive'))
               WHERE log(
                 message=format(
                   format='Reconfiguring deb package %v',
                   args=[PackageName, ]),
                 level='INFO')
             })
         }),
       else=Install)