- TypeScript 79.4%
- HTML 10%
- CSS 6.7%
- JavaScript 2.2%
- Shell 1.3%
- Other 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| bin | ||
| deploy | ||
| src | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| deno.json | ||
| deno.lock | ||
| docker-compose.example.yml | ||
| Dockerfile | ||
| README.md | ||
| SPEC.md | ||
files-web-ui
This is the source code for my personal file manager: a small, self-hosted web interface for the files on my own server.
I used to run Nextcloud for this. Nextcloud is impressive software and it is very good at being everything for everyone — groupware, office, calendars, sync clients, an app store. That is exactly why it stopped being the right tool for me: I am one person who mostly wants to browse, upload, and edit the files in my home directory over HTTP. For that, Nextcloud asks me to run and maintain a PHP application server, a database, a job scheduler and an app ecosystem, and to give all of that resources that the actual job — serving some files — does not really need.
So I wrote a smaller thing instead. It covers the parts I actually used:
- log in, browse folders as a list or a grid
- upload files, create text files and folders, edit text files online
- image thumbnails and a viewer, markdown preview
- download, and delete as a soft delete with a trash you can restore from
- share folders with other users on the server, as read or edit access, backed by POSIX ACLs so the web UI and Samba see the same permissions
- a small JSON API so my phone can reach the same files
Your files stay on the disk where they are. There is no database holding your data — SQLite only stores accounts and sessions, the trash keeps its metadata in a sidecar file next to the moved item, and thumbnails live in their own cache folder. The server is Deno with server-rendered templates and a little HTMX: no build step, no SPA, no framework lock-in.
Running it
deno task user:add <name>— create a userdeno task start— serve on :3000 (port, file root, upload limits etc. are env vars with sensible defaults; everything it writes lives indata/)deno task test— integration test suite
Design decisions and their reasoning are collected in SPEC.md.
Deployment
For a multi-user setup the app runs as a dedicated service account (default
filesweb) that owns everything under data/. Human users never own files;
their access — their own tree, and shares granted by other users — comes from
named POSIX ACL entries that the service manages itself, so it needs no root
privileges at runtime. getfacl shows the complete, auditable permission state
on disk.
sudo deploy/setup.sh— one-time: create the service account, prepare the app directorysudo deploy/migrate-fs.sh— one-time: migrate existing data (chown to the service account, setgid dirs, default ACLs per user tree)sudo deploy/provision-user.sh <name>— create a user (system account + file tree + database entry); replacesdeno task user:addon the serverdeploy/files-web.service— sample systemd unit
If the files are exposed over Samba, the share must use inherit owner = yes
and inherit acls = yes, so files created by clients stay owned by the service
account and keep the ACL inheritance intact.
Known corners of this model: files created directly on the server (SSH/shell)
are owned by that user — the app can still read and write them via group access,
but it cannot manage their ACLs. A restrictive chmod clips the ACL mask and
locks the app out of that file until it is chmod'd back. Per-user disk quotas
are not possible in this model.
Docker
The image installs the acl package and runs as UID/GID 1000, so the bind
mount matches a normal host user and the app can manage ACLs on it. Everything
the app writes (SQLite db, files, thumbnails, trash) lives under data/, which
must be owned by UID 1000.
docker build -t files-web-ui .docker run -d --name files-web -p 3000:3000 -v ./data:/app/data -e COOKIE_SECURE=1 -e ACL_UID_MAP="alice:60101,bob:60102" files-web-ui- or with Compose:
cp docker-compose.example.yml docker-compose.yml, adjust it, thendocker compose up -d --build - create users with
docker compose exec files-web deno task user:add <name>(interactive password prompt) - config comes from environment variables (
docker run -e/ composeenvironment:); they take precedence over a mounted.env. Behind a reverse proxy setCOOKIE_SECURE=1
To do
- sharing files ability
- copy and move files
- button to show/hide hidden files
- close button for success messages
- have view mode (list/grid) on a per folder basis
- line break mode switch in text editor
- in grid mode display name size etc. for sorting
- collaborative editing [later / separate project]
NOTE ON AI USE
While I do use AI to write code I decide on the architecture, design of the software, design of the website, how it works and what it should do. I also decide which dependencies and tools to use.