Files
keeporg/cmd/keeporg/main.go
T
root 4d0d53ff04 Add client engine and desktop CLI that mirror KDBX files
Why: a KeePass application must see an ordinary .kdbx file. A mirror
directory of real files gives that on every desktop platform without FUSE,
WinFsp or macFUSE, and without cgo.

What: internal/client holds the API wrapper, an SSE reader, and a mirror
agent whose entire policy is one pure decide() table: adopt, pull, push,
force-push, server-wins, restore or manual. Saves are detected by rescan,
remote changes arrive over the event stream, and every overwrite of local
bytes leaves a conflict copy. cmd/keeporg drives it: login (password, TOTP or
browser/OIDC device flow), run, sync, status, plus the group, grant, database
and invite administration.

Proved: go test ./internal/client covers the decision table and runs a real
server in-process for adopt, follow, server-wins, client-wins, manual,
read-only restore, server-side rename and event-driven update. Also smoke
tested live: built both binaries, bootstrapped a server, logged in, created a
database and adopted a local file.
2026-10-29 16:01:35 +01:00

88 lines
2.5 KiB
Go

// Command keeporg is the KeepOrg desktop client.
//
// It mirrors every KDBX database assigned to you into one directory as real
// files, pushes each save to the server and applies what other clients push.
// Point KeePassXC, KeePass2 or any compatible application at those files.
package main
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"syscall"
"text/tabwriter"
"cc.tiredsysadmin.keeporg/internal/client"
)
// Version is stamped at build time from versions.mk.
var Version = "dev"
type command struct {
name string
help string
run func(ctx context.Context, args []string) error
}
func main() {
cmds := []command{
{"login", "sign in to a server and choose the mirror directory", cmdLogin},
{"logout", "revoke this device's session", cmdLogout},
{"run", "keep the mirror in sync until stopped", cmdRun},
{"sync", "reconcile once and exit", cmdSync},
{"status", "show each mirrored database", cmdStatus},
{"totp", "enrol a second factor for this account", cmdTOTP},
{"whoami", "show the signed-in account and its groups", cmdWhoami},
{"groups", "list groups you can see", cmdGroups},
{"group", "create or delete a group", cmdGroup},
{"member", "add or remove a group member", cmdMember},
{"db", "create, rename, re-strategy or delete a database", cmdDB},
{"grant", "give or take a group's access to a database", cmdGrant},
{"invite", "create or list invite links", cmdInvite},
{"users", "list accounts you administer", cmdUsers},
{"version", "print the version", nil},
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
if len(os.Args) < 2 {
usage(cmds)
os.Exit(2)
}
name := os.Args[1]
if name == "version" || name == "-version" || name == "--version" {
fmt.Println("keeporg", Version)
return
}
for _, c := range cmds {
if c.name != name {
continue
}
if err := c.run(ctx, os.Args[2:]); err != nil {
if errors.Is(err, context.Canceled) {
return
}
fmt.Fprintln(os.Stderr, "keeporg:", err)
os.Exit(1)
}
return
}
fmt.Fprintf(os.Stderr, "keeporg: unknown command %q\n\n", name)
usage(cmds)
os.Exit(2)
}
func usage(cmds []command) {
fmt.Fprintf(os.Stderr, "keeporg %s - KeePass database synchronization\n\nusage: keeporg <command> [flags]\n\n", Version)
w := tabwriter.NewWriter(os.Stderr, 0, 2, 2, ' ', 0)
for _, c := range cmds {
fmt.Fprintf(w, " %s\t%s\n", c.name, c.help)
}
w.Flush()
fmt.Fprintf(os.Stderr, "\nRun `keeporg <command> -h` for a command's flags.\n"+
"Configuration lives in %s\n", client.DefaultConfigPath())
}