Skip to main content

entrenar/sovereign/nix/
config.rs

1//! Nix flake configuration
2
3use super::crate_spec::CrateSpec;
4use super::system::NixSystem;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Nix flake configuration
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct NixFlakeConfig {
11    /// Crate specifications
12    pub crates: Vec<CrateSpec>,
13    /// Rust toolchain version
14    pub rust_version: String,
15    /// Features to enable per crate
16    pub features: HashMap<String, Vec<String>>,
17    /// Target systems
18    pub systems: Vec<NixSystem>,
19    /// Flake description
20    pub description: String,
21    /// Enable GPU support
22    pub gpu_support: bool,
23    /// Include dev shell
24    pub include_dev_shell: bool,
25    /// Include CI checks
26    pub include_checks: bool,
27}
28
29impl NixFlakeConfig {
30    /// Create a new flake config
31    pub fn new(description: impl Into<String>) -> Self {
32        Self {
33            crates: Vec::new(),
34            rust_version: "1.75.0".to_string(),
35            features: HashMap::new(),
36            systems: NixSystem::all(),
37            description: description.into(),
38            gpu_support: false,
39            include_dev_shell: true,
40            include_checks: true,
41        }
42    }
43
44    /// Create the sovereign stack configuration (all PAIML crates)
45    pub fn sovereign_stack() -> Self {
46        let mut config = Self::new("PAIML Sovereign ML Stack - Air-gapped deployment ready");
47
48        // Add all PAIML crates
49        config.crates = vec![
50            CrateSpec::crates_io("trueno", "0.2"),
51            CrateSpec::crates_io("aprender", "0.1"),
52            CrateSpec::crates_io("renacer", "0.1"),
53            CrateSpec::crates_io("entrenar", "0.2"),
54            CrateSpec::crates_io("realizar", "0.1"),
55        ];
56
57        // Set features
58        config.features.insert("trueno".to_string(), vec!["simd".to_string()]);
59        config.features.insert("entrenar".to_string(), vec!["full".to_string()]);
60
61        config.rust_version = "1.75.0".to_string();
62        config.include_dev_shell = true;
63        config.include_checks = true;
64
65        config
66    }
67
68    /// Add a crate to the configuration
69    pub fn add_crate(mut self, spec: CrateSpec) -> Self {
70        self.crates.push(spec);
71        self
72    }
73
74    /// Set features for a crate
75    pub fn with_features(
76        mut self,
77        crate_name: impl Into<String>,
78        features: impl IntoIterator<Item = impl Into<String>>,
79    ) -> Self {
80        self.features.insert(crate_name.into(), features.into_iter().map(Into::into).collect());
81        self
82    }
83
84    /// Set Rust version
85    pub fn with_rust_version(mut self, version: impl Into<String>) -> Self {
86        self.rust_version = version.into();
87        self
88    }
89
90    /// Set target systems
91    pub fn with_systems(mut self, systems: Vec<NixSystem>) -> Self {
92        self.systems = systems;
93        self
94    }
95
96    /// Enable GPU support
97    pub fn with_gpu_support(mut self, enabled: bool) -> Self {
98        self.gpu_support = enabled;
99        self
100    }
101
102    /// Generate the flake.nix content
103    pub fn generate_flake_nix(&self) -> String {
104        let systems_list: Vec<&str> = self.systems.iter().map(NixSystem::as_str).collect();
105        let systems_str =
106            systems_list.iter().map(|s| format!("\"{s}\"")).collect::<Vec<_>>().join(" ");
107
108        let crate_names: Vec<&str> = self.crates.iter().map(|c| c.name.as_str()).collect();
109
110        let mut flake = String::new();
111
112        // Header
113        flake.push_str(&format!(
114            r#"# Nix Flake for PAIML Sovereign Stack
115# {}
116# Generated by entrenar sovereign deployment tooling
117{{
118  description = "{}";
119
120  inputs = {{
121    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
122    rust-overlay = {{
123      url = "github:oxalica/rust-overlay";
124      inputs.nixpkgs.follows = "nixpkgs";
125    }};
126    crane = {{
127      url = "github:ipetkov/crane";
128      inputs.nixpkgs.follows = "nixpkgs";
129    }};
130    flake-utils.url = "github:numtide/flake-utils";
131  }};
132
133  outputs = {{ self, nixpkgs, rust-overlay, crane, flake-utils, ... }}:
134    flake-utils.lib.eachSystem [ {} ] (system:
135      let
136        overlays = [ (import rust-overlay) ];
137        pkgs = import nixpkgs {{
138          inherit system overlays;
139        }};
140
141        rustToolchain = pkgs.rust-bin.stable."{}" .default.override {{
142          extensions = [ "rust-src" "rust-analyzer" ];
143        }};
144
145        craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
146
147"#,
148            chrono::Utc::now().format("%Y-%m-%d"),
149            self.description,
150            systems_str,
151            self.rust_version,
152        ));
153
154        // Build inputs
155        flake.push_str("        buildInputs = with pkgs; [\n");
156        flake.push_str("          openssl\n");
157        flake.push_str("          pkg-config\n");
158        if self.gpu_support {
159            flake.push_str("          # GPU support\n");
160            flake.push_str("          cudatoolkit\n");
161            flake.push_str("          cudnn\n");
162        }
163        flake.push_str("        ];\n\n");
164
165        // Common args
166        flake.push_str(&format!(
167            r"        commonArgs = {{
168          src = craneLib.cleanCargoSource ./.;
169          inherit buildInputs;
170          nativeBuildInputs = with pkgs; [ pkg-config ];
171        }};
172
173        # Build dependencies first (for caching)
174        cargoArtifacts = craneLib.buildDepsOnly commonArgs;
175
176        # Main packages
177{}",
178            self.generate_package_definitions(&crate_names),
179        ));
180
181        // Outputs
182        flake.push_str(&format!(
183            r"
184      in {{
185        packages = {{
186{}          default = {};
187        }};
188",
189            self.generate_packages_attr(&crate_names),
190            crate_names.first().unwrap_or(&"entrenar"),
191        ));
192
193        // Dev shell
194        if self.include_dev_shell {
195            flake.push_str(&format!(
196                r"
197        devShells.default = pkgs.mkShell {{
198          inputsFrom = [ {} ];
199          buildInputs = with pkgs; [
200            rustToolchain
201            rust-analyzer
202            cargo-watch
203            cargo-edit
204            cargo-expand
205          ];
206        }};
207",
208                crate_names.first().unwrap_or(&"entrenar"),
209            ));
210        }
211
212        // Checks
213        if self.include_checks {
214            flake.push_str(
215                r#"
216        checks = {
217          clippy = craneLib.cargoClippy (commonArgs // {
218            inherit cargoArtifacts;
219            cargoClippyExtraArgs = "--all-targets -- -D warnings";
220          });
221
222          test = craneLib.cargoNextest (commonArgs // {
223            inherit cargoArtifacts;
224            partitions = 1;
225            partitionType = "count";
226          });
227
228          fmt = craneLib.cargoFmt {
229            src = craneLib.cleanCargoSource ./.;
230          };
231        };
232"#,
233            );
234        }
235
236        // Close outputs
237        flake.push_str("      });\n}\n");
238
239        flake
240    }
241
242    /// Generate package definitions
243    fn generate_package_definitions(&self, crate_names: &[&str]) -> String {
244        use std::fmt::Write;
245        let mut result = String::new();
246        for name in crate_names {
247            let features = self
248                .features
249                .get(*name)
250                .map(|f| {
251                    let feature_list = f.join(",");
252                    format!(r#"cargoExtraArgs = "--features {feature_list}";"#)
253                })
254                .unwrap_or_default();
255
256            let _ = writeln!(
257                &mut result,
258                "        {name} = craneLib.buildPackage (commonArgs // {{\n\
259          inherit cargoArtifacts;\n\
260          pname = \"{name}\";\n\
261          {features}\n\
262        }});\n"
263            );
264        }
265        result
266    }
267
268    /// Generate packages attribute
269    fn generate_packages_attr(&self, crate_names: &[&str]) -> String {
270        use std::fmt::Write;
271        let mut result = String::new();
272        for name in crate_names {
273            let _ = writeln!(&mut result, "          {name} = {name};");
274        }
275        result
276    }
277
278    /// Generate Cachix configuration
279    pub fn generate_cachix_config(&self) -> String {
280        format!(
281            r#"# Cachix configuration for PAIML Sovereign Stack
282# Push: cachix push paiml $(nix-build)
283# Use:  cachix use paiml
284
285{{
286  "name": "paiml",
287  "signing_key_path": "$HOME/.config/cachix/cachix.dhall",
288  "binary_caches": [
289    {{
290      "url": "https://paiml.cachix.org",
291      "public_signing_keys": ["paiml.cachix.org-1:..."]
292    }}
293  ],
294  "crates": {:?},
295  "rust_version": "{}",
296  "generated": "{}"
297}}
298"#,
299            self.crates.iter().map(|c| &c.name).collect::<Vec<_>>(),
300            self.rust_version,
301            chrono::Utc::now().format("%Y-%m-%dT%H:%M:%SZ"),
302        )
303    }
304
305    /// Generate a minimal flake for a single crate
306    pub fn minimal_flake(crate_name: &str, crate_path: &str) -> String {
307        format!(
308            r#"{{
309  description = "{crate_name} - PAIML component";
310
311  inputs = {{
312    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
313    rust-overlay.url = "github:oxalica/rust-overlay";
314    crane.url = "github:ipetkov/crane";
315    flake-utils.url = "github:numtide/flake-utils";
316  }};
317
318  outputs = {{ self, nixpkgs, rust-overlay, crane, flake-utils, ... }}:
319    flake-utils.lib.eachDefaultSystem (system:
320      let
321        pkgs = import nixpkgs {{
322          inherit system;
323          overlays = [ (import rust-overlay) ];
324        }};
325        craneLib = crane.mkLib pkgs;
326      in {{
327        packages.default = craneLib.buildPackage {{
328          src = ./{crate_path};
329          pname = "{crate_name}";
330        }};
331
332        devShells.default = pkgs.mkShell {{
333          inputsFrom = [ self.packages.${{system}}.default ];
334          buildInputs = with pkgs; [ rust-analyzer ];
335        }};
336      }});
337}}
338"#
339        )
340    }
341}
342
343impl Default for NixFlakeConfig {
344    fn default() -> Self {
345        Self::sovereign_stack()
346    }
347}