Site icon Gioxx.org

YOURLS-diff: patch per aggiornare la tua istanza YOURLS

YOURLS-diff: patch per aggiornare la tua istanza YOURLS 1

Non stai sbagliandoti: non è la prima volta che parlo di YOURLS, né tanto meno di file diff (banalmente: delle patch) che ti permettono di tenere aggiornata la tua installazione.
Un articolo, che ho pubblicato nel 2019, ti spiegava come andare a confrontare molto agilmente due versioni di YOURLS per individuare i file differenti tra le due, e creare così una patch adatta a effettuare l’upgrade della tua installazione: YOURLS: upgrade dell’installazione (file diff).

Ho voluto riprendere in mano il codice proposto ormai più di 5 anni fa, farci un aeroplano di carta e tirarlo da qualche parte fuori dalla finestra.

Riscritto in Python, un pelo più al passo con i tempi, il codice di oggi permette di effettuare il confronto passando semplicemente i numeri di versione dal prompt dei comandi, ottenendo in uscita il file ZIP che ti servirà per effettuare l’upgrade (caricando i file sullo spazio FTP e sostituendo quelli vecchi già presenti). Su GitHub, nel repository che ho riportato a casa (nel mio account principale), l’operazione di confronto parte da sola, in base ai rilasci ufficiale del progetto YOURLS, consentendo così a chiunque di ottenere il file di patch nel più breve tempo possibile*.

Il codice parla più o meno da solo, ma te lo commento poco sotto.

Lo script principale

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
YOURLS-diff_CreatePackage.py

Generate a ZIP "patch" and an external manifest listing only the changed files
between two YOURLS releases, ready for FTP upload.

Usage:
    python YOURLS-diff_CreatePackage.py --old <OLD_TAG> [--new <NEW_TAG>] [--output <ZIP_NAME>] [--no-verify]

Options:
    --old        Tag of the starting release (required, e.g. 1.8.10)
    --new        Tag of the target release (default: latest)
    --output     Output ZIP filename (default: YOURLS-update-OLD-to-NEW.zip)
    --no-verify  Disable SSL certificate verification (not recommended)

Author: Gioxx
Repo:   https://github.com/gioxx/YOURLS-diff
License: MIT
"""
import argparse
import os
import sys
import tempfile
import requests
import urllib3
import zipfile
import filecmp

GITHUB_API_LATEST = "https://api.github.com/repos/YOURLS/YOURLS/releases/latest"
ZIP_URL_TEMPLATE = "https://github.com/YOURLS/YOURLS/archive/refs/tags/{tag}.zip"

def download_zip(tag, dest_path, verify_ssl):
    """Download the ZIP archive for the given YOURLS release tag to dest_path."""
    url = ZIP_URL_TEMPLATE.format(tag=tag)
    print(f"→ Downloading {tag} from {url}")
    r = requests.get(url, stream=True, verify=verify_ssl)
    r.raise_for_status()
    with open(dest_path, "wb") as f:
        for chunk in r.iter_content(1024 * 1024):
            f.write(chunk)
    return dest_path

def get_latest_tag(verify_ssl):
    """Fetch the tag name of the latest YOURLS release from the GitHub API."""
    r = requests.get(GITHUB_API_LATEST, verify=verify_ssl)
    r.raise_for_status()
    return r.json()["tag_name"]

def extract_zip(zip_path, extract_to):
    """Extract the ZIP file at zip_path into extract_to and return the main folder path."""
    with zipfile.ZipFile(zip_path, "r") as z:
        z.extractall(extract_to)
    subdirs = [d for d in os.listdir(extract_to) if os.path.isdir(os.path.join(extract_to, d))]
    return os.path.join(extract_to, subdirs[0]) if len(subdirs) == 1 else extract_to

def collect_changed(old_dir, new_dir):
    """Return a list of new or modified file paths in new_dir compared to old_dir."""
    changed = []
    comp = filecmp.dircmp(old_dir, new_dir)
    # Files only in new_dir
    for name in comp.right_only:
        path = os.path.join(new_dir, name)
        if os.path.isfile(path):
            changed.append(path)
        else:
            # Recurse into new directories
            for root, _, files in os.walk(path):
                for fn in files:
                    changed.append(os.path.join(root, fn))
    # Files present in both but different
    for name in comp.diff_files:
        changed.append(os.path.join(new_dir, name))
    # Recurse into common subdirectories
    for sub in comp.common_dirs:
        changed += collect_changed(
            os.path.join(old_dir, sub),
            os.path.join(new_dir, sub)
        )
    return changed

def write_manifest(changed_files, new_root, manifest_path):
    """Write a manifest file listing the changed files (paths relative to new_root)."""
    with open(manifest_path, "w", encoding="utf-8") as mf:
        for full in sorted(changed_files):
            rel = os.path.relpath(full, new_root)
            mf.write(rel + "\n")
    print(f"→ Manifest saved to {manifest_path}")

def create_diff_zip(changed_files, new_root, zip_output):
    """Create a ZIP archive containing only the changed files."""
    print(f"→ Creating package {zip_output}")
    with zipfile.ZipFile(zip_output, "w", compression=zipfile.ZIP_DEFLATED) as z:
        for full_path in changed_files:
            rel = os.path.relpath(full_path, new_root)
            z.write(full_path, rel)
    print("→ Done.")

def main():
    parser = argparse.ArgumentParser(
        description="Prepare a ZIP package with differences between two YOURLS releases and an external manifest file."
    )
    parser.add_argument("--old", required=True,
                        help="Tag of the starting release (e.g. '1.8.10').")
    parser.add_argument("--new", default=None,
                        help="Tag of the target release (if omitted, 'latest' is used).")
    parser.add_argument("--output", default=None,
                        help="Output ZIP filename (default: YOURLS-update-OLD-to-NEW.zip).")
    parser.add_argument(
        "--no-verify",
        action="store_true",
        help="Disable SSL certificate verification (not recommended)."
    )
    args = parser.parse_args()

    # Determine SSL verification setting
    verify_ssl = not args.no_verify
    print(f"→ SSL verification is {'disabled' if not verify_ssl else 'enabled'}.")
    if args.no_verify:
        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

    # Determine tags
    old_tag = args.old
    if args.new:
        new_tag = args.new
    else:
        new_tag = get_latest_tag(verify_ssl)
        print(f"→ No target version specified, using latest: {new_tag}")

    # Exit early if tags are identical
    if old_tag == new_tag:
        print(f"Old tag '{old_tag}' and new tag '{new_tag}' are identical. Nothing to do.")
        sys.exit(0)

    # Determine output names
    zip_name = args.output or f"YOURLS-update-{old_tag}-to-{new_tag}.zip"
    manifest_name = os.path.splitext(zip_name)[0] + ".txt"

    with tempfile.TemporaryDirectory() as tmp:
        old_zip = os.path.join(tmp, f"{old_tag}.zip")
        new_zip = os.path.join(tmp, f"{new_tag}.zip")
        download_zip(old_tag, old_zip, verify_ssl)
        download_zip(new_tag, new_zip, verify_ssl)

        old_dir = extract_zip(old_zip, os.path.join(tmp, "old"))
        new_dir = extract_zip(new_zip, os.path.join(tmp, "new"))

        print("→ Comparing directories…")
        changed = collect_changed(old_dir, new_dir)
        if not changed:
            print("No differences found. Exiting.")
            sys.exit(0)

        # Generate external manifest
        manifest_path = os.path.join(os.getcwd(), manifest_name)
        write_manifest(changed, new_dir, manifest_path)

        # Create the diff ZIP
        create_diff_zip(changed, new_dir, zip_name)

    print(f"All set: upload {zip_name} via FTP and review {manifest_name} for the list of changed files.")

if __name__ == "__main__":
    main()

Il codice più aggiornato sarà sempre disponibile su GitHub, qui sopra ti ho riportato la prima versione del nuovo script.

python YOURLS-diff_CreatePackage.py --old <OLD_TAG> [--new <NEW_TAG>] [--output <ZIP_NAME>] [--no-verify]

Lo script, lanciato da riga di comando, accetta i parametri:

Forse inutile dirlo (ma non si sa mai), lo script non creerà alcun file ZIP nel caso in cui a confrontarsi saranno due versioni che non hanno alcuna differenza, cosa che può capitare se qualche burlone decide di confrontare la medesima versione (o l’ultima disponibile, senza specificare alcunché tramite parametro –new).
E ancora: se vuoi creare file patch che coprano salti più grandi (passare, per esempio, dalla versione 1.9.2 alla 1.10.1) potrai sempre farlo, semplicemente andrai a creare una patch che non esiste sul mio repository, dedicata magari solo alla tua installazione. Puoi anche invertire ciò che passi tramite old con ciò che passi con new, poco importa, la sostanza non cambia.

Verrà prodotto anche un piccolo file manifest, testuale, per riepilogare l’elenco dei file modificati da una versione all’altra. Forse non ti servirà a granché, ma torna utile al sottoscritto, per poter individuare facilmente cosa è stato toccato e dirlo anche nelle informazioni di riepilogo del pacchetto patch che rilascio su GitHub.

Automatismo

Tramite GitHub Actions sono andato a creare un meccanismo automatico che permetterà allo script di funzionare senza mio intervento (questo: github.com/gioxx/YOURLS-diff/blob/main/.github/workflows/patch.yml, NdA), creando un pacchetto di aggiornamento che chiunque potrà scaricare e utilizzare per aggiornare rapidamente la propria istanza di YOURLS nel momento in cui il team di sviluppo rilascerà una nuova versione nel suo repository ufficiale (questo: github.com/YOURLS/YOURLS, NdA).

L’azione è programmata per avviarsi ogni giorno a mezzanotte. Se esiste una nuova versione di YOURLS, allora comincia il processo di creazione del file di patch. Tu troverai la nuova patch disponibile nei rilasci del mio repository: github.com/gioxx/YOURLS-diff/releases, l’ultima patch disponibile (la più aggiornata) risponderà sempre all’URL github.com/gioxx/YOURLS-diff/releases/latest. Il file ZIP, come già spiegato nel paragrafo precedente, ti servirà per saltare da vecchia a nuova versione.

Attenzione

Non utilizzare una mia patch per saltare all’ultima versione disponibile partendo, però, da una versione più vecchia non compresa nel file di patch.

Spiego meglio facendo un esempio pratico: se stai usando YOURLS 1.9.2 e vuoi passare a YOURLS 1.10.1, dovrai scaricare entrambi i pacchetti di patch (YOURLS-update-1.9.2-to-1.10.0.zip e YOURLS-update-1.10.0-to-1.10.1.zip) avendo cura di scompattarli e caricarli in ordine di versione.

Farai, perciò, l’upload e la sovrascrittura dei file contenuti in YOURLS-update-1.9.2-to-1.10.0.zip, poi farai lo stesso con quelli contenuti nel pacchetto YOURLS-update-1.10.0-to-1.10.1.zip, diversamente romperai il giocattolo e renderai inutilizzabile la tua istanza YOURLS.

Se hai bisogno di saltare in un solo colpo più versioni, ti consiglio caldamente di utilizzare lo script sul tuo PC per generare manualmente il pacchetto patch partendo da una vecchia versione più in là nel tempo. Se non sei capce di farlo da solo, ti invito a lasciare un commento, ti aiuterò io.

In conclusione

Penso e spero di aver scritto tutto il necessario.
Il repository è già in piedi da qualche tempo e ha prodotto in automatico il file di patch che permette l’upgrade a YOURLS 1.10.1, l’ho usato per aggiornare un paio di istanze senza battere ciglio.

Ho proposto di includere il progetto all’interno di Awesome YOURLS (dove già trovi i miei plugin per YOURLS, da due presto ne tirerò fuori uno solo, più completo), ora non resta che attendere l’esito della segnalazione (github.com/YOURLS/awesome/issues/199).

In caso di dubbi, usa l’area commenti che trovi subito dopo l’articolo! 😁👋

#KeepItSimple

Correzioni, suggerimenti? Lascia un commento nell'apposita area qui di seguito o contattami privatamente.
Ti è piaciuto l'articolo? Offrimi un caffè! ☕ :-)

Exit mobile version