mirror of
https://github.com/lifegpc/pythonscript.git
synced 2026-06-05 11:08:49 +08:00
206 lines
9.8 KiB
Python
206 lines
9.8 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import datetime, hashlib, os, re, shutil, subprocess, sys, glob, argparse
|
|
|
|
COMPONENTS = []
|
|
supported_arches = ['all', 'arm', 'i686', 'aarch64', 'x86_64']
|
|
encountered_arches = set()
|
|
hashes = ['md5', 'sha1', 'sha256', 'sha512']
|
|
|
|
def get_package_name(filename):
|
|
# Expects the 'name_version_arch.deb' naming scheme.
|
|
return filename.split('_')[0]
|
|
|
|
def run_shell_command(cmd):
|
|
print(cmd)
|
|
try:
|
|
return subprocess.check_output(["cmd", "/C", cmd], shell=True).strip()
|
|
except subprocess.CalledProcessError:
|
|
return None
|
|
|
|
def control_file_contents(debfile):
|
|
file_list = run_shell_command("ar t {}".format(debfile))
|
|
if file_list is None:
|
|
sys.exit("Error listing contents of '{}'".format(debfile))
|
|
|
|
file_list = file_list.decode().split("\r\n")
|
|
print(file_list)
|
|
|
|
if "control.tar.gz" in file_list:
|
|
control_filename = "control.tar.gz"
|
|
tar_args = "-z"
|
|
elif "control.tar.xz" in file_list:
|
|
control_filename = "control.tar.xz"
|
|
tar_args = "-J"
|
|
else:
|
|
sys.exit("Failed to find control file in '{}'".format(debfile))
|
|
|
|
re = run_shell_command("7z x {} {}".format(debfile, control_filename))
|
|
if re is None:
|
|
sys.exit(-1)
|
|
contents = run_shell_command("tar -O {} -xf {} ./control".format(tar_args, control_filename))
|
|
os.remove(control_filename)
|
|
if contents is None:
|
|
sys.exit("Error extracting control file from '{}'".format(debfile))
|
|
|
|
return contents
|
|
|
|
def list_package_files(debfile):
|
|
run_shell_command("7z x -y " + debfile + " data.tar.xz")
|
|
all_content = run_shell_command("bsdtar -tJf data.tar.xz").decode()
|
|
os.remove('data.tar.xz')
|
|
return [files.strip("./") for files in all_content.splitlines() if not files[-1] == "/"]
|
|
|
|
def add_deb(deb_to_add_path, component, use_hard_links):
|
|
deb_to_add_control_file = control_file_contents(deb_to_add_path).decode()
|
|
print(deb_to_add_control_file)
|
|
deb_to_add_pkg_name = re.search('Package: (.*)', deb_to_add_control_file).group(1)
|
|
deb_to_add_pkg_version = re.search('Version: (.*)', deb_to_add_control_file).group(1)
|
|
deb_arch = re.search('Architecture: (.*)', deb_to_add_control_file).group(1)
|
|
|
|
if not deb_arch in supported_arches:
|
|
sys.exit('Unsupported arch "' + deb_arch + '" in ' + os.path.basename(deb_to_add_path))
|
|
encountered_arches.add(deb_arch)
|
|
|
|
package_name = get_package_name(os.path.basename(deb_to_add_path))
|
|
arch_dir_path = os.path.join(distribution_path, component, 'binary-' + deb_arch)
|
|
|
|
if not os.path.isdir(arch_dir_path):
|
|
os.makedirs(arch_dir_path)
|
|
|
|
# Add .deb file:
|
|
print('Adding deb file: ' + os.path.basename(deb_to_add_path) + '...')
|
|
dest_deb_dir_path = os.path.join(distribution_path, component, 'binary-' + deb_arch)
|
|
if not os.path.isdir(dest_deb_dir_path): os.makedirs(dest_deb_dir_path)
|
|
destination_deb_file = os.path.join(dest_deb_dir_path, os.path.basename(deb_to_add_path))
|
|
|
|
if use_hard_links:
|
|
os.link(deb_to_add_path, destination_deb_file)
|
|
else:
|
|
shutil.copy2(deb_to_add_path, destination_deb_file)
|
|
|
|
with open(os.path.join(distribution_path, component, 'Contents-' + deb_arch), 'a') as contents_file:
|
|
for f in list_package_files(destination_deb_file):
|
|
print("%-80s %s" % (f, deb_to_add_pkg_name), file=contents_file)
|
|
|
|
parser = argparse.ArgumentParser(description='Create a repository with deb files')
|
|
parser.add_argument('input', metavar='input', type=str,
|
|
help='folder where .deb files are located')
|
|
parser.add_argument('output', metavar='output', type=str,
|
|
help='folder with repository tree')
|
|
parser.add_argument('distribution', metavar='dist', type=str, nargs='?', default='termux',
|
|
help='name of distribution folder. deb files are put into output/dists/distribution/component/binary-$ARCH/')
|
|
parser.add_argument('component', metavar='comp', type=str, nargs='?', default='extras',
|
|
help='name of component folder. deb files are put into output/dists/distribution/component/binary-$ARCH/')
|
|
parser.add_argument('--use-hard-links', default=False, action='store_true',
|
|
help='use hard links instead of copying deb files. Will not work on an android device')
|
|
|
|
args = parser.parse_args()
|
|
input_path = args.input
|
|
output_path = args.output
|
|
DISTRIBUTION = args.distribution
|
|
default_component = args.component
|
|
use_hard_links = args.use_hard_links
|
|
distribution_path = os.path.join(output_path, 'dists', DISTRIBUTION)
|
|
|
|
if not os.path.isdir(input_path):
|
|
sys.exit("'" + input_path + '" does not exist')
|
|
|
|
debs_in_path = glob.glob(os.path.join(input_path, "*.deb"))
|
|
debs_in_path += glob.glob(os.path.join(input_path, "*/*.deb"))
|
|
if not debs_in_path:
|
|
sys.exit('No .deb file found in ' + input_path)
|
|
else:
|
|
for deb_path in sorted(debs_in_path):
|
|
component = os.path.dirname(os.path.relpath(deb_path, input_path))
|
|
if not component:
|
|
component = default_component
|
|
if component not in COMPONENTS:
|
|
COMPONENTS.append(component)
|
|
if os.path.isdir(os.path.join(distribution_path, component)):
|
|
shutil.rmtree(os.path.join(distribution_path, component))
|
|
os.makedirs(os.path.join(distribution_path, component))
|
|
|
|
add_deb(deb_path, component, use_hard_links)
|
|
|
|
# See https://wiki.debian.org/RepositoryFormat#A.22Release.22_files for format:
|
|
release_file_path = distribution_path + '/Release'
|
|
release_file = open(release_file_path, 'w')
|
|
print("Codename: termux", file=release_file)
|
|
print("Version: 1", file=release_file)
|
|
print("Architectures: " + ' '.join(encountered_arches), file=release_file)
|
|
print("Description: " + DISTRIBUTION + " repository", file=release_file)
|
|
print("Suite: " + DISTRIBUTION, file=release_file)
|
|
print("Date: " + datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S UTC'), file=release_file)
|
|
|
|
# Create Packages files:
|
|
for component in COMPONENTS:
|
|
for arch_dir_path in glob.glob(os.path.join(distribution_path, component, 'binary-*')):
|
|
arch = os.path.basename(arch_dir_path).split('-')[1]
|
|
print('Creating package file for ' + component + " and " + arch + '...')
|
|
packages_file_path = arch_dir_path + '/Packages'
|
|
packagesxz_file_path = packages_file_path + '.xz'
|
|
binary_path = 'binary-' + arch
|
|
with open(packages_file_path, 'w') as packages_file:
|
|
for deb_to_read_path in sorted(glob.glob(os.path.join(arch_dir_path, "*.deb"))):
|
|
# Extract the control file from the .deb:
|
|
scanpackages_output = control_file_contents(deb_to_read_path).decode()
|
|
package_name = re.search('Package: (.*)', scanpackages_output).group(1)
|
|
package_arch = re.search('Architecture: (.*)', scanpackages_output).group(1)
|
|
# Add these fields which dpkg-scanpackages would have done:
|
|
scanpackages_output += '\nFilename: ' + os.path.join('dists', DISTRIBUTION, component, binary_path, os.path.basename(deb_to_read_path)).replace('\\', '/')
|
|
scanpackages_output += '\nSize: ' + str(os.stat(deb_to_read_path).st_size)
|
|
for hash in hashes:
|
|
if hash == "md5":
|
|
hash_string = hash.upper()+'Sum'
|
|
else:
|
|
hash_string = hash.upper()
|
|
scanpackages_output += '\n'+hash_string + ': ' + getattr(hashlib, hash)(open(deb_to_read_path, 'rb').read()).hexdigest()
|
|
print(scanpackages_output, file=packages_file)
|
|
print('', file=packages_file)
|
|
# Create Packages.xz
|
|
run_shell_command('xz -9 --keep ' + packages_file_path)
|
|
for contents_file in glob.glob(os.path.join(distribution_path, component, 'Contents-*')):
|
|
run_shell_command('xz -9 --keep ' + contents_file)
|
|
|
|
# Get components in output folder, we might have more folders than we are adding now
|
|
COMPONENTS = [d for d in os.listdir(distribution_path) if os.path.isdir(os.path.join(distribution_path, d))]
|
|
for hash in hashes:
|
|
if hash == 'md5':
|
|
hash_string = hash.upper()+'Sum'
|
|
else:
|
|
hash_string = hash.upper()
|
|
print(hash_string + ':', file=release_file)
|
|
|
|
for component in COMPONENTS:
|
|
for arch_dir_path in glob.glob(os.path.join(distribution_path, component, 'binary-*')):
|
|
# Write info about Packages and Packages.xz to Release file:
|
|
for f in ['Packages', 'Packages.xz']:
|
|
print(' '+' '.join([getattr(hashlib, hash)(open(os.path.join(arch_dir_path, f), 'rb').read()).hexdigest(),
|
|
str(os.stat(os.path.join(arch_dir_path, f)).st_size),
|
|
os.path.join(component, os.path.basename(arch_dir_path), f).replace('\\', '/')])
|
|
, file=release_file)
|
|
# Write info about Contents and Contents.gz to Release file:
|
|
for contents_file in glob.glob(os.path.join(distribution_path, component, 'Contents-*')):
|
|
print(' '+' '.join([getattr(hashlib, hash)(open(contents_file, 'rb').read()).hexdigest(),
|
|
str(os.stat(contents_file).st_size), os.path.relpath(contents_file, distribution_path).replace('\\', '/')])
|
|
, file=release_file)
|
|
release_file.close()
|
|
|
|
if True:
|
|
print('Signing with gpg...')
|
|
run_shell_command('gpg --yes --digest-algo SHA256 --clearsign -o '
|
|
+ distribution_path + '/InRelease ' + distribution_path + '/Release')
|
|
|
|
print('Done!')
|
|
print('')
|
|
print('Make the ' + output_path + ' directory accessible at $REPO_URL')
|
|
print('')
|
|
print('Users can then access the repo by adding a file at')
|
|
print(' $PREFIX/etc/apt/sources.list.d')
|
|
print('containing:')
|
|
for component in COMPONENTS:
|
|
print(' deb [trusted=yes] $REPO_URL '+DISTRIBUTION+' '+component)
|
|
print('')
|
|
print('[trusted=yes] is not needed if the repo has been signed with a gpg key')
|