Created
June 5, 2011 15:17
-
-
Save leafstorm/1009040 to your computer and use it in GitHub Desktop.
Minecraft server management script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# craftr | |
# This manages a Craftbukkit server. It helps you start it, stop it, | |
# back up your world, and download specific Craftbukkit builds. | |
# You must "gem install thor" first. | |
# Run it in your server directory. | |
require 'thor' | |
require 'yaml' | |
THIS_FILE = File.expand_path(__FILE__) | |
LAUNCHER_SCRIPT = <<EOS | |
#!/bin/sh | |
MEMORY=$1 | |
JAR=$2 | |
STATUSFILE=$3 | |
if test -f $STATUSFILE; then | |
echo Error: Will not start while status file $3 exists | |
exit 1 | |
fi | |
echo running > $STATUSFILE | |
java -Xms${MEMORY}M -Xmx${MEMORY}M -jar $JAR nogui | |
rm $STATUSFILE | |
EOS | |
CRAFTR_KEYS = %w[jvm-memory server-build status-file console-screen-name | |
backups-dir runtime-level-dir before-start after-stop | |
server-type] | |
DEFAULT_SERVER_CONFIG = { | |
"jvm-memory" => 1024, | |
"server-build" => "recommended", | |
"status-file" => ".server.status", | |
"console-screen-name" => "minecraft", | |
"server-ip" => "", | |
"server-port" => 25565, | |
"level-name" => "world", | |
"online-mode" => true, | |
"max-players" => 20, | |
"spawn-monsters" => true, | |
"pvp" => true, | |
"hellworld" => false, | |
"backups-dir" => "world-backups", | |
"runtime-level-dir" => nil, | |
"before-start" => nil, | |
"after-stop" => nil | |
} | |
CONFIGURATION_HELP = <<EOH | |
All keys not described here are copied to the server.properties file. That | |
way, Craftr has access to the keys it needs, and you only have to maintain | |
one file. (The server.properties file is regenerated whenever you start the | |
server.) | |
jvm-memory: The amount of memory to allocate for the server's heap. 512 is | |
good for smaller machines, 1024 for ones with more memory to spare. Anything | |
less than 512 is not recommended. | |
server-build: The build of Craftbukkit to run the server with. Use an | |
integer to use that numbered build, "latest" to just use the latest | |
snapshot, or "recommended" to use the latest recommended build (redownload it | |
with "craftr update"). If you don't want to use Craftbukkit, but rather | |
Notch's Minecraft server, set this to "vanilla". | |
status-file: The name of the file to store the server's current state in. | |
".server.status" should be fine, but if you don't want it hidden you could | |
change it to "server.status". | |
console-screen-name: The name of the screen session to start the server in. | |
You should only need to change this if you run multiple servers. Each server | |
running on the same machine *must* have the same name. | |
backups-dir: The directory to make world backups into. All backups are .tar.gz | |
files stamped with the current date and time, and they contain the complete | |
level. You can restore them with "craftr restore". (However, you must take | |
care of deleting old backups yourself.) | |
runtime-level-dir: If you set this, then the world will be copied into a | |
subdirectory of this directory whenever the server starts, then copied back | |
after it stops. You can use this with a RAM disk or something. (Note that this | |
messes with level-name in the server.properties file to do its job.) (Also | |
note that this is not actually implemented.) | |
before-start: A command that should be run whenever the server starts. | |
after-stop: A command that should be run after the server stops. | |
EOH | |
LATEST_URL = "http://ci.bukkit.org/job/dev-CraftBukkit/lastSuccessfulBuild/artifact/target/craftbukkit-0.0.1-SNAPSHOT.jar" | |
RECOMMENDED_URL = "http://ci.bukkit.org/job/dev-CraftBukkit/promotion/latest/Recommended/artifact/target/craftbukkit-0.0.1-SNAPSHOT.jar" | |
VANILLA_URL = "https://s3.amazonaws.com/MinecraftDownload/launcher/minecraft_server.jar" | |
BUILD_TEMPLATE = "http://ci.bukkit.org/job/dev-CraftBukkit/%d/artifact/target/craftbukkit-0.0.1-SNAPSHOT.jar" | |
class Craftr < Thor | |
include Thor::Actions | |
desc "makeconfig", "generates a default config file" | |
def makeconfig | |
if file_exists(server_config_path) | |
say "Sorry, but your server configuration file already exists." | |
say "If you need it regenerated from default, delete or move it." | |
else | |
File.open(server_config_path, 'w') do |io| | |
io.write "# This is the craftr server configuration file.\n" | |
io.write "# In addition to configuring craftr's settings,\n" | |
io.write "# it is also used to autogenerate your server.properties.\n" | |
io.write "# Any keys not used by craftr will be automatically copied\n" | |
io.write "# to the server.properties file before the server is started.\n" | |
io.write "# Run 'craftr confighelp' for more information\n" | |
YAML::dump(DEFAULT_SERVER_CONFIG, io) | |
end | |
end | |
end | |
desc "confighelp", "prints help for the server-config.yml file" | |
def confighelp | |
puts CONFIGURATION_HELP | |
end | |
desc "update", "downloads the Craftbukkit JAR" | |
def update | |
build = server_config["server-build"] | |
if build == "vanilla" | |
say "Downloading Notch's Minecraft server..." | |
get VANILLA_URL, jar_filename | |
elsif build == "latest" | |
say "Downloading latest Craftbukkit build..." | |
get LATEST_URL, jar_filename | |
elsif build == "recommended" | |
say "Downloading latest Craftbukkit recommended build..." | |
get RECOMMENDED_URL, jar_filename | |
elsif build.is_a?(Integer) | |
target = jar_filename | |
if file_exists(target) | |
say "Build #{build} is already downloaded." | |
say "Delete the #{target} file if you need it redownloaded." | |
else | |
say "Downloading Craftbukkit build #{build}..." | |
get (BUILD_TEMPLATE % build), "craftbukkit-#{build}.jar" | |
end | |
else | |
halt "invalid build (must be \"latest\", \"recommended\", \"vanilla\" or an integer)" | |
end | |
end | |
desc "start", "starts the server" | |
method_options :console => :false | |
def start | |
ensure_server_shutdown | |
ensure_jar_exists | |
ensure_launcher_exists | |
update_properties | |
detach = options[:console] ? "" : "-d " | |
run(server_config["before-start"]) if !server_config["before-start"].nil? | |
memory = server_config["jvm-memory"] | |
statusfile = server_config["status-file"] | |
run( | |
"screen -m #{detach}-S #{screen_name} sh .craftr-launcher.sh " + | |
"#{memory} #{jar_filename} #{statusfile}", | |
:verbose => false | |
) | |
command_server "say Server online." unless options[:console] | |
end | |
desc "stop", "stops the server" | |
def stop | |
ensure_server_running | |
command_server "stop" | |
say "Stopping server..." | |
counter = 0 | |
while counter < 31 | |
sleep 1 | |
if server_state == :stopped | |
say "Server shut down successfully." | |
run(server_config["after-stop"]) if !server_config["after-stop"].nil? | |
return | |
else | |
counter = counter + 1 | |
end | |
end | |
say "The server is taking too long to shut down. Something's wrong." | |
end | |
desc "status", "checks the server status" | |
def status | |
case server_state | |
when :running | |
say "The server is currently running." | |
when :crashed | |
say "The server crashed without shutting down safely!" | |
say "Check the logs to diagnose the problem, then delete the status file." | |
when :stopped | |
say "The server is not running right now." | |
else | |
say "The server's status could not be determined!" | |
end | |
end | |
desc "running", "fails if server is not running (for scripts)" | |
def running | |
case server_state | |
when :running | |
exit 0 | |
else | |
exit 1 | |
end | |
end | |
desc "console", "opens the console (press ctrl-a then d to exit console)" | |
def console | |
ensure_server_running | |
run "screen -r #{screen_name}", :verbose => false | |
end | |
desc "command COMMAND...", "sends a console command to the server" | |
def command(*cmds) | |
ensure_server_running | |
command_server(cmds.join(" ")) | |
end | |
desc "backup", "creates a new backup of the world" | |
def backup | |
save_if_running | |
save_locked do | |
worldpath = path(server_config["level-name"]) | |
filename = safe_level_name + Time.now.strftime("-%Y-%m-%d-%H-%M-%S") + ".tar.gz" | |
say "Backing up level to #{filename}..." | |
backupdir = path(server_config["backups-dir"]) | |
Dir.mkdir(backupdir) if !Dir.exist?(backupdir) | |
target = path(backupdir, filename) | |
run "tar -cjf '#{target}' -C '#{worldpath}' ." | |
end | |
end | |
desc "restore ARCHIVE", "restore the world from a backup" | |
def restore(archive) | |
ensure_server_shutdown | |
worldpath = path(server_config["level-name"]) | |
archive = File.expand_path(archive) | |
run "tar -xf '#{target}' -C '#{worldpath}'" | |
end | |
private | |
def halt(message) | |
say "Error: #{message}" | |
exit | |
end | |
def path(*components) | |
File.expand_path(File.join(*components), destination_root) | |
end | |
def file_exists(*components) | |
File.exist?(path(*components)) | |
end | |
def server_config | |
@config ||= begin | |
if file_exists(server_config_path) | |
File.open(server_config_path) { |io| return YAML.load(io) } | |
else | |
DEFAULT_SERVER_CONFIG | |
end | |
end | |
end | |
def server_config_path | |
path( | |
ENV["CRAFTR_SERVER_CONFIG"] || options[:server] || "server-config.yml" | |
) | |
end | |
def safe_level_name(level=nil) | |
level = level || server_config["level-name"] | |
level.gsub(/[^a-zA-Z0-9_-]/, "-") | |
end | |
def jar_filename(build=nil) | |
build = build || server_config["server-build"] | |
build == "vanilla" ? path("minecraft_server.jar") : path("craftbukkit-#{build}.jar") | |
end | |
def ensure_jar_exists | |
if (not file_exists(jar_filename)) | |
invoke :update | |
end | |
end | |
def ensure_launcher_exists | |
if (not file_exists(".craftr-launcher.sh")) | |
create_file ".craftr-launcher.sh", LAUNCHER_SCRIPT, :verbose => false | |
end | |
end | |
def server_properties | |
props = server_config.clone | |
if props["runtime-level-dir"] | |
props["level-name"] = File.join(props["runtime-level-dir"], safe_level_name) | |
end | |
CRAFTR_KEYS.each { |k| props.delete(k) } | |
props | |
end | |
def update_properties | |
remove_file("server.properties", :verbose => false) if file_exists("server.properties") | |
create_file "server.properties", :verbose => false do | |
lines = [] | |
lines << "# Automatically generated by craftr" | |
lines << "# Do not edit this file, it will just get overwritten" | |
lines << "# Make your changes in the server-config.yml file instead" | |
server_properties.each do |k, v| | |
lines << "#{k}=#{v.to_s}" | |
end | |
lines.join "\n" | |
end | |
end | |
def server_state | |
status_file = path(server_config["status-file"]) | |
if file_exists(status_file) | |
File.open(status_file) { |io| io.read.strip }.to_sym | |
else | |
:stopped | |
end | |
end | |
def screen_name | |
server_config["console-screen-name"] | |
end | |
def command_server(cmd) | |
run "screen -p 0 -S #{screen_name} -X stuff '#{cmd}\n'", :verbose => false | |
end | |
def save_if_running | |
return if !server_running | |
say "Server is running, forcing save in 5 seconds..." | |
command_server "say Warning - saving level in 5 seconds." | |
sleep 5 | |
say "Issuing save-all command. Wait 5 seconds..." | |
command_server "say Warning - saving level now." | |
command_server "save-all" | |
sleep 5 | |
say "World saved." | |
end | |
def save_locked | |
if server_running | |
say "Disabling saving..." | |
command_server "save-off" | |
sleep 1 | |
yield | |
say "Re-enabling saving..." | |
command_server "save-on" | |
sleep 1 | |
else | |
yield | |
end | |
end | |
def server_running | |
return server_state == :running | |
end | |
def ensure_server_running | |
case server_state | |
when :running | |
nil | |
when :stopped | |
halt "The server is not running right now." | |
when :crashed | |
halt "The server crashed without shutting down safely!" | |
else | |
halt "The server's status could not be determined!" | |
end | |
end | |
def ensure_server_shutdown | |
case server_state | |
when :running | |
halt "The server is currently running." | |
when :stopped | |
nil | |
when :crashed | |
halt "The server crashed without shutting down safely!" | |
else | |
halt "The server's status could not be determined!" | |
end | |
end | |
def ensure_server_offline | |
case server_state | |
when :running | |
halt "The server is currently running." | |
when :stopped | |
nil | |
when :crashed | |
say "Warning: The server crashed without shutting down safely!" | |
else | |
halt "The server's status could not be determined!" | |
end | |
end | |
end | |
Craftr.start |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment