Fade gives Linux directories a time-to-live.
Most filesystems treat time as metadata, not policy. A file created for a temporary job can sit on disk for years unless an application, cron job, or human remembers to remove it. Fade moves that cleanup decision closer to the storage boundary: files receive a time-to-live, and the filesystem enforces that lifetime.
Status: v0.1.0 is a developer preview. Policy mode now supports ordered TOML rules. Do not use Fade for important or irreplaceable data.
Implemented in the current phase 1 slice:
- Rust CLI crate with
fade mount,fade ls,fade status,fade gc, andfade version. - Developer-mode FUSE mount with TTL folders such as
1m,1h,24h,7d,30d, andforever. - SQLite metadata for path, TTL, expiry time, recovery deadline, lifecycle state, policy source, and observed size.
- Expiry checks for lookup, stat, open, read, write, rename, unlink, and normal directory listings.
- Background reaper plus manual
fade gcfor physically deleting expired files after the recovery window. - Unit coverage for duration parsing, path safety, TTL assignment, metadata lifecycle transitions, record rename/recreate behavior, and reaper deletion.
- End-to-end FUSE lifecycle coverage for expiry, garbage collection, and remount persistence.
- Startup reconciliation for interrupted create, rename, and delete operations.
- Policy-mode mounts with ordered TOML glob rules.
fade checkpolicy previews without mounting.- Recovery of expired files during the configured recovery window.
- JSON Lines audit events for file creation, expiry, recovery, and deletion.
Not implemented yet:
- Mountpoint discovery for inspection commands. For now
fade ls,fade status, andfade gcoperate on the Fade backing directory.
Requirements:
- Linux.
- Rust 1.95 or newer.
- FUSE runtime support, including
fusermount3orfusermount.
Install Fade for the current user:
./install.shThe script installs fade in ~/.local/bin. Add that directory to PATH if
you need to. To remove Fade, run:
./install.sh --uninstallUse FADE_PREFIX to install in another prefix:
FADE_PREFIX=/opt/fade ./install.shRequirements:
- Linux.
- Rust 1.95 or newer.
- FUSE runtime support for mounting, including
/dev/fuseandfusermount3orfusermount.
Build and test:
cargo test
cargo test --no-default-features
cargo build
./tests/fuse_lifecycle.sh # requires /dev/fuseRun a developer-mode mount:
mkdir -p /tmp/fade-data /tmp/fade-mnt
target/debug/fade mount /tmp/fade-data /tmp/fade-mnt --mode devThe mount command stays in the foreground. In another terminal:
printf 'hello\n' > /tmp/fade-mnt/1m/hello.txt
cat /tmp/fade-mnt/1m/hello.txt
target/debug/fade ls /tmp/fade-data
target/debug/fade status /tmp/fade-data
target/debug/fade gc /tmp/fade-dataUnmount when finished:
fusermount3 -u /tmp/fade-mntTemporary files become permanent by accident.
Build artifacts, exported reports, test fixtures, generated archives, and local scratch data often start with an obvious lifetime. The problem is that the lifetime is rarely attached to the file itself. Cleanup gets implemented later as a script, a scheduled job, or a runbook. Those approaches work until they are skipped, misconfigured, or forgotten.
Fade makes expiry part of the filesystem experience:
- New files get a TTL when they are created.
- Expired files are no longer visible or readable through the mounted filesystem.
- A background reaper removes expired bytes after the configured recovery window.
- Operators can inspect what is alive, expired, recoverable, and pending deletion.
The goal is a small filesystem primitive for local data that belongs on disk for a known amount of time. Fade does not replace a secret manager, object storage lifecycle policy, backup policy, or compliance program.
Linux already has good cleanup tools. systemd-tmpfiles removes old files when
a cleanup pass runs, and tmpfs drops an entire in-memory filesystem when it is
unmounted. Use either when those semantics are enough.
Fade is for the narrower case where a path must stop resolving on the next Fade-mediated access after its TTL expires, even if physical deletion happens later. Applications keep using normal file operations; the retention rule lives at the mount boundary.
Fade has two layers of expiry.
- Access enforcement: once a file expires, Fade returns
ENOENTfor normal filesystem operations through the mount. From the calling process's point of view, the file is gone. - Physical cleanup: a reaper process deletes expired backing files after the recovery window has passed, reclaiming disk space without a separate cleanup job.
This split gives applications immediate expiry semantics while still allowing a short, explicit recovery path for mistakes.
Fade checks metadata and backing files before each mount.
- Fade marks a tracked file as deleted when its backing file is missing.
- Fade completes a recorded rename when the destination exists.
- Fade cancels a recorded rename when the source still exists.
- Fade refuses to mount when it finds an untracked backing file.
- Fade refuses to mount when a recorded rename has an ambiguous disk state.
Fade does not import unknown files. It cannot know their original TTL. Remove or move the file before you mount Fade again.
The lifecycle is:
created -> alive -> expired -> recoverable -> deleted
The recovery window is configurable. Strict deployments can set it to 0 so
expired files are eligible for deletion immediately.
Developer mode uses TTL folders. The folder name is the policy.
fade mount ./fade-data ./mnt --mode dev
cp report.json ./mnt/24h/
cp cache.tar ./mnt/7d/
cp notes.txt ./mnt/forever/Files created under 24h/ live for 24 hours. Files created under 7d/ live for
7 days. No application code needs to pass flags or call a Fade API.
Policy mode uses a config file. It is intended for services and shared environments where expiry should be controlled centrally.
[[rules]]
pattern = "*.token"
ttl = "1h"
[[rules]]
pattern = "*.key"
ttl = "7d"
[[rules]]
pattern = "session_*"
ttl = "30m"
[[rules]]
pattern = "*"
ttl = "24h"
[reaper]
interval = "60s"
recovery_window = "1h"Fade evaluates rules in order and assigns the first matching TTL when a file is
created. The final rule must be *, so every file receives a TTL. Reaper
settings in the config override the built-in defaults; mount flags override the
config.
Start a policy-mode mount with:
fade mount ./fade-data ./mnt --mode policy --config fade.tomlPreview a rule assignment without mounting:
fade check --config fade.toml --path reports/session.tokenThe implemented inspection commands expose expiry without requiring operators to inspect the SQLite store directly:
fade ls ./fade-data
fade status ./fade-data
fade gc ./fade-dataMountpoint discovery is planned.
fade mount <backing-dir> <mountpoint> starts a developer-mode mount. Use
--mode policy --config <file> for TOML rules. Use --recovery-window <duration> to set the physical deletion delay. Use --reaper-interval <duration> to set the reaper interval. Durations use s, m, h, or d.
Use 0 for no recovery delay.
fade ls <backing-dir> shows tracked files. fade status <backing-dir> shows
counts and the last reaper result. fade gc <backing-dir> runs one reaper pass.
Use --json with any inspection command for machine-readable output.
fade check --config <file> --path <relative-file> previews the first matching
policy rule without mounting or changing metadata. Use --json for
machine-readable output.
fade recover <backing-dir> <relative-file> --to <output> exports an expired
file before its recovery deadline. The original remains expired and Fade refuses
to overwrite an existing output. Successful recoveries are appended to
.fade/audit.jsonl.
fade version prints the installed version.
Fade is intentionally narrow at first:
- Local scratch directories that should clean themselves.
- Temporary exports and reports.
- Build and CI workspaces whose output has a known maximum age.
- Generated test data that is safe to recreate.
The first public release should win these cases before expanding into service policy or security-sensitive data. Fade can expire files inside its mount. It cannot delete copies from logs, backups, snapshots, shell history, editor swap files, or external systems.
Fade is not a secret manager. Tools like Vault solve authentication, authorization, dynamic secrets, revocation, and audit workflows that Fade does not attempt to own.
Fade is not a backup system. Expiry and recovery windows are short-lived operational features, not durable restore guarantees.
Fade is not a legal compliance product by itself. It can help enforce retention inside a mounted path, but real compliance also depends on application behavior, backups, snapshots, access controls, audit retention, and organizational policy.
Fade is not secure erase on every storage device. On SSDs, copy-on-write filesystems, journaled filesystems, and snapshotting environments, overwriting or deleting a file does not necessarily remove every physical copy.
Use regular files and directories in a Fade mount. Fade hides symlinks and does
not support hard links, ownership changes, permission changes, or timestamp
changes. Do not rely on mmap, fsync, or path-level truncate behavior.
Data that the kernel or an application already cached can outlive expiry. Fade blocks later path lookups and open-handle reads and writes after expiry.
If mounting reports a missing FUSE device, load the fuse kernel module. Make
sure /dev/fuse exists and fusermount3 or fusermount is installed.
If startup reports an untracked backing file, move or remove that file from the backing directory. Fade refuses to assign a TTL without metadata.
If unmount fails, close programs that use the mount. Then run:
fusermount3 -u <mountpoint>Use fusermount -u <mountpoint> when your system provides FUSE 2.
- Expiry should be the default, not an afterthought.
- The filesystem should enforce policy at access time.
- Production behavior should be observable and testable before rollout.
- Recovery should be explicit, time-bounded, and easy to disable.
- The tool should be honest about what it can and cannot guarantee.
- Application code should not need to learn a new storage API.
Fade is implemented as a Linux FUSE filesystem written in Rust.
The mounted filesystem will store file contents in a backing directory and TTL metadata in SQLite. Every filesystem operation that resolves a path will check metadata before returning file data or attributes. A reaper loop will scan for expired records and delete eligible backing files.
High-level components:
- FUSE mount: path resolution, reads, writes, stats, renames, and directory listing behavior.
- Policy engine: TTL folder parsing and ordered config-rule matching.
- Metadata store: SQLite records for path, TTL, creation time, expiry time, recovery deadline, state, and backing object location.
- Reaper: periodic and manually triggered physical cleanup.
- CLI: mount, inspect, and garbage-collect operations.
Rust stack:
fuserfor FUSE integration.clapfor CLI parsing.rusqlitefor SQLite metadata.serdefor machine-readable output.tracingfor structured logs.thiserrorandanyhowfor error boundaries.
- Linux FUSE mount.
- TTL folders for developer mode.
- SQLite metadata.
- Expiry checks for lookup, stat, read, write, and directory listing.
- Background reaper.
fade ls,fade status, andfade gc.- Integration tests for basic file lifecycle behavior.
- TOML config rules.
fade checkdry run.- Config validation with clear error messages.
- Rule precedence tests.
fade recoverfor the existing recovery window.
- Structured audit log.
- Rename, symlink, hard link, and open-file semantics documented and tested.
- Packaging for common Linux distributions.
- Benchmarks against direct filesystem access.
These questions should be answered before claiming production readiness.
- How should Fade handle buffered I/O and
mmap, where data may already be in the kernel or process address space when the TTL expires? - Are hard links allowed, rejected, or represented as shared metadata?
- How are symlinks resolved, and can they escape the mount?
- What are the exact guarantees after crash, remount, and metadata recovery?
- Should file TTL be immutable after creation, or can policy changes shorten existing files?
- What is the safest default recovery window?
Fade is early. The best contributions right now are design review, Linux filesystem edge cases, test scenarios, and small implementation slices that keep the core behavior easy to reason about.
Project conventions for comments and commit messages live in CONTRIBUTING.md.