backstory: basically ran blirox files thru a tunnel for like a bit, but running thru a tunnel aka cloudflared makes it very slow, or slow at startup then speeds up, but if your gonna host a file hosting service probably should have around a 1Gbps connection, now this can be fully hosted on a VPS, which is reccomended but not, because storage limits, but you can connect a NAS to this and it should work fine :) anyway, for any help you can join the blirox.cc discord, and check out other services @ https://blirox.cc -> https://discord.gg/dQY9ySFmJH
using this project please credit me or my github, that would be so amazing if you did <3
well this is the same build used in my production build, so nothing really changes, except the secrets arent on the github build, you need that yourself anything in here was completely made by me :) ; and contributors, if they exist
- what you get
- how it works
- the stack
- quick start (local dev)
- going live
- configuration
- the developer API
- security model
- moderation + the legal part
- project layout
- contributing
- license
uploading + sharing
so with this, it chunks uploads, and has limits from 15GB/file in any accounts, and resumes around 64mb chunks (dont quote me on this) sharing;
- can share stuff like encrypted folders, it decrypts within the browser
- folders, galleries, and per-folder collabs
- image and video previews
end-to-end encryption
- encrypted folders; files are encrypted in the browser with a passphrase before they ever leave your machine (AES-GCM, PBKDF2-SHA256, 600k iterations)
- the server only ever sees ciphertext for those
accounts + access
- invite-only registration with a full accountability chain (every account records who vouched for it by username that signed up)
- google sign-in (optional), TOTP 2FA, and a proper session model
- per-account quotas with deliberate overcommit [google sign in requires you to make a google cloud project i think? idk i forgot >.<]
running it sanely
- a shared egress budget so one big download can't saturate your home uplink (this was entirely tested with 750Mbps, behind CGNAT)
- a hard "refuse to boot if the uploads drive isn't mounted" safety check
- an admin panel: users, invites, moderation queue, audit log, egress charts, appearance/branding
- a developer API (v1) with an OpenAPI spec, served from its own hostname [https://api.blirox.cc -> same api as in here]
abuse handling — see moderation. legality is probably something you should read for your own sake
it's one Next.js app. there's no separate backend [though if your smart enough, host a node of it], no microservices and no queue
┌─────────────────────────────────────────┐
browser ───────► │ Next.js app (next start -p 4001) │
│ │
│ app/ pages + route handlers │
│ lib/ all the actual logic │
│ SQLite ◄──── metadata, users, audit │
└───────┬───────────────────────────────────┘
│ reads/writes
▼
┌───────────────────────────────┐
│ the uploads drive │
│ blobs/ finished files │
│ staging/ in-flight chunks │
│ thumbs/ derived previews │
│ quarantine/ held for review │
│ db/ the SQLite file │
└───────────────────────────────┘
a few ideas do most of the work:
everything lives on one disk, tracked in SQLite. file bytes go in blobs/,
metadata goes in a SQLite db on the same drive. no s3, no external db. lib/db.ts
owns the schema; lib/storage.ts owns the bytes.
uploads are chunked because cloudflare caps request bodies at 100mb. the
client (components/Uploader.tsx) slices the file, PUTs each chunk, and the
server reassembles + SHA-256s the result on complete. this is also why the app
is happy behind a tunnel with a small body limit.
-one note is like IDK if cloudflared still has this limit, i assumed they did when making this, so dont quote me on this either- :D
downloads share an egress budget, they aren't capped per-download. on a home
connection bandwidth is the real constraint, not disk. lib/egress.ts hands out
a shared budget so one person alone gets the whole pipe, and eight split it —
instead of the naive "cap each download and let concurrency multiply it" that
gets both cases wrong. there's a per-IP daily cap on top
file bytes are served from their own hostname (us01.example.com), split
from the app host (files.example.com). two reasons: you can move file serving
off cloudflare later without breaking share links already out (cloudflare's ToS
§2.8 frowns on bulk non-HTML through the CDN), and user content gets served with
a locked-down sandbox CSP so it can never run as the app.
the developer API rides the same process on a third hostname
(api.example.com). Next rewrites (next.config.js) route that host to
/api/v1/* and the docs page so there is no duplicate server.
if you read one file to understand the shape of things, read lib/config.ts. it's the central config and the comments there explain most of the tradeoffs . . .
- Next.js 14 (app router) + React 18 + TypeScript
- better-sqlite3 — synchronous, single-file, plenty fast for this
- sharp — image re-encode + thumbnails (also how EXIF gets stripped)
- tailwind for styling, lucide-react for icons
- crypto is the platform's own Web Crypto / node
crypto— no roll-your-own - runs on node 18+
git clone [https://github.com/piinkmonth/bliroxfiles](https://github.com/piinkmonth/bliroxfiles.git)
cd blirox-files
npm install
npm run devthat's it. it comes up on http://localhost:4001 against a throwaway database in
.devdata/, with the mounted-drive safety check bypassed for you. nothing's
reachable from outside and nothing you do here touches a real drive.
make yourself an account (there's no open signup):
node scripts/create-admin.mjs putyourusernamehereit'll prompt for a password. sign in, and you're an admin; invites, moderation, appearance, all of it instantly
the full walkthrough — mounting the drive, env, the systemd unit, cloudflare tunnel routes, google sign-in, malware scanning, the stuff that'll bite you in the ass is in SETUP.md. read it in order; every step needs sudo so none of it happens by accident.
if you outgrow the cloudflare tunnel (the 100mb body cap, the HTTP/2
single-connection problem, or ToS §2.8), deploy/VPS-SETUP.md
walks through moving file serving onto a cheap VPS reverse-proxy over tailscale
without breaking a single share link. the nginx config it uses lives in
deploy/ and its comments explain why it's HTTP/1.1-only and how the
X-Forwarded-For handling keeps the rate limiter honest.
the short version:
- mount a drive, set
BLIROX_STORAGE_ROOTto a path on it cp .env.example .env.productionand fill it innpm run build- point three hostnames at the app (
files,us01,api) - run it under systemd with
RequiresMountsForso it won't start before the drive mounts node scripts/create-admin.mjs youand go
everything is env vars, all prefixed BLIROX_. copy .env.example
and fill it in — it's got a comment on every one. the ones that matter most:
| var | what it does |
|---|---|
BLIROX_STORAGE_ROOT |
where uploads live. must be on the mounted drive — the app refuses to boot if this lands on the same fs as / |
BLIROX_PUBLIC_ORIGIN |
the app's public url, used to build share links |
BLIROX_CDN_ORIGIN / BLIROX_CDN_HOSTS |
the hostname file bytes are served from |
BLIROX_API_HOST / BLIROX_API_ORIGIN |
the dev api + docs hostname |
BLIROX_DEFAULT_QUOTA_GB |
per-account space (overcommitted on purpose) |
BLIROX_MAX_FILE_GB |
biggest single upload |
BLIROX_CHUNK_MB |
upload chunk size — keep under cloudflare's 100mb cap |
BLIROX_EGRESS_BUDGET_KBPS |
the throttle that matters. total download bandwidth across everything at once. set it from your measured upstream |
BLIROX_DOWNLOAD_KBPS |
per-connection ceiling (not a fixed rate) |
BLIROX_ENCRYPTION_KEY |
encrypts stored IP addresses at rest. back this up — lose it and existing encrypted values are gone |
BLIROX_GOOGLE_CLIENT_ID / _SECRET |
google sign-in (optional) |
BLIROX_CLAMAV_ENABLED / BLIROX_VIRUSTOTAL_KEY |
malware scanning (optional) |
there's a versioned REST API at /v1/*, served from api.example.com (or
files.example.com/api/v1/* — same handlers). authenticate with a token you
generate in settings → API tokens.
GET /v1/account— who am i, quota, usageGET /v1/files/GET /v1/files/:id— list + inspectPOST /v1/files— one-shot upload (under the ~90mb one-shot cap)POST /v1/uploads+ chunk endpoints — the chunked flow for big filesGET /v1/files/:id/content— download bytes- folders CRUD under
/v1/folders
the full spec is generated as OpenAPI — hit /openapi.json on the api host, or
the human docs at the api host root. the generator lives in
lib/apispec.ts.
the short tour (details are in the code comments, which is where they belong):
- invite-only. well yes
- end-to-end encryption for encrypted folders — AES-GCM with a PBKDF2-SHA256-derived key (600k iters), done in the browser, the server stores ciphertext and never sees the passphrase.
- 2FA via TOTP, and google oauth with a deliberate no-adopt-by-email rule (see SETUP.md for why matching on a self-asserted email would be a takeover vector).
- stored IPs are encrypted at rest with
BLIROX_ENCRYPTION_KEY. - user content is sandboxed. everything served from the byte host gets
Content-Security-Policy: default-src 'none'; sandboxandnosniff, and?inline=1only ever honours a strict raster/media allowlist — never SVG, HTML, or PDF. so an upload can't turn into a page running on your domain. - CSP, HSTS, frame-ancestors 'none', and friends on the app itself — see next.config.js.
- rate limiting + a per-IP daily egress cap, both keyed on an IP the proxy layer is careful not to let clients forge (the nginx comments in deploy/ are load-bearing here).
- CSRF via origin checks +
sameSitecookies.
none of this is exotic. it's mostly about being careful with the boring parts because if you dont read it ur cooked bruh
if you run a file host that other people can upload to, you will eventually host something you have a legal obligation to deal with. this ships with the tooling to handle that, but the tooling tracks your responsibilities — it doesn't discharge them.
when reporting is done on a file for CSAM, it is taken very seriously, so just know that usually a report shows up in the panel, you DO have to review it yourself unless you have a algorithm to auto-review
if you're a US operator, read the "safety obligations" section of SETUP.md before you go live. 18 U.S.C. § 2258A makes reporting apparent CSAM to NCMEC mandatory once you have actual knowledge — and a report sitting unactioned in your moderation queue counts as knowledge. register as an ESP with NCMEC before you need to, not mid-incident.
app/ pages + API route handlers (Next app router)
api/ all the server endpoints
dl/ the byte-serving download route (sandboxed)
v1/ the developer API
...
dashboard/ the logged-in file/folder UI
admin/ moderation, users, invites, audit, appearance
f/ g/ public file + gallery share pages
lib/ the actual logic — config, db, storage, crypto, egress,
uploads, moderation, auth, api, ...
components/ shared react components (uploader, media player, nav, ...)
scripts/ create-admin, the prod start wrapper
deploy/ nginx / cloudflared / sysctl templates + the VPS guide
SETUP.md the going-live walkthrough
.env.example every config var, documented # MAKE SURE TO CHANGE THIS TO .env.production WHEN YOU FINISH IT
https://github.com/piinkmonth/bliroxfiles/tree/main#GPL-3.0-1-ov-file