Skip to content

v0.56.0

Choose a tag to compare

@github-actions github-actions released this 11 Oct 12:07
· 35 commits to main since this release
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.0

What 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, a viewer view and files:read) is granted the
    derived permission files: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; no blob: 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
    and job.submit answer unavailable; 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 from FILEX_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 takes expect and
    answers the version's modified
    (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-plaintext that 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 (core plaintextOpenOf); 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, expect and
    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.set in an encrypted session goes to a store of its own,
    appStateE2e in 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 same files: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's file.read and writes the
    app's file.save as the vault's next generation, under the vault's write
    lock, only over the version the app read (failed / changed otherwise; 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.dirty count 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's state answer now carries words) 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.set now 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's FileInfo.plaintext comes beside encrypted: "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, a viewer view and files:read) is granted the derived
    permission files:co-edit - the review says it in its own words, it is
    never written into permissions - and edits a file with other people: the
    bridge's coedit.* 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 with through is 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 under FILEX_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's reason and
    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 granted files: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 says app_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's expect: 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, then helm install filex brf-tech/filex; the same
    chart is at oci://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, and helm search repo brf-tech/filex --versions shows
    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 by internal/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's engine_ext still named
    LibreOffice as the office engine: both corrected.
  • pnpm release builds its two images from the release commit, in
    minutes
    (#165). The pretag's image gates stream git archive of the
    commit into docker 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 .dockerignore leaves 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 --failed once 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. The ci stage 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 -race shards 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.json keeps the median seconds of every test file and
    package from the -race jobs of five GitHub runs (0.53-0.55);
    node scripts/test-shards.mjs rebalance --write cuts the handlers and
    wasmplugin units from it, times re-measures from a run's race-*
    artifacts, and estimate says what each shard will take. Still 14 -race
    jobs: wasmplugin runs in three instead of four (Go (wasmplugin-3-4) is
    now Go (wasmplugin-3)), and a new auth shard (the auth packages, and
    the SFTP, FTP and NFS servers) takes what db-io (37 minutes) and rest
    (41) had too much of. Estimated on GitHub's runner: 26-30 minutes a shard,
    against 22-61 before. web/tests/deploy/testShards.test.ts is 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.sh has a helm step, 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's appVersion, for at most SHIP_HELM_WAIT minutes
    (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