install apt packages from deb postinst
10 Sep 2015
During the last couple of years I’ve been building yet another Linux distribution, mostly to have my favorite software nicely packaged, but also to experiment and have fun =)
One important part of it is its configuration file, /etc/minos/config or ~/.minos/config, e.g.
wallpaper ~/data/images/wallpapers/sunlight.png lock-wallpaper ~/data/images/wallpapers/lock.png app-core mozilla-firefox mozilla-flashplayer app-purge xinetd sasl2-bin sendmail sendmail-base sendmail-bin sensible-mda
I’ve chosen Debian/Ubuntu infrastructure for the initial implementation but probably will change it in the future (bedrock linux?). Anyway, since some of the parameters accept additional packages I’ve been having fun abusing the maintainer scripts to do so, this is how I’ve done it.
Locks
Apt, rpm, and most package managers use locks to ensure its operations are as atomic as possible, it helps them to keep packages under control, so when trying to abuse maintainer scripts (on this case postinst) the first error to come up will be:
E: Could not get lock /var/lib/dpkg/lock - open (11: Resource temporarily unavailable) E: Unable to lock the administration directory (/var/lib/dpkg/), is another process using it?`
These files can be moved temporally to launch additional apt/dpkg instances, after some experimentation the list is as follows:
/var/lib/dpkg/lock
/var/cache/apt/archives/lock
/var/lib/dpkg/updates/
Dpkg/apt-get uses a database in text plain located at /var/lib/dpkg/status, it’s kind of important to keep track of it too since the result of every apt/dpkg invocation is dumped to it upon completion (multiple backups are available at /var/backups/dpkg.status).
Post execution
There seem to exist several options to abuse apt-get, cron jobs, daemons queues (aptdaemon?), custom waits, but all them require a considerable amount of time after the main apt-get/dpkg is done, what if the system go down short after?. I finally decided to install everything within the main apt-get process and merge changes at the end (that way it takes a couple of seconds processing the missing text operations instead of probably several minutes for further apt instances).
#!/bin/sh package=my-pkg _dpkg_suspend_process() { #unlock standard files busybox mv /var/lib/dpkg/lock /var/lib/dpkg/lock.suspended busybox rm -rf /var/lib/dpkg/updates.suspended/ busybox mv /var/lib/dpkg/updates/ /var/lib/dpkg/updates.suspended busybox mkdir /var/lib/dpkg/updates/ busybox mv /var/cache/apt/archives/lock /var/cache/apt/archives/lock.suspended #debconf missing file descriptors workaround busybox cp /usr/share/debconf/confmodule /usr/share/debconf/confmodule.bk busybox cp /usr/share/minos/debconf/confmodule /usr/share/debconf/confmodule #while apt is being executed it modifies the status file which brings conflicts #to new packages if they're installed/removed in abused apt instances, therefore #the status-old file (which represent the original state in which the first #apt instance was launched) is used to create temporal diffs which will be merged #at the end busybox cp /var/lib/dpkg/status /var/lib/dpkg/status.suspended busybox cp /var/lib/dpkg/status-old /var/lib/dpkg/status-orig busybox cp /var/lib/dpkg/status-orig /var/lib/dpkg/status } _dpkg_continue_process() { #relock standard files busybox rm -rf /var/lib/dpkg/updates busybox mv /var/lib/dpkg/lock.suspended /var/lib/dpkg/lock busybox mv /var/lib/dpkg/updates.suspended /var/lib/dpkg/updates busybox mv /var/cache/apt/archives/lock.suspended /var/cache/apt/archives/lock busybox mv /var/lib/dpkg/status.suspended /var/lib/dpkg/status #debconf missing file descriptors workaround busybox mv /usr/share/debconf/confmodule.bk /usr/share/debconf/confmodule #keep status-old file to survive multiple abused apt instances busybox mv /var/lib/dpkg/status-orig /var/lib/dpkg/status-old } _dpkg_sync_status_db() { _dpkg_sync_status_db_script="/var/lib/dpkg/dpkg-sync-status-db" _dpkg_sync_status_db_script_generator() { printf "%s\\n" "#!/bin/sh" printf "%s\\n" "#autogenerated by ${package}: $(date +%d-%m-%Y:%H:%M)" printf "\\n" printf "%s\\n" '##close stdout' printf "%s\\n" '#exec 1<&-' printf "%s\\n" '##close stderr' printf "%s\\n" '#exec 2<&-' printf "%s\\n" '##open stdout as $log_file file for read and write.' printf "%s\\n" "#exec 1<> /tmp/${package}.\${$}.debug" printf "%s\\n" '##redirect stderr to stdout' printf "%s\\n" '#exec 2>&1' printf "%s\\n" '#set -x #enable trace mode' printf "\\n" printf "%s\\n" "while fuser /var/lib/dpkg/lock >/dev/null 2>&1; do sleep 1; done" printf "\\n" printf "%s\\n" 'pkgs__add="$(cat /var/lib/apt/apt-add-queue)"' printf "%s\\n" 'if [ -n "${pkgs__add}" ]; then' printf "%s\\n" ' for pkg in $pkgs__add; do' printf "%s\\n" ' if ! busybox grep "^Package: ${pkg}$" /var/lib/dpkg/status >/dev/null 2>&1; then' printf "%s\\n" ' busybox sed -n "/Package: ${pkg}$/,/^$/p" \' printf "%s\\n" " /var/lib/dpkg/status-append-queue >> /var/lib/dpkg/status" printf "%s\\n" " fi" printf "%s\\n" " done" printf "%s\\n" "fi" printf "\\n" printf "%s\\n" 'pkgs__rm="$(cat /var/lib/apt/apt-rm-queue)"' printf "%s\\n" 'if [ -n "${pkgs__rm}" ]; then' printf "%s\\n" ' for pkg in $pkgs__rm; do' printf "%s\\n" ' busybox sed -i "/Package: ${pkg}$/,/^$/d" /var/lib/dpkg/status' printf "%s\\n" " done" printf "%s\\n" "fi" printf "\\n" printf "%s\\n" "mv /var/lib/apt/apt-add-queue /var/lib/apt/apt-add-queue.bk" printf "%s\\n" "mv /var/lib/apt/apt-rm-queue /var/lib/apt/apt-rm-queue.bk" printf "%s\\n" "mv /var/lib/dpkg/status-append-queue /var/lib/dpkg/status-append-queue.bk" printf "\\n" printf "%s\\n" "rm -rf /var/lib/apt/apt-add-queue /var/lib/apt/apt-rm-queue" printf "%s\\n" "rm -rf ${_dpkg_sync_status_db_script}" } _dpkg_sync_status_db_script_generator > "${_dpkg_sync_status_db_script}" chmod +x "${_dpkg_sync_status_db_script}" _daemonize /bin/sh -c "${_dpkg_sync_status_db_script}" } _daemonize() { #http://blog.n01se.net/blog-n01se-net-p-145.html [ -z "${1}" ] && return 1 ( #1. fork, to guarantee the child is not a process #group leader, necessary for setsid) and have the #parent exit (to allow control to return to the shell) #2. redirect stdin/stdout/stderr before running child [ -t 0 ] && exec </dev/null [ -t 1 ] && exec >/dev/null [ -t 2 ] && exec 2>/dev/null if ! command -v "setsid" >/dev/null 2>&1; then #2.1 guard against HUP and INT (in child) trap '' 1 2 fi #3. ensure cwd isn't a mounted fs so it does't block #umount invocations cd / #4. umask (leave this to caller) #umask 0 #5. close unneeded fds #XCU 2.7 Redirection says: open files are represented by #decimal numbers starting with zero. The largest possible #value is implementation-defined; however, all #implementations shall support at least 0 to 9, inclusive, #for use by the application. i=3; while [ "${i}" -le "9" ]; do eval "exec ${i}>&-" i="$(($i + 1))" done #6. create new session, so the child has no #controlling terminal, this prevents the child from #accesing a terminal (using /dev/tty) and getting #signals from the controlling terminal (e.g. HUP, INT) if command -v "setsid" >/dev/null 2>&1; then exec setsid "$@" elif command -v "nohup" >/dev/null 2>&1; then exec nohup "$@" >/dev/null 2>&1 else if [ ! -f "${1}" ]; then "$@" else exec "$@" fi fi ) & #2.2 guard against HUP (in parent) if ! command -v "setsid" >/dev/null 2>&1 \ && ! command -v "nohup" >/dev/null 2>&1; then disown -h "${!}" fi } _apt_add_queue() { for pkg in "${@}"; do if busybox grep "${pkg}" /var/lib/apt/apt-rm-queue >/dev/null 2>&1; then busybox sed -i "/^${pkg}$/d" /var/lib/apt/apt-rm-queue else if ! busybox grep "^Package: ${pkg}$" /var/lib/dpkg/status >/dev/null 2>&1; then printf "%s\\n" "${pkg}" >> /var/lib/apt/apt-add-queue fi fi done; unset pkg } _apt_rm_queue() { for pkg in "${@}"; do if busybox grep "${pkg}" /var/lib/apt/apt-add-queue >/dev/null 2>&1; then busybox sed -i "/^${pkg}$/d" /var/lib/apt/apt-add-queue else if busybox grep "^Package: ${pkg}$" /var/lib/dpkg/status >/dev/null 2>&1; then printf "%s\\n" "${pkg}" >> /var/lib/apt/apt-rm-queue fi fi done; unset pkg } _apt_install() { [ -z "${1}" ] && return _apt_add_queue $(printf "%s\\n" "${@}" | busybox sed "s:${package}::g") } _apt_purge() { [ -z "${1}" ] && return _apt_rm_queue $(printf "%s\\n" "${@}" | busybox sed "s:${package}::g") } _apt_run() { [ ! -f /var/lib/apt/apt-add-queue ] && [ ! -f /var/lib/apt/apt-rm-queue ] && return pkgs__add="$(cat /var/lib/apt/apt-add-queue 2>/dev/null)" if [ -n "${pkgs__add}" ]; then _dpkg_suspend_process busybox awk '/^Package: /{print $2}' /var/lib/dpkg/status | \ busybox sort > /var/lib/dpkg/status-pkgs.orig _apt_run__output="$(DEBIAN_FRONTEND=noninteractive apt-get install \ --no-install-recommends -y -o Dpkg::Options::="--force-confdef" \ -o Dpkg::Options::="--force-confold" --force-yes ${pkgs__add} 2>&1)" || \ printf "%s\\n" "${_apt_run__output}" >&2 busybox awk '/^Package: /{print $2}' /var/lib/dpkg/status | \ busybox sort > /var/lib/dpkg/status-pkgs.current _dpkg__added_pkgs="$(busybox diff -Naur /var/lib/dpkg/status-pkgs.orig \ /var/lib/dpkg/status-pkgs.current | busybox awk '/^\+[a-zA-Z]/{gsub("^+","");print;}')" busybox rm -rf /var/lib/dpkg/status-pkgs* #add dependencies if [ -n "${_dpkg__added_pkgs}" ]; then printf "%s\\n" "${_dpkg__added_pkgs}" >> /var/lib/apt/apt-add-queue printf "%s\\n" "$(busybox sort /var/lib/apt/apt-add-queue | busybox uniq)" \ > /var/lib/apt/apt-add-queue fi #extract dpkg status output to append it at the end for pkg in $_dpkg__added_pkgs; do busybox sed -n '/Package: '"${pkg}"'$/,/^$/p' /var/lib/dpkg/status \ >> /var/lib/dpkg/status-append-queue done _dpkg_continue_process fi pkgs__rm="$(cat /var/lib/apt/apt-rm-queue 2>/dev/null)" if [ -n "${pkgs__rm}" ]; then _dpkg_suspend_process busybox awk '/^Package: /{print $2}' /var/lib/dpkg/status | \ busybox sort > /var/lib/dpkg/status-pkgs.orig _apt_run__output="$(DEBIAN_FRONTEND=noninteractive apt-get purge \ -y ${pkgs__rm} 2>&1)" || printf "%s\\n" "${_apt_run__output}" >&2 busybox awk '/^Package: /{print $2}' /var/lib/dpkg/status | \ busybox sort > /var/lib/dpkg/status-pkgs.current _dpkg__removed_pkgs="$(busybox diff -Naur /var/lib/dpkg/status-pkgs.orig \ /var/lib/dpkg/status-pkgs.current | busybox awk '/^-[a-zA-Z]/{gsub("^-","");print;}')" busybox rm -rf /var/lib/dpkg/status-pkgs* #remove dependencies if [ -n "${_dpkg__removed_pkgs}" ]; then printf "%s\\n" "${_dpkg__removed_pkgs}" >> /var/lib/apt/apt-rm-queue printf "%s\\n" "$(busybox sort /var/lib/apt/apt-rm-queue | busybox uniq)" \ > /var/lib/apt/apt-rm-queue fi _dpkg_continue_process fi _dpkg_sync_status_db } _apt_install additional packages _apt_purge ugly packages _apt_run
Not the most elegant solution but it’s works, I’ll leave it like that until I find a better alternative or change base distros.
Happy abusing 😋