flickr2grain #
Local utility for migrating Flickr albums to grain.social as atproto records. Runs on your machine — no cloud component, no telemetry.
Quick start #
npm install- Register a Flickr app (see
docs/flickr-setup.md) and put the credentials in.env.local. npm run dev- Visit http://127.0.0.1:5173, click Connect Flickr, complete the OAuth flow.
- Click Connect atproto, enter your handle, complete the OAuth flow at your PDS.
- Pick an album. Click Migrate. Progress streams live.
Development #
npm run typecheck— strict TypeScriptnpm run test— full Vitest suite (unit + integration)npm run build— production build todist/
Platform paths #
flickr2grain stores credentials + its state database at platform-appropriate locations:
| OS | Path |
|---|---|
| macOS | ~/Library/Application Support/flickr2grain/ |
| Linux | ~/.local/share/flickr2grain/ |
| Windows | %LOCALAPPDATA%\flickr2grain\ |
The directory contains:
db.sqlite— main state (mode0600).logs/migration-<id>.ndjson— one file per migration, greppable state-transition log.
Resetting #
Two ways:
- In-app reset button (recommended): wipes credentials but keeps migration history. All auth must be redone before the next migration. If you reconnect under a different atproto handle after reset, prior migrations will be preserved as history but cannot be resumed against the new DID — start a fresh migration instead.
- Full wipe:
rm -rf "~/Library/Application Support/flickr2grain"(or the platform equivalent). This deletes everything — credentials, migration history, logs.
Security model — v1 (local-only) #
flickr2grain stores plaintext OAuth tokens on disk with file mode 0600 (user-only readable). This is appropriate for the v1 threat model:
- Single-user trusted machine: the user is expected to have full-disk encryption enabled and to be the only person with access to the machine.
- No network-exposed interface: the server binds to
127.0.0.1only. - No remote state: all credentials live on this one machine.
Explicit non-goal: hosted deployment. If this utility is ever repackaged as a hosted service for multiple users, the token storage layer MUST be replaced with a KMS-backed encrypted store (e.g. AWS KMS, HashiCorp Vault, GCP KMS) with per-user data-encryption keys, rotation, and audit logging. The current flickr_tokens / atproto_sessions repository interfaces are narrow enough that such a migration is localised to src/storage/repos/.
Tokens are wiped by:
- The in-app Reset button.
POST /api/reset.- Deleting the platform data directory (full wipe).
Data flow — what lands where #
- Flickr: only read operations — album list, photo metadata, EXIF, original-file download.
- atproto: only write operations —
uploadBlob,createRecordforsocial.grain.photo,social.grain.photo.exif,social.grain.gallery,social.grain.gallery.item. - Nothing is sent anywhere else.
Troubleshooting #
- "Reset the Flickr/atproto connection": Click the in-app reset, then re-authenticate. If that doesn't work, do a full wipe.
- "Blob upload rejected": The uploaded blob exceeded the PDS's lexicon-declared limit. If this happens consistently, pass
{ maxBytes: 1_000_000 }when starting a migration (the utility defaults to 2 MB per user-confirmed lexicon update; some PDSes may still enforce 1 MB literally). - "Migration halted with pds_rejected": The PDS refused a record's shape. Likely a lexicon change. Check
logs/migration-<id>.ndjsonfor the exact error; the fix is typically a record-builder update. - "EXIF fields look wrong": Run the verification procedure in
docs/exif-verification.md.
End-to-End Smoke Test (Operational Checklist) #
With real Flickr credentials in .env.local, verify:
- TypeScript 95%
- JavaScript 4%
- HTML 1%