Documentation
¶
Overview ¶
Package console is the human side of a lockkit product: it holds the credentials and the only backend connection, enforces the approved policy, shows every request that needs a human decision on an approval screen, and writes the audit trail. Clients (a product's CLI and MCP server) reach it over the per-profile socket and never see a secret.
A product supplies a Backend (its methods, its connection, its checks and its policy) and calls Run. The pieces Run is made of — the terminal, the approvers, the isolation checks, the request loop — are exported for products that assemble their own console.
Index ¶
- Constants
- Variables
- func Bell(io IO)
- func CloseVerifier(v fingerprint.Verifier)
- func Decode(req ipc.Request, v any) *ipc.Response
- func DenyReason(ans string) string
- func ErrResp(id int64, code int, msg string) ipc.Response
- func FormatChanges(changes []config.Change) []string
- func Harden() error
- func Level(ctx context.Context, quantum time.Duration, start time.Time)
- func Loosens(changes []config.Change) bool
- func OKResp(id int64, v any) ipc.Response
- func PeerFrom(ctx context.Context) (ipc.Cred, bool)
- func PeerText(ctx context.Context) string
- func ProcessText(pid int) string
- func Run(ctx context.Context, p kit.Product, b Backend, o Options) error
- func SameUserRefusal(p kit.Product) string
- func SameUserRefused(p kit.Product, sys *sysconf.Config, allowSameUser bool) error
- func StampPeer(ctx context.Context, b *audit.Base)
- func WithPeer(ctx context.Context, c ipc.Cred) context.Context
- type Approval
- type Approver
- type Backend
- type Beller
- type Credentials
- type Decision
- type Env
- type Finding
- type Handler
- type IO
- type Isolation
- func (iso *Isolation) Boundary() string
- func (iso *Isolation) Level() int
- func (iso *Isolation) Listen(path string) (net.Listener, error)
- func (iso *Isolation) PeerCheck() func(ipc.Cred) bool
- func (iso *Isolation) RefuseX11IfProduction(production bool) error
- func (iso *Isolation) SocketPath(projectKey, profile string) (string, error)
- type IsolationEnv
- type Key
- type KeySource
- type LineSource
- type Loop
- type Options
- type Outcome
- type PIIBackend
- type Policy
- type Profile
- type Result
- type Screen
- type Server
- func (s *Server) Approve(ctx context.Context, id int64, sc Screen) Approval
- func (s *Server) Audit(rec audit.Record)
- func (s *Server) AuditBase(ctx context.Context, b audit.Base)
- func (s *Server) Auto() bool
- func (s *Server) CheckPolicy()
- func (s *Server) Close()
- func (s *Server) Command(ctx context.Context, line string)
- func (s *Server) End(reason string)
- func (s *Server) Ended() (string, bool)
- func (s *Server) Handle(ctx context.Context, req ipc.Request) ipc.Response
- func (s *Server) Key(k Key)
- func (s *Server) Level(ctx context.Context, start time.Time)
- func (s *Server) Loop() *Loop
- func (s *Server) Lost()
- func (s *Server) Policy() Policy
- func (s *Server) PolicyPending() bool
- func (s *Server) Println(line string)
- func (s *Server) Safe(text string) string
- func (s *Server) Sanitizer() *sanitize.Sanitizer
- func (s *Server) Session(ctx context.Context) (Session, error)
- func (s *Server) ShowResults() bool
- func (s *Server) Status() StatusResult
- func (s *Server) Tick()
- func (s *Server) UnmaskAllowed() bool
- type ServerConfig
- type Session
- type Sized
- type StatusLine
- type StatusResult
- type Terminal
- func (t *Terminal) Ask(ctx context.Context, prompt string, timeout time.Duration) (string, bool)
- func (t *Terminal) AskSecret(ctx context.Context, prompt string) ([]byte, error)
- func (t *Terminal) Bell()
- func (t *Terminal) Interactive() error
- func (t *Terminal) Keys() <-chan Key
- func (t *Terminal) Lines() <-chan string
- func (t *Terminal) Println(s string)
- func (t *Terminal) Restore()
- func (t *Terminal) SetKeyHandler(f func(Key))
- func (t *Terminal) SetStatus(s string)
- func (t *Terminal) Width() int
Constants ¶
const ( // ApprovalTimeout bounds every human answer. ApprovalTimeout = 5 * time.Minute // PolicyPoll is how often the config files are checked for changes. PolicyPoll = 2 * time.Second // ResponseQuantum levels the response time of a request whose answer // could otherwise tell the agent how long a human or a backend took. ResponseQuantum = 250 * time.Millisecond )
Timeouts of a console session. The idle timeout and the maximum session length are the profile's.
const CredentialsNone = "none"
CredentialsNone is the credentials mode of a backend that needs no secret (a local file, a socket the account owns).
const FingerDeadline = 30 * time.Second
FingerDeadline bounds the wait for a finger after a typed yes.
Variables ¶
var ErrConfig = errors.New("configuration error")
ErrConfig marks usage and configuration errors (exit code 3).
var ErrSecretTimeout = errors.New("console: no password entered")
ErrSecretTimeout is AskSecret's error when no answer comes within ApprovalTimeout.
var ErrTooManyNoMatch = errors.New("fingerprint: three consecutive non-matching fingers")
ErrTooManyNoMatch is set on the third consecutive no-match: the caller ends the console.
var Paint = ui.Painter{On: true}
Paint styles console lines.
Functions ¶
func CloseVerifier ¶
func CloseVerifier(v fingerprint.Verifier)
CloseVerifier releases a verifier that holds a connection (fprintd's).
func Decode ¶
Decode decodes the params of req into v, refusing unknown fields; nil when they decode (or are absent), else the error response.
func DenyReason ¶
DenyReason is the reason of an answer "n <text>" or "no <text>" (n and no in any case), trimmed, cut at 200 bytes and escaped; "" for any other answer.
func FormatChanges ¶
FormatChanges renders a policy diff, loosenings in red. Every value is escaped: a pattern or a host could otherwise carry terminal escapes that hide a loosening from the human.
func Harden ¶
func Harden() error
Harden keeps the secret out of core dumps and away from same-user ptrace and /proc/<pid>/mem readers: RLIMIT_CORE=0 and PR_SET_DUMPABLE=0.
func Level ¶
Level waits until the next multiple of quantum since start, so that the response time tells nothing finer than quantum (0 or less: no wait).
func ProcessText ¶
ProcessText names the process behind pid on Linux: its executable path when the kernel lets the console read it (a process of its own account), else the name the process gave itself, labelled as such; "" elsewhere. The uid and pid are the kernel's; the name is the process's choice.
func Run ¶
Run is a product's `console` command: the isolation checks, the policy review, the production confirmation, the credentials and the connection, the backend's checks, the socket, then the request loop until Ctrl-C, logout, :quit or a timeout.
func SameUserRefusal ¶
SameUserRefusal is the one text of the same-user refusal of p.
func SameUserRefused ¶
SameUserRefused is the refusal of a console of p without a separated setup (sys nil, as sysconf.Load returns it), or nil.
Types ¶
type Approval ¶
type Approval struct {
Approved bool
// Auto: approved by auto mode, without a prompt.
Auto bool
// Retype: the human typed Screen.Retype.
Retype bool
// Refusal is the answer to send when neither Approved nor Retype: the
// human denied (with the reason they typed after "n"), the approval
// timed out, the client left, or no finger matched.
Refusal ipc.Response
}
Approval is the outcome of Env.Approve.
type Approver ¶
type Approver interface {
Approve(ctx context.Context, d Decision) Result
// Mode is the audit value: "session" or "desktop+fingerprint".
Mode() string
}
Approver asks the human for one decision. Each call is one decision: a fingerprint match never carries over.
func NewDesktopTypedApprover ¶
NewDesktopTypedApprover is the level-1 approver: typed answers in a window the agent can type into, audited apart from the session shape.
func NewFingerprintApprover ¶
func NewFingerprintApprover(io IO, v fingerprint.Verifier, finger string, deadline time.Duration) Approver
NewFingerprintApprover wraps the typed approval with a fingerprint of the dedicated finger (system.toml `finger`), named in the prompt (level 2).
func NewTypedApprover ¶
NewTypedApprover is the session approver: typed answers in a login session of the console account, audited as "session".
type Backend ¶
type Backend interface {
// Methods are the product's IPC methods, beyond the console's own
// (hello, status, change.request, logout). A name the console serves
// is refused at start.
Methods() map[string]Handler
// Connect opens a session with the credentials the console obtained
// (asked, from the OS keychain, or from Vault). It is called at start
// and again after a lost connection or an expired login. The console
// wipes c.Secret once Connect returns.
Connect(ctx context.Context, c Credentials) (Session, error)
// Checks audits a new session: what the backend account can do beyond
// the profile (a red warning, typed "continue" to go on), or a reason
// to refuse it. On a production profile every finding refuses.
Checks(ctx context.Context, s Session) []Finding
// LoadPolicy reads the current policy from the config files. The
// console calls it at start and every PolicyPoll.
LoadPolicy() (Policy, error)
// Status is the product's part of the status method's answer.
Status(s Session) any
}
Backend is what a product plugs into the console: its methods, its connection to the backend it guards, the checks of that connection and its policy. Everything else — the terminal, the isolation, the approval screen, the modes, the policy review, the session limits, the audit log, the socket — is the console's.
type Beller ¶
type Beller interface {
Bell()
}
Beller is implemented by an IO that can ring the terminal bell, so that a human away from the console notices a prompt waiting for them.
type Credentials ¶
type Credentials struct {
User string
// Secret is nil for a profile with credentials "none".
Secret []byte
}
Credentials are what Connect needs: the user and its secret.
type Decision ¶
type Decision struct {
// Prompt is the typed prompt, already framed by the caller.
Prompt string
// Expected is the typed answer: "y" (any case) or the production
// profile name (exact).
Expected string
// Production selects the exact match.
Production bool
// Retype is the typed answer that asks to retype placeholders ("" when
// not offered).
Retype string
}
Decision names what a human is approving.
type Env ¶
type Env interface {
// Approve shows s and asks the human (or approves it in auto mode, see
// Screen.NeverAuto). The decision is audited before Approve returns.
Approve(ctx context.Context, id int64, s Screen) Approval
// Auto reports auto mode in effect (never on a production profile).
Auto() bool
// ShowResults reports the clear results switch (Ctrl-O).
ShowResults() bool
// UnmaskAllowed reports whether clients may ask for unmasked output
// (:unmask), each such request approved by the human.
UnmaskAllowed() bool
// Session returns the live session, reconnecting first when the login
// expired (credentials_ttl, a Vault lease) or the connection was lost
// (Lost).
Session(ctx context.Context) (Session, error)
// Lost drops the session after a lost connection: the next Session
// reconnects.
Lost()
// Policy is the approved policy in force.
Policy() Policy
// Audit writes rec as is; AuditBase stamps b with the profile and the
// client behind ctx first.
Audit(rec audit.Record)
AuditBase(ctx context.Context, b audit.Base)
// Println writes one console line; Safe escapes client text for it.
Println(line string)
Safe(s string) string
// Sanitizer scrubs error text for a client or the audit log.
Sanitizer() *sanitize.Sanitizer
// Level levels a response time (Options.Quantum) from start.
Level(ctx context.Context, start time.Time)
// End ends the session after the current request.
End(reason string)
}
Env is what a Handler may use of the console.
type Finding ¶
type Finding struct {
// Refuse stops the console (or the reconnection) whatever the profile.
Refuse bool
Text string
}
Finding is one result of Checks.
type Handler ¶
Handler serves one product method. env is the console: the approval screen, the modes, the session, the audit log.
type IO ¶
type IO interface {
Println(s string)
// Ask prints prompt and reads one line. It returns false when no answer
// came within timeout, when ctx ended (Ctrl-C, client gone) or when the
// terminal is closed.
Ask(ctx context.Context, prompt string, timeout time.Duration) (string, bool)
// AskSecret reads a line with echo off.
AskSecret(ctx context.Context, prompt string) ([]byte, error)
}
IO is the console's terminal. Ask and AskSecret discard pending input before prompting, so type-ahead never answers a prompt.
type Isolation ¶
type Isolation struct {
Product kit.Product
Sys *sysconf.Config
Display sysconf.Display
Self int
GID int
// Desktop is the desktop shape; Verifier is then its fingerprint
// reader (nil at level 1 and in the session shape).
Desktop bool
Verifier fingerprint.Verifier
// contains filtered or unexported fields
}
Isolation is how the console is kept apart from the agent: in a separated setup (sysconf) the console runs as the service account and serves the client group over a shared socket, either in its own login session (the session shape: the kernel keeps the agent out of its terminal) or in a terminal of the console account opened on the agent's desktop (the desktop shape: every approval also takes a fingerprint). Without one it refuses to start (except when IsolationEnv.AllowSameUser is set by a product's test build).
func CheckIsolation ¶
CheckIsolation applies the separation rules before any prompt and before the console writes any state. Warnings go to the terminal; a violation is an error. The production rule on X11 needs the profile and is applied later, by RefuseX11IfProduction.
func (*Isolation) Boundary ¶
Boundary names where the approval screen stands, for the status screen.
func (*Isolation) Level ¶
Level is the security level of the isolation: 2 for the desktop shape with a fingerprint reader, 1 for the desktop shape without, 0 for the session shape (the kernel is the boundary).
func (*Isolation) PeerCheck ¶
PeerCheck decides which peers are served: the console's own account and, in a separated setup, allowed_uids and the client group. The group is looked up at every connection: a uid removed from it loses access at its next connection, not at the next console start. Nil means the console's own uid only.
func (*Isolation) RefuseX11IfProduction ¶
RefuseX11IfProduction applies the production rule once the profile is known: a production profile never runs in an X11 session.
type IsolationEnv ¶
type IsolationEnv struct {
LoadSys func() (*sysconf.Config, error)
Display func() sysconf.Display
UID func() int
UserName func() (string, error)
LoginUID func() (int, bool)
TerminalOwner func() (int, bool)
ClientAllowed func(sys *sysconf.Config, uid int) bool
ClientGID func(sys *sysconf.Config) (int, error)
// AccountExists reports whether uid names an account.
AccountExists func(uid int) bool
// Relay reports a session opened through a relay (sysconf.SessionRelay).
Relay func() string
// MachinectlUIDs lists the callers of running `machinectl shell`
// processes (sysconf.MachinectlShellUIDs).
MachinectlUIDs func() ([]int, bool)
// DesktopEnv reports <PREFIX>DESKTOP=1: the desktop shape is asked for.
DesktopEnv func() bool
// DisplaySet reports a non-empty DISPLAY.
DisplaySet func() bool
// ParentChain lists the ancestors of the process, from the parent up
// (sysconf.ParentChain).
ParentChain func() ([]sysconf.Proc, error)
// NewVerifier opens the fingerprint reader of user.
NewVerifier func(user string) (fingerprint.Verifier, error)
// EnrolledTimeout bounds the start-up question to fprintd; past it, the
// reader counts as unavailable. 0 is 10s.
EnrolledTimeout time.Duration
// AllowSameUser lets the console run without a separated setup, in the
// agent's account. Only a product's test build sets it (a build tag):
// no config file, flag, environment variable or client may.
AllowSameUser bool
}
IsolationEnv holds what CheckIsolation reads from the system; tests replace its functions (RealIsolationEnv is the real one).
func RealIsolationEnv ¶
func RealIsolationEnv(p kit.Product, tty *os.File) IsolationEnv
RealIsolationEnv is the environment of a console of p on terminal tty (nil when unknown).
type Key ¶
type Key int
Key is a control key the human pressed, outside a line. Keys reach the console only from its own terminal: no client can send one.
type KeySource ¶
KeySource is implemented by an IO that reports the control keys pressed in the console: on Keys between prompts, to the handler during one.
type LineSource ¶
type LineSource interface {
Lines() <-chan string
}
LineSource is implemented by an IO that also delivers the lines the human types between requests (console commands such as :review).
type Loop ¶
type Loop struct {
IO IO
// Handle serves one request (required).
Handle func(ctx context.Context, req ipc.Request) ipc.Response
// Command runs a line typed by the human between requests.
Command func(ctx context.Context, line string)
// Key handles a control key pressed between prompts; with a KeySource
// IO it also handles the keys pressed during a prompt.
Key func(Key)
// Tick runs every second (session timeouts).
Tick func()
// Poll runs every PolicyPoll (config file changes).
Poll func()
// Ended reports the end of the session (required); End ends it.
Ended func() bool
End func(reason string)
// Refresh runs after every event (the status row); Clear on return.
Refresh func()
Clear func()
// PeerAllowed decides which clients are served, from the kernel's
// credentials of the peer. Nil serves the console's own account only.
PeerAllowed func(ipc.Cred) bool
}
Loop is the console's request loop: it accepts clients, checks their peer credentials and serves their requests one at a time on one goroutine, between the lines and control keys the human types and the periodic ticks. A request whose client goes away has its context cancelled, which cancels a pending approval prompt.
func (*Loop) Serve ¶
Serve accepts clients on ln and serves their requests one at a time until ctx ends or the session ends (logout, timeouts, :quit). lines are the console commands typed by the human (nil for none); the control keys come from the IO when it is a KeySource. ln is closed on return, which removes the socket.
type Options ¶
type Options struct {
// Profile is the profile to serve; ProjectRoot the project whose
// config holds it ("" for the user config).
Profile string
ProjectRoot string
IO IO
// Auto, AllowUnmask and ShowResults are the start-up switches (refused
// at level 2).
Auto, AllowUnmask, ShowResults bool
// StateDir is the state base (kit.StateBase() when empty).
StateDir string
// TTY is the console's terminal device (nil when unknown).
TTY *os.File
// Now defaults to time.Now.
Now func() time.Time
// Quantum levels response times (Env.Level): 0 is ResponseQuantum, a
// negative value none.
Quantum time.Duration
// Sanitizer scrubs error text; nil is sanitize.New().
Sanitizer *sanitize.Sanitizer
// Isolation replaces the system the isolation checks read (tests and
// a product's test build); nil is RealIsolationEnv.
Isolation *IsolationEnv
// Harden replaces Harden (tests).
Harden func() error
// VaultRead replaces secrets.VaultRead (tests).
VaultRead func(ctx context.Context, path string) (*secrets.VaultCredentials, error)
// Keychain replaces the product's OS keychain (tests).
Keychain *secrets.Keychain
// Ready, when set, is told the socket path once the console listens.
Ready func(socket string)
}
Options configure Run.
type Outcome ¶
type Outcome int
Outcome of an approval.
const ( Approved Outcome = iota // Denied is a typed answer that is not the expected one; Answer holds it. Denied // Unanswered: no answer within the timeout, or ctx ended. Unanswered RetypeRequested // NoFinger: desktop mode, typed yes, then no match, timeout or reader // error; Err holds it. NoFinger )
type PIIBackend ¶
type PIIBackend interface {
// PIIRules returns the mask and allow rules of p.
PIIRules(p Policy) (mask, allow []string)
// CheckMask validates a pattern sent by a client and returns its
// canonical form; an error is shown to the client (invalid params).
CheckMask(pattern string) (string, error)
// SaveMask adds the canonical pattern to the config file as it is on
// disk, so the human's unconfirmed edits are neither lost nor applied.
SaveMask(pattern string) error
// WithMask returns p with the canonical pattern added (canonical form,
// sorted lists); p itself is not changed.
WithMask(p Policy, pattern string) Policy
}
PIIBackend is implemented by a backend whose policy has mask rules: the console then serves pii.list and pii.add for it. A pattern's syntax (db.table.column, a JSON path, a header name) is the product's. Adding a mask rule only tightens the policy, so it applies at once.
type Policy ¶
Policy is a product's policy: the diff and description of config.Policy, plus the profile the console needs. Its JSON encoding is what the console fingerprints and stores as the approved policy, so it must be canonical (sorted lists, defaults applied) and decode back into the same type.
type Profile ¶
type Profile struct {
Name string
Production bool
// Where names the backend for the human (host:port, a path): it is
// shown escaped.
Where string
// Host, Port and DefaultPort name the OS keychain item of the secret
// ("<profile>@<host>:<port>").
Host string
Port, DefaultPort int
// User is the backend account; "" asks the human (or comes from Vault).
User string
// Credentials is config.CredentialsAsk, CredentialsKeychain,
// CredentialsVault or CredentialsNone; VaultPath is read for "vault".
Credentials string
VaultPath string
// CredentialsTTL asks for the secret again (and reconnects) once the
// connection is that old; 0 keeps it for the session.
CredentialsTTL time.Duration
// IdleTimeout ends the session after that long without activity,
// MaxSession after that long in all. 0 takes 20m and 4h.
IdleTimeout, MaxSession time.Duration
}
Profile is what the console needs to know of a profile.
type Screen ¶
type Screen struct {
// Header follows the profile name on the top line ("db / shop · tier read").
Header string
// Requester names the client; "" takes the peer behind the request.
Requester string
// Note is the agent's one-line reason (untrusted, escaped, audited).
Note string
// Body are the lines of the request; Warnings are shown in red.
Body []string
Warnings []string
// Production forces the production prompt; the profile's own flag
// always does.
Production bool
// NeverAuto is never approved by auto mode (an unmasked read, a
// destructive-looking operation): the human answers every time.
NeverAuto bool
// Red frames the screen in red outside production too.
Red bool
// Retype, when set, is an answer that asks to retype values; Approve
// then returns Approval.Retype.
Retype string
// Record, when set, turns the decision record into the product's own
// (with its fields); nil writes the audit.Base.
Record func(audit.Base) audit.Record
}
Screen is the content of an approval screen, entirely supplied by the product. The console frames it (red on production), closes it with the prompt and keeps the rules: typed profile name on production, timeout, fingerprint at level 2, audit of the decision.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server serves client requests one at a time against one session. It is not safe for concurrent use: Serve runs every request on one goroutine.
func NewServer ¶
func NewServer(cfg ServerConfig) (*Server, error)
NewServer checks cfg and returns a server enforcing cfg.Policy.
func (*Server) Approve ¶
Approve implements Env: it frames sc, approves it in auto mode or asks the human, and audits the decision.
func (*Server) CheckPolicy ¶
func (s *Server) CheckPolicy()
CheckPolicy re-reads the policy from the config files. Changes that only tighten are applied at once; a loosening waits for :review.
func (*Server) Handle ¶
Handle serves one request. None of the console's methods loosens the policy.
func (*Server) PolicyPending ¶
PolicyPending reports a loosening awaiting :review: a product refuses requests meanwhile (ipc.CodePolicyPending) when its policy matters to them.
func (*Server) Status ¶
func (s *Server) Status() StatusResult
Status is the status method's answer.
type ServerConfig ¶
type ServerConfig struct {
Product kit.Product
Backend Backend
// Policy is the approved policy the server enforces.
Policy Policy
// StateDir and ApprovedKey locate the approved policy file ("" keeps
// adoptions in memory).
StateDir string
ApprovedKey string
Session Session
Audit *audit.Log
IO IO
// Approver takes every human decision; nil means typed approvals.
Approver Approver
// Level is the isolation level (2: every decision takes a finger, and
// a typed answer alone never widens anything).
Level int
// Boundary names the desktop boundary for the status screen.
Boundary string
Now func() time.Time
// Auto, AllowUnmask and ShowResults are the switches at start.
Auto, AllowUnmask, ShowResults bool
// Reconnect opens a new session (credentials, Connect, Checks) under
// the profile in force; nil means a lost connection ends the console.
Reconnect func(ctx context.Context, p Profile) (Session, error)
// Quantum levels response times (Env.Level).
Quantum time.Duration
// PeerAllowed decides which clients are served; nil: the console's own
// account.
PeerAllowed func(ipc.Cred) bool
// Sanitizer scrubs error text; nil is sanitize.New().
Sanitizer *sanitize.Sanitizer
// Refused is the fingerprint of a loosening the human refused at start,
// not announced again until the files change.
Refused string
}
ServerConfig holds what a Server needs once the start-up sequence is done. Run builds it; tests build it directly.
type Sized ¶
type Sized interface {
Width() int
}
Sized is implemented by an IO that knows its width in columns.
type StatusLine ¶
type StatusLine interface {
SetStatus(string)
}
StatusLine is implemented by an IO that keeps a status row on screen.
type StatusResult ¶
type StatusResult struct {
Profile string `json:"profile"`
Production bool `json:"production"`
AutoApprove bool `json:"auto_approve"`
AllowUnmask bool `json:"allow_unmask"`
ShowResults bool `json:"show_results"`
IdleTimeoutInS int `json:"idle_timeout_in_s"`
SessionEndsInS int `json:"session_ends_in_s"`
// Level is the security level: 0 session shape, 1 or 2 desktop shape.
Level int `json:"level"`
Product any `json:"product,omitempty"`
}
StatusResult answers the status method: the console's part, and the product's (Backend.Status) under "product".
type Terminal ¶
type Terminal struct {
// contains filtered or unexported fields
}
Terminal is the IO of a real console: prompts and messages go to out, answers and console commands are read from in, which must be the controlling terminal.
Interactive puts the terminal in cbreak mode: the Terminal does the line editing itself, so that it also sees the control keys (Shift+Tab, Ctrl-O) and can keep a status line on the bottom row, above which the output scrolls. One goroutine reads in from the first prompt on, so that the console can wait for a request and for a typed command at the same time. Before each prompt the kernel's pending input is flushed and the lines already read are dropped: type-ahead never answers a prompt.
func NewTerminal ¶
NewTerminal returns the IO over in and out (normally os.Stdin and os.Stdout).
func (*Terminal) AskSecret ¶
AskSecret reads a line with echo off. Before the reader goroutine runs it uses secrets.PromptPassword; in cbreak mode it stops echoing itself; otherwise it turns the kernel echo off on in.
func (*Terminal) Interactive ¶
Interactive puts the terminal in cbreak mode (no line discipline, no kernel echo; signals and output post-processing kept) and starts reading it. Restore undoes it. An input that is not a terminal (a test pipe) stays as it is: lines are read, keys and the status line are off.
func (*Terminal) Restore ¶
func (t *Terminal) Restore()
Restore ends the status line and puts the terminal back in the mode it had before Interactive.
func (*Terminal) SetKeyHandler ¶
SetKeyHandler sets the function that handles a control key pressed while a prompt waits. It runs on the goroutine that asked.