feat(init): interactive remote pre-staging via ssh-copy-id
orca init now interactively prompts for remote host addresses and runs ssh-copy-id automatically (password prompt passes through to the operator). This makes orca init the single entry point — no manual pre-staging of SSH keys required. - Interactive: enter host addresses (one per line, empty line to finish) - ssh-copy-id deploys the orca public key to each host - Skipped in --json mode (non-interactive) - Idempotent: re-running init can stage additional hosts Also fixed: install.sh defaults to /usr/local/bin (on PATH for all users). Non-root without sudo falls back to ~/.local/bin + auto-adds to .bashrc.
This commit is contained in:
+48
-7
@@ -5,15 +5,15 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"bufio"
|
||||
"os"
|
||||
"strings"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"git.cloudinit.dev/coreci/orca/internal/acl"
|
||||
"git.cloudinit.dev/coreci/orca/internal/certpaths"
|
||||
"git.cloudinit.dev/coreci/orca/internal/identity"
|
||||
@@ -63,8 +63,7 @@ func runInit(out interface{ Write([]byte) (int, error) }) error {
|
||||
Database string `json:"database"`
|
||||
CAFingerprint string `json:"ca_fingerprint,omitempty"`
|
||||
CertFingerprint string `json:"cert_fingerprint,omitempty"`
|
||||
OS string `json:"os"
|
||||
"path/filepath"`
|
||||
OS string `json:"os"`
|
||||
NodeID string `json:"node_id"`
|
||||
NodeName string `json:"node_name"`
|
||||
Steps []stepResult `json:"steps"`
|
||||
@@ -158,8 +157,50 @@ func runInit(out interface{ Write([]byte) (int, error) }) error {
|
||||
summary.Steps = append(summary.Steps, stepResult{Label: "ssh-key", Status: "ok", Detail: sshKeyFp[:min(16, len(sshKeyFp))] + "..."})
|
||||
if !jsonOutput {
|
||||
fmt.Fprintf(out, "\xe2\x9c\x93 SSH keypair provisioned: fp=%s\n", sshKeyFp[:min(16, len(sshKeyFp))]+"...")
|
||||
fmt.Fprintf(out, "\n To onboard remote nodes, deploy the orca public key first:\n")
|
||||
fmt.Fprintf(out, " ssh-copy-id -i %s root@<remote-host>\n\n", certpaths.SSHPubPath())
|
||||
}
|
||||
|
||||
// Step 4a-2: Interactively pre-stage the orca public key on remote
|
||||
// hosts (ssh-copy-id). Skipped in --json mode (non-interactive).
|
||||
// The operator enters host addresses (one per line, empty line to
|
||||
// finish). For each host, ssh-copy-id is run; if SSH key auth is
|
||||
// not yet established, ssh-copy-id prompts for the password
|
||||
// interactively. This makes orca init the single entry point —
|
||||
// no manual pre-staging required.
|
||||
if !jsonOutput {
|
||||
pubPath := certpaths.SSHPubPath()
|
||||
fmt.Fprintf(out, "\n Pre-stage the orca public key on remote nodes.\n")
|
||||
fmt.Fprintf(out, " Enter host addresses (one per line, empty line to skip):\n")
|
||||
stagedHosts := []string{}
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Fprintf(out, " host> ")
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
host := strings.TrimSpace(line)
|
||||
if host == "" {
|
||||
break
|
||||
}
|
||||
// Run ssh-copy-id interactively (password prompt passes through).
|
||||
fmt.Fprintf(out, " Deploying orca key to %s...\n", host)
|
||||
cmd := exec.Command("ssh-copy-id", "-i", pubPath, "-o", "StrictHostKeyChecking=accept-new", "root@"+host)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(out, " \xe2\x9a\xa0 Failed to deploy key to %s: %v\n", host, err)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(out, " \xe2\x9c\x93 Key deployed to %s\n", host)
|
||||
stagedHosts = append(stagedHosts, host)
|
||||
}
|
||||
if len(stagedHosts) > 0 {
|
||||
summary.Steps = append(summary.Steps, stepResult{Label: "pre-stage", Status: "ok", Detail: strings.Join(stagedHosts, ", ")})
|
||||
} else {
|
||||
summary.Steps = append(summary.Steps, stepResult{Label: "pre-stage", Status: "skipped", Detail: "no hosts entered"})
|
||||
}
|
||||
fmt.Fprintf(out, "\n")
|
||||
}
|
||||
|
||||
// Step 4b: known_hosts file (empty, 0600). Without this, the TOFU
|
||||
|
||||
Reference in New Issue
Block a user