docs/v0.20-migration.md
Rama main · réplica de lectura del repositorio original.
# v0.20 migration guide
v0.20 is kei's breaking config cleanup release.
Use the tagged Docker `:latest` image or pin `:0.20.0`.
The rule is simple:
- TOML is for settings you want to keep.
- CLI flags are for one run or one action.
- Env vars are for secrets, config path/bootstrap, process-manager glue, and tests.
Use [`example.config.toml`](../example.config.toml) as the full config
dictionary. Each option block lists the option name, default value, valid
values, and description. The wiki
[Configuration](https://github.com/rhoopr/kei/wiki/Configuration) page has the
same searchable option index.
## Quick migration
1. Run `kei config show` on your current setup, if it still starts.
2. Copy the durable settings into `~/.config/kei/config.toml`.
3. Replace old env-as-config values in Docker or systemd with mounted TOML.
4. Keep only secrets and runtime glue in env.
Minimal config:
```toml
[auth]
username = "you@example.com"
[download]
directory = "/photos"
```
Then run:
```sh
kei sync --config ~/.config/kei/config.toml
```
For a guided file, run:
```sh
kei config setup
```
The setup wizard defaults to all visible iCloud Photos libraries. When that
would mix primary and shared-library unfiled files, it writes a `{library}` path
segment for the unfiled template.
### Manual copy from old `icloudpd-rs` paths
Current kei releases don't auto-copy the old `icloudpd-rs` config, cookies, or
state DB at startup. If you still have files in those paths, copy only the
ones you need before the first run:
```sh
mkdir -p ~/.config/kei/cookies
cp ~/.config/icloudpd-rs/config.toml ~/.config/kei/config.toml
cp ~/.icloudpd-rs/* ~/.config/kei/cookies/
```
Run only the `cp` commands whose source paths exist. Leave the old files in
place until you've confirmed kei starts and can read the copied state.
## Upgrade failure hints
v0.20 removed durable sync flags and env mirrors instead of accepting them with
warnings. If an old command fails with `unexpected argument '--download-dir'` or
another removed sync flag, move that setting into `config.toml`.
If sync or import reports a missing `[download].directory` while old `KEI_*`
sync env vars are still set, the error lists the ignored env names. Those env
vars no longer configure sync. Keep env vars for secrets, config discovery, and
runtime glue.
Unknown TOML keys now include a v0.20 migration hint. This is usually an old
pre-v0.20 key such as `[filters].album`, `[filters].library`,
`[download].threads_num`, or `[metrics].port`.
## What changed
### Account and storage
| Old | New |
|---|---|
| `--username`, `ICLOUD_USERNAME` as durable config | `[auth].username`; keep `ICLOUD_USERNAME` for automation |
| `--domain` | `[auth].domain` |
| `--data-dir`, `KEI_DATA_DIR` as durable config | top-level `data_dir`; Docker still uses `KEI_DATA_DIR=/config` as runtime glue |
| `--download-dir`, `KEI_DOWNLOAD_DIR` | `[download].directory` |
| `--folder-structure` | `[download].folder_structure` |
| `--folder-structure-albums` | `[download].folder_structure_albums` |
| `--folder-structure-smart-folders` | `[download].folder_structure_smart_folders` |
### Selection and filters
| Old | New |
|---|---|
| `--album` | `[filters].albums = ["Name"]` |
| `--smart-folder` | `[filters].smart_folders = ["Favorites"]` |
| sync `--library` | `[filters].libraries = ["primary"]` |
| `--unfiled false` | `[filters].unfiled = false` |
| `--filename-exclude "*.AAE"` | `[filters].filename_exclude = ["*.AAE"]` |
| `--skip-videos`, `--skip-photos` | `[filters].media` |
| `--exclude-album Name`, `KEI_EXCLUDE_ALBUM` | `[filters].albums = ["!Name"]` |
| `[filters].album` | `[filters].albums` |
| `[filters].exclude_albums` | `[filters].albums = ["!Name"]` |
| `[filters].library` | `[filters].libraries` |
These stay as one-run overrides:
- `--recent`
- `--recent-scope`
- `--skip-created-before`
- `--skip-created-after`
They also remain valid TOML keys for users who intentionally run a permanently
scoped mirror:
```toml
[filters]
recent = 100
recent_scope = "per-filter"
skip_created_before = "2024-01-01"
skip_created_after = "30d"
```
`recent_scope` only applies to count-form `recent` values. The default is
`global`, which takes the library-wide recent set first and then applies album
or smart-folder filters. Use `per-filter` when you want each selected album,
smart folder, or unfiled pass to get its own recent window.
Date-only bounds compare each asset's capture-local calendar date using
Apple's per-asset timezone offset when it is usable. Assets without a usable
offset retain host-local date rendering. Datetime values and relative
intervals are instant cutoffs. Existing files are not moved when capture-local
path rendering places an asset in a different date folder; a later full sync
downloads it into the capture-local folder and leaves the earlier copy in
place. Use `kei sync --refresh-metadata` to apply the resolved timestamp and
offset to sidecars and to embedded files that do not already hold a timestamp.
To replace an older embedded timestamp, enable `metadata.set_exif_datetime`
and run `kei sync --refresh-metadata --repair-capture-timestamps`. This can
overwrite camera-supplied metadata.
Selector values can be escaped with `=` when a real album, smart folder, or
library name looks like a sentinel:
```toml
[filters]
albums = ["=all", "=none", "=!Drafts", "!Archive"]
```
Explicit album and smart-folder selectors are collection-scoped. When
`[filters].albums` or `[filters].smart_folders` is set, kei looks for those
passes across every visible library. `[filters].libraries` still scopes unfiled
photos and default library selection.
`smart_folders = ["all"]` still excludes Apple's sensitive folders. Use
`all-with-sensitive`, or name `Hidden` / `Recently Deleted` directly, to include
them.
### Photos, Live Photos, RAW, and filenames
| Old | New |
|---|---|
| `--size original` | `[photos].resolution = "original"` |
| `--live-photo-size original` | `[photos].live_resolution = "original"` |
| `--live-photo-mode skip` | `[photos].live_photo_mode = "skip"` |
| `--align-raw original` | `[photos].raw_policy = "prefer-raw"` |
| `--align-raw alternative` | `[photos].raw_policy = "prefer-jpeg"` |
| `--force-size` | `[photos].force_resolution = true` |
| `--keep-unicode-in-filenames` | `[photos].keep_unicode_in_filenames = true` |
| `--live-photo-mov-filename-policy original` | `[photos].live_photo_mov_filename_policy = "original"` |
| `--file-match-policy name-id7` | `[photos].file_match_policy = "name-id7"` |
The old multi-size workflow:
```sh
kei sync --size adjusted --size original --size alternative
```
maps to:
```toml
[photos]
resolution = "original"
edited = true
alternative = true
raw_policy = "as-is"
```
`edited = true` adds adjusted/edited extras. `alternative = true` adds the
alternative or RAW sibling when Apple provides one. Missing extras are skipped.
### Retry, watch, service, reports, and server
| Old | New |
|---|---|
| `--threads`, `KEI_THREADS` | `[download].threads` |
| `--bandwidth-limit`, `KEI_BANDWIDTH_LIMIT` | `[download].bandwidth_limit` |
| `--temp-suffix`, `KEI_TEMP_SUFFIX` | `[download].temp_suffix` |
| `[download.retry].max_retries` | `[download.retry].per_transfer` |
| `[download.retry].max_download_attempts` | `[download.retry].per_asset` |
| `--watch-with-interval`, `KEI_WATCH_WITH_INTERVAL` | `[watch].interval` |
| `--notify-systemd` | `[watch].notify_systemd` |
| `--pid-file` | `[watch].pid_file` |
| `--reconcile-every-n-cycles` | `[watch].reconcile_every_n_cycles` |
| `--notification-script` | `[notifications].script` |
| `--report-json` | `[report].json` |
| `--http-bind` | `[server].bind` |
| `--http-port` | `[server].port` |
| `[metrics].port`, `KEI_METRICS_PORT` | `[server].port` |
`kei service run` supplies a 24-hour watch fallback when `[watch].interval` is
unset. Plain `kei sync` stays one-shot unless you set a watch interval.
### Metadata and UI
| Old | New |
|---|---|
| `--set-exif-datetime` | `[metadata].set_exif_datetime = true` |
| `--set-exif-rating` | `[metadata].set_exif_rating = true` |
| `--set-exif-gps` | `[metadata].set_exif_gps = true` |
| `--set-exif-description` | `[metadata].set_exif_description = true` |
| `--embed-xmp` | `[metadata].embed_xmp = true` |
| `--xmp-sidecar` | `[metadata].xmp_sidecar = true` |
| `[download].no_progress_bar = true` | `[ui].progress_bar = false` |
`--no-progress-bar` is still a one-run override.
## Docker migration
Use `ghcr.io/rhoopr/kei:latest` or pin `ghcr.io/rhoopr/kei:0.20.0`.
The Docker image reads `/config/config.toml` by default:
```yaml
services:
kei:
image: ghcr.io/rhoopr/kei:0.20.0
container_name: kei
restart: unless-stopped
environment:
- TZ=${TZ:-UTC}
# The image sets this by default. It keeps long-running watch containers
# from reserving large glibc malloc arenas on multi-threaded hosts.
# - MALLOC_ARENA_MAX=2
# ICLOUD_USERNAME is still fine for automation, but [auth].username is preferred.
# - ICLOUD_USERNAME=${ICLOUD_USERNAME}
volumes:
- ./config:/config
- /path/to/photos:/photos
```
Put durable settings in `./config/config.toml`. For every option and
default, see [`example.config.toml`](../example.config.toml) or the wiki
[Configuration](https://github.com/rhoopr/kei/wiki/Configuration) page:
```toml
[auth]
username = "you@example.com"
[download]
directory = "/photos"
[watch]
interval = 86400
```
Keep secrets out of Compose when you can. Good options:
```sh
docker exec -it kei kei password set
```
or a Docker secret:
```yaml
services:
kei:
secrets:
- icloud_password
command:
- service
- run
- --config
- /config/config.toml
- --password-file
- /run/secrets/icloud_password
secrets:
icloud_password:
file: ./icloud_password.txt
```
The image still sets `KEI_DATA_DIR=/config`. That is container runtime glue so
sessions, the state DB, and credentials stay on the mounted config volume.
It also sets `MALLOC_ARENA_MAX=2` for glibc-based images. This lowers virtual
memory reservations during long watch runs. RSS stays roughly the same; the
knob mostly stops glibc from reserving a large arena for each worker thread.
Linux `kei install` writes the same `MALLOC_ARENA_MAX=2` setting into the
systemd unit. If you manage your own unit file, add:
```ini
Environment=MALLOC_ARENA_MAX=2
```
If a Docker container exits with `/config/config.toml is required for v0.20
Docker sync settings`, it found removed sync env config such as
`KEI_DOWNLOAD_DIR` but no mounted config file. Create `/config/config.toml`,
move durable settings there, and leave only secrets or runtime glue in env.
For one-shot commands, override the command:
```sh
docker run --rm \
-v "$PWD/config:/config" \
-v "/path/to/photos:/photos" \
ghcr.io/rhoopr/kei:0.20.0 \
sync --config /config/config.toml --dry-run
```
## Kept runtime flags
These remain public because they are action or one-run controls:
- `--config`
- `--log-level`, `--verbose`
- `--friendly`, `--no-friendly`
- `--password`, `--password-file`, `--password-command`, `--save-password`
- `--dry-run`
- `--only-print-filenames`
- `--retry-failed`
- `--no-progress-bar`
- `--recent`
- `--recent-scope`
- `--skip-created-before`
- `--skip-created-after`
- subcommand action flags such as `reset --yes`, `uninstall --purge`,
`verify --checksums`, `import-existing --library`, `import-existing --strict`,
and service install flags