feat(pass): add new async and improved cache service (#258)
This commit is contained in:
+2
-2
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user