ybox 0.9.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. ybox/__init__.py +2 -0
  2. ybox/cmd.py +307 -0
  3. ybox/conf/completions/ybox.fish +93 -0
  4. ybox/conf/distros/arch/add-gpg-key.sh +29 -0
  5. ybox/conf/distros/arch/distro.ini +192 -0
  6. ybox/conf/distros/arch/init-base.sh +10 -0
  7. ybox/conf/distros/arch/init-user.sh +35 -0
  8. ybox/conf/distros/arch/init.sh +82 -0
  9. ybox/conf/distros/arch/list_fmt_long.py +76 -0
  10. ybox/conf/distros/arch/pkgdeps.py +276 -0
  11. ybox/conf/distros/deb-generic/check-package.sh +77 -0
  12. ybox/conf/distros/deb-generic/distro.ini +190 -0
  13. ybox/conf/distros/deb-generic/fetch-gpg-key-id.sh +30 -0
  14. ybox/conf/distros/deb-generic/init-base.sh +11 -0
  15. ybox/conf/distros/deb-generic/init-user.sh +3 -0
  16. ybox/conf/distros/deb-generic/init.sh +136 -0
  17. ybox/conf/distros/deb-generic/list_fmt_long.py +114 -0
  18. ybox/conf/distros/deb-generic/pkgdeps.py +208 -0
  19. ybox/conf/distros/deb-oldstable/distro.ini +21 -0
  20. ybox/conf/distros/deb-stable/distro.ini +21 -0
  21. ybox/conf/distros/supported.list +5 -0
  22. ybox/conf/distros/ubuntu2204/distro.ini +21 -0
  23. ybox/conf/distros/ubuntu2404/distro.ini +21 -0
  24. ybox/conf/profiles/apps.ini +26 -0
  25. ybox/conf/profiles/basic.ini +310 -0
  26. ybox/conf/profiles/dev.ini +25 -0
  27. ybox/conf/profiles/games.ini +39 -0
  28. ybox/conf/resources/entrypoint-base.sh +170 -0
  29. ybox/conf/resources/entrypoint-common.sh +23 -0
  30. ybox/conf/resources/entrypoint-cp.sh +32 -0
  31. ybox/conf/resources/entrypoint-root.sh +20 -0
  32. ybox/conf/resources/entrypoint-user.sh +21 -0
  33. ybox/conf/resources/entrypoint.sh +249 -0
  34. ybox/conf/resources/prime-run +13 -0
  35. ybox/conf/resources/run-in-dir +60 -0
  36. ybox/conf/resources/run-user-bash-cmd +14 -0
  37. ybox/config.py +255 -0
  38. ybox/env.py +205 -0
  39. ybox/filelock.py +77 -0
  40. ybox/migrate/0.9.0-0.9.7:0.9.8.py +33 -0
  41. ybox/pkg/__init__.py +0 -0
  42. ybox/pkg/clean.py +33 -0
  43. ybox/pkg/info.py +40 -0
  44. ybox/pkg/inst.py +638 -0
  45. ybox/pkg/list.py +191 -0
  46. ybox/pkg/mark.py +68 -0
  47. ybox/pkg/repair.py +150 -0
  48. ybox/pkg/repo.py +251 -0
  49. ybox/pkg/search.py +52 -0
  50. ybox/pkg/uninst.py +92 -0
  51. ybox/pkg/update.py +56 -0
  52. ybox/print.py +121 -0
  53. ybox/run/__init__.py +0 -0
  54. ybox/run/cmd.py +54 -0
  55. ybox/run/control.py +102 -0
  56. ybox/run/create.py +1116 -0
  57. ybox/run/destroy.py +64 -0
  58. ybox/run/graphics.py +367 -0
  59. ybox/run/logs.py +57 -0
  60. ybox/run/ls.py +64 -0
  61. ybox/run/pkg.py +445 -0
  62. ybox/schema/0.9.1-added.sql +27 -0
  63. ybox/schema/0.9.6-added.sql +18 -0
  64. ybox/schema/init.sql +39 -0
  65. ybox/schema/migrate/0.9.0:0.9.1.sql +42 -0
  66. ybox/schema/migrate/0.9.1:0.9.2.sql +8 -0
  67. ybox/schema/migrate/0.9.2:0.9.3.sql +2 -0
  68. ybox/schema/migrate/0.9.5:0.9.6.sql +2 -0
  69. ybox/state.py +914 -0
  70. ybox/util.py +351 -0
  71. ybox-0.9.8.dist-info/LICENSE +19 -0
  72. ybox-0.9.8.dist-info/METADATA +533 -0
  73. ybox-0.9.8.dist-info/RECORD +76 -0
  74. ybox-0.9.8.dist-info/WHEEL +5 -0
  75. ybox-0.9.8.dist-info/entry_points.txt +8 -0
  76. ybox-0.9.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,190 @@
1
+ # Configuration specific to each distribution (INI style file)
2
+
3
+ # The following environment variables are set when running the init.sh and init-user.sh scripts.
4
+ # The scripts are required to honor the meaning of the corresponding variables as described in
5
+ # their comments.
6
+ # CONFIGURE_FASTEST_MIRRORS: empty or non-empty corresponding to configure_fastest_mirrors below
7
+ # REQUIRED_PKGS: packages specified in `packages.required` below
8
+ # REQUIRED_DEPS: packages specified in `packages.required_deps` below
9
+ # RECOMMENDED_PKGS: packages specified in `packages.recommended` below
10
+ # RECOMMENDED_DEPS: packages specified in `packages.recommended_deps` below
11
+ # SUGGESTED_PKGS: packages specified in `packages.suggested` below
12
+ # SUGGESTED_DEPS: packages specified in `packages.suggested_deps` below
13
+ # EXTRA_PKGS: packages specified in `packages.extra` below
14
+
15
+
16
+ # Base configuration for the distribution
17
+ [base]
18
+ # name is required and should be overridden by distributions using this
19
+ name = Generic Debian/Ubuntu distribution
20
+ # Comma separated files to include before applying these settings.
21
+ # Paths can be absolute or relative to the location of this file.
22
+ includes =
23
+ # docker image of the distribution
24
+ image =
25
+ # directories which are shared between the containers of a distribution when
26
+ # `shared_root` is provided in the container configuration
27
+ shared_root_dirs = /etc,/opt,/usr,/var
28
+ # the secondary groups of the container user; it requires to include at least the equivalent of
29
+ # nobody/nogroup to work correctly (the last field in /etc/subgid)
30
+ secondary_groups = nogroup,sudo,video,input,lp,mail
31
+ # whether to search for and configure fastest available mirrors for packages
32
+ configure_fastest_mirrors = false
33
+ # distribution scripts that need to be copied to the container in $YBOX_TARGET_SCRIPTS_DIR
34
+ # (should include init.sh, init-base.sh and init-user.sh scripts that are are normally required
35
+ # for all distributions)
36
+ # distributions should have relative paths to the scripts from this directory
37
+ #scripts = init-base.sh,init.sh,init-user.sh,check-package.sh,list_fmt_long.py,fetch-gpg-key-id.sh
38
+ scripts =
39
+
40
+
41
+ # Initial set of packages to be installed in the distribution image
42
+ [packages]
43
+ # packages required for a functional ybox container
44
+ required = git ed zip dctrl-tools
45
+ # dependencies of the `required` packages
46
+ required_deps = less patch openssh-client unzip bash-completion psmisc
47
+ # recommended packages required for many GUI/CLI apps to work properly
48
+ recommended = bc man-db manpages pulseaudio-utils bzip2 xz-utils zstd fastjar wget shared-mime-info
49
+ iso-codes vainfo mesa-utils vulkan-tools iputils-ping iproute2 tzdata distro-info
50
+ pciutils whiptail python3-pip fonts-liberation2
51
+ # dependencies of the `recommended` packages
52
+ recommended_deps = xauth netbase xdg-user-dirs intel-media-va-driver-non-free mesa-va-drivers
53
+ mesa-vulkan-drivers libfribidi0 fonts-dejavu-core sensible-utils libpam-cap
54
+ # optional packages for enhanced experience in shell and GUI apps
55
+ suggested = fonts-cantarell fonts-firacode fonts-noto-core neovim ncdu fd-find bat
56
+ kitty-terminfo tree autojump
57
+ # dependencies of the `suggested` packages
58
+ suggested_deps = xsel xxd
59
+
60
+
61
+ # The commands here will be run as normal userns mapped user, so use sudo if the command
62
+ # needs to run as root inside the container
63
+ [pkgmgr]
64
+ # the variables here are all required ones unless noted otherwise
65
+
66
+ # install command does not have a placeholder for {package} since it is also used by
67
+ # entrypoint scripts to install packages, so this assumes that this command accepts a list of one
68
+ # or more space-separated packages at the end
69
+ install = DOWNLOADBEFORE=true /usr/bin/apt-fast {quiet} {opt_dep} install
70
+ # Show the packages that satisfy the given {package} where latter can be a virtual package
71
+ # (i.e. "Provides") or an actual package or a combination of both (e.g. some packages provide and
72
+ # replace another actual package).
73
+ check_avail = /bin/sh $YBOX_TARGET_SCRIPTS_DIR/check-package.sh available '{package}'
74
+ # check an installed actual or virtual (i.e. "Provides") package and list them in reverse
75
+ # install/upgrade time order (i.e. most recently installed/upgraded first)
76
+ check_install = /bin/sh $YBOX_TARGET_SCRIPTS_DIR/check-package.sh installed '{package}'
77
+ # proceed quietly without asking questions
78
+ quiet_flag = -y
79
+ # this is substituted for `{quiet}` placeholder in `info`, `info_all`, `search` and `search_all`
80
+ quiet_details_flag = -qq
81
+ # apt-mark works correctly only if an optional dependency is actually marked to be so in the
82
+ # deb package, but will cause trouble for cases where user wants to mark a package as an optional
83
+ # dependency of another even otherwise (e.g. qt5ct as optional dependency of libqt5gui5)
84
+ opt_dep_flag =
85
+ # Expected output of the `opt_deps` command is:
86
+ # {header}
87
+ # {prefix}<name>{separator}<level>{separator}<installed>{separator}<description>
88
+ # where
89
+ # <name>: name of the optional dependency
90
+ # <level>: level of the dependency i.e. 1 for direct dependency, 2 for dependency of dependency and
91
+ # so on; resolution of level > 2 is not required since caller currently ignores those
92
+ # <installed>: true if the dependency already installed and false otherwise
93
+ opt_deps = /usr/bin/python3 $YBOX_TARGET_SCRIPTS_DIR/pkgdeps.py \
94
+ -s '{separator}' -p '{prefix}' -H '{header}'
95
+ # disable unstable API warning for apt
96
+ apt_no_warn = /usr/bin/apt -o Apt::Cmd::Disable-Script-Warning=true
97
+ uninstall = /usr/bin/sudo %(apt_no_warn)s {quiet} {purge} {remove_deps} remove {package}
98
+ purge_flag = --purge
99
+ remove_deps_flag = --autoremove
100
+ orphans = /usr/bin/sudo %(apt_no_warn)s --just-print autoremove | /usr/bin/mawk '/^Rem/ { print $2 }'
101
+ update_meta = DOWNLOADBEFORE=true /usr/bin/apt-fast update
102
+ update = DOWNLOADBEFORE=true /usr/bin/apt-fast --only-upgrade {quiet} install {packages}
103
+ update_all = DOWNLOADBEFORE=true /usr/bin/apt-fast {quiet} full-upgrade
104
+ clean = DOWNLOADBEFORE=true && /usr/bin/apt-fast clean && /usr/bin/sudo /usr/bin/apt clean
105
+ clean_quiet = %(clean)s
106
+ mark_explicit = /usr/bin/sudo /usr/bin/apt-mark manual {package}
107
+
108
+ info = /usr/bin/dpkg-query -s {packages}
109
+ info_all = /usr/bin/apt-cache {quiet} show {packages}
110
+
111
+ # list and list_all show package name and version separated by {separator}; the list command only
112
+ # shows explicitly installed packages while the "_all" variant also shows dependents
113
+ list = PKGS="$(/usr/bin/apt-mark showmanual {packages} | /usr/bin/tr '\n' ' ')" && \
114
+ /usr/bin/dpkg-query -W -f '${{binary:Package}}{separator}${{Version}}\n' $PKGS
115
+ # empty {packages} means all packages
116
+ list_all = /usr/bin/dpkg-query -W -f '${{binary:Package}}{separator}${{Version}}\n' {packages}
117
+
118
+ # next two variables are not required ones rather are used for expansion in list variables
119
+ # to avoid repetition
120
+ list_fmt_long = /usr/bin/python3 $YBOX_TARGET_SCRIPTS_DIR/list_fmt_long.py '{separator}'
121
+ list_long_fields = '${{binary:Package}}{separator}${{Version}}{separator}${{Provides}}'`\
122
+ `'{separator}${{Pre-Depends}}{separator}${{Depends}}{separator}${{Recommends}}'`\
123
+ `'{separator}${{Suggests}}{separator}${{Description}}\n\n'
124
+ # list_long and list_all_long show package name, version, dependency-of and description separated
125
+ # by {separator}; the dependency-of column gives the required and optional dependencies in the
126
+ # format: req(<pkg> <pkg> ...),opt(<pkg> <pkg> ...); like above the "_all" variant shows all
127
+ # packages including dependents while other one shows only explicitly installed ones
128
+ list_long = PKGS="$(/usr/bin/apt-mark showmanual {packages} | /usr/bin/tr '\n' ' ')" && \
129
+ /usr/bin/dpkg-query -W -f %(list_long_fields)s $PKGS | %(list_fmt_long)s
130
+ list_all_long = /usr/bin/dpkg-query -W -f %(list_long_fields)s {packages} | %(list_fmt_long)s
131
+
132
+ list_files = /usr/bin/dpkg-query -L {package}
133
+
134
+ # next variable is not a required one rather are used for expansion in search variables
135
+
136
+ # the weird "set ..." subcommand converts space-separated quoted arguments into a
137
+ # regex i.e. ' one ' 'two' ' three' to " one |two | three" which is accomplished by declaring
138
+ # those arguments as positional arguments $1, $2 etc, then echo all arguments with IFS as "|"
139
+ args_re = "{word_start}($(set -- {search}; IFS="|"; echo "$*")){word_end}"
140
+
141
+ # search in package names in the repositories
142
+ search = %(apt_no_warn)s --names-only -q {quiet} {official} search %(args_re)s
143
+ # search in package names, provides and descriptions in the repositories
144
+ search_all = /usr/bin/apt-cache {quiet} {official} search %(args_re)s
145
+ # this is substituted for `{official}` placeholder in `search` and `search_all`
146
+ search_official_flag =
147
+ # this is substituted for `{word_start}` placeholder in `search` and `search_all`
148
+ search_word_start_flag = \b
149
+ # this is substituted for `{word_end}` placeholder in `search` and `search_all`
150
+ search_word_end_flag = \b
151
+
152
+ # regex pattern matching processes that may be invoked by package manager directly or indirectly
153
+ # that may need to be terminated for cleanup
154
+ processes_pattern = \b(apt-get|apt-fast|apt|aptitude|nala|keyboxd|dirmngr)\b
155
+ # comma separated globs for package manager related lock files (no spaces in names or within)
156
+ locks_pattern = /var/lib/apt/lists/lock,/var/lib/dpkg/lock*,$HOME/.gnupg/public-keys.d/*.lock
157
+ repair = /usr/bin/sudo /usr/bin/apt-get --fix-broken {quiet} install
158
+ # reinstall all packages
159
+ repair_all = %(repair)s && \
160
+ MANUAL_PKGS="$(/usr/bin/apt-mark showmanual)" && \
161
+ PKGS="$(/usr/bin/apt-mark showinstall)" && \
162
+ /usr/bin/apt-fast --reinstall {quiet} install $PKGS && \
163
+ /usr/bin/sudo /usr/bin/apt-mark auto $PKGS >/dev/null && \
164
+ /usr/bin/sudo /usr/bin/apt-mark manual $MANUAL_PKGS >/dev/null && %(clean)s
165
+
166
+
167
+ # Commands related to repository management
168
+ [repo]
169
+ conf_dir = /etc/apt/sources.list.d
170
+ keyring_dir = /etc/apt/keyrings
171
+ exists = /bin/test -f "%(conf_dir)s/{name}.list"
172
+ # Ubuntu's keyserver is more reliable than the standard gnupg.net/pgp.net ones
173
+ default_gpg_key_server = hkps://keyserver.ubuntu.com
174
+ # output of the `add_key` command should have a line of the form "KEYID=..." that provides the
175
+ # fingerprints of the registered keys that can be passed to `remove_key`
176
+ add_key = ( /usr/bin/curl -sSL '{url}' | /usr/bin/gpg --dearmor | \
177
+ /usr/bin/sudo /usr/bin/tee '%(keyring_dir)s/{name}.gpg' >/dev/null) && \
178
+ /bin/echo KEYID="$(/usr/bin/gpg --show-keys --with-colons '%(keyring_dir)s/{name}.gpg' | \
179
+ /usr/bin/sed -n 's/^fpr:*\([^:]*\).*/\1/p' | /usr/bin/tr '\n' ' ')"
180
+ # add gpg/pgp key given the key ID and key server
181
+ add_key_id = /usr/bin/sudo /bin/bash $YBOX_TARGET_SCRIPTS_DIR/fetch-gpg-key-id.sh \
182
+ '{key}' '{server}' '%(keyring_dir)s/{name}.gpg'
183
+ # additional options if supported should be mentioned using {options} below
184
+ add = /bin/echo 'deb [signed-by=%(keyring_dir)s/{name}.gpg] {urls} {options}' | \
185
+ /usr/bin/sudo /usr/bin/tee '%(conf_dir)s/{name}.list'
186
+ # if source repository is supported, then it should be provided in `add_source` like for `add`
187
+ add_source = /bin/echo 'deb-src [signed-by=%(keyring_dir)s/{name}.gpg] {urls} {options}' | \
188
+ /usr/bin/sudo /usr/bin/tee -a '%(conf_dir)s/{name}.list'
189
+ remove_key = /usr/bin/sudo /bin/rm '%(keyring_dir)s/{name}.gpg'
190
+ remove = /usr/bin/sudo /bin/rm '%(conf_dir)s/{name}.list'
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+
3
+ # this script fetches a GPG/PGP key and writes it to an output file given the key ID and key server
4
+
5
+ set -e
6
+
7
+ # Check if all arguments are provided
8
+ if [ $# -ne 3 ]; then
9
+ echo "Usage: $0 <key ID> <key server> <output key file>"
10
+ exit 1
11
+ fi
12
+
13
+ # ensure that only system paths are searched for all the system utilities
14
+ export PATH="/usr/sbin:/usr/bin:/sbin:/bin"
15
+
16
+ current_user="$(id -un)"
17
+ export HOME="$(getent passwd "$current_user" | cut -d: -f6)"
18
+ mkdir -p "$HOME/.gnupg"
19
+ chmod 0700 "$HOME/.gnupg"
20
+
21
+ temp_keyring="$(mktemp /tmp/gpg-keyring-XXXXXXXXXX)"
22
+
23
+ trap "rm -f $temp_keyring ${temp_keyring}~" 0 1 2 3 13 15
24
+
25
+ # fetch the key from the key server and export to given output key file
26
+ GPG_CMD="gpg --no-default-keyring --keyring $temp_keyring"
27
+ $GPG_CMD --keyserver "$2" --recv-key "$1"
28
+ $GPG_CMD --export --output "$3"
29
+
30
+ exit 0
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ export DEBIAN_FRONTEND=noninteractive
6
+
7
+ apt-get update
8
+ apt-get install -y apt-utils procps
9
+ apt-get install -y sudo dbus perl
10
+ addgroup --quiet --system input
11
+ apt-get clean
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ exit 0
@@ -0,0 +1,136 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+
7
+ source "$SCRIPT_DIR/entrypoint-common.sh"
8
+
9
+ if [ -f /etc/dpkg/dpkg.cfg.d/excludes ]; then
10
+ echo_color "$fg_cyan" "Removing dpkg excludes" >> $status_file
11
+ mv /etc/dpkg/dpkg.cfg.d/excludes /etc/dpkg/dpkg.cfg.d/excludes.dpkg-tmp
12
+ elif [ -f /etc/dpkg/dpkg.cfg.d/docker ]; then
13
+ echo_color "$fg_cyan" "Removing dpkg docker excludes" >> $status_file
14
+ mv /etc/dpkg/dpkg.cfg.d/docker /etc/dpkg/dpkg.cfg.d/docker.dpkg-tmp
15
+ fi
16
+
17
+ echo_color "$fg_cyan" "Configuring apt and updating package list" >> $status_file
18
+ export HOME=/root
19
+ export DEBIAN_FRONTEND=noninteractive
20
+ # don't install recommended and suggested packages by default but keep them if they are
21
+ # deliberately marked as dependencies
22
+ cat > /etc/apt/apt.conf.d/10-ybox << EOF
23
+ APT::Install-Recommends "false";
24
+ APT::Install-Suggests "false";
25
+ APT::AutoRemove::RecommendsImportant "true";
26
+ APT::AutoRemove::SuggestsImportant "true";
27
+ EOF
28
+ # allow language translations in apt for non english locales
29
+ if [[ -n "$LANG" && ! "$LANG" =~ ^C(\..+)?$ && "$LANG" != "POSIX" && ! "$LANG" == en_* ]]; then
30
+ rm -f /etc/apt/apt.conf.d/docker-no-languages
31
+ fi
32
+
33
+ if [ "$(sed -n 's/^ID=//p' /etc/os-release)" = "ubuntu" ]; then
34
+ apt_fast_rel="$(sed -n 's/^VERSION_CODENAME=//p' /etc/os-release)"
35
+ else
36
+ apt_fast_rel=focal # use focal for apt-fast install which works on all recent Debian releases
37
+ # enable contrib and non-free repositories for debian
38
+ if [ -f /etc/apt/sources.list.d/debian.sources ]; then
39
+ sed -i 's/^Components: main[ ]*$/Components: main contrib non-free non-free-firmware/' \
40
+ /etc/apt/sources.list.d/debian.sources
41
+ else
42
+ sed -i 's/ main[ ]*$/ main contrib non-free/' /etc/apt/sources.list
43
+ fi
44
+ fi
45
+ apt-get update
46
+
47
+ echo_color "$fg_cyan" "Setting up apt-fast" >> $status_file
48
+ apt-get install --install-recommends -y curl gnupg lsb-release
49
+ keyring_file=/etc/apt/keyrings/apt-fast.gpg
50
+ rm -f $keyring_file
51
+ echo -e "deb [signed-by=$keyring_file] http://ppa.launchpad.net/apt-fast/stable/ubuntu $apt_fast_rel main" \
52
+ > /etc/apt/sources.list.d/apt-fast.list
53
+ mkdir -p /etc/apt/keyrings
54
+ bash "$SCRIPT_DIR/fetch-gpg-key-id.sh" 0xBC5934FD3DEBD4DAEA544F791E2824A7F22B44BD \
55
+ "$DEFAULT_GPG_KEY_SERVER" $keyring_file
56
+
57
+ apt-get update
58
+ apt-get install -y apt-fast
59
+ # update couple of apt-fast defaults (both conf file and debconf selection need to be changed)
60
+ sed -i 's/^_APTMGR=.*/_APTMGR=apt/' /etc/apt-fast.conf
61
+ sed -i 's/^_MAXNUM=.*/_MAXNUM=6/' /etc/apt-fast.conf
62
+ echo "apt-fast apt-fast/aptmanager select apt" | debconf-set-selections
63
+ echo "apt-fast apt-fast/maxdownloads select 6" | debconf-set-selections
64
+
65
+ echo_color "$fg_cyan" "Upgrading all packages" >> $status_file
66
+ export DOWNLOADBEFORE=true
67
+ apt-fast full-upgrade -y --autoremove
68
+
69
+ # skip unminimize if not installing any recommended packages which should happen only in testing
70
+ if [ -n "$RECOMMENDED_PKGS" ]; then
71
+ unminimize_path="$(type -p unminimize 2>/dev/null || true)"
72
+ if [ -z "$unminimize_path" ]; then
73
+ apt-get install -y unminimize 2>/dev/null || true
74
+ fi
75
+ unminimize_path="$(type -p unminimize 2>/dev/null || true)"
76
+ if [ -n "$unminimize_path" ]; then
77
+ echo_color "$fg_cyan" "Running unminimize" >> $status_file
78
+ sed -i 's/apt-get/apt-fast/g' "$unminimize_path"
79
+ yes | "$unminimize_path"
80
+ apt-get remove --purge -y unminimize 2>/dev/null || true
81
+ fi
82
+ fi
83
+
84
+ # packages can be marked as manually installed in the base image, so mark most of them as auto
85
+ apt-mark auto $(apt-mark showinstall) >/dev/null
86
+ apt-mark manual procps sudo curl gnupg lsb-release apt-fast
87
+
88
+ # generate the configured locale and assume it is UTF-8
89
+ echo_color "$fg_cyan" "Configuring locale" >> $status_file
90
+ apt-fast install -y locales
91
+ if [ -n "$LANG" -a "$LANG" != "C.UTF-8" ] && ! grep -q "^$LANG UTF-8" /etc/locale.gen; then
92
+ echo "$LANG UTF-8" >> /etc/locale.gen
93
+ # always add en_US.UTF-8 regardless since some apps seem to depend on it
94
+ if [ "$LANG" != "en_US.UTF-8" ]; then
95
+ echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
96
+ fi
97
+ if ! locale-gen; then
98
+ echo_color "$fg_red" "FAILED to generate locale for $LANG, fallback to en_US.UTF-8" >> $status_file
99
+ export LANG=en_US.UTF-8
100
+ export LANGUAGE="en_US:en"
101
+ fi
102
+ echo "LANG=$LANG" > /etc/default/locale
103
+ if [ -n "$LANGUAGE" ]; then
104
+ echo "LANGUAGE=\"$LANGUAGE\"" >> /etc/default/locale
105
+ fi
106
+ fi
107
+
108
+ echo_color "$fg_cyan" "Installing base set of packages" >> $status_file
109
+ apt-fast install -y $REQUIRED_PKGS $RECOMMENDED_PKGS $SUGGESTED_PKGS \
110
+ $REQUIRED_DEPS $RECOMMENDED_DEPS $SUGGESTED_DEPS
111
+ apt-mark auto $REQUIRED_DEPS $RECOMMENDED_DEPS $SUGGESTED_DEPS
112
+ apt-fast clean
113
+ apt clean
114
+
115
+ # common environment variables
116
+ if ! grep -q '^export EDITOR=' /etc/bash.bashrc && dpkg --no-pager -l neovim 2>/dev/null >/dev/null; then
117
+ echo -e '\nexport EDITOR=nvim\nexport VISUAL=nvim' >> /etc/bash.bashrc
118
+ fi
119
+ if ! grep -q '^export LESSOPEN=' /etc/bash.bashrc && dpkg --no-pager -l lesspipe 2>/dev/null >/dev/null; then
120
+ echo -e '\nexport PAGER="less -RL"' >> /etc/bash.bashrc
121
+ lesspipe >> /etc/bash.bashrc
122
+ fi
123
+ if ! grep -q '^export LANG=' /etc/bash.bashrc && [ -n "$LANG" -a "$LANG" != "C.UTF-8" ]; then
124
+ echo -e "\nexport LANG=$LANG" >> /etc/bash.bashrc
125
+ if [ -n "$LANGUAGE" ]; then
126
+ echo "export LANGUAGE=\"$LANGUAGE\"" >> /etc/bash.bashrc
127
+ fi
128
+ fi
129
+
130
+ # skip starship if not installing any recommended packages which should happen only in testing
131
+ if [ -n "$RECOMMENDED_PKGS" ]; then
132
+ echo_color "$fg_cyan" "Installing starship for fancy bash prompt" >> $status_file
133
+ curl -sSL https://starship.rs/install.sh -o starship-install.sh && \
134
+ /bin/sh starship-install.sh -y && rm -f starship-install.sh /tmp/tmp.*
135
+ echo -e 'eval "$(starship init bash)"' >> /etc/bash.bashrc
136
+ fi
@@ -0,0 +1,114 @@
1
+ """
2
+ Format output of `dpkg-query -W -f '${binary:Package}<sep>${Version}<sep>${Provides}<sep>\
3
+ ${Pre-Depends}<sep>${Depends}<sep>${Recommends}<sep>${Suggests}<sep>${Description}\n\n'`
4
+ into four columns ${binary:Package}<sep>${Version}<sep>${Dependency Of}<sep>${Description}
5
+ where <sep> is a user provided separator.
6
+ """
7
+
8
+ import argparse
9
+ import re
10
+ import sys
11
+ from collections import defaultdict
12
+ from typing import Iterable
13
+
14
+ from pkgdeps import PKG_DEP_RE # pylint: disable=no-name-in-module
15
+
16
+ _DOT_LINE = re.compile(r"\s+\.\s*")
17
+
18
+
19
+ def parse_separator():
20
+ """expect a single argument which will be used as the separator between the fields"""
21
+ parser = argparse.ArgumentParser(description="Format output of dpkg-query into a table.")
22
+ parser.add_argument("separator", type=str,
23
+ help="separator to use between the fields of the output")
24
+ args = parser.parse_args()
25
+ return args.separator
26
+
27
+
28
+ def format_dep_of(req_parts: Iterable[str], opt_parts: Iterable[str]) -> str:
29
+ """format the `Dependency Of` column to include the required and optional dependencies"""
30
+ dep_of_parts = [f"req({' '.join(req_parts)})"] if req_parts else []
31
+ if opt_parts:
32
+ dep_of_parts.append(f"opt({' '.join(opt_parts)})")
33
+ return ",".join(dep_of_parts)
34
+
35
+
36
+ def process() -> None:
37
+ """process dpkg-query output on stdin to create fields separated by a given separator"""
38
+ sep = parse_separator()
39
+ current_pkg = ""
40
+ desc: list[str] = [] # description can be multiline so accumulate it
41
+ # map of package name to version and description; required and optional dependencies
42
+ # have be to looked up in the respective maps using the dependency name so need to build
43
+ # the full pkg_map first
44
+ pkg_map: defaultdict[str, list[str]] = defaultdict(lambda: ["", ""])
45
+ # reverse map of package to list of packages that require it (in their pre-depends or depends)
46
+ req_map: defaultdict[str, list[str]] = defaultdict(list[str])
47
+ # reverse map of package to list of packages that require it (in their recommends or suggests)
48
+ opt_map: defaultdict[str, list[str]] = defaultdict(list[str])
49
+ # map of package name to list of packages it provides (the reverse maps above can have
50
+ # either the dependency as the key or one of its provides as the key)
51
+ provides_map: defaultdict[str, list[str]] = defaultdict(list[str])
52
+
53
+ for line in sys.stdin:
54
+ if not line:
55
+ continue
56
+ if line[0].isspace():
57
+ # continuation of the previous description
58
+ if _DOT_LINE.fullmatch(line):
59
+ desc.append(r"\n") # literal \n will be replaced by newline in the table display
60
+ else:
61
+ # for simple line breaks in the description, it is just a continuation and can be
62
+ # appended with a space, but for the case of `_DOT_LINE` as well as the first
63
+ # description line and details later, it constitutes a new paragraph which are
64
+ # separated by a literal \n
65
+ if desc and desc[-1] != r"\n":
66
+ desc.append(" ")
67
+ desc.append(line.strip())
68
+ else:
69
+ if current_pkg:
70
+ # indicates start of fields of a new package, so fill in description and clear
71
+ pkg_map[current_pkg][1] = "".join(desc)
72
+ desc.clear()
73
+ current_pkg, version, provides, pre_depends, depends, recommends, suggests, desc_s = \
74
+ line.split(sep, maxsplit=7)
75
+ pkg_map[current_pkg][0] = version
76
+ # Iterate Provides, Pre-Depends, Depends, Recommends, Suggests to fill in their maps.
77
+ # This does not take care of version comparisons in the dependencies but it should
78
+ # not be possible for an installed package to fail version dependency check (assuming
79
+ # the packages are not broken), while virtual packages don't have versions
80
+ for match in PKG_DEP_RE.finditer(provides):
81
+ provides_map[current_pkg].append(match.group(1))
82
+ # fill in reverse mapping of required dependencies
83
+ for match in PKG_DEP_RE.finditer(pre_depends):
84
+ req_map[match.group(1)].append(current_pkg)
85
+ for match in PKG_DEP_RE.finditer(depends):
86
+ req_map[match.group(1)].append(current_pkg)
87
+ # fill in reverse mapping of optional dependencies
88
+ for match in PKG_DEP_RE.finditer(recommends):
89
+ opt_map[match.group(1)].append(current_pkg)
90
+ for match in PKG_DEP_RE.finditer(suggests):
91
+ opt_map[match.group(1)].append(current_pkg)
92
+ desc.append(desc_s.rstrip())
93
+ desc.append(r"\n") # literal \n will be replaced by newline in the table display
94
+ # fill description of the last package
95
+ if current_pkg and desc:
96
+ pkg_map[current_pkg][1] = "".join(desc)
97
+
98
+ # for each package in pkg_map, find the packages that require it by looking up the
99
+ # req_map and opt_map reverse maps; it also need to check all entries in the provides
100
+ # map to get the full list of packages that require the dependency
101
+ for pkg_name, (version, description) in pkg_map.items():
102
+ req_parts = set[str]()
103
+ opt_parts = set[str]()
104
+ for dep_name in (pkg_name, *provides_map.get(pkg_name, [])):
105
+ if req_pkg := req_map.get(dep_name):
106
+ req_parts.update(req_pkg)
107
+ if opt_pkg := opt_map.get(dep_name):
108
+ opt_parts.update(opt_pkg)
109
+ dep_of = format_dep_of(req_parts, opt_parts)
110
+ print(f"{pkg_name}{sep}{version}{sep}{dep_of}{sep}{description}")
111
+
112
+
113
+ if __name__ == "__main__":
114
+ process()