A Go password manager for the terminal
Two Widgets
The terminal UI, like in the previous Snapshot columns, uses the termui package from GitHub. Listing 5 calls ui.Init()
in line 12 to initialize its functions and calls ui.Close()
to acknowledge a user abort in the defer
statement in line 15. This neatly folds up the UI to leave a usable terminal for the shell.
Listing 5
ui.go
01 package main 02 import ( 03 "fmt" 04 ui "github.com/gizak/termui/v3" 05 "github.com/gizak/termui/v3/widgets" 06 ) 07 func runUI(lines []string) { 08 rows := []string{} 09 for _, line := range lines { 10 rows = append(rows, mask(line)) 11 } 12 if err := ui.Init(); err != nil { 13 panic(err) 14 } 15 defer ui.Close() 16 lb := widgets.NewList() 17 lb.Rows = rows 18 lb.SelectedRow = 0 19 lb.SelectedRowStyle = ui.NewStyle(ui.ColorBlack) 20 lb.TextStyle.Fg = ui.ColorGreen 21 lb.Title = fmt.Sprintf("passview 1.0") 22 pa := widgets.NewParagraph() 23 pa.Text = "[Q]uit [Enter]reveal" 24 pa.TextStyle.Fg = ui.ColorBlack 25 w, h := ui.TerminalDimensions() 26 lb.SetRect(0, 0, w, h-3) 27 pa.SetRect(0, h-3, w, h) 28 ui.Render(lb, pa) 29 uiEvents := ui.PollEvents() 30 for { 31 select { 32 case e := <-uiEvents: 33 switch e.ID { 34 case "k": 35 hideCur(lb) 36 lb.ScrollUp() 37 ui.Render(lb) 38 case "j": 39 hideCur(lb) 40 lb.ScrollDown() 41 ui.Render(lb) 42 case "q", "<C-c>": 43 return 44 case "<Enter>": 45 showCur(lb, lines) 46 ui.Render(lb) 47 } 48 } 49 } 50 } 51 func hideCur(lb *widgets.List) { 52 idx := lb.SelectedRow 53 lb.Rows[idx] = mask(lb.Rows[idx]) 54 } 55 func showCur(lb *widgets.List, lines []string) { 56 idx := lb.SelectedRow 57 lb.Rows[idx] = lines[idx] 58 }
The terminal UI shown in Figure 1 consists of two stacked widgets: On top, there is a listbox with the password entries, which the user can scroll through. It also supports leafing through multiple pages if the list of entries is longer than the maximum number of lines displayed. Below the listbox, at the bottom of the terminal window, a paragraph widget indicates which keys the user can press next: Enter reveals the selected password, while Q exits the program.
To allow the UI to take advantage of the entire geometry of the terminal window, line 25 queries the window dimensions, using the TerminalsDimensions()
helper function from the termui package. From the width and height of the window, lines 26 and 27 then determine the position and dimensions of the two stacked widgets. In this case, the paragraph widget is assigned the bottom three lines, while the listbox on top gets everything else. Horizontally, both widgets extend to the edges of the terminal window.
The listbox entries reside in the Rows
attribute of the listbox as an array slice of strings. Line 17 populates this array with the rows
array slice. Before this happened, the for
loop starting in line 9 stuffed all masked entries into rows
but kept the original lines in lines
. The two array slices for masked and unmasked entries make it easy to later reveal masked entries: The code only needs to look at the same index number in the original slice to reveal the unmasked content.
After line 28 has drawn the widgets on screen, line 29 fires off the PollEvents()
Go routine, which will intercept all of the user's keystrokes concurrently from now on and send them to the uiEvents
channel. From there, the program fetches events via the select
statement in the infinite for
loop starting in line 30 and immediately responds to all incoming keystrokes. If the user presses K to scroll up, line 35 uses hideCur()
(starting in line 51) and the mask()
function to hide a password that may have been previously revealed in the current listbox entry. Then, ScrollUp()
(line 36) tells the listbox to scroll up one item, and the subsequent Render
command smoothly displays the change in the UI. The same applies to pressing J, which lets the user scroll down through the list of entries.
Line 44 intercepts presses of the Enter key and calls the showCur()
function defined in line 55. The function fetches the original unmasked password entry from the lines
list and replaces the currently selected line of the listbox with it. And, hey presto, account and password are displayed on the screen in clear text. hideCur()
starting in line 51 does the opposite and hides the current entry using the mask()
function when the user moves away.
Installation
As always, the binary can be generated from the Go code using the typical three-step process (Listing 6). This process fetches all the dependent libraries from GitHub, compiles them, and binds everything together to create the finished pv
binary. You can then copy this to any target computer with a similar architecture. It'll run there without complaints, and it also conveniently even conjures up the UI into the terminal on remote machines. You will want to copy the test.age
password file to a file in your home directory for production operation; the password reminder is then ready for use.
Listing 6
Compiling the Program
$ go mod init pv $ go mod tidy $ go build pv.go crypto.go util.go ui.go
Infos
- Age: https://github.com/FiloSottile/age
- "What did Ken Thompson mean when he said, 'I'd spell creat with an "e".'?": https://unix.stackexchange.com/questions/10893/what-did-ken-thompson-mean-when-he-vsaid-id-spell-creat-with-an-e
« Previous 1 2 3
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Gnome 48 Debuts New Audio Player
To date, the audio player found within the Gnome desktop has been meh at best, but with the upcoming release that all changes.
-
Plasma 6.3 Ready for Public Beta Testing
Plasma 6.3 will ship with KDE Gear 24.12.1 and KDE Frameworks 6.10, along with some new and exciting features.
-
Budgie 10.10 Scheduled for Q1 2025 with a Surprising Desktop Update
If Budgie is your desktop environment of choice, 2025 is going to be a great year for you.
-
Firefox 134 Offers Improvements for Linux Version
Fans of Linux and Firefox rejoice, as there's a new version available that includes some handy updates.
-
Serpent OS Arrives with a New Alpha Release
After months of silence, Ikey Doherty has released a new alpha for his Serpent OS.
-
HashiCorp Cofounder Unveils Ghostty, a Linux Terminal App
Ghostty is a new Linux terminal app that's fast, feature-rich, and offers a platform-native GUI while remaining cross-platform.
-
Fedora Asahi Remix 41 Available for Apple Silicon
If you have an Apple Silicon Mac and you're hoping to install Fedora, you're in luck because the latest release supports the M1 and M2 chips.
-
Systemd Fixes Bug While Facing New Challenger in GNU Shepherd
The systemd developers have fixed a really nasty bug amid the release of the new GNU Shepherd init system.
-
AlmaLinux 10.0 Beta Released
The AlmaLinux OS Foundation has announced the availability of AlmaLinux 10.0 Beta ("Purple Lion") for all supported devices with significant changes.
-
Gnome 47.2 Now Available
Gnome 47.2 is now available for general use but don't expect much in the way of newness, as this is all about improvements and bug fixes.