feat: auto-select the gating group; 'party' becomes the fallback

Bare 'party host' now walks staff, users, wheel, party and gates on
the first group the caller belongs to, announcing the pick. Boxes
where people already share a system group need no setup; the curated
'party' group remains the fallback and the explicit --group /
TMUX_PARTY_GROUP paths are unchanged. Caller membership is the only
probe (id -nG, portable everywhere); whether a guest is a member stays
an invite-time warning, where it can actually be answered.
This commit is contained in:
veg 2026-07-11 12:14:50 +00:00
parent 22606ea55d
commit 7770289d85
6 changed files with 165 additions and 22 deletions

66
party
View file

@ -16,9 +16,25 @@ PARTY_VERSION=0.1.0
# `env -i` invocations). Every cmd_* path references it, so guarantee it
# now via id -un rather than crashing later under set -eu.
: "${USER:=$(id -un)}"
: "${TMUX_PARTY_GROUP:=party}"
# Group default. Remember whether the caller supplied TMUX_PARTY_GROUP
# explicitly: cmd_host auto-selects a candidate group only when it was
# not (an empty value counts as unset; an empty group name gates
# nothing).
if [ -n "${TMUX_PARTY_GROUP:-}" ]; then
TMUX_PARTY_GROUP_EXPLICIT=1
else
TMUX_PARTY_GROUP_EXPLICIT=0
TMUX_PARTY_GROUP=party
fi
: "${PARTY_SOCKET_DIR:=/tmp}"
: "${PARTY_TMUX:=tmux}"
# Candidate groups for hosting when none was named: the admin
# convention ('staff', also every macOS user's primary group), the
# classic shared-users group, the BSD admin group, then the
# recommended custom group as the fallback. Caller membership is the
# only test; whether *guests* are members is checked where it can
# actually be answered, at invite time (cmd_invite warns).
: "${PARTY_GROUP_CANDIDATES:=staff users wheel party}"
# Usage
# =====
@ -110,10 +126,28 @@ user_exists() {
id -u "$1" >/dev/null 2>&1
}
# Boolean: is user $1 a member of group $2 (supplementary or primary)?
# id -nG is the one portable membership probe: no getent on macOS, no
# dscl anywhere else, and it answers primary-GID membership too.
user_in_group() {
id -nG "$1" 2>/dev/null | tr ' ' '\n' | grep -qx "$2"
}
# Boolean: is the named user in TMUX_PARTY_GROUP?
user_in_party_group() {
user="$1"
id -nG "$user" 2>/dev/null | tr ' ' '\n' | grep -qx "$TMUX_PARTY_GROUP"
user_in_group "$1" "$TMUX_PARTY_GROUP"
}
# Print the first PARTY_GROUP_CANDIDATES entry the caller belongs to;
# print nothing and return 1 when the caller is in none of them.
pick_host_group() {
for _pg_g in $PARTY_GROUP_CANDIDATES; do
if user_in_group "$USER" "$_pg_g"; then
printf '%s\n' "$_pg_g"
return 0
fi
done
return 1
}
# Soft assertion: caller is in TMUX_PARTY_GROUP. Fails loudly with
@ -132,6 +166,9 @@ One-time setup (as root), your OS may differ:
OpenBSD: groupadd $TMUX_PARTY_GROUP && usermod -G $TMUX_PARTY_GROUP $USER
illumos: groupadd $TMUX_PARTY_GROUP && usermod -G $TMUX_PARTY_GROUP $USER
Then log out and back in.
Or gate on any existing group you are already in:
party host --group <group>
EOF
exit 1
fi
@ -612,9 +649,10 @@ Usage: party host [name] [--group <name>]
name Party name (default: random).
--group <g> Group used to gate this party. The host must be a
member; only members can discover or join. Precedence:
--group flag > $TMUX_PARTY_GROUP env > default 'party'.
Pick any existing group (e.g. 'staff', 'users', 'wheel');
no install-time setup needed.
--group flag > $TMUX_PARTY_GROUP env > auto-selection:
the first of staff, users, wheel, party the host
belongs to (candidate list overridable via
$PARTY_GROUP_CANDIDATES).
EOF
exit 0 ;;
-*)
@ -626,14 +664,24 @@ EOF
esac
done
# Group precedence: --group flag > $TMUX_PARTY_GROUP env > default
# 'party' (set by the env-defaults block at the top of the file).
# The chosen group becomes the per-party perimeter: chgrp on the
# Group precedence: --group flag > $TMUX_PARTY_GROUP env >
# auto-selection over PARTY_GROUP_CANDIDATES ('party' is the last
# candidate, a fallback rather than a hardcoded default). The
# chosen group becomes the per-party perimeter: chgrp on the
# private dir, on the socket, and on the roster. No shared install
# directory, pick any existing group the host is in.
if [ -n "$group_arg" ]; then
TMUX_PARTY_GROUP="$group_arg"
export TMUX_PARTY_GROUP
elif [ "$TMUX_PARTY_GROUP_EXPLICIT" = 0 ]; then
auto_group=$(pick_host_group || true)
if [ -n "$auto_group" ]; then
TMUX_PARTY_GROUP="$auto_group"
export TMUX_PARTY_GROUP
echo "party host: gating on group '$TMUX_PARTY_GROUP' (auto-selected; --group overrides)."
fi
# In none of the candidates: keep the 'party' default so
# require_caller_in_group prints its remediation message.
fi
require_caller_in_group