Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Loft

Loft is a Rust-based HTTP file server with a Finder-inspired UI, public/private visibility, admin editing, and secret share links for private files and folders.

It is built as a single service with:

  • axum for the web server
  • askama for server-rendered HTML
  • sqlx + SQLite for metadata
  • local blob storage for file content
  • cookie sessions + CSRF protection for admin flows

Current Status

Implemented:

  • public main-tree browsing
  • admin login and logout
  • public/private visibility rules
  • secret share routes for private files and folders
  • raw and download routes with safer content serving
  • admin mutations for create, upload, rename, delete, toggle visibility, and regenerate share links
  • rolling admin sessions with configurable expiry
  • optional expiration times for secret share links
  • text-file editing by stable node_id
  • responsive SSR pages for desktop and phone layouts
  • inline preview support for text, image, video, and audio files
  • image and video previews that preserve aspect ratio, with fullscreen viewing
  • video HLS playback with Auto, Preview, and Original stream choices
  • background video preview generation with retry controls and an admin status page
  • streaming upload staging so large uploads do not have to stay in memory
  • byte-range raw file serving for browser media playback
  • /healthz for deployment and container health checks

Still open as follow-up work:

  • final visual polish pass

Requirements

  • Rust toolchain
  • SQLite is embedded through sqlx, so no separate database server is required

Run Locally

cargo run

By default Loft starts on 127.0.0.1:3000.

Open:

Configuration

Loft is configured with environment variables.

Variable Default Purpose
LOFT_BIND_HOST 127.0.0.1 Bind address
LOFT_PORT 3000 HTTP port
LOFT_DATA_DIR data Data root for SQLite + blobs
LOFT_ADMIN_USERNAME admin Admin login username
LOFT_ADMIN_PASSWORD_HASH placeholder hash Argon2id password hash
LOFT_SESSION_SECRET dev-session-secret Session secret configuration
LOFT_SESSION_TTL_DAYS 30 Rolling login expiry in days
LOFT_TEXT_EDITOR_SIZE_THRESHOLD 1048576 Max editable text-file size in bytes
LOFT_UPLOAD_SIZE_LIMIT 52428800 Upload request size limit in bytes

Example:

export LOFT_BIND_HOST=127.0.0.1
export LOFT_PORT=3000
export LOFT_DATA_DIR=./data
export LOFT_ADMIN_USERNAME=admin
export LOFT_ADMIN_PASSWORD_HASH='$argon2id$...'
export LOFT_SESSION_SECRET='replace-this-in-real-deployments'
export LOFT_SESSION_TTL_DAYS=30

cargo run

To generate a password hash for LOFT_ADMIN_PASSWORD_HASH:

cargo run -- admin hash-password 'replace-me'

If you are deploying from Docker Hub and do not want to install Rust locally, you can generate the hash with the published image itself:

docker run --rm kaelzs/loft:latest admin hash-password 'replace-me'

For LOFT_SESSION_SECRET, use a long random string. Two easy options:

openssl rand -hex 32
python3 -c "import secrets; print(secrets.token_urlsafe(32))"

Deployment

There is a focused deployment guide at docs/deployment.md.

It covers:

  • local runtime setup
  • environment variables
  • data directory layout
  • Docker Hub run examples
  • Docker Compose configuration

Docker Hub

Pull the published image:

docker pull kaelzs/loft:latest

Generate an admin password hash:

docker run --rm kaelzs/loft:latest admin hash-password 'your-admin-password'

Generate a session secret:

openssl rand -hex 32

Prepare a writable data directory on Linux hosts:

mkdir -p ./data
sudo chown -R 10001:10001 ./data

Run Loft:

docker run --rm \
  -p 3000:3000 \
  -v "$(pwd)/data:/data" \
  -e LOFT_ADMIN_USERNAME=admin \
  -e LOFT_ADMIN_PASSWORD_HASH='replace-with-argon2id-hash' \
  -e LOFT_SESSION_SECRET='replace-with-a-long-random-string' \
  -e LOFT_SESSION_TTL_DAYS=30 \
  kaelzs/loft:latest

Open:

Run it in the background:

docker run -d \
  --name loft \
  --restart unless-stopped \
  -p 3000:3000 \
  -v "$(pwd)/data:/data" \
  -e LOFT_ADMIN_USERNAME=admin \
  -e LOFT_ADMIN_PASSWORD_HASH='replace-with-argon2id-hash' \
  -e LOFT_SESSION_SECRET='replace-with-a-long-random-string' \
  -e LOFT_SESSION_TTL_DAYS=30 \
  kaelzs/loft:latest

Useful follow-up commands:

docker logs -f loft
docker stop loft
docker rm loft

Docker Compose

Example compose.yaml:

services:
  loft:
    image: kaelzs/loft:latest
    container_name: loft
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      LOFT_BIND_HOST: 0.0.0.0
      LOFT_PORT: 3000
      LOFT_DATA_DIR: /data
      LOFT_ADMIN_USERNAME: admin
      LOFT_ADMIN_PASSWORD_HASH: ${LOFT_ADMIN_PASSWORD_HASH}
      LOFT_SESSION_SECRET: ${LOFT_SESSION_SECRET}
      LOFT_SESSION_TTL_DAYS: 30
      LOFT_TEXT_EDITOR_SIZE_THRESHOLD: 1048576
      LOFT_UPLOAD_SIZE_LIMIT: 52428800
    volumes:
      - ./data:/data

Example .env:

LOFT_ADMIN_PASSWORD_HASH='$argon2id$...'
LOFT_SESSION_SECRET=replace-with-a-long-random-string

Start Loft:

docker compose up -d

View logs:

docker compose logs -f

Stop Loft:

docker compose down

Update to a newer image:

docker compose pull
docker compose up -d

For Docker Compose, keep the Argon2 password hash in .env and keep it quoted because the hash contains $.

If you do not want to use .env, you must escape every $ in the Argon2 hash as $$ inside compose.yaml.

On Linux hosts, make the mounted data directory writable by Loft's container user:

mkdir -p ./data
sudo chown -R 10001:10001 ./data

Docker Configuration

Loft currently supports these Docker environment variables:

Variable Docker image default Purpose
LOFT_BIND_HOST 0.0.0.0 Bind address inside the container.
LOFT_PORT 3000 HTTP port inside the container.
LOFT_DATA_DIR /data Internal storage path for SQLite and blobs.
LOFT_ADMIN_USERNAME admin Admin login username.
LOFT_ADMIN_PASSWORD_HASH placeholder hash Argon2id hash for the admin password. Set this explicitly.
LOFT_SESSION_SECRET replace-this-before-production Session signing secret. Set this explicitly.
LOFT_SESSION_TTL_DAYS 30 Rolling login expiry in days. Each valid request refreshes it.
LOFT_TEXT_EDITOR_SIZE_THRESHOLD 1048576 Max inline-editable text size in bytes.
LOFT_UPLOAD_SIZE_LIMIT 52428800 Max upload request size in bytes.

Data Layout

Inside LOFT_DATA_DIR, Loft keeps:

  • loft.sqlite3 for metadata
  • blobs/ for file content, stored by internal blob ID

Logical paths in the UI are not tied directly to blob storage paths.

Security Notes

  • Admin passwords are checked against an Argon2id hash.
  • Anonymous users can only browse publicly reachable nodes.
  • Private content in the main tree returns 404 to anonymous users.
  • Secret share links only work while the target node is private.
  • Browser write actions require CSRF protection.
  • raw routes send X-Content-Type-Options: nosniff.

Testing

Run the full test suite:

cargo test

Formatting check:

cargo fmt --check

Project Structure

src/
  app.rs
  auth/
  catalog/
  storage/
  web/
migrations/
static/
templates/
tests/

Design

The visual direction is based on the files in Design/, with a warm Apple/Finder-like presentation adapted for both desktop and phone layouts.

AI Credits

  • Product and UI design direction in this project was created with Claude Design.
  • Application code and implementation work in this project was created with Codex.

About

HTTP File Server written in Rust by AI

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages