From 8d136d036fff3f8ee301972560ac8d5cd1fbe93c Mon Sep 17 00:00:00 2001 From: cyril constantin Date: Thu, 28 May 2026 08:47:50 +0200 Subject: [PATCH] Init repo --- README.md | 56 +++- customize_iso.sh | 273 +++++++++++++++++ download_myfiles.py | 706 ++++++++++++++++++++++++++++++++++++++++++++ excluded.txt | 33 +++ excluded_tags.txt | 11 + wanted-full.txt | 451 ++++++++++++++++++++++++++++ wanted.txt | 283 ++++++++++++++++++ 7 files changed, 1812 insertions(+), 1 deletion(-) create mode 100755 customize_iso.sh create mode 100755 download_myfiles.py create mode 100644 excluded.txt create mode 100644 excluded_tags.txt create mode 100644 wanted-full.txt create mode 100644 wanted.txt diff --git a/README.md b/README.md index 20f63af..237fc9f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,57 @@ # customize_debian_iso -Build an iso with all your deb favorite package and you personnal files in it. With dependencies. \ No newline at end of file +Build an iso with all your deb favorite package and you personnal files in it. With dependencies. + +Those scripts are intended to customise an existing debian installer iso. + +The initial idea is to anticipate the end of i386 architecture ( bookworm is the last release to include this architecture ) and be able to reinstall i386 hosts in the future with the last existing release even in future (retroxxxx). +Those servers will no more have security fixes, so a local usage is recommanded. + +The goals are : + +- to be able to install a debian on a specific arch in full offline mode +- to store everything you need and will need without network on a cd or usb disk +- to have all package dependencies included +- to include package you want +- to exclude package tags you don't want +- to exclude package you don't want + +You may also include : + +- recommended packages of a package +- suggested packages of a package +- suggestions from popcon + +It doesn't need admin rights but apt should be configured properly. +If you want to include a package in contrib, contrib must be available in sources.list. + +You need to execute it on the same architecture as the target. +You need to execute it on the same debian version as the target. + +It can be executed on an existing host or in a virtual machine. + +Copy all files on your home directory. +Adjust wanted.txt, excluded.txt and exclude_tags.txt to your needs. + +Execute customize_iso.sh with the iso as argument. + +customize_iso.sh : + +- mount iso as temp +- select and download packages +- prepare a custom preseed +- copy the downloaded packages +- generate the custom iso you can write on a disk or a usb key + +You can adjust it tou your needs. + +$ customize_iso.sh debian-12.12.0-i386-netinst.iso + +download_myfiles.py : + +- load existing package of the original iso +- download wanted packages +- ask to include recommanded, suggested and popcon packages +- simulate an apt cache to integrate all dependencies + +The custom iso include scripts to use local-apt-repository package after installation for a good apt experience. diff --git a/customize_iso.sh b/customize_iso.sh new file mode 100755 index 0000000..faffa20 --- /dev/null +++ b/customize_iso.sh @@ -0,0 +1,273 @@ +#!/bin/bash + +release=${1%-netinst.iso} +execution_directory=$(pwd) +release_name=${release}-netinst +newrelease=${release}-custom +myfiles=${HOME}/myfiles +wantedfile=${execution_directory}/wanted.txt +excludedfile=${execution_directory}/excluded.txt + +# global parameters +original_iso=${execution_directory}/${release_name}.iso +newrelease_iso=${execution_directory}/${newrelease}.iso + +cd "${execution_directory}" || exit 1 + +temp_file=/tmp/tmp_iso_directory.txt +if [ -e ${temp_file} ] +then + temp_dir=$(cat ${temp_file}) +else + temp_dir=$(mktemp -d) + echo "${temp_dir}" > ${temp_file} +fi + +if [ ! -e "${original_iso}" ] +then + echo "image not found : ${original_iso}" + exit 0 +fi + +package_list='xorriso isolinux python3-apt apt-utils python3-requests rsync wget' +# shellcheck disable=SC2086 +if apt-get -s install ${package_list} |grep ^Inst + then + echo "sudo apt install ${package_list} !!!" + exit 0 +fi + +ask() { + USER_ANSWER="?"; + while [ "$USER_ANSWER" != "n" ] && [ "$USER_ANSWER" != "y" ] ; do + echo -n "$1 ? [y/n] " + read -r -n 1 USER_ANSWER + echo ""; + done + if [ "$USER_ANSWER" == "y" ] ; then + return 0 + else + return 1 + fi +} + +if [ ! -e "${wantedfile}" ] +then +cat > "${wantedfile}" < "${excludedfile}" < /tmp/preseed.cfg <<"EOF" +#_preseed_V1 +### Locale configuration +d-i debian-installer/language string fr +d-i debian-installer/country string FR +d-i debian-installer/locale select fr_FR.UTF-8 +d-i keyboard-configuration/xkb-keymap select fr(latin9) +#d-i keyboard-configuration/toggle select No toggling +d-i keyboard-configuration/layoutcode string fr +d-i keyboard-configuration/modelcode string pc105 +d-i keyboard-configuration/variantcode string latin9 +### Network configuration +d-i netcfg/enable boolean false +### account configuration +#d-i passwd/root-password-crypted password [$6$tYi0kNPyvi45IWqW$vOvUycJFPqZhg.N.g7wNZ96UriyvrxoumTuWpRD0OoRp47iuqGfvICxNrsVE0IRHaTs.KkU/btWqJYdetRlpE1] +#d-i passwd/user-fullname string Cyril CONSTANTIN +#d-i passwd/username string constcyr +#d-i passwd/user-password-crypted password [$6$tYi0kNPyvi45IWqW$vOvUycJFPqZhg.N.g7wNZ96UriyvrxoumTuWpRD0OoRp47iuqGfvICxNrsVE0IRHaTs.KkU/btWqJYdetRlpE1] +#d-i passwd/user-uid string 1000 +#d-i passwd/user-default-groups string audio cdrom video +### Clock and time zone setup +d-i clock-setup/utc boolean true +d-i time/zone string Europe/Paris +# Controls whether to use NTP to set the clock during the install +#d-i clock-setup/ntp boolean true +# NTP server to use. The default is almost always fine here. +#d-i clock-setup/ntp-server string ntp.example.com +### Partitioning +### Base system installation +### Apt setup +d-i apt-setup/use_mirror boolean false +apt-cdrom-setup apt-setup/cdrom/set-next boolean false +### Package selection +popularity-contest popularity-contest/participate boolean false +EOF + +cat > "${myfiles}"/libdvdcss2.README <<"EOF" +libdvd-pkg: Downloading orig source... +I: libdvdcss_1.4.3 +/usr/bin/wget --tries=3 --timeout=40 --read-timeout=40 --continue -O libdvdcss_1.4.3.orig.tar.bz2 \ + https://download.videolan.org/pub/libdvdcss/1.4.3/libdvdcss-1.4.3.tar.bz2 \ + || /usr/bin/uscan --noconf --verbose --rename --destdir=/usr/src/libdvd-pkg --check-dirname-level=0 --force-download --download-current-version /usr/share/libdvd-pkg/debian +--2026-01-02 21:06:57-- https://download.videolan.org/pub/libdvdcss/1.4.3/libdvdcss-1.4.3.tar.bz2 +Résolution de download.videolan.org (download.videolan.org)… 213.36.253.2, 2a01:e0d:1:3:58bf:fa02:c0de:5 +Connexion à download.videolan.org (download.videolan.org)|213.36.253.2|:443… connecté. +requête HTTP transmise, en attente de la réponse… 200 OK +Taille: 388404 (379K) [application/octet-stream] +Sauvegarde en: «libdvdcss_1.4.3.orig.tar.bz2» +libdvdcss_1.4.3.orig.tar.bz2 100%[==============================================================>] 379,30K 725KB/s ds 0,5s +2026-01-02 21:06:58 (725 KB/s) — «libdvdcss_1.4.3.orig.tar.bz2» sauvegardé [388404/388404] +EOF + +cat > "${myfiles}"/cs46xx.README <<"EOF" +# not included +apt install alsa-utils +tar xvf alsa-firmware-1.2.4.tar.bz2 +cd alsa-firmware-1.2.4/cs46xx +mkdir /lib/firmware/cs46xx +cp ba1 cwc4630 cwcasync cwcbinhack cwcdma cwcsnoop /lib/firmware/cs46xx +modprobe -r snd-cs46xx +modprobe snd-cs46xx +adduser yourusername audio +alsamixer : demute +aplay /usr/share/sounds/alsa/Noise.wav +speaker-test -t sine -f 440 -c 2 +speaker-test -t wav -c 2 +EOF + +cat > "${myfiles}"/local-apt-repository.sh <<"EOF" +#!/bin/bash +help() +{ + echo "rsync and local-apt-repository packages are mandatory" +} +if [[ -n $(find "$(pwd)" -name "rsync*deb") ]] +then + find "$(pwd)" -name "rsync*deb" -exec dpkg -i {} \; +else + help +fi +if [[ -n $(find "$(pwd)" -name "local-apt-repository*deb") ]] +then + find "$(pwd)" -name "local-apt-repository*deb" -exec dpkg -i {} \; +else + help +fi +mkdir /srv/local-apt-repository +mkdir /srv/zips +rsync --recursive "$(pwd)"/pool /srv/local-apt-repository/ +rsync --recursive "$(pwd)"/firmware /srv/local-apt-repository/ +rsync --recursive "$(pwd)"/zips/ /srv/zips +apt update +EOF + +cat > "${myfiles}"/README.custom <<"EOF" +You can add package under or with dpkg e.g. +# find /mnt/cdrom0 -name "rsync*.deb" -exec dpkg -i {} \; +But you have to install dependencies one by one as the same method. + +The most convenient is to execute : +# mount +# cd +# bash local-apt-repository.sh + +It copy all packages into /srv/local-apt-repository and populate apt with all package +Check you have enough space on /srv before (size of the disk/iso). + +Start the systemd service +# systemctl start local-apt-repository.service +Check after one or two minutes with +# apt list +And you can install them +# apt install tmux +EOF + +if ask "customize installer (add preseed and modify installation option) ?" +then + chmod -R +w "${temp_dir}" + cp -v /tmp/preseed.cfg "${temp_dir}"/preseed.cfg +# sed -e "s#quiet#priority=high locale=fr_FR.UTF-8 keymap=fr file=/cdrom/preseed.cfg#" ${temp_dir}/isolinux/txt.cfg +# sed -i -e "s#quiet#priority=high locale=fr_FR.UTF-8 keymap=fr file=/cdrom/preseed.cfg#" ${temp_dir}/isolinux/txt.cfg + sed -e "s#quiet#file=/cdrom/preseed.cfg#" "${temp_dir}"/isolinux/txt.cfg + sed -i -e "s#quiet#file=/cdrom/preseed.cfg#" "${temp_dir}"/isolinux/txt.cfg +fi + +rsync --dry-run --verbose --recursive "${myfiles}"/ "${temp_dir}" +if ask "copy additionnal packages ?" +then + rsync --verbose --recursive "${myfiles}"/ "${temp_dir}" +# echo "Generate Release file" +# apt-ftparchive \ +# -o "APT::FTPArchive::Release::Origin=local-apt-repository" \ +# -o "APT::FTPArchive::Release::Description=Local APT repository" \ +# release ${custom_dir} > ${custom_dir}/Release +fi + +# Final steps +# Calculate md5 and generate iso + +echo "calculate md5sum.txt" +chmod +w "${temp_dir}"/md5sum.txt +cd "${temp_dir}" && find . -follow -type f ! -name "md5sum.txt" -print0 | xargs -0 md5sum > md5sum.txt +chmod -R -w "${temp_dir}" + +cd "${HOME}" || exit 1 + +if ask "generate final iso ?" +then + xorriso -as mkisofs -o "${newrelease_iso}" \ + -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin \ + -c isolinux/boot.cat -b isolinux/isolinux.bin -no-emul-boot \ + -boot-load-size 4 -boot-info-table "${temp_dir}" +fi + +echo "=== End of script ===" +exit 0 \ No newline at end of file diff --git a/download_myfiles.py b/download_myfiles.py new file mode 100755 index 0000000..474d509 --- /dev/null +++ b/download_myfiles.py @@ -0,0 +1,706 @@ +#!/usr/bin/env python3 + +# +# SPECIFICATIONS +# +# lire une liste de packages requis +# lire les entrées popcon by_installed +# lire une liste de packages non souhaités +# rechercher dans le cache les packages souhaités +# rechercher leurs dépendances (DEPENDS PREDEPENDS) +# exclure les packages avec dépendances non souhaités +# télécharger chaque "deb" en local +# calculer la taille approximative de l'image avec les nouveaux packages +# en fonction de la taille, réitérer dans la base popcon pour identifier des packages intéressants +# les proposer interactivement à l'ajout + +import apt # +import sys # +import pprint # +import requests +import logging.config +import logging # log ! +import os # open +import fnmatch # find debs +import re # read lines +import gzip # jigdo files are compressed +from collections.abc import MutableMapping, Iterator, Iterable +from typing import Any + +# Source - https://stackoverflow.com/a +# Posted by Sridhar Ratnakumar, modified by community. See post 'Timeline' for change history +# Retrieved 2025-11-13, License - CC BY-SA 4.0 +def sizeof_fmt(num, suffix="B"): + for unit in ("", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"): + if abs(num) < 1024.0: + return f"{num:3.1f}{unit}{suffix}" + num /= 1024.0 + return f"{num:.1f}Yi{suffix}" + +class WantedList: + """Iterator for looping over lines in a file.""" + def __init__(self, path: str): + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.INFO) + self.wanted_list = [] + if os.path.exists(path): + for line in open(path): + self.logger.debug('WantedList: ADD {}'.format(line.strip())) + self.wanted_list.append(line.strip()) + else: + self.logger.warning('WantedList: {} does not exist'.format(path)) + self.wanted_list.reverse() + self.index = len(self.wanted_list) + self.logger.info("WantedList: Initiated") + + def __iter__(self): + return self + + def __next__(self): + if self.index == 0: + raise StopIteration + self.index = self.index - 1 + return self.wanted_list[self.index] + +class ExcludedList: + """Iterator for looping over lines in a file.""" + def __init__(self, path: str): + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.INFO) + self.excluded_list = [] + if os.path.exists(path): + for line in open(path): + self.logger.debug('ExcludedList: ADD {}'.format(line.strip())) + self.excluded_list.append(line.strip()) + else: + self.logger.warning('ExcludedList: {} does not exist'.format(path)) + self.index = len(self.excluded_list) + self.excluded_list.reverse() + self.logger.info("ExcludedList: Initiated") + + def __iter__(self): + return self + + def __next__(self): + if self.index == 0: + raise StopIteration + self.index = self.index - 1 + return self.excluded_list[self.index] + +class ExcludedTags: + """Iterator for looping over lines in a file.""" + def __init__(self, path: str): + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.DEBUG) + self.excluded_tags = [] + if os.path.exists(path): + for line in open(path): + self.logger.debug('ExcludedTags: ADD {}'.format(line.strip())) + self.excluded_tags.append(line.strip()) + else: + self.logger.warning('ExcludedTags: {} does not exist'.format(path)) + self.index = len(self.excluded_tags) + self.logger.info("Excluded tags initiated: {}".format(self.excluded_tags)) + + def __iter__(self): + return self + + def __next__(self): + if self.index == 0: + raise StopIteration + self.index = self.index - 1 + return self.excluded_tags[self.index] + +class PopConList: + """Iterator for looping over lines in a file.""" + def __init__(self): + self.logger = logging.getLogger(__name__) + #self.logger.setLevel(logging.INFO) + self.popconrecordspath = '/tmp/myfiles_popcon.txt' + popcon_url = 'https://popcon.debian.org/by_inst.gz' + path = '/tmp/by_inst.gz' + rank = 0 + headers_regex = re.compile(r'^[#].*$|^[-]+$') + stat_regex = re.compile(r'^(?P[0-9]+)[ ]+(?P[a-zA-Z0-9_.+-]+)[ ]+[0-9]+[ ]+[0-9]+[ ]+[0-9]+[ ]+[0-9]+[ ]+[0-9]+[ ]+.*$') + if not os.path.exists(path): + r = requests.get(popcon_url) + with open(path,'wb') as f: + f.write(r.content) + for bbytes in gzip.open(path,'r'): + line = bbytes.decode('utf-8').strip() + stat = re.match(stat_regex,line) + if stat: + if rank < int(stat.groupdict()['rank']): + self.logger.debug("PopConList: ADD {} from {}".format(stat.groupdict()['name'],line)) + with open(self.popconrecordspath, 'a') as prp: + prp.write('{}\n'.format(stat.groupdict()['name'])) + rank += 1 + else: + self.logger.warning("rank {} already exist : {} :".format(rank,stat.groupdict()['rank'])) + pass + else: + headers = re.match(headers_regex,line) + if headers: + self.logger.debug("PopConList: SKIP {}".format(line)) + else: + self.logger.warning("PopConList: ERROR {}".format(line)) + self.logger.info("PopConList: Initiated") + + def __iter__(self): + return self + + def __next__(self): + if self.index == 0: + raise StopIteration + self.index = self.index - 1 + return self.popcon_list[self.index] + +class OriginalPackageSource: + def __init__(self, path: str): + pass + +class JigdoFile(OriginalPackageSource): + """Read original jigdo""" + def __init__(self, path: str): + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.INFO) + path_regex = re.compile(r'^(?P[A-Za-z0-9_-]+)[=](?P[A-Za-z0-9]+)[:](?P[A-Za-z0-9+~/._-]+deb)$') + package_regex = re.compile(r'(?P[A-Za-z0-9/+.-]+)[/](?P[a-z0-9+.-]+)[_](?P[a-z0-9.+-~]+)[_](?Pi386|amd64|armhf|all)[.](?Pdeb|udeb)') + debianMirror = 'http://deb.debian.org/debian/' + nonusMirror = 'http://debian.proxad.net/debian-non-US/' + self.packages = {} + for bbytes in gzip.open(path,'r'): + line = bbytes.decode('utf-8').strip() + self.logger.debug("JigdoFile: read line : {}".format(line)) + path = re.match(path_regex,line) + if path: + self.logger.debug("JigdoFile: found {}".format(path.groupdict())) + self.logger.debug("JigdoFile: DUPLICATE line : {}".format(line)) + package = re.match(package_regex,path.groupdict()['path']) + if package: + self.logger.debug("JigdoFile: found {}".format(package.groupdict())) + if package.groupdict()['Format'] == 'deb': + self.logger.debug("JigdoFile: PACKAGE add : {}".format(package.groupdict()['Package'])) + self.packages[package.groupdict()['Package']] = package.groupdict() + else: + self.logger.warning("JigdoFile: Package not found {}".format(path)) + self.logger.debug("JigdoFile: {} packages registered".format(len(self.packages))) + +class IsoDir(OriginalPackageSource): + """Read original extracted iso""" + def __init__(self, path: str, created: bool = False): + self.logger = logging.getLogger(__name__) + #self.logger.setLevel(logging.INFO) + self.logger.setLevel(logging.DEBUG) + self.packages = {} + self.created = created + self.path = path + package_regex = re.compile(r'(?P[A-Za-z0-9/+.-]+)[/](?P[a-z0-9+.-]+)[_](?P[a-z0-9.+-~]+)[_](?Pi386|amd64|armhf|all)[.](?Pdeb|udeb)') + for filepath in self.__find_files_by_pattern('*.deb', path): + self.logger.debug("IsoDir: read file path : {}".format(filepath)) + package = re.match(package_regex,filepath) + if package: + self.logger.debug("IsoDir: found package : {}".format(package.groupdict())) + if package.groupdict()['Format'] == 'deb': + self.logger.debug("IsoDir: PACKAGE add : {}".format(package.groupdict()['Package'])) + self.packages[package.groupdict()['Package']] = package.groupdict() + else: + self.logger.warning("IsoDir: Package not found {}".format(path)) + self.logger.info("IsoDir: {} packages registered".format(len(self.packages))) + self.logger.info('IsoDir: Initiated') + + def __find_files_by_pattern(self, pattern: str, path: str) -> list[str]: + """ __find_files_by_pattern('*.txt', '/path/to/dir') """ + result = [] + for root, dirs, files in os.walk(path): + for name in files: + if fnmatch.fnmatch(name, pattern): + result.append(os.path.join(root, name)) + self.logger.debug("Check if path {} was created or not : {}".format(self.path, self.created)) + if len(result) < 1 and not self.created: + self.logger.critical("Unable to find asked files !!!") + sys.exit(1) + return result + +class LocalFiles(IsoDir): + + def __init__(self, path: str, created : bool = True): + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.DEBUG) + self.path=os.path.expanduser(path) + self.created = created + if not os.path.exists(self.path): + os.mkdir(self.path) + self.logger.debug("Check if path {} was created or not : {}".format(self.path, self.created)) + super().__init__(self.path, self.created) + + def add(self, key: str, record: str): + self.packages[key]=record + +class JRecords(MutableMapping[Any, Any]): + """Store a list of Record from package + Under the form + __ : apt.package.Record + """ + + def __init__(self) -> None: + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.INFO) + self._rec = {} + self._list = [] + self.logger.info('JRecords: Initiated') + + def __hash__(self) -> int: + return hash(self._rec) + + def __str__(self) -> str: + return str(self._rec) + + def __getitem__(self, key: str) -> str: + return self._rec[key] + + def __setitem__(self, key: str, value): + self._rec[key] = value + self._list.append(re.sub(r'([a-z0-9+.-]+)[_]([a-z0-9.+-~]+)[_](i386|amd64|armhf|all)',r'\1',key)) + + def __delitem__(self, key: str): + del self_rec[key] + + def __contains__(self, key: object) -> bool: + return key in self._rec or key in self._list + + def __iter__(self) -> Iterator[str]: + return iter(self._rec.keys()) + + def iteritems(self) -> Iterable[tuple[object, str]]: + """An iterator over the (key, value) items of the record.""" + for key in self._rec.keys(): + yield key, self._rec[key] + + def get(self, key: str, default: object = None) -> object: + """Return record[key] if key in record, else *default*. + + The parameter *default* must be either a string or None. + """ + return self._rec.get(key, default) + + def has_key(self, key: str) -> bool: + """deprecated form of ``key in x``.""" + return key in self._rec or key in self._list + + def __len__(self) -> int: + return len(self._rec) + +class Controller: + + def __init__(self, aptcache, jrecords, localfiles, originalpackagesource, maxsize: int, excludedlist: [str], excludedtags: [str], interactive: bool) -> None: + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.DEBUG) + self.aptcache = aptcache + self.jrecords = jrecords + self.originalpackagesource = originalpackagesource + self.localfiles = localfiles + self.excludedlist = excludedlist + self.excludedtags = excludedtags + self.excludedrecordspath = '/tmp/myfiles_excluded.txt' + self.recommended = set([]) + self.suggested = set([]) + self.suggestedrecordspath = '/tmp/myfiles_suggested.txt' + self.recommendedrecordspath = '/tmp/myfiles_recommended.txt' + self.addedrecordspath = '/tmp/myfiles_added.txt' + self.popconrecordspath = '/tmp/myfiles_popcon.txt' + self.maxsize = maxsize + self.interactive = interactive + self.size = 0 + self.tags_regex = re.compile(r'(?P[a-z0-9:, -]+)') + self.recordkey_regex = re.compile(r'(?P[a-z0-9()<>=|., -]+)') + self.package_regex = re.compile(r'[ ]*(?P[a-z0-9.-]+)[ ]*') + self.want_exit = False + for name,version in ( originalpackagesource.packages | localfiles.packages ).items(): + self.logger.debug("Controller: recording {}_{}_{}.{} ".format(name,version['Version'],version['Architecture'],version['Format'])) + record = self.aptcache.record(name) + key = "{}_{}_{}".format(name,version['Version'],version['Architecture']) + if not key in self.jrecords: + self.jrecords[key] = record + self.logger.debug("Controller: RECORDED {}.deb ({})".format(key,record['Size'])) + self.size += int(record["Size"]) + if not name in self.recommended: + self.__record_recommend(record) + self.recommended.add(name) + if not name in self.suggested and not name in self.recommended: + self.__record_suggest(record) + self.suggested.add(name) + else: + self.logger.warning("Controller: Already registered") + for name in self.excludedlist: + key = "{}_{}_{}".format(name,'0.0.0','all') + self.jrecords[key] = "" + self.logger.info("Controller: EXCLUDE fixed package {}".format(key)) + if os.path.exists(self.excludedrecordspath): + for line in open(self.excludedrecordspath,'r'): + key = "{}_{}_{}".format(line.strip(),'0.0.0','all') + self.jrecords[key] = "" + self.logger.info("Controller: EXCLUDE recorded package {}".format(key)) + self.logger.debug("Controller: original size is {}".format(self.size)) + print("Original size is {}".format(sizeof_fmt(self.size))) + self.logger.debug("Controller: Initiated") + + def clean_exit(self) -> bool: + return self.want_exit + + def __add(self, name: str) -> bool: + self.logger.debug("Add package {}".format(name)) + if self.size > self.maxsize: + self.logger.warning("Controller: {} > {}".format(self.size,self.maxsize)) + if self.interactive: + print("Taille maximum d'image désirée atteinte: {} > {}".format(self.size,self.maxsize)) + return False + else: + candidates = self.aptcache.records([],name) + self.logger.debug("Controller: candidates: {}".format(candidates)) + for candidate in candidates: + record = self.aptcache.record(candidate) + self.logger.debug("Controller: record: {}".format(record)) + key = "{}_{}_{}".format(record['Package'],record['Version'],record['Architecture']) + self.logger.debug("Controller: recording {} ".format(key)) + self.localfiles.add(key,record) + self.logger.debug("Controller: RECORDED {}.deb ({})".format(key,record['Size'])) + self.size += int(record["Size"]) + if not name in self.recommended: + self.__record_recommend(record) + self.recommended.add(name) + if not name in self.suggested and not name in self.recommended: + self.__record_suggest(record) + self.suggested.add(name) + return True + + def __exclude_tags(self, record) -> bool: + self.logger.debug("call: __exclude_tags(record)") + if 'Tag' in record: + self.logger.debug("__exclude_tags:: tags in record : {}".format(record['Tag'])) + tags = re.match(self.tags_regex,record['Tag'].replace('\n','').replace(' ','')) + self.logger.debug("__exclude_tags:: match tags_regex: {} ({})".format(tags,type(tags))) + if tags: + for tag in re.split(r',', tags.groupdict()['tag']): + self.logger.debug("Check if exclude tag : {}".format(tag)) + if tag == "" or tag == " ": + continue + elif tag in self.excludedtags.excluded_tags: + self.logger.debug("Package has excluded tags") + if self.interactive: + print("Tag excluded in package {} : {} ({})".format(record['Package'],tag,record['Description'])) + pass + return True + else: + self.logger.warning("__exclude_records_tags:: no regex match of record['Tag']") + pass + else: + self.logger.debug("No tag in record") + return False + + def finalsize(self, size:str) -> str: + return "{}".format(sizeof_fmt(self.size + int(size))) + + def recordadded(self,name: str): + with open(self.addedrecordspath,'a') as added: + self.logger.debug('AddedList: ADD {}'.format(name)) + added.write('{}\n'.format(name)) + + def exclude(self, name: str): + key = "{}_{}_{}".format(name,'0.0.0','all') + self.jrecords[key] = "" + with open(self.excludedrecordspath,'a') as excluded: + self.logger.debug('ExcludedList: ADD {}'.format(name)) + excluded.write('{}\n'.format(name)) + + def add_wanted(self, name: str): + return self.__add(name) + + def __record(self, record, key: str, path: str): + self.logger.debug('__record(key={},record={})'.format(key,record)) + if key in record: + matching = re.match(self.recordkey_regex,record[key]) + self.logger.debug('__record: matching={}'.format(matching)) + if matching: + for comma in re.split(r',', matching.groupdict()['recordkey']): + self.logger.debug('__record::comma={}'.format(comma)) + for pipe in re.split(r'\|', comma): + self.logger.debug('__record::pipe={}'.format(pipe)) + package = re.match(self.package_regex, pipe) + self.logger.debug('__record::package={}'.format(package)) + if package: + self.logger.info('__record::ADD {} : {} ({})'.format(key,package,type(package))) + name = package.groupdict()['package'] + self.logger.debug('__record::WRITE {} : {} ({})'.format(key,name,type(name))) + with open(path, 'a') as srp: + srp.write('{}\n'.format(name)) + if self.interactive: + print("Add {} package for later : {}".format(key,name)) + else: + self.logger.debug("__record::record has no {} section".format(key)) + self.logger.debug("__record::record.keys() = {}".format(list(record.keys()))) + pass + + def __record_recommend(self, record): + self.__record(record=record, key='Recommends', path=self.recommendedrecordspath) + + def __record_suggest(self, record): + self.__record(record=record, key='Suggests', path=self.suggestedrecordspath) + + def __add_package_from_path(self, key: str, path: str): + self.logger.setLevel(logging.DEBUG) + recorded = True + package_list = set([]) + while recorded: + recorded = False + if os.path.exists(path): + for name in open(path,'r'): + package_list.add(name.strip()) + for name in package_list: + self.logger.debug("__add_package_from_path::{}".format(key)) + if name in self.jrecords: + self.logger.debug("Package {} already registered".format(name)) + continue + try: + record = self.aptcache.record(name) + self.logger.debug("__add_package_from_path::{} {} with record {}".format(key, name, record)) + except KeyError as ke: + self.logger.debug("Package {} does not exist".format(name)) + if self.interactive: + print("Package {} does not exist".format(name)) + continue + if self.__exclude_tags(record): + self.exclude(name) + continue + if self.interactive: + print("record: {}".format(record)) + answer = input("Add {} package: {} ({} -> {})? y/1 (yes) | n/0 (no) l/. (later) | q (quit)".format(key,name,record['Size'],self.finalsize(record['Size']))) + if answer == 'y' or answer == '1': + self.__add(name) + self.recordadded(name) + recorded = True + self.__record(record=record, key=key, path=path) + elif answer == 'n' or answer == '0': + self.exclude(name) + continue + elif answer == 'l' or answer == '.': + continue + elif answer == 'q': + recorded = False + self.want_exit = True + break + + def add_popcon(self): + self.__add_package_from_path(key='Popcon', path=self.popconrecordspath) + + def add_recommend(self): + self.__add_package_from_path(key='Recommends', path=self.recommendedrecordspath) + + def add_suggest(self): + self.__add_package_from_path(key='Suggests', path=self.suggestedrecordspath) + +class AptCache: + def __init__(self, jrecords, myfiles, interactive) -> None: + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.INFO) + self.cache = apt.Cache() + self.jrecords = jrecords + self.myfiles = os.path.expanduser(myfiles) + self.excludedrecordspath = '/tmp/myfiles_excluded.txt' + self.interactive = interactive + self.logger.info('AptCache initiated') + + def fetch_source(self, version): + self.logger.debug('AptCache: fetch_source {}'.format(version)) + record = version.record + destfile = '{}/{}'.format(self.myfiles,record['Filename']) + package_regex = re.compile(r'(?P[A-Za-z0-9/+.-]+)[/](?P[a-z0-9+.-]+)[_](?P[a-z0-9.+-~]+)[_](?Pi386|amd64|armhf|all)[.](?Pdeb)') + directory = re.match(package_regex,record['Filename']) + destdir = '{}/{}'.format(self.myfiles,directory.groupdict()['directory']) + os.makedirs(destdir, exist_ok=True) + key = "{}_{}_{}".format(record['Package'],record['Version'],record['Architecture']) + if not os.path.exists(destfile): + self.logger.debug("JRecord : download source: {}".format(version)) + version.fetch_source(destdir=destdir, unpack=False) + else: + self.logger.debug("JRecord : already downloaded source : {}".format(version)) + + def package(self, name: str): + self.logger.debug("AptCache: ask package for {}".format(name)) + return self.cache[name] + + def version(self, name: str): + self.logger.debug("AptCache: ask version for {}".format(name)) + return self.cache[name].candidate + + def record(self, name: str): + self.logger.debug("AptCache: ask record for {}".format(name)) + return self.cache[name].candidate.record + + def records(self, candidates: [], name: str): + self.logger.debug("AptCache: ask record with dependencies for {}".format(name)) + self.logger.debug("AptCache: start with candidates: {}".format(candidates)) + if name in self.jrecords: + self.logger.debug("AptCache: already exist: {}".format(name)) + pass + else: + self.logger.debug("AptCache: new record: {}".format(name)) + if self.cache.is_virtual_package(name): + self.logger.debug("AptCache: virtualpackage: {}".format(name)) + names = [] + for providingpackage in self.cache.get_providing_packages(name): + self.logger.debug("AptCache: add providing_package: {}".format(providingpackage.name)) + names.append(providingpackage.name) + else: + names = [name] + self.logger.debug("AptCache: building package list: {}".format(names)) + for name in names: + try: + package = self.package(name) + except KeyError as ke: + if interactive: + print("Package {} does not exist".format(name)) + answer = input("Package {} does not exist ... Ignore ? y / 1 (yes) / n / 0 (no)".format(name)) + if answer == 'y' or answer == '1': + self.jrecords[name] = "" + with open(self.excludedrecordspath,'a') as excluded: + self.logger.debug('ExcludedList: ADD {}'.format(name)) + excluded.write('{}\n'.format(name)) + continue + else: + return [] + else: + return [] + version = package.candidate + record = version.record + destfile = '{}/{}'.format(self.myfiles,record['Filename']) + package_regex = re.compile(r'(?P[A-Za-z0-9/+.-]+)[/](?P[a-z0-9+.-]+)[_](?P[a-z0-9.+-~]+)[_](?Pi386|amd64|armhf|all)[.](?Pdeb)') + directory = re.match(package_regex,record['Filename']) + destdir = '{}/{}'.format(self.myfiles,directory.groupdict()['directory']) + os.makedirs(destdir, exist_ok=True) + key = "{}_{}_{}".format(record['Package'],record['Version'],record['Architecture']) + self.logger.debug("JRecord : new package: {} ({})=> {}".format(key,destdir,record)) + self.jrecords[key] = record + if not os.path.exists(destfile): + self.logger.debug("JRecord : download package: {}".format(version)) + version.fetch_binary(destdir) + else: + self.logger.debug("JRecord : already downloaded package: {}".format(version)) + pass + candidates.append(name) + if 'Depends' in record: + self.logger.debug("AptCache: dependencies: {}".format(record['Depends'])) + pass + for dependency in package.candidate.dependencies: + for or_dependency in dependency.or_dependencies: + self.logger.debug("AptCache: or_dependency: {}".format(or_dependency)) + precandidate_name = or_dependency.name + self.logger.debug("AptCache: precandidate: {}".format(precandidate_name)) + candidate_name = re.sub(r'(?P[a-z0-9+.-]+)[:]*.*',r'\g',precandidate_name) + self.logger.debug("AptCache: candidate: {}".format(candidate_name)) + #subpackage = self.package(candidate_name) + #candidate_record = subpackage.candidate.record + #self.logger.debug("AptCache: candidate_record: {}".format(candidate_record)) + candidates.extend(self.records([],candidate_name)) + self.logger.debug("AptCache: return candidates: {}".format(candidates)) + return candidates + + def display_package(self, name: str) -> None: + pkg = self.cache[name] + print("Name: %s " % pkg.name) + print("ID: %s " % pkg.id) + print("Priority (Candidate): %s " % pkg.candidate.priority) + print("Priority (Installed): %s " % pkg.installed.priority) + print("Installed: %s " % pkg.installed.version) + print("Candidate: %s " % pkg.candidate.version) + print("CandidateDownloadable: %s" % pkg.candidate.downloadable) + print("CandidateOrigins: %s" % pkg.candidate.origins) + print("SourcePkg: %s " % pkg.candidate.source_name) + print("Summary: %s" % pkg.candidate.summary) + print("Description (formatted) :\n%s" % pkg.candidate.description) + print("Description (unformatted):\n%s" % pkg.candidate.raw_description) + print("InstalledSize: %s " % pkg.candidate.installed_size) + print("PackageSize: %s " % pkg.candidate.size) + print("Dependencies: %s" % pkg.installed.dependencies) + print("Recommends: %s" % pkg.installed.recommends) + for dep in pkg.candidate.dependencies: + print( + ",".join( + f"{o.name} ({o.version}) ({o.relation}) ({o.pre_depend})" + for o in dep.or_dependencies + ) + ) + print("arch: %s" % pkg.candidate.architecture) + print("homepage: %s" % pkg.candidate.homepage) + print("rec: ", pkg.candidate.record) + +if __name__ == u'__main__': + ''' + import apt + c = apt.Cache() + p = c['apt-file'] + v = p.candidate + r = v.record + ''' + import argparse + parser = argparse.ArgumentParser(description="A CLI tool to generate custom jigdo files") + parser.add_argument("-e","--excluded",default='excluded.txt',help="File containing packages to exclude") + parser.add_argument("-f","--files",default='~/myfiles',help="Directory added to custom original image") + parser.add_argument("-i","--isodir",help="Original path of xorriso extracted iso file") + parser.add_argument("-j","--jigdofile",help="Original path of jigdo file to customize. The cd netinst jigdo is recommended.") + parser.add_argument("-m","--maxsize",type=int,default=3500000000,help="Fix a limit to iso size") + parser.add_argument("-p","--popcon",action='store_true',help="Suggest packages from popcon statistics") + parser.add_argument("-q","--quiet",action='store_true',help="Do not prompt, do not ask") + parser.add_argument("-r","--recommend",action='store_true',help="Suggest packages from recommended records") + parser.add_argument("-s","--suggest",action='store_true',help="Suggest packages from suggested records") + parser.add_argument("-t","--excludedtags",default='excluded_tags.txt',help="Exclude package with specific tags") + parser.add_argument("-w","--wanted",default='wanted.txt',help="File containing packages to include") + args = parser.parse_args() + if os.path.exists('/tmp/jigdo.log'): + os.remove('/tmp/jigdo.log') + #logging.config.dictConfig(logging_config) + logging.basicConfig(filename='/tmp/myfiles.log',level=logging.DEBUG) + logger=logging.getLogger(__name__) + interactive = not args.quiet + jrecords = JRecords() + myfiles = LocalFiles(args.files) + aptcache = AptCache(jrecords,args.files,interactive) + if args.isodir: + originalpackagesource = IsoDir(args.isodir) + elif args.jigdofile: + originalpackagesource = JigdoFile(args.jigdofile,args.label) + else: + raise SyntaxError('You have to specify a minimal source (isodir or jigdofile)') + wantedlist = WantedList(args.wanted) + excludedlist = ExcludedList(args.excluded) + excludedtags = ExcludedTags(args.excludedtags) + controller = Controller(aptcache=aptcache, jrecords=jrecords, originalpackagesource=originalpackagesource, localfiles=myfiles, maxsize=args.maxsize, excludedlist=excludedlist, excludedtags=excludedtags, interactive=interactive) + for wanted in iter(wantedlist): + if not controller.add_wanted(wanted): + break + if interactive: + print('Wanted Packages added successfully ...') + recommend = False + suggest = False + popcon = False + if not args.recommend and not controller.clean_exit(): + answer = input("Check for adding recommended package ? (y/N) (1/0)") + if answer == 'y' or answer == '1': + recommend = True + if args.recommend or recommend and not controller.clean_exit(): + controller.add_recommend() + if not args.suggest and not controller.clean_exit(): + answer = input("Check for adding suggested package ? (y/N)") + if answer == 'y' or answer == '1': + suggest = True + if args.suggest or suggest and not controller.clean_exit(): + controller.add_suggest() + if not args.popcon and not controller.clean_exit(): + answer = input("Check for adding popcon package ? (y/N)") + if answer == 'y' or answer == '1': + popcon = True + if args.popcon or popcon and not controller.clean_exit(): + print('Checking popcon suggestions ...') + popconlist = PopConList() + controller.add_popcon() + diff --git a/excluded.txt b/excluded.txt new file mode 100644 index 0000000..f8bdb94 --- /dev/null +++ b/excluded.txt @@ -0,0 +1,33 @@ +gdm3 +chromium +www-browser +firefox +firefox-esr +apt-listchanges +debian-reference-ja +debian-reference-id +debian-reference-zh-tw +nvidia-opencl-icd +adwaita-icon-theme +debian-reference-it +debian-reference-zh-cn +libclipboard-perl +debian-reference-es +glyrc +brltty-x11 +gcc-multilib +libosra-dev +libgm2-17-mips64el-cross +getdp-sparskit +libyuv-utils +librust-strsim-0.9+default-dev +librust-strsim-0.8+default-dev +librust-strsim-0.7+default-developers +exim4-daemon-custom +postfix +init-d-script +iproute +openssl-provider-legacy +w3m-ssl +w3mee +dbus-system-bus \ No newline at end of file diff --git a/excluded_tags.txt b/excluded_tags.txt new file mode 100644 index 0000000..aa3fe76 --- /dev/null +++ b/excluded_tags.txt @@ -0,0 +1,11 @@ +interface::graphical +interface::x11 +suite::gnome +uitoolkit::gtk +x11::application +culture::japanese +uitoolkit::gtk +culture::portuguese +culture::italian +x11::font +culture::hungarian \ No newline at end of file diff --git a/wanted-full.txt b/wanted-full.txt new file mode 100644 index 0000000..7cb16d9 --- /dev/null +++ b/wanted-full.txt @@ -0,0 +1,451 @@ +abcde +abs-guide +acpi-fakekey +acpi-support +adb +aldo +alsa-utils +anacron +android-sdk-platform-tools-common +apachetop +apache2 +apache2-doc +apt-doc +aptitude +arpalert +arpwatch +aspell-en +aspell-fr +atop +autoconf +automake +bash-completion +bash-doc +beep +bettercap +binfmt-support +bison +bison-doc +bluemon +bluetooth +bluez-alsa-utils +bluez-firmware +bluez-hcidump +bluez-meshd +bluez-obexd +bluez-source +bluez-test-scripts +bluez-test-tools +bluez-tools +bmon +bookworm +bpfmon +btop +btscanner +build-essential +busybox +busybox-static +bzip2-doc +bwm-ng +bzr +ca-certificates +calibre +care +cbm +cdrkit-doc +cgoban +chrony +clang-tools-19 +connman +connman-dev +connman-vpn +conserver-client +conserver-server +console-common +console-data +convlit +cpipe +cpp-12-doc +cpustat +cw +cwcp +cwdaemon +dablin +dbeacon +ddccontrol +debconf-utils +debian-faq +debian-handbook +debian-installer +debian-installer-12-netboot-amd64 +debian-installer-12-netboot-armhf +debian-installer-12-netboot-i386 +debian-kernel-handbook +debian-policy +debian-reference +debian-reference-fr +debtags +default-jdk-headless +detox +developers-reference +developers-reference-fr +dict +dictd +dict-wn +dillo +direvent +direwolf +dlocate +dmidecode +dnsmasq +dnsmasq-base +dns-root-data +doc-debian +dosfstools +dpkg +dtv-scan-tables +dvb-apps +dvb-tools +dvblast +dvbpsi-utils +dvbstream +dvbstreamer +dvbtune +ebook2cw +ed +emacs-common-non-dfsg +emacs-nox +epub-utils +ethstatus +ethtool +exfatprogs +exim4-doc-html +fakeroot +fancontrol +farpd +fbcat +fbi +fbreader +fdisk +feh +fim +firewalld +firmware-atheros +firmware-iwlwifi +firmware-misc-nonfree +firmware-realtek +firmware-realtek-rtl8723cs-bt +flashrom +flex +flex-doc +flim +forkstat +fortran95-compiler +fspy +gawk +gcc-12-doc +gcc-doc +gdb +gdb-doc +gdbserver +genisoimage +getstream +gfortran +gfortran-12-doc +gfortran-12-multilib +gimp +gimp-help-en +git +git-doc +git-svn +gitweb +glibc-doc +gnugo +gnupg +go-bluetooth +gparted +gpicview +gpm +gpsd +gpsd-tools +gqrx-sdr +groff-base +grub-common +grub-pc +grub-pc-bin +grub2-common +gsasl-common +hyphen-fr +horst +htop +hwinfo +i2c-tools +icewm +installation-guide-i386 +ioping +iotop +iotop-c +ipband +iperf3 +iproute2-doc +ipset +iptotal +iptraf-ng +ir-keytable +iwd +kannel-extras +kicad +kicad-doc-fr +kmon +kpcli +krb5-doc +libalgorithm-diff-xs-perl +libalgorithm-merge-perl +libapache2-mod-ruwsgi +libasound2-plugin-bluez +libauthen-ntlm-perl +libauthen-sasl-perl +libbluetooth3 +libbluetooth-dev +libbtbb1 +libbtbb-dev +libcapture-tiny-perl +libcgi-fast-perl +libclang-rt-14-dev +libclang-rt-16-dev +libclang-rt-19-dev +libcwiid1 +libdata-dump-perl +libdata-password-perl +libdigest-sha-perl +libdvdcss2 +libfcgi-bin +libfftw3-dev +libfile-fcntllock-perl +libfuture-asyncawait-perl +libhtml-format-perl +libhtml-form-perl +libhttp-daemon-perl +libio-socket-inet6-perl +libio-socket-socks-perl +libiw-dev +libjs-leaflet.markercluster +libjs-sizzle +libltdl-dev +libmailtools-perl +libmath-random-isaac-perl +libmath-random-isaac-xs-perl +libmbim-utils +libmojo-server-fastcgi-perl +libncurses-dev +libnet-idn-encode-perl +libnet-libidn-perl +libnss-myhostname +libnss-mymachines +libnss-nisplus +libnss-resolve +libparted-dev +libqmi-utils +libreoffice +libreoffice-help-fr +libreoffice-l10n-fr +librole-tiny-perl +librust-clap-2-dev +librust-proc-macro2-dev +libssl-doc +libtest-hexstring-perl +libtool +libtool-doc +libxml-sax-expat-perl +libyaml-libyaml-perl +libyaml-syck-perl +libwrap0 +links2 +linux-doc +linux-image-686-pae +linux-libc-dev +linux-source +lirc +llvm-13-dev +llvm-14-dev +llvm-15-dev +llvm-16-dev +llvm-19-dev +lm-sensors +local-apt-repository +locate +logrotate +low-memory-monitor +low-memory-monitor-doc +lshw +lsof +lynx +m4 +m4-doc +mailcap +mailutils +manpages +manpages-fr +mc +memtest86+ +mighttpd2 +modemmanager +modemmanager-dev +modemmanager-doc +morse +morse2ascii +mumudvb +mupdf +mutt +mythes-fr +mwc +ncdu +ncftp +ncurses-examples +ncurses-term +netcat-openbsd +netplug +netproc +nfs-common +nfstrace +nfstrace-doc +nftables +nginx +nginx-doc +nginx-full +nginx-light +nload +node-jquery +ntfs-3g +nyx +obexfs +obexftp +obfs4proxy +ofono +onionprobe +oomd +openssh-server +openssl +openssl-provider-legacy +packit +pagemon +parted +pcapfix +pcp +pgpainless-cli +pkg-config +pmount +polkitd-pkla +powermgmt-base +ppp +pulseaudio +pxelinux +python3.11-doc +python3-bleak +python3-bluez +python3-brotli +python3-cap-ng +python3-crccheck +python3-cryptography +python3-doc +python3-fastimport +python3-github +python3-gpg +python3-invoke +python3-json-pointer +python3-keyring +python3-launchpadlib +python3-openssl +python3-paramiko +python3-requests-oauthlib +python3-socks +python-bleak-doc +python-openssl-doc +python-requests-doc +python3-rfc3987 +python3-uno +python3-uritemplate +python3-webcolors +qjoypad +read-edid +readline-doc +rfkill +rsync +rtl-sdr +runit-helper +s-tui +screen +sidedoor +slurm +speex-doc +sq +sqop +ssl-cert +ssldump +stockfish +strace +sudo +supercat +sxiv +syslinux-common +systemd-boot +systemd-container +systemd-resolved +tar-doc +tcpdump +testdisk +texinfo +tftpd-hpa +tlp-rdw +tmux +tor +tor-geoipdb +torsocks +traceroute +tsdecrypt +tshark +tvoe +udisks2 +unzip +usb.ids +usb-modeswitch +ussp-push-dbg +uvccapture +uwsgi-core +viewnior +vim-doc +vim-nox +vlc +w-scan +w-scan-cpp +w3m +w3m-el +w3m-img +wajig +wamerican +welle.io +wfrench +wget +wireless-regdb +wireless-tools +wireshark +wireshark-doc +wl +wl-beta +wodim +wpasupplicant +wvdial +x11-apps +xbitmaps +xboard +xchm +xfsdump +xinit +xl2tpd +xli +xloadimage +xpdf +xorriso +xz-utils +zip +zvbi \ No newline at end of file diff --git a/wanted.txt b/wanted.txt new file mode 100644 index 0000000..0dded92 --- /dev/null +++ b/wanted.txt @@ -0,0 +1,283 @@ +abs-guide +acpi-fakekey +acpi-support +alsa-utils +anacron +apt-doc +autoconf +automake +bash-completion +bash-doc +beep +bettercap +binfmt-support +bison +bison-doc +bluemon +bluetooth +bluez-alsa-utils +bluez-firmware +bluez-hcidump +bluez-meshd +bluez-obexd +bluez-source +bluez-test-scripts +bluez-test-tools +bluez-tools +bmon +bpfmon +bsdextrautils +btop +btscanner +build-essential +busybox +busybox-static +bzip2-doc +bwm-ng +ca-certificates +care +cbm +cdrkit-doc +chrony +clang-tools-19 +connman +connman-dev +connman-vpn +conserver-client +conserver-server +console-common +console-data +convlit +cpipe +cpp-12-doc +cpustat +debconf-utils +debian-faq +debian-handbook +debian-installer +debian-installer-12-netboot-amd64 +debian-installer-12-netboot-armhf +debian-installer-12-netboot-i386 +debian-kernel-handbook +debian-policy +debian-reference +debian-reference-fr +debtags +default-jdk-headless +detox +developers-reference +developers-reference-fr +direvent +dlocate +dmidecode +dnsmasq +dnsmasq-base +dns-root-data +doc-debian +dosfstools +dpkg +ed +emacs-common-non-dfsg +emacs-nox +ethstatus +ethtool +exfatprogs +fakeroot +fancontrol +farpd +fbcat +fbi +fdisk +firewalld +firmware-atheros +firmware-iwlwifi +firmware-misc-nonfree +firmware-realtek +firmware-realtek-rtl8723cs-bt +flashrom +flex +flex-doc +forkstat +fortran95-compiler +fspy +gawk +gcc-12-doc +gcc-doc +gdb +gdb-doc +gdbserver +genisoimage +gfortran +gfortran-12-doc +gfortran-12-multilib +git +git-doc +git-svn +glibc-doc +gnupg +gpm +groff-base +grub-common +grub-pc +grub-pc-bin +grub2-common +gsasl-common +horst +htop +hwinfo +i2c-tools +installation-guide-i386 +ioping +iotop +iotop-c +ipband +iperf3 +iproute2 +iproute2-doc +ipset +iptotal +iptraf-ng +ir-keytable +iwd +kpcli +krb5-doc +linux-doc +linux-image-686-pae +linux-libc-dev +linux-source +lirc +llvm-13-dev +llvm-14-dev +llvm-15-dev +llvm-16-dev +llvm-19-dev +lm-sensors +local-apt-repository +locate +logrotate +low-memory-monitor +low-memory-monitor-doc +lshw +lsof +lynx +m4 +m4-doc +mailcap +mailutils +manpages +manpages-fr +mc +memtest86+ +modemmanager +modemmanager-dev +modemmanager-doc +mutt +ncdu +ncftp +ncurses-examples +ncurses-term +netcat-openbsd +netplug +netproc +nfs-common +nfstrace +nfstrace-doc +nftables +nload +ntfs-3g +nyx +obexfs +obexftp +obfs4proxy +onionprobe +oomd +openssh-server +openssl +openssl-provider-legacy +packit +pagemon +parted +pcapfix +pmount +polkitd-pkla +powermgmt-base +ppp +pulseaudio +pxelinux +python3.11-doc +python3-bleak +python3-bluez +python3-brotli +python3-cap-ng +python3-crccheck +python3-cryptography +python3-doc +python3-fastimport +python3-github +python3-gpg +python3-invoke +python3-json-pointer +python3-keyring +python3-launchpadlib +python3-openssl +python3-paramiko +python3-requests-oauthlib +python3-socks +python-bleak-doc +python-openssl-doc +python-requests-doc +python3-rfc3987 +python3-uritemplate +python3-webcolors +qemu-guest-agent +read-edid +readline-doc +rfkill +rsync +runit-helper +s-tui +screen +sidedoor +slurm +sq +sqop +ssl-cert +ssldump +strace +sudo +supercat +syslinux-common +systemd-boot +systemd-container +systemd-resolved +tar-doc +tcpdump +testdisk +texinfo +tftpd-hpa +tmux +tor +tor-geoipdb +torsocks +traceroute +tshark +udisks2 +unzip +usb.ids +usb-modeswitch +ussp-push-dbg +uvccapture +vim-doc +vim-nox +w3m +w3m-el +wajig +wget +wireless-regdb +wireless-tools +wodim +wpasupplicant +wvdial +xorriso +xz-utils +zip \ No newline at end of file