/ OPTIMIZATION_GUIDE.md
OPTIMIZATION_GUIDE.md
   1  # Radicle Secure - Optimization & Security Implementation Guide
   2  
   3  **Project:** Radicle Heartwood Fork with Security Enhancements
   4  **Goal:** Add secret scanning, vulnerability detection, and reduce hosting costs by 70-85%
   5  **Target Cost:** $3-8/month (from $30/month baseline)
   6  
   7  ---
   8  
   9  ## Table of Contents
  10  
  11  1. [Project Overview](#project-overview)
  12  2. [Adding Security Features](#adding-security-features)
  13  3. [Hosting Cost Breakdown Analysis](#hosting-cost-breakdown-analysis)
  14  4. [Optimization Strategies](#optimization-strategies)
  15  5. [Implementation Roadmap](#implementation-roadmap)
  16  6. [Deployment Guide](#deployment-guide)
  17  7. [Monitoring & Maintenance](#monitoring--maintenance)
  18  
  19  ---
  20  
  21  ## Project Overview
  22  
  23  ### Repository Structure
  24  
  25  ```
  26  radicle-secure/
  27  ├── crates/
  28  │   ├── radicle/              # Core library
  29  │   ├── radicle-cli/          # Command-line interface
  30  │   ├── radicle-node/         # P2P network daemon
  31  │   ├── radicle-remote-helper/# Git remote helper
  32  │   └── ...                   # Other supporting crates
  33  ├── Cargo.toml                # Workspace configuration
  34  └── systemd/                  # Systemd unit files
  35  ```
  36  
  37  ### Key Components
  38  
  39  - **radicle-cli**: User-facing CLI tool
  40  - **radicle-node**: Always-on P2P daemon (optimization target)
  41  - **radicle-httpd**: HTTP API gateway (if added)
  42  - **rad-web**: Web interface (deploy separately to reduce costs)
  43  
  44  ### License
  45  
  46  MIT OR Apache-2.0 (fully permissive - fork-friendly)
  47  
  48  ---
  49  
  50  ## Adding Security Features
  51  
  52  ### 1. Secret Scanning Integration
  53  
  54  **Available Rust Libraries:**
  55  
  56  | Library    | Speed                        | Accuracy                | Integration Complexity |
  57  |------------|------------------------------|-------------------------|------------------------|
  58  | ripsecrets | 95x faster than alternatives | High                    | Low - simple CLI       |
  59  | secretscan | 51,020 files/sec             | 99% detection           | Low - library API      |
  60  | guardy     | Multi-threaded               | High (entropy analysis) | Medium - git hooks     |
  61  
  62  #### Implementation Approach
  63  
  64  ```rust
  65  // Create: crates/radicle/src/security/mod.rs
  66  pub mod secrets;
  67  pub mod vulnerabilities;
  68  pub mod compression;
  69  
  70  // Create: crates/radicle/src/security/secrets.rs
  71  use secretscan::{Scanner, SecretMatch};
  72  use std::path::Path;
  73  
  74  pub struct SecretScanner {
  75      scanner: Scanner,
  76  }
  77  
  78  impl SecretScanner {
  79      pub fn new() -> Self {
  80          Self {
  81              scanner: Scanner::default(),
  82          }
  83      }
  84  
  85      /// Scan a git diff for secrets
  86      pub fn scan_diff(&self, diff: &str) -> Result<Vec<SecretMatch>, Error> {
  87          self.scanner.scan_text(diff)
  88      }
  89  
  90      /// Scan a file for secrets
  91      pub fn scan_file(&self, path: &Path) -> Result<Vec<SecretMatch>, Error> {
  92          self.scanner.scan_file(path)
  93      }
  94  
  95      /// Scan an entire repository
  96      pub fn scan_repository(&self, repo_path: &Path) -> Result<Vec<SecretMatch>, Error> {
  97          self.scanner.scan_directory(repo_path)
  98      }
  99  }
 100  ```
 101  
 102  #### Integration Points
 103  
 104  **A. Pre-commit hooks in radicle-cli**
 105  
 106  ```rust
 107  // Modify: crates/radicle-cli/src/commands/push.rs
 108  use radicle::security::secrets::SecretScanner;
 109  
 110  pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
 111      // Existing code...
 112  
 113      // Add secret scanning before push
 114      let scanner = SecretScanner::new();
 115      let commits = get_new_commits(&repo)?;
 116  
 117      for commit in commits {
 118          let diff = commit.diff()?;
 119          if let Ok(secrets) = scanner.scan_diff(&diff) {
 120              if !secrets.is_empty() {
 121                  term::error(format!("Found {} potential secrets in commit {}",
 122                      secrets.len(), commit.id()));
 123                  for secret in secrets {
 124                      term::warning(format!("  {} at line {}", secret.kind, secret.line));
 125                  }
 126                  return Err(anyhow!("Commit contains potential secrets. Aborting."));
 127              }
 128          }
 129      }
 130  
 131      // Continue with push...
 132  }
 133  ```
 134  
 135  **B. Real-time scanning in radicle-node during replication**
 136  
 137  ```rust
 138  // Modify: crates/radicle-node/src/service/sync.rs
 139  use radicle::security::secrets::SecretScanner;
 140  
 141  async fn sync_repository(&self, repo: &Repository) -> Result<(), Error> {
 142      let scanner = SecretScanner::new();
 143  
 144      // Scan incoming commits during replication
 145      for commit in repo.new_commits() {
 146          let diff = commit.diff()?;
 147          if let Ok(secrets) = scanner.scan_diff(&diff) {
 148              if !secrets.is_empty() {
 149                  log::warn!("Found {} potential secrets in replicated commit {}",
 150                      secrets.len(), commit.id());
 151                  // Optionally: quarantine, reject, or alert
 152              }
 153          }
 154      }
 155  
 156      // Continue sync...
 157      Ok(())
 158  }
 159  ```
 160  
 161  **C. API endpoint for on-demand scans**
 162  
 163  ```rust
 164  // Create: crates/radicle-node/src/api/security.rs
 165  use radicle::security::secrets::SecretScanner;
 166  use axum::{Json, extract::Path};
 167  
 168  #[derive(Deserialize)]
 169  pub struct ScanRequest {
 170      repo_id: String,
 171      commit_id: Option<String>,
 172  }
 173  
 174  pub async fn scan_repository(
 175      Path(repo_id): Path<String>,
 176  ) -> Result<Json<ScanResult>, Error> {
 177      let scanner = SecretScanner::new();
 178      let repo = Repository::open(&repo_id)?;
 179  
 180      let secrets = scanner.scan_repository(repo.path())?;
 181  
 182      Ok(Json(ScanResult {
 183          total_secrets: secrets.len(),
 184          secrets: secrets.into_iter().map(|s| s.into()).collect(),
 185      }))
 186  }
 187  ```
 188  
 189  ---
 190  
 191  ### 2. Vulnerability Detection Integration
 192  
 193  #### RustSec Integration
 194  
 195  ```rust
 196  // Create: crates/radicle/src/security/vulnerabilities.rs
 197  use rustsec::{Database, Lockfile, Warning};
 198  use std::path::Path;
 199  
 200  pub struct VulnerabilityScanner {
 201      db: Database,
 202  }
 203  
 204  impl VulnerabilityScanner {
 205      pub fn new() -> Result<Self, Error> {
 206          let db = Database::fetch()?;
 207          Ok(Self { db })
 208      }
 209  
 210      /// Scan a Cargo.lock file for known vulnerabilities
 211      pub fn scan_cargo_lock(&self, lockfile_path: &Path) -> Result<ScanReport, Error> {
 212          let lockfile = Lockfile::load(lockfile_path)?;
 213          let warnings = rustsec::Warning::load_from_lockfile(&lockfile, &self.db)?;
 214  
 215          Ok(ScanReport {
 216              vulnerabilities: warnings.vulnerabilities.len(),
 217              warnings: warnings.warnings.len(),
 218              details: warnings,
 219          })
 220      }
 221  
 222      /// Scan all Cargo.lock files in a repository
 223      pub fn scan_repository(&self, repo_path: &Path) -> Result<Vec<ScanReport>, Error> {
 224          let mut reports = Vec::new();
 225  
 226          for entry in walkdir::WalkDir::new(repo_path)
 227              .follow_links(true)
 228              .into_iter()
 229              .filter_map(|e| e.ok())
 230          {
 231              if entry.file_name() == "Cargo.lock" {
 232                  if let Ok(report) = self.scan_cargo_lock(entry.path()) {
 233                      reports.push(report);
 234                  }
 235              }
 236          }
 237  
 238          Ok(reports)
 239      }
 240  }
 241  
 242  #[derive(Debug, Serialize)]
 243  pub struct ScanReport {
 244      pub vulnerabilities: usize,
 245      pub warnings: usize,
 246      pub details: Warning,
 247  }
 248  ```
 249  
 250  #### Multi-Language Support (Trivy Integration)
 251  
 252  ```rust
 253  // Create: crates/radicle/src/security/trivy.rs
 254  use std::process::Command;
 255  use serde_json::Value;
 256  
 257  pub struct TrivyScanner {
 258      trivy_path: PathBuf,
 259  }
 260  
 261  impl TrivyScanner {
 262      pub fn new() -> Result<Self, Error> {
 263          // Check if trivy is installed
 264          let trivy_path = which::which("trivy")?;
 265          Ok(Self { trivy_path })
 266      }
 267  
 268      /// Scan a filesystem for vulnerabilities
 269      pub fn scan_filesystem(&self, path: &Path) -> Result<TrivyReport, Error> {
 270          let output = Command::new(&self.trivy_path)
 271              .args(&["fs", "--format", "json", path.to_str().unwrap()])
 272              .output()?;
 273  
 274          let report: Value = serde_json::from_slice(&output.stdout)?;
 275          Ok(TrivyReport::from_json(report))
 276      }
 277  }
 278  ```
 279  
 280  ---
 281  
 282  ## Hosting Cost Breakdown Analysis
 283  
 284  ### Where Costs Come From
 285  
 286  | Component          | Cost Driver               | % of Total | Optimization Potential |
 287  |--------------------|---------------------------|------------|------------------------|
 288  | **Compute (VM)**   | vCPU + RAM always running | 40-50%     | ⭐⭐⭐⭐⭐ High             |
 289  | **Storage**        | SSD persistent disks      | 15-20%     | ⭐⭐⭐ Medium             |
 290  | **Bandwidth (Egress)** | Data transfer out     | 25-35%     | ⭐⭐⭐⭐ High              |
 291  | **Static IP**      | Reserved address          | 5-10%      | ⭐ Low                  |
 292  | **Snapshots/Backups** | Optional redundancy    | 5-10%      | ⭐⭐⭐ Medium             |
 293  
 294  ### Detailed Cost Breakdown (e2-small example)
 295  
 296  ```
 297  Monthly Costs:
 298  ├─ Compute: $12.23 (730 hrs × $0.01675/hr)
 299  ├─ Storage: $3.20 (20 GB × $0.16/GB)
 300  ├─ Egress: $12.00 (100 GB × $0.12/GB)
 301  ├─ Static IP: $2.88 (when not attached to running instance)
 302  └─ Total: ~$30.31/month
 303  ```
 304  
 305  **Key Insight:** Bandwidth egress is surprisingly expensive and scales with usage!
 306  
 307  ---
 308  
 309  ## Optimization Strategies
 310  
 311  ### 1. Compute Optimization (Reduce VM costs by 60-80%)
 312  
 313  #### A. On-Demand Architecture
 314  
 315  **Current:** radicle-node runs 24/7
 316  **Optimized:** Socket activation + smart sleep
 317  
 318  ```rust
 319  // Modify: crates/radicle-node/src/main.rs
 320  use std::time::{Duration, Instant};
 321  use tokio::sync::RwLock;
 322  use std::sync::Arc;
 323  
 324  struct IdleMonitor {
 325      last_activity: Arc<RwLock<Instant>>,
 326      idle_timeout: Duration,
 327  }
 328  
 329  impl IdleMonitor {
 330      fn new(idle_timeout: Duration) -> Self {
 331          Self {
 332              last_activity: Arc::new(RwLock::new(Instant::now())),
 333              idle_timeout,
 334          }
 335      }
 336  
 337      async fn record_activity(&self) {
 338          let mut last = self.last_activity.write().await;
 339          *last = Instant::now();
 340      }
 341  
 342      async fn check_idle(&self) -> bool {
 343          let last = self.last_activity.read().await;
 344          last.elapsed() > self.idle_timeout
 345      }
 346  }
 347  
 348  #[tokio::main]
 349  async fn main() -> anyhow::Result<()> {
 350      let node = Node::new()?;
 351      let monitor = IdleMonitor::new(Duration::from_secs(600)); // 10 min timeout
 352  
 353      // Clone for idle monitoring task
 354      let monitor_clone = Arc::new(monitor);
 355      let node_clone = node.clone();
 356  
 357      // Spawn idle monitor
 358      tokio::spawn(async move {
 359          loop {
 360              tokio::time::sleep(Duration::from_secs(60)).await;
 361  
 362              if monitor_clone.check_idle().await {
 363                  log::info!("Node idle for 10 minutes, initiating shutdown...");
 364  
 365                  // Persist state to disk
 366                  if let Err(e) = node_clone.persist_state().await {
 367                      log::error!("Failed to persist state: {}", e);
 368                  }
 369  
 370                  // Graceful shutdown
 371                  node_clone.shutdown().await;
 372                  std::process::exit(0);
 373              }
 374          }
 375      });
 376  
 377      // Start node with activity tracking
 378      node.serve_with_monitor(monitor_clone).await?;
 379  
 380      Ok(())
 381  }
 382  ```
 383  
 384  **Cost Impact:**
 385  - From 24/7 uptime → ~4-8 hrs/day active
 386  - **Savings: ~70%** ($12/mo → $4/mo)
 387  
 388  #### B. Systemd Socket Activation
 389  
 390  ```ini
 391  # Create: systemd/radicle-node.socket
 392  [Unit]
 393  Description=Radicle Node Socket
 394  PartOf=radicle-node.service
 395  
 396  [Socket]
 397  ListenStream=8776
 398  Accept=false
 399  
 400  [Install]
 401  WantedBy=sockets.target
 402  ```
 403  
 404  ```ini
 405  # Modify: systemd/radicle-node.service
 406  [Unit]
 407  Description=Radicle Node
 408  Requires=radicle-node.socket
 409  After=network.target radicle-node.socket
 410  
 411  [Service]
 412  Type=notify
 413  ExecStart=/usr/local/bin/radicle-node
 414  Restart=on-failure
 415  
 416  # Auto-shutdown after idle
 417  TimeoutStopSec=10
 418  KillMode=mixed
 419  
 420  [Install]
 421  WantedBy=multi-user.target
 422  ```
 423  
 424  ---
 425  
 426  ### 2. Storage Optimization (Reduce by 40-50%)
 427  
 428  #### A. Compression Middleware
 429  
 430  ```rust
 431  // Create: crates/radicle/src/security/compression.rs
 432  use zstd::stream::{encode_all, decode_all};
 433  use std::io::{Read, Write};
 434  
 435  pub struct CompressionLayer {
 436      level: i32,
 437  }
 438  
 439  impl CompressionLayer {
 440      pub fn new(level: i32) -> Self {
 441          Self { level }
 442      }
 443  
 444      /// Compress data using zstd
 445      pub fn compress(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
 446          Ok(encode_all(data, self.level)?)
 447      }
 448  
 449      /// Decompress zstd data
 450      pub fn decompress(&self, compressed: &[u8]) -> Result<Vec<u8>, Error> {
 451          Ok(decode_all(compressed)?)
 452      }
 453  }
 454  
 455  // Usage in storage layer
 456  // Modify: crates/radicle/src/storage/git.rs
 457  use crate::security::compression::CompressionLayer;
 458  
 459  impl Storage {
 460      pub fn store_object_compressed(&self, oid: Oid, data: &[u8]) -> Result<(), Error> {
 461          let compressor = CompressionLayer::new(3); // Level 3 for speed/ratio balance
 462          let compressed = compressor.compress(data)?;
 463  
 464          // Store compressed version
 465          self.write_object(oid, &compressed)?;
 466          Ok(())
 467      }
 468  
 469      pub fn read_object_compressed(&self, oid: Oid) -> Result<Vec<u8>, Error> {
 470          let compressed = self.read_object(oid)?;
 471          let compressor = CompressionLayer::new(3);
 472          Ok(compressor.decompress(&compressed)?)
 473      }
 474  }
 475  ```
 476  
 477  **Impact:** 30-50% storage reduction for text-heavy repos
 478  
 479  #### B. Tiered Storage
 480  
 481  ```rust
 482  // Create: crates/radicle-node/src/storage/tiered.rs
 483  use chrono::{DateTime, Utc, Duration};
 484  use std::path::PathBuf;
 485  
 486  pub struct TieredStorage {
 487      hot_storage: PathBuf,
 488      cold_storage: String, // GCS bucket URI
 489      archive_threshold: Duration,
 490  }
 491  
 492  impl TieredStorage {
 493      pub fn new(hot: PathBuf, cold: String) -> Self {
 494          Self {
 495              hot_storage: hot,
 496              cold_storage: cold,
 497              archive_threshold: Duration::days(90),
 498          }
 499      }
 500  
 501      /// Archive old commits to cold storage
 502      pub async fn archive_cold_data(&self, repo: &Repository) -> Result<(), Error> {
 503          let cutoff = Utc::now() - self.archive_threshold;
 504          let old_commits = repo.commits_older_than(cutoff)?;
 505  
 506          for commit in old_commits {
 507              // Upload to GCS cold storage
 508              self.move_to_cold_storage(&commit).await?;
 509  
 510              // Keep metadata locally, remove blob data
 511              self.create_cold_reference(&commit)?;
 512          }
 513  
 514          Ok(())
 515      }
 516  
 517      async fn move_to_cold_storage(&self, commit: &Commit) -> Result<(), Error> {
 518          // Implementation using google-cloud-storage crate
 519          // Store in coldline storage class
 520          todo!()
 521      }
 522  }
 523  ```
 524  
 525  **Cost Comparison:**
 526  - SSD persistent disk: $0.16/GB/month
 527  - HDD persistent disk: $0.04/GB/month (75% cheaper)
 528  - Coldline Storage: $0.004/GB/month (96% cheaper!)
 529  
 530  **Savings:** For 50GB data, $8/mo → $2/mo (75%)
 531  
 532  ---
 533  
 534  ### 3. Bandwidth Optimization (Reduce by 60-70%)
 535  
 536  #### A. Delta Compression Enhancement
 537  
 538  ```rust
 539  // Modify: crates/radicle-fetch/src/transport.rs
 540  use git2::{DeltaType, Odb};
 541  
 542  pub struct OptimizedTransport {
 543      use_delta: bool,
 544  }
 545  
 546  impl OptimizedTransport {
 547      /// Send repository updates using delta compression
 548      pub async fn replicate_with_delta(
 549          &self,
 550          peer: &Peer,
 551          refs: &[Ref]
 552      ) -> Result<(), Error> {
 553          // Compute deltas instead of full objects
 554          let deltas = self.compute_minimal_deltas(refs)?;
 555  
 556          // Send packed delta stream
 557          peer.send_pack(deltas).await?;
 558  
 559          Ok(())
 560      }
 561  
 562      fn compute_minimal_deltas(&self, refs: &[Ref]) -> Result<Vec<Delta>, Error> {
 563          // Use git pack protocol with aggressive delta compression
 564          // 70-90% size reduction vs full objects
 565          todo!()
 566      }
 567  }
 568  ```
 569  
 570  **Impact:** 70-90% bandwidth reduction for incremental syncs
 571  
 572  #### B. HTTP Compression Middleware
 573  
 574  ```rust
 575  // Create: crates/radicle-node/src/api/compression.rs
 576  use async_compression::tokio::bufread::BrotliEncoder;
 577  use axum::{
 578      body::Body,
 579      http::{Request, Response, header},
 580      middleware::Next,
 581  };
 582  use tokio::io::AsyncReadExt;
 583  
 584  pub async fn compression_middleware(
 585      req: Request<Body>,
 586      next: Next,
 587  ) -> Result<Response<Body>, Error> {
 588      let response = next.run(req).await;
 589  
 590      // Check if client accepts brotli
 591      if !accepts_encoding(&req, "br") {
 592          return Ok(response);
 593      }
 594  
 595      // Compress response body
 596      let (parts, body) = response.into_parts();
 597      let body_bytes = body.collect().await?.to_bytes();
 598  
 599      let mut encoder = BrotliEncoder::new(&body_bytes[..]);
 600      let mut compressed = Vec::new();
 601      encoder.read_to_end(&mut compressed).await?;
 602  
 603      let mut response = Response::from_parts(parts, Body::from(compressed));
 604      response.headers_mut().insert(
 605          header::CONTENT_ENCODING,
 606          "br".parse().unwrap()
 607      );
 608  
 609      Ok(response)
 610  }
 611  
 612  fn accepts_encoding(req: &Request<Body>, encoding: &str) -> bool {
 613      req.headers()
 614          .get(header::ACCEPT_ENCODING)
 615          .and_then(|v| v.to_str().ok())
 616          .map(|v| v.contains(encoding))
 617          .unwrap_or(false)
 618  }
 619  ```
 620  
 621  **Impact:** 50-70% bandwidth reduction for API calls
 622  
 623  ---
 624  
 625  ### 4. Component Optimization
 626  
 627  #### Remove/Externalize Web UI
 628  
 629  **Current Architecture:**
 630  ```
 631  radicle-node (running 24/7)
 632  ├── P2P networking
 633  ├── HTTP API server
 634  └── Static web UI serving  ← Wastes resources
 635  ```
 636  
 637  **Optimized Architecture:**
 638  ```
 639  radicle-node (idle-aware)
 640  └── P2P networking + HTTP API
 641  
 642  rad-web (external)
 643  └── Deploy to Vercel/Netlify (FREE)
 644  ```
 645  
 646  **Implementation:**
 647  
 648  ```bash
 649  # Deploy web UI to Vercel
 650  cd rad-web  # If it exists in the repo
 651  vercel deploy --prod
 652  
 653  # Update environment configuration
 654  export RADICLE_API_URL=https://your-node-ip:8776
 655  ```
 656  
 657  **Savings:** ~500MB RAM, ~0.5 vCPU → ~$6-8/month
 658  
 659  ---
 660  
 661  ## Implementation Roadmap
 662  
 663  ### Phase 1: Low-Hanging Fruit (Week 1-2)
 664  
 665  **Estimated Savings: 30-40%**
 666  
 667  ```bash
 668  cd radicle-secure
 669  
 670  # 1. Add security dependencies
 671  cat >> Cargo.toml << 'EOF'
 672  
 673  # Security enhancements
 674  secretscan = "0.2"
 675  rustsec = "0.28"
 676  walkdir = "2"
 677  
 678  # Compression
 679  zstd = "0.13"
 680  async-compression = { version = "0.4", features = ["tokio", "brotli"] }
 681  EOF
 682  
 683  # 2. Create security module structure
 684  mkdir -p crates/radicle/src/security
 685  cat > crates/radicle/src/security/mod.rs << 'EOF'
 686  pub mod secrets;
 687  pub mod vulnerabilities;
 688  pub mod compression;
 689  EOF
 690  
 691  # 3. Update radicle lib.rs to include security module
 692  echo "pub mod security;" >> crates/radicle/src/lib.rs
 693  
 694  # 4. Implement secret scanning (copy code from above)
 695  # Create crates/radicle/src/security/secrets.rs
 696  
 697  # 5. Implement vulnerability scanning
 698  # Create crates/radicle/src/security/vulnerabilities.rs
 699  
 700  # 6. Implement compression
 701  # Create crates/radicle/src/security/compression.rs
 702  
 703  # 7. Test compilation
 704  cargo build --all
 705  
 706  # 8. Run tests
 707  cargo test --all
 708  ```
 709  
 710  **Deliverables:**
 711  - ✅ Secret scanning functionality
 712  - ✅ Vulnerability detection
 713  - ✅ Compression utilities
 714  - ✅ All tests passing
 715  
 716  ---
 717  
 718  ### Phase 2: Architecture Changes (Week 3-4)
 719  
 720  **Estimated Savings: 50-60% total**
 721  
 722  ```bash
 723  # 1. Implement idle timeout in radicle-node
 724  # Modify crates/radicle-node/src/main.rs (see code above)
 725  
 726  # 2. Update systemd units for socket activation
 727  cp systemd/radicle-node.service systemd/radicle-node.service.bak
 728  # Edit systemd files (see code above)
 729  
 730  # 3. Add compression middleware to HTTP API
 731  # Modify crates/radicle-node/src/api/mod.rs
 732  
 733  # 4. Test idle behavior
 734  cargo build --release
 735  ./target/release/radicle-node --idle-timeout 600
 736  
 737  # Monitor for 10+ minutes and verify shutdown
 738  ```
 739  
 740  **Deliverables:**
 741  - ✅ Idle-aware node daemon
 742  - ✅ Socket activation support
 743  - ✅ HTTP response compression
 744  - ✅ Reduced runtime hours
 745  
 746  ---
 747  
 748  ### Phase 3: Advanced Optimization (Week 5-8)
 749  
 750  **Estimated Savings: 70-80% total**
 751  
 752  ```bash
 753  # 1. Implement tiered storage
 754  # Create crates/radicle-node/src/storage/tiered.rs
 755  
 756  # 2. Add GCS cold storage support
 757  cargo add google-cloud-storage
 758  
 759  # 3. Optimize delta compression
 760  # Modify crates/radicle-fetch/src/transport.rs
 761  
 762  # 4. Deploy web UI externally
 763  cd rad-web  # If available
 764  vercel deploy --prod
 765  
 766  # 5. Configure CDN caching
 767  # Setup Cloud CDN for static content
 768  ```
 769  
 770  **Deliverables:**
 771  - ✅ Tiered hot/cold storage
 772  - ✅ Enhanced delta compression
 773  - ✅ External web UI deployment
 774  - ✅ CDN integration
 775  
 776  ---
 777  
 778  ## Deployment Guide
 779  
 780  ### Local Development Setup
 781  
 782  ```bash
 783  # 1. Install Rust toolchain
 784  curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
 785  
 786  # 2. Clone and build
 787  cd radicle-secure
 788  cargo build --release
 789  
 790  # 3. Install locally
 791  cargo install --path crates/radicle-cli --force --locked
 792  cargo install --path crates/radicle-node --force --locked
 793  cargo install --path crates/radicle-remote-helper --force --locked
 794  
 795  # 4. Initialize node
 796  rad auth init
 797  rad node start
 798  ```
 799  
 800  ---
 801  
 802  ### GCP Deployment (e2-micro - Optimized)
 803  
 804  #### 1. Create VM Instance
 805  
 806  ```bash
 807  # Create instance with minimal specs
 808  gcloud compute instances create radicle-node \
 809    --machine-type=e2-micro \
 810    --zone=us-central1-a \
 811    --image-family=ubuntu-2204-lts \
 812    --image-project=ubuntu-os-cloud \
 813    --boot-disk-size=20GB \
 814    --boot-disk-type=pd-standard \
 815    --tags=radicle-node
 816  
 817  # Reserve static IP
 818  gcloud compute addresses create radicle-ip \
 819    --region=us-central1
 820  
 821  # Attach static IP
 822  gcloud compute instances add-access-config radicle-node \
 823    --zone=us-central1-a \
 824    --address=$(gcloud compute addresses describe radicle-ip \
 825      --region=us-central1 --format='value(address)')
 826  ```
 827  
 828  #### 2. Configure Firewall
 829  
 830  ```bash
 831  # Allow P2P traffic
 832  gcloud compute firewall-rules create radicle-p2p \
 833    --allow=tcp:8776 \
 834    --target-tags=radicle-node \
 835    --description="Radicle P2P port"
 836  
 837  # Optional: Allow HTTP API (restrict to your IP)
 838  gcloud compute firewall-rules create radicle-api \
 839    --allow=tcp:8777 \
 840    --source-ranges=YOUR_IP/32 \
 841    --target-tags=radicle-node
 842  ```
 843  
 844  #### 3. Deploy Application
 845  
 846  ```bash
 847  # SSH into instance
 848  gcloud compute ssh radicle-node --zone=us-central1-a
 849  
 850  # Install Rust
 851  curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
 852  source $HOME/.cargo/env
 853  
 854  # Clone and build
 855  git clone https://github.com/YOUR_USERNAME/radicle-secure.git
 856  cd radicle-secure
 857  cargo build --release
 858  
 859  # Install binaries
 860  sudo cp target/release/radicle-node /usr/local/bin/
 861  sudo cp target/release/rad /usr/local/bin/
 862  sudo cp target/release/git-remote-rad /usr/local/bin/
 863  
 864  # Setup systemd
 865  sudo cp systemd/radicle-node.service /etc/systemd/system/
 866  sudo cp systemd/radicle-node.socket /etc/systemd/system/
 867  sudo systemctl daemon-reload
 868  sudo systemctl enable radicle-node.socket
 869  sudo systemctl start radicle-node.socket
 870  ```
 871  
 872  #### 4. Initialize Node
 873  
 874  ```bash
 875  # Create radicle user
 876  sudo useradd -r -s /bin/false radicle
 877  sudo mkdir -p /var/lib/radicle
 878  sudo chown radicle:radicle /var/lib/radicle
 879  
 880  # Initialize as radicle user
 881  sudo -u radicle rad auth init
 882  sudo -u radicle rad node start
 883  ```
 884  
 885  ---
 886  
 887  ### Cost Monitoring
 888  
 889  ```bash
 890  # Create monitoring script
 891  cat > /usr/local/bin/radicle-cost-monitor.sh << 'EOF'
 892  #!/bin/bash
 893  
 894  # Monitor resource usage
 895  echo "=== Radicle Node Resource Usage ==="
 896  echo "Uptime: $(uptime -p)"
 897  echo "CPU: $(top -bn1 | grep "radicle-node" | awk '{print $9"%"}')"
 898  echo "Memory: $(ps aux | grep radicle-node | awk '{print $4"%"}')"
 899  echo "Disk: $(df -h /var/lib/radicle | tail -1 | awk '{print $3 "/" $2 " (" $5 ")"}')"
 900  
 901  # Estimate monthly costs
 902  HOURS_UP=$(awk '{print int($1/3600)}' /proc/uptime)
 903  MONTHLY_HOURS=$((HOURS_UP * 30 / $(date +%d)))
 904  COMPUTE_COST=$(echo "$MONTHLY_HOURS * 0.00508" | bc -l)
 905  STORAGE_COST=$(echo "$(df -BG /var/lib/radicle | tail -1 | awk '{print $3}' | tr -d 'G') * 0.04" | bc -l)
 906  
 907  echo ""
 908  echo "=== Estimated Monthly Costs ==="
 909  printf "Compute (e2-micro): \$%.2f\n" $COMPUTE_COST
 910  printf "Storage (HDD): \$%.2f\n" $STORAGE_COST
 911  printf "Total: \$%.2f\n" $(echo "$COMPUTE_COST + $STORAGE_COST" | bc -l)
 912  EOF
 913  
 914  chmod +x /usr/local/bin/radicle-cost-monitor.sh
 915  
 916  # Add to crontab for daily reports
 917  (crontab -l 2>/dev/null; echo "0 9 * * * /usr/local/bin/radicle-cost-monitor.sh | mail -s 'Radicle Daily Report' you@example.com") | crontab -
 918  ```
 919  
 920  ---
 921  
 922  ## Monitoring & Maintenance
 923  
 924  ### Health Check Endpoints
 925  
 926  ```rust
 927  // Add to crates/radicle-node/src/api/health.rs
 928  use axum::{Json, response::IntoResponse};
 929  use serde_json::json;
 930  
 931  pub async fn health_check() -> impl IntoResponse {
 932      Json(json!({
 933          "status": "healthy",
 934          "version": env!("CARGO_PKG_VERSION"),
 935          "uptime": get_uptime(),
 936      }))
 937  }
 938  
 939  pub async fn metrics() -> impl IntoResponse {
 940      Json(json!({
 941          "peers": get_peer_count(),
 942          "repos": get_repo_count(),
 943          "disk_usage": get_disk_usage(),
 944          "memory_usage": get_memory_usage(),
 945      }))
 946  }
 947  ```
 948  
 949  ### Logging Configuration
 950  
 951  ```bash
 952  # Configure log rotation
 953  sudo cat > /etc/logrotate.d/radicle << EOF
 954  /var/log/radicle/*.log {
 955      daily
 956      rotate 7
 957      compress
 958      delaycompress
 959      missingok
 960      notifempty
 961      create 0640 radicle radicle
 962  }
 963  EOF
 964  ```
 965  
 966  ### Backup Strategy
 967  
 968  ```bash
 969  # Automated backup script
 970  cat > /usr/local/bin/radicle-backup.sh << 'EOF'
 971  #!/bin/bash
 972  
 973  BACKUP_DATE=$(date +%Y%m%d)
 974  BACKUP_DIR="/var/backups/radicle"
 975  DATA_DIR="/var/lib/radicle"
 976  
 977  mkdir -p $BACKUP_DIR
 978  
 979  # Backup repositories
 980  tar -czf $BACKUP_DIR/repos-$BACKUP_DATE.tar.gz $DATA_DIR/repos
 981  
 982  # Backup configuration
 983  tar -czf $BACKUP_DIR/config-$BACKUP_DATE.tar.gz $DATA_DIR/config
 984  
 985  # Upload to GCS (optional)
 986  gsutil cp $BACKUP_DIR/repos-$BACKUP_DATE.tar.gz gs://your-backup-bucket/
 987  gsutil cp $BACKUP_DIR/config-$BACKUP_DATE.tar.gz gs://your-backup-bucket/
 988  
 989  # Clean old backups (keep 7 days)
 990  find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
 991  
 992  echo "Backup completed: $BACKUP_DATE"
 993  EOF
 994  
 995  chmod +x /usr/local/bin/radicle-backup.sh
 996  
 997  # Schedule daily backups
 998  (crontab -l 2>/dev/null; echo "0 2 * * * /usr/local/bin/radicle-backup.sh") | crontab -
 999  ```
1000  
1001  ---
1002  
1003  ## Final Cost Comparison
1004  
1005  | Configuration                             | Monthly Cost | Savings |
1006  |-------------------------------------------|--------------|---------|
1007  | **Stock Radicle (e2-small)**              | $30          | Baseline |
1008  | **+ Secret Scanning**                     | $32          | -$2 (minimal overhead) |
1009  | **+ Phase 1 Optimizations**               | $20          | 33% |
1010  | **+ Phase 2 Optimizations**               | $12          | 60% |
1011  | **+ Phase 3 Optimizations**               | $8           | 73% |
1012  | **Full Optimization (e2-micro + aggressive)** | $3-5     | 83-90% |
1013  
1014  ---
1015  
1016  ## Security Best Practices
1017  
1018  ### 1. Secret Scanning Pre-Commit Hook
1019  
1020  ```bash
1021  # Install pre-commit hook
1022  cat > .git/hooks/pre-commit << 'EOF'
1023  #!/bin/bash
1024  
1025  # Run secret scanner on staged files
1026  rad scan --staged
1027  
1028  if [ $? -ne 0 ]; then
1029      echo "❌ Secret scanning failed. Commit aborted."
1030      exit 1
1031  fi
1032  
1033  echo "✅ Secret scanning passed."
1034  exit 0
1035  EOF
1036  
1037  chmod +x .git/hooks/pre-commit
1038  ```
1039  
1040  ### 2. Regular Vulnerability Audits
1041  
1042  ```bash
1043  # Weekly vulnerability scan
1044  cat > /etc/cron.weekly/radicle-audit << 'EOF'
1045  #!/bin/bash
1046  
1047  cd /opt/radicle-secure
1048  cargo audit >> /var/log/radicle/audit.log 2>&1
1049  
1050  # Alert if vulnerabilities found
1051  if grep -q "Vulnerabilities found" /var/log/radicle/audit.log; then
1052      echo "Vulnerabilities detected in Radicle dependencies" | \
1053          mail -s "Radicle Security Alert" admin@example.com
1054  fi
1055  EOF
1056  
1057  chmod +x /etc/cron.weekly/radicle-audit
1058  ```
1059  
1060  ### 3. Access Control
1061  
1062  ```bash
1063  # Restrict node access to specific peers
1064  rad config set node.policy restricted
1065  rad config set node.allowed-peers "peer1,peer2,peer3"
1066  
1067  # Enable signature verification
1068  rad config set node.verify-signatures true
1069  ```
1070  
1071  ---
1072  
1073  ## Troubleshooting
1074  
1075  ### Node Won't Start
1076  
1077  ```bash
1078  # Check systemd status
1079  sudo systemctl status radicle-node
1080  
1081  # View logs
1082  sudo journalctl -u radicle-node -f
1083  
1084  # Check socket activation
1085  sudo systemctl status radicle-node.socket
1086  
1087  # Verify permissions
1088  sudo ls -la /var/lib/radicle
1089  ```
1090  
1091  ### High Bandwidth Usage
1092  
1093  ```bash
1094  # Monitor network traffic
1095  sudo iftop -i eth0
1096  
1097  # Check peer connections
1098  rad node peers
1099  
1100  # Limit replication bandwidth
1101  rad config set node.max-bandwidth 1MB
1102  ```
1103  
1104  ### Storage Issues
1105  
1106  ```bash
1107  # Check disk usage
1108  du -sh /var/lib/radicle/*
1109  
1110  # Run garbage collection
1111  rad node gc
1112  
1113  # Archive old data
1114  /usr/local/bin/radicle-archive-cold-data.sh
1115  ```
1116  
1117  ---
1118  
1119  ## Next Steps
1120  
1121  1. **Complete Phase 1** implementation (security features)
1122  2. **Test** thoroughly in local environment
1123  3. **Deploy** to GCP e2-micro instance
1124  4. **Monitor** costs and performance for 1 week
1125  5. **Iterate** on Phase 2 & 3 optimizations
1126  6. **Document** team workflows and best practices
1127  
1128  ---
1129  
1130  ## References
1131  
1132  - [Radicle Documentation](https://radicle.xyz/guides)
1133  - [RustSec Advisory Database](https://rustsec.org/)
1134  - [Secretscan Crate](https://crates.io/crates/secretscan)
1135  - [GCP Pricing Calculator](https://cloud.google.com/products/calculator)
1136  - [Zstd Compression](https://facebook.github.io/zstd/)
1137  
1138  ---
1139  
1140  **Last Updated:** 2025-11-02
1141  **Version:** 1.0
1142  **Author:** Radicle Secure Project