From 7284cb4578b9632f53acf56377e4c3d91a063ccf Mon Sep 17 00:00:00 2001 From: anonpenguin23 Date: Sat, 28 Mar 2026 15:27:54 +0200 Subject: [PATCH] feat(cli): add fanout push strategy and improve website responsiveness - Add --fanout flag to push command for server-to-server deployment - Implement agent forwarding for efficient multi-node distribution - Update landing page scene heights and section padding for mobile devices --- core/pkg/cli/cmd/pushcmd/push.go | 135 +++++++++++++----- .../components/landing/about-hero-scene.tsx | 2 +- .../components/landing/compute-mesh-scene.tsx | 2 +- .../components/landing/consensus-scene.tsx | 2 +- .../components/landing/growth-vault-scene.tsx | 2 +- .../components/landing/orama-one-scene.tsx | 4 +- website/src/components/layout/section.tsx | 2 +- website/src/components/layout/shell.tsx | 10 +- .../src/components/navigation/mobile-menu.tsx | 41 +++--- website/src/components/navigation/navbar.tsx | 9 +- .../navigation/whitelist-banner.tsx | 4 +- website/src/docs/operator/node-management.mdx | 5 + website/src/docs/operator/upgrades.mdx | 6 + website/src/pages/blockchain.tsx | 2 +- website/src/pages/compute.tsx | 16 ++- website/src/pages/home.tsx | 8 +- 16 files changed, 167 insertions(+), 83 deletions(-) diff --git a/core/pkg/cli/cmd/pushcmd/push.go b/core/pkg/cli/cmd/pushcmd/push.go index 48bdb61..c280e04 100644 --- a/core/pkg/cli/cmd/pushcmd/push.go +++ b/core/pkg/cli/cmd/pushcmd/push.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "sort" + "strings" "github.com/DeBrosOfficial/network/pkg/cli" "github.com/DeBrosOfficial/network/pkg/cli/noderesolver" @@ -14,9 +15,10 @@ import ( ) var ( - envFlag string - ipFlag string - userFlag string + envFlag string + ipFlag string + userFlag string + fanoutFlag bool ) // Cmd is the top-level "push" command — upload binary archive to nodes. @@ -25,14 +27,13 @@ var Cmd = &cobra.Command{ Short: "Push binary archive to your nodes", Long: `Upload the pre-built binary archive to nodes and extract it. -Use --ip to push to a single node, or omit it to push to all nodes -in the active environment. +By default, uploads from your machine to each node sequentially. +Use --fanout to upload to one node, then fan out server-to-server (faster). Examples: orama push --ip 1.2.3.4 # Push to one node - orama push --ip 1.2.3.4 --user ubuntu # Push with specific SSH user - orama push --env devnet # Push to all devnet nodes - orama push --env devnet --ip 1.2.3.4 # Push to one devnet node`, + orama push --env devnet # Sequential push to all devnet nodes + orama push --env devnet --fanout # Fan out server-to-server (faster)`, RunE: func(cmd *cobra.Command, args []string) error { archivePath := findNewestArchive() if archivePath == "" { @@ -47,7 +48,6 @@ Examples: var nodes []inspector.Node if ipFlag != "" { - // Single node push user := userFlag if user == "" { user = "root" @@ -55,8 +55,8 @@ Examples: vaultTarget := fmt.Sprintf("%s/%s", ipFlag, user) env := envFlag if env == "" { - active, err := cli.GetActiveEnvironment() - if err == nil { + active, _ := cli.GetActiveEnvironment() + if active != nil { env = active.Name } } @@ -64,13 +64,9 @@ Examples: vaultTarget = "sandbox/root" } nodes = []inspector.Node{{ - Host: ipFlag, - User: user, - VaultTarget: vaultTarget, - Environment: env, + Host: ipFlag, User: user, VaultTarget: vaultTarget, Environment: env, }} } else { - // All nodes in environment env := envFlag if env == "" { active, err := cli.GetActiveEnvironment() @@ -79,7 +75,6 @@ Examples: } env = active.Name } - resolved, err := noderesolver.ResolveNodes(env) if err != nil { return fmt.Errorf("failed to resolve nodes: %w", err) @@ -97,27 +92,13 @@ Examples: } defer cleanup() - fmt.Printf("Pushing to %d node(s)...\n\n", len(nodes)) - - remotePath := "/tmp/" + filepath.Base(archivePath) - extractCmd := fmt.Sprintf("sudo bash -c 'mkdir -p /opt/orama && tar xzf %s -C /opt/orama && rm -f %s && /opt/orama/bin/orama version'", remotePath, remotePath) - - for _, n := range nodes { - fmt.Printf(" %s: uploading...", n.Host) - if err := remotessh.UploadFile(n, archivePath, remotePath); err != nil { - fmt.Printf(" FAILED (%v)\n", err) - continue - } - fmt.Printf(" extracting...") - if err := remotessh.RunSSHStreaming(n, extractCmd); err != nil { - fmt.Printf(" FAILED (%v)\n", err) - continue - } - fmt.Println(" OK") + // Single node or default: upload sequentially + if len(nodes) == 1 || !fanoutFlag { + return pushDirect(nodes, archivePath) } - fmt.Println("\nPush complete") - return nil + // Multi-node with --fanout: use agent forwarding + return pushFanout(nodes, archivePath) }, } @@ -125,6 +106,88 @@ func init() { Cmd.Flags().StringVar(&envFlag, "env", "", "Target environment (default: active)") Cmd.Flags().StringVar(&ipFlag, "ip", "", "Push to a single node by IP") Cmd.Flags().StringVar(&userFlag, "user", "", "SSH user (default: root)") + Cmd.Flags().BoolVar(&fanoutFlag, "fanout", false, "Upload to first node, then fan out server-to-server (faster)") +} + +// pushDirect uploads the archive from local machine to each node sequentially. +func pushDirect(nodes []inspector.Node, archivePath string) error { + fmt.Printf("Pushing to %d node(s) (direct)...\n\n", len(nodes)) + + remotePath := "/tmp/" + filepath.Base(archivePath) + extractCmd := fmt.Sprintf("sudo bash -c 'mkdir -p /opt/orama && tar xzf %s -C /opt/orama && rm -f %s && /opt/orama/bin/orama version'", remotePath, remotePath) + + for _, n := range nodes { + fmt.Printf(" %s: uploading...", n.Host) + if err := remotessh.UploadFile(n, archivePath, remotePath); err != nil { + fmt.Printf(" FAILED (%v)\n", err) + continue + } + fmt.Printf(" extracting...") + if err := remotessh.RunSSHStreaming(n, extractCmd); err != nil { + fmt.Printf(" FAILED (%v)\n", err) + continue + } + fmt.Println(" OK") + } + + fmt.Println("\nPush complete") + return nil +} + +// pushFanout uploads the archive to the first node, then fans out server-to-server +// using SSH agent forwarding. +func pushFanout(nodes []inspector.Node, archivePath string) error { + fmt.Printf("Pushing to %d node(s) (fanout)...\n\n", len(nodes)) + + hub := nodes[0] + targets := nodes[1:] + remotePath := "/tmp/" + filepath.Base(archivePath) + extractCmd := fmt.Sprintf("mkdir -p /opt/orama && tar xzf %s -C /opt/orama && rm -f %s", remotePath, remotePath) + + // Load SSH keys into the system ssh-agent for agent forwarding + fmt.Println(" Loading SSH keys into agent...") + if err := remotessh.LoadAgentKeys(nodes); err != nil { + fmt.Printf(" Warning: failed to load agent keys: %v\n", err) + fmt.Println(" Falling back to direct push...") + return pushDirect(nodes, archivePath) + } + + // Upload archive to hub + fmt.Printf(" %s (hub): uploading...", hub.Host) + if err := remotessh.UploadFile(hub, archivePath, remotePath); err != nil { + return fmt.Errorf("failed to upload to hub %s: %w", hub.Host, err) + } + fmt.Printf(" extracting...") + if err := remotessh.RunSSHStreaming(hub, "sudo bash -c '"+extractCmd+"'"); err != nil { + return fmt.Errorf("failed to extract on hub %s: %w", hub.Host, err) + } + fmt.Println(" OK") + + // Build the fanout command — hub SCPs to all targets in parallel + var fanoutParts []string + for _, t := range targets { + scpCmd := fmt.Sprintf( + "scp -o StrictHostKeyChecking=accept-new -o IdentitiesOnly=no %s %s@%s:%s && ssh -o StrictHostKeyChecking=accept-new %s@%s 'sudo bash -c \"%s\"' && echo '%s: done'", + remotePath, t.User, t.Host, remotePath, + t.User, t.Host, extractCmd, + t.Host, + ) + fanoutParts = append(fanoutParts, "("+scpCmd+") &") + } + fanoutParts = append(fanoutParts, "wait", "echo 'Fanout complete'") + fanoutScript := strings.Join(fanoutParts, "\n") + + fmt.Printf(" Fanning out to %d nodes from %s...\n", len(targets), hub.Host) + if err := remotessh.RunSSHStreaming(hub, "bash -c '"+fanoutScript+"'", remotessh.WithAgentForward()); err != nil { + fmt.Printf(" Fanout failed: %v\n", err) + fmt.Println(" Some nodes may not have been updated") + } + + // Clean up archive on hub + remotessh.RunSSHStreaming(hub, "rm -f "+remotePath) + + fmt.Println("\nPush complete") + return nil } func findNewestArchive() string { diff --git a/website/src/components/landing/about-hero-scene.tsx b/website/src/components/landing/about-hero-scene.tsx index 7d2d754..8dab338 100644 --- a/website/src/components/landing/about-hero-scene.tsx +++ b/website/src/components/landing/about-hero-scene.tsx @@ -254,7 +254,7 @@ function AboutNetwork() { export function AboutHeroScene() { return ( -
+
+
+
+
- - + setBannerVisible(false)} /> + @@ -26,7 +28,7 @@ export function Shell() {
} > -
+
diff --git a/website/src/components/navigation/mobile-menu.tsx b/website/src/components/navigation/mobile-menu.tsx index 904ee29..ce71e6d 100644 --- a/website/src/components/navigation/mobile-menu.tsx +++ b/website/src/components/navigation/mobile-menu.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { Link, useLocation } from "react-router"; -import { ExternalLink } from "lucide-react"; +import { ExternalLink, X } from "lucide-react"; import { NAV_LINKS, MORE_LINKS } from "../../data/navigation"; import { Button } from "../ui/button"; import { cn } from "../../lib/utils"; @@ -33,13 +33,22 @@ export function MobileMenu({ open, onClose }: MobileMenuProps) { return (
-
+
+ +
diff --git a/website/src/components/navigation/navbar.tsx b/website/src/components/navigation/navbar.tsx index 0be0408..cf5d742 100644 --- a/website/src/components/navigation/navbar.tsx +++ b/website/src/components/navigation/navbar.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { Link, useLocation } from "react-router"; import { Menu, X, ExternalLink, ChevronDown } from "lucide-react"; import { cn } from "../../lib/utils"; @@ -11,10 +11,11 @@ const linkClass = "px-3 py-1.5 text-xs tracking-wider uppercase font-mono rounde const activeClass = "text-fg bg-white/[0.06]"; const inactiveClass = "text-muted hover:text-fg hover:bg-white/[0.04]"; -export function Navbar() { +export function Navbar({ bannerVisible = true }: { bannerVisible?: boolean }) { const [mobileOpen, setMobileOpen] = useState(false); const [moreOpen, setMoreOpen] = useState(false); const { pathname } = useLocation(); + const handleMobileClose = useCallback(() => setMobileOpen(false), []); useEffect(() => { setMoreOpen(false); @@ -22,7 +23,7 @@ export function Navbar() { return ( <> -
+
@@ -187,7 +187,7 @@ function OramaOneSection() { { label: "Consensus", value: "Hybrid PoS+PoC+PoI" }, { label: "Block Time", value: "6 seconds" }, { label: "OramaOS", value: "1.5x Multiplier" }, - { label: "Status", value: "Coming Soon" }, + { label: "Status", value: "Live" }, ].map((stat) => (
{stat.label} @@ -196,8 +196,10 @@ function OramaOneSection() { ))}
- {/* 3D Node */} diff --git a/website/src/pages/home.tsx b/website/src/pages/home.tsx index a3f6ce3..d663b94 100644 --- a/website/src/pages/home.tsx +++ b/website/src/pages/home.tsx @@ -75,7 +75,7 @@ function HomeHero() { Decentralized Cloud + L1 Blockchain -

+

- Bitcoin gave us decentralized money. Ethereum gave us decentralized - contracts. Orama gives us decentralized everything — - an L1 blockchain fused with a full cloud platform. + An L1 blockchain fused with a full cloud platform — decentralized everything.

@@ -149,7 +147,7 @@ function HomeHero() {
- Testnet Live — 300 Nodes Required for Genesis + Testnet Live — WITH 50+ NODES