From 53e31ed2b99beefb85452e00b991c2d8a1ffeed0 Mon Sep 17 00:00:00 2001 From: Adam0 Date: Tue, 28 Jul 2026 22:24:00 -0400 Subject: [PATCH] Restore flake support and checks --- .github/workflows/checks.yml | 38 +++++++ .github/workflows/update-flake-lock.yml | 25 +++++ README.md | 37 +++++-- flake.lock | 131 ++++++++++++++++++++++++ flake.nix | 31 ++++++ flake/packages.nix | 10 ++ flake/treefmt.nix | 29 ++++++ shell.nix | 19 ++++ updater/discovery.py | 19 ++-- updater/models.py | 2 + updater/nix.py | 44 ++++++-- updater/package_backend.py | 56 ++++++---- 12 files changed, 395 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/checks.yml create mode 100644 .github/workflows/update-flake-lock.yml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 flake/packages.nix create mode 100644 flake/treefmt.nix create mode 100644 shell.nix diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..5693a13 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,38 @@ +# yamllint disable rule:document-start +name: CI +"on": + push: + branches: + - main + pull_request: + workflow_dispatch: +permissions: + contents: read +jobs: + ci: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + - name: Install Nix + uses: cachix/install-nix-action@v31 + with: + # yamllint disable rule:line-length + extra_nix_config: | + experimental-features = nix-command flakes + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + substituters = https://cache.nixos.org https://adam01110-nur.cachix.org/ + trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= adam01110-nur.cachix.org-1:43B8awTREG19aQ20luDD9BkxijKG/Q7hf8voMzS1X9I= + # yamllint enable rule:line-length + - name: Restore and save Nix store + uses: nix-community/cache-nix-action@v7 + with: + primary-key: >- + nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} + restore-prefixes-first-match: | + nix-${{ runner.os }}- + gc-max-store-size-linux: 2G + - name: Run checks + env: + NIXPKGS_ALLOW_UNFREE: "1" + run: nix flake check --impure diff --git a/.github/workflows/update-flake-lock.yml b/.github/workflows/update-flake-lock.yml new file mode 100644 index 0000000..79dd563 --- /dev/null +++ b/.github/workflows/update-flake-lock.yml @@ -0,0 +1,25 @@ +# yamllint disable rule:document-start +name: update-flake-lock +'on': + workflow_dispatch: + schedule: + - cron: "0 0 * * 5" +permissions: + contents: write + pull-requests: write +jobs: + lockfile: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v7 + - name: Install Nix + uses: cachix/install-nix-action@v31 + with: + nix_path: nixpkgs=channel:nixos-unstable + - name: Update flake.lock + uses: DeterminateSystems/update-flake-lock@v28 + with: + sign-commits: true + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }} diff --git a/README.md b/README.md index a214fd7..b71d277 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,13 @@ Personal Nix packages, modules, and plugin builds that I want available outside my main config. + [![Checks](https://img.shields.io/github/actions/workflow/status/adam01110/nur/checks.yml?branch=main&style=flat-square&label=Checks&labelColor=504945&color=cc241d)](https://github.com/adam01110/nur/actions/workflows/checks.yml) [![Build](https://img.shields.io/github/actions/workflow/status/adam01110/nur/build.yml?branch=main&style=flat-square&label=Build&labelColor=504945&color=cc241d)](https://github.com/adam01110/nur/actions/workflows/build.yml) [![Repo Size](https://img.shields.io/github/repo-size/adam01110/nur?style=flat-square&label=repo%20size&labelColor=504945&color=3c3836)](https://github.com/adam01110/nur) [![Cachix](https://img.shields.io/badge/cachix-adam01110--nur-689d6a?style=flat-square&labelColor=504945&color=689d6a)](https://adam01110-nur.cachix.org)
[![NUR](https://img.shields.io/badge/NUR-adam0-458588?style=flat-square&labelColor=504945&color=458588)](https://github.com/nix-community/NUR) + [![Flakes](https://img.shields.io/badge/Nix-flakes-b16286?style=flat-square&labelColor=504945&color=b16286)](https://nixos.wiki/wiki/Flakes) [Usage](#usage) - [Maintenance](#maintenance) - [Layout](#layout) @@ -18,36 +20,53 @@ This is my small NUR repo for packages that either are not in nixpkgs, need chan ## Usage -Use the repository through NUR: +Add the repo as a flake input: ```nix -{pkgs, ...}: { +{ + inputs.adam0-nur.url = "github:adam01110/nur"; +} +``` + +Then use packages from the flake output for your system: + +```nix +{ + inputs, + pkgs, + ... +}: let + nurPkgs = inputs.adam0-nur.packages.${pkgs.stdenv.hostPlatform.system}; +in { home.packages = [ - pkgs.nur.repos.adam0.gruvbox-plus-icons - pkgs.nur.repos.adam0.tg + nurPkgs.gruvbox-plus-icons + nurPkgs.bibata-modern-cursors-gruvbox-dark ]; } ``` -It can also be imported directly: +For classic NUR usage, import it like any other NUR repository: ```nix -import (builtins.fetchTarball "https://github.com/adam01110/nur/archive/main.tar.gz") { - inherit pkgs; -} +import inputs.adam0-nur { inherit pkgs; } ``` ## Maintenance +This repo is intentionally automated because I do not want package bumps to become fulltime job. + - `build.yml` evaluates and builds cacheable outputs against unstable and stable nixpkgs channels. - `update-packages.yml` runs `python3 -m updater` and opens a signed pull request when package versions move. +- `update-flake-lock.yml` refreshes `flake.lock` weekly. +- `treefmt-nix` keeps formatting consistent with the rest of my Nix repos. ## Layout | Path | Contents | | --- | --- | | `pkgs/` | Package definitions and grouped package sets | -| `hm-modules/` | Home Manager modules | +| `hm-modules/` | Home Manager modules exported by the flake | +| `flake/` | Flake parts for packages, formatting, and dev shell wiring | | `updater/` | Python package updater used by CI | | `ci.nix` | Build/cache selection for CI | | `default.nix` | Classic NUR export surface | diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..b328320 --- /dev/null +++ b/flake.lock @@ -0,0 +1,131 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1782949081, + "narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "import-tree": { + "locked": { + "lastModified": 1784254960, + "narHash": "sha256-iI88R3wHz8wTKQb5orvpc51L/Xr64AJyxid/0MKa/b8=", + "owner": "vic", + "repo": "import-tree", + "rev": "4ebb10ae17d5f1ad366e7aef5b92cb8eecf24f69", + "type": "github" + }, + "original": { + "owner": "vic", + "repo": "import-tree", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1785141334, + "narHash": "sha256-kh35kIx7el4Jk8Ki3BH9/Pn1eZYSYLJ6LMALos0zOy0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "38a4887411571457d700c51c64a6e49ead2ed5ab", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1782614948, + "narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-parts": "flake-parts", + "import-tree": "import-tree", + "nixpkgs": "nixpkgs", + "systems": "systems", + "treefmt-nix": "treefmt-nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1784369104, + "narHash": "sha256-47cxbcZODibHv3rELFQ9vZly0vUNkND/atn/U7HLeb0=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "df3c0640565d04a0261253cdd89fce78ec50168a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..fe3621a --- /dev/null +++ b/flake.nix @@ -0,0 +1,31 @@ +{ + description = "My personal NUR repository"; + + inputs = { + # keep-sorted start block=yes newline_separated=yes + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + + flake-parts.url = "github:hercules-ci/flake-parts"; + + import-tree.url = "github:vic/import-tree"; + + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + systems.url = "github:nix-systems/default"; + + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # keep-sorted end + }; + + outputs = inputs: + inputs.flake-parts.lib.mkFlake {inherit inputs;} { + systems = import inputs.systems; + imports = [(inputs.import-tree ./flake)]; + }; +} diff --git a/flake/packages.nix b/flake/packages.nix new file mode 100644 index 0000000..e3a6b57 --- /dev/null +++ b/flake/packages.nix @@ -0,0 +1,10 @@ +{ + perSystem = {pkgs, ...}: let + inherit (pkgs) lib; + legacy = import ../default.nix {inherit pkgs;}; + packages = lib.filterAttrs (_: lib.isDerivation) legacy; + in { + legacyPackages = legacy; + inherit packages; + }; +} diff --git a/flake/treefmt.nix b/flake/treefmt.nix new file mode 100644 index 0000000..e1a5082 --- /dev/null +++ b/flake/treefmt.nix @@ -0,0 +1,29 @@ +{inputs, ...}: { + imports = [inputs.treefmt-nix.flakeModule]; + + perSystem = _: { + treefmt.programs = { + # keep-sorted start + alejandra.enable = true; + deadnix.enable = true; + nixf-diagnose.enable = true; + statix.enable = true; + # keep-sorted end + + ruff-check = { + enable = true; + extendSelect = ["I"]; + }; + ruff-format.enable = true; + + rumdl-format.enable = true; + + # keep-sorted start + yamlfmt.enable = true; + yamllint.enable = true; + # keep-sorted end + + keep-sorted.enable = true; + }; + }; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..9cf1792 --- /dev/null +++ b/shell.nix @@ -0,0 +1,19 @@ +let + inherit + (builtins) + # keep-sorted start + fromJSON + readFile + # keep-sorted end + ; + + lock = fromJSON (readFile ./flake.lock); + nodeName = lock.nodes.root.inputs.flake-compat; + flakeCompat = fetchTarball { + url = + lock.nodes.${nodeName}.locked.url + or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.${nodeName}.locked.rev}.tar.gz"; + sha256 = lock.nodes.${nodeName}.locked.narHash; + }; +in + (import flakeCompat {src = ./.;}).shellNix diff --git a/updater/discovery.py b/updater/discovery.py index 36170cd..7ed512a 100644 --- a/updater/discovery.py +++ b/updater/discovery.py @@ -3,16 +3,18 @@ from __future__ import annotations import logging from collections.abc import Iterable -from .models import PackageRef -from .nix import attr_file_path, list_derivations, list_file_attrsets +from .models import PackageRef, SourceKind +from .nix import attr_file_path, flake_attrsets, list_derivations, list_file_attrsets logger = logging.getLogger(__name__) def discover_packages(system: str) -> list[PackageRef]: refs: list[PackageRef] = [] + for attrset in flake_attrsets(system): + refs.extend(_refs_for_attrset("flake", attrset)) for attrset in list_file_attrsets(): - refs.extend(_refs_for_attrset(attrset)) + refs.extend(_refs_for_attrset("file", attrset)) logger.info("discovered %d package(s) for %s", len(refs), system) return refs @@ -24,20 +26,21 @@ def filter_packages(refs: Iterable[PackageRef], selected: list[str]) -> list[Pac return [ref for ref in refs if ref.attr_path in wanted or ref.attr in wanted] -def _refs_for_attrset(attrset: str) -> list[PackageRef]: +def _refs_for_attrset(source_kind: SourceKind, attrset: str) -> list[PackageRef]: refs: list[PackageRef] = [] - attrs = list_derivations(attrset) - logger.info("found %d derivation(s) in %s", len(attrs), attrset or "default.nix") + attrs = list_derivations(source_kind, attrset) + logger.info("found %d derivation(s) in %s", len(attrs), attrset) for attr in attrs: - file_path = attr_file_path(attrset, attr) + file_path = attr_file_path(source_kind, attrset, attr) if not file_path: logger.info("skipping %s.%s: no source file", attrset, attr) continue refs.append( PackageRef( + source_kind=source_kind, attrset=attrset, attr=attr, - attr_path=f"{attrset}.{attr}" if attrset else attr, + attr_path=f"{attrset}.{attr}", file_path=file_path, ) ) diff --git a/updater/models.py b/updater/models.py index 3e0cee1..dc5fedc 100644 --- a/updater/models.py +++ b/updater/models.py @@ -5,11 +5,13 @@ from pathlib import Path from typing import Literal VersionMode = Literal["stable", "branch"] +SourceKind = Literal["flake", "file"] ResultStatus = Literal["updated", "skipped", "invalid", "failed"] @dataclass(frozen=True) class PackageRef: + source_kind: SourceKind attrset: str attr: str attr_path: str diff --git a/updater/nix.py b/updater/nix.py index 7f2b9b4..c5c17c0 100644 --- a/updater/nix.py +++ b/updater/nix.py @@ -1,25 +1,34 @@ from __future__ import annotations import json +import re from pathlib import Path -from .models import PackageState +from .models import PackageState, SourceKind from .process import ROOT, run from .versions import version_mode -def nix_eval_attrset(attrset: str, apply_expr: str) -> str: - command = ["nix", "eval", "--raw", "--file", "default.nix"] - if attrset: - command.append(attrset) +def nix_eval_attrset(source_kind: SourceKind, attrset: str, apply_expr: str) -> str: + if source_kind == "flake": + command = ["nix", "eval", "--raw", f".#{attrset}", "--apply", apply_expr] else: - apply_expr = f"f: ({apply_expr}) (f {{}})" - command.extend(["--apply", apply_expr]) + command = [ + "nix", + "eval", + "--raw", + "--file", + "default.nix", + attrset, + "--apply", + apply_expr, + ] return run(command).stdout -def list_derivations(attrset: str) -> list[str]: +def list_derivations(source_kind: SourceKind, attrset: str) -> list[str]: output = nix_eval_attrset( + source_kind, attrset, r""" pkgs: @@ -60,12 +69,19 @@ in """, ] ).stdout - return [""] + [line for line in output.splitlines() if line] + return [line for line in output.splitlines() if line] + +def flake_attrsets(system: str) -> list[str]: + attrset = f"packages.{system}" + result = run(["nix", "eval", "--raw", f".#{attrset}", "--apply", 'pkgs: ""'], check=False) + return [attrset] if result.returncode == 0 else [] -def read_state(attrset: str, attr: str) -> PackageState: + +def read_state(source_kind: SourceKind, attrset: str, attr: str) -> PackageState: escaped = _escape(attr) output = nix_eval_attrset( + source_kind, attrset, f''' pkgs: @@ -93,9 +109,10 @@ pkgs: ) -def attr_file_path(attrset: str, attr: str) -> Path | None: +def attr_file_path(source_kind: SourceKind, attrset: str, attr: str) -> Path | None: escaped = _escape(attr) position = nix_eval_attrset( + source_kind, attrset, f''' pkgs: @@ -107,6 +124,11 @@ pkgs: return None file_name = position.split(":", 1)[0] + if source_kind == "flake": + match = re.match(r"^/nix/store/[^/]+-source(/.*)$", file_name) + if match: + file_name = str(ROOT) + match.group(1) + file_path = Path(file_name) if not file_path.is_relative_to(ROOT) or not file_path.is_file(): return None diff --git a/updater/package_backend.py b/updater/package_backend.py index da411e2..ade9e9e 100644 --- a/updater/package_backend.py +++ b/updater/package_backend.py @@ -30,7 +30,7 @@ def update_package( return UpdateResult(ref.attr_path, "skipped", f"manifest updater owns {manifest}") owned_roots = package_owned_roots(ref.file_path) - before = read_state(ref.attrset, ref.attr) + before = read_state(ref.source_kind, ref.attrset, ref.attr) with FileTransaction(owned_roots) as transaction: try: @@ -40,12 +40,12 @@ def update_package( logger.info("nix-update failed for %s:\n%s", ref.attr_path, error.details) return UpdateResult(ref.attr_path, "skipped", f"nix-update failed: {error}") - after = read_state(ref.attrset, ref.attr) + after = read_state(ref.source_kind, ref.attrset, ref.attr) changed = transaction.new_changed_files() owned_changed, unrelated = paths_owned_by(changed, owned_roots) release_prefix = latest_release_prefix_for_url(after.src_url) if _preserve_unproven_branch_prefix(ref.file_path, before, after, release_prefix): - after = read_state(ref.attrset, ref.attr) + after = read_state(ref.source_kind, ref.attrset, ref.attr) changed = transaction.new_changed_files() owned_changed, unrelated = paths_owned_by(changed, owned_roots) validation = validate_transition( @@ -144,16 +144,28 @@ def _rejected_status(reason: str) -> ResultStatus: def _run_nix_update(ref: PackageRef, version_mode: str, *, timeout: str | None) -> None: - command = [ - "nix", - "run", - "nixpkgs#nix-update", - "--", - "-f", - "default.nix", - f"--version={version_mode}", - ref.attr_path, - ] + if ref.source_kind == "flake": + command = [ + "nix", + "run", + "nixpkgs#nix-update", + "--", + "--flake", + "--use-github-releases", + f"--version={version_mode}", + ref.attr_path, + ] + else: + command = [ + "nix", + "run", + "nixpkgs#nix-update", + "--", + "-f", + "default.nix", + f"--version={version_mode}", + ref.attr_path, + ] run(command, timeout=timeout) @@ -204,8 +216,16 @@ def _last_got_hash(ref: PackageRef, *, timeout: str | None) -> str: def _build_with_fake_hash( ref: PackageRef, *, timeout: str | None ) -> subprocess.CompletedProcess[str]: - return run( - ["nix-build", "-A", ref.attr_path, "--no-out-link"], - timeout=timeout, - check=False, - ) + if ref.source_kind == "flake": + result = run( + ["nix", "build", f".#{ref.attr_path}", "--no-link"], + timeout=timeout, + check=False, + ) + else: + result = run( + ["nix-build", "-A", ref.attr_path, "--no-out-link"], + timeout=timeout, + check=False, + ) + return result -- 2.51.2