Commit Graph

50 Commits

Author SHA1 Message Date
5bb65795c8 Step 18: FIDO2 support with interface and mock.
FIDO2Device interface abstracts hardware interaction (Register, Derive,
Available, MatchesCredential). Real libfido2 implementation deferred;
mock device used for full test coverage.

AddFIDO2Slot: registers FIDO2 credential, derives KEK via HMAC-secret,
wraps DEK, adds fido2/<label> slot to manifest.

UnlockDEK: tries all fido2/* slots first (checks credential_id against
connected device), falls back to passphrase. User never specifies
which method.

6 tests: add slot, reject duplicate, unlock via FIDO2, fallback to
passphrase when device unavailable, slot persistence, encrypted
round-trip unlocked via FIDO2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 09:15:20 -07:00
3b961b5d8a Step 17: Encryption core — passphrase-only, selective per-file.
Manifest schema: Entry gains Encrypted, PlaintextHash fields.
Manifest gains Encryption section with KekSlots map (passphrase slot
with Argon2id params, salt, and wrapped DEK as base64).

garden/encrypt.go: EncryptInit (generate DEK, wrap with passphrase KEK),
UnlockDEK (derive KEK, unwrap), encryptBlob/decryptBlob using
XChaCha20-Poly1305 with random 24-byte nonces.

Modified operations:
- Add: optional encrypt flag, stores encrypted blob + plaintext_hash
- Checkpoint: detects changes via plaintext_hash, re-encrypts
- Restore: decrypts encrypted blobs before writing
- Diff: decrypts stored blob before comparing
- Status: compares against plaintext_hash for encrypted entries

10 tests covering init, persistence, unlock, add-encrypted, restore
round-trip, checkpoint, status, diff, requires-DEK guard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 08:50:53 -07:00
582f2116d2 Change sgardd default repo path to /srv/sgard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 08:40:55 -07:00
529d45f8eb Add Phase 3 encryption plan (Steps 17-20) and update progress.
Step 17: Encryption core — Argon2id KEK, XChaCha20 DEK wrapping,
  selective per-file encryption, manifest schema changes.
Step 18: FIDO2 support — hmac-secret slots, credential_id matching,
  automatic unlock resolution.
Step 19: CLI + slot management — encrypt init/add-fido2/remove-slot/
  list-slots/change-passphrase, proto updates.
Step 20: Polish — e2e encrypted push/pull test, doc updates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 08:35:29 -07:00
f6bdb93066 KEK slots: named map with passphrase + fido2/<label> convention.
Slots are a map keyed by user-chosen label. One passphrase slot
(universal fallback), zero or more fido2/<label> slots (default to
hostname, overridable via --label).

FIDO2 slots carry credential_id to match connected devices without
prompting for touch. Unlock tries all fido2/* slots first, falls
back to passphrase.

CLI: add-fido2 [--label], remove-slot, list-slots, change-passphrase.
New FIDO2 slots propagate to server on next push.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 08:32:55 -07:00
e24b66776c Encryption config lives in the manifest, syncs with push/pull.
Wrapped DEKs and salts stored inline as base64 in the manifest's
encryption section. No separate files (encryption.yaml, salt files,
dek.enc.*) — the manifest is fully self-contained.

Pulling to a new machine gives you everything needed to decrypt.
Server never has the DEK. FIDO2 cross-machine note: device-bound
hmac-secret requires add-fido2 on each machine; passphrase fallback
enables cross-machine decryption.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 08:23:25 -07:00
079b235c9d Refine encryption: FIDO2 preferred with passphrase fallback.
Automatic unlock resolution: try FIDO2 first (no typing, just touch),
fall back to passphrase if device not present. User never specifies
which method — sgard reads encryption.yaml and walks sources in order.

encrypt init --fido2 creates both sources (FIDO2 primary + passphrase
fallback) to prevent lockout on FIDO2 key loss. Separate salt files
per KEK source.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 08:18:51 -07:00
4d9e156eea Update encryption design: selective per-file encryption, punt signing.
Encryption is per-file (--encrypt flag on add), not per-repo. A repo
can have a mix of encrypted and plaintext blobs. Commands that only
touch plaintext entries never prompt for the DEK.

Manifest signing deferred — the trust model (which key signs, how do
pulling clients verify across multiple machines) needs proper design.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 08:06:27 -07:00
c6b92a70b1 Document encryption design in ARCHITECTURE.md.
Two-layer key hierarchy: DEK (random, encrypts blobs) wrapped by
KEK (derived from passphrase via Argon2id or FIDO2 hmac-secret).

XChaCha20-Poly1305 for both blob encryption and DEK wrapping.
Post-encryption hashing (manifest hash = SHA-256 of ciphertext).
Plaintext hash stored separately for efficient status checks.
Multiple KEK sources per repo. Server never sees the DEK.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:36:44 -07:00
edef642025 Implement JWT token auth with transparent auto-renewal.
Replace per-call SSH signing with a two-layer auth system:

Server: AuthInterceptor verifies JWT tokens (HMAC-SHA256 signed with
repo-local jwt.key). Authenticate RPC accepts SSH-signed challenges
and issues 30-day JWTs. Expired-but-valid tokens return a
ReauthChallenge in error details (server-provided nonce for fast
re-auth). Authenticate RPC is exempt from token requirement.

Client: TokenCredentials replaces SSHCredentials as the primary
PerRPCCredentials. NewWithAuth creates clients with auto-renewal —
EnsureAuth obtains initial token, retryOnAuth catches Unauthenticated
errors and re-authenticates transparently. Token cached at
$XDG_STATE_HOME/sgard/token (0600).

CLI: dialRemote() helper handles token loading, connection setup,
and initial auth. Push/pull/prune commands simplified to use it.

Proto: Added Authenticate RPC, AuthenticateRequest/Response,
ReauthChallenge messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 00:52:16 -07:00
b7b1b27064 Refine auth flow: server-provided reauth challenge for expired tokens.
Two rejection paths: expired-but-valid tokens get a ReauthChallenge
with a server-generated nonce (fast path, saves a round trip).
Invalid/corrupted tokens get plain Unauthenticated (client falls back
to full self-generated auth flow).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 00:40:26 -07:00
66af104155 Document JWT token auth design in ARCHITECTURE.md.
Two-layer auth: SSH key signing to obtain a 30-day JWT, then
token-based auth for all subsequent requests. Auto-renewal is
transparent — client interceptor catches Unauthenticated, re-signs,
caches new token, retries. Server is stateless (JWT signed with
repo-local secret key). Token cached at $XDG_STATE_HOME/sgard/token.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 00:36:58 -07:00
92d64d5540 Fix doc inconsistencies between README and ARCHITECTURE.
- ARCHITECTURE.md: move mirror/prune to local command table, fix
  remove description (prune cleans blobs, not checkpoint), fix
  Phase 2 section to only list remote commands
- README.md: add --force to mirror down, fix prune --remote
  description, build instructions include both binaries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 00:26:35 -07:00
5f1bc4e14c Step 16: Polish — docs, flake, goreleaser, e2e test.
Phase 2 complete.

ARCHITECTURE.md: full rewrite covering gRPC protocol, SSH auth,
updated package structure, all Garden methods, design decisions.
README.md: add remote sync section, mirror/prune commands, sgardd usage.
CLAUDE.md: add gRPC/proto/x-crypto deps, server/client/sgardpb packages.
flake.nix: build both sgard + sgardd, updated vendorHash.
goreleaser: add sgardd build target.
E2e test: full push/pull cycle with SSH auth between two clients.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 00:10:04 -07:00
94963bb8d6 Step 15: CLI wiring, prune, and sgardd daemon.
Local prune: garden.Prune() removes orphaned blobs. 2 tests.

CLI commands: sgard push, sgard pull (with SSH auth via --ssh-key
or ssh-agent), sgard prune (local by default, remote with --remote).

Server daemon: cmd/sgardd with --listen, --repo, --authorized-keys
flags. Runs gRPC server with optional SSH key auth interceptor.

Root command gains --remote and --ssh-key persistent flags with
resolveRemote() (flag > env > repo/remote file).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 00:03:51 -07:00
4b841cdd82 Step 14: SSH key auth for gRPC.
Server: AuthInterceptor parses authorized_keys, extracts SSH signature
from gRPC metadata (nonce + timestamp signed by client's SSH key),
verifies against authorized public keys with 5-minute timestamp window.

Client: SSHCredentials implements PerRPCCredentials, signs nonce+timestamp
per request. LoadSigner resolves key from flag, ssh-agent, or default paths.

8 tests: valid auth, reject unauthenticated, reject unauthorized key,
reject expired timestamp, metadata generation, plus 2 integration tests
(authenticated succeeds, unauthenticated rejected).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:58:09 -07:00
525c3f0b4f Step 13: Client library with Push, Pull, and Prune.
Client orchestrates the two-step push/pull protocol: manifest exchange
followed by chunked blob streaming. Push detects server-newer (returns
ErrServerNewer) and up-to-date states. Pull computes missing blobs
locally and streams only what's needed. Prune delegates to server RPC.

6 integration tests via in-process bufconn server.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:53:03 -07:00
0078b6b0f4 Steps 12 & 12b: gRPC server and directory recursion + mirror.
Step 12: GardenSync gRPC server with 5 RPC handlers — PushManifest
(timestamp comparison, missing blob detection), PushBlobs (chunked
streaming, manifest replacement), PullManifest, PullBlobs, Prune.
Added store.List() and garden.ListBlobs()/DeleteBlob() for prune.
In-process tests via bufconn.

Step 12b: Add now recurses directories (walks files/symlinks, skips
dir entries). Mirror up syncs filesystem → manifest (add new, remove
deleted, rehash changed). Mirror down syncs manifest → filesystem
(restore + delete untracked with optional confirm). 7 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:48:04 -07:00
19217ec216 Merge branch 'worktree-agent-a0166844'
# Conflicts:
#	garden/garden.go
2026-03-23 23:44:30 -07:00
b4bfce1291 Add directory recursion for Add and mirror up/down commands.
Add now recursively walks directories instead of creating a single
"directory" type entry. Extract addEntry helper for reuse. Implement
MirrorUp (sync filesystem state into manifest) and MirrorDown (sync
manifest state to filesystem with untracked file cleanup). Add CLI
mirror command with up/down subcommands.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:42:58 -07:00
153cc9c203 Add Step 12b: directory recursion and mirror command.
Add recurses directories. mirror up syncs filesystem -> manifest,
mirror down syncs manifest -> filesystem (exact restore with cleanup).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:34:25 -07:00
ebf55bb570 Steps 10 & 11: Garden accessors and proto-manifest conversion.
Step 10: GetManifest, BlobExists, ReadBlob, WriteBlob, ReplaceManifest
accessor methods on Garden. 5 tests.

Step 11: ManifestToProto/ProtoToManifest conversion functions in
server package with time.Time <-> timestamppb handling. Round-trip
test covering all 3 entry types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:25:07 -07:00
34330a35ef Add Garden accessor methods for manifest and blob store access.
Expose GetManifest, BlobExists, ReadBlob, WriteBlob, and
ReplaceManifest on *Garden to support future gRPC and higher-level
operations without breaking encapsulation. Includes 5 unit tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:23:46 -07:00
0113703908 Step 9: Proto definitions and gRPC code generation.
Define GardenSync service with 5 RPCs: PushManifest, PushBlobs,
PullManifest, PullBlobs, Prune. Messages for manifest, entries,
blob chunks (64 KiB streaming), and push/pull protocol flow.

Generated Go code in sgardpb/. Added Makefile proto target, gRPC +
protobuf + x/crypto deps, protoc tools to flake devShell.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:12:10 -07:00
b1313c1048 Update docs for v1.0.0.
ARCHITECTURE.md: update package structure to reflect actual file layout
(per-operation files, version command, flake, goreleaser), fix Garden
struct (clock field, Restore confirm callback, List method), add
.gitignore to repo layout, remove stale C++ note.

README.md: add NixOS installation instructions.
CLAUDE.md: add nix build command.
PROGRESS.md: add post-Step-8 release work to change log.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v1.0.0
2026-03-23 22:38:53 -07:00
8953090718 Add Nix flake for building and installing on NixOS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v0.1.2
2026-03-23 22:32:10 -07:00
caf1698c16 Create .gitignore in sgard init to exclude blobs/ from git.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 22:26:34 -07:00
db9aa7bbff Add README, fix Node.js 24 deprecation in release workflow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 22:21:34 -07:00
e7e353daec Add goreleaser config and version command for v0.1.0.
GoReleaser builds static binaries for linux/darwin on amd64/arm64,
injecting version via ldflags. Version command prints the embedded
version (defaults to "dev" for local builds).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v0.1.0
2026-03-23 22:14:38 -07:00
6dc84598de Fix release workflow for sgard homebrew formula.
Update formula references from cert to sgard, add needs: goreleaser
dependency, and pass tag-name to the bump action.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 22:11:06 -07:00
d1a6e533a4 Step 8: Polish — lint, clock abstraction, e2e test.
- golangci-lint config with errcheck, govet, staticcheck, errorlint
- Fix all lint issues (unchecked error returns in cleanup paths, De Morgan)
- Inject jonboulle/clockwork into Garden for deterministic timestamps
- Add manifest.NewWithTime() for clock-aware initialization
- E2e lifecycle test: init → add → checkpoint → modify → status → restore → verify
- Update CLAUDE.md, PROJECT_PLAN.md, PROGRESS.md

Phase 1 (local) is now complete. All 9 CLI commands implemented and tested.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 22:03:53 -07:00
08e24b44e0 Step 7: Remaining commands — remove, verify, list, diff.
Remove: untrack files, remove manifest entries, save. 2 tests.
Verify: check blobs against manifest hashes, report ok/mismatch/missing. 3 tests.
List: return all tracked entries, CLI formats by type. 2 tests.
Diff: compare stored blob vs current file, simple line diff. 3 tests.

Each command in its own file (garden/<cmd>.go) for parallel development.
Remove, verify, list implemented by parallel worktree agents; diff manual.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:55:37 -07:00
d03378c9c1 Merge branch 'worktree-agent-a6bd332c' 2026-03-23 21:55:00 -07:00
1cd4d2a198 Merge branch 'worktree-agent-a79f3e32' 2026-03-23 21:55:00 -07:00
34f9598a19 Add list command to display all tracked entries
Adds Garden.List() method that returns manifest entries, unit tests
for empty and populated repos, and a CLI command that formats output
by entry type (file with hash prefix, link with target, directory).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:51:01 -07:00
1471cf0ebc Add verify command to check blob store integrity.
Verify iterates manifest file entries, confirms each blob exists in the
store, and re-hashes the content to detect corruption. Includes unit
tests for the ok, hash-mismatch, and blob-missing cases, plus a thin
CLI wrapper that exits non-zero on any failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:50:17 -07:00
4da1574949 Step 7: Add remove command to stop tracking files.
Implements Garden.Remove() which unregisters paths from the manifest,
plus unit tests and the CLI wiring via cobra.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:49:57 -07:00
0d53ca34aa Note clock abstraction as Step 8 polish item.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:45:20 -07:00
c552a3657f Step 6: Restore with timestamp logic and confirm callback.
Restore writes tracked files back to their original locations.
Supports selective path restoration, force mode, and a confirm
callback for files where the on-disk mtime >= manifest timestamp
(truncated to seconds for cross-platform reliability). Creates
parent directories, recreates symlinks, and sets file permissions.

CLI: sgard restore [path...] [--force].
6 new tests (file, permissions, symlink, parent dirs, selective, confirm skip).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:41:53 -07:00
661c050d83 Step 5: Checkpoint and Status.
Checkpoint re-hashes all tracked files, stores changed blobs, and
updates per-file timestamps only when content changes. Missing files
are skipped gracefully. Status compares each tracked entry against
the filesystem and reports ok/modified/missing.

CLI: sgard checkpoint [-m message], sgard status.
4 new tests (changed file, unchanged file, missing file, status).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:36:55 -07:00
1550bdf940 Step 4: Garden core with Init, Open, Add and CLI commands.
Garden package ties manifest and store together. Supports adding
files (hashed and stored as blobs), directories (manifest-only),
and symlinks (target recorded). Paths under $HOME are stored as
~/... in the manifest for portability. CLI init and add commands
wired up via cobra.

8 tests covering init, open, add for all three entry types,
duplicate rejection, HashFile, and tilde path expansion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:34:55 -07:00
87db4b912f Steps 2 & 3 complete: manifest and store packages.
Manifest package (5 tests): Manifest/Entry structs with YAML tags,
New(), Load(), Save() with atomic write.

Store package (11 tests): content-addressable blob store keyed by
SHA-256, Write/Read/Exists/Delete with atomic writes, two-level
directory layout, hash validation.

Both implemented in parallel worktrees and merged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:26:47 -07:00
4d53fb584e Merge branch 'store-package'
# Conflicts:
#	go.mod
2026-03-23 21:26:04 -07:00
ab57f6d01d Add content-addressable blob store package.
Implement the store package with SHA-256 keyed blob storage using a
two-level directory layout (blobs/XX/YY/hash). Supports atomic writes
via temp file + rename, deduplication, and hash validation. Includes
comprehensive tests for round-trip, deduplication, existence checks,
deletion, and subdirectory creation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:25:06 -07:00
7a3d78d741 Step 2: Add manifest package with YAML data model and persistence.
Implements Manifest and Entry structs with YAML tags, New() constructor,
Load(path) for reading, and Save(path) with atomic write (temp file +
rename). Tests cover round-trip serialization, atomic save cleanup,
entry type invariants, nonexistent file error, and empty manifest.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:24:56 -07:00
6cadda01a8 Step 1: Replace C++ prototype with Go project scaffolding.
Remove all old C++ source files, proto definitions, CMake build,
clang configs, IDE files, and Trunk linter config. Initialize Go
module (github.com/kisom/sgard) with cobra and yaml.v3 deps.
Set up cobra root command with --repo persistent flag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:19:37 -07:00
b678ce572a Add design docs for Go rewrite.
ARCHITECTURE.md: tech stack (Go, YAML, cobra), repo layout, manifest
schema, content-addressable blob store, CLI commands, package structure.
PROJECT_PLAN.md: 8-step implementation sequence with parallelism notes.
PROGRESS.md: status tracker for resumable implementation.
CLAUDE.md: project guidance for Claude Code, references design docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:16:42 -07:00
db09939d38 scsl flag -> flags. 2023-10-24 22:02:19 -07:00
79f30684de Basic proto file start. 2023-10-24 22:01:15 -07:00
d9f779f071 initial import 2023-10-16 21:57:57 -07:00