From 716ff4180e724bb4c2d3296a18f55d8ddce104da Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Sun, 1 Jun 2025 10:44:10 +0900 Subject: [PATCH] Add python script that automates uploading VeraCrypt release files to Launchpad --- src/Build/veracrypt-launchpad-uploader.py | 158 ++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/Build/veracrypt-launchpad-uploader.py diff --git a/src/Build/veracrypt-launchpad-uploader.py b/src/Build/veracrypt-launchpad-uploader.py new file mode 100644 index 00000000..d71eda4c --- /dev/null +++ b/src/Build/veracrypt-launchpad-uploader.py @@ -0,0 +1,158 @@ +# ============================================================================= +# VeraCrypt Launchpad Uploader +# ============================================================================= +# +# Author: Mounir IDRASSI +# Date: May 31st, 2025 +# +# This script is part of the VeraCrypt project +# https://www.veracrypt.jp +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Description: +# This script automates the process of uploading VeraCrypt release packages +# to Launchpad. It authenticates with Launchpad, locates the appropriate +# project, series, milestone, and release, and then uploads all package files +# from a specified directory, skipping any that have already been uploaded. +# ============================================================================= + +import os +import mimetypes +from launchpadlib.launchpad import Launchpad + +# === CONFIGURATION === +PROJECT_NAME = 'veracrypt' +SERIES_NAME = 'trunk' +MILESTONE_NAME = '1.26.24' +RELEASE_VERSION = '1.26.24' +FILES_DIRECTORY = r"/opt/VeraCrypt_Packages/1.26.24" + +APPLICATION_NAME = 'launchpad-batch-uploader' +CACHEDIR = os.path.expanduser(r"~/.launchpadlib/cache") + +# === AUTHENTICATION === +print("Authenticating with Launchpad…") +launchpad = Launchpad.login_with(APPLICATION_NAME, 'production', CACHEDIR) + +# === LOOK UP TARGET OBJECTS === +try: + # First try direct dictionary-style lookup + project = launchpad.projects[PROJECT_NAME] +except KeyError: + # Fallback: use getByName on projects + project = launchpad.projects.getByName(name=PROJECT_NAME) + if project is None: + raise Exception(f"Project '{PROJECT_NAME}' not found.") + +# Safely fetch the series object +try: + series = project.series[SERIES_NAME] +except (KeyError, TypeError): + series = project.getSeries(name=SERIES_NAME) + if series is None: + raise Exception(f"Series '{SERIES_NAME}' not found in project '{PROJECT_NAME}'.") + +# === REPLACE getMilestone with a loop over all_milestones === +milestone = None +print(f"Locating milestone '{MILESTONE_NAME}' in series '{SERIES_NAME}'…") +for m in series.all_milestones: # ← series.all_milestones is a PagedCollection of Milestone + if m.name == MILESTONE_NAME: + milestone = m + break + +if milestone is None: + raise Exception(f"Milestone '{MILESTONE_NAME}' not found in series '{SERIES_NAME}'.") + +# --- FIND THE RELEASE UNDER THAT MILESTONE ---------------------------- +print(f"Locating release for milestone '{MILESTONE_NAME}'…") + +try: + release = milestone.release # <-- the only release tied to this milestone +except AttributeError: + # (very old Launchpadlib versions expose only the _link) + release = launchpad.load(milestone.release_link) + +# sanity-check +if release is None or release.version != RELEASE_VERSION: + raise Exception( + f"Expected version '{RELEASE_VERSION}', " + f"but milestone only links to '{getattr(release, 'version', None)}'." + ) +print("Release found. Beginning upload…") + +# === UPLOAD FILES === + +# Build a set of filenames already present on the release +existing_files = set() +for f in release.files: + # Each f is a URL; the filename is after the last '/' + filename_on_release = os.path.basename(f.self_link) + existing_files.add(filename_on_release) + +# Print existing files if existing_files is not empty +if not existing_files: + print("No files already uploaded to this release.") +else: + print("Files already uploaded to this release:") + for ef in sorted(existing_files): + print(" -", ef) + +print() + +for filename in os.listdir(FILES_DIRECTORY): + if filename.endswith('.sig'): + continue + + if filename in existing_files: + print(f">>> Skipping {filename} (already uploaded)") + continue + + filepath = os.path.join(FILES_DIRECTORY, filename) + sig_path = filepath + '.sig' + has_signature = os.path.isfile(sig_path) + + content_type, _ = mimetypes.guess_type(filepath) + content_type = content_type or 'application/octet-stream' + + print(f"Uploading: {filename} (type: {content_type})") + try: + with open(filepath, 'rb') as file_content: + file_bytes = file_content.read() + if has_signature: + with open(sig_path, 'rb') as sig_handle: + sig_bytes = sig_handle.read() + release.add_file( + description=f"Uploaded file: {filename}", + content_type=content_type, + filename=filename, + file_content=file_bytes, + signature_filename=os.path.basename(sig_path), + signature_content=sig_bytes + ) + print(f" -> Uploaded {filename} with signature.") + else: + release.add_file( + description=f"Uploaded file: {filename}", + content_type=content_type, + filename=filename, + file_content=file_bytes, + signature_filename=None, + signature_content=None + ) + print(f" -> Uploaded {filename} without signature.") + except Exception as e: + print(f"!!! Failed to upload '{filename}': {e}") + continue + +print("Done! All files uploaded (or attempted) successfully.")