Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save claudenobs/e0e57dabc2d405de47c2784a1bdef83c to your computer and use it in GitHub Desktop.

Select an option

Save claudenobs/e0e57dabc2d405de47c2784a1bdef83c to your computer and use it in GitHub Desktop.
decluttering cachyos
#!/usr/bin/env python3
import re
import sys
import subprocess
import urllib.request
import yaml
from pathlib import Path
url = "https://githubusercontent.com"
cache_path = Path(__file__).parent / "netinstall.yaml"
# Group flags that are always included by default in CachyOS
BASE_GROUPS_HELP = {
"CachyOS required (hidden)",
"CachyOS Packages",
"CachyOS shell configuration",
"Base-devel + Common packages",
"CPU specific Microcode update packages",
}
def download_yaml():
"""Downloads the netinstall.yaml definition from CachyOS repository."""
if "--help" in sys.argv or "-h" in sys.argv:
print("(Downloading package list to show available groups...)", file=sys.stderr)
try:
with urllib.request.urlopen(url) as response:
content = response.read()
cache_path.write_bytes(content)
except Exception as e:
print(f"Error downloading package list: {e}", file=sys.stderr)
sys.exit(1)
def get_transitive_dependencies(packages):
"""
Uses the system's `pactree` utility to find all transitive dependencies
for the provided list of base packages.
"""
print("(Resolving transitive dependencies via pactree... This may take a moment)", file=sys.stderr)
all_deps = set(packages)
# Check if pactree is available on the host system
if subprocess.call(["type", "pactree"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) != 0:
print("Warning: 'pactree' utility not found. Please install 'pacman-contrib'.", file=sys.stderr)
print("Falling back to standard pacman dependency resolving...", file=sys.stderr)
return resolve_dependencies_fallback(packages)
for pkg in packages:
try:
# pactree -u output gives a unique list of dependencies, one per line
result = subprocess.run(
["pactree", "-u", pkg],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
check=True
)
for line in result.stdout.splitlines():
dep = line.strip()
if dep:
all_deps.add(dep)
except subprocess.CalledProcessError:
# Skip errors for virtual/meta packages or items not currently in synced repos
continue
return all_deps
def resolve_dependencies_fallback(packages):
"""Fallback method using standard `pacman -Si` queries if pactree is missing."""
all_deps = set(packages)
queue = list(packages)
processed = set()
while queue:
current = queue.pop(0)
if current in processed:
continue
processed.add(current)
try:
result = subprocess.run(
["pacman", "-Si", current],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True
)
if result.returncode != 0:
continue
# Extract the 'Depends On' section out of the pacman sync info
match = re.search(r"(?:Depends On|Depends)\s*:\s*(.*)", result.stdout)
if match:
dep_string = match.group(1)
# Parse out version identifiers like >=2.0, clean up spaces and None values
raw_deps = re.split(r'\s+', dep_string)
for rd in raw_deps:
rd = re.sub(r'[>=<].*', '', rd).strip()
if rd and rd != "None" and rd not in all_deps:
all_deps.add(rd)
queue.append(rd)
except Exception:
continue
return all_deps
# Check for explicit manual refresh or if data is missing
if "--refresh" in sys.argv or not cache_path.exists():
download_yaml()
try:
temp_data = yaml.safe_load(cache_path.read_text())
except Exception as e:
print(f"Error parsing local YAML cache: {e}", file=sys.stderr)
sys.exit(1)
optional = [
item for item in temp_data
if isinstance(item, dict) and "name" in item and item["name"] not in BASE_GROUPS_HELP
]
# Display help menu
if "--help" in sys.argv or "-h" in sys.argv:
print("Usage: get_names.py [OPTIONS] [--<group-flag> ...]")
print()
print("Prints a list of packages for different CachyOS desktop environments and optional extras.")
print()
print("The following base groups are always included:")
for name in sorted(BASE_GROUPS_HELP):
print(f" - {name}")
print()
print("Options:")
print(" -h, --help Show this help message and exit")
print(" --refresh Re-download the package list from GitHub (ignores cache)")
print(" --sort Sort the output package list alphabetically")
print(" --dependencies Include all recursive/transitive package dependencies")
print()
import textwrap
print("Optional package groups:")
for item in optional:
flag = re.sub(r"[^a-z0-9]+", "-", item["name"].lower()).strip("-")
desc = item.get("description", "No description available.")
prefix = f" --{flag:<40} "
indent = " " * len(prefix)
print(textwrap.fill(desc, width=90, initial_indent=prefix, subsequent_indent=indent))
sys.exit(0)
# Build list of active packages based on command line choices
target_packages = set()
def extract_pkg_names(pkg_list):
names = []
for pkg in pkg_list:
if isinstance(pkg, str):
names.append(pkg)
elif isinstance(pkg, dict) and "name" in pkg:
names.append(pkg["name"])
return names
for item in temp_data:
if not isinstance(item, dict) or "name" not in item:
continue
name = item["name"]
flag = f"--{re.sub(r'[^a-z0-9]+', '-', name.lower()).strip('-')}"
# Process if mandatory or selected by runtime argument flags
if name in BASE_GROUPS_HELP or flag in sys.argv:
extracted = extract_pkg_names(item.get("packages", []))
target_packages.update(extracted)
# Expand to all transitive dependencies if explicitly requested
if "--dependencies" in sys.argv:
final_packages = list(get_transitive_dependencies(target_packages))
else:
final_packages = list(target_packages)
# Apply alphabetic sort if specified
if "--sort" in sys.argv:
final_packages.sort()
# Output the package inventory
for package in final_packages:
print(package)
#!/usr/bin/env python3
import re
import sys
import urllib.request
import yaml
from pathlib import Path
url = "https://raw.githubusercontent.com/CachyOS/cachyos-calamares/cachyos-dev/src/modules/netinstall/netinstall.yaml"
cache_path = Path(__file__).parent / "netinstall.yaml"
if "--help" in sys.argv or "-h" in sys.argv:
import io
temp_data = None
if cache_path.exists():
temp_data = yaml.safe_load(cache_path.read_text())
else:
print("(Downloading package list to show available groups...)", file=sys.stderr)
with urllib.request.urlopen(url) as response:
content = response.read()
cache_path.write_bytes(content)
temp_data = yaml.safe_load(cache_path.read_text())
BASE_GROUPS_HELP = {
"CachyOS required (hidden)",
"CachyOS Packages",
"CachyOS shell configuration",
"Base-devel + Common packages",
"CPU specific Microcode update packages",
}
optional = [
item for item in temp_data
if isinstance(item, dict) and "name" in item and item["name"] not in BASE_GROUPS_HELP
]
print("Usage: get_names.py [OPTIONS] [--<group-flag> ...]")
print()
print("Prints a list of packages for different CachyOS desktop environments and optional extras.")
print()
print("The following base groups are always included:")
for name in sorted(BASE_GROUPS_HELP):
print(f" - {name}")
print()
print("Options:")
print(" -h, --help Show this help message and exit")
print(" --refresh Re-download the package list from GitHub (ignores cache)")
print(" --sort Sort the output package list alphabetically")
print()
import textwrap
print("Optional package groups:")
for item in optional:
flag = re.sub(r"[^a-z0-9]+", "-", item["name"].lower()).strip("-")
desc = item.get("description", "")
prefix = f" --{flag:<40} "
indent = " " * len(prefix)
wrapped = textwrap.fill(desc, width=120, initial_indent=prefix, subsequent_indent=indent)
print(wrapped)
sys.exit(0)
force_download = "--refresh" in sys.argv
if force_download or not cache_path.exists():
with urllib.request.urlopen(url) as response:
content = response.read()
cache_path.write_bytes(content)
print(f"Downloaded and cached to {cache_path}", file=sys.stderr)
else:
print(f"Using cached file: {cache_path}", file=sys.stderr)
data = yaml.safe_load(cache_path.read_text())
BASE_GROUPS = {
"CachyOS required (hidden)",
"CachyOS Packages",
"CachyOS shell configuration",
"Base-devel + Common packages",
"CPU specific Microcode update packages",
}
def collect_packages(item):
pkgs = list(item.get("packages") or [])
for sub in item.get("subgroups") or []:
pkgs.extend(collect_packages(sub))
return pkgs
def name_to_flag(name):
slug = name.lower()
slug = re.sub(r"[^a-z0-9]+", "-", slug)
return slug.strip("-")
optional_groups = {
name_to_flag(item["name"]): item
for item in data
if isinstance(item, dict) and "name" in item and item["name"] not in BASE_GROUPS
}
base_packages = []
for item in data:
if isinstance(item, dict) and item.get("name") in BASE_GROUPS:
base_packages.extend(collect_packages(item))
for flag, item in optional_groups.items():
if f"--{flag}" in sys.argv:
base_packages.extend(collect_packages(item))
packages = sorted(base_packages) if "--sort" in sys.argv else base_packages
for pkg in packages:
print(pkg)
#!/usr/bin/env python3
import re
import sys
import subprocess
import urllib.request
import yaml
from pathlib import Path
url = "https://githubusercontent.com"
cache_path = Path(__file__).parent / "netinstall.yaml"
# Group flags that are always included by default in CachyOS
BASE_GROUPS_HELP = {
"CachyOS required (hidden)",
"CachyOS Packages",
"CachyOS shell configuration",
"Base-devel + Common packages",
"CPU specific Microcode update packages",
}
def download_yaml():
"""Downloads the netinstall.yaml definition from CachyOS repository."""
if "--help" in sys.argv or "-h" in sys.argv:
print("(Downloading package list to show available groups...)", file=sys.stderr)
try:
with urllib.request.urlopen(url) as response:
content = response.read()
cache_path.write_bytes(content)
except Exception as e:
print(f"Error downloading package list: {e}", file=sys.stderr)
sys.exit(1)
def get_transitive_dependencies(packages):
"""
Uses the system's `pactree` utility to find all transitive dependencies
for the provided list of base packages.
"""
print("(Resolving transitive dependencies via pactree... This may take a moment)", file=sys.stderr)
all_deps = set(packages)
# Check if pactree is available on the host system
if subprocess.call(["type", "pactree"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) != 0:
print("Warning: 'pactree' utility not found. Please install 'pacman-contrib'.", file=sys.stderr)
print("Falling back to standard pacman dependency resolving...", file=sys.stderr)
return resolve_dependencies_fallback(packages)
for pkg in packages:
try:
# pactree -u output gives a unique list of dependencies, one per line
result = subprocess.run(
["pactree", "-u", pkg],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
check=True
)
for line in result.stdout.splitlines():
dep = line.strip()
if dep:
all_deps.add(dep)
except subprocess.CalledProcessError:
# Skip errors for virtual/meta packages or items not currently in synced repos
continue
return all_deps
def resolve_dependencies_fallback(packages):
"""Fallback method using standard `pacman -Si` queries if pactree is missing."""
all_deps = set(packages)
queue = list(packages)
processed = set()
while queue:
current = queue.pop(0)
if current in processed:
continue
processed.add(current)
try:
result = subprocess.run(
["pacman", "-Si", current],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True
)
if result.returncode != 0:
continue
# Extract the 'Depends On' section out of the pacman sync info
match = re.search(r"(?:Depends On|Depends)\s*:\s*(.*)", result.stdout)
if match:
dep_string = match.group(1)
# Parse out version identifiers like >=2.0, clean up spaces and None values
raw_deps = re.split(r'\s+', dep_string)
for rd in raw_deps:
rd = re.sub(r'[>=<].*', '', rd).strip()
if rd and rd != "None" and rd not in all_deps:
all_deps.add(rd)
queue.append(rd)
except Exception:
continue
return all_deps
def parse_extra_packages():
"""Parses extra packages supplied via --include or -i flags."""
extra_pkgs = set()
args = sys.argv
for flag in ("--include", "-i"):
if flag in args:
try:
idx = args.index(flag)
val = args[idx + 1]
# Split by commas and strip surrounding whitespace
for p in val.split(","):
p_cleaned = p.strip()
if p_cleaned:
extra_pkgs.add(p_cleaned)
except IndexError:
print(f"Error: {flag} requires a comma-separated list of packages.", file=sys.stderr)
sys.exit(1)
return extra_pkgs
# Check for explicit manual refresh or if data is missing
if "--refresh" in sys.argv or not cache_path.exists():
download_yaml()
try:
temp_data = yaml.safe_load(cache_path.read_text())
except Exception as e:
print(f"Error parsing local YAML cache: {e}", file=sys.stderr)
sys.exit(1)
optional = [
item for item in temp_data
if isinstance(item, dict) and "name" in item and item["name"] not in BASE_GROUPS_HELP
]
# Display help menu
if "--help" in sys.argv or "-h" in sys.argv:
print("Usage: get_names.py [OPTIONS] [--<group-flag> ...]")
print()
print("Prints a list of packages for different CachyOS desktop environments and optional extras.")
print()
print("The following base groups are always included:")
for name in sorted(BASE_GROUPS_HELP):
print(f" - {name}")
print()
print("Options:")
print(" -h, --help Show this help message and exit")
print(" --refresh Re-download the package list from GitHub (ignores cache)")
print(" --sort Sort the output package list alphabetically")
print(" --dependencies Include all recursive/transitive package dependencies")
print(" -i, --include <pkgs> Include extra comma-separated packages (e.g., -i gimp,vlc)")
print()
import textwrap
print("Optional package groups:")
for item in optional:
flag = re.sub(r"[^a-z0-9]+", "-", item["name"].lower()).strip("-")
desc = item.get("description", "No description available.")
prefix = f" --{flag:<40} "
indent = " " * len(prefix)
print(textwrap.fill(desc, width=90, initial_indent=prefix, subsequent_indent=indent))
sys.exit(0)
# Build list of active packages based on command line choices
target_packages = set()
def extract_pkg_names(pkg_list):
names = []
for pkg in pkg_list:
if isinstance(pkg, str):
names.append(pkg)
elif isinstance(pkg, dict) and "name" in pkg:
names.append(pkg["name"])
return names
for item in temp_data:
if not isinstance(item, dict) or "name" not in item:
continue
name = item["name"]
flag = f"--{re.sub(r'[^a-z0-9]+', '-', name.lower()).strip('-')}"
# Process if mandatory or selected by runtime argument flags
if name in BASE_GROUPS_HELP or flag in sys.argv:
extracted = extract_pkg_names(item.get("packages", []))
target_packages.update(extracted)
# Inject extra user-defined packages
extra_packages = parse_extra_packages()
target_packages.update(extra_packages)
# Expand to all transitive dependencies if explicitly requested
if "--dependencies" in sys.argv:
final_packages = list(get_transitive_dependencies(target_packages))
else:
final_packages = list(target_packages)
# Apply alphabetic sort if specified
if "--sort" in sys.argv:
final_packages.sort()
# Output the package inventory
for package in final_packages:
print(package)
#!/usr/bin/env python3
import re
import sys
import subprocess
import urllib.request
import yaml
from pathlib import Path
url = "https://githubusercontent.com"
cache_path = Path(__file__).parent / "netinstall.yaml"
# Group flags that are always included by default in CachyOS
BASE_GROUPS_HELP = {
"CachyOS required (hidden)",
"CachyOS Packages",
"CachyOS shell configuration",
"Base-devel + Common packages",
"CPU specific Microcode update packages",
}
def download_yaml():
"""Downloads the netinstall.yaml definition from CachyOS repository."""
if "--help" in sys.argv or "-h" in sys.argv:
print("(Downloading package list to show available groups...)", file=sys.stderr)
try:
with urllib.request.urlopen(url) as response:
content = response.read()
cache_path.write_bytes(content)
except Exception as e:
print(f"Error downloading package list: {e}", file=sys.stderr)
sys.exit(1)
def get_transitive_dependencies(packages):
"""
Uses the system's `pactree` utility to find all transitive dependencies
for the provided list of base packages.
"""
print("(Resolving transitive dependencies via pactree... This may take a moment)", file=sys.stderr)
all_deps = set(packages)
# Check if pactree is available on the host system
if subprocess.call(["type", "pactree"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) != 0:
print("Warning: 'pactree' utility not found. Please install 'pacman-contrib'.", file=sys.stderr)
print("Falling back to standard pacman dependency resolving...", file=sys.stderr)
return resolve_dependencies_fallback(packages)
for pkg in packages:
try:
# pactree -u output gives a unique list of dependencies, one per line
result = subprocess.run(
["pactree", "-u", pkg],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
check=True
)
for line in result.stdout.splitlines():
dep = line.strip()
if dep:
all_deps.add(dep)
except subprocess.CalledProcessError:
# Skip errors for virtual/meta packages or items not currently in synced repos
continue
return all_deps
def resolve_dependencies_fallback(packages):
"""Fallback method using standard `pacman -Si` queries if pactree is missing."""
all_deps = set(packages)
queue = list(packages)
processed = set()
while queue:
current = queue.pop(0)
if current in processed:
continue
processed.add(current)
try:
result = subprocess.run(
["pacman", "-Si", current],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True
)
if result.returncode != 0:
continue
# Extract the 'Depends On' section out of the pacman sync info
match = re.search(r"(?:Depends On|Depends)\s*:\s*(.*)", result.stdout)
if match:
dep_string = match.group(1)
# Parse out version identifiers like >=2.0, clean up spaces and None values
raw_deps = re.split(r'\s+', dep_string)
for rd in raw_deps:
rd = re.sub(r'[>=<].*', '', rd).strip()
if rd and rd != "None" and rd not in all_deps:
all_deps.add(rd)
queue.append(rd)
except Exception:
continue
return all_deps
def parse_extra_packages():
"""Parses extra packages supplied via --include or -i flags from raw strings or files."""
extra_pkgs = set()
args = sys.argv
for flag in ("--include", "-i"):
if flag in args:
try:
idx = args.index(flag)
val = args[idx + 1]
path_target = Path(val)
# If argument points to a valid file path, parse by whitespace/newlines
if path_target.is_file():
content = path_target.read_text()
# split() with no arguments matches any consecutive whitespace (newlines, tabs, spaces)
for p in content.split():
p_cleaned = p.strip()
if p_cleaned:
extra_pkgs.add(p_cleaned)
else:
# Otherwise, fallback to fallback comma-separated values line parser
for p in val.split(","):
p_cleaned = p.strip()
if p_cleaned:
extra_pkgs.add(p_cleaned)
except IndexError:
print(f"Error: {flag} requires a comma-separated list or a valid file path.", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error reading include resource: {e}", file=sys.stderr)
sys.exit(1)
return extra_pkgs
# Check for explicit manual refresh or if data is missing
if "--refresh" in sys.argv or not cache_path.exists():
download_yaml()
try:
temp_data = yaml.safe_load(cache_path.read_text())
except Exception as e:
print(f"Error parsing local YAML cache: {e}", file=sys.stderr)
sys.exit(1)
optional = [
item for item in temp_data
if isinstance(item, dict) and "name" in item and item["name"] not in BASE_GROUPS_HELP
]
# Display help menu
if "--help" in sys.argv or "-h" in sys.argv:
print("Usage: get_names.py [OPTIONS] [--<group-flag> ...]")
print()
print("Prints a list of packages for different CachyOS desktop environments and optional extras.")
print()
print("The following base groups are always included:")
for name in sorted(BASE_GROUPS_HELP):
print(f" - {name}")
print()
print("Options:")
print(" -h, --help Show this help message and exit")
print(" --refresh Re-download the package list from GitHub (ignores cache)")
print(" --sort Sort the output package list alphabetically")
print(" --dependencies Include all recursive/transitive package dependencies")
print(" -i, --include <src> Include extra items via comma-separated list or whitespace-separated file path")
print()
import textwrap
print("Optional package groups:")
for item in optional:
flag = re.sub(r"[^a-z0-9]+", "-", item["name"].lower()).strip("-")
desc = item.get("description", "No description available.")
prefix = f" --{flag:<40} "
indent = " " * len(prefix)
print(textwrap.fill(desc, width=90, initial_indent=prefix, subsequent_indent=indent))
sys.exit(0)
# Build list of active packages based on command line choices
target_packages = set()
def extract_pkg_names(pkg_list):
names = []
for pkg in pkg_list:
if isinstance(pkg, str):
names.append(pkg)
elif isinstance(pkg, dict) and "name" in pkg:
names.append(pkg["name"])
return names
for item in temp_data:
if not isinstance(item, dict) or "name" not in item:
continue
name = item["name"]
flag = f"--{re.sub(r'[^a-z0-9]+', '-', name.lower()).strip('-')}"
# Process if mandatory or selected by runtime argument flags
if name in BASE_GROUPS_HELP or flag in sys.argv:
extracted = extract_pkg_names(item.get("packages", []))
target_packages.update(extracted)
# Inject extra user-defined packages
extra_packages = parse_extra_packages()
target_packages.update(extra_packages)
# Expand to all transitive dependencies if explicitly requested
if "--dependencies" in sys.argv:
final_packages = list(get_transitive_dependencies(target_packages))
else:
final_packages = list(target_packages)
# Apply alphabetic sort if specified
if "--sort" in sys.argv:
final_packages.sort()
# Output the package inventory
for package in final_packages:
print(package)
@claudenobs

claudenobs commented Jul 31, 2026

Copy link
Copy Markdown
Author
  • 1st file : carbon copy from https://vilimpoc.org/blog/2026/20260320-cleaning-up-cachyos-host-system/
  • 2nd : after asking google ai-mode to add transitive dependencies in the output
  • 3rd : after asking it to add an includes for packages, listing it and all transitive packages
  • 4th : after asking it to alternatively read packages from a file (for easy copy and paste from /var/log/pacman.log; which incidentally contains a list of initial packages installed)

i'd suggest calling it as follows:

diff -y <(pacman -Q | awk '{print $1}') \
        <(./cachyos-list-default-packages.v4.py --kde-desktop --firefox-and-language-package --printing-support --sort --dependencies --include cachyos-includes.list \
          | sed -E 's/>?=.+//g') \
        | grep -E '[<|>]'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment