diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b9affeb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM ubuntu:24.04 AS baseos +RUN apt update -y && apt install -y python3 curl xz-utils +FROM ubuntu:24.04 AS bombuilder +RUN apt update -y && apt install -y git build-essential autoconf libxml2-dev libssl-dev zlib1g-dev +RUN git clone "https://github.com/maltob/bomutils.git" /tmp/bomutils +WORKDIR /tmp/bomutils +RUN make +RUN git clone "https://github.com/mackyle/xar.git" /tmp/xar +WORKDIR /tmp/xar/xar +RUN sed -i 's/OpenSSL_add_all_ciphers/OPENSSL_init_crypto/g' configure.ac && ./autogen.sh && make +FROM baseos AS add7z +RUN curl https://www.7-zip.org/a/7z2408-linux-x64.tar.xz -o 7z.tar.xz && tar -xf 7z.tar.xz 7zzs && cp 7zzs /usr/bin/7zzs && chmod +x /usr/bin/7zzs +FROM add7z AS app +COPY --from=bombuilder /tmp/bomutils/build/bin/mkbom /usr/bin/ +COPY --from=bombuilder /tmp/bomutils/build/bin/lsbom /usr/bin/ +COPY --from=bombuilder /tmp/bomutils/build/bin/dumpbom /usr/bin/ +COPY --from=bombuilder /tmp/xar/xar/src/xar /usr/bin/ +RUN mkdir /app +RUN mkdir /out +COPY build_a_pkg.py /app/build_a_pkg.py +WORKDIR /app/ +VOLUME /out +VOLUME /app/input +CMD python /app/build_a_pkg.py \ No newline at end of file diff --git a/Dockerfile.fedora b/Dockerfile.fedora new file mode 100644 index 0000000..afaa727 --- /dev/null +++ b/Dockerfile.fedora @@ -0,0 +1,20 @@ +FROM fedora:41 AS baseos +RUN dnf update -y && dnf install -y xar dmg2img hfsplus-tools python3 curl +FROM fedora:41 AS bomBuilder +RUN dnf install -y git make g++ ld automake +RUN git clone "https://github.com/maltob/bomutils.git" /tmp/bomutils +WORKDIR /tmp/bomutils +RUN make +FROM baseos AS add7z +RUN curl https://www.7-zip.org/a/7z2408-linux-x64.tar.xz -o 7z.tar.xz && tar -xf 7z.tar.xz 7zzs && cp 7zzs /usr/bin/7zzs && chmod +x /usr/bin/7zzs +FROM add7z AS app +COPY --from=bomBuilder /tmp/bomutils/build/bin/mkbom /usr/bin/ +COPY --from=bomBuilder /tmp/bomutils/build/bin/lsbom /usr/bin/ +COPY --from=bomBuilder /tmp/bomutils/build/bin/dumpbom /usr/bin/ +RUN mkdir /app +RUN mkdir /out +COPY build_a_pkg.py /app/build_a_pkg.py +WORKDIR /app/ +VOLUME /out +VOLUME /app/input +CMD python /app/build_a_pkg.py \ No newline at end of file diff --git a/build_a_pkg.py b/build_a_pkg.py new file mode 100644 index 0000000..9200f16 --- /dev/null +++ b/build_a_pkg.py @@ -0,0 +1,125 @@ +import subprocess +import os +import pathlib +import shutil +import plistlib +from string import Template +import tempfile + + +class MacOSAppInfo: + CFBundleIdentifier: str + CFBundleVersion: str + CFBundleName: str + CFBundleExecutable: str + CFBundleDisplayName: str + LSMinimumSystemVersion: str + + def load_from_info_plist(self,path: str): + with open(path, 'rb') as iplfd: + plist = plistlib.load(iplfd) + if 'CFBundleIdentifier' in plist: + self.CFBundleIdentifier = plist["CFBundleIdentifier"] + if 'CFBundleVersion' in plist: + self.CFBundleVersion = plist["CFBundleVersion"] + if 'CFBundleName' in plist: + self.CFBundleName = plist["CFBundleName"] + if 'CFBundleExecutable' in plist: + self.CFBundleExecutable = plist["CFBundleExecutable"] + if 'CFBundleDisplayName' in plist: + self.CFBundleDisplayName = plist["CFBundleDisplayName"] + if 'LSMinimumSystemVersion' in plist: + self.LSMinimumSystemVersion = plist["LSMinimumSystemVersion"] + +def extract_dmg_app_and_contents(dmg_path: str, destination: str): + with tempfile.TemporaryDirectory("dmg_") as tdir: + convert_result = subprocess.run(['/usr/bin/7zzs', 'x', dmg_path,f"-o{tdir}"],shell=False) + for app in pathlib.Path(tdir).glob("*/*.app"): + shutil.copytree(app.absolute(),destination+"/"+app.name,dirs_exist_ok=True) + for pkg in pathlib.Path(tdir).glob("*/*.pkg"): + shutil.copyfile(pkg.absolute(),destination+"/"+pkg.name) +_input_path = "input" + +#Check if we mapped a volume for working on +if not os.path.isdir(_input_path): + exit(1) + +#Has a dmg - we should mount it and pull the app out +for dmg in pathlib.Path(_input_path).glob("*.dmg"): + dmg_path = dmg.absolute() + extract_dmg_app_and_contents(dmg_path= dmg_path, destination=_input_path) + +has_app: bool = False +for app in pathlib.Path(_input_path).glob("*.app"): + has_app = True + +has_pkg: bool = False +for pkg in pathlib.Path(_input_path).glob("*.pkg"): + shutil.copyfile(pkg.absolute(),f"/out/{pkg.name}") + has_pkg = True + +#If the DMG has a package - we can stop +if has_pkg: + exit(0) + +if not has_app: + print("No app to package!") + exit(1) + +#Package the app +#Root is where we create the payload from +#Flat is where we make the pkg from with root GZ'd into a Payload file +with tempfile.TemporaryDirectory("app_") as _package_base_path: + pathlib.Path(f"{_package_base_path}/flat/Resources/en.lproj").mkdir(parents=True) + pathlib.Path(f"{_package_base_path}/flat/base.pkg").mkdir(parents=True) + pathlib.Path(f"{_package_base_path}/root/Applications").mkdir(parents=True) + + app_name = "ConvertedApp" + appInfo = MacOSAppInfo() + #Copy over all apps + for app in pathlib.Path(_input_path).glob("*.app"): + shutil.copytree(app.absolute(),f"{_package_base_path}/root/Applications/{app.name}") + app_name = app.name + appInfo.load_from_info_plist(f"{_package_base_path}/root/Applications/{app.name}/Contents/Info.plist") + + + identifier = "com.converted."+app_name + + + #Get file counts and size for MacOS to use for progress and estimation of the install size + file_count = 0 + file_size = 0 + for item in pathlib.Path(f"{_package_base_path}/root/").rglob("*"): + if item.is_file() : + file_count+= 1 + file_size += item.stat().st_size + + #Add the metadata + package_info_template_src = '' + with open('templates/PackageInfo.template','r') as pi_file: + package_info_template_src = pi_file.read() + + package_info_template = Template(package_info_template_src) + package_info = package_info_template.substitute(IDENTIFIER=appInfo.CFBundleIdentifier,CFBUNDLE_IDENTIFIER=appInfo.CFBundleIdentifier,INSTALL_KBYTES=int(round(file_size/1024,ndigits=0)),NUMBER_OF_FILES=file_count,CFBUNDLEVERSION=appInfo.CFBundleVersion,PKG_VERSION="1.0",INSTALL_LOCATION="/",APP_PATH=f"./Applications/{app_name}",MIN_OS_VERSION="11.0",APP_NAME=app_name) + + #Make a Distribution file https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html + distribution_template_src = '' + with open('templates/Distribution.template','r') as di_file: + distribution_template_src = di_file.read() + distribution_template = Template(distribution_template_src) + distribution = distribution_template.substitute(IDENTIFIER=appInfo.CFBundleIdentifier,CFBUNDLE_IDENTIFIER=appInfo.CFBundleIdentifier,INSTALL_KBYTES=int(round(file_size/1024,ndigits=0)),NUMBER_OF_FILES=file_count,CFBUNDLEVERSION=appInfo.CFBundleVersion,PKG_VERSION="1.0",INSTALL_LOCATION="/",APP_PATH=f"./Applications/{app_name}",MIN_OS_VERSION="11.0",APP_NAME=app_name) + + + with open(f"{_package_base_path}/flat/base.pkg/PackageInfo", "w") as pkgInfoFd: + pkgInfoFd.write(package_info) + + + with open(f"{_package_base_path}/flat/Distribution", "w") as distFd: + distFd.write(distribution) + + #Package it up to CPIO and GZIP + subprocess.call(f"( cd {_package_base_path}/root && find . | cpio -o --format odc --owner 0:80 | gzip -c ) > {_package_base_path}/flat/base.pkg/Payload",shell=True) + subprocess.call(f"mkbom -u 0 -g 80 {_package_base_path}/root {_package_base_path}/flat/base.pkg/Bom",shell=True) + subprocess.call(f"cd {_package_base_path}/flat/ && xar --compression none -cf \"/out/{appInfo.CFBundleIdentifier}.pkg\" *",shell=True) + subprocess.call(f"( cd {_package_base_path} && pwd )",shell=True) + diff --git a/install_prereq_ubuntu.sh b/install_prereq_ubuntu.sh new file mode 100644 index 0000000..9059063 --- /dev/null +++ b/install_prereq_ubuntu.sh @@ -0,0 +1,10 @@ +#assumes ubuntu:24.04 + +apt update -y && apt install -y python3 curl xz-utils git build-essential autoconf libxml2-dev libssl-dev zlib1g-dev +curl https://www.7-zip.org/a/7z2408-linux-x64.tar.xz -o 7z.tar.xz && tar -xf 7z.tar.xz 7zzs && cp 7zzs /usr/bin/7zzs && chmod +x /usr/bin/7zzs +git clone "https://github.com/maltob/bomutils.git" /tmp/bomutils +git clone "https://github.com/mackyle/xar.git" /tmp/xar +cd /tmp/bomutils/ +make && make install +cd /tmp/xar/xar +sed -i 's/OpenSSL_add_all_ciphers/OPENSSL_init_crypto/g' configure.ac && ./autogen.sh && make && make install