nix_data/cache/
channel.rs

1use crate::CACHEDIR;
2use anyhow::{anyhow, Context, Result};
3use log::info;
4use serde::Deserialize;
5use sqlx::SqlitePool;
6use std::{
7    collections::{HashMap, HashSet},
8    fs::{self, File},
9    io::{BufReader, Read, Write},
10    path::Path,
11    process::Command,
12};
13
14use super::{
15    nixos::{self, getnixospkgs, nixospkgs},
16    NixPkgList,
17};
18
19/// Gets a list of all packages in legacy NixOS systems with their name and version.
20/// Can be used to find what versions of system packages are currently installed.
21/// Will only work on legacy NixOS systems.
22pub async fn legacypkgs() -> Result<String> {
23    let versionout = Command::new("nixos-version").arg("--json").output()?;
24    let version: HashMap<String, String> = serde_json::from_slice(&versionout.stdout)?;
25
26    let nixosversion = version
27        .get("nixosVersion")
28        .context("No NixOS version found")?;
29    let relver = if nixosversion[5..8].eq("pre") {
30        "unstable"
31    } else {
32        &nixosversion[0..5]
33    };
34
35    // If cache directory doesn't exist, create it
36    if !std::path::Path::new(&*CACHEDIR).exists() {
37        std::fs::create_dir_all(&*CACHEDIR)?;
38    }
39
40    // Check if latest version is already downloaded
41    if let Ok(prevver) = fs::read_to_string(&format!("{}/legacypkgs.ver", &*CACHEDIR)) {
42        if prevver.eq(nixosversion) && Path::new(&format!("{}/legacypkgs.db", &*CACHEDIR)).exists()
43        {
44            info!("No new version of NixOS legacy found");
45            return Ok(format!("{}/legacypkgs.db", &*CACHEDIR));
46        }
47    }
48
49    async fn downloadrelease(relver: &str, nixosversion: &str) -> Result<HashMap<String, String>> {
50        let url = format!(
51            "https://releases.nixos.org/nixos/{}/nixos-{}/packages.json.br",
52            relver, nixosversion
53        );
54        // Download file with reqwest
55        let client = reqwest::Client::builder().brotli(true).build()?;
56        let resp = client.get(url).send().await;
57        let resp = if let Ok(r) = resp {
58            r
59        } else {
60            return Err(anyhow!("Failed to download legacy packages.json"));
61        };
62        if resp.status().is_success() {
63            let pkgjson: NixPkgList =
64                serde_json::from_reader(BufReader::new(resp.text().await?.as_bytes()))?;
65            let pkgout = pkgjson
66                .packages
67                .iter()
68                .map(|(k, v)| (k.to_string(), v.version.to_string()))
69                .collect::<HashMap<String, String>>();
70            Ok(pkgout)
71        } else {
72            Err(anyhow!("Failed to download legacy packages.json"))
73        }
74    }
75
76    // Get list of packages
77    let pkgout = if let Some(rev) = version.get("nixpkgsRevision") {
78        let url = format!("https://raw.githubusercontent.com/snowflakelinux/nixpkgs-version-data/main/nixos-{}/{}.json.br", relver, rev);
79        println!("{}", url);
80        let resp = reqwest::get(&url).await?;
81        if resp.status().is_success() {
82            let r = resp.bytes().await?;
83            println!("Downloaded");
84            let mut br = brotli::Decompressor::new(r.as_ref(), 4096);
85            let mut pkgsout = Vec::new();
86            br.read_to_end(&mut pkgsout)?;
87            let pkgsjson: HashMap<String, String> = serde_json::from_slice(&pkgsout)?;
88            println!("Decompressed");
89            pkgsjson
90        } else {
91            let url = format!("https://raw.githubusercontent.com/snowflakelinux/nixpkgs-version-data/main/nixos-unstable/{}.json.br", rev);
92            println!("{}", url);
93            let resp = reqwest::get(&url).await?;
94            if resp.status().is_success() {
95                let r = resp.bytes().await?;
96                println!("Downloaded");
97                let mut br = brotli::Decompressor::new(r.as_ref(), 4096);
98                let mut pkgsout = Vec::new();
99                br.read_to_end(&mut pkgsout)?;
100                let pkgsjson: HashMap<String, String> = serde_json::from_slice(&pkgsout)?;
101                println!("Decompressed");
102                pkgsjson
103            } else {
104                downloadrelease(relver, nixosversion).await?
105            }
106        }
107    } else {
108        downloadrelease(relver, nixosversion).await?
109    };
110    let dbfile = format!("{}/legacypkgs.db", &*CACHEDIR);
111
112    nixos::createdb(&dbfile, &pkgout).await?;
113
114    // Write version downloaded to file
115    File::create(format!("{}/legacypkgs.ver", &*CACHEDIR))?.write_all(nixosversion.as_bytes())?;
116
117    Ok(format!("{}/legacypkgs.db", &*CACHEDIR))
118}
119
120/// Gets a list of all packages in NixOS systems with their attribute and version.
121/// The input `paths` should be the paths to the `configuration.nix` files containing `environment.systemPackages`
122pub async fn getlegacypkgs(paths: &[&str]) -> Result<HashMap<String, String>> {
123    getnixospkgs(paths, nixos::NixosType::Legacy).await
124}
125
126#[derive(Debug, Deserialize)]
127struct EnvPkgOut {
128    pname: String,
129    version: String,
130}
131
132/// Gets a list of all packages installed with `nix-env` with their name and version.
133/// Due to limitations of `nix-env`, the HashMap keys are the packages `pname` rather than `attributePath`.
134/// This means that finding more information about the specific derivations is more difficult.
135pub fn getenvpkgs() -> Result<HashMap<String, String>> {
136    let output = Command::new("nix-env").arg("-q").arg("--json").output()?;
137    let pkgs: HashMap<String, EnvPkgOut> = serde_json::from_slice(&output.stdout)?;
138    let mut out = HashMap::new();
139    for (_, v) in pkgs {
140        out.insert(v.pname, v.version);
141    }
142    Ok(out)
143}
144
145pub fn uptodate() -> Result<Option<(String, String)>> {
146    let legacyver = fs::read_to_string(&format!("{}/legacypkgs.ver", &*CACHEDIR))?;
147    let nixosver = fs::read_to_string(&format!("{}/nixospkgs.ver", &*CACHEDIR))?;
148    if !nixosver.eq(&legacyver) {
149        Ok(Some((legacyver, nixosver)))
150    } else {
151        Ok(None)
152    }
153}
154
155pub async fn unavailablepkgs(paths: &[&str]) -> Result<HashMap<String, String>> {
156    let aliases = Command::new("nix-instantiate")
157        .arg("--eval")
158        .arg("-E")
159        .arg("with import <nixpkgs> {}; builtins.attrNames ((self: super: lib.optionalAttrs config.allowAliases (import <nixpkgs/pkgs/top-level/aliases.nix> lib self super)) {} {})")
160        .arg("--json")
161        .output()?;
162    let aliasstr = String::from_utf8(aliases.stdout)?;
163    let aliasesout: HashSet<String> = serde_json::from_str(&aliasstr)?;
164
165    let pkgs = {
166        let mut allpkgs: HashSet<String> = HashSet::new();
167        for path in paths {
168            if let Ok(filepkgs) = nix_editor::read::getarrvals(
169                &fs::read_to_string(path)?,
170                "environment.systemPackages",
171            ) {
172                let filepkgset = filepkgs
173                    .into_iter()
174                    .map(|x| x.strip_prefix("pkgs.").unwrap_or(&x).to_string())
175                    .collect::<HashSet<_>>();
176                allpkgs = allpkgs.union(&filepkgset).map(|x| x.to_string()).collect();
177            }
178        }
179        allpkgs
180    };
181
182    let mut unavailable = HashMap::new();
183    for pkg in pkgs {
184        if aliasesout.contains(&pkg) && Command::new("nix-instantiate")
185                .arg("--eval")
186                .arg("-E")
187                .arg(&format!("with import <nixpkgs> {{}}; builtins.tryEval ((self: super: lib.optionalAttrs config.allowAliases (import <nixpkgs/pkgs/top-level/aliases.nix> lib self super)) {{}} {{}}).{}", pkg))
188                .output()?.status.success() {
189            let out = Command::new("nix-instantiate")
190                .arg("--eval")
191                .arg("-E")
192                .arg(&format!("with import <nixpkgs> {{}}; ((self: super: lib.optionalAttrs config.allowAliases (import <nixpkgs/pkgs/top-level/aliases.nix> lib self super)) {{}} {{}}).{}", pkg))
193                .output()?;
194            let err = String::from_utf8(out.stderr)?;
195            let err = err.strip_prefix("error: ").unwrap_or(&err).trim();
196            unavailable.insert(pkg, err.to_string());
197        }
198    }
199
200    let legacypkgs = getlegacypkgs(paths).await?;
201    let nixospkgs = nixospkgs().await?;
202    let pool = SqlitePool::connect(&format!("sqlite://{}", nixospkgs)).await?;
203
204    for (pkg, _) in legacypkgs {
205        let (x, broken, insecure): (String, u8, u8) =
206            sqlx::query_as("SELECT attribute,broken,insecure FROM meta WHERE attribute = $1")
207                .bind(&pkg)
208                .fetch_one(&pool)
209                .await?;
210        if x != pkg {
211            unavailable.insert(
212                pkg,
213                String::from("Package not found in newer version of nixpkgs"),
214            );
215        } else if broken == 1 {
216            unavailable.insert(pkg, String::from("Package is marked as broken"));
217        } else if insecure == 1 {
218            unavailable.insert(pkg, String::from("Package is marked as insecure"));
219        }
220    }
221    Ok(unavailable)
222}