- TypeScript 99.6%
- Dockerfile 0.2%
| client | ||
| docker | ||
| server | ||
| .dockerignore | ||
| .env.production.example | ||
| .gitignore | ||
| AGENT.md | ||
| AGENTS.md | ||
| Build_Store_function.md | ||
| docker-compose.prod.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE | ||
| README.md | ||
| Requirements.md | ||
Filux
A modern, self-hosted, open-source enterprise file storage and sharing application.
Features
Implemented:
- Authentication & Identity (Epic 1) — Password auth (argon2id), TOTP 2FA, WebAuthn passkeys, and OpenID Connect SSO with "Sign In with SSO" button (supports Keycloak, Authentik, Authelia, Okta, Azure AD, and any OIDC-compliant provider)
- Directory architecture (Epic 2) — Unlimited N-level folder nesting, streaming uploads with checksum verification, breadcrumb + sidebar tree navigation, file/folder CRUD, sidebar folder creation with duplicate name prevention
- Granular sharing (Epic 3) — User-to-user RBAC (View/Download/Edit/Delete), public web links with expiration and usage limits, password-protected links, guest preview pages, email share links
- Responsive UI & themes (Epic 4) — Dark/light theme engine, mobile-first layout with a collapsible sidebar drawer, 44px touch targets, validated down to 320px
- Administration & user management (Epic 5) — Admin panel (user list with role badges, quota and status, promote/demote, enable/disable, per-user quota, delete), registration control with manual user provisioning, default and per-user storage quotas with 507 enforcement
- Password lifecycle (Epic 6) — Forgot-password email reset (SMTP), self-service change password, admin password reset with forced change on next login
- Bulk upload & archive management (Epic 7) — Multi-file upload queue with progress and retry, ZIP upload with optional safe extraction (decompression bomb and quota protected) and archive audit logging
- Reverse share (Epic 8) — Inbound "Request Files" links that let guests upload into a folder you choose, with password, expiry, and upload limits, sanitized filenames, and audit logging
- Internationalization (Epic 10) — English + Spanish locales, language selector in the navbar, auth screen and guest preview page, per-user locale persisted to the server, Intl-localized dates, numbers and file sizes, English fallback for missing translations
- Click/double-click & multi-select (Issue #1) — Checkbox clicks accumulate a multi-selection with no modifier keys (44px hit target); plain row click selects that item only; Ctrl/Cmd+click jump-toggles items; Shift+click adds the range from the last anchor; double-click folder navigation / inline file preview, a contextual batch action bar (files-only vs folders-only vs mixed), and bulk operations: compress to zip (background job with progress), streamed zip download, atomic batch move, share-all-selected dialog, and "add selected to new folder"
- Favorites — Heart icon on any file/folder row (plus a Favorite/Unfavorite context-menu entry) to mark items as favorites; a Favorites section in the sidebar with a count, a dedicated Favorites view listing all favorited items, and a Favorites mini card at the top of the file area as a shortcut. Per-user and synced server-side.
- Shared-item engagement counts — Every shared item reports how much it has been used: the file list shows a compact views + downloads line on shared rows, and the Share dialog always shows the public link's view/download counters (and the link's cap when one is set) plus the item's total downloads across public links, user shares, purchases, and owner downloads (from the download ledger).
- HEIC/HEIF viewing — Inline previews (authenticated "View" and public share links) transcode iPhone HEIC/HEIF photos to JPEG server-side (portable WASM decode + sharp), so they render in every browser; downloads still deliver the original file. Correct Content-Type headers are now set for all known file types.
- Store: paid downloads (Epic 11) — Sell any file or folder with a price, a download allowance, optional expiry, and optional max-per-buyer, behind a public storefront link (
/p/slug) — guests check out without accounts, pay through Stripe/PayPal/Square hosted checkout or offline manual transfer, and download through personal, hashed, rate-limited links. Sellers get a Sales dashboard (mark-paid, refund, resend, revenue) and every download in the instance is ledgered, so reports separate purchase-link downloads from ordinary shares. See Store: paid downloads.
Planned (see Requirements.md for full specs and phased roadmap):
- Epic 9 — WebDAV protocol access
Tech Stack
| Layer | Choice |
|---|---|
| Frontend | React 18 + Vite, Tailwind CSS, FontAwesome Free |
| Backend | Node.js + Fastify |
| Database | PostgreSQL 17+ (Prisma ORM) |
| Session/Cache | Redis / Valkey |
| Auth | argon2id, otplib, @simplewebauthn, openid-client |
| File storage | Local filesystem (POSIX) |
| Testing | Vitest (unit/API), Playwright (E2E) |
Getting Started
Production Deployment
For production, use the dedicated production compose file with the combined image:
cp .env.production.example .env.production
# Edit .env.production with your domain and secrets
docker compose -f docker-compose.prod.yml up -d
The production image (bmcgonag/filux:latest) runs migrations automatically on startup and serves both frontend and backend through Nginx on port 80.
First user registered becomes admin automatically.
Key Environment Variables
| Variable | Description |
|---|---|
APP_URL |
Your public-facing URL (e.g. https://filux.example.com). Used for OIDC redirect URIs and post-login redirects. |
DATABASE_URL |
PostgreSQL connection string (auto-configured by compose). |
REDIS_URL |
Redis connection string (auto-configured by compose). |
JWT_SECRET |
Secret for signing session tokens. |
ENCRYPTION_KEY |
32-byte key for encrypting secrets at rest (OIDC client secrets, TOTP secrets). |
WEB_AUTHN_RP_ID |
Domain for WebAuthn/Passkey (e.g. filux.example.com). |
WEB_AUTHN_RP_ORIGIN |
Origin for WebAuthn (e.g. https://filux.example.com). |
Configuring OIDC SSO
Filux supports any OIDC-compliant identity provider (Keycloak, Authentik, Authelia, Okta, Azure AD, etc.):
- Log in as admin and open Admin Settings (shield icon in navbar).
- Go to the OIDC tab.
- Set the Discovery Issuer URL to your provider's OIDC issuer (e.g.
https://auth.example.com/realms/myrealm). - Enter the Client ID and Client Secret from your provider.
- Select the Scopes you need (minimum:
openid,profile,email). - Copy the Redirect URI shown in the form and register it in your OIDC provider.
- Toggle Enable OIDC SSO and save.
A "Sign In with SSO" button will appear on the login screen. First-time OIDC users are automatically provisioned (JIT). Existing accounts are linked by matching email.
Docker Compose (development)
docker compose up --build -d
- Client: http://localhost:4001
- Server API: http://localhost:3002
- PostgreSQL exposed on
5434, Redis on6380
Local development
# 1. Start dependencies
docker compose up -d postgres redis
# 2. Backend (http://localhost:3001)
cd server
cp .env.example .env
npm install
npx prisma migrate dev
npm run dev
# 3. Frontend (http://localhost:5173)
cd client
npm install
npm run dev
Note: set CORS_ORIGIN, WEB_AUTHN_RP_ORIGIN, and WEB_AUTHN_RP_ID to http://localhost:5173 for local development (they default to http://localhost:5173).
Project Structure
Filux/
├── Requirements.md # Feature specs (epics 1-10) & roadmap
├── AGENT.md # Session notes & build log
├── docker-compose.yml # Development: PostgreSQL + Redis + server + client
├── docker-compose.prod.yml # Production: PostgreSQL + Redis + combined image
├── .env.production.example # Production environment template
├── server/ # Fastify + Prisma backend
│ ├── src/routes/ # API route modules (auth, items, share, ...)
│ ├── src/services/ # Business logic (permissions, audit, mailer, ...)
│ ├── src/config/ # Environment config, Prisma & Redis clients
│ ├── prisma/
│ │ ├── schema.prisma # Data model
│ │ └── migrations/ # Database migrations
│ └── tests/ # Vitest unit/API/E2E tests
└── client/ # React + Vite frontend
├── src/components/ # UI components (Sidebar, FileList, modals, ...)
├── src/pages/ # AuthPage, FolderView, PublicPreview
├── src/hooks/ # useAuth, useTheme
├── src/i18n/ # Internationalization (en, es)
└── tests/unit/ # Vitest + Testing Library tests
Testing
cd server && npm run test # 468 unit + API tests (needs Postgres :5434 + Redis :6380)
cd client && npm run test # 234 unit tests
cd client && npm run lint && npm run build
cd client && npx playwright test # 20 e2e specs; boots its own API (:3010) + vite (:5175)
Store: paid downloads (Epic 11)
Selling is four steps: Payment settings (add a payment account) → Share on any owned file or folder → Sell tab (title, price, currency, downloads per purchase, email requirement, link expiry) → share the storefront URL that appears. Buyers never need a Filux account: the storefront, the hosted payment redirect, and the "confirm your email" claim step run with no session at all. The Sales page in the sidebar lists orders with Mark paid (offline payments), Refund, and Resend link, plus revenue tiles and a downloads-by-source table.
Providers
| Provider | What you configure |
|---|---|
| Manual / offline | Just a payment instructions text (IBAN, cash-on-collection…). It is shown to the buyer on the storefront and the pending-order page; the order waits as pending until you click Mark paid. |
| Stripe | A restricted API key (Checkout Sessions write, transfers/payouts denied) and a webhook endpoint https://YOUR-DOMAIN/api/v1/payments/webhooks/stripe subscribed to checkout.session.completed, checkout.session.expired, charge.refunded — paste its whsec_ signing secret. |
| PayPal | App client ID + secret (sandbox or live) and a webhook …/api/v1/payments/webhooks/paypal for PAYMENT.CAPTURE.COMPLETED/DENIED/REFUNDED/REVERSED and ORDER.VOIDED — paste the Webhook ID. Signatures are verified locally against PayPal's certificate (restricted to *.paypal.com / *.paypalobjects.com). |
| Square | Access token and a Payment Links notification subscription …/api/v1/payments/webhooks/square — paste the exact notification URL and its signature key. |
Secrets are either encrypted at rest (AES-256-GCM via ENCRYPTION_KEY) or kept entirely outside the database as environment-variable references (env_ref) — see .env.production.example.
Money never touches Filux
Checkout happens on the provider's hosted page (Filux renders no card fields, and the CSP stays default-src 'self'), the provider holds the funds, and the provider settles to the seller's own bank account on their own schedule. There is no payout, withdrawal, or transfer capability anywhere in the payment interface — the absence of code, not a permission check, is the guarantee. The only money operation Filux can perform is reversing an existing charge back to the buyer's original card when the seller clicks Refund.
Instance admins (the host operator) see only a global kill switch and aggregate counts (GET/PUT /api/v1/admin/settings/store) — never any seller's listings, buyers, credentials, or revenue detail.
Residual risk, stated honestly
The Filux instance admin is normally the host operator and holds root on the box; no in-app design defeats that. What this design does guarantee is that Filux never holds funds, never moves money, and that a database or backup compromise alone does not yield live payment credentials or the ability to mint fake paid orders beyond what the provider's own signature scheme permits.
Roadmap
Delivery phases and dependency notes are documented in Requirements.md. Epics 1-8, 10 and 11 are complete: Authentication & Identity, Directory Architecture, Granular Sharing, Responsive UI, Administration & User Management, Password Lifecycle, Bulk Upload & Archive Management, Reverse Share, Internationalization, and Store (Paid Downloads). Epic 9 (WebDAV) is next.
License
Open source, self-hosted.