Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/1mb-start-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: 1MB start wrapper tests

on:
push:
paths:
- "Resources/Server/1MB-start.sh"
- "Resources/Server/tests/1MB-start/**"
- ".github/workflows/1mb-start-tests.yml"
pull_request:
paths:
- "Resources/Server/1MB-start.sh"
- "Resources/Server/tests/1MB-start/**"
- ".github/workflows/1mb-start-tests.yml"
workflow_dispatch:

permissions:
contents: read

jobs:
test:
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os:
- macos-latest
- ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v6

- name: Install ShellCheck on macOS
if: runner.os == 'macOS'
run: command -v shellcheck >/dev/null 2>&1 || brew install shellcheck

- name: Install ShellCheck on Ubuntu
if: runner.os == 'Linux'
run: |
if ! command -v shellcheck >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install --yes shellcheck
fi

- name: Run syntax, behavior, and lint tests
shell: bash
run: /bin/bash Resources/Server/tests/1MB-start/run-tests.sh all
147 changes: 126 additions & 21 deletions Resources/Server/1MB-start.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash

# @Filename: 1MB-start.sh
# @Version: 2.17.9, build 082 for Minecraft 26.2 (Java 25, 64bit)
# @Version: 2.18.3, build 086 for Minecraft 26.2 (Java 25, 64bit)
# @Release: August 4th, 2026
# @Description: Helps us start and fork a Minecraft 26.2 server session.
# @Contact: I am @floris on Twitter, and mrfloris in MineCraft.
Expand All @@ -18,7 +18,8 @@
###

_serverName="mcserver"
# Keep the name short, simple, and lowercase (no numbers or weird characters).
# Use 1-32 lowercase ASCII letters, numbers, hyphens or underscores. The first
# character must be a letter or number.
# The name makes it easier to recognize the session in 'tmux ls', you can
# re-attach to the forked sessions with 'tmux attach -t (name)'.

Expand All @@ -30,6 +31,7 @@ _serverName="mcserver"
###

_sibling="1MB-minecraft.sh"
_startupCheckSeconds=2 # Check process liveness only; this is not Paper readiness.
_debug=true # Set to false to minimize output.

### FUNCTIONS AND CODE
Expand All @@ -52,7 +54,7 @@ function _output {
fi

case "$_mode" in
oops|okay) _fd=2 ;;
oops) _fd=2 ;;
esac

if [ -z "${NO_COLOR+x}" ] && [ -t "$_fd" ]; then
Expand All @@ -68,7 +70,7 @@ function _output {
;;
okay)
_args="${*:2}"; _prefix="(Info)";
printf '\n%s%s%s %s%s\n' "$_yellow" "$_prefix" "$_cyan" "$_args" "$_reset" >&2; exit 1
printf '\n%s%s%s %s%s\n' "$_yellow" "$_prefix" "$_cyan" "$_args" "$_reset"; return 0
;;
debug)
_args="${*:2}"; _prefix="(Debug)";
Expand Down Expand Up @@ -131,46 +133,139 @@ function _showHelp {
" --attach [name] Attach to the exact tmux session." \
" --help, -h Show this help." \
"" \
"The session name defaults to '$_serverName'."
"The session name defaults to '$_serverName'." \
"Names use 1-32 lowercase ASCII letters, numbers, hyphens or underscores." \
"The first character must be a letter or number."
}

function _setServerName {
local _requestedName="${1:-}"
local _input=""
local _requestedName="${1-}"
local LC_ALL=C

if [ -n "$_requestedName" ]; then
_input=$(printf '%s\n' "$_requestedName" | awk '{ print tolower($1) }')
_input=$(printf '%s\n' "$_input" | awk '{ print substr($0, 1, 16) }')
if [ "${#_requestedName}" -gt 32 ] || ! [[ "$_requestedName" =~ ^[a-z0-9][a-z0-9_-]*$ ]]; then
_output oops "Invalid session name. Use 1-32 lowercase ASCII letters, numbers, hyphens or underscores. The first character must be a letter or number."
fi

if [[ "$_input" =~ ^[a-z]+$ ]]; then
_serverName="$_input"
else
_output oops "Provided input is invalid! Input is for a unique '_serverName'. Do not use numbers, spaces or weird chars. Keep it short, and a-z characters only."
_serverName="$_requestedName"
}

function _isPaneId {
local _paneId="${1:-}"
local _paneNumber=""

case "$_paneId" in
%*) _paneNumber="${_paneId#%}" ;;
*) return 1 ;;
esac

case "$_paneNumber" in
""|*[!0-9]*) return 1 ;;
*) return 0 ;;
esac
}

function _verifyStartedPane {
local _paneId="${1:-}"
local _paneReport=""
local _paneDead=""
local _paneResult=""
local _paneStatus=""
local _paneSignal=""
local _exitDescription=""

_output debug "Checking whether '$_sibling' remains active for $_startupCheckSeconds seconds ..."

if ! sleep "$_startupCheckSeconds"; then
if ! tmux set-option -p -t "$_paneId" remain-on-exit off >/dev/null 2>&1; then
_output oops "The $_startupCheckSeconds-second startup check was interrupted, and tmux could not restore normal close-on-exit behavior. Inspect the '$_serverName' session before retrying."
fi
_output oops "The $_startupCheckSeconds-second startup check was interrupted. The tmux session was not reported as started."
fi

if ! _paneReport=$(tmux display-message -p -t "$_paneId" '#{pane_dead}|#{pane_dead_status}|#{pane_dead_signal}' 2>/dev/null); then
tmux set-option -p -t "$_paneId" remain-on-exit off >/dev/null 2>&1 || true
_output oops "'$_sibling' ended or its tmux pane disappeared during the $_startupCheckSeconds-second startup check. Run './$_sibling' directly from '$_scriptDir' to inspect its full output."
fi

_paneDead="${_paneReport%%|*}"
_paneResult="${_paneReport#*|}"
_paneStatus="${_paneResult%%|*}"
_paneSignal="${_paneResult#*|}"

case "$_paneDead" in
0)
if ! tmux set-option -p -t "$_paneId" remain-on-exit off >/dev/null 2>&1; then
_output oops "'$_sibling' remained active, but tmux could not restore normal close-on-exit behavior for pane '$_paneId'. Inspect the '$_serverName' session before retrying."
fi

if ! _paneDead=$(tmux display-message -p -t "$_paneId" '#{pane_dead}' 2>/dev/null); then
_output oops "'$_sibling' ended as the $_startupCheckSeconds-second startup check completed. The tmux session was not reported as started."
fi

if [ "$_paneDead" != 0 ]; then
tmux kill-pane -t "$_paneId" >/dev/null 2>&1 || true
_output oops "'$_sibling' ended as the $_startupCheckSeconds-second startup check completed. The tmux session was not reported as started."
fi
;;
1)
if [ -n "$_paneStatus" ]; then
_exitDescription="exit status $_paneStatus"
fi
if [ -n "$_paneSignal" ]; then
if [ -n "$_exitDescription" ]; then
_exitDescription="$_exitDescription, signal $_paneSignal"
else
_exitDescription="signal $_paneSignal"
fi
fi
[ -z "$_exitDescription" ] && _exitDescription="unknown exit status"

tmux kill-pane -t "$_paneId" >/dev/null 2>&1 || true
_output oops "'$_sibling' exited during the $_startupCheckSeconds-second startup check ($_exitDescription). Run './$_sibling' directly from '$_scriptDir' to inspect its full output."
;;
*)
tmux set-option -p -t "$_paneId" remain-on-exit off >/dev/null 2>&1 || true
_output oops "tmux returned an unexpected state for pane '$_paneId' during the startup check. The session was not reported as started."
;;
esac
}

_action="start"
_nameArgument="${1:-}"
_nameArgument="$_serverName"

case "${1:-}" in
-h|--help)
[ "$#" -eq 1 ] || _output oops "Unexpected extra arguments. Run '$0 --help' for usage."
_showHelp
exit 0
;;
--status)
[ "$#" -le 2 ] || _output oops "Unexpected extra arguments. Run '$0 --help' for usage."
_action="status"
_nameArgument="${2:-}"
if [ "$#" -eq 2 ]; then
_nameArgument="$2"
fi
;;
--attach)
[ "$#" -le 2 ] || _output oops "Unexpected extra arguments. Run '$0 --help' for usage."
_action="attach"
_nameArgument="${2:-}"
if [ "$#" -eq 2 ]; then
_nameArgument="$2"
fi
;;
--*)
_output oops "Unknown option '$1'. Run '$0 --help' for usage."
;;
*)
[ "$#" -le 1 ] || _output oops "Unexpected extra arguments. Run '$0 --help' for usage."
if [ "$#" -eq 1 ]; then
_nameArgument="$1"
fi
;;
esac

_setServerName "$_nameArgument"

[ "$EUID" -eq 0 ] && _output oops "*!* This script should not be run using sudo, or as the root user!"

if [ "$_action" = start ]; then
Expand All @@ -182,8 +277,6 @@ if [ "$_action" = start ]; then
fi
fi

_setServerName "$_nameArgument"

if ! type "tmux" >/dev/null 2>&1; then
_output oops "'tmux' is required but was not found. On macOS, install it with: brew install tmux"
fi
Expand Down Expand Up @@ -214,9 +307,21 @@ _output debug "Using server directory: $_scriptDir"

_output debug "Found 'tmux', attempting to start '$_sibling' in a detached tmux session ..."
# Deliberately launch only ./1MB-minecraft.sh from the wrapper's own directory.
if ! tmux new-session -d -s "$_serverName" -c "$_scriptDir" "exec \"./$_sibling\""; then
_output oops "Could not create the '$_serverName' tmux session and start '$_sibling'. Review the tmux error above and check with 'tmux ls'."
_paneId=""
if ! _paneId=$(tmux new-session -d -P -F '#{pane_id}' -s "$_serverName" -c "$_scriptDir" "exec \"./$_sibling\"" \; set-option -p -t "=$_serverName:" remain-on-exit on); then
if _isPaneId "$_paneId"; then
tmux kill-pane -t "$_paneId" >/dev/null 2>&1 || true
fi
_output oops "Could not create the '$_serverName' tmux session and prepare its startup check. Review the tmux error above and check with 'tmux ls'."
fi

if ! _isPaneId "$_paneId"; then
tmux kill-session -t "=$_serverName" >/dev/null 2>&1 || true
_output oops "tmux created the '$_serverName' session but did not return a valid pane identifier, so the new session was closed."
fi

_verifyStartedPane "$_paneId"

[[ "$_debug" == true ]] && tmux ls; _output debug "To re-attach: tmux attach -t $_serverName"

_output debug "tmux session started."
Expand Down
Loading
Loading