50 |
SLV
51 |
Secure Local Vault
52 |
If you are not redirected, click here.
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/internal/cli/slv.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 | "slv.sh/slv/internal/cli/commands/cmdenv"
8 | "slv.sh/slv/internal/cli/commands/cmdprofile"
9 | "slv.sh/slv/internal/cli/commands/cmdsystem"
10 | "slv.sh/slv/internal/cli/commands/cmdvault"
11 | "slv.sh/slv/internal/cli/commands/utils"
12 | "slv.sh/slv/internal/core/config"
13 | )
14 |
15 | var (
16 | slvCmd *cobra.Command
17 | versionCmd *cobra.Command
18 | webCmd *cobra.Command
19 | tuiCmd *cobra.Command
20 |
21 | versionFlag = utils.FlagDef{
22 | Name: "version",
23 | Shorthand: "v",
24 | Usage: "Shows version info",
25 | }
26 | )
27 |
28 | func slvCommand() *cobra.Command {
29 | if slvCmd == nil {
30 | slvCmd = &cobra.Command{
31 | Use: "slv",
32 | Short: "SLV is a tool to encrypt secrets locally",
33 | Run: func(cmd *cobra.Command, args []string) {
34 | version, _ := cmd.Flags().GetBool(versionFlag.Name)
35 | if version {
36 | fmt.Println(config.VersionInfo())
37 | } else {
38 | cmd.Help()
39 | }
40 | },
41 | }
42 | slvCmd.Flags().BoolP(versionFlag.Name, versionFlag.Shorthand, false, versionFlag.Usage)
43 | slvCmd.AddCommand(versionCommand())
44 | slvCmd.AddCommand(cmdsystem.SystemCommand())
45 | slvCmd.AddCommand(cmdenv.EnvCommand())
46 | slvCmd.AddCommand(cmdprofile.ProfileCommand())
47 | slvCmd.AddCommand(cmdvault.VaultCommand())
48 | slvCmd.AddCommand(webCommand())
49 | slvCmd.AddCommand(tuiCommand())
50 | }
51 | return slvCmd
52 | }
53 |
54 | func Run() {
55 | if err := slvCommand().Execute(); err != nil {
56 | utils.ExitOnError(err)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/internal/tui/components/maincontent.go:
--------------------------------------------------------------------------------
1 | package components
2 |
3 | import (
4 | "github.com/rivo/tview"
5 | "slv.sh/slv/internal/tui/interfaces"
6 | )
7 |
8 | // MainContent represents the main content area component
9 | type MainContent struct {
10 | tui interfaces.TUIInterface
11 | primitive *tview.Pages
12 | content tview.Primitive
13 | }
14 |
15 | // NewMainContent creates a new MainContent component
16 | func NewMainContent(tui interfaces.TUIInterface) *MainContent {
17 | mc := &MainContent{
18 | tui: tui,
19 | primitive: tview.NewPages(),
20 | }
21 | return mc
22 | }
23 |
24 | // Render returns the primitive for this component
25 | func (mc *MainContent) Render() tview.Primitive {
26 | return mc.primitive
27 | }
28 |
29 | // Refresh refreshes the component with current data
30 | func (mc *MainContent) Refresh() {
31 | // Main content refreshes are handled by the router
32 | // This method is here for interface compliance
33 | }
34 |
35 | // SetFocus sets focus on the component
36 | func (mc *MainContent) SetFocus(focus bool) {
37 | // Pages component doesn't have SetFocus method
38 | // Focus is handled by the application
39 | }
40 |
41 | // SetContent sets the content for the main area
42 | func (mc *MainContent) SetContent(content tview.Primitive) {
43 | mc.content = content
44 | mc.primitive.AddPage("main", content, true, true)
45 | }
46 |
47 | // GetContent returns the current content
48 | func (mc *MainContent) GetContent() tview.Primitive {
49 | return mc.content
50 | }
51 |
52 | // GetPages returns the pages container for direct manipulation
53 | func (mc *MainContent) GetPages() *tview.Pages {
54 | return mc.primitive
55 | }
56 |
--------------------------------------------------------------------------------
/pages/scripts/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | if ! command -v unzip >/dev/null && ! command -v 7z >/dev/null; then
6 | echo "Error: either unzip or 7z is required to install SLV" 1>&2
7 | exit 1
8 | fi
9 |
10 | if [ "$OS" = "Windows_NT" ]; then
11 | target="windows-amd64"
12 | else
13 | case $(uname -sm) in
14 | "Darwin x86_64") target="darwin_amd64" ;;
15 | "Darwin arm64") target="darwin_arm64" ;;
16 | "Linux aarch64") target="linux_arm64" ;;
17 | *) target="linux_amd64" ;;
18 | esac
19 | fi
20 |
21 | if [ $# -eq 0 ]; then
22 | slv_uri="https://github.com/amagioss/slv/releases/latest/download/slv_${target}.zip"
23 | else
24 | slv_uri="https://github.com/amagioss/slv/releases/download/${1}/slv_${target}.zip"
25 | fi
26 |
27 | slv_install="${SLV_INSTALL:-$HOME/.slv}"
28 | bin_dir="$slv_install/bin"
29 | exe="$bin_dir/slv"
30 |
31 | if [ ! -d "$bin_dir" ]; then
32 | mkdir -p "$bin_dir"
33 | fi
34 |
35 | curl --fail --location --progress-bar --output "$exe.zip" "$slv_uri"
36 | if command -v unzip >/dev/null; then
37 | unzip -d "$bin_dir" -o "$exe.zip"
38 | else
39 | 7z x -o"$bin_dir" -y "$exe.zip"
40 | fi
41 | chmod +x "$exe"
42 | rm "$exe.zip"
43 |
44 | echo "SLV was installed successfully to $exe"
45 | if command -v slv >/dev/null; then
46 | echo "Run 'slv --help' to get started"
47 | else
48 | case $SHELL in
49 | /bin/zsh) shell_profile=".zshrc" ;;
50 | *) shell_profile=".bashrc" ;;
51 | esac
52 | echo "Manually add the directory to your \$HOME/$shell_profile (or similar)"
53 | echo " export SLV_INSTALL=\"$slv_install\""
54 | echo " export PATH=\"\$SLV_INSTALL/bin:\$PATH\""
55 | echo "Run '$exe --help' to get started"
56 | fi
57 | echo
58 |
--------------------------------------------------------------------------------
/internal/cli/commands/cmdvault/delete.go:
--------------------------------------------------------------------------------
1 | package cmdvault
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/fatih/color"
7 | "github.com/spf13/cobra"
8 | "slv.sh/slv/internal/cli/commands/utils"
9 | "slv.sh/slv/internal/core/vaults"
10 | )
11 |
12 | func vaultDeleteCommand() *cobra.Command {
13 | if vaultDeleteCmd == nil {
14 | vaultDeleteCmd = &cobra.Command{
15 | Use: "rm",
16 | Aliases: []string{"del", "remove", "delete", "destroy", "erase"},
17 | Short: "Removes an item from the vault",
18 | Run: func(cmd *cobra.Command, args []string) {
19 | vaultFile := cmd.Flag(vaultFileFlag.Name).Value.String()
20 | vault, err := vaults.Get(vaultFile)
21 | if err != nil {
22 | utils.ExitOnError(err)
23 | }
24 | secretNames, err := cmd.Flags().GetStringSlice(itemNameFlag.Name)
25 | if err != nil {
26 | utils.ExitOnError(err)
27 | }
28 | if len(secretNames) == 0 {
29 | if err = vault.Delete(); err != nil {
30 | utils.ExitOnError(err)
31 | }
32 | fmt.Printf(color.GreenString("Successfully deleted the vault: %s\n"), vaultFile)
33 | } else {
34 | if err = vault.DeleteItems(secretNames); err != nil {
35 | utils.ExitOnError(err)
36 | }
37 | fmt.Printf(color.GreenString("Successfully deleted the secrets: %v from the vault: %s\n"), secretNames, vaultFile)
38 | }
39 | },
40 | }
41 | vaultDeleteCmd.Flags().StringSliceP(itemNameFlag.Name, itemNameFlag.Shorthand, []string{}, itemNameFlag.Usage)
42 | if err := vaultDeleteCmd.RegisterFlagCompletionFunc(itemNameFlag.Name, vaultItemNameCompletion); err != nil {
43 | utils.ExitOnError(err)
44 | }
45 | }
46 | return vaultDeleteCmd
47 | }
48 |
--------------------------------------------------------------------------------
/internal/api/auth.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "strings"
7 | "time"
8 |
9 | "github.com/gin-gonic/gin"
10 | "github.com/golang-jwt/jwt/v5"
11 | )
12 |
13 | const (
14 | jwtExpiry = 10 * time.Second
15 | )
16 |
17 | func validateJWT(tokenString string, jwtSecret []byte) (jwt.MapClaims, error) {
18 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
19 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
20 | return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
21 | }
22 | return jwtSecret, nil
23 | })
24 | if err != nil {
25 | return nil, fmt.Errorf("failed to parse or validate token: %v", err)
26 | }
27 | claims, ok := token.Claims.(jwt.MapClaims)
28 | if !ok || !token.Valid {
29 | return nil, fmt.Errorf("invalid token")
30 | }
31 | if claims["iat"] == nil || time.Unix(int64(claims["iat"].(float64)), 0).Add(jwtExpiry).Before(time.Now()) {
32 | return nil, fmt.Errorf("invalid token")
33 | }
34 | return claims, nil
35 | }
36 |
37 | func authMiddleware(jwtSecret []byte) gin.HandlerFunc {
38 | return func(context *gin.Context) {
39 | token := context.GetHeader("Authorization")
40 | if token == "" {
41 | context.JSON(http.StatusUnauthorized, apiResponse{Success: false, Error: "Unauthorized"})
42 | context.Abort()
43 | return
44 | }
45 | token = strings.TrimPrefix(token, "Bearer ")
46 | claims, err := validateJWT(token, jwtSecret)
47 | if err != nil {
48 | context.JSON(http.StatusUnauthorized, apiResponse{Success: false, Error: err.Error()})
49 | context.Abort()
50 | return
51 | }
52 | context.Set("claims", claims)
53 | context.Next()
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/internal/tui/pages/profiles/profiles.go:
--------------------------------------------------------------------------------
1 | package profiles
2 |
3 | import (
4 | "github.com/gdamore/tcell/v2"
5 | "github.com/rivo/tview"
6 | "slv.sh/slv/internal/tui/interfaces"
7 | "slv.sh/slv/internal/tui/pages"
8 | "slv.sh/slv/internal/tui/theme"
9 | )
10 |
11 | // ProfilesPage handles the profiles page functionality
12 | type ProfilesPage struct {
13 | pages.BasePage
14 | }
15 |
16 | // NewProfilesPage creates a new ProfilesPage instance
17 | func NewProfilesPage(tui interfaces.TUIInterface) *ProfilesPage {
18 | return &ProfilesPage{
19 | BasePage: *pages.NewBasePage(tui, "Profiles"),
20 | }
21 | }
22 |
23 | // Create implements the Page interface
24 | func (pp *ProfilesPage) Create() tview.Primitive {
25 | // Create content
26 | text := tview.NewTextView().
27 | SetText("Profiles Page\n\nThis page will show profile management options.").
28 | SetTextAlign(tview.AlignCenter).
29 | SetDynamicColors(true)
30 |
31 | // Style the text
32 | colors := theme.GetCurrentPalette()
33 | text.SetTextColor(colors.TextPrimary)
34 |
35 | // Update status bar
36 | pp.UpdateStatus("Profiles management - Coming soon")
37 |
38 | // Create layout using BasePage method
39 | return pp.CreateLayout(text)
40 | }
41 |
42 | // Refresh implements the Page interface
43 | func (pp *ProfilesPage) Refresh() {
44 | // Profiles page doesn't need refresh logic yet
45 | }
46 |
47 | // HandleInput implements the Page interface
48 | func (pp *ProfilesPage) HandleInput(event *tcell.EventKey) *tcell.EventKey {
49 | // Profiles page doesn't handle specific input yet
50 | return event
51 | }
52 |
53 | // GetTitle implements the Page interface
54 | func (pp *ProfilesPage) GetTitle() string {
55 | return pp.BasePage.GetTitle()
56 | }
57 |
--------------------------------------------------------------------------------
/website/docs/command-reference/environment/del.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 6
3 | ---
4 | # Delete an Environment
5 |
6 | Delete one or more environments based on search parameters such as `name`, `email`, `tags`.
7 |
8 | #### General Usage:
9 | ```bash
10 | slv env rm [flags]
11 | ```
12 |
13 | #### Flags:
14 | | Flag | Arguments | Required | Default | Description |
15 | | -- | -- | -- | -- | -- |
16 | | --env-search | String(s) | False | None | Search for environments based on `tag`/`email`/`name` to delete |
17 | | --help | None | NA | NA| Help text for `slv env del` |
18 |
19 | #### Usage:
20 | ```bash
21 | slv env rm --env-search