cmd

package
v0.12.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 26, 2023 License: MIT Imports: 39 Imported by: 1

Documentation

Overview

Package cmd implements the Cobra commands for the charm CLI.

Index

Constants

This section is empty.

Variables

View Source
var BackupKeysCmd = &cobra.Command{
	Use:                   "backup-keys",
	Hidden:                false,
	Short:                 "Backup your Charm account keys",
	Long:                  paragraph(fmt.Sprintf("%s your Charm account keys to a tar archive file. \nYou can restore your keys from backup using import-keys. \nRun `charm import-keys -help` to learn more.", keyword("Backup"))),
	Args:                  cobra.NoArgs,
	DisableFlagsInUseLine: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		cfg, err := client.ConfigFromEnv()
		if err != nil {
			return err
		}

		cc, err := client.NewClient(cfg)
		if err != nil {
			return err
		}

		dd, err := cc.DataPath()
		if err != nil {
			return err
		}

		if err := validateDirectory(dd); err != nil {
			return err
		}

		backupPath := backupOutputFile
		if backupPath == "-" {
			exp := regexp.MustCompilePOSIX("charm_(rsa|ed25519)$")
			paths, err := getKeyPaths(dd, exp)
			if err != nil {
				return err
			}
			if len(paths) != 1 {
				return fmt.Errorf("backup to stdout only works with 1 key, you have %d", len(paths))
			}
			bts, err := os.ReadFile(paths[0])
			if err != nil {
				return err
			}
			_, _ = fmt.Fprint(cmd.OutOrStdout(), string(bts))
			return nil
		}

		if !strings.HasSuffix(backupPath, ".tar") {
			backupPath = backupPath + ".tar"
		}

		if fileOrDirectoryExists(backupPath) {
			fmt.Printf("Not creating backup file: %s already exists.\n\n", code(backupPath))
			os.Exit(1)
		}

		if err := os.MkdirAll(filepath.Dir(backupPath), 0o754); err != nil {
			return err
		}

		if err := createTar(dd, backupPath); err != nil {
			return err
		}

		fmt.Printf("Done! Saved keys to %s.\n\n", code(backupPath))
		return nil
	},
}

BackupKeysCmd is the cobra.Command to back up a user's account SSH keys.

View Source
var BioCmd = &cobra.Command{
	Use:    "bio",
	Hidden: true,
	Short:  "",
	Long:   "",
	Args:   cobra.NoArgs,
	RunE: func(cmd *cobra.Command, args []string) error {
		cc := initCharmClient()
		u, err := cc.Bio()
		if err != nil {
			return err
		}

		fmt.Println(u)
		return nil
	},
}

BioCmd is the cobra.Command to return a user's bio JSON result.

View Source
var CompletionCmd = &cobra.Command{
	Use:                   "completion [bash|zsh|fish|powershell]",
	Short:                 "Generate shell completion",
	Long:                  completionInstructions(),
	DisableFlagsInUseLine: true,
	ValidArgs:             []string{"bash", "zsh", "fish", "powershell"},
	Args:                  cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
	Run: func(cmd *cobra.Command, args []string) {
		switch args[0] {
		case "bash":
			cmd.Root().GenBashCompletion(os.Stdout)
		case "zsh":
			cmd.Root().GenZshCompletion(os.Stdout)
		case "fish":
			cmd.Root().GenFishCompletion(os.Stdout, true)
		case "powershell":
			cmd.Root().GenPowerShellCompletion(os.Stdout)
		}
	},
}

CompletionCmd is the cobra.Command to generate shell completion.

View Source
var (
	// CryptCmd is the cobra.Command to manage encryption and decryption for a user.
	CryptCmd = &cobra.Command{
		Use:    "crypt",
		Hidden: false,
		Short:  "Use Charm encryption.",
		Long:   styles.Paragraph.Render("Commands to encrypt and decrypt data with your Charm Cloud encryption keys."),
		Args:   cobra.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			return nil
		},
	}
)
View Source
var (

	// FSCmd is the cobra.Command to use the Charm file system.
	FSCmd = &cobra.Command{
		Use:    "fs",
		Hidden: false,
		Short:  "Use the Charm file system.",
		Long:   paragraph("Commands to set, get and delete data from your Charm Cloud backed file system."),
	}
)
View Source
var IDCmd = &cobra.Command{
	Use:   "id",
	Short: "Print your Charm ID",
	Long:  paragraph("Want to know your " + keyword("Charm ID") + "? You’re in luck, kiddo."),
	Args:  cobra.NoArgs,
	RunE: func(cmd *cobra.Command, args []string) error {
		cc := initCharmClient()
		id, err := cc.ID()
		if err != nil {
			return err
		}

		fmt.Println(id)
		return nil
	},
}

IDCmd is the cobra.Command to print a user's Charm ID.

View Source
var (

	// ImportKeysCmd is the cobra.Command to import a user's ssh key backup as creaed by `backup-keys`.
	ImportKeysCmd = &cobra.Command{
		Use:                   "import-keys BACKUP.tar",
		Hidden:                false,
		Short:                 "Import previously backed up Charm account keys.",
		Long:                  paragraph(fmt.Sprintf("%s previously backed up Charm account keys.", keyword("Import"))),
		Args:                  cobra.MaximumNArgs(1),
		DisableFlagsInUseLine: false,
		RunE: func(cmd *cobra.Command, args []string) error {
			cfg, err := client.ConfigFromEnv()
			if err != nil {
				return err
			}
			cc, err := client.NewClient(cfg)
			if err != nil {
				return err
			}
			dd, err := cc.DataPath()
			if err != nil {
				return err
			}

			if err := os.MkdirAll(dd, 0o700); err != nil {
				return err
			}

			empty, err := isEmpty(dd)
			if err != nil {
				return err
			}

			path := "-"
			if len(args) > 0 {
				path = args[0]
			}
			if !empty && !forceImportOverwrite {
				if common.IsTTY() {
					p := newImportConfirmationTUI(cmd.InOrStdin(), path, dd)
					if _, err := p.Run(); err != nil {
						return err
					}
					return nil
				}
				return fmt.Errorf("not overwriting the existing keys in %s; to force, use -f", dd)
			}

			if isStdin(path) {
				if err := restoreFromReader(cmd.InOrStdin(), dd); err != nil {
					return err
				}
			} else {
				if err := untar(path, dd); err != nil {
					return err
				}
			}

			paragraph(fmt.Sprintf("Done! Keys imported to %s", code(dd)))
			return nil
		},
	}
)
View Source
var JWTCmd = &cobra.Command{
	Use:   "jwt",
	Short: "Print a JWT",
	Long:  paragraph(keyword("JSON Web Tokens") + " are a way to authenticate to different services that utilize your Charm account. Use " + code("jwt") + " to get one for your account."),
	Args:  cobra.ArbitraryArgs,
	RunE: func(cmd *cobra.Command, args []string) error {
		cc := initCharmClient()
		jwt, err := cc.JWT(args...)
		if err != nil {
			return err
		}

		fmt.Printf("%s\n", jwt)
		return nil
	},
}

JWTCmd is the cobra.Command that prints a user's JWT token.

View Source
var (

	// KVCmd is the cobra.Command for a user to use the Charm key value store.
	KVCmd = &cobra.Command{
		Use:    "kv",
		Hidden: false,
		Short:  "Use the Charm key value store.",
		Long:   paragraph("Commands to set, get and delete data from your Charm Cloud backed key value store."),
		Args:   cobra.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			return nil
		},
	}
)
View Source
var KeySyncCmd = &cobra.Command{
	Use:    "sync-keys",
	Hidden: true,
	Short:  "Re-encrypt encrypt keys for all linked public keys",
	Long:   paragraph(fmt.Sprintf("%s encrypt keys for all linked public keys", keyword("Re-encrypt"))),
	Args:   cobra.NoArgs,
	RunE: func(cmd *cobra.Command, args []string) error {
		cc := initCharmClient()
		return cc.SyncEncryptKeys()
	},
}

KeySyncCmd is the cobra.Command to rencrypt and sync all encrypt keys for a user.

View Source
var KeysCmd = &cobra.Command{
	Use:   "keys",
	Short: "Browse or print linked SSH keys",
	Long:  paragraph("Charm accounts are powered by " + keyword("SSH keys") + ". This command prints all of the keys linked to your account. To remove keys use the main " + code("charm") + " interface."),
	Args:  cobra.NoArgs,
	RunE: func(cmd *cobra.Command, args []string) error {
		if common.IsTTY() && !randomart && !simpleOutput {

			cfg := getCharmConfig()
			if cfg.Logfile != "" {
				f, err := tea.LogToFile(cfg.Logfile, "charm")
				if err != nil {
					return err
				}
				defer f.Close()
			}
			p := keys.NewProgram(cfg)
			if _, err := p.Run(); err != nil {
				return err
			}
			return nil
		}
		cc := initCharmClient()

		k, err := cc.AuthorizedKeysWithMetadata()
		if err != nil {
			return err
		}

		keys := k.Keys
		for i := 0; i < len(keys); i++ {
			if !randomart {
				fmt.Println(keys[i].Key)
				continue
			}
			fp, err := client.FingerprintSHA256(*keys[i])
			if err != nil {
				fp.Value = fmt.Sprintf("Could not generate fingerprint for key %s: %v\n\n", keys[i].Key, err)
			}
			board, err := client.RandomArt(*keys[i])
			if err != nil {
				board = fmt.Sprintf("Could not generate randomart for key %s: %v\n\n", keys[i].Key, err)
			}
			cr := "\n\n"
			if i == len(keys)-1 {
				cr = "\n"
			}
			fmt.Printf("%s\n%s%s", fp, board, cr)
		}
		return nil
	},
}

KeysCmd is the cobra.Command for a user to browser and print their linked SSH keys.

View Source
var (
	MigrateAccountCmd = &cobra.Command{
		Use:    "migrate-account",
		Hidden: true,
		Short:  "",
		Long:   "",
		Args:   cobra.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			fmt.Println("Migrating account...")
			rcfg, err := client.ConfigFromEnv()
			if err != nil {
				return err
			}
			rcfg.KeyType = "rsa"
			rsaClient, err := client.NewClient(rcfg)
			if err != nil {
				return err
			}

			ecfg, err := client.ConfigFromEnv()
			if err != nil {
				return err
			}
			ecfg.KeyType = "ed25519"
			ed25519Client, err := client.NewClient(ecfg)
			if err != nil {
				return err
			}

			lc := make(chan string)
			go func() {
				lh := &linkHandler{desc: "link-gen", linkChan: lc}
				_ = rsaClient.LinkGen(lh)
			}()
			tok := <-lc
			lh := &linkHandler{desc: "link-request", linkChan: lc}
			_ = ed25519Client.Link(lh, tok)
			if verbose {
				log.Info("link-gen sync encrypt keys")
			}
			err = rsaClient.SyncEncryptKeys()
			if err != nil {
				if verbose {
					log.Info("link-gen sync encrypt keys failed")
				} else {
					printError()
				}
				return err
			}
			if verbose {
				log.Info("link-request sync encrypt keys")
			}
			err = ed25519Client.SyncEncryptKeys()
			if err != nil {
				if verbose {
					log.Info("link-request sync encrypt keys failed")
				} else {
					printError()
				}
				return err
			}
			if !linkError {
				fmt.Println("Account migrated! You're good to go.")
			} else {
				printError()
			}
			return nil
		},
	}
)

MigrateAccountCmd is a command to convert your legacy RSA SSH keys to the new Ed25519 standard keys.

View Source
var NameCmd = &cobra.Command{
	Use:     "name [username]",
	Short:   "Username stuff",
	Long:    paragraph("Print or set your " + keyword("username") + ". If the name is already taken, just run it again with a different, cooler name. Basic latin letters and numbers only, 50 characters max."),
	Args:    cobra.RangeArgs(0, 1),
	Example: indent.String("charm name\ncharm name beatrix", 2),
	RunE: func(cmd *cobra.Command, args []string) error {
		cc := initCharmClient()
		switch len(args) {
		case 0:
			u, err := cc.Bio()
			if err != nil {
				return err
			}

			fmt.Println(u.Name)
			return nil
		default:
			n := args[0]
			if !client.ValidateName(n) {
				msg := fmt.Sprintf("%s is invalid.\n\nUsernames must be basic latin letters, numerals, and no more than 50 characters. And no emojis, kid.\n", code(n))
				fmt.Println(paragraph(msg))
				os.Exit(1)
			}
			u, err := cc.SetName(n)
			if err == charm.ErrNameTaken {
				paragraph(fmt.Sprintf("User name %s is already taken. Try a different, cooler name.\n", code(n)))
				os.Exit(1)
			}
			if err != nil {
				paragraph(fmt.Sprintf("Welp, there’s been an error. %s", subtle(err.Error())))
				return err
			}

			paragraph(fmt.Sprintf("OK! Your new username is %s", code(u.Name)))
			return nil
		}
	},
}

NameCmd is the cobra.Command to print or set a username.

View Source
var (

	// PostNewsCmd is the cobra.Command to self-host the Charm Cloud.
	PostNewsCmd = &cobra.Command{
		Use:    "post-news",
		Hidden: true,
		Short:  "Post news to the self-hosted Charm server.",
		Args:   cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			cfg := server.DefaultConfig()
			if serverDataDir != "" {
				cfg.DataDir = serverDataDir
			}
			sp := filepath.Join(cfg.DataDir, ".ssh")
			kp, err := keygen.New(filepath.Join(sp, "charm_server_ed25519"), keygen.WithKeyType(keygen.Ed25519), keygen.WithWrite())
			if err != nil {
				return err
			}
			cfg = cfg.WithKeys(kp.RawAuthorizedKey(), kp.RawPrivateKey())
			s, err := server.NewServer(cfg)
			if err != nil {
				return err
			}
			if newsSubject == "" {
				newsSubject = args[0]
			}
			ts := strings.Split(newsTagList, ",")
			d, err := os.ReadFile(args[0])
			if err != nil {
				return err
			}
			err = s.Config.DB.PostNews(newsSubject, string(d), ts)
			if err != nil {
				return err
			}
			return nil
		},
	}
)
View Source
var (

	// ServeCmd is the cobra.Command to self-host the Charm Cloud.
	ServeCmd = &cobra.Command{
		Use:     "serve",
		Aliases: []string{"server"},
		Hidden:  false,
		Short:   "Start a self-hosted Charm Cloud server.",
		Long:    paragraph("Start the SSH and HTTP servers needed to power a SQLite-backed Charm Cloud."),
		Args:    cobra.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			cfg := server.DefaultConfig()
			if serverHTTPPort != 0 {
				cfg.HTTPPort = serverHTTPPort
			}
			if serverSSHPort != 0 {
				cfg.SSHPort = serverSSHPort
			}
			if serverStatsPort != 0 {
				cfg.StatsPort = serverStatsPort
			}
			if serverHealthPort != 0 {
				cfg.HealthPort = serverHealthPort
			}
			if serverDataDir != "" {
				cfg.DataDir = serverDataDir
			}
			sp := filepath.Join(cfg.DataDir, ".ssh")
			kp, err := keygen.New(filepath.Join(sp, "charm_server_ed25519"), keygen.WithKeyType(keygen.Ed25519), keygen.WithWrite())
			if err != nil {
				return err
			}
			cfg = cfg.WithKeys(kp.RawAuthorizedKey(), kp.RawPrivateKey())
			s, err := server.NewServer(cfg)
			if err != nil {
				return err
			}

			done := make(chan os.Signal, 1)
			signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
			go func() {
				if err := s.Start(); err != nil {
					log.Fatal("error starting server", "err", err)
				}
			}()

			<-done

			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
			defer func() { cancel() }()

			return s.Shutdown(ctx)
		},
	}
)
View Source
var ServeMigrationCmd = &cobra.Command{
	Use:     "migrate",
	Aliases: []string{"migration"},
	Hidden:  true,
	Short:   "Run the server migration tool.",
	Long:    paragraph("Run the server migration tool to migrate the database."),
	Args:    cobra.NoArgs,
	RunE: func(cmd *cobra.Command, args []string) error {
		cfg := server.DefaultConfig()
		dp := filepath.Join(cfg.DataDir, "db", sqlite.DbName)
		_, err := os.Stat(dp)
		if err != nil {
			return fmt.Errorf("database does not exist: %s", err)
		}
		db := sqlite.NewDB(dp)
		for _, m := range []migration.Migration{
			migration.Migration0001,
		} {
			log.Print("Running migration", "id", fmt.Sprintf("%04d", m.ID), "name", m.Name)
			err = db.WrapTransaction(func(tx *sql.Tx) error {
				_, err := tx.Exec(m.SQL)
				if err != nil {
					return err
				}
				return nil
			})
			if err != nil {
				break
			}
		}
		if err != nil {
			return err
		}
		return nil
	},
}

ServeMigrationCmd migrate server db.

View Source
var WhereCmd = &cobra.Command{
	Use:   "where",
	Short: "Find where your cloud.charm.sh folder resides on your machine",
	Long:  paragraph("Find the absolute path to your charm keys, databases, etc."),
	RunE: func(cmd *cobra.Command, args []string) error {
		cc := initCharmClient()
		path, err := cc.DataPath()
		if err != nil {
			return err
		}
		fmt.Fprintln(cmd.OutOrStdout(), path)
		return nil
	},
}

WhereCmd is a command to find the absolute path to your charm data folder.

Functions

func LinkCmd

func LinkCmd(parentName string) *cobra.Command

LinkCmd is the cobra.Command to manage user account linking. Pass the name of the parent command.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL