164 lines
4.1 KiB
Go
164 lines
4.1 KiB
Go
|
|
package cmd
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
|
||
|
|
"github.com/Snider/Borg/pkg/console"
|
||
|
|
"github.com/Snider/Borg/pkg/tim"
|
||
|
|
"github.com/spf13/cobra"
|
||
|
|
)
|
||
|
|
|
||
|
|
var consoleCmd = NewConsoleCmd()
|
||
|
|
|
||
|
|
// NewConsoleCmd creates the console parent command.
|
||
|
|
func NewConsoleCmd() *cobra.Command {
|
||
|
|
cmd := &cobra.Command{
|
||
|
|
Use: "console",
|
||
|
|
Short: "Manage encrypted PWA console demos",
|
||
|
|
Long: `The Borg Console packages and serves encrypted PWA demos.
|
||
|
|
|
||
|
|
Build a console STIM:
|
||
|
|
borg console build -p "password" -o console.stim
|
||
|
|
|
||
|
|
Serve with unlock page:
|
||
|
|
borg console serve console.stim --open
|
||
|
|
|
||
|
|
Serve pre-unlocked:
|
||
|
|
borg console serve console.stim -p "password" --open`,
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd.AddCommand(NewConsoleBuildCmd())
|
||
|
|
cmd.AddCommand(NewConsoleServeCmd())
|
||
|
|
|
||
|
|
return cmd
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewConsoleBuildCmd creates the build subcommand.
|
||
|
|
func NewConsoleBuildCmd() *cobra.Command {
|
||
|
|
cmd := &cobra.Command{
|
||
|
|
Use: "build",
|
||
|
|
Short: "Build a console STIM from demo files",
|
||
|
|
Long: `Packages HTML demo files into an encrypted STIM container.
|
||
|
|
|
||
|
|
By default, looks for files in js/borg-stmf/ directory.
|
||
|
|
Required files: index.html, support-reply.html, stmf.wasm, wasm_exec.js`,
|
||
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||
|
|
password, _ := cmd.Flags().GetString("password")
|
||
|
|
output, _ := cmd.Flags().GetString("output")
|
||
|
|
sourceDir, _ := cmd.Flags().GetString("source")
|
||
|
|
|
||
|
|
if password == "" {
|
||
|
|
return fmt.Errorf("password is required")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create new TIM
|
||
|
|
m, err := tim.New()
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("creating TIM: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Required demo files
|
||
|
|
files := []string{
|
||
|
|
"index.html",
|
||
|
|
"support-reply.html",
|
||
|
|
"stmf.wasm",
|
||
|
|
"wasm_exec.js",
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add each file to the TIM
|
||
|
|
for _, f := range files {
|
||
|
|
path := filepath.Join(sourceDir, f)
|
||
|
|
data, err := os.ReadFile(path)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("reading %s: %w", f, err)
|
||
|
|
}
|
||
|
|
m.RootFS.AddData(f, data)
|
||
|
|
fmt.Printf(" + %s (%d bytes)\n", f, len(data))
|
||
|
|
}
|
||
|
|
|
||
|
|
// Encrypt to STIM
|
||
|
|
stim, err := m.ToSigil(password)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("encrypting STIM: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Write output
|
||
|
|
if err := os.WriteFile(output, stim, 0644); err != nil {
|
||
|
|
return fmt.Errorf("writing output: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Printf("\nBuilt: %s (%d bytes)\n", output, len(stim))
|
||
|
|
fmt.Println("Encrypted with ChaCha20-Poly1305")
|
||
|
|
|
||
|
|
return nil
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd.Flags().StringP("password", "p", "", "Encryption password (required)")
|
||
|
|
cmd.Flags().StringP("output", "o", "console.stim", "Output file")
|
||
|
|
cmd.Flags().StringP("source", "s", "js/borg-stmf", "Source directory")
|
||
|
|
cmd.MarkFlagRequired("password")
|
||
|
|
|
||
|
|
return cmd
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewConsoleServeCmd creates the serve subcommand.
|
||
|
|
func NewConsoleServeCmd() *cobra.Command {
|
||
|
|
cmd := &cobra.Command{
|
||
|
|
Use: "serve [stim-file]",
|
||
|
|
Short: "Serve an encrypted console STIM",
|
||
|
|
Long: `Starts an HTTP server to serve encrypted STIM content.
|
||
|
|
|
||
|
|
Without a password, shows a dark-themed unlock page.
|
||
|
|
With a password, decrypts immediately and serves content.
|
||
|
|
|
||
|
|
Examples:
|
||
|
|
borg console serve demos.stim --open
|
||
|
|
borg console serve demos.stim -p "password" --port 3000`,
|
||
|
|
Args: cobra.ExactArgs(1),
|
||
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||
|
|
stimPath := args[0]
|
||
|
|
password, _ := cmd.Flags().GetString("password")
|
||
|
|
port, _ := cmd.Flags().GetString("port")
|
||
|
|
openBrowser, _ := cmd.Flags().GetBool("open")
|
||
|
|
|
||
|
|
// Create server
|
||
|
|
server, err := console.NewServer(stimPath, password, port)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
// Print status
|
||
|
|
fmt.Printf("Borg Console serving at %s\n", server.URL())
|
||
|
|
if password != "" {
|
||
|
|
fmt.Println("Status: Unlocked (password provided)")
|
||
|
|
} else {
|
||
|
|
fmt.Println("Status: Locked (unlock page active)")
|
||
|
|
}
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
// Open browser if requested
|
||
|
|
if openBrowser {
|
||
|
|
if err := console.OpenBrowser(server.URL()); err != nil {
|
||
|
|
fmt.Printf("Warning: could not open browser: %v\n", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Start serving
|
||
|
|
return server.Start()
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd.Flags().StringP("password", "p", "", "Decryption password (skip unlock page)")
|
||
|
|
cmd.Flags().String("port", "8080", "Port to serve on")
|
||
|
|
cmd.Flags().Bool("open", false, "Auto-open browser")
|
||
|
|
|
||
|
|
return cmd
|
||
|
|
}
|
||
|
|
|
||
|
|
func init() {
|
||
|
|
RootCmd.AddCommand(consoleCmd)
|
||
|
|
}
|