From ade9cb6db95750e96e0d0ddd3a009bcdf227bd14 Mon Sep 17 00:00:00 2001 From: phantom <137958098+phantomcortex@users.noreply.github.com> Date: Tue, 10 Feb 2026 11:41:24 -0700 Subject: [PATCH 1/3] testing: release overhaul 01 --- .github/workflows/build.yml | 99 ++++++--- .github/workflows/changelog.py | 372 ++++++++++++++++++++++++++++----- 2 files changed, 390 insertions(+), 81 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8386230..9ebcec3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,8 +5,7 @@ on: branches: - main schedule: - - cron: '05 6 */5 * *' # 12:05am Mountain time every 5 days - # Reference: 'minute hour day(s) Month day-of-the-week' + - cron: '05 6 */5 * *' # 12:05am Mountain time every 5 days Ref: 'minute hour day(s) Month day-of-the-week' push: branches: - main @@ -22,16 +21,18 @@ on: description: 'Clear rechunk history (forces full redownload for users)' type: boolean default: false + handwritten: + description: 'Optional handwritten changelog message for this release' + type: string + default: '' env: IMAGE_DESC: "My Customized Bazzite Image" IMAGE_KEYWORDS: "bootc,ublue,universal-blue" - IMAGE_LOGO_URL: "https://avatars.githubusercontent.com/u/120078124?s=200&v=4" + IMAGE_LOGO_URL: "https://avatars.githubusercontent.com/u/120078124?s=200&v=4" IMAGE_NAME: "${{ github.event.repository.name }}" # output image name, usually same as repo name IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}" # do not edit DEFAULT_TAG: "latest" -# TODO: optimize build (if possible) - concurrency: group: ${{ github.workflow }}-${{ github.ref || github.run_id }} cancel-in-progress: true @@ -46,11 +47,16 @@ jobs: packages: write id-token: write + outputs: + digest: ${{ steps.push.outputs.digest }} + steps: # These stage versions are pinned by https://github.com/renovatebot/renovate - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - + with: + fetch-depth: 0 # Full history for changelog commit range + - name: Prepare environment run: | # Lowercase the image uri @@ -84,6 +90,7 @@ jobs: org.opencontainers.image.url=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} org.opencontainers.image.vendor=${{ github.repository_owner }} org.opencontainers.image.version=${{ env.DEFAULT_TAG }}.{{date 'YYYYMMDD'}} + org.opencontainers.image.revision=${{ github.sha }} io.artifacthub.package.deprecated=false io.artifacthub.package.keywords=${{ env.IMAGE_KEYWORDS }} io.artifacthub.package.license=Apache-2.0 @@ -92,7 +99,7 @@ jobs: containers.bootc=1 sep-tags: " " sep-annotations: " " - + - name: Maximize build space uses: jlumbroso/free-disk-space@v1.3.1 with: @@ -102,7 +109,7 @@ jobs: large-packages: true docker-images: false swap-storage: true - + - name: Build image (rootful) id: build_image run: | @@ -112,7 +119,7 @@ jobs: --tag "localhost/${IMAGE_NAME}:${{ env.DEFAULT_TAG }}" \ --file Containerfile \ . - + - name: Remove source images run: | images=$(sudo podman images -n --sort repository --format '{{.ID}} {{.Repository}}' | grep -v localhost | awk '{print $1}') @@ -124,8 +131,7 @@ jobs: else echo "No images to remove." fi - - + - name: Run Rechunker id: rechunk uses: hhd-dev/rechunk@v1.2.4 @@ -146,7 +152,7 @@ jobs: else echo "No image to remove" fi - + - name: Rechunk output continue-on-error: true if: github.event_name != 'pull_request' @@ -168,8 +174,8 @@ jobs: STEPS_RECHUNK_CONCLUSION: ${{ steps.rechunk.conclusion }} STEPS_RECHUNK_OUTPUTS_CHANGELOG: ${{ steps.rechunk.outputs.changelog }} STEPS_RECHUNK_OUTPUTS_MANIFEST: ${{ steps.rechunk.outputs.manifest }} - - - name: Load in podman and tag + + - name: Load in podman and tag if: github.event_name != 'pull_request' run: | IMAGE=$(podman pull ${STEPS_RECHUNK_OUTPUTS_REF}) @@ -182,7 +188,7 @@ jobs: STEPS_RECHUNK_OUTPUTS_LOCATION: ${{ steps.rechunk.outputs.location }} STEPS_METADATA_OUTPUTS_TAGS: ${{ steps.metadata.outputs.tags }} IMAGE_NAME: ${{ env.IMAGE_NAME }} - + - name: Login to GitHub Container Registry uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 if: github.event_name != 'pull_request' @@ -191,19 +197,18 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Lowercase Registry + - name: Lowercase Registry id: registry_case uses: ASzc/change-string-case-action@v6 with: string: ${{ env.IMAGE_REGISTRY }} - + - name: Inspect layer sizes run: | echo "=== Layer size analysis ===" podman inspect localhost/${IMAGE_NAME}:${DEFAULT_TAG} | jq '.[0].RootFS.Layers[] | length' | \ awk '{sum+=$1; print "Layer size: " $1/1024/1024 " MB"} END {print "Total: " sum/1024/1024 " MB"}' - #This should be temporary, builds are failing to upload, There are a few likely culprits - + - name: Push To GHCR uses: redhat-actions/push-to-registry@5ed88d269cf581ea9ef6dd6806d01562096bee9c # v2 if: github.event_name != 'pull_request' @@ -242,10 +247,56 @@ jobs: STEPS_PUSH_OUTPUTS_REGISTRY_PATHS: ${{ steps.push.outputs.registry-paths }} STEPS_REGISTRY_CASE_OUTPUTS_LOWERCASE: ${{ steps.registry_case.outputs.lowercase }} STEPS_PUSH_OUTPUTS_DIGEST: ${{ steps.push.outputs.digest }} - - - name: Create changelog annotation + + generate_release: + name: Generate Release + needs: build_push + if: github.event_name != 'pull_request' + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + with: + fetch-depth: 500 # Deep history for commit range in changelog + + - name: Install skopeo + run: | + sudo apt-get update -q + sudo apt-get install -y -q skopeo + + - name: Generate changelog id: changelog - continue-on-error: true + env: + IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}" + IMAGE_NAME: ${{ github.event.repository.name }} + HANDWRITTEN: ${{ github.event.inputs.handwritten || '' }} run: | - CHANGELOG=$(.github/workflows/changelog.py distinctionos -) - echo "$CHANGELOG" >> $GITHUB_STEP_SUMMARY + REGISTRY="${IMAGE_REGISTRY,,}" + IMAGE="${IMAGE_NAME,,}" + python3 .github/workflows/changelog.py \ + "${IMAGE}" \ + ./output.env \ + ./changelog.md \ + --registry "docker://${REGISTRY}/" \ + --workdir . \ + --handwritten "${HANDWRITTEN}" + + # Source the env file for tag and title + source ./output.env + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "title=${TITLE}" >> $GITHUB_OUTPUT + + # Show in job summary + echo "## Release: ${TITLE}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + cat ./changelog.md >> $GITHUB_STEP_SUMMARY + + - name: Create Release + uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2 + with: + name: ${{ steps.changelog.outputs.title }} + tag_name: ${{ steps.changelog.outputs.tag }} + body_path: ./changelog.md + make_latest: true diff --git a/.github/workflows/changelog.py b/.github/workflows/changelog.py index 696d437..f9d8a26 100755 --- a/.github/workflows/changelog.py +++ b/.github/workflows/changelog.py @@ -1,4 +1,16 @@ #!/usr/bin/env python3 +""" +Generate a Bazzite-style changelog for DistinctionOS releases. + +Produces a release page with: + - Pinned major packages (Kernel, GNOME, Mesa, Gamescope) at the top + - Commit messages between builds + - Full package diff table (added/removed/updated) + - Rebase instructions + +Reads package data from the `dev.hhd.rechunk.info` OCI label embedded by +Rechunker, and derives the commit range from `org.opencontainers.image.revision`. +""" import subprocess import json @@ -6,16 +18,36 @@ import time import argparse import sys -from typing import List, Dict, Tuple +from typing import Dict, List, Optional, Tuple + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- -# Defaults DEFAULT_REGISTRY = "docker://ghcr.io/phantomcortex/" RETRIES = 3 RETRY_WAIT = 5 FEDORA_PATTERN = re.compile(r"\.fc\d{1,2}") SIG_PATTERN = re.compile(r"^sha256-.*\.sig$") +GITHUB_REPO = "phantomcortex/distinctionos" + +# Major / pinned packages shown prominently at the top of every release. +# Display Name -> RPM package name used as the lookup key in rechunk metadata. +PINNED_PACKAGES: List[Tuple[str, str]] = [ + ("Kernel", "kernel"), + ("GNOME", "gnome-control-center-filesystem"), + ("Mesa", "mesa-filesystem"), + ("Gamescope", "gamescope"), +] -# Helpers +# Package names that appear in the pinned table are excluded from the +# full diff table to avoid duplication. +PINNED_PKG_NAMES = {pkg for _, pkg in PINNED_PACKAGES} + + +# --------------------------------------------------------------------------- +# Registry helpers +# --------------------------------------------------------------------------- def run_skopeo_inspect(registry: str, image: str, tag: str) -> Dict: """Fetch the manifest JSON for a given registry/image:tag using skopeo.""" @@ -26,117 +58,343 @@ def run_skopeo_inspect(registry: str, image: str, tag: str) -> Dict: ["skopeo", "inspect", ref], check=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, ).stdout return json.loads(output) except subprocess.CalledProcessError: if attempt < RETRIES: + print(f" Retry {attempt}/{RETRIES} for {ref}", file=sys.stderr) time.sleep(RETRY_WAIT) else: raise def get_all_tags(registry: str, image: str) -> List[str]: - """Retrieve all non-signature tags from the repository for a given image.""" - manifest = run_skopeo_inspect(registry, image, 'latest') - tags = manifest.get('RepoTags', []) or [] - # filter out .0 suffix, signature manifests, and digests - filtered = [t for t in tags - if not t.endswith('.0') - and not SIG_PATTERN.match(t) - and not t.startswith('sha256-')] + """Retrieve all non-signature, non-digest tags sorted lexicographically.""" + manifest = run_skopeo_inspect(registry, image, "latest") + tags = manifest.get("RepoTags", []) or [] + filtered = [ + t for t in tags + if not t.endswith(".0") + and not SIG_PATTERN.match(t) + and not t.startswith("sha256-") + ] return sorted(filtered) def select_two_latest(tags: List[str]) -> Tuple[str, str]: - """Pick the two newest tags (sorted lexicographically).""" + """Return (previous_tag, current_tag) from the sorted tag list.""" if len(tags) < 2: raise ValueError(f"Not enough tags to diff, found: {tags}") return tags[-2], tags[-1] +# --------------------------------------------------------------------------- +# Package extraction +# --------------------------------------------------------------------------- + def extract_packages(info: Dict) -> Dict[str, str]: - """Extract package-version mapping from manifest labels.""" - labels = info.get('Labels', {}) or {} - data = labels.get('dev.hhd.rechunk.info') + """Extract {package: version} from the rechunk metadata label.""" + labels = info.get("Labels", {}) or {} + data = labels.get("dev.hhd.rechunk.info") if not data: return {} - packages = json.loads(data).get('packages', {}) - return {pkg: re.sub(FEDORA_PATTERN, '', ver) for pkg, ver in packages.items()} + packages = json.loads(data).get("packages", {}) + return {pkg: re.sub(FEDORA_PATTERN, "", ver) for pkg, ver in packages.items()} + + +def get_version(packages: Dict[str, str], rpm_name: str) -> Optional[str]: + """Look up a single package version, returning None if absent.""" + return packages.get(rpm_name) + + +# --------------------------------------------------------------------------- +# Pinned packages table +# --------------------------------------------------------------------------- + +def build_pinned_table( + prev_pkgs: Dict[str, str], + curr_pkgs: Dict[str, str], +) -> str: + """Render the 'Major Packages' markdown table.""" + lines = [ + "### Major Packages", + "", + "| Name | Version |", + "| --- | --- |", + ] + for display_name, rpm_name in PINNED_PACKAGES: + prev_ver = get_version(prev_pkgs, rpm_name) + curr_ver = get_version(curr_pkgs, rpm_name) + if curr_ver is None: + lines.append(f"| **{display_name}** | *not found* |") + elif prev_ver and prev_ver != curr_ver: + lines.append(f"| **{display_name}** | {prev_ver} -> {curr_ver} |") + else: + lines.append(f"| **{display_name}** | {curr_ver} |") + return "\n".join(lines) + + +# --------------------------------------------------------------------------- +# Full package diff +# --------------------------------------------------------------------------- +def build_package_diff( + prev_pkgs: Dict[str, str], + curr_pkgs: Dict[str, str], +) -> str: + """Render the full added/removed/updated package diff table. -def compare_versions(prev: Dict[str, str], curr: Dict[str, str]) -> str: - """Generate markdown table rows for added, removed, and changed packages.""" - added = set(curr) - set(prev) - removed = set(prev) - set(curr) - changed = {pkg for pkg in prev.keys() & curr.keys() if prev[pkg] != curr[pkg]} + Packages already shown in the pinned table are excluded. + """ + added = (set(curr_pkgs) - set(prev_pkgs)) - PINNED_PKG_NAMES + removed = (set(prev_pkgs) - set(curr_pkgs)) - PINNED_PKG_NAMES + changed = { + pkg for pkg in (prev_pkgs.keys() & curr_pkgs.keys()) + if prev_pkgs[pkg] != curr_pkgs[pkg] and pkg not in PINNED_PKG_NAMES + } + if not added and not removed and not changed: + return "" + + # De-duplicate packages whose version string is identical to one we already + # listed (sub-packages from the same SRPM). + seen_versions: set = set() lines = [ - "| Change | Package | Previous | Current |", - "|---|---|---|---|" + "### Package Changes", + "", + "| | Name | Previous | New |", + "| --- | --- | --- | --- |", ] for pkg in sorted(added): - lines.append(f"| ✨ Added | {pkg} | | {curr[pkg]} |") + v = curr_pkgs[pkg] + if v in seen_versions: + continue + seen_versions.add(v) + lines.append(f"| ✨ | {pkg} | | {v} |") for pkg in sorted(removed): - lines.append(f"| ❌ Removed | {pkg} | {prev[pkg]} | |") + v = prev_pkgs[pkg] + if v in seen_versions: + continue + seen_versions.add(v) + lines.append(f"| ❌ | {pkg} | {v} | |") for pkg in sorted(changed): - lines.append(f"| 🔄 Updated | {pkg} | {prev[pkg]} | {curr[pkg]} |") + new_v = curr_pkgs[pkg] + if new_v in seen_versions: + continue + seen_versions.add(new_v) + lines.append(f"| 🔄 | {pkg} | {prev_pkgs[pkg]} | {new_v} |") + + return "\n".join(lines) + - return '\n'.join(lines) +# --------------------------------------------------------------------------- +# Commit log +# --------------------------------------------------------------------------- +def get_commits( + prev_manifest: Dict, + curr_manifest: Dict, + workdir: Optional[str], +) -> str: + """Generate a markdown table of commits between two builds. + + Uses the `org.opencontainers.image.revision` label to determine the + git SHA range, then runs `git log` in the provided workdir. + """ + if workdir is None: + return "" + + prev_labels = prev_manifest.get("Labels", {}) or {} + curr_labels = curr_manifest.get("Labels", {}) or {} + prev_sha = prev_labels.get("org.opencontainers.image.revision", "") + curr_sha = curr_labels.get("org.opencontainers.image.revision", "") + + if not prev_sha or not curr_sha or prev_sha == curr_sha: + return "" + + try: + result = subprocess.run( + [ + "git", "log", + "--pretty=format:%H|%h|%an|%s", + f"{prev_sha}..{curr_sha}", + ], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=workdir, + ) + except subprocess.CalledProcessError: + return "" + + raw = result.stdout.decode().strip() + if not raw: + return "" + + lines = [ + "### Commits", + "", + "| Hash | Subject | Author |", + "| --- | --- | --- |", + ] + for entry in raw.splitlines(): + parts = entry.split("|", 3) + if len(parts) < 4: + continue + full_hash, short_hash, author, subject = parts + # Skip merge commits + if subject.lower().startswith("merge"): + continue + link = f"https://github.com/{GITHUB_REPO}/commit/{full_hash}" + lines.append(f"| [**{short_hash}**]({link}) | {subject} | {author} |") + + if len(lines) <= 4: + # Only header rows, no actual commits after filtering + return "" + + return "\n".join(lines) + + +# --------------------------------------------------------------------------- +# Changelog assembly +# --------------------------------------------------------------------------- + +def generate_changelog( + image: str, + prev_tag: str, + curr_tag: str, + prev_manifest: Dict, + curr_manifest: Dict, + prev_pkgs: Dict[str, str], + curr_pkgs: Dict[str, str], + workdir: Optional[str] = None, + handwritten: str = "", +) -> Tuple[str, str, str]: + """Build the full markdown changelog, release title, and tag. + + Returns: + (changelog_md, title, tag) + """ + tag = curr_tag + title = f"{curr_tag}: DistinctionOS" + + sections: List[str] = [] + + # Intro + if handwritten: + sections.append(handwritten) + else: + sections.append( + f"Automatically generated changelog for **{image}:{curr_tag}**.\n\n" + f"Compared against previous build `{prev_tag}`.\n" + f"One package per new version shown." + ) + + # Pinned packages + sections.append(build_pinned_table(prev_pkgs, curr_pkgs)) + + # Commits + commits_md = get_commits(prev_manifest, curr_manifest, workdir) + if commits_md: + sections.append(commits_md) + + # Package diff + diff_md = build_package_diff(prev_pkgs, curr_pkgs) + if diff_md: + sections.append(diff_md) + + # Rebase instructions + sections.append( + "### How to rebase\n\n" + "For current users, run the following to rebase to this version:\n\n" + "```bash\n" + f"sudo bootc switch ghcr.io/{GITHUB_REPO}:latest\n" + f"# Or pin to this specific build:\n" + f"sudo bootc switch ghcr.io/{GITHUB_REPO}:{curr_tag}\n" + "```" + ) + + changelog_md = "\n\n".join(sections) + "\n" + return changelog_md, title, tag + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- def main(): parser = argparse.ArgumentParser( - description="Generate a simple changelog for a UBlue image" + description="Generate a Bazzite-style changelog for a single UBlue image" ) + parser.add_argument("image", help="Image name (e.g. distinctionos)") + parser.add_argument("env_output", help="Path to write TITLE/TAG env file") + parser.add_argument("changelog_output", help="Path to write changelog markdown") parser.add_argument( - 'image', - help='Image name (e.g. distinctionos)' + "--registry", + default=DEFAULT_REGISTRY, + help=f"Registry prefix (default: {DEFAULT_REGISTRY})", ) parser.add_argument( - 'output', - nargs='?', - default='-', - help='Path to output Markdown file (use \'-\' or omit to print to stdout)' + "--workdir", + default=None, + help="Git working directory for commit log generation", ) parser.add_argument( - '--registry', - default=DEFAULT_REGISTRY, - help=f"Registry prefix (default: {DEFAULT_REGISTRY})" + "--handwritten", + default="", + help="Optional handwritten message to replace the auto-generated intro", ) args = parser.parse_args() - registry, image, output = args.registry, args.image, args.output + registry = args.registry + image = args.image - # 1. Get tags and pick the latest two + # 1. Discover tags + print(f"Inspecting {registry}{image}...", file=sys.stderr) tags = get_all_tags(registry, image) - if args.output != "-": - print(f"Found tags: {tags}") + print(f"Found {len(tags)} tags", file=sys.stderr) prev_tag, curr_tag = select_two_latest(tags) + print(f"Diffing {prev_tag} -> {curr_tag}", file=sys.stderr) - # 2. Fetch manifests + # 2. Fetch manifests for both tags prev_manifest = run_skopeo_inspect(registry, image, prev_tag) curr_manifest = run_skopeo_inspect(registry, image, curr_tag) # 3. Extract package lists prev_pkgs = extract_packages(prev_manifest) curr_pkgs = extract_packages(curr_manifest) + print( + f"Packages: {len(prev_pkgs)} (prev) / {len(curr_pkgs)} (curr)", + file=sys.stderr, + ) - # 4. Build diff markdown - diff_md = compare_versions(prev_pkgs, curr_pkgs) + # 4. Generate changelog + changelog_md, title, tag = generate_changelog( + image=image, + prev_tag=prev_tag, + curr_tag=curr_tag, + prev_manifest=prev_manifest, + curr_manifest=curr_manifest, + prev_pkgs=prev_pkgs, + curr_pkgs=curr_pkgs, + workdir=args.workdir, + handwritten=args.handwritten.strip(), + ) - # 5. Assemble full changelog - header = f"# Changelog for {image}:{curr_tag}\n" - header += f"Based on previous release: {prev_tag}\n\n" - changelog = header + diff_md + "\n" + # 5. Write env file (sourced by GitHub Actions) + with open(args.env_output, "w") as f: + # Shell-safe quoting + f.write(f'TITLE="{title}"\n') + f.write(f"TAG={tag}\n") + print(f"Env written to {args.env_output}", file=sys.stderr) + + # 6. Write changelog + with open(args.changelog_output, "w") as f: + f.write(changelog_md) + print(f"Changelog written to {args.changelog_output}", file=sys.stderr) - # 6. Output - if output == '-': - sys.stdout.write(changelog) - else: - with open(output, 'w') as f: - f.write(changelog) - print(f"Changelog written to {output}") -if __name__ == '__main__': +if __name__ == "__main__": main() From 8279655d4181d4650ff1c2466a8f8b41c7b23461 Mon Sep 17 00:00:00 2001 From: phantom <137958098+phantomcortex@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:43:45 -0700 Subject: [PATCH 2/3] testing: release overhaul 02 --- .github/workflows/changelog.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/changelog.py b/.github/workflows/changelog.py index f9d8a26..0cdbf38 100755 --- a/.github/workflows/changelog.py +++ b/.github/workflows/changelog.py @@ -103,6 +103,30 @@ def extract_packages(info: Dict) -> Dict[str, str]: return {pkg: re.sub(FEDORA_PATTERN, "", ver) for pkg, ver in packages.items()} +def extract_fedora_version(info: Dict) -> str: + """Extract the Fedora version number from the image manifest. + + Tries the rechunk package list first (fedora-release), then falls back + to parsing the .fcNN suffix from any package version string. + """ + labels = info.get("Labels", {}) or {} + data = labels.get("dev.hhd.rechunk.info") + if data: + packages = json.loads(data).get("packages", {}) + # fedora-release version is just the Fedora number (e.g. "43") + fr = packages.get("fedora-release") + if fr: + m = re.match(r"(\d+)", fr) + if m: + return m.group(1) + # Fallback: scan any package for the .fcNN suffix + for ver in packages.values(): + m = re.search(r"\.fc(\d+)", ver) + if m: + return m.group(1) + return "" + + def get_version(packages: Dict[str, str], rpm_name: str) -> Optional[str]: """Look up a single package version, returning None if absent.""" return packages.get(rpm_name) @@ -277,7 +301,12 @@ def generate_changelog( (changelog_md, title, tag) """ tag = curr_tag - title = f"{curr_tag}: DistinctionOS" + # Extract Fedora version and date portion from tag for title + # Tag format is "latest.YYYYMMDD" or "YYYYMMDD" + fedora_ver = extract_fedora_version(curr_manifest) + date_part = curr_tag.split(".")[-1] if "." in curr_tag else curr_tag + fedora_label = f"F{fedora_ver}.{date_part}" if fedora_ver else date_part + title = f"DistinctionOS: latest {fedora_label}" sections: List[str] = [] From f47ea898fa848fb1a51d8519eb852d7d828c6cc1 Mon Sep 17 00:00:00 2001 From: phantom Date: Tue, 10 Feb 2026 12:47:01 -0700 Subject: [PATCH 3/3] Update Kernel package name in changelog.py --- .github/workflows/changelog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/changelog.py b/.github/workflows/changelog.py index 0cdbf38..c3a0c3f 100755 --- a/.github/workflows/changelog.py +++ b/.github/workflows/changelog.py @@ -34,10 +34,11 @@ # Major / pinned packages shown prominently at the top of every release. # Display Name -> RPM package name used as the lookup key in rechunk metadata. PINNED_PACKAGES: List[Tuple[str, str]] = [ - ("Kernel", "kernel"), + ("Kernel", "kernel-cachyos-lto"), ("GNOME", "gnome-control-center-filesystem"), ("Mesa", "mesa-filesystem"), ("Gamescope", "gamescope"), + ("Ptyxis", "ptyxis"), ] # Package names that appear in the pinned table are excluded from the