Guard HOME for set -u in the rescue paths, surface a failed rescue mv instead of silently dropping the transcript, and never delete the party dir after a failed save in either close or clean (close now refuses to delete and exits nonzero on a failed rescue too, matching clean, since a message promising the transcript survived followed by rm -rf was a false assurance). Also stop log --stop from announcing a stop that never started, and sync README's knock/log/write(1) wording with the actual behavior. |
||
|---|---|---|
| tests | ||
| tools | ||
| .gitignore | ||
| deploy.sh | ||
| Makefile | ||
| party | ||
| party.1 | ||
| README.md | ||
| ROADMAP.md | ||
| tmux-party.gif | ||
tmux-party
Share a live tmux session with your buddies on a wide range of UNICES!
tmux-party ships party, a POSIX shell utility. One person hosts a party, and others join the fun! Wanna pair-program, mentor, debug an outage together, or demo a workflow? ssh in, party join & off you go!
Built for small, mutually trusted groups: a hacklab, a tech team, a circle of friends, not strangers across the internet. Inspired by epic sessions running GNU's screen -x way back when, now on BSD's tmux modern codebase!
Requirements
- POSIX shell (
/bin/sh) tmux≥ 3.3 (forserver-access) on$PATH, or named viaPARTY_TMUX- A shared system group. A party is gated on the first of
staff,users,wheel,partythe host belongs to (--group <name>orTMUX_PARTY_GROUPalways win). Boxes where people already share one of those groups need no setup; creating a dedicatedpartygroup is the fallback recipe. Every host and guest must be a member of whichever group ends up gating the party. write(1), optional: used to ping invited guests and knocking guests.party invite's ping is best-effort, silently skipped when absent;party knockdepends on it and reports a delivery failure to the caller.
Install
make install # installs to /usr/local
make install PREFIX=$HOME/.local # or any prefix you prefer
Installs party to $(PREFIX)/bin and party.1 to $(PREFIX)/share/man/man1; man party is the full reference. make uninstall removes both.
On illumos/Solaris the default prefix is /opt/party and the install needs root (pfexec make install) for the system bindir and /etc/default/login. The Makefile is plain POSIX make (works with /usr/bin/make, BSD make, GNU make).
Quick start
One-time setup, as root. Create the group and add members:
groupadd party
usermod -aG party alice
usermod -aG party bob
# alice and bob log out and back in so the new group takes effect
Host a party, then invite from inside it:
party host standup # spawn a dedicated party tmux server
party invite alice # add alice to the allowlist (read/write)
party invite bob -r # invite bob as a read-only watcher
Guests join:
party list # see what's running on this host
party join standup # attach (auto-attaches if only one party is running)
party leave # detach and clean up
The party group is the partyline: filesystem access to party sockets is shared among members, and tmux server-access decides who is actually let into a given session. No shared install directory is needed: each party keeps its own state under its own per-party private directory. Parties you haven't been invited to yet show up in party list as invite-only; party join on one tells you which host to ask (exit status 13).
How it works
party host spawns a dedicated tmux server for the party (separate from your personal tmux) so inviting someone exposes only the party, not your other sessions. Each party gets a private directory at ${PARTY_SOCKET_DIR}/party-${USER}:${PARTY_NAME}.d/, holding the tmux socket and a join/leave notification helper. Guests never type the path; party join reads the roster and handles the attach.
Two gates protect every party, and both must pass:
- Filesystem gate: the per-party directory is
chgrp'd toTMUX_PARTY_GROUPat mode0750; the socket isg+rw. Non-members can't traverse in or reach the socket. - Auth gate:
tmux server-accessallowlist, editable only by the host. A user who clears the FS gate but isn't on the allowlist is rejected at the protocol layer.
Party metadata (host, server pid, group, creation time) lives in a roster file beside the socket. It's bookkeeping only, as security-relevant fields (host user, socket path) are re-derived from the directory on every read, never trusted from the file.
Configuration
| Variable | Default | Purpose |
|---|---|---|
TMUX_PARTY_GROUP |
auto-selected | Shared group for socket access. When unset, party host auto-selects (see PARTY_GROUP_CANDIDATES); pin an existing group here, or pass --group <name> to party host. |
PARTY_GROUP_CANDIDATES |
staff users wheel party |
Space-separated candidate list party host walks when TMUX_PARTY_GROUP is unset; gates on the first the host belongs to. |
PARTY_SOCKET_DIR |
/tmp |
Where each party's private directory (socket + roster) is created. No whitespace in the path. |
PARTY_TMUX |
tmux |
tmux binary to use. Override if tmux ≥ 3.3 lives at a non-standard path. |
Support
The mechanism is identical everywhere: group ownership and mode bits plus tmux server-access. No ACL syscalls involved, so the trust path is one piece of code, not a per-OS matrix.
| Platform | Status |
|---|---|
| Linux | supported, validated on Debian 13 & Alpine v3.21 |
| FreeBSD UFS or ZFS | supported, validated on FreeBSD 15.0 ZFS |
| illumos LX-branded zones | supported, validated on 4.4 BrandZ linux |
| illumos native, Solaris | supported, validated on omnios-r151058 |
| NetBSD, DragonFly | unconfirmed, should work, tests welcome |
| OpenBSD | supported, validated on OpenBSD 7.8 |
| macOS | supported, validated on Darwin 25.5.0 |
Security
party assumes you already know and trust everyone you add to the group; it is not a public access-control system. Both gates above protect each party, and the auth gate (tmux server-access) is authoritative, with the filesystem gate as defense in depth.
The threat model is accidents, not adversaries. party runs where everyone already shares the box, so the ownership and symlink gates in front of destructive operations (close's directory removal, moderation) are there so a stale roster, a typo, or two racing party host invocations can't remove or hijack the wrong thing: that the same checks would also stop a malicious group member is a side effect, not the design goal. New checks should earn their place by preventing a plausible mistake, not by closing a hypothetical attack from a friend.
Three honest caveats, with the full detail in man party:
- On ACL-enabled filesystems (ZFS, HFS+/APFS), inherited ACLs can override the mode bits, so the FS gate is best-effort. The auth gate still holds.
- A party's existence is not hidden the way attaching is. That confidentiality rides on the FS gate.
- Active guests share one tmux server, where any write-capable invitee is trusted by design. Invite read-only (
-r) if you do not trust that far. party log's transcript is group-readable, not invitee-readable: any member of the party's group can read it, including group members who were never invited.
Status
party is pre-release. The on-disk layout (per-party directory, roster format, socket path) may change between commits, with no upgrade migration, so close running parties before pulling. A stale roster pointing at an unreadable socket simply won't be discovered; old tmux servers left running must be killed by hand.
Reference
| Subcommand | Effect |
|---|---|
party host [name] |
Spawn a dedicated party tmux server. Only the host is on the allowlist. Names: letters, digits, _, -; max 63 chars. |
party invite alice [-r] |
Add alice to the allowlist. -r / --read-only invites as a watcher. |
party voice alice |
Promote alice to read/write. --party <name> selects among several hosted parties. |
party mute alice |
Demote alice to read-only. --party <name> selects among several hosted parties. |
party kick alice |
Revoke alice's invite, disconnect her, kill her guest session. --party <name> selects among several hosted parties. |
party detach alice |
Disconnect alice; keep her on the allowlist. --party <name> selects among several hosted parties. |
party list |
List live parties on this host. |
party who [--short] |
Show invited and attached users for the current party. |
party status |
Show the caller's own state: hosting, attached, or idle. |
party close |
Tear down the party server and its roster entry. Host-only. |
party clean |
Remove the caller's own dead party dirs: crash leftovers whose tmux server is gone. Rescues a non-empty transcript first. |
party log [--stop] [--party <name>] |
Record the party into its private dir via tmux pipe-pane (host only), group-readable. --stop ends it. |
party join [name] [--passive] |
Join a party. Auto-attaches when one is running; picker otherwise. --passive attaches read-only to the host's view (watcher mode). Read-only invitees always join passive. |
party leave |
Detach and clean up the per-guest session. |
party knock [name] |
Ask the host of an invite-only party for an invite (write(1) ping with the exact invite command). |
party role [active|passive|switch] |
Flip your clients between guest and host session. No arg prints the current role. |
party --help |
Help text. |
Credits
Written by veg and kol3rby in the context of the UNIX Social Club. Issues and contributions welcome.
