# PST Archive — Mail Indexer A self-hosted, Dockerized application for **uploading Microsoft Outlook PST/OST files**, **indexing every email inside them**, and **searching the contents through a web browser** — with full **user authentication**, **multi-factor authentication (MFA)**, **encrypted-at-rest credentials**, and a **separate admin panel** on its own port. Designed for **forensic review, e-discovery, personal archive browsing, and migration audits** — any situation where you need to crack open a PST and actually find things inside it without loading it into Outlook. --- ## Table of Contents 1. [Features](#features) 2. [Quick Start](#quick-start) 3. [First-Run Setup](#first-run-setup) 4. [Running as a Daemon](#running-as-a-daemon) 5. [Theming & Dark Mode](#theming--dark-mode) 6. [Authentication & Security Model](#authentication--security-model) 7. [Admin Panel](#admin-panel) 8. [Organizing PSTs with Folders](#organizing-psts-with-folders) 9. [Folder Access Permissions](#folder-access-permissions) 10. [Multi-Factor Authentication (MFA)](#multi-factor-authentication-mfa) 11. [Encryption at Rest](#encryption-at-rest) 12. [How It Works](#how-it-works) 13. [Architecture](#architecture) 14. [Project Layout](#project-layout) 15. [Data Model](#data-model) 16. [REST API Reference](#rest-api-reference) 17. [Frontend Interfaces](#frontend-interfaces) 18. [Search Syntax](#search-syntax) 19. [Configuration](#configuration) 20. [Persistence & Backup](#persistence--backup) 21. [Troubleshooting](#troubleshooting) 22. [Performance Notes](#performance-notes) 23. [Security Considerations](#security-considerations) 24. [Extending the App](#extending-the-app) --- ## Features ### Core - **Drag-and-drop upload** of `.pst` and `.ost` files through the browser - **Streaming upload with progress bar** — handles multi-gigabyte archives - **Background indexing** into SQLite with **FTS5 full-text search** (ranked, prefix-matched, with highlighted snippets) - **Multi-archive support**: switch between uploaded PSTs; each is searched independently - **Folder organization**: create folders in the sidebar to group related PST files (e.g. by client, case, year). Move archives between folders, rename folders, or delete folders (any archives inside are safely moved to Uncategorized) - **Per-folder access permissions**: admins can restrict which non-admin users can view each folder. A single user can be granted access to many folders; a single folder can be shared with many users (true many-to-many) - **Folder filter**: narrow search to any folder within the archive (Inbox, Sent Items, nested folders) - **Full message viewer**: subject, sender, recipients, date, folder, attachments, body - **Persistent storage**: everything lives in a mounted `./data` volume ### Security - **First-run setup wizard** forces creation of an initial administrator before any other action is possible - **Password authentication** using **Argon2id** (winner of the Password Hashing Competition) with constant-time verification - **TOTP-based MFA** (RFC 6238; compatible with Google Authenticator, Authy, 1Password, Microsoft Authenticator, and every other standard authenticator app) - **Encryption at rest** for all user PII — usernames, password hashes, and TOTP secrets are stored as Fernet (AES-128-CBC + HMAC-SHA256) ciphertexts - **Master key** auto-generated on first run and stored with `chmod 600`, or overridable via `MASTER_SECRET` environment variable - **HMAC-based username lookup** — usernames are never stored in plaintext but are still indexable for login - **JWT session cookies**, signed with an HKDF-derived sub-key, HttpOnly, SameSite=Lax - **Rate limiting** on login attempts (10 failures per 5 minutes per IP+username) - **Admin-only user management**: create, delete, role-change, password-reset, MFA-enroll other users - **Admin file & folder management**: system-wide view of every uploaded PST and every folder, with create/rename/delete for folders and force-delete for any file regardless of uploader - **Separate admin panel on a separate port** — lockable behind its own firewall rule or VPN ### UI - **Light and dark themes** on both the main app and the admin panel, with a sun/moon toggle in the masthead and on login/setup screens - Preference persists in `localStorage` per-browser and respects the operating system's `prefers-color-scheme` on first visit - Theme applied inline in `
` before stylesheets load — **no flash of wrong theme** on page load - Dark mode uses a custom "inked paper" palette (deep charcoal with warm paper-cream text and softened oxblood accent) — intentional character, not generic-dark - Editorial typography (Fraunces serif + JetBrains Mono), grain overlay, responsive layout down to mobile widths --- ## Quick Start ### Prerequisites - Docker 20.10 or newer - Docker Compose v2 - A `.pst` or `.ost` file to index ### Run (as a daemon — recommended) ```bash docker compose up -d --build ``` The `-d` flag runs the container in the background (detached). On the first run this takes **3–5 minutes** because `libpff-python` compiles a C extension from source; subsequent startups are nearly instant. The container is configured with `restart: unless-stopped`, so it will automatically come back up after a reboot or a crash, and keep running across Docker daemon restarts — until you explicitly stop it. Once the container is up: - **Main app**: `http://localhost:8000` - **Admin panel**: `http://localhost:8001` ### Foreground mode (useful for debugging) ```bash docker compose up --build ``` This runs the container attached to your terminal so you can see logs inline. Press `Ctrl-C` to stop. ### Tail the logs ```bash docker compose logs -f # follow both streams docker compose logs -f pst-indexer # explicit service name ``` ### Check status ```bash docker compose ps # is it running? docker compose top # which processes? ``` ### Stop ```bash docker compose stop # stop, keep data, keep container docker compose down # stop and remove the container, keep data volume ``` ### Full reset ```bash docker compose down rm -rf ./data # erases everything including users and the master key docker compose up -d --build ``` ### Restart / update ```bash # After editing code or pulling new images: docker compose up -d --build # rebuild & restart with one command # Or just restart without rebuilding: docker compose restart ``` --- ## First-Run Setup On the very first visit to `http://localhost:8000`, you will be presented with a **setup wizard** that asks you to create the initial administrator: 1. Choose a username (3–64 characters) 2. Choose a password (8+ characters — use a passphrase you'll remember) 3. Confirm the password The setup endpoint is only callable **while the users table is empty**. Once the first admin is created, the endpoint returns `409 Conflict` and is effectively disabled. After setup, you are automatically signed in and landed on the main app. **Heads up:** the admin panel on port 8001 will refuse to accept any logins until the first admin has been created via the main app's setup wizard. This prevents a race where someone could hit `/api/login` on the admin port before you've set things up. --- ## Running as a Daemon By default, this app is designed to **run as a long-lived background service**. The `docker-compose.yml` sets `restart: unless-stopped`, which means once started, the container will: - Keep running after you close your terminal - Automatically restart if the process crashes - Automatically restart when the host reboots and Docker comes back up - **Not** restart if you explicitly stop it with `docker compose stop` (hence "unless-stopped") ### Starting the daemon ```bash docker compose up -d --build ``` The `-d` flag is short for "detached" — the container is launched in the background and the command returns immediately. Your terminal is free to do other things; the container keeps running. ### Verifying it's running ```bash docker compose ps ``` You should see output like: ``` NAME IMAGE STATUS PORTS pst-indexer pst-indexer-pst-indexer Up 2 minutes (healthy) 0.0.0.0:8000->8000/tcp, 0.0.0.0:8001->8001/tcp ``` The `(healthy)` flag comes from the built-in healthcheck, which probes both `/api/health` endpoints every 30 seconds. ### Watching logs Since the container is detached, you won't see logs in your terminal by default. Attach to them on demand: ```bash docker compose logs # print recent logs and exit docker compose logs -f # follow (live tail), Ctrl-C to detach docker compose logs -f --tail=100 # follow, starting from the last 100 lines docker compose logs --since 1h # only logs from the last hour ``` Detaching from `-f` does **not** stop the container — it just stops showing you the logs. ### Stopping ```bash docker compose stop # stop the container but keep it around docker compose start # start it again if previously stopped docker compose restart # stop and start in one command docker compose down # stop and remove the container (data volume is kept) docker compose down -v # stop, remove container, AND remove anonymous volumes ``` None of these delete your `./data` directory — that's a bind mount, not a Docker-managed volume, so it's always yours. ### Survives reboots Because `restart: unless-stopped` is set, after a host reboot Docker will automatically start the container as soon as the Docker daemon comes up. Nothing manual required on your part. If you ever explicitly stopped it with `docker compose stop`, Docker will *not* auto-start it after reboot (that's the whole point of "unless-stopped"). Run `docker compose start` or `docker compose up -d` to bring it back. ### Updating to a new version ```bash # Pull new source, then: docker compose up -d --build ``` Docker Compose compares the current image to the built one; if they differ, it rebuilds and recreates the container with zero-downtime-style replacement. Your `./data` volume is preserved throughout. --- ## Theming & Dark Mode Both the main app and the admin panel support **light and dark themes**, with a sun/moon toggle visible in the masthead (when signed in) and floating in the top-right corner on the login/setup screens. ### How the toggle works - Click the icon to flip between themes - Choice is saved to `localStorage["pst-theme"]` in your browser - On a fresh browser (nothing saved), the app respects your operating system's `prefers-color-scheme` setting - The theme is applied by an inline `