btl (Bottle)
btl (pronounced "bottle") is a lightweight background task manager that automatically replaces processes when you re-run the same command. Think of it as a smart, automatic process manager that keeps only the latest instance of each command running.
Why btl?
- Auto-replace: Running the same command again automatically kills the old process
- Zero configuration: No config files, no setup, just works
- Safe by default: Refuses to run as root, validates PIDs, isolates users
- Simple interface: Intuitive commands that feel natural
- Process isolation: Each user's processes are tracked separately
- Persistent state: Survives terminal sessions and restarts
Installation
From Source
Using Cargo
Usage
Background a Process
Example:
# Or
# Or
Auto-replace: If you run the same command again, btl automatically kills the old process and starts a new one. This is perfect for development workflows where you frequently restart servers.
List Running Processes
Shows all tracked background processes with their hash, PID, start time, command, and working directory.
Example output:
HASH PID STARTED COMMAND CWD
ba2f49187372 45123 2m ago npm run dev /Users/you/project
13fae122192f 45201 30s ago python -m http.server /Users/you/server
View Logs
Logs are stored in ~/.local/share/btl/logs/ and can be accessed even after the process terminates.
Kill a Process
The kill command attempts a graceful shutdown (SIGTERM) first, with a 2-second timeout before forcing (SIGKILL).
Clean Up
btl automatically cleans up stale entries, but you can manually clean if needed.
How It Works
Hash-Based Process Identification
btl generates a deterministic hash from your command and current working directory:
hash = SHA256(command + cwd)[:16]
This means:
- Same command in the same directory = same hash = auto-replace
- Same command in different directory = different hash = both run
- Different commands = different hashes = both run
Background Process Management
When you run btl command args:
- Hash Generation: Computes hash from command + working directory
- State Check: Loads state from
~/.local/share/btl/state.json - Auto-Replace: If hash exists and process is alive, kills it gracefully
- Process Spawn: Forks and creates new session with
setsid() - State Update: Records PID, UID, command, cwd, timestamp, log path
- Log Redirection: stdout/stderr redirected to
~/.local/share/btl/logs/<hash>.log
State Management
State is stored in ~/.local/share/btl/state.json:
Process Validation
btl validates processes by:
- Checking if PID exists via
kill(pid, 0) - Verifying the process belongs to the current user (UID match)
- Auto-cleaning stale entries on any state read
Safety Features
Root Protection
btl refuses to run as root to prevent accidental system-wide changes:
User Isolation
Each user's processes are tracked independently:
- State files use user-specific paths
- PID validation checks UID ownership
- Users cannot see or affect other users' processes
Graceful Shutdown
The kill operation tries to be kind:
- Send SIGTERM (graceful shutdown signal)
- Wait up to 2 seconds for process to exit
- If still running, send SIGKILL (force kill)
PID Reuse Protection
btl validates that a PID still belongs to the expected process:
- Checks process existence
- Verifies UID matches
- Removes stale entries automatically
Platform Support
Supported Platforms
- Linux: Full support (tested on Ubuntu, Debian, Arch)
- macOS: Full support (tested on macOS 11+)
- Unix-like: Should work on any POSIX-compliant system
Requirements
- Rust 1.70+ (for building from source)
- POSIX-compliant system with:
setsid()for session managementfork()for process spawning/procor similar for PID validation
Not Supported
- Windows: Native Windows is not supported due to Unix-specific process management APIs
Files and Directories
- State:
~/.local/share/btl/state.json- Process tracking state - Logs:
~/.local/share/btl/logs/<hash>.log- Per-process log files
These directories are created automatically on first run.
Common Use Cases
Development Servers
# Start dev server
# Make changes, restart server
Long-Running Jobs
# Start a long build
# Check if still running
# View progress
Multiple Environments
# Terminal 1 - Frontend
# Terminal 2 - Backend
# Both run simultaneously (different working directories)
Troubleshooting
Process Not Showing in List
If a process isn't listed, it may have:
- Exited immediately (check logs with
btl logs) - Failed to start (check command syntax)
- Been cleaned up (run
btl cleanto verify)
Can't Kill Process
If btl kill <hash> doesn't work:
- Process may have already exited
- Run
btl cleanto remove stale entries - Check logs for error messages
State Corruption
If state seems corrupted:
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE file for details.
Author
Luke Oliff (luke@lukeoliff.com)