Getting started¶
This tutorial builds a tiny "credential setup" flow: it asks the user how they want
to store an API key, records that decision, and resolves the key back at runtime.
You will see the three storage modes, the interactive Prompter, and the resolve
step — the whole shape of the package in about thirty lines.
By the end you will have a program that runs on any machine, with no OS keychain required.
Prerequisites¶
- Go 1.26 or newer.
- A terminal (the stdlib prompter reads from standard input).
1. Create a module¶
2. Offer the storage modes¶
The package never decides for you which modes are appropriate — it hands you the
list, filtered by the environment. ModeChoices
returns the selectable modes as plain data (keychain only when usable, literal only
outside CI); a Prompter turns that into a question.
Create main.go:
package main
import (
"context"
"fmt"
"os"
"gitlab.com/phpboyscout/go/credentials"
)
func main() {
ctx := context.Background()
prompter := credentials.DefaultPrompter()
// Env-var reference is always offered; keychain only when a backend
// is registered and reachable; literal only outside CI. This demo
// registers no keychain backend, so two modes are offered.
choices := credentials.ModeChoices(
credentials.IsCI(),
credentials.Probe(ctx),
"Environment variable reference (recommended)",
"OS keychain",
"Literal value (plaintext)",
)
mode, err := prompter.SelectMode(ctx, "How should we store your API key?", choices)
if err != nil {
fmt.Fprintln(os.Stderr, "cancelled:", err)
os.Exit(1)
}
fmt.Println("You chose:", mode)
}
Run it:
You will see a numbered menu. Press Enter to take the default (environment-variable reference).
3. Capture the reference, resolve the secret¶
Env-var mode stores the name of a variable, not the secret. Add the capture and
resolve steps to main(), after the SelectMode call:
if mode == credentials.ModeEnvVar {
name, err := prompter.InputEnvVarName(ctx,
"Which environment variable holds the key?",
"CREDDEMO_API_KEY",
credentials.ValidateEnvVarName, // enforces ^[A-Z][A-Z0-9_]{0,63}$
)
if err != nil {
fmt.Fprintln(os.Stderr, "cancelled:", err)
os.Exit(1)
}
// A real tool writes `name` to its config file. At runtime it
// resolves the secret from the environment — the secret itself
// never touches disk.
fmt.Printf("Config would record: api.env = %q\n", name)
fmt.Printf("Resolved key: %q\n", os.Getenv(name))
}
Run it again, accept the default variable name, and watch it resolve from your environment:
How should we store your API key?
* 1) Environment variable reference (recommended)
2) Literal value (plaintext)
Select [1-2] (default 1):
Which environment variable holds the key? [CREDDEMO_API_KEY]:
Config would record: api.env = "CREDDEMO_API_KEY"
Resolved key: "sk-demo-123"
That is the whole model: the config records a reference, and the secret is resolved from a source outside the config file.
4. (Optional) Store a secret in the OS keychain¶
On a desktop with an OS keychain, you can store the secret itself outside both the config and the environment. Add one blank import to activate the keychain backend:
Now credentials.Probe(ctx) returns true on a machine with a working keychain, so
ModeChoices offers OS keychain as a third option. When the user picks it, the
secret round-trips through the platform store:
if mode == credentials.ModeKeychain {
secret, _ := prompter.InputSecret(ctx, "Paste your API key")
if err := credentials.Store(ctx, "creddemo", "api", secret); err != nil {
fmt.Fprintln(os.Stderr, "store failed:", err)
os.Exit(1)
}
got, _ := credentials.Retrieve(ctx, "creddemo", "api")
fmt.Printf("Stored and read back %d bytes from the keychain\n", len(got))
}
On a headless server without a keychain, Probe returns false and this option is
simply never offered — the flow degrades gracefully to env-var and literal modes.
Where next¶
- Choose a storage mode — the trade-offs between the three modes, and CI behaviour.
- Enable OS-keychain storage — the opt-in import and the regulated-build opt-out.
- Theme the prompts — replace
DefaultPrompterwith a themed TUI so the questions match your tool. - Trust model — what each mode protects against.