前言
在B站看到一个项目
仅用几十KB就构建了一个Minecraft服务端,运行更是只需要少得可怜的资源(esp32-c3都能运行),于是记录一下在Alpine Linux上的移植过程。
开始
因为使用的是 Alpine Linux,它底层使用 musl 而不是通常的 glibc (Ubuntu) 或 uClibc (Buildroot)。这需要一个专门针对 arm-linux-musleabihf 的交叉编译器,否则编译出的程序在板子上运行时会报错(例如 File not found 或段错误)。
因此我们需要获取 musl 交叉编译工具链,不过我建议在宿主机手动下载然后传到wsl,不然这速度太慢了。
# 下载工具链
wget https://musl.cc/arm-linux-musleabihf-cross.tgz
# 解压
tar -xf arm-linux-musleabihf-cross.tgz
echo 'export PATH=$PATH:/home/luckfox/Desktop/arm-linux-musleabihf-cross/bin' >> ~/.bashrc
source ~/.bashrc
arm-linux-musleabihf-gcc --version搭建编译环境,运行setup_env.sh
#!/bin/bash
# Setup script for BareIron development environment on WSL/Ubuntu
# Installs Java 21 and Node.js
set -e
echo "[1/3] Updating package lists..."
sudo apt update
echo "[2/3] Installing dependencies (curl, wget, tar, build-essential)..."
sudo apt install -y curl wget tar sudo build-essential
echo "[3/3] Installing Java 21..."
# Check if OpenJDK 21 is available in default repos
if apt-cache search openjdk-21-jdk | grep -q openjdk-21-jdk; then
sudo apt install -y openjdk-21-jdk
else
echo "OpenJDK 21 not found in default repositories. Adding PPA..."
sudo add-apt-repository -y ppa:openjdk-r/ppa
sudo apt update
sudo apt install -y openjdk-21-jdk
fi
# Verify Java installation
java -version
echo "Installing Node.js (Latest LTS)..."
# Using NodeSource for up-to-date Node.js
if ! command -v node &> /dev/null; then
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
else
echo "Node.js is already installed."
fi
# Verify Node installation
node -v
echo "--------------------------------------------------------"
echo "Setup Complete!"
echo "--------------------------------------------------------"
echo "1. Java 21 and Node.js have been installed."
echo ""
echo "Note: You need to manually install or configure your cross-compilation toolchain."
echo "For Luckfox Pico (Alpine), ensure 'arm-linux-musleabihf-gcc' is in your PATH."
echo ""
echo "Then you can proceed with:"
echo " ./extract_registries.sh"
echo " ./build_luckfox.sh --type alpine"
准备注册表数据:运行 ./extract_registries.sh 。(没权限自己添加),手动下载mc 1.21.8 server.jar,放到notchian文件夹,然后继续执行./extract_registries.sh
添加交叉编译编译脚本build_luckfox.sh
#!/bin/bash
# Build script for Luckfox Pico (RV1103)
# Target Architecture: ARMv7l (Cortex-A7)
set -e
# Default settings
BUILD_TYPE="buildroot" # Options: buildroot, ubuntu, alpine
OUTPUT_NAME="bareiron_luckfox"
# Check for registries
if [ ! -f "include/registries.h" ]; then
echo "Error: 'include/registries.h' is missing."
echo "Please run './extract_registries.sh' first (requires Minecraft server jar and Java 21+)."
exit 1
fi
# Help function
print_usage() {
echo "Usage: $0 [options]"
echo "Options:"
echo " --type <type> Target system type: 'buildroot' (default), 'ubuntu', or 'alpine'"
echo " --help Show this help message"
}
# Parse arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
--type) BUILD_TYPE="$2"; shift ;;
--help) print_usage; exit 0 ;;
*) echo "Unknown parameter: $1"; print_usage; exit 1 ;;
esac
shift
done
echo "Building for Luckfox Pico ($BUILD_TYPE)..."
# Set compiler based on type
if [ "$BUILD_TYPE" == "buildroot" ]; then
# Toolchain for Buildroot (uClibc)
DEFAULT_CC_PATH="/home/buildroot/luckfox-pico/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc"
CC="${CROSS_COMPILE:-$DEFAULT_CC_PATH}"
if ! command -v "$CC" &> /dev/null && [ ! -x "$CC" ]; then
echo "Error: Compiler not found at '$CC'"
echo "Please set CROSS_COMPILE environment variable to your toolchain path."
exit 1
fi
elif [ "$BUILD_TYPE" == "ubuntu" ]; then
# Toolchain for Ubuntu (glibc)
DEFAULT_CC_PATH="arm-none-linux-gnueabihf-gcc"
CC="${CROSS_COMPILE:-$DEFAULT_CC_PATH}"
if ! command -v "$CC" &> /dev/null; then
echo "Error: Compiler '$CC' not found in PATH."
echo "Please install 'gcc-arm-linux-gnueabihf' or set CROSS_COMPILE."
exit 1
fi
elif [ "$BUILD_TYPE" == "alpine" ]; then
# Toolchain for Alpine (musl)
# User needs to download a musl toolchain, e.g., from musl.cc or bootlin
DEFAULT_CC_PATH="arm-linux-musleabihf-gcc"
CC="${CROSS_COMPILE:-$DEFAULT_CC_PATH}"
# Try to find compiler in common locations if not in PATH
if ! command -v "$CC" &> /dev/null; then
# Check user-provided path from common locations
POTENTIAL_PATH="/home/luckfox/Desktop/arm-linux-musleabihf-cross/bin/$DEFAULT_CC_PATH"
if [ -x "$POTENTIAL_PATH" ]; then
CC="$POTENTIAL_PATH"
fi
fi
if ! command -v "$CC" &> /dev/null && [ ! -x "$CC" ]; then
echo "Error: Compiler '$CC' not found in PATH."
echo "For Alpine, you need a musl-based toolchain (e.g., arm-linux-musleabihf-gcc)."
echo "Please download one (e.g. from musl.cc) and add it to PATH or set CROSS_COMPILE."
exit 1
fi
else
echo "Error: Invalid build type '$BUILD_TYPE'. Use 'buildroot', 'ubuntu', or 'alpine'."
exit 1
fi
echo "Using compiler: $CC"
# Clean previous build
rm -f "$OUTPUT_NAME"
# Compile flags
# -O2: Optimization
# -Iinclude: Include directory
# -lpthread: Threading support (needed for glibc/uclibc, usually implicit in musl but harmless)
# -lm: Math library
# -static: For Alpine/Musl, static linking is often preferred for portability,
# but if you have the libraries on the board, dynamic is fine.
# We will default to dynamic to match other builds, but you can uncomment -static below if needed.
# LDFLAGS="-static"
$CC src/*.c -O2 -Iinclude -o "$OUTPUT_NAME" -lpthread -lm
if [ -f "$OUTPUT_NAME" ]; then
echo "Build successful! Binary: $OUTPUT_NAME"
file "$OUTPUT_NAME"
else
echo "Build failed."
exit 1
fi运行前修改项目中include/globals.h,在嵌入式系统(如 ESP32)上, task_yield() 通常用于让出 CPU 时间片给其他任务,但在 Linux/PC 上,如果这个宏定义为空(或者只是一个空操作),程序就会在一个死循环里全速空转,导致单核 CPU 占用率飙升到 100%。
Linux/Alpine : 使用 usleep(1000) (休眠 1 毫秒)
#ifndef H_GLOBALS
#define H_GLOBALS
#include <stdint.h>
#include <unistd.h>
#ifdef ESP_PLATFORM
#define WIFI_SSID "your-ssid"
#define WIFI_PASS "your-password"
void task_yield ();
#else
#ifdef _WIN32
#define task_yield() Sleep(1)
#else
#define task_yield() usleep(1000)
#endif
#endif
#define true 1
#define false 0
// TCP port, Minecraft's default is 25565
#define PORT 25565
// How many players to keep in memory, NOT the amount of concurrent players
// Even when offline, players who have logged on before take up a slot
#define MAX_PLAYERS 16
// How many mobs to allocate memory for
#define MAX_MOBS (MAX_PLAYERS)
// Manhattan distance at which mobs despawn
#define MOB_DESPAWN_DISTANCE 256
// Server game mode: 0 - survival; 1 - creative; 2 - adventure; 3 - spectator
#define GAMEMODE 0
// Max render distance, determines how many chunks to send
#define VIEW_DISTANCE 2
// Time between server ticks in microseconds (default = 1s)
#define TIME_BETWEEN_TICKS 1000000
// Calculated from TIME_BETWEEN_TICKS
#define TICKS_PER_SECOND ((float)1000000 / TIME_BETWEEN_TICKS)
// Initial world generation seed, will be hashed on startup
// Used in generating terrain and biomes
#define INITIAL_WORLD_SEED 0xA103DE6C
// Initial general RNG seed, will be hashed on startup
// Used in random game events like item drops and mob behavior
#define INITIAL_RNG_SEED 0xE2B9419
// Size of each bilinearly interpolated area ("minichunk")
// For best performance, CHUNK_SIZE should be a power of 2
#define CHUNK_SIZE 8
// Terrain low point - should start a bit below sea level for rivers/lakes
#define TERRAIN_BASE_HEIGHT 60
// Cave generation Y level
#define CAVE_BASE_DEPTH 24
// Size of every major biome in multiples of CHUNK_SIZE
// For best performance, should also be a power of 2
#define BIOME_SIZE (CHUNK_SIZE * 8)
// Calculated from BIOME_SIZE
#define BIOME_RADIUS (BIOME_SIZE / 2)
// How many visited chunk coordinates to "remember"
// The server will not re-send chunks that the player has recently been in
// Must be at least 1, otherwise chunks will be sent on each position update
#define VISITED_HISTORY 4
// How many player-made block changes to allow
// Determines the fixed amount of memory allocated to blocks
#define MAX_BLOCK_CHANGES 20000
// If defined, writes and reads world data to/from disk (or flash).
// This is a synchronous operation, and can cause performance issues if
// frequent random disk access is slow. Data is still stored in and
// accessed from memory - reading from disk is only done on startup.
// When targeting ESP-IDF, LittleFS is used to manage flash reads and
// writes. Flash is typically *very* slow and unreliable, which is why
// this option is disabled by default when targeting ESP-IDF.
#ifndef ESP_PLATFORM
#define SYNC_WORLD_TO_DISK
#endif
// The minimum interval (in microseconds) at which certain data is written
// to disk/flash. Bounded on the low end by TIME_BETWEEN_TICKS. By default,
// applies only to player data. Block changes are written as soon as they
// are made, but in much smaller portions. Set DISK_SYNC_BLOCKS_ON_INTERVAL
// to make this apply to block changes as well.
#define DISK_SYNC_INTERVAL 15000000
// Whether to sync block changes to disk on an interval, instead of syncing
// on each change. On systems with fast random disk access, this shouldn't
// be necessary.
// #define DISK_SYNC_BLOCKS_ON_INTERVAL
// Time in microseconds to spend waiting for data transmission before
// timing out. Default is 15s, which leaves 5s to prevent starving other
// clients from Keep Alive packets.
#define NETWORK_TIMEOUT_TIME 15000000
// Size of the receive buffer for incoming string data
#define MAX_RECV_BUF_LEN 256
// If defined, sends the server brand to clients. Doesn't do much, but will
// show up in the top-left of the F3/debug menu, in the Minecraft client.
// You can change the brand string in the "brand" variable in src/globals.c
#define SEND_BRAND
// If defined, rebroadcasts ALL incoming movement updates, disconnecting
// movement from the server's tickrate. This makes movement much smoother
// on very low tickrates, at the cost of potential network instability when
// hosting more than just a couple of players. When disabling this on low
// tickrates, consider disabling SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT too.
#define BROADCAST_ALL_MOVEMENT
// If defined, scales the frequency at which player movement updates are
// broadcast based on the amount of players, reducing overhead for higher
// player counts. For very many players, makes movement look jittery.
// It is not recommended to use this if BROADCAST_ALL_MOVEMENT is disabled
// on low tickrates, as that might drastically decrease the update rate.
#define SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT
// If defined, calculates fluid flow when blocks are updated near fluids
// Somewhat computationally expensive and potentially unstable
#define DO_FLUID_FLOW
// If defined, allows players to craft and use chests.
// Chests take up 15 block change slots each, require additional checks,
// and use some terrible memory hacks to function. On some platforms, this
// could cause bad performance or even crashes during gameplay.
#define ALLOW_CHESTS
// If defined, enables flight for all players. As a side-effect, allows
// players to sprint when starving.
// #define ENABLE_PLAYER_FLIGHT
// If defined, enables the item pickup animation when mining a block/
// Does not affect how item pickups work! Items from broken blocks still
// get placed directly in the inventory, this is just an animation.
// Relatively inexpensive, though requires sending a few more packets
// every time a block is broken.
#define ENABLE_PICKUP_ANIMATION
// If defined, players are able to receive damage from nearby cacti.
#define ENABLE_CACTUS_DAMAGE
// If defined, logs unrecognized packet IDs
// #define DEV_LOG_UNKNOWN_PACKETS
// If defined, logs cases when packet length doesn't match parsed byte count
#define DEV_LOG_LENGTH_DISCREPANCY
// If defined, log chunk generation events
// #define DEV_LOG_CHUNK_GENERATION
// If defined, allows dumping world data by sending 0xBEEF (big-endian),
// and uploading world data by sending 0xFEED, followed by the data buffer.
// Doesn't implement authentication, hence disabled by default.
// #define DEV_ENABLE_BEEF_DUMPS
#define STATE_NONE 0
#define STATE_STATUS 1
#define STATE_LOGIN 2
#define STATE_TRANSFER 3
#define STATE_CONFIGURATION 4
#define STATE_PLAY 5
extern ssize_t recv_count;
extern uint8_t recv_buffer[MAX_RECV_BUF_LEN];
extern uint32_t world_seed;
extern uint32_t rng_seed;
extern uint16_t world_time;
extern uint32_t server_ticks;
extern char motd[];
extern uint8_t motd_len;
#ifdef SEND_BRAND
extern char brand[];
extern uint8_t brand_len;
#endif
extern uint16_t client_count;
typedef struct {
short x;
short z;
uint8_t y;
uint8_t block;
} BlockChange;
#pragma pack(push, 1)
typedef struct {
uint8_t uuid[16];
char name[16];
int client_fd;
short x;
uint8_t y;
short z;
short visited_x[VISITED_HISTORY];
short visited_z[VISITED_HISTORY];
#ifdef SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT
uint16_t packets_since_update;
#endif
int8_t yaw;
int8_t pitch;
uint8_t grounded_y;
uint8_t health;
uint8_t hunger;
uint16_t saturation;
uint8_t hotbar;
uint16_t inventory_items[41];
uint16_t craft_items[9];
uint8_t inventory_count[41];
uint8_t craft_count[9];
// Usage depends on player's flags, see below
// When no flags are set, acts as cursor item ID
uint16_t flagval_16;
// Usage depends on player's flags, see below
// When no flags are set, acts as cursor item count
uint8_t flagval_8;
// 0x01 - attack cooldown, uses flagval_8 as the timer
// 0x02 - has not spawned yet
// 0x04 - sneaking
// 0x08 - sprinting
// 0x10 - eating, makes flagval_16 act as eating timer
// 0x20 - client loading, uses flagval_16 as fallback timer
// 0x40 - movement update cooldown
// 0x80 - craft_items lock (for storing pointers)
uint8_t flags;
} PlayerData;
typedef struct {
uint8_t type;
short x;
// When the mob is dead (health is 0), the Y coordinate acts
// as a timer for deleting and deallocating the mob
uint8_t y;
short z;
// Lower 5 bits: health
// Middle 1 bit: sheep sheared, unused for other mobs
// Upper 2 bits: panic timer
uint8_t data;
} MobData;
#pragma pack(pop)
union EntityDataValue {
uint8_t byte;
int pose;
};
typedef struct {
uint8_t index;
// 0 - Byte
// 21 - Pose
int type;
union EntityDataValue value;
} EntityData;
extern BlockChange block_changes[MAX_BLOCK_CHANGES];
extern int block_changes_count;
extern PlayerData player_data[MAX_PLAYERS];
extern int player_data_count;
extern MobData mob_data[MAX_MOBS];
#endif执行命令
chmod +x build_luckfox.sh
./build_luckfox.sh --type alpine传输到板子root目录上,配置后台运行
编写openrc/bareiron
#!/sbin/openrc-run
name="BareIron Minecraft Server"
description="Minimalist Minecraft server for embedded systems"
# Path to the executable
command="/root/bareiron_luckfox"
# Background the process
command_background=true
# PID file location
pidfile="/run/${RC_SVCNAME}.pid"
# Working directory (where world.bin is stored)
directory="/root"
# Redirect output to log file (optional, helps debugging)
output_log="/var/log/bareiron.log"
error_log="/var/log/bareiron.err"
depend() {
# Start after networking is ready
need net
# Use DNS if available
use dns logger
}
start_pre() {
# Ensure the binary exists and is executable
if [ ! -x "$command" ]; then
eerror "Executable not found or not executable: $command"
return 1
fi
einfo "Starting $name..."
}
编写install_service.sh
#!/bin/sh
# Installation script for BareIron OpenRC service on Alpine Linux
# Run this on your Luckfox Pico
SERVICE_NAME="bareiron"
SERVICE_SCRIPT="openrc/bareiron"
TARGET_DIR="/etc/init.d"
BINARY_NAME="bareiron_luckfox"
BINARY_PATH="/root/$BINARY_NAME"
# Check if running as root
if [ "$(id -u)" -ne 0 ]; then
echo "Error: This script must be run as root."
exit 1
fi
# Check if binary exists on the board
if [ ! -f "$BINARY_PATH" ]; then
echo "Warning: Binary '$BINARY_PATH' not found."
echo "Make sure you have copied '$BINARY_NAME' to /root/"
fi
echo "Installing service script..."
if [ ! -f "$SERVICE_SCRIPT" ]; then
echo "Error: Service script '$SERVICE_SCRIPT' not found in current directory."
echo "Please upload the 'openrc' folder to the board first."
exit 1
fi
cp "$SERVICE_SCRIPT" "$TARGET_DIR/$SERVICE_NAME"
chmod +x "$TARGET_DIR/$SERVICE_NAME"
echo "Service installed to $TARGET_DIR/$SERVICE_NAME"
echo "Adding service to default runlevel (autostart on boot)..."
rc-update add "$SERVICE_NAME" default
echo "---------------------------------------------------"
echo "Installation Complete!"
echo "---------------------------------------------------"
echo "To start the server now:"
echo " rc-service $SERVICE_NAME start"
echo ""
echo "To stop the server:"
echo " rc-service $SERVICE_NAME stop"
echo ""
echo "To check status:"
echo " rc-service $SERVICE_NAME status"
echo ""
echo "To view logs:"
echo " tail -f /var/log/bareiron.log"执行命令,然后删除openrc/bareiron和install_service.sh
chmod +x install_service.sh
./install_service.sh- 启动: rc-service bareiron start
- 停止: rc-service bareiron stop
查看日志: tail -f /var/log/bareiron.log
如何游玩
- 下载 : 直接搜索 "HMCL 下载" 或访问其 GitHub/官网。
- 使用方法 :
- 打开 HMCL。
- 点击“账户” -> 添加离线账户 (随便起个名字)。
- 点击“下载” -> 选择 1.21.8 版本(必须与服务器版本一致)。
- 下载完成后,点击“启动游戏”。
进入游戏后,选择“多人游戏” -> “添加服务器” -> 输入Luckfox 板子的 IP 地址 -> 连接。
配置内网穿透
使用樱花frp创建tcp隧道重新配置内外穿透
配置域名,具体看设置 SRV 解析
多人服务器,填入域名(一般延迟80ms)
enjoy yourself!
评论已关闭