nix: nix-racer -> selector4nix
Replace the vendored nix-racer Rust proxy with selector4nix as the local substituter proxy. Drop the nix-racer crate and its systemd service, add the selector4nix flake input, repoint nix.settings.substituters at its port, and load upstream nixos modules via an upstreamModules list in flake/hosts.nix.
This commit is contained in:
Generated
+41
@@ -121,6 +121,22 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1784564969,
|
||||
"narHash": "sha256-TkTWNhJV/8Wk3czlbz4bwHZkFCaCHPsOy+oJe4PUiXg=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "7930f6c291de6f83c257839d434592aa085f290a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"ref": "master",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"darkly": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -613,6 +629,7 @@
|
||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||
"nixpkgs-unstable-small": "nixpkgs-unstable-small",
|
||||
"noctalia": "noctalia",
|
||||
"selector4nix": "selector4nix",
|
||||
"sops-nix": "sops-nix",
|
||||
"stylix": "stylix",
|
||||
"system76-scheduler-niri": "system76-scheduler-niri",
|
||||
@@ -638,6 +655,30 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"selector4nix": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"flake-parts": [
|
||||
"flake-parts"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1786060471,
|
||||
"narHash": "sha256-jOIHxqMGJ5vdWkuX/4KAYwMV4av1hW6YVO62vM+Q7Fk=",
|
||||
"owner": "StarryReverie",
|
||||
"repo": "selector4nix",
|
||||
"rev": "590f98ce3123fa854cc7dc42f04cd5b92c37d7a6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "StarryReverie",
|
||||
"repo": "selector4nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"sops-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
|
||||
@@ -94,6 +94,11 @@
|
||||
inputs.noctalia-qs.inputs.systems.follows = "systems";
|
||||
inputs.noctalia-qs.inputs.treefmt-nix.follows = "treefmt";
|
||||
};
|
||||
selector4nix = {
|
||||
url = "github:StarryReverie/selector4nix";
|
||||
inputs.flake-parts.follows = "flake-parts";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
zen = {
|
||||
url = "github:0xc000022070/zen-browser-flake";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
||||
+53
-59
@@ -2,7 +2,6 @@
|
||||
self,
|
||||
lib,
|
||||
inputs,
|
||||
withSystem,
|
||||
config,
|
||||
pkgsParams,
|
||||
...
|
||||
@@ -35,6 +34,16 @@ let
|
||||
useGlobalPkgs = true;
|
||||
};
|
||||
};
|
||||
upstreamModules = [
|
||||
# keep-sorted start
|
||||
inputs.angrr.nixosModules.angrr
|
||||
inputs.home-manager.nixosModules.default
|
||||
inputs.impermanence.nixosModules.impermanence
|
||||
inputs.niri-nix.nixosModules.default
|
||||
inputs.selector4nix.nixosModules.selector4nix
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
# keep-sorted end
|
||||
];
|
||||
in
|
||||
{
|
||||
options.nixosHosts = lib.mkOption {
|
||||
@@ -82,66 +91,51 @@ in
|
||||
# Generate nixosConfigurations from declarative host definitions
|
||||
flake.nixosConfigurations = lib.mapAttrs (
|
||||
hostname: hostConfig:
|
||||
withSystem hostConfig.system (
|
||||
{ ... }:
|
||||
lib.nixosSystem {
|
||||
inherit (hostConfig) system;
|
||||
lib.nixosSystem {
|
||||
inherit (hostConfig) system;
|
||||
|
||||
specialArgs = {
|
||||
inherit
|
||||
inputs
|
||||
self
|
||||
hostname
|
||||
;
|
||||
assets =
|
||||
with lib.haumea;
|
||||
load {
|
||||
src = ../assets;
|
||||
loader = [ (matchers.always loaders.path) ];
|
||||
};
|
||||
secrets =
|
||||
with lib.haumea;
|
||||
load {
|
||||
src = ../secrets;
|
||||
loader = [ (matchers.always loaders.path) ];
|
||||
};
|
||||
}
|
||||
// vars
|
||||
// hostConfig.extraSpecialArgs;
|
||||
|
||||
modules =
|
||||
# Automatically import all feature modules
|
||||
(lib.umport {
|
||||
paths = [ ../modules ];
|
||||
extraExcludePredicate = path: lib.hasInfix "/_" (toString path);
|
||||
recursive = true;
|
||||
})
|
||||
++ [
|
||||
# Base profile (always included)
|
||||
../profiles/base.nix
|
||||
]
|
||||
# Add requested profiles
|
||||
++ (map (profile: ../profiles/${profile}.nix) hostConfig.profiles)
|
||||
# Add host-specific modules
|
||||
++ hostConfig.modules
|
||||
++ [
|
||||
(lib.mkAliasOptionModule [ "my" "hm" ] [ "home-manager" "users" vars.username ])
|
||||
|
||||
# Upstream modules
|
||||
# keep-sorted start
|
||||
inputs.angrr.nixosModules.angrr
|
||||
inputs.home-manager.nixosModules.default
|
||||
inputs.impermanence.nixosModules.impermanence
|
||||
inputs.niri-nix.nixosModules.default
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
# keep-sorted end
|
||||
|
||||
# pkgs and home-manager configuration
|
||||
pkgsModule
|
||||
hmModule
|
||||
];
|
||||
specialArgs = {
|
||||
inherit
|
||||
inputs
|
||||
self
|
||||
hostname
|
||||
;
|
||||
assets =
|
||||
with lib.haumea;
|
||||
load {
|
||||
src = ../assets;
|
||||
loader = [ (matchers.always loaders.path) ];
|
||||
};
|
||||
secrets =
|
||||
with lib.haumea;
|
||||
load {
|
||||
src = ../secrets;
|
||||
loader = [ (matchers.always loaders.path) ];
|
||||
};
|
||||
}
|
||||
)
|
||||
// vars
|
||||
// hostConfig.extraSpecialArgs;
|
||||
|
||||
modules =
|
||||
(lib.umport {
|
||||
paths = [ ../modules ];
|
||||
extraExcludePredicate = path: lib.hasInfix "/_" (toString path);
|
||||
recursive = true;
|
||||
})
|
||||
++ [
|
||||
../profiles/base.nix
|
||||
]
|
||||
++ (map (profile: ../profiles/${profile}.nix) hostConfig.profiles)
|
||||
++ hostConfig.modules
|
||||
++ upstreamModules
|
||||
++ [
|
||||
(lib.mkAliasOptionModule [ "my" "hm" ] [ "home-manager" "users" vars.username ])
|
||||
|
||||
# pkgs and home-manager configuration
|
||||
pkgsModule
|
||||
hmModule
|
||||
];
|
||||
}
|
||||
) config.nixosHosts;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/target
|
||||
Generated
-2105
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "nix-racer"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.8"
|
||||
reqwest = { version = "0.13", features = ["rustls"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
tokio = { version = "1.52", features = ["full"] }
|
||||
toml = "1.1"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
lib,
|
||||
rustPlatform,
|
||||
}:
|
||||
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = "nix-racer";
|
||||
version = "0.1.0";
|
||||
|
||||
src = lib.fileset.toSource {
|
||||
root = ./.;
|
||||
fileset = lib.fileset.unions [
|
||||
./Cargo.toml
|
||||
./Cargo.lock
|
||||
./src
|
||||
];
|
||||
};
|
||||
|
||||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "Nix substituter proxy with parallel cache queries and latency-aware selection";
|
||||
mainProgram = "nix-racer";
|
||||
license = lib.licenses.mit;
|
||||
platforms = lib.platforms.unix;
|
||||
};
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
use reqwest::Url;
|
||||
use serde::{Deserialize, Deserializer, de::Error};
|
||||
use std::net::{SocketAddr, SocketAddrV4};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct AppConfig {
|
||||
#[serde(default = "default_listen")]
|
||||
pub listen: SocketAddr,
|
||||
pub substituters: Vec<Substituter>,
|
||||
}
|
||||
|
||||
impl Default for AppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
listen: default_listen(),
|
||||
substituters: vec![
|
||||
Substituter {
|
||||
url: "https://mirror.sjtu.edu.cn/nix-channels/store",
|
||||
penalty: 0,
|
||||
},
|
||||
Substituter {
|
||||
url: "https://mirrors.ustc.edu.cn/nix-channels/store",
|
||||
penalty: 0,
|
||||
},
|
||||
Substituter {
|
||||
url: "https://cache.nixos.org",
|
||||
penalty: 100,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_listen() -> SocketAddr {
|
||||
SocketAddr::V4(SocketAddrV4::new([127, 0, 0, 1].into(), 2048))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Substituter {
|
||||
pub url: &'static str,
|
||||
pub penalty: u32,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Substituter {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct ShadowSubstituter {
|
||||
url: String,
|
||||
#[serde(default)]
|
||||
penalty: u32,
|
||||
}
|
||||
let sub = <ShadowSubstituter as Deserialize>::deserialize(deserializer);
|
||||
sub.and_then(|ShadowSubstituter { url, penalty }| {
|
||||
let url = match Url::parse(&url) {
|
||||
Ok(url) => Ok(Box::leak(url.to_string().into_boxed_str())),
|
||||
Err(err) => Err(<D::Error as Error>::custom(err.to_string())),
|
||||
}?;
|
||||
Ok(Substituter { url, penalty })
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use axum::{
|
||||
Router,
|
||||
extract::{Path, State},
|
||||
http::{StatusCode, header},
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::get,
|
||||
};
|
||||
use reqwest::Client;
|
||||
use tokio::task::JoinSet;
|
||||
use tracing_subscriber::{EnvFilter, layer::SubscriberExt as _, util::SubscriberInitExt as _};
|
||||
|
||||
mod config;
|
||||
use config::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
client: Client,
|
||||
config: Arc<AppConfig>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.with(
|
||||
EnvFilter::builder()
|
||||
.with_env_var("NIX_RACER_LOG")
|
||||
.with_default_directive("nix_racer=debug".parse().unwrap())
|
||||
.from_env()
|
||||
.unwrap(),
|
||||
)
|
||||
.init();
|
||||
|
||||
let config: Arc<AppConfig> = match std::fs::read_to_string("/etc/nix/nix-racer.toml") {
|
||||
Ok(file) => toml::from_str::<AppConfig>(&file).unwrap_or_else(|x| {
|
||||
tracing::warn!("Failed to parse config file ({x}), fallback to default configuration");
|
||||
AppConfig::default()
|
||||
}),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
tracing::info!("Config file not found, fallback to default configuration");
|
||||
AppConfig::default()
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!("Failed to read config file ({err}), fallback to default configuration");
|
||||
AppConfig::default()
|
||||
}
|
||||
}
|
||||
.into();
|
||||
|
||||
if config.substituters.is_empty() {
|
||||
tracing::error!("No substituters found");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let listen = config.listen;
|
||||
|
||||
let client = Client::builder()
|
||||
.timeout(Duration::from_secs(5))
|
||||
.pool_idle_timeout(Duration::from_secs(90))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let state = AppState { client, config };
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(listen).await.unwrap();
|
||||
|
||||
let app = Router::new()
|
||||
.route("/nix-cache-info", get(nix_cache_info_handler))
|
||||
.route("/{*path}", get(proxy_handler))
|
||||
.with_state(state);
|
||||
|
||||
tracing::info!("Smart Nix Proxy listening on http://{listen}");
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
|
||||
async fn nix_cache_info_handler() -> impl IntoResponse {
|
||||
let info = "StoreDir: /nix/store\nWantMassQuery: 1\nPriority: 30\n";
|
||||
([(header::CONTENT_TYPE, "text/x-nix-cache-info")], info)
|
||||
}
|
||||
|
||||
async fn proxy_handler(
|
||||
Path(path): Path<String>,
|
||||
State(state): State<AppState>,
|
||||
) -> impl IntoResponse {
|
||||
if !path.ends_with(".narinfo") {
|
||||
let redirect_url = format!("{}/{}", state.config.substituters[0].url, path);
|
||||
return Redirect::temporary(&redirect_url).into_response();
|
||||
}
|
||||
|
||||
let start_time = Instant::now();
|
||||
let mut set = JoinSet::new();
|
||||
|
||||
let mut penalties = HashMap::new();
|
||||
|
||||
for upstream in state.config.substituters.iter() {
|
||||
let url = format!("{}/{}", upstream.url, path);
|
||||
let client = state.client.clone();
|
||||
let base_url = upstream.url;
|
||||
|
||||
let handle = set.spawn(async move {
|
||||
let max_retries = 2;
|
||||
let mut delay = Duration::from_millis(50);
|
||||
|
||||
for attempt in 0..=max_retries {
|
||||
match client.get(&url).send().await {
|
||||
Ok(resp) if resp.status().is_success() => {
|
||||
let bytes = resp.bytes().await.map_err(|_| "Body error")?;
|
||||
|
||||
let text = String::from_utf8_lossy(&bytes);
|
||||
let mut new_text = String::with_capacity(text.len() + 64);
|
||||
for line in text.lines() {
|
||||
if let Some(rel_url) = line.strip_prefix("URL: ") {
|
||||
if rel_url.starts_with("http://") || rel_url.starts_with("https://")
|
||||
{
|
||||
new_text.push_str(line);
|
||||
} else {
|
||||
new_text.push_str(&format!("URL: {}/{}", base_url, rel_url));
|
||||
}
|
||||
} else {
|
||||
new_text.push_str(line);
|
||||
}
|
||||
new_text.push('\n');
|
||||
}
|
||||
return Ok((new_text.into_bytes(), base_url));
|
||||
}
|
||||
Ok(resp) if resp.status() == StatusCode::NOT_FOUND => return Err("404"),
|
||||
Err(_) => {
|
||||
if attempt == max_retries {
|
||||
return Err("Max retries");
|
||||
}
|
||||
tokio::time::sleep(delay).await;
|
||||
delay *= 2;
|
||||
}
|
||||
_ => return Err("Other HTTP Error"),
|
||||
}
|
||||
}
|
||||
Err("Unreachable")
|
||||
});
|
||||
|
||||
penalties.insert(handle.id(), upstream.penalty);
|
||||
}
|
||||
|
||||
struct ProxyResult {
|
||||
score: Duration,
|
||||
body: Vec<u8>,
|
||||
url: &'static str,
|
||||
}
|
||||
let mut best_result: Option<ProxyResult> = None;
|
||||
|
||||
loop {
|
||||
let min_active_penalty = penalties.values().min().copied();
|
||||
|
||||
if let Some(ProxyResult { score, .. }) = best_result {
|
||||
if let Some(min_p) = min_active_penalty {
|
||||
if start_time.elapsed() + Duration::from_millis(u64::from(min_p)) >= score {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if min_active_penalty.is_none() {
|
||||
break;
|
||||
}
|
||||
|
||||
let timeout_dur = if let Some(ProxyResult { score, .. }) = best_result {
|
||||
score.saturating_sub(start_time.elapsed())
|
||||
} else {
|
||||
Duration::from_secs(86400)
|
||||
};
|
||||
|
||||
if timeout_dur.is_zero() && best_result.is_some() {
|
||||
break;
|
||||
}
|
||||
|
||||
let res = tokio::time::timeout(timeout_dur, set.join_next_with_id()).await;
|
||||
|
||||
match res {
|
||||
Ok(Some(Ok((task_id, task_res)))) => {
|
||||
let penalty = penalties.remove(&task_id).unwrap();
|
||||
if let Ok((body, url)) = task_res {
|
||||
let score = start_time.elapsed() + Duration::from_millis(u64::from(penalty));
|
||||
if best_result.as_ref().is_none_or(|best| score < best.score) {
|
||||
best_result = Some(ProxyResult { score, body, url })
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(Err(join_err))) => {
|
||||
penalties.remove(&join_err.id());
|
||||
}
|
||||
Ok(None) => break,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ProxyResult { score, body, url }) = best_result {
|
||||
tracing::debug!("Winner for {}: {} (Score: {:?})", path, url, score,);
|
||||
(
|
||||
StatusCode::OK,
|
||||
[(header::CONTENT_TYPE, "text/x-nix-narinfo")],
|
||||
body,
|
||||
)
|
||||
.into_response()
|
||||
} else {
|
||||
(StatusCode::NOT_FOUND, "Not Found").into_response()
|
||||
}
|
||||
}
|
||||
+29
-82
@@ -32,99 +32,46 @@ in
|
||||
nh
|
||||
];
|
||||
|
||||
environment.etc =
|
||||
(lib.mapAttrs' (name: value: {
|
||||
environment.etc = (
|
||||
lib.mapAttrs' (name: value: {
|
||||
name = "nix/path/${name}";
|
||||
value.source = value.flake;
|
||||
}) config.nix.registry)
|
||||
// {
|
||||
"nix/nix-racer.toml".source = (pkgs.formats.toml { }).generate "nix-racer.toml" {
|
||||
listen = "127.0.0.1:2048";
|
||||
substituters = [
|
||||
# {
|
||||
# penalty = 0;
|
||||
# url = "https://mirror.sjtu.edu.cn/nix-channels/store";
|
||||
# }
|
||||
{
|
||||
penalty = 0;
|
||||
url = "https://mirrors.sjtug.sjtu.edu.cn/nix-channels/store";
|
||||
}
|
||||
{
|
||||
penalty = 50;
|
||||
url = "https://mirrors.ustc.edu.cn/nix-channels/store";
|
||||
}
|
||||
{
|
||||
penalty = 0;
|
||||
url = "https://nix-community.cachix.org";
|
||||
}
|
||||
{
|
||||
penalty = 0;
|
||||
url = "https://cache.numtide.com";
|
||||
}
|
||||
{
|
||||
penalty = 100;
|
||||
url = "https://cache.nixos.org";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}) config.nix.registry
|
||||
);
|
||||
|
||||
systemd.services.nix-racer = {
|
||||
description = "Nix substituter proxy with parallel cache queries and latency-aware selection";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${lib.getExe (pkgs.callPackage ./nix-racer/_package.nix { })}";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
|
||||
DynamicUser = true;
|
||||
CapabilityBoundingSet = [ "" ];
|
||||
DeviceAllow = "";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectSystem = "strict";
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
services.selector4nix = {
|
||||
enable = true;
|
||||
configureSubstituter = "keep";
|
||||
settings = {
|
||||
server.port = 5496;
|
||||
substituters = [
|
||||
{
|
||||
url = "https://mirror.sjtu.edu.cn/nix-channels/store";
|
||||
}
|
||||
{
|
||||
url = "https://mirrors.sjtug.sjtu.edu.cn/nix-channels/store";
|
||||
}
|
||||
{
|
||||
url = "https://mirrors.ustc.edu.cn/nix-channels/store";
|
||||
}
|
||||
{
|
||||
url = "https://nix-community.cachix.org";
|
||||
}
|
||||
{
|
||||
url = "https://cache.numtide.com";
|
||||
}
|
||||
{
|
||||
url = "https://cache.nixos.org";
|
||||
}
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@resources"
|
||||
"~@privileged"
|
||||
];
|
||||
UMask = "0077";
|
||||
};
|
||||
};
|
||||
|
||||
nix.settings = {
|
||||
experimental-features = "nix-command flakes pipe-operators";
|
||||
substituters = lib.mkForce [
|
||||
"http://127.0.0.1:2048"
|
||||
"https://mirrors.sjtug.sjtu.edu.cn/nix-channels/store"
|
||||
"https://cache.numtide.com"
|
||||
"http://127.0.0.1:${toString (config.services.selector4nix.settings.server.port or 5496)}"
|
||||
"https://cache.nixos.org"
|
||||
|
||||
# "https://mirrors.ustc.edu.cn/nix-channels/store"
|
||||
# "https://mirrors.sjtug.sjtu.edu.cn/nix-channels/store"
|
||||
# "https://nix-community.cachix.org"
|
||||
];
|
||||
trusted-public-keys = [
|
||||
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
|
||||
|
||||
Reference in New Issue
Block a user