entrenar/sovereign/nix/
config.rs1use super::crate_spec::CrateSpec;
4use super::system::NixSystem;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct NixFlakeConfig {
11 pub crates: Vec<CrateSpec>,
13 pub rust_version: String,
15 pub features: HashMap<String, Vec<String>>,
17 pub systems: Vec<NixSystem>,
19 pub description: String,
21 pub gpu_support: bool,
23 pub include_dev_shell: bool,
25 pub include_checks: bool,
27}
28
29impl NixFlakeConfig {
30 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 pub fn sovereign_stack() -> Self {
46 let mut config = Self::new("PAIML Sovereign ML Stack - Air-gapped deployment ready");
47
48 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 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 pub fn add_crate(mut self, spec: CrateSpec) -> Self {
70 self.crates.push(spec);
71 self
72 }
73
74 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 pub fn with_rust_version(mut self, version: impl Into<String>) -> Self {
86 self.rust_version = version.into();
87 self
88 }
89
90 pub fn with_systems(mut self, systems: Vec<NixSystem>) -> Self {
92 self.systems = systems;
93 self
94 }
95
96 pub fn with_gpu_support(mut self, enabled: bool) -> Self {
98 self.gpu_support = enabled;
99 self
100 }
101
102 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 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 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 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 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 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 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 flake.push_str(" });\n}\n");
238
239 flake
240 }
241
242 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 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 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 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}