diff --git a/systems/x86_64-linux/nas/apps/options.nix b/systems/x86_64-linux/nas/apps/options.nix deleted file mode 100644 index 9cbdbda..0000000 --- a/systems/x86_64-linux/nas/apps/options.nix +++ /dev/null @@ -1,51 +0,0 @@ -{ lib, ... }: -let - inherit (lib) types mkOption; -in -{ - options.nas-apps = mkOption { - type = types.attrsOf ( - types.submodule ( - { ... }: - { - options = { - enable = mkOption { - type = types.bool; - default = false; - }; - - port = mkOption { - type = types.int; - default = 80; - }; - - localAddress = mkOption { - type = types.str; - default = "127.0.0.1"; - }; - - dataDir = mkOption { - type = types.str; - default = ""; - }; - - reverseProxy = { - enable = mkOption { - type = types.bool; - default = false; - }; - host = mkOption { - type = types.str; - default = ""; - }; - middlewares = mkOption { - type = with types; listOf str; - default = [ ]; - }; - }; - }; - } - ) - ); - }; -} diff --git a/systems/x86_64-linux/nas/sensors.txt b/systems/x86_64-linux/nas/sensors.txt deleted file mode 100755 index a4ce84b..0000000 --- a/systems/x86_64-linux/nas/sensors.txt +++ /dev/null @@ -1,36 +0,0 @@ -Common Settings: -INTERVAL=10 - -Settings of hwmon6/pwm5: -- chipset? - Depends on hwmon6/temp9_input - Controls hwmon6/fan5_input - MINTEMP=20 - MAXTEMP=60 - MINSTART=16 - MINSTOP=14 - MINPWM=14 - -Settings of hwmon6/pwm4: -- case? - Depends on hwmon2/temp1_input - Controls hwmon6/fan4_input - MINTEMP=20 - MAXTEMP=90 - MINSTART=60 - MINSTOP=45 - -Settings of hwmon6/pwm3: -- cpu? - Depends on hwmon2/temp1_input - Controls hwmon6/fan3_input - MINTEMP=20 - MAXTEMP=90 - MINSTART=150 - MINSTOP=0 - MAXPWM=30 - -Settings of hwmon6/pwm2: -- cpu? - Depends on hwmon2/temp1_input - Controls hwmon6/fan2_input - MINTEMP=20 - MAXTEMP=90 - MINSTART=105 - MINSTOP=0 diff --git a/update-check.py b/update-check.py deleted file mode 100755 index 6bd907e..0000000 --- a/update-check.py +++ /dev/null @@ -1,551 +0,0 @@ -#!/usr/bin/env python3 -""" -Automatic Nix package update checker -Auto-discovers and checks GitHub-based Nix packages for updates -""" - -import re -import json -import argparse -import requests -import subprocess -import shutil -from pathlib import Path -from typing import Dict, List, Optional, Tuple, NamedTuple -from dataclasses import dataclass -import sys - -class PackageInfo(NamedTuple): - owner: str - repo: str - version: str - rev: str - current_hash: str - package_name: str - file_path: Path - -@dataclass -class UpdateResult: - name: str - current_version: str - latest_version: str - has_update: bool - file_path: Path - repo_url: str - current_hash: Optional[str] = None - new_hash: Optional[str] = None - error: Optional[str] = None - -class NixPackageChecker: - def __init__(self, search_paths: List[str] = None, max_depth: int = 3): - self.search_paths = search_paths or ["."] - self.max_depth = max_depth - self.session = requests.Session() - self.session.headers.update({'User-Agent': 'nix-package-checker'}) - - def find_nix_packages(self) -> List[Path]: - """Auto-discover Nix package files with fetchFromGitHub""" - packages = [] - - for search_path in self.search_paths: - base_path = Path(search_path) - if not base_path.exists(): - continue - - # Find .nix files up to max_depth - for depth in range(self.max_depth + 1): - pattern = "**/" * depth + "*.nix" - for nix_file in base_path.glob(pattern): - if self._is_github_package(nix_file): - packages.append(nix_file) - - return sorted(set(packages)) - - def _is_github_package(self, nix_file: Path) -> bool: - """Check if a .nix file contains fetchFromGitHub""" - try: - content = nix_file.read_text(encoding='utf-8') - return 'fetchFromGitHub' in content and any( - pattern in content for pattern in ['owner =', 'repo =', 'version ='] - ) - except (UnicodeDecodeError, PermissionError): - return False - - def compare_versions(self, current: str, latest: str) -> bool: - """Compare versions, return True if latest is newer""" - if current == latest: - return False - - # Handle HACS-X format - hacs_current = re.match(r'HACS-(\d+)', current) - hacs_latest = re.match(r'HACS-(\d+)', latest) - if hacs_current and hacs_latest: - return int(hacs_latest.group(1)) > int(hacs_current.group(1)) - - # Handle semantic versioning vX.Y.Z - sem_current = re.match(r'v?(\d+)\.(\d+)\.(\d+)', current) - sem_latest = re.match(r'v?(\d+)\.(\d+)\.(\d+)', latest) - if sem_current and sem_latest: - curr_parts = tuple(map(int, sem_current.groups())) - lat_parts = tuple(map(int, sem_latest.groups())) - return lat_parts > curr_parts - - # Fallback to string comparison - return latest > current - - def parse_nix_file(self, nix_file: Path) -> Optional[PackageInfo]: - """Extract package information from a .nix file""" - try: - content = nix_file.read_text(encoding='utf-8') - except (UnicodeDecodeError, PermissionError) as e: - print(f"❌ Error reading {nix_file}: {e}") - return None - - # Patterns to extract fields - patterns = { - 'owner': r'owner\s*=\s*"([^"]+)"', - 'repo': r'repo\s*=\s*"([^"]+)"', - 'version': r'version\s*=\s*"([^"]+)"', - 'rev': r'rev\s*=\s*(?:"([^"]+)"|([^;"\s]+))', - 'hash': r'hash\s*=\s*"([^"]+)"', - # Package name patterns (in order of preference) - 'domain': r'domain\s*=\s*"([^"]+)"', # Home Assistant components - 'pname': r'pname\s*=\s*"([^"]+)"', # Standard Nix convention - 'name': r'name\s*=\s*"([^"]+)"' # Older convention - } - - extracted = {} - for field, pattern in patterns.items(): - match = re.search(pattern, content) - if match: - if field == 'rev': - # Handle both quoted and unquoted rev values - extracted[field] = match.group(1) or match.group(2) - else: - extracted[field] = match.group(1) - - # Validate required fields - required = ['owner', 'repo', 'version'] - if not all(field in extracted for field in required): - missing = [f for f in required if f not in extracted] - print(f"⚠️ {nix_file.name}: Missing fields: {missing}") - return None - - # Handle rev = version case - rev = extracted.get('rev', extracted['version']) - if rev == 'version': - rev = extracted['version'] - - # Extract current hash (may not exist for all packages) - current_hash = extracted.get('hash', '') - - # Determine package name (priority: domain > pname > name > repo > directory) - package_name = None - for name_field in ['domain', 'pname', 'name']: - if name_field in extracted: - package_name = extracted[name_field] - break - - if not package_name: - # Fall back to repo name - package_name = extracted['repo'] - - # If still no name and it's in a subdirectory, use directory name - if not package_name or package_name == extracted['repo']: - parent_dir = nix_file.parent.name - if parent_dir != '.' and parent_dir != nix_file.parent.parent.name: - package_name = f"{parent_dir}-{extracted['repo']}" if package_name == extracted['repo'] else parent_dir - - return PackageInfo( - owner=extracted['owner'], - repo=extracted['repo'], - version=extracted['version'], - rev=rev, - current_hash=current_hash, - package_name=package_name, - file_path=nix_file - ) - - def get_latest_release(self, owner: str, repo: str) -> Optional[str]: - """Get latest GitHub release tag""" - url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest" - - try: - response = self.session.get(url, timeout=10) - if response.status_code == 200: - return response.json().get('tag_name') - elif response.status_code == 404: - # Try getting tags if no releases - return self._get_latest_tag(owner, repo) - else: - print(f"⚠️ API error for {owner}/{repo}: {response.status_code}") - return None - except requests.RequestException as e: - print(f"⚠️ Network error for {owner}/{repo}: {e}") - return None - - def _get_latest_tag(self, owner: str, repo: str) -> Optional[str]: - """Fallback to get latest tag if no releases""" - url = f"https://api.github.com/repos/{owner}/{repo}/tags" - - try: - response = self.session.get(url, timeout=10) - if response.status_code == 200: - tags = response.json() - return tags[0]['name'] if tags else None - except requests.RequestException: - pass - return None - - def get_github_hash(self, owner: str, repo: str, rev: str) -> Optional[str]: - """Get hash for GitHub source using nix-prefetch-url or nix-prefetch-github""" - # Try nix-prefetch-url first (more commonly available) - if shutil.which('nix-prefetch-url'): - return self._get_hash_with_prefetch_url(owner, repo, rev) - # Fall back to nix-prefetch-github - elif shutil.which('nix-prefetch-github'): - return self._get_hash_with_prefetch_github(owner, repo, rev) - else: - print("⚠️ Neither nix-prefetch-url nor nix-prefetch-github found.") - print(" nix-prefetch-url is included in nix by default") - print(" nix-prefetch-github: nix-env -iA nixpkgs.nix-prefetch-github") - return None - - def _get_hash_with_prefetch_url(self, owner: str, repo: str, rev: str) -> Optional[str]: - """Get hash using nix-prefetch-url with GitHub archive URL""" - # GitHub archive URL format - url = f"https://github.com/{owner}/{repo}/archive/{rev}.tar.gz" - - try: - # Use --unpack to match fetchFromGitHub behavior - cmd = ['nix-prefetch-url', '--unpack', url] - result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) - - if result.returncode == 0: - # nix-prefetch-url outputs the hash directly (sha256) - hash_value = result.stdout.strip() - return hash_value - else: - print(f"⚠️ nix-prefetch-url failed for {owner}/{repo}@{rev}:") - print(f" URL: {url}") - print(f" Error: {result.stderr.strip()}") - return None - - except subprocess.TimeoutExpired: - print(f"⚠️ Timeout fetching hash for {owner}/{repo}@{rev} (60s limit)") - return None - except subprocess.SubprocessError as e: - print(f"⚠️ Error with nix-prefetch-url for {owner}/{repo}@{rev}: {e}") - return None - - def _get_hash_with_prefetch_github(self, owner: str, repo: str, rev: str) -> Optional[str]: - """Get hash using nix-prefetch-github (fallback method)""" - try: - cmd = ['nix-prefetch-github', owner, repo, '--rev', rev] - result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) - - if result.returncode == 0: - # Parse JSON output to get sha256 - data = json.loads(result.stdout) - return data.get('sha256') - else: - print(f"⚠️ nix-prefetch-github failed for {owner}/{repo}@{rev}:") - print(f" {result.stderr.strip()}") - return None - - except subprocess.TimeoutExpired: - print(f"⚠️ Timeout fetching hash for {owner}/{repo}@{rev}") - return None - except (subprocess.SubprocessError, json.JSONDecodeError) as e: - print(f"⚠️ Error with nix-prefetch-github for {owner}/{repo}@{rev}: {e}") - return None - - def update_nix_file(self, pkg_info: PackageInfo, new_version: str, new_hash: str) -> bool: - """Update a .nix file with new version and hash""" - try: - content = pkg_info.file_path.read_text(encoding='utf-8') - - # Create backup - backup_path = pkg_info.file_path.with_suffix('.nix.backup') - backup_path.write_text(content, encoding='utf-8') - - # Update version - content = re.sub( - r'(version\s*=\s*)"[^"]+";', - f'\\1"{new_version}";', - content - ) - - # Update hash - if pkg_info.current_hash: - content = re.sub( - r'(hash\s*=\s*)"[^"]+";', - f'\\1"sha256-{new_hash}";', - content - ) - - # Write updated content - pkg_info.file_path.write_text(content, encoding='utf-8') - return True - - except Exception as e: - print(f"❌ Error updating {pkg_info.file_path}: {e}") - return False - """Compare versions, return True if latest is newer""" - if current == latest: - return False - - # Handle HACS-X format - hacs_current = re.match(r'HACS-(\d+)', current) - hacs_latest = re.match(r'HACS-(\d+)', latest) - if hacs_current and hacs_latest: - return int(hacs_latest.group(1)) > int(hacs_current.group(1)) - - # Handle semantic versioning vX.Y.Z - sem_current = re.match(r'v?(\d+)\.(\d+)\.(\d+)', current) - sem_latest = re.match(r'v?(\d+)\.(\d+)\.(\d+)', latest) - if sem_current and sem_latest: - curr_parts = tuple(map(int, sem_current.groups())) - lat_parts = tuple(map(int, sem_latest.groups())) - return lat_parts > curr_parts - - # Fallback to string comparison - return latest > current - - def check_package(self, pkg_info: PackageInfo, fetch_hash: bool = False) -> UpdateResult: - """Check a single package for updates""" - latest_version = self.get_latest_release(pkg_info.owner, pkg_info.repo) - - if latest_version is None: - return UpdateResult( - name=pkg_info.package_name, - current_version=pkg_info.version, - latest_version="unknown", - has_update=False, - file_path=pkg_info.file_path, - repo_url=f"https://github.com/{pkg_info.owner}/{pkg_info.repo}", - current_hash=pkg_info.current_hash, - error="Could not fetch latest release" - ) - - has_update = self.compare_versions(pkg_info.version, latest_version) - new_hash = None - - # Fetch new hash if update available and requested - if has_update and fetch_hash: - print(f" 🔄 Fetching hash for {pkg_info.package_name} ({pkg_info.owner}/{pkg_info.repo}@{latest_version})...") - new_hash = self.get_github_hash(pkg_info.owner, pkg_info.repo, latest_version) - - return UpdateResult( - name=pkg_info.package_name, - current_version=pkg_info.version, - latest_version=latest_version, - has_update=has_update, - file_path=pkg_info.file_path, - repo_url=f"https://github.com/{pkg_info.owner}/{pkg_info.repo}", - current_hash=pkg_info.current_hash, - new_hash=new_hash - ) - - def check_all_packages(self, fetch_hash: bool = False, auto_update: bool = False) -> List[UpdateResult]: - """Check all discovered packages""" - nix_files = self.find_nix_packages() - - if not nix_files: - print("No Nix package files found") - return [] - - print(f"Found {len(nix_files)} package files") - - # Show which hash fetching tool is available - if fetch_hash or auto_update: - if shutil.which('nix-prefetch-url'): - print("Hash fetching: using nix-prefetch-url") - elif shutil.which('nix-prefetch-github'): - print("Hash fetching: using nix-prefetch-github") - else: - print("⚠️ No hash fetching tool available") - - results = [] - - for nix_file in nix_files: - pkg_info = self.parse_nix_file(nix_file) - if pkg_info: - result = self.check_package(pkg_info, fetch_hash=fetch_hash) - results.append(result) - - # Auto-update if requested and update available - if auto_update and result.has_update and result.new_hash: - print(f" 🔄 Auto-updating {result.name}...") - if self.update_nix_file(pkg_info, result.latest_version, result.new_hash): - print(f" ✅ Updated {result.file_path}") - else: - print(f" ❌ Failed to update {result.file_path}") - - return results - -def print_results(results: List[UpdateResult], show_all: bool = True, show_hashes: bool = False): - """Print results in a nice format""" - if not results: - return - - updates_available = [r for r in results if r.has_update] - - if show_all: - print(f"\n{'Package':<25} {'Current':<15} {'Latest':<15} {'Status'}") - print("-" * 70) - - for result in results: - if result.error: - status = f"❌ {result.error}" - elif result.has_update: - status = "🔄 Update available" - else: - status = "✅ Up to date" - - print(f"{result.name:<25} {result.current_version:<15} {result.latest_version:<15} {status}") - - # Show file path for default.nix files or when there might be confusion - if result.file_path.name == 'default.nix' or len([r for r in results if r.name == result.name]) > 1: - rel_path = result.file_path.relative_to(Path.cwd()) if result.file_path.is_absolute() else result.file_path - print(f"{'':>25} File: {rel_path}") - - # Show hash information if available and requested - if show_hashes and result.has_update and result.new_hash: - print(f"{'':>25} Current hash: {result.current_hash[:16]}..." if result.current_hash else "") - print(f"{'':>25} New hash: sha256-{result.new_hash[:16]}...") - - # Summary - print(f"\nSummary:") - print(f" Total packages: {len(results)}") - print(f" Updates available: {len(updates_available)}") - - if updates_available: - print(f"\nPackages with updates:") - for result in updates_available: - rel_path = result.file_path.relative_to(Path.cwd()) if result.file_path.is_absolute() else result.file_path - print(f" • {result.name}: {result.current_version} → {result.latest_version}") - print(f" File: {rel_path}") - print(f" Repo: {result.repo_url}") - if show_hashes and result.new_hash: - print(f" New hash: sha256-{result.new_hash}") - -def print_updates_only(results: List[UpdateResult], show_hashes: bool = False): - """Print only packages with updates""" - updates = [r for r in results if r.has_update] - - if not updates: - print("No updates available") - return - - print("Updates available:") - for result in updates: - rel_path = result.file_path.relative_to(Path.cwd()) if result.file_path.is_absolute() else result.file_path - print(f" {result.name}: {result.current_version} → {result.latest_version}") - print(f" File: {rel_path}") - if show_hashes and result.new_hash: - print(f" New hash: sha256-{result.new_hash}") - elif show_hashes: - print(f" Hash: (not fetched)") - -def output_json(results: List[UpdateResult]): - """Output results as JSON""" - data = {} - for result in results: - data[result.name] = { - "current_version": result.current_version, - "latest_version": result.latest_version, - "has_update": result.has_update, - "file_path": str(result.file_path), - "repo_url": result.repo_url, - "current_hash": result.current_hash, - "new_hash": result.new_hash, - "error": result.error - } - - print(json.dumps(data, indent=2)) - -def main(): - parser = argparse.ArgumentParser( - description="Automatically check Nix packages for GitHub updates", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Examples: - %(prog)s # Check all packages in current directory - %(prog)s --updates # Show only packages with updates - %(prog)s --fetch-hash # Also fetch new hashes for updates - %(prog)s --auto-update # Automatically update .nix files - %(prog)s --json # Output as JSON - %(prog)s --path ./packages # Check specific directory - %(prog)s --depth 5 # Search deeper directory levels - -Requirements: - For hash fetching: nix-prefetch-url (part of nix) or nix-prefetch-github - nix-prefetch-url is preferred and usually already available - """ - ) - - parser.add_argument('--updates', action='store_true', - help='Show only packages with updates available') - parser.add_argument('--fetch-hash', action='store_true', - help='Fetch new hashes for packages with updates (requires nix-prefetch-url or nix-prefetch-github)') - parser.add_argument('--auto-update', action='store_true', - help='Automatically update .nix files with new versions and hashes') - parser.add_argument('--json', action='store_true', - help='Output results as JSON') - parser.add_argument('--path', action='append', default=[], - help='Search path for .nix files (can be used multiple times)') - parser.add_argument('--depth', type=int, default=3, - help='Maximum directory depth to search (default: 3)') - parser.add_argument('--list', action='store_true', - help='List discovered package files without checking updates') - - args = parser.parse_args() - - # Auto-update implies fetch-hash - if args.auto_update: - args.fetch_hash = True - - # Use provided paths or default to current directory - search_paths = args.path if args.path else ["."] - - checker = NixPackageChecker(search_paths=search_paths, max_depth=args.depth) - - if args.list: - # Just list discovered files - nix_files = checker.find_nix_packages() - print(f"Discovered {len(nix_files)} package files:") - for nix_file in nix_files: - pkg_info = checker.parse_nix_file(nix_file) - if pkg_info: - rel_path = nix_file.relative_to(Path.cwd()) if nix_file.is_absolute() else nix_file - print(f" {pkg_info.package_name:<25} {pkg_info.owner}/{pkg_info.repo} ({pkg_info.version}) - {rel_path}") - else: - rel_path = nix_file.relative_to(Path.cwd()) if nix_file.is_absolute() else nix_file - print(f" {'(parse failed)':<25} - {rel_path}") - return - - # Check for updates - results = checker.check_all_packages( - fetch_hash=args.fetch_hash, - auto_update=args.auto_update - ) - - if not results: - print("No packages found to check") - return - - # Output results - if args.json: - output_json(results) - elif args.updates: - print_updates_only(results, show_hashes=args.fetch_hash) - else: - print_results(results, show_all=True, show_hashes=args.fetch_hash) - - # Set exit code based on updates available - updates_available = any(r.has_update for r in results) - sys.exit(1 if updates_available else 0) - -if __name__ == "__main__": - main() \ No newline at end of file