Nested dpkg / apt-get approach
As Franklin emphasizes, this will create many problems , but if it is for internal use, your package can handle them, it will still be scary, so try to avoid this by saying that the approach will include:
- Disable apt / dpkg temporary locks
- Update / install / uninstall software
- Merge changes into dpkg database
- Enable lock back apt / dpkg
- Profit
1. Disable apt / dpkg temporary locks
You need to temporarily move the following files:
- / var / Library / Dpkg / lock
- / var / Library / Dpkg / updates /
- / var / cache / sq / archive / lock
2. Update / install / uninstall software
Now you can run apt-get / dpkg if you want to update the index, which you can run apt-get update
, enable locks again and continue, otherwise be ready to work with the dpkg database.
If you want to install / uninstall the software, you need to take into account that apt-get / dpkg writes its final state to:
- / var / Library / Dpkg / status
Suppose you have a system with the following packages: firefox, htop, curl
, and let your package foo
remove curl
, so when you install your package you must have firefox, htop, foo
, since dpkg updates its state once per instance your nested state will be overridden by the parent process, leaving the following status as firefox, htop, curl, foo
So, you will not have curl files, but for dpkg the package will still be installed, this will also happen with new software and dependencies.
Suppose your foo
packages install apache2
, which depend on apache2-data
, you expect them in your dpkg database as: firefox, htop, curl, foo, apache2, apache2-data
, however you will have firefox, htop, curl, foo
, the nested result was overridden by the parent process, which knew only about installing foo
3. Merge the changes into the dpkg database
To avoid this confusion, you will need to take care of the dpkg changes manually, since the file is redefined using the apt-get / dpkg instance, you will need to save the changes in another place and apply them to the source file only after , the main instance of apt-get / dpkg will be executed, since your script will complete before this happens, you need to leave a cronjob entry or a manual daemon.
4. Enable back lock apt / dpkg
5. Profit
Since the above process can be somehow unpleasant, I leave the naive implementation, do not forget to understand it before using it and expect corner cases.
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 (eg 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 foo bar _apt_purge ugly packages _apt_run