Documentation ¶
Overview ¶
Package cmd implements the Cobra commands for the charm CLI.
Index ¶
Constants ¶
This section is empty.
Variables ¶
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.
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.
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.
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 }, } )
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."), } )
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.
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 }, } )
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.
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 }, } )
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.
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.
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.
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.
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 }, } )
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) }, } )
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.
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 ¶
Types ¶
This section is empty.