Skip to content

feat: add login and sync commands#1297

Open
grauschnabel wants to merge 24 commits intosimulot:developfrom
grauschnabel:feature/login-and-sync
Open

feat: add login and sync commands#1297
grauschnabel wants to merge 24 commits intosimulot:developfrom
grauschnabel:feature/login-and-sync

Conversation

@grauschnabel
Copy link
Copy Markdown

@grauschnabel grauschnabel commented Feb 1, 2026

Summary

  • immich-go login: Interactive credential storage to ~/.config/immich-go/config.yaml with server validation and API key verification
  • immich-go sync down: Download server assets to local directory with checksum-based state tracking, atomic writes, and YYYY/YYYY-MM/ directory structure
  • immich-go sync up: Upload local assets to server with robust dedup (SHA1 → name+date±5s+size matching via shared assetmatch package)
  • Config layering: Global config → local config → CLI flags, with flat-key fallback so server/api-key from global config work for all subcommands
  • Delete safeguards: Only tracked assets deleted, confirmation for >10 deletions, --force to bypass
  • Graceful Ctrl+C: Saves state and releases lock on interrupt, resumable on next run
  • Dry-run: No state written, all actions logged to stdout
  • Documentation: New docs/commands/login.md and docs/commands/sync.md, updated README, command reference, and configuration docs

Test plan

  • go build ./... passes
  • go test -race ./internal/syncstate/... — 10 tests pass (incl. CaptureDate migration + roundtrip)
  • go test -race ./internal/assetmatch/... — 13 tests pass (dedup decision matrix)
  • golangci-lint run — 0 new issues
  • Manual: immich-go login → credentials saved and reused by subsequent commands
  • Manual: immich-go sync down -d /tmp/test --dry-run → shows planned downloads on stdout
  • Manual: immich-go sync down -d /path → downloads assets, creates state
  • Manual: Ctrl+C during sync → state saved, lock released, resumes on next run
  • Manual: immich-go sync up -d /path
  • Manual: immich-go sync down --delete --dry-run

🤖 Generated with Claude Code

simulot and others added 23 commits November 22, 2025 22:28
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](actions/setup-go@v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](actions/setup-node@v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@v7...v8)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
…s/actions/setup-go-6

chore(deps): bump actions/setup-go from 5 to 6
…s/actions/upload-artifact-5

chore(deps): bump actions/upload-artifact from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
…s/actions/checkout-6

chore(deps): bump actions/checkout from 4 to 6
…s/actions/setup-node-6

chore(deps): bump actions/setup-node from 4 to 6
…s/actions/github-script-8

chore(deps): bump actions/github-script from 7 to 8
updated flags to match program
- `immich-go login`: interactive credential storage to ~/.config/immich-go/config.yaml
- `immich-go sync down`: download server assets to local directory with state tracking
- `immich-go sync up`: upload local assets to server with checksum-based dedup
- Config layering: global config → local config → CLI flags with flat-key fallback
- Sync state: atomic writes, lock files, server/user validation, batch saves
- Delete safeguards: only tracked assets, confirmation for >10 deletions
- Graceful Ctrl+C: saves state and releases lock on interrupt
- Dry-run: no state written, all actions logged to stdout

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- New docs/commands/login.md and docs/commands/sync.md
- Updated command reference, docs hub, readme, and configuration docs
- Fix: Ctrl+C no longer shows "context canceled" error and usage text

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@grauschnabel grauschnabel requested a review from simulot as a code owner February 1, 2026 23:09
@simulot
Copy link
Copy Markdown
Owner

simulot commented Feb 7, 2026

Thanks for the PR and for contributing to immich-go.

To help me better understand the context of these changes, could you please provide a brief overview of your motivation and the specific use case this addresses?

In the meantime, please consider the following technical requirements for this PR:

Branching: All PRs should be targeted against the develop branch rather than main.

End-to-End (e2e) Testing: While unit tests are necessary, e2e tests are welcomed to validate logic against a live test server. Please add new test cases in ./internal/e2e/client.

Edge Cases: Ensure you account for scenarios where Immich handles multiple assets with identical filenames and capture dates.

Cross-Platform Validation: These tests run on both Linux and Windows runners to catch OS-specific bugs, such as improper usage of path vs. filepath.

Once these items are addressed, I'll be happy to do a deeper dive into the code. Looking forward to your updates!

@grauschnabel grauschnabel changed the base branch from main to develop February 7, 2026 20:46
@grauschnabel
Copy link
Copy Markdown
Author

Motivation & Use Cases

Why login + sync?

login solves a friction point: every immich-go invocation currently requires --server and --api-key flags. For users who run sync regularly (cron jobs, backup scripts), this is tedious. login stores credentials in ~/.config/immich-go/config.yaml with server validation and API key verification, so subsequent commands just work.

sync down enables offline backups: download your entire Immich library to a local directory with YYYY/YYYY-MM/filename organization. State tracking (.immich-sync/state.json) ensures idempotent operation — running it twice only downloads new assets.

sync up enables bidirectional workflows: edit photos locally (Lightroom, etc.), then push changes back to the server. Uses the same robust dedup logic as the upload command (SHA1 → filename+date±5s+size matching).

Changes in this update (addressing review feedback)

  1. Shared dedup logic (internal/assetmatch/): Extracted the matching algorithm from app/upload/advice.go into a reusable package. Both upload and sync up now share the same AdviceCode types and CompareDate() function, ensuring consistent dedup behavior.

  2. Richer sync up matching: Previously sync up only checked SHA1. Now it uses the full 2-phase algorithm (checksum → name+date+size), correctly handling cases like re-encoded photos with different checksums.

  3. CaptureDate in sync state: AssetEntry now stores capture_date for smarter matching across sync sessions. Backward compatible with omitzero.

  4. Dry-run + delete fix (previous commit): confirmDeletion() skipped in dry-run, summary messages show [dry-run] prefix.

  5. E2E tests (internal/e2e/client/sync_test.go): 4 test cases covering sync up/down basics and idempotency.

  6. PR base → develop per project branching conventions.

- Extract asset matching algorithm into internal/assetmatch package
  with shared AdviceCode types, CompareDate(), and Index for both
  upload and sync commands
- Upgrade sync up from SHA1-only to full 2-phase matching
  (checksum → name+date±5s+size), handling SmallerOnServer case
- Add CaptureDate to syncstate.AssetEntry (omitzero for compat)
- Refactor upload advice.go to import shared types from assetmatch
- Add E2E tests for sync up/down (basic + idempotency)
- Add unit tests for assetmatch decision matrix and syncstate migration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants