Scripting

Contents

  1. Contents
  2. Scripting on APT
    1. Why Lua?
    2. Learning Lua
    3. What about the Python bindings?
  3. Understanding slots
  4. Available slots
    1. Scripts::Init
    2. Scripts::Cache::Init
    3. Scripts::Acquire::Archive::Done
    4. Scripts::PM::Pre
    5. Scripts::PM::Post
    6. Scripts::AptGet::Script
    7. Scripts::AptCache::Script
    8. Scripts::AptGet::Install::TranslateArg
    9. Scripts::AptGet::Install::PreResolve
    10. Scripts::AptGet::Install::PostResolve
    11. Scripts::AptGet::Install::SelectPackage
    12. Scripts::AptGet::Upgrade
    13. Scripts::AptGet::DistUpgrade
    14. Scripts::AptGet::Update::Pre
    15. Scripts::AptGet::Update::Post
    16. Scripts::AptShell::Init
    17. Scripts::AptShell::Command
    18. Scripts::AptGet::Command
    19. Scripts::AptCache::Command
    20. Scripts::Cdrom::Mount
    21. Scripts::Cdrom::Umount
  5. Standalone scripts
  6. Available API
    1. Common variables
    2. confget(confkey, default="")
    3. confgetlist(confkey)
    4. confset(confkey, value, cnd=0)
    5. confexists(confkey)
    6. confclear(confkey)
    7. pkgfind(name)
    8. pkgname(pkg)
    9. pkgid(pkg)
    10. pkgsummary(pkg)
    11. pkgdescr(pkg)
    12. pkglist()
    13. pkgisvirtual(pkg)
    14. pkgvercur(pkg)
    15. pkgverinst(pkg)
    16. pkgvercand(pkg)
    17. pkgverlist(pkg)
    18. verpkg(ver)
    19. verstr(ver)
    20. verarch(ver)
    21. verid(pkg)
    22. verisonline(ver)
    23. verprovlist(ver)
    24. verdeplist(ver)
    25. verstrcmp(ver1, ver2)
    26. verstrcmp(ver1, arch1, ver2, arch2)
    27. markkeep(pkg)
    28. markinstall(pkg)
    29. markremove(pkg)
    30. markupgrade()
    31. markdistupgrade()
    32. statkeep(pkg)
    33. statremove(pkg)
    34. statinstall(pkg)
    35. statnewinstall(pkg)
    36. statupgrade(pkg)
    37. statupgradable(pkg)
    38. statdowngrade(pkg)
    39. statnowbroken(pkg)
    40. statinstbroken(pkg)
    41. statstr(pkg)
    42. apterror(errorstring)
    43. aptwarning(warningstring)
    44. _(string)
  7. Turning a script into a subcommand
  8. Interactive interpreter
  9. How to deliver scripts
  10. Do you miss something?

Scripting on APT

There are many interesting tasks that can't be achieved easily using just the command line APT tools. With scripting capabilities, more advanced upgrading setups can easily be done, and it's possible to customize operations to your taste.

Why Lua?

A number of different languages have been evaluated before Lua was chosen. It was chosen because it's not a general purpose language. Lua is very small, fast, and perfect for embedding. No other language compares to Lua in that field.

Learning Lua

Details about the Lua language itself won't be covered in this document. To learn about the Lua language, have a look at the Lua documentation.

What about the Python bindings?

Python bindings are still experimental, in development, and will not be discarded. They were born with a different purpose in mind. Python bindings were intended to provide APT capabilities to Python programs, while Lua was intended to provide extension capabilities to APT itself.

Understanding slots

APT extension system is based on "slots". These slots are places during APT execution where extensions are called. A single slot may have multiple extensions attached. As an example, the following configuration will attach several scripts to the Init slot.

Scripts::Init { "extension1.lua"; "extension2.lua"; "/some/path/extension3.lua"; }

The first and the second extensions must be in /usr/share/apt/scripts (or whatever Dir::Bin::Scripts was set to), and the last one in /some/path. As usual, these options could be set as

Scripts::Init:: "extension1.lua"; Scripts::Init:: "extension2.lua"; Scripts::Init:: "/some/path/extension3.lua";

Or in the command line as -o Scripts::Init::="extension1.lua". Not every slot has access to the same facilities. In some slots, for example, the package cache may be unavailable, so it's not possible to access functionality related to it (package searching, etc).

Available slots

The following slots are available. Notice that slots which work in apt-get subcommands also work in the apt-shell counterpart.

Scripts::Init

Runs just after APT was initialized. The configuration was already initialized, but there's no cache at that point.

Scripts::Cache::Init

Runs whenever a cache is initialized. Extensions plugged in this slot will be able to check and change the cache before any application touches it.

Scripts::Acquire::Archive::Done

Runs in the exact moment a package is successfully acquired. Extensions plugged into this slot have access to two global variables: acquire_filename is the file path in the current filesystem; and acquire_error will make the acquire process fail if set to a valid string.

Scripts::PM::Pre

Runs just before changes are committed to the system. This slot has access to several global variables: files_install is a list of filenames which are going to be installed; names_remove is a list of package names which are going to be removed; pkgs_install is a list of packages which are going to be installed; pkgs_remove is a list of packages which are going to be removed.

Scripts::PM::Post

Runs just after changes are committed to the system. This slot have the same global variables available as in Scripts::PM::Pre. Additionally transaction_success variable contains a boolean value stating whether the package transaction succeeded or not.

Scripts::AptGet::Script

Runs when the apt-get script subcommand is used. Scripts passed in the command line are appended to that slot. See the Standalone scripts section for more information.

Scripts::AptCache::Script

Same as Scripts::Apt::Script, but for apt-cache.

Scripts::AptGet::Install::TranslateArg

Runs when using the apt-get install subcommand, whenever the argument passed for installation is not found in the cache as a package name. It is useful to extend the way packages are selected with custom scripts. An example would be to translate file names to package names (this example is actually implemented and available in the contrib/apt-files/ subdirectory).

Scripts::AptGet::Install::PreResolve

Runs when using the apt-get install or apt-get remove subcommands, just after the given packages were selected for installation/deletion, and before trying to solve possible problems created by these actions. Cache operations in this slot will run with problem solving disabled, as every problem introduced will be checked in a single pass.

Scripts::AptGet::Install::PostResolve

Runs when using the apt-get install or apt-get remove subcommands, just after the problem solving pass. Extensions plugged into this slot will use the same problem resolver as was used in the problem solving step executed. It means that packages selected for installation by the user in the command line will still be protected, so that you don't change them as a side effect of other operation (you can still change them directly, if you want).

Scripts::AptGet::Install::SelectPackage

Runs when a virtual package is manually selected for installation, and there are multiple possible solutions for the given virtual package. Extensions plugged in this slot are able to select a given package as the correct selection, and also to change the package names which are going to be shown to the user, if no obvious solution was found. The following global variables are available to scripts plugged in this slot: virtualname is the package name originally selected for installation by the user; packages is the list of packages detected as good solutions for the asked package (its length is always greater than 1); packagenames is the list of the package names in the packages variable, and will be shown to the user if no package is selected; and selected should be set to the chosen package, if one was chosen at all.

Scripts::AptGet::Upgrade

Runs when using the apt-get upgrade subcommand, just after the cache was marked with the upgrade pass.

Scripts::AptGet::DistUpgrade

Runs when using the apt-get dist-upgrade subcommand, just after the cache was marked with the dist-upgrade pass.

Scripts::AptGet::Update::Pre

Runs just after apt-get update starts running. The available cache hasn't suffered any changes yet.

Scripts::AptGet::Update::Post

Runs after apt-get update finishes its job, already with the new cache.

Scripts::AptShell::Init

Runs just after apt-shell initializes.

Scripts::AptGet::Command

Allows one to introduce new commands in apt-get and apt-shell Extensions plugged into this slot have access to two global variables: command_args, with the command line arguments (without options); and command_consume, which if set to 1 will inform the system that this command was a valid command. In apt-get, the global variable commit_ask is also available to tell if the changes done in the cache should be confirmed by the user. The default value is 1.

Scripts::AptCache::Command

Equivalent to Scripts::Apt::Command, but for apt-cache and apt-shell. This should be used to introduced new informative commands, which do not change the cache state.

Scripts::Cdrom::Mount

Allows one to introduce a custom mounting mechanism. If the done global variable is set to 1 in this script, the default mount behavior won't be executed.

Scripts::Cdrom::Umount

Same as Scripts::Cdrom::Mount, but for umount operations.

Standalone scripts

You may control an operation completely with scripts. To do that use the apt-get script command, or the apt-cache script one. After all extensions passed in the command line are executed, a commit of the cache changes is tried. Extensions may set commit_ask to 0 if they don't want to ask the user for confirmation before the changes are commited to the system. Every script passed in the command line is appended to the Scripts::AptGet::Script slot.

Example, try to execute /usr/share/apt/scripts/script.lua

apt-get script script.lua

Example, try to execute ~/otherscript.lua

apt-get script ~/otherscript.lua

You may also create executable apt-get scripts using a bang line, like this:

#!/usr/bin/apt-get script print("Hello world!")

For scripts that do not change the cache, use the apt-cache script command, which works in a very similar way, and scripts available to either apt-get or apt-cache may also be executed inside apt-shell.

Available API

In this section the API available to extensions will be presented.

In the following description you will notice that currently there are five kinds of data currently used by the APT API:

nil This is the common nil type available in Lua, and is used to present a false or unavailable value.
numbers Most of the time used to enable or disable a feature.
string Normal strings.
package This is a structure representing a package in the APT system. Notice that for the APT system, virtual packages such as dependencies are also represented as packages.
version This is a structure representing a version known to a given package.

As a bonus, most of the functions accepting a package as an argument also accept the package name. Notice that in environments where the package is used multiple times, it's adivisable that you store that package in some variable.

Common variables

All scripts have access to some common global variables. They are:

script_slot This is the slot used to run that script. This option may be used to hook the same script into different slots, and have special actions depending on the slot in use.

confget(confkey, default="")

Get a configuration option. The option may be suffixed by a key suffix: /f for files; /d for directories; and /b for booleans (returns "true" or "false").

Examples: if confget("APT::Architecture") === "powerpc" then print("This is a powerpc!") end confset("APT::Test", "1") if confget("APT::Test/b") === "true" then print("Test is enabled!") end value = confget("APT::None", "default")

confgetlist(confkey)

Get the list of strings defined in the given configuration option. This is useful for options like RPM::Hold, which are set to a list in the command line or in the configuration file, like the following example:

RPM::Hold { "^pkgone$"; "^pkgtwo$"; }; RPM::Hold:: "^pkgthree$";

Examples:

values = confgetlist("RPM::Allow-Duplicated") table.foreach(values, print)

confset(confkey, value, cnd=0)

Set the configuration key confkey to value. If cnd is 1, confkey will only be set if it doesn't yet exist. As is usual with the APT configuration system, if the suffix :: is appended to some configuration key, that key will be considered as a list, and value will be appended to that list.

Examples: confset("APT::Test", "Will be set") confset("APT::Test", "Won't be set", 1) confset("APT::Test", "Will be set again") confset("APT::TestList::", "Will be appended")

confexists(confkey)

Return 1 if confkey exists, nil otherwise.

Examples: if confexists("APT::Test") then print("Is set!") end

confclear(confkey)

Clear the entire tree under confkey. Notice that confkey itself won't be removed, but will be set to "".

Examples: confclear("APT::Test")

pkgfind(name)

Return the package named name, if found, and nil otherwise.

Examples: pkg = pkgfind("apt") if pkg then print("That package exists!") end

pkgname(pkg)

Return the name of package pkg.

Examples: pkg = pkgfind("apt") if pkg then print("Package "..pkgname(pkg).." exists!") end

pkgid(pkg)

Returns an integer identifier which is guaranted to be unique for the given package up to the moment that the cache is rebuilt. This is useful for hashing purposes, for example, since currently Lua doesn't support hashing of custom types.

Examples: seenpkg = {} for i, pkg in ipairs(pkglist()) then seenpkg[pkgid(pkg)] = true end

pkgsummary(pkg)

Return the summary of package pkg.

Examples: pkg = pkgfind("apt") if pkg then print(pkgname(pkg).." - "..pkgsummary(pkg)) end

pkgdescr(pkg)

Return the description of package pkg.

Examples: pkg = pkgfind("apt") if pkg then print(pkgdescr(pkg)) end

pkglist()

Return a list of all packages in the system. Notice that for the APT system, virtual packages such as dependencies are also considered to be packages.

Examples: list = pkglist() for i, pkg in pairs(list) do print(i..": "..pkgname(pkg)) end

pkgisvirtual(pkg)

Return 1 if pkg is a virtual package (no versions available), and nil otherwise.

Examples: pkg = pkgfind("webserver") if pkgisvirtual(pkg) then print("That's a virtual package!") end

pkgvercur(pkg)

Return the version (not a string!) of pkg which is currently installed in the system, or nil if there's no current version.

Examples: pkg = pkgfind("apt") if pkg then ver = pkgvercur(pkg) if ver then print(pkgname(pkg).."-"..verstr(ver).." is currently installed") else print("No version of "..pkgname(pkg).." is installed") end end

pkgverinst(pkg)

Return the version (not a string!) of pkg which is selected for installation, or nil if there's no such version. This is the version which will be installed if the cache changes are committed.

Examples: pkg = pkgfind("apt") if pkg then ver = pkgverinst(pkg) if ver then print(verstr(ver).." is marked for installation") else print("No available version for installation") end end

pkgvercand(pkg)

Return the version (not a string!) of pkg which is a candidate for installation, or nil if there's no such version. This is the version APT has seen as the most indicated for "upgrading" (could be a downgrade as well) given your current configuration, and is the version which will be marked for installation if markinstall() is used in that package.

Examples: pkg = pkgfind("apt") if pkg then ver = pkgvercand(pkg) if ver then print(verstr(ver).." is the candidate version for that package") else print("There's no candidate version for that package") end end

pkgverlist(pkg)

Return the list of versions found for that package.

Examples: pkg = pkgfind("apt") list = pkgverlist(pkg) for i, ver in pairs(list) do print(i..": "..verstr(ver)) end

verpkg(ver)

Return the parent package for the given version.

Examples: pkg = pkgfind("apt") ver = pkgvercur(pkg) assert(pkg == verpkg(ver))

verstr(ver)

Return the string representation for the given version.

Examples: pkg = pkgfind("apt") list = pkgverlist(pkg) for i, ver in pairs(list) do print(i..": "..verstr(ver)) end

verarch(ver)

Return the string representation of the architecture for the given version.

Examples: pkg = pkgfind("apt") list = pkgverlist(pkg) for i, ver in pairs(list) do print(i..": "..verarch(ver)) end

verid(pkg)

Return an integer identifier which is guaranted to be unique for the given version up to the moment that the cache is rebuilt. This is useful for hashing purposes, for example, since currently Lua doesn't support hashing of custom types.

Examples: pkg = pkgfind("apt") seenver = {} for i, ver in pkgverlist(pkg) then seenver[verid(pkg)] = true end

verisonline(ver)

Return a true value if the given version is downloadable in some repository.

Examples: print("Installed packages without online version:") for i, pkg in pairs(pkglist()) do ver = pkgvercur(pkg) verlist = pkgverlist(pkg) if ver and not verisonline(ver) and table.getn(verlist) == 1 then print(pkgname(pkg) .. "-" .. verstr(ver)) end end

verprovlist(ver)

Return a list containing the provides for the given version. Each list item has a name and a verstr attribute.

Examples: pkg = pkgfind("libapt-pkg0") ver = pkgvercur(pkg) list = verprovlist(ver) for i, prov in pairs(list) do print(i..": "..prov.name.."-"..prov.verstr) end

verdeplist(ver)

Return a list containing the dependencies for the given version. Each list item has the following attributes: name, containing the dependency name; pkg, containing the package with the dependency name; verstr, containing the version string this dependency requires; operator, containing the string representation of the relation between the dependency name and the version ("", "<=", ">=", "<", ">", "=", or "!="); type, containing the dependency type ("", "depends", "predepends", "suggests", "recommends", "conflicts", "replaces", or "obsoletes"); and verlist, containing the list of versions which satisfy the given dependency.

Examples: pkg = pkgfind("apt") ver = pkgvercur(pkg) for i, dep in verdeplist(ver) do print(dep.name.." "..dep.operator.." "..dep.verstr) assert(pkgname(dep.pkg) == dep.name) end

verstrcmp(ver1, ver2)

Compare ver1 and ver2 strings, returning a negative number if ver1 < ver2, zero if ver1 = ver2, or a positive number if ver1 > ver2.

Examples: res = verstrcmp("1:1.0-1cl", "1:1.0-2cl") if res < 0 then print("The second version is greater") end

verstrcmp(ver1, arch1, ver2, arch2)

This is similar to the form above, but it also considers architecture information if ver1 and ver2 are equal. Notice that the architecture comparison depends on the running machine. If both architectures are not valid for the running machine they're not considered while comparing.

Examples: res = verstrcmp("1:1.0-1cl", "i386", "1:1.0-1cl", "i586") if res < 0 then print("The second version is greater") end

markkeep(pkg)

Roll back any changes marked for that package.

Examples: pkg = pkgfind("apt") markinstall(pkg) markkeep(pkg)

markinstall(pkg)

Try to mark that package for installation or upgrading, including whatever changes in the cache are necessary for doing so.

Examples: pkg = pkgfind("apt") markinstall(pkg)

markremove(pkg)

Try to mark that package for deletion, including whatever changes in the cache are necessary for doing so.

Examples: pkg = pkgfind("apt") markremove(pkg)

markupgrade()

Try to mark the whole system for upgrading. If you want to mark some specific package for upgrading, use markinstall() instead.

Examples: markupgrade()

markdistupgrade()

Try to mark the whole system for dist-upgrading.

Examples: markdistupgrade()

statkeep(pkg)

Return 1 if pkg is marked to stay the way it is, or nil otherwise.

Examples: pkg = pkgfind("apt") if pkg and statkeep(pkg) then print("No changes will be made in that package") end

statremove(pkg)

Return 1 if pkg is marked to be removed from the system, or nil otherwise.

Examples: pkg = pkgfind("apt") if pkg and statremove(pkg) then print("This package will be removed") end

statinstall(pkg)

Return 1 if pkg is marked to be installed as a new package in the system, upgraded, or downgraded, and nil otherwise.

Examples: pkg = pkgfind("apt") if pkg and statinstall(pkg) then print("This package is marked for installation") end

statnewinstall(pkg)

Return 1 if pkg is marked to be installed as a new package in the system, or nil otherwise.

Examples: pkg = pkgfind("apt") if pkg and statnewinstall(pkg) then print("This package is not installed, and marked to be installed") end

statupgrade(pkg)

Return 1 if pkg is marked to be upgraded, or nil otherwise.

Examples: pkg = pkgfind("apt") if pkg and statupgrade(pkg) then print("This package is installed, and marked to be upgraded") end

statupgradable(pkg)

Return 1 if there's a candidate version of pkg available for installation, or nil otherwise.

Examples: pkg = pkgfind("apt") if pkg and statupgradable(pkg) then print("This package is upgradable") end

statdowngrade(pkg)

Return 1 if pkg is marked to be downgraded, or nil otherwise.

Examples: pkg = pkgfind("apt") if pkg and statdowngraded(pkg) then print("This package is installed, and marked to be downgraded") end

statnowbroken(pkg)

Return 1 if at least one relation of the current version of pkg is broken in the current cache state, or nil otherwise.

Examples: pkg = pkgfind("apt") if pkg and statnowbroken(pkg) then print("The current version of that package has broken relations") end

statinstbroken(pkg)

Return 1 if at least one relation of the version in the selected state of pkg is broken in the current cache state, or nil otherwise. For example, if pkg is selected for installation, this will return information about the state of the version retrieved by pkgverinst().

Examples: pkg = pkgfind("apt") if pkg and statinstbroken(pkg) then print("The install version of that package has broken relations") end

statstr(pkg)

Return a string describing the current state of pkg. This should be used for debugging purposes, not for conditional tests. On conditionsals, use the functions above.

Examples: pkg = pkgfind("apt") if pkg then print(pkgname(pkg).." state: "..statstr(pkg)) end

apterror(errorstring)

Send errorstring as an error to the APT system. The effect of issuing that error will depend on the slot it is issued. Besides that, all errors are usually shown to the user.

Examples: apterror("Unknown error")

aptwarning(warningstring)

Send warningstring as a warning to the APT system. This is usually some information message about something the user should know.

Examples: aptwarning("Be careful")

_(string)

Return the gettext translated version of string, according to the current textdomain, or according to the string defined in the global variable TEXTDOMAIN, if it is available.

Examples: TEXTDOMAIN="apt" print(_("Translate me"))

Turning a script into a subcommand

One of the interesting features of the APT-RPM extension system is the ability to turn a given script into a subcommand of one of the main APT-RPM binaries. The following example will explain the idea.

Suppose you have the following script. This script will list the installed packages which are not being provided by any of the registered repositories.

for i, pkg in pairs(pkglist()) do ver = pkgvercur(pkg) verlist = pkgverlist(pkg) if ver and not verisonline(ver) and table.getn(verlist) == 1 then print(pkgname(pkg) .. "-" .. verstr(ver)) end end

Right now, you're able to execute this script with:

apt-cache script ./list-extras.lua

Since this might be an interesting command to have around, we'll register this script as a command, so that we may call it as:

apt-cache list-extras

First, we must include in the script some code to make it check if the mentioned command is being executed. The follownig lines will do the job if inserted at the top of the script:

if command_args[1] ~= "list-extras" then return end command_consume = 1

Now, it's just a matter of registering the script in the right slot. To do that, insert the following lines in the configuration file:

Scripts::AptCache::Command:: "/path/to/list-extras.lua";

If /path/to is the default script path, as registered in the Dir::Bin::scripts option (usually /usr/share/apt/scripts), it may be omitted.

Interactive interpreter

The extension system offers a very confortable way to test and debug functionality. You can plug an interactive Lua interpreter into any slot available. To do that, just plug the special interactive extension into the slot you want to test. For example:

apt-shell -o Scripts::Init::=interactive

Of course, it also works for apt-get script:

apt-get script interactive

How to deliver scripts

If you want to include some custom functionality in APT, perhaps the best way to do that is to package one or more scripts, together with an individual configuration file. For example, your package would contain the following files:

 /usr/share/apt/scripts/onefile.lua
 /usr/share/apt/scripts/otherfile.lua
 /etc/apt/apt.conf.d/myfeature.conf
Then, myfeature.conf would contain the APT options to insert onefile.lua and otherfile.lua in the correct slots.

Do you miss something?

Do you miss any important feature in the scripting interface of APT? If so, contact me providing information about what you miss and why that feature is important.

Last updated 01.01.2023