Repository navigation
v0.56.0
filex v0.56.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.56.0
docker pull ghcr.io/brf-tech/filex:full-v0.56.0What changed
Added
-
An app edits a file of an end-to-end encrypted folder (#189). An app
whose manifest says"encrypted_folders": {"open": true}(filex 0.56; it
needs an interface, aviewerview andfiles:read) is granted the
derived permissionfiles:e2e-plaintext, which the install review lists
in its own words - the app sees every such file opened with it, the server
still holds only encrypted data - and which is never written into
permissions; an update that starts asking is a new grant, and a module
app's describe must say the same. With the folder unlocked, Open and
Open with offer such an app beside filex's own viewer (the Default
apps rule for the kind applies; never ONLYOFFICE, never an app without
the grant). The browser does both halves, the CryptPad way: the app's read
door answers the ciphertext (?e2e=1), the explorer decrypts it and
transfers the plaintext to the app's sandboxed frame (FileInfo.plaintext
says so; noblob:address is made of it), and encrypts what the app
saves before sending it to the save door (?e2e=1), which writes a new
version only over the file as it was opened (expect;412 changed
otherwise) and refuses a body that is not an encrypted file (422 not_encrypted). While the frame holds such a file, save-as,engine.call
andjob.submitanswerunavailable; Lock closes the app. In a vault
too (its own entry below). Not for a.fxe, at most 200 MiB a file
(LIMITS.maxEncryptedBytes). The administrator's switch
apps.e2e_plaintext(on; seeded once fromFILEX_APPS_E2E_PLAINTEXT;
capabilities.apps_e2e_plaintext) turns it off for every app; every
handover and save is an audit row (app_plugin.e2e_read,
app_plugin.e2e_save). An app's save over a file also takesexpectand
answers the version'smodified
(E2E-ENCRYPTION.md → Editing in an app,
APP-PLUGINS-API.md). -
In a folder whose names are encrypted too, the browser picks the app
(#189). At level 2 the server stores each name as ciphertext and cannot
tell the kind of a file, so an app was not offered there. Now the explorer
decrypts the name and chooses the app by its extension - the views granted
files:e2e-plaintextthat apply to it, the administrator's rule for the
kind, the person's choice, filex's own viewer last - with one rule every
surface uses (coreplaintextOpenOf); neither the name nor its extension
is ever sent, and Choose an app… offers no "Always use this app" for
such a file (that choice is kept on the account). The app doors do not ask
a kind of a stored name that is ciphertext - a save there is held to the
grant, the encrypted folder, the person's write right,expectand
ciphertext only - and keep holding every name they can read to its kind
(E2E-ENCRYPTION.md → The kind is the browser's). -
What an app keeps while it edits an encrypted file is sealed (#189).
state.setin an encrypted session goes to a store of its own,
appStateE2ein the account's preferences: one ledger per account, app and
folder, encrypted in the browser with a key derived from the folder key
(HKDF, the signed-in account and the app's name in the context and in the
associated data, so a member's ledger copied into another member's
preferences does not open), so a "setting" can no longer carry plaintext to
the server. It is apart from the app's ordinary store; the same limits hold,
measured on what is stored (the server enforces them on the sealed store
too,413 APP_STATE_TOO_LARGE); a locked folder reads as an empty store
(E2E-ENCRYPTION.md → The app's own store, sealed). -
An app edits a file of a vault (#189, encryption level 3). The same
app, granted the samefiles:e2e-plaintext, is offered a file of a vault
the person has opened and edits it there - the vault's own way, all in the
browser: the explorer picks the app by the name it read from the vault's
encrypted index (the server never learns a vault's names), its vault engine
reads the file from the packs for the app'sfile.readand writes the
app'sfile.saveas the vault's next generation, under the vault's write
lock, only over the version the app read (failed/changedotherwise; a
file that went meanwhile is not made again). One writer at a time: a save
while somebody else writes the vault is refused in the server's words
(failed/vault_locked), and one whose write lock ended before the
commit writes nothing and says why and what was not saved (failed/
vault_lock_lost). When the vault locks - Lock, or its own lock after
15 idle minutes - the app closes with it and the person is told which file
lost the changes the app had not saved; the app's reads, saves and
ui.dirtycount as activity in the vault. A minute before the vault would
lock itself while the app holds unsaved changes, filex warns above the app
in the server's words (the vault'sstateanswer now carrieswords) and
offers Keep it open. The app's own store there is the sealed one, keyed
from the vault's own key and the account; where the page cannot tell which
account it is signed in as,state.setnow says so instead of calling the
folder locked (folders too). No server door is used and no
endpoint was added: the server sees packs and an index, never the file's
name, its path or that an app opened it, and its app doors keep refusing a
vault (403 encrypted, whose sentence now says where a vault's file is
opened). The SDK'sFileInfo.plaintextcomes besideencrypted: "vault"
too (E2E-ENCRYPTION.md → In a vault,
APP-PLUGINS-API.md). -
Editing a file together, through filex's relay (#189). An app whose
manifest says"co_edit": {"open": true}(filex 0.56; it needs an
interface, aviewerview andfiles:read) is granted the derived
permissionfiles:co-edit- the review says it in its own words, it is
never written intopermissions- and edits a file with other people: the
bridge'scoedit.*methods 0.55 defined now answer. filex's page seals what the
app appends with the session's key, the relay (internal/coedit) puts the
sealed entries in one order without reading them, and every member's page
reads the whole log through the reader that refuses one the writers did
not write; the app never holds a key or opens a connection. A conflict is
filex's to retry, the changes lease goes to one writer at a time and only
to one that has seen every change, a member's cursor reaches the others
unkept, a save withthroughis recorded in the log, a member whose page
went away is dropped after 45 s, and a session keeps its unsaved changes
for the next person who opens the file (30 days). Who joins and whether
they may write are the server's answer (two people behind one proxy token
are two members, each request carries the member's token, rights are read
again while a member is in). The relay's state is in the database
(migration 00107, compare-and-set per session: several processes on one
database serve one session), its blobs underFILEX_COEDIT_DIR. In an
ordinary folder the session's key is the server's, kept sealed with
FILEX_SECRET_KEY(without it the app edits alone); in an encrypted
folder, see the next entry. With the
office editor app's 0.3.0, editing office documents - alone or together -
needs no ONLYOFFICE Document Server; one you have stays supported and
optional (filex's office thumbnails and server-side office conversions
still use it). New:FILEX_COEDIT,FILEX_COEDIT_DIR,
FILEX_COEDIT_MAX_MEMBERS,FILEX_COEDIT_MAX_LOG_MB,
FILEX_COEDIT_MAX_BLOB_MB,FILEX_COEDIT_UNSAVED_DAYS; the SDK's
CoEditHello.created,coedit.dropped'sreasonand
fx.save(data, {through})
(APP-PLUGINS-API.md → Editing together,
CONFIGURATION.md → Editing together). -
Editing together in an encrypted folder, watching, and nothing lost to a
dropped connection (#189). A file of an end-to-end encrypted folder is
edited together by an app also grantedfiles:e2e-plaintext: the member
that starts the session makes its key in the page and gives the server
only that key sealed with the folder key (POST …/coedit/{sid}/key,
once); a member that joins (e2e: true) is handed it as it is
(sealed_key) and opens it with the folder key - the server opens neither
the key nor anything of the session; not in a vault, not a.fxe. A
person who may only read the file joins as a watcher (canEdit: false): every change reaches them live, the relay refuses whatever they
would write, and the audit saysapp_plugin.coedit_watch. Every change is
kept in the browser's own IndexedDB until the relay placed it (sealed with
the folder key in an encrypted folder) and a request that got no answer is
sent again: what a lost connection, a closed tab or a crash left is sent
when the file is opened again - at once when nobody wrote since, otherwise
after the person's answer to a question above the editor in the server's
words (unsent_words, English and Turkish); a tab still open keeps its own
(Web Locks), so none is sent twice. Cursors reach the members connected to
every filex process: each member's last sealed cursor is a row (migration
00109,coedit_cursors), written at most twice a second, read with the
log. A save in a session is held to the file the session holds (its base
or its last save, by any member) instead of the page'sexpect: two
members' saves no longer refuse each other, and a version written outside
the session meanwhile is refused (412 changed) in an ordinary folder too
(APP-PLUGINS-API.md → Editing together,
E2E-ENCRYPTION.md → Editing in an app). -
The Helm chart installs from a Helm repository.
helm repo add brf-tech https://helm.brf.sh, thenhelm install filex brf-tech/filex; the same
chart is atoci://ghcr.io/brf-tech/charts/filex. Every release's chart is
published there within ten minutes of the release, from 0.55.0 on, by
BRF-Tech/helm-charts;--version
pins a chart version, andhelm search repo brf-tech/filex --versionsshows
which filex release each one installs. The chart's source stays in
deploy/helm/filex
(INSTALLATION.md → Kubernetes (Helm)).
Changed
- A storage plugin build signed over its SHA-256 alone is still accepted:
plugins and apps get a compatibility window. 0.55 said this old form would
be refused from 0.56; it is not. A storage plugin or an app built for a
release keeps working, unchanged, for at least 6 minor releases or 12 months
after it, whichever is longer: an old form it sends (a signature format, a
manifest field, a host function, a bridge call) stays accepted with a warning
through that window, and is refused only after the CHANGELOG of an earlier
release has announced it. The old signature form
(storage-build-signature-sha256) is accepted at least until filex 0.61 and
2027-10-09, whichever comes later; the plugin's row and its log name that
end (PLUGINS.md → Compatibility window,
CONTRIBUTING.md → Compatibility window for plugins and apps). - The co-editing relay prototype (
internal/e2eoffice, in memory) is
replaced byinternal/coedit, which keeps the same rules in the database
(#189). - The README and the first-steps guides are shorter, and start with the
short path (#218; a reader on r/selfhosted called the documentation
"extremely verbose and lengthy"). The README is about 6,200 words instead of
20,270: each tour part keeps its short version and its screenshots, and the
long text and the full feature list are one entry per feature on the new
FEATURES.md; its five translations follow the new
structure. CONFIGURATION.md,
DESKTOP.md,
PROTOCOLS.md,
ONLYOFFICE.md and
APP-PLUGINS.md open with what to do first; "since
0.xx" stories leave the guides (the release that made a change is in this
file) and the reasons and measurements behind them are in
CONTRIBUTING.md → Background notes from the user guides.
The arm64 table moved to INSTALLATION.md.
API.md's version note said 0.54, PLUGIN-KIT.md'sengine_extstill named
LibreOffice as the office engine: both corrected. pnpm releasebuilds its two images from the release commit, in
minutes (#165). The pretag's image gates streamgit archiveof the
commit intodocker build -instead of building.: on a workstation whose
docker reads the checkout through WSL's 9P bridge, BuildKit's walk of it
(.git, every worktree, the build output) took 30-52 minutes per image at
0.54.0 and 0.55.0, and 0.55.0's four pretag runs 4.5 hours. The gate first
checks that.dockerignoreleaves out no tracked file the Dockerfile copies
(BuildKit applies none to a tar on stdin), the images start beside the UI
build instead of after the binary, and each is taken from the gate cache
while what it is made of is unchanged and its image is still under its
tag. The pretag goes from 66-74 minutes to about 12-15.- The release gate re-runs a part red on the runner, once, by itself, and
asks GitHub again when it does not answer (#165). A part whose every
failed step only sets the machine up (GitHub's setup, a checkout, a
toolchain, an artifact, the browser install through apt) is re-run with
gh run rerun --failedonce the run has finished; a red test never is. A
5xx, a timeout or a dropped connection is asked again at the next poll
instead of stopping the gate. Thecistage waits for the tag's run to
finish before it reads what it published, and the export's workflow-guard
gate is taken from the gate cache on an unchanged tree and workflows
(CONTRIBUTING.md → Release process). - The
-raceshards are cut from measured times, and none is expected to
take over 35 minutes (#174). The longest one is how long CI's full run
takes, and that run is the release's land → green gate (50 minutes). 0.53's
split was an estimate: by 0.55 the handlers' rest shard had taken every new
test file (244 → 437 tests) and ran 53-57 minutes, the run 71-75.
scripts/test-times.jsonkeeps the median seconds of every test file and
package from the-racejobs of five GitHub runs (0.53-0.55);
node scripts/test-shards.mjs rebalance --writecuts the handlers and
wasmplugin units from it,timesre-measures from a run'srace-*
artifacts, andestimatesays what each shard will take. Still 14-race
jobs: wasmplugin runs in three instead of four (Go (wasmplugin-3-4)is
nowGo (wasmplugin-3)), and a newauthshard (the auth packages, and
the SFTP, FTP and NFS servers) takes whatdb-io(37 minutes) andrest
(41) had too much of. Estimated on GitHub's runner: 26-30 minutes a shard,
against 22-61 before.web/tests/deploy/testShards.test.tsis red when a
shard is estimated past 35 minutes with the tree as it is, so the rest
shard's growth shows before a release waits for it. The build host's chain
reads the same list, with GitHub's seconds scaled to the host
(CONTRIBUTING.md → Shards). - The ship publishes the Helm chart itself and waits for it (#247).
filex-ship.shhas ahelmstep, waiting only for the preflight (a red
Releases page does not hold it back): it starts the Helm charts
repository's publishing (SHIP_HELM_CMD, e.g.
gh workflow run publish.yml -R BRF-Tech/helm-charts) and reads the
repository's index (SHIP_HELM_INDEX) every 30 seconds until it lists the
chart at the release'sappVersion, for at mostSHIP_HELM_WAITminutes
(15), red after that. An index that already lists the release is green
without starting anything. That repository publishes on a ten-minute
schedule, which GitHub had not started for hours, so a release could sit
without its chart and nobody saw it. Without the settings the step is left
to a person and listed at the end
(CONTRIBUTING.md → The ship).
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