docs: add user guide and release zip packaging
pipeline / test (push) Has been cancelled
pipeline / build (push) Has been cancelled

This commit is contained in:
Ein Anderssono
2026-06-15 00:06:27 +02:00
parent 2e73d01b40
commit 0a905758cc
3 changed files with 748 additions and 4 deletions
+8 -4
View File
@@ -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 ---"
+2
View File
@@ -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.
+738
View File
@@ -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
<asset-id> <filename>
```
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.