Skip to content

v0.24.0

Choose a tag to compare

@github-actions github-actions released this 22 Aug 05:12
· 308 commits to main since this release

filex v0.24.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.24.0
docker pull ghcr.io/brf-tech/filex:full-v0.24.0

What changed

Added

  • Transfers run in parallel. The engine walked its plan one action at a
    time, so a tree of small files was priced at one full round-trip each —
    measured on a live deployment, 2 GB of ~400 KB files crawled at 0.24 MB/s
    with the network mostly idle. Uploads and downloads now run on a small
    worker pool (default 4, --transfers to tune, 1 restores the serial
    engine); directory creation stays first and deletes/conflicts stay serial
    in the planner's careful order.

Fixed

  • An interrupted first run RESUMES instead of conflicting every finished
    file.
    With no baseline, "present on both sides" always meant a conflict —
    so a restart mid-first-run turned every already-downloaded file into a
    "(remote copy)" duplicate; on the tree above that would have been ~7,800 of
    them. Downloads now stamp the server's own mtime on the local copy, and
    twins with no history that match by (size, mtime) are adopted silently.

  • Changing the mirror root keeps each pair's history. Migration used to
    remove and re-add the pair, throwing the baseline away — and the first-run
    merge that followed conflicted every file the machine had ever uploaded.
    New filex sync move <id> <path> repoints a pair and keeps its baseline;
    the desktop root change uses it, and stops the account's watcher first so
    a mid-round rename can never read as a mass delete.

  • The remote walk lists eight folders at a time. One round-trip per
    folder made the inventory the slow phase of a big sync: measured behind a
    CDN proxy (~0.35s per request), a 3,328-folder invoice tree took ~19
    minutes to list serially — twice per run, since the settle pass walks
    again. The walk is breadth-first by level now, eight listings in flight,
    with the snapshot merge kept single-threaded; the same tree lists in a
    couple of minutes.

  • A filename containing ".." is a filename, not a traversal. Eleven API
    guards rejected any path CONTAINING the substring — so a real invoice
    named "… Tic. Sic. Gaz..pdf" could be stored but never previewed,
    downloaded or synced (400 "bad path" everywhere). Traversal needs a whole
    ../ segment, and that is what every guard now checks — the same rule
    sync add already applied.

  • No-history adoption tolerates coarse mtimes (±2s). FAT stores mtimes
    in 2-second steps, and any tool that stamps times through float seconds
    can land a millisecond off — measured: a repair pass one millisecond short
    turned 1,667 identical files into conflict pairs. Change detection against
    a baseline stays exact; only the adopt rule for twins with no history is
    tolerant, rsync's modify-window logic.

  • A half-dead connection can no longer freeze a sync forever. All the
    CLI's parallel streams ride one HTTP/2 connection; when a CDN proxy killed
    it silently mid-first-sync, every stream blocked — for good, since Go's
    http2 sends no health pings by default and the client had no transport
    limits at all. The client now pings an idle connection (ReadIdleTimeout
    30s), bounds dialing, TLS and response headers, and leaves bodies
    unbounded — a big transfer may take long, a hang may not.

  • A folder that could not be listed is not a folder that is gone. The
    remote walk skipped ANY failed sub-listing as "vanished" — and it now runs
    eight listings wide through proxies the client expects to die under it.
    One 502 on a subtree would have read as "folder removed on the server"
    and binned the local copy of everything below it; in the settle pass it
    would have dropped the subtree from the baseline instead, and every
    uploaded file in it would have come back as a conflict pair next round.
    Only a 404 is skipped now; any other listing error fails the run, names
    the folder, and touches nothing.

  • A pair cannot be pointed at a path that is not there. sync move
    accepted a non-existent path; the next run would have created it empty
    under a surviving baseline, and an empty mirror with history means "every
    file deleted here" — carried to the server. The path must exist and be
    the pair's kind: a folder for a folder pair, a file for a file pair.

  • A missing mirror with history refuses to run. The engine created a
    missing sync folder and carried on, which turned an unplugged drive or a
    folder moved by hand into a mass delete on the server. A pair whose
    folder is gone while its baseline still remembers files now stops and
    says what to do — sync move if it moved, plug the drive back in, sync remove to stop syncing it. A pair that never synced a file still gets
    its folder created: there is nothing to lose.

  • Moving the filex folder to another drive keeps modification times.
    The cross-device copy stamped every file "now", and change detection is
    (size, mtime) — so the very history sync move preserves would have read
    as every file edited here, and re-uploaded the whole tree. The copy now
    preserves timestamps. If a move fails halfway the pair follows whichever
    side holds the COMPLETE tree (a finished copy with a half-failed cleanup
    points at the new place; a failed copy discards its partial litter), and
    unpairs as the last resort — an unpaired folder syncs nothing and deletes
    nothing.

  • A replaced watcher's late exit no longer unhooks its successor.
    Stopping an account's watcher for the root move and starting a new one
    could race: the old process's exit handler deleted the supervisor's entry
    for the NEW process, so the next reconcile started a second watcher for
    the same account — two engines over one baseline.

  • The ".." guard also reads Windows paths. Segments are split on both
    separators, and a segment made only of dots and spaces is refused
    (Windows trims ".. " to "..").

Full changelog entry