Add unlock --idle, and packaging/repo.sh for repo.dixon.cx

vault unlock --idle counts the duration from the last command that used
the key rather than from the unlock: the agent resets its timer each
time it hands the key out. The agent loop no longer needs its own
goroutine.

packaging/repo.sh builds the dixon-vault .deb, .rpm and Arch packages
from a release tag (a clean git archive) with nfpm for amd64, arm64,
armv7, 386 and riscv64, then publishes them to the apt, dnf and pacman
repositories at repo.dixon.cx: apt-ftparchive with a signed InRelease,
createrepo_c (in a Fedora container) with signed RPMs and repomd.xml,
and repo-add (in an Arch container) with signed packages and database.
Indexes cover every package in the repository, under a lock, so other
projects publishing with their copy of the script are kept. The package
is dixon-vault because vault is HashiCorp Vault in Arch's [extra] and
HashiCorp's own repositories; it conflicts with vault.

README, guide and CLAUDE.md point at the repository.
This commit is contained in:
Andy Dixon
2026-10-03 09:44:49 +01:00
parent ec204f0ea2
commit 4c1507c463
8 changed files with 334 additions and 47 deletions
+37 -1
View File
@@ -21,6 +21,8 @@ import (
"strings"
"testing"
"time"
"github.com/andydixon/vault/internal/pqcrypt"
)
func TestKeyFile(t *testing.T) {
@@ -668,7 +670,7 @@ func TestAgent(t *testing.T) {
secret = append(secret, id.Seed()...)
pr, pw := io.Pipe()
stopped := make(chan struct{})
go func() { runAgent(sock, "1m", bytes.NewReader(secret), pw); close(stopped) }()
go func() { runAgent(sock, "1m", "fixed", bytes.NewReader(secret), pw); close(stopped) }()
if msg, _ := io.ReadAll(pr); string(msg) != "ok" {
t.Fatalf("agent said %q", msg)
}
@@ -690,3 +692,37 @@ func TestAgent(t *testing.T) {
t.Fatal("agent still running")
}
}
func TestAgentIdle(t *testing.T) {
dir := t.TempDir()
t.Setenv("XDG_RUNTIME_DIR", dir)
keyPath := filepath.Join(dir, "key")
sock, err := agentSocket(keyPath)
if err != nil {
t.Fatal(err)
}
id, err := pqcrypt.GeneratePQIdentity()
if err != nil {
t.Fatal(err)
}
secret := binary.BigEndian.AppendUint16(nil, id.SuiteID())
secret = append(secret, id.Seed()...)
pr, pw := io.Pipe()
stopped := make(chan struct{})
go func() { runAgent(sock, "400ms", "idle", bytes.NewReader(secret), pw); close(stopped) }()
if msg, _ := io.ReadAll(pr); string(msg) != "ok" {
t.Fatalf("agent said %q", msg)
}
// Used every 150ms, it outlives its 400ms by well over twice.
for range 7 {
time.Sleep(150 * time.Millisecond)
if _, err := agentIdentity(keyPath); err != nil {
t.Fatal("idle agent expired while in use: ", err)
}
}
select {
case <-stopped:
case <-time.After(2 * time.Second):
t.Fatal("idle agent outlived its idle time")
}
}