diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 5f3078b..eeb6749 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -613,11 +613,51 @@ The rotation process: Plaintext entries are untouched. -### Planned: Multi-Repo + Per-Machine Inclusion (Phase 5) +### Per-Machine Targeting (Phase 5) -Support for multiple repos on a single server, and per-machine -inclusion rules (e.g., "this file only applies to Linux machines" or -"this directory is only for the workstation"). Design TBD. +Entries can be targeted to specific machines using `only` and `never` +labels. A machine's identity is a set of labels computed at runtime: + +- **Short hostname:** `vade` (before the first dot, lowercased) +- **OS:** `os:linux`, `os:darwin`, `os:windows` (from `runtime.GOOS`) +- **Architecture:** `arch:amd64`, `arch:arm64` (from `runtime.GOARCH`) +- **Tags:** `tag:work`, `tag:server` (from `/tags`, local-only) + +**Manifest fields on Entry:** + +```yaml +files: + - path: ~/.bashrc.linux + only: [os:linux] # restore/checkpoint only on Linux + ... + - path: ~/.ssh/work-config + only: [tag:work] # only on machines tagged "work" + ... + - path: ~/.config/heavy + never: [arch:arm64] # everywhere except ARM + ... + - path: ~/.special + only: [vade] # only on host "vade" + ... +``` + +**Matching rules:** +- `only` set → entry applies if *any* label matches the machine +- `never` set → entry excluded if *any* label matches +- Both set → error (mutually exclusive) +- Neither set → applies everywhere (current behavior) + +**Operations affected:** +- `restore` — skip non-matching entries +- `checkpoint` — skip non-matching entries (don't clobber stored version) +- `status` — report non-matching entries as `skipped` +- `add`, `list`, `verify`, `diff` — operate on all entries regardless + +**Tags file:** `/tags`, one tag per line, not synced. Each +machine defines its own tags. `sgard init` adds `tags` to `.gitignore`. + +**Label format:** bare string = hostname, `prefix:value` = typed matcher. +The `tag:` prefix in `only`/`never` maps to bare names in the tags file. ### Future: Manifest Signing (Phase 6) diff --git a/PROGRESS.md b/PROGRESS.md index e0f643a..de4c9ec 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -42,7 +42,7 @@ ARCHITECTURE.md for design details. ## Up Next -Phase 5: Multi-Repo + Per-Machine Inclusion (to be planned). +Phase 5: Per-Machine Targeting (Steps 28–32). Ready for Step 28. ## Known Issues / Decisions Deferred @@ -91,3 +91,4 @@ Phase 5: Multi-Repo + Per-Machine Inclusion (to be planned). | 2026-03-24 | 25 | Real FIDO2: go-libfido2 bindings, build tag gating, CLI wiring, nix sgard-fido2 package. | | 2026-03-24 | 26 | Test cleanup: tightened lint, 3 combo tests (encrypted+locked, dir-only+locked, toggle), stale doc fixes. | | 2026-03-24 | 27 | Phase 4 polish: e2e test (TLS+encryption+locked+push/pull), final doc review. Phase 4 complete. | +| 2026-03-24 | — | Phase 5 planned (Steps 28–32): machine identity, targeting, tags, proto update, polish. | diff --git a/PROJECT_PLAN.md b/PROJECT_PLAN.md index d599553..3f8dd9d 100644 --- a/PROJECT_PLAN.md +++ b/PROJECT_PLAN.md @@ -276,9 +276,45 @@ Depends on Steps 17, 18. - [x] E2e test: integration/phase4_test.go covering TLS + encryption + locked files + push/pull - [x] Verify: all tests pass, lint clean, both binaries compile -## Phase 5: Multi-Repo + Per-Machine Inclusion +## Phase 5: Per-Machine Targeting -(To be planned) +### Step 28: Machine Identity + Targeting Core + +- [ ] `manifest/manifest.go`: add `Only []string` and `Never []string` to Entry (yaml tags `only,omitempty` / `never,omitempty`) +- [ ] `garden/identity.go`: `Identity(repoRoot string) []string` — returns machine's label set: short hostname, `os:`, `arch:`, `tag:` from `/tags` +- [ ] `garden/targeting.go`: `EntryApplies(entry, labels) bool` — match logic: `only` → any match, `never` → no match, error if both set +- [ ] `garden/tags.go`: `LoadTags(repoRoot)`, `SaveTag(repoRoot, tag)`, `RemoveTag(repoRoot, tag)` — read/write `/tags` file +- [ ] `garden/garden.go`: `Init` appends `tags` to `.gitignore` +- [ ] Tests: identity computation, tag load/save, matching (only, never, both-error, bare hostname, os:, arch:, tag:, neither) + +### Step 29: Operations Respect Targeting + +- [ ] `garden/garden.go`: `Checkpoint` skips entries where `!EntryApplies` +- [ ] `garden/garden.go`: `Restore` skips entries where `!EntryApplies` +- [ ] `garden/garden.go`: `Status` reports `skipped` for non-matching entries (or omits them — TBD) +- [ ] `garden/garden.go`: `Add` accepts `Only`/`Never` in `AddOptions` +- [ ] Tests: checkpoint/restore/status skip non-matching, add with targeting + +### Step 30: Targeting CLI Commands + +- [ ] `cmd/sgard/tag.go`: `sgard tag add `, `sgard tag remove `, `sgard tag list` +- [ ] `cmd/sgard/identity.go`: `sgard identity` — print full label set +- [ ] `cmd/sgard/add.go`: `--only` and `--never` flags (comma-separated or repeated) +- [ ] `cmd/sgard/target.go`: `sgard target --only `, `--never `, `--clear` +- [ ] Tests: tag file CRUD, identity output + +### Step 31: Proto + Sync Update + +- [ ] `proto/sgard/v1/sgard.proto`: add `repeated string only` and `repeated string never` to ManifestEntry +- [ ] `server/convert.go`: update proto ↔ manifest conversion +- [ ] Regenerate proto: `make proto` +- [ ] Tests: round-trip conversion with targeting fields + +### Step 32: Phase 5 Polish + +- [ ] Update ARCHITECTURE.md, README.md, CLAUDE.md, PROGRESS.md +- [ ] E2e test: add entries with targeting, push/pull, restore on different identity +- [ ] Verify: all tests pass, lint clean, both binaries compile ## Phase 6: Manifest Signing