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
10 changes: 10 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@
kubernetes
]
))

(pkgs.buildGoModule {
pname = "toolbox";
version = "0.1.0";
src = builtins.path {
path = ./toolbox;
name = "toolbox-src";
};
vendorHash = "sha256-wNp4c6d9W/7RUuElMLiQEHca6ctvnFBZB/zb8MqbVr4=";
})
];
};
});
Expand Down
14 changes: 14 additions & 0 deletions settings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
secrets:
secret/khuedoan/notes/password:
VALUE:
type: random
length: 32 # optional, default 32
secret/git/deploy:
admin:
type: ssh
secret/platform/cloudflare:
API_TOKEN:
type: manual
description: |
Enter Cloudflare API token:
(Generate it from https://dash.cloudflare.com -> Manage account -> Account API tokens)
Comment on lines +1 to +14
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like an environment-/user-specific secrets configuration (it references personal paths and a Cloudflare token prompt). Committing it at repo root as settings.yaml risks accidental use in the wrong environment and makes it harder to ship the CLI as a general-purpose tool. Consider moving it to an examples/ (or toolbox/examples/) sample file with generic placeholder paths, and add settings.yaml to .gitignore if it’s meant to be local-only.

Copilot uses AI. Check for mistakes.
5 changes: 5 additions & 0 deletions toolbox/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.PHONY: test

test:
go vet ./...
go test -race ./...
59 changes: 59 additions & 0 deletions toolbox/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package cmd

import (
"os"
"path/filepath"

"github.com/charmbracelet/log"
"github.com/spf13/cobra"
)

var (
hostsFile string
host string
sshUser string
sshKey string
sshKnownHosts string
)

func init() {
log.SetReportTimestamp(false)

rootCmd.PersistentFlags().StringVar(&hostsFile, "hosts-file", "", "Path to hosts.json file")
rootCmd.PersistentFlags().StringVar(&host, "host", "", "Host name to connect to (e.g., kube-1)")
rootCmd.PersistentFlags().StringVar(&sshUser, "ssh-user", "root", "SSH user")
rootCmd.PersistentFlags().StringVar(&sshKey, "ssh-key", defaultSSHKey(), "Path to SSH private key")
rootCmd.PersistentFlags().StringVar(&sshKnownHosts, "ssh-known-hosts", defaultKnownHostsFile(), "Path to SSH known_hosts file")

rootCmd.AddCommand(secretsCmd)
}

var rootCmd = &cobra.Command{
Use: "toolbox",
Short: "CLI tools for managing cloudlab infrastructure",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
Comment on lines +34 to +36
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PersistentPreRunE function is defined but doesn't perform any validation or operations. If it's not needed, it should be removed to avoid confusion. If validation is intended but not implemented, this should be completed.

Suggested change
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return nil
},

Copilot uses AI. Check for mistakes.
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}

func defaultSSHKey() string {
home, err := os.UserHomeDir()
if err != nil {
return ""
}
return filepath.Join(home, ".ssh", "id_ed25519")
}

func defaultKnownHostsFile() string {
home, err := os.UserHomeDir()
if err != nil {
return ""
}
return filepath.Join(home, ".ssh", "known_hosts")
}
73 changes: 73 additions & 0 deletions toolbox/cmd/secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package cmd

import (
"context"
"fmt"
"time"

"github.com/charmbracelet/log"
"github.com/spf13/cobra"

"github.com/khuedoan/cloudlab/toolbox/internal/cluster"
"github.com/khuedoan/cloudlab/toolbox/internal/secrets"
)

const connectTimeout = 30 * time.Second

var settingsFile string

func init() {
secretsCmd.Flags().StringVar(&settingsFile, "settings", "", "Path to settings YAML file")
secretsCmd.MarkFlagRequired("settings")
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MarkFlagRequired returns an error; ignoring it can hide programmer errors (e.g., if the flag name changes) and may be flagged by linters. Capture and handle the returned error (or panic in init() if it fails) to ensure the CLI setup is correct.

Suggested change
secretsCmd.MarkFlagRequired("settings")
if err := secretsCmd.MarkFlagRequired("settings"); err != nil {
panic(err)
}

Copilot uses AI. Check for mistakes.
}

var secretsCmd = &cobra.Command{
Use: "secrets",
Short: "Manage secrets in Vault",
PreRunE: func(cmd *cobra.Command, args []string) error {
if hostsFile == "" {
return fmt.Errorf("--hosts-file is required")
}
if host == "" {
return fmt.Errorf("--host is required")
}
return nil
},
RunE: runSecrets,
}

func runSecrets(cmd *cobra.Command, args []string) error {
config, err := secrets.LoadConfig(settingsFile)
if err != nil {
return fmt.Errorf("load settings file: %w", err)
}

entries, err := secrets.ParseAndValidate(config)
if err != nil {
return fmt.Errorf("validate config: %w", err)
}

connectCtx, cancel := context.WithTimeout(cmd.Context(), connectTimeout)
defer cancel()

client, err := cluster.NewClient(connectCtx, cluster.ClientConfig{
HostsFile: hostsFile,
Host: host,
SSHUser: sshUser,
SSHKey: sshKey,
SSHKnownHosts: sshKnownHosts,
})
if err != nil {
return fmt.Errorf("connect to cluster: %w", err)
}
defer client.Close()
log.Debug("connected to cluster")

service := secrets.NewService(client.Vault(), secrets.HuhPrompter{})
if err := service.Run(cmd.Context(), entries); err != nil {
return err
}

log.Info("all secrets processed successfully")
return nil
}
61 changes: 61 additions & 0 deletions toolbox/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
module github.com/khuedoan/cloudlab/toolbox

go 1.25.5

require (
github.com/charmbracelet/huh v0.8.0
github.com/charmbracelet/log v0.4.2
github.com/hashicorp/vault/api v1.22.0
github.com/spf13/cobra v1.10.2
golang.org/x/crypto v0.47.0
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
github.com/charmbracelet/bubbletea v1.3.6 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/time v0.12.0 // indirect
)
Loading