# LumaVault

LumaVault is a self-hosted photo and video library for organizing, browsing, and sharing personal media. It stores original files on infrastructure you control while providing automatic backups from mobile devices, timeline browsing, albums, metadata search, and private sharing.

> LumaVault is under active development. Keep an independent backup of your original media.

## Features

- Automatic photo and video uploads
- Responsive timeline and gallery views
- Albums, favorites, archive, and trash
- Search by date, filename, camera, location, and tags
- EXIF metadata and location-map support
- Duplicate detection using file hashes
- Background thumbnail and video-preview generation
- Multiple users with separate private libraries
- Password-protected and expiring share links
- Import from existing directory structures
- Support for local disks and S3-compatible object storage
- Dark mode and installable web app
- Docker-based deployment

## Requirements

- Docker Engine 24 or newer
- Docker Compose 2.20 or newer
- At least 2 GB of RAM
- Storage for originals, thumbnails, and database backups

For video transcoding or large libraries, 4 GB of RAM and hardware acceleration are recommended.

## Installation

Create a directory for LumaVault:

```bash
mkdir lumavault
cd lumavault
```

Create a `compose.yaml` file:

```yaml
services:
  app:
    image: ghcr.io/lumavault/lumavault:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    env_file:
      - .env
    volumes:
      - ./data/library:/data/library
      - ./data/cache:/data/cache
    depends_on:
      database:
        condition: service_healthy

  database:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: lumavault
      POSTGRES_USER: lumavault
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U lumavault"]
      interval: 10s
      timeout: 5s
      retries: 5
```

Create `.env`:

```dotenv
DB_PASSWORD=replace-with-a-long-random-password
DATABASE_URL=postgresql://lumavault:replace-with-a-long-random-password@database:5432/lumavault
APP_SECRET=replace-with-at-least-32-random-characters
PUBLIC_URL=http://localhost:8080
TZ=America/Toronto
```

Ensure `DB_PASSWORD` in `DATABASE_URL` matches `DB_PASSWORD`, then start the services:

```bash
docker compose up -d
```

Open [http://localhost:8080](http://localhost:8080) and create the first account. The first registered user becomes the administrator.

## Usage

### Uploading media

Select **Upload** in the web interface, then choose photos, videos, or an entire directory. Uploads continue in the background while the browser remains open.

Mobile devices can use the installable web app:

1. Open LumaVault in the device browser.
2. Choose **Add to Home Screen**.
3. Sign in and enable **Automatic Uploads**.
4. Select the folders or albums to back up.

### Importing an existing library

Mount the source directory read-only in `compose.yaml`:

```yaml
services:
  app:
    volumes:
      - ./data/library:/data/library
      - ./data/cache:/data/cache
      - /mnt/photos:/imports/photos:ro
```

Restart the application and begin an import:

```bash
docker compose up -d
docker compose exec app lumavault import /imports/photos
```

Imported files are copied into the managed library by default. Use `--reference` to index files in place:

```bash
docker compose exec app lumavault import --reference /imports/photos
```

Referenced files must remain available at the same path and are not included in LumaVault-managed backups.

### Creating an album

1. Select one or more items in the timeline.
2. Choose **Add to album**.
3. Select an existing album or create a new one.
4. Use **Share** to generate an optional public link.

### Updating

Review the release notes before upgrading, then run:

```bash
docker compose pull
docker compose up -d
```

Database migrations run automatically when the application starts.

### Backup and restore

Back up both the media directory and PostgreSQL database:

```bash
docker compose exec -T database pg_dump -U lumavault lumavault > lumavault.sql
tar -czf lumavault-media.tar.gz data/library
```

To restore the database into an empty deployment:

```bash
docker compose exec -T database psql -U lumavault lumavault < lumavault.sql
```

Thumbnail and preview files in `data/cache` do not need to be backed up; LumaVault can regenerate them.

## Configuration

Configuration is provided through environment variables in `.env`.

| Variable | Default | Description |
| --- | --- | --- |
| `DATABASE_URL` | Required | PostgreSQL connection URL |
| `APP_SECRET` | Required | Secret used to sign sessions and share tokens |
| `PUBLIC_URL` | `http://localhost:8080` | External URL used in links and redirects |
| `TZ` | `UTC` | Time zone used for jobs and displayed dates |
| `LIBRARY_PATH` | `/data/library` | Storage path for original media |
| `CACHE_PATH` | `/data/cache` | Storage path for thumbnails and previews |
| `MAX_UPLOAD_SIZE` | `10G` | Maximum size of a single uploaded file |
| `REGISTRATION_ENABLED` | `false` | Allow new users to register without an invitation |
| `TRASH_RETENTION_DAYS` | `30` | Days before trashed items are permanently removed |
| `WORKER_CONCURRENCY` | `2` | Number of concurrent background jobs |
| `LOG_LEVEL` | `info` | Logging level: `debug`, `info`, `warn`, or `error` |
| `MAP_TILES_URL` | Empty | Optional custom map-tile provider URL |

Restart the application after changing configuration:

```bash
docker compose up -d
```

### S3-compatible storage

To store original media in S3-compatible object storage, add:

```dotenv
STORAGE_DRIVER=s3
S3_ENDPOINT=https://s3.example.com
S3_REGION=us-east-1
S3_BUCKET=lumavault
S3_ACCESS_KEY_ID=replace-me
S3_SECRET_ACCESS_KEY=replace-me
S3_FORCE_PATH_STYLE=false
```

The PostgreSQL database and cache still require persistent local storage.

### Reverse proxy

For production deployments, place LumaVault behind a reverse proxy with HTTPS. The proxy must:

- Forward the original `Host`, `X-Forwarded-For`, and `X-Forwarded-Proto` headers.
- Allow request bodies up to `MAX_UPLOAD_SIZE`.
- Disable response buffering for upload endpoints.
- Use extended timeouts for large uploads.

Set `PUBLIC_URL` to the externally accessible HTTPS URL, such as:

```dotenv
PUBLIC_URL=https://photos.example.com
```

## Security

- Use unique, randomly generated values for `DB_PASSWORD` and `APP_SECRET`.
- Serve the application over HTTPS outside a trusted local network.
- Do not expose PostgreSQL directly to the internet.
- Keep Docker images and the host operating system updated.
- Back up originals and the database regularly.
- Test restoration procedures before relying on backups.

## License

LumaVault is licensed under the GNU Affero General Public License v3.0. See `LICENSE` for details.