Repository navigation
v0.53.0
filex v0.53.0
Self-hosted file manager - Go single binary + multi-framework frontend.
Download a binary below, or pull a Docker image:
docker pull ghcr.io/brf-tech/filex:slim-v0.53.0
docker pull ghcr.io/brf-tech/filex:full-v0.53.0What changed
Added
- The App store inside filex (#162). The navigation panel's Apps → App
store opens filex's own page over a trusted store's catalog - read and
verified on the server from the store's signed index, its icons through
filex, no frame of the store and no change to the Content-Security-Policy;
a catalog is kept ten minutes and served marked stale while the store is
down; the desktop app shows the same screen under the same rule, in a
window of its own. A person asks for an app with a reason and follows
My requests;
the request lands on Install requests beside an API key's. Approving it
asks the store for a fresh install link and opens the store review -
the same SHA-256, permission and license steps as any install link - and
that install closes the request. Admin → Plugins → Apps → Store screen
turns it on, picks the stores and who sees it (everyone, built-in roles or
groups), per tenant; Trusted stores → Connect binds this filex to a
store with the one-time code its "My instances" page makes (an ed25519 key
sealed withFILEX_SECRET_KEY, every request signed with a timestamp and a
single-use nonce). Reading a link, trusting, connecting and installing stay
the platform operator's, signed in to the panel
(APP-PLUGINS.md → The store screen). - The whole test chain runs on GitHub, split into parts that run side by
side.ci.ymlis a matrix on every push ofmain: the Go suite in the
shards ofscripts/test-shards.jsonbeside PostgreSQL, MySQL, Redis and
Samba and again under-race, the migrations on three engines and through
the CLI (up, three steps down, up), the unit suites in UTC and on
Istanbul's clock, the typechecks and the docs gates, Cypress, Playwright on
Chromium, Firefox and WebKit in parts with the Document Server specs again
with one, the store and S3 lines, both images, and oneAll tests (full)
job that is green only when every part was. Every part is named by
scripts/ci-parts.mjs, and the release gate reads the run part by part: a
red part is named at once, and a run without a part is no full matrix.
A pull request runs the same less-race, Firefox, WebKit and the Document
Server, and a push is never cancelled by the next one
(CONTRIBUTING.md → Release process). - A tag run promotes what the dry run of its commit built. The dry run of
an untagged version is the release candidate: it pushes both images by
digest with no tag and keeps every desktop row's files with their sums, and
the tag run checks each image on its own architecture (built from the
tag's commit,filex --versionnaming it) and each file byte for byte
before it tags or publishes anything - the images in minutes instead of a
second build. The release workflow no longer runs the test suite itself:
the push run of the commit is the test,verifyasks that it was the full
matrix, and the dry run packages at once.-f macos=falsestarts a dry run
without the macOS row while GitHub has no macOS runner. - The release can be packaged off GitHub.
node scripts/release/package-local.mjs X.Y.Z [--run [--publish]]runs the
release workflow's packaging on the tag's tree from a maintainer's machine -
goreleaser and the GitHub Release, both images for amd64 and arm64, the
Windows and Linux desktop packages - and says what it leaves to GitHub
(macOS, the arm64 snap) and to a person (npm, the stores)
(CONTRIBUTING.md → When GitHub Actions is
down). - The Playwright and Go suites run in parts, side by side.
node e2e/run.mjs local --shard i/Nruns part i of N with Playwright's own
split - taken after--grepand the engines, so the parts add up to the
whole run - each part on a server, port, output directory and server log of
its own. The Go tests have one shard list,scripts/test-shards.json, for
every runner:internal/api/handlersandinternal/wasmpluginare cut by
test file into exact-rungroups, the other packages run as package lists,
and a test file or package the list does not name runs in a rest group.
node scripts/test-shards.mjs checkholds the list togo list ./...and
go test -list,e2e-checkholds the parts to the whole run, and
rebalancere-cuts a package from the times a run measured
(CONTRIBUTING.md → Shards). - Comments are an API key's own permission -
comments, at Read
(read) or Read and write (comments:rw), the first permission a key
holds at a level. Adding and deleting a comment askcomments:rwon every
door -/api/files/comments,/api/ai/commentsand the MCP
file_comment_add/file_comment_deletetools - through one check in the
handler they all run; reading them asksread. A key is minted with it on
both API keys screens (Comments: Read / Read and write, buttons, not a
list), raised or lowered later with Edit on Admin → API / MCP, the
row's Comments: allow writing on the API keys panel, or
PATCH /api/tokens/{id}/PATCH /api/admin/ai-tokens/{id}
{"permissions": {"comments": "rw"}}- the verbs never change - and every
key list answers each key's levels inpermissions. A key never gives a key
a level above its own (403 token_ceiling). The rule for every such
permission added from now on is in the code and the docs: each declares its
default,read- existing keys get it with no migration - or none for a
super-administrator kind, andtokenperm_test.gois red for one that does
not (RBAC.md → Permissions with a
level,
CONTRIBUTING.md → Adding a permission). - Multi-tenant mode is a switch: Admin → Multi-tenant mode (System →
Customization), the platform operator's alone - a tenant's administrator is
refused the page and its API (403 supertenant_only), an API key the change
(403 session_required).FILEX_MULTI_TENANTor the config file's
multi_tenant, when set, pin the mode and the switch shows it locked with
the variable to change instead; unset, the switch decides, off until somebody
turns it on. A change takes effect when filex is restarted and the page says
so until then (the mode is handed to the route groups, the sign-in providers
and the SFTP, FTPS, NFS, WebDAV and S3 servers once, at start). Turning the
mode off while the install has tenants asks for their number first: they go
into maintenance mode, nothing is deleted, and turning it back on brings
every tenant back as it was. Both directions are in the audit log
(tenancy.enable,tenancy.disable)./api/capabilitiessays
multi_tenant(the mode in force) on every install, and every screen follows
it.GET/PUT /api/admin/tenancy. (MULTI-TENANCY.md → Mode
gating, ADMIN-PANEL.md) - The notification digest - one notification instead of a flood, if you
want it. Optional and off out of the box: every kind of notification is
still told at once, so an upgrade changes nobody's notifications. A kind
turned off - by an administrator for everybody in their tenant (Admin →
Notifications, which also sets the window, 1-15 minutes, default 1), or by
a person for themselves (user settings → Notifications, an Urgent switch
beside every kind and one for all the administrator alerts) - is held for
the window and told in ONE notification that says, folder by folder, what
changed - "Rapor: 30 files added; Fotoğraflar: 3 files moved to the trash, 1
comment" - so a folder that receives 30 files in a minute is one badge step,
one browser pop-up and one desktop toast, not 30. Every event still keeps
its own row the moment it happens (the history, the admin list, the audit
log and every webhook are unchanged); a held row is in the person's list,
read, until the digest carries it. A digest names and counts only what the
person's own bell shows (tenant and grants), survives a restart (the window
is in the database) and is never written twice. A file-request owner who
holds those notices gets one email per window too. Webhooks can subscribe to the new
notification.digestevent; only a target that ticks it receives it. API:
urgent_overridesanddigeston/api/notifications/settings,
GET/PATCH /api/admin/notifications/digest. Migration 00087
(NOTIFICATIONS.md → The digest). - A search for the whole admin panel (task #168). The top bar's search
button opened the file search page, and a phone had no search at all. Now one
search, beside the menu - Ctrl+K / ⌘K (the explorer's palette key, as
you have it bound), and a button with a layer over the window on a phone -
finds a page and its tabs, a single setting (Trash retention, Require
two-factor authentication), an installed app and what it does, a person, a
group, an API key (by its name, never its value), a storage or a share, by
its name in the interface's language or in English and by its synonyms
(LDAP finds Identity providers), with Turkish letters and capitals
folded. Files come on demand: the first three and a Search files row, or
file:for files alone;user:,app:,key:,group:,storage:and
setting:keep one kind. You find only what you may open: the pages are the
menu's own, and every record comes from the list its page reads, through the
same permission - a delegated administrator holdingadmin.usersfinds
people and groups, a tenant's administrator their own tenant's. Your recent
searches are kept on the server (the newest 20, migration 00090), so they
follow you to another browser; remove one or all of them, and they are never
in the audit log. New:GET /api/admin/panel-search,GET·POST·
DELETE /api/admin/panel-search/recent,DELETE …/recent/{id}, and
?stats=noneonGET /api/admin/storages; core exportsPanelSearch, its
rules andfoldText(ADMIN-PANEL.md → Search). - CircleCI stands in for GitHub Actions at the release gate.
.circleci/config.ymlruns, on every push ofmain, the Go suite in the
shards ofscripts/test-shards.jsonbeside PostgreSQL and MySQL (and red
unless both engines really ran), the web and package unit suites in UTC,
the desktop unit tests and the Playwright suite in Chromium, in parts. When
Actions is down,pnpm release X.Y.Z --resume --gate circlecireads that
workflow on the export commit instead ofci.ymland the release dry run,
runs here the heavy suites CircleCI does not, and records which CI passed
the commit. Once Actions is back, the tag run publishes that commit: its
verifytakes the green CircleCI workflow when GitHub lacks its full
matrix and dry run, and with no dry run to promote the tag run builds the
images and every desktop package itself (the public repository needs a
CIRCLECI_TOKENsecret for it).
node scripts/test-shards.mjs go --node i/Ngives copy i of a CI job its
shard, and refuses a copy count that is not the list's
(CONTRIBUTING.md → When GitHub Actions is
down). - The language packs keep up with
mainbetween releases.
node scripts/langpacks.mjs statusnames, for every language pack checkout,
the strings it does not translate yet, the ones whose English changed since
the pack was translated (whichpack.mjs syncused to keep without a word)
and the ones filex dropped;todowrites the translator's worklists, and
applychecks every answer against the language pack validator and the
fixed names (filex, ONLYOFFICE) before it writes anything, then runs the
pack's own sync, build and validators and commits locally.release X.Y.Z
is the whole release-day step of a pack: the release's catalogue, one patch
version up, the README's status block, the validators, a commit, and the
signed tag and push commands printed, not run
(CONTRIBUTING.md → Translations and language packs). - The language packs are translated every night, by an agent, on the
build host. A timer of its own (scripts/chain/install-langpacks.sh
installs it, the driver's copy and the pack checkouts) starts
scripts/langpacks-nightly.mjs run, which waits for the nightly test run
to end, fetchesorigin/maininto a worktree of its own and, when a pack
lacks something, gives each pack's worklist to a Claude Code session that
can only read and edit its own directory of copies (no command, web or MCP
tool, none of the host user's settings, a HOME of its own) with the
project's Claude account asked from the work server at every run; only the
answers are taken from it, refused answers or a red validator go back to it
once,apply --commitcommits each pack on the build host, and one
notification says per language what was translated and what is left.
Nothing is pushed or tagged there: on release daynode scripts/langpacks.mjs pullfast-forwards a maintainer's packs to theirnightlyremote,
releaserefuses a pack not pulled yet and prints the push of the release
commit back. Worklist items now carry the answer right after the key, and
every worklist's rules ask for the language's own letters and one term per
concept
(CONTRIBUTING.md → Translations and language packs). - The release train's tools (
scripts/train/), for the maintainers:
pnpm train X.Y.Zwrites the day's release note (the train rule, read from
CONTRIBUTING; the 10:00 cut;mainsince the last tag up to the cut; what
came after it; the merge queue; the closing "every task to Done");
pnpm merge-queue --queue <note>merges a train's branches one after another
with--no-ffand their own messages, mergesCHANGELOG.mdconflicts by
Keep a Changelog section, leaves[Unreleased]with each heading once, stops
for a person on any other conflict and builds and vets the Go module after
every merge;bash scripts/train/filex-ship.sh X.Y.Ztakes a green tag run
to everything read back in one command - backup, the trusted-proxy check,
the deploy instance by instance with an automatic rollback, both update
feeds and the CDN purge, the Releases page, docs.filex.sh, npm and the
embeds, thenpnpm release X.Y.Z --resume --only deploy- with a log per
step; andwhen-done.mjsruns a long command and wakes whoever waits for it
when it ends. Hosts and keys are settings (scripts/train/train.env.example),
never in the repository. (CONTRIBUTING.md → The release train's
tools) - A
.csvopens with filex on the desktop too (#151). Open with filex
handles eleven types now: the ten office ones and.csv, registered the
same way on every system (Windows' "Open with" list, the Microsoft Store
package,text/csvin the Linux desktop entry andxdg-mime, macOS with
rank Alternate), never taking the type over. With ONLYOFFICE on the server
it opens in the spreadsheet editor, and a save as CSV - which the server
writes back in the file's own dialect, the text of every cell nobody changed
kept - goes over the.csvas any save goes over its document; a save that
comes back as a spreadsheet goes beside it as<name>.xlsx
(DESKTOP.md → Opening documents from your computer).
This release has more to it than fits on one page. The rest of the
entry - and every earlier release - is in CHANGELOG.md.
Verify: sha256sum -c checksums.txt
- Documentation - https://docs.filex.sh
- Report a bug - https://github.com/BRF-Tech/filex/issues
- Full changelog - https://github.com/BRF-Tech/filex/blob/main/CHANGELOG.md
- Every release - https://github.com/BRF-Tech/filex/releases