0

I want to list upgradable packages and automatically repack them to .debs. My try:

fakeroot -u dpkg-repack `apt list --upgradable | cut -f1 -d"/" | awk '{if(NR>2)print}'`

So it gets package names, then redirects names to dpkg-repack. It works partially, because dpkg-repack throws error when amd64 and i386 package exists and both have the same name - in this case it expects architecture to be added to package name.

Do you have any idea how to manage it and in case of multiple arch exists, auto generate both debs? It seems dpkg-repack is not smart enough to be able to generate multiple architectures automatically, it only throws error message saying multiple packages with the same name are installed

user1209216
  • 103
  • 4
  • If you anticipate needing to rollback a lot, you might want to maintain your own apt mirror(s). – Michael Hampton Aug 10 '18 at 14:44
  • Not a lot, rather everyday upgrades. My approach seems to the best possible – user1209216 Aug 10 '18 at 14:52
  • Probably so, you're quite limited since apt doesn't have easy rollbacks like yum/dnf does. I'm certainly not aware of a _good_ solution. – Michael Hampton Aug 10 '18 at 14:54
  • Well, yes that sucks, but it ensures I will alwas revert to the same versions. Better than look in online repos, where particular version may be not available anymore. Sometines configs may be not backward compatible, but I think it won't be very often – user1209216 Aug 10 '18 at 16:14
  • I would rather advice to use a cache, and keep all the packages you're installing. Then, "just" keep track of the package version which was installed before, and in case, install it again. – gxx Jun 25 '19 at 09:19

1 Answers1

1

I use the following script to dpkg-repack all packages that would be affected by an apt-get operation:

#!/bin/zsh
#
# creates subdir and dpkg-repacks all relevant packages in it
#
DIR=$(date +%Y%m%d)
OPER=${@:-dist-upgrade}
mkdir -p $DIR
cd $DIR || exit 1
zmodload zsh/system

renice -n 20 -p $$ >/dev/null 2>/dev/null

nproc=$(nproc)
native_arch=$(dpkg --print-architecture)
typeset -U possible_arch=($native_arch all)
[[ $native_arch = amd64 ]] && possible_arch=($possible_arch i386)

function repacked() {
    local p=$1
    local a=$2
    local existing
    existing=(${p}_*${a}.deb(N) ${p}_*all.deb(N))   # no need to try repacking in a binary arch if _all already exists, because arch-specific and arch=all packages of the same name can't exist simultaneously
    if (($#existing)); then
        [[ "$a" = none ]] && unset a    # avoid confusing message with arch specified as "none"
        echo "Skipping $p${a:+:$a}, already repacked." >&2
        return 0
    fi
    return 1
}

function worker() {
    local pkg=$1
    local arch=$2
    local lockfd lockfd2
    local success=0
    local try_arch
    [[ -z "$pkg" ]] && return 0
    : >>"$pkg.$arch.lock"
    if zsystem flock -f lockfd -t 0 "$pkg.$arch.lock" 2>/dev/null; then
        if [[ $arch = none ]]; then
            for try_arch in $possible_arch; do
                repacked $pkg $try_arch && success=1
            done
            if ! ((success)) && ! dpkg-repack $pkg; then
                for try_arch in $possible_arch; do
                    echo "*** DEBUG: *** entering architecture guessing branch in worker()." >&2    # it's unclear whether this branch will ever even be run
                    repacked $pkg $try_arch && break    # avoid even locking if another thread got here first; allows main thread to start an additional worker sooner
                    : >>"$pkg.$try_arch.lock"
                    if zsystem flock -f lockfd2 -t 0 "$pkg.$try_arch.lock" 2>/dev/null; then
                        if repacked $pkg $try_arch; then    # make sure another thread didn't get to this package after our previous check but before acquiring the lock
                            success=1
                        else
                            dpkg-repack --arch=$try_arch $pkg:$try_arch && success=1
                        fi
                        rm $pkg.$try_arch.lock
                        zsystem flock -u $lockfd2
                        ((success)) && break
                    fi
                done
            fi
        else
            repacked $pkg $arch || dpkg-repack --arch=$arch $pkg:$arch  # TODO: run with loadwatch
        fi
        rm $pkg.$arch.lock
        zsystem flock -u $lockfd
    fi
}

# clean up any leftover locks from a previous invocation
for i in *.lock(N); do
    zsystem flock -f lockfd $i  # if they're still locked, a dpkg-repack may still be running; wait for it to finish
    rm $i
    zsystem flock -u $lockfd
done

for i in $(echo n \
    | LC_MESSAGES=C apt-get -d -u ${=OPER} 2>&1 \
    | sed -r '/^The following packages were automatically installed/,/^Use .apt(-get)? autoremove. to remove them/d
          /^The following NEW packages will be installed:/,/^The/s/^[[:space:]].*//
          /^The following packages have been kept back:/,/^The following packages will be upgraded:/d
          /^The following packages have unmet dependencies:/,$d' \
    | grep '^[[:space:]]' \
    | tr -d '*'); do
        if ((${i[(I):]})); then # package name includes architecture (separated by colon) -- index of colon within $i is not zero
            echo ${${i:t}/:/ }  # print name and architecture
        else
            p=${i:t}
            # guess architecture:
            [[ -e /var/lib/dpkg/info/${p}.list ]] && echo $p none   # we don't know the architecture, but perhaps dpkg-repack doesn't need it; have the worker try
            for try_arch in $possible_arch; do
                [[ -e /var/lib/dpkg/info/${p}:${try_arch}.list ]] && echo $p $try_arch
            done
        fi
done | sort -u | while read p a; do
    if ! repacked $p $a; then
        workers=(*.lock(N))
        while [[ $#workers -ge $nproc ]]; do    # wait for a worker slot to become available
            sleep 0.5
            workers=(*.lock(N))
        done
        [[ -n "$p" ]] && worker $p $a &
    fi
done
wait
rm -f *.lock(N)

Just call it with the same arguments you would pass to apt-get. It'll create a directory named for today's date, and put all generated .debs in there. It will repack as many packages in parallel as you have CPU cores. It's idempotent in the sense that if a .deb for a package already exists, it won't repack it again, making it safe to abort and restart.

It has the i386 and amd64 architectures hardwired, so if you have something else you'll need to change it.

András Korn
  • 641
  • 5
  • 13
  • It tries i386, all and amd64 for each package. Not only when i386 is installed, so it throws tons of errors saying package is not installed. As improvement, there should be some chekck performed which architecture (or architectures) versions are actually installed. – user1209216 Jun 23 '19 at 18:25
  • I found another issue: it performs backup kept back packages, for example hold packages, that's not an issue actually since it backups the same versions, but it's not neccesary. – user1209216 Jun 24 '19 at 05:34
  • As for blindly trying all architectures: the check to see which version is installed is just as expensive as trying the repack and seeing it fail, so there is no point. I agree it would be more elegant to not repack held-back packages; please feel free to improve it. :) I played with it this morning to get rid of the `chpst` dependency and to make it slightly faster, but now it's longer. – András Korn Jun 24 '19 at 20:40
  • Point is to stop dumb error messages... as for kept back packages, I guess one additional sed should be added – user1209216 Jun 24 '19 at 21:55
  • I want my script to run efficiently; if there are harmless but unaesthetic messages, I don't mind. You can add a `2>&1 | egrep -v` to the `dpkg-repack` invocation to get rid of them, likely at the cost of some performance (or pipe the entire output of the script through `egrep -v`). If you know of a straightforward way of determining in advance whether a `dpkg-repack` command would fail, I can add it. As for the other `sed`, you're right; I'll add it, but it's not a priority for me. – András Korn Jun 24 '19 at 22:00
  • `dpkg -s` and check exit code shiuld be enough to determine if any particular package is installed or not. If not, don't call dpkg-repack. Another way is more inteligent: `apt` adds architecture specification only for i386, if package with :i386 is found, you need to add :amd64 as well, otherwise, architecture specification can be ommited. `all` can be always ommited – user1209216 Jun 25 '19 at 04:11
  • I'm not sure it would be faster if I called `dpkg -s` on each package before calling `dpkg-repack`; would you like to benchmark it? I have already added the optimisation of using the architecture `apt-get` prints, in cases where it prints one. – András Korn Jun 25 '19 at 06:30
  • Well, I did not benchmark that, but I see no reason why dpkg query would be slower than dpkg-repack call. I guess dpkg-repack still calls dpkg query internally to determine if package is installed or not if not, it throws error. I think completly mute dpkg-repack error ouput is not good, becase sometimes useful warning or error may be generated and resulting .deb may be broken, good to know that. – user1209216 Jun 25 '19 at 06:49
  • I was right, look here: https://github.com/guillemj/dpkg-repack/blob/master/dpkg-repack#L128 it just calls `dpkg -s ` – user1209216 Jun 25 '19 at 07:05
  • 1
    Yes, I know it calls `dpkg -s` -- that's why I don't call it. :) Since `dpkg-repack` calls it anyway, there is no point in calling it twice (it just slows things down). However, I rewrote the architecture guessing logic so that in my experiments it no longer calls `dpkg-repack` with architectures that aren't installed. I also added more sed blocks to avoid trying to repack held-back and newly installed packages. – András Korn Jun 25 '19 at 08:40
  • 1
    Good work, I'm going to test it soon. What I need to do is make sure locale is English, because english texts are paresed, I added `export LC_ALL=C` – user1209216 Jun 25 '19 at 08:52
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/95372/discussion-between-andras-korn-and-user1209216). – András Korn Jun 25 '19 at 09:04
  • To speed it upe even more, script could also look at cache directory. If required package is found in apt cache, copy it from cache to destination folder and don't repack. Only missing packages repacked with `dpkg-repack` – user1209216 Jun 25 '19 at 09:31
  • I replied via chat; link above. – András Korn Jun 25 '19 at 20:38