mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-05-01 05:04:13 +00:00
feat(cli): add push command and improve node setup
- Add `orama push` command to upload and extract binary archives to nodes - Update `node setup` to pass operator metadata and auto-configure environments - Improve SSH configuration and node registration logic
This commit is contained in:
parent
ab1be4105c
commit
f3f4a84762
@ -19,6 +19,7 @@ import (
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/namespacecmd"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/node"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/nodescmd"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/pushcmd"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/rolloutcmd"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/sandboxcmd"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/cmd/sshcmd"
|
||||
@ -97,6 +98,7 @@ and interacting with the Orama distributed network.`,
|
||||
|
||||
// Unified node management commands
|
||||
rootCmd.AddCommand(nodescmd.Cmd)
|
||||
rootCmd.AddCommand(pushcmd.Cmd)
|
||||
rootCmd.AddCommand(rolloutcmd.Cmd)
|
||||
rootCmd.AddCommand(statuscmd.Cmd)
|
||||
rootCmd.AddCommand(sshcmd.Cmd)
|
||||
|
||||
@ -40,6 +40,7 @@ func init() {
|
||||
setupCmd.Flags().StringVar(&setupOpts.User, "user", "root", "SSH user on the VPS")
|
||||
setupCmd.Flags().StringVar(&setupOpts.Password, "password", "", "One-time password for initial SSH access")
|
||||
setupCmd.Flags().StringVar(&setupOpts.BaseDomain, "base-domain", "", "Base domain for the network")
|
||||
setupCmd.Flags().StringVar(&setupOpts.Gateway, "gateway", "", "Gateway URL for invite tokens (e.g., http://1.2.3.4)")
|
||||
setupCmd.Flags().BoolVar(&setupOpts.Genesis, "genesis", false, "Create a new cluster (first node)")
|
||||
setupCmd.Flags().BoolVar(&setupOpts.AnyoneRelay, "anyone-relay", false, "Run as Anyone relay operator")
|
||||
setupCmd.MarkFlagRequired("ip")
|
||||
|
||||
152
core/pkg/cli/cmd/pushcmd/push.go
Normal file
152
core/pkg/cli/cmd/pushcmd/push.go
Normal file
@ -0,0 +1,152 @@
|
||||
package pushcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/cli"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/noderesolver"
|
||||
"github.com/DeBrosOfficial/network/pkg/cli/remotessh"
|
||||
"github.com/DeBrosOfficial/network/pkg/inspector"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
envFlag string
|
||||
ipFlag string
|
||||
userFlag string
|
||||
)
|
||||
|
||||
// Cmd is the top-level "push" command — upload binary archive to nodes.
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "push",
|
||||
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.
|
||||
|
||||
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`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
archivePath := findNewestArchive()
|
||||
if archivePath == "" {
|
||||
return fmt.Errorf("no binary archive found in /tmp/ (run `orama build` first)")
|
||||
}
|
||||
info, err := os.Stat(archivePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stat archive: %w", err)
|
||||
}
|
||||
fmt.Printf("Archive: %s (%s)\n", filepath.Base(archivePath), formatBytes(info.Size()))
|
||||
|
||||
var nodes []inspector.Node
|
||||
|
||||
if ipFlag != "" {
|
||||
// Single node push
|
||||
user := userFlag
|
||||
if user == "" {
|
||||
user = "root"
|
||||
}
|
||||
vaultTarget := fmt.Sprintf("%s/%s", ipFlag, user)
|
||||
env := envFlag
|
||||
if env == "" {
|
||||
active, err := cli.GetActiveEnvironment()
|
||||
if err == nil {
|
||||
env = active.Name
|
||||
}
|
||||
}
|
||||
if env == "sandbox" {
|
||||
vaultTarget = "sandbox/root"
|
||||
}
|
||||
nodes = []inspector.Node{{
|
||||
Host: ipFlag,
|
||||
User: user,
|
||||
VaultTarget: vaultTarget,
|
||||
Environment: env,
|
||||
}}
|
||||
} else {
|
||||
// All nodes in environment
|
||||
env := envFlag
|
||||
if env == "" {
|
||||
active, err := cli.GetActiveEnvironment()
|
||||
if err != nil {
|
||||
return fmt.Errorf("no --ip or --env specified and no active environment")
|
||||
}
|
||||
env = active.Name
|
||||
}
|
||||
|
||||
resolved, err := noderesolver.ResolveNodes(env)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve nodes: %w", err)
|
||||
}
|
||||
if len(resolved) == 0 {
|
||||
return fmt.Errorf("no nodes found for environment %q", env)
|
||||
}
|
||||
nodes = resolved
|
||||
}
|
||||
|
||||
// Prepare SSH keys
|
||||
cleanup, err := remotessh.PrepareNodeKeys(nodes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare SSH keys: %w", err)
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
fmt.Println("\nPush complete")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
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)")
|
||||
}
|
||||
|
||||
func findNewestArchive() string {
|
||||
matches, _ := filepath.Glob("/tmp/orama-*-linux-*.tar.gz")
|
||||
if len(matches) == 0 {
|
||||
return ""
|
||||
}
|
||||
sort.Slice(matches, func(i, j int) bool {
|
||||
fi, _ := os.Stat(matches[i])
|
||||
fj, _ := os.Stat(matches[j])
|
||||
if fi == nil || fj == nil {
|
||||
return false
|
||||
}
|
||||
return fi.ModTime().After(fj.ModTime())
|
||||
})
|
||||
return matches[0]
|
||||
}
|
||||
|
||||
func formatBytes(b int64) string {
|
||||
const mb = 1024 * 1024
|
||||
if b >= mb {
|
||||
return fmt.Sprintf("%.1f MB", float64(b)/float64(mb))
|
||||
}
|
||||
return fmt.Sprintf("%d KB", b/1024)
|
||||
}
|
||||
@ -78,6 +78,7 @@ func sshInto(node inspector.Node) error {
|
||||
sshCmd := exec.Command(sshBin,
|
||||
"-i", keyPath,
|
||||
"-o", "StrictHostKeyChecking=accept-new",
|
||||
"-o", "IdentitiesOnly=yes",
|
||||
fmt.Sprintf("%s@%s", node.User, node.Host),
|
||||
)
|
||||
sshCmd.Stdin = os.Stdin
|
||||
|
||||
@ -43,6 +43,11 @@ type Flags struct {
|
||||
AnyoneFamily string // Comma-separated fingerprints of other relays you operate
|
||||
AnyoneBandwidth int // Percentage of VPS bandwidth for relay (default: 30, 0=unlimited)
|
||||
AnyoneAccounting int // Monthly data cap for relay in GB (0=unlimited)
|
||||
|
||||
// Operator metadata (set by orama node setup, written to node.yaml for registration)
|
||||
SSHUser string // SSH user for remote management
|
||||
Environment string // Environment name (devnet, testnet, etc.)
|
||||
OperatorWallet string // Operator wallet address
|
||||
}
|
||||
|
||||
// ParseFlags parses install command flags
|
||||
@ -90,6 +95,11 @@ func ParseFlags(args []string) (*Flags, error) {
|
||||
fs.IntVar(&flags.AnyoneBandwidth, "anyone-bandwidth", 30, "Limit relay to N% of VPS bandwidth (0=unlimited, runs speedtest)")
|
||||
fs.IntVar(&flags.AnyoneAccounting, "anyone-accounting", 0, "Monthly data cap for relay in GB (0=unlimited)")
|
||||
|
||||
// Operator metadata (set by orama node setup)
|
||||
fs.StringVar(&flags.SSHUser, "ssh-user", "", "SSH user for remote management")
|
||||
fs.StringVar(&flags.Environment, "environment", "", "Environment name (devnet, testnet, etc.)")
|
||||
fs.StringVar(&flags.OperatorWallet, "operator-wallet", "", "Operator wallet address")
|
||||
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if err == flag.ErrHelp {
|
||||
return nil, err
|
||||
|
||||
@ -68,6 +68,11 @@ func NewOrchestrator(flags *Flags) (*Orchestrator, error) {
|
||||
setup.SetAnyoneClient(true)
|
||||
}
|
||||
|
||||
// Set operator metadata (from orama node setup)
|
||||
setup.SSHUser = flags.SSHUser
|
||||
setup.Environment = flags.Environment
|
||||
setup.OperatorWallet = flags.OperatorWallet
|
||||
|
||||
validator := NewValidator(flags, oramaDir)
|
||||
|
||||
return &Orchestrator{
|
||||
|
||||
@ -38,7 +38,8 @@ type Options struct {
|
||||
User string // SSH user (default: "root")
|
||||
Password string // One-time password for initial SSH access
|
||||
BaseDomain string
|
||||
Genesis bool // If true, create a new cluster instead of joining
|
||||
Gateway string // Gateway URL to use for invite tokens (overrides env config)
|
||||
Genesis bool // If true, create a new cluster instead of joining
|
||||
AnyoneRelay bool
|
||||
}
|
||||
|
||||
@ -154,6 +155,25 @@ func Run(opts Options) error {
|
||||
return fmt.Errorf("install failed: %w", err)
|
||||
}
|
||||
|
||||
// 9. After genesis install, update the environment gateway URL to this node's IP.
|
||||
// This allows subsequent `node setup` calls to find the gateway automatically.
|
||||
if opts.Genesis && opts.Env != "" {
|
||||
gatewayURL := fmt.Sprintf("http://%s", opts.IP)
|
||||
desc := fmt.Sprintf("%s (genesis: %s)", opts.Env, opts.IP)
|
||||
if err := cli.AddEnvironment(opts.Env, gatewayURL, desc); err != nil {
|
||||
fmt.Fprintf(os.Stderr, " Warning: failed to update environment: %v\n", err)
|
||||
} else {
|
||||
if err := cli.SwitchEnvironment(opts.Env); err != nil {
|
||||
fmt.Fprintf(os.Stderr, " Warning: failed to switch environment: %v\n", err)
|
||||
}
|
||||
fmt.Printf(" Environment %q updated: gateway → %s\n", opts.Env, gatewayURL)
|
||||
fmt.Printf("\n To join more nodes, first authenticate:\n")
|
||||
fmt.Printf(" orama auth login\n")
|
||||
fmt.Printf(" Then:\n")
|
||||
fmt.Printf(" orama node setup --ip <IP> --password '<PASS>' --env %s --base-domain %s\n", opts.Env, opts.BaseDomain)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\n Node %s setup complete!\n", opts.IP)
|
||||
return nil
|
||||
}
|
||||
@ -176,6 +196,8 @@ func installPublicKey(ip, user, password, pubKey string) error {
|
||||
"ssh",
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
"-o", "ConnectTimeout=10",
|
||||
"-o", "PreferredAuthentications=password",
|
||||
"-o", "PubkeyAuthentication=no",
|
||||
fmt.Sprintf("%s@%s", user, ip),
|
||||
cmd,
|
||||
}
|
||||
@ -212,22 +234,38 @@ func buildInstallCommand(opts Options, node inspector.Node, agentClient *rwagent
|
||||
parts = append(parts, "--anyone-client")
|
||||
}
|
||||
|
||||
if !opts.Genesis {
|
||||
// Get gateway URL and invite token
|
||||
env := opts.Env
|
||||
if env == "" {
|
||||
active, err := cli.GetActiveEnvironment()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get active environment: %w", err)
|
||||
}
|
||||
env = active.Name
|
||||
}
|
||||
// Pass operator metadata so the node registers with correct values
|
||||
if opts.User != "" {
|
||||
parts = append(parts, "--ssh-user", opts.User)
|
||||
}
|
||||
if opts.Env != "" {
|
||||
parts = append(parts, "--environment", opts.Env)
|
||||
}
|
||||
|
||||
envConfig, err := cli.GetEnvironmentByName(env)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("environment %q not found: %w", env, err)
|
||||
// Get wallet address for operator tagging
|
||||
ctx := context.Background()
|
||||
if addrData, err := agentClient.GetAddress(ctx, "evm"); err == nil && addrData.Address != "" {
|
||||
parts = append(parts, "--operator-wallet", addrData.Address)
|
||||
}
|
||||
|
||||
if !opts.Genesis {
|
||||
// Determine gateway URL for invite token request
|
||||
gatewayURL := opts.Gateway
|
||||
if gatewayURL == "" {
|
||||
env := opts.Env
|
||||
if env == "" {
|
||||
active, err := cli.GetActiveEnvironment()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get active environment: %w", err)
|
||||
}
|
||||
env = active.Name
|
||||
}
|
||||
envConfig, err := cli.GetEnvironmentByName(env)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("environment %q not found (use --gateway to specify directly): %w", env, err)
|
||||
}
|
||||
gatewayURL = envConfig.GatewayURL
|
||||
}
|
||||
gatewayURL := envConfig.GatewayURL
|
||||
|
||||
// Request invite token via operator API
|
||||
token, err := requestInviteToken(gatewayURL)
|
||||
|
||||
@ -42,7 +42,7 @@ func UploadFile(node inspector.Node, localPath, remotePath string, opts ...SSHOp
|
||||
|
||||
dest := fmt.Sprintf("%s@%s:%s", node.User, node.Host, remotePath)
|
||||
|
||||
args := []string{"-o", "ConnectTimeout=10", "-i", node.SSHKey}
|
||||
args := []string{"-o", "ConnectTimeout=10", "-o", "IdentitiesOnly=yes", "-i", node.SSHKey}
|
||||
if cfg.noHostKeyCheck {
|
||||
args = append([]string{"-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"}, args...)
|
||||
} else {
|
||||
@ -73,7 +73,7 @@ func RunSSHStreaming(node inspector.Node, command string, opts ...SSHOption) err
|
||||
o(&cfg)
|
||||
}
|
||||
|
||||
args := []string{"-o", "ConnectTimeout=10", "-i", node.SSHKey}
|
||||
args := []string{"-o", "ConnectTimeout=10", "-o", "IdentitiesOnly=yes", "-i", node.SSHKey}
|
||||
if cfg.noHostKeyCheck {
|
||||
args = append([]string{"-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"}, args...)
|
||||
} else {
|
||||
|
||||
@ -7,4 +7,7 @@ type NodeConfig struct {
|
||||
DataDir string `yaml:"data_dir"` // Data directory
|
||||
MaxConnections int `yaml:"max_connections"` // Maximum peer connections
|
||||
Domain string `yaml:"domain"` // Domain for this node (e.g., node-1.orama.network)
|
||||
SSHUser string `yaml:"ssh_user,omitempty"` // SSH user for remote management
|
||||
Environment string `yaml:"environment,omitempty"` // Environment name (devnet, testnet, etc.)
|
||||
OperatorWallet string `yaml:"operator_wallet,omitempty"` // Operator wallet address
|
||||
}
|
||||
|
||||
@ -20,7 +20,10 @@ import (
|
||||
|
||||
// ConfigGenerator manages generation of node, gateway, and service configs
|
||||
type ConfigGenerator struct {
|
||||
oramaDir string
|
||||
oramaDir string
|
||||
SSHUser string // Operator metadata
|
||||
Environment string
|
||||
OperatorWallet string
|
||||
}
|
||||
|
||||
// NewConfigGenerator creates a new config generator
|
||||
@ -192,6 +195,11 @@ func (cg *ConfigGenerator) GenerateNodeConfig(peerAddresses []string, vpsIP stri
|
||||
// HTTPS is still used for client-facing gateway traffic via autocert
|
||||
// TLS can be enabled manually later if needed for inter-node encryption
|
||||
|
||||
// Operator metadata (set by orama node setup via --ssh-user, --environment, --operator-wallet)
|
||||
data.SSHUser = cg.SSHUser
|
||||
data.Environment = cg.Environment
|
||||
data.OperatorWallet = cg.OperatorWallet
|
||||
|
||||
return templates.RenderNodeConfig(data)
|
||||
}
|
||||
|
||||
|
||||
@ -53,6 +53,11 @@ type ProductionSetup struct {
|
||||
serviceController *SystemdController
|
||||
binaryInstaller *BinaryInstaller
|
||||
NodePeerID string // Captured during Phase3 for later display
|
||||
|
||||
// Operator metadata (from --ssh-user, --environment, --operator-wallet flags)
|
||||
SSHUser string
|
||||
Environment string
|
||||
OperatorWallet string
|
||||
}
|
||||
|
||||
// ReadBranchPreference reads the stored branch preference from disk
|
||||
@ -599,6 +604,11 @@ func (ps *ProductionSetup) Phase4GenerateConfigs(peerAddresses []string, vpsIP s
|
||||
ps.logf("Phase 4: Generating configurations...")
|
||||
}
|
||||
|
||||
// Propagate operator metadata to config generator
|
||||
ps.configGenerator.SSHUser = ps.SSHUser
|
||||
ps.configGenerator.Environment = ps.Environment
|
||||
ps.configGenerator.OperatorWallet = ps.OperatorWallet
|
||||
|
||||
// Node config (unified architecture)
|
||||
nodeConfig, err := ps.configGenerator.GenerateNodeConfig(peerAddresses, vpsIP, joinAddress, domain, baseDomain, enableHTTPS)
|
||||
if err != nil {
|
||||
|
||||
@ -5,6 +5,15 @@ node:
|
||||
data_dir: "{{.DataDir}}"
|
||||
max_connections: 50
|
||||
domain: "{{.Domain}}"
|
||||
{{- if .SSHUser}}
|
||||
ssh_user: "{{.SSHUser}}"
|
||||
{{- end}}
|
||||
{{- if .Environment}}
|
||||
environment: "{{.Environment}}"
|
||||
{{- end}}
|
||||
{{- if .OperatorWallet}}
|
||||
operator_wallet: "{{.OperatorWallet}}"
|
||||
{{- end}}
|
||||
|
||||
database:
|
||||
data_dir: "{{.DataDir}}/rqlite"
|
||||
|
||||
@ -41,6 +41,11 @@ type NodeConfigData struct {
|
||||
NodeKey string // Path to X.509 private key for node-to-node communication
|
||||
NodeCACert string // Path to CA certificate (optional)
|
||||
NodeNoVerify bool // Skip certificate verification (for self-signed certs)
|
||||
|
||||
// Operator metadata — written to dns_nodes during registration
|
||||
SSHUser string // SSH user for remote management
|
||||
Environment string // Environment name (devnet, testnet, etc.)
|
||||
OperatorWallet string // Operator wallet address
|
||||
}
|
||||
|
||||
// GatewayConfigData holds parameters for gateway.yaml rendering
|
||||
|
||||
@ -88,6 +88,7 @@ func runSSHOnce(ctx context.Context, node Node, command string) SSHResult {
|
||||
"-o", "StrictHostKeyChecking=accept-new",
|
||||
"-o", "ConnectTimeout=10",
|
||||
"-o", "BatchMode=yes",
|
||||
"-o", "IdentitiesOnly=yes",
|
||||
"-i", node.SSHKey,
|
||||
fmt.Sprintf("%s@%s", node.User, node.Host),
|
||||
command,
|
||||
|
||||
@ -44,21 +44,29 @@ func (n *Node) registerDNSNode(ctx context.Context) error {
|
||||
// Determine region (defaulting to "local" for now, could be from cloud metadata in future)
|
||||
region := "local"
|
||||
|
||||
// Read optional metadata from node config
|
||||
sshUser := n.config.Node.SSHUser
|
||||
environment := n.config.Node.Environment
|
||||
operatorWallet := n.config.Node.OperatorWallet
|
||||
|
||||
// Insert or update node record
|
||||
query := `
|
||||
INSERT INTO dns_nodes (id, ip_address, internal_ip, region, status, last_seen, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, 'active', datetime('now'), datetime('now'), datetime('now'))
|
||||
INSERT INTO dns_nodes (id, ip_address, internal_ip, region, status, ssh_user, environment, operator_wallet, last_seen, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, 'active', ?, ?, ?, datetime('now'), datetime('now'), datetime('now'))
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
ip_address = excluded.ip_address,
|
||||
internal_ip = excluded.internal_ip,
|
||||
region = excluded.region,
|
||||
status = 'active',
|
||||
ssh_user = COALESCE(NULLIF(excluded.ssh_user, ''), dns_nodes.ssh_user),
|
||||
environment = COALESCE(NULLIF(excluded.environment, ''), dns_nodes.environment),
|
||||
operator_wallet = COALESCE(NULLIF(excluded.operator_wallet, ''), dns_nodes.operator_wallet),
|
||||
last_seen = datetime('now'),
|
||||
updated_at = datetime('now')
|
||||
`
|
||||
|
||||
db := n.rqliteAdapter.GetSQLDB()
|
||||
_, err = rqlite.SafeExecContext(db, ctx, query, nodeID, ipAddress, internalIP, region)
|
||||
_, err = rqlite.SafeExecContext(db, ctx, query, nodeID, ipAddress, internalIP, region, sshUser, environment, operatorWallet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register DNS node: %w", err)
|
||||
}
|
||||
|
||||
@ -48,79 +48,87 @@ The CLI is used both for initial node setup and ongoing management. All node ope
|
||||
To become an operator, you need:
|
||||
|
||||
1. A VPS meeting the hardware requirements above
|
||||
2. An invite token (generated by an existing node in the cluster)
|
||||
3. The `orama` binary on your VPS
|
||||
2. The `orama` CLI on your local machine
|
||||
3. RootWallet desktop app (for SSH key management and wallet authentication)
|
||||
|
||||
### Step 1: Get an Invite Token
|
||||
### Setting Up Your First Node (Genesis)
|
||||
|
||||
An existing cluster node generates a single-use invite token:
|
||||
The first node creates a new cluster. Run this from your **local machine** (not the VPS):
|
||||
|
||||
```bash
|
||||
# On an existing node (requires sudo)
|
||||
sudo orama node invite --expiry 24h
|
||||
```
|
||||
|
||||
Tokens are single-use and expire after the specified duration (default: 1 hour).
|
||||
|
||||
### Step 2: Install the Node
|
||||
|
||||
**Genesis node (first node, creates a new cluster):**
|
||||
|
||||
```bash
|
||||
sudo orama node install \
|
||||
--vps-ip <PUBLIC_IP> \
|
||||
--domain <BASE_DOMAIN> \
|
||||
orama node setup \
|
||||
--ip <VPS_IP> \
|
||||
--password '<VPS_PASSWORD>' \
|
||||
--env devnet \
|
||||
--base-domain <BASE_DOMAIN> \
|
||||
--nameserver
|
||||
--role nameserver \
|
||||
--genesis
|
||||
```
|
||||
|
||||
**Nameserver node (joins existing cluster):**
|
||||
This single command:
|
||||
1. Creates an SSH key in your RootWallet vault
|
||||
2. Installs the SSH key on the VPS (using the password once)
|
||||
3. Uploads the binary archive
|
||||
4. Installs and starts all services
|
||||
5. Updates your local environment config to point to this node
|
||||
|
||||
After genesis, authenticate with the new cluster:
|
||||
|
||||
```bash
|
||||
sudo orama node install \
|
||||
--join http://<GENESIS_IP> --token <TOKEN> \
|
||||
--vps-ip <PUBLIC_IP> \
|
||||
--domain <BASE_DOMAIN> \
|
||||
orama auth login
|
||||
```
|
||||
|
||||
### Adding More Nodes
|
||||
|
||||
Once the genesis node is running and you're authenticated, add more nodes with one command:
|
||||
|
||||
```bash
|
||||
# Add a nameserver node
|
||||
orama node setup \
|
||||
--ip <VPS_IP> \
|
||||
--password '<VPS_PASSWORD>' \
|
||||
--env devnet \
|
||||
--base-domain <BASE_DOMAIN> \
|
||||
--nameserver
|
||||
```
|
||||
--role nameserver
|
||||
|
||||
**Regular node (joins existing cluster, domain is auto-generated):**
|
||||
|
||||
```bash
|
||||
sudo orama node install \
|
||||
--join http://<GENESIS_IP> --token <TOKEN> \
|
||||
--vps-ip <PUBLIC_IP> \
|
||||
# Add a regular node
|
||||
orama node setup \
|
||||
--ip <VPS_IP> \
|
||||
--password '<VPS_PASSWORD>' \
|
||||
--env devnet \
|
||||
--base-domain <BASE_DOMAIN>
|
||||
```
|
||||
|
||||
**Regular node with Anyone relay:**
|
||||
|
||||
```bash
|
||||
sudo orama node install \
|
||||
--join http://<GENESIS_IP> --token <TOKEN> \
|
||||
--vps-ip <PUBLIC_IP> \
|
||||
# Add a node with Anyone relay
|
||||
orama node setup \
|
||||
--ip <VPS_IP> \
|
||||
--password '<VPS_PASSWORD>' \
|
||||
--env devnet \
|
||||
--base-domain <BASE_DOMAIN> \
|
||||
--anyone-relay \
|
||||
--anyone-nickname <NAME> \
|
||||
--anyone-wallet <ETH_ADDRESS> \
|
||||
--anyone-contact "<EMAIL_OR_TELEGRAM>"
|
||||
--anyone-relay
|
||||
```
|
||||
|
||||
### Install Flags
|
||||
The setup command automatically:
|
||||
- Creates a unique SSH key for the node in RootWallet
|
||||
- Installs the key on the VPS (password is used once, then discarded)
|
||||
- Requests an invite token from the cluster (using your wallet authentication)
|
||||
- Uploads binaries and runs the full installation
|
||||
- Joins the node to the cluster
|
||||
|
||||
The `--password` flag is only needed for the initial SSH key installation. After that, all SSH access uses the RootWallet key.
|
||||
|
||||
### Setup Flags
|
||||
|
||||
| Flag | Required | Description |
|
||||
|------|----------|-------------|
|
||||
| `--vps-ip` | Yes | Public IP address of this VPS |
|
||||
| `--base-domain` | Yes | Base domain for deployment routing (e.g., `orama-devnet.network`) |
|
||||
| `--domain` | No | Domain for HTTPS. Auto-generated for non-nameserver nodes (e.g., `node-a3f8k2.orama-devnet.network`) |
|
||||
| `--nameserver` | No | Make this node a nameserver (runs CoreDNS + Caddy) |
|
||||
| `--join` | No | URL of an existing node to join (e.g., `http://1.2.3.4`) |
|
||||
| `--token` | No | Invite token from `orama node invite` on an existing node |
|
||||
| `--force` | No | Force reconfiguration even if already installed |
|
||||
| `--dry-run` | No | Show what would be done without making changes |
|
||||
| `--skip-checks` | No | Skip minimum resource checks (RAM/CPU) |
|
||||
| `--skip-firewall` | No | Skip UFW firewall setup (for users who manage their own firewall) |
|
||||
| `--ip` | Yes | Public IP address of the VPS |
|
||||
| `--password` | No | One-time VPS password for SSH key installation |
|
||||
| `--env` | No | Target environment (default: active environment) |
|
||||
| `--base-domain` | Yes | Base domain for the network (e.g., `orama-devnet.network`) |
|
||||
| `--role` | No | `node` (default) or `nameserver` |
|
||||
| `--user` | No | SSH user (default: `root`) |
|
||||
| `--gateway` | No | Gateway URL override for invite tokens |
|
||||
| `--genesis` | No | Create a new cluster (first node only) |
|
||||
| `--anyone-relay` | No | Run as Anyone relay operator |
|
||||
|
||||
#### Anyone Relay Flags
|
||||
|
||||
|
||||
@ -98,13 +98,46 @@ For detailed monitoring with alerts, use the full monitor:
|
||||
orama monitor report --env devnet
|
||||
```
|
||||
|
||||
## Pushing Binary Updates
|
||||
|
||||
Push a new binary archive to nodes without reinstalling:
|
||||
|
||||
```bash
|
||||
# Build the archive first
|
||||
orama build
|
||||
|
||||
# Push to a single node
|
||||
orama push --ip 1.2.3.4
|
||||
|
||||
# Push to all nodes in an environment
|
||||
orama push --env devnet
|
||||
```
|
||||
|
||||
## Adding New Nodes
|
||||
|
||||
Add a new VPS to your cluster with one command:
|
||||
|
||||
```bash
|
||||
orama node setup --ip <IP> --password '<PASS>' --env devnet --base-domain orama-devnet.network
|
||||
```
|
||||
|
||||
This creates an SSH key in RootWallet, installs it on the VPS, uploads binaries, requests an invite token, and joins the node to the cluster. The `--password` is used once for initial SSH access — after that, RootWallet handles authentication.
|
||||
|
||||
For the first node in a new cluster, add `--genesis`:
|
||||
|
||||
```bash
|
||||
orama node setup --ip <IP> --password '<PASS>' --env devnet \
|
||||
--base-domain orama-devnet.network --role nameserver --genesis
|
||||
```
|
||||
|
||||
## How Node Ownership Works
|
||||
|
||||
When you install a node (via `orama node install` with an invite token), the network records your wallet address as the node's operator. This happens automatically through the invite token system:
|
||||
When you set up a node via `orama node setup`, the CLI:
|
||||
|
||||
1. You generate an invite token (authenticated with your wallet)
|
||||
2. The new node joins using that token
|
||||
3. The network tags the node with your wallet address from the token
|
||||
1. Authenticates with the cluster using your wallet (via RootWallet)
|
||||
2. Requests an invite token tagged with your wallet address
|
||||
3. The new node joins using that token
|
||||
4. The network records your wallet as the node's operator
|
||||
|
||||
This means `orama nodes` can query the network for "all nodes owned by my wallet" — no local files to maintain.
|
||||
|
||||
@ -124,8 +157,10 @@ The legacy `nodes.conf` continues to work as a fallback — you can migrate at y
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `orama node setup --ip <ip>` | Set up a new node (SSH key + install + join) |
|
||||
| `orama nodes [--env]` | List your nodes |
|
||||
| `orama ssh <ip> [--env]` | SSH into a node |
|
||||
| `orama status [--env] [--json]` | Health check all nodes |
|
||||
| `orama push [--ip <ip>] [--env]` | Push binary archive to nodes |
|
||||
| `orama rollout [--env] [--delay]` | Rolling upgrade all nodes |
|
||||
| `orama node migrate-conf [--env]` | Migrate nodes.conf to wallet-based tracking |
|
||||
|
||||
@ -2,7 +2,30 @@
|
||||
|
||||
This guide walks you through the full process of setting up an Orama node on a fresh VPS — from system requirements through installation, verification, and day-to-day lifecycle management.
|
||||
|
||||
> **Tip:** After installation, manage your nodes with the unified CLI commands: `orama nodes`, `orama ssh`, `orama status`, `orama rollout`. See [Node Management](/docs/operator/node-management) for details.
|
||||
## Quick Start
|
||||
|
||||
The fastest way to set up a node is with `orama node setup` from your local machine:
|
||||
|
||||
```bash
|
||||
# First node (creates cluster)
|
||||
orama node setup --ip <IP> --password '<PASS>' --env devnet \
|
||||
--base-domain orama-devnet.network --role nameserver --genesis
|
||||
|
||||
# Authenticate with the new cluster
|
||||
orama auth login
|
||||
|
||||
# Add more nodes (invite token handled automatically)
|
||||
orama node setup --ip <IP> --password '<PASS>' --env devnet \
|
||||
--base-domain orama-devnet.network
|
||||
```
|
||||
|
||||
This handles everything: SSH key management (via RootWallet), binary upload, invite tokens, and installation. See [Getting Started](/docs/operator/getting-started) for the full walkthrough.
|
||||
|
||||
> **Tip:** After installation, manage your nodes with: `orama nodes`, `orama ssh`, `orama status`, `orama rollout`, `orama push`. See [Node Management](/docs/operator/node-management) for details.
|
||||
|
||||
## Manual Setup
|
||||
|
||||
The sections below describe the manual installation process for advanced users or environments where `orama node setup` is not suitable.
|
||||
|
||||
## System Requirements
|
||||
|
||||
|
||||
@ -147,19 +147,34 @@ orama node rollout --env testnet --no-build --yes --delay 60
|
||||
|
||||
---
|
||||
|
||||
## Push Command
|
||||
## Push Command (Recommended)
|
||||
|
||||
The `orama node push` command uploads the binary to nodes without restarting services. Useful for pre-staging a binary before a maintenance window.
|
||||
The `orama push` command uploads the binary archive to nodes without restarting services. Useful for pre-staging a binary before a maintenance window. Uses RootWallet for SSH authentication.
|
||||
|
||||
```bash
|
||||
# Push to all nodes in an environment
|
||||
orama node push --env devnet
|
||||
|
||||
# Push to a single node
|
||||
orama node push --env testnet --node 1.2.3.4
|
||||
orama push --ip 1.2.3.4
|
||||
|
||||
# Upload directly to each node (no hub fanout)
|
||||
orama node push --env testnet --direct
|
||||
# Push to all nodes in an environment
|
||||
orama push --env devnet
|
||||
|
||||
# Push with a specific SSH user
|
||||
orama push --ip 1.2.3.4 --user ubuntu
|
||||
```
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `--ip` | | Push to a single node by IP |
|
||||
| `--env` | active env | Push to all nodes in environment |
|
||||
| `--user` | `root` | SSH user |
|
||||
|
||||
### Legacy Push
|
||||
|
||||
The `orama node push` command is still available and uses `nodes.conf`:
|
||||
|
||||
```bash
|
||||
orama node push --env devnet
|
||||
orama node push --env testnet --node 1.2.3.4
|
||||
```
|
||||
|
||||
| Flag | Default | Description |
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user