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
8 changes: 4 additions & 4 deletions internal/verda-cli/cmd/images/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,16 @@ func runImages(cmd *cobra.Command, f cmdutil.Factory, ioStreams cmdutil.IOStream
}

_, _ = fmt.Fprintf(ioStreams.Out, " %d image(s) found\n\n", len(images))
_, _ = fmt.Fprintf(ioStreams.Out, " %-45s %-12s %s\n", "NAME", "CATEGORY", "DETAILS")
_, _ = fmt.Fprintf(ioStreams.Out, " %-45s %-12s %s\n", "----", "--------", "-------")
_, _ = fmt.Fprintf(ioStreams.Out, " %-38s %-45s %-12s %s\n", "ID", "NAME", "CATEGORY", "DETAILS")
_, _ = fmt.Fprintf(ioStreams.Out, " %-38s %-45s %-12s %s\n", "--", "----", "--------", "-------")
for i := range images {
details := strings.Join(images[i].Details, ", ")
def := ""
if images[i].IsDefault {
def = " *"
}
_, _ = fmt.Fprintf(ioStreams.Out, " %-45s %-12s %s%s\n",
images[i].Name, images[i].Category, details, def)
_, _ = fmt.Fprintf(ioStreams.Out, " %-38s %-45s %-12s %s%s\n",
images[i].ID, images[i].Name, images[i].Category, details, def)
}
return nil
}
40 changes: 29 additions & 11 deletions internal/verda-cli/cmd/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/verda-cloud/verdagostack/pkg/version"

cmdutil "github/verda-cloud/verda-cli/internal/verda-cli/cmd/util"
"github/verda-cloud/verda-cli/internal/verda-cli/options"
)

const (
Expand All @@ -42,6 +43,8 @@ func NewCmdUpdate(f cmdutil.Factory, ioStreams cmdutil.IOStreams) *cobra.Command
Update the Verda CLI binary in-place by downloading from GitHub Releases.
No Go installation required.

The binary is installed to ~/.verda/bin/ (no sudo required).

Without flags, updates to the latest version.
Use --version to install a specific version (upgrade or downgrade).
Use --list to show available versions.
Expand Down Expand Up @@ -78,6 +81,9 @@ func runList(ctx context.Context, ioStreams cmdutil.IOStreams) error {
}

current := version.Get().GitVersion
if !strings.HasPrefix(current, "v") {
current = "v" + current
}
_, _ = fmt.Fprintf(ioStreams.Out, " Available versions (current: %s)\n\n", current)
for _, v := range versions {
marker := " "
Expand All @@ -91,6 +97,9 @@ func runList(ctx context.Context, ioStreams cmdutil.IOStreams) error {

func runUpdate(ctx context.Context, f cmdutil.Factory, ioStreams cmdutil.IOStreams, targetVersion string) error {
current := version.Get().GitVersion
if !strings.HasPrefix(current, "v") {
current = "v" + current
}

// Resolve target version.
target := targetVersion
Expand Down Expand Up @@ -132,24 +141,33 @@ func runUpdate(ctx context.Context, f cmdutil.Factory, ioStreams cmdutil.IOStrea
return err
}

// Replace current executable.
exe, err := resolveExecutable()
// Determine install destination: always ~/.verda/bin/verda.
binDir, err := options.EnsureVerdaBinDir()
if err != nil {
return fmt.Errorf("resolving executable path: %w", err)
return fmt.Errorf("preparing install directory: %w", err)
}
binaryName := "verda"
if runtime.GOOS == osWindows {
binaryName = "verda.exe"
}
dst := filepath.Join(binDir, binaryName)

if err := replaceBinary(exe, binary); err != nil {
if errors.Is(err, os.ErrPermission) {
hint := "sudo verda update"
if runtime.GOOS == osWindows {
hint = "running the command in an elevated (Administrator) terminal"
}
return fmt.Errorf("permission denied writing to %s\n\nTry: %s", filepath.Dir(exe), hint)
}
if err := replaceBinary(dst, binary); err != nil {
return fmt.Errorf("replacing binary: %w", err)
}

_, _ = fmt.Fprintf(ioStreams.Out, "Updated to %s\n", target)

// Warn if old binary exists in a system path.
oldExe, _ := resolveExecutable()
if oldExe != "" && oldExe != dst {
_, _ = fmt.Fprintf(ioStreams.ErrOut,
"\nNote: old binary still exists at %s\n"+
" Remove it to avoid conflicts: sudo rm %s\n"+
" Ensure %s is in your PATH.\n",
oldExe, oldExe, binDir)
}

return nil
}

Expand Down
22 changes: 9 additions & 13 deletions internal/verda-cli/cmd/vm/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,25 +126,21 @@ func fetchInstanceVolumes(ctx context.Context, client *verda.Client, inst *verda
}

func printInstanceTable(ioStreams cmdutil.IOStreams, instances []verda.Instance) error {
// Find max hostname length for dynamic column width.
nameW := 20
for i := range instances {
if len(instances[i].Hostname) > nameW {
nameW = len(instances[i].Hostname)
}
}

_, _ = fmt.Fprintf(ioStreams.Out, " %d instance(s) found\n\n", len(instances))
_, _ = fmt.Fprintf(ioStreams.Out, " %-*s %-13s %-18s %-8s %s\n", nameW, "HOSTNAME", "STATUS", "TYPE", "LOCATION", "IP")
_, _ = fmt.Fprintf(ioStreams.Out, " %-*s %-13s %-18s %-8s %s\n", nameW, "--------", "------", "----", "--------", "--")
_, _ = fmt.Fprintf(ioStreams.Out, " %-36s %-30s %-13s %-18s %-8s %s\n", "ID", "HOSTNAME", "STATUS", "TYPE", "LOCATION", "IP")
_, _ = fmt.Fprintf(ioStreams.Out, " %-36s %-30s %-13s %-18s %-8s %s\n", "--", "--------", "------", "----", "--------", "--")
for i := range instances {
ip := ""
if instances[i].IP != nil && *instances[i].IP != "" {
ip = *instances[i].IP
}
_, _ = fmt.Fprintf(ioStreams.Out, " %-*s %-13s %-18s %-8s %s\n",
nameW,
instances[i].Hostname,
hostname := instances[i].Hostname
if len(hostname) > 30 {
hostname = hostname[:27] + "..."
}
_, _ = fmt.Fprintf(ioStreams.Out, " %-36s %-30s %-13s %-18s %-8s %s\n",
instances[i].ID,
hostname,
instances[i].Status,
instances[i].InstanceType,
instances[i].Location,
Expand Down
12 changes: 8 additions & 4 deletions internal/verda-cli/cmd/volume/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,15 @@ func runList(cmd *cobra.Command, f cmdutil.Factory, ioStreams cmdutil.IOStreams,
}

_, _ = fmt.Fprintf(ioStreams.Out, " %d volume(s) found\n\n", len(volumes))
_, _ = fmt.Fprintf(ioStreams.Out, " %-20s %-36s %6s %-10s %-10s %s\n", "NAME", "ID", "SIZE", "TYPE", "STATUS", "LOCATION")
_, _ = fmt.Fprintf(ioStreams.Out, " %-20s %-36s %6s %-10s %-10s %s\n", "----", "--", "----", "----", "------", "--------")
_, _ = fmt.Fprintf(ioStreams.Out, " %-36s %-30s %6s %-10s %-10s %s\n", "ID", "NAME", "SIZE", "TYPE", "STATUS", "LOCATION")
_, _ = fmt.Fprintf(ioStreams.Out, " %-36s %-30s %6s %-10s %-10s %s\n", "--", "----", "----", "----", "------", "--------")
for i := range volumes {
_, _ = fmt.Fprintf(ioStreams.Out, " %-20s %-36s %4dGB %-10s %-10s %s\n",
volumes[i].Name, volumes[i].ID, volumes[i].Size, volumes[i].Type, volumes[i].Status, volumes[i].Location)
name := volumes[i].Name
if len(name) > 30 {
name = name[:27] + "..."
}
_, _ = fmt.Fprintf(ioStreams.Out, " %-36s %-30s %4dGB %-10s %-10s %s\n",
volumes[i].ID, name, volumes[i].Size, volumes[i].Type, volumes[i].Status, volumes[i].Location)
}
return nil
}
21 changes: 21 additions & 0 deletions internal/verda-cli/options/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,27 @@ func EnsureVerdaDir() (string, error) {
return dir, nil
}

// VerdaBinDir returns the path to the Verda binary directory (~/.verda/bin).
func VerdaBinDir() (string, error) {
dir, err := VerdaDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "bin"), nil
}

// EnsureVerdaBinDir creates the Verda binary directory if it doesn't exist.
func EnsureVerdaBinDir() (string, error) {
dir, err := VerdaBinDir()
if err != nil {
return "", err
}
if err := mkdirSecure(dir); err != nil {
return "", fmt.Errorf("cannot create binary directory %s: %w", dir, err)
}
return dir, nil
}

// WriteSecureFile writes data to path with restrictive permissions.
// On Unix, the file is created with 0600. On Windows, it inherits the
// parent directory ACL (Go's os.WriteFile uses default security).
Expand Down
62 changes: 53 additions & 9 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
# Usage: curl -sSL https://raw.githubusercontent.com/verda-cloud/verda-cli/main/scripts/install.sh | sh
#
# Environment variables:
# VERDA_INSTALL_DIR - Installation directory (default: /usr/local/bin)
# VERDA_INSTALL_DIR - Installation directory (default: ~/.verda/bin)
# VERDA_VERSION - Specific version to install (default: latest)

set -e

REPO="verda-cloud/verda-cli"
BINARY="verda"
INSTALL_DIR="${VERDA_INSTALL_DIR:-/usr/local/bin}"
INSTALL_DIR="${VERDA_INSTALL_DIR:-$HOME/.verda/bin}"

# Detect OS
OS="$(uname -s)"
Expand Down Expand Up @@ -84,19 +84,63 @@ elif [ "$EXT" = "zip" ]; then
fi

# Install
if [ -w "$INSTALL_DIR" ]; then
mv "$BINARY" "$INSTALL_DIR/$BINARY"
else
echo "Elevating permissions to install to ${INSTALL_DIR}..."
sudo mv "$BINARY" "$INSTALL_DIR/$BINARY"
fi

mkdir -p "$INSTALL_DIR"
mv "$BINARY" "$INSTALL_DIR/$BINARY"
chmod +x "$INSTALL_DIR/$BINARY"

# Ensure ~/.verda/bin is in PATH
setup_path() {
case ":$PATH:" in
*":$INSTALL_DIR:"*) return ;; # already in PATH
esac

SHELL_NAME="$(basename "$SHELL")"
case "$SHELL_NAME" in
zsh) RC_FILE="$HOME/.zshrc" ;;
bash)
if [ -f "$HOME/.bashrc" ]; then
RC_FILE="$HOME/.bashrc"
else
RC_FILE="$HOME/.bash_profile"
fi
;;
fish) RC_FILE="$HOME/.config/fish/config.fish" ;;
*) RC_FILE="$HOME/.profile" ;;
esac

PATH_LINE="export PATH=\"$INSTALL_DIR:\$PATH\""
if [ "$SHELL_NAME" = "fish" ]; then
PATH_LINE="set -gx PATH $INSTALL_DIR \$PATH"
fi

if [ -f "$RC_FILE" ] && grep -qF "$INSTALL_DIR" "$RC_FILE" 2>/dev/null; then
return # already configured
fi

echo "" >> "$RC_FILE"
echo "# Added by Verda CLI installer" >> "$RC_FILE"
echo "$PATH_LINE" >> "$RC_FILE"
echo " Added $INSTALL_DIR to PATH in $RC_FILE"
echo " Run: source $RC_FILE (or open a new terminal)"
}

# Only set up PATH if using the default location
if [ "$VERDA_INSTALL_DIR" = "" ]; then
setup_path
fi

echo ""
echo "Verda CLI ${VERDA_VERSION} installed successfully!"
echo ""
echo "Get started:"
echo " verda auth login # Configure credentials"
echo " verda vm list # List VM instances"
echo " verda --help # See all commands"

# Warn about old binary in system path
OLD_BINARY="$(command -v verda 2>/dev/null || true)"
if [ -n "$OLD_BINARY" ] && [ "$OLD_BINARY" != "$INSTALL_DIR/$BINARY" ]; then
echo ""
echo "Warning: an older verda binary exists at $OLD_BINARY"
echo " Remove it to avoid conflicts: sudo rm $OLD_BINARY"
fi
Loading