আমার প্রথম NixOS Flake ছিল ৫০০ লাইনের এক বিরাট জগাখিচুড়ি। সবকিছু একটা ফাইলে—হার্ডওয়্যার কনফিগারেশন, ইউজার প্যাকেজ, এসএসএইচ সেটিংস আর জিটিকে থিম। কাজ করতো ঠিকই, কিন্তু ঝামেলা শুরু হলো যখন নতুন ল্যাপটপ কিনলাম। ডেস্কটপের কনফিগারেশন ল্যাপটপে কপি করার পর ৩ ঘণ্টা ধরে ডিবাগ করেছি কেন এনভিডিয়া সেটিংস ল্যাপটপে লোড করার চেষ্টা করছে। তখনই বুঝেছি, জাস্ট "ফাইল ভাগ করা" আসলে কোনো সমাধান নয়। এর জন্য প্রয়োজন একটা সুনির্দিষ্ট NixOS Module অর্গানাইজেশন।

আমি চেয়েছিলাম এমন একটা সেটআপ যেখানে প্রতিটা মডিউল বলবে "আমি এই জিনিসটা প্রোভাইড করি" আর হোস্টগুলো বলবে "আমার এই এই জিনিসগুলো দরকার"। মেশিনের মধ্যে যেন কোনো ভুল বোঝাবুঝি না হয়।

Flake-Parts আর অটো-ডিসকভারি প্যাটার্ন

আমি Flake-parts ব্যবহার শুরু করলাম যা মডিউলগুলোকে একটা ক্যাটালগ হিসেবে ট্রিট করতে সাহায্য করে। এতে flake.nix ফাইলটা দেখতে অনেকটা এরকম হয়:

{
  outputs = inputs@{ flake-parts, ... }:
    let
      inherit (inputs.nixpkgs.lib.fileset) toList fileFilter;
      import-tree = path:
        toList (fileFilter
          (file: file.hasExt "nix" && !(inputs.nixpkgs.lib.hasPrefix "_" file.name))
          path);
    in
    flake-parts.lib.mkFlake { inherit inputs; } {
      imports = import-tree ./modules;
    };
}

এখানে import-tree ফাংশনটা অটোমেটিকালি ./modules ফোল্ডার স্ক্যান করে সব .nix ফাইল খুঁজে নেয়। নতুন কোনো NixOS Module যোগ করার জন্য জাস্ট একটা ফাইল তৈরি করলেই হয়, ম্যানুয়ালি ইমপোর্ট লিস্টে নাম অ্যাড করার প্রয়োজন নেই।

মডিউল যেভাবে কাজ করে

প্রতিটা মডিউল নিজেকে একটা নির্দিষ্ট নেমস্পেসে রেজিস্টার করে। ধরুন, নিওভিম মডিউলের জন্য আমার কোডটা এরকম:

{ inputs, ... }:
{
  flake.modules.nixos.programs_neovim = { config, pkgs, ... }: {
    environment.systemPackages = [
      config.packages.neovim-nvf
    ];
    custom.persist.home.directories = [ ".local/share/nvim" ];
  };
}

এই মডিউলটা একা কিছুই করবে না। যতক্ষণ না কোনো হোস্ট এটাকে ইমপোর্ট করছে, ততক্ষণ এটা জাস্ট একটা অপশন হিসেবে ক্যাটালগে থাকবে।

হোস্টের কনফিগারেশন: যেন একটা রেস্টুরেন্ট মেনু

হোস্ট কনফিগারেশন ফাইলগুলো এখন জাস্ট একটা লিস্টের মতো। ডেস্কটপের জন্য আমার সেটিংস অনেকটা এরকম:

{ inputs, ... }@top:
{
  flake.modules.nixos.host_xenomorph = { config, ... }: {
    imports = with top.config.flake.modules.nixos; [
      gui
      wm
      hardware_nvidiagpu
      programs_neovim
      services_docker
    ];
  };
}

এক নজরেই বুঝতে পারছি এই মেশিনে কী কী আছে। ল্যাপটপের সাথে তুলনা করাও এখন অনেক সহজ হয়ে গেছে।

ট্রেড-অফ আর তিক্ত অভিজ্ঞতা

এই সেটআপের কিছু বড় অসুবিধা আছে যা আমাকে বেশ ভুগিয়েছে। প্রথমত, Flake-parts এর লার্নিং কার্ভ অনেক বড়। একটা পুরো উইকএন্ড আমার নষ্ট হয়েছে শুধু এটা বুঝতে যে কীভাবে perSystem আর flake.modules একসাথে কাজ করে।

আরও একটা বিরক্তিকর বিষয় হলো, nix flake check এখন অনেক স্লো। আপনি যদি ছোট একটা চেঞ্জও করেন, পুরো মডিউল ট্রি ইভ্যালুয়েট করতে হয়। এছাড়া কোনো অপশন কোথা থেকে আসছে সেটা ডিবাগ করাও একটু ট্রিকি। আমি সাধারণত nixos-option ব্যবহার করি, তবে সেটা সবসময় ঠিকমতো কাজ করে কি না তা নিয়ে আমি ১০০% নিশ্চিত না।

আমার মনে হয় ১০০+ মডিউল থাকা মানেই যে সিস্টেম ভালো হবে, তা নয়। আমার ৪০টা মডিউল আছে মেইন ফিচারের জন্য, বাকিগুলো সব ছোটখাটো হেল্পার। তবে এই NixOS Module সাজানোর প্যাটার্নটা আমাকে পাগল হওয়া থেকে বাঁচিয়েছে।

NixOS এ নতুন হলে এই সিস্টেমটা হয়তো আপনার কাছে ওভারকিল মনে হতে পারে। তবে আপনার যদি অনেকগুলো মেশিন থাকে এবং ৫০০ লাইনের flake.nix নিয়ে বিরক্ত হয়ে থাকেন, তবে এই পদ্ধতিটা একবার ট্রাই করে দেখতে পারেন।

NixOS Module অর্গানাইজেশন নিয়ে এই ব্লগে জানুন কীভাবে Flake-parts ব্যবহার করে ১০০+ মডিউল সাজানো যায়, এর সুবিধা-অসুবিধা এবং কোড কনফিগারেশন।

Asaduzzaman Pavel

About the Author

Asaduzzaman Pavel is a Software Engineer who actually enjoys the friction of a well-architected system. He has over 15 years of experience building high-performance backends and infrastructure that can actually handle the real-world chaos of scale.

Currently looking for new opportunities to build something amazing.