Skip to main content

Storage Layout

Server

All server-side data lives under a single path prefix (the library root). Only encrypted blobs are stored.

/
├── remote_id_{uuid} UUID of this remote (empty marker file, one per remote root)
├── library/
│ ├── library_id_{uuid} UUID of this library (encoded in filename)
│ ├── library_salt random 32-byte salt
│ ├── version_1 write format version
│ └── mk_{username}_{uuid}.enc master key encrypted with the user's KEK
├── media/
│ └── YYYY/
│ └── MM/
│ ├── {UUID}.data encrypted photo/video bytes
│ └── {UUID}.thumb encrypted thumbnail
└── operations/
├── {UUID}.op{N}_{count} encrypted compacted operations (tier N, count groups)
└── LOCK.op compaction lock (JSON, transient)

The remote_id_{uuid} marker sits at the root of each remote's storage, alongside library/, media/, and operations/ when the remote holds a full copy. It is written once by initialize_remote and checked on every subsequent connect, so a client can detect a stale or mismatched remote before syncing against it.

On-device

All application data is stored under a single root directory (the app dir), which defaults to the platform's application data directory:

Operating SystemDefault Path
Linux~/.local/share/lasco
macOS~/Library/Application Support/lasco
Windows%LOCALAPPDATA%\lasco

The root can be overridden via the --data-dir CLI flag.

{app dir}/
├── config.json global index of libraries
└── libraries/
└── {library_id}/
├── library.json per-library config and remotes
├── local_state/
│ ├── library/
│ │ ├── library_salt random 32-byte salt
│ │ ├── version_1 write format version
│ │ └── mk_{username}_{uuid}.enc master key encrypted with the user's KEK
│ ├── operations.log append-only log of committed operation groups
│ ├── pending.op in-progress operation group
│ └── media/
│ └── YYYY/
│ └── MM/
│ ├── {UUID}.data
│ └── {UUID}.thumb
└── remotes/
└── {remote_id}/
└── state/ last known state of the remote
├── operations/ downloaded remote op files
└── media/
└── media_list.json last known remote media availability

Each library directory is self-describing. Its configuration lives in library.json next to the library data, so the whole directory can be copied or moved as a unit. The global config.json is only a thin index used to discover libraries.

config.json

The global index. It maps each library id to its nickname and records the default library. It holds no remote or credential data.

{
"version": 1,
"default_library": "my-photos",
"libraries": {
"a1b2c3d4-5678-90ef-ghij-klmnopqrstuv": {
"nickname": "my-photos"
}
}
}

library.json

Per-library configuration stored at {app dir}/libraries/{library_id}/library.json. It holds the library preferences and the ordered list of remotes. Each remote entry carries a stable remote_uuid, a human-readable name, and a kind object with that kind's connection details, including, for credentialed remotes, the encrypted secret key.

{
"version": 1,
"nickname": "my-photos",
"default_username": "alice",
"active_password_uuid": "c3d4e5f6-7890-12gh-ijkl-mnopqrstuvwx",
"default_fetch_remote": "a1b2c3d4-0000-0000-0000-000000000001",
"default_upload_album": "b2c3d4e5-6789-01fg-hijk-lmnopqrstuvw",
"auto_import_device_media": false,
"remotes": [
{
"remote_uuid": "a1b2c3d4-0000-0000-0000-000000000001",
"name": "my-s3-remote",
"kind": {
"kind": "s3",
"endpoint": "https://s3.amazonaws.com",
"bucket": "my-photos",
"region": "us-east-1",
"access_key": "AKIAIOSFODNN7EXAMPLE",
"secret_key_encrypted": "<base64-encoded AES-256-GCM ciphertext>",
"secret_key_encryption_description": "AES-256-GCM v1"
}
},
{
"remote_uuid": "a1b2c3d4-0000-0000-0000-000000000002",
"name": "usb-backup",
"kind": {
"kind": "fixed_path",
"root_dir": "/Volumes/backup/my-photos"
}
}
]
}

remote_uuid is the remote's stable identity. It is checked against the remote_id_{uuid} marker on the remote's storage on every connect, and never changes even if the human-readable name is later renamed. name is how the CLI and app let a user refer to the remote. It must be unique within a library but can be changed freely without affecting sync.

The kind field's own kind tag selects the remote's storage type:

  • s3 — an S3-compatible bucket, reached with the given credentials.
  • fixed_path — trusts the stored root_dir absolute path as-is. Suitable for USB drives, network mounts, or any path stable across the app's lifetime.
  • debug_local_apple (macOS/iOS only) — stores only a local_dir_name and re-resolves its location against the current app-support directory on every use, because sandboxed containers get a fresh container path on each install or reinstall. This is a development and testing convenience, not for production backup use.

The secret key is never stored in plaintext. It is encrypted with the library master key using AES-256-GCM and held only in the secret_key_encrypted field. Local filesystem remotes have no credentials. See Encryption for details.