Omniflake: Một flake Nix duy nhất để quản lý tất cả

30 tháng 8, 2026·5 phút đọc

Omniflake là một dự án táo bạo gom gần 12.000 flake Nix vào một input duy nhất, giúp người dùng không phải khai báo và đồng bộ hàng tá input lẻ tẻ. Nhờ tính lười biếng (laziness) của ngôn ngữ Nix, bạn chỉ phải trả chi phí cho những gì thực sự sử dụng, và hiệu suất vẫn rất ấn tượng. Bài viết này sẽ giải thích cơ chế hoạt động, những thách thức kỹ thuật và cách dùng Omniflake trong thực tế.

Omniflake: Một flake Nix duy nhất để quản lý tất cả

Omniflake: Một flake Nix duy nhất để quản lý tất cả

Trong thế giới Nix, việc quản lý các flake input luôn là một điểm gây khó chịu. Bạn muốn dùng disko, phải thêm URL, rồi cấu hình follows để tránh kéo theo một bản nixpkgs riêng, và cứ thế lặp lại cho từng thư viện. Omniflake ra đời để giải quyết vấn đề này bằng cách gom gần như toàn bộ flakes trên GitHub vào một input duy nhất, mang lại sự đơn giản của tập trung hóa mà vẫn giữ được sức mạnh của phân tán.

Ý tưởng nghe có vẻ điên rồ, nhưng nó hoạt động. Nhờ vào tính lười biếng (laziness) của ngôn ngữ Nix và cơ chế flake.lock, bạn chỉ phải trả chi phí cho những gì thực sự sử dụng, trong khi vẫn có quyền truy cập vào hàng nghìn flakes khác một cách linh hoạt.

Vấn đề với cách quản lý input truyền thống

Flakes mang lại khả năng tái lập (reproducibility) và phân quyền (federation), nhưng việc thêm một input mới luôn là một chuỗi thao tác thủ công phiền toái. Bạn phải khai báo URL, cấu hình follows để tránh trùng lặp nixpkgs, và làm lại từ đầu cho input tiếp theo. Việc mỗi flake kéo theo một bản flake-utils riêng đã trở thành trò cười trong cộng đồng Nix.

Sự trùng lặp này không chỉ gây rối mắt mà còn làm phình to flake.lock, khiến quá trình cài đặt trở nên nặng nề hơn cần thiết.

Omniflake hoạt động như thế nào?

Thay vì khai báo từng flake như một input riêng biệt, bạn chỉ cần thêm một input duy nhất:

inputs.omniflake.url = "github:fzakaria/omniflake";
inputs.omniflake.inputs.nixpkgs.follows = "nixpkgs";

Sau đó, bạn có thể truy cập vào bất kỳ flake nào gần như là một phần của hệ thống. Ví dụ, để cài đặt một gói:

environment.systemPackages = [
  omniflake.flakes.nh.packages.${system}.default
];

Hoặc sử dụng overlay, NixOS module, hay thậm chí chạy trực tiếp từ dòng lệnh:

$ nix run 'github:fzakaria/omniflake#flakes.nh.packages.x86_64-linux.default' -- --version

Hiện tại, Omniflake đang gom gần 12.000 flakes từ GitHub, được cập nhật tự động. Bạn có thể xem danh sách đầy đủ tại omniflake.com.

Bí quyết: Tính lười biếng của Nix

Điều khiến Omniflake khả thi chính là tính lười biếng (laziness) của ngôn ngữ Nix. Một input không có output nào chạm tới sẽ không bao giờ được tải về hay đánh giá (evaluate). Điều này có thể được chứng minh bằng cách khóa một flake với hai input, rồi cố tình làm hỏng một mục trong flake.lock — flake vẫn đánh giá bình thường nếu không ai dùng đến input bị hỏng.

Thêm vào đó, khi bạn thêm một flake làm input, toàn bộ đồ thị phụ thuộc của nó chỉ được chép vào flake.lock như metadata. Không có gì được tải về hay đánh giá cho đến khi thực sự cần.

Vượt qua thách thức về hiệu suất

Ý tưởng đầu tiên là thêm tất cả flakes làm input trực tiếp vào flake.nix. Tuy nhiên, việc tạo flake.lock trở nên cực kỳ chậm do thuật toán đặt tên cho các input trùng lặp có độ phức tạp bình phương. Với 4.000 input, việc đánh giá phải xử lý khoảng 8 triệu phép định dạng chuỗi.

Tác giả đã phát hiện và sửa lỗi này trong Nix (PR #16387), giúp tốc độ tăng gấp 21 lần. Tuy nhiên, vấn đề cốt lõi vẫn còn: để hoàn tất flake.lock, Nix phải đánh giá từng input, và chỉ cần một input không khóa được là toàn bộ quá trình thất bại.

Giải pháp đột phá: Không dùng inputs

Bí quyết của Omniflake là không khai báo các flakes như inputs. Thay vào đó, nó chỉ khai báo 5 input nhỏ (gồm nixpkgs và một vài thư viện phụ trợ), đồng thời lưu một bảng các tham chiếu (JSONL) cho tất cả các flakes khác.

Mỗi dòng trong index.json là một object đã khóa, giống hệt những gì flake.lock lưu trữ:

{
  "disko": {
    "locked": {
      "narHash": "sha256-RxWs…",
      "owner": "nix-community",
      "repo": "disko",
      "rev": "ff8702b4…",
      "type": "github"
    }
  }
}

Khi bạn yêu cầu một flake cụ thể (omniflake.flakes.disko), một bộ nạp (loader) nhỏ sẽ mô phỏng chính xác những gì Nix làm với một flake.lock: tải về đúng commit, đọc flake.nix, import các input và gọi outputs. Nó hỗ trợ cả follows và mọi tính năng khác vì về cơ bản, nó đang đánh giá flake giống hệt cách Nix vẫn làm.

Điều này mang lại sự linh hoạt tối đa:

  • omniflake.flakes.disko — flakes đã được hợp nhất nixpkgs theo chính sách của Omniflake.
  • omniflake.pinned.disko — flakes giữ nguyên đúng như tác giả gốc đã khóa.
  • omniflake.lib.withOverrides { nixpkgs = nixpkgs-stable; } — cho phép bạn tự quyết định chính sách thay thế input.

Hiệu năng và kết luận

Việc thêm Omniflake vào dự án chỉ mất khoảng 1,5 giây để khóa, kế thừa chỉ 6 node và không tải về bất kỳ flake nào trong hàng ngàn flakes phía sau:

$ time nix flake lock
real    0m1.5s
$ nix eval github:fzakaria/omniflake#lib.count
11975

Omniflake có thể bị coi là "cursed" (nguyền rủa) trong mắt một số người, nhưng nó giải quyết triệt để một vấn đề thực tế mà mọi người dùng Nix đều gặp phải. Tác giả muốn có cả sự phân quyền của flakes lẫn sự đơn giản của tập trung hóa — và Omniflake chính là chiếc bánh mà anh ấy muốn ăn.

Với cộng đồng Nix tại Việt Nam, nếu bạn thường xuyên làm việc với nhiều flakes và cảm thấy mệt mỏi với việc quản lý input, Omniflake chắc chắn là một công cụ đáng để thử nghiệm.

Cấu trúc OmniflakeCấu trúc Omniflake

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗