remove the need to setup .env and ask for porkbun credential name directly

This commit is contained in:
juancwu 2026-01-21 19:18:47 +00:00
commit ec94e6ad46
7 changed files with 164 additions and 47 deletions

View file

@ -1,2 +0,0 @@
PORKBUN_API_KEY_NAME=
PORKBUN_SECRET_API_KEY_NAME=

View file

@ -5,14 +5,11 @@ import (
"os" "os"
"git.juancwu.dev/juancwu/porkbacon/internal/app" "git.juancwu.dev/juancwu/porkbacon/internal/app"
"git.juancwu.dev/juancwu/porkbacon/internal/config"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )
func main() { func main() {
cfg := config.Load() a := app.New()
a := app.New(cfg)
p := tea.NewProgram(a) p := tea.NewProgram(a)
if _, err := p.Run(); err != nil { if _, err := p.Run(); err != nil {

2
go.mod
View file

@ -3,11 +3,13 @@ module git.juancwu.dev/juancwu/porkbacon
go 1.25.6 go 1.25.6
require ( require (
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/bubbletea v1.3.10
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
) )
require ( require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect

4
go.sum
View file

@ -1,5 +1,9 @@
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=

View file

@ -1,33 +1,164 @@
package app package app
import ( import (
"git.juancwu.dev/juancwu/porkbacon/internal/config" "fmt"
"git.juancwu.dev/juancwu/porkbacon/internal/pass"
"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )
const (
StateInit uint = iota
StateRetrivePorkbunCredentials
StateIdle
)
type App struct { type App struct {
cfg *config.Config state uint
apiKeyName string
secretApiKeyName string
apiKey string
secretKey string
loading bool
textInput textinput.Model
spinner spinner.Model
err error
} }
func New(cfg *config.Config) App { func New() App {
return App{cfg: cfg} ti := textinput.New()
ti.Placeholder = "Pass/Name"
ti.Focus()
ti.Width = 20
s := spinner.New()
s.Spinner = spinner.Dot
return App{
state: StateInit,
textInput: ti,
spinner: s,
}
} }
func (a App) Init() tea.Cmd { func (a App) Init() tea.Cmd {
return nil return tea.Sequence(textinput.Blink, a.spinner.Tick)
} }
func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
var cmd tea.Cmd
var key string
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
switch msg.String() { switch msg.String() {
case "ctrl+c", "q": case "ctrl+c":
return a, tea.Quit return a, tea.Quit
default:
key = msg.String()
} }
} }
if a.err != nil {
return a, nil return a, nil
}
if a.state == StateInit && key == "enter" {
return a.handleEnter()
}
if a.state == StateRetrivePorkbunCredentials && !a.loading {
return a.retrievePorkbunCredentials()
}
a.textInput, cmd = a.textInput.Update(msg)
cmds = append(cmds, cmd)
a.spinner, cmd = a.spinner.Update(msg)
cmds = append(cmds, cmd)
return a, tea.Sequence(cmds...)
} }
func (a App) View() string { func (a App) View() string {
return "porkbacon app" if a.err != nil {
return a.err.Error()
}
if a.loading {
return fmt.Sprintf("%s Loading...", a.spinner.View())
}
switch a.state {
case StateInit:
return a.renderPorkbunCredentialForm()
case StateIdle:
return fmt.Sprintf("API key: %s\nSecret Key: %s\n\n", a.apiKey, a.secretKey)
}
return "Invalid state"
}
func (a *App) renderPorkbunCredentialForm() string {
if a.apiKeyName == "" {
return fmt.Sprintf("Enter Porkbun API Key Name:\n%s\n\n", a.textInput.View())
}
if a.secretApiKeyName == "" {
return fmt.Sprintf("Enter Porkbun Secret API Key Name:\n%s\n\n", a.textInput.View())
}
return "Invalid state"
}
func (a *App) handleEnter() (tea.Model, tea.Cmd) {
switch a.state {
case StateInit:
if a.apiKeyName == "" && a.textInput.Value() != "" {
a.apiKeyName = a.textInput.Value()
a.textInput.Reset()
} else if a.secretApiKeyName == "" && a.textInput.Value() != "" {
a.secretApiKeyName = a.textInput.Value()
a.textInput.Reset()
a.state = StateRetrivePorkbunCredentials
}
}
return *a, nil
}
func (a *App) retrievePorkbunCredentials() (tea.Model, tea.Cmd) {
if a.apiKeyName == "" || a.secretApiKeyName == "" {
panic(fmt.Errorf("Porkbun credentials incomplete. Either API key or secret API key not defined."))
}
a.loading = true
apiKey, err := pass.Get(a.apiKeyName)
if err != nil {
a.err = err
a.state = StateIdle
a.loading = false
return *a, nil
}
secretKey, err := pass.Get(a.secretApiKeyName)
if err != nil {
a.err = err
a.state = StateIdle
a.loading = false
return *a, nil
}
a.apiKey = apiKey
a.secretKey = secretKey
a.state = StateIdle
a.loading = false
return *a, nil
} }

View file

@ -1,33 +0,0 @@
package config
import (
"fmt"
"os"
"github.com/joho/godotenv"
)
type Config struct {
PorkbunApiKeyName string
PorkbunSecretApiKeyName string
}
func Load() *Config {
godotenv.Load()
cfg := &Config{
PorkbunApiKeyName: envRequired("PORKBUN_API_KEY_NAME"),
PorkbunSecretApiKeyName: envRequired("PORKBUN_SECRET_API_KEY_NAME"),
}
return cfg
}
func envRequired(key string) string {
value, ok := os.LookupEnv(key)
if !ok || value == "" {
fmt.Printf("Error: required environment variable %s not set.\n", key)
os.Exit(1)
}
return value
}

18
internal/pass/pass.go Normal file
View file

@ -0,0 +1,18 @@
package pass
import (
"fmt"
"os/exec"
"strings"
)
func Get(name string) (string, error) {
cmd := exec.Command("pass", "show", name)
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("command execution failed: %w", err)
}
return strings.TrimSpace(string(output)), nil
}