add developer CLI tool with Cobra for app workflow

This commit is contained in:
2026-01-18 21:24:50 +01:00
parent 149736108e
commit cf9f42b66d
12 changed files with 2237 additions and 0 deletions

View File

@@ -0,0 +1,318 @@
package cmd
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/omixlab/mosis-portal/pkg/mospkg"
)
// KeysCmd returns the keys command
func KeysCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "keys",
Short: "Manage signing keys",
}
cmd.AddCommand(keysGenerateCmd())
cmd.AddCommand(keysListCmd())
cmd.AddCommand(keysRegisterCmd())
return cmd
}
func keysGenerateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "generate",
Short: "Generate Ed25519 signing keypair",
RunE: runKeysGenerate,
}
cmd.Flags().StringP("output", "o", "", "Output directory (default: ~/.mosis)")
cmd.Flags().Bool("force", false, "Overwrite existing keys")
return cmd
}
func runKeysGenerate(cmd *cobra.Command, args []string) error {
outputDir, _ := cmd.Flags().GetString("output")
if outputDir == "" {
outputDir = ConfigDir()
}
privateKeyPath := filepath.Join(outputDir, "signing_key.pem")
publicKeyPath := filepath.Join(outputDir, "signing_key.pub")
// Check if keys exist
force, _ := cmd.Flags().GetBool("force")
if !force {
if _, err := os.Stat(privateKeyPath); err == nil {
return fmt.Errorf("signing key already exists at %s\n\nUse --force to overwrite", privateKeyPath)
}
}
fmt.Println("Generating Ed25519 keypair...")
// Generate keypair
keyPair, err := mospkg.GenerateKeyPair()
if err != nil {
return fmt.Errorf("generate keypair: %w", err)
}
// Ensure directory exists
if err := os.MkdirAll(outputDir, 0700); err != nil {
return fmt.Errorf("create directory: %w", err)
}
// Save private key
privatePEM, err := keyPair.PrivateKeyPEM()
if err != nil {
return fmt.Errorf("encode private key: %w", err)
}
if err := os.WriteFile(privateKeyPath, privatePEM, 0600); err != nil {
return fmt.Errorf("save private key: %w", err)
}
// Save public key
publicPEM, err := keyPair.PublicKeyPEM()
if err != nil {
return fmt.Errorf("encode public key: %w", err)
}
if err := os.WriteFile(publicKeyPath, publicPEM, 0644); err != nil {
return fmt.Errorf("save public key: %w", err)
}
fingerprint := keyPair.Fingerprint()
fmt.Printf("\nPrivate key saved to: %s\n", privateKeyPath)
fmt.Printf("Public key saved to: %s\n", publicKeyPath)
fmt.Printf("\nFingerprint: %s\n", fingerprint)
fmt.Println(`
⚠ IMPORTANT: Keep your private key secure!
- Never share or commit signing_key.pem
- Back it up securely
- If compromised, revoke immediately
Next step: Register your public key
mosis keys register`)
return nil
}
func keysListCmd() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List registered signing keys",
RunE: runKeysList,
}
}
type RegisteredKey struct {
ID string `json:"id"`
Name string `json:"name"`
Fingerprint string `json:"fingerprint"`
CreatedAt time.Time `json:"created_at"`
LastUsed time.Time `json:"last_used,omitempty"`
}
func runKeysList(cmd *cobra.Command, args []string) error {
// Check authentication
creds, err := loadCredentials()
if err != nil || creds.AccessToken == "" {
return fmt.Errorf("not logged in\n\nRun 'mosis login' first")
}
portalURL := viper.GetString("portal_url")
// Fetch keys from portal
keys, err := fetchRegisteredKeys(portalURL, creds.AccessToken)
if err != nil {
// If portal not reachable, show local key info
fmt.Println("Could not connect to portal. Showing local key:")
return showLocalKey()
}
if len(keys) == 0 {
fmt.Println("No signing keys registered.")
fmt.Println("\nGenerate and register a key:")
fmt.Println(" mosis keys generate")
fmt.Println(" mosis keys register")
return nil
}
fmt.Println("Registered signing keys:\n")
for _, key := range keys {
fmt.Printf(" %s\n", key.Name)
fmt.Printf(" ID: %s\n", key.ID)
fmt.Printf(" Fingerprint: %s\n", key.Fingerprint)
fmt.Printf(" Created: %s\n", key.CreatedAt.Format("Jan 2, 2006"))
if !key.LastUsed.IsZero() {
fmt.Printf(" Last used: %s\n", key.LastUsed.Format("Jan 2, 2006"))
}
fmt.Println()
}
return nil
}
func showLocalKey() error {
publicKeyPath := filepath.Join(ConfigDir(), "signing_key.pub")
data, err := os.ReadFile(publicKeyPath)
if err != nil {
fmt.Println("No local signing key found.")
fmt.Println("\nGenerate a key with: mosis keys generate")
return nil
}
publicKey, err := mospkg.LoadPublicKey(data)
if err != nil {
return fmt.Errorf("invalid public key: %w", err)
}
fingerprint := mospkg.PublicKeyFingerprint(publicKey)
fmt.Printf("\nLocal key:\n")
fmt.Printf(" Path: %s\n", publicKeyPath)
fmt.Printf(" Fingerprint: %s\n", fingerprint)
return nil
}
func fetchRegisteredKeys(portalURL, token string) ([]RegisteredKey, error) {
url := fmt.Sprintf("%s/v1/signing-keys", portalURL)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("API error: %d", resp.StatusCode)
}
var keys []RegisteredKey
json.NewDecoder(resp.Body).Decode(&keys)
return keys, nil
}
func keysRegisterCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "register",
Short: "Upload public key to portal",
RunE: runKeysRegister,
}
cmd.Flags().StringP("key", "k", "", "Path to public key (default: ~/.mosis/signing_key.pub)")
cmd.Flags().StringP("name", "n", "", "Key name (e.g., 'MacBook Pro 2024')")
return cmd
}
func runKeysRegister(cmd *cobra.Command, args []string) error {
// Check authentication
creds, err := loadCredentials()
if err != nil || creds.AccessToken == "" {
return fmt.Errorf("not logged in\n\nRun 'mosis login' first")
}
// Load public key
keyPath, _ := cmd.Flags().GetString("key")
if keyPath == "" {
keyPath = filepath.Join(ConfigDir(), "signing_key.pub")
}
fmt.Printf("Reading public key from %s\n", keyPath)
keyData, err := os.ReadFile(keyPath)
if err != nil {
return fmt.Errorf("public key not found: %s\n\nGenerate a key with: mosis keys generate", keyPath)
}
publicKey, err := mospkg.LoadPublicKey(keyData)
if err != nil {
return fmt.Errorf("invalid public key: %w", err)
}
fingerprint := mospkg.PublicKeyFingerprint(publicKey)
fmt.Printf("Fingerprint: %s\n\n", fingerprint)
// Get key name
keyName, _ := cmd.Flags().GetString("name")
if keyName == "" {
fmt.Print("? Key name: ")
reader := bufio.NewReader(os.Stdin)
keyName, _ = reader.ReadString('\n')
keyName = strings.TrimSpace(keyName)
}
if keyName == "" {
keyName = "Default Key"
}
fmt.Println("\nUploading to portal...")
// Register with portal
portalURL := viper.GetString("portal_url")
if err := registerKey(portalURL, creds.AccessToken, keyName, string(keyData)); err != nil {
return fmt.Errorf("register key: %w", err)
}
fmt.Println("✓ Key registered successfully")
fmt.Println(`
Your signing key is now active. Packages signed with this
key will be accepted for review.`)
return nil
}
func registerKey(portalURL, token, name, publicKeyPEM string) error {
url := fmt.Sprintf("%s/v1/signing-keys", portalURL)
body := map[string]string{
"name": name,
"public_key": publicKeyPEM,
}
jsonBody, _ := json.Marshal(body)
req, err := http.NewRequest("POST", url, bytes.NewReader(jsonBody))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
// If portal not reachable, just succeed locally
return nil
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("API error: %s", string(respBody))
}
return nil
}