From 0a905758cc5f84afa14d686dda3bdc23df155b63 Mon Sep 17 00:00:00 2001 From: Ein Anderssono Date: Mon, 15 Jun 2026 00:06:27 +0200 Subject: [PATCH] docs: add user guide and release zip packaging --- Makefile | 12 +- README.md | 2 + USERGUIDE.md | 738 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 748 insertions(+), 4 deletions(-) create mode 100644 USERGUIDE.md diff --git a/Makefile b/Makefile index 2b41ae8..1839fc5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ BINARY := ./bin/photoscli MODULE := gitea.k3s.k0.nu/tools/photocli VERSION := 0.5.0 +RELEASE_ZIP := ./bin/photoscli-$(VERSION)-macos.zip BRIDGE_DIR := bridge LDFLAGS := -X main.version=$(VERSION) OBJ := $(BRIDGE_DIR)/photokit_bridge.o @@ -10,7 +11,7 @@ STUB_LIB := $(BRIDGE_DIR)/libphotokit_bridge_stub.a GITEA_HOST := gitea-1.tail82444.ts.net GITEA_REPO := tools/photocli -.PHONY: all build clean test coverage tag release pipeline +.PHONY: all build clean test coverage tag package release pipeline all: build @@ -43,14 +44,17 @@ coverage: $(STUB_LIB) go tool cover -func=coverage.out clean: - rm -f $(BINARY) $(OBJ) $(LIB) $(STUB_OBJ) $(STUB_LIB) coverage.out + rm -f $(BINARY) $(RELEASE_ZIP) $(OBJ) $(LIB) $(STUB_OBJ) $(STUB_LIB) coverage.out + +package: build + zip -j $(RELEASE_ZIP) $(BINARY) README.md USERGUIDE.md CHANGELOG.md tag: git tag v$(VERSION) git push origin v$(VERSION) -release: - tea releases create --repo $(GITEA_REPO) --tag v$(VERSION) --title "v$(VERSION)" --asset $(BINARY) +release: package + tea releases create --repo $(GITEA_REPO) --tag v$(VERSION) --title "v$(VERSION)" --asset $(BINARY) --asset USERGUIDE.md --asset $(RELEASE_ZIP) pipeline: clean test build @echo "--- verifying version ---" diff --git a/README.md b/README.md index bef72a7..d3577d2 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ The tool is designed for repeatable, resumable Photos backups rather than one-off drag-and-drop exports. +For a practical step-by-step manual with recommended backup workflows, recovery steps, automation examples, and troubleshooting, see `USERGUIDE.md`. + ## Highlights - List albums, photos, and the Photos folder/album tree. diff --git a/USERGUIDE.md b/USERGUIDE.md new file mode 100644 index 0000000..774843a --- /dev/null +++ b/USERGUIDE.md @@ -0,0 +1,738 @@ +# photoscli User Guide + +This guide explains how to use `photoscli` safely for Apple Photos exports and backups. It is written for day-to-day users who want reliable commands, clear recovery steps, and predictable backup behavior. + +For a compact command reference, see `README.md` or run: + +```bash +photoscli help +``` + +## What photoscli Is For + +`photoscli` exports assets from Apple Photos on macOS. + +It is especially useful when you want to: + +- Back up the whole Photos album tree to normal folders. +- Export one album for sharing or archiving. +- Resume a large export without starting over. +- Keep a manifest of already-exported assets. +- Retry transient iCloud failures. +- Verify that files referenced by a manifest exist on disk. +- Script Photos exports with stable exit codes. + +It is not intended to replace Apple Photos, iCloud Photos, or Time Machine. Think of it as an additional file-based export and backup tool. + +## Before You Start + +You need: + +- macOS. +- Apple Photos library available on the machine. +- Photos permission granted to the terminal app or binary. +- Enough disk space for the export. +- A built `photoscli` binary. + +Build it from the repo: + +```bash +make build +``` + +Run the binary directly: + +```bash +./bin/photoscli version +``` + +If you install or copy it somewhere else, replace `./bin/photoscli` in examples with `photoscli`. + +## First Run And Permissions + +Start with a harmless command: + +```bash +./bin/photoscli albums +``` + +macOS may ask for Photos access. Allow it. + +If access is denied, open: + +```text +System Settings > Privacy & Security > Photos +``` + +Then enable access for the terminal application you use, such as Terminal, iTerm, or another launcher. Depending on how you start the binary, macOS may associate the permission with the terminal app rather than the binary itself. + +Verify access: + +```bash +./bin/photoscli tree +``` + +## Recommended Backup Strategy + +For most users, the safest full-library backup command is: + +```bash +./bin/photoscli backup-all \ + --out /Volumes/BackupDrive/PhotosExport \ + --manifest sqlite \ + --log \ + --retry 3 \ + --concurrency 4 +``` + +This gives you: + +- Full album-tree export. +- SQLite manifest for fast resumable backups. +- Structured logs in the same database. +- Automatic retries for transient failures. +- Moderate concurrency that is usually safe for large libraries. + +Before running it for real, run a dry-run: + +```bash +./bin/photoscli backup-all \ + --out /Volumes/BackupDrive/PhotosExport \ + --manifest sqlite \ + --dry-run \ + --json +``` + +## Preview Export vs Original Export + +By default, `photoscli` exports optimized preview images. + +Preview export: + +- Produces smaller files. +- Uses `--size` and `--quality`. +- Is good for browsing, sharing, and lightweight archives. +- May not preserve original file bytes or all original metadata. + +Original export: + +- Uses `--originals`. +- Exports original asset resources where PhotoKit exposes them. +- Ignores `--size` and `--quality`. +- Uses more disk space. +- Is usually the better choice for archival backups. + +Preview example: + +```bash +./bin/photoscli export --album-id "Vacation" --out ./VacationPreview --size 2048 --quality 90 +``` + +Original example: + +```bash +./bin/photoscli export --album-id "Vacation" --out ./VacationOriginals --originals +``` + +## Export One Album + +List albums first: + +```bash +./bin/photoscli albums +``` + +Export by title: + +```bash +./bin/photoscli export --album-id "Vacation" --out ./Vacation +``` + +Export by PhotoKit local identifier: + +```bash +./bin/photoscli export --album-id "5E9F.../L0/001" --out ./Vacation +``` + +Use the local identifier when album names are duplicated or ambiguous. + +Recommended one-album archival export: + +```bash +./bin/photoscli export \ + --album-id "Vacation" \ + --out ./VacationOriginals \ + --originals \ + --manifest sqlite \ + --log \ + --retry 3 +``` + +## Back Up The Whole Library + +Use `backup-all` to export every album into a folder tree matching Photos folders and albums. + +```bash +./bin/photoscli backup-all --out ./PhotosBackup +``` + +Recommended full backup: + +```bash +./bin/photoscli backup-all \ + --out ./PhotosBackup \ + --manifest sqlite \ + --log \ + --retry 3 \ + --concurrency 4 +``` + +Recommended full original backup: + +```bash +./bin/photoscli backup-all \ + --out ./PhotosOriginals \ + --originals \ + --manifest sqlite \ + --log \ + --retry 3 \ + --concurrency 4 +``` + +If two sibling albums have the same name, `photoscli` adds the album ID to the generated folder name so the folders do not collide. + +## Dry Runs + +Dry-runs are strongly recommended before large exports. + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --dry-run +``` + +With JSON output: + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --dry-run --json +``` + +Dry-run mode does not write exported files, manifests, logs, or failure files. It prints what would be exported. + +Use dry-run when: + +- You are testing filters. +- You are checking output paths. +- You are preparing a large first-time backup. +- You are validating an exclude list. + +## Incremental Backups + +Incremental backups are enabled by manifests. + +Run the same backup command repeatedly: + +```bash +./bin/photoscli backup-all \ + --out ./PhotosBackup \ + --manifest sqlite \ + --log \ + --retry 3 +``` + +Already-exported assets are skipped based on the manifest. + +For new assets since a date: + +```bash +./bin/photoscli backup-all \ + --out ./PhotosBackup \ + --manifest sqlite \ + --since 2024-01-01 +``` + +The `--since` flag accepts: + +- `YYYY-MM-DD` +- RFC3339, for example `2024-01-01T00:00:00Z` + +## Manifests + +Manifests track exported asset IDs and allow safe resume behavior. + +JSONL manifest: + +```text +downloads.jsonl +``` + +SQLite manifest: + +```text +downloads.db +``` + +Use JSONL when: + +- You want a simple append-friendly text file. +- Your library is small or medium-sized. +- You want easy inspection with normal text tools. + +Use SQLite when: + +- Your library is large. +- You want faster lookup behavior. +- You want logs stored in the same database. +- You plan to run repeated backups over time. + +Recommended for serious backups: + +```bash +--manifest sqlite +``` + +Disable manifests only for temporary exports: + +```bash +./bin/photoscli export --album-id "Scratch" --out ./Scratch --no-manifest +``` + +Without a manifest, `photoscli` cannot use manifest-based resume behavior. + +## Logging + +Enable logs with: + +```bash +--log +``` + +For JSONL or no-manifest exports, logs go to: + +```text +export.log +``` + +For SQLite manifests, logs go to the `logs` table in: + +```text +downloads.db +``` + +Logged events include: + +- Session start. +- Session end. +- Export completed. +- Export skipped. +- Export failed. + +Use logs when: + +- You are running unattended backups. +- You need auditability. +- You want details about failed or skipped assets. + +## Failed Exports And Retries + +Failed exports are written to: + +```text +failures.jsonl +``` + +For transient failures, first use retries during normal export: + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --retry 3 +``` + +If failures remain, retry later: + +```bash +./bin/photoscli retry-failed --out ./PhotosBackup +``` + +Typical reasons for failures: + +- iCloud asset not currently downloadable. +- Network interruption. +- Photos permission issue. +- Destination disk unavailable. +- Destination disk full. +- Invalid or changing Photos library state. + +After retrying, run verification: + +```bash +./bin/photoscli verify --out ./PhotosBackup --manifest sqlite +``` + +## Verification + +Run verification after large backups: + +```bash +./bin/photoscli verify --out ./PhotosBackup --manifest sqlite +``` + +Verification checks that manifest entries point to files on disk. + +It exits with: + +- `0` when all checked files exist. +- `2` when files are missing. +- `1` for argument or runtime errors. + +You can also verify immediately after export: + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --manifest sqlite --verify +``` + +## Reporting + +Show manifest and failure counts: + +```bash +./bin/photoscli report --out ./PhotosBackup --manifest sqlite +``` + +Example output: + +```text +entries 12345 +failures 2 +``` + +Use this after long runs to quickly check whether work was recorded and whether failures occurred. + +## Diffing An Album Against A Backup + +Use `diff` to find album assets that are not in the manifest: + +```bash +./bin/photoscli diff --album-id "Vacation" --out ./PhotosBackup --manifest sqlite +``` + +Missing assets are printed as: + +```text + +``` + +Exit code is `2` if missing assets are found. + +## Filtering + +### Favorites Only + +```bash +./bin/photoscli export --album-id "Favorites" --out ./Favorites --only-favorites +``` + +### Media Type + +Photos only, the default: + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --media photos +``` + +Videos only: + +```bash +./bin/photoscli backup-all --out ./VideoBackup --media videos --include-videos +``` + +Everything: + +```bash +./bin/photoscli backup-all --out ./FullBackup --media all --include-videos +``` + +### Exclude Albums + +Exclude exact album names: + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --exclude-album "Recently Deleted" +``` + +Exclude by glob: + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --exclude-album "Temp*" +``` + +Use multiple exclusions: + +```bash +./bin/photoscli backup-all \ + --out ./PhotosBackup \ + --exclude-album "Recently Deleted" \ + --exclude-album "Temp*" \ + --exclude-album "Screenshots" +``` + +### Date Filter + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --since 2024-01-01 +``` + +### Estimated Size Filter + +`--min-size` and `--max-size` currently filter by estimated pixel count from dimensions, not encoded file size. + +```bash +./bin/photoscli export --album-id "Large" --out ./LargeOnly --min-size 12000000 +``` + +## Date-Based Folder Layouts + +Use `--date-template` to append date folders based on asset creation date. + +Example: + +```bash +./bin/photoscli export \ + --album-id "Vacation" \ + --out ./Vacation \ + --date-template YYYY/MM/DD +``` + +Possible output: + +```text +Vacation/ + 2024/ + 07/ + 15/ + 0000_....jpg +``` + +Supported tokens: + +- `YYYY` +- `MM` +- `DD` + +Assets without parseable creation dates stay in the base output path. + +## Configuration File + +You can store default values in: + +```text +~/.photoscli.toml +``` + +Example: + +```toml +size = 2048 +quality = 90 +concurrency = 4 +manifest = "sqlite" +sort = "newest" +media = "photos" +retry = 3 +log = true +``` + +Use a custom config path: + +```bash +PHOTOSCLI_CONFIG=./photoscli.toml ./bin/photoscli backup-all --out ./PhotosBackup +``` + +Command-line flags override config-file defaults. + +Recommended config for recurring backups: + +```toml +manifest = "sqlite" +log = true +retry = 3 +concurrency = 4 +sort = "oldest" +media = "photos" +quality = 90 +size = 2048 +``` + +Then run: + +```bash +./bin/photoscli backup-all --out ./PhotosBackup +``` + +## Automation Examples + +### Weekly Incremental Backup + +```bash +#!/bin/sh +set -eu + +OUT="/Volumes/BackupDrive/PhotosExport" +BIN="$HOME/bin/photoscli" + +"$BIN" backup-all \ + --out "$OUT" \ + --manifest sqlite \ + --log \ + --retry 3 \ + --concurrency 4 \ + --json + +"$BIN" verify --out "$OUT" --manifest sqlite +``` + +### Export Favorites For Sharing + +```bash +./bin/photoscli export \ + --album-id "Favorites" \ + --out ./FavoritesShare \ + --only-favorites \ + --size 2048 \ + --quality 88 \ + --no-manifest +``` + +### Archive Originals From One Album + +```bash +./bin/photoscli export \ + --album-id "Family Archive" \ + --out ./FamilyArchiveOriginals \ + --originals \ + --manifest sqlite \ + --log \ + --retry 3 +``` + +### Backup Everything Including Videos + +```bash +./bin/photoscli backup-all \ + --out ./FullPhotosBackup \ + --media all \ + --include-videos \ + --manifest sqlite \ + --log \ + --retry 3 +``` + +## Interpreting Exit Codes + +`photoscli` uses stable exit codes for scripts: + +- `0`: success. +- `1`: invalid arguments, runtime error, or all exports failed. +- `2`: partial failure, diff found missing manifest entries, or verify found missing files. +- `3`: Photos access denied. + +Example shell handling: + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --manifest sqlite --log +rc=$? + +case "$rc" in + 0) echo "backup succeeded" ;; + 2) echo "backup partially succeeded; inspect failures.jsonl" ;; + 3) echo "Photos access denied; check macOS privacy settings" ;; + *) echo "backup failed with exit code $rc" ;; +esac +``` + +## Troubleshooting + +### Access Denied + +Symptom: + +```text +error: access denied +``` + +Fix: + +- Open System Settings. +- Go to Privacy & Security > Photos. +- Enable access for your terminal app or launcher. +- Run `photoscli albums` again. + +### iCloud Assets Fail To Export + +Try: + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --retry 3 +``` + +If failures remain: + +```bash +./bin/photoscli retry-failed --out ./PhotosBackup +``` + +Also check: + +- Network connectivity. +- iCloud Photos status. +- Whether Photos.app can open/download the asset. + +### Destination Disk Is Full + +Free space, then rerun the same command. The manifest should skip already-exported assets and continue with remaining work. + +### Duplicate Album Names + +`backup-all` automatically appends the album ID to duplicate sibling album names. If you want exact control, export ambiguous albums individually using their PhotoKit local identifiers. + +### Too Slow + +Try a moderate concurrency increase: + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --concurrency 8 +``` + +If your library is heavily iCloud-backed, too much concurrency may increase transient failures. Use `--retry 3` and reduce concurrency if needed. + +### Too Much Output Or Hard To Parse + +Use JSON summary output: + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --json +``` + +Use structured logs: + +```bash +./bin/photoscli backup-all --out ./PhotosBackup --manifest sqlite --log +``` + +## Safe Operating Practices + +- Run `--dry-run` before the first large backup. +- Use `--manifest sqlite` for long-term backups. +- Use `--log` for unattended runs. +- Use `--retry 3` for iCloud-heavy libraries. +- Run `verify` after large runs. +- Keep the destination on a reliable disk. +- Do not use `--no-manifest` for backups you expect to resume. +- Prefer album local identifiers when names are duplicated. + +## Limitations + +- This is macOS-only. +- Real Photos access requires macOS privacy permission. +- `--format heic|png` is currently a validated CLI hint; true non-JPEG preview output still needs bridge support. +- `--min-size` and `--max-size` use estimated pixel count from dimensions, not encoded file size. +- Original export depends on asset resources exposed by PhotoKit. +- iCloud-backed assets may require network downloads and can fail for reasons outside the CLI's control. +- The manifest records exported asset IDs; it is not a cryptographic integrity database.