Find and retrieve Debian packages, even deprecated ones or for others platforms.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

275 lines
9.5 KiB

#!/usr/bin/python3
##
# this script is intended for searching a Packages file of the debian servers
# and retrieving single packages and their dependencies and their dependencies,...
# from the server into the destination folder.
#
# the packages will not be installed, only downloaded, you can then transfer
# them to the target system and install them there
#
# by selecting an appropriate Packages file, you can select the architecture
# of the packages to download, i.e. choose the armel-version of squeeze to
# download old packages from squeeze for an ARM platform.
#
# Known Bugs:
# * dependencies with alternatives (a (version) | b (version)) are skipped entirely
import sys
import urllib.request
import os.path
import os
import hashlib
import subprocess
'''debian download server to use'''
server="http://archive.debian.org/debian/"
'''destination directory for the packages to go'''
destination="pkgs/"
def filesize ( size ):
arr = ["B", "KB", "MB", "GB", "TB"]
sz = float(size)
index = 0
while sz > 1024 and index < len(arr):
sz /= 1024;
index += 1;
return str(round(sz, 2)) + " " + arr[index]
def CheckHash ( package, filename ):
if os.path.isfile ( filename ) == False :
return False
st = os.stat(filename)
if st.st_size != int(package.Size) :
return False
data = b''
with open(filename, "rb") as f :
data = f.read()
sha1 = hashlib.sha1(data).hexdigest()
if sha1 != package.SHA1.strip():
return False
return True
def DownloadPackage ( package ) :
'''Download a package from the debian server /server/'''
if type(package) is not Package:
return
url = server + package.Filename;
filename = destination + package.Filename.split("/")[-1].strip()
if CheckHash ( package, filename ) == False:
print ("> Downloading package", package.Package, "to ", filename)
with urllib.request.urlopen(url) as response, open(filename, 'wb') as out_file:
data = response.read() # a `bytes` object
out_file.write(data)
if CheckHash ( package, filename ) == False:
print ("\033[31;1m> Problem downloading package ", package.Package, ": wrong hashes...\033[0m")
else:
print ("\033[33m> Package ", package.Package, " is already downloaded\033[0m")
def genericStr ( obj ):
''' prints all attributes of an object '''
result = ""
for a in dir( obj ):
if ( len(a) <= 0 or a[0] == "_" ) :
continue
if ( type(getattr(obj,a)) is int ):
result = result + "%s = %d\n"%(a, getattr(obj,a))
elif ( type(getattr(obj,a)) is str ):
result = result + "%s = %s\n"%(a, getattr(obj,a))
elif ( callable(getattr(obj,a)) ):
pass
elif ( type(getattr(obj,a)) is list ):
result = result + "%s = [ \n"%(a)
for l in getattr(obj,a):
result = result + " " + str(l) + ",\n";
result = result[:-2] + "]\n";
else:
result = result + "%s = %s\n"%(a, str(getattr(obj, a)))
return (result)
class Package ( object ):
'''representation of a debian Package'''
def __init__ ( self, txt, member=["all"] ):
lines = txt.split ( "\n" )
state = ""
for l in lines:
# a line starts with space, append it to the previous line
if len(l) > 1 and l[0] == " ":
if "all" in member or state in member:
old = getattr ( self, state )
old = old + l[0].strip()
setattr ( self, state, old )
else: ## otherwise it has the form Package: <name>
ln = l.split(": ")
if len(ln) < 2:
print("Error parsing line: ", l);
continue;
if len(ln) > 2 :
out = ""
for x in ln[1:]:
out += out + ": "
ln[1] = out;
state = ln[0].strip()
if "all" in member or state in member:
setattr ( self, state, ln[1].strip() )
if hasattr(self, "Depends") and ("Depends" in member or "all" in member) :
self.populateList( self.Depends )
def populateList ( self, string ):
'''after parsing the package-text, read the depends-member and create a list of dependencies'''
s = string.split(",")
self.Dependencies = []
for item in s:
i = item.split ("(")
if len(i) > 2 or len(i) == 0:
if "|" in string:
continue;
print ("Error parsing depends: %s"%s)
continue
if len(i) == 1:
self.Dependencies += [( i[0].strip(), "any" )]
else:
self.Dependencies += [( i[0].strip(), i[1].strip()[:-1].strip() )]
def __str__ ( self ):
return genericStr ( self )
def readPackageList ( filename ) :
'''open and read the Packages file, return a list of Package-objects'''
txt = ""
with open(filename, "r") as f:
txt = f.read()
pkgsplit = txt.split ("\n\n")
pkglist = []
for p in pkgsplit:
pkg = Package ( p )
if hasattr(pkg, "Package"):
pkglist += [pkg];
return pkglist
def findPackage ( pkglist, name ):
'''traverse the list of packages by name'''
for p in pkglist:
if name == p.Package:
return p
print ("\033[31;1m!! Could not find: %s\033[0m"%name)
return None;
def unpackPackages ( action, dest=None ):
'''unpack the already downloaded packages into a folder using dpkg'''
size = 0
if action == "list":
print ("> Downloaded packages")
if action == "unpack":
os.makedirs(dest, exist_ok=True)
with os.scandir ( destination ) as it:
for entry in it:
if entry.name.endswith(".deb") and entry.is_file():
if action == "unpack":
print ("\033[33m> Unpacking ", entry.name, "\033[0m")
proc = subprocess.run ( ["dpkg", "-x", destination+"/"+entry.name, dest] )
if proc.returncode != 0:
print ("\033[31;1m> Failed dpkg call: ", proc.stdout, "\033[0m")
sys.exit(proc.returncode)
elif action == "list":
print (" *", entry.name)
size += os.stat(destination+"/"+entry.name).st_size;
if action == "list":
print ("> Total size: ", filesize(size) )
def help ( action="help" ):
if action == "unpack":
print ("%s unpack [destination]")
print ("Unpack all downloaded packages with dpkg")
else:
print ("%s [action] [packagelist|destination] [packages to download]"%sys.argv[0]);
print ("")
print ("Actions:")
print (" * get - Download the listed packages")
print (" * list-get - List all packages which would be downloaded (dry-run of get)")
print (" * list - List downloaded packages")
print (" * search - Search and display information about the packages")
print (" * unpack - Extract all downloaded packages into destination (needs dpkg)")
#print (" * packagelist - Download package list of release and architecture (in this order)")
sys.exit(1);
def dwnldList ( action, pkglist, checklist ):
'''generate a list of downloads from the packagelist and a list of wanted packages'''
'''action == "get" then the dependencies are their deps are added as well'''
downloadlist = []
for name in checklist:
pkg = findPackage ( pkglist, name )
if pkg == None:
continue;
if pkg in downloadlist:
continue;
downloadlist += [pkg];
if action == "get" or action == "list-get":
if hasattr(pkg, "Dependencies"):
for dep in pkg.Dependencies:
print ("%s requires %s in version %s"%(pkg.Package, dep[0], dep[1]))
checklist += [dep[0].strip()]
return downloadlist
def getPackages ( action, packagefile, checklist ):
'''download a list of packages and their dependencies'''
pkglist = readPackageList ( packagefile )
downloadlist = dwnldList ( action, pkglist, checklist )
size = 0;
if action == "list-get":
print ("> I would download:")
for dnld in downloadlist:
if action == "list-get":
print (" * ", dnld.Package)
size += int(dnld.Size.strip())
elif action == "get":
DownloadPackage ( dnld );
if action == "list-get":
print ("> Total Size: ", filesize(size) )
def searchPackages ( action, packagefile, checklist ):
'''search a list of packages in the packagefile'''
pkglist = readPackageList ( packagefile )
downloadlist = dwnldList ( action, pkglist, checklist )
for entry in downloadlist:
print ( entry )
print ( "" )
def main ():
os.makedirs ( destination, exist_ok=True )
action = sys.argv[1] or "help"
if action == "unpack" and len(sys.argv) == 3:
unpackPackages ( action, sys.argv[2] )
elif action == "list":
unpackPackages ( action )
elif (action == "get" or action=="list-get") and len(sys.argv) > 3:
getPackages ( action, sys.argv[2], sys.argv[3:] )
elif action == "search" and len(sys.argv) > 3:
searchPackages ( action, sys.argv[2], sys.argv[3:] )
else:
help ( action )
if __name__ == "__main__" :
main()