← echo

Detached background tasks with tmux

Model downloads, fine-tunes, big rsyncs — anything that outlives an SSH session. park drops the command into a detached tmux session and tees its output to a log; parked tells you which ones are still going, finished, or died.

~/.local/bin/park — start a command detached, log it, auto-close on finish:

#!/usr/bin/env bash
# usage: park <session-name> <command...>
set -euo pipefail

if [ "$#" -lt 2 ]; then
    echo "usage: park <session-name> <command...>" >&2
    exit 1
fi

session="$1"; shift

# refuse to clobber a session that's already running
if tmux has-session -t "$session" 2>/dev/null; then
    echo "session '$session' already exists — attach with: tmux attach -t $session" >&2
    exit 1
fi

logdir="$HOME/.park"
mkdir -p "$logdir"
logfile="$logdir/${session}.log"

# -d = detached: tmux runs the command in the background and returns immediately
tmux new-session -d -s "$session" \
    bash -c '
        set -o pipefail
        logfile="$1"; shift
        {
            printf ">>> started: %s\n" "$(date)"
            printf ">>> cmd:"; printf " %q" "$@"; printf "\n\n"  # %q re-quotes each arg exactly
            "$@"
        } 2>&1 | tee "$logfile"
        code=${PIPESTATUS[0]}                                    # real exit code, not tee's
        {
            printf "\n>>> exit code: %s\n" "$code"
            printf ">>> finished: %s\n" "$(date)"
        } >> "$logfile"
    ' bash "$logfile" "$@"

echo "started '$session'  (log: $logfile)"
echo "check later:  tmux ls   |   tail -f $logfile   |   cat $logfile"

~/.local/bin/parked — summarize every task at a glance:

#!/usr/bin/env bash
# usage: parked [-l|--logs]   (-l also prints the last log line)
set -euo pipefail

logdir="$HOME/.park"

show_tail=0
case "${1:-}" in
    -l|--logs) show_tail=1 ;;
    "")        ;;
    *) echo "usage: parked [-l|--logs]" >&2; exit 1 ;;
esac

# colours only when writing to a real terminal
if [ -t 1 ]; then
    c_run=$(tput setaf 4); c_ok=$(tput setaf 2); c_fail=$(tput setaf 1)
    c_die=$(tput setaf 3); c_dim=$(tput setaf 8); c_off=$(tput sgr0); c_bold=$(tput bold)
else
    c_run=; c_ok=; c_fail=; c_die=; c_dim=; c_off=; c_bold=
fi

# a task is "running" if its tmux session still exists
active=""
if command -v tmux >/dev/null 2>&1; then
    active=$(tmux ls -F '#{session_name}' 2>/dev/null || true)
fi
is_active() { printf '%s\n' "$active" | grep -qxF -- "$1"; }

shopt -s nullglob
logs=("$logdir"/*.log)
if [ ${#logs[@]} -eq 0 ]; then
    echo "no park tasks found in $logdir"
    exit 0
fi

printf "%s%-20s %-9s %s%s\n" "$c_bold" "SESSION" "STATUS" "DETAIL" "$c_off"

for log in "${logs[@]}"; do
    name=$(basename "$log" .log)

    if is_active "$name"; then                                  # session alive → still running
        started=$(grep -m1 '^>>> started:' "$log" | sed 's/^>>> started: //' || true)
        printf "%-20s %s%-9s%s %s%s%s\n" \
            "$name" "$c_run" "running" "$c_off" "$c_dim" "since ${started:-?}" "$c_off"
    else
        code=$(grep '^>>> exit code:' "$log" | tail -1 | sed 's/^>>> exit code: //' || true)
        fin=$(grep  '^>>> finished:'  "$log" | tail -1 | sed 's/^>>> finished: //'  || true)
        if [ -z "$code" ]; then                                 # no exit code → killed / OOM / reboot
            printf "%-20s %s%-9s%s %s%s%s\n" \
                "$name" "$c_die" "died" "$c_off" "$c_dim" "no exit code — killed / reboot?" "$c_off"
        elif [ "$code" = "0" ]; then
            printf "%-20s %s%-9s%s %s%s%s\n" \
                "$name" "$c_ok" "ok" "$c_off" "$c_dim" "exit 0${fin:+, $fin}" "$c_off"
        else
            printf "%-20s %s%-9s%s %s%s%s\n" \
                "$name" "$c_fail" "FAILED" "$c_off" "$c_dim" "exit $code${fin:+, $fin}" "$c_off"
        fi
    fi

    if [ "$show_tail" -eq 1 ]; then
        last=$(tail -1 "$log" 2>/dev/null || true)
        printf "   %s└ %s%s\n" "$c_dim" "$last" "$c_off"
    fi
done

Drop both in ~/.local/bin, chmod +x, and go:

park dl-llama huggingface-cli download meta-llama/Llama-3.1-8B-Instruct --local-dir ./models/llama
parked               # running / ok / FAILED / died
parked -l            # same, plus each task's last log line
cat ~/.park/dl-llama.log

Quote any argument with a space; for pipes, &&, or redirection, put it in a script and run park job ./job.sh.