feat(pass): add new async and improved cache service (#258)

This commit is contained in:
emrtnn
2026-08-05 17:50:27 -04:00
committed by GitHub
parent 835770702d
commit d2ecb3eb6e
3 changed files with 65 additions and 56 deletions
+2 -2
View File
@@ -12,7 +12,7 @@ Pass adds password-store search to the Noctalia launcher so you can copy passwor
## Requirements
Install `pass`, `pass-otp`, `gpg`, and `wl-copy` on `PATH`.
Install `pass`, `pass-otp`, `gpg`, `wl-copy`, and `find` on `PATH`.
A working password store is expected in the same location `pass` uses: `$PASSWORD_STORE_DIR` when that variable is set, otherwise the default `~/.password-store`. OTP copying requires entries that are configured for `pass-otp`.
@@ -41,7 +41,7 @@ This plugin does not expose custom IPC actions. It provides launcher provider `s
## Notes
- Filesystem reads: the service recursively scans `$PASSWORD_STORE_DIR` when set, otherwise `~/.password-store`, and indexes non-hidden `*.gpg` file names. It does not read decrypted password contents.
- Spawned processes: activating a password result runs `pass -c <entry>`; activating an OTP result runs `pass otp -c <entry>`. If GPG reports an unlock failure, the plugin opens a terminal and runs the same command interactively.
- Spawned processes: the cache service runs `find` asynchronously to index entry paths without blocking Noctalia's plugin runtime. Activating a password result runs `pass -c <entry>`; activating an OTP result runs `pass otp -c <entry>`. If GPG reports an unlock failure, the plugin opens a terminal and runs the same command interactively.
- Clipboard/privacy: copied secrets are handled by `pass`/`pass-otp` and the system clipboard tooling, typically including `gpg` and `wl-copy` on Wayland. The plugin stores only entry paths/titles in Noctalia state, not decrypted secrets.
- Network: the plugin makes no network calls.
- Writes: the plugin does not write files directly. `pass`, `pass-otp`, `gpg`, or clipboard tools may update their own runtime files such as agent or clipboard state.
+2 -2
View File
@@ -1,6 +1,6 @@
id = "emrtnn/pass"
name = "Pass"
version = "0.1.0"
version = "0.1.1"
plugin_api = 3
author = "emrtnn"
license = "MIT"
@@ -8,7 +8,7 @@ deprecated = false
icon = "key"
description = "Search and copy password-store entries from the Noctalia launcher"
tags = ["launcher", "privacy", "productivity", "utility"]
dependencies = ["pass", "pass-otp", "gpg", "wl-copy"]
dependencies = ["pass", "pass-otp", "gpg", "wl-copy", "find"]
[[setting]]
key = "refresh_interval"
+61 -52
View File
@@ -1,11 +1,16 @@
--!nonstrict
local entries = {}
local scanInFlight = false
local function publish()
noctalia.state.set("entries", entries)
end
local function shellEscape(value)
return "'" .. value:gsub("'", "'\\''") .. "'"
end
local function passwordStoreDir()
local dir = noctalia.getenv("PASSWORD_STORE_DIR")
@@ -16,56 +21,11 @@ local function passwordStoreDir()
return noctalia.expandPath("~/.password-store")
end
local function scan(dir, prefix)
prefix = prefix or ""
local names, err = noctalia.listDir(dir)
if not names then
noctalia.log("Failed to list " .. dir .. ": " .. tostring(err))
local function rebuild()
if scanInFlight then
return
end
for _, name in ipairs(names) do
-- Ignore hidden files/directories (.git, .extensions, .gpg-id, ...)
if name:sub(1, 1) ~= "." then
-- listDir already proved the name exists, so an entry needs no stat.
-- Only non-entry names get one, to decide whether to recurse.
if name:sub(-4) == ".gpg" then
local path = prefix .. name:sub(1, -5)
local title = path
local subtitle = ""
local lastSlash = path:match("^.*()/")
if lastSlash then
subtitle = path:sub(1, lastSlash - 1)
title = path:sub(lastSlash + 1)
end
table.insert(entries, {
id = path,
path = path,
title = title,
subtitle = subtitle,
})
else
local full = dir .. "/" .. name
local info = noctalia.fileInfo(full)
if info and info.isDir then
scan(full, prefix .. name .. "/")
end
end
end
end
end
local function rebuild()
entries = {}
local root = passwordStoreDir()
if not noctalia.fileExists(root) then
@@ -73,13 +33,62 @@ local function rebuild()
return
end
scan(root)
-- Directory traversal can exceed Noctalia's callback CPU budget on large stores.
-- Run it in a subprocess and only build the small launcher cache in Luau.
local command = "cd " .. shellEscape(root)
.. " && find . -mindepth 1"
.. " \\( -type d -name '.*' -prune \\)"
.. " -o \\( -name '*.gpg' ! -name '.*' -print0 \\)"
table.sort(entries, function(a, b)
return a.title:lower() < b.title:lower()
end)
scanInFlight = true
publish()
local accepted = noctalia.runAsync(command, function(result)
scanInFlight = false
if result.exitCode ~= 0 then
noctalia.log("Failed to scan " .. root .. ": " .. (result.stderr or "unknown error"))
return
end
if result.stdoutTruncated then
noctalia.log("Failed to scan " .. root .. ": command output was truncated")
return
end
local nextEntries = {}
for file in (result.stdout or ""):gmatch("([^%z]+)%z") do
-- find returns paths as ./folder/entry.gpg.
local path = file:sub(3, -5)
local title = path
local subtitle = ""
local lastSlash = path:match("^.*()/")
if lastSlash then
subtitle = path:sub(1, lastSlash - 1)
title = path:sub(lastSlash + 1)
end
table.insert(nextEntries, {
id = path,
path = path,
title = title,
subtitle = subtitle,
})
end
table.sort(nextEntries, function(a, b)
return a.title:lower() < b.title:lower()
end)
entries = nextEntries
publish()
end, 60000)
if not accepted then
scanInFlight = false
noctalia.log("Failed to start password-store scan")
end
end
function update()