Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 12 additions & 5 deletions docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -612,15 +612,20 @@ 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
```
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
Expand Down Expand Up @@ -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"

Youre 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
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/help/self.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 │
Expand Down
3 changes: 2 additions & 1 deletion docs/examples/help/self/reinstall.txt
Original file line number Diff line number Diff line change
@@ -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 ─────────────────────────────────────────────────────────────╮
Expand Down
81 changes: 72 additions & 9 deletions internal/cli/self/reinstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 == "" {
Expand Down
10 changes: 3 additions & 7 deletions internal/cli/self/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,16 @@ 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
} else {
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
Expand Down
53 changes: 34 additions & 19 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}"
Expand All @@ -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"
}

Expand Down