mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 09:36:56 +00:00
Refactor installation scripts and improve security measures
- Updated `clean-testnet.sh` to stop and disable legacy services. - Added `upload-source.sh` for streamlined source archive uploads. - Enhanced password input handling in `ssh.go` for better security. - Adjusted directory permissions in `validator.go` for improved security. - Simplified node configuration logic in `config.go`. - Removed unnecessary commands from `gateway.go` to streamline installation.
This commit is contained in:
parent
25a167f9b4
commit
7163aad850
@ -437,59 +437,12 @@ func (o *Orchestrator) verifyWGTunnel(peers []joinhandlers.WGPeerInfo) error {
|
||||
return fmt.Errorf("could not reach %s via WireGuard after 30s", targetIP)
|
||||
}
|
||||
|
||||
func (o *Orchestrator) buildIPFSPeerInfo() *production.IPFSPeerInfo {
|
||||
if o.flags.IPFSPeerID != "" {
|
||||
var addrs []string
|
||||
if o.flags.IPFSAddrs != "" {
|
||||
addrs = strings.Split(o.flags.IPFSAddrs, ",")
|
||||
}
|
||||
return &production.IPFSPeerInfo{
|
||||
PeerID: o.flags.IPFSPeerID,
|
||||
Addrs: addrs,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Orchestrator) buildIPFSClusterPeerInfo() *production.IPFSClusterPeerInfo {
|
||||
if o.flags.IPFSClusterPeerID != "" {
|
||||
var addrs []string
|
||||
if o.flags.IPFSClusterAddrs != "" {
|
||||
addrs = strings.Split(o.flags.IPFSClusterAddrs, ",")
|
||||
}
|
||||
return &production.IPFSClusterPeerInfo{
|
||||
PeerID: o.flags.IPFSClusterPeerID,
|
||||
Addrs: addrs,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Orchestrator) printFirstNodeSecrets() {
|
||||
fmt.Printf("📋 Save these for joining future nodes:\n\n")
|
||||
|
||||
// Print cluster secret
|
||||
clusterSecretPath := filepath.Join(o.oramaDir, "secrets", "cluster-secret")
|
||||
if clusterSecretData, err := os.ReadFile(clusterSecretPath); err == nil {
|
||||
fmt.Printf(" Cluster Secret (--cluster-secret):\n")
|
||||
fmt.Printf(" %s\n\n", string(clusterSecretData))
|
||||
}
|
||||
|
||||
// Print swarm key
|
||||
swarmKeyPath := filepath.Join(o.oramaDir, "secrets", "swarm.key")
|
||||
if swarmKeyData, err := os.ReadFile(swarmKeyPath); err == nil {
|
||||
swarmKeyContent := strings.TrimSpace(string(swarmKeyData))
|
||||
lines := strings.Split(swarmKeyContent, "\n")
|
||||
if len(lines) >= 3 {
|
||||
// Extract just the hex part (last line)
|
||||
fmt.Printf(" IPFS Swarm Key (--swarm-key, last line only):\n")
|
||||
fmt.Printf(" %s\n\n", lines[len(lines)-1])
|
||||
}
|
||||
}
|
||||
|
||||
// Print peer ID
|
||||
fmt.Printf(" Node Peer ID:\n")
|
||||
fmt.Printf(" %s\n\n", o.setup.NodePeerID)
|
||||
fmt.Printf("📋 To add more nodes to this cluster:\n\n")
|
||||
fmt.Printf(" 1. Generate an invite token:\n")
|
||||
fmt.Printf(" orama invite\n\n")
|
||||
fmt.Printf(" 2. Run the printed command on the new VPS.\n\n")
|
||||
fmt.Printf(" Node Peer ID: %s\n\n", o.setup.NodePeerID)
|
||||
}
|
||||
|
||||
// promptForBaseDomain interactively prompts the user to select a network environment
|
||||
|
||||
@ -2,7 +2,6 @@ package install
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -13,9 +12,8 @@ import (
|
||||
// It uploads the source archive, extracts it on the VPS, and runs
|
||||
// the actual install command remotely.
|
||||
type RemoteOrchestrator struct {
|
||||
flags *Flags
|
||||
node inspector.Node
|
||||
archive string
|
||||
flags *Flags
|
||||
node inspector.Node
|
||||
}
|
||||
|
||||
// NewRemoteOrchestrator creates a new remote orchestrator.
|
||||
@ -25,11 +23,6 @@ func NewRemoteOrchestrator(flags *Flags) (*RemoteOrchestrator, error) {
|
||||
return nil, fmt.Errorf("--vps-ip is required\nExample: orama install --vps-ip 1.2.3.4 --nameserver --domain orama-testnet.network")
|
||||
}
|
||||
|
||||
// Check source archive exists
|
||||
if _, err := os.Stat(sourceArchivePath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("source archive not found at %s\nRun: make build-linux && ./scripts/generate-source-archive.sh", sourceArchivePath)
|
||||
}
|
||||
|
||||
// Resolve SSH credentials
|
||||
node, err := resolveSSHCredentials(flags.VpsIP)
|
||||
if err != nil {
|
||||
@ -37,31 +30,17 @@ func NewRemoteOrchestrator(flags *Flags) (*RemoteOrchestrator, error) {
|
||||
}
|
||||
|
||||
return &RemoteOrchestrator{
|
||||
flags: flags,
|
||||
node: node,
|
||||
archive: sourceArchivePath,
|
||||
flags: flags,
|
||||
node: node,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Execute runs the full remote install process.
|
||||
// Execute runs the remote install process.
|
||||
// Source must already be uploaded via: ./scripts/upload-source.sh <vps-ip>
|
||||
func (r *RemoteOrchestrator) Execute() error {
|
||||
fmt.Printf("Installing on %s via SSH (%s@%s)...\n\n", r.flags.VpsIP, r.node.User, r.node.Host)
|
||||
|
||||
// Step 1: Upload archive
|
||||
fmt.Printf("Uploading source archive...\n")
|
||||
if err := r.uploadArchive(); err != nil {
|
||||
return fmt.Errorf("upload failed: %w", err)
|
||||
}
|
||||
fmt.Printf(" Done.\n\n")
|
||||
|
||||
// Step 2: Extract on VPS
|
||||
fmt.Printf("Extracting on VPS...\n")
|
||||
if err := r.extractOnVPS(); err != nil {
|
||||
return fmt.Errorf("extract failed: %w", err)
|
||||
}
|
||||
fmt.Printf(" Done.\n\n")
|
||||
|
||||
// Step 3: Run remote install
|
||||
// Run remote install
|
||||
fmt.Printf("Running install on VPS...\n\n")
|
||||
if err := r.runRemoteInstall(); err != nil {
|
||||
return err
|
||||
@ -70,31 +49,6 @@ func (r *RemoteOrchestrator) Execute() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// uploadArchive copies the source archive to the VPS.
|
||||
func (r *RemoteOrchestrator) uploadArchive() error {
|
||||
return uploadFile(r.node, r.archive, "/tmp/network-source.tar.gz")
|
||||
}
|
||||
|
||||
// extractOnVPS runs extract-deploy.sh on the VPS.
|
||||
func (r *RemoteOrchestrator) extractOnVPS() error {
|
||||
// Extract source archive and install only the CLI binary.
|
||||
// All other binaries are built from source on the VPS during install.
|
||||
extractCmd := r.sudoPrefix() + "bash -c '" +
|
||||
`ARCHIVE="/tmp/network-source.tar.gz" && ` +
|
||||
`SRC_DIR="/opt/orama/src" && ` +
|
||||
`BIN_DIR="/opt/orama/bin" && ` +
|
||||
`rm -rf "$SRC_DIR" && mkdir -p "$SRC_DIR" "$BIN_DIR" && ` +
|
||||
`tar xzf "$ARCHIVE" -C "$SRC_DIR" && ` +
|
||||
// Install pre-built CLI binary (only binary cross-compiled locally)
|
||||
`if [ -f "$SRC_DIR/bin-linux/orama" ]; then ` +
|
||||
`cp "$SRC_DIR/bin-linux/orama" /usr/local/bin/orama && ` +
|
||||
`chmod +x /usr/local/bin/orama; fi && ` +
|
||||
`echo "Extract complete."` +
|
||||
"'"
|
||||
|
||||
return runSSHStreaming(r.node, extractCmd)
|
||||
}
|
||||
|
||||
// runRemoteInstall executes `orama install` on the VPS.
|
||||
func (r *RemoteOrchestrator) runRemoteInstall() error {
|
||||
cmd := r.buildRemoteCommand()
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/inspector"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const sourceArchivePath = "/tmp/network-source.tar.gz"
|
||||
@ -65,8 +66,18 @@ func promptSSHCredentials(vpsIP string) inspector.Node {
|
||||
}
|
||||
|
||||
fmt.Print(" SSH password: ")
|
||||
password, _ := reader.ReadString('\n')
|
||||
password = strings.TrimSpace(password)
|
||||
passwordBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Println() // newline after hidden input
|
||||
if err != nil {
|
||||
// Fall back to plain read if terminal is not available
|
||||
password, _ := reader.ReadString('\n')
|
||||
return inspector.Node{
|
||||
User: user,
|
||||
Host: vpsIP,
|
||||
Password: strings.TrimSpace(password),
|
||||
}
|
||||
}
|
||||
password := string(passwordBytes)
|
||||
|
||||
return inspector.Node{
|
||||
User: user,
|
||||
|
||||
@ -84,7 +84,7 @@ func (v *Validator) SaveSecrets() error {
|
||||
// If cluster secret was provided, save it to secrets directory before setup
|
||||
if v.flags.ClusterSecret != "" {
|
||||
secretsDir := filepath.Join(v.oramaDir, "secrets")
|
||||
if err := os.MkdirAll(secretsDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(secretsDir, 0700); err != nil {
|
||||
return fmt.Errorf("failed to create secrets directory: %w", err)
|
||||
}
|
||||
secretPath := filepath.Join(secretsDir, "cluster-secret")
|
||||
@ -97,7 +97,7 @@ func (v *Validator) SaveSecrets() error {
|
||||
// If swarm key was provided, save it to secrets directory in full format
|
||||
if v.flags.SwarmKey != "" {
|
||||
secretsDir := filepath.Join(v.oramaDir, "secrets")
|
||||
if err := os.MkdirAll(secretsDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(secretsDir, 0700); err != nil {
|
||||
return fmt.Errorf("failed to create secrets directory: %w", err)
|
||||
}
|
||||
// Extract hex only (strips headers if user passed full file content)
|
||||
|
||||
@ -178,15 +178,13 @@ func (cg *ConfigGenerator) GenerateNodeConfig(peerAddresses []string, vpsIP stri
|
||||
WGIP: vpsIP,
|
||||
}
|
||||
|
||||
// Set MinClusterSize based on whether this is a genesis or joining node.
|
||||
// Genesis nodes (no join address) bootstrap alone, so MinClusterSize=1.
|
||||
// Joining nodes should wait for at least 2 remote peers before writing peers.json
|
||||
// to prevent accidental solo bootstrap during mass restarts.
|
||||
if rqliteJoinAddr != "" {
|
||||
data.MinClusterSize = 3
|
||||
} else {
|
||||
data.MinClusterSize = 1
|
||||
}
|
||||
// MinClusterSize=1 for all nodes. Joining nodes use the -join flag to
|
||||
// connect to the existing cluster; gating on peer discovery caused a
|
||||
// deadlock where the WG sync loop (needs RQLite) couldn't add new peers
|
||||
// and RQLite (needs WG peers discovered) couldn't start.
|
||||
// Solo-bootstrap protection is already handled by performPreStartClusterDiscovery
|
||||
// which refuses to write a single-node peers.json.
|
||||
data.MinClusterSize = 1
|
||||
|
||||
// RQLite node-to-node TLS encryption is disabled by default
|
||||
// This simplifies certificate management - RQLite uses plain TCP for internal Raft
|
||||
|
||||
@ -117,20 +117,6 @@ func (gi *GatewayInstaller) InstallDeBrosBinaries(oramaHome string) error {
|
||||
if err := exec.Command("chmod", "-R", "755", binDir).Run(); err != nil {
|
||||
fmt.Fprintf(gi.logWriter, " ⚠️ Warning: failed to chmod bin directory: %v\n", err)
|
||||
}
|
||||
if err := exec.Command("chown", "-R", "orama:orama", binDir).Run(); err != nil {
|
||||
fmt.Fprintf(gi.logWriter, " ⚠️ Warning: failed to chown bin directory: %v\n", err)
|
||||
}
|
||||
|
||||
// Grant CAP_NET_BIND_SERVICE to orama-node to allow binding to ports 80/443 without root
|
||||
nodeBinary := filepath.Join(binDir, "orama-node")
|
||||
if _, err := os.Stat(nodeBinary); err == nil {
|
||||
if err := exec.Command("setcap", "cap_net_bind_service=+ep", nodeBinary).Run(); err != nil {
|
||||
fmt.Fprintf(gi.logWriter, " ⚠️ Warning: failed to setcap on orama-node: %v\n", err)
|
||||
fmt.Fprintf(gi.logWriter, " ⚠️ Gateway may not be able to bind to port 80/443\n")
|
||||
} else {
|
||||
fmt.Fprintf(gi.logWriter, " ✓ Set CAP_NET_BIND_SERVICE on orama-node\n")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(gi.logWriter, " ✓ Orama binaries installed\n")
|
||||
return nil
|
||||
@ -230,22 +216,10 @@ func (gi *GatewayInstaller) InstallAnyoneClient() error {
|
||||
fmt.Fprintf(gi.logWriter, " ⚠️ Failed to create %s: %v\n", dir, err)
|
||||
continue
|
||||
}
|
||||
// Fix ownership to orama user (sequential to avoid race conditions)
|
||||
if err := exec.Command("chown", "orama:orama", dir).Run(); err != nil {
|
||||
fmt.Fprintf(gi.logWriter, " ⚠️ Warning: failed to chown %s: %v\n", dir, err)
|
||||
}
|
||||
if err := exec.Command("chmod", "700", dir).Run(); err != nil {
|
||||
fmt.Fprintf(gi.logWriter, " ⚠️ Warning: failed to chmod %s: %v\n", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively fix ownership of entire .npm directory to ensure all nested files are owned by orama
|
||||
if err := exec.Command("chown", "-R", "orama:orama", filepath.Join(oramaHome, ".npm")).Run(); err != nil {
|
||||
fmt.Fprintf(gi.logWriter, " ⚠️ Warning: failed to chown .npm directory: %v\n", err)
|
||||
}
|
||||
|
||||
// Run npm cache verify as orama user with proper environment
|
||||
cacheInitCmd := exec.Command("sudo", "-u", "orama", "npm", "cache", "verify", "--silent")
|
||||
// Run npm cache verify
|
||||
cacheInitCmd := exec.Command("npm", "cache", "verify", "--silent")
|
||||
cacheInitCmd.Env = append(os.Environ(), "HOME="+oramaHome)
|
||||
if err := cacheInitCmd.Run(); err != nil {
|
||||
fmt.Fprintf(gi.logWriter, " ⚠️ NPM cache verify warning: %v (continuing anyway)\n", err)
|
||||
@ -261,10 +235,6 @@ func (gi *GatewayInstaller) InstallAnyoneClient() error {
|
||||
termsFile := filepath.Join(oramaHome, "terms-agreement")
|
||||
if err := os.WriteFile(termsFile, []byte("agreed"), 0644); err != nil {
|
||||
fmt.Fprintf(gi.logWriter, " ⚠️ Warning: failed to create terms-agreement: %v\n", err)
|
||||
} else {
|
||||
if err := exec.Command("chown", "orama:orama", termsFile).Run(); err != nil {
|
||||
fmt.Fprintf(gi.logWriter, " ⚠️ Warning: failed to chown terms-agreement: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify installation - try npx with the correct CLI name (anyone-client, not full scoped package name)
|
||||
|
||||
@ -31,9 +31,13 @@ export DEBIAN_FRONTEND=noninteractive
|
||||
echo " Stopping services..."
|
||||
systemctl stop orama-node orama-gateway orama-ipfs orama-ipfs-cluster orama-olric orama-anyone-relay orama-anyone-client coredns caddy 2>/dev/null || true
|
||||
systemctl disable orama-node orama-gateway orama-ipfs orama-ipfs-cluster orama-olric orama-anyone-relay orama-anyone-client coredns caddy 2>/dev/null || true
|
||||
# Legacy debros-* services (pre-rename)
|
||||
systemctl stop debros-anyone-relay debros-anyone-client 2>/dev/null || true
|
||||
systemctl disable debros-anyone-relay debros-anyone-client 2>/dev/null || true
|
||||
|
||||
echo " Removing systemd service files..."
|
||||
rm -f /etc/systemd/system/orama-*.service
|
||||
rm -f /etc/systemd/system/debros-*.service
|
||||
rm -f /etc/systemd/system/coredns.service
|
||||
rm -f /etc/systemd/system/caddy.service
|
||||
rm -f /etc/systemd/system/orama-deploy-*.service
|
||||
|
||||
103
scripts/upload-source.sh
Executable file
103
scripts/upload-source.sh
Executable file
@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
# Upload and extract the source archive to one or more VPS nodes.
|
||||
#
|
||||
# Prerequisites:
|
||||
# make build-linux
|
||||
# ./scripts/generate-source-archive.sh
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/upload-source.sh <vps-ip> [<vps-ip2> ...]
|
||||
# ./scripts/upload-source.sh --env testnet # upload to all testnet nodes
|
||||
#
|
||||
# After uploading, run install:
|
||||
# ./bin/orama install --vps-ip <ip> --nameserver --domain ...
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ARCHIVE="/tmp/network-source.tar.gz"
|
||||
CONF="$SCRIPT_DIR/remote-nodes.conf"
|
||||
|
||||
if [ ! -f "$ARCHIVE" ]; then
|
||||
echo "Error: $ARCHIVE not found"
|
||||
echo "Run: make build-linux && ./scripts/generate-source-archive.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Resolve VPS list from --env flag or direct IPs
|
||||
resolve_nodes() {
|
||||
if [ "$1" = "--env" ] && [ -n "$2" ] && [ -f "$CONF" ]; then
|
||||
grep "^$2|" "$CONF" | while IFS='|' read -r env userhost pass role; do
|
||||
local user="${userhost%%@*}"
|
||||
local host="${userhost##*@}"
|
||||
echo "$user|$host|$pass"
|
||||
done
|
||||
return
|
||||
fi
|
||||
|
||||
# Direct IPs — look up credentials from conf
|
||||
for ip in "$@"; do
|
||||
if [ -f "$CONF" ]; then
|
||||
local match
|
||||
match=$(grep "|[^|]*@${ip}|" "$CONF" | head -1)
|
||||
if [ -n "$match" ]; then
|
||||
local userhost pass
|
||||
userhost=$(echo "$match" | cut -d'|' -f2)
|
||||
pass=$(echo "$match" | cut -d'|' -f3)
|
||||
local user="${userhost%%@*}"
|
||||
echo "$user|$ip|$pass"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
# Fallback: prompt for credentials
|
||||
echo "ubuntu|$ip|"
|
||||
done
|
||||
}
|
||||
|
||||
upload_to_node() {
|
||||
local user="$1" host="$2" pass="$3"
|
||||
|
||||
echo "→ Uploading to $user@$host..."
|
||||
|
||||
# Upload archive
|
||||
if [ -n "$pass" ]; then
|
||||
sshpass -p "$pass" scp -o StrictHostKeyChecking=no -o ConnectTimeout=10 \
|
||||
-o PreferredAuthentications=password -o PubkeyAuthentication=no \
|
||||
"$ARCHIVE" "$user@$host:/tmp/network-source.tar.gz"
|
||||
else
|
||||
scp -o StrictHostKeyChecking=no -o ConnectTimeout=10 \
|
||||
"$ARCHIVE" "$user@$host:/tmp/network-source.tar.gz"
|
||||
fi
|
||||
|
||||
# Extract on VPS
|
||||
local sudo_prefix=""
|
||||
[ "$user" != "root" ] && sudo_prefix="sudo "
|
||||
|
||||
local extract_cmd="${sudo_prefix}bash -c 'rm -rf /opt/orama/src && mkdir -p /opt/orama/src /opt/orama/bin && tar xzf /tmp/network-source.tar.gz -C /opt/orama/src 2>/dev/null && if [ -f /opt/orama/src/bin-linux/orama ]; then cp /opt/orama/src/bin-linux/orama /usr/local/bin/orama && chmod +x /usr/local/bin/orama; fi && echo \" ✓ Extracted (\$(ls /opt/orama/src/ | wc -l) files)\"'"
|
||||
|
||||
if [ -n "$pass" ]; then
|
||||
sshpass -p "$pass" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 \
|
||||
-o PreferredAuthentications=password -o PubkeyAuthentication=no \
|
||||
"$user@$host" "$extract_cmd"
|
||||
else
|
||||
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 \
|
||||
"$user@$host" "$extract_cmd"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Usage: $0 <vps-ip> [<vps-ip2> ...]"
|
||||
echo " $0 --env testnet"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Source archive: $ARCHIVE ($(du -h "$ARCHIVE" | cut -f1))"
|
||||
echo ""
|
||||
|
||||
resolve_nodes "$@" | while IFS='|' read -r user host pass; do
|
||||
upload_to_node "$user" "$host" "$pass"
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "Done. Now run: ./bin/orama install --vps-ip <ip> ..."
|
||||
Loading…
x
Reference in New Issue
Block a user