Skip to content
Open
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
88 changes: 28 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,74 +21,42 @@ You can change these in the helper scripts under `scripts/`, via the new web con

## Install

### 1) Create the helper scripts (as root)
```
install -Dm755 scripts/rust_install.sh /usr/local/bin/rust_install.sh
install -Dm755 scripts/rust_update.sh /usr/local/bin/rust_update.sh
install -Dm755 scripts/oxide_update.sh /usr/local/bin/oxide_update.sh
```
### Automated Ubuntu install (recommended)

Run the interactive installer as root. It will ask for the panel service user,
Rust service name, Steam account paths, and the initial web login credentials.
The script enables the multiverse repository, installs SteamCMD and the 32-bit
runtime libraries it needs, installs the required Python packages system-wide,
copies the web application into `/opt/rust-panel/app` (by default), configures
sudo rules for the panel user, installs the helper scripts into
`/usr/local/bin`, and enables a `systemd` service so the panel starts
automatically.

### 2) Sudoers (limit panel privileges)
Allow the `rustpanel` service user to run just these commands without a password:
```
/etc/sudoers.d/rustpanel
---------------------------------------
rustpanel ALL=NOPASSWD: /bin/systemctl start rust-server
rustpanel ALL=NOPASSWD: /bin/systemctl stop rust-server
rustpanel ALL=NOPASSWD: /bin/systemctl restart rust-server
rustpanel ALL=NOPASSWD: /bin/systemctl status rust-server
rustpanel ALL=NOPASSWD: /bin/journalctl -u rust-server
rustpanel ALL=NOPASSWD: /usr/local/bin/rust_install.sh
rustpanel ALL=NOPASSWD: /usr/local/bin/rust_update.sh
rustpanel ALL=NOPASSWD: /usr/local/bin/oxide_update.sh *
---------------------------------------
sudo ./scripts/install_panel.sh
```

> The `*` after `oxide_update.sh` allows passing a single URL argument.
After the prompts complete you can visit the panel on port 8080 immediately.
All environment settings are stored in `/etc/rust-panel/panel.env`; edit that
file and restart the `rustpanel` service if you ever need to change the
defaults.

### 3) Python app
Deploy `app.py` and `templates/index.html` to e.g. `/home/rustpanel/rust-panel/`.
Create a venv and install Flask:
```
sudo -u rustpanel bash -lc '
cd ~/rust-panel
python3 -m venv ../venv
../venv/bin/pip install --upgrade pip Flask
'
```
### Manual install (advanced)

### 4) Systemd unit for the panel
```
/etc/systemd/system/rustpanel.service
---------------------------------------
[Unit]
Description=Flask Rust Panel
After=network.target
If you would like to perform the setup yourself, follow the high level steps
below as a reference:

[Service]
User=rustpanel
WorkingDirectory=/home/rustpanel/rust-panel
Environment=RUST_SERVICE=rust-server
# Optional: override login credentials (defaults admin/rustpanel)
# Environment=RUSTPANEL_USER=rustpanel
# Environment=RUSTPANEL_PASSWORD=ChangeMeNow
# Or provide a pre-hashed password (pbkdf2:sha256)
# Environment=RUSTPANEL_PASSWORD_HASH=...
# Optional auth token for API callers (still works in addition to UI login)
# Environment=RUSTPANEL_TOKEN=YourStrongTokenHere
ExecStart=/home/rustpanel/venv/bin/python /home/rustpanel/rust-panel/app.py
Restart=on-failure
RestartSec=5
1. Copy the helper scripts from `scripts/` into `/usr/local/bin` and make them
executable.
2. Grant the panel service user password-less sudo access to the helper scripts
and to the `systemctl`/`journalctl` commands for your Rust server unit.
3. Deploy the Flask app somewhere (for example `/opt/rust-panel/app.py` and the
`templates/` directory) and install the Python dependencies globally.
4. Create a `systemd` unit that launches `/usr/bin/python3 /path/to/app.py` with
the appropriate environment variables for credentials and paths.
5. Enable and start the unit.

[Install]
WantedBy=multi-user.target
---------------------------------------
```
Enable:
```
systemctl daemon-reload
systemctl enable --now rustpanel
```
The automated installer performs these steps for you using safe defaults.

## Using the panel
- Visit: `http://<container-ip>:8080`
Expand Down
214 changes: 184 additions & 30 deletions scripts/install_panel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
INSTALL_DIR="${1:-/opt/rust-panel}"
APP_DIR="${INSTALL_DIR}/app"
VENV_DIR="${INSTALL_DIR}/venv"
PYTHON_BIN="${PYTHON_BIN:-python3}"
CONFIG_DIR="/etc/rust-panel"
ENV_FILE="${CONFIG_DIR}/panel.env"
DEFAULT_SERVICE_NAME="rustpanel"
DEFAULT_RUST_SERVICE="rust-server"

usage() {
cat <<'USAGE'
Usage: install_panel.sh [INSTALL_DIR]

Installs the Rust-La-Panel application into INSTALL_DIR (default: /opt/rust-panel)
and copies the helper scripts into /usr/local/bin. Requires Python 3 and pip.
Interactive installer for the Rust-La-Panel application. Copies the
application into INSTALL_DIR (default: /opt/rust-panel), installs Python
dependencies system-wide, sets up a systemd service, configures sudoers
rules for the panel service user, and installs the helper scripts needed to
manage the Rust server. Run this script as root on Ubuntu.
USAGE
}

Expand All @@ -22,12 +27,102 @@ if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
exit 0
fi

if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
echo "[ERROR] Python interpreter '${PYTHON_BIN}' not found." >&2
if [[ "${EUID}" -ne 0 ]]; then
echo "[ERROR] This installer must be run as root." >&2
exit 1
fi

mkdir -p "${APP_DIR}"
echo "[INFO] Preparing APT repositories for Steam installation..."
export DEBIAN_FRONTEND=noninteractive
if ! dpkg --print-foreign-architectures | grep -qx 'i386'; then
dpkg --add-architecture i386
fi

apt-get update >/dev/null

if ! command -v add-apt-repository >/dev/null 2>&1; then
apt-get install -y --no-install-recommends software-properties-common lsb-release >/dev/null
fi

if ! add-apt-repository -yn multiverse >/dev/null; then
echo "[WARN] Unable to enable the multiverse repository automatically. Please ensure it is enabled." >&2
fi

apt-get update >/dev/null

echo "[INFO] Installing required system packages, including SteamCMD..."
apt-get install -y --no-install-recommends \
python3 python3-flask python3-dotenv sudo \
steamcmd lib32gcc-s1 lib32stdc++6 ca-certificates >/dev/null

read -rp "Enter the systemd service name for the panel [${DEFAULT_SERVICE_NAME}]: " input_service
PANEL_SERVICE_NAME="${input_service:-${DEFAULT_SERVICE_NAME}}"
SERVICE_FILE="/etc/systemd/system/${PANEL_SERVICE_NAME}.service"
SUDOERS_FILE="/etc/sudoers.d/${PANEL_SERVICE_NAME}"

read -rp "Enter the Linux user to run the panel service [${PANEL_SERVICE_NAME}]: " input_user
PANEL_SERVICE_USER="${input_user:-${PANEL_SERVICE_NAME}}"

read -rp "Enter the systemd service name that manages the Rust server [${DEFAULT_RUST_SERVICE}]: " input_rust_service
RUST_SERVER_SERVICE="${input_rust_service:-${DEFAULT_RUST_SERVICE}}"

read -rp "Enter the Steam user that owns the Rust server [steam]: " input_steam_user
STEAM_USER="${input_steam_user:-steam}"

default_steam_home="/home/${STEAM_USER}"
read -rp "Enter the Steam home directory [${default_steam_home}]: " input_steam_home
STEAM_HOME="${input_steam_home:-${default_steam_home}}"

default_steamcmd="${STEAM_HOME}/steamcmd/steamcmd.sh"
if command -v steamcmd >/dev/null 2>&1; then
default_steamcmd="$(command -v steamcmd)"
fi
read -rp "Enter the SteamCMD path [${default_steamcmd}]: " input_steamcmd
STEAMCMD="${input_steamcmd:-${default_steamcmd}}"

default_rust_dir="${STEAM_HOME}/rust-server"
read -rp "Enter the Rust server directory [${default_rust_dir}]: " input_rust_dir
RUST_DIR="${input_rust_dir:-${default_rust_dir}}"

read -rp "Enter the panel login username [admin]: " input_panel_user
PANEL_LOGIN_USER="${input_panel_user:-admin}"

while true; do
read -srp "Enter the panel login password: " PANEL_LOGIN_PASSWORD
echo
read -srp "Confirm the panel login password: " PANEL_LOGIN_PASSWORD_CONFIRM
echo
if [[ -z "${PANEL_LOGIN_PASSWORD}" ]]; then
echo "[WARN] Password cannot be empty. Please try again."
continue
fi
if [[ "${PANEL_LOGIN_PASSWORD}" != "${PANEL_LOGIN_PASSWORD_CONFIRM}" ]]; then
echo "[WARN] Passwords do not match. Please try again."
continue
fi
break
done

echo "[INFO] Hashing panel password..."
PANEL_PASSWORD_HASH="$(
PANEL_PASSWORD="${PANEL_LOGIN_PASSWORD}" python3 - <<'PY'
import os
from werkzeug.security import generate_password_hash

password = os.environ["PANEL_PASSWORD"]
print(generate_password_hash(password))
PY
)"
unset PANEL_LOGIN_PASSWORD PANEL_LOGIN_PASSWORD_CONFIRM PANEL_PASSWORD

mkdir -p "${INSTALL_DIR}" "${APP_DIR}" "${CONFIG_DIR}"

if ! id "${PANEL_SERVICE_USER}" >/dev/null 2>&1; then
echo "[INFO] Creating service user '${PANEL_SERVICE_USER}'."
useradd --system --create-home --home-dir "${INSTALL_DIR}" --shell /usr/sbin/nologin "${PANEL_SERVICE_USER}"
fi

PANEL_SERVICE_GROUP="$(id -gn "${PANEL_SERVICE_USER}")"

copy_path() {
local source_path="${1}" destination_path="${2}"
Expand All @@ -36,38 +131,97 @@ copy_path() {
mkdir -p "${destination_path}"
cp -a "${source_path}/." "${destination_path}/"
else
cp -a "${source_path}" "${destination_path}"
install -Dm644 "${source_path}" "${destination_path}"
fi
}

echo "[INFO] Copying application files to ${APP_DIR}"
copy_path "${REPO_ROOT}/app.py" "${APP_DIR}/app.py"
copy_path "${REPO_ROOT}/templates" "${APP_DIR}/templates"
copy_path "${REPO_ROOT}/.env" "${APP_DIR}/.env"
copy_path "${REPO_ROOT}/requirements.txt" "${APP_DIR}/requirements.txt"

"${PYTHON_BIN}" -m venv "${VENV_DIR}"
"${VENV_DIR}/bin/pip" install --upgrade pip
"${VENV_DIR}/bin/pip" install -r "${APP_DIR}/requirements.txt"

if [[ "${EUID}" -eq 0 ]]; then
echo "Installing helper scripts into /usr/local/bin"
install -Dm755 "${REPO_ROOT}/scripts/rust_install.sh" /usr/local/bin/rust_install.sh
install -Dm755 "${REPO_ROOT}/scripts/rust_update.sh" /usr/local/bin/rust_update.sh
install -Dm755 "${REPO_ROOT}/scripts/oxide_update.sh" /usr/local/bin/oxide_update.sh

if [[ -d "${REPO_ROOT}/static" ]]; then
copy_path "${REPO_ROOT}/static" "${APP_DIR}/static"
fi

install -Dm644 "${REPO_ROOT}/requirements.txt" "${APP_DIR}/requirements.txt"

echo "[INFO] Installing helper scripts into /usr/local/bin"
install -Dm755 "${REPO_ROOT}/scripts/rust_install.sh" /usr/local/bin/rust_install.sh
install -Dm755 "${REPO_ROOT}/scripts/rust_update.sh" /usr/local/bin/rust_update.sh
install -Dm755 "${REPO_ROOT}/scripts/oxide_update.sh" /usr/local/bin/oxide_update.sh

cat >"${ENV_FILE}" <<ENV
# Managed by install_panel.sh
FLASK_ENV=production
RUSTPANEL_USER=${PANEL_LOGIN_USER}
RUSTPANEL_PASSWORD_HASH=${PANEL_PASSWORD_HASH}
RUST_SERVICE=${RUST_SERVER_SERVICE}
Comment on lines +153 to +158

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Badge Quote password hash when writing env file

The installer writes RUSTPANEL_PASSWORD_HASH=${PANEL_PASSWORD_HASH} directly into the generated environment file. The PBKDF2 hash contains $ separators, so when systemd parses the file it performs shell-like expansion and drops everything after the first $. The application then receives an invalid hash and check_password_hash raises ValueError, making login impossible. Wrap the hash in quotes (or escape the $ characters) before writing the line so the literal value is preserved.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex fix

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Wrapped the generated panel password hash in double quotes when writing scripts/install_panel.sh so systemd reads the full PBKDF2 value without truncating on $. scripts/install_panel.shL153-L164

Testing

  • bash -n scripts/install_panel.sh

View task →

STEAM_USER=${STEAM_USER}
STEAM_HOME=${STEAM_HOME}
STEAMCMD=${STEAMCMD}
RUST_DIR=${RUST_DIR}
PANEL_FILE_ROOT=${RUST_DIR}
ENV

chmod 640 "${ENV_FILE}"
chown root:"${PANEL_SERVICE_GROUP}" "${ENV_FILE}"

cat >"${SERVICE_FILE}" <<SERVICE
[Unit]
Description=Rust-La-Panel web interface
After=network.target

[Service]
User=${PANEL_SERVICE_USER}
Group=${PANEL_SERVICE_GROUP}
WorkingDirectory=${APP_DIR}
EnvironmentFile=${ENV_FILE}
ExecStart=/usr/bin/python3 ${APP_DIR}/app.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
SERVICE

chmod 644 "${SERVICE_FILE}"

cat >"${SUDOERS_FILE}" <<SUDOERS
# Managed by install_panel.sh
${PANEL_SERVICE_USER} ALL=NOPASSWD: /bin/systemctl start ${RUST_SERVER_SERVICE}
${PANEL_SERVICE_USER} ALL=NOPASSWD: /bin/systemctl stop ${RUST_SERVER_SERVICE}
${PANEL_SERVICE_USER} ALL=NOPASSWD: /bin/systemctl restart ${RUST_SERVER_SERVICE}
${PANEL_SERVICE_USER} ALL=NOPASSWD: /bin/systemctl status ${RUST_SERVER_SERVICE}
${PANEL_SERVICE_USER} ALL=NOPASSWD: /bin/journalctl -u ${RUST_SERVER_SERVICE}
${PANEL_SERVICE_USER} ALL=NOPASSWD: /usr/local/bin/rust_install.sh
${PANEL_SERVICE_USER} ALL=NOPASSWD: /usr/local/bin/rust_update.sh
${PANEL_SERVICE_USER} ALL=NOPASSWD: /usr/local/bin/oxide_update.sh *
SUDOERS

chmod 440 "${SUDOERS_FILE}"
if command -v visudo >/dev/null 2>&1; then
visudo -cf "${SUDOERS_FILE}" >/dev/null
fi

chown -R "${PANEL_SERVICE_USER}:${PANEL_SERVICE_GROUP}" "${INSTALL_DIR}"

if command -v systemctl >/dev/null 2>&1; then
echo "[INFO] Enabling and starting ${PANEL_SERVICE_NAME}.service"
systemctl daemon-reload
systemctl enable --now "${PANEL_SERVICE_NAME}.service"
else
cat <<'MSG'
[INFO] Run this script as root (or re-run with sudo) to install the helper scripts
into /usr/local/bin. They remain available under scripts/ in the repository.
MSG
echo "[WARN] systemctl not found. Skipping automatic service enable."
fi

cat <<MSG
Rust-La-Panel installed into: ${INSTALL_DIR}
Python virtual environment: ${VENV_DIR}
Rust-La-Panel installed successfully.

To run the application manually:
${VENV_DIR}/bin/python ${APP_DIR}/app.py
- Application directory: ${APP_DIR}
- Service user: ${PANEL_SERVICE_USER}
- Panel service: ${PANEL_SERVICE_NAME}.service
- Rust server service: ${RUST_SERVER_SERVICE}

For systemd integration, create a unit similar to the example in README.md with:
ExecStart=${VENV_DIR}/bin/python ${APP_DIR}/app.py
You can adjust environment settings at ${ENV_FILE}. After any changes run:
sudo systemctl daemon-reload
sudo systemctl restart ${PANEL_SERVICE_NAME}.service
MSG