From e89943224d4aca05fb49c0b3578090027776e535 Mon Sep 17 00:00:00 2001 From: Dawnsorrow Date: Sat, 20 Jun 2026 22:47:19 -0500 Subject: [PATCH] Add Eden Smash Ultimate VPS room deployment scripts. Provides install/update systemd workflow for git-based deploys to Ubuntu VPS. Co-authored-by: Cursor --- .gitignore | 12 +++ README.md | 131 +++++++++++++++++++++++++++++++++ banlist.txt | 1 + env.example | 26 +++++++ scripts/install.sh | 151 ++++++++++++++++++++++++++++++++++++++ scripts/start-room.sh | 55 ++++++++++++++ scripts/update.sh | 35 +++++++++ systemd/eden-room.service | 24 ++++++ 8 files changed, 435 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 banlist.txt create mode 100644 env.example create mode 100755 scripts/install.sh create mode 100755 scripts/start-room.sh create mode 100755 scripts/update.sh create mode 100644 systemd/eden-room.service diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..420482c --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Secrets — never commit +.env +*.token +token + +# Runtime / local state +logs/ +*.log +banlist.txt.bak + +# Downloaded binary (fetched by install.sh) +bin/eden-room diff --git a/README.md b/README.md new file mode 100644 index 0000000..1858c3f --- /dev/null +++ b/README.md @@ -0,0 +1,131 @@ +# eden-room + +Deploy a dedicated [Eden](https://eden-emu.dev/) multiplayer lobby on an Ubuntu VPS. Designed for git-based deploys (`git clone` / `git pull`). + +Default game: **Super Smash Bros. Ultimate** (`01006A800016E000`). + +## Requirements + +- Ubuntu VPS with a public IP +- Port **24872** open (TCP + UDP) in cloud firewall and UFW +- Eden token from your PC (`~/.config/eden/qt-config.ini` → `eden_token`) +- Optional: DNS A record (grey cloud / DNS-only in Cloudflare) pointing at the VPS + +> Cloudflare **proxied** (orange cloud) records do **not** work for game traffic. Use DNS-only. + +## Quick start (VPS) + +```bash +sudo apt update && sudo apt install -y git curl + +sudo git clone https://your-gitea.example.com/you/eden-room.git /opt/eden-room +cd /opt/eden-room + +cp env.example .env +chmod 600 .env +nano .env # set EDEN_TOKEN, ROOM_NAME, etc. + +sudo ./scripts/install.sh +sudo systemctl start eden-room +sudo journalctl -u eden-room -f +``` + +## Deploy updates + +On the VPS after pushing changes to Gitea: + +```bash +cd /opt/eden-room +sudo ./scripts/update.sh +``` + +Re-download the `eden-room` binary when Eden releases a new version: + +```bash +# bump EDEN_RELEASE_VERSION in .env first, then: +sudo UPDATE_BINARY=1 ./scripts/update.sh +``` + +## Configuration (`.env`) + +| Variable | Description | +|----------|-------------| +| `EDEN_TOKEN` | From Eden config on your PC (required for public listing) | +| `ROOM_NAME` | 4–20 characters, shown in public lobby browser | +| `ROOM_DESCRIPTION` | Optional lobby description | +| `PREFERRED_GAME` | Display name for the lobby | +| `PREFERRED_GAME_ID` | Hex title ID (`01006A800016E000` for Smash Ultimate) | +| `PORT` | Room port (default `24872`) | +| `MAX_MEMBERS` | 2–16 players | +| `PASSWORD` | Optional room password | +| `WEB_API_URL` | Metaserver URL (default `https://api.ynet-fun.xyz`) | +| `EDEN_RELEASE_VERSION` | Eden release tag for binary download | +| `EDEN_ROOM_ARCH` | `x86_64-unknown-linux-musl` or `aarch64-unknown-linux-musl` | + +## How players connect + +**Public lobby:** Eden → Multiplayer → Browse Public Game Lobby + +**Direct connect:** Eden → Multiplayer → Direct Connect + +- Server: your domain or VPS IP +- Port: `24872` (or whatever you set in `.env`) + +In-game, use **local wireless / LDN** — not Nintendo Online. + +## Verify public listing + +```bash +curl -s https://api.ynet-fun.xyz/lobby | jq '.rooms[] | select(.name == "YOUR_ROOM_NAME")' +``` + +## Troubleshooting + +**Service won't start** + +```bash +sudo journalctl -u eden-room -n 50 --no-pager +cat /opt/eden-room/logs/eden-room.log +``` + +**Public listing fails but room works** + +Some VPS hosts hit JWT registration errors. Players can still Direct Connect. If the lobby browser doesn't load, add to `/etc/hosts`: + +``` +28.165.181.135 api.ynet-fun.xyz +``` + +**Check port is listening** + +```bash +sudo ss -tulnp | grep 24872 +``` + +## Repository layout + +``` +eden-room/ +├── env.example # copy to .env (gitignored) +├── banlist.txt # room ban list (persisted) +├── scripts/ +│ ├── install.sh # first-time VPS setup +│ ├── update.sh # git pull + restart +│ └── start-room.sh # invoked by systemd +├── systemd/ +│ └── eden-room.service # reference unit file +└── bin/ + └── eden-room # downloaded by install.sh (gitignored) +``` + +## Push to Gitea + +On your dev machine: + +```bash +cd eden-room +git remote add origin https://your-gitea.example.com/you/eden-room.git +git push -u origin main +``` + +Never commit `.env` — it contains your Eden token. diff --git a/banlist.txt b/banlist.txt new file mode 100644 index 0000000..11a32a2 --- /dev/null +++ b/banlist.txt @@ -0,0 +1 @@ +YuzuRoom-BanList-1 diff --git a/env.example b/env.example new file mode 100644 index 0000000..6456c1a --- /dev/null +++ b/env.example @@ -0,0 +1,26 @@ +# Copy this file to .env and fill in your values: +# cp env.example .env && chmod 600 .env && nano .env + +# Required for public lobby listing (from ~/.config/eden/qt-config.ini on your PC) +EDEN_TOKEN= + +# Room settings (room name must be 4–20 characters) +ROOM_NAME=SmashLobby +ROOM_DESCRIPTION=Super Smash Bros Ultimate public lobby +PREFERRED_GAME=Super Smash Bros. Ultimate +PREFERRED_GAME_ID=01006A800016E000 + +# Network +PORT=24872 +MAX_MEMBERS=8 +BIND_ADDRESS=0.0.0.0 +# Leave empty for no password +PASSWORD= + +# Metaserver (public listing) +WEB_API_URL=https://api.ynet-fun.xyz + +# eden-room binary download (change when updating Eden) +EDEN_RELEASE_VERSION=v0.2.0-rc2 +EDEN_ROOM_ARCH=x86_64-unknown-linux-musl +# EDEN_ROOM_ARCH=aarch64-unknown-linux-musl # use on ARM VPS diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..24a1eca --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,151 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${REPO_DIR}/.env" +ENV_EXAMPLE="${REPO_DIR}/env.example" +BINARY_DIR="${REPO_DIR}/bin" +BINARY="${BINARY_DIR}/eden-room" +SERVICE_NAME="eden-room" +SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" +EDEN_USER="eden" +EDEN_GROUP="eden" + +BASE_URL="https://git.eden-emu.dev/eden-emu/eden/releases/download" + +require_root() { + if [[ "${EUID}" -ne 0 ]]; then + echo "Run as root: sudo $0" >&2 + exit 1 + fi +} + +load_env() { + if [[ -f "${ENV_FILE}" ]]; then + # shellcheck source=/dev/null + source "${ENV_FILE}" + elif [[ -f "${ENV_EXAMPLE}" ]]; then + # shellcheck source=/dev/null + source "${ENV_EXAMPLE}" + fi + : "${EDEN_RELEASE_VERSION:=v0.2.0-rc2}" + : "${EDEN_ROOM_ARCH:=x86_64-unknown-linux-musl}" + : "${PORT:=24872}" +} + +create_user() { + if ! id "${EDEN_USER}" &>/dev/null; then + useradd -r -s /usr/sbin/nologin -d "${REPO_DIR}" "${EDEN_USER}" + echo "Created system user: ${EDEN_USER}" + fi +} + +setup_env() { + if [[ ! -f "${ENV_FILE}" ]]; then + cp "${ENV_EXAMPLE}" "${ENV_FILE}" + chmod 600 "${ENV_FILE}" + echo "Created ${ENV_FILE} from env.example — edit it before starting the service." + else + echo "Using existing ${ENV_FILE}" + fi +} + +download_binary() { + mkdir -p "${BINARY_DIR}" + local url="${BASE_URL}/${EDEN_RELEASE_VERSION}/eden-room-${EDEN_ROOM_ARCH}" + echo "Downloading eden-room ${EDEN_RELEASE_VERSION} (${EDEN_ROOM_ARCH})..." + curl -fsSL "${url}" -o "${BINARY}.tmp" + chmod +x "${BINARY}.tmp" + mv "${BINARY}.tmp" "${BINARY}" + echo "Installed ${BINARY}" +} + +setup_firewall() { + if command -v ufw &>/dev/null && ufw status | grep -q "Status: active"; then + ufw allow "${PORT}/tcp" comment "eden-room" + ufw allow "${PORT}/udp" comment "eden-room" + echo "UFW rules added for port ${PORT} (tcp/udp)" + else + echo "UFW not active — open port ${PORT}/tcp and ${PORT}/udp in your cloud firewall manually." + fi +} + +install_systemd() { + cat > "${SERVICE_FILE}" </dev/null || true + chmod +x "${BINARY}" +} + +metaserver_hosts_hint() { + if ! grep -q 'api.ynet-fun.xyz' /etc/hosts 2>/dev/null; then + echo "" + echo "Tip: if public lobby registration fails, add to /etc/hosts:" + echo " 28.165.181.135 api.ynet-fun.xyz" + fi +} + +binary_only() { + require_root + load_env + download_binary + fix_permissions + echo "Binary updated at ${BINARY}" +} + +main() { + if [[ "${1:-}" == "--binary-only" ]]; then + binary_only + return + fi + + require_root + load_env + create_user + setup_env + download_binary + setup_firewall + install_systemd + fix_permissions + metaserver_hosts_hint + + echo "" + echo "Install complete." + echo " 1. Edit ${ENV_FILE} (set EDEN_TOKEN and ROOM_NAME)" + echo " 2. sudo systemctl start ${SERVICE_NAME}" + echo " 3. sudo journalctl -u eden-room -f" +} + +main "$@" diff --git a/scripts/start-room.sh b/scripts/start-room.sh new file mode 100755 index 0000000..4c801ae --- /dev/null +++ b/scripts/start-room.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${REPO_DIR}/.env" +BINARY="${REPO_DIR}/bin/eden-room" +BANLIST="${REPO_DIR}/banlist.txt" +LOG_FILE="${REPO_DIR}/logs/eden-room.log" + +if [[ ! -f "${ENV_FILE}" ]]; then + echo "Missing ${ENV_FILE}. Copy env.example to .env and configure it." >&2 + exit 1 +fi + +# shellcheck source=/dev/null +source "${ENV_FILE}" + +: "${ROOM_NAME:?ROOM_NAME is required in .env}" +: "${PREFERRED_GAME:?PREFERRED_GAME is required in .env}" +: "${PREFERRED_GAME_ID:?PREFERRED_GAME_ID is required in .env}" +: "${PORT:=24872}" +: "${MAX_MEMBERS:=8}" +: "${BIND_ADDRESS:=0.0.0.0}" +: "${WEB_API_URL:=https://api.ynet-fun.xyz}" + +if [[ ! -x "${BINARY}" ]]; then + echo "Missing ${BINARY}. Run scripts/install.sh first." >&2 + exit 1 +fi + +mkdir -p "${REPO_DIR}/logs" + +ARGS=( + --room-name "${ROOM_NAME}" + --room-description "${ROOM_DESCRIPTION:-Smash Ultimate lobby}" + --preferred-game "${PREFERRED_GAME}" + --preferred-game-id "${PREFERRED_GAME_ID}" + --port "${PORT}" + --max-members "${MAX_MEMBERS}" + --bind-address "${BIND_ADDRESS}" + --ban-list-file "${BANLIST}" + --log-file "${LOG_FILE}" +) + +if [[ -n "${PASSWORD:-}" ]]; then + ARGS+=(--password "${PASSWORD}") +fi + +if [[ -n "${EDEN_TOKEN:-}" ]]; then + ARGS+=(--token "${EDEN_TOKEN}" --web-api-url "${WEB_API_URL}") +else + echo "Warning: EDEN_TOKEN is empty — hosting as unlisted/private room." >&2 +fi + +exec "${BINARY}" "${ARGS[@]}" diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100755 index 0000000..07d5982 --- /dev/null +++ b/scripts/update.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SERVICE_NAME="eden-room" +UPDATE_BINARY="${UPDATE_BINARY:-0}" + +require_root() { + if [[ "${EUID}" -ne 0 ]]; then + echo "Run as root: sudo $0" >&2 + exit 1 + fi +} + +main() { + require_root + + echo "Pulling latest config from git..." + git -C "${REPO_DIR}" pull --ff-only + + if [[ "${UPDATE_BINARY}" == "1" ]]; then + echo "Re-downloading eden-room binary..." + "${REPO_DIR}/scripts/install.sh" --binary-only + else + chown -R eden:eden "${REPO_DIR}" + chmod 700 "${REPO_DIR}/scripts/"*.sh + chmod 600 "${REPO_DIR}/.env" 2>/dev/null || true + fi + + systemctl restart "${SERVICE_NAME}" + echo "Updated and restarted ${SERVICE_NAME}." + systemctl status "${SERVICE_NAME}" --no-pager +} + +main "$@" diff --git a/systemd/eden-room.service b/systemd/eden-room.service new file mode 100644 index 0000000..b50bad0 --- /dev/null +++ b/systemd/eden-room.service @@ -0,0 +1,24 @@ +# Reference unit file — install.sh writes /etc/systemd/system/eden-room.service +# with paths adjusted to your clone location (default: /opt/eden-room). + +[Unit] +Description=Eden Multiplayer Room +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=eden +Group=eden +WorkingDirectory=/opt/eden-room +ExecStart=/opt/eden-room/scripts/start-room.sh +Restart=on-failure +RestartSec=10 +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/opt/eden-room +PrivateTmp=true + +[Install] +WantedBy=multi-user.target