diff --git a/README.md b/README.md index 0ca67f9..35d6eaa 100644 --- a/README.md +++ b/README.md @@ -63,15 +63,12 @@ For detailed instructions & manual setup options see [Getting Started](docs/USER ### Uninstall -1. If you installed shell helpers, remove them first: - ```bash - nssh self uninstall - # add --dry-run to preview what would be removed - ``` -2. Remove the binary: - ```bash - rm ~/.local/bin/nssh - ``` +```bash +nssh self uninstall +# add --dry-run to preview what would be removed +``` + +This removes shell integration, the binary, config, and recordings. Use `--keep-config` or `--keep-recordings` to preserve specific data. ## Learn More diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 3251297..5b154cb 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -612,7 +612,7 @@ If your `age.pub` is missing or corrupted, you can regenerate it: nssh self rekey --repair-pubkey ``` #### self reinstall -`reinstall` is intended for development workflows (build from source and refresh shell integration): +`reinstall` downloads and installs the latest release from GitHub: ```bash nssh self reinstall ``` @@ -620,7 +620,12 @@ Hardware builds (YubiKey/PIV support): ```bash nssh self reinstall --hardware ``` -If the hardware build fails, you likely need CGO and PC/SC libraries: +For development workflows (build from source): +```bash +nssh self reinstall --dev +nssh self reinstall --dev --hardware +``` +If the hardware dev build fails, you likely need CGO and PC/SC libraries: - macOS: PCSC.framework is built in - Linux: install `pcscd` and development headers (distro-specific) #### self bench @@ -754,14 +759,16 @@ If smart connect says it can’t find a host: ``` - Ensure `asciinema` is installed and available on PATH. - Check host include/exclude patterns in `[logging.session]`. -### “Hardware support not compiled into this binary” +### "Hardware support not compiled into this binary" -You’re running a non-hardware build. Rebuild with hardware support: +You're running a non-hardware build. Install the hardware build: ```bash nssh self reinstall --hardware ``` -Or build manually: +Or build from source: ```bash +nssh self reinstall --dev --hardware +# or manually: go build -tags hardware ./cmd/nssh ``` ### Performance questions diff --git a/docs/examples/help/self.txt b/docs/examples/help/self.txt index 6eee945..44f4f7e 100644 --- a/docs/examples/help/self.txt +++ b/docs/examples/help/self.txt @@ -6,7 +6,7 @@ $ nssh self --help │ bench Run performance benchmarks │ │ init Initialize nssh │ │ piv Manage enrolled YubiKeys │ -│ reinstall Reinstall from source │ +│ reinstall Reinstall nssh from latest GitHub r... │ │ rekey Rotate or switch security mode │ │ reset Delete all nssh data and start fresh │ │ status Show installation status │ diff --git a/docs/examples/help/self/reinstall.txt b/docs/examples/help/self/reinstall.txt index cb4c323..0b5cee5 100644 --- a/docs/examples/help/self/reinstall.txt +++ b/docs/examples/help/self/reinstall.txt @@ -1,8 +1,9 @@ $ nssh self reinstall --help ╭─ Usage ──────────────────────────────────────────────────────────────────────╮ -│ nssh self reinstall [flags] Reinstall from source │ +│ nssh self reinstall [flags] Reinstall nssh from latest GitHub r... │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Options ────────────────────────────────────────────────────────────────────╮ +│ --dev build from source instead of downlo... │ │ --hardware build with hardware security suppor... │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Global Options ─────────────────────────────────────────────────────────────╮ diff --git a/internal/cli/self/reinstall.go b/internal/cli/self/reinstall.go index 66a457f..1b8f0a6 100644 --- a/internal/cli/self/reinstall.go +++ b/internal/cli/self/reinstall.go @@ -11,40 +11,103 @@ import ( "github.com/spf13/cobra" ) +const installScriptURL = "https://raw.githubusercontent.com/ntwrknrd/nssh/main/scripts/install.sh" + // NewReinstallCmd creates the reinstall subcommand. func NewReinstallCmd() *cobra.Command { + var dev bool var hardware bool cmd := &cobra.Command{ Use: "reinstall", - Short: "Reinstall from source", - Long: `Rebuild nssh from source and refresh shell integration. + Short: "Reinstall nssh from latest GitHub release", + Long: `Download and install the latest nssh release from GitHub. -This command is intended for development workflows: -1. Finds the project root (directory containing go.mod) -2. Runs 'go build' to create a new binary +This command: +1. Downloads the install script from GitHub +2. Installs the latest release binary to ~/.local/bin 3. Refreshes shell integration -Must be run from within the nssh project directory. -Use --hardware to build with YubiKey/PIV support (requires CGO + PC/SC libs). +Use --hardware for YubiKey/PIV support. +Use --dev to build from source instead (for development workflows). To reinitialize credentials, use 'nssh self rekey' or 'nssh self reset'.`, RunE: func(cmd *cobra.Command, args []string) error { - return runReinstall(hardware) + if dev { + return runReinstallDev(hardware) + } + return runReinstallRelease(hardware) }, } + cmd.Flags().BoolVar(&dev, "dev", false, "build from source instead of downloading release") cmd.Flags().BoolVar(&hardware, "hardware", false, "build with hardware security support (YubiKey/PIV)") return cmd } -func runReinstall(hardware bool) error { +func runReinstallRelease(hardware bool) error { ui.CommandStart("REINSTALL NSSH") // Track warnings for final status hasWarnings := false + // Run install script from GitHub + ui.SubSection("Download and Install") + ui.Info("Fetching latest release from GitHub...") + + // Build install command with optional --hardware flag + var shellCmd string + if hardware { + shellCmd = fmt.Sprintf("curl -fsSL %s | sh -s -- --hardware", installScriptURL) + } else { + shellCmd = fmt.Sprintf("curl -fsSL %s | sh", installScriptURL) + } + + // Use sh -c to pipe curl output to sh + installCmd := exec.Command("sh", "-c", shellCmd) + installCmd.Stdout = os.Stdout + installCmd.Stderr = os.Stderr + + if err := installCmd.Run(); err != nil { + ui.Error("Installation failed: %v", err) + ui.CommandEnd(ui.StatusError) + return &exit.ExitError{Code: 1} + } + + // Refresh shell integration + ui.SubSection("Shell Integration") + if err := RunInitQuiet(false); err != nil { + ui.Warning("Shell integration refresh failed: %v", err) + hasWarnings = true + } else { + ui.Success("Shell integration refreshed") + } + + // Check if ~/.local/bin is on PATH + installDir := filepath.Join(homeDir(), ".local", "bin") + if FindBinary() == "" { + fmt.Println() + ui.Warning("Add to PATH: export PATH=\"%s:$PATH\"", AbbreviatePath(installDir)) + hasWarnings = true + } + + // Footer with appropriate status + if hasWarnings { + ui.CommandEnd(ui.StatusWarning) + } else { + ui.CommandEnd(ui.StatusSuccess) + } + + return nil +} + +func runReinstallDev(hardware bool) error { + ui.CommandStart("REINSTALL NSSH (DEV)") + + // Track warnings for final status + hasWarnings := false + // Find project root projectRoot := FindProjectRoot() if projectRoot == "" { diff --git a/internal/cli/self/uninstall.go b/internal/cli/self/uninstall.go index c332b83..4083965 100644 --- a/internal/cli/self/uninstall.go +++ b/internal/cli/self/uninstall.go @@ -124,8 +124,8 @@ func runUninstall(keepConfig, keepRecordings, dryRun, yes bool) error { // 4. Remove binary ui.SubSection("Binary") - binaryPath := filepath.Join(home, ".local", "bin", "nssh") - if FileExists(binaryPath) { + binaryPath := FindBinary() + if binaryPath != "" && FileExists(binaryPath) { if err := removeFile(binaryPath, dryRun); err != nil { ui.Warning("Failed to remove binary: %v", err) hasErrors = true @@ -133,11 +133,7 @@ func runUninstall(keepConfig, keepRecordings, dryRun, yes bool) error { ui.Success("Removed %s", AbbreviatePath(binaryPath)) } } else { - // Check if installed elsewhere on PATH - pathBinary := FindBinary() - if pathBinary != "" && pathBinary != binaryPath { - ui.Warning("Binary at %s not managed by self - remove manually", AbbreviatePath(pathBinary)) - } + ui.Info("Binary not found on PATH") } // 5. Optionally remove config/credentials diff --git a/scripts/install.sh b/scripts/install.sh index 42c24ef..c5e2030 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -5,36 +5,49 @@ set -e REPO="ntwrknrd/nssh" INSTALL_DIR="${HOME}/.local/bin" BINARY="nssh" +HARDWARE=false + +# Parse arguments +for arg in "$@"; do + case "$arg" in + --hardware) + HARDWARE=true + ;; + esac +done # Colors (if terminal supports it) if [ -t 1 ]; then - RED='\033[0;31m' - GREEN='\033[0;32m' - YELLOW='\033[0;33m' - BLUE='\033[0;34m' - NC='\033[0m' # No Color + ESC=$(printf '\033') + DIM="${ESC}[2m" + RED="${ESC}[0;31m" + GREEN="${ESC}[0;32m" + YELLOW="${ESC}[0;33m" + GRAY="${ESC}[0;90m" + NC="${ESC}[0m" else + DIM='' RED='' GREEN='' YELLOW='' - BLUE='' + GRAY='' NC='' fi info() { - printf "${BLUE}==>${NC} %s\n" "$1" + printf " ${DIM}${GRAY}[*]${NC} %s\n" "$1" } success() { - printf "${GREEN}==>${NC} %s\n" "$1" + printf " ${DIM}${GREEN}[✓]${NC} %s\n" "$1" } warn() { - printf "${YELLOW}WARNING:${NC} %s\n" "$1" + printf " ${DIM}${YELLOW}[!]${NC} %s\n" "$1" } error() { - printf "${RED}ERROR:${NC} %s\n" "$1" >&2 + printf " ${DIM}${RED}[✗]${NC} %s\n" "$1" >&2 exit 1 } @@ -91,8 +104,15 @@ main() { info "Latest version: ${VERSION}" - # Build URLs - ARCHIVE="nssh_${VERSION_NUM}_${OS}_${ARCH}.tar.gz" + # Build URLs based on hardware flag + if [ "${HARDWARE}" = true ]; then + ARCHIVE="nssh-hardware_${VERSION_NUM}_${OS}_${ARCH}.tar.gz" + ARCHIVE_BINARY="nssh-hardware" + info "Installing hardware build (YubiKey/PIV support)" + else + ARCHIVE="nssh_${VERSION_NUM}_${OS}_${ARCH}.tar.gz" + ARCHIVE_BINARY="nssh" + fi BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}" # Create temp directory @@ -129,7 +149,7 @@ main() { info "Installing to ${INSTALL_DIR}..." mkdir -p "${INSTALL_DIR}" tar -xzf "${ARCHIVE}" - mv "${BINARY}" "${INSTALL_DIR}/" + mv "${ARCHIVE_BINARY}" "${INSTALL_DIR}/${BINARY}" chmod +x "${INSTALL_DIR}/${BINARY}" success "Installed ${BINARY} to ${INSTALL_DIR}/${BINARY}" @@ -139,16 +159,11 @@ main() { *":${INSTALL_DIR}:"*) ;; *) - echo "" warn "${INSTALL_DIR} is not in your PATH" - echo " Add this to your shell profile (.bashrc, .zshrc, etc.):" - echo "" - echo " export PATH=\"\${HOME}/.local/bin:\${PATH}\"" - echo "" + printf " Add to shell profile: ${GRAY}export PATH=\"\${HOME}/.local/bin:\${PATH}\"${NC}\n" ;; esac - echo "" info "Run 'nssh self init' to set up shell integration" }