nix_data/cache/
profile.rs1use crate::{
2 cache::nixos,
3 CACHEDIR,
4};
5use anyhow::{anyhow, Context, Result};
6use log::{debug, error, info};
7use serde::Deserialize;
8use sqlx::SqlitePool;
9use std::{
10 collections::{HashMap, HashSet},
11 fs::{self, File},
12 io::{Read, Write},
13 path::Path,
14 process::Command,
15};
16
17use super::nixos::nixospkgs;
18
19#[derive(Debug, Deserialize)]
20struct ProfilePkgsRoot {
21 elements: Vec<ProfilePkgOut>,
22}
23
24#[derive(Debug, Deserialize)]
25struct ProfilePkgOut {
26 #[serde(rename = "attrPath")]
27 attrpath: Option<String>,
28 #[serde(rename = "originalUrl")]
29 originalurl: Option<String>,
30 #[serde(rename = "storePaths")]
31 storepaths: Vec<String>,
32}
33
34#[derive(Debug)]
36pub struct ProfilePkg {
37 pub name: String,
38 pub originalurl: String,
39}
40
41pub fn getprofilepkgs() -> Result<HashMap<String, ProfilePkg>> {
44 if !Path::new(&format!(
45 "{}/.nix-profile/manifest.json",
46 std::env::var("HOME")?
47 ))
48 .exists()
49 {
50 return Ok(HashMap::new());
51 }
52 let profileroot: ProfilePkgsRoot = serde_json::from_reader(File::open(&format!(
53 "{}/.nix-profile/manifest.json",
54 std::env::var("HOME")?
55 ))?)?;
56 let mut out = HashMap::new();
57 for pkg in profileroot.elements {
58 if let (Some(attrpath), Some(originalurl)) = (pkg.attrpath, pkg.originalurl) {
59 let attr = if attrpath.starts_with("legacyPackages") {
60 attrpath
61 .split('.')
62 .collect::<Vec<_>>()
63 .get(2..)
64 .context("Failed to get legacyPackage attribute")?
65 .join(".")
66 } else {
67 format!("{}#{}", originalurl, attrpath)
68 };
69 if let Some(first) = pkg.storepaths.get(0) {
70 let ver = first
71 .get(44..)
72 .context("Failed to get pkg name from store path")?;
73 out.insert(
74 attr,
75 ProfilePkg {
76 name: ver.to_string(),
77 originalurl,
78 },
79 );
80 }
81 }
82 }
83 Ok(out)
84}
85
86pub async fn getprofilepkgs_versioned() -> Result<HashMap<String, String>> {
89 if !Path::new(&format!(
90 "{}/.nix-profile/manifest.json",
91 std::env::var("HOME")?
92 ))
93 .exists()
94 {
95 return Ok(HashMap::new());
96 }
97 let profilepkgs = getprofilepkgs()?;
98 let latestpkgs = if Path::new(&format!("{}/nixpkgs.db", &*CACHEDIR)).exists() {
99 format!("{}/nixpkgs.db", &*CACHEDIR)
100 } else {
101 nixpkgslatest().await?
103 };
104 let mut out = HashMap::new();
105 let pool = SqlitePool::connect(&format!("sqlite://{}", latestpkgs)).await?;
106 for (pkg, _v) in profilepkgs {
107 let versions: Vec<(String,)> = sqlx::query_as(
108 r#"
109 SELECT version FROM pkgs WHERE attribute = $1
110 "#,
111 )
112 .bind(&pkg)
113 .fetch_all(&pool)
114 .await?;
115 if !versions.is_empty() {
116 out.insert(pkg, versions.get(0).unwrap().0.to_string());
117 }
118 }
119 Ok(out)
120}
121
122pub async fn nixpkgslatest() -> Result<String> {
125 if !std::path::Path::new(&*CACHEDIR).exists() {
127 std::fs::create_dir_all(&*CACHEDIR)?;
128 }
129
130 let mut nixpkgsver = None;
131 let mut pinned = false;
132 let regout = Command::new("nix").arg("registry").arg("list").output()?;
133 let reg = String::from_utf8(regout.stdout)?.replace(" ", " ");
134
135 let mut latestnixpkgsver = String::new();
136
137 for l in reg.split('\n') {
138 let parts = l.split(' ').collect::<Vec<_>>();
139 if let Some(x) = parts.get(1) {
140 if x == &"flake:nixpkgs" {
141 if let Some(x) = parts.get(2) {
142 nixpkgsver = Some(x.to_string().replace("github:NixOS/nixpkgs/", ""));
143
144 if let Some(rev) = x.find("&rev=") {
145 if let Some(rev) = (*x).get(rev + 5..) {
146 info!(
147 "Found specific revision: {}. Switching to versioned checking",
148 rev
149 );
150 nixpkgsver = Some(rev.to_string());
151 latestnixpkgsver = rev.to_string();
152 pinned = true;
153 }
154 }
155 break;
156 }
157 }
158 }
159 }
160
161 if !pinned {
162 let verurl = if let Some(v) = &nixpkgsver {
163 format!(
164 "https://raw.githubusercontent.com/snowflakelinux/nix-data-db/main/{}/nixpkgs.ver",
165 v
166 )
167 } else {
168 String::from("https://raw.githubusercontent.com/snowflakelinux/nix-data-db/main/nixpkgs-unstable/nixpkgs.ver")
169 };
170 debug!("Checking nixpkgs version");
171 let resp = reqwest::get(&verurl).await;
172 let resp = if let Ok(r) = resp {
173 r
174 } else {
175 let dbpath = format!("{}/nixpkgs.db", &*CACHEDIR);
178 if Path::new(&dbpath).exists() {
179 info!("Using old database");
180 return Ok(dbpath);
181 } else {
182 return Err(anyhow!("Could not find latest nixpkgs version"));
183 }
184 };
185 latestnixpkgsver = if resp.status().is_success() {
186 resp.text().await?
187 } else {
188 return Err(anyhow!("Could not find latest nixpkgs version"));
189 };
190 debug!("Latest nixpkgs version: {}", latestnixpkgsver);
191 }
192
193 if let Ok(prevver) = fs::read_to_string(&format!("{}/nixpkgs.ver", &*CACHEDIR)) {
195 if prevver == latestnixpkgsver && Path::new(&format!("{}/nixpkgs.db", &*CACHEDIR)).exists()
196 {
197 debug!("No new version of nixpkgs found");
198 return Ok(format!("{}/nixpkgs.db", &*CACHEDIR));
199 }
200 }
201
202 let url = if pinned {
203 format!(
204 "https://raw.githubusercontent.com/snowflakelinux/nixpkgs-version-data/main/nixos-unstable/{}.json.br",
205 latestnixpkgsver
206 )
207 } else if let Some(v) = &nixpkgsver {
208 format!(
209 "https://raw.githubusercontent.com/snowflakelinux/nix-data-db/main/{}/nixpkgs_versions.db.br",
210 v
211 )
212 } else {
213 String::from("https://raw.githubusercontent.com/snowflakelinux/nix-data-db/main/nixpkgs-unstable/nixpkgs_versions.db.br")
214 };
215 debug!("Downloading nix-data database");
216 let client = reqwest::Client::builder().brotli(true).build()?;
217 let resp = client.get(url).send().await?;
218 if resp.status().is_success() {
219 debug!("Writing nix-data database");
220 {
222 let bytes = resp.bytes().await?;
223 let mut br = brotli::Decompressor::new(bytes.as_ref(), 4096);
224 let mut pkgsout = Vec::new();
225 br.read_to_end(&mut pkgsout)?;
226 if pinned {
227 let pkgsjson: HashMap<String, String> = serde_json::from_slice(&pkgsout)?;
228 let dbfile = format!("{}/nixpkgs.db", &*CACHEDIR);
229 nixos::createdb(&dbfile, &pkgsjson).await?;
230 } else {
231 let mut out = File::create(&format!("{}/nixpkgs.db", &*CACHEDIR))?;
232 if let Err(e) = out.write_all(&pkgsout) {
233 error!("{}", e);
234 return Err(anyhow!("Failed write to nixpkgs.br"));
235 }
236 }
237 }
238 debug!("Writing nix-data version");
239 File::create(format!("{}/nixpkgs.ver", &*CACHEDIR))?
241 .write_all(latestnixpkgsver.as_bytes())?;
242 } else {
243 return Err(anyhow!("Failed to download latest nixpkgs.db.br"));
244 }
245 Ok(format!("{}/nixpkgs.db", &*CACHEDIR))
246}
247
248pub async fn unavailablepkgs() -> Result<HashMap<String, String>> {
249 let nixpath = Command::new("nix")
250 .arg("eval")
251 .arg("nixpkgs#path")
252 .output()?
253 .stdout;
254 let nixpath = String::from_utf8(nixpath)?;
255 let nixpath = nixpath.trim();
256
257 let aliases = Command::new("nix-instantiate")
258 .arg("--eval")
259 .arg("-E")
260 .arg(&format!("with import {} {{}}; builtins.attrNames ((self: super: lib.optionalAttrs config.allowAliases (import {}/pkgs/top-level/aliases.nix lib self super)) {{}} {{}})", nixpath, nixpath))
261 .arg("--json")
262 .output()?;
263 let aliasstr = String::from_utf8(aliases.stdout)?;
264 let aliasesout: HashSet<String> = serde_json::from_str(&aliasstr)?;
265
266 let flakespkgs = getprofilepkgs()?;
267 let mut unavailable = HashMap::new();
268 for pkg in flakespkgs.keys() {
269 if aliasesout.contains(pkg) && Command::new("nix-instantiate")
270 .arg("--eval")
271 .arg("-E")
272 .arg(&format!("with import {} {{}}; builtins.tryEval ((self: super: lib.optionalAttrs config.allowAliases (import {}/pkgs/top-level/aliases.nix lib self super)) {{}} {{}}).{}", nixpath, nixpath, pkg))
273 .output()?.status.success() {
274 let out = Command::new("nix-instantiate")
275 .arg("--eval")
276 .arg("-E")
277 .arg(&format!("with import {} {{}}; ((self: super: lib.optionalAttrs config.allowAliases (import {}/pkgs/top-level/aliases.nix lib self super)) {{}} {{}}).{}", nixpath, nixpath, pkg))
278 .output()?;
279 let err = String::from_utf8(out.stderr)?;
280 let err = err.strip_prefix("error: ").unwrap_or(&err).trim();
281 unavailable.insert(pkg.to_string(), err.to_string());
282 }
283 }
284
285 let nixospkgs = nixospkgs().await?;
286 let pool = SqlitePool::connect(&format!("sqlite://{}", nixospkgs)).await?;
287
288 for pkg in flakespkgs.keys() {
289 let (x, broken, insecure): (String, u8, u8) =
290 sqlx::query_as("SELECT attribute,broken,insecure FROM meta WHERE attribute = $1")
291 .bind(pkg)
292 .fetch_one(&pool)
293 .await?;
294 if &x != pkg {
295 unavailable.insert(
296 pkg.to_string(),
297 String::from("Package not found in newer version of nixpkgs"),
298 );
299 } else if broken == 1 {
300 unavailable.insert(pkg.to_string(), String::from("Package is marked as broken"));
301 } else if insecure == 1 {
302 unavailable.insert(
303 pkg.to_string(),
304 String::from("Package is marked as insecure"),
305 );
306 }
307 }
308 Ok(unavailable)
309}