feat: party log records a group-readable transcript
tmux pipe-pane on every pane of the party session, appending into <party dir>/log (0640, party group) so any attendee can grab a copy. Start and stop are announced to everyone attached; close rescues a non-empty transcript to the host's home before removing the dir. Panes opened later are picked up by re-running party log (a pane_pipe check keeps already-piped panes single-piped, since tmux's pipe-pane -o toggles an existing pipe closed rather than skipping it). Raw output, escapes included. Close polls the transcript for size stability before rescuing it, since a cross-filesystem mv is copy+unlink and could otherwise drop bytes still draining from the pipe-pane writers.
This commit is contained in:
parent
a011e4b03c
commit
aea7ef58b6
5 changed files with 229 additions and 7 deletions
109
party
109
party
|
|
@ -50,6 +50,8 @@ Hosting:
|
|||
close [name] Tear down the party you host (name it when
|
||||
you host several).
|
||||
clean Remove your own dead party dirs (crash leftovers).
|
||||
log [--stop] [--party <name>]
|
||||
Record the party into its private dir (host only).
|
||||
|
||||
Joining:
|
||||
join [name] [--passive] Join a party. Without name: auto-pick or numbered prompt.
|
||||
|
|
@ -944,6 +946,31 @@ cmd_close() {
|
|||
# PARTY_SOCKET_DIR is caller-controlled env, not a privilege
|
||||
# boundary, so we don't canonicalize it.
|
||||
"$PARTY_TMUX" -S "$expected_sock" kill-server 2>/dev/null || true
|
||||
|
||||
# Rescue a transcript before the dir vanishes: losing the night's
|
||||
# log to a routine close is exactly the accident class the threat
|
||||
# model says to prevent. After kill-server the pipe-pane `cat`
|
||||
# children are exiting but may still be draining their last bytes,
|
||||
# so poll for two equal size samples (bounded, integer sleeps only)
|
||||
# before moving it. A same-filesystem mv is a rename and would keep
|
||||
# catching late writes regardless, but $HOME and PARTY_SOCKET_DIR
|
||||
# are commonly different filesystems (tmpfs /tmp vs a home
|
||||
# partition), where mv is copy+unlink and tail bytes written after
|
||||
# the copy started would be lost from the copy.
|
||||
if [ -s "$expected_dir/log" ]; then
|
||||
_cl_prev=-1
|
||||
for _cl_i in 1 2 3; do
|
||||
_cl_size=$(wc -c < "$expected_dir/log")
|
||||
[ "$_cl_size" -eq "$_cl_prev" ] && break
|
||||
_cl_prev="$_cl_size"
|
||||
sleep 1
|
||||
done
|
||||
_cl_dest="$HOME/party-$name-$(date -u '+%Y%m%d-%H%M%S').log"
|
||||
if mv "$expected_dir/log" "$_cl_dest" 2>/dev/null; then
|
||||
echo "party close: transcript saved to $_cl_dest"
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -rf "$expected_dir"
|
||||
echo "Party '$name' closed."
|
||||
}
|
||||
|
|
@ -984,6 +1011,87 @@ EOF
|
|||
done
|
||||
[ "$removed" -gt 0 ] || echo "nothing to clean."
|
||||
}
|
||||
|
||||
# Tell everyone in the room when recording starts or stops: a
|
||||
# transcript nobody knew about is the kind of surprise the social
|
||||
# design exists to avoid. Same fan-out shape as the .party-notify
|
||||
# helper, run inline because only the host (who is authorized by
|
||||
# definition) calls it.
|
||||
log_notify() {
|
||||
"$PARTY_TMUX" -S "$RR_SOCKET" list-clients -F '#{client_name}' 2>/dev/null \
|
||||
| while read -r _ln_c; do
|
||||
"$PARTY_TMUX" -S "$RR_SOCKET" display-message -c "$_ln_c" \
|
||||
"$USER $1 logging this party" 2>/dev/null || :
|
||||
done
|
||||
}
|
||||
|
||||
cmd_log() {
|
||||
stop=0 partyname=''
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--stop) stop=1; shift ;;
|
||||
--party)
|
||||
[ $# -ge 2 ] || { echo "party log: --party needs an argument" >&2; exit 2; }
|
||||
partyname="$2"; shift 2 ;;
|
||||
-h|--help)
|
||||
cat <<'EOF'
|
||||
Usage: party log [--stop] [--party <name>]
|
||||
Record the party (host only) into <party dir>/log, readable by every
|
||||
group member. Raw pane output, ANSI escapes included (read with
|
||||
less -R). Panes opened after logging starts are not captured; re-run
|
||||
`party log` to pick them up. Starting and stopping is announced to
|
||||
everyone attached. `party close` rescues a non-empty transcript to
|
||||
your home directory before removing the party dir.
|
||||
EOF
|
||||
exit 0 ;;
|
||||
-*) echo "party log: unknown flag '$1'" >&2; exit 2 ;;
|
||||
*)
|
||||
if [ -z "$partyname" ]; then partyname="$1"; shift
|
||||
else echo "party log: unexpected arg '$1'" >&2; exit 2; fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
resolve_authoritative_party "$partyname" || exit 1
|
||||
logfile="${RR_SOCKET%/sock}/log"
|
||||
|
||||
if [ "$stop" = 1 ]; then
|
||||
# pipe-pane with no command closes a pane's pipe; harmless on
|
||||
# panes that were never piped.
|
||||
"$PARTY_TMUX" -S "$RR_SOCKET" list-panes -s -t "$RR_PARTY_NAME" -F '#{pane_id}' \
|
||||
| while read -r _lg_p; do
|
||||
"$PARTY_TMUX" -S "$RR_SOCKET" pipe-pane -t "$_lg_p"
|
||||
done
|
||||
log_notify stopped
|
||||
echo "Logging stopped. Transcript: $logfile"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Create the file ahead of pipe-pane with the roster's perms
|
||||
# recipe (0640 + party group): every group member can grab a copy,
|
||||
# only the host writes. Append so resuming mid-party keeps earlier
|
||||
# segments.
|
||||
: >> "$logfile"
|
||||
chmod 0640 "$logfile"
|
||||
chgrp "${RR_GROUP:-$TMUX_PARTY_GROUP}" "$logfile"
|
||||
|
||||
# Filter to unpiped panes ourselves rather than trusting `-o` to
|
||||
# skip them: on tmux 3.5a, `-o` toggles an existing pipe closed
|
||||
# instead of leaving it alone, which would silently stop logging
|
||||
# every previously-piped pane on a second `party log` call. Reading
|
||||
# #{pane_pipe} first keeps re-running `party log` idempotent for
|
||||
# already-piped panes while still attaching new ones. The path is
|
||||
# single-quoted for the server-side sh: a quote in PARTY_SOCKET_DIR
|
||||
# is accepted breakage, same as the status-right widget.
|
||||
"$PARTY_TMUX" -S "$RR_SOCKET" list-panes -s -t "$RR_PARTY_NAME" -F '#{pane_pipe} #{pane_id}' \
|
||||
| while read -r _lg_piped _lg_p; do
|
||||
[ "$_lg_piped" = 1 ] && continue
|
||||
"$PARTY_TMUX" -S "$RR_SOCKET" pipe-pane -t "$_lg_p" "cat >> '$logfile'"
|
||||
done
|
||||
log_notify started
|
||||
echo "Logging to $logfile"
|
||||
}
|
||||
|
||||
cmd_join() {
|
||||
passive=0
|
||||
name=
|
||||
|
|
@ -1586,6 +1694,7 @@ dispatch() {
|
|||
host) cmd_host "$@" ;;
|
||||
close) cmd_close "$@" ;;
|
||||
clean) cmd_clean "$@" ;;
|
||||
log) cmd_log "$@" ;;
|
||||
join) cmd_join "$@" ;;
|
||||
leave) cmd_leave "$@" ;;
|
||||
knock) cmd_knock "$@" ;;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue