mirror of
https://github.com/getnora-io/nora.git
synced 2026-04-12 10:20:32 +00:00
chore: remove unused crates and demo traffic scripts
- Remove nora-cli (unimplemented stub) - Remove nora-storage (standalone S3 server, not used) - Remove demo traffic generator and systemd service
This commit is contained in:
@@ -1,83 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Demo traffic simulator for NORA registry
|
||||
# Generates random registry activity for dashboard demo
|
||||
|
||||
REGISTRY="http://localhost:4000"
|
||||
LOG_FILE="/var/log/nora-demo-traffic.log"
|
||||
|
||||
# Sample packages to fetch
|
||||
NPM_PACKAGES=("lodash" "express" "react" "axios" "moment" "underscore" "chalk" "debug")
|
||||
MAVEN_ARTIFACTS=(
|
||||
"org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.pom"
|
||||
"com/google/guava/guava/31.1-jre/guava-31.1-jre.pom"
|
||||
"org/slf4j/slf4j-api/2.0.0/slf4j-api-2.0.0.pom"
|
||||
)
|
||||
DOCKER_IMAGES=("alpine" "busybox" "hello-world")
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Random sleep between min and max seconds
|
||||
random_sleep() {
|
||||
local min=$1
|
||||
local max=$2
|
||||
local delay=$((RANDOM % (max - min + 1) + min))
|
||||
sleep $delay
|
||||
}
|
||||
|
||||
# Fetch random npm package
|
||||
fetch_npm() {
|
||||
local pkg=${NPM_PACKAGES[$RANDOM % ${#NPM_PACKAGES[@]}]}
|
||||
log "NPM: fetching $pkg"
|
||||
curl -s "$REGISTRY/npm/$pkg" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# Fetch random maven artifact
|
||||
fetch_maven() {
|
||||
local artifact=${MAVEN_ARTIFACTS[$RANDOM % ${#MAVEN_ARTIFACTS[@]}]}
|
||||
log "MAVEN: fetching $artifact"
|
||||
curl -s "$REGISTRY/maven2/$artifact" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# Docker push/pull cycle
|
||||
docker_cycle() {
|
||||
local img=${DOCKER_IMAGES[$RANDOM % ${#DOCKER_IMAGES[@]}]}
|
||||
local tag="demo-$(date +%s)"
|
||||
|
||||
log "DOCKER: push/pull cycle for $img"
|
||||
|
||||
# Tag and push
|
||||
docker tag "$img:latest" "localhost:4000/demo/$img:$tag" 2>/dev/null
|
||||
docker push "localhost:4000/demo/$img:$tag" > /dev/null 2>&1
|
||||
|
||||
# Pull back
|
||||
docker rmi "localhost:4000/demo/$img:$tag" > /dev/null 2>&1
|
||||
docker pull "localhost:4000/demo/$img:$tag" > /dev/null 2>&1
|
||||
|
||||
# Cleanup
|
||||
docker rmi "localhost:4000/demo/$img:$tag" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# Main loop
|
||||
log "Starting demo traffic simulator"
|
||||
|
||||
while true; do
|
||||
# Random operation
|
||||
op=$((RANDOM % 10))
|
||||
|
||||
case $op in
|
||||
0|1|2|3) # 40% npm
|
||||
fetch_npm
|
||||
;;
|
||||
4|5|6) # 30% maven
|
||||
fetch_maven
|
||||
;;
|
||||
7|8|9) # 30% docker
|
||||
docker_cycle
|
||||
;;
|
||||
esac
|
||||
|
||||
# Random delay: 30-120 seconds
|
||||
random_sleep 30 120
|
||||
done
|
||||
@@ -1,15 +0,0 @@
|
||||
[Unit]
|
||||
Description=NORA Demo Traffic Simulator
|
||||
After=docker.service
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/opt/nora/demo-traffic.sh
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "nora-cli"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
description = "CLI tool for NORA registry"
|
||||
|
||||
[[bin]]
|
||||
name = "nora-cli"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
tokio.workspace = true
|
||||
reqwest.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
indicatif = "0.18"
|
||||
tar = "0.4"
|
||||
flate2 = "1.1"
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright (c) 2026 Volkov Pavel | DevITWay
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "nora-cli")]
|
||||
#[command(about = "CLI tool for Nora registry")]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Login to a registry
|
||||
Login {
|
||||
#[arg(long)]
|
||||
registry: String,
|
||||
#[arg(short, long)]
|
||||
username: String,
|
||||
},
|
||||
/// Push an artifact
|
||||
Push {
|
||||
#[arg(long)]
|
||||
registry: String,
|
||||
path: String,
|
||||
},
|
||||
/// Pull an artifact
|
||||
Pull {
|
||||
#[arg(long)]
|
||||
registry: String,
|
||||
artifact: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Commands::Login { registry, username } => {
|
||||
println!("Logging in to {} as {}", registry, username);
|
||||
// TODO: implement
|
||||
}
|
||||
Commands::Push { registry, path } => {
|
||||
println!("Pushing {} to {}", path, registry);
|
||||
// TODO: implement
|
||||
}
|
||||
Commands::Pull { registry, artifact } => {
|
||||
println!("Pulling {} from {}", artifact, registry);
|
||||
// TODO: implement
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
[package]
|
||||
name = "nora-storage"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
description = "S3-compatible storage server for NORA"
|
||||
|
||||
[[bin]]
|
||||
name = "nora-storage"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
tokio.workspace = true
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
toml = "1.0"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
sha2 = "0.10"
|
||||
base64 = "0.22"
|
||||
httpdate = "1"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
quick-xml = { version = "0.39", features = ["serialize"] }
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright (c) 2026 Volkov Pavel | DevITWay
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub server: ServerConfig,
|
||||
pub storage: StorageConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerConfig {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StorageConfig {
|
||||
pub data_dir: String,
|
||||
pub max_body_size: usize,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Self {
|
||||
fs::read_to_string("config.toml")
|
||||
.ok()
|
||||
.and_then(|content| toml::from_str(&content).ok())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
server: ServerConfig {
|
||||
host: String::from("127.0.0.1"),
|
||||
port: 3000,
|
||||
},
|
||||
storage: StorageConfig {
|
||||
data_dir: String::from("data"),
|
||||
max_body_size: 1024 * 1024 * 1024, // 1GB
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
// Copyright (c) 2026 Volkov Pavel | DevITWay
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
mod config;
|
||||
|
||||
use axum::extract::DefaultBodyLimit;
|
||||
use axum::{
|
||||
body::Bytes,
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
routing::{delete, get, put},
|
||||
Router,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use config::Config;
|
||||
use quick_xml::se::to_string as to_xml;
|
||||
use serde::Serialize;
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
use tracing::info;
|
||||
|
||||
pub struct AppState {
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename = "ListAllMyBucketsResult")]
|
||||
struct ListBucketsResult {
|
||||
#[serde(rename = "Buckets")]
|
||||
buckets: Buckets,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Buckets {
|
||||
#[serde(rename = "Bucket")]
|
||||
bucket: Vec<BucketInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct BucketInfo {
|
||||
#[serde(rename = "Name")]
|
||||
name: String,
|
||||
#[serde(rename = "CreationDate")]
|
||||
creation_date: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename = "ListBucketResult")]
|
||||
struct ListObjectsResult {
|
||||
#[serde(rename = "Name")]
|
||||
name: String,
|
||||
#[serde(rename = "Contents")]
|
||||
contents: Vec<ObjectInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ObjectInfo {
|
||||
#[serde(rename = "Key")]
|
||||
key: String,
|
||||
#[serde(rename = "Size")]
|
||||
size: u64,
|
||||
#[serde(rename = "LastModified")]
|
||||
last_modified: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename = "Error")]
|
||||
struct S3Error {
|
||||
#[serde(rename = "Code")]
|
||||
code: String,
|
||||
#[serde(rename = "Message")]
|
||||
message: String,
|
||||
}
|
||||
|
||||
fn xml_response<T: Serialize>(data: T) -> Response {
|
||||
let xml = format!(
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n{}",
|
||||
to_xml(&data).unwrap_or_default()
|
||||
);
|
||||
(
|
||||
StatusCode::OK,
|
||||
[(axum::http::header::CONTENT_TYPE, "application/xml")],
|
||||
xml,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
fn error_response(status: StatusCode, code: &str, message: &str) -> Response {
|
||||
let error = S3Error {
|
||||
code: code.to_string(),
|
||||
message: message.to_string(),
|
||||
};
|
||||
let xml = format!(
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n{}",
|
||||
to_xml(&error).unwrap_or_default()
|
||||
);
|
||||
(
|
||||
status,
|
||||
[(axum::http::header::CONTENT_TYPE, "application/xml")],
|
||||
xml,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::from_default_env()
|
||||
.add_directive("nora_storage=info".parse().expect("valid directive")),
|
||||
)
|
||||
.init();
|
||||
|
||||
let config = Config::load();
|
||||
fs::create_dir_all(&config.storage.data_dir).expect("Failed to create data directory");
|
||||
|
||||
let state = Arc::new(AppState {
|
||||
config: config.clone(),
|
||||
});
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(list_buckets))
|
||||
.route("/{bucket}", get(list_objects))
|
||||
.route("/{bucket}", put(create_bucket))
|
||||
.route("/{bucket}", delete(delete_bucket))
|
||||
.route("/{bucket}/{*key}", put(put_object))
|
||||
.route("/{bucket}/{*key}", get(get_object))
|
||||
.route("/{bucket}/{*key}", delete(delete_object))
|
||||
.layer(DefaultBodyLimit::max(config.storage.max_body_size))
|
||||
.with_state(state);
|
||||
|
||||
let addr = format!("{}:{}", config.server.host, config.server.port);
|
||||
let listener = tokio::net::TcpListener::bind(&addr)
|
||||
.await
|
||||
.expect("Failed to bind to address");
|
||||
|
||||
info!("nora-storage (S3 compatible) running on http://{}", addr);
|
||||
axum::serve(listener, app).await.expect("Server error");
|
||||
}
|
||||
|
||||
async fn list_buckets(State(state): State<Arc<AppState>>) -> Response {
|
||||
let data_dir = &state.config.storage.data_dir;
|
||||
let entries = match fs::read_dir(data_dir) {
|
||||
Ok(e) => e,
|
||||
Err(_) => {
|
||||
return error_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"InternalError",
|
||||
"Failed to read data",
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let bucket_list: Vec<BucketInfo> = entries
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.path().is_dir())
|
||||
.filter_map(|e| {
|
||||
let name = e.file_name().into_string().ok()?;
|
||||
let modified = e.metadata().ok()?.modified().ok()?;
|
||||
let datetime: chrono::DateTime<Utc> = modified.into();
|
||||
Some(BucketInfo {
|
||||
name,
|
||||
creation_date: datetime.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
xml_response(ListBucketsResult {
|
||||
buckets: Buckets {
|
||||
bucket: bucket_list,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async fn list_objects(State(state): State<Arc<AppState>>, Path(bucket): Path<String>) -> Response {
|
||||
let bucket_path = format!("{}/{}", state.config.storage.data_dir, bucket);
|
||||
|
||||
if !std::path::Path::new(&bucket_path).is_dir() {
|
||||
return error_response(
|
||||
StatusCode::NOT_FOUND,
|
||||
"NoSuchBucket",
|
||||
"The specified bucket does not exist",
|
||||
);
|
||||
}
|
||||
|
||||
let objects = collect_files(std::path::Path::new(&bucket_path), "");
|
||||
xml_response(ListObjectsResult {
|
||||
name: bucket,
|
||||
contents: objects,
|
||||
})
|
||||
}
|
||||
|
||||
fn collect_files(dir: &std::path::Path, prefix: &str) -> Vec<ObjectInfo> {
|
||||
let mut objects = Vec::new();
|
||||
if let Ok(entries) = fs::read_dir(dir) {
|
||||
for entry in entries.filter_map(|e| e.ok()) {
|
||||
let path = entry.path();
|
||||
let name = entry.file_name().into_string().unwrap_or_default();
|
||||
let key = if prefix.is_empty() {
|
||||
name.clone()
|
||||
} else {
|
||||
format!("{}/{}", prefix, name)
|
||||
};
|
||||
|
||||
if path.is_dir() {
|
||||
objects.extend(collect_files(&path, &key));
|
||||
} else if let Ok(metadata) = entry.metadata() {
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
let datetime: chrono::DateTime<Utc> = modified.into();
|
||||
objects.push(ObjectInfo {
|
||||
key,
|
||||
size: metadata.len(),
|
||||
last_modified: datetime.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
objects
|
||||
}
|
||||
|
||||
async fn create_bucket(State(state): State<Arc<AppState>>, Path(bucket): Path<String>) -> Response {
|
||||
let bucket_path = format!("{}/{}", state.config.storage.data_dir, bucket);
|
||||
match fs::create_dir(&bucket_path) {
|
||||
Ok(_) => (StatusCode::OK, "").into_response(),
|
||||
Err(_) => error_response(
|
||||
StatusCode::CONFLICT,
|
||||
"BucketAlreadyExists",
|
||||
"Bucket already exists",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn put_object(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path((bucket, key)): Path<(String, String)>,
|
||||
body: Bytes,
|
||||
) -> Response {
|
||||
let file_path = format!("{}/{}/{}", state.config.storage.data_dir, bucket, key);
|
||||
|
||||
if let Some(parent) = std::path::Path::new(&file_path).parent() {
|
||||
let _ = fs::create_dir_all(parent);
|
||||
}
|
||||
|
||||
match fs::write(&file_path, &body) {
|
||||
Ok(_) => {
|
||||
println!("PUT {}/{} ({} bytes)", bucket, key, body.len());
|
||||
(StatusCode::OK, "").into_response()
|
||||
}
|
||||
Err(e) => {
|
||||
println!("ERROR writing {}/{}: {}", bucket, key, e);
|
||||
error_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"InternalError",
|
||||
"Failed to write object",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_object(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path((bucket, key)): Path<(String, String)>,
|
||||
) -> Response {
|
||||
let file_path = format!("{}/{}/{}", state.config.storage.data_dir, bucket, key);
|
||||
|
||||
match fs::read(&file_path) {
|
||||
Ok(data) => (StatusCode::OK, data).into_response(),
|
||||
Err(_) => error_response(
|
||||
StatusCode::NOT_FOUND,
|
||||
"NoSuchKey",
|
||||
"The specified key does not exist",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_object(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path((bucket, key)): Path<(String, String)>,
|
||||
) -> Response {
|
||||
let file_path = format!("{}/{}/{}", state.config.storage.data_dir, bucket, key);
|
||||
|
||||
match fs::remove_file(&file_path) {
|
||||
Ok(_) => {
|
||||
println!("DELETE {}/{}", bucket, key);
|
||||
(StatusCode::NO_CONTENT, "").into_response()
|
||||
}
|
||||
Err(_) => error_response(
|
||||
StatusCode::NOT_FOUND,
|
||||
"NoSuchKey",
|
||||
"The specified key does not exist",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_bucket(State(state): State<Arc<AppState>>, Path(bucket): Path<String>) -> Response {
|
||||
let bucket_path = format!("{}/{}", state.config.storage.data_dir, bucket);
|
||||
|
||||
match fs::remove_dir(&bucket_path) {
|
||||
Ok(_) => {
|
||||
println!("DELETE bucket {}", bucket);
|
||||
(StatusCode::NO_CONTENT, "").into_response()
|
||||
}
|
||||
Err(_) => error_response(
|
||||
StatusCode::CONFLICT,
|
||||
"BucketNotEmpty",
|
||||
"The bucket is not empty",
|
||||
),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user