party list already shows invite-only parties; knock closes the loop. The knocker can't run any tmux command against the server, so the channel is write(1), the same one invite uses toward guests, except here delivery failure is surfaced: the ping is the whole feature.
512 lines
12 KiB
Groff
512 lines
12 KiB
Groff
.\" party - share a tmux session with the people you already work with
|
|
.Dd July 11, 2026
|
|
.Dt PARTY 1
|
|
.Os
|
|
.Sh NAME
|
|
.Nm party
|
|
.Nd share a tmux session with the people you already work with
|
|
.Sh SYNOPSIS
|
|
.Nm
|
|
.Cm host
|
|
.Op Ar name
|
|
.Op Fl -group Ar group
|
|
.Nm
|
|
.Cm close
|
|
.Op Ar name
|
|
.Nm
|
|
.Cm join
|
|
.Op Ar name
|
|
.Op Fl -passive
|
|
.Nm
|
|
.Cm leave
|
|
.Nm
|
|
.Cm knock
|
|
.Op Ar name
|
|
.Nm
|
|
.Cm invite
|
|
.Ar user
|
|
.Op Fl r
|
|
.Op Fl -party Ar name
|
|
.Nm
|
|
.Cm voice
|
|
.Ar user
|
|
.Op Fl -party Ar name
|
|
.Nm
|
|
.Cm mute
|
|
.Ar user
|
|
.Op Fl -party Ar name
|
|
.Nm
|
|
.Cm kick
|
|
.Ar user
|
|
.Op Fl -party Ar name
|
|
.Nm
|
|
.Cm detach
|
|
.Ar user
|
|
.Op Fl -party Ar name
|
|
.Nm
|
|
.Cm role
|
|
.Op Cm active | passive | switch
|
|
.Nm
|
|
.Cm list
|
|
.Nm
|
|
.Cm who
|
|
.Op Fl -short
|
|
.Op Fl -party Ar name
|
|
.Nm
|
|
.Cm status
|
|
.Sh DESCRIPTION
|
|
.Nm
|
|
hosts a live
|
|
.Xr tmux 1
|
|
session for other users on the same UNIX host, or joins one
|
|
someone else is hosting, with the ergonomics of
|
|
.Ql screen -x
|
|
and none of the copy/paste friction.
|
|
Use it to pair-program, mentor, debug an outage together, or demo a
|
|
workflow live: same terminal, no screen-sharing software, no
|
|
intermediary servers.
|
|
.Pp
|
|
Each party runs on its own dedicated
|
|
.Xr tmux 1
|
|
server, distinct from the host's personal
|
|
.Xr tmux 1 .
|
|
This keeps
|
|
.Xr tmux 1
|
|
.Cm server-access
|
|
scoped: inviting someone to your party does not expose your other
|
|
sessions.
|
|
Two gates protect each party.
|
|
The
|
|
.Em filesystem gate
|
|
is universal across every supported platform: the per-party directory
|
|
is
|
|
.Xr chgrp 1 Ns 'd
|
|
to
|
|
.Ev TMUX_PARTY_GROUP
|
|
at mode 0750 so only group members can traverse into it; the socket
|
|
itself is
|
|
.Ql g+rw .
|
|
The
|
|
.Em auth gate
|
|
is
|
|
.Xr tmux 1
|
|
.Cm server-access :
|
|
a user who passes the FS gate but is not on the allowlist is rejected
|
|
at the protocol layer.
|
|
Both gates must pass.
|
|
.Pp
|
|
.Nm
|
|
is designed for small, mutually trusted groups: a hacklab, a tech
|
|
team, a circle of friends, not strangers across the internet.
|
|
The auth gate is authoritative; the FS gate is defense in depth.
|
|
.Sh SUBCOMMANDS
|
|
.Bl -tag -width Ds
|
|
.It Cm host Op Ar name Op Fl -group Ar group
|
|
Spawn a dedicated party
|
|
.Xr tmux 1
|
|
server.
|
|
With no
|
|
.Ar name ,
|
|
a random one is generated.
|
|
Names may use letters, digits,
|
|
.Ql _
|
|
and
|
|
.Ql - ,
|
|
up to 63 characters.
|
|
Dots are not accepted:
|
|
.Xr tmux 1
|
|
silently rewrites
|
|
.Ql \&.
|
|
in session names, which would desync the party name from its session.
|
|
Only the host is on the allowlist; invite others explicitly with
|
|
.Cm invite .
|
|
Group precedence:
|
|
.Fl -group
|
|
flag >
|
|
.Ev TMUX_PARTY_GROUP
|
|
> auto-selection, which walks
|
|
.Ql staff ,
|
|
.Ql users ,
|
|
.Ql wheel ,
|
|
.Ql party
|
|
and gates on the first the host belongs to, announcing the pick.
|
|
.It Cm close Op Ar name
|
|
Tear down the party server and remove its per-party directory.
|
|
With no
|
|
.Ar name ,
|
|
closes the only party the caller hosts.
|
|
Host-only.
|
|
.It Cm join Op Ar name Op Fl -passive
|
|
Join a party.
|
|
With no
|
|
.Ar name
|
|
and exactly one party visible, auto-attaches; otherwise presents a
|
|
numbered picker.
|
|
By default joins in
|
|
.Em active
|
|
mode (your own session group with an independent window cursor).
|
|
.Fl -passive
|
|
attaches read-only to the host's session (mirrored view).
|
|
The read-only boundary is set at attach time and is not toggled by
|
|
later
|
|
.Cm role
|
|
changes.
|
|
An invitee added with
|
|
.Cm invite Fl r
|
|
is read-only and always joins passive:
|
|
.Xr tmux 1
|
|
refuses session creation from read-only clients, so an active join is
|
|
not possible.
|
|
.It Cm leave
|
|
Detach from the current party and clean up the per-guest session.
|
|
.It Cm knock Op Ar name
|
|
Ask the host of an invite-only party for an invite.
|
|
Sends a
|
|
.Xr write 1
|
|
message to the host naming the caller and the exact
|
|
.Cm invite
|
|
command.
|
|
Works for parties
|
|
.Cm list
|
|
shows as invite-only: the caller shares the party's group but is not
|
|
on its allowlist.
|
|
Fails when the host has no tty or has messages disabled with
|
|
.Xr mesg 1 .
|
|
.It Cm invite Ar user Op Fl r Op Fl -party Ar name
|
|
Add
|
|
.Ar user
|
|
to the allowlist as read/write.
|
|
.Fl r
|
|
or
|
|
.Fl -read-only
|
|
invites as a watcher.
|
|
Best-effort
|
|
.Xr write 1
|
|
ping is sent if available.
|
|
.Fl -party Ar name
|
|
selects the party when you host more than one.
|
|
Host-only.
|
|
.It Cm voice Ar user Op Fl -party Ar name
|
|
Promote
|
|
.Ar user
|
|
to read/write.
|
|
Alias:
|
|
.Cm rw .
|
|
.Fl -party Ar name
|
|
selects the party when you host more than one.
|
|
Host-only.
|
|
.It Cm mute Ar user Op Fl -party Ar name
|
|
Demote
|
|
.Ar user
|
|
to read-only.
|
|
Alias:
|
|
.Cm ro .
|
|
.Fl -party Ar name
|
|
selects the party when you host more than one.
|
|
Host-only.
|
|
.It Cm kick Ar user Op Fl -party Ar name
|
|
Revoke
|
|
.Ar user Ns 's
|
|
invite, disconnect their client, and kill their guest session.
|
|
.Fl -party Ar name
|
|
selects the party when you host more than one.
|
|
Host-only.
|
|
.It Cm detach Ar user Op Fl -party Ar name
|
|
Disconnect
|
|
.Ar user Ns 's
|
|
client and kill their guest session, but keep them on the allowlist.
|
|
.Fl -party Ar name
|
|
selects the party when you host more than one.
|
|
Host-only.
|
|
.It Cm role Op Cm active | passive | switch
|
|
Flip your clients between the guest session and the host session.
|
|
With no argument, prints the current role for each of your clients.
|
|
Session-level only: the read-only boundary set at attach time
|
|
.Po see
|
|
.Cm join Fl -passive
|
|
.Pc
|
|
is not toggled by role changes
|
|
.Po
|
|
.Xr tmux 1
|
|
.Cm switch-client
|
|
has no read-only flag
|
|
.Pc .
|
|
.It Cm list
|
|
List live parties on this host from the roster.
|
|
Parties whose
|
|
.Cm server-access
|
|
allowlist does not include you are shown as
|
|
.Ql invite-only
|
|
with the host to ask; a party you cannot see at all is either dead or
|
|
outside your group.
|
|
.It Cm who Op Fl -short Op Fl -party Ar name
|
|
Show invited and attached users for the current party.
|
|
.Fl -short
|
|
emits a compact form suitable for a status-line widget.
|
|
.Fl -party Ar name
|
|
selects a party you are not currently hosting.
|
|
.It Cm status
|
|
Show the caller's own state: hosting, attached, or idle.
|
|
.El
|
|
.Sh ENVIRONMENT
|
|
.Bl -tag -width "PARTY_SOCKET_DIR"
|
|
.It Ev TMUX_PARTY_GROUP
|
|
Shared system group used for socket access.
|
|
When unset, the group is auto-selected at host time; see
|
|
.Cm host .
|
|
Override to pin an existing group like
|
|
.Ql wheel ,
|
|
.Ql users ,
|
|
or
|
|
.Ql staff ,
|
|
or pass
|
|
.Fl -group Ar name
|
|
to
|
|
.Cm host .
|
|
.It Ev PARTY_GROUP_CANDIDATES
|
|
Space-separated candidate list for host-time group auto-selection.
|
|
Default:
|
|
.Dq staff users wheel party .
|
|
.It Ev PARTY_SOCKET_DIR
|
|
Where each party's per-party private directory (and its socket and
|
|
roster) is created.
|
|
Default
|
|
.Pa /tmp .
|
|
Must not contain whitespace;
|
|
.Cm host
|
|
refuses such a directory.
|
|
.It Ev PARTY_TMUX
|
|
.Xr tmux 1
|
|
binary to use.
|
|
Default
|
|
.Ql tmux .
|
|
Override if
|
|
.Xr tmux 1
|
|
\(>= 3.3 lives at a non-standard path.
|
|
.El
|
|
.Sh FILES
|
|
.Bl -tag -width Ds
|
|
.It Pa ${PARTY_SOCKET_DIR}/party-${USER}:${NAME}.d/
|
|
Per-party private directory, mode 0750, group
|
|
.Ev TMUX_PARTY_GROUP .
|
|
.It Pa ${PARTY_SOCKET_DIR}/party-${USER}:${NAME}.d/sock
|
|
The
|
|
.Xr tmux 1
|
|
socket, mode
|
|
.Ql g+rw .
|
|
.It Pa ${PARTY_SOCKET_DIR}/party-${USER}:${NAME}.d/roster
|
|
Bookkeeping:
|
|
.Va HOST_USER ,
|
|
.Va PARTY_NAME ,
|
|
.Va SOCKET ,
|
|
.Va SERVER_PID ,
|
|
.Va GROUP ,
|
|
.Va CREATED .
|
|
Security-relevant fields are re-derived from the directory itself on
|
|
every read, not trusted from the file.
|
|
.It Pa ${PARTY_SOCKET_DIR}/party-${USER}:${NAME}.d/.party-notify
|
|
Auto-generated helper that fans
|
|
.Cm display-message
|
|
out to every attached client on join/leave.
|
|
.El
|
|
.Sh EXIT STATUS
|
|
.Bl -tag -width Ds
|
|
.It 0
|
|
Success.
|
|
.It 1
|
|
Operational failure (no such party, not a member of the group, socket
|
|
gone, ambiguous match, etc.).
|
|
.It 2
|
|
Usage error (unknown subcommand or flag, missing argument, invalid
|
|
party name).
|
|
.It 13
|
|
.Cm join :
|
|
caller is not on the allowlist for the named party.
|
|
.El
|
|
.Sh EXAMPLES
|
|
One-time setup, as root:
|
|
.Bd -literal -offset indent
|
|
groupadd party
|
|
usermod -aG party alice
|
|
usermod -aG party bob
|
|
.Ed
|
|
.Pp
|
|
Have alice and bob log out and back in so the new group takes effect.
|
|
.Pp
|
|
Host a party named
|
|
.Ql debug-the-deploy
|
|
and invite alice as a watcher and bob as a co-driver:
|
|
.Bd -literal -offset indent
|
|
$ party host debug-the-deploy
|
|
$ party invite alice -r
|
|
$ party invite bob
|
|
.Ed
|
|
.Pp
|
|
On bob's side, join the only running party on this host:
|
|
.Bd -literal -offset indent
|
|
$ party join
|
|
.Ed
|
|
.Pp
|
|
On alice's side, join read-only:
|
|
.Bd -literal -offset indent
|
|
$ party join debug-the-deploy --passive
|
|
.Ed
|
|
.Pp
|
|
Reuse an existing group instead of creating a new one:
|
|
.Bd -literal -offset indent
|
|
$ party host debug-the-deploy --group staff
|
|
.Ed
|
|
.Sh SECURITY
|
|
The OS group is the perimeter,
|
|
.Xr tmux 1 Ns 's
|
|
.Cm server-access
|
|
allowlist is the per-user filter.
|
|
Both are universal across the supported platforms (Linux, FreeBSD,
|
|
NetBSD, OpenBSD, illumos, Solaris, macOS), so the mechanism is one
|
|
liner:
|
|
.Xr chgrp 1
|
|
+
|
|
.Xr chmod 1 .
|
|
No ACL syscalls are invoked.
|
|
.Pp
|
|
Trust is anchored in unforgeable primitives:
|
|
.Xr mkdir 2
|
|
and
|
|
.Xr bind 2
|
|
set the owner to the calling effective UID, and
|
|
.Xr chown 1
|
|
requires root.
|
|
A group member who plants a forged directory cannot fake its
|
|
ownership, cannot redirect the socket path, and cannot forge a socket
|
|
elsewhere.
|
|
.Pp
|
|
The threat model is accidents, not adversaries.
|
|
The ownership and symlink gates in front of destructive operations
|
|
exist so a stale roster, a typo, or two racing
|
|
.Cm host
|
|
invocations cannot remove or hijack the wrong directory; that they
|
|
would also stop a malicious group member is a side effect, not the
|
|
design goal.
|
|
New checks should prevent a plausible mistake, not a hypothetical
|
|
attack from a friend.
|
|
.Pp
|
|
.Nm
|
|
is designed for small, mutually trusted groups: a hacklab, a tech
|
|
team, a circle of friends, not strangers across the internet.
|
|
It assumes you already know and trust the people you are adding to
|
|
the group; it does not try to be a public access-control system.
|
|
.Sh CAVEATS
|
|
On filesystems with NFSv4-style inherited ACLs (macOS HFS+/APFS,
|
|
FreeBSD/illumos ZFS), an ACE on
|
|
.Ev PARTY_SOCKET_DIR
|
|
can override the mode bits
|
|
.Nm
|
|
sets, widening access (e.g.\& an inherited
|
|
.Ql everyone@:rx
|
|
survives our
|
|
.Ql chmod 0750 )
|
|
or narrowing it (e.g.\& a
|
|
.Ql deny everyone:
|
|
blocks group members our
|
|
.Xr chgrp 1
|
|
tried to admit).
|
|
The script does not invoke
|
|
.Xr getfacl 1
|
|
or
|
|
.Xr getextattr 8
|
|
to verify effective access; re-introducing per-platform ACL plumbing
|
|
would unwind the simplification this design commits to.
|
|
The FS gate is therefore best-effort on those filesystems; the
|
|
auth gate
|
|
.Po
|
|
.Xr tmux 1
|
|
.Cm server-access
|
|
.Pc
|
|
remains authoritative regardless.
|
|
The default
|
|
.Ev PARTY_SOCKET_DIR Ns = Ns Pa /tmp
|
|
rarely has ACL trouble in practice; if you deploy on a custom dir
|
|
with restrictive inherited ACLs, expect to handle traversal at the
|
|
ACL layer for your intended group members.
|
|
Strangers stay out via
|
|
.Cm server-access
|
|
either way.
|
|
.Pp
|
|
.Cm server-access
|
|
decides who may
|
|
.Em attach ,
|
|
not who can see that a party
|
|
.Em exists .
|
|
Hiding a party's existence, meaning the host, its name, group, and
|
|
creation time recorded in the
|
|
.Pa roster ,
|
|
falls to the FS gate, not the auth gate: on an ACL-shadowed dir where the
|
|
mode bits do not hold, a non-member may read that metadata even though
|
|
.Cm server-access
|
|
still blocks the join.
|
|
The
|
|
.Cm list
|
|
subcommand re-checks group membership before printing, as defense in
|
|
depth, but treat discovery as confidential only insofar as the filesystem
|
|
enforces it.
|
|
.Pp
|
|
An active
|
|
.Cm join
|
|
lands you in your own
|
|
.Ql __party_guest_<you>
|
|
session on the shared server.
|
|
Any write-capable invitee can create sessions there, including one named
|
|
for someone else; if they pre-create yours, your next active
|
|
.Cm join
|
|
attaches to it.
|
|
This is inside the trust boundary by design, not a defended one;
|
|
invitees are people you already trust.
|
|
If you do not trust an invitee that far, invite them read-only
|
|
.Pq Fl r ,
|
|
which cannot create sessions, or not at all.
|
|
.Pp
|
|
.Nm
|
|
requires
|
|
.Xr tmux 1
|
|
\(>= 3.3 for
|
|
.Cm server-access .
|
|
Older
|
|
.Xr tmux 1
|
|
versions are refused at host time rather than degrading to the FS
|
|
gate alone.
|
|
.Pp
|
|
The
|
|
.Cm role
|
|
subcommand can flip a client between the guest session and the host
|
|
session, but it cannot change the read-only state of an attached
|
|
client;
|
|
.Xr tmux 1
|
|
.Cm switch-client
|
|
has no equivalent of
|
|
.Cm attach Fl r .
|
|
The watcher boundary is set at
|
|
.Cm join Fl -passive
|
|
time and is fixed for the life of that client.
|
|
.Sh SEE ALSO
|
|
.Xr tmux 1 ,
|
|
.Xr screen 1 ,
|
|
.Xr write 1 ,
|
|
.Xr chgrp 1 ,
|
|
.Xr chmod 1
|
|
.Sh HISTORY
|
|
.Nm
|
|
was written for the UNIX Social Club
|
|
.Pq Lk https://club.unix.rocks/ ,
|
|
inspired by many years of collective working sessions using
|
|
.Ql screen -x
|
|
across collectives.
|
|
.Sh AUTHORS
|
|
.Nm
|
|
was written by
|
|
.An veg
|
|
and
|
|
.An kol3rby .
|
|
See the
|
|
.Pa party
|
|
script header for additional credits.
|
|
Issues and contributions welcome!
|