feat: init
This commit is contained in:
commit
0d7d4cf662
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
/target
|
||||
|
||||
*.swp
|
||||
|
||||
result
|
||||
result-*
|
||||
|
||||
.pre-commit-config.yaml
|
4378
Cargo.lock
generated
Normal file
4378
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
24
Cargo.toml
Normal file
24
Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "multitier-tvix-cache"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0.204"
|
||||
tokio = { version = "1.38.1", features = ["full"] }
|
||||
tokio-stream = "0.1.15"
|
||||
tonic = "0.12.1"
|
||||
tower = "0.4.13"
|
||||
tower-http = "0.5.2"
|
||||
tracing = "0.1.40"
|
||||
tvix-castore = { git = "https://git.dgnum.eu/mdebray/tvl-depot", rev = "920b7118d5b0917e426367107f7b7b66089a8d7b" }
|
||||
tvix-store = { git = "https://git.dgnum.eu/mdebray/tvl-depot", rev = "920b7118d5b0917e426367107f7b7b66089a8d7b", features = ["xp-store-composition" ] }
|
||||
tvix-tracing = { git = "https://git.dgnum.eu/mdebray/tvl-depot", rev = "920b7118d5b0917e426367107f7b7b66089a8d7b" }
|
||||
nar-bridge = { git = "https://git.dgnum.eu/mdebray/tvl-depot", rev = "920b7118d5b0917e426367107f7b7b66089a8d7b" }
|
||||
url = "2.5.2"
|
||||
clap = "4.5.9"
|
||||
axum = "0.7.5"
|
||||
tokio-listener = { version = "0.4.3", features = ["serde"] }
|
||||
toml = "0.8.15"
|
||||
futures = "0.3.30"
|
||||
tonic-health = "0.12.1"
|
42
default.nix
Normal file
42
default.nix
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
sources ? import ./npins,
|
||||
pkgs ? import sources.nixpkgs { },
|
||||
}:
|
||||
let
|
||||
check = (import sources.git-hooks).run {
|
||||
src = ./.;
|
||||
|
||||
hooks = {
|
||||
# Nix Hooks
|
||||
statix.enable = true;
|
||||
deadnix.enable = true;
|
||||
rfc101 = {
|
||||
enable = true;
|
||||
|
||||
name = "RFC-101 formatting";
|
||||
entry = "${pkgs.lib.getExe pkgs.nixfmt-rfc-style}";
|
||||
files = "\\.nix$";
|
||||
};
|
||||
|
||||
# Misc Hooks
|
||||
commitizen.enable = true;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit pkgs;
|
||||
shell = pkgs.mkShell {
|
||||
name = "multitenant-tvix-binary-cache";
|
||||
buildInputs = check.enabledPackages ++ [
|
||||
pkgs.cargo
|
||||
pkgs.protobuf
|
||||
pkgs.rustc
|
||||
pkgs.rust-analyzer
|
||||
(pkgs.rustfmt.override { asNightly = true; })
|
||||
];
|
||||
shellHook = ''
|
||||
${check.shellHook}
|
||||
'';
|
||||
|
||||
};
|
||||
}
|
79
npins/default.nix
Normal file
79
npins/default.nix
Normal file
|
@ -0,0 +1,79 @@
|
|||
# Generated by npins. Do not modify; will be overwritten regularly
|
||||
let
|
||||
data = builtins.fromJSON (builtins.readFile ./sources.json);
|
||||
inherit (data) version;
|
||||
|
||||
mkSource =
|
||||
spec:
|
||||
assert spec ? type;
|
||||
let
|
||||
path =
|
||||
if spec.type == "Git" then
|
||||
mkGitSource spec
|
||||
else if spec.type == "GitRelease" then
|
||||
mkGitSource spec
|
||||
else if spec.type == "PyPi" then
|
||||
mkPyPiSource spec
|
||||
else if spec.type == "Channel" then
|
||||
mkChannelSource spec
|
||||
else
|
||||
builtins.throw "Unknown source type ${spec.type}";
|
||||
in
|
||||
spec // { outPath = path; };
|
||||
|
||||
mkGitSource =
|
||||
{
|
||||
repository,
|
||||
revision,
|
||||
url ? null,
|
||||
hash,
|
||||
...
|
||||
}:
|
||||
assert repository ? type;
|
||||
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
|
||||
# In the latter case, there we will always be an url to the tarball
|
||||
if url != null then
|
||||
(builtins.fetchTarball {
|
||||
inherit url;
|
||||
sha256 = hash; # FIXME: check nix version & use SRI hashes
|
||||
})
|
||||
else
|
||||
assert repository.type == "Git";
|
||||
let
|
||||
urlToName =
|
||||
url: rev:
|
||||
let
|
||||
matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
|
||||
|
||||
short = builtins.substring 0 7 rev;
|
||||
|
||||
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
|
||||
in
|
||||
"${if matched == null then "source" else builtins.head matched}${appendShort}";
|
||||
name = urlToName repository.url revision;
|
||||
in
|
||||
builtins.fetchGit {
|
||||
inherit (repository) url;
|
||||
rev = revision;
|
||||
inherit name;
|
||||
# hash = hash;
|
||||
};
|
||||
|
||||
mkPyPiSource =
|
||||
{ url, hash, ... }:
|
||||
builtins.fetchurl {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
};
|
||||
|
||||
mkChannelSource =
|
||||
{ url, hash, ... }:
|
||||
builtins.fetchTarball {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
};
|
||||
in
|
||||
if version == 3 then
|
||||
builtins.mapAttrs (_: mkSource) data.pins
|
||||
else
|
||||
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
|
23
npins/sources.json
Normal file
23
npins/sources.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"pins": {
|
||||
"git-hooks": {
|
||||
"type": "Git",
|
||||
"repository": {
|
||||
"type": "GitHub",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix"
|
||||
},
|
||||
"branch": "master",
|
||||
"revision": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07",
|
||||
"url": "https://github.com/cachix/git-hooks.nix/archive/0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07.tar.gz",
|
||||
"hash": "0bmgc731c5rvky6qxc4f6gvgyiic8dna5dv3j19kya86idf7wn0p"
|
||||
},
|
||||
"nixpkgs": {
|
||||
"type": "Channel",
|
||||
"name": "nixpkgs-unstable",
|
||||
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-24.11pre644361.1e3deb3d8a86/nixexprs.tar.xz",
|
||||
"hash": "0q8wrydwkyyjag9dz6mazmqnzw14jgg0vzj4n5zz94zq9fgnl8kc"
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
3
rustfmt.toml
Normal file
3
rustfmt.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
unstable_features = true
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Module"
|
20
src/config.rs
Normal file
20
src/config.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct EndpointsConfig(pub HashMap<tokio_listener::ListenerAddress, SingleEndpointConfig>);
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct SingleEndpointConfig {
|
||||
pub endpoint_type: EndpointType,
|
||||
pub blob_service: String,
|
||||
pub directory_service: String,
|
||||
pub path_info_service: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||
pub enum EndpointType {
|
||||
Http,
|
||||
Grpc,
|
||||
}
|
156
src/main.rs
Normal file
156
src/main.rs
Normal file
|
@ -0,0 +1,156 @@
|
|||
mod config;
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use config::{EndpointType, EndpointsConfig};
|
||||
use futures::future::join_all;
|
||||
use tonic::transport::Server;
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::trace::{DefaultMakeSpan, TraceLayer};
|
||||
use tracing::{info, Level};
|
||||
use tvix_castore::blobservice::BlobService;
|
||||
use tvix_castore::directoryservice::DirectoryService;
|
||||
use tvix_castore::proto::blob_service_server::BlobServiceServer;
|
||||
use tvix_castore::proto::directory_service_server::DirectoryServiceServer;
|
||||
use tvix_castore::proto::{GRPCBlobServiceWrapper, GRPCDirectoryServiceWrapper};
|
||||
use tvix_store::composition::{with_registry, Composition, REG};
|
||||
use tvix_store::nar::{NarCalculationService, SimpleRenderer};
|
||||
use tvix_store::pathinfoservice::PathInfoService;
|
||||
use tvix_store::proto::path_info_service_server::PathInfoServiceServer;
|
||||
use tvix_store::proto::GRPCPathInfoServiceWrapper;
|
||||
use tvix_store::utils::CompositionConfigs;
|
||||
use {nar_bridge, toml};
|
||||
|
||||
/// Expose the Nix HTTP Binary Cache protocol for a tvix-store.
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[arg(long, env)]
|
||||
endpoints_config: String,
|
||||
|
||||
#[arg(long, env)]
|
||||
store_composition: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let _tracing_handle = {
|
||||
let builder = tvix_tracing::TracingBuilder::default();
|
||||
builder.build()?
|
||||
};
|
||||
|
||||
let composition_text = tokio::fs::read_to_string(cli.store_composition).await?;
|
||||
let configs: CompositionConfigs = with_registry(®, || toml::from_str(&composition_text))?;
|
||||
|
||||
let endpoints_text = tokio::fs::read_to_string(cli.endpoints_config).await?;
|
||||
let endpoints: EndpointsConfig = toml::from_str(&endpoints_text)?;
|
||||
|
||||
info!("Starting multitenant-tvix-cache...");
|
||||
|
||||
let mut comp = Composition::default();
|
||||
|
||||
comp.extend(configs.blobservices);
|
||||
comp.extend(configs.directoryservices);
|
||||
comp.extend(configs.pathinfoservices);
|
||||
|
||||
let mut services: Vec<
|
||||
Pin<Box<dyn Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>>>>,
|
||||
> = Vec::with_capacity(endpoints.0.len());
|
||||
for (k, v) in endpoints.0.iter() {
|
||||
if v.endpoint_type == EndpointType::Http {
|
||||
let router = nar_bridge::gen_router(39)
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(
|
||||
TraceLayer::new_for_http().make_span_with(
|
||||
DefaultMakeSpan::new()
|
||||
.level(tracing::Level::INFO)
|
||||
.include_headers(true),
|
||||
),
|
||||
)
|
||||
.map_request(tvix_tracing::propagate::axum::accept_trace),
|
||||
)
|
||||
.with_state(nar_bridge::AppState::new(
|
||||
comp.build(v.blob_service.as_ref()).await?,
|
||||
comp.build(v.directory_service.as_ref()).await?,
|
||||
comp.build(v.path_info_service.as_ref()).await?,
|
||||
));
|
||||
|
||||
// TODO: Tracing ?
|
||||
|
||||
services.push(Box::pin(async {
|
||||
let listener = build_listener(k.clone())
|
||||
.await
|
||||
.map_err(|e| Box::<dyn std::error::Error + Send + Sync>::from(e.to_string()))?;
|
||||
tokio_listener::axum07::serve(listener, router.into_make_service())
|
||||
.await
|
||||
.map_err(|e| Box::<dyn std::error::Error + Sync + Send>::from(e.to_string()))
|
||||
}));
|
||||
} else {
|
||||
let blob_service: Arc<dyn BlobService> = comp.build(v.blob_service.as_ref()).await?;
|
||||
let directory_service: Arc<dyn DirectoryService> =
|
||||
comp.build(v.directory_service.as_ref()).await?;
|
||||
let path_info_service: Arc<dyn PathInfoService> =
|
||||
comp.build(v.path_info_service.as_ref()).await?;
|
||||
|
||||
let nar_calculation_service: Box<dyn NarCalculationService> = path_info_service
|
||||
.nar_calculation_service()
|
||||
.unwrap_or_else(|| {
|
||||
Box::new(SimpleRenderer::new(
|
||||
blob_service.clone(),
|
||||
directory_service.clone(),
|
||||
))
|
||||
});
|
||||
|
||||
let mut server = Server::builder().layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(
|
||||
TraceLayer::new_for_grpc().make_span_with(
|
||||
DefaultMakeSpan::new()
|
||||
.level(Level::INFO)
|
||||
.include_headers(true),
|
||||
),
|
||||
)
|
||||
.map_request(tvix_tracing::propagate::tonic::accept_trace),
|
||||
);
|
||||
|
||||
let (_health_reporter, health_service) = tonic_health::server::health_reporter();
|
||||
|
||||
let router = server
|
||||
.add_service(health_service)
|
||||
.add_service(BlobServiceServer::new(GRPCBlobServiceWrapper::new(
|
||||
blob_service,
|
||||
)))
|
||||
.add_service(DirectoryServiceServer::new(
|
||||
GRPCDirectoryServiceWrapper::new(directory_service),
|
||||
))
|
||||
.add_service(PathInfoServiceServer::new(GRPCPathInfoServiceWrapper::new(
|
||||
path_info_service,
|
||||
nar_calculation_service,
|
||||
)));
|
||||
|
||||
services.push(Box::pin(async {
|
||||
let listener = build_listener(k.clone())
|
||||
.await
|
||||
.map_err(|e| Box::<dyn std::error::Error + Send + Sync>::from(e.to_string()))?;
|
||||
router
|
||||
.serve_with_incoming(listener)
|
||||
.await
|
||||
.map_err(|e| Box::<dyn std::error::Error + Send + Sync>::from(e.to_string()))
|
||||
}));
|
||||
}
|
||||
}
|
||||
join_all(services).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn build_listener(
|
||||
listen_address: tokio_listener::ListenerAddress,
|
||||
) -> Result<tokio_listener::Listener, std::io::Error> {
|
||||
tokio_listener::Listener::bind(&listen_address, &Default::default(), &Default::default()).await
|
||||
}
|
Loading…
Reference in a new issue