By Edward, Published January 13 2024, Updated March 3 2024
The server I have set up consists of a dedicated server user account, a home directory, and server scripts. The server runs in Terminal Multiplexor (TMUX). The server user serverd has a home directory set up in /usr/servers. Included is a ~/hooks/ folder including /usr/servers/hooks/startup/ and /usr/servers/hooks/shutdown/ and includes a $SERVER.sh file for each relevant server instance. A service file is placed inside /etc/systemd/system/ and launches the /usr/server/hooks/startup/$SERVER.sh file and terminates the server using the /usr/server/hooks/shutdown/$SERVER.sh file.
All setup commands should be run as root or with sudo.
I have also set up a github repo that automatically installs TMUX on Ubuntu Server. Tested in my VM. https://github.com/anedward01/tmux-deployment
Use Case
TMUX can be used to create several terminal instances that allow user interaction. In my specific use case, I use it to run video game servers that may require additional input, or just need to review session logs. On that end, I set up TMUX to create several sessions, load up the server files, and run the servers. However, this system requires time and manual effort to do on each boot. For that purpose, I investigated how to launch TMUX on boot as its own user, with its own directory.
User and TMUX Setup
The command to set up the user account is quite simple. We will make the user and group, then add the new user to the new group.
mkdir /usr/servers
useradd serverd -d /usr/servers -s /bin/bash
groupadd servermod
usermod -aG servermod serverd
chown -R serverd:servermod /usr/servers
BashTmux requires a bit more setup than running a standard session. In order to connect to the server session, a socket must be created. This will launch a session instance.
tmux -S /usr/servers/Server
BashDisconnecting from the session and listing the home directory contents reveals a new file Server. This file is constantly referenced to connect to the tmux session.
To make sure the servermod group can access Server, run the following command:
chown serverd:servermod /usr/servers/Server
chmod 770 /usr/servers/Server
BashOverview
User: serverd (server daemon)
Group: servermod (server moderators)
Home: /usr/servers/
Scripts: /usr/servers/hooks/
Start: /usr/servers/hooks/startup/$SERVER.sh
Stop: /usr/servers/hooks/shutdown/$SERVER.sh
Service: /etc/systemd/system/$SERVER.service
Socket: /usr/servers/Server
Full code
mkdir /usr/servers
useradd serverd -d /usr/servers -s /bin/bash
groupadd servermod
usermod -aG servermod serverd
chown -R serverd:servermod /usr/servers
tmux -S /usr/servers/Server
chown serverd:servermod /usr/servers/Server
chmod 770 /usr/servers/Server
BashTMUX on Boot
The general setup for TMUX is now done. To have it automatically start up, create /usr/servers/tmux.sh with the following code
#!/usr/bin/env bash
# This file begins the Terminal MUltipleXor (TMUX) session the servers
# will be running on.
SESSION="tmux -S /usr/servers/Server send-keys -t Servers"
tmux -S "/usr/servers/Server" new -s "Servers" -d
$SESSION "tmux rename-window 'Management'" C-m
BashThen allow the file to be run as an executable and set the owner of the whole /usr/servers folder back to serverd.
chmod ug+x /usr/servers/tmux.sh
chown -R serverd:servermod /usr/servers
BashNext, create /etc/systemd/system/serverd.service and add the following code
[Unit]
Description=Server Terminal Multiplexor
Wants=network.target
After=network.target
[Service]
User=serverd
Group=servermod
Type=forking
ProtectHome=true
ProtectSystem=full
PrivateDevices=true
NoNewPrivileges=true
InaccessibleDirectories=/root /sys /srv -/opt /media -/lost+found
ReadWriteDirectories=/usr/servers
WorkingDirectory=/usr/servers
ExecStart="/usr/servers/tmux.sh"
[Install]
WantedBy=multi-user.target
Systemd ServiceFinally, reload systemctl, launch the service, and enable it
systemctl daemon-reload
systemctl start serverd.service
systemctl enable serverd.service
BashTo make sure the session is working, run the following command:
tmux -S /usr/servers/Server attach -t Servers
BashIf all went well, you should see the new window. If it didn’t work, review the steps, check systemctl logs.
Quick notes
Use absolute references for the server session
tmux.sh line 8 new -s "Servers" -d
creates a new session named Servers and immediately detaches the program from the session.
serverd.service line 12 Type=forking
is necessary to prevent systemctl from killing serverd.service and its child processes, which includes the TMUX session.
serverd.service does not include ExecStop=
since it is not immediately necessary. However, it can be added as desired.
serverd.service line 10-16 are extra commands which prevent privilege escalation and stop the service from modifying system files
Troubleshooting
After reloading systemctl, if it throws any errors, use one or both of the commands below
journalctl -eu serverd.service
systemctl status serverd.service
BashEach will contain relevant information towards the crash.
Sometimes a permissions issue prevents /usr/servers/tmux.sh from starting.
chmod ug+x /usr/servers/tmux.sh
BashIf it throws an access issue or read-write denial, serverd probably lacks permissions for tmux.sh.
chown -R serverd:servermod /usr/servers
BashSession Startup Script
Great! If you made it this far, your TMUX socket session is up and running properly. That’s pretty much the hard part. The rest from here is just appendages for the server.
Source code for ~/hooks/startup/example.sh
#!/usr/bin/env bash
# This script is a hook for the file located at /usr/servers/tmux.sh
# and adds a window to TMUX session "Servers".
SESSION_DIR="/usr/servers/Server"
# Pulls filename for TMUX window. Typically name this file a concatenated string
# "exampleServer.sh" or "ExampleServer.sh" to avoid issues
FILE_NAME=$(basename -s .sh $0)
SESSION="tmux -S $SESSION_DIR send-keys -t Servers"
WINDOW="$SESSION:$FILE_NAME"
MANAGE="tmux select-window -t Management"
# The following creates a new window, renames it to the corresponding server,
# then reselects the Management window for conflict prevention.
$SESSION":Management" "$MANAGE && tmux new-window && tmux rename-window $FILE_NAME && $MANAGE" C-m
# This is necessary. Without it, the following commands won't find its window.
sleep 0.01
# The following runs the actual server commands to its specific window.
# Make sure the folders are made before launching this script
$WINDOW "cd /usr/servers/serverFiles/example" C-m
# Example command of running a Minecraft server
#$WINDOW "java -Xmx10G -jar server.jar nogui" C-m
BashNotes
Don’t forget to make this file executable
chmod ug+x /usr/servers/hooks/startup/example.sh
BashVariables
SESSION_DIR states the TMUX socket
FILE_NAME states the server’s name
SESSION sets the relevant keypress command
WINDOW sets the window to send the keypress command to from SESSIONMANAGE sets the sesson’s main window, through which startup commands should be input
Elaboration
is necessary. Without it, the server won’t be able to find it’s relevant window.sleep 0.01
The abundance of variables is used to cut down on overall length of commands and to make the instance operation structure
For example, during boot server1 and server2 boot in parallel, and if line 16 is split in two commands, then their commands may overlap and conflict.
Session Shutdown Script
Great! If you made it this far, your TMUX socket session is up and running properly. That’s pretty much the hard part. The rest from here is just appendages for the server.
Source code for ~/hooks/startup/example.sh
#!/usr/bin/env bash
# This script is a hook for the file located at /usr/servers/tmux.sh
# and sends the shutdown procedure for the program
SESSION_DIR="/usr/servers/Server"
FILE_NAME=$(basename -s .sh $0)
SESSION="tmux -S $SESSION_DIR send-keys -t Servers"
WINDOW="$SESSION:$FILE_NAME"
MANAGE="tmux select-window -t Management"
# Insert shutdown commands here. Begin with $WINDOW to direct commands
# into its proper window. This prevents collisions with other programs.
#$WINDOW "stop" C-m
$WINDOW "exit" C-m
BashNotes
Don’t forget to make this file executable
chmod ug+x /usr/servers/hooks/shutdown/example.sh
BashSince all commands are piped through their corresponding window, sleep is not necessary for the base file.
Session Service Script
[Unit]
Description=Example Server Service
Wants=network.target
Wants=serverd.service
[Service]
User=serverd
Group=servermod
Type=oneshot
RemainAfterExit=true
ProtectHome=true
ProtectSystem=full
PrivateDevices=true
NoNewPrivileges=true
InaccessibleDirectories=/root /sys /srv -/opt /media -/lost+found
ReadWriteDirectories=/usr/servers
WorkingDirectory=/usr/servers/hooks/
ExecStartPre=/bin/sleep 1
ExecStart=/usr/servers/hooks/startup/example.sh
ExecStop=/usr/servers/hooks/shutdown/example.sh
[Install]
WantedBy=multi-user.target
Systemd ServiceNotes
Type=oneshot
is necessary to prevent example’s server from being shut down, while still marking the service as active until the TMUX window closes or the service shutdown command is sent
ExecStartPre
is necessary to prevent conflict with other servers at bootup. Use alongside a Wants=previousservice.service
entry.
Final Steps
Run the same systemctl steps as for serverd.service
systemctl daemon-reload
systemctl start example.service
BashMake sure the server works
tmux -S /usr/servers/Server attach -t Servers:example
BashFinally, if the server runs and all is right:
systemctl enable example.service
BashFinal Notes
TMUX is amazing for running programs that need occasional input from the terminal, rather than running as a straight service with no interaction. Creating an automated TMUX instance and sessions helps create an amazing platform that can easily be scaled and modified based on needs.